forked from McModLauncher/securejarhandler
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix ServiceLoader queries using ModuleClassLoader & add related tests (…
…McModLauncher#52) The fix is just to call the package-private `ModuleLayer.bindToLoader(ClassLoader)` method on the parent layers when creating the `ModuleClassLoader`. The rest of the PR is a complicated testing setup, to make sure that we can test with both CP-loaded and SJH-loaded source sets. Closes McModLauncher/modlauncher#100.
- Loading branch information
1 parent
529e8f1
commit d2a51e3
Showing
13 changed files
with
380 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
package cpw.mods.cl.test; | ||
|
||
import cpw.mods.cl.ModuleClassLoader; | ||
import org.junit.jupiter.api.Test; | ||
|
||
import java.net.spi.URLStreamHandlerProvider; | ||
import java.nio.file.spi.FileSystemProvider; | ||
import java.util.ServiceLoader; | ||
|
||
import static org.junit.jupiter.api.Assertions.assertFalse; | ||
import static org.junit.jupiter.api.Assertions.assertTrue; | ||
|
||
public class TestServiceLoader { | ||
/** | ||
* Tests that we can load services from modules that are part of the boot layer. | ||
* In principle this also tests that services correctly get loaded from parent module layers too. | ||
*/ | ||
@Test | ||
public void testLoadServiceFromBootLayer() throws Exception { | ||
TestjarUtil.withTestjar1Setup(cl -> { | ||
// We expect to find at least the unionfs provider | ||
ServiceLoader<FileSystemProvider> sl = TestjarUtil.loadTestjar1(cl, FileSystemProvider.class); | ||
boolean foundUnionFsProvider = sl.stream().map(ServiceLoader.Provider::get).anyMatch(p -> p.getScheme().equals("union")); | ||
|
||
assertTrue(foundUnionFsProvider, "Expected to be able to find the UFS provider"); | ||
}); | ||
} | ||
|
||
@Test | ||
public void testLoadServiceFromBootLayerNested() throws Exception { | ||
TestjarUtil.withTestjar2Setup(cl -> { | ||
// Try to find service from boot layer | ||
// We expect to find at least the unionfs provider | ||
ServiceLoader<FileSystemProvider> sl = TestjarUtil.loadTestjar2(cl, FileSystemProvider.class); | ||
boolean foundUnionFsProvider = sl.stream().map(ServiceLoader.Provider::get).anyMatch(p -> p.getScheme().equals("union")); | ||
|
||
assertTrue(foundUnionFsProvider, "Expected to be able to find the UFS provider"); | ||
|
||
// Try to find service from testjar1 layer | ||
var foundService = TestjarUtil.loadTestjar2(cl, URLStreamHandlerProvider.class) | ||
.stream() | ||
.anyMatch(p -> p.type().getName().startsWith("cpw.mods.cl.testjar1")); | ||
|
||
assertTrue(foundService, "Expected to be able to find the provider in testjar1 layer"); | ||
}); | ||
} | ||
|
||
/** | ||
* Tests that services that would normally be loaded from the classpath | ||
* do not get loaded by {@link ModuleClassLoader}. | ||
* In other words, test that our class loader isolation also works with services. | ||
*/ | ||
@Test | ||
public void testClassPathServiceDoesNotLeak() throws Exception { | ||
// Test that the DummyURLStreamHandlerProvider service provider can be loaded from the classpath | ||
var foundService = TestjarUtil.loadClasspath(TestServiceLoader.class.getClassLoader(), URLStreamHandlerProvider.class) | ||
.stream() | ||
.anyMatch(p -> p.type().getName().startsWith("cpw.mods.testjar_cp")); | ||
|
||
assertTrue(foundService, "Could not find service in classpath using application class loader!"); | ||
|
||
TestjarUtil.withTestjar1Setup(cl -> { | ||
// Test that the DummyURLStreamHandlerProvider service provider cannot be loaded | ||
// from the classpath via ModuleClassLoader | ||
var foundServiceMCL = TestjarUtil.loadTestjar1(cl, URLStreamHandlerProvider.class) | ||
.stream() | ||
.anyMatch(p -> p.type().getName().startsWith("cpw.mods.testjar_cp")); | ||
|
||
assertFalse(foundServiceMCL, "Could find service in classpath using application class loader!"); | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
package cpw.mods.cl.test; | ||
|
||
import cpw.mods.cl.JarModuleFinder; | ||
import cpw.mods.cl.ModuleClassLoader; | ||
import cpw.mods.jarhandling.SecureJar; | ||
|
||
import java.io.File; | ||
import java.lang.module.Configuration; | ||
import java.lang.module.ModuleFinder; | ||
import java.nio.file.Path; | ||
import java.nio.file.Paths; | ||
import java.util.List; | ||
import java.util.ServiceLoader; | ||
import java.util.stream.Stream; | ||
|
||
public class TestjarUtil { | ||
private record BuiltLayer(ModuleClassLoader cl, ModuleLayer layer) {} | ||
|
||
/** | ||
* Build a layer for a {@code testjarX} source set. | ||
*/ | ||
private static BuiltLayer buildTestjarLayer(int testjar, List<ModuleLayer> parentLayers) { | ||
var paths = Stream.of(System.getenv("sjh.testjar" + testjar).split(File.pathSeparator)) | ||
.map(Paths::get) | ||
.toArray(Path[]::new); | ||
var jar = SecureJar.from(paths); | ||
|
||
var roots = List.of(jar.name()); | ||
var jf = JarModuleFinder.of(jar); | ||
var conf = Configuration.resolveAndBind( | ||
jf, | ||
parentLayers.stream().map(ModuleLayer::configuration).toList(), | ||
ModuleFinder.of(), | ||
roots); | ||
var cl = new ModuleClassLoader("testjar2-layer", conf, parentLayers); | ||
var layer = ModuleLayer.defineModules(conf, parentLayers, m -> cl).layer(); | ||
return new BuiltLayer(cl, layer); | ||
} | ||
|
||
private static void withClassLoader(ClassLoader cl, TestCallback callback) throws Exception { | ||
// Replace context classloader during the callback | ||
var previousCl = Thread.currentThread().getContextClassLoader(); | ||
Thread.currentThread().setContextClassLoader(cl); | ||
|
||
try { | ||
callback.test(cl); | ||
} finally { | ||
Thread.currentThread().setContextClassLoader(previousCl); | ||
} | ||
} | ||
|
||
/** | ||
* Load the {@code testjar1} source set as new module into a new layer, | ||
* and run the callback with the new layer's classloader. | ||
*/ | ||
public static void withTestjar1Setup(TestCallback callback) throws Exception { | ||
var built = buildTestjarLayer(1, List.of(ModuleLayer.boot())); | ||
|
||
withClassLoader(built.cl, callback); | ||
} | ||
|
||
/** | ||
* Load the {@code testjar2} source set as new module into a new layer, | ||
* whose parent is a layer loaded from the {@code testjar1} source set. | ||
*/ | ||
public static void withTestjar2Setup(TestCallback callback) throws Exception { | ||
var built1 = buildTestjarLayer(1, List.of(ModuleLayer.boot())); | ||
var built2 = buildTestjarLayer(2, List.of(built1.layer)); | ||
|
||
withClassLoader(built2.cl, callback); | ||
} | ||
|
||
@FunctionalInterface | ||
public interface TestCallback { | ||
void test(ClassLoader cl) throws Exception; | ||
} | ||
|
||
/** | ||
* Instantiates a {@link ServiceLoader} within the testjar1 module. | ||
*/ | ||
public static <S> ServiceLoader<S> loadTestjar1(ClassLoader cl, Class<S> clazz) throws Exception { | ||
// Use the `load` method from the testjar sourceset. | ||
var testClass = cl.loadClass("cpw.mods.cl.testjar1.ServiceLoaderTest"); | ||
var loadMethod = testClass.getMethod("load", Class.class); | ||
//noinspection unchecked | ||
return (ServiceLoader<S>) loadMethod.invoke(null, clazz); | ||
} | ||
|
||
/** | ||
* Instantiates a {@link ServiceLoader} within the testjar2 module. | ||
*/ | ||
public static <S> ServiceLoader<S> loadTestjar2(ClassLoader cl, Class<S> clazz) throws Exception { | ||
// Use the `load` method from the testjar sourceset. | ||
var testClass = cl.loadClass("cpw.mods.cl.testjar2.ServiceLoaderTest"); | ||
var loadMethod = testClass.getMethod("load", Class.class); | ||
//noinspection unchecked | ||
return (ServiceLoader<S>) loadMethod.invoke(null, clazz); | ||
} | ||
|
||
/** | ||
* Instantiates a {@link ServiceLoader} within the classpath source set. | ||
*/ | ||
public static <S> ServiceLoader<S> loadClasspath(ClassLoader cl, Class<S> clazz) throws Exception { | ||
// Use the `load` method from the testjar sourceset. | ||
var testClass = cl.loadClass("cpw.mods.testjar_cp.ServiceLoaderTest"); | ||
var loadMethod = testClass.getMethod("load", Class.class); | ||
//noinspection unchecked | ||
return (ServiceLoader<S>) loadMethod.invoke(null, clazz); | ||
} | ||
} |
14 changes: 14 additions & 0 deletions
14
src/testjar1/java/cpw/mods/cl/testjar1/DummyURLStreamHandlerProvider.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package cpw.mods.cl.testjar1; | ||
|
||
import java.net.URLStreamHandler; | ||
import java.net.spi.URLStreamHandlerProvider; | ||
|
||
/** | ||
* Referenced by {@code TestServiceLoader}. | ||
*/ | ||
public class DummyURLStreamHandlerProvider extends URLStreamHandlerProvider { | ||
@Override | ||
public URLStreamHandler createURLStreamHandler(String protocol) { | ||
return null; | ||
} | ||
} |
Oops, something went wrong.