Native provider loading

- Native provider classes load from the provider jars before the plugin class loader
- Another plugin bundling VolmLib natives no longer supplies Iris native capabilities on Spigot
- Tests cover provider jar classes taking precedence over the plugin loader
This commit is contained in:
Brian Neumann-Fopiano committed 2026-10-29 01:49:27 -04:00
1 parent 21ab5baf27
commit c700138b66
2 files changed
+61 -2

No files matched your search

@@ -65,8 +65,7 @@ public final class NativeRuntimeLibraries {
public URLClassLoader openProviderLoader(Path downloads) { public URLClassLoader openProviderLoader(Path downloads) {
try { try {
List<URL> urls = providerUrls(downloads, NativeRuntimeLibraries.class.getClassLoader()); List<URL> urls = providerUrls(downloads, NativeRuntimeLibraries.class.getClassLoader());
URLClassLoader loader = new URLClassLoader("Iris native providers", urls.toArray(URL[]::new), URLClassLoader loader = providerLoader(urls.toArray(URL[]::new), NativeAdapters.class.getClassLoader());
NativeAdapters.class.getClassLoader());
NativeAdapters.registerProviderLoader(loader); NativeAdapters.registerProviderLoader(loader);
return loader; return loader;
} catch (IOException exception) { } catch (IOException exception) {
@@ -74,6 +73,10 @@ public final class NativeRuntimeLibraries {
} }
} }
static URLClassLoader providerLoader(URL[] urls, ClassLoader parent) {
return new ProviderClassLoader(urls, parent);
}
List<URL> providerUrls(Path downloads, ClassLoader resources) throws IOException { List<URL> providerUrls(Path downloads, ClassLoader resources) throws IOException {
List<URL> urls = new ArrayList<>(providers.size()); List<URL> urls = new ArrayList<>(providers.size());
for (Provider provider : providers) { for (Provider provider : providers) {
@@ -123,4 +126,28 @@ public final class NativeRuntimeLibraries {
private record Provider(String module, String version, String sha256) { private record Provider(String module, String version, String sha256) {
} }
private static final class ProviderClassLoader extends URLClassLoader {
private ProviderClassLoader(URL[] urls, ClassLoader parent) {
super("Iris native providers", urls, parent);
}
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
Class<?> loaded = findLoadedClass(name);
if (loaded == null) {
try {
loaded = findClass(name);
} catch (ClassNotFoundException absent) {
return super.loadClass(name, resolve);
}
}
if (resolve) {
resolveClass(loaded);
}
return loaded;
}
}
}
} }
@@ -3,6 +3,8 @@ package art.arcane.iris.platform.bootstrap;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.TemporaryFolder; import org.junit.rules.TemporaryFolder;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
@@ -25,6 +27,7 @@ import java.util.stream.Stream;
import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
@@ -41,6 +44,20 @@ public class NativeRuntimeLibrariesTest {
assertThrows(IllegalStateException.class, () -> NativeRuntimeLibraries.from(fixture.manifest(), "26.2.0")); assertThrows(IllegalStateException.class, () -> NativeRuntimeLibraries.from(fixture.manifest(), "26.2.0"));
} }
@Test
public void providerClassesLoadFromTheProviderJarBeforeThePluginLoader() throws Exception {
String implementation = "art/arcane/volmlib/nativelib/v26_2_R1/Probe";
String api = "art/arcane/volmlib/nativelib/Api";
Path plugin = classJar("plugin.jar", implementation, api);
Path provider = classJar("provider.jar", implementation);
try (URLClassLoader parent = new URLClassLoader(new URL[]{plugin.toUri().toURL()}, null);
URLClassLoader loader = NativeRuntimeLibraries.providerLoader(new URL[]{provider.toUri().toURL()}, parent)) {
assertSame(loader, loader.loadClass(implementation.replace('/', '.')).getClassLoader());
assertSame(parent, loader.loadClass(api.replace('/', '.')).getClassLoader());
assertSame(String.class, loader.loadClass("java.lang.String"));
}
}
@Test @Test
public void rejectsMinecraftVersionsWithoutProvider() throws Exception { public void rejectsMinecraftVersionsWithoutProvider() throws Exception {
EmbeddedFixture fixture = embeddedFixture(); EmbeddedFixture fixture = embeddedFixture();
@@ -156,6 +173,21 @@ public class NativeRuntimeLibrariesTest {
return new EmbeddedFixture(properties, resources); return new EmbeddedFixture(properties, resources);
} }
private Path classJar(String name, String... classes) throws IOException {
Path jar = temporary.newFolder().toPath().resolve(name);
try (JarOutputStream output = new JarOutputStream(Files.newOutputStream(jar))) {
for (String type : classes) {
ClassWriter writer = new ClassWriter(0);
writer.visit(Opcodes.V17, Opcodes.ACC_PUBLIC, type, null, "java/lang/Object", null);
writer.visitEnd();
output.putNextEntry(new JarEntry(type + ".class"));
output.write(writer.toByteArray());
output.closeEntry();
}
}
return jar;
}
private static void assertNoFailedExtract(Path downloads) throws IOException { private static void assertNoFailedExtract(Path downloads) throws IOException {
try (Stream<Path> files = Files.walk(downloads)) { try (Stream<Path> files = Files.walk(downloads)) {
assertFalse(files.anyMatch(path -> Files.isRegularFile(path) assertFalse(files.anyMatch(path -> Files.isRegularFile(path)