JVM_IR: JvmArgumentNullabilityAssertionsLowering

This commit is contained in:
Dmitry Petrov
2019-11-25 17:34:20 +03:00
parent c676b58c14
commit fc7783c7dd
8 changed files with 286 additions and 1 deletions
@@ -299,6 +299,8 @@ private val jvmFilePhases =
staticDefaultFunctionPhase then staticDefaultFunctionPhase then
syntheticAccessorPhase then syntheticAccessorPhase then
jvmArgumentNullabilityAssertions then
toArrayPhase then toArrayPhase then
jvmBuiltinOptimizationLoweringPhase then jvmBuiltinOptimizationLoweringPhase then
additionalClassAnnotationPhase then additionalClassAnnotationPhase then
@@ -0,0 +1,243 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.backend.jvm.lower
import org.jetbrains.kotlin.backend.common.FileLoweringPass
import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.config.ApiVersion
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.declarations.IrField
import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.declarations.IrVariable
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
val jvmArgumentNullabilityAssertions =
makeIrFilePhase(
::JvmArgumentNullabilityAssertionsLowering,
name = "Nullability assertions on arguments",
description = "Transform nullability assertions on arguments according to the compiler settings"
)
class JvmArgumentNullabilityAssertionsLowering(context: JvmBackendContext) :
FileLoweringPass, IrElementVisitorVoid {
private val isWithUnifiedNullChecks =
context.state.languageVersionSettings.apiVersion >= ApiVersion.KOTLIN_1_4
private val isCallAssertionsDisabled = context.state.isCallAssertionsDisabled
private val isReceiverAssertionsDisabled = context.state.isReceiverAssertionsDisabled
override fun lower(irFile: IrFile) {
irFile.acceptChildrenVoid(this)
}
override fun visitElement(element: IrElement) {
element.acceptChildrenVoid(this)
}
private inline fun <T : IrElement> T.transformPostfix(fn: T.() -> Unit) {
acceptChildrenVoid(this@JvmArgumentNullabilityAssertionsLowering)
fn()
}
override fun visitMemberAccess(expression: IrMemberAccessExpression) {
expression.transformPostfix {
// Always drop nullability assertions on dispatch receivers, assuming that it will throw NPE.
//
// NB there are some members in Kotlin built-in classes which are NOT implemented as platform method calls,
// and thus break this assertion - e.g., 'Array<T>.iterator()' and similar functions.
// See KT-30908 for more details.
dispatchReceiver = dispatchReceiver?.replaceImplicitNotNullWithArgument()
if (isReceiverAssertionsDisabled || shouldDropNullabilityAssertionOnExtensionReceiver(expression)) {
extensionReceiver = extensionReceiver?.replaceImplicitNotNullWithArgument()
}
if (isCallAssertionsDisabled) {
for (i in 0 until expression.valueArgumentsCount) {
getValueArgument(i)?.let { irArgument ->
putValueArgument(i, irArgument.replaceImplicitNotNullWithArgument())
}
}
}
}
}
override fun visitContainerExpression(expression: IrContainerExpression) {
expression.transformPostfix {
if (isCallAssertionsDisabled) {
val lastIndex = statements.lastIndex
if (lastIndex >= 0) {
val lastStatement = statements[lastIndex]
if (lastStatement is IrExpression) {
statements[lastIndex] = lastStatement.replaceImplicitNotNullWithArgument()
}
}
}
}
}
override fun visitReturn(expression: IrReturn) {
expression.transformPostfix {
if (isCallAssertionsDisabled) {
value = value.replaceImplicitNotNullWithArgument()
}
}
}
override fun visitSetVariable(expression: IrSetVariable) {
expression.transformPostfix {
if (isCallAssertionsDisabled) {
value = value.replaceImplicitNotNullWithArgument()
}
}
}
override fun visitGetField(expression: IrGetField) {
expression.transformPostfix {
receiver = receiver?.replaceImplicitNotNullWithArgument()
}
}
override fun visitSetField(expression: IrSetField) {
expression.transformPostfix {
receiver = receiver?.replaceImplicitNotNullWithArgument()
if (isCallAssertionsDisabled) {
value = value.replaceImplicitNotNullWithArgument()
}
}
}
override fun visitVariable(declaration: IrVariable) {
declaration.transformPostfix {
if (isCallAssertionsDisabled) {
initializer = initializer?.replaceImplicitNotNullWithArgument()
}
}
}
private fun IrExpressionBody.replaceImplicitNotNullWithArgument() {
expression = expression.replaceImplicitNotNullWithArgument()
}
override fun visitField(declaration: IrField) {
declaration.transformPostfix {
if (isCallAssertionsDisabled) {
initializer?.replaceImplicitNotNullWithArgument()
}
}
}
override fun visitFunction(declaration: IrFunction) {
declaration.transformPostfix {
if (isCallAssertionsDisabled) {
for (valueParameter in valueParameters) {
valueParameter.defaultValue?.replaceImplicitNotNullWithArgument()
}
}
}
}
override fun visitWhen(expression: IrWhen) {
expression.transformPostfix {
if (isCallAssertionsDisabled) {
for (irBranch in branches) {
irBranch.condition = irBranch.condition.replaceImplicitNotNullWithArgument()
irBranch.result = irBranch.result.replaceImplicitNotNullWithArgument()
}
}
}
}
override fun visitLoop(loop: IrLoop) {
loop.transformPostfix {
if (isCallAssertionsDisabled) {
condition = condition.replaceImplicitNotNullWithArgument()
}
}
}
override fun visitThrow(expression: IrThrow) {
expression.transformPostfix {
if (isCallAssertionsDisabled) {
value = value.replaceImplicitNotNullWithArgument()
}
}
}
override fun visitTry(aTry: IrTry) {
aTry.transformPostfix {
if (isCallAssertionsDisabled) {
tryResult = tryResult.replaceImplicitNotNullWithArgument()
finallyExpression = finallyExpression?.replaceImplicitNotNullWithArgument()
}
}
}
override fun visitCatch(aCatch: IrCatch) {
aCatch.transformPostfix {
if (isCallAssertionsDisabled) {
result = result.replaceImplicitNotNullWithArgument()
}
}
}
override fun visitTypeOperator(expression: IrTypeOperatorCall) {
expression.transformPostfix {
if (isCallAssertionsDisabled) {
argument = argument.replaceImplicitNotNullWithArgument()
}
}
}
override fun visitVararg(expression: IrVararg) {
expression.transformPostfix {
if (isCallAssertionsDisabled) {
elements.forEachIndexed { index, irVarargElement ->
when (irVarargElement) {
is IrSpreadElement ->
irVarargElement.expression = irVarargElement.expression.replaceImplicitNotNullWithArgument()
is IrExpression ->
putElement(index, irVarargElement.replaceImplicitNotNullWithArgument())
}
}
}
}
}
private fun shouldDropNullabilityAssertionOnExtensionReceiver(expression: IrMemberAccessExpression): Boolean {
if (!isWithUnifiedNullChecks) {
if (expression.origin.isOperatorWithNoNullabilityAssertionsOnExtensionReceiver) return true
}
return false
}
companion object {
private val operatorsWithNoNullabilityAssertionsOnExtensionReceiver =
hashSetOf(
IrStatementOrigin.PREFIX_INCR, IrStatementOrigin.POSTFIX_INCR,
IrStatementOrigin.PREFIX_DECR, IrStatementOrigin.POSTFIX_DECR
)
internal val IrStatementOrigin?.isOperatorWithNoNullabilityAssertionsOnExtensionReceiver
get() =
this is IrStatementOrigin.COMPONENT_N ||
this in operatorsWithNoNullabilityAssertionsOnExtensionReceiver
internal fun IrExpression.replaceImplicitNotNullWithArgument(): IrExpression =
if (this is IrTypeOperatorCall && this.operator == IrTypeOperator.IMPLICIT_NOTNULL)
argument
else
this
}
}
@@ -1,5 +1,4 @@
// IGNORE_BACKEND_FIR: JVM_IR // IGNORE_BACKEND_FIR: JVM_IR
// IGNORE_BACKEND: JVM_IR
// TARGET_BACKEND: JVM // TARGET_BACKEND: JVM
// FULL_JDK // FULL_JDK
@@ -0,0 +1,21 @@
// IGNORE_BACKEND_FIR: JVM_IR
// TARGET_BACKEND: JVM
// WITH_RUNTIME
// FILE: test.kt
import kotlin.test.*
fun box(): String {
assertFailsWith<NullPointerException> { J.j().method() }
assertFailsWith<NullPointerException> { J.j().field }
assertFailsWith<NullPointerException> { J.j().field = 42 }
return "OK"
}
// FILE: J.java
public class J {
public Object field;
public void method() {}
public static J j() { return null; }
}
@@ -14376,6 +14376,11 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
runTest("compiler/testData/codegen/box/javaInterop/notNullAssertions/mapPut.kt"); runTest("compiler/testData/codegen/box/javaInterop/notNullAssertions/mapPut.kt");
} }
@TestMetadata("nullabilityAssertionOnDispatchReceiver.kt")
public void testNullabilityAssertionOnDispatchReceiver() throws Exception {
runTest("compiler/testData/codegen/box/javaInterop/notNullAssertions/nullabilityAssertionOnDispatchReceiver.kt");
}
@TestMetadata("nullabilityAssertionOnExtensionReceiver.kt") @TestMetadata("nullabilityAssertionOnExtensionReceiver.kt")
public void testNullabilityAssertionOnExtensionReceiver() throws Exception { public void testNullabilityAssertionOnExtensionReceiver() throws Exception {
runTest("compiler/testData/codegen/box/javaInterop/notNullAssertions/nullabilityAssertionOnExtensionReceiver.kt"); runTest("compiler/testData/codegen/box/javaInterop/notNullAssertions/nullabilityAssertionOnExtensionReceiver.kt");
@@ -14376,6 +14376,11 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
runTest("compiler/testData/codegen/box/javaInterop/notNullAssertions/mapPut.kt"); runTest("compiler/testData/codegen/box/javaInterop/notNullAssertions/mapPut.kt");
} }
@TestMetadata("nullabilityAssertionOnDispatchReceiver.kt")
public void testNullabilityAssertionOnDispatchReceiver() throws Exception {
runTest("compiler/testData/codegen/box/javaInterop/notNullAssertions/nullabilityAssertionOnDispatchReceiver.kt");
}
@TestMetadata("nullabilityAssertionOnExtensionReceiver.kt") @TestMetadata("nullabilityAssertionOnExtensionReceiver.kt")
public void testNullabilityAssertionOnExtensionReceiver() throws Exception { public void testNullabilityAssertionOnExtensionReceiver() throws Exception {
runTest("compiler/testData/codegen/box/javaInterop/notNullAssertions/nullabilityAssertionOnExtensionReceiver.kt"); runTest("compiler/testData/codegen/box/javaInterop/notNullAssertions/nullabilityAssertionOnExtensionReceiver.kt");
@@ -13226,6 +13226,11 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT
runTest("compiler/testData/codegen/box/javaInterop/notNullAssertions/mapPut.kt"); runTest("compiler/testData/codegen/box/javaInterop/notNullAssertions/mapPut.kt");
} }
@TestMetadata("nullabilityAssertionOnDispatchReceiver.kt")
public void testNullabilityAssertionOnDispatchReceiver() throws Exception {
runTest("compiler/testData/codegen/box/javaInterop/notNullAssertions/nullabilityAssertionOnDispatchReceiver.kt");
}
@TestMetadata("nullabilityAssertionOnExtensionReceiver.kt") @TestMetadata("nullabilityAssertionOnExtensionReceiver.kt")
public void testNullabilityAssertionOnExtensionReceiver() throws Exception { public void testNullabilityAssertionOnExtensionReceiver() throws Exception {
runTest("compiler/testData/codegen/box/javaInterop/notNullAssertions/nullabilityAssertionOnExtensionReceiver.kt"); runTest("compiler/testData/codegen/box/javaInterop/notNullAssertions/nullabilityAssertionOnExtensionReceiver.kt");
@@ -13226,6 +13226,11 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
runTest("compiler/testData/codegen/box/javaInterop/notNullAssertions/mapPut.kt"); runTest("compiler/testData/codegen/box/javaInterop/notNullAssertions/mapPut.kt");
} }
@TestMetadata("nullabilityAssertionOnDispatchReceiver.kt")
public void testNullabilityAssertionOnDispatchReceiver() throws Exception {
runTest("compiler/testData/codegen/box/javaInterop/notNullAssertions/nullabilityAssertionOnDispatchReceiver.kt");
}
@TestMetadata("nullabilityAssertionOnExtensionReceiver.kt") @TestMetadata("nullabilityAssertionOnExtensionReceiver.kt")
public void testNullabilityAssertionOnExtensionReceiver() throws Exception { public void testNullabilityAssertionOnExtensionReceiver() throws Exception {
runTest("compiler/testData/codegen/box/javaInterop/notNullAssertions/nullabilityAssertionOnExtensionReceiver.kt"); runTest("compiler/testData/codegen/box/javaInterop/notNullAssertions/nullabilityAssertionOnExtensionReceiver.kt");