Skip to content

Commit 0fafd24

Browse files
committed
Merge branch 'main' into substitute-internal-vars
2 parents a808179 + 1272e50 commit 0fafd24

File tree

5 files changed

+294
-39
lines changed

5 files changed

+294
-39
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Refinement Error
2+
package testSuite;
3+
4+
import liquidjava.specification.Refinement;
5+
6+
public class ErrorAssignmentBeforeReturn {
7+
@Refinement("_ > 0")
8+
static int example(int x) {
9+
x = x + 1;
10+
return x;
11+
}
12+
}

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

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package liquidjava.processor.refinement_checker;
22

33
import java.util.ArrayList;
4+
import java.util.HashSet;
45
import java.util.List;
6+
import java.util.Optional;
57
import java.util.Set;
68
import java.util.Stack;
79
import java.util.function.Consumer;
@@ -12,6 +14,9 @@
1214
import liquidjava.processor.VCImplication;
1315
import liquidjava.processor.context.*;
1416
import liquidjava.rj_language.Predicate;
17+
import liquidjava.rj_language.ast.Expression;
18+
import liquidjava.rj_language.ast.Ite;
19+
import liquidjava.rj_language.ast.Var;
1520
import liquidjava.smt.Counterexample;
1621
import liquidjava.smt.SMTEvaluator;
1722
import liquidjava.smt.SMTResult;
@@ -191,7 +196,18 @@ private VCImplication joinPredicates(Predicate expectedType, List<RefinedVariabl
191196

192197
for (RefinedVariable var : vars) { // join refinements of vars
193198
addMap(var, map);
194-
VCImplication si = new VCImplication(var.getName(), var.getType(), var.getRefinement());
199+
200+
// if the last instance is already in vars, it is already in the premises
201+
// adding "var == lastInstance" would create a contradictory cycle (e.g. x == x + 1 for x = x + 1)
202+
// so we need to use main refinement to avoid this
203+
Predicate refinement = var.getRefinement();
204+
if (var instanceof Variable v) {
205+
Optional<VariableInstance> lastInst = v.getLastInstance();
206+
if (lastInst.isPresent() && vars.contains(lastInst.get())
207+
&& hasDependencyCycle(lastInst.get(), var.getName(), vars, new HashSet<>()))
208+
refinement = v.getMainRefinement();
209+
}
210+
VCImplication si = new VCImplication(var.getName(), var.getType(), refinement);
195211
if (lastSi != null) {
196212
lastSi.setNext(si);
197213
lastSi = si;
@@ -268,6 +284,22 @@ void removePathVariableThatIncludes(String otherVar) {
268284
.forEach(pathVariables::remove);
269285
}
270286

287+
private boolean hasDependencyCycle(RefinedVariable rv, String var, List<RefinedVariable> vars, Set<String> seen) {
288+
if (!seen.add(rv.getName()))
289+
return false;
290+
Expression e = rv.getRefinement().getExpression();
291+
return hasVariable(e, var) || vars.stream().filter(o -> hasVariable(e, o.getName()))
292+
.anyMatch(o -> hasDependencyCycle(o, var, vars, seen));
293+
}
294+
295+
private boolean hasVariable(Expression exp, String var) {
296+
if (exp instanceof Var v)
297+
return v.getName().equals(var);
298+
if (exp instanceof Ite ite)
299+
return hasVariable(ite.getThen(), var) || hasVariable(ite.getElse(), var);
300+
return exp.getChildren().stream().anyMatch(c -> hasVariable(c, var));
301+
}
302+
271303
// Errors---------------------------------------------------------------------------------------------------
272304

273305
protected void throwRefinementError(SourcePosition position, Predicate expected, Predicate found,

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -172,18 +172,24 @@ private static ValDerivationNode foldUnary(ValDerivationNode node) {
172172
// !true => false, !false => true
173173
boolean value = operand.isBooleanTrue();
174174
Expression res = new LiteralBoolean(!value);
175-
return new ValDerivationNode(res, new UnaryDerivationNode(operandNode, operator));
175+
DerivationNode origin = operandNode.getOrigin() != null ? new UnaryDerivationNode(operandNode, operator)
176+
: null;
177+
return new ValDerivationNode(res, origin);
176178
}
177179
// unary minus
178180
if ("-".equals(operator)) {
179181
// -(x) => -x
180182
if (operand instanceof LiteralInt) {
181183
Expression res = new LiteralInt(-((LiteralInt) operand).getValue());
182-
return new ValDerivationNode(res, new UnaryDerivationNode(operandNode, operator));
184+
DerivationNode origin = operandNode.getOrigin() != null ? new UnaryDerivationNode(operandNode, operator)
185+
: null;
186+
return new ValDerivationNode(res, origin);
183187
}
184188
if (operand instanceof LiteralReal) {
185189
Expression res = new LiteralReal(-((LiteralReal) operand).getValue());
186-
return new ValDerivationNode(res, new UnaryDerivationNode(operandNode, operator));
190+
DerivationNode origin = operandNode.getOrigin() != null ? new UnaryDerivationNode(operandNode, operator)
191+
: null;
192+
return new ValDerivationNode(res, origin);
187193
}
188194
}
189195

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

Lines changed: 73 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
import liquidjava.rj_language.ast.BinaryExpression;
44
import liquidjava.rj_language.ast.Expression;
55
import liquidjava.rj_language.ast.LiteralBoolean;
6+
import liquidjava.rj_language.ast.UnaryExpression;
67
import liquidjava.rj_language.opt.derivation_node.BinaryDerivationNode;
78
import liquidjava.rj_language.opt.derivation_node.DerivationNode;
9+
import liquidjava.rj_language.opt.derivation_node.UnaryDerivationNode;
810
import liquidjava.rj_language.opt.derivation_node.ValDerivationNode;
911

1012
public class ExpressionSimplifier {
@@ -15,12 +17,13 @@ public class ExpressionSimplifier {
1517
*/
1618
public static ValDerivationNode simplify(Expression exp) {
1719
ValDerivationNode fixedPoint = simplifyToFixedPoint(null, exp);
18-
return simplifyValDerivationNode(fixedPoint);
20+
ValDerivationNode simplified = simplifyValDerivationNode(fixedPoint);
21+
return unwrapBooleanLiterals(simplified);
1922
}
2023

2124
/**
2225
* Recursively applies propagation and folding until the expression stops changing (fixed point) Stops early if the
23-
* expression simplifies to 'true', which means we've simplified too much
26+
* expression simplifies to a boolean literal, which means we've simplified too much
2427
*/
2528
private static ValDerivationNode simplifyToFixedPoint(ValDerivationNode current, Expression prevExp) {
2629
// apply propagation and folding
@@ -34,6 +37,11 @@ private static ValDerivationNode simplifyToFixedPoint(ValDerivationNode current,
3437
return current;
3538
}
3639

40+
// prevent oversimplification
41+
if (current != null && currExp instanceof LiteralBoolean && !(current.getValue() instanceof LiteralBoolean)) {
42+
return current;
43+
}
44+
3745
// continue simplifying
3846
return simplifyToFixedPoint(simplified, simplified.getValue());
3947
}
@@ -76,8 +84,12 @@ private static ValDerivationNode simplifyValDerivationNode(ValDerivationNode nod
7684

7785
// return the conjunction with simplified children
7886
Expression newValue = new BinaryExpression(leftSimplified.getValue(), "&&", rightSimplified.getValue());
79-
DerivationNode newOrigin = new BinaryDerivationNode(leftSimplified, rightSimplified, "&&");
80-
return new ValDerivationNode(newValue, newOrigin);
87+
// only create origin if at least one child has a meaningful origin
88+
if (leftSimplified.getOrigin() != null || rightSimplified.getOrigin() != null) {
89+
DerivationNode newOrigin = new BinaryDerivationNode(leftSimplified, rightSimplified, "&&");
90+
return new ValDerivationNode(newValue, newOrigin);
91+
}
92+
return new ValDerivationNode(newValue, null);
8193
}
8294
// no simplification
8395
return node;
@@ -114,4 +126,61 @@ private static boolean isRedundant(Expression exp) {
114126
}
115127
return false;
116128
}
129+
130+
/**
131+
* Recursively traverses the derivation tree and replaces boolean literals with the expressions that produced them,
132+
* but only when at least one operand in the derivation is non-boolean. e.g. "x == true" where true came from "1 >
133+
* 0" becomes "x == 1 > 0"
134+
*/
135+
private static ValDerivationNode unwrapBooleanLiterals(ValDerivationNode node) {
136+
Expression value = node.getValue();
137+
DerivationNode origin = node.getOrigin();
138+
139+
if (origin == null)
140+
return node;
141+
142+
// unwrap binary expressions
143+
if (value instanceof BinaryExpression binExp && origin instanceof BinaryDerivationNode binOrigin) {
144+
ValDerivationNode left = unwrapBooleanLiterals(binOrigin.getLeft());
145+
ValDerivationNode right = unwrapBooleanLiterals(binOrigin.getRight());
146+
if (left != binOrigin.getLeft() || right != binOrigin.getRight()) {
147+
Expression newValue = new BinaryExpression(left.getValue(), binExp.getOperator(), right.getValue());
148+
return new ValDerivationNode(newValue, new BinaryDerivationNode(left, right, binOrigin.getOp()));
149+
}
150+
return node;
151+
}
152+
153+
// unwrap unary expressions
154+
if (value instanceof UnaryExpression unaryExp && origin instanceof UnaryDerivationNode unaryOrigin) {
155+
ValDerivationNode operand = unwrapBooleanLiterals(unaryOrigin.getOperand());
156+
if (operand != unaryOrigin.getOperand()) {
157+
Expression newValue = new UnaryExpression(unaryExp.getOp(), operand.getValue());
158+
return new ValDerivationNode(newValue, new UnaryDerivationNode(operand, unaryOrigin.getOp()));
159+
}
160+
return node;
161+
}
162+
163+
// boolean literal with binary origin: unwrap if at least one child is non-boolean
164+
if (value instanceof LiteralBoolean && origin instanceof BinaryDerivationNode binOrigin) {
165+
ValDerivationNode left = unwrapBooleanLiterals(binOrigin.getLeft());
166+
ValDerivationNode right = unwrapBooleanLiterals(binOrigin.getRight());
167+
if (!(left.getValue() instanceof LiteralBoolean) || !(right.getValue() instanceof LiteralBoolean)) {
168+
Expression newValue = new BinaryExpression(left.getValue(), binOrigin.getOp(), right.getValue());
169+
return new ValDerivationNode(newValue, new BinaryDerivationNode(left, right, binOrigin.getOp()));
170+
}
171+
return node;
172+
}
173+
174+
// boolean literal with unary origin: unwrap if operand is non-boolean
175+
if (value instanceof LiteralBoolean && origin instanceof UnaryDerivationNode unaryOrigin) {
176+
ValDerivationNode operand = unwrapBooleanLiterals(unaryOrigin.getOperand());
177+
if (!(operand.getValue() instanceof LiteralBoolean)) {
178+
Expression newValue = new UnaryExpression(unaryOrigin.getOp(), operand.getValue());
179+
return new ValDerivationNode(newValue, new UnaryDerivationNode(operand, unaryOrigin.getOp()));
180+
}
181+
return node;
182+
}
183+
184+
return node;
185+
}
117186
}

0 commit comments

Comments
 (0)