1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache license, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the license for the specific language governing permissions and 15 * limitations under the license. 16 */ 17 package org.apache.logging.log4j.core.impl; 18 19 import java.lang.reflect.InvocationTargetException; 20 import java.lang.reflect.Method; 21 import java.lang.reflect.Modifier; 22 23 import org.apache.logging.log4j.Logger; 24 import org.apache.logging.log4j.core.helpers.Loader; 25 import org.apache.logging.log4j.status.StatusLogger; 26 27 /** 28 * Utility class that handles the instability of the Sun/OpenJDK {@code sun.reflect.Reflection.getCallerClass(int)} 29 * method.<br> 30 * <br> 31 * <strong>Background:</strong> This method, available only in the Oracle/Sun/OpenJDK implementations of the Java 32 * Virtual Machine, is a much more efficient mechanism for determining the {@link Class} of the caller of a particular 33 * method. When it is not available, a {@link SecurityManager} is the second-best option. When this is also not 34 * possible, the {@code StackTraceElement[]} returned by {@link Thread#getStackTrace()} must be used, and its 35 * {@code String} class name converted to a {@code Class} using the slow {@link Class#forName}.<br> 36 * <br> 37 * As of Java 8, the {@code getCallerClass(int)} method has been removed from Oracle/OpenJDK and is no longer usable. A 38 * back-port of the feature that resulted in this change was made in Java 7u25, but the {@code getCallerClass(int)} was 39 * left around for that version and deprecated, with the intention of being removed in 7u40. By coincidence, the change 40 * actually broke {@code getCallerClass(int)} (the return value was inadvertently offset by 1 stack frame). This was 41 * actually a good thing, because it made the hundreds of libraries and frameworks relying on this method aware of what 42 * the JDK developers were up to.<br> 43 * <br> 44 * After much community backlash, the JDK team agreed to restore {@code getCallerClass(int)} and keep its existing 45 * behavior for the rest of Java 7. However, the method will still not be available in Java 8, and so backup options 46 * must be used. This class:<br> 47 * <ul> 48 * <li>Uses {@code getCallerClass(int)} the traditional way when possible.</li> 49 * <li>Uses {@code getCallerClass(int)} with an adjusted offset in Oracle/OpenJDK 7u25.</li> 50 * <li>Returns null otherwise. (Currently, it is the caller's responsibility to use the backup mechanisms.)</li> 51 * </ul> 52 * <br> 53 * <strong>IMPORTANT NOTE:</strong> This class should not be relied upon. It is considered an internal class and could 54 * change at any time, breaking your code if you use it. Specifically, as a possible public API replacement for 55 * {@code getCallerClass(int)} develops in Java 8, this class is very likely to change or even go away. 56 */ 57 public final class ReflectiveCallerClassUtility { 58 59 private static final Logger LOGGER = StatusLogger.getLogger(); 60 61 private static final boolean GET_CALLER_CLASS_SUPPORTED; 62 63 private static final Method GET_CALLER_CLASS_METHOD; 64 65 static final int JAVA_7U25_COMPENSATION_OFFSET; 66 67 static { 68 Method getCallerClass = null; 69 int java7u25CompensationOffset = 0; 70 71 try { 72 final ClassLoader loader = Loader.getClassLoader(); 73 // Use wildcard to avoid compile-time reference. 74 final Class<?> clazz = loader.loadClass("sun.reflect.Reflection"); 75 final Method[] methods = clazz.getMethods(); 76 for (final Method method : methods) { 77 final int modifier = method.getModifiers(); 78 final Class<?>[] parameterTypes = method.getParameterTypes(); 79 if (method.getName().equals("getCallerClass") && Modifier.isStatic(modifier) && 80 parameterTypes.length == 1 && parameterTypes[0] == int.class) { 81 getCallerClass = method; 82 break; 83 } 84 } 85 86 if (getCallerClass == null) { 87 LOGGER.info("sun.reflect.Reflection#getCallerClass does not exist."); 88 } else { 89 Object o = getCallerClass.invoke(null, 0); 90 if (o == null || o != clazz) { 91 getCallerClass = null; 92 LOGGER.warn("sun.reflect.Reflection#getCallerClass returned unexpected value of [{}] and is " + 93 "unusable. Will fall back to another option.", o); 94 } else { 95 o = getCallerClass.invoke(null, 1); 96 if (o == clazz) { 97 java7u25CompensationOffset = 1; 98 LOGGER.warn("sun.reflect.Reflection#getCallerClass is broken in Java 7u25. " + 99 "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 }