Skip to content

Commit 85d0a8e

Browse files
committed
[GR-50432] Allow fields to be registered for reflection without being made reachable
PullRequest: graal/16267
2 parents 3627df4 + 647cb6b commit 85d0a8e

File tree

6 files changed

+64
-31
lines changed

6 files changed

+64
-31
lines changed

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/MissingReflectionRegistrationUtils.java

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,17 @@ public static void forField(Class<?> declaringClass, String fieldName) {
5656
report(exception);
5757
}
5858

59+
public static MissingReflectionRegistrationError errorForQueriedOnlyField(Field field) {
60+
MissingReflectionRegistrationError exception = new MissingReflectionRegistrationError(errorMessage("read or write field", field.toString()),
61+
field.getClass(), field.getDeclaringClass(), field.getName(), null);
62+
report(exception);
63+
/*
64+
* If report doesn't throw, we throw the exception anyway since this is a Native
65+
* Image-specific error that is unrecoverable in any case.
66+
*/
67+
return exception;
68+
}
69+
5970
public static void forMethod(Class<?> declaringClass, String methodName, Class<?>[] paramTypes) {
6071
StringJoiner paramTypeNames = new StringJoiner(", ", "(", ")");
6172
if (paramTypes != null) {
@@ -69,15 +80,15 @@ public static void forMethod(Class<?> declaringClass, String methodName, Class<?
6980
report(exception);
7081
}
7182

72-
public static void forQueriedOnlyExecutable(Executable executable) {
83+
public static MissingReflectionRegistrationError errorForQueriedOnlyExecutable(Executable executable) {
7384
MissingReflectionRegistrationError exception = new MissingReflectionRegistrationError(errorMessage("invoke method", executable.toString()),
7485
executable.getClass(), executable.getDeclaringClass(), executable.getName(), executable.getParameterTypes());
7586
report(exception);
7687
/*
7788
* If report doesn't throw, we throw the exception anyway since this is a Native
7889
* Image-specific error that is unrecoverable in any case.
7990
*/
80-
throw exception;
91+
return exception;
8192
}
8293

8394
public static void forBulkQuery(Class<?> declaringClass, String methodName) {
@@ -87,7 +98,7 @@ public static void forBulkQuery(Class<?> declaringClass, String methodName) {
8798
report(exception);
8899
}
89100

90-
public static void forProxy(Class<?>... interfaces) {
101+
public static MissingReflectionRegistrationError errorForProxy(Class<?>... interfaces) {
91102
MissingReflectionRegistrationError exception = new MissingReflectionRegistrationError(errorMessage("access the proxy class inheriting",
92103
Arrays.toString(Arrays.stream(interfaces).map(Class::getTypeName).toArray()),
93104
"The order of interfaces used to create proxies matters.", "dynamic-proxy"),
@@ -97,7 +108,7 @@ public static void forProxy(Class<?>... interfaces) {
97108
* If report doesn't throw, we throw the exception anyway since this is a Native
98109
* Image-specific error that is unrecoverable in any case.
99110
*/
100-
throw exception;
111+
return exception;
101112
}
102113

103114
private static String errorMessage(String failedAction, String elementDescriptor) {

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/proxy/DynamicProxySupport.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
import org.graalvm.collections.EconomicMap;
3232
import org.graalvm.collections.EconomicSet;
3333
import org.graalvm.collections.Equivalence;
34-
import jdk.graal.compiler.debug.GraalError;
3534
import org.graalvm.nativeimage.Platform;
3635
import org.graalvm.nativeimage.Platforms;
3736
import org.graalvm.nativeimage.hosted.RuntimeClassInitialization;
@@ -47,6 +46,8 @@
4746
import com.oracle.svm.util.LogUtils;
4847
import com.oracle.svm.util.ReflectionUtil;
4948

49+
import jdk.graal.compiler.debug.GraalError;
50+
5051
public class DynamicProxySupport implements DynamicProxyRegistry {
5152

5253
public static final Pattern PROXY_CLASS_NAME_PATTERN = Pattern.compile(".*\\$Proxy[0-9]+");
@@ -173,7 +174,7 @@ public Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces) {
173174
ProxyCacheKey key = new ProxyCacheKey(interfaces);
174175
Object clazzOrError = proxyCache.get(key);
175176
if (clazzOrError == null) {
176-
MissingReflectionRegistrationUtils.forProxy(interfaces);
177+
throw MissingReflectionRegistrationUtils.errorForProxy(interfaces);
177178
}
178179
if (clazzOrError instanceof Throwable) {
179180
throw new GraalError((Throwable) clazzOrError);

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_java_lang_reflect_Constructor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ public native void constructor(Class<?> declaringClass, Class<?>[] parameterType
7171
@Substitute
7272
Target_jdk_internal_reflect_ConstructorAccessor acquireConstructorAccessor() {
7373
if (constructorAccessor == null) {
74-
MissingReflectionRegistrationUtils.forQueriedOnlyExecutable(SubstrateUtil.cast(this, Executable.class));
74+
throw MissingReflectionRegistrationUtils.errorForQueriedOnlyExecutable(SubstrateUtil.cast(this, Executable.class));
7575
}
7676
return constructorAccessor;
7777
}

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_java_lang_reflect_Method.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ public native void constructor(Class<?> declaringClass, String name, Class<?>[]
7474
@Substitute
7575
public Target_jdk_internal_reflect_MethodAccessor acquireMethodAccessor() {
7676
if (methodAccessor == null) {
77-
MissingReflectionRegistrationUtils.forQueriedOnlyExecutable(SubstrateUtil.cast(this, Executable.class));
77+
throw MissingReflectionRegistrationUtils.errorForQueriedOnlyExecutable(SubstrateUtil.cast(this, Executable.class));
7878
}
7979
return methodAccessor;
8080
}

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_jdk_internal_misc_Unsafe_Reflection.java

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
import com.oracle.svm.core.SubstrateUtil;
3232
import com.oracle.svm.core.annotate.Substitute;
3333
import com.oracle.svm.core.annotate.TargetClass;
34-
import com.oracle.svm.core.util.VMError;
34+
import com.oracle.svm.core.reflect.MissingReflectionRegistrationUtils;
3535

3636
@TargetClass(className = "jdk.internal.misc.Unsafe")
3737
@SuppressWarnings({"static-method"})
@@ -80,13 +80,10 @@ static long getFieldOffset(Target_java_lang_reflect_Field field) {
8080
throw new NullPointerException();
8181
}
8282
int offset = field.root == null ? field.offset : field.root.offset;
83-
if (offset > 0) {
84-
return offset;
83+
if (offset <= 0) {
84+
throw MissingReflectionRegistrationUtils.errorForQueriedOnlyField(SubstrateUtil.cast(field, Field.class));
8585
}
86-
throw VMError.unsupportedFeature("The offset of " + field + " is accessed without the field being first registered as unsafe accessed. " +
87-
"Please register the field as unsafe accessed. You can do so with a reflection configuration that " +
88-
"contains an entry for the field with the attribute \"allowUnsafeAccess\": true. Such a configuration " +
89-
"file can be generated for you. Read BuildConfiguration.md and Reflection.md for details.");
86+
return offset;
9087
}
9188

9289
}

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -446,50 +446,58 @@ public void registerConstructorLookup(ConfigurationCondition condition, Class<?>
446446
public void register(ConfigurationCondition condition, boolean finalIsWritable, Field... fields) {
447447
requireNonNull(fields, "field");
448448
checkNotSealed();
449-
registerInternal(condition, fields);
449+
registerInternal(condition, false, fields);
450450
}
451451

452-
private void registerInternal(ConfigurationCondition condition, Field... fields) {
452+
private void registerInternal(ConfigurationCondition condition, boolean queriedOnly, Field... fields) {
453453
register(analysisUniverse -> registerConditionalConfiguration(condition, (cnd) -> {
454454
for (Field field : fields) {
455-
analysisUniverse.getBigbang().postTask(debug -> registerField(field));
455+
analysisUniverse.getBigbang().postTask(debug -> registerField(queriedOnly, field));
456456
}
457457
}));
458458
}
459459

460460
@Override
461461
public void registerAllFieldsQuery(ConfigurationCondition condition, Class<?> clazz) {
462+
registerAllFieldsQuery(condition, false, clazz);
463+
}
464+
465+
private void registerAllFieldsQuery(ConfigurationCondition condition, boolean queriedOnly, Class<?> clazz) {
462466
checkNotSealed();
463467
for (Class<?> current = clazz; current != null; current = current.getSuperclass()) {
464468
final Class<?> currentLambda = current;
465469
registerConditionalConfiguration(condition, (cnd) -> setQueryFlag(currentLambda, ALL_FIELDS_FLAG));
466470
}
467471
try {
468-
registerInternal(condition, clazz.getFields());
472+
registerInternal(condition, queriedOnly, clazz.getFields());
469473
} catch (LinkageError e) {
470474
registerLinkageError(clazz, e, fieldLookupExceptions);
471475
}
472476
}
473477

474478
@Override
475479
public void registerAllDeclaredFieldsQuery(ConfigurationCondition condition, Class<?> clazz) {
480+
registerAllDeclaredFieldsQuery(condition, false, clazz);
481+
}
482+
483+
private void registerAllDeclaredFieldsQuery(ConfigurationCondition condition, boolean queriedOnly, Class<?> clazz) {
476484
checkNotSealed();
477485
registerConditionalConfiguration(condition, (cnd) -> setQueryFlag(clazz, ALL_DECLARED_FIELDS_FLAG));
478486
try {
479-
registerInternal(condition, clazz.getDeclaredFields());
487+
registerInternal(condition, queriedOnly, clazz.getDeclaredFields());
480488
} catch (LinkageError e) {
481489
registerLinkageError(clazz, e, fieldLookupExceptions);
482490
}
483491
}
484492

485-
private void registerField(Field reflectField) {
493+
private void registerField(boolean queriedOnly, Field reflectField) {
486494
if (SubstitutionReflectivityFilter.shouldExclude(reflectField, metaAccess, universe)) {
487495
return;
488496
}
489497

490498
AnalysisField analysisField = metaAccess.lookupJavaField(reflectField);
491499
if (registeredFields.put(analysisField, reflectField) == null) {
492-
registerTypesForField(analysisField, reflectField);
500+
registerTypesForField(analysisField, reflectField, true);
493501
AnalysisType declaringClass = analysisField.getDeclaringClass();
494502

495503
/*
@@ -504,13 +512,21 @@ private void registerField(Field reflectField) {
504512
processAnnotationField(reflectField);
505513
}
506514
}
515+
516+
/*
517+
* We need to run this even if the method has already been registered, in case it was only
518+
* registered as queried.
519+
*/
520+
if (!queriedOnly) {
521+
registerTypesForField(analysisField, reflectField, false);
522+
}
507523
}
508524

509525
@Override
510526
public void registerFieldLookup(ConfigurationCondition condition, Class<?> declaringClass, String fieldName) {
511527
checkNotSealed();
512528
try {
513-
registerInternal(condition, declaringClass.getDeclaredField(fieldName));
529+
registerInternal(condition, false, declaringClass.getDeclaredField(fieldName));
514530
} catch (NoSuchFieldException e) {
515531
registerConditionalConfiguration(condition,
516532
(cnd) -> negativeFieldLookups.computeIfAbsent(metaAccess.lookupJavaType(declaringClass), (key) -> ConcurrentHashMap.newKeySet()).add(fieldName));
@@ -680,13 +696,15 @@ private Object[] getEnclosingMethodInfo(Class<?> clazz) {
680696
}
681697
}
682698

683-
private void registerTypesForField(AnalysisField analysisField, Field reflectField) {
684-
/*
685-
* Reflection accessors use Unsafe, so ensure that all reflectively accessible fields are
686-
* registered as unsafe-accessible, whether they have been explicitly registered or their
687-
* Field object is reachable in the image heap.
688-
*/
689-
analysisField.registerAsUnsafeAccessed("is registered for reflection.");
699+
private void registerTypesForField(AnalysisField analysisField, Field reflectField, boolean queriedOnly) {
700+
if (!queriedOnly) {
701+
/*
702+
* Reflection accessors use Unsafe, so ensure that all reflectively accessible fields
703+
* are registered as unsafe-accessible, whether they have been explicitly registered or
704+
* their Field object is reachable in the image heap.
705+
*/
706+
analysisField.registerAsUnsafeAccessed("is registered for reflection.");
707+
}
690708

691709
/*
692710
* The generic signature is parsed at run time, so we need to make all the types necessary
@@ -1045,7 +1063,7 @@ public void registerHeapReflectionField(Field reflectField, ScanReason reason) {
10451063
assert !sealed;
10461064
AnalysisField analysisField = metaAccess.lookupJavaField(reflectField);
10471065
if (heapFields.put(analysisField, reflectField) == null && !SubstitutionReflectivityFilter.shouldExclude(reflectField, metaAccess, universe)) {
1048-
registerTypesForField(analysisField, reflectField);
1066+
registerTypesForField(analysisField, reflectField, false);
10491067
if (analysisField.getDeclaringClass().isAnnotation()) {
10501068
processAnnotationField(reflectField);
10511069
}
@@ -1155,4 +1173,10 @@ private static void requireNonNull(Object[] values, String kind) {
11551173
private static String nullErrorMessage(String kind) {
11561174
return "Cannot register null value as " + kind + " for reflection. Please ensure that all values you register are not null.";
11571175
}
1176+
1177+
public static class TestBackdoor {
1178+
public static void registerField(ReflectionDataBuilder reflectionDataBuilder, boolean queriedOnly, Field field) {
1179+
reflectionDataBuilder.registerInternal(ConfigurationCondition.alwaysTrue(), queriedOnly, field);
1180+
}
1181+
}
11581182
}

0 commit comments

Comments
 (0)