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                }
153                ++i;
154            }
155        }
156        LOGGER.error("Could not find an appropriate StackTraceElement at index {}", depth);
157        throw new IndexOutOfBoundsException(Integer.toString(depth));
158    }
159
160    private static boolean isValid(final StackTraceElement element) {
161        // ignore native methods (oftentimes are repeated frames)
162        if (element.isNativeMethod()) {
163            return false;
164        }
165        final String cn = element.getClassName();
166        // ignore OpenJDK internal classes involved with reflective invocation
167        if (cn.startsWith("sun.reflect.")) {
168            return false;
169        }
170        final String mn = element.getMethodName();
171        // ignore use of reflection including:
172        // Method.invoke
173        // InvocationHandler.invoke
174        // Constructor.newInstance
175        if (cn.startsWith("java.lang.reflect.") && (mn.equals("invoke") || mn.equals("newInstance"))) {
176            return false;
177        }
178        // ignore Class.newInstance
179        if (cn.equals("java.lang.Class") && mn.equals("newInstance")) {
180            return false;
181        }
182        // ignore use of Java 1.7+ MethodHandle.invokeFoo() methods
183        if (cn.equals("java.lang.invoke.MethodHandle") && mn.startsWith("invoke")) {
184            return false;
185        }
186        // any others?
187        return true;
188    }
189
190    // migrated from ClassLoaderContextSelector
191    public static Class<?> getCallerClass(final String fqcn) {
192        return getCallerClass(fqcn, Strings.EMPTY);
193    }
194
195    // migrated from Log4jLoggerFactory
196    public static Class<?> getCallerClass(final String fqcn, final String pkg) {
197        if (supportsFastReflection()) {
198            boolean next = false;
199            Class<?> clazz;
200            for (int i = 2; null != (clazz = getCallerClass(i)); i++) {
201                if (fqcn.equals(clazz.getName())) {
202                    next = true;
203                    continue;
204                }
205                if (next && clazz.getName().startsWith(pkg)) {
206                    return clazz;
207                }
208            }
209            // TODO: return Object.class
210            return null;
211        }
212        if (SECURITY_MANAGER != null) {
213            return SECURITY_MANAGER.getCallerClass(fqcn, pkg);
214        }
215        try {
216            return LoaderUtil.loadClass(getCallerClassName(fqcn, pkg, new Throwable().getStackTrace()));
217        } catch (final ClassNotFoundException ignored) {
218            // no problem really
219        }
220        // TODO: return Object.class
221        return null;
222    }
223
224    // added for use in LoggerAdapter implementations mainly
225    public static Class<?> getCallerClass(final Class<?> anchor) {
226        if (supportsFastReflection()) {
227            boolean next = false;
228            Class<?> clazz;
229            for (int i = 2; null != (clazz = getCallerClass(i)); i++) {
230                if (anchor.equals(clazz)) {
231                    next = true;
232                    continue;
233                }
234                if (next) {
235                    return clazz;
236                }
237            }
238            return Object.class;
239        }
240        if (SECURITY_MANAGER != null) {
241            return SECURITY_MANAGER.getCallerClass(anchor);
242        }
243        try {
244            return LoaderUtil.loadClass(getCallerClassName(anchor.getName(), Strings.EMPTY,
245                new Throwable().getStackTrace()));
246        } catch (final ClassNotFoundException ignored) {
247            // no problem really
248        }
249        return Object.class;
250    }
251
252    private static String getCallerClassName(final String fqcn, final String pkg, final StackTraceElement... elements) {
253        boolean next = false;
254        for (final StackTraceElement element : elements) {
255            final String className = element.getClassName();
256            if (className.equals(fqcn)) {
257                next = true;
258                continue;
259            }
260            if (next && className.startsWith(pkg)) {
261                return className;
262            }
263        }
264        return Object.class.getName();
265    }
266
267    // migrated from ThrowableProxy
268    public static Stack<Class<?>> getCurrentStackTrace() {
269        // benchmarks show that using the SecurityManager is much faster than looping through getCallerClass(int)
270        if (SECURITY_MANAGER != null) {
271            final Class<?>[] array = SECURITY_MANAGER.getClassContext();
272            final Stack<Class<?>> classes = new Stack<>();
273            classes.ensureCapacity(array.length);
274            for (final Class<?> clazz : array) {
275                classes.push(clazz);
276            }
277            return classes;
278        }
279        // slower version using getCallerClass where we cannot use a SecurityManager
280        if (supportsFastReflection()) {
281            final Stack<Class<?>> classes = new Stack<>();
282            Class<?> clazz;
283            for (int i = 1; null != (clazz = getCallerClass(i)); i++) {
284                classes.push(clazz);
285            }
286            return classes;
287        }
288        return new Stack<>();
289    }
290
291    static final class PrivateSecurityManager extends SecurityManager {
292
293        @Override
294        protected Class<?>[] getClassContext() {
295            return super.getClassContext();
296        }
297
298        protected Class<?> getCallerClass(final String fqcn, final String pkg) {
299            boolean next = false;
300            for (final Class<?> clazz : getClassContext()) {
301                if (fqcn.equals(clazz.getName())) {
302                    next = true;
303                    continue;
304                }
305                if (next && clazz.getName().startsWith(pkg)) {
306                    return clazz;
307                }
308            }
309            // TODO: return Object.class
310            return null;
311        }
312
313        protected Class<?> getCallerClass(final Class<?> anchor) {
314            boolean next = false;
315            for (final Class<?> clazz : getClassContext()) {
316                if (anchor.equals(clazz)) {
317                    next = true;
318                    continue;
319                }
320                if (next) {
321                    return clazz;
322                }
323            }
324            return Object.class;
325        }
326
327    }
328
329    private ReflectionUtil() {
330    }
331}