Introduce inspection for nullable extension receiver of inline function

So #KT-22303 Fixed
This commit is contained in:
Mikhail Glukhikh
2018-01-19 11:32:12 +03:00
parent 08d3f20e09
commit c90056f8c4
9 changed files with 174 additions and 1 deletions
@@ -0,0 +1,9 @@
<html>
<body>
Reports potentially unsafe calls of inline functions with flexible nullable (platform type with unknown nullability) extension receivers.
In Kotlin 1.0 or 1.1, such calls do not include nullability check in bytecode,
but in Kotlin 1.2, nullability check is included and can provoke NPE if actual receiver is null.
It's recommended to add explicit '!!' if exception is what you want,
or consider changing the function' receiver type to nullable if it should work without exceptions.
</body>
</html>
+10 -1
View File
@@ -2527,7 +2527,7 @@
language="kotlin"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.ExplicitThisInspection"
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.ExplicitThisInspection"
displayName="Redundant explicit 'this'"
groupPath="Kotlin"
groupName="Redundant constructs"
@@ -2673,6 +2673,15 @@
language="kotlin"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.PlatformExtensionReceiverOfInlineInspection"
displayName="Unsafe call of inline function with nullable extension receiver"
groupPath="Kotlin"
groupName="Java interop issues"
enabledByDefault="true"
level="WARNING"
language="kotlin"
/>
<referenceImporter implementation="org.jetbrains.kotlin.idea.quickfix.KotlinReferenceImporter"/>
<fileType.fileViewProviderFactory filetype="KJSM" implementationClass="com.intellij.psi.ClassFileViewProviderFactory"/>
@@ -0,0 +1,100 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.inspections
import com.intellij.codeInspection.IntentionWrapper
import com.intellij.codeInspection.LocalInspectionToolSession
import com.intellij.codeInspection.ProblemHighlightType
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.openapi.editor.event.DocumentEvent
import com.intellij.openapi.editor.event.DocumentListener
import com.intellij.openapi.ui.LabeledComponent
import com.intellij.ui.EditorTextField
import org.intellij.lang.regexp.RegExpFileType
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.project.languageVersionSettings
import org.jetbrains.kotlin.idea.quickfix.AddExclExclCallFix
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtDotQualifiedExpression
import org.jetbrains.kotlin.psi.KtVisitorVoid
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.types.isNullabilityFlexible
import java.awt.BorderLayout
import java.util.regex.PatternSyntaxException
import javax.swing.JPanel
class PlatformExtensionReceiverOfInlineInspection : AbstractKotlinInspection() {
private var nameRegex: Regex? = defaultNamePattern.toRegex()
var namePattern: String = defaultNamePattern
set(value) {
field = value
nameRegex = try {
value.toRegex()
} catch (e: PatternSyntaxException) {
null
}
}
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession) =
object : KtVisitorVoid() {
override fun visitDotQualifiedExpression(expression: KtDotQualifiedExpression) {
super.visitDotQualifiedExpression(expression)
if (!expression.languageVersionSettings.supportsFeature(LanguageFeature.NullabilityAssertionOnExtensionReceiver)) {
return
}
val nameRegex = nameRegex
val callExpression = expression.selectorExpression as? KtCallExpression ?: return
val calleeText = callExpression.calleeExpression?.text ?: return
if (nameRegex != null && !nameRegex.matches(calleeText)) {
return
}
val context = expression.analyze(BodyResolveMode.PARTIAL)
val resolvedCall = expression.getResolvedCall(context) ?: return
val extensionReceiverType = resolvedCall.extensionReceiver?.type ?: return
if (!extensionReceiverType.isNullabilityFlexible()) return
val descriptor = resolvedCall.resultingDescriptor as? FunctionDescriptor ?: return
if (!descriptor.isInline) return
val receiverExpression = expression.receiverExpression
holder.registerProblem(
receiverExpression,
"Call of inline function with nullable extension receiver can provoke NPE in Kotlin 1.2+",
ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
IntentionWrapper(AddExclExclCallFix(receiverExpression), receiverExpression.containingKtFile)
)
}
}
override fun createOptionsPanel() = OptionsPanel(this)
class OptionsPanel internal constructor(owner: PlatformExtensionReceiverOfInlineInspection) : JPanel() {
init {
layout = BorderLayout()
val regexField = EditorTextField(owner.namePattern, null, RegExpFileType.INSTANCE).apply {
setOneLineMode(true)
}
regexField.document.addDocumentListener(object : DocumentListener {
override fun documentChanged(e: DocumentEvent?) {
owner.namePattern = regexField.text
}
})
val labeledComponent = LabeledComponent.create(regexField, "Pattern:", BorderLayout.WEST)
add(labeledComponent, BorderLayout.NORTH)
}
}
companion object {
const val defaultNamePattern = "(toBoolean)|(content.*)"
}
}
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
@@ -0,0 +1,9 @@
public class JavaClass {
public static String getNull() {
return null;
}
public static My getMy() {
return null;
}
}
@@ -0,0 +1,15 @@
fun test() {
JavaClass.getNull().toBoolean() // ISE in runtime
JavaClass.getNull().toInt() // OK
"123".toBoolean() // OK
null.toBoolean() // Compilation error, no inspection message
JavaClass.getNull().contentNotInline() // OK
JavaClass.getMy().contentNonExtensionInlineFun() // OK
}
fun String.contentNotInline() {}
class My {
inline fun contentNonExtensionInlineFun() {}
}
@@ -0,0 +1,9 @@
<problems>
<problem>
<file>Test.kt</file>
<line>2</line>
<entry_point TYPE="file" FQNAME="Test.kt" />
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Unsafe call of inline function with nullable extension receiver</problem_class>
<description>Call of inline function with nullable extension receiver can provoke NPE in Kotlin 1.2+</description>
</problem>
</problems>
@@ -0,0 +1,5 @@
{
"inspectionClass": "org.jetbrains.kotlin.idea.inspections.PlatformExtensionReceiverOfInlineInspection",
"withRuntime": "true",
"isMultiModule": "false"
}
@@ -54,4 +54,10 @@ public class MultiFileInspectionTestGenerated extends AbstractMultiFileInspectio
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/multiFileInspections/mismatchedProjectAndDirectory/mismatchedProjectAndDirectory.test");
doTest(fileName);
}
@TestMetadata("platformExtensionReceiverOfInline/platformExtensionReceiverOfInline.test")
public void testPlatformExtensionReceiverOfInline_PlatformExtensionReceiverOfInline() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/multiFileInspections/platformExtensionReceiverOfInline/platformExtensionReceiverOfInline.test");
doTest(fileName);
}
}