From fae673b95c06b50eeaab28d21b02f6690f8da23c Mon Sep 17 00:00:00 2001 From: Nassim Jahnke Date: Sun, 30 Oct 2022 23:47:26 +0100 Subject: [PATCH] Remap reflection calls in plugins using internals Co-authored-by: Jason Penilla <11360596+jpenilla@users.noreply.github.com> --- paper-server/build.gradle.kts | 6 + .../serializer/PacketClassSerializer.java | 3 +- .../BytecodeModifyingURLClassLoader.java | 187 ++++++++++++++++ .../PaperClassloaderBytecodeModifier.java | 2 +- .../plugin/loader/PaperClasspathBuilder.java | 12 +- .../spigot/SpigotPluginProviderFactory.java | 9 + .../pluginremap/reflect/PaperReflection.java | 211 ++++++++++++++++++ .../reflect/ReflectionRemapper.java | 66 ++++++ .../paper/util/MappingEnvironment.java | 2 + .../paper/util/StacktraceDeobfuscator.java | 6 + .../bukkit/craftbukkit/util/Commodore.java | 33 +-- .../craftbukkit/util/CraftMagicNumbers.java | 9 +- 12 files changed, 518 insertions(+), 28 deletions(-) create mode 100644 paper-server/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/BytecodeModifyingURLClassLoader.java create mode 100644 paper-server/src/main/java/io/papermc/paper/pluginremap/reflect/PaperReflection.java create mode 100644 paper-server/src/main/java/io/papermc/paper/pluginremap/reflect/ReflectionRemapper.java diff --git a/paper-server/build.gradle.kts b/paper-server/build.gradle.kts index fdf92d6d2f..605853668f 100644 --- a/paper-server/build.gradle.kts +++ b/paper-server/build.gradle.kts @@ -63,6 +63,12 @@ dependencies { testImplementation("org.junit-pioneer:junit-pioneer:2.2.0") // Paper - CartesianTest implementation("net.neoforged:srgutils:1.0.9") // Paper - mappings handling implementation("net.neoforged:AutoRenamingTool:2.0.3") // Paper - remap plugins + // Paper start - Remap reflection + val reflectionRewriterVersion = "0.0.3" + implementation("io.papermc:reflection-rewriter:$reflectionRewriterVersion") + implementation("io.papermc:reflection-rewriter-runtime:$reflectionRewriterVersion") + implementation("io.papermc:reflection-rewriter-proxy-generator:$reflectionRewriterVersion") + // Paper end - Remap reflection } paperweight { diff --git a/paper-server/src/main/java/io/papermc/paper/configuration/serializer/PacketClassSerializer.java b/paper-server/src/main/java/io/papermc/paper/configuration/serializer/PacketClassSerializer.java index b619350521..a2fe12513b 100644 --- a/paper-server/src/main/java/io/papermc/paper/configuration/serializer/PacketClassSerializer.java +++ b/paper-server/src/main/java/io/papermc/paper/configuration/serializer/PacketClassSerializer.java @@ -5,6 +5,7 @@ import com.google.common.collect.ImmutableBiMap; import com.mojang.logging.LogUtils; import io.leangen.geantyref.TypeToken; import io.papermc.paper.configuration.serializer.collections.MapSerializer; +import io.papermc.paper.util.MappingEnvironment; import io.papermc.paper.util.ObfHelper; import java.lang.reflect.Type; import java.util.List; @@ -68,7 +69,7 @@ public final class PacketClassSerializer extends ScalarSerializer> packetClass, final Predicate> typeSupported) { final String name = packetClass.getName(); - @Nullable String mojName = ObfHelper.INSTANCE.mappingsByMojangName() == null ? name : MOJANG_TO_OBF.inverse().get(name); // if the mappings are null, running on moj-mapped server + @Nullable String mojName = ObfHelper.INSTANCE.mappingsByMojangName() == null || !MappingEnvironment.reobf() ? name : MOJANG_TO_OBF.inverse().get(name); // if the mappings are null, running on moj-mapped server if (mojName == null && MOJANG_TO_OBF.containsKey(name)) { mojName = name; } diff --git a/paper-server/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/BytecodeModifyingURLClassLoader.java b/paper-server/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/BytecodeModifyingURLClassLoader.java new file mode 100644 index 0000000000..1240c061c1 --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/BytecodeModifyingURLClassLoader.java @@ -0,0 +1,187 @@ +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.URI; +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 modifier; + private final Map manifests = new ConcurrentHashMap<>(); + + public BytecodeModifyingURLClassLoader( + final URL[] urls, + final ClassLoader parent, + final Function 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); + final URL jarUrl = URI.create(jarName(url)).toURL(); + if (this.getAndVerifyPackage(pkgname, man, jarUrl) == null) { + try { + if (man != null) { + this.definePackage(pkgname, man, jarUrl); + } 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, jarUrl) == 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/paper-server/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/PaperClassloaderBytecodeModifier.java b/paper-server/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/PaperClassloaderBytecodeModifier.java index f9a2c55a35..0e734c07db 100644 --- a/paper-server/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/PaperClassloaderBytecodeModifier.java +++ b/paper-server/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/PaperClassloaderBytecodeModifier.java @@ -7,6 +7,6 @@ public class PaperClassloaderBytecodeModifier implements ClassloaderBytecodeModi @Override public byte[] modify(PluginMeta configuration, byte[] bytecode) { - return bytecode; + return io.papermc.paper.pluginremap.reflect.ReflectionRemapper.processClass(bytecode); } } diff --git a/paper-server/src/main/java/io/papermc/paper/plugin/loader/PaperClasspathBuilder.java b/paper-server/src/main/java/io/papermc/paper/plugin/loader/PaperClasspathBuilder.java index f576060c8f..f871a329eb 100644 --- a/paper-server/src/main/java/io/papermc/paper/plugin/loader/PaperClasspathBuilder.java +++ b/paper-server/src/main/java/io/papermc/paper/plugin/loader/PaperClasspathBuilder.java @@ -2,12 +2,12 @@ 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 io.papermc.paper.util.MappingEnvironment; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; @@ -17,6 +17,7 @@ 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 { @@ -60,7 +61,10 @@ public class PaperClasspathBuilder implements PluginClasspathBuilder { } try { - return new PaperPluginClassLoader(logger, source, jarFile, configuration, this.getClass().getClassLoader(), new URLClassLoader(urls, getClass().getClassLoader())); + final URLClassLoader libraryLoader = MappingEnvironment.DISABLE_PLUGIN_REMAPPING + ? new URLClassLoader(urls, this.getClass().getClassLoader()) + : 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/paper-server/src/main/java/io/papermc/paper/plugin/provider/type/spigot/SpigotPluginProviderFactory.java b/paper-server/src/main/java/io/papermc/paper/plugin/provider/type/spigot/SpigotPluginProviderFactory.java index bdd9bc8a41..1bf0fa1530 100644 --- a/paper-server/src/main/java/io/papermc/paper/plugin/provider/type/spigot/SpigotPluginProviderFactory.java +++ b/paper-server/src/main/java/io/papermc/paper/plugin/provider/type/spigot/SpigotPluginProviderFactory.java @@ -1,9 +1,12 @@ 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 io.papermc.paper.util.MappingEnvironment; 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; @@ -15,6 +18,12 @@ import java.util.jar.JarFile; class SpigotPluginProviderFactory implements PluginTypeFactory { + static { + if (!MappingEnvironment.DISABLE_PLUGIN_REMAPPING) { + 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/paper-server/src/main/java/io/papermc/paper/pluginremap/reflect/PaperReflection.java b/paper-server/src/main/java/io/papermc/paper/pluginremap/reflect/PaperReflection.java new file mode 100644 index 0000000000..92bc8e4933 --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/pluginremap/reflect/PaperReflection.java @@ -0,0 +1,211 @@ +package io.papermc.paper.pluginremap.reflect; + +import com.mojang.logging.LogUtils; +import io.papermc.paper.util.MappingEnvironment; +import io.papermc.paper.util.ObfHelper; +import io.papermc.reflectionrewriter.runtime.AbstractDefaultRulesReflectionProxy; +import io.papermc.reflectionrewriter.runtime.DefineClassReflectionProxy; +import java.lang.invoke.MethodHandles; +import java.nio.ByteBuffer; +import java.security.CodeSource; +import java.security.ProtectionDomain; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.framework.qual.DefaultQualifier; +import org.slf4j.Logger; + +// todo proper inheritance handling +@SuppressWarnings("unused") +@DefaultQualifier(NonNull.class) +public final class PaperReflection extends AbstractDefaultRulesReflectionProxy implements DefineClassReflectionProxy { + // concat to avoid being rewritten by shadow + private static final Logger LOGGER = LogUtils.getLogger(); + private static final String CB_PACKAGE_PREFIX = "org.bukkit.".concat("craftbukkit."); + private static final String LEGACY_CB_PACKAGE_PREFIX = "org.bukkit.".concat("craftbukkit.") + MappingEnvironment.LEGACY_CB_VERSION + "."; + + private final DefineClassReflectionProxy defineClassProxy; + private final Map mappingsByMojangName; + private final Map mappingsByObfName; + // Reflection does not care about method return values, so this map removes the return value descriptor from the key + private final Map> strippedMethodMappings; + + PaperReflection() { + this.defineClassProxy = DefineClassReflectionProxy.create(PaperReflection::processClass); + if (!MappingEnvironment.hasMappings()) { + this.mappingsByMojangName = Map.of(); + this.mappingsByObfName = Map.of(); + this.strippedMethodMappings = Map.of(); + return; + } + final ObfHelper obfHelper = ObfHelper.INSTANCE; + this.mappingsByMojangName = Objects.requireNonNull(obfHelper.mappingsByMojangName(), "mappingsByMojangName"); + this.mappingsByObfName = Objects.requireNonNull(obfHelper.mappingsByObfName(), "mappingsByObfName"); + this.strippedMethodMappings = this.mappingsByMojangName.entrySet().stream().collect(Collectors.toUnmodifiableMap( + Map.Entry::getKey, + entry -> entry.getValue().strippedMethods() + )); + } + + @Override + protected String mapClassName(final String name) { + final ObfHelper.@Nullable ClassMapping mapping = this.mappingsByObfName.get(name); + return mapping != null ? mapping.mojangName() : removeCraftBukkitRelocation(name); + } + + @Override + protected String mapDeclaredMethodName(final Class clazz, final String name, final Class @Nullable ... parameterTypes) { + final @Nullable Map mapping = this.strippedMethodMappings.get(clazz.getName()); + if (mapping == null) { + return name; + } + return mapping.getOrDefault(strippedMethodKey(name, parameterTypes), name); + } + + @Override + protected String mapMethodName(final Class clazz, final String name, final Class @Nullable ... parameterTypes) { + final @Nullable String mapped = this.findMappedMethodName(clazz, name, parameterTypes); + return mapped != null ? mapped : name; + } + + @Override + protected String mapDeclaredFieldName(final Class clazz, final String name) { + final ObfHelper.@Nullable ClassMapping mapping = this.mappingsByMojangName.get(clazz.getName()); + if (mapping == null) { + return name; + } + return mapping.fieldsByObf().getOrDefault(name, name); + } + + @Override + protected String mapFieldName(final Class clazz, final String name) { + final @Nullable String mapped = this.findMappedFieldName(clazz, name); + return mapped != null ? mapped : name; + } + + private @Nullable String findMappedMethodName(final Class clazz, final String name, final Class @Nullable ... parameterTypes) { + final Map map = this.strippedMethodMappings.get(clazz.getName()); + @Nullable String mapped = null; + if (map != null) { + mapped = map.get(strippedMethodKey(name, parameterTypes)); + if (mapped != null) { + return mapped; + } + } + // JVM checks super before interfaces + final Class superClass = clazz.getSuperclass(); + if (superClass != null) { + mapped = this.findMappedMethodName(superClass, name, parameterTypes); + } + if (mapped == null) { + for (final Class i : clazz.getInterfaces()) { + mapped = this.findMappedMethodName(i, name, parameterTypes); + if (mapped != null) { + break; + } + } + } + return mapped; + } + + private @Nullable String findMappedFieldName(final Class clazz, final String name) { + final ObfHelper.ClassMapping mapping = this.mappingsByMojangName.get(clazz.getName()); + @Nullable String mapped = null; + if (mapping != null) { + mapped = mapping.fieldsByObf().get(name); + if (mapped != null) { + return mapped; + } + } + // The JVM checks super before interfaces + final Class superClass = clazz.getSuperclass(); + if (superClass != null) { + mapped = this.findMappedFieldName(superClass, name); + } + if (mapped == null) { + for (final Class i : clazz.getInterfaces()) { + mapped = this.findMappedFieldName(i, name); + if (mapped != null) { + break; + } + } + } + return mapped; + } + + private static String strippedMethodKey(final String methodName, final Class @Nullable ... parameterTypes) { + return methodName + parameterDescriptor(parameterTypes); + } + + private static String parameterDescriptor(final Class @Nullable ... parameterTypes) { + if (parameterTypes == null) { + // Null parameterTypes is treated as an empty array + return "()"; + } + final StringBuilder builder = new StringBuilder(); + builder.append('('); + for (final Class parameterType : parameterTypes) { + builder.append(parameterType.descriptorString()); + } + builder.append(')'); + return builder.toString(); + } + + private static String removeCraftBukkitRelocation(final String name) { + if (MappingEnvironment.hasMappings()) { + // Relocation is applied in reobf, and when mappings are present they handle the relocation + return name; + } + if (name.startsWith(LEGACY_CB_PACKAGE_PREFIX)) { + return CB_PACKAGE_PREFIX + name.substring(LEGACY_CB_PACKAGE_PREFIX.length()); + } + return name; + } + + @Override + public Class defineClass(final Object loader, final byte[] b, final int off, final int len) throws ClassFormatError { + return this.defineClassProxy.defineClass(loader, b, off, len); + } + + @Override + public Class defineClass(final Object loader, final String name, final byte[] b, final int off, final int len) throws ClassFormatError { + return this.defineClassProxy.defineClass(loader, name, b, off, len); + } + + @Override + public Class defineClass(final Object loader, final @Nullable String name, final byte[] b, final int off, final int len, final @Nullable ProtectionDomain protectionDomain) throws ClassFormatError { + return this.defineClassProxy.defineClass(loader, name, b, off, len, protectionDomain); + } + + @Override + public Class defineClass(final Object loader, final String name, final ByteBuffer b, final ProtectionDomain protectionDomain) throws ClassFormatError { + return this.defineClassProxy.defineClass(loader, name, b, protectionDomain); + } + + @Override + public Class defineClass(final Object secureLoader, final String name, final byte[] b, final int off, final int len, final CodeSource cs) { + return this.defineClassProxy.defineClass(secureLoader, name, b, off, len, cs); + } + + @Override + public Class defineClass(final Object secureLoader, final String name, final ByteBuffer b, final CodeSource cs) { + return this.defineClassProxy.defineClass(secureLoader, name, b, cs); + } + + @Override + public Class defineClass(final MethodHandles.Lookup lookup, final byte[] bytes) throws IllegalAccessException { + return this.defineClassProxy.defineClass(lookup, bytes); + } + + // todo apply bytecode remap here as well + private static byte[] processClass(final byte[] bytes) { + try { + return ReflectionRemapper.processClass(bytes); + } catch (final Exception ex) { + LOGGER.warn("Failed to process class bytes", ex); + return bytes; + } + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/pluginremap/reflect/ReflectionRemapper.java b/paper-server/src/main/java/io/papermc/paper/pluginremap/reflect/ReflectionRemapper.java new file mode 100644 index 0000000000..a3045afbc0 --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/pluginremap/reflect/ReflectionRemapper.java @@ -0,0 +1,66 @@ +package io.papermc.paper.pluginremap.reflect; + +import io.papermc.asm.ClassInfoProvider; +import io.papermc.asm.RewriteRuleVisitorFactory; +import io.papermc.paper.util.MappingEnvironment; +import io.papermc.reflectionrewriter.BaseReflectionRules; +import io.papermc.reflectionrewriter.DefineClassRule; +import io.papermc.reflectionrewriter.proxygenerator.ProxyGenerator; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Method; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.framework.qual.DefaultQualifier; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Opcodes; + +@DefaultQualifier(NonNull.class) +public final class ReflectionRemapper { + private static final String PAPER_REFLECTION_HOLDER = "io.papermc.paper.pluginremap.reflect.PaperReflectionHolder"; + private static final String PAPER_REFLECTION_HOLDER_DESC = PAPER_REFLECTION_HOLDER.replace('.', '/'); + private static final RewriteRuleVisitorFactory VISITOR_FACTORY = RewriteRuleVisitorFactory.create( + Opcodes.ASM9, + chain -> chain.then(new BaseReflectionRules(PAPER_REFLECTION_HOLDER).rules()) + .then(DefineClassRule.create(PAPER_REFLECTION_HOLDER_DESC, true)), + ClassInfoProvider.basic() + ); + + static { + if (!MappingEnvironment.reobf()) { + setupProxy(); + } + } + + private ReflectionRemapper() { + } + + public static ClassVisitor visitor(final ClassVisitor parent) { + if (MappingEnvironment.reobf() || MappingEnvironment.DISABLE_PLUGIN_REMAPPING) { + return parent; + } + return VISITOR_FACTORY.createVisitor(parent); + } + + public static byte[] processClass(final byte[] bytes) { + if (MappingEnvironment.DISABLE_PLUGIN_REMAPPING) { + return bytes; + } + final ClassReader classReader = new ClassReader(bytes); + final ClassWriter classWriter = new ClassWriter(classReader, 0); + classReader.accept(ReflectionRemapper.visitor(classWriter), 0); + return classWriter.toByteArray(); + } + + private static void setupProxy() { + try { + final byte[] bytes = ProxyGenerator.generateProxy(PaperReflection.class, PAPER_REFLECTION_HOLDER_DESC); + final MethodHandles.Lookup lookup = MethodHandles.lookup(); + final Class generated = lookup.defineClass(bytes); + final Method init = generated.getDeclaredMethod("init", PaperReflection.class); + init.invoke(null, new PaperReflection()); + } catch (final ReflectiveOperationException ex) { + throw new RuntimeException(ex); + } + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/util/MappingEnvironment.java b/paper-server/src/main/java/io/papermc/paper/util/MappingEnvironment.java index 8e4229634d..8511c24708 100644 --- a/paper-server/src/main/java/io/papermc/paper/util/MappingEnvironment.java +++ b/paper-server/src/main/java/io/papermc/paper/util/MappingEnvironment.java @@ -10,6 +10,8 @@ import org.checkerframework.framework.qual.DefaultQualifier; @DefaultQualifier(NonNull.class) public final class MappingEnvironment { + public static final boolean DISABLE_PLUGIN_REMAPPING = Boolean.getBoolean("paper.disablePluginRemapping"); + public static final String LEGACY_CB_VERSION = "v1_21_R3"; private static final @Nullable String MAPPINGS_HASH = readMappingsHash(); private static final boolean REOBF = checkReobf(); diff --git a/paper-server/src/main/java/io/papermc/paper/util/StacktraceDeobfuscator.java b/paper-server/src/main/java/io/papermc/paper/util/StacktraceDeobfuscator.java index 242811578a..0b65fdf531 100644 --- a/paper-server/src/main/java/io/papermc/paper/util/StacktraceDeobfuscator.java +++ b/paper-server/src/main/java/io/papermc/paper/util/StacktraceDeobfuscator.java @@ -29,6 +29,9 @@ public enum StacktraceDeobfuscator { }); public void deobfuscateThrowable(final Throwable throwable) { + if (!MappingEnvironment.reobf()) { + return; + } if (GlobalConfiguration.get() != null && !GlobalConfiguration.get().logging.deobfuscateStacktraces) { // handle null as true return; } @@ -44,6 +47,9 @@ public enum StacktraceDeobfuscator { } public StackTraceElement[] deobfuscateStacktrace(final StackTraceElement[] traceElements) { + if (!MappingEnvironment.reobf()) { + return traceElements; + } if (GlobalConfiguration.get() != null && !GlobalConfiguration.get().logging.deobfuscateStacktraces) { // handle null as true return traceElements; } diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/util/Commodore.java b/paper-server/src/main/java/org/bukkit/craftbukkit/util/Commodore.java index 7d2d5c4ee2..371d31266a 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/util/Commodore.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/util/Commodore.java @@ -131,36 +131,26 @@ public class Commodore { } // Paper start - Plugin rewrites - private static final Map SEARCH_AND_REMOVE = initReplacementsMap(); - private static Map initReplacementsMap() { - Map getAndRemove = new HashMap<>(); - // Be wary of maven shade's relocations - - final java.util.jar.Manifest manifest = io.papermc.paper.util.JarManifests.manifest(Commodore.class); - if (Boolean.getBoolean( "debug.rewriteForIde") && manifest != null) - { - // unversion incoming calls for pre-relocate debug work - final String NMS_REVISION_PACKAGE = "v" + manifest.getMainAttributes().getValue("CraftBukkit-Package-Version") + "/"; - - getAndRemove.put("org/bukkit/".concat("craftbukkit/" + NMS_REVISION_PACKAGE), NMS_REVISION_PACKAGE); + private static final String CB_PACKAGE_PREFIX = "org/bukkit/".concat("craftbukkit/"); + private static final String LEGACY_CB_PACKAGE_PREFIX = CB_PACKAGE_PREFIX + io.papermc.paper.util.MappingEnvironment.LEGACY_CB_VERSION + "/"; + private static String runtimeCbPkgPrefix() { + if (io.papermc.paper.util.MappingEnvironment.reobf()) { + return LEGACY_CB_PACKAGE_PREFIX; } - - return getAndRemove; + return CB_PACKAGE_PREFIX; } @Nonnull private static String getOriginalOrRewrite(@Nonnull String original) { - String rewrite = null; - for ( Map.Entry entry : SEARCH_AND_REMOVE.entrySet() ) - { - if ( original.contains( entry.getKey() ) ) - { - rewrite = original.replace( entry.getValue(), "" ); + // Relocation is applied in reobf, and when mappings are present they handle the relocation + if (!io.papermc.paper.util.MappingEnvironment.reobf() && !io.papermc.paper.util.MappingEnvironment.hasMappings()) { + if (original.contains(LEGACY_CB_PACKAGE_PREFIX)) { + original = original.replace(LEGACY_CB_PACKAGE_PREFIX, CB_PACKAGE_PREFIX); } } - return rewrite != null ? rewrite : original; + return original; } // Paper end - Plugin rewrites @@ -245,6 +235,7 @@ public class Commodore { visitor = new LimitedClassRemapper(cw, new SimpleRemapper(Commodore.ENUM_RENAMES)); } + visitor = io.papermc.paper.pluginremap.reflect.ReflectionRemapper.visitor(visitor); // Paper cr.accept(new ClassRemapper(new ClassVisitor(Opcodes.ASM9, visitor) { final Set rerouteMethodData = new HashSet<>(); String className; diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/paper-server/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java index 8e747d48fb..07316f0043 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java @@ -75,6 +75,7 @@ import org.bukkit.potion.PotionType; @SuppressWarnings("deprecation") public final class CraftMagicNumbers implements UnsafeValues { public static final CraftMagicNumbers INSTANCE = new CraftMagicNumbers(); + public static final boolean DISABLE_OLD_API_SUPPORT = Boolean.getBoolean("paper.disableOldApiSupport"); // Paper private final Commodore commodore = new Commodore(); @@ -347,7 +348,7 @@ public final class CraftMagicNumbers implements UnsafeValues { throw new InvalidPluginException("Plugin API version " + pdf.getAPIVersion() + " is lower than the minimum allowed version. Please update or replace it."); } - if (toCheck.isOlderThan(ApiVersion.FLATTENING)) { + if (!DISABLE_OLD_API_SUPPORT && toCheck.isOlderThan(ApiVersion.FLATTENING)) { // Paper CraftLegacy.init(); } @@ -362,6 +363,12 @@ public final class CraftMagicNumbers implements UnsafeValues { @Override public byte[] processClass(PluginDescriptionFile pdf, String path, byte[] clazz) { + // Paper start + if (DISABLE_OLD_API_SUPPORT) { + // Make sure we still go through our reflection rewriting if needed + return io.papermc.paper.pluginremap.reflect.ReflectionRemapper.processClass(clazz); + } + // Paper end try { clazz = this.commodore.convert(clazz, pdf.getName(), ApiVersion.getOrCreateVersion(pdf.getAPIVersion()), ((CraftServer) Bukkit.getServer()).activeCompatibilities); } catch (Exception ex) {