Skip to content

Commit 821cc7e

Browse files
committed
Operator Assignment Support
1 parent 25cba58 commit 821cc7e

3 files changed

Lines changed: 133 additions & 3 deletions

File tree

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package testSuite;
2+
3+
import liquidjava.specification.Refinement;
4+
5+
public class CorrectOperatorAssignments {
6+
7+
@Refinement("_ > 0")
8+
int plus(@Refinement("_ >= 0") int x) {
9+
x += 1; // x is now > 0
10+
return x;
11+
}
12+
13+
@Refinement("_ > 0")
14+
int plusInvocation(@Refinement("_ >= 0") int x) {
15+
x += one(); // x is now > 0
16+
return x;
17+
}
18+
19+
@Refinement("_ == 1")
20+
int one() {
21+
return 1;
22+
}
23+
24+
@Refinement("_ < 0")
25+
int minus(@Refinement("_ <= 0") int x) {
26+
x -= 1; // x is now < 0
27+
return x;
28+
}
29+
30+
@Refinement("_ >= 0")
31+
int multiply(int x) {
32+
x *= x; // x is now >= 0
33+
return x;
34+
}
35+
36+
@Refinement("_ <= 1")
37+
int divide(@Refinement("0 <= _ && _ <= 3") int x) {
38+
x /= 2; // x is now <= 1
39+
return x;
40+
}
41+
42+
@Refinement("_ == 0 || _ == 1")
43+
int remainder(@Refinement("_ >= 0") int x) {
44+
x %= 2; // x is now == 0 || x is now == 1
45+
return x;
46+
}
47+
}

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

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import spoon.reflect.code.CtLocalVariable;
3939
import spoon.reflect.code.CtNewArray;
4040
import spoon.reflect.code.CtNewClass;
41+
import spoon.reflect.code.CtOperatorAssignment;
4142
import spoon.reflect.code.CtReturn;
4243
import spoon.reflect.code.CtStatement;
4344
import spoon.reflect.code.CtThisAccess;
@@ -186,10 +187,23 @@ public <T> void visitCtThisAccess(CtThisAccess<T> thisAccess) {
186187
}
187188
}
188189

