Loading tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/AidlImplementationDetector.kt +1 −1 Original line number Diff line number Diff line Loading @@ -36,7 +36,7 @@ abstract class AidlImplementationDetector : Detector(), SourceCodeScanner { private inner class AidlStubHandler(val context: JavaContext) : UElementHandler() { override fun visitMethod(node: UMethod) { val interfaceName = getContainingAidlInterface(node) val interfaceName = getContainingAidlInterface(context, node) .takeUnless(EXCLUDED_CPP_INTERFACES::contains) ?: return val body = (node.uastBody as? UBlockExpression) ?: return visitAidlMethod(context, node, interfaceName, body) Loading tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/Constants.kt +1 −0 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ val AIDL_PERMISSION_ANNOTATIONS = listOf( ANNOTATION_PERMISSION_MANUALLY_ENFORCED ) const val BINDER_CLASS = "android.os.Binder" const val IINTERFACE_INTERFACE = "android.os.IInterface" /** Loading tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt +14 −53 Original line number Diff line number Diff line Loading @@ -38,6 +38,7 @@ import com.intellij.psi.PsiMethod import org.jetbrains.uast.UAnnotation import org.jetbrains.uast.UElement import org.jetbrains.uast.UMethod import org.jetbrains.uast.toUElement /** * Lint Detector that ensures that any method overriding a method annotated Loading @@ -55,9 +56,6 @@ import org.jetbrains.uast.UMethod */ class EnforcePermissionDetector : Detector(), SourceCodeScanner { val BINDER_CLASS = "android.os.Binder" val JAVA_OBJECT = "java.lang.Object" override fun applicableAnnotations(): List<String> { return listOf(ANNOTATION_ENFORCE_PERMISSION) } Loading Loading @@ -123,6 +121,11 @@ class EnforcePermissionDetector : Detector(), SourceCodeScanner { overriddenMethod: PsiMethod, checkEquivalence: Boolean = true ) { // If method is not from a Stub subclass, this method shouldn't use @EP at all. // This is handled by EnforcePermissionHelperDetector. if (!isContainedInSubclassOfStub(context, overridingMethod.toUElement() as? UMethod)) { return } val overridingAnnotation = overridingMethod.getAnnotation(ANNOTATION_ENFORCE_PERMISSION) val overriddenAnnotation = overriddenMethod.getAnnotation(ANNOTATION_ENFORCE_PERMISSION) val location = context.getLocation(element) Loading @@ -131,13 +134,6 @@ class EnforcePermissionDetector : Detector(), SourceCodeScanner { val overridingName = "${overridingClass.name}.${overridingMethod.name}" val overriddenName = "${overriddenClass.name}.${overriddenMethod.name}" if (overridingAnnotation == null) { if (shouldIgnoreGeneratedMethod( context, overriddenClass = overriddenClass, overridingClass = overridingClass) ) { return } val msg = "The method $overridingName overrides the method $overriddenName which " + "is annotated with @EnforcePermission. The same annotation must be used " + "on $overridingName" Loading Loading @@ -177,8 +173,7 @@ class EnforcePermissionDetector : Detector(), SourceCodeScanner { if (node.qualifiedName != ANNOTATION_ENFORCE_PERMISSION) { return } val method = node.uastParent as? UMethod if (method != null) { val method = node.uastParent as? UMethod ?: return val overridingMethod = method as PsiMethod val parents = overridingMethod.findSuperMethods() for (overriddenMethod in parents) { Loading @@ -190,40 +185,6 @@ class EnforcePermissionDetector : Detector(), SourceCodeScanner { } } } } /** * since this lint runs globally, it will also run against generated * test code e.g. * system/tools/aidl/tests/golden_output/aidl-test-interface-permission-java-source/gen/android/aidl/tests/permission/IProtected.java * system/tools/aidl/tests/golden_output/aidl-test-interface-permission-java-source/gen/android/aidl/tests/permission/IProtectedInterface.java * we do not want to report errors against generated `Stub` and `Proxy` classes in those files */ private fun shouldIgnoreGeneratedMethod( context: JavaContext, overriddenClass: PsiClass, overridingClass: PsiClass, ): Boolean { if (isInterfaceAndExtendsIInterface(overriddenClass) && context.evaluator.isStatic(overridingClass)) { if (overridingClass.name == "Default") return true if (overridingClass.name == "Proxy") { val shouldBeStub = overridingClass.parent as? PsiClass ?: return false return shouldBeStub.name == "Stub" && context.evaluator.isAbstract(shouldBeStub) && context.evaluator.isStatic(shouldBeStub) && shouldBeStub.extendsList?.referenceElements ?.any { it.qualifiedName == BINDER_CLASS } == true } } return false } private fun isInterfaceAndExtendsIInterface(overriddenClass: PsiClass): Boolean = overriddenClass.isInterface && overriddenClass.extendsList?.referenceElements ?.any { it.qualifiedName == IINTERFACE_INTERFACE } == true companion object { val EXPLANATION = """ Loading tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt +30 −5 Original line number Diff line number Diff line Loading @@ -45,8 +45,19 @@ class EnforcePermissionHelperDetector : Detector(), SourceCodeScanner { if (context.evaluator.isAbstract(node)) return if (!node.hasAnnotation(ANNOTATION_ENFORCE_PERMISSION)) return if (!isContainedInSubclassOfStub(context, node)) { context.report( ISSUE_MISUSING_ENFORCE_PERMISSION, node, context.getLocation(node), "The class of ${node.name} does not inherit from an AIDL generated Stub class" ) return } val targetExpression = "${node.name}$HELPER_SUFFIX()" val message = "Method must start with $targetExpression or super.${node.name}()" val message = "Method must start with $targetExpression or super.${node.name}(), if applicable" val firstExpression = (node.uastBody as? UBlockExpression) ?.expressions?.firstOrNull() Loading Loading @@ -99,11 +110,12 @@ class EnforcePermissionHelperDetector : Detector(), SourceCodeScanner { private const val HELPER_SUFFIX = "_enforcePermission" private const val EXPLANATION = """ When @EnforcePermission is applied, the AIDL compiler generates a Stub method to do the permission check called yourMethodName$HELPER_SUFFIX. The @EnforcePermission annotation can only be used on methods whose class extends from the Stub class generated by the AIDL compiler. When @EnforcePermission is applied, the AIDL compiler generates a Stub method to do the permission check called yourMethodName$HELPER_SUFFIX. yourMethodName$HELPER_SUFFIX must be executed before any other operation. To do that, you can either call it directly or indirectly via super.yourMethodName(). either call it directly, or call it indirectly via super.yourMethodName(). """ val ISSUE_ENFORCE_PERMISSION_HELPER: Issue = Issue.create( Loading @@ -120,6 +132,19 @@ class EnforcePermissionHelperDetector : Detector(), SourceCodeScanner { ) ) val ISSUE_MISUSING_ENFORCE_PERMISSION: Issue = Issue.create( id = "MisusingEnforcePermissionAnnotation", briefDescription = "@EnforcePermission cannot be used here", explanation = EXPLANATION, category = Category.SECURITY, priority = 6, severity = Severity.ERROR, implementation = Implementation( EnforcePermissionDetector::class.java, Scope.JAVA_FILE_SCOPE ) ) /** * handles an edge case with UDeclarationsExpression, where sourcePsi is null, * resulting in an incorrect Location if used directly Loading tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt +35 −12 Original line number Diff line number Diff line Loading @@ -16,17 +16,18 @@ package com.google.android.lint.aidl import com.google.android.lint.CLASS_STUB import com.intellij.psi.PsiAnonymousClass import com.android.tools.lint.detector.api.JavaContext import com.intellij.psi.PsiClass import com.intellij.psi.PsiReferenceList import org.jetbrains.uast.UMethod /** * Given a UMethod, determine if this method is * an entrypoint to an interface generated by AIDL, * returning the interface name if so * the entrypoint to an interface generated by AIDL, * returning the interface name if so, otherwise returning null */ fun getContainingAidlInterface(node: UMethod): String? { if (!isInClassCalledStub(node)) return null fun getContainingAidlInterface(context: JavaContext, node: UMethod): String? { if (!isContainedInSubclassOfStub(context, node)) return null for (superMethod in node.findSuperMethods()) { for (extendsInterface in superMethod.containingClass?.extendsList?.referenceElements ?: continue) { Loading @@ -38,11 +39,33 @@ fun getContainingAidlInterface(node: UMethod): String? { return null } private fun isInClassCalledStub(node: UMethod): Boolean { (node.containingClass as? PsiAnonymousClass)?.let { return it.baseClassReference.referenceName == CLASS_STUB fun isContainedInSubclassOfStub(context: JavaContext, node: UMethod?): Boolean { var superClass = node?.containingClass?.superClass while (superClass != null) { if (isStub(context, superClass)) return true superClass = superClass.superClass } return node.containingClass?.extendsList?.referenceElements?.any { it.referenceName == CLASS_STUB } ?: false return false } fun isStub(context: JavaContext, psiClass: PsiClass?): Boolean { if (psiClass == null) return false if (psiClass.name != "Stub") return false if (!context.evaluator.isStatic(psiClass)) return false if (!context.evaluator.isAbstract(psiClass)) return false if (!hasSingleAncestor(psiClass.extendsList, BINDER_CLASS)) return false val parent = psiClass.parent as? PsiClass ?: return false if (!hasSingleAncestor(parent.extendsList, IINTERFACE_INTERFACE)) return false val parentName = parent.qualifiedName ?: return false if (!hasSingleAncestor(psiClass.implementsList, parentName)) return false return true } private fun hasSingleAncestor(references: PsiReferenceList?, qualifiedName: String) = references != null && references.referenceElements.size == 1 && references.referenceElements[0].qualifiedName == qualifiedName Loading
tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/AidlImplementationDetector.kt +1 −1 Original line number Diff line number Diff line Loading @@ -36,7 +36,7 @@ abstract class AidlImplementationDetector : Detector(), SourceCodeScanner { private inner class AidlStubHandler(val context: JavaContext) : UElementHandler() { override fun visitMethod(node: UMethod) { val interfaceName = getContainingAidlInterface(node) val interfaceName = getContainingAidlInterface(context, node) .takeUnless(EXCLUDED_CPP_INTERFACES::contains) ?: return val body = (node.uastBody as? UBlockExpression) ?: return visitAidlMethod(context, node, interfaceName, body) Loading
tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/Constants.kt +1 −0 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ val AIDL_PERMISSION_ANNOTATIONS = listOf( ANNOTATION_PERMISSION_MANUALLY_ENFORCED ) const val BINDER_CLASS = "android.os.Binder" const val IINTERFACE_INTERFACE = "android.os.IInterface" /** Loading
tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt +14 −53 Original line number Diff line number Diff line Loading @@ -38,6 +38,7 @@ import com.intellij.psi.PsiMethod import org.jetbrains.uast.UAnnotation import org.jetbrains.uast.UElement import org.jetbrains.uast.UMethod import org.jetbrains.uast.toUElement /** * Lint Detector that ensures that any method overriding a method annotated Loading @@ -55,9 +56,6 @@ import org.jetbrains.uast.UMethod */ class EnforcePermissionDetector : Detector(), SourceCodeScanner { val BINDER_CLASS = "android.os.Binder" val JAVA_OBJECT = "java.lang.Object" override fun applicableAnnotations(): List<String> { return listOf(ANNOTATION_ENFORCE_PERMISSION) } Loading Loading @@ -123,6 +121,11 @@ class EnforcePermissionDetector : Detector(), SourceCodeScanner { overriddenMethod: PsiMethod, checkEquivalence: Boolean = true ) { // If method is not from a Stub subclass, this method shouldn't use @EP at all. // This is handled by EnforcePermissionHelperDetector. if (!isContainedInSubclassOfStub(context, overridingMethod.toUElement() as? UMethod)) { return } val overridingAnnotation = overridingMethod.getAnnotation(ANNOTATION_ENFORCE_PERMISSION) val overriddenAnnotation = overriddenMethod.getAnnotation(ANNOTATION_ENFORCE_PERMISSION) val location = context.getLocation(element) Loading @@ -131,13 +134,6 @@ class EnforcePermissionDetector : Detector(), SourceCodeScanner { val overridingName = "${overridingClass.name}.${overridingMethod.name}" val overriddenName = "${overriddenClass.name}.${overriddenMethod.name}" if (overridingAnnotation == null) { if (shouldIgnoreGeneratedMethod( context, overriddenClass = overriddenClass, overridingClass = overridingClass) ) { return } val msg = "The method $overridingName overrides the method $overriddenName which " + "is annotated with @EnforcePermission. The same annotation must be used " + "on $overridingName" Loading Loading @@ -177,8 +173,7 @@ class EnforcePermissionDetector : Detector(), SourceCodeScanner { if (node.qualifiedName != ANNOTATION_ENFORCE_PERMISSION) { return } val method = node.uastParent as? UMethod if (method != null) { val method = node.uastParent as? UMethod ?: return val overridingMethod = method as PsiMethod val parents = overridingMethod.findSuperMethods() for (overriddenMethod in parents) { Loading @@ -190,40 +185,6 @@ class EnforcePermissionDetector : Detector(), SourceCodeScanner { } } } } /** * since this lint runs globally, it will also run against generated * test code e.g. * system/tools/aidl/tests/golden_output/aidl-test-interface-permission-java-source/gen/android/aidl/tests/permission/IProtected.java * system/tools/aidl/tests/golden_output/aidl-test-interface-permission-java-source/gen/android/aidl/tests/permission/IProtectedInterface.java * we do not want to report errors against generated `Stub` and `Proxy` classes in those files */ private fun shouldIgnoreGeneratedMethod( context: JavaContext, overriddenClass: PsiClass, overridingClass: PsiClass, ): Boolean { if (isInterfaceAndExtendsIInterface(overriddenClass) && context.evaluator.isStatic(overridingClass)) { if (overridingClass.name == "Default") return true if (overridingClass.name == "Proxy") { val shouldBeStub = overridingClass.parent as? PsiClass ?: return false return shouldBeStub.name == "Stub" && context.evaluator.isAbstract(shouldBeStub) && context.evaluator.isStatic(shouldBeStub) && shouldBeStub.extendsList?.referenceElements ?.any { it.qualifiedName == BINDER_CLASS } == true } } return false } private fun isInterfaceAndExtendsIInterface(overriddenClass: PsiClass): Boolean = overriddenClass.isInterface && overriddenClass.extendsList?.referenceElements ?.any { it.qualifiedName == IINTERFACE_INTERFACE } == true companion object { val EXPLANATION = """ Loading
tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt +30 −5 Original line number Diff line number Diff line Loading @@ -45,8 +45,19 @@ class EnforcePermissionHelperDetector : Detector(), SourceCodeScanner { if (context.evaluator.isAbstract(node)) return if (!node.hasAnnotation(ANNOTATION_ENFORCE_PERMISSION)) return if (!isContainedInSubclassOfStub(context, node)) { context.report( ISSUE_MISUSING_ENFORCE_PERMISSION, node, context.getLocation(node), "The class of ${node.name} does not inherit from an AIDL generated Stub class" ) return } val targetExpression = "${node.name}$HELPER_SUFFIX()" val message = "Method must start with $targetExpression or super.${node.name}()" val message = "Method must start with $targetExpression or super.${node.name}(), if applicable" val firstExpression = (node.uastBody as? UBlockExpression) ?.expressions?.firstOrNull() Loading Loading @@ -99,11 +110,12 @@ class EnforcePermissionHelperDetector : Detector(), SourceCodeScanner { private const val HELPER_SUFFIX = "_enforcePermission" private const val EXPLANATION = """ When @EnforcePermission is applied, the AIDL compiler generates a Stub method to do the permission check called yourMethodName$HELPER_SUFFIX. The @EnforcePermission annotation can only be used on methods whose class extends from the Stub class generated by the AIDL compiler. When @EnforcePermission is applied, the AIDL compiler generates a Stub method to do the permission check called yourMethodName$HELPER_SUFFIX. yourMethodName$HELPER_SUFFIX must be executed before any other operation. To do that, you can either call it directly or indirectly via super.yourMethodName(). either call it directly, or call it indirectly via super.yourMethodName(). """ val ISSUE_ENFORCE_PERMISSION_HELPER: Issue = Issue.create( Loading @@ -120,6 +132,19 @@ class EnforcePermissionHelperDetector : Detector(), SourceCodeScanner { ) ) val ISSUE_MISUSING_ENFORCE_PERMISSION: Issue = Issue.create( id = "MisusingEnforcePermissionAnnotation", briefDescription = "@EnforcePermission cannot be used here", explanation = EXPLANATION, category = Category.SECURITY, priority = 6, severity = Severity.ERROR, implementation = Implementation( EnforcePermissionDetector::class.java, Scope.JAVA_FILE_SCOPE ) ) /** * handles an edge case with UDeclarationsExpression, where sourcePsi is null, * resulting in an incorrect Location if used directly Loading
tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt +35 −12 Original line number Diff line number Diff line Loading @@ -16,17 +16,18 @@ package com.google.android.lint.aidl import com.google.android.lint.CLASS_STUB import com.intellij.psi.PsiAnonymousClass import com.android.tools.lint.detector.api.JavaContext import com.intellij.psi.PsiClass import com.intellij.psi.PsiReferenceList import org.jetbrains.uast.UMethod /** * Given a UMethod, determine if this method is * an entrypoint to an interface generated by AIDL, * returning the interface name if so * the entrypoint to an interface generated by AIDL, * returning the interface name if so, otherwise returning null */ fun getContainingAidlInterface(node: UMethod): String? { if (!isInClassCalledStub(node)) return null fun getContainingAidlInterface(context: JavaContext, node: UMethod): String? { if (!isContainedInSubclassOfStub(context, node)) return null for (superMethod in node.findSuperMethods()) { for (extendsInterface in superMethod.containingClass?.extendsList?.referenceElements ?: continue) { Loading @@ -38,11 +39,33 @@ fun getContainingAidlInterface(node: UMethod): String? { return null } private fun isInClassCalledStub(node: UMethod): Boolean { (node.containingClass as? PsiAnonymousClass)?.let { return it.baseClassReference.referenceName == CLASS_STUB fun isContainedInSubclassOfStub(context: JavaContext, node: UMethod?): Boolean { var superClass = node?.containingClass?.superClass while (superClass != null) { if (isStub(context, superClass)) return true superClass = superClass.superClass } return node.containingClass?.extendsList?.referenceElements?.any { it.referenceName == CLASS_STUB } ?: false return false } fun isStub(context: JavaContext, psiClass: PsiClass?): Boolean { if (psiClass == null) return false if (psiClass.name != "Stub") return false if (!context.evaluator.isStatic(psiClass)) return false if (!context.evaluator.isAbstract(psiClass)) return false if (!hasSingleAncestor(psiClass.extendsList, BINDER_CLASS)) return false val parent = psiClass.parent as? PsiClass ?: return false if (!hasSingleAncestor(parent.extendsList, IINTERFACE_INTERFACE)) return false val parentName = parent.qualifiedName ?: return false if (!hasSingleAncestor(psiClass.implementsList, parentName)) return false return true } private fun hasSingleAncestor(references: PsiReferenceList?, qualifiedName: String) = references != null && references.referenceElements.size == 1 && references.referenceElements[0].qualifiedName == qualifiedName