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.core.impl; 018 019import java.lang.reflect.InvocationTargetException; 020import java.lang.reflect.Method; 021import java.lang.reflect.Modifier; 022 023import org.apache.logging.log4j.Logger; 024import org.apache.logging.log4j.core.helpers.Loader; 025import 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.<br> 030 * <br> 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}.<br> 036 * <br> 037 * As of Java 8, the {@code getCallerClass(int)} method has been removed from Oracle/OpenJDK and is no longer usable. A 038 * back-port of the feature that resulted in this change was made in Java 7u25, but the {@code getCallerClass(int)} was 039 * left around for that version and deprecated, with the intention of being removed in 7u40. By coincidence, the change 040 * actually broke {@code getCallerClass(int)} (the return value was inadvertently offset by 1 stack frame). This was 041 * actually a good thing, because it made the hundreds of libraries and frameworks relying on this method aware of what 042 * the JDK developers were up to.<br> 043 * <br> 044 * After much community backlash, the JDK team agreed to restore {@code getCallerClass(int)} and keep its existing 045 * behavior for the rest of Java 7. However, the method will still not be available in Java 8, and so backup options 046 * must be used. This class:<br> 047 * <ul> 048 * <li>Uses {@code getCallerClass(int)} the traditional way when possible.</li> 049 * <li>Uses {@code getCallerClass(int)} with an adjusted offset in Oracle/OpenJDK 7u25.</li> 050 * <li>Returns null otherwise. (Currently, it is the caller's responsibility to use the backup mechanisms.)</li> 051 * </ul> 052 * <br> 053 * <strong>IMPORTANT NOTE:</strong> This class should not be relied upon. It is considered an internal class and could 054 * change at any time, breaking your code if you use it. Specifically, as a possible public API replacement for 055 * {@code getCallerClass(int)} develops in Java 8, this class is very likely to change or even go away. 056 */ 057public final class ReflectiveCallerClassUtility { 058 059 private static final Logger LOGGER = StatusLogger.getLogger(); 060 061 private static final boolean GET_CALLER_CLASS_SUPPORTED; 062 063 private static final Method GET_CALLER_CLASS_METHOD; 064 065 static final int JAVA_7U25_COMPENSATION_OFFSET; 066 067 static { 068 Method getCallerClass = null; 069 int java7u25CompensationOffset = 0; 070 071 try { 072 final ClassLoader loader = Loader.getClassLoader(); 073 // Use wildcard to avoid compile-time reference. 074 final Class<?> clazz = loader.loadClass("sun.reflect.Reflection"); 075 final Method[] methods = clazz.getMethods(); 076 for (final Method method : methods) { 077 final int modifier = method.getModifiers(); 078 final Class<?>[] parameterTypes = method.getParameterTypes(); 079 if (method.getName().equals("getCallerClass") && Modifier.isStatic(modifier) && 080 parameterTypes.length == 1 && parameterTypes[0] == int.class) { 081 getCallerClass = method; 082 break; 083 } 084 } 085 086 if (getCallerClass == null) { 087 LOGGER.info("sun.reflect.Reflection#getCallerClass does not exist."); 088 } else { 089 Object o = getCallerClass.invoke(null, 0); 090 if (o == null || o != clazz) { 091 getCallerClass = null; 092 LOGGER.warn("sun.reflect.Reflection#getCallerClass returned unexpected value of [{}] and is " + 093 "unusable. Will fall back to another option.", o); 094 } else { 095 o = getCallerClass.invoke(null, 1); 096 if (o == clazz) { 097 java7u25CompensationOffset = 1; 098 LOGGER.warn("sun.reflect.Reflection#getCallerClass is broken in Java 7u25. " + 099 "You should upgrade to 7u40. Using alternate stack offset to compensate."); 100 } 101 } 102 } 103 } catch (final ClassNotFoundException e) { 104 LOGGER.info("sun.reflect.Reflection is not installed."); 105 } catch (final IllegalAccessException e) { 106 LOGGER.info("sun.reflect.Reflection#getCallerClass is not accessible."); 107 } catch (final InvocationTargetException e) { 108 LOGGER.info("sun.reflect.Reflection#getCallerClass is not supported."); 109 } 110 111 if (getCallerClass == null) { 112 GET_CALLER_CLASS_SUPPORTED = false; 113 GET_CALLER_CLASS_METHOD = null; 114 JAVA_7U25_COMPENSATION_OFFSET = -1; 115 } else { 116 GET_CALLER_CLASS_SUPPORTED = true; 117 GET_CALLER_CLASS_METHOD = getCallerClass; 118 JAVA_7U25_COMPENSATION_OFFSET = java7u25CompensationOffset; 119 } 120 } 121 122 private ReflectiveCallerClassUtility() { 123 124 } 125 126 /** 127 * Indicates whether {@code getCallerClass(int)} can be used on this JVM. 128 * 129 * @return {@code true} if it can be used. If {@code false}, {@link #getCaller} should not be called. Use a backup 130 * mechanism instead. 131 */ 132 public static boolean isSupported() { 133 return GET_CALLER_CLASS_SUPPORTED; 134 } 135 136 /** 137 * Reflectively calls {@code getCallerClass(int)}, compensating for the additional frame on the stack, and 138 * compensating for the Java 7u25 bug if necessary. You should check with {@link #isSupported} before using this 139 * method. 140 * 141 * @param depth The depth of the caller to retrieve. 142 * @return the caller class, or {@code null} if {@code getCallerClass(int)} is not supported. 143 */ 144 public static Class<?> getCaller(int depth) { 145 if (!GET_CALLER_CLASS_SUPPORTED) { 146 return null; 147 } 148 149 try { 150 return (Class<?>) GET_CALLER_CLASS_METHOD.invoke(null, depth + 1 + JAVA_7U25_COMPENSATION_OFFSET); 151 } catch (IllegalAccessException ignore) { 152 LOGGER.warn("Should not have failed to call getCallerClass."); 153 } catch (InvocationTargetException ignore) { 154 LOGGER.warn("Should not have failed to call getCallerClass."); 155 } 156 return null; 157 } 158}