Skip to content

Commit 99c8db0

Browse files
committed
[GR-66539] Fix varargs handling in HostExecuteNode.
PullRequest: graal/21268
2 parents 49350e5 + 1b545dc commit 99c8db0

File tree

2 files changed

+172
-6
lines changed

2 files changed

+172
-6
lines changed
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
/*
2+
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* The Universal Permissive License (UPL), Version 1.0
6+
*
7+
* Subject to the condition set forth below, permission is hereby granted to any
8+
* person obtaining a copy of this software, associated documentation and/or
9+
* data (collectively the "Software"), free of charge and under any and all
10+
* copyright rights in the Software, and any and all patent rights owned or
11+
* freely licensable by each licensor hereunder covering either (i) the
12+
* unmodified Software as contributed to or provided by such licensor, or (ii)
13+
* the Larger Works (as defined below), to deal in both
14+
*
15+
* (a) the Software, and
16+
*
17+
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
18+
* one is included with the Software each a "Larger Work" to which the Software
19+
* is contributed by such licensors),
20+
*
21+
* without restriction, including without limitation the rights to copy, create
22+
* derivative works of, display, perform, and distribute the Software and make,
23+
* use, sell, offer for sale, import, export, have made, and have sold the
24+
* Software and the Larger Work(s), and to sublicense the foregoing rights on
25+
* either these or other terms.
26+
*
27+
* This license is subject to the following condition:
28+
*
29+
* The above copyright notice and either this complete permission notice or at a
30+
* minimum a reference to the UPL must be included in all copies or substantial
31+
* portions of the Software.
32+
*
33+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
39+
* SOFTWARE.
40+
*/
41+
package com.oracle.truffle.api.test.host;
42+
43+
import static org.junit.Assert.assertArrayEquals;
44+
import static org.junit.Assert.assertNull;
45+
46+
import java.util.List;
47+
import java.util.concurrent.atomic.AtomicBoolean;
48+
49+
import org.graalvm.polyglot.Context;
50+
import org.graalvm.polyglot.HostAccess;
51+
import org.graalvm.polyglot.Value;
52+
import org.junit.Test;
53+
import org.junit.runner.RunWith;
54+
import org.junit.runners.Parameterized;
55+
import org.junit.runners.Parameterized.Parameter;
56+
import org.junit.runners.Parameterized.Parameters;
57+
58+
@RunWith(Parameterized.class)
59+
public class GR66539Test {
60+
61+
@Parameter(0) public String methodName;
62+
63+
@Parameters(name = "{0}")
64+
public static List<String> data() {
65+
return List.of("directMethod", "overloadedMethod");
66+
}
67+
68+
/*
69+
* Originally reported bug.
70+
*/
71+
@Test
72+
public void test1Direct() {
73+
try (Context context = Context.create()) {
74+
Value v = context.asValue(new GR66539Test());
75+
76+
assertNull(v.invokeMember(methodName, "a", null).as(Object[].class));
77+
assertArrayEquals(new Object[]{"b"}, v.invokeMember(methodName, "a", "b").as(Object[].class));
78+
}
79+
}
80+
81+
@Test
82+
public void test2Direct() {
83+
try (Context context = Context.create()) {
84+
Value v = context.asValue(new GR66539Test());
85+
Object[] testArray = new Object[2];
86+
87+
assertArrayEquals(new Object[]{null, null}, v.invokeMember(methodName, "a", testArray).as(Object[].class));
88+
assertArrayEquals(new Object[]{"b"}, v.invokeMember(methodName, "a", "b").as(Object[].class));
89+
}
90+
}
91+
92+
@Test
93+
public void test3Direct() {
94+
AtomicBoolean targetMappingEnabled = new AtomicBoolean(true);
95+
Object testArray = new Object();
96+
97+
HostAccess access = HostAccess.newBuilder(HostAccess.EXPLICIT)//
98+
.targetTypeMapping(Value.class, Object[].class, (v) -> targetMappingEnabled.get() && v.isHostObject() && v.asHostObject() == testArray, (v) -> {
99+
if (targetMappingEnabled.get()) {
100+
return new Object[1];
101+
} else {
102+
throw new ClassCastException();
103+
}
104+
})//
105+
.build();
106+
107+
try (Context context = Context.newBuilder().allowHostAccess(access).build()) {
108+
Value v = context.asValue(new GR66539Test());
109+
110+
assertArrayEquals(new Object[]{null}, v.invokeMember(methodName, "a", testArray).as(Object[].class));
111+
targetMappingEnabled.set(false);
112+
113+
// testing that this does not throw now when the targetTypeMapping changes
114+
assertArrayEquals(new Object[]{testArray}, v.invokeMember(methodName, "a", testArray).as(Object[].class));
115+
}
116+
}
117+
118+
@HostAccess.Export
119+
@SuppressWarnings("unused")
120+
public Object[] directMethod(String a, final Object... b) {
121+
return b;
122+
}
123+
124+
@HostAccess.Export
125+
@SuppressWarnings("unused")
126+
public Object[] overloadedMethod(String a, final Object... b) {
127+
return b;
128+
}
129+
130+
@HostAccess.Export
131+
@SuppressWarnings("unused")
132+
public Object[] overloadedMethod(Object a, final Object... b) {
133+
// not supposed to be called
134+
throw new AssertionError();
135+
}
136+
137+
}

