1 /*
<lambda>null2  * 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.parcel
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.PsiArrayType
23 import com.intellij.psi.PsiCallExpression
24 import com.intellij.psi.PsiClassType
25 import com.intellij.psi.PsiIntersectionType
26 import com.intellij.psi.PsiMethod
27 import com.intellij.psi.PsiType
28 import com.intellij.psi.PsiTypeParameter
29 import com.intellij.psi.PsiWildcardType
30 import org.jetbrains.uast.UCallExpression
31 import org.jetbrains.uast.UElement
32 import org.jetbrains.uast.UExpression
33 import org.jetbrains.uast.UVariable
34 
35 /**
36  * Subclass this class and override {@link #getBoundingClass} to report an unsafe Parcel API issue
37  * with a fix that migrates towards the new safer API by appending an argument in the form of
38  * {@code com.package.ItemType.class} coming from the result of the overridden method.
39  */
40 abstract class CallMigrator(
41         val method: Method,
42         private val rejects: Set<String> = emptySet(),
43 ) {
44     open fun report(context: JavaContext, call: UCallExpression, method: PsiMethod) {
45         val location = context.getLocation(call)
46         val itemType = filter(getBoundingClass(context, call, method))
47         val fix = (itemType as? PsiClassType)?.let { type ->
48             getParcelFix(location, this.method.name, getArgumentSuffix(type))
49         }
50         val message = "Unsafe `${this.method.className}.${this.method.name}()` API usage"
51         context.report(SaferParcelChecker.ISSUE_UNSAFE_API_USAGE, call, location, message, fix)
52     }
53 
54     protected open fun getArgumentSuffix(type: PsiClassType) =
55             ", ${type.rawType().canonicalText}.class"
56 
57     protected open fun getBoundingClass(
58             context: JavaContext,
59             call: UCallExpression,
60             method: PsiMethod,
61     ): PsiType? = null
62 
63     protected fun getItemType(type: PsiType, container: String): PsiClassType? {
64         val supers = getParentTypes(type).mapNotNull { it as? PsiClassType }
65         val containerType = supers.firstOrNull { it.rawType().canonicalText == container }
66                 ?: return null
67         val itemType = containerType.parameters.getOrNull(0) ?: return null
68         // TODO: Expand to other types, see PsiTypeVisitor
69         return when (itemType) {
70             is PsiClassType -> itemType
71             is PsiWildcardType -> itemType.bound as PsiClassType
72             else -> null
73         }
74     }
75 
76     /**
77      * Tries to obtain the type expected by the "receiving" end given a certain {@link UElement}.
78      *
79      * This could be an assignment, an argument passed to a method call, to a constructor call, a
80      * type cast, etc. If no receiving end is found, the type of the UExpression itself is returned.
81      */
82     protected fun getReceivingType(expression: UElement): PsiType? {
83         val parent = expression.uastParent
84         var type = when (parent) {
85             is UCallExpression -> {
86                 val i = parent.valueArguments.indexOf(expression)
87                 val psiCall = parent.sourcePsi as? PsiCallExpression ?: return null
88                 val typeSubstitutor = psiCall.resolveMethodGenerics().substitutor
89                 val method = psiCall.resolveMethod()!!
90                 method.getSignature(typeSubstitutor).parameterTypes[i]
91             }
92             is UVariable -> parent.type
93             is UExpression -> parent.getExpressionType()
94             else -> null
95         }
96         if (type == null && expression is UExpression) {
97             type = expression.getExpressionType()
98         }
99         return type
100     }
101 
102     protected fun filter(type: PsiType?): PsiType? {
103         // It's important that PsiIntersectionType case is above the one that check the type in
104         // rejects, because for intersect types, the canonicalText is one of the terms.
105         if (type is PsiIntersectionType) {
106             return type.conjuncts.mapNotNull(this::filter).firstOrNull()
107         }
108         if (type == null || type.canonicalText in rejects) {
109             return null
110         }
111         if (type is PsiClassType && type.resolve() is PsiTypeParameter) {
112             return null
113         }
114         return type
115     }
116 
117     private fun getParentTypes(type: PsiType): Set<PsiType> =
118             type.superTypes.flatMap(::getParentTypes).toSet() + type
119 
120     protected fun getParcelFix(location: Location, method: String, arguments: String) =
121             LintFix
122                     .create()
123                     .name("Migrate to safer Parcel.$method() API")
124                     .replace()
125                     .range(location)
126                     .pattern("$method\\s*\\(((?:.|\\n)*)\\)")
127                     .with("\\k<1>$arguments")
128                     .autoFix()
129                     .build()
130 }
131 
132 /**
133  * This class derives the type to be appended by inferring the generic type of the {@code container}
134  * type (eg. "java.util.List") of the {@code argument}-th argument.
135  */
136 class ContainerArgumentMigrator(
137         method: Method,
138         private val argument: Int,
139         private val container: String,
140         rejects: Set<String> = emptySet(),
141 ) : CallMigrator(method, rejects) {
getBoundingClassnull142     override fun getBoundingClass(
143             context: JavaContext, call: UCallExpression, method: PsiMethod
144     ): PsiType? {
145         val firstParamType = call.valueArguments[argument].getExpressionType() ?: return null
146         return getItemType(firstParamType, container)!!
147     }
148 
149     /**
150      * We need to insert a casting construct in the class parameter. For example:
151      *   (Class<Foo<Bar>>) (Class<?>) Foo.class.
152      * This is needed for when the arguments of the conflict (eg. when there is List<Foo<Bar>> and
153      * class type is Class<Foo?).
154      */
getArgumentSuffixnull155     override fun getArgumentSuffix(type: PsiClassType): String {
156         if (type.parameters.isNotEmpty()) {
157             val rawType = type.rawType()
158             return ", (Class<${type.canonicalText}>) (Class<?>) ${rawType.canonicalText}.class"
159         }
160         return super.getArgumentSuffix(type)
161     }
162 }
163 
164 /**
165  * This class derives the type to be appended by inferring the generic type of the {@code container}
166  * type (eg. "java.util.List") of the return type of the method.
167  */
168 class ContainerReturnMigrator(
169         method: Method,
170         private val container: String,
171         rejects: Set<String> = emptySet(),
172 ) : CallMigrator(method, rejects) {
getBoundingClassnull173     override fun getBoundingClass(
174             context: JavaContext, call: UCallExpression, method: PsiMethod
175     ): PsiType? {
176         val type = getReceivingType(call.uastParent!!) ?: return null
177         return getItemType(type, container)
178     }
179 }
180 
181 /**
182  * This class derives the type to be appended by inferring the expected type for the method result.
183  */
184 class ReturnMigrator(
185         method: Method,
186         rejects: Set<String> = emptySet(),
187 ) : CallMigrator(method, rejects) {
getBoundingClassnull188     override fun getBoundingClass(
189             context: JavaContext, call: UCallExpression, method: PsiMethod
190     ): PsiType? {
191         return getReceivingType(call.uastParent!!)
192     }
193 }
194 
195 /**
196  * This class appends the class loader and the class object by deriving the type from the method
197  * result.
198  */
199 class ReturnMigratorWithClassLoader(
200         method: Method,
201         rejects: Set<String> = emptySet(),
202 ) : CallMigrator(method, rejects) {
getBoundingClassnull203     override fun getBoundingClass(
204             context: JavaContext, call: UCallExpression, method: PsiMethod
205     ): PsiType? {
206         return getReceivingType(call.uastParent!!)
207     }
208 
getArgumentSuffixnull209     override fun getArgumentSuffix(type: PsiClassType): String =
210             "${type.rawType().canonicalText}.class.getClassLoader(), " +
211                     "${type.rawType().canonicalText}.class"
212 
213 }
214 
215 /**
216  * This class derives the type to be appended by inferring the expected array type
217  * for the method result.
218  */
219 class ArrayReturnMigrator(
220     method: Method,
221     rejects: Set<String> = emptySet(),
222 ) : CallMigrator(method, rejects) {
223     override fun getBoundingClass(
224            context: JavaContext, call: UCallExpression, method: PsiMethod
225     ): PsiType? {
226         val type = getReceivingType(call.uastParent!!)
227         return (type as? PsiArrayType)?.componentType
228     }
229 }
230