Skip to content

Commit 04cde4b

Browse files
author
Christian Wimmer
committed
[GR-41674] Class instanceOf and isAssignableFrom checks do need to make the checked type reachable.
PullRequest: graal/12922
2 parents 19a0590 + 0f0a1c6 commit 04cde4b

File tree

7 files changed

+146
-11
lines changed

7 files changed

+146
-11
lines changed

substratevm/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ This changelog summarizes major changes to GraalVM Native Image.
66
* (GR-40187) Report invalid use of SVM specific classes on image class- or module-path as error. As a temporary workaround, `-H:+AllowDeprecatedBuilderClassesOnImageClasspath` allows turning the error into a warning.
77
* (GR-41196) Provide `.debug.svm.imagebuild.*` sections that contain build options and properties used in the build of the image.
88
* (GR-41978) Disallow `--initialize-at-build-time` without arguments. As a temporary workaround, `-H:+AllowDeprecatedInitializeAllClassesAtBuildTime` allows turning this error into a warning.
9+
* (GR-41674) Class instanceOf and isAssignableFrom checks do need to make the checked type reachable.
910

1011
## Version 22.3.0
1112
* (GR-35721) Remove old build output style and the `-H:±BuildOutputUseNewStyle` option.

substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/flow/MethodTypeFlowBuilder.java

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,14 @@
7070
import org.graalvm.compiler.nodes.calc.IsNullNode;
7171
import org.graalvm.compiler.nodes.calc.ObjectEqualsNode;
7272
import org.graalvm.compiler.nodes.extended.BoxNode;
73+
import org.graalvm.compiler.nodes.extended.BytecodeExceptionNode;
74+
import org.graalvm.compiler.nodes.extended.BytecodeExceptionNode.BytecodeExceptionKind;
7375
import org.graalvm.compiler.nodes.extended.ForeignCall;
7476
import org.graalvm.compiler.nodes.extended.GetClassNode;
7577
import org.graalvm.compiler.nodes.extended.RawLoadNode;
7678
import org.graalvm.compiler.nodes.extended.RawStoreNode;
7779
import org.graalvm.compiler.nodes.java.AtomicReadAndWriteNode;
80+
import org.graalvm.compiler.nodes.java.ClassIsAssignableFromNode;
7881
import org.graalvm.compiler.nodes.java.DynamicNewArrayNode;
7982
import org.graalvm.compiler.nodes.java.DynamicNewInstanceNode;
8083
import org.graalvm.compiler.nodes.java.ExceptionObjectNode;
@@ -134,6 +137,7 @@
134137
import com.oracle.graal.pointsto.nodes.UnsafePartitionStoreNode;
135138
import com.oracle.graal.pointsto.phases.InlineBeforeAnalysis;
136139
import com.oracle.graal.pointsto.results.StaticAnalysisResultsBuilder;
140+
import com.oracle.graal.pointsto.results.StrengthenGraphs;
137141
import com.oracle.graal.pointsto.typestate.TypeState;
138142
import com.oracle.graal.pointsto.util.AnalysisError;
139143

