Skip to content

Commit 6c43c32

Browse files
fix if issue
1 parent 46f6922 commit 6c43c32

4 files changed

Lines changed: 160 additions & 10 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_error;
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: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package testSuite.classes.ts_bufferedreader_error;
2+
import java.io.BufferedReader;
3+
import java.io.FileReader;
4+
import java.io.IOException;
5+
6+
// (A) The model is co-located: BufferedReaderRefinementsExpert (de-packaged
7+
// copy) declares states open/marked/closed on java.io.BufferedReader and
8+
// requires open or marked for read methods.
9+
//
10+
// (B) The client: load the first non-comment setting from a config file.
11+
// The author handles three branches — empty file, leading-comment header,
12+
// plain header — and tries to close the reader as soon as it is no longer
13+
// needed. In the leading-comment branch the close happens too early; the
14+
// follow-up readLine runs against a closed reader.
15+
16+
class ConfigLoader {
17+
18+
String loadFirstSetting(String configPath, String defaultValue) throws IOException {
19+
BufferedReader reader = new BufferedReader(new FileReader(configPath));
20+
21+
String header = reader.readLine();
22+
if (header.isEmpty()) {
23+
reader.close();
24+
return defaultValue;
25+
}
26+
27+
if (header.startsWith("#")) {
28+
// Header was a comment — the real value is on the next line.
29+
reader.close();
30+
return reader.readLine(); // State Refinement Error
31+
}
32+
33+
reader.close();
34+
return header;
35+
}
36+
}

