diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/meta/HotSpotGraphBuilderPlugins.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/meta/HotSpotGraphBuilderPlugins.java index 17adb8ed2466f..99eda688bb195 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/meta/HotSpotGraphBuilderPlugins.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/meta/HotSpotGraphBuilderPlugins.java @@ -525,9 +525,9 @@ public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Rec * NewInstanceNode. */ if (b.currentBlockCatchesOOM()) { - DynamicNewInstanceWithExceptionNode.createAndPush(b, clazz); + DynamicNewInstanceWithExceptionNode.createAndPush(b, clazz, true); } else { - DynamicNewInstanceNode.createAndPush(b, clazz); + DynamicNewInstanceNode.createAndPush(b, clazz, true); } return true; } diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/java/DynamicNewInstanceNode.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/java/DynamicNewInstanceNode.java index d60d5a8eb9850..4f9413892f717 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/java/DynamicNewInstanceNode.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/java/DynamicNewInstanceNode.java @@ -39,7 +39,6 @@ import jdk.graal.compiler.nodes.spi.Canonicalizable; import jdk.graal.compiler.nodes.spi.CanonicalizerTool; import jdk.graal.compiler.nodes.spi.CoreProviders; - import jdk.vm.ci.meta.JavaKind; import jdk.vm.ci.meta.MetaAccessProvider; import jdk.vm.ci.meta.ResolvedJavaType; @@ -50,12 +49,12 @@ public final class DynamicNewInstanceNode extends AbstractNewObjectNode implemen @Input ValueNode clazz; - public static void createAndPush(GraphBuilderContext b, ValueNode clazz) { + public static void createAndPush(GraphBuilderContext b, ValueNode clazz, boolean validateClass) { ResolvedJavaType constantType = tryConvertToNonDynamic(clazz, b); if (constantType != null) { b.addPush(JavaKind.Object, new NewInstanceNode(constantType, true)); } else { - ValueNode clazzLegal = b.add(new ValidateNewInstanceClassNode(clazz)); + ValueNode clazzLegal = validateClass ? b.add(new ValidateNewInstanceClassNode(clazz)) : clazz; b.addPush(JavaKind.Object, new DynamicNewInstanceNode(clazzLegal, true)); } } diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/java/DynamicNewInstanceWithExceptionNode.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/java/DynamicNewInstanceWithExceptionNode.java index a53f8e6bfbe39..e08c2f2915096 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/java/DynamicNewInstanceWithExceptionNode.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/nodes/java/DynamicNewInstanceWithExceptionNode.java @@ -48,12 +48,12 @@ public class DynamicNewInstanceWithExceptionNode extends AllocateWithExceptionNo public static final NodeClass TYPE = NodeClass.create(DynamicNewInstanceWithExceptionNode.class); protected boolean fillContents; - public static void createAndPush(GraphBuilderContext b, ValueNode clazz) { + public static void createAndPush(GraphBuilderContext b, ValueNode clazz, boolean validateClass) { ResolvedJavaType constantType = tryConvertToNonDynamic(clazz, b); if (constantType != null) { b.addPush(JavaKind.Object, new NewInstanceWithExceptionNode(constantType, true)); } else { - ValueNode clazzLegal = b.add(new ValidateNewInstanceClassNode(clazz)); + ValueNode clazzLegal = validateClass ? b.add(new ValidateNewInstanceClassNode(clazz)) : clazz; b.addPush(JavaKind.Object, new DynamicNewInstanceWithExceptionNode(clazzLegal, true)); } } diff --git a/sdk/src/org.graalvm.nativeimage/snapshot.sigtest b/sdk/src/org.graalvm.nativeimage/snapshot.sigtest index 725a230bfaeb3..2587dbb511226 100644 --- a/sdk/src/org.graalvm.nativeimage/snapshot.sigtest +++ b/sdk/src/org.graalvm.nativeimage/snapshot.sigtest @@ -1001,6 +1001,7 @@ meth public abstract !varargs void registerReachabilityHandler(java.util.functio meth public abstract void registerAsAccessed(java.lang.reflect.Field) meth public abstract void registerAsInHeap(java.lang.Class) meth public abstract void registerAsUnsafeAccessed(java.lang.reflect.Field) +meth public abstract void registerAsUnsafeAllocated(java.lang.Class) meth public abstract void registerAsUsed(java.lang.Class) meth public abstract void registerClassInitializerReachabilityHandler(java.util.function.Consumer,java.lang.Class) meth public abstract void registerFieldValueTransformer(java.lang.reflect.Field,org.graalvm.nativeimage.hosted.FieldValueTransformer) diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/Feature.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/Feature.java index 87ce85c52ed92..3222eb2c61d7a 100644 --- a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/Feature.java +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/Feature.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -220,6 +220,14 @@ interface BeforeAnalysisAccess extends FeatureAccess { */ void registerAsInHeap(Class type); + /** + * Registers the provided type as allocatable without running a constructor, via + * Unsafe.allocateInstance or via the JNI function AllocObject. + * + * @since 24.1 + */ + void registerAsUnsafeAllocated(Class type); + /** * Registers the provided field as accesses, i.e., the static analysis assumes the field is * used even if there are no explicit reads or writes in the bytecodes. diff --git a/substratevm/CHANGELOG.md b/substratevm/CHANGELOG.md index 9e94b2597f85d..4abbf93096d1c 100644 --- a/substratevm/CHANGELOG.md +++ b/substratevm/CHANGELOG.md @@ -20,6 +20,7 @@ This changelog summarizes major changes to GraalVM Native Image. * (GR-52534) Change the digest (used e.g. for symbol names) from SHA-1 encoded as a hex string (40 bytes) to 128-bit Murmur3 as a Base-62 string (22 bytes). * (GR-52578) Print information about embedded resources into `embedded-resources.json` using the `-H:+GenerateEmbeddedResourcesFile` option. * (GR-51172) Add support to catch OutOfMemoryError exceptions on native image if there is no memory left. +* (GR-53803) In the strict reflection configuration mode (when `ThrowMissingRegistrationErrors` is enabled), only allow `Unsafe.allocateInstance` for types registered explicitly in the configuration. * (GR-43837) `--report-unsupported-elements-at-runtime` is now enabled by default and the option is deprecated. ## GraalVM for JDK 22 (Internal Version 24.0.0) diff --git a/substratevm/src/com.oracle.graal.pointsto.standalone/src/com/oracle/graal/pointsto/standalone/features/StandaloneAnalysisFeatureImpl.java b/substratevm/src/com.oracle.graal.pointsto.standalone/src/com/oracle/graal/pointsto/standalone/features/StandaloneAnalysisFeatureImpl.java index d6f43c7c4ef54..3758d1c7e7ad0 100644 --- a/substratevm/src/com.oracle.graal.pointsto.standalone/src/com/oracle/graal/pointsto/standalone/features/StandaloneAnalysisFeatureImpl.java +++ b/substratevm/src/com.oracle.graal.pointsto.standalone/src/com/oracle/graal/pointsto/standalone/features/StandaloneAnalysisFeatureImpl.java @@ -186,6 +186,11 @@ public void registerAsInHeap(AnalysisType aType, Object reason) { aType.registerAsInstantiated(reason); } + @Override + public void registerAsUnsafeAllocated(Class type) { + getMetaAccess().lookupJavaType(type).registerAsUnsafeAllocated("registered from Feature API"); + } + @Override public void registerAsAccessed(Field field) { registerAsAccessed(getMetaAccess().lookupJavaField(field), "registered from Feature API"); 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 117c83b260c36..26ce9395f8ecc 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 @@ -100,6 +100,9 @@ public abstract class AnalysisType extends AnalysisElement implements WrappedJav private static final AtomicReferenceFieldUpdater isInstantiatedUpdater = AtomicReferenceFieldUpdater .newUpdater(AnalysisType.class, Object.class, "isInstantiated"); + private static final AtomicReferenceFieldUpdater isUnsafeAllocatedUpdater = AtomicReferenceFieldUpdater + .newUpdater(AnalysisType.class, Object.class, "isUnsafeAllocated"); + private static final AtomicReferenceFieldUpdater isReachableUpdater = AtomicReferenceFieldUpdater .newUpdater(AnalysisType.class, Object.class, "isReachable"); @@ -112,6 +115,8 @@ public abstract class AnalysisType extends AnalysisElement implements WrappedJav private final String unqualifiedName; @SuppressWarnings("unused") private volatile Object isInstantiated; + /** Can be allocated via Unsafe or JNI, i.e., without executing a constructor. */ + @SuppressWarnings("unused") private volatile Object isUnsafeAllocated; @SuppressWarnings("unused") private volatile Object isReachable; @SuppressWarnings("unused") private volatile int isAnySubtypeInstantiated; private boolean reachabilityListenerNotified; @@ -523,6 +528,11 @@ protected void onInstantiated() { processMethodOverrides(); } + public boolean registerAsUnsafeAllocated(Object reason) { + registerAsInstantiated(reason); + return AtomicUtils.atomicSet(this, reason, isUnsafeAllocatedUpdater); + } + private void processMethodOverrides() { /* * Walk up the type hierarchy from this type keeping track of all processed types. For each @@ -812,6 +822,10 @@ public Object getInstantiatedReason() { return isInstantiated; } + public boolean isUnsafeAllocated() { + return AtomicUtils.isSet(this, isUnsafeAllocatedUpdater); + } + /** Returns true if this type or any of its subtypes was marked as instantiated. */ public boolean isAnySubtypeInstantiated() { return AtomicUtils.isSet(this, isAnySubtypeInstantiatedUpdater); diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/JniCallInterceptor.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/JniCallInterceptor.java index 37b59db1afd86..1e69823e839bc 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/JniCallInterceptor.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/JniCallInterceptor.java @@ -143,6 +143,22 @@ private static JNIObjectHandle findClass(JNIEnvironment env, CCharPointer name) return result; } + @CEntryPoint(name = "AllocObject") + @CEntryPointOptions(prologue = AgentIsolate.Prologue.class) + static JNIObjectHandle allocObject(JNIEnvironment env, JNIObjectHandle clazz) { + InterceptedState state = initInterceptedState(); + JNIObjectHandle callerClass = getCallerClass(state, env); + JNIObjectHandle result = jniFunctions().getAllocObject().invoke(env, clazz); + if (nullHandle().equal(clazz) || clearException(env)) { + result = nullHandle(); + } + if (shouldTrace()) { + traceCall(env, "AllocObject", clazz, nullHandle(), callerClass, result.notEqual(nullHandle()), state); + } + return result; + + } + @CEntryPoint(name = "GetMethodID") @CEntryPointOptions(prologue = AgentIsolate.Prologue.class) private static JNIMethodId getMethodID(JNIEnvironment env, JNIObjectHandle clazz, CCharPointer name, CCharPointer signature) { @@ -316,6 +332,7 @@ public static void onVMStart(JvmtiEnv jvmti) { JNINativeInterface functions = functionsPtr.read(); functions.setDefineClass(defineClassLiteral.getFunctionPointer()); functions.setFindClass(findClassLiteral.getFunctionPointer()); + functions.setAllocObject(allocObjectLiteral.getFunctionPointer()); functions.setGetMethodID(getMethodIDLiteral.getFunctionPointer()); functions.setGetStaticMethodID(getStaticMethodIDLiteral.getFunctionPointer()); functions.setGetFieldID(getFieldIDLiteral.getFunctionPointer()); @@ -342,6 +359,9 @@ public static void onUnload() { private static final CEntryPointLiteral findClassLiteral = CEntryPointLiteral.create(JniCallInterceptor.class, "findClass", JNIEnvironment.class, CCharPointer.class); + private static final CEntryPointLiteral allocObjectLiteral = CEntryPointLiteral.create(JniCallInterceptor.class, + "allocObject", JNIEnvironment.class, JNIObjectHandle.class); + private static final CEntryPointLiteral getMethodIDLiteral = CEntryPointLiteral.create(JniCallInterceptor.class, "getMethodID", JNIEnvironment.class, JNIObjectHandle.class, CCharPointer.class, CCharPointer.class); diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/JniProcessor.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/JniProcessor.java index 822757dee925a..f6fb770cd8f2d 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/JniProcessor.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/JniProcessor.java @@ -82,6 +82,14 @@ void processEntry(EconomicMap entry, ConfigurationSet configurationSe ConfigurationMemberDeclaration declaration = (declaringClass != null) ? ConfigurationMemberDeclaration.DECLARED : ConfigurationMemberDeclaration.PRESENT; TypeConfiguration config = configurationSet.getJniConfiguration(); switch (function) { + case "AllocObject": + expectSize(args, 0); + /* + * AllocObject is implemented via Unsafe.allocateInstance, so we need to set the + * "unsafe allocated" flag in the reflection configuration file. + */ + configurationSet.getReflectionConfiguration().getOrCreateType(condition, clazz).setUnsafeAllocated(); + break; case "GetStaticMethodID": case "GetMethodID": { expectSize(args, 2); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/MissingRegistrationSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/MissingRegistrationSupport.java index 85f79376f6d21..4f943c600893b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/MissingRegistrationSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/MissingRegistrationSupport.java @@ -24,13 +24,14 @@ */ package com.oracle.svm.core; -import jdk.graal.compiler.api.replacements.Fold; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import com.oracle.svm.core.option.OptionClassFilter; +import jdk.graal.compiler.api.replacements.Fold; + public class MissingRegistrationSupport { private final OptionClassFilter classFilter; @@ -48,7 +49,11 @@ public boolean reportMissingRegistrationErrors(StackTraceElement responsibleClas return reportMissingRegistrationErrors(responsibleClass.getModuleName(), getPackageName(responsibleClass.getClassName()), responsibleClass.getClassName()); } - public boolean reportMissingRegistrationErrors(String moduleName, String packageName, String className) { + public boolean reportMissingRegistrationErrors(Class clazz) { + return reportMissingRegistrationErrors(clazz.getModule().getName(), clazz.getPackageName(), clazz.getName()); + } + + private boolean reportMissingRegistrationErrors(String moduleName, String packageName, String className) { return classFilter.isIncluded(moduleName, packageName, className) != null; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java index 67de5054ea33e..115bb1e349373 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java @@ -1174,4 +1174,8 @@ public static class TruffleStableOptions { @Option(help = "Reduce the amount of metadata in the image for implicit exceptions by removing inlining information from the stack trace. " + "This makes the image smaller, but also the stack trace of implicit exceptions less precise.", type = OptionType.Expert)// public static final HostedOptionKey ReduceImplicitExceptionStackTraceInformation = new HostedOptionKey<>(false); + + @Option(help = "Allow all instantiated types to be allocated via Unsafe.allocateInstance().", type = OptionType.Expert, // + deprecated = true, deprecationMessage = "ThrowMissingRegistrationErrors is the preferred way of configuring this on a per-type level.") // + public static final HostedOptionKey AllowUnsafeAllocationOfAllInstantiatedTypes = new HostedOptionKey<>(null); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/deopt/Deoptimizer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/deopt/Deoptimizer.java index 1bea210ad2cb9..94ca119985802 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/deopt/Deoptimizer.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/deopt/Deoptimizer.java @@ -89,7 +89,6 @@ import jdk.graal.compiler.options.Option; import jdk.graal.compiler.word.BarrieredAccess; import jdk.graal.compiler.word.Word; -import jdk.internal.misc.Unsafe; import jdk.vm.ci.code.InstalledCode; import jdk.vm.ci.meta.DeoptimizationAction; import jdk.vm.ci.meta.DeoptimizationReason; @@ -1040,11 +1039,7 @@ private Object materializeObject(int virtualObjectId, FrameInfoQueryResult sourc if (!LayoutEncoding.isPureInstance(layoutEncoding)) { throw fatalDeoptimizationError("Non-pure instance layout encoding: " + layoutEncoding, sourceFrame); } - try { - obj = Unsafe.getUnsafe().allocateInstance(DynamicHub.toClass(hub)); - } catch (InstantiationException ex) { - throw fatalDeoptimizationError("Instantiation exception: " + ex, sourceFrame); - } + obj = KnownIntrinsics.unvalidatedAllocateInstance(DynamicHub.toClass(hub)); curOffset = WordFactory.unsigned(objectLayout.getFirstFieldOffset()); curIdx = 1; } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/jdk/SubstrateObjectCloneSnippets.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/jdk/SubstrateObjectCloneSnippets.java index 23a76947560ef..b05a41d32c373 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/jdk/SubstrateObjectCloneSnippets.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/jdk/SubstrateObjectCloneSnippets.java @@ -80,7 +80,6 @@ import jdk.graal.compiler.replacements.nodes.ObjectClone; import jdk.graal.compiler.word.BarrieredAccess; import jdk.graal.compiler.word.ObjectAccess; -import jdk.internal.misc.Unsafe; import jdk.vm.ci.meta.ResolvedJavaType; public final class SubstrateObjectCloneSnippets extends SubstrateTemplates implements Snippets { @@ -92,7 +91,7 @@ public static void registerForeignCalls(SubstrateForeignCallsProvider foreignCal } @SubstrateForeignCallTarget(stubCallingConvention = false) - private static Object doClone(Object original) throws CloneNotSupportedException, InstantiationException { + private static Object doClone(Object original) throws CloneNotSupportedException { if (original == null) { throw new NullPointerException(); } else if (!(original instanceof Cloneable)) { @@ -122,7 +121,7 @@ private static Object doClone(Object original) throws CloneNotSupportedException throw VMError.shouldNotReachHere("Hybrid classes do not support Object.clone()."); } } else { - result = Unsafe.getUnsafe().allocateInstance(DynamicHub.toClass(hub)); + result = KnownIntrinsics.unvalidatedAllocateInstance(DynamicHub.toClass(hub)); } int firstFieldOffset = ConfigurationValues.getObjectLayout().getFirstFieldOffset(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/SubstrateAllocationSnippets.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/SubstrateAllocationSnippets.java index 9916b2072ee56..9ff04f324eae7 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/SubstrateAllocationSnippets.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/SubstrateAllocationSnippets.java @@ -272,7 +272,7 @@ protected Object newmultiarray(DynamicHub hub, @ConstantParameter int rank, @Con public DynamicHub validateNewInstanceClass(DynamicHub hub) { if (probability(EXTREMELY_FAST_PATH_PROBABILITY, hub != null)) { DynamicHub nonNullHub = (DynamicHub) PiNode.piCastNonNull(hub, SnippetAnchorNode.anchor()); - if (probability(EXTREMELY_FAST_PATH_PROBABILITY, nonNullHub.canInstantiateAsInstance())) { + if (probability(EXTREMELY_FAST_PATH_PROBABILITY, nonNullHub.canUnsafeInstantiateAsInstance())) { return nonNullHub; } } @@ -317,7 +317,7 @@ private static void instanceHubErrorStub(DynamicHub hub) throws InstantiationExc throw new NullPointerException("Allocation type is null."); } else if (!hub.isInstanceClass() || LayoutEncoding.isSpecial(hub.getLayoutEncoding())) { throw new InstantiationException("Can only allocate instance objects for concrete classes."); - } else if (!hub.isInstantiated()) { + } else if (!hub.canUnsafeInstantiateAsInstance()) { if (MissingRegistrationUtils.throwMissingRegistrationErrors()) { MissingReflectionRegistrationUtils.forClass(hub.getTypeName()); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java index 919bf5b2fb3c9..495b5fcfec93d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java @@ -312,7 +312,7 @@ public final class DynamicHub implements AnnotatedElement, java.lang.reflect.Typ /** Indicates whether the type has been discovered as instantiated by the static analysis. */ private static final int IS_INSTANTIATED_BIT = 0; /** Can this class be instantiated as an instance. */ - private static final int CAN_INSTANTIATE_AS_INSTANCE_BIT = 1; + private static final int CAN_UNSAFE_INSTANTIATE_AS_INSTANCE_BIT = 1; private static final int IS_REGISTERED_FOR_SERIALIZATION = 2; @@ -491,8 +491,8 @@ public void setClassInitializationInfo(ClassInitializationInfo classInitializati @Platforms(Platform.HOSTED_ONLY.class) public void setSharedData(int layoutEncoding, int monitorOffset, int identityHashOffset, long referenceMapIndex, - boolean isInstantiated, boolean canInstantiateAsInstance, boolean isRegisteredForSerialization) { - assert !(!isInstantiated && canInstantiateAsInstance); + boolean isInstantiated, boolean canUnsafeInstantiateAsInstance, boolean isRegisteredForSerialization) { + assert !(!isInstantiated && canUnsafeInstantiateAsInstance); VMError.guarantee(monitorOffset == (char) monitorOffset, "Class %s has an invalid monitor field offset. Most likely, its objects are larger than supported.", name); VMError.guarantee(identityHashOffset == (char) identityHashOffset, "Class %s has an invalid identity hash code field offset. Most likely, its objects are larger than supported.", name); @@ -505,7 +505,7 @@ public void setSharedData(int layoutEncoding, int monitorOffset, int identityHas } this.referenceMapIndex = (int) referenceMapIndex; this.additionalFlags = NumUtil.safeToUByte(makeFlag(IS_INSTANTIATED_BIT, isInstantiated) | - makeFlag(CAN_INSTANTIATE_AS_INSTANCE_BIT, canInstantiateAsInstance) | + makeFlag(CAN_UNSAFE_INSTANTIATE_AS_INSTANCE_BIT, canUnsafeInstantiateAsInstance) | makeFlag(IS_REGISTERED_FOR_SERIALIZATION, isRegisteredForSerialization)); } @@ -747,8 +747,8 @@ public boolean isInstantiated() { return isFlagSet(additionalFlags, IS_INSTANTIATED_BIT); } - public boolean canInstantiateAsInstance() { - return isFlagSet(additionalFlags, CAN_INSTANTIATE_AS_INSTANCE_BIT); + public boolean canUnsafeInstantiateAsInstance() { + return isFlagSet(additionalFlags, CAN_UNSAFE_INSTANTIATE_AS_INSTANCE_BIT); } public boolean isProxyClass() { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/headers/JNIFunctionPointerTypes.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/headers/JNIFunctionPointerTypes.java index 4ccec63d9e0f2..4b944d8bfd620 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/headers/JNIFunctionPointerTypes.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/headers/JNIFunctionPointerTypes.java @@ -45,6 +45,11 @@ public interface FindClassFunctionPointer extends CFunctionPointer { JNIObjectHandle invoke(JNIEnvironment env, CCharPointer name); } + public interface AllocObjectFunctionPointer extends CFunctionPointer { + @InvokeCFunctionPointer + JNIObjectHandle invoke(JNIEnvironment env, JNIObjectHandle clazz); + } + public interface GetMethodIDFunctionPointer extends CFunctionPointer { @InvokeCFunctionPointer JNIMethodId invoke(JNIEnvironment env, JNIObjectHandle clazz, CCharPointer name, CCharPointer signature); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/headers/JNINativeInterface.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/headers/JNINativeInterface.java index 42444829db927..893c2a6af7285 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/headers/JNINativeInterface.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/headers/JNINativeInterface.java @@ -31,6 +31,7 @@ import org.graalvm.nativeimage.c.type.WordPointer; import org.graalvm.word.PointerBase; +import com.oracle.svm.core.jni.headers.JNIFunctionPointerTypes.AllocObjectFunctionPointer; import com.oracle.svm.core.jni.headers.JNIFunctionPointerTypes.CallBooleanMethodAFunctionPointer; import com.oracle.svm.core.jni.headers.JNIFunctionPointerTypes.CallIntMethodAFunctionPointer; import com.oracle.svm.core.jni.headers.JNIFunctionPointerTypes.CallLongMethodAFunctionPointer; @@ -229,7 +230,7 @@ public interface JNINativeInterface extends PointerBase { void setEnsureLocalCapacity(CFunctionPointer p); @CField - CFunctionPointer getAllocObject(); + AllocObjectFunctionPointer getAllocObject(); @CField void setAllocObject(CFunctionPointer p); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/snippets/KnownIntrinsics.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/snippets/KnownIntrinsics.java index b700a391647e0..d24ce1fafc859 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/snippets/KnownIntrinsics.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/snippets/KnownIntrinsics.java @@ -109,4 +109,12 @@ public class KnownIntrinsics { * @see Class#cast(Object) */ public static native T castExact(Object object, Class clazz); + + /** + * Like {@link jdk.internal.misc.Unsafe#allocateInstance} but without the checks that the class + * is an instance class, without the checks that the class was registered for unsafe allocation + * using the reflection configuration, without checks that the class was seen as instantiated by + * the static analysis, and without the check that the class is already initialized. + */ + public static native Object unvalidatedAllocateInstance(Class hub); } diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/FieldsOffsetsFeature.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/FieldsOffsetsFeature.java index 04fab61102e82..2e105e1758a02 100644 --- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/FieldsOffsetsFeature.java +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/FieldsOffsetsFeature.java @@ -35,6 +35,7 @@ import org.graalvm.nativeimage.hosted.Feature; import com.oracle.graal.pointsto.meta.AnalysisField; +import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.svm.core.BuildPhaseProvider; import com.oracle.svm.core.fieldvaluetransformer.FieldValueTransformerWithAvailability; import com.oracle.svm.core.util.VMError; @@ -188,6 +189,10 @@ private static void registerFields(FieldIntrospection introspection, BeforeAn if (introspection instanceof NodeClass) { NodeClass nodeClass = (NodeClass) introspection; + /* The partial evaluator allocates Node classes via Unsafe. */ + AnalysisType nodeType = config.getMetaAccess().lookupJavaType(nodeClass.getJavaClass()); + nodeType.registerInstantiatedCallback(unused -> nodeType.registerAsUnsafeAllocated("Graal node class")); + Fields dataFields = nodeClass.getData(); registerFields(dataFields, config, "Graal node data field"); 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 3b2c9fe82f7a3..54b626d34d230 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 @@ -70,6 +70,7 @@ import com.oracle.svm.core.meta.SharedMethod; import com.oracle.svm.core.meta.SharedType; import com.oracle.svm.core.option.SubstrateOptionsParser; +import com.oracle.svm.core.util.UserError; import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.ameta.FieldValueInterceptionSupport; import com.oracle.svm.hosted.analysis.Inflation; @@ -374,6 +375,18 @@ public void registerAsInHeap(AnalysisType aType, Object reason) { aType.registerAsInstantiated(reason); } + @Override + public void registerAsUnsafeAllocated(Class clazz) { + registerAsUnsafeAllocated(getMetaAccess().lookupJavaType(clazz)); + } + + public void registerAsUnsafeAllocated(AnalysisType aType) { + if (aType.isAbstract()) { + throw UserError.abort("Cannot register an abstract class as instantiated: " + aType.toJavaName(true)); + } + aType.registerAsUnsafeAllocated("From feature"); + } + @Override public void registerAsAccessed(Field field) { registerAsAccessed(getMetaAccess().lookupJavaField(field), "registered from Feature API"); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/HostedConfiguration.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/HostedConfiguration.java index f314a9679ea81..f4e10792a9580 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/HostedConfiguration.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/HostedConfiguration.java @@ -45,6 +45,7 @@ import com.oracle.graal.pointsto.meta.PointsToAnalysisMethod; import com.oracle.graal.pointsto.results.StrengthenGraphs; import com.oracle.objectfile.ObjectFile; +import com.oracle.svm.core.MissingRegistrationSupport; import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.SubstrateTargetDescription; import com.oracle.svm.core.config.ConfigurationValues; @@ -222,8 +223,9 @@ public static boolean isInlinedField(HostedField field) { return HybridLayout.isHybridField(field) || DynamicHubLayout.singleton().isInlinedField(field); } - public SVMHost createHostVM(OptionValues options, ImageClassLoader loader, ClassInitializationSupport classInitializationSupport, AnnotationSubstitutionProcessor annotationSubstitutions) { - return new SVMHost(options, loader, classInitializationSupport, annotationSubstitutions); + public SVMHost createHostVM(OptionValues options, ImageClassLoader loader, ClassInitializationSupport classInitializationSupport, AnnotationSubstitutionProcessor annotationSubstitutions, + MissingRegistrationSupport missingRegistrationSupport) { + return new SVMHost(options, loader, classInitializationSupport, annotationSubstitutions, missingRegistrationSupport); } public CompileQueue createCompileQueue(DebugContext debug, FeatureHandler featureHandler, HostedUniverse hostedUniverse, RuntimeConfiguration runtimeConfiguration, boolean deoptimizeAll) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java index a7983d68f86da..6953f4da3ca75 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java @@ -940,7 +940,8 @@ protected void setupNativeImage(OptionValues options, Map additionalSubstitutions) { + ClassInitializationSupport classInitializationSupport, List additionalSubstitutions, MissingRegistrationSupport missingRegistrationSupport) { SubstitutionProcessor aSubstitutions = createAnalysisSubstitutionProcessor(cEnumProcessor, annotationSubstitutions, additionalSubstitutions); - SVMHost hostVM = HostedConfiguration.instance().createHostVM(options, loader, classInitializationSupport, annotationSubstitutions); + SVMHost hostVM = HostedConfiguration.instance().createHostVM(options, loader, classInitializationSupport, annotationSubstitutions, missingRegistrationSupport); AnalysisPolicy analysisPolicy = PointstoOptions.AllocationSiteSensitiveHeap.getValue(options) ? new BytecodeSensitiveAnalysisPolicy(options) : new DefaultAnalysisPolicy(options); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java index 113bc24141745..13e21e6fa0cf4 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java @@ -70,6 +70,7 @@ import com.oracle.graal.pointsto.util.GraalAccess; import com.oracle.svm.common.meta.MultiMethod; import com.oracle.svm.core.BuildPhaseProvider; +import com.oracle.svm.core.MissingRegistrationSupport; import com.oracle.svm.core.NeverInline; import com.oracle.svm.core.NeverInlineTrivial; import com.oracle.svm.core.RuntimeAssertionsSupport; @@ -182,14 +183,19 @@ public enum UsageKind { private final InlineBeforeAnalysisPolicy inlineBeforeAnalysisPolicy; private final FieldValueInterceptionSupport fieldValueInterceptionSupport; + private final MissingRegistrationSupport missingRegistrationSupport; private final boolean useBaseLayer; private Set excludedFields; + private final Boolean optionAllowUnsafeAllocationOfAllInstantiatedTypes = SubstrateOptions.AllowUnsafeAllocationOfAllInstantiatedTypes.getValue(); + @SuppressWarnings("this-escape") - public SVMHost(OptionValues options, ImageClassLoader loader, ClassInitializationSupport classInitializationSupport, AnnotationSubstitutionProcessor annotationSubstitutions) { + public SVMHost(OptionValues options, ImageClassLoader loader, ClassInitializationSupport classInitializationSupport, AnnotationSubstitutionProcessor annotationSubstitutions, + MissingRegistrationSupport missingRegistrationSupport) { super(options, loader.getClassLoader()); this.classInitializationSupport = classInitializationSupport; + this.missingRegistrationSupport = missingRegistrationSupport; this.stringTable = HostedStringDeduplication.singleton(); this.forbiddenTypes = setupForbiddenTypes(options); this.automaticUnsafeTransformations = new AutomaticUnsafeTransformationSupport(options, annotationSubstitutions, loader); @@ -327,6 +333,19 @@ public void onTypeReachable(BigBang bb, AnalysisType analysisType) { @Override public void onTypeInstantiated(BigBang bb, AnalysisType type) { checkForbidden(type, UsageKind.Instantiated); + + if (optionAllowUnsafeAllocationOfAllInstantiatedTypes != null) { + if (optionAllowUnsafeAllocationOfAllInstantiatedTypes) { + type.registerAsUnsafeAllocated("All types are registered as Unsafe allocated via option AllowUnsafeAllocationOfAllInstantiatedTypes"); + } else { + /* + * No default registration for unsafe allocation, setting the explicit option has + * precedence over the generic ThrowMissingRegistrationError option. + */ + } + } else if (!missingRegistrationSupport.reportMissingRegistrationErrors(type.getJavaClass())) { + type.registerAsUnsafeAllocated("Type is not listed as ThrowMissingRegistrationError and therefore registered as Unsafe allocated automatically for compatibility reasons"); + } } @Override diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java index a79f36b4dc2fb..bc280543cec67 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java @@ -382,6 +382,11 @@ private void addMethod(Executable method, DuringAnalysisAccessImpl access) { if (targetMethod.isConstructor() && !targetMethod.getDeclaringClass().isAbstract()) { var aFactoryMethod = FactoryMethodSupport.singleton().lookup(access.getMetaAccess(), aTargetMethod, false); access.registerAsRoot(aFactoryMethod, true, "JNI constructor, registered in " + JNIAccessFeature.class); + /* + * Constructors can be invoked on objects allocated separately via AllocObject, + * which we implement via Unsafe. + */ + access.registerAsUnsafeAllocated(aTargetMethod.getDeclaringClass()); newObjectMethod = aFactoryMethod.getWrapped(); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/UniverseBuilder.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/UniverseBuilder.java index 0df6a53e13c13..1fca1967bab00 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/UniverseBuilder.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/UniverseBuilder.java @@ -865,7 +865,7 @@ private void buildHubs() { hUniverse.hostVM().recordActivity(); int layoutHelper; - boolean canInstantiateAsInstance = false; + boolean canUnsafeInstantiateAsInstance = false; int monitorOffset = 0; int identityHashOffset = 0; if (type.isInstanceClass()) { @@ -879,10 +879,10 @@ private void buildHubs() { JavaKind storageKind = hybridLayout.getArrayElementStorageKind(); boolean isObject = (storageKind == JavaKind.Object); layoutHelper = LayoutEncoding.forHybrid(type, isObject, hybridLayout.getArrayBaseOffset(), ol.getArrayIndexShift(storageKind)); - canInstantiateAsInstance = type.isInstantiated() && HybridLayout.canInstantiateAsInstance(type); + canUnsafeInstantiateAsInstance = type.wrapped.isUnsafeAllocated() && HybridLayout.canInstantiateAsInstance(type); } else { layoutHelper = LayoutEncoding.forPureInstance(type, ConfigurationValues.getObjectLayout().alignUp(instanceClass.getInstanceSize())); - canInstantiateAsInstance = type.isInstantiated(); + canUnsafeInstantiateAsInstance = type.wrapped.isUnsafeAllocated(); } monitorOffset = instanceClass.getMonitorFieldOffset(); identityHashOffset = instanceClass.getIdentityHashOffset(); @@ -910,7 +910,7 @@ private void buildHubs() { DynamicHub hub = type.getHub(); SerializationRegistry s = ImageSingletons.lookup(SerializationRegistry.class); hub.setSharedData(layoutHelper, monitorOffset, identityHashOffset, - referenceMapIndex, type.isInstantiated(), canInstantiateAsInstance, + referenceMapIndex, type.isInstantiated(), canUnsafeInstantiateAsInstance, s.isRegisteredForSerialization(type.getJavaClass())); if (SubstrateOptions.closedTypeWorld()) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java index e94f2ed66cb46..9117cc58778c2 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java @@ -231,7 +231,7 @@ private void registerClass(ConfigurationCondition condition, Class clazz, boo AnalysisType type = metaAccess.lookupJavaType(clazz); type.registerAsReachable("Is registered for reflection."); if (unsafeInstantiated) { - type.registerAsInstantiated("Is registered for reflection."); + type.registerAsUnsafeAllocated("Is registered for reflection."); } if (allowForName) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/SubstrateGraphBuilderPlugins.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/SubstrateGraphBuilderPlugins.java index 27a606c579f39..218672a134a8d 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/SubstrateGraphBuilderPlugins.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/SubstrateGraphBuilderPlugins.java @@ -425,8 +425,7 @@ public boolean defaultHandler(GraphBuilderContext b, ResolvedJavaMethod targetMe Runnable proxyRegistrationRunnable = interceptProxyInterfaces(b, targetMethod, annotationSubstitutions, args[1]); if (proxyRegistrationRunnable != null) { Class callerClass = OriginalClassProvider.getJavaClass(b.getMethod().getDeclaringClass()); - boolean callerInScope = MissingRegistrationSupport.singleton().reportMissingRegistrationErrors(callerClass.getModule().getName(), callerClass.getPackageName(), - callerClass.getName()); + boolean callerInScope = MissingRegistrationSupport.singleton().reportMissingRegistrationErrors(callerClass); if (callerInScope && reason.duringAnalysis() && reason != ParsingReason.JITCompilation) { b.add(ReachabilityRegistrationNode.create(proxyRegistrationRunnable, reason)); return true; @@ -808,9 +807,9 @@ public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Rec ensureInitialized.setStateAfter(b.getInvocationPluginBeforeState()); if (b.currentBlockCatchesOOM()) { - DynamicNewInstanceWithExceptionNode.createAndPush(b, clazzNonNull); + DynamicNewInstanceWithExceptionNode.createAndPush(b, clazzNonNull, true); } else { - DynamicNewInstanceNode.createAndPush(b, clazzNonNull); + DynamicNewInstanceNode.createAndPush(b, clazzNonNull, true); } return true; } @@ -959,6 +958,18 @@ public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Rec return true; } }); + r.register(new RequiredInvocationPlugin("unvalidatedAllocateInstance", Class.class) { + @Override + public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Receiver unused, ValueNode clazz) { + ValueNode clazzNonNull = b.nullCheckedValue(clazz, DeoptimizationAction.None); + if (b.currentBlockCatchesOOM()) { + DynamicNewInstanceWithExceptionNode.createAndPush(b, clazzNonNull, false); + } else { + DynamicNewInstanceNode.createAndPush(b, clazzNonNull, false); + } + return true; + } + }); registerCastExact(r); }