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