liquidjava-verifier/src/main/java/liquidjava/processor/context/Variable.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ void saveInstanceElse() {
130130
Optional<VariableInstance> getIfInstanceCombination(int counter, Predicate cond) {
131131
if (ifCombiner.isEmpty() || (!has(ifthenIndex) && !has(ifelseIndex) && !has(ifbeforeIndex)))
132132
return Optional.empty();
133+
if (has(ifbeforeIndex) && !has(ifthenIndex) && !has(ifelseIndex))
134+
return Optional.empty();
133135

134136
String nName = String.format("#%s_%d", super.getName(), counter);
135137
Predicate ref = new Predicate();

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

Lines changed: 64 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import spoon.reflect.code.CtArrayWrite;
2525
import spoon.reflect.code.CtAssignment;
2626
import spoon.reflect.code.CtBinaryOperator;
27+
import spoon.reflect.code.CtBlock;
2728
import spoon.reflect.code.CtConditional;
2829
import spoon.reflect.code.CtConstructorCall;
2930
import spoon.reflect.code.CtExpression;
@@ -36,6 +37,7 @@
3637
import spoon.reflect.code.CtNewArray;
3738
import spoon.reflect.code.CtNewClass;
3839
import spoon.reflect.code.CtReturn;
40+
import spoon.reflect.code.CtStatement;
3941
import spoon.reflect.code.CtThisAccess;
4042
import spoon.reflect.code.CtUnaryOperator;
4143
import spoon.reflect.code.CtVariableAccess;
@@ -336,18 +338,30 @@ public void visitCtIf(CtIf ifElement) {
336338
CtExpression<Boolean> exp = ifElement.getCondition();
337339
Predicate expRefs = getExpressionRefinements(exp);
338340

339-
String freshVarName = String.format(Formats.FRESH, context.getCounter());
340-
expRefs = expRefs.substituteVariable(Keys.WILDCARD, freshVarName);
341-
Predicate lastExpRefs = substituteAllVariablesForLastInstance(expRefs);
342-
expRefs = Predicate.createConjunction(expRefs, lastExpRefs);
341+
String pathVarName;
342+
RefinedVariable freshRV;
343+
if (isUninformativeCondition(expRefs, exp)) {
344+
// No refinement means the condition is unknown, not true.
345+
String conditionVarName = String.format(Formats.FRESH, context.getCounter());
346+
context.addInstanceToContext(conditionVarName, factory.Type().BOOLEAN_PRIMITIVE, new Predicate(), exp);
347+
expRefs = Predicate.createVar(conditionVarName);
348+
349+
pathVarName = String.format(Formats.FRESH, context.getCounter());
350+
freshRV = context.addInstanceToContext(pathVarName, factory.Type().BOOLEAN_PRIMITIVE, expRefs, exp);
351+
} else {
352+
pathVarName = String.format(Formats.FRESH, context.getCounter());
353+
expRefs = expRefs.substituteVariable(Keys.WILDCARD, pathVarName);
354+
Predicate lastExpRefs = substituteAllVariablesForLastInstance(expRefs);
355+
expRefs = Predicate.createConjunction(expRefs, lastExpRefs);
356+
357+
freshRV = context.addInstanceToContext(pathVarName, factory.Type().INTEGER_PRIMITIVE, expRefs, exp);
358+
}
343359

344360
// TODO Change in future
345361
if (expRefs.getVariableNames().contains("null")) {
346362
expRefs = new Predicate();
347363
}
348364

349-
RefinedVariable freshRV = context.addInstanceToContext(freshVarName, factory.Type().INTEGER_PRIMITIVE, expRefs,
350-
exp);
351365
vcChecker.addPathVariable(freshRV);
352366

353367
context.variablesNewIfCombination();
@@ -357,19 +371,23 @@ public void visitCtIf(CtIf ifElement) {
357371
// VISIT THEN
358372
context.enterContext();
359373
visitCtBlock(ifElement.getThenStatement());
360-
context.variablesSetThenIf();
374+
if (canCompleteNormally(ifElement.getThenStatement())) {
375+
context.variablesSetThenIf();
376+
}
361377
contextHistory.saveContext(ifElement.getThenStatement(), context);
362378
context.exitContext();
363379

364380
// VISIT ELSE
365381
if (ifElement.getElseStatement() != null) {
366-
context.getVariableByName(freshVarName);
382+
context.getVariableByName(pathVarName);
367383
// expRefs = expRefs.negate();
368-
context.newRefinementToVariableInContext(freshVarName, expRefs.negate());
384+
context.newRefinementToVariableInContext(pathVarName, expRefs.negate());
369385

370386
context.enterContext();
371387
visitCtBlock(ifElement.getElseStatement());
372-
context.variablesSetElseIf();
388+
if (canCompleteNormally(ifElement.getElseStatement())) {
389+
context.variablesSetElseIf();
390+
}
373391
contextHistory.saveContext(ifElement.getElseStatement(), context);
374392
context.exitContext();
375393
}
@@ -380,6 +398,42 @@ public void visitCtIf(CtIf ifElement) {
380398
context.variablesFinishIfCombination();
381399
}
382400

401+
/**
402+
* Returns true when a condition's refinement gives no useful information about which branch may run.
403+
*/
404+
private boolean isUninformativeCondition(Predicate conditionRefinement, CtExpression<Boolean> condition) {
405+
if (!conditionRefinement.isBooleanTrue()) {
406+
return false;
407+
}
408+
if (condition instanceof CtLiteral<?> literal && literal.getValue() instanceof Boolean) {
409+
return false;
410+
}
411+
return true;
412+
}
413+
414+
/**
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}.
417+
*/
418+
private boolean canCompleteNormally(CtStatement statement) {
419+
if (statement == null) {
420+
return true;
421+
}
422+
if (statement instanceof CtReturn<?>) {
423+
return false;
424+
}
425+
if (statement instanceof CtBlock<?> block) {
426+
List<CtStatement> statements = block.getStatements();
427+
return statements.isEmpty() || canCompleteNormally(statements.get(statements.size() - 1));
428+
}
429+
if (statement instanceof CtIf nestedIf) {
430+
CtStatement elseStatement = nestedIf.getElseStatement();
431+
return canCompleteNormally(nestedIf.getThenStatement()) || elseStatement == null
432+
|| canCompleteNormally(elseStatement);
433+
}
434+
return true;
435+
}
436+
383437
@Override
384438
public <T> void visitCtArrayWrite(CtArrayWrite<T> arrayWrite) {
385439
super.visitCtArrayWrite(arrayWrite);

0 commit comments

Comments
 (0)