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 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     public static Class<?> getCallerClass(final int depth) {
121         if (depth < 0) {
122             throw new IndexOutOfBoundsException(Integer.toString(depth));
123         }
124         // note that we need to add 1 to the depth value to compensate for this method, but not for the Method.invoke
125         // since Reflection.getCallerClass ignores the call to Method.invoke()
126         if (supportsFastReflection()) {
127             try {
128                 return (Class<?>) GET_CALLER_CLASS.invoke(null, depth + 1 + JDK_7u25_OFFSET);
129             } catch (final Exception e) {
130                 // theoretically this could happen if the caller class were native code
131                 LOGGER.error("Error in ReflectionUtil.getCallerClass({}).", depth, e);
132                 // TODO: return Object.class
133                 return null;
134             }
135         }
136         // TODO: SecurityManager-based version?
137         // slower fallback method using stack trace
138         final StackTraceElement element = getEquivalentStackTraceElement(depth + 1);
139         try {
140             return LoaderUtil.loadClass(element.getClassName());
141         } catch (final ClassNotFoundException e) {
142             LOGGER.error("Could not find class in ReflectionUtil.getCallerClass({}).", depth, e);
143         }
144         // TODO: return Object.class
145         return null;
146     }
147 
148     static StackTraceElement getEquivalentStackTraceElement(final int depth) {
149         // (MS) I tested the difference between using Throwable.getStackTrace() and Thread.getStackTrace(), and
150         // the version using Throwable was surprisingly faster! at least on Java 1.8. See ReflectionBenchmark.
151         final StackTraceElement[] elements = new Throwable().getStackTrace();
152         int i = 0;
153         for (final StackTraceElement element : elements) {
154             if (isValid(element)) {
155                 if (i == depth) {
156                     return element;
157                 }
158                 ++i;
159             }
160         }
161         LOGGER.error("Could not find an appropriate StackTraceElement at index {}", depth);
162         throw new IndexOutOfBoundsException(Integer.toString(depth));
163     }
164 
165     private static boolean isValid(final StackTraceElement element) {
166         // ignore native methods (oftentimes are repeated frames)
167         if (element.isNativeMethod()) {
168             return false;
169         }
170         final String cn = element.getClassName();
171         // ignore OpenJDK internal classes involved with reflective invocation
172         if (cn.startsWith("sun.reflect.")) {
173             return false;
174         }
175         final String mn = element.getMethodName();
176         // ignore use of reflection including:
177         // Method.invoke
178         // InvocationHandler.invoke
179         // Constructor.newInstance
180         if (cn.startsWith("java.lang.reflect.") && (mn.equals("invoke") || mn.equals("newInstance"))) {
181             return false;
182         }
183         // ignore Class.newInstance
184         if (cn.equals("java.lang.Class") && mn.equals("newInstance")) {
185             return false;
186         }
187         // ignore use of Java 1.7+ MethodHandle.invokeFoo() methods
188         if (cn.equals("java.lang.invoke.MethodHandle") && mn.startsWith("invoke")) {
189             return false;
190         }
191         // any others?
192         return true;
193     }
194 
195     // migrated from ClassLoaderContextSelector
196     public static Class<?> getCallerClass(final String fqcn) {
197         return getCallerClass(fqcn, Strings.EMPTY);
198     }
199 
200     // migrated from Log4jLoggerFactory
201     public static Class<?> getCallerClass(final String fqcn, final String pkg) {
202         if (supportsFastReflection()) {
203             boolean next = false;
204             Class<?> clazz;
205             for (int i = 2; null != (clazz = getCallerClass(i)); i++) {
206                 if (fqcn.equals(clazz.getName())) {
207                     next = true;
208                     continue;
209                 }
210                 if (next && clazz.getName().startsWith(pkg)) {
211                     return clazz;
212                 }
213             }
214             // TODO: return Object.class
215             return null;
216         }
217         if (SECURITY_MANAGER != null) {
218             return SECURITY_MANAGER.getCallerClass(fqcn, pkg);
219         }
220         try {
221             return LoaderUtil.loadClass(getCallerClassName(fqcn, pkg, new Throwable().getStackTrace()));
222         } catch (final ClassNotFoundException ignored) {
223             // no problem really
224         }
225         // TODO: return Object.class
226         return null;
227     }
228 
229     // added for use in LoggerAdapter implementations mainly
230     public static Class<?> getCallerClass(final Class<?> anchor) {
231         if (supportsFastReflection()) {
232             boolean next = false;
233             Class<?> clazz;
234             for (int i = 2; null != (clazz = getCallerClass(i)); i++) {
235                 if (anchor.equals(clazz)) {
236                     next = true;
237                     continue;
238                 }
239                 if (next) {
240                     return clazz;
241                 }
242             }
243             return Object.class;
244         }
245         if (SECURITY_MANAGER != null) {
246             return SECURITY_MANAGER.getCallerClass(anchor);
247         }
248         try {
249             return LoaderUtil.loadClass(getCallerClassName(anchor.getName(), Strings.EMPTY,
250                     new Throwable().getStackTrace()));
251         } catch (final ClassNotFoundException ignored) {
252             // no problem really
253         }
254         return Object.class;
255     }
256 
257     private static String getCallerClassName(final String fqcn, final String pkg, final StackTraceElement... elements) {
258         boolean next = false;
259         for (final StackTraceElement element : elements) {
260             final String className = element.getClassName();
261             if (className.equals(fqcn)) {
262                 next = true;
263                 continue;
264             }
265             if (next && className.startsWith(pkg)) {
266                 return className;
267             }
268         }
269         return Object.class.getName();
270     }
271 
272     // migrated from ThrowableProxy
273     public static Stack<Class<?>> getCurrentStackTrace() {
274         // benchmarks show that using the SecurityManager is much faster than looping through getCallerClass(int)
275         if (SECURITY_MANAGER != null) {
276             final Class<?>[] array = SECURITY_MANAGER.getClassContext();
277             final Stack<Class<?>> classes = new Stack<>();
278             classes.ensureCapacity(array.length);
279             for (final Class<?> clazz : array) {
280                 classes.push(clazz);
281             }
282             return classes;
283         }
284         // slower version using getCallerClass where we cannot use a SecurityManager
285         if (supportsFastReflection()) {
286             final Stack<Class<?>> classes = new Stack<>();
287             Class<?> clazz;
288             for (int i = 1; null != (clazz = getCallerClass(i)); i++) {
289                 classes.push(clazz);
290             }
291             return classes;
292         }
293         return new Stack<>();
294     }
295 
296     /**
297      * 
298      */
299     static final class PrivateSecurityManager extends SecurityManager {
300 
301         @Override
302         protected Class<?>[] getClassContext() {
303             return super.getClassContext();
304         }
305 
306         protected Class<?> getCallerClass(final String fqcn, final String pkg) {
307             boolean next = false;
308             for (final Class<?> clazz : getClassContext()) {
309                 if (fqcn.equals(clazz.getName())) {
310                     next = true;
311                     continue;
312                 }
313                 if (next && clazz.getName().startsWith(pkg)) {
314                     return clazz;
315                 }
316             }
317             // TODO: return Object.class
318             return null;
319         }
320 
321         protected Class<?> getCallerClass(final Class<?> anchor) {
322             boolean next = false;
323             for (final Class<?> clazz : getClassContext()) {
324                 if (anchor.equals(clazz)) {
325                     next = true;
326                     continue;
327                 }
328                 if (next) {
329                     return clazz;
330                 }
331             }
332             return Object.class;
333         }
334     }
335 }