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