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

Commit 128dd429 authored by Cole Faust's avatar Cole Faust Committed by Automerger Merge Worker
Browse files

Merge "Update lint checks to their state on internal master" am: 771f94f7 am: 75575edd

parents fc707f2d 75575edd
Loading
Loading
Loading
Loading
+41 −7
Original line number Diff line number Diff line
# Android Framework Lint Checker
# Android Lint Checks for AOSP

Custom lint checks written here are going to be executed for modules that opt in to those (e.g. any
Custom Android Lint checks are written here to be executed against java modules
in AOSP. These checks are broken down into two subdirectories:

1. [Global Checks](#android-global-lint-checker)
2. [Framework Checks](#android-framework-lint-checker)

# [Android Global Lint Checker](/global)
Checks written here are executed for the entire tree. The `AndroidGlobalLintChecker`
build target produces a jar file that is included in the overall build output
(`AndroidGlobalLintChecker.jar`).  This file is then downloaded as a prebuilt under the
`prebuilts/cmdline-tools` subproject, and included by soong with all invocations of lint.

## How to add new global lint checks
1. Write your detector with its issues and put it into
   `global/checks/src/main/java/com/google/android/lint`.
2. Add your detector's issues into `AndroidGlobalIssueRegistry`'s `issues`
   field.
3. Write unit tests for your detector in one file and put it into
   `global/checks/test/java/com/google/android/lint`.
4. Have your change reviewed and merged.  Once your change is merged,
   obtain a build number from a successful build that includes your change.
5. Run `prebuilts/cmdline-tools/update-android-global-lint-checker.sh
   <build_number>`. The script will create a commit that you can upload for
   approval to the `prebuilts/cmdline-tools` subproject.
6. Done! Your lint check should be applied in lint report builds across the
   entire tree!

# [Android Framework Lint Checker](/framework)

Checks written here are going to be executed for modules that opt in to those (e.g. any
`services.XXX` module) and results will be automatically reported on CLs on gerrit.

## How to add new lint checks
## How to add new framework lint checks

1. Write your detector with its issues and put it into
   `checks/src/main/java/com/google/android/lint`.
   `framework/checks/src/main/java/com/google/android/lint`.
2. Add your detector's issues into `AndroidFrameworkIssueRegistry`'s `issues` field.
3. Write unit tests for your detector in one file and put it into
   `checks/test/java/com/google/android/lint`.
   `framework/checks/test/java/com/google/android/lint`.
4. Done! Your lint checks should be applied in lint report builds for modules that include
   `AndroidFrameworkLintChecker`.

@@ -44,7 +73,11 @@ m out/soong/.intermediates/frameworks/base/services/autofill/services.autofill/a
  environment variable with the id of the lint. For example:
  `ANDROID_LINT_CHECK=UnusedTokenOfOriginalCallingIdentity m out/[...]/lint-report.html`

## Create or update a baseline
# How to apply automatic fixes suggested by lint

See [lint_fix](fix/README.md)

# Create or update a baseline

Baseline files can be used to silence known errors (and warnings) that are deemed to be safe. When
there is a lint-baseline.xml file in the root folder of the java library, soong will
@@ -75,9 +108,10 @@ locally change the soong code in
[lint.go](http://cs/aosp-master/build/soong/java/lint.go;l=451;rcl=2e778d5bc4a8d1d77b4f4a3029a4a254ad57db75)
adding `cmd.Flag("--nowarn")` and running lint again.

## Documentation
# Documentation

- [Android Lint Docs](https://googlesamples.github.io/android-custom-lint-rules/)
- [Lint Check Unit Testing](https://googlesamples.github.io/android-custom-lint-rules/api-guide/unit-testing.md.html)
- [Android Lint source files](https://source.corp.google.com/studio-main/tools/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/)
- [PSI source files](https://github.com/JetBrains/intellij-community/tree/master/java/java-psi-api/src/com/intellij/psi)
- [UAST source files](https://upsource.jetbrains.com/idea-ce/structure/idea-ce-7b9b8cc138bbd90aec26433f82cd2c6838694003/uast/uast-common/src/org/jetbrains/uast)
+0 −202
Original line number Diff line number Diff line
/*
 * 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

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.detector.api.Detector
import com.android.tools.lint.detector.api.Issue

@Suppress("UnstableApiUsage")
class EnforcePermissionDetectorTest : LintDetectorTest() {
    override fun getDetector(): Detector = EnforcePermissionDetector()

    override fun getIssues(): List<Issue> = listOf(
            EnforcePermissionDetector.ISSUE_MISSING_ENFORCE_PERMISSION,
            EnforcePermissionDetector.ISSUE_MISMATCHING_ENFORCE_PERMISSION
    )

    override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)

    fun testDoesNotDetectIssuesCorrectAnnotationOnClass() {
        lint().files(java(
            """
            package test.pkg;
            @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
            public class TestClass1 extends IFoo.Stub {
                public void testMethod() {}
            }
            """).indented(),
                *stubs
        )
        .run()
        .expectClean()
    }

    fun testDoesNotDetectIssuesCorrectAnnotationOnMethod() {
        lint().files(java(
            """
            package test.pkg;
            import android.annotation.EnforcePermission;
            public class TestClass2 extends IFooMethod.Stub {
                @Override
                @EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
                public void testMethod() {}
            }
            """).indented(),
                *stubs
        )
        .run()
        .expectClean()
    }

    fun testDetectIssuesMismatchingAnnotationOnClass() {
        lint().files(java(
            """
            package test.pkg;
            @android.annotation.EnforcePermission(android.Manifest.permission.INTERNET)
            public class TestClass3 extends IFoo.Stub {
                public void testMethod() {}
            }
            """).indented(),
                *stubs
        )
        .run()
        .expect("""src/test/pkg/TestClass3.java:3: Error: The class test.pkg.TestClass3 is \
annotated with @android.annotation.EnforcePermission(android.Manifest.permission.INTERNET) \
which differs from the parent class IFoo.Stub: \
@android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE). The \
same annotation must be used for both classes. [MismatchingEnforcePermissionAnnotation]
public class TestClass3 extends IFoo.Stub {
                                ~~~~~~~~~
1 errors, 0 warnings""".addLineContinuation())
    }

    fun testDetectIssuesMismatchingAnnotationOnMethod() {
        lint().files(java(
            """
            package test.pkg;
            public class TestClass4 extends IFooMethod.Stub {
                @android.annotation.EnforcePermission(android.Manifest.permission.INTERNET)
                public void testMethod() {}
            }
            """).indented(),
                *stubs
        )
        .run()
        .expect("""src/test/pkg/TestClass4.java:4: Error: The method TestClass4.testMethod is \
annotated with @android.annotation.EnforcePermission(android.Manifest.permission.INTERNET) \
which differs from the overridden method Stub.testMethod: \
@android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE). The same \
annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation]
    public void testMethod() {}
                ~~~~~~~~~~
1 errors, 0 warnings""".addLineContinuation())
    }

    fun testDetectIssuesMissingAnnotationOnClass() {
        lint().files(java(
            """
            package test.pkg;
            public class TestClass5 extends IFoo.Stub {
                public void testMethod() {}
            }
            """).indented(),
                *stubs
        )
        .run()
        .expect("""src/test/pkg/TestClass5.java:2: Error: The class test.pkg.TestClass5 extends \
the class IFoo.Stub which is annotated with @EnforcePermission. The same annotation must be \
used on test.pkg.TestClass5. [MissingEnforcePermissionAnnotation]
public class TestClass5 extends IFoo.Stub {
                                ~~~~~~~~~
1 errors, 0 warnings""".addLineContinuation())
    }

    fun testDetectIssuesMissingAnnotationOnMethod() {
        lint().files(java(
            """
            package test.pkg;
            public class TestClass6 extends IFooMethod.Stub {
                public void testMethod() {}
            }
            """).indented(),
                *stubs
        )
        .run()
        .expect("""src/test/pkg/TestClass6.java:3: Error: The method TestClass6.testMethod \
overrides the method Stub.testMethod which is annotated with @EnforcePermission. The same \
annotation must be used on TestClass6.testMethod [MissingEnforcePermissionAnnotation]
    public void testMethod() {}
                ~~~~~~~~~~
1 errors, 0 warnings""".addLineContinuation())
    }

    /* Stubs */

    private val interfaceIFooStub: TestFile = java(
        """
        @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
        public interface IFoo {
         @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
         public static abstract class Stub implements IFoo {
           @Override
           public void testMethod() {}
         }
         public void testMethod();
        }
        """
    ).indented()

    private val interfaceIFooMethodStub: TestFile = java(
        """
        public interface IFooMethod {
         public static abstract class Stub implements IFooMethod {
            @Override
            @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
            public void testMethod() {}
          }
          @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
          public void testMethod();
        }
        """
    ).indented()

    private val manifestPermissionStub: TestFile = java(
        """
        package android.Manifest;
        class permission {
          public static final String READ_PHONE_STATE = "android.permission.READ_PHONE_STATE";
          public static final String INTERNET = "android.permission.INTERNET";
        }
        """
    ).indented()

    private val enforcePermissionAnnotationStub: TestFile = java(
        """
        package android.annotation;
        public @interface EnforcePermission {}
        """
    ).indented()

    private val stubs = arrayOf(interfaceIFooStub, interfaceIFooMethodStub,
            manifestPermissionStub, enforcePermissionAnnotationStub)

    // Substitutes "backslash + new line" with an empty string to imitate line continuation
    private fun String.addLineContinuation(): String = this.trimIndent().replace("\\\n", "")
}
+29 −0
Original line number Diff line number Diff line
// 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 {
    // See: http://go/android-license-faq
    // A large-scale-change added 'default_applicable_licenses' to import
    // all of the 'license_kinds' from "frameworks_base_license"
    // to get the below license kinds:
    //   SPDX-license-identifier-Apache-2.0
    default_applicable_licenses: ["frameworks_base_license"],
}

java_library_host {
    name: "AndroidCommonLint",
    srcs: ["src/main/java/**/*.kt"],
    libs: ["lint_api"],
    kotlincflags: ["-Xjvm-default=all"],
}
+40 −0
Original line number Diff line number Diff line
/*
 * 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

import com.google.android.lint.model.Method

const val CLASS_STUB = "Stub"
const val CLASS_CONTEXT = "android.content.Context"
const val CLASS_ACTIVITY_MANAGER_SERVICE = "com.android.server.am.ActivityManagerService"
const val CLASS_ACTIVITY_MANAGER_INTERNAL = "android.app.ActivityManagerInternal"

// Enforce permission APIs
val ENFORCE_PERMISSION_METHODS = listOf(
        Method(CLASS_CONTEXT, "checkPermission"),
        Method(CLASS_CONTEXT, "checkCallingPermission"),
        Method(CLASS_CONTEXT, "checkCallingOrSelfPermission"),
        Method(CLASS_CONTEXT, "enforcePermission"),
        Method(CLASS_CONTEXT, "enforceCallingPermission"),
        Method(CLASS_CONTEXT, "enforceCallingOrSelfPermission"),
        Method(CLASS_ACTIVITY_MANAGER_SERVICE, "checkPermission"),
        Method(CLASS_ACTIVITY_MANAGER_INTERNAL, "enforceCallingPermission")
)

const val ANNOTATION_PERMISSION_METHOD = "android.annotation.PermissionMethod"
const val ANNOTATION_PERMISSION_NAME = "android.annotation.PermissionName"
const val ANNOTATION_PERMISSION_RESULT = "android.content.pm.PackageManager.PermissionResult"
+52 −0
Original line number Diff line number Diff line
/*
 * 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

import com.android.tools.lint.detector.api.getUMethod
import org.jetbrains.uast.UAnnotation
import org.jetbrains.uast.UCallExpression
import org.jetbrains.uast.UElement
import org.jetbrains.uast.UMethod
import org.jetbrains.uast.UParameter
import org.jetbrains.uast.UQualifiedReferenceExpression

fun isPermissionMethodCall(callExpression: UCallExpression): Boolean {
    val method = callExpression.resolve()?.getUMethod() ?: return false
    return hasPermissionMethodAnnotation(method)
}

fun hasPermissionMethodAnnotation(method: UMethod): Boolean =
        getPermissionMethodAnnotation(method) != null

fun getPermissionMethodAnnotation(method: UMethod?): UAnnotation? = method?.uAnnotations
        ?.firstOrNull { it.qualifiedName == ANNOTATION_PERMISSION_METHOD }

fun hasPermissionNameAnnotation(parameter: UParameter) = parameter.annotations.any {
    it.hasQualifiedName(ANNOTATION_PERMISSION_NAME)
}

/**
 * Attempts to return a CallExpression from a QualifiedReferenceExpression (or returns it directly if passed directly)
 * @param callOrReferenceCall expected to be UCallExpression or UQualifiedReferenceExpression
 * @return UCallExpression, if available
 */
fun findCallExpression(callOrReferenceCall: UElement?): UCallExpression? =
        when (callOrReferenceCall) {
            is UCallExpression -> callOrReferenceCall
            is UQualifiedReferenceExpression -> callOrReferenceCall.selector as? UCallExpression
            else -> null
        }
Loading