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.core.impl; 018 019 import java.lang.reflect.InvocationTargetException; 020 import java.lang.reflect.Method; 021 import java.lang.reflect.Modifier; 022 023 import org.apache.logging.log4j.Logger; 024 import org.apache.logging.log4j.core.util.Loader; 025 import org.apache.logging.log4j.status.StatusLogger; 026 027 /** 028 * Utility class that handles the instability of the Sun/OpenJDK {@code sun.reflect.Reflection.getCallerClass(int)} 029 * method. 030 * <p> 031 * <strong>Background:</strong> This method, available only in the Oracle/Sun/OpenJDK implementations of the Java 032 * Virtual Machine, is a much more efficient mechanism for determining the {@link Class} of the caller of a particular 033 * method. When it is not available, a {@link SecurityManager} is the second-best option. When this is also not 034 * possible, the {@code StackTraceElement[]} returned by {@link Thread#getStackTrace()} must be used, and its 035 * {@code String} class name converted to a {@code Class} using the slow {@link Class#forName}. 036 * </p> 037 * <p> 038 * As of Java 8, the {@code getCallerClass(int)} method has been removed from Oracle/OpenJDK and is no longer usable. A 039 * back-port of the feature that resulted in this change was made in Java 7u25, but the {@code getCallerClass(int)} was 040 * left around for that version and deprecated, with the intention of being removed in 7u40. By coincidence, the change 041 * actually broke {@code getCallerClass(int)} (the return value was inadvertently offset by 1 stack frame). This was 042 * actually a good thing, because it made the hundreds of libraries and frameworks relying on this method aware of what 043 * the JDK developers were up to. 044 * </p> 045 * <p> 046 * After much community backlash, the JDK team agreed to restore {@code getCallerClass(int)} and keep its existing 047 * behavior for the rest of Java 7. However, the method is deprecated in Java 8, supposedly won't be in Java 9 (unless 048 * no public API is exposed which may force them to keep it), and so backup options must be used. This class: 049 * </p> 050 * <ul> 051 * <li>Uses {@code getCallerClass(int)} the traditional way when possible.</li> 052 * <li>Uses {@code getCallerClass(int)} with an adjusted offset in Oracle/OpenJDK 7u25.</li> 053 * <li>Returns null otherwise. (Currently, it is the caller's responsibility to use the backup mechanisms.)</li> 054 * </ul> 055 * <p> 056 * <strong>IMPORTANT NOTE:</strong> This class should not be relied upon. It is considered an internal class and could 057 * change at any time, breaking your code if you use it. Specifically, as a possible public API replacement for 058 * {@code getCallerClass(int)} develops in Java 9, this class is very likely to change or even go away. 059 * </p> 060 */ 061 public final class ReflectiveCallerClassUtility { 062 063 private static final Logger LOGGER = StatusLogger.getLogger(); 064 065 private static final boolean GET_CALLER_CLASS_SUPPORTED; 066 067 private static final Method GET_CALLER_CLASS_METHOD; 068 069 static final int JAVA_7U25_COMPENSATION_OFFSET; 070 071 static { 072 Method getCallerClass = null; 073 int java7u25CompensationOffset = 0; 074 075 try { 076 final ClassLoader loader = Loader.getClassLoader(); 077 // Use wildcard to avoid compile-time reference. 078 final Class<?> clazz = loader.loadClass("sun.reflect.Reflection"); 079 final Method[] methods = clazz.getMethods(); 080 for (final Method method : methods) { 081 final int modifier = method.getModifiers(); 082 final Class<?>[] parameterTypes = method.getParameterTypes(); 083 if (method.getName().equals("getCallerClass") && Modifier.isStatic(modifier) && 084 parameterTypes.length == 1 && parameterTypes[0] == int.class) { 085 getCallerClass = method; 086 break; 087 } 088 } 089 090 if (getCallerClass == null) { 091 LOGGER.info("sun.reflect.Reflection#getCallerClass does not exist."); 092 } else { 093 Object o = getCallerClass.invoke(null, 0); 094 if (o == null || o != clazz) { 095 getCallerClass = null; 096 LOGGER.warn("sun.reflect.Reflection#getCallerClass returned unexpected value of [{}] and is " + 097 "unusable. Will fall back to another option.", o); 098 } else { 099 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 }