Lint: Use IntellijApiDetector in IDE

This commit is contained in:
Yan Zhulanow
2016-10-17 19:14:44 +03:00
committed by Yan Zhulanow
parent 6ff29f473b
commit 65cea7e92c
10 changed files with 1351 additions and 396 deletions
@@ -23,16 +23,7 @@ import com.android.resources.ResourceType;
import com.android.tools.klint.detector.api.ConstantEvaluator;
import com.android.tools.klint.detector.api.JavaContext;
import com.google.common.base.Joiner;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiClassType;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiField;
import com.intellij.psi.PsiJavaFile;
import com.intellij.psi.PsiLocalVariable;
import com.intellij.psi.PsiModifier;
import com.intellij.psi.PsiParameter;
import com.intellij.psi.PsiType;
import com.intellij.psi.PsiVariable;
import com.intellij.psi.*;
import org.jetbrains.uast.*;
import org.jetbrains.uast.expressions.UReferenceExpression;
@@ -43,6 +34,35 @@ import java.util.Collections;
import java.util.List;
public class UastLintUtils {
@Nullable
public static String getQualifiedName(PsiElement element) {
if (element instanceof PsiClass) {
return ((PsiClass) element).getQualifiedName();
} else if (element instanceof PsiMethod) {
PsiClass containingClass = ((PsiMethod) element).getContainingClass();
if (containingClass == null) {
return null;
}
String containingClassFqName = getQualifiedName(containingClass);
if (containingClassFqName == null) {
return null;
}
return containingClassFqName + "." + ((PsiMethod) element).getName();
} else if (element instanceof PsiField) {
PsiClass containingClass = ((PsiField) element).getContainingClass();
if (containingClass == null) {
return null;
}
String containingClassFqName = getQualifiedName(containingClass);
if (containingClassFqName == null) {
return null;
}
return containingClassFqName + "." + ((PsiField) element).getName();
} else {
return null;
}
}
@Nullable
public static PsiElement resolve(ExternalReferenceExpression expression, UElement context) {
UDeclaration declaration = UastUtils.getParentOfType(context, UDeclaration.class);
@@ -97,19 +97,11 @@ 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.*;
import org.jetbrains.uast.expressions.UReferenceExpression;
import org.jetbrains.uast.java.JavaUAnnotation;
import org.jetbrains.uast.java.JavaUTypeCastExpression;
import org.jetbrains.uast.util.UastExpressionUtils;
import org.jetbrains.uast.visitor.AbstractUastVisitor;
import org.jetbrains.uast.visitor.UastVisitor;
@@ -249,34 +241,29 @@ public class AnnotationDetector extends Detector implements Detector.UastScanner
}
@Override
public boolean visitAnnotation(UAnnotation annotation) {
public boolean visitAnnotation(@NonNull UAnnotation annotation) {
String type = annotation.getQualifiedName();
if (type == null || type.startsWith("java.lang.")) {
return false;
}
if (FQCN_SUPPRESS_LINT.equals(type)) {
PsiAnnotationOwner owner = annotation.getOwner();
if (owner == null) {
UElement parent = annotation.getContainingElement();
if (parent == null) {
return false;
}
if (owner instanceof PsiModifierList) {
PsiElement parent = ((PsiModifierList) owner).getParent();
// Only flag local variables and parameters (not classes, fields and methods)
if (!(parent instanceof PsiDeclarationStatement
|| parent instanceof PsiLocalVariable
|| parent instanceof PsiParameter)) {
return false;
}
} else {
// Only flag local variables and parameters (not classes, fields and methods)
if (!(parent instanceof UVariableDeclarationsExpression
|| parent instanceof ULocalVariable
|| parent instanceof UParameter)) {
return false;
}
PsiNameValuePair[] attributes = annotation.getParameterList().getAttributes();
if (attributes.length == 1) {
PsiNameValuePair attribute = attributes[0];
PsiAnnotationMemberValue value = attribute.getValue();
if (value instanceof PsiLiteral) {
Object v = ((PsiLiteral) value).getValue();
List<UNamedExpression> attributes = annotation.getAttributeValues();
if (attributes.size() == 1) {
UNamedExpression attribute = attributes.get(0);
UExpression value = attribute.getExpression();
if (value instanceof ULiteralExpression) {
Object v = ((ULiteralExpression) value).getValue();
if (v instanceof String) {
String id = (String) v;
checkSuppressLint(annotation, id);
@@ -300,9 +287,8 @@ public class AnnotationDetector extends Detector implements Detector.UastScanner
} else if (type.startsWith(SUPPORT_ANNOTATIONS_PREFIX)) {
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) {
PsiMethod method = (PsiMethod) annotation.getParent().getParent();
if (annotation.getContainingElement() instanceof UMethod) {
UMethod method = (UMethod) annotation.getContainingElement();
if (!method.isConstructor()
&& PsiType.VOID.equals(method.getReturnType())) {
mContext.report(ANNOTATION_USAGE, annotation.getPsi(),
@@ -367,8 +353,7 @@ public class AnnotationDetector extends Detector implements Detector.UastScanner
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
&& annotation.getParent().getParent() instanceof PsiMethod) {
if (annotation.getContainingElement() instanceof UMethod) {
String value = PermissionRequirement.getAnnotationStringValue(annotation, ATTR_VALUE);
String[] anyOf = PermissionRequirement.getAnnotationStringValues(annotation, ATTR_ANY_OF);
String[] allOf = PermissionRequirement.getAnnotationStringValues(annotation, ATTR_ALL_OF);
@@ -405,20 +390,16 @@ public class AnnotationDetector extends Detector implements Detector.UastScanner
}
} else {
// Look for typedefs (and make sure they're specified on the right type)
PsiJavaCodeReferenceElement referenceElement = annotation
.getNameReferenceElement();
if (referenceElement != null) {
PsiElement resolved = referenceElement.resolve();
if (resolved instanceof PsiClass) {
PsiClass cls = (PsiClass) resolved;
if (cls.isAnnotationType() && cls.getModifierList() != null) {
for (PsiAnnotation a : cls.getModifierList().getAnnotations()) {
String name = a.getQualifiedName();
if (INT_DEF_ANNOTATION.equals(name)) {
checkTargetType(annotation, TYPE_INT, TYPE_LONG, true);
} else if (STRING_DEF_ANNOTATION.equals(type)) {
checkTargetType(annotation, TYPE_STRING, null, true);
}
PsiElement resolved = annotation.resolve();
if (resolved != null) {
PsiClass cls = (PsiClass) resolved;
if (cls.isAnnotationType() && cls.getModifierList() != null) {
for (PsiAnnotation a : cls.getModifierList().getAnnotations()) {
String name = a.getQualifiedName();
if (INT_DEF_ANNOTATION.equals(name)) {
checkTargetType(annotation, TYPE_INT, TYPE_LONG, true);
} else if (STRING_DEF_ANNOTATION.equals(type)) {
checkTargetType(annotation, TYPE_STRING, null, true);
}
}
}
@@ -428,75 +409,73 @@ public class AnnotationDetector extends Detector implements Detector.UastScanner
return false;
}
private void checkTargetType(@NonNull PsiAnnotation node, @NonNull String type1,
private void checkTargetType(@NonNull UAnnotation node, @NonNull String type1,
@Nullable String type2, boolean allowCollection) {
PsiAnnotationOwner owner = node.getOwner();
if (owner instanceof PsiModifierList) {
PsiElement parent = ((PsiModifierList) owner).getParent();
PsiType type;
if (parent instanceof PsiDeclarationStatement) {
PsiElement[] elements = ((PsiDeclarationStatement) parent).getDeclaredElements();
if (elements.length > 0) {
PsiElement element = elements[0];
if (element instanceof PsiLocalVariable) {
type = ((PsiLocalVariable)element).getType();
} else {
return;
}
UElement parent = node.getContainingElement();
PsiType type;
if (parent instanceof UVariableDeclarationsExpression) {
List<UVariable> elements = ((UVariableDeclarationsExpression) parent).getVariables();
if (!elements.isEmpty()) {
UVariable element = elements.get(0);
if (element instanceof ULocalVariable) {
type = element.getType();
} else {
return;
}
} else if (parent instanceof PsiMethod) {
PsiMethod method = (PsiMethod) parent;
type = method.isConstructor()
? mContext.getEvaluator().getClassType(method.getContainingClass())
: method.getReturnType();
} else if (parent instanceof PsiVariable) {
// Field or local variable or parameter
type = ((PsiVariable)parent).getType();
} else {
return;
}
if (type == null) {
return;
}
} else if (parent instanceof UMethod) {
UMethod method = (UMethod) parent;
type = method.isConstructor()
? mContext.getEvaluator().getClassType(method.getContainingClass())
: method.getReturnType();
} else if (parent instanceof UVariable) {
// Field or local variable or parameter
type = ((UVariable) parent).getType();
} else {
return;
}
if (type == null) {
return;
}
if (allowCollection) {
if (type instanceof PsiArrayType) {
// For example, int[]
type = type.getDeepComponentType();
} else if (type instanceof PsiClassType) {
// For example, List<Integer>
PsiClassType classType = (PsiClassType)type;
if (classType.getParameters().length == 1) {
PsiClass resolved = classType.resolve();
if (resolved != null &&
InheritanceUtil.isInheritor(resolved, false, "java.util.Collection")) {
type = classType.getParameters()[0];
}
if (allowCollection) {
if (type instanceof PsiArrayType) {
// For example, int[]
type = type.getDeepComponentType();
} else if (type instanceof PsiClassType) {
// For example, List<Integer>
PsiClassType classType = (PsiClassType)type;
if (classType.getParameters().length == 1) {
PsiClass resolved = classType.resolve();
if (resolved != null &&
InheritanceUtil.isInheritor(resolved, false, "java.util.Collection")) {
type = classType.getParameters()[0];
}
}
}
}
String typeName = type.getCanonicalText();
if (!typeName.equals(type1)
&& (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))) {
return;
}
String expectedTypes = type2 == null ? type1 : type1 + " or " + type2;
if (typeName.equals(TYPE_STRING)) {
typeName = "String";
}
String message = String.format(
"This annotation does not apply for type %1$s; expected %2$s",
typeName, expectedTypes);
Location location = mContext.getLocation(node);
mContext.report(ANNOTATION_USAGE, node, location, message);
String typeName = type.getCanonicalText();
if (!typeName.equals(type1)
&& (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))) {
return;
}
String expectedTypes = type2 == null ? type1 : type1 + " or " + type2;
if (typeName.equals(TYPE_STRING)) {
typeName = "String";
}
String message = String.format(
"This annotation does not apply for type %1$s; expected %2$s",
typeName, expectedTypes);
Location location = mContext.getUastLocation(node);
mContext.report(ANNOTATION_USAGE, node, location, message);
}
}
@@ -504,17 +483,19 @@ public class AnnotationDetector extends Detector implements Detector.UastScanner
public boolean visitSwitchExpression(USwitchExpression switchExpression) {
UExpression condition = switchExpression.getExpression();
if (condition != null && PsiType.INT.equals(condition.getExpressionType())) {
PsiAnnotation annotation = findIntDefAnnotation(condition);
UAnnotation annotation = findIntDefAnnotation(condition);
if (annotation != null) {
PsiAnnotationMemberValue value =
UNamedExpression namedValue =
annotation.findDeclaredAttributeValue(ATTR_VALUE);
if (value == null) {
value = annotation.findDeclaredAttributeValue(null);
if (namedValue == null) {
namedValue = annotation.findDeclaredAttributeValue(null);
}
if (value instanceof PsiArrayInitializerMemberValue) {
PsiAnnotationMemberValue[] allowedValues =
((PsiArrayInitializerMemberValue)value).getInitializers();
UExpression value = (namedValue != null) ? namedValue.getExpression() : null;
if (UastExpressionUtils.isArrayInitializer(value)) {
List<UExpression> allowedValues =
((UCallExpression) value).getValueArguments();
switchExpression.accept(new SwitchChecker(switchExpression, allowedValues));
}
}
@@ -537,15 +518,17 @@ public class AnnotationDetector extends Detector implements Detector.UastScanner
* with a given node
*/
@Nullable
private PsiAnnotation findIntDefAnnotation(@NonNull UExpression expression) {
private UAnnotation findIntDefAnnotation(@NonNull UExpression expression) {
if (expression instanceof UReferenceExpression) {
PsiElement resolved = ((UReferenceExpression) expression).resolve();
if (resolved instanceof PsiModifierListOwner) {
PsiAnnotation[] annotations = mContext.getEvaluator().getAllAnnotations(
(PsiModifierListOwner)resolved);
PsiAnnotation annotation = SupportAnnotationDetector.findIntDef(
filterRelevantAnnotations(mContext.getEvaluator(), annotations));
PsiAnnotation[] relevantAnnotations =
filterRelevantAnnotations(mContext.getEvaluator(), annotations);
UAnnotation annotation = SupportAnnotationDetector.findIntDef(
JavaUAnnotation.wrap(relevantAnnotations));
if (annotation != null) {
return annotation;
}
@@ -565,10 +548,12 @@ public class AnnotationDetector extends Detector implements Detector.UastScanner
} else if (expression instanceof UCallExpression) {
PsiMethod method = ((UCallExpression) expression).resolve();
if (method != null) {
PsiAnnotation[] annotations = mContext.getEvaluator()
.getAllAnnotations(method);
PsiAnnotation annotation = SupportAnnotationDetector.findIntDef(
filterRelevantAnnotations(mContext.getEvaluator(), annotations));
PsiAnnotation[] annotations =
mContext.getEvaluator().getAllAnnotations(method);
PsiAnnotation[] relevantAnnotations =
filterRelevantAnnotations(mContext.getEvaluator(), annotations);
List<UAnnotation> uAnnotations = JavaUAnnotation.wrap(relevantAnnotations);
UAnnotation annotation = SupportAnnotationDetector.findIntDef(uAnnotations);
if (annotation != null) {
return annotation;
}
@@ -576,13 +561,13 @@ public class AnnotationDetector extends Detector implements Detector.UastScanner
} else if (expression instanceof UIfExpression) {
UIfExpression ifExpression = (UIfExpression) expression;
if (ifExpression.getThenExpression() != null) {
PsiAnnotation result = findIntDefAnnotation(ifExpression.getThenExpression());
UAnnotation result = findIntDefAnnotation(ifExpression.getThenExpression());
if (result != null) {
return result;
}
}
if (ifExpression.getElseExpression() != null) {
PsiAnnotation result = findIntDefAnnotation(ifExpression.getElseExpression());
UAnnotation result = findIntDefAnnotation(ifExpression.getElseExpression());
if (result != null) {
return result;
}
@@ -597,16 +582,17 @@ public class AnnotationDetector extends Detector implements Detector.UastScanner
return null;
}
private void ensureUniqueValues(@NonNull PsiAnnotation node) {
PsiAnnotationMemberValue value = node.findAttributeValue(ATTR_VALUE);
if (value == null) {
value = node.findAttributeValue(null);
private void ensureUniqueValues(@NonNull UAnnotation node) {
UNamedExpression namedValue = node.findAttributeValue(ATTR_VALUE);
if (namedValue == null) {
namedValue = node.findAttributeValue(null);
}
if (value == null) {
if (namedValue == null) {
return;
}
UExpression value = namedValue.getExpression();
if (!(value instanceof PsiArrayInitializerMemberValue)) {
if (!(UastExpressionUtils.isArrayInitializer(value))) {
return;
}
@@ -644,8 +630,8 @@ public class AnnotationDetector extends Detector implements Detector.UastScanner
Location secondary = mContext.getLocation(prevConstant);
secondary.setMessage("Previous same value");
location.setSecondary(secondary);
PsiElement scope = getAnnotationScope(node);
mContext.report(UNIQUE, scope, location, message);
UElement scope = getAnnotationScope(node);
mContext.reportUast(UNIQUE, scope, location, message);
break;
}
valueToIndex.put(number, index);
@@ -697,7 +683,7 @@ public class AnnotationDetector extends Detector implements Detector.UastScanner
}
}
private boolean checkSuppressLint(@NonNull PsiAnnotation node, @NonNull String id) {
private boolean checkSuppressLint(@NonNull UAnnotation node, @NonNull String id) {
IssueRegistry registry = mContext.getDriver().getRegistry();
Issue issue = registry.getIssue(id);
// Special-case the ApiDetector issue, since it does both source file analysis
@@ -707,8 +693,8 @@ public class AnnotationDetector extends Detector implements Detector.UastScanner
|| 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(
UElement scope = getAnnotationScope(node);
mContext.report(INSIDE_METHOD, scope, mContext.getUastLocation(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));
@@ -721,19 +707,19 @@ public class AnnotationDetector extends Detector implements Detector.UastScanner
private class SwitchChecker extends AbstractUastVisitor {
private final USwitchExpression mSwitchExpression;
private final PsiAnnotationMemberValue[] mAllowedValues;
private final List<PsiElement> mFields;
private final List<UExpression> mAllowedValues;
private final List<Object> mFields;
private final List<Integer> mSeenValues;
private boolean mReported = false;
private SwitchChecker(USwitchExpression switchExpression,
PsiAnnotationMemberValue[] allowedValues) {
List<UExpression> allowedValues) {
mSwitchExpression = switchExpression;
mAllowedValues = allowedValues;
mFields = Lists.newArrayListWithCapacity(allowedValues.length);
for (PsiAnnotationMemberValue allowedValue : allowedValues) {
mFields = Lists.newArrayListWithCapacity(allowedValues.size());
for (UExpression allowedValue : allowedValues) {
if (allowedValue instanceof ExternalReferenceExpression) {
ExternalReferenceExpression externalRef =
(ExternalReferenceExpression) allowedValue;
@@ -743,17 +729,17 @@ public class AnnotationDetector extends Detector implements Detector.UastScanner
if (resolved instanceof PsiField) {
mFields.add(resolved);
}
} else if (allowedValue instanceof PsiReferenceExpression) {
PsiElement resolved = ((PsiReferenceExpression) allowedValue).resolve();
} else if (allowedValue instanceof UReferenceExpression) {
PsiElement resolved = ((UReferenceExpression) allowedValue).resolve();
if (resolved != null) {
mFields.add(resolved);
}
} else if (allowedValue instanceof PsiLiteral) {
} else if (allowedValue instanceof ULiteralExpression) {
mFields.add(allowedValue);
}
}
mSeenValues = Lists.newArrayListWithCapacity(allowedValues.length);
mSeenValues = Lists.newArrayListWithCapacity(allowedValues.size());
}
@Override
@@ -807,9 +793,9 @@ public class AnnotationDetector extends Detector implements Detector.UastScanner
// So instead, manually check for equals. These lists tend to
// be very short anyway.
boolean found = false;
ListIterator<PsiElement> iterator = mFields.listIterator();
ListIterator<Object> iterator = mFields.listIterator();
while (iterator.hasNext()) {
PsiElement field = iterator.next();
Object field = iterator.next();
if (field.equals(resolved)) {
iterator.remove();
found = true;
@@ -825,7 +811,7 @@ public class AnnotationDetector extends Detector implements Detector.UastScanner
if (resolved instanceof PsiField) {
iterator = mFields.listIterator();
while (iterator.hasNext()) {
PsiElement field = iterator.next();
Object field = iterator.next();
if (field.equals(resolved)) {
iterator.remove();
found = true;
@@ -842,8 +828,8 @@ public class AnnotationDetector extends Detector implements Detector.UastScanner
mSeenValues.add(cv);
}
} else {
List<String> list = computeFieldNames(mSwitchExpression,
Arrays.asList(mAllowedValues));
List<String> list = computeFieldNames(
mSwitchExpression, Collections.singletonList(mAllowedValues));
// Keep error message in sync with {@link #getMissingCases}
String message = "Unexpected constant; expected one of: " + Joiner
.on(", ").join(list);
@@ -874,9 +860,9 @@ public class AnnotationDetector extends Detector implements Detector.UastScanner
// Any missing switch constants? Before we flag them, look to see if any
// of them have the same values: those can be omitted
if (!mFields.isEmpty()) {
ListIterator<PsiElement> iterator = mFields.listIterator();
ListIterator<Object> iterator = mFields.listIterator();
while (iterator.hasNext()) {
PsiElement next = iterator.next();
Object next = iterator.next();
if (next instanceof PsiField) {
Integer cv = getConstantValue((PsiField)next);
if (mSeenValues.contains(cv)) {
@@ -937,30 +923,6 @@ public class AnnotationDetector extends Detector implements Detector.UastScanner
return list;
}
/**
* Given an error message produced by this lint detector for the {@link #SWITCH_TYPE_DEF} issue
* type, returns the list of missing enum cases. <p> Intended for IDE quickfix implementations.
*
* @param errorMessage the error message associated with the error
* @param format the format of the error message
* @return the list of enum cases, or null if not recognized
*/
@Nullable
public static List<String> getMissingCases(@NonNull String errorMessage,
@NonNull TextFormat format) {
errorMessage = format.toText(errorMessage);
String substring = findSubstring(errorMessage, " missing case ", null);
if (substring == null) {
substring = findSubstring(errorMessage, "expected one of: ", null);
}
if (substring != null) {
return Splitter.on(",").trimResults().splitToList(substring);
}
return null;
}
/**
* Returns the node to use as the scope for the given annotation node.
* You can't annotate an annotation itself (with {@code @SuppressLint}), but
@@ -968,8 +930,8 @@ public class AnnotationDetector extends Detector implements Detector.UastScanner
* suppress the error on this annotated element, not the whole surrounding class.
*/
@NonNull
private static PsiElement getAnnotationScope(@NonNull PsiAnnotation node) {
PsiElement scope = PsiTreeUtil.getParentOfType(node, PsiAnnotation.class, true);
private static UElement getAnnotationScope(@NonNull UAnnotation node) {
UElement scope = UastUtils.getParentOfType(node, UAnnotation.class, true);
if (scope == null) {
scope = node;
}
@@ -106,30 +106,10 @@ import com.intellij.psi.PsiReferenceExpression;
import com.intellij.psi.PsiResourceListElement;
import com.intellij.psi.PsiType;
import org.jetbrains.uast.UBinaryExpression;
import org.jetbrains.uast.UBinaryExpressionWithType;
import org.jetbrains.uast.UBlockExpression;
import org.jetbrains.uast.UCallExpression;
import org.jetbrains.uast.UCatchClause;
import org.jetbrains.uast.UClass;
import org.jetbrains.uast.UElement;
import org.jetbrains.uast.UExpression;
import org.jetbrains.uast.UFile;
import org.jetbrains.uast.UIfExpression;
import org.jetbrains.uast.UImportStatement;
import org.jetbrains.uast.ULiteralExpression;
import org.jetbrains.uast.ULocalVariable;
import org.jetbrains.uast.UMethod;
import org.jetbrains.uast.UReturnExpression;
import org.jetbrains.uast.USimpleNameReferenceExpression;
import org.jetbrains.uast.USwitchClauseExpression;
import org.jetbrains.uast.UTryExpression;
import org.jetbrains.uast.UVariable;
import org.jetbrains.uast.UastBinaryOperator;
import org.jetbrains.uast.UastOperator;
import org.jetbrains.uast.UastUtils;
import org.jetbrains.uast.*;
import org.jetbrains.uast.expressions.UReferenceExpression;
import org.jetbrains.uast.expressions.UTypeReferenceExpression;
import org.jetbrains.uast.java.JavaUAnnotation;
import org.jetbrains.uast.util.UastExpressionUtils;
import org.jetbrains.uast.visitor.AbstractUastVisitor;
import org.jetbrains.uast.visitor.UastVisitor;
@@ -1923,12 +1903,13 @@ public class ApiDetector extends ResourceXmlDetector
}
PsiModifierList modifierList = method.getModifierList();
if (!checkRequiresApi(expression, method, modifierList)) {
List<UAnnotation> annotations = JavaUAnnotation.wrap(modifierList.getAnnotations());
if (!checkRequiresApi(expression, method, annotations)) {
PsiClass containingClass = method.getContainingClass();
if (containingClass != null) {
modifierList = containingClass.getModifierList();
if (modifierList != null) {
checkRequiresApi(expression, method, modifierList);
checkRequiresApi(expression, method, annotations);
}
}
}
@@ -1938,9 +1919,11 @@ public class ApiDetector extends ResourceXmlDetector
}
// Look for @RequiresApi in modifier lists
private boolean checkRequiresApi(UCallExpression expression, PsiMethod method,
PsiModifierList modifierList) {
for (PsiAnnotation annotation : modifierList.getAnnotations()) {
private boolean checkRequiresApi(
UCallExpression expression,
PsiMethod method,
List<UAnnotation> annotations) {
for (UAnnotation annotation : annotations) {
if (REQUIRES_API_ANNOTATION.equals(annotation.getQualifiedName())) {
int api = (int) SupportAnnotationDetector.getLongAttribute(annotation,
ATTR_VALUE, -1);
@@ -1953,10 +1936,10 @@ public class ApiDetector extends ResourceXmlDetector
if (api > minSdk) {
int target = getTargetApi(expression);
if (target == -1 || api > target) {
if (ApiDetector.isWithinVersionCheckConditional(expression, api, mContext)) {
if (isWithinVersionCheckConditional(expression, api, mContext)) {
return true;
}
if (ApiDetector.isPrecededByVersionCheckExit(expression, api, mContext)) {
if (isPrecededByVersionCheckExit(expression, api, mContext)) {
return true;
}
@@ -2588,7 +2571,7 @@ public class ApiDetector extends ResourceXmlDetector
}
}
private static boolean isPrecededByVersionCheckExit(UElement element, int api,
protected static boolean isPrecededByVersionCheckExit(UElement element, int api,
JavaContext context) {
//noinspection unchecked
UExpression currentExpression = UastUtils.getParentOfType(element, UExpression.class,
@@ -19,45 +19,17 @@ import static com.android.SdkConstants.CLASS_INTENT;
import static com.android.tools.klint.checks.SupportAnnotationDetector.PERMISSION_ANNOTATION;
import static com.android.tools.klint.checks.SupportAnnotationDetector.PERMISSION_ANNOTATION_READ;
import static com.android.tools.klint.checks.SupportAnnotationDetector.PERMISSION_ANNOTATION_WRITE;
import static org.jetbrains.uast.UastBinaryExpressionWithTypeKind.TYPE_CAST;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.tools.klint.client.api.UastLintUtils;
import com.android.tools.klint.detector.api.JavaContext;
import com.intellij.psi.PsiAnnotation;
import com.intellij.psi.PsiAssignmentExpression;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiConditionalExpression;
import com.intellij.psi.PsiDeclarationStatement;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiExpression;
import com.intellij.psi.PsiExpressionList;
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.PsiModifierList;
import com.intellij.psi.PsiNameValuePair;
import com.intellij.psi.PsiNewExpression;
import com.intellij.psi.PsiParenthesizedExpression;
import com.intellij.psi.PsiReferenceExpression;
import com.intellij.psi.PsiStatement;
import com.intellij.psi.PsiTypeCastExpression;
import com.intellij.psi.PsiVariable;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.uast.UBinaryExpressionWithType;
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.UastCallKind;
import org.jetbrains.uast.UastLiteralUtils;
import org.jetbrains.uast.UastUtils;
import org.jetbrains.uast.*;
import org.jetbrains.uast.expressions.UReferenceExpression;
import org.jetbrains.uast.util.UastExpressionUtils;
@@ -188,25 +160,24 @@ public class PermissionFinder {
} else if (node instanceof UReferenceExpression) {
PsiElement resolved = ((UReferenceExpression) node).resolve();
if (resolved instanceof PsiField) {
PsiField field = (PsiField) resolved;
UField field = (UField) mContext.getUastContext().convertElementWithParent(resolved, UField.class);
if (field == null) {
return null;
}
if (mOperation == Operation.ACTION) {
PsiModifierList modifierList = field.getModifierList();
PsiAnnotation annotation = modifierList != null
? modifierList.findAnnotation(PERMISSION_ANNOTATION) : null;
UAnnotation annotation = field.findAnnotation(PERMISSION_ANNOTATION);
if (annotation != null) {
return getPermissionRequirement(field, annotation);
}
} else if (mOperation == Operation.READ || mOperation == Operation.WRITE) {
String fqn = mOperation == Operation.READ
? PERMISSION_ANNOTATION_READ : PERMISSION_ANNOTATION_WRITE;
PsiModifierList modifierList = field.getModifierList();
PsiAnnotation annotation = modifierList != null
? modifierList.findAnnotation(fqn) : null;
UAnnotation annotation = field.findAnnotation(fqn);
if (annotation != null) {
PsiNameValuePair[] attributes = annotation.getParameterList().getAttributes();
PsiNameValuePair o = attributes.length == 1 ? attributes[0] : null;
if (o != null && o.getValue() instanceof PsiAnnotation) {
annotation = (PsiAnnotation) o.getValue();
List<UNamedExpression> attributes = annotation.getAttributeValues();
UNamedExpression o = attributes.size() == 1 ? attributes.get(0) : null;
if (o != null && o.getExpression() instanceof UAnnotation) {
annotation = (UAnnotation) o.getExpression();
if (PERMISSION_ANNOTATION.equals(annotation.getQualifiedName())) {
return getPermissionRequirement(field, annotation);
}
@@ -242,8 +213,8 @@ public class PermissionFinder {
@NonNull
private Result getPermissionRequirement(
@NonNull PsiField field,
@NonNull PsiAnnotation annotation) {
PermissionRequirement requirement = PermissionRequirement.create(mContext, annotation);
@NonNull UAnnotation annotation) {
PermissionRequirement requirement = PermissionRequirement.create(annotation);
PsiClass containingClass = field.getContainingClass();
String name = containingClass != null
? containingClass.getName() + "." + field.getName()
@@ -34,6 +34,8 @@ import com.intellij.psi.PsiAnnotation;
import com.intellij.psi.PsiAnnotationMemberValue;
import com.intellij.psi.PsiArrayInitializerMemberValue;
import com.intellij.psi.tree.IElementType;
import org.jetbrains.uast.*;
import org.jetbrains.uast.util.UastExpressionUtils;
import java.util.Arrays;
import java.util.Collections;
@@ -48,7 +50,7 @@ public abstract class PermissionRequirement {
public static final String ATTR_PROTECTION_LEVEL = "protectionLevel"; //$NON-NLS-1$
public static final String VALUE_DANGEROUS = "dangerous"; //$NON-NLS-1$
protected final PsiAnnotation annotation;
protected final UAnnotation annotation;
private int firstApi;
private int lastApi;
@@ -102,15 +104,12 @@ public abstract class PermissionRequirement {
}
};
private PermissionRequirement(@NonNull PsiAnnotation annotation) {
private PermissionRequirement(@NonNull UAnnotation annotation) {
this.annotation = annotation;
}
@NonNull
public static PermissionRequirement create(
@NonNull JavaContext context,
@NonNull PsiAnnotation annotation) {
public static PermissionRequirement create(@NonNull UAnnotation annotation) {
String value = getAnnotationStringValue(annotation, ATTR_VALUE);
if (value != null && !value.isEmpty()) {
return new Single(annotation, value);
@@ -138,10 +137,10 @@ public abstract class PermissionRequirement {
}
@Nullable
public static Boolean getAnnotationBooleanValue(@Nullable PsiAnnotation annotation,
public static Boolean getAnnotationBooleanValue(@Nullable UAnnotation annotation,
@NonNull String name) {
if (annotation != null) {
PsiAnnotationMemberValue attributeValue = annotation.findDeclaredAttributeValue(name);
UNamedExpression attributeValue = annotation.findDeclaredAttributeValue(name);
if (attributeValue == null && ATTR_VALUE.equals(name)) {
attributeValue = annotation.findDeclaredAttributeValue(null);
}
@@ -158,10 +157,10 @@ public abstract class PermissionRequirement {
}
@Nullable
public static Long getAnnotationLongValue(@Nullable PsiAnnotation annotation,
public static Long getAnnotationLongValue(@Nullable UAnnotation annotation,
@NonNull String name) {
if (annotation != null) {
PsiAnnotationMemberValue attributeValue = annotation.findDeclaredAttributeValue(name);
UNamedExpression attributeValue = annotation.findDeclaredAttributeValue(name);
if (attributeValue == null && ATTR_VALUE.equals(name)) {
attributeValue = annotation.findDeclaredAttributeValue(null);
}
@@ -178,10 +177,10 @@ public abstract class PermissionRequirement {
}
@Nullable
public static Double getAnnotationDoubleValue(@Nullable PsiAnnotation annotation,
public static Double getAnnotationDoubleValue(@Nullable UAnnotation annotation,
@NonNull String name) {
if (annotation != null) {
PsiAnnotationMemberValue attributeValue = annotation.findDeclaredAttributeValue(name);
UNamedExpression attributeValue = annotation.findDeclaredAttributeValue(name);
if (attributeValue == null && ATTR_VALUE.equals(name)) {
attributeValue = annotation.findDeclaredAttributeValue(null);
}
@@ -198,10 +197,10 @@ public abstract class PermissionRequirement {
}
@Nullable
public static String getAnnotationStringValue(@Nullable PsiAnnotation annotation,
public static String getAnnotationStringValue(@Nullable UAnnotation annotation,
@NonNull String name) {
if (annotation != null) {
PsiAnnotationMemberValue attributeValue = annotation.findDeclaredAttributeValue(name);
UNamedExpression attributeValue = annotation.findDeclaredAttributeValue(name);
if (attributeValue == null && ATTR_VALUE.equals(name)) {
attributeValue = annotation.findDeclaredAttributeValue(null);
}
@@ -218,19 +217,22 @@ public abstract class PermissionRequirement {
}
@Nullable
public static String[] getAnnotationStringValues(@Nullable PsiAnnotation annotation,
public static String[] getAnnotationStringValues(@Nullable UAnnotation annotation,
@NonNull String name) {
if (annotation != null) {
PsiAnnotationMemberValue attributeValue = annotation.findDeclaredAttributeValue(name);
UNamedExpression attributeValue = annotation.findDeclaredAttributeValue(name);
if (attributeValue == null && ATTR_VALUE.equals(name)) {
attributeValue = annotation.findDeclaredAttributeValue(null);
}
if (attributeValue instanceof PsiArrayInitializerMemberValue) {
PsiAnnotationMemberValue[] initializers =
((PsiArrayInitializerMemberValue) attributeValue).getInitializers();
List<String> result = Lists.newArrayListWithCapacity(initializers.length);
if (attributeValue == null) {
return null;
}
if (UastExpressionUtils.isArrayInitializer(attributeValue.getExpression())) {
List<UExpression> initializers =
((UCallExpression) attributeValue.getExpression()).getValueArguments();
List<String> result = Lists.newArrayListWithCapacity(initializers.size());
ConstantEvaluator constantEvaluator = new ConstantEvaluator(null);
for (PsiAnnotationMemberValue element : initializers) {
for (UExpression element : initializers) {
Object o = constantEvaluator.evaluate(element);
if (o instanceof String) {
result.add((String)o);
@@ -243,22 +245,20 @@ public abstract class PermissionRequirement {
}
} else {
// Use constant evaluator since we want to resolve field references as well
if (attributeValue != null) {
Object o = ConstantEvaluator.evaluate(null, attributeValue);
if (o instanceof String) {
return new String[]{(String) o};
} else if (o instanceof String[]) {
return (String[])o;
} else if (o instanceof Object[]) {
Object[] array = (Object[]) o;
List<String> strings = Lists.newArrayListWithCapacity(array.length);
for (Object element : array) {
if (element instanceof String) {
strings.add((String) element);
}
Object o = ConstantEvaluator.evaluate(null, attributeValue.getExpression());
if (o instanceof String) {
return new String[]{(String) o};
} else if (o instanceof String[]) {
return (String[])o;
} else if (o instanceof Object[]) {
Object[] array = (Object[]) o;
List<String> strings = Lists.newArrayListWithCapacity(array.length);
for (Object element : array) {
if (element instanceof String) {
strings.add((String) element);
}
return strings.toArray(new String[0]);
}
return strings.toArray(new String[0]);
}
}
}
@@ -416,7 +416,7 @@ public abstract class PermissionRequirement {
private static class Single extends PermissionRequirement {
public final String name;
public Single(@NonNull PsiAnnotation annotation, @NonNull String name) {
public Single(@NonNull UAnnotation annotation, @NonNull String name) {
super(annotation);
this.name = name;
}
@@ -496,7 +496,7 @@ public abstract class PermissionRequirement {
public final List<PermissionRequirement> permissions;
public Many(
@NonNull PsiAnnotation annotation,
@NonNull UAnnotation annotation,
IElementType operator,
String[] names) {
super(annotation);
@@ -1167,7 +1167,7 @@ public class StringFormatDetector extends ResourceXmlDetector implements Detecto
(PsiVariable) resolved);
if (initializer != null &&
(UastExpressionUtils.isNewArray(initializer) ||
UastExpressionUtils.isNestedArrayInitializer(initializer))) {
UastExpressionUtils.isArrayInitializer(initializer))) {
argWasReference = true;
// Now handled by check below
lastArg = initializer;
@@ -1176,11 +1176,11 @@ public class StringFormatDetector extends ResourceXmlDetector implements Detecto
}
if (UastExpressionUtils.isNewArray(lastArg) ||
UastExpressionUtils.isNestedArrayInitializer(lastArg)) {
UastExpressionUtils.isArrayInitializer(lastArg)) {
UCallExpression arrayInitializer = (UCallExpression) lastArg;
if (UastExpressionUtils.isNewArrayWithInitializer(lastArg) ||
UastExpressionUtils.isNestedArrayInitializer(lastArg)) {
UastExpressionUtils.isArrayInitializer(lastArg)) {
callCount = arrayInitializer.getValueArgumentCount();
knownArity = true;
} else if (UastExpressionUtils.isNewArrayWithDimensions(lastArg)) {
@@ -42,7 +42,6 @@ import static com.android.tools.klint.checks.PermissionRequirement.getAnnotation
import static com.android.tools.klint.checks.PermissionRequirement.getAnnotationDoubleValue;
import static com.android.tools.klint.checks.PermissionRequirement.getAnnotationLongValue;
import static com.android.tools.klint.checks.PermissionRequirement.getAnnotationStringValue;
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;
@@ -74,27 +73,23 @@ import com.android.utils.XmlUtils;
import com.google.common.base.Joiner;
import com.google.common.collect.Sets;
import com.intellij.psi.PsiAnnotation;
import com.intellij.psi.PsiAnnotationMemberValue;
import com.intellij.psi.PsiArrayInitializerMemberValue;
import com.intellij.psi.PsiArrayType;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiClassType;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiField;
import com.intellij.psi.PsiJavaCodeReferenceElement;
import com.intellij.psi.PsiLiteral;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiModifier;
import com.intellij.psi.PsiModifierList;
import com.intellij.psi.PsiNameValuePair;
import com.intellij.psi.PsiParameter;
import com.intellij.psi.PsiParameterList;
import com.intellij.psi.PsiReference;
import com.intellij.psi.PsiType;
import com.intellij.psi.PsiVariable;
import org.jetbrains.uast.*;
import org.jetbrains.uast.expressions.UReferenceExpression;
import org.jetbrains.uast.java.JavaUAnnotation;
import org.jetbrains.uast.util.UastExpressionUtils;
import org.jetbrains.uast.visitor.AbstractUastVisitor;
import org.jetbrains.uast.visitor.UastVisitor;
@@ -284,9 +279,9 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
@NonNull JavaContext context,
@NonNull PsiMethod method,
@NonNull UCallExpression call,
@NonNull PsiAnnotation annotation,
@NonNull PsiAnnotation[] allMethodAnnotations,
@NonNull PsiAnnotation[] allClassAnnotations) {
@NonNull UAnnotation annotation,
@NonNull List<UAnnotation> allMethodAnnotations,
@NonNull List<UAnnotation> allClassAnnotations) {
String signature = annotation.getQualifiedName();
if (signature == null) {
return;
@@ -296,7 +291,7 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
|| signature.endsWith(".CheckReturnValue")) {
checkResult(context, call, method, annotation);
} else if (signature.equals(PERMISSION_ANNOTATION)) {
PermissionRequirement requirement = PermissionRequirement.create(context, annotation);
PermissionRequirement requirement = PermissionRequirement.create(annotation);
checkPermission(context, call, method, null, requirement);
} else if (signature.endsWith(THREAD_SUFFIX)
&& signature.startsWith(SUPPORT_ANNOTATIONS_PREFIX)) {
@@ -310,9 +305,9 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
@NonNull UExpression argument,
@NonNull UCallExpression call,
@NonNull PsiMethod method,
@NonNull PsiAnnotation[] annotations) {
@NonNull List<UAnnotation> annotations) {
boolean handledResourceTypes = false;
for (PsiAnnotation annotation : annotations) {
for (UAnnotation annotation : annotations) {
String signature = annotation.getQualifiedName();
if (signature == null) {
continue;
@@ -356,7 +351,7 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
// Handle all resource type annotations in one go: there could be multiple
// resource type annotations specified on the same element; we need to
// know about them all up front.
for (PsiAnnotation a : annotations) {
for (UAnnotation a : annotations) {
String s = a.getQualifiedName();
if (s != null && s.endsWith(RES_SUFFIX)) {
String typeString = s.substring(SUPPORT_ANNOTATIONS_PREFIX.length(),
@@ -485,7 +480,7 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
if (!requirement.isSatisfied(permissions)) {
// See if it looks like we're holding the permission implicitly by @RequirePermission
// annotations in the surrounding context
permissions = addLocalPermissions(context, permissions, node);
permissions = addLocalPermissions(permissions, node);
if (!requirement.isSatisfied(permissions)) {
if (isIgnoredInIde(MISSING_PERMISSION, context, node)) {
return;
@@ -567,35 +562,32 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
@NonNull
private static PermissionHolder addLocalPermissions(
@NonNull JavaContext context,
@NonNull PermissionHolder permissions,
@NonNull UElement node) {
@NonNull UElement node
) {
// Accumulate @RequirePermissions available in the local context
UMethod method = UastUtils.getParentOfType(node, UMethod.class, true);
if (method == null) {
return permissions;
}
PsiAnnotation annotation = method.getModifierList().findAnnotation(PERMISSION_ANNOTATION);
permissions = mergeAnnotationPermissions(context, permissions, annotation);
UAnnotation annotation = method.findAnnotation(PERMISSION_ANNOTATION);
permissions = mergeAnnotationPermissions(permissions, annotation);
PsiClass containingClass = method.getContainingClass();
UClass containingClass = UastUtils.getContainingUClass(method);
if (containingClass != null) {
PsiModifierList modifierList = containingClass.getModifierList();
if (modifierList != null) {
annotation = modifierList.findAnnotation(PERMISSION_ANNOTATION);
permissions = mergeAnnotationPermissions(context, permissions, annotation);
}
annotation = containingClass.findAnnotation(PERMISSION_ANNOTATION);
permissions = mergeAnnotationPermissions(permissions, annotation);
}
return permissions;
}
@NonNull
private static PermissionHolder mergeAnnotationPermissions(
@NonNull JavaContext context,
@NonNull PermissionHolder permissions,
@Nullable PsiAnnotation annotation) {
@Nullable UAnnotation annotation
) {
if (annotation != null) {
PermissionRequirement requirement = PermissionRequirement.create(context, annotation);
PermissionRequirement requirement = PermissionRequirement.create(annotation);
permissions = SetPermissionLookup.join(permissions, requirement);
}
@@ -759,7 +751,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) {
@NonNull PsiMethod method, @NonNull UAnnotation annotation) {
if (isExpressionValueUnused(node)) {
String methodName = JavaContext.getMethodName(node);
String suggested = getAnnotationStringValue(annotation, ATTR_SUGGEST);
@@ -806,9 +798,9 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
@NonNull UElement node,
@NonNull PsiMethod method,
@NonNull String signature,
@NonNull PsiAnnotation annotation,
@NonNull PsiAnnotation[] allMethodAnnotations,
@NonNull PsiAnnotation[] allClassAnnotations) {
@NonNull UAnnotation annotation,
@NonNull List<UAnnotation> allMethodAnnotations,
@NonNull List<UAnnotation> allClassAnnotations) {
List<String> threadContext = getThreadContext(context, node);
if (threadContext != null && !isCompatibleThread(threadContext, signature)
&& !isIgnoredInIde(THREAD, context, node)) {
@@ -823,7 +815,7 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
assert containsAnnotation(allMethodAnnotations, annotation);
// See if any of the *other* annotations are compatible.
Boolean isFirst = null;
for (PsiAnnotation other : allMethodAnnotations) {
for (UAnnotation other : allMethodAnnotations) {
if (other == annotation) {
if (isFirst == null) {
isFirst = true;
@@ -870,9 +862,9 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
}
public static boolean containsAnnotation(
@NonNull PsiAnnotation[] array,
@NonNull PsiAnnotation annotation) {
for (PsiAnnotation a : array) {
@NonNull List<UAnnotation> array,
@NonNull UAnnotation annotation) {
for (UAnnotation a : array) {
if (a == annotation) {
return true;
}
@@ -881,8 +873,8 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
return false;
}
public static boolean containsThreadingAnnotation(@NonNull PsiAnnotation[] array) {
for (PsiAnnotation annotation : array) {
public static boolean containsThreadingAnnotation(@NonNull List<UAnnotation> array) {
for (UAnnotation annotation : array) {
if (isThreadingAnnotation(annotation)) {
return true;
}
@@ -891,7 +883,7 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
return false;
}
public static boolean isThreadingAnnotation(@NonNull PsiAnnotation annotation) {
public static boolean isThreadingAnnotation(@NonNull UAnnotation annotation) {
String signature = annotation.getQualifiedName();
return signature != null
&& signature.endsWith(THREAD_SUFFIX)
@@ -1225,9 +1217,9 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
private static void checkIntRange(
@NonNull JavaContext context,
@NonNull PsiAnnotation annotation,
@NonNull UAnnotation annotation,
@NonNull UElement argument,
@NonNull PsiAnnotation[] allAnnotations) {
@NonNull List<UAnnotation> allAnnotations) {
String message = getIntRangeError(context, annotation, argument);
if (message != null) {
if (findIntDef(allAnnotations) != null) {
@@ -1248,7 +1240,7 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
@Nullable
private static String getIntRangeError(
@NonNull JavaContext context,
@NonNull PsiAnnotation annotation,
@NonNull UAnnotation annotation,
@NonNull UElement argument) {
if (UastExpressionUtils.isNewArrayWithInitializer(argument)) {
UCallExpression newExpression = (UCallExpression) argument;
@@ -1295,7 +1287,7 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
private static void checkFloatRange(
@NonNull JavaContext context,
@NonNull PsiAnnotation annotation,
@NonNull UAnnotation annotation,
@NonNull UElement argument) {
Object object = ConstantEvaluator.evaluate(context, argument);
if (!(object instanceof Number)) {
@@ -1387,7 +1379,7 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
private static void checkSize(
@NonNull JavaContext context,
@NonNull PsiAnnotation annotation,
@NonNull UAnnotation annotation,
@NonNull UElement argument) {
int actual;
boolean isString = false;
@@ -1459,9 +1451,9 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
}
@Nullable
private static PsiAnnotation findIntRange(
@NonNull PsiAnnotation[] annotations) {
for (PsiAnnotation annotation : annotations) {
private static UAnnotation findIntRange(
@NonNull List<UAnnotation> annotations) {
for (UAnnotation annotation : annotations) {
if (INT_RANGE_ANNOTATION.equals(annotation.getQualifiedName())) {
return annotation;
}
@@ -1471,8 +1463,8 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
}
@Nullable
static PsiAnnotation findIntDef(@NonNull PsiAnnotation[] annotations) {
for (PsiAnnotation annotation : annotations) {
static UAnnotation findIntDef(@NonNull List<UAnnotation> annotations) {
for (UAnnotation annotation : annotations) {
if (INT_DEF_ANNOTATION.equals(annotation.getQualifiedName())) {
return annotation;
}
@@ -1483,11 +1475,11 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
private static void checkTypeDefConstant(
@NonNull JavaContext context,
@NonNull PsiAnnotation annotation,
@NonNull UAnnotation annotation,
@Nullable UElement argument,
@Nullable UElement errorNode,
boolean flag,
@NonNull PsiAnnotation[] allAnnotations) {
@NonNull List<UAnnotation> allAnnotations) {
if (argument == null) {
return;
}
@@ -1615,10 +1607,10 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
}
private static void checkTypeDefConstant(@NonNull JavaContext context,
@NonNull PsiAnnotation annotation, @NonNull UElement argument,
@NonNull UAnnotation annotation, @NonNull UElement argument,
@Nullable UElement errorNode, boolean flag, Object value,
@NonNull PsiAnnotation[] allAnnotations) {
PsiAnnotation rangeAnnotation = findIntRange(allAnnotations);
@NonNull List<UAnnotation> allAnnotations) {
UAnnotation rangeAnnotation = findIntRange(allAnnotations);
if (rangeAnnotation != null) {
// Allow @IntRange on this number
if (getIntRangeError(context, rangeAnnotation, argument) == null) {
@@ -1626,18 +1618,17 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
}
}
PsiAnnotationMemberValue allowed = getAnnotationValue(annotation);
UExpression allowed = getAnnotationValue(annotation);
if (allowed == null) {
return;
}
if (allowed instanceof PsiArrayInitializerMemberValue) {
PsiArrayInitializerMemberValue initializerExpression =
(PsiArrayInitializerMemberValue) allowed;
PsiAnnotationMemberValue[] initializers = initializerExpression.getInitializers();
for (PsiAnnotationMemberValue expression : initializers) {
if (expression instanceof PsiLiteral) {
if (value.equals(((PsiLiteral)expression).getValue())) {
if (UastExpressionUtils.isArrayInitializer(allowed)) {
UCallExpression initializerExpression = (UCallExpression) allowed;
List<UExpression> initializers = initializerExpression.getValueArguments();
for (UExpression expression : initializers) {
if (expression instanceof ULiteralExpression) {
if (value.equals(((ULiteralExpression)expression).getValue())) {
return;
}
} else if (expression instanceof ExternalReferenceExpression) {
@@ -1646,8 +1637,8 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
if (resolved != null && resolved.equals(value)) {
return;
}
} else if (expression instanceof PsiReference) {
PsiElement resolved = ((PsiReference) expression).resolve();
} else if (expression instanceof UReferenceExpression) {
PsiElement resolved = ((UReferenceExpression) expression).resolve();
if (resolved != null && resolved.equals(value)) {
return;
}
@@ -1669,23 +1660,27 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
}
}
private static void reportTypeDef(@NonNull JavaContext context,
@NonNull PsiAnnotation annotation, @NonNull UElement argument,
@Nullable UElement errorNode, @NonNull PsiAnnotation[] allAnnotations) {
private static void reportTypeDef(
@NonNull JavaContext context,
@NonNull UAnnotation annotation,
@NonNull UElement argument,
@Nullable UElement errorNode,
@NonNull List<UAnnotation> allAnnotations
) {
// reportTypeDef(context, argument, errorNode, false, allowedValues, allAnnotations);
PsiAnnotationMemberValue allowed = getAnnotationValue(annotation);
if (allowed instanceof PsiArrayInitializerMemberValue) {
PsiArrayInitializerMemberValue initializerExpression =
(PsiArrayInitializerMemberValue) allowed;
PsiAnnotationMemberValue[] initializers = initializerExpression.getInitializers();
UExpression allowed = getAnnotationValue(annotation);
if (UastExpressionUtils.isArrayInitializer(allowed)) {
UCallExpression initializerExpression =
(UCallExpression) allowed;
List<UExpression> initializers = initializerExpression.getValueArguments();
reportTypeDef(context, argument, errorNode, false, initializers, allAnnotations);
}
}
private static void reportTypeDef(@NonNull JavaContext context, @NonNull UElement node,
@Nullable UElement errorNode, boolean flag,
@NonNull PsiAnnotationMemberValue[] allowedValues,
@NonNull PsiAnnotation[] allAnnotations) {
@NonNull List<UExpression> allowedValues,
@NonNull List<UAnnotation> allAnnotations) {
if (errorNode == null) {
errorNode = node;
}
@@ -1701,7 +1696,7 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
message = "Must be one of: " + values;
}
PsiAnnotation rangeAnnotation = findIntRange(allAnnotations);
UAnnotation rangeAnnotation = findIntRange(allAnnotations);
if (rangeAnnotation != null) {
// Allow @IntRange on this number
String rangeError = getIntRangeError(context, rangeAnnotation, node);
@@ -1715,27 +1710,29 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
}
@Nullable
private static PsiAnnotationMemberValue getAnnotationValue(@NonNull PsiAnnotation annotation) {
PsiNameValuePair[] attributes = annotation.getParameterList().getAttributes();
for (PsiNameValuePair pair : attributes) {
if (pair.getName() == null || pair.getName().equals(ATTR_VALUE)) {
return pair.getValue();
}
private static UExpression getAnnotationValue(@NonNull UAnnotation annotation) {
UNamedExpression value = annotation.findDeclaredAttributeValue(ATTR_VALUE);
if (value == null) {
value = annotation.findDeclaredAttributeValue(null);
}
return null;
if (value == null) {
return null;
}
return value.getExpression();
}
private static String listAllowedValues(@NonNull UElement context,
@NonNull PsiAnnotationMemberValue[] allowedValues) {
@NonNull List<UExpression> allowedValues) {
StringBuilder sb = new StringBuilder();
for (PsiAnnotationMemberValue allowedValue : allowedValues) {
for (UExpression allowedValue : allowedValues) {
String s = null;
PsiElement resolved = null;
if (allowedValue instanceof ExternalReferenceExpression) {
resolved = UastLintUtils.resolve(
(ExternalReferenceExpression) allowedValue, context);
} else if (allowedValue instanceof PsiReference) {
resolved = ((PsiReference) allowedValue).resolve();
} else if (allowedValue instanceof UReferenceExpression) {
resolved = ((UReferenceExpression) allowedValue).resolve();
}
if (resolved instanceof PsiField) {
@@ -1749,7 +1746,7 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
}
if (s == null) {
s = allowedValue.getText();
s = allowedValue.asSourceString();
}
if (sb.length() > 0) {
sb.append(", ");
@@ -1759,7 +1756,7 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
return sb.toString();
}
static double getDoubleAttribute(@NonNull PsiAnnotation annotation,
static double getDoubleAttribute(@NonNull UAnnotation annotation,
@NonNull String name, double defaultValue) {
Double value = getAnnotationDoubleValue(annotation, name);
if (value != null) {
@@ -1769,7 +1766,7 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
return defaultValue;
}
static long getLongAttribute(@NonNull PsiAnnotation annotation,
static long getLongAttribute(@NonNull UAnnotation annotation,
@NonNull String name, long defaultValue) {
Long value = getAnnotationLongValue(annotation, name);
if (value != null) {
@@ -1779,7 +1776,7 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
return defaultValue;
}
static boolean getBoolean(@NonNull PsiAnnotation annotation,
static boolean getBoolean(@NonNull UAnnotation annotation,
@NonNull String name, boolean defaultValue) {
Boolean value = getAnnotationBooleanValue(annotation, name);
if (value != null) {
@@ -1909,27 +1906,30 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
public void checkCall(PsiMethod method, UCallExpression call) {
JavaEvaluator evaluator = mContext.getEvaluator();
PsiAnnotation[] methodAnnotations = evaluator.getAllAnnotations(method);
methodAnnotations = filterRelevantAnnotations(evaluator, methodAnnotations);
List<UAnnotation> methodAnnotations;
{
PsiAnnotation[] annotations = evaluator.getAllAnnotations(method);
methodAnnotations = JavaUAnnotation.wrap(filterRelevantAnnotations(evaluator, annotations));
}
// Look for annotations on the class as well: these trickle
// down to all the methods in the class
PsiClass containingClass = method.getContainingClass();
PsiAnnotation[] classAnnotations;
List<UAnnotation> classAnnotations;
if (containingClass != null) {
classAnnotations = evaluator.getAllAnnotations(containingClass);
classAnnotations = filterRelevantAnnotations(evaluator, classAnnotations);
PsiAnnotation[] annotations = evaluator.getAllAnnotations(containingClass);
classAnnotations = JavaUAnnotation.wrap(filterRelevantAnnotations(evaluator, annotations));
} else {
classAnnotations = PsiAnnotation.EMPTY_ARRAY;
classAnnotations = Collections.emptyList();
}
for (PsiAnnotation annotation : methodAnnotations) {
for (UAnnotation annotation : methodAnnotations) {
checkMethodAnnotation(mContext, method, call, annotation, methodAnnotations,
classAnnotations);
}
if (classAnnotations.length > 0) {
for (PsiAnnotation annotation : classAnnotations) {
if (!classAnnotations.isEmpty()) {
for (UAnnotation annotation : classAnnotations) {
checkMethodAnnotation(mContext, method, call, annotation, methodAnnotations,
classAnnotations);
}
@@ -1938,14 +1938,14 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
List<UExpression> arguments = call.getValueArguments();
PsiParameterList parameterList = method.getParameterList();
PsiParameter[] parameters = parameterList.getParameters();
PsiAnnotation[] annotations = null;
List<UAnnotation> annotations = null;
for (int i = 0, n = Math.min(parameters.length, arguments.size());
i < n;
i++) {
UExpression argument = arguments.get(i);
PsiParameter parameter = parameters[i];
annotations = evaluator.getAllAnnotations(parameter);
annotations = filterRelevantAnnotations(evaluator, annotations);
annotations = JavaUAnnotation.wrap(
filterRelevantAnnotations(evaluator, evaluator.getAllAnnotations(parameter)));
checkParameterAnnotations(mContext, argument, call, method, annotations);
}
if (annotations != null) {
@@ -0,0 +1,913 @@
/*
* Copyright (C) 2013 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 org.jetbrains.android.inspections.klint;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.tools.klint.checks.ApiDetector;
import com.android.tools.klint.checks.ApiLookup;
import com.android.tools.klint.client.api.UastLintUtils;
import com.android.tools.klint.detector.api.*;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.PsiClassReferenceType;
import com.intellij.psi.util.MethodSignatureUtil;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.uast.*;
import org.jetbrains.uast.expressions.UInstanceExpression;
import org.jetbrains.uast.util.UastExpressionUtils;
import org.jetbrains.uast.visitor.AbstractUastVisitor;
import org.jetbrains.uast.visitor.UastVisitor;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import static org.jetbrains.android.inspections.klint.IntellijLintUtils.SUPPRESS_LINT_FQCN;
import static org.jetbrains.android.inspections.klint.IntellijLintUtils.SUPPRESS_WARNINGS_FQCN;
/**
* Intellij-specific version of the {@link ApiDetector} which uses the PSI structure
* to check accesses
* <p>
* TODO:
* <ul>
* <li> Port this part from the bytecode based check:
* if (owner.equals("java/text/SimpleDateFormat")) {
* checkSimpleDateFormat(context, method, node, minSdk);
* }
* </li>
* <li>Compare to the bytecode based results</li>
* </ul>
*/
public class IntellijApiDetector extends ApiDetector {
@SuppressWarnings("unchecked")
public static final Implementation IMPLEMENTATION = new Implementation(
IntellijApiDetector.class,
EnumSet.of(Scope.RESOURCE_FILE, Scope.MANIFEST, Scope.JAVA_FILE),
Scope.MANIFEST_SCOPE,
Scope.RESOURCE_FILE_SCOPE,
Scope.JAVA_FILE_SCOPE
);
@NonNls
private static final String TARGET_API_FQCN = "android.annotation.TargetApi";
@Override
public List<Class<? extends UElement>> getApplicableUastTypes() {
return Collections.<Class<? extends UElement>>singletonList(UFile.class);
}
// TODO: Reuse the parent ApiVisitor to share more code between these two!
@Override
public UastVisitor createUastVisitor(@NonNull final JavaContext context) {
return new AbstractUastVisitor() {
@Override
public boolean visitFile(@NotNull UFile file) {
List<UClass> classes = file.getClasses();
if (!classes.isEmpty()) {
// TODO: This is weird; I should just perform the per class checks as part of visitClass!!
file.accept(new ApiCheckVisitor(context, classes.get(0), file));
}
return super.visitFile(file);
}
};
}
private static int getTargetApi(@NonNull UElement e, @NonNull UFile file) {
UElement element = e;
// Search upwards for target api annotations
while (element != null && element != file) {
if (element instanceof UAnnotated) {
UAnnotated owner = (UAnnotated)element;
UAnnotation annotation = owner.findAnnotation(TARGET_API_FQCN);
if (annotation == null) {
annotation = owner.findAnnotation(REQUIRES_API_ANNOTATION);
}
if (annotation != null) {
for (UNamedExpression pair : annotation.getAttributeValues()) {
UExpression v = pair.getExpression();
if (v instanceof ULiteralExpression) {
ULiteralExpression literal = (ULiteralExpression)v;
Object value = literal.getValue();
if (value instanceof Integer) {
return (Integer) value;
} else if (value instanceof String) {
return codeNameToApi((String) value);
}
} else if (UastExpressionUtils.isArrayInitializer(v)) {
UCallExpression mv = (UCallExpression)v;
for (UExpression mmv : mv.getValueArguments()) {
if (mmv instanceof ULiteralExpression) {
ULiteralExpression literal = (ULiteralExpression)mmv;
Object value = literal.getValue();
if (value instanceof Integer) {
return (Integer) value;
} else if (value instanceof String) {
return codeNameToApi((String) value);
}
}
}
} else if (v instanceof UResolvable) {
PsiElement resolved = ((UResolvable) v).resolve();
if (resolved != null) {
String fqcn = UastLintUtils.getQualifiedName(resolved);
return codeNameToApi(fqcn);
} else {
return codeNameToApi(v.asRenderString());
}
}
}
}
}
element = element.getContainingElement();
}
return -1;
}
private class ApiCheckVisitor extends AbstractUastVisitor {
private final JavaContext myContext;
private boolean mySeenSuppress;
private boolean mySeenTargetApi;
private final UClass myClass;
private final UFile myFile;
private final boolean myCheckAccess;
private boolean myCheckOverride;
private String myFrameworkParent;
public ApiCheckVisitor(JavaContext context, UClass clz, UFile file) {
myContext = context;
myClass = clz;
myFile = file;
myCheckAccess = context.isEnabled(UNSUPPORTED) || context.isEnabled(INLINED);
myCheckOverride = context.isEnabled(OVERRIDE)
&& context.getMainProject().getBuildSdk() >= 1;
int depth = 0;
if (myCheckOverride) {
myFrameworkParent = null;
UClass superClass = myClass.getUastSuperClass();
while (superClass != null) {
String fqcn = superClass.getQualifiedName();
if (fqcn == null) {
myCheckOverride = false;
} else if (fqcn.startsWith("android.") //$NON-NLS-1$
|| fqcn.startsWith("java.") //$NON-NLS-1$
|| fqcn.startsWith("javax.")) { //$NON-NLS-1$
if (!fqcn.equals(CommonClassNames.JAVA_LANG_OBJECT)) {
myFrameworkParent = ClassContext.getInternalName(fqcn);
}
break;
}
superClass = superClass.getUastSuperClass();
depth++;
if (depth == 500) {
// Shouldn't happen in practice; this prevents the IDE from
// hanging if the user has accidentally typed in an incorrect
// super class which creates a cycle.
break;
}
}
if (myFrameworkParent == null) {
myCheckOverride = false;
}
}
}
@Override
public boolean visitAnnotation(@NotNull UAnnotation annotation) {
String fqcn = annotation.getQualifiedName();
if (TARGET_API_FQCN.equals(fqcn) || REQUIRES_API_ANNOTATION.equals(fqcn)) {
mySeenTargetApi = true;
}
else if (SUPPRESS_LINT_FQCN.equals(fqcn) || SUPPRESS_WARNINGS_FQCN.equals(fqcn)) {
mySeenSuppress = true;
}
return super.visitAnnotation(annotation);
}
@Override
public boolean visitMethod(@NotNull UMethod method) {
// API check for default methods
if (method.getModifierList().hasExplicitModifier(PsiModifier.DEFAULT)) {
int api = 24; // minSdk for default methods
int minSdk = getMinSdk(myContext);
if (!isSuppressed(api, method, minSdk)) {
Location location = IntellijLintUtils.getUastLocation(myContext.file, method);
String message = String.format("Default method requires API level %1$d (current min is %2$d)", api, minSdk);
myContext.report(UNSUPPORTED, location, message);
}
}
if (!myCheckOverride) {
return super.visitMethod(method);
}
int buildSdk = myContext.getMainProject().getBuildSdk();
String name = method.getName();
assert myFrameworkParent != null;
String desc = IntellijLintUtils.getInternalDescription(method, false, false);
if (desc == null) {
// Couldn't compute description of method for some reason; probably
// failure to resolve parameter types
return super.visitMethod(method);
}
int api = mApiDatabase.getCallVersion(myFrameworkParent, name, desc);
if (api > buildSdk && buildSdk != -1) {
if (mySeenSuppress &&
IntellijLintUtils.isSuppressed(method, myFile.getPsi(), OVERRIDE)) {
return super.visitMethod(method);
}
// TODO: Don't complain if it's annotated with @Override; that means
// somehow the build target isn't correct.
String fqcn;
PsiClass containingClass = method.getContainingClass();
if (containingClass != null) {
String className = containingClass.getName();
String fullClassName = containingClass.getQualifiedName();
if (fullClassName != null) {
className = fullClassName;
}
fqcn = className + '#' + name;
} else {
fqcn = name;
}
String message = String.format(
"This method is not overriding anything with the current build " +
"target, but will in API level %1$d (current target is %2$d): %3$s",
api, buildSdk, fqcn);
PsiElement locationNode = method.getNameIdentifier();
if (locationNode == null) {
locationNode = method;
}
Location location = IntellijLintUtils.getLocation(myContext.file, locationNode);
myContext.report(OVERRIDE, location, message);
}
return super.visitMethod(method);
}
@Override
public boolean visitClass(@NotNull UClass aClass) {
if (!myCheckAccess) {
return super.visitClass(aClass);
}
if (aClass.isAnnotationType()) {
PsiModifierList modifierList = aClass.getModifierList();
if (modifierList != null) {
for (PsiAnnotation annotation : modifierList.getAnnotations()) {
String name = annotation.getQualifiedName();
if ("java.lang.annotation.Repeatable".equals(name)) {
int api = 24; // minSdk for repeatable annotations
int minSdk = getMinSdk(myContext);
if (!isSuppressed(api, aClass, minSdk)) {
Location location = IntellijLintUtils.getLocation(myContext.file, annotation);
String message = String.format("Repeatable annotation requires API level %1$d (current min is %2$d)", api, minSdk);
myContext.report(UNSUPPORTED, location, message);
}
}
}
}
}
for (PsiClassType type : aClass.getSuperTypes()) {
String signature = IntellijLintUtils.getInternalName(type);
if (signature == null) {
continue;
}
int api = mApiDatabase.getClassVersion(signature);
if (api == -1) {
continue;
}
int minSdk = getMinSdk(myContext);
if (api <= minSdk) {
continue;
}
if (mySeenTargetApi) {
int target = getTargetApi(aClass, myFile);
if (target != -1) {
if (api <= target) {
continue;
}
}
}
if (mySeenSuppress && IntellijLintUtils.isSuppressed(aClass, myFile.getPsi(), UNSUPPORTED)) {
continue;
}
Location location;
if (type instanceof PsiClassReferenceType) {
PsiReference reference = ((PsiClassReferenceType)type).getReference();
PsiElement element = reference.getElement();
if (isWithinVersionCheckConditional(aClass, api, myContext)) {
continue;
}
if (isPrecededByVersionCheckExit(aClass, api, myContext)) {
continue;
}
location = IntellijLintUtils.getLocation(myContext.file, element);
} else {
location = IntellijLintUtils.getLocation(myContext.file, aClass); //TODO to super type reference
}
String fqcn = type.getClassName();
String message = String.format("Class requires API level %1$d (current min is %2$d): %3$s", api, minSdk, fqcn);
myContext.report(UNSUPPORTED, location, message);
}
return super.visitClass(aClass);
}
@Override
public boolean visitSimpleNameReferenceExpression(@NotNull USimpleNameReferenceExpression expression) {
if (!myCheckAccess) {
return super.visitSimpleNameReferenceExpression(expression);
}
PsiElement resolved = expression.resolve();
if (resolved != null) {
if (resolved instanceof PsiField) {
PsiField field = (PsiField)resolved;
PsiClass containingClass = field.getContainingClass();
if (containingClass == null) {
return super.visitSimpleNameReferenceExpression(expression);
}
String owner = IntellijLintUtils.getInternalName(containingClass);
if (owner == null) {
return super.visitSimpleNameReferenceExpression(expression); // Couldn't resolve type
}
String name = field.getName();
if (name == null) {
return super.visitSimpleNameReferenceExpression(expression);
}
int api = mApiDatabase.getFieldVersion(owner, name);
if (api == -1) {
return super.visitSimpleNameReferenceExpression(expression);
}
int minSdk = getMinSdk(myContext);
if (isSuppressed(api, expression, minSdk)) {
return super.visitSimpleNameReferenceExpression(expression);
}
Location location = IntellijLintUtils.getUastLocation(myContext.file, expression);
String fqcn = containingClass.getQualifiedName();
String message = String.format(
"Field requires API level %1$d (current min is %2$d): %3$s",
api, minSdk, fqcn + '#' + name);
Issue issue = UNSUPPORTED;
// When accessing primitive types or Strings, the values get copied into
// the class files (e.g. get inlined) which has a separate issue type:
// INLINED.
PsiType type = field.getType();
if (PsiType.INT.equals(type) || PsiType.CHAR.equals(type) || PsiType.BOOLEAN.equals(type)
|| PsiType.DOUBLE.equals(type) || PsiType.FLOAT.equals(type) || PsiType.BYTE.equals(type)
|| type.equalsToText(CommonClassNames.JAVA_LANG_STRING)) {
issue = INLINED;
// Some usages of inlined constants are okay:
if (isBenignConstantUsage(expression, name, owner)) {
return super.visitSimpleNameReferenceExpression(expression);
}
}
myContext.report(issue, location, message);
}
}
return super.visitSimpleNameReferenceExpression(expression);
}
@Override
public boolean visitTryExpression(@NotNull UTryExpression statement) {
List<PsiResourceListElement> resourceList = statement.getResources();
if (resourceList != null) {
int api = 19; // minSdk for try with resources
int minSdk = getMinSdk(myContext);
if (isSuppressed(api, statement, minSdk)) {
return super.visitTryExpression(statement);
}
Location location = IntellijLintUtils.getUastLocation(myContext.file, statement.getTryClause());
String message = String.format("Try-with-resources requires API level %1$d (current min is %2$d)", api, minSdk);
myContext.report(UNSUPPORTED, location, message);
}
for (UCatchClause catchClause : statement.getCatchClauses()) {
for (PsiParameter parameter : catchClause.getParameters()) {
PsiTypeElement typeElement = parameter.getTypeElement();
if (typeElement != null) {
checkCatchTypeElement(statement, typeElement, typeElement.getType());
}
}
}
return super.visitTryExpression(statement);
}
private void checkCatchTypeElement(@NonNull UTryExpression statement,
@NotNull PsiTypeElement typeElement,
@Nullable PsiType type) {
PsiClass resolved = null;
if (type instanceof PsiDisjunctionType) {
PsiDisjunctionType disjunctionType = (PsiDisjunctionType)type;
type = disjunctionType.getLeastUpperBound();
if (type instanceof PsiClassType) {
resolved = ((PsiClassType)type).resolve();
}
for (PsiElement child : typeElement.getChildren()) {
if (child instanceof PsiTypeElement) {
PsiTypeElement childTypeElement = (PsiTypeElement)child;
PsiType childType = childTypeElement.getType();
if (!type.equals(childType)) {
checkCatchTypeElement(statement, childTypeElement, childType);
}
}
}
} else if (type instanceof PsiClassReferenceType) {
PsiClassReferenceType referenceType = (PsiClassReferenceType)type;
resolved = referenceType.resolve();
} else if (type instanceof PsiClassType) {
resolved = ((PsiClassType)type).resolve();
}
if (resolved != null) {
String signature = IntellijLintUtils.getInternalName(resolved);
if (signature == null) {
return;
}
int api = mApiDatabase.getClassVersion(signature);
if (api == -1) {
return;
}
int minSdk = getMinSdk(myContext);
if (api <= minSdk) {
return;
}
if (mySeenTargetApi) {
int target = getTargetApi(statement, myFile);
if (target != -1) {
if (api <= target) {
return;
}
}
}
if (mySeenSuppress && IntellijLintUtils.isSuppressed(statement, myFile, UNSUPPORTED)) {
return;
}
Location location;
location = IntellijLintUtils.getLocation(myContext.file, typeElement);
String fqcn = resolved.getName();
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);
}
myContext.report(UNSUPPORTED, location, message);
}
}
private boolean isSuppressed(int api, UElement element, int minSdk) {
if (api <= minSdk) {
return true;
}
if (mySeenTargetApi) {
int target = getTargetApi(element, myFile);
if (target != -1) {
if (api <= target) {
return true;
}
}
}
if (mySeenSuppress &&
(IntellijLintUtils.isSuppressed(element, myFile, UNSUPPORTED) || IntellijLintUtils.isSuppressed(element, myFile, INLINED))) {
return true;
}
if (isWithinVersionCheckConditional(element, api, myContext)) {
return true;
}
if (isPrecededByVersionCheckExit(element, api, myContext)) {
return true;
}
return false;
}
@Override
public boolean visitBinaryExpressionWithType(@NotNull UBinaryExpressionWithType expression) {
if (myCheckAccess) {
UExpression operand = expression.getOperand();
PsiType operandType = operand.getExpressionType();
PsiType castType = expression.getType();
if (castType.equals(operandType)) {
return super.visitBinaryExpressionWithType(expression);
}
if (!(operandType instanceof PsiClassType)) {
return super.visitBinaryExpressionWithType(expression);
}
if (!(castType instanceof PsiClassType)) {
return super.visitBinaryExpressionWithType(expression);
}
PsiClassType classType = (PsiClassType)operandType;
PsiClassType interfaceType = (PsiClassType)castType;
checkCast(expression, classType, interfaceType);
}
return super.visitBinaryExpressionWithType(expression);
}
private void checkCast(@NotNull UElement node, @NotNull PsiClassType classType, @NotNull PsiClassType interfaceType) {
if (classType.equals(interfaceType)) {
return;
}
String classTypeInternal = IntellijLintUtils.getInternalName(classType);
String interfaceTypeInternal = IntellijLintUtils.getInternalName(interfaceType);
if (classTypeInternal == null || interfaceTypeInternal == null || "java/lang/Object".equals(interfaceTypeInternal)) {
return; // Couldn't resolve type
}
int api = mApiDatabase.getValidCastVersion(classTypeInternal, interfaceTypeInternal);
if (api == -1) {
return;
}
int minSdk = getMinSdk(myContext);
if (api <= minSdk) {
return;
}
if (isSuppressed(api, node, minSdk)) {
return;
}
Location location = IntellijLintUtils.getUastLocation(myContext.file, node);
String message = String.format("Cast from %1$s to %2$s requires API level %3$d (current min is %4$d)",
classType.getClassName(), interfaceType.getClassName(), api, minSdk);
myContext.report(UNSUPPORTED, location, message);
}
@Override
public boolean visitVariable(@NotNull UVariable variable) {
if (variable instanceof ULocalVariable) {
if (!myCheckAccess) {
return super.visitVariable(variable);
}
UExpression initializer = variable.getUastInitializer();
if (initializer == null) {
return super.visitVariable(variable);
}
PsiType initializerType = initializer.getExpressionType();
if (initializerType == null || !(initializerType instanceof PsiClassType)) {
return super.visitVariable(variable);
}
PsiType interfaceType = variable.getType();
if (initializerType.equals(interfaceType)) {
return super.visitVariable(variable);
}
if (!(interfaceType instanceof PsiClassType)) {
return super.visitVariable(variable);
}
checkCast(initializer, (PsiClassType)initializerType, (PsiClassType)interfaceType);
}
return super.visitVariable(variable);
}
@Override
public boolean visitBinaryExpression(@NotNull UBinaryExpression expression) {
if (expression.getOperator() instanceof UastBinaryOperator.AssignOperator) {
if (!myCheckAccess) {
return super.visitBinaryExpression(expression);
}
UExpression rExpression = expression.getRightOperand();
PsiType rhsType = rExpression.getExpressionType();
if (rhsType == null || !(rhsType instanceof PsiClassType)) {
return super.visitBinaryExpression(expression);
}
PsiType interfaceType = expression.getLeftOperand().getExpressionType();
if (rhsType.equals(interfaceType)) {
return super.visitBinaryExpression(expression);
}
if (!(interfaceType instanceof PsiClassType)) {
return super.visitBinaryExpression(expression);
}
checkCast(rExpression, (PsiClassType)rhsType, (PsiClassType)interfaceType);
}
return super.visitBinaryExpression(expression);
}
@Override
public boolean visitForEachExpression(@NotNull UForEachExpression statement) {
// The for each method will implicitly call iterator() on the
// Iterable that is used in the for each loop; make sure that
// the API level for that
if (!myCheckAccess) {
return super.visitForEachExpression(statement);
}
UExpression value = statement.getIteratedValue();
PsiType type = value.getExpressionType();
if (type instanceof PsiClassType) {
String expressionOwner = IntellijLintUtils.getInternalName((PsiClassType)type);
if (expressionOwner != null) {
int api = mApiDatabase.getClassVersion(expressionOwner);
if (api == -1) {
return super.visitForEachExpression(statement);
}
int minSdk = getMinSdk(myContext);
if (api <= minSdk) {
return super.visitForEachExpression(statement);
}
if (mySeenTargetApi) {
int target = getTargetApi(statement, myFile);
if (target != -1) {
if (api <= target) {
return super.visitForEachExpression(statement);
}
}
}
if (mySeenSuppress && IntellijLintUtils.isSuppressed(statement, myFile, UNSUPPORTED)) {
return super.visitForEachExpression(statement);
}
if (isWithinVersionCheckConditional(statement, api, myContext)) {
return super.visitForEachExpression(statement);
}
if (isPrecededByVersionCheckExit(statement, api, myContext)) {
return super.visitForEachExpression(statement);
}
Location location = IntellijLintUtils.getUastLocation(myContext.file, value);
String message = String.format("The type of the for loop iterated value is %1$s, which requires API level %2$d" +
" (current min is %3$d)", type.getCanonicalText(), api, minSdk);
// Add specific check ConcurrentHashMap#keySet and add workaround text.
// This was an unfortunate incompatible API change in Open JDK 8, which is
// not an issue for the Android SDK but is relevant if you're using a
// Java library.
if (value instanceof PsiMethodCallExpression) {
PsiMethodCallExpression valueCall = (PsiMethodCallExpression)value;
if ("keySet".equals(valueCall.getMethodExpression().getReferenceName())) {
PsiMethod keySet = valueCall.resolveMethod();
if (keySet != null && keySet.getContainingClass() != null &&
"java.util.concurrent.ConcurrentHashMap".equals(keySet.getContainingClass().getQualifiedName())) {
message += "; to work around this, add an explicit cast to (Map) before the `keySet` call.";
}
}
}
myContext.report(UNSUPPORTED, location, message);
}
}
return super.visitForEachExpression(statement);
}
@Override
public boolean visitCallExpression(@NotNull UCallExpression node) {
checkMethodCallExpression(node);
return super.visitCallExpression(node);
}
private void checkMethodCallExpression(@NotNull UCallExpression expression) {
super.visitCallExpression(expression);
if (!myCheckAccess) {
return;
}
PsiMethod method = expression.resolve();
if (method != null) {
PsiClass containingClass = method.getContainingClass();
if (containingClass == null) {
return;
}
PsiParameterList parameterList = method.getParameterList();
if (parameterList.getParametersCount() > 0) {
PsiParameter[] parameters = parameterList.getParameters();
List<UExpression> arguments = expression.getValueArguments();
for (int i = 0; i < parameters.length; i++) {
PsiType parameterType = parameters[i].getType();
if (parameterType instanceof PsiClassType) {
if (i >= arguments.size()) {
// We can end up with more arguments than parameters when
// there is a varargs call.
break;
}
UExpression argument = arguments.get(i);
PsiType argumentType = argument.getExpressionType();
if (argumentType == null || parameterType.equals(argumentType) || !(argumentType instanceof PsiClassType)) {
continue;
}
checkCast(argument, (PsiClassType)argumentType, (PsiClassType)parameterType);
}
}
}
String fqcn = containingClass.getQualifiedName();
String owner = IntellijLintUtils.getInternalName(containingClass);
if (owner == null) {
return; // Couldn't resolve type
}
String name = IntellijLintUtils.getInternalMethodName(method);
String desc = IntellijLintUtils.getInternalDescription(method, false, false);
if (desc == null) {
// Couldn't compute description of method for some reason; probably
// failure to resolve parameter types
return;
}
int api = mApiDatabase.getCallVersion(owner, name, desc);
if (api == -1) {
return;
}
int minSdk = getMinSdk(myContext);
if (api <= minSdk) {
return;
}
// The lint API database contains two optimizations:
// First, all members that were available in API 1 are omitted from the database, since that saves
// about half of the size of the database, and for API check purposes, we don't need to distinguish
// between "doesn't exist" and "available in all versions".
// Second, all inherited members were inlined into each class, so that it doesn't have to do a
// repeated search up the inheritance chain.
//
// Unfortunately, in this custom PSI detector, we look up the real resolved method, which can sometimes
// have a different minimum API.
//
// For example, SQLiteDatabase had a close() method from API 1. Therefore, calling SQLiteDatabase is supported
// in all versions. However, it extends SQLiteClosable, which in API 16 added "implements Closable". In
// this detector, if we have the following code:
// void test(SQLiteDatabase db) { db.close }
// here the call expression will be the close method on type SQLiteClosable. And that will result in an API
// requirement of API 16, since the close method it now resolves to is in API 16.
//
// To work around this, we can now look up the type of the call expression ("db" in the above, but it could
// have been more complicated), and if that's a different type than the type of the method, we look up
// *that* method from lint's database instead. Furthermore, it's possible for that method to return "-1"
// and we can't tell if that means "doesn't exist" or "present in API 1", we then check the package prefix
// to see whether we know it's an API method whose members should all have been inlined.
if (UastExpressionUtils.isMethodCall(expression)) {
UExpression qualifier = expression.getReceiver();
if (qualifier != null && !(qualifier instanceof UThisExpression) && !(qualifier instanceof USuperExpression)) {
PsiType type = qualifier.getExpressionType();
if (type != null && type instanceof PsiClassType) {
String expressionOwner = IntellijLintUtils.getInternalName((PsiClassType)type);
if (expressionOwner != null && !expressionOwner.equals(owner)) {
int specificApi = mApiDatabase.getCallVersion(expressionOwner, name, desc);
if (specificApi == -1) {
if (ApiLookup.isRelevantOwner(expressionOwner)) {
return;
}
} else if (specificApi <= minSdk) {
return;
} else {
// For example, for Bundle#getString(String,String) the API level is 12, whereas for
// BaseBundle#getString(String,String) the API level is 21. If the code specified a Bundle instead of
// a BaseBundle, reported the Bundle level in the error message instead.
if (specificApi < api) {
api = specificApi;
fqcn = expressionOwner.replace('/', '.');
}
api = Math.min(specificApi, api);
}
}
}
} else {
// Unqualified call; need to search in our super hierarchy
PsiClass cls = UastUtils.getContainingClass(expression);
//noinspection ConstantConditions
if (qualifier instanceof UThisExpression || qualifier instanceof USuperExpression) {
UInstanceExpression pte = (UInstanceExpression) qualifier;
PsiElement resolved = pte.resolve();
if (resolved instanceof PsiClass) {
cls = (PsiClass)resolved;
}
}
while (cls != null) {
if (cls instanceof PsiAnonymousClass) {
// If it's an unqualified call in an anonymous class, we need to rely on the
// resolve method to find out whether the method is picked up from the anonymous
// class chain or any outer classes
boolean found = false;
PsiClassType anonymousBaseType = ((PsiAnonymousClass)cls).getBaseClassType();
PsiClass anonymousBase = anonymousBaseType.resolve();
if (anonymousBase != null && anonymousBase.isInheritor(containingClass, true)) {
cls = anonymousBase;
found = true;
} else {
PsiClass surroundingBaseType = PsiTreeUtil.getParentOfType(cls, PsiClass.class, true);
if (surroundingBaseType != null && surroundingBaseType.isInheritor(containingClass, true)) {
cls = surroundingBaseType;
found = true;
}
}
if (!found) {
break;
}
}
String expressionOwner = IntellijLintUtils.getInternalName(cls);
if (expressionOwner == null) {
break;
}
int specificApi = mApiDatabase.getCallVersion(expressionOwner, name, desc);
if (specificApi == -1) {
if (ApiLookup.isRelevantOwner(expressionOwner)) {
return;
}
} else if (specificApi <= minSdk) {
return;
} else {
if (specificApi < api) {
api = specificApi;
fqcn = expressionOwner.replace('/', '.');
}
api = Math.min(specificApi, api);
break;
}
cls = cls.getSuperClass();
}
}
}
if (isSuppressed(api, expression, minSdk)) {
return;
}
// If you're simply calling super.X from method X, even if method X is in a higher API level than the minSdk, we're
// generally safe; that method should only be called by the framework on the right API levels. (There is a danger of
// somebody calling that method locally in other contexts, but this is hopefully unlikely.)
if (UastExpressionUtils.isMethodCall(expression)) {
if (expression.getReceiver() instanceof USuperExpression) {
PsiMethod containingMethod = UastUtils.getContainingMethod(expression);
if (containingMethod != null && name.equals(containingMethod.getName())
&& MethodSignatureUtil.areSignaturesEqual(method, containingMethod)
// We specifically exclude constructors from this check, because we do want to flag constructors requiring the
// new API level; it's highly likely that the constructor is called by local code so you should specifically
// investigate this as a developer
&& !method.isConstructor()) {
return;
}
}
}
UElement locationNode = expression.getMethodIdentifier();
if (locationNode == null) {
locationNode = expression;
}
Location location = IntellijLintUtils.getUastLocation(myContext.file, locationNode);
String message = String.format("Call requires API level %1$d (current min is %2$d): %3$s", api, minSdk,
fqcn + '#' + method.getName());
myContext.report(UNSUPPORTED, location, message);
}
}
}
}
@@ -68,6 +68,11 @@ public class IntellijLintIssueRegistry extends BuiltinIssueRegistry {
Implementation implementation = issue.getImplementation();
EnumSet<Scope> scope = implementation.getScope();
Class<? extends Detector> detectorClass = implementation.getDetectorClass();
if (detectorClass == ApiDetector.class) {
issue.setImplementation(IntellijApiDetector.IMPLEMENTATION);
} else if (detectorClass == ViewTypeDetector.class) {
issue.setImplementation(IntellijViewTypeDetector.IMPLEMENTATION);
}
if (detectorClass == SupportAnnotationDetector.class) {
// Handled by the ResourceTypeInspection
continue;
@@ -39,6 +39,10 @@ import com.intellij.psi.util.TypeConversionUtil;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.uast.*;
import org.jetbrains.uast.psi.PsiElementBacked;
import org.jetbrains.uast.psi.UElementWithLocation;
import org.jetbrains.uast.util.UastExpressionUtils;
import java.io.File;
import java.util.ArrayList;
@@ -88,6 +92,51 @@ public class IntellijLintUtils {
return Location.create(file, start, end);
}
/**
* Gets the location of the given element
*
* @param file the file containing the location
* @param element the element to look up the location for
* @return the location of the given element
*/
@NonNull
public static Location getUastLocation(@NonNull File file, @NonNull UElement element) {
//noinspection ConstantConditions
PsiFile containingPsiFile = UastUtils.getContainingFile(element).getPsi();
assert containingPsiFile.getVirtualFile() == null
|| FileUtil.filesEqual(VfsUtilCore.virtualToIoFile(containingPsiFile.getVirtualFile()), file);
if (element instanceof UClass) {
// Point to the name rather than the beginning of the javadoc
UClass clz = (UClass)element;
UElement nameIdentifier = clz.getUastAnchor();
if (nameIdentifier != null) {
element = nameIdentifier;
}
}
TextRange textRange = null;
if (element instanceof PsiElementBacked) {
PsiElement psi = ((PsiElementBacked) element).getPsi();
if (psi != null) {
textRange = psi.getTextRange();
}
} else if (element instanceof UElementWithLocation) {
UElementWithLocation elementWithLocation = (UElementWithLocation) element;
textRange = new TextRange(
elementWithLocation.getStartOffset(),
elementWithLocation.getEndOffset());
}
if (textRange == null) {
return Location.NONE;
}
Position start = new DefaultPosition(-1, -1, textRange.getStartOffset());
Position end = new DefaultPosition(-1, -1, textRange.getEndOffset());
return Location.create(file, start, end);
}
/**
* Returns the {@link PsiFile} associated with a given lint {@link Context}
*
@@ -169,6 +218,58 @@ public class IntellijLintUtils {
return false;
}
/**
* Returns true if the given issue is suppressed at the given element within the given file
*
* @param element the element to check
* @param file the file containing the element
* @param issue the issue to check
* @return true if the given issue is suppressed
*/
public static boolean isSuppressed(@NonNull UElement element, @NonNull UFile file, @NonNull Issue issue) {
// Search upwards for suppress lint and suppress warnings annotations
//noinspection ConstantConditions
while (element != null && element != file) { // otherwise it will keep going into directories!
if (element instanceof UAnnotated) {
UAnnotated annotated = (UAnnotated)element;
for (UAnnotation annotation : annotated.getAnnotations()) {
String fqcn = annotation.getQualifiedName();
if (fqcn != null && (fqcn.equals(SUPPRESS_LINT_FQCN) || fqcn.equals(SUPPRESS_WARNINGS_FQCN))) {
List<UNamedExpression> parameterList = annotation.getAttributeValues();
for (UNamedExpression pair : parameterList) {
UExpression v = pair.getExpression();
if (v instanceof ULiteralExpression) {
ULiteralExpression literal = (ULiteralExpression)v;
Object value = literal.getValue();
if (value instanceof String) {
if (isSuppressed(issue, (String) value)) {
return true;
}
}
} else if (UastExpressionUtils.isArrayInitializer(v)) {
UCallExpression mv = (UCallExpression)v;
for (UExpression mmv : mv.getValueArguments()) {
if (mmv instanceof ULiteralExpression) {
ULiteralExpression literal = (ULiteralExpression) mmv;
Object value = literal.getValue();
if (value instanceof String) {
if (isSuppressed(issue, (String) value)) {
return true;
}
}
}
}
}
}
}
}
}
element = element.getContainingElement();
}
return false;
}
/**
* Returns true if the given issue is suppressed by the given suppress string; this
* is typically the same as the issue id, but is allowed to not match case sensitively,