summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Matt Gilbride <mattgilbride@google.com> 2022-10-19 15:43:05 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2022-10-19 15:43:05 +0000
commitab3f6695d9c45ad0247ce27c76a033fe09a1f429 (patch)
treed875797508e1646ef14060050f8cc76481540fa5
parente491de2d288af45765da0c24d3b37d485d735ded (diff)
parent69dec36abefb81658dcedc6d3cab28b423cb139a (diff)
Merge "Refactor ManualPermissionCheckDetector for @PermissionMethod"
-rw-r--r--tools/lint/checks/src/main/java/com/google/android/lint/PermissionMethodDetector.kt6
-rw-r--r--tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt129
-rw-r--r--tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt67
-rw-r--r--tools/lint/checks/src/main/java/com/google/android/lint/aidl/ManualPermissionCheckDetector.kt57
-rw-r--r--tools/lint/checks/src/test/java/com/google/android/lint/aidl/ManualPermissionCheckDetectorTest.kt271
5 files changed, 404 insertions, 126 deletions
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/PermissionMethodDetector.kt b/tools/lint/checks/src/main/java/com/google/android/lint/PermissionMethodDetector.kt
index 68a450d956a8..1b0f03564c3b 100644
--- a/tools/lint/checks/src/main/java/com/google/android/lint/PermissionMethodDetector.kt
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/PermissionMethodDetector.kt
@@ -26,6 +26,7 @@ import com.android.tools.lint.detector.api.Scope
import com.android.tools.lint.detector.api.Severity
import com.android.tools.lint.detector.api.SourceCodeScanner
import com.android.tools.lint.detector.api.getUMethod
+import com.google.android.lint.aidl.hasPermissionMethodAnnotation
import com.intellij.psi.PsiType
import org.jetbrains.uast.UAnnotation
import org.jetbrains.uast.UBlockExpression
@@ -149,11 +150,6 @@ class PermissionMethodDetector : Detector(), SourceCodeScanner {
enabledByDefault = false
)
- private fun hasPermissionMethodAnnotation(method: UMethod): Boolean = method.annotations
- .any {
- it.hasQualifiedName(ANNOTATION_PERMISSION_METHOD)
- }
-
private fun isPermissionMethodReturnType(method: UMethod): Boolean =
listOf(PsiType.VOID, PsiType.INT, PsiType.BOOLEAN).contains(method.returnType)
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt
index 510611161ea8..d120e1d41c99 100644
--- a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt
@@ -18,37 +18,54 @@ package com.google.android.lint.aidl
import com.android.tools.lint.detector.api.JavaContext
import com.android.tools.lint.detector.api.Location
-import com.intellij.psi.PsiVariable
+import com.android.tools.lint.detector.api.getUMethod
+import org.jetbrains.kotlin.psi.psiUtil.parameterIndex
import org.jetbrains.uast.UCallExpression
-import org.jetbrains.uast.ULiteralExpression
-import org.jetbrains.uast.UQualifiedReferenceExpression
-import org.jetbrains.uast.USimpleNameReferenceExpression
-import org.jetbrains.uast.asRecursiveLogString
+import org.jetbrains.uast.evaluateString
+import org.jetbrains.uast.visitor.AbstractUastVisitor
/**
- * Helper ADT class that facilitates the creation of lint auto fixes
+ * Helper class that facilitates the creation of lint auto fixes
*
* Handles "Single" permission checks that should be migrated to @EnforcePermission(...), as well as consecutive checks
* that should be migrated to @EnforcePermission(allOf={...})
*
* TODO: handle anyOf style annotations
*/
-sealed class EnforcePermissionFix {
- abstract fun locations(): List<Location>
- abstract fun javaAnnotationParameter(): String
-
- fun javaAnnotation(): String = "@$ANNOTATION_ENFORCE_PERMISSION(${javaAnnotationParameter()})"
+data class EnforcePermissionFix(
+ val locations: List<Location>,
+ val permissionNames: List<String>
+) {
+ val annotation: String
+ get() {
+ val quotedPermissions = permissionNames.joinToString(", ") { """"$it"""" }
+ val annotationParameter =
+ if (permissionNames.size > 1) "allOf={$quotedPermissions}" else quotedPermissions
+ return "@$ANNOTATION_ENFORCE_PERMISSION($annotationParameter)"
+ }
companion object {
- fun fromCallExpression(callExpression: UCallExpression, context: JavaContext): SingleFix =
- SingleFix(
- getPermissionCheckLocation(context, callExpression),
- getPermissionCheckArgumentValue(callExpression)
- )
+ /**
+ * conditionally constructs EnforcePermissionFix from a UCallExpression
+ * @return EnforcePermissionFix if the called method is annotated with @PermissionMethod, else null
+ */
+ fun fromCallExpression(
+ context: JavaContext,
+ callExpression: UCallExpression
+ ): EnforcePermissionFix? =
+ if (isPermissionMethodCall(callExpression)) {
+ EnforcePermissionFix(
+ listOf(getPermissionCheckLocation(context, callExpression)),
+ getPermissionCheckValues(callExpression)
+ )
+ } else null
- fun maybeAddManifestPrefix(permissionName: String): String =
- if (permissionName.contains(".")) permissionName
- else "android.Manifest.permission.$permissionName"
+
+ fun compose(individuals: List<EnforcePermissionFix>): EnforcePermissionFix =
+ EnforcePermissionFix(
+ individuals.flatMap { it.locations },
+ individuals.flatMap { it.permissionNames }
+ )
/**
* Given a permission check, get its proper location
@@ -70,49 +87,51 @@ sealed class EnforcePermissionFix {
}
/**
- * Given a permission check and an argument,
- * pull out the permission value that is being used
+ * Given a @PermissionMethod, find arguments annotated with @PermissionName
+ * and pull out the permission value(s) being used. Also evaluates nested calls
+ * to @PermissionMethod(s) in the given method's body.
*/
- private fun getPermissionCheckArgumentValue(
- callExpression: UCallExpression,
- argumentPosition: Int = 0
- ): String {
+ private fun getPermissionCheckValues(
+ callExpression: UCallExpression
+ ): List<String> {
+ if (!isPermissionMethodCall(callExpression)) return emptyList()
- val identifier = when (
- val argument = callExpression.valueArguments.getOrNull(argumentPosition)
- ) {
- is UQualifiedReferenceExpression -> when (val selector = argument.selector) {
- is USimpleNameReferenceExpression ->
- ((selector.resolve() as PsiVariable).computeConstantValue() as String)
+ val result = mutableSetOf<String>() // protect against duplicate permission values
+ val visitedCalls = mutableSetOf<UCallExpression>() // don't visit the same call twice
+ val bfsQueue = ArrayDeque(listOf(callExpression))
- else -> throw RuntimeException(
- "Couldn't resolve argument: ${selector.asRecursiveLogString()}"
- )
- }
+ // Breadth First Search - evalutaing nested @PermissionMethod(s) in the available
+ // source code for @PermissionName(s).
+ while (bfsQueue.isNotEmpty()) {
+ val current = bfsQueue.removeFirst()
+ visitedCalls.add(current)
+ result.addAll(findPermissions(current))
- is USimpleNameReferenceExpression -> (
- (argument.resolve() as PsiVariable).computeConstantValue() as String)
+ current.resolve()?.getUMethod()?.accept(object : AbstractUastVisitor() {
+ override fun visitCallExpression(node: UCallExpression): Boolean {
+ if (isPermissionMethodCall(node) && node !in visitedCalls) {
+ bfsQueue.add(node)
+ }
+ return false
+ }
+ })
+ }
- is ULiteralExpression -> argument.value as String
+ return result.toList()
+ }
- else -> throw RuntimeException(
- "Couldn't resolve argument: ${argument?.asRecursiveLogString()}"
- )
- }
+ private fun findPermissions(
+ callExpression: UCallExpression,
+ ): List<String> {
+ val indices = callExpression.resolve()?.getUMethod()
+ ?.uastParameters
+ ?.filter(::hasPermissionNameAnnotation)
+ ?.mapNotNull { it.sourcePsi?.parameterIndex() }
+ ?: emptyList()
- return identifier.substringAfterLast(".")
+ return indices.mapNotNull {
+ callExpression.getArgumentForParameter(it)?.evaluateString()
+ }
}
}
}
-
-data class SingleFix(val location: Location, val permissionName: String) : EnforcePermissionFix() {
- override fun locations(): List<Location> = listOf(this.location)
- override fun javaAnnotationParameter(): String = maybeAddManifestPrefix(this.permissionName)
-}
-data class AllOfFix(val checks: List<SingleFix>) : EnforcePermissionFix() {
- override fun locations(): List<Location> = this.checks.map { it.location }
- override fun javaAnnotationParameter(): String =
- "allOf={${
- this.checks.joinToString(", ") { maybeAddManifestPrefix(it.permissionName) }
- }}"
-}
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt
new file mode 100644
index 000000000000..edbdd8d2adf3
--- /dev/null
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.lint.aidl
+
+import com.android.tools.lint.detector.api.getUMethod
+import com.google.android.lint.ANNOTATION_PERMISSION_METHOD
+import com.google.android.lint.ANNOTATION_PERMISSION_NAME
+import com.google.android.lint.CLASS_STUB
+import com.intellij.psi.PsiAnonymousClass
+import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.UMethod
+import org.jetbrains.uast.UParameter
+
+/**
+ * Given a UMethod, determine if this method is
+ * an entrypoint to an interface generated by AIDL,
+ * returning the interface name if so
+ */
+fun getContainingAidlInterface(node: UMethod): String? {
+ if (!isInClassCalledStub(node)) return null
+ for (superMethod in node.findSuperMethods()) {
+ for (extendsInterface in superMethod.containingClass?.extendsList?.referenceElements
+ ?: continue) {
+ if (extendsInterface.qualifiedName == IINTERFACE_INTERFACE) {
+ return superMethod.containingClass?.name
+ }
+ }
+ }
+ return null
+}
+
+private fun isInClassCalledStub(node: UMethod): Boolean {
+ (node.containingClass as? PsiAnonymousClass)?.let {
+ return it.baseClassReference.referenceName == CLASS_STUB
+ }
+ return node.containingClass?.extendsList?.referenceElements?.any {
+ it.referenceName == CLASS_STUB
+ } ?: false
+}
+
+fun isPermissionMethodCall(callExpression: UCallExpression): Boolean {
+ val method = callExpression.resolve()?.getUMethod() ?: return false
+ return hasPermissionMethodAnnotation(method)
+}
+
+fun hasPermissionMethodAnnotation(method: UMethod): Boolean = method.annotations
+ .any {
+ it.hasQualifiedName(ANNOTATION_PERMISSION_METHOD)
+ }
+
+fun hasPermissionNameAnnotation(parameter: UParameter) = parameter.annotations.any {
+ it.hasQualifiedName(ANNOTATION_PERMISSION_NAME)
+}
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/ManualPermissionCheckDetector.kt b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/ManualPermissionCheckDetector.kt
index 2cea39423f1d..2c53f390128c 100644
--- a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/ManualPermissionCheckDetector.kt
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/ManualPermissionCheckDetector.kt
@@ -25,9 +25,6 @@ import com.android.tools.lint.detector.api.JavaContext
import com.android.tools.lint.detector.api.Scope
import com.android.tools.lint.detector.api.Severity
import com.android.tools.lint.detector.api.SourceCodeScanner
-import com.google.android.lint.CLASS_STUB
-import com.google.android.lint.ENFORCE_PERMISSION_METHODS
-import com.intellij.psi.PsiAnonymousClass
import org.jetbrains.uast.UBlockExpression
import org.jetbrains.uast.UCallExpression
import org.jetbrains.uast.UElement
@@ -56,7 +53,7 @@ class ManualPermissionCheckDetector : Detector(), SourceCodeScanner {
val body = (node.uastBody as? UBlockExpression) ?: return
val fix = accumulateSimplePermissionCheckFixes(body) ?: return
- val javaRemoveFixes = fix.locations().map {
+ val javaRemoveFixes = fix.locations.map {
fix()
.replace()
.reformat(true)
@@ -67,7 +64,7 @@ class ManualPermissionCheckDetector : Detector(), SourceCodeScanner {
}
val javaAnnotateFix = fix()
- .annotate(fix.javaAnnotation())
+ .annotate(fix.annotation)
.range(context.getLocation(node))
.autoFix()
.build()
@@ -77,7 +74,7 @@ class ManualPermissionCheckDetector : Detector(), SourceCodeScanner {
context.report(
ISSUE_USE_ENFORCE_PERMISSION_ANNOTATION,
- fix.locations().last(),
+ fix.locations.last(),
message,
fix().composite(*javaRemoveFixes.toTypedArray(), javaAnnotateFix)
)
@@ -97,14 +94,14 @@ class ManualPermissionCheckDetector : Detector(), SourceCodeScanner {
*/
private fun accumulateSimplePermissionCheckFixes(methodBody: UBlockExpression):
EnforcePermissionFix? {
- val singleFixes = mutableListOf<SingleFix>()
+ val singleFixes = mutableListOf<EnforcePermissionFix>()
for (expression in methodBody.expressions) {
singleFixes.add(getPermissionCheckFix(expression) ?: break)
}
return when (singleFixes.size) {
0 -> null
1 -> singleFixes[0]
- else -> AllOfFix(singleFixes)
+ else -> EnforcePermissionFix.compose(singleFixes)
}
}
@@ -113,7 +110,7 @@ class ManualPermissionCheckDetector : Detector(), SourceCodeScanner {
* the helper for creating a lint auto fix to @EnforcePermission
*/
private fun getPermissionCheckFix(startingExpression: UElement?):
- SingleFix? {
+ EnforcePermissionFix? {
return when (startingExpression) {
is UQualifiedReferenceExpression -> getPermissionCheckFix(
startingExpression.selector
@@ -121,11 +118,8 @@ class ManualPermissionCheckDetector : Detector(), SourceCodeScanner {
is UIfExpression -> getPermissionCheckFix(startingExpression.condition)
- is UCallExpression -> {
- return if (isPermissionCheck(startingExpression))
- EnforcePermissionFix.fromCallExpression(startingExpression, context)
- else null
- }
+ is UCallExpression -> return EnforcePermissionFix
+ .fromCallExpression(context, startingExpression)
else -> null
}
@@ -160,40 +154,5 @@ class ManualPermissionCheckDetector : Detector(), SourceCodeScanner {
),
enabledByDefault = false, // TODO: enable once b/241171714 is resolved
)
-
- private fun isPermissionCheck(callExpression: UCallExpression): Boolean {
- val method = callExpression.resolve() ?: return false
- val className = method.containingClass?.qualifiedName
- return ENFORCE_PERMISSION_METHODS.any {
- it.clazz == className && it.name == method.name
- }
- }
-
- /**
- * given a UMethod, determine if this method is
- * an entrypoint to an interface generated by AIDL,
- * returning the interface name if so
- */
- fun getContainingAidlInterface(node: UMethod): String? {
- if (!isInClassCalledStub(node)) return null
- for (superMethod in node.findSuperMethods()) {
- for (extendsInterface in superMethod.containingClass?.extendsList?.referenceElements
- ?: continue) {
- if (extendsInterface.qualifiedName == IINTERFACE_INTERFACE) {
- return superMethod.containingClass?.name
- }
- }
- }
- return null
- }
-
- private fun isInClassCalledStub(node: UMethod): Boolean {
- (node.containingClass as? PsiAnonymousClass)?.let {
- return it.baseClassReference.referenceName == CLASS_STUB
- }
- return node.containingClass?.extendsList?.referenceElements?.any {
- it.referenceName == CLASS_STUB
- } ?: false
- }
}
}
diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/aidl/ManualPermissionCheckDetectorTest.kt b/tools/lint/checks/src/test/java/com/google/android/lint/aidl/ManualPermissionCheckDetectorTest.kt
index 1a1c6bc77785..a968f5e6cb1c 100644
--- a/tools/lint/checks/src/test/java/com/google/android/lint/aidl/ManualPermissionCheckDetectorTest.kt
+++ b/tools/lint/checks/src/test/java/com/google/android/lint/aidl/ManualPermissionCheckDetectorTest.kt
@@ -19,6 +19,7 @@ package com.google.android.lint.aidl
import com.android.tools.lint.checks.infrastructure.LintDetectorTest
import com.android.tools.lint.checks.infrastructure.TestFile
import com.android.tools.lint.checks.infrastructure.TestLintTask
+import com.android.tools.lint.checks.infrastructure.TestMode
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
@@ -42,7 +43,7 @@ class ManualPermissionCheckDetectorTest : LintDetectorTest() {
private Context mContext;
@Override
public void test() throws android.os.RemoteException {
- mContext.enforceCallingOrSelfPermission("android.Manifest.permission.READ_CONTACTS", "foo");
+ mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
}
}
"""
@@ -53,8 +54,8 @@ class ManualPermissionCheckDetectorTest : LintDetectorTest() {
.expect(
"""
src/Foo.java:7: Warning: ITest permission check can be converted to @EnforcePermission annotation [UseEnforcePermissionAnnotation]
- mContext.enforceCallingOrSelfPermission("android.Manifest.permission.READ_CONTACTS", "foo");
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0 errors, 1 warnings
"""
)
@@ -62,9 +63,9 @@ class ManualPermissionCheckDetectorTest : LintDetectorTest() {
"""
Fix for src/Foo.java line 7: Annotate with @EnforcePermission:
@@ -5 +5
- + @android.annotation.EnforcePermission(android.Manifest.permission.READ_CONTACTS)
+ + @android.annotation.EnforcePermission("android.permission.READ_CONTACTS")
@@ -7 +8
- - mContext.enforceCallingOrSelfPermission("android.Manifest.permission.READ_CONTACTS", "foo");
+ - mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
"""
)
}
@@ -81,7 +82,7 @@ class ManualPermissionCheckDetectorTest : LintDetectorTest() {
@Override
public void test() throws android.os.RemoteException {
mContext.enforceCallingOrSelfPermission(
- "android.Manifest.permission.READ_CONTACTS", "foo");
+ "android.permission.READ_CONTACTS", "foo");
}
};
}
@@ -102,10 +103,49 @@ class ManualPermissionCheckDetectorTest : LintDetectorTest() {
"""
Fix for src/Foo.java line 8: Annotate with @EnforcePermission:
@@ -6 +6
- + @android.annotation.EnforcePermission(android.Manifest.permission.READ_CONTACTS)
+ + @android.annotation.EnforcePermission("android.permission.READ_CONTACTS")
@@ -8 +9
- mContext.enforceCallingOrSelfPermission(
- - "android.Manifest.permission.READ_CONTACTS", "foo");
+ - "android.permission.READ_CONTACTS", "foo");
+ """
+ )
+ }
+
+ fun testConstantEvaluation() {
+ lint().files(
+ java(
+ """
+ import android.content.Context;
+ import android.test.ITest;
+
+ public class Foo extends ITest.Stub {
+ private Context mContext;
+ @Override
+ public void test() throws android.os.RemoteException {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.READ_CONTACTS, "foo");
+ }
+ }
+ """
+ ).indented(),
+ *stubs,
+ manifestStub
+ )
+ .run()
+ .expect(
+ """
+ src/Foo.java:8: Warning: ITest permission check can be converted to @EnforcePermission annotation [UseEnforcePermissionAnnotation]
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.READ_CONTACTS, "foo");
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ 0 errors, 1 warnings
+ """
+ )
+ .expectFixDiffs(
+ """
+ Fix for src/Foo.java line 7: Annotate with @EnforcePermission:
+ @@ -6 +6
+ + @android.annotation.EnforcePermission("android.permission.READ_CONTACTS")
+ @@ -8 +9
+ - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.READ_CONTACTS, "foo");
"""
)
}
@@ -122,9 +162,9 @@ class ManualPermissionCheckDetectorTest : LintDetectorTest() {
@Override
public void test() throws android.os.RemoteException {
mContext.enforceCallingOrSelfPermission(
- "android.Manifest.permission.READ_CONTACTS", "foo");
+ "android.permission.READ_CONTACTS", "foo");
mContext.enforceCallingOrSelfPermission(
- "android.Manifest.permission.WRITE_CONTACTS", "foo");
+ "android.permission.WRITE_CONTACTS", "foo");
}
};
}
@@ -144,13 +184,13 @@ class ManualPermissionCheckDetectorTest : LintDetectorTest() {
.expectFixDiffs(
"""
Fix for src/Foo.java line 10: Annotate with @EnforcePermission:
- @@ -6 +6
- + @android.annotation.EnforcePermission(allOf={android.Manifest.permission.READ_CONTACTS, android.Manifest.permission.WRITE_CONTACTS})
+ @@ -6 +6
+ + @android.annotation.EnforcePermission(allOf={"android.permission.READ_CONTACTS", "android.permission.WRITE_CONTACTS"})
@@ -8 +9
- mContext.enforceCallingOrSelfPermission(
- - "android.Manifest.permission.READ_CONTACTS", "foo");
+ - "android.permission.READ_CONTACTS", "foo");
- mContext.enforceCallingOrSelfPermission(
- - "android.Manifest.permission.WRITE_CONTACTS", "foo");
+ - "android.permission.WRITE_CONTACTS", "foo");
"""
)
}
@@ -166,7 +206,7 @@ class ManualPermissionCheckDetectorTest : LintDetectorTest() {
@Override
public void test() throws android.os.RemoteException {
long uid = Binder.getCallingUid();
- mContext.enforceCallingOrSelfPermission("android.Manifest.permission.READ_CONTACTS", "foo");
+ mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
}
}
"""
@@ -177,6 +217,149 @@ class ManualPermissionCheckDetectorTest : LintDetectorTest() {
.expectClean()
}
+ fun testPermissionHelper() {
+ lint().skipTestModes(TestMode.PARENTHESIZED).files(
+ java(
+ """
+ import android.content.Context;
+ import android.test.ITest;
+
+ public class Foo extends ITest.Stub {
+ private Context mContext;
+
+ @android.content.pm.PermissionMethod
+ private void helper() {
+ mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
+ }
+
+ @Override
+ public void test() throws android.os.RemoteException {
+ helper();
+ }
+ }
+ """
+ ).indented(),
+ *stubs
+ )
+ .run()
+ .expect(
+ """
+ src/Foo.java:14: Warning: ITest permission check can be converted to @EnforcePermission annotation [UseEnforcePermissionAnnotation]
+ helper();
+ ~~~~~~~~~
+ 0 errors, 1 warnings
+ """
+ )
+ .expectFixDiffs(
+ """
+ Fix for src/Foo.java line 14: Annotate with @EnforcePermission:
+ @@ -12 +12
+ + @android.annotation.EnforcePermission("android.permission.READ_CONTACTS")
+ @@ -14 +15
+ - helper();
+ """
+ )
+ }
+
+ fun testPermissionHelperAllOf() {
+ lint().skipTestModes(TestMode.PARENTHESIZED).files(
+ java(
+ """
+ import android.content.Context;
+ import android.test.ITest;
+
+ public class Foo extends ITest.Stub {
+ private Context mContext;
+
+ @android.content.pm.PermissionMethod
+ private void helper() {
+ mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
+ mContext.enforceCallingOrSelfPermission("android.permission.WRITE_CONTACTS", "foo");
+ }
+
+ @Override
+ public void test() throws android.os.RemoteException {
+ helper();
+ mContext.enforceCallingOrSelfPermission("FOO", "foo");
+ }
+ }
+ """
+ ).indented(),
+ *stubs
+ )
+ .run()
+ .expect(
+ """
+ src/Foo.java:16: Warning: ITest permission check can be converted to @EnforcePermission annotation [UseEnforcePermissionAnnotation]
+ mContext.enforceCallingOrSelfPermission("FOO", "foo");
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ 0 errors, 1 warnings
+ """
+ )
+ .expectFixDiffs(
+ """
+ Fix for src/Foo.java line 16: Annotate with @EnforcePermission:
+ @@ -13 +13
+ + @android.annotation.EnforcePermission(allOf={"android.permission.READ_CONTACTS", "android.permission.WRITE_CONTACTS", "FOO"})
+ @@ -15 +16
+ - helper();
+ - mContext.enforceCallingOrSelfPermission("FOO", "foo");
+ """
+ )
+ }
+
+
+ fun testPermissionHelperNested() {
+ lint().skipTestModes(TestMode.PARENTHESIZED).files(
+ java(
+ """
+ import android.content.Context;
+ import android.test.ITest;
+
+ public class Foo extends ITest.Stub {
+ private Context mContext;
+
+ @android.content.pm.PermissionMethod
+ private void helperHelper() {
+ helper("android.permission.WRITE_CONTACTS");
+ }
+
+ @android.content.pm.PermissionMethod
+ private void helper(@android.content.pm.PermissionName String extraPermission) {
+ mContext.enforceCallingOrSelfPermission("android.permission.READ_CONTACTS", "foo");
+ }
+
+ @Override
+ public void test() throws android.os.RemoteException {
+ helperHelper();
+ }
+ }
+ """
+ ).indented(),
+ *stubs
+ )
+ .run()
+ .expect(
+ """
+ src/Foo.java:19: Warning: ITest permission check can be converted to @EnforcePermission annotation [UseEnforcePermissionAnnotation]
+ helperHelper();
+ ~~~~~~~~~~~~~~~
+ 0 errors, 1 warnings
+ """
+ )
+ .expectFixDiffs(
+ """
+ Fix for src/Foo.java line 19: Annotate with @EnforcePermission:
+ @@ -17 +17
+ + @android.annotation.EnforcePermission(allOf={"android.permission.WRITE_CONTACTS", "android.permission.READ_CONTACTS"})
+ @@ -19 +20
+ - helperHelper();
+ """
+ )
+ }
+
+
+
companion object {
private val aidlStub: TestFile = java(
"""
@@ -192,7 +375,8 @@ class ManualPermissionCheckDetectorTest : LintDetectorTest() {
"""
package android.content;
public class Context {
- public void enforceCallingOrSelfPermission(String permission, String message) {}
+ @android.content.pm.PermissionMethod
+ public void enforceCallingOrSelfPermission(@android.content.pm.PermissionName String permission, String message) {}
}
"""
).indented()
@@ -206,6 +390,59 @@ class ManualPermissionCheckDetectorTest : LintDetectorTest() {
"""
).indented()
- val stubs = arrayOf(aidlStub, contextStub, binderStub)
+ private val permissionMethodStub: TestFile = java(
+ """
+ package android.content.pm;
+
+ import static java.lang.annotation.ElementType.METHOD;
+ import static java.lang.annotation.RetentionPolicy.CLASS;
+
+ import java.lang.annotation.Retention;
+ import java.lang.annotation.Target;
+
+ @Retention(CLASS)
+ @Target({METHOD})
+ public @interface PermissionMethod {}
+ """
+ ).indented()
+
+ private val permissionNameStub: TestFile = java(
+ """
+ package android.content.pm;
+
+ import static java.lang.annotation.ElementType.FIELD;
+ import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+ import static java.lang.annotation.ElementType.METHOD;
+ import static java.lang.annotation.ElementType.PARAMETER;
+ import static java.lang.annotation.RetentionPolicy.CLASS;
+
+ import java.lang.annotation.Retention;
+ import java.lang.annotation.Target;
+
+ @Retention(CLASS)
+ @Target({PARAMETER, METHOD, LOCAL_VARIABLE, FIELD})
+ public @interface PermissionName {}
+ """
+ ).indented()
+
+ private val manifestStub: TestFile = java(
+ """
+ package android;
+
+ public final class Manifest {
+ public static final class permission {
+ public static final String READ_CONTACTS="android.permission.READ_CONTACTS";
+ }
+ }
+ """.trimIndent()
+ )
+
+ val stubs = arrayOf(
+ aidlStub,
+ contextStub,
+ binderStub,
+ permissionMethodStub,
+ permissionNameStub
+ )
}
}