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.util;
    18
    19  import java.lang.reflect.Method;
    20  import java.util.Stack;
    21
    22  import org.apache.logging.log4j.Logger;
    23  import org.apache.logging.log4j.status.StatusLogger;
    24
    25  /**
    26   * <em>Consider this class private.</em> Provides various methods to determine the caller class.
    27   * <h3>Background</h3>
    28   * <p>This method, available only in the Oracle/Sun/OpenJDK implementations of the Java
    29   * Virtual Machine, is a much more efficient mechanism for determining the {@link Class} of the caller of a particular
    30   * method. When it is not available, a {@link SecurityManager} is the second-best option. When this is also not
    31   * possible, the {@code StackTraceElement[]} returned by {@link Throwable#getStackTrace()} must be used, and its
    32   * {@code String} class name converted to a {@code Class} using the slow {@link Class#forName} (which can add an extra
    33   * microsecond or more for each invocation depending on the runtime ClassLoader hierarchy).
    34   * </p>
    35   * <p>
    36   * During Java 8 development, the {@code sun.reflect.Reflection.getCallerClass(int)} was removed from OpenJDK, and this
    37   * 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
    38   * by one stack frame. This turned out to be beneficial for the survival of this API as the change broke hundreds of
    39   * libraries and frameworks relying on the API which brought much more attention to the intended API removal.
    40   * </p>
    41   * <p>
    42   * After much community backlash, the JDK team agreed to restore {@code getCallerClass(int)} and keep its existing
    43   * behavior for the rest of Java 7. However, the method is deprecated in Java 8, and current Java 9 development has not
    44   * addressed this API. Therefore, the functionality of this class cannot be relied upon for all future versions of
    45   * Java. It does, however, work just fine in Sun JDK 1.6, OpenJDK 1.6, Oracle/OpenJDK 1.7, and Oracle/OpenJDK 1.8.
    46   * Other Java environments may fall back to using {@link Throwable#getStackTrace()} which is significantly slower due
    47   * to examination of every virtual frame of execution.
    48   * </p>
    49   */
    50  public final class ReflectionUtil {
    51
    52      private static final Logger LOGGER = StatusLogger.getLogger();
    53
    54      private static final boolean SUN_REFLECTION_SUPPORTED;
    55      private static final Method GET_CALLER_CLASS;
    56      static final int JDK_7u25_OFFSET;
    57      private static final PrivateSecurityManager SECURITY_MANAGER;
    58
    59      static {
    60          Method getCallerClass;
    61          int java7u25CompensationOffset = 0;
    62          try {
    63              final Class<?> sunReflectionClass = LoaderUtil.loadClass("sun.reflect.Reflection");
    64              getCallerClass = sunReflectionClass.getDeclaredMethod("getCallerClass", int.class);
    65              Object o = getCallerClass.invoke(null, 0);
    66              final Object test1 = getCallerClass.invoke(null, 0);
    67              if (o == null || o != sunReflectionClass) {
    68                  LOGGER.warn("Unexpected return value from Reflection.getCallerClass(): {}", test1);
    69                  getCallerClass = null;
    70                  java7u25CompensationOffset = -1;
    71              } else {
    72                  o = getCallerClass.invoke(null, 1);
    73                  if (o == sunReflectionClass) {
    74                      LOGGER.warn(
    75                          "You are using Java 1.7.0_25 which has a broken implementation of Reflection.getCallerClass.");
    76                      LOGGER.warn("You should upgrade to at least Java 1.7.0_40 or later.");
    77                      LOGGER.debug("Using stack depth compensation offset of 1 due to Java 7u25.");
    78                      java7u25CompensationOffset = 1;
    79                  }
    80              }
    81          } catch (final Exception e) {
    82              LOGGER.info("sun.reflect.Reflection.getCallerClass is not supported. " +
    83                  "ReflectionUtil.getCallerClass will be much slower due to this.", e);
    84              getCallerClass = null;
    85              java7u25CompensationOffset = -1;
    86          }
    87
    88          SUN_REFLECTION_SUPPORTED = getCallerClass != null;
    89          GET_CALLER_CLASS = getCallerClass;
    90          JDK_7u25_OFFSET = java7u25CompensationOffset;
    91
    92          PrivateSecurityManager psm;
    93          try {
    94              final SecurityManager sm = System.getSecurityManager();
    95              if (sm != null) {
    96                  sm.checkPermission(new RuntimePermission("createSecurityManager"));
    97              }
    98              psm = new PrivateSecurityManager();
    99          } 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 }