/**
* Copyright (C) 2006-2017 INRIA and contributors
* Spoon - http://spoon.gforge.inria.fr/
*
* This software is governed by the CeCILL-C License under French law and
* abiding by the rules of distribution of free software. You can use, modify
* and/or redistribute the software under the terms of the CeCILL-C license as
* circulated by CEA, CNRS and INRIA at http://www.cecill.info.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the CeCILL-C License for more details.
*
* The fact that you are presently reading this means that you have had
* knowledge of the CeCILL-C license and that you accept its terms.
*/
package spoon.support.reflect.eval;
import spoon.reflect.code.CtAnnotationFieldAccess;
import spoon.reflect.code.CtAssignment;
import spoon.reflect.code.CtBinaryOperator;
import spoon.reflect.code.CtBlock;
import spoon.reflect.code.CtCatchVariable;
import spoon.reflect.code.CtComment;
import spoon.reflect.code.CtConditional;
import spoon.reflect.code.CtDo;
import spoon.reflect.code.CtExpression;
import spoon.reflect.code.CtFieldAccess;
import spoon.reflect.code.CtFieldRead;
import spoon.reflect.code.CtFieldWrite;
import spoon.reflect.code.CtFor;
import spoon.reflect.code.CtIf;
import spoon.reflect.code.CtInvocation;
import spoon.reflect.code.CtLiteral;
import spoon.reflect.code.CtLocalVariable;
import spoon.reflect.code.CtReturn;
import spoon.reflect.code.CtStatement;
import spoon.reflect.code.CtSynchronized;
import spoon.reflect.code.CtThrow;
import spoon.reflect.code.CtUnaryOperator;
import spoon.reflect.code.CtVariableAccess;
import spoon.reflect.code.CtVariableRead;
import spoon.reflect.code.CtVariableWrite;
import spoon.reflect.code.CtWhile;
import spoon.reflect.declaration.CtElement;
import spoon.reflect.declaration.CtExecutable;
import spoon.reflect.declaration.CtField;
import spoon.reflect.declaration.CtType;
import spoon.reflect.declaration.CtVariable;
import spoon.reflect.declaration.ModifierKind;
import spoon.reflect.eval.PartialEvaluator;
import spoon.reflect.reference.CtFieldReference;
import spoon.reflect.reference.CtTypeReference;
import spoon.reflect.visitor.CtScanner;
import spoon.support.util.RtHelper;
import java.util.List;
/**
*
* Simplifies an AST by performing all operations that are statically known and changes the AST accordingly (eg "0+1" -> "1")
* This visitor implements a simple partial evaluator for the program
* compile-time metamodel.
*/
public class VisitorPartialEvaluator extends CtScanner implements PartialEvaluator {
boolean flowEnded = false;
CtElement result;
Number convert(CtTypeReference<?> type, Number n) {
if ((type.getActualClass() == int.class) || (type.getActualClass() == Integer.class)) {
return n.intValue();
}
if ((type.getActualClass() == byte.class) || (type.getActualClass() == Byte.class)) {
return n.byteValue();
}
if ((type.getActualClass() == long.class) || (type.getActualClass() == Long.class)) {
return n.longValue();
}
if ((type.getActualClass() == float.class) || (type.getActualClass() == Float.class)) {
return n.floatValue();
}
if ((type.getActualClass() == short.class) || (type.getActualClass() == Short.class)) {
return n.shortValue();
}
return n;
}
@Override
protected void exit(CtElement e) {
// if we go back to CtScanner, we discard the temp result
result = null;
}
@SuppressWarnings("unchecked")
public <R extends CtElement> R evaluate(R element) {
if (element == null) {
return null;
}
element.accept(this);
if (result != null) {
result.setParent(element.getParent());
return (R) result;
}
// otherwise nothing has been changed
return (R) element.clone();
}
void setResult(CtElement element) {
result = element;
}
@SuppressWarnings("unchecked")
public <T> void visitCtBinaryOperator(CtBinaryOperator<T> operator) {
CtExpression<?> left = evaluate(operator.getLeftHandOperand());
CtExpression<?> right = evaluate(operator.getRightHandOperand());
if ((left instanceof CtLiteral) && (right instanceof CtLiteral)) {
Object leftObject = ((CtLiteral<?>) left).getValue();
Object rightObject = ((CtLiteral<?>) right).getValue();
CtLiteral<Object> res = operator.getFactory().Core().createLiteral();
switch (operator.getKind()) {
case AND:
res.setValue((Boolean) leftObject && (Boolean) rightObject);
break;
case OR:
res.setValue((Boolean) leftObject || (Boolean) rightObject);
break;
case EQ:
if (leftObject == null) {
res.setValue(leftObject == rightObject);
} else {
res.setValue(leftObject.equals(rightObject));
}
break;
case NE:
if (leftObject == null) {
res.setValue(leftObject != rightObject);
} else {
res.setValue(!leftObject.equals(rightObject));
}
break;
case GE:
res.setValue(((Number) leftObject).doubleValue() >= ((Number) rightObject).doubleValue());
break;
case LE:
res.setValue(((Number) leftObject).doubleValue() <= ((Number) rightObject).doubleValue());
break;
case GT:
res.setValue(((Number) leftObject).doubleValue() > ((Number) rightObject).doubleValue());
break;
case LT:
res.setValue(((Number) leftObject).doubleValue() < ((Number) rightObject).doubleValue());
break;
case MINUS:
res.setValue(convert(operator.getType(),
((Number) leftObject).doubleValue() - ((Number) rightObject).doubleValue()));
break;
case MUL:
res.setValue(convert(operator.getType(),
((Number) leftObject).doubleValue() * ((Number) rightObject).doubleValue()));
break;
case DIV:
res.setValue(convert(operator.getType(),
((Number) leftObject).doubleValue() / ((Number) rightObject).doubleValue()));
break;
case PLUS:
if ((leftObject instanceof String) || (rightObject instanceof String)) {
res.setValue("" + leftObject + rightObject);
} else {
res.setValue(convert(operator.getType(),
((Number) leftObject).doubleValue() + ((Number) rightObject).doubleValue()));
}
break;
case BITAND:
if (leftObject instanceof Boolean) {
res.setValue((Boolean) leftObject & (Boolean) rightObject);
} else {
res.setValue(((Number) leftObject).intValue() & ((Number) rightObject).intValue());
}
break;
case BITOR:
if (leftObject instanceof Boolean) {
res.setValue((Boolean) leftObject | (Boolean) rightObject);
} else {
res.setValue(((Number) leftObject).intValue() | ((Number) rightObject).intValue());
}
break;
case BITXOR:
if (leftObject instanceof Boolean) {
res.setValue((Boolean) leftObject ^ (Boolean) rightObject);
} else {
res.setValue(((Number) leftObject).intValue() ^ ((Number) rightObject).intValue());
}
break;
default:
throw new RuntimeException("unsupported operator " + operator.getKind());
}
setResult(res);
return;
} else if ((left instanceof CtLiteral) || (right instanceof CtLiteral)) {
CtLiteral<?> literal;
CtExpression<?> expr;
if (left instanceof CtLiteral) {
literal = (CtLiteral<?>) left;
expr = right;
} else {
literal = (CtLiteral<?>) right;
expr = left;
}
Object o = literal.getValue();
CtLiteral<Object> res = operator.getFactory().Core().createLiteral();
switch (operator.getKind()) {
case AND:
if ((Boolean) o) {
setResult(expr);
} else {
res.setValue(false);
setResult(res);
}
return;
case OR:
if ((Boolean) o) {
res.setValue(true);
setResult(res);
} else {
setResult(expr);
}
return;
case BITOR:
if ((o instanceof Boolean) && (Boolean) o) {
res.setValue(true);
setResult(res);
}
return;
default:
// TODO: other cases?
}
}
}
public <R> void visitCtBlock(CtBlock<R> block) {
CtBlock<?> b = block.getFactory().Core().createBlock();
for (CtStatement s : block.getStatements()) {
CtElement res = evaluate(s);
if (res != null) {
b.addStatement((CtStatement) res);
}
// do not copy unreachable statements
if (flowEnded) {
break;
}
}
setResult(b);
}
public void visitCtDo(CtDo doLoop) {
CtDo w = doLoop.clone();
w.setLoopingExpression(evaluate(doLoop.getLoopingExpression()));
w.setBody(evaluate(doLoop.getBody()));
setResult(w);
}
@Override
public <T> void visitCtFieldRead(CtFieldRead<T> fieldRead) {
visitFieldAccess(fieldRead);
}
@Override
public <T> void visitCtFieldWrite(CtFieldWrite<T> fieldWrite) {
visitFieldAccess(fieldWrite);
}
private <T> void visitFieldAccess(CtFieldAccess<T> fieldAccess) {
if ("class".equals(fieldAccess.getVariable().getSimpleName())) {
Class<?> actualClass = fieldAccess.getVariable().getDeclaringType().getActualClass();
if (actualClass != null) {
CtLiteral<Class<?>> literal = fieldAccess.getFactory().Core().createLiteral();
literal.setValue(actualClass);
setResult(literal);
return;
}
}
if (fieldAccess.getFactory().Type().createReference(Enum.class)
.isSubtypeOf(fieldAccess.getVariable().getDeclaringType())) {
CtLiteral<CtFieldReference<?>> l = fieldAccess.getFactory().Core().createLiteral();
l.setValue(fieldAccess.getVariable());
setResult(l);
return;
}
CtField<?> f = fieldAccess.getVariable().getDeclaration();
if ((f != null) && f.getModifiers().contains(ModifierKind.FINAL)) {
setResult(evaluate(f.getDefaultExpression()));
return;
}
setResult(fieldAccess.clone());
}
public <T> void visitCtAnnotationFieldAccess(CtAnnotationFieldAccess<T> annotationFieldAccess) {
CtField<?> f = annotationFieldAccess.getVariable().getDeclaration();
setResult(evaluate(f.getDefaultExpression()));
}
public void visitCtFor(CtFor forLoop) {
// Evaluate forInit
List<CtStatement> lst = forLoop.getForInit();
for (CtStatement s : lst) {
CtStatement evaluateStatement = evaluate(s);
if (evaluateStatement != null) {
forLoop.addForInit(evaluateStatement);
}
}
// Evaluate Expression
forLoop.setExpression(evaluate(forLoop.getExpression()));
// Evaluate forUpdate
lst = forLoop.getForUpdate();
for (CtStatement s : lst) {
CtStatement evaluateStatement = evaluate(s);
if (evaluateStatement != null) {
forLoop.addForUpdate(evaluateStatement);
}
}
setResult(forLoop.clone());
}
public void visitCtIf(CtIf ifElement) {
CtExpression<Boolean> r = evaluate(ifElement.getCondition());
if (r instanceof CtLiteral) {
CtLiteral<Boolean> l = (CtLiteral<Boolean>) r;
if (l.getValue()) {
setResult(evaluate(ifElement.getThenStatement()));
} else {
if (ifElement.getElseStatement() != null) {
setResult(evaluate(ifElement.getElseStatement()));
} else {
setResult(ifElement.getFactory().Code().createComment("if removed", CtComment.CommentType.INLINE));
}
}
} else {
CtIf ifRes = ifElement.getFactory().Core().createIf();
ifRes.setCondition(r);
boolean thenEnded = false, elseEnded = false;
ifRes.setThenStatement((CtStatement) evaluate(ifElement.getThenStatement()));
if (flowEnded) {
thenEnded = true;
flowEnded = false;
}
if (ifElement.getElseStatement() != null) {
ifRes.setElseStatement((CtStatement) evaluate(ifElement.getElseStatement()));
}
if (flowEnded) {
elseEnded = true;
flowEnded = false;
}
setResult(ifRes);
if (thenEnded && elseEnded) {
flowEnded = true;
}
}
}
public <T> void visitCtInvocation(CtInvocation<T> invocation) {
CtInvocation<T> i = invocation.getFactory().Core().createInvocation();
i.setExecutable(invocation.getExecutable());
boolean constant = true;
i.setTarget(evaluate(invocation.getTarget()));
if ((i.getTarget() != null) && !(i.getTarget() instanceof CtLiteral)) {
constant = false;
}
for (CtExpression<?> e : invocation.getArguments()) {
CtExpression<?> re = evaluate(e);
if (!(re instanceof CtLiteral)) {
constant = false;
}
i.addArgument(re);
}
// do not partially evaluate super(...)
if (i.getExecutable().getSimpleName().equals("<init>")) {
setResult(i);
return;
}
if (constant) {
CtExecutable<?> executable = invocation.getExecutable().getDeclaration();
// try to inline partial evaluation results for local calls
// (including to superclasses)
if ((executable != null) && (invocation.getType() != null) && invocation.getExecutable().getDeclaringType()
.isSubtypeOf(((CtType<?>) invocation.getParent(CtType.class)).getReference())) {
CtBlock<?> b = evaluate(executable.getBody());
flowEnded = false;
CtStatement last = b.getStatements().get(b.getStatements().size() - 1);
if ((last != null) && (last instanceof CtReturn)) {
if (((CtReturn<?>) last).getReturnedExpression() instanceof CtLiteral) {
setResult(((CtReturn<?>) last).getReturnedExpression());
return;
}
}
} else {
// try to completely evaluate
T r = null;
try {
// System.err.println("invocking "+i);
r = RtHelper.invoke(i);
CtLiteral<T> l = invocation.getFactory().Core().createLiteral();
l.setValue(r);
setResult(l);
return;
} catch (Exception e) {
}
}
}
setResult(i);
}
@Override
public <T> void visitCtField(CtField<T> f) {
CtField<T> r = f.clone();
r.setDefaultExpression(evaluate(f.getDefaultExpression()));
setResult(r);
}
public <T> void visitCtLocalVariable(final CtLocalVariable<T> localVariable) {
CtLocalVariable<T> r = localVariable.clone();
r.setDefaultExpression(evaluate(localVariable.getDefaultExpression()));
setResult(r);
}
@Override
public <T> void visitCtCatchVariable(CtCatchVariable<T> catchVariable) {
CtCatchVariable<T> r = catchVariable.clone();
r.setDefaultExpression(evaluate(catchVariable.getDefaultExpression()));
setResult(r);
}
public <R> void visitCtReturn(CtReturn<R> returnStatement) {
CtReturn<R> r = returnStatement.getFactory().Core().createReturn();
r.setReturnedExpression(evaluate(returnStatement.getReturnedExpression()));
setResult(r);
flowEnded = true;
}
public void visitCtSynchronized(CtSynchronized synchro) {
CtSynchronized s = synchro.clone();
s.setBlock(evaluate(synchro.getBlock()));
setResult(s);
}
public void visitCtThrow(CtThrow throwStatement) {
CtThrow r = throwStatement.getFactory().Core().createThrow();
r.setThrownExpression(evaluate(throwStatement.getThrownExpression()));
setResult(r);
flowEnded = true;
}
public <T> void visitCtUnaryOperator(CtUnaryOperator<T> operator) {
CtExpression<?> operand = evaluate(operator.getOperand());
if (operand instanceof CtLiteral) {
Object object = ((CtLiteral<?>) operand).getValue();
CtLiteral<Object> res = operator.getFactory().Core().createLiteral();
switch (operator.getKind()) {
case NOT:
res.setValue(!(Boolean) object);
break;
default:
throw new RuntimeException("unsupported operator " + operator.getKind());
}
setResult(res);
return;
}
setResult(operator.clone());
}
@Override
public <T> void visitCtVariableRead(CtVariableRead<T> variableRead) {
visitVariableAccess(variableRead);
}
@Override
public <T> void visitCtVariableWrite(CtVariableWrite<T> variableWrite) {
visitVariableAccess(variableWrite);
}
private <T> void visitVariableAccess(CtVariableAccess<T> variableAccess) {
CtVariable<?> v = variableAccess.getVariable().getDeclaration();
if (v != null && v.hasModifier(ModifierKind.FINAL) && v.getDefaultExpression() != null) {
setResult(evaluate(v.getDefaultExpression()));
} else {
setResult(variableAccess.clone());
}
}
public <T, A extends T> void visitCtAssignment(CtAssignment<T, A> variableAssignment) {
CtAssignment<T, A> a = variableAssignment.clone();
a.setAssignment(evaluate(a.getAssignment()));
setResult(a);
}
public void visitCtWhile(CtWhile whileLoop) {
CtWhile w = whileLoop.clone();
w.setLoopingExpression(evaluate(whileLoop.getLoopingExpression()));
// If lopping Expression always false
if ((whileLoop.getLoopingExpression() instanceof CtLiteral) && !((CtLiteral<Boolean>) whileLoop
.getLoopingExpression()).getValue()) {
setResult(null);
return;
}
w.setBody(evaluate(whileLoop.getBody()));
setResult(w);
}
public <T> void visitCtConditional(CtConditional<T> conditional) {
CtExpression<Boolean> r = evaluate(conditional.getCondition());
if (r instanceof CtLiteral) {
CtLiteral<Boolean> l = (CtLiteral<Boolean>) r;
if (l.getValue()) {
setResult(evaluate(conditional.getThenExpression()));
} else {
setResult(evaluate(conditional.getElseExpression()));
}
} else {
CtConditional<T> ifRes = conditional.getFactory().Core().createConditional();
ifRes.setCondition(r);
ifRes.setThenExpression(evaluate(conditional.getThenExpression()));
ifRes.setElseExpression(evaluate(conditional.getElseExpression()));
setResult(ifRes);
}
}
}