Skip to content

Commit 29b9521

Browse files
committed
Android lint check for api requirements for classes in catch clause (KT-13243)
Added test for #KT-14047, #KT-13243 Fixed, #KT-14047 Fixed
1 parent 387d5ff commit 29b9521

File tree

4 files changed

+95
-105
lines changed

4 files changed

+95
-105
lines changed

plugins/lint/lint-checks/src/com/android/tools/klint/checks/ApiDetector.java

Lines changed: 50 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -2023,7 +2023,7 @@ private void visitAssignmentExpression(UBinaryExpression expression) {
20232023
public boolean visitTryExpression(UTryExpression statement) {
20242024
List<PsiResourceListElement> resourceList = statement.getResources();
20252025
//noinspection VariableNotUsedInsideIf
2026-
if (!resourceList.isEmpty()) {
2026+
if (resourceList != null && !resourceList.isEmpty()) {
20272027
int api = 19; // minSdk for try with resources
20282028
int minSdk = getMinSdk(mContext);
20292029

@@ -2061,53 +2061,6 @@ public boolean visitTryExpression(UTryExpression statement) {
20612061
return super.visitTryExpression(statement);
20622062
}
20632063

2064-
private Location getCatchParametersLocation(JavaContext context, UCatchClause catchClause) {
2065-
List<UTypeReferenceExpression> types = catchClause.getTypeReferences();
2066-
if (types.isEmpty()) {
2067-
return Location.NONE;
2068-
}
2069-
2070-
Location first = context.getUastLocation(types.get(0));
2071-
if (types.size() < 2) {
2072-
return first;
2073-
}
2074-
2075-
Location last = context.getUastLocation(types.get(types.size() - 1));
2076-
File file = first.getFile();
2077-
Position start = first.getStart();
2078-
Position end = last.getEnd();
2079-
2080-
if (start == null) {
2081-
return Location.create(file);
2082-
}
2083-
2084-
return Location.create(file, start, end);
2085-
}
2086-
2087-
private boolean isMultiCatchReflectiveOperationException(UCatchClause catchClause) {
2088-
List<PsiType> types = catchClause.getTypes();
2089-
if (types.size() < 2) {
2090-
return false;
2091-
}
2092-
2093-
for (PsiType t : types) {
2094-
if(!isSubclassOfReflectiveOperationException(t)) {
2095-
return false;
2096-
}
2097-
}
2098-
2099-
return true;
2100-
}
2101-
2102-
private boolean isSubclassOfReflectiveOperationException(PsiType type) {
2103-
for (PsiType t : type.getSuperTypes()) {
2104-
if (REFLECTIVE_OPERATION_EXCEPTION.equals(t.getCanonicalText())) {
2105-
return true;
2106-
}
2107-
}
2108-
return false;
2109-
}
2110-
21112064
private void checkCatchTypeElement(@NonNull UTryExpression statement,
21122065
@NonNull UTypeReferenceExpression typeReference,
21132066
@Nullable PsiType type) {
@@ -2236,7 +2189,7 @@ private static boolean isSuppressed(
22362189
return false;
22372190
}
22382191

2239-
private static int getTargetApi(@Nullable UElement scope) {
2192+
public static int getTargetApi(@Nullable UElement scope) {
22402193
while (scope != null) {
22412194
if (scope instanceof PsiModifierListOwner) {
22422195
PsiModifierList modifierList = ((PsiModifierListOwner) scope).getModifierList();
@@ -2737,6 +2690,45 @@ else if (tokenType == UastBinaryOperator.EQUALS
27372690
return null;
27382691
}
27392692

2693+
2694+
public static Location getCatchParametersLocation(JavaContext context, UCatchClause catchClause) {
2695+
List<UTypeReferenceExpression> types = catchClause.getTypeReferences();
2696+
if (types.isEmpty()) {
2697+
return Location.NONE;
2698+
}
2699+
2700+
Location first = context.getUastLocation(types.get(0));
2701+
if (types.size() < 2) {
2702+
return first;
2703+
}
2704+
2705+
Location last = context.getUastLocation(types.get(types.size() - 1));
2706+
File file = first.getFile();
2707+
Position start = first.getStart();
2708+
Position end = last.getEnd();
2709+
2710+
if (start == null) {
2711+
return Location.create(file);
2712+
}
2713+
2714+
return Location.create(file, start, end);
2715+
}
2716+
2717+
public static boolean isMultiCatchReflectiveOperationException(UCatchClause catchClause) {
2718+
List<PsiType> types = catchClause.getTypes();
2719+
if (types.size() < 2) {
2720+
return false;
2721+
}
2722+
2723+
for (PsiType t : types) {
2724+
if(!isSubclassOfReflectiveOperationException(t)) {
2725+
return false;
2726+
}
2727+
}
2728+
2729+
return true;
2730+
}
2731+
27402732
private static boolean isAndedWithConditional(UElement element, int api, @Nullable UElement before) {
27412733
if (element instanceof UBinaryExpression) {
27422734
UBinaryExpression inner = (UBinaryExpression) element;
@@ -2782,4 +2774,13 @@ else if (tokenType == UastBinaryOperator.EQUALS
27822774

27832775
return false;
27842776
}
2777+
2778+
private static boolean isSubclassOfReflectiveOperationException(PsiType type) {
2779+
for (PsiType t : type.getSuperTypes()) {
2780+
if (REFLECTIVE_OPERATION_EXCEPTION.equals(t.getCanonicalText())) {
2781+
return true;
2782+
}
2783+
}
2784+
return false;
2785+
}
27852786
}

plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/IntellijApiDetector.java

Lines changed: 27 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.jetbrains.annotations.NotNull;
3030
import org.jetbrains.uast.*;
3131
import org.jetbrains.uast.expressions.UInstanceExpression;
32+
import org.jetbrains.uast.expressions.UTypeReferenceExpression;
3233
import org.jetbrains.uast.util.UastExpressionUtils;
3334
import org.jetbrains.uast.visitor.AbstractUastVisitor;
3435
import org.jetbrains.uast.visitor.UastVisitor;
@@ -419,48 +420,37 @@ public boolean visitTryExpression(@NotNull UTryExpression statement) {
419420
}
420421

421422
for (UCatchClause catchClause : statement.getCatchClauses()) {
422-
for (PsiParameter parameter : catchClause.getParameters()) {
423-
PsiTypeElement typeElement = parameter.getTypeElement();
424-
if (typeElement != null) {
425-
checkCatchTypeElement(statement, typeElement, typeElement.getType());
426-
}
423+
424+
// Special case reflective operation exception which can be implicitly used
425+
// with multi-catches: see issue 153406
426+
int minSdk = getMinSdk(myContext);
427+
if(minSdk < 19 && isMultiCatchReflectiveOperationException(catchClause)) {
428+
String message = String.format("Multi-catch with these reflection exceptions requires API level 19 (current min is %d) " +
429+
"because they get compiled to the common but new super type `ReflectiveOperationException`. " +
430+
"As a workaround either create individual catch statements, or catch `Exception`.",
431+
minSdk);
432+
433+
myContext.report(UNSUPPORTED, getCatchParametersLocation(myContext, catchClause), message);
434+
continue;
435+
}
436+
437+
for (UTypeReferenceExpression typeReference : catchClause.getTypeReferences()) {
438+
checkCatchTypeElement(statement, typeReference, typeReference.getType());
427439
}
428440
}
429441

430442
return super.visitTryExpression(statement);
431443
}
432444

433445
private void checkCatchTypeElement(@NonNull UTryExpression statement,
434-
@NotNull PsiTypeElement typeElement,
446+
@NonNull UTypeReferenceExpression typeReference,
435447
@Nullable PsiType type) {
436448
PsiClass resolved = null;
437-
if (type instanceof PsiDisjunctionType) {
438-
PsiDisjunctionType disjunctionType = (PsiDisjunctionType)type;
439-
type = disjunctionType.getLeastUpperBound();
440-
if (type instanceof PsiClassType) {
441-
resolved = ((PsiClassType)type).resolve();
442-
}
443-
for (PsiElement child : typeElement.getChildren()) {
444-
if (child instanceof PsiTypeElement) {
445-
PsiTypeElement childTypeElement = (PsiTypeElement)child;
446-
PsiType childType = childTypeElement.getType();
447-
if (!type.equals(childType)) {
448-
checkCatchTypeElement(statement, childTypeElement, childType);
449-
}
450-
}
451-
}
452-
} else if (type instanceof PsiClassReferenceType) {
453-
PsiClassReferenceType referenceType = (PsiClassReferenceType)type;
454-
resolved = referenceType.resolve();
455-
} else if (type instanceof PsiClassType) {
456-
resolved = ((PsiClassType)type).resolve();
449+
if (type instanceof PsiClassType) {
450+
resolved = ((PsiClassType) type).resolve();
457451
}
458452
if (resolved != null) {
459-
String signature = IntellijLintUtils.getInternalName(resolved);
460-
if (signature == null) {
461-
return;
462-
}
463-
453+
String signature = myContext.getEvaluator().getInternalName(resolved);
464454
int api = mApiDatabase.getClassVersion(signature);
465455
if (api == -1) {
466456
return;
@@ -469,31 +459,15 @@ private void checkCatchTypeElement(@NonNull UTryExpression statement,
469459
if (api <= minSdk) {
470460
return;
471461
}
472-
if (mySeenTargetApi) {
473-
int target = getTargetApi(statement, myFile);
474-
if (target != -1) {
475-
if (api <= target) {
476-
return;
477-
}
478-
}
479-
}
480-
if (mySeenSuppress && IntellijLintUtils.isSuppressed(statement, myFile, UNSUPPORTED)) {
462+
int target = getTargetApi(statement);
463+
if (target != -1 && api <= target) {
481464
return;
482465
}
483466

484-
Location location;
485-
location = IntellijLintUtils.getLocation(myContext.file, typeElement);
486-
String fqcn = resolved.getName();
487-
String message = String.format("Class requires API level %1$d (current min is %2$d): %3$s", api, minSdk, fqcn);
488-
489-
// Special case reflective operation exception which can be implicitly used
490-
// with multi-catches: see issue 153406
491-
if (api == 19 && "ReflectiveOperationException".equals(fqcn)) {
492-
message = String.format("Multi-catch with these reflection exceptions requires API level 19 (current min is %2$d) " +
493-
"because they get compiled to the common but new super type `ReflectiveOperationException`. " +
494-
"As a workaround either create individual catch statements, or catch `Exception`.",
495-
api, minSdk);
496-
}
467+
Location location = myContext.getUastLocation(typeReference);
468+
String fqcn = resolved.getQualifiedName();
469+
String message = String.format("Class requires API level %1$d (current min is %2$d): %3$s",
470+
api, minSdk, fqcn);
497471
myContext.report(UNSUPPORTED, location, message);
498472
}
499473
}

plugins/uast-kotlin/test/org.jetbrains.kotlin.uast/AbstractKotlinLintTest.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,9 @@ abstract class AbstractKotlinLintTest : KotlinAndroidTestCase() {
6363
}
6464
}
6565

66-
val virtualFile = myFixture.copyFileToProject(ktFile.absolutePath, "src/" + getTestName(true) + ".kt");
66+
val virtualFile = myFixture.copyFileToProject(ktFile.absolutePath, "src/" + getTestName(true) + ".kt")
6767
myFixture.configureFromExistingVirtualFile(virtualFile)
6868

69-
myFixture.doHighlighting()
7069
myFixture.checkHighlighting(true, false, false)
7170
}
7271

plugins/uast-kotlin/testData/lint/apiCheck.kt

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
<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>
1+
2+
// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintNewApiInspection
23
// INSPECTION_CLASS2: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintInlinedApiInspection
34
// INSPECTION_CLASS3: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintOverrideInspection
45

@@ -13,6 +14,7 @@ import android.view.ViewGroup
1314
import android.view.ViewGroup.LayoutParams
1415
import android.app.Activity
1516
import android.app.ApplicationErrorReport
17+
import android.graphics.Path
1618
import android.graphics.PorterDuff
1719
import android.graphics.Rect
1820
import android.os.Build
@@ -25,6 +27,7 @@ import android.os.Build.VERSION_CODES
2527
import android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH
2628
import android.os.Build.VERSION_CODES.JELLY_BEAN
2729
import android.os.Bundle
30+
import android.system.ErrnoException
2831
import android.widget.TextView
2932

3033
@Suppress("SENSELESS_COMPARISON", "UNUSED_EXPRESSION", "UsePropertyAccessSyntax", "UNUSED_VARIABLE", "unused", "UNUSED_PARAMETER", "DEPRECATION")
@@ -275,6 +278,19 @@ class ApiCallTest: Activity() {
275278
}
276279
}
277280

281+
fun testCatch() {
282+
try {
283+
284+
} catch (e: <error descr="Class requires API level 21 (current min is 1): android.system.ErrnoException">ErrnoException</error>) {
285+
286+
}
287+
}
288+
289+
fun testOverload() {
290+
// this overloaded addOval available only on API Level 21
291+
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)
292+
}
293+
278294
// Return type
279295
internal // API 14
280296
val gridLayout: GridLayout?

0 commit comments

Comments
 (0)