From 1de05b965e224de891106fcd04b7ed97ca38dee2 Mon Sep 17 00:00:00 2001 From: Yan Zhulanow Date: Fri, 10 Jun 2016 21:35:41 +0300 Subject: [PATCH] KT-12015 (Kotlin Lint False Positive for Bundle.getInt()) Java approach is not sufficient here, in Kotlin receivers can be implicit. Use Kotlin extension/dispatch receivers to figure out the receiver type for our method call. (cherry picked from commit cdedf1d) --- .../android/tools/klint/checks/ApiDetector.kt | 26 ++++++++- .../uast/expressions/UCallExpression.kt | 2 + .../java/expressions/JavaUAssertExpression.kt | 3 ++ .../java/expressions/javaUCallExpressions.kt | 16 ++++++ .../KotlinUFunctionCallExpression.kt | 54 ++++++++++++++----- ...opertyAsCallAndroidUastVisitorExtension.kt | 5 ++ 6 files changed, 90 insertions(+), 16 deletions(-) diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ApiDetector.kt b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ApiDetector.kt index 3f550ebe243..f2568da47f2 100755 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ApiDetector.kt +++ b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ApiDetector.kt @@ -166,8 +166,18 @@ open class ApiDetector : Detector(), UastScanner { val parentClass = declaration.parent as? UClass ?: return if (!isSdkClass(parentClass) || checkAosp(parentClass)) return - val parentInternalName = (node as? UCallExpression)?.getReceiver()?.getExpressionType()?.resolveClass(context)?.internalName - ?: parentClass.internalName ?: return + + var parentInternalName = parentClass.internalName + + if (node is UCallExpression) { + val clazz = node.receiverType?.resolveClass(context)?.let { getSupertypeFromAndroidSdk(context, it) } + val internalName = clazz?.internalName + if (internalName != null) { + parentInternalName = internalName + } + } + + if (parentInternalName == null) return when (declaration) { is UFunction -> { @@ -197,6 +207,18 @@ open class ApiDetector : Detector(), UastScanner { return mMinApi } + + private fun getSupertypeFromAndroidSdk(context: UastContext, clazz: UClass): UClass? { + tailrec fun getSuperclassFromAndroidSdk(clazz: UClass): UClass? { + if (clazz.fqName?.startsWith("android.") ?: false) return clazz + return getSuperclassFromAndroidSdk(clazz.getSuperClass(context) ?: return null) + } + + val superClass = getSuperclassFromAndroidSdk(clazz) + if (superClass != null) return superClass + + return clazz.superTypes.firstOrNull { it.fqName?.startsWith("android.") ?: false }?.resolve(context) + } } companion object { diff --git a/plugins/uast-common/src/org/jetbrains/uast/expressions/UCallExpression.kt b/plugins/uast-common/src/org/jetbrains/uast/expressions/UCallExpression.kt index b550bab0014..010b4ed5809 100644 --- a/plugins/uast-common/src/org/jetbrains/uast/expressions/UCallExpression.kt +++ b/plugins/uast-common/src/org/jetbrains/uast/expressions/UCallExpression.kt @@ -21,6 +21,8 @@ import org.jetbrains.uast.visitor.UastVisitor * Represents a call expression (function call, constructor call, array initializer). */ interface UCallExpression : UExpression, UResolvable { + val receiverType: UType? + /** * Returns the call kind. */ diff --git a/plugins/uast-java/src/org/jetbrains/uast/java/expressions/JavaUAssertExpression.kt b/plugins/uast-java/src/org/jetbrains/uast/java/expressions/JavaUAssertExpression.kt index fe8764f3f82..bda84960082 100644 --- a/plugins/uast-java/src/org/jetbrains/uast/java/expressions/JavaUAssertExpression.kt +++ b/plugins/uast-java/src/org/jetbrains/uast/java/expressions/JavaUAssertExpression.kt @@ -27,6 +27,9 @@ class JavaUAssertExpression( val condition: UExpression by lz { JavaConverter.convertOrEmpty(psi.assertCondition, this) } val message: UExpression? by lz { JavaConverter.convertOrNull(psi.assertDescription, this) } + override val receiverType: UType? + get() = null + override val functionReference: USimpleReferenceExpression? get() = null diff --git a/plugins/uast-java/src/org/jetbrains/uast/java/expressions/javaUCallExpressions.kt b/plugins/uast-java/src/org/jetbrains/uast/java/expressions/javaUCallExpressions.kt index d2482ba57d8..ad005094bea 100644 --- a/plugins/uast-java/src/org/jetbrains/uast/java/expressions/javaUCallExpressions.kt +++ b/plugins/uast-java/src/org/jetbrains/uast/java/expressions/javaUCallExpressions.kt @@ -26,6 +26,13 @@ class JavaUCallExpression( override val psi: PsiMethodCallExpression, override val parent: UElement ) : JavaAbstractUElement(), UCallExpression, PsiElementBacked, JavaUElementWithType { + override val receiverType: UType? + get() { + val qualifiedExpression = parent as? UQualifiedExpression ?: return null + if (qualifiedExpression.selector != this) return null + return qualifiedExpression.receiver.getExpressionType() + } + override val kind: UastCallKind get() = UastCallKind.FUNCTION_CALL @@ -63,6 +70,9 @@ class JavaConstructorUCallExpression( } } + override val receiverType: UType? + get() = null + override val functionReference: USimpleReferenceExpression? get() = null @@ -119,6 +129,9 @@ class JavaArrayInitializerUCallExpression( override val psi: PsiArrayInitializerExpression, override val parent: UElement ) : JavaAbstractUElement(), UCallExpression, PsiElementBacked, JavaUElementWithType, JavaEvaluatableUElement { + override val receiverType: UType? + get() = null + override val functionReference: USimpleReferenceExpression? get() = null @@ -150,6 +163,9 @@ class JavaAnnotationArrayInitializerUCallExpression( override val psi: PsiArrayInitializerMemberValue, override val parent: UElement ) : JavaAbstractUElement(), UCallExpression, PsiElementBacked, JavaUElementWithType, JavaEvaluatableUElement { + override val receiverType: UType? + get() = null + override val kind: UastCallKind get() = UastCallKind.ARRAY_INITIALIZER diff --git a/plugins/uast-kotlin/src/org/jetbrains/kotlin/uast/expressions/KotlinUFunctionCallExpression.kt b/plugins/uast-kotlin/src/org/jetbrains/kotlin/uast/expressions/KotlinUFunctionCallExpression.kt index 8353e6be25a..ba2d0178c6c 100644 --- a/plugins/uast-kotlin/src/org/jetbrains/kotlin/uast/expressions/KotlinUFunctionCallExpression.kt +++ b/plugins/uast-kotlin/src/org/jetbrains/kotlin/uast/expressions/KotlinUFunctionCallExpression.kt @@ -30,7 +30,15 @@ class KotlinUFunctionCallExpression( override val psi: KtCallExpression, override val parent: UElement ) : KotlinAbstractUElement(), UCallExpression, PsiElementBacked, KotlinUElementWithType { - override val functionName: String? by lz { resolveCall()?.resultingDescriptor?.name?.asString().orAnonymous() } + private val resolvedCall by lz { psi.getResolvedCall(psi.analyze(BodyResolveMode.PARTIAL)) } + + override val receiverType by lz { + val resolvedCall = this.resolvedCall ?: return@lz null + val receiver = resolvedCall.extensionReceiver ?: resolvedCall.dispatchReceiver ?: return@lz null + KotlinConverter.convert(receiver.type, psi.project, null) + } + + override val functionName: String? by lz { resolvedCall?.resultingDescriptor?.name?.asString().orAnonymous() } override fun matchesFunctionName(name: String) = functionName == name override val functionNameElement by lz { psi.calleeExpression?.let { KotlinConverter.convert(it, this) } } @@ -56,14 +64,13 @@ class KotlinUFunctionCallExpression( override val typeArguments by lz { psi.typeArguments.map { KotlinConverter.convert(it.typeReference, this) } } override val kind by lz { - when (resolveCall()?.resultingDescriptor) { + when (resolvedCall?.resultingDescriptor) { is ConstructorDescriptor -> UastCallKind.CONSTRUCTOR_CALL else -> UastCallKind.FUNCTION_CALL } } override fun resolve(context: UastContext): UFunction? { - val resolvedCall = resolveCall() val descriptor = resolvedCall?.resultingDescriptor ?: return null val source = descriptor.toSource() ?: return null @@ -74,9 +81,7 @@ class KotlinUFunctionCallExpression( } return context.convert(source) as? UFunction - } - - private fun resolveCall() = psi.getResolvedCall(psi.analyze(BodyResolveMode.PARTIAL)) + } } class KotlinUComponentFunctionCallExpression( @@ -84,14 +89,35 @@ class KotlinUComponentFunctionCallExpression( n: Int, override val parent: UElement ) : UCallExpression, PsiElementBacked { - override val valueArgumentCount = 0 - override val valueArguments = emptyList() - override val typeArgumentCount = 0 - override val typeArguments = emptyList() - override val classReference = null + override val receiverType: UType? + get() = null + + override val valueArgumentCount: Int + get() = 0 + + override val valueArguments: List + get() = emptyList() + + override val typeArgumentCount: Int + get() = 0 + + override val typeArguments: List + get() = emptyList() + + override val classReference: USimpleReferenceExpression? + get() = null + override val functionName = "component$n" + override val functionReference by lz { KotlinStringUSimpleReferenceExpression(functionName, this) } - override val functionNameElement = null - override val kind = UastCallKind.FUNCTION_CALL - override fun resolve(context: UastContext) = null + + override val functionNameElement: UElement? + get() = null + + override val kind: UastCallKind + get() = UastCallKind.FUNCTION_CALL + + override fun resolve(context: UastContext): UFunction? { + return null + } } \ No newline at end of file diff --git a/plugins/uast-kotlin/src/org/jetbrains/kotlin/uast/extensions/PropertyAsCallAndroidUastVisitorExtension.kt b/plugins/uast-kotlin/src/org/jetbrains/kotlin/uast/extensions/PropertyAsCallAndroidUastVisitorExtension.kt index da912aff55a..56b5a76c28a 100644 --- a/plugins/uast-kotlin/src/org/jetbrains/kotlin/uast/extensions/PropertyAsCallAndroidUastVisitorExtension.kt +++ b/plugins/uast-kotlin/src/org/jetbrains/kotlin/uast/extensions/PropertyAsCallAndroidUastVisitorExtension.kt @@ -51,6 +51,11 @@ class PropertyAsCallAndroidUastVisitorExtension : UastVisitorExtension { override val parent = element.parent override val psi = ktElement + override val receiverType by lz { + val type = (resolvedCall.extensionReceiver ?: resolvedCall.dispatchReceiver)?.type ?: return@lz null + KotlinConverter.convert(type, psi.project, null) + } + override val functionReference = KotlinNameUSimpleReferenceExpression( expr.psi, expr.identifier, expr.parent, accessorDescriptor)