001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache license, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the license for the specific language governing permissions and
015 * limitations under the license.
016 */
017package org.apache.logging.log4j.util;
018
019import java.lang.reflect.Method;
020import java.util.ArrayDeque;
021import java.util.Deque;
022import java.util.function.Predicate;
023
024/**
025 * <em>Consider this class private.</em> Provides various methods to determine the caller class. <h3>Background</h3>
026 * <p>
027 * This method, available only in the Oracle/Sun/OpenJDK implementations of the Java Virtual Machine, is a much more
028 * efficient mechanism for determining the {@link Class} of the caller of a particular method. When it is not available,
029 * a {@link SecurityManager} is the second-best option. When this is also not possible, the {@code StackTraceElement[]}
030 * returned by {@link Throwable#getStackTrace()} must be used, and its {@code String} class name converted to a
031 * {@code Class} using the slow {@link Class#forName} (which can add an extra microsecond or more for each invocation
032 * depending on the runtime ClassLoader hierarchy).
033 * </p>
034 * <p>
035 * During Java 8 development, the {@code sun.reflect.Reflection.getCallerClass(int)} was removed from OpenJDK, and this
036 * 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
037 * by one stack frame. This turned out to be beneficial for the survival of this API as the change broke hundreds of
038 * libraries and frameworks relying on the API which brought much more attention to the intended API removal.
039 * </p>
040 * <p>
041 * After much community backlash, the JDK team agreed to restore {@code getCallerClass(int)} and keep its existing
042 * behavior for the rest of Java 7. However, the method is deprecated in Java 8, and current Java 9 development has not
043 * addressed this API. Therefore, the functionality of this class cannot be relied upon for all future versions of Java.
044 * 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
045 * environments may fall back to using {@link Throwable#getStackTrace()} which is significantly slower due to
046 * examination of every virtual frame of execution.
047 * </p>
048 */
049public final class StackLocator {
050
051    /** TODO Consider removing now that we require Java 8. */
052    static final int JDK_7U25_OFFSET;
053
054    private static final Method GET_CALLER_CLASS_METHOD;
055
056    private static final StackLocator INSTANCE;
057    
058    /** TODO: Use Object.class. */
059    private static final Class<?> DEFAULT_CALLER_CLASS = null;
060
061    static {
062        Method getCallerClassMethod;
063        int java7u25CompensationOffset = 0;
064        try {
065            final Class<?> sunReflectionClass = LoaderUtil.loadClass("sun.reflect.Reflection");
066            getCallerClassMethod = sunReflectionClass.getDeclaredMethod("getCallerClass", int.class);
067            Object o = getCallerClassMethod.invoke(null, 0);
068            getCallerClassMethod.invoke(null, 0);
069            if (o == null || o != sunReflectionClass) {
070                getCallerClassMethod = null;
071                java7u25CompensationOffset = -1;
072            } else {
073                o = getCallerClassMethod.invoke(null, 1);
074                if (o == sunReflectionClass) {
075                    System.out.println("WARNING: Unexpected result from sun.reflect.Reflection.getCallerClass(int), adjusting offset for future calls.");
076                    java7u25CompensationOffset = 1;
077                }
078            }
079        } catch (final Exception | LinkageError e) {
080            System.out.println("WARNING: sun.reflect.Reflection.getCallerClass is not supported. This will impact performance.");
081            getCallerClassMethod = null;
082            java7u25CompensationOffset = -1;
083        }
084
085        GET_CALLER_CLASS_METHOD = getCallerClassMethod;
086        JDK_7U25_OFFSET = java7u25CompensationOffset;
087        INSTANCE = new StackLocator();
088    }
089
090    /**
091     * Gets the singleton instance.
092     *
093     * @return the singleton instance.
094     */
095    public static StackLocator getInstance() {
096        return INSTANCE;
097    }
098
099    private StackLocator() {
100    }
101
102    // TODO: return Object.class instead of null (though it will have a null ClassLoader)
103    // (MS) I believe this would work without any modifications elsewhere, but I could be wrong
104
105    @PerformanceSensitive
106    public Class<?> getCallerClass(final Class<?> sentinelClass, final Predicate<Class<?>> callerPredicate) {
107        if (sentinelClass == null) {
108            throw new IllegalArgumentException("sentinelClass cannot be null");
109        }
110        if (callerPredicate == null) {
111            throw new IllegalArgumentException("callerPredicate cannot be null");
112        }
113        boolean foundSentinel = false;
114        Class<?> clazz;
115        for (int i = 2; null != (clazz = getCallerClass(i)); i++) {
116            if (sentinelClass.equals(clazz)) {
117                foundSentinel = true;
118            } else if (foundSentinel && callerPredicate.test(clazz)) {
119                return clazz;
120            }
121        }
122        return DEFAULT_CALLER_CLASS;
123    }
124
125    /**
126     * Gets the Class of the method that called <em>this</em> method at the location up the call stack by the given stack
127     * frame depth.
128     * <p>
129     * This method returns {@code null} if:
130     * </p>
131     * <ul>
132     * <li>{@code sun.reflect.Reflection.getCallerClass(int)} is not present.</li>
133     * <li>An exception is caught calling {@code sun.reflect.Reflection.getCallerClass(int)}.</li>
134     * </ul>
135     *
136     * @param depth The stack frame count to walk.
137     * @return A class or null.
138     * @throws IndexOutOfBoundsException if depth is negative.
139     */
140    // migrated from ReflectiveCallerClassUtility
141    @PerformanceSensitive
142    public Class<?> getCallerClass(final int depth) {
143        if (depth < 0) {
144            throw new IndexOutOfBoundsException(Integer.toString(depth));
145        }
146        if (GET_CALLER_CLASS_METHOD == null) {
147            return DEFAULT_CALLER_CLASS;
148        }
149        // note that we need to add 1 to the depth value to compensate for this method, but not for the Method.invoke
150        // since Reflection.getCallerClass ignores the call to Method.invoke()
151        try {
152            return (Class<?>) GET_CALLER_CLASS_METHOD.invoke(null, depth + 1 + JDK_7U25_OFFSET);
153        } catch (final Exception e) {
154            // theoretically this could happen if the caller class were native code
155            // TODO: return Object.class
156            return DEFAULT_CALLER_CLASS;
157        }
158    }
159
160    // migrated from Log4jLoggerFactory
161    @PerformanceSensitive
162    public Class<?> getCallerClass(final String fqcn, final String pkg) {
163        boolean next = false;
164        Class<?> clazz;
165        for (int i = 2; null != (clazz = getCallerClass(i)); i++) {
166            if (fqcn.equals(clazz.getName())) {
167                next = true;
168                continue;
169            }
170            if (next && clazz.getName().startsWith(pkg)) {
171                return clazz;
172            }
173        }
174        // TODO: return Object.class
175        return DEFAULT_CALLER_CLASS;
176    }
177
178    // added for use in LoggerAdapter implementations mainly
179    @PerformanceSensitive
180    public Class<?> getCallerClass(final Class<?> anchor) {
181        boolean next = false;
182        Class<?> clazz;
183        for (int i = 2; null != (clazz = getCallerClass(i)); i++) {
184            if (anchor.equals(clazz)) {
185                next = true;
186                continue;
187            }
188            if (next) {
189                return clazz;
190            }
191        }
192        return Object.class;
193    }
194
195    // migrated from ThrowableProxy
196    @PerformanceSensitive
197    public Deque<Class<?>> getCurrentStackTrace() {
198        // benchmarks show that using the SecurityManager is much faster than looping through getCallerClass(int)
199        if (PrivateSecurityManagerStackTraceUtil.isEnabled()) {
200            return PrivateSecurityManagerStackTraceUtil.getCurrentStackTrace();
201        }
202        // slower version using getCallerClass where we cannot use a SecurityManager
203        final Deque<Class<?>> classes = new ArrayDeque<>();
204        Class<?> clazz;
205        for (int i = 1; null != (clazz = getCallerClass(i)); i++) {
206            classes.push(clazz);
207        }
208        return classes;
209    }
210
211    public StackTraceElement calcLocation(final String fqcnOfLogger) {
212        if (fqcnOfLogger == null) {
213            return null;
214        }
215        // LOG4J2-1029 new Throwable().getStackTrace is faster than Thread.currentThread().getStackTrace().
216        final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
217        boolean found = false;
218        for (int i = 0; i < stackTrace.length; i++) {
219            final String className = stackTrace[i].getClassName();
220            if (fqcnOfLogger.equals(className)) {
221                found = true;
222                continue;
223            }
224            if (found && !fqcnOfLogger.equals(className)) {
225                return stackTrace[i];
226            }
227        }
228        return null;
229    }
230
231    public StackTraceElement getStackTraceElement(final int depth) {
232        // (MS) I tested the difference between using Throwable.getStackTrace() and Thread.getStackTrace(), and
233        // the version using Throwable was surprisingly faster! at least on Java 1.8. See ReflectionBenchmark.
234        int i = 0;
235        for (final StackTraceElement element : new Throwable().getStackTrace()) {
236            if (isValid(element)) {
237                if (i == depth) {
238                    return element;
239                }
240                ++i;
241            }
242        }
243        throw new IndexOutOfBoundsException(Integer.toString(depth));
244    }
245
246    private boolean isValid(final StackTraceElement element) {
247        // ignore native methods (oftentimes are repeated frames)
248        if (element.isNativeMethod()) {
249            return false;
250        }
251        final String cn = element.getClassName();
252        // ignore OpenJDK internal classes involved with reflective invocation
253        if (cn.startsWith("sun.reflect.")) {
254            return false;
255        }
256        final String mn = element.getMethodName();
257        // ignore use of reflection including:
258        // Method.invoke
259        // InvocationHandler.invoke
260        // Constructor.newInstance
261        if (cn.startsWith("java.lang.reflect.") && (mn.equals("invoke") || mn.equals("newInstance"))) {
262            return false;
263        }
264        // ignore use of Java 1.9+ reflection classes
265        if (cn.startsWith("jdk.internal.reflect.")) {
266            return false;
267        }
268        // ignore Class.newInstance
269        if (cn.equals("java.lang.Class") && mn.equals("newInstance")) {
270            return false;
271        }
272        // ignore use of Java 1.7+ MethodHandle.invokeFoo() methods
273        if (cn.equals("java.lang.invoke.MethodHandle") && mn.startsWith("invoke")) {
274            return false;
275        }
276        // any others?
277        return true;
278    }
279}