Introduce inspection for nullable extension receiver of inline function
So #KT-22303 Fixed
This commit is contained in:
@@ -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>
|
||||||
@@ -2527,7 +2527,7 @@
|
|||||||
language="kotlin"
|
language="kotlin"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.ExplicitThisInspection"
|
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.ExplicitThisInspection"
|
||||||
displayName="Redundant explicit 'this'"
|
displayName="Redundant explicit 'this'"
|
||||||
groupPath="Kotlin"
|
groupPath="Kotlin"
|
||||||
groupName="Redundant constructs"
|
groupName="Redundant constructs"
|
||||||
@@ -2673,6 +2673,15 @@
|
|||||||
language="kotlin"
|
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"/>
|
<referenceImporter implementation="org.jetbrains.kotlin.idea.quickfix.KotlinReferenceImporter"/>
|
||||||
|
|
||||||
<fileType.fileViewProviderFactory filetype="KJSM" implementationClass="com.intellij.psi.ClassFileViewProviderFactory"/>
|
<fileType.fileViewProviderFactory filetype="KJSM" implementationClass="com.intellij.psi.ClassFileViewProviderFactory"/>
|
||||||
|
|||||||
+100
@@ -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.*)"
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+11
@@ -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>
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
public class JavaClass {
|
||||||
|
public static String getNull() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static My getMy() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+15
@@ -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() {}
|
||||||
|
}
|
||||||
+9
@@ -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>
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"inspectionClass": "org.jetbrains.kotlin.idea.inspections.PlatformExtensionReceiverOfInlineInspection",
|
||||||
|
"withRuntime": "true",
|
||||||
|
"isMultiModule": "false"
|
||||||
|
}
|
||||||
+6
@@ -54,4 +54,10 @@ public class MultiFileInspectionTestGenerated extends AbstractMultiFileInspectio
|
|||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/multiFileInspections/mismatchedProjectAndDirectory/mismatchedProjectAndDirectory.test");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/multiFileInspections/mismatchedProjectAndDirectory/mismatchedProjectAndDirectory.test");
|
||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("platformExtensionReceiverOfInline/platformExtensionReceiverOfInline.test")
|
||||||
|
public void testPlatformExtensionReceiverOfInline_PlatformExtensionReceiverOfInline() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/multiFileInspections/platformExtensionReceiverOfInline/platformExtensionReceiverOfInline.test");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user