From 8a38c5f4b72cf610c49bff5a3fef5f8752921c6b Mon Sep 17 00:00:00 2001 From: Luck Date: Thu, 26 Nov 2020 15:52:39 +0000 Subject: [PATCH] Properly open URLClassLoader module on Java 9+ to allow reflection and avoid access warning (#952) --- .../classloader/ReflectionClassLoader.java | 72 ++++++++++--------- 1 file changed, 40 insertions(+), 32 deletions(-) diff --git a/common/src/main/java/me/lucko/luckperms/common/dependencies/classloader/ReflectionClassLoader.java b/common/src/main/java/me/lucko/luckperms/common/dependencies/classloader/ReflectionClassLoader.java index c5b5ab41a..e68cc43d7 100644 --- a/common/src/main/java/me/lucko/luckperms/common/dependencies/classloader/ReflectionClassLoader.java +++ b/common/src/main/java/me/lucko/luckperms/common/dependencies/classloader/ReflectionClassLoader.java @@ -25,9 +25,6 @@ package me.lucko.luckperms.common.dependencies.classloader; -import com.google.common.base.Supplier; -import com.google.common.base.Suppliers; - import me.lucko.luckperms.common.plugin.bootstrap.LuckPermsBootstrap; import java.lang.reflect.InvocationTargetException; @@ -38,10 +35,28 @@ import java.net.URLClassLoader; import java.nio.file.Path; public class ReflectionClassLoader implements PluginClassLoader { - private final URLClassLoader classLoader; - @SuppressWarnings("Guava") // we can't use java.util.Function because old Guava versions are used at runtime - private final Supplier addUrlMethod; + private static final Method ADD_URL_METHOD; + + static { + // If on Java 9+, open the URLClassLoader module to this module + // so we can access its API via reflection without producing a warning. + try { + openUrlClassLoaderModule(); + } catch (Throwable e) { + // ignore exception - will throw on Java 8 since the Module classes don't exist + } + + // Get the protected 'addURL' method on URLClassLoader and set it to accessible. + try { + ADD_URL_METHOD = URLClassLoader.class.getDeclaredMethod("addURL", URL.class); + ADD_URL_METHOD.setAccessible(true); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + + private final URLClassLoader classLoader; public ReflectionClassLoader(LuckPermsBootstrap bootstrap) throws IllegalStateException { ClassLoader classLoader = bootstrap.getClass().getClassLoader(); @@ -50,42 +65,35 @@ public class ReflectionClassLoader implements PluginClassLoader { } else { throw new IllegalStateException("ClassLoader is not instance of URLClassLoader"); } - - this.addUrlMethod = Suppliers.memoize(() -> { - if (isJava9OrNewer()) { - bootstrap.getPluginLogger().info("It is safe to ignore any warning printed following this message " + - "starting with 'WARNING: An illegal reflective access operation has occurred, Illegal reflective " + - "access by " + getClass().getName() + "'. This is intended, and will not have any impact on the " + - "operation of LuckPerms."); - } - - try { - Method addUrlMethod = URLClassLoader.class.getDeclaredMethod("addURL", URL.class); - addUrlMethod.setAccessible(true); - return addUrlMethod; - } catch (NoSuchMethodException e) { - throw new RuntimeException(e); - } - }); } @Override public void addJarToClasspath(Path file) { try { - this.addUrlMethod.get().invoke(this.classLoader, file.toUri().toURL()); + ADD_URL_METHOD.invoke(this.classLoader, file.toUri().toURL()); } catch (IllegalAccessException | InvocationTargetException | MalformedURLException e) { throw new RuntimeException(e); } } @SuppressWarnings("JavaReflectionMemberAccess") - private static boolean isJava9OrNewer() { - try { - // method was added in the Java 9 release - Runtime.class.getMethod("version"); - return true; - } catch (NoSuchMethodException e) { - return false; - } + private static void openUrlClassLoaderModule() throws Exception { + // This is effectively calling: + // + // URLClassLoader.class.getModule().addOpens( + // URLClassLoader.class.getPackageName(), + // ReflectionClassLoader.class.getModule() + // ); + // + // We use reflection since we build against Java 8. + + Class moduleClass = Class.forName("java.lang.Module"); + Method getModuleMethod = Class.class.getMethod("getModule"); + Method addOpensMethod = moduleClass.getMethod("addOpens", String.class, moduleClass); + + Object urlClassLoaderModule = getModuleMethod.invoke(URLClassLoader.class); + Object thisModule = getModuleMethod.invoke(ReflectionClassLoader.class); + + addOpensMethod.invoke(urlClassLoaderModule, URLClassLoader.class.getPackage().getName(), thisModule); } }