Skip to content

Commit abb2c43

Browse files
new example and small fixes
1 parent 6c43c32 commit abb2c43

5 files changed

Lines changed: 108 additions & 17 deletions

File tree

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package testSuite.classes.ts_bufferedreader_correct;
2+
import java.io.IOException;
3+
import java.io.Reader;
4+
import java.util.stream.Stream;
5+
6+
import liquidjava.specification.ExternalRefinementsFor;
7+
import liquidjava.specification.Refinement;
8+
import liquidjava.specification.RefinementAlias;
9+
import liquidjava.specification.StateRefinement;
10+
import liquidjava.specification.StateSet;
11+
12+
@RefinementAlias("NonNegative(int v) { v >= 0 }")
13+
@RefinementAlias("Positive(int v) { v > 0 }")
14+
@StateSet({"open", "marked", "closed"})
15+
@ExternalRefinementsFor("java.io.BufferedReader")
16+
public interface BufferedReaderRefinementsExpert {
17+
18+
@StateRefinement(to="open(this)")
19+
public void BufferedReader(Reader in);
20+
21+
@StateRefinement(to="open(this)")
22+
public void BufferedReader(Reader in, @Refinement("Positive(_)") int sz);
23+
24+
@StateRefinement(from="open(this)")
25+
@StateRefinement(from="marked(this)")
26+
public int read();
27+
28+
@StateRefinement(from="open(this)")
29+
@StateRefinement(from="marked(this)")
30+
@Refinement(" _ >= -1")
31+
public int read(char[] cbuf, @Refinement("NonNegative(_)") int off, @Refinement("NonNegative(_)") int len);
32+
33+
@StateRefinement(from="open(this)")
34+
@StateRefinement(from="marked(this)")
35+
public String readLine() throws IOException;
36+
37+
@StateRefinement(from="open(this)")
38+
@StateRefinement(from="marked(this)")
39+
public boolean ready() throws IOException;
40+
41+
@StateRefinement(from="open(this)")
42+
@StateRefinement(from="marked(this)")
43+
public boolean markSupported();
44+
45+
@StateRefinement(from="open(this)", to="marked(this)")
46+
@StateRefinement(from="marked(this)")
47+
public void mark(@Refinement("NonNegative(_)") int readAheadLimit);
48+
49+
@StateRefinement(from="marked(this)", to="open(this)")
50+
public void reset();
51+
52+
@StateRefinement(from="!closed(this)", to="closed(this)")
53+
public void close();
54+
55+
@StateRefinement(from="open(this)")
56+
@StateRefinement(from="marked(this)")
57+
public Stream<String> lines();
58+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package testSuite.classes.ts_bufferedreader_correct;
2+
import java.io.BufferedReader;
3+
import java.io.FileReader;
4+
import java.io.IOException;
5+
6+
// Regression test for unrefined conditions: each branch reads (open/marked) before
7+
// closing. The verifier must not assume the if-condition is unconditionally true,
8+
// otherwise it would conclude the reader is always closed by line 24 and flag the
9+
// readLine() call on line 31 as a state-refinement error.
10+
11+
class ConfigLoader {
12+
13+
String loadFirstSetting(String configPath, String defaultValue) throws IOException {
14+
BufferedReader reader = new BufferedReader(new FileReader(configPath));
15+
16+
String header = reader.readLine();
17+
if (header.isEmpty()) {
18+
reader.close();
19+
return defaultValue;
20+
}
21+
22+
if (header.startsWith("#")) {
23+
String value = reader.readLine();
24+
reader.close();
25+
return value;
26+
}
27+
28+
reader.close();
29+
return header;
30+
}
31+
}

liquidjava-example/src/main/java/testSuite/classes/ts_bufferedreader_error/BufferedReaderRefinementsExpert.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,4 @@ public interface BufferedReaderRefinementsExpert {
5555
@StateRefinement(from="open(this)")
5656
@StateRefinement(from="marked(this)")
5757
public Stream<String> lines();
58-
}
58+
}

