From 433b5886e13fc49057388dc7328b677244913df5 Mon Sep 17 00:00:00 2001 From: Luke Bemish Date: Sat, 28 Mar 2026 12:53:05 -0400 Subject: [PATCH 1/3] Warn on mixin plugin classloads from the game layer --- .../net/neoforged/fml/loading/FMLLoader.java | 10 +++++++++ .../fml/loading/mixin/FMLClassProvider.java | 22 +++++++++++++++++-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/loader/src/main/java/net/neoforged/fml/loading/FMLLoader.java b/loader/src/main/java/net/neoforged/fml/loading/FMLLoader.java index af52a1122..c96a3e078 100644 --- a/loader/src/main/java/net/neoforged/fml/loading/FMLLoader.java +++ b/loader/src/main/java/net/neoforged/fml/loading/FMLLoader.java @@ -106,6 +106,10 @@ public final class FMLLoader implements AutoCloseable { * The current tail of the class-loader chain. It is moved whenever a new set of Jars is loaded. */ private ClassLoader currentClassLoader; + /** + * Supplies the current tail of the class-loader chain, excluding the game layer. It is safe for plugins to load classes from during transformation or the like. + */ + private Supplier pluginClassLoader = () -> this.currentClassLoader; /** * Resources owned by this loader, such as opened URL classloaders, which * will be closed alongside the loader. @@ -276,6 +280,10 @@ public ClassLoader getCurrentClassLoader() { return currentClassLoader; } + public ClassLoader getPluginClassLoader() { + return pluginClassLoader.get(); + } + public ProgramArgs getProgramArgs() { return programArgs; } @@ -502,6 +510,8 @@ private TransformingClassLoader buildTransformingLoader(ClassProcessorSet classP gameLayer = layer; ownedResources.add(loader); + var parentClassLoader = currentClassLoader; + pluginClassLoader = () -> parentClassLoader; currentClassLoader = loader; Thread.currentThread().setContextClassLoader(loader); return loader; diff --git a/loader/src/main/java/net/neoforged/fml/loading/mixin/FMLClassProvider.java b/loader/src/main/java/net/neoforged/fml/loading/mixin/FMLClassProvider.java index 32754df15..955d0fe11 100644 --- a/loader/src/main/java/net/neoforged/fml/loading/mixin/FMLClassProvider.java +++ b/loader/src/main/java/net/neoforged/fml/loading/mixin/FMLClassProvider.java @@ -7,12 +7,16 @@ import java.net.URL; import net.neoforged.fml.loading.FMLLoader; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.spongepowered.asm.service.IClassProvider; /** * Class provider for use under ModLauncher */ class FMLClassProvider implements IClassProvider { + private static final Logger LOGGER = LogManager.getLogger(); + FMLClassProvider() {} @Override @@ -23,12 +27,26 @@ public URL[] getClassPath() { @Override public Class findClass(String name) throws ClassNotFoundException { - return Class.forName(name, true, FMLLoader.getCurrent().getCurrentClassLoader()); + try { + return Class.forName(name, true, FMLLoader.getCurrent().getPluginClassLoader()); + } catch (ClassNotFoundException e) { + warnOnDeprecatedTclPluginLoad(name); + return Class.forName(name, true, FMLLoader.getCurrent().getCurrentClassLoader()); + } } @Override public Class findClass(String name, boolean initialize) throws ClassNotFoundException { - return Class.forName(name, initialize, FMLLoader.getCurrent().getCurrentClassLoader()); + try { + return Class.forName(name, initialize, FMLLoader.getCurrent().getPluginClassLoader()); + } catch (ClassNotFoundException e) { + warnOnDeprecatedTclPluginLoad(name); + return Class.forName(name, initialize, FMLLoader.getCurrent().getCurrentClassLoader()); + } + } + + private static void warnOnDeprecatedTclPluginLoad(String name) { + LOGGER.error("Mixin attempted to load class {} from the transforming classloader. This behavior is deprecated and may not continue to work; mixin config plugins should be provided from FMLModType=LIBRARY jars instead.", name); } @Override From 0470931790c278b42ea8244f1d144e9263493f06 Mon Sep 17 00:00:00 2001 From: Luke Bemish Date: Sat, 28 Mar 2026 18:33:34 -0400 Subject: [PATCH 2/3] Make slightly more flexible system for providing plugin classloader to ClassProcessors --- .../classloading/transformation/ClassProcessorSet.java | 4 ++-- .../transformation/TransformingClassLoader.java | 2 +- .../main/java/net/neoforged/fml/loading/FMLLoader.java | 10 ---------- .../neoforged/fml/loading/mixin/FMLClassProvider.java | 10 +++++++--- .../fml/loading/mixin/FMLMixinClassProcessor.java | 1 + .../neoforged/fml/loading/mixin/FMLMixinService.java | 8 ++++++-- .../neoforgespi/transformation/ClassProcessor.java | 8 +++++--- 7 files changed, 22 insertions(+), 21 deletions(-) diff --git a/loader/src/main/java/net/neoforged/fml/classloading/transformation/ClassProcessorSet.java b/loader/src/main/java/net/neoforged/fml/classloading/transformation/ClassProcessorSet.java index b31aa0240..6e1c15588 100644 --- a/loader/src/main/java/net/neoforged/fml/classloading/transformation/ClassProcessorSet.java +++ b/loader/src/main/java/net/neoforged/fml/classloading/transformation/ClassProcessorSet.java @@ -113,14 +113,14 @@ public List transformersFor(Type classDesc, boolean isEmpty, Pro return out; } - public void link(Function bytecodeProviderLookup) { + public void link(Function bytecodeProviderLookup, ClassLoader parentClassLoader) { if (linked) { throw new IllegalStateException("This set of class processors is already linked."); } linked = true; for (var processor : sortedProcessors) { - var context = new ClassProcessor.LinkContext(processors, bytecodeProviderLookup.apply(processor.name())); + var context = new ClassProcessor.LinkContext(processors, bytecodeProviderLookup.apply(processor.name()), parentClassLoader); processor.link(context); } } diff --git a/loader/src/main/java/net/neoforged/fml/classloading/transformation/TransformingClassLoader.java b/loader/src/main/java/net/neoforged/fml/classloading/transformation/TransformingClassLoader.java index 1d6eb8b88..7ed0a0487 100644 --- a/loader/src/main/java/net/neoforged/fml/classloading/transformation/TransformingClassLoader.java +++ b/loader/src/main/java/net/neoforged/fml/classloading/transformation/TransformingClassLoader.java @@ -38,7 +38,7 @@ public TransformingClassLoader(ClassProcessorSet classProcessorSet, ClassProcess super("TRANSFORMER", configuration, parentLayers, parentClassLoader); this.classTransformer = new ClassTransformer(classProcessorSet, auditTrail); // The state of this class has to be set up fully before the processors are linked - classProcessorSet.link(processorName -> className -> buildTransformedClassNodeFor(className, processorName)); + classProcessorSet.link(processorName -> className -> buildTransformedClassNodeFor(className, processorName), parentClassLoader); } @Override diff --git a/loader/src/main/java/net/neoforged/fml/loading/FMLLoader.java b/loader/src/main/java/net/neoforged/fml/loading/FMLLoader.java index c96a3e078..af52a1122 100644 --- a/loader/src/main/java/net/neoforged/fml/loading/FMLLoader.java +++ b/loader/src/main/java/net/neoforged/fml/loading/FMLLoader.java @@ -106,10 +106,6 @@ public final class FMLLoader implements AutoCloseable { * The current tail of the class-loader chain. It is moved whenever a new set of Jars is loaded. */ private ClassLoader currentClassLoader; - /** - * Supplies the current tail of the class-loader chain, excluding the game layer. It is safe for plugins to load classes from during transformation or the like. - */ - private Supplier pluginClassLoader = () -> this.currentClassLoader; /** * Resources owned by this loader, such as opened URL classloaders, which * will be closed alongside the loader. @@ -280,10 +276,6 @@ public ClassLoader getCurrentClassLoader() { return currentClassLoader; } - public ClassLoader getPluginClassLoader() { - return pluginClassLoader.get(); - } - public ProgramArgs getProgramArgs() { return programArgs; } @@ -510,8 +502,6 @@ private TransformingClassLoader buildTransformingLoader(ClassProcessorSet classP gameLayer = layer; ownedResources.add(loader); - var parentClassLoader = currentClassLoader; - pluginClassLoader = () -> parentClassLoader; currentClassLoader = loader; Thread.currentThread().setContextClassLoader(loader); return loader; diff --git a/loader/src/main/java/net/neoforged/fml/loading/mixin/FMLClassProvider.java b/loader/src/main/java/net/neoforged/fml/loading/mixin/FMLClassProvider.java index 955d0fe11..80a686866 100644 --- a/loader/src/main/java/net/neoforged/fml/loading/mixin/FMLClassProvider.java +++ b/loader/src/main/java/net/neoforged/fml/loading/mixin/FMLClassProvider.java @@ -16,8 +16,12 @@ */ class FMLClassProvider implements IClassProvider { private static final Logger LOGGER = LogManager.getLogger(); + + private final ClassLoader pluginClassLoader; - FMLClassProvider() {} + FMLClassProvider(ClassLoader pluginClassLoader) { + this.pluginClassLoader = pluginClassLoader; + } @Override @Deprecated @@ -28,7 +32,7 @@ public URL[] getClassPath() { @Override public Class findClass(String name) throws ClassNotFoundException { try { - return Class.forName(name, true, FMLLoader.getCurrent().getPluginClassLoader()); + return Class.forName(name, true, pluginClassLoader); } catch (ClassNotFoundException e) { warnOnDeprecatedTclPluginLoad(name); return Class.forName(name, true, FMLLoader.getCurrent().getCurrentClassLoader()); @@ -38,7 +42,7 @@ public Class findClass(String name) throws ClassNotFoundException { @Override public Class findClass(String name, boolean initialize) throws ClassNotFoundException { try { - return Class.forName(name, initialize, FMLLoader.getCurrent().getPluginClassLoader()); + return Class.forName(name, initialize, pluginClassLoader); } catch (ClassNotFoundException e) { warnOnDeprecatedTclPluginLoad(name); return Class.forName(name, initialize, FMLLoader.getCurrent().getCurrentClassLoader()); diff --git a/loader/src/main/java/net/neoforged/fml/loading/mixin/FMLMixinClassProcessor.java b/loader/src/main/java/net/neoforged/fml/loading/mixin/FMLMixinClassProcessor.java index 691c764fd..9840fa192 100644 --- a/loader/src/main/java/net/neoforged/fml/loading/mixin/FMLMixinClassProcessor.java +++ b/loader/src/main/java/net/neoforged/fml/loading/mixin/FMLMixinClassProcessor.java @@ -34,6 +34,7 @@ public FMLMixinClassProcessor(FMLMixinService service) { @Override public void link(LinkContext context) { this.service.setBytecodeProvider(new FMLClassBytecodeProvider(context.bytecodeProvider(), this)); + this.service.setClassProvider(new FMLClassProvider(context.pluginClassLoader())); } @Override diff --git a/loader/src/main/java/net/neoforged/fml/loading/mixin/FMLMixinService.java b/loader/src/main/java/net/neoforged/fml/loading/mixin/FMLMixinService.java index 113582db5..b6f665696 100644 --- a/loader/src/main/java/net/neoforged/fml/loading/mixin/FMLMixinService.java +++ b/loader/src/main/java/net/neoforged/fml/loading/mixin/FMLMixinService.java @@ -100,6 +100,10 @@ public String getSideName() { public void setBytecodeProvider(@Nullable IClassBytecodeProvider bytecodeProvider) { this.bytecodeProvider = bytecodeProvider; } + + public void setClassProvider(@Nullable IClassProvider classProvider) { + this.classProvider = classProvider; + } @Override public void offer(IMixinInternal internal) { @@ -136,7 +140,7 @@ public boolean isValid() { @Override public IClassProvider getClassProvider() { if (this.classProvider == null) { - this.classProvider = new FMLClassProvider(); + throw new IllegalStateException("Service initialisation incomplete, FMLMixinClassProcessor has not yet been linked"); } return this.classProvider; } @@ -144,7 +148,7 @@ public IClassProvider getClassProvider() { @Override public IClassBytecodeProvider getBytecodeProvider() { if (this.bytecodeProvider == null) { - throw new IllegalStateException("Service initialisation incomplete, launch plugin was not created"); + throw new IllegalStateException("Service initialisation incomplete, FMLMixinClassProcessor has not yet been linked"); } return this.bytecodeProvider; } diff --git a/loader/src/main/java/net/neoforged/neoforgespi/transformation/ClassProcessor.java b/loader/src/main/java/net/neoforged/neoforgespi/transformation/ClassProcessor.java index 63a4f23e8..3c05f3325 100644 --- a/loader/src/main/java/net/neoforged/neoforgespi/transformation/ClassProcessor.java +++ b/loader/src/main/java/net/neoforged/neoforgespi/transformation/ClassProcessor.java @@ -221,12 +221,14 @@ record AfterProcessingContext(Type type) { * The context provided to {@linkplain ClassProcessor class processors} * when they are linked with a bytecode source. * - * @param processors an immutable map of the processors that are being linked. The maps iteration order is the order in which the processors will be applied - * @param bytecodeProvider a provider to access class bytecode for other classes than the class that is being transformed + * @param processors an immutable map of the processors that are being linked. The maps iteration order is the order in which the processors will be applied + * @param bytecodeProvider a provider to access class bytecode for other classes than the class that is being transformed + * @param pluginClassLoader provides access to the classloader before the transforming classloader, for use in safely loading classes */ record LinkContext( @Unmodifiable SequencedMap processors, - BytecodeProvider bytecodeProvider) { + BytecodeProvider bytecodeProvider, + ClassLoader pluginClassLoader) { @ApiStatus.Internal public LinkContext {} } From bb299492fb4f785d89170b0b64e5ba0000854d29 Mon Sep 17 00:00:00 2001 From: Luke Bemish Date: Sat, 28 Mar 2026 18:35:48 -0400 Subject: [PATCH 3/3] Fix formatting --- .../java/net/neoforged/fml/loading/mixin/FMLClassProvider.java | 2 +- .../java/net/neoforged/fml/loading/mixin/FMLMixinService.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/loader/src/main/java/net/neoforged/fml/loading/mixin/FMLClassProvider.java b/loader/src/main/java/net/neoforged/fml/loading/mixin/FMLClassProvider.java index 80a686866..d0082da25 100644 --- a/loader/src/main/java/net/neoforged/fml/loading/mixin/FMLClassProvider.java +++ b/loader/src/main/java/net/neoforged/fml/loading/mixin/FMLClassProvider.java @@ -16,7 +16,7 @@ */ class FMLClassProvider implements IClassProvider { private static final Logger LOGGER = LogManager.getLogger(); - + private final ClassLoader pluginClassLoader; FMLClassProvider(ClassLoader pluginClassLoader) { diff --git a/loader/src/main/java/net/neoforged/fml/loading/mixin/FMLMixinService.java b/loader/src/main/java/net/neoforged/fml/loading/mixin/FMLMixinService.java index b6f665696..83afe7ce4 100644 --- a/loader/src/main/java/net/neoforged/fml/loading/mixin/FMLMixinService.java +++ b/loader/src/main/java/net/neoforged/fml/loading/mixin/FMLMixinService.java @@ -100,7 +100,7 @@ public String getSideName() { public void setBytecodeProvider(@Nullable IClassBytecodeProvider bytecodeProvider) { this.bytecodeProvider = bytecodeProvider; } - + public void setClassProvider(@Nullable IClassProvider classProvider) { this.classProvider = classProvider; }