From 041f5dbddf3b5cc89665d3ea4c17302d8da46234 Mon Sep 17 00:00:00 2001 From: Peter Hofer Date: Wed, 22 Feb 2023 08:37:53 +0100 Subject: [PATCH] Disallow Cleaner and Cleanable on the image heap. --- .../heap/Target_jdk_internal_ref_Cleaner.java | 20 ++++++++ .../image/DisallowedImageHeapObjects.java | 51 ++++++++++++++++--- 2 files changed, 64 insertions(+), 7 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/Target_jdk_internal_ref_Cleaner.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/Target_jdk_internal_ref_Cleaner.java index bedf1ac7c9b4..5cb07d80e10d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/Target_jdk_internal_ref_Cleaner.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/heap/Target_jdk_internal_ref_Cleaner.java @@ -27,6 +27,8 @@ import java.lang.ref.Cleaner; import java.lang.ref.ReferenceQueue; +import org.graalvm.nativeimage.hosted.FieldValueTransformer; + import com.oracle.svm.core.annotate.Alias; import com.oracle.svm.core.annotate.RecomputeFieldValue; import com.oracle.svm.core.annotate.Substitute; @@ -150,10 +152,28 @@ public void run() { @TargetClass(className = "jdk.internal.ref.PhantomCleanable") final class Target_jdk_internal_ref_PhantomCleanable { + /* + * Unlink from the list for the image heap so that we cannot reach Cleanables irrelevant for the + * image heap which could fail the image build; we reset the list head anyway. + */ + @Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Custom, declClass = HolderObjectFieldTransformer.class) // + Target_jdk_internal_ref_PhantomCleanable prev; + @Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Custom, declClass = HolderObjectFieldTransformer.class) // + Target_jdk_internal_ref_PhantomCleanable next; + @Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Custom, declClass = HolderObjectFieldTransformer.class) // + Target_jdk_internal_ref_PhantomCleanable list; + @Alias native boolean isListEmpty(); } +final class HolderObjectFieldTransformer implements FieldValueTransformer { + @Override + public Object transform(Object receiver, Object originalValue) { + return receiver; + } +} + // Removed by JDK-8251861 @TargetClass(className = "jdk.internal.ref.WeakCleanable", onlyWith = JDK11OrEarlier.class) final class Target_jdk_internal_ref_WeakCleanable { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/image/DisallowedImageHeapObjects.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/image/DisallowedImageHeapObjects.java index 7f9b9c7a260f..7cd03ceed323 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/image/DisallowedImageHeapObjects.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/image/DisallowedImageHeapObjects.java @@ -25,6 +25,8 @@ package com.oracle.svm.core.image; import java.io.FileDescriptor; +import java.lang.ref.Cleaner; +import java.lang.ref.Reference; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -39,6 +41,8 @@ import com.oracle.svm.core.util.VMError; import com.oracle.svm.util.ReflectionUtil; +import jdk.internal.ref.CleanerFactory; + public final class DisallowedImageHeapObjects { public interface DisallowedObjectReporter { /** @@ -52,22 +56,25 @@ public interface DisallowedObjectReporter { private static final Class JDK_VIRTUAL_THREAD_CLASS; private static final Class CONTINUATION_CLASS; private static final Method CONTINUATION_IS_STARTED_METHOD; + private static final Class CLEANER_CLEANABLE_CLASS; + private static final Class LEGACY_CLEANER_CLASS; static { CANCELLABLE_CLASS = ReflectionUtil.lookupClass(false, "sun.nio.fs.Cancellable"); JDK_VIRTUAL_THREAD_CLASS = ReflectionUtil.lookupClass(true, "java.lang.VirtualThread"); CONTINUATION_CLASS = ReflectionUtil.lookupClass(true, "jdk.internal.vm.Continuation"); CONTINUATION_IS_STARTED_METHOD = (CONTINUATION_CLASS == null) ? null : ReflectionUtil.lookupMethod(CONTINUATION_CLASS, "isStarted"); + CLEANER_CLEANABLE_CLASS = ReflectionUtil.lookupClass(false, "jdk.internal.ref.CleanerImpl$CleanerCleanable"); + LEGACY_CLEANER_CLASS = ReflectionUtil.lookupClass(false, "jdk.internal.ref.Cleaner"); } public static void check(Object obj, DisallowedObjectReporter reporter) { - /* Random/SplittableRandom can not be in the image heap. */ if (((obj instanceof Random) && !(obj instanceof ThreadLocalRandom)) || obj instanceof SplittableRandom) { throw reporter.raise("Detected an instance of Random/SplittableRandom class in the image heap. " + "Instances created during image generation have cached seed values and don't behave as expected.", obj, "Try avoiding to initialize the class that caused initialization of the object."); } - /* Started platform threads can not be in the image heap. */ + /* Started platform threads */ if (obj instanceof Thread) { final Thread asThread = (Thread) obj; if (VirtualThreads.isSupported() && (VirtualThreads.singleton().isVirtual(asThread) || (JDK_VIRTUAL_THREAD_CLASS != null && JDK_VIRTUAL_THREAD_CLASS.isInstance(asThread)))) { @@ -92,17 +99,17 @@ public static void check(Object obj, DisallowedObjectReporter reporter) { obj, "Prevent continuations from starting during image generation, or started continuations from being included in the image."); } } - /* FileDescriptors can not be in the image heap. */ + if (obj instanceof FileDescriptor) { final FileDescriptor asFileDescriptor = (FileDescriptor) obj; - /* Except for a few well-known FileDescriptors. */ + /* Exemptions for well-known FileDescriptors. */ if (!((asFileDescriptor == FileDescriptor.in) || (asFileDescriptor == FileDescriptor.out) || (asFileDescriptor == FileDescriptor.err) || (!asFileDescriptor.valid()))) { throw reporter.raise("Detected a FileDescriptor in the image heap. " + "File descriptors opened during image generation are no longer open at image runtime, and the files might not even be present anymore at image runtime.", asFileDescriptor, "Try avoiding to initialize the class that caused initialization of the FileDescriptor."); } } - /* Direct ByteBuffers can not be in the image heap. */ + if (obj instanceof MappedByteBuffer) { MappedByteBuffer buffer = (MappedByteBuffer) obj; /* @@ -121,11 +128,41 @@ public static void check(Object obj, DisallowedObjectReporter reporter) { obj, "Try avoiding to initialize the class that caused initialization of the direct Buffer."); } - /* ZipFiles can not be in the image heap. */ + if (obj instanceof Cleaner.Cleanable || LEGACY_CLEANER_CLASS.isInstance(obj)) { + /* + * Cleanable and jdk.internal.ref.Cleaner are used to release various resources such as + * native memory, file descriptors, or timers, which are not available at image runtime. + * By disallowing these objects, we detect when such resources are reachable. + * + * If a Cleanable is a nulled (Phantom)Reference, its problematic resource is already + * unreachable, so we tolerate it. + * + * A CleanerCleanable serves only to keep a cleaner thread alive (without referencing + * the Thread) and does nothing, so we also tolerate it. We should encounter at least + * one such object for jdk.internal.ref.CleanerFactory.commonCleaner. + * + * Legacy jdk.internal.ref.Cleaner objects (formerly in sun.misc) should be used only by + * DirectByteBuffer, which we already cover above, but other code could also use them. + * If they have been nulled, we tolerate them, too. + */ + if (!(obj instanceof Reference && ((Reference) obj).refersTo(null)) && !CLEANER_CLEANABLE_CLASS.isInstance(obj)) { + throw reporter.raise("Detected an active instance of Cleanable or jdk.internal.ref.Cleaner in the image heap. This usually means that a resource " + + "such as a Timer, native memory, a file descriptor or another resource is reachable which is not available at image runtime.", + obj, "Prevent such objects being used during image generation, including by class initializers."); + } + } + + if (obj instanceof Cleaner && obj != CleanerFactory.cleaner()) { + /* We handle the "common cleaner", CleanerFactory.cleaner(), in reference handling. */ + throw reporter.raise("Detected a java.lang.ref.Cleaner object in the image heap which uses a daemon thread that invokes " + + "cleaning actions, but threads running in the image generator are no longer running at image runtime.", + obj, "Prevent such objects being used during image generation, including by class initializers."); + } + if (obj instanceof java.util.zip.ZipFile) { throw reporter.raise("Detected a ZipFile object in the image heap. " + "A ZipFile object contains pointers to unmanaged C memory and file descriptors, and these resources are no longer available at image runtime.", - obj, "Try avoiding to initialize the class that caused initialization of the direct Buffer."); + obj, "Try avoiding to initialize the class that caused initialization of the ZipFile."); } if (CANCELLABLE_CLASS.isInstance(obj)) {