Skip to content

Commit 3e53cec

Browse files
committed
Backport GR-50432: Allow fields to be registered for reflection without being made reachable
1 parent b2ca310 commit 3e53cec

File tree

4 files changed

+52
-29
lines changed

4 files changed

+52
-29
lines changed

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,17 @@ public static void forField(Class<?> declaringClass, String fieldName) {
5454
report(exception);
5555
}
5656

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

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/config/ReflectionRegistryAdapter.java

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import com.oracle.svm.core.TypeResult;
3535
import com.oracle.svm.core.configure.ConfigurationTypeDescriptor;
3636
import com.oracle.svm.hosted.ImageClassLoader;
37+
import com.oracle.svm.hosted.reflect.ReflectionDataBuilder;
3738

3839
public class ReflectionRegistryAdapter extends RegistryAdapter {
3940
private final RuntimeReflectionSupport reflectionSupport;
@@ -89,16 +90,12 @@ public void registerSigners(ConfigurationCondition condition, Class<?> type) {
8990

9091
@Override
9192
public void registerPublicFields(ConfigurationCondition condition, boolean queriedOnly, Class<?> type) {
92-
if (!queriedOnly) {
93-
reflectionSupport.registerAllFieldsQuery(condition, type);
94-
}
93+
((ReflectionDataBuilder) reflectionSupport).registerAllFieldsQuery(condition, queriedOnly, type);
9594
}
9695

9796
@Override
9897
public void registerDeclaredFields(ConfigurationCondition condition, boolean queriedOnly, Class<?> type) {
99-
if (!queriedOnly) {
100-
reflectionSupport.registerAllDeclaredFieldsQuery(condition, type);
101-
}
98+
((ReflectionDataBuilder) reflectionSupport).registerAllDeclaredFieldsQuery(condition, queriedOnly, type);
10299
}
103100

104101
@Override

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

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -410,50 +410,58 @@ public void registerConstructorLookup(ConfigurationCondition condition, Class<?>
410410
@Override
411411
public void register(ConfigurationCondition condition, boolean finalIsWritable, Field... fields) {
412412
checkNotSealed();
413-
registerInternal(condition, fields);
413+
registerInternal(condition, false, fields);
414414
}
415415

416-
private void registerInternal(ConfigurationCondition condition, Field... fields) {
416+
private void registerInternal(ConfigurationCondition condition, boolean queriedOnly, Field... fields) {
417417
register(analysisUniverse -> registerConditionalConfiguration(condition, () -> {
418418
for (Field field : fields) {
419-
analysisUniverse.getBigbang().postTask(debug -> registerField(field));
419+
analysisUniverse.getBigbang().postTask(debug -> registerField(queriedOnly, field));
420420
}
421421
}));
422422
}
423423

424424
@Override
425425
public void registerAllFieldsQuery(ConfigurationCondition condition, Class<?> clazz) {
426+
registerAllFieldsQuery(condition, false, clazz);
427+
}
428+
429+
public void registerAllFieldsQuery(ConfigurationCondition condition, boolean queriedOnly, Class<?> clazz) {
426430
checkNotSealed();
427431
for (Class<?> current = clazz; current != null; current = current.getSuperclass()) {
428432
final Class<?> currentLambda = current;
429433
registerConditionalConfiguration(condition, () -> setQueryFlag(currentLambda, ALL_FIELDS_FLAG));
430434
}
431435
try {
432-
registerInternal(condition, clazz.getFields());
436+
registerInternal(condition, queriedOnly, clazz.getFields());
433437
} catch (LinkageError e) {
434438
/* Ignore the error */
435439
}
436440
}
437441

438442
@Override
439443
public void registerAllDeclaredFieldsQuery(ConfigurationCondition condition, Class<?> clazz) {
444+
registerAllDeclaredFieldsQuery(condition, false, clazz);
445+
}
446+
447+
public void registerAllDeclaredFieldsQuery(ConfigurationCondition condition, boolean queriedOnly, Class<?> clazz) {
440448
checkNotSealed();
441449
registerConditionalConfiguration(condition, () -> setQueryFlag(clazz, ALL_DECLARED_FIELDS_FLAG));
442450
try {
443-
registerInternal(condition, clazz.getDeclaredFields());
451+
registerInternal(condition, queriedOnly, clazz.getDeclaredFields());
444452
} catch (LinkageError e) {
445453
/* Ignore the error */
446454
}
447455
}
448456

449-
private void registerField(Field reflectField) {
457+
private void registerField(boolean queriedOnly, Field reflectField) {
450458
if (SubstitutionReflectivityFilter.shouldExclude(reflectField, metaAccess, universe)) {
451459
return;
452460
}
453461

454462
AnalysisField analysisField = metaAccess.lookupJavaField(reflectField);
455463
if (registeredFields.put(analysisField, reflectField) == null) {
456-
registerTypesForField(analysisField, reflectField);
464+
registerTypesForField(analysisField, reflectField, true);
457465
AnalysisType declaringClass = analysisField.getDeclaringClass();
458466

459467
/*
@@ -468,13 +476,21 @@ private void registerField(Field reflectField) {
468476
processAnnotationField(reflectField);
469477
}
470478
}
479+
480+
/*
481+
* We need to run this even if the method has already been registered, in case it was only
482+
* registered as queried.
483+
*/
484+
if (!queriedOnly) {
485+
registerTypesForField(analysisField, reflectField, false);
486+
}
471487
}
472488

473489
@Override
474490
public void registerFieldLookup(ConfigurationCondition condition, Class<?> declaringClass, String fieldName) {
475491
checkNotSealed();
476492
try {
477-
registerInternal(condition, declaringClass.getDeclaredField(fieldName));
493+
registerInternal(condition, false, declaringClass.getDeclaredField(fieldName));
478494
} catch (NoSuchFieldException e) {
479495
registerConditionalConfiguration(condition, () -> negativeFieldLookups.computeIfAbsent(metaAccess.lookupJavaType(declaringClass), (key) -> ConcurrentHashMap.newKeySet()).add(fieldName));
480496
}
@@ -641,13 +657,15 @@ private Object[] getEnclosingMethodInfo(Class<?> clazz) {
641657
}
642658
}
643659

644-
private void registerTypesForField(AnalysisField analysisField, Field reflectField) {
645-
/*
646-
* Reflection accessors use Unsafe, so ensure that all reflectively accessible fields are
647-
* registered as unsafe-accessible, whether they have been explicitly registered or their
648-
* Field object is reachable in the image heap.
649-
*/
650-
analysisField.registerAsUnsafeAccessed("is registered for reflection.");
660+
private void registerTypesForField(AnalysisField analysisField, Field reflectField, boolean queriedOnly) {
661+
if (!queriedOnly) {
662+
/*
663+
* Reflection accessors use Unsafe, so ensure that all reflectively accessible fields
664+
* are registered as unsafe-accessible, whether they have been explicitly registered or
665+
* their Field object is reachable in the image heap.
666+
*/
667+
analysisField.registerAsUnsafeAccessed("is registered for reflection.");
668+
}
651669

652670
/*
653671
* The generic signature is parsed at run time, so we need to make all the types necessary
@@ -992,7 +1010,7 @@ public void registerHeapReflectionField(Field reflectField, ScanReason reason) {
9921010
assert !sealed;
9931011
AnalysisField analysisField = metaAccess.lookupJavaField(reflectField);
9941012
if (heapFields.put(analysisField, reflectField) == null && !SubstitutionReflectivityFilter.shouldExclude(reflectField, metaAccess, universe)) {
995-
registerTypesForField(analysisField, reflectField);
1013+
registerTypesForField(analysisField, reflectField, false);
9961014
if (analysisField.getDeclaringClass().isAnnotation()) {
9971015
processAnnotationField(reflectField);
9981016
}

0 commit comments

Comments
 (0)