Skip to content

Commit c515c45

Browse files
author
Christian Wimmer
committed
Synthesize the correct exception in case of incompatible class changes
1 parent 59bfc86 commit c515c45

File tree

6 files changed

+246
-8
lines changed

6 files changed

+246
-8
lines changed

substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/BigBang.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,4 +129,9 @@ default void onTypeInitialized(AnalysisType type) {
129129
default void afterAnalysis() {
130130

131131
}
132+
133+
@SuppressWarnings("unused")
134+
default AnalysisMethod fallbackResolveConcreteMethod(AnalysisType resolvingType, AnalysisMethod method) {
135+
return null;
136+
}
132137
}

substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/meta/AnalysisType.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1046,6 +1046,9 @@ public AnalysisMethod resolveConcreteMethod(ResolvedJavaMethod method, ResolvedJ
10461046
ResolvedJavaType substCallerType = substMethod.getDeclaringClass();
10471047

10481048
Object newResolvedMethod = universe.lookup(wrapped.resolveConcreteMethod(substMethod, substCallerType));
1049+
if (newResolvedMethod == null) {
1050+
newResolvedMethod = getUniverse().getBigbang().fallbackResolveConcreteMethod(this, (AnalysisMethod) method);
1051+
}
10491052
if (newResolvedMethod == null) {
10501053
newResolvedMethod = NULL_METHOD;
10511054
}

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/analysis/NativeImagePointsToAnalysis.java

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424
*/
2525
package com.oracle.svm.hosted.analysis;
2626

27+
import java.lang.reflect.Executable;
28+
import java.lang.reflect.Method;
29+
import java.lang.reflect.Modifier;
2730
import java.util.concurrent.ForkJoinPool;
2831

2932
import org.graalvm.compiler.options.OptionValues;
@@ -43,6 +46,7 @@
4346
import com.oracle.svm.core.graal.meta.SubstrateReplacements;
4447
import com.oracle.svm.hosted.HostedConfiguration;
4548
import com.oracle.svm.hosted.SVMHost;
49+
import com.oracle.svm.hosted.code.IncompatibleClassChangeFallbackMethod;
4650
import com.oracle.svm.hosted.meta.HostedType;
4751
import com.oracle.svm.hosted.substitute.AnnotationSubstitutionProcessor;
4852

@@ -140,4 +144,71 @@ public boolean trackConcreteAnalysisObjects(AnalysisType type) {
140144
public SubstrateReplacements getReplacements() {
141145
return (SubstrateReplacements) super.getReplacements();
142146
}
147+
148+
/** See {@link IncompatibleClassChangeFallbackMethod} for documentation. */
149+
@Override
150+
public AnalysisMethod fallbackResolveConcreteMethod(AnalysisType resolvingType, AnalysisMethod method) {
151+
if (!resolvingType.isAbstract() && !resolvingType.isInterface() && !method.isStatic() && method.getDeclaringClass().isAssignableFrom(resolvingType)) {
152+
/*
153+
* We are resolving an instance method for a concrete (non-abstract) class that is a
154+
* subtype of the method's declared type. So this is a method invocation that can happen
155+
* at run time, and we need to return a method that throws an exception when being
156+
* executed.
157+
*/
158+
159+
if (method.getWrapped() instanceof IncompatibleClassChangeFallbackMethod) {
160+
/*
161+
* We are re-resolving a method that we already processed. Nothing to do, we already
162+
* have the appropriate fallback method.
163+
*/
164+
return method;
165+
}
166+
return getUniverse().lookup(new IncompatibleClassChangeFallbackMethod(resolvingType.getWrapped(), method.getWrapped(), findResolutionError(resolvingType, method.getJavaMethod())));
167+
}
168+
return super.fallbackResolveConcreteMethod(resolvingType, method);
169+
}
170+
171+
/**
172+
* Finding the correct exception that needs to be thrown at run time is a bit tricky, since
173+
* JVMCI does not report that information back when method resolution fails. We need to look
174+
* down the class hierarchy to see if there would be an appropriate method with a matching
175+
* signature which is just not accessible.
176+
*
177+
* We do all the method lookups (to search for a method with the same signature as searchMethod)
178+
* using reflection and not JVMCI because the lookup can throw all sorts of errors, and we want
179+
* to ignore the errors without any possible side effect on AnalysisType and AnalysisMethod.
180+
*/
181+
private static Class<? extends IncompatibleClassChangeError> findResolutionError(AnalysisType resolvingType, Executable searchMethod) {
182+
if (searchMethod != null) {
183+
Class<?>[] searchSignature = searchMethod.getParameterTypes();
184+
for (Class<?> cur = resolvingType.getJavaClass(); cur != null; cur = cur.getSuperclass()) {
185+
Method found;
186+
try {
187+
found = cur.getDeclaredMethod(searchMethod.getName(), searchSignature);
188+
} catch (Throwable ex) {
189+
/*
190+
* Method does not exist, a linkage error was thrown, or something else random
191+
* is wrong with the class files. Ignore this class.
192+
*/
193+
continue;
194+
}
195+
if (Modifier.isAbstract(found.getModifiers()) || Modifier.isPrivate(found.getModifiers()) || Modifier.isStatic(found.getModifiers())) {
196+
/*
197+
* We found a method with a matching signature, but it does not have an
198+
* implementation, or it is a private / static method that does not count from
199+
* the point of view of method resolution.
200+
*/
201+
return AbstractMethodError.class;
202+
} else {
203+
/*
204+
* We found a method with a matching signature, but it must have the wrong
205+
* access modifier (otherwise method resolution would have returned it).
206+
*/
207+
return IllegalAccessError.class;
208+
}
209+
}
210+
}
211+
/* Not matching method found at all. */
212+
return AbstractMethodError.class;
213+
}
143214
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
package com.oracle.svm.hosted.code;
26+
27+
import org.graalvm.compiler.debug.DebugContext;
28+
import org.graalvm.compiler.nodes.CallTargetNode.InvokeKind;
29+
import org.graalvm.compiler.nodes.StructuredGraph;
30+
import org.graalvm.compiler.nodes.UnwindNode;
31+
import org.graalvm.compiler.nodes.java.AbstractNewObjectNode;
32+
import org.graalvm.compiler.nodes.java.NewInstanceNode;
33+
34+
import com.oracle.graal.pointsto.meta.HostedProviders;
35+
import com.oracle.svm.hosted.analysis.NativeImagePointsToAnalysis;
36+
import com.oracle.svm.hosted.phases.HostedGraphKit;
37+
import com.oracle.svm.util.ReflectionUtil;
38+
39+
import jdk.vm.ci.meta.ResolvedJavaMethod;
40+
import jdk.vm.ci.meta.ResolvedJavaType;
41+
42+
/**
43+
* When interface/virtual call resolution via JVMCI does not return a concrete implementation method
44+
* for a concrete implementation type, then most likely some classes are in an incompatible state,
45+
* i.e., a class was compiled against a different version of a dependent class than the class we got
46+
* on the class path. Since we use vtable calls for all interface/virtual method calls, we cannot
47+
* just leave such unresolved methods as "no method is invoked" in the static analysis and "null" in
48+
* the vtable: that would lead to wrong static analysis results, wrong devirtualizations, and/or
49+
* vtable calls to uninitialized vtable slots.
50+
*
51+
* The solution is to always resolve a concrete synthetic fallback method, which then throws the
52+
* correct error at run time. This "fallback resolution" is implemented in
53+
* {@link NativeImagePointsToAnalysis#fallbackResolveConcreteMethod}.
54+
*/
55+
public final class IncompatibleClassChangeFallbackMethod extends NonBytecodeMethod {
56+
57+
private final ResolvedJavaMethod original;
58+
private final Class<? extends IncompatibleClassChangeError> resolutionError;
59+
60+
public IncompatibleClassChangeFallbackMethod(ResolvedJavaType declaringClass, ResolvedJavaMethod original, Class<? extends IncompatibleClassChangeError> resolutionError) {
61+
super(original.getName(), false, declaringClass, original.getSignature(), original.getConstantPool());
62+
this.original = original;
63+
this.resolutionError = resolutionError;
64+
}
65+
66+
public ResolvedJavaMethod getOriginal() {
67+
return original;
68+
}
69+
70+
@Override
71+
public StructuredGraph buildGraph(DebugContext debug, ResolvedJavaMethod method, HostedProviders providers, Purpose purpose) {
72+
HostedGraphKit kit = new HostedGraphKit(debug, providers, method);
73+
ResolvedJavaMethod constructor = providers.getMetaAccess().lookupJavaMethod(ReflectionUtil.lookupConstructor(resolutionError));
74+
75+
AbstractNewObjectNode newInstance = kit.append(new NewInstanceNode(constructor.getDeclaringClass(), true));
76+
kit.createInvokeWithExceptionAndUnwind(constructor, InvokeKind.Special, kit.getFrameState(), kit.bci(), newInstance);
77+
kit.append(new UnwindNode(newInstance));
78+
return kit.finalizeGraph();
79+
}
80+
}

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/SharedGraphBuilderPhase.java

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@
2424
*/
2525
package com.oracle.svm.hosted.phases;
2626

27+
import java.lang.reflect.Constructor;
28+
import java.lang.reflect.Executable;
29+
import java.lang.reflect.Method;
30+
import java.lang.reflect.Modifier;
31+
import java.util.Arrays;
2732
import java.util.List;
2833

2934
import org.graalvm.compiler.api.replacements.Fold;
@@ -63,7 +68,9 @@
6368

6469
import com.oracle.graal.pointsto.constraints.TypeInstantiationException;
6570
import com.oracle.graal.pointsto.constraints.UnresolvedElementException;
71+
import com.oracle.graal.pointsto.infrastructure.OriginalClassProvider;
6672
import com.oracle.graal.pointsto.infrastructure.UniverseMetaAccess;
73+
import com.oracle.graal.pointsto.util.GraalAccess;
6774
import com.oracle.svm.common.meta.MultiMethod;
6875
import com.oracle.svm.core.SubstrateOptions;
6976
import com.oracle.svm.core.SubstrateUtil;
@@ -435,11 +442,76 @@ private void handleUnresolvedMethod(JavaMethod javaMethod) {
435442
if (linkAtBuildTime) {
436443
reportUnresolvedElement("method", javaMethod.format("%H.%n(%P)"));
437444
} else {
438-
ExceptionSynthesizer.throwException(this, NoSuchMethodError.class, javaMethod.format("%H.%n(%P)"));
445+
ExceptionSynthesizer.throwException(this, findResolutionError((ResolvedJavaType) declaringClass, javaMethod), javaMethod.format("%H.%n(%P)"));
439446
}
440447
}
441448
}
442449

450+
/**
451+
* Finding the correct exception that needs to be thrown at run time is a bit tricky, since
452+
* JVMCI does not report that information back when method resolution fails. We need to look
453+
* down the class hierarchy to see if there would be an appropriate method with a matching
454+
* signature which is just not accessible.
455+
*
456+
* We do all the method lookups (to search for a method with the same signature as
457+
* searchMethod) using reflection and not JVMCI because the lookup can throw all sorts of
458+
* errors, and we want to ignore the errors without any possible side effect on AnalysisType
459+
* and AnalysisMethod.
460+
*/
461+
private static Class<? extends IncompatibleClassChangeError> findResolutionError(ResolvedJavaType declaringType, JavaMethod searchMethod) {
462+
Class<?>[] searchSignature = signatureToClasses(searchMethod);
463+
Class<?> searchReturnType = null;
464+
if (searchMethod.getSignature().getReturnType(null) instanceof ResolvedJavaType) {
465+
searchReturnType = OriginalClassProvider.getJavaClass(GraalAccess.getOriginalSnippetReflection(), (ResolvedJavaType) searchMethod.getSignature().getReturnType(null));
466+
}
467+
468+
Class<?> declaringClass = OriginalClassProvider.getJavaClass(GraalAccess.getOriginalSnippetReflection(), declaringType);
469+
for (Class<?> cur = declaringClass; cur != null; cur = cur.getSuperclass()) {
470+
Executable[] methods = null;
471+
try {
472+
if (searchMethod.getName().equals("<init>")) {
473+
methods = cur.getDeclaredConstructors();
474+
} else {
475+
methods = cur.getDeclaredMethods();
476+
}
477+
} catch (Throwable ignored) {
478+
/*
479+
* A linkage error was thrown, or something else random is wrong with the class
480+
* files. Ignore this class.
481+
*/
482+
}
483+
if (methods != null) {
484+
for (Executable method : methods) {
485+
if (Arrays.equals(searchSignature, method.getParameterTypes()) &&
486+
(method instanceof Constructor || (searchMethod.getName().equals(method.getName()) && searchReturnType == ((Method) method).getReturnType()))) {
487+
if (Modifier.isAbstract(method.getModifiers())) {
488+
return AbstractMethodError.class;
489+
} else {
490+
return IllegalAccessError.class;
491+
}
492+
}
493+
}
494+
}
495+
if (searchMethod.getName().equals("<init>")) {
496+
/* For constructors, do not search in superclasses. */
497+
break;
498+
}
499+
}
500+
return NoSuchMethodError.class;
501+
}
502+
503+
private static Class<?>[] signatureToClasses(JavaMethod method) {
504+
int paramCount = method.getSignature().getParameterCount(false);
505+
Class<?>[] result = new Class<?>[paramCount];
506+
for (int i = 0; i < paramCount; i++) {
507+
JavaType parameterType = method.getSignature().getParameterType(0, null);
508+
if (parameterType instanceof ResolvedJavaType) {
509+
result[i] = OriginalClassProvider.getJavaClass(GraalAccess.getOriginalSnippetReflection(), (ResolvedJavaType) parameterType);
510+
}
511+
}
512+
return result;
513+
}
514+
443515
private void reportUnresolvedElement(String elementKind, String elementAsString) {
444516
reportUnresolvedElement(elementKind, elementAsString, null);
445517
}

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/substitute/AnnotationSubstitutionProcessor.java

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
import com.oracle.svm.hosted.annotation.AnnotationSubstitutionType;
7676
import com.oracle.svm.hosted.annotation.CustomSubstitutionMethod;
7777
import com.oracle.svm.hosted.classinitialization.ClassInitializationSupport;
78+
import com.oracle.svm.hosted.code.IncompatibleClassChangeFallbackMethod;
7879
import com.oracle.svm.util.ReflectionUtil;
7980
import com.oracle.svm.util.ReflectionUtil.ReflectionUtilError;
8081

@@ -299,14 +300,20 @@ public ResolvedJavaMethod lookup(ResolvedJavaMethod method) {
299300

300301
@Override
301302
public ResolvedJavaMethod resolve(ResolvedJavaMethod method) {
302-
if (method instanceof SubstitutionMethod) {
303-
return ((SubstitutionMethod) method).getOriginal();
304-
} else if (method instanceof CustomSubstitutionMethod) {
305-
return ((CustomSubstitutionMethod) method).getOriginal();
306-
} else if (method instanceof AnnotatedMethod) {
307-
return ((AnnotatedMethod) method).getOriginal();
303+
ResolvedJavaMethod cur = method;
304+
while (true) {
305+
if (cur instanceof SubstitutionMethod) {
306+
cur = ((SubstitutionMethod) cur).getOriginal();
307+
} else if (cur instanceof CustomSubstitutionMethod) {
308+
cur = ((CustomSubstitutionMethod) cur).getOriginal();
309+
} else if (cur instanceof AnnotatedMethod) {
310+
cur = ((AnnotatedMethod) cur).getOriginal();
311+
} else if (cur instanceof IncompatibleClassChangeFallbackMethod) {
312+
cur = ((IncompatibleClassChangeFallbackMethod) cur).getOriginal();
313+
} else {
314+
return cur;
315+
}
308316
}
309-
return method;
310317
}
311318

312319
/**

0 commit comments

Comments
 (0)