Skip to content

Commit b2cf676

Browse files
Check typestate calls made directly on a constructor call
Fixes #336. In `new T(args).method()` the receiver had no variable instance, so the method's from-state was never checked. The constructor call now gets a fresh instance in the state the constructor gives (with its supertypes), and the call is checked and transitions it like any other receiver. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent be47cb6 commit b2cf676

3 files changed

Lines changed: 62 additions & 0 deletions

File tree

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package testSuite;
2+
3+
import liquidjava.specification.StateRefinement;
4+
import liquidjava.specification.StateSet;
5+
6+
@StateSet({"fresh", "used"})
7+
class CorrectReceiverToken {
8+
@StateRefinement(to = "fresh(this)")
9+
public CorrectReceiverToken() {}
10+
11+
@StateRefinement(to = "used(this)")
12+
public CorrectReceiverToken(int spent) {}
13+
14+
@StateRefinement(from = "fresh(this)", to = "used(this)")
15+
public void use() {}
16+
17+
@StateRefinement(from = "used(this)")
18+
public void report() {}
19+
}
20+
21+
public class CorrectConstructorReceiver {
22+
static void chained() {
23+
new CorrectReceiverToken().use();
24+
new CorrectReceiverToken(1).report();
25+
}
26+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package testSuite;
2+
3+
import liquidjava.specification.StateRefinement;
4+
import liquidjava.specification.StateSet;
5+
6+
@StateSet({"fresh", "used"})
7+
class ErrorReceiverToken {
8+
@StateRefinement(to = "fresh(this)")
9+
public ErrorReceiverToken() {}
10+
11+
@StateRefinement(to = "used(this)")
12+
public ErrorReceiverToken(int spent) {}
13+
14+
@StateRefinement(from = "fresh(this)", to = "used(this)")
15+
public void use() {}
16+
17+
@StateRefinement(from = "used(this)")
18+
public void report() {}
19+
}
20+
21+
public class ErrorConstructorReceiver {
22+
static void chained() {
23+
new ErrorReceiverToken(1).use(); // Expect: State Refinement Error
24+
}
25+
}

‎liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxStateHandler.java‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -647,6 +647,17 @@ public static String prepareInvocationTarget(TypeChecker tc, CtElement target2,
647647
Optional<Variable> v = target2Vi.getParent();
648648
invocation.putMetadata(Keys.TARGET, target2Vi);
649649
return v.map(Refined::getName).orElse(target2Vi.getName());
650+
} else if (target2 instanceof CtConstructorCall<?> newCall
651+
&& newCall.getMetadata(Keys.REFINEMENT)instanceof Predicate state) {
652+
// `new T(args).method()`: the receiver is a fresh object in the state the constructor gives
653+
String name = String.format(Formats.FRESH, tc.getContext().getCounter());
654+
CtTypeReference<?> type = newCall.getType();
655+
RefinedVariable receiver = tc.getContext().addInstanceToContext(name, type,
656+
state.substituteVariable(Keys.THIS, name).substituteVariable(Keys.WILDCARD, name), newCall);
657+
receiver.addSuperTypes(type.getSuperclass(), type.getSuperInterfaces());
658+
newCall.putMetadata(Keys.TARGET, receiver);
659+
invocation.putMetadata(Keys.TARGET, receiver);
660+
return name;
650661
}
651662
return null;
652663
}

0 commit comments

Comments
 (0)