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  import java.util.function.Predicate;
22  
23  /**
24   * <em>Consider this class private.</em> Provides various methods to determine the caller class. <h3>Background</h3>
25   * <p>
26   * This method, available only in the Oracle/Sun/OpenJDK implementations of the Java Virtual Machine, is a much more
27   * efficient mechanism for determining the {@link Class} of the caller of a particular method. When it is not available,
28   * a {@link SecurityManager} is the second-best option. When this is also not possible, the {@code StackTraceElement[]}
29   * returned by {@link Throwable#getStackTrace()} must be used, and its {@code String} class name converted to a
30   * {@code Class} using the slow {@link Class#forName} (which can add an extra microsecond or more for each invocation
31   * depending on the runtime ClassLoader hierarchy).
32   * </p>
33   * <p>
34   * During Java 8 development, the {@code sun.reflect.Reflection.getCallerClass(int)} was removed from OpenJDK, and this
35   * 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
36   * by one stack frame. This turned out to be beneficial for the survival of this API as the change broke hundreds of
37   * libraries and frameworks relying on the API which brought much more attention to the intended API removal.
38   * </p>
39   * <p>
40   * After much community backlash, the JDK team agreed to restore {@code getCallerClass(int)} and keep its existing
41   * behavior for the rest of Java 7. However, the method is deprecated in Java 8, and current Java 9 development has not
42   * addressed this API. Therefore, the functionality of this class cannot be relied upon for all future versions of Java.
43   * 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
44   * environments may fall back to using {@link Throwable#getStackTrace()} which is significantly slower due to
45   * examination of every virtual frame of execution.
46   * </p>
47   */
48  public final class StackLocator {
49  
50      // Checkstyle Suppress: the lower-case 'u' ticks off CheckStyle...
51      // CHECKSTYLE:OFF
52      static final int JDK_7u25_OFFSET;
53      // CHECKSTYLE:OFF
54  
55      private static final Method GET_CALLER_CLASS;
56  
57      private static final StackLocator INSTANCE;
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              getCallerClass.invoke(null, 0);
67              if (o == null || o != sunReflectionClass) {
68                  getCallerClass = null;
69                  java7u25CompensationOffset = -1;
70              } else {
71                  o = getCallerClass.invoke(null, 1);
72                  if (o == sunReflectionClass) {
73                      System.out.println("WARNING: Java 1.7.0_25 is in use which has a broken implementation of Reflection.getCallerClass(). " +
74                          " Please consider upgrading to Java 1.7.0_40 or later.");
75                      java7u25CompensationOffset = 1;
76                  }
77              }
78          } catch (final Exception | LinkageError e) {
79              System.out.println("WARNING: sun.reflect.Reflection.getCallerClass is not supported. This will impact performance.");
80              getCallerClass = null;
81              java7u25CompensationOffset = -1;
82          }
83  
84          GET_CALLER_CLASS = getCallerClass;
85          JDK_7u25_OFFSET = java7u25CompensationOffset;
86  
87          INSTANCE = new StackLocator();
88      }
89  
90      public static StackLocator getInstance() {
91          return INSTANCE;
92      }
93  
94      private StackLocator() {
95      }
96  
97      // TODO: return Object.class instead of null (though it will have a null ClassLoader)
98      // (MS) I believe this would work without any modifications elsewhere, but I could be wrong
99  
100     @PerformanceSensitive
101     public Class<?> getCallerClass(final Class<?> sentinelClass, final Predicate<Class<?>> callerPredicate) {
102         if (sentinelClass == null) {
103             throw new IllegalArgumentException("sentinelClass cannot be null");
104         }
105         if (callerPredicate == null) {
106             throw new IllegalArgumentException("callerPredicate cannot be null");
107         }
108         boolean foundSentinel = false;
109         Class<?> clazz;
110         for (int i = 2; null != (clazz = getCallerClass(i)); i++) {
111             if (sentinelClass.equals(clazz)) {
112                 foundSentinel = true;
113             } else if (foundSentinel && callerPredicate.test(clazz)) {
114                 return clazz;
115             }
116         }
117         return null;
118     }
119 
120     // migrated from ReflectiveCallerClassUtility
121     @PerformanceSensitive
122     public Class<?> getCallerClass(final int depth) {
123         if (depth < 0) {
124             throw new IndexOutOfBoundsException(Integer.toString(depth));
125         }
126         if (GET_CALLER_CLASS == null) {
127             return null;
128         }
129         // note that we need to add 1 to the depth value to compensate for this method, but not for the Method.invoke
130         // since Reflection.getCallerClass ignores the call to Method.invoke()
131         try {
132             return (Class<?>) GET_CALLER_CLASS.invoke(null, depth + 1 + JDK_7u25_OFFSET);
133         } catch (final Exception e) {
134             // theoretically this could happen if the caller class were native code
135             // TODO: return Object.class
136             return null;
137         }
138     }
139 
140     // migrated from Log4jLoggerFactory
141     @PerformanceSensitive
142     public Class<?> getCallerClass(final String fqcn, final String pkg) {
143         boolean next = false;
144         Class<?> clazz;
145         for (int i = 2; null != (clazz = getCallerClass(i)); i++) {
146             if (fqcn.equals(clazz.getName())) {
147                 next = true;
148                 continue;
149             }
150             if (next && clazz.getName().startsWith(pkg)) {
151                 return clazz;
152             }
153         }
154         // TODO: return Object.class
155         return null;
156     }
157 
158     // added for use in LoggerAdapter implementations mainly
159     @PerformanceSensitive
160     public Class<?> getCallerClass(final Class<?> anchor) {
161         boolean next = false;
162         Class<?> clazz;
163         for (int i = 2; null != (clazz = getCallerClass(i)); i++) {
164             if (anchor.equals(clazz)) {
165                 next = true;
166                 continue;
167             }
168             if (next) {
169                 return clazz;
170             }
171         }
172         return Object.class;
173     }
174 
175     // migrated from ThrowableProxy
176     @PerformanceSensitive
177     public Stack<Class<?>> getCurrentStackTrace() {
178         // benchmarks show that using the SecurityManager is much faster than looping through getCallerClass(int)
179         if (PrivateSecurityManagerStackTraceUtil.isEnabled()) {
180             return PrivateSecurityManagerStackTraceUtil.getCurrentStackTrace();
181         }
182         // slower version using getCallerClass where we cannot use a SecurityManager
183         final Stack<Class<?>> classes = new Stack<>();
184         Class<?> clazz;
185         for (int i = 1; null != (clazz = getCallerClass(i)); i++) {
186             classes.push(clazz);
187         }
188         return classes;
189     }
190 
191     public StackTraceElement calcLocation(final String fqcnOfLogger) {
192         if (fqcnOfLogger == null) {
193             return null;
194         }
195         // LOG4J2-1029 new Throwable().getStackTrace is faster than Thread.currentThread().getStackTrace().
196         final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
197         boolean found = false;
198         for (int i = 0; i < stackTrace.length; i++) {
199             final String className = stackTrace[i].getClassName();
200             if (fqcnOfLogger.equals(className)) {
201 
202                 found = true;
203                 continue;
204             }
205             if (found && !fqcnOfLogger.equals(className)) {
206                 return stackTrace[i];
207             }
208         }
209         return null;
210     }
211 
212     public StackTraceElement getStackTraceElement(final int depth) {
213         // (MS) I tested the difference between using Throwable.getStackTrace() and Thread.getStackTrace(), and
214         // the version using Throwable was surprisingly faster! at least on Java 1.8. See ReflectionBenchmark.
215         final StackTraceElement[] elements = new Throwable().getStackTrace();
216         int i = 0;
217         for (final StackTraceElement element : elements) {
218             if (isValid(element)) {
219                 if (i == depth) {
220                     return element;
221                 }
222                 ++i;
223             }
224         }
225         throw new IndexOutOfBoundsException(Integer.toString(depth));
226     }
227 
228     private boolean isValid(final StackTraceElement element) {
229         // ignore native methods (oftentimes are repeated frames)
230         if (element.isNativeMethod()) {
231             return false;
232         }
233         final String cn = element.getClassName();
234         // ignore OpenJDK internal classes involved with reflective invocation
235         if (cn.startsWith("sun.reflect.")) {
236             return false;
237         }
238         final String mn = element.getMethodName();
239         // ignore use of reflection including:
240         // Method.invoke
241         // InvocationHandler.invoke
242         // Constructor.newInstance
243         if (cn.startsWith("java.lang.reflect.") && (mn.equals("invoke") || mn.equals("newInstance"))) {
244             return false;
245         }
246         // ignore use of Java 1.9+ reflection classes
247         if (cn.startsWith("jdk.internal.reflect.")) {
248             return false;
249         }
250         // ignore Class.newInstance
251         if (cn.equals("java.lang.Class") && mn.equals("newInstance")) {
252             return false;
253         }
254         // ignore use of Java 1.7+ MethodHandle.invokeFoo() methods
255         if (cn.equals("java.lang.invoke.MethodHandle") && mn.startsWith("invoke")) {
256             return false;
257         }
258         // any others?
259         return true;
260     }
261 }