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