diff --git a/core/src/main/java/hudson/ExtensionFinder.java b/core/src/main/java/hudson/ExtensionFinder.java index 92e524e04d..62234e0692 100644 --- a/core/src/main/java/hudson/ExtensionFinder.java +++ b/core/src/main/java/hudson/ExtensionFinder.java @@ -486,12 +486,6 @@ public abstract class ExtensionFinder implements ExtensionPoint { return; } try { - ClassLoader ecl = c.getClassLoader(); - if (ecl != null) { // Not bootstrap classloader - Method m = ClassLoader.class.getDeclaredMethod("resolveClass", Class.class); - m.setAccessible(true); - m.invoke(ecl, c); - } for (Class cc = c; cc != Object.class && cc != null; cc = cc.getSuperclass()) { /* * See {@link com.google.inject.spi.InjectionPoint#getInjectionPoints(TypeLiteral, boolean, Errors)} diff --git a/core/src/main/java/hudson/PluginFirstClassLoader.java b/core/src/main/java/hudson/PluginFirstClassLoader.java index dde635d24d..51ef789da0 100644 --- a/core/src/main/java/hudson/PluginFirstClassLoader.java +++ b/core/src/main/java/hudson/PluginFirstClassLoader.java @@ -84,7 +84,7 @@ public class PluginFirstClassLoader } @Override - protected Enumeration findResources( String name ) + public Enumeration findResources( String name ) throws IOException { return super.findResources( name ); diff --git a/core/src/main/java/jenkins/ClassLoaderReflectionToolkit.java b/core/src/main/java/jenkins/ClassLoaderReflectionToolkit.java index 052fc7bd8f..06a61b879b 100644 --- a/core/src/main/java/jenkins/ClassLoaderReflectionToolkit.java +++ b/core/src/main/java/jenkins/ClassLoaderReflectionToolkit.java @@ -4,41 +4,29 @@ import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URL; +import java.net.URLClassLoader; import java.util.Enumeration; import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; +import jenkins.util.AntClassLoader; +import jenkins.util.AntWithFindResourceClassLoader; /** * Reflective access to various {@link ClassLoader} methods which are otherwise {@code protected}. + *

+ * Initially tries to access methods using known classloaders in use that expose the methods + * to prevent illegal reflective access errors on Java 11+ + * Then falls back to accessing the {@link ClassLoader} methods. + *

+ * All reflection method initialisation is delayed until first use so that we don't access the methods if we don't need to. + *

+ * Note: Currently there is no known production use-case for the fallback case of accessing these methods via reflection: + * the {@code JenkinsRule} tests use a different classloader, + * but once that is made consistent with production Jenkins we can re-evaluate the fallback code. */ @SuppressWarnings({"unchecked", "rawtypes"}) public class ClassLoaderReflectionToolkit { - private static final Method FIND_CLASS, FIND_LOADED_CLASS, FIND_RESOURCE, FIND_RESOURCES, GET_CLASS_LOADING_LOCK; - - static { - try { - FIND_CLASS = ClassLoader.class.getDeclaredMethod("findClass",String.class); - FIND_CLASS.setAccessible(true); - FIND_LOADED_CLASS = ClassLoader.class.getDeclaredMethod("findLoadedClass",String.class); - FIND_LOADED_CLASS.setAccessible(true); - FIND_RESOURCE = ClassLoader.class.getDeclaredMethod("findResource",String.class); - FIND_RESOURCE.setAccessible(true); - FIND_RESOURCES = ClassLoader.class.getDeclaredMethod("findResources",String.class); - FIND_RESOURCES.setAccessible(true); - } catch (NoSuchMethodException e) { - throw new AssertionError(e); - } - Method gCLL; - try { - gCLL = ClassLoader.class.getDeclaredMethod("getClassLoadingLock", String.class); - gCLL.setAccessible(true); - } catch (NoSuchMethodException x) { - throw new AssertionError(x); - } - GET_CLASS_LOADING_LOCK = gCLL; - } - private static Object invoke(Method method, Class exception, Object obj, Object... args) throws T { try { return method.invoke(obj, args); @@ -59,7 +47,25 @@ public class ClassLoaderReflectionToolkit { } private static Object getClassLoadingLock(ClassLoader cl, String name) { - return invoke(GET_CLASS_LOADING_LOCK, RuntimeException.class, cl, name); + if (cl instanceof AntWithFindResourceClassLoader) { + return ((AntWithFindResourceClassLoader) cl).getClassLoadingLock(name); + } + return invoke(GetClassLoadingLock.GET_CLASS_LOADING_LOCK, RuntimeException.class, cl, name); + } + + private static class GetClassLoadingLock { + private static final Method GET_CLASS_LOADING_LOCK; + + static { + Method gCLL; + try { + gCLL = ClassLoader.class.getDeclaredMethod("getClassLoadingLock", String.class); + gCLL.setAccessible(true); + } catch (NoSuchMethodException x) { + throw new AssertionError(x); + } + GET_CLASS_LOADING_LOCK = gCLL; + } } /** @@ -68,7 +74,27 @@ public class ClassLoaderReflectionToolkit { */ public static @CheckForNull Class _findLoadedClass(ClassLoader cl, String name) { synchronized (getClassLoadingLock(cl, name)) { - return (Class) invoke(FIND_LOADED_CLASS, RuntimeException.class, cl, name); + Class c; + if (cl instanceof AntWithFindResourceClassLoader) { + c = ((AntWithFindResourceClassLoader) cl).findLoadedClass2(name); + } else { + c = (Class) invoke(FindLoadedClass.FIND_LOADED_CLASS, RuntimeException.class, cl, name); + } + + return c; + } + } + + private static class FindLoadedClass { + private static final Method FIND_LOADED_CLASS; + + static { + try { + FIND_LOADED_CLASS = ClassLoader.class.getDeclaredMethod("findLoadedClass", String.class); + } catch (NoSuchMethodException e) { + throw new AssertionError(e); + } + FIND_LOADED_CLASS.setAccessible(true); } } @@ -77,17 +103,57 @@ public class ClassLoaderReflectionToolkit { * @since 1.553 */ public static @NonNull Class _findClass(ClassLoader cl, String name) throws ClassNotFoundException { + if (cl instanceof AntClassLoader) { + return ((AntClassLoader) cl).findClass(name); + } + synchronized (getClassLoadingLock(cl, name)) { - return (Class) invoke(FIND_CLASS, ClassNotFoundException.class, cl, name); + return (Class) invoke(FindClass.FIND_CLASS, ClassNotFoundException.class, cl, name); } } + private static class FindClass { + private static final Method FIND_CLASS; + + static { + try { + FIND_CLASS = ClassLoader.class.getDeclaredMethod("findClass",String.class); + } catch (NoSuchMethodException e) { + throw new AssertionError(e); + } + FIND_CLASS.setAccessible(true); + } + } + + /** * Calls {@link ClassLoader#findResource}. * @since 1.553 */ public static @CheckForNull URL _findResource(ClassLoader cl, String name) { - return (URL) invoke(FIND_RESOURCE, RuntimeException.class, cl, name); + URL url; + if (cl instanceof AntWithFindResourceClassLoader) { + url = ((AntWithFindResourceClassLoader) cl).findResource(name); + } else if (cl instanceof URLClassLoader) { + url = ((URLClassLoader) cl).findResource(name); + } else { + url = (URL) invoke(FindResource.FIND_RESOURCE, RuntimeException.class, cl, name); + } + + return url; + } + + private static class FindResource { + private static final Method FIND_RESOURCE; + + static { + try { + FIND_RESOURCE = ClassLoader.class.getDeclaredMethod("findResource", String.class); + } catch (NoSuchMethodException e) { + throw new AssertionError(e); + } + FIND_RESOURCE.setAccessible(true); + } } /** @@ -95,7 +161,27 @@ public class ClassLoaderReflectionToolkit { * @since 1.553 */ public static @NonNull Enumeration _findResources(ClassLoader cl, String name) throws IOException { - return (Enumeration) invoke(FIND_RESOURCES, IOException.class, cl, name); + Enumeration urls; + if (cl instanceof AntWithFindResourceClassLoader) { + urls = ((AntWithFindResourceClassLoader) cl).findResources(name); + } else { + urls = (Enumeration) invoke(FindResources.FIND_RESOURCES, IOException.class, cl, name); + } + + return urls; + } + + private static class FindResources { + private static final Method FIND_RESOURCES; + + static { + try { + FIND_RESOURCES = ClassLoader.class.getDeclaredMethod("findResources", String.class); + } catch (NoSuchMethodException e) { + throw new AssertionError(e); + } + FIND_RESOURCES.setAccessible(true); + } } /** @deprecated unsafe */ @@ -105,7 +191,7 @@ public class ClassLoaderReflectionToolkit { @Deprecated public Class findLoadedClass(ClassLoader cl, String name) throws InvocationTargetException { try { - return (Class)FIND_LOADED_CLASS.invoke(cl,name); + return (Class)FindLoadedClass.FIND_LOADED_CLASS.invoke(cl,name); } catch (IllegalAccessException e) { throw new Error(e); } @@ -115,7 +201,7 @@ public class ClassLoaderReflectionToolkit { @Deprecated public Class findClass(ClassLoader cl, String name) throws InvocationTargetException { try { - return (Class)FIND_CLASS.invoke(cl,name); + return (Class)FindClass.FIND_CLASS.invoke(cl,name); } catch (IllegalAccessException e) { throw new Error(e); } @@ -125,7 +211,7 @@ public class ClassLoaderReflectionToolkit { @Deprecated public URL findResource(ClassLoader cl, String name) throws InvocationTargetException { try { - return (URL)FIND_RESOURCE.invoke(cl,name); + return (URL)FindResource.FIND_RESOURCE.invoke(cl,name); } catch (IllegalAccessException e) { throw new Error(e); } @@ -135,7 +221,7 @@ public class ClassLoaderReflectionToolkit { @Deprecated public Enumeration findResources(ClassLoader cl, String name) throws InvocationTargetException { try { - return (Enumeration)FIND_RESOURCES.invoke(cl,name); + return (Enumeration)FindResources.FIND_RESOURCES.invoke(cl,name); } catch (IllegalAccessException e) { throw new Error(e); } diff --git a/core/src/main/java/jenkins/model/AssetManager.java b/core/src/main/java/jenkins/model/AssetManager.java index a688f0f718..5d317f2b2a 100644 --- a/core/src/main/java/jenkins/model/AssetManager.java +++ b/core/src/main/java/jenkins/model/AssetManager.java @@ -2,8 +2,10 @@ package jenkins.model; import hudson.Extension; import hudson.model.UnprotectedRootAction; + import java.util.concurrent.TimeUnit; +import jenkins.ClassLoaderReflectionToolkit; import org.apache.commons.lang.StringUtils; import org.jenkinsci.Symbol; import org.kohsuke.stapler.StaplerRequest; @@ -14,8 +16,6 @@ import edu.umd.cs.findbugs.annotations.NonNull; import javax.servlet.ServletException; import javax.servlet.http.HttpServletResponse; import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.net.URL; import java.util.Enumeration; @@ -83,41 +83,26 @@ public class AssetManager implements UnprotectedRootAction { return null; } - try { - if (path.contains("..")) // crude avoidance of directory traversal attack - throw new IllegalArgumentException(path); + if (path.contains("..")) // crude avoidance of directory traversal attack + throw new IllegalArgumentException(path); - String name; - if (path.charAt(0) == '/') { - name = "assets" + path; - } else { - name = "assets/" + path; - } - - ClassLoader cl = Jenkins.class.getClassLoader(); - URL url = (URL) $findResource.invoke(cl, name); - if (url==null) { - // pick the last one, which is the one closest to the leaf of the classloader tree. - Enumeration e = cl.getResources(name); - while (e.hasMoreElements()) { - url = e.nextElement(); - } - } - return url; - } catch (InvocationTargetException|IllegalAccessException e) { - throw new Error(e); + String name; + if (path.charAt(0) == '/') { + name = "assets" + path; + } else { + name = "assets/" + path; } + + ClassLoader cl = Jenkins.class.getClassLoader(); + URL url = ClassLoaderReflectionToolkit._findResource(cl, name); + if (url == null) { + // pick the last one, which is the one closest to the leaf of the classloader tree. + Enumeration e = cl.getResources(name); + while (e.hasMoreElements()) { + url = e.nextElement(); + } + } + return url; } - private static final Method $findResource = init(); - - private static Method init() { - try { - Method m = ClassLoader.class.getDeclaredMethod("findResource", String.class); - m.setAccessible(true); - return m; - } catch (NoSuchMethodException e) { - throw (Error)new NoSuchMethodError().initCause(e); - } - } } diff --git a/core/src/main/java/jenkins/util/AntClassLoader.java b/core/src/main/java/jenkins/util/AntClassLoader.java index 7843abb69a..656678a7e8 100644 --- a/core/src/main/java/jenkins/util/AntClassLoader.java +++ b/core/src/main/java/jenkins/util/AntClassLoader.java @@ -960,7 +960,7 @@ public class AntClassLoader extends ClassLoader implements SubBuildListener { * @return an enumeration of URLs for the resources * @exception IOException if I/O errors occurs (can't happen) */ - protected Enumeration/**/ findResources(String name) throws IOException { + public Enumeration/**/ findResources(String name) throws IOException { return findResources(name, true); } diff --git a/core/src/main/java/jenkins/util/AntWithFindResourceClassLoader.java b/core/src/main/java/jenkins/util/AntWithFindResourceClassLoader.java index 7b70413a48..6f45892bfa 100644 --- a/core/src/main/java/jenkins/util/AntWithFindResourceClassLoader.java +++ b/core/src/main/java/jenkins/util/AntWithFindResourceClassLoader.java @@ -37,10 +37,25 @@ public class AntWithFindResourceClassLoader extends AntClassLoader implements Cl } @Override - protected URL findResource(String name) { + public URL findResource(String name) { // try and load from this loader if the parent either didn't find // it or wasn't consulted. return getUrl(pathComponents, name); } + /** + * Public version of {@link ClassLoader#findLoadedClass(String)} + */ + public Class findLoadedClass2(String name) { + return findLoadedClass(name); + } + + /** + * Public version of {@link ClassLoader#getClassLoadingLock(String)} + */ + @Override + public Object getClassLoadingLock(String className) { + return super.getClassLoadingLock(className); + } + }