Skip to content

Commit c6771bc

Browse files
committed
Register reflectively-accessed types as unsafe allocated
1 parent 4877ac5 commit c6771bc

File tree

13 files changed

+41
-58
lines changed

13 files changed

+41
-58
lines changed

docs/reference-manual/native-image/ReachabilityMetadata.md

+6-10
Original file line numberDiff line numberDiff line change
@@ -331,11 +331,10 @@ In case the field-value-access metadata is missing, the following methods will t
331331

332332
### Unsafe Allocation of a Type
333333

334-
For unsafe allocation of a type via `sun.misc.Unsafe#allocateInstance(Class<?>)`, or from native code via `AllocObject(jClass)`, we must provide the following metadata:
334+
For unsafe allocation of a type via `sun.misc.Unsafe#allocateInstance(Class<?>)`, or from native code via `AllocObject(jClass)`, the type must be registered for reflection. The following metadata is sufficient:
335335
```json
336336
{
337-
"type": "FullyQualifiedUnsafeAllocatedType",
338-
"unsafeAllocated": true
337+
"type": "FullyQualifiedUnsafeAllocatedType"
339338
}
340339
```
341340
Otherwise, these methods will throw a `MissingReflectionRegistrationError`.
@@ -360,8 +359,7 @@ The overall definition of a type in JSON can have the following values:
360359
"allDeclaredMethods": true,
361360
"allPublicMethods": true,
362361
"allDeclaredFields": true,
363-
"allPublicFields": true,
364-
"unsafeAllocated": true
362+
"allPublicFields": true
365363
}
366364
```
367365

@@ -430,13 +428,12 @@ As a convenience, one can allow method invocation for groups of methods by addin
430428
`allDeclaredConstructors` and `allDeclaredMethods` allow calls invocations of methods declared on a given type.
431429
`allPublicConstructors` and `allPublicMethods` allow invocations of all public methods defined on a type and all of its supertypes.
432430

433-
To allocate objects of a type with `AllocObject`, the metadata must be stored in the `reflection` section:
431+
To allocate objects of a type with `AllocObject`, the type must be registered in the `reflection` section:
434432
```json
435433
{
436434
"reflection": [
437435
{
438-
"type": "jni.accessed.Type",
439-
"unsafeAllocated": true
436+
"type": "jni.unsafe.allocated.Type"
440437
}
441438
]
442439
}
@@ -698,8 +695,7 @@ See below is a sample reachability metadata configuration that you can use in _r
698695
"allDeclaredFields": true,
699696
"allPublicFields": true,
700697
"allDeclaredMethods": true,
701-
"allPublicMethods": true,
702-
"unsafeAllocated": true
698+
"allPublicMethods": true
703699
}
704700
],
705701
"jni": [

sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/ReflectionRegistry.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,10 @@
4646

4747
public interface ReflectionRegistry {
4848
default void register(ConfigurationCondition condition, Class<?>... classes) {
49-
Arrays.stream(classes).forEach(clazz -> register(condition, false, clazz));
49+
Arrays.stream(classes).forEach(clazz -> register(condition, clazz));
5050
}
5151

52-
void register(ConfigurationCondition condition, boolean unsafeAllocated, Class<?> clazz);
52+
void register(ConfigurationCondition condition, Class<?> clazz);
5353

5454
void register(ConfigurationCondition condition, boolean queriedOnly, Executable... methods);
5555

substratevm/CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ At runtime, premain runtime options are set along with main class' arguments in
1717
* (GR-58914) `ActiveProcessorCount` must be set at isolate or VM creation time.
1818
* (GR-59326) Ensure builder ForkJoin commonPool parallelism always respects NativeImageOptions.NumberOfThreads.
1919
* (GR-60234) Remove `"customTargetConstructorClass"` field from the serialization JSON metadata. All possible constructors are now registered by default when registering a type for serialization.
20+
* (GR-60235) Remove `"unsafeAllocated"` field from the reflection JSON metadata. All reflectively-accessible types are now automatically registered for unsafe allocation.
2021

2122
## GraalVM for JDK 23 (Internal Version 24.1.0)
2223
* (GR-51520) The old class initialization strategy, which was deprecated in GraalVM for JDK 22, is removed. The option `StrictImageHeap` no longer has any effect.

substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationType.java

-7
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,6 @@ static ConfigurationType copyAndMerge(ConfigurationType type, ConfigurationType
100100
private boolean allPublicClasses;
101101
private ConfigurationMemberAccessibility allDeclaredFieldsAccess = ConfigurationMemberAccessibility.NONE;
102102
private ConfigurationMemberAccessibility allPublicFieldsAccess = ConfigurationMemberAccessibility.NONE;
103-
private boolean unsafeAllocated;
104103
private ConfigurationMemberAccessibility allDeclaredMethodsAccess = ConfigurationMemberAccessibility.NONE;
105104
private ConfigurationMemberAccessibility allPublicMethodsAccess = ConfigurationMemberAccessibility.NONE;
106105
private ConfigurationMemberAccessibility allDeclaredConstructorsAccess = ConfigurationMemberAccessibility.NONE;
@@ -281,7 +280,6 @@ private void setFlagsFromOther(ConfigurationType other, BiPredicate<Boolean, Boo
281280
allPublicClasses = flagPredicate.test(allPublicClasses, other.allPublicClasses);
282281
allDeclaredFieldsAccess = accessCombiner.apply(allDeclaredFieldsAccess, other.allDeclaredFieldsAccess);
283282
allPublicFieldsAccess = accessCombiner.apply(allPublicFieldsAccess, other.allPublicFieldsAccess);
284-
unsafeAllocated = flagPredicate.test(unsafeAllocated, other.unsafeAllocated);
285283
allDeclaredMethodsAccess = accessCombiner.apply(allDeclaredMethodsAccess, other.allDeclaredMethodsAccess);
286284
allPublicMethodsAccess = accessCombiner.apply(allPublicMethodsAccess, other.allPublicMethodsAccess);
287285
allDeclaredConstructorsAccess = accessCombiner.apply(allDeclaredConstructorsAccess, other.allDeclaredConstructorsAccess);
@@ -411,10 +409,6 @@ public synchronized void setAllPublicClasses() {
411409
allPublicClasses = true;
412410
}
413411

414-
public void setUnsafeAllocated() {
415-
this.unsafeAllocated = true;
416-
}
417-
418412
public synchronized void setAllDeclaredFields(ConfigurationMemberAccessibility accessibility) {
419413
if (!allDeclaredFieldsAccess.includes(accessibility)) {
420414
allDeclaredFieldsAccess = accessibility;
@@ -470,7 +464,6 @@ public synchronized void printJson(JsonWriter writer) throws IOException {
470464
printJsonBooleanIfSet(writer, allPublicMethodsAccess == ConfigurationMemberAccessibility.ACCESSED, "allPublicMethods");
471465
printJsonBooleanIfSet(writer, allDeclaredConstructorsAccess == ConfigurationMemberAccessibility.ACCESSED, "allDeclaredConstructors");
472466
printJsonBooleanIfSet(writer, allPublicConstructorsAccess == ConfigurationMemberAccessibility.ACCESSED, "allPublicConstructors");
473-
printJsonBooleanIfSet(writer, unsafeAllocated, "unsafeAllocated");
474467

475468
if (fields != null) {
476469
writer.appendSeparator().quote("fields").appendFieldSeparator();

substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ParserConfigurationAdapter.java

-6
Original file line numberDiff line numberDiff line change
@@ -81,12 +81,6 @@ public boolean registerAllConstructors(UnresolvedConfigurationCondition conditio
8181
return true;
8282
}
8383

84-
@Override
85-
public void registerUnsafeAllocated(UnresolvedConfigurationCondition condition, ConfigurationType type) {
86-
VMError.guarantee(condition.equals(type.getCondition()), "condition is here part of the type");
87-
type.setUnsafeAllocated();
88-
}
89-
9084
@Override
9185
public void registerMethod(UnresolvedConfigurationCondition condition, boolean queriedOnly, ConfigurationType type, String methodName, List<ConfigurationType> methodParameterTypes) {
9286
VMError.guarantee(condition.equals(type.getCondition()), "condition is already a part of the type");

substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/JniProcessor.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -89,10 +89,10 @@ void processEntry(EconomicMap<String, ?> entry, ConfigurationSet configurationSe
8989
case "AllocObject":
9090
expectSize(args, 0);
9191
/*
92-
* AllocObject is implemented via Unsafe.allocateInstance, so we need to set the
93-
* "unsafe allocated" flag in the reflection configuration file.
92+
* AllocObject is implemented via Unsafe.allocateInstance, so we need to add the
93+
* type to the reflection configuration file.
9494
*/
95-
configurationSet.getReflectionConfiguration().getOrCreateType(condition, clazz).setUnsafeAllocated();
95+
configurationSet.getReflectionConfiguration().getOrCreateType(condition, clazz);
9696
break;
9797
case "GetStaticMethodID":
9898
case "GetMethodID": {

substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/ReflectionProcessor.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,8 @@ public void processEntry(EconomicMap<String, ?> entry, ConfigurationSet configur
283283
break;
284284
}
285285
case "allocateInstance": {
286-
configuration.getOrCreateType(condition, clazz).setUnsafeAllocated();
286+
/* Reflectively-accessed types are unsafe-allocatable by default */
287+
configuration.getOrCreateType(condition, clazz);
287288
break;
288289
}
289290
default:

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/LegacyReflectionConfigurationParser.java

+8-1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import java.util.List;
3030
import java.util.Optional;
3131

32+
import com.oracle.svm.util.LogUtils;
3233
import org.graalvm.collections.EconomicMap;
3334
import org.graalvm.collections.MapCursor;
3435
import org.graalvm.nativeimage.impl.UnresolvedConfigurationCondition;
@@ -56,6 +57,8 @@ public void parseAndRegister(Object json, URI origin) {
5657
parseClassArray(asList(json, "first level of document must be an array of class descriptors"));
5758
}
5859

60+
private boolean unsafeAllocatedWarningTriggered = false;
61+
5962
@Override
6063
protected void parseClass(EconomicMap<String, Object> data) {
6164
checkAttributes(data, "reflection class descriptor object", List.of(NAME_KEY), OPTIONAL_REFLECT_CONFIG_OBJECT_ATTRS);
@@ -93,6 +96,11 @@ protected void parseClass(EconomicMap<String, Object> data) {
9396
T clazz = result.get();
9497
delegate.registerType(conditionResult.get(), clazz);
9598

99+
if (!unsafeAllocatedWarningTriggered && data.containsKey("unsafeAllocated")) {
100+
unsafeAllocatedWarningTriggered = true;
101+
LogUtils.warning("\"unsafeAllocated\" is deprecated in reflection-config.json. All reflectively-accessed classes can be instantiated through unsafe without the use of the flag.");
102+
}
103+
96104
registerIfNotDefault(data, false, clazz, "allDeclaredConstructors", () -> delegate.registerDeclaredConstructors(condition, false, clazz));
97105
registerIfNotDefault(data, false, clazz, "allPublicConstructors", () -> delegate.registerPublicConstructors(condition, false, clazz));
98106
registerIfNotDefault(data, false, clazz, "allDeclaredMethods", () -> delegate.registerDeclaredMethods(condition, false, clazz));
@@ -117,7 +125,6 @@ protected void parseClass(EconomicMap<String, Object> data) {
117125
delegate.registerDeclaredFields(queryCondition, true, clazz);
118126
delegate.registerPublicFields(queryCondition, true, clazz);
119127
}
120-
registerIfNotDefault(data, false, clazz, "unsafeAllocated", () -> delegate.registerUnsafeAllocated(condition, clazz));
121128
MapCursor<String, Object> cursor = data.getEntries();
122129
while (cursor.advance()) {
123130
String name = cursor.getKey();

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParserDelegate.java

-2
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,6 @@ public interface ReflectionConfigurationParserDelegate<C, T> {
6868

6969
boolean registerAllConstructors(C condition, boolean queriedOnly, T type);
7070

71-
void registerUnsafeAllocated(C condition, T clazz);
72-
7371
String getTypeName(T type);
7472

7573
String getSimpleName(T type);

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionMetadataParser.java

+8-2
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import org.graalvm.nativeimage.impl.UnresolvedConfigurationCondition;
3535

3636
import com.oracle.svm.core.TypeResult;
37+
import com.oracle.svm.util.LogUtils;
3738

3839
class ReflectionMetadataParser<C, T> extends ReflectionConfigurationParser<C, T> {
3940
private static final List<String> OPTIONAL_REFLECT_METADATA_ATTRS = Arrays.asList(CONDITIONAL_KEY,
@@ -56,6 +57,8 @@ public void parseAndRegister(Object json, URI origin) {
5657
}
5758
}
5859

60+
private boolean unsafeAllocatedWarningTriggered = false;
61+
5962
@Override
6063
protected void parseClass(EconomicMap<String, Object> data) {
6164
checkAttributes(data, "reflection class descriptor object", List.of(TYPE_KEY), OPTIONAL_REFLECT_METADATA_ATTRS);
@@ -84,7 +87,7 @@ protected void parseClass(EconomicMap<String, Object> data) {
8487

8588
C queryCondition = conditionResolver.alwaysTrue();
8689
T clazz = result.get();
87-
delegate.registerType(conditionResult.get(), clazz);
90+
delegate.registerType(condition, clazz);
8891

8992
delegate.registerDeclaredClasses(queryCondition, clazz);
9093
delegate.registerRecordComponents(queryCondition, clazz);
@@ -98,14 +101,17 @@ protected void parseClass(EconomicMap<String, Object> data) {
98101
delegate.registerPublicMethods(queryCondition, true, clazz);
99102
delegate.registerDeclaredFields(queryCondition, true, clazz);
100103
delegate.registerPublicFields(queryCondition, true, clazz);
104+
if (!unsafeAllocatedWarningTriggered && data.containsKey("unsafeAllocated")) {
105+
unsafeAllocatedWarningTriggered = true;
106+
LogUtils.warning("\"unsafeAllocated\" is deprecated in reachability-metadata.json. All reflectively-accessed classes can be instantiated through unsafe without the use of the flag.");
107+
}
101108

102109
registerIfNotDefault(data, false, clazz, "allDeclaredConstructors", () -> delegate.registerDeclaredConstructors(condition, false, clazz));
103110
registerIfNotDefault(data, false, clazz, "allPublicConstructors", () -> delegate.registerPublicConstructors(condition, false, clazz));
104111
registerIfNotDefault(data, false, clazz, "allDeclaredMethods", () -> delegate.registerDeclaredMethods(condition, false, clazz));
105112
registerIfNotDefault(data, false, clazz, "allPublicMethods", () -> delegate.registerPublicMethods(condition, false, clazz));
106113
registerIfNotDefault(data, false, clazz, "allDeclaredFields", () -> delegate.registerDeclaredFields(condition, false, clazz));
107114
registerIfNotDefault(data, false, clazz, "allPublicFields", () -> delegate.registerPublicFields(condition, false, clazz));
108-
registerIfNotDefault(data, false, clazz, "unsafeAllocated", () -> delegate.registerUnsafeAllocated(condition, clazz));
109115

110116
MapCursor<String, Object> cursor = data.getEntries();
111117
while (cursor.advance()) {

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/RegistryAdapter.java

-12
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828

2929
import java.lang.reflect.Executable;
3030
import java.lang.reflect.Method;
31-
import java.lang.reflect.Modifier;
3231
import java.util.ArrayList;
3332
import java.util.List;
3433

@@ -223,17 +222,6 @@ public boolean registerAllConstructors(ConfigurationCondition condition, boolean
223222
return methods.length > 0;
224223
}
225224

226-
@Override
227-
public void registerUnsafeAllocated(ConfigurationCondition condition, Class<?> clazz) {
228-
if (!clazz.isArray() && !clazz.isInterface() && !Modifier.isAbstract(clazz.getModifiers())) {
229-
registry.register(condition, true, clazz);
230-
/*
231-
* Ignore otherwise as the implementation of allocateInstance will anyhow throw an
232-
* exception.
233-
*/
234-
}
235-
}
236-
237225
@Override
238226
public void registerMethod(ConfigurationCondition condition, boolean queriedOnly, Class<?> type, String methodName, List<Class<?>> methodParameterTypes) throws NoSuchMethodException {
239227
try {

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java

+2-3
Original file line numberDiff line numberDiff line change
@@ -219,8 +219,7 @@ private class JNIRuntimeAccessibilitySupportImpl extends ConditionalConfiguratio
219219
implements RuntimeJNIAccessSupport {
220220

221221
@Override
222-
public void register(ConfigurationCondition condition, boolean unsafeAllocated, Class<?> clazz) {
223-
assert !unsafeAllocated : "unsafeAllocated can be only set via Unsafe.allocateInstance, not via JNI.";
222+
public void register(ConfigurationCondition condition, Class<?> clazz) {
224223
Objects.requireNonNull(clazz, () -> nullErrorMessage("class"));
225224
abortIfSealed();
226225
registerConditionalConfiguration(condition, (cnd) -> newClasses.add(clazz));
@@ -252,7 +251,7 @@ private void registerFields(boolean finalIsWritable, Field[] fields) {
252251
@Override
253252
public void registerClassLookup(ConfigurationCondition condition, String typeName) {
254253
try {
255-
register(condition, false, Class.forName(typeName));
254+
register(condition, Class.forName(typeName));
256255
} catch (ClassNotFoundException e) {
257256
newNegativeClassLookups.add(typeName);
258257
}

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

+9-9
Original file line numberDiff line numberDiff line change
@@ -197,9 +197,9 @@ private boolean isQueryFlagSet(Class<?> clazz, int flag) {
197197
}
198198

199199
@Override
200-
public void register(ConfigurationCondition condition, boolean unsafeInstantiated, Class<?> clazz) {
200+
public void register(ConfigurationCondition condition, Class<?> clazz) {
201201
Objects.requireNonNull(clazz, () -> nullErrorMessage("class"));
202-
runConditionalInAnalysisTask(condition, (cnd) -> registerClass(cnd, clazz, unsafeInstantiated, true));
202+
runConditionalInAnalysisTask(condition, (cnd) -> registerClass(cnd, clazz, true));
203203
}
204204

205205
@Override
@@ -213,7 +213,7 @@ public void registerAllClassesQuery(ConfigurationCondition condition, Class<?> c
213213
/* Malformed inner classes can have no declaring class */
214214
innerClasses.computeIfAbsent(innerClass.getDeclaringClass(), c -> ConcurrentHashMap.newKeySet()).add(innerClass);
215215
}
216-
registerClass(cnd, innerClass, false, !MissingRegistrationUtils.throwMissingRegistrationErrors());
216+
registerClass(cnd, innerClass, !MissingRegistrationUtils.throwMissingRegistrationErrors());
217217
}
218218
} catch (LinkageError e) {
219219
registerLinkageError(clazz, e, classLookupExceptions);
@@ -239,22 +239,22 @@ public void registerAllDeclaredClassesQuery(ConfigurationCondition condition, Cl
239239
try {
240240
for (Class<?> innerClass : clazz.getDeclaredClasses()) {
241241
innerClasses.computeIfAbsent(clazz, c -> ConcurrentHashMap.newKeySet()).add(innerClass);
242-
registerClass(cnd, innerClass, false, !MissingRegistrationUtils.throwMissingRegistrationErrors());
242+
registerClass(cnd, innerClass, !MissingRegistrationUtils.throwMissingRegistrationErrors());
243243
}
244244
} catch (LinkageError e) {
245245
registerLinkageError(clazz, e, classLookupExceptions);
246246
}
247247
});
248248
}
249249

250-
private void registerClass(ConfigurationCondition condition, Class<?> clazz, boolean unsafeInstantiated, boolean allowForName) {
250+
private void registerClass(ConfigurationCondition condition, Class<?> clazz, boolean allowForName) {
251251
if (shouldExcludeClass(clazz)) {
252252
return;
253253
}
254254

255255
AnalysisType type = metaAccess.lookupJavaType(clazz);
256256
type.registerAsReachable("Is registered for reflection.");
257-
if (unsafeInstantiated) {
257+
if (type.isArray() || (type.isInstanceClass() && !type.isAbstract())) {
258258
type.registerAsUnsafeAllocated("Is registered via reflection metadata.");
259259
classForNameSupport.registerUnsafeAllocated(condition, clazz);
260260
}
@@ -291,7 +291,7 @@ public void registerClassLookupException(ConfigurationCondition condition, Strin
291291
public void registerClassLookup(ConfigurationCondition condition, String typeName) {
292292
runConditionalInAnalysisTask(condition, (cnd) -> {
293293
try {
294-
registerClass(cnd, Class.forName(typeName, false, ClassLoader.getSystemClassLoader()), false, true);
294+
registerClass(cnd, Class.forName(typeName, false, ClassLoader.getSystemClassLoader()), true);
295295
} catch (ClassNotFoundException e) {
296296
classForNameSupport.registerNegativeQuery(cnd, typeName);
297297
} catch (Throwable t) {
@@ -316,7 +316,7 @@ public void registerAllPermittedSubclassesQuery(ConfigurationCondition condition
316316
setQueryFlag(clazz, ALL_PERMITTED_SUBCLASSES_FLAG);
317317
if (clazz.isSealed()) {
318318
for (Class<?> permittedSubclass : clazz.getPermittedSubclasses()) {
319-
registerClass(condition, permittedSubclass, false, false);
319+
registerClass(condition, permittedSubclass, false);
320320
}
321321
}
322322
});
@@ -329,7 +329,7 @@ public void registerAllNestMembersQuery(ConfigurationCondition condition, Class<
329329
setQueryFlag(clazz, ALL_NEST_MEMBERS_FLAG);
330330
for (Class<?> nestMember : clazz.getNestMembers()) {
331331
if (nestMember != clazz) {
332-
registerClass(condition, nestMember, false, false);
332+
registerClass(condition, nestMember, false);
333333
}
334334
}
335335
});

0 commit comments

Comments
 (0)