Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 {
/**
Expand All @@ -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)))) {
Expand All @@ -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;
/*
Expand All @@ -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)) {
Expand Down