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"
|
||||
/>
|
||||
|
||||
<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"/>
|
||||
|
||||
+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");
|
||||
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