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:
Dmitry Petrov
2017-07-21 17:42:18 +03:00
parent 2427b2cc6c
commit 5d44e095c8
26 changed files with 563 additions and 21 deletions
@@ -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)
}
}
}
@@ -53,7 +53,8 @@ object JvmPlatformConfigurator : PlatformConfigurator(
UnsupportedSyntheticCallableReferenceChecker(),
SuperCallWithDefaultArgumentsChecker(),
ProtectedSyntheticExtensionCallChecker,
ReifiedTypeParameterSubstitutionChecker()
ReifiedTypeParameterSubstitutionChecker(),
RuntimeAssertionsOnExtensionReceiverCallChecker
),
additionalTypeCheckers = listOf(