Skip to content

Commit f73a6d9

Browse files
committed
Merge branch 'main' into vc-function-substitution
2 parents 8073094 + fa8114a commit f73a6d9

8 files changed

Lines changed: 94 additions & 26 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package testSuite;
2+
3+
import liquidjava.specification.StateSet;
4+
5+
@StateSet({"idle", "running"})
6+
public interface CorrectInterfaceStateSet {
7+
}

liquidjava-example/src/main/java/testSuite/classes/resultset_forward_correct/ResultSetTests.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,20 @@ float readAverage(Connection conn) throws SQLException {
6262
return 0.0f;
6363
}
6464
}
65+
66+
float readAverageStoredCondition(Connection conn) throws SQLException {
67+
Statement parentstmt = conn.createStatement();
68+
ResultSet parentMessage =
69+
parentstmt.executeQuery("SELECT SUM(IMPORTANCE) AS IMPAVG FROM MAIL");
70+
// Regression for issue #241: next()'s result is stored in a boolean before the `if`.
71+
// The transition (_ ? onRow : endRows) must still flow through `b`, so the guarded
72+
// getFloat() is on a row (onRow) and verifies — exactly as the inline readAverage above.
73+
boolean b = parentMessage.next();
74+
if( b ) {
75+
float avgsum = parentMessage.getFloat("IMPAVG");
76+
return avgsum;
77+
} else {
78+
return 0.0f;
79+
}
80+
}
6581
}

