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); public abstract PsiClassType getClassType(@Nullable PsiClass psiClass);
@NonNull @NonNull
public abstract PsiAnnotation[] getAllAnnotations(@NonNull PsiModifierListOwner owner, public abstract PsiAnnotation[] getAllAnnotations(@NonNull PsiModifierListOwner owner);
boolean inHierarchy);
@Nullable @Nullable
public abstract PsiAnnotation findAnnotationInHierarchy( public abstract PsiAnnotation findAnnotationInHierarchy(
@@ -504,6 +504,17 @@ public class UElementVisitor {
} }
private class DispatchPsiVisitor extends AbstractUastVisitor { 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 @Override
public boolean visitCatchClause(UCatchClause node) { public boolean visitCatchClause(UCatchClause node) {
@@ -34,15 +34,7 @@ import com.intellij.psi.PsiParameter;
import com.intellij.psi.PsiType; import com.intellij.psi.PsiType;
import com.intellij.psi.PsiVariable; import com.intellij.psi.PsiVariable;
import org.jetbrains.uast.UElement; import org.jetbrains.uast.*;
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.expressions.UReferenceExpression; import org.jetbrains.uast.expressions.UReferenceExpression;
import org.jetbrains.uast.java.JavaAbstractUExpression; import org.jetbrains.uast.java.JavaAbstractUExpression;
import org.jetbrains.uast.java.JavaUVariableDeclarationsExpression; import org.jetbrains.uast.java.JavaUVariableDeclarationsExpression;
@@ -51,6 +43,16 @@ import java.util.Collections;
import java.util.List; import java.util.List;
public class UastLintUtils { 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 @NonNull
public static String getClassName(PsiClassType type) { public static String getClassName(PsiClassType type) {
PsiClass psiClass = type.resolve(); PsiClass psiClass = type.resolve();
@@ -88,10 +90,8 @@ public class UastLintUtils {
(variable instanceof PsiLocalVariable || variable instanceof PsiParameter)) { (variable instanceof PsiLocalVariable || variable instanceof PsiParameter)) {
UMethod containingFunction = UastUtils.getContainingUMethod(call); UMethod containingFunction = UastUtils.getContainingUMethod(call);
if (containingFunction != null) { if (containingFunction != null) {
ConstantEvaluator.LastAssignmentFinder ConstantEvaluator.LastAssignmentFinder finder =
finder = new ConstantEvaluator.LastAssignmentFinder( new ConstantEvaluator.LastAssignmentFinder(variable, call, context, null, -1);
variable, call, context, null,
(variable instanceof PsiParameter) ? 1 : 0);
containingFunction.accept(finder); containingFunction.accept(finder);
lastAssignment = finder.getLastAssignment(); lastAssignment = finder.getLastAssignment();
} }
@@ -63,21 +63,7 @@ import com.intellij.psi.PsiVariable;
import com.intellij.psi.tree.IElementType; import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.uast.UBinaryExpression; import org.jetbrains.uast.*;
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.expressions.UReferenceExpression; import org.jetbrains.uast.expressions.UReferenceExpression;
import org.jetbrains.uast.util.UastExpressionUtils; import org.jetbrains.uast.util.UastExpressionUtils;
import org.jetbrains.uast.visitor.AbstractUastVisitor; import org.jetbrains.uast.visitor.AbstractUastVisitor;
@@ -1073,7 +1059,6 @@ public class ConstantEvaluator {
private final PsiVariable mVariable; private final PsiVariable mVariable;
private final UElement mEndAt; private final UElement mEndAt;
private final JavaContext mContext;
private final ConstantEvaluator mConstantEvaluator; private final ConstantEvaluator mConstantEvaluator;
private boolean mDone = false; private boolean mDone = false;
@@ -1092,7 +1077,6 @@ public class ConstantEvaluator {
mEndAt = endAt; mEndAt = endAt;
UExpression initializer = context.getUastContext().getInitializerBody(variable); UExpression initializer = context.getUastContext().getInitializerBody(variable);
mLastAssignment = initializer; mLastAssignment = initializer;
mContext = context;
mConstantEvaluator = constantEvaluator; mConstantEvaluator = constantEvaluator;
if (initializer != null && constantEvaluator != null) { if (initializer != null && constantEvaluator != null) {
mCurrentValue = constantEvaluator.evaluate(initializer); mCurrentValue = constantEvaluator.evaluate(initializer);
@@ -1112,7 +1096,7 @@ public class ConstantEvaluator {
@Override @Override
public boolean visitElement(UElement node) { public boolean visitElement(UElement node) {
if (!(node instanceof UBlockExpression)) { if (elementHasLevel(node)) {
mCurrentLevel++; mCurrentLevel++;
} }
if (node.equals(mEndAt)) { if (node.equals(mEndAt)) {
@@ -1123,23 +1107,23 @@ public class ConstantEvaluator {
@Override @Override
public boolean visitVariable(UVariable node) { public boolean visitVariable(UVariable node) {
if (mVariableLevel < 0 && node.equals(mVariable)) { if (mVariableLevel < 0 && node.getPsi().isEquivalentTo(mVariable)) {
mVariableLevel = mCurrentLevel; mVariableLevel = mCurrentLevel;
} }
return super.visitVariable(node); return super.visitVariable(node);
} }
@Override @Override
public void afterVisitBinaryExpression(UBinaryExpression node) { public void afterVisitBinaryExpression(UBinaryExpression node) {
if (!mDone if (!mDone
&& node.getOperator() instanceof UastBinaryOperator.AssignOperator && node.getOperator() instanceof UastBinaryOperator.AssignOperator
&& mVariableLevel >= 0) { && mVariableLevel >= 0) {
UExpression leftOperand = node.getLeftOperand(); UExpression leftOperand = node.getLeftOperand();
UastBinaryOperator operator = node.getOperator(); UastBinaryOperator operator = node.getOperator();
if (!(operator instanceof UastBinaryOperator.AssignOperator) if (!(operator instanceof UastBinaryOperator.AssignOperator)
|| !(leftOperand instanceof UResolvable)) { || !(leftOperand instanceof UResolvable)) {
return; return;
} }
@@ -1148,39 +1132,38 @@ public class ConstantEvaluator {
return; return;
} }
// Stop search if we see an assignment inside some conditional or loop statement. // Last assignment is unknown if we see an assignment inside
if (mCurrentLevel > mVariableLevel) { // some conditional or loop statement.
if (mCurrentLevel > mVariableLevel + 1) {
mLastAssignment = null; mLastAssignment = null;
mCurrentValue = null; mCurrentValue = null;
mDone = true; return;
} }
UExpression rightOperand = node.getRightOperand(); UExpression rightOperand = node.getRightOperand();
ConstantEvaluator constantEvaluator = mConstantEvaluator; ConstantEvaluator constantEvaluator = mConstantEvaluator;
Object newExpression = (constantEvaluator != null) mCurrentValue = (constantEvaluator != null)
? constantEvaluator.evaluate(rightOperand) ? constantEvaluator.evaluate(rightOperand)
: null; : null;
mLastAssignment = rightOperand;
//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;
}
} }
super.afterVisitBinaryExpression(node);
} }
@Override @Override
public void afterVisitElement(UElement node) { public void afterVisitElement(UElement node) {
if (!(node instanceof UBlockExpression)) { if (elementHasLevel(node)) {
mCurrentLevel--; mCurrentLevel--;
} }
super.afterVisitElement(node); 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) { 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) { public boolean isSuppressedWithComment(@NonNull PsiElement scope, @NonNull Issue issue) {
@@ -589,7 +589,7 @@ public class ResourceEvaluator {
if (mEvaluator == null) { if (mEvaluator == null) {
return null; return null;
} }
for (PsiAnnotation annotation : mEvaluator.getAllAnnotations(owner, true)) { for (PsiAnnotation annotation : mEvaluator.getAllAnnotations(owner)) {
String signature = annotation.getQualifiedName(); String signature = annotation.getQualifiedName();
if (signature == null) { if (signature == null) {
continue; 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.NonNull;
import com.android.annotations.Nullable; 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.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.Category;
import com.android.tools.klint.detector.api.ConstantEvaluator; import com.android.tools.klint.detector.api.ConstantEvaluator;
import com.android.tools.klint.detector.api.Detector; 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.Implementation;
import com.android.tools.klint.detector.api.Issue; import com.android.tools.klint.detector.api.Issue;
import com.android.tools.klint.detector.api.JavaContext; 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.Lists;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import com.intellij.psi.JavaElementVisitor;
import com.intellij.psi.PsiAnnotation; import com.intellij.psi.PsiAnnotation;
import com.intellij.psi.PsiAnnotationMemberValue; import com.intellij.psi.PsiAnnotationMemberValue;
import com.intellij.psi.PsiAnnotationOwner; import com.intellij.psi.PsiAnnotationOwner;
import com.intellij.psi.PsiArrayInitializerMemberValue; import com.intellij.psi.PsiArrayInitializerMemberValue;
import com.intellij.psi.PsiArrayType; import com.intellij.psi.PsiArrayType;
import com.intellij.psi.PsiAssignmentExpression;
import com.intellij.psi.PsiClass; import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiClassType; import com.intellij.psi.PsiClassType;
import com.intellij.psi.PsiCodeBlock;
import com.intellij.psi.PsiConditionalExpression;
import com.intellij.psi.PsiDeclarationStatement; import com.intellij.psi.PsiDeclarationStatement;
import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiExpression; import com.intellij.psi.PsiExpression;
import com.intellij.psi.PsiExpressionStatement;
import com.intellij.psi.PsiField; import com.intellij.psi.PsiField;
import com.intellij.psi.PsiJavaCodeReferenceElement; import com.intellij.psi.PsiJavaCodeReferenceElement;
import com.intellij.psi.PsiLiteral; import com.intellij.psi.PsiLiteral;
import com.intellij.psi.PsiLocalVariable; import com.intellij.psi.PsiLocalVariable;
import com.intellij.psi.PsiMethod; import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiMethodCallExpression;
import com.intellij.psi.PsiModifierList; import com.intellij.psi.PsiModifierList;
import com.intellij.psi.PsiModifierListOwner; import com.intellij.psi.PsiModifierListOwner;
import com.intellij.psi.PsiNameValuePair; import com.intellij.psi.PsiNameValuePair;
import com.intellij.psi.PsiParameter; import com.intellij.psi.PsiParameter;
import com.intellij.psi.PsiParenthesizedExpression;
import com.intellij.psi.PsiReference;
import com.intellij.psi.PsiReferenceExpression; 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.PsiType;
import com.intellij.psi.PsiTypeCastExpression;
import com.intellij.psi.PsiVariable; import com.intellij.psi.PsiVariable;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiTreeUtil; 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.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
@@ -117,11 +124,11 @@ import java.util.Set;
/** /**
* Checks annotations to make sure they are valid * 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( public static final Implementation IMPLEMENTATION = new Implementation(
AnnotationDetector.class, AnnotationDetector.class,
Scope.JAVA_FILE_SCOPE); Scope.JAVA_FILE_SCOPE);
/** Placing SuppressLint on a local variable doesn't work for class-file based checks */ /** Placing SuppressLint on a local variable doesn't work for class-file based checks */
public static final Issue INSIDE_METHOD = Issue.create( public static final Issue INSIDE_METHOD = Issue.create(
@@ -217,38 +224,41 @@ public class AnnotationDetector extends Detector implements JavaPsiScanner {
*/ */
private Set<PsiElement> mWarnedFlags; private Set<PsiElement> mWarnedFlags;
@Nullable
@Override @Override
public List<Class<? extends PsiElement>> getApplicablePsiTypes() { public List<Class<? extends UElement>> getApplicableUastTypes() {
List<Class<? extends PsiElement>> types = new ArrayList<Class<? extends PsiElement>>(2); List<Class<? extends UElement>> types = new ArrayList<Class<? extends UElement>>(2);
types.add(PsiAnnotation.class); types.add(UAnnotation.class);
types.add(PsiSwitchStatement.class); types.add(USwitchExpression.class);
return types; return types;
} }
@Nullable @Nullable
@Override @Override
public JavaElementVisitor createPsiVisitor(@NonNull JavaContext context) { public UastVisitor createUastVisitor(@NonNull JavaContext context) {
return new AnnotationChecker(context); return new AnnotationChecker(context);
} }
private class AnnotationChecker extends JavaElementVisitor { private class AnnotationChecker extends AbstractUastVisitor {
private final JavaContext mContext; private final JavaContext mContext;
public AnnotationChecker(JavaContext context) { private AnnotationChecker(JavaContext context) {
mContext = context; mContext = context;
} }
@Override @Override
public void visitAnnotation(PsiAnnotation annotation) { public boolean visitAnnotation(UAnnotation annotation) {
String type = annotation.getQualifiedName(); String type = annotation.getQualifiedName();
if (type == null || type.startsWith("java.lang.")) { if (type == null || type.startsWith("java.lang.")) {
return; return false;
} }
if (FQCN_SUPPRESS_LINT.equals(type)) { if (FQCN_SUPPRESS_LINT.equals(type)) {
PsiAnnotationOwner owner = annotation.getOwner(); PsiAnnotationOwner owner = annotation.getOwner();
if (owner == null) { if (owner == null) {
return; return false;
} }
if (owner instanceof PsiModifierList) { if (owner instanceof PsiModifierList) {
PsiElement parent = ((PsiModifierList) owner).getParent(); PsiElement parent = ((PsiModifierList) owner).getParent();
@@ -256,10 +266,10 @@ public class AnnotationDetector extends Detector implements JavaPsiScanner {
if (!(parent instanceof PsiDeclarationStatement if (!(parent instanceof PsiDeclarationStatement
|| parent instanceof PsiLocalVariable || parent instanceof PsiLocalVariable
|| parent instanceof PsiParameter)) { || parent instanceof PsiParameter)) {
return; return false;
} }
} else { } else {
return; return false;
} }
PsiNameValuePair[] attributes = annotation.getParameterList().getAttributes(); PsiNameValuePair[] attributes = annotation.getParameterList().getAttributes();
if (attributes.length == 1) { if (attributes.length == 1) {
@@ -280,7 +290,7 @@ public class AnnotationDetector extends Detector implements JavaPsiScanner {
if (v instanceof String) { if (v instanceof String) {
String id = (String) v; String id = (String) v;
if (!checkSuppressLint(annotation, id)) { if (!checkSuppressLint(annotation, id)) {
return; return false;
} }
} }
} }
@@ -291,17 +301,17 @@ public class AnnotationDetector extends Detector implements JavaPsiScanner {
if (CHECK_RESULT_ANNOTATION.equals(type)) { if (CHECK_RESULT_ANNOTATION.equals(type)) {
// Check that the return type of this method is not void! // Check that the return type of this method is not void!
if (annotation.getParent() instanceof PsiModifierList if (annotation.getParent() instanceof PsiModifierList
&& annotation.getParent().getParent() instanceof PsiMethod) { && annotation.getParent().getParent() instanceof PsiMethod) {
PsiMethod method = (PsiMethod) annotation.getParent().getParent(); PsiMethod method = (PsiMethod) annotation.getParent().getParent();
if (!method.isConstructor() if (!method.isConstructor()
&& PsiType.VOID.equals(method.getReturnType())) { && PsiType.VOID.equals(method.getReturnType())) {
mContext.report(ANNOTATION_USAGE, annotation, mContext.report(ANNOTATION_USAGE, annotation.getPsi(),
mContext.getLocation(annotation), mContext.getLocation(annotation.getPsi()),
"@CheckResult should not be specified on `void` methods"); "@CheckResult should not be specified on `void` methods");
} }
} }
} else if (INT_RANGE_ANNOTATION.equals(type) } 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. // Check that the annotated element's type is int or long.
// Also make sure that from <= to. // Also make sure that from <= to.
boolean invalid; boolean invalid;
@@ -315,14 +325,14 @@ public class AnnotationDetector extends Detector implements JavaPsiScanner {
checkTargetType(annotation, TYPE_FLOAT, TYPE_DOUBLE, true); checkTargetType(annotation, TYPE_FLOAT, TYPE_DOUBLE, true);
double from = getDoubleAttribute(annotation, ATTR_FROM, double from = getDoubleAttribute(annotation, ATTR_FROM,
Double.NEGATIVE_INFINITY); Double.NEGATIVE_INFINITY);
double to = getDoubleAttribute(annotation, ATTR_TO, double to = getDoubleAttribute(annotation, ATTR_TO,
Double.POSITIVE_INFINITY); Double.POSITIVE_INFINITY);
invalid = from > to; invalid = from > to;
} }
if (invalid) { if (invalid) {
mContext.report(ANNOTATION_USAGE, annotation, mContext.getLocation(annotation), mContext.report(ANNOTATION_USAGE, annotation.getPsi(), mContext.getLocation(annotation.getPsi()),
"Invalid range: the `from` attribute must be less than " "Invalid range: the `from` attribute must be less than "
+ "the `to` attribute"); + "the `to` attribute");
} }
} else if (SIZE_ANNOTATION.equals(type)) { } 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 max = getLongAttribute(annotation, ATTR_MAX, Long.MAX_VALUE);
long multiple = getLongAttribute(annotation, ATTR_MULTIPLE, 1); long multiple = getLongAttribute(annotation, ATTR_MULTIPLE, 1);
if (min > max) { if (min > max) {
mContext.report(ANNOTATION_USAGE, annotation, mContext.getLocation(annotation), mContext.report(ANNOTATION_USAGE, annotation.getPsi(), mContext.getLocation(annotation.getPsi()),
"Invalid size range: the `min` attribute must be less than " "Invalid size range: the `min` attribute must be less than "
+ "the `max` attribute"); + "the `max` attribute");
} else if (multiple < 1) { } else if (multiple < 1) {
mContext.report(ANNOTATION_USAGE, annotation, mContext.getLocation(annotation), mContext.report(ANNOTATION_USAGE, annotation.getPsi(), mContext.getLocation(annotation.getPsi()),
"The size multiple must be at least 1"); "The size multiple must be at least 1");
} else if (exact < 0 && exact != unset || min < 0 && min != Long.MIN_VALUE) { } else if (exact < 0 && exact != unset || min < 0 && min != Long.MIN_VALUE) {
mContext.report(ANNOTATION_USAGE, annotation, mContext.getLocation(annotation), mContext.report(ANNOTATION_USAGE, annotation.getPsi(), mContext.getLocation(annotation.getPsi()),
"The size can't be negative"); "The size can't be negative");
} }
} else if (COLOR_INT_ANNOTATION.equals(type) || (PX_ANNOTATION.equals(type))) { } else if (COLOR_INT_ANNOTATION.equals(type) || (PX_ANNOTATION.equals(type))) {
// Check that ColorInt applies to the right 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 // Make sure IntDef constants are unique
ensureUniqueValues(annotation); ensureUniqueValues(annotation);
} else if (PERMISSION_ANNOTATION.equals(type) || } else if (PERMISSION_ANNOTATION.equals(type) ||
PERMISSION_ANNOTATION_READ.equals(type) || PERMISSION_ANNOTATION_READ.equals(type) ||
PERMISSION_ANNOTATION_WRITE.equals(type)) { PERMISSION_ANNOTATION_WRITE.equals(type)) {
// Check that if there are no arguments, this is specified on a parameter, // Check that if there are no arguments, this is specified on a parameter,
// and conversely, on methods and fields there is a valid argument. // and conversely, on methods and fields there is a valid argument.
if (annotation.getParent() instanceof PsiModifierList if (annotation.getParent() instanceof PsiModifierList
@@ -378,14 +388,14 @@ public class AnnotationDetector extends Detector implements JavaPsiScanner {
} }
if (set == 0) { if (set == 0) {
mContext.report(ANNOTATION_USAGE, annotation, mContext.report(ANNOTATION_USAGE, annotation.getPsi(),
mContext.getLocation(annotation), mContext.getLocation(annotation.getPsi()),
"For methods, permission annotation should specify one " "For methods, permission annotation should specify one "
+ "of `value`, `anyOf` or `allOf`"); + "of `value`, `anyOf` or `allOf`");
} else if (set > 1) { } else if (set > 1) {
mContext.report(ANNOTATION_USAGE, annotation, mContext.report(ANNOTATION_USAGE, annotation.getPsi(),
mContext.getLocation(annotation), mContext.getLocation(annotation.getPsi()),
"Only specify one of `value`, `anyOf` or `allOf`"); "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, 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) { } else if (parent instanceof PsiMethod) {
PsiMethod method = (PsiMethod) parent; PsiMethod method = (PsiMethod) parent;
type = method.isConstructor() type = method.isConstructor()
? mContext.getEvaluator().getClassType(method.getContainingClass()) ? mContext.getEvaluator().getClassType(method.getContainingClass())
: method.getReturnType(); : method.getReturnType();
} else if (parent instanceof PsiVariable) { } else if (parent instanceof PsiVariable) {
// Field or local variable or parameter // Field or local variable or parameter
type = ((PsiVariable)parent).getType(); type = ((PsiVariable)parent).getType();
@@ -459,8 +471,7 @@ public class AnnotationDetector extends Detector implements JavaPsiScanner {
if (classType.getParameters().length == 1) { if (classType.getParameters().length == 1) {
PsiClass resolved = classType.resolve(); PsiClass resolved = classType.resolve();
if (resolved != null && if (resolved != null &&
mContext.getEvaluator().implementsInterface(resolved, InheritanceUtil.isInheritor(resolved, false, "java.util.Collection")) {
"java.util.Collection", false)) {
type = classType.getParameters()[0]; type = classType.getParameters()[0];
} }
} }
@@ -469,10 +480,10 @@ public class AnnotationDetector extends Detector implements JavaPsiScanner {
String typeName = type.getCanonicalText(); String typeName = type.getCanonicalText();
if (!typeName.equals(type1) 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 // Autoboxing? You can put @DrawableRes on a java.lang.Integer for example
if (typeName.equals(getAutoBoxedType(type1)) if (typeName.equals(getAutoBoxedType(type1))
|| type2 != null && typeName.equals(getAutoBoxedType(type2))) { || type2 != null && typeName.equals(getAutoBoxedType(type2))) {
return; return;
} }
@@ -490,14 +501,35 @@ public class AnnotationDetector extends Detector implements JavaPsiScanner {
} }
@Override @Override
public void visitSwitchStatement(PsiSwitchStatement statement) { public boolean visitSwitchExpression(USwitchExpression switchExpression) {
PsiExpression condition = statement.getExpression(); UExpression condition = switchExpression.getExpression();
if (condition != null && PsiType.INT.equals(condition.getType())) { if (condition != null && PsiType.INT.equals(condition.getExpressionType())) {
PsiAnnotation annotation = findIntDef(condition); PsiAnnotation annotation = findIntDefAnnotation(condition);
if (annotation != null) { 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 * with a given node
*/ */
@Nullable @Nullable
private PsiAnnotation findIntDef(@NonNull PsiElement node) { private PsiAnnotation findIntDefAnnotation(@NonNull UExpression expression) {
if (node instanceof PsiReferenceExpression) { if (expression instanceof UReferenceExpression) {
PsiElement resolved = ((PsiReference) node).resolve(); PsiElement resolved = ((UReferenceExpression) expression).resolve();
if (resolved instanceof PsiModifierListOwner) { if (resolved instanceof PsiModifierListOwner) {
PsiAnnotation[] annotations = mContext.getEvaluator().getAllAnnotations( PsiAnnotation[] annotations = mContext.getEvaluator().getAllAnnotations(
(PsiModifierListOwner)resolved, true); (PsiModifierListOwner)resolved);
PsiAnnotation annotation = SupportAnnotationDetector.findIntDef( PsiAnnotation annotation = SupportAnnotationDetector.findIntDef(
filterRelevantAnnotations(annotations)); filterRelevantAnnotations(annotations));
if (annotation != null) { if (annotation != null) {
@@ -520,217 +553,50 @@ public class AnnotationDetector extends Detector implements JavaPsiScanner {
if (resolved instanceof PsiLocalVariable) { if (resolved instanceof PsiLocalVariable) {
PsiLocalVariable variable = (PsiLocalVariable) resolved; PsiLocalVariable variable = (PsiLocalVariable) resolved;
PsiStatement statement = PsiTreeUtil.getParentOfType(node, PsiStatement.class, UExpression lastAssignment = UastLintUtils.findLastAssignment(variable,
false); expression, mContext);
if (statement != null) {
PsiStatement prev = PsiTreeUtil.getPrevSiblingOfType(statement, if(lastAssignment != null) {
PsiStatement.class); return findIntDefAnnotation(lastAssignment);
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);
}
} }
} }
} else if (node instanceof PsiMethodCallExpression) {
PsiMethod method = ((PsiMethodCallExpression) node).resolveMethod(); } else if (expression instanceof UCallExpression) {
PsiMethod method = ((UCallExpression) expression).resolve();
if (method != null) { if (method != null) {
PsiAnnotation[] annotations = mContext.getEvaluator().getAllAnnotations(method, true); PsiAnnotation[] annotations = mContext.getEvaluator()
.getAllAnnotations(method);
PsiAnnotation annotation = SupportAnnotationDetector.findIntDef( PsiAnnotation annotation = SupportAnnotationDetector.findIntDef(
filterRelevantAnnotations(annotations)); filterRelevantAnnotations(annotations));
if (annotation != null) { if (annotation != null) {
return annotation; return annotation;
} }
} }
} else if (node instanceof PsiConditionalExpression) { } else if (expression instanceof UIfExpression) {
PsiConditionalExpression expression = (PsiConditionalExpression) node; UIfExpression ifExpression = (UIfExpression) expression;
if (expression.getThenExpression() != null) { if (ifExpression.getThenExpression() != null) {
PsiAnnotation result = findIntDef(expression.getThenExpression()); PsiAnnotation result = findIntDefAnnotation(ifExpression.getThenExpression());
if (result != null) { if (result != null) {
return result; return result;
} }
} }
if (expression.getElseExpression() != null) { if (ifExpression.getElseExpression() != null) {
PsiAnnotation result = findIntDef(expression.getElseExpression()); PsiAnnotation result = findIntDefAnnotation(ifExpression.getElseExpression());
if (result != null) { if (result != null) {
return result; return result;
} }
} }
} else if (node instanceof PsiTypeCastExpression) { } else if (expression instanceof JavaUTypeCastExpression) {
PsiTypeCastExpression cast = (PsiTypeCastExpression) node; return findIntDefAnnotation(((JavaUTypeCastExpression)expression).getOperand());
if (cast.getOperand() != null) {
return findIntDef(cast.getOperand()); } else if (expression instanceof UParenthesizedExpression) {
} return findIntDefAnnotation(((UParenthesizedExpression) expression).getExpression());
} else if (node instanceof PsiParenthesizedExpression) {
PsiParenthesizedExpression expression = (PsiParenthesizedExpression) node;
if (expression.getExpression() != null) {
return findIntDef(expression.getExpression());
}
} }
return null; 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) { private void ensureUniqueValues(@NonNull PsiAnnotation node) {
PsiAnnotationMemberValue value = node.findAttributeValue(ATTR_VALUE); PsiAnnotationMemberValue value = node.findAttributeValue(ATTR_VALUE);
if (value == null) { if (value == null) {
@@ -770,8 +636,8 @@ public class AnnotationDetector extends Detector implements JavaPsiScanner {
PsiElement prevConstant = initializers[prevIndex]; PsiElement prevConstant = initializers[prevIndex];
message = String.format( message = String.format(
"Constants `%1$s` and `%2$s` specify the same exact " "Constants `%1$s` and `%2$s` specify the same exact "
+ "value (%3$s); this is usually a cut & paste or " + "value (%3$s); this is usually a cut & paste or "
+ "merge error", + "merge error",
expression.getText(), prevConstant.getText(), expression.getText(), prevConstant.getText(),
repeatedValue.toString()); repeatedValue.toString());
location = mContext.getLocation(expression); 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 // only on field references, and class file analysis on the rest, so we allow
// annotations outside of methods only on fields // annotations outside of methods only on fields
if (issue != null && !issue.getImplementation().getScope().contains(Scope.JAVA_FILE) 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 // This issue doesn't have AST access: annotations are not
// available for local variables or parameters // available for local variables or parameters
PsiElement scope = getAnnotationScope(node); PsiElement scope = getAnnotationScope(node);
mContext.report(INSIDE_METHOD, scope, mContext.getLocation(node), String.format( mContext.report(INSIDE_METHOD, scope, mContext.getLocation(node), String.format(
"The `@SuppressLint` annotation cannot be used on a local " + "The `@SuppressLint` annotation cannot be used on a local " +
"variable with the lint check '%1$s': move out to the " + "variable with the lint check '%1$s': move out to the " +
"surrounding method", id)); "surrounding method", id));
return false; return false;
} }
return true; 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(
private static List<String> computeFieldNames(@NonNull PsiSwitchStatement node, @NonNull USwitchExpression node, Iterable<?> allowedValues) {
Iterable<?> allowedValues) {
List<String> list = Lists.newArrayList(); List<String> list = Lists.newArrayList();
for (Object o : allowedValues) { 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(); PsiElement resolved = ((PsiReferenceExpression) o).resolve();
if (resolved != null) { if (resolved != null) {
o = resolved; o = resolved;
@@ -872,17 +923,11 @@ public class AnnotationDetector extends Detector implements JavaPsiScanner {
PsiField field = (PsiField) o; PsiField field = (PsiField) o;
// Only include class name if necessary // Only include class name if necessary
String name = field.getName(); String name = field.getName();
PsiClass clz = PsiTreeUtil.getParentOfType(node, PsiClass.class, true); UClass clz = UastUtils.getParentOfType(node, UClass.class, true);
if (clz != null) { if (clz != null) {
PsiClass containingClass = field.getContainingClass(); PsiClass containingClass = field.getContainingClass();
if (containingClass != null && !containingClass.equals(clz)) { if (containingClass != null && !containingClass.equals(clz.getPsi())) {
name = containingClass.getName() + '.' + field.getName();
//if (Objects.equal(containingClass.getPackage(),
// ((ResolvedClass) resolved).getPackage())) {
// name = containingClass.getSimpleName() + '.' + field.getName();
//} else {
name = containingClass.getName() + '.' + field.getName();
//}
} }
} }
list.add('`' + name + '`'); 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.JavaEvaluator;
import com.android.tools.klint.client.api.LintDriver; import com.android.tools.klint.client.api.LintDriver;
import com.android.tools.klint.client.api.UastLintUtils; import com.android.tools.klint.client.api.UastLintUtils;
import com.android.tools.klint.detector.api.Category; import com.android.tools.klint.detector.api.*;
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.Detector.ClassScanner; 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.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.google.common.collect.Lists;
import com.intellij.psi.PsiAnnotation; import com.intellij.psi.PsiAnnotation;
import com.intellij.psi.PsiAnnotationMemberValue; import com.intellij.psi.PsiAnnotationMemberValue;
@@ -148,6 +135,7 @@ import org.w3c.dom.Element;
import org.w3c.dom.Node; import org.w3c.dom.Node;
import org.w3c.dom.NodeList; import org.w3c.dom.NodeList;
import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; 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 SDK_INT = "SDK_INT";
private static final String ANDROID_OS_BUILD_VERSION = "android/os/Build$VERSION"; 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; protected ApiLookup mApiDatabase;
private boolean mWarnedMissingDb; private boolean mWarnedMissingDb;
@@ -864,33 +854,6 @@ public class ApiDetector extends ResourceXmlDetector
continue; 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) { if (CHECK_DECLARATIONS) {
// Check types in parameter list and types of local variables // Check types in parameter list and types of local variables
List localVariables = method.localVariables; List localVariables = method.localVariables;
@@ -1493,7 +1456,7 @@ public class ApiDetector extends ResourceXmlDetector
return -1; return -1;
} }
private static void report(final ClassContext context, String message, AbstractInsnNode node, private static void report(final ClassContext context, String message, AbstractInsnNode node,
MethodNode method, String patternStart, String patternEnd, SearchHints hints) { MethodNode method, String patternStart, String patternEnd, SearchHints hints) {
int lineNumber = node != null ? ClassContext.findLineNumber(node) : -1; int lineNumber = node != null ? ClassContext.findLineNumber(node) : -1;
@@ -1647,7 +1610,7 @@ public class ApiDetector extends ResourceXmlDetector
checkField(statement, (PsiField)resolved); checkField(statement, (PsiField)resolved);
} }
} }
return super.visitImportStatement(statement); return super.visitImportStatement(statement);
} }
@@ -1657,7 +1620,7 @@ public class ApiDetector extends ResourceXmlDetector
if (resolved instanceof PsiField) { if (resolved instanceof PsiField) {
checkField(node, (PsiField)resolved); checkField(node, (PsiField)resolved);
} }
return super.visitSimpleNameReferenceExpression(node); return super.visitSimpleNameReferenceExpression(node);
} }
@@ -1666,16 +1629,16 @@ public class ApiDetector extends ResourceXmlDetector
if (UastExpressionUtils.isTypeCast(node)) { if (UastExpressionUtils.isTypeCast(node)) {
visitTypeCastExpression(node); visitTypeCastExpression(node);
} }
return super.visitBinaryExpressionWithType(node); return super.visitBinaryExpressionWithType(node);
} }
private void visitTypeCastExpression(UBinaryExpressionWithType expression) { private void visitTypeCastExpression(UBinaryExpressionWithType expression) {
UExpression operand = expression.getOperand(); UExpression operand = expression.getOperand();
PsiType operandType = operand.getExpressionType(); PsiType operandType = operand.getExpressionType();
PsiType castType = expression.getType(); PsiType castType = expression.getType();
if (castType.equals(operandType)) { if (castType.equals(operandType)) {
return; return;
} }
@@ -1690,7 +1653,7 @@ public class ApiDetector extends ResourceXmlDetector
checkCast(expression, classType, interfaceType); 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) { @NonNull PsiClassType interfaceType) {
if (classType.equals(interfaceType)) { if (classType.equals(interfaceType)) {
return; return;
@@ -1737,7 +1700,7 @@ public class ApiDetector extends ResourceXmlDetector
mContext.reportUast(UNSUPPORTED, method, location, message); mContext.reportUast(UNSUPPORTED, method, location, message);
} }
} }
return super.visitMethod(method); return super.visitMethod(method);
} }
@@ -1777,7 +1740,7 @@ public class ApiDetector extends ResourceXmlDetector
} }
} }
} }
return super.visitClass(aClass); return super.visitClass(aClass);
} }
@@ -1840,7 +1803,7 @@ public class ApiDetector extends ResourceXmlDetector
} }
} }
} }
return super.visitCallExpression(expression); return super.visitCallExpression(expression);
} }
@@ -1908,7 +1871,7 @@ public class ApiDetector extends ResourceXmlDetector
if (UastExpressionUtils.isAssignment(node)) { if (UastExpressionUtils.isAssignment(node)) {
visitAssignmentExpression(node); visitAssignmentExpression(node);
} }
return super.visitBinaryExpression(node); return super.visitBinaryExpression(node);
} }
@@ -1951,14 +1914,75 @@ public class ApiDetector extends ResourceXmlDetector
} }
for (UCatchClause catchClause : statement.getCatchClauses()) { 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()) { for (UTypeReferenceExpression typeReference : catchClause.getTypeReferences()) {
checkCatchTypeElement(statement, typeReference, typeReference.getType()); checkCatchTypeElement(statement, typeReference, typeReference.getType());
} }
} }
return super.visitTryExpression(statement); 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, private void checkCatchTypeElement(@NonNull UTryExpression statement,
@NonNull UTypeReferenceExpression typeReference, @NonNull UTypeReferenceExpression typeReference,
@Nullable PsiType type) { @Nullable PsiType type) {
@@ -1981,19 +2005,10 @@ public class ApiDetector extends ResourceXmlDetector
return; return;
} }
Location location; Location location = mContext.getUastLocation(typeReference);
location = mContext.getUastLocation(typeReference);
String fqcn = resolved.getQualifiedName(); String fqcn = resolved.getQualifiedName();
String message = String.format("Class requires API level %1$d (current min is %2$d): %3$s", api, minSdk, fqcn); 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);
}
mContext.report(UNSUPPORTED, location, message); mContext.report(UNSUPPORTED, location, message);
} }
} }
@@ -2356,55 +2371,97 @@ public class ApiDetector extends ResourceXmlDetector
} }
} }
private static boolean isPrecededByVersionCheckExit( private static class VersionCheckWithExitFinder extends AbstractUastVisitor {
UElement element, int api, JavaContext context) { private final UExpression mExpression;
UElement current = UastUtils.getParentOfType(element, UExpression.class); private final UElement mEndElement;
if (current != null) { private final int mApi;
UElement prev = getPreviousStatement(current); private final JavaContext mContext;
if (prev == null) {
//noinspection unchecked private boolean mFound = false;
current = UastUtils.getParentOfType(current, UExpression.class, true, private boolean mDone = false;
UMethod.class, UClass.class);
} else { public VersionCheckWithExitFinder(UExpression expression, UElement endElement,
current = prev; int api, JavaContext context) {
} mExpression = expression;
mEndElement = endElement;
mApi = api;
mContext = context;
} }
while (current != null) {
if (current instanceof UIfExpression) { @Override
UIfExpression ifStatement = (UIfExpression)current; public boolean visitElement(UElement node) {
UExpression thenBranch = ifStatement.getThenExpression(); if (mDone) {
UExpression elseBranch = ifStatement.getElseExpression(); return true;
if (thenBranch != null) { }
Boolean level = isVersionCheckConditional(api, thenBranch, ifStatement, context);
//noinspection VariableNotUsedInsideIf if (node.equals(mEndElement)) {
if (level != null) { mDone = true;
// See if the body does an immediate return }
if (isUnconditionalReturn(thenBranch)) {
return true; return mDone || !mExpression.equals(node);
} }
}
} @Override
if (elseBranch != null) { public boolean visitIfExpression(UIfExpression ifStatement) {
Boolean level = isVersionCheckConditional(api, elseBranch, ifStatement, context);
//noinspection VariableNotUsedInsideIf if (mDone) {
if (level != null) { return true;
if (isUnconditionalReturn(elseBranch)) { }
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) { if (elseBranch != null) {
//noinspection unchecked Boolean level = isVersionCheckConditional(mApi, elseBranch, ifStatement, mContext);
current = UastUtils.getParentOfType(current, UExpression.class, true, //noinspection VariableNotUsedInsideIf
UMethod.class, UClass.class); if (level != null) {
if (current == null) { if (isUnconditionalReturn(elseBranch)) {
return false; 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; return false;
@@ -2420,16 +2477,6 @@ public class ApiDetector extends ResourceXmlDetector
return statement instanceof UReturnExpression; 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( public static boolean isWithinVersionCheckConditional(
UElement element, int api, JavaContext context) { UElement element, int api, JavaContext context) {
UElement current = element.getContainingElement(); UElement current = element.getContainingElement();
@@ -2453,7 +2500,7 @@ public class ApiDetector extends ResourceXmlDetector
@Nullable @Nullable
private static Boolean isVersionCheckConditional( private static Boolean isVersionCheckConditional(
int api, int api,
UElement prev, UElement prev,
UIfExpression ifStatement, UIfExpression ifStatement,
@NonNull JavaContext context) { @NonNull JavaContext context) {
@@ -2543,7 +2590,7 @@ public class ApiDetector extends ResourceXmlDetector
// if (SDK_INT < ICE_CREAM_SANDWICH) { ... } else { <call> } // if (SDK_INT < ICE_CREAM_SANDWICH) { ... } else { <call> }
return level >= api && fromElse; return level >= api && fromElse;
} }
else if (tokenType == UastBinaryOperator.EQUALS else if (tokenType == UastBinaryOperator.EQUALS
|| tokenType == UastBinaryOperator.IDENTITY_EQUALS) { || tokenType == UastBinaryOperator.IDENTITY_EQUALS) {
// if (SDK_INT == ICE_CREAM_SANDWICH) { <call> } else { } // if (SDK_INT == ICE_CREAM_SANDWICH) { <call> } else { }
return level >= api && fromThen; 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())) { && (ifStatement != null && prev == ifStatement.getThenExpression())) {
if (isAndedWithConditional(ifStatement.getCondition(), api, prev)) { if (isAndedWithConditional(ifStatement.getCondition(), api, prev)) {
return true; return true;
@@ -346,8 +346,7 @@ public class CleanupDetector extends Detector implements Detector.UastScanner {
recycleName); recycleName);
} }
UElement locationNode = node instanceof UCallExpression ? UElement locationNode = node.getMethodIdentifier();
((UCallExpression) node).getMethodIdentifier() : node;
if (locationNode == null) { if (locationNode == null) {
locationNode = node; locationNode = node;
} }
@@ -507,7 +506,12 @@ public class CleanupDetector extends Detector implements Detector.UastScanner {
protected boolean isCleanupCall(@NonNull UCallExpression call) { protected boolean isCleanupCall(@NonNull UCallExpression call) {
if (isEditorApplyMethodCall(mContext, call) if (isEditorApplyMethodCall(mContext, call)
|| isEditorCommitMethodCall(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) { if (operand != null) {
PsiElement resolved = UastUtils.tryResolve(operand); PsiElement resolved = UastUtils.tryResolve(operand);
//noinspection SuspiciousMethodCalls //noinspection SuspiciousMethodCalls
@@ -515,7 +519,7 @@ public class CleanupDetector extends Detector implements Detector.UastScanner {
return true; return true;
} else if (resolved instanceof PsiMethod } else if (resolved instanceof PsiMethod
&& operand instanceof UCallExpression && operand instanceof UCallExpression
&& isCommittedInChainedCalls(mContext, && isEditorCommittedInChainedCalls(mContext,
(UCallExpression) operand)) { (UCallExpression) operand)) {
// Check that the target of the committed chains is the // Check that the target of the committed chains is the
// right variable! // right variable!
@@ -20,17 +20,18 @@ import com.android.annotations.NonNull;
import com.android.annotations.Nullable; import com.android.annotations.Nullable;
import com.android.tools.klint.detector.api.Category; import com.android.tools.klint.detector.api.Category;
import com.android.tools.klint.detector.api.Detector; 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.Implementation;
import com.android.tools.klint.detector.api.Issue; import com.android.tools.klint.detector.api.Issue;
import com.android.tools.klint.detector.api.JavaContext; import com.android.tools.klint.detector.api.JavaContext;
import com.android.tools.klint.detector.api.Location; import com.android.tools.klint.detector.api.Location;
import com.android.tools.klint.detector.api.Scope; import com.android.tools.klint.detector.api.Scope;
import com.android.tools.klint.detector.api.Severity; import com.android.tools.klint.detector.api.Severity;
import com.intellij.psi.JavaElementVisitor;
import com.intellij.psi.PsiComment; import org.jetbrains.uast.UElement;
import com.intellij.psi.PsiElement; import org.jetbrains.uast.UComment;
import com.intellij.psi.PsiLiteralExpression; import org.jetbrains.uast.UFile;
import org.jetbrains.uast.visitor.AbstractUastVisitor;
import org.jetbrains.uast.visitor.UastVisitor;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@@ -38,7 +39,7 @@ import java.util.List;
/** /**
* Looks for issues in Java comments * 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 String STOPSHIP_COMMENT = "STOPSHIP"; //$NON-NLS-1$
private static final Implementation IMPLEMENTATION = new Implementation( 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$ 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 /** 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; private static final boolean USE_AST = false;
@@ -83,20 +84,20 @@ public class CommentDetector extends Detector implements JavaPsiScanner {
public CommentDetector() { public CommentDetector() {
} }
@Nullable
@Override @Override
public List<Class<? extends PsiElement>> getApplicablePsiTypes() { public List<Class<? extends UElement>> getApplicableUastTypes() {
if (USE_AST) { if (USE_AST) {
return Collections.<Class<? extends PsiElement>>singletonList( return Collections.<Class<? extends UElement>>singletonList(
PsiLiteralExpression.class); UFile.class);
} else { } else {
return null; return null;
} }
} }
@Nullable
@Override @Override
public JavaElementVisitor createPsiVisitor(@NonNull JavaContext context) { public UastVisitor createUastVisitor(@NonNull JavaContext context) {
// Lombok does not generate comment nodes for block and line comments, only for
// javadoc comments!
if (USE_AST) { if (USE_AST) {
return new CommentChecker(context); return new CommentChecker(context);
} else { } 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; private final JavaContext mContext;
public CommentChecker(JavaContext context) { public CommentChecker(JavaContext context) {
@@ -143,16 +144,19 @@ public class CommentDetector extends Detector implements JavaPsiScanner {
} }
@Override @Override
public void visitComment(PsiComment comment) { public boolean visitFile(UFile node) {
String contents = comment.getText(); for (UComment comment : node.getAllCommentsInFile()) {
checkComment(mContext, comment, contents, comment.getTextRange().getStartOffset(), 0, String contents = comment.getText();
contents.length()); checkComment(mContext, comment, contents,
comment.getPsi().getTextRange().getStartOffset(), 0, contents.length());
}
return super.visitFile(node);
} }
} }
private static void checkComment( private static void checkComment(
@NonNull JavaContext context, @NonNull JavaContext context,
@Nullable PsiComment node, @Nullable UComment node,
@NonNull String source, @NonNull String source,
int offset, int offset,
int start, int start,
@@ -164,24 +168,31 @@ public class CommentDetector extends Detector implements JavaPsiScanner {
if (prev == '\\') { if (prev == '\\') {
if (c == 'u' || c == 'U') { if (c == 'u' || c == 'U') {
if (source.regionMatches(true, i - 1, ESCAPE_STRING, if (source.regionMatches(true, i - 1, ESCAPE_STRING,
0, ESCAPE_STRING.length())) { 0, ESCAPE_STRING.length())) {
Location location = Location.create(context.file, source, 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, context.report(EASTER_EGG, node, location,
"Code might be hidden here; found unicode escape sequence " + "Code might be hidden here; found unicode escape sequence " +
"which is interpreted as comment end, compiled code follows"); "which is interpreted as comment end, compiled code follows");
} }
} else { } else {
i++; i++;
} }
} else if (prev == 'S' && c == 'T' && } 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?? // TODO: Only flag this issue in release mode??
Location location = Location.create(context.file, source, Location location;
offset + i - 1, offset + i - 1 + STOPSHIP_COMMENT.length()); 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, context.report(STOP_SHIP, node, location,
"`STOPSHIP` comment found; points to code which must be fixed prior " + "`STOPSHIP` comment found; points to code which must be fixed prior " +
"to release"); "to release");
} }
} }
} }
@@ -42,7 +42,7 @@ import com.intellij.psi.PsiStatement;
import com.intellij.psi.PsiWhiteSpace; import com.intellij.psi.PsiWhiteSpace;
import org.jetbrains.uast.UArrayAccessExpression; import org.jetbrains.uast.UArrayAccessExpression;
import org.jetbrains.uast.UBinaryExpression; import org.jetbrains.uast.*;
import org.jetbrains.uast.UCallExpression; import org.jetbrains.uast.UCallExpression;
import org.jetbrains.uast.UElement; import org.jetbrains.uast.UElement;
import org.jetbrains.uast.UExpression; import org.jetbrains.uast.UExpression;
@@ -52,6 +52,7 @@ import org.jetbrains.uast.UQualifiedReferenceExpression;
import org.jetbrains.uast.UastUtils; import org.jetbrains.uast.UastUtils;
import org.jetbrains.uast.expressions.UReferenceExpression; import org.jetbrains.uast.expressions.UReferenceExpression;
import org.jetbrains.uast.util.UastExpressionUtils; import org.jetbrains.uast.util.UastExpressionUtils;
import org.jetbrains.uast.visitor.AbstractUastVisitor;
import org.jetbrains.uast.visitor.UastVisitor; import org.jetbrains.uast.visitor.UastVisitor;
import java.util.Collections; import java.util.Collections;
@@ -165,137 +166,201 @@ public class CutPasteDetector extends Detector implements Detector.UastScanner {
@Nullable @Nullable
private static String getLhs(@NonNull UCallExpression call) { private static String getLhs(@NonNull UCallExpression call) {
UElement parent = skipParentheses(call.getContainingElement()); UElement parent = call.getContainingElement();
if (parent != null && UastExpressionUtils.isTypeCast(parent)) { 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(); 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; return null;
} }
static boolean isReachableFrom( static boolean isReachableFrom(
@NonNull PsiMethod method, @NonNull UMethod 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 UElement from, @NonNull UElement from,
@NonNull UElement to) { @NonNull UElement to) {
//TODO ReachabilityVisitor visitor = new ReachabilityVisitor(from, to);
return false; method.accept(visitor);
return visitor.isReachable();
} }
@Nullable private static class ReachabilityVisitor extends AbstractUastVisitor {
static PsiElement next(
@NonNull PsiMethod method,
@NonNull PsiElement curr,
@NonNull PsiElement target,
@Nullable PsiElement prev) {
if (curr instanceof PsiMethod) { private final UElement mFrom;
return null; 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(); @Override
if (curr instanceof PsiContinueStatement) { public boolean visitElement(UElement node) {
PsiStatement continuedStatement = ((PsiContinueStatement) curr) if (mIsFinished || mBreakedExpression != null || mContinuedExpression != null) {
.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)) {
return true; 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 ---- // ---- Implements UastScanner ----
private static final String NOTIFICATION_CLASS = "Notification"; private static final String NOTIFICATION_CLASS = "android.app.Notification";
private static final String NOTIFICATION_BUILDER_CLASS = "Notification.Builder"; private static final String NOTIFICATION_BUILDER_CLASS = "android.app.Notification.Builder";
private static final String NOTIFICATION_COMPAT_BUILDER_CLASS = "NotificationCompat.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 SET_SMALL_ICON = "setSmallIcon";
private static final String ON_CREATE_OPTIONS_MENU = "onCreateOptionsMenu"; 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)) { if (!(resolved instanceof PsiClass)) {
return; return;
} }
String typeName = ((PsiClass) resolved).getName(); String typeName = ((PsiClass) resolved).getQualifiedName();
if (NOTIFICATION_CLASS.equals(typeName)) { if (NOTIFICATION_CLASS.equals(typeName)) {
List<UExpression> args = node.getValueArguments(); List<UExpression> args = node.getValueArguments();
if (args.size() == 3) { if (args.size() == 3) {
@@ -41,6 +41,7 @@ import com.intellij.psi.PsiType;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.uast.UClass; import org.jetbrains.uast.UClass;
import org.jetbrains.uast.UElement;
import org.jetbrains.uast.UField; import org.jetbrains.uast.UField;
import org.jetbrains.uast.UVariable; import org.jetbrains.uast.UVariable;
import org.jetbrains.uast.visitor.AbstractUastVisitor; import org.jetbrains.uast.visitor.AbstractUastVisitor;
@@ -127,7 +128,7 @@ public class LeakDetector extends Detector implements Detector.UastScanner {
if (isLeakCandidate(cls, mContext.getEvaluator())) { if (isLeakCandidate(cls, mContext.getEvaluator())) {
String message = "Do not place Android context classes in static fields; " String message = "Do not place Android context classes in static fields; "
+ "this is a memory leak (and also breaks Instant Run)"; + "this is a memory leak (and also breaks Instant Run)";
report(field, modifierList, message); report(field, message);
} }
} else { } else {
// User application object -- look to see if that one itself has // 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 `" + "`" + referenced.getName() + "` pointing to `"
+ innerCls.getName() + "`); " + innerCls.getName() + "`); "
+ "this is a memory leak (and also breaks Instant Run)"; + "this is a memory leak (and also breaks Instant Run)";
report(field, modifierList, message); report(field, message);
break; break;
} }
} }
@@ -170,11 +171,8 @@ public class LeakDetector extends Detector implements Detector.UastScanner {
} }
} }
private void report(@NonNull PsiField field, @NonNull PsiModifierList modifierList, private void report(@NonNull UElement element, @NonNull String message) {
@NonNull String message) { mContext.report(ISSUE, element, mContext.getUastLocation(element), message);
Location location = mContext.getLocation(
modifierList.getTextRange().getLength() > 0 ? modifierList : field);
mContext.report(ISSUE, field, location, message);
} }
} }
@@ -37,19 +37,7 @@ import com.intellij.psi.PsiNamedElement;
import com.intellij.psi.PsiParameterList; import com.intellij.psi.PsiParameterList;
import com.intellij.psi.PsiVariable; import com.intellij.psi.PsiVariable;
import org.jetbrains.uast.UBinaryExpressionWithType; import org.jetbrains.uast.*;
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.visitor.UastVisitor; import org.jetbrains.uast.visitor.UastVisitor;
import java.util.Arrays; 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 " + String message = String.format("The log call Log.%1$s(...) should be " +
"conditional: surround with `if (Log.isLoggable(...))` or " + "conditional: surround with `if (Log.isLoggable(...))` or " +
"`if (BuildConfig.DEBUG) { ... }`", "`if (BuildConfig.DEBUG) { ... }`",
node.getMethodIdentifier()); name);
context.report(CONDITIONAL, node, context.getUastLocation(node), message); context.report(CONDITIONAL, node, context.getUastLocation(node), message);
} }
@@ -205,7 +193,7 @@ public class LogDetector extends Detector implements Detector.UastScanner {
if (argument instanceof ULiteralExpression) { if (argument instanceof ULiteralExpression) {
return false; return false;
} }
if (argument instanceof UBinaryExpressionWithType) { if (argument instanceof UBinaryExpression) {
String string = UastUtils.evaluateString(argument); String string = UastUtils.evaluateString(argument);
//noinspection VariableNotUsedInsideIf //noinspection VariableNotUsedInsideIf
if (string != null) { // does it resolve to a constant? if (string != null) { // does it resolve to a constant?
@@ -233,17 +221,21 @@ public class LogDetector extends Detector implements Detector.UastScanner {
return false; return false;
} }
private static boolean checkWithinConditional( private static boolean checkWithinConditional(
@NonNull JavaContext context, @NonNull JavaContext context,
@Nullable UElement curr, @Nullable UElement curr,
@NonNull UCallExpression logCall) { @NonNull UCallExpression logCall) {
while (curr != null) { while (curr != null) {
if (curr instanceof UIfExpression) { if (curr instanceof UIfExpression) {
UIfExpression ifNode = (UIfExpression) curr;
List<UExpression> chain = UastUtils.getQualifiedChain(ifNode.getCondition()); UExpression condition = ((UIfExpression) curr).getCondition();
if (!chain.isEmpty() && chain.get(chain.size() - 1) instanceof UCallExpression) { if (condition instanceof UQualifiedReferenceExpression) {
UCallExpression call = (UCallExpression) chain.get(chain.size() - 1); condition = getLastInQualifiedChain((UQualifiedReferenceExpression) condition);
}
if (condition instanceof UCallExpression) {
UCallExpression call = (UCallExpression) condition;
if (IS_LOGGABLE.equals(call.getMethodName())) { if (IS_LOGGABLE.equals(call.getMethodName())) {
checkTagConsistent(context, logCall, call); checkTagConsistent(context, logCall, call);
} }
@@ -251,10 +243,10 @@ public class LogDetector extends Detector implements Detector.UastScanner {
return true; return true;
} else if (curr instanceof UCallExpression } else if (curr instanceof UCallExpression
|| curr instanceof UMethod || curr instanceof UMethod
|| curr instanceof UClassInitializer || curr instanceof UClassInitializer
|| curr instanceof UField || curr instanceof UField
|| curr instanceof UClass) { // static block || curr instanceof UClass) { // static block
break; break;
} }
curr = curr.getContainingElement(); curr = curr.getContainingElement();
@@ -283,7 +275,8 @@ public class LogDetector extends Detector implements Detector.UastScanner {
} }
if (logTag != null) { if (logTag != null) {
if (!UastLintUtils.areIdentifiersEqual(isLoggableTag, logTag)) { if (!areLiteralsEqual(isLoggableTag, logTag) &&
!UastLintUtils.areIdentifiersEqual(isLoggableTag, logTag)) {
PsiNamedElement resolved1 = UastUtils.tryResolveNamed(isLoggableTag); PsiNamedElement resolved1 = UastUtils.tryResolveNamed(isLoggableTag);
PsiNamedElement resolved2 = UastUtils.tryResolveNamed(logTag); PsiNamedElement resolved2 = UastUtils.tryResolveNamed(logTag);
if ((resolved1 == null || resolved2 == null || !resolved1.equals(resolved2)) 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); 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.PsiParameter;
import com.intellij.psi.PsiVariable; import com.intellij.psi.PsiVariable;
import org.jetbrains.uast.UAnonymousClass; import org.jetbrains.uast.*;
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.expressions.UReferenceExpression; import org.jetbrains.uast.expressions.UReferenceExpression;
import org.jetbrains.uast.util.UastExpressionUtils; import org.jetbrains.uast.util.UastExpressionUtils;
import org.jetbrains.uast.visitor.AbstractUastVisitor; import org.jetbrains.uast.visitor.AbstractUastVisitor;
@@ -128,14 +119,15 @@ public class RecyclerViewDetector extends Detector implements Detector.UastScann
PsiParameter parameter = parameters[1]; PsiParameter parameter = parameters[1];
ParameterEscapesVisitor visitor = new ParameterEscapesVisitor(context, cls, parameter); 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()) { if (visitor.variableEscapes()) {
reportError(context, viewHolder, parameter); reportError(context, viewHolder, parameter);
} }
// Look for pending data binder calls that aren't executed before the method finishes // Look for pending data binder calls that aren't executed before the method finishes
List<UCallExpression> dataBinderReferences = visitor.getDataBinders(); List<UCallExpression> dataBinderReferences = visitor.getDataBinders();
checkDataBinders(context, declaration, dataBinderReferences); checkDataBinders(context, method, dataBinderReferences);
} }
private static void reportError(@NonNull JavaContext context, PsiParameter viewHolder, 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, private static void checkDataBinders(@NonNull JavaContext context,
@NonNull PsiMethod declaration, List<UCallExpression> references) { @NonNull UMethod declaration, List<UCallExpression> references) {
if (references != null && !references.isEmpty()) { if (references != null && !references.isEmpty()) {
List<UCallExpression> targets = Lists.newArrayList(); List<UCallExpression> targets = Lists.newArrayList();
List<UCallExpression> sources = Lists.newArrayList(); List<UCallExpression> sources = Lists.newArrayList();
@@ -1162,12 +1162,12 @@ public class StringFormatDetector extends ResourceXmlDetector implements Detecto
boolean argWasReference = false; boolean argWasReference = false;
if (lastArg instanceof UReferenceExpression) { if (lastArg instanceof UReferenceExpression) {
PsiElement resolved = ((UReferenceExpression) lastArg).resolve(); PsiElement resolved = ((UReferenceExpression) lastArg).resolve();
if (resolved instanceof PsiVariable) { if (resolved instanceof PsiVariable) {
UExpression initializer = context.getUastContext().getInitializerBody ( UExpression initializer = context.getUastContext().getInitializerBody (
(PsiVariable) resolved); (PsiVariable) resolved);
if (UastExpressionUtils.isConstructorCall(initializer)) { if (initializer != null &&
(UastExpressionUtils.isNewArray(initializer) ||
UastExpressionUtils.isNestedArrayInitializer(initializer))) {
argWasReference = true; argWasReference = true;
// Now handled by check below // Now handled by check below
lastArg = initializer; lastArg = initializer;
@@ -1175,14 +1175,16 @@ public class StringFormatDetector extends ResourceXmlDetector implements Detecto
} }
} }
if (UastExpressionUtils.isNewArray(lastArg)) { if (UastExpressionUtils.isNewArray(lastArg) ||
UCallExpression callExpression = (UCallExpression) lastArg; UastExpressionUtils.isNestedArrayInitializer(lastArg)) {
UCallExpression arrayInitializer = (UCallExpression) lastArg;
if (UastExpressionUtils.isNewArrayWithInitializer(lastArg)) { if (UastExpressionUtils.isNewArrayWithInitializer(lastArg) ||
callCount = callExpression.getValueArgumentCount(); UastExpressionUtils.isNestedArrayInitializer(lastArg)) {
callCount = arrayInitializer.getValueArgumentCount();
knownArity = true; knownArity = true;
} else if (UastExpressionUtils.isNewArrayWithDimensions(lastArg)) { } else if (UastExpressionUtils.isNewArrayWithDimensions(lastArg)) {
List<UExpression> arrayDimensions = callExpression.getValueArguments(); List<UExpression> arrayDimensions = arrayInitializer.getValueArguments();
if (arrayDimensions.size() == 1) { if (arrayDimensions.size() == 1) {
UExpression first = arrayDimensions.get(0); UExpression first = arrayDimensions.get(0);
if (first instanceof ULiteralExpression) { 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.COLOR_INT_ANNOTATION;
import static com.android.tools.klint.detector.api.ResourceEvaluator.PX_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 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.NonNull;
import com.android.annotations.Nullable; 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.Operation;
import com.android.tools.klint.checks.PermissionFinder.Result; import com.android.tools.klint.checks.PermissionFinder.Result;
import com.android.tools.klint.checks.PermissionHolder.SetPermissionLookup; 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.JavaEvaluator;
import com.android.tools.klint.client.api.LintClient; import com.android.tools.klint.client.api.LintClient;
import com.android.tools.klint.client.api.UastLintUtils; 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.PsiType;
import com.intellij.psi.PsiVariable; import com.intellij.psi.PsiVariable;
import org.jetbrains.uast.UAnonymousClass; import org.jetbrains.uast.*;
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.expressions.UReferenceExpression; import org.jetbrains.uast.expressions.UReferenceExpression;
import org.jetbrains.uast.util.UastExpressionUtils; import org.jetbrains.uast.util.UastExpressionUtils;
import org.jetbrains.uast.visitor.AbstractUastVisitor; 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_ANY_OF = "anyOf";
public static final String ATTR_CONDITIONAL = "conditional"; public static final String ATTR_CONDITIONAL = "conditional";
public static final String SECURITY_EXCEPTION = "java.lang.SecurityException";
/** /**
* Constructs a new {@link SupportAnnotationDetector} check * Constructs a new {@link SupportAnnotationDetector} check
*/ */
@@ -557,8 +542,8 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
if (tryCatch == null) { if (tryCatch == null) {
break; break;
} else { } else {
for (UCatchClause psiCatchSection : tryCatch.getCatchClauses()) { for (UCatchClause catchClause : tryCatch.getCatchClauses()) {
if (isSecurityException(psiCatchSection.getTypes())) { if (containsSecurityException(catchClause.getTypes())) {
return true; return true;
} }
} }
@@ -572,7 +557,7 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
UMethod declaration = UastUtils.getParentOfType(parent, UMethod.class, false); UMethod declaration = UastUtils.getParentOfType(parent, UMethod.class, false);
if (declaration != null) { if (declaration != null) {
PsiClassType[] thrownTypes = declaration.getThrowsList().getReferencedTypes(); PsiClassType[] thrownTypes = declaration.getThrowsList().getReferencedTypes();
if (isSecurityException(Arrays.<PsiType>asList(thrownTypes))) { if (containsSecurityException(Arrays.asList(thrownTypes))) {
return true; return true;
} }
} }
@@ -685,8 +670,8 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
} }
} }
private static boolean isSecurityException( private static boolean containsSecurityException(
@NonNull List<PsiType> types) { @NonNull List<? extends PsiType> types) {
for (PsiType type : types) { for (PsiType type : types) {
if (type instanceof PsiClassType) { if (type instanceof PsiClassType) {
PsiClass cls = ((PsiClassType) type).resolve(); 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 // any super type as well, however that probably hides warnings in cases where
// users don't want that; see http://b.android.com/182165 // users don't want that; see http://b.android.com/182165
//return context.getEvaluator().extendsClass(cls, "java.lang.SecurityException", false); //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, private static void checkResult(@NonNull JavaContext context, @NonNull UCallExpression node,
@NonNull PsiMethod method, @NonNull PsiAnnotation annotation) { @NonNull PsiMethod method, @NonNull PsiAnnotation annotation) {
if (context.getUastContext().isExpressionValueUsed(UastUtils.getQualifiedParentOrThis(node))) { if (isExpressionValueUnused(node)) {
String methodName = JavaContext.getMethodName(node); String methodName = JavaContext.getMethodName(node);
String suggested = getAnnotationStringValue(annotation, ATTR_SUGGEST); 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( private static void checkThreading(
@NonNull JavaContext context, @NonNull JavaContext context,
@NonNull UElement node, @NonNull UElement node,
@@ -1150,7 +1142,7 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
*/ */
public static boolean typeArrayFromArrayLiteral( public static boolean typeArrayFromArrayLiteral(
@Nullable UElement node, @NonNull JavaContext context) { @Nullable UElement node, @NonNull JavaContext context) {
if (UastExpressionUtils.isMethodCall(node)) { if (isMethodCall(node)) {
UCallExpression expression = (UCallExpression) node; UCallExpression expression = (UCallExpression) node;
assert expression != null; assert expression != null;
String name = expression.getMethodName(); String name = expression.getMethodName();
@@ -1213,6 +1205,24 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
return false; 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( private static void checkIntRange(
@NonNull JavaContext context, @NonNull JavaContext context,
@NonNull PsiAnnotation annotation, @NonNull PsiAnnotation annotation,
@@ -1630,6 +1640,12 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
if (value.equals(((PsiLiteral)expression).getValue())) { if (value.equals(((PsiLiteral)expression).getValue())) {
return; 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) { } else if (expression instanceof PsiReference) {
PsiElement resolved = ((PsiReference) expression).resolve(); PsiElement resolved = ((PsiReference) expression).resolve();
if (resolved != null && resolved.equals(value)) { if (resolved != null && resolved.equals(value)) {
@@ -1677,7 +1693,7 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
return; return;
} }
String values = listAllowedValues(allowedValues); String values = listAllowedValues(node, allowedValues);
String message; String message;
if (flag) { if (flag) {
message = "Must be one or more of: " + values; message = "Must be one or more of: " + values;
@@ -1709,22 +1725,29 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
return null; return null;
} }
private static String listAllowedValues(@NonNull PsiAnnotationMemberValue[] allowedValues) { private static String listAllowedValues(@NonNull UElement context,
@NonNull PsiAnnotationMemberValue[] allowedValues) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
for (PsiAnnotationMemberValue allowedValue : allowedValues) { for (PsiAnnotationMemberValue allowedValue : allowedValues) {
String s = null; String s = null;
if (allowedValue instanceof PsiReference) { PsiElement resolved = null;
PsiElement resolved = ((PsiReference) allowedValue).resolve(); if (allowedValue instanceof ExternalReferenceExpression) {
if (resolved instanceof PsiField) { resolved = UastLintUtils.resolve(
PsiField field = (PsiField) resolved; (ExternalReferenceExpression) allowedValue, context);
String containingClassName = field.getContainingClass() != null } else if (allowedValue instanceof PsiReference) {
? field.getContainingClass().getName() : null; resolved = ((PsiReference) allowedValue).resolve();
if (containingClassName == null) {
continue;
}
s = containingClassName + "." + field.getName();
}
} }
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) { if (s == null) {
s = allowedValue.getText(); s = allowedValue.getText();
} }
@@ -1768,7 +1791,7 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
@NonNull @NonNull
static PsiAnnotation[] filterRelevantAnnotations( static PsiAnnotation[] filterRelevantAnnotations(
@NonNull PsiAnnotation[] annotations) { @NonNull JavaEvaluator evaluator, @NonNull PsiAnnotation[] annotations) {
List<PsiAnnotation> result = null; List<PsiAnnotation> result = null;
int length = annotations.length; int length = annotations.length;
if (length == 0) { if (length == 0) {
@@ -1812,33 +1835,31 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
continue; continue;
} }
PsiClass cls = (PsiClass)resolved; PsiClass cls = (PsiClass)resolved;
PsiModifierList modifierList = cls.getModifierList(); PsiAnnotation[] innerAnnotations = evaluator.getAllAnnotations(cls);
if (modifierList != null) { for (int j = 0; j < innerAnnotations.length; j++) {
PsiAnnotation[] innerAnnotations = modifierList.getAnnotations(); PsiAnnotation inner = innerAnnotations[j];
for (int j = 0; j < innerAnnotations.length; j++) { String a = inner.getQualifiedName();
PsiAnnotation inner = innerAnnotations[j]; if (a == null || a.startsWith("java.")) {
String a = inner.getQualifiedName(); // @Override, @SuppressWarnings etc. Ignore
if (a == null) { continue;
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) if (result == null) {
|| a.equals(PERMISSION_ANNOTATION) result = new ArrayList<PsiAnnotation>(2);
|| 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);
} }
result.add(inner);
} }
} }
} }
return result != null return result != null
? result.toArray(PsiAnnotation.EMPTY_ARRAY) : PsiAnnotation.EMPTY_ARRAY; ? result.toArray(PsiAnnotation.EMPTY_ARRAY) : PsiAnnotation.EMPTY_ARRAY;
} }
// ---- Implements UastScanner ---- // ---- Implements UastScanner ----
@@ -1849,9 +1870,7 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
public List<Class<? extends UElement>> getApplicableUastTypes() { public List<Class<? extends UElement>> getApplicableUastTypes() {
List<Class<? extends UElement>> types = new ArrayList<Class<? extends UElement>>(3); List<Class<? extends UElement>> types = new ArrayList<Class<? extends UElement>>(3);
types.add(UCallExpression.class); types.add(UCallExpression.class);
//types.add(PsiMethodCallExpression.class); types.add(UVariable.class);
//types.add(PsiNewExpression.class);
types.add(UField.class);
return types; return types;
} }
@@ -1886,33 +1905,33 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
} }
return super.visitVariable(node); return super.visitVariable(node);
} }
public void checkCall(PsiMethod method, UCallExpression call) { public void checkCall(PsiMethod method, UCallExpression call) {
JavaEvaluator evaluator = mContext.getEvaluator(); JavaEvaluator evaluator = mContext.getEvaluator();
PsiAnnotation[] methodAnnotations = evaluator.getAllAnnotations(method, true); PsiAnnotation[] methodAnnotations = evaluator.getAllAnnotations(method);
methodAnnotations = filterRelevantAnnotations(methodAnnotations); methodAnnotations = filterRelevantAnnotations(evaluator, methodAnnotations);
// Look for annotations on the class as well: these trickle // Look for annotations on the class as well: these trickle
// down to all the methods in the class // down to all the methods in the class
PsiClass containingClass = method.getContainingClass(); PsiClass containingClass = method.getContainingClass();
PsiAnnotation[] classAnnotations; PsiAnnotation[] classAnnotations;
if (containingClass != null) { if (containingClass != null) {
classAnnotations = evaluator.getAllAnnotations(containingClass, true); classAnnotations = evaluator.getAllAnnotations(containingClass);
classAnnotations = filterRelevantAnnotations(classAnnotations); classAnnotations = filterRelevantAnnotations(evaluator, classAnnotations);
} else { } else {
classAnnotations = PsiAnnotation.EMPTY_ARRAY; classAnnotations = PsiAnnotation.EMPTY_ARRAY;
} }
for (PsiAnnotation annotation : methodAnnotations) { for (PsiAnnotation annotation : methodAnnotations) {
checkMethodAnnotation(mContext, method, call, annotation, methodAnnotations, checkMethodAnnotation(mContext, method, call, annotation, methodAnnotations,
classAnnotations); classAnnotations);
} }
if (classAnnotations.length > 0) { if (classAnnotations.length > 0) {
for (PsiAnnotation annotation : classAnnotations) { for (PsiAnnotation annotation : classAnnotations) {
checkMethodAnnotation(mContext, method, call, annotation, methodAnnotations, checkMethodAnnotation(mContext, method, call, annotation, methodAnnotations,
classAnnotations); classAnnotations);
} }
} }
@@ -1921,12 +1940,12 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
PsiParameter[] parameters = parameterList.getParameters(); PsiParameter[] parameters = parameterList.getParameters();
PsiAnnotation[] annotations = null; PsiAnnotation[] annotations = null;
for (int i = 0, n = Math.min(parameters.length, arguments.size()); for (int i = 0, n = Math.min(parameters.length, arguments.size());
i < n; i < n;
i++) { i++) {
UExpression argument = arguments.get(i); UExpression argument = arguments.get(i);
PsiParameter parameter = parameters[i]; PsiParameter parameter = parameters[i];
annotations = evaluator.getAllAnnotations(parameter, true); annotations = evaluator.getAllAnnotations(parameter);
annotations = filterRelevantAnnotations(annotations); annotations = filterRelevantAnnotations(evaluator, annotations);
checkParameterAnnotations(mContext, argument, call, method, annotations); checkParameterAnnotations(mContext, argument, call, method, annotations);
} }
if (annotations != null) { if (annotations != null) {
@@ -183,7 +183,7 @@ public class IdeaJavaParser extends JavaParser {
@NonNull @NonNull
@Override @Override
public PsiAnnotation[] getAllAnnotations(@NonNull PsiModifierListOwner owner, boolean inHierarchy) { public PsiAnnotation[] getAllAnnotations(@NonNull PsiModifierListOwner owner) {
return AnnotationUtil.getAllAnnotations(owner, inHierarchy, null, true); return AnnotationUtil.getAllAnnotations(owner, inHierarchy, null, true);
} }