blob: 4550cc52e69336c2ce844ac540ca5cf36f4479ea [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.tools.lint.detector.api;
import static com.android.tools.lint.detector.api.JavaContext.getParentOfType;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.tools.lint.client.api.JavaParser.ResolvedField;
import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
import java.util.ListIterator;
import lombok.ast.BinaryExpression;
import lombok.ast.BinaryOperator;
import lombok.ast.BooleanLiteral;
import lombok.ast.Cast;
import lombok.ast.Expression;
import lombok.ast.ExpressionStatement;
import lombok.ast.FloatingPointLiteral;
import lombok.ast.InlineIfExpression;
import lombok.ast.IntegralLiteral;
import lombok.ast.Node;
import lombok.ast.NullLiteral;
import lombok.ast.Select;
import lombok.ast.Statement;
import lombok.ast.StringLiteral;
import lombok.ast.UnaryExpression;
import lombok.ast.UnaryOperator;
import lombok.ast.VariableDeclaration;
import lombok.ast.VariableDefinition;
import lombok.ast.VariableDefinitionEntry;
import lombok.ast.VariableReference;
/** Evaluates constant expressions */
public class ConstantEvaluator {
private final JavaContext mContext;
private boolean mAllowUnknown;
/**
* Creates a new constant evaluator
*
* @param context the context to use to resolve field references, if any
*/
public ConstantEvaluator(@Nullable JavaContext context) {
mContext = context;
}
/**
* Whether we allow computing values where some terms are unknown. For example, the expression
* {@code "foo" + x + "bar"} would return {@code null} without and {@code "foobar"} with.
*
* @return this for constructor chaining
*/
public ConstantEvaluator allowUnknowns() {
mAllowUnknown = true;
return this;
}
/**
* Evaluates the given node and returns the constant value it resolves to, if any
*
* @param node the node to compute the constant value for
* @return the corresponding constant value - a String, an Integer, a Float, and so on
*/
@Nullable
public Object evaluate(@NonNull Node node) {
if (node instanceof NullLiteral) {
return null;
} else if (node instanceof BooleanLiteral) {
return ((BooleanLiteral)node).astValue();
} else if (node instanceof StringLiteral) {
StringLiteral string = (StringLiteral) node;
return string.astValue();
} else if (node instanceof IntegralLiteral) {
IntegralLiteral literal = (IntegralLiteral) node;
// Don't combine to ?: since that will promote astIntValue to a long
if (literal.astMarkedAsLong()) {
return literal.astLongValue();
} else {
return literal.astIntValue();
}
} else if (node instanceof FloatingPointLiteral) {
FloatingPointLiteral literal = (FloatingPointLiteral) node;
// Don't combine to ?: since that will promote astFloatValue to a double
if (literal.astMarkedAsFloat()) {
return literal.astFloatValue();
} else {
return literal.astDoubleValue();
}
} else if (node instanceof UnaryExpression) {
UnaryOperator operator = ((UnaryExpression) node).astOperator();
Object operand = evaluate(((UnaryExpression) node).astOperand());
if (operand == null) {
return null;
}
switch (operator) {
case LOGICAL_NOT:
if (operand instanceof Boolean) {
return !(Boolean) operand;
}
break;
case UNARY_PLUS:
return operand;
case BINARY_NOT:
if (operand instanceof Integer) {
return ~(Integer) operand;
} else if (operand instanceof Long) {
return ~(Long) operand;
} else if (operand instanceof Short) {
return ~(Short) operand;
} else if (operand instanceof Character) {
return ~(Character) operand;
} else if (operand instanceof Byte) {
return ~(Byte) operand;
}
break;
case UNARY_MINUS:
if (operand instanceof Integer) {
return -(Integer) operand;
} else if (operand instanceof Long) {
return -(Long) operand;
} else if (operand instanceof Double) {
return -(Double) operand;
} else if (operand instanceof Float) {
return -(Float) operand;
} else if (operand instanceof Short) {
return -(Short) operand;
} else if (operand instanceof Character) {
return -(Character) operand;
} else if (operand instanceof Byte) {
return -(Byte) operand;
}
break;
}
} else if (node instanceof InlineIfExpression) {
InlineIfExpression expression = (InlineIfExpression) node;
Object known = evaluate(expression.astCondition());
if (known == Boolean.TRUE && expression.astIfTrue() != null) {
return evaluate(expression.astIfTrue());
} else if (known == Boolean.FALSE && expression.astIfFalse() != null) {
return evaluate(expression.astIfFalse());
}
} else if (node instanceof BinaryExpression) {
BinaryOperator operator = ((BinaryExpression) node).astOperator();
Object operandLeft = evaluate(((BinaryExpression) node).astLeft());
Object operandRight = evaluate(((BinaryExpression) node).astRight());
if (operandLeft == null || operandRight == null) {
if (mAllowUnknown) {
if (operandLeft == null) {
return operandRight;
} else {
return operandLeft;
}
}
return null;
}
if (operandLeft instanceof String && operandRight instanceof String) {
if (operator == BinaryOperator.PLUS) {
return operandLeft.toString() + operandRight.toString();
}
return null;
} else if (operandLeft instanceof Boolean && operandRight instanceof Boolean) {
boolean left = (Boolean) operandLeft;
boolean right = (Boolean) operandRight;
switch (operator) {
case LOGICAL_OR:
return left || right;
case LOGICAL_AND:
return left && right;
case BITWISE_OR:
return left | right;
case BITWISE_XOR:
return left ^ right;
case BITWISE_AND:
return left & right;
case EQUALS:
return left == right;
case NOT_EQUALS:
return left != right;
}
} else if (operandLeft instanceof Number && operandRight instanceof Number) {
Number left = (Number) operandLeft;
Number right = (Number) operandRight;
boolean isInteger =
!(left instanceof Float || left instanceof Double
|| right instanceof Float || right instanceof Double);
boolean isWide =
isInteger ? (left instanceof Long || right instanceof Long)
: (left instanceof Double || right instanceof Double);
switch (operator) {
case BITWISE_OR:
if (isWide) {
return left.longValue() | right.longValue();
} else {
return left.intValue() | right.intValue();
}
case BITWISE_XOR:
if (isWide) {
return left.longValue() ^ right.longValue();
} else {
return left.intValue() ^ right.intValue();
}
case BITWISE_AND:
if (isWide) {
return left.longValue() & right.longValue();
} else {
return left.intValue() & right.intValue();
}
case EQUALS:
if (isInteger) {
return left.longValue() == right.longValue();
} else {
return left.doubleValue() == right.doubleValue();
}
case NOT_EQUALS:
if (isInteger) {
return left.longValue() != right.longValue();
} else {
return left.doubleValue() != right.doubleValue();
}
case GREATER:
if (isInteger) {
return left.longValue() > right.longValue();
} else {
return left.doubleValue() > right.doubleValue();
}
case GREATER_OR_EQUAL:
if (isInteger) {
return left.longValue() >= right.longValue();
} else {
return left.doubleValue() >= right.doubleValue();
}
case LESS:
if (isInteger) {
return left.longValue() < right.longValue();
} else {
return left.doubleValue() < right.doubleValue();
}
case LESS_OR_EQUAL:
if (isInteger) {
return left.longValue() <= right.longValue();
} else {
return left.doubleValue() <= right.doubleValue();
}
case SHIFT_LEFT:
if (isWide) {
return left.longValue() << right.intValue();
} else {
return left.intValue() << right.intValue();
}
case SHIFT_RIGHT:
if (isWide) {
return left.longValue() >> right.intValue();
} else {
return left.intValue() >> right.intValue();
}
case BITWISE_SHIFT_RIGHT:
if (isWide) {
return left.longValue() >>> right.intValue();
} else {
return left.intValue() >>> right.intValue();
}
case PLUS:
if (isInteger) {
if (isWide) {
return left.longValue() + right.longValue();
} else {
return left.intValue() + right.intValue();
}
} else {
if (isWide) {
return left.doubleValue() + right.doubleValue();
} else {
return left.floatValue() + right.floatValue();
}
}
case MINUS:
if (isInteger) {
if (isWide) {
return left.longValue() - right.longValue();
} else {
return left.intValue() - right.intValue();
}
} else {
if (isWide) {
return left.doubleValue() - right.doubleValue();
} else {
return left.floatValue() - right.floatValue();
}
}
case MULTIPLY:
if (isInteger) {
if (isWide) {
return left.longValue() * right.longValue();
} else {
return left.intValue() * right.intValue();
}
} else {
if (isWide) {
return left.doubleValue() * right.doubleValue();
} else {
return left.floatValue() * right.floatValue();
}
}
case DIVIDE:
if (isInteger) {
if (isWide) {
return left.longValue() / right.longValue();
} else {
return left.intValue() / right.intValue();
}
} else {
if (isWide) {
return left.doubleValue() / right.doubleValue();
} else {
return left.floatValue() / right.floatValue();
}
}
case REMAINDER:
if (isInteger) {
if (isWide) {
return left.longValue() % right.longValue();
} else {
return left.intValue() % right.intValue();
}
} else {
if (isWide) {
return left.doubleValue() % right.doubleValue();
} else {
return left.floatValue() % right.floatValue();
}
}
default:
return null;
}
}
} else if (node instanceof Cast) {
Cast cast = (Cast)node;
Object operandValue = evaluate(cast.astOperand());
if (operandValue instanceof Number) {
Number number = (Number)operandValue;
String typeName = cast.astTypeReference().getTypeName();
if (typeName.equals("float")) {
return number.floatValue();
} else if (typeName.equals("double")) {
return number.doubleValue();
} else if (typeName.equals("int")) {
return number.intValue();
} else if (typeName.equals("long")) {
return number.longValue();
} else if (typeName.equals("short")) {
return number.shortValue();
} else if (typeName.equals("byte")) {
return number.byteValue();
}
}
return operandValue;
} else if (mContext != null && (node instanceof VariableReference ||
node instanceof Select)) {
ResolvedNode resolved = mContext.resolve(node);
if (resolved instanceof ResolvedField) {
ResolvedField field = (ResolvedField) resolved;
return field.getValue();
} else if (node instanceof VariableReference) {
Statement statement = getParentOfType(node, Statement.class, false);
if (statement != null) {
ListIterator<Node> iterator = statement.getParent().getChildren().listIterator();
while (iterator.hasNext()) {
if (iterator.next() == statement) {
if (iterator.hasPrevious()) { // should always be true
iterator.previous();
}
break;
}
}
String targetName = ((VariableReference)node).astIdentifier().astValue();
while (iterator.hasPrevious()) {
Node previous = iterator.previous();
if (previous instanceof VariableDeclaration) {
VariableDeclaration declaration = (VariableDeclaration) previous;
VariableDefinition definition = declaration.astDefinition();
for (VariableDefinitionEntry entry : definition
.astVariables()) {
if (entry.astInitializer() != null
&& entry.astName().astValue().equals(targetName)) {
return evaluate(entry.astInitializer());
}
}
} else if (previous instanceof ExpressionStatement) {
ExpressionStatement expressionStatement = (ExpressionStatement) previous;
Expression expression = expressionStatement.astExpression();
if (expression instanceof BinaryExpression &&
((BinaryExpression) expression).astOperator()
== BinaryOperator.ASSIGN) {
BinaryExpression binaryExpression = (BinaryExpression) expression;
if (targetName.equals(binaryExpression.astLeft().toString())) {
return evaluate(binaryExpression.astRight());
}
}
}
}
}
}
}
// TODO: Check for MethodInvocation and perform some common operations -
// Math.* methods, String utility methods like notNullize, etc
return null;
}
/**
* Evaluates the given node and returns the constant value it resolves to, if any. Convenience
* wrapper which creates a new {@linkplain ConstantEvaluator}, evaluates the node and returns
* the result.
*
* @param context the context to use to resolve field references, if any
* @param node the node to compute the constant value for
* @return the corresponding constant value - a String, an Integer, a Float, and so on
*/
@Nullable
public static Object evaluate(@NonNull JavaContext context, @NonNull Node node) {
return new ConstantEvaluator(context).evaluate(node);
}
/**
* Evaluates the given node and returns the constant string it resolves to, if any. Convenience
* wrapper which creates a new {@linkplain ConstantEvaluator}, evaluates the node and returns
* the result if the result is a string.
*
* @param context the context to use to resolve field references, if any
* @param node the node to compute the constant value for
* @param allowUnknown whether we should construct the string even if some parts of it are
* unknown
* @return the corresponding string, if any
*/
@Nullable
public static String evaluateString(@NonNull JavaContext context, @NonNull Node node,
boolean allowUnknown) {
ConstantEvaluator evaluator = new ConstantEvaluator(context);
if (allowUnknown) {
evaluator.allowUnknowns();
}
Object value = evaluator.evaluate(node);
return value instanceof String ? (String) value : null;
}
}