    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.util;
    018
    019    import java.lang.reflect.Method;
    020    import java.util.Stack;
    021
    022    import org.apache.logging.log4j.Logger;
    023    import 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     */
    050    public 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    }