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