@@ -226,7 +230,9 @@ public void registerUsedElements(boolean registerEmbeddedRoots) {
226230
if (n instanceof InstanceOfNode) {
227231
InstanceOfNode node = (InstanceOfNode) n;
228232
AnalysisType type = (AnalysisType) node.type().getType();
229-
type.registerAsReachable();
233+
if (!ignoreInstanceOfType(type)) {
234+
type.registerAsReachable();
235+
}
230236

231237
} else if (n instanceof NewInstanceNode) {
232238
NewInstanceNode node = (NewInstanceNode) n;
@@ -290,7 +296,7 @@ public void registerUsedElements(boolean registerEmbeddedRoots) {
290296
assert StampTool.isExactType(cn);
291297
AnalysisType type = (AnalysisType) StampTool.typeOrNull(cn);
292298
type.registerAsInHeap();
293-
if (registerEmbeddedRoots) {
299+
if (registerEmbeddedRoots && !ignoreConstant(cn)) {
294300
registerEmbeddedRoot(cn);
295301
}
296302
}
@@ -321,6 +327,59 @@ public void registerUsedElements(boolean registerEmbeddedRoots) {
321327
}
322328
}
323329

330+
/**
331+
* This method filters constants, i.e., these constants are not seen as reachable on its own.
332+
* This avoids making things reachable just because of that constant usage. For all cases where
333+
* this method returns true, {@link StrengthenGraphs} must have a corresponding re-write of the
334+
* constant in case nothing else in the application made that constant reachable.
335+
*
336+
* {@link Class#isAssignableFrom} is often used with a constant receiver class. In that case, we
337+
* do not want to make the receiver class reachable, because as long as the receiver class is
338+
* not reachable for any other "real" reason we know that isAssignableFrom will always return
339+
* false. So in {@link StrengthenGraphs} we can then constant-fold the
340+
* {@link ClassIsAssignableFromNode} to false.
341+
*
342+
* Similarly, a class should not be marked as reachable only so that we can add the class name
343+
* to the error message of a {@link ClassCastException}. In {@link StrengthenGraphs} we can
344+
* re-write the Class constant to a String constant, i.e., only embed the class name and not the
345+
* full java.lang.Class object in the image.
346+
*/
347+
protected boolean ignoreConstant(ConstantNode cn) {
348+
if (!ignoreInstanceOfType((AnalysisType) bb.getProviders().getConstantReflection().asJavaType(cn.asConstant()))) {
349+
return false;
350+
}
351+
for (var usage : cn.usages()) {
352+
if (usage instanceof ClassIsAssignableFromNode) {
353+
if (((ClassIsAssignableFromNode) usage).getThisClass() != cn) {
354+
return false;
355+
}
356+
} else if (usage instanceof BytecodeExceptionNode) {
357+
if (((BytecodeExceptionNode) usage).getExceptionKind() != BytecodeExceptionKind.CLASS_CAST) {
358+
return false;
359+
}
360+
} else {
361+
return false;
362+
}
363+
}
364+
/* Success, the ConstantNode do not need to be seen as reachable. */
365+
return true;
366+
}
367+
368+
protected boolean ignoreInstanceOfType(AnalysisType type) {
369+
if (type == null || !bb.strengthenGraalGraphs()) {
370+
return false;
371+
}
372+
if (type.isArray()) {
373+
/*
374+
* There is no real overhead when making array types reachable (which automatically also
375+
* makes them instantiated), and it avoids manual reflection configuration because
376+
* casting to an array type then automatically marks the array type as instantiated.
377+
*/
378+
return false;
379+
}
380+
return true;
381+
}
382+
324383
private void registerEmbeddedRoot(ConstantNode cn) {
325384
JavaConstant root = cn.asJavaConstant();
326385
if (bb.scanningPolicy().trackConstant(bb, root)) {

substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/results/StrengthenGraphs.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import org.graalvm.compiler.nodeinfo.InputType;
4343
import org.graalvm.compiler.nodes.AbstractBeginNode;
4444
import org.graalvm.compiler.nodes.CallTargetNode;
45+
import org.graalvm.compiler.nodes.ConstantNode;
4546
import org.graalvm.compiler.nodes.FixedGuardNode;
4647
import org.graalvm.compiler.nodes.FixedNode;
4748
import org.graalvm.compiler.nodes.FixedWithNextNode;
@@ -60,7 +61,9 @@
6061
import org.graalvm.compiler.nodes.StateSplit;
6162
import org.graalvm.compiler.nodes.StructuredGraph;
6263
import org.graalvm.compiler.nodes.ValueNode;
64+
import org.graalvm.compiler.nodes.extended.BytecodeExceptionNode;
6365
import org.graalvm.compiler.nodes.extended.ValueAnchorNode;
66+
import org.graalvm.compiler.nodes.java.ClassIsAssignableFromNode;
6467
import org.graalvm.compiler.nodes.java.InstanceOfNode;
6568
import org.graalvm.compiler.nodes.java.LoadFieldNode;
6669
import org.graalvm.compiler.nodes.java.LoadIndexedNode;
@@ -187,6 +190,8 @@ public JavaTypeProfile makeTypeProfile(AnalysisField field) {
187190

188191
protected abstract void setInvokeProfiles(Invoke invoke, JavaTypeProfile typeProfile, JavaMethodProfile methodProfile);
189192

193+
protected abstract String getTypeName(AnalysisType type);
194+
190195
class StrengthenSimplifier implements CustomSimplification {
191196

192197
private final StructuredGraph graph;
@@ -307,6 +312,35 @@ public void simplify(Node n, SimplifierTool tool) {
307312
tool.addToWorkList(replacement);
308313
}
309314

315+
} else if (n instanceof ClassIsAssignableFromNode) {
316+
ClassIsAssignableFromNode node = (ClassIsAssignableFromNode) n;
317+
ValueNode thisClass = node.getThisClass();
318+
if (thisClass.isConstant()) {
319+
AnalysisType thisType = (AnalysisType) tool.getConstantReflection().asJavaType(thisClass.asConstant());
320+
if (!thisType.isReachable()) {
321+
node.replaceAndDelete(LogicConstantNode.contradiction(graph));
322+
}
323+
}
324+
325+
} else if (n instanceof BytecodeExceptionNode) {
326+
/*
327+
* We do not want a type to be reachable only to be used for the error message of a
328+
* ClassCastException. Therefore, in that case we replace the java.lang.Class with a
329+
* java.lang.String that is then used directly in the error message.
330+
*/
331+
BytecodeExceptionNode node = (BytecodeExceptionNode) n;
332+
if (node.getExceptionKind() == BytecodeExceptionNode.BytecodeExceptionKind.CLASS_CAST) {
333+
ValueNode expectedClass = node.getArguments().get(1);
334+
if (expectedClass.isConstant()) {
335+
AnalysisType expectedType = (AnalysisType) tool.getConstantReflection().asJavaType(expectedClass.asConstant());
336+
if (expectedType != null && !expectedType.isReachable()) {
337+
String expectedName = getTypeName(expectedType);
338+
ConstantNode expectedConstant = ConstantNode.forConstant(tool.getConstantReflection().forString(expectedName), tool.getMetaAccess(), graph);
339+
node.getArguments().set(1, expectedConstant);
340+
}
341+
}
342+
}
343+
310344
} else if (n instanceof PiNode) {
311345
PiNode node = (PiNode) n;
312346
Stamp oldStamp = node.piStamp();
@@ -630,6 +664,16 @@ private Stamp strengthenStamp(Stamp s) {
630664
return null;
631665
}
632666

667+
if (!originalType.isReachable()) {
668+
/* We must be in dead code. */
669+
if (stamp.nonNull()) {
670+
/* We must be in dead code. */
671+
return StampFactory.empty(JavaKind.Object);
672+
} else {
673+
return StampFactory.alwaysNull();
674+
}
675+
}
676+
633677
AnalysisType singleImplementorType = getSingleImplementorType(originalType);
634678
if (singleImplementorType != null && (!stamp.isExactType() || !singleImplementorType.equals(originalType))) {
635679
ResolvedJavaType targetType = toTarget(singleImplementorType);

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/snippets/ImplicitExceptions.java

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@
2727
import java.lang.reflect.GenericSignatureFormatError;
2828

2929
import com.oracle.svm.core.SubstrateDiagnostics;
30-
import com.oracle.svm.core.heap.RestrictHeapAccess;
3130
import com.oracle.svm.core.code.FactoryMethodMarker;
31+
import com.oracle.svm.core.heap.RestrictHeapAccess;
3232
import com.oracle.svm.core.jdk.InternalVMMethod;
3333
import com.oracle.svm.core.jdk.StackTraceUtils;
3434
import com.oracle.svm.core.snippets.SnippetRuntime.SubstrateForeignCallDescriptor;
@@ -173,10 +173,16 @@ private static ArrayIndexOutOfBoundsException createOutOfBoundsException(int ind
173173

174174
/** Foreign call: {@link #CREATE_CLASS_CAST_EXCEPTION}. */
175175
@SubstrateForeignCallTarget(stubCallingConvention = true)
176-
private static ClassCastException createClassCastException(Object object, Class<?> expectedClass) {
176+
private static ClassCastException createClassCastException(Object object, Object expectedClass) {
177177
assert object != null : "null can be cast to any type, so it cannot show up as a source of a ClassCastException";
178178
vmErrorIfImplicitExceptionsAreFatal();
179-
return new ClassCastException(object.getClass().getTypeName() + " cannot be cast to " + expectedClass.getTypeName());
179+
String expectedClassName;
180+
if (expectedClass instanceof Class) {
181+
expectedClassName = ((Class<?>) expectedClass).getTypeName();
182+
} else {
183+
expectedClassName = String.valueOf(expectedClass);
184+
}
185+
return new ClassCastException(object.getClass().getTypeName() + " cannot be cast to " + expectedClassName);
180186
}
181187

182188
/** Foreign call: {@link #CREATE_ARRAY_STORE_EXCEPTION}. */
@@ -256,10 +262,16 @@ private static void throwNewClassCastException() {
256262

257263
/** Foreign call: {@link #THROW_NEW_CLASS_CAST_EXCEPTION_WITH_ARGS}. */
258264
@SubstrateForeignCallTarget(stubCallingConvention = true)
259-
private static void throwNewClassCastExceptionWithArgs(Object object, Class<?> expectedClass) {
265+
private static void throwNewClassCastExceptionWithArgs(Object object, Object expectedClass) {
260266
assert object != null : "null can be cast to any type, so it cannot show up as a source of a ClassCastException";
261267
vmErrorIfImplicitExceptionsAreFatal();
262-
throw new ClassCastException(object.getClass().getTypeName() + " cannot be cast to " + expectedClass.getTypeName());
268+
String expectedClassName;
269+
if (expectedClass instanceof Class) {
270+
expectedClassName = ((Class<?>) expectedClass).getTypeName();
271+
} else {
272+
expectedClassName = String.valueOf(expectedClass);
273+
}
274+
throw new ClassCastException(object.getClass().getTypeName() + " cannot be cast to " + expectedClassName);
263275
}
264276

265277
/** Foreign call: {@link #THROW_NEW_ARRAY_STORE_EXCEPTION}. */

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/SubstrateStrengthenGraphs.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import com.oracle.svm.core.graal.nodes.LoweredDeadEndNode;
4343
import com.oracle.svm.core.nodes.SubstrateMethodCallTargetNode;
4444
import com.oracle.svm.core.snippets.SnippetRuntime;
45+
import com.oracle.svm.core.util.HostedStringDeduplication;
4546
import com.oracle.svm.hosted.meta.HostedType;
4647

4748
import jdk.vm.ci.meta.JavaMethodProfile;
@@ -94,4 +95,9 @@ protected void setInvokeProfiles(Invoke invoke, JavaTypeProfile typeProfile, Jav
9495
protected void setInvokeProfiles(Invoke invoke, JavaTypeProfile typeProfile, JavaMethodProfile methodProfile, JavaTypeProfile staticTypeProfile) {
9596
((SubstrateMethodCallTargetNode) invoke.callTarget()).setProfiles(typeProfile, methodProfile, staticTypeProfile);
9697
}
98+
99+
@Override
100+
protected String getTypeName(AnalysisType type) {
101+
return HostedStringDeduplication.singleton().deduplicate(type.toJavaName(true), false);
102+
}
97103
}

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ameta/AnalysisConstantReflectionProvider.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -305,9 +305,7 @@ public ResolvedJavaType asJavaType(Constant constant) {
305305

306306
@Override
307307
public JavaConstant asJavaClass(ResolvedJavaType type) {
308-
DynamicHub dynamicHub = getHostVM().dynamicHub(type);
309-
registerAsReachable(getHostVM(), dynamicHub);
310-
return SubstrateObjectConstant.forObject(dynamicHub);
308+
return SubstrateObjectConstant.forObject(getHostVM().dynamicHub(type));
311309
}
312310

313311
protected static void registerAsReachable(SVMHost hostVM, DynamicHub dynamicHub) {

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/flow/SVMMethodTypeFlowBuilder.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import com.oracle.svm.core.util.UserError.UserException;
5252
import com.oracle.svm.hosted.NativeImageOptions;
5353
import com.oracle.svm.hosted.SVMHost;
54+
import com.oracle.svm.hosted.classinitialization.ClassInitializationOptions;
5455
import com.oracle.svm.hosted.substitute.ComputedValueField;
5556

5657
import jdk.vm.ci.code.BytecodePosition;
@@ -82,7 +83,7 @@ public void registerUsedElements(boolean registerEmbeddedRoots) {
8283
if (cn.hasUsages() && cn.isJavaConstant() && constant.getJavaKind() == JavaKind.Object && constant.isNonNull()) {
8384
if (constant instanceof ImageHeapConstant) {
8485
/* No replacement for ImageHeapObject. */
85-
} else {
86+
} else if (!ignoreConstant(cn)) {
8687
/*
8788
* Constants that are embedded into graphs via constant folding of static
8889
* fields have already been replaced. But constants embedded manually by
@@ -107,6 +108,20 @@ public void registerUsedElements(boolean registerEmbeddedRoots) {
107108
}
108109
}
109110

111+
@Override
112+
protected boolean ignoreInstanceOfType(AnalysisType type) {
113+
if (ClassInitializationOptions.AllowDeprecatedInitializeAllClassesAtBuildTime.getValue()) {
114+
/*
115+
* Compatibility mode for Helidon MP: It initializes all classes at build time, and
116+
* relies on the side effect of a class initializer of a class that is only used in an
117+
* instanceof. See https://github.com/oracle/graal/pull/5224#issuecomment-1279586997 for
118+
* details.
119+
*/
120+
return false;
121+
}
122+
return super.ignoreInstanceOfType(type);
123+
}
124+
110125
@SuppressWarnings("serial")
111126
public static class UnsafeOffsetError extends UserException {
112127

0 commit comments

Comments
 (0)