diff options
| author | 2025-03-07 03:22:41 -0800 | |
|---|---|---|
| committer | 2025-03-07 03:22:41 -0800 | |
| commit | db608e9ece77cdcc2542886f282130a09c15cb0f (patch) | |
| tree | d459f4d7f520d1085c4b2fd36f0a95674971672f | |
| parent | d14aac5e7a9a2c99d25850b9b271070bd677aacc (diff) | |
| parent | 2b0bc385c8de35b5472f976ccc86c3b6c05a5fad (diff) | |
Merge "Introduce linter to prevent runBlocking from being injected" into main
3 files changed, 189 insertions, 0 deletions
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RunBlockingDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RunBlockingDetector.kt new file mode 100644 index 000000000000..fce536a60b84 --- /dev/null +++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RunBlockingDetector.kt @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2025 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.android.internal.systemui.lint + +import com.android.tools.lint.client.api.UElementHandler +import com.android.tools.lint.detector.api.Category +import com.android.tools.lint.detector.api.Detector +import com.android.tools.lint.detector.api.Implementation +import com.android.tools.lint.detector.api.Issue +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 org.jetbrains.uast.UElement +import org.jetbrains.uast.UFile +import org.jetbrains.uast.UImportStatement + +/** Detects whether [runBlocking] is being imported. */ +class RunBlockingDetector : Detector(), SourceCodeScanner { + +    override fun getApplicableUastTypes(): List<Class<out UElement>> { +        return listOf(UFile::class.java) +    } + +    override fun createUastHandler(context: JavaContext): UElementHandler { +        return object : UElementHandler() { +            override fun visitFile(node: UFile) { +                for (importStatement in node.imports) { +                    visitImportStatement(context, importStatement) +                } +            } +        } +    } + +    private fun visitImportStatement(context: JavaContext, importStatement: UImportStatement) { +        val importName = importStatement.importReference?.asSourceString() +        if (FORBIDDEN_IMPORTS.contains(importName)) { +            context.report( +                ISSUE, +                importStatement as UElement, +                context.getLocation(importStatement), +                "Importing $importName is not allowed.", +            ) +        } +    } + +    companion object { +        @JvmField +        val ISSUE = +            Issue.create( +                id = "RunBlockingUsage", +                briefDescription = "Discouraged runBlocking call", +                explanation = +                    """ +                    Using `runBlocking` is generally discouraged in Android +                    development as it can lead to UI freezes and ANRs. +                    Consider using `launch` or `async` with coroutine scope +                    instead. If needed from java, consider introducing a method +                    with a callback instead from kotlin. +                    """, +                category = Category.PERFORMANCE, +                priority = 8, +                severity = Severity.WARNING, +                implementation = +                    Implementation(RunBlockingDetector::class.java, Scope.JAVA_FILE_SCOPE), +            ) + +        val FORBIDDEN_IMPORTS = listOf("kotlinx.coroutines.runBlocking") +    } +} diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt index adb311610587..b455c0021517 100644 --- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt +++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt @@ -50,6 +50,7 @@ class SystemUIIssueRegistry : IssueRegistry() {                  ShadeDisplayAwareDialogDetector.ISSUE,                  RegisterContentObserverSyncViaSettingsProxyDetector.SYNC_WARNING,                  RegisterContentObserverViaContentResolverDetector.CONTENT_RESOLVER_ERROR, +                RunBlockingDetector.ISSUE,              )      override val api: Int diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RunBlockingDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RunBlockingDetectorTest.kt new file mode 100644 index 000000000000..4ae429d204aa --- /dev/null +++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RunBlockingDetectorTest.kt @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2025 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.android.internal.systemui.lint + +import com.android.tools.lint.detector.api.Detector +import com.android.tools.lint.detector.api.Issue +import org.junit.Test + +class RunBlockingDetectorTest : SystemUILintDetectorTest() { + +    override fun getDetector(): Detector = RunBlockingDetector() + +    override fun getIssues(): List<Issue> = listOf(RunBlockingDetector.ISSUE) + +    @Test +    fun testViolation() { +        lint() +            .files( +                kotlin( +                    """ +                    package com.example + +                    import kotlinx.coroutines.runBlocking + +                    class MyClass { +                        fun myMethod() { +                            runBlocking { +                                // Some code here +                            } +                        } +                    } +                    """ +                ), +                RUN_BLOCKING_DEFINITION, +            ) +            .issues(RunBlockingDetector.ISSUE) +            .run() +            .expect( +                """ +src/com/example/MyClass.kt:4: Warning: Importing kotlinx.coroutines.runBlocking is not allowed. [RunBlockingUsage] +                    import kotlinx.coroutines.runBlocking +                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +0 errors, 1 warnings +""" +                    .trimIndent() +            ) +    } + +    // Verifies that the lint check does *not* flag calls to other methods. +    @Test +    fun testNotViolation() { +        lint() +            .detector(RunBlockingDetector()) +            .issues(RunBlockingDetector.ISSUE) +            .files( +                kotlin( +                    """ +                    package com.example + +                    class MyClass { +                        fun myMethod() { +                            myOtherMethod { +                            } +                        } + +                        fun myOtherMethod(block: () -> Unit) { +                            block() +                        } +                    } +                    """ +                ) +            ) +            .run() +            .expectClean() +    } + +    private companion object { +        val RUN_BLOCKING_DEFINITION = +            kotlin( +                """ +                    package kotlinx.coroutines + +                    fun runBlocking(block: suspend () -> Unit) { +                        // Implementation details don't matter for this test. +                    } +                    """ +            ) +    } +}  |