|
22 | 22 | */ |
23 | 23 | package com.oracle.truffle.espresso; |
24 | 24 |
|
| 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 | + |
25 | 33 | import com.oracle.truffle.espresso.meta.EspressoError; |
26 | 34 | import com.oracle.truffle.espresso.substitutions.HostJavaVersionUtil; |
27 | 35 | import com.oracle.truffle.espresso.vm.UnsafeAccess; |
28 | | -import org.graalvm.nativeimage.hosted.Feature; |
29 | | -import sun.misc.Unsafe; |
30 | 36 |
|
31 | | -import java.lang.ref.PublicFinalReference; |
32 | | -import java.lang.reflect.InvocationTargetException; |
33 | | -import java.security.ProtectionDomain; |
| 37 | +import sun.misc.Unsafe; |
34 | 38 |
|
35 | 39 | /** |
36 | 40 | * Support for finalizers (FinalReference) in Espresso. |
37 | | - * |
| 41 | + * |
38 | 42 | * <p> |
39 | 43 | * Espresso implements non-strong references e.g. {@link java.lang.ref.WeakReference} by using the |
40 | 44 | * host equivalents, inheriting the same semantics and behavior as the host VM. |
41 | | - * |
| 45 | + * |
42 | 46 | * Since FinalReference is package private, Espresso injects {@link PublicFinalReference} in the |
43 | 47 | * boot class loader of the host VM to open the hierarchy. This mechanism is very fragile, but |
44 | 48 | * allows Espresso to share the same implementation for HotSpot and SubstrateVM. |
45 | 49 | */ |
46 | | -public final class FinalizationFeature implements Feature { |
47 | | - |
48 | | - @Override |
49 | | - public void beforeAnalysis(BeforeAnalysisAccess access) { |
50 | | - ensureInitialized(); |
51 | | - } |
| 50 | +public final class FinalizationSupport { |
52 | 51 |
|
53 | | - static final Class<?> PUBLIC_FINAL_REFERENCE; |
| 52 | + private static final boolean UnsafeOverride = "true".equalsIgnoreCase(System.getProperty("espresso.finalization.UnsafeOverride", "true")); |
54 | 53 |
|
55 | 54 | /** |
56 | 55 | * Compiled {@link java.lang.ref.PublicFinalReference} without the poisoned static initializer. |
57 | | - * |
| 56 | + * |
58 | 57 | * <p> |
59 | 58 | * To examine the contents, execute the following commands: |
60 | | - * |
| 59 | + * |
61 | 60 | * <pre> |
62 | 61 | * jshell <<EOF |
63 | 62 | * try (var fos = new FileOutputStream("PublicFinalReference.class")) { |
@@ -90,41 +89,62 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { |
90 | 89 | 0, 18, 0, 0, 0, 2, 0, 25, 0, 0, 0, 2, 0, 26, 0, 7, 0, 0, 0, 2, 0, 27}; |
91 | 90 |
|
92 | 91 | 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 | + } |
94 | 99 | } |
95 | 100 |
|
96 | 101 | public static void ensureInitialized() { |
97 | 102 | /* nop */ |
98 | 103 | } |
99 | 104 |
|
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 { |
105 | 106 | if (HostJavaVersionUtil.JAVA_SPEC == 8) { |
106 | 107 | // Inject class via sun.misc.Unsafe#defineClass. |
107 | 108 | // 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) { |
117 | 114 | // 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); |
125 | 132 | } |
| 133 | + |
| 134 | + return (Class<?>) defineClass1.invoke(null, null, className, classBytes, 0, classBytes.length, null, null); |
126 | 135 | } 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); |
128 | 137 | } |
129 | 138 | } |
130 | 139 | } |
| 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 | +} |
0 commit comments