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.Stack;
021import java.util.function.Predicate;
022
023/**
024 * <em>Consider this class private.</em> Provides various methods to determine the caller class. <h3>Background</h3>
025 * <p>
026 * This method, available only in the Oracle/Sun/OpenJDK implementations of the Java Virtual Machine, is a much more
027 * efficient mechanism for determining the {@link Class} of the caller of a particular method. When it is not available,
028 * a {@link SecurityManager} is the second-best option. When this is also not possible, the {@code StackTraceElement[]}
029 * returned by {@link Throwable#getStackTrace()} must be used, and its {@code String} class name converted to a
030 * {@code Class} using the slow {@link Class#forName} (which can add an extra microsecond or more for each invocation
031 * depending on the runtime ClassLoader hierarchy).
032 * </p>
033 * <p>
034 * During Java 8 development, the {@code sun.reflect.Reflection.getCallerClass(int)} was removed from OpenJDK, and this
035 * 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
036 * by one stack frame. This turned out to be beneficial for the survival of this API as the change broke hundreds of
037 * libraries and frameworks relying on the API which brought much more attention to the intended API removal.
038 * </p>
039 * <p>
040 * After much community backlash, the JDK team agreed to restore {@code getCallerClass(int)} and keep its existing
041 * behavior for the rest of Java 7. However, the method is deprecated in Java 8, and current Java 9 development has not
042 * addressed this API. Therefore, the functionality of this class cannot be relied upon for all future versions of Java.
043 * 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
044 * environments may fall back to using {@link Throwable#getStackTrace()} which is significantly slower due to
045 * examination of every virtual frame of execution.
046 * </p>
047 */
048public final class StackLocator {
049
050    // Checkstyle Suppress: the lower-case 'u' ticks off CheckStyle...
051    // CHECKSTYLE:OFF
052    static final int JDK_7u25_OFFSET;
053    // CHECKSTYLE:OFF
054
055    private static final Method GET_CALLER_CLASS;
056
057    private static final StackLocator INSTANCE;
058
059    static {
060        Method getCallerClass;
061        int java7u25CompensationOffset = 0;
062        try {
063            final Class<?> sunReflectionClass = LoaderUtil.loadClass("sun.reflect.Reflection");
064            getCallerClass = sunReflectionClass.getDeclaredMethod("getCallerClass", int.class);
065            Object o = getCallerClass.invoke(null, 0);
066            getCallerClass.invoke(null, 0);
067            if (o == null || o != sunReflectionClass) {
068                getCallerClass = null;
069                java7u25CompensationOffset = -1;
070            } else {
071                o = getCallerClass.invoke(null, 1);
072                if (o == sunReflectionClass) {
073                    System.out.println("WARNING: Java 1.7.0_25 is in use which has a broken implementation of Reflection.getCallerClass(). " +
074                        " Please consider upgrading to Java 1.7.0_40 or later.");
075                    java7u25CompensationOffset = 1;
076                }
077            }
078        } catch (final Exception | LinkageError e) {
079            System.out.println("WARNING: sun.reflect.Reflection.getCallerClass is not supported. This will impact performance.");
080            getCallerClass = null;
081            java7u25CompensationOffset = -1;
082        }
083
084        GET_CALLER_CLASS = getCallerClass;
085        JDK_7u25_OFFSET = java7u25CompensationOffset;
086
087        INSTANCE = new StackLocator();
088    }
089
090    public static StackLocator getInstance() {
091        return INSTANCE;
092    }
093
094    private StackLocator() {
095    }
096
097    // TODO: return Object.class instead of null (though it will have a null ClassLoader)
098    // (MS) I believe this would work without any modifications elsewhere, but I could be wrong
099
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}