This is an automated email from the ASF dual-hosted git repository. mattsicker pushed a commit to branch 2.x in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git
commit 80de81652e0d3eaf195402c2bdc28316a8c71dde Author: Matt Sicker <[email protected]> AuthorDate: Fri Nov 3 18:16:25 2023 -0500 Backport LoaderUtil updates Most of what's changed in 3.0 minus the JPMS-specific code. Signed-off-by: Matt Sicker <[email protected]> --- .../java/org/apache/logging/log4j/util/Cast.java | 40 +++ .../logging/log4j/util/InternalException.java | 54 ++++ .../org/apache/logging/log4j/util/LoaderUtil.java | 345 +++++++++++++++------ .../apache/logging/log4j/util/package-info.java | 2 +- 4 files changed, 353 insertions(+), 88 deletions(-) diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/Cast.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/Cast.java new file mode 100644 index 0000000000..8a1dc40975 --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/Cast.java @@ -0,0 +1,40 @@ +/* + * 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; + +@InternalApi +public final class Cast { + + /** + * Returns the provided object cast to the generic parameter type or null when the argument is null. + * + * @param o object to cast + * @param <T> the type to cast + * @return object after casting or null if the object was null + * @throws ClassCastException if the object cannot be cast to the provided type + */ + public static <T> T cast(final Object o) { + if (o == null) { + return null; + } + @SuppressWarnings("unchecked") final T t = (T) o; + return t; + } + + private Cast() { + } +} diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/InternalException.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/InternalException.java new file mode 100644 index 0000000000..486aa8b84d --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/InternalException.java @@ -0,0 +1,54 @@ +/* + * 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; + +/** + * Exception thrown when an error occurs while accessing internal resources. This is generally used to + * convert checked exceptions to runtime exceptions. + */ +public class InternalException extends RuntimeException { + + private static final long serialVersionUID = 6366395965071580537L; + + /** + * Construct an exception with a message. + * + * @param message The reason for the exception + */ + public InternalException(final String message) { + super(message); + } + + /** + * Construct an exception with a message and underlying cause. + * + * @param message The reason for the exception + * @param cause The underlying cause of the exception + */ + public InternalException(final String message, final Throwable cause) { + super(message, cause); + } + + /** + * Construct an exception with an underlying cause. + * + * @param cause The underlying cause of the exception + */ + public InternalException(final Throwable cause) { + super(cause); + } +} diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/LoaderUtil.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/LoaderUtil.java index b39291cd35..cbc5044759 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/LoaderUtil.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/LoaderUtil.java @@ -17,6 +17,7 @@ package org.apache.logging.log4j.util; import java.io.IOException; +import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.net.URL; import java.security.AccessController; @@ -34,10 +35,9 @@ import java.util.Objects; * @see Thread#getContextClassLoader() * @see ClassLoader#getSystemClassLoader() */ +@InternalApi public final class LoaderUtil { - private static final ClassLoader[] EMPTY_CLASS_LOADER_ARRAY = {}; - /** * System property to set to ignore the thread context ClassLoader. * @@ -45,24 +45,34 @@ public final class LoaderUtil { */ public static final String IGNORE_TCCL_PROPERTY = "log4j.ignoreTCL"; - private static final SecurityManager SECURITY_MANAGER = System.getSecurityManager(); - // this variable must be lazily loaded; otherwise, we get a nice circular class loading problem where LoaderUtil // wants to use PropertiesUtil, but then PropertiesUtil wants to use LoaderUtil. private static Boolean ignoreTCCL; + private static final RuntimePermission GET_CLASS_LOADER = new RuntimePermission("getClassLoader"); private static final boolean GET_CLASS_LOADER_DISABLED; private static final PrivilegedAction<ClassLoader> TCCL_GETTER = new ThreadContextClassLoaderGetter(); static { - if (SECURITY_MANAGER != null) { + if (System.getSecurityManager() != null) { boolean getClassLoaderDisabled; try { - SECURITY_MANAGER.checkPermission(new RuntimePermission("getClassLoader")); + AccessController.checkPermission(GET_CLASS_LOADER); + // seems like we'll be ok getClassLoaderDisabled = false; } catch (final SecurityException ignored) { - getClassLoaderDisabled = true; + try { + // let's see if we can obtain that permission + AccessController.doPrivileged((PrivilegedAction<Void>) () -> { + AccessController.checkPermission(GET_CLASS_LOADER); + return null; + }, null, GET_CLASS_LOADER); + getClassLoaderDisabled = false; + } catch (final SecurityException ignore) { + // no chance + getClassLoaderDisabled = true; + } } GET_CLASS_LOADER_DISABLED = getClassLoaderDisabled; } else { @@ -74,34 +84,90 @@ public final class LoaderUtil { } /** - * Gets the current Thread ClassLoader. Returns the system ClassLoader if the TCCL is {@code null}. If the system - * ClassLoader is {@code null} as well, then the ClassLoader for this class is returned. If running with a - * {@link SecurityManager} that does not allow access to the Thread ClassLoader or system ClassLoader, then the - * ClassLoader for this class is returned. + * Returns the ClassLoader to use. + * + * @return the ClassLoader. + */ + public static ClassLoader getClassLoader() { + return getClassLoader(LoaderUtil.class, null); + } + + // TODO: this method could use some explanation + public static ClassLoader getClassLoader(final Class<?> class1, final Class<?> class2) { + PrivilegedAction<ClassLoader> action = () -> { + final ClassLoader loader1 = class1 == null ? null : class1.getClassLoader(); + final ClassLoader loader2 = class2 == null ? null : class2.getClassLoader(); + final ClassLoader referenceLoader = GET_CLASS_LOADER_DISABLED + ? getThisClassLoader() + : Thread.currentThread().getContextClassLoader(); + if (isChild(referenceLoader, loader1)) { + return isChild(referenceLoader, loader2) ? referenceLoader : loader2; + } + return isChild(loader1, loader2) ? loader1 : loader2; + }; + return AccessController.doPrivileged(action, null, GET_CLASS_LOADER); + } + + /** + * Determines if one ClassLoader is a child of another ClassLoader. Note that a {@code null} ClassLoader is + * interpreted as the system ClassLoader as per convention. + * + * @param loader1 the ClassLoader to check for childhood. + * @param loader2 the ClassLoader to check for parenthood. + * @return {@code true} if the first ClassLoader is a strict descendant of the second ClassLoader. + */ + private static boolean isChild(final ClassLoader loader1, final ClassLoader loader2) { + if (loader1 != null && loader2 != null) { + ClassLoader parent = loader1.getParent(); + while (parent != null && parent != loader2) { + parent = parent.getParent(); + } + // once parent is null, we're at the system CL, which would indicate they have separate ancestry + return parent != null; + } + return loader1 != null; + } + + /** + * Looks up the ClassLoader for this current thread. If this class does not have the runtime permission + * {@code getClassLoader}, then the only ClassLoader this attempts to look up is the loader behind this + * class. When a SecurityManager is installed, this attempts to make a privileged call to get the current + * {@linkplain Thread#getContextClassLoader() thread context ClassLoader}, falling back to either the + * ClassLoader of this class or the {@linkplain ClassLoader#getSystemClassLoader() system ClassLoader}. + * When no SecurityManager is present, the same lookups are performed without use of {@link AccessController}. + * If none of these strategies can obtain a ClassLoader, then this returns {@code null}. * - * @return the current ThreadContextClassLoader. + * @return the current thread's ClassLoader, a fallback loader, or null if no fallback can be determined */ public static ClassLoader getThreadContextClassLoader() { if (GET_CLASS_LOADER_DISABLED) { // we can at least get this class's ClassLoader regardless of security context // however, if this is null, there's really no option left at this point - return LoaderUtil.class.getClassLoader(); + try { + return getThisClassLoader(); + } catch (final SecurityException ignored) { + return null; + } } - return SECURITY_MANAGER == null ? TCCL_GETTER.run() : AccessController.doPrivileged(TCCL_GETTER); + return AccessController.doPrivileged(TCCL_GETTER, null, GET_CLASS_LOADER); + } + + private static ClassLoader getThisClassLoader() { + return LoaderUtil.class.getClassLoader(); } - /** - * - */ private static class ThreadContextClassLoaderGetter implements PrivilegedAction<ClassLoader> { @Override public ClassLoader run() { - final ClassLoader cl = Thread.currentThread().getContextClassLoader(); - if (cl != null) { - return cl; + final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); + if (contextClassLoader != null) { + return contextClassLoader; } - final ClassLoader ccl = LoaderUtil.class.getClassLoader(); - return ccl == null && !GET_CLASS_LOADER_DISABLED ? ClassLoader.getSystemClassLoader() : ccl; + final ClassLoader thisClassLoader = getThisClassLoader(); + if (thisClassLoader != null || GET_CLASS_LOADER_DISABLED) { + return thisClassLoader; + } + return ClassLoader.getSystemClassLoader(); } } @@ -114,8 +180,8 @@ public final class LoaderUtil { */ public static boolean isClassAvailable(final String className) { try { - final Class<?> clazz = loadClass(className); - return clazz != null; + loadClass(className); + return true; } catch (final ClassNotFoundException | LinkageError e) { return false; } catch (final Throwable e) { @@ -128,81 +194,119 @@ public final class LoaderUtil { * Loads a class by name. This method respects the {@link #IGNORE_TCCL_PROPERTY} Log4j property. If this property is * specified and set to anything besides {@code false}, then the default ClassLoader will be used. * - * @param className The class name. - * @return the Class for the given name. - * @throws ClassNotFoundException if the specified class name could not be found + * @param className fully qualified class name to load + * @return the loaded class + * @throws ClassNotFoundException if the specified class name could not be found + * @throws ExceptionInInitializerError if an exception is thrown during class initialization + * @throws LinkageError if the linkage of the class fails for any other reason * @since 2.1 */ public static Class<?> loadClass(final String className) throws ClassNotFoundException { - if (isIgnoreTccl()) { - return Class.forName(className); + ClassLoader classLoader = isIgnoreTccl() ? getThisClassLoader() : getThreadContextClassLoader(); + if (classLoader == null) { + classLoader = getThisClassLoader(); } + return Class.forName(className, true, classLoader); + } + + /** + * Loads and initializes a class given its fully qualified class name. All checked reflective operation + * exceptions are translated into equivalent {@link LinkageError} classes. + * + * @param className fully qualified class name to load + * @return the loaded class + * @throws NoClassDefFoundError if the specified class name could not be found + * @throws ExceptionInInitializerError if an exception is thrown during class initialization + * @throws LinkageError if the linkage of the class fails for any other reason + * @see #loadClass(String) + * @since 2.22.0 + */ + public static Class<?> loadClassUnchecked(final String className) { try { - final ClassLoader tccl = getThreadContextClassLoader(); - if (tccl != null) { - return tccl.loadClass(className); - } - } catch (final Throwable ignored) { + return loadClass(className); + } catch (final ClassNotFoundException e) { + final NoClassDefFoundError error = new NoClassDefFoundError(e.getMessage()); + error.initCause(e); + throw error; } - return Class.forName(className); } /** * Loads and instantiates a Class using the default constructor. * - * @param <T> the type of the class modeled by the {@code Class} object. + * @param <T> the type of the class modeled by the {@code Class} object. * @param clazz The class. * @return new instance of the class. - * @throws IllegalAccessException if the class can't be instantiated through a public constructor - * @throws InstantiationException if there was an exception whilst instantiating the class - * @throws InvocationTargetException if there was an exception whilst constructing the class + * @throws NoSuchMethodException if no zero-arg constructor exists + * @throws SecurityException if this class is not allowed to access declared members of the provided class + * @throws IllegalAccessException if the class can't be instantiated through a public constructor + * @throws InstantiationException if the provided class is abstract or an interface + * @throws InvocationTargetException if an exception is thrown by the constructor + * @throws ExceptionInInitializerError if an exception was thrown while initializing the class * @since 2.7 */ public static <T> T newInstanceOf(final Class<T> clazz) - throws InstantiationException, IllegalAccessException, InvocationTargetException { - try { - return clazz.getConstructor().newInstance(); - } catch (final NoSuchMethodException ignored) { - // FIXME: looking at the code for Class.newInstance(), this seems to do the same thing as above - return clazz.newInstance(); - } + throws InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { + final Constructor<T> constructor = clazz.getDeclaredConstructor(); + return constructor.newInstance(); } /** - * Loads and instantiates a Class using the default constructor. + * Creates an instance of the provided class using the default constructor. All checked reflective operation + * exceptions are translated into {@link LinkageError} or {@link InternalException}. * - * @param className The class name. - * @return new instance of the class. - * @throws ClassNotFoundException if the class isn't available to the usual ClassLoaders - * @throws IllegalAccessException if the class can't be instantiated through a public constructor - * @throws InstantiationException if there was an exception whilst instantiating the class - * @throws InvocationTargetException if there was an exception whilst constructing the class - * @since 2.1 + * @param clazz class to instantiate + * @param <T> the type of the object being instantiated + * @return instance of the class + * @throws NoSuchMethodError if no zero-arg constructor exists + * @throws SecurityException if this class is not allowed to access declared members of the provided class + * @throws InternalException if an exception is thrown by the constructor + * @throws InstantiationError if the provided class is abstract or an interface + * @throws IllegalAccessError if the class cannot be accessed + * @since 2.22.0 */ - @SuppressWarnings("unchecked") - public static <T> T newInstanceOf(final String className) throws ClassNotFoundException, IllegalAccessException, - InstantiationException, InvocationTargetException { - return newInstanceOf((Class<T>) loadClass(className)); + public static <T> T newInstanceOfUnchecked(final Class<T> clazz) { + try { + return newInstanceOf(clazz); + } catch (final NoSuchMethodException e) { + final NoSuchMethodError error = new NoSuchMethodError(e.getMessage()); + error.initCause(e); + throw error; + } catch (final InvocationTargetException e) { + final Throwable cause = e.getCause(); + throw new InternalException(cause); + } catch (final InstantiationException e) { + final InstantiationError error = new InstantiationError(e.getMessage()); + error.initCause(e); + throw error; + } catch (final IllegalAccessException e) { + final IllegalAccessError error = new IllegalAccessError(e.getMessage()); + error.initCause(e); + throw error; + } } /** - * Loads and instantiates a derived class using its default constructor. + * Loads and instantiates a Class using the default constructor. * - * @param className The class name. - * @param clazz The class to cast it to. - * @param <T> The type of the class to check. - * @return new instance of the class cast to {@code T} - * @throws ClassNotFoundException if the class isn't available to the usual ClassLoaders - * @throws IllegalAccessException if the class can't be instantiated through a public constructor - * @throws InstantiationException if there was an exception whilst instantiating the class - * @throws InvocationTargetException if there was an exception whilst constructing the class - * @throws ClassCastException if the constructed object isn't type compatible with {@code T} + * @param className fully qualified class name to load, initialize, and construct + * @param <T> type the class must be compatible with + * @return new instance of the class + * @throws ClassNotFoundException if the class isn't available to the usual ClassLoaders + * @throws ExceptionInInitializerError if an exception was thrown while initializing the class + * @throws LinkageError if the linkage of the class fails for any other reason + * @throws ClassCastException if the class is not compatible with the generic type parameter provided + * @throws NoSuchMethodException if no zero-arg constructor exists + * @throws SecurityException if this class is not allowed to access declared members of the provided class + * @throws IllegalAccessException if the class can't be instantiated through a public constructor + * @throws InstantiationException if the provided class is abstract or an interface + * @throws InvocationTargetException if an exception is thrown by the constructor * @since 2.1 */ - public static <T> T newCheckedInstanceOf(final String className, final Class<T> clazz) - throws ClassNotFoundException, InvocationTargetException, InstantiationException, - IllegalAccessException { - return clazz.cast(newInstanceOf(className)); + public static <T> T newInstanceOf(final String className) throws ClassNotFoundException, IllegalAccessException, + InstantiationException, InvocationTargetException, NoSuchMethodException { + final Class<T> clazz = Cast.cast(loadClass(className)); + return newInstanceOf(clazz); } /** @@ -212,16 +316,20 @@ public final class LoaderUtil { * @param clazz The class to cast it to. * @param <T> The type to cast it to. * @return new instance of the class given in the property or {@code null} if the property was unset. - * @throws ClassNotFoundException if the class isn't available to the usual ClassLoaders - * @throws IllegalAccessException if the class can't be instantiated through a public constructor - * @throws InstantiationException if there was an exception whilst instantiating the class - * @throws InvocationTargetException if there was an exception whilst constructing the class - * @throws ClassCastException if the constructed object isn't type compatible with {@code T} + * @throws ClassNotFoundException if the class isn't available to the usual ClassLoaders + * @throws ExceptionInInitializerError if an exception was thrown while initializing the class + * @throws LinkageError if the linkage of the class fails for any other reason + * @throws ClassCastException if the class is not compatible with the generic type parameter provided + * @throws NoSuchMethodException if no zero-arg constructor exists + * @throws SecurityException if this class is not allowed to access declared members of the provided class + * @throws IllegalAccessException if the class can't be instantiated through a public constructor + * @throws InstantiationException if the provided class is abstract or an interface + * @throws InvocationTargetException if an exception is thrown by the constructor * @since 2.5 */ public static <T> T newCheckedInstanceOfProperty(final String propertyName, final Class<T> clazz) - throws ClassNotFoundException, InvocationTargetException, InstantiationException, - IllegalAccessException { + throws ClassNotFoundException, InvocationTargetException, InstantiationException, + IllegalAccessException, NoSuchMethodException { final String className = PropertiesUtil.getProperties().getStringProperty(propertyName); if (className == null) { return null; @@ -229,6 +337,76 @@ public final class LoaderUtil { return newCheckedInstanceOf(className, clazz); } + /** + * Loads and instantiates a class by name using its default constructor. All checked reflective operation + * exceptions are translated into corresponding {@link LinkageError} classes. + * + * @param className fully qualified class name to load, initialize, and construct + * @param <T> type the class must be compatible with + * @return new instance of the class + * @throws NoClassDefFoundError if the specified class name could not be found + * @throws ExceptionInInitializerError if an exception is thrown during class initialization + * @throws ClassCastException if the class is not compatible with the generic type parameter provided + * @throws NoSuchMethodError if no zero-arg constructor exists + * @throws SecurityException if this class is not allowed to access declared members of the provided class + * @throws InternalException if an exception is thrown by the constructor + * @throws InstantiationError if the provided class is abstract or an interface + * @throws IllegalAccessError if the class cannot be accessed + * @throws LinkageError if the linkage of the class fails for any other reason + * @since 2.22.0 + */ + public static <T> T newInstanceOfUnchecked(final String className) { + final Class<T> clazz = Cast.cast(loadClassUnchecked(className)); + return newInstanceOfUnchecked(clazz); + } + + /** + * Loads and instantiates a derived class using its default constructor. + * + * @param className The class name. + * @param clazz The class to cast it to. + * @param <T> The type of the class to check. + * @return new instance of the class cast to {@code T} + * @throws ClassNotFoundException if the class isn't available to the usual ClassLoaders + * @throws ExceptionInInitializerError if an exception is thrown during class initialization + * @throws LinkageError if the linkage of the class fails for any other reason + * @throws ClassCastException if the constructed object isn't type compatible with {@code T} + * @throws NoSuchMethodException if no zero-arg constructor exists + * @throws SecurityException if this class is not allowed to access declared members of the provided class + * @throws IllegalAccessException if the class can't be instantiated through a public constructor + * @throws InstantiationException if the provided class is abstract or an interface + * @throws InvocationTargetException if there was an exception whilst constructing the class + * @since 2.1 + */ + public static <T> T newCheckedInstanceOf(final String className, final Class<T> clazz) throws ClassNotFoundException, + InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException { + return newInstanceOf(loadClass(className).asSubclass(clazz)); + } + + /** + * Loads the provided class by name as a checked subtype of the given class. All checked reflective operation + * exceptions are translated into corresponding {@link LinkageError} classes. + * + * @param className fully qualified class name to load + * @param supertype supertype of the class being loaded + * @param <T> type of instance to return + * @return new instance of the requested class + * @throws NoClassDefFoundError if the provided class name could not be found + * @throws ExceptionInInitializerError if an exception is thrown during class initialization + * @throws ClassCastException if the loaded class is not a subtype of the provided class + * @throws NoSuchMethodError if no zero-arg constructor exists + * @throws SecurityException if this class is not allowed to access declared members of the provided class + * @throws InternalException if an exception is thrown by the constructor + * @throws InstantiationError if the provided class is abstract or an interface + * @throws IllegalAccessError if the class cannot be accessed + * @throws LinkageError if the linkage of the class fails for any other reason + * @since 2.22.0 + */ + public static <T> T newInstanceOfUnchecked(final String className, final Class<T> supertype) { + final Class<? extends T> clazz = loadClassUnchecked(className).asSubclass(supertype); + return newInstanceOfUnchecked(clazz); + } + private static boolean isIgnoreTccl() { // we need to lazily initialize this, but concurrent access is not an issue if (ignoreTCCL == null) { @@ -312,14 +490,7 @@ public final class LoaderUtil { final UrlResource that = (UrlResource) o; - if (classLoader != null ? !classLoader.equals(that.classLoader) : that.classLoader != null) { - return false; - } - if (url != null ? !url.equals(that.url) : that.url != null) { - return false; - } - - return true; + return Objects.equals(classLoader, that.classLoader) && Objects.equals(url, that.url); } @Override diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/package-info.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/package-info.java index 99db5c911f..8265e82149 100644 --- a/log4j-api/src/main/java/org/apache/logging/log4j/util/package-info.java +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/package-info.java @@ -20,7 +20,7 @@ * There are no guarantees for binary or logical compatibility in this package. */ @Export -@Version("2.20.1") +@Version("2.22.0") package org.apache.logging.log4j.util; import org.osgi.annotation.bundle.Export;
