From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: snoopdoooggyttv Date: Wed, 5 May 2021 20:32:22 +0200 Subject: [PATCH] Fix Log4j Warning diff --git a/src/main/java/org/apache/logging/log4j/util/StackLocator.java b/src/main/java/org/apache/logging/log4j/util/StackLocator.java new file mode 100644 index 0000000000000000000000000000000000000000..44161840e64946b4b6ce0483495304809d15ade8 --- /dev/null +++ b/src/main/java/org/apache/logging/log4j/util/StackLocator.java @@ -0,0 +1,250 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.util; + +import java.lang.reflect.Method; +import java.util.Stack; + +/** + * Consider this class private. Provides various methods to determine the caller class.

Background

+ *

+ * This method, available only in the Oracle/Sun/OpenJDK implementations of the Java Virtual Machine, is a much more + * efficient mechanism for determining the {@link Class} of the caller of a particular method. When it is not available, + * a {@link SecurityManager} is the second-best option. When this is also not possible, the {@code StackTraceElement[]} + * returned by {@link Throwable#getStackTrace()} must be used, and its {@code String} class name converted to a + * {@code Class} using the slow {@link Class#forName} (which can add an extra microsecond or more for each invocation + * depending on the runtime ClassLoader hierarchy). + *

+ *

+ * During Java 8 development, the {@code sun.reflect.Reflection.getCallerClass(int)} was removed from OpenJDK, and this + * 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 + * by one stack frame. This turned out to be beneficial for the survival of this API as the change broke hundreds of + * libraries and frameworks relying on the API which brought much more attention to the intended API removal. + *

+ *

+ * After much community backlash, the JDK team agreed to restore {@code getCallerClass(int)} and keep its existing + * behavior for the rest of Java 7. However, the method is deprecated in Java 8, and current Java 9 development has not + * addressed this API. Therefore, the functionality of this class cannot be relied upon for all future versions of Java. + * It does, however, work just fine in Sun JDK 1.6, OpenJDK 1.6, Oracle/OpenJDK 1.7, and Oracle/OpenJDK 1.8. Other Java + * environments may fall back to using {@link Throwable#getStackTrace()} which is significantly slower due to + * examination of every virtual frame of execution. + *

+ */ +public final class StackLocator { + + // Checkstyle Suppress: the lower-case 'u' ticks off CheckStyle... + // CHECKSTYLE:OFF + static final int JDK_7u25_OFFSET; + // CHECKSTYLE:OFF + + private static final Method GET_CALLER_CLASS; + + private static final StackLocator INSTANCE; + + static { + Method getCallerClass; + int java7u25CompensationOffset = 0; + try { + final Class sunReflectionClass = LoaderUtil.loadClass("sun.reflect.Reflection"); + getCallerClass = sunReflectionClass.getDeclaredMethod("getCallerClass", int.class); + Object o = getCallerClass.invoke(null, 0); + getCallerClass.invoke(null, 0); + if (o == null || o != sunReflectionClass) { + getCallerClass = null; + java7u25CompensationOffset = -1; + } else { + o = getCallerClass.invoke(null, 1); + if (o == sunReflectionClass) { + System.out.println("WARNING: Java 1.7.0_25 is in use which has a broken implementation of Reflection.getCallerClass(). " + + " Please consider upgrading to Java 1.7.0_40 or later."); + java7u25CompensationOffset = 1; + } + } + } catch (final Exception | LinkageError e) { + //System.out.println("WARNING: sun.reflect.Reflection.getCallerClass is not supported. This will impact performance."); // Yatopia - Fix Error Message + getCallerClass = null; + java7u25CompensationOffset = -1; + } + + GET_CALLER_CLASS = getCallerClass; + JDK_7u25_OFFSET = java7u25CompensationOffset; + + INSTANCE = new StackLocator(); + } + + public static StackLocator getInstance() { + return INSTANCE; + } + + private StackLocator() { + } + + // TODO: return Object.class instead of null (though it will have a null ClassLoader) + // (MS) I believe this would work without any modifications elsewhere, but I could be wrong + + // migrated from ReflectiveCallerClassUtility + @PerformanceSensitive + public Class getCallerClass(final int depth) { + if (depth < 0) { + throw new IndexOutOfBoundsException(Integer.toString(depth)); + } + if (GET_CALLER_CLASS == null) { + return null; + } + // note that we need to add 1 to the depth value to compensate for this method, but not for the Method.invoke + // since Reflection.getCallerClass ignores the call to Method.invoke() + try { + return (Class) GET_CALLER_CLASS.invoke(null, depth + 1 + JDK_7u25_OFFSET); + } catch (final Exception e) { + // theoretically this could happen if the caller class were native code + // TODO: return Object.class + return null; + } + } + + // migrated from Log4jLoggerFactory + @PerformanceSensitive + public Class getCallerClass(final String fqcn, final String pkg) { + return getCallerClass(fqcn, pkg, 0); + } + + @PerformanceSensitive + public Class getCallerClass(final String fqcn, final String pkg, final int skipDepth) { + if (skipDepth < 0) { + throw new IllegalArgumentException("skipDepth cannot be negative"); + } + boolean next = false; + Class clazz; + for (int i = 2; null != (clazz = getCallerClass(i)); i++) { + if (fqcn.equals(clazz.getName())) { + next = true; + continue; + } + if (next && clazz.getName().startsWith(pkg)) { + return skipDepth == 0 + ? clazz + : getCallerClass(i + skipDepth); + } + } + // TODO: return Object.class + return null; + } + + // added for use in LoggerAdapter implementations mainly + @PerformanceSensitive + public Class getCallerClass(final Class anchor) { + boolean next = false; + Class clazz; + for (int i = 2; null != (clazz = getCallerClass(i)); i++) { + if (anchor.equals(clazz)) { + next = true; + continue; + } + if (next) { + return clazz; + } + } + return Object.class; + } + + // migrated from ThrowableProxy + @PerformanceSensitive + public Stack> getCurrentStackTrace() { + // benchmarks show that using the SecurityManager is much faster than looping through getCallerClass(int) + if (PrivateSecurityManagerStackTraceUtil.isEnabled()) { + return PrivateSecurityManagerStackTraceUtil.getCurrentStackTrace(); + } + // slower version using getCallerClass where we cannot use a SecurityManager + final Stack> classes = new Stack<>(); + Class clazz; + for (int i = 1; null != (clazz = getCallerClass(i)); i++) { + classes.push(clazz); + } + return classes; + } + + public StackTraceElement calcLocation(final String fqcnOfLogger) { + if (fqcnOfLogger == null) { + return null; + } + // LOG4J2-1029 new Throwable().getStackTrace is faster than Thread.currentThread().getStackTrace(). + final StackTraceElement[] stackTrace = new Throwable().getStackTrace(); + boolean found = false; + for (int i = 0; i < stackTrace.length; i++) { + final String className = stackTrace[i].getClassName(); + if (fqcnOfLogger.equals(className)) { + + found = true; + continue; + } + if (found && !fqcnOfLogger.equals(className)) { + return stackTrace[i]; + } + } + return null; + } + + public StackTraceElement getStackTraceElement(final int depth) { + // (MS) I tested the difference between using Throwable.getStackTrace() and Thread.getStackTrace(), and + // the version using Throwable was surprisingly faster! at least on Java 1.8. See ReflectionBenchmark. + final StackTraceElement[] elements = new Throwable().getStackTrace(); + int i = 0; + for (final StackTraceElement element : elements) { + if (isValid(element)) { + if (i == depth) { + return element; + } + ++i; + } + } + throw new IndexOutOfBoundsException(Integer.toString(depth)); + } + + private boolean isValid(final StackTraceElement element) { + // ignore native methods (oftentimes are repeated frames) + if (element.isNativeMethod()) { + return false; + } + final String cn = element.getClassName(); + // ignore OpenJDK internal classes involved with reflective invocation + if (cn.startsWith("sun.reflect.")) { + return false; + } + final String mn = element.getMethodName(); + // ignore use of reflection including: + // Method.invoke + // InvocationHandler.invoke + // Constructor.newInstance + if (cn.startsWith("java.lang.reflect.") && (mn.equals("invoke") || mn.equals("newInstance"))) { + return false; + } + // ignore use of Java 1.9+ reflection classes + if (cn.startsWith("jdk.internal.reflect.")) { + return false; + } + // ignore Class.newInstance + if (cn.equals("java.lang.Class") && mn.equals("newInstance")) { + return false; + } + // ignore use of Java 1.7+ MethodHandle.invokeFoo() methods + if (cn.equals("java.lang.invoke.MethodHandle") && mn.startsWith("invoke")) { + return false; + } + // any others? + return true; + } +}