liquidjava-example/src/main/java/testSuite/classes/ts_bufferedreader_error/Example.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ class ConfigLoader {
1717

1818
String loadFirstSetting(String configPath, String defaultValue) throws IOException {
1919
BufferedReader reader = new BufferedReader(new FileReader(configPath));
20-
20+
2121
String header = reader.readLine();
2222
if (header.isEmpty()) {
2323
reader.close();

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

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@
2525
import spoon.reflect.code.CtAssignment;
2626
import spoon.reflect.code.CtBinaryOperator;
2727
import spoon.reflect.code.CtBlock;
28+
import spoon.reflect.code.CtBreak;
2829
import spoon.reflect.code.CtConditional;
30+
import spoon.reflect.code.CtContinue;
2931
import spoon.reflect.code.CtConstructorCall;
3032
import spoon.reflect.code.CtExpression;
3133
import spoon.reflect.code.CtFieldRead;
@@ -39,6 +41,7 @@
3941
import spoon.reflect.code.CtReturn;
4042
import spoon.reflect.code.CtStatement;
4143
import spoon.reflect.code.CtThisAccess;
44+
import spoon.reflect.code.CtThrow;
4245
import spoon.reflect.code.CtUnaryOperator;
4346
import spoon.reflect.code.CtVariableAccess;
4447
import spoon.reflect.code.CtVariableRead;
@@ -399,37 +402,36 @@ public void visitCtIf(CtIf ifElement) {
399402
}
400403

401404
/**
402-
* Returns true when a condition's refinement gives no useful information about which branch may run.
405+
* A condition is uninformative when its refinement is the trivial {@code true} predicate yet the expression itself
406+
* is not a boolean literal — i.e. the verifier has no symbolic information to relate the branch to. Treating such a
407+
* condition as {@code true} would force every if-then to be taken, producing spurious state-refinement errors.
403408
*/
404409
private boolean isUninformativeCondition(Predicate conditionRefinement, CtExpression<Boolean> condition) {
405-
if (!conditionRefinement.isBooleanTrue()) {
410+
if (!conditionRefinement.isBooleanTrue())
406411
return false;
407-
}
408-
if (condition instanceof CtLiteral<?> literal && literal.getValue() instanceof Boolean) {
409-
return false;
410-
}
411-
return true;
412+
return !(condition instanceof CtLiteral<?> literal && literal.getValue() instanceof Boolean);
412413
}
413414

414415
/**
415-
* Best-effort normal-completion check for branch joins. Branches that end in {@code return} cannot contribute state
416-
* to the code following the {@code if}.
416+
* Best-effort normal-completion check (JLS §14.21): branches that always {@code return} or {@code throw} cannot
417+
* contribute state to code following the {@code if}, so their post-context must be discarded at the join.
417418
*/
418419
private boolean canCompleteNormally(CtStatement statement) {
419-
if (statement == null) {
420+
if (statement == null)
420421
return true;
421-
}
422-
if (statement instanceof CtReturn<?>) {
422+
if (statement instanceof CtReturn<?> || statement instanceof CtThrow || statement instanceof CtBreak
423+
|| statement instanceof CtContinue)
423424
return false;
424-
}
425425
if (statement instanceof CtBlock<?> block) {
426426
List<CtStatement> statements = block.getStatements();
427427
return statements.isEmpty() || canCompleteNormally(statements.get(statements.size() - 1));
428428
}
429429
if (statement instanceof CtIf nestedIf) {
430430
CtStatement elseStatement = nestedIf.getElseStatement();
431-
return canCompleteNormally(nestedIf.getThenStatement()) || elseStatement == null
432-
|| canCompleteNormally(elseStatement);
431+
// No else means the false path always falls through.
432+
if (elseStatement == null)
433+
return true;
434+
return canCompleteNormally(nestedIf.getThenStatement()) || canCompleteNormally(elseStatement);
433435
}
434436
return true;
435437
}

0 commit comments

Comments
 (0)