/* * 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.aidl import com.android.tools.lint.detector.api.JavaContext import com.android.tools.lint.detector.api.LintFix import com.android.tools.lint.detector.api.Location import com.intellij.psi.PsiClass import com.intellij.psi.PsiReferenceList import org.jetbrains.uast.UMethod /** * Given a UMethod, determine if this method is the entrypoint to an interface generated by AIDL, * returning the interface name if so, otherwise returning null. */ fun getContainingAidlInterface(context: JavaContext, node: UMethod): String? { return containingAidlInterfacePsiClass(context, node)?.name } /** * Given a UMethod, determine if this method is the entrypoint to an interface generated by AIDL, * returning the fully qualified interface name if so, otherwise returning null. */ fun getContainingAidlInterfaceQualified(context: JavaContext, node: UMethod): String? { return containingAidlInterfacePsiClass(context, node)?.qualifiedName } private fun containingAidlInterfacePsiClass(context: JavaContext, node: UMethod): PsiClass? { val containingStub = containingStub(context, node) ?: return null val superMethod = node.findSuperMethods(containingStub) if (superMethod.isEmpty()) return null return containingStub.containingClass } /** * Returns the containing Stub class if any. This is not sufficient to infer that the method itself * extends an AIDL generated method. See getContainingAidlInterface for that purpose. */ fun containingStub(context: JavaContext, node: UMethod?): PsiClass? { var superClass = node?.containingClass?.superClass while (superClass != null) { if (isStub(context, superClass)) return superClass superClass = superClass.superClass } return null } fun isStub(context: JavaContext, psiClass: PsiClass?): Boolean { if (psiClass == null) return false if (psiClass.name != "Stub") return false if (!context.evaluator.isStatic(psiClass)) return false if (!context.evaluator.isAbstract(psiClass)) return false if (!hasSingleAncestor(psiClass.extendsList, CLASS_BINDER)) return false val parent = psiClass.parent as? PsiClass ?: return false if (!hasSingleAncestor(parent.extendsList, INTERFACE_IINTERFACE)) return false val parentName = parent.qualifiedName ?: return false if (!hasSingleAncestor(psiClass.implementsList, parentName)) return false return true } private fun hasSingleAncestor(references: PsiReferenceList?, qualifiedName: String) = references != null && references.referenceElements.size == 1 && references.referenceElements[0].qualifiedName == qualifiedName fun getHelperMethodCallSourceString(node: UMethod) = "${node.name}$AIDL_PERMISSION_HELPER_SUFFIX()" fun getHelperMethodFix( node: UMethod, manualCheckLocation: Location, prepend: Boolean = true ): LintFix { val helperMethodSource = getHelperMethodCallSourceString(node) val indent = " ".repeat(manualCheckLocation.start?.column ?: 0) val newText = "$helperMethodSource;${if (prepend) "\n\n$indent" else ""}" val fix = LintFix.create() .replace() .range(manualCheckLocation) .with(newText) .reformat(true) .autoFix() if (prepend) fix.beginning() return fix.build() } /** * PermissionAnnotationDetector uses this method to determine whether a specific file should be * checked for unannotated methods. Only files located in directories whose paths begin with one * of these prefixes will be considered. */ fun isSystemServicePath(context: JavaContext): Boolean { val systemServicePathPrefixes = setOf( "frameworks/base/services", "frameworks/base/apex", "frameworks/opt/wear", "packages/modules" ) val filePath = context.file.path // We perform `filePath.contains` instead of `filePath.startsWith` since getting the // relative path of a source file is non-trivial. That is because `context.file.path` // returns the path to where soong builds the file (i.e. /out/soong/...). Moreover, the // logic to extract the relative path would need to consider several /out/soong/... // locations patterns. return systemServicePathPrefixes.any { filePath.contains(it) } }