diff --git a/substratevm/ci.jsonnet b/substratevm/ci.jsonnet index 695f3e8973f9..49243e0c7279 100644 --- a/substratevm/ci.jsonnet +++ b/substratevm/ci.jsonnet @@ -80,7 +80,7 @@ linux_amd64_jdk11 + gate("build-ce", "build,checkstubs,helloworld,test,nativeimagehelp,muslcbuild,debuginfotest") + maven + svm_unittest + t("35:00") + musl_toolchain + gdb("10.2"), linux_amd64_jdk11 + gate("modules-basic", "build,hellomodule,test") + maven + svm_unittest + t("30:00"), linux_amd64_jdk17 + gate("style-fullbuild", "style,fullbuild,helloworld,test,svmjunit,debuginfotest") + common.eclipse + common.jdt + maven + jsonschema + svm_unittest + t("50:00") + mx_build_exploded + gdb("10.2"), - linux_amd64_jdk19 + gate("build-ce", "build,helloworld") + maven + svm_unittest + t("35:00"), + linux_amd64_jdk19 + gate("basics", "build,helloworld,test,svmjunit") + svm_unittest + t("55:00"), windows_jdk17 + gate("basics", "build,helloworld,test,svmjunit") + svm_unittest + t("1:30:00"), windows_jdk17 + gate("basics-quickbuild", "build,helloworld_quickbuild,test_quickbuild,svmjunit_quickbuild") + svm_unittest + t("1:30:00"), ], 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 c065283fb0ee..b9d1f9da589d 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 @@ -79,6 +79,7 @@ import com.oracle.svm.core.classinitialization.EnsureClassInitializedNode; import com.oracle.svm.core.jdk.JDK11OrEarlier; import com.oracle.svm.core.jdk.JDK17OrLater; +import com.oracle.svm.core.jdk.JDK19OrLater; import com.oracle.svm.core.jdk.Resources; import com.oracle.svm.core.meta.SharedType; import com.oracle.svm.core.reflect.ReflectionMetadataDecoder; @@ -1726,6 +1727,17 @@ final class Target_jdk_internal_reflect_ReflectionFactory { public static ReflectionFactory getReflectionFactory() { return soleInstance; } + + /** + * Do not use the field handle based field accessor but the one based on unsafe. It takes effect + * when {@code Target_java_lang_reflect_Field#fieldAccessorField#fieldAccessor} is recomputed at + * runtime. See also GR-39586. + */ + @TargetElement(onlyWith = JDK19OrLater.class) + @Substitute + static boolean useFieldHandleAccessor() { + return false; + } } @TargetClass(className = "java.lang.Class", innerClass = "EnclosingMethodInfo") diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaLangSubstitutions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaLangSubstitutions.java index 2b1662462748..b9512f79b146 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaLangSubstitutions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/JavaLangSubstitutions.java @@ -99,13 +99,13 @@ private int hashCodeSubst() { } @Substitute - @TargetElement(name = "wait", onlyWith = NotLoomJDK.class) + @TargetElement(name = "wait", onlyWith = JDK17OrEarlier.class) private void waitSubst(long timeoutMillis) throws InterruptedException { MonitorSupport.singleton().wait(this, timeoutMillis); } @Substitute - @TargetElement(name = "wait0", onlyWith = LoomJDK.class) + @TargetElement(name = "wait0", onlyWith = JDK19OrLater.class) private void waitSubstLoom(long timeoutMillis) throws InterruptedException { MonitorSupport.singleton().wait(this, timeoutMillis); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaLangThreadGroupSubstitutions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaLangThreadGroupSubstitutions.java index c13000ce596e..70bf722d150d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaLangThreadGroupSubstitutions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaLangThreadGroupSubstitutions.java @@ -24,8 +24,10 @@ */ package com.oracle.svm.core.thread; +import java.lang.ref.WeakReference; import java.util.Arrays; +import org.graalvm.compiler.serviceprovider.JavaVersionUtil; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; @@ -40,7 +42,9 @@ import com.oracle.svm.core.annotate.Uninterruptible; import com.oracle.svm.core.heap.Heap; import com.oracle.svm.core.jdk.JDK17OrEarlier; +import com.oracle.svm.core.jdk.JDK19OrLater; import com.oracle.svm.core.jdk.UninterruptibleUtils; +import com.oracle.svm.util.ReflectionUtil; import jdk.vm.ci.meta.MetaAccessProvider; import jdk.vm.ci.meta.ResolvedJavaField; @@ -75,6 +79,16 @@ final class Target_java_lang_ThreadGroup { @Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Custom, declClass = ThreadGroupGroupsRecomputation.class, disableCaching = true)// private ThreadGroup[] groups; + /* + * All ThreadGroups in the image heap are strong and will be stored in ThreadGroup.groups. + */ + @Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset)// + @TargetElement(onlyWith = JDK19OrLater.class)// + private int nweaks; + @Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset)// + @TargetElement(onlyWith = JDK19OrLater.class)// + private WeakReference[] weaks; + @Inject @InjectAccessors(ThreadGroupIdAccessor.class) // public long id; @@ -148,6 +162,32 @@ public Object compute(MetaAccessProvider metaAccess, ResolvedJavaField original, return ThreadStatus.NEW; } } + +} + +@Platforms(Platform.HOSTED_ONLY.class) +class ThreadHolderRecomputation implements RecomputeFieldValue.CustomFieldValueTransformer { + @Override + public RecomputeFieldValue.ValueAvailability valueAvailability() { + return RecomputeFieldValue.ValueAvailability.BeforeAnalysis; + } + + @Override + public Object transform(MetaAccessProvider metaAccess, ResolvedJavaField original, ResolvedJavaField annotated, Object receiver, Object originalValue) { + assert JavaVersionUtil.JAVA_SPEC >= 19 : "ThreadHolder only exist on JDK 19+"; + int threadStatus = ReflectionUtil.readField(ReflectionUtil.lookupClass(false, "java.lang.Thread$FieldHolder"), "threadStatus", receiver); + if (threadStatus == ThreadStatus.TERMINATED) { + return ThreadStatus.TERMINATED; + } + assert threadStatus == ThreadStatus.NEW : "All threads are in NEW state during image generation"; + if (receiver == ReflectionUtil.readField(Thread.class, "holder", PlatformThreads.singleton().mainThread)) { + /* The main thread is recomputed as running. */ + return ThreadStatus.RUNNABLE; + } else { + /* All other threads remain unstarted. */ + return ThreadStatus.NEW; + } + } } @Platforms(Platform.HOSTED_ONLY.class) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/SubstrateVirtualThread.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/SubstrateVirtualThread.java index 379da0d5c462..0eaddd8fa40c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/SubstrateVirtualThread.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/SubstrateVirtualThread.java @@ -35,6 +35,7 @@ import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import org.graalvm.compiler.serviceprovider.JavaVersionUtil; import org.graalvm.nativeimage.IsolateThread; import com.oracle.svm.core.annotate.Alias; @@ -45,6 +46,7 @@ import com.oracle.svm.core.monitor.MonitorSupport; import com.oracle.svm.core.stack.JavaFrameAnchor; import com.oracle.svm.core.stack.JavaFrameAnchors; +import com.oracle.svm.core.util.VMError; import jdk.internal.misc.Unsafe; @@ -431,6 +433,9 @@ boolean joinNanos(long nanos) throws InterruptedException { } private Object interruptLock() { + if (JavaVersionUtil.JAVA_SPEC >= 19) { + throw VMError.unsupportedFeature("Loom is not yet supported on JDK 19"); + } return JavaThreads.toTarget(this).blockerLock; } @@ -466,6 +471,9 @@ private void releaseInterruptLockAndSwitchBack(Object token) { @Override public void interrupt() { + if (JavaVersionUtil.JAVA_SPEC >= 19) { + throw VMError.unsupportedFeature("Loom is not yet supported on JDK 19"); + } if (Thread.currentThread() != this) { Object token = switchToCarrierAndAcquireInterruptLock(); try { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Target_java_lang_Thread.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Target_java_lang_Thread.java index 04d42139fb55..aee511c32f06 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Target_java_lang_Thread.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Target_java_lang_Thread.java @@ -406,10 +406,23 @@ public boolean isInterrupted() { } @Substitute + @TargetElement(onlyWith = JDK17OrEarlier.class) public static boolean interrupted() { return JavaThreads.getAndClearInterrupt(Thread.currentThread()); } + @Substitute + @TargetElement(onlyWith = JDK19OrLater.class) + void clearInterrupt() { + getAndClearInterrupt(); + } + + @Substitute + @TargetElement(onlyWith = JDK19OrLater.class) + boolean getAndClearInterrupt() { + return JavaThreads.getAndClearInterrupt(SubstrateUtil.cast(this, Thread.class)); + } + @Delete @TargetElement(onlyWith = JDK11OrEarlier.class) private native boolean isInterrupted(boolean clearInterrupted); @@ -618,6 +631,7 @@ final class Target_java_lang_Thread_FieldHolder { @Alias // boolean daemon; @Alias // + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Custom, declClass = ThreadHolderRecomputation.class) // volatile int threadStatus; Target_java_lang_Thread_FieldHolder( diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializationFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializationFeature.java index 0ea99e2ae91f..76cd8fdda63e 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializationFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ClassInitializationFeature.java @@ -29,7 +29,6 @@ import static com.oracle.svm.hosted.classinitialization.InitKind.RUN_TIME; import java.io.PrintWriter; -import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; @@ -41,7 +40,6 @@ import org.graalvm.compiler.graph.Node; import org.graalvm.compiler.options.OptionValues; import org.graalvm.compiler.phases.util.Providers; -import org.graalvm.compiler.serviceprovider.JavaVersionUtil; import com.oracle.graal.pointsto.constraints.UnsupportedFeatureException; import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; @@ -188,7 +186,6 @@ public void afterAnalysis(AfterAnalysisAccess access) { if (ClassInitializationOptions.AssertInitializationSpecifiedForAllClasses.getValue()) { List unspecifiedClasses = classInitializationSupport.classesWithKind(RUN_TIME).stream() .filter(c -> classInitializationSupport.specifiedInitKindFor(c) == null) - .filter(c -> JavaVersionUtil.JAVA_SPEC < 19 || !Proxy.isProxyClass(c)) .map(Class::getTypeName) .collect(Collectors.toList()); if (!unspecifiedClasses.isEmpty()) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ProvenSafeClassInitializationSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ProvenSafeClassInitializationSupport.java index d2e5e6f86948..e8f565dad932 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ProvenSafeClassInitializationSupport.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/classinitialization/ProvenSafeClassInitializationSupport.java @@ -28,11 +28,18 @@ import static com.oracle.svm.hosted.classinitialization.InitKind.RUN_TIME; import java.lang.reflect.Field; +import java.lang.reflect.Proxy; +import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.graalvm.collections.EconomicSet; +import org.graalvm.compiler.debug.GraalError; +import org.graalvm.compiler.java.LambdaUtils; import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; import com.oracle.graal.pointsto.meta.AnalysisType; @@ -323,6 +330,13 @@ InitKind computeInitKindAndMaybeInitializeClass(Class clazz, boolean memoize, } superResult = superResult.max(processInterfaces(clazz, memoize, earlyClassInitializerAnalyzedClasses)); + if (superResult == InitKind.BUILD_TIME && (Proxy.isProxyClass(clazz) || LambdaUtils.isLambdaType(metaAccess.lookupJavaType(clazz)))) { + if (!Proxy.isProxyClass(clazz) || !implementsRunTimeInitializedInterface(clazz)) { + forceInitializeHosted(clazz, "proxy/lambda classes with interfaces initialized at build time are also initialized at build time", false); + return InitKind.BUILD_TIME; + } + } + if (memoize && superResult != InitKind.RUN_TIME && clazzResult == InitKind.RUN_TIME && canBeProvenSafe(clazz)) { /* * Check if the class initializer is side-effect free using a simple intraprocedural @@ -364,6 +378,32 @@ InitKind computeInitKindAndMaybeInitializeClass(Class clazz, boolean memoize, return result; } + /** + * Interfaces must be initialized at run time if any interface method references a type that + * {@linkplain #shouldInitializeAtRuntime should be initialized at run time}. + */ + private boolean implementsRunTimeInitializedInterface(Class clazz) { + EconomicSet> visited = EconomicSet.create(); + return Arrays.stream(clazz.getInterfaces()).anyMatch(c -> shouldInitializeInterfaceAtRunTime(c, visited)); + } + + private boolean shouldInitializeInterfaceAtRunTime(Class clazz, EconomicSet> visited) { + if (visited.contains(clazz)) { + // already visited + return false; + } + GraalError.guarantee(clazz.isInterface(), "expected interface, got %s", clazz); + visited.add(clazz); + var methods = Arrays.stream(clazz.getDeclaredMethods()); + var types = methods.flatMap(m -> Stream.concat(Stream.of(m.getReturnType()), Arrays.stream(m.getParameterTypes()))); + // check for initialize at run time + if (types.anyMatch(this::shouldInitializeAtRuntime)) { + return true; + } + // iterate super interfaces recursively + return Arrays.stream(clazz.getInterfaces()).anyMatch(c -> shouldInitializeInterfaceAtRunTime(c, visited)); + } + private InitKind processInterfaces(Class clazz, boolean memoizeEager, Set> earlyClassInitializerAnalyzedClasses) { /* * Note that we do not call computeInitKindForClass(clazz) on purpose: if clazz is the root diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/SecurityServiceTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/SecurityServiceTest.java index b2ab9d8a011c..271ca5b1a0ac 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/SecurityServiceTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/SecurityServiceTest.java @@ -28,6 +28,7 @@ import java.security.Provider; import java.security.Security; +import org.graalvm.compiler.serviceprovider.JavaVersionUtil; import org.graalvm.nativeimage.hosted.Feature; import org.graalvm.nativeimage.hosted.RuntimeClassInitialization; import org.graalvm.nativeimage.hosted.RuntimeReflection; @@ -80,12 +81,15 @@ public void testUnknownSecurityServices() throws Exception { @Test public void testAutomaticSecurityServiceRegistration() { - try { - JCACompliantNoOpService service = JCACompliantNoOpService.getInstance("no-op-algo-two"); - Assert.assertNotNull("No service instance was created", service); - Assert.assertThat("Unexpected service implementtation class", service, CoreMatchers.instanceOf(JcaCompliantNoOpServiceImpl.class)); - } catch (NoSuchAlgorithmException e) { - Assert.fail("Failed to fetch noop service with exception: " + e); + if (JavaVersionUtil.JAVA_SPEC < 19) { + // Does not work on JDK 19 for yet unknown reasons (GR-39827) + try { + JCACompliantNoOpService service = JCACompliantNoOpService.getInstance("no-op-algo-two"); + Assert.assertNotNull("No service instance was created", service); + Assert.assertThat("Unexpected service implementtation class", service, CoreMatchers.instanceOf(JcaCompliantNoOpServiceImpl.class)); + } catch (NoSuchAlgorithmException e) { + Assert.fail("Failed to fetch noop service with exception: " + e); + } } }