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;
021
022/**
023 * <em>Consider this class private.</em> Provides various methods to determine the caller class. <h3>Background</h3>
024 * <p>
025 * This method, available only in the Oracle/Sun/OpenJDK implementations of the Java Virtual Machine, is a much more
026 * efficient mechanism for determining the {@link Class} of the caller of a particular method. When it is not available,
027 * a {@link SecurityManager} is the second-best option. When this is also not possible, the {@code StackTraceElement[]}
028 * returned by {@link Throwable#getStackTrace()} must be used, and its {@code String} class name converted to a
029 * {@code Class} using the slow {@link Class#forName} (which can add an extra microsecond or more for each invocation
030 * depending on the runtime ClassLoader hierarchy).
031 * </p>
032 * <p>
033 * During Java 8 development, the {@code sun.reflect.Reflection.getCallerClass(int)} was removed from OpenJDK, and this
034 * 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
035 * by one stack frame. This turned out to be beneficial for the survival of this API as the change broke hundreds of
036 * libraries and frameworks relying on the API which brought much more attention to the intended API removal.
037 * </p>
038 * <p>
039 * After much community backlash, the JDK team agreed to restore {@code getCallerClass(int)} and keep its existing
040 * behavior for the rest of Java 7. However, the method is deprecated in Java 8, and current Java 9 development has not
041 * addressed this API. Therefore, the functionality of this class cannot be relied upon for all future versions of Java.
042 * 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
043 * environments may fall back to using {@link Throwable#getStackTrace()} which is significantly slower due to
044 * examination of every virtual frame of execution.
045 * </p>
046 */
047public final class StackLocator {
048
049    private static PrivateSecurityManager SECURITY_MANAGER;
050
051    // Checkstyle Suppress: the lower-case 'u' ticks off CheckStyle...
052    // CHECKSTYLE:OFF
053    static final int JDK_7u25_OFFSET;
054    // CHECKSTYLE:OFF
055
056    private static final boolean SUN_REFLECTION_SUPPORTED;
057    private static final Method GET_CALLER_CLASS;
058
059    private static final StackLocator INSTANCE;
060
061    static {
062        Method getCallerClass;
063        int java7u25CompensationOffset = 0;
064        try {
065            final Class<?> sunReflectionClass = LoaderUtil.loadClass("sun.reflect.Reflection");
066            getCallerClass = sunReflectionClass.getDeclaredMethod("getCallerClass", int.class);
067            Object o = getCallerClass.invoke(null, 0);
068            final Object test1 = getCallerClass.invoke(null, 0);
069            if (o == null || o != sunReflectionClass) {
070                getCallerClass = null;
071                java7u25CompensationOffset = -1;
072            } else {
073                o = getCallerClass.invoke(null, 1);
074                if (o == sunReflectionClass) {
075                    System.out.println("WARNING: Java 1.7.0_25 is in use which has a broken implementation of Reflection.getCallerClass(). " +
076                        " Please consider upgrading to Java 1.7.0_40 or later.");
077                    java7u25CompensationOffset = 1;
078                }
079            }
080        } catch (final Exception | LinkageError e) {
081            System.out.println("WARNING: sun.reflect.Reflection.getCallerClass is not supported. This will impact performance.");
082            getCallerClass = null;
083            java7u25CompensationOffset = -1;
084        }
085
086        SUN_REFLECTION_SUPPORTED = getCallerClass != null;
087        GET_CALLER_CLASS = getCallerClass;
088        JDK_7u25_OFFSET = java7u25CompensationOffset;
089
090        INSTANCE = new StackLocator();
091    }
092
093    public static StackLocator getInstance() {
094        return INSTANCE;
095    }
096
097    private StackLocator() {
098    }
099
100    // TODO: return Object.class instead of null (though it will have a null ClassLoader)
101    // (MS) I believe this would work without any modifications elsewhere, but I could be wrong
102
103    // migrated from ReflectiveCallerClassUtility
104    @PerformanceSensitive
105    public Class<?> getCallerClass(final int depth) {
106        if (depth < 0) {
107            throw new IndexOutOfBoundsException(Integer.toString(depth));
108        }
109        // note that we need to add 1 to the depth value to compensate for this method, but not for the Method.invoke
110        // since Reflection.getCallerClass ignores the call to Method.invoke()
111        try {
112            return (Class<?>) GET_CALLER_CLASS.invoke(null, depth + 1 + JDK_7u25_OFFSET);
113        } catch (final Exception e) {
114            // theoretically this could happen if the caller class were native code
115            // TODO: return Object.class
116            return null;
117        }
118    }
119
120    // migrated from Log4jLoggerFactory
121    @PerformanceSensitive
122    public Class<?> getCallerClass(final String fqcn, final String pkg) {
123        boolean next = false;
124        Class<?> clazz;
125        for (int i = 2; null != (clazz = getCallerClass(i)); i++) {
126            if (fqcn.equals(clazz.getName())) {
127                next = true;
128                continue;
129            }
130            if (next && clazz.getName().startsWith(pkg)) {
131                return clazz;
132            }
133        }
134        // TODO: return Object.class
135        return null;
136    }
137
138    // added for use in LoggerAdapter implementations mainly
139    @PerformanceSensitive
140    public Class<?> getCallerClass(final Class<?> anchor) {
141        boolean next = false;
142        Class<?> clazz;
143        for (int i = 2; null != (clazz = getCallerClass(i)); i++) {
144            if (anchor.equals(clazz)) {
145                next = true;
146                continue;
147            }
148            if (next) {
149                return clazz;
150            }
151        }
152        return Object.class;
153    }
154
155    // migrated from ThrowableProxy
156    @PerformanceSensitive
157    public Stack<Class<?>> getCurrentStackTrace() {
158        // benchmarks show that using the SecurityManager is much faster than looping through getCallerClass(int)
159        if (getSecurityManager() != null) {
160            final Class<?>[] array = getSecurityManager().getClassContext();
161            final Stack<Class<?>> classes = new Stack<>();
162            classes.ensureCapacity(array.length);
163            for (final Class<?> clazz : array) {
164                classes.push(clazz);
165            }
166            return classes;
167        }
168        // slower version using getCallerClass where we cannot use a SecurityManager
169        final Stack<Class<?>> classes = new Stack<>();
170        Class<?> clazz;
171        for (int i = 1; null != (clazz = getCallerClass(i)); i++) {
172            classes.push(clazz);
173        }
174        return classes;
175    }
176
177    public StackTraceElement calcLocation(final String fqcnOfLogger) {
178        if (fqcnOfLogger == null) {
179            return null;
180        }
181        // LOG4J2-1029 new Throwable().getStackTrace is faster than Thread.currentThread().getStackTrace().
182        final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
183        StackTraceElement last = null;
184        for (int i = stackTrace.length - 1; i > 0; i--) {
185            final String className = stackTrace[i].getClassName();
186            if (fqcnOfLogger.equals(className)) {
187                return last;
188            }
189            last = stackTrace[i];
190        }
191        return null;
192    }
193
194    public StackTraceElement getStackTraceElement(final int depth) {
195        // (MS) I tested the difference between using Throwable.getStackTrace() and Thread.getStackTrace(), and
196        // the version using Throwable was surprisingly faster! at least on Java 1.8. See ReflectionBenchmark.
197        final StackTraceElement[] elements = new Throwable().getStackTrace();
198        int i = 0;
199        for (final StackTraceElement element : elements) {
200            if (isValid(element)) {
201                if (i == depth) {
202                    return element;
203                }
204                ++i;
205            }
206        }
207        throw new IndexOutOfBoundsException(Integer.toString(depth));
208    }
209
210    private boolean isValid(final StackTraceElement element) {
211        // ignore native methods (oftentimes are repeated frames)
212        if (element.isNativeMethod()) {
213            return false;
214        }
215        final String cn = element.getClassName();
216        // ignore OpenJDK internal classes involved with reflective invocation
217        if (cn.startsWith("sun.reflect.")) {
218            return false;
219        }
220        final String mn = element.getMethodName();
221        // ignore use of reflection including:
222        // Method.invoke
223        // InvocationHandler.invoke
224        // Constructor.newInstance
225        if (cn.startsWith("java.lang.reflect.") && (mn.equals("invoke") || mn.equals("newInstance"))) {
226            return false;
227        }
228        // ignore use of Java 1.9+ reflection classes
229        if (cn.startsWith("jdk.internal.reflect.")) {
230            return false;
231        }
232        // ignore Class.newInstance
233        if (cn.equals("java.lang.Class") && mn.equals("newInstance")) {
234            return false;
235        }
236        // ignore use of Java 1.7+ MethodHandle.invokeFoo() methods
237        if (cn.equals("java.lang.invoke.MethodHandle") && mn.startsWith("invoke")) {
238            return false;
239        }
240        // any others?
241        return true;
242    }
243
244    protected PrivateSecurityManager getSecurityManager() {
245        return SECURITY_MANAGER;
246    }
247
248    private static final class PrivateSecurityManager extends SecurityManager {
249
250        @Override
251        protected Class<?>[] getClassContext() {
252            return super.getClassContext();
253        }
254
255        protected Class<?> getCallerClass(final String fqcn, final String pkg) {
256            boolean next = false;
257            for (final Class<?> clazz : getClassContext()) {
258                if (fqcn.equals(clazz.getName())) {
259                    next = true;
260                    continue;
261                }
262                if (next && clazz.getName().startsWith(pkg)) {
263                    return clazz;
264                }
265            }
266            // TODO: return Object.class
267            return null;
268        }
269
270        protected Class<?> getCallerClass(final Class<?> anchor) {
271            boolean next = false;
272            for (final Class<?> clazz : getClassContext()) {
273                if (anchor.equals(clazz)) {
274                    next = true;
275                    continue;
276                }
277                if (next) {
278                    return clazz;
279                }
280            }
281            return Object.class;
282        }
283    }
284}