truffle/src/com.oracle.truffle.host/src/com/oracle/truffle/host/HostExecuteNode.java

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -152,9 +152,23 @@ static Object doVarArgs(Node node, SingleMethod method, Object obj, Object[] arg
152152
@Exclusive @Cached InlinedExactClassProfile receiverProfile,
153153
@Shared("errorBranch") @Cached InlinedBranchProfile errorBranch,
154154
@Shared("seenScope") @Cached InlinedBranchProfile seenDynamicScope,
155-
@Cached("asVarArgs(args, cachedMethod, hostContext)") boolean asVarArgs,
155+
@Shared @Cached InlinedBranchProfile seenVargArgs,
156+
@Exclusive @Cached HostTargetMappingNode varArgsMappingNode,
157+
@Exclusive @CachedLibrary(limit = "3") InteropLibrary varArgsMappingInterop,
156158
@Cached(value = "hostContext.getGuestToHostCache()", allowUncached = true) GuestToHostCodeCache cache) throws ArityException, UnsupportedTypeException {
159+
boolean asVarArgs;
157160
int parameterCount = cachedMethod.getParameterCount();
161+
if (args.length == parameterCount) {
162+
seenVargArgs.enter(node);
163+
Class<?> varArgParamType = cachedMethod.getParameterTypes()[parameterCount - 1];
164+
asVarArgs = !HostToTypeNode.canConvert(node, args[parameterCount - 1], varArgParamType,
165+
cachedMethod.getGenericParameterTypes()[parameterCount - 1],
166+
null, hostContext, HostToTypeNode.COERCE,
167+
varArgsMappingInterop, varArgsMappingNode);
168+
} else {
169+
assert args.length != parameterCount;
170+
asVarArgs = true;
171+
}
158172
int minArity = parameterCount - 1;
159173
if (args.length < minArity) {
160174
errorBranch.enter(node);
@@ -244,19 +258,35 @@ static Object doSingleUncached(Node node, SingleMethod method, Object obj, Objec
244258
// Note: checkArgTypes must be evaluated after selectOverload.
245259
@SuppressWarnings({"unused", "static-method", "truffle-static-method"})
246260
@ExplodeLoop
247-
@Specialization(guards = {"method == cachedMethod", "checkArgTypes(args, cachedArgTypes, interop, hostContext, asVarArgs)"}, limit = "LIMIT")
261+
@Specialization(guards = {"method == cachedMethod", "checkArgTypes(args, cachedArgTypes, interop, hostContext)"}, limit = "LIMIT")
248262
static final Object doOverloadedCached(Node node, OverloadedMethod method, Object obj, Object[] args, HostContext hostContext,
249263
@Cached("method") OverloadedMethod cachedMethod,
250264
@Exclusive @Cached HostToTypeNode toJavaNode,
251265
@Exclusive @Cached ToGuestValueNode toGuest,
252266
@CachedLibrary(limit = "LIMIT") InteropLibrary interop,
253267
@Cached("createArgTypesArray(args)") TypeCheckNode[] cachedArgTypes,
254268
@Cached("selectOverload(node, method, args, hostContext, cachedArgTypes)") SingleMethod overload,
255-
@Cached("asVarArgs(args, overload, hostContext)") boolean asVarArgs,
256269
@Exclusive @Cached InlinedExactClassProfile receiverProfile,
257270
@Shared("errorBranch") @Cached InlinedBranchProfile errorBranch,
258271
@Shared("seenScope") @Cached InlinedBranchProfile seenVariableScope,
272+
@Shared @Cached InlinedBranchProfile seenVargArgs,
273+
@Exclusive @Cached HostTargetMappingNode varArgsMappingNode,
274+
@Exclusive @CachedLibrary(limit = "3") InteropLibrary varArgsMappingInterop,
259275
@Cached(value = "hostContext.getGuestToHostCache()", allowUncached = true) GuestToHostCodeCache cache) throws ArityException, UnsupportedTypeException {
276+
277+
boolean asVarArgs;
278+
int parameterCount = cachedArgTypes.length;
279+
if (overload.isVarArgs()) {
280+
seenVargArgs.enter(node);
281+
Class<?> varArgParamType = overload.getParameterTypes()[parameterCount - 1];
282+
asVarArgs = !HostToTypeNode.canConvert(node, args[parameterCount - 1], varArgParamType,
283+
overload.getGenericParameterTypes()[parameterCount - 1],
284+
null, hostContext, HostToTypeNode.COERCE,
285+
varArgsMappingInterop, varArgsMappingNode);
286+
} else {
287+
asVarArgs = false;
288+
}
289+
260290
assert overload == selectOverload(node, method, args, hostContext);
261291
Class<?>[] types = overload.getParameterTypes();
262292
Type[] genericTypes = overload.getGenericParameterTypes();
@@ -267,7 +297,6 @@ static final Object doOverloadedCached(Node node, OverloadedMethod method, Objec
267297
try {
268298
if (asVarArgs) {
269299
assert overload.isVarArgs();
270-
int parameterCount = overload.getParameterCount();
271300
for (int i = 0; i < cachedArgTypes.length; i++) {
272301
Class<?> expectedType = i < parameterCount - 1 ? types[i] : types[parameterCount - 1].getComponentType();
273302
Type expectedGenericType = i < parameterCount - 1 ? genericTypes[i] : getGenericComponentType(genericTypes[parameterCount - 1]);
@@ -417,7 +446,7 @@ private static void fillArgTypesArray(Node node, Object[] args, TypeCheckNode[]
417446
cachedArgTypes[i] = node.insert(argType);
418447
}
419448

420-
assert checkArgTypes(args, cachedArgTypes, InteropLibrary.getFactory().getUncached(), context, false) : Arrays.toString(cachedArgTypes);
449+
assert checkArgTypes(args, cachedArgTypes, InteropLibrary.getFactory().getUncached(), context) : Arrays.toString(cachedArgTypes);
421450
}
422451

423452
private static TypeCheckNode createPrimitiveTargetCheck(List<SingleMethod> applicable, SingleMethod selected, Object arg, Class<?> targetType, int parameterIndex, int priority, boolean varArgs,
@@ -454,7 +483,7 @@ private static TypeCheckNode createPrimitiveTargetCheck(List<SingleMethod> appli
454483
}
455484

456485
@ExplodeLoop
457-
static boolean checkArgTypes(Object[] args, TypeCheckNode[] argTypes, InteropLibrary interop, HostContext context, @SuppressWarnings("unused") boolean dummy) {
486+
static boolean checkArgTypes(Object[] args, TypeCheckNode[] argTypes, InteropLibrary interop, HostContext context) {
458487
if (args.length != argTypes.length) {
459488
return false;
460489
}

0 commit comments

Comments
 (0)