Skip to content

Commit 990ddeb

Browse files
committed
Fix Expression Mutation in ConstantPropagation
1 parent cbb80d5 commit 990ddeb

File tree

5 files changed

+83
-21
lines changed

5 files changed

+83
-21
lines changed

liquidjava-verifier/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
<groupId>io.github.liquid-java</groupId>
1313
<artifactId>liquidjava-verifier</artifactId>
14-
<version>0.0.4</version>
14+
<version>0.0.8</version>
1515
<name>liquidjava-verifier</name>
1616
<description>LiquidJava Verifier</description>
1717
<url>https://github.com/liquid-java/liquidjava</url>

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

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,23 +44,25 @@ private static ValDerivationNode propagateRecursive(Expression exp, Map<String,
4444
// lift unary origin
4545
if (exp instanceof UnaryExpression unary) {
4646
ValDerivationNode operand = propagateRecursive(unary.getChildren().get(0), subs);
47-
unary.setChild(0, operand.getValue());
47+
UnaryExpression cloned = (UnaryExpression) unary.clone();
48+
cloned.setChild(0, operand.getValue());
4849

49-
DerivationNode origin = operand.getOrigin() != null ? new UnaryDerivationNode(operand, unary.getOp())
50+
DerivationNode origin = operand.getOrigin() != null ? new UnaryDerivationNode(operand, cloned.getOp())
5051
: null;
51-
return new ValDerivationNode(unary, origin);
52+
return new ValDerivationNode(cloned, origin);
5253
}
5354

5455
// lift binary origin
5556
if (exp instanceof BinaryExpression binary) {
5657
ValDerivationNode left = propagateRecursive(binary.getFirstOperand(), subs);
5758
ValDerivationNode right = propagateRecursive(binary.getSecondOperand(), subs);
58-
binary.setChild(0, left.getValue());
59-
binary.setChild(1, right.getValue());
59+
BinaryExpression cloned = (BinaryExpression) binary.clone();
60+
cloned.setChild(0, left.getValue());
61+
cloned.setChild(1, right.getValue());
6062

6163
DerivationNode origin = (left.getOrigin() != null || right.getOrigin() != null)
62-
? new BinaryDerivationNode(left, right, binary.getOperator()) : null;
63-
return new ValDerivationNode(binary, origin);
64+
? new BinaryDerivationNode(left, right, cloned.getOperator()) : null;
65+
return new ValDerivationNode(cloned, origin);
6466
}
6567

6668
// recursively propagate children

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ public class VariableResolver {
1313

1414
/**
1515
* Extracts variables with constant values from an expression
16+
*
1617
* @param exp
18+
*
1719
* @returns map from variable names to their values
1820
*/
1921
public static Map<String, Expression> resolve(Expression exp) {
@@ -28,8 +30,10 @@ public static Map<String, Expression> resolve(Expression exp) {
2830

2931
/**
3032
* Counts occurrences of a variable in an expression
33+
*
3134
* @param exp
3235
* @param varName
36+
*
3337
* @return number of occurrences
3438
*/
3539
private static int countOccurrences(Expression exp, String varName) {
@@ -47,6 +51,7 @@ private static int countOccurrences(Expression exp, String varName) {
4751

4852
/**
4953
* Recursively extracts variable equalities from an expression (e.g. ... && x == 1 && y == 2 => map: x -> 1, y -> 2)
54+
*
5055
* @param exp
5156
* @param map
5257
*/
@@ -71,7 +76,9 @@ private static void resolveRecursive(Expression exp, Map<String, Expression> map
7176

7277
/**
7378
* Handles transitive variable equalities in the map (e.g. map: x -> y, y -> 1 => map: x -> 1, y -> 1)
79+
*
7480
* @param map
81+
*
7582
* @return new map with resolved values
7683
*/
7784
private static Map<String, Expression> resolveTransitive(Map<String, Expression> map) {
@@ -85,9 +92,11 @@ private static Map<String, Expression> resolveTransitive(Map<String, Expression>
8592
/**
8693
* Returns the value of a variable by looking up in the map recursively Uses the seen set to avoid circular
8794
* references (e.g. x -> y, y -> x) which would cause infinite recursion
95+
*
8896
* @param exp
8997
* @param map
9098
* @param seen
99+
*
91100
* @return resolved expression
92101
*/
93102
private static Expression lookup(Expression exp, Map<String, Expression> map, Set<String> seen) {

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

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,57 @@ void testCircularDependencyShouldNotSimplify() {
416416
assertEquals("y == x", resultExpr.getSecondOperand().toString(), "Right operand should be y == x");
417417
}
418418

419+
@Test
420+
void testRealExpression() {
421+
// Given: #a_5 == (-#fresh_4) && #fresh_4 == #x_2 / #y_3 && #x_2 == #x_0 && #x_0 == 6 && #y_3 == #y_1 && #y_1 ==
422+
// 3
423+
// Expected: #a_5 == -2
424+
Expression varA5 = new Var("#a_5");
425+
Expression varFresh4 = new Var("#fresh_4");
426+
Expression varX2 = new Var("#x_2");
427+
Expression varY3 = new Var("#y_3");
428+
Expression varX0 = new Var("#x_0");
429+
Expression varY1 = new Var("#y_1");
430+
Expression six = new LiteralInt(6);
431+
Expression three = new LiteralInt(3);
432+
Expression fresh4EqualsX2DivY3 = new BinaryExpression(varFresh4, "==", new BinaryExpression(varX2, "/", varY3));
433+
Expression x2EqualsX0 = new BinaryExpression(varX2, "==", varX0);
434+
Expression x0Equals6 = new BinaryExpression(varX0, "==", six);
435+
Expression y3EqualsY1 = new BinaryExpression(varY3, "==", varY1);
436+
Expression y1Equals3 = new BinaryExpression(varY1, "==", three);
437+
Expression negFresh4 = new UnaryExpression("-", varFresh4);
438+
Expression a5EqualsNegFresh4 = new BinaryExpression(varA5, "==", negFresh4);
439+
Expression firstAnd = new BinaryExpression(a5EqualsNegFresh4, "&&", fresh4EqualsX2DivY3);
440+
Expression secondAnd = new BinaryExpression(x2EqualsX0, "&&", x0Equals6);
441+
Expression thirdAnd = new BinaryExpression(y3EqualsY1, "&&", y1Equals3);
442+
Expression firstBigAnd = new BinaryExpression(firstAnd, "&&", secondAnd);
443+
Expression fullExpression = new BinaryExpression(firstBigAnd, "&&", thirdAnd);
444+
// When
445+
ValDerivationNode result = ExpressionSimplifier.simplify(fullExpression);
446+
// Then
447+
assertNotNull(result, "Result should not be null");
448+
assertEquals("#a_5 == -2", result.getValue().toString(), "Expected result to be #a_5 == -2");
449+
450+
}
451+
452+
@Test
453+
void testTransitive() {
454+
// Given: a == b && b == 1
455+
// Expected: a == 1
456+
Expression varA = new Var("a");
457+
Expression varB = new Var("b");
458+
Expression one = new LiteralInt(1);
459+
Expression aEqualsB = new BinaryExpression(varA, "==", varB);
460+
Expression bEquals1 = new BinaryExpression(varB, "==", one);
461+
Expression fullExpression = new BinaryExpression(aEqualsB, "&&", bEquals1);
462+
// When
463+
ValDerivationNode result = ExpressionSimplifier.simplify(fullExpression);
464+
// Then
465+
assertNotNull(result, "Result should not be null");
466+
assertEquals("a == 1", result.getValue().toString(), "Expected result to be a == 1");
467+
468+
}
469+
419470
/**
420471
* Helper method to compare two derivation nodes recursively
421472
*/

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

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -108,17 +108,17 @@ void testGroupedEquality() {
108108
assertTrue(result.isEmpty(), "Grouped single equality should not extract variable mapping");
109109
}
110110

111-
@Test
112-
void testCircularDependency() {
113-
// x == y && y == x should not extract anything due to circular dependency
114-
Expression varX = new Var("x");
115-
Expression varY = new Var("y");
116-
117-
Expression xEqualsY = new BinaryExpression(varX, "==", varY);
118-
Expression yEqualsX = new BinaryExpression(varY, "==", varX);
119-
Expression conjunction = new BinaryExpression(xEqualsY, "&&", yEqualsX);
120-
121-
Map<String, Expression> result = VariableResolver.resolve(conjunction);
122-
assertTrue(result.isEmpty(), "Circular dependency should not extract variable mappings");
123-
}
111+
@Test
112+
void testCircularDependency() {
113+
// x == y && y == x should not extract anything due to circular dependency
114+
Expression varX = new Var("x");
115+
Expression varY = new Var("y");
116+
117+
Expression xEqualsY = new BinaryExpression(varX, "==", varY);
118+
Expression yEqualsX = new BinaryExpression(varY, "==", varX);
119+
Expression conjunction = new BinaryExpression(xEqualsY, "&&", yEqualsX);
120+
121+
Map<String, Expression> result = VariableResolver.resolve(conjunction);
122+
assertTrue(result.isEmpty(), "Circular dependency should not extract variable mappings");
123+
}
124124
}

0 commit comments

Comments
 (0)