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