Skip to content
Closed
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
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
Expand Down Expand Up @@ -43,6 +43,7 @@
import org.graalvm.nativeimage.ImageSingletons;
import org.graalvm.nativeimage.Platform;
import org.graalvm.nativeimage.Platforms;
import org.graalvm.nativeimage.impl.ConfigurationCondition;
import org.graalvm.nativeimage.impl.RuntimeProxyCreationSupport;

/**
Expand All @@ -61,7 +62,7 @@ public final class RuntimeProxyCreation {
* @since 22.3
*/
public static void register(Class<?>... interfaces) {
ImageSingletons.lookup(RuntimeProxyCreationSupport.class).addProxyClass(interfaces);
ImageSingletons.lookup(RuntimeProxyCreationSupport.class).addProxyClass(ConfigurationCondition.alwaysTrue(), interfaces);
}

private RuntimeProxyCreation() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
Expand Down Expand Up @@ -41,5 +41,5 @@
package org.graalvm.nativeimage.impl;

public interface RuntimeProxyCreationSupport {
void addProxyClass(Class<?>... interfaces);
void addProxyClass(ConfigurationCondition condition, Class<?>... interfaces);
}
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ public void printJson(JsonWriter writer) throws IOException {

@Override
public ConfigurationParser createParser() {
return new ReflectionConfigurationParser<>(ConfigurationConditionResolver.identityResolver(), new ParserConfigurationAdapter(this), true, false);
return new ReflectionConfigurationParser<>(ConfigurationConditionResolver.identityResolver(), new ParserConfigurationAdapter(this), true, false, false);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@

import com.oracle.svm.core.SubstrateUtil;
import com.oracle.svm.core.c.NonmovableArrays;
import com.oracle.svm.core.configure.RuntimeConditionSet;
import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton;
import com.oracle.svm.core.hub.DynamicHub;
import com.oracle.svm.core.reflect.RuntimeMetadataDecoder;
Expand Down Expand Up @@ -320,14 +321,16 @@ private static Object decodeField(UnsafeArrayTypeReader buf, Class<?> declaringC
int modifiers = buf.getUVInt();
boolean inHeap = (modifiers & IN_HEAP_FLAG_MASK) != 0;
boolean complete = (modifiers & COMPLETE_FLAG_MASK) != 0;

RuntimeConditionSet conditions = decodeConditions(buf);
if (inHeap) {
Field field = (Field) decodeObject(buf);
if (publicOnly && !Modifier.isPublic(field.getModifiers())) {
/*
* Generate negative copy of the field. Finding a non-public field when looking for
* a public one should not result in a missing registration exception.
*/
return ReflectionObjectFactory.newField(declaringClass, field.getName(), Object.class, field.getModifiers() | NEGATIVE_FLAG_MASK, false,
return ReflectionObjectFactory.newField(conditions, declaringClass, field.getName(), Object.class, field.getModifiers() | NEGATIVE_FLAG_MASK, false,
null, null, ReflectionObjectFactory.FIELD_OFFSET_NONE, null, null);
}
if (reflectOnly) {
Expand Down Expand Up @@ -356,7 +359,8 @@ private static Object decodeField(UnsafeArrayTypeReader buf, Class<?> declaringC
if (!reflectOnly) {
return new FieldDescriptor(declaringClass, name);
}
return ReflectionObjectFactory.newField(declaringClass, name, negative ? Object.class : type, modifiers, false, null, null, ReflectionObjectFactory.FIELD_OFFSET_NONE, null, null);
return ReflectionObjectFactory.newField(conditions, declaringClass, name, negative ? Object.class : type, modifiers, false, null, null, ReflectionObjectFactory.FIELD_OFFSET_NONE, null,
null);
}
boolean trustedFinal = buf.getU1() == 1;
String signature = decodeOtherString(buf);
Expand All @@ -368,10 +372,15 @@ private static Object decodeField(UnsafeArrayTypeReader buf, Class<?> declaringC
modifiers |= NEGATIVE_FLAG_MASK;
}

Field reflectField = ReflectionObjectFactory.newField(declaringClass, name, type, modifiers, trustedFinal, signature, annotations, offset, deletedReason, typeAnnotations);
Field reflectField = ReflectionObjectFactory.newField(conditions, declaringClass, name, type, modifiers, trustedFinal, signature, annotations, offset, deletedReason, typeAnnotations);
return reflectOnly ? reflectField : new FieldDescriptor(reflectField);
}

private static RuntimeConditionSet decodeConditions(UnsafeArrayTypeReader buf) {
var conditionTypes = decodeArray(buf, Class.class, i -> decodeType(buf));
return RuntimeConditionSet.createDecoded(conditionTypes);
}

/**
* Complete method encoding.
*
Expand Down Expand Up @@ -479,6 +488,7 @@ private static Object decodeExecutable(UnsafeArrayTypeReader buf, Class<?> decla
int modifiers = buf.getUVInt();
boolean inHeap = (modifiers & IN_HEAP_FLAG_MASK) != 0;
boolean complete = (modifiers & COMPLETE_FLAG_MASK) != 0;
RuntimeConditionSet conditions = decodeConditions(buf);
if (inHeap) {
Executable executable = (Executable) decodeObject(buf);
if (publicOnly && !Modifier.isPublic(executable.getModifiers())) {
Expand All @@ -487,10 +497,11 @@ private static Object decodeExecutable(UnsafeArrayTypeReader buf, Class<?> decla
* looking for a public one should not result in a missing registration exception.
*/
if (isMethod) {
executable = ReflectionObjectFactory.newMethod(declaringClass, executable.getName(), executable.getParameterTypes(), Object.class, null, modifiers | NEGATIVE_FLAG_MASK,
executable = ReflectionObjectFactory.newMethod(conditions, declaringClass, executable.getName(), executable.getParameterTypes(), Object.class, null, modifiers | NEGATIVE_FLAG_MASK,
null, null, null, null, null, null, null);
} else {
executable = ReflectionObjectFactory.newConstructor(declaringClass, executable.getParameterTypes(), null, modifiers | NEGATIVE_FLAG_MASK, null, null, null, null, null, null);
executable = ReflectionObjectFactory.newConstructor(conditions, declaringClass, executable.getParameterTypes(), null, modifiers | NEGATIVE_FLAG_MASK, null, null, null, null, null,
null);
}
}
if (reflectOnly) {
Expand Down Expand Up @@ -532,13 +543,13 @@ private static Object decodeExecutable(UnsafeArrayTypeReader buf, Class<?> decla
if (!reflectOnly) {
return new MethodDescriptor(declaringClass, name, (String[]) parameterTypes);
}
return ReflectionObjectFactory.newMethod(declaringClass, name, (Class<?>[]) parameterTypes, negative ? Object.class : returnType, null, modifiers,
return ReflectionObjectFactory.newMethod(conditions, declaringClass, name, (Class<?>[]) parameterTypes, negative ? Object.class : returnType, null, modifiers,
null, null, null, null, null, null, null);
} else {
if (!reflectOnly) {
return new ConstructorDescriptor(declaringClass, (String[]) parameterTypes);
}
return ReflectionObjectFactory.newConstructor(declaringClass, (Class<?>[]) parameterTypes, null, modifiers, null, null, null, null, null, null);
return ReflectionObjectFactory.newConstructor(conditions, declaringClass, (Class<?>[]) parameterTypes, null, modifiers, null, null, null, null, null, null);
}
}
Class<?>[] exceptionTypes = decodeArray(buf, Class.class, (i) -> decodeType(buf));
Expand All @@ -555,14 +566,14 @@ private static Object decodeExecutable(UnsafeArrayTypeReader buf, Class<?> decla

Target_java_lang_reflect_Executable executable;
if (isMethod) {
Method method = ReflectionObjectFactory.newMethod(declaringClass, name, (Class<?>[]) parameterTypes, returnType, exceptionTypes, modifiers,
Method method = ReflectionObjectFactory.newMethod(conditions, declaringClass, name, (Class<?>[]) parameterTypes, returnType, exceptionTypes, modifiers,
signature, annotations, parameterAnnotations, annotationDefault, accessor, reflectParameters, typeAnnotations);
if (!reflectOnly) {
return new MethodDescriptor(method);
}
executable = SubstrateUtil.cast(method, Target_java_lang_reflect_Executable.class);
} else {
Constructor<?> constructor = ReflectionObjectFactory.newConstructor(declaringClass, (Class<?>[]) parameterTypes, exceptionTypes,
Constructor<?> constructor = ReflectionObjectFactory.newConstructor(conditions, declaringClass, (Class<?>[]) parameterTypes, exceptionTypes,
modifiers, signature, annotations, parameterAnnotations, accessor, reflectParameters, typeAnnotations);
if (!reflectOnly) {
return new ConstructorDescriptor(constructor);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,9 @@
*/
package com.oracle.svm.core.configure;

import java.util.Set;
import java.util.function.Predicate;

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

import com.oracle.svm.core.util.VMError;

/**
* A image-heap stored {@link ConditionalRuntimeValue#value} that is guarded by run-time computed
* {@link ConditionalRuntimeValue#conditions}.
Expand All @@ -42,20 +37,11 @@
* @param <T> type of the stored value.
*/
public final class ConditionalRuntimeValue<T> {
private final Class<?>[] conditions;
private boolean satisfied;
RuntimeConditionSet conditions;
volatile T value;

@Platforms(Platform.HOSTED_ONLY.class)
public ConditionalRuntimeValue(Set<Class<?>> conditions, T value) {
if (!conditions.isEmpty()) {
this.conditions = conditions.toArray(Class[]::new);
} else {
this.conditions = null;
satisfied = true;
}

VMError.guarantee(conditions.stream().noneMatch(c -> c.equals(Object.class)), "java.lang.Object must not be in conditions as it is always true.");
public ConditionalRuntimeValue(RuntimeConditionSet conditions, T value) {
this.conditions = conditions;
this.value = value;
}

Expand All @@ -64,25 +50,20 @@ public T getValueUnconditionally() {
return value;
}

@Platforms(Platform.HOSTED_ONLY.class)
public Set<Class<?>> getConditions() {
return conditions == null ? Set.of() : Set.of(conditions);
public RuntimeConditionSet getConditions() {
return conditions;
}

public T getValue(Predicate<Class<?>> conditionSatisfied) {
if (satisfied) {
public T getValue() {
if (conditions.satisfied()) {
return value;
} else {
for (Class<?> element : conditions) {
if (conditionSatisfied.test(element)) {
satisfied = true;
break;
}
}
if (satisfied) {
return value;
}
return null;
}
return null;
}

@Platforms(Platform.HOSTED_ONLY.class)
public void updateValue(T newValue) {
this.value = newValue;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,20 @@ public static final class Options {
public static final HostedOptionKey<Boolean> StrictConfiguration = new HostedOptionKey<>(false);

@Option(help = "Testing flag: the typeReachable condition is treated as typeReached so the semantics of programs can change.")//
public static final HostedOptionKey<Boolean> TreatAllReachableConditionsAsReached = new HostedOptionKey<>(false);
public static final HostedOptionKey<Boolean> TreatAllTypeReachableConditionsAsTypeReached = new HostedOptionKey<>(false);

@Option(help = "Testing flag: the 'name' is treated as 'type' reflection configuration.")//
public static final HostedOptionKey<Boolean> TreatAllNameEntriesAsType = new HostedOptionKey<>(false);

@Option(help = "Testing flag: the 'typeReached' condition is always satisfied however it prints the stack trace where it would not be satisfied.")//
public static final HostedOptionKey<Boolean> TrackUnsatisfiedTypeReachedConditions = new HostedOptionKey<>(false);

@Option(help = "Testing flag: print 'typeReached' conditions that are used on interfaces at build time.")//
public static final HostedOptionKey<Boolean> TrackTypeReachedOnInterfaces = new HostedOptionKey<>(false);

@Option(help = "Testing flag: every type is considered as it participates in a typeReachable condition.")//
public static final HostedOptionKey<Boolean> TreatAllUserSpaceTypesAsTrackedForTypeReached = new HostedOptionKey<>(false);

@Option(help = "Warn when reflection and JNI configuration files have elements that could not be found on the classpath or modulepath.", type = OptionType.Expert)//
public static final HostedOptionKey<Boolean> WarnAboutMissingReflectionOrJNIMetadataElements = new HostedOptionKey<>(false);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
*/
package com.oracle.svm.core.configure;

import static com.oracle.svm.core.configure.ConfigurationFiles.Options.TreatAllReachableConditionsAsReached;
import static com.oracle.svm.core.configure.ConfigurationFiles.Options.TreatAllTypeReachableConditionsAsTypeReached;
import static org.graalvm.nativeimage.impl.UnresolvedConfigurationCondition.TYPE_REACHABLE_KEY;
import static org.graalvm.nativeimage.impl.UnresolvedConfigurationCondition.TYPE_REACHED_KEY;

Expand Down Expand Up @@ -205,7 +205,7 @@ protected static long asLong(Object value, String propertyName) {
throw new JSONParserException("Invalid long value '" + value + "' for element '" + propertyName + "'");
}

protected UnresolvedConfigurationCondition parseCondition(EconomicMap<String, Object> data) {
protected UnresolvedConfigurationCondition parseCondition(EconomicMap<String, Object> data, boolean runtimeCondition) {
Object conditionData = data.get(CONDITIONAL_KEY);
if (conditionData != null) {
EconomicMap<String, Object> conditionObject = asMap(conditionData, "Attribute 'condition' must be an object");
Expand All @@ -214,18 +214,24 @@ protected UnresolvedConfigurationCondition parseCondition(EconomicMap<String, Ob
}

if (conditionObject.containsKey(TYPE_REACHED_KEY)) {
if (!runtimeCondition) {
failOnSchemaError("'" + TYPE_REACHED_KEY + "' condition cannot be used in older schemas. Please migrate the file to the latest schema.");
}
Object object = conditionObject.get(TYPE_REACHED_KEY);
var condition = parseTypeContents(object);
if (condition.isPresent()) {
String className = ((NamedConfigurationTypeDescriptor) condition.get()).name();
return UnresolvedConfigurationCondition.create(className, true);
}
} else if (conditionObject.containsKey(TYPE_REACHABLE_KEY)) {
if (runtimeCondition && !TreatAllTypeReachableConditionsAsTypeReached.getValue()) {
failOnSchemaError("'" + TYPE_REACHABLE_KEY + "' condition can not be used with the latest schema. Please use '" + TYPE_REACHED_KEY + "'.");
}
Object object = conditionObject.get(TYPE_REACHABLE_KEY);
var condition = parseTypeContents(object);
if (condition.isPresent()) {
String className = ((NamedConfigurationTypeDescriptor) condition.get()).name();
return UnresolvedConfigurationCondition.create(className, TreatAllReachableConditionsAsReached.getValue());
return UnresolvedConfigurationCondition.create(className, TreatAllTypeReachableConditionsAsTypeReached.getValue());
}
}
}
Expand All @@ -236,21 +242,21 @@ private static JSONParserException failOnSchemaError(String message) {
throw new JSONParserException(message);
}

protected static Optional<ConfigurationTypeDescriptor> parseType(EconomicMap<String, Object> data) {
protected static Optional<ConfigurationTypeDescriptor> parseTypeOrName(EconomicMap<String, Object> data, boolean treatAllNameEntriesAsType) {
Object typeObject = data.get(TYPE_KEY);
Object name = data.get(NAME_KEY);
if (typeObject != null) {
return parseTypeContents(typeObject);
} else if (name != null) {
return Optional.of(new NamedConfigurationTypeDescriptor(asString(name)));
return Optional.of(new NamedConfigurationTypeDescriptor(asString(name), treatAllNameEntriesAsType));
} else {
throw failOnSchemaError("must have type or name specified for an element");
}
}

protected static Optional<ConfigurationTypeDescriptor> parseTypeContents(Object typeObject) {
if (typeObject instanceof String stringValue) {
return Optional.of(new NamedConfigurationTypeDescriptor(stringValue));
return Optional.of(new NamedConfigurationTypeDescriptor(stringValue, true));
} else {
EconomicMap<String, Object> type = asMap(typeObject, "type descriptor should be a string or object");
if (type.containsKey(PROXY_KEY)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,6 @@ static String checkQualifiedJavaName(String javaName) {
assert javaName.indexOf('/') == -1 || javaName.indexOf('/') > javaName.lastIndexOf('.') : "Requires qualified Java name, not internal representation: %s".formatted(javaName);
return canonicalizeTypeName(javaName);
}

boolean definedAsType();
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,20 @@

import com.oracle.svm.core.util.json.JsonWriter;

public record NamedConfigurationTypeDescriptor(String name) implements ConfigurationTypeDescriptor {
public record NamedConfigurationTypeDescriptor(String name, boolean definedAsType) implements ConfigurationTypeDescriptor {

public NamedConfigurationTypeDescriptor(String name) {
this(name, false);
}

public NamedConfigurationTypeDescriptor(String name, boolean definedAsType) {
this.name = ConfigurationTypeDescriptor.checkQualifiedJavaName(name);
this.definedAsType = definedAsType;
}

@Override
public boolean definedAsType() {
return definedAsType;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ private void parseInterfaceList(C condition, List<?> data) {

private void parseWithConditionalConfig(EconomicMap<String, Object> proxyConfigObject) {
checkAttributes(proxyConfigObject, "proxy descriptor object", Collections.singleton("interfaces"), Collections.singletonList(CONDITIONAL_KEY));
UnresolvedConfigurationCondition condition = parseCondition(proxyConfigObject);
UnresolvedConfigurationCondition condition = parseCondition(proxyConfigObject, false);
TypeResult<C> resolvedCondition = conditionResolver.resolveCondition(condition);
if (resolvedCondition.isPresent()) {
parseInterfaceList(resolvedCondition.get(), asList(proxyConfigObject.get("interfaces"), "The interfaces property must be an array of fully qualified interface names"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ public int compareTo(ConfigurationTypeDescriptor other) {
}
}

@Override
public boolean definedAsType() {
return true;
}

@Override
public void printJson(JsonWriter writer) throws IOException {
writer.append("{").indent().newline();
Expand Down
Loading