Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 39401634 authored by Matt Gilbride's avatar Matt Gilbride Committed by Android (Google) Code Review
Browse files

Merge changes Ice19df39,I9e2e6d19

* changes:
  EnforcePermissionFix: refactor for greater separation of concern and reuse
  SimpleManualPermissionEnforcementDetector: suggest calling helper method and turn on
parents 729d0a62 d27d379e
Loading
Loading
Loading
Loading
+14 −6
Original line number Diff line number Diff line
@@ -38,12 +38,6 @@ java_library_host {

java_test_host {
    name: "AndroidGlobalLintCheckerTest",
    // TODO(b/239881504): Since this test was written, Android
    // Lint was updated, and now includes classes that were
    // compiled for java 15. The soong build doesn't support
    // java 15 yet, so we can't compile against "lint". Disable
    // the test until java 15 is supported.
    enabled: false,
    srcs: ["checks/src/test/java/**/*.kt"],
    static_libs: [
        "AndroidGlobalLintChecker",
@@ -53,5 +47,19 @@ java_test_host {
    ],
    test_options: {
        unit_test: true,
        tradefed_options: [
            {
                // lint bundles in some classes that were built with older versions
                // of libraries, and no longer load. Since tradefed tries to load
                // all classes in the jar to look for tests, it crashes loading them.
                // Exclude these classes from tradefed's search.
                name: "exclude-paths",
                value: "org/apache",
            },
            {
                name: "exclude-paths",
                value: "META-INF",
            },
        ],
    },
}
+2 −0
Original line number Diff line number Diff line
@@ -29,6 +29,8 @@ val AIDL_PERMISSION_ANNOTATIONS = listOf(
const val BINDER_CLASS = "android.os.Binder"
const val IINTERFACE_INTERFACE = "android.os.IInterface"

const val AIDL_PERMISSION_HELPER_SUFFIX = "_enforcePermission"

/**
 * If a non java (e.g. c++) backend is enabled, the @EnforcePermission
 * annotation cannot be used.  At time of writing, the mechanism
+69 −15
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ import org.jetbrains.uast.UCallExpression
import org.jetbrains.uast.UExpression
import org.jetbrains.uast.UExpressionList
import org.jetbrains.uast.UIfExpression
import org.jetbrains.uast.UMethod
import org.jetbrains.uast.UThrowExpression
import org.jetbrains.uast.UastBinaryOperator
import org.jetbrains.uast.evaluateString
@@ -46,29 +47,37 @@ import org.jetbrains.uast.visitor.AbstractUastVisitor
 * Helper class that facilitates the creation of lint auto fixes
 */
data class EnforcePermissionFix(
    val locations: List<Location>,
    val manualCheckLocations: List<Location>,
    val permissionNames: List<String>,
    val errorLevel: Boolean,
    val anyOf: Boolean,
) {
    fun toLintFix(annotationLocation: Location): LintFix {
        val removeFixes = this.locations.map {
    fun toLintFix(context: JavaContext, node: UMethod): LintFix {
        val methodLocation = context.getLocation(node)
        val replaceOrRemoveFixes = manualCheckLocations.mapIndexed { index, manualCheckLocation ->
            if (index == 0) {
                // Replace the first manual check with a call to the helper method
                getHelperMethodFix(node, manualCheckLocation, false)
            } else {
                // Remove all subsequent manual checks
                LintFix.create()
                    .replace()
                    .reformat(true)
                .range(it)
                    .range(manualCheckLocation)
                    .with("")
                    .autoFix()
                    .build()
            }
        }

        // Annotate the method with @EnforcePermission(...)
        val annotateFix = LintFix.create()
            .annotate(this.annotation)
            .range(annotationLocation)
            .annotate(annotation)
            .range(methodLocation)
            .autoFix()
            .build()

        return LintFix.create().composite(annotateFix, *removeFixes.toTypedArray())
        return LintFix.create().composite(annotateFix, *replaceOrRemoveFixes.toTypedArray())
    }

    private val annotation: String
@@ -88,8 +97,51 @@ data class EnforcePermissionFix(
        }

    companion object {
        /**
         * Walks the expressions in a block, looking for simple permission checks.
         *
         * As soon as something other than a permission check is encountered, stop looking,
         * as some other business logic is happening that prevents an automated fix.
         */
        fun fromBlockExpression(
            context: JavaContext,
            blockExpression: UBlockExpression
        ): EnforcePermissionFix? {
            try {
                val singleFixes = mutableListOf<EnforcePermissionFix>()
                for (expression in blockExpression.expressions) {
                    val fix = fromExpression(context, expression) ?: break
                    singleFixes.add(fix)
                }
                return compose(singleFixes)
            } catch (e: AnyOfAllOfException) {
                return null
            }
        }

        /**
         * Conditionally constructs EnforcePermissionFix from any UExpression
         *
         * @return EnforcePermissionFix if the expression boils down to a permission check,
         * else null
         */
        fun fromExpression(
            context: JavaContext,
            expression: UExpression
        ): EnforcePermissionFix? {
            val trimmedExpression = expression.skipParenthesizedExprDown()
            if (trimmedExpression is UIfExpression) {
                return fromIfExpression(context, trimmedExpression)
            }
            findCallExpression(trimmedExpression)?.let {
                return fromCallExpression(context, it)
            }
            return null
        }

        /**
         * Conditionally constructs EnforcePermissionFix from a UCallExpression
         *
         * @return EnforcePermissionFix if the called method is annotated with @PermissionMethod, else null
         */
        fun fromCallExpression(
@@ -111,6 +163,7 @@ data class EnforcePermissionFix(

        /**
         * Conditionally constructs EnforcePermissionFix from a UCallExpression
         *
         * @return EnforcePermissionFix IF AND ONLY IF:
         * * The condition of the if statement compares the return value of a
         *   PermissionMethod to one of the PackageManager.PermissionResult values
@@ -172,7 +225,8 @@ data class EnforcePermissionFix(
        }


        fun compose(individuals: List<EnforcePermissionFix>): EnforcePermissionFix {
        fun compose(individuals: List<EnforcePermissionFix>): EnforcePermissionFix? {
            if (individuals.isEmpty()) return null
            val anyOfs = individuals.filter(EnforcePermissionFix::anyOf)
            // anyOf/allOf should be consistent.  If we encounter some @PermissionMethods that are anyOf
            // and others that aren't, we don't know what to do.
@@ -180,7 +234,7 @@ data class EnforcePermissionFix(
                throw AnyOfAllOfException()
            }
            return EnforcePermissionFix(
                    individuals.flatMap(EnforcePermissionFix::locations),
                    individuals.flatMap(EnforcePermissionFix::manualCheckLocations),
                    individuals.flatMap(EnforcePermissionFix::permissionNames),
                    errorLevel = individuals.all(EnforcePermissionFix::errorLevel),
                    anyOf = anyOfs.isNotEmpty()
+2 −13
Original line number Diff line number Diff line
@@ -55,7 +55,7 @@ class EnforcePermissionHelperDetector : Detector(), SourceCodeScanner {
                return
            }

            val targetExpression = "${node.name}$HELPER_SUFFIX()"
            val targetExpression = getHelperMethodCallSourceString(node)
            val message =
                "Method must start with $targetExpression or super.${node.name}(), if applicable"

@@ -85,22 +85,11 @@ class EnforcePermissionHelperDetector : Detector(), SourceCodeScanner {
                val locationTarget = getLocationTarget(firstExpression)
                val expressionLocation = context.getLocation(locationTarget)

                val indent = " ".repeat(expressionLocation.start?.column ?: 0)

                val fix = fix()
                    .replace()
                    .range(expressionLocation)
                    .beginning()
                    .with("$targetExpression;\n\n$indent")
                    .reformat(true)
                    .autoFix()
                    .build()

                context.report(
                    ISSUE_ENFORCE_PERMISSION_HELPER,
                    context.getLocation(node),
                    message,
                    fix
                    getHelperMethodFix(node, expressionLocation),
                )
            }
        }
+25 −0
Original line number Diff line number Diff line
@@ -17,6 +17,8 @@
package com.google.android.lint.aidl

import com.android.tools.lint.detector.api.JavaContext
import com.android.tools.lint.detector.api.LintFix
import com.android.tools.lint.detector.api.Location
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiReferenceList
import org.jetbrains.uast.UMethod
@@ -69,3 +71,26 @@ private fun hasSingleAncestor(references: PsiReferenceList?, qualifiedName: Stri
        references != null &&
                references.referenceElements.size == 1 &&
                references.referenceElements[0].qualifiedName == qualifiedName

fun getHelperMethodCallSourceString(node: UMethod) = "${node.name}$AIDL_PERMISSION_HELPER_SUFFIX()"

fun getHelperMethodFix(
    node: UMethod,
    manualCheckLocation: Location,
    prepend: Boolean = true
): LintFix {
    val helperMethodSource = getHelperMethodCallSourceString(node)
    val indent = " ".repeat(manualCheckLocation.start?.column ?: 0)
    val newText = "$helperMethodSource;${if (prepend) "\n\n$indent" else ""}"

    val fix = LintFix.create()
            .replace()
            .range(manualCheckLocation)
            .with(newText)
            .reformat(true)
            .autoFix()

    if (prepend) fix.beginning()

    return fix.build()
}
Loading