Lint: Android Lint diagnostics fixes

This commit is contained in:
Vyacheslav Gerasimov
2016-10-13 18:55:02 +03:00
committed by Yan Zhulanow
parent c2ddd943f9
commit 2be9a083ad
19 changed files with 963 additions and 735 deletions
@@ -0,0 +1,25 @@
/*
* Copyright (C) 2016 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.client.api;
import com.android.annotations.Nullable;
import com.intellij.psi.PsiElement;
public interface ExternalReferenceExpression {
@Nullable
PsiElement resolve(PsiElement context);
}
@@ -248,8 +248,7 @@ public abstract class JavaEvaluator {
public abstract PsiClassType getClassType(@Nullable PsiClass psiClass);
@NonNull
public abstract PsiAnnotation[] getAllAnnotations(@NonNull PsiModifierListOwner owner,
boolean inHierarchy);
public abstract PsiAnnotation[] getAllAnnotations(@NonNull PsiModifierListOwner owner);
@Nullable
public abstract PsiAnnotation findAnnotationInHierarchy(
@@ -504,6 +504,17 @@ public class UElementVisitor {
}
private class DispatchPsiVisitor extends AbstractUastVisitor {
@Override
public boolean visitAnnotation(UAnnotation node) {
List<VisitingDetector> list = mNodePsiTypeDetectors.get(UAnnotation.class);
if (list != null) {
for (VisitingDetector v : list) {
v.getVisitor().visitAnnotation(node);
}
}
return super.visitAnnotation(node);
}
@Override
public boolean visitCatchClause(UCatchClause node) {
@@ -34,15 +34,7 @@ import com.intellij.psi.PsiParameter;
import com.intellij.psi.PsiType;
import com.intellij.psi.PsiVariable;
import org.jetbrains.uast.UElement;
import org.jetbrains.uast.UExpression;
import org.jetbrains.uast.ULiteralExpression;
import org.jetbrains.uast.UMethod;
import org.jetbrains.uast.UQualifiedReferenceExpression;
import org.jetbrains.uast.UResolvable;
import org.jetbrains.uast.USimpleNameReferenceExpression;
import org.jetbrains.uast.UVariable;
import org.jetbrains.uast.UastUtils;
import org.jetbrains.uast.*;
import org.jetbrains.uast.expressions.UReferenceExpression;
import org.jetbrains.uast.java.JavaAbstractUExpression;
import org.jetbrains.uast.java.JavaUVariableDeclarationsExpression;
@@ -51,6 +43,16 @@ import java.util.Collections;
import java.util.List;
public class UastLintUtils {
@Nullable
public static PsiElement resolve(ExternalReferenceExpression expression, UElement context) {
UDeclaration declaration = UastUtils.getParentOfType(context, UDeclaration.class);
if (declaration == null) {
return null;
}
return expression.resolve(declaration.getPsi());
}
@NonNull
public static String getClassName(PsiClassType type) {
PsiClass psiClass = type.resolve();
@@ -88,10 +90,8 @@ public class UastLintUtils {
(variable instanceof PsiLocalVariable || variable instanceof PsiParameter)) {
UMethod containingFunction = UastUtils.getContainingUMethod(call);
if (containingFunction != null) {
ConstantEvaluator.LastAssignmentFinder
finder = new ConstantEvaluator.LastAssignmentFinder(
variable, call, context, null,
(variable instanceof PsiParameter) ? 1 : 0);
ConstantEvaluator.LastAssignmentFinder finder =
new ConstantEvaluator.LastAssignmentFinder(variable, call, context, null, -1);
containingFunction.accept(finder);
lastAssignment = finder.getLastAssignment();
}
@@ -63,21 +63,7 @@ import com.intellij.psi.PsiVariable;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.uast.UBinaryExpression;
import org.jetbrains.uast.UBinaryExpressionWithType;
import org.jetbrains.uast.UBlockExpression;
import org.jetbrains.uast.UCallExpression;
import org.jetbrains.uast.UElement;
import org.jetbrains.uast.UExpression;
import org.jetbrains.uast.UIfExpression;
import org.jetbrains.uast.ULiteralExpression;
import org.jetbrains.uast.UParenthesizedExpression;
import org.jetbrains.uast.UPrefixExpression;
import org.jetbrains.uast.UResolvable;
import org.jetbrains.uast.UVariable;
import org.jetbrains.uast.UastBinaryOperator;
import org.jetbrains.uast.UastContext;
import org.jetbrains.uast.UastPrefixOperator;
import org.jetbrains.uast.*;
import org.jetbrains.uast.expressions.UReferenceExpression;
import org.jetbrains.uast.util.UastExpressionUtils;
import org.jetbrains.uast.visitor.AbstractUastVisitor;
@@ -1073,7 +1059,6 @@ public class ConstantEvaluator {
private final PsiVariable mVariable;
private final UElement mEndAt;
private final JavaContext mContext;
private final ConstantEvaluator mConstantEvaluator;
private boolean mDone = false;
@@ -1092,7 +1077,6 @@ public class ConstantEvaluator {
mEndAt = endAt;
UExpression initializer = context.getUastContext().getInitializerBody(variable);
mLastAssignment = initializer;
mContext = context;
mConstantEvaluator = constantEvaluator;
if (initializer != null && constantEvaluator != null) {
mCurrentValue = constantEvaluator.evaluate(initializer);
@@ -1112,7 +1096,7 @@ public class ConstantEvaluator {
@Override
public boolean visitElement(UElement node) {
if (!(node instanceof UBlockExpression)) {
if (elementHasLevel(node)) {
mCurrentLevel++;
}
if (node.equals(mEndAt)) {
@@ -1123,23 +1107,23 @@ public class ConstantEvaluator {
@Override
public boolean visitVariable(UVariable node) {
if (mVariableLevel < 0 && node.equals(mVariable)) {
if (mVariableLevel < 0 && node.getPsi().isEquivalentTo(mVariable)) {
mVariableLevel = mCurrentLevel;
}
return super.visitVariable(node);
}
@Override
public void afterVisitBinaryExpression(UBinaryExpression node) {
if (!mDone
&& node.getOperator() instanceof UastBinaryOperator.AssignOperator
&& mVariableLevel >= 0) {
if (!mDone
&& node.getOperator() instanceof UastBinaryOperator.AssignOperator
&& mVariableLevel >= 0) {
UExpression leftOperand = node.getLeftOperand();
UastBinaryOperator operator = node.getOperator();
if (!(operator instanceof UastBinaryOperator.AssignOperator)
|| !(leftOperand instanceof UResolvable)) {
|| !(leftOperand instanceof UResolvable)) {
return;
}
@@ -1148,39 +1132,38 @@ public class ConstantEvaluator {
return;
}
// Stop search if we see an assignment inside some conditional or loop statement.
if (mCurrentLevel > mVariableLevel) {
// Last assignment is unknown if we see an assignment inside
// some conditional or loop statement.
if (mCurrentLevel > mVariableLevel + 1) {
mLastAssignment = null;
mCurrentValue = null;
mDone = true;
return;
}
UExpression rightOperand = node.getRightOperand();
ConstantEvaluator constantEvaluator = mConstantEvaluator;
Object newExpression = (constantEvaluator != null)
? constantEvaluator.evaluate(rightOperand)
: null;
//TODO implement other assign operators
if (node.getOperator() == UastBinaryOperator.ASSIGN) {
mCurrentValue = newExpression;
mLastAssignment = rightOperand;
} else {
mCurrentValue = newExpression;
// Technically wrong, just reflect the old behaviour for now
mLastAssignment = rightOperand;
}
mCurrentValue = (constantEvaluator != null)
? constantEvaluator.evaluate(rightOperand)
: null;
mLastAssignment = rightOperand;
}
super.afterVisitBinaryExpression(node);
}
@Override
public void afterVisitElement(UElement node) {
if (!(node instanceof UBlockExpression)) {
if (elementHasLevel(node)) {
mCurrentLevel--;
}
super.afterVisitElement(node);
}
private static boolean elementHasLevel(UElement node) {
return !(node instanceof UBlockExpression
|| node instanceof UVariableDeclarationsExpression);
}
}
/**
@@ -478,7 +478,12 @@ public class JavaContext extends Context {
}
public boolean isSuppressedWithComment(@NonNull UElement scope, @NonNull Issue issue) {
return false;
if (!(scope instanceof PsiElementBacked)) {
return false;
}
PsiElement psi = ((PsiElementBacked) scope).getPsi();
return psi != null && isSuppressedWithComment(psi, issue);
}
public boolean isSuppressedWithComment(@NonNull PsiElement scope, @NonNull Issue issue) {
@@ -589,7 +589,7 @@ public class ResourceEvaluator {
if (mEvaluator == null) {
return null;
}
for (PsiAnnotation annotation : mEvaluator.getAllAnnotations(owner, true)) {
for (PsiAnnotation annotation : mEvaluator.getAllAnnotations(owner)) {
String signature = annotation.getQualifiedName();
if (signature == null) {
continue;
@@ -53,11 +53,13 @@ import static com.android.tools.klint.detector.api.ResourceEvaluator.RES_SUFFIX;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.tools.klint.client.api.ExternalReferenceExpression;
import com.android.tools.klint.client.api.IssueRegistry;
import com.android.tools.klint.client.api.JavaEvaluator;
import com.android.tools.klint.client.api.UastLintUtils;
import com.android.tools.klint.detector.api.Category;
import com.android.tools.klint.detector.api.ConstantEvaluator;
import com.android.tools.klint.detector.api.Detector;
import com.android.tools.klint.detector.api.Detector.JavaPsiScanner;
import com.android.tools.klint.detector.api.Implementation;
import com.android.tools.klint.detector.api.Issue;
import com.android.tools.klint.detector.api.JavaContext;
@@ -70,42 +72,47 @@ import com.google.common.base.Splitter;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.intellij.psi.JavaElementVisitor;
import com.intellij.psi.PsiAnnotation;
import com.intellij.psi.PsiAnnotationMemberValue;
import com.intellij.psi.PsiAnnotationOwner;
import com.intellij.psi.PsiArrayInitializerMemberValue;
import com.intellij.psi.PsiArrayType;
import com.intellij.psi.PsiAssignmentExpression;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiClassType;
import com.intellij.psi.PsiCodeBlock;
import com.intellij.psi.PsiConditionalExpression;
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.PsiJavaCodeReferenceElement;
import com.intellij.psi.PsiLiteral;
import com.intellij.psi.PsiLocalVariable;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiMethodCallExpression;
import com.intellij.psi.PsiModifierList;
import com.intellij.psi.PsiModifierListOwner;
import com.intellij.psi.PsiNameValuePair;
import com.intellij.psi.PsiParameter;
import com.intellij.psi.PsiParenthesizedExpression;
import com.intellij.psi.PsiReference;
import com.intellij.psi.PsiReferenceExpression;
import com.intellij.psi.PsiStatement;
import com.intellij.psi.PsiSwitchLabelStatement;
import com.intellij.psi.PsiSwitchStatement;
import com.intellij.psi.PsiType;
import com.intellij.psi.PsiTypeCastExpression;
import com.intellij.psi.PsiVariable;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.uast.UAnnotation;
import org.jetbrains.uast.UCallExpression;
import org.jetbrains.uast.UClass;
import org.jetbrains.uast.UElement;
import org.jetbrains.uast.UExpression;
import org.jetbrains.uast.UIfExpression;
import org.jetbrains.uast.ULiteralExpression;
import org.jetbrains.uast.UParenthesizedExpression;
import org.jetbrains.uast.USwitchClauseExpression;
import org.jetbrains.uast.USwitchExpression;
import org.jetbrains.uast.UastUtils;
import org.jetbrains.uast.expressions.UReferenceExpression;
import org.jetbrains.uast.java.JavaUTypeCastExpression;
import org.jetbrains.uast.visitor.AbstractUastVisitor;
import org.jetbrains.uast.visitor.UastVisitor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -117,11 +124,11 @@ import java.util.Set;
/**
* Checks annotations to make sure they are valid
*/
public class AnnotationDetector extends Detector implements JavaPsiScanner {
public class AnnotationDetector extends Detector implements Detector.UastScanner {
public static final Implementation IMPLEMENTATION = new Implementation(
AnnotationDetector.class,
Scope.JAVA_FILE_SCOPE);
AnnotationDetector.class,
Scope.JAVA_FILE_SCOPE);
/** Placing SuppressLint on a local variable doesn't work for class-file based checks */
public static final Issue INSIDE_METHOD = Issue.create(
@@ -217,38 +224,41 @@ public class AnnotationDetector extends Detector implements JavaPsiScanner {
*/
private Set<PsiElement> mWarnedFlags;
@Nullable
@Override
public List<Class<? extends PsiElement>> getApplicablePsiTypes() {
List<Class<? extends PsiElement>> types = new ArrayList<Class<? extends PsiElement>>(2);
types.add(PsiAnnotation.class);
types.add(PsiSwitchStatement.class);
public List<Class<? extends UElement>> getApplicableUastTypes() {
List<Class<? extends UElement>> types = new ArrayList<Class<? extends UElement>>(2);
types.add(UAnnotation.class);
types.add(USwitchExpression.class);
return types;
}
@Nullable
@Override
public JavaElementVisitor createPsiVisitor(@NonNull JavaContext context) {
public UastVisitor createUastVisitor(@NonNull JavaContext context) {
return new AnnotationChecker(context);
}
private class AnnotationChecker extends JavaElementVisitor {
private class AnnotationChecker extends AbstractUastVisitor {
private final JavaContext mContext;
public AnnotationChecker(JavaContext context) {
private AnnotationChecker(JavaContext context) {
mContext = context;
}
@Override
public void visitAnnotation(PsiAnnotation annotation) {
public boolean visitAnnotation(UAnnotation annotation) {
String type = annotation.getQualifiedName();
if (type == null || type.startsWith("java.lang.")) {
return;
return false;
}
if (FQCN_SUPPRESS_LINT.equals(type)) {
PsiAnnotationOwner owner = annotation.getOwner();
if (owner == null) {
return;
return false;
}
if (owner instanceof PsiModifierList) {
PsiElement parent = ((PsiModifierList) owner).getParent();
@@ -256,10 +266,10 @@ public class AnnotationDetector extends Detector implements JavaPsiScanner {
if (!(parent instanceof PsiDeclarationStatement
|| parent instanceof PsiLocalVariable
|| parent instanceof PsiParameter)) {
return;
return false;
}
} else {
return;
return false;
}
PsiNameValuePair[] attributes = annotation.getParameterList().getAttributes();
if (attributes.length == 1) {
@@ -280,7 +290,7 @@ public class AnnotationDetector extends Detector implements JavaPsiScanner {
if (v instanceof String) {
String id = (String) v;
if (!checkSuppressLint(annotation, id)) {
return;
return false;
}
}
}
@@ -291,17 +301,17 @@ public class AnnotationDetector extends Detector implements JavaPsiScanner {
if (CHECK_RESULT_ANNOTATION.equals(type)) {
// Check that the return type of this method is not void!
if (annotation.getParent() instanceof PsiModifierList
&& annotation.getParent().getParent() instanceof PsiMethod) {
&& annotation.getParent().getParent() instanceof PsiMethod) {
PsiMethod method = (PsiMethod) annotation.getParent().getParent();
if (!method.isConstructor()
&& PsiType.VOID.equals(method.getReturnType())) {
mContext.report(ANNOTATION_USAGE, annotation,
mContext.getLocation(annotation),
"@CheckResult should not be specified on `void` methods");
&& PsiType.VOID.equals(method.getReturnType())) {
mContext.report(ANNOTATION_USAGE, annotation.getPsi(),
mContext.getLocation(annotation.getPsi()),
"@CheckResult should not be specified on `void` methods");
}
}
} else if (INT_RANGE_ANNOTATION.equals(type)
|| FLOAT_RANGE_ANNOTATION.equals(type)) {
|| FLOAT_RANGE_ANNOTATION.equals(type)) {
// Check that the annotated element's type is int or long.
// Also make sure that from <= to.
boolean invalid;
@@ -315,14 +325,14 @@ public class AnnotationDetector extends Detector implements JavaPsiScanner {
checkTargetType(annotation, TYPE_FLOAT, TYPE_DOUBLE, true);
double from = getDoubleAttribute(annotation, ATTR_FROM,
Double.NEGATIVE_INFINITY);
Double.NEGATIVE_INFINITY);
double to = getDoubleAttribute(annotation, ATTR_TO,
Double.POSITIVE_INFINITY);
Double.POSITIVE_INFINITY);
invalid = from > to;
}
if (invalid) {
mContext.report(ANNOTATION_USAGE, annotation, mContext.getLocation(annotation),
"Invalid range: the `from` attribute must be less than "
mContext.report(ANNOTATION_USAGE, annotation.getPsi(), mContext.getLocation(annotation.getPsi()),
"Invalid range: the `from` attribute must be less than "
+ "the `to` attribute");
}
} else if (SIZE_ANNOTATION.equals(type)) {
@@ -335,16 +345,16 @@ public class AnnotationDetector extends Detector implements JavaPsiScanner {
long max = getLongAttribute(annotation, ATTR_MAX, Long.MAX_VALUE);
long multiple = getLongAttribute(annotation, ATTR_MULTIPLE, 1);
if (min > max) {
mContext.report(ANNOTATION_USAGE, annotation, mContext.getLocation(annotation),
"Invalid size range: the `min` attribute must be less than "
mContext.report(ANNOTATION_USAGE, annotation.getPsi(), mContext.getLocation(annotation.getPsi()),
"Invalid size range: the `min` attribute must be less than "
+ "the `max` attribute");
} else if (multiple < 1) {
mContext.report(ANNOTATION_USAGE, annotation, mContext.getLocation(annotation),
"The size multiple must be at least 1");
mContext.report(ANNOTATION_USAGE, annotation.getPsi(), mContext.getLocation(annotation.getPsi()),
"The size multiple must be at least 1");
} else if (exact < 0 && exact != unset || min < 0 && min != Long.MIN_VALUE) {
mContext.report(ANNOTATION_USAGE, annotation, mContext.getLocation(annotation),
"The size can't be negative");
mContext.report(ANNOTATION_USAGE, annotation.getPsi(), mContext.getLocation(annotation.getPsi()),
"The size can't be negative");
}
} else if (COLOR_INT_ANNOTATION.equals(type) || (PX_ANNOTATION.equals(type))) {
// Check that ColorInt applies to the right type
@@ -353,8 +363,8 @@ public class AnnotationDetector extends Detector implements JavaPsiScanner {
// Make sure IntDef constants are unique
ensureUniqueValues(annotation);
} else if (PERMISSION_ANNOTATION.equals(type) ||
PERMISSION_ANNOTATION_READ.equals(type) ||
PERMISSION_ANNOTATION_WRITE.equals(type)) {
PERMISSION_ANNOTATION_READ.equals(type) ||
PERMISSION_ANNOTATION_WRITE.equals(type)) {
// Check that if there are no arguments, this is specified on a parameter,
// and conversely, on methods and fields there is a valid argument.
if (annotation.getParent() instanceof PsiModifierList
@@ -378,14 +388,14 @@ public class AnnotationDetector extends Detector implements JavaPsiScanner {
}
if (set == 0) {
mContext.report(ANNOTATION_USAGE, annotation,
mContext.getLocation(annotation),
"For methods, permission annotation should specify one "
mContext.report(ANNOTATION_USAGE, annotation.getPsi(),
mContext.getLocation(annotation.getPsi()),
"For methods, permission annotation should specify one "
+ "of `value`, `anyOf` or `allOf`");
} else if (set > 1) {
mContext.report(ANNOTATION_USAGE, annotation,
mContext.getLocation(annotation),
"Only specify one of `value`, `anyOf` or `allOf`");
mContext.report(ANNOTATION_USAGE, annotation.getPsi(),
mContext.getLocation(annotation.getPsi()),
"Only specify one of `value`, `anyOf` or `allOf`");
}
}
@@ -414,6 +424,8 @@ public class AnnotationDetector extends Detector implements JavaPsiScanner {
}
}
}
return false;
}
private void checkTargetType(@NonNull PsiAnnotation node, @NonNull String type1,
@@ -437,8 +449,8 @@ public class AnnotationDetector extends Detector implements JavaPsiScanner {
} else if (parent instanceof PsiMethod) {
PsiMethod method = (PsiMethod) parent;
type = method.isConstructor()
? mContext.getEvaluator().getClassType(method.getContainingClass())
: method.getReturnType();
? mContext.getEvaluator().getClassType(method.getContainingClass())
: method.getReturnType();
} else if (parent instanceof PsiVariable) {
// Field or local variable or parameter
type = ((PsiVariable)parent).getType();
@@ -459,8 +471,7 @@ public class AnnotationDetector extends Detector implements JavaPsiScanner {
if (classType.getParameters().length == 1) {
PsiClass resolved = classType.resolve();
if (resolved != null &&
mContext.getEvaluator().implementsInterface(resolved,
"java.util.Collection", false)) {
InheritanceUtil.isInheritor(resolved, false, "java.util.Collection")) {
type = classType.getParameters()[0];
}
}
@@ -469,10 +480,10 @@ public class AnnotationDetector extends Detector implements JavaPsiScanner {
String typeName = type.getCanonicalText();
if (!typeName.equals(type1)
&& (type2 == null || !typeName.equals(type2))) {
&& (type2 == null || !typeName.equals(type2))) {
// Autoboxing? You can put @DrawableRes on a java.lang.Integer for example
if (typeName.equals(getAutoBoxedType(type1))
|| type2 != null && typeName.equals(getAutoBoxedType(type2))) {
|| type2 != null && typeName.equals(getAutoBoxedType(type2))) {
return;
}
@@ -490,14 +501,35 @@ public class AnnotationDetector extends Detector implements JavaPsiScanner {
}
@Override
public void visitSwitchStatement(PsiSwitchStatement statement) {
PsiExpression condition = statement.getExpression();
if (condition != null && PsiType.INT.equals(condition.getType())) {
PsiAnnotation annotation = findIntDef(condition);
public boolean visitSwitchExpression(USwitchExpression switchExpression) {
UExpression condition = switchExpression.getExpression();
if (condition != null && PsiType.INT.equals(condition.getExpressionType())) {
PsiAnnotation annotation = findIntDefAnnotation(condition);
if (annotation != null) {
checkSwitch(statement, annotation);
PsiAnnotationMemberValue value =
annotation.findDeclaredAttributeValue(ATTR_VALUE);
if (value == null) {
value = annotation.findDeclaredAttributeValue(null);
}
if (value instanceof PsiArrayInitializerMemberValue) {
PsiAnnotationMemberValue[] allowedValues =
((PsiArrayInitializerMemberValue)value).getInitializers();
switchExpression.accept(new SwitchChecker(switchExpression, allowedValues));
}
}
}
return false;
}
@Nullable
private Integer getConstantValue(@NonNull PsiField intDefConstantRef) {
Object constant = intDefConstantRef.computeConstantValue();
if (constant instanceof Number) {
return ((Number)constant).intValue();
}
return null;
}
/**
@@ -505,12 +537,13 @@ public class AnnotationDetector extends Detector implements JavaPsiScanner {
* with a given node
*/
@Nullable
private PsiAnnotation findIntDef(@NonNull PsiElement node) {
if (node instanceof PsiReferenceExpression) {
PsiElement resolved = ((PsiReference) node).resolve();
private PsiAnnotation findIntDefAnnotation(@NonNull UExpression expression) {
if (expression instanceof UReferenceExpression) {
PsiElement resolved = ((UReferenceExpression) expression).resolve();
if (resolved instanceof PsiModifierListOwner) {
PsiAnnotation[] annotations = mContext.getEvaluator().getAllAnnotations(
(PsiModifierListOwner)resolved, true);
(PsiModifierListOwner)resolved);
PsiAnnotation annotation = SupportAnnotationDetector.findIntDef(
filterRelevantAnnotations(annotations));
if (annotation != null) {
@@ -520,217 +553,50 @@ public class AnnotationDetector extends Detector implements JavaPsiScanner {
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)) {
PsiExpression initializer = variable.getInitializer();
if (initializer != null) {
return findIntDef(initializer);
}
break;
}
}
} 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) {
PsiExpression rExpression = assign.getRExpression();
if (rExpression != null) {
return findIntDef(rExpression);
}
break;
}
}
}
}
prev = PsiTreeUtil.getPrevSiblingOfType(prev,
PsiStatement.class);
}
UExpression lastAssignment = UastLintUtils.findLastAssignment(variable,
expression, mContext);
if(lastAssignment != null) {
return findIntDefAnnotation(lastAssignment);
}
}
} else if (node instanceof PsiMethodCallExpression) {
PsiMethod method = ((PsiMethodCallExpression) node).resolveMethod();
} else if (expression instanceof UCallExpression) {
PsiMethod method = ((UCallExpression) expression).resolve();
if (method != null) {
PsiAnnotation[] annotations = mContext.getEvaluator().getAllAnnotations(method, true);
PsiAnnotation[] annotations = mContext.getEvaluator()
.getAllAnnotations(method);
PsiAnnotation annotation = SupportAnnotationDetector.findIntDef(
filterRelevantAnnotations(annotations));
if (annotation != null) {
return annotation;
}
}
} else if (node instanceof PsiConditionalExpression) {
PsiConditionalExpression expression = (PsiConditionalExpression) node;
if (expression.getThenExpression() != null) {
PsiAnnotation result = findIntDef(expression.getThenExpression());
} else if (expression instanceof UIfExpression) {
UIfExpression ifExpression = (UIfExpression) expression;
if (ifExpression.getThenExpression() != null) {
PsiAnnotation result = findIntDefAnnotation(ifExpression.getThenExpression());
if (result != null) {
return result;
}
}
if (expression.getElseExpression() != null) {
PsiAnnotation result = findIntDef(expression.getElseExpression());
if (ifExpression.getElseExpression() != null) {
PsiAnnotation result = findIntDefAnnotation(ifExpression.getElseExpression());
if (result != null) {
return result;
}
}
} else if (node instanceof PsiTypeCastExpression) {
PsiTypeCastExpression cast = (PsiTypeCastExpression) node;
if (cast.getOperand() != null) {
return findIntDef(cast.getOperand());
}
} else if (node instanceof PsiParenthesizedExpression) {
PsiParenthesizedExpression expression = (PsiParenthesizedExpression) node;
if (expression.getExpression() != null) {
return findIntDef(expression.getExpression());
}
} else if (expression instanceof JavaUTypeCastExpression) {
return findIntDefAnnotation(((JavaUTypeCastExpression)expression).getOperand());
} else if (expression instanceof UParenthesizedExpression) {
return findIntDefAnnotation(((UParenthesizedExpression) expression).getExpression());
}
return null;
}
private void checkSwitch(@NonNull PsiSwitchStatement node, @NonNull PsiAnnotation annotation) {
PsiCodeBlock block = node.getBody();
if (block == null) {
return;
}
PsiAnnotationMemberValue value = annotation.findDeclaredAttributeValue(ATTR_VALUE);
if (value == null) {
value = annotation.findDeclaredAttributeValue(null);
}
if (value == null) {
return;
}
if (!(value instanceof PsiArrayInitializerMemberValue)) {
return;
}
PsiArrayInitializerMemberValue array = (PsiArrayInitializerMemberValue)value;
PsiAnnotationMemberValue[] allowedValues = array.getInitializers();
List<PsiElement> fields = Lists.newArrayListWithCapacity(allowedValues.length);
for (PsiAnnotationMemberValue allowedValue : allowedValues) {
if (allowedValue instanceof PsiReferenceExpression) {
PsiElement resolved = ((PsiReferenceExpression) allowedValue).resolve();
if (resolved != null) {
fields.add(resolved);
}
} else if (allowedValue instanceof PsiLiteral) {
fields.add(allowedValue);
}
}
// Empty switch: arguably we could skip these (since the IDE already warns about
// empty switches) but it's useful since the quickfix will kick in and offer all
// the missing ones when you're editing.
// if (block.getStatements().length == 0) { return; }
for (PsiStatement statement : block.getStatements()) {
if (statement instanceof PsiSwitchLabelStatement) {
PsiSwitchLabelStatement caseStatement = (PsiSwitchLabelStatement) statement;
PsiExpression expression = caseStatement.getCaseValue();
if (expression instanceof PsiLiteral) {
// Report warnings if you specify hardcoded constants.
// It's the wrong thing to do.
List<String> list = computeFieldNames(node, Arrays.asList(allowedValues));
// Keep error message in sync with {@link #getMissingCases}
String message = "Don't use a constant here; expected one of: " + Joiner
.on(", ").join(list);
mContext.report(SWITCH_TYPE_DEF, expression,
mContext.getLocation(expression), message);
return; // Don't look for other missing typedef constants since you might
// have aliased with value
} else if (expression instanceof PsiReferenceExpression) { // default case can have null expression
PsiElement resolved = ((PsiReferenceExpression) expression).resolve();
if (resolved == null) {
// If there are compilation issues (e.g. user is editing code) we
// can't be certain, so don't flag anything.
return;
}
if (resolved instanceof PsiField) {
// We can't just do
// fields.remove(resolved);
// since the fields list contains instances of potentially
// different types with different hash codes (due to the
// external annotations, which are not of the same type as
// for example the ECJ based ones.
//
// The equals method on external field class deliberately handles
// this (but it can't make its hash code match what
// the ECJ fields do, which is tied to the ECJ binding hash code.)
// So instead, manually check for equals. These lists tend to
// be very short anyway.
boolean found = false;
ListIterator<PsiElement> iterator = fields.listIterator();
while (iterator.hasNext()) {
PsiElement field = iterator.next();
if (field.isEquivalentTo(resolved)) {
iterator.remove();
found = true;
break;
}
}
if (!found) {
// Look for local alias
PsiExpression initializer = ((PsiField) resolved).getInitializer();
if (initializer instanceof PsiReferenceExpression) {
resolved = ((PsiReferenceExpression) expression).resolve();
if (resolved instanceof PsiField) {
iterator = fields.listIterator();
while (iterator.hasNext()) {
PsiElement field = iterator.next();
if (field.equals(initializer)) {
iterator.remove();
found = true;
break;
}
}
}
}
}
if (!found) {
List<String> list = computeFieldNames(node, Arrays.asList(allowedValues));
// Keep error message in sync with {@link #getMissingCases}
String message = "Unexpected constant; expected one of: " + Joiner
.on(", ").join(list);
Location location = mContext.getNameLocation(expression);
mContext.report(SWITCH_TYPE_DEF, expression, location, message);
}
}
}
}
}
if (!fields.isEmpty()) {
List<String> list = computeFieldNames(node, fields);
// Keep error message in sync with {@link #getMissingCases}
String message = "Switch statement on an `int` with known associated constant "
+ "missing case " + Joiner.on(", ").join(list);
Location location = mContext.getNameLocation(node);
mContext.report(SWITCH_TYPE_DEF, node, location, message);
}
}
private void ensureUniqueValues(@NonNull PsiAnnotation node) {
PsiAnnotationMemberValue value = node.findAttributeValue(ATTR_VALUE);
if (value == null) {
@@ -770,8 +636,8 @@ public class AnnotationDetector extends Detector implements JavaPsiScanner {
PsiElement prevConstant = initializers[prevIndex];
message = String.format(
"Constants `%1$s` and `%2$s` specify the same exact "
+ "value (%3$s); this is usually a cut & paste or "
+ "merge error",
+ "value (%3$s); this is usually a cut & paste or "
+ "merge error",
expression.getText(), prevConstant.getText(),
repeatedValue.toString());
location = mContext.getLocation(expression);
@@ -838,27 +704,212 @@ public class AnnotationDetector extends Detector implements JavaPsiScanner {
// only on field references, and class file analysis on the rest, so we allow
// annotations outside of methods only on fields
if (issue != null && !issue.getImplementation().getScope().contains(Scope.JAVA_FILE)
|| issue == ApiDetector.UNSUPPORTED) {
|| issue == ApiDetector.UNSUPPORTED) {
// This issue doesn't have AST access: annotations are not
// available for local variables or parameters
PsiElement scope = getAnnotationScope(node);
mContext.report(INSIDE_METHOD, scope, mContext.getLocation(node), String.format(
"The `@SuppressLint` annotation cannot be used on a local " +
"variable with the lint check '%1$s': move out to the " +
"surrounding method", id));
"The `@SuppressLint` annotation cannot be used on a local " +
"variable with the lint check '%1$s': move out to the " +
"surrounding method", id));
return false;
}
return true;
}
private class SwitchChecker extends AbstractUastVisitor {
private final USwitchExpression mSwitchExpression;
private final PsiAnnotationMemberValue[] mAllowedValues;
private final List<PsiElement> mFields;
private final List<Integer> mSeenValues;
private boolean mReported = false;
private SwitchChecker(USwitchExpression switchExpression,
PsiAnnotationMemberValue[] allowedValues) {
mSwitchExpression = switchExpression;
mAllowedValues = allowedValues;
mFields = Lists.newArrayListWithCapacity(allowedValues.length);
for (PsiAnnotationMemberValue allowedValue : allowedValues) {
if (allowedValue instanceof ExternalReferenceExpression) {
ExternalReferenceExpression externalRef =
(ExternalReferenceExpression) allowedValue;
PsiElement resolved = UastLintUtils.resolve(externalRef, switchExpression);
if (resolved instanceof PsiField) {
mFields.add(resolved);
}
} else if (allowedValue instanceof PsiReferenceExpression) {
PsiElement resolved = ((PsiReferenceExpression) allowedValue).resolve();
if (resolved != null) {
mFields.add(resolved);
}
} else if (allowedValue instanceof PsiLiteral) {
mFields.add(allowedValue);
}
}
mSeenValues = Lists.newArrayListWithCapacity(allowedValues.length);
}
@Override
public boolean visitSwitchClauseExpression(USwitchClauseExpression node) {
if (mReported) {
return true;
}
if (mAllowedValues == null) {
return true;
}
List<UExpression> caseValues = node.getCaseValues();
if (caseValues == null) {
return true;
}
for (UExpression caseValue : caseValues) {
if (caseValue instanceof ULiteralExpression) {
// Report warnings if you specify hardcoded constants.
// It's the wrong thing to do.
List<String> list = computeFieldNames(mSwitchExpression,
Arrays.asList(mAllowedValues));
// Keep error message in sync with {@link #getMissingCases}
String message = "Don't use a constant here; expected one of: " + Joiner
.on(", ").join(list);
mContext.report(SWITCH_TYPE_DEF, caseValue,
mContext.getUastLocation(caseValue), message);
// Don't look for other missing typedef constants since you might
// have aliased with value
mReported = true;
} else if (caseValue instanceof UReferenceExpression) { // default case can have null expression
PsiElement resolved = ((UReferenceExpression) caseValue).resolve();
if (resolved == null) {
// If there are compilation issues (e.g. user is editing code) we
// can't be certain, so don't flag anything.
return true;
}
if (resolved instanceof PsiField) {
// We can't just do
// fields.remove(resolved);
// since the fields list contains instances of potentially
// different types with different hash codes (due to the
// external annotations, which are not of the same type as
// for example the ECJ based ones.
//
// The equals method on external field class deliberately handles
// this (but it can't make its hash code match what
// the ECJ fields do, which is tied to the ECJ binding hash code.)
// So instead, manually check for equals. These lists tend to
// be very short anyway.
boolean found = false;
ListIterator<PsiElement> iterator = mFields.listIterator();
while (iterator.hasNext()) {
PsiElement field = iterator.next();
if (field.equals(resolved)) {
iterator.remove();
found = true;
break;
}
}
if (!found) {
// Look for local alias
UExpression initializer = mContext.getUastContext()
.getInitializerBody(((PsiField) resolved));
if (initializer instanceof UReferenceExpression) {
resolved = ((UReferenceExpression) initializer).resolve();
if (resolved instanceof PsiField) {
iterator = mFields.listIterator();
while (iterator.hasNext()) {
PsiElement field = iterator.next();
if (field.equals(resolved)) {
iterator.remove();
found = true;
break;
}
}
}
}
}
if (found) {
Integer cv = getConstantValue((PsiField) resolved);
if (cv != null) {
mSeenValues.add(cv);
}
} else {
List<String> list = computeFieldNames(mSwitchExpression,
Arrays.asList(mAllowedValues));
// Keep error message in sync with {@link #getMissingCases}
String message = "Unexpected constant; expected one of: " + Joiner
.on(", ").join(list);
Location location = mContext.getUastNameLocation(caseValue);
mContext.report(SWITCH_TYPE_DEF, caseValue, location, message);
}
}
}
}
return true;
}
@Override
public void afterVisitSwitchExpression(USwitchExpression node) {
reportMissingSwitchCases();
super.afterVisitSwitchExpression(node);
}
private void reportMissingSwitchCases() {
if (mReported) {
return;
}
if (mAllowedValues == null) {
return;
}
// Any missing switch constants? Before we flag them, look to see if any
// of them have the same values: those can be omitted
if (!mFields.isEmpty()) {
ListIterator<PsiElement> iterator = mFields.listIterator();
while (iterator.hasNext()) {
PsiElement next = iterator.next();
if (next instanceof PsiField) {
Integer cv = getConstantValue((PsiField)next);
if (mSeenValues.contains(cv)) {
iterator.remove();
}
}
}
}
if (!mFields.isEmpty()) {
List<String> list = computeFieldNames(mSwitchExpression, mFields);
// Keep error message in sync with {@link #getMissingCases}
String message = "Switch statement on an `int` with known associated constant "
+ "missing case " + Joiner.on(", ").join(list);
Location location = mContext.getUastLocation(mSwitchExpression.getSwitchIdentifier());
mContext.report(SWITCH_TYPE_DEF, mSwitchExpression, location, message);
}
}
}
}
@NonNull
private static List<String> computeFieldNames(@NonNull PsiSwitchStatement node,
Iterable<?> allowedValues) {
private static List<String> computeFieldNames(
@NonNull USwitchExpression node, Iterable<?> allowedValues) {
List<String> list = Lists.newArrayList();
for (Object o : allowedValues) {
if (o instanceof PsiReferenceExpression) {
if (o instanceof ExternalReferenceExpression) {
ExternalReferenceExpression externalRef = (ExternalReferenceExpression) o;
PsiElement resolved = UastLintUtils.resolve(externalRef, node);
if (resolved != null) {
o = resolved;
}
} else if (o instanceof PsiReferenceExpression) {
PsiElement resolved = ((PsiReferenceExpression) o).resolve();
if (resolved != null) {
o = resolved;
@@ -872,17 +923,11 @@ public class AnnotationDetector extends Detector implements JavaPsiScanner {
PsiField field = (PsiField) o;
// Only include class name if necessary
String name = field.getName();
PsiClass clz = PsiTreeUtil.getParentOfType(node, PsiClass.class, true);
UClass clz = UastUtils.getParentOfType(node, UClass.class, true);
if (clz != null) {
PsiClass containingClass = field.getContainingClass();
if (containingClass != null && !containingClass.equals(clz)) {
//if (Objects.equal(containingClass.getPackage(),
// ((ResolvedClass) resolved).getPackage())) {
// name = containingClass.getSimpleName() + '.' + field.getName();
//} else {
name = containingClass.getName() + '.' + field.getName();
//}
if (containingClass != null && !containingClass.equals(clz.getPsi())) {
name = containingClass.getName() + '.' + field.getName();
}
}
list.add('`' + name + '`');
@@ -58,22 +58,9 @@ import com.android.sdklib.SdkVersionInfo;
import com.android.tools.klint.client.api.JavaEvaluator;
import com.android.tools.klint.client.api.LintDriver;
import com.android.tools.klint.client.api.UastLintUtils;
import com.android.tools.klint.detector.api.Category;
import com.android.tools.klint.detector.api.ClassContext;
import com.android.tools.klint.detector.api.Context;
import com.android.tools.klint.detector.api.Detector;
import com.android.tools.klint.detector.api.*;
import com.android.tools.klint.detector.api.Detector.ClassScanner;
import com.android.tools.klint.detector.api.Implementation;
import com.android.tools.klint.detector.api.Issue;
import com.android.tools.klint.detector.api.JavaContext;
import com.android.tools.klint.detector.api.LintUtils;
import com.android.tools.klint.detector.api.Location;
import com.android.tools.klint.detector.api.Location.SearchHints;
import com.android.tools.klint.detector.api.ResourceXmlDetector;
import com.android.tools.klint.detector.api.Scope;
import com.android.tools.klint.detector.api.Severity;
import com.android.tools.klint.detector.api.TextFormat;
import com.android.tools.klint.detector.api.XmlContext;
import com.google.common.collect.Lists;
import com.intellij.psi.PsiAnnotation;
import com.intellij.psi.PsiAnnotationMemberValue;
@@ -148,6 +135,7 @@ import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -310,6 +298,8 @@ public class ApiDetector extends ResourceXmlDetector
private static final String SDK_INT = "SDK_INT";
private static final String ANDROID_OS_BUILD_VERSION = "android/os/Build$VERSION";
private static final String REFLECTIVE_OPERATION_EXCEPTION
= "java.lang.ReflectiveOperationException";
protected ApiLookup mApiDatabase;
private boolean mWarnedMissingDb;
@@ -864,33 +854,6 @@ public class ApiDetector extends ResourceXmlDetector
continue;
}
List tryCatchBlocks = method.tryCatchBlocks;
// single-catch blocks are already handled by an AST level check in ApiVisitor
if (tryCatchBlocks.size() > 1) {
List<String> checked = Lists.newArrayList();
for (Object o : tryCatchBlocks) {
TryCatchBlockNode tryCatchBlock = (TryCatchBlockNode) o;
String className = tryCatchBlock.type;
if (className == null || checked.contains(className)) {
continue;
}
int api = mApiDatabase.getClassVersion(className);
if (api > minSdk) {
// Find instruction node
LabelNode label = tryCatchBlock.handler;
String fqcn = getFqcn(className);
String message = String.format(
"Class requires API level %1$d (current min is %2$d): `%3$s`",
api, minSdk, fqcn);
report(context, message, label, method,
className.substring(className.lastIndexOf('/') + 1), null,
SearchHints.create(EOL_NEAREST).matchJavaSymbol());
}
}
}
if (CHECK_DECLARATIONS) {
// Check types in parameter list and types of local variables
List localVariables = method.localVariables;
@@ -1493,7 +1456,7 @@ public class ApiDetector extends ResourceXmlDetector
return -1;
}
private static void report(final ClassContext context, String message, AbstractInsnNode node,
MethodNode method, String patternStart, String patternEnd, SearchHints hints) {
int lineNumber = node != null ? ClassContext.findLineNumber(node) : -1;
@@ -1647,7 +1610,7 @@ public class ApiDetector extends ResourceXmlDetector
checkField(statement, (PsiField)resolved);
}
}
return super.visitImportStatement(statement);
}
@@ -1657,7 +1620,7 @@ public class ApiDetector extends ResourceXmlDetector
if (resolved instanceof PsiField) {
checkField(node, (PsiField)resolved);
}
return super.visitSimpleNameReferenceExpression(node);
}
@@ -1666,16 +1629,16 @@ public class ApiDetector extends ResourceXmlDetector
if (UastExpressionUtils.isTypeCast(node)) {
visitTypeCastExpression(node);
}
return super.visitBinaryExpressionWithType(node);
}
private void visitTypeCastExpression(UBinaryExpressionWithType expression) {
UExpression operand = expression.getOperand();
PsiType operandType = operand.getExpressionType();
PsiType castType = expression.getType();
if (castType.equals(operandType)) {
return;
}
@@ -1690,7 +1653,7 @@ public class ApiDetector extends ResourceXmlDetector
checkCast(expression, classType, interfaceType);
}
private void checkCast(@NonNull UElement node, @NonNull PsiClassType classType,
private void checkCast(@NonNull UElement node, @NonNull PsiClassType classType,
@NonNull PsiClassType interfaceType) {
if (classType.equals(interfaceType)) {
return;
@@ -1737,7 +1700,7 @@ public class ApiDetector extends ResourceXmlDetector
mContext.reportUast(UNSUPPORTED, method, location, message);
}
}
return super.visitMethod(method);
}
@@ -1777,7 +1740,7 @@ public class ApiDetector extends ResourceXmlDetector
}
}
}
return super.visitClass(aClass);
}
@@ -1840,7 +1803,7 @@ public class ApiDetector extends ResourceXmlDetector
}
}
}
return super.visitCallExpression(expression);
}
@@ -1908,7 +1871,7 @@ public class ApiDetector extends ResourceXmlDetector
if (UastExpressionUtils.isAssignment(node)) {
visitAssignmentExpression(node);
}
return super.visitBinaryExpression(node);
}
@@ -1951,14 +1914,75 @@ public class ApiDetector extends ResourceXmlDetector
}
for (UCatchClause catchClause : statement.getCatchClauses()) {
// Special case reflective operation exception which can be implicitly used
// with multi-catches: see issue 153406
int minSdk = getMinSdk(mContext);
if(minSdk < 19 && isMultiCatchReflectiveOperationException(catchClause)) {
String message = String.format("Multi-catch with these reflection exceptions requires API level 19 (current min is %d) " +
"because they get compiled to the common but new super type `ReflectiveOperationException`. " +
"As a workaround either create individual catch statements, or catch `Exception`.",
minSdk);
mContext.report(UNSUPPORTED, getCatchParametersLocation(mContext, catchClause), message);
continue;
}
for (UTypeReferenceExpression typeReference : catchClause.getTypeReferences()) {
checkCatchTypeElement(statement, typeReference, typeReference.getType());
}
}
return super.visitTryExpression(statement);
}
private Location getCatchParametersLocation(JavaContext context, UCatchClause catchClause) {
List<UTypeReferenceExpression> types = catchClause.getTypeReferences();
if (types.isEmpty()) {
return Location.NONE;
}
Location first = context.getUastLocation(types.get(0));
if (types.size() < 2) {
return first;
}
Location last = context.getUastLocation(types.get(types.size() - 1));
File file = first.getFile();
Position start = first.getStart();
Position end = last.getEnd();
if (start == null) {
return Location.create(file);
}
return Location.create(file, start, end);
}
private boolean isMultiCatchReflectiveOperationException(UCatchClause catchClause) {
List<PsiType> types = catchClause.getTypes();
if (types.size() < 2) {
return false;
}
for (PsiType t : types) {
if(!isSubclassOfReflectiveOperationException(t)) {
return false;
}
}
return true;
}
private boolean isSubclassOfReflectiveOperationException(PsiType type) {
for (PsiType t : type.getSuperTypes()) {
if (REFLECTIVE_OPERATION_EXCEPTION.equals(t.getCanonicalText())) {
return true;
}
}
return false;
}
private void checkCatchTypeElement(@NonNull UTryExpression statement,
@NonNull UTypeReferenceExpression typeReference,
@Nullable PsiType type) {
@@ -1981,19 +2005,10 @@ public class ApiDetector extends ResourceXmlDetector
return;
}
Location location;
location = mContext.getUastLocation(typeReference);
Location location = mContext.getUastLocation(typeReference);
String fqcn = resolved.getQualifiedName();
String message = String.format("Class requires API level %1$d (current min is %2$d): %3$s", api, minSdk, fqcn);
// Special case reflective operation exception which can be implicitly used
// with multi-catches: see issue 153406
if (api == 19 && "ReflectiveOperationException".equals(fqcn)) {
message = String.format("Multi-catch with these reflection exceptions requires API level 19 (current min is %2$d) " +
"because they get compiled to the common but new super type `ReflectiveOperationException`. " +
"As a workaround either create individual catch statements, or catch `Exception`.",
api, minSdk);
}
String message = String.format("Class requires API level %1$d (current min is %2$d): %3$s",
api, minSdk, fqcn);
mContext.report(UNSUPPORTED, location, message);
}
}
@@ -2356,55 +2371,97 @@ public class ApiDetector extends ResourceXmlDetector
}
}
private static boolean isPrecededByVersionCheckExit(
UElement element, int api, JavaContext context) {
UElement current = UastUtils.getParentOfType(element, UExpression.class);
if (current != null) {
UElement prev = getPreviousStatement(current);
if (prev == null) {
//noinspection unchecked
current = UastUtils.getParentOfType(current, UExpression.class, true,
UMethod.class, UClass.class);
} else {
current = prev;
}
private static class VersionCheckWithExitFinder extends AbstractUastVisitor {
private final UExpression mExpression;
private final UElement mEndElement;
private final int mApi;
private final JavaContext mContext;
private boolean mFound = false;
private boolean mDone = false;
public VersionCheckWithExitFinder(UExpression expression, UElement endElement,
int api, JavaContext context) {
mExpression = expression;
mEndElement = endElement;
mApi = api;
mContext = context;
}
while (current != null) {
if (current instanceof UIfExpression) {
UIfExpression ifStatement = (UIfExpression)current;
UExpression thenBranch = ifStatement.getThenExpression();
UExpression elseBranch = ifStatement.getElseExpression();
if (thenBranch != null) {
Boolean level = isVersionCheckConditional(api, thenBranch, ifStatement, context);
//noinspection VariableNotUsedInsideIf
if (level != null) {
// See if the body does an immediate return
if (isUnconditionalReturn(thenBranch)) {
return true;
}
}
}
if (elseBranch != null) {
Boolean level = isVersionCheckConditional(api, elseBranch, ifStatement, context);
//noinspection VariableNotUsedInsideIf
if (level != null) {
if (isUnconditionalReturn(elseBranch)) {
return true;
}
@Override
public boolean visitElement(UElement node) {
if (mDone) {
return true;
}
if (node.equals(mEndElement)) {
mDone = true;
}
return mDone || !mExpression.equals(node);
}
@Override
public boolean visitIfExpression(UIfExpression ifStatement) {
if (mDone) {
return true;
}
UExpression thenBranch = ifStatement.getThenExpression();
UExpression elseBranch = ifStatement.getElseExpression();
if (thenBranch != null) {
Boolean level = isVersionCheckConditional(mApi, thenBranch, ifStatement, mContext);
//noinspection VariableNotUsedInsideIf
if (level != null) {
// See if the body does an immediate return
if (isUnconditionalReturn(thenBranch)) {
mFound = true;
mDone = true;
}
}
}
UElement prev = getPreviousStatement(current);
if (prev == null) {
//noinspection unchecked
current = UastUtils.getParentOfType(current, UExpression.class, true,
UMethod.class, UClass.class);
if (current == null) {
return false;
if (elseBranch != null) {
Boolean level = isVersionCheckConditional(mApi, elseBranch, ifStatement, mContext);
//noinspection VariableNotUsedInsideIf
if (level != null) {
if (isUnconditionalReturn(elseBranch)) {
mFound = true;
mDone = true;
}
}
} else {
current = prev;
}
return true;
}
public boolean found() {
return mFound;
}
}
private static boolean isPrecededByVersionCheckExit(UElement element, int api,
JavaContext context) {
//noinspection unchecked
UExpression currentExpression = UastUtils.getParentOfType(element, UExpression.class,
true, UMethod.class, UClass.class);
while(currentExpression != null) {
VersionCheckWithExitFinder visitor = new VersionCheckWithExitFinder(
currentExpression, element, api, context);
currentExpression.accept(visitor);
if (visitor.found()) {
return true;
}
element = currentExpression;
//noinspection unchecked
currentExpression = UastUtils.getParentOfType(currentExpression, UExpression.class,
true, UMethod.class, UClass.class);
}
return false;
@@ -2420,16 +2477,6 @@ public class ApiDetector extends ResourceXmlDetector
return statement instanceof UReturnExpression;
}
@Nullable
public static UElement getPreviousStatement(UElement element) {
//TODO
return null;
//final PsiElement prevStatement = PsiTreeUtil.skipSiblingsBackward(element,
// PsiWhiteSpace.class, PsiComment.class);
//return prevStatement instanceof PsiStatement ? (PsiStatement)prevStatement : null;
}
public static boolean isWithinVersionCheckConditional(
UElement element, int api, JavaContext context) {
UElement current = element.getContainingElement();
@@ -2453,7 +2500,7 @@ public class ApiDetector extends ResourceXmlDetector
@Nullable
private static Boolean isVersionCheckConditional(
int api,
int api,
UElement prev,
UIfExpression ifStatement,
@NonNull JavaContext context) {
@@ -2543,7 +2590,7 @@ public class ApiDetector extends ResourceXmlDetector
// if (SDK_INT < ICE_CREAM_SANDWICH) { ... } else { <call> }
return level >= api && fromElse;
}
else if (tokenType == UastBinaryOperator.EQUALS
else if (tokenType == UastBinaryOperator.EQUALS
|| tokenType == UastBinaryOperator.IDENTITY_EQUALS) {
// if (SDK_INT == ICE_CREAM_SANDWICH) { <call> } else { }
return level >= api && fromThen;
@@ -2553,7 +2600,7 @@ public class ApiDetector extends ResourceXmlDetector
}
}
}
} else if (tokenType == UastBinaryOperator.LOGICAL_AND
} else if (tokenType == UastBinaryOperator.LOGICAL_AND
&& (ifStatement != null && prev == ifStatement.getThenExpression())) {
if (isAndedWithConditional(ifStatement.getCondition(), api, prev)) {
return true;
@@ -346,8 +346,7 @@ public class CleanupDetector extends Detector implements Detector.UastScanner {
recycleName);
}
UElement locationNode = node instanceof UCallExpression ?
((UCallExpression) node).getMethodIdentifier() : node;
UElement locationNode = node.getMethodIdentifier();
if (locationNode == null) {
locationNode = node;
}
@@ -507,7 +506,12 @@ public class CleanupDetector extends Detector implements Detector.UastScanner {
protected boolean isCleanupCall(@NonNull UCallExpression call) {
if (isEditorApplyMethodCall(mContext, call)
|| isEditorCommitMethodCall(mContext, call)) {
UExpression operand = call.getReceiver();
List<UExpression> chain = getQualifiedChain(getOutermostQualified(call));
if (chain.isEmpty()) {
return false;
}
UExpression operand = chain.get(0);
if (operand != null) {
PsiElement resolved = UastUtils.tryResolve(operand);
//noinspection SuspiciousMethodCalls
@@ -515,7 +519,7 @@ public class CleanupDetector extends Detector implements Detector.UastScanner {
return true;
} else if (resolved instanceof PsiMethod
&& operand instanceof UCallExpression
&& isCommittedInChainedCalls(mContext,
&& isEditorCommittedInChainedCalls(mContext,
(UCallExpression) operand)) {
// Check that the target of the committed chains is the
// right variable!
@@ -20,17 +20,18 @@ import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.tools.klint.detector.api.Category;
import com.android.tools.klint.detector.api.Detector;
import com.android.tools.klint.detector.api.Detector.JavaPsiScanner;
import com.android.tools.klint.detector.api.Implementation;
import com.android.tools.klint.detector.api.Issue;
import com.android.tools.klint.detector.api.JavaContext;
import com.android.tools.klint.detector.api.Location;
import com.android.tools.klint.detector.api.Scope;
import com.android.tools.klint.detector.api.Severity;
import com.intellij.psi.JavaElementVisitor;
import com.intellij.psi.PsiComment;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiLiteralExpression;
import org.jetbrains.uast.UElement;
import org.jetbrains.uast.UComment;
import org.jetbrains.uast.UFile;
import org.jetbrains.uast.visitor.AbstractUastVisitor;
import org.jetbrains.uast.visitor.UastVisitor;
import java.util.Collections;
import java.util.List;
@@ -38,7 +39,7 @@ import java.util.List;
/**
* Looks for issues in Java comments
*/
public class CommentDetector extends Detector implements JavaPsiScanner {
public class CommentDetector extends Detector implements Detector.UastScanner {
private static final String STOPSHIP_COMMENT = "STOPSHIP"; //$NON-NLS-1$
private static final Implementation IMPLEMENTATION = new Implementation(
@@ -75,7 +76,7 @@ public class CommentDetector extends Detector implements JavaPsiScanner {
private static final String ESCAPE_STRING = "\\u002a\\u002f"; //$NON-NLS-1$
/** The current AST only passes comment nodes for Javadoc so I need to do manual token scanning
instead */
instead */
private static final boolean USE_AST = false;
@@ -83,20 +84,20 @@ public class CommentDetector extends Detector implements JavaPsiScanner {
public CommentDetector() {
}
@Nullable
@Override
public List<Class<? extends PsiElement>> getApplicablePsiTypes() {
public List<Class<? extends UElement>> getApplicableUastTypes() {
if (USE_AST) {
return Collections.<Class<? extends PsiElement>>singletonList(
PsiLiteralExpression.class);
return Collections.<Class<? extends UElement>>singletonList(
UFile.class);
} else {
return null;
}
}
@Nullable
@Override
public JavaElementVisitor createPsiVisitor(@NonNull JavaContext context) {
// Lombok does not generate comment nodes for block and line comments, only for
// javadoc comments!
public UastVisitor createUastVisitor(@NonNull JavaContext context) {
if (USE_AST) {
return new CommentChecker(context);
} else {
@@ -135,7 +136,7 @@ public class CommentDetector extends Detector implements JavaPsiScanner {
}
}
private static class CommentChecker extends JavaElementVisitor {
private static class CommentChecker extends AbstractUastVisitor {
private final JavaContext mContext;
public CommentChecker(JavaContext context) {
@@ -143,16 +144,19 @@ public class CommentDetector extends Detector implements JavaPsiScanner {
}
@Override
public void visitComment(PsiComment comment) {
String contents = comment.getText();
checkComment(mContext, comment, contents, comment.getTextRange().getStartOffset(), 0,
contents.length());
public boolean visitFile(UFile node) {
for (UComment comment : node.getAllCommentsInFile()) {
String contents = comment.getText();
checkComment(mContext, comment, contents,
comment.getPsi().getTextRange().getStartOffset(), 0, contents.length());
}
return super.visitFile(node);
}
}
private static void checkComment(
@NonNull JavaContext context,
@Nullable PsiComment node,
@Nullable UComment node,
@NonNull String source,
int offset,
int start,
@@ -164,24 +168,31 @@ public class CommentDetector extends Detector implements JavaPsiScanner {
if (prev == '\\') {
if (c == 'u' || c == 'U') {
if (source.regionMatches(true, i - 1, ESCAPE_STRING,
0, ESCAPE_STRING.length())) {
0, ESCAPE_STRING.length())) {
Location location = Location.create(context.file, source,
offset + i - 1, offset + i - 1 + ESCAPE_STRING.length());
offset + i - 1, offset + i - 1 + ESCAPE_STRING.length());
context.report(EASTER_EGG, node, location,
"Code might be hidden here; found unicode escape sequence " +
"which is interpreted as comment end, compiled code follows");
"Code might be hidden here; found unicode escape sequence " +
"which is interpreted as comment end, compiled code follows");
}
} else {
i++;
}
} else if (prev == 'S' && c == 'T' &&
source.regionMatches(i - 1, STOPSHIP_COMMENT, 0, STOPSHIP_COMMENT.length())) {
source.regionMatches(i - 1, STOPSHIP_COMMENT, 0, STOPSHIP_COMMENT.length())) {
// TODO: Only flag this issue in release mode??
Location location = Location.create(context.file, source,
offset + i - 1, offset + i - 1 + STOPSHIP_COMMENT.length());
Location location;
if (node != null) {
location = context.getLocation(node);
} else {
location = Location.create(context.file, source,
offset + i - 1, offset + i - 1 + STOPSHIP_COMMENT.length());
}
context.report(STOP_SHIP, node, location,
"`STOPSHIP` comment found; points to code which must be fixed prior " +
"to release");
"`STOPSHIP` comment found; points to code which must be fixed prior " +
"to release");
}
}
}
@@ -42,7 +42,7 @@ import com.intellij.psi.PsiStatement;
import com.intellij.psi.PsiWhiteSpace;
import org.jetbrains.uast.UArrayAccessExpression;
import org.jetbrains.uast.UBinaryExpression;
import org.jetbrains.uast.*;
import org.jetbrains.uast.UCallExpression;
import org.jetbrains.uast.UElement;
import org.jetbrains.uast.UExpression;
@@ -52,6 +52,7 @@ import org.jetbrains.uast.UQualifiedReferenceExpression;
import org.jetbrains.uast.UastUtils;
import org.jetbrains.uast.expressions.UReferenceExpression;
import org.jetbrains.uast.util.UastExpressionUtils;
import org.jetbrains.uast.visitor.AbstractUastVisitor;
import org.jetbrains.uast.visitor.UastVisitor;
import java.util.Collections;
@@ -165,137 +166,201 @@ public class CutPasteDetector extends Detector implements Detector.UastScanner {
@Nullable
private static String getLhs(@NonNull UCallExpression call) {
UElement parent = skipParentheses(call.getContainingElement());
if (parent != null && UastExpressionUtils.isTypeCast(parent)) {
UElement parent = call.getContainingElement();
while (parent != null && !(parent instanceof UBlockExpression)) {
if (parent instanceof ULocalVariable) {
return ((ULocalVariable) parent).getName();
} else if (UastExpressionUtils.isAssignment(parent)) {
UExpression left = ((UBinaryExpression) parent).getLeftOperand();
if (left instanceof UReferenceExpression) {
return left.asSourceString();
} else if (left instanceof UArrayAccessExpression) {
UArrayAccessExpression aa = (UArrayAccessExpression) left;
return aa.getReceiver().asSourceString();
}
}
parent = parent.getContainingElement();
}
if (parent instanceof ULocalVariable) {
return ((ULocalVariable) parent).getName();
} else if (parent instanceof UBinaryExpression) {
UBinaryExpression be = (UBinaryExpression) parent;
UExpression left = be.getLeftOperand();
if (left instanceof UReferenceExpression) {
return left.asRenderString();
} else if (left instanceof UArrayAccessExpression) {
UArrayAccessExpression aa = (UArrayAccessExpression) left;
return aa.getReceiver().asSourceString();
}
} else if (UastExpressionUtils.isAssignment(parent)) {
//noinspection ConstantConditions
UExpression left = ((UBinaryExpression) parent).getLeftOperand();
if (left instanceof UReferenceExpression) {
return left.asSourceString();
} else if (left instanceof UArrayAccessExpression) {
UArrayAccessExpression aa = (UArrayAccessExpression) left;
return aa.getReceiver().asSourceString();
}
}
return null;
}
static boolean isReachableFrom(
@NonNull PsiMethod method,
@NonNull PsiElement from,
@NonNull PsiElement to) {
PsiElement prev = from;
PsiElement curr = next(method, from, to, null);
//noinspection ConstantConditions
while (curr != null) {
if (containsElement(method, curr, to)) {
return true;
}
curr = next(method, curr, to, prev);
prev = curr;
}
return false;
}
static boolean isReachableFrom(
@NonNull PsiMethod method,
@NonNull UMethod method,
@NonNull UElement from,
@NonNull UElement to) {
//TODO
return false;
ReachabilityVisitor visitor = new ReachabilityVisitor(from, to);
method.accept(visitor);
return visitor.isReachable();
}
@Nullable
static PsiElement next(
@NonNull PsiMethod method,
@NonNull PsiElement curr,
@NonNull PsiElement target,
@Nullable PsiElement prev) {
private static class ReachabilityVisitor extends AbstractUastVisitor {
if (curr instanceof PsiMethod) {
return null;
private final UElement mFrom;
private final UElement mTarget;
private boolean mIsFromReached;
private boolean mIsTargetReachable;
private boolean mIsFinished;
private UExpression mBreakedExpression;
private UExpression mContinuedExpression;
ReachabilityVisitor(UElement from, UElement target) {
mFrom = from;
mTarget = target;
}
PsiElement parent = curr.getParent();
if (curr instanceof PsiContinueStatement) {
PsiStatement continuedStatement = ((PsiContinueStatement) curr)
.findContinuedStatement();
if (continuedStatement != null) {
if (containsElement(method, continuedStatement, target)) {
return target;
}
return next(method, continuedStatement, target, curr);
} else {
return next(method, parent, target, curr);
}
} else if (curr instanceof PsiBreakStatement) {
PsiStatement exitedStatement = ((PsiBreakStatement) curr).findExitedStatement();
if (exitedStatement != null) {
return next(method, exitedStatement, target, curr);
} else {
return next(method, parent, target, curr);
}
} else if (curr instanceof PsiReturnStatement) {
return null;
} else if (curr instanceof PsiLoopStatement && prev != null &&
containsElement(method, curr, prev)) {
// If we stepped *up* (from a last child nested in the loop) up to the loop
// itself, mark all children in the loop as reachable since we're iterating
if (containsElement(method, curr, target)) {
return target;
}
}
PsiElement sibling = curr.getNextSibling();
while (sibling instanceof PsiWhiteSpace || sibling instanceof PsiJavaToken) {
// Skip whitespaces and tokens such as PsiJavaToken.SEMICOLON etc
sibling = sibling.getNextSibling();
}
if (sibling == null) {
return next(method, parent, target, curr);
}
if (parent instanceof PsiIfStatement &&
curr == ((PsiIfStatement)parent).getThenBranch()) {
return next(method, parent, target, curr);
} else if (parent instanceof PsiLoopStatement) {
if (containsElement(method, parent, target)) {
return target;
}
}
return sibling;
}
private static boolean containsElement(
@NonNull PsiMethod method,
@NonNull PsiElement root,
@NonNull PsiElement element) {
//noinspection ConstantConditions
while (element != null && element != method) {
if (root.equals(element)) {
@Override
public boolean visitElement(UElement node) {
if (mIsFinished || mBreakedExpression != null || mContinuedExpression != null) {
return true;
}
element = element.getParent();
if (node.equals(mFrom)) {
mIsFromReached = true;
}
if (node.equals(mTarget)) {
mIsFinished = true;
if (mIsFromReached) {
mIsTargetReachable = true;
}
return true;
}
if (mIsFromReached) {
if (node instanceof UReturnExpression) {
mIsFinished = true;
} else if (node instanceof UBreakExpression) {
mBreakedExpression = getBreakedExpression((UBreakExpression) node);
} else if (node instanceof UContinueExpression) {
UExpression expression = getContinuedExpression((UContinueExpression) node);
if (expression != null && UastUtils.isChildOf(mTarget, expression, false)) {
mIsTargetReachable = true;
mIsFinished = true;
} else {
mContinuedExpression = expression;
}
} else if (UastUtils.isChildOf(mTarget, node, false)) {
mIsTargetReachable = true;
mIsFinished = true;
}
return true;
} else {
if (node instanceof UIfExpression) {
UIfExpression ifExpression = (UIfExpression) node;
ifExpression.getCondition().accept(this);
boolean isFromReached = mIsFromReached;
UExpression thenExpression = ifExpression.getThenExpression();
if (thenExpression != null) {
thenExpression.accept(this);
}
UExpression elseExpression = ifExpression.getElseExpression();
if (elseExpression != null && isFromReached == mIsFromReached) {
elseExpression.accept(this);
}
return true;
} else if (node instanceof ULoopExpression) {
visitLoopExpressionHeader(node);
boolean isFromReached = mIsFromReached;
((ULoopExpression) node).getBody().accept(this);
if (isFromReached != mIsFromReached
&& UastUtils.isChildOf(mTarget, node, false)) {
mIsTargetReachable = true;
mIsFinished = true;
}
return true;
}
}
return false;
}
return false;
@Override
public void afterVisitElement(UElement node) {
if (node.equals(mBreakedExpression)) {
mBreakedExpression = null;
} else if (node.equals(mContinuedExpression)) {
mContinuedExpression = null;
}
}
private void visitLoopExpressionHeader(UElement node) {
if (node instanceof UWhileExpression) {
((UWhileExpression) node).getCondition().accept(this);
} else if (node instanceof UDoWhileExpression) {
((UDoWhileExpression) node).getCondition().accept(this);
} else if (node instanceof UForExpression) {
UForExpression forExpression = (UForExpression) node;
if (forExpression.getDeclaration() != null) {
forExpression.getDeclaration().accept(this);
}
if (forExpression.getCondition() != null) {
forExpression.getCondition().accept(this);
}
if (forExpression.getUpdate() != null) {
forExpression.getUpdate().accept(this);
}
} else if (node instanceof UForEachExpression) {
UForEachExpression forEachExpression = (UForEachExpression) node;
forEachExpression.getForIdentifier().accept(this);
forEachExpression.getIteratedValue().accept(this);
}
}
private static UExpression getBreakedExpression(UBreakExpression node) {
UElement parent = node.getContainingElement();
String label = node.getLabel();
while (parent != null) {
if (label != null) {
if (parent instanceof ULabeledExpression) {
ULabeledExpression labeledExpression = (ULabeledExpression) parent;
if (labeledExpression.getLabel().equals(label)) {
return labeledExpression.getExpression();
}
}
} else {
if (parent instanceof ULoopExpression || parent instanceof USwitchExpression) {
return (UExpression) parent;
}
}
parent = parent.getContainingElement();
}
return null;
}
private static UExpression getContinuedExpression(UContinueExpression node) {
UElement parent = node.getContainingElement();
String label = node.getLabel();
while (parent != null) {
if (label != null) {
if (parent instanceof ULabeledExpression) {
ULabeledExpression labeledExpression = (ULabeledExpression) parent;
if (labeledExpression.getLabel().equals(label)) {
return labeledExpression.getExpression();
}
}
} else {
if (parent instanceof ULoopExpression) {
return (UExpression) parent;
}
}
parent = parent.getContainingElement();
}
return null;
}
public boolean isReachable() {
return mIsTargetReachable;
}
}
}
@@ -1972,9 +1972,10 @@ public class IconDetector extends ResourceXmlDetector implements Detector.UastSc
// ---- Implements UastScanner ----
private static final String NOTIFICATION_CLASS = "Notification";
private static final String NOTIFICATION_BUILDER_CLASS = "Notification.Builder";
private static final String NOTIFICATION_COMPAT_BUILDER_CLASS = "NotificationCompat.Builder";
private static final String NOTIFICATION_CLASS = "android.app.Notification";
private static final String NOTIFICATION_BUILDER_CLASS = "android.app.Notification.Builder";
private static final String NOTIFICATION_COMPAT_BUILDER_CLASS =
"android.support.v4.app.NotificationCompat.Builder";
private static final String SET_SMALL_ICON = "setSmallIcon";
private static final String ON_CREATE_OPTIONS_MENU = "onCreateOptionsMenu";
@@ -2026,7 +2027,7 @@ public class IconDetector extends ResourceXmlDetector implements Detector.UastSc
if (!(resolved instanceof PsiClass)) {
return;
}
String typeName = ((PsiClass) resolved).getName();
String typeName = ((PsiClass) resolved).getQualifiedName();
if (NOTIFICATION_CLASS.equals(typeName)) {
List<UExpression> args = node.getValueArguments();
if (args.size() == 3) {
@@ -41,6 +41,7 @@ import com.intellij.psi.PsiType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.uast.UClass;
import org.jetbrains.uast.UElement;
import org.jetbrains.uast.UField;
import org.jetbrains.uast.UVariable;
import org.jetbrains.uast.visitor.AbstractUastVisitor;
@@ -127,7 +128,7 @@ public class LeakDetector extends Detector implements Detector.UastScanner {
if (isLeakCandidate(cls, mContext.getEvaluator())) {
String message = "Do not place Android context classes in static fields; "
+ "this is a memory leak (and also breaks Instant Run)";
report(field, modifierList, message);
report(field, message);
}
} else {
// User application object -- look to see if that one itself has
@@ -162,7 +163,7 @@ public class LeakDetector extends Detector implements Detector.UastScanner {
+ "`" + referenced.getName() + "` pointing to `"
+ innerCls.getName() + "`); "
+ "this is a memory leak (and also breaks Instant Run)";
report(field, modifierList, message);
report(field, message);
break;
}
}
@@ -170,11 +171,8 @@ public class LeakDetector extends Detector implements Detector.UastScanner {
}
}
private void report(@NonNull PsiField field, @NonNull PsiModifierList modifierList,
@NonNull String message) {
Location location = mContext.getLocation(
modifierList.getTextRange().getLength() > 0 ? modifierList : field);
mContext.report(ISSUE, field, location, message);
private void report(@NonNull UElement element, @NonNull String message) {
mContext.report(ISSUE, element, mContext.getUastLocation(element), message);
}
}
@@ -37,19 +37,7 @@ import com.intellij.psi.PsiNamedElement;
import com.intellij.psi.PsiParameterList;
import com.intellij.psi.PsiVariable;
import org.jetbrains.uast.UBinaryExpressionWithType;
import org.jetbrains.uast.UCallExpression;
import org.jetbrains.uast.UClass;
import org.jetbrains.uast.UClassInitializer;
import org.jetbrains.uast.UElement;
import org.jetbrains.uast.UExpression;
import org.jetbrains.uast.UField;
import org.jetbrains.uast.UIfExpression;
import org.jetbrains.uast.ULiteralExpression;
import org.jetbrains.uast.UMethod;
import org.jetbrains.uast.UQualifiedReferenceExpression;
import org.jetbrains.uast.USimpleNameReferenceExpression;
import org.jetbrains.uast.UastUtils;
import org.jetbrains.uast.*;
import org.jetbrains.uast.visitor.UastVisitor;
import java.util.Arrays;
@@ -165,7 +153,7 @@ public class LogDetector extends Detector implements Detector.UastScanner {
String message = String.format("The log call Log.%1$s(...) should be " +
"conditional: surround with `if (Log.isLoggable(...))` or " +
"`if (BuildConfig.DEBUG) { ... }`",
node.getMethodIdentifier());
name);
context.report(CONDITIONAL, node, context.getUastLocation(node), message);
}
@@ -205,7 +193,7 @@ public class LogDetector extends Detector implements Detector.UastScanner {
if (argument instanceof ULiteralExpression) {
return false;
}
if (argument instanceof UBinaryExpressionWithType) {
if (argument instanceof UBinaryExpression) {
String string = UastUtils.evaluateString(argument);
//noinspection VariableNotUsedInsideIf
if (string != null) { // does it resolve to a constant?
@@ -233,17 +221,21 @@ public class LogDetector extends Detector implements Detector.UastScanner {
return false;
}
private static boolean checkWithinConditional(
@NonNull JavaContext context,
@Nullable UElement curr,
@NonNull UCallExpression logCall) {
while (curr != null) {
if (curr instanceof UIfExpression) {
UIfExpression ifNode = (UIfExpression) curr;
List<UExpression> chain = UastUtils.getQualifiedChain(ifNode.getCondition());
if (!chain.isEmpty() && chain.get(chain.size() - 1) instanceof UCallExpression) {
UCallExpression call = (UCallExpression) chain.get(chain.size() - 1);
UExpression condition = ((UIfExpression) curr).getCondition();
if (condition instanceof UQualifiedReferenceExpression) {
condition = getLastInQualifiedChain((UQualifiedReferenceExpression) condition);
}
if (condition instanceof UCallExpression) {
UCallExpression call = (UCallExpression) condition;
if (IS_LOGGABLE.equals(call.getMethodName())) {
checkTagConsistent(context, logCall, call);
}
@@ -251,10 +243,10 @@ public class LogDetector extends Detector implements Detector.UastScanner {
return true;
} else if (curr instanceof UCallExpression
|| curr instanceof UMethod
|| curr instanceof UClassInitializer
|| curr instanceof UField
|| curr instanceof UClass) { // static block
|| curr instanceof UMethod
|| curr instanceof UClassInitializer
|| curr instanceof UField
|| curr instanceof UClass) { // static block
break;
}
curr = curr.getContainingElement();
@@ -283,7 +275,8 @@ public class LogDetector extends Detector implements Detector.UastScanner {
}
if (logTag != null) {
if (!UastLintUtils.areIdentifiersEqual(isLoggableTag, logTag)) {
if (!areLiteralsEqual(isLoggableTag, logTag) &&
!UastLintUtils.areIdentifiersEqual(isLoggableTag, logTag)) {
PsiNamedElement resolved1 = UastUtils.tryResolveNamed(isLoggableTag);
PsiNamedElement resolved2 = UastUtils.tryResolveNamed(logTag);
if ((resolved1 == null || resolved2 == null || !resolved1.equals(resolved2))
@@ -348,4 +341,32 @@ public class LogDetector extends Detector implements Detector.UastScanner {
context.report(WRONG_TAG, isLoggableCall, location, message);
}
}
@NonNull
private static UExpression getLastInQualifiedChain(@NonNull UQualifiedReferenceExpression node) {
UExpression last = node.getSelector();
while (last instanceof UQualifiedReferenceExpression) {
last = ((UQualifiedReferenceExpression) last).getSelector();
}
return last;
}
private static boolean areLiteralsEqual(UExpression first, UExpression second) {
if (!(first instanceof ULiteralExpression)) {
return false;
}
if (!(second instanceof ULiteralExpression)) {
return false;
}
Object firstValue = ((ULiteralExpression) first).getValue();
Object secondValue = ((ULiteralExpression) second).getValue();
if (firstValue == null) {
return secondValue == null;
}
return firstValue.equals(secondValue);
}
}
@@ -39,16 +39,7 @@ import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiParameter;
import com.intellij.psi.PsiVariable;
import org.jetbrains.uast.UAnonymousClass;
import org.jetbrains.uast.UBinaryExpression;
import org.jetbrains.uast.UCallExpression;
import org.jetbrains.uast.UClass;
import org.jetbrains.uast.UElement;
import org.jetbrains.uast.UExpression;
import org.jetbrains.uast.USimpleNameReferenceExpression;
import org.jetbrains.uast.UVariable;
import org.jetbrains.uast.UastBinaryOperator;
import org.jetbrains.uast.UastUtils;
import org.jetbrains.uast.*;
import org.jetbrains.uast.expressions.UReferenceExpression;
import org.jetbrains.uast.util.UastExpressionUtils;
import org.jetbrains.uast.visitor.AbstractUastVisitor;
@@ -128,14 +119,15 @@ public class RecyclerViewDetector extends Detector implements Detector.UastScann
PsiParameter parameter = parameters[1];
ParameterEscapesVisitor visitor = new ParameterEscapesVisitor(context, cls, parameter);
context.getUastContext().getMethodBody(declaration).accept(visitor);
UMethod method = context.getUastContext().getMethod(declaration);
method.accept(visitor);
if (visitor.variableEscapes()) {
reportError(context, viewHolder, parameter);
}
// Look for pending data binder calls that aren't executed before the method finishes
List<UCallExpression> dataBinderReferences = visitor.getDataBinders();
checkDataBinders(context, declaration, dataBinderReferences);
checkDataBinders(context, method, dataBinderReferences);
}
private static void reportError(@NonNull JavaContext context, PsiParameter viewHolder,
@@ -152,7 +144,7 @@ public class RecyclerViewDetector extends Detector implements Detector.UastScann
}
private static void checkDataBinders(@NonNull JavaContext context,
@NonNull PsiMethod declaration, List<UCallExpression> references) {
@NonNull UMethod declaration, List<UCallExpression> references) {
if (references != null && !references.isEmpty()) {
List<UCallExpression> targets = Lists.newArrayList();
List<UCallExpression> sources = Lists.newArrayList();
@@ -1162,12 +1162,12 @@ public class StringFormatDetector extends ResourceXmlDetector implements Detecto
boolean argWasReference = false;
if (lastArg instanceof UReferenceExpression) {
PsiElement resolved = ((UReferenceExpression) lastArg).resolve();
if (resolved instanceof PsiVariable) {
UExpression initializer = context.getUastContext().getInitializerBody (
(PsiVariable) resolved);
if (UastExpressionUtils.isConstructorCall(initializer)) {
if (initializer != null &&
(UastExpressionUtils.isNewArray(initializer) ||
UastExpressionUtils.isNestedArrayInitializer(initializer))) {
argWasReference = true;
// Now handled by check below
lastArg = initializer;
@@ -1175,14 +1175,16 @@ public class StringFormatDetector extends ResourceXmlDetector implements Detecto
}
}
if (UastExpressionUtils.isNewArray(lastArg)) {
UCallExpression callExpression = (UCallExpression) lastArg;
if (UastExpressionUtils.isNewArray(lastArg) ||
UastExpressionUtils.isNestedArrayInitializer(lastArg)) {
UCallExpression arrayInitializer = (UCallExpression) lastArg;
if (UastExpressionUtils.isNewArrayWithInitializer(lastArg)) {
callCount = callExpression.getValueArgumentCount();
if (UastExpressionUtils.isNewArrayWithInitializer(lastArg) ||
UastExpressionUtils.isNestedArrayInitializer(lastArg)) {
callCount = arrayInitializer.getValueArgumentCount();
knownArity = true;
} else if (UastExpressionUtils.isNewArrayWithDimensions(lastArg)) {
List<UExpression> arrayDimensions = callExpression.getValueArguments();
List<UExpression> arrayDimensions = arrayInitializer.getValueArguments();
if (arrayDimensions.size() == 1) {
UExpression first = arrayDimensions.get(0);
if (first instanceof ULiteralExpression) {
@@ -46,6 +46,7 @@ import static com.android.tools.klint.detector.api.LintUtils.skipParentheses;
import static com.android.tools.klint.detector.api.ResourceEvaluator.COLOR_INT_ANNOTATION;
import static com.android.tools.klint.detector.api.ResourceEvaluator.PX_ANNOTATION;
import static com.android.tools.klint.detector.api.ResourceEvaluator.RES_SUFFIX;
import static org.jetbrains.uast.UastUtils.getQualifiedParentOrThis;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
@@ -54,6 +55,7 @@ import com.android.sdklib.AndroidVersion;
import com.android.tools.klint.checks.PermissionFinder.Operation;
import com.android.tools.klint.checks.PermissionFinder.Result;
import com.android.tools.klint.checks.PermissionHolder.SetPermissionLookup;
import com.android.tools.klint.client.api.ExternalReferenceExpression;
import com.android.tools.klint.client.api.JavaEvaluator;
import com.android.tools.klint.client.api.LintClient;
import com.android.tools.klint.client.api.UastLintUtils;
@@ -91,26 +93,7 @@ import com.intellij.psi.PsiReference;
import com.intellij.psi.PsiType;
import com.intellij.psi.PsiVariable;
import org.jetbrains.uast.UAnonymousClass;
import org.jetbrains.uast.UBinaryExpression;
import org.jetbrains.uast.UBinaryExpressionWithType;
import org.jetbrains.uast.UCallExpression;
import org.jetbrains.uast.UCatchClause;
import org.jetbrains.uast.UElement;
import org.jetbrains.uast.UEnumConstant;
import org.jetbrains.uast.UExpression;
import org.jetbrains.uast.UField;
import org.jetbrains.uast.UIfExpression;
import org.jetbrains.uast.ULiteralExpression;
import org.jetbrains.uast.UMethod;
import org.jetbrains.uast.UParenthesizedExpression;
import org.jetbrains.uast.UPrefixExpression;
import org.jetbrains.uast.UTryExpression;
import org.jetbrains.uast.UVariable;
import org.jetbrains.uast.UastBinaryOperator;
import org.jetbrains.uast.UastOperator;
import org.jetbrains.uast.UastPrefixOperator;
import org.jetbrains.uast.UastUtils;
import org.jetbrains.uast.*;
import org.jetbrains.uast.expressions.UReferenceExpression;
import org.jetbrains.uast.util.UastExpressionUtils;
import org.jetbrains.uast.visitor.AbstractUastVisitor;
@@ -289,6 +272,8 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
public static final String ATTR_ANY_OF = "anyOf";
public static final String ATTR_CONDITIONAL = "conditional";
public static final String SECURITY_EXCEPTION = "java.lang.SecurityException";
/**
* Constructs a new {@link SupportAnnotationDetector} check
*/
@@ -557,8 +542,8 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
if (tryCatch == null) {
break;
} else {
for (UCatchClause psiCatchSection : tryCatch.getCatchClauses()) {
if (isSecurityException(psiCatchSection.getTypes())) {
for (UCatchClause catchClause : tryCatch.getCatchClauses()) {
if (containsSecurityException(catchClause.getTypes())) {
return true;
}
}
@@ -572,7 +557,7 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
UMethod declaration = UastUtils.getParentOfType(parent, UMethod.class, false);
if (declaration != null) {
PsiClassType[] thrownTypes = declaration.getThrowsList().getReferencedTypes();
if (isSecurityException(Arrays.<PsiType>asList(thrownTypes))) {
if (containsSecurityException(Arrays.asList(thrownTypes))) {
return true;
}
}
@@ -685,8 +670,8 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
}
}
private static boolean isSecurityException(
@NonNull List<PsiType> types) {
private static boolean containsSecurityException(
@NonNull List<? extends PsiType> types) {
for (PsiType type : types) {
if (type instanceof PsiClassType) {
PsiClass cls = ((PsiClassType) type).resolve();
@@ -694,7 +679,9 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
// any super type as well, however that probably hides warnings in cases where
// users don't want that; see http://b.android.com/182165
//return context.getEvaluator().extendsClass(cls, "java.lang.SecurityException", false);
return cls != null && "java.lang.SecurityException".equals(cls.getQualifiedName());
if (cls != null && SECURITY_EXCEPTION.equals(cls.getQualifiedName())) {
return true;
}
}
}
@@ -773,7 +760,7 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
private static void checkResult(@NonNull JavaContext context, @NonNull UCallExpression node,
@NonNull PsiMethod method, @NonNull PsiAnnotation annotation) {
if (context.getUastContext().isExpressionValueUsed(UastUtils.getQualifiedParentOrThis(node))) {
if (isExpressionValueUnused(node)) {
String methodName = JavaContext.getMethodName(node);
String suggested = getAnnotationStringValue(annotation, ATTR_SUGGEST);
@@ -809,6 +796,11 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
}
}
private static boolean isExpressionValueUnused(UExpression expression) {
return getQualifiedParentOrThis(expression).getContainingElement()
instanceof UBlockExpression;
}
private static void checkThreading(
@NonNull JavaContext context,
@NonNull UElement node,
@@ -1150,7 +1142,7 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
*/
public static boolean typeArrayFromArrayLiteral(
@Nullable UElement node, @NonNull JavaContext context) {
if (UastExpressionUtils.isMethodCall(node)) {
if (isMethodCall(node)) {
UCallExpression expression = (UCallExpression) node;
assert expression != null;
String name = expression.getMethodName();
@@ -1213,6 +1205,24 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
return false;
}
private static boolean isMethodCall(UElement node) {
if (node instanceof UQualifiedReferenceExpression) {
UExpression last = getLastInQualifiedChain((UQualifiedReferenceExpression) node);
return UastExpressionUtils.isMethodCall(last);
}
return UastExpressionUtils.isMethodCall(node);
}
@NonNull
private static UExpression getLastInQualifiedChain(@NonNull UQualifiedReferenceExpression node) {
UExpression last = node.getSelector();
while (last instanceof UQualifiedReferenceExpression) {
last = ((UQualifiedReferenceExpression) last).getSelector();
}
return last;
}
private static void checkIntRange(
@NonNull JavaContext context,
@NonNull PsiAnnotation annotation,
@@ -1630,6 +1640,12 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
if (value.equals(((PsiLiteral)expression).getValue())) {
return;
}
} else if (expression instanceof ExternalReferenceExpression) {
PsiElement resolved = UastLintUtils.resolve(
(ExternalReferenceExpression) expression, argument);
if (resolved != null && resolved.equals(value)) {
return;
}
} else if (expression instanceof PsiReference) {
PsiElement resolved = ((PsiReference) expression).resolve();
if (resolved != null && resolved.equals(value)) {
@@ -1677,7 +1693,7 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
return;
}
String values = listAllowedValues(allowedValues);
String values = listAllowedValues(node, allowedValues);
String message;
if (flag) {
message = "Must be one or more of: " + values;
@@ -1709,22 +1725,29 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
return null;
}
private static String listAllowedValues(@NonNull PsiAnnotationMemberValue[] allowedValues) {
private static String listAllowedValues(@NonNull UElement context,
@NonNull PsiAnnotationMemberValue[] allowedValues) {
StringBuilder sb = new StringBuilder();
for (PsiAnnotationMemberValue allowedValue : allowedValues) {
String s = null;
if (allowedValue instanceof PsiReference) {
PsiElement resolved = ((PsiReference) allowedValue).resolve();
if (resolved instanceof PsiField) {
PsiField field = (PsiField) resolved;
String containingClassName = field.getContainingClass() != null
? field.getContainingClass().getName() : null;
if (containingClassName == null) {
continue;
}
s = containingClassName + "." + field.getName();
}
PsiElement resolved = null;
if (allowedValue instanceof ExternalReferenceExpression) {
resolved = UastLintUtils.resolve(
(ExternalReferenceExpression) allowedValue, context);
} else if (allowedValue instanceof PsiReference) {
resolved = ((PsiReference) allowedValue).resolve();
}
if (resolved instanceof PsiField) {
PsiField field = (PsiField) resolved;
String containingClassName = field.getContainingClass() != null
? field.getContainingClass().getName() : null;
if (containingClassName == null) {
continue;
}
s = containingClassName + "." + field.getName();
}
if (s == null) {
s = allowedValue.getText();
}
@@ -1768,7 +1791,7 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
@NonNull
static PsiAnnotation[] filterRelevantAnnotations(
@NonNull PsiAnnotation[] annotations) {
@NonNull JavaEvaluator evaluator, @NonNull PsiAnnotation[] annotations) {
List<PsiAnnotation> result = null;
int length = annotations.length;
if (length == 0) {
@@ -1812,33 +1835,31 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
continue;
}
PsiClass cls = (PsiClass)resolved;
PsiModifierList modifierList = cls.getModifierList();
if (modifierList != null) {
PsiAnnotation[] innerAnnotations = modifierList.getAnnotations();
for (int j = 0; j < innerAnnotations.length; j++) {
PsiAnnotation inner = innerAnnotations[j];
String a = inner.getQualifiedName();
if (a == null) {
continue;
PsiAnnotation[] innerAnnotations = evaluator.getAllAnnotations(cls);
for (int j = 0; j < innerAnnotations.length; j++) {
PsiAnnotation inner = innerAnnotations[j];
String a = inner.getQualifiedName();
if (a == null || a.startsWith("java.")) {
// @Override, @SuppressWarnings etc. Ignore
continue;
}
if (a.equals(INT_DEF_ANNOTATION)
|| a.equals(PERMISSION_ANNOTATION)
|| a.equals(INT_RANGE_ANNOTATION)
|| a.equals(STRING_DEF_ANNOTATION)) {
if (length == 1 && j == innerAnnotations.length - 1 && result == null) {
return innerAnnotations;
}
if (a.equals(INT_DEF_ANNOTATION)
|| a.equals(PERMISSION_ANNOTATION)
|| a.equals(INT_RANGE_ANNOTATION)
|| a.equals(STRING_DEF_ANNOTATION)) {
if (length == 1 && j == innerAnnotations.length - 1) {
return innerAnnotations;
}
if (result == null) {
result = new ArrayList<PsiAnnotation>(2);
}
result.add(inner);
if (result == null) {
result = new ArrayList<PsiAnnotation>(2);
}
result.add(inner);
}
}
}
return result != null
? result.toArray(PsiAnnotation.EMPTY_ARRAY) : PsiAnnotation.EMPTY_ARRAY;
? result.toArray(PsiAnnotation.EMPTY_ARRAY) : PsiAnnotation.EMPTY_ARRAY;
}
// ---- Implements UastScanner ----
@@ -1849,9 +1870,7 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
public List<Class<? extends UElement>> getApplicableUastTypes() {
List<Class<? extends UElement>> types = new ArrayList<Class<? extends UElement>>(3);
types.add(UCallExpression.class);
//types.add(PsiMethodCallExpression.class);
//types.add(PsiNewExpression.class);
types.add(UField.class);
types.add(UVariable.class);
return types;
}
@@ -1886,33 +1905,33 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
}
return super.visitVariable(node);
}
public void checkCall(PsiMethod method, UCallExpression call) {
JavaEvaluator evaluator = mContext.getEvaluator();
PsiAnnotation[] methodAnnotations = evaluator.getAllAnnotations(method, true);
methodAnnotations = filterRelevantAnnotations(methodAnnotations);
PsiAnnotation[] methodAnnotations = evaluator.getAllAnnotations(method);
methodAnnotations = filterRelevantAnnotations(evaluator, methodAnnotations);
// Look for annotations on the class as well: these trickle
// down to all the methods in the class
PsiClass containingClass = method.getContainingClass();
PsiAnnotation[] classAnnotations;
if (containingClass != null) {
classAnnotations = evaluator.getAllAnnotations(containingClass, true);
classAnnotations = filterRelevantAnnotations(classAnnotations);
classAnnotations = evaluator.getAllAnnotations(containingClass);
classAnnotations = filterRelevantAnnotations(evaluator, classAnnotations);
} else {
classAnnotations = PsiAnnotation.EMPTY_ARRAY;
}
for (PsiAnnotation annotation : methodAnnotations) {
checkMethodAnnotation(mContext, method, call, annotation, methodAnnotations,
classAnnotations);
classAnnotations);
}
if (classAnnotations.length > 0) {
for (PsiAnnotation annotation : classAnnotations) {
checkMethodAnnotation(mContext, method, call, annotation, methodAnnotations,
classAnnotations);
classAnnotations);
}
}
@@ -1921,12 +1940,12 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
PsiParameter[] parameters = parameterList.getParameters();
PsiAnnotation[] annotations = null;
for (int i = 0, n = Math.min(parameters.length, arguments.size());
i < n;
i++) {
i < n;
i++) {
UExpression argument = arguments.get(i);
PsiParameter parameter = parameters[i];
annotations = evaluator.getAllAnnotations(parameter, true);
annotations = filterRelevantAnnotations(annotations);
annotations = evaluator.getAllAnnotations(parameter);
annotations = filterRelevantAnnotations(evaluator, annotations);
checkParameterAnnotations(mContext, argument, call, method, annotations);
}
if (annotations != null) {
@@ -183,7 +183,7 @@ public class IdeaJavaParser extends JavaParser {
@NonNull
@Override
public PsiAnnotation[] getAllAnnotations(@NonNull PsiModifierListOwner owner, boolean inHierarchy) {
public PsiAnnotation[] getAllAnnotations(@NonNull PsiModifierListOwner owner) {
return AnnotationUtil.getAllAnnotations(owner, inHierarchy, null, true);
}