Skip to content

Commit 0cda565

Browse files
committed
Fix: Properly trace "accessed" members; report missing registration errors for constructors & fields
1 parent 4b94ec8 commit 0cda565

File tree

12 files changed

+133
-100
lines changed

12 files changed

+133
-100
lines changed

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,19 @@ public static String toInternalSignature(List<?> parameterTypes) {
8080
return sb.append(')').toString();
8181
}
8282

83+
public static String toInternalSignature(Class<?>[] parameters) {
84+
List<String> names;
85+
if (parameters == null) {
86+
names = List.of();
87+
} else {
88+
names = new ArrayList<>(parameters.length);
89+
for (Class<?> parameter : parameters) {
90+
names.add(parameter.getName());
91+
}
92+
}
93+
return toInternalSignature(names);
94+
}
95+
8396
public static String toInternalClassName(String qualifiedForNameString) {
8497
assert qualifiedForNameString.indexOf('/') == -1 : "Requires qualified Java name, not internal representation";
8598
assert !qualifiedForNameString.endsWith("[]") : "Requires Class.forName syntax, for example '[Ljava.lang.String;'";

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java

Lines changed: 7 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
*/
2525
package com.oracle.svm.core.hub;
2626

27-
import static com.oracle.svm.configure.config.ConfigurationMemberInfo.ConfigurationMemberAccessibility;
2827
import static com.oracle.svm.configure.config.ConfigurationMemberInfo.ConfigurationMemberDeclaration;
2928
import static com.oracle.svm.core.MissingRegistrationUtils.throwMissingRegistrationErrors;
3029
import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE;
@@ -91,8 +90,6 @@
9190
import org.graalvm.nativeimage.Platform;
9291
import org.graalvm.nativeimage.Platforms;
9392

94-
import com.oracle.svm.configure.ConfigurationTypeDescriptor;
95-
import com.oracle.svm.configure.config.ConfigurationType;
9693
import com.oracle.svm.configure.config.SignatureUtil;
9794
import com.oracle.svm.core.BuildPhaseProvider.AfterHostedUniverse;
9895
import com.oracle.svm.core.BuildPhaseProvider.CompileQueueFinished;
@@ -762,7 +759,7 @@ private ReflectionMetadata reflectionMetadata() {
762759

763760
private void checkClassFlag(int mask, String methodName) {
764761
if (MetadataTracer.enabled()) {
765-
traceClassFlagQuery(mask);
762+
MetadataTracer.singleton().traceReflectionType(toClass(this));
766763
}
767764
if (throwMissingRegistrationErrors() && !(isClassFlagSet(mask) && getConditions().satisfied())) {
768765
MissingReflectionRegistrationUtils.reportClassQuery(DynamicHub.toClass(this), methodName);
@@ -786,30 +783,6 @@ private static boolean isClassFlagSet(int mask, ReflectionMetadata reflectionMet
786783
return reflectionMetadata != null && (reflectionMetadata.classFlags & mask) != 0;
787784
}
788785

789-
private void traceClassFlagQuery(int mask) {
790-
ConfigurationType type = MetadataTracer.singleton().traceReflectionTypeImpl(ConfigurationTypeDescriptor.fromClass(toClass(this)));
791-
if (type == null) {
792-
return;
793-
}
794-
// TODO (GR-64765): We over-approximate member accessibility here because we don't trace
795-
// accesses. Once we trace accesses, it will suffice to register the class for reflection.
796-
switch (mask) {
797-
case ALL_FIELDS_FLAG -> type.setAllPublicFields(ConfigurationMemberAccessibility.ACCESSED);
798-
case ALL_DECLARED_FIELDS_FLAG -> type.setAllDeclaredFields(ConfigurationMemberAccessibility.ACCESSED);
799-
case ALL_METHODS_FLAG -> type.setAllPublicMethods(ConfigurationMemberAccessibility.ACCESSED);
800-
case ALL_DECLARED_METHODS_FLAG -> type.setAllDeclaredMethods(ConfigurationMemberAccessibility.ACCESSED);
801-
case ALL_CONSTRUCTORS_FLAG -> type.setAllPublicConstructors(ConfigurationMemberAccessibility.ACCESSED);
802-
case ALL_DECLARED_CONSTRUCTORS_FLAG -> type.setAllDeclaredConstructors(ConfigurationMemberAccessibility.ACCESSED);
803-
case ALL_CLASSES_FLAG -> type.setAllPublicClasses();
804-
case ALL_DECLARED_CLASSES_FLAG -> type.setAllDeclaredClasses();
805-
case ALL_RECORD_COMPONENTS_FLAG -> type.setAllRecordComponents();
806-
case ALL_PERMITTED_SUBCLASSES_FLAG -> type.setAllPermittedSubclasses();
807-
case ALL_NEST_MEMBERS_FLAG -> type.setAllNestMembers();
808-
case ALL_SIGNERS_FLAG -> type.setAllSigners();
809-
default -> throw VMError.shouldNotReachHere("unknown class flag " + mask);
810-
}
811-
}
812-
813786
/** Executed at runtime. */
814787
private static Object initEnumConstantsAtRuntime(Method values) {
815788
try {
@@ -1397,13 +1370,13 @@ private void checkField(String fieldName, Field field, boolean publicOnly) throw
13971370
private void traceFieldLookup(String fieldName, Field field, boolean publicOnly) {
13981371
ConfigurationMemberDeclaration declaration = publicOnly ? ConfigurationMemberDeclaration.PRESENT : ConfigurationMemberDeclaration.DECLARED;
13991372
if (field != null) {
1400-
// register declaring type and field
1401-
MetadataTracer.singleton().traceField(field.getDeclaringClass(), fieldName, declaration);
1373+
// register declaring type (registers all fields for lookup)
1374+
MetadataTracer.singleton().traceReflectionType(field.getDeclaringClass());
14021375
// register receiver type
14031376
MetadataTracer.singleton().traceReflectionType(toClass(this));
14041377
} else {
14051378
// register receiver type and negative field query
1406-
MetadataTracer.singleton().traceField(toClass(this), fieldName, declaration);
1379+
MetadataTracer.singleton().traceFieldAccess(toClass(this), fieldName, declaration);
14071380
}
14081381
}
14091382

@@ -1476,28 +1449,14 @@ private boolean checkExecutableExists(String methodName, Class<?>[] parameterTyp
14761449
private void traceMethodLookup(String methodName, Class<?>[] parameterTypes, Executable method, boolean publicOnly) {
14771450
ConfigurationMemberDeclaration declaration = publicOnly ? ConfigurationMemberDeclaration.PRESENT : ConfigurationMemberDeclaration.DECLARED;
14781451
if (method != null) {
1479-
// register declaring type and method
1480-
// TODO (GR-64765) loosen to queried accessibility once method invocations are traced
1481-
MetadataTracer.singleton().traceMethod(method.getDeclaringClass(), methodName, toInternalSignature(parameterTypes), declaration, ConfigurationMemberAccessibility.ACCESSED);
1452+
// register declaring type (registers all methods for lookup)
1453+
MetadataTracer.singleton().traceReflectionType(method.getDeclaringClass());
14821454
// register receiver type
14831455
MetadataTracer.singleton().traceReflectionType(toClass(this));
14841456
} else {
14851457
// register receiver type and negative method query
1486-
MetadataTracer.singleton().traceMethod(toClass(this), methodName, toInternalSignature(parameterTypes), declaration, ConfigurationMemberAccessibility.QUERIED);
1487-
}
1488-
}
1489-
1490-
private static String toInternalSignature(Class<?>[] classes) {
1491-
List<String> names;
1492-
if (classes == null) {
1493-
names = List.of();
1494-
} else {
1495-
names = new ArrayList<>(classes.length);
1496-
for (Class<?> aClass : classes) {
1497-
names.add(aClass.getName());
1498-
}
1458+
MetadataTracer.singleton().traceMethodAccess(toClass(this), methodName, SignatureUtil.toInternalSignature(parameterTypes), declaration);
14991459
}
1500-
return SignatureUtil.toInternalSignature(names);
15011460
}
15021461

15031462
private boolean allElementsRegistered(boolean publicOnly, int allDeclaredElementsFlag, int allPublicElementsFlag) {

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/access/JNIReflectionDictionary.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -274,9 +274,8 @@ public static JNIMethodId getDeclaredMethodID(Class<?> classObject, JNIAccessibl
274274
private static JNIAccessibleMethod getDeclaredMethod(Class<?> classObject, JNIAccessibleMethodDescriptor descriptor, String dumpLabel) {
275275
if (MetadataTracer.enabled()) {
276276
MetadataTracer.singleton().traceJNIType(classObject);
277-
// TODO (GR-64765) loosen to queried accessibility once method invocations are traced
278-
MetadataTracer.singleton().traceMethod(classObject, descriptor.getNameConvertToString(), descriptor.getSignatureConvertToString(),
279-
ConfigurationMemberInfo.ConfigurationMemberDeclaration.DECLARED, ConfigurationMemberInfo.ConfigurationMemberAccessibility.ACCESSED);
277+
MetadataTracer.singleton().traceMethodAccess(classObject, descriptor.getNameConvertToString(), descriptor.getSignatureConvertToString(),
278+
ConfigurationMemberInfo.ConfigurationMemberDeclaration.DECLARED);
280279
}
281280
boolean foundClass = false;
282281
for (var dictionary : layeredSingletons()) {
@@ -335,7 +334,7 @@ private static JNIAccessibleMethod checkMethod(JNIAccessibleMethod method, Class
335334
private static JNIAccessibleField getDeclaredField(Class<?> classObject, CharSequence name, boolean isStatic, String dumpLabel) {
336335
if (MetadataTracer.enabled()) {
337336
MetadataTracer.singleton().traceJNIType(classObject);
338-
MetadataTracer.singleton().traceField(classObject, name.toString(), ConfigurationMemberInfo.ConfigurationMemberDeclaration.DECLARED);
337+
MetadataTracer.singleton().traceFieldAccess(classObject, name.toString(), ConfigurationMemberInfo.ConfigurationMemberDeclaration.DECLARED);
339338
}
340339
boolean foundClass = false;
341340
for (var dictionary : layeredSingletons()) {

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/metadata/MetadataTracer.java

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -197,9 +197,9 @@ public void traceReflectionArrayType(Class<?> componentClazz) {
197197
}
198198

199199
/**
200-
* Marks the given field as reachable from reflection.
200+
* Marks the given field as accessible from reflection.
201201
*/
202-
public void traceField(Class<?> declaringClass, String fieldName, ConfigurationMemberInfo.ConfigurationMemberDeclaration declaration) {
202+
public void traceFieldAccess(Class<?> declaringClass, String fieldName, ConfigurationMemberInfo.ConfigurationMemberDeclaration declaration) {
203203
ConfigurationTypeDescriptor typeDescriptor = ConfigurationTypeDescriptor.fromClass(declaringClass);
204204
ConfigurationType type = traceReflectionTypeImpl(typeDescriptor);
205205
if (type != null) {
@@ -209,15 +209,14 @@ public void traceField(Class<?> declaringClass, String fieldName, ConfigurationM
209209
}
210210

211211
/**
212-
* Marks the given method as reachable from reflection.
212+
* Marks the given method as accessible from reflection.
213213
*/
214-
public void traceMethod(Class<?> declaringClass, String methodName, String internalSignature, ConfigurationMemberInfo.ConfigurationMemberDeclaration declaration,
215-
ConfigurationMemberInfo.ConfigurationMemberAccessibility accessibility) {
214+
public void traceMethodAccess(Class<?> declaringClass, String methodName, String internalSignature, ConfigurationMemberInfo.ConfigurationMemberDeclaration declaration) {
216215
ConfigurationTypeDescriptor typeDescriptor = ConfigurationTypeDescriptor.fromClass(declaringClass);
217216
ConfigurationType type = traceReflectionTypeImpl(typeDescriptor);
218217
if (type != null) {
219-
debugMethod(typeDescriptor, methodName, internalSignature, accessibility);
220-
type.addMethod(methodName, internalSignature, declaration, accessibility);
218+
debugMethod(typeDescriptor, methodName, internalSignature);
219+
type.addMethod(methodName, internalSignature, declaration, ConfigurationMemberInfo.ConfigurationMemberAccessibility.ACCESSED);
221220
}
222221
}
223222

@@ -242,13 +241,7 @@ public void traceProxyType(Class<?>[] interfaces) {
242241
traceReflectionTypeImpl(descriptor);
243242
}
244243

245-
/*
246-
* TODO (GR-64765): This method should not be used outside of this class, but we need it for
247-
* tracing class flag queries. Once class flag tracing is unnecessary, mark it private. We do
248-
* not want to expose ConfigurationTypes to the caller, because they could perform further
249-
* registration (e.g., of methods) which would not be traced by debug logging.
250-
*/
251-
public ConfigurationType traceReflectionTypeImpl(ConfigurationTypeDescriptor typeDescriptor) {
244+
private ConfigurationType traceReflectionTypeImpl(ConfigurationTypeDescriptor typeDescriptor) {
252245
assert enabledAtRunTime();
253246
if (isInternal(typeDescriptor)) {
254247
debug("type not registered for reflection (uses an internal interface)", typeDescriptor);
@@ -406,11 +399,11 @@ private void debugField(ConfigurationTypeDescriptor typeDescriptor, String field
406399
/**
407400
* Debug helper for methods. Avoids method name computations if debug logging is disabled.
408401
*/
409-
private void debugMethod(ConfigurationTypeDescriptor typeDescriptor, String methodName, String internalSignature, ConfigurationMemberInfo.ConfigurationMemberAccessibility accessibility) {
402+
private void debugMethod(ConfigurationTypeDescriptor typeDescriptor, String methodName, String internalSignature) {
410403
if (debugWriter == null) {
411404
return;
412405
}
413-
debug("method registered for reflection (" + accessibility.name().toLowerCase() + ")", typeDescriptor + "." + methodName + internalSignature);
406+
debug("method registered for reflection", typeDescriptor + "." + methodName + internalSignature);
414407
}
415408

416409
/**

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodHandleNatives.java

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
import com.oracle.svm.core.imagelayer.ImageLayerBuildingSupport;
5353
import com.oracle.svm.core.invoke.Target_java_lang_invoke_MemberName;
5454
import com.oracle.svm.core.layeredimagesingleton.MultiLayeredImageSingleton;
55+
import com.oracle.svm.core.reflect.UnsafeFieldUtil;
5556
import com.oracle.svm.core.reflect.target.Target_java_lang_reflect_Field;
5657
import com.oracle.svm.core.util.VMError;
5758
import com.oracle.svm.util.ReflectionUtil;
@@ -134,11 +135,7 @@ private static long objectFieldOffset(Target_java_lang_invoke_MemberName self) {
134135
if (self.intrinsic != null) {
135136
return -1L;
136137
}
137-
int offset = SubstrateUtil.cast(self.reflectAccess, Target_java_lang_reflect_Field.class).offset;
138-
if (offset == -1) {
139-
throw unsupportedFeature("Trying to access field " + self.reflectAccess + " without registering it as unsafe accessed.");
140-
}
141-
return offset;
138+
return UnsafeFieldUtil.getFieldOffset(SubstrateUtil.cast(self.reflectAccess, Target_java_lang_reflect_Field.class));
142139
}
143140

144141
@Substitute
@@ -153,11 +150,7 @@ private static long staticFieldOffset(Target_java_lang_invoke_MemberName self) {
153150
if (self.intrinsic != null) {
154151
return -1L;
155152
}
156-
int offset = SubstrateUtil.cast(self.reflectAccess, Target_java_lang_reflect_Field.class).offset;
157-
if (offset == -1) {
158-
throw unsupportedFeature("Trying to access field " + self.reflectAccess + " without registering it as unsafe accessed.");
159-
}
160-
return offset;
153+
return UnsafeFieldUtil.getFieldOffset(SubstrateUtil.cast(self.reflectAccess, Target_java_lang_reflect_Field.class));
161154
}
162155

163156
@Substitute
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
package com.oracle.svm.core.reflect;
26+
27+
import java.lang.reflect.Field;
28+
29+
import com.oracle.svm.configure.config.ConfigurationMemberInfo;
30+
import com.oracle.svm.core.SubstrateUtil;
31+
import com.oracle.svm.core.metadata.MetadataTracer;
32+
import com.oracle.svm.core.reflect.target.Target_java_lang_reflect_AccessibleObject;
33+
import com.oracle.svm.core.reflect.target.Target_java_lang_reflect_Field;
34+
35+
public class UnsafeFieldUtil {
36+
public static long getFieldOffset(Target_java_lang_reflect_Field field) {
37+
if (field == null) {
38+
throw new NullPointerException();
39+
}
40+
if (MetadataTracer.enabled()) {
41+
traceFieldAccess(SubstrateUtil.cast(field, Field.class));
42+
}
43+
int offset = field.root == null ? field.offset : field.root.offset;
44+
boolean conditionsSatisfied = SubstrateUtil.cast(field, Target_java_lang_reflect_AccessibleObject.class).conditions.satisfied();
45+
if (offset <= 0 || !conditionsSatisfied) {
46+
throw MissingReflectionRegistrationUtils.reportAccessedField(SubstrateUtil.cast(field, Field.class));
47+
}
48+
return offset;
49+
}
50+
51+
private static void traceFieldAccess(Field f) {
52+
MetadataTracer.singleton().traceFieldAccess(f.getDeclaringClass(), f.getName(), ConfigurationMemberInfo.ConfigurationMemberDeclaration.DECLARED);
53+
}
54+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public static Constructor<?> newConstructor(RuntimeConditionSet conditions, Clas
6868
byte[] annotations, byte[] parameterAnnotations, Object accessor, byte[] rawParameters, byte[] typeAnnotations) {
6969
Target_java_lang_reflect_Constructor ctor = new Target_java_lang_reflect_Constructor();
7070
ctor.constructor(declaringClass, parameterTypes, exceptionTypes, modifiers, -1, signature, annotations, parameterAnnotations);
71-
ctor.constructorAccessor = (Target_jdk_internal_reflect_ConstructorAccessor) accessor;
71+
ctor.constructorAccessorFromMetadata = (Target_jdk_internal_reflect_ConstructorAccessor) accessor;
7272
SubstrateUtil.cast(ctor, Target_java_lang_reflect_Executable.class).rawParameters = rawParameters;
7373
Target_java_lang_reflect_AccessibleObject accessibleObject = SubstrateUtil.cast(ctor, Target_java_lang_reflect_AccessibleObject.class);
7474
accessibleObject.typeAnnotations = typeAnnotations;

0 commit comments

Comments
 (0)