View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.core.impl;
18  
19  import java.lang.reflect.InvocationTargetException;
20  import java.lang.reflect.Method;
21  import java.lang.reflect.Modifier;
22  
23  import org.apache.logging.log4j.Logger;
24  import org.apache.logging.log4j.core.util.Loader;
25  import org.apache.logging.log4j.status.StatusLogger;
26  
27  /**
28   * Utility class that handles the instability of the Sun/OpenJDK {@code sun.reflect.Reflection.getCallerClass(int)}
29   * method.
30   * <p>
31   * <strong>Background:</strong> This method, available only in the Oracle/Sun/OpenJDK implementations of the Java
32   * Virtual Machine, is a much more efficient mechanism for determining the {@link Class} of the caller of a particular
33   * method. When it is not available, a {@link SecurityManager} is the second-best option. When this is also not
34   * possible, the {@code StackTraceElement[]} returned by {@link Thread#getStackTrace()} must be used, and its
35   * {@code String} class name converted to a {@code Class} using the slow {@link Class#forName}.
36   * </p>
37   * <p>
38   * As of Java 8, the {@code getCallerClass(int)} method has been removed from Oracle/OpenJDK and is no longer usable. A
39   * back-port of the feature that resulted in this change was made in Java 7u25, but the {@code getCallerClass(int)} was
40   * left around for that version and deprecated, with the intention of being removed in 7u40. By coincidence, the change
41   * actually broke {@code getCallerClass(int)} (the return value was inadvertently offset by 1 stack frame). This was
42   * actually a good thing, because it made the hundreds of libraries and frameworks relying on this method aware of what
43   * the JDK developers were up to.
44   * </p>
45   * <p>
46   * After much community backlash, the JDK team agreed to restore {@code getCallerClass(int)} and keep its existing
47   * behavior for the rest of Java 7. However, the method is deprecated in Java 8, supposedly won't be in Java 9 (unless
48   * no public API is exposed which may force them to keep it), and so backup options must be used. This class:
49   * </p>
50   * <ul>
51   *     <li>Uses {@code getCallerClass(int)} the traditional way when possible.</li>
52   *     <li>Uses {@code getCallerClass(int)} with an adjusted offset in Oracle/OpenJDK 7u25.</li>
53   *     <li>Returns null otherwise. (Currently, it is the caller's responsibility to use the backup mechanisms.)</li>
54   * </ul>
55   * <p>
56   * <strong>IMPORTANT NOTE:</strong> This class should not be relied upon. It is considered an internal class and could
57   * change at any time, breaking your code if you use it. Specifically, as a possible public API replacement for
58   * {@code getCallerClass(int)} develops in Java 9, this class is very likely to change or even go away.
59   * </p>
60   */
61  public final class ReflectiveCallerClassUtility {
62  
63      private static final Logger LOGGER = StatusLogger.getLogger();
64  
65      private static final boolean GET_CALLER_CLASS_SUPPORTED;
66  
67      private static final Method GET_CALLER_CLASS_METHOD;
68  
69      static final int JAVA_7U25_COMPENSATION_OFFSET;
70  
71      static {
72          Method getCallerClass = null;
73          int java7u25CompensationOffset = 0;
74  
75          try {
76              final ClassLoader loader = Loader.getClassLoader();
77              // Use wildcard to avoid compile-time reference.
78              final Class<?> clazz = loader.loadClass("sun.reflect.Reflection");
79              final Method[] methods = clazz.getMethods();
80              for (final Method method : methods) {
81                  final int modifier = method.getModifiers();
82                  final Class<?>[] parameterTypes = method.getParameterTypes();
83                  if (method.getName().equals("getCallerClass") && Modifier.isStatic(modifier) &&
84                          parameterTypes.length == 1 && parameterTypes[0] == int.class) {
85                      getCallerClass = method;
86                      break;
87                  }
88              }
89  
90              if (getCallerClass == null) {
91                  LOGGER.info("sun.reflect.Reflection#getCallerClass does not exist.");
92              } else {
93                  Object o = getCallerClass.invoke(null, 0);
94                  if (o == null || o != clazz) {
95                      getCallerClass = null;
96                      LOGGER.warn("sun.reflect.Reflection#getCallerClass returned unexpected value of [{}] and is " +
97                              "unusable. Will fall back to another option.", o);
98                  } else {
99                      o = getCallerClass.invoke(null, 1);
100                     if (o == clazz) {
101                         java7u25CompensationOffset = 1;
102                         LOGGER.warn("sun.reflect.Reflection#getCallerClass is broken in Java 7u25. " +
103                                 "You should upgrade to 7u40. Using alternate stack offset to compensate.");
104                     }
105                 }
106             }
107         } catch (final ClassNotFoundException e) {
108             LOGGER.info("sun.reflect.Reflection is not installed.");
109         } catch (final IllegalAccessException e) {
110             LOGGER.info("sun.reflect.Reflection#getCallerClass is not accessible.");
111         } catch (final InvocationTargetException e) {
112             LOGGER.info("sun.reflect.Reflection#getCallerClass is not supported.");
113         }
114 
115         if (getCallerClass == null) {
116             GET_CALLER_CLASS_SUPPORTED = false;
117             GET_CALLER_CLASS_METHOD = null;
118             JAVA_7U25_COMPENSATION_OFFSET = -1;
119         } else {
120             GET_CALLER_CLASS_SUPPORTED = true;
121             GET_CALLER_CLASS_METHOD = getCallerClass;
122             JAVA_7U25_COMPENSATION_OFFSET = java7u25CompensationOffset;
123         }
124     }
125 
126     private ReflectiveCallerClassUtility() {
127 
128     }
129 
130     /**
131      * Indicates whether {@code getCallerClass(int)} can be used on this JVM.
132      *
133      * @return {@code true} if it can be used. If {@code false}, {@link #getCaller} should not be called. Use a backup
134      *         mechanism instead.
135      */
136     public static boolean isSupported() {
137         return GET_CALLER_CLASS_SUPPORTED;
138     }
139 
140     /**
141      * Reflectively calls {@code getCallerClass(int)}, compensating for the additional frame on the stack, and
142      * compensating for the Java 7u25 bug if necessary. You should check with {@link #isSupported} before using this
143      * method.
144      *
145      * @param depth The depth of the caller to retrieve.
146      * @return the caller class, or {@code null} if {@code getCallerClass(int)} is not supported.
147      */
148     public static Class<?> getCaller(final int depth) {
149         if (!GET_CALLER_CLASS_SUPPORTED) {
150             return null;
151         }
152 
153         try {
154             return (Class<?>) GET_CALLER_CLASS_METHOD.invoke(null, depth + 1 + JAVA_7U25_COMPENSATION_OFFSET);
155         } catch (final IllegalAccessException ignore) {
156             LOGGER.warn("Should not have failed to call getCallerClass.");
157         } catch (final InvocationTargetException ignore) {
158             LOGGER.warn("Should not have failed to call getCallerClass.");
159         }
160         return null;
161     }
162 }