From c82479dc52ff972c625f3ac4a01aaaef2e62c27c Mon Sep 17 00:00:00 2001 From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> Date: Sun, 28 Apr 2024 14:55:10 -0700 Subject: [PATCH] Remap plugin libraries with namespace set to spigot (#10610) * Remap plugin libraries with namespace set to spigot * Remap plugin libraries with namespace set to spigot --- .../api/Add-hook-to-remap-library-jars.patch | 38 +++ .../Modify-library-loader-jars-bytecode.patch | 261 ------------------ patches/server/Plugin-remapping.patch | 114 +++++++- ...ion-calls-in-plugins-using-internals.patch | 255 +++++++++++++++++ 4 files changed, 395 insertions(+), 273 deletions(-) create mode 100644 patches/api/Add-hook-to-remap-library-jars.patch delete mode 100644 patches/server/Modify-library-loader-jars-bytecode.patch diff --git a/patches/api/Add-hook-to-remap-library-jars.patch b/patches/api/Add-hook-to-remap-library-jars.patch new file mode 100644 index 0000000000..af92be8749 --- /dev/null +++ b/patches/api/Add-hook-to-remap-library-jars.patch @@ -0,0 +1,38 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> +Date: Sun, 28 Apr 2024 13:51:08 -0700 +Subject: [PATCH] Add hook to remap library jars + + +diff --git a/src/main/java/org/bukkit/plugin/java/LibraryLoader.java b/src/main/java/org/bukkit/plugin/java/LibraryLoader.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/org/bukkit/plugin/java/LibraryLoader.java ++++ b/src/main/java/org/bukkit/plugin/java/LibraryLoader.java +@@ -0,0 +0,0 @@ public class LibraryLoader + private final DefaultRepositorySystemSession session; + private final List<RemoteRepository> repositories; + public static java.util.function.BiFunction<URL[], ClassLoader, URLClassLoader> LIBRARY_LOADER_FACTORY; // Paper - rewrite reflection in libraries ++ public static java.util.function.Function<List<java.nio.file.Path>, List<java.nio.file.Path>> REMAPPER; // Paper - remap libraries + + public LibraryLoader(@NotNull Logger logger) + { +@@ -0,0 +0,0 @@ public class LibraryLoader + } + + List<URL> jarFiles = new ArrayList<>(); ++ List<java.nio.file.Path> jarPaths = new ArrayList<>(); // Paper - remap libraries + for ( ArtifactResult artifact : result.getArtifactResults() ) + { +- File file = artifact.getArtifact().getFile(); ++ // Paper start - remap libraries ++ jarPaths.add(artifact.getArtifact().getFile().toPath()); ++ } ++ if (REMAPPER != null) { ++ jarPaths = REMAPPER.apply(jarPaths); ++ } ++ for (java.nio.file.Path path : jarPaths) { ++ File file = path.toFile(); ++ // Paper end - remap libraries + + URL url; + try diff --git a/patches/server/Modify-library-loader-jars-bytecode.patch b/patches/server/Modify-library-loader-jars-bytecode.patch deleted file mode 100644 index b8012ea193..0000000000 --- a/patches/server/Modify-library-loader-jars-bytecode.patch +++ /dev/null @@ -1,261 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> -Date: Sun, 28 Apr 2024 11:12:14 -0700 -Subject: [PATCH] Modify library loader jars bytecode - - -diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/BytecodeModifyingURLClassLoader.java b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/BytecodeModifyingURLClassLoader.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/BytecodeModifyingURLClassLoader.java -@@ -0,0 +0,0 @@ -+package io.papermc.paper.plugin.entrypoint.classloader; -+ -+import io.papermc.paper.pluginremap.reflect.ReflectionRemapper; -+import java.io.IOException; -+import java.io.InputStream; -+import java.io.UncheckedIOException; -+import java.net.JarURLConnection; -+import java.net.URL; -+import java.net.URLClassLoader; -+import java.security.CodeSigner; -+import java.security.CodeSource; -+import java.util.Map; -+import java.util.concurrent.ConcurrentHashMap; -+import java.util.function.Function; -+import java.util.jar.Attributes; -+import java.util.jar.Manifest; -+import org.checkerframework.checker.nullness.qual.Nullable; -+import org.objectweb.asm.ClassReader; -+import org.objectweb.asm.ClassVisitor; -+import org.objectweb.asm.ClassWriter; -+ -+import static java.util.Objects.requireNonNullElse; -+ -+public final class BytecodeModifyingURLClassLoader extends URLClassLoader { -+ static { -+ ClassLoader.registerAsParallelCapable(); -+ } -+ -+ private static final Object MISSING_MANIFEST = new Object(); -+ -+ private final Function<byte[], byte[]> modifier; -+ private final Map<String, Object> manifests = new ConcurrentHashMap<>(); -+ -+ public BytecodeModifyingURLClassLoader( -+ final URL[] urls, -+ final ClassLoader parent, -+ final Function<byte[], byte[]> modifier -+ ) { -+ super(urls, parent); -+ this.modifier = modifier; -+ } -+ -+ public BytecodeModifyingURLClassLoader( -+ final URL[] urls, -+ final ClassLoader parent -+ ) { -+ this(urls, parent, bytes -> { -+ final ClassReader classReader = new ClassReader(bytes); -+ final ClassWriter classWriter = new ClassWriter(classReader, 0); -+ final ClassVisitor visitor = ReflectionRemapper.visitor(classWriter); -+ if (visitor == classWriter) { -+ return bytes; -+ } -+ classReader.accept(visitor, 0); -+ return classWriter.toByteArray(); -+ }); -+ } -+ -+ @Override -+ protected Class<?> findClass(final String name) throws ClassNotFoundException { -+ final Class<?> result; -+ final String path = name.replace('.', '/').concat(".class"); -+ final URL url = this.findResource(path); -+ if (url != null) { -+ try { -+ result = this.defineClass(name, url); -+ } catch (final IOException e) { -+ throw new ClassNotFoundException(name, e); -+ } -+ } else { -+ result = null; -+ } -+ if (result == null) { -+ throw new ClassNotFoundException(name); -+ } -+ return result; -+ } -+ -+ private Class<?> defineClass(String name, URL url) throws IOException { -+ int i = name.lastIndexOf('.'); -+ if (i != -1) { -+ String pkgname = name.substring(0, i); -+ // Check if package already loaded. -+ final @Nullable Manifest man = this.manifestFor(url); -+ if (this.getAndVerifyPackage(pkgname, man, url) == null) { -+ try { -+ if (man != null) { -+ this.definePackage(pkgname, man, url); -+ } else { -+ this.definePackage(pkgname, null, null, null, null, null, null, null); -+ } -+ } catch (IllegalArgumentException iae) { -+ // parallel-capable class loaders: re-verify in case of a -+ // race condition -+ if (this.getAndVerifyPackage(pkgname, man, url) == null) { -+ // Should never happen -+ throw new AssertionError("Cannot find package " + -+ pkgname); -+ } -+ } -+ } -+ } -+ final byte[] bytes; -+ try (final InputStream is = url.openStream()) { -+ bytes = is.readAllBytes(); -+ } -+ -+ final byte[] modified = this.modifier.apply(bytes); -+ -+ final CodeSource cs = new CodeSource(url, (CodeSigner[]) null); -+ return this.defineClass(name, modified, 0, modified.length, cs); -+ } -+ -+ private Package getAndVerifyPackage( -+ String pkgname, -+ Manifest man, URL url -+ ) { -+ Package pkg = getDefinedPackage(pkgname); -+ if (pkg != null) { -+ // Package found, so check package sealing. -+ if (pkg.isSealed()) { -+ // Verify that code source URL is the same. -+ if (!pkg.isSealed(url)) { -+ throw new SecurityException( -+ "sealing violation: package " + pkgname + " is sealed"); -+ } -+ } else { -+ // Make sure we are not attempting to seal the package -+ // at this code source URL. -+ if ((man != null) && this.isSealed(pkgname, man)) { -+ throw new SecurityException( -+ "sealing violation: can't seal package " + pkgname + -+ ": already loaded"); -+ } -+ } -+ } -+ return pkg; -+ } -+ -+ private boolean isSealed(String name, Manifest man) { -+ Attributes attr = man.getAttributes(name.replace('.', '/').concat("/")); -+ String sealed = null; -+ if (attr != null) { -+ sealed = attr.getValue(Attributes.Name.SEALED); -+ } -+ if (sealed == null) { -+ if ((attr = man.getMainAttributes()) != null) { -+ sealed = attr.getValue(Attributes.Name.SEALED); -+ } -+ } -+ return "true".equalsIgnoreCase(sealed); -+ } -+ -+ private @Nullable Manifest manifestFor(final URL url) throws IOException { -+ Manifest man = null; -+ if (url.getProtocol().equals("jar")) { -+ try { -+ final Object computedManifest = this.manifests.computeIfAbsent(jarName(url), $ -> { -+ try { -+ final Manifest m = ((JarURLConnection) url.openConnection()).getManifest(); -+ return requireNonNullElse(m, MISSING_MANIFEST); -+ } catch (final IOException e) { -+ throw new UncheckedIOException(e); -+ } -+ }); -+ if (computedManifest instanceof Manifest found) { -+ man = found; -+ } -+ } catch (final UncheckedIOException e) { -+ throw e.getCause(); -+ } catch (final IllegalArgumentException e) { -+ throw new IOException(e); -+ } -+ } -+ return man; -+ } -+ -+ private static String jarName(final URL sourceUrl) { -+ final int exclamationIdx = sourceUrl.getPath().lastIndexOf('!'); -+ if (exclamationIdx != -1) { -+ return sourceUrl.getPath().substring(0, exclamationIdx); -+ } -+ throw new IllegalArgumentException("Could not find jar for URL " + sourceUrl); -+ } -+} -diff --git a/src/main/java/io/papermc/paper/plugin/loader/PaperClasspathBuilder.java b/src/main/java/io/papermc/paper/plugin/loader/PaperClasspathBuilder.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/io/papermc/paper/plugin/loader/PaperClasspathBuilder.java -+++ b/src/main/java/io/papermc/paper/plugin/loader/PaperClasspathBuilder.java -@@ -0,0 +0,0 @@ - package io.papermc.paper.plugin.loader; - - import io.papermc.paper.plugin.bootstrap.PluginProviderContext; -+import io.papermc.paper.plugin.entrypoint.classloader.BytecodeModifyingURLClassLoader; -+import io.papermc.paper.plugin.entrypoint.classloader.PaperPluginClassLoader; - import io.papermc.paper.plugin.loader.library.ClassPathLibrary; - import io.papermc.paper.plugin.loader.library.PaperLibraryStore; --import io.papermc.paper.plugin.entrypoint.classloader.PaperPluginClassLoader; - import io.papermc.paper.plugin.provider.configuration.PaperPluginMeta; --import org.jetbrains.annotations.NotNull; -- - import java.io.IOException; - import java.net.MalformedURLException; - import java.net.URL; -@@ -0,0 +0,0 @@ import java.util.ArrayList; - import java.util.List; - import java.util.jar.JarFile; - import java.util.logging.Logger; -+import org.jetbrains.annotations.NotNull; - - public class PaperClasspathBuilder implements PluginClasspathBuilder { - -@@ -0,0 +0,0 @@ public class PaperClasspathBuilder implements PluginClasspathBuilder { - } - - try { -- return new PaperPluginClassLoader(logger, source, jarFile, configuration, this.getClass().getClassLoader(), new URLClassLoader(urls, getClass().getClassLoader())); -+ final URLClassLoader libraryLoader = new BytecodeModifyingURLClassLoader(urls, this.getClass().getClassLoader()); -+ return new PaperPluginClassLoader(logger, source, jarFile, configuration, this.getClass().getClassLoader(), libraryLoader); - } catch (IOException exception) { - throw new RuntimeException(exception); - } -diff --git a/src/main/java/io/papermc/paper/plugin/provider/type/spigot/SpigotPluginProviderFactory.java b/src/main/java/io/papermc/paper/plugin/provider/type/spigot/SpigotPluginProviderFactory.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/io/papermc/paper/plugin/provider/type/spigot/SpigotPluginProviderFactory.java -+++ b/src/main/java/io/papermc/paper/plugin/provider/type/spigot/SpigotPluginProviderFactory.java -@@ -0,0 +0,0 @@ - package io.papermc.paper.plugin.provider.type.spigot; - -+import io.papermc.paper.plugin.entrypoint.classloader.BytecodeModifyingURLClassLoader; - import io.papermc.paper.plugin.provider.configuration.serializer.constraints.PluginConfigConstraints; - import io.papermc.paper.plugin.provider.type.PluginTypeFactory; - import org.bukkit.plugin.InvalidDescriptionException; - import org.bukkit.plugin.PluginDescriptionFile; -+import org.bukkit.plugin.java.LibraryLoader; - import org.yaml.snakeyaml.error.YAMLException; - - import java.io.IOException; -@@ -0,0 +0,0 @@ import java.util.jar.JarFile; - - class SpigotPluginProviderFactory implements PluginTypeFactory<SpigotPluginProvider, PluginDescriptionFile> { - -+ static { -+ LibraryLoader.LIBRARY_LOADER_FACTORY = BytecodeModifyingURLClassLoader::new; -+ } -+ - @Override - public SpigotPluginProvider build(JarFile file, PluginDescriptionFile configuration, Path source) throws InvalidDescriptionException { - // Copied from SimplePluginManager#loadPlugins diff --git a/patches/server/Plugin-remapping.patch b/patches/server/Plugin-remapping.patch index 7e66e41726..6bb3f1573b 100644 --- a/patches/server/Plugin-remapping.patch +++ b/patches/server/Plugin-remapping.patch @@ -74,10 +74,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 import io.papermc.paper.plugin.provider.PluginProvider; import io.papermc.paper.plugin.provider.type.paper.PaperPluginParent; +import io.papermc.paper.pluginremap.PluginRemapper; ++import java.util.function.Function; import joptsimple.OptionSet; import net.minecraft.server.dedicated.DedicatedServer; import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.craftbukkit.CraftServer; ++import org.bukkit.plugin.java.LibraryLoader; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; @@ -93,6 +95,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + this.pluginRemapper = Boolean.getBoolean("paper.disable-plugin-rewriting") + ? null + : PluginRemapper.create(pluginDirectory); ++ LibraryLoader.REMAPPER = this.pluginRemapper == null ? Function.identity() : this.pluginRemapper::remapLibraries; } private static PluginInitializerManager parse(@NotNull final OptionSet minecraftOptionSet) throws Exception { @@ -104,6 +107,31 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 // Register the default plugin directory io.papermc.paper.plugin.util.EntrypointUtil.registerProvidersFromSource(io.papermc.paper.plugin.provider.source.DirectoryProviderSource.INSTANCE, pluginSystem.pluginDirectoryPath()); +diff --git a/src/main/java/io/papermc/paper/plugin/loader/PaperClasspathBuilder.java b/src/main/java/io/papermc/paper/plugin/loader/PaperClasspathBuilder.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/io/papermc/paper/plugin/loader/PaperClasspathBuilder.java ++++ b/src/main/java/io/papermc/paper/plugin/loader/PaperClasspathBuilder.java +@@ -0,0 +0,0 @@ + package io.papermc.paper.plugin.loader; + ++import io.papermc.paper.plugin.PluginInitializerManager; + import io.papermc.paper.plugin.bootstrap.PluginProviderContext; + import io.papermc.paper.plugin.loader.library.ClassPathLibrary; + import io.papermc.paper.plugin.loader.library.PaperLibraryStore; +@@ -0,0 +0,0 @@ public class PaperClasspathBuilder implements PluginClasspathBuilder { + } + + List<Path> paths = paperLibraryStore.getPaths(); ++ if (PluginInitializerManager.instance().pluginRemapper != null) { ++ paths = PluginInitializerManager.instance().pluginRemapper.remapLibraries(paths); ++ } + URL[] urls = new URL[paths.size()]; + for (int i = 0; i < paths.size(); i++) { +- Path path = paperLibraryStore.getPaths().get(i); ++ Path path = paths.get(i); + try { + urls[i] = path.toUri().toURL(); + } catch (MalformedURLException e) { diff --git a/src/main/java/io/papermc/paper/plugin/provider/source/DirectoryProviderSource.java b/src/main/java/io/papermc/paper/plugin/provider/source/DirectoryProviderSource.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/io/papermc/paper/plugin/provider/source/DirectoryProviderSource.java @@ -397,6 +425,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + public static final boolean DEBUG_LOGGING = Boolean.getBoolean("Paper.PluginRemapperDebug"); + private static final String PAPER_REMAPPED = ".paper-remapped"; + private static final String UNKNOWN_ORIGIN = "unknown-origin"; ++ private static final String LIBRARIES = "libraries"; + private static final String EXTRA_PLUGINS = "extra-plugins"; + private static final String REMAP_CLASSPATH = "remap-classpath"; + private static final String REVERSED_MAPPINGS = "mappings/reversed"; @@ -407,6 +436,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + private final RemappedPluginIndex remappedPlugins; + private final RemappedPluginIndex extraPlugins; + private final UnknownOriginRemappedPluginIndex unknownOrigin; ++ private final UnknownOriginRemappedPluginIndex libraries; + private @Nullable CompletableFuture<IMappingFile> reversedMappings; + + public PluginRemapper(final Path pluginsDir) { @@ -418,6 +448,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + this.remappedPlugins = new RemappedPluginIndex(remappedPlugins, false); + this.extraPlugins = new RemappedPluginIndex(this.remappedPlugins.dir().resolve(EXTRA_PLUGINS), true); + this.unknownOrigin = new UnknownOriginRemappedPluginIndex(this.remappedPlugins.dir().resolve(UNKNOWN_ORIGIN)); ++ this.libraries = new UnknownOriginRemappedPluginIndex(this.remappedPlugins.dir().resolve(LIBRARIES)); + } + + public static @Nullable PluginRemapper create(final Path pluginsDir) { @@ -446,6 +477,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + this.remappedPlugins.write(); + this.extraPlugins.write(); + this.unknownOrigin.write(clean); ++ this.libraries.write(clean); + } + + // Called on startup and reload @@ -465,6 +497,29 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + this.save(false); + } + ++ public List<Path> remapLibraries(final List<Path> libraries) { ++ final List<CompletableFuture<Path>> tasks = new ArrayList<>(); ++ for (final Path lib : libraries) { ++ if (!lib.getFileName().toString().endsWith(".jar")) { ++ if (DEBUG_LOGGING) { ++ LOGGER.info("Library '{}' is not a jar.", libraries); ++ } ++ tasks.add(CompletableFuture.completedFuture(lib)); ++ continue; ++ } ++ final @Nullable Path cached = this.libraries.getIfPresent(lib); ++ if (cached != null) { ++ if (DEBUG_LOGGING) { ++ LOGGER.info("Library '{}' has not changed since last remap.", libraries); ++ } ++ tasks.add(CompletableFuture.completedFuture(cached)); ++ continue; ++ } ++ tasks.add(this.remapLibrary(this.libraries, lib)); ++ } ++ return waitForAll(tasks); ++ } ++ + public Path rewritePlugin(final Path plugin) { + // Already remapped + if (plugin.getParent().equals(this.remappedPlugins.dir()) @@ -585,6 +640,20 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + }, executor).thenCompose(f -> f); + } + ++ private CompletableFuture<Path> remapPlugin( ++ final RemappedPluginIndex index, ++ final Path inputFile ++ ) { ++ return this.remap(index, inputFile, false); ++ } ++ ++ private CompletableFuture<Path> remapLibrary( ++ final RemappedPluginIndex index, ++ final Path inputFile ++ ) { ++ return this.remap(index, inputFile, true); ++ } ++ + /** + * Returns the remapped file if remapping was necessary, otherwise null. + * @@ -592,7 +661,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + * @param inputFile input file + * @return remapped file, or inputFile if no remapping was necessary + */ -+ private CompletableFuture<Path> remapPlugin(final RemappedPluginIndex index, final Path inputFile) { ++ private CompletableFuture<Path> remap( ++ final RemappedPluginIndex index, ++ final Path inputFile, ++ final boolean library ++ ) { + final Path destination = index.input(inputFile); + + try (final FileSystem fs = FileSystems.newFileSystem(inputFile, new HashMap<>())) { @@ -608,18 +681,35 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } else { + ns = null; + } -+ if (ns != null && (ns.equals(InsertManifestAttribute.MOJANG_NAMESPACE) || ns.equals(InsertManifestAttribute.MOJANG_PLUS_YARN_NAMESPACE))) { -+ if (DEBUG_LOGGING) { -+ LOGGER.info("Plugin '{}' is already Mojang mapped.", inputFile); ++ final boolean mojangMappedManifest = ns != null && (ns.equals(InsertManifestAttribute.MOJANG_NAMESPACE) || ns.equals(InsertManifestAttribute.MOJANG_PLUS_YARN_NAMESPACE)); ++ if (library) { ++ if (mojangMappedManifest) { ++ if (DEBUG_LOGGING) { ++ LOGGER.info("Library '{}' is already Mojang mapped.", inputFile); ++ } ++ index.skip(inputFile); ++ return CompletableFuture.completedFuture(inputFile); ++ } else if (ns == null) { ++ if (DEBUG_LOGGING) { ++ LOGGER.info("Library '{}' does not specify a mappings namespace (not remapping).", inputFile); ++ } ++ index.skip(inputFile); ++ return CompletableFuture.completedFuture(inputFile); + } -+ index.skip(inputFile); -+ return CompletableFuture.completedFuture(inputFile); -+ } else if (ns == null && Files.exists(fs.getPath(PluginFileType.PAPER_PLUGIN_YML))) { -+ if (DEBUG_LOGGING) { -+ LOGGER.info("Plugin '{}' is a Paper plugin with no namespace specified.", inputFile); ++ } else { ++ if (mojangMappedManifest) { ++ if (DEBUG_LOGGING) { ++ LOGGER.info("Plugin '{}' is already Mojang mapped.", inputFile); ++ } ++ index.skip(inputFile); ++ return CompletableFuture.completedFuture(inputFile); ++ } else if (ns == null && Files.exists(fs.getPath(PluginFileType.PAPER_PLUGIN_YML))) { ++ if (DEBUG_LOGGING) { ++ LOGGER.info("Plugin '{}' is a Paper plugin with no namespace specified.", inputFile); ++ } ++ index.skip(inputFile); ++ return CompletableFuture.completedFuture(inputFile); + } -+ index.skip(inputFile); -+ return CompletableFuture.completedFuture(inputFile); + } + } catch (final IOException ex) { + throw new RuntimeException("Failed to open plugin jar " + inputFile, ex); @@ -643,7 +733,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } catch (final Exception ex) { + throw new RuntimeException("Failed to remap plugin jar '" + inputFile + "'", ex); + } -+ LOGGER.info("Done remapping plugin '{}' in {}ms.", inputFile, System.currentTimeMillis() - start); ++ LOGGER.info("Done remapping {} '{}' in {}ms.", library ? "library" : "plugin", inputFile, System.currentTimeMillis() - start); + return destination; + }, this.threadPool); + } diff --git a/patches/server/Remap-reflection-calls-in-plugins-using-internals.patch b/patches/server/Remap-reflection-calls-in-plugins-using-internals.patch index 8b164ce7b7..9e8da09556 100644 --- a/patches/server/Remap-reflection-calls-in-plugins-using-internals.patch +++ b/patches/server/Remap-reflection-calls-in-plugins-using-internals.patch @@ -43,6 +43,197 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 if (mojName == null && MOJANG_TO_OBF.containsKey(name)) { mojName = name; } +diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/BytecodeModifyingURLClassLoader.java b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/BytecodeModifyingURLClassLoader.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/BytecodeModifyingURLClassLoader.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.entrypoint.classloader; ++ ++import io.papermc.paper.pluginremap.reflect.ReflectionRemapper; ++import java.io.IOException; ++import java.io.InputStream; ++import java.io.UncheckedIOException; ++import java.net.JarURLConnection; ++import java.net.URL; ++import java.net.URLClassLoader; ++import java.security.CodeSigner; ++import java.security.CodeSource; ++import java.util.Map; ++import java.util.concurrent.ConcurrentHashMap; ++import java.util.function.Function; ++import java.util.jar.Attributes; ++import java.util.jar.Manifest; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.objectweb.asm.ClassReader; ++import org.objectweb.asm.ClassVisitor; ++import org.objectweb.asm.ClassWriter; ++ ++import static java.util.Objects.requireNonNullElse; ++ ++public final class BytecodeModifyingURLClassLoader extends URLClassLoader { ++ static { ++ ClassLoader.registerAsParallelCapable(); ++ } ++ ++ private static final Object MISSING_MANIFEST = new Object(); ++ ++ private final Function<byte[], byte[]> modifier; ++ private final Map<String, Object> manifests = new ConcurrentHashMap<>(); ++ ++ public BytecodeModifyingURLClassLoader( ++ final URL[] urls, ++ final ClassLoader parent, ++ final Function<byte[], byte[]> modifier ++ ) { ++ super(urls, parent); ++ this.modifier = modifier; ++ } ++ ++ public BytecodeModifyingURLClassLoader( ++ final URL[] urls, ++ final ClassLoader parent ++ ) { ++ this(urls, parent, bytes -> { ++ final ClassReader classReader = new ClassReader(bytes); ++ final ClassWriter classWriter = new ClassWriter(classReader, 0); ++ final ClassVisitor visitor = ReflectionRemapper.visitor(classWriter); ++ if (visitor == classWriter) { ++ return bytes; ++ } ++ classReader.accept(visitor, 0); ++ return classWriter.toByteArray(); ++ }); ++ } ++ ++ @Override ++ protected Class<?> findClass(final String name) throws ClassNotFoundException { ++ final Class<?> result; ++ final String path = name.replace('.', '/').concat(".class"); ++ final URL url = this.findResource(path); ++ if (url != null) { ++ try { ++ result = this.defineClass(name, url); ++ } catch (final IOException e) { ++ throw new ClassNotFoundException(name, e); ++ } ++ } else { ++ result = null; ++ } ++ if (result == null) { ++ throw new ClassNotFoundException(name); ++ } ++ return result; ++ } ++ ++ private Class<?> defineClass(String name, URL url) throws IOException { ++ int i = name.lastIndexOf('.'); ++ if (i != -1) { ++ String pkgname = name.substring(0, i); ++ // Check if package already loaded. ++ final @Nullable Manifest man = this.manifestFor(url); ++ if (this.getAndVerifyPackage(pkgname, man, url) == null) { ++ try { ++ if (man != null) { ++ this.definePackage(pkgname, man, url); ++ } else { ++ this.definePackage(pkgname, null, null, null, null, null, null, null); ++ } ++ } catch (IllegalArgumentException iae) { ++ // parallel-capable class loaders: re-verify in case of a ++ // race condition ++ if (this.getAndVerifyPackage(pkgname, man, url) == null) { ++ // Should never happen ++ throw new AssertionError("Cannot find package " + ++ pkgname); ++ } ++ } ++ } ++ } ++ final byte[] bytes; ++ try (final InputStream is = url.openStream()) { ++ bytes = is.readAllBytes(); ++ } ++ ++ final byte[] modified = this.modifier.apply(bytes); ++ ++ final CodeSource cs = new CodeSource(url, (CodeSigner[]) null); ++ return this.defineClass(name, modified, 0, modified.length, cs); ++ } ++ ++ private Package getAndVerifyPackage( ++ String pkgname, ++ Manifest man, URL url ++ ) { ++ Package pkg = getDefinedPackage(pkgname); ++ if (pkg != null) { ++ // Package found, so check package sealing. ++ if (pkg.isSealed()) { ++ // Verify that code source URL is the same. ++ if (!pkg.isSealed(url)) { ++ throw new SecurityException( ++ "sealing violation: package " + pkgname + " is sealed"); ++ } ++ } else { ++ // Make sure we are not attempting to seal the package ++ // at this code source URL. ++ if ((man != null) && this.isSealed(pkgname, man)) { ++ throw new SecurityException( ++ "sealing violation: can't seal package " + pkgname + ++ ": already loaded"); ++ } ++ } ++ } ++ return pkg; ++ } ++ ++ private boolean isSealed(String name, Manifest man) { ++ Attributes attr = man.getAttributes(name.replace('.', '/').concat("/")); ++ String sealed = null; ++ if (attr != null) { ++ sealed = attr.getValue(Attributes.Name.SEALED); ++ } ++ if (sealed == null) { ++ if ((attr = man.getMainAttributes()) != null) { ++ sealed = attr.getValue(Attributes.Name.SEALED); ++ } ++ } ++ return "true".equalsIgnoreCase(sealed); ++ } ++ ++ private @Nullable Manifest manifestFor(final URL url) throws IOException { ++ Manifest man = null; ++ if (url.getProtocol().equals("jar")) { ++ try { ++ final Object computedManifest = this.manifests.computeIfAbsent(jarName(url), $ -> { ++ try { ++ final Manifest m = ((JarURLConnection) url.openConnection()).getManifest(); ++ return requireNonNullElse(m, MISSING_MANIFEST); ++ } catch (final IOException e) { ++ throw new UncheckedIOException(e); ++ } ++ }); ++ if (computedManifest instanceof Manifest found) { ++ man = found; ++ } ++ } catch (final UncheckedIOException e) { ++ throw e.getCause(); ++ } catch (final IllegalArgumentException e) { ++ throw new IOException(e); ++ } ++ } ++ return man; ++ } ++ ++ private static String jarName(final URL sourceUrl) { ++ final int exclamationIdx = sourceUrl.getPath().lastIndexOf('!'); ++ if (exclamationIdx != -1) { ++ return sourceUrl.getPath().substring(0, exclamationIdx); ++ } ++ throw new IllegalArgumentException("Could not find jar for URL " + sourceUrl); ++ } ++} diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/PaperClassloaderBytecodeModifier.java b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/PaperClassloaderBytecodeModifier.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/PaperClassloaderBytecodeModifier.java @@ -66,6 +257,70 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + return classWriter.toByteArray(); } } +diff --git a/src/main/java/io/papermc/paper/plugin/loader/PaperClasspathBuilder.java b/src/main/java/io/papermc/paper/plugin/loader/PaperClasspathBuilder.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/io/papermc/paper/plugin/loader/PaperClasspathBuilder.java ++++ b/src/main/java/io/papermc/paper/plugin/loader/PaperClasspathBuilder.java +@@ -0,0 +0,0 @@ package io.papermc.paper.plugin.loader; + + import io.papermc.paper.plugin.PluginInitializerManager; + import io.papermc.paper.plugin.bootstrap.PluginProviderContext; ++import io.papermc.paper.plugin.entrypoint.classloader.BytecodeModifyingURLClassLoader; ++import io.papermc.paper.plugin.entrypoint.classloader.PaperPluginClassLoader; + import io.papermc.paper.plugin.loader.library.ClassPathLibrary; + import io.papermc.paper.plugin.loader.library.PaperLibraryStore; +-import io.papermc.paper.plugin.entrypoint.classloader.PaperPluginClassLoader; + import io.papermc.paper.plugin.provider.configuration.PaperPluginMeta; +-import org.jetbrains.annotations.NotNull; +- + import java.io.IOException; + import java.net.MalformedURLException; + import java.net.URL; +@@ -0,0 +0,0 @@ import java.util.ArrayList; + import java.util.List; + import java.util.jar.JarFile; + import java.util.logging.Logger; ++import org.jetbrains.annotations.NotNull; + + public class PaperClasspathBuilder implements PluginClasspathBuilder { + +@@ -0,0 +0,0 @@ public class PaperClasspathBuilder implements PluginClasspathBuilder { + } + + try { +- return new PaperPluginClassLoader(logger, source, jarFile, configuration, this.getClass().getClassLoader(), new URLClassLoader(urls, getClass().getClassLoader())); ++ final URLClassLoader libraryLoader = new BytecodeModifyingURLClassLoader(urls, this.getClass().getClassLoader()); ++ return new PaperPluginClassLoader(logger, source, jarFile, configuration, this.getClass().getClassLoader(), libraryLoader); + } catch (IOException exception) { + throw new RuntimeException(exception); + } +diff --git a/src/main/java/io/papermc/paper/plugin/provider/type/spigot/SpigotPluginProviderFactory.java b/src/main/java/io/papermc/paper/plugin/provider/type/spigot/SpigotPluginProviderFactory.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/io/papermc/paper/plugin/provider/type/spigot/SpigotPluginProviderFactory.java ++++ b/src/main/java/io/papermc/paper/plugin/provider/type/spigot/SpigotPluginProviderFactory.java +@@ -0,0 +0,0 @@ + package io.papermc.paper.plugin.provider.type.spigot; + ++import io.papermc.paper.plugin.entrypoint.classloader.BytecodeModifyingURLClassLoader; + import io.papermc.paper.plugin.provider.configuration.serializer.constraints.PluginConfigConstraints; + import io.papermc.paper.plugin.provider.type.PluginTypeFactory; + import org.bukkit.plugin.InvalidDescriptionException; + import org.bukkit.plugin.PluginDescriptionFile; ++import org.bukkit.plugin.java.LibraryLoader; + import org.yaml.snakeyaml.error.YAMLException; + + import java.io.IOException; +@@ -0,0 +0,0 @@ import java.util.jar.JarFile; + + class SpigotPluginProviderFactory implements PluginTypeFactory<SpigotPluginProvider, PluginDescriptionFile> { + ++ static { ++ LibraryLoader.LIBRARY_LOADER_FACTORY = BytecodeModifyingURLClassLoader::new; ++ } ++ + @Override + public SpigotPluginProvider build(JarFile file, PluginDescriptionFile configuration, Path source) throws InvalidDescriptionException { + // Copied from SimplePluginManager#loadPlugins diff --git a/src/main/java/io/papermc/paper/pluginremap/reflect/PaperReflection.java b/src/main/java/io/papermc/paper/pluginremap/reflect/PaperReflection.java new file mode 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000