From 5664813622e4a5b43c04f10594795398084a1829 Mon Sep 17 00:00:00 2001 From: ivan-ristovic Date: Mon, 26 Feb 2024 16:57:55 +0100 Subject: [PATCH] Restructure and update module substitutions --- .../{ModuleUtil.java => ModuleNative.java} | 266 ++++++++++-------- .../svm/core/jdk/RuntimeModuleSupport.java | 12 +- .../svm/core/jdk/Target_java_lang_Module.java | 37 +-- .../jdk/Target_java_lang_NamedPackage.java | 33 --- .../thread/Target_java_lang_ScopedValue.java | 4 +- 5 files changed, 180 insertions(+), 172 deletions(-) rename substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/{ModuleUtil.java => ModuleNative.java} (57%) delete mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_NamedPackage.java diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/ModuleUtil.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/ModuleNative.java similarity index 57% rename from substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/ModuleUtil.java rename to substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/ModuleNative.java index c5db6979a7cb..ae3b2c8b7485 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/ModuleUtil.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/ModuleNative.java @@ -30,21 +30,150 @@ import java.util.Map; import java.util.Objects; import java.util.Set; -import java.util.stream.Collectors; import javax.lang.model.SourceVersion; import com.oracle.svm.core.SubstrateUtil; -public final class ModuleUtil { - private ModuleUtil() { +public final class ModuleNative { + private ModuleNative() { } + /** + * Re-implementations of native methods from {@code src/hotspot/share/classfile/modules.cpp}. + * See {@link Target_java_lang_Module} for more information on module system native + * substitutions. + */ + + /** + * {@code Modules::define_module}. + */ + public static void defineModule(Module module, boolean isOpen, Object[] pns) { + if (Objects.isNull(module)) { + throw new NullPointerException("Null module object"); + } + + if (Objects.isNull(module.getName())) { + throw new IllegalArgumentException("Module name cannot be null"); + } + + if (module.getName().equals("java.base")) { + if (isOpen) { + // Checkstyle: stop + throw new AssertionError("java.base module cannot be open"); + // Checkstyle: resume + } + + for (Object pn : pns) { + checkPackageNameForModule(pn, "java.base"); + } + + if (module.getClassLoader() != null) { + throw new IllegalArgumentException("Class loader must be the boot class loader"); + } + + synchronized (moduleLock) { + boolean duplicateJavaBase = bootLayerContainsModule("java.base"); + if (duplicateJavaBase) { + throw new InternalError("Module java.base is already defined"); + } + } + + return; + } + + ClassLoader loader = module.getClassLoader(); + if (Objects.nonNull(loader) && loader.getClass().getName().equals("jdk.internal.reflect.DelegatingClassLoader")) { + throw new IllegalArgumentException("Class loader is an invalid delegating class loader"); + } + + boolean javaPkgDisallowed = !Objects.isNull(loader) && !Objects.equals(loader, ClassLoader.getPlatformClassLoader()); + for (Object pn : pns) { + checkPackageNameForModule(pn, module.getName()); + if (javaPkgDisallowed && isPackageNameForbidden(pn.toString())) { + throw new IllegalArgumentException("Class loader (instance of): " + loader.getClass().getName() + + " tried to define prohibited package name: " + pn); + } + } + + String definedPackage = null; + boolean moduleAlreadyDefined; + synchronized (moduleLock) { + moduleAlreadyDefined = isModuleDefinedToLoader(loader, module.getName()); + if (!moduleAlreadyDefined) { + List definedPackages = getPackagesDefinedToLoader(loader); + for (Object pn : pns) { + String pnString = pn.toString(); + if (definedPackages.contains(pnString)) { + definedPackage = pnString; + break; + } + } + } + } + + if (moduleAlreadyDefined) { + throw new IllegalStateException("Module " + module.getName() + " is already defined"); + } else if (Objects.nonNull(definedPackage)) { + Module moduleContainingDefinedPackage = SubstrateUtil.cast(getModuleContainingPackage(loader, definedPackage), Module.class); + if (moduleContainingDefinedPackage.isNamed()) { + throw new IllegalStateException("Package " + definedPackage + " is already in another module, " + moduleContainingDefinedPackage.getName() + ", defined to the class loader"); + } else { + throw new IllegalStateException("Package " + definedPackage + " is already in the unnamed module defined to the class loader"); + } + } + + synchronized (moduleLock) { + addDefinedModule(loader, module); + } + } + + /** + * {@code Modules::add_reads_module}. + */ + public static void addReads(Module from, @SuppressWarnings("unused") Module to) { + checkIsNull(from, FROM_MODULE_TAG); + } + + /** + * {@code Modules::add_module_exports_qualified}. + */ + public static void addExports(Module from, String pn, Module to) { + checkIsNull(to, TO_MODULE_TAG); + addExportsToAll(from, pn); + } + + /** + * {@code Modules::add_module_exports}. + */ + public static void addExportsToAll(Module from, String pn) { + checkIsNull(pn, PACKAGE_TAG); + checkIsNull(from, FROM_MODULE_TAG); + checkIsPackageContainedInModule(pn, from, FROM_MODULE_TAG); + } + + /** + * {@code Modules::add_module_exports_to_all_unnamed}. + */ + public static void addExportsToAllUnnamed(Module module, String pn) { + checkIsNull(module, MODULE_TAG); + checkIsNull(pn, PACKAGE_TAG); + checkIsPackageContainedInModule(pn, module, MODULE_TAG); + } + + /** + * Module bookkeeping and utility methods used by substitutions. + */ + + private static final String PACKAGE_TAG = "module"; + private static final String MODULE_TAG = "module"; + private static final String FROM_MODULE_TAG = "from_" + MODULE_TAG; + private static final String TO_MODULE_TAG = "to_" + MODULE_TAG; private static final Object moduleLock = new Object(); private static final Map> definedModules = new HashMap<>(); - public static Map> getDefinedModules() { - if (definedModules.size() == 0) { + private static Map> getDefinedModules() { + if (definedModules.isEmpty()) { for (Module module : ModuleLayer.boot().modules()) { Set modules = definedModules.get(module.getClassLoader()); if (Objects.isNull(modules)) { @@ -59,17 +188,13 @@ public static Map> getDefinedModules() { return definedModules; } - public static void checkFromModuleAndPackageNullability(Module from, String pn) { - if (Objects.isNull(from)) { - throw new NullPointerException("The from_module is null"); - } - - if (Objects.isNull(pn)) { - throw new NullPointerException("The package is null"); + private static void checkIsNull(Object o, String tag) { + if (Objects.isNull(o)) { + throw new NullPointerException(tag + " is null"); } } - public static boolean isPackageNameForbidden(String pn) { + private static boolean isPackageNameForbidden(String pn) { if (!pn.startsWith("java")) { return false; } @@ -77,17 +202,23 @@ public static boolean isPackageNameForbidden(String pn) { return trailingChar == '.'; } - public static boolean isValidPackageName(String pn) { + private static void checkPackageNameForModule(Object pn, String module) { + if (Objects.isNull(pn) || !(pn instanceof String pnString)) { + throw new IllegalArgumentException("Bad package name"); + } + // It is OK to use SourceVersion.isName here even though it calls String.split() // because pattern "\\." will take the fast path in the String.split() method - return Objects.nonNull(pn) && SourceVersion.isName(pn); + if (!SourceVersion.isName(pnString)) { + throw new IllegalArgumentException("Invalid package name: " + pnString + " for module: " + module); + } } - public static boolean isModuleDefinedToLoader(ClassLoader loader, String moduleName) { + private static boolean isModuleDefinedToLoader(ClassLoader loader, String moduleName) { return getDefinedModules().getOrDefault(loader, Set.of()).stream().anyMatch(m -> m.getName().equals(moduleName)); } - public static void addDefinedModule(ClassLoader loader, Module module) { + private static void addDefinedModule(ClassLoader loader, Module module) { Set modules = getDefinedModules().get(loader); if (Objects.isNull(modules)) { modules = new HashSet<>(); @@ -98,30 +229,23 @@ public static void addDefinedModule(ClassLoader loader, Module module) { } } - public static void checkIsPackageContainedInModule(String pn, Module module) { - ClassLoader loader = module.getClassLoader() == null ? ClassLoader.getPlatformClassLoader() : module.getClassLoader(); - Package definedPackage = loader.getDefinedPackage(pn); - if (definedPackage != null) { - Target_java_lang_NamedPackage namedPackage = SubstrateUtil.cast(definedPackage, Target_java_lang_NamedPackage.class); - Module actualModule = namedPackage.module; - if (!actualModule.equals(module)) { - throw new IllegalArgumentException("Package " + pn + " found in module " + actualModule.getName() + - ", not in module: " + module.getName()); - } + private static void checkIsPackageContainedInModule(String pn, Module module, String tag) { + if (!module.isNamed() || module.getDescriptor().isOpen()) { + return; } if (!module.getPackages().contains(pn)) { - throw new IllegalArgumentException("Package " + pn + " not found in from_module " + module.getName()); + throw new IllegalArgumentException("Package " + pn + " not found in " + tag + " " + module.getName()); } } - public static List getPackagesDefinedToLoader(ClassLoader loader) { + private static List getPackagesDefinedToLoader(ClassLoader loader) { return getDefinedModules().getOrDefault(loader, Set.of()) .stream() .flatMap(m -> m.getPackages().stream()) - .collect(Collectors.toUnmodifiableList()); + .toList(); } - public static Object getModuleContainingPackage(ClassLoader loader, String pn) { + private static Object getModuleContainingPackage(ClassLoader loader, String pn) { return getDefinedModules().getOrDefault(loader, Set.of()) .stream() .filter(m -> m.getPackages().contains(pn)) @@ -132,84 +256,4 @@ public static boolean bootLayerContainsModule(String name) { return ModuleLayer.boot().modules().stream().anyMatch(m -> m.getName().equals(name)); } - public static void defineModule(Module module, boolean isOpen, List pns) { - if (Objects.isNull(module)) { - throw new NullPointerException("Null module object"); - } - - if (Objects.isNull(module.getName())) { - throw new IllegalArgumentException("Module name cannot be null"); - } - - if (module.getName().equals("java.base")) { - if (isOpen) { - throw new AssertionError("The java.base module cannot be open"); - } - - for (String pn : pns) { - if (!ModuleUtil.isValidPackageName(pn)) { - throw new IllegalArgumentException("Invalid package name: " + pn + " for module: java.base"); - } - } - - if (module.getClassLoader() != null) { - throw new IllegalArgumentException("Class loader must be the boot class loader"); - } - - synchronized (moduleLock) { - boolean duplicateJavaBase = ModuleUtil.bootLayerContainsModule("java.base"); - if (duplicateJavaBase) { - throw new InternalError("Module java.base is already defined"); - } - } - - return; - } - - ClassLoader loader = module.getClassLoader(); - if (Objects.isNull(loader) || loader.getClass().getName().equals("jdk.internal.reflect.DelegatingClassLoader")) { - throw new IllegalArgumentException("Class loader is an invalid delegating class loader"); - } - - for (String pn : pns) { - if (!ModuleUtil.isValidPackageName(pn)) { - throw new IllegalArgumentException("Invalid package name: " + pn + " for module: " + module.getName()); - } - - if (loader != ClassLoader.getPlatformClassLoader() && ModuleUtil.isPackageNameForbidden(pn)) { - throw new IllegalArgumentException("Class loader (instance of): " + loader.getClass().getName() + - " tried to define prohibited package name: " + pn); - } - } - - String definedPackage = null; - boolean moduleAlreadyDefined; - synchronized (moduleLock) { - moduleAlreadyDefined = ModuleUtil.isModuleDefinedToLoader(loader, module.getName()); - if (!moduleAlreadyDefined) { - List definedPackages = ModuleUtil.getPackagesDefinedToLoader(loader); - for (String pn : pns) { - if (definedPackages.contains(pn)) { - definedPackage = pn; - break; - } - } - } - } - - if (moduleAlreadyDefined) { - throw new IllegalStateException("Module " + module.getName() + " is already defined"); - } else if (Objects.nonNull(definedPackage)) { - Module moduleContainingDefinedPackage = SubstrateUtil.cast(ModuleUtil.getModuleContainingPackage(loader, definedPackage), Module.class); - if (moduleContainingDefinedPackage.isNamed()) { - throw new IllegalStateException("Package " + definedPackage + " is already in another module, " + moduleContainingDefinedPackage.getName() + ", defined to the class loader"); - } else { - throw new IllegalStateException("Package " + definedPackage + " is already in the unnamed module defined to the class loader"); - } - } - - synchronized (moduleLock) { - ModuleUtil.addDefinedModule(loader, module); - } - } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/RuntimeModuleSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/RuntimeModuleSupport.java index 89babece5ed1..ee6d23ffd5c8 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/RuntimeModuleSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/RuntimeModuleSupport.java @@ -33,13 +33,23 @@ import com.oracle.svm.core.BuildPhaseProvider.AfterHostedUniverse; import com.oracle.svm.core.heap.UnknownObjectField; +/** + * Runtime module support singleton, containing the runtime boot module layer. The boot module layer + * is synthesized by a feature during native image generation, after analysis (as module layer + * synthesizing requires analysis information). For convenience, this singleton also contains + * hosted-only hosted-to-runtime module mappers used by other parts of the module system during the + * image build. These are important, as every hosted module has its own synthesized runtime + * counterpart. The lookup function is implemented inside the module layer synthesis feature. See + * {@code ModuleLayerFeature} for more information. + */ public final class RuntimeModuleSupport { public static RuntimeModuleSupport instance() { return ImageSingletons.lookup(RuntimeModuleSupport.class); } - @UnknownObjectField(availability = AfterHostedUniverse.class) private ModuleLayer bootLayer; + @UnknownObjectField(availability = AfterHostedUniverse.class) // + private ModuleLayer bootLayer; @Platforms(Platform.HOSTED_ONLY.class) // private Function hostedToRuntimeModuleMapper; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_Module.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_Module.java index 614a53e13afe..f81ef257489d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_Module.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_Module.java @@ -24,17 +24,19 @@ */ package com.oracle.svm.core.jdk; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; - import com.oracle.svm.core.annotate.Alias; import com.oracle.svm.core.annotate.Substitute; import com.oracle.svm.core.annotate.TargetClass; import com.oracle.svm.core.annotate.TargetElement; import com.oracle.svm.core.util.BasedOnJDKFile; +/** + * Substitution class for {@link java.lang.Module}. We need to substitute native methods + * particularly, because original methods in the JDK contain VM state updates and perform additional + * bookkeeping. We implement all the data structures we need to answer module system queries in Java + * (see {@link ModuleNative}. In order to preserve JCK compatibility, we need to perform all + * the checks performed by original methods and throw the exact same exception types and messages. + */ @SuppressWarnings("unused") @TargetClass(value = java.lang.Module.class) public final class Target_java_lang_Module { @@ -56,45 +58,30 @@ public boolean isNativeAccessEnabled() { @Substitute @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-23+10/src/hotspot/share/classfile/modules.cpp#L275-L479") private static void defineModule0(Module module, boolean isOpen, String version, String location, Object[] pns) { - if (Arrays.stream(pns).anyMatch(Objects::isNull)) { - throw new IllegalArgumentException("Bad package name"); - } - List packages = Arrays.stream(pns).map(Object::toString).collect(Collectors.toUnmodifiableList()); - ModuleUtil.defineModule(module, isOpen, packages); + ModuleNative.defineModule(module, isOpen, pns); } @Substitute @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-23+10/src/hotspot/share/classfile/modules.cpp#L763-L799") private static void addReads0(Module from, Module to) { - if (Objects.isNull(from)) { - throw new NullPointerException("The from_module is null"); - } + ModuleNative.addReads(from, to); } @Substitute @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-23+10/src/hotspot/share/classfile/modules.cpp#L753-L761") private static void addExports0(Module from, String pn, Module to) { - if (Objects.isNull(to)) { - throw new NullPointerException("The to_module is null"); - } - - ModuleUtil.checkFromModuleAndPackageNullability(from, pn); - ModuleUtil.checkIsPackageContainedInModule(pn, from); + ModuleNative.addExports(from, pn, to); } @Substitute @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-23+10/src/hotspot/share/classfile/modules.cpp#L686-L750") private static void addExportsToAll0(Module from, String pn) { - ModuleUtil.checkFromModuleAndPackageNullability(from, pn); - ModuleUtil.checkIsPackageContainedInModule(pn, from); + ModuleNative.addExportsToAll(from, pn); } @Substitute @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-23+10/src/hotspot/share/classfile/modules.cpp#L869-L918") private static void addExportsToAllUnnamed0(Module from, String pn) { - ModuleUtil.checkFromModuleAndPackageNullability(from, pn); - if (from.isNamed()) { - ModuleUtil.checkIsPackageContainedInModule(pn, from); - } + ModuleNative.addExportsToAllUnnamed(from, pn); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_NamedPackage.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_NamedPackage.java deleted file mode 100644 index b265503b8071..000000000000 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_NamedPackage.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package com.oracle.svm.core.jdk; - -import com.oracle.svm.core.annotate.Alias; -import com.oracle.svm.core.annotate.TargetClass; - -@TargetClass(className = "java.lang.NamedPackage") // -final class Target_java_lang_NamedPackage { - @Alias Module module; -} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Target_java_lang_ScopedValue.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Target_java_lang_ScopedValue.java index 0e6152bcc791..2aaf4cbab947 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Target_java_lang_ScopedValue.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Target_java_lang_ScopedValue.java @@ -35,13 +35,13 @@ import com.oracle.svm.core.annotate.Alias; import com.oracle.svm.core.annotate.Substitute; import com.oracle.svm.core.annotate.TargetClass; -import com.oracle.svm.core.jdk.ModuleUtil; +import com.oracle.svm.core.jdk.ModuleNative; @Platforms(Platform.HOSTED_ONLY.class) final class ScopedValuesEnabled implements BooleanSupplier { @Override public boolean getAsBoolean() { - return JavaVersionUtil.JAVA_SPEC >= 21 || (JavaVersionUtil.JAVA_SPEC >= 20 && ModuleUtil.bootLayerContainsModule("jdk.incubator.concurrent")); + return JavaVersionUtil.JAVA_SPEC >= 21 || (JavaVersionUtil.JAVA_SPEC >= 20 && ModuleNative.bootLayerContainsModule("jdk.incubator.concurrent")); } }