diff --git a/substratevm/mx.substratevm/mx_substratevm.py b/substratevm/mx.substratevm/mx_substratevm.py index a2dcfc2546c9..f7e473a9c7df 100644 --- a/substratevm/mx.substratevm/mx_substratevm.py +++ b/substratevm/mx.substratevm/mx_substratevm.py @@ -1097,6 +1097,7 @@ def hellomodule(args): with native_image_context(hosted_assertions=False, config=config) as native_image: module_path_sep = ';' if mx.is_windows() else ':' moduletest_run_args = [ + '-ea', '--add-exports=moduletests.hello.lib/hello.privateLib=moduletests.hello.app', '--add-opens=moduletests.hello.lib/hello.privateLib2=moduletests.hello.app', '-p', module_path_sep.join(module_path), '-m', 'moduletests.hello.app' diff --git a/substratevm/src/com.oracle.svm.core.jdk11/src/com/oracle/svm/core/jdk11/Target_java_lang_Module_JDK11OrLater.java b/substratevm/src/com.oracle.svm.core.jdk11/src/com/oracle/svm/core/jdk11/Target_java_lang_Module_JDK11OrLater.java index 5c9ba0cadd97..d4f19b46db79 100644 --- a/substratevm/src/com.oracle.svm.core.jdk11/src/com/oracle/svm/core/jdk11/Target_java_lang_Module_JDK11OrLater.java +++ b/substratevm/src/com.oracle.svm.core.jdk11/src/com/oracle/svm/core/jdk11/Target_java_lang_Module_JDK11OrLater.java @@ -24,6 +24,12 @@ */ package com.oracle.svm.core.jdk11; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.util.Arrays; +import java.util.Map; +import java.util.Objects; + import com.oracle.svm.core.annotate.Alias; import com.oracle.svm.core.annotate.RecomputeFieldValue; import com.oracle.svm.core.annotate.Substitute; @@ -34,23 +40,10 @@ import com.oracle.svm.core.jdk.Resources; import com.oracle.svm.core.jdk.resources.ResourceStorageEntry; -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.util.Arrays; -import java.util.Map; -import java.util.Objects; - @SuppressWarnings("unused") @TargetClass(value = java.lang.Module.class, onlyWith = JDK11OrLater.class) public final class Target_java_lang_Module_JDK11OrLater { - @SuppressWarnings({"unused", "static-method"}) - @Substitute - public boolean isReflectivelyExportedOrOpen(String pn, Module other, boolean open) { - // This workaround should be removed once GR-34444 is fixed. - return true; - } - @SuppressWarnings("static-method") @Substitute public InputStream getResourceAsStream(String name) { @@ -97,10 +90,13 @@ private static void addExportsToAllUnnamed0(Module from, String pn) { @TargetClass(className = "java.lang.Module", innerClass = "ReflectionData", onlyWith = JDK11OrLater.class) // private static final class Target_java_lang_Module_ReflectionData { + @Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.NewInstance, declClassName = "java.lang.WeakPairMap") // static Target_java_lang_WeakPairMap reads; + @Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.NewInstance, declClassName = "java.lang.WeakPairMap") // static Target_java_lang_WeakPairMap> exports; + @Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.NewInstance, declClassName = "java.lang.WeakPairMap") // static Target_java_lang_WeakPairMap, Boolean> uses; } diff --git a/substratevm/src/com.oracle.svm.hosted.jdk11/src/com/oracle/svm/hosted/ModuleLayerFeature.java b/substratevm/src/com.oracle.svm.hosted.jdk11/src/com/oracle/svm/hosted/ModuleLayerFeature.java index 3f0c27bec9d0..3c5130849d29 100644 --- a/substratevm/src/com.oracle.svm.hosted.jdk11/src/com/oracle/svm/hosted/ModuleLayerFeature.java +++ b/substratevm/src/com.oracle.svm.hosted.jdk11/src/com/oracle/svm/hosted/ModuleLayerFeature.java @@ -24,18 +24,6 @@ */ package com.oracle.svm.hosted; -import com.oracle.graal.pointsto.meta.AnalysisUniverse; -import com.oracle.svm.core.annotate.AutomaticFeature; -import com.oracle.svm.core.jdk11.BootModuleLayerSupport; -import com.oracle.svm.core.jdk.JDK11OrLater; -import com.oracle.svm.core.util.VMError; -import com.oracle.svm.util.ModuleSupport; -import com.oracle.svm.util.ReflectionUtil; -import org.graalvm.nativeimage.ImageSingletons; -import org.graalvm.nativeimage.Platform; -import org.graalvm.nativeimage.Platforms; -import org.graalvm.nativeimage.hosted.Feature; - import java.lang.module.Configuration; import java.lang.module.FindException; import java.lang.module.ModuleDescriptor; @@ -61,17 +49,32 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import com.oracle.graal.pointsto.meta.AnalysisType; +import com.oracle.graal.pointsto.meta.AnalysisUniverse; +import com.oracle.svm.core.annotate.AutomaticFeature; +import com.oracle.svm.core.hub.DynamicHub; +import com.oracle.svm.core.jdk11.BootModuleLayerSupport; +import com.oracle.svm.core.jdk.JDK11OrLater; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.util.ModuleSupport; +import com.oracle.svm.util.ReflectionUtil; +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.hosted.Feature; + /** * This feature: *
    *
  • synthesizes the runtime boot module layer
  • - *
  • ensures that fields/methods from the {@link ClassLoader} class are reachable in order to make - * native methods of the {@link Module} class work
  • + *
  • replicates build-time module relations at runtime
  • + *
  • patches dynamic hubs of every class with new module instances
  • *
*

* This feature synthesizes the runtime boot module layer by using type reachability information. If * a type is reachable, its module is also reachable and therefore should be included in the runtime - * boot module layer. + * boot module layer. If those modules require any additional modules, they will also be marked as + * reachable. *

*

* The configuration for the runtime boot module layer is resolved using the module reachability @@ -82,7 +85,7 @@ * {@link ModuleLayer#defineModulesWithOneLoader(Configuration, ClassLoader)}, because as a side * effect this will create a new class loader. Instead, we use a private constructor to construct * the {@link ModuleLayer} instance, which we then patch using the module reachability data provided - * to us by the analysis. + * to us by the analysis. This should be updated if JDK-8277013 gets resolved. *

*

* Because the result of this feature is dependant on the analysis results, and because this feature @@ -97,7 +100,7 @@ public final class ModuleLayerFeature implements Feature { private Constructor moduleLayerConstructor; private Field moduleLayerNameToModuleField; private Field moduleLayerParentsField; - private NameToModuleSynthesizer nameToModuleSynthesizer; + private ModuleLayerFeatureUtils moduleLayerFeatureUtils; @Override public boolean isInConfiguration(IsInConfigurationAccess access) { @@ -110,7 +113,7 @@ public void afterRegistration(AfterRegistrationAccess access) { moduleLayerConstructor = ReflectionUtil.lookupConstructor(ModuleLayer.class, Configuration.class, List.class, Function.class); moduleLayerNameToModuleField = ReflectionUtil.lookupField(ModuleLayer.class, "nameToModule"); moduleLayerParentsField = ReflectionUtil.lookupField(ModuleLayer.class, "parents"); - nameToModuleSynthesizer = new NameToModuleSynthesizer(); + moduleLayerFeatureUtils = new ModuleLayerFeatureUtils(); } @Override @@ -120,7 +123,7 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { .stream() .map(Module::getName) .collect(Collectors.toSet()); - ModuleLayer runtimeBootLayer = synthesizeRuntimeBootLayer(accessImpl.imageClassLoader, baseModules); + ModuleLayer runtimeBootLayer = synthesizeRuntimeBootLayer(accessImpl.imageClassLoader, baseModules, Set.of()); BootModuleLayerSupport.instance().setBootLayer(runtimeBootLayer); } @@ -135,14 +138,47 @@ public void afterAnalysis(AfterAnalysisAccess access) { .map(t -> t.getJavaClass().getModule()) .distinct(); - Set allReachableModules = analysisReachableModules + Set analysisReachableNamedModules = analysisReachableModules .filter(Module::isNamed) - .filter(m -> !m.getDescriptor().modifiers().contains(ModuleDescriptor.Modifier.SYNTHETIC)) + .collect(Collectors.toSet()); + + Set analysisReachableSyntheticModules = analysisReachableNamedModules + .stream() + .filter(ModuleLayerFeatureUtils::isModuleSynthetic) + .collect(Collectors.toSet()); + + Set allReachableModules = analysisReachableNamedModules + .stream() .flatMap(ModuleLayerFeature::extractRequiredModuleNames) .collect(Collectors.toSet()); - ModuleLayer runtimeBootLayer = synthesizeRuntimeBootLayer(accessImpl.imageClassLoader, allReachableModules); + ModuleLayer runtimeBootLayer = synthesizeRuntimeBootLayer(accessImpl.imageClassLoader, allReachableModules, analysisReachableSyntheticModules); BootModuleLayerSupport.instance().setBootLayer(runtimeBootLayer); + + replicateVisibilityModifications(runtimeBootLayer, accessImpl.imageClassLoader, analysisReachableNamedModules); + patchDynamicHubs(universe.getTypes(), accessImpl.bb.getHostVM(), runtimeBootLayer); + } + + /* + * Updates module fields of all DynamicHubs with appropriate synthesized module instances + */ + private static void patchDynamicHubs(List types, SVMHost host, ModuleLayer runtimeBootLayer) { + for (AnalysisType type : types) { + if (!type.isReachable() || type.isArray()) { + continue; + } + Class clazz = type.getJavaClass(); + if (!clazz.getModule().isNamed()) { + continue; + } + DynamicHub hub = host.dynamicHub(type); + String moduleName = clazz.getModule().getName(); + Optional module = runtimeBootLayer.findModule(moduleName); + if (module.isEmpty()) { + throw VMError.shouldNotReachHere("Runtime boot module layer failed to include reachable module: " + moduleName); + } + hub.setModule(module.get()); + } } /* @@ -153,11 +189,15 @@ private static Stream extractRequiredModuleNames(Module m) { return Stream.concat(Stream.of(m.getName()), requiredModules); } - private ModuleLayer synthesizeRuntimeBootLayer(ImageClassLoader cl, Set reachableModules) { + private ModuleLayer synthesizeRuntimeBootLayer(ImageClassLoader cl, Set reachableModules, Set syntheticModules) { Configuration cf = synthesizeRuntimeBootLayerConfiguration(cl.modulepath(), reachableModules); try { ModuleLayer runtimeBootLayer = moduleLayerConstructor.newInstance(cf, List.of(), null); - Map nameToModule = nameToModuleSynthesizer.synthesizeNameToModule(runtimeBootLayer, cl.getClassLoader()); + Map nameToModule = moduleLayerFeatureUtils.synthesizeNameToModule(runtimeBootLayer, cl.getClassLoader()); + for (Module syntheticModule : syntheticModules) { + nameToModule.putIfAbsent(syntheticModule.getName(), syntheticModule); + moduleLayerFeatureUtils.patchModuleLayerField(syntheticModule, runtimeBootLayer); + } patchRuntimeBootLayer(runtimeBootLayer, nameToModule); return runtimeBootLayer; } catch (InstantiationException | IllegalAccessException | InvocationTargetException ex) { @@ -165,6 +205,89 @@ private ModuleLayer synthesizeRuntimeBootLayer(ImageClassLoader cl, Set } } + private void replicateVisibilityModifications(ModuleLayer runtimeBootLayer, ImageClassLoader cl, Set analysisReachableModules) { + List applicationModules = findApplicationModules(runtimeBootLayer, cl.applicationModulePath()); + + Map moduleLookupMap = analysisReachableModules + .stream() + .collect(Collectors.toMap(Module::getName, m -> new HostedRuntimeModulePair(m, runtimeBootLayer))); + moduleLookupMap.putIfAbsent("ALL-UNNAMED", HostedRuntimeModulePair.withReplicatedHostedModule(moduleLayerFeatureUtils.allUnnamedModule)); + moduleLookupMap.putIfAbsent("EVERYONE", HostedRuntimeModulePair.withReplicatedHostedModule(moduleLayerFeatureUtils.everyoneModule)); + + Module builderModule = ModuleLayerFeature.class.getModule(); + assert builderModule != null; + + try { + for (Map.Entry e1 : moduleLookupMap.entrySet()) { + Module hostedFrom = e1.getValue().hostedModule; + if (!hostedFrom.isNamed()) { + continue; + } + Module runtimeFrom = e1.getValue().runtimeModule; + for (Map.Entry e2 : moduleLookupMap.entrySet()) { + Module hostedTo = e2.getValue().hostedModule; + if (hostedTo == hostedFrom) { + continue; + } + Module runtimeTo = e2.getValue().runtimeModule; + if (ModuleLayerFeatureUtils.isModuleSynthetic(hostedFrom) || hostedFrom.canRead(hostedTo)) { + moduleLayerFeatureUtils.addReads(runtimeFrom, runtimeTo); + if (hostedFrom == builderModule) { + for (Module appModule : applicationModules) { + moduleLayerFeatureUtils.addReads(appModule, runtimeTo); + } + } + } + for (String pn : runtimeFrom.getPackages()) { + if (ModuleLayerFeatureUtils.isModuleSynthetic(hostedFrom) || hostedFrom.isOpen(pn, hostedTo)) { + moduleLayerFeatureUtils.addOpens(runtimeFrom, pn, runtimeTo); + if (hostedTo == builderModule) { + for (Module appModule : applicationModules) { + moduleLayerFeatureUtils.addOpens(runtimeFrom, pn, appModule); + } + } + } + if (ModuleLayerFeatureUtils.isModuleSynthetic(hostedFrom) || hostedFrom.isExported(pn, hostedTo)) { + moduleLayerFeatureUtils.addExports(runtimeFrom, pn, runtimeTo); + if (hostedTo == builderModule) { + for (Module appModule : applicationModules) { + moduleLayerFeatureUtils.addExports(runtimeFrom, pn, appModule); + } + } + } + } + } + } + } catch (IllegalAccessException ex) { + throw VMError.shouldNotReachHere("Failed to transfer hosted module relations to the runtime boot module layer.", ex); + } + } + + private static List findApplicationModules(ModuleLayer runtimeBootLayer, List applicationModulePath) { + List applicationModules = new ArrayList<>(); + List applicationModuleNames; + try { + ModuleFinder applicationModuleFinder = ModuleFinder.of(applicationModulePath.toArray(Path[]::new)); + applicationModuleNames = applicationModuleFinder.findAll() + .stream() + .map(m -> m.descriptor().name()) + .collect(Collectors.toList()); + } catch (FindException | ResolutionException | SecurityException ex) { + throw VMError.shouldNotReachHere("Failed to locate application modules.", ex); + } + + for (String moduleName : applicationModuleNames) { + Optional module = runtimeBootLayer.findModule(moduleName); + if (module.isEmpty()) { + // Module is not reachable + continue; + } + applicationModules.add(module.get()); + } + + return applicationModules; + } + private static Configuration synthesizeRuntimeBootLayerConfiguration(List mp, Set reachableModules) { ModuleFinder beforeFinder = new BootModuleLayerModuleFinder(); ModuleFinder afterFinder = ModuleFinder.of(mp.toArray(Path[]::new)); @@ -190,6 +313,12 @@ private void patchRuntimeBootLayer(ModuleLayer runtimeBootLayer, Map hostedModule = ModuleLayer.boot().findModule(m.getName()); + if (hostedModule.isPresent() && hostedModule.get().getClassLoader() == null) { + moduleLayerFeatureUtils.patchModuleLoaderField(m); + } + } } catch (IllegalAccessException ex) { throw VMError.shouldNotReachHere("Failed to patch the runtime boot module layer.", ex); } @@ -219,17 +348,43 @@ public Set findAll() { } } - private static final class NameToModuleSynthesizer { + private static final class HostedRuntimeModulePair { + static HostedRuntimeModulePair withReplicatedHostedModule(Module module) { + return new HostedRuntimeModulePair(module, module); + } + + final Module hostedModule; + final Module runtimeModule; + + HostedRuntimeModulePair(Module hostedModule, ModuleLayer runtimeBootLayer) { + this.hostedModule = hostedModule; + this.runtimeModule = runtimeBootLayer.findModule(hostedModule.getName()).orElseThrow(() -> errorSupplier(hostedModule)); + } + + private HostedRuntimeModulePair(Module hosted, Module runtime) { + hostedModule = hosted; + runtimeModule = runtime; + } + + static RuntimeException errorSupplier(Module m) { + return VMError.shouldNotReachHere("Failed to find module " + m.getName() + " in the runtime boot module layer"); + } + } + + private static final class ModuleLayerFeatureUtils { + private final Module allUnnamedModule; + private final Set allUnnamedModuleSet; private final Module everyoneModule; private final Set everyoneSet; private final Constructor moduleConstructor; private final Field moduleLayerField; + private final Field moduleLoaderField; private final Field moduleReadsField; private final Field moduleOpenPackagesField; private final Field moduleExportedPackagesField; private final Method moduleFindModuleMethod; - NameToModuleSynthesizer() { + ModuleLayerFeatureUtils() { Method classGetDeclaredMethods0Method = ReflectionUtil.lookupMethod(Class.class, "getDeclaredFields0", boolean.class); try { ModuleSupport.openModuleByClass(Module.class, ModuleLayerFeature.class); @@ -239,24 +394,37 @@ private static final class NameToModuleSynthesizer { everyoneModuleField.setAccessible(true); everyoneModule = (Module) everyoneModuleField.get(null); + Field allUnnamedModuleField = findFieldByName(moduleClassFields, "ALL_UNNAMED_MODULE"); + allUnnamedModuleField.setAccessible(true); + allUnnamedModule = (Module) allUnnamedModuleField.get(null); + moduleLayerField = findFieldByName(moduleClassFields, "layer"); + moduleLoaderField = findFieldByName(moduleClassFields, "loader"); moduleReadsField = findFieldByName(moduleClassFields, "reads"); moduleOpenPackagesField = findFieldByName(moduleClassFields, "openPackages"); moduleExportedPackagesField = findFieldByName(moduleClassFields, "exportedPackages"); moduleLayerField.setAccessible(true); + moduleLoaderField.setAccessible(true); moduleReadsField.setAccessible(true); moduleOpenPackagesField.setAccessible(true); moduleExportedPackagesField.setAccessible(true); } catch (ReflectiveOperationException | NoSuchElementException ex) { - throw VMError.shouldNotReachHere("Failed to find the value of EVERYONE_MODULE field of Module class.", ex); + throw VMError.shouldNotReachHere("Failed to retrieve fields of the Module class.", ex); } - everyoneSet = Set.of(everyoneModule); + allUnnamedModuleSet = new HashSet<>(); + allUnnamedModuleSet.add(allUnnamedModule); + everyoneSet = new HashSet<>(); + everyoneSet.add(everyoneModule); moduleConstructor = ReflectionUtil.lookupConstructor(Module.class, ClassLoader.class, ModuleDescriptor.class); moduleFindModuleMethod = ReflectionUtil.lookupMethod(Module.class, "findModule", String.class, Map.class, Map.class, List.class); } private static Field findFieldByName(Field[] fields, String name) { - return Arrays.stream(fields).filter(f -> f.getName().equals(name)).findAny().get(); + return Arrays.stream(fields).filter(f -> f.getName().equals(name)).findAny().orElseThrow(VMError::shouldNotReachHere); + } + + static boolean isModuleSynthetic(Module m) { + return m.getDescriptor().modifiers().contains(ModuleDescriptor.Modifier.SYNTHETIC); } /** @@ -282,7 +450,7 @@ Map synthesizeNameToModule(ModuleLayer runtimeBootLayer, ClassLo ModuleDescriptor descriptor = mref.descriptor(); String name = descriptor.name(); Module m = moduleConstructor.newInstance(cl, descriptor); - moduleLayerField.set(m, runtimeBootLayer); + patchModuleLayerField(m, runtimeBootLayer); nameToModule.put(name, m); } @@ -303,6 +471,10 @@ Map synthesizeNameToModule(ModuleLayer runtimeBootLayer, ClassLo Module m2 = nameToModule.get(other.name()); reads.add(m2); } + + if (descriptor.isAutomatic()) { + reads.add(allUnnamedModule); + } moduleReadsField.set(m, reads); if (!descriptor.isOpen() && !descriptor.isAutomatic()) { @@ -380,5 +552,75 @@ Map synthesizeNameToModule(ModuleLayer runtimeBootLayer, ClassLo return nameToModule; } + + @SuppressWarnings("unchecked") + void addReads(Module module, Module other) throws IllegalAccessException { + Set reads = (Set) moduleReadsField.get(module); + if (reads == null) { + reads = new HashSet<>(); + moduleReadsField.set(module, reads); + } + reads.add(other == null ? allUnnamedModule : other); + } + + @SuppressWarnings("unchecked") + void addExports(Module module, String pn, Module other) throws IllegalAccessException { + if (other != null && module.isExported(pn, other)) { + return; + } + + Map> exports = (Map>) moduleExportedPackagesField.get(module); + if (exports == null) { + exports = new HashMap<>(); + moduleExportedPackagesField.set(module, exports); + } + + Set prev; + if (other == null) { + prev = exports.putIfAbsent(pn, allUnnamedModuleSet); + } else { + HashSet targets = new HashSet<>(); + targets.add(other); + prev = exports.putIfAbsent(pn, targets); + } + + if (prev != null) { + prev.add(other == null ? allUnnamedModule : other); + } + } + + @SuppressWarnings("unchecked") + void addOpens(Module module, String pn, Module other) throws IllegalAccessException { + if (other != null && module.isOpen(pn, other)) { + return; + } + + Map> opens = (Map>) moduleOpenPackagesField.get(module); + if (opens == null) { + opens = new HashMap<>(); + moduleOpenPackagesField.set(module, opens); + } + + Set prev; + if (other == null) { + prev = opens.putIfAbsent(pn, allUnnamedModuleSet); + } else { + HashSet targets = new HashSet<>(); + targets.add(other); + prev = opens.putIfAbsent(pn, targets); + } + + if (prev != null) { + prev.add(other == null ? allUnnamedModule : other); + } + } + + void patchModuleLayerField(Module module, ModuleLayer runtimeBootLayer) throws IllegalAccessException { + moduleLayerField.set(module, runtimeBootLayer); + } + + void patchModuleLoaderField(Module module) throws IllegalAccessException { + moduleLoaderField.set(module, null); + } } } diff --git a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrFeature.java b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrFeature.java index 7cae26757538..a3a49d7852f9 100644 --- a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrFeature.java +++ b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrFeature.java @@ -118,6 +118,7 @@ public List> getRequiredFeatures() { public void afterRegistration(AfterRegistrationAccess access) { ModuleSupport.exportAndOpenAllPackagesToUnnamed("jdk.jfr", false); ModuleSupport.exportAndOpenAllPackagesToUnnamed("java.base", false); + ModuleSupport.exportAndOpenPackageToClass("jdk.jfr", "jdk.jfr.events", false, JfrFeature.class); ModuleSupport.exportAndOpenPackageToClass("jdk.internal.vm.ci", "jdk.vm.ci.hotspot", false, JfrEventSubstitution.class); // Initialize some parts of JFR/JFC at image build time. diff --git a/substratevm/src/native-image-module-tests/hello.app/src/main/java/hello/Main.java b/substratevm/src/native-image-module-tests/hello.app/src/main/java/hello/Main.java index a642af8e8291..387386b55820 100644 --- a/substratevm/src/native-image-module-tests/hello.app/src/main/java/hello/Main.java +++ b/substratevm/src/native-image-module-tests/hello.app/src/main/java/hello/Main.java @@ -34,6 +34,8 @@ public class Main { public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { + failIfAssertionsAreDisabled(); + Module helloAppModule = Main.class.getModule(); Module helloLibModule = Greeter.class.getModule(); testModuleObjects(helloAppModule, helloLibModule); @@ -49,10 +51,17 @@ public static void main(String[] args) throws NoSuchMethodException, InvocationT greetMethod.setAccessible(true); greetMethod.invoke(null); - System.out.println("Now testing boot module layer"); testBootLayer(helloAppModule, helloLibModule); } + private static void failIfAssertionsAreDisabled() { + boolean enabled = false; + assert enabled = true; + if (!enabled) { + throw new AssertionError("This example requires that assertions are enabled (-ea)"); + } + } + private static void testModuleObjects(Module helloAppModule, Module helloLibModule) { assert helloAppModule.getName().equals("moduletests.hello.app"); assert helloAppModule.isExported(Main.class.getPackageName()); @@ -73,6 +82,8 @@ private static void testModuleObjects(Module helloAppModule, Module helloLibModu @SuppressWarnings("OptionalGetWithoutIsPresent") private static void testBootLayer(Module helloAppModule, Module helloLibModule) { + System.out.println("Now testing boot module layer"); + ModuleLayer bootLayer = ModuleLayer.boot(); System.out.println("Now testing boot module layer configuration");