key = types.getOrCreateValidType(typeBytes, true);
+ assert key != null : typeBytes;
+ Class> prev = aotClasses.put(key, cls);
+ assert prev == null || prev == cls;
+ }
+}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/registry/AbstractRuntimeClassRegistry.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/registry/AbstractRuntimeClassRegistry.java
new file mode 100644
index 000000000000..e8877beb3486
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/registry/AbstractRuntimeClassRegistry.java
@@ -0,0 +1,515 @@
+/*
+ * Copyright (c) 2024, 2024, 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.hub.registry;
+
+import static com.oracle.svm.espresso.classfile.Constants.ACC_SUPER;
+import static com.oracle.svm.espresso.classfile.Constants.JVM_ACC_WRITTEN_FLAGS;
+
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.graalvm.nativeimage.impl.ClassLoading;
+
+import com.oracle.svm.core.SubstrateUtil;
+import com.oracle.svm.core.hub.DynamicHub;
+import com.oracle.svm.core.hub.RuntimeClassLoading;
+import com.oracle.svm.core.hub.RuntimeClassLoading.ClassDefinitionInfo;
+import com.oracle.svm.core.jdk.Target_java_lang_ClassLoader;
+import com.oracle.svm.core.util.VMError;
+import com.oracle.svm.espresso.classfile.ClassfileParser;
+import com.oracle.svm.espresso.classfile.ClassfileStream;
+import com.oracle.svm.espresso.classfile.ParserConstantPool;
+import com.oracle.svm.espresso.classfile.ParserException;
+import com.oracle.svm.espresso.classfile.ParserKlass;
+import com.oracle.svm.espresso.classfile.attributes.InnerClassesAttribute;
+import com.oracle.svm.espresso.classfile.attributes.NestHostAttribute;
+import com.oracle.svm.espresso.classfile.attributes.PermittedSubclassesAttribute;
+import com.oracle.svm.espresso.classfile.attributes.RecordAttribute;
+import com.oracle.svm.espresso.classfile.attributes.SignatureAttribute;
+import com.oracle.svm.espresso.classfile.attributes.SourceFileAttribute;
+import com.oracle.svm.espresso.classfile.descriptors.Name;
+import com.oracle.svm.espresso.classfile.descriptors.ParserSymbols.ParserNames;
+import com.oracle.svm.espresso.classfile.descriptors.Symbol;
+import com.oracle.svm.espresso.classfile.descriptors.Type;
+import com.oracle.svm.espresso.classfile.descriptors.ValidationException;
+
+import jdk.internal.misc.Unsafe;
+import jdk.vm.ci.meta.MetaUtil;
+
+/**
+ * This class registry is used for ClassLoader instances if runtime class loading is supported.
+ *
+ * Parallel class loading is currently disabled (see {@link Target_java_lang_ClassLoader}) and not
+ * supported by this implementation (GR-62338). Note that the boot class loader is always considered
+ * parallel-capable. To prevent issues until parallel class loading is implemented, the
+ * {@link BootClassRegistry#doLoadClass(Symbol)} method is synchronized.
+ *
+ * Classloaders synchronize on a class loading lock in their loadClass implementation. When parallel
+ * class loading is disabled, this lock is the classloader object itself.
+ *
+ * When defining a class, the registry synchronizes on the classloader if it is not
+ * parallel-capable.
+ *
+ * The registry synchronizes on the classloader during class resolution (such as
+ * {@code Class.forName} or {@code ClassLoader.findBootstrapClassOrNull}) if the classloader is not
+ * parallel-capable.
+ *
+ * There are exactly 2 types of runtime class registries: the one used for the
+ * {@linkplain BootClassRegistry boot class loader} and the one used for
+ * {@linkplain UserDefinedClassRegistry all other class loaders}.
+ */
+public abstract sealed class AbstractRuntimeClassRegistry extends AbstractClassRegistry permits BootClassRegistry, UserDefinedClassRegistry {
+ private static final Unsafe UNSAFE = Unsafe.getUnsafe();
+ private static final Class>[] EMPTY_CLASS_ARRAY = new Class>[0];
+ /**
+ * Strong hidden classes must be referenced by the class loader data to prevent them from being
+ * reclaimed, while not appearing in the actual registry. This field simply keeps those hidden
+ * classes strongly reachable from the class registry.
+ */
+ private final Collection> strongHiddenClasses = new ArrayList<>();
+
+ AbstractRuntimeClassRegistry() {
+ super(new ConcurrentHashMap<>());
+ }
+
+ @Override
+ public final Class> loadClass(Symbol name) throws ClassNotFoundException {
+ Class> aotClass = findAOTLoadedClass(name);
+ if (aotClass != null) {
+ return aotClass;
+ }
+ if (isParallelClassLoader()) {
+ return loadClassInner(name);
+ } else {
+ if (runtimeClasses.get(name) instanceof Class> entry) {
+ return entry;
+ }
+ synchronized (getClassLoader()) {
+ return loadClassInner(name);
+ }
+ }
+ }
+
+ private Class> loadClassInner(Symbol name) throws ClassNotFoundException {
+ if (getClassLoader() != null && isParallelClassLoader()) {
+ // GR-62338
+ throw VMError.unimplemented("Parallel class loading:" + getClassLoader());
+ }
+ Object existing = runtimeClasses.get(name);
+ if (existing instanceof Class> entry) {
+ // someone else won the race and populated the slot
+ return entry;
+ }
+ if (existing instanceof Placeholder placeholder && placeholder.isSuperProbingThread()) {
+ throw new ClassCircularityError(name.toString());
+ }
+ Class> result = doLoadClass(name);
+ if (result == null) {
+ // The boot class loader can return null
+ return null;
+ }
+ var prev = runtimeClasses.put(name, result);
+ assert prev == null || prev == result : prev;
+ return result;
+ }
+
+ protected boolean isParallelClassLoader() {
+ return isParallelClassLoader(getClassLoader());
+ }
+
+ private static boolean isParallelClassLoader(ClassLoader loader) {
+ if (loader == null) {
+ // The boot class loader is always considered parallel
+ return true;
+ }
+ return SubstrateUtil.cast(loader, Target_java_lang_ClassLoader.class).parallelLockMap != null;
+ }
+
+ protected abstract Class> doLoadClass(Symbol type) throws ClassNotFoundException;
+
+ protected abstract boolean loaderIsBootOrPlatform();
+
+ public final Class> defineClass(Symbol typeOrNull, byte[] b, int off, int len, ClassDefinitionInfo info) {
+ if (isParallelClassLoader()) {
+ return defineClassInner(typeOrNull, b, off, len, info);
+ } else {
+ synchronized (getClassLoader()) {
+ return defineClassInner(typeOrNull, b, off, len, info);
+ }
+ }
+ }
+
+ private Class> defineClassInner(Symbol typeOrNull, byte[] b, int off, int len, ClassDefinitionInfo info) {
+ if (isParallelClassLoader() || getClassLoader() == null) {
+ // GR-62338
+ throw VMError.unimplemented("Parallel class loading:" + getClassLoader());
+ }
+ byte[] data = b;
+ if (off != 0 || b.length != len) {
+ if (len < 0) {
+ throw new ArrayIndexOutOfBoundsException("Length " + len + " is negative");
+ }
+ if (off < 0 || off > b.length - len) {
+ throw new ArrayIndexOutOfBoundsException("Array region " + off + ".." + ((long) off + len) + " out of bounds for length " + len);
+ }
+ data = Arrays.copyOfRange(data, off, off + len);
+ }
+ ParserKlass parsed = parseClass(typeOrNull, info, data);
+ Symbol type = typeOrNull == null ? parsed.getType() : typeOrNull;
+ assert typeOrNull == null || type == parsed.getType();
+ if (info.addedToRegistry() && findLoadedClass(type) != null) {
+ String kind;
+ if (Modifier.isInterface(parsed.getFlags())) {
+ kind = "interface";
+ } else if (Modifier.isAbstract(parsed.getFlags())) {
+ kind = "abstract class";
+ } else {
+ kind = "class";
+ }
+ String externalName = getExternalName(parsed, info);
+ throw new LinkageError("Loader " + ClassRegistries.loaderNameAndId(getClassLoader()) + " attempted duplicate " + kind + " definition for " + externalName + ".");
+ }
+ Class> clazz;
+ try {
+ clazz = createClass(parsed, info, type);
+ } catch (ParserException.ClassFormatError error) {
+ throw new ClassFormatError(error.getMessage());
+ }
+ if (info.addedToRegistry()) {
+ registerClass(clazz, type);
+ } else if (info.isStrongHidden()) {
+ registerStrongHiddenClass(clazz);
+ }
+ return clazz;
+ }
+
+ private ParserKlass parseClass(Symbol typeOrNull, ClassDefinitionInfo info, byte[] data) {
+ boolean verifiable = RuntimeClassLoading.Options.ClassVerification.getValue().needsVerification(getClassLoader());
+ try {
+ return ClassfileParser.parse(ClassRegistries.getParsingContext(), new ClassfileStream(data, null), verifiable, loaderIsBootOrPlatform(), typeOrNull, info.isHidden(),
+ info.forceAllowVMAnnotations(), verifiable);
+ } catch (ValidationException | ParserException.ClassFormatError validationOrBadFormat) {
+ throw new ClassFormatError(validationOrBadFormat.getMessage());
+ } catch (ParserException.UnsupportedClassVersionError unsupportedClassVersionError) {
+ throw new UnsupportedClassVersionError(unsupportedClassVersionError.getMessage());
+ } catch (ParserException.NoClassDefFoundError noClassDefFoundError) {
+ throw new NoClassDefFoundError(noClassDefFoundError.getMessage());
+ } catch (ParserException parserException) {
+ throw VMError.shouldNotReachHere("Not a validation nor parser exception", parserException);
+ }
+ }
+
+ private Class> createClass(ParserKlass parsed, ClassDefinitionInfo info, Symbol type) {
+ Symbol superKlassType = parsed.getSuperKlass();
+ assert superKlassType != null; // j.l.Object is always AOT
+ // Load direct super interfaces
+ Symbol[] superInterfacesTypes = parsed.getSuperInterfaces();
+ Class>[] superInterfaces = superInterfacesTypes.length == 0 ? EMPTY_CLASS_ARRAY : new Class>[superInterfacesTypes.length];
+ for (int i = 0; i < superInterfacesTypes.length; ++i) {
+ superInterfaces[i] = loadSuperType(type, superInterfacesTypes[i]);
+ if (!superInterfaces[i].isInterface()) {
+ throw new IncompatibleClassChangeError("Class " + parsed.getType() + " cannot implement " + superInterfaces[i] + ", because it is not an interface");
+ }
+ }
+
+ Class> superClass = loadSuperType(type, superKlassType);
+ if (superClass.isInterface()) {
+ throw new IncompatibleClassChangeError("Class " + parsed.getType() + " has interface " + superKlassType + " as super class");
+ }
+ if (Modifier.isFinal(superClass.getModifiers())) {
+ throw new IncompatibleClassChangeError("Class " + parsed.getType() + " is a subclass of final class " + superKlassType);
+ }
+ // GR-62339: Perform super class and interfaces access checks
+
+ String externalName = getExternalName(parsed, info);
+ String simpleBinaryName = getSimpleBinaryName(parsed);
+ String sourceFile = getSourceFile(parsed);
+ Class> nestHost = getNestHost(parsed);
+ Class> enclosingClass = getEnclosingClass(parsed);
+ String classSignature = getClassSignature(parsed);
+
+ int modifiers = getClassModifiers(parsed);
+
+ boolean isInterface = Modifier.isInterface(modifiers);
+ boolean isRecord = Modifier.isFinal(modifiers) && superClass == Record.class && parsed.getAttribute(RecordAttribute.NAME) != null;
+ // GR-62320 This should be set based on build-time and run-time arguments.
+ boolean assertionsEnabled = true;
+ // GR-59687 itable setup should set this
+ boolean declaresDefaultMethods = false;
+ boolean hasDefaultMethods = declaresDefaultMethods || hasInheritedDefaultMethods(superClass, superInterfaces);
+ boolean isSealed = isSealed(parsed);
+
+ short flags = DynamicHub.makeFlags(false, isInterface, info.isHidden(), isRecord, assertionsEnabled, hasDefaultMethods, declaresDefaultMethods, isSealed, false, false, false, false);
+
+ Object interfacesEncoding = null;
+ if (superInterfaces.length == 1) {
+ interfacesEncoding = DynamicHub.fromClass(superInterfaces[0]);
+ } else if (superInterfaces.length > 1) {
+ DynamicHub[] superHubs = new DynamicHub[superInterfaces.length];
+ for (int i = 0; i < superHubs.length; ++i) {
+ superHubs[i] = DynamicHub.fromClass(superInterfaces[i]);
+ }
+ interfacesEncoding = superHubs;
+ }
+
+ DynamicHub hub = DynamicHub.allocate(externalName, DynamicHub.fromClass(superClass), interfacesEncoding, null,
+ sourceFile, modifiers, flags, getClassLoader(), nestHost, simpleBinaryName, enclosingClass, classSignature);
+
+ return DynamicHub.toClass(hub);
+ }
+
+ private static boolean isSealed(ParserKlass parsed) {
+ PermittedSubclassesAttribute permittedSubclasses = (PermittedSubclassesAttribute) parsed.getAttribute(PermittedSubclassesAttribute.NAME);
+ return permittedSubclasses != null && permittedSubclasses.getClasses().length > 0;
+ }
+
+ private static boolean hasInheritedDefaultMethods(Class> superClass, Class>[] superInterfaces) {
+ if (DynamicHub.fromClass(superClass).hasDefaultMethods()) {
+ return true;
+ }
+ for (Class> superInterface : superInterfaces) {
+ if (DynamicHub.fromClass(superInterface).hasDefaultMethods()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static int getClassModifiers(ParserKlass parsed) {
+ int modifiers = parsed.getFlags();
+ InnerClassesAttribute innerClassesAttribute = (InnerClassesAttribute) parsed.getAttribute(InnerClassesAttribute.NAME);
+ if (innerClassesAttribute != null) {
+ ParserConstantPool pool = parsed.getConstantPool();
+ for (int i = 0; i < innerClassesAttribute.entryCount(); i++) {
+ InnerClassesAttribute.Entry entry = innerClassesAttribute.entryAt(i);
+ if (entry.innerClassIndex != 0) {
+ Symbol innerClassName = pool.className(entry.innerClassIndex);
+ if (innerClassName.equals(parsed.getName())) {
+ modifiers = entry.innerClassAccessFlags;
+ break;
+ }
+ }
+ }
+ }
+ return modifiers & ~ACC_SUPER & JVM_ACC_WRITTEN_FLAGS;
+ }
+
+ private static Class> getNestHost(ParserKlass parsed) {
+ Class> nestHost = null;
+ NestHostAttribute nestHostAttribute = (NestHostAttribute) parsed.getAttribute(NestHostAttribute.NAME);
+ if (nestHostAttribute != null) {
+ // must be lazy, should move to companion
+ throw VMError.unimplemented("nest host is not supported yet");
+ }
+ return nestHost;
+ }
+
+ private static String getExternalName(ParserKlass parsed, ClassDefinitionInfo info) {
+ String externalName = MetaUtil.internalNameToJava(parsed.getType().toString(), true, true);
+ if (info.isHidden()) {
+ int idx = externalName.lastIndexOf('+');
+ char[] chars = externalName.toCharArray();
+ chars[idx] = '/';
+ externalName = new String(chars);
+ }
+ return externalName;
+ }
+
+ private static Class> getEnclosingClass(ParserKlass parsed) {
+ InnerClassesAttribute innerClassesAttribute = (InnerClassesAttribute) parsed.getAttribute(InnerClassesAttribute.NAME);
+ if (innerClassesAttribute == null) {
+ return null;
+ }
+ throw VMError.unimplemented("enclosing class is not supported yet");
+ }
+
+ private static String getSimpleBinaryName(ParserKlass parsed) {
+ InnerClassesAttribute innerClassesAttribute = (InnerClassesAttribute) parsed.getAttribute(InnerClassesAttribute.NAME);
+ if (innerClassesAttribute == null) {
+ return null;
+ }
+ ParserConstantPool pool = parsed.getConstantPool();
+ for (int i = 0; i < innerClassesAttribute.entryCount(); i++) {
+ InnerClassesAttribute.Entry entry = innerClassesAttribute.entryAt(i);
+ int innerClassIndex = entry.innerClassIndex;
+ if (innerClassIndex != 0) {
+ if (pool.className(innerClassIndex) == parsed.getName()) {
+ if (entry.innerNameIndex == 0) {
+ break;
+ } else {
+ Symbol> innerName = pool.utf8At(entry.innerNameIndex, "inner class name");
+ return innerName.toString();
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ private static String getSourceFile(ParserKlass parsed) {
+ String sourceFile = null;
+ SourceFileAttribute sourceFileAttribute = (SourceFileAttribute) parsed.getAttribute(ParserNames.SourceFile);
+ if (sourceFileAttribute != null) {
+ sourceFile = parsed.getConstantPool().utf8At(sourceFileAttribute.getSourceFileIndex()).toString();
+ }
+ return sourceFile;
+ }
+
+ private static String getClassSignature(ParserKlass parsed) {
+ String sourceFile = null;
+ SignatureAttribute signatureAttribute = (SignatureAttribute) parsed.getAttribute(ParserNames.Signature);
+ if (signatureAttribute != null) {
+ sourceFile = parsed.getConstantPool().utf8At(signatureAttribute.getSignatureIndex()).toString();
+ }
+ return sourceFile;
+ }
+
+ @SuppressWarnings("try")
+ public final Class> loadSuperType(Symbol name, Symbol superName) {
+ Placeholder placeholder = new Placeholder();
+ var prev = runtimeClasses.putIfAbsent(name, placeholder);
+ if (prev instanceof Placeholder otherPlaceHolder) {
+ if (otherPlaceHolder.isSuperProbingThread()) {
+ throw new ClassCircularityError(name.toString());
+ }
+ otherPlaceHolder.addSuperProbingThread();
+ }
+ assert prev == null : prev;
+ try (var scope = ClassLoading.allowArbitraryClassLoading()) {
+ return loadClass(superName);
+ } catch (ClassNotFoundException e) {
+ NoClassDefFoundError error = new NoClassDefFoundError(superName.toString());
+ error.initCause(e);
+ throw error;
+ } finally {
+ runtimeClasses.remove(name, placeholder);
+ }
+ }
+
+ private void registerClass(Class> clazz, Symbol type) {
+ // GR-62320: record constraints
+ var previous = runtimeClasses.put(type, clazz);
+ assert previous == null;
+ }
+
+ private void registerStrongHiddenClass(Class> clazz) {
+ strongHiddenClasses.add(clazz);
+ }
+
+ /**
+ * See {@link AbstractClassRegistry#runtimeClasses}.
+ */
+ private static final class Placeholder extends CompletableFuture> {
+ private static final long SUPER_PROBING_OFFSET = UNSAFE.objectFieldOffset(Placeholder.class, "otherSuperProbingThreads");
+ private static final long THREAD_ARRAY_BASE_OFFSET = UNSAFE.arrayBaseOffset(Thread[].class);
+ private static final long THREAD_ELEMENT_SIZE = UNSAFE.arrayIndexScale(Thread[].class);
+ private static final AtomicInteger NEXT_ID = new AtomicInteger(0);
+ private final int id = NEXT_ID.getAndIncrement();
+ private final Thread thread;
+ private volatile Object otherSuperProbingThreads;
+
+ Placeholder() {
+ this.thread = Thread.currentThread();
+ }
+
+ @Override
+ public String toString() {
+ return "#" + id + ' ' + thread;
+ }
+
+ public boolean isSuperProbingThread() {
+ Thread t = Thread.currentThread();
+ if (t == thread) {
+ return true;
+ }
+ Object others = otherSuperProbingThreads;
+ if (t == others) {
+ return true;
+ }
+ if (others instanceof Thread[] otherThreads) {
+ for (Thread other : otherThreads) {
+ if (other == t) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ public void addSuperProbingThread() {
+ Thread t = Thread.currentThread();
+ if (t == thread) {
+ return;
+ }
+ Object others = otherSuperProbingThreads;
+ if (others == null) {
+ Object found = UNSAFE.compareAndExchangeReference(this, SUPER_PROBING_OFFSET, null, t);
+ if (found == null) {
+ return;
+ }
+ others = found;
+ }
+ if (others == t) {
+ return;
+ }
+ if (others instanceof Thread otherThread) {
+ // extend to array
+ Object found = UNSAFE.compareAndExchangeReference(this, SUPER_PROBING_OFFSET, otherThread, new Thread[]{otherThread, t});
+ if (found == otherThread) {
+ return;
+ }
+ others = found;
+ }
+ Thread[] otherThreads = (Thread[]) others;
+ while (true) {
+ // try to insert in existing array
+ for (int i = 0; i < otherThreads.length; i++) {
+ if (otherThreads[i] == t) {
+ return;
+ }
+ if (otherThreads[i] == null) {
+ if (UNSAFE.compareAndSetReference(otherThreads, THREAD_ARRAY_BASE_OFFSET + i * THREAD_ELEMENT_SIZE, null, t)) {
+ return;
+ }
+ }
+ }
+ // we have to grow
+ Thread[] newArray = Arrays.copyOf(otherThreads, Math.max(otherThreads.length << 1, otherThreads.length + 1));
+ newArray[otherThreads.length] = t;
+ Object found = UNSAFE.compareAndExchangeReference(this, SUPER_PROBING_OFFSET, otherThreads, newArray);
+ if (found == otherThreads) {
+ return;
+ }
+ otherThreads = (Thread[]) found;
+ // try to insert in that new array
+ }
+ }
+ }
+}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/registry/BootClassRegistry.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/registry/BootClassRegistry.java
new file mode 100644
index 000000000000..280c9f54982a
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/registry/BootClassRegistry.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2024, 2024, 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.hub.registry;
+
+import java.io.IOException;
+import java.net.URI;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import org.graalvm.nativeimage.Platform;
+import org.graalvm.nativeimage.Platforms;
+
+import com.oracle.svm.core.hub.RuntimeClassLoading.ClassDefinitionInfo;
+import com.oracle.svm.core.util.VMError;
+import com.oracle.svm.espresso.classfile.descriptors.Symbol;
+import com.oracle.svm.espresso.classfile.descriptors.Type;
+
+/**
+ * This class registry corresponds to the {@code null} class loader when runtime class loading is
+ * supported.
+ */
+public final class BootClassRegistry extends AbstractRuntimeClassRegistry {
+ private volatile FileSystem jrtFS;
+
+ @Platforms(Platform.HOSTED_ONLY.class)
+ public BootClassRegistry() {
+ }
+
+ private FileSystem getFileSystem() {
+ // jrtFS is lazily initialized to avoid having this FileSystem in the image heap.
+ FileSystem fs = jrtFS;
+ if (fs == null) {
+ synchronized (this) {
+ if ((fs = jrtFS) == null) {
+ jrtFS = fs = FileSystems.getFileSystem(URI.create("jrt:/"));
+ }
+ }
+ }
+ return fs;
+ }
+
+ // synchronized until parallel class loading is implemented (GR-62338)
+ @Override
+ public synchronized Class> doLoadClass(Symbol type) {
+ // Only looking into the jimage for now. There could be appended elements.
+ // see GraalServices.getSavedProperty("jdk.boot.class.path.append")
+ String pkg = packageFromType(type);
+ try {
+ String moduleName = ClassRegistries.getBootModuleForPackage(pkg);
+ if (moduleName == null) {
+ return null;
+ }
+ Path classPath = getFileSystem().getPath("/modules/" + moduleName + "/" + type + ".class");
+ if (!Files.exists(classPath)) {
+ return null;
+ }
+ byte[] bytes = Files.readAllBytes(classPath);
+ return defineClass(type, bytes, 0, bytes.length, ClassDefinitionInfo.EMPTY);
+ } catch (IOException e) {
+ throw VMError.shouldNotReachHere(e);
+ }
+ }
+
+ private static String packageFromType(Symbol type) {
+ int lastSlash = type.lastIndexOf((byte) '/');
+ if (lastSlash == -1) {
+ return null;
+ }
+ return type.subSequence(0, lastSlash).toString();
+ }
+
+ @Override
+ protected boolean loaderIsBootOrPlatform() {
+ return true;
+ }
+
+ @Override
+ public ClassLoader getClassLoader() {
+ return null;
+ }
+}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/registry/ClassLoadingSupportImpl.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/registry/ClassLoadingSupportImpl.java
new file mode 100644
index 000000000000..12debc238488
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/registry/ClassLoadingSupportImpl.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2025, 2025, 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.hub.registry;
+
+import org.graalvm.nativeimage.impl.ClassLoadingSupport;
+
+import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton;
+import com.oracle.svm.core.hub.RuntimeClassLoading;
+
+@AutomaticallyRegisteredImageSingleton(ClassLoadingSupport.class)
+public class ClassLoadingSupportImpl implements ClassLoadingSupport {
+ // This should work for virtual threads so it can't be a FastThreadLocal.
+ private final ThreadLocal ignoreReflectionConfiguration = new ThreadLocal<>() {
+ @Override
+ protected Integer initialValue() {
+ return 0;
+ }
+ };
+
+ @Override
+ public boolean isSupported() {
+ return RuntimeClassLoading.isSupported();
+ }
+
+ @Override
+ public boolean followReflectionConfiguration() {
+ return ignoreReflectionConfiguration.get() == 0;
+ }
+
+ @Override
+ public void startIgnoreReflectionConfigurationScope() {
+ int previous = ignoreReflectionConfiguration.get();
+ ignoreReflectionConfiguration.set(Math.incrementExact(previous));
+ }
+
+ @Override
+ public void endIgnoreReflectionConfigurationScope() {
+ int previous = ignoreReflectionConfiguration.get();
+ if (previous == 0) {
+ throw new IllegalStateException("Unbalanced start/end arbitrary classloading allowed scopes");
+ }
+ assert previous > 0;
+ ignoreReflectionConfiguration.set(previous - 1);
+ }
+}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/registry/ClassRegistries.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/registry/ClassRegistries.java
new file mode 100644
index 000000000000..40b80267d700
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/registry/ClassRegistries.java
@@ -0,0 +1,455 @@
+/*
+ * Copyright (c) 2025, 2025, 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.hub.registry;
+
+import static com.oracle.svm.core.MissingRegistrationUtils.throwMissingRegistrationErrors;
+
+import java.lang.reflect.Field;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Supplier;
+
+import org.graalvm.collections.EconomicMap;
+import org.graalvm.collections.MapCursor;
+import org.graalvm.nativeimage.ImageSingletons;
+import org.graalvm.nativeimage.Platform;
+import org.graalvm.nativeimage.Platforms;
+import org.graalvm.nativeimage.hosted.FieldValueTransformer;
+import org.graalvm.nativeimage.impl.ClassLoadingSupport;
+
+import com.oracle.svm.core.SubstrateUtil;
+import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton;
+import com.oracle.svm.core.hub.ClassForNameSupport;
+import com.oracle.svm.core.hub.DynamicHub;
+import com.oracle.svm.core.hub.RuntimeClassLoading;
+import com.oracle.svm.core.hub.RuntimeClassLoading.ClassDefinitionInfo;
+import com.oracle.svm.core.jdk.Target_java_lang_ClassLoader;
+import com.oracle.svm.core.log.Log;
+import com.oracle.svm.core.reflect.MissingReflectionRegistrationUtils;
+import com.oracle.svm.core.util.VMError;
+import com.oracle.svm.espresso.classfile.JavaVersion;
+import com.oracle.svm.espresso.classfile.ParsingContext;
+import com.oracle.svm.espresso.classfile.descriptors.ByteSequence;
+import com.oracle.svm.espresso.classfile.descriptors.ModifiedUTF8;
+import com.oracle.svm.espresso.classfile.descriptors.Name;
+import com.oracle.svm.espresso.classfile.descriptors.NameSymbols;
+import com.oracle.svm.espresso.classfile.descriptors.ParserSymbols;
+import com.oracle.svm.espresso.classfile.descriptors.Symbol;
+import com.oracle.svm.espresso.classfile.descriptors.Symbols;
+import com.oracle.svm.espresso.classfile.descriptors.Type;
+import com.oracle.svm.espresso.classfile.descriptors.TypeSymbols;
+import com.oracle.svm.espresso.classfile.descriptors.Utf8Symbols;
+import com.oracle.svm.espresso.classfile.perf.TimerCollection;
+import com.oracle.svm.util.ReflectionUtil;
+
+import jdk.graal.compiler.api.replacements.Fold;
+import jdk.internal.loader.BootLoader;
+import jdk.internal.misc.PreviewFeatures;
+
+/**
+ * Class registries are used when native image respects the class loader hierarchy. There is one
+ * {@linkplain AbstractClassRegistry registry} per class loader. Each registry maps class names to
+ * classes. This allows multiple class loaders to load classes with the same name without conflicts.
+ *
+ * Class registries are attached to class loaders through an
+ * {@linkplain Target_java_lang_ClassLoader#classRegistry injected field}, which binds their
+ * lifetime to that of the class loader.
+ *
+ * Classes that require dynamic lookup via reflection or other mechanisms at runtime are
+ * pre-registered in their respective declaring class loader's registry during build time. At
+ * runtime class registries can grow in 2 ways:
+ *
+ * - When a class lookup is answered through delegation rather than directly. This means the next
+ * lookup can use that cached answer directly as required by JVMS sect. 5.3.1 & 5.3.2.
+ * - When a class is defined (i.e., by runtime class loading when it's enabled)
+ *
+ */
+@AutomaticallyRegisteredImageSingleton
+public final class ClassRegistries implements ParsingContext {
+ public final TimerCollection timers = TimerCollection.create(false);
+
+ @Platforms(Platform.HOSTED_ONLY.class)//
+ private final ConcurrentHashMap buildTimeRegistries;
+
+ private final Utf8Symbols utf8;
+ private final NameSymbols names;
+ private final TypeSymbols types;
+ private final AbstractClassRegistry bootRegistry;
+ private final EconomicMap bootPackageToModule;
+
+ @Platforms(Platform.HOSTED_ONLY.class)
+ public ClassRegistries() {
+ if (RuntimeClassLoading.isSupported()) {
+ bootRegistry = new BootClassRegistry();
+ } else {
+ bootRegistry = new AOTClassRegistry(null);
+ }
+ ParserSymbols.ensureInitialized();
+ int initialSymbolTableCapacity = 4 * 1024;
+ Symbols symbols = Symbols.fromExisting(ParserSymbols.SYMBOLS.freeze(), initialSymbolTableCapacity, 0);
+ // let this resize when first used at runtime
+ utf8 = new Utf8Symbols(symbols);
+ names = new NameSymbols(symbols);
+ types = new TypeSymbols(symbols);
+ buildTimeRegistries = new ConcurrentHashMap<>();
+ bootPackageToModule = computeBootPackageToModuleMap();
+ }
+
+ private static EconomicMap computeBootPackageToModuleMap() {
+ Field moduleField = ReflectionUtil.lookupField(ReflectionUtil.lookupClass(false, "java.lang.NamedPackage"), "module");
+ EconomicMap bootPackageToModule = EconomicMap.create();
+ BootLoader.packages().forEach(p -> {
+ try {
+ bootPackageToModule.put(p.getName(), ((Module) moduleField.get(p)).getName());
+ } catch (IllegalAccessException e) {
+ throw VMError.shouldNotReachHere(e);
+ }
+ });
+ return bootPackageToModule;
+ }
+
+ @Fold
+ static ClassRegistries singleton() {
+ return ImageSingletons.lookup(ClassRegistries.class);
+ }
+
+ static String getBootModuleForPackage(String pkg) {
+ return singleton().bootPackageToModule.get(pkg);
+ }
+
+ public static String[] getSystemPackageNames() {
+ String[] result = new String[singleton().bootPackageToModule.size()];
+ MapCursor cursor = singleton().bootPackageToModule.getEntries();
+ int i = 0;
+ while (cursor.advance()) {
+ result[i++] = cursor.getKey();
+ }
+ assert i == result.length;
+ return result;
+ }
+
+ TypeSymbols getTypes() {
+ return types;
+ }
+
+ public static Class> findBootstrapClass(String name) {
+ try {
+ return singleton().resolve(name, null);
+ } catch (ClassNotFoundException e) {
+ throw VMError.shouldNotReachHere("The boot class loader shouldn't throw ClassNotFoundException", e);
+ }
+ }
+
+ public static Class> findLoadedClass(String name, ClassLoader loader) {
+ if (throwMissingRegistrationErrors() && RuntimeClassLoading.followReflectionConfiguration() && !ClassForNameSupport.isRegisteredClass(name)) {
+ MissingReflectionRegistrationUtils.forClass(name);
+ return null;
+ }
+ ByteSequence typeBytes = ByteSequence.createTypeFromName(name);
+ Symbol type = singleton().getTypes().lookupValidType(typeBytes);
+ Class> result = null;
+ if (type != null) {
+ result = singleton().getRegistry(loader).findLoadedClass(type);
+ }
+ return result;
+ }
+
+ public static ParsingContext getParsingContext() {
+ assert RuntimeClassLoading.isSupported();
+ return singleton();
+ }
+
+ public static Class> forName(String name, ClassLoader loader) throws ClassNotFoundException {
+ return singleton().resolveOrThrowException(name, loader);
+ }
+
+ private Class> resolveOrThrowException(String name, ClassLoader loader) throws ClassNotFoundException {
+ Class> clazz = resolve(name, loader);
+ if (clazz == null) {
+ throw new ClassNotFoundException(name);
+ }
+ return clazz;
+ }
+
+ /**
+ * This resolves the given class name. Expects dot-names.
+ *
+ * It may or may not throw a {@link ClassNotFoundException} if the class is not found: the boot
+ * class loader returns null while user-defined class loaders throw
+ * {@link ClassNotFoundException}. This approach avoids unnecessary exceptions during class
+ * loader delegation.
+ */
+ private Class> resolve(String name, ClassLoader loader) throws ClassNotFoundException {
+ if (RuntimeClassLoading.followReflectionConfiguration()) {
+ if (throwMissingRegistrationErrors() && !ClassForNameSupport.isRegisteredClass(name)) {
+ MissingReflectionRegistrationUtils.forClass(name);
+ if (loader == null) {
+ return null;
+ }
+ throw new ClassNotFoundException(name);
+ }
+ if (!RuntimeClassLoading.isSupported()) {
+ Throwable savedException = ClassForNameSupport.getSavedException(name);
+ if (savedException != null) {
+ if (savedException instanceof Error error) {
+ throw error;
+ } else if (savedException instanceof ClassNotFoundException cnfe) {
+ throw cnfe;
+ }
+ throw VMError.shouldNotReachHere("Unexpected exception type", savedException);
+ }
+ }
+ }
+ int arrayDimensions = 0;
+ while (arrayDimensions < name.length() && name.charAt(arrayDimensions) == '[') {
+ arrayDimensions++;
+ }
+ if (arrayDimensions == name.length()) {
+ throw new ClassNotFoundException(name);
+ }
+ Class> elementalResult;
+ if (arrayDimensions > 0) {
+ /*
+ * We know that the array name was registered for reflection. The elemental type might
+ * not be, so we have to ignore registration during its lookup.
+ */
+ ClassLoadingSupport classLoadingSupport = ImageSingletons.lookup(ClassLoadingSupport.class);
+ classLoadingSupport.startIgnoreReflectionConfigurationScope();
+ try {
+ elementalResult = resolveElementalType(name, arrayDimensions, loader);
+ } finally {
+ classLoadingSupport.endIgnoreReflectionConfigurationScope();
+ }
+ } else {
+ elementalResult = resolveInstanceType(name, loader);
+ }
+ if (elementalResult == null) {
+ if (loader == null) {
+ return null;
+ }
+ throw new ClassNotFoundException(name);
+ }
+ if (arrayDimensions > 0) {
+ Class> result = getArrayClass(name, elementalResult, arrayDimensions);
+ if (result == null && loader != null) {
+ throw new ClassNotFoundException(name);
+ }
+ return result;
+ }
+ return elementalResult;
+ }
+
+ private Class> resolveElementalType(String fullName, int arrayDimensions, ClassLoader loader) throws ClassNotFoundException {
+ if (fullName.length() == arrayDimensions + 1) {
+ return switch (fullName.charAt(arrayDimensions)) {
+ case 'Z' -> boolean.class;
+ case 'B' -> byte.class;
+ case 'C' -> char.class;
+ case 'S' -> short.class;
+ case 'I' -> int.class;
+ case 'F' -> float.class;
+ case 'J' -> long.class;
+ case 'D' -> double.class;
+ default -> null; // also 'V'
+ };
+ }
+ assert fullName.length() > arrayDimensions;
+ ByteSequence elementalType = ByteSequence.createReplacingDot(fullName, arrayDimensions);
+ return resolveInstanceType(loader, elementalType);
+ }
+
+ private Class> resolveInstanceType(String name, ClassLoader loader) throws ClassNotFoundException {
+ ByteSequence elementalType = ByteSequence.createTypeFromName(name);
+ return resolveInstanceType(loader, elementalType);
+ }
+
+ private Class> resolveInstanceType(ClassLoader loader, ByteSequence elementalType) throws ClassNotFoundException {
+ Symbol type = getTypes().getOrCreateValidType(elementalType);
+ if (type == null) {
+ return null;
+ }
+ return getRegistry(loader).loadClass(type);
+ }
+
+ private static Class> getArrayClass(String name, Class> elementalResult, int arrayDimensions) {
+ DynamicHub hub = SubstrateUtil.cast(elementalResult, DynamicHub.class);
+ int remainingDims = arrayDimensions;
+ while (remainingDims > 0) {
+ if (hub.getArrayHub() == null) {
+ if (RuntimeClassLoading.isSupported()) {
+ RuntimeClassLoading.getOrCreateArrayHub(hub);
+ } else {
+ if (throwMissingRegistrationErrors()) {
+ MissingReflectionRegistrationUtils.forClass(name);
+ }
+ return null;
+ }
+ }
+ remainingDims--;
+ hub = hub.getArrayHub();
+ }
+ return SubstrateUtil.cast(hub, Class.class);
+ }
+
+ public static Class> defineClass(ClassLoader loader, String name, byte[] b, int off, int len, ClassDefinitionInfo info) {
+ // name is a "binary name": `foo.Bar$1`
+ assert RuntimeClassLoading.isSupported();
+ if (RuntimeClassLoading.followReflectionConfiguration() && throwMissingRegistrationErrors() && !ClassForNameSupport.isRegisteredClass(name)) {
+ MissingReflectionRegistrationUtils.forClass(name);
+ // The defineClass path usually can't throw ClassNotFoundException
+ throw sneakyThrow(new ClassNotFoundException(name));
+ }
+ ByteSequence typeBytes = ByteSequence.createTypeFromName(name);
+ Symbol type = singleton().getTypes().getOrCreateValidType(typeBytes);
+ if (type == null) {
+ throw new NoClassDefFoundError(name);
+ }
+ AbstractRuntimeClassRegistry registry = (AbstractRuntimeClassRegistry) singleton().getRegistry(loader);
+ return registry.defineClass(type, b, off, len, info);
+ }
+
+ @SuppressWarnings("unchecked")
+ private static RuntimeException sneakyThrow(Throwable ex) throws T {
+ throw (T) ex;
+ }
+
+ public static String loaderNameAndId(ClassLoader loader) {
+ if (loader == null) {
+ return "bootstrap";
+ }
+ return SubstrateUtil.cast(loader, Target_java_lang_ClassLoader.class).nameAndId();
+ }
+
+ private AbstractClassRegistry getRegistry(ClassLoader loader) {
+ if (loader == null) {
+ return bootRegistry;
+ }
+ Target_java_lang_ClassLoader svmLoader = SubstrateUtil.cast(loader, Target_java_lang_ClassLoader.class);
+ AbstractClassRegistry registry = svmLoader.classRegistry;
+ if (registry == null) {
+ synchronized (loader) {
+ registry = svmLoader.classRegistry;
+ if (registry == null) {
+ if (RuntimeClassLoading.isSupported()) {
+ registry = new UserDefinedClassRegistry(loader);
+ } else {
+ registry = new AOTClassRegistry(loader);
+ }
+ svmLoader.classRegistry = registry;
+ }
+ }
+ }
+ return registry;
+ }
+
+ @Platforms(Platform.HOSTED_ONLY.class)
+ public static void addAOTClass(ClassLoader loader, Class> cls) {
+ singleton().getBuildTimeRegistry(loader).addAOTType(cls);
+ }
+
+ @Platforms(Platform.HOSTED_ONLY.class)
+ private AbstractClassRegistry getBuildTimeRegistry(ClassLoader loader) {
+ if (loader == null) {
+ return bootRegistry;
+ }
+ return this.buildTimeRegistries.computeIfAbsent(loader, l -> {
+ AbstractClassRegistry newRegistry;
+ if (RuntimeClassLoading.isSupported()) {
+ newRegistry = new UserDefinedClassRegistry(l);
+ } else {
+ newRegistry = new AOTClassRegistry(l);
+ }
+ return newRegistry;
+ });
+ }
+
+ public static class ClassRegistryComputer implements FieldValueTransformer {
+ @Override
+ public Object transform(Object receiver, Object originalValue) {
+ assert receiver != null;
+ return ClassRegistries.singleton().getBuildTimeRegistry((ClassLoader) receiver);
+ }
+ }
+
+ @Override
+ public JavaVersion getJavaVersion() {
+ return JavaVersion.HOST_VERSION;
+ }
+
+ @Override
+ public boolean isStrictJavaCompliance() {
+ return false;
+ }
+
+ @Override
+ public TimerCollection getTimers() {
+ return timers;
+ }
+
+ @Override
+ public boolean isPreviewEnabled() {
+ return PreviewFeatures.isEnabled();
+ }
+
+ final Logger logger = new Logger() {
+ // Checkstyle: Allow raw info or warning printing - begin
+ @Override
+ public void log(String message) {
+ Log.log().string("Warning: ").string(message).newline();
+ }
+
+ @Override
+ public void log(Supplier messageSupplier) {
+ Log.log().string("Warning: ").string(messageSupplier.get()).newline();
+ }
+
+ @Override
+ public void log(String message, Throwable throwable) {
+ Log.log().string("Warning: ").string(message).newline();
+ Log.log().exception(throwable);
+ }
+ // Checkstyle: Allow raw info or warning printing - end
+ };
+
+ @Override
+ public Logger getLogger() {
+ return logger;
+ }
+
+ @Override
+ public Symbol getOrCreateName(ByteSequence byteSequence) {
+ return ClassRegistries.singleton().names.getOrCreate(byteSequence);
+ }
+
+ @Override
+ public Symbol getOrCreateTypeFromName(ByteSequence byteSequence) {
+ return ClassRegistries.singleton().getTypes().getOrCreateValidType(TypeSymbols.nameToType(byteSequence));
+ }
+
+ @Override
+ public Symbol extends ModifiedUTF8> getOrCreateUtf8(ByteSequence byteSequence) {
+ // Note: all symbols are strong for now
+ return ClassRegistries.singleton().utf8.getOrCreateValidUtf8(byteSequence, true);
+ }
+}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/registry/UserDefinedClassRegistry.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/registry/UserDefinedClassRegistry.java
new file mode 100644
index 000000000000..4fce34cc4d64
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/registry/UserDefinedClassRegistry.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2024, 2024, 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.hub.registry;
+
+import java.util.Objects;
+
+import com.oracle.svm.espresso.classfile.descriptors.Symbol;
+import com.oracle.svm.espresso.classfile.descriptors.Type;
+
+import jdk.internal.loader.ClassLoaders;
+
+/**
+ * This class registry corresponds to any non-null class loader when runtime class loading is
+ * supported.
+ *
+ * Note that "user-defined" should be understood from the JVM's point of view: it is any type of
+ * classloader that extends {@link ClassLoader}, including those that are part of the standard
+ * library.
+ */
+public final class UserDefinedClassRegistry extends AbstractRuntimeClassRegistry {
+ private final ClassLoader loader;
+ private final boolean isPlatform;
+
+ UserDefinedClassRegistry(ClassLoader loader) {
+ this.loader = Objects.requireNonNull(loader);
+ this.isPlatform = loader == ClassLoaders.platformClassLoader();
+ }
+
+ @Override
+ public Class> doLoadClass(Symbol type) throws ClassNotFoundException {
+ assert type.byteAt(0) == 'L' && type.byteAt(type.length() - 1) == ';' : type;
+ String name = type.subSequence(1, type.length() - 1).toString().replace('/', '.');
+ return loader.loadClass(name);
+ }
+
+ @Override
+ protected boolean loaderIsBootOrPlatform() {
+ return isPlatform;
+ }
+
+ @Override
+ public ClassLoader getClassLoader() {
+ return loader;
+ }
+}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaLangSubstitutions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaLangSubstitutions.java
index a9b3343b74c9..b5d4988007ec 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaLangSubstitutions.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaLangSubstitutions.java
@@ -66,6 +66,7 @@
import com.oracle.svm.core.fieldvaluetransformer.FieldValueTransformerWithAvailability;
import com.oracle.svm.core.hub.ClassForNameSupport;
import com.oracle.svm.core.hub.DynamicHub;
+import com.oracle.svm.core.hub.registry.ClassRegistries;
import com.oracle.svm.core.monitor.MonitorSupport;
import com.oracle.svm.core.snippets.SubstrateForeignCallTarget;
import com.oracle.svm.core.thread.JavaThreads;
@@ -627,6 +628,7 @@ static Package getDefinedPackage(String name) {
}
@Substitute
+ @TargetElement(onlyWith = ClassForNameSupport.IgnoresClassLoader.class)
public static Stream packages() {
Target_jdk_internal_loader_BuiltinClassLoader bootClassLoader = Target_jdk_internal_loader_ClassLoaders.bootLoader();
Target_java_lang_ClassLoader systemClassLoader = SubstrateUtil.cast(bootClassLoader, Target_java_lang_ClassLoader.class);
@@ -634,15 +636,27 @@ public static Stream packages() {
}
@Delete("only used by #packages()")
- private static native String[] getSystemPackageNames();
+ @TargetElement(name = "getSystemPackageNames", onlyWith = ClassForNameSupport.IgnoresClassLoader.class)
+ private static native String[] getSystemPackageNamesDeleted();
@Substitute
+ @TargetElement(onlyWith = ClassForNameSupport.RespectsClassLoader.class)
+ @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+16/src/java.base/share/native/libjava/BootLoader.c#L37-L41")
+ @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+16/src/hotspot/share/prims/jvm.cpp#L3003-L3007")
+ @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+16/src/hotspot/share/classfile/classLoader.cpp#L907-L924")
+ private static String[] getSystemPackageNames() {
+ return ClassRegistries.getSystemPackageNames();
+ }
+
+ @Substitute
+ @TargetElement(onlyWith = ClassForNameSupport.IgnoresClassLoader.class)
private static Class> loadClassOrNull(String name) {
return ClassForNameSupport.forNameOrNull(name, null);
}
@SuppressWarnings("unused")
@Substitute
+ @TargetElement(onlyWith = ClassForNameSupport.IgnoresClassLoader.class)
private static Class> loadClass(Module module, String name) {
/* The module system is not supported for now, therefore the module parameter is ignored. */
return ClassForNameSupport.forNameOrNull(name, null);
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SunMiscSubstitutions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SunMiscSubstitutions.java
index 78b18292e05d..9a2a6d699a6e 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SunMiscSubstitutions.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SunMiscSubstitutions.java
@@ -43,11 +43,11 @@
import com.oracle.svm.core.heap.ReferenceAccess;
import com.oracle.svm.core.hub.DynamicHub;
import com.oracle.svm.core.hub.LayoutEncoding;
-import com.oracle.svm.core.hub.PredefinedClassesSupport;
+import com.oracle.svm.core.hub.RuntimeClassLoading;
import com.oracle.svm.core.memory.NativeMemory;
import com.oracle.svm.core.nmt.NmtCategory;
import com.oracle.svm.core.os.VirtualMemoryProvider;
-import com.oracle.svm.core.util.VMError;
+import com.oracle.svm.core.util.BasedOnJDKFile;
import jdk.graal.compiler.nodes.extended.MembarNode;
import jdk.graal.compiler.word.Word;
@@ -138,11 +138,6 @@ public void ensureClassInitialized(Class> c) {
DynamicHub.fromClass(c).ensureInitialized();
}
- @Substitute
- private Class> defineClass(String name, byte[] b, int off, int len, ClassLoader loader, ProtectionDomain protectionDomain) {
- return PredefinedClassesSupport.loadClass(loader, name, b, off, len, protectionDomain);
- }
-
@Substitute
private int getLoadAverage0(double[] loadavg, int nelems) {
/* Adapted from `Unsafe_GetLoadAverage0` in `src/hotspot/share/prims/unsafe.cpp`. */
@@ -192,9 +187,12 @@ public Object getUncompressedObject(long address) {
private native int arrayIndexScale0(Class> arrayClass);
@Substitute
+ @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+16/src/hotspot/share/prims/unsafe.cpp#L708-L712")
+ @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+16/src/hotspot/share/prims/unsafe.cpp#L649-L705")
@SuppressWarnings("unused")
private Class> defineClass0(String name, byte[] b, int off, int len, ClassLoader loader, ProtectionDomain protectionDomain) {
- throw VMError.unsupportedFeature("Target_Unsafe_Core.defineClass0(String, byte[], int, int, ClassLoader, ProtectionDomain)");
+ // Note that if name is not null, it is a binary name in either / or .-form
+ return RuntimeClassLoading.defineClass(loader, name, b, off, len, new RuntimeClassLoading.ClassDefinitionInfo(protectionDomain));
}
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_ClassLoader.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_ClassLoader.java
index 875126ca0b04..9a9e34a666da 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_ClassLoader.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_ClassLoader.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2018, 2025, 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
@@ -35,24 +35,52 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream;
+import org.graalvm.nativeimage.hosted.FieldValueTransformer;
+
import com.oracle.svm.core.SubstrateUtil;
import com.oracle.svm.core.annotate.Alias;
import com.oracle.svm.core.annotate.Delete;
+import com.oracle.svm.core.annotate.Inject;
import com.oracle.svm.core.annotate.RecomputeFieldValue;
import com.oracle.svm.core.annotate.RecomputeFieldValue.Kind;
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.hub.ClassForNameSupport;
import com.oracle.svm.core.hub.PredefinedClassesSupport;
import com.oracle.svm.core.hub.RuntimeClassLoading;
-import com.oracle.svm.core.option.SubstrateOptionsParser;
+import com.oracle.svm.core.hub.RuntimeClassLoading.ClassDefinitionInfo;
+import com.oracle.svm.core.hub.registry.AbstractClassRegistry;
+import com.oracle.svm.core.hub.registry.ClassRegistries;
+import com.oracle.svm.core.util.BasedOnJDKFile;
import com.oracle.svm.core.util.VMError;
+import com.oracle.svm.util.ReflectionUtil;
import jdk.graal.compiler.java.LambdaUtils;
import jdk.graal.compiler.util.Digest;
import jdk.internal.loader.ClassLoaderValue;
import jdk.internal.loader.NativeLibrary;
+/**
+ * Note that we currently disable parallel class loading at image run time because the
+ * {@linkplain RuntimeClassLoading runtime class loading} implementation doesn't support parallel
+ * class loading (GR-62338). In particular this means:
+ *
+ * - {@code ClassLoader.parallelLockMap} is reset to null for class loaders that are part of the
+ * image heap.
+ * - {@code ClassLoader.assertionLock} is reset to "this" for class loaders that are part of the
+ * image heap.
+ * - {@code ClassLoader.ParallelLoaders.loaderTypes} is reset to an empty set.
+ * - Class loaders created at runtime will also have parallel class loading disabled since a class
+ * loader can only be parallel if their superclass is parallel.
+ *
+ *
+ * Depending on the value of the {@code ClassForNameRespectsClassLoader} flag, methods that lookup
+ * classes by name (such as {@code ClassLoader#findLoadedClass},
+ * {@code ClassLoader#findBootstrapClass}, or {@code Class#forName}) will either ignore the class
+ * loader argument and find classes in a global namespace or respect it and look names up in
+ * per-class loader {@linkplain ClassRegistries registries}.
+ */
@TargetClass(ClassLoader.class)
@SuppressWarnings("static-method")
public final class Target_java_lang_ClassLoader {
@@ -69,12 +97,19 @@ public final class Target_java_lang_ClassLoader {
@Alias @RecomputeFieldValue(kind = Kind.Reset)//
private ArrayList> classes;
- @Alias @RecomputeFieldValue(kind = Kind.NewInstanceWhenNotNull, declClass = ConcurrentHashMap.class)//
- private ConcurrentHashMap parallelLockMap;
+ @Alias @RecomputeFieldValue(kind = Kind.Reset, isFinal = true)// GR-62338
+ public ConcurrentHashMap parallelLockMap;
+
+ @Alias @RecomputeFieldValue(kind = Kind.Custom, declClass = AssertionLockComputer.class, isFinal = true) // GR-62338
+ private Object assertionLock;
@Alias //
private static ClassLoader scl;
+ @Inject @RecomputeFieldValue(kind = Kind.Custom, declClass = ClassRegistries.ClassRegistryComputer.class)//
+ @TargetElement(onlyWith = ClassForNameSupport.RespectsClassLoader.class)//
+ public volatile AbstractClassRegistry classRegistry;
+
@Substitute
public static ClassLoader getSystemClassLoader() {
VMError.guarantee(scl != null);
@@ -110,10 +145,8 @@ static NativeLibrary loadLibrary(Class> fromClass, File file) {
return null;
}
- @Substitute
- private Class> loadClass(String name) throws ClassNotFoundException {
- return loadClass(name, false);
- }
+ @Alias
+ public native String nameAndId();
@Alias
protected native Class> findLoadedClass(String name);
@@ -122,6 +155,7 @@ private Class> loadClass(String name) throws ClassNotFoundException {
protected native Class> findClass(String name);
@Substitute
+ @TargetElement(onlyWith = ClassForNameSupport.IgnoresClassLoader.class)
@SuppressWarnings("unused")
Class> loadClass(String name, boolean resolve) throws ClassNotFoundException {
Class> clazz = findLoadedClass(name);
@@ -133,7 +167,7 @@ Class> loadClass(String name, boolean resolve) throws ClassNotFoundException {
}
if (parent != null) {
try {
- clazz = parent.loadClass(name);
+ clazz = parent.loadClass(name, resolve);
if (clazz != null) {
return clazz;
}
@@ -146,7 +180,8 @@ Class> loadClass(String name, boolean resolve) throws ClassNotFoundException {
// JDK-8265605
@Delete
- static native Class> findBootstrapClassOrNull(String name);
+ @TargetElement(name = "findBootstrapClassOrNull", onlyWith = ClassForNameSupport.IgnoresClassLoader.class)
+ static native Class> findBootstrapClassOrNullDeleted(String name);
@Substitute //
@SuppressWarnings("unused")
@@ -160,9 +195,20 @@ Class> loadClass(Module module, String name) {
}
@Substitute //
+ @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+16/src/java.base/share/native/libjava/ClassLoader.c#L320-L329")
+ @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+16/src/hotspot/share/prims/jvm.cpp#L1056-L1096")
@SuppressWarnings({"unused"}) //
private Class> findLoadedClass0(String name) {
- return ClassForNameSupport.forNameOrNull(name, SubstrateUtil.cast(this, ClassLoader.class));
+ /*
+ * HotSpot supports both dot- and slash-names here as well as array types The only caller
+ * (findLoadedClass) errors out on slash-names and array types so we assume dot-names
+ */
+ assert !name.contains("/") && !name.startsWith("[");
+ if (ClassForNameSupport.respectClassLoader()) {
+ return ClassRegistries.findLoadedClass(name, SubstrateUtil.cast(this, ClassLoader.class));
+ } else {
+ return ClassForNameSupport.forNameOrNull(name, SubstrateUtil.cast(this, ClassLoader.class));
+ }
}
/**
@@ -240,81 +286,137 @@ private void clearAssertionStatus() {
@Substitute
@SuppressWarnings({"unused", "static-method"})
- Class> defineClass(byte[] b, int off, int len) throws ClassFormatError {
- return defineClass(null, b, off, len);
+ @TargetElement(onlyWith = ClassForNameSupport.IgnoresClassLoader.class)
+ private Class> defineClass(String name, byte[] b, int off, int len, ProtectionDomain protectionDomain) {
+ return RuntimeClassLoading.defineClass(SubstrateUtil.cast(this, ClassLoader.class), name, b, off, len, new ClassDefinitionInfo(protectionDomain));
}
@Substitute
@SuppressWarnings({"unused", "static-method"})
- Class> defineClass(String name, byte[] b, int off, int len) throws ClassFormatError {
- return defineClass(name, b, off, len, null);
+ @TargetElement(onlyWith = ClassForNameSupport.IgnoresClassLoader.class)
+ private Class> defineClass(String name, java.nio.ByteBuffer b, ProtectionDomain protectionDomain) {
+ return defineClass2(SubstrateUtil.cast(this, ClassLoader.class), name, b, b.position(), b.remaining(), protectionDomain, null);
}
+ @Delete
+ @TargetElement(name = "defineClass1", onlyWith = ClassForNameSupport.IgnoresClassLoader.class)
+ @SuppressWarnings("unused")
+ private static native Class> defineClass1Deleted(ClassLoader loader, String name, byte[] b, int off, int len, ProtectionDomain pd, String source);
+
+ @Delete
+ @TargetElement(name = "defineClass2", onlyWith = ClassForNameSupport.IgnoresClassLoader.class)
+ private static native Class> defineClass2Deleted(ClassLoader loader, String name, java.nio.ByteBuffer b, int off, int len, ProtectionDomain pd, String source);
+
@Substitute
- @SuppressWarnings({"unused", "static-method"})
- private Class> defineClass(String name, byte[] b, int off, int len, ProtectionDomain protectionDomain) {
- return ClassLoaderHelper.defineClass(SubstrateUtil.cast(this, ClassLoader.class), name, b, off, len, protectionDomain);
+ @TargetElement(onlyWith = ClassForNameSupport.RespectsClassLoader.class)
+ @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+16/src/java.base/share/native/libjava/ClassLoader.c#L71-L151")
+ @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+16/src/hotspot/share/prims/jvm.cpp#L1051-L1054")
+ @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+16/src/hotspot/share/prims/jvm.cpp#L857-L896")
+ private static Class> defineClass1(ClassLoader loader, String name, byte[] b, int off, int len, ProtectionDomain pd, @SuppressWarnings("unused") String source) {
+ // Note that if name is not null, it is a binary name in either / or .-form
+ return RuntimeClassLoading.defineClass(loader, name, b, off, len, new ClassDefinitionInfo(pd));
}
@Substitute
- @SuppressWarnings({"unused", "static-method"})
- private Class> defineClass(String name, java.nio.ByteBuffer b, ProtectionDomain protectionDomain) {
+ @TargetElement(onlyWith = ClassForNameSupport.RespectsClassLoader.class)
+ @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+16/src/java.base/share/native/libjava/ClassLoader.c#L153-L213")
+ @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+16/src/hotspot/share/prims/jvm.cpp#L1051-L1054")
+ @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+16/src/hotspot/share/prims/jvm.cpp#L857-L896")
+ private static Class> defineClass2(ClassLoader loader, String name, java.nio.ByteBuffer b, int off, int len, ProtectionDomain pd, @SuppressWarnings("unused") String source) {
+ // Note that if name is not null, it is a binary name in either / or .-form
// only bother extracting the bytes if it has a chance to work
if (PredefinedClassesSupport.hasBytecodeClasses() || RuntimeClassLoading.isSupported()) {
byte[] array;
- int off;
- int len = b.remaining();
+ int offset;
if (b.hasArray()) {
array = b.array();
- off = b.position() + b.arrayOffset();
+ offset = off + b.arrayOffset();
} else {
array = new byte[len];
- b.get(array);
- off = 0;
+ b.get(off, array);
+ offset = 0;
}
- return ClassLoaderHelper.defineClass(SubstrateUtil.cast(this, ClassLoader.class), name, array, off, len, null);
+ return RuntimeClassLoading.defineClass(loader, name, array, offset, len, new ClassDefinitionInfo(pd));
}
- throw PredefinedClassesSupport.throwNoBytecodeClasses(name);
+ throw RuntimeClassLoading.throwNoBytecodeClasses(name);
}
@Substitute
- protected void resolveClass(@SuppressWarnings("unused") Class> c) {
- // All classes are already linked at runtime.
- }
-
- @Delete
- @SuppressWarnings("unused")
- private static native Class> defineClass1(ClassLoader loader, String name, byte[] b, int off, int len, ProtectionDomain pd, String source);
-
- @Delete
- private static native Class> defineClass2(ClassLoader loader, String name, java.nio.ByteBuffer b, int off, int len, ProtectionDomain pd, String source);
-
- @Substitute
- @SuppressWarnings("unused")
- private static Class> defineClass0(ClassLoader loader, Class> lookup, String name, byte[] b, int off, int len, ProtectionDomain pd, boolean initialize, int flags, Object classData) {
+ @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+16/src/java.base/share/native/libjava/ClassLoader.c#L215-L283")
+ @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+16/src/hotspot/share/prims/jvm.cpp#L1039-L1049")
+ @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+16/src/hotspot/share/prims/jvm.cpp#L909-L1022")
+ private static Class> defineClass0(ClassLoader loader, Class> lookup, String name, byte[] b, int off, int len, ProtectionDomain pd,
+ @SuppressWarnings("unused") boolean initialize, int flags, Object classData) {
+ // Note that if name is not null, it is a binary name in either / or .-form
String actualName = name;
if (LambdaUtils.isLambdaClassName(name)) {
actualName += Digest.digest(b);
}
- return PredefinedClassesSupport.loadClass(loader, actualName.replace('/', '.'), b, off, b.length, null);
+ boolean isNestMate = (flags & ClassLoaderHelper.NESTMATE_CLASS) != 0;
+ boolean isHidden = (flags & ClassLoaderHelper.HIDDEN_CLASS) != 0;
+ boolean isStrong = (flags & ClassLoaderHelper.STRONG_LOADER_LINK) != 0;
+ boolean vmAnnotations = (flags & ClassLoaderHelper.ACCESS_VM_ANNOTATIONS) != 0;
+ Class> nest = null;
+ if (isNestMate) {
+ nest = lookup.getNestHost();
+ }
+ ClassDefinitionInfo info;
+ if (isHidden) {
+ info = new ClassDefinitionInfo(pd, nest, classData, isStrong, vmAnnotations);
+ } else {
+ if (classData != null) {
+ throw new IllegalArgumentException("Class data is only applicable for hidden classes");
+ }
+ if (isNestMate) {
+ throw new IllegalArgumentException("Dynamic nestmate is only applicable for hidden classes");
+ }
+ if (!isStrong) {
+ throw new IllegalArgumentException("An ordinary class must be strongly referenced by its defining loader");
+ }
+ if (vmAnnotations) {
+ throw new IllegalArgumentException("VM annotations only allowed for hidden classes");
+ }
+ if (flags != ClassLoaderHelper.STRONG_LOADER_LINK) {
+ throw new IllegalArgumentException(String.format("invalid flags 0x%x", flags));
+ }
+ info = new ClassDefinitionInfo(pd);
+ }
+ return RuntimeClassLoading.defineClass(loader, actualName, b, off, len, info);
}
// JDK-8265605
@Delete
- private static native Class> findBootstrapClass(String name);
+ @TargetElement(name = "findBootstrapClass", onlyWith = ClassForNameSupport.IgnoresClassLoader.class)
+ private static native Class> findBootstrapClassDeleted(String name);
+
+ @Substitute
+ @TargetElement(onlyWith = ClassForNameSupport.RespectsClassLoader.class)
+ @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+16/src/java.base/share/native/libjava/ClassLoader.c#L288-L328")
+ @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+16/src/hotspot/share/prims/jvm.cpp#L780-L800")
+ static Class> findBootstrapClass(String name) {
+ /*
+ * HotSpot supports both dot- and slash-names here as well as array types The only caller
+ * (findBootstrapClassOrNull) errors out on slash-names and array types.
+ */
+ assert !name.contains("/") && !name.startsWith("[");
+ return ClassRegistries.findBootstrapClass(name);
+ }
@Delete
private static native Target_java_lang_AssertionStatusDirectives retrieveDirectives();
}
final class ClassLoaderHelper {
- private static final String ERROR_MSG = SubstrateOptionsParser.commandArgument(RuntimeClassLoading.Options.SupportRuntimeClassLoading, "+") + " is not yet supported.";
-
- public static Class> defineClass(ClassLoader loader, String name, byte[] b, int off, int len, ProtectionDomain protectionDomain) {
- if (PredefinedClassesSupport.hasBytecodeClasses()) {
- return PredefinedClassesSupport.loadClass(loader, name, b, off, len, protectionDomain);
- }
- throw VMError.unimplemented(ERROR_MSG);
+ static final int NESTMATE_CLASS;
+ static final int HIDDEN_CLASS;
+ static final int STRONG_LOADER_LINK;
+ static final int ACCESS_VM_ANNOTATIONS;
+ static {
+ Class> constantsClass = ReflectionUtil.lookupClass("java.lang.invoke.MethodHandleNatives$Constants");
+ NESTMATE_CLASS = ReflectionUtil.readStaticField(constantsClass, "NESTMATE_CLASS");
+ HIDDEN_CLASS = ReflectionUtil.readStaticField(constantsClass, "HIDDEN_CLASS");
+ STRONG_LOADER_LINK = ReflectionUtil.readStaticField(constantsClass, "STRONG_LOADER_LINK");
+ ACCESS_VM_ANNOTATIONS = ReflectionUtil.readStaticField(constantsClass, "ACCESS_VM_ANNOTATIONS");
}
}
@@ -322,10 +424,22 @@ public static Class> defineClass(ClassLoader loader, String name, byte[] b, in
final class Target_java_lang_AssertionStatusDirectives {
}
+@TargetClass(className = "java.lang.NamedPackage") //
+final class Target_java_lang_NamedPackage {
+}
+
@TargetClass(className = "java.lang.ClassLoader", innerClass = "ParallelLoaders")
final class Target_java_lang_ClassLoader_ParallelLoaders {
@Alias //
- @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.FromAlias) //
+ @RecomputeFieldValue(kind = Kind.FromAlias) // GR-62338
private static Set> loaderTypes = Collections.newSetFromMap(new WeakHashMap<>());
}
+
+final class AssertionLockComputer implements FieldValueTransformer {
+ @Override
+ public Object transform(Object receiver, Object originalValue) {
+ assert receiver != null;
+ return receiver;
+ }
+}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_jdk_internal_loader_BuiltinClassLoader.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_jdk_internal_loader_BuiltinClassLoader.java
index 6b7bc68d4350..45fd4da1c477 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_jdk_internal_loader_BuiltinClassLoader.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_jdk_internal_loader_BuiltinClassLoader.java
@@ -38,6 +38,8 @@
import com.oracle.svm.core.annotate.RecomputeFieldValue;
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.hub.ClassForNameSupport;
@TargetClass(value = jdk.internal.loader.BuiltinClassLoader.class)
@SuppressWarnings({"unused", "static-method"})
@@ -47,11 +49,13 @@ final class Target_jdk_internal_loader_BuiltinClassLoader {
private Map moduleToReader;
@Substitute
+ @TargetElement(onlyWith = ClassForNameSupport.IgnoresClassLoader.class)
protected Class> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
@Substitute
+ @TargetElement(onlyWith = ClassForNameSupport.IgnoresClassLoader.class)
protected Class> loadClass(String name, boolean resolve) throws ClassNotFoundException {
Target_java_lang_ClassLoader self = SubstrateUtil.cast(this, Target_java_lang_ClassLoader.class);
Class> clazz = self.findLoadedClass(name);
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/proxy/ProxySubstitutions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/proxy/ProxySubstitutions.java
index 9459f3041918..c68388ce2080 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/proxy/ProxySubstitutions.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/proxy/ProxySubstitutions.java
@@ -34,6 +34,7 @@
import com.oracle.svm.core.annotate.TargetClass;
import com.oracle.svm.core.annotate.TargetElement;
import com.oracle.svm.core.hub.DynamicHub;
+import com.oracle.svm.core.util.VMError;
@TargetClass(java.lang.reflect.Proxy.class)
final class Target_java_lang_reflect_Proxy {
@@ -69,7 +70,12 @@ private static Constructor> getProxyConstructor(ClassLoader loader, Class>..
@Substitute
public static boolean isProxyClass(Class> cl) {
- return DynamicHub.fromClass(cl).isProxyClass();
+ DynamicHub dynamicHub = DynamicHub.fromClass(cl);
+ if (dynamicHub.isRuntimeLoaded()) {
+ // GR-63186
+ throw VMError.unimplemented("isProxyClass for dynamically loaded classes");
+ }
+ return dynamicHub.isProxyClass();
}
}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/functions/JNIFunctions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/functions/JNIFunctions.java
index d9f55dc7c5fe..7e318b2114bf 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/functions/JNIFunctions.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/functions/JNIFunctions.java
@@ -72,7 +72,8 @@
import com.oracle.svm.core.handles.PrimitiveArrayView;
import com.oracle.svm.core.heap.RestrictHeapAccess;
import com.oracle.svm.core.hub.DynamicHub;
-import com.oracle.svm.core.hub.PredefinedClassesSupport;
+import com.oracle.svm.core.hub.RuntimeClassLoading;
+import com.oracle.svm.core.hub.RuntimeClassLoading.ClassDefinitionInfo;
import com.oracle.svm.core.jdk.DirectByteBufferUtil;
import com.oracle.svm.core.jni.JNIObjectFieldAccess;
import com.oracle.svm.core.jni.JNIObjectHandles;
@@ -1110,13 +1111,10 @@ static JNIObjectHandle DefineClass(JNIEnvironment env, CCharPointer cname, JNIOb
throw new ClassFormatError();
}
String name = Utf8.utf8ToString(cname);
- if (name != null) { // inverse to HotSpot fixClassname():
- name = name.replace('/', '.');
- }
ClassLoader classLoader = JNIObjectHandles.getObject(loader);
byte[] data = new byte[bufLen];
CTypeConversion.asByteBuffer(buf, bufLen).get(data);
- Class> clazz = PredefinedClassesSupport.loadClass(classLoader, name, data, 0, data.length, null);
+ Class> clazz = RuntimeClassLoading.defineClass(classLoader, name, data, 0, data.length, ClassDefinitionInfo.EMPTY);
return JNIObjectHandles.createLocal(clazz);
}
diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ClassRegistryFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ClassRegistryFeature.java
new file mode 100644
index 000000000000..02ce91cf6ebe
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ClassRegistryFeature.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2025, 2025, 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.hosted;
+
+import java.lang.reflect.Field;
+
+import org.graalvm.nativeimage.ImageSingletons;
+import org.graalvm.nativeimage.impl.RuntimeClassInitializationSupport;
+
+import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature;
+import com.oracle.svm.core.feature.InternalFeature;
+import com.oracle.svm.core.fieldvaluetransformer.NewInstanceFieldValueTransformer;
+import com.oracle.svm.core.hub.ClassForNameSupport;
+import com.oracle.svm.core.hub.RuntimeClassLoading;
+import com.oracle.svm.core.hub.registry.ClassRegistries;
+
+@AutomaticallyRegisteredFeature
+public class ClassRegistryFeature implements InternalFeature {
+ @Override
+ public boolean isInConfiguration(IsInConfigurationAccess access) {
+ return ClassForNameSupport.respectClassLoader();
+ }
+
+ @Override
+ public void afterRegistration(AfterRegistrationAccess access) {
+ ImageSingletons.lookup(RuntimeClassInitializationSupport.class).initializeAtBuildTime("com.oracle.svm.espresso.classfile",
+ "Native Image classes needed for runtime class loading initialized at build time");
+ }
+
+ @Override
+ public void beforeAnalysis(BeforeAnalysisAccess a) {
+ FeatureImpl.BeforeAnalysisAccessImpl access = (FeatureImpl.BeforeAnalysisAccessImpl) a;
+ access.registerSubtypeReachabilityHandler((unused, cls) -> onTypeReachable(cls), Object.class);
+ /*
+ * This works around issues when analysis concurrently scans the readWriteLock in
+ * SymbolsImpl and might add a Thread to the image heap. It could be generalized (GR-62530).
+ */
+ Field readWriteLockField = access.findField("com.oracle.svm.espresso.classfile.descriptors.SymbolsImpl", "readWriteLock");
+ access.registerFieldValueTransformer(readWriteLockField, new NewInstanceFieldValueTransformer());
+ }
+
+ private static void onTypeReachable(Class> cls) {
+ if (cls.isArray() || cls.isHidden()) {
+ return;
+ }
+ if (RuntimeClassLoading.isSupported() || ClassForNameSupport.isCurrentLayerRegisteredClass(cls.getName())) {
+ ClassRegistries.addAOTClass(ClassLoaderFeature.getRuntimeClassLoader(cls.getClassLoader()), cls);
+ }
+ }
+}
diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ImageClassLoader.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ImageClassLoader.java
index c099c98e886e..93eba3a77e04 100644
--- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ImageClassLoader.java
+++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ImageClassLoader.java
@@ -261,6 +261,11 @@ public TypeResult> findClass(String name) {
/** Find class, return result encoding class or failure reason. */
public TypeResult> findClass(String name, boolean allowPrimitives) {
+ return findClass(name, allowPrimitives, getClassLoader());
+ }
+
+ /** Find class, return result encoding class or failure reason. */
+ public static TypeResult> findClass(String name, boolean allowPrimitives, ClassLoader loader) {
try {
if (allowPrimitives && name.indexOf('.') == -1) {
Class> primitive = forPrimitive(name);
@@ -268,7 +273,7 @@ public TypeResult> findClass(String name, boolean allowPrimitives) {
return TypeResult.forClass(primitive);
}
}
- return TypeResult.forClass(forName(name));
+ return TypeResult.forClass(forName(name, false, loader));
} catch (ClassNotFoundException | LinkageError ex) {
return TypeResult.forException(name, ex);
}
@@ -294,7 +299,11 @@ public Class> forName(String className) throws ClassNotFoundException {
}
public Class> forName(String className, boolean initialize) throws ClassNotFoundException {
- return Class.forName(className, initialize, getClassLoader());
+ return forName(className, initialize, getClassLoader());
+ }
+
+ public static Class> forName(String className, boolean initialize, ClassLoader loader) throws ClassNotFoundException {
+ return Class.forName(className, initialize, loader);
}
public Class> forName(String className, Module module) throws ClassNotFoundException {
diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/RuntimeMetadataEncoderImpl.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/RuntimeMetadataEncoderImpl.java
index 136e5cb39e36..f832425deda2 100644
--- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/RuntimeMetadataEncoderImpl.java
+++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/RuntimeMetadataEncoderImpl.java
@@ -445,7 +445,7 @@ public void addReflectionFieldMetadata(MetaAccessProvider metaAccess, HostedFiel
public void addReflectionExecutableMetadata(MetaAccessProvider metaAccess, HostedMethod hostedMethod, ConditionalRuntimeValue conditionalMethod, Object accessor) {
boolean isMethod = !hostedMethod.isConstructor();
HostedType declaringType = hostedMethod.getDeclaringClass();
- String name = isMethod ? hostedMethod.getName() : null;
+ String name = isMethod ? hostedMethod.getReflectionName() : null;
HostedType[] parameterTypes = getParameterTypes(hostedMethod);
/* Reflect method because substitution of Object.hashCode() is private */
Executable reflectMethod = conditionalMethod.getValueUnconditionally();
@@ -643,7 +643,7 @@ public void addReachableFieldMetadata(HostedField field) {
public void addReachableExecutableMetadata(HostedMethod executable) {
boolean isMethod = !executable.isConstructor();
HostedType declaringType = executable.getDeclaringClass();
- String name = isMethod ? executable.getName() : null;
+ String name = isMethod ? executable.getReflectionName() : null;
String[] parameterTypeNames = getParameterTypeNames(executable);
/* Fill encoders with the necessary values. */
diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedMethod.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedMethod.java
index 536b1832ebc8..42e570bfc9a5 100644
--- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedMethod.java
+++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedMethod.java
@@ -398,6 +398,15 @@ public String getName() {
return name;
}
+ /**
+ * Returns the original name of the method, without any suffix that might have been added by
+ * {@link HostedMethodNameFactory}.
+ */
+ public String getReflectionName() {
+ VMError.guarantee(this.isOriginalMethod());
+ return wrapped.getName();
+ }
+
@Override
public ResolvedSignature getSignature() {
return signature;
diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java
index ec098837c07a..94d4e8b56bd1 100644
--- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java
+++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java
@@ -88,6 +88,7 @@
import com.oracle.svm.core.hub.DynamicHub;
import com.oracle.svm.core.reflect.SubstrateAccessor;
import com.oracle.svm.core.util.VMError;
+import com.oracle.svm.hosted.ClassLoaderFeature;
import com.oracle.svm.hosted.ConditionalConfigurationRegistry;
import com.oracle.svm.hosted.FeatureImpl.BeforeAnalysisAccessImpl;
import com.oracle.svm.hosted.LinkAtBuildTimeSupport;
@@ -262,7 +263,7 @@ private void registerClass(ConfigurationCondition condition, Class> clazz, boo
}
if (allowForName) {
- classForNameSupport.registerClass(condition, clazz);
+ classForNameSupport.registerClass(condition, clazz, ClassLoaderFeature.getRuntimeClassLoader(clazz.getClassLoader()));
if (!MissingRegistrationUtils.throwMissingRegistrationErrors()) {
/*
@@ -927,7 +928,7 @@ private void registerTypesForGenericSignature(Type type, int dimension) {
/*
* Reflection signature parsing will try to instantiate classes via Class.forName().
*/
- classForNameSupport.registerClass(clazz);
+ classForNameSupport.registerClass(clazz, ClassLoaderFeature.getRuntimeClassLoader(clazz.getClassLoader()));
} else if (type instanceof TypeVariable>) {
/* Bounds are reified lazily. */
registerTypesForGenericSignature(queryGenericInfo(((TypeVariable>) type)::getBounds), dimension);
diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/ReflectionPlugins.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/ReflectionPlugins.java
index 4aaede7ed49a..d6ee18d202e7 100644
--- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/ReflectionPlugins.java
+++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/ReflectionPlugins.java
@@ -59,7 +59,9 @@
import com.oracle.svm.core.MissingRegistrationUtils;
import com.oracle.svm.core.ParsingReason;
import com.oracle.svm.core.annotate.Delete;
+import com.oracle.svm.core.hub.ClassForNameSupport;
import com.oracle.svm.core.hub.PredefinedClassesSupport;
+import com.oracle.svm.core.hub.RuntimeClassLoading;
import com.oracle.svm.core.jdk.StackTraceUtils;
import com.oracle.svm.core.option.HostedOptionKey;
import com.oracle.svm.core.util.VMError;
@@ -319,25 +321,48 @@ private void registerClassPlugins(InvocationPlugins plugins) {
}
Registration r = new Registration(plugins, Class.class);
- r.register(new RequiredInvocationPlugin("forName", String.class) {
+ r.register(new RequiredInlineOnlyInvocationPlugin("forName", String.class) {
@Override
public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Receiver receiver, ValueNode nameNode) {
- return processClassForName(b, targetMethod, nameNode, ConstantNode.forBoolean(true));
+ ClassLoader loader;
+ if (ClassForNameSupport.respectClassLoader()) {
+ Class> callerClass = OriginalClassProvider.getJavaClass(b.getMethod().getDeclaringClass());
+ loader = callerClass.getClassLoader();
+ } else {
+ loader = imageClassLoader.getClassLoader();
+ }
+ return processClassForName(b, targetMethod, nameNode, ConstantNode.forBoolean(true), loader);
}
});
- r.register(new RequiredInvocationPlugin("forName", String.class, boolean.class, ClassLoader.class) {
+ r.register(new RequiredInlineOnlyInvocationPlugin("forName", String.class, boolean.class, ClassLoader.class) {
@Override
public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Receiver receiver, ValueNode nameNode, ValueNode initializeNode, ValueNode classLoaderNode) {
- /*
- * For now, we ignore the ClassLoader parameter. We only intrinsify class names that
- * are found by the ImageClassLoader, i.e., the application class loader at run
- * time. We assume that every class loader used at run time delegates to the
- * application class loader.
- */
- return processClassForName(b, targetMethod, nameNode, initializeNode);
+ ClassLoader loader;
+ if (ClassForNameSupport.respectClassLoader()) {
+ if (!classLoaderNode.isJavaConstant()) {
+ return false;
+ }
+ loader = (ClassLoader) unboxObjectConstant(b, classLoaderNode.asJavaConstant());
+ if (loader == ClassLoader.getSystemClassLoader()) {
+ /*
+ * The run time's application class loader is the build time's image class
+ * loader.
+ */
+ loader = imageClassLoader.getClassLoader();
+ }
+ } else {
+ /*
+ * When we ignore the ClassLoader parameter, we only intrinsify class names that
+ * are found by the ImageClassLoader, i.e., the application class loader at run
+ * time. We assume that every class loader used at run time delegates to the
+ * application class loader.
+ */
+ loader = imageClassLoader.getClassLoader();
+ }
+ return processClassForName(b, targetMethod, nameNode, initializeNode, loader);
}
});
- r.register(new RequiredInvocationPlugin("getClassLoader", Receiver.class) {
+ r.register(new RequiredInlineOnlyInvocationPlugin("getClassLoader", Receiver.class) {
@Override
public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Receiver receiver) {
return processClassGetClassLoader(b, targetMethod, receiver);
@@ -381,7 +406,7 @@ private boolean processMethodHandlesLookup(GraphBuilderContext b, ResolvedJavaMe
* {@link ImageClassLoader} to look up the class name, not the class loader that loaded the
* native image generator.
*/
- private boolean processClassForName(GraphBuilderContext b, ResolvedJavaMethod targetMethod, ValueNode nameNode, ValueNode initializeNode) {
+ private boolean processClassForName(GraphBuilderContext b, ResolvedJavaMethod targetMethod, ValueNode nameNode, ValueNode initializeNode, ClassLoader loader) {
Object classNameValue = unbox(b, nameNode, JavaKind.Object);
Object initializeValue = unbox(b, initializeNode, JavaKind.Boolean);
@@ -392,8 +417,11 @@ private boolean processClassForName(GraphBuilderContext b, ResolvedJavaMethod ta
boolean initialize = (Boolean) initializeValue;
Supplier targetParameters = () -> className + ", " + initialize;
- TypeResult> typeResult = imageClassLoader.findClass(className, false);
+ TypeResult> typeResult = ImageClassLoader.findClass(className, false, loader);
if (!typeResult.isPresent()) {
+ if (RuntimeClassLoading.isSupported()) {
+ return false;
+ }
Throwable e = typeResult.getException();
return throwException(b, targetMethod, targetParameters, e.getClass(), e.getMessage());
}
diff --git a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/CremaFeature.java b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/CremaFeature.java
index 7e0e67043695..c9a15fc99046 100644
--- a/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/CremaFeature.java
+++ b/substratevm/src/com.oracle.svm.interpreter/src/com/oracle/svm/interpreter/CremaFeature.java
@@ -39,6 +39,7 @@
import com.oracle.graal.pointsto.meta.AnalysisUniverse;
import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature;
import com.oracle.svm.core.feature.InternalFeature;
+import com.oracle.svm.core.hub.ClassForNameSupport;
import com.oracle.svm.core.hub.CremaSupport;
import com.oracle.svm.core.hub.RuntimeClassLoading;
import com.oracle.svm.core.util.VMError;
@@ -71,6 +72,7 @@ public List> getRequiredFeatures() {
@Override
public void afterRegistration(AfterRegistrationAccess access) {
ImageSingletons.add(CremaSupport.class, new CremaSupportImpl());
+ VMError.guarantee(!RuntimeClassLoading.isSupported() || ClassForNameSupport.respectClassLoader());
}
private static boolean assertionsEnabled() {
diff --git a/sulong/ci/ci.jsonnet b/sulong/ci/ci.jsonnet
index c422f030782f..a288029127af 100644
--- a/sulong/ci/ci.jsonnet
+++ b/sulong/ci/ci.jsonnet
@@ -43,6 +43,7 @@ local sc = (import "ci_common/sulong-common.jsonnet");
"/tools/**",
# substratevm and its dependencies
"/substratevm/**",
+ "/espresso-shared/**",
# vm and its dependencies
"/vm/**",
] else []) + (if style then [