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