Skip to content

Commit

Permalink
[GR-50176] Implemention of the typeReached conditional for reflection.
Browse files Browse the repository at this point in the history
PullRequest: graal/16125
  • Loading branch information
vjovanov committed May 25, 2024
2 parents 3c7f0c3 + 01c61f0 commit eab2a6f
Show file tree
Hide file tree
Showing 37 changed files with 680 additions and 276 deletions.
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

0 comments on commit eab2a6f

Please sign in to comment.