diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapScanner.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapScanner.java index fa757545e9ac..b71030577aa5 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapScanner.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapScanner.java @@ -515,6 +515,10 @@ protected void onObjectReachable(ImageHeapConstant imageHeapConstant, ScanReason AnalysisType objectType = metaAccess.lookupJavaType(imageHeapConstant); imageHeap.addReachableObject(objectType, imageHeapConstant); + AnalysisType type = imageHeapConstant.getType(metaAccess); + Object object = bb.getSnippetReflectionProvider().asObject(Object.class, imageHeapConstant); + type.notifyObjectReachable(universe.getConcurrentAnalysisAccess(), object); + markTypeInstantiated(objectType, reason); if (imageHeapConstant instanceof ImageHeapObjectArray imageHeapArray) { AnalysisType arrayType = imageHeapArray.getType(metaAccess); diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisField.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisField.java index 453fd6234529..e8ed9ab33b56 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisField.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisField.java @@ -40,6 +40,7 @@ import com.oracle.graal.pointsto.infrastructure.OriginalFieldProvider; import com.oracle.graal.pointsto.infrastructure.WrappedJavaField; import com.oracle.graal.pointsto.typestate.TypeState; +import com.oracle.graal.pointsto.util.AnalysisError; import com.oracle.graal.pointsto.util.AnalysisFuture; import com.oracle.graal.pointsto.util.AtomicUtils; import com.oracle.graal.pointsto.util.ConcurrentLightHashSet; @@ -476,7 +477,7 @@ public void setPosition(int newPosition) { } public int getPosition() { - assert position != -1 : this; + AnalysisError.guarantee(position != -1, "Unknown position for field %s", this); return position; } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisType.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisType.java index 1a378ff0ca68..e87a907f9d3f 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisType.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisType.java @@ -37,6 +37,7 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Function; @@ -96,6 +97,9 @@ public abstract class AnalysisType extends AnalysisElement implements WrappedJav private static final AtomicReferenceFieldUpdater instantiatedNotificationsUpdater = AtomicReferenceFieldUpdater .newUpdater(AnalysisType.class, Object.class, "typeInstantiatedNotifications"); + private static final AtomicReferenceFieldUpdater objectReachableCallbacksUpdater = AtomicReferenceFieldUpdater + .newUpdater(AnalysisType.class, Object.class, "objectReachableCallbacks"); + private static final AtomicReferenceFieldUpdater isAllocatedUpdater = AtomicReferenceFieldUpdater .newUpdater(AnalysisType.class, Object.class, "isAllocated"); @@ -219,6 +223,11 @@ public enum UsageKind { */ @SuppressWarnings("unused") private volatile Object typeInstantiatedNotifications; + /** + * Contains callbacks that are executed when an object of this type is marked as reachable. + */ + @SuppressWarnings("unused") private volatile Object objectReachableCallbacks; + @SuppressWarnings("this-escape") public AnalysisType(AnalysisUniverse universe, ResolvedJavaType javaType, JavaKind storageKind, AnalysisType objectType, AnalysisType cloneableType) { this.universe = universe; @@ -614,6 +623,21 @@ public Set getOverrideReachabilityNotificat return ConcurrentLightHashMap.getOrDefault(this, overrideReachableNotificationsUpdater, method, Collections.emptySet()); } + public void registerObjectReachableCallback(BiConsumer callback) { + ConcurrentLightHashSet.addElement(this, objectReachableCallbacksUpdater, callback); + /* Register the callback with already discovered subtypes too. */ + for (AnalysisType subType : subTypes) { + /* Subtypes include this type itself. */ + if (!subType.equals(this)) { + subType.registerObjectReachableCallback(callback); + } + } + } + + public void notifyObjectReachable(DuringAnalysisAccess access, T object) { + ConcurrentLightHashSet.forEach(this, objectReachableCallbacksUpdater, (BiConsumer c) -> c.accept(access, object)); + } + public void registerInstantiatedCallback(Consumer callback) { if (this.isInstantiated()) { /* If the type is already instantiated just trigger the callback. */ @@ -1013,6 +1037,11 @@ public Set getSubTypes() { private void addSubType(AnalysisType subType) { boolean result = this.subTypes.add(subType); + /* Register the object reachability callbacks with the newly discovered subtype. */ + if (!subType.equals(this)) { + /* Subtypes include this type itself. */ + ConcurrentLightHashSet.forEach(this, objectReachableCallbacksUpdater, (BiConsumer callback) -> subType.registerObjectReachableCallback(callback)); + } assert result : "Tried to add a " + subType + " which is already registered"; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/SubstrateAccessor.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/SubstrateAccessor.java index 241e3605e4bd..18f775676588 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/SubstrateAccessor.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/SubstrateAccessor.java @@ -33,6 +33,8 @@ import com.oracle.svm.core.hub.DynamicHub; import com.oracle.svm.core.jdk.InternalVMMethod; +import jdk.vm.ci.meta.ResolvedJavaMethod; + @InternalVMMethod public abstract class SubstrateAccessor { /** @@ -43,6 +45,14 @@ public abstract class SubstrateAccessor { @Platforms(Platform.HOSTED_ONLY.class) // final Executable member; + /** + * The actual target method. For @{@link SubstrateConstructorAccessor} this is a factory method. + * For {@link SubstrateMethodAccessor} it can be the member itself or an adapter for caller + * sensitive methods. + */ + @Platforms(Platform.HOSTED_ONLY.class) // + final ResolvedJavaMethod targetMethod; + /** * The first-level function that is invoked. It expands the boxed Object[] signature to the * expanded real signature. @@ -62,14 +72,24 @@ public abstract class SubstrateAccessor { final DynamicHub initializeBeforeInvoke; @Platforms(Platform.HOSTED_ONLY.class) - SubstrateAccessor(Executable member, CFunctionPointer expandSignature, CFunctionPointer directTarget, DynamicHub initializeBeforeInvoke) { + SubstrateAccessor(Executable member, CFunctionPointer expandSignature, CFunctionPointer directTarget, ResolvedJavaMethod targetMethod, DynamicHub initializeBeforeInvoke) { this.member = member; this.expandSignature = expandSignature; this.directTarget = directTarget; this.initializeBeforeInvoke = initializeBeforeInvoke; + this.targetMethod = targetMethod; } public Executable getMember() { return member; } + + public CFunctionPointer getExpandSignature() { + return expandSignature; + } + + @Platforms(Platform.HOSTED_ONLY.class) + public ResolvedJavaMethod getTargetMethod() { + return targetMethod; + } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/SubstrateConstructorAccessor.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/SubstrateConstructorAccessor.java index 9c3c96273844..d181594bafc2 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/SubstrateConstructorAccessor.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/SubstrateConstructorAccessor.java @@ -34,12 +34,13 @@ import com.oracle.svm.core.reflect.ReflectionAccessorHolder.MethodInvokeFunctionPointer; import jdk.internal.reflect.ConstructorAccessor; +import jdk.vm.ci.meta.ResolvedJavaMethod; @InternalVMMethod public final class SubstrateConstructorAccessor extends SubstrateAccessor implements ConstructorAccessor { - public SubstrateConstructorAccessor(Executable member, CFunctionPointer expandSignature, CFunctionPointer directTarget, DynamicHub initializeBeforeInvoke) { - super(member, expandSignature, directTarget, initializeBeforeInvoke); + public SubstrateConstructorAccessor(Executable member, CFunctionPointer expandSignature, CFunctionPointer directTarget, ResolvedJavaMethod targetMethod, DynamicHub initializeBeforeInvoke) { + super(member, expandSignature, directTarget, targetMethod, initializeBeforeInvoke); } @Override diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/SubstrateMethodAccessor.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/SubstrateMethodAccessor.java index 1f522fe4400a..90a03acca074 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/SubstrateMethodAccessor.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/SubstrateMethodAccessor.java @@ -26,8 +26,6 @@ import java.lang.reflect.Executable; -import jdk.graal.compiler.nodes.NamedLocationIdentity; -import jdk.graal.compiler.word.BarrieredAccess; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.c.function.CFunctionPointer; @@ -39,7 +37,10 @@ import com.oracle.svm.core.reflect.ReflectionAccessorHolder.MethodInvokeFunctionPointerForCallerSensitiveAdapter; import com.oracle.svm.core.util.VMError; +import jdk.graal.compiler.nodes.NamedLocationIdentity; +import jdk.graal.compiler.word.BarrieredAccess; import jdk.internal.reflect.MethodAccessor; +import jdk.vm.ci.meta.ResolvedJavaMethod; interface MethodAccessorJDK19 { Object invoke(Object obj, Object[] args, Class caller); @@ -61,9 +62,9 @@ public final class SubstrateMethodAccessor extends SubstrateAccessor implements private final boolean callerSensitiveAdapter; @Platforms(Platform.HOSTED_ONLY.class) - public SubstrateMethodAccessor(Executable member, Class receiverType, CFunctionPointer expandSignature, CFunctionPointer directTarget, int vtableOffset, DynamicHub initializeBeforeInvoke, - boolean callerSensitiveAdapter) { - super(member, expandSignature, directTarget, initializeBeforeInvoke); + public SubstrateMethodAccessor(Executable member, Class receiverType, CFunctionPointer expandSignature, CFunctionPointer directTarget, ResolvedJavaMethod targetMethod, int vtableOffset, + DynamicHub initializeBeforeInvoke, boolean callerSensitiveAdapter) { + super(member, expandSignature, directTarget, targetMethod, initializeBeforeInvoke); this.receiverType = receiverType; this.vtableOffset = vtableOffset; this.callerSensitiveAdapter = callerSensitiveAdapter; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/FeatureImpl.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/FeatureImpl.java index 8daa17a3dc08..102d0b5fec39 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/FeatureImpl.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/FeatureImpl.java @@ -46,8 +46,6 @@ import java.util.stream.Collectors; import org.graalvm.collections.Pair; -import jdk.graal.compiler.debug.DebugContext; -import jdk.graal.compiler.phases.util.Providers; import org.graalvm.nativeimage.AnnotationAccess; import org.graalvm.nativeimage.hosted.Feature; import org.graalvm.nativeimage.hosted.Feature.DuringAnalysisAccess; @@ -89,6 +87,8 @@ import com.oracle.svm.util.ReflectionUtil; import com.oracle.svm.util.UnsafePartitionKind; +import jdk.graal.compiler.debug.DebugContext; +import jdk.graal.compiler.phases.util.Providers; import jdk.vm.ci.meta.MetaAccessProvider; @SuppressWarnings("deprecation") @@ -290,6 +290,16 @@ public void registerObjectReplacer(Function replacer) { getUniverse().registerObjectReplacer(replacer); } + /** + * Register a callback that is executed when an object of the specified type or any of its + * subtypes is marked as reachable. + * + * @since 24.0 + */ + public void registerObjectReachableCallback(Class clazz, BiConsumer callback) { + getMetaAccess().lookupJavaType(clazz).registerObjectReachableCallback(callback); + } + public void registerSubstitutionProcessor(SubstitutionProcessor substitution) { getUniverse().registerFeatureSubstitution(substitution); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java index dbfc7ff67bd0..2277fd9418c1 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java @@ -70,6 +70,7 @@ import com.oracle.svm.hosted.FallbackFeature; import com.oracle.svm.hosted.FeatureImpl; import com.oracle.svm.hosted.FeatureImpl.BeforeCompilationAccessImpl; +import com.oracle.svm.hosted.FeatureImpl.DuringAnalysisAccessImpl; import com.oracle.svm.hosted.FeatureImpl.DuringSetupAccessImpl; import com.oracle.svm.hosted.ImageClassLoader; import com.oracle.svm.hosted.analysis.Inflation; @@ -163,6 +164,7 @@ public SubstrateAccessor getOrCreateAccessor(Executable member) { private SubstrateAccessor createAccessor(Executable member) { MethodPointer expandSignature; MethodPointer directTarget = null; + AnalysisMethod targetMethod = null; DynamicHub initializeBeforeInvoke = null; if (member instanceof Method) { int vtableOffset = SubstrateMethodAccessor.STATICALLY_BOUND; @@ -171,7 +173,7 @@ private SubstrateAccessor createAccessor(Executable member) { if (member.getDeclaringClass() == MethodHandle.class && (member.getName().equals("invoke") || member.getName().equals("invokeExact"))) { /* Method handles must not be invoked via reflection. */ - expandSignature = register(analysisAccess.getMetaAccess().lookupJavaMethod(methodHandleInvokeErrorMethod), "Registered in " + ReflectionFeature.class); + expandSignature = asMethodPointer(analysisAccess.getMetaAccess().lookupJavaMethod(methodHandleInvokeErrorMethod)); } else { Method target = (Method) member; try { @@ -184,18 +186,17 @@ private SubstrateAccessor createAccessor(Executable member) { throw VMError.shouldNotReachHere(ex); } expandSignature = createExpandSignatureMethod(target, callerSensitiveAdapter); - AnalysisMethod targetMethod = analysisAccess.getMetaAccess().lookupJavaMethod(target); + targetMethod = analysisAccess.getMetaAccess().lookupJavaMethod(target); /* * The SubstrateMethodAccessor is also used for the implementation of MethodHandle * that are created to do an invokespecial. So non-abstract instance methods have * both a directTarget and a vtableOffset. */ if (!targetMethod.isAbstract()) { - directTarget = register(targetMethod, "Reflection target, registered in " + ReflectionFeature.class); + directTarget = asMethodPointer(targetMethod); } if (!targetMethod.canBeStaticallyBound()) { vtableOffset = SubstrateMethodAccessor.OFFSET_NOT_YET_COMPUTED; - analysisAccess.registerAsRoot(targetMethod, false, "Accessor method for reflection, registered in " + ReflectionFeature.class); } VMError.guarantee(directTarget != null || vtableOffset != SubstrateMethodAccessor.STATICALLY_BOUND, "Must have either a directTarget or a vtableOffset"); if (!targetMethod.isStatic()) { @@ -205,7 +206,7 @@ private SubstrateAccessor createAccessor(Executable member) { initializeBeforeInvoke = analysisAccess.getHostVM().dynamicHub(targetMethod.getDeclaringClass()); } } - return new SubstrateMethodAccessor(member, receiverType, expandSignature, directTarget, vtableOffset, initializeBeforeInvoke, callerSensitiveAdapter); + return new SubstrateMethodAccessor(member, receiverType, expandSignature, directTarget, targetMethod, vtableOffset, initializeBeforeInvoke, callerSensitiveAdapter); } else { Class holder = member.getDeclaringClass(); @@ -216,31 +217,30 @@ private SubstrateAccessor createAccessor(Executable member) { * an interface, array, or primitive type, but we are defensive and throw the * exception in that case too. */ - expandSignature = register(analysisAccess.getMetaAccess().lookupJavaMethod(newInstanceErrorMethod), "Registered in " + ReflectionFeature.class); + expandSignature = asMethodPointer(analysisAccess.getMetaAccess().lookupJavaMethod(newInstanceErrorMethod)); } else { expandSignature = createExpandSignatureMethod(member, false); AnalysisMethod constructor = analysisAccess.getMetaAccess().lookupJavaMethod(member); - AnalysisMethod factoryMethod = FactoryMethodSupport.singleton().lookup(analysisAccess.getMetaAccess(), constructor, false); - directTarget = register(factoryMethod, "Factory method, registered in " + ReflectionFeature.class); + targetMethod = FactoryMethodSupport.singleton().lookup(analysisAccess.getMetaAccess(), constructor, false); + directTarget = asMethodPointer(targetMethod); if (!constructor.getDeclaringClass().isInitialized()) { initializeBeforeInvoke = analysisAccess.getHostVM().dynamicHub(constructor.getDeclaringClass()); } } - return new SubstrateConstructorAccessor(member, expandSignature, directTarget, initializeBeforeInvoke); + return new SubstrateConstructorAccessor(member, expandSignature, directTarget, targetMethod, initializeBeforeInvoke); } } private MethodPointer createExpandSignatureMethod(Executable member, boolean callerSensitiveAdapter) { return expandSignatureMethods.computeIfAbsent(new SignatureKey(member, callerSensitiveAdapter), signatureKey -> { ResolvedJavaMethod prototype = analysisAccess.getMetaAccess().lookupJavaMethod(callerSensitiveAdapter ? invokePrototypeForCallerSensitiveAdapter : invokePrototype).getWrapped(); - return register(new ReflectionExpandSignatureMethod("invoke_" + signatureKey.uniqueShortName(), prototype, signatureKey.isStatic, signatureKey.argTypes, signatureKey.returnKind, - signatureKey.callerSensitiveAdapter), "Registered in " + ReflectionFeature.class); + return asMethodPointer(new ReflectionExpandSignatureMethod("invoke_" + signatureKey.uniqueShortName(), prototype, signatureKey.isStatic, signatureKey.argTypes, signatureKey.returnKind, + signatureKey.callerSensitiveAdapter)); }); } - private MethodPointer register(ResolvedJavaMethod method, String reason) { + private MethodPointer asMethodPointer(ResolvedJavaMethod method) { AnalysisMethod aMethod = method instanceof AnalysisMethod ? (AnalysisMethod) method : analysisAccess.getUniverse().lookup(method); - analysisAccess.registerAsRoot(aMethod, true, reason); return new MethodPointer(aMethod); } @@ -273,6 +273,27 @@ public void duringSetup(DuringSetupAccess a) { for (Class primitiveClass : PRIMITIVE_CLASSES) { ClassForNameSupport.registerNegativeQuery(primitiveClass.getName()); } + + access.registerObjectReachableCallback(SubstrateAccessor.class, ReflectionFeature::onAccessorReachable); + } + + private static void onAccessorReachable(DuringAnalysisAccess a, SubstrateAccessor accessor) { + DuringAnalysisAccessImpl access = (DuringAnalysisAccessImpl) a; + + String reason = "Registered in " + ReflectionFeature.class; + ResolvedJavaMethod expandSignatureMethod = ((MethodPointer) accessor.getExpandSignature()).getMethod(); + access.registerAsRoot((AnalysisMethod) expandSignatureMethod, true, reason); + + ResolvedJavaMethod targetMethod = accessor.getTargetMethod(); + if (targetMethod != null) { + if (!targetMethod.isAbstract()) { + access.registerAsRoot((AnalysisMethod) targetMethod, true, reason); + } + /* If the accessor can be used for a virtual call, register virtual root method. */ + if (accessor instanceof SubstrateMethodAccessor mAccessor && mAccessor.getVTableOffset() != SubstrateMethodAccessor.STATICALLY_BOUND) { + access.registerAsRoot((AnalysisMethod) targetMethod, false, reason); + } + } } @Override