Merge "Introduce Android lint checks around Binder.clearCallingIdentity()"
diff --git a/services/Android.bp b/services/Android.bp
index cc0fd98..a2d6beb4 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -21,6 +21,9 @@
// "-XepPatchLocation:/tmp/refaster/",
],
},
+ lint: {
+ extra_check_modules: ["AndroidFrameworkLintChecker"],
+ },
}
filegroup {
diff --git a/tools/lint/Android.bp b/tools/lint/Android.bp
new file mode 100644
index 0000000..dcbc32b
--- /dev/null
+++ b/tools/lint/Android.bp
@@ -0,0 +1,46 @@
+// Copyright (C) 2021 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: "AndroidFrameworkLintChecker",
+ srcs: ["checks/src/main/java/**/*.kt"],
+ plugins: ["auto_service_plugin"],
+ libs: [
+ "auto_service_annotations",
+ "lint_api",
+ ],
+}
+
+// TODO: (b/162368644) Implement these (working in gradle) Kotlin Tests to run on Soong
+//java_test_host {
+// name: "AndroidFrameworkLintCheckerTest",
+// srcs: [
+// "checks/src/test/java/**/*.kt",
+// "checks/src/main/java/**/*.kt",
+// ],
+// plugins: ["auto_service_plugin"],
+// static_libs: [
+// "auto_service_annotations",
+// "lint_api",
+// ],
+//}
diff --git a/tools/lint/checks/src/main/java/com/android/lint/CallingIdentityTokenDetector.kt b/tools/lint/checks/src/main/java/com/android/lint/CallingIdentityTokenDetector.kt
new file mode 100644
index 0000000..4e30834
--- /dev/null
+++ b/tools/lint/checks/src/main/java/com/android/lint/CallingIdentityTokenDetector.kt
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2021 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.lint
+
+import com.android.lint.CallingIdentityTokenIssueRegistry.Companion.ISSUE_NESTED_CLEAR_IDENTITY_CALLS
+import com.android.lint.CallingIdentityTokenIssueRegistry.Companion.ISSUE_NON_FINAL_TOKEN
+import com.android.lint.CallingIdentityTokenIssueRegistry.Companion.ISSUE_RESTORE_IDENTITY_CALL_NOT_IN_FINALLY_BLOCK
+import com.android.lint.CallingIdentityTokenIssueRegistry.Companion.ISSUE_UNUSED_TOKEN
+import com.android.lint.CallingIdentityTokenIssueRegistry.Companion.ISSUE_USE_OF_CALLER_AWARE_METHODS_WITH_CLEARED_IDENTITY
+import com.android.lint.CallingIdentityTokenIssueRegistry.Companion.getIncidentMessageNestedClearIdentityCallsPrimary
+import com.android.lint.CallingIdentityTokenIssueRegistry.Companion.getIncidentMessageNestedClearIdentityCallsSecondary
+import com.android.lint.CallingIdentityTokenIssueRegistry.Companion.getIncidentMessageNonFinalToken
+import com.android.lint.CallingIdentityTokenIssueRegistry.Companion.getIncidentMessageRestoreIdentityCallNotInFinallyBlock
+import com.android.lint.CallingIdentityTokenIssueRegistry.Companion.getIncidentMessageUnusedToken
+import com.android.lint.CallingIdentityTokenIssueRegistry.Companion.getIncidentMessageUseOfCallerAwareMethodsWithClearedIdentity
+import com.android.tools.lint.client.api.UElementHandler
+import com.android.tools.lint.detector.api.Context
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Location
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.intellij.psi.PsiMethod
+import com.intellij.psi.search.PsiSearchScopeUtil
+import com.intellij.psi.search.SearchScope
+import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.UElement
+import org.jetbrains.uast.UExpression
+import org.jetbrains.uast.ULocalVariable
+import org.jetbrains.uast.UQualifiedReferenceExpression
+import org.jetbrains.uast.USimpleNameReferenceExpression
+import org.jetbrains.uast.UTryExpression
+import org.jetbrains.uast.getParentOfType
+import org.jetbrains.uast.isUastChildOf
+
+/**
+ * Lint Detector that finds issues with improper usages of the token returned by
+ * Binder.clearCallingIdentity()
+ */
+@Suppress("UnstableApiUsage")
+class CallingIdentityTokenDetector : Detector(), SourceCodeScanner {
+ private companion object {
+ const val CLASS_BINDER = "android.os.Binder"
+ const val CLASS_USER_HANDLE = "android.os.UserHandle"
+
+ @JvmField
+ val callerAwareMethods = listOf(
+ Method.BINDER_GET_CALLING_PID,
+ Method.BINDER_GET_CALLING_UID,
+ Method.BINDER_GET_CALLING_UID_OR_THROW,
+ Method.BINDER_GET_CALLING_USER_HANDLE,
+ Method.USER_HANDLE_GET_CALLING_APP_ID,
+ Method.USER_HANDLE_GET_CALLING_USER_ID
+ )
+ }
+
+ /** Map of <Token variable name, Token object> */
+ private val tokensMap = mutableMapOf<String, Token>()
+
+ override fun getApplicableUastTypes(): List<Class<out UElement?>> =
+ listOf(ULocalVariable::class.java, UQualifiedReferenceExpression::class.java)
+
+ override fun createUastHandler(context: JavaContext): UElementHandler =
+ TokenUastHandler(context)
+
+ /** File analysis starts with a clear map */
+ override fun beforeCheckFile(context: Context) {
+ tokensMap.clear()
+ }
+
+ /**
+ * - If tokensMap has tokens after checking the file -> reports all locations as unused token
+ * issue incidents
+ * - File analysis ends with a clear map
+ */
+ override fun afterCheckFile(context: Context) {
+ for (token in tokensMap.values) {
+ context.report(
+ ISSUE_UNUSED_TOKEN,
+ token.location,
+ getIncidentMessageUnusedToken(token.variableName)
+ )
+ }
+ tokensMap.clear()
+ }
+
+ /** UAST handler that analyses elements and reports incidents */
+ private inner class TokenUastHandler(val context: JavaContext) : UElementHandler() {
+ /**
+ * For every variable initialization with Binder.clearCallingIdentity():
+ * - Checks for non-final token issue
+ * - Checks for unused token issue within different scopes
+ * - Checks for nested calls of clearCallingIdentity() issue
+ * - Stores token variable name, scope in the file and its location in tokensMap
+ */
+ override fun visitLocalVariable(node: ULocalVariable) {
+ val rhsExpression = node.uastInitializer as? UQualifiedReferenceExpression ?: return
+ if (!isMethodCall(rhsExpression, Method.BINDER_CLEAR_CALLING_IDENTITY)) return
+ val location = context.getLocation(node as UElement)
+ val variableName = node.getName()
+ if (!node.isFinal) {
+ context.report(
+ ISSUE_NON_FINAL_TOKEN,
+ location,
+ getIncidentMessageNonFinalToken(variableName)
+ )
+ }
+ // If there exists an unused variable with the same name in the map, we can imply that
+ // we left the scope of the previous declaration, so we need to report the unused token
+ val oldToken = tokensMap[variableName]
+ if (oldToken != null) {
+ context.report(
+ ISSUE_UNUSED_TOKEN,
+ oldToken.location,
+ getIncidentMessageUnusedToken(oldToken.variableName)
+ )
+ }
+ // If there exists a token in the same scope as the current new token, it means that
+ // clearCallingIdentity() has been called at least twice without immediate restoration
+ // of identity, so we need to report the nested call of clearCallingIdentity()
+ val firstCallToken = findFirstTokenInScope(node)
+ if (firstCallToken != null) {
+ context.report(
+ ISSUE_NESTED_CLEAR_IDENTITY_CALLS,
+ createNestedLocation(firstCallToken, location),
+ getIncidentMessageNestedClearIdentityCallsPrimary(
+ firstCallToken.variableName,
+ variableName
+ )
+ )
+ }
+ tokensMap[variableName] = Token(variableName, node.sourcePsi?.getUseScope(), location)
+ }
+
+ /**
+ * For every class.method():
+ * - Checks use of caller-aware methods issue
+ * For every call of Binder.restoreCallingIdentity(token):
+ * - Checks for restoreCallingIdentity() not in the finally block issue
+ * - Removes token from tokensMap if token is within the scope of the method
+ */
+ override fun visitQualifiedReferenceExpression(node: UQualifiedReferenceExpression) {
+ val token = findFirstTokenInScope(node)
+ if (isCallerAwareMethod(node) && token != null) {
+ context.report(
+ ISSUE_USE_OF_CALLER_AWARE_METHODS_WITH_CLEARED_IDENTITY,
+ context.getLocation(node),
+ getIncidentMessageUseOfCallerAwareMethodsWithClearedIdentity(
+ token.variableName,
+ node.asRenderString()
+ )
+ )
+ return
+ }
+ if (!isMethodCall(node, Method.BINDER_RESTORE_CALLING_IDENTITY)) return
+ val selector = node.selector as UCallExpression
+ val arg = selector.valueArguments[0] as? USimpleNameReferenceExpression ?: return
+ val variableName = arg.identifier
+ if (!isInFinallyBlock(node)) {
+ context.report(
+ ISSUE_RESTORE_IDENTITY_CALL_NOT_IN_FINALLY_BLOCK,
+ context.getLocation(node),
+ getIncidentMessageRestoreIdentityCallNotInFinallyBlock(variableName)
+ )
+ }
+ val originalScope = tokensMap[variableName]?.scope ?: return
+ val psi = arg.sourcePsi ?: return
+ if (PsiSearchScopeUtil.isInScope(originalScope, psi)) {
+ tokensMap.remove(variableName)
+ }
+ }
+
+ private fun isCallerAwareMethod(expression: UQualifiedReferenceExpression): Boolean =
+ callerAwareMethods.any { method -> isMethodCall(expression, method) }
+
+ private fun isMethodCall(
+ expression: UQualifiedReferenceExpression,
+ method: Method
+ ): Boolean {
+ val psiMethod = expression.resolve() as? PsiMethod ?: return false
+ return psiMethod.getName() == method.methodName &&
+ context.evaluator.methodMatches(
+ psiMethod,
+ method.className,
+ /* allowInherit */ true,
+ *method.args
+ )
+ }
+ }
+
+ private fun isInFinallyBlock(expression: UExpression): Boolean {
+ val tryExpression = expression.getParentOfType<UTryExpression>(strict = true)
+ ?: return false
+ return expression.isUastChildOf(tryExpression.finallyClause)
+ }
+
+ private fun findFirstTokenInScope(node: UElement): Token? {
+ val psi = node.sourcePsi ?: return null
+ for (token in tokensMap.values) {
+ if (token.scope != null && PsiSearchScopeUtil.isInScope(token.scope, psi)) {
+ return token
+ }
+ }
+ return null
+ }
+
+ /**
+ * Creates a new instance of the primary location with the secondary location
+ *
+ * Here, secondary location is the helper location that shows where the issue originated
+ *
+ * The detector reports locations as objects, so when we add a secondary location to a location
+ * that has multiple issues, the secondary location gets displayed every time a location is
+ * referenced.
+ *
+ * Example:
+ * 1: final long token1 = Binder.clearCallingIdentity();
+ * 2: long token2 = Binder.clearCallingIdentity();
+ * 3: Binder.restoreCallingIdentity(token1);
+ * 4: Binder.restoreCallingIdentity(token2);
+ *
+ * Explanation:
+ * token2 has 2 issues: NonFinal and NestedCalls
+ *
+ * Lint report without cloning Lint report with cloning
+ * line 2: [NonFinalIssue] line 2: [NonFinalIssue]
+ * line 1: [NestedCallsIssue]
+ * line 2: [NestedCallsIssue] line 2: [NestedCallsIssue]
+ * line 1: [NestedCallsIssue] line 1: [NestedCallsIssue]
+ */
+ private fun createNestedLocation(
+ firstCallToken: Token,
+ secondCallTokenLocation: Location
+ ): Location {
+ return cloneLocation(secondCallTokenLocation)
+ .withSecondary(
+ cloneLocation(firstCallToken.location),
+ getIncidentMessageNestedClearIdentityCallsSecondary(
+ firstCallToken.variableName
+ )
+ )
+ }
+
+ private fun cloneLocation(location: Location): Location {
+ // smart cast of location.start to 'Position' is impossible, because 'location.start' is a
+ // public API property declared in different module
+ val locationStart = location.start
+ return if (locationStart == null) {
+ Location.create(location.file)
+ } else {
+ Location.create(location.file, locationStart, location.end)
+ }
+ }
+
+ private enum class Method(
+ val className: String,
+ val methodName: String,
+ val args: Array<String>
+ ) {
+ BINDER_CLEAR_CALLING_IDENTITY(CLASS_BINDER, "clearCallingIdentity", emptyArray()),
+ BINDER_RESTORE_CALLING_IDENTITY(CLASS_BINDER, "restoreCallingIdentity", arrayOf("long")),
+ BINDER_GET_CALLING_PID(CLASS_BINDER, "getCallingPid", emptyArray()),
+ BINDER_GET_CALLING_UID(CLASS_BINDER, "getCallingUid", emptyArray()),
+ BINDER_GET_CALLING_UID_OR_THROW(CLASS_BINDER, "getCallingUidOrThrow", emptyArray()),
+ BINDER_GET_CALLING_USER_HANDLE(CLASS_BINDER, "getCallingUserHandle", emptyArray()),
+ USER_HANDLE_GET_CALLING_APP_ID(CLASS_USER_HANDLE, "getCallingAppId", emptyArray()),
+ USER_HANDLE_GET_CALLING_USER_ID(CLASS_USER_HANDLE, "getCallingUserId", emptyArray())
+ }
+
+ private data class Token(
+ val variableName: String,
+ val scope: SearchScope?,
+ val location: Location
+ )
+}
diff --git a/tools/lint/checks/src/main/java/com/android/lint/CallingIdentityTokenIssueRegistry.kt b/tools/lint/checks/src/main/java/com/android/lint/CallingIdentityTokenIssueRegistry.kt
new file mode 100644
index 0000000..a0e9249
--- /dev/null
+++ b/tools/lint/checks/src/main/java/com/android/lint/CallingIdentityTokenIssueRegistry.kt
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2021 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.lint
+
+import com.android.tools.lint.client.api.IssueRegistry
+// TODO: uncomment when lint API in Soong becomes 30.0+
+// import com.android.tools.lint.client.api.Vendor
+import com.android.tools.lint.detector.api.CURRENT_API
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.google.auto.service.AutoService
+
+@AutoService(IssueRegistry::class)
+@Suppress("UnstableApiUsage")
+class CallingIdentityTokenIssueRegistry : IssueRegistry() {
+ override val issues = listOf(
+ ISSUE_UNUSED_TOKEN,
+ ISSUE_NON_FINAL_TOKEN,
+ ISSUE_NESTED_CLEAR_IDENTITY_CALLS,
+ ISSUE_RESTORE_IDENTITY_CALL_NOT_IN_FINALLY_BLOCK,
+ ISSUE_USE_OF_CALLER_AWARE_METHODS_WITH_CLEARED_IDENTITY
+ )
+
+ override val api: Int
+ get() = CURRENT_API
+
+ override val minApi: Int
+ get() = 8
+
+// TODO: uncomment when lint API in Soong becomes 30.0+
+// override val vendor: Vendor = Vendor(
+// vendorName = "Android Open Source Project",
+// feedbackUrl = "http://b/issues/new?component=315013",
+// contact = "brufino@google.com"
+// )
+
+ companion object {
+ /** Issue: unused token from Binder.clearCallingIdentity() */
+ @JvmField
+ val ISSUE_UNUSED_TOKEN: Issue = Issue.create(
+ id = "UnusedTokenOfOriginalCallingIdentity",
+ briefDescription = "Unused token of Binder.clearCallingIdentity()",
+ explanation = """
+ You cleared the original calling identity with \
+ `Binder.clearCallingIdentity()`, but have not used the returned token to \
+ restore the identity.
+
+ Call `Binder.restoreCallingIdentity(token)` in the `finally` block, at the end \
+ of the method or when you need to restore the identity.
+
+ `token` is the result of `Binder.clearCallingIdentity()`
+ """,
+ category = Category.SECURITY,
+ priority = 6,
+ severity = Severity.WARNING,
+ implementation = Implementation(
+ CallingIdentityTokenDetector::class.java,
+ Scope.JAVA_FILE_SCOPE
+ )
+ )
+
+ fun getIncidentMessageUnusedToken(variableName: String) = "`$variableName` has not been " +
+ "used to restore the calling identity. Call " +
+ "`Binder.restoreCallingIdentity($variableName)` or remove `$variableName`."
+
+ /** Issue: non-final token from Binder.clearCallingIdentity() */
+ @JvmField
+ val ISSUE_NON_FINAL_TOKEN: Issue = Issue.create(
+ id = "NonFinalTokenOfOriginalCallingIdentity",
+ briefDescription = "Non-final token of Binder.clearCallingIdentity()",
+ explanation = """
+ You cleared the original calling identity with \
+ `Binder.clearCallingIdentity()`, but have not made the returned token `final`.
+
+ The token should be `final` in order to prevent it from being overwritten, \
+ which can cause problems when restoring the identity with \
+ `Binder.restoreCallingIdentity(token)`.
+ """,
+ category = Category.SECURITY,
+ priority = 6,
+ severity = Severity.WARNING,
+ implementation = Implementation(
+ CallingIdentityTokenDetector::class.java,
+ Scope.JAVA_FILE_SCOPE
+ )
+ )
+
+ fun getIncidentMessageNonFinalToken(variableName: String) = "`$variableName` is a " +
+ "non-final token from `Binder.clearCallingIdentity()`. Add `final` keyword to " +
+ "`$variableName`."
+
+ /** Issue: nested calls of Binder.clearCallingIdentity() */
+ @JvmField
+ val ISSUE_NESTED_CLEAR_IDENTITY_CALLS: Issue = Issue.create(
+ id = "NestedClearCallingIdentityCalls",
+ briefDescription = "Nested calls of Binder.clearCallingIdentity()",
+ explanation = """
+ You cleared the original calling identity with \
+ `Binder.clearCallingIdentity()` twice without restoring identity with the \
+ result of the first call.
+
+ Make sure to restore the identity after each clear identity call.
+ """,
+ category = Category.SECURITY,
+ priority = 6,
+ severity = Severity.WARNING,
+ implementation = Implementation(
+ CallingIdentityTokenDetector::class.java,
+ Scope.JAVA_FILE_SCOPE
+ )
+ )
+
+ fun getIncidentMessageNestedClearIdentityCallsPrimary(
+ firstCallVariableName: String,
+ secondCallVariableName: String
+ ): String = "The calling identity has already been cleared and returned into " +
+ "`$firstCallVariableName`. Move `$secondCallVariableName` declaration after " +
+ "restoring the calling identity with " +
+ "`Binder.restoreCallingIdentity($firstCallVariableName)`."
+
+ fun getIncidentMessageNestedClearIdentityCallsSecondary(
+ firstCallVariableName: String
+ ): String = "Location of the `$firstCallVariableName` declaration."
+
+ /** Issue: Binder.restoreCallingIdentity() is not in finally block */
+ @JvmField
+ val ISSUE_RESTORE_IDENTITY_CALL_NOT_IN_FINALLY_BLOCK: Issue = Issue.create(
+ id = "RestoreIdentityCallNotInFinallyBlock",
+ briefDescription = "Binder.restoreCallingIdentity() is not in finally block",
+ explanation = """
+ You are restoring the original calling identity with \
+ `Binder.restoreCallingIdentity()`, but the call is not in the `finally` block \
+ of the `try` statement.
+
+ Use the following pattern for running operations with your own identity:
+
+ ```
+ final long token = Binder.clearCallingIdentity();
+ try {
+ // Code using your own identity
+ } finally {
+ Binder.restoreCallingIdentity();
+ }
+ ```
+
+ If you do not surround the code using your identity with the `try` statement \
+ and call `Binder.restoreCallingIdentity()` in the `finally` block, you may run \
+ code with your identity that was originally intended to run with the calling \
+ application's identity.
+ """,
+ category = Category.SECURITY,
+ priority = 6,
+ severity = Severity.WARNING,
+ implementation = Implementation(
+ CallingIdentityTokenDetector::class.java,
+ Scope.JAVA_FILE_SCOPE
+ )
+ )
+
+ fun getIncidentMessageRestoreIdentityCallNotInFinallyBlock(variableName: String): String =
+ "`Binder.restoreCallingIdentity($variableName)` is not in the `finally` block. " +
+ "Surround the call with `finally` block."
+
+ /** Issue: Use of caller-aware methods after Binder.clearCallingIdentity() */
+ @JvmField
+ val ISSUE_USE_OF_CALLER_AWARE_METHODS_WITH_CLEARED_IDENTITY: Issue = Issue.create(
+ id = "UseOfCallerAwareMethodsWithClearedIdentity",
+ briefDescription = "Use of caller-aware methods after " +
+ "Binder.clearCallingIdentity()",
+ explanation = """
+ You cleared the original calling identity with \
+ `Binder.clearCallingIdentity()`, but used one of the methods below before \
+ restoring the identity. These methods will use your own identity instead of \
+ the caller's identity, so if this is expected replace them with methods that \
+ explicitly query your own identity such as `Process.myUid()`, \
+ `Process.myPid()` and `UserHandle.myUserId()`, otherwise move those methods \
+ out of the `Binder.clearCallingIdentity()` / `Binder.restoreCallingIdentity()` \
+ section.
+
+ ```
+ Binder.getCallingPid()
+ Binder.getCallingUid()
+ Binder.getCallingUidOrThrow()
+ Binder.getCallingUserHandle()
+ UserHandle.getCallingAppId()
+ UserHandle.getCallingUserId()
+ ```
+ """,
+ category = Category.SECURITY,
+ priority = 6,
+ severity = Severity.WARNING,
+ implementation = Implementation(
+ CallingIdentityTokenDetector::class.java,
+ Scope.JAVA_FILE_SCOPE
+ )
+ )
+
+ fun getIncidentMessageUseOfCallerAwareMethodsWithClearedIdentity(
+ variableName: String,
+ methodName: String
+ ): String = "You cleared the original identity with `Binder.clearCallingIdentity()` " +
+ "and returned into `$variableName`, so `$methodName` will be using your own " +
+ "identity instead of the caller's. Either explicitly query your own identity or " +
+ "move it after restoring the identity with " +
+ "`Binder.restoreCallingIdentity($variableName)`."
+ }
+}
diff --git a/tools/lint/checks/src/test/java/com/android/lint/CallingIdentityTokenDetectorTest.kt b/tools/lint/checks/src/test/java/com/android/lint/CallingIdentityTokenDetectorTest.kt
new file mode 100644
index 0000000..8a81609
--- /dev/null
+++ b/tools/lint/checks/src/test/java/com/android/lint/CallingIdentityTokenDetectorTest.kt
@@ -0,0 +1,747 @@
+/*
+ * Copyright (C) 2021 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.lint
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.checks.infrastructure.TestFile
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+
+@Suppress("UnstableApiUsage")
+class CallingIdentityTokenDetectorTest : LintDetectorTest() {
+ override fun getDetector(): Detector = CallingIdentityTokenDetector()
+
+ override fun getIssues(): List<Issue> = listOf(
+ CallingIdentityTokenIssueRegistry.ISSUE_UNUSED_TOKEN,
+ CallingIdentityTokenIssueRegistry.ISSUE_NON_FINAL_TOKEN,
+ CallingIdentityTokenIssueRegistry.ISSUE_NESTED_CLEAR_IDENTITY_CALLS,
+ CallingIdentityTokenIssueRegistry.ISSUE_RESTORE_IDENTITY_CALL_NOT_IN_FINALLY_BLOCK,
+ CallingIdentityTokenIssueRegistry
+ .ISSUE_USE_OF_CALLER_AWARE_METHODS_WITH_CLEARED_IDENTITY
+ )
+
+ /** No issue scenario */
+
+ fun testDoesNotDetectIssuesInCorrectScenario() {
+ lint().files(
+ java(
+ """
+ package test.pkg;
+ import android.os.Binder;
+ public class TestClass1 {
+ private void testMethod() {
+ final long token1 = Binder.clearCallingIdentity();
+ try {
+ } finally {
+ Binder.restoreCallingIdentity(token1);
+ }
+ final long token2 = android.os.Binder.clearCallingIdentity();
+ try {
+ } finally {
+ android.os.Binder.restoreCallingIdentity(token2);
+ }
+ }
+ }
+ """
+ ).indented(),
+ *stubs
+ )
+ .run()
+ .expectClean()
+ }
+
+ /** Unused token issue tests */
+
+ fun testDetectsUnusedTokens() {
+ lint().files(
+ java(
+ """
+ package test.pkg;
+ import android.os.Binder;
+ public class TestClass1 {
+ private void testMethod1() {
+ final long token1 = Binder.clearCallingIdentity();
+ }
+ private void testMethod2() {
+ final long token2 = android.os.Binder.clearCallingIdentity();
+ }
+ }
+ """
+ ).indented(),
+ *stubs
+ )
+ .run()
+ .expect(
+ """
+ src/test/pkg/TestClass1.java:5: Warning: token1 has not been used to \
+ restore the calling identity. Call Binder.restoreCallingIdentity(token1) \
+ or remove token1. [UnusedTokenOfOriginalCallingIdentity]
+ final long token1 = Binder.clearCallingIdentity();
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ src/test/pkg/TestClass1.java:8: Warning: token2 has not been used to \
+ restore the calling identity. Call Binder.restoreCallingIdentity(token2) \
+ or remove token2. [UnusedTokenOfOriginalCallingIdentity]
+ final long token2 = android.os.Binder.clearCallingIdentity();
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ 0 errors, 2 warnings
+ """.addLineContinuation()
+ )
+ }
+
+ fun testDetectsUnusedTokensInScopes() {
+ lint().files(
+ java(
+ """
+ package test.pkg;
+ import android.os.Binder;
+ public class TestClass1 {
+ private void testMethod1() {
+ final long token = Binder.clearCallingIdentity();
+ }
+ private void testMethod2() {
+ long token = 0;
+ try {
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ }
+ """
+ ).indented(),
+ *stubs
+ )
+ .run()
+ .expect(
+ """
+ src/test/pkg/TestClass1.java:5: Warning: token has not been used to \
+ restore the calling identity. Call Binder.restoreCallingIdentity(token) \
+ or remove token. [UnusedTokenOfOriginalCallingIdentity]
+ final long token = Binder.clearCallingIdentity();
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ 0 errors, 1 warnings
+ """.addLineContinuation()
+ )
+ }
+
+ fun testDoesNotDetectUsedTokensInScopes() {
+ lint().files(
+ java(
+ """
+ package test.pkg;
+ import android.os.Binder;
+ public class TestClass1 {
+ private void testMethod1() {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ private void testMethod2() {
+ long token = 0;
+ try {
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ }
+ """
+ ).indented(),
+ *stubs
+ )
+ .run()
+ .expectClean()
+ }
+
+ fun testDetectsUnusedTokensWithSimilarNamesInScopes() {
+ lint().files(
+ java(
+ """
+ package test.pkg;
+ import android.os.Binder;
+ public class TestClass1 {
+ private void testMethod1() {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ } finally {
+ }
+ }
+ private void testMethod2() {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ } finally {
+ }
+ }
+ }
+ """
+ ).indented(),
+ *stubs
+ )
+ .run()
+ .expect(
+ """
+ src/test/pkg/TestClass1.java:5: Warning: token has not been used to \
+ restore the calling identity. Call Binder.restoreCallingIdentity(token) \
+ or remove token. [UnusedTokenOfOriginalCallingIdentity]
+ final long token = Binder.clearCallingIdentity();
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ src/test/pkg/TestClass1.java:11: Warning: token has not been used to \
+ restore the calling identity. Call Binder.restoreCallingIdentity(token) \
+ or remove token. [UnusedTokenOfOriginalCallingIdentity]
+ final long token = Binder.clearCallingIdentity();
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ 0 errors, 2 warnings
+ """.addLineContinuation()
+ )
+ }
+
+ /** Non-final token issue tests */
+
+ fun testDetectsNonFinalTokens() {
+ lint().files(
+ java(
+ """
+ package test.pkg;
+ import android.os.Binder;
+ public class TestClass1 {
+ private void testMethod() {
+ long token1 = Binder.clearCallingIdentity();
+ try {
+ } finally {
+ Binder.restoreCallingIdentity(token1);
+ }
+ long token2 = android.os.Binder.clearCallingIdentity();
+ try {
+ } finally {
+ android.os.Binder.restoreCallingIdentity(token2);
+ }
+ }
+ }
+ """
+ ).indented(),
+ *stubs
+ )
+ .run()
+ .expect(
+ """
+ src/test/pkg/TestClass1.java:5: Warning: token1 is a non-final token from \
+ Binder.clearCallingIdentity(). Add final keyword to token1. \
+ [NonFinalTokenOfOriginalCallingIdentity]
+ long token1 = Binder.clearCallingIdentity();
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ src/test/pkg/TestClass1.java:10: Warning: token2 is a non-final token from \
+ Binder.clearCallingIdentity(). Add final keyword to token2. \
+ [NonFinalTokenOfOriginalCallingIdentity]
+ long token2 = android.os.Binder.clearCallingIdentity();
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ 0 errors, 2 warnings
+ """.addLineContinuation()
+ )
+ }
+
+ /** Nested clearCallingIdentity() calls issue tests */
+
+ fun testDetectsNestedClearCallingIdentityCallsPattern1() {
+ // Pattern: clear - clear - restore - clear - restore - restore
+ lint().files(
+ java(
+ """
+ package test.pkg;
+ import android.os.Binder;
+ public class TestClass1 {
+ private void testMethod() {
+ final long token1 = Binder.clearCallingIdentity();
+ final long token2 = android.os.Binder.clearCallingIdentity();
+ try {
+ } finally {
+ android.os.Binder.restoreCallingIdentity(token1);
+ }
+ final long token3 = android.os.Binder.clearCallingIdentity();
+ try {
+ } finally {
+ android.os.Binder.restoreCallingIdentity(token2);
+ }
+ try {
+ } finally {
+ android.os.Binder.restoreCallingIdentity(token3);
+ }
+ }
+ }
+ """
+ ).indented(),
+ *stubs
+ )
+ .run()
+ .expect(
+ """
+ src/test/pkg/TestClass1.java:6: Warning: The calling identity has already \
+ been cleared and returned into token1. Move token2 declaration after \
+ restoring the calling identity with Binder.restoreCallingIdentity(token1). \
+ [NestedClearCallingIdentityCalls]
+ final long token2 = android.os.Binder.clearCallingIdentity();
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ src/test/pkg/TestClass1.java:5: Location of the token1 declaration.
+ src/test/pkg/TestClass1.java:11: Warning: The calling identity has already \
+ been cleared and returned into token2. Move token3 declaration after \
+ restoring the calling identity with Binder.restoreCallingIdentity(token2). \
+ [NestedClearCallingIdentityCalls]
+ final long token3 = android.os.Binder.clearCallingIdentity();
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ src/test/pkg/TestClass1.java:6: Location of the token2 declaration.
+ 0 errors, 2 warnings
+ """.addLineContinuation()
+ )
+ }
+
+ fun testDetectsNestedClearCallingIdentityCallsPattern2() {
+ // Pattern: clear - clear - clear - restore - restore - restore
+ lint().files(
+ java(
+ """
+ package test.pkg;
+ import android.os.Binder;
+ public class TestClass1 {
+ private void testMethod() {
+ final long token1 = Binder.clearCallingIdentity();
+ final long token2 = android.os.Binder.clearCallingIdentity();
+ final long token3 = android.os.Binder.clearCallingIdentity();
+ try {
+ } finally {
+ Binder.restoreCallingIdentity(token1);
+ }
+ try {
+ } finally {
+ android.os.Binder.restoreCallingIdentity(token2);
+ }
+ try {
+ } finally {
+ android.os.Binder.restoreCallingIdentity(token3);
+ }
+ }
+ }
+ """
+ ).indented(),
+ *stubs
+ )
+ .run()
+ .expect(
+ """
+ src/test/pkg/TestClass1.java:6: Warning: The calling identity has already \
+ been cleared and returned into token1. Move token2 declaration after \
+ restoring the calling identity with Binder.restoreCallingIdentity(token1). \
+ [NestedClearCallingIdentityCalls]
+ final long token2 = android.os.Binder.clearCallingIdentity();
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ src/test/pkg/TestClass1.java:5: Location of the token1 declaration.
+ src/test/pkg/TestClass1.java:7: Warning: The calling identity has already \
+ been cleared and returned into token1. Move token3 declaration after \
+ restoring the calling identity with Binder.restoreCallingIdentity(token1). \
+ [NestedClearCallingIdentityCalls]
+ final long token3 = android.os.Binder.clearCallingIdentity();
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ src/test/pkg/TestClass1.java:5: Location of the token1 declaration.
+ 0 errors, 2 warnings
+ """.addLineContinuation()
+ )
+ }
+
+ fun testDetectsNestedClearCallingIdentityCallsInScopes() {
+ lint().files(
+ java(
+ """
+ package test.pkg;
+ import android.os.Binder;
+ public class TestClass1 {
+ private void testMethod() {
+ final long token1 = Binder.clearCallingIdentity();
+ try {
+ final long token2 = android.os.Binder.clearCallingIdentity();
+ try {
+ } finally {
+ Binder.restoreCallingIdentity(token2);
+ }
+ } finally {
+ android.os.Binder.restoreCallingIdentity(token1);
+ }
+ }
+ }
+ """
+ ).indented(),
+ *stubs
+ )
+ .run()
+ .expect(
+ """
+ src/test/pkg/TestClass1.java:7: Warning: The calling identity has already \
+ been cleared and returned into token1. Move token2 declaration after \
+ restoring the calling identity with Binder.restoreCallingIdentity(token1). \
+ [NestedClearCallingIdentityCalls]
+ final long token2 = android.os.Binder.clearCallingIdentity();
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ src/test/pkg/TestClass1.java:5: Location of the token1 declaration.
+ 0 errors, 1 warnings
+ """.addLineContinuation()
+ )
+ }
+
+ /** restoreCallingIdentity() call not in finally block issue tests */
+
+ fun testDetectsRestoreCallingIdentityCallNotInFinally() {
+ lint().files(
+ java(
+ """
+ package test.pkg;
+ import android.os.Binder;
+ public class TestClass1 {
+ private void testMethod1() {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ } catch (Exception e) {
+ }
+ Binder.restoreCallingIdentity(token);
+ }
+ private void testMethod2() {
+ final long token = android.os.Binder.clearCallingIdentity();
+ try {
+ } finally {
+ }
+ android.os.Binder.restoreCallingIdentity(token);
+ }
+ }
+ """
+ ).indented(),
+ *stubs
+ )
+ .run()
+ .expect(
+ """
+ src/test/pkg/TestClass1.java:9: Warning: \
+ Binder.restoreCallingIdentity(token) is not in the finally block. Surround \
+ the call with finally block. [RestoreIdentityCallNotInFinallyBlock]
+ Binder.restoreCallingIdentity(token);
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ src/test/pkg/TestClass1.java:16: Warning: \
+ Binder.restoreCallingIdentity(token) is not in the finally block. Surround \
+ the call with finally block. [RestoreIdentityCallNotInFinallyBlock]
+ android.os.Binder.restoreCallingIdentity(token);
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ 0 errors, 2 warnings
+ """.addLineContinuation()
+ )
+ }
+
+ fun testDetectsRestoreCallingIdentityCallNotInFinallyInScopes() {
+ lint().files(
+ java(
+ """
+ package test.pkg;
+ import android.os.Binder;
+ public class TestClass1 {
+ private void testMethod1() {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ } catch (Exception e) {
+ }
+ {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ private void testMethod2() {
+ final long token = android.os.Binder.clearCallingIdentity();
+ try {
+ } finally {
+ }
+ {
+ {
+ {
+ android.os.Binder.restoreCallingIdentity(token);
+ }
+ }
+ }
+ }
+ }
+ """
+ ).indented(),
+ *stubs
+ )
+ .run()
+ .expect(
+ """
+ src/test/pkg/TestClass1.java:10: Warning: \
+ Binder.restoreCallingIdentity(token) is not in the finally block. Surround \
+ the call with finally block. [RestoreIdentityCallNotInFinallyBlock]
+ Binder.restoreCallingIdentity(token);
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ src/test/pkg/TestClass1.java:21: Warning: \
+ Binder.restoreCallingIdentity(token) is not in the finally block. Surround \
+ the call with finally block. [RestoreIdentityCallNotInFinallyBlock]
+ android.os.Binder.restoreCallingIdentity(token);
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ 0 errors, 2 warnings
+ """.addLineContinuation()
+ )
+ }
+
+ fun testDoesNotDetectRestoreCallingIdentityCallInFinallyInScopes() {
+ lint().files(
+ java(
+ """
+ package test.pkg;
+ import android.os.Binder;
+ public class TestClass1 {
+ private void testMethod() {
+ final long token1 = Binder.clearCallingIdentity();
+ try {
+ } finally {
+ {
+ {
+ Binder.restoreCallingIdentity(token1);
+ }
+ }
+ }
+ final long token2 = android.os.Binder.clearCallingIdentity();
+ try {
+ } finally {
+ {
+ {
+ android.os.Binder.restoreCallingIdentity(token2);
+ }
+ }
+ }
+ }
+ }
+ """
+ ).indented(),
+ *stubs
+ )
+ .run()
+ .expectClean()
+ }
+
+ /** Use of caller-aware methods after clearCallingIdentity() issue tests */
+
+ fun testDetectsUseOfCallerAwareMethodsWithClearedIdentityIssuesInScopes() {
+ lint().files(
+ java(
+ """
+ package test.pkg;
+ import android.os.Binder;
+ import android.os.UserHandle;
+ public class TestClass1 {
+ private void testMethod() {
+ final long token = Binder.clearCallingIdentity();
+ int pid1 = Binder.getCallingPid();
+ int pid2 = android.os.Binder.getCallingPid();
+ int uid1 = Binder.getCallingUid();
+ int uid2 = android.os.Binder.getCallingUid();
+ try {
+ int uid3 = Binder.getCallingUidOrThrow();
+ int uid4 = android.os.Binder.getCallingUidOrThrow();
+ UserHandle uh1 = Binder.getCallingUserHandle();
+ UserHandle uh2 = android.os.Binder.getCallingUserHandle();
+ {
+ int appId1 = UserHandle.getCallingAppId();
+ int appId2 = android.os.UserHandle.getCallingAppId();
+ int userId1 = UserHandle.getCallingUserId();
+ int userId2 = android.os.UserHandle.getCallingUserId();
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ }
+ """
+ ).indented(),
+ *stubs
+ )
+ .run()
+ .expect(
+ """
+ src/test/pkg/TestClass1.java:7: Warning: You cleared the original identity \
+ with Binder.clearCallingIdentity() and returned into token, so \
+ Binder.getCallingPid() will be using your own identity instead of the \
+ caller's. Either explicitly query your own identity or move it after \
+ restoring the identity with Binder.restoreCallingIdentity(token). \
+ [UseOfCallerAwareMethodsWithClearedIdentity]
+ int pid1 = Binder.getCallingPid();
+ ~~~~~~~~~~~~~~~~~~~~~~
+ src/test/pkg/TestClass1.java:8: Warning: You cleared the original identity \
+ with Binder.clearCallingIdentity() and returned into token, so \
+ android.os.Binder.getCallingPid() will be using your own identity instead \
+ of the caller's. Either explicitly query your own identity or move it \
+ after restoring the identity with Binder.restoreCallingIdentity(token). \
+ [UseOfCallerAwareMethodsWithClearedIdentity]
+ int pid2 = android.os.Binder.getCallingPid();
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ src/test/pkg/TestClass1.java:9: Warning: You cleared the original identity \
+ with Binder.clearCallingIdentity() and returned into token, so \
+ Binder.getCallingUid() will be using your own identity instead of the \
+ caller's. Either explicitly query your own identity or move it after \
+ restoring the identity with Binder.restoreCallingIdentity(token). \
+ [UseOfCallerAwareMethodsWithClearedIdentity]
+ int uid1 = Binder.getCallingUid();
+ ~~~~~~~~~~~~~~~~~~~~~~
+ src/test/pkg/TestClass1.java:10: Warning: You cleared the original \
+ identity with Binder.clearCallingIdentity() and returned into token, so \
+ android.os.Binder.getCallingUid() will be using your own identity instead \
+ of the caller's. Either explicitly query your own identity or move it \
+ after restoring the identity with Binder.restoreCallingIdentity(token). \
+ [UseOfCallerAwareMethodsWithClearedIdentity]
+ int uid2 = android.os.Binder.getCallingUid();
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ src/test/pkg/TestClass1.java:12: Warning: You cleared the original \
+ identity with Binder.clearCallingIdentity() and returned into token, so \
+ Binder.getCallingUidOrThrow() will be using your own identity instead of \
+ the caller's. Either explicitly query your own identity or move it after \
+ restoring the identity with Binder.restoreCallingIdentity(token). \
+ [UseOfCallerAwareMethodsWithClearedIdentity]
+ int uid3 = Binder.getCallingUidOrThrow();
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ src/test/pkg/TestClass1.java:13: Warning: You cleared the original \
+ identity with Binder.clearCallingIdentity() and returned into token, so \
+ android.os.Binder.getCallingUidOrThrow() will be using your own identity \
+ instead of the caller's. Either explicitly query your own identity or move \
+ it after restoring the identity with Binder.restoreCallingIdentity(token). \
+ [UseOfCallerAwareMethodsWithClearedIdentity]
+ int uid4 = android.os.Binder.getCallingUidOrThrow();
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ src/test/pkg/TestClass1.java:14: Warning: You cleared the original \
+ identity with Binder.clearCallingIdentity() and returned into token, so \
+ Binder.getCallingUserHandle() will be using your own identity instead of \
+ the caller's. Either explicitly query your own identity or move it after \
+ restoring the identity with Binder.restoreCallingIdentity(token). \
+ [UseOfCallerAwareMethodsWithClearedIdentity]
+ UserHandle uh1 = Binder.getCallingUserHandle();
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ src/test/pkg/TestClass1.java:15: Warning: You cleared the original \
+ identity with Binder.clearCallingIdentity() and returned into token, so \
+ android.os.Binder.getCallingUserHandle() will be using your own identity \
+ instead of the caller's. Either explicitly query your own identity or move \
+ it after restoring the identity with Binder.restoreCallingIdentity(token). \
+ [UseOfCallerAwareMethodsWithClearedIdentity]
+ UserHandle uh2 = android.os.Binder.getCallingUserHandle();
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ src/test/pkg/TestClass1.java:17: Warning: You cleared the original \
+ identity with Binder.clearCallingIdentity() and returned into token, so \
+ UserHandle.getCallingAppId() will be using your own identity instead of \
+ the caller's. Either explicitly query your own identity or move it after \
+ restoring the identity with Binder.restoreCallingIdentity(token). \
+ [UseOfCallerAwareMethodsWithClearedIdentity]
+ int appId1 = UserHandle.getCallingAppId();
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ src/test/pkg/TestClass1.java:18: Warning: You cleared the original \
+ identity with Binder.clearCallingIdentity() and returned into token, so \
+ android.os.UserHandle.getCallingAppId() will be using your own identity \
+ instead of the caller's. Either explicitly query your own identity or move \
+ it after restoring the identity with Binder.restoreCallingIdentity(token). \
+ [UseOfCallerAwareMethodsWithClearedIdentity]
+ int appId2 = android.os.UserHandle.getCallingAppId();
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ src/test/pkg/TestClass1.java:19: Warning: You cleared the original \
+ identity with Binder.clearCallingIdentity() and returned into token, so \
+ UserHandle.getCallingUserId() will be using your own identity instead of \
+ the caller's. Either explicitly query your own identity or move it after \
+ restoring the identity with Binder.restoreCallingIdentity(token). \
+ [UseOfCallerAwareMethodsWithClearedIdentity]
+ int userId1 = UserHandle.getCallingUserId();
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ src/test/pkg/TestClass1.java:20: Warning: You cleared the original \
+ identity with Binder.clearCallingIdentity() and returned into token, so \
+ android.os.UserHandle.getCallingUserId() will be using your own identity \
+ instead of the caller's. Either explicitly query your own identity or move \
+ it after restoring the identity with Binder.restoreCallingIdentity(token). \
+ [UseOfCallerAwareMethodsWithClearedIdentity]
+ int userId2 = android.os.UserHandle.getCallingUserId();
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ 0 errors, 12 warnings
+ """.addLineContinuation()
+ )
+ }
+
+ /** Stubs for classes used for testing */
+
+ private val binderStub: TestFile = java(
+ """
+ package android.os;
+ public class Binder {
+ public static final native long clearCallingIdentity() {
+ return 0;
+ }
+ public static final native void restoreCallingIdentity(long token) {
+ }
+ public static final native int getCallingPid() {
+ return 0;
+ }
+ public static final native int getCallingUid() {
+ return 0;
+ }
+ public static final int getCallingUidOrThrow() {
+ return 0;
+ }
+ public static final @NonNull UserHandle getCallingUserHandle() {
+ return UserHandle.of(UserHandle.getUserId(getCallingUid()));
+ }
+ }
+ """
+ ).indented()
+
+ private val userHandleStub: TestFile = java(
+ """
+ package android.os;
+ import android.annotation.AppIdInt;
+ import android.annotation.UserIdInt;
+ public class UserHandle {
+ public static @AppIdInt int getCallingAppId() {
+ return getAppId(Binder.getCallingUid());
+ }
+ public static @UserIdInt int getCallingUserId() {
+ return getUserId(Binder.getCallingUid());
+ }
+ public static @UserIdInt int getUserId(int uid) {
+ return 0;
+ }
+ public static @AppIdInt int getAppId(int uid) {
+ return 0;
+ }
+ public static UserHandle of(@UserIdInt int userId) {
+ return new UserHandle();
+ }
+ }
+ """
+ ).indented()
+
+ private val userIdIntStub: TestFile = java(
+ """
+ package android.annotation;
+ public @interface UserIdInt {
+ }
+ """
+ ).indented()
+
+ private val appIdIntStub: TestFile = java(
+ """
+ package android.annotation;
+ public @interface AppIdInt {
+ }
+ """
+ ).indented()
+
+ private val stubs = arrayOf(binderStub, userHandleStub, userIdIntStub, appIdIntStub)
+
+ // Substitutes "backslash + new line" with an empty string to imitate line continuation
+ private fun String.addLineContinuation(): String = this.trimIndent().replace("\\\n", "")
+}