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  import org.apache.logging.log4j.Logger;
23  import org.apache.logging.log4j.status.StatusLogger;
24  
25  /**
26   * <em>Consider this class private.</em> Provides various methods to determine the caller class. <h3>Background</h3>
27   * <p>
28   * This method, available only in the Oracle/Sun/OpenJDK implementations of the Java Virtual Machine, is a much more
29   * efficient mechanism for determining the {@link Class} of the caller of a particular method. When it is not available,
30   * a {@link SecurityManager} is the second-best option. When this is also not possible, the {@code StackTraceElement[]}
31   * returned by {@link Throwable#getStackTrace()} must be used, and its {@code String} class name converted to a
32   * {@code Class} using the slow {@link Class#forName} (which can add an extra microsecond or more for each invocation
33   * depending on the runtime ClassLoader hierarchy).
34   * </p>
35   * <p>
36   * During Java 8 development, the {@code sun.reflect.Reflection.getCallerClass(int)} was removed from OpenJDK, and this
37   * 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
38   * by one stack frame. This turned out to be beneficial for the survival of this API as the change broke hundreds of
39   * libraries and frameworks relying on the API which brought much more attention to the intended API removal.
40   * </p>
41   * <p>
42   * After much community backlash, the JDK team agreed to restore {@code getCallerClass(int)} and keep its existing
43   * behavior for the rest of Java 7. However, the method is deprecated in Java 8, and current Java 9 development has not
44   * addressed this API. Therefore, the functionality of this class cannot be relied upon for all future versions of Java.
45   * 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
46   * environments may fall back to using {@link Throwable#getStackTrace()} which is significantly slower due to
47   * examination of every virtual frame of execution.
48   * </p>
49   */
50  public final class ReflectionUtil {
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 Logger LOGGER = StatusLogger.getLogger();
57      private static final boolean SUN_REFLECTION_SUPPORTED;
58      private static final Method GET_CALLER_CLASS;
59      private static final PrivateSecurityManager SECURITY_MANAGER;
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                  LOGGER.warn("Unexpected return value from Reflection.getCallerClass(): {}", test1);
71                  getCallerClass = null;
72                  java7u25CompensationOffset = -1;
73              } else {
74                  o = getCallerClass.invoke(null, 1);
75                  if (o == sunReflectionClass) {
76                      LOGGER.warn("You are using Java 1.7.0_25 which has a broken implementation of "
77                              + "Reflection.getCallerClass.");
78                      LOGGER.warn("You should upgrade to at least Java 1.7.0_40 or later.");
79                      LOGGER.debug("Using stack depth compensation offset of 1 due to Java 7u25.");
80                      java7u25CompensationOffset = 1;
81                  }
82              }
83          } catch (final Exception | LinkageError e) {
84              LOGGER.info("sun.reflect.Reflection.getCallerClass is not supported. "
85                      + "ReflectionUtil.getCallerClass will be much slower due to this.", e);
86              getCallerClass = null;
87              java7u25CompensationOffset = -1;
88          }
89  
90          SUN_REFLECTION_SUPPORTED = getCallerClass != null;
91          GET_CALLER_CLASS = getCallerClass;
92          JDK_7u25_OFFSET = java7u25CompensationOffset;
93  
94          PrivateSecurityManager psm;
95          try {
96              final SecurityManager sm = System.getSecurityManager();
97              if (sm != null) {
98                  sm.checkPermission(new RuntimePermission("createSecurityManager"));
99              }
100             psm = new PrivateSecurityManager();
101         } catch (final SecurityException ignored) {
102             LOGGER.debug("Not allowed to create SecurityManager. "
103                     + "Falling back to slowest ReflectionUtil implementation.");
104             psm = null;
105         }
106         SECURITY_MANAGER = psm;
107     }
108 
109     private ReflectionUtil() {
110     }
111 
112     public static boolean supportsFastReflection() {
113         return SUN_REFLECTION_SUPPORTED;
114     }
115 
116     // TODO: return Object.class instead of null (though it will have a null ClassLoader)
117     // (MS) I believe this would work without any modifications elsewhere, but I could be wrong
118 
119     // migrated from ReflectiveCallerClassUtility
120     @PerformanceSensitive
121     public static Class<?> getCallerClass(final int depth) {
122         if (depth < 0) {
123             throw new IndexOutOfBoundsException(Integer.toString(depth));
124         }
125         // note that we need to add 1 to the depth value to compensate for this method, but not for the Method.invoke
126         // since Reflection.getCallerClass ignores the call to Method.invoke()
127         if (supportsFastReflection()) {
128             try {
129                 return (Class<?>) GET_CALLER_CLASS.invoke(null, depth + 1 + JDK_7u25_OFFSET);
130             } catch (final Exception e) {
131                 // theoretically this could happen if the caller class were native code
132                 LOGGER.error("Error in ReflectionUtil.getCallerClass({}).", depth, e);
133                 // TODO: return Object.class
134                 return null;
135             }
136         }
137         // TODO: SecurityManager-based version?
138         // slower fallback method using stack trace
139         final StackTraceElement element = getEquivalentStackTraceElement(depth + 1);
140         try {
141             return LoaderUtil.loadClass(element.getClassName());
142         } catch (final ClassNotFoundException e) {
143             LOGGER.error("Could not find class in ReflectionUtil.getCallerClass({}).", depth, e);
144         }
145         // TODO: return Object.class
146         return null;
147     }
148 
149     static StackTraceElement getEquivalentStackTraceElement(final int depth) {
150         // (MS) I tested the difference between using Throwable.getStackTrace() and Thread.getStackTrace(), and
151         // the version using Throwable was surprisingly faster! at least on Java 1.8. See ReflectionBenchmark.
152         final StackTraceElement[] elements = new Throwable().getStackTrace();
153         int i = 0;
154         for (final StackTraceElement element : elements) {
155             if (isValid(element)) {
156                 if (i == depth) {
157                     return element;
158                 }
159                 ++i;
160             }
161         }
162         LOGGER.error("Could not find an appropriate StackTraceElement at index {}", depth);
163         throw new IndexOutOfBoundsException(Integer.toString(depth));
164     }
165 
166     private static boolean isValid(final StackTraceElement element) {
167         // ignore native methods (oftentimes are repeated frames)
168         if (element.isNativeMethod()) {
169             return false;
170         }
171         final String cn = element.getClassName();
172         // ignore OpenJDK internal classes involved with reflective invocation
173         if (cn.startsWith("sun.reflect.")) {
174             return false;
175         }
176         final String mn = element.getMethodName();
177         // ignore use of reflection including:
178         // Method.invoke
179         // InvocationHandler.invoke
180         // Constructor.newInstance
181         if (cn.startsWith("java.lang.reflect.") && (mn.equals("invoke") || mn.equals("newInstance"))) {
182             return false;
183         }
184         // ignore use of Java 1.9+ reflection classes
185         if (cn.startsWith("jdk.internal.reflect.")) {
186             return false;
187         }
188         // ignore Class.newInstance
189         if (cn.equals("java.lang.Class") && mn.equals("newInstance")) {
190             return false;
191         }
192         // ignore use of Java 1.7+ MethodHandle.invokeFoo() methods
193         if (cn.equals("java.lang.invoke.MethodHandle") && mn.startsWith("invoke")) {
194             return false;
195         }
196         // any others?
197         return true;
198     }
199 
200     // migrated from ClassLoaderContextSelector
201     @PerformanceSensitive
202     public static Class<?> getCallerClass(final String fqcn) {
203         return getCallerClass(fqcn, Strings.EMPTY);
204     }
205 
206     // migrated from Log4jLoggerFactory
207     @PerformanceSensitive
208     public static Class<?> getCallerClass(final String fqcn, final String pkg) {
209         if (supportsFastReflection()) {
210             boolean next = false;
211             Class<?> clazz;
212             for (int i = 2; null != (clazz = getCallerClass(i)); i++) {
213                 if (fqcn.equals(clazz.getName())) {
214                     next = true;
215                     continue;
216                 }
217                 if (next && clazz.getName().startsWith(pkg)) {
218                     return clazz;
219                 }
220             }
221             // TODO: return Object.class
222             return null;
223         }
224         if (SECURITY_MANAGER != null) {
225             return SECURITY_MANAGER.getCallerClass(fqcn, pkg);
226         }
227         try {
228             return LoaderUtil.loadClass(getCallerClassName(fqcn, pkg, new Throwable().getStackTrace()));
229         } catch (final ClassNotFoundException ignored) {
230             // no problem really
231         }
232         // TODO: return Object.class
233         return null;
234     }
235 
236     // added for use in LoggerAdapter implementations mainly
237     @PerformanceSensitive
238     public static Class<?> getCallerClass(final Class<?> anchor) {
239         if (supportsFastReflection()) {
240             boolean next = false;
241             Class<?> clazz;
242             for (int i = 2; null != (clazz = getCallerClass(i)); i++) {
243                 if (anchor.equals(clazz)) {
244                     next = true;
245                     continue;
246                 }
247                 if (next) {
248                     return clazz;
249                 }
250             }
251             return Object.class;
252         }
253         if (SECURITY_MANAGER != null) {
254             return SECURITY_MANAGER.getCallerClass(anchor);
255         }
256         try {
257             return LoaderUtil.loadClass(getCallerClassName(anchor.getName(), Strings.EMPTY,
258                     new Throwable().getStackTrace()));
259         } catch (final ClassNotFoundException ignored) {
260             // no problem really
261         }
262         return Object.class;
263     }
264 
265     private static String getCallerClassName(final String fqcn, final String pkg, final StackTraceElement... elements) {
266         boolean next = false;
267         for (final StackTraceElement element : elements) {
268             final String className = element.getClassName();
269             if (className.equals(fqcn)) {
270                 next = true;
271                 continue;
272             }
273             if (next && className.startsWith(pkg)) {
274                 return className;
275             }
276         }
277         return Object.class.getName();
278     }
279 
280     // migrated from ThrowableProxy
281     @PerformanceSensitive
282     public static Stack<Class<?>> getCurrentStackTrace() {
283         // benchmarks show that using the SecurityManager is much faster than looping through getCallerClass(int)
284         if (SECURITY_MANAGER != null) {
285             final Class<?>[] array = SECURITY_MANAGER.getClassContext();
286             final Stack<Class<?>> classes = new Stack<>();
287             classes.ensureCapacity(array.length);
288             for (final Class<?> clazz : array) {
289                 classes.push(clazz);
290             }
291             return classes;
292         }
293         // slower version using getCallerClass where we cannot use a SecurityManager
294         if (supportsFastReflection()) {
295             final Stack<Class<?>> classes = new Stack<>();
296             Class<?> clazz;
297             for (int i = 1; null != (clazz = getCallerClass(i)); i++) {
298                 classes.push(clazz);
299             }
300             return classes;
301         }
302         return new Stack<>();
303     }
304 
305     /**
306      * 
307      */
308     static final class PrivateSecurityManager extends SecurityManager {
309 
310         @Override
311         protected Class<?>[] getClassContext() {
312             return super.getClassContext();
313         }
314 
315         protected Class<?> getCallerClass(final String fqcn, final String pkg) {
316             boolean next = false;
317             for (final Class<?> clazz : getClassContext()) {
318                 if (fqcn.equals(clazz.getName())) {
319                     next = true;
320                     continue;
321                 }
322                 if (next && clazz.getName().startsWith(pkg)) {
323                     return clazz;
324                 }
325             }
326             // TODO: return Object.class
327             return null;
328         }
329 
330         protected Class<?> getCallerClass(final Class<?> anchor) {
331             boolean next = false;
332             for (final Class<?> clazz : getClassContext()) {
333                 if (anchor.equals(clazz)) {
334                     next = true;
335                     continue;
336                 }
337                 if (next) {
338                     return clazz;
339                 }
340             }
341             return Object.class;
342         }
343     }
344 }