Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import com.oracle.svm.core.hub.DynamicHub;
import com.oracle.svm.core.hub.RuntimeClassLoading.ClassDefinitionInfo;
import com.oracle.svm.core.hub.crema.CremaSupport;
import com.oracle.svm.core.jdk.BootLoaderClassPathSupport;
import com.oracle.svm.espresso.classfile.descriptors.Symbol;
import com.oracle.svm.espresso.classfile.descriptors.Type;
import com.oracle.svm.espresso.classfile.descriptors.TypeSymbols;
Expand Down Expand Up @@ -104,27 +105,16 @@ private FileSystem getFileSystem() {
// synchronized until parallel class loading is implemented (GR-62338)
@Override
public synchronized Class<?> doLoadClass(Symbol<Type> 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);
if (pkg == null) {
return null;
}
try {
String moduleName = ClassRegistries.getBootModuleForPackage(pkg);
if (moduleName == null) {
return null;
}
FileSystem fileSystem = getFileSystem();
if (fileSystem == null) {
return null;
byte[] bytes = pkg == null ? null : loadFromJImage(type, pkg);
if (bytes == null) {
/* Preserve boot class path append semantics by looking there after the jimage. */
bytes = loadFromAppendedBootClassPathBytes(type);
}
var jrtTypePath = TypeSymbols.typeToName(type);
Path classPath = fileSystem.getPath("/modules/" + moduleName + "/" + jrtTypePath + ".class");
if (!Files.exists(classPath)) {
if (bytes == null) {
return null;
}
byte[] bytes = Files.readAllBytes(classPath);
Class<?> loaded = defineClass(type, bytes, 0, bytes.length, ClassDefinitionInfo.EMPTY);
CremaSupport.singleton().recordLoadingConstraint(type, DynamicHub.fromClass(loaded), null);
return loaded;
Expand All @@ -133,6 +123,27 @@ public synchronized Class<?> doLoadClass(Symbol<Type> type) {
}
}

private byte[] loadFromJImage(Symbol<Type> type, String pkg) throws IOException {
String moduleName = ClassRegistries.getBootModuleForPackage(pkg);
if (moduleName == null) {
return null;
}
FileSystem fileSystem = getFileSystem();
if (fileSystem == null) {
return null;
}
var typeName = TypeSymbols.typeToName(type);
Path classPath = fileSystem.getPath("/modules/" + moduleName + "/" + typeName + ".class");
if (!Files.exists(classPath)) {
return null;
}
return Files.readAllBytes(classPath);
}

private static byte[] loadFromAppendedBootClassPathBytes(Symbol<Type> type) throws IOException {
return BootLoaderClassPathSupport.getResourceBytes(TypeSymbols.typeToName(type) + ".class");
}

private static String packageFromType(Symbol<Type> type) {
int lastSlash = type.lastIndexOf((byte) '/');
if (lastSlash == -1) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright (c) 2026, 2026, 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 java.io.IOException;

/// Accesses the runtime boot loader class path exposed via `ClassLoaders.bootLoader().ucp`.
/// This is used for resources and classes supplied with `-Xbootclasspath/a:` in
/// configurations where the boot loader is initialized at run time.
public final class BootLoaderClassPathSupport {
private BootLoaderClassPathSupport() {
}

/// Looks up a resource on the boot loader's runtime class path and returns its bytes.
public static byte[] getResourceBytes(String resourceName) throws IOException {
Target_jdk_internal_loader_BuiltinClassLoader bootLoader = Target_jdk_internal_loader_ClassLoaders.bootLoader();
if (bootLoader == null || bootLoader.ucp == null) {
return null;
}
Target_jdk_internal_loader_Resource resource = bootLoader.ucp.getResource(resourceName);
if (resource == null) {
return null;
}
return resource.getBytes();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.WeakHashMap;

Expand All @@ -41,6 +42,15 @@ final class Target_jdk_internal_loader_URLClassPath {

/* Reset fields that can store a Zip file via sun.misc.URLClassPath$JarLoader.jar. */

@Alias
public native URL findResource(String name);

@Alias
public native Enumeration<URL> findResources(String name);

@Alias
public native Target_jdk_internal_loader_Resource getResource(String name);

@Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.NewInstance, declClass = ArrayList.class)//
private ArrayList<?> loaders;

Expand All @@ -52,6 +62,13 @@ final class Target_jdk_internal_loader_URLClassPath {
private ArrayList<URL> path;
}

@TargetClass(className = "jdk.internal.loader.Resource")
@SuppressWarnings({"unused", "static-method"})
final class Target_jdk_internal_loader_Resource {
@Alias
public native byte[] getBytes() throws java.io.IOException;
}

@TargetClass(URLClassLoader.class)
@SuppressWarnings({"unused", "static-method"})
final class Target_java_net_URLClassLoader {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
import java.lang.module.ModuleReader;
import java.lang.module.ModuleReference;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
Expand All @@ -50,6 +52,8 @@
@SuppressWarnings({"unused", "static-method"})
final class Target_jdk_internal_loader_BuiltinClassLoader {

@Alias Target_jdk_internal_loader_URLClassPath ucp;

@Alias @RecomputeFieldValue(kind = Kind.Custom, declClass = NewConcurrentHashMap.class) //
private Map<ModuleReference, ModuleReader> moduleToReader;

Expand Down Expand Up @@ -93,20 +97,51 @@ public InputStream findResourceAsStream(String mn, String name) throws IOExcepti

@Substitute
public URL findResource(String name) {
if (ClassRegistries.respectClassLoader() && this != Target_jdk_internal_loader_ClassLoaders.bootLoader()) {
/* Workaround for GR-73221 */
return null;
if (!ClassRegistries.respectClassLoader()) {
// Return only image resources
return ResourcesHelper.nameToResourceURL(name);
}

if (this == Target_jdk_internal_loader_ClassLoaders.bootLoader()) {
// Workaround for GR-73221: Only retrieve image resources for boot loader
URL url = ResourcesHelper.nameToResourceURL(name);
if (url != null) {
return url;
}
}
return ResourcesHelper.nameToResourceURL(name);

// TODO GR-73221: Also look into the modules defined to this loader

return ucp == null ? null : ucp.findResource(name);
}

@Substitute
public Enumeration<URL> findResources(String name) {
if (ClassRegistries.respectClassLoader() && this != Target_jdk_internal_loader_ClassLoaders.bootLoader()) {
/* Workaround for GR-73221 */
return null;
public Enumeration<URL> findResources(String name) throws IOException {
if (!ClassRegistries.respectClassLoader()) {
// Return only image resources
return ResourcesHelper.nameToResourceEnumerationURLs(name);
}

List<URL> resources = new ArrayList<>();

if (this == Target_jdk_internal_loader_ClassLoaders.bootLoader()) {
// Workaround for GR-73221: Only retrieve image resources for boot loader
resources.addAll(ResourcesHelper.nameToResourceListURLs(name));
}

// TODO GR-73221: Also look into the modules defined to this loader

if (ucp != null) {
Enumeration<URL> e = ucp.findResources(name);
if (resources.isEmpty()) {
return e;
}
while (e.hasMoreElements()) {
URL url = e.nextElement();
resources.add(url);
}
}
return ResourcesHelper.nameToResourceEnumerationURLs(name);
return resources.isEmpty() ? Collections.emptyEnumeration() : Collections.enumeration(resources);
}

@Substitute
Expand All @@ -120,24 +155,6 @@ private URL findResource(ModuleReference mref, String name) {
return ResourcesHelper.nameToResourceURL(module, name);
}

@Substitute
private URL findResourceOnClassPath(String name) {
if (ClassRegistries.respectClassLoader() && this != Target_jdk_internal_loader_ClassLoaders.bootLoader()) {
/* Workaround for GR-73221 */
return null;
}
return ResourcesHelper.nameToResourceURL(name);
}

@Substitute
private Enumeration<URL> findResourcesOnClassPath(String name) {
if (ClassRegistries.respectClassLoader() && this != Target_jdk_internal_loader_ClassLoaders.bootLoader()) {
/* Workaround for GR-73221 */
return null;
}
return ResourcesHelper.nameToResourceEnumerationURLs(name);
}

static final class NewConcurrentHashMap implements FieldValueTransformer {
@Override
public Object transform(Object receiver, Object originalValue) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,15 @@
*/
package com.oracle.svm.core.option;

import java.io.File;

import org.graalvm.collections.EconomicMap;
import org.graalvm.nativeimage.Platform;
import org.graalvm.nativeimage.Platforms;

import com.oracle.svm.core.SubstrateGCOptions;
import com.oracle.svm.core.SubstrateOptions;
import com.oracle.svm.core.jdk.SystemPropertiesSupport;
import com.oracle.svm.shared.option.SubstrateOptionsParser;

import jdk.graal.compiler.options.OptionKey;
Expand All @@ -55,6 +58,12 @@ public static boolean parse(String keyAndValue, EconomicMap<OptionKey<?>, Object
long value = parse(xFlag, keyAndValue);
xFlag.optionKey.update(values, value);
return true;
} else if (keyAndValue.startsWith("bootclasspath/a:")) {
// Set the property read by jdk.internal.loader.ClassLoaders.<clinit>
String value = keyAndValue.substring("bootclasspath/a:".length());
String currentValue = SystemPropertiesSupport.singleton().getInitialProperty("jdk.boot.class.path.append", false);
SystemPropertiesSupport.singleton().initializeProperty("jdk.boot.class.path.append", appendPath(currentValue, value));
return true;
}
return false;
}
Expand Down Expand Up @@ -93,6 +102,13 @@ private static long parse(XFlag xFlag, String keyAndValue) {
}
}

private static String appendPath(String paths, String toAppend) {
if (paths != null && !paths.isEmpty()) {
return toAppend != null && !toAppend.isEmpty() ? paths + File.pathSeparator + toAppend : paths;
}
return toAppend;
}

private static class XFlag {
final String name;
final RuntimeOptionKey<Long> optionKey;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,14 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BooleanSupplier;
import java.util.function.Function;
import java.util.function.Predicate;

import com.oracle.svm.core.BuilderUtil;
import org.graalvm.nativeimage.AnnotationAccess;
import org.graalvm.nativeimage.ImageSingletons;
import org.graalvm.nativeimage.Platform;
Expand All @@ -58,6 +58,7 @@
import com.oracle.graal.pointsto.infrastructure.SubstitutionProcessor;
import com.oracle.graal.pointsto.meta.AnalysisField;
import com.oracle.graal.pointsto.meta.AnalysisUniverse;
import com.oracle.svm.core.BuilderUtil;
import com.oracle.svm.core.SubstrateOptions;
import com.oracle.svm.core.annotate.Alias;
import com.oracle.svm.core.annotate.AnnotateOriginal;
Expand Down Expand Up @@ -99,6 +100,7 @@

import jdk.internal.reflect.Reflection;
import jdk.vm.ci.meta.MetaAccessProvider;
import jdk.vm.ci.meta.ModifiersProvider;
import jdk.vm.ci.meta.ResolvedJavaField;
import jdk.vm.ci.meta.ResolvedJavaMethod;
import jdk.vm.ci.meta.ResolvedJavaType;
Expand Down Expand Up @@ -1003,7 +1005,20 @@ public static boolean isIncluded(TargetElement targetElementAnnotation, Class<?>
return SVMHost.evaluateOnlyWith(targetElementAnnotation.onlyWith(), context.toString(), originalClass);
}

private static <T> void register(Map<T, T> substitutions, T annotated, T original, T target) {
/**
* Registers a mapping between annotated, original, and target objects in the provided
* substitutions map. Ensures that no conflicting substitutions are added, preserving
* consistency.
*
* @param substitutions The map where substitutions are maintained. Maps objects of type
* {@code T} to their substitutions.
* @param annotated The annotated object to be mapped to the target. May be null.
* @param original The original object to be mapped to the target or itself. May be null.
* @param target The target object to map annotated and original to.
* @param <T> A type that extends {@code ModifiersProvider}.
* @throws IllegalArgumentException If attempting to add a conflicting substitution.
*/
private static <T extends ModifiersProvider> void register(Map<T, T> substitutions, T annotated, T original, T target) {
if (annotated != null) {
guarantee(!substitutions.containsKey(annotated) || substitutions.get(annotated).equals(original) || substitutions.get(annotated).equals(target),
"Substitution: %s -> %s conflicts with previously registered: %s", annotated, target, substitutions.get(annotated));
Expand All @@ -1019,6 +1034,16 @@ private static <T> void register(Map<T, T> substitutions, T annotated, T origina
guarantee(!substitutions.containsKey(original) || substitutions.get(original).equals(original) || substitutions.get(original).equals(target),
"Substitution: %s -> %s conflicts with previously registered: %s", original, target, substitutions.get(original));
substitutions.put(original, target);
} else {
// GR-74443
ResolvedJavaMethod originalMethod = (ResolvedJavaMethod) original;
if (!original.isStatic() && !originalMethod.isConstructor() && !originalMethod.isPrivate()) {
ResolvedJavaMethod aliasMethod = (ResolvedJavaMethod) Objects.requireNonNull(annotated);
ResolvedJavaMethod targetMethod = (ResolvedJavaMethod) target;
UserError.abort("Cannot have both an alias and a substitution to a non-static, non-<init>, non-private method: %s -> %s",
aliasMethod.format("%H.%n(%p)"),
targetMethod.format("%H.%n(%p)"));
}
}
}
}
Expand Down