Skip to content

Commit cfa09da

Browse files
committed
Fix Refinements After Early Return
1 parent d91f256 commit cfa09da

3 files changed

Lines changed: 35 additions & 6 deletions

File tree

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package testSuite;
2+
3+
import liquidjava.specification.Refinement;
4+
5+
public class CorrectEarlyReturn {
6+
7+
public static int divide(int a, @Refinement("b != 0") int b) {
8+
return a / b;
9+
}
10+
11+
public static void divideUnlessZero(int x, int y) {
12+
if (y == 0) return;
13+
divide(x, y);
14+
}
15+
}

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

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ public <T> void visitCtConstructor(CtConstructor<T> constructor) {
114114
}
115115
contextHistory.saveContext(constructor, context);
116116
context.exitContext();
117+
vcChecker.clearPathVariables();
117118
}
118119

119120
public <R> void visitCtMethod(CtMethod<R> method) {
@@ -128,6 +129,7 @@ public <R> void visitCtMethod(CtMethod<R> method) {
128129
}
129130
contextHistory.saveContext(method, context);
130131
context.exitContext();
132+
vcChecker.clearPathVariables();
131133
}
132134

133135
@Override
@@ -409,30 +411,38 @@ public void visitCtIf(CtIf ifElement) {
409411
// VISIT THEN
410412
context.enterContext();
411413
visitCtBlock(ifElement.getThenStatement());
412-
if (canCompleteNormally(ifElement.getThenStatement())) {
414+
boolean thenCompletes = canCompleteNormally(ifElement.getThenStatement());
415+
if (thenCompletes) {
413416
context.variablesSetThenIf();
414417
}
415418
contextHistory.saveContext(ifElement.getThenStatement(), context);
416419
context.exitContext();
417420

418421
// VISIT ELSE
422+
boolean elseCompletes = true;
419423
if (ifElement.getElseStatement() != null) {
420424
context.getVariableByName(pathVarName);
421425
context.newRefinementToVariableInContext(pathVarName, elseRefs);
422426

423427
context.enterContext();
424428
visitCtBlock(ifElement.getElseStatement());
425-
if (canCompleteNormally(ifElement.getElseStatement())) {
429+
elseCompletes = canCompleteNormally(ifElement.getElseStatement());
430+
if (elseCompletes) {
426431
context.variablesSetElseIf();
427432
}
428433
contextHistory.saveContext(ifElement.getElseStatement(), context);
429434
context.exitContext();
430435
}
431436
// end
432-
// Reset the path variable's refinement to the original condition after the if,
433-
// so branch-local truth assertions (and any typestate they imply) don't leak past the join.
434-
context.newRefinementToVariableInContext(pathVarName, expRefs);
435-
vcChecker.removePathVariable(freshRV);
437+
if (thenCompletes == elseCompletes) {
438+
// Reset the path variable's refinement to the original condition after the if,
439+
// so branch-local truth assertions (and any typestate they imply) don't leak past the join.
440+
context.newRefinementToVariableInContext(pathVarName, expRefs);
441+
vcChecker.removePathVariable(freshRV);
442+
} else {
443+
// Keep the refinement of the only branch that reaches the code after the if.
444+
context.newRefinementToVariableInContext(pathVarName, thenCompletes ? thenRefs : elseRefs);
445+
}
436446
context.exitContext();
437447
context.variablesCombineFromIf(expRefs);
438448
context.variablesFinishIfCombination();

liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/VCChecker.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,10 @@ public void removePathVariable(RefinedVariable rv) {
353353
pathVariables.remove(rv);
354354
}
355355

356+
void clearPathVariables() {
357+
pathVariables.clear();
358+
}
359+
356360
void removePathVariableThatIncludes(String otherVar) {
357361
pathVariables.stream().filter(rv -> rv.getRefinement().getVariableNames().contains(otherVar)).toList()
358362
.forEach(pathVariables::remove);

0 commit comments

Comments
 (0)