From b736880787376361c64cd90bf298e97f3bca5209 Mon Sep 17 00:00:00 2001 From: Dmitry Petrov Date: Wed, 30 Dec 2015 16:02:48 +0300 Subject: [PATCH] KT-6646, KT-10482: when a method (or a property getter) returns Nothing, emit ACONST_NULL ATHROW after a call so that class files verifier knows that this is an exit point in a method. Note that if an inline method returning Nothing throws an exception explicitly (or via a chain of inline methods), this code will be deleted by DCE. --- .../kotlin/codegen/ExpressionCodegen.java | 6 +++ .../jetbrains/kotlin/codegen/StackValue.java | 8 ++++ .../returnsNothing/ifElse.kt | 14 +++++++ .../returnsNothing/inlineMethod.kt | 12 ++++++ .../returnsNothing/propertyGetter.kt | 16 ++++++++ .../returnsNothing/tryCatch.kt | 12 ++++++ .../controlStructures/returnsNothing/when.kt | 14 +++++++ .../inline/inlineReturnsNothing1.kt | 16 ++++++++ .../inline/inlineReturnsNothing2.kt | 16 ++++++++ .../inline/inlineReturnsNothing3.kt | 17 ++++++++ .../codegen/BytecodeTextTestGenerated.java | 18 +++++++++ .../BlackBoxCodegenTestGenerated.java | 39 +++++++++++++++++++ 12 files changed, 188 insertions(+) create mode 100644 compiler/testData/codegen/box/controlStructures/returnsNothing/ifElse.kt create mode 100644 compiler/testData/codegen/box/controlStructures/returnsNothing/inlineMethod.kt create mode 100644 compiler/testData/codegen/box/controlStructures/returnsNothing/propertyGetter.kt create mode 100644 compiler/testData/codegen/box/controlStructures/returnsNothing/tryCatch.kt create mode 100644 compiler/testData/codegen/box/controlStructures/returnsNothing/when.kt create mode 100644 compiler/testData/codegen/bytecodeText/inline/inlineReturnsNothing1.kt create mode 100644 compiler/testData/codegen/bytecodeText/inline/inlineReturnsNothing2.kt create mode 100644 compiler/testData/codegen/bytecodeText/inline/inlineReturnsNothing3.kt diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java index 1f843d8e818..532b0c63cdd 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java @@ -2420,6 +2420,12 @@ public class ExpressionCodegen extends KtVisitor impleme } callGenerator.genCall(callableMethod, resolvedCall, defaultMaskWasGenerated, this); + + KotlinType returnType = resolvedCall.getResultingDescriptor().getReturnType(); + if (returnType != null && KotlinBuiltIns.isNothing(returnType)) { + v.aconst(null); + v.athrow(); + } } @NotNull diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/StackValue.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/StackValue.java index c21cbd9c97e..d2d595cd484 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/StackValue.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/StackValue.java @@ -23,6 +23,7 @@ import kotlin.jvm.functions.Function1; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.kotlin.builtins.KotlinBuiltIns; import org.jetbrains.kotlin.builtins.PrimitiveType; import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicMethods; import org.jetbrains.kotlin.codegen.intrinsics.JavaClassProperty; @@ -45,6 +46,7 @@ import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodParameterKind; import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodParameterSignature; import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue; import org.jetbrains.kotlin.synthetic.SamAdapterExtensionFunctionDescriptor; +import org.jetbrains.kotlin.types.KotlinType; import org.jetbrains.org.objectweb.asm.Label; import org.jetbrains.org.objectweb.asm.Type; import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter; @@ -1086,6 +1088,12 @@ public abstract class StackValue { else { getter.genInvokeInstruction(v); coerce(getter.getReturnType(), type, v); + + KotlinType returnType = descriptor.getReturnType(); + if (returnType != null && KotlinBuiltIns.isNothing(returnType)) { + v.aconst(null); + v.athrow(); + } } } diff --git a/compiler/testData/codegen/box/controlStructures/returnsNothing/ifElse.kt b/compiler/testData/codegen/box/controlStructures/returnsNothing/ifElse.kt new file mode 100644 index 00000000000..98d5401d12e --- /dev/null +++ b/compiler/testData/codegen/box/controlStructures/returnsNothing/ifElse.kt @@ -0,0 +1,14 @@ +var flag = true + +fun exit(): Nothing = null!! + +fun box(): String { + val a: String + if (flag) { + a = "OK" + } + else { + exit() + } + return a +} \ No newline at end of file diff --git a/compiler/testData/codegen/box/controlStructures/returnsNothing/inlineMethod.kt b/compiler/testData/codegen/box/controlStructures/returnsNothing/inlineMethod.kt new file mode 100644 index 00000000000..5ec5f8feff2 --- /dev/null +++ b/compiler/testData/codegen/box/controlStructures/returnsNothing/inlineMethod.kt @@ -0,0 +1,12 @@ +inline fun exit(): Nothing = null!! + +fun box(): String { + val a: String + try { + a = "OK" + } + catch (e: Exception) { + exit() + } + return a +} \ No newline at end of file diff --git a/compiler/testData/codegen/box/controlStructures/returnsNothing/propertyGetter.kt b/compiler/testData/codegen/box/controlStructures/returnsNothing/propertyGetter.kt new file mode 100644 index 00000000000..1b69f5b1b66 --- /dev/null +++ b/compiler/testData/codegen/box/controlStructures/returnsNothing/propertyGetter.kt @@ -0,0 +1,16 @@ +var flag = true + +object Test { + val magic: Nothing get() = null!! +} + +fun box(): String { + val a: String + if (flag) { + a = "OK" + } + else { + Test.magic + } + return a +} \ No newline at end of file diff --git a/compiler/testData/codegen/box/controlStructures/returnsNothing/tryCatch.kt b/compiler/testData/codegen/box/controlStructures/returnsNothing/tryCatch.kt new file mode 100644 index 00000000000..bc23f5cb748 --- /dev/null +++ b/compiler/testData/codegen/box/controlStructures/returnsNothing/tryCatch.kt @@ -0,0 +1,12 @@ +fun exit(): Nothing = null!! + +fun box(): String { + val a: String + try { + a = "OK" + } + catch (e: Exception) { + exit() + } + return a +} \ No newline at end of file diff --git a/compiler/testData/codegen/box/controlStructures/returnsNothing/when.kt b/compiler/testData/codegen/box/controlStructures/returnsNothing/when.kt new file mode 100644 index 00000000000..e4953bb6e3c --- /dev/null +++ b/compiler/testData/codegen/box/controlStructures/returnsNothing/when.kt @@ -0,0 +1,14 @@ +fun exit(): Nothing = null!! + +var x = 0 + +fun box(): String { + val a: String + when (x) { + 0 -> a = "OK" + 1 -> a = "???" + 2 -> exit() + else -> exit() + } + return a +} \ No newline at end of file diff --git a/compiler/testData/codegen/bytecodeText/inline/inlineReturnsNothing1.kt b/compiler/testData/codegen/bytecodeText/inline/inlineReturnsNothing1.kt new file mode 100644 index 00000000000..74e36070427 --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/inline/inlineReturnsNothing1.kt @@ -0,0 +1,16 @@ +// NB '!!' uses Intrinsics.throwNpe() +inline fun exit(): Nothing = null!! + +fun box(): String { + val a: String + try { + a = "OK" + } + catch (e: Exception) { + exit() + // ATHROW + } + return a +} + +// 1 ATHROW \ No newline at end of file diff --git a/compiler/testData/codegen/bytecodeText/inline/inlineReturnsNothing2.kt b/compiler/testData/codegen/bytecodeText/inline/inlineReturnsNothing2.kt new file mode 100644 index 00000000000..f9ddb499bec --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/inline/inlineReturnsNothing2.kt @@ -0,0 +1,16 @@ +inline fun exit(): Nothing = + throw RuntimeException() // ATHROW + +fun box(): String { + val a: String + try { + a = "OK" + } + catch (e: Exception) { + exit() // ATHROW inlined + // no ATHROW (removed as dead code) + } + return a +} + +// 2 ATHROW \ No newline at end of file diff --git a/compiler/testData/codegen/bytecodeText/inline/inlineReturnsNothing3.kt b/compiler/testData/codegen/bytecodeText/inline/inlineReturnsNothing3.kt new file mode 100644 index 00000000000..f39c50f633f --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/inline/inlineReturnsNothing3.kt @@ -0,0 +1,17 @@ +inline fun exit(): Nothing = null!! +inline fun exita(): Nothing = exit() // ATHROW +inline fun exitb(): Nothing = exita() // ATHROW +inline fun exitc(): Nothing = exitb() // ATHROW + +fun box(): String { + val a: String + try { + a = "OK" + } + catch (e: Exception) { + exitc() // ATHROW + } + return a +} + +// 4 ATHROW \ No newline at end of file diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java index a1db9dc827b..de9f79b078d 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java @@ -706,6 +706,24 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest { KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/bytecodeText/inline"), Pattern.compile("^(.+)\\.kt$"), true); } + @TestMetadata("inlineReturnsNothing1.kt") + public void testInlineReturnsNothing1() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/inline/inlineReturnsNothing1.kt"); + doTest(fileName); + } + + @TestMetadata("inlineReturnsNothing2.kt") + public void testInlineReturnsNothing2() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/inline/inlineReturnsNothing2.kt"); + doTest(fileName); + } + + @TestMetadata("inlineReturnsNothing3.kt") + public void testInlineReturnsNothing3() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/inline/inlineReturnsNothing3.kt"); + doTest(fileName); + } + @TestMetadata("noSynAccessor.kt") public void testNoSynAccessor() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/inline/noSynAccessor.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/generated/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/generated/BlackBoxCodegenTestGenerated.java index df3e4db58fd..7e7a56d0e83 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/generated/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/generated/BlackBoxCodegenTestGenerated.java @@ -2422,6 +2422,45 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { } } + @TestMetadata("compiler/testData/codegen/box/controlStructures/returnsNothing") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class ReturnsNothing extends AbstractBlackBoxCodegenTest { + public void testAllFilesPresentInReturnsNothing() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/controlStructures/returnsNothing"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("ifElse.kt") + public void testIfElse() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/returnsNothing/ifElse.kt"); + doTest(fileName); + } + + @TestMetadata("inlineMethod.kt") + public void testInlineMethod() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/returnsNothing/inlineMethod.kt"); + doTest(fileName); + } + + @TestMetadata("propertyGetter.kt") + public void testPropertyGetter() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/returnsNothing/propertyGetter.kt"); + doTest(fileName); + } + + @TestMetadata("tryCatch.kt") + public void testTryCatch() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/returnsNothing/tryCatch.kt"); + doTest(fileName); + } + + @TestMetadata("when.kt") + public void testWhen() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/controlStructures/returnsNothing/when.kt"); + doTest(fileName); + } + } + @TestMetadata("compiler/testData/codegen/box/controlStructures/tryCatchInExpressions") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)