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