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