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.core.impl;
018
019import java.lang.reflect.InvocationTargetException;
020import java.lang.reflect.Method;
021import java.lang.reflect.Modifier;
022
023import org.apache.logging.log4j.Logger;
024import org.apache.logging.log4j.core.helpers.Loader;
025import org.apache.logging.log4j.status.StatusLogger;
026
027/**
028 * Utility class that handles the instability of the Sun/OpenJDK {@code sun.reflect.Reflection.getCallerClass(int)}
029 * method.<br>
030 * <br>
031 * <strong>Background:</strong> This method, available only in the Oracle/Sun/OpenJDK implementations of the Java
032 * Virtual Machine, is a much more efficient mechanism for determining the {@link Class} of the caller of a particular
033 * method. When it is not available, a {@link SecurityManager} is the second-best option. When this is also not
034 * possible, the {@code StackTraceElement[]} returned by {@link Thread#getStackTrace()} must be used, and its
035 * {@code String} class name converted to a {@code Class} using the slow {@link Class#forName}.<br>
036 * <br>
037 * As of Java 8, the {@code getCallerClass(int)} method has been removed from Oracle/OpenJDK and is no longer usable. A
038 * back-port of the feature that resulted in this change was made in Java 7u25, but the {@code getCallerClass(int)} was
039 * left around for that version and deprecated, with the intention of being removed in 7u40. By coincidence, the change
040 * actually broke {@code getCallerClass(int)} (the return value was inadvertently offset by 1 stack frame). This was
041 * actually a good thing, because it made the hundreds of libraries and frameworks relying on this method aware of what
042 * the JDK developers were up to.<br>
043 * <br>
044 * After much community backlash, the JDK team agreed to restore {@code getCallerClass(int)} and keep its existing
045 * behavior for the rest of Java 7. However, the method will still not be available in Java 8, and so backup options
046 * must be used. This class:<br>
047 * <ul>
048 *     <li>Uses {@code getCallerClass(int)} the traditional way when possible.</li>
049 *     <li>Uses {@code getCallerClass(int)} with an adjusted offset in Oracle/OpenJDK 7u25.</li>
050 *     <li>Returns null otherwise. (Currently, it is the caller's responsibility to use the backup mechanisms.)</li>
051 * </ul>
052 * <br>
053 * <strong>IMPORTANT NOTE:</strong> This class should not be relied upon. It is considered an internal class and could
054 * change at any time, breaking your code if you use it. Specifically, as a possible public API replacement for
055 * {@code getCallerClass(int)} develops in Java 8, this class is very likely to change or even go away.
056 */
057public final class ReflectiveCallerClassUtility {
058
059    private static final Logger LOGGER = StatusLogger.getLogger();
060
061    private static final boolean GET_CALLER_CLASS_SUPPORTED;
062
063    private static final Method GET_CALLER_CLASS_METHOD;
064
065    static final int JAVA_7U25_COMPENSATION_OFFSET;
066
067    static {
068        Method getCallerClass = null;
069        int java7u25CompensationOffset = 0;
070
071        try {
072            final ClassLoader loader = Loader.getClassLoader();
073            // Use wildcard to avoid compile-time reference.
074            final Class<?> clazz = loader.loadClass("sun.reflect.Reflection");
075            final Method[] methods = clazz.getMethods();
076            for (final Method method : methods) {
077                final int modifier = method.getModifiers();
078                final Class<?>[] parameterTypes = method.getParameterTypes();
079                if (method.getName().equals("getCallerClass") && Modifier.isStatic(modifier) &&
080                        parameterTypes.length == 1 && parameterTypes[0] == int.class) {
081                    getCallerClass = method;
082                    break;
083                }
084            }
085
086            if (getCallerClass == null) {
087                LOGGER.info("sun.reflect.Reflection#getCallerClass does not exist.");
088            } else {
089                Object o = getCallerClass.invoke(null, 0);
090                if (o == null || o != clazz) {
091                    getCallerClass = null;
092                    LOGGER.warn("sun.reflect.Reflection#getCallerClass returned unexpected value of [{}] and is " +
093                            "unusable. Will fall back to another option.", o);
094                } else {
095                    o = getCallerClass.invoke(null, 1);
096                    if (o == clazz) {
097                        java7u25CompensationOffset = 1;
098                        LOGGER.warn("sun.reflect.Reflection#getCallerClass is broken in Java 7u25. " +
099                                "You should upgrade to 7u40. Using alternate stack offset to compensate.");
100                    }
101                }
102            }
103        } catch (final ClassNotFoundException e) {
104            LOGGER.info("sun.reflect.Reflection is not installed.");
105        } catch (final IllegalAccessException e) {
106            LOGGER.info("sun.reflect.Reflection#getCallerClass is not accessible.");
107        } catch (final InvocationTargetException e) {
108            LOGGER.info("sun.reflect.Reflection#getCallerClass is not supported.");
109        }
110
111        if (getCallerClass == null) {
112            GET_CALLER_CLASS_SUPPORTED = false;
113            GET_CALLER_CLASS_METHOD = null;
114            JAVA_7U25_COMPENSATION_OFFSET = -1;
115        } else {
116            GET_CALLER_CLASS_SUPPORTED = true;
117            GET_CALLER_CLASS_METHOD = getCallerClass;
118            JAVA_7U25_COMPENSATION_OFFSET = java7u25CompensationOffset;
119        }
120    }
121
122    private ReflectiveCallerClassUtility() {
123
124    }
125
126    /**
127     * Indicates whether {@code getCallerClass(int)} can be used on this JVM.
128     *
129     * @return {@code true} if it can be used. If {@code false}, {@link #getCaller} should not be called. Use a backup
130     *         mechanism instead.
131     */
132    public static boolean isSupported() {
133        return GET_CALLER_CLASS_SUPPORTED;
134    }
135
136    /**
137     * Reflectively calls {@code getCallerClass(int)}, compensating for the additional frame on the stack, and
138     * compensating for the Java 7u25 bug if necessary. You should check with {@link #isSupported} before using this
139     * method.
140     *
141     * @param depth The depth of the caller to retrieve.
142     * @return the caller class, or {@code null} if {@code getCallerClass(int)} is not supported.
143     */
144    public static Class<?> getCaller(int depth) {
145        if (!GET_CALLER_CLASS_SUPPORTED) {
146            return null;
147        }
148
149        try {
150            return (Class<?>) GET_CALLER_CLASS_METHOD.invoke(null, depth + 1 + JAVA_7U25_COMPENSATION_OFFSET);
151        } catch (IllegalAccessException ignore) {
152            LOGGER.warn("Should not have failed to call getCallerClass.");
153        } catch (InvocationTargetException ignore) {
154            LOGGER.warn("Should not have failed to call getCallerClass.");
155        }
156        return null;
157    }
158}