xref: /aosp_15_r20/frameworks/base/tools/lint/common/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt (revision d57664e9bc4670b3ecf6748a746a57c557b6bc9e)
1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.android.lint.aidl
18 
19 import com.android.tools.lint.detector.api.JavaContext
20 import com.android.tools.lint.detector.api.LintFix
21 import com.android.tools.lint.detector.api.Location
22 import com.intellij.psi.PsiClass
23 import com.intellij.psi.PsiReferenceList
24 import org.jetbrains.uast.UMethod
25 
26 /**
27  * Given a UMethod, determine if this method is the entrypoint to an interface generated by AIDL,
28  * returning the interface name if so, otherwise returning null.
29  */
getContainingAidlInterfacenull30 fun getContainingAidlInterface(context: JavaContext, node: UMethod): String? {
31     return containingAidlInterfacePsiClass(context, node)?.name
32 }
33 
34 /**
35  * Given a UMethod, determine if this method is the entrypoint to an interface generated by AIDL,
36  * returning the fully qualified interface name if so, otherwise returning null.
37  */
getContainingAidlInterfaceQualifiednull38 fun getContainingAidlInterfaceQualified(context: JavaContext, node: UMethod): String? {
39     return containingAidlInterfacePsiClass(context, node)?.qualifiedName
40 }
41 
containingAidlInterfacePsiClassnull42 private fun containingAidlInterfacePsiClass(context: JavaContext, node: UMethod): PsiClass? {
43     val containingStub = containingStub(context, node) ?: return null
44     val superMethod = node.findSuperMethods(containingStub)
45     if (superMethod.isEmpty()) return null
46     return containingStub.containingClass
47 }
48 
49 /**
50  * Returns the containing Stub class if any. This is not sufficient to infer that the method itself
51  * extends an AIDL generated method. See getContainingAidlInterface for that purpose.
52  */
containingStubnull53 fun containingStub(context: JavaContext, node: UMethod?): PsiClass? {
54     var superClass = node?.containingClass?.superClass
55     while (superClass != null) {
56         if (isStub(context, superClass)) return superClass
57         superClass = superClass.superClass
58     }
59     return null
60 }
61 
isStubnull62 fun isStub(context: JavaContext, psiClass: PsiClass?): Boolean {
63     if (psiClass == null) return false
64     if (psiClass.name != "Stub") return false
65     if (!context.evaluator.isStatic(psiClass)) return false
66     if (!context.evaluator.isAbstract(psiClass)) return false
67 
68     if (!hasSingleAncestor(psiClass.extendsList, BINDER_CLASS)) return false
69 
70     val parent = psiClass.parent as? PsiClass ?: return false
71     if (!hasSingleAncestor(parent.extendsList, IINTERFACE_INTERFACE)) return false
72 
73     val parentName = parent.qualifiedName ?: return false
74     if (!hasSingleAncestor(psiClass.implementsList, parentName)) return false
75 
76     return true
77 }
78 
hasSingleAncestornull79 private fun hasSingleAncestor(references: PsiReferenceList?, qualifiedName: String) =
80         references != null &&
81                 references.referenceElements.size == 1 &&
82                 references.referenceElements[0].qualifiedName == qualifiedName
83 
84 fun getHelperMethodCallSourceString(node: UMethod) = "${node.name}$AIDL_PERMISSION_HELPER_SUFFIX()"
85 
86 fun getHelperMethodFix(
87     node: UMethod,
88     manualCheckLocation: Location,
89     prepend: Boolean = true
90 ): LintFix {
91     val helperMethodSource = getHelperMethodCallSourceString(node)
92     val indent = " ".repeat(manualCheckLocation.start?.column ?: 0)
93     val newText = "$helperMethodSource;${if (prepend) "\n\n$indent" else ""}"
94 
95     val fix = LintFix.create()
96             .replace()
97             .range(manualCheckLocation)
98             .with(newText)
99             .reformat(true)
100             .autoFix()
101 
102     if (prepend) fix.beginning()
103 
104     return fix.build()
105 }
106 
107 /**
108  * PermissionAnnotationDetector uses this method to determine whether a specific file should be
109  * checked for unannotated methods. Only files located in directories whose paths begin with one
110  * of these prefixes will be considered.
111  */
isSystemServicePathnull112 fun isSystemServicePath(context: JavaContext): Boolean {
113     val systemServicePathPrefixes = setOf(
114         "frameworks/base/services",
115         "frameworks/base/apex",
116         "frameworks/opt/wear",
117         "packages/modules"
118     )
119 
120     val filePath = context.file.path
121 
122     // We perform `filePath.contains` instead of `filePath.startsWith` since getting the
123     // relative path of a source file is non-trivial. That is because `context.file.path`
124     // returns the path to where soong builds the file (i.e. /out/soong/...). Moreover, the
125     // logic to extract the relative path would need to consider several /out/soong/...
126     // locations patterns.
127     return systemServicePathPrefixes.any { filePath.contains(it) }
128 }
129