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