Skip to content

Commit 7ba4d14

Browse files
committed
[GR-66389] Add InterceptJSInvokeNodes in WasmGC backend
PullRequest: graal/21209
2 parents c28e6a5 + 4115586 commit 7ba4d14

File tree

4 files changed

+142
-133
lines changed

4 files changed

+142
-133
lines changed

web-image/src/com.oracle.svm.hosted.webimage.test/src/com/oracle/svm/hosted/webimage/test/spec/JS_JTT_JSAnnotation.java

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,20 +51,13 @@
5151
public class JS_JTT_JSAnnotation extends JTTTestSuite {
5252

5353
// @formatter:off
54-
// TODO GR-62854 Remove Runnable#run once JSBodyFeature is enabled for WasmGC and InterceptJSInvokeTypeFlow is used
5554
private static final String REFLECT_CONFIG = """
5655
[
5756
{
5857
"name" : "java.lang.String",
5958
"methods": [
6059
{"name" : "indexOf"}
6160
]
62-
},
63-
{
64-
"name" : "java.lang.Runnable",
65-
"methods": [
66-
{"name" : "run"}
67-
]
6861
}
6962
]""";
7063

web-image/src/com.oracle.svm.hosted.webimage/src/com/oracle/svm/hosted/webimage/codegen/JSBodyFeature.java

Lines changed: 0 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,7 @@
2424
*/
2525
package com.oracle.svm.hosted.webimage.codegen;
2626

27-
import java.lang.reflect.Executable;
28-
import java.lang.reflect.Method;
2927
import java.lang.reflect.Modifier;
30-
import java.util.List;
31-
import java.util.Set;
3228

3329
import org.graalvm.nativeimage.AnnotationAccess;
3430
import org.graalvm.nativeimage.Platforms;
@@ -46,11 +42,8 @@
4642
import com.oracle.svm.core.feature.InternalFeature;
4743
import com.oracle.svm.core.nodes.SubstrateMethodCallTargetNode;
4844
import com.oracle.svm.hosted.FeatureImpl;
49-
import com.oracle.svm.hosted.ImageClassLoader;
50-
import com.oracle.svm.hosted.webimage.codegen.node.InterceptJSInvokeNode;
5145
import com.oracle.svm.hosted.webimage.codegen.oop.ClassWithMirrorLowerer;
5246
import com.oracle.svm.hosted.webimage.js.JSObjectAccessMethodSupport;
53-
import com.oracle.svm.hosted.webimage.util.ReflectUtil;
5447
import com.oracle.svm.util.ReflectionUtil;
5548
import com.oracle.svm.webimage.api.Nothing;
5649
import com.oracle.svm.webimage.platform.WebImageJSPlatform;
@@ -79,17 +72,6 @@
7972
@AutomaticallyRegisteredFeature
8073
@Platforms(WebImageJSPlatform.class)
8174
public final class JSBodyFeature implements InternalFeature {
82-
// The set of methods that are potentially overridden by a JS-annotated method.
83-
private Set<Method> jsOverridden;
84-
85-
@Override
86-
public void afterRegistration(AfterRegistrationAccess access) {
87-
FeatureImpl.AfterRegistrationAccessImpl accessImpl = (FeatureImpl.AfterRegistrationAccessImpl) access;
88-
ImageClassLoader imageClassLoader = accessImpl.getImageClassLoader();
89-
List<Class<?>> allClasses = imageClassLoader.findSubclasses(Object.class, true);
90-
jsOverridden = ReflectUtil.findBaseMethodsOfJSAnnotated(allClasses);
91-
}
92-
9375
@Override
9476
public void registerGraphBuilderPlugins(Providers providers, GraphBuilderConfiguration.Plugins plugins, ParsingReason reason) {
9577
plugins.appendNodePlugin(new NodePlugin() {
@@ -98,28 +80,6 @@ public boolean handleInvoke(GraphBuilderContext b, ResolvedJavaMethod method, Va
9880
if (AnnotationAccess.isAnnotationPresent(method.getDeclaringClass(), JS.Import.class)) {
9981
((AnalysisType) method.getDeclaringClass()).registerAsInstantiated("JS.Import classes might be allocated in JavaScript. We need to tell the static analysis about that");
10082
}
101-
if (canBeJavaScriptCall((AnalysisMethod) method)) {
102-
InterceptJSInvokeNode intercept = b.append(new InterceptJSInvokeNode(method, b.bci()));
103-
for (final ValueNode arg : args) {
104-
intercept.arguments().add(arg);
105-
}
106-
}
107-
return false;
108-
}
109-
110-
private boolean canBeJavaScriptCall(AnalysisMethod method) {
111-
Executable executable;
112-
try {
113-
executable = method.getJavaMethod();
114-
} catch (Throwable e) {
115-
// Either a substituted method, or a method with a malformed bytecode signature.
116-
// This is most likely not a JS-annotated method.
117-
return false;
118-
}
119-
if (executable instanceof Method) {
120-
return jsOverridden.contains(executable);
121-
}
122-
// Not a normal method (constructor).
12383
return false;
12484
}
12585

@@ -314,9 +274,4 @@ private static void findJSObjectSubtypes(FeatureImpl.DuringAnalysisAccessImpl ac
314274
access.requireAnalysisIteration();
315275
}
316276
}
317-
318-
@Override
319-
public void afterAnalysis(AfterAnalysisAccess access) {
320-
jsOverridden = null;
321-
}
322277
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*
2+
* Copyright (c) 2025, 2025, 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+
26+
package com.oracle.svm.hosted.webimage.codegen;
27+
28+
import java.lang.reflect.Executable;
29+
import java.lang.reflect.Method;
30+
import java.util.Set;
31+
32+
import org.graalvm.nativeimage.Platforms;
33+
34+
import com.oracle.graal.pointsto.meta.AnalysisMethod;
35+
import com.oracle.svm.core.ParsingReason;
36+
import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature;
37+
import com.oracle.svm.core.feature.InternalFeature;
38+
import com.oracle.svm.hosted.FeatureImpl;
39+
import com.oracle.svm.hosted.ImageClassLoader;
40+
import com.oracle.svm.hosted.webimage.codegen.node.InterceptJSInvokeNode;
41+
import com.oracle.svm.hosted.webimage.util.ReflectUtil;
42+
import com.oracle.svm.webimage.platform.WebImagePlatform;
43+
44+
import jdk.graal.compiler.nodes.ValueNode;
45+
import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration;
46+
import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderContext;
47+
import jdk.graal.compiler.nodes.graphbuilderconf.NodePlugin;
48+
import jdk.graal.compiler.phases.util.Providers;
49+
import jdk.vm.ci.meta.ResolvedJavaMethod;
50+
51+
/**
52+
* Inserts {@link InterceptJSInvokeNode} into the IR at any invoke that may target a
53+
* {@link org.graalvm.webimage.api.JS} annotated method to deal with types that may leak to
54+
* JavaScript.
55+
* <p>
56+
* TODO GR-62854 Merge into JSBodyFeature once it is enabled by default
57+
*/
58+
@AutomaticallyRegisteredFeature
59+
@Platforms(WebImagePlatform.class)
60+
public class JSBodyTypeFlowFeature implements InternalFeature {
61+
/**
62+
* The set of methods that are potentially overridden by a JS-annotated method.
63+
* <p>
64+
* Any call to such a method needs to have a {@link InterceptJSInvokeNode} attached to it.
65+
*/
66+
private Set<Method> jsOverridden;
67+
68+
@Override
69+
public void afterRegistration(AfterRegistrationAccess access) {
70+
FeatureImpl.AfterRegistrationAccessImpl accessImpl = (FeatureImpl.AfterRegistrationAccessImpl) access;
71+
ImageClassLoader imageClassLoader = accessImpl.getImageClassLoader();
72+
jsOverridden = ReflectUtil.findBaseMethodsOfJSAnnotated(imageClassLoader);
73+
}
74+
75+
@Override
76+
public void registerGraphBuilderPlugins(Providers providers, GraphBuilderConfiguration.Plugins plugins, ParsingReason reason) {
77+
plugins.appendNodePlugin(new NodePlugin() {
78+
@Override
79+
public boolean handleInvoke(GraphBuilderContext b, ResolvedJavaMethod method, ValueNode[] args) {
80+
if (canBeJavaScriptCall((AnalysisMethod) method)) {
81+
InterceptJSInvokeNode intercept = b.append(new InterceptJSInvokeNode(method, b.bci()));
82+
for (final ValueNode arg : args) {
83+
intercept.arguments().add(arg);
84+
}
85+
}
86+
return false;
87+
}
88+
89+
private boolean canBeJavaScriptCall(AnalysisMethod method) {
90+
Executable executable;
91+
try {
92+
executable = method.getJavaMethod();
93+
} catch (Throwable e) {
94+
// Either a substituted method, or a method with a malformed bytecode signature.
95+
// This is most likely not a JS-annotated method.
96+
return false;
97+
}
98+
if (executable instanceof Method) {
99+
return jsOverridden.contains(executable);
100+
}
101+
// Not a normal method (constructor).
102+
return false;
103+
}
104+
105+
});
106+
}
107+
108+
@Override
109+
public void afterAnalysis(AfterAnalysisAccess access) {
110+
jsOverridden = null;
111+
}
112+
}

web-image/src/com.oracle.svm.hosted.webimage/src/com/oracle/svm/hosted/webimage/util/ReflectUtil.java

Lines changed: 30 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,7 @@
2525

2626
package com.oracle.svm.hosted.webimage.util;
2727

28-
import java.lang.annotation.Annotation;
29-
import java.lang.reflect.GenericSignatureFormatError;
3028
import java.lang.reflect.Method;
31-
import java.lang.reflect.Modifier;
3229
import java.util.Arrays;
3330
import java.util.HashSet;
3431
import java.util.LinkedHashSet;
@@ -42,6 +39,8 @@
4239

4340
import com.oracle.graal.pointsto.infrastructure.OriginalClassProvider;
4441
import com.oracle.graal.pointsto.infrastructure.OriginalMethodProvider;
42+
import com.oracle.svm.hosted.ImageClassLoader;
43+
import com.oracle.svm.util.ReflectionUtil;
4544

4645
import jdk.vm.ci.meta.MetaAccessProvider;
4746
import jdk.vm.ci.meta.ResolvedJavaMethod;
@@ -169,92 +168,42 @@ private static void findAllInterfaces(Class<?> type, LinkedHashSet<Class<?>> int
169168
}
170169
}
171170

172-
public static Set<Method> findBaseMethodsOfJSAnnotated(List<Class<?>> allClasses) {
173-
HashSet<Method> set = new HashSet<>();
174-
for (Class<?> cls : allClasses) {
175-
Method[] declaredMethods;
176-
try {
177-
declaredMethods = cls.getDeclaredMethods();
178-
} catch (NoClassDefFoundError e) {
179-
// Some classes may be missing on the class path, but these will not be used by the
180-
// image.
181-
continue;
182-
} catch (VerifyError | ClassFormatError | ClassCircularityError | IllegalAccessError e) {
183-
// Skip the corrupted class encountered during the analysis.
184-
System.err.printf(
185-
"Skipped JS annotated base methods lookup for class %s.%nError: %s, Message: %s.%n",
186-
cls.getName(),
187-
e.getClass().getName(),
188-
e.getMessage());
189-
continue;
190-
}
191-
for (Method method : declaredMethods) {
192-
Annotation jsAnnotation;
193-
try {
194-
jsAnnotation = method.getAnnotation(JS.class);
195-
} catch (GenericSignatureFormatError e) {
196-
// Skip the corrupted method encountered during the analysis.
197-
System.err.printf(
198-
"Skipped JS annotated base methods lookup for method %s::%s.%nError: %s, Message: %s.%n",
199-
method.getDeclaringClass().getName(),
200-
method.getName(),
201-
e.getClass().getName(),
202-
e.getMessage());
203-
continue;
204-
}
205-
if (jsAnnotation != null) {
206-
markBaseMethods(method, cls, set, new HashSet<>());
207-
}
208-
}
209-
}
210-
return set;
211-
}
171+
/**
172+
* Finds all methods annotated with {@link JS} as well as all the methods it overrides.
173+
*/
174+
public static Set<Method> findBaseMethodsOfJSAnnotated(ImageClassLoader imageClassLoader) {
175+
Set<Method> methods = new HashSet<>();
212176

213-
private static void markBaseMethods(Method method, Class<?> type, HashSet<Method> set, HashSet<Class<?>> seen) {
214-
if (seen.contains(type)) {
215-
// We already saw this type -- we skip it to avoid traversing an interface type twice.
216-
return;
217-
}
218-
seen.add(type);
219-
if (!set.contains(method)) {
220-
set.add(method);
221-
}
222-
if (Modifier.isPrivate(method.getModifiers()) || Modifier.isStatic(method.getModifiers())) {
223-
// Private and static methods cannot override anything.
224-
return;
225-
}
226-
resolveAndMarkBaseMethods(method, type.getSuperclass(), set, seen);
227-
for (Class<?> i : type.getInterfaces()) {
228-
resolveAndMarkBaseMethods(method, i, set, seen);
177+
List<Method> annotatedMethods = imageClassLoader.findAnnotatedMethods(JS.class);
178+
179+
for (Method annotatedMethod : annotatedMethods) {
180+
findBaseMethods(annotatedMethod, annotatedMethod.getDeclaringClass(), methods);
229181
}
182+
183+
return methods;
230184
}
231185

232-
private static void resolveAndMarkBaseMethods(Method method, Class<?> type, HashSet<Method> set, HashSet<Class<?>> seen) {
233-
if (type == null) {
234-
// We reached Object's superclass.
186+
/**
187+
* Finds all methods the given {@code originalMethod} overrides (including itself) from the
188+
* given class upwards and adds them to the given set.
189+
*/
190+
private static void findBaseMethods(Method originalMethod, Class<?> clazz, Set<Method> jsOverridenMethodSet) {
191+
if (clazz == null) {
235192
return;
236193
}
237-
for (Method declaredMethod : type.getDeclaredMethods()) {
238-
if (isOverriding(method, declaredMethod)) {
239-
markBaseMethods(declaredMethod, type, set, seen);
240-
return;
241-
}
242-
}
243-
// There is no override in the current class, continue using the subtype's method.
244-
markBaseMethods(method, type, set, seen);
245-
}
246194

247-
private static boolean isOverriding(Method method, Method baseMethod) {
248-
if (method.getParameterCount() != baseMethod.getParameterCount() || !method.getName().equals(baseMethod.getName())) {
249-
return false;
195+
// This is either the same method as originalMethod or a method it overrides.
196+
Method baseMethod = ReflectionUtil.lookupMethod(true, clazz, originalMethod.getName(), originalMethod.getParameterTypes());
197+
198+
if (baseMethod != null) {
199+
jsOverridenMethodSet.add(baseMethod);
250200
}
251-
Class<?>[] parameters = method.getParameterTypes();
252-
Class<?>[] baseParameters = baseMethod.getParameterTypes();
253-
for (int i = 0; i < parameters.length; i++) {
254-
if (!parameters[i].equals(baseParameters[i])) {
255-
return false;
256-
}
201+
202+
for (Class<?> clazzInterface : clazz.getInterfaces()) {
203+
findBaseMethods(originalMethod, clazzInterface, jsOverridenMethodSet);
257204
}
258-
return true;
205+
206+
findBaseMethods(originalMethod, clazz.getSuperclass(), jsOverridenMethodSet);
259207
}
208+
260209
}

0 commit comments

Comments
 (0)