Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[GR-53985] Add classloader awareness for ClassForNameSupport. #9893

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -27,72 +27,105 @@
import static com.oracle.svm.core.MissingRegistrationUtils.throwMissingRegistrationErrors;

import java.util.Objects;
import java.util.function.Function;

import org.graalvm.collections.EconomicMap;
import org.graalvm.nativeimage.ImageSingletons;
import org.graalvm.nativeimage.Platform;
import org.graalvm.nativeimage.Platforms;
import org.graalvm.nativeimage.impl.ConfigurationCondition;

import com.oracle.svm.core.SubstrateUtil;
import com.oracle.svm.core.configure.ConditionalRuntimeValue;
import com.oracle.svm.core.configure.RuntimeConditionSet;
import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton;
import com.oracle.svm.core.heap.UnknownObjectField;
import com.oracle.svm.core.jdk.Target_java_lang_ClassLoader;
import com.oracle.svm.core.reflect.MissingReflectionRegistrationUtils;
import com.oracle.svm.core.util.ImageHeapMap;
import com.oracle.svm.core.util.VMError;

@AutomaticallyRegisteredImageSingleton
public final class ClassForNameSupport {

@Platforms(Platform.HOSTED_ONLY.class) //
private final Function<ClassLoader, ClassLoader> getRuntimeClassLoaderFunc;

@Platforms(Platform.HOSTED_ONLY.class)
public ClassForNameSupport(Function<ClassLoader, ClassLoader> getRuntimeClassLoaderFunc) {
Objects.requireNonNull(getRuntimeClassLoaderFunc);
this.getRuntimeClassLoaderFunc = getRuntimeClassLoaderFunc;
}

public static ClassForNameSupport singleton() {
return ImageSingletons.lookup(ClassForNameSupport.class);
}

@UnknownObjectField(fullyQualifiedTypes = "org.graalvm.collections.EconomicMapImpl") //
private EconomicMap<String, ClassLoader> packageToLoader;

@Platforms(Platform.HOSTED_ONLY.class)
public void setPackageToLoader(EconomicMap<String, ClassLoader> packageToLoader) {
this.packageToLoader = packageToLoader;
}

public ClassLoader findLoadedModuleLoader(String cn) {
int pos = cn.lastIndexOf('.');
if (pos < 0) {
return null; /* unnamed package */
}
String pn = cn.substring(0, pos);
return packageToLoader.get(pn);
}

private record Entry(ClassLoader loader, String className) {
private static Entry of(String className) {
return of(null, className);
}

private static Entry of(Class<?> clazz) {
return of(clazz.getClassLoader(), clazz.getName());
}

private static Entry of(ClassLoader loader, String className) {
return new Entry(loader, className);
}
}

/** The map used to collect registered classes. */
private final EconomicMap<String, ConditionalRuntimeValue<Object>> knownClasses = ImageHeapMap.create();
private final EconomicMap<Entry, ConditionalRuntimeValue<Object>> knownClasses = ImageHeapMap.create();

private static final Object NEGATIVE_QUERY = new Object();

@Platforms(Platform.HOSTED_ONLY.class)
public void registerClass(Class<?> clazz) {
registerClass(ConfigurationCondition.alwaysTrue(), clazz);
public void registerClass(DynamicHub hub) {
registerClass(ConfigurationCondition.alwaysTrue(), hub);
}

@Platforms(Platform.HOSTED_ONLY.class)
public void registerClass(ConfigurationCondition condition, Class<?> clazz) {
assert !clazz.isPrimitive() : "primitive classes cannot be looked up by name";
public void registerClass(ConfigurationCondition condition, DynamicHub hub) {
assert !hub.isPrimitive() : "primitive classes cannot be looked up by name";
Class<?> clazz = hub.getHostedJavaClass();
if (PredefinedClassesSupport.isPredefined(clazz)) {
return; // must be defined at runtime before it can be looked up
}
synchronized (knownClasses) {
String name = clazz.getName();
ConditionalRuntimeValue<Object> exisingEntry = knownClasses.get(name);
String name = hub.getName();
Entry entry = Entry.of(getRuntimeClassLoaderFunc.apply(hub.getClassLoader()), hub.getName());
ConditionalRuntimeValue<Object> exisingEntry = knownClasses.get(entry);
Object currentValue = exisingEntry == null ? null : exisingEntry.getValueUnconditionally();

/* TODO: Remove workaround once GR-53985 is implemented */
if (currentValue instanceof Class<?> currentClazz && clazz.getClassLoader() != currentClazz.getClassLoader()) {
/* Ensure runtime lookup of GuestGraalClassLoader classes */
if (isGuestGraalClass(currentClazz)) {
return;
}
if (isGuestGraalClass(clazz)) {
currentValue = null;
}
}

if (currentValue == null || // never seen
currentValue == NEGATIVE_QUERY ||
currentValue == clazz) {
currentValue = clazz;
var cond = updateConditionalValue(exisingEntry, currentValue, condition);
knownClasses.put(name, cond);
knownClasses.put(entry, cond);
} else if (currentValue instanceof Throwable) { // failed at linking time
var cond = updateConditionalValue(exisingEntry, currentValue, condition);
/*
* If the class has already been seen as throwing an error, we don't overwrite this
* error. Nevertheless, we have to update the set of conditionals to be correct.
*/
knownClasses.put(name, cond);
knownClasses.put(entry, cond);
} else {
throw VMError.shouldNotReachHere("""
Invalid Class.forName value for %s: %s
Expand All @@ -105,14 +138,6 @@ accessible through the builder class loader, and it was already registered by na
}
}

private static boolean isGuestGraalClass(Class<?> clazz) {
var loader = clazz.getClassLoader();
if (loader == null) {
return false;
}
return "GuestGraalClassLoader".equals(loader.getName());
}

public static ConditionalRuntimeValue<Object> updateConditionalValue(ConditionalRuntimeValue<Object> existingConditionalValue, Object newValue,
ConfigurationCondition additionalCondition) {
if (existingConditionalValue == null) {
Expand Down Expand Up @@ -140,7 +165,7 @@ public void registerNegativeQuery(ConfigurationCondition condition, String class

private void updateCondition(ConfigurationCondition condition, String className, Object value) {
synchronized (knownClasses) {
var runtimeConditions = knownClasses.putIfAbsent(className, new ConditionalRuntimeValue<>(RuntimeConditionSet.createHosted(condition), value));
var runtimeConditions = knownClasses.putIfAbsent(Entry.of(className), new ConditionalRuntimeValue<>(RuntimeConditionSet.createHosted(condition), value));
if (runtimeConditions != null) {
runtimeConditions.getConditions().addCondition(condition);
}
Expand All @@ -163,9 +188,13 @@ private Class<?> forName(String className, ClassLoader classLoader, boolean retu
if (className == null) {
return null;
}
var conditional = knownClasses.get(className);
var conditional = knownClasses.get(Entry.of(classLoader, className));
Object result = conditional == null ? null : conditional.getValue();
if (result == NEGATIVE_QUERY || className.endsWith("[]")) {
if (result == NEGATIVE_QUERY) {
assert classLoader == null : "Unexpected NEGATIVE_QUERY result from classloader " + classLoader;
/* The class was registered for reflective access but not available at build-time */
result = new ClassNotFoundException(className);
} else if (className.endsWith("[]")) {
/* Querying array classes with their "TypeName[]" name always throws */
result = new ClassNotFoundException(className);
}
Expand All @@ -187,7 +216,7 @@ private Class<?> forName(String className, ClassLoader classLoader, boolean retu
throw (ClassNotFoundException) result;
}
} else if (result == null) {
if (throwMissingRegistrationErrors()) {
if (classLoader == null && throwMissingRegistrationErrors()) {
MissingReflectionRegistrationUtils.forClass(className);
}

Expand All @@ -206,11 +235,28 @@ public int count() {

public RuntimeConditionSet getConditionFor(Class<?> jClass) {
Objects.requireNonNull(jClass);
ConditionalRuntimeValue<Object> conditionalClass = knownClasses.get(jClass.getName());
ConditionalRuntimeValue<Object> conditionalClass = knownClasses.get(Entry.of(jClass));
if (conditionalClass == null) {
return RuntimeConditionSet.unmodifiableEmptySet();
} else {
return conditionalClass.getConditions();
}
}

public Class<?> dynamicHubForName0(String name, ClassLoader loader) throws ClassNotFoundException {
ClassLoader current = loader;
while (true) {
Class<?> result = forNameOrNull(name, current);
if (result != null) {
return result;
}
if (current != null) {
Target_java_lang_ClassLoader loaderInternal = SubstrateUtil.cast(current, Target_java_lang_ClassLoader.class);
current = SubstrateUtil.cast(loaderInternal.parent, ClassLoader.class);
} else {
break;
}
}
throw new ClassNotFoundException(name);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1454,12 +1454,8 @@ private static Class<?> forName(Module module, String className) throws Throwabl
@Platforms(InternalPlatform.NATIVE_ONLY.class)
@CallerSensitiveAdapter
private static Class<?> forName(@SuppressWarnings("unused") Module module, String className, Class<?> caller) throws Throwable {
/*
* The module system is not supported for now, therefore the module parameter is ignored and
* we use the class loader of the caller class instead of the module's loader.
*/
try {
return forName(className, false, caller.getClassLoader(), caller);
return forName(className, false, module.getClassLoader(), caller);
} catch (ClassNotFoundException e) {
return null;
}
Expand All @@ -1473,20 +1469,11 @@ private static Class<?> forName(String name, boolean initialize, ClassLoader loa

@Substitute
@CallerSensitiveAdapter
private static Class<?> forName(String name, boolean initialize, ClassLoader loader, @SuppressWarnings("unused") Class<?> caller) throws Throwable {
private static Class<?> forName(String name, boolean initialize, ClassLoader loader, @SuppressWarnings("unused") Class<?> caller) throws ClassNotFoundException {
if (name == null) {
throw new NullPointerException();
}
Class<?> result;
try {
result = ClassForNameSupport.singleton().forName(name, loader);
} catch (ClassNotFoundException e) {
if (loader != null && PredefinedClassesSupport.hasBytecodeClasses()) {
result = loader.loadClass(name); // may throw
} else {
throw e;
}
}
Class<?> result = ClassForNameSupport.singleton().dynamicHubForName0(name, loader);
if (initialize) {
DynamicHub.fromClass(result).ensureInitialized();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
@SuppressWarnings("static-method")
public final class Target_java_lang_ClassLoader {

@Alias private Target_java_lang_ClassLoader parent;
@Alias public Target_java_lang_ClassLoader parent;

/**
* This field can be safely deleted, but that would require substituting the entire constructor
Expand Down Expand Up @@ -116,36 +116,18 @@ private Class<?> loadClass(String name) throws ClassNotFoundException {
@Alias
protected native Class<?> findLoadedClass(String name);

@Alias
static native Class<?> findBootstrapClassOrNull(String name);

@Alias
protected native Class<?> findClass(String name);

@Substitute
@SuppressWarnings("unused")
Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
Class<?> clazz = findLoadedClass(name);
if (clazz != null) {
return clazz;
}
if (!PredefinedClassesSupport.hasBytecodeClasses()) {
throw new ClassNotFoundException(name);
}
if (parent != null) {
try {
clazz = parent.loadClass(name);
if (clazz != null) {
return clazz;
}
} catch (ClassNotFoundException ignored) {
// not found in parent loader
}
}
return findClass(name);
return ClassLoaderUtil.loadClass(this, name, false);
}

// JDK-8265605
@Delete
static native Class<?> findBootstrapClassOrNull(String name);

@Substitute
@SuppressWarnings("unused")
static void checkClassLoaderPermission(ClassLoader cl, Class<?> caller) {
Expand All @@ -168,6 +150,12 @@ private Class<?> findLoadedClass0(String name) {
return ClassForNameSupport.singleton().forNameOrNull(name, SubstrateUtil.cast(this, ClassLoader.class));
}

@Substitute //
@SuppressWarnings({"unused"}) //
private static Class<?> findBootstrapClass(String name) {
return ClassForNameSupport.singleton().forNameOrNull(name, null);
}

/**
* Most {@link ClassLoaderValue}s are reset. For the list of preserved transformers see
* {@link ClassLoaderValueMapFieldValueTransformer}.
Expand Down Expand Up @@ -309,10 +297,6 @@ private static Class<?> defineClass0(ClassLoader loader, Class<?> lookup, String
return PredefinedClassesSupport.loadClass(loader, actualName.replace('/', '.'), b, off, b.length, null);
}

// JDK-8265605
@Delete
private static native Class<?> findBootstrapClass(String name);

@Delete
private static native Target_java_lang_AssertionStatusDirectives retrieveDirectives();
}
Expand All @@ -321,6 +305,38 @@ private static Class<?> defineClass0(ClassLoader loader, Class<?> lookup, String
final class Target_java_lang_AssertionStatusDirectives {
}

final class ClassLoaderUtil {

public static Class<?> loadClass(Target_java_lang_ClassLoader receiver, String name, boolean isBuiltin) throws ClassNotFoundException {
Class<?> clazz = receiver.findLoadedClass(name);
if (clazz != null) {
return clazz;
}
try {
ClassLoader loaderForModule = isBuiltin ? ClassForNameSupport.singleton().findLoadedModuleLoader(name) : null;
if (loaderForModule != null) {
clazz = ClassForNameSupport.singleton().forNameOrNull(name, loaderForModule);
} else {
if (receiver.parent != null) {
clazz = SubstrateUtil.cast(receiver.parent, ClassLoader.class).loadClass(name);
} else {
clazz = Target_java_lang_ClassLoader.findBootstrapClassOrNull(name);
}
}
if (clazz != null) {
return clazz;
}
} catch (ClassNotFoundException ignored) {
// not found in parent loader
}

if (!PredefinedClassesSupport.hasBytecodeClasses()) {
throw new ClassNotFoundException(name);
}
return receiver.findClass(name);
}
}

@TargetClass(className = "java.lang.ClassLoader", innerClass = "ParallelLoaders")
final class Target_java_lang_ClassLoader_ParallelLoaders {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
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.hub.ClassForNameSupport;

@TargetClass(value = jdk.internal.loader.BuiltinClassLoader.class)
@SuppressWarnings({"unused", "static-method"})
Expand All @@ -48,17 +49,12 @@ final class Target_jdk_internal_loader_BuiltinClassLoader {

@Substitute
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
return ClassForNameSupport.singleton().forName(name, SubstrateUtil.cast(this, ClassLoader.class));
}

@Substitute
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);
if (clazz == null) {
throw new ClassNotFoundException(name);
}
return clazz;
protected Class<?> loadClass(String cn, boolean resolve) throws ClassNotFoundException {
return ClassLoaderUtil.loadClass(SubstrateUtil.cast(this, Target_java_lang_ClassLoader.class), cn, true);
}

@Substitute
Expand Down
Loading