Nullability assertions for extension receiver
In Kotlin 1.1 and before, there were no nullability assertions on extension receivers, because receiver is resolved with NO_EXPECTED_TYPE. So, if an expression of platform type is passed as an extension receiver to a non-private function, it would fail with IllegalArgumentException. However, if the function is private, then we generated no parameter assertions under assumption that such function can be called from Kotlin only, and all arguments are checked on the call site. Thus 'null' could propagate indefinitely. In Kotlin 1.2, we do the following: - Generate nullability assertions for expression receivers. NB nullability assertions are stored for ReceiverValue instances, not for expressions: given expression can act as receiver in different calls, each with an expected receiver type of its own. - Generate nullability assertions for extension receivers of private operator functions. NB it still can throw NPE for some particular "optimized" cases, but at least those nulls would not propagate indefinitely. This behavior is disabled by an "advanced" command-line option '-Xno-receiver-assertions'.
This commit is contained in:
@@ -50,6 +50,8 @@ public class JVMConfigurationKeys {
|
||||
|
||||
public static final CompilerConfigurationKey<Boolean> DISABLE_CALL_ASSERTIONS =
|
||||
CompilerConfigurationKey.create("disable not-null call assertions");
|
||||
public static final CompilerConfigurationKey<Boolean> DISABLE_RECEIVER_ASSERTIONS =
|
||||
CompilerConfigurationKey.create("disable not-null call receiver assertions");
|
||||
public static final CompilerConfigurationKey<Boolean> DISABLE_PARAM_ASSERTIONS =
|
||||
CompilerConfigurationKey.create("disable not-null parameter assertions");
|
||||
public static final CompilerConfigurationKey<Boolean> DISABLE_OPTIMIZATION =
|
||||
|
||||
@@ -18,6 +18,8 @@ package org.jetbrains.kotlin.resolve.jvm
|
||||
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||
import org.jetbrains.kotlin.psi.KtExpression
|
||||
import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver
|
||||
import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue
|
||||
import org.jetbrains.kotlin.util.slicedMap.BasicWritableSlice
|
||||
import org.jetbrains.kotlin.util.slicedMap.RewritePolicy
|
||||
import org.jetbrains.kotlin.util.slicedMap.Slices
|
||||
@@ -27,6 +29,9 @@ object JvmBindingContextSlices {
|
||||
@JvmField
|
||||
val RUNTIME_ASSERTION_INFO: WritableSlice<KtExpression, RuntimeAssertionInfo> = BasicWritableSlice(RewritePolicy.DO_NOTHING)
|
||||
|
||||
@JvmField
|
||||
val RECEIVER_RUNTIME_ASSERTION_INFO: WritableSlice<ExpressionReceiver, RuntimeAssertionInfo> = BasicWritableSlice(RewritePolicy.DO_NOTHING)
|
||||
|
||||
@JvmField
|
||||
val LOAD_FROM_JAVA_SIGNATURE_ERRORS: WritableSlice<DeclarationDescriptor, List<String>> = Slices.createCollectiveSlice()
|
||||
|
||||
|
||||
@@ -17,14 +17,24 @@
|
||||
package org.jetbrains.kotlin.resolve.jvm
|
||||
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.descriptors.ReceiverParameterDescriptor
|
||||
import org.jetbrains.kotlin.load.java.typeEnhancement.hasEnhancedNullability
|
||||
import org.jetbrains.kotlin.psi.KtExpression
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.isSafeCall
|
||||
import org.jetbrains.kotlin.resolve.calls.checkers.AdditionalTypeChecker
|
||||
import org.jetbrains.kotlin.resolve.calls.checkers.CallChecker
|
||||
import org.jetbrains.kotlin.resolve.calls.checkers.CallCheckerContext
|
||||
import org.jetbrains.kotlin.resolve.calls.context.ResolutionContext
|
||||
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValue
|
||||
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValueFactory
|
||||
import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver
|
||||
import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.TypeUtils
|
||||
import org.jetbrains.kotlin.types.isError
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
||||
|
||||
class RuntimeAssertionInfo(val needNotNullAssertion: Boolean, val message: String) {
|
||||
interface DataFlowExtras {
|
||||
@@ -73,6 +83,19 @@ class RuntimeAssertionInfo(val needNotNullAssertion: Boolean, val message: Strin
|
||||
}
|
||||
}
|
||||
|
||||
class RuntimeAssertionsDataFlowExtras(
|
||||
private val c: ResolutionContext<*>,
|
||||
private val dataFlowValue: DataFlowValue,
|
||||
private val expression: KtExpression
|
||||
) : RuntimeAssertionInfo.DataFlowExtras {
|
||||
override val canBeNull: Boolean
|
||||
get() = c.dataFlowInfo.getStableNullability(dataFlowValue).canBeNull()
|
||||
override val possibleTypes: Set<KotlinType>
|
||||
get() = c.dataFlowInfo.getCollectedTypes(dataFlowValue)
|
||||
override val presentableText: String
|
||||
get() = StringUtil.trimMiddle(expression.text, 50)
|
||||
}
|
||||
|
||||
object RuntimeAssertionsTypeChecker : AdditionalTypeChecker {
|
||||
override fun checkType(expression: KtExpression, expressionType: KotlinType, expressionTypeWithSmartCast: KotlinType, c: ResolutionContext<*>) {
|
||||
if (TypeUtils.noExpectedType(c.expectedType)) return
|
||||
@@ -80,20 +103,39 @@ object RuntimeAssertionsTypeChecker : AdditionalTypeChecker {
|
||||
val assertionInfo = RuntimeAssertionInfo.create(
|
||||
c.expectedType,
|
||||
expressionType,
|
||||
object : RuntimeAssertionInfo.DataFlowExtras {
|
||||
override val canBeNull: Boolean
|
||||
get() = c.dataFlowInfo.getStableNullability(dataFlowValue).canBeNull()
|
||||
override val possibleTypes: Set<KotlinType>
|
||||
get() = c.dataFlowInfo.getCollectedTypes(dataFlowValue)
|
||||
override val presentableText: String
|
||||
get() = StringUtil.trimMiddle(expression.text, 50)
|
||||
|
||||
private val dataFlowValue = DataFlowValueFactory.createDataFlowValue(expression, expressionType, c)
|
||||
}
|
||||
RuntimeAssertionsDataFlowExtras(c, DataFlowValueFactory.createDataFlowValue(expression, expressionType, c), expression)
|
||||
)
|
||||
|
||||
if (assertionInfo != null) {
|
||||
c.trace.record(JvmBindingContextSlices.RUNTIME_ASSERTION_INFO, expression, assertionInfo)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object RuntimeAssertionsOnExtensionReceiverCallChecker : CallChecker {
|
||||
override fun check(resolvedCall: ResolvedCall<*>, reportOn: PsiElement, context: CallCheckerContext) {
|
||||
if (resolvedCall.call.isSafeCall()) return
|
||||
|
||||
val callee = resolvedCall.resultingDescriptor
|
||||
checkReceiver(callee.extensionReceiverParameter, resolvedCall.extensionReceiver, context)
|
||||
}
|
||||
|
||||
private fun checkReceiver(receiverParameter: ReceiverParameterDescriptor?, receiverValue: ReceiverValue?, context: CallCheckerContext) {
|
||||
if (receiverParameter == null || receiverValue == null) return
|
||||
val expressionReceiverValue = receiverValue.safeAs<ExpressionReceiver>() ?: return
|
||||
val receiverExpression = expressionReceiverValue.expression
|
||||
val c = context.resolutionContext
|
||||
val dataFlowValue = DataFlowValueFactory.createDataFlowValue(receiverExpression, receiverValue.type, c)
|
||||
|
||||
val assertionInfo = RuntimeAssertionInfo.create(
|
||||
receiverParameter.type,
|
||||
receiverValue.type,
|
||||
RuntimeAssertionsDataFlowExtras(c, dataFlowValue, receiverExpression)
|
||||
)
|
||||
|
||||
if (assertionInfo != null) {
|
||||
c.trace.record(JvmBindingContextSlices.RECEIVER_RUNTIME_ASSERTION_INFO, expressionReceiverValue, assertionInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -53,7 +53,8 @@ object JvmPlatformConfigurator : PlatformConfigurator(
|
||||
UnsupportedSyntheticCallableReferenceChecker(),
|
||||
SuperCallWithDefaultArgumentsChecker(),
|
||||
ProtectedSyntheticExtensionCallChecker,
|
||||
ReifiedTypeParameterSubstitutionChecker()
|
||||
ReifiedTypeParameterSubstitutionChecker(),
|
||||
RuntimeAssertionsOnExtensionReceiverCallChecker
|
||||
),
|
||||
|
||||
additionalTypeCheckers = listOf(
|
||||
|
||||
Reference in New Issue
Block a user