liquidjava-example/src/main/java/testSuite/classes/resultset_forward_error/ResultSetTests.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,29 @@ float readAverage(Connection conn) throws SQLException {
6767
float avgsum = parentMessage.getFloat("IMPAVG"); // State Refinement Error
6868
return avgsum;
6969
}
70+
71+
// Issue #241 soundness: storing next()'s result in a boolean must NOT make getFloat()
72+
// unconditionally legal. With no guard the state is `next ? onRow : endRows`, so onRow is
73+
// not guaranteed and getFloat() must still be rejected.
74+
float readAverageStoredNoGuard(Connection conn) throws SQLException {
75+
Statement parentstmt = conn.createStatement();
76+
ResultSet parentMessage = parentstmt.executeQuery("SELECT SUM(IMPORTANCE) AS IMPAVG FROM MAIL");
77+
boolean b = parentMessage.next();
78+
float avgsum = parentMessage.getFloat("IMPAVG"); // State Refinement Error
79+
return avgsum;
80+
}
81+
82+
// Issue #241 branch-sensitivity: legal in the then-branch (onRow), illegal in the else-branch (endRows).
83+
float readAverageVarElse(Connection conn) throws SQLException {
84+
Statement parentstmt = conn.createStatement();
85+
ResultSet parentMessage = parentstmt.executeQuery("SELECT SUM(IMPORTANCE) AS IMPAVG FROM MAIL");
86+
float avgsum = 0.0f;
87+
boolean b = parentMessage.next();
88+
if (b) {
89+
avgsum = parentMessage.getFloat("IMPAVG");
90+
} else {
91+
avgsum = parentMessage.getFloat("IMPAVG"); // State Refinement Error
92+
}
93+
return avgsum;
94+
}
7095
}

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,10 @@ private <T> boolean tryStaticFinalConstantRefinement(CtFieldRead<T> fieldRead) {
321321
public <T> void visitCtVariableRead(CtVariableRead<T> variableRead) {
322322
super.visitCtVariableRead(variableRead);
323323
CtVariable<T> varDecl = variableRead.getVariable().getDeclaration();
324+
// Some CtVariableRead forms have no resolvable declaration (e.g. accesses to symbols outside the
325+
// model); with no name there is no context entry to attach, so leave the metadata as-is.
326+
if (varDecl == null)
327+
return;
324328
getPutVariableMetadata(variableRead, varDecl.getSimpleName());
325329
}
326330

@@ -538,8 +542,11 @@ private Predicate getAssignmentRefinement(String name, CtExpression<?> assignmen
538542
}
539543

540544
private Predicate getExpressionRefinements(CtExpression<?> element) throws LJError {
541-
if (element instanceof CtVariableRead<?>) {
542-
// CtVariableRead<?> elemVar = (CtVariableRead<?>) element;
545+
if (element instanceof CtFieldRead<?> fieldRead) {
546+
visitCtFieldRead(fieldRead);
547+
return getRefinement(element);
548+
} else if (element instanceof CtVariableRead<?> varRead) {
549+
visitCtVariableRead(varRead);
543550
return getRefinement(element);
544551
} else if (element instanceof CtBinaryOperator<?>) {
545552
CtBinaryOperator<?> binop = (CtBinaryOperator<?>) element;

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

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -263,40 +263,32 @@ private void createStateGhost(String string, CtElement element, SourcePosition p
263263
}
264264

265265
protected String getQualifiedClassName(CtElement element) {
266-
if (element.getParent() instanceof CtClass<?>) {
267-
return ((CtClass<?>) element.getParent()).getQualifiedName();
268-
} else if (element instanceof CtClass<?>) {
269-
return ((CtClass<?>) element).getQualifiedName();
270-
}
271-
return null;
266+
return getEnclosingType(element).map(CtType::getQualifiedName).orElse(null);
272267
}
273268

274269
protected String getSimpleClassName(CtElement element) {
275-
if (element.getParent() instanceof CtClass<?>) {
276-
return ((CtClass<?>) element.getParent()).getSimpleName();
277-
} else if (element instanceof CtClass<?>) {
278-
return ((CtClass<?>) element).getSimpleName();
279-
}
280-
return null;
270+
return getEnclosingType(element).map(CtType::getSimpleName).orElse(null);
281271
}
282272

283273
protected Optional<GhostFunction> createStateGhost(int order, CtElement element) {
284-
CtClass<?> klass = null;
285-
if (element.getParent() instanceof CtClass<?>) {
286-
klass = (CtClass<?>) element.getParent();
287-
} else if (element instanceof CtClass<?>) {
288-
klass = (CtClass<?>) element;
289-
}
290-
if (klass != null) {
274+
Optional<CtType<?>> enclosingType = getEnclosingType(element);
275+
if (enclosingType.isPresent()) {
276+
CtType<?> type = enclosingType.get();
291277
CtTypeReference<?> ret = factory.Type().INTEGER_PRIMITIVE;
292-
List<String> params = Collections.singletonList(klass.getSimpleName());
278+
List<String> params = Collections.singletonList(type.getSimpleName());
293279
String name = String.format("state%d", order);
294-
GhostFunction gh = new GhostFunction(name, params, ret, factory, klass.getQualifiedName());
280+
GhostFunction gh = new GhostFunction(name, params, ret, factory, type.getQualifiedName());
295281
return Optional.of(gh);
296282
}
297283
return Optional.empty();
298284
}
299285

286+
private Optional<CtType<?>> getEnclosingType(CtElement element) {
287+
if (element instanceof CtType<?> type)
288+
return Optional.of(type);
289+
return Optional.ofNullable(element.getParent(CtType.class));
290+
}
291+
300292
protected void getGhostFunction(String value, CtElement element, SourcePosition position) throws LJError {
301293
GhostDTO f = getGhostDeclaration(value, position);
302294
CtType<?> type = element instanceof CtType<?> t ? t : element.getParent()instanceof CtType<?> t ? t : null;

liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VCSubstitution.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,8 @@ private Optional<Substitution> findSubstitution(VCImplication implication) {
7575
if (implication == null)
7676
return Optional.empty();
7777

78-
Optional<Substitution> current = getSubstitution(implication);
78+
Optional<Substitution> current = containsVar(implication.getNext(), implication.getName())
79+
? getSubstitution(implication) : Optional.empty();
7980
if (current.isPresent())
8081
return current;
8182

@@ -120,4 +121,15 @@ private boolean containsVar(Expression expression, String name) {
120121
expression.getVariableNames(names);
121122
return names.contains(name);
122123
}
124+
125+
/**
126+
* Checks whether a VC suffix contains a variable name
127+
*/
128+
private boolean containsVar(VCImplication implication, String name) {
129+
for (VCImplication current = implication; current != null; current = current.getNext()) {
130+
if (containsVar(current.getRefinement().getExpression(), name))
131+
return true;
132+
}
133+
return false;
134+
}
123135
}

liquidjava-verifier/src/main/java/liquidjava/smt/TranslatorToZ3.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,12 +338,14 @@ public Expr<?> makeDiv(Expr<?> eval, Expr<?> eval2) {
338338
return z3.mkFPDiv(z3.mkFPRoundNearestTiesToEven(), toFP(eval), toFP(eval2));
339339
if (eval instanceof RealExpr || eval2 instanceof RealExpr)
340340
return z3.mkDiv(toReal(eval), toReal(eval2));
341+
// Z3 integer division does not model Java's truncation toward zero for negative operands
341342
return z3.mkDiv((ArithExpr) eval, (ArithExpr) eval2);
342343
}
343344

344345
public Expr<?> makeMod(Expr<?> eval, Expr<?> eval2) {
345346
if (eval instanceof FPExpr || eval2 instanceof FPExpr)
346347
return z3.mkFPRem(toFP(eval), toFP(eval2));
348+
// Z3 integer modulo does not model Java's dividend-signed remainder for negative operands
347349
if (eval instanceof RealExpr || eval2 instanceof RealExpr)
348350
return z3.mkMod(toInt(eval), toInt(eval2));
349351
return z3.mkMod((IntExpr) eval, (IntExpr) eval2);

liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VCSubstitutionTest.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,17 @@ void preservesRemainingBinderAfterSubstitution() {
5353
}
5454

5555
@Test
56-
void removesSourceNodeWhenItIsLastInChain() {
56+
void keepsSourceNodeWhenItIsLastInChain() {
5757
VCImplication implication = vc("x > 0", "∀y:int. y == 1");
5858

59-
assertSimplificationSteps(substitution::apply, implication, chain(expect("x > 0")));
59+
assertSimplificationSteps(substitution::apply, implication, chain(expect("x > 0"), expect("y == 1")));
60+
}
61+
62+
@Test
63+
void keepsReturnBinderWhenConclusionIsSeparate() {
64+
VCImplication implication = vc("∀x:int. true", "∀#ret_8:int. #ret_8 == x + 1");
65+
66+
assertSimplificationSteps(substitution::apply, implication, chain(expect("true"), expect("#ret⁸ == x + 1")));
6067
}
6168

6269
@Test

0 commit comments

Comments
 (0)