/* * 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.klint.detector.api; import static com.android.tools.klint.client.api.JavaParser.TYPE_BOOLEAN; import static com.android.tools.klint.client.api.JavaParser.TYPE_CHAR; import static com.android.tools.klint.client.api.JavaParser.TYPE_DOUBLE; import static com.android.tools.klint.client.api.JavaParser.TYPE_FLOAT; import static com.android.tools.klint.client.api.JavaParser.TYPE_INT; import static com.android.tools.klint.client.api.JavaParser.TYPE_LONG; import static com.android.tools.klint.client.api.JavaParser.TYPE_STRING; import static com.android.tools.klint.detector.api.JavaContext.getParentOfType; import com.android.annotations.NonNull; import com.android.annotations.Nullable; import com.android.tools.klint.client.api.JavaParser.DefaultTypeDescriptor; import com.android.tools.klint.client.api.JavaParser.ResolvedClass; import com.android.tools.klint.client.api.JavaParser.ResolvedField; import com.android.tools.klint.client.api.JavaParser.ResolvedMethod; import com.android.tools.klint.client.api.JavaParser.ResolvedNode; import com.android.tools.klint.client.api.JavaParser.ResolvedVariable; import com.android.tools.klint.client.api.JavaParser.TypeDescriptor; import com.android.tools.klint.client.api.UastLintUtils; import com.intellij.psi.PsiAssignmentExpression; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiDeclarationStatement; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiExpression; import com.intellij.psi.PsiExpressionStatement; import com.intellij.psi.PsiField; import com.intellij.psi.PsiLocalVariable; import com.intellij.psi.PsiMethod; import com.intellij.psi.PsiReference; import com.intellij.psi.PsiReferenceExpression; import com.intellij.psi.PsiStatement; import com.intellij.psi.PsiType; import com.intellij.psi.util.PsiTreeUtil; import org.jetbrains.uast.UCallExpression; import org.jetbrains.uast.UElement; import org.jetbrains.uast.UExpression; import org.jetbrains.uast.UMethod; import org.jetbrains.uast.UVariable; import org.jetbrains.uast.UastUtils; import org.jetbrains.uast.UReferenceExpression; import org.jetbrains.uast.util.UastExpressionUtils; import java.util.ListIterator; import lombok.ast.BinaryExpression; import lombok.ast.BinaryOperator; import lombok.ast.BooleanLiteral; import lombok.ast.Cast; import lombok.ast.CharLiteral; import lombok.ast.Expression; import lombok.ast.ExpressionStatement; import lombok.ast.FloatingPointLiteral; import lombok.ast.InlineIfExpression; import lombok.ast.IntegralLiteral; import lombok.ast.Literal; import lombok.ast.Node; import lombok.ast.NullLiteral; import lombok.ast.Statement; import lombok.ast.StringLiteral; import lombok.ast.UnaryExpression; import lombok.ast.VariableDeclaration; import lombok.ast.VariableDefinition; import lombok.ast.VariableDefinitionEntry; import lombok.ast.VariableReference; /** * Evaluates the types of nodes. This goes deeper than * {@link JavaContext#getType(Node)} in that it analyzes the * flow and for example figures out that if you ask for the type of {@code var} * in this code snippet: * <pre> * Object o = new StringBuilder(); * Object var = o; * </pre> * it will return "java.lang.StringBuilder". * <p> * <b>NOTE:</b> This type evaluator does not (yet) compute the correct * types when involving implicit type conversions, so be careful * if using this for primitives; e.g. for "int * long" it might return * the type "int". */ public class TypeEvaluator { private final JavaContext mContext; /** * Creates a new constant evaluator * * @param context the context to use to resolve field references, if any */ public TypeEvaluator(@Nullable JavaContext context) { mContext = context; } /** * Returns the inferred type of the given node * @deprecated Use {@link #evaluate(PsiElement)} instead */ @Deprecated @Nullable public TypeDescriptor evaluate(@NonNull Node node) { ResolvedNode resolved = null; if (mContext != null) { resolved = mContext.resolve(node); } if (resolved instanceof ResolvedMethod) { TypeDescriptor type; ResolvedMethod method = (ResolvedMethod) resolved; if (method.isConstructor()) { ResolvedClass containingClass = method.getContainingClass(); type = containingClass.getType(); } else { type = method.getReturnType(); } return type; } if (resolved instanceof ResolvedField) { ResolvedField field = (ResolvedField) resolved; Node astNode = field.findAstNode(); if (astNode instanceof VariableDeclaration) { VariableDeclaration declaration = (VariableDeclaration)astNode; VariableDefinition definition = declaration.astDefinition(); if (definition != null) { VariableDefinitionEntry first = definition.astVariables().first(); if (first != null) { Expression initializer = first.astInitializer(); if (initializer != null) { TypeDescriptor type = evaluate(initializer); if (type != null) { return type; } } } } } return field.getType(); } 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()); } } } } } } else if (node instanceof Cast) { Cast cast = (Cast) node; if (mContext != null) { ResolvedNode typeReference = mContext.resolve(cast.astTypeReference()); if (typeReference instanceof ResolvedClass) { return ((ResolvedClass) typeReference).getType(); } } TypeDescriptor viewType = evaluate(cast.astOperand()); if (viewType != null) { return viewType; } } else if (node instanceof Literal) { if (node instanceof NullLiteral) { return null; } else if (node instanceof BooleanLiteral) { return new DefaultTypeDescriptor(TYPE_BOOLEAN); } else if (node instanceof StringLiteral) { return new DefaultTypeDescriptor(TYPE_STRING); } else if (node instanceof CharLiteral) { return new DefaultTypeDescriptor(TYPE_CHAR); } 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 new DefaultTypeDescriptor(TYPE_LONG); } else { return new DefaultTypeDescriptor(TYPE_INT); } } 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 new DefaultTypeDescriptor(TYPE_FLOAT); } else { return new DefaultTypeDescriptor(TYPE_DOUBLE); } } } else if (node instanceof UnaryExpression) { return evaluate(((UnaryExpression) node).astOperand()); } else if (node instanceof InlineIfExpression) { InlineIfExpression expression = (InlineIfExpression) node; if (expression.astIfTrue() != null) { return evaluate(expression.astIfTrue()); } else if (expression.astIfFalse() != null) { return evaluate(expression.astIfFalse()); } } else if (node instanceof BinaryExpression) { BinaryExpression expression = (BinaryExpression) node; BinaryOperator operator = expression.astOperator(); switch (operator) { case LOGICAL_OR: case LOGICAL_AND: case EQUALS: case NOT_EQUALS: case GREATER: case GREATER_OR_EQUAL: case LESS: case LESS_OR_EQUAL: return new DefaultTypeDescriptor(TYPE_BOOLEAN); } TypeDescriptor type = evaluate(expression.astLeft()); if (type != null) { return type; } return evaluate(expression.astRight()); } if (resolved instanceof ResolvedVariable) { ResolvedVariable variable = (ResolvedVariable) resolved; return variable.getType(); } return null; } /** * Returns the inferred type of the given node */ @Nullable public PsiType evaluate(@Nullable PsiElement node) { if (node == null) { return null; } PsiElement resolved = null; if (node instanceof PsiReference) { resolved = ((PsiReference) node).resolve(); } if (resolved instanceof PsiMethod) { PsiMethod method = (PsiMethod) resolved; if (method.isConstructor()) { PsiClass containingClass = method.getContainingClass(); if (containingClass != null && mContext != null) { return mContext.getEvaluator().getClassType(containingClass); } } else { return method.getReturnType(); } } if (resolved instanceof PsiField) { PsiField field = (PsiField) resolved; if (field.getInitializer() != null) { PsiType type = evaluate(field.getInitializer()); if (type != null) { return type; } } return field.getType(); } else if (resolved instanceof PsiLocalVariable) { PsiLocalVariable variable = (PsiLocalVariable) resolved; PsiStatement statement = PsiTreeUtil.getParentOfType(node, PsiStatement.class, false); if (statement != null) { PsiStatement prev = PsiTreeUtil.getPrevSiblingOfType(statement, PsiStatement.class); String targetName = variable.getName(); if (targetName == null) { return null; } while (prev != null) { if (prev instanceof PsiDeclarationStatement) { for (PsiElement element : ((PsiDeclarationStatement)prev).getDeclaredElements()) { if (variable.equals(element)) { return evaluate(variable.getInitializer()); } } } else if (prev instanceof PsiExpressionStatement) { PsiExpression expression = ((PsiExpressionStatement)prev).getExpression(); if (expression instanceof PsiAssignmentExpression) { PsiAssignmentExpression assign = (PsiAssignmentExpression) expression; PsiExpression lhs = assign.getLExpression(); if (lhs instanceof PsiReferenceExpression) { PsiReferenceExpression reference = (PsiReferenceExpression) lhs; if (targetName.equals(reference.getReferenceName()) && reference.getQualifier() == null) { return evaluate(assign.getRExpression()); } } } } prev = PsiTreeUtil.getPrevSiblingOfType(prev, PsiStatement.class); } } return variable.getType(); } else if (node instanceof PsiExpression) { PsiExpression expression = (PsiExpression) node; return expression.getType(); } return null; } @Nullable public static PsiType evaluate(@NonNull JavaContext context, @Nullable UElement node) { if (node == null) { return null; } UElement resolved = node; if (resolved instanceof UReferenceExpression) { resolved = UastUtils.tryResolveUDeclaration(resolved, context.getUastContext()); } if (resolved instanceof UMethod) { return ((UMethod) resolved).getPsi().getReturnType(); } else if (resolved instanceof UVariable) { UVariable variable = (UVariable) resolved; UElement lastAssignment = UastLintUtils.findLastAssignment(variable, node, context); if (lastAssignment != null) { return evaluate(context, lastAssignment); } return variable.getType(); } else if (resolved instanceof UCallExpression) { if (UastExpressionUtils.isMethodCall(resolved)) { PsiMethod resolvedMethod = ((UCallExpression) resolved).resolve(); return resolvedMethod != null ? resolvedMethod.getReturnType() : null; } else { return ((UCallExpression) resolved).getExpressionType(); } } else if (resolved instanceof UExpression) { return ((UExpression) resolved).getExpressionType(); } return null; } /** * Evaluates the given node and returns the likely type of the instance. Convenience * wrapper which creates a new {@linkplain TypeEvaluator}, 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 type for * @return the corresponding type descriptor, if found * @deprecated Use {@link #evaluate(JavaContext, PsiElement)} instead */ @Deprecated @Nullable public static TypeDescriptor evaluate(@NonNull JavaContext context, @NonNull Node node) { return new TypeEvaluator(context).evaluate(node); } /** * Evaluates the given node and returns the likely type of the instance. Convenience * wrapper which creates a new {@linkplain TypeEvaluator}, 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 type for * @return the corresponding type descriptor, if found */ @Nullable public static PsiType evaluate(@NonNull JavaContext context, @NonNull PsiElement node) { return new TypeEvaluator(context).evaluate(node); } }