Skip to content

Commit a0b466a

Browse files
Pack spread varargs arguments into one array argument (#349)
Arguments were matched one-to-one with the method parameters, so a call to a varargs method with more arguments than parameters indexed past the parameter list and crashed (`Index 1 out of bounds for length 1`), and a call with zero or one spread argument was checked against the varargs parameter as if that argument were the array itself. When a call passes its trailing arguments individually (the argument count differs from the parameter count, or the last argument is not a subtype of the array type and not null), the varargs parameter is now bound to a fresh array instance whose only refinement is its length (the number of spread arguments). Refinements on the varargs parameter are checked against that array; the individual elements are not checked against anything. Passing an explicit array keeps the one-to-one mapping. Parameter checking now iterates over the mapped parameters instead of the arguments. Fixes #349 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 3e3aac3 commit a0b466a

4 files changed

Lines changed: 111 additions & 9 deletions

File tree

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package testSuite;
2+
3+
import liquidjava.specification.Refinement;
4+
5+
public class CorrectVarargsCall {
6+
7+
static int count(String... names) {
8+
return names.length;
9+
}
10+
11+
static int first(@Refinement("length(xs) > 0") int... xs) {
12+
return xs[0];
13+
}
14+
15+
static int pick(@Refinement("k >= 0") int k, int... xs) {
16+
return k;
17+
}
18+
19+
CorrectVarargsCall(@Refinement("length(xs) > 1") int... xs) {
20+
}
21+
22+
public static void main(String[] args) {
23+
int a = count();
24+
int b = count("a");
25+
int c = count("a", "b", "c");
26+
int d = count(new String[] { "x" });
27+
int e = count(args);
28+
int f = first(1, 2);
29+
int g = first(5);
30+
int h = pick(0);
31+
int i = pick(1, 2, 3);
32+
int j = count("a", "b") + 1;
33+
new CorrectVarargsCall(1, 2);
34+
}
35+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package testSuite;
2+
3+
import liquidjava.specification.Refinement;
4+
5+
public class ErrorEmptyVarargsCall {
6+
7+
static int first(@Refinement("length(xs) > 0") int... xs) {
8+
return xs[0];
9+
}
10+
11+
public static void main(String[] args) {
12+
int a = first(); // Expect: Refinement Error
13+
}
14+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package testSuite;
2+
3+
import liquidjava.specification.Refinement;
4+
5+
public class ErrorVarargsCall {
6+
7+
static int pick(@Refinement("k >= 0") int k, int... xs) {
8+
return k;
9+
}
10+
11+
public static void main(String[] args) {
12+
int i = pick(-1, 2, 3); // Expect: Refinement Error
13+
}
14+
}

‎liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/general_checkers/MethodsFunctionsChecker.java‎

Lines changed: 48 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,12 @@
2020
import liquidjava.utils.constants.Formats;
2121
import liquidjava.utils.constants.Keys;
2222
import liquidjava.utils.constants.Types;
23+
import spoon.reflect.code.CtAbstractInvocation;
2324
import spoon.reflect.code.CtConstructorCall;
2425
import spoon.reflect.code.CtExpression;
2526
import spoon.reflect.code.CtFieldRead;
2627
import spoon.reflect.code.CtInvocation;
28+
import spoon.reflect.code.CtLiteral;
2729
import spoon.reflect.code.CtReturn;
2830
import spoon.reflect.code.CtVariableRead;
2931
import spoon.reflect.declaration.CtClass;
@@ -332,7 +334,7 @@ private Map<String, String> checkInvocationRefinements(CtElement invocation, Lis
332334
f = rtc.getContext().getFunction(methodName, className, paramTypes);
333335
if (f == null)
334336
return new HashMap<>();
335-
Map<String, String> map = mapInvocation(arguments, f);
337+
Map<String, String> map = mapInvocation(invocation, arguments, f);
336338

337339
String returnViName = prepareReturnInstance(invocation, f);
338340

@@ -351,7 +353,7 @@ private Map<String, String> checkInvocationRefinements(CtElement invocation, Lis
351353
return map;
352354
}
353355

354-
checkParameters(invocation, arguments, f, map);
356+
checkParameters(invocation, f, map);
355357

356358
if (target != null)
357359
AuxStateHandler.checkTargetChanges(rtc, f, target, map, invocation);
@@ -412,10 +414,18 @@ private void applyReturnRefinement(CtElement invocation, RefinedFunction f, Map<
412414
invocation.putMetadata(Keys.REFINEMENT, methodRef);
413415
}
414416

415-
private Map<String, String> mapInvocation(List<CtExpression<?>> arguments, RefinedFunction f) {
417+
private Map<String, String> mapInvocation(CtElement invocation, List<CtExpression<?>> arguments,
418+
RefinedFunction f) {
416419
Map<String, String> mapInvocation = new HashMap<>();
417420
List<Variable> functionParams = f.getArguments();
418-
for (int i = 0; i < arguments.size(); i++) {
421+
int mapped = arguments.size();
422+
if (spreadsVarargs(invocation, arguments, functionParams.size())) {
423+
// the trailing arguments are packed into a new array, which is what the varargs parameter refers to
424+
mapped = functionParams.size() - 1;
425+
Variable varargs = functionParams.get(mapped);
426+
mapInvocation.put(varargs.getName(), createVarargsArray(invocation, varargs, arguments.size() - mapped));
427+
}
428+
for (int i = 0; i < mapped; i++) {
419429
Variable fArg = functionParams.get(i);
420430
CtExpression<?> iArg = arguments.get(i);
421431
String invStr;
@@ -433,6 +443,36 @@ private Map<String, String> mapInvocation(List<CtExpression<?>> arguments, Refin
433443
return mapInvocation;
434444
}
435445

446+
/**
447+
* Whether a call to a varargs method passes its trailing arguments individually (zero, one or several of them)
448+
* instead of passing an array as the varargs parameter.
449+
*/
450+
private boolean spreadsVarargs(CtElement invocation, List<CtExpression<?>> arguments, int paramCount) {
451+
if (paramCount == 0 || !(invocation instanceof CtAbstractInvocation<?> inv))
452+
return false;
453+
CtExecutable<?> method = inv.getExecutable().getExecutableDeclaration();
454+
if (method == null || method.getParameters().size() != paramCount)
455+
return false;
456+
CtParameter<?> last = method.getParameters().get(paramCount - 1);
457+
if (!last.isVarArgs())
458+
return false;
459+
if (arguments.size() != paramCount)
460+
return true;
461+
CtExpression<?> lastArg = arguments.get(paramCount - 1);
462+
if (lastArg instanceof CtLiteral<?> lit && lit.getValue() == null)
463+
return false; // a null array
464+
CtTypeReference<?> argType = lastArg.getType();
465+
return argType == null || !argType.isSubtypeOf(last.getType());
466+
}
467+
468+
private String createVarargsArray(CtElement invocation, Variable fArg, int length) {
469+
String nVar = String.format(Formats.INSTANCE, fArg.getName(), rtc.getContext().getCounter());
470+
Predicate lengthRef = Predicate.createEquals(Predicate.createInvocation("length", Predicate.createVar(nVar)),
471+
Predicate.createLit(String.valueOf(length), Types.INT));
472+
rtc.getContext().addInstanceToContext(nVar, fArg.getType(), lengthRef, invocation);
473+
return nVar;
474+
}
475+
436476
private String createVariableRepresentingArgument(CtExpression<?> iArg, Variable fArg) {
437477
Predicate met = (Predicate) iArg.getMetadata(Keys.REFINEMENT);
438478
if (met == null)
@@ -444,11 +484,10 @@ private String createVariableRepresentingArgument(CtExpression<?> iArg, Variable
444484
return nVar;
445485
}
446486

447-
private void checkParameters(CtElement invocation, List<CtExpression<?>> arguments, RefinedFunction f,
448-
Map<String, String> map) throws LJError {
449-
List<Variable> functionParams = f.getArguments();
450-
for (int i = 0; i < arguments.size(); i++) {
451-
Variable fArg = functionParams.get(i);
487+
private void checkParameters(CtElement invocation, RefinedFunction f, Map<String, String> map) throws LJError {
488+
for (Variable fArg : f.getArguments()) {
489+
if (!map.containsKey(fArg.getName()))
490+
continue;
452491
Predicate c = fArg.getMainRefinement();
453492
c = c.substituteVariable(fArg.getName(), map.get(fArg.getName()));
454493
List<String> vars = c.getVariableNames();

0 commit comments

Comments
 (0)