Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package testSuite;

import liquidjava.specification.Refinement;

public class CorrectOperatorAssignments {

@Refinement("_ > 0")
int plus(@Refinement("_ >= 0") int x) {
x += 1; // x is now > 0
return x;
}

@Refinement("_ > 0")
int plusInvocation(@Refinement("_ >= 0") int x) {
x += one(); // x is now > 0
return x;
}

@Refinement("_ == 1")
int one() {
return 1;
}

@Refinement("_ < 0")
int minus(@Refinement("_ <= 0") int x) {
x -= 1; // x is now < 0
return x;
}

@Refinement("_ >= 0")
int multiply(int x) {
x *= x; // x is now >= 0
return x;
}

@Refinement("_ <= 1")
int divide(@Refinement("0 <= _ && _ <= 3") int x) {
x /= 2; // x is now <= 1
return x;
}

@Refinement("_ == 0 || _ == 1")
int remainder(@Refinement("_ >= 0") int x) {
x %= 2; // x is now == 0 || x is now == 1
return x;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import spoon.reflect.code.CtLocalVariable;
import spoon.reflect.code.CtNewArray;
import spoon.reflect.code.CtNewClass;
import spoon.reflect.code.CtOperatorAssignment;
import spoon.reflect.code.CtReturn;
import spoon.reflect.code.CtStatement;
import spoon.reflect.code.CtThisAccess;
Expand Down Expand Up @@ -186,10 +187,23 @@ public <T> void visitCtThisAccess(CtThisAccess<T> thisAccess) {
}
}

@SuppressWarnings("unchecked")
@Override
public <T, A extends T> void visitCtAssignment(CtAssignment<T, A> assignment) throws LJError {
super.visitCtAssignment(assignment);
visitAssignment(assignment);
}

@Override
public <T, A extends T> void visitCtOperatorAssignment(CtOperatorAssignment<T, A> assignment) {
super.visitCtOperatorAssignment(assignment);
visitAssignment(assignment);
}

/**
* Handles simple and operator assignments after Spoon has visited their children
*/
@SuppressWarnings("unchecked")
private <T, A extends T> void visitAssignment(CtAssignment<T, A> assignment) throws LJError {
CtExpression<T> ex = assignment.getAssigned();

if (ex instanceof CtVariableWriteImpl) {
Expand Down Expand Up @@ -379,7 +393,7 @@ public void visitCtIf(CtIf ifElement) {
thenRefs = Predicate.createConjunction(expRefs, freshIsTrue);
elseRefs = Predicate.createConjunction(expRefs, freshIsFalse);
}

freshRV = context.addInstanceToContext(pathVarName, factory.Type().BOOLEAN_PRIMITIVE, thenRefs, exp);
}
vcChecker.addPathVariable(freshRV);
Expand Down Expand Up @@ -495,7 +509,7 @@ private void checkAssignment(String name, CtTypeReference<?> type, CtExpression<
CtElement parentElem, CtElement varDecl) throws LJError {
getPutVariableMetadata(ex, name);

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

/**
* Get the refinement for operator assignments (e.g. x += 1)
*/
private Predicate getAssignmentRefinement(String name, CtExpression<?> assignment, CtElement parentElem)
throws LJError {
if (parentElem instanceof CtOperatorAssignment<?, ?> operatorAssignment) {
return otc.getOperatorAssignmentRefinement(name, operatorAssignment);
}
return getRefinement(assignment);
}

private Predicate getExpressionRefinements(CtExpression<?> element) throws LJError {
if (element instanceof CtVariableRead<?>) {
// CtVariableRead<?> elemVar = (CtVariableRead<?>) element;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
import liquidjava.utils.constants.Ops;
import liquidjava.utils.constants.Types;
import liquidjava.rj_language.Predicate;
import liquidjava.rj_language.ast.BinaryExpression;
import liquidjava.rj_language.ast.Expression;
import liquidjava.rj_language.ast.GroupExpression;
import org.apache.commons.lang3.NotImplementedException;
import spoon.reflect.code.BinaryOperatorKind;
import spoon.reflect.code.CtAssignment;
Expand All @@ -26,6 +29,7 @@
import spoon.reflect.code.CtInvocation;
import spoon.reflect.code.CtLiteral;
import spoon.reflect.code.CtLocalVariable;
import spoon.reflect.code.CtOperatorAssignment;
import spoon.reflect.code.CtReturn;
import spoon.reflect.code.CtUnaryOperator;
import spoon.reflect.code.CtVariableRead;
Expand Down Expand Up @@ -94,6 +98,18 @@ public <T> void getBinaryOpRefinements(CtBinaryOperator<T> operator) throws LJEr
// TODO ADD TYPES
}

/**
* Builds the refinement for a operator assignment. Java operator assignments such as {@code x += y} are modeled as
* {@code x = x + y}; the returned predicate refines the assigned value as {@code _ == current(x) <op> rhs}.
*/
public Predicate getOperatorAssignmentRefinement(String assignedName, CtOperatorAssignment<?, ?> assignment)
throws LJError {
Predicate left = getCurrentVariableValue(assignedName);
Predicate right = getOperatorAssignmentRefinement(assignment.getAssignment());
Predicate operation = Predicate.createOperation(left, getOperatorFromKind(assignment.getKind()), right);
return Predicate.createEquals(Predicate.createVar(Keys.WILDCARD), operation);
}

/**
* Finds and adds refinement metadata to the unary operation
*
Expand Down Expand Up @@ -280,6 +296,48 @@ private Predicate getOperationRefinementFromExternalLib(CtInvocation<?> inv) thr
return new Predicate();
}

/**
* Returns the latest symbolic value for a variable
*/
private Predicate getCurrentVariableValue(String name) {
Optional<VariableInstance> variableInstance = rtc.getContext().getLastVariableInstance(name);
return Predicate.createVar(variableInstance.map(VariableInstance::getName).orElse(name));
}

/**
* Converts a operator assignment into an arithmetic predicate operand
*/
private Predicate getOperatorAssignmentRefinement(CtExpression<?> element) throws LJError {
if (element instanceof CtVariableRead<?> variableRead) {
String name = variableRead.getVariable().getSimpleName();
if (variableRead instanceof CtFieldRead<?>)
name = String.format(Formats.THIS, name);
return getCurrentVariableValue(name);
} else if (element instanceof CtBinaryOperator<?> binaryOperator) {
Predicate left = getOperatorAssignmentRefinement(binaryOperator.getLeftHandOperand());
Predicate right = getOperatorAssignmentRefinement(binaryOperator.getRightHandOperand());
return Predicate.createOperation(left, getOperatorFromKind(binaryOperator.getKind()), right);
} else if (element instanceof CtLiteral<?> literal) {
if (literal.getValue() == null)
throw new CustomError("Null literals are not supported", literal.getPosition());
return new Predicate(literal.getValue().toString(), element);
}
// unwrap wildcard equality: _ == expr -> expr
Predicate refinement = rtc.getRefinement(element);
Expression expression = unwrapGroupExpression(refinement.getExpression());
if (expression instanceof BinaryExpression binaryExpression && Ops.EQ.equals(binaryExpression.getOperator())
&& Keys.WILDCARD.equals(binaryExpression.getFirstOperand().toString())) {
return new Predicate(binaryExpression.getSecondOperand());
}
return refinement;
}

private Expression unwrapGroupExpression(Expression expression) {
while (expression instanceof GroupExpression groupExpression)
expression = groupExpression.getExpression();
return expression;
}

/**
* Retrieves the refinements for the variable write inside unary operation
*
Expand Down
Loading