Skip to content

Commit 7f18649

Browse files
Look up spread varargs calls by declared arity in operations and loops
Arithmetic operands and loop havoc looked methods up by the call's argument count, so a call that spreads its varargs missed the declaration: its return refinement was lost inside an expression, and a typestate-changing varargs call in a loop did not havoc its receiver, letting a post-loop state violation pass. Both now use the number of parameters the method declares. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 8843527 commit 7f18649

5 files changed

Lines changed: 49 additions & 3 deletions

File tree

‎liquidjava-example/src/main/java/testSuite/CorrectVarargsCall.java‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ static int pick(@Refinement("k >= 0") int k, int... xs) {
1616
return k;
1717
}
1818

19+
@Refinement("_ == 7")
20+
static int seven(int... xs) {
21+
return 7;
22+
}
23+
1924
CorrectVarargsCall(@Refinement("length(xs) > 1") int... xs) {
2025
}
2126

@@ -30,6 +35,8 @@ public static void main(String[] args) {
3035
int h = pick(0);
3136
int i = pick(1, 2, 3);
3237
int j = count("a", "b") + 1;
38+
@Refinement("_ == 8")
39+
int k = seven(1, 2) + 1;
3340
new CorrectVarargsCall(1, 2);
3441
}
3542
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package testSuite;
2+
3+
import liquidjava.specification.StateRefinement;
4+
import liquidjava.specification.StateSet;
5+
6+
@StateSet({"open", "marked"})
7+
public class ErrorVarargsLoopState {
8+
@StateRefinement(to = "open(this)")
9+
ErrorVarargsLoopState() {}
10+
11+
@StateRefinement(to = "marked(this)")
12+
void mark(int... tags) {}
13+
14+
@StateRefinement(from = "marked(this)", to = "open(this)")
15+
void reset() {}
16+
17+
// The loop may run zero times, leaving b open, even when mark spreads its varargs.
18+
static void mayBeEmpty(int n) {
19+
ErrorVarargsLoopState b = new ErrorVarargsLoopState();
20+
int k = 0;
21+
while (k < n) {
22+
b.mark(1, 2);
23+
k++;
24+
}
25+
b.reset(); // Expect: State Refinement Error
26+
}
27+
}

‎liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/RefinementTypeChecker.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -677,7 +677,7 @@ private void havocChangedIn(CtLoop loop) {
677677
Set<String> names = new LinkedHashSet<>();
678678
for (CtAbstractInvocation<?> call : calls) {
679679
if (call instanceof CtInvocation<?> inv
680-
&& context.getAllMethodsWithNameSize(inv.getExecutable().getSimpleName(), inv.getArguments().size())
680+
&& context.getAllMethodsWithNameSize(inv.getExecutable().getSimpleName(), Utils.declaredArity(inv))
681681
.stream().anyMatch(f -> f.getAllStates().stream().anyMatch(ObjectState::hasTo))) {
682682
if (inv.getTarget()instanceof CtVariableAccess<?> target)
683683
changed.add(target);

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,8 @@ private Predicate getOperationRefinements(CtBinaryOperator<?> operator, CtVariab
275275

276276
// Get function refinements with non_used variables
277277
String met = method.getParent(CtType.class).getQualifiedName(); // TODO check
278-
RefinedFunction fi = rtc.getContext().getFunction(method.getSimpleName(), met, inv.getArguments().size());
278+
RefinedFunction fi = rtc.getContext().getFunction(method.getSimpleName(), met,
279+
method.getParameters().size());
279280
if (fi == null)
280281
return getUnconstrainedInvocationVariable(inv);
281282
Predicate innerRefs = fi.getRenamedRefinements(rtc.getContext(), inv); // TODO REVIEW!!
@@ -313,7 +314,7 @@ private Predicate getOperationRefinementFromExternalLib(CtInvocation<?> inv) thr
313314
String typeNotParametrized = (i > 0) ? c.substring(0, i) : c;
314315
String methodInClassName = typeNotParametrized + "." + simpleName;
315316
RefinedFunction fi = rtc.getContext().getFunction(methodInClassName, typeNotParametrized,
316-
inv.getArguments().size());
317+
Utils.declaredArity(inv));
317318
if (fi == null)
318319
return getUnconstrainedInvocationVariable(inv);
319320
Predicate innerRefs = fi.getRenamedRefinements(rtc.getContext(), inv); // TODO REVIEW!!

‎liquidjava-verifier/src/main/java/liquidjava/utils/Utils.java‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import java.util.stream.Stream;
99

1010
import liquidjava.utils.constants.Types;
11+
import spoon.reflect.code.CtAbstractInvocation;
1112
import spoon.reflect.code.CtBlock;
1213
import spoon.reflect.code.CtFor;
1314
import spoon.reflect.code.CtForEach;
@@ -18,6 +19,7 @@
1819
import spoon.reflect.declaration.CtAnnotation;
1920
import spoon.reflect.declaration.CtConstructor;
2021
import spoon.reflect.declaration.CtElement;
22+
import spoon.reflect.declaration.CtExecutable;
2123
import spoon.reflect.declaration.CtMethod;
2224
import spoon.reflect.factory.Factory;
2325
import spoon.reflect.reference.CtFieldReference;
@@ -56,6 +58,15 @@ public static String qualifyFieldName(CtFieldReference<?> field) {
5658
return "this#" + field.getDeclaringType().getQualifiedName() + "." + field.getSimpleName();
5759
}
5860

61+
/**
62+
* Number of parameters the invoked method declares, which differs from the number of arguments when a call spreads
63+
* its varargs
64+
*/
65+
public static int declaredArity(CtAbstractInvocation<?> invocation) {
66+
CtExecutable<?> method = invocation.getExecutable().getExecutableDeclaration();
67+
return method != null ? method.getParameters().size() : invocation.getExecutable().getParameters().size();
68+
}
69+
5970
public static String getFile(CtElement element) {
6071
SourcePosition pos = element.getPosition();
6172
if (pos == null || pos.getFile() == null)

0 commit comments

Comments
 (0)