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 */
017 package org.apache.logging.log4j.util;
018
019 import java.lang.reflect.Method;
020 import java.util.Stack;
021
022 import org.apache.logging.log4j.Logger;
023 import org.apache.logging.log4j.status.StatusLogger;
024
025 /**
026 * <em>Consider this class private.</em> Provides various methods to determine the caller class.
027 * <h3>Background</h3>
028 * <p>This method, available only in the Oracle/Sun/OpenJDK implementations of the Java
029 * Virtual Machine, is a much more efficient mechanism for determining the {@link Class} of the caller of a particular
030 * method. When it is not available, a {@link SecurityManager} is the second-best option. When this is also not
031 * possible, the {@code StackTraceElement[]} returned by {@link Throwable#getStackTrace()} must be used, and its
032 * {@code String} class name converted to a {@code Class} using the slow {@link Class#forName} (which can add an extra
033 * microsecond or more for each invocation 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
045 * Java. It does, however, work just fine in Sun JDK 1.6, OpenJDK 1.6, Oracle/OpenJDK 1.7, and Oracle/OpenJDK 1.8.
046 * Other Java environments may fall back to using {@link Throwable#getStackTrace()} which is significantly slower due
047 * to examination of every virtual frame of execution.
048 * </p>
049 */
050 public final class ReflectionUtil {
051
052 private static final Logger LOGGER = StatusLogger.getLogger();
053
054 private static final boolean SUN_REFLECTION_SUPPORTED;
055 private static final Method GET_CALLER_CLASS;
056 static final int JDK_7u25_OFFSET;
057 private static final PrivateSecurityManager SECURITY_MANAGER;
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 final Object test1 = getCallerClass.invoke(null, 0);
067 if (o == null || o != sunReflectionClass) {
068 LOGGER.warn("Unexpected return value from Reflection.getCallerClass(): {}", test1);
069 getCallerClass = null;
070 java7u25CompensationOffset = -1;
071 } else {
072 o = getCallerClass.invoke(null, 1);
073 if (o == sunReflectionClass) {
074 LOGGER.warn(
075 "You are using Java 1.7.0_25 which has a broken implementation of Reflection.getCallerClass.");
076 LOGGER.warn("You should upgrade to at least Java 1.7.0_40 or later.");
077 LOGGER.debug("Using stack depth compensation offset of 1 due to Java 7u25.");
078 java7u25CompensationOffset = 1;
079 }
080 }
081 } catch (final Exception e) {
082 LOGGER.info("sun.reflect.Reflection.getCallerClass is not supported. " +
083 "ReflectionUtil.getCallerClass will be much slower due to this.", e);
084 getCallerClass = null;
085 java7u25CompensationOffset = -1;
086 }
087
088 SUN_REFLECTION_SUPPORTED = getCallerClass != null;
089 GET_CALLER_CLASS = getCallerClass;
090 JDK_7u25_OFFSET = java7u25CompensationOffset;
091
092 PrivateSecurityManager psm;
093 try {
094 final SecurityManager sm = System.getSecurityManager();
095 if (sm != null) {
096 sm.checkPermission(new RuntimePermission("createSecurityManager"));
097 }
098 psm = new PrivateSecurityManager();
099 } catch (final SecurityException ignored) {
100 LOGGER.debug(
101 "Not allowed to create SecurityManager. Falling back to slowest ReflectionUtil implementation.");
102 psm = null;
103 }
104 SECURITY_MANAGER = psm;
105 }
106
107 public static boolean supportsFastReflection() {
108 return SUN_REFLECTION_SUPPORTED;
109 }
110
111 // TODO: return Object.class instead of null (though it will have a null ClassLoader)
112 // (MS) I believe this would work without any modifications elsewhere, but I could be wrong
113
114 // migrated from ReflectiveCallerClassUtility
115 public static Class<?> getCallerClass(final int depth) {
116 if (depth < 0) {
117 throw new IndexOutOfBoundsException(Integer.toString(depth));
118 }
119 // note that we need to add 1 to the depth value to compensate for this method, but not for the Method.invoke
120 // since Reflection.getCallerClass ignores the call to Method.invoke()
121 if (supportsFastReflection()) {
122 try {
123 return (Class<?>) GET_CALLER_CLASS.invoke(null, depth + 1 + JDK_7u25_OFFSET);
124 } catch (final Exception e) {
125 // theoretically this could happen if the caller class were native code
126 LOGGER.error("Error in ReflectionUtil.getCallerClass({}).", depth, e);
127 // TODO: return Object.class
128 return null;
129 }
130 }
131 // TODO: SecurityManager-based version?
132 // slower fallback method using stack trace
133 final StackTraceElement element = getEquivalentStackTraceElement(depth + 1);
134 try {
135 return LoaderUtil.loadClass(element.getClassName());
136 } catch (final ClassNotFoundException e) {
137 LOGGER.error("Could not find class in ReflectionUtil.getCallerClass({}).", depth, e);
138 }
139 // TODO: return Object.class
140 return null;
141 }
142
143 static StackTraceElement getEquivalentStackTraceElement(final int depth) {
144 // (MS) I tested the difference between using Throwable.getStackTrace() and Thread.getStackTrace(), and
145 // the version using Throwable was surprisingly faster! at least on Java 1.8. See ReflectionBenchmark.
146 final StackTraceElement[] elements = new Throwable().getStackTrace();
147 int i = 0;
148 for (final StackTraceElement element : elements) {
149 if (isValid(element)) {
150 if (i == depth) {
151 return element;
152 } else {
153 ++i;
154 }
155 }
156 }
157 LOGGER.error("Could not find an appropriate StackTraceElement at index {}", depth);
158 throw new IndexOutOfBoundsException(Integer.toString(depth));
159 }
160
161 private static boolean isValid(final StackTraceElement element) {
162 // ignore native methods (oftentimes are repeated frames)
163 if (element.isNativeMethod()) {
164 return false;
165 }
166 final String cn = element.getClassName();
167 // ignore OpenJDK internal classes involved with reflective invocation
168 if (cn.startsWith("sun.reflect.")) {
169 return false;
170 }
171 final String mn = element.getMethodName();
172 // ignore use of reflection including:
173 // Method.invoke
174 // InvocationHandler.invoke
175 // Constructor.newInstance
176 if (cn.startsWith("java.lang.reflect.") && (mn.equals("invoke") || mn.equals("newInstance"))) {
177 return false;
178 }
179 // ignore Class.newInstance
180 if (cn.equals("java.lang.Class") && mn.equals("newInstance")) {
181 return false;
182 }
183 // ignore use of Java 1.7+ MethodHandle.invokeFoo() methods
184 if (cn.equals("java.lang.invoke.MethodHandle") && mn.startsWith("invoke")) {
185 return false;
186 }
187 // any others?
188 return true;
189 }
190
191 // migrated from ClassLoaderContextSelector
192 public static Class<?> getCallerClass(final String fqcn) {
193 return getCallerClass(fqcn, Strings.EMPTY);
194 }
195
196 // migrated from Log4jLoggerFactory
197 public static Class<?> getCallerClass(final String fqcn, final String pkg) {
198 if (supportsFastReflection()) {
199 boolean next = false;
200 Class<?> clazz;
201 for (int i = 2; null != (clazz = getCallerClass(i)); i++) {
202 if (fqcn.equals(clazz.getName())) {
203 next = true;
204 continue;
205 }
206 if (next && clazz.getName().startsWith(pkg)) {
207 return clazz;
208 }
209 }
210 // TODO: return Object.class
211 return null;
212 }
213 if (SECURITY_MANAGER != null) {
214 return SECURITY_MANAGER.getCallerClass(fqcn, pkg);
215 }
216 try {
217 return LoaderUtil.loadClass(getCallerClassName(fqcn, pkg, new Throwable().getStackTrace()));
218 } catch (final ClassNotFoundException ignored) {
219 // no problem really
220 }
221 // TODO: return Object.class
222 return null;
223 }
224
225 // added for use in LoggerAdapter implementations mainly
226 public static Class<?> getCallerClass(final Class<?> anchor) {
227 if (supportsFastReflection()) {
228 boolean next = false;
229 Class<?> clazz;
230 for (int i = 2; null != (clazz = getCallerClass(i)); i++) {
231 if (anchor.equals(clazz)) {
232 next = true;
233 continue;
234 }
235 if (next) {
236 return clazz;
237 }
238 }
239 return Object.class;
240 }
241 if (SECURITY_MANAGER != null) {
242 return SECURITY_MANAGER.getCallerClass(anchor);
243 }
244 try {
245 return LoaderUtil.loadClass(getCallerClassName(anchor.getName(), Strings.EMPTY,
246 new Throwable().getStackTrace()));
247 } catch (final ClassNotFoundException ignored) {
248 // no problem really
249 }
250 return Object.class;
251 }
252
253 private static String getCallerClassName(final String fqcn, final String pkg, final StackTraceElement... elements) {
254 boolean next = false;
255 for (final StackTraceElement element : elements) {
256 final String className = element.getClassName();
257 if (className.equals(fqcn)) {
258 next = true;
259 continue;
260 }
261 if (next && className.startsWith(pkg)) {
262 return className;
263 }
264 }
265 return Object.class.getName();
266 }
267
268 // migrated from ThrowableProxy
269 public static Stack<Class<?>> getCurrentStackTrace() {
270 // benchmarks show that using the SecurityManager is much faster than looping through getCallerClass(int)
271 if (SECURITY_MANAGER != null) {
272 final Class<?>[] array = SECURITY_MANAGER.getClassContext();
273 final Stack<Class<?>> classes = new Stack<Class<?>>();
274 classes.ensureCapacity(array.length);
275 for (final Class<?> clazz : array) {
276 classes.push(clazz);
277 }
278 return classes;
279 }
280 // slower version using getCallerClass where we cannot use a SecurityManager
281 if (supportsFastReflection()) {
282 final Stack<Class<?>> classes = new Stack<Class<?>>();
283 Class<?> clazz;
284 for (int i = 1; null != (clazz = getCallerClass(i)); i++) {
285 classes.push(clazz);
286 }
287 return classes;
288 }
289 return new Stack<Class<?>>();
290 }
291
292 static final class PrivateSecurityManager extends SecurityManager {
293
294 @Override
295 protected Class<?>[] getClassContext() {
296 return super.getClassContext();
297 }
298
299 protected Class<?> getCallerClass(final String fqcn, final String pkg) {
300 boolean next = false;
301 for (final Class<?> clazz : getClassContext()) {
302 if (fqcn.equals(clazz.getName())) {
303 next = true;
304 continue;
305 }
306 if (next && clazz.getName().startsWith(pkg)) {
307 return clazz;
308 }
309 }
310 // TODO: return Object.class
311 return null;
312 }
313
314 protected Class<?> getCallerClass(final Class<?> anchor) {
315 boolean next = false;
316 for (final Class<?> clazz : getClassContext()) {
317 if (anchor.equals(clazz)) {
318 next = true;
319 continue;
320 }
321 if (next) {
322 return clazz;
323 }
324 }
325 return Object.class;
326 }
327
328 }
329
330 private ReflectionUtil() {
331 }
332 }