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 }