189-
@SuppressWarnings("unchecked")
190190
@Override
191191
public <T, A extends T> void visitCtAssignment(CtAssignment<T, A> assignment) throws LJError {
192192
super.visitCtAssignment(assignment);
193+
visitAssignment(assignment);
194+
}
195+
196+
@Override
197+
public <T, A extends T> void visitCtOperatorAssignment(CtOperatorAssignment<T, A> assignment) {
198+
super.visitCtOperatorAssignment(assignment);
199+
visitAssignment(assignment);
200+
}
201+
202+
/**
203+
* Handles simple and operator assignments after Spoon has visited their children
204+
*/
205+
@SuppressWarnings("unchecked")
206+
private <T, A extends T> void visitAssignment(CtAssignment<T, A> assignment) throws LJError {
193207
CtExpression<T> ex = assignment.getAssigned();
194208

195209
if (ex instanceof CtVariableWriteImpl) {
@@ -379,7 +393,7 @@ public void visitCtIf(CtIf ifElement) {
379393
thenRefs = Predicate.createConjunction(expRefs, freshIsTrue);
380394
elseRefs = Predicate.createConjunction(expRefs, freshIsFalse);
381395
}
382-
396+
383397
freshRV = context.addInstanceToContext(pathVarName, factory.Type().BOOLEAN_PRIMITIVE, thenRefs, exp);
384398
}
385399
vcChecker.addPathVariable(freshRV);
@@ -495,7 +509,7 @@ private void checkAssignment(String name, CtTypeReference<?> type, CtExpression<
495509
CtElement parentElem, CtElement varDecl) throws LJError {
496510
getPutVariableMetadata(ex, name);
497511

498-
Predicate refinementFound = getRefinement(assignment);
512+
Predicate refinementFound = getAssignmentRefinement(name, assignment, parentElem);
499513
if (refinementFound == null) {
500514
RefinedVariable rv = context.getVariableByName(name);
501515
if (rv instanceof Variable) {
@@ -512,6 +526,17 @@ private void checkAssignment(String name, CtTypeReference<?> type, CtExpression<
512526
checkVariableRefinements(refinementFound, name, type, parentElem, varDecl);
513527
}
514528

529+
/**
530+
* Get the refinement for operator assignments (e.g. x += 1)
531+
*/
532+
private Predicate getAssignmentRefinement(String name, CtExpression<?> assignment, CtElement parentElem)
533+
throws LJError {
534+
if (parentElem instanceof CtOperatorAssignment<?, ?> operatorAssignment) {
535+
return otc.getOperatorAssignmentRefinement(name, operatorAssignment);
536+
}
537+
return getRefinement(assignment);
538+
}
539+
515540
private Predicate getExpressionRefinements(CtExpression<?> element) throws LJError {
516541
if (element instanceof CtVariableRead<?>) {
517542
// CtVariableRead<?> elemVar = (CtVariableRead<?>) element;

liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/general_checkers/OperationsChecker.java

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
import liquidjava.utils.constants.Ops;
1717
import liquidjava.utils.constants.Types;
1818
import liquidjava.rj_language.Predicate;
19+
import liquidjava.rj_language.ast.BinaryExpression;
20+
import liquidjava.rj_language.ast.Expression;
21+
import liquidjava.rj_language.ast.GroupExpression;
1922
import org.apache.commons.lang3.NotImplementedException;
2023
import spoon.reflect.code.BinaryOperatorKind;
2124
import spoon.reflect.code.CtAssignment;
@@ -26,6 +29,7 @@
2629
import spoon.reflect.code.CtInvocation;
2730
import spoon.reflect.code.CtLiteral;
2831
import spoon.reflect.code.CtLocalVariable;
32+
import spoon.reflect.code.CtOperatorAssignment;
2933
import spoon.reflect.code.CtReturn;
3034
import spoon.reflect.code.CtUnaryOperator;
3135
import spoon.reflect.code.CtVariableRead;
@@ -94,6 +98,18 @@ public <T> void getBinaryOpRefinements(CtBinaryOperator<T> operator) throws LJEr
9498
// TODO ADD TYPES
9599
}
96100

101+
/**
102+
* Builds the refinement for a operator assignment. Java operator assignments such as {@code x += y} are modeled as
103+
* {@code x = x + y}; the returned predicate refines the assigned value as {@code _ == current(x) <op> rhs}.
104+
*/
105+
public Predicate getOperatorAssignmentRefinement(String assignedName, CtOperatorAssignment<?, ?> assignment)
106+
throws LJError {
107+
Predicate left = getCurrentVariableValue(assignedName);
108+
Predicate right = getOperatorAssignmentRefinement(assignment.getAssignment());
109+
Predicate operation = Predicate.createOperation(left, getOperatorFromKind(assignment.getKind()), right);
110+
return Predicate.createEquals(Predicate.createVar(Keys.WILDCARD), operation);
111+
}
112+
97113
/**
98114
* Finds and adds refinement metadata to the unary operation
99115
*
@@ -280,6 +296,48 @@ private Predicate getOperationRefinementFromExternalLib(CtInvocation<?> inv) thr
280296
return new Predicate();
281297
}
282298

299+
/**
300+
* Returns the latest symbolic value for a variable
301+
*/
302+
private Predicate getCurrentVariableValue(String name) {
303+
Optional<VariableInstance> variableInstance = rtc.getContext().getLastVariableInstance(name);
304+
return Predicate.createVar(variableInstance.map(VariableInstance::getName).orElse(name));
305+
}
306+
307+
/**
308+
* Converts a operator assignment into an arithmetic predicate operand
309+
*/
310+
private Predicate getOperatorAssignmentRefinement(CtExpression<?> element) throws LJError {
311+
if (element instanceof CtVariableRead<?> variableRead) {
312+
String name = variableRead.getVariable().getSimpleName();
313+
if (variableRead instanceof CtFieldRead<?>)
314+
name = String.format(Formats.THIS, name);
315+
return getCurrentVariableValue(name);
316+
} else if (element instanceof CtBinaryOperator<?> binaryOperator) {
317+
Predicate left = getOperatorAssignmentRefinement(binaryOperator.getLeftHandOperand());
318+
Predicate right = getOperatorAssignmentRefinement(binaryOperator.getRightHandOperand());
319+
return Predicate.createOperation(left, getOperatorFromKind(binaryOperator.getKind()), right);
320+
} else if (element instanceof CtLiteral<?> literal) {
321+
if (literal.getValue() == null)
322+
throw new CustomError("Null literals are not supported", literal.getPosition());
323+
return new Predicate(literal.getValue().toString(), element);
324+
}
325+
// unwrap wildcard equality: _ == expr -> expr
326+
Predicate refinement = rtc.getRefinement(element);
327+
Expression expression = unwrapGroupExpression(refinement.getExpression());
328+
if (expression instanceof BinaryExpression binaryExpression && Ops.EQ.equals(binaryExpression.getOperator())
329+
&& Keys.WILDCARD.equals(binaryExpression.getFirstOperand().toString())) {
330+
return new Predicate(binaryExpression.getSecondOperand());
331+
}
332+
return refinement;
333+
}
334+
335+
private Expression unwrapGroupExpression(Expression expression) {
336+
while (expression instanceof GroupExpression groupExpression)
337+
expression = groupExpression.getExpression();
338+
return expression;
339+
}
340+
283341
/**
284342
* Retrieves the refinements for the variable write inside unary operation
285343
*

0 commit comments

Comments
 (0)