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