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