From 2c0650331140b2c5b4176bbc00c43d5b16c0f388 Mon Sep 17 00:00:00 2001 From: pyos Date: Fri, 7 Feb 2020 11:17:32 +0100 Subject: [PATCH] JVM_IR: partially fix inline methods using captured crossinline lambdas The fields containing crossinline lambdas should be package-private to avoid generating synthetic accessors, which break object regeneration. Note that the inline methods cannot actually be called, as call sites will attempt to read the captured lambda from a field through a *copy* of the local containing the object, so these reads will not be inlined, causing an exception at runtime: inline fun f(crossinline g: () -> Unit) = object : I { inline fun h() = g() // effectively `val tmp = this; return tmp.$g()`: override fun run() = h() } f {}.run() // NoSuchFieldError: $g This particular example can be fixed by reusing locals for receiver parameters in IrInlineCodegen, but explicitly assigning `this` to another variable and calling an inline method on it will break it again. (This is only applicable to the JVM_IR backend, as the non-IR one fails to generate `f` at all for some other reason.) --- .../common/lower/LocalDeclarationsLowering.kt | 5 ++++- .../jetbrains/kotlin/backend/jvm/JvmLower.kt | 9 ++++++++- .../javaInterop/objectWithSeveralSuspends.kt | 1 - .../tailCallOptimizations/crossinline_ir.txt | 4 ++-- .../innerObjectRetransformation_ir.txt | 4 ++-- .../unit/override5_ir.txt | 2 +- .../anonymousObject/withInlineMethod.kt | 20 +++++++++++++++++++ .../coroutines/tcoContinuation_ir.txt | 4 ++-- .../BlackBoxInlineCodegenTestGenerated.java | 5 +++++ ...otlinAgainstInlineKotlinTestGenerated.java | 5 +++++ .../IrBlackBoxInlineCodegenTestGenerated.java | 5 +++++ ...otlinAgainstInlineKotlinTestGenerated.java | 5 +++++ .../IrJsCodegenInlineTestGenerated.java | 5 +++++ .../JsCodegenInlineTestGenerated.java | 5 +++++ 14 files changed, 69 insertions(+), 10 deletions(-) create mode 100644 compiler/testData/codegen/boxInline/anonymousObject/withInlineMethod.kt diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/LocalDeclarationsLowering.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/LocalDeclarationsLowering.kt index a50bb3a7341..f8680fb8133 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/LocalDeclarationsLowering.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/LocalDeclarationsLowering.kt @@ -66,6 +66,9 @@ interface VisibilityPolicy { fun forConstructor(declaration: IrConstructor, inInlineFunctionScope: Boolean): Visibility = Visibilities.PRIVATE + fun forCapturedField(value: IrValueSymbol): Visibility = + Visibilities.PRIVATE + companion object { val DEFAULT = object : VisibilityPolicy {} } @@ -778,7 +781,7 @@ class LocalDeclarationsLowering( classDeclaration.startOffset, classDeclaration.endOffset, suggestNameForCapturedValue(owner, generatedNames), - Visibilities.PRIVATE, + visibilityPolicy.forCapturedField(capturedValue), classDeclaration, owner.type, owner is IrValueParameter && owner.isCrossinline 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 0993d169e56..6e800bcfc4d 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 @@ -11,12 +11,13 @@ import org.jetbrains.kotlin.backend.common.lower.* import org.jetbrains.kotlin.backend.common.lower.loops.forLoopsPhase import org.jetbrains.kotlin.backend.common.lower.optimizations.foldConstantLoweringPhase import org.jetbrains.kotlin.backend.common.phaser.* -import org.jetbrains.kotlin.backend.jvm.ir.getJvmVisibilityOfDefaultArgumentStub import org.jetbrains.kotlin.backend.jvm.lower.* import org.jetbrains.kotlin.descriptors.Visibilities import org.jetbrains.kotlin.descriptors.Visibility import org.jetbrains.kotlin.ir.IrElement import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.symbols.IrValueParameterSymbol +import org.jetbrains.kotlin.ir.symbols.IrValueSymbol import org.jetbrains.kotlin.ir.util.PatchDeclarationParentsVisitor import org.jetbrains.kotlin.ir.util.isAnonymousObject import org.jetbrains.kotlin.ir.util.parentAsClass @@ -129,6 +130,12 @@ internal val localDeclarationsPhase = makeIrFilePhase( else declaration.visibility + override fun forCapturedField(value: IrValueSymbol): Visibility = + if (value is IrValueParameterSymbol && value.owner.isCrossinline) + JavaVisibilities.PACKAGE_VISIBILITY // avoid requiring a synthetic accessor for it + else + Visibilities.PRIVATE + private fun scopedVisibility(inInlineFunctionScope: Boolean): Visibility = if (inInlineFunctionScope) Visibilities.PUBLIC else JavaVisibilities.PACKAGE_VISIBILITY } diff --git a/compiler/testData/codegen/box/coroutines/javaInterop/objectWithSeveralSuspends.kt b/compiler/testData/codegen/box/coroutines/javaInterop/objectWithSeveralSuspends.kt index dfdcb1eda0f..c1f276024bd 100644 --- a/compiler/testData/codegen/box/coroutines/javaInterop/objectWithSeveralSuspends.kt +++ b/compiler/testData/codegen/box/coroutines/javaInterop/objectWithSeveralSuspends.kt @@ -1,5 +1,4 @@ // IGNORE_BACKEND_FIR: JVM_IR -// IGNORE_BACKEND: JVM_IR // TARGET_BACKEND: JVM // COMMON_COROUTINES_TEST // WITH_RUNTIME diff --git a/compiler/testData/codegen/box/coroutines/tailCallOptimizations/crossinline_ir.txt b/compiler/testData/codegen/box/coroutines/tailCallOptimizations/crossinline_ir.txt index 794818cfd2e..d04fb6d49ed 100644 --- a/compiler/testData/codegen/box/coroutines/tailCallOptimizations/crossinline_ir.txt +++ b/compiler/testData/codegen/box/coroutines/tailCallOptimizations/crossinline_ir.txt @@ -89,7 +89,7 @@ public final class CrossinlineKt$consumeEach$2$send$1 { @kotlin.Metadata public final class CrossinlineKt$consumeEach$2 { - private synthetic final field $action: kotlin.jvm.functions.Function2 + synthetic final @org.jetbrains.annotations.NotNull field $action: kotlin.jvm.functions.Function2 inner class CrossinlineKt$consumeEach$2 inner class CrossinlineKt$consumeEach$2$send$1 public method (p0: kotlin.jvm.functions.Function2): void @@ -355,7 +355,7 @@ public final class CrossinlineKt$source$1$consume$1 { @kotlin.Metadata public final class CrossinlineKt$source$1 { - private synthetic final field $action: kotlin.jvm.functions.Function2 + synthetic final @org.jetbrains.annotations.NotNull field $action: kotlin.jvm.functions.Function2 inner class CrossinlineKt$source$1 inner class CrossinlineKt$source$1$consume$1 public method (p0: kotlin.jvm.functions.Function2): void diff --git a/compiler/testData/codegen/box/coroutines/tailCallOptimizations/innerObjectRetransformation_ir.txt b/compiler/testData/codegen/box/coroutines/tailCallOptimizations/innerObjectRetransformation_ir.txt index c57e13b50f3..3fb78b70c43 100644 --- a/compiler/testData/codegen/box/coroutines/tailCallOptimizations/innerObjectRetransformation_ir.txt +++ b/compiler/testData/codegen/box/coroutines/tailCallOptimizations/innerObjectRetransformation_ir.txt @@ -65,7 +65,7 @@ public final class flow/InnerObjectRetransformationKt$collect$2$emit$1 { @kotlin.Metadata public final class flow/InnerObjectRetransformationKt$collect$2 { - private synthetic final field $action: kotlin.jvm.functions.Function2 + synthetic final @org.jetbrains.annotations.NotNull field $action: kotlin.jvm.functions.Function2 inner class flow/InnerObjectRetransformationKt$collect$2 inner class flow/InnerObjectRetransformationKt$collect$2$emit$1 public method (p0: kotlin.jvm.functions.Function2): void @@ -86,7 +86,7 @@ public final class flow/InnerObjectRetransformationKt$flow$1$collect$1 { @kotlin.Metadata public final class flow/InnerObjectRetransformationKt$flow$1 { - private synthetic final field $block: kotlin.jvm.functions.Function2 + synthetic final @org.jetbrains.annotations.NotNull field $block: kotlin.jvm.functions.Function2 inner class flow/InnerObjectRetransformationKt$flow$1 inner class flow/InnerObjectRetransformationKt$flow$1$collect$1 public method (p0: kotlin.jvm.functions.Function2): void diff --git a/compiler/testData/codegen/box/coroutines/tailCallOptimizations/unit/override5_ir.txt b/compiler/testData/codegen/box/coroutines/tailCallOptimizations/unit/override5_ir.txt index a650083850b..26c9679fd1e 100644 --- a/compiler/testData/codegen/box/coroutines/tailCallOptimizations/unit/override5_ir.txt +++ b/compiler/testData/codegen/box/coroutines/tailCallOptimizations/unit/override5_ir.txt @@ -48,7 +48,7 @@ public final class Override5Kt$inlineMe$1$generic$2 { @kotlin.Metadata public final class Override5Kt$inlineMe$1 { - private synthetic final field $c: kotlin.jvm.functions.Function1 + synthetic final @org.jetbrains.annotations.NotNull field $c: kotlin.jvm.functions.Function1 inner class Override5Kt$inlineMe$1 inner class Override5Kt$inlineMe$1$generic$1 inner class Override5Kt$inlineMe$1$generic$2 diff --git a/compiler/testData/codegen/boxInline/anonymousObject/withInlineMethod.kt b/compiler/testData/codegen/boxInline/anonymousObject/withInlineMethod.kt new file mode 100644 index 00000000000..84c5d9d696e --- /dev/null +++ b/compiler/testData/codegen/boxInline/anonymousObject/withInlineMethod.kt @@ -0,0 +1,20 @@ +// FILE: 1.kt +package test + +interface I { + fun f(): String +} + +inline fun test(crossinline h: () -> String) = object : I { + // TODO: actually call g() in f() -- currently, the inliner fails to detect + // an inlined read of h's field because it uses a copy of `this` + // as a receiver + override fun f(): String = h() + inline fun g(): String = h() +} + +// FILE: 2.kt + +import test.* + +fun box(): String = test { "OK" }.f() diff --git a/compiler/testData/codegen/bytecodeListing/coroutines/tcoContinuation_ir.txt b/compiler/testData/codegen/bytecodeListing/coroutines/tcoContinuation_ir.txt index 797cf19ed43..2bfcbabf4b6 100644 --- a/compiler/testData/codegen/bytecodeListing/coroutines/tcoContinuation_ir.txt +++ b/compiler/testData/codegen/bytecodeListing/coroutines/tcoContinuation_ir.txt @@ -21,7 +21,7 @@ public final class TcoContinuationKt$collect$2$emit$1 { @kotlin.Metadata public final class TcoContinuationKt$collect$2 { - private synthetic final field $action: kotlin.jvm.functions.Function2 + synthetic final @org.jetbrains.annotations.NotNull field $action: kotlin.jvm.functions.Function2 inner class TcoContinuationKt$collect$2 inner class TcoContinuationKt$collect$2$emit$1 public method (p0: kotlin.jvm.functions.Function2): void @@ -42,7 +42,7 @@ public final class TcoContinuationKt$flow$1$collect$1 { @kotlin.Metadata public final class TcoContinuationKt$flow$1 { - private synthetic final field $block: kotlin.jvm.functions.Function2 + synthetic final @org.jetbrains.annotations.NotNull field $block: kotlin.jvm.functions.Function2 inner class TcoContinuationKt$flow$1 inner class TcoContinuationKt$flow$1$collect$1 public method (p0: kotlin.jvm.functions.Function2): void diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxInlineCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxInlineCodegenTestGenerated.java index 2f95f6b5b62..2959bdbcd78 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxInlineCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxInlineCodegenTestGenerated.java @@ -276,6 +276,11 @@ public class BlackBoxInlineCodegenTestGenerated extends AbstractBlackBoxInlineCo runTest("compiler/testData/codegen/boxInline/anonymousObject/superConstructorWithObjectParameter.kt"); } + @TestMetadata("withInlineMethod.kt") + public void testWithInlineMethod() throws Exception { + runTest("compiler/testData/codegen/boxInline/anonymousObject/withInlineMethod.kt"); + } + @TestMetadata("compiler/testData/codegen/boxInline/anonymousObject/enumWhen") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/CompileKotlinAgainstInlineKotlinTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/CompileKotlinAgainstInlineKotlinTestGenerated.java index c0d569ebe99..fa087fd0a90 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/CompileKotlinAgainstInlineKotlinTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/CompileKotlinAgainstInlineKotlinTestGenerated.java @@ -276,6 +276,11 @@ public class CompileKotlinAgainstInlineKotlinTestGenerated extends AbstractCompi runTest("compiler/testData/codegen/boxInline/anonymousObject/superConstructorWithObjectParameter.kt"); } + @TestMetadata("withInlineMethod.kt") + public void testWithInlineMethod() throws Exception { + runTest("compiler/testData/codegen/boxInline/anonymousObject/withInlineMethod.kt"); + } + @TestMetadata("compiler/testData/codegen/boxInline/anonymousObject/enumWhen") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxInlineCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxInlineCodegenTestGenerated.java index 466c4bfeacf..43af79ec9b0 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxInlineCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxInlineCodegenTestGenerated.java @@ -276,6 +276,11 @@ public class IrBlackBoxInlineCodegenTestGenerated extends AbstractIrBlackBoxInli runTest("compiler/testData/codegen/boxInline/anonymousObject/superConstructorWithObjectParameter.kt"); } + @TestMetadata("withInlineMethod.kt") + public void testWithInlineMethod() throws Exception { + runTest("compiler/testData/codegen/boxInline/anonymousObject/withInlineMethod.kt"); + } + @TestMetadata("compiler/testData/codegen/boxInline/anonymousObject/enumWhen") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrCompileKotlinAgainstInlineKotlinTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrCompileKotlinAgainstInlineKotlinTestGenerated.java index ab97018e14e..a585f2aadbf 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrCompileKotlinAgainstInlineKotlinTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrCompileKotlinAgainstInlineKotlinTestGenerated.java @@ -276,6 +276,11 @@ public class IrCompileKotlinAgainstInlineKotlinTestGenerated extends AbstractIrC runTest("compiler/testData/codegen/boxInline/anonymousObject/superConstructorWithObjectParameter.kt"); } + @TestMetadata("withInlineMethod.kt") + public void testWithInlineMethod() throws Exception { + runTest("compiler/testData/codegen/boxInline/anonymousObject/withInlineMethod.kt"); + } + @TestMetadata("compiler/testData/codegen/boxInline/anonymousObject/enumWhen") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/IrJsCodegenInlineTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/IrJsCodegenInlineTestGenerated.java index 91c51c3847d..7df55978643 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/IrJsCodegenInlineTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/IrJsCodegenInlineTestGenerated.java @@ -251,6 +251,11 @@ public class IrJsCodegenInlineTestGenerated extends AbstractIrJsCodegenInlineTes runTest("compiler/testData/codegen/boxInline/anonymousObject/superConstructorWithObjectParameter.kt"); } + @TestMetadata("withInlineMethod.kt") + public void testWithInlineMethod() throws Exception { + runTest("compiler/testData/codegen/boxInline/anonymousObject/withInlineMethod.kt"); + } + @TestMetadata("compiler/testData/codegen/boxInline/anonymousObject/enumWhen") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenInlineTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenInlineTestGenerated.java index 306d4df4eb7..37e2235abad 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenInlineTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenInlineTestGenerated.java @@ -251,6 +251,11 @@ public class JsCodegenInlineTestGenerated extends AbstractJsCodegenInlineTest { runTest("compiler/testData/codegen/boxInline/anonymousObject/superConstructorWithObjectParameter.kt"); } + @TestMetadata("withInlineMethod.kt") + public void testWithInlineMethod() throws Exception { + runTest("compiler/testData/codegen/boxInline/anonymousObject/withInlineMethod.kt"); + } + @TestMetadata("compiler/testData/codegen/boxInline/anonymousObject/enumWhen") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)