Skip to content
Open
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
1 change: 1 addition & 0 deletions substratevm/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Do not run `mx` commands concurrently; parallel runs can produce misleading fail

## Change Hygiene

- Do not use lambdas in runtime code.
- If you touch documented behavior, update `docs/`.
- Do not commit generated output from `mxbuild/`, `svmbuild/`, `graal_dumps/`, or `sources/`.
- Be careful when changing `mx native-unittest` / `svmjunit` feature registration. Features added to the shared native test image affect unrelated tests too.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import com.oracle.svm.configure.config.ConfigurationMemberInfo;
import com.oracle.svm.configure.config.ConfigurationMethod;
import com.oracle.svm.configure.config.ConfigurationType;
import com.oracle.svm.core.configure.RuntimeDynamicAccessMetadata;
import com.oracle.svm.core.util.ExitStatus;
import com.oracle.svm.shared.util.VMError;
import com.oracle.svm.shared.util.StringUtil;
Expand All @@ -56,6 +57,8 @@
import jdk.graal.compiler.util.json.JsonWriter;

public class MissingRegistrationUtils {
private static final String MATCHING_METADATA_HEADER = "The matching metadata element in the '";
private static final String METADATA_HEADER = "The metadata element in the '";

public static boolean throwMissingRegistrationErrors() {
return ThrowMissingRegistrationErrors.hasBeenSet();
Expand Down Expand Up @@ -173,15 +176,52 @@ protected static String quote(String element) {
protected static String registrationMessage(String failedAction, String elementDescriptor, String json, String accessManner, String section, String helpLink) {
/* Can't use multi-line strings as they pull in format and bloat "Hello, World!" */
String optionalSpace = accessManner.isEmpty() ? "" : " ";
return "Cannot" + optionalSpace + accessManner + " " + failedAction + " " + elementDescriptor + ". To allow this operation, add the following to the '" + section +
"' section of 'reachability-metadata.json' and rebuild the native image:" + System.lineSeparator() +
return "Cannot" + optionalSpace + accessManner + " " + failedAction + " " + elementDescriptor + "." + System.lineSeparator() +
System.lineSeparator() +
"The matching metadata element in the '" + section + "' section of 'reachability-metadata.json' is:" + System.lineSeparator() +
System.lineSeparator() +
json + System.lineSeparator() +
System.lineSeparator() +
"The 'reachability-metadata.json' file should be located in 'META-INF/native-image/<group-id>/<artifact-id>/' of your project. For further help, see https://www.graalvm.org/latest/reference-manual/native-image/metadata/#" +
helpLink;
}

protected static String appendUnsatisfiedConditions(String message, RuntimeDynamicAccessMetadata dynamicAccessMetadata) {
if (dynamicAccessMetadata == null) {
return message;
}
String conditions = dynamicAccessMetadata.formatUnsatisfiedConditionsAsJson();
if (conditions.isEmpty()) {
return message;
}
String lineSeparator = System.lineSeparator();
String paragraphSeparator = lineSeparator + lineSeparator;
int splitIndex = message.indexOf(paragraphSeparator);
if (splitIndex == -1) {
return replaceFirst(message + paragraphSeparator +
"Reachability metadata for this access was found, but it is inactive because its runtime conditions were not satisfied." + lineSeparator +
"To fix this, either change/remove the metadata condition, or make sure the condition is reached before this access." + lineSeparator +
"Unsatisfied runtime conditions:" + paragraphSeparator +
conditions,
MATCHING_METADATA_HEADER, METADATA_HEADER);
}
return replaceFirst(message.substring(0, splitIndex) + paragraphSeparator +
"Reachability metadata for this access was found, but it is inactive because its runtime conditions were not satisfied." + lineSeparator +
"To fix this, either change/remove the metadata condition, or make sure the condition is reached before this access." + lineSeparator +
"Unsatisfied runtime conditions:" + paragraphSeparator +
conditions + paragraphSeparator +
message.substring(splitIndex + paragraphSeparator.length()),
MATCHING_METADATA_HEADER, METADATA_HEADER);
}

private static String replaceFirst(String text, String oldValue, String newValue) {
int index = text.indexOf(oldValue);
if (index == -1) {
return text;
}
return text.substring(0, index) + newValue + text.substring(index + oldValue.length());
}

protected static ConfigurationType namedConfigurationType(String typeName) {
return new ConfigurationType(UnresolvedAccessCondition.unconditional(), new NamedConfigurationTypeDescriptor(typeName), true);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
import com.oracle.svm.shared.singletons.MultiLayeredImageSingleton;
import com.oracle.svm.core.reflect.RuntimeMetadataDecoder;
import com.oracle.svm.core.reflect.target.ReflectionObjectFactory;
import com.oracle.svm.core.reflect.target.Target_java_lang_reflect_AccessibleObject;
import com.oracle.svm.core.reflect.target.Target_java_lang_reflect_Constructor;
import com.oracle.svm.core.reflect.target.Target_java_lang_reflect_Executable;
import com.oracle.svm.core.reflect.target.Target_java_lang_reflect_Field;
Expand Down Expand Up @@ -400,6 +401,7 @@ private static Object decodeField(UnsafeArrayTypeReader buf, Class<?> declaringC
RuntimeDynamicAccessMetadata dynamicAccessMetadata = decodeDynamicAccessMetadata(buf, layerId, preserved);
if (inHeap) {
Field field = (Field) decodeObject(buf, layerId);
SubstrateUtil.cast(field, Target_java_lang_reflect_AccessibleObject.class).dynamicAccessMetadata = RuntimeDynamicAccessMetadata.alwaysAllow(preserved);
if (publicOnly && !Modifier.isPublic(field.getModifiers())) {
/*
* Generate negative copy of the field. Finding a non-public field when looking for
Expand Down Expand Up @@ -569,6 +571,7 @@ private static Object decodeExecutable(UnsafeArrayTypeReader buf, Class<?> decla
RuntimeDynamicAccessMetadata dynamicAccessMetadata = decodeDynamicAccessMetadata(buf, layerId, preserved);
if (inHeap) {
Executable executable = (Executable) decodeObject(buf, layerId);
SubstrateUtil.cast(executable, Target_java_lang_reflect_AccessibleObject.class).dynamicAccessMetadata = RuntimeDynamicAccessMetadata.alwaysAllow(preserved);
if (publicOnly && !Modifier.isPublic(executable.getModifiers())) {
/*
* Generate negative copy of the executable. Finding a non-public method when
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;

import jdk.graal.compiler.api.replacements.Fold;
import org.graalvm.nativeimage.Platform;
import org.graalvm.nativeimage.Platforms;

Expand Down Expand Up @@ -156,6 +157,18 @@ public static final class Options {
@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 the stack trace when the configured 'typeReached' condition becomes satisfied. " +
"Value must be a fully qualified class name or '*' to match any type.")//
public static final HostedOptionKey<String> TrackConditionSatisfied = new HostedOptionKey<>(null);

@Option(help = "Testing flag: print concise diagnostics for reflection class-query checks, including successes and failures.")//
public static final HostedOptionKey<Boolean> TrackReflectionClassQueryChecks = new HostedOptionKey<>(false);

@Fold
public static boolean trackReflectionClassQueryChecks() {
return TrackReflectionClassQueryChecks.getValue();
}

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,13 @@
package com.oracle.svm.core.configure;

import static com.oracle.svm.core.configure.ConfigurationFiles.Options.TrackUnsatisfiedTypeReachedConditions;
import static com.oracle.svm.core.configure.ConfigurationFiles.Options.TrackConditionSatisfied;

import java.io.IOException;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.HashSet;
import java.util.StringJoiner;
import java.util.Set;

import org.graalvm.collections.EconomicSet;
Expand All @@ -41,6 +45,9 @@
import com.oracle.svm.shared.util.LogUtils;
import com.oracle.svm.shared.util.VMError;

import jdk.graal.compiler.api.replacements.Fold;
import jdk.graal.compiler.util.json.JsonWriter;

/**
* The dynamic access metadata for some value that can be accessed at run time. Contains a set of
* {@link #conditions} that dictate whether the value (e.g., a resource) should be accessible; also
Expand All @@ -59,7 +66,7 @@ public class RuntimeDynamicAccessMetadata {
private boolean satisfied;
private volatile boolean preserved;

public static RuntimeDynamicAccessMetadata emptySet(boolean preserved) {
public static RuntimeDynamicAccessMetadata alwaysAllow(boolean preserved) {
return new RuntimeDynamicAccessMetadata(new Object[0], preserved);
}

Expand All @@ -76,8 +83,11 @@ public synchronized void addCondition(AccessCondition cnd) {
TypeReachabilityCondition reachabilityCondition = (TypeReachabilityCondition) cnd;
VMError.guarantee(reachabilityCondition.isRuntimeChecked(), "Only runtime conditions can be added to the ConditionalRuntimeValue.");
if (satisfied) {
maybeTrackConditionSatisfied(reachabilityCondition, true);
return;
} else if (reachabilityCondition.isAlwaysTrue()) {
maybeTrackConditionSatisfied(conditions, true);

conditions = null;
satisfied = true;
return;
Expand Down Expand Up @@ -129,6 +139,7 @@ public boolean satisfied() {
} else {
for (Object condition : localConditions) {
if (isSatisfied(condition)) {
maybeTrackConditionSatisfied(condition);
conditions = null;
satisfied = result = true;
break;
Expand All @@ -146,6 +157,41 @@ public boolean satisfied() {
return result;
}

private static void maybeTrackConditionSatisfied(Object condition) {
maybeTrackConditionSatisfied(condition, false);
}

private static void maybeTrackConditionSatisfied(Object condition, boolean ignoredAtBuildTime) {
if (condition == null) {
return;
} else if (condition instanceof Object[] conditionsArray) {
for (Object singleCondition : conditionsArray) {
maybeTrackConditionSatisfied(singleCondition, ignoredAtBuildTime);
}
return;
}

String trackedType = trackConditionSatisfied();
String typeName = null;
if (condition instanceof Class<?> reachedTypeCondition) {
typeName = reachedTypeCondition.getTypeName();
} else if (condition instanceof TypeReachabilityCondition reachedTypeCondition) {
typeName = reachedTypeCondition.getType().getTypeName();
}
if (trackedType != null && typeName != null && ("*".equals(trackedType) || typeName.equals(trackedType)) && !"java.lang.Object".equals(typeName)) {
if (ignoredAtBuildTime) {
LogUtils.info("Tracked runtime condition reached at build time and ignored: type = " + typeName);
} else {
LogUtils.info("Tracked runtime condition reached: type = " + typeName);
}
}
}

@Fold
public static String trackConditionSatisfied() {
return TrackConditionSatisfied.getValue();
}

/*
* Used in snippets, returns true only if the condition was already satisfied beforehand.
*/
Expand All @@ -157,6 +203,79 @@ public boolean isPreserved() {
return preserved;
}

/**
* Returns a user-facing description of unresolved runtime conditions.
*/
public String formatUnsatisfiedConditions() {
Object[] localConditions = conditions;
if (satisfied || localConditions == null) {
return "";
}

StringJoiner joiner = new StringJoiner(", ");
for (Object condition : localConditions) {
String formattedCondition = formatUnsatisfiedCondition(condition);
if (formattedCondition != null) {
joiner.add(formattedCondition);
}
}
return joiner.toString();
}

/**
* Returns unresolved runtime conditions formatted as JSON.
*/
public String formatUnsatisfiedConditionsAsJson() {
Object[] localConditions = conditions;
if (satisfied || localConditions == null) {
return "";
}

String lineSeparator = System.lineSeparator();
StringBuilder builder = new StringBuilder();
boolean wroteAny = false;
for (Object condition : localConditions) {
if (wroteAny) {
builder.append(lineSeparator);
}
appendUnsatisfiedConditionAsJson(builder, condition);
wroteAny = true;
}
if (!wroteAny) {
return "";
}
/*
* Remove trailing line separator to keep spacing deterministic in user-facing messages.
*/
builder.setLength(builder.length() - lineSeparator.length());
return builder.toString();
}

private static String formatUnsatisfiedCondition(Object condition) {
if (condition instanceof Class<?> reachedTypeCondition) {
return "typeReached(" + DynamicHub.fromClass(reachedTypeCondition).getTypeName() + ")";
}
return String.valueOf(condition);
}

private static void appendUnsatisfiedConditionAsJson(StringBuilder builder, Object condition) {
if (condition instanceof Class<?> reachedTypeCondition) {
builder.append(" ").append(quoteJsonString("typeReached")).append(": ").append(quoteJsonString(DynamicHub.fromClass(reachedTypeCondition).getTypeName()));
} else {
builder.append(" ").append(quoteJsonString("condition")).append(": ").append(quoteJsonString(String.valueOf(condition)));
}
builder.append(System.lineSeparator());
}

private static String quoteJsonString(String value) {
try (StringWriter stringWriter = new StringWriter(); JsonWriter writer = new JsonWriter(stringWriter)) {
writer.quote(value);
return stringWriter.toString();
} catch (IOException e) {
throw VMError.shouldNotReachHere("Writing JSON to memory", e);
}
}

@Platforms(Platform.HOSTED_ONLY.class)
public void setNotPreserved() {
this.preserved = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,7 @@ private static DynamicHub slowPathHubOrUnsafeInstantiationError(DynamicHub hub)
return hub;
} else {
if (MissingRegistrationUtils.throwMissingRegistrationErrors()) {
MissingReflectionRegistrationUtils.reportUnsafeAllocation(DynamicHub.toClass(hub));
MissingReflectionRegistrationUtils.reportUnsafeAllocation(DynamicHub.toClass(hub), hub.getUnsafeInstantiateAsInstanceMetadata());
}
throw new IllegalArgumentException("Type " + DynamicHub.toClass(hub).getTypeName() + " is instantiated reflectively but was never registered." +
" Register the type by adding \"unsafeAllocated\" for the type in " + ConfigurationFile.REFLECTION.getFileName() + ".");
Expand Down
Loading