Android lint check for api requirements for classes in catch clause (KT-13243)
Added test for #KT-14047, #KT-13243 Fixed, #KT-14047 Fixed
This commit is contained in:
@@ -2023,7 +2023,7 @@ public class ApiDetector extends ResourceXmlDetector
|
|||||||
public boolean visitTryExpression(UTryExpression statement) {
|
public boolean visitTryExpression(UTryExpression statement) {
|
||||||
List<PsiResourceListElement> resourceList = statement.getResources();
|
List<PsiResourceListElement> resourceList = statement.getResources();
|
||||||
//noinspection VariableNotUsedInsideIf
|
//noinspection VariableNotUsedInsideIf
|
||||||
if (!resourceList.isEmpty()) {
|
if (resourceList != null && !resourceList.isEmpty()) {
|
||||||
int api = 19; // minSdk for try with resources
|
int api = 19; // minSdk for try with resources
|
||||||
int minSdk = getMinSdk(mContext);
|
int minSdk = getMinSdk(mContext);
|
||||||
|
|
||||||
@@ -2061,53 +2061,6 @@ public class ApiDetector extends ResourceXmlDetector
|
|||||||
return super.visitTryExpression(statement);
|
return super.visitTryExpression(statement);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Location getCatchParametersLocation(JavaContext context, UCatchClause catchClause) {
|
|
||||||
List<UTypeReferenceExpression> types = catchClause.getTypeReferences();
|
|
||||||
if (types.isEmpty()) {
|
|
||||||
return Location.NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
Location first = context.getUastLocation(types.get(0));
|
|
||||||
if (types.size() < 2) {
|
|
||||||
return first;
|
|
||||||
}
|
|
||||||
|
|
||||||
Location last = context.getUastLocation(types.get(types.size() - 1));
|
|
||||||
File file = first.getFile();
|
|
||||||
Position start = first.getStart();
|
|
||||||
Position end = last.getEnd();
|
|
||||||
|
|
||||||
if (start == null) {
|
|
||||||
return Location.create(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Location.create(file, start, end);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isMultiCatchReflectiveOperationException(UCatchClause catchClause) {
|
|
||||||
List<PsiType> types = catchClause.getTypes();
|
|
||||||
if (types.size() < 2) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (PsiType t : types) {
|
|
||||||
if(!isSubclassOfReflectiveOperationException(t)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isSubclassOfReflectiveOperationException(PsiType type) {
|
|
||||||
for (PsiType t : type.getSuperTypes()) {
|
|
||||||
if (REFLECTIVE_OPERATION_EXCEPTION.equals(t.getCanonicalText())) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkCatchTypeElement(@NonNull UTryExpression statement,
|
private void checkCatchTypeElement(@NonNull UTryExpression statement,
|
||||||
@NonNull UTypeReferenceExpression typeReference,
|
@NonNull UTypeReferenceExpression typeReference,
|
||||||
@Nullable PsiType type) {
|
@Nullable PsiType type) {
|
||||||
@@ -2236,7 +2189,7 @@ public class ApiDetector extends ResourceXmlDetector
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int getTargetApi(@Nullable UElement scope) {
|
public static int getTargetApi(@Nullable UElement scope) {
|
||||||
while (scope != null) {
|
while (scope != null) {
|
||||||
if (scope instanceof PsiModifierListOwner) {
|
if (scope instanceof PsiModifierListOwner) {
|
||||||
PsiModifierList modifierList = ((PsiModifierListOwner) scope).getModifierList();
|
PsiModifierList modifierList = ((PsiModifierListOwner) scope).getModifierList();
|
||||||
@@ -2737,6 +2690,45 @@ public class ApiDetector extends ResourceXmlDetector
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static Location getCatchParametersLocation(JavaContext context, UCatchClause catchClause) {
|
||||||
|
List<UTypeReferenceExpression> types = catchClause.getTypeReferences();
|
||||||
|
if (types.isEmpty()) {
|
||||||
|
return Location.NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
Location first = context.getUastLocation(types.get(0));
|
||||||
|
if (types.size() < 2) {
|
||||||
|
return first;
|
||||||
|
}
|
||||||
|
|
||||||
|
Location last = context.getUastLocation(types.get(types.size() - 1));
|
||||||
|
File file = first.getFile();
|
||||||
|
Position start = first.getStart();
|
||||||
|
Position end = last.getEnd();
|
||||||
|
|
||||||
|
if (start == null) {
|
||||||
|
return Location.create(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Location.create(file, start, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isMultiCatchReflectiveOperationException(UCatchClause catchClause) {
|
||||||
|
List<PsiType> types = catchClause.getTypes();
|
||||||
|
if (types.size() < 2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (PsiType t : types) {
|
||||||
|
if(!isSubclassOfReflectiveOperationException(t)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private static boolean isAndedWithConditional(UElement element, int api, @Nullable UElement before) {
|
private static boolean isAndedWithConditional(UElement element, int api, @Nullable UElement before) {
|
||||||
if (element instanceof UBinaryExpression) {
|
if (element instanceof UBinaryExpression) {
|
||||||
UBinaryExpression inner = (UBinaryExpression) element;
|
UBinaryExpression inner = (UBinaryExpression) element;
|
||||||
@@ -2782,4 +2774,13 @@ public class ApiDetector extends ResourceXmlDetector
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean isSubclassOfReflectiveOperationException(PsiType type) {
|
||||||
|
for (PsiType t : type.getSuperTypes()) {
|
||||||
|
if (REFLECTIVE_OPERATION_EXCEPTION.equals(t.getCanonicalText())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+27
-53
@@ -29,6 +29,7 @@ import org.jetbrains.annotations.NonNls;
|
|||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.uast.*;
|
import org.jetbrains.uast.*;
|
||||||
import org.jetbrains.uast.expressions.UInstanceExpression;
|
import org.jetbrains.uast.expressions.UInstanceExpression;
|
||||||
|
import org.jetbrains.uast.expressions.UTypeReferenceExpression;
|
||||||
import org.jetbrains.uast.util.UastExpressionUtils;
|
import org.jetbrains.uast.util.UastExpressionUtils;
|
||||||
import org.jetbrains.uast.visitor.AbstractUastVisitor;
|
import org.jetbrains.uast.visitor.AbstractUastVisitor;
|
||||||
import org.jetbrains.uast.visitor.UastVisitor;
|
import org.jetbrains.uast.visitor.UastVisitor;
|
||||||
@@ -419,11 +420,22 @@ public class IntellijApiDetector extends ApiDetector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (UCatchClause catchClause : statement.getCatchClauses()) {
|
for (UCatchClause catchClause : statement.getCatchClauses()) {
|
||||||
for (PsiParameter parameter : catchClause.getParameters()) {
|
|
||||||
PsiTypeElement typeElement = parameter.getTypeElement();
|
// Special case reflective operation exception which can be implicitly used
|
||||||
if (typeElement != null) {
|
// with multi-catches: see issue 153406
|
||||||
checkCatchTypeElement(statement, typeElement, typeElement.getType());
|
int minSdk = getMinSdk(myContext);
|
||||||
}
|
if(minSdk < 19 && isMultiCatchReflectiveOperationException(catchClause)) {
|
||||||
|
String message = String.format("Multi-catch with these reflection exceptions requires API level 19 (current min is %d) " +
|
||||||
|
"because they get compiled to the common but new super type `ReflectiveOperationException`. " +
|
||||||
|
"As a workaround either create individual catch statements, or catch `Exception`.",
|
||||||
|
minSdk);
|
||||||
|
|
||||||
|
myContext.report(UNSUPPORTED, getCatchParametersLocation(myContext, catchClause), message);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (UTypeReferenceExpression typeReference : catchClause.getTypeReferences()) {
|
||||||
|
checkCatchTypeElement(statement, typeReference, typeReference.getType());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -431,36 +443,14 @@ public class IntellijApiDetector extends ApiDetector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void checkCatchTypeElement(@NonNull UTryExpression statement,
|
private void checkCatchTypeElement(@NonNull UTryExpression statement,
|
||||||
@NotNull PsiTypeElement typeElement,
|
@NonNull UTypeReferenceExpression typeReference,
|
||||||
@Nullable PsiType type) {
|
@Nullable PsiType type) {
|
||||||
PsiClass resolved = null;
|
PsiClass resolved = null;
|
||||||
if (type instanceof PsiDisjunctionType) {
|
if (type instanceof PsiClassType) {
|
||||||
PsiDisjunctionType disjunctionType = (PsiDisjunctionType)type;
|
resolved = ((PsiClassType) type).resolve();
|
||||||
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) {
|
if (resolved != null) {
|
||||||
String signature = IntellijLintUtils.getInternalName(resolved);
|
String signature = myContext.getEvaluator().getInternalName(resolved);
|
||||||
if (signature == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int api = mApiDatabase.getClassVersion(signature);
|
int api = mApiDatabase.getClassVersion(signature);
|
||||||
if (api == -1) {
|
if (api == -1) {
|
||||||
return;
|
return;
|
||||||
@@ -469,31 +459,15 @@ public class IntellijApiDetector extends ApiDetector {
|
|||||||
if (api <= minSdk) {
|
if (api <= minSdk) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (mySeenTargetApi) {
|
int target = getTargetApi(statement);
|
||||||
int target = getTargetApi(statement, myFile);
|
if (target != -1 && api <= target) {
|
||||||
if (target != -1) {
|
|
||||||
if (api <= target) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (mySeenSuppress && IntellijLintUtils.isSuppressed(statement, myFile, UNSUPPORTED)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Location location;
|
Location location = myContext.getUastLocation(typeReference);
|
||||||
location = IntellijLintUtils.getLocation(myContext.file, typeElement);
|
String fqcn = resolved.getQualifiedName();
|
||||||
String fqcn = resolved.getName();
|
String message = String.format("Class requires API level %1$d (current min is %2$d): %3$s",
|
||||||
String message = String.format("Class requires API level %1$d (current min is %2$d): %3$s", api, minSdk, fqcn);
|
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);
|
myContext.report(UNSUPPORTED, location, message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,10 +63,9 @@ abstract class AbstractKotlinLintTest : KotlinAndroidTestCase() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val virtualFile = myFixture.copyFileToProject(ktFile.absolutePath, "src/" + getTestName(true) + ".kt");
|
val virtualFile = myFixture.copyFileToProject(ktFile.absolutePath, "src/" + getTestName(true) + ".kt")
|
||||||
myFixture.configureFromExistingVirtualFile(virtualFile)
|
myFixture.configureFromExistingVirtualFile(virtualFile)
|
||||||
|
|
||||||
myFixture.doHighlighting()
|
|
||||||
myFixture.checkHighlighting(true, false, false)
|
myFixture.checkHighlighting(true, false, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+17
-1
@@ -1,4 +1,5 @@
|
|||||||
<error descr="The SDK platform-tools version (23) is too old to check APIs compiled with API 23; please update">// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintNewApiInspection</error>
|
|
||||||
|
// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintNewApiInspection
|
||||||
// INSPECTION_CLASS2: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintInlinedApiInspection
|
// INSPECTION_CLASS2: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintInlinedApiInspection
|
||||||
// INSPECTION_CLASS3: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintOverrideInspection
|
// INSPECTION_CLASS3: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintOverrideInspection
|
||||||
|
|
||||||
@@ -13,6 +14,7 @@ import android.view.ViewGroup
|
|||||||
import android.view.ViewGroup.LayoutParams
|
import android.view.ViewGroup.LayoutParams
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.app.ApplicationErrorReport
|
import android.app.ApplicationErrorReport
|
||||||
|
import android.graphics.Path
|
||||||
import android.graphics.PorterDuff
|
import android.graphics.PorterDuff
|
||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
@@ -25,6 +27,7 @@ import android.os.Build.VERSION_CODES
|
|||||||
import android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH
|
import android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH
|
||||||
import android.os.Build.VERSION_CODES.JELLY_BEAN
|
import android.os.Build.VERSION_CODES.JELLY_BEAN
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.system.ErrnoException
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
|
||||||
@Suppress("SENSELESS_COMPARISON", "UNUSED_EXPRESSION", "UsePropertyAccessSyntax", "UNUSED_VARIABLE", "unused", "UNUSED_PARAMETER", "DEPRECATION")
|
@Suppress("SENSELESS_COMPARISON", "UNUSED_EXPRESSION", "UsePropertyAccessSyntax", "UNUSED_VARIABLE", "unused", "UNUSED_PARAMETER", "DEPRECATION")
|
||||||
@@ -275,6 +278,19 @@ class ApiCallTest: Activity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun testCatch() {
|
||||||
|
try {
|
||||||
|
|
||||||
|
} catch (e: <error descr="Class requires API level 21 (current min is 1): android.system.ErrnoException">ErrnoException</error>) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testOverload() {
|
||||||
|
// this overloaded addOval available only on API Level 21
|
||||||
|
Path().<error descr="Call requires API level 21 (current min is 1): android.graphics.Path#addOval">addOval</error>(0f, 0f, 0f, 0f, Path.Direction.CW)
|
||||||
|
}
|
||||||
|
|
||||||
// Return type
|
// Return type
|
||||||
internal // API 14
|
internal // API 14
|
||||||
val gridLayout: GridLayout?
|
val gridLayout: GridLayout?
|
||||||
|
|||||||
Reference in New Issue
Block a user