diff --git a/compiler/src/org.graalvm.compiler.java/src/org/graalvm/compiler/java/BytecodeParser.java b/compiler/src/org.graalvm.compiler.java/src/org/graalvm/compiler/java/BytecodeParser.java index 8e5ca99e96ee..befd6230a3ba 100644 --- a/compiler/src/org.graalvm.compiler.java/src/org/graalvm/compiler/java/BytecodeParser.java +++ b/compiler/src/org.graalvm.compiler.java/src/org/graalvm/compiler/java/BytecodeParser.java @@ -1595,6 +1595,14 @@ protected GuardingNode maybeEmitExplicitStoreCheck(ValueNode array, JavaKind ele return emitBytecodeExceptionCheck(condition, true, BytecodeExceptionKind.ARRAY_STORE, value); } + protected ValueNode maybeEmitIncompatibleClassChangeErrorCheck(ValueNode receiver, InvokeKind invokeKind, ResolvedJavaType referencedType) { + if (invokeKind != InvokeKind.Interface || receiver.getStackKind() != JavaKind.Object || !needsIncompatibleClassChangeErrorCheck()) { + return receiver; + } + GraalError.guarantee(referencedType != null, "Interface calls must have a referenceType"); + return emitIncompatibleClassChangeCheck(receiver, referencedType); + } + @Override public AbstractBeginNode emitBytecodeExceptionCheck(LogicNode condition, boolean passingOnTrue, BytecodeExceptionKind exceptionKind, ValueNode... arguments) { AbstractBeginNode result = GraphBuilderContext.super.emitBytecodeExceptionCheck(condition, passingOnTrue, exceptionKind, arguments); @@ -1664,7 +1672,7 @@ void genInvokeStatic(JavaMethod target) { } ValueNode[] args = frameState.popArguments(resolvedTarget.getSignature().getParameterCount(false)); - Invoke invoke = appendInvoke(InvokeKind.Static, resolvedTarget, args); + Invoke invoke = appendInvoke(InvokeKind.Static, resolvedTarget, args, null); if (invoke != null && classInit[0] != null) { invoke.setClassInit(classInit[0]); } @@ -1689,7 +1697,7 @@ protected void genInvokeInterface(int cpi, int opcode) { protected void genInvokeInterface(JavaType referencedType, JavaMethod target) { if (callTargetIsResolved(target) && (referencedType == null || referencedType instanceof ResolvedJavaType)) { ValueNode[] args = frameState.popArguments(target.getSignature().getParameterCount(true)); - Invoke invoke = appendInvoke(InvokeKind.Interface, (ResolvedJavaMethod) target, args); + Invoke invoke = appendInvoke(InvokeKind.Interface, (ResolvedJavaMethod) target, args, (ResolvedJavaType) referencedType); if (invoke != null) { invoke.callTarget().setReferencedType((ResolvedJavaType) referencedType); } @@ -1732,7 +1740,7 @@ protected void genInvokeVirtual(ResolvedJavaMethod resolvedTarget) { } ValueNode[] args = frameState.popArguments(resolvedTarget.getSignature().getParameterCount(true)); - appendInvoke(InvokeKind.Virtual, resolvedTarget, args); + appendInvoke(InvokeKind.Virtual, resolvedTarget, args, null); } private boolean genDynamicInvokeHelper(ResolvedJavaMethod target, int cpi, int opcode) { @@ -1751,9 +1759,9 @@ private boolean genDynamicInvokeHelper(ResolvedJavaMethod target, int cpi, int o boolean hasReceiver = (opcode == INVOKEDYNAMIC) ? false : !target.isStatic(); ValueNode[] args = frameState.popArguments(target.getSignature().getParameterCount(hasReceiver)); if (hasReceiver) { - appendInvoke(InvokeKind.Virtual, target, args); + appendInvoke(InvokeKind.Virtual, target, args, null); } else { - appendInvoke(InvokeKind.Static, target, args); + appendInvoke(InvokeKind.Static, target, args, null); } return true; @@ -1769,7 +1777,7 @@ void genInvokeSpecial(JavaMethod target) { assert target != null; assert target.getSignature() != null; ValueNode[] args = frameState.popArguments(target.getSignature().getParameterCount(true)); - appendInvoke(InvokeKind.Special, (ResolvedJavaMethod) target, args); + appendInvoke(InvokeKind.Special, (ResolvedJavaMethod) target, args, null); } else { handleUnresolvedInvoke(target, InvokeKind.Special); } @@ -1816,11 +1824,16 @@ public JavaType getInvokeReturnType() { @Override public Invoke handleReplacedInvoke(InvokeKind invokeKind, ResolvedJavaMethod targetMethod, ValueNode[] args, boolean inlineEverything) { + GraalError.guarantee(invokeKind != InvokeKind.Interface, "Interface invoke needs a referencedType"); + return handleReplacedInvoke(invokeKind, targetMethod, args, inlineEverything, null); + } + + public Invoke handleReplacedInvoke(InvokeKind invokeKind, ResolvedJavaMethod targetMethod, ValueNode[] args, boolean inlineEverything, ResolvedJavaType referencedType) { boolean previous = forceInliningEverything; forceInliningEverything = previous || inlineEverything; try { setBciCanBeDuplicated(true); - return appendInvoke(invokeKind, targetMethod, args); + return appendInvoke(invokeKind, targetMethod, args, referencedType); } finally { setBciCanBeDuplicated(false); forceInliningEverything = previous; @@ -1834,7 +1847,20 @@ public void handleReplacedInvoke(CallTargetNode callTarget, JavaKind resultType) createNonInlinedInvoke(exceptionEdgeAction, bci(), callTarget, resultType); } - protected Invoke appendInvoke(InvokeKind initialInvokeKind, ResolvedJavaMethod initialTargetMethod, ValueNode[] args) { + protected Invoke appendInvoke(InvokeKind initialInvokeKind, ResolvedJavaMethod initialTargetMethod, ValueNode[] args, ResolvedJavaType referencedType) { + if (!parsingIntrinsic() && DeoptALot.getValue(options)) { + append(new DeoptimizeNode(DeoptimizationAction.None, RuntimeConstraint)); + JavaKind resultType = initialTargetMethod.getSignature().getReturnKind(); + frameState.pushReturn(resultType, ConstantNode.defaultForKind(resultType, graph)); + return null; + } + + if (initialInvokeKind.hasReceiver()) { + args[0] = maybeEmitExplicitNullCheck(args[0]); + /* This check must be done before any de-virtualization of the invoke. */ + args[0] = maybeEmitIncompatibleClassChangeErrorCheck(args[0], initialInvokeKind, referencedType); + } + ResolvedJavaMethod targetMethod = initialTargetMethod; InvokeKind invokeKind = initialInvokeKind; if (initialInvokeKind.isIndirect()) { @@ -1847,16 +1873,7 @@ protected Invoke appendInvoke(InvokeKind initialInvokeKind, ResolvedJavaMethod i } JavaKind resultType = targetMethod.getSignature().getReturnKind(); - if (!parsingIntrinsic() && DeoptALot.getValue(options)) { - append(new DeoptimizeNode(DeoptimizationAction.None, RuntimeConstraint)); - frameState.pushReturn(resultType, ConstantNode.defaultForKind(resultType, graph)); - return null; - } - JavaType returnType = maybeEagerlyResolve(targetMethod.getSignature().getReturnType(method.getDeclaringClass()), targetMethod.getDeclaringClass()); - if (invokeKind.hasReceiver()) { - args[0] = maybeEmitExplicitNullCheck(args[0]); - } if (initialInvokeKind == InvokeKind.Special && !targetMethod.isConstructor()) { emitCheckForInvokeSuperSpecial(args); @@ -1956,11 +1973,7 @@ protected Invoke appendInvoke(InvokeKind initialInvokeKind, ResolvedJavaMethod i * declared in a interface */ private void emitCheckForDeclaringClassChange(ResolvedJavaType declaringClass, ValueNode[] args) { - ValueNode receiver = args[0]; - TypeReference checkedType = TypeReference.createTrusted(graph.getAssumptions(), declaringClass); - LogicNode condition = genUnique(createInstanceOf(checkedType, receiver, null)); - FixedGuardNode fixedGuard = append(new FixedGuardNode(condition, ClassCastException, None, false)); - args[0] = append(PiNode.create(receiver, StampFactory.object(checkedType, true), fixedGuard)); + args[0] = emitIncompatibleClassChangeCheck(args[0], declaringClass); } /** @@ -1977,14 +1990,22 @@ protected void emitCheckForInvokeSuperSpecial(ValueNode[] args) { ResolvedJavaType callingClass = method.getDeclaringClass(); callingClass = getHostClass(callingClass); if (callingClass.isInterface()) { - ValueNode receiver = args[0]; - TypeReference checkedType = TypeReference.createTrusted(graph.getAssumptions(), callingClass); - LogicNode condition = genUnique(createInstanceOf(checkedType, receiver, null)); - FixedGuardNode fixedGuard = append(new FixedGuardNode(condition, ClassCastException, None, false)); - args[0] = append(PiNode.create(receiver, StampFactory.object(checkedType, true), fixedGuard)); + args[0] = emitIncompatibleClassChangeCheck(args[0], callingClass); } } + protected ValueNode emitIncompatibleClassChangeCheck(ValueNode object, ResolvedJavaType checkedType) { + TypeReference checkedTypeRef = TypeReference.createTrusted(graph.getAssumptions(), checkedType); + LogicNode condition = genUnique(InstanceOfNode.create(checkedTypeRef, object)); + ValueNode guardingNode; + if (needsExplicitException()) { + guardingNode = emitBytecodeExceptionCheck(condition, true, BytecodeExceptionKind.INCOMPATIBLE_CLASS_CHANGE); + } else { + guardingNode = append(new FixedGuardNode(condition, ClassCastException, None, false)); + } + return append(PiNode.create(object, StampFactory.object(checkedTypeRef, true), guardingNode)); + } + @SuppressWarnings("deprecation") private static ResolvedJavaType getHostClass(ResolvedJavaType type) { ResolvedJavaType hostClass = type.getHostClass(); @@ -4723,6 +4744,10 @@ protected boolean needsExplicitStoreCheckException(ValueNode array, ValueNode va return needsExplicitException(); } + protected boolean needsIncompatibleClassChangeErrorCheck() { + return false; + } + @Override public boolean needsExplicitException() { BytecodeExceptionMode exceptionMode = graphBuilderConfig.getBytecodeExceptionMode(); diff --git a/compiler/src/org.graalvm.compiler.nodes/src/org/graalvm/compiler/nodes/extended/BytecodeExceptionNode.java b/compiler/src/org.graalvm.compiler.nodes/src/org/graalvm/compiler/nodes/extended/BytecodeExceptionNode.java index 70dd148a0f14..afff818960c6 100644 --- a/compiler/src/org.graalvm.compiler.nodes/src/org/graalvm/compiler/nodes/extended/BytecodeExceptionNode.java +++ b/compiler/src/org.graalvm.compiler.nodes/src/org/graalvm/compiler/nodes/extended/BytecodeExceptionNode.java @@ -101,6 +101,11 @@ public enum BytecodeExceptionKind { */ ARRAY_STORE(1, ArrayStoreException.class), + /** + * Represents a {@link IncompatibleClassChangeError}. No arguments are allowed. + */ + INCOMPATIBLE_CLASS_CHANGE(0, IncompatibleClassChangeError.class), + /** Represents a {@link AssertionError} without arguments. */ ASSERTION_ERROR_NULLARY(0, AssertionError.class), 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 3c4f03c350be..8315b143ea76 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 @@ -129,4 +129,9 @@ default void onTypeInitialized(AnalysisType type) { default void afterAnalysis() { } + + @SuppressWarnings("unused") + default AnalysisMethod fallbackResolveConcreteMethod(AnalysisType resolvingType, AnalysisMethod method) { + return null; + } } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/AccessFieldTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/AccessFieldTypeFlow.java index 207926c7937c..749bbef438f5 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/AccessFieldTypeFlow.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/AccessFieldTypeFlow.java @@ -38,7 +38,7 @@ public abstract class AccessFieldTypeFlow extends TypeFlow { protected AccessFieldTypeFlow(BytecodePosition accessLocation, AnalysisField field) { /* The declared type of a field access node is the field declared type. */ - super(accessLocation, field.getType()); + super(accessLocation, filterUncheckedInterface(field.getType())); this.field = field; } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/ActualParameterTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/ActualParameterTypeFlow.java index 8718bdc08990..14ce838bd70e 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/ActualParameterTypeFlow.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/ActualParameterTypeFlow.java @@ -34,7 +34,7 @@ */ public class ActualParameterTypeFlow extends TypeFlow { public ActualParameterTypeFlow(AnalysisType declaredType) { - super(null, declaredType); + super(null, filterUncheckedInterface(declaredType)); } @Override diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/ActualReturnTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/ActualReturnTypeFlow.java index eb8de0b0e1a3..fac14f47f0fc 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/ActualReturnTypeFlow.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/ActualReturnTypeFlow.java @@ -33,11 +33,11 @@ public class ActualReturnTypeFlow extends TypeFlow { private InvokeTypeFlow invokeFlow; public ActualReturnTypeFlow(AnalysisType declaredType) { - super(null, declaredType); + this(null, declaredType); } public ActualReturnTypeFlow(BytecodePosition source, AnalysisType declaredType) { - super(source, declaredType); + super(source, filterUncheckedInterface(declaredType)); } public ActualReturnTypeFlow(ActualReturnTypeFlow original, MethodFlowsGraph methodFlows) { diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/FieldTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/FieldTypeFlow.java index 96c08c11b8b0..b94a55a02ebf 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/FieldTypeFlow.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/FieldTypeFlow.java @@ -61,7 +61,7 @@ private static TypeState initialFieldState(AnalysisField field) { private volatile FieldFilterTypeFlow filterFlow; public FieldTypeFlow(AnalysisField field, AnalysisType type) { - super(field, type, initialFieldState(field)); + super(field, filterUncheckedInterface(type), initialFieldState(field)); } public FieldTypeFlow(AnalysisField field, AnalysisType type, AnalysisObject object) { diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/FormalParamTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/FormalParamTypeFlow.java index b680fc574ace..3b9b11d07a04 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/FormalParamTypeFlow.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/FormalParamTypeFlow.java @@ -38,7 +38,7 @@ public class FormalParamTypeFlow extends TypeFlow { protected final int position; public FormalParamTypeFlow(BytecodePosition sourcePosition, AnalysisType declaredType, int position) { - super(sourcePosition, declaredType); + super(sourcePosition, filterUncheckedInterface(declaredType)); this.position = position; } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/FormalReturnTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/FormalReturnTypeFlow.java index 57cd968b2e98..59378a6d4688 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/FormalReturnTypeFlow.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/FormalReturnTypeFlow.java @@ -31,7 +31,7 @@ public class FormalReturnTypeFlow extends TypeFlow { public FormalReturnTypeFlow(BytecodePosition source, AnalysisType declaredType) { - super(source, declaredType); + super(source, filterUncheckedInterface(declaredType)); } public FormalReturnTypeFlow(FormalReturnTypeFlow original, MethodFlowsGraph methodFlows) { diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/OffsetLoadTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/OffsetLoadTypeFlow.java index 5c5bdc42b44d..deae88130a82 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/OffsetLoadTypeFlow.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/OffsetLoadTypeFlow.java @@ -144,7 +144,7 @@ public String toString() { public abstract static class AbstractUnsafeLoadTypeFlow extends OffsetLoadTypeFlow { AbstractUnsafeLoadTypeFlow(BytecodePosition loadLocation, AnalysisType objectType, AnalysisType componentType, TypeFlow objectFlow) { - super(loadLocation, objectType, componentType, objectFlow); + super(loadLocation, objectType, filterUncheckedInterface(componentType), objectFlow); } AbstractUnsafeLoadTypeFlow(PointsToAnalysis bb, MethodFlowsGraph methodFlows, AbstractUnsafeLoadTypeFlow original) { diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/OffsetStoreTypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/OffsetStoreTypeFlow.java index 14e219539cd1..4d2050260b80 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/OffsetStoreTypeFlow.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/OffsetStoreTypeFlow.java @@ -183,7 +183,7 @@ public String toString() { public abstract static class AbstractUnsafeStoreTypeFlow extends OffsetStoreTypeFlow { AbstractUnsafeStoreTypeFlow(BytecodePosition storeLocation, AnalysisType objectType, AnalysisType componentType, TypeFlow objectFlow, TypeFlow valueFlow) { - super(storeLocation, objectType, componentType, objectFlow, valueFlow); + super(storeLocation, objectType, filterUncheckedInterface(componentType), objectFlow, valueFlow); } AbstractUnsafeStoreTypeFlow(PointsToAnalysis bb, MethodFlowsGraph methodFlows, OffsetStoreTypeFlow original) { diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/TypeFlow.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/TypeFlow.java index 563438cb6e43..bcdfe0dd8e55 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/TypeFlow.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/TypeFlow.java @@ -568,6 +568,28 @@ public TypeState declaredTypeFilter(PointsToAnalysis bb, TypeState newState) { return TypeState.forIntersection(bb, newState, declaredType.getAssignableTypes(true)); } + /** + * In Java, interface types are not checked by the bytecode verifier. So even when, e.g., a + * method parameter has the declared type Comparable, any Object can be passed in. We therefore + * need to filter out interface types, as well as arrays of interface types, in many places + * where we use the declared type. + * + * Places where interface types need to be filtered: method parameters, method return values, + * and field loads (including unsafe memory loads). + * + * Places where interface types need not be filtered: array element loads (because all array + * stores have an array store check). + */ + public static AnalysisType filterUncheckedInterface(AnalysisType type) { + if (type != null) { + AnalysisType elementalType = type.getElementalType(); + if (elementalType.isInterface()) { + return type.getUniverse().objectType().getArrayClass(type.getArrayDimension()); + } + } + return type; + } + public void update(PointsToAnalysis bb) { TypeState curState = getState(); for (TypeFlow use : getUses()) { 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 42e1d9c44bc5..508bdce97e94 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 @@ -314,6 +314,10 @@ public AnalysisType getArrayClass(int dim) { return result; } + public int getArrayDimension() { + return dimension; + } + public void cleanupAfterAnalysis() { instantiatedTypes = null; instantiatedTypesNonNull = null; @@ -1011,7 +1015,7 @@ public AnalysisType getComponentType() { } @Override - public ResolvedJavaType getElementalType() { + public AnalysisType getElementalType() { return elementalType; } @@ -1042,6 +1046,9 @@ public AnalysisMethod resolveConcreteMethod(ResolvedJavaMethod method, ResolvedJ ResolvedJavaType substCallerType = substMethod.getDeclaringClass(); Object newResolvedMethod = universe.lookup(wrapped.resolveConcreteMethod(substMethod, substCallerType)); + if (newResolvedMethod == null) { + newResolvedMethod = getUniverse().getBigbang().fallbackResolveConcreteMethod(this, (AnalysisMethod) method); + } if (newResolvedMethod == null) { newResolvedMethod = NULL_METHOD; } @@ -1250,7 +1257,7 @@ public ResolvedJavaType getHostClass() { return universe.lookup(wrapped.getHostClass()); } - AnalysisUniverse getUniverse() { + public AnalysisUniverse getUniverse() { return universe; } 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 3d2580cb9ac7..d1874546aa2f 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 @@ -434,7 +434,7 @@ static class WorkListEntry { } private boolean suppressType(AnalysisType type) { - AnalysisType elementalType = (AnalysisType) type.getElementalType(); + AnalysisType elementalType = type.getElementalType(); String elementalTypeName = elementalType.toJavaName(true); if (expandTypeMatcher.matches(elementalTypeName)) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/NonSnippetLowerings.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/NonSnippetLowerings.java index 158c8f2ed9ea..2e2314cdb889 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/NonSnippetLowerings.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/snippets/NonSnippetLowerings.java @@ -143,6 +143,7 @@ protected NonSnippetLowerings(RuntimeConfiguration runtimeConfig, Predicate t.toJavaName(true)).collect(Collectors.joining(",")); - } - private void checkUniverse() { - if (bb instanceof NativeImagePointsToAnalysis) { - NativeImagePointsToAnalysis bigbang = (NativeImagePointsToAnalysis) this.bb; - /* - * Check that the type states for method parameters and fields are compatible with the - * declared type. This is required for interface types because interfaces are not - * trusted according to the Java language specification, but we trust all interface - * types (see HostedType.isTrustedInterfaceType) - * - * TODO Enable checks for non-interface types too. - */ - for (AnalysisMethod m : aUniverse.getMethods()) { - PointsToAnalysisMethod method = PointsToAnalysis.assertPointsToAnalysisMethod(m); - for (TypeFlow parameter : method.getTypeFlow().getParameters()) { - TypeState parameterState = method.getTypeFlow().foldTypeFlow(bigbang, parameter); - if (parameterState != null) { - AnalysisType declaredType = parameter.getDeclaredType(); - if (declaredType.isInterface()) { - TypeState declaredTypeState = declaredType.getAssignableTypes(true); - parameterState = TypeState.forSubtraction(bigbang, parameterState, declaredTypeState); - if (!parameterState.isEmpty()) { - String methodKey = method.format("%H.%n(%p)"); - bigbang.getUnsupportedFeatures().addMessage(methodKey, method, - "Parameter " + ((FormalParamTypeFlow) parameter).position() + " of " + methodKey + " has declared type " + declaredType.toJavaName(true) + - ", with assignable types: " + format(bb, declaredTypeState) + - ", which is incompatible with analysis inferred types: " + format(bb, parameterState) + "."); - } - } - } - } - } - for (AnalysisField field : aUniverse.getFields()) { - TypeState state = field.getTypeState(); - if (state != null) { - AnalysisType declaredType = field.getType(); - if (declaredType.isInterface()) { - TypeState declaredTypeState = declaredType.getAssignableTypes(true); - state = TypeState.forSubtraction(bigbang, state, declaredTypeState); - if (!state.isEmpty()) { - String fieldKey = field.format("%H.%n"); - bigbang.getUnsupportedFeatures().addMessage(fieldKey, null, - "Field " + fieldKey + " has declared type " + declaredType.toJavaName(true) + - ", with assignable types: " + format(bb, declaredTypeState) + - ", which is incompatible with analysis inferred types: " + format(bb, state) + "."); - } - } - } - } - } - if (SubstrateOptions.VerifyNamingConventions.getValue()) { for (AnalysisMethod method : aUniverse.getMethods()) { if ((method.isInvoked() || method.isReachable()) && method.getAnnotation(Fold.class) == null) { 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 f33005d5fcfe..6ee6be5530d4 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 @@ -24,6 +24,9 @@ */ package com.oracle.svm.hosted.analysis; +import java.lang.reflect.Executable; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; import java.util.concurrent.ForkJoinPool; import org.graalvm.compiler.options.OptionValues; @@ -43,6 +46,7 @@ import com.oracle.svm.core.graal.meta.SubstrateReplacements; import com.oracle.svm.hosted.HostedConfiguration; import com.oracle.svm.hosted.SVMHost; +import com.oracle.svm.hosted.code.IncompatibleClassChangeFallbackMethod; import com.oracle.svm.hosted.meta.HostedType; import com.oracle.svm.hosted.substitute.AnnotationSubstitutionProcessor; @@ -140,4 +144,71 @@ public boolean trackConcreteAnalysisObjects(AnalysisType type) { public SubstrateReplacements getReplacements() { return (SubstrateReplacements) super.getReplacements(); } + + /** See {@link IncompatibleClassChangeFallbackMethod} for documentation. */ + @Override + public AnalysisMethod fallbackResolveConcreteMethod(AnalysisType resolvingType, AnalysisMethod method) { + if (!resolvingType.isAbstract() && !resolvingType.isInterface() && !method.isStatic() && method.getDeclaringClass().isAssignableFrom(resolvingType)) { + /* + * We are resolving an instance method for a concrete (non-abstract) class that is a + * subtype of the method's declared type. So this is a method invocation that can happen + * at run time, and we need to return a method that throws an exception when being + * executed. + */ + + if (method.getWrapped() instanceof IncompatibleClassChangeFallbackMethod) { + /* + * We are re-resolving a method that we already processed. Nothing to do, we already + * have the appropriate fallback method. + */ + return method; + } + return getUniverse().lookup(new IncompatibleClassChangeFallbackMethod(resolvingType.getWrapped(), method.getWrapped(), findResolutionError(resolvingType, method.getJavaMethod()))); + } + return super.fallbackResolveConcreteMethod(resolvingType, method); + } + + /** + * Finding the correct exception that needs to be thrown at run time is a bit tricky, since + * JVMCI does not report that information back when method resolution fails. We need to look + * down the class hierarchy to see if there would be an appropriate method with a matching + * signature which is just not accessible. + * + * We do all the method lookups (to search for a method with the same signature as searchMethod) + * using reflection and not JVMCI because the lookup can throw all sorts of errors, and we want + * to ignore the errors without any possible side effect on AnalysisType and AnalysisMethod. + */ + private static Class findResolutionError(AnalysisType resolvingType, Executable searchMethod) { + if (searchMethod != null) { + Class[] searchSignature = searchMethod.getParameterTypes(); + for (Class cur = resolvingType.getJavaClass(); cur != null; cur = cur.getSuperclass()) { + Method found; + try { + found = cur.getDeclaredMethod(searchMethod.getName(), searchSignature); + } catch (Throwable ex) { + /* + * Method does not exist, a linkage error was thrown, or something else random + * is wrong with the class files. Ignore this class. + */ + continue; + } + if (Modifier.isAbstract(found.getModifiers()) || Modifier.isPrivate(found.getModifiers()) || Modifier.isStatic(found.getModifiers())) { + /* + * We found a method with a matching signature, but it does not have an + * implementation, or it is a private / static method that does not count from + * the point of view of method resolution. + */ + return AbstractMethodError.class; + } else { + /* + * We found a method with a matching signature, but it must have the wrong + * access modifier (otherwise method resolution would have returned it). + */ + return IllegalAccessError.class; + } + } + } + /* Not matching method found at all. */ + return AbstractMethodError.class; + } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/EntryPointCallStubMethod.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/EntryPointCallStubMethod.java index 40971e9359c9..613c9f4ed749 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/EntryPointCallStubMethod.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/EntryPointCallStubMethod.java @@ -34,10 +34,10 @@ import jdk.vm.ci.meta.ResolvedJavaType; import jdk.vm.ci.meta.Signature; -public abstract class EntryPointCallStubMethod extends NonBytecodeStaticMethod { +public abstract class EntryPointCallStubMethod extends NonBytecodeMethod { protected EntryPointCallStubMethod(String name, ResolvedJavaType declaringClass, Signature signature, ConstantPool constantPool) { - super(name, declaringClass, signature, constantPool); + super(name, true, declaringClass, signature, constantPool); } /** diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/FactoryMethod.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/FactoryMethod.java index 8fc68906730a..47f50b64cebc 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/FactoryMethod.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/FactoryMethod.java @@ -50,13 +50,13 @@ import jdk.vm.ci.meta.ResolvedJavaType; import jdk.vm.ci.meta.Signature; -public final class FactoryMethod extends NonBytecodeStaticMethod { +public final class FactoryMethod extends NonBytecodeMethod { private final ResolvedJavaMethod targetConstructor; private final boolean throwAllocatedObject; FactoryMethod(ResolvedJavaMethod targetConstructor, ResolvedJavaType declaringClass, Signature signature, ConstantPool constantPool, boolean throwAllocatedObject) { - super(SubstrateUtil.uniqueStubName(targetConstructor), declaringClass, signature, constantPool); + super(SubstrateUtil.uniqueStubName(targetConstructor), true, declaringClass, signature, constantPool); this.targetConstructor = targetConstructor; this.throwAllocatedObject = throwAllocatedObject; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/IncompatibleClassChangeFallbackMethod.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/IncompatibleClassChangeFallbackMethod.java new file mode 100644 index 000000000000..de2b05a85fb3 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/IncompatibleClassChangeFallbackMethod.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2022, 2022, 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.code; + +import org.graalvm.compiler.debug.DebugContext; +import org.graalvm.compiler.nodes.CallTargetNode.InvokeKind; +import org.graalvm.compiler.nodes.StructuredGraph; +import org.graalvm.compiler.nodes.UnwindNode; +import org.graalvm.compiler.nodes.java.AbstractNewObjectNode; +import org.graalvm.compiler.nodes.java.NewInstanceNode; + +import com.oracle.graal.pointsto.meta.HostedProviders; +import com.oracle.svm.hosted.analysis.NativeImagePointsToAnalysis; +import com.oracle.svm.hosted.phases.HostedGraphKit; +import com.oracle.svm.util.ReflectionUtil; + +import jdk.vm.ci.meta.ResolvedJavaMethod; +import jdk.vm.ci.meta.ResolvedJavaType; + +/** + * When interface/virtual call resolution via JVMCI does not return a concrete implementation method + * for a concrete implementation type, then most likely some classes are in an incompatible state, + * i.e., a class was compiled against a different version of a dependent class than the class we got + * on the class path. Since we use vtable calls for all interface/virtual method calls, we cannot + * just leave such unresolved methods as "no method is invoked" in the static analysis and "null" in + * the vtable: that would lead to wrong static analysis results, wrong devirtualizations, and/or + * vtable calls to uninitialized vtable slots. + * + * The solution is to always resolve a concrete synthetic fallback method, which then throws the + * correct error at run time. This "fallback resolution" is implemented in + * {@link NativeImagePointsToAnalysis#fallbackResolveConcreteMethod}. + */ +public final class IncompatibleClassChangeFallbackMethod extends NonBytecodeMethod { + + private final ResolvedJavaMethod original; + private final Class resolutionError; + + public IncompatibleClassChangeFallbackMethod(ResolvedJavaType declaringClass, ResolvedJavaMethod original, Class resolutionError) { + super(original.getName(), false, declaringClass, original.getSignature(), original.getConstantPool()); + this.original = original; + this.resolutionError = resolutionError; + } + + public ResolvedJavaMethod getOriginal() { + return original; + } + + @Override + public StructuredGraph buildGraph(DebugContext debug, ResolvedJavaMethod method, HostedProviders providers, Purpose purpose) { + HostedGraphKit kit = new HostedGraphKit(debug, providers, method); + ResolvedJavaMethod constructor = providers.getMetaAccess().lookupJavaMethod(ReflectionUtil.lookupConstructor(resolutionError)); + + AbstractNewObjectNode newInstance = kit.append(new NewInstanceNode(constructor.getDeclaringClass(), true)); + kit.createInvokeWithExceptionAndUnwind(constructor, InvokeKind.Special, kit.getFrameState(), kit.bci(), newInstance); + kit.append(new UnwindNode(newInstance)); + return kit.finalizeGraph(); + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/NonBytecodeStaticMethod.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/NonBytecodeMethod.java similarity index 93% rename from substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/NonBytecodeStaticMethod.java rename to substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/NonBytecodeMethod.java index 2bd8496b1016..a54180cbf445 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/NonBytecodeStaticMethod.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/code/NonBytecodeMethod.java @@ -48,7 +48,7 @@ * Abstract base class for methods with generated Graal IR, i.e., methods that do not originate from * bytecode. */ -public abstract class NonBytecodeStaticMethod implements GraphProvider, ResolvedJavaMethod, AnnotationWrapper { +public abstract class NonBytecodeMethod implements GraphProvider, ResolvedJavaMethod, AnnotationWrapper { /** * Line numbers are bogus because this is generated code, but we need to include them in our @@ -58,14 +58,16 @@ public abstract class NonBytecodeStaticMethod implements GraphProvider, Resolved private static final LineNumberTable lineNumberTable = new LineNumberTable(new int[]{1}, new int[]{0}); private final String name; + private final boolean isStatic; private final ResolvedJavaType declaringClass; private final Signature signature; private final ConstantPool constantPool; private StackTraceElement stackTraceElement; - public NonBytecodeStaticMethod(String name, ResolvedJavaType declaringClass, Signature signature, ConstantPool constantPool) { + public NonBytecodeMethod(String name, boolean isStatic, ResolvedJavaType declaringClass, Signature signature, ConstantPool constantPool) { this.name = name; + this.isStatic = isStatic; this.declaringClass = declaringClass; this.signature = signature; this.constantPool = constantPool; @@ -153,7 +155,7 @@ public boolean isConstructor() { @Override public boolean canBeStaticallyBound() { - return true; + return isStatic; } @Override @@ -236,6 +238,6 @@ public AnnotatedElement getAnnotationRoot() { @Override public int getModifiers() { - return Modifier.PUBLIC | Modifier.STATIC; + return Modifier.PUBLIC | (isStatic ? Modifier.STATIC : 0); } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java index 49868b11a602..662d14223503 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java @@ -425,7 +425,7 @@ private static void addField(Field reflField, boolean writable, DuringAnalysisAc // For convenience, make the array type reachable if its elemental type becomes // such, allowing the array creation via JNI without an explicit reflection config. access.registerReachabilityHandler(a -> access.getBigBang().registerTypeAsAllocated(fieldType, "Is accessed via JNI."), - ((AnalysisType) fieldType.getElementalType()).getJavaClass()); + (fieldType.getElementalType()).getJavaClass()); } } else if (field.isStatic() && field.isFinal()) { MaterializedConstantFields.singleton().register(field); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIJavaCallWrapperMethod.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIJavaCallWrapperMethod.java index 0877100a2b86..f997ee6fbf65 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIJavaCallWrapperMethod.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIJavaCallWrapperMethod.java @@ -61,7 +61,7 @@ import com.oracle.svm.core.jni.JNIJavaCallWrapperHolder; import com.oracle.svm.core.jni.access.JNIAccessibleMethod; import com.oracle.svm.hosted.code.FactoryMethodSupport; -import com.oracle.svm.hosted.code.NonBytecodeStaticMethod; +import com.oracle.svm.hosted.code.NonBytecodeMethod; import com.oracle.svm.hosted.code.SimpleSignature; import com.oracle.svm.hosted.meta.HostedMetaAccess; import com.oracle.svm.util.ReflectionUtil; @@ -85,7 +85,7 @@ * @see Java 11 * JNI functions documentation */ -public class JNIJavaCallWrapperMethod extends NonBytecodeStaticMethod { +public class JNIJavaCallWrapperMethod extends NonBytecodeMethod { private static final Constructor CLASS_CAST_EXCEPTION_CONSTRUCTOR = ReflectionUtil.lookupConstructor(ClassCastException.class); private static final Constructor INSTANTIATION_EXCEPTION_CONSTRUCTOR = ReflectionUtil.lookupConstructor(InstantiationException.class); @@ -121,7 +121,7 @@ public static SimpleSignature getGeneralizedSignatureForTarget(ResolvedJavaMetho private final Signature targetSignature; protected JNIJavaCallWrapperMethod(SimpleSignature targetSignature, MetaAccessProvider metaAccess, WordTypes wordTypes) { - super("invoke_" + targetSignature.getIdentifier(), metaAccess.lookupJavaType(JNIJavaCallWrapperHolder.class), + super("invoke_" + targetSignature.getIdentifier(), true, metaAccess.lookupJavaType(JNIJavaCallWrapperHolder.class), createSignature(targetSignature, metaAccess, wordTypes), JNIJavaCallWrapperHolder.getConstantPool(metaAccess)); this.targetSignature = targetSignature; } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalysisGraphBuilderPhase.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalysisGraphBuilderPhase.java index 472009b00de1..d8414daaffd7 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalysisGraphBuilderPhase.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalysisGraphBuilderPhase.java @@ -121,7 +121,7 @@ private boolean tryNodePluginForDynamicInvocation(BootstrapMethodIntrospection b for (NodePlugin plugin : graphBuilderConfig.getPlugins().getNodePlugins()) { var result = plugin.convertInvokeDynamic(this, bootstrap); if (result != null) { - appendInvoke(InvokeKind.Static, result.getLeft(), result.getRight()); + appendInvoke(InvokeKind.Static, result.getLeft(), result.getRight(), null); return true; } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/IntrinsifyMethodHandlesInvocationPlugin.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/IntrinsifyMethodHandlesInvocationPlugin.java index 3d6ffea29121..6064b3e03a1d 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/IntrinsifyMethodHandlesInvocationPlugin.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/IntrinsifyMethodHandlesInvocationPlugin.java @@ -895,7 +895,7 @@ private ValueNode node(Node oNode) throws AbortTransplantException { return (ValueNode) tNode; } - private void transplantInvoke(FixedWithNextNode oNode, ResolvedJavaMethod tTargetMethod, InvokeKind invokeKind, ValueNode[] arguments, JavaKind invokeResultKind) { + private void transplantInvoke(InvokeNode oNode, ResolvedJavaMethod tTargetMethod, InvokeKind invokeKind, ValueNode[] arguments, JavaKind invokeResultKind) { maybeEmitClassInitialization(b, invokeKind == InvokeKind.Static, tTargetMethod.getDeclaringClass()); if (invokeResultKind == JavaKind.Void) { @@ -918,7 +918,7 @@ private void transplantInvoke(FixedWithNextNode oNode, ResolvedJavaMethod tTarge } } - b.handleReplacedInvoke(invokeKind, tTargetMethod, arguments, false); + b.handleReplacedInvoke(invokeKind, tTargetMethod, arguments, false, lookup(oNode.callTarget().referencedType())); if (invokeResultKind != JavaKind.Void) { /* diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/SharedGraphBuilderPhase.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/SharedGraphBuilderPhase.java index e8d8cf16238a..6f1696c9c440 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/SharedGraphBuilderPhase.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/SharedGraphBuilderPhase.java @@ -24,12 +24,19 @@ */ package com.oracle.svm.hosted.phases; +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Arrays; import java.util.List; import org.graalvm.compiler.api.replacements.Fold; import org.graalvm.compiler.core.common.calc.Condition; import org.graalvm.compiler.core.common.type.StampPair; +import org.graalvm.compiler.debug.DebugCloseable; import org.graalvm.compiler.graph.Node.NodeIntrinsic; +import org.graalvm.compiler.graph.NodeSourcePosition; import org.graalvm.compiler.java.BciBlockMapping; import org.graalvm.compiler.java.BytecodeParser; import org.graalvm.compiler.java.FrameStateBuilder; @@ -61,7 +68,9 @@ import com.oracle.graal.pointsto.constraints.TypeInstantiationException; import com.oracle.graal.pointsto.constraints.UnresolvedElementException; +import com.oracle.graal.pointsto.infrastructure.OriginalClassProvider; import com.oracle.graal.pointsto.infrastructure.UniverseMetaAccess; +import com.oracle.graal.pointsto.util.GraalAccess; import com.oracle.svm.common.meta.MultiMethod; import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.SubstrateUtil; @@ -354,14 +363,14 @@ public static void replaceWithThrowingAtRuntime(SharedByte var causeCtor = ReflectionUtil.lookupConstructor(cause.getClass(), String.class); ResolvedJavaMethod causeCtorMethod = FactoryMethodSupport.singleton().lookup(metaAccess, metaAccess.lookupJavaMethod(causeCtor), false); ValueNode causeMessageNode = ConstantNode.forConstant(b.getConstantReflection().forString(cause.getMessage()), metaAccess, b.getGraph()); - Invoke causeCtorInvoke = b.appendInvoke(InvokeKind.Static, causeCtorMethod, new ValueNode[]{causeMessageNode}); + Invoke causeCtorInvoke = b.appendInvoke(InvokeKind.Static, causeCtorMethod, new ValueNode[]{causeMessageNode}, null); /* * Invoke method that creates and throws throwable-instance with message and cause */ var errorCtor = ReflectionUtil.lookupConstructor(throwable.getClass(), String.class, Throwable.class); ResolvedJavaMethod throwingMethod = FactoryMethodSupport.singleton().lookup(metaAccess, metaAccess.lookupJavaMethod(errorCtor), true); ValueNode messageNode = ConstantNode.forConstant(b.getConstantReflection().forString(throwable.getMessage()), metaAccess, b.getGraph()); - b.appendInvoke(InvokeKind.Static, throwingMethod, new ValueNode[]{messageNode, causeCtorInvoke.asNode()}); + b.appendInvoke(InvokeKind.Static, throwingMethod, new ValueNode[]{messageNode, causeCtorInvoke.asNode()}, null); b.add(new LoweredDeadEndNode()); } else { replaceWithThrowingAtRuntime(b, throwable.getClass(), throwable.getMessage()); @@ -386,7 +395,7 @@ public static void replaceWithThrowingAtRuntime(SharedBytecodeParser b, Class findResolutionError(ResolvedJavaType declaringType, JavaMethod searchMethod) { + Class[] searchSignature = signatureToClasses(searchMethod); + Class searchReturnType = null; + if (searchMethod.getSignature().getReturnType(null) instanceof ResolvedJavaType) { + searchReturnType = OriginalClassProvider.getJavaClass(GraalAccess.getOriginalSnippetReflection(), (ResolvedJavaType) searchMethod.getSignature().getReturnType(null)); + } + + Class declaringClass = OriginalClassProvider.getJavaClass(GraalAccess.getOriginalSnippetReflection(), declaringType); + for (Class cur = declaringClass; cur != null; cur = cur.getSuperclass()) { + Executable[] methods = null; + try { + if (searchMethod.getName().equals("")) { + methods = cur.getDeclaredConstructors(); + } else { + methods = cur.getDeclaredMethods(); + } + } catch (Throwable ignored) { + /* + * A linkage error was thrown, or something else random is wrong with the class + * files. Ignore this class. + */ + } + if (methods != null) { + for (Executable method : methods) { + if (Arrays.equals(searchSignature, method.getParameterTypes()) && + (method instanceof Constructor || (searchMethod.getName().equals(method.getName()) && searchReturnType == ((Method) method).getReturnType()))) { + if (Modifier.isAbstract(method.getModifiers())) { + return AbstractMethodError.class; + } else { + return IllegalAccessError.class; + } + } + } + } + if (searchMethod.getName().equals("")) { + /* For constructors, do not search in superclasses. */ + break; + } + } + return NoSuchMethodError.class; + } + + private static Class[] signatureToClasses(JavaMethod method) { + int paramCount = method.getSignature().getParameterCount(false); + Class[] result = new Class[paramCount]; + for (int i = 0; i < paramCount; i++) { + JavaType parameterType = method.getSignature().getParameterType(0, null); + if (parameterType instanceof ResolvedJavaType) { + result[i] = OriginalClassProvider.getJavaClass(GraalAccess.getOriginalSnippetReflection(), (ResolvedJavaType) parameterType); + } + } + return result; + } + private void reportUnresolvedElement(String elementKind, String elementAsString) { reportUnresolvedElement(elementKind, elementAsString, null); } @@ -518,6 +592,17 @@ public boolean needsExplicitException() { return explicitExceptionEdges && !parsingIntrinsic(); } + @Override + protected boolean needsIncompatibleClassChangeErrorCheck() { + /* + * Note that the explicit check for incompatible class changes is necessary even when + * explicit exception edges for other exception are not required. We have no mechanism + * to do the check implicitly as part of interface calls. Interface calls are vtable + * calls both in AOT compiled code and JIT compiled code. + */ + return !parsingIntrinsic(); + } + @Override public boolean isPluginEnabled(GraphBuilderPlugin plugin) { return true; @@ -720,5 +805,26 @@ public boolean allowDeoptInPlugins() { return super.allowDeoptInPlugins(); } + @Override + @SuppressWarnings("try") + protected ValueNode emitIncompatibleClassChangeCheck(ValueNode object, ResolvedJavaType checkedType) { + try (DebugCloseable context = maybeDisableNodeSourcePositions()) { + return super.emitIncompatibleClassChangeCheck(object, checkedType); + } + } + + private DebugCloseable maybeDisableNodeSourcePositions() { + if (!SubstrateOptions.parseOnce() && graph.trackNodeSourcePosition()) { + /* + * Without "parse once", we use the bci of the invocation to look up static analysis + * results. Having a InstanceOfNode with the same bci disables static analysis + * results because we treat non-unique bci as "do not store any information. The + * workaround is to give the InstanceOfNode for the incompatible class change check + * the invalid bci -1. + */ + return graph.withNodeSourcePosition(new NodeSourcePosition(createBytecodePosition(), method, -1)); + } + return null; + } } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionExpandSignatureMethod.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionExpandSignatureMethod.java index 12d5bb6f19ba..ef968c67a462 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionExpandSignatureMethod.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionExpandSignatureMethod.java @@ -39,13 +39,13 @@ import com.oracle.svm.core.reflect.ReflectionAccessorHolder.MethodInvokeFunctionPointer; import com.oracle.svm.core.reflect.SubstrateConstructorAccessor; import com.oracle.svm.core.reflect.SubstrateMethodAccessor; -import com.oracle.svm.hosted.code.NonBytecodeStaticMethod; +import com.oracle.svm.hosted.code.NonBytecodeMethod; import jdk.vm.ci.meta.JavaKind; import jdk.vm.ci.meta.ResolvedJavaMethod; import jdk.vm.ci.meta.ResolvedJavaType; -public class ReflectionExpandSignatureMethod extends NonBytecodeStaticMethod { +public class ReflectionExpandSignatureMethod extends NonBytecodeMethod { private final boolean isStatic; private final Class[] argTypes; @@ -53,7 +53,7 @@ public class ReflectionExpandSignatureMethod extends NonBytecodeStaticMethod { private final boolean callerSensitiveAdapter; public ReflectionExpandSignatureMethod(String name, ResolvedJavaMethod prototype, boolean isStatic, Class[] argTypes, JavaKind returnKind, boolean callerSensitiveAdapter) { - super(name, prototype.getDeclaringClass(), prototype.getSignature(), prototype.getConstantPool()); + super(name, true, prototype.getDeclaringClass(), prototype.getSignature(), prototype.getConstantPool()); this.isStatic = isStatic; this.argTypes = argTypes; this.returnKind = returnKind; 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 d135604c3281..c26fd0e46bf2 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 @@ -75,6 +75,7 @@ import com.oracle.svm.hosted.annotation.AnnotationSubstitutionType; import com.oracle.svm.hosted.annotation.CustomSubstitutionMethod; import com.oracle.svm.hosted.classinitialization.ClassInitializationSupport; +import com.oracle.svm.hosted.code.IncompatibleClassChangeFallbackMethod; import com.oracle.svm.util.ReflectionUtil; import com.oracle.svm.util.ReflectionUtil.ReflectionUtilError; @@ -299,14 +300,20 @@ public ResolvedJavaMethod lookup(ResolvedJavaMethod method) { @Override public ResolvedJavaMethod resolve(ResolvedJavaMethod method) { - if (method instanceof SubstitutionMethod) { - return ((SubstitutionMethod) method).getOriginal(); - } else if (method instanceof CustomSubstitutionMethod) { - return ((CustomSubstitutionMethod) method).getOriginal(); - } else if (method instanceof AnnotatedMethod) { - return ((AnnotatedMethod) method).getOriginal(); + ResolvedJavaMethod cur = method; + while (true) { + if (cur instanceof SubstitutionMethod) { + cur = ((SubstitutionMethod) cur).getOriginal(); + } else if (cur instanceof CustomSubstitutionMethod) { + cur = ((CustomSubstitutionMethod) cur).getOriginal(); + } else if (cur instanceof AnnotatedMethod) { + cur = ((AnnotatedMethod) cur).getOriginal(); + } else if (cur instanceof IncompatibleClassChangeFallbackMethod) { + cur = ((IncompatibleClassChangeFallbackMethod) cur).getOriginal(); + } else { + return cur; + } } - return method; } /**