diff --git a/sdk/src/org.graalvm.nativeimage/snapshot.sigtest b/sdk/src/org.graalvm.nativeimage/snapshot.sigtest index 2083fb7967bf..58c08e078c20 100644 --- a/sdk/src/org.graalvm.nativeimage/snapshot.sigtest +++ b/sdk/src/org.graalvm.nativeimage/snapshot.sigtest @@ -893,6 +893,7 @@ CLSS public abstract interface static org.graalvm.nativeimage.hosted.Feature$Dur intf org.graalvm.nativeimage.hosted.Feature$BeforeAnalysisAccess intf org.graalvm.nativeimage.hosted.Feature$QueryReachabilityAccess meth public abstract void requireAnalysisIteration() +meth public abstract void rescanObject(java.lang.Object) CLSS public abstract interface static org.graalvm.nativeimage.hosted.Feature$DuringSetupAccess outer org.graalvm.nativeimage.hosted.Feature diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/Feature.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/Feature.java index e8a3492499e8..e02f36f2551f 100644 --- a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/Feature.java +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/Feature.java @@ -277,6 +277,13 @@ interface DuringAnalysisAccess extends BeforeAnalysisAccess, QueryReachabilityAc * @since 19.0 */ void requireAnalysisIteration(); + + /** + * Rescan an object to be included in the image heap. + * + * @since 22.0 + */ + void rescanObject(Object obj); } /** diff --git a/substratevm/ci_includes/gate.hocon b/substratevm/ci_includes/gate.hocon index aba5f15fa7e4..00fcd70818c0 100644 --- a/substratevm/ci_includes/gate.hocon +++ b/substratevm/ci_includes/gate.hocon @@ -69,6 +69,7 @@ builds += [ } ${labsjdk-ce-17} ${svm-common-gate} ${svm-common-windows-jdk17} ${svmUnittest} { name: "gate-svm-windows-basics" + timelimit: "1:30:00" run: [ ${svm-cmd-gate} ["build,helloworld,test,svmjunit"] ] diff --git a/substratevm/mx.substratevm/suite.py b/substratevm/mx.substratevm/suite.py index 92c29f5609ea..52b93813b5d1 100644 --- a/substratevm/mx.substratevm/suite.py +++ b/substratevm/mx.substratevm/suite.py @@ -1155,7 +1155,9 @@ "sun.reflect.generics.reflectiveObjects", "sun.reflect.generics.repository", "sun.reflect.generics.tree", + "sun.reflect.generics.scope", "sun.util.calendar", + "sun.util.locale", "sun.security.jca", "sun.security.util", "sun.security.provider", @@ -1457,6 +1459,8 @@ "exports" : [ "com.oracle.graal.pointsto", "com.oracle.graal.pointsto.api", + "com.oracle.graal.pointsto.heap", + "com.oracle.graal.pointsto.heap.value", "com.oracle.graal.pointsto.reports", "com.oracle.graal.pointsto.constraints", "com.oracle.graal.pointsto.util", diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/AnalysisObjectScanningObserver.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/AnalysisObjectScanningObserver.java index 3dca19404a31..b4060dd3a302 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/AnalysisObjectScanningObserver.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/AnalysisObjectScanningObserver.java @@ -24,6 +24,7 @@ */ package com.oracle.graal.pointsto; +import com.oracle.graal.pointsto.ObjectScanner.ScanReason; import com.oracle.graal.pointsto.flow.ArrayElementsTypeFlow; import com.oracle.graal.pointsto.flow.FieldTypeFlow; import com.oracle.graal.pointsto.flow.context.object.AnalysisObject; @@ -42,23 +43,25 @@ public AnalysisObjectScanningObserver(BigBang bb) { } @Override - public void forRelocatedPointerFieldValue(JavaConstant receiver, AnalysisField field, JavaConstant fieldValue) { + public boolean forRelocatedPointerFieldValue(JavaConstant receiver, AnalysisField field, JavaConstant fieldValue, ScanReason reason) { if (!field.isWritten()) { - field.registerAsWritten(null); + return field.registerAsWritten(null); } + return false; } @Override - public void forNullFieldValue(JavaConstant receiver, AnalysisField field) { + public boolean forNullFieldValue(JavaConstant receiver, AnalysisField field, ScanReason reason) { FieldTypeFlow fieldTypeFlow = getFieldTypeFlow(field, receiver); if (!fieldTypeFlow.getState().canBeNull()) { /* Signal that the field can contain null. */ - fieldTypeFlow.addState(getAnalysis(), TypeState.forNull()); + return fieldTypeFlow.addState(getAnalysis(), TypeState.forNull()); } + return false; } @Override - public void forNonNullFieldValue(JavaConstant receiver, AnalysisField field, JavaConstant fieldValue) { + public boolean forNonNullFieldValue(JavaConstant receiver, AnalysisField field, JavaConstant fieldValue, ScanReason reason) { PointsToAnalysis analysis = getAnalysis(); AnalysisType fieldType = analysis.getMetaAccess().lookupJavaType(analysis.getSnippetReflectionProvider().asObject(Object.class, fieldValue).getClass()); assert fieldType.isInstantiated() : fieldType; @@ -69,8 +72,9 @@ public void forNonNullFieldValue(JavaConstant receiver, AnalysisField field, Jav if (!fieldTypeFlow.getState().containsObject(constantObject)) { /* Add the new constant to the field's flow state. */ TypeState constantTypeState = TypeState.forNonNullObject(analysis, constantObject); - fieldTypeFlow.addState(analysis, constantTypeState); + return fieldTypeFlow.addState(analysis, constantTypeState); } + return false; } /** @@ -94,16 +98,17 @@ private FieldTypeFlow getFieldTypeFlow(AnalysisField field, JavaConstant receive } @Override - public void forNullArrayElement(JavaConstant array, AnalysisType arrayType, int elementIndex) { + public boolean forNullArrayElement(JavaConstant array, AnalysisType arrayType, int elementIndex, ScanReason reason) { ArrayElementsTypeFlow arrayObjElementsFlow = getArrayElementsFlow(array, arrayType); if (!arrayObjElementsFlow.getState().canBeNull()) { /* Signal that the constant array can contain null. */ - arrayObjElementsFlow.addState(getAnalysis(), TypeState.forNull()); + return arrayObjElementsFlow.addState(getAnalysis(), TypeState.forNull()); } + return false; } @Override - public void forNonNullArrayElement(JavaConstant array, AnalysisType arrayType, JavaConstant elementConstant, AnalysisType elementType, int elementIndex) { + public boolean forNonNullArrayElement(JavaConstant array, AnalysisType arrayType, JavaConstant elementConstant, AnalysisType elementType, int elementIndex, ScanReason reason) { assert elementType.isInstantiated() : elementType; ArrayElementsTypeFlow arrayObjElementsFlow = getArrayElementsFlow(array, arrayType); PointsToAnalysis analysis = getAnalysis(); @@ -111,8 +116,9 @@ public void forNonNullArrayElement(JavaConstant array, AnalysisType arrayType, J if (!arrayObjElementsFlow.getState().containsObject(constantObject)) { /* Add the constant element to the constant's array type flow. */ TypeState elementTypeState = TypeState.forNonNullObject(analysis, constantObject); - arrayObjElementsFlow.addState(analysis, elementTypeState); + return arrayObjElementsFlow.addState(analysis, elementTypeState); } + return false; } /** @@ -125,7 +131,7 @@ private ArrayElementsTypeFlow getArrayElementsFlow(JavaConstant array, AnalysisT } @Override - public void forScannedConstant(JavaConstant value, ObjectScanner.ScanReason reason) { + public void forScannedConstant(JavaConstant value, ScanReason reason) { PointsToAnalysis analysis = getAnalysis(); Object valueObj = analysis.getSnippetReflectionProvider().asObject(Object.class, value); AnalysisType type = bb.getMetaAccess().lookupJavaType(valueObj.getClass()); diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/BigBang.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/BigBang.java index 71dbffd995a0..ce56018f46d0 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/BigBang.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/BigBang.java @@ -24,22 +24,27 @@ */ package com.oracle.graal.pointsto; +import java.io.PrintWriter; +import java.util.List; +import java.util.function.Function; + +import org.graalvm.compiler.api.replacements.SnippetReflectionProvider; +import org.graalvm.compiler.debug.DebugContext; +import org.graalvm.compiler.debug.DebugHandlersFactory; +import org.graalvm.compiler.graph.NodeSourcePosition; +import org.graalvm.compiler.options.OptionValues; + import com.oracle.graal.pointsto.api.HostVM; import com.oracle.graal.pointsto.constraints.UnsupportedFeatures; +import com.oracle.graal.pointsto.meta.AnalysisField; import com.oracle.graal.pointsto.meta.AnalysisMethod; +import com.oracle.graal.pointsto.meta.AnalysisType; +import com.oracle.graal.pointsto.meta.AnalysisType.UsageKind; import com.oracle.graal.pointsto.meta.AnalysisUniverse; import com.oracle.graal.pointsto.meta.HostedProviders; import com.oracle.graal.pointsto.util.Timer; -import jdk.vm.ci.meta.ConstantReflectionProvider; -import org.graalvm.compiler.api.replacements.SnippetReflectionProvider; -import org.graalvm.compiler.debug.DebugContext; -import org.graalvm.compiler.debug.DebugHandlersFactory; -import org.graalvm.compiler.graph.NodeSourcePosition; -import org.graalvm.compiler.options.OptionValues; -import java.io.PrintWriter; -import java.util.List; -import java.util.function.Function; +import jdk.vm.ci.meta.ConstantReflectionProvider; /** * Central static analysis interface that groups together the functionality of reachability analysis @@ -104,4 +109,20 @@ public interface BigBang extends ReachabilityAnalysis, HeapScanning { default boolean isCallAllowed(PointsToAnalysis bb, AnalysisMethod caller, AnalysisMethod target, NodeSourcePosition srcPosition) { return true; } + + /** + * Callback for when a field is marked as read, written, or unsafe accessed. See + * {@link AnalysisField#isAccessed()} for field accessibility definition. + */ + @SuppressWarnings("unused") + default void onFieldAccessed(AnalysisField field) { + } + + @SuppressWarnings("unused") + default void onTypeInstantiated(AnalysisType type, UsageKind usageKind) { + } + + @SuppressWarnings("unused") + default void onTypeScanned(AnalysisType type) { + } } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/DefaultAnalysisPolicy.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/DefaultAnalysisPolicy.java index 7650d5278a9f..5f27088aab59 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/DefaultAnalysisPolicy.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/DefaultAnalysisPolicy.java @@ -30,9 +30,9 @@ import java.util.Collections; import java.util.List; -import com.oracle.graal.pointsto.meta.PointsToAnalysisMethod; import org.graalvm.compiler.options.OptionValues; +import com.oracle.graal.pointsto.constraints.UnsupportedFeatureException; import com.oracle.graal.pointsto.flow.AbstractSpecialInvokeTypeFlow; import com.oracle.graal.pointsto.flow.AbstractVirtualInvokeTypeFlow; import com.oracle.graal.pointsto.flow.ActualReturnTypeFlow; @@ -47,6 +47,7 @@ import com.oracle.graal.pointsto.meta.AnalysisMethod; import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.graal.pointsto.meta.AnalysisUniverse; +import com.oracle.graal.pointsto.meta.PointsToAnalysisMethod; import com.oracle.graal.pointsto.typestate.TypeState; import com.oracle.graal.pointsto.typestore.ArrayElementsTypeStore; import com.oracle.graal.pointsto.typestore.FieldTypeStore; @@ -208,7 +209,14 @@ public void onObservedUpdate(PointsToAnalysis bb) { continue; } - AnalysisMethod method = type.resolveConcreteMethod(getTargetMethod()); + AnalysisMethod method = null; + try { + method = type.resolveConcreteMethod(targetMethod); + } catch (UnsupportedFeatureException ex) { + /* Register the ex with UnsupportedFeatures and allow analysis to continue. */ + bb.getUnsupportedFeatures().addMessage("resolve_" + targetMethod.format("%H.%n(%p)"), targetMethod, ex.getMessage()); + } + if (method == null || Modifier.isAbstract(method.getModifiers())) { /* * Type states can be conservative, i.e., we can have receiver types that do not diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/ObjectScanner.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/ObjectScanner.java index 8de2aa860a51..2e46d07b6e97 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/ObjectScanner.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/ObjectScanner.java @@ -59,7 +59,7 @@ * structure can be reused over multiple scanning iterations to save CPU resources. (For details * {@link ReusableSet}). */ -public abstract class ObjectScanner { +public class ObjectScanner { protected final BigBang bb; private final ReusableSet scannedObjects; @@ -67,6 +67,14 @@ public abstract class ObjectScanner { private final Deque worklist; private final ObjectScanningObserver scanningObserver; + public ObjectScanner(BigBang bb, ObjectScanningObserver scanningObserver) { + this(bb, null, new ObjectScanner.ReusableSet(), scanningObserver); + } + + public ObjectScanner(BigBang bb, ReusableSet scannedObjects, ObjectScanningObserver scanningObserver) { + this(bb, null, scannedObjects, scanningObserver); + } + public ObjectScanner(BigBang bb, CompletionExecutor executor, ReusableSet scannedObjects, ObjectScanningObserver scanningObserver) { this.bb = bb; this.scanningObserver = scanningObserver; @@ -80,6 +88,10 @@ public ObjectScanner(BigBang bb, CompletionExecutor executor, ReusableSet scanne this.scannedObjects = scannedObjects; } + public void scanBootImageHeapRoots() { + scanBootImageHeapRoots(null, null); + } + public void scanBootImageHeapRoots(Comparator fieldComparator, Comparator embeddedRootComparator) { // scan the original roots // the original roots are all the static fields, of object type, that were accessed @@ -123,10 +135,12 @@ default DebugContext getDebug(OptionValues options, List f } private void scanEmbeddedRoot(JavaConstant root, BytecodePosition position) { - AnalysisMethod method = (AnalysisMethod) position.getMethod(); try { - scanConstant(root, new MethodScan(method, position)); + EmbeddedRootScan reason = new EmbeddedRootScan(position, root); + scanningObserver.forEmbeddedRoot(root, reason); + scanConstant(root, reason); } catch (UnsupportedFeatureException ex) { + AnalysisMethod method = (AnalysisMethod) position.getMethod(); bb.getUnsupportedFeatures().addMessage(method.format("%H.%n(%p)"), method, ex.getMessage(), null, ex); } } @@ -145,34 +159,37 @@ protected final void scanRootField(AnalysisField field) { * * @param field the scanned field * @param receiver the receiver object - * @param previous reference to the work list entry containing parent object */ - protected final void scanField(AnalysisField field, JavaConstant receiver, WorklistEntry previous) { - ScanReason reason = new FieldScan(field); + protected final void scanField(AnalysisField field, JavaConstant receiver, ScanReason prevReason) { + ScanReason reason = new FieldScan(field, receiver, prevReason); try { JavaConstant fieldValue = bb.getConstantReflectionProvider().readFieldValue(field, receiver); if (fieldValue == null) { StringBuilder backtrace = new StringBuilder(); - buildObjectBacktrace(reason, previous, backtrace); + buildObjectBacktrace(bb, reason, backtrace); throw AnalysisError.shouldNotReachHere("Could not find field " + field.format("%H.%n") + (receiver == null ? "" : " on " + constantType(bb, receiver).toJavaName()) + System.lineSeparator() + backtrace); } if (fieldValue.getJavaKind() == JavaKind.Object && bb.getHostVM().isRelocatedPointer(constantAsObject(bb, fieldValue))) { - scanningObserver.forRelocatedPointerFieldValue(receiver, field, fieldValue); + scanningObserver.forRelocatedPointerFieldValue(receiver, field, fieldValue, reason); } else if (fieldValue.isNull()) { - scanningObserver.forNullFieldValue(receiver, field); + scanningObserver.forNullFieldValue(receiver, field, reason); } else if (fieldValue.getJavaKind() == JavaKind.Object) { - /* Scan the field value. */ - scanConstant(fieldValue, reason, previous); - /* Process the field value. */ - scanningObserver.forNonNullFieldValue(receiver, field, fieldValue); + /* First notify the observer about the field value... */ + scanningObserver.forNonNullFieldValue(receiver, field, fieldValue, reason); + /* + * ... and only then scan the new value, i.e., follow its references. The order is + * important for observers that expect to see the receiver before any of its + * referenced elements are being scanned. + */ + scanConstant(fieldValue, reason); } } catch (UnsupportedFeatureException ex) { - unsupportedFeature(field.format("%H.%n"), ex.getMessage(), reason, previous); + unsupportedFeature(field.format("%H.%n"), ex.getMessage(), reason); } } @@ -180,42 +197,40 @@ protected final void scanField(AnalysisField field, JavaConstant receiver, Workl * Scans constant arrays, one element at the time. * * @param array the array to be scanned - * @param previous reference to the work list entry containing parent object */ - protected final void scanArray(JavaConstant array, WorklistEntry previous) { + protected final void scanArray(JavaConstant array, ScanReason prevReason) { Object valueObj = constantAsObject(bb, array); AnalysisType arrayType = analysisType(bb, valueObj); assert valueObj instanceof Object[]; - ScanReason reason = new ArrayScan(arrayType); + ScanReason reason = new ArrayScan(arrayType, array, prevReason); Object[] arrayObject = (Object[]) valueObj; for (int idx = 0; idx < arrayObject.length; idx++) { Object e = arrayObject[idx]; try { if (e == null) { - scanningObserver.forNullArrayElement(array, arrayType, idx); + scanningObserver.forNullArrayElement(array, arrayType, idx, reason); } else { Object element = bb.getUniverse().replaceObject(e); JavaConstant elementConstant = bb.getSnippetReflectionProvider().forObject(element); AnalysisType elementType = analysisType(bb, element); - - /* Scan the array element. */ - scanConstant(elementConstant, reason, previous); - /* Process the array element. */ - scanningObserver.forNonNullArrayElement(array, arrayType, elementConstant, elementType, idx); + /* First notify the observer about the array element value... */ + scanningObserver.forNonNullArrayElement(array, arrayType, elementConstant, elementType, idx, reason); + /* + * ... and only then scan the new value, i.e., follow its references. The order + * is important for observers that expect to see the receiver before any of its + * referenced elements are being scanned. + */ + scanConstant(elementConstant, reason); } } catch (UnsupportedFeatureException ex) { - unsupportedFeature(arrayType.toJavaName(true), ex.getMessage(), reason, previous); + unsupportedFeature(arrayType.toJavaName(true), ex.getMessage(), reason); } } } public final void scanConstant(JavaConstant value, ScanReason reason) { - scanConstant(value, reason, null); - } - - public final void scanConstant(JavaConstant value, ScanReason reason, WorklistEntry previous) { Object valueObj = constantAsObject(bb, value); if (valueObj == null || valueObj instanceof WordBase) { return; @@ -229,7 +244,7 @@ public final void scanConstant(JavaConstant value, ScanReason reason, WorklistEn scanningObserver.forScannedConstant(value, reason); } finally { scannedObjects.release(valueObj); - WorklistEntry worklistEntry = new WorklistEntry(previous, value, reason); + WorklistEntry worklistEntry = new WorklistEntry(value, reason); if (executor != null) { executor.execute((ObjectScannerRunnable) debug -> doScan(worklistEntry)); } else { @@ -239,53 +254,83 @@ public final void scanConstant(JavaConstant value, ScanReason reason, WorklistEn } } - private void unsupportedFeature(String key, String message, ScanReason reason, WorklistEntry entry) { + private void unsupportedFeature(String key, String message, ScanReason reason) { StringBuilder objectBacktrace = new StringBuilder(); - AnalysisMethod method = buildObjectBacktrace(reason, entry, objectBacktrace); + AnalysisMethod method = buildObjectBacktrace(bb, reason, objectBacktrace); bb.getUnsupportedFeatures().addMessage(key, method, message, objectBacktrace.toString()); } - private AnalysisMethod buildObjectBacktrace(ScanReason reason, WorklistEntry entry, StringBuilder objectBacktrace) { - WorklistEntry cur = entry; - objectBacktrace.append("Object was reached by ").append(System.lineSeparator()); - objectBacktrace.append('\t').append(asString(reason)); - ScanReason rootReason = null; + static final String indent = " "; + + public static AnalysisMethod buildObjectBacktrace(BigBang bb, ScanReason reason, StringBuilder objectBacktrace) { + ScanReason cur = reason; + objectBacktrace.append("Object was reached by "); + objectBacktrace.append(System.lineSeparator()).append(indent).append(asString(bb, cur)); + ScanReason rootReason = cur; + cur = cur.previous; while (cur != null) { - objectBacktrace.append(System.lineSeparator()); - objectBacktrace.append("\t\t").append("constant ").append(asString(cur.constant)).append(" reached by ").append(System.lineSeparator()); - objectBacktrace.append('\t').append(asString(cur.reason)); - rootReason = cur.reason; + objectBacktrace.append(System.lineSeparator()).append(indent).append(asString(bb, cur)); + rootReason = cur.previous; cur = cur.previous; } - if (rootReason instanceof MethodScan) { + if (rootReason instanceof EmbeddedRootScan) { /* The root constant was found during scanning of 'method'. */ - return ((MethodScan) rootReason).method; + return ((EmbeddedRootScan) rootReason).getMethod(); } /* The root constant was not found during method scanning. */ return null; } - String asString(ScanReason reason) { + static String asString(BigBang bb, ScanReason reason) { if (reason instanceof FieldScan) { FieldScan fieldScan = (FieldScan) reason; if (fieldScan.field.isStatic()) { - return "reading field " + reason; + return "reading static field " + reason; } else { /* Instance field scans must have a receiver, hence the 'of'. */ - return "reading field " + reason + " of"; + return "reading field " + reason + " of constant \n " + asString(bb, reason.constant); } - } else if (reason instanceof MethodScan) { - return "scanning method " + reason; + } else if (reason instanceof EmbeddedRootScan) { + return "scanning root " + asString(bb, reason.constant) + " embedded in \n " + reason; } else if (reason instanceof ArrayScan) { - return "indexing into array"; + return "indexing into array " + asString(bb, reason.constant); } else { return reason.toString(); } } - private String asString(JavaConstant constant) { + public static String asString(BigBang bb, JavaConstant constant) { + return asString(bb, constant, true); + } + + public static String asString(BigBang bb, JavaConstant constant, boolean appendToString) { + if (constant == null) { + return "null"; + } Object obj = constantAsObject(bb, constant); - return obj.getClass().getTypeName() + '@' + Integer.toHexString(System.identityHashCode(obj)); + if (obj == null) { + return "null"; + } + String str = obj.getClass().getTypeName() + '@' + Integer.toHexString(System.identityHashCode(obj)); + if (appendToString) { + try { + str += ": " + limit(obj.toString(), 80).replace(System.lineSeparator(), ""); + } catch (Throwable e) { + // ignore any error in creating the string representation + } + } + + return str; + } + + public static String limit(String value, int length) { + StringBuilder buf = new StringBuilder(value); + if (buf.length() > length) { + buf.setLength(length); + buf.append("..."); + } + + return buf.toString(); } /** @@ -305,15 +350,15 @@ private void doScan(WorklistEntry entry) { for (AnalysisField field : type.getInstanceFields(true)) { if (field.getJavaKind() == JavaKind.Object && field.isRead()) { assert !Modifier.isStatic(field.getModifiers()); - scanField(field, entry.constant, entry); + scanField(field, entry.constant, entry.reason); } } } else if (type.isArray() && bb.getProviders().getWordTypes().asKind(type.getComponentType()) == JavaKind.Object) { /* Scan the array elements. */ - scanArray(entry.constant, entry); + scanArray(entry.constant, entry.reason); } } catch (UnsupportedFeatureException ex) { - unsupportedFeature("", ex.getMessage(), entry.reason, entry.previous); + unsupportedFeature("", ex.getMessage(), entry.reason); } } @@ -339,17 +384,15 @@ protected static AnalysisType analysisType(BigBang bb, Object constant) { return bb.getMetaAccess().lookupJavaType(constant.getClass()); } - protected static AnalysisType constantType(BigBang bb, JavaConstant constant) { + public static AnalysisType constantType(BigBang bb, JavaConstant constant) { return bb.getMetaAccess().lookupJavaType(constant); } - protected static Object constantAsObject(BigBang bb, JavaConstant constant) { + public static Object constantAsObject(BigBang bb, JavaConstant constant) { return bb.getSnippetReflectionProvider().asObject(Object.class, constant); } static class WorklistEntry { - /** The previously processed entry. */ - private final WorklistEntry previous; /** The constant to be scanned. */ private final JavaConstant constant; /** @@ -359,46 +402,52 @@ static class WorklistEntry { */ private final ScanReason reason; - WorklistEntry(WorklistEntry previous, JavaConstant constant, ScanReason reason) { - this.previous = previous; + WorklistEntry(JavaConstant constant, ScanReason reason) { this.constant = constant; this.reason = reason; } - public WorklistEntry getPrevious() { - return previous; + public ScanReason getReason() { + return reason; } + } + + public abstract static class ScanReason { + final ScanReason previous; + final JavaConstant constant; - public JavaConstant getConstant() { - return constant; + protected ScanReason(ScanReason previous, JavaConstant constant) { + this.previous = previous; + this.constant = constant; } - public ScanReason getReason() { - return reason; + public ScanReason getPrevious() { + return previous; } } - public interface ScanReason { - OtherReason HUB = new OtherReason("Hub"); - } + public static class OtherReason extends ScanReason { + public static final ScanReason SCAN = new OtherReason("SCAN"); + public static final ScanReason RESCAN = new OtherReason("RESCAN"); + public static final ScanReason HUB = new OtherReason("HUB"); - static class OtherReason implements ScanReason { final String reason; - OtherReason(String reason) { + protected OtherReason(String reason) { + super(null, null); this.reason = reason; } - - @Override - public String toString() { - return reason; - } } - protected static class FieldScan implements ScanReason { + public static class FieldScan extends ScanReason { final AnalysisField field; - FieldScan(AnalysisField field) { + public FieldScan(AnalysisField field) { + this(field, null, null); + } + + public FieldScan(AnalysisField field, JavaConstant receiver, ScanReason previous) { + super(previous, receiver); this.field = field; } @@ -412,10 +461,11 @@ public String toString() { } } - static class ArrayScan implements ScanReason { + public static class ArrayScan extends ScanReason { final AnalysisType arrayType; - ArrayScan(AnalysisType arrayType) { + public ArrayScan(AnalysisType arrayType, JavaConstant array, ScanReason previous) { + super(previous, array); this.arrayType = arrayType; } @@ -425,22 +475,25 @@ public String toString() { } } - protected static class MethodScan implements ScanReason { - final AnalysisMethod method; - final BytecodePosition sourcePosition; + public static class EmbeddedRootScan extends ScanReason { + final BytecodePosition position; + + public EmbeddedRootScan(BytecodePosition nodeSourcePosition, JavaConstant root) { + this(nodeSourcePosition, root, null); + } - MethodScan(AnalysisMethod method, BytecodePosition nodeSourcePosition) { - this.method = method; - this.sourcePosition = nodeSourcePosition; + public EmbeddedRootScan(BytecodePosition nodeSourcePosition, JavaConstant root, ScanReason previous) { + super(previous, root); + this.position = nodeSourcePosition; } public AnalysisMethod getMethod() { - return method; + return (AnalysisMethod) position.getMethod(); } @Override public String toString() { - return sourcePosition == null ? method.format("%H.%n(%p)") : method.asStackTraceElement(sourcePosition.getBCI()).toString(); + return position.getMethod().asStackTraceElement(position.getBCI()).toString(); } } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/ObjectScanningObserver.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/ObjectScanningObserver.java index 9101fa4ab148..d208dbbd5e21 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/ObjectScanningObserver.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/ObjectScanningObserver.java @@ -38,6 +38,7 @@ * possible cases: the element value is either null or the field value is non-null. The implementers * of this API will call the appropriate method during scanning. */ +@SuppressWarnings("unused") public interface ObjectScanningObserver { /** @@ -46,30 +47,51 @@ public interface ObjectScanningObserver { * For relocated pointers the value is only known at runtime after methods are relocated, which * is pretty much the same as a field written at runtime: we do not have a constant value. */ - void forRelocatedPointerFieldValue(JavaConstant receiver, AnalysisField field, JavaConstant fieldValue); + default boolean forRelocatedPointerFieldValue(JavaConstant receiver, AnalysisField field, JavaConstant fieldValue, ScanReason reason) { + return false; + } /** * Hook for scanned null field value. */ - void forNullFieldValue(JavaConstant receiver, AnalysisField field); + default boolean forNullFieldValue(JavaConstant receiver, AnalysisField field, ScanReason reason) { + return false; + } /** * Hook for scanned non-null field value. + * + * @return true if value is consumed, false otherwise */ - void forNonNullFieldValue(JavaConstant receiver, AnalysisField field, JavaConstant fieldValue); + default boolean forNonNullFieldValue(JavaConstant receiver, AnalysisField field, JavaConstant fieldValue, ScanReason reason) { + return false; + } /** * Hook for scanned null element value. */ - void forNullArrayElement(JavaConstant array, AnalysisType arrayType, int elementIndex); + default boolean forNullArrayElement(JavaConstant array, AnalysisType arrayType, int elementIndex, ScanReason reason) { + return false; + } /** * Hook for scanned non-null element value. + * + * @return true if value is consumed, false otherwise */ - void forNonNullArrayElement(JavaConstant array, AnalysisType arrayType, JavaConstant elementConstant, AnalysisType elementType, int elementIndex); + default boolean forNonNullArrayElement(JavaConstant array, AnalysisType arrayType, JavaConstant elementConstant, AnalysisType elementType, int elementIndex, ScanReason reason) { + return false; + } + + /** + * Hook for scanned embedded root. + */ + default void forEmbeddedRoot(JavaConstant root, ScanReason reason) { + } /** * Hook for scanned constant. */ - void forScannedConstant(JavaConstant scannedValue, ScanReason reason); + default void forScannedConstant(JavaConstant scannedValue, ScanReason reason) { + } } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/PointsToAnalysis.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/PointsToAnalysis.java index bea0c4d5348b..9f72be1fe3bc 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/PointsToAnalysis.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/PointsToAnalysis.java @@ -45,7 +45,6 @@ import java.util.concurrent.atomic.AtomicLongArray; import java.util.function.Function; -import com.oracle.graal.pointsto.meta.PointsToAnalysisMethod; import org.graalvm.compiler.api.replacements.SnippetReflectionProvider; import org.graalvm.compiler.core.common.SuppressFBWarnings; import org.graalvm.compiler.core.common.spi.ConstantFieldProvider; @@ -58,7 +57,6 @@ import org.graalvm.compiler.printer.GraalDebugHandlersFactory; import org.graalvm.nativeimage.hosted.Feature; -import com.oracle.graal.pointsto.ObjectScanner.ReusableSet; import com.oracle.graal.pointsto.api.HostVM; import com.oracle.graal.pointsto.api.PointstoOptions; import com.oracle.graal.pointsto.constraints.UnsupportedFeatures; @@ -77,6 +75,7 @@ import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.graal.pointsto.meta.AnalysisUniverse; import com.oracle.graal.pointsto.meta.HostedProviders; +import com.oracle.graal.pointsto.meta.PointsToAnalysisMethod; import com.oracle.graal.pointsto.reports.StatisticsPrinter; import com.oracle.graal.pointsto.typestate.PointsToStats; import com.oracle.graal.pointsto.typestate.TypeState; @@ -128,7 +127,7 @@ public abstract class PointsToAnalysis implements BigBang { private final CompletionExecutor.Timing timing; public final Timer typeFlowTimer; - public final Timer checkObjectsTimer; + public final Timer verifyHeapTimer; public final Timer processFeaturesTimer; public final Timer analysisTimer; @@ -142,7 +141,7 @@ public PointsToAnalysis(OptionValues options, AnalysisUniverse universe, HostedP this.hostVM = hostVM; String imageName = hostVM.getImageName(); this.typeFlowTimer = new Timer(imageName, "(typeflow)", false); - this.checkObjectsTimer = new Timer(imageName, "(objects)", false); + this.verifyHeapTimer = new Timer(imageName, "(verify)", false); this.processFeaturesTimer = new Timer(imageName, "(features)", false); this.analysisTimer = new Timer(imageName, "analysis", true); @@ -152,6 +151,7 @@ public PointsToAnalysis(OptionValues options, AnalysisUniverse universe, HostedP this.unsupportedFeatures = unsupportedFeatures; this.providers = providers; this.strengthenGraalGraphs = strengthenGraalGraphs; + providers.setBigBang(this); this.objectType = metaAccess.lookupJavaType(Object.class); /* @@ -194,14 +194,14 @@ public Timer getProcessFeaturesTimer() { @Override public void printTimers() { typeFlowTimer.print(); - checkObjectsTimer.print(); + verifyHeapTimer.print(); processFeaturesTimer.print(); } @Override public void printTimerStatistics(PrintWriter out) { StatisticsPrinter.print(out, "typeflow_time_ms", typeFlowTimer.getTotalTime()); - StatisticsPrinter.print(out, "objects_time_ms", checkObjectsTimer.getTotalTime()); + StatisticsPrinter.print(out, "verify_time_ms", verifyHeapTimer.getTotalTime()); StatisticsPrinter.print(out, "features_time_ms", processFeaturesTimer.getTotalTime()); StatisticsPrinter.print(out, "total_analysis_time_ms", analysisTimer.getTotalTime()); @@ -323,13 +323,15 @@ public void cleanupAfterAnalysis() { allSynchronizedTypeFlow = null; unsafeLoads = null; unsafeStores = null; - scannedObjects = null; ConstantObjectsProfiler.constantTypes.clear(); universe.getTypes().forEach(AnalysisType::cleanupAfterAnalysis); universe.getFields().forEach(AnalysisField::cleanupAfterAnalysis); universe.getMethods().forEach(AnalysisMethod::cleanupAfterAnalysis); + + universe.getHeapScanner().cleanupAfterAnalysis(); + universe.getHeapVerifier().cleanupAfterAnalysis(); } @Override @@ -570,14 +572,14 @@ public ConstantFieldProvider getConstantFieldProvider() { return providers.getConstantFieldProvider(); } - public CompletionExecutor getExecutor() { - return executor; - } - @Override public void checkUserLimitations() { } + public void handleUnknownValueField(@SuppressWarnings("unused") AnalysisField field) { + + } + public interface TypeFlowRunnable extends DebugContextRunnable { TypeFlow getTypeFlow(); } @@ -630,26 +632,9 @@ public void postTask(final DebugContextRunnable task) { public boolean finish() throws InterruptedException { try (Indent indent = debug.logAndIndent("starting analysis in BigBang.finish")) { universe.setAnalysisDataValid(false); - boolean didSomeWork = false; - - int numTypes; - do { - didSomeWork |= doTypeflow(); - - /* - * Check if the object graph introduces any new types, which leads to new operations - * being posted. - */ - assert executor.getPostedOperations() == 0; - numTypes = universe.getTypes().size(); - try (StopTimer t = checkObjectsTimer.start()) { - // track static fields - checkObjectGraph(); - } - } while (executor.getPostedOperations() != 0 || numTypes != universe.getTypes().size()); - + boolean didSomeWork = doTypeflow(); + assert executor.getPostedOperations() == 0; universe.setAnalysisDataValid(true); - return didSomeWork; } } @@ -668,39 +653,11 @@ public boolean doTypeflow() throws InterruptedException { return didSomeWork; } - private ReusableSet scannedObjects = new ReusableSet(); - - @SuppressWarnings("try") - private void checkObjectGraph() throws InterruptedException { - scannedObjects.reset(); - // scan constants - boolean isParallel = PointstoOptions.ScanObjectsParallel.getValue(options); - ObjectScanner objectScanner = new AnalysisObjectScanner(this, isParallel ? executor : null, scannedObjects); - checkObjectGraph(objectScanner); - if (isParallel) { - executor.start(); - objectScanner.scanBootImageHeapRoots(null, null); - executor.complete(); - executor.shutdown(); - executor.init(timing); - } else { - objectScanner.scanBootImageHeapRoots(null, null); - } - } - @Override public HeapScanningPolicy scanningPolicy() { return heapScanningPolicy; } - /** - * Traverses the object graph to discover references to new types. - * - * @param objectScanner - */ - protected void checkObjectGraph(ObjectScanner objectScanner) { - } - @Override public HostVM getHostVM() { return hostVM; @@ -744,19 +701,28 @@ public void runAnalysis(DebugContext debugContext, Function 0; + if (pendingOperations) { + System.out.println("Found pending operations, continuing analysis."); + continue; + } + /* Outer analysis loop is done. Check if heap verification modifies analysis. */ + if (!analysisModified()) { return; } } @@ -764,6 +730,18 @@ public void runAnalysis(DebugContext debugContext, Function new SubstrateWorkerThread(pool, debug); } + @Override + public void onTypeInstantiated(AnalysisType type, AnalysisType.UsageKind usageKind) { + /* Register the type as instantiated with all its super types. */ + + assert type.isAllocated() || type.isInHeap(); + assert type.isArray() || (type.isInstanceClass() && !type.isAbstract()) : this; + universe.hostVM().checkForbidden(type, usageKind); + + TypeState typeState = TypeState.forExactType(this, type, true); + TypeState typeStateNonNull = TypeState.forExactType(this, type, false); + + /* Register the instantiated type with its super types. */ + type.forAllSuperTypes(t -> { + t.instantiatedTypes.addState(this, typeState); + t.instantiatedTypesNonNull.addState(this, typeStateNonNull); + }); + } + public static class ConstantObjectsProfiler { static final ConcurrentHashMap constantTypes = new ConcurrentHashMap<>(ESTIMATED_NUMBER_OF_TYPES); diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/HostVM.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/HostVM.java index 4b9e8f035a51..b371abb8e1fc 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/HostVM.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/api/HostVM.java @@ -122,7 +122,7 @@ default boolean skipInterface(AnalysisUniverse universe, ResolvedJavaType interf } @SuppressWarnings("unused") - default boolean platformSupported(AnalysisUniverse universe, AnnotatedElement element) { + default boolean platformSupported(AnnotatedElement element) { return true; } } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/MethodTypeFlowBuilder.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/MethodTypeFlowBuilder.java index e275eec692af..614f9216b959 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/MethodTypeFlowBuilder.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/MethodTypeFlowBuilder.java @@ -134,6 +134,7 @@ import jdk.vm.ci.code.BytecodePosition; import jdk.vm.ci.common.JVMCIError; +import jdk.vm.ci.meta.JavaConstant; import jdk.vm.ci.meta.JavaKind; public class MethodTypeFlowBuilder { @@ -316,12 +317,13 @@ public void registerUsedElements(boolean registerEmbeddedRoots) { } private void registerEmbeddedRoot(ConstantNode cn) { - if (bb.scanningPolicy().trackConstant(bb, cn.asJavaConstant())) { + JavaConstant root = cn.asJavaConstant(); + if (bb.scanningPolicy().trackConstant(bb, root)) { BytecodePosition position = cn.getNodeSourcePosition(); if (position == null) { position = new BytecodePosition(null, method, 0); } - bb.getUniverse().registerEmbeddedRoot(cn.asJavaConstant(), position); + bb.getUniverse().registerEmbeddedRoot(root, position); } } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/HeapSnapshotVerifier.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/HeapSnapshotVerifier.java new file mode 100644 index 000000000000..335c1a48bc72 --- /dev/null +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/HeapSnapshotVerifier.java @@ -0,0 +1,354 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.graal.pointsto.heap; + +import static com.oracle.graal.pointsto.ObjectScanner.ScanReason; +import static com.oracle.graal.pointsto.ObjectScanner.asString; +import static com.oracle.graal.pointsto.ObjectScanner.constantAsObject; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.function.BooleanSupplier; + +import org.graalvm.compiler.options.Option; +import org.graalvm.compiler.options.OptionKey; +import org.graalvm.compiler.options.OptionType; + +import com.oracle.graal.pointsto.BigBang; +import com.oracle.graal.pointsto.ObjectScanner; +import com.oracle.graal.pointsto.ObjectScanner.ReusableSet; +import com.oracle.graal.pointsto.ObjectScanningObserver; +import com.oracle.graal.pointsto.heap.ImageHeap.ImageHeapArray; +import com.oracle.graal.pointsto.heap.ImageHeap.ImageHeapInstance; +import com.oracle.graal.pointsto.heap.ImageHeap.ImageHeapObject; +import com.oracle.graal.pointsto.meta.AnalysisField; +import com.oracle.graal.pointsto.meta.AnalysisType; +import com.oracle.graal.pointsto.util.AnalysisError; +import com.oracle.graal.pointsto.util.AnalysisFuture; +import com.oracle.graal.pointsto.util.CompletionExecutor; + +import jdk.vm.ci.meta.JavaConstant; + +public class HeapSnapshotVerifier { + private static final int QUIET = 0; + private static final int MOST = 1; + private static final int ALL = 2; + + static class Options { + @Option(help = "Control heap verifier verbosity level: 0 - quiet, 1 - print most, 2 - print all.", type = OptionType.Expert)// + public static final OptionKey HeapVerifierVerbosity = new OptionKey<>(2); + } + + protected final BigBang bb; + protected final ImageHeapScanner scanner; + protected final ImageHeap imageHeap; + + private ReusableSet scannedObjects; + private boolean heapPatched; + private boolean analysisModified; + + private final int verbosity; + + private final Set skipArrayTypes = new HashSet<>(); + /** + * Internal data structure fields that can always be skipped from reporting, for example + * `java.util.HashMap$Node.next`. These fields can change for example when a map is balanced. + */ + private final Set internalFields = new HashSet<>(); + /** + * External data structure fields that should be reported when their value is not in the heap + * (even if they don't link to the value), but should be reported when the value is completely + * missing. + */ + private final Set externalFields = new HashSet<>(); + + public HeapSnapshotVerifier(BigBang bb, ImageHeap imageHeap, ImageHeapScanner scanner) { + this.bb = bb; + this.scanner = scanner; + this.imageHeap = imageHeap; + scannedObjects = new ReusableSet(); + verbosity = Options.HeapVerifierVerbosity.getValue(bb.getOptions()); + + skipArrayTypes.add(scanner.lookupJavaType("java.util.concurrent.ConcurrentHashMap$Node").getArrayClass()); + skipArrayTypes.add(scanner.lookupJavaType("java.util.HashMap$Node").getArrayClass()); + skipArrayTypes.add(scanner.lookupJavaType("java.util.WeakHashMap$Entry").getArrayClass()); + + internalFields.add(scanner.lookupJavaField("java.util.concurrent.ConcurrentHashMap", "table")); + internalFields.add(scanner.lookupJavaField("java.util.concurrent.ConcurrentHashMap$Node", "next")); + internalFields.add(scanner.lookupJavaField("java.util.HashMap", "table")); + internalFields.add(scanner.lookupJavaField("java.util.HashMap$Node", "next")); + internalFields.add(scanner.lookupJavaField("java.util.LinkedList", "last")); + internalFields.add(scanner.lookupJavaField("java.util.LinkedList", "first")); + internalFields.add(scanner.lookupJavaField("java.util.LinkedHashMap", "head")); + internalFields.add(scanner.lookupJavaField("java.util.LinkedHashMap", "tail")); + internalFields.add(scanner.lookupJavaField("java.util.LinkedHashMap$Entry", "before")); + internalFields.add(scanner.lookupJavaField("java.util.LinkedHashMap$Entry", "after")); + internalFields.add(scanner.lookupJavaField("java.util.LinkedList$Node", "prev")); + internalFields.add(scanner.lookupJavaField("java.util.LinkedList$Node", "next")); + internalFields.add(scanner.lookupJavaField("java.util.ArrayList", "elementData")); + + externalFields.add(scanner.lookupJavaField("java.util.concurrent.ConcurrentHashMap$Node", "key")); + externalFields.add(scanner.lookupJavaField("java.util.concurrent.ConcurrentHashMap$Node", "val")); + externalFields.add(scanner.lookupJavaField("java.util.HashMap$Node", "key")); + externalFields.add(scanner.lookupJavaField("java.util.HashMap$Node", "value")); + // externalFields.add(scanner.getAnalysisField("java.lang.ref.Reference", "referent")); ? + } + + public boolean verifyHeapSnapshot(CompletionExecutor executor) throws InterruptedException { + heapPatched = false; + analysisModified = false; + scannedObjects.reset(); + ObjectScanner objectScanner = new ObjectScanner(bb, executor, scannedObjects, new ScanningObserver()); + executor.start(); + scanTypes(objectScanner); + objectScanner.scanBootImageHeapRoots(); + executor.complete(); + executor.shutdown(); + return analysisModified || heapPatched; + } + + protected void scanTypes(@SuppressWarnings("unused") ObjectScanner objectScanner) { + } + + public void cleanupAfterAnalysis() { + scannedObjects = null; + } + + private final class ScanningObserver implements ObjectScanningObserver { + + @Override + public boolean forRelocatedPointerFieldValue(JavaConstant receiver, AnalysisField field, JavaConstant fieldValue, ScanReason reason) { + boolean result = scanner.getScanningObserver().forRelocatedPointerFieldValue(receiver, field, fieldValue, reason); + if (result) { + analysisModified = true; + } + return result; + } + + @Override + public boolean forNullFieldValue(JavaConstant receiver, AnalysisField field, ScanReason reason) { + boolean result = scanner.getScanningObserver().forNullFieldValue(receiver, field, reason); + if (result) { + analysisModified = true; + } + return result; + } + + @Override + public boolean forNonNullFieldValue(JavaConstant receiver, AnalysisField field, JavaConstant fieldValue, ScanReason reason) { + if (field.isStatic()) { + TypeData typeData = field.getDeclaringClass().getOrComputeData(); + AnalysisFuture fieldValueTask = typeData.getFieldTask(field); + if (!fieldValueTask.isDone()) { + // warnStaticFieldNotComputed(field, fieldValue, reason); + fieldValueTask.ensureDone(); + } + JavaConstant fieldSnapshot = fieldValueTask.guardedGet(); + if (!Objects.equals(fieldSnapshot, fieldValue)) { + Runnable onAnalysisModified = () -> warnStaticFieldMismatch(field, fieldSnapshot, fieldValue, reason); + scanner.patchStaticField(typeData, field, fieldValue, reason, onAnalysisModified).ensureDone(); + heapPatched = true; + } + } else { + ImageHeapInstance receiverObject = (ImageHeapInstance) getReceiverObject(receiver, reason); + AnalysisFuture fieldValueTask = receiverObject.getFieldTask(field); + if (!fieldValueTask.isDone()) { + // warnInstanceFieldNotComputed(field, fieldValue, reason); + fieldValueTask.ensureDone(); + } + JavaConstant fieldSnapshot = fieldValueTask.guardedGet(); + if (!Objects.equals(fieldSnapshot, fieldValue)) { + Runnable onAnalysisModified = () -> warnInstanceFieldMismatch(field, fieldSnapshot, fieldValue, reason); + scanner.patchInstanceField(receiverObject, field, fieldValue, reason, onAnalysisModified).ensureDone(); + heapPatched = true; + } + } + return false; + } + + @Override + public boolean forNullArrayElement(JavaConstant array, AnalysisType arrayType, int elementIndex, ScanReason reason) { + boolean result = scanner.getScanningObserver().forNullArrayElement(array, arrayType, elementIndex, reason); + if (result) { + analysisModified = true; + } + return result; + } + + @Override + public boolean forNonNullArrayElement(JavaConstant array, AnalysisType arrayType, JavaConstant elementValue, AnalysisType elementType, int index, ScanReason reason) { + ImageHeapArray arrayObject = (ImageHeapArray) getReceiverObject(array, reason); + JavaConstant elementSnapshot = arrayObject.getElement(index); + if (!Objects.equals(elementSnapshot, elementValue)) { + Runnable onAnalysisModified = () -> warnArrayElementMismatch(arrayType, elementSnapshot, elementValue, reason); + arrayObject.setElement(index, scanner.onArrayElementReachable(array, arrayType, elementValue, index, reason, onAnalysisModified)); + heapPatched = true; + } + return false; + } + + private ImageHeapObject getReceiverObject(JavaConstant constant, ScanReason reason) { + AnalysisFuture task = imageHeap.getTask(constant); + if (task == null || !task.isDone()) { + throw error(reason, "Task %s for constant %s.", (task == null ? "is null" : "not yet executed"), constant); + } + return task.guardedGet(); + } + + @Override + public void forEmbeddedRoot(JavaConstant root, ScanReason reason) { + AnalysisFuture rootTask = imageHeap.getTask(root); + if (rootTask == null) { + throw error(reason, "No snapshot task found for embedded root %s %n", root); + } else if (rootTask.isDone()) { + JavaConstant rootSnapshot = rootTask.guardedGet().getObject(); + if (!Objects.equals(rootSnapshot, root)) { + throw error(reason, "Value mismatch for embedded root %n snapshot: %s %n new value: %s %n", rootSnapshot, root); + } + } else { + throw error(reason, "Snapshot not yet computed for embedded root %n new value: %s %n", root); + } + } + + @Override + public void forScannedConstant(JavaConstant value, ScanReason reason) { + AnalysisFuture task = imageHeap.getTask(value); + if (constantAsObject(bb, value).getClass().equals(Class.class)) { + /* Make sure the DynamicHub value is scanned. */ + if (task == null) { + warnNoTaskForHub(value, reason); + scanner.toImageHeapObject(value, reason); + heapPatched = true; + } else { + if (!task.isDone()) { + /* If there is a task for the hub it should have been triggered. */ + warnNoTaskComputedForHub(value, reason); + task.ensureDone(); + heapPatched = true; + } + JavaConstant snapshot = task.guardedGet().getObject(); + if (!Objects.equals(snapshot, value)) { + throw error(reason, "Value mismatch for hub snapshot: %s %n new value: %s %n", snapshot, value); + } + } + } + } + } + + private void warnNoTaskForHub(JavaConstant value, ScanReason reason) { + analysisModified = true; + if (!skipWarning()) { + warning(reason, "No snapshot task found for hub %s %n", value); + } + } + + private void warnNoTaskComputedForHub(JavaConstant value, ScanReason reason) { + analysisModified = true; + if (!skipWarning()) { + warning(reason, "Snapshot not yet computed for hub %n new value: %s %n", value); + } + } + + private void warnArrayElementMismatch(AnalysisType arrayType, JavaConstant elementSnapshot, JavaConstant elementValue, ScanReason reason) { + analysisModified = true; + if (skipWarning(() -> skipArrayTypes.contains(arrayType))) { + return; + } + warning(reason, "Value mismatch for array element %n snapshot: %s %n new value: %s %n", elementSnapshot, elementValue); + } + + private void warnStaticFieldMismatch(AnalysisField field, JavaConstant fieldSnapshot, JavaConstant fieldValue, ScanReason reason) { + analysisModified = true; + if (!skipWarning(() -> internalFields.contains(field))) { + warning(reason, "Value mismatch for static field %s %n snapshot: %s %n new value: %s %n", field, fieldSnapshot, fieldValue); + } + } + + @SuppressWarnings("unused") + private void warnStaticFieldNotComputed(AnalysisField field, JavaConstant fieldValue, ScanReason reason) { + if (!skipWarning(() -> internalFields.contains(field))) { + warning(reason, "Snapshot not yet computed for static field %s %n new value: %s %n", field, fieldValue); + } + } + + private void warnInstanceFieldMismatch(AnalysisField field, JavaConstant fieldSnapshot, JavaConstant fieldValue, ScanReason reason) { + analysisModified = true; + if (!skipWarning(() -> internalFields.contains(field))) { + warning(reason, "Value mismatch for instance field %s %n snapshot: %s %n new value: %s %n", field, fieldSnapshot, fieldValue); + } + } + + @SuppressWarnings("unused") + private void warnInstanceFieldNotComputed(AnalysisField field, JavaConstant fieldValue, ScanReason reason) { + if (!skipWarning(() -> internalFields.contains(field) || externalFields.contains(field))) { + warning(reason, "Snapshot not yet computed for instance field %s %n new value: %s %n", field, fieldValue); + } + } + + private boolean skipWarning() { + return skipWarning(() -> false); + } + + private boolean skipWarning(BooleanSupplier skip) { + if (verbosity == QUIET) { + return true; + } else if (verbosity == MOST) { + return skip.getAsBoolean(); + } + assert verbosity == ALL; + return false; + } + + private void warning(ScanReason reason, String format, Object... args) { + System.out.println("WARNING: " + message(reason, format, args)); + } + + private RuntimeException error(ScanReason reason, String format, Object... args) { + throw AnalysisError.shouldNotReachHere(message(reason, format, args)); + } + + private String message(ScanReason reason, String format, Object... args) { + String message = format(format, args); + StringBuilder objectBacktrace = new StringBuilder(); + ObjectScanner.buildObjectBacktrace(bb, reason, objectBacktrace); + message += objectBacktrace; + return message; + } + + private String format(String msg, Object... args) { + if (args != null) { + for (int i = 0; i < args.length; ++i) { + if (args[i] instanceof JavaConstant) { + args[i] = asString(bb, (JavaConstant) args[i]); + } else if (args[i] instanceof AnalysisField) { + args[i] = ((AnalysisField) args[i]).format("%H.%n"); + } + } + } + return String.format(msg, args); + } + +} diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeap.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeap.java new file mode 100644 index 000000000000..8f759046c761 --- /dev/null +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeap.java @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.graal.pointsto.heap; + +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import com.oracle.graal.pointsto.ObjectScanner; +import com.oracle.graal.pointsto.heap.value.ValueSupplier; +import com.oracle.graal.pointsto.meta.AnalysisField; +import com.oracle.graal.pointsto.meta.AnalysisType; +import com.oracle.graal.pointsto.util.AnalysisFuture; + +import jdk.vm.ci.meta.JavaConstant; + +public class ImageHeap { + + /** Map the original object *and* the replaced object to the HeapObject snapshot. */ + protected ConcurrentHashMap> heapObjects; + /** Store a mapping from types to all scanned objects. */ + protected Map> typesToObjects; + + public ImageHeap() { + heapObjects = new ConcurrentHashMap<>(); + typesToObjects = new ConcurrentHashMap<>(); + } + + public AnalysisFuture getTask(JavaConstant constant) { + return heapObjects.get(constant); + } + + public AnalysisFuture addTask(JavaConstant constant, AnalysisFuture object) { + return heapObjects.putIfAbsent(constant, object); + } + + public Set getObjects(AnalysisType type) { + return typesToObjects.getOrDefault(type, Collections.emptySet()); + } + + public boolean add(AnalysisType type, ImageHeapObject heapObj) { + Set objectSet = typesToObjects.computeIfAbsent(type, t -> ConcurrentHashMap.newKeySet()); + return objectSet.add(heapObj); + } + + /** + * Model for snapshotted objects. It stores the replaced object, i.e., the result of applying + * object replacers on the original object, and the instance field values of this object. The + * field values are stored as JavaConstant to also encode primitive values. ImageHeapObject are + * created only after an object is processed through the object replacers. + */ + public static class ImageHeapObject { + /** Store the object, already processed by the object transformers. */ + private final JavaConstant object; + + ImageHeapObject(JavaConstant object) { + this.object = object; + } + + public JavaConstant getObject() { + return object; + } + + /* Equals and hashCode just compare the replaced constant. */ + + @Override + public boolean equals(Object o) { + // TODO == + if (o instanceof ImageHeapObject) { + ImageHeapObject other = (ImageHeapObject) o; + return this.object.equals(other.object); + } + return false; + } + + @Override + public int hashCode() { + return object.hashCode(); + } + } + + public static final class ImageHeapInstance extends ImageHeapObject { + + /** Store original field values of the object. */ + private final AnalysisFuture[] objectFieldValues; + + ImageHeapInstance(JavaConstant object, AnalysisFuture[] objectFieldValues) { + super(object); + this.objectFieldValues = objectFieldValues; + } + + public JavaConstant readFieldValue(AnalysisField field) { + return objectFieldValues[field.getPosition()].ensureDone(); + } + + /** + * Return a task for transforming and snapshotting the field value, effectively a future for + * {@link ImageHeapScanner#onFieldValueReachable(AnalysisField, JavaConstant, ValueSupplier, ObjectScanner.ScanReason)}. + */ + public AnalysisFuture getFieldTask(AnalysisField field) { + return objectFieldValues[field.getPosition()]; + } + + /** + * Read the field value, executing the field task in this thread if not already executed. + */ + public JavaConstant readField(AnalysisField field) { + return objectFieldValues[field.getPosition()].ensureDone(); + } + + public void setFieldTask(AnalysisField field, AnalysisFuture task) { + objectFieldValues[field.getPosition()] = task; + } + } + + static final class ImageHeapArray extends ImageHeapObject { + /** Contains the already scanned array elements. */ + private final JavaConstant[] arrayElementValues; + + ImageHeapArray(JavaConstant object, JavaConstant[] arrayElementValues) { + super(object); + this.arrayElementValues = arrayElementValues; + } + + /** + * Return the value of the element at the specified index as computed by + * {@link ImageHeapScanner#onArrayElementReachable(JavaConstant, AnalysisType, JavaConstant, int, ObjectScanner.ScanReason)}. + */ + public JavaConstant getElement(int idx) { + return arrayElementValues[idx]; + } + + public void setElement(int idx, JavaConstant value) { + arrayElementValues[idx] = value; + } + + public int getLength() { + return arrayElementValues.length; + } + } + +} diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapScanner.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapScanner.java new file mode 100644 index 000000000000..ec4c4333614a --- /dev/null +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageHeapScanner.java @@ -0,0 +1,605 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.graal.pointsto.heap; + +import java.lang.reflect.Field; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +import org.graalvm.collections.EconomicMap; +import org.graalvm.collections.MapCursor; +import org.graalvm.compiler.api.replacements.SnippetReflectionProvider; +import org.graalvm.compiler.debug.GraalError; +import org.graalvm.compiler.options.Option; +import org.graalvm.compiler.options.OptionKey; + +import com.oracle.graal.pointsto.ObjectScanner; +import com.oracle.graal.pointsto.ObjectScanner.ArrayScan; +import com.oracle.graal.pointsto.ObjectScanner.EmbeddedRootScan; +import com.oracle.graal.pointsto.ObjectScanner.FieldScan; +import com.oracle.graal.pointsto.ObjectScanner.OtherReason; +import com.oracle.graal.pointsto.ObjectScanner.ScanReason; +import com.oracle.graal.pointsto.ObjectScanningObserver; +import com.oracle.graal.pointsto.PointsToAnalysis; +import com.oracle.graal.pointsto.api.HostVM; +import com.oracle.graal.pointsto.heap.ImageHeap.ImageHeapArray; +import com.oracle.graal.pointsto.heap.ImageHeap.ImageHeapInstance; +import com.oracle.graal.pointsto.heap.ImageHeap.ImageHeapObject; +import com.oracle.graal.pointsto.heap.value.ValueSupplier; +import com.oracle.graal.pointsto.meta.AnalysisField; +import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; +import com.oracle.graal.pointsto.meta.AnalysisType; +import com.oracle.graal.pointsto.meta.AnalysisUniverse; +import com.oracle.graal.pointsto.util.AnalysisError; +import com.oracle.graal.pointsto.util.AnalysisFuture; +import com.oracle.graal.pointsto.util.GraalAccess; +import com.oracle.svm.util.ReflectionUtil; + +import jdk.vm.ci.code.BytecodePosition; +import jdk.vm.ci.meta.ConstantReflectionProvider; +import jdk.vm.ci.meta.JavaConstant; +import jdk.vm.ci.meta.JavaKind; +import jdk.vm.ci.meta.ResolvedJavaField; + +/** + * Scanning is triggered when: + *
    + *
  • a static final field is marked as accessed, or
  • s + *
  • a method that is parsed and embedded roots are discovered
  • + *
+ *

+ * When an instance field is marked as accessed the objects of its declaring type (and all the + * subtypes) are re-scanned. + */ +public abstract class ImageHeapScanner { + public static class Options { + @Option(help = "Enable manual rescanning of image heap objects.")// + public static final OptionKey EnableManualRescan = new OptionKey<>(true); + } + + protected final ImageHeap imageHeap; + protected final AnalysisMetaAccess metaAccess; + protected final AnalysisUniverse universe; + protected final HostVM hostVM; + + protected final SnippetReflectionProvider snippetReflection; + protected final ConstantReflectionProvider constantReflection; + protected final ConstantReflectionProvider hostedConstantReflection; + protected final SnippetReflectionProvider hostedSnippetReflection; + + protected ObjectScanningObserver scanningObserver; + private final boolean enableManualRescan; + + public ImageHeapScanner(ImageHeap heap, AnalysisMetaAccess aMetaAccess, SnippetReflectionProvider aSnippetReflection, + ConstantReflectionProvider aConstantReflection, ObjectScanningObserver aScanningObserver) { + imageHeap = heap; + metaAccess = aMetaAccess; + universe = aMetaAccess.getUniverse(); + hostVM = aMetaAccess.getUniverse().hostVM(); + snippetReflection = aSnippetReflection; + constantReflection = aConstantReflection; + scanningObserver = aScanningObserver; + hostedConstantReflection = GraalAccess.getOriginalProviders().getConstantReflection(); + hostedSnippetReflection = GraalAccess.getOriginalProviders().getSnippetReflection(); + enableManualRescan = Options.EnableManualRescan.getValue(hostVM.options()); + } + + public void scanEmbeddedRoot(JavaConstant root, BytecodePosition position) { + AnalysisType type = metaAccess.lookupJavaType(root); + type.registerAsReachable(); + getOrCreateConstantReachableTask(root, new EmbeddedRootScan(position, root)).ensureDone(); + } + + @SuppressWarnings("unused") + public void scanFieldValue(AnalysisField field, JavaConstant receiver) { + assert field.isReachable() && isValueAvailable(field); + if (field.isStatic()) { + postTask(() -> field.getDeclaringClass().getOrComputeData().readField(field)); + } else { + postTask(() -> ((ImageHeapInstance) toImageHeapObject(receiver)).readField(field)); + } + } + + public void onFieldRead(AnalysisField field) { + assert field.isRead(); + /* Check if the value is available before accessing it. */ + if (isValueAvailable(field)) { + AnalysisType declaringClass = field.getDeclaringClass(); + if (field.isStatic()) { + postTask(() -> declaringClass.getOrComputeData().readField(field)); + } else { + /* Trigger field scanning for the already processed objects. */ + postTask(() -> onInstanceFieldRead(field, declaringClass)); + } + } + } + + private void onInstanceFieldRead(AnalysisField field, AnalysisType type) { + for (AnalysisType subtype : type.getSubTypes()) { + for (ImageHeapObject imageHeapObject : imageHeap.getObjects(subtype)) { + postTask(((ImageHeapInstance) imageHeapObject).getFieldTask(field)); + } + /* Subtypes include this type itself. */ + if (!subtype.equals(type)) { + onInstanceFieldRead(field, subtype); + } + } + } + + /** + * Computes the class initialization status and the snapshot of all static fields. This is an + * expensive operation and therefore done in an asynchronous task. + */ + public TypeData computeTypeData(AnalysisType type) { + GraalError.guarantee(type.isReachable(), "TypeData is only available for reachable types"); + + /* Decide if the type should be initialized at build time or at run time: */ + boolean initializeAtRunTime = shouldInitializeAtRunTime(type); + + /* + * Snapshot all static fields. This reads the raw field value of all fields regardless of + * reachability status. The field value is processed when a field is marked as reachable, in + * onFieldValueReachable(). + */ + Map> rawStaticFieldValues = new HashMap<>(); + for (AnalysisField field : type.getStaticFields()) { + ValueSupplier rawFieldValue = readHostedFieldValue(field, null); + rawStaticFieldValues.put(field, new AnalysisFuture<>(() -> onFieldValueReachable(field, null, rawFieldValue, new FieldScan(field)))); + } + + return new TypeData(initializeAtRunTime, rawStaticFieldValues); + } + + protected boolean shouldInitializeAtRunTime(@SuppressWarnings("unused") AnalysisType type) { + return false; + } + + void markTypeInstantiated(AnalysisType type) { + if (universe.sealed() && !type.isReachable()) { + throw AnalysisError.shouldNotReachHere("Universe is sealed. New type reachable: " + type.toJavaName()); + } + type.registerAsInHeap(); + } + + JavaConstant markConstantReachable(JavaConstant constant, ScanReason reason) { + if (constant.getJavaKind() == JavaKind.Object && constant.isNonNull()) { + return getOrCreateConstantReachableTask(constant, reason).ensureDone().getObject(); + } + + return constant; + } + + public ImageHeapObject toImageHeapObject(JavaConstant constant) { + return toImageHeapObject(constant, OtherReason.SCAN); + } + + public ImageHeapObject toImageHeapObject(JavaConstant constant, ScanReason reason) { + assert constant != null && constant.getJavaKind() == JavaKind.Object && constant.isNonNull(); + return getOrCreateConstantReachableTask(constant, reason).ensureDone(); + } + + public void scan(JavaConstant javaConstant, ScanReason reason) { + assert javaConstant.getJavaKind() == JavaKind.Object && javaConstant.isNonNull(); + postTask(getOrCreateConstantReachableTask(javaConstant, reason)); + } + + protected AnalysisFuture getOrCreateConstantReachableTask(JavaConstant javaConstant, ScanReason reason) { + ScanReason nonNullReason = Objects.requireNonNull(reason); + AnalysisFuture existingTask = imageHeap.getTask(javaConstant); + if (existingTask == null) { + /* + * TODO - this still true? is it ok to seal the heap? + * + * The constants can be generated at any stage, not only before/during analysis. For + * example `com.oracle.svm.core.graal.snippets.SubstrateAllocationSnippets.Templates. + * getProfilingData()` generates `AllocationProfilingData` during lowering. + */ + if (universe.sealed()) { + throw AnalysisError.shouldNotReachHere("Universe is sealed. New constant reachable: " + javaConstant.toValueString()); + } + AnalysisFuture newTask = new AnalysisFuture<>(() -> createImageHeapObject(javaConstant, nonNullReason)); + existingTask = imageHeap.addTask(javaConstant, newTask); + if (existingTask == null) { + /* + * Immediately schedule the new task. There is no need to have not-yet-reachable + * ImageHeapObject. + */ + postTask(newTask); + return newTask; + } + } + return existingTask; + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + protected ImageHeapObject createImageHeapObject(JavaConstant constant, ScanReason reason) { + assert constant.getJavaKind() == JavaKind.Object && !constant.isNull(); + + Optional replaced = maybeReplace(constant, reason); + if (replaced.isPresent()) { + /* + * This ensures that we have a unique ImageHeapObject for the original and replaced + * object. As a side effect, this runs all object transformer again on the replaced + * constant. + */ + return toImageHeapObject(replaced.get(), reason); + } + + if (!hostVM.platformSupported(asObject(constant).getClass())) { + return null; + } + + /* + * Access the constant type after the replacement. Some constants may have types that should + * not be reachable at run time and thus are replaced. + */ + AnalysisType type = metaAccess.lookupJavaType(constant); + + ImageHeapObject newImageHeapObject; + if (type.isArray()) { + int length = constantReflection.readArrayLength(constant); + JavaConstant[] arrayElements = new JavaConstant[length]; + ScanReason arrayReason = new ArrayScan(type, constant, reason); + for (int idx = 0; idx < length; idx++) { + final JavaConstant rawElementValue = constantReflection.readArrayElement(constant, idx); + arrayElements[idx] = onArrayElementReachable(constant, type, rawElementValue, idx, arrayReason); + } + newImageHeapObject = new ImageHeapArray(constant, arrayElements); + markTypeInstantiated(type); + } else { + /* + * We need to have the new ImageHeapInstance early so that we can reference it in the + * lambda when the field value gets reachable. But it must not be published to any other + * thread before all instanceFieldValues are filled in. + */ + /* We are about to query the type's fields, the type must be marked as reachable. */ + markTypeInstantiated(type); + AnalysisField[] instanceFields = type.getInstanceFields(true); + AnalysisFuture[] instanceFieldValues = new AnalysisFuture[instanceFields.length]; + for (AnalysisField field : instanceFields) { + ScanReason fieldReason = new FieldScan(field, constant, reason); + ValueSupplier rawFieldValue; + try { + rawFieldValue = readHostedFieldValue(field, universe.toHosted(constant)); + } catch (InternalError | TypeNotPresentException | LinkageError e) { + /* Ignore missing type errors. */ + continue; + } + instanceFieldValues[field.getPosition()] = new AnalysisFuture<>(() -> onFieldValueReachable(field, constant, rawFieldValue, fieldReason)); + } + newImageHeapObject = new ImageHeapInstance(constant, instanceFieldValues); + } + + /* + * Following all the array elements and reachable field values can be done asynchronously. + */ + postTask(() -> onObjectReachable(newImageHeapObject)); + return newImageHeapObject; + } + + private Optional maybeReplace(JavaConstant constant, ScanReason reason) { + Object unwrapped = unwrapObject(constant); + if (unwrapped == null) { + throw GraalError.shouldNotReachHere(formatReason("Could not unwrap constant", reason)); + } else if (unwrapped instanceof ImageHeapObject) { + throw GraalError.shouldNotReachHere(formatReason("Double wrapping of constant. Most likely, the reachability analysis code itself is seen as reachable.", reason)); + } + + /* Run all registered object replacers. */ + if (constant.getJavaKind() == JavaKind.Object) { + Object replaced = universe.replaceObject(unwrapped); + if (replaced != unwrapped) { + JavaConstant replacedConstant = universe.getSnippetReflection().forObject(replaced); + return Optional.of(replacedConstant); + } + } + return Optional.empty(); + } + + protected Object unwrapObject(JavaConstant constant) { + return snippetReflection.asObject(Object.class, constant); + } + + JavaConstant onFieldValueReachable(AnalysisField field, JavaConstant receiver, JavaConstant fieldValue, ScanReason reason, Runnable onAnalysisModified) { + return onFieldValueReachable(field, receiver, ValueSupplier.eagerValue(fieldValue), reason, onAnalysisModified); + } + + JavaConstant onFieldValueReachable(AnalysisField field, JavaConstant receiver, ValueSupplier rawValue, ScanReason reason) { + return onFieldValueReachable(field, receiver, rawValue, reason, null); + } + + JavaConstant onFieldValueReachable(AnalysisField field, JavaConstant receiver, ValueSupplier rawValue, ScanReason reason, Runnable onAnalysisModified) { + AnalysisError.guarantee(field.isReachable(), "Field value is only reachable when field is reachable " + field.format("%H.%n")); + + /* + * Check if the field value is available. If not, trying to access it is an error. This + * forces the callers to only trigger the execution of the future task when the value is + * ready to be materialized. + */ + AnalysisError.guarantee(rawValue.isAvailable(), "Value not yet available for " + field.format("%H.%n")); + + // TODO the transformer here and markConstantReachable both run the object replacers + JavaConstant transformedValue = transformFieldValue(field, receiver, rawValue.get()); + /* Add the transformed value to the image heap. */ + JavaConstant fieldValue = markConstantReachable(transformedValue, reason); + + if (scanningObserver != null) { + /* Notify the points-to analysis of the scan. */ + boolean analysisModified = notifyAnalysis(field, receiver, fieldValue, reason); + if (analysisModified && onAnalysisModified != null) { + onAnalysisModified.run(); + } + } + /* Return the transformed value, but NOT the image heap object. */ + return fieldValue; + } + + private boolean notifyAnalysis(AnalysisField field, JavaConstant receiver, JavaConstant fieldValue, ScanReason reason) { + boolean analysisModified = false; + if (fieldValue.getJavaKind() == JavaKind.Object && hostVM.isRelocatedPointer(asObject(fieldValue))) { + analysisModified = scanningObserver.forRelocatedPointerFieldValue(receiver, field, fieldValue, reason); + } else if (fieldValue.isNull()) { + analysisModified = scanningObserver.forNullFieldValue(receiver, field, reason); + } else if (fieldValue.getJavaKind() == JavaKind.Object) { + analysisModified = scanningObserver.forNonNullFieldValue(receiver, field, fieldValue, reason); + } + return analysisModified; + } + + @SuppressWarnings("unused") + protected JavaConstant transformFieldValue(AnalysisField field, JavaConstant receiverConstant, JavaConstant originalValueConstant) { + return originalValueConstant; + } + + protected JavaConstant onArrayElementReachable(JavaConstant array, AnalysisType arrayType, JavaConstant rawElementValue, int elementIndex, ScanReason reason) { + return onArrayElementReachable(array, arrayType, rawElementValue, elementIndex, reason, null); + } + + protected JavaConstant onArrayElementReachable(JavaConstant array, AnalysisType arrayType, JavaConstant rawElementValue, int elementIndex, ScanReason reason, Runnable onAnalysisModified) { + JavaConstant elementValue = markConstantReachable(rawElementValue, reason); + if (scanningObserver != null && arrayType.getComponentType().getJavaKind() == JavaKind.Object) { + /* Notify the points-to analysis of the scan. */ + boolean analysisModified = notifyAnalysis(array, arrayType, elementValue, elementIndex, reason); + if (analysisModified && onAnalysisModified != null) { + onAnalysisModified.run(); + } + } + return elementValue; + } + + private boolean notifyAnalysis(JavaConstant array, AnalysisType arrayType, JavaConstant elementValue, int elementIndex, ScanReason reason) { + boolean analysisModified; + if (elementValue.isNull()) { + analysisModified = scanningObserver.forNullArrayElement(array, arrayType, elementIndex, reason); + } else { + AnalysisType elementType = metaAccess.lookupJavaType(elementValue); + analysisModified = scanningObserver.forNonNullArrayElement(array, arrayType, elementValue, elementType, elementIndex, reason); + } + return analysisModified; + } + + void onObjectReachable(ImageHeapObject imageHeapObject) { + AnalysisType objectType = metaAccess.lookupJavaType(imageHeapObject.getObject()); + imageHeap.add(objectType, imageHeapObject); + + markTypeInstantiated(objectType); + + if (imageHeapObject instanceof ImageHeapInstance) { + ImageHeapInstance imageHeapInstance = (ImageHeapInstance) imageHeapObject; + for (AnalysisField field : objectType.getInstanceFields(true)) { + if (field.isReachable() && field.isRead() && isValueAvailable(field)) { + postTask(imageHeapInstance.getFieldTask(field)); + } + } + } + } + + public boolean isValueAvailable(@SuppressWarnings("unused") AnalysisField field) { + return true; + } + + protected String formatReason(String message, ScanReason reason) { + return message + ' ' + reason; + } + + protected ValueSupplier readHostedFieldValue(AnalysisField field, JavaConstant receiver) { + assert !field.isStatic() || !shouldInitializeAtRunTime(field.getDeclaringClass()); + // Wrap the hosted constant into a substrate constant + JavaConstant value = universe.lookup(hostedConstantReflection.readFieldValue(field.wrapped, receiver)); + return ValueSupplier.eagerValue(value); + } + + protected boolean skipScanning() { + return false; + } + + public Object rescanRoot(Field reflectionField) { + if (!enableManualRescan) { + return null; + } + if (skipScanning()) { + return null; + } + AnalysisType type = metaAccess.lookupJavaType(reflectionField.getDeclaringClass()); + if (type.isReachable()) { + AnalysisField field = metaAccess.lookupJavaField(reflectionField); + JavaConstant fieldValue = readHostedFieldValue(field, null).get(); + TypeData typeData = field.getDeclaringClass().getOrComputeData(); + AnalysisFuture fieldTask = patchStaticField(typeData, field, fieldValue, OtherReason.RESCAN, null); + if (field.isRead()) { + Object root = asObject(fieldTask.ensureDone()); + rescanCollectionElements(root); + return root; + } + } + return null; + } + + public void rescanField(Object receiver, Field reflectionField) { + if (!enableManualRescan) { + return; + } + if (skipScanning()) { + return; + } + AnalysisType type = metaAccess.lookupJavaType(reflectionField.getDeclaringClass()); + if (type.isReachable()) { + AnalysisField field = metaAccess.lookupJavaField(reflectionField); + assert !field.isStatic(); + JavaConstant receiverConstant = asConstant(receiver); + Optional replaced = maybeReplace(receiverConstant, OtherReason.RESCAN); + if (replaced.isPresent()) { + receiverConstant = replaced.get(); + } + JavaConstant fieldValue = readHostedFieldValue(field, universe.toHosted(receiverConstant)).get(); + if (fieldValue != null) { + ImageHeapInstance receiverObject = (ImageHeapInstance) toImageHeapObject(receiverConstant); + AnalysisFuture fieldTask = patchInstanceField(receiverObject, field, fieldValue, OtherReason.RESCAN, null); + if (field.isRead()) { + rescanCollectionElements(asObject(fieldTask.ensureDone())); + } + } + } + } + + protected AnalysisFuture patchStaticField(TypeData typeData, AnalysisField field, JavaConstant fieldValue, ScanReason reason, Runnable onAnalysisModified) { + AnalysisFuture task = new AnalysisFuture<>(() -> onFieldValueReachable(field, null, fieldValue, reason, onAnalysisModified)); + typeData.setFieldTask(field, task); + return task; + } + + protected AnalysisFuture patchInstanceField(ImageHeapInstance receiverObject, AnalysisField field, JavaConstant fieldValue, ScanReason reason, Runnable onAnalysisModified) { + AnalysisFuture task = new AnalysisFuture<>(() -> onFieldValueReachable(field, receiverObject.getObject(), fieldValue, reason, onAnalysisModified)); + receiverObject.setFieldTask(field, task); + return task; + } + + /** Trigger rescanning of constants. */ + public void rescanObject(Object object) { + if (!enableManualRescan) { + return; + } + if (skipScanning()) { + return; + } + if (object == null) { + return; + } + doScan(asConstant(object)); + rescanCollectionElements(object); + } + + private void rescanCollectionElements(Object object) { + if (object instanceof Object[]) { + Object[] array = (Object[]) object; + for (Object element : array) { + doScan(asConstant(element)); + } + } else if (object instanceof Collection) { + Collection collection = (Collection) object; + collection.forEach(e -> doScan(asConstant(e))); + } else if (object instanceof Map) { + Map map = (Map) object; + map.forEach((k, v) -> { + doScan(asConstant(k)); + doScan(asConstant(v)); + }); + } else if (object instanceof EconomicMap) { + rescanEconomicMap((EconomicMap) object); + } + } + + protected void rescanEconomicMap(EconomicMap object) { + MapCursor cursor = object.getEntries(); + while (cursor.advance()) { + doScan(asConstant(cursor.getKey())); + doScan(asConstant(cursor.getValue())); + } + } + + void doScan(JavaConstant constant) { + if (constant.getJavaKind() == JavaKind.Object && constant.isNonNull()) { + getOrCreateConstantReachableTask(constant, OtherReason.SCAN); + } + } + + public void scanHub(AnalysisType type) { + /* Initialize dynamic hub metadata before scanning it. */ + universe.onTypeScanned(type); + metaAccess.lookupJavaType(java.lang.Class.class).registerAsReachable(); + /* We scan the original class here, the scanner does the replacement to DynamicHub. */ + getOrCreateConstantReachableTask(asConstant(type.getJavaClass()), ObjectScanner.OtherReason.HUB); + } + + protected AnalysisType analysisType(Object constant) { + return metaAccess.lookupJavaType(constant.getClass()); + } + + protected AnalysisType constantType(JavaConstant constant) { + return metaAccess.lookupJavaType(constant); + } + + protected Object asObject(JavaConstant constant) { + return snippetReflection.asObject(Object.class, constant); + } + + public JavaConstant asConstant(Object object) { + return snippetReflection.forObject(object); + } + + public void cleanupAfterAnalysis() { + scanningObserver = null; + imageHeap.heapObjects = null; + imageHeap.typesToObjects = null; + } + + public ObjectScanningObserver getScanningObserver() { + return scanningObserver; + } + + protected abstract Class getClass(String className); + + protected AnalysisType lookupJavaType(String className) { + return metaAccess.lookupJavaType(getClass(className)); + } + + protected AnalysisField lookupJavaField(String className, String fieldName) { + return metaAccess.lookupJavaField(ReflectionUtil.lookupField(getClass(className), fieldName)); + } + + public void postTask(AnalysisFuture future) { + if (future.isDone()) { + return; + } + ((PointsToAnalysis) universe.getBigbang()).postTask(debug -> future.ensureDone()); + } + + public void postTask(Runnable task) { + ((PointsToAnalysis) universe.getBigbang()).postTask(debug -> task.run()); + } +} diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/TypeData.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/TypeData.java new file mode 100644 index 000000000000..7297070a44b9 --- /dev/null +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/TypeData.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.graal.pointsto.heap; + +import java.util.Map; + +import com.oracle.graal.pointsto.ObjectScanner; +import com.oracle.graal.pointsto.heap.value.ValueSupplier; +import com.oracle.graal.pointsto.meta.AnalysisField; +import com.oracle.graal.pointsto.meta.AnalysisType; +import com.oracle.graal.pointsto.util.AnalysisFuture; + +import jdk.vm.ci.meta.JavaConstant; +import jdk.vm.ci.meta.ResolvedJavaField; + +/** + * Additional data for a {@link AnalysisType type} that is only available for types that are marked + * as reachable. Computed lazily once the type is seen as reachable. + */ +public final class TypeData { + /** + * The class initialization state: initialize the class at build time or at run time. + */ + final boolean shouldInitializeAtRunTime; + /** + * The raw values of all static fields, regardless of field reachability status. Evaluating the + * {@link AnalysisFuture} runs + * {@link ImageHeapScanner#onFieldValueReachable(AnalysisField, JavaConstant, ValueSupplier, ObjectScanner.ScanReason)} + * adds the result to the image heap}. + */ + final Map> staticFieldValues; + + public TypeData(boolean shouldInitializeAtRunTime, Map> staticFieldValues) { + this.shouldInitializeAtRunTime = shouldInitializeAtRunTime; + this.staticFieldValues = staticFieldValues; + } + + public boolean shouldInitializeAtRunTime() { + return shouldInitializeAtRunTime; + } + + /** + * Return a task for transforming and snapshotting the field value, effectively a future for + * {@link ImageHeapScanner#onFieldValueReachable(AnalysisField, JavaConstant, ValueSupplier, ObjectScanner.ScanReason)}. + */ + public AnalysisFuture getFieldTask(AnalysisField field) { + return staticFieldValues.get(field); + } + + /** Read the field value, executing the field task in this thread if not already executed. */ + public JavaConstant readField(AnalysisField field) { + return staticFieldValues.get(field).ensureDone(); + } + + public void setFieldTask(AnalysisField field, AnalysisFuture task) { + staticFieldValues.put(field, task); + } + +} diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/value/EagerValueSupplier.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/value/EagerValueSupplier.java new file mode 100644 index 000000000000..4010b72e5d9c --- /dev/null +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/value/EagerValueSupplier.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.graal.pointsto.heap.value; + +public final class EagerValueSupplier implements ValueSupplier { + + private final V value; + + EagerValueSupplier(V value) { + this.value = value; + } + + @Override + public boolean isAvailable() { + return true; + } + + @Override + public V get() { + return value; + } +} diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/value/LazyValueSupplier.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/value/LazyValueSupplier.java new file mode 100644 index 000000000000..2df2c32db1ed --- /dev/null +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/value/LazyValueSupplier.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.graal.pointsto.heap.value; + +import java.util.function.Supplier; + +import com.oracle.graal.pointsto.util.AnalysisError; + +public final class LazyValueSupplier implements ValueSupplier { + + private final Supplier valueSupplier; + private final Supplier isAvailable; + + LazyValueSupplier(Supplier valueSupplier, Supplier isAvailable) { + this.valueSupplier = valueSupplier; + this.isAvailable = isAvailable; + } + + @Override + public boolean isAvailable() { + return isAvailable.get(); + } + + @Override + public V get() { + AnalysisError.guarantee(isAvailable(), "Value is not yet available."); + return valueSupplier.get(); + } +} diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/value/ValueSupplier.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/value/ValueSupplier.java new file mode 100644 index 000000000000..3616e889f17c --- /dev/null +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/value/ValueSupplier.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.graal.pointsto.heap.value; + +import java.util.function.Supplier; + +/** + * Interface for accessing hosted heap values. It provides mechanisms for accessing hosted heap + * values either eagerly or lazily. + *

+ * Eager heap values are objects whose state doesn't change during heap snapshotting. Their value is + * immediately available. + *

+ * Lazy heap values are objects whose state can change during heap snapshotting, therefore reading + * the actual value is delayed. Instead, both a value supplier and an availability supplier are + * installed. The implementation guarantees that the value is indeed available, by checking the + * availability supplier, before it attempts to retrieve it. + */ +public interface ValueSupplier { + + static ValueSupplier eagerValue(V value) { + return new EagerValueSupplier<>(value); + } + + static ValueSupplier lazyValue(Supplier valueSupplier, Supplier isAvailable) { + return new LazyValueSupplier<>(valueSupplier, isAvailable); + } + + /** Checks if the value is available. */ + boolean isAvailable(); + + /** + * Retrieves the value, if available. Attempting to access a value before it is available + * results in error. + */ + V get(); +} diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/infrastructure/UniverseMetaAccess.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/infrastructure/UniverseMetaAccess.java index b4c2d4c77fd8..f76d51959025 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/infrastructure/UniverseMetaAccess.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/infrastructure/UniverseMetaAccess.java @@ -52,7 +52,7 @@ public ResolvedJavaType apply(Class clazz) { return universe.lookup(wrapped.lookupJavaType(clazz)); } }; - private final Universe universe; + protected final Universe universe; private final MetaAccessProvider wrapped; public UniverseMetaAccess(Universe universe, MetaAccessProvider wrapped) { diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisField.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisField.java index 5b843c17374c..291a124d64bd 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisField.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisField.java @@ -77,6 +77,8 @@ public class AnalysisField implements ResolvedJavaField, OriginalFieldProvider { private AtomicBoolean isAccessed = new AtomicBoolean(); private AtomicBoolean isRead = new AtomicBoolean(); private AtomicBoolean isWritten = new AtomicBoolean(); + private final AtomicBoolean isReachable = new AtomicBoolean(); + private boolean isJNIAccessed; private boolean isUsedInComparison; private AtomicBoolean isUnsafeAccessed; @@ -99,6 +101,7 @@ public class AnalysisField implements ResolvedJavaField, OriginalFieldProvider { private final AnalysisType declaringClass; private final AnalysisType fieldType; + private final boolean isStatic; public AnalysisField(AnalysisUniverse universe, ResolvedJavaField wrappedField) { assert !wrappedField.isInternal(); @@ -109,9 +112,11 @@ public AnalysisField(AnalysisUniverse universe, ResolvedJavaField wrappedField) this.wrapped = wrappedField; this.id = universe.nextFieldId.getAndIncrement(); + this.isStatic = Modifier.isStatic(this.getModifiers()); - readBy = PointstoOptions.TrackAccessChain.getValue(universe.hostVM().options()) ? new ConcurrentHashMap<>() : null; - writtenBy = new ConcurrentHashMap<>(); + boolean trackAccessChain = PointstoOptions.TrackAccessChain.getValue(universe.hostVM().options()); + readBy = trackAccessChain ? new ConcurrentHashMap<>() : null; + writtenBy = trackAccessChain ? new ConcurrentHashMap<>() : null; declaringClass = universe.lookup(wrappedField.getDeclaringClass()); fieldType = getDeclaredType(universe, wrappedField); @@ -129,6 +134,11 @@ public AnalysisField(AnalysisUniverse universe, ResolvedJavaField wrappedField) } } + private AnalysisUniverse getUniverse() { + /* Access the universe via the declaring class to avoid storing it here. */ + return declaringClass.getUniverse(); + } + private static AnalysisType getDeclaredType(AnalysisUniverse universe, ResolvedJavaField wrappedField) { ResolvedJavaType resolvedType; try { @@ -260,7 +270,12 @@ public void cleanupAfterAnalysis() { public boolean registerAsAccessed() { boolean firstAttempt = AtomicUtils.atomicMark(isAccessed); + markReachable(); notifyUpdateAccessInfo(); + if (firstAttempt) { + getUniverse().onFieldAccessed(this); + getUniverse().getHeapScanner().onFieldRead(this); + } return firstAttempt; } @@ -270,6 +285,11 @@ public boolean registerAsRead(MethodTypeFlow method) { if (readBy != null && method != null) { readBy.put(method, Boolean.TRUE); } + markReachable(); + if (firstAttempt) { + getUniverse().onFieldAccessed(this); + getUniverse().getHeapScanner().onFieldRead(this); + } return firstAttempt; } @@ -285,14 +305,24 @@ public boolean registerAsWritten(MethodTypeFlow method) { if (writtenBy != null && method != null) { writtenBy.put(method, Boolean.TRUE); } + markReachable(); + if (firstAttempt && (Modifier.isVolatile(getModifiers()) || getStorageKind() == JavaKind.Object)) { + getUniverse().onFieldAccessed(this); + } return firstAttempt; } - public void registerAsUnsafeAccessed(AnalysisUniverse universe) { - registerAsUnsafeAccessed(universe, DefaultUnsafePartition.get()); + public void markReachable() { + if (AtomicUtils.atomicMark(isReachable)) { + getDeclaringClass().registerAsReachable(); + } + } + + public void registerAsUnsafeAccessed() { + registerAsUnsafeAccessed(DefaultUnsafePartition.get()); } - public void registerAsUnsafeAccessed(AnalysisUniverse universe, UnsafePartitionKind partitionKind) { + public void registerAsUnsafeAccessed(UnsafePartitionKind partitionKind) { /* * A field can potentially be registered as unsafe accessed multiple times. This is * especially true for the Graal nodes because FieldsOffsetsFeature.registerFields iterates @@ -315,7 +345,7 @@ public void registerAsUnsafeAccessed(AnalysisUniverse universe, UnsafePartitionK if (isStatic()) { /* Register the static field as unsafe accessed with the analysis universe. */ - universe.registerUnsafeAccessedStaticField(this); + getUniverse().registerUnsafeAccessedStaticField(this); } else { /* Register the instance field as unsafe accessed on the declaring type. */ AnalysisType declaringType = getDeclaringClass(); @@ -380,6 +410,10 @@ public boolean isRead() { return isAccessed.get() || isRead.get(); } + public boolean isReachable() { + return isReachable.get(); + } + public boolean isWritten() { return isAccessed.get() || isWritten.get(); } @@ -444,7 +478,7 @@ public boolean isSynthetic() { @Override public boolean isStatic() { - return Modifier.isStatic(this.getModifiers()); + return isStatic; } @Override @@ -464,7 +498,7 @@ public T getAnnotation(Class annotationClass) { @Override public String toString() { - return "AnalysisField<" + format("%h.%n") + " accessed: " + isAccessed + " reads: " + isRead + " written: " + isWritten + ">"; + return "AnalysisField<" + format("%h.%n") + " accessed: " + isAccessed + " reads: " + isRead + " written: " + isWritten + " reachable: " + isReachable + ">"; } public void markAsUsedInComparison() { @@ -477,7 +511,7 @@ public boolean isUsedInComparison() { @Override public Field getJavaField() { - return OriginalFieldProvider.getJavaField(getDeclaringClass().universe.getOriginalSnippetReflection(), wrapped); + return OriginalFieldProvider.getJavaField(getUniverse().getOriginalSnippetReflection(), wrapped); } public void addAnalysisFieldObserver(AnalysisFieldObserver observer) { diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisMetaAccess.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisMetaAccess.java index 1e2704ce11f4..3f3a613f31bc 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisMetaAccess.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisMetaAccess.java @@ -57,7 +57,7 @@ public Optional optionalLookupJavaType(Class clazz) { if (result != null) { return Optional.of(result); } - result = ((AnalysisUniverse) getUniverse()).optionalLookup(getWrapped().lookupJavaType(clazz)); + result = getUniverse().optionalLookup(getWrapped().lookupJavaType(clazz)); return Optional.ofNullable(result); } @@ -85,4 +85,10 @@ public int getArrayIndexScale(JavaKind elementKind) { public int getArrayBaseOffset(JavaKind elementKind) { throw shouldNotReachHere(); } + + @Override + public AnalysisUniverse getUniverse() { + return (AnalysisUniverse) universe; + } + } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisMethod.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisMethod.java index c3863da0896f..9f7cce7c7e13 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisMethod.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisMethod.java @@ -39,13 +39,13 @@ import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; -import com.oracle.graal.pointsto.BigBang; import org.graalvm.compiler.debug.DebugContext; import org.graalvm.compiler.java.BytecodeParser.BytecodeParserError; import org.graalvm.compiler.nodes.StructuredGraph; import org.graalvm.compiler.nodes.graphbuilderconf.InvocationPlugin; import org.graalvm.util.GuardedAnnotationAccess; +import com.oracle.graal.pointsto.BigBang; import com.oracle.graal.pointsto.api.PointstoOptions; import com.oracle.graal.pointsto.constraints.UnsupportedFeatureException; import com.oracle.graal.pointsto.flow.AnalysisParsedGraph; @@ -72,7 +72,6 @@ public abstract class AnalysisMethod implements WrappedJavaMethod, GraphProvider, OriginalMethodProvider { - private final AnalysisUniverse universe; public final ResolvedJavaMethod wrapped; private final int id; @@ -103,7 +102,6 @@ public abstract class AnalysisMethod implements WrappedJavaMethod, GraphProvider protected AnalysisMethod[] implementations; public AnalysisMethod(AnalysisUniverse universe, ResolvedJavaMethod wrapped) { - this.universe = universe; this.wrapped = wrapped; this.id = universe.nextMethodId.getAndIncrement(); declaringClass = universe.lookup(wrapped.getDeclaringClass()); @@ -118,7 +116,7 @@ public AnalysisMethod(AnalysisUniverse universe, ResolvedJavaMethod wrapped) { exceptionHandlers = new ExceptionHandler[original.length]; for (int i = 0; i < original.length; i++) { ExceptionHandler h = original[i]; - JavaType catchType = getCatchType(h); + JavaType catchType = getCatchType(universe, h); exceptionHandlers[i] = new ExceptionHandler(h.getStartBCI(), h.getEndBCI(), h.getHandlerBCI(), h.catchTypeCPI(), catchType); } @@ -148,7 +146,7 @@ public String getQualifiedName() { return qualifiedName; } - private JavaType getCatchType(ExceptionHandler handler) { + private JavaType getCatchType(AnalysisUniverse universe, ExceptionHandler handler) { JavaType catchType = handler.getCatchType(); if (catchType == null) { return null; @@ -167,6 +165,11 @@ private JavaType getCatchType(ExceptionHandler handler) { return universe.lookup(resolvedCatchType); } + private AnalysisUniverse getUniverse() { + /* Access the universe via the declaring class to avoid storing it here. */ + return declaringClass.getUniverse(); + } + public void cleanupAfterAnalysis() { if (parsedGraphCacheState.get() instanceof AnalysisParsedGraph) { parsedGraphCacheState.set(GRAPH_CACHE_CLEARED); @@ -308,7 +311,7 @@ public String getName() { @Override public WrappedSignature getSignature() { - return universe.lookup(wrapped.getSignature(), getDeclaringClass()); + return getUniverse().lookup(wrapped.getSignature(), getDeclaringClass()); } @Override @@ -396,7 +399,7 @@ public boolean canBeStaticallyBound() { } public AnalysisMethod[] getImplementations() { - assert universe.analysisDataValid; + assert getUniverse().analysisDataValid; if (implementations == null) { return new AnalysisMethod[0]; } @@ -424,7 +427,7 @@ public ProfilingInfo getProfilingInfo(boolean includeNormal, boolean includeOSR) @Override public ConstantPool getConstantPool() { - return universe.lookup(wrapped.getConstantPool(), getDeclaringClass()); + return getUniverse().lookup(wrapped.getConstantPool(), getDeclaringClass()); } @Override @@ -519,7 +522,7 @@ public boolean equals(Object obj) { @Override public Executable getJavaMethod() { - return OriginalMethodProvider.getJavaMethod(universe.getOriginalSnippetReflection(), wrapped); + return OriginalMethodProvider.getJavaMethod(getUniverse().getOriginalSnippetReflection(), wrapped); } /** diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisType.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisType.java index 00dedf487478..0b6af67853c9 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisType.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisType.java @@ -25,7 +25,6 @@ package com.oracle.graal.pointsto.meta; import java.lang.annotation.Annotation; -import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -53,9 +52,11 @@ import com.oracle.graal.pointsto.flow.AllInstantiatedTypeFlow; import com.oracle.graal.pointsto.flow.context.object.AnalysisObject; import com.oracle.graal.pointsto.flow.context.object.ConstantContextSensitiveObject; +import com.oracle.graal.pointsto.heap.TypeData; import com.oracle.graal.pointsto.infrastructure.OriginalClassProvider; import com.oracle.graal.pointsto.infrastructure.WrappedJavaType; import com.oracle.graal.pointsto.typestate.TypeState; +import com.oracle.graal.pointsto.util.AnalysisError; import com.oracle.graal.pointsto.util.AnalysisFuture; import com.oracle.graal.pointsto.util.AtomicUtils; import com.oracle.graal.pointsto.util.ConcurrentLightHashSet; @@ -155,6 +156,10 @@ public enum UsageKind { } private final AnalysisFuture initializationTask; + /** + * Additional information that is only available for types that are marked as reachable. + */ + final AnalysisFuture typeData; AnalysisType(AnalysisUniverse universe, ResolvedJavaType javaType, JavaKind storageKind, AnalysisType objectType, AnalysisType cloneableType) { this.universe = universe; @@ -238,6 +243,10 @@ public enum UsageKind { /* The registration task initializes the type. */ this.initializationTask = new AnalysisFuture<>(() -> universe.hostVM.initializeType(this), null); + this.typeData = new AnalysisFuture<>(() -> { + AnalysisError.guarantee(universe.getHeapScanner() != null, "Heap scanner is not available."); + return universe.getHeapScanner().computeTypeData(this); + }); } private AnalysisType[] convertTypes(ResolvedJavaType[] originalTypes) { @@ -410,7 +419,7 @@ public static boolean verifyAssignableTypes(BigBang bb) { public boolean registerAsInHeap() { registerAsReachable(); if (AtomicUtils.atomicMark(isInHeap)) { - registerAsInstantiated(UsageKind.InHeap); + universe.onTypeInstantiated(this, UsageKind.InHeap); return true; } return false; @@ -422,29 +431,12 @@ public boolean registerAsInHeap() { public boolean registerAsAllocated(Node node) { registerAsReachable(); if (AtomicUtils.atomicMark(isAllocated)) { - registerAsInstantiated(UsageKind.Allocated); + universe.onTypeInstantiated(this, UsageKind.Allocated); return true; } return false; } - /** Register the type as instantiated with all its super types. */ - private void registerAsInstantiated(UsageKind usageKind) { - assert isAllocated.get() || isInHeap.get(); - assert isArray() || (isInstanceClass() && !Modifier.isAbstract(getModifiers())) : this; - universe.hostVM.checkForbidden(this, usageKind); - - PointsToAnalysis bb = ((PointsToAnalysis) universe.getBigbang()); - TypeState typeState = TypeState.forExactType(bb, this, true); - TypeState typeStateNonNull = TypeState.forExactType(bb, this, false); - - /* Register the instantiated type with its super types. */ - forAllSuperTypes(t -> { - t.instantiatedTypes.addState(bb, typeState); - t.instantiatedTypesNonNull.addState(bb, typeStateNonNull); - }); - } - /** * Register the type as assignable with all its super types. This is a blocking call to ensure * that the type is registered with all its super types before it is propagated by the analysis @@ -504,7 +496,7 @@ private void markReachable() { * reachable directly, but also through java.lang.GenericDeclaration, so it will be processed * twice. */ - private void forAllSuperTypes(Consumer superTypeConsumer) { + public void forAllSuperTypes(Consumer superTypeConsumer) { forAllSuperTypes(superTypeConsumer, true); } @@ -536,6 +528,11 @@ private synchronized void addAssignableType(BigBang bb, TypeState typeState) { assignableTypesNonNullState = assignableTypesState.forNonNull(((PointsToAnalysis) bb)); } + public TypeData getOrComputeData() { + GraalError.guarantee(isReachable.get(), "TypeData is only available for reachable types"); + return this.typeData.ensureDone(); + } + public void ensureInitialized() { /* Run the registration and wait for it to complete, if necessary. */ initializationTask.ensureDone(); @@ -1059,6 +1056,14 @@ public boolean isLinked() { return wrapped.isLinked(); } + public boolean isInHeap() { + return isInHeap.get(); + } + + public boolean isAllocated() { + return isAllocated.get(); + } + @Override public void link() { wrapped.link(); @@ -1085,6 +1090,10 @@ public ResolvedJavaType getHostClass() { return universe.lookup(wrapped.getHostClass()); } + AnalysisUniverse getUniverse() { + return universe; + } + @Override public int compareTo(AnalysisType other) { return Integer.compare(this.id, other.id); diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisUniverse.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisUniverse.java index f26cabef0053..51cb89d25d1e 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisUniverse.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisUniverse.java @@ -46,12 +46,15 @@ import com.oracle.graal.pointsto.BigBang; import com.oracle.graal.pointsto.api.HostVM; import com.oracle.graal.pointsto.constraints.UnsupportedFeatureException; +import com.oracle.graal.pointsto.heap.HeapSnapshotVerifier; +import com.oracle.graal.pointsto.heap.ImageHeapScanner; import com.oracle.graal.pointsto.infrastructure.AnalysisConstantPool; import com.oracle.graal.pointsto.infrastructure.SubstitutionProcessor; import com.oracle.graal.pointsto.infrastructure.Universe; import com.oracle.graal.pointsto.infrastructure.WrappedConstantPool; import com.oracle.graal.pointsto.infrastructure.WrappedJavaType; import com.oracle.graal.pointsto.infrastructure.WrappedSignature; +import com.oracle.graal.pointsto.meta.AnalysisType.UsageKind; import com.oracle.graal.pointsto.util.AnalysisError; import jdk.vm.ci.code.BytecodePosition; @@ -115,6 +118,8 @@ public class AnalysisUniverse implements Universe { private AnalysisType cloneableClass; private final JavaKind wordKind; private AnalysisPolicy analysisPolicy; + private ImageHeapScanner heapScanner; + private HeapSnapshotVerifier heapVerifier; private BigBang bb; public JavaKind getWordKind() { @@ -210,7 +215,7 @@ public JavaType lookupAllowUnresolved(JavaType rawType) { @SuppressFBWarnings(value = {"ES_COMPARING_STRINGS_WITH_EQ"}, justification = "Bug in findbugs") private AnalysisType createType(ResolvedJavaType type) { - if (!hostVM.platformSupported(this, type)) { + if (!hostVM.platformSupported(type)) { throw new UnsupportedFeatureException("type is not available in this platform: " + type.toJavaName(true)); } if (sealed && !type.isArray()) { @@ -382,7 +387,7 @@ public JavaField lookupAllowUnresolved(JavaField rawField) { } private AnalysisField createField(ResolvedJavaField field) { - if (!hostVM.platformSupported(this, field)) { + if (!hostVM.platformSupported(field)) { throw new UnsupportedFeatureException("field is not available in this platform: " + field.format("%H.%n")); } if (sealed) { @@ -425,7 +430,7 @@ public JavaMethod lookupAllowUnresolved(JavaMethod rawMethod) { } private AnalysisMethod createMethod(ResolvedJavaMethod method) { - if (!hostVM.platformSupported(this, method)) { + if (!hostVM.platformSupported(method)) { throw new UnsupportedFeatureException("Method " + method.format("%H.%n(%p)" + " is not available in this platform.")); } if (sealed) { @@ -439,7 +444,7 @@ private AnalysisMethod createMethod(ResolvedJavaMethod method) { public AnalysisMethod[] lookup(JavaMethod[] inputs) { List result = new ArrayList<>(inputs.length); for (JavaMethod method : inputs) { - if (hostVM.platformSupported(this, (ResolvedJavaMethod) method)) { + if (hostVM.platformSupported((ResolvedJavaMethod) method)) { AnalysisMethod aMethod = lookup(method); if (aMethod != null) { result.add(aMethod); @@ -488,7 +493,7 @@ public JavaConstant toHosted(JavaConstant constant) { if (constant == null) { return null; } else if (constant.getJavaKind().isObject() && !constant.isNull()) { - return originalSnippetReflection.forObject(getSnippetReflection().asObject(Object.class, constant)); + return originalSnippetReflection.forObject(snippetReflection.asObject(Object.class, constant)); } else { return constant; } @@ -520,6 +525,7 @@ public Map getEmbeddedRoots() { * Register an embedded root, i.e., a JavaConstant embedded in a Graal graph via a ConstantNode. */ public void registerEmbeddedRoot(JavaConstant root, BytecodePosition position) { + this.heapScanner.scanEmbeddedRoot(root, position); this.embeddedRoots.put(root, position); } @@ -674,6 +680,18 @@ public AnalysisType objectType() { return objectClass; } + public void onFieldAccessed(AnalysisField field) { + bb.onFieldAccessed(field); + } + + public void onTypeInstantiated(AnalysisType type, UsageKind usage) { + bb.onTypeInstantiated(type, usage); + } + + public void onTypeScanned(AnalysisType type) { + bb.onTypeScanned(type); + } + public SubstitutionProcessor getSubstitutions() { return substitutions; } @@ -693,4 +711,20 @@ public void setBigBang(BigBang bb) { public BigBang getBigbang() { return bb; } + + public void setHeapScanner(ImageHeapScanner heapScanner) { + this.heapScanner = heapScanner; + } + + public ImageHeapScanner getHeapScanner() { + return heapScanner; + } + + public void setHeapVerifier(HeapSnapshotVerifier heapVerifier) { + this.heapVerifier = heapVerifier; + } + + public HeapSnapshotVerifier getHeapVerifier() { + return heapVerifier; + } } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/HostedProviders.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/HostedProviders.java index 076c731d911f..8ff5583eb9ca 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/HostedProviders.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/HostedProviders.java @@ -37,12 +37,15 @@ import org.graalvm.compiler.phases.util.Providers; import org.graalvm.compiler.word.WordTypes; +import com.oracle.graal.pointsto.PointsToAnalysis; + import jdk.vm.ci.code.CodeCacheProvider; import jdk.vm.ci.meta.ConstantReflectionProvider; import jdk.vm.ci.meta.MetaAccessProvider; public class HostedProviders extends Providers { + private PointsToAnalysis bigBang; private GraphBuilderConfiguration.Plugins graphBuilderPlugins; public HostedProviders(MetaAccessProvider metaAccess, CodeCacheProvider codeCache, ConstantReflectionProvider constantReflection, ConstantFieldProvider constantFieldProvider, @@ -60,6 +63,14 @@ public void setGraphBuilderPlugins(GraphBuilderConfiguration.Plugins graphBuilde this.graphBuilderPlugins = graphBuilderPlugins; } + public void setBigBang(PointsToAnalysis bigBang) { + this.bigBang = bigBang; + } + + public PointsToAnalysis getBigBang() { + return bigBang; + } + @Override public Providers copyWith(ConstantReflectionProvider substitution) { assert this.getClass() == HostedProviders.class : "must override in " + getClass(); diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/reports/AnalysisHeapHistogramPrinter.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/reports/AnalysisHeapHistogramPrinter.java index c9b855de77cf..66d7b3ce6d3c 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/reports/AnalysisHeapHistogramPrinter.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/reports/AnalysisHeapHistogramPrinter.java @@ -91,23 +91,28 @@ public void forScannedConstant(JavaConstant scannedValue, ScanReason reason) { } @Override - public void forRelocatedPointerFieldValue(JavaConstant receiver, AnalysisField field, JavaConstant fieldValue) { + public boolean forRelocatedPointerFieldValue(JavaConstant receiver, AnalysisField field, JavaConstant fieldValue, ScanReason reason) { + return false; } @Override - public void forNullFieldValue(JavaConstant receiver, AnalysisField field) { + public boolean forNullFieldValue(JavaConstant receiver, AnalysisField field, ScanReason reason) { + return false; } @Override - public void forNonNullFieldValue(JavaConstant receiver, AnalysisField field, JavaConstant fieldValue) { + public boolean forNonNullFieldValue(JavaConstant receiver, AnalysisField field, JavaConstant fieldValue, ScanReason reason) { + return false; } @Override - public void forNullArrayElement(JavaConstant array, AnalysisType arrayType, int index) { + public boolean forNullArrayElement(JavaConstant array, AnalysisType arrayType, int index, ScanReason reason) { + return false; } @Override - public void forNonNullArrayElement(JavaConstant array, AnalysisType arrayType, JavaConstant elementConstant, AnalysisType elementType, int index) { + public boolean forNonNullArrayElement(JavaConstant array, AnalysisType arrayType, JavaConstant elementConstant, AnalysisType elementType, int index, ScanReason reason) { + return false; } } } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/reports/ObjectTreePrinter.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/reports/ObjectTreePrinter.java index fafe5d3364ec..3d2580cb9ac7 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/reports/ObjectTreePrinter.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/reports/ObjectTreePrinter.java @@ -320,28 +320,30 @@ private ScanningObserver(BigBang bb, Map constantT } @Override - public void forRelocatedPointerFieldValue(JavaConstant receiver, AnalysisField field, JavaConstant fieldValue) { + public boolean forRelocatedPointerFieldValue(JavaConstant receiver, AnalysisField field, JavaConstant fieldValue, ScanReason reason) { + return false; } @Override - public void forNullFieldValue(JavaConstant receiver, AnalysisField field) { + public boolean forNullFieldValue(JavaConstant receiver, AnalysisField field, ScanReason reason) { if (receiver == null) { // static field - return; + return false; } assert constantToNode.containsKey(receiver); ObjectNode receiverNode = (ObjectNode) constantToNode.get(receiver); receiverNode.addField(field, ObjectNodeBase.forNull()); + return true; } @Override - public void forNonNullFieldValue(JavaConstant receiver, AnalysisField field, JavaConstant fieldValue) { + public boolean forNonNullFieldValue(JavaConstant receiver, AnalysisField field, JavaConstant fieldValue, ScanReason reason) { if (receiver == null) { // static field - return; + return false; } if (constantToNode.containsKey(receiver) && constantToNode.containsKey(fieldValue)) { @@ -349,23 +351,27 @@ public void forNonNullFieldValue(JavaConstant receiver, AnalysisField field, Jav ObjectNodeBase valueNode = constantToNode.get(fieldValue); receiverNode.addField(field, valueNode); } + return true; } @Override - public void forNullArrayElement(JavaConstant array, AnalysisType arrayType, int index) { + public boolean forNullArrayElement(JavaConstant array, AnalysisType arrayType, int index, ScanReason reason) { assert constantToNode.containsKey(array); ArrayObjectNode arrayNode = (ArrayObjectNode) constantToNode.get(array); arrayNode.addElement(index, ObjectNodeBase.forNull()); + return true; } @Override - public void forNonNullArrayElement(JavaConstant array, AnalysisType arrayType, JavaConstant elementConstant, AnalysisType elementType, int index) { + public boolean forNonNullArrayElement(JavaConstant array, AnalysisType arrayType, JavaConstant elementConstant, AnalysisType elementType, int index, ScanReason reason) { if (constantToNode.containsKey(array) && constantToNode.containsKey(elementConstant)) { ArrayObjectNode arrayNode = (ArrayObjectNode) constantToNode.get(array); ObjectNodeBase valueNode = constantToNode.get(elementConstant); arrayNode.addElement(index, valueNode); + return true; } + return false; } @Override @@ -380,8 +386,8 @@ public void forScannedConstant(JavaConstant scannedValue, ScanReason reason) { } else { node = ObjectNodeBase.fromConstant(bb, scannedValue); } - } else if (reason instanceof MethodScan) { - ResolvedJavaMethod method = ((MethodScan) reason).getMethod(); + } else if (reason instanceof EmbeddedRootScan) { + ResolvedJavaMethod method = ((EmbeddedRootScan) reason).getMethod(); node = ObjectNodeBase.fromConstant(bb, scannedValue, new RootSource(method)); } else { node = ObjectNodeBase.fromConstant(bb, scannedValue); diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/reports/ReportUtils.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/reports/ReportUtils.java index 3ec8d973e6fa..fe82c1f7261e 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/reports/ReportUtils.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/reports/ReportUtils.java @@ -62,7 +62,7 @@ public class ReportUtils { static final Comparator fieldComparator = Comparator.comparing(f -> f.format("%H.%n")); static final Comparator invokeInfoComparator = Comparator.comparing(i -> i.getTargetMethod().format("%H.%n(%p)")); static final Comparator positionMethodComparator = Comparator.comparing(pos -> pos.getMethod().format("%H.%n(%p)")); - static final Comparator positionComparator = positionMethodComparator.thenComparing(pos -> pos.getBCI()); + public static final Comparator positionComparator = positionMethodComparator.thenComparing(pos -> pos.getBCI()); public static Path report(String description, String path, String name, String extension, Consumer reporter) { return report(description, path, name, extension, reporter, true); diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/util/AnalysisFuture.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/util/AnalysisFuture.java index cceb2af8e7b0..9dd26e85233b 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/util/AnalysisFuture.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/util/AnalysisFuture.java @@ -67,4 +67,12 @@ public V ensureDone() { } } + public V guardedGet() { + try { + return get(); + } catch (InterruptedException | ExecutionException e) { + throw AnalysisError.shouldNotReachHere(e); + } + } + } diff --git a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSunSecuritySubstitutions.java b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSunSecuritySubstitutions.java index 5dc6f389d666..eeb0c59de3ae 100644 --- a/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSunSecuritySubstitutions.java +++ b/substratevm/src/com.oracle.svm.core.posix/src/com/oracle/svm/core/posix/PosixSunSecuritySubstitutions.java @@ -58,7 +58,11 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { } private static void registerShutdownHook(@SuppressWarnings("unused") DuringAnalysisAccess access) { - RuntimeSupport.getRuntimeSupport().addTearDownHook(new NativeSecureRandomFilesCloserShutdownHook()); + NativeSecureRandomFilesCloserShutdownHook hook = new NativeSecureRandomFilesCloserShutdownHook(); + RuntimeSupport.getRuntimeSupport().addTearDownHook(hook); + // TODO just rescanning the tearDownHooks should be enough + access.rescanObject(RuntimeSupport.getRuntimeSupport()); + access.rescanObject(hook); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/annotate/RecomputeFieldValue.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/annotate/RecomputeFieldValue.java index cfc12163f2de..bec2e0489e28 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/annotate/RecomputeFieldValue.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/annotate/RecomputeFieldValue.java @@ -110,11 +110,49 @@ enum Kind { Custom, } + enum ValueAvailability { + DuringAnalysis, + AfterAnalysis, + AfterCompilation + } + + interface CustomFieldValueProvider { + + /** + * When is the value for this custom computation available? By default, it is assumed that + * the value is available {@link ValueAvailability#DuringAnalysis during analysis}. + */ + default ValueAvailability valueAvailability() { + return ValueAvailability.DuringAnalysis; + } + + /** Return true if value is available during analysis, false otherwise. */ + default boolean isAvailableDuringAnalysis() { + return valueAvailability() == ValueAvailability.DuringAnalysis; + } + + /** + * Return true if value is available only after analysis, i.e., it depends on data computed + * by the analysis (such as field offsets), false otherwise. + */ + default boolean isAvailableAfterAnalysis() { + return valueAvailability() == ValueAvailability.AfterAnalysis; + } + + /** + * Return true if value is available only after compilation, i.e., it depends on data + * computed during compilation, false otherwise. + */ + default boolean isAvailableAfterCompilation() { + return valueAvailability() == ValueAvailability.AfterCompilation; + } + } + /** * Custom recomputation of field values. A class implementing this interface must have a * no-argument constructor, which is used to instantiate it before invoking {@link #compute}. */ - interface CustomFieldValueComputer { + interface CustomFieldValueComputer extends CustomFieldValueProvider { /** * Computes the new field value. This method can already be invoked during the analysis, * especially when it computes the value of an object field that needs to be visited. @@ -140,7 +178,7 @@ interface CustomFieldValueComputer { * original value, but also requires the original field to be present, e.g., it cannot be use * for {@link Inject injected fields}. */ - interface CustomFieldValueTransformer { + interface CustomFieldValueTransformer extends CustomFieldValueProvider { /** * Computes the new field value. This method can already be invoked during the analysis, * especially when it computes the value of an object field that needs to be visited. diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/SubstrateAllocationSnippets.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/SubstrateAllocationSnippets.java index 776e25bd3f54..00ed54f1cfb9 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/SubstrateAllocationSnippets.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/SubstrateAllocationSnippets.java @@ -77,6 +77,7 @@ import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.allocationprofile.AllocationCounter; import com.oracle.svm.core.allocationprofile.AllocationSite; +import com.oracle.svm.core.annotate.UnknownObjectField; import com.oracle.svm.core.config.ConfigurationValues; import com.oracle.svm.core.graal.meta.SubstrateForeignCallsProvider; import com.oracle.svm.core.graal.nodes.NewStoredContinuationNode; @@ -384,7 +385,7 @@ public SubstrateAllocationProfilingData(AllocationSnippetCounters snippetCounter public abstract static class Templates extends SubstrateTemplates { protected final AllocationSnippetCounters snippetCounters; - private final AllocationProfilingData profilingData; + @UnknownObjectField(types = {AllocationProfilingData.class}) private AllocationProfilingData profilingData; private final SnippetInfo allocateInstance; private final SnippetInfo allocateArray; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/AnnotationTypeSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/AnnotationTypeSupport.java index 063257212b10..addff38f96f7 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/AnnotationTypeSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/AnnotationTypeSupport.java @@ -43,14 +43,17 @@ public class AnnotationTypeSupport { private Map, AnnotationType> annotationTypeMap = new HashMap<>(); @Platforms(Platform.HOSTED_ONLY.class) - public void createInstance(Class annotationClass) { - annotationTypeMap.putIfAbsent(annotationClass, AnnotationType.getInstance(annotationClass)); + public boolean createInstance(Class annotationClass) { + return annotationTypeMap.putIfAbsent(annotationClass, AnnotationType.getInstance(annotationClass)) == null; } public AnnotationType getInstance(Class annotationClass) { return annotationTypeMap.get(annotationClass); } + public Map, AnnotationType> getAnnotationTypeMap() { + return annotationTypeMap; + } } @TargetClass(className = "sun.reflect.annotation.AnnotationType") 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 e1f19af7975c..b0839cce43d0 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 @@ -424,8 +424,10 @@ public Object getInterfacesEncoding() { } @Platforms(Platform.HOSTED_ONLY.class) - public void setAnnotationsEncoding(Object annotationsEncoding) { + public boolean setAnnotationsEncoding(Object annotationsEncoding) { + boolean result = this.annotationsEncoding != annotationsEncoding; this.annotationsEncoding = annotationsEncoding; + return result; } @Platforms(Platform.HOSTED_ONLY.class) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java index a23352760526..96e5ea27fbd9 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java @@ -658,6 +658,13 @@ private Target_sun_security_ssl_SunJSSE(java.security.Provider cryptoProvider, S @TargetClass(className = "sun.security.jca.Providers") final class Target_sun_security_jca_Providers { + /* + * TODO The computation for providerList relies on repeated scans and assumes that eventually + * SecurityServicesFeature.usedProviders contains all used providers. That's why caching is also + * disabled. When the field is read too early is going to clean all providers, since none is yet + * found as used. With the new heap scanning there is no repeated scanning of the field. + */ + // @UnknownObjectField isValidForAnalysis = false @Alias// @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Custom, declClass = ProviderListTransformer.class, disableCaching = true)// private static ProviderList providerList; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/VarHandleFeature.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/VarHandleFeature.java index d19a0b91920c..77f9fe5196df 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/VarHandleFeature.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/VarHandleFeature.java @@ -221,6 +221,11 @@ class VarHandleInfo { } class VarHandleFieldOffsetComputer implements RecomputeFieldValue.CustomFieldValueComputer { + @Override + public RecomputeFieldValue.ValueAvailability valueAvailability() { + return RecomputeFieldValue.ValueAvailability.AfterAnalysis; + } + @Override public Object compute(MetaAccessProvider metaAccess, ResolvedJavaField original, ResolvedJavaField annotated, Object varHandle) { Field field = ImageSingletons.lookup(VarHandleFeature.class).findVarHandleField(varHandle); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/meta/ReadableJavaField.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/meta/ReadableJavaField.java index e22ee8e409c1..7b5b885a2e6e 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/meta/ReadableJavaField.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/meta/ReadableJavaField.java @@ -33,7 +33,9 @@ public interface ReadableJavaField extends ResolvedJavaField { static JavaConstant readFieldValue(MetaAccessProvider metaAccess, ConstantReflectionProvider originalConstantReflection, ResolvedJavaField javaField, JavaConstant javaConstant) { if (javaField instanceof ReadableJavaField) { - return ((ReadableJavaField) javaField).readValue(metaAccess, javaConstant); + ReadableJavaField readableField = (ReadableJavaField) javaField; + assert readableField.isValueAvailable(); + return readableField.readValue(metaAccess, javaConstant); } else { return originalConstantReflection.readFieldValue(javaField, javaConstant); } @@ -41,6 +43,14 @@ static JavaConstant readFieldValue(MetaAccessProvider metaAccess, ConstantReflec JavaConstant readValue(MetaAccessProvider metaAccess, JavaConstant receiver); + default boolean isValueAvailableDuringAnalysis() { + return true; + } + + default boolean isValueAvailable() { + return true; + } + boolean allowConstantFolding(); boolean injectFinalForRuntimeCompilation(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/meta/SharedField.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/meta/SharedField.java index 0ea0d16660ec..e5a98cce3a08 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/meta/SharedField.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/meta/SharedField.java @@ -52,6 +52,8 @@ public interface SharedField extends ResolvedJavaField { boolean isAccessed(); + boolean isReachable(); + boolean isWritten(); JavaKind getStorageKind(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/ImageHeapMap.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/ImageHeapMap.java index 7374640e01c0..6ac76f56378f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/ImageHeapMap.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/util/ImageHeapMap.java @@ -112,6 +112,7 @@ public void duringAnalysis(DuringAnalysisAccess access) { for (HostedImageHeapMap hostedImageHeapMap : allInstances) { if (needsUpdate(hostedImageHeapMap)) { update(hostedImageHeapMap); + access.rescanObject(hostedImageHeapMap.runtimeMap); access.requireAnalysisIteration(); } } diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/GraalSubstitutions.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/GraalSubstitutions.java index b1c02ef1fb63..04184102eb73 100644 --- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/GraalSubstitutions.java +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/GraalSubstitutions.java @@ -72,7 +72,6 @@ import com.oracle.svm.core.annotate.Substitute; import com.oracle.svm.core.annotate.TargetClass; import com.oracle.svm.core.config.ConfigurationValues; -import com.oracle.svm.core.hub.DynamicHub; import com.oracle.svm.core.log.Log; import com.oracle.svm.core.option.HostedOptionValues; import com.oracle.svm.core.util.VMError; @@ -314,7 +313,7 @@ final class Target_org_graalvm_compiler_graph_NodeClass { @SuppressWarnings("unlikely-arg-type") @SuppressFBWarnings(value = {"GC_UNRELATED_TYPES"}, justification = "Class is DynamicHub") public static NodeClass get(Class clazz) { - NodeClass nodeClass = GraalSupport.get().nodeClasses.get(DynamicHub.fromClass(clazz)); + NodeClass nodeClass = GraalSupport.get().nodeClasses.get(clazz); if (nodeClass == null) { throw VMError.shouldNotReachHere("Unknown node class: " + clazz.getName() + "\n"); } @@ -338,7 +337,7 @@ final class Target_org_graalvm_compiler_lir_LIRInstructionClass { @SuppressWarnings("unlikely-arg-type") @SuppressFBWarnings(value = {"GC_UNRELATED_TYPES"}, justification = "Class is DynamicHub") public static LIRInstructionClass get(Class clazz) { - LIRInstructionClass instructionClass = GraalSupport.get().instructionClasses.get(DynamicHub.fromClass(clazz)); + LIRInstructionClass instructionClass = GraalSupport.get().instructionClasses.get(clazz); if (instructionClass == null) { throw VMError.shouldNotReachHere("Unknown instruction class: " + clazz.getName() + "\n"); } @@ -353,7 +352,7 @@ final class Target_org_graalvm_compiler_lir_CompositeValueClass { @SuppressWarnings("unlikely-arg-type") @SuppressFBWarnings(value = {"GC_UNRELATED_TYPES"}, justification = "Class is DynamicHub") public static CompositeValueClass get(Class clazz) { - CompositeValueClass compositeValueClass = GraalSupport.get().compositeValueClasses.get(DynamicHub.fromClass(clazz)); + CompositeValueClass compositeValueClass = GraalSupport.get().compositeValueClasses.get(clazz); if (compositeValueClass == null) { throw VMError.shouldNotReachHere("Unknown composite value class: " + clazz.getName() + "\n"); } diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/GraalSupport.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/GraalSupport.java index 94af62be2e31..ce4900022458 100644 --- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/GraalSupport.java +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/GraalSupport.java @@ -27,6 +27,7 @@ import static org.graalvm.word.LocationIdentity.ANY_LOCATION; import java.io.PrintStream; +import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; @@ -68,6 +69,7 @@ import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.hosted.Feature.CompilationAccess; import org.graalvm.nativeimage.hosted.Feature.DuringAnalysisAccess; +import org.graalvm.nativeimage.hosted.Feature.FeatureAccess; import org.graalvm.word.Pointer; import org.graalvm.word.WordFactory; @@ -79,8 +81,10 @@ import com.oracle.svm.core.graal.meta.RuntimeConfiguration; import com.oracle.svm.core.graal.meta.SharedRuntimeMethod; import com.oracle.svm.core.option.RuntimeOptionValues; +import com.oracle.svm.core.util.ImageHeapMap; import com.oracle.svm.graal.meta.SubstrateMethod; import com.oracle.svm.hosted.FeatureImpl.DuringAnalysisAccessImpl; +import com.oracle.svm.util.ReflectionUtil; /** * Holds data that is pre-computed during native image generation and accessed at run time during a @@ -100,13 +104,13 @@ public class GraalSupport { private Object[] graphObjects; private NodeClass[] graphNodeTypes; - public final Map, NodeClass> nodeClasses = new HashMap<>(); - public final Map, LIRInstructionClass> instructionClasses = new HashMap<>(); - public final Map, CompositeValueClass> compositeValueClasses = new HashMap<>(); + public final EconomicMap, NodeClass> nodeClasses = ImageHeapMap.create(); + public final EconomicMap, LIRInstructionClass> instructionClasses = ImageHeapMap.create(); + public final EconomicMap, CompositeValueClass> compositeValueClasses = ImageHeapMap.create(); public HashMap, EconomicMap, List>> matchRuleRegistry; - protected Map, BasePhase.BasePhaseStatistics> basePhaseStatistics; - protected Map, LIRPhase.LIRPhaseStatistics> lirPhaseStatistics; + protected EconomicMap, BasePhase.BasePhaseStatistics> basePhaseStatistics; + protected EconomicMap, LIRPhase.LIRPhaseStatistics> lirPhaseStatistics; protected Function runtimeBackendProvider; protected final GlobalMetrics metricValues = new GlobalMetrics(); @@ -114,6 +118,11 @@ public class GraalSupport { protected final DiagnosticsOutputDirectory outputDirectory = new DiagnosticsOutputDirectory(RuntimeOptionValues.singleton()); protected final Map compilationProblemsPerAction = new EnumMap<>(ExceptionAction.class); + private static final Field graphEncodingField = ReflectionUtil.lookupField(GraalSupport.class, "graphEncoding"); + private static final Field graphObjectsField = ReflectionUtil.lookupField(GraalSupport.class, "graphObjects"); + private static final Field graphNodeTypesField = ReflectionUtil.lookupField(GraalSupport.class, "graphNodeTypes"); + private static final Field methodsToCompileField = ReflectionUtil.lookupField(GraalSupport.class, "methodsToCompile"); + private static final CGlobalData nextIsolateId = CGlobalDataFactory.createWord((Pointer) WordFactory.unsigned(1L)); private volatile long isolateId = 0; @@ -188,38 +197,50 @@ public static void setRuntimeConfig(RuntimeConfiguration runtimeConfig, Suites s } @Platforms(Platform.HOSTED_ONLY.class) - public static boolean setMethodsToCompile(SubstrateMethod[] methodsToCompile) { + public static boolean setMethodsToCompile(DuringAnalysisAccessImpl config, SubstrateMethod[] methodsToCompile) { boolean result = false; - if (!Arrays.equals(get().methodsToCompile, methodsToCompile)) { - get().methodsToCompile = methodsToCompile; + GraalSupport support = get(); + if (!Arrays.equals(support.methodsToCompile, methodsToCompile)) { + support.methodsToCompile = methodsToCompile; + GraalSupport.rescan(support, config, methodsToCompileField); result = true; } return result; } @Platforms(Platform.HOSTED_ONLY.class) - public static boolean setGraphEncoding(byte[] graphEncoding, Object[] graphObjects, NodeClass[] graphNodeTypes) { - if (get().graphObjects == null && graphObjects.length == 0) { + public static boolean setGraphEncoding(FeatureAccess a, byte[] graphEncoding, Object[] graphObjects, NodeClass[] graphNodeTypes) { + GraalSupport support = get(); + if (support.graphObjects == null && graphObjects.length == 0) { assert graphEncoding.length == 0; assert graphNodeTypes.length == 0; return false; } boolean result = false; - if (!Arrays.equals(get().graphEncoding, graphEncoding)) { - get().graphEncoding = graphEncoding; + if (!Arrays.equals(support.graphEncoding, graphEncoding)) { + support.graphEncoding = graphEncoding; + GraalSupport.rescan(support, a, graphEncodingField); result = true; } - if (!Arrays.deepEquals(get().graphObjects, graphObjects)) { - get().graphObjects = graphObjects; + if (!Arrays.deepEquals(support.graphObjects, graphObjects)) { + support.graphObjects = graphObjects; + GraalSupport.rescan(support, a, graphObjectsField); result = true; } - if (!Arrays.equals(get().graphNodeTypes, graphNodeTypes)) { - get().graphNodeTypes = graphNodeTypes; + if (!Arrays.equals(support.graphNodeTypes, graphNodeTypes)) { + support.graphNodeTypes = graphNodeTypes; + GraalSupport.rescan(support, a, graphNodeTypesField); result = true; } return result; } + private static void rescan(GraalSupport support, FeatureAccess a, Field field) { + if (a instanceof DuringAnalysisAccessImpl) { + ((DuringAnalysisAccessImpl) a).rescanField(support, field); + } + } + @Platforms(Platform.HOSTED_ONLY.class) public static void registerImmutableObjects(CompilationAccess access) { access.registerAsImmutable(get().graphEncoding); @@ -229,8 +250,8 @@ public static void registerImmutableObjects(CompilationAccess access) { @Platforms(Platform.HOSTED_ONLY.class) public static void allocatePhaseStatisticsCache() { - GraalSupport.get().basePhaseStatistics = new HashMap<>(); - GraalSupport.get().lirPhaseStatistics = new HashMap<>(); + GraalSupport.get().basePhaseStatistics = ImageHeapMap.create(); + GraalSupport.get().lirPhaseStatistics = ImageHeapMap.create(); } /* Invoked once for every class that is reachable in the native image. */ @@ -239,17 +260,16 @@ public static void registerPhaseStatistics(DuringAnalysisAccess a, Class newl DuringAnalysisAccessImpl access = (DuringAnalysisAccessImpl) a; if (!Modifier.isAbstract(newlyReachableClass.getModifiers())) { + GraalSupport support = GraalSupport.get(); if (BasePhase.class.isAssignableFrom(newlyReachableClass)) { - registerStatistics(newlyReachableClass, GraalSupport.get().basePhaseStatistics, new BasePhase.BasePhaseStatistics(newlyReachableClass), access); - + registerStatistics(newlyReachableClass, support.basePhaseStatistics, new BasePhase.BasePhaseStatistics(newlyReachableClass), access); } else if (LIRPhase.class.isAssignableFrom(newlyReachableClass)) { - registerStatistics(newlyReachableClass, GraalSupport.get().lirPhaseStatistics, new LIRPhase.LIRPhaseStatistics(newlyReachableClass), access); - + registerStatistics(newlyReachableClass, support.lirPhaseStatistics, new LIRPhase.LIRPhaseStatistics(newlyReachableClass), access); } } } - private static void registerStatistics(Class phaseSubClass, Map, S> cache, S newStatistics, DuringAnalysisAccessImpl access) { + private static void registerStatistics(Class phaseSubClass, EconomicMap, S> cache, S newStatistics, DuringAnalysisAccessImpl access) { assert !cache.containsKey(phaseSubClass); cache.put(phaseSubClass, newStatistics); diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/FieldsOffsetsFeature.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/FieldsOffsetsFeature.java index c35490ce1d93..b55c9069bf36 100644 --- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/FieldsOffsetsFeature.java +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/FieldsOffsetsFeature.java @@ -30,6 +30,7 @@ import java.util.Map; import java.util.function.Function; +import org.graalvm.collections.EconomicMap; import org.graalvm.compiler.core.common.FieldIntrospection; import org.graalvm.compiler.core.common.Fields; import org.graalvm.compiler.graph.Edges; @@ -71,6 +72,11 @@ public class FieldsOffsetsFeature implements Feature { private static final Unsafe UNSAFE = GraalUnsafeAccess.getUnsafe(); abstract static class IterationMaskRecomputation implements RecomputeFieldValue.CustomFieldValueComputer { + @Override + public RecomputeFieldValue.ValueAvailability valueAvailability() { + return RecomputeFieldValue.ValueAvailability.AfterAnalysis; + } + @Override public Object compute(MetaAccessProvider metaAccess, ResolvedJavaField original, ResolvedJavaField annotated, Object receiver) { Edges edges = getEdges((NodeClass) receiver); @@ -157,19 +163,18 @@ private static Object replaceFieldsOffsets(Object source) { private static void classReachabilityListener(DuringAnalysisAccess a, Class newlyReachableClass) { DuringAnalysisAccessImpl access = (DuringAnalysisAccessImpl) a; + GraalSupport support = GraalSupport.get(); if (Node.class.isAssignableFrom(newlyReachableClass) && newlyReachableClass != Node.class) { - FieldsOffsetsFeature.> registerClass(newlyReachableClass, GraalSupport.get().nodeClasses, NodeClass::get, false, access); - + FieldsOffsetsFeature.registerClass(newlyReachableClass, support.nodeClasses, NodeClass::get, false, access); } else if (LIRInstruction.class.isAssignableFrom(newlyReachableClass) && newlyReachableClass != LIRInstruction.class) { - FieldsOffsetsFeature.> registerClass(newlyReachableClass, GraalSupport.get().instructionClasses, LIRInstructionClass::get, true, access); - + FieldsOffsetsFeature.registerClass(newlyReachableClass, support.instructionClasses, LIRInstructionClass::get, true, access); } else if (CompositeValue.class.isAssignableFrom(newlyReachableClass) && newlyReachableClass != CompositeValue.class) { - FieldsOffsetsFeature.> registerClass(newlyReachableClass, GraalSupport.get().compositeValueClasses, CompositeValueClass::get, true, access); + FieldsOffsetsFeature.registerClass(newlyReachableClass, support.compositeValueClasses, CompositeValueClass::get, true, access); } } - private static > void registerClass(Class clazz, Map, R> registry, Function, R> lookup, boolean excludeAbstract, - DuringAnalysisAccessImpl access) { + private static > void registerClass(Class clazz, EconomicMap, R> registry, + Function, R> lookup, boolean excludeAbstract, DuringAnalysisAccessImpl access) { assert !registry.containsKey(clazz); if (!excludeAbstract || !Modifier.isAbstract(clazz.getModifiers())) { @@ -247,7 +252,7 @@ public void beforeCompilation(BeforeCompilationAccess a) { @Override public void afterCompilation(AfterCompilationAccess access) { - access.registerAsImmutable(GraalSupport.get().nodeClasses.values(), o -> true); - access.registerAsImmutable(GraalSupport.get().instructionClasses.values(), o -> true); + access.registerAsImmutable(GraalSupport.get().nodeClasses.getValues(), o -> true); + access.registerAsImmutable(GraalSupport.get().instructionClasses.getValues(), o -> true); } } diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/GraalFeature.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/GraalFeature.java index 82572f3f693a..0ae44414b925 100644 --- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/GraalFeature.java +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/GraalFeature.java @@ -338,7 +338,7 @@ public void duringSetup(DuringSetupAccess c) { ImageSingletons.add(RuntimeGraalSetup.class, new SubstrateRuntimeGraalSetup()); } GraalProviderObjectReplacements providerReplacements = ImageSingletons.lookup(RuntimeGraalSetup.class).getProviderObjectReplacements(aMetaAccess); - objectReplacer = new GraalObjectReplacer(config.getUniverse(), aMetaAccess, providerReplacements); + objectReplacer = new GraalObjectReplacer(config, config.getUniverse(), aMetaAccess, providerReplacements); config.registerObjectReplacer(objectReplacer); config.registerClassReachabilityListener(GraalSupport::registerPhaseStatistics); @@ -493,7 +493,7 @@ public void duringAnalysis(DuringAnalysisAccess c) { for (CallTreeNode node : methods.values()) { methodsToCompileArr[idx++] = objectReplacer.createMethod(node.implementationMethod); } - if (GraalSupport.setMethodsToCompile(methodsToCompileArr)) { + if (GraalSupport.setMethodsToCompile(config, methodsToCompileArr)) { config.requireAnalysisIteration(); } @@ -503,7 +503,7 @@ public void duringAnalysis(DuringAnalysisAccess c) { for (NodeClass nodeClass : nodeClasses) { metaAccess.lookupJavaType(nodeClass.getClazz()).registerAsAllocated(null); } - if (GraalSupport.setGraphEncoding(graphEncoder.getEncoding(), graphEncoder.getObjects(), nodeClasses)) { + if (GraalSupport.setGraphEncoding(config, graphEncoder.getEncoding(), graphEncoder.getObjects(), nodeClasses)) { config.requireAnalysisIteration(); } @@ -727,7 +727,7 @@ public void beforeCompilation(BeforeCompilationAccess c) { } ProgressReporter.singleton().setGraphEncodingByteLength(graphEncoder.getEncoding().length); - GraalSupport.setGraphEncoding(graphEncoder.getEncoding(), graphEncoder.getObjects(), graphEncoder.getNodeClasses()); + GraalSupport.setGraphEncoding(config, graphEncoder.getEncoding(), graphEncoder.getObjects(), graphEncoder.getNodeClasses()); objectReplacer.updateDataDuringAnalysis((AnalysisMetaAccess) hMetaAccess.getWrapped()); } @@ -888,7 +888,7 @@ public void afterCompilation(AfterCompilationAccess a) { CompilationAccessImpl config = (CompilationAccessImpl) a; HostedMetaAccess hMetaAccess = config.getMetaAccess(); - HostedUniverse hUniverse = (HostedUniverse) hMetaAccess.getUniverse(); + HostedUniverse hUniverse = hMetaAccess.getUniverse(); objectReplacer.updateSubstrateDataAfterCompilation(hUniverse, config.getProviders().getConstantFieldProvider()); objectReplacer.registerImmutableObjects(config); @@ -900,7 +900,7 @@ public void afterCompilation(AfterCompilationAccess a) { public void afterHeapLayout(AfterHeapLayoutAccess a) { AfterHeapLayoutAccessImpl config = (AfterHeapLayoutAccessImpl) a; HostedMetaAccess hMetaAccess = config.getMetaAccess(); - HostedUniverse hUniverse = (HostedUniverse) hMetaAccess.getUniverse(); + HostedUniverse hUniverse = hMetaAccess.getUniverse(); objectReplacer.updateSubstrateDataAfterHeapLayout(hUniverse); } } diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/GraalObjectReplacer.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/GraalObjectReplacer.java index 76dcd99d9580..66ec71027ece 100644 --- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/GraalObjectReplacer.java +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/GraalObjectReplacer.java @@ -24,6 +24,7 @@ */ package com.oracle.svm.graal.hosted; +import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.HashMap; @@ -36,6 +37,7 @@ import org.graalvm.compiler.core.common.spi.ConstantFieldProvider; import org.graalvm.compiler.core.common.spi.ForeignCallsProvider; import org.graalvm.compiler.debug.MetricKey; +import org.graalvm.compiler.graph.NodeClass; import org.graalvm.compiler.hotspot.GraalHotSpotVMConfig; import org.graalvm.compiler.hotspot.HotSpotBackendFactory; import org.graalvm.compiler.hotspot.SnippetResolvedJavaMethod; @@ -61,6 +63,7 @@ import com.oracle.svm.graal.meta.SubstrateMethod; import com.oracle.svm.graal.meta.SubstrateSignature; import com.oracle.svm.graal.meta.SubstrateType; +import com.oracle.svm.hosted.FeatureImpl; import com.oracle.svm.hosted.SVMHost; import com.oracle.svm.hosted.ameta.AnalysisConstantFieldProvider; import com.oracle.svm.hosted.ameta.AnalysisConstantReflectionProvider; @@ -105,11 +108,26 @@ public class GraalObjectReplacer implements Function { private final HostedStringDeduplication stringTable; - public GraalObjectReplacer(AnalysisUniverse aUniverse, AnalysisMetaAccess aMetaAccess, GraalProviderObjectReplacements providerReplacements) { + private static Field substrateFieldAnnotationsEncodingField; + private static Field substrateFieldTypeField; + private static Field substrateFieldDeclaringClassField; + private static Field dynamicHubMetaTypeField; + private static Field substrateTypeRawAllInstanceFields; + private static Field substrateMethodImplementationsFields; + private static Field substrateMethodAnnotationsEncodingFields; + + public GraalObjectReplacer(FeatureImpl.DuringSetupAccessImpl config, AnalysisUniverse aUniverse, AnalysisMetaAccess aMetaAccess, GraalProviderObjectReplacements providerReplacements) { this.aUniverse = aUniverse; this.aMetaAccess = aMetaAccess; this.providerReplacements = providerReplacements; this.stringTable = HostedStringDeduplication.singleton(); + substrateFieldAnnotationsEncodingField = config.findField(SubstrateField.class, "annotationsEncoding"); + substrateFieldTypeField = config.findField(SubstrateField.class, "type"); + substrateFieldDeclaringClassField = config.findField(SubstrateField.class, "declaringClass"); + dynamicHubMetaTypeField = config.findField(DynamicHub.class, "metaType"); + substrateTypeRawAllInstanceFields = config.findField(SubstrateType.class, "rawAllInstanceFields"); + substrateMethodImplementationsFields = config.findField(SubstrateMethod.class, "implementations"); + substrateMethodAnnotationsEncodingFields = config.findField(SubstrateMethod.class, "annotationsEncoding"); } public void setGraalRuntime(SubstrateGraalRuntime sGraalRuntime) { @@ -159,6 +177,9 @@ public Object apply(Object source) { } else if (source instanceof MetricKey) { /* Ensure lazily initialized name fields are computed. */ ((MetricKey) source).getName(); + } else if (source instanceof NodeClass) { + /* Ensure lazily initialized shortName field is computed. */ + ((NodeClass) source).shortName(); } else if (source instanceof HotSpotResolvedJavaMethod) { throw new UnsupportedFeatureException(source.toString()); @@ -217,7 +238,11 @@ public synchronized SubstrateMethod createMethod(ResolvedJavaMethod original) { * Annotations are updated in every analysis iteration, but this is a starting point. It * also ensures that all types used by annotations are created eagerly. */ - sMethod.setAnnotationsEncoding(AnnotationsProcessor.encodeAnnotations(aMetaAccess, aMethod.getAnnotations(), aMethod.getDeclaredAnnotations(), null)); + Object annotationsEncoding = AnnotationsProcessor.encodeAnnotations(aMetaAccess, aMethod.getAnnotations(), aMethod.getDeclaredAnnotations(), null); + if (sMethod.setAnnotationsEncoding(annotationsEncoding)) { + aUniverse.getHeapScanner().rescanField(sMethod, substrateFieldAnnotationsEncodingField); + // aUniverse.getHeapScanner().rescanObject(sMethod); + } } return sMethod; } @@ -246,12 +271,18 @@ public synchronized SubstrateField createField(ResolvedJavaField original) { fields.put(aField, sField); sField.setLinks(createType(aField.getType()), createType(aField.getDeclaringClass())); + aUniverse.getHeapScanner().rescanField(sField, substrateFieldTypeField); + aUniverse.getHeapScanner().rescanField(sField, substrateFieldDeclaringClassField); /* * Annotations are updated in every analysis iteration, but this is a starting point. It * also ensures that all types used by annotations are created eagerly. */ - sField.setAnnotationsEncoding(AnnotationsProcessor.encodeAnnotations(aMetaAccess, aField.getAnnotations(), aField.getDeclaredAnnotations(), null)); + Object annotationsEncoding = AnnotationsProcessor.encodeAnnotations(aMetaAccess, aField.getAnnotations(), aField.getDeclaredAnnotations(), null); + if (sField.setAnnotationsEncoding(annotationsEncoding)) { + aUniverse.getHeapScanner().rescanField(sField, substrateFieldAnnotationsEncodingField); + // aUniverse.getHeapScanner().rescanObject(sField); + } } return sField; } @@ -297,13 +328,16 @@ public synchronized SubstrateType createType(JavaType original) { sType = new SubstrateType(aType.getJavaKind(), hub); types.put(aType, sType); hub.setMetaType(sType); + aUniverse.getHeapScanner().rescanField(hub, dynamicHubMetaTypeField); sType.setRawAllInstanceFields(createAllInstanceFields(aType)); + aUniverse.getHeapScanner().rescanField(sType, substrateTypeRawAllInstanceFields); createType(aType.getSuperclass()); createType(aType.getComponentType()); for (AnalysisType aInterface : aType.getInterfaces()) { createType(aInterface); } + // aUniverse.getHeapScanner().rescanObject(sType); } return sType; } @@ -344,6 +378,7 @@ private synchronized SubstrateSignature createSignature(Signature original) { * infinite recursion. */ sSignature.setTypes(parameterTypes, createType(original.getReturnType(null))); + // aUniverse.getHeapScanner().rescanObject(sSignature); } return sSignature; } @@ -373,19 +408,26 @@ public boolean updateDataDuringAnalysis(AnalysisMetaAccess metaAccess) { implementations[idx++] = sImpl; } if (sMethod.setImplementations(implementations)) { + aUniverse.getHeapScanner().rescanField(sMethod, substrateMethodImplementationsFields); result = true; } } for (Map.Entry entry : methods.entrySet()) { - if (entry.getValue().setAnnotationsEncoding(AnnotationsProcessor.encodeAnnotations(metaAccess, entry.getKey().getAnnotations(), entry.getKey().getDeclaredAnnotations(), - entry.getValue().getAnnotationsEncoding()))) { + SubstrateMethod method = entry.getValue(); + AnalysisMethod sMethod = entry.getKey(); + Object annotationsEncoding = AnnotationsProcessor.encodeAnnotations(metaAccess, sMethod.getAnnotations(), sMethod.getDeclaredAnnotations(), method.getAnnotationsEncoding()); + if (method.setAnnotationsEncoding(annotationsEncoding)) { + aUniverse.getHeapScanner().rescanField(method, substrateMethodAnnotationsEncodingFields); result = true; } } for (Map.Entry entry : fields.entrySet()) { - if (entry.getValue().setAnnotationsEncoding(AnnotationsProcessor.encodeAnnotations(metaAccess, entry.getKey().getAnnotations(), entry.getKey().getDeclaredAnnotations(), - entry.getValue().getAnnotationsEncoding()))) { + SubstrateField field = entry.getValue(); + AnalysisField sField = entry.getKey(); + Object annotationsEncoding = AnnotationsProcessor.encodeAnnotations(metaAccess, sField.getAnnotations(), sField.getDeclaredAnnotations(), field.getAnnotationsEncoding()); + if (field.setAnnotationsEncoding(annotationsEncoding)) { + aUniverse.getHeapScanner().rescanField(field, substrateMethodAnnotationsEncodingFields); result = true; } } diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/meta/SubstrateField.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/meta/SubstrateField.java index b70bdc42ed1a..c86e6cc00a1e 100644 --- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/meta/SubstrateField.java +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/meta/SubstrateField.java @@ -28,7 +28,6 @@ import java.lang.annotation.Annotation; -import com.oracle.svm.core.util.VMError; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; @@ -38,6 +37,7 @@ import com.oracle.svm.core.meta.DirectSubstrateObjectConstant; import com.oracle.svm.core.meta.SharedField; import com.oracle.svm.core.util.HostedStringDeduplication; +import com.oracle.svm.core.util.VMError; import com.oracle.truffle.api.nodes.Node.Child; import com.oracle.truffle.api.nodes.Node.Children; import com.oracle.truffle.api.nodes.NodeCloneable; @@ -117,6 +117,11 @@ public boolean isAccessed() { return isAccessed; } + @Override + public boolean isReachable() { + return isAccessed || isWritten; + } + @Override public boolean isWritten() { return isWritten; diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/meta/SubstrateRuntimeConfigurationBuilder.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/meta/SubstrateRuntimeConfigurationBuilder.java index e7e8bbffbc9f..f7dea69bfefc 100644 --- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/meta/SubstrateRuntimeConfigurationBuilder.java +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/meta/SubstrateRuntimeConfigurationBuilder.java @@ -75,7 +75,7 @@ protected ConstantReflectionProvider createConstantReflectionProvider(Providers @Override protected ConstantFieldProvider createConstantFieldProvider(Providers p) { - return new AnalysisConstantFieldProvider(aUniverse, (AnalysisMetaAccess) p.getMetaAccess(), (AnalysisConstantReflectionProvider) p.getConstantReflection(), classInitializationSupport); + return new AnalysisConstantFieldProvider(aUniverse, (AnalysisMetaAccess) p.getMetaAccess(), classInitializationSupport); } @Override diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/FeatureImpl.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/FeatureImpl.java index e2f71e41d8ed..252337a29891 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/FeatureImpl.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/FeatureImpl.java @@ -81,6 +81,7 @@ import com.oracle.svm.hosted.meta.HostedType; import com.oracle.svm.hosted.meta.HostedUniverse; import com.oracle.svm.hosted.option.HostedOptionProvider; +import com.oracle.svm.util.ReflectionUtil; import com.oracle.svm.util.UnsafePartitionKind; import jdk.vm.ci.meta.JavaConstant; @@ -245,6 +246,26 @@ public Set reachableMethodOverrides(Executable baseMethod) { Set reachableMethodOverrides(AnalysisMethod baseMethod) { return AnalysisUniverse.getMethodImplementations(getBigBang(), baseMethod, true); } + + public void rescanObject(Object obj) { + getUniverse().getHeapScanner().rescanObject(obj); + } + + public void rescanField(Object receiver, Field field) { + getUniverse().getHeapScanner().rescanField(receiver, field); + } + + public Object rescanRoot(Field field) { + return getUniverse().getHeapScanner().rescanRoot(field); + } + + public Field findField(String declaringClassName, String fieldName) { + return findField(imageClassLoader.findClassOrFail(declaringClassName), fieldName); + } + + public Field findField(Class declaringClass, String fieldName) { + return ReflectionUtil.lookupField(declaringClass, fieldName); + } } public static class DuringSetupAccessImpl extends AnalysisAccessBase implements Feature.DuringSetupAccess { @@ -348,7 +369,7 @@ public boolean registerAsUnsafeAccessed(AnalysisField aField) { if (!aField.isUnsafeAccessed()) { /* Register the field as unsafe accessed. */ aField.registerAsAccessed(); - aField.registerAsUnsafeAccessed(bb.getUniverse()); + aField.registerAsUnsafeAccessed(); /* Force the update of registered unsafe loads and stores. */ bb.forceUnsafeUpdate(aField); return true; @@ -374,7 +395,7 @@ public void registerAsUnsafeAccessed(AnalysisField aField, UnsafePartitionKind p if (!aField.isUnsafeAccessed()) { /* Register the field as unsafe accessed. */ aField.registerAsAccessed(); - aField.registerAsUnsafeAccessed(bb.getUniverse(), partitionKind); + aField.registerAsUnsafeAccessed(partitionKind); /* Force the update of registered unsafe loads and stores. */ bb.forceUnsafeUpdate(aField); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/HostedConfiguration.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/HostedConfiguration.java index 4e7930ba7365..9ecdd04255b7 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/HostedConfiguration.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/HostedConfiguration.java @@ -133,8 +133,8 @@ public static ObjectLayout createObjectLayout(JavaKind referenceKind) { } public SVMHost createHostVM(OptionValues options, ClassLoader classLoader, ClassInitializationSupport classInitializationSupport, - UnsafeAutomaticSubstitutionProcessor automaticSubstitutions, Platform platform) { - return new SVMHost(options, classLoader, classInitializationSupport, automaticSubstitutions, platform); + UnsafeAutomaticSubstitutionProcessor automaticSubstitutions, Platform platform, SnippetReflectionProvider originalSnippetReflection) { + return new SVMHost(options, classLoader, classInitializationSupport, automaticSubstitutions, platform, originalSnippetReflection); } public CompileQueue createCompileQueue(DebugContext debug, FeatureHandler featureHandler, HostedUniverse hostedUniverse, diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ImageClassLoader.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ImageClassLoader.java index 7ab7aa98bd6c..feb6c911b8b8 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ImageClassLoader.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ImageClassLoader.java @@ -281,8 +281,12 @@ public TypeResult> findClass(String name) { } } - Class forName(String name) throws ClassNotFoundException { - return Class.forName(name, false, classLoaderSupport.getClassLoader()); + public Class forName(String className) throws ClassNotFoundException { + return forName(className, false); + } + + public Class forName(String className, boolean initialize) throws ClassNotFoundException { + return Class.forName(className, initialize, classLoaderSupport.getClassLoader()); } public Class forName(String className, Object module) throws ClassNotFoundException { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/LoggingFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/LoggingFeature.java index 6c11c1cff0f7..cb47747277c0 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/LoggingFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/LoggingFeature.java @@ -24,6 +24,7 @@ */ package com.oracle.svm.hosted; +import java.lang.reflect.Field; import java.util.logging.LogManager; import org.graalvm.compiler.options.Option; @@ -35,6 +36,7 @@ import com.oracle.svm.core.option.HostedOptionKey; import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.FeatureImpl.DuringAnalysisAccessImpl; +import com.oracle.svm.hosted.FeatureImpl.DuringSetupAccessImpl; @AutomaticFeature public class LoggingFeature implements Feature { @@ -51,6 +53,8 @@ public static class Options { private boolean reflectionConfigured = false; + private Field loggersField; + @Override public boolean isInConfiguration(IsInConfigurationAccess access) { return LoggingFeature.Options.EnableLoggingFeature.getValue(); @@ -60,12 +64,15 @@ public boolean isInConfiguration(IsInConfigurationAccess access) { public void duringSetup(DuringSetupAccess access) { /* Ensure that the log manager is initialized and the initial configuration is read. */ LogManager.getLogManager(); + loggersField = ((DuringSetupAccessImpl) access).findField("sun.util.logging.PlatformLogger", "loggers"); } @Override public void duringAnalysis(DuringAnalysisAccess a) { DuringAnalysisAccessImpl access = (DuringAnalysisAccessImpl) a; + access.rescanRoot(loggersField); + if (!reflectionConfigured && access.getMetaAccess().optionalLookupJavaType(java.util.logging.Logger.class).isPresent()) { registerForReflection(java.util.logging.ConsoleHandler.class); registerForReflection(java.util.logging.SimpleFormatter.class); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java index 2fa307cb3bbd..afd414bd4372 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/NativeImageGenerator.java @@ -56,9 +56,6 @@ import java.util.function.BooleanSupplier; import java.util.stream.Collectors; -import com.oracle.graal.pointsto.PointsToAnalysis; -import com.oracle.graal.pointsto.meta.PointsToAnalysisFactory; -import com.oracle.graal.pointsto.meta.PointsToAnalysisMethod; import org.graalvm.collections.EconomicSet; import org.graalvm.collections.Pair; import org.graalvm.compiler.api.replacements.Fold; @@ -131,12 +128,17 @@ import org.graalvm.nativeimage.impl.clinit.ClassInitializationTracking; import org.graalvm.word.PointerBase; +import com.oracle.graal.pointsto.AnalysisObjectScanningObserver; import com.oracle.graal.pointsto.AnalysisPolicy; import com.oracle.graal.pointsto.BigBang; import com.oracle.graal.pointsto.BytecodeSensitiveAnalysisPolicy; import com.oracle.graal.pointsto.DefaultAnalysisPolicy; +import com.oracle.graal.pointsto.PointsToAnalysis; import com.oracle.graal.pointsto.api.PointstoOptions; import com.oracle.graal.pointsto.constraints.UnsupportedFeatureException; +import com.oracle.graal.pointsto.heap.HeapSnapshotVerifier; +import com.oracle.graal.pointsto.heap.ImageHeap; +import com.oracle.graal.pointsto.heap.ImageHeapScanner; import com.oracle.graal.pointsto.infrastructure.SubstitutionProcessor; import com.oracle.graal.pointsto.infrastructure.WrappedJavaMethod; import com.oracle.graal.pointsto.meta.AnalysisField; @@ -146,6 +148,8 @@ import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.graal.pointsto.meta.AnalysisUniverse; import com.oracle.graal.pointsto.meta.HostedProviders; +import com.oracle.graal.pointsto.meta.PointsToAnalysisFactory; +import com.oracle.graal.pointsto.meta.PointsToAnalysisMethod; import com.oracle.graal.pointsto.reports.AnalysisReporter; import com.oracle.graal.pointsto.typestate.TypeState; import com.oracle.graal.pointsto.util.AnalysisError; @@ -226,6 +230,8 @@ import com.oracle.svm.hosted.analysis.NativeImagePointsToAnalysis; import com.oracle.svm.hosted.analysis.SVMAnalysisMetaAccess; import com.oracle.svm.hosted.analysis.SubstrateUnsupportedFeatures; +import com.oracle.svm.hosted.analysis.heap.SVMImageHeapScanner; +import com.oracle.svm.hosted.analysis.heap.SVMImageHeapVerifier; import com.oracle.svm.hosted.annotation.AnnotationSupport; import com.oracle.svm.hosted.c.CAnnotationProcessorCache; import com.oracle.svm.hosted.c.CConstantValueSupportImpl; @@ -547,6 +553,7 @@ private void doRun(Map entryPoints, hUniverse = new HostedUniverse(bb); hMetaAccess = new HostedMetaAccess(hUniverse, bb.getMetaAccess()); + ((SVMImageHeapScanner) aUniverse.getHeapScanner()).setHostedMetaAccess(hMetaAccess); BeforeUniverseBuildingAccessImpl beforeUniverseBuildingConfig = new BeforeUniverseBuildingAccessImpl(featureHandler, loader, debug, hMetaAccess); featureHandler.forEachFeature(feature -> feature.beforeUniverseBuilding(beforeUniverseBuildingConfig)); @@ -700,8 +707,10 @@ private boolean runPointsToAnalysis(String imageName, OptionValues options, Debu DuringAnalysisAccessImpl config = new DuringAnalysisAccessImpl(featureHandler, loader, bb, nativeLibraries, debug); try { bb.runAnalysis(debug, (universe) -> { - bb.getHostVM().notifyClassReachabilityListener(universe, config); - featureHandler.forEachFeature(feature -> feature.duringAnalysis(config)); + try (StopTimer t2 = bb.getProcessFeaturesTimer().start()) { + bb.getHostVM().notifyClassReachabilityListener(universe, config); + featureHandler.forEachFeature(feature -> feature.duringAnalysis(config)); + } return !config.getAndResetRequireAnalysisIteration(); }); } catch (AnalysisError e) { @@ -829,6 +838,15 @@ private void setupNativeImage(String imageName, Timer classlistTimer, OptionValu bb = createBigBang(options, target, aUniverse, analysisExecutor, watchdog::recordActivity, aMetaAccess, aConstantReflection, aWordTypes, aSnippetReflection, annotationSubstitutions, aForeignCalls, classInitializationSupport, originalProviders); aUniverse.setBigBang(bb); + + /* Create the HeapScanner and install it into the universe. */ + ImageHeap imageHeap = new ImageHeap(); + AnalysisObjectScanningObserver aScanningObserver = new AnalysisObjectScanningObserver(bb); + ImageHeapScanner heapScanner = new SVMImageHeapScanner(imageHeap, loader, aMetaAccess, aSnippetReflection, aConstantReflection, aScanningObserver); + aUniverse.setHeapScanner(heapScanner); + HeapSnapshotVerifier heapVerifier = new SVMImageHeapVerifier(bb, imageHeap, heapScanner); + aUniverse.setHeapVerifier(heapVerifier); + /* Register already created types as assignable. */ aUniverse.getTypes().forEach(t -> t.registerAsAssignable(bb)); @@ -874,7 +892,7 @@ public static AnalysisUniverse createAnalysisUniverse(OptionValues options, Targ SubstitutionProcessor aSubstitutions = createAnalysisSubstitutionProcessor(originalMetaAccess, originalSnippetReflection, cEnumProcessor, automaticSubstitutions, annotationSubstitutions, additionalSubstitutions); - SVMHost hostVM = HostedConfiguration.instance().createHostVM(options, loader.getClassLoader(), classInitializationSupport, automaticSubstitutions, loader.platform); + SVMHost hostVM = HostedConfiguration.instance().createHostVM(options, loader.getClassLoader(), classInitializationSupport, automaticSubstitutions, loader.platform, originalSnippetReflection); automaticSubstitutions.init(loader, originalMetaAccess); AnalysisPolicy analysisPolicy = PointstoOptions.AllocationSiteSensitiveHeap.getValue(options) ? new BytecodeSensitiveAnalysisPolicy(options) @@ -992,7 +1010,7 @@ public static Inflation createBigBang(OptionValues options, TargetDescription ta ClassInitializationSupport classInitializationSupport, Providers originalProviders) { assert aUniverse != null : "Analysis universe must be initialized."; aMetaAccess.lookupJavaType(String.class).registerAsReachable(); - AnalysisConstantFieldProvider aConstantFieldProvider = new AnalysisConstantFieldProvider(aUniverse, aMetaAccess, aConstantReflection, classInitializationSupport); + AnalysisConstantFieldProvider aConstantFieldProvider = new AnalysisConstantFieldProvider(aUniverse, aMetaAccess, classInitializationSupport); /* * Install all snippets so that the types, methods, and fields used in the snippets get * added to the universe. @@ -1449,7 +1467,7 @@ private void checkUniverse() { if (SubstrateOptions.VerifyNamingConventions.getValue()) { for (AnalysisMethod method : aUniverse.getMethods()) { - if ((method.isInvoked() || method.isReachable()) && method.getAnnotation(Fold.class) == null) { + if ((method.isInvoked() || method.isImplementationInvoked()) && method.getAnnotation(Fold.class) == null) { checkName(method.format("%H.%n(%p)"), method); } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ResourcesFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ResourcesFeature.java index 7d1252f684d7..8e1b768abcd7 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ResourcesFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ResourcesFeature.java @@ -268,6 +268,7 @@ public void duringAnalysis(DuringAnalysisAccess access) { ImageSingletons.lookup(ClassLoaderSupport.class).collectResources(collector); resourcePatternWorkSet.clear(); + // access.rescanObject(ImageSingletons.lookup(Resources.ResourcesSupport.class).hostedResources); } private ResourcePattern[] compilePatterns(Set patterns) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java index 447ff9be7918..53a8e5cae1e5 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SVMHost.java @@ -45,7 +45,7 @@ import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.BiConsumer; -import com.oracle.svm.core.jdk.SealedClassSupport; +import org.graalvm.compiler.api.replacements.SnippetReflectionProvider; import org.graalvm.compiler.core.common.spi.ForeignCallDescriptor; import org.graalvm.compiler.core.common.spi.ForeignCallsProvider; import org.graalvm.compiler.debug.MethodFilter; @@ -103,6 +103,7 @@ import com.oracle.svm.core.hub.ReferenceType; import com.oracle.svm.core.jdk.ClassLoaderSupport; import com.oracle.svm.core.jdk.RecordSupport; +import com.oracle.svm.core.jdk.SealedClassSupport; import com.oracle.svm.core.option.HostedOptionKey; import com.oracle.svm.core.option.SubstrateOptionsParser; import com.oracle.svm.core.util.HostedStringDeduplication; @@ -133,6 +134,7 @@ public class SVMHost implements HostVM { private final HostedStringDeduplication stringTable; private final UnsafeAutomaticSubstitutionProcessor automaticSubstitutions; private final List>> classReachabilityListeners; + private final SnippetReflectionProvider originalSnippetReflection; /** * Optionally keep the Graal graphs alive during analysis. This increases the memory footprint @@ -152,10 +154,11 @@ public class SVMHost implements HostVM { private static final Method getNestHostMethod = JavaVersionUtil.JAVA_SPEC >= 11 ? ReflectionUtil.lookupMethod(Class.class, "getNestHost") : null; public SVMHost(OptionValues options, ClassLoader classLoader, ClassInitializationSupport classInitializationSupport, - UnsafeAutomaticSubstitutionProcessor automaticSubstitutions, Platform platform) { + UnsafeAutomaticSubstitutionProcessor automaticSubstitutions, Platform platform, SnippetReflectionProvider originalSnippetReflection) { this.options = options; this.classLoader = classLoader; this.classInitializationSupport = classInitializationSupport; + this.originalSnippetReflection = originalSnippetReflection; this.stringTable = HostedStringDeduplication.singleton(); this.classReachabilityListeners = new ArrayList<>(); this.forbiddenTypes = setupForbiddenTypes(options); @@ -531,7 +534,7 @@ private static ReferenceType computeReferenceType(AnalysisType type) { @Override public void checkType(ResolvedJavaType type, AnalysisUniverse universe) { - Class originalClass = OriginalClassProvider.getJavaClass(universe.getOriginalSnippetReflection(), type); + Class originalClass = OriginalClassProvider.getJavaClass(originalSnippetReflection, type); ClassLoader originalClassLoader = originalClass.getClassLoader(); if (NativeImageSystemClassLoader.singleton().isDisallowedClassLoader(originalClassLoader)) { String message = "Class " + originalClass.getName() + " was loaded by " + originalClassLoader + " and not by the current image class loader " + classLoader + ". "; @@ -732,7 +735,7 @@ public static class Options { @Override public boolean skipInterface(AnalysisUniverse universe, ResolvedJavaType interfaceType, ResolvedJavaType implementingType) { - if (!platformSupported(universe, interfaceType)) { + if (!platformSupported(interfaceType)) { String message = "The interface " + interfaceType.toJavaName(true) + " is not available in the current platform, but used by " + implementingType.toJavaName(true) + ". " + "GraalVM before version 21.2 ignored such interfaces, but this was an oversight."; @@ -750,16 +753,16 @@ public boolean skipInterface(AnalysisUniverse universe, ResolvedJavaType interfa } @Override - public boolean platformSupported(AnalysisUniverse universe, AnnotatedElement element) { + public boolean platformSupported(AnnotatedElement element) { if (element instanceof ResolvedJavaType) { - Package p = OriginalClassProvider.getJavaClass(universe.getOriginalSnippetReflection(), (ResolvedJavaType) element).getPackage(); - if (p != null && !platformSupported(universe, p)) { + Package p = OriginalClassProvider.getJavaClass(originalSnippetReflection, (ResolvedJavaType) element).getPackage(); + if (p != null && !platformSupported(p)) { return false; } } if (element instanceof Class) { Package p = ((Class) element).getPackage(); - if (p != null && !platformSupported(universe, p)) { + if (p != null && !platformSupported(p)) { return false; } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java index 7b3a05ae5474..b3035d4a533d 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SecurityServicesFeature.java @@ -191,8 +191,19 @@ public static class Options { /** List of providers deemed not to be used by this feature. */ private List removedProviders; + // TODO Filtering temporarily disabled see + // com.oracle.svm.core.jdk.Target_sun_security_jca_Providers.providerList for details. + // private boolean shouldFilterProviders = true; private boolean shouldFilterProviders = true; + private Field verificationResultsField; + private Field providerListField; + private Field oidTableField; + private Field oidMapField; + private Field classCacheField; + private Field constructorCacheField; + private Field classRefField; + @Override public void afterRegistration(AfterRegistrationAccess a) { ModuleSupport.exportAndOpenPackageToClass("java.base", "sun.security.x509", false, getClass()); @@ -222,6 +233,19 @@ public void duringSetup(DuringSetupAccess a) { DuringSetupAccessImpl access = (DuringSetupAccessImpl) a; addManuallyConfiguredUsedProviders(a); + verificationResultsField = access.findField("javax.crypto.JceSecurity", "verificationResults"); + providerListField = access.findField("sun.security.jca.Providers", "providerList"); + if (JavaVersionUtil.JAVA_SPEC >= 17) { + oidTableField = access.findField("sun.security.util.ObjectIdentifier", "oidTable"); + } + oidMapField = access.findField(OIDMap.class, "oidMap"); + if (JavaVersionUtil.JAVA_SPEC >= 17) { + classCacheField = access.findField(Service.class, "classCache"); + constructorCacheField = access.findField(Service.class, "constructorCache"); + } else { + classRefField = access.findField(Service.class, "classRef"); + } + RuntimeClassInitializationSupport rci = ImageSingletons.lookup(RuntimeClassInitializationSupport.class); /* * The SecureRandom implementations open the /dev/random and /dev/urandom files which are @@ -296,6 +320,40 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { loader = ((BeforeAnalysisAccessImpl) access).getImageClassLoader(); verificationCacheCleaner = constructVerificationCacheCleaner(); + try { + /* Ensure sun.security.provider.certpath.CertPathHelper.instance is initialized. */ + loader.forName("java.security.cert.TrustAnchor", true); + /* + * Ensure jdk.internal.access.SharedSecrets.javaxCryptoSpecAccess is initialized before + * scanning. + */ + loader.forName("javax.crypto.spec.SecretKeySpec", true); + /* + * Ensure jdk.internal.access.SharedSecrets.javaxCryptoSealedObjectAccess is initialized + * before scanning. + */ + loader.forName("javax.crypto.SealedObject", true); + + /* + * Ensure jdk.internal.access.SharedSecrets.javaIOAccess is initialized before scanning. + */ + loader.forName("java.io.Console", true); + + /* + * Ensure jdk.internal.access.SharedSecrets.javaSecuritySignatureAccess is initialized + * before scanning. + */ + loader.forName("java.security.Signature", true); + + /* + * Ensure all X509Certificate caches are initialized. + */ + loader.forName("sun.security.util.AnchorCertificates", true); + + } catch (ClassNotFoundException e) { + VMError.shouldNotReachHere(e); + } + if (Options.EnableSecurityServicesFeature.getValue()) { registerServiceReachabilityHandlers(access); } @@ -546,7 +604,7 @@ private void doRegisterServices(DuringAnalysisAccess access, Object trigger, Str try (TracingAutoCloseable ignored = trace(access, trigger, serviceType)) { Set services = availableServices.get(serviceType); for (Service service : services) { - registerService(service); + registerService(access, service); } } } @@ -644,7 +702,8 @@ private void registerProvider(Provider provider) { if (!usedProviders.contains(provider)) { usedProviders.add(provider); registerForReflection(provider.getClass()); - + /* Trigger initialization of lazy field java.security.Provider.entrySet. */ + provider.entrySet(); try { Method getVerificationResult = ReflectionUtil.lookupMethod(loader.findClassOrFail("javax.crypto.JceSecurity"), "getVerificationResult", Provider.class); /* @@ -661,7 +720,7 @@ private void registerProvider(Provider provider) { } @SuppressWarnings("try") - private void registerService(Service service) { + private void registerService(DuringAnalysisAccess a, Service service) { TypeResult> serviceClassResult = loader.findClass(service.getClassName()); if (serviceClassResult.isPresent()) { try (TracingAutoCloseable ignored = trace(service)) { @@ -682,7 +741,7 @@ private void registerService(Service service) { registerJks(loader); } if (isCertificateFactory(service) && service.getAlgorithm().equals(X509)) { - registerX509Extensions(); + registerX509Extensions(a); } registerProvider(service.getProvider()); } @@ -706,7 +765,8 @@ private static void registerJks(ImageClassLoader loader) { /** * Register the x509 certificate extension classes for reflection. */ - private static void registerX509Extensions() { + private void registerX509Extensions(DuringAnalysisAccess a) { + DuringAnalysisAccessImpl access = (DuringAnalysisAccessImpl) a; /* * The OIDInfo class which represents the values in the map is not visible. Get the list of * extension names through reflection, i.e., the keys in the map, and use the @@ -723,6 +783,29 @@ private static void registerX509Extensions() { throw VMError.shouldNotReachHere(e); } } + access.rescanRoot(oidMapField); + } + + @Override + public void duringAnalysis(DuringAnalysisAccess a) { + DuringAnalysisAccessImpl access = (DuringAnalysisAccessImpl) a; + access.rescanRoot(verificationResultsField); + access.rescanRoot(providerListField); + if (JavaVersionUtil.JAVA_SPEC >= 17) { + access.rescanRoot(oidTableField); + } + if (filteredProviderList != null) { + for (Provider provider : filteredProviderList.providers()) { + for (Service service : provider.getServices()) { + if (JavaVersionUtil.JAVA_SPEC >= 17) { + access.rescanField(service, classCacheField); + access.rescanField(service, constructorCacheField); + } else { + access.rescanField(service, classRefField); + } + } + } + } } @Override diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java index 3f504ac8b767..b68e0d5c758b 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ServiceLoaderFeature.java @@ -363,6 +363,7 @@ private boolean handleType(AnalysisType type, DuringAnalysisAccessImpl access) { debugContext.log("ServiceLoaderFeature: registerResource: " + serviceResourceLocation); } Resources.registerResource(null, serviceResourceLocation, new ByteArrayInputStream(newResourceValue.toString().getBytes(StandardCharsets.UTF_8))); + // access.rescanObject(ImageSingletons.lookup(Resources.ResourcesSupport.class).hostedResources); /* Ensure that the static analysis runs again for the new implementation classes. */ access.requireAnalysisIteration(); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/AnalysisConstantFieldProvider.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/AnalysisConstantFieldProvider.java index 0688a83a2668..b305295dda9d 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/AnalysisConstantFieldProvider.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/AnalysisConstantFieldProvider.java @@ -42,14 +42,11 @@ public class AnalysisConstantFieldProvider extends SharedConstantFieldProvider { private final AnalysisUniverse universe; private final AnalysisMetaAccess metaAccess; - private final AnalysisConstantReflectionProvider constantReflection; - public AnalysisConstantFieldProvider(AnalysisUniverse universe, AnalysisMetaAccess metaAccess, AnalysisConstantReflectionProvider constantReflection, - ClassInitializationSupport classInitializationSupport) { + public AnalysisConstantFieldProvider(AnalysisUniverse universe, AnalysisMetaAccess metaAccess, ClassInitializationSupport classInitializationSupport) { super(metaAccess, classInitializationSupport); this.universe = universe; this.metaAccess = metaAccess; - this.constantReflection = constantReflection; } @Override @@ -63,7 +60,7 @@ public T readConstantField(ResolvedJavaField field, ConstantFieldTool ana if (readableField.allowConstantFolding()) { JavaConstant fieldValue = readableField.readValue(metaAccess, universe.toHosted(analysisTool.getReceiver())); if (fieldValue != null) { - return analysisTool.foldConstant(constantReflection.interceptValue(f, universe.lookup(fieldValue))); + return analysisTool.foldConstant(AnalysisConstantReflectionProvider.interceptValue(universe, f, universe.lookup(fieldValue))); } } return null; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/AnalysisConstantReflectionProvider.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/AnalysisConstantReflectionProvider.java index b67be844f287..94392e153140 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/AnalysisConstantReflectionProvider.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/AnalysisConstantReflectionProvider.java @@ -37,6 +37,7 @@ import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.graal.pointsto.meta.AnalysisUniverse; import com.oracle.graal.pointsto.util.AnalysisError; +import com.oracle.svm.core.BuildPhaseProvider; import com.oracle.svm.core.RuntimeAssertionsSupport; import com.oracle.svm.core.annotate.InjectAccessors; import com.oracle.svm.core.graal.meta.SharedConstantReflectionProvider; @@ -99,7 +100,13 @@ public JavaConstant readValue(MetaAccessProvider suppliedMetaAccess, AnalysisFie value = universe.lookup(ReadableJavaField.readFieldValue(suppliedMetaAccess, originalConstantReflection, field.wrapped, universe.toHosted(receiver))); } - return interceptValue(field, value); + JavaConstant result = interceptValue(universe, field, value); + + if (!BuildPhaseProvider.isAnalysisFinished()) { + field.markReachable(); + } + + return result; } /* @@ -126,7 +133,7 @@ public JavaConstant readValue(MetaAccessProvider suppliedMetaAccess, AnalysisFie * pretty likely (although not guaranteed) that we are not returning an unintended value for a * class that is re-initialized at run time. */ - private static JavaConstant readUninitializedStaticValue(AnalysisField field) { + public static JavaConstant readUninitializedStaticValue(AnalysisField field) { JavaKind kind = field.getJavaKind(); boolean canHaveConstantValueAttribute = kind.isPrimitive() || field.getType().getName().equals("Ljava/lang/String;"); @@ -175,19 +182,20 @@ private static JavaConstant readUninitializedStaticValue(AnalysisField field) { case Object: Object value = GraalUnsafeAccess.getUnsafe().getObject(base, offset); assert value == null || value instanceof String : "String is currently the only specified object type for the ConstantValue class file attribute"; + // return ImageHeapScanner.asConstant(value); return SubstrateObjectConstant.forObject(value); default: - throw VMError.shouldNotReachHere(); + throw AnalysisError.shouldNotReachHere(); } } - public JavaConstant interceptValue(AnalysisField field, JavaConstant value) { + public static JavaConstant interceptValue(AnalysisUniverse universe, AnalysisField field, JavaConstant value) { JavaConstant result = value; if (result != null) { result = filterInjectedAccessor(field, result); - result = replaceObject(result); + result = replaceObject(universe, result); result = interceptAssertionStatus(field, result); - result = interceptWordType(field, result); + result = interceptWordType(universe, field, result); } return result; } @@ -208,7 +216,7 @@ private static JavaConstant filterInjectedAccessor(AnalysisField field, JavaCons /** * Run all registered object replacers. */ - private JavaConstant replaceObject(JavaConstant value) { + private static JavaConstant replaceObject(AnalysisUniverse universe, JavaConstant value) { if (value == JavaConstant.NULL_POINTER) { return JavaConstant.NULL_POINTER; } @@ -242,7 +250,7 @@ private static JavaConstant interceptAssertionStatus(AnalysisField field, JavaCo * Intercept {@link Word} types. They are boxed objects in the hosted world, but primitive * values in the runtime world. */ - private JavaConstant interceptWordType(AnalysisField field, JavaConstant value) { + private static JavaConstant interceptWordType(AnalysisUniverse universe, AnalysisField field, JavaConstant value) { if (value.getJavaKind() == JavaKind.Object) { Object originalObject = universe.getSnippetReflection().asObject(Object.class, value); if (universe.hostVM().isRelocatedPointer(originalObject)) { @@ -269,7 +277,8 @@ public ResolvedJavaType asJavaType(Constant constant) { if (obj instanceof DynamicHub) { return getHostVM().lookupType((DynamicHub) obj); } else if (obj instanceof Class) { - throw VMError.shouldNotReachHere("Must not have java.lang.Class object: " + obj); + // TODO do we need to make sure the hub is scanned? + return metaAccess.lookupJavaType((Class) obj); } } return null; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/DynamicHubInitializer.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/DynamicHubInitializer.java index 760d97244dc6..52db9e14b917 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/DynamicHubInitializer.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/DynamicHubInitializer.java @@ -26,6 +26,7 @@ import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedType; +import java.lang.reflect.Field; import java.lang.reflect.MalformedParameterizedTypeException; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; @@ -36,15 +37,16 @@ import com.oracle.graal.pointsto.constraints.UnsupportedFeatureException; import com.oracle.graal.pointsto.constraints.UnsupportedFeatures; +import com.oracle.graal.pointsto.heap.ImageHeapScanner; import com.oracle.graal.pointsto.meta.AnalysisField; import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; import com.oracle.graal.pointsto.meta.AnalysisType; -import com.oracle.graal.pointsto.meta.AnalysisUniverse; import com.oracle.svm.core.hub.AnnotatedSuperInfo; import com.oracle.svm.core.hub.DynamicHub; import com.oracle.svm.core.hub.GenericInfo; import com.oracle.svm.core.meta.SubstrateObjectConstant; import com.oracle.svm.hosted.SVMHost; +import com.oracle.svm.util.ReflectionUtil; import jdk.vm.ci.meta.ConstantReflectionProvider; import jdk.vm.ci.meta.JavaKind; @@ -53,7 +55,6 @@ public class DynamicHubInitializer { private final SVMHost hostVM; - private final AnalysisUniverse universe; private final AnalysisMetaAccess metaAccess; private final UnsupportedFeatures unsupportedFeatures; private final ConstantReflectionProvider constantReflection; @@ -62,38 +63,54 @@ public class DynamicHubInitializer { private final Map annotatedInterfacesMap; private final Map interfacesEncodings; - public DynamicHubInitializer(AnalysisUniverse universe, AnalysisMetaAccess metaAccess, - UnsupportedFeatures unsupportedFeatures, ConstantReflectionProvider constantReflection) { - this.hostVM = (SVMHost) universe.hostVM(); - this.universe = universe; + private final Field dynamicHubArrayHubField; + private final Field dynamicHubEnclosingClassField; + private final Field dynamicHubInterfacesEncodingField; + private final Field dynamicHubAnnotationsEncodingField; + private final Field dynamicHubAnnotationsEnumConstantsReferenceField; + private final Field dynamicHubAnnotatedSuperInfoField; + private final Field dynamicHubGenericInfoField; + + public DynamicHubInitializer(AnalysisMetaAccess metaAccess, UnsupportedFeatures unsupportedFeatures, ConstantReflectionProvider constantReflection) { this.metaAccess = metaAccess; + this.hostVM = (SVMHost) metaAccess.getUniverse().hostVM(); this.unsupportedFeatures = unsupportedFeatures; this.constantReflection = constantReflection; this.genericInterfacesMap = new ConcurrentHashMap<>(); this.annotatedInterfacesMap = new ConcurrentHashMap<>(); this.interfacesEncodings = new ConcurrentHashMap<>(); + + dynamicHubArrayHubField = ReflectionUtil.lookupField(DynamicHub.class, "arrayHub"); + dynamicHubEnclosingClassField = ReflectionUtil.lookupField(DynamicHub.class, "enclosingClass"); + dynamicHubInterfacesEncodingField = ReflectionUtil.lookupField(DynamicHub.class, "interfacesEncoding"); + dynamicHubAnnotationsEncodingField = ReflectionUtil.lookupField(DynamicHub.class, "annotationsEncoding"); + dynamicHubAnnotationsEnumConstantsReferenceField = ReflectionUtil.lookupField(DynamicHub.class, "enumConstantsReference"); + dynamicHubAnnotatedSuperInfoField = ReflectionUtil.lookupField(DynamicHub.class, "annotatedSuperInfo"); + dynamicHubGenericInfoField = ReflectionUtil.lookupField(DynamicHub.class, "genericInfo"); } - public void initializeMetaData(AnalysisType type) { - assert type.isReachable(); + public void initializeMetaData(ImageHeapScanner heapScanner, AnalysisType type) { + assert type.isReachable() : "Type " + type.toJavaName(true) + " is not marked as reachable."; DynamicHub hub = hostVM.dynamicHub(type); if (hub.getGenericInfo() == null) { - fillGenericInfo(type, hub); + fillGenericInfo(heapScanner, type, hub); } if (hub.getAnnotatedSuperInfo() == null) { - fillAnnotatedSuperInfo(type, hub); + fillAnnotatedSuperInfo(heapScanner, type, hub); } if (type.getJavaKind() == JavaKind.Object) { if (type.isArray()) { hub.getComponentHub().setArrayHub(hub); + heapScanner.rescanField(hub.getComponentHub(), dynamicHubArrayHubField); } try { AnalysisType enclosingType = type.getEnclosingType(); if (enclosingType != null) { hub.setEnclosingClass(hostVM.dynamicHub(enclosingType)); + heapScanner.rescanField(hub, dynamicHubEnclosingClassField); } } catch (UnsupportedFeatureException ex) { unsupportedFeatures.addMessage(type.toJavaName(true), null, ex.getMessage(), null, ex); @@ -101,10 +118,14 @@ public void initializeMetaData(AnalysisType type) { if (hub.getInterfacesEncoding() == null) { fillInterfaces(type, hub); + heapScanner.rescanField(hub, dynamicHubInterfacesEncodingField); } /* * Support for Java annotations. + * + * The annotation encodings must be updated after each analysis iteration since only the + * annotation types marked as reachable are included. */ try { /* @@ -113,7 +134,10 @@ public void initializeMetaData(AnalysisType type) { */ Annotation[] annotations = type.getWrappedWithoutResolve().getAnnotations(); Annotation[] declared = type.getWrappedWithoutResolve().getDeclaredAnnotations(); - hub.setAnnotationsEncoding(AnnotationsProcessor.encodeAnnotations(metaAccess, annotations, declared, hub.getAnnotationsEncoding())); + Object annotationsEncoding = AnnotationsProcessor.encodeAnnotations(metaAccess, annotations, declared, hub.getAnnotationsEncoding()); + if (hub.setAnnotationsEncoding(annotationsEncoding)) { + heapScanner.rescanField(hub, dynamicHubAnnotationsEncodingField); + } } catch (ArrayStoreException e) { /* If we hit JDK-7183985 just encode the exception. */ hub.setAnnotationsEncoding(e); @@ -165,6 +189,7 @@ public void initializeMetaData(AnalysisType type) { } hub.initEnumConstants(enumConstants); } + heapScanner.rescanField(hub, dynamicHubAnnotationsEnumConstantsReferenceField); } } } @@ -247,7 +272,7 @@ public int hashCode() { } } - private void fillGenericInfo(AnalysisType type, DynamicHub hub) { + private void fillGenericInfo(ImageHeapScanner heapScanner, AnalysisType type, DynamicHub hub) { Class javaClass = type.getJavaClass(); TypeVariable[] typeParameters = javaClass.getTypeParameters(); @@ -290,9 +315,10 @@ private void fillGenericInfo(AnalysisType type, DynamicHub hub) { genericSuperClass = null; } hub.setGenericInfo(GenericInfo.factory(typeParameters, cachedGenericInterfaces, genericSuperClass)); + heapScanner.rescanField(hub, dynamicHubGenericInfoField); } - private void fillAnnotatedSuperInfo(AnalysisType type, DynamicHub hub) { + private void fillAnnotatedSuperInfo(ImageHeapScanner heapScanner, AnalysisType type, DynamicHub hub) { Class javaClass = type.getJavaClass(); AnnotatedType annotatedSuperclass; @@ -325,12 +351,13 @@ private void fillAnnotatedSuperInfo(AnalysisType type, DynamicHub hub) { AnnotatedType[] cachedAnnotatedInterfaces = annotatedInterfacesMap.computeIfAbsent( new AnnotatedInterfacesEncodingKey(annotatedInterfaces), k -> annotatedInterfaces); hub.setAnnotatedSuperInfo(AnnotatedSuperInfo.factory(annotatedSuperclass, cachedAnnotatedInterfaces)); + heapScanner.rescanField(hub, dynamicHubAnnotatedSuperInfoField); } private boolean isTypeAllowed(Type t) { if (t instanceof Class) { Optional resolved = metaAccess.optionalLookupJavaType((Class) t); - return resolved.isPresent() && hostVM.platformSupported(universe, resolved.get()); + return resolved.isPresent() && hostVM.platformSupported(resolved.get()); } return true; } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/NativeImagePointsToAnalysis.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/NativeImagePointsToAnalysis.java index b69ec03d5d0f..42e7b0419fd4 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/NativeImagePointsToAnalysis.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/NativeImagePointsToAnalysis.java @@ -29,25 +29,22 @@ import org.graalvm.compiler.graph.NodeSourcePosition; import org.graalvm.compiler.options.OptionValues; -import com.oracle.graal.pointsto.ObjectScanner; -import com.oracle.graal.pointsto.ObjectScanner.ScanReason; import com.oracle.graal.pointsto.PointsToAnalysis; import com.oracle.graal.pointsto.constraints.UnsupportedFeatures; import com.oracle.graal.pointsto.flow.MethodTypeFlow; import com.oracle.graal.pointsto.flow.MethodTypeFlowBuilder; +import com.oracle.graal.pointsto.meta.AnalysisField; import com.oracle.graal.pointsto.meta.AnalysisMethod; import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.graal.pointsto.meta.AnalysisUniverse; import com.oracle.graal.pointsto.meta.HostedProviders; import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.graal.meta.SubstrateReplacements; -import com.oracle.svm.core.meta.SubstrateObjectConstant; import com.oracle.svm.hosted.HostedConfiguration; import com.oracle.svm.hosted.SVMHost; import com.oracle.svm.hosted.meta.HostedType; import com.oracle.svm.hosted.substitute.AnnotationSubstitutionProcessor; -import jdk.vm.ci.meta.JavaConstant; import jdk.vm.ci.meta.ResolvedJavaType; public class NativeImagePointsToAnalysis extends PointsToAnalysis implements Inflation { @@ -59,11 +56,11 @@ public class NativeImagePointsToAnalysis extends PointsToAnalysis implements Inf public NativeImagePointsToAnalysis(OptionValues options, AnalysisUniverse universe, HostedProviders providers, AnnotationSubstitutionProcessor annotationSubstitutionProcessor, ForkJoinPool executor, Runnable heartbeatCallback, UnsupportedFeatures unsupportedFeatures) { - super(options, universe, providers, universe.hostVM(), executor, heartbeatCallback, new SubstrateUnsupportedFeatures(), SubstrateOptions.parseOnce()); + super(options, universe, providers, universe.hostVM(), executor, heartbeatCallback, unsupportedFeatures, SubstrateOptions.parseOnce()); this.annotationSubstitutionProcessor = annotationSubstitutionProcessor; - dynamicHubInitializer = new DynamicHubInitializer(universe, metaAccess, unsupportedFeatures, providers.getConstantReflection()); - unknownFieldHandler = new PointsToUnknownFieldHandler(metaAccess); + dynamicHubInitializer = new DynamicHubInitializer(metaAccess, unsupportedFeatures, providers.getConstantReflection()); + unknownFieldHandler = new PointsToUnknownFieldHandler(this, metaAccess); callChecker = new CallChecker(); } @@ -77,15 +74,6 @@ public MethodTypeFlowBuilder createMethodTypeFlowBuilder(PointsToAnalysis bb, Me return HostedConfiguration.instance().createMethodTypeFlowBuilder(bb, methodFlow); } - @Override - protected void checkObjectGraph(ObjectScanner objectScanner) { - universe.getFields().forEach(field -> unknownFieldHandler.handleUnknownValueField(this, field)); - universe.getTypes().stream().filter(AnalysisType::isReachable).forEach(dynamicHubInitializer::initializeMetaData); - - /* Scan hubs of all types that end up in the native image. */ - universe.getTypes().stream().filter(AnalysisType::isReachable).forEach(type -> scanHub(objectScanner, type)); - } - @Override public SVMHost getHostVM() { return (SVMHost) hostVM; @@ -108,10 +96,14 @@ public AnnotationSubstitutionProcessor getAnnotationSubstitutionProcessor() { return annotationSubstitutionProcessor; } - private void scanHub(ObjectScanner objectScanner, AnalysisType type) { - SVMHost svmHost = (SVMHost) hostVM; - JavaConstant hubConstant = SubstrateObjectConstant.forObject(svmHost.dynamicHub(type)); - objectScanner.scanConstant(hubConstant, ScanReason.HUB); + @Override + public void onFieldAccessed(AnalysisField field) { + unknownFieldHandler.handleUnknownValueField(field); + } + + @Override + public void onTypeScanned(AnalysisType type) { + dynamicHubInitializer.initializeMetaData(universe.getHeapScanner(), type); } public static ResolvedJavaType toWrappedType(ResolvedJavaType type) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/PointsToUnknownFieldHandler.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/PointsToUnknownFieldHandler.java index bea2f3c102f0..22fe87c5dd6b 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/PointsToUnknownFieldHandler.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/PointsToUnknownFieldHandler.java @@ -29,11 +29,12 @@ import com.oracle.graal.pointsto.meta.AnalysisField; import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; import com.oracle.graal.pointsto.meta.AnalysisType; + import jdk.vm.ci.meta.JavaKind; public class PointsToUnknownFieldHandler extends UnknownFieldHandler { - public PointsToUnknownFieldHandler(AnalysisMetaAccess metaAccess) { - super(metaAccess); + public PointsToUnknownFieldHandler(BigBang bb, AnalysisMetaAccess metaAccess) { + super(bb, metaAccess); } /** @@ -41,7 +42,7 @@ public PointsToUnknownFieldHandler(AnalysisMetaAccess metaAccess) { * code. It can have multiple declared types provided via annotation. */ @Override - protected void handleUnknownObjectField(BigBang bb, AnalysisField aField, AnalysisType... declaredTypes) { + protected void handleUnknownObjectField(AnalysisField aField, AnalysisType... declaredTypes) { NativeImagePointsToAnalysis analysis = (NativeImagePointsToAnalysis) bb; assert aField.getJavaKind() == JavaKind.Object; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/UnknownFieldHandler.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/UnknownFieldHandler.java index 8078dc808cf1..883b44736679 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/UnknownFieldHandler.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/UnknownFieldHandler.java @@ -24,14 +24,7 @@ */ package com.oracle.svm.hosted.analysis; -import com.oracle.graal.pointsto.BigBang; -import com.oracle.graal.pointsto.meta.AnalysisField; -import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; -import com.oracle.graal.pointsto.meta.AnalysisType; -import com.oracle.svm.core.annotate.UnknownObjectField; -import com.oracle.svm.core.annotate.UnknownPrimitiveField; -import jdk.vm.ci.meta.JavaKind; -import org.graalvm.word.WordBase; +import static jdk.vm.ci.common.JVMCIError.shouldNotReachHere; import java.lang.reflect.Modifier; import java.util.ArrayList; @@ -40,28 +33,36 @@ import java.util.List; import java.util.Set; -import static jdk.vm.ci.common.JVMCIError.shouldNotReachHere; +import org.graalvm.word.WordBase; + +import com.oracle.graal.pointsto.BigBang; +import com.oracle.graal.pointsto.meta.AnalysisField; +import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; +import com.oracle.graal.pointsto.meta.AnalysisType; +import com.oracle.svm.core.annotate.UnknownObjectField; +import com.oracle.svm.core.annotate.UnknownPrimitiveField; + +import jdk.vm.ci.meta.JavaKind; public abstract class UnknownFieldHandler { + protected final BigBang bb; private Set handledUnknownValueFields = new HashSet<>(); private final AnalysisMetaAccess metaAccess; - public UnknownFieldHandler(AnalysisMetaAccess metaAccess) { + public UnknownFieldHandler(BigBang bb, AnalysisMetaAccess metaAccess) { + this.bb = bb; this.metaAccess = metaAccess; } - public void handleUnknownValueField(BigBang bb, AnalysisField field) { + public void handleUnknownValueField(AnalysisField field) { if (handledUnknownValueFields.contains(field)) { return; } - if (!field.isAccessed()) { - /* - * Field is not reachable yet, so do no process it. In particular, we must not register - * types listed in the @UnknownObjectField annotation as allocated when the field is not - * yet reachable - */ - return; - } + /* + * Only process fields that are accessed. In particular, we must not register types listed + * in the @UnknownObjectField annotation as allocated when the field is not yet accessed. + */ + assert field.isAccessed(); UnknownObjectField unknownObjectField = field.getAnnotation(UnknownObjectField.class); UnknownPrimitiveField unknownPrimitiveField = field.getAnnotation(UnknownPrimitiveField.class); @@ -81,7 +82,7 @@ public void handleUnknownValueField(BigBang bb, AnalysisField field) { * Use the annotation types, instead of the declared type, in the UnknownObjectField * annotated fields initialization. */ - handleUnknownObjectField(bb, field, aAnnotationTypes.toArray(new AnalysisType[0])); + handleUnknownObjectField(field, aAnnotationTypes.toArray(new AnalysisType[0])); } else if (unknownPrimitiveField != null) { assert !Modifier.isFinal(field.getModifiers()) : "@UnknownPrimitiveField annotated field " + field.format("%H.%n") + " cannot be final"; @@ -125,7 +126,7 @@ private List extractAnnotationTypes(AnalysisField field, UnknownOb return aAnnotationTypes; } - protected abstract void handleUnknownObjectField(BigBang bb, AnalysisField aField, AnalysisType... declaredTypes); + protected abstract void handleUnknownObjectField(AnalysisField aField, AnalysisType... declaredTypes); public void cleanupAfterAnalysis() { handledUnknownValueFields = null; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/heap/SVMImageHeapScanner.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/heap/SVMImageHeapScanner.java new file mode 100644 index 000000000000..b480ce7f63fa --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/heap/SVMImageHeapScanner.java @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.hosted.analysis.heap; + +import java.lang.reflect.Field; + +import org.graalvm.collections.EconomicMap; +import org.graalvm.compiler.api.replacements.SnippetReflectionProvider; + +import com.oracle.graal.pointsto.ObjectScanner.ScanReason; +import com.oracle.graal.pointsto.ObjectScanningObserver; +import com.oracle.graal.pointsto.heap.ImageHeap; +import com.oracle.graal.pointsto.heap.ImageHeapScanner; +import com.oracle.graal.pointsto.heap.value.ValueSupplier; +import com.oracle.graal.pointsto.meta.AnalysisField; +import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; +import com.oracle.graal.pointsto.meta.AnalysisType; +import com.oracle.graal.pointsto.util.AnalysisFuture; +import com.oracle.svm.core.BuildPhaseProvider; +import com.oracle.svm.core.meta.ReadableJavaField; +import com.oracle.svm.core.meta.SubstrateObjectConstant; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.hosted.ImageClassLoader; +import com.oracle.svm.hosted.SVMHost; +import com.oracle.svm.hosted.ameta.AnalysisConstantReflectionProvider; +import com.oracle.svm.hosted.meta.HostedMetaAccess; +import com.oracle.svm.util.ReflectionUtil; + +import jdk.vm.ci.meta.ConstantReflectionProvider; +import jdk.vm.ci.meta.JavaConstant; + +public class SVMImageHeapScanner extends ImageHeapScanner { + + private final ImageClassLoader loader; + protected HostedMetaAccess hostedMetaAccess; + private final Class economicMapImpl; + private final Field economicMapImplEntriesField; + private final Field economicMapImplHashArrayField; + + public SVMImageHeapScanner(ImageHeap imageHeap, ImageClassLoader loader, AnalysisMetaAccess metaAccess, + SnippetReflectionProvider snippetReflection, ConstantReflectionProvider aConstantReflection, ObjectScanningObserver aScanningObserver) { + super(imageHeap, metaAccess, snippetReflection, aConstantReflection, aScanningObserver); + this.loader = loader; + economicMapImpl = getClass("org.graalvm.collections.EconomicMapImpl"); + economicMapImplEntriesField = ReflectionUtil.lookupField(economicMapImpl, "entries"); + economicMapImplHashArrayField = ReflectionUtil.lookupField(economicMapImpl, "hashArray"); + } + + public void setHostedMetaAccess(HostedMetaAccess hostedMetaAccess) { + this.hostedMetaAccess = hostedMetaAccess; + } + + @Override + protected Class getClass(String className) { + return loader.findClassOrFail(className); + } + + @Override + protected boolean shouldInitializeAtRunTime(AnalysisType type) { + return ((SVMHost) hostVM).getClassInitializationSupport().shouldInitializeAtRuntime(type); + } + + @Override + protected AnalysisFuture getOrCreateConstantReachableTask(JavaConstant javaConstant, ScanReason reason) { + VMError.guarantee(javaConstant instanceof SubstrateObjectConstant, "Not a substrate constant " + javaConstant); + return super.getOrCreateConstantReachableTask(javaConstant, reason); + } + + @Override + protected Object unwrapObject(JavaConstant constant) { + /* + * Unwrap the original object from the constant. Unlike HostedSnippetReflectionProvider this + * will just return the wrapped object, without any transformation. This is important during + * scanning: when scanning a java.lang.Class it will be replaced by a DynamicHub which is + * then actually scanned. The HostedSnippetReflectionProvider returns the original Class for + * a DynamicHub, which would lead to a deadlock during scanning. + */ + return SubstrateObjectConstant.asObject(Object.class, constant); + } + + @Override + public boolean isValueAvailable(AnalysisField field) { + if (field.wrapped instanceof ReadableJavaField) { + ReadableJavaField readableField = (ReadableJavaField) field.wrapped; + return readableField.isValueAvailable(); + } + return super.isValueAvailable(field); + } + + @Override + protected ValueSupplier readHostedFieldValue(AnalysisField field, JavaConstant receiver) { + if (field.isStatic() && shouldInitializeAtRunTime(field.getDeclaringClass())) { + return ValueSupplier.eagerValue(AnalysisConstantReflectionProvider.readUninitializedStaticValue(field)); + } + + if (field.wrapped instanceof ReadableJavaField) { + ReadableJavaField readableField = (ReadableJavaField) field.wrapped; + if (readableField.isValueAvailableDuringAnalysis()) { + /* Materialize and return the value. */ + JavaConstant value = universe.lookup(readableField.readValue(metaAccess, receiver)); + return ValueSupplier.eagerValue(value); + } else { + /* + * Return a lazy value. This applies to RecomputeFieldValue.Kind.FieldOffset and + * RecomputeFieldValue.Kind.Custom. The value becomes available during hosted + * universe building and is installed by calling + * ComputedValueField.processSubstrate() or by ComputedValueField.readValue(). + * Attempts to materialize the value earlier will result in an error. + */ + return ValueSupplier.lazyValue(() -> universe.lookup(readableField.readValue(hostedMetaAccess, receiver)), + readableField::isValueAvailable); + } + } + return super.readHostedFieldValue(field, receiver); + } + + @Override + protected JavaConstant transformFieldValue(AnalysisField field, JavaConstant receiverConstant, JavaConstant originalValueConstant) { + return AnalysisConstantReflectionProvider.interceptValue(universe, field, originalValueConstant); + } + + @Override + protected boolean skipScanning() { + return BuildPhaseProvider.isAnalysisFinished(); + } + + @Override + protected void rescanEconomicMap(EconomicMap map) { + super.rescanEconomicMap(map); + /* Make sure any EconomicMapImpl$CollisionLink objects are scanned. */ + if (map.getClass() == economicMapImpl) { + rescanField(map, economicMapImplEntriesField); + rescanField(map, economicMapImplHashArrayField); + } + + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/heap/SVMImageHeapVerifier.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/heap/SVMImageHeapVerifier.java new file mode 100644 index 000000000000..16df81086c33 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/heap/SVMImageHeapVerifier.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.hosted.analysis.heap; + +import com.oracle.graal.pointsto.BigBang; +import com.oracle.graal.pointsto.ObjectScanner; +import com.oracle.graal.pointsto.heap.HeapSnapshotVerifier; +import com.oracle.graal.pointsto.heap.ImageHeap; +import com.oracle.graal.pointsto.heap.ImageHeapScanner; +import com.oracle.graal.pointsto.meta.AnalysisType; +import com.oracle.svm.core.meta.SubstrateObjectConstant; +import com.oracle.svm.hosted.SVMHost; + +import jdk.vm.ci.meta.JavaConstant; + +public class SVMImageHeapVerifier extends HeapSnapshotVerifier { + public SVMImageHeapVerifier(BigBang bb, ImageHeap imageHeap, ImageHeapScanner scanner) { + super(bb, imageHeap, scanner); + } + + @Override + protected void scanTypes(ObjectScanner objectScanner) { + SVMHost svmHost = (SVMHost) bb.getHostVM(); + bb.getUniverse().getTypes().stream().filter(AnalysisType::isReachable).forEach(bb::onTypeScanned); + + bb.getUniverse().getTypes().stream().filter(AnalysisType::isReachable).forEach(t -> scanHub(svmHost, objectScanner, t)); + } + + private static void scanHub(SVMHost svmHost, ObjectScanner objectScanner, AnalysisType type) { + JavaConstant hubConstant = SubstrateObjectConstant.forObject(svmHost.dynamicHub(type)); + objectScanner.scanConstant(hubConstant, ObjectScanner.OtherReason.HUB); + } + +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/annotation/AnnotationSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/annotation/AnnotationSupport.java index a10facb8c8df..05ef8419ee08 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/annotation/AnnotationSupport.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/annotation/AnnotationSupport.java @@ -211,7 +211,7 @@ public ResolvedJavaField lookup(ResolvedJavaField field) { @Override public ResolvedJavaMethod lookup(ResolvedJavaMethod method) { - if (isConstantAnnotationType(method.getDeclaringClass())) { + if (isConstantAnnotationType(method.getDeclaringClass()) && !method.getName().equals("proxyClassLookup")) { AnnotationSubstitutionType declaringClass = getSubstitution(method.getDeclaringClass()); AnnotationSubstitutionMethod result = declaringClass.getSubstitutionMethod(method); assert result != null && result.original.equals(method); @@ -236,6 +236,9 @@ private synchronized AnnotationSubstitutionType getSubstitution(ResolvedJavaType for (ResolvedJavaMethod originalMethod : type.getDeclaredMethods()) { AnnotationSubstitutionMethod substitutionMethod; String methodName = canonicalMethodName(originalMethod); + if (methodName.equals("proxyClassLookup")) { + continue; + } if (methodName.equals("equals")) { substitutionMethod = new AnnotationEqualsMethod(originalMethod); } else if (methodName.equals("hashCode")) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/annotation/AnnotationTypeFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/annotation/AnnotationTypeFeature.java index 81baa14f6f26..e7d689e34876 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/annotation/AnnotationTypeFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/annotation/AnnotationTypeFeature.java @@ -27,6 +27,7 @@ import java.lang.annotation.Annotation; import java.lang.annotation.Repeatable; import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Field; import java.util.Objects; import java.util.stream.Stream; @@ -40,6 +41,7 @@ import com.oracle.svm.core.hub.AnnotationTypeSupport; import com.oracle.svm.hosted.FeatureImpl.AfterRegistrationAccessImpl; import com.oracle.svm.hosted.FeatureImpl.DuringAnalysisAccessImpl; +import com.oracle.svm.hosted.FeatureImpl.DuringSetupAccessImpl; @AutomaticFeature public class AnnotationTypeFeature implements Feature { @@ -47,6 +49,8 @@ public class AnnotationTypeFeature implements Feature { private EconomicSet repeatableAnnotationClasses = EconomicSet.create(); private EconomicSet visitedElements = EconomicSet.create(); + private Field annotationTypeMapField; + @Override public void afterRegistration(AfterRegistrationAccess access) { ImageSingletons.add(AnnotationTypeSupport.class, new AnnotationTypeSupport()); @@ -57,6 +61,11 @@ public void afterRegistration(AfterRegistrationAccess access) { .forEach(repeatableAnnotationClasses::add); } + @Override + public void duringSetup(DuringSetupAccess access) { + annotationTypeMapField = ((DuringSetupAccessImpl) access).findField(AnnotationTypeSupport.class, "annotationTypeMap"); + } + @Override public void duringAnalysis(DuringAnalysisAccess access) { DuringAnalysisAccessImpl accessImpl = (DuringAnalysisAccessImpl) access; @@ -78,13 +87,18 @@ public void duringAnalysis(DuringAnalysisAccess access) { }); Stream allElements = Stream.concat(Stream.concat(universe.getFields().stream(), universe.getMethods().stream()), universe.getTypes().stream()); Stream newElements = allElements.filter(visitedElements::add); - newElements.forEach(this::reportAnnotation); + newElements.forEach(e -> reportAnnotation(access, e)); } - private void reportAnnotation(AnnotatedElement element) { + private void reportAnnotation(DuringAnalysisAccess a, AnnotatedElement element) { + DuringAnalysisAccessImpl access = (DuringAnalysisAccessImpl) a; for (Annotation annotation : element.getDeclaredAnnotations()) { - if (repeatableAnnotationClasses.contains(annotation.annotationType())) { - ImageSingletons.lookup(AnnotationTypeSupport.class).createInstance(annotation.annotationType()); + Class annotationClass = annotation.annotationType(); + if (repeatableAnnotationClasses.contains(annotationClass)) { + AnnotationTypeSupport annotationTypeSupport = ImageSingletons.lookup(AnnotationTypeSupport.class); + if (annotationTypeSupport.createInstance(annotationClass)) { + access.rescanField(annotationTypeSupport, annotationTypeMapField); + } } } } 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 196757e27ac5..a86cf54ce9dd 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,6 +29,7 @@ import static com.oracle.svm.hosted.classinitialization.InitKind.RUN_TIME; import java.io.PrintWriter; +import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; @@ -75,6 +76,7 @@ public class ClassInitializationFeature implements GraalFeature { private ClassInitializationSupport classInitializationSupport; private AnalysisUniverse universe; private AnalysisMetaAccess metaAccess; + private Field dynamicHubClassInitializationInfoField; public static void processClassInitializationOptions(ClassInitializationSupport initializationSupport) { initializeNativeImagePackagesAtBuildTime(initializationSupport); @@ -115,6 +117,7 @@ public void duringSetup(DuringSetupAccess a) { access.registerObjectReplacer(this::checkImageHeapInstance); universe = ((FeatureImpl.DuringSetupAccessImpl) a).getBigBang().getUniverse(); metaAccess = ((FeatureImpl.DuringSetupAccessImpl) a).getBigBang().getMetaAccess(); + dynamicHubClassInitializationInfoField = access.findField(DynamicHub.class, "classInitializationInfo"); } private Object checkImageHeapInstance(Object obj) { @@ -299,7 +302,9 @@ private Set> initializeSafeDelayedClasses(TypeInitializerGraph initGrap provenSafe.add(c); ClassInitializationInfo initializationInfo = type.getClassInitializer() == null ? ClassInitializationInfo.NO_INITIALIZER_INFO_SINGLETON : ClassInitializationInfo.INITIALIZED_INFO_SINGLETON; - ((SVMHost) universe.hostVM()).dynamicHub(type).setClassInitializationInfo(initializationInfo); + DynamicHub hub = ((SVMHost) universe.hostVM()).dynamicHub(type); + hub.setClassInitializationInfo(initializationInfo); + universe.getHeapScanner().rescanField(hub, dynamicHubClassInitializationInfoField); } } }); @@ -324,6 +329,9 @@ private void buildClassInitializationInfo(FeatureImpl.DuringAnalysisAccessImpl a info = type.getClassInitializer() == null ? ClassInitializationInfo.NO_INITIALIZER_INFO_SINGLETON : ClassInitializationInfo.INITIALIZED_INFO_SINGLETON; } hub.setClassInitializationInfo(info); + // TODO rescanning the hub here should not be necessary + universe.getHeapScanner().scanHub(type); + universe.getHeapScanner().rescanField(hub, dynamicHubClassInitializationInfoField); } private static ClassInitializationInfo buildRuntimeInitializationInfo(FeatureImpl.DuringAnalysisAccessImpl access, AnalysisType type) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/FactoryMethodSupport.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/FactoryMethodSupport.java index bc380c7afbe1..5c56a6b13d4e 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/FactoryMethodSupport.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/FactoryMethodSupport.java @@ -67,7 +67,7 @@ public ResolvedJavaMethod lookup(UniverseMetaAccess metaAccess, ResolvedJavaMeth hUniverse = null; aMetaAccess = (AnalysisMetaAccess) metaAccess; } - AnalysisUniverse aUniverse = (AnalysisUniverse) aMetaAccess.getUniverse(); + AnalysisUniverse aUniverse = aMetaAccess.getUniverse(); MetaAccessProvider unwrappedMetaAccess = aMetaAccess.getWrapped(); AnalysisMethod aConstructor = constructor instanceof HostedMethod ? ((HostedMethod) constructor).getWrapped() : (AnalysisMethod) constructor; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageCodeCache.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageCodeCache.java index f12ee3327105..0bf964450126 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageCodeCache.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageCodeCache.java @@ -173,7 +173,7 @@ public void addConstantsToHeap() { for (DataSection.Data data : dataSection) { if (data instanceof SubstrateDataBuilder.ObjectData) { JavaConstant constant = ((SubstrateDataBuilder.ObjectData) data).getConstant(); - addConstantToHeap(constant); + addConstantToHeap(constant, "data section"); } } for (CompilationResult compilationResult : compilations.values()) { @@ -191,10 +191,6 @@ public void addConstantsToHeap() { } } - private void addConstantToHeap(Constant constant) { - addConstantToHeap(constant, null); - } - private void addConstantToHeap(Constant constant, Object reason) { Object obj = SubstrateObjectConstant.asObject(constant); @@ -203,7 +199,7 @@ private void addConstantToHeap(Constant constant, Object reason) { (reason != null ? " Method: " + reason : "")); } - imageHeap.addObject(obj, false, constantReasons.get(constant)); + imageHeap.addObject(obj, false, reason != null ? reason : constantReasons.get(constant)); } protected int getConstantsSize() { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JDKRegistrations.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JDKRegistrations.java index c1dc373bf787..b3573db69330 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JDKRegistrations.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JDKRegistrations.java @@ -69,5 +69,8 @@ public void duringSetup(DuringSetupAccess a) { * members and do not allow instantiation. */ rerunClassInit(a, "java.lang.ApplicationShutdownHooks", "java.io.DeleteOnExitHook"); + + /* Trigger initialization of java.net.URLConnection.fileNameMap. */ + java.net.URLConnection.getFileNameMap(); } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/localization/LocalizationFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/localization/LocalizationFeature.java index 80587c48cdda..8d2d021cf859 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/localization/LocalizationFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/localization/LocalizationFeature.java @@ -58,12 +58,6 @@ import java.util.spi.LocaleServiceProvider; import java.util.spi.TimeZoneNameProvider; -import com.oracle.svm.core.jdk.localization.BundleContentSubstitutedLocalizationSupport; -import com.oracle.svm.core.jdk.localization.LocalizationSupport; -import com.oracle.svm.core.jdk.localization.OptimizedLocalizationSupport; -import com.oracle.svm.hosted.NativeImageOptions; -import com.oracle.svm.core.jdk.localization.compression.GzipBundleCompression; -import com.oracle.svm.core.jdk.localization.substitutions.Target_sun_util_locale_provider_LocaleServiceProviderPool_OptimizedLocaleMode; import org.graalvm.collections.Pair; import org.graalvm.compiler.nodes.ValueNode; import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderContext; @@ -78,16 +72,25 @@ import com.oracle.svm.core.ClassLoaderSupport; import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.jdk.localization.BundleContentSubstitutedLocalizationSupport; +import com.oracle.svm.core.jdk.localization.LocalizationSupport; +import com.oracle.svm.core.jdk.localization.OptimizedLocalizationSupport; +import com.oracle.svm.core.jdk.localization.compression.GzipBundleCompression; +import com.oracle.svm.core.jdk.localization.substitutions.Target_sun_util_locale_provider_LocaleServiceProviderPool_OptimizedLocaleMode; import com.oracle.svm.core.option.HostedOptionKey; import com.oracle.svm.core.option.LocatableMultiOptionValue; import com.oracle.svm.core.option.OptionUtils; import com.oracle.svm.core.util.UserError; import com.oracle.svm.core.util.VMError; +import com.oracle.svm.hosted.FeatureImpl.DuringAnalysisAccessImpl; +import com.oracle.svm.hosted.FeatureImpl.DuringSetupAccessImpl; +import com.oracle.svm.hosted.NativeImageOptions; import com.oracle.svm.util.ReflectionUtil; import jdk.vm.ci.meta.ResolvedJavaField; import jdk.vm.ci.meta.ResolvedJavaMethod; import jdk.vm.ci.meta.ResolvedJavaType; +import sun.util.locale.LocaleObjectCache; import sun.util.locale.provider.LocaleProviderAdapter; import sun.util.locale.provider.ResourceBundleBasedAdapter; import sun.util.resources.LocaleData; @@ -145,6 +148,11 @@ public abstract class LocalizationFeature implements Feature { private Function> findClassByName; + private Field baseLocaleCacheField; + private Field localeCacheField; + private Field candidatesCacheField; + private Field localeObjectCacheMapField; + public static class Options { @Option(help = "Comma separated list of bundles to be included into the image.", type = OptionType.User)// public static final HostedOptionKey IncludeResourceBundles = new HostedOptionKey<>(new LocatableMultiOptionValue.Strings()); @@ -261,10 +269,20 @@ public void afterRegistration(AfterRegistrationAccess access) { } @Override - public void duringSetup(DuringSetupAccess access) { + public void duringSetup(DuringSetupAccess a) { + DuringSetupAccessImpl access = (DuringSetupAccessImpl) a; if (optimizedMode) { access.registerObjectReplacer(this::eagerlyInitializeBundles); } + if (JavaVersionUtil.JAVA_SPEC >= 17) { + baseLocaleCacheField = access.findField("sun.util.locale.BaseLocale$Cache", "CACHE"); + localeCacheField = access.findField("java.util.Locale$Cache", "LOCALECACHE"); + } else { + baseLocaleCacheField = access.findField("sun.util.locale.BaseLocale", "CACHE"); + localeCacheField = access.findField("java.util.Locale", "LOCALECACHE"); + } + candidatesCacheField = access.findField("java.util.ResourceBundle$Control", "CANDIDATES_CACHE"); + localeObjectCacheMapField = access.findField(LocaleObjectCache.class, "map"); } /** @@ -310,6 +328,21 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { addResourceBundles(); } + @Override + public void duringAnalysis(DuringAnalysisAccess a) { + DuringAnalysisAccessImpl access = (DuringAnalysisAccessImpl) a; + scanLocaleCache(access, baseLocaleCacheField); + scanLocaleCache(access, localeCacheField); + scanLocaleCache(access, candidatesCacheField); + } + + private void scanLocaleCache(DuringAnalysisAccessImpl access, Field cacheFieldField) { + Object localeCache = access.rescanRoot(cacheFieldField); + if (localeCache != null) { + access.rescanField(localeCache, localeObjectCacheMapField); + } + } + @Override public void afterAnalysis(AfterAnalysisAccess access) { if (compressionPool != null) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedField.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedField.java index e0d9f51c1cb7..a8174004187c 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedField.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedField.java @@ -104,6 +104,11 @@ public boolean isAccessed() { return wrapped.isAccessed(); } + @Override + public boolean isReachable() { + return wrapped.isReachable(); + } + public boolean isRead() { return wrapped.isRead(); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedMetaAccess.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedMetaAccess.java index b4b0bb9fd7b2..51ad8431f7a6 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedMetaAccess.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/meta/HostedMetaAccess.java @@ -63,7 +63,7 @@ public Optional optionalLookupJavaType(Class clazz) { if (!analysisType.isPresent()) { return Optional.empty(); } - result = ((HostedUniverse) getUniverse()).optionalLookup(analysisType.get()); + result = getUniverse().optionalLookup(analysisType.get()); return Optional.ofNullable(result); } @@ -80,7 +80,7 @@ public HostedMethod lookupJavaMethod(Executable reflectionMethod) { } public HostedMethod optionalLookupJavaMethod(Executable reflectionMethod) { - return ((HostedUniverse) getUniverse()).optionalLookup(getWrapped().lookupJavaMethod(reflectionMethod)); + return getUniverse().optionalLookup(getWrapped().lookupJavaMethod(reflectionMethod)); } @Override @@ -89,7 +89,7 @@ public HostedField lookupJavaField(Field reflectionField) { } public HostedField optionalLookupJavaField(Field reflectionField) { - return ((HostedUniverse) getUniverse()).optionalLookup(getWrapped().lookupJavaField(reflectionField)); + return getUniverse().optionalLookup(getWrapped().lookupJavaField(reflectionField)); } @Override @@ -121,4 +121,9 @@ public int getArrayBaseOffset(JavaKind elementKind) { public int getArrayIndexScale(JavaKind elementKind) { return getObjectLayout().getArrayIndexScale(elementKind); } + + @Override + public HostedUniverse getUniverse() { + return (HostedUniverse) universe; + } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/SubstrateGraphBuilderPlugins.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/SubstrateGraphBuilderPlugins.java index 8480356b2202..7471b49d5594 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/SubstrateGraphBuilderPlugins.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/snippets/SubstrateGraphBuilderPlugins.java @@ -94,10 +94,8 @@ import org.graalvm.word.PointerBase; import org.graalvm.word.UnsignedWord; -import com.oracle.graal.pointsto.infrastructure.UniverseMetaAccess; import com.oracle.graal.pointsto.meta.AnalysisField; import com.oracle.graal.pointsto.meta.AnalysisType; -import com.oracle.graal.pointsto.meta.AnalysisUniverse; import com.oracle.graal.pointsto.nodes.UnsafePartitionLoadNode; import com.oracle.graal.pointsto.nodes.UnsafePartitionStoreNode; import com.oracle.svm.core.FrameAccess; @@ -528,8 +526,7 @@ private static void interceptUpdaterInvoke(MetaAccessProvider metaAccess, Snippe private static void registerAsUnsafeAccessed(MetaAccessProvider metaAccess, Field field) { AnalysisField targetField = (AnalysisField) metaAccess.lookupJavaField(field); targetField.registerAsAccessed(); - AnalysisUniverse universe = (AnalysisUniverse) ((UniverseMetaAccess) metaAccess).getUniverse(); - targetField.registerAsUnsafeAccessed(universe); + targetField.registerAsUnsafeAccessed(); } private static void registerObjectPlugins(InvocationPlugins plugins) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/AnnotationSubstitutionProcessor.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/AnnotationSubstitutionProcessor.java index 9a6fdf00cefd..c1a63f80db56 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/AnnotationSubstitutionProcessor.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/AnnotationSubstitutionProcessor.java @@ -249,7 +249,7 @@ public void processComputedValueFields(BigBang bb) { targetFieldDeclaringType.registerAsReachable(); AnalysisField targetField = bb.getMetaAccess().lookupJavaField(cvField.getTargetField()); targetField.registerAsAccessed(); - targetField.registerAsUnsafeAccessed(bb.getUniverse()); + targetField.registerAsUnsafeAccessed(); break; } } @@ -864,7 +864,7 @@ private ResolvedJavaField fieldValueRecomputation(Class originalClass, Resolv targetName = recomputeAnnotation.name(); isFinal = recomputeAnnotation.isFinal(); disableCaching = recomputeAnnotation.disableCaching(); - guarantee(!isFinal || ComputedValueField.isFinalValid(kind), "@%s with %s can never be final during analysis: unset isFinal in the annotation on %s", + guarantee(!isFinal || !ComputedValueField.isOffsetRecomputation(kind), "@%s with %s can never be final during analysis: unset isFinal in the annotation on %s", RecomputeFieldValue.class.getSimpleName(), kind, annotated); guarantee(!isFinal || !disableCaching, "@%s can not be final if caching is disabled: unset isFinal in the annotation on %s", RecomputeFieldValue.class.getSimpleName(), kind, annotated); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/ComputedValueField.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/ComputedValueField.java index 8ecffa14c994..51b974522260 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/ComputedValueField.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/ComputedValueField.java @@ -24,6 +24,9 @@ */ package com.oracle.svm.hosted.substitute; +import static com.oracle.svm.core.annotate.RecomputeFieldValue.Kind.AtomicFieldUpdaterOffset; +import static com.oracle.svm.core.annotate.RecomputeFieldValue.Kind.FieldOffset; +import static com.oracle.svm.core.annotate.RecomputeFieldValue.Kind.TranslateFieldOffset; import static com.oracle.svm.core.util.VMError.guarantee; import static com.oracle.svm.core.util.VMError.shouldNotReachHere; @@ -46,8 +49,10 @@ import com.oracle.graal.pointsto.meta.AnalysisField; import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; import com.oracle.graal.pointsto.util.GraalAccess; +import com.oracle.svm.core.BuildPhaseProvider; import com.oracle.svm.core.annotate.RecomputeFieldValue; import com.oracle.svm.core.annotate.RecomputeFieldValue.CustomFieldValueComputer; +import com.oracle.svm.core.annotate.RecomputeFieldValue.CustomFieldValueProvider; import com.oracle.svm.core.annotate.RecomputeFieldValue.CustomFieldValueTransformer; import com.oracle.svm.core.config.ConfigurationValues; import com.oracle.svm.core.meta.ReadableJavaField; @@ -76,14 +81,18 @@ public class ComputedValueField implements ReadableJavaField, OriginalFieldProvider, ComputedValue { private static final Unsafe UNSAFE = GraalUnsafeAccess.getUnsafe(); + private static final EnumSet offsetComputationKinds = EnumSet.of(FieldOffset, TranslateFieldOffset, AtomicFieldUpdaterOffset); private final ResolvedJavaField original; private final ResolvedJavaField annotated; private final RecomputeFieldValue.Kind kind; private final Class targetClass; private final Field targetField; + private final CustomFieldValueProvider customValueProvider; private final boolean isFinal; private final boolean disableCaching; + private final boolean isCustomValueAvailableDuringAnalysis; + private final boolean isOffsetField; private JavaConstant constantValue; @@ -95,6 +104,10 @@ public class ComputedValueField implements ReadableJavaField, OriginalFieldProvi private JavaConstant valueCacheNullKey; private final ReentrantReadWriteLock valueCacheLock = new ReentrantReadWriteLock(); + public ComputedValueField(ResolvedJavaField original, ResolvedJavaField annotated, RecomputeFieldValue.Kind kind, Class targetClass, String targetName, boolean isFinal) { + this(original, annotated, kind, targetClass, targetName, isFinal, false); + } + public ComputedValueField(ResolvedJavaField original, ResolvedJavaField annotated, RecomputeFieldValue.Kind kind, Class targetClass, String targetName, boolean isFinal, boolean disableCaching) { assert original != null; @@ -106,7 +119,10 @@ public ComputedValueField(ResolvedJavaField original, ResolvedJavaField annotate this.targetClass = targetClass; this.isFinal = isFinal; this.disableCaching = disableCaching; + this.isOffsetField = isOffsetRecomputation(kind); + boolean customValueAvailableDuringAnalysis = true; + CustomFieldValueProvider customProvider = null; Field f = null; switch (kind) { case Reset: @@ -119,20 +135,44 @@ public ComputedValueField(ResolvedJavaField original, ResolvedJavaField annotate throw shouldNotReachHere("could not find target field " + targetClass.getName() + "." + targetName + " for alias " + annotated.format("%H.%n")); } break; + case Custom: + try { + Constructor[] constructors = targetClass.getDeclaredConstructors(); + if (constructors.length != 1) { + throw UserError.abort("The custom field value computer class %s has more than one constructor", targetClass.getName()); + } + Constructor constructor = constructors[0]; + + Object[] constructorArgs = new Object[constructor.getParameterCount()]; + for (int i = 0; i < constructorArgs.length; i++) { + constructorArgs[i] = configurationValue(constructor.getParameterTypes()[i]); + } + constructor.setAccessible(true); + customProvider = (CustomFieldValueProvider) constructor.newInstance(constructorArgs); + customValueAvailableDuringAnalysis = customProvider.isAvailableDuringAnalysis(); + } catch (InvocationTargetException | InstantiationException | IllegalAccessException ex) { + throw shouldNotReachHere("Error creating custom field value computer for alias " + annotated.format("%H.%n"), ex); + } } - guarantee(!isFinal || isFinalValid(kind)); + guarantee(!isFinal || !isOffsetField); + this.isCustomValueAvailableDuringAnalysis = customValueAvailableDuringAnalysis; this.targetField = f; + this.customValueProvider = customProvider; this.valueCache = EconomicMap.create(); } - public ComputedValueField(ResolvedJavaField original, ResolvedJavaField annotated, RecomputeFieldValue.Kind kind, Class targetClass, String targetName, boolean isFinal) { - this(original, annotated, kind, targetClass, targetName, isFinal, false); + public static boolean isOffsetRecomputation(RecomputeFieldValue.Kind kind) { + return offsetComputationKinds.contains(kind); } - public static boolean isFinalValid(RecomputeFieldValue.Kind kind) { - EnumSet finalIllegal = EnumSet.of(RecomputeFieldValue.Kind.FieldOffset, - RecomputeFieldValue.Kind.TranslateFieldOffset, RecomputeFieldValue.Kind.AtomicFieldUpdaterOffset); - return !finalIllegal.contains(kind); + @Override + public boolean isValueAvailableDuringAnalysis() { + return isCustomValueAvailableDuringAnalysis && !isOffsetField; + } + + @Override + public boolean isValueAvailable() { + return constantValue != null || BuildPhaseProvider.isAnalysisFinished() || isValueAvailableDuringAnalysis(); } public ResolvedJavaField getAnnotated() { @@ -185,6 +225,13 @@ public boolean isSynthetic() { return original.isSynthetic(); } + /* + * TODO remove this method + * + * It is a catch-all method in case we forgot to register some of the target fields as accessed. + * AnnotationSubstitutionProcessor.processComputedValueFields() also registers the target fields + * of the automatic re-computation as unsafe accessed. + */ public void processAnalysis(AnalysisMetaAccess aMetaAccess) { switch (kind) { case FieldOffset: @@ -292,43 +339,26 @@ private JavaConstant computeValue(MetaAccessProvider metaAccess, JavaConstant re result = translateFieldOffset(metaAccess, receiver, targetClass); break; case Custom: - try { - Constructor[] constructors = targetClass.getDeclaredConstructors(); - if (constructors.length != 1) { - throw UserError.abort("The custom field value computer class %s has more than one constructor", targetClass.getName()); - } - Constructor constructor = constructors[0]; - - Object[] constructorArgs = new Object[constructor.getParameterCount()]; - for (int i = 0; i < constructorArgs.length; i++) { - constructorArgs[i] = configurationValue(constructor.getParameterTypes()[i]); - } - constructor.setAccessible(true); - Object instance = constructor.newInstance(constructorArgs); - - Object receiverValue = receiver == null ? null : originalSnippetReflection.asObject(Object.class, receiver); - Object newValue; - if (instance instanceof CustomFieldValueComputer) { - newValue = ((CustomFieldValueComputer) instance).compute(metaAccess, original, annotated, receiverValue); - } else if (instance instanceof CustomFieldValueTransformer) { - JavaConstant originalValueConstant = ReadableJavaField.readFieldValue(metaAccess, GraalAccess.getOriginalProviders().getConstantReflection(), original, receiver); - Object originalValue; - if (originalValueConstant.getJavaKind().isPrimitive()) { - originalValue = originalValueConstant.asBoxedPrimitive(); - } else { - originalValue = originalSnippetReflection.asObject(Object.class, originalValueConstant); - } - newValue = ((CustomFieldValueTransformer) instance).transform(metaAccess, original, annotated, receiverValue, originalValue); + Object receiverValue = receiver == null ? null : originalSnippetReflection.asObject(Object.class, receiver); + Object newValue; + if (customValueProvider instanceof CustomFieldValueComputer) { + newValue = ((CustomFieldValueComputer) customValueProvider).compute(metaAccess, original, annotated, receiverValue); + } else if (customValueProvider instanceof CustomFieldValueTransformer) { + JavaConstant originalValueConstant = ReadableJavaField.readFieldValue(metaAccess, GraalAccess.getOriginalProviders().getConstantReflection(), original, receiver); + Object originalValue; + if (originalValueConstant.getJavaKind().isPrimitive()) { + originalValue = originalValueConstant.asBoxedPrimitive(); } else { - throw UserError.abort("The custom field value computer class %s does not implement %s or %s", targetClass.getName(), - CustomFieldValueComputer.class.getSimpleName(), CustomFieldValueTransformer.class.getSimpleName()); + originalValue = originalSnippetReflection.asObject(Object.class, originalValueConstant); } - - result = originalSnippetReflection.forBoxed(annotated.getJavaKind(), newValue); - assert result.getJavaKind() == annotated.getJavaKind(); - } catch (InvocationTargetException | InstantiationException | IllegalAccessException ex) { - throw shouldNotReachHere("Error performing field recomputation for alias " + annotated.format("%H.%n"), ex); + newValue = ((CustomFieldValueTransformer) customValueProvider).transform(metaAccess, original, annotated, receiverValue, originalValue); + } else { + throw UserError.abort("The custom field value computer class %s does not implement %s or %s", targetClass.getName(), + CustomFieldValueComputer.class.getSimpleName(), CustomFieldValueTransformer.class.getSimpleName()); } + + result = originalSnippetReflection.forBoxed(annotated.getJavaKind(), newValue); + assert result.getJavaKind() == annotated.getJavaKind(); break; default: throw shouldNotReachHere("Field recomputation of kind " + kind + " for field " + original.format("%H.%n") + diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/SubstitutionReflectivityFilter.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/SubstitutionReflectivityFilter.java index f81ce25afc16..6f90109e6d9e 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/SubstitutionReflectivityFilter.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/SubstitutionReflectivityFilter.java @@ -46,7 +46,7 @@ public class SubstitutionReflectivityFilter { public static boolean shouldExclude(Class classObj, AnalysisMetaAccess metaAccess, AnalysisUniverse universe) { try { ResolvedJavaType analysisClass = metaAccess.lookupJavaType(classObj); - if (!universe.hostVM().platformSupported(universe, analysisClass)) { + if (!universe.hostVM().platformSupported(analysisClass)) { return true; } else if (analysisClass.isAnnotationPresent(Delete.class)) { return true; // accesses would fail at runtime @@ -60,7 +60,7 @@ public static boolean shouldExclude(Class classObj, AnalysisMetaAccess metaAc public static boolean shouldExclude(Executable method, AnalysisMetaAccess metaAccess, AnalysisUniverse universe) { try { AnalysisMethod aMethod = metaAccess.lookupJavaMethod(method); - if (!universe.hostVM().platformSupported(universe, aMethod)) { + if (!universe.hostVM().platformSupported(aMethod)) { return true; } else if (aMethod.isAnnotationPresent(Delete.class)) { return true; // accesses would fail at runtime @@ -83,7 +83,7 @@ public static boolean shouldExclude(Executable method, AnalysisMetaAccess metaAc public static boolean shouldExclude(Field field, AnalysisMetaAccess metaAccess, AnalysisUniverse universe) { try { AnalysisField aField = metaAccess.lookupJavaField(field); - if (!universe.hostVM().platformSupported(universe, aField)) { + if (!universe.hostVM().platformSupported(aField)) { return true; } if (aField.isAnnotationPresent(Delete.class)) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/thread/VMThreadMTFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/thread/VMThreadMTFeature.java index 815d2acefc0e..ddc1121c1f38 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/thread/VMThreadMTFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/thread/VMThreadMTFeature.java @@ -24,6 +24,7 @@ */ package com.oracle.svm.hosted.thread; +import java.lang.reflect.Field; import java.util.List; import org.graalvm.compiler.api.replacements.SnippetReflectionProvider; @@ -60,6 +61,8 @@ import com.oracle.svm.core.threadlocal.VMThreadLocalInfos; import com.oracle.svm.core.threadlocal.VMThreadLocalMTSupport; import com.oracle.svm.core.util.VMError; +import com.oracle.svm.hosted.FeatureImpl.DuringAnalysisAccessImpl; +import com.oracle.svm.hosted.FeatureImpl.DuringSetupAccessImpl; import jdk.vm.ci.code.MemoryBarriers; import jdk.vm.ci.meta.ResolvedJavaMethod; @@ -74,6 +77,8 @@ public class VMThreadMTFeature implements GraalFeature { private final VMThreadLocalCollector threadLocalCollector = new VMThreadLocalCollector(); private final VMThreadLocalMTSupport threadLocalSupport = new VMThreadLocalMTSupport(); + private Field infosField; + public int getVMThreadSize() { assert threadLocalSupport.vmThreadSize != -1 : "not yet initialized"; return threadLocalSupport.vmThreadSize; @@ -88,6 +93,8 @@ public boolean isInConfiguration(IsInConfigurationAccess access) { public void duringSetup(DuringSetupAccess config) { ImageSingletons.add(VMThreadLocalMTSupport.class, threadLocalSupport); config.registerObjectReplacer(threadLocalCollector); + + infosField = ((DuringSetupAccessImpl) config).findField(VMThreadLocalInfos.class, "infos"); } /** @@ -226,13 +233,15 @@ private boolean handleGetAddress(GraphBuilderContext b, ResolvedJavaMethod targe } @Override - public void duringAnalysis(DuringAnalysisAccess access) { + public void duringAnalysis(DuringAnalysisAccess a) { /* * Update during analysis so that the static analysis sees all infos. After analysis only * the order is going to change. */ if (VMThreadLocalInfos.setInfos(threadLocalCollector.threadLocals.values())) { - access.requireAnalysisIteration(); + a.requireAnalysisIteration(); + DuringAnalysisAccessImpl access = (DuringAnalysisAccessImpl) a; + access.rescanField(ImageSingletons.lookup(VMThreadLocalInfos.class), infosField); } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/thread/VMThreadSTFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/thread/VMThreadSTFeature.java index 13ec85cf0ca4..4544f3e3eeb9 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/thread/VMThreadSTFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/thread/VMThreadSTFeature.java @@ -24,6 +24,7 @@ */ package com.oracle.svm.hosted.thread; +import java.lang.reflect.Field; import java.util.List; import org.graalvm.compiler.api.replacements.SnippetReflectionProvider; @@ -56,6 +57,8 @@ import com.oracle.svm.core.threadlocal.VMThreadLocalInfo; import com.oracle.svm.core.threadlocal.VMThreadLocalInfos; import com.oracle.svm.core.threadlocal.VMThreadLocalSTSupport; +import com.oracle.svm.hosted.FeatureImpl; +import com.oracle.svm.hosted.FeatureImpl.DuringAnalysisAccessImpl; import jdk.vm.ci.meta.JavaKind; import jdk.vm.ci.meta.ResolvedJavaMethod; @@ -68,6 +71,7 @@ public class VMThreadSTFeature implements GraalFeature { private final VMThreadLocalCollector threadLocalCollector = new VMThreadLocalCollector(); + private Field infosField; @Override public boolean isInConfiguration(IsInConfigurationAccess access) { @@ -78,6 +82,7 @@ public boolean isInConfiguration(IsInConfigurationAccess access) { public void duringSetup(DuringSetupAccess config) { ImageSingletons.add(VMThreadLocalSTSupport.class, new VMThreadLocalSTSupport()); config.registerObjectReplacer(threadLocalCollector); + infosField = ((FeatureImpl.DuringSetupAccessImpl) config).findField(VMThreadLocalInfos.class, "infos"); } /** @@ -205,13 +210,15 @@ private boolean handleGetAddress(GraphBuilderContext b, ResolvedJavaMethod targe } @Override - public void duringAnalysis(DuringAnalysisAccess access) { + public void duringAnalysis(DuringAnalysisAccess a) { /* * Update during analysis so that the static analysis sees all infos. After analysis only * the order is going to change. */ if (VMThreadLocalInfos.setInfos(threadLocalCollector.threadLocals.values())) { - access.requireAnalysisIteration(); + a.requireAnalysisIteration(); + DuringAnalysisAccessImpl access = (DuringAnalysisAccessImpl) a; + access.rescanField(ImageSingletons.lookup(VMThreadLocalInfos.class), infosField); } } diff --git a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrFeature.java b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrFeature.java index a3a49d7852f9..e2be25ec57f8 100644 --- a/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrFeature.java +++ b/substratevm/src/com.oracle.svm.jfr/src/com/oracle/svm/jfr/JfrFeature.java @@ -49,6 +49,7 @@ import com.oracle.svm.core.thread.ThreadListenerSupport; import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.FeatureImpl; +import com.oracle.svm.hosted.FeatureImpl.DuringAnalysisAccessImpl; import com.oracle.svm.jfr.traceid.JfrTraceId; import com.oracle.svm.jfr.traceid.JfrTraceIdEpoch; import com.oracle.svm.jfr.traceid.JfrTraceIdMap; @@ -174,7 +175,8 @@ public void beforeCompilation(BeforeCompilationAccess a) { } @Override - public void duringAnalysis(DuringAnalysisAccess access) { + public void duringAnalysis(DuringAnalysisAccess a) { + DuringAnalysisAccessImpl access = (DuringAnalysisAccessImpl) a; Class eventClass = access.findClassByName("jdk.internal.event.Event"); if (eventClass != null && access.isReachable(eventClass)) { Set> s = access.reachableSubtypes(eventClass); @@ -187,6 +189,7 @@ public void duringAnalysis(DuringAnalysisAccess access) { try { Field f = c.getDeclaredField("eventHandler"); RuntimeReflection.register(f); + access.rescanRoot(f); } catch (Exception e) { throw VMError.shouldNotReachHere("Unable to register eventHandler for: " + c.getCanonicalName(), e); } diff --git a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/access/JNIAccessFeature.java b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/access/JNIAccessFeature.java index 13576d1f4d07..3c847bfb60c6 100644 --- a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/access/JNIAccessFeature.java +++ b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/access/JNIAccessFeature.java @@ -281,7 +281,7 @@ private void addMethod(Executable method, DuringAnalysisAccessImpl access) { } JNIAccessibleClass jniClass = addClass(method.getDeclaringClass(), access); JNIAccessibleMethodDescriptor descriptor = JNIAccessibleMethodDescriptor.of(method); - jniClass.addMethodIfAbsent(descriptor, d -> { + jniClass.addMethodIfAbsent(access, descriptor, d -> { MetaAccessProvider wrappedMetaAccess = access.getMetaAccess().getWrapped(); JNIJavaCallWrapperMethod varargsCallWrapper = new JNIJavaCallWrapperMethod(method, CallVariant.VARARGS, false, wrappedMetaAccess, nativeLibraries); @@ -318,7 +318,7 @@ private static void addField(Field reflField, boolean writable, DuringAnalysisAc } JNIAccessibleClass jniClass = addClass(reflField.getDeclaringClass(), access); AnalysisField field = access.getMetaAccess().lookupJavaField(reflField); - jniClass.addFieldIfAbsent(field.getName(), name -> new JNIAccessibleField(jniClass, name, field.getJavaKind(), field.getModifiers())); + jniClass.addFieldIfAbsent(access, field.getName(), name -> new JNIAccessibleField(jniClass, name, field.getJavaKind(), field.getModifiers())); field.registerAsJNIAccessed(); field.registerAsRead(null); if (writable) { diff --git a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/access/JNIAccessibleClass.java b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/access/JNIAccessibleClass.java index ff87a61be8c0..4054d775010b 100644 --- a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/access/JNIAccessibleClass.java +++ b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/access/JNIAccessibleClass.java @@ -33,6 +33,8 @@ import org.graalvm.nativeimage.Platforms; import com.oracle.svm.core.util.ImageHeapMap; +import com.oracle.svm.hosted.FeatureImpl; +import com.oracle.svm.util.ReflectionUtil; import jdk.vm.ci.meta.MetaUtil; @@ -65,22 +67,24 @@ public JNIAccessibleField getField(String name) { } @Platforms(HOSTED_ONLY.class) - void addFieldIfAbsent(String name, Function mappingFunction) { + void addFieldIfAbsent(FeatureImpl.DuringAnalysisAccessImpl access, String name, Function mappingFunction) { if (fields == null) { fields = ImageHeapMap.create(); } if (!fields.containsKey(name)) { fields.put(name, mappingFunction.apply(name)); + access.rescanField(this, ReflectionUtil.lookupField(JNIAccessibleClass.class, "fields")); } } @Platforms(HOSTED_ONLY.class) - void addMethodIfAbsent(JNIAccessibleMethodDescriptor descriptor, Function mappingFunction) { + void addMethodIfAbsent(FeatureImpl.DuringAnalysisAccessImpl access, JNIAccessibleMethodDescriptor descriptor, Function mappingFunction) { if (methods == null) { methods = ImageHeapMap.create(); } if (!methods.containsKey(descriptor)) { methods.put(descriptor, mappingFunction.apply(descriptor)); + access.rescanField(this, ReflectionUtil.lookupField(JNIAccessibleClass.class, "methods")); } } diff --git a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/access/JNINativeLinkage.java b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/access/JNINativeLinkage.java index 14389e851dc2..e4d1134ebefa 100644 --- a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/access/JNINativeLinkage.java +++ b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/access/JNINativeLinkage.java @@ -24,16 +24,22 @@ */ package com.oracle.svm.jni.access; +//Checkstyle: allow reflection + +import java.lang.reflect.Field; + import org.graalvm.nativeimage.c.function.CFunctionPointer; import org.graalvm.word.PointerBase; import org.graalvm.word.WordFactory; +import com.oracle.graal.pointsto.BigBang; import com.oracle.svm.core.c.CGlobalData; import com.oracle.svm.core.c.CGlobalDataFactory; import com.oracle.svm.core.graal.code.CGlobalDataInfo; import com.oracle.svm.core.jdk.NativeLibrarySupport; import com.oracle.svm.core.jdk.PlatformNativeLibrarySupport; import com.oracle.svm.hosted.c.CGlobalDataFeature; +import com.oracle.svm.util.ReflectionUtil; import jdk.vm.ci.meta.JavaType; import jdk.vm.ci.meta.MetaUtil; @@ -85,11 +91,13 @@ public boolean isBuiltInFunction() { return (PlatformNativeLibrarySupport.singleton().isBuiltinPkgNative(this.getShortName())); } - public CGlobalDataInfo getBuiltInAddress() { + public CGlobalDataInfo getBuiltInAddress(BigBang bb) { assert this.isBuiltInFunction(); if (builtInAddress == null) { CGlobalData linkage = CGlobalDataFactory.forSymbol(this.getShortName()); builtInAddress = CGlobalDataFeature.singleton().registerAsAccessedOrGet(linkage); + Field jniNativeLinkageBuiltInAddressField = ReflectionUtil.lookupField(JNINativeLinkage.class, "builtInAddress"); + bb.getUniverse().getHeapScanner().rescanField(this, jniNativeLinkageBuiltInAddressField); } return builtInAddress; } diff --git a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNICallTrampolineMethod.java b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNICallTrampolineMethod.java index afcb8b398851..2b9f45dc36c4 100644 --- a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNICallTrampolineMethod.java +++ b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNICallTrampolineMethod.java @@ -139,7 +139,7 @@ public CompileFunction createCustomCompileFunction() { private int getFieldOffset(HostedProviders providers) { HostedMetaAccess metaAccess = (HostedMetaAccess) providers.getMetaAccess(); - HostedUniverse universe = (HostedUniverse) metaAccess.getUniverse(); + HostedUniverse universe = metaAccess.getUniverse(); AnalysisUniverse analysisUniverse = universe.getBigBang().getUniverse(); HostedField hostedField = universe.lookup(analysisUniverse.lookup(callWrapperField)); assert hostedField.hasLocation(); diff --git a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNINativeCallWrapperMethod.java b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNINativeCallWrapperMethod.java index f7d58d95bba2..09422a089f22 100644 --- a/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNINativeCallWrapperMethod.java +++ b/substratevm/src/com.oracle.svm.jni/src/com/oracle/svm/jni/hosted/JNINativeCallWrapperMethod.java @@ -113,7 +113,7 @@ public StructuredGraph buildGraph(DebugContext debug, ResolvedJavaMethod method, ValueNode callAddress; if (linkage.isBuiltInFunction()) { - callAddress = kit.unique(new CGlobalDataLoadAddressNode(linkage.getBuiltInAddress())); + callAddress = kit.unique(new CGlobalDataLoadAddressNode(linkage.getBuiltInAddress(providers.getBigBang()))); } else { callAddress = kit.nativeCallAddress(kit.createObject(linkage)); } diff --git a/substratevm/src/com.oracle.svm.methodhandles/src/com/oracle/svm/methodhandles/MethodHandleFeature.java b/substratevm/src/com.oracle.svm.methodhandles/src/com/oracle/svm/methodhandles/MethodHandleFeature.java index 5089c3b4dfcf..9a7cba02cdfd 100644 --- a/substratevm/src/com.oracle.svm.methodhandles/src/com/oracle/svm/methodhandles/MethodHandleFeature.java +++ b/substratevm/src/com.oracle.svm.methodhandles/src/com/oracle/svm/methodhandles/MethodHandleFeature.java @@ -46,6 +46,7 @@ import com.oracle.svm.core.annotate.AutomaticFeature; import com.oracle.svm.core.invoke.MethodHandleIntrinsic; import com.oracle.svm.core.util.VMError; +import com.oracle.svm.hosted.FeatureImpl.DuringAnalysisAccessImpl; import com.oracle.svm.util.ReflectionUtil; // Checkstyle: stop @@ -99,6 +100,15 @@ public class MethodHandleFeature implements Feature { private Field lambdaFormArity; private Field nameFunction; private Field namedFunctionMemberName; + private Field lambdaFormLFIdentity; + private Field lambdaFormLFZero; + private Field lambdaFormNFIdentity; + private Field lambdaFormNFZero; + private Field typedAccessors; + private Field classSpecializerCache; + private Class lambdaFormClass; + private Class classSpecializerClass; + private Class arrayAccessorClass; @Override public boolean isInConfiguration(IsInConfigurationAccess access) { @@ -123,7 +133,7 @@ public void duringSetup(DuringSetupAccess access) { memberNameIsField = ReflectionUtil.lookupMethod(memberNameClass, "isField"); memberNameGetParameterTypes = ReflectionUtil.lookupMethod(memberNameClass, "getParameterTypes"); - Class lambdaFormClass = access.findClassByName("java.lang.invoke.LambdaForm"); + lambdaFormClass = access.findClassByName("java.lang.invoke.LambdaForm"); lambdaFormNames = ReflectionUtil.lookupField(lambdaFormClass, "names"); lambdaFormArity = ReflectionUtil.lookupField(lambdaFormClass, "arity"); Class nameClass = access.findClassByName("java.lang.invoke.LambdaForm$Name"); @@ -131,6 +141,16 @@ public void duringSetup(DuringSetupAccess access) { Class namedFunctionClass = access.findClassByName("java.lang.invoke.LambdaForm$NamedFunction"); namedFunctionMemberName = ReflectionUtil.lookupField(namedFunctionClass, "member"); + lambdaFormLFIdentity = ReflectionUtil.lookupField(lambdaFormClass, "LF_identity"); + lambdaFormLFZero = ReflectionUtil.lookupField(lambdaFormClass, "LF_zero"); + lambdaFormNFIdentity = ReflectionUtil.lookupField(lambdaFormClass, "NF_identity"); + lambdaFormNFZero = ReflectionUtil.lookupField(lambdaFormClass, "NF_zero"); + + classSpecializerClass = access.findClassByName("java.lang.invoke.ClassSpecializer"); + arrayAccessorClass = access.findClassByName("java.lang.invoke.MethodHandleImpl$ArrayAccessor"); + typedAccessors = ReflectionUtil.lookupField(arrayAccessorClass, "TYPED_ACCESSORS"); + classSpecializerCache = ReflectionUtil.lookupField(classSpecializerClass, "cache"); + access.registerObjectReplacer(this::registerMethodHandle); } @@ -174,6 +194,8 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { access.registerSubtypeReachabilityHandler(MethodHandleFeature::registerVarHandleMethodsForReflection, access.findClassByName("java.lang.invoke.VarHandle")); + + access.registerSubtypeReachabilityHandler(MethodHandleFeature::scanBoundMethodHandle, boundMethodHandleClass); } private static void registerMHImplFunctionsForReflection(DuringAnalysisAccess access) { @@ -357,4 +379,29 @@ private void registerMemberName(Object memberName) { throw VMError.shouldNotReachHere(e); } } + + @Override + public void duringAnalysis(DuringAnalysisAccess a) { + DuringAnalysisAccessImpl access = (DuringAnalysisAccessImpl) a; + access.rescanRoot(lambdaFormLFIdentity); + access.rescanRoot(lambdaFormLFZero); + access.rescanRoot(lambdaFormNFIdentity); + access.rescanRoot(lambdaFormNFZero); + access.rescanRoot(typedAccessors); + + // Object specializer = ReflectionUtil.readStaticField(boundMethodHandleClass, + // "SPECIALIZER"); + // // Map cache = ReflectionUtil.readField(classSpecializerClass, "cache", + // specializer); + // // access.rescanObject(cache); + // access.rescanField(specializer, classSpecializerCache); + } + + private static void scanBoundMethodHandle(DuringAnalysisAccess a, Class bmhSubtype) { + DuringAnalysisAccessImpl access = (DuringAnalysisAccessImpl) a; + Field bmhSpeciesField = ReflectionUtil.lookupField(true, bmhSubtype, "BMH_SPECIES"); + if (bmhSpeciesField != null) { + access.rescanRoot(bmhSpeciesField); + } + } } diff --git a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/FieldOffsetComputer.java b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/FieldOffsetComputer.java index af4dc5b6d7e8..68f5b7b79228 100644 --- a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/FieldOffsetComputer.java +++ b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/FieldOffsetComputer.java @@ -28,6 +28,7 @@ import java.lang.reflect.Field; +import com.oracle.svm.core.annotate.RecomputeFieldValue; import com.oracle.svm.core.annotate.RecomputeFieldValue.CustomFieldValueComputer; import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.meta.HostedField; @@ -38,6 +39,11 @@ public class FieldOffsetComputer implements CustomFieldValueComputer { + @Override + public RecomputeFieldValue.ValueAvailability valueAvailability() { + return RecomputeFieldValue.ValueAvailability.AfterAnalysis; + } + @Override public Object compute(MetaAccessProvider metaAccess, ResolvedJavaField original, ResolvedJavaField annotated, Object receiver) { VMError.guarantee(metaAccess instanceof HostedMetaAccess, "Field offset computation must be done during compilation."); diff --git a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionDataBuilder.java b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionDataBuilder.java index 36455d524d25..caf5b5e9ed1a 100644 --- a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionDataBuilder.java +++ b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionDataBuilder.java @@ -51,10 +51,10 @@ import java.util.function.Predicate; import java.util.stream.Collectors; -import com.oracle.svm.core.jdk.SealedClassSupport; import org.graalvm.compiler.debug.GraalError; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.hosted.Feature.DuringAnalysisAccess; +import org.graalvm.nativeimage.hosted.Feature.DuringSetupAccess; import org.graalvm.nativeimage.impl.ConfigurationCondition; import org.graalvm.nativeimage.impl.RuntimeReflectionSupport; import org.graalvm.util.GuardedAnnotationAccess; @@ -67,11 +67,13 @@ import com.oracle.svm.core.hub.ClassForNameSupport; import com.oracle.svm.core.hub.DynamicHub; import com.oracle.svm.core.jdk.RecordSupport; +import com.oracle.svm.core.jdk.SealedClassSupport; import com.oracle.svm.core.jdk.proxy.DynamicProxyRegistry; import com.oracle.svm.core.util.UserError; import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.ConditionalConfigurationRegistry; import com.oracle.svm.hosted.FeatureImpl.DuringAnalysisAccessImpl; +import com.oracle.svm.hosted.FeatureImpl.DuringSetupAccessImpl; import com.oracle.svm.hosted.FeatureImpl.FeatureAccessImpl; import com.oracle.svm.hosted.annotation.AnnotationSubstitutionType; import com.oracle.svm.hosted.substitute.SubstitutionReflectivityFilter; @@ -108,6 +110,9 @@ public class ReflectionDataBuilder extends ConditionalConfigurationRegistry impl private final ReflectionDataAccessors accessors; + private static Field annotationTypeMapField; + private static Field dynamicHubReflectionDataField; + public ReflectionDataBuilder(FeatureAccessImpl access) { arrayReflectionData = getArrayReflectionData(); accessors = new ReflectionDataAccessors(access); @@ -198,6 +203,12 @@ private void checkNotSealed() { } } + protected void duringSetup(DuringSetupAccess a) { + DuringSetupAccessImpl access = (DuringSetupAccessImpl) a; + annotationTypeMapField = access.findField(AnnotationTypeSupport.class, "annotationTypeMap"); + dynamicHubReflectionDataField = access.findField(DynamicHub.class, "rd"); + } + protected void duringAnalysis(DuringAnalysisAccess a) { DuringAnalysisAccessImpl access = (DuringAnalysisAccessImpl) a; processReachableTypes(access); @@ -411,6 +422,7 @@ private static void makeTypeReachable(DuringAnalysisAccessImpl access, Type type access.requireAnalysisIteration(); } ClassForNameSupport.registerClass(clazz); + // access.rescanObject(annotationTypeSupport.getAnnotationTypeMap()); } else if (type instanceof TypeVariable) { for (Type bound : ((TypeVariable) type).getBounds()) { makeTypeReachable(access, bound); @@ -443,7 +455,10 @@ private static void registerTypesForAnnotationValue(DuringAnalysisAccessImpl acc * Parsing annotation data in reflection classes requires being able to instantiate all * annotation types at runtime. */ - ImageSingletons.lookup(AnnotationTypeSupport.class).createInstance((Class) type); + AnnotationTypeSupport annotationTypeSupport = ImageSingletons.lookup(AnnotationTypeSupport.class); + if (annotationTypeSupport.createInstance((Class) type)) { + access.rescanField(annotationTypeSupport, annotationTypeMapField); + } ImageSingletons.lookup(DynamicProxyRegistry.class).addProxyClass(type); Annotation annotation = (Annotation) value; @@ -507,6 +522,7 @@ private void processClass(DuringAnalysisAccessImpl access, Class clazz) { if (reflectionClasses.contains(clazz)) { ClassForNameSupport.registerClass(clazz); + // access.rescanObject(ImageSingletons.lookup(ClassForNameSupport.class).registeredClasses); } /* @@ -560,6 +576,7 @@ private void processClass(DuringAnalysisAccessImpl access, Class clazz) { buildRecordComponents(clazz, access)); } hub.setReflectionData(reflectionData); + access.rescanField(hub, dynamicHubReflectionDataField); if (type.isAnnotation()) { /* diff --git a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionFeature.java b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionFeature.java index 151d1681cbf1..898948251cc0 100644 --- a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionFeature.java +++ b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionFeature.java @@ -188,6 +188,7 @@ public void duringSetup(DuringSetupAccess a) { loader = access.getImageClassLoader(); annotationSubstitutions = ((Inflation) access.getBigBang()).getAnnotationSubstitutionProcessor(); + reflectionData.duringSetup(access); } @Override diff --git a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionObjectReplacer.java b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionObjectReplacer.java index 703160bb97e2..a8daf5a64c0f 100644 --- a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionObjectReplacer.java +++ b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/hosted/ReflectionObjectReplacer.java @@ -41,7 +41,6 @@ import com.oracle.graal.pointsto.meta.AnalysisField; import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; import com.oracle.graal.pointsto.meta.AnalysisType; -import com.oracle.graal.pointsto.meta.AnalysisUniverse; import com.oracle.svm.core.annotate.Delete; import sun.reflect.generics.repository.AbstractRepository; @@ -50,6 +49,7 @@ import sun.reflect.generics.repository.FieldRepository; import sun.reflect.generics.repository.GenericDeclRepository; import sun.reflect.generics.repository.MethodRepository; +import sun.reflect.generics.scope.AbstractScope; public class ReflectionObjectReplacer implements Function { private final AnalysisMetaAccess metaAccess; @@ -80,7 +80,8 @@ public boolean equals(Object obj) { @Override public Object apply(Object original) { - if (original instanceof AccessibleObject || original instanceof Parameter || original instanceof AbstractRepository) { + if (original instanceof AccessibleObject || original instanceof Parameter || + original instanceof AbstractRepository || original instanceof AbstractScope) { if (scanned.add(new Identity(original))) { scan(original); } @@ -115,7 +116,7 @@ private void scan(Object original) { if (!analysisField.isUnsafeAccessed()) { analysisField.registerAsAccessed(); - analysisField.registerAsUnsafeAccessed((AnalysisUniverse) metaAccess.getUniverse()); + analysisField.registerAsUnsafeAccessed(); } } } @@ -182,5 +183,22 @@ private void scan(Object original) { classRepository.getSuperclass(); classRepository.getSuperInterfaces(); } + if (original instanceof AbstractScope) { + AbstractScope abstractScope = (AbstractScope) original; + /* + * Lookup a type variable in the scope to trigger creation of + * sun.reflect.generics.scope.AbstractScope.enclosingScope. The looked-up value is not + * important, we just want to trigger creation of lazy internal state. The same eager + * initialization is triggered by + * sun.reflect.generics.repository.MethodRepository.getReturnType() called above, + * however if the AbstractScope is seen first by the heap scanner then a `null` value + * will be snapshotted for the `enclosingScope`. + */ + try { + abstractScope.lookup(""); + } catch (UnsatisfiedLinkError | NoClassDefFoundError e) { + // The lookup calls Class.getEnclosingClass() which may fail. + } + } } } diff --git a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/proxy/hosted/DynamicProxyFeature.java b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/proxy/hosted/DynamicProxyFeature.java index d32ddb8c634b..a26e6e5fb0fd 100644 --- a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/proxy/hosted/DynamicProxyFeature.java +++ b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/proxy/hosted/DynamicProxyFeature.java @@ -24,6 +24,9 @@ */ package com.oracle.svm.reflect.proxy.hosted; +// Checkstyle: allow reflection + +import java.lang.reflect.Field; import java.util.Collections; import java.util.List; @@ -37,6 +40,7 @@ import com.oracle.svm.core.jdk.proxy.DynamicProxyRegistry; import com.oracle.svm.hosted.ConfigurationTypeResolver; import com.oracle.svm.hosted.FallbackFeature; +import com.oracle.svm.hosted.FeatureImpl.DuringAnalysisAccessImpl; import com.oracle.svm.hosted.FeatureImpl.DuringSetupAccessImpl; import com.oracle.svm.hosted.ImageClassLoader; import com.oracle.svm.hosted.NativeImageOptions; @@ -47,6 +51,7 @@ @AutomaticFeature public final class DynamicProxyFeature implements Feature { private int loadedConfigurations; + private Field proxyCacheField; @Override public List> getRequiredFeatures() { @@ -67,6 +72,8 @@ public void duringSetup(DuringSetupAccess a) { loadedConfigurations = ConfigurationParserUtils.parseAndRegisterConfigurations(parser, imageClassLoader, "dynamic proxy", ConfigurationFiles.Options.DynamicProxyConfigurationFiles, ConfigurationFiles.Options.DynamicProxyConfigurationResources, ConfigurationFile.DYNAMIC_PROXY.getFileName()); + + proxyCacheField = access.findField(DynamicProxySupport.class, "proxyCache"); } private static ProxyRegistry proxyRegistry() { @@ -79,8 +86,10 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { } @Override - public void duringAnalysis(DuringAnalysisAccess access) { - proxyRegistry().flushConditionalConfiguration(access); + public void duringAnalysis(DuringAnalysisAccess a) { + DuringAnalysisAccessImpl access = (DuringAnalysisAccessImpl) a; + access.rescanField(ImageSingletons.lookup(DynamicProxyRegistry.class), proxyCacheField); + proxyRegistry().flushConditionalConfiguration(a); } @Override diff --git a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/serialize/hosted/SerializationFeature.java b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/serialize/hosted/SerializationFeature.java index ce31066570ff..b0acaca0f7d4 100644 --- a/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/serialize/hosted/SerializationFeature.java +++ b/substratevm/src/com.oracle.svm.reflect/src/com/oracle/svm/reflect/serialize/hosted/SerializationFeature.java @@ -99,6 +99,14 @@ public void duringSetup(DuringSetupAccess a) { @Override public void beforeAnalysis(BeforeAnalysisAccess access) { serializationBuilder.flushConditionalConfiguration(access); + + try { + ImageClassLoader loader = ((FeatureImpl.BeforeAnalysisAccessImpl) access).getImageClassLoader(); + /* Ensure SharedSecrets.javaObjectInputStreamAccess is initialized before scanning. */ + loader.forName("java.io.ObjectInputStream", true); + } catch (ClassNotFoundException e) { + VMError.shouldNotReachHere(e); + } } @Override diff --git a/substratevm/src/com.oracle.svm.truffle.tck/src/com/oracle/svm/truffle/tck/WhiteListParser.java b/substratevm/src/com.oracle.svm.truffle.tck/src/com/oracle/svm/truffle/tck/WhiteListParser.java index 6d3c23b1f18c..ada994664bf9 100644 --- a/substratevm/src/com.oracle.svm.truffle.tck/src/com/oracle/svm/truffle/tck/WhiteListParser.java +++ b/substratevm/src/com.oracle.svm.truffle.tck/src/com/oracle/svm/truffle/tck/WhiteListParser.java @@ -192,12 +192,12 @@ private AnalysisType resolve(String type) throws UnsupportedPlatformException { private void verifySupportedOnActivePlatform(Class clz) throws UnsupportedPlatformException { AnalysisUniverse universe = bb.getUniverse(); Package pkg = clz.getPackage(); - if (pkg != null && !universe.hostVM().platformSupported(universe, pkg)) { + if (pkg != null && !universe.hostVM().platformSupported(pkg)) { throw new UnsupportedPlatformException(clz.getPackage()); } Class current = clz; do { - if (!universe.hostVM().platformSupported(universe, current)) { + if (!universe.hostVM().platformSupported(current)) { throw new UnsupportedPlatformException(current); } current = current.getEnclosingClass(); diff --git a/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/TruffleBaseFeature.java b/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/TruffleBaseFeature.java index 3ea37735132f..f5c3777e4044 100644 --- a/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/TruffleBaseFeature.java +++ b/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/TruffleBaseFeature.java @@ -155,6 +155,10 @@ public boolean getAsBoolean() { private boolean profilingEnabled; // Checkstyle: resume + private Field layoutInfoMapField; + private Field layoutMapField; + private Field uncachedDispatchField; + private static void initializeTruffleReflectively(ClassLoader imageClassLoader) { invokeStaticMethod("com.oracle.truffle.api.impl.Accessor", "getTVMCI", Collections.emptyList()); invokeStaticMethod("com.oracle.truffle.polyglot.LanguageCache", "initializeNativeImageState", @@ -209,6 +213,9 @@ public void afterRegistration(AfterRegistrationAccess a) { invokeStaticMethod("com.oracle.truffle.api.library.LibraryFactory", "reinitializeNativeImageState", Collections.emptyList()); + // pre-initialize TruffleLogger$LoggerCache.INSTANCE + invokeStaticMethod("com.oracle.truffle.api.TruffleLogger$LoggerCache", "getInstance", Collections.emptyList()); + profilingEnabled = false; } @@ -288,8 +295,12 @@ public void duringSetup(DuringSetupAccess access) { } GraalProviderObjectReplacements providerReplacements = ImageSingletons.lookup(RuntimeGraalSetup.class) .getProviderObjectReplacements(metaAccess); - graalObjectReplacer = new GraalObjectReplacer(config.getUniverse(), metaAccess, providerReplacements); + graalObjectReplacer = new GraalObjectReplacer(config, config.getUniverse(), metaAccess, providerReplacements); access.registerObjectReplacer(this::replaceNodeFieldAccessor); + + layoutInfoMapField = config.findField("com.oracle.truffle.object.DefaultLayout$LayoutInfo", "LAYOUT_INFO_MAP"); + layoutMapField = config.findField("com.oracle.truffle.object.DefaultLayout", "LAYOUT_MAP"); + uncachedDispatchField = config.findField(LibraryFactory.class, "uncachedDispatch"); } @SuppressWarnings("deprecation") @@ -346,25 +357,41 @@ private static void registerTruffleLibrariesAsInHeap(DuringAnalysisAccess access } @Override - public void duringAnalysis(DuringAnalysisAccess access) { - StaticObjectSupport.duringAnalysis(access); + public void duringAnalysis(DuringAnalysisAccess a) { + DuringAnalysisAccessImpl access = (DuringAnalysisAccessImpl) a; + + StaticObjectSupport.duringAnalysis(a); for (Class clazz : access.reachableSubtypes(com.oracle.truffle.api.nodes.Node.class)) { registerUnsafeAccess(access, clazz.asSubclass(com.oracle.truffle.api.nodes.Node.class)); - AnalysisType type = ((DuringAnalysisAccessImpl) access).getMetaAccess().lookupJavaType(clazz); + AnalysisType type = access.getMetaAccess().lookupJavaType(clazz); if (type.isInstantiated()) { graalObjectReplacer.createType(type); } } - for (AnalysisType type : ((DuringAnalysisAccessImpl) access).getBigBang().getUniverse().getTypes()) { - if (!access.isReachable(type.getJavaClass())) { + for (AnalysisType type : access.getBigBang().getUniverse().getTypes()) { + if (!a.isReachable(type.getJavaClass())) { continue; } - initializeTruffleLibrariesAtBuildTime(type); + initializeTruffleLibrariesAtBuildTime(access, type); initializeDynamicObjectLayouts(type); } + // access.rescanRoot("com.oracle.truffle.api.library.LibraryFactory$ResolvedDispatch", + // "REGISTRY"); + // access.rescanRoot("com.oracle.truffle.api.library.LibraryFactory$ResolvedDispatch", + // "CACHE"); + access.rescanRoot(layoutInfoMapField); + access.rescanRoot(layoutMapField); + // access.rescanRoot("com.oracle.truffle.polyglot.PolyglotEngineImpl", "ENGINES"); + // access.rescanRoot("com.oracle.truffle.api.TruffleLogger$LoggerCache", "INSTANCE"); + + // Object instance = access.rescanRoot("com.oracle.truffle.api.TruffleLogger$LoggerCache", + // "INSTANCE"); + // access.rescanField(instance, instance.getClass(), "loggers"); + // Object root = access.rescanField(instance, instance.getClass(), "root"); + // access.rescanField(root, root.getClass(), "children"); } @Override @@ -424,15 +451,22 @@ private void registerUnsafeAccess(DuringAnalysisAccess access, * * @see #registerTruffleLibrariesAsInHeap */ - private static void initializeTruffleLibrariesAtBuildTime(AnalysisType type) { + private void initializeTruffleLibrariesAtBuildTime(DuringAnalysisAccessImpl access, AnalysisType type) { if (type.isAnnotationPresent(GenerateLibrary.class)) { /* Eagerly resolve library type. */ - LibraryFactory.resolve(type.getJavaClass().asSubclass(Library.class)); + LibraryFactory factory = LibraryFactory.resolve(type.getJavaClass().asSubclass(Library.class)); + /* Trigger computation, then rescan uncachedDispatch. */ + factory.getUncached(); + access.rescanField(factory, uncachedDispatchField); } if (type.getDeclaredAnnotationsByType(ExportLibrary.class).length != 0) { /* Eagerly resolve receiver type. */ - invokeStaticMethod("com.oracle.truffle.api.library.LibraryFactory$ResolvedDispatch", "lookup", + Object dispatch = invokeStaticMethod("com.oracle.truffle.api.library.LibraryFactory$ResolvedDispatch", "lookup", Collections.singleton(Class.class), type.getJavaClass()); + // access.rescanField(dispatch, + // "com.oracle.truffle.api.library.LibraryFactory$ResolvedDispatch", + // "uncachedDispatch"); + access.rescanObject(dispatch); } } @@ -444,8 +478,9 @@ private void initializeDynamicObjectLayouts(AnalysisType type) { Class javaClass = type.getJavaClass(); if (DynamicObject.class.isAssignableFrom(javaClass) && dynamicObjectClasses.add(javaClass)) { // Force layout initialization. - com.oracle.truffle.api.object.Layout.newLayout().type(javaClass.asSubclass(DynamicObject.class)) - .build(); + // Object layout = + com.oracle.truffle.api.object.Layout.newLayout().type(javaClass.asSubclass(DynamicObject.class)).build(); + // access.rescanObject(layout); } } } @@ -726,6 +761,11 @@ public static final class OffsetTransformer implements RecomputeFieldValue.Custo // Checkstyle: resume } + @Override + public RecomputeFieldValue.ValueAvailability valueAvailability() { + return RecomputeFieldValue.ValueAvailability.AfterAnalysis; + } + @Override public Object transform(MetaAccessProvider metaAccess, ResolvedJavaField original, ResolvedJavaField annotated, Object receiver, Object originalValue) { diff --git a/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/TruffleFeature.java b/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/TruffleFeature.java index 0ce94cf5869b..c5d5c8765099 100644 --- a/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/TruffleFeature.java +++ b/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/TruffleFeature.java @@ -364,6 +364,13 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { */ TruffleBaseFeature.invokeStaticMethod("com.oracle.truffle.polyglot.PolyglotEngineImpl", "resetFallbackEngine", Collections.emptyList()); TruffleBaseFeature.preInitializeEngine(); + + try { + /* Ensure org.graalvm.polyglot.io.IOHelper.IMPL is initialized. */ + ((FeatureImpl.BeforeAnalysisAccessImpl) access).getImageClassLoader().forName("org.graalvm.polyglot.io.IOHelper", true); + } catch (ClassNotFoundException e) { + throw VMError.shouldNotReachHere(e); + } } static class TruffleParsingInlineInvokePlugin implements InlineInvokePlugin {