Skip to content

Commit 82260b6

Browse files
committed
[GR-47365] Integrate proxy registration to the reflection config
PullRequest: graal/15025
2 parents dd8dcad + c8d9b02 commit 82260b6

28 files changed

+411
-206
lines changed

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

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -323,22 +323,26 @@ The following methods are evaluated at build time when called with constant argu
323323
324324
### Dynamic Proxy Metadata in JSON
325325
326-
Dynamic proxy metadata should be specified in a _proxy-config.json_ file and conform to the JSON schema defined in
327-
[proxy-config-schema-v1.0.0.json](https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/proxy-config-schema-v1.0.0.json).
328-
The schema also includes further details and explanations how this configuration works. Here is the example of the proxy-config.json:
326+
Dynamic proxy metadata should be specified as part of a _reflect-config.json_ file by adding `"proxy"`-type entries, conforming to the JSON schema defined in [config-type-schema-v1.1.0.json](https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/config-type-schema-v1.0.0.json).
327+
It enables you to register members of a proxy class for reflection the same way as it would be done for a named class.
328+
The order in which interfaces are given matters and the interfaces will be passed in the same order to generate the proxy class.
329+
The schema also includes further details and explanations how this configuration works.
330+
Here is an example of dynamic proxy metadata in reflect-config.json:
329331
```json
330332
[
331333
{
332334
"condition": {
333335
"typeReachable": "<condition-class>"
334336
},
335-
"interfaces": [
336-
"IA",
337-
"IB"
338-
]
337+
"type": { "proxy": [
338+
"IA",
339+
"IB"
340+
]}
339341
}
340342
]
341343
```
344+
Contents of _proxy-config.json_ files will still be parsed and honored by Native Image, but this file is now deprecated
345+
and the [Native Image agent](AutomaticMetadataCollection.md) outputs proxy metadata to reflect-config.json.
342346
343347
## Serialization
344348
Java can serialize any class that implements the `Serializable` interface.
@@ -388,6 +392,14 @@ The schema also includes further details and explanations how this configuration
388392
},
389393
"type": "<fully-qualified-class-name>",
390394
"customTargetConstructorClass": "<custom-target-constructor-class>"
395+
},
396+
{
397+
"condition": {
398+
"typeReachable": "<condition-class>"
399+
},
400+
"type": {
401+
"proxy": ["<fully-qualified-interface-name-1>", "<fully-qualified-interface-name-n>"]
402+
}
391403
}
392404
],
393405
"lambdaCapturingTypes": [
@@ -397,15 +409,7 @@ The schema also includes further details and explanations how this configuration
397409
},
398410
"name": "<fully-qualified-class-name>"
399411
}
400-
],
401-
"proxies": [
402-
{
403-
"condition": {
404-
"typeReachable": "<condition-class>"
405-
},
406-
"interfaces": ["<fully-qualified-interface-name-1>", "<fully-qualified-interface-name-n>"]
407-
}
408-
]
412+
]
409413
}
410414
```
411415

substratevm/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ This changelog summarizes major changes to GraalVM Native Image.
2222
* (GR-51172) Add support to catch OutOfMemoryError exceptions on native image if there is no memory left.
2323
* (GR-43837) `--report-unsupported-elements-at-runtime` is now enabled by default and the option is deprecated.
2424
* (GR-53359) Provide the `.debug_gdb_scripts` section that triggers auto-loading of `svmhelpers.py` in GDB. Remove single and double quotes from `ClassLoader.nameAndId` in the debuginfo.
25+
* (GR-47365) Include dynamic proxy metadata in the reflection metadata with the syntax `"type": { "proxy": [<interface list>] }`. This allows members of proxy classes to be accessed reflectively. `proxy-config.json` is now deprecated but will still be honored.
2526

2627
## GraalVM for JDK 22 (Internal Version 24.0.0)
2728
* (GR-48304) Red Hat added support for the JFR event ThreadAllocationStatistics.

substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java

Lines changed: 42 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -191,8 +191,8 @@ private static void traceBreakpoint(JNIEnvironment env, String context, JNIObjec
191191
if (tracer != null) {
192192
tracer.traceCall(context,
193193
function,
194-
getClassNameOr(env, clazz, null, Tracer.UNKNOWN_VALUE),
195-
getClassNameOr(env, declaringClass, null, Tracer.UNKNOWN_VALUE),
194+
getClassOrProxyInterfaceNames(env, clazz),
195+
getClassOrProxyInterfaceNames(env, declaringClass),
196196
getClassNameOr(env, callerClass, null, Tracer.UNKNOWN_VALUE),
197197
result,
198198
stackTrace,
@@ -210,6 +210,39 @@ private static void traceBreakpoint(JNIEnvironment env, String context, JNIObjec
210210
}
211211
}
212212

213+
/**
214+
* If the given class is a proxy, returns an array containing the names of its implemented
215+
* interfaces, else returns the class name. This prevents classes with arbitrary names from
216+
* being exposed outside the agent, since those names only make sense within a single execution
217+
* of the program.
218+
*
219+
* @param env JNI environment of the thread running the JVMTI callback.
220+
* @param clazz Handle to the class.
221+
* @return The interface, or the original class if it is not a proxy or implements multiple
222+
* interfaces.
223+
*/
224+
private static Object getClassOrProxyInterfaceNames(JNIEnvironment env, JNIObjectHandle clazz) {
225+
if (clazz.equal(nullHandle())) {
226+
return null;
227+
}
228+
229+
boolean isProxy = Support.callStaticBooleanMethodL(env, agent.handles().getJavaLangReflectProxy(env), agent.handles().getJavaLangReflectProxyIsProxyClass(env), clazz);
230+
if (Support.clearException(env)) {
231+
return Tracer.UNKNOWN_VALUE;
232+
}
233+
234+
if (!isProxy) {
235+
return getClassNameOr(env, clazz, null, Tracer.UNKNOWN_VALUE);
236+
}
237+
238+
JNIObjectHandle interfaces = Support.callObjectMethod(env, clazz, agent.handles().javaLangClassGetInterfaces);
239+
if (Support.clearException(env)) {
240+
return Tracer.UNKNOWN_VALUE;
241+
}
242+
243+
return getClassArrayNames(env, interfaces);
244+
}
245+
213246
private static boolean forName(JNIEnvironment jni, JNIObjectHandle thread, Breakpoint bp, InterceptedState state) {
214247
JNIObjectHandle callerClass = state.getDirectCallerClass();
215248
JNIObjectHandle name = getObjectArgument(thread, 0);
@@ -232,7 +265,7 @@ private static boolean getDeclaredFields(JNIEnvironment jni, JNIObjectHandle thr
232265
private static boolean handleGetFields(JNIEnvironment jni, JNIObjectHandle thread, Breakpoint bp, InterceptedState state) {
233266
JNIObjectHandle callerClass = state.getDirectCallerClass();
234267
JNIObjectHandle self = getReceiver(thread);
235-
traceReflectBreakpoint(jni, getClassOrSingleProxyInterface(jni, self), nullHandle(), callerClass, bp.specification.methodName, null, state.getFullStackTraceOrNull());
268+
traceReflectBreakpoint(jni, self, nullHandle(), callerClass, bp.specification.methodName, null, state.getFullStackTraceOrNull());
236269
return true;
237270
}
238271

@@ -256,8 +289,7 @@ private static boolean handleGetMethods(JNIEnvironment jni, JNIObjectHandle thre
256289
JNIObjectHandle callerClass = state.getDirectCallerClass();
257290
JNIObjectHandle self = getReceiver(thread);
258291
/* When reflection metadata tracking is disabled, all methods are considered invoked */
259-
traceReflectBreakpoint(jni, getClassOrSingleProxyInterface(jni, self), nullHandle(), callerClass, bp.specification.methodName, trackReflectionMetadata ? null : true,
260-
state.getFullStackTraceOrNull());
292+
traceReflectBreakpoint(jni, self, nullHandle(), callerClass, bp.specification.methodName, trackReflectionMetadata ? null : true, state.getFullStackTraceOrNull());
261293
return true;
262294
}
263295

@@ -315,8 +347,7 @@ private static boolean handleGetField(JNIEnvironment jni, JNIObjectHandle thread
315347
declaring = nullHandle();
316348
}
317349
}
318-
traceReflectBreakpoint(jni, getClassOrSingleProxyInterface(jni, self), getClassOrSingleProxyInterface(jni, declaring), callerClass, bp.specification.methodName, name.notEqual(nullHandle()),
319-
state.getFullStackTraceOrNull(), fromJniString(jni, name));
350+
traceReflectBreakpoint(jni, self, declaring, callerClass, bp.specification.methodName, name.notEqual(nullHandle()), state.getFullStackTraceOrNull(), fromJniString(jni, name));
320351
return true;
321352
}
322353

@@ -379,8 +410,7 @@ private static boolean getConstructor(JNIEnvironment jni, JNIObjectHandle thread
379410
JNIObjectHandle self = getReceiver(thread);
380411
JNIObjectHandle paramTypesHandle = getObjectArgument(thread, 1);
381412
Object paramTypes = getClassArrayNames(jni, paramTypesHandle);
382-
traceReflectBreakpoint(jni, getClassOrSingleProxyInterface(jni, self), nullHandle(), callerClass, bp.specification.methodName, true, state.getFullStackTraceOrNull(),
383-
paramTypes);
413+
traceReflectBreakpoint(jni, self, nullHandle(), callerClass, bp.specification.methodName, true, state.getFullStackTraceOrNull(), paramTypes);
384414
return true;
385415
}
386416

@@ -410,8 +440,7 @@ private static boolean handleGetMethod(JNIEnvironment jni, JNIObjectHandle threa
410440
}
411441
String name = fromJniString(jni, nameHandle);
412442
Object paramTypes = getClassArrayNames(jni, paramTypesHandle);
413-
traceReflectBreakpoint(jni, getClassOrSingleProxyInterface(jni, self), getClassOrSingleProxyInterface(jni, declaring), callerClass, bp.specification.methodName,
414-
nameHandle.notEqual(nullHandle()), state.getFullStackTraceOrNull(), name, paramTypes);
443+
traceReflectBreakpoint(jni, self, declaring, callerClass, bp.specification.methodName, nameHandle.notEqual(nullHandle()), state.getFullStackTraceOrNull(), name, paramTypes);
415444
return true;
416445
}
417446

@@ -476,8 +505,7 @@ private static boolean handleInvokeMethod(JNIEnvironment jni, JNIObjectHandle th
476505
}
477506
Object paramTypes = getClassArrayNames(jni, paramTypesHandle);
478507

479-
traceReflectBreakpoint(jni, getClassOrSingleProxyInterface(jni, declaring), getClassOrSingleProxyInterface(jni, declaring), callerClass, "invokeMethod", declaring.notEqual(nullHandle()),
480-
state.getFullStackTraceOrNull(), name, paramTypes);
508+
traceReflectBreakpoint(jni, declaring, declaring, callerClass, "invokeMethod", declaring.notEqual(nullHandle()), state.getFullStackTraceOrNull(), name, paramTypes);
481509

482510
/*
483511
* Calling Class.newInstance through Method.invoke should register the class for reflective
@@ -524,8 +552,7 @@ private static boolean handleInvokeConstructor(JNIEnvironment jni, @SuppressWarn
524552
}
525553
Object paramTypes = getClassArrayNames(jni, paramTypesHandle);
526554

527-
traceReflectBreakpoint(jni, getClassOrSingleProxyInterface(jni, declaring), getClassOrSingleProxyInterface(jni, declaring), callerClass, "invokeConstructor", declaring.notEqual(nullHandle()),
528-
state.getFullStackTraceOrNull(), paramTypes);
555+
traceReflectBreakpoint(jni, declaring, declaring, callerClass, "invokeConstructor", declaring.notEqual(nullHandle()), state.getFullStackTraceOrNull(), paramTypes);
529556
return true;
530557
}
531558

@@ -1521,39 +1548,6 @@ private static JNIMethodId resolveBreakpointMethod(JNIEnvironment jni, JNIObject
15211548
return method;
15221549
}
15231550

1524-
/**
1525-
* If the given class is a proxy implementing a single interface, returns this interface. This
1526-
* prevents classes with arbitrary names from being exposed outside the agent, since those names
1527-
* only make sense within a single execution of the program.
1528-
*
1529-
* @param env JNI environment of the thread running the JVMTI callback.
1530-
* @param clazz Handle to the class.
1531-
* @return The interface, or the original class if it is not a proxy or implements multiple
1532-
* interfaces.
1533-
*/
1534-
public static JNIObjectHandle getClassOrSingleProxyInterface(JNIEnvironment env, JNIObjectHandle clazz) {
1535-
boolean isProxy = Support.callStaticBooleanMethodL(env, agent.handles().getJavaLangReflectProxy(env), agent.handles().getJavaLangReflectProxyIsProxyClass(env), clazz);
1536-
if (Support.clearException(env) || !isProxy) {
1537-
return clazz;
1538-
}
1539-
1540-
JNIObjectHandle interfaces = Support.callObjectMethod(env, clazz, agent.handles().javaLangClassGetInterfaces);
1541-
if (Support.clearException(env) || interfaces.equal(nullHandle())) {
1542-
return clazz;
1543-
}
1544-
1545-
int interfacesLength = Support.jniFunctions().getGetArrayLength().invoke(env, interfaces);
1546-
guarantee(!Support.clearException(env));
1547-
if (interfacesLength != 1) {
1548-
return clazz;
1549-
}
1550-
1551-
JNIObjectHandle iface = Support.jniFunctions().getGetObjectArrayElement().invoke(env, interfaces, 0);
1552-
guarantee(!Support.clearException(env) && iface.notEqual(nullHandle()));
1553-
1554-
return iface;
1555-
}
1556-
15571551
private static void bindNativeBreakpoint(JNIEnvironment jni, NativeBreakpoint bp, CodePointer originalAddress, WordPointer newAddressPtr) {
15581552
assert !recursive.get();
15591553
bp.replacedFunction = originalAddress;

substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/OmitPreviousConfigTests.java

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@
5252
import com.oracle.svm.configure.config.ResourceConfiguration;
5353
import com.oracle.svm.configure.config.SerializationConfiguration;
5454
import com.oracle.svm.configure.config.TypeConfiguration;
55+
import com.oracle.svm.core.configure.ConfigurationTypeDescriptor;
56+
import com.oracle.svm.core.configure.NamedConfigurationTypeDescriptor;
5557
import com.oracle.svm.core.util.VMError;
5658

5759
public class OmitPreviousConfigTests {
@@ -142,8 +144,8 @@ private static void doTestTypeConfig(TypeConfiguration typeConfig) {
142144
}
143145

144146
private static void doTestExpectedMissingTypes(TypeConfiguration typeConfig) {
145-
Assert.assertNull(typeConfig.get(UnresolvedConfigurationCondition.alwaysTrue(), "FlagTestA"));
146-
Assert.assertNull(typeConfig.get(UnresolvedConfigurationCondition.alwaysTrue(), "FlagTestB"));
147+
Assert.assertNull(typeConfig.get(UnresolvedConfigurationCondition.alwaysTrue(), new NamedConfigurationTypeDescriptor("FlagTestA")));
148+
Assert.assertNull(typeConfig.get(UnresolvedConfigurationCondition.alwaysTrue(), new NamedConfigurationTypeDescriptor("FlagTestB")));
147149
}
148150

149151
private static void doTestTypeFlags(TypeConfiguration typeConfig) {
@@ -201,7 +203,7 @@ private static void doTestSerializationConfig(SerializationConfiguration seriali
201203
}
202204

203205
private static ConfigurationType getConfigTypeOrFail(TypeConfiguration typeConfig, String typeName) {
204-
ConfigurationType type = typeConfig.get(UnresolvedConfigurationCondition.alwaysTrue(), typeName);
206+
ConfigurationType type = typeConfig.get(UnresolvedConfigurationCondition.alwaysTrue(), new NamedConfigurationTypeDescriptor(typeName));
205207
Assert.assertNotNull(type);
206208
return type;
207209
}
@@ -289,14 +291,14 @@ void setFlags(ConfigurationType config) {
289291
}
290292
}
291293

292-
String getTypeName() {
293-
return TEST_CLASS_NAME_PREFIX + "_" + methodKind.name();
294+
ConfigurationTypeDescriptor getTypeName() {
295+
return new NamedConfigurationTypeDescriptor(TEST_CLASS_NAME_PREFIX + "_" + methodKind.name());
294296
}
295297

296298
void doTest() {
297299
TypeConfiguration currentConfigWithoutPrevious = currentConfig.copyAndSubtract(previousConfig);
298300

299-
String name = getTypeName();
301+
ConfigurationTypeDescriptor name = getTypeName();
300302
ConfigurationType configurationType = currentConfigWithoutPrevious.get(UnresolvedConfigurationCondition.alwaysTrue(), name);
301303
if (methodsThatMustExist.size() == 0) {
302304
Assert.assertNull("Generated configuration type " + name + " exists. Expected it to be cleared as it is empty.", configurationType);

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import java.util.List;
2929
import java.util.Objects;
3030

31+
import com.oracle.svm.core.configure.NamedConfigurationTypeDescriptor;
3132
import com.oracle.svm.core.util.json.JsonPrintable;
3233
import com.oracle.svm.core.util.json.JsonWriter;
3334

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

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939

4040
import com.oracle.svm.configure.config.ConfigurationMemberInfo.ConfigurationMemberAccessibility;
4141
import com.oracle.svm.configure.config.ConfigurationMemberInfo.ConfigurationMemberDeclaration;
42+
import com.oracle.svm.core.configure.ConfigurationTypeDescriptor;
4243
import com.oracle.svm.core.util.json.JsonPrintable;
4344
import com.oracle.svm.core.util.json.JsonPrinter;
4445
import com.oracle.svm.core.util.json.JsonWriter;
@@ -103,10 +104,6 @@ static ConfigurationType copyAndMerge(ConfigurationType type, ConfigurationType
103104
private ConfigurationMemberAccessibility allDeclaredConstructorsAccess = ConfigurationMemberAccessibility.NONE;
104105
private ConfigurationMemberAccessibility allPublicConstructorsAccess = ConfigurationMemberAccessibility.NONE;
105106

106-
public ConfigurationType(UnresolvedConfigurationCondition condition, String qualifiedJavaName, boolean includeAllElements) {
107-
this(condition, new NamedConfigurationTypeDescriptor(qualifiedJavaName), includeAllElements);
108-
}
109-
110107
public ConfigurationType(UnresolvedConfigurationCondition condition, ConfigurationTypeDescriptor typeDescriptor, boolean includeAllElements) {
111108
this.condition = condition;
112109
this.typeDescriptor = typeDescriptor;
@@ -445,8 +442,8 @@ public synchronized void setAllPublicConstructors(ConfigurationMemberAccessibili
445442
public synchronized void printJson(JsonWriter writer) throws IOException {
446443
writer.append('{').indent().newline();
447444
ConfigurationConditionPrintable.printConditionAttribute(condition, writer);
448-
/* GR-50385: Replace with "type" (and flip boolean entries below) */
449-
writer.quote("name").append(":");
445+
/* GR-50385: Flip boolean entries below when "type" includes them by default. */
446+
writer.quote("type").append(":");
450447
typeDescriptor.printJson(writer);
451448

452449
optionallyPrintJsonBoolean(writer, allDeclaredFields, "allDeclaredFields");

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import com.oracle.svm.configure.config.ConfigurationMemberInfo.ConfigurationMemberAccessibility;
3232
import com.oracle.svm.configure.config.ConfigurationMemberInfo.ConfigurationMemberDeclaration;
3333
import com.oracle.svm.core.TypeResult;
34+
import com.oracle.svm.core.configure.ConfigurationTypeDescriptor;
3435
import com.oracle.svm.core.configure.ReflectionConfigurationParserDelegate;
3536
import com.oracle.svm.core.util.VMError;
3637

@@ -43,10 +44,10 @@ public ParserConfigurationAdapter(TypeConfiguration configuration) {
4344
}
4445

4546
@Override
46-
public TypeResult<ConfigurationType> resolveType(UnresolvedConfigurationCondition condition, String typeName, boolean allowPrimitives, boolean includeAllElements) {
47-
ConfigurationType type = configuration.get(condition, typeName);
48-
ConfigurationType result = type != null ? type : new ConfigurationType(condition, typeName, includeAllElements);
49-
return TypeResult.forType(typeName, result);
47+
public TypeResult<ConfigurationType> resolveType(UnresolvedConfigurationCondition condition, ConfigurationTypeDescriptor typeDescriptor, boolean allowPrimitives, boolean includeAllElements) {
48+
ConfigurationType type = configuration.get(condition, typeDescriptor);
49+
ConfigurationType result = type != null ? type : new ConfigurationType(condition, typeDescriptor, includeAllElements);
50+
return TypeResult.forType(typeDescriptor.toString(), result);
5051
}
5152

5253
@Override

0 commit comments

Comments
 (0)