Skip to content

Commit 4ea3144

Browse files
committed
[GR-29096] Avoid illegal reflective access warnings in Java >= 11.
PullRequest: graal/9192
2 parents 31436f1 + df49055 commit 4ea3144

File tree

6 files changed

+103
-110
lines changed

6 files changed

+103
-110
lines changed
Lines changed: 59 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -22,42 +22,41 @@
2222
*/
2323
package com.oracle.truffle.espresso;
2424

25+
import java.lang.ref.PublicFinalReference;
26+
import java.lang.reflect.AccessibleObject;
27+
import java.lang.reflect.Field;
28+
import java.lang.reflect.Method;
29+
import java.security.ProtectionDomain;
30+
31+
import org.graalvm.nativeimage.hosted.Feature;
32+
2533
import com.oracle.truffle.espresso.meta.EspressoError;
2634
import com.oracle.truffle.espresso.substitutions.HostJavaVersionUtil;
2735
import com.oracle.truffle.espresso.vm.UnsafeAccess;
28-
import org.graalvm.nativeimage.hosted.Feature;
29-
import sun.misc.Unsafe;
3036

31-
import java.lang.ref.PublicFinalReference;
32-
import java.lang.reflect.InvocationTargetException;
33-
import java.security.ProtectionDomain;
37+
import sun.misc.Unsafe;
3438

3539
/**
3640
* Support for finalizers (FinalReference) in Espresso.
37-
*
41+
*
3842
* <p>
3943
* Espresso implements non-strong references e.g. {@link java.lang.ref.WeakReference} by using the
4044
* host equivalents, inheriting the same semantics and behavior as the host VM.
41-
*
45+
*
4246
* Since FinalReference is package private, Espresso injects {@link PublicFinalReference} in the
4347
* boot class loader of the host VM to open the hierarchy. This mechanism is very fragile, but
4448
* allows Espresso to share the same implementation for HotSpot and SubstrateVM.
4549
*/
46-
public final class FinalizationFeature implements Feature {
47-
48-
@Override
49-
public void beforeAnalysis(BeforeAnalysisAccess access) {
50-
ensureInitialized();
51-
}
50+
public final class FinalizationSupport {
5251

53-
static final Class<?> PUBLIC_FINAL_REFERENCE;
52+
private static final boolean UnsafeOverride = "true".equalsIgnoreCase(System.getProperty("espresso.finalization.UnsafeOverride", "true"));
5453

5554
/**
5655
* Compiled {@link java.lang.ref.PublicFinalReference} without the poisoned static initializer.
57-
*
56+
*
5857
* <p>
5958
* To examine the contents, execute the following commands:
60-
*
59+
*
6160
* <pre>
6261
* jshell &#60;&#60;EOF
6362
* try (var fos = new FileOutputStream("PublicFinalReference.class")) {
@@ -90,41 +89,62 @@ public void beforeAnalysis(BeforeAnalysisAccess access) {
9089
0, 18, 0, 0, 0, 2, 0, 25, 0, 0, 0, 2, 0, 26, 0, 7, 0, 0, 0, 2, 0, 27};
9190

9291
static {
93-
PUBLIC_FINAL_REFERENCE = injectClassInBootClassLoader("java/lang/ref/PublicFinalReference", PUBLIC_FINAL_REFERENCE_BYTES);
92+
try {
93+
Class<?> publicFinalReference = injectClassInBootClassLoader("java/lang/ref/PublicFinalReference", PUBLIC_FINAL_REFERENCE_BYTES);
94+
EspressoError.guarantee("java.lang.ref.FinalReference".equals(publicFinalReference.getSuperclass().getName()),
95+
"Injected class does not subclass FinalReference");
96+
} catch (Exception e) {
97+
throw EspressoError.shouldNotReachHere("Error injecting PublicFinalReference in the host (version " + HostJavaVersionUtil.JAVA_SPEC + ")", e);
98+
}
9499
}
95100

96101
public static void ensureInitialized() {
97102
/* nop */
98103
}
99104

100-
/**
101-
* Inject raw class in the host boot class loader.
102-
*/
103-
private static Class<?> injectClassInBootClassLoader(String className, byte[] classBytes) {
104-
EspressoError.guarantee(HostJavaVersionUtil.JAVA_SPEC == 8 || HostJavaVersionUtil.JAVA_SPEC == 11, "Unsupported host Java version: {}", HostJavaVersionUtil.JAVA_SPEC);
105+
private static Class<?> injectClassInBootClassLoader(String className, byte[] classBytes) throws Exception {
105106
if (HostJavaVersionUtil.JAVA_SPEC == 8) {
106107
// Inject class via sun.misc.Unsafe#defineClass.
107108
// The use of reflection here is deliberate, so the code compiles with both Java 8/11.
108-
try {
109-
java.lang.reflect.Method defineClass = Unsafe.class.getDeclaredMethod("defineClass",
110-
String.class, byte[].class, int.class, int.class, ClassLoader.class, ProtectionDomain.class);
111-
defineClass.setAccessible(true);
112-
return (Class<?>) defineClass.invoke(UnsafeAccess.get(), className, classBytes, 0, classBytes.length, null, null);
113-
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
114-
throw EspressoError.shouldNotReachHere(e);
115-
}
116-
} else if (HostJavaVersionUtil.JAVA_SPEC >= 11 /* removal of sun.misc.Unsafe#defineClass */) {
109+
Method defineClass = Unsafe.class.getDeclaredMethod("defineClass",
110+
String.class, byte[].class, int.class, int.class, ClassLoader.class, ProtectionDomain.class);
111+
defineClass.setAccessible(true);
112+
return (Class<?>) defineClass.invoke(UnsafeAccess.get(), className, classBytes, 0, classBytes.length, null, null);
113+
} else if (HostJavaVersionUtil.JAVA_SPEC == 11) {
117114
// Inject class via j.l.ClassLoader#defineClass1.
118-
try {
119-
java.lang.reflect.Method defineClass1 = ClassLoader.class.getDeclaredMethod("defineClass1",
120-
ClassLoader.class, String.class, byte[].class, int.class, int.class, ProtectionDomain.class, String.class);
121-
defineClass1.setAccessible(true);
122-
return (Class<?>) defineClass1.invoke(null, null, className, classBytes, 0, classBytes.length, null, null);
123-
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
124-
throw EspressoError.shouldNotReachHere(e);
115+
Method defineClass1 = ClassLoader.class.getDeclaredMethod("defineClass1",
116+
ClassLoader.class, String.class, byte[].class, int.class, int.class, ProtectionDomain.class, String.class);
117+
118+
if (UnsafeOverride) {
119+
/*
120+
* Overwrites the AccessibleObject.override field via Unsafe to force-enable
121+
* reflection access and get rid of illegal access warnings.
122+
*/
123+
Object unsafeInstance = UnsafeAccess.get();
124+
Method putBoolean = unsafeInstance.getClass().getMethod("putBoolean", Object.class, long.class, boolean.class);
125+
Method objectFieldOffset = unsafeInstance.getClass().getMethod("objectFieldOffset", Field.class);
126+
127+
Field overrideField = AccessibleObject.class.getDeclaredField("override");
128+
long overrideFieldOffset = (long) objectFieldOffset.invoke(unsafeInstance, overrideField);
129+
130+
// Force-enable access to j.l.ClassLoader#defineClass1.
131+
putBoolean.invoke(unsafeInstance, defineClass1, overrideFieldOffset, true);
125132
}
133+
134+
return (Class<?>) defineClass1.invoke(null, null, className, classBytes, 0, classBytes.length, null, null);
126135
} else {
127-
throw EspressoError.shouldNotReachHere("Java version not supported: " + HostJavaVersionUtil.JAVA_SPEC);
136+
throw EspressoError.shouldNotReachHere("Class injection not supported for host Java " + HostJavaVersionUtil.JAVA_SPEC);
128137
}
129138
}
130139
}
140+
141+
/**
142+
* Ensures that SVM initializes the finalization support before loading any classes.
143+
*/
144+
final class FinalizationFeature implements Feature {
145+
146+
@Override
147+
public void beforeAnalysis(BeforeAnalysisAccess access) {
148+
FinalizationSupport.ensureInitialized();
149+
}
150+
}

espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/ffi/nfi/NativeUtils.java

Lines changed: 22 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,6 @@
2323
package com.oracle.truffle.espresso.ffi.nfi;
2424

2525
import java.io.IOException;
26-
import java.lang.reflect.Constructor;
27-
import java.lang.reflect.Field;
28-
import java.lang.reflect.InvocationTargetException;
2926
import java.nio.ByteBuffer;
3027
import java.nio.ByteOrder;
3128
import java.nio.IntBuffer;
@@ -40,45 +37,44 @@
4037
import com.oracle.truffle.espresso.jni.ModifiedUtf8;
4138
import com.oracle.truffle.espresso.meta.EspressoError;
4239
import com.oracle.truffle.espresso.meta.JavaKind;
40+
import com.oracle.truffle.espresso.vm.UnsafeAccess;
41+
42+
import sun.misc.Unsafe;
4343

4444
public final class NativeUtils {
45+
46+
private static final Unsafe UNSAFE = UnsafeAccess.get();
47+
4548
public static ByteBuffer directByteBuffer(@Pointer TruffleObject addressPtr, long size, JavaKind kind) {
4649
return directByteBuffer(addressPtr, Math.multiplyExact(size, kind.getByteCount()));
4750
}
4851

49-
private static final Constructor<? extends ByteBuffer> constructor;
50-
private static final Field addressField;
51-
52-
@SuppressWarnings("unchecked")
53-
private static Class<? extends ByteBuffer> getByteBufferClass(String className) {
54-
try {
55-
return (Class<? extends ByteBuffer>) Class.forName(className);
56-
} catch (ClassNotFoundException e) {
57-
throw EspressoError.shouldNotReachHere(e);
58-
}
59-
}
52+
private static final Class<?> DIRECT_BYTE_BUFFER_CLASS;
53+
private static final long ADDRESS_FIELD_OFFSET;
54+
private static final long CAPACITY_FIELD_OFFSET;
6055

6156
static {
6257
try {
63-
Class<? extends ByteBuffer> clazz = getByteBufferClass("java.nio.DirectByteBuffer");
64-
Class<? extends ByteBuffer> bufferClazz = getByteBufferClass("java.nio.Buffer");
65-
constructor = clazz.getDeclaredConstructor(long.class, int.class);
66-
addressField = bufferClazz.getDeclaredField("address");
67-
addressField.setAccessible(true);
68-
constructor.setAccessible(true);
69-
} catch (NoSuchMethodException | NoSuchFieldException e) {
58+
ADDRESS_FIELD_OFFSET = UNSAFE.objectFieldOffset(java.nio.Buffer.class.getDeclaredField("address"));
59+
CAPACITY_FIELD_OFFSET = UNSAFE.objectFieldOffset(java.nio.Buffer.class.getDeclaredField("capacity"));
60+
DIRECT_BYTE_BUFFER_CLASS = Class.forName("java.nio.DirectByteBuffer");
61+
} catch (ClassNotFoundException | NoSuchFieldException e) {
7062
throw EspressoError.shouldNotReachHere(e);
7163
}
7264
}
7365

7466
@TruffleBoundary
75-
public static ByteBuffer directByteBuffer(long address, long capacity) {
67+
public static ByteBuffer directByteBuffer(long address, long longCapacity) {
68+
int capacity = Math.toIntExact(longCapacity);
7669
ByteBuffer buffer = null;
7770
try {
78-
buffer = constructor.newInstance(address, Math.toIntExact(capacity));
79-
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
71+
buffer = (ByteBuffer) UNSAFE.allocateInstance(DIRECT_BYTE_BUFFER_CLASS);
72+
} catch (InstantiationException e) {
8073
throw EspressoError.shouldNotReachHere(e);
8174
}
75+
UNSAFE.putLong(buffer, ADDRESS_FIELD_OFFSET, address);
76+
UNSAFE.putInt(buffer, CAPACITY_FIELD_OFFSET, capacity);
77+
buffer.clear();
8278
buffer.order(ByteOrder.nativeOrder());
8379
return buffer;
8480
}
@@ -150,12 +146,8 @@ public static TruffleObject dereferencePointerPointer(InteropLibrary library, Tr
150146

151147
@TruffleBoundary
152148
public static long byteBufferAddress(ByteBuffer byteBuffer) {
153-
try {
154-
assert byteBuffer.isDirect();
155-
return (long) addressField.get(byteBuffer);
156-
} catch (IllegalAccessException e) {
157-
throw EspressoError.shouldNotReachHere(e);
158-
}
149+
assert byteBuffer.isDirect();
150+
return UNSAFE.getLong(byteBuffer, ADDRESS_FIELD_OFFSET);
159151
}
160152

161153
public static @Pointer TruffleObject byteBufferPointer(ByteBuffer byteBuffer) {

espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/EspressoContext.java

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,6 @@
4545
import java.util.logging.Level;
4646
import java.util.stream.Collectors;
4747

48-
import com.oracle.truffle.espresso.FinalizationFeature;
49-
import com.oracle.truffle.espresso.redefinition.plugins.api.InternalRedefinitionPlugin;
50-
import org.graalvm.nativeimage.ImageInfo;
5148
import org.graalvm.options.OptionMap;
5249
import org.graalvm.polyglot.Engine;
5350

@@ -67,6 +64,7 @@
6764
import com.oracle.truffle.espresso.EspressoBindings;
6865
import com.oracle.truffle.espresso.EspressoLanguage;
6966
import com.oracle.truffle.espresso.EspressoOptions;
67+
import com.oracle.truffle.espresso.FinalizationSupport;
7068
import com.oracle.truffle.espresso.descriptors.Names;
7169
import com.oracle.truffle.espresso.descriptors.Signatures;
7270
import com.oracle.truffle.espresso.descriptors.Symbol;
@@ -86,6 +84,7 @@
8684
import com.oracle.truffle.espresso.perf.DebugCloseable;
8785
import com.oracle.truffle.espresso.perf.DebugTimer;
8886
import com.oracle.truffle.espresso.perf.TimerCollection;
87+
import com.oracle.truffle.espresso.redefinition.plugins.api.InternalRedefinitionPlugin;
8988
import com.oracle.truffle.espresso.substitutions.Substitutions;
9089
import com.oracle.truffle.espresso.substitutions.Target_java_lang_Thread;
9190
import com.oracle.truffle.espresso.vm.InterpreterToVM;
@@ -403,10 +402,9 @@ public void initializeContext() {
403402
"Allow native access on context creation e.g. contextBuilder.allowNativeAccess(true)");
404403
assert !this.initialized;
405404
eventListener = new EmptyListener();
406-
if (!ImageInfo.inImageRuntimeCode()) {
407-
// Setup finalization support in the host VM.
408-
FinalizationFeature.ensureInitialized();
409-
}
405+
// Setup finalization support in the host VM.
406+
FinalizationSupport.ensureInitialized();
407+
410408
spawnVM();
411409
this.initialized = true;
412410
this.jdwpContext = new JDWPContextImpl(this);

espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Target_java_lang_Thread.java

Lines changed: 12 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@
2323

2424
package com.oracle.truffle.espresso.substitutions;
2525

26-
import java.lang.reflect.InvocationTargetException;
2726
import java.util.Arrays;
27+
import java.util.concurrent.atomic.AtomicLong;
2828

2929
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
3030
import com.oracle.truffle.api.nodes.DirectCallNode;
@@ -61,33 +61,23 @@
6161
// @formatter:on
6262
@EspressoSubstitutions
6363
public final class Target_java_lang_Thread {
64-
private static final java.lang.reflect.Method isInterrupted;
65-
static {
66-
try {
67-
isInterrupted = Thread.class.getDeclaredMethod("isInterrupted", boolean.class);
68-
isInterrupted.setAccessible(true);
69-
} catch (Throwable e) {
70-
throw EspressoError.shouldNotReachHere();
71-
}
72-
}
7364

7465
public static void incrementThreadCounter(StaticObject thread, Field hiddenField) {
7566
assert hiddenField.isHidden();
76-
Long counter = (Long) hiddenField.getHiddenObject(thread);
77-
if (counter == null) {
78-
counter = 0L;
67+
AtomicLong atomicCounter = (AtomicLong) hiddenField.getHiddenObject(thread);
68+
if (atomicCounter == null) {
69+
hiddenField.setHiddenObject(thread, atomicCounter = new AtomicLong());
7970
}
80-
++counter;
81-
hiddenField.setHiddenObject(thread, counter);
71+
atomicCounter.incrementAndGet();
8272
}
8373

8474
public static long getThreadCounter(StaticObject thread, Field hiddenField) {
8575
assert hiddenField.isHidden();
86-
Long counter = (Long) hiddenField.getHiddenObject(thread);
87-
if (counter == null) {
88-
counter = 0L;
76+
AtomicLong atomicCounter = (AtomicLong) hiddenField.getHiddenObject(thread);
77+
if (atomicCounter == null) {
78+
return 0L;
8979
}
90-
return counter;
80+
return atomicCounter.get();
9181
}
9282

9383
public enum State {
@@ -405,28 +395,21 @@ public static void interrupt0(@Host(Object.class) StaticObject self) {
405395
hostThread.interrupt();
406396
}
407397

398+
@TruffleBoundary
408399
@Substitution(hasReceiver = true)
409400
public static boolean isInterrupted(@Host(Thread.class) StaticObject self, boolean clear) {
410401
boolean result = checkInterrupt(self);
411402
if (clear) {
412403
Thread hostThread = getHostFromGuestThread(self);
404+
EspressoError.guarantee(hostThread == Thread.currentThread(), "Thread#isInterrupted(true) is only supported for the current thread.");
413405
if (hostThread != null && hostThread.isInterrupted()) {
414-
try {
415-
callHostThreadIsInterrupted(hostThread);
416-
} catch (Throwable e) {
417-
throw EspressoError.shouldNotReachHere(e);
418-
}
406+
Thread.interrupted();
419407
}
420408
setInterrupt(self, false);
421409
}
422410
return result;
423411
}
424412

425-
@TruffleBoundary
426-
private static void callHostThreadIsInterrupted(Thread hostThread) throws IllegalAccessException, InvocationTargetException {
427-
isInterrupted.invoke(hostThread, true);
428-
}
429-
430413
@TruffleBoundary
431414
@SuppressWarnings({"unused"})
432415
@Substitution(hasReceiver = true)

espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Target_java_lang_ref_Reference.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
import java.lang.ref.ReferenceQueue;
2727

2828
import com.oracle.truffle.api.nodes.DirectCallNode;
29-
import com.oracle.truffle.espresso.FinalizationFeature;
29+
import com.oracle.truffle.espresso.FinalizationSupport;
3030
import com.oracle.truffle.espresso.meta.Meta;
3131
import com.oracle.truffle.espresso.runtime.StaticObject;
3232
import com.oracle.truffle.espresso.vm.InterpreterToVM;
@@ -36,7 +36,7 @@ public final class Target_java_lang_ref_Reference {
3636

3737
static {
3838
// Ensure PublicFinalReference is injected in the host VM.
39-
FinalizationFeature.ensureInitialized();
39+
FinalizationSupport.ensureInitialized();
4040
}
4141

4242
@Substitution(hasReceiver = true, methodName = "<init>")

espresso/src/com.oracle.truffle.espresso/src/java/lang/ref/PublicFinalReference.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,11 @@
3232
*
3333
* <p>
3434
* This class is just a placeholder, not usable as-is. A modified version without the throwing
35-
* static initializer is injected via {@link sun.misc.Unsafe#defineClass}. The injected version
36-
* subclasses {@link FinalReference}.
35+
* static initializer is injected in the boot class loader. The injected version subclasses
36+
* {@link FinalReference}.
3737
*
3838
* @see Target_java_lang_ref_Reference
39-
* @see com.oracle.truffle.espresso.FinalizationFeature
39+
* @see com.oracle.truffle.espresso.FinalizationSupport
4040
* @see EspressoReference
4141
*/
4242
public abstract class PublicFinalReference<T> {

0 commit comments

Comments
 (0)