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.ArrayDeque; 021import java.util.Deque; 022import java.util.function.Predicate; 023 024/** 025 * <em>Consider this class private.</em> Provides various methods to determine the caller class. <h3>Background</h3> 026 * <p> 027 * This method, available only in the Oracle/Sun/OpenJDK implementations of the Java Virtual Machine, is a much more 028 * efficient mechanism for determining the {@link Class} of the caller of a particular method. When it is not available, 029 * a {@link SecurityManager} is the second-best option. When this is also not possible, the {@code StackTraceElement[]} 030 * returned by {@link Throwable#getStackTrace()} must be used, and its {@code String} class name converted to a 031 * {@code Class} using the slow {@link Class#forName} (which can add an extra microsecond or more for each invocation 032 * depending on the runtime ClassLoader hierarchy). 033 * </p> 034 * <p> 035 * During Java 8 development, the {@code sun.reflect.Reflection.getCallerClass(int)} was removed from OpenJDK, and this 036 * 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 037 * by one stack frame. This turned out to be beneficial for the survival of this API as the change broke hundreds of 038 * libraries and frameworks relying on the API which brought much more attention to the intended API removal. 039 * </p> 040 * <p> 041 * After much community backlash, the JDK team agreed to restore {@code getCallerClass(int)} and keep its existing 042 * behavior for the rest of Java 7. However, the method is deprecated in Java 8, and current Java 9 development has not 043 * addressed this API. Therefore, the functionality of this class cannot be relied upon for all future versions of Java. 044 * 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 045 * environments may fall back to using {@link Throwable#getStackTrace()} which is significantly slower due to 046 * examination of every virtual frame of execution. 047 * </p> 048 */ 049public final class StackLocator { 050 051 /** TODO Consider removing now that we require Java 8. */ 052 static final int JDK_7U25_OFFSET; 053 054 private static final Method GET_CALLER_CLASS_METHOD; 055 056 private static final StackLocator INSTANCE; 057 058 /** TODO: Use Object.class. */ 059 private static final Class<?> DEFAULT_CALLER_CLASS = null; 060 061 static { 062 Method getCallerClassMethod; 063 int java7u25CompensationOffset = 0; 064 try { 065 final Class<?> sunReflectionClass = LoaderUtil.loadClass("sun.reflect.Reflection"); 066 getCallerClassMethod = sunReflectionClass.getDeclaredMethod("getCallerClass", int.class); 067 Object o = getCallerClassMethod.invoke(null, 0); 068 getCallerClassMethod.invoke(null, 0); 069 if (o == null || o != sunReflectionClass) { 070 getCallerClassMethod = null; 071 java7u25CompensationOffset = -1; 072 } else { 073 o = getCallerClassMethod.invoke(null, 1); 074 if (o == sunReflectionClass) { 075 System.out.println("WARNING: Unexpected result from sun.reflect.Reflection.getCallerClass(int), adjusting offset for future calls."); 076 java7u25CompensationOffset = 1; 077 } 078 } 079 } catch (final Exception | LinkageError e) { 080 System.out.println("WARNING: sun.reflect.Reflection.getCallerClass is not supported. This will impact performance."); 081 getCallerClassMethod = null; 082 java7u25CompensationOffset = -1; 083 } 084 085 GET_CALLER_CLASS_METHOD = getCallerClassMethod; 086 JDK_7U25_OFFSET = java7u25CompensationOffset; 087 INSTANCE = new StackLocator(); 088 } 089 090 /** 091 * Gets the singleton instance. 092 * 093 * @return the singleton instance. 094 */ 095 public static StackLocator getInstance() { 096 return INSTANCE; 097 } 098 099 private StackLocator() { 100 } 101 102 // TODO: return Object.class instead of null (though it will have a null ClassLoader) 103 // (MS) I believe this would work without any modifications elsewhere, but I could be wrong 104 105 @PerformanceSensitive 106 public Class<?> getCallerClass(final Class<?> sentinelClass, final Predicate<Class<?>> callerPredicate) { 107 if (sentinelClass == null) { 108 throw new IllegalArgumentException("sentinelClass cannot be null"); 109 } 110 if (callerPredicate == null) { 111 throw new IllegalArgumentException("callerPredicate cannot be null"); 112 } 113 boolean foundSentinel = false; 114 Class<?> clazz; 115 for (int i = 2; null != (clazz = getCallerClass(i)); i++) { 116 if (sentinelClass.equals(clazz)) { 117 foundSentinel = true; 118 } else if (foundSentinel && callerPredicate.test(clazz)) { 119 return clazz; 120 } 121 } 122 return DEFAULT_CALLER_CLASS; 123 } 124 125 /** 126 * Gets the Class of the method that called <em>this</em> method at the location up the call stack by the given stack 127 * frame depth. 128 * <p> 129 * This method returns {@code null} if: 130 * </p> 131 * <ul> 132 * <li>{@code sun.reflect.Reflection.getCallerClass(int)} is not present.</li> 133 * <li>An exception is caught calling {@code sun.reflect.Reflection.getCallerClass(int)}.</li> 134 * </ul> 135 * 136 * @param depth The stack frame count to walk. 137 * @return A class or null. 138 * @throws IndexOutOfBoundsException if depth is negative. 139 */ 140 // migrated from ReflectiveCallerClassUtility 141 @PerformanceSensitive 142 public Class<?> getCallerClass(final int depth) { 143 if (depth < 0) { 144 throw new IndexOutOfBoundsException(Integer.toString(depth)); 145 } 146 if (GET_CALLER_CLASS_METHOD == null) { 147 return DEFAULT_CALLER_CLASS; 148 } 149 // note that we need to add 1 to the depth value to compensate for this method, but not for the Method.invoke 150 // since Reflection.getCallerClass ignores the call to Method.invoke() 151 try { 152 return (Class<?>) GET_CALLER_CLASS_METHOD.invoke(null, depth + 1 + JDK_7U25_OFFSET); 153 } catch (final Exception e) { 154 // theoretically this could happen if the caller class were native code 155 // TODO: return Object.class 156 return DEFAULT_CALLER_CLASS; 157 } 158 } 159 160 // migrated from Log4jLoggerFactory 161 @PerformanceSensitive 162 public Class<?> getCallerClass(final String fqcn, final String pkg) { 163 boolean next = false; 164 Class<?> clazz; 165 for (int i = 2; null != (clazz = getCallerClass(i)); i++) { 166 if (fqcn.equals(clazz.getName())) { 167 next = true; 168 continue; 169 } 170 if (next && clazz.getName().startsWith(pkg)) { 171 return clazz; 172 } 173 } 174 // TODO: return Object.class 175 return DEFAULT_CALLER_CLASS; 176 } 177 178 // added for use in LoggerAdapter implementations mainly 179 @PerformanceSensitive 180 public Class<?> getCallerClass(final Class<?> anchor) { 181 boolean next = false; 182 Class<?> clazz; 183 for (int i = 2; null != (clazz = getCallerClass(i)); i++) { 184 if (anchor.equals(clazz)) { 185 next = true; 186 continue; 187 } 188 if (next) { 189 return clazz; 190 } 191 } 192 return Object.class; 193 } 194 195 // migrated from ThrowableProxy 196 @PerformanceSensitive 197 public Deque<Class<?>> getCurrentStackTrace() { 198 // benchmarks show that using the SecurityManager is much faster than looping through getCallerClass(int) 199 if (PrivateSecurityManagerStackTraceUtil.isEnabled()) { 200 return PrivateSecurityManagerStackTraceUtil.getCurrentStackTrace(); 201 } 202 // slower version using getCallerClass where we cannot use a SecurityManager 203 final Deque<Class<?>> classes = new ArrayDeque<>(); 204 Class<?> clazz; 205 for (int i = 1; null != (clazz = getCallerClass(i)); i++) { 206 classes.push(clazz); 207 } 208 return classes; 209 } 210 211 public StackTraceElement calcLocation(final String fqcnOfLogger) { 212 if (fqcnOfLogger == null) { 213 return null; 214 } 215 // LOG4J2-1029 new Throwable().getStackTrace is faster than Thread.currentThread().getStackTrace(). 216 final StackTraceElement[] stackTrace = new Throwable().getStackTrace(); 217 boolean found = false; 218 for (int i = 0; i < stackTrace.length; i++) { 219 final String className = stackTrace[i].getClassName(); 220 if (fqcnOfLogger.equals(className)) { 221 found = true; 222 continue; 223 } 224 if (found && !fqcnOfLogger.equals(className)) { 225 return stackTrace[i]; 226 } 227 } 228 return null; 229 } 230 231 public StackTraceElement getStackTraceElement(final int depth) { 232 // (MS) I tested the difference between using Throwable.getStackTrace() and Thread.getStackTrace(), and 233 // the version using Throwable was surprisingly faster! at least on Java 1.8. See ReflectionBenchmark. 234 int i = 0; 235 for (final StackTraceElement element : new Throwable().getStackTrace()) { 236 if (isValid(element)) { 237 if (i == depth) { 238 return element; 239 } 240 ++i; 241 } 242 } 243 throw new IndexOutOfBoundsException(Integer.toString(depth)); 244 } 245 246 private boolean isValid(final StackTraceElement element) { 247 // ignore native methods (oftentimes are repeated frames) 248 if (element.isNativeMethod()) { 249 return false; 250 } 251 final String cn = element.getClassName(); 252 // ignore OpenJDK internal classes involved with reflective invocation 253 if (cn.startsWith("sun.reflect.")) { 254 return false; 255 } 256 final String mn = element.getMethodName(); 257 // ignore use of reflection including: 258 // Method.invoke 259 // InvocationHandler.invoke 260 // Constructor.newInstance 261 if (cn.startsWith("java.lang.reflect.") && (mn.equals("invoke") || mn.equals("newInstance"))) { 262 return false; 263 } 264 // ignore use of Java 1.9+ reflection classes 265 if (cn.startsWith("jdk.internal.reflect.")) { 266 return false; 267 } 268 // ignore Class.newInstance 269 if (cn.equals("java.lang.Class") && mn.equals("newInstance")) { 270 return false; 271 } 272 // ignore use of Java 1.7+ MethodHandle.invokeFoo() methods 273 if (cn.equals("java.lang.invoke.MethodHandle") && mn.startsWith("invoke")) { 274 return false; 275 } 276 // any others? 277 return true; 278 } 279}