View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.util;
18  
19  import java.lang.reflect.Method;
20  import java.util.Stack;
21  
22  /**
23   * <em>Consider this class private.</em> Provides various methods to determine the caller class. <h3>Background</h3>
24   * <p>
25   * This method, available only in the Oracle/Sun/OpenJDK implementations of the Java Virtual Machine, is a much more
26   * efficient mechanism for determining the {@link Class} of the caller of a particular method. When it is not available,
27   * a {@link SecurityManager} is the second-best option. When this is also not possible, the {@code StackTraceElement[]}
28   * returned by {@link Throwable#getStackTrace()} must be used, and its {@code String} class name converted to a
29   * {@code Class} using the slow {@link Class#forName} (which can add an extra microsecond or more for each invocation
30   * depending on the runtime ClassLoader hierarchy).
31   * </p>
32   * <p>
33   * During Java 8 development, the {@code sun.reflect.Reflection.getCallerClass(int)} was removed from OpenJDK, and this
34   * change was back-ported to Java 7 in version 1.7.0_25 which changed the behavior of the call and caused it to be off
35   * by one stack frame. This turned out to be beneficial for the survival of this API as the change broke hundreds of
36   * libraries and frameworks relying on the API which brought much more attention to the intended API removal.
37   * </p>
38   * <p>
39   * After much community backlash, the JDK team agreed to restore {@code getCallerClass(int)} and keep its existing
40   * behavior for the rest of Java 7. However, the method is deprecated in Java 8, and current Java 9 development has not
41   * addressed this API. Therefore, the functionality of this class cannot be relied upon for all future versions of Java.
42   * It does, however, work just fine in Sun JDK 1.6, OpenJDK 1.6, Oracle/OpenJDK 1.7, and Oracle/OpenJDK 1.8. Other Java
43   * environments may fall back to using {@link Throwable#getStackTrace()} which is significantly slower due to
44   * examination of every virtual frame of execution.
45   * </p>
46   */
47  public final class StackLocator {
48  
49      private static PrivateSecurityManager SECURITY_MANAGER;
50  
51      // Checkstyle Suppress: the lower-case 'u' ticks off CheckStyle...
52      // CHECKSTYLE:OFF
53      static final int JDK_7u25_OFFSET;
54      // CHECKSTYLE:OFF
55  
56      private static final boolean SUN_REFLECTION_SUPPORTED;
57      private static final Method GET_CALLER_CLASS;
58  
59      private static final StackLocator INSTANCE;
60  
61      static {
62          Method getCallerClass;
63          int java7u25CompensationOffset = 0;
64          try {
65              final Class<?> sunReflectionClass = LoaderUtil.loadClass("sun.reflect.Reflection");
66              getCallerClass = sunReflectionClass.getDeclaredMethod("getCallerClass", int.class);
67              Object o = getCallerClass.invoke(null, 0);
68              final Object test1 = getCallerClass.invoke(null, 0);
69              if (o == null || o != sunReflectionClass) {
70                  getCallerClass = null;
71                  java7u25CompensationOffset = -1;
72              } else {
73                  o = getCallerClass.invoke(null, 1);
74                  if (o == sunReflectionClass) {
75                      System.out.println("WARNING: Java 1.7.0_25 is in use which has a broken implementation of Reflection.getCallerClass(). " +
76                          " Please consider upgrading to Java 1.7.0_40 or later.");
77                      java7u25CompensationOffset = 1;
78                  }
79              }
80          } catch (final Exception | LinkageError e) {
81              System.out.println("WARNING: sun.reflect.Reflection.getCallerClass is not supported. This will impact performance.");
82              getCallerClass = null;
83              java7u25CompensationOffset = -1;
84          }
85  
86          SUN_REFLECTION_SUPPORTED = getCallerClass != null;
87          GET_CALLER_CLASS = getCallerClass;
88          JDK_7u25_OFFSET = java7u25CompensationOffset;
89  
90          INSTANCE = new StackLocator();
91      }
92  
93      public static StackLocator getInstance() {
94          return INSTANCE;
95      }
96  
97      private StackLocator() {
98      }
99  
100     // TODO: return Object.class instead of null (though it will have a null ClassLoader)
101     // (MS) I believe this would work without any modifications elsewhere, but I could be wrong
102 
103     // migrated from ReflectiveCallerClassUtility
104     @PerformanceSensitive
105     public Class<?> getCallerClass(final int depth) {
106         if (depth < 0) {
107             throw new IndexOutOfBoundsException(Integer.toString(depth));
108         }
109         // note that we need to add 1 to the depth value to compensate for this method, but not for the Method.invoke
110         // since Reflection.getCallerClass ignores the call to Method.invoke()
111         try {
112             return (Class<?>) GET_CALLER_CLASS.invoke(null, depth + 1 + JDK_7u25_OFFSET);
113         } catch (final Exception e) {
114             // theoretically this could happen if the caller class were native code
115             // TODO: return Object.class
116             return null;
117         }
118     }
119 
120     // migrated from Log4jLoggerFactory
121     @PerformanceSensitive
122     public Class<?> getCallerClass(final String fqcn, final String pkg) {
123         boolean next = false;
124         Class<?> clazz;
125         for (int i = 2; null != (clazz = getCallerClass(i)); i++) {
126             if (fqcn.equals(clazz.getName())) {
127                 next = true;
128                 continue;
129             }
130             if (next && clazz.getName().startsWith(pkg)) {
131                 return clazz;
132             }
133         }
134         // TODO: return Object.class
135         return null;
136     }
137 
138     // added for use in LoggerAdapter implementations mainly
139     @PerformanceSensitive
140     public Class<?> getCallerClass(final Class<?> anchor) {
141         boolean next = false;
142         Class<?> clazz;
143         for (int i = 2; null != (clazz = getCallerClass(i)); i++) {
144             if (anchor.equals(clazz)) {
145                 next = true;
146                 continue;
147             }
148             if (next) {
149                 return clazz;
150             }
151         }
152         return Object.class;
153     }
154 
155     // migrated from ThrowableProxy
156     @PerformanceSensitive
157     public Stack<Class<?>> getCurrentStackTrace() {
158         // benchmarks show that using the SecurityManager is much faster than looping through getCallerClass(int)
159         if (getSecurityManager() != null) {
160             final Class<?>[] array = getSecurityManager().getClassContext();
161             final Stack<Class<?>> classes = new Stack<>();
162             classes.ensureCapacity(array.length);
163             for (final Class<?> clazz : array) {
164                 classes.push(clazz);
165             }
166             return classes;
167         }
168         // slower version using getCallerClass where we cannot use a SecurityManager
169         final Stack<Class<?>> classes = new Stack<>();
170         Class<?> clazz;
171         for (int i = 1; null != (clazz = getCallerClass(i)); i++) {
172             classes.push(clazz);
173         }
174         return classes;
175     }
176 
177     public StackTraceElement calcLocation(final String fqcnOfLogger) {
178         if (fqcnOfLogger == null) {
179             return null;
180         }
181         // LOG4J2-1029 new Throwable().getStackTrace is faster than Thread.currentThread().getStackTrace().
182         final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
183         StackTraceElement last = null;
184         for (int i = stackTrace.length - 1; i > 0; i--) {
185             final String className = stackTrace[i].getClassName();
186             if (fqcnOfLogger.equals(className)) {
187                 return last;
188             }
189             last = stackTrace[i];
190         }
191         return null;
192     }
193 
194     public StackTraceElement getStackTraceElement(final int depth) {
195         // (MS) I tested the difference between using Throwable.getStackTrace() and Thread.getStackTrace(), and
196         // the version using Throwable was surprisingly faster! at least on Java 1.8. See ReflectionBenchmark.
197         final StackTraceElement[] elements = new Throwable().getStackTrace();
198         int i = 0;
199         for (final StackTraceElement element : elements) {
200             if (isValid(element)) {
201                 if (i == depth) {
202                     return element;
203                 }
204                 ++i;
205             }
206         }
207         throw new IndexOutOfBoundsException(Integer.toString(depth));
208     }
209 
210     private boolean isValid(final StackTraceElement element) {
211         // ignore native methods (oftentimes are repeated frames)
212         if (element.isNativeMethod()) {
213             return false;
214         }
215         final String cn = element.getClassName();
216         // ignore OpenJDK internal classes involved with reflective invocation
217         if (cn.startsWith("sun.reflect.")) {
218             return false;
219         }
220         final String mn = element.getMethodName();
221         // ignore use of reflection including:
222         // Method.invoke
223         // InvocationHandler.invoke
224         // Constructor.newInstance
225         if (cn.startsWith("java.lang.reflect.") && (mn.equals("invoke") || mn.equals("newInstance"))) {
226             return false;
227         }
228         // ignore use of Java 1.9+ reflection classes
229         if (cn.startsWith("jdk.internal.reflect.")) {
230             return false;
231         }
232         // ignore Class.newInstance
233         if (cn.equals("java.lang.Class") && mn.equals("newInstance")) {
234             return false;
235         }
236         // ignore use of Java 1.7+ MethodHandle.invokeFoo() methods
237         if (cn.equals("java.lang.invoke.MethodHandle") && mn.startsWith("invoke")) {
238             return false;
239         }
240         // any others?
241         return true;
242     }
243 
244     protected PrivateSecurityManager getSecurityManager() {
245         return SECURITY_MANAGER;
246     }
247 
248     private static final class PrivateSecurityManager extends SecurityManager {
249 
250         @Override
251         protected Class<?>[] getClassContext() {
252             return super.getClassContext();
253         }
254 
255         protected Class<?> getCallerClass(final String fqcn, final String pkg) {
256             boolean next = false;
257             for (final Class<?> clazz : getClassContext()) {
258                 if (fqcn.equals(clazz.getName())) {
259                     next = true;
260                     continue;
261                 }
262                 if (next && clazz.getName().startsWith(pkg)) {
263                     return clazz;
264                 }
265             }
266             // TODO: return Object.class
267             return null;
268         }
269 
270         protected Class<?> getCallerClass(final Class<?> anchor) {
271             boolean next = false;
272             for (final Class<?> clazz : getClassContext()) {
273                 if (anchor.equals(clazz)) {
274                     next = true;
275                     continue;
276                 }
277                 if (next) {
278                     return clazz;
279                 }
280             }
281             return Object.class;
282         }
283     }
284 }