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
022import org.apache.logging.log4j.Logger;
023import org.apache.logging.log4j.status.StatusLogger;
024
025/**
026 * <em>Consider this class private.</em> Provides various methods to determine the caller class.
027 * <h3>Background</h3>
028 * <p>This method, available only in the Oracle/Sun/OpenJDK implementations of the Java
029 * Virtual Machine, is a much more efficient mechanism for determining the {@link Class} of the caller of a particular
030 * method. When it is not available, a {@link SecurityManager} is the second-best option. When this is also not
031 * possible, the {@code StackTraceElement[]} returned by {@link Throwable#getStackTrace()} must be used, and its
032 * {@code String} class name converted to a {@code Class} using the slow {@link Class#forName} (which can add an extra
033 * microsecond or more for each invocation depending on the runtime ClassLoader hierarchy).
034 * </p>
035 * <p>
036 * During Java 8 development, the {@code sun.reflect.Reflection.getCallerClass(int)} was removed from OpenJDK, and this
037 * 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
038 * by one stack frame. This turned out to be beneficial for the survival of this API as the change broke hundreds of
039 * libraries and frameworks relying on the API which brought much more attention to the intended API removal.
040 * </p>
041 * <p>
042 * After much community backlash, the JDK team agreed to restore {@code getCallerClass(int)} and keep its existing
043 * behavior for the rest of Java 7. However, the method is deprecated in Java 8, and current Java 9 development has not
044 * addressed this API. Therefore, the functionality of this class cannot be relied upon for all future versions of
045 * Java. It does, however, work just fine in Sun JDK 1.6, OpenJDK 1.6, Oracle/OpenJDK 1.7, and Oracle/OpenJDK 1.8.
046 * Other Java environments may fall back to using {@link Throwable#getStackTrace()} which is significantly slower due
047 * to examination of every virtual frame of execution.
048 * </p>
049 */
050public final class ReflectionUtil {
051
052    private static final Logger LOGGER = StatusLogger.getLogger();
053
054    private static final boolean SUN_REFLECTION_SUPPORTED;
055    private static final Method GET_CALLER_CLASS;
056    static final int JDK_7u25_OFFSET;
057    private static final PrivateSecurityManager SECURITY_MANAGER;
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            final Object test1 = getCallerClass.invoke(null, 0);
067            if (o == null || o != sunReflectionClass) {
068                LOGGER.warn("Unexpected return value from Reflection.getCallerClass(): {}", test1);
069                getCallerClass = null;
070                java7u25CompensationOffset = -1;
071            } else {
072                o = getCallerClass.invoke(null, 1);
073                if (o == sunReflectionClass) {
074                    LOGGER.warn(
075                        "You are using Java 1.7.0_25 which has a broken implementation of Reflection.getCallerClass.");
076                    LOGGER.warn("You should upgrade to at least Java 1.7.0_40 or later.");
077                    LOGGER.debug("Using stack depth compensation offset of 1 due to Java 7u25.");
078                    java7u25CompensationOffset = 1;
079                }
080            }
081        } catch (final Exception e) {
082            LOGGER.info("sun.reflect.Reflection.getCallerClass is not supported. " +
083                "ReflectionUtil.getCallerClass will be much slower due to this.", e);
084            getCallerClass = null;
085            java7u25CompensationOffset = -1;
086        }
087
088        SUN_REFLECTION_SUPPORTED = getCallerClass != null;
089        GET_CALLER_CLASS = getCallerClass;
090        JDK_7u25_OFFSET = java7u25CompensationOffset;
091
092        PrivateSecurityManager psm;
093        try {
094            final SecurityManager sm = System.getSecurityManager();
095            if (sm != null) {
096                sm.checkPermission(new RuntimePermission("createSecurityManager"));
097            }
098            psm = new PrivateSecurityManager();
099        } catch (final SecurityException ignored) {
100            LOGGER.debug(
101                "Not allowed to create SecurityManager. Falling back to slowest ReflectionUtil implementation.");
102            psm = null;
103        }
104        SECURITY_MANAGER = psm;
105    }
106
107    public static boolean supportsFastReflection() {
108        return SUN_REFLECTION_SUPPORTED;
109    }
110
111    // TODO: return Object.class instead of null (though it will have a null ClassLoader)
112    // (MS) I believe this would work without any modifications elsewhere, but I could be wrong
113
114    // migrated from ReflectiveCallerClassUtility
115    public static Class<?> getCallerClass(final int depth) {
116        if (depth < 0) {
117            throw new IndexOutOfBoundsException(Integer.toString(depth));
118        }
119        // note that we need to add 1 to the depth value to compensate for this method, but not for the Method.invoke
120        // since Reflection.getCallerClass ignores the call to Method.invoke()
121        if (supportsFastReflection()) {
122            try {
123                return (Class<?>) GET_CALLER_CLASS.invoke(null, depth + 1 + JDK_7u25_OFFSET);
124            } catch (final Exception e) {
125                // theoretically this could happen if the caller class were native code
126                LOGGER.error("Error in ReflectionUtil.getCallerClass({}).", depth, e);
127                // TODO: return Object.class
128                return null;
129            }
130        }
131        // TODO: SecurityManager-based version?
132        // slower fallback method using stack trace
133        final StackTraceElement element = getEquivalentStackTraceElement(depth + 1);
134        try {
135            return LoaderUtil.loadClass(element.getClassName());
136        } catch (final ClassNotFoundException e) {
137            LOGGER.error("Could not find class in ReflectionUtil.getCallerClass({}).", depth, e);
138        }
139        // TODO: return Object.class
140        return null;
141    }
142
143    static StackTraceElement getEquivalentStackTraceElement(final int depth) {
144        // (MS) I tested the difference between using Throwable.getStackTrace() and Thread.getStackTrace(), and
145        //      the version using Throwable was surprisingly faster! at least on Java 1.8. See ReflectionBenchmark.
146        final StackTraceElement[] elements = new Throwable().getStackTrace();
147        int i = 0;
148        for (final StackTraceElement element : elements) {
149            if (isValid(element)) {
150                if (i == depth) {
151                    return element;
152                } else {
153                    ++i;
154                }
155            }
156        }
157        LOGGER.error("Could not find an appropriate StackTraceElement at index {}", depth);
158        throw new IndexOutOfBoundsException(Integer.toString(depth));
159    }
160
161    private static boolean isValid(final StackTraceElement element) {
162        // ignore native methods (oftentimes are repeated frames)
163        if (element.isNativeMethod()) {
164            return false;
165        }
166        final String cn = element.getClassName();
167        // ignore OpenJDK internal classes involved with reflective invocation
168        if (cn.startsWith("sun.reflect.")) {
169            return false;
170        }
171        final String mn = element.getMethodName();
172        // ignore use of reflection including:
173        // Method.invoke
174        // InvocationHandler.invoke
175        // Constructor.newInstance
176        if (cn.startsWith("java.lang.reflect.") && (mn.equals("invoke") || mn.equals("newInstance"))) {
177            return false;
178        }
179        // ignore Class.newInstance
180        if (cn.equals("java.lang.Class") && mn.equals("newInstance")) {
181            return false;
182        }
183        // ignore use of Java 1.7+ MethodHandle.invokeFoo() methods
184        if (cn.equals("java.lang.invoke.MethodHandle") && mn.startsWith("invoke")) {
185            return false;
186        }
187        // any others?
188        return true;
189    }
190
191    // migrated from ClassLoaderContextSelector
192    public static Class<?> getCallerClass(final String fqcn) {
193        return getCallerClass(fqcn, Strings.EMPTY);
194    }
195
196    // migrated from Log4jLoggerFactory
197    public static Class<?> getCallerClass(final String fqcn, final String pkg) {
198        if (supportsFastReflection()) {
199            boolean next = false;
200            Class<?> clazz;
201            for (int i = 2; null != (clazz = getCallerClass(i)); i++) {
202                if (fqcn.equals(clazz.getName())) {
203                    next = true;
204                    continue;
205                }
206                if (next && clazz.getName().startsWith(pkg)) {
207                    return clazz;
208                }
209            }
210            // TODO: return Object.class
211            return null;
212        }
213        if (SECURITY_MANAGER != null) {
214            return SECURITY_MANAGER.getCallerClass(fqcn, pkg);
215        }
216        try {
217            return LoaderUtil.loadClass(getCallerClassName(fqcn, pkg, new Throwable().getStackTrace()));
218        } catch (final ClassNotFoundException ignored) {
219            // no problem really
220        }
221        // TODO: return Object.class
222        return null;
223    }
224
225    // added for use in LoggerAdapter implementations mainly
226    public static Class<?> getCallerClass(final Class<?> anchor) {
227        if (supportsFastReflection()) {
228            boolean next = false;
229            Class<?> clazz;
230            for (int i = 2; null != (clazz = getCallerClass(i)); i++) {
231                if (anchor.equals(clazz)) {
232                    next = true;
233                    continue;
234                }
235                if (next) {
236                    return clazz;
237                }
238            }
239            return Object.class;
240        }
241        if (SECURITY_MANAGER != null) {
242            return SECURITY_MANAGER.getCallerClass(anchor);
243        }
244        try {
245            return LoaderUtil.loadClass(getCallerClassName(anchor.getName(), Strings.EMPTY,
246                new Throwable().getStackTrace()));
247        } catch (final ClassNotFoundException ignored) {
248            // no problem really
249        }
250        return Object.class;
251    }
252
253    private static String getCallerClassName(final String fqcn, final String pkg, final StackTraceElement... elements) {
254        boolean next = false;
255        for (final StackTraceElement element : elements) {
256            final String className = element.getClassName();
257            if (className.equals(fqcn)) {
258                next = true;
259                continue;
260            }
261            if (next && className.startsWith(pkg)) {
262                return className;
263            }
264        }
265        return Object.class.getName();
266    }
267
268    // migrated from ThrowableProxy
269    public static Stack<Class<?>> getCurrentStackTrace() {
270        // benchmarks show that using the SecurityManager is much faster than looping through getCallerClass(int)
271        if (SECURITY_MANAGER != null) {
272            final Class<?>[] array = SECURITY_MANAGER.getClassContext();
273            final Stack<Class<?>> classes = new Stack<Class<?>>();
274            classes.ensureCapacity(array.length);
275            for (final Class<?> clazz : array) {
276                classes.push(clazz);
277            }
278            return classes;
279        }
280        // slower version using getCallerClass where we cannot use a SecurityManager
281        if (supportsFastReflection()) {
282            final Stack<Class<?>> classes = new Stack<Class<?>>();
283            Class<?> clazz;
284            for (int i = 1; null != (clazz = getCallerClass(i)); i++) {
285                classes.push(clazz);
286            }
287            return classes;
288        }
289        return new Stack<Class<?>>();
290    }
291
292    static final class PrivateSecurityManager extends SecurityManager {
293
294        @Override
295        protected Class<?>[] getClassContext() {
296            return super.getClassContext();
297        }
298
299        protected Class<?> getCallerClass(final String fqcn, final String pkg) {
300            boolean next = false;
301            for (final Class<?> clazz : getClassContext()) {
302                if (fqcn.equals(clazz.getName())) {
303                    next = true;
304                    continue;
305                }
306                if (next && clazz.getName().startsWith(pkg)) {
307                    return clazz;
308                }
309            }
310            // TODO: return Object.class
311            return null;
312        }
313
314        protected Class<?> getCallerClass(final Class<?> anchor) {
315            boolean next = false;
316            for (final Class<?> clazz : getClassContext()) {
317                if (anchor.equals(clazz)) {
318                    next = true;
319                    continue;
320                }
321                if (next) {
322                    return clazz;
323                }
324            }
325            return Object.class;
326        }
327
328    }
329
330    private ReflectionUtil() {
331    }
332}