From fc7783c7dd301436e7d7f683be7390aa312bb8ef Mon Sep 17 00:00:00 2001 From: Dmitry Petrov Date: Mon, 25 Nov 2019 17:34:20 +0300 Subject: [PATCH] JVM_IR: JvmArgumentNullabilityAssertionsLowering --- .../jetbrains/kotlin/backend/jvm/JvmLower.kt | 2 + ...vmArgumentNullabilityAssertionsLowering.kt | 243 ++++++++++++++++++ .../platformTypeAssertionStackTrace.kt | 1 - .../nullabilityAssertionOnDispatchReceiver.kt | 21 ++ .../codegen/BlackBoxCodegenTestGenerated.java | 5 + .../LightAnalysisModeTestGenerated.java | 5 + .../ir/FirBlackBoxCodegenTestGenerated.java | 5 + .../ir/IrBlackBoxCodegenTestGenerated.java | 5 + 8 files changed, 286 insertions(+), 1 deletion(-) create mode 100644 compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/JvmArgumentNullabilityAssertionsLowering.kt create mode 100644 compiler/testData/codegen/box/javaInterop/notNullAssertions/nullabilityAssertionOnDispatchReceiver.kt diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmLower.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmLower.kt index 5effaf81677..53620210698 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmLower.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmLower.kt @@ -299,6 +299,8 @@ private val jvmFilePhases = staticDefaultFunctionPhase then syntheticAccessorPhase then + + jvmArgumentNullabilityAssertions then toArrayPhase then jvmBuiltinOptimizationLoweringPhase then additionalClassAnnotationPhase then diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/JvmArgumentNullabilityAssertionsLowering.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/JvmArgumentNullabilityAssertionsLowering.kt new file mode 100644 index 00000000000..97fb0e34355 --- /dev/null +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/JvmArgumentNullabilityAssertionsLowering.kt @@ -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.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.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 + + } +} \ No newline at end of file diff --git a/compiler/testData/codegen/box/fullJdk/platformTypeAssertionStackTrace.kt b/compiler/testData/codegen/box/fullJdk/platformTypeAssertionStackTrace.kt index b5b5c9dba13..8a704f402db 100644 --- a/compiler/testData/codegen/box/fullJdk/platformTypeAssertionStackTrace.kt +++ b/compiler/testData/codegen/box/fullJdk/platformTypeAssertionStackTrace.kt @@ -1,5 +1,4 @@ // IGNORE_BACKEND_FIR: JVM_IR -// IGNORE_BACKEND: JVM_IR // TARGET_BACKEND: JVM // FULL_JDK diff --git a/compiler/testData/codegen/box/javaInterop/notNullAssertions/nullabilityAssertionOnDispatchReceiver.kt b/compiler/testData/codegen/box/javaInterop/notNullAssertions/nullabilityAssertionOnDispatchReceiver.kt new file mode 100644 index 00000000000..13f4e7d2ca4 --- /dev/null +++ b/compiler/testData/codegen/box/javaInterop/notNullAssertions/nullabilityAssertionOnDispatchReceiver.kt @@ -0,0 +1,21 @@ +// IGNORE_BACKEND_FIR: JVM_IR +// TARGET_BACKEND: JVM +// WITH_RUNTIME +// FILE: test.kt +import kotlin.test.* + +fun box(): String { + assertFailsWith { J.j().method() } + assertFailsWith { J.j().field } + assertFailsWith { J.j().field = 42 } + return "OK" +} + +// FILE: J.java +public class J { + public Object field; + + public void method() {} + + public static J j() { return null; } +} diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index f1d7a5ae50d..a40fcc926f5 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -14376,6 +14376,11 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { 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") public void testNullabilityAssertionOnExtensionReceiver() throws Exception { runTest("compiler/testData/codegen/box/javaInterop/notNullAssertions/nullabilityAssertionOnExtensionReceiver.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java index 1e6f7eb15ad..8b6e950d49c 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java @@ -14376,6 +14376,11 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes 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") public void testNullabilityAssertionOnExtensionReceiver() throws Exception { runTest("compiler/testData/codegen/box/javaInterop/notNullAssertions/nullabilityAssertionOnExtensionReceiver.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/ir/FirBlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/ir/FirBlackBoxCodegenTestGenerated.java index 30bc5ac2d9d..08281bc0218 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/ir/FirBlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/ir/FirBlackBoxCodegenTestGenerated.java @@ -13226,6 +13226,11 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT 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") public void testNullabilityAssertionOnExtensionReceiver() throws Exception { runTest("compiler/testData/codegen/box/javaInterop/notNullAssertions/nullabilityAssertionOnExtensionReceiver.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java index 5a54c2d607e..992b7b10609 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java @@ -13226,6 +13226,11 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes 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") public void testNullabilityAssertionOnExtensionReceiver() throws Exception { runTest("compiler/testData/codegen/box/javaInterop/notNullAssertions/nullabilityAssertionOnExtensionReceiver.kt");