Lint: Android Lint diagnostics fixes
This commit is contained in:
committed by
Yan Zhulanow
parent
c2ddd943f9
commit
2be9a083ad
+25
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
+24
-41
@@ -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) {
|
||||||
|
|||||||
+1
-1
@@ -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;
|
||||||
|
|||||||
+320
-275
@@ -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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+180
-115
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-13
@@ -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();
|
||||||
|
|||||||
+10
-8
@@ -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) {
|
||||||
|
|||||||
+95
-76
@@ -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) {
|
||||||
|
|||||||
+1
-1
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user