From cd9f59325e59256d2c09b8adad0b651008fdb58c Mon Sep 17 00:00:00 2001 From: Roman Artemev Date: Tue, 14 Jul 2020 16:41:27 +0300 Subject: [PATCH] [KLIB] Fix deserialization of anonymous classes In case of initializing property or function with anonymous object the object is being exposed outside its field/function's scope and accessible on previous level. In this case in `declarations only` mode we have unbound symbols. Fix is to force body/initializer loading in such cases. Make sure it is deserialized in `declarations'only` mode too. - Fix KT-40216 - Add test --- .../ir/FirBlackBoxCodegenTestGenerated.java | 5 ++ .../serialization/IrFileDeserializer.kt | 74 +++++++++++++++---- .../codegen/box/ir/anonymousClassLeak.kt | 72 ++++++++++++++++++ .../codegen/BlackBoxCodegenTestGenerated.java | 5 ++ .../LightAnalysisModeTestGenerated.java | 5 ++ .../ir/IrBlackBoxCodegenTestGenerated.java | 5 ++ .../IrJsCodegenBoxES6TestGenerated.java | 5 ++ .../IrJsCodegenBoxTestGenerated.java | 5 ++ .../semantics/JsCodegenBoxTestGenerated.java | 5 ++ 9 files changed, 166 insertions(+), 15 deletions(-) create mode 100644 compiler/testData/codegen/box/ir/anonymousClassLeak.kt diff --git a/compiler/fir/fir2ir/tests/org/jetbrains/kotlin/codegen/ir/FirBlackBoxCodegenTestGenerated.java b/compiler/fir/fir2ir/tests/org/jetbrains/kotlin/codegen/ir/FirBlackBoxCodegenTestGenerated.java index 62dcce0493a..b2ff755596c 100644 --- a/compiler/fir/fir2ir/tests/org/jetbrains/kotlin/codegen/ir/FirBlackBoxCodegenTestGenerated.java +++ b/compiler/fir/fir2ir/tests/org/jetbrains/kotlin/codegen/ir/FirBlackBoxCodegenTestGenerated.java @@ -14595,6 +14595,11 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/ir"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true); } + @TestMetadata("anonymousClassLeak.kt") + public void testAnonymousClassLeak() throws Exception { + runTest("compiler/testData/codegen/box/ir/anonymousClassLeak.kt"); + } + @TestMetadata("anonymousObjectInForLoopIteratorAndBody.kt") public void testAnonymousObjectInForLoopIteratorAndBody() throws Exception { runTest("compiler/testData/codegen/box/ir/anonymousObjectInForLoopIteratorAndBody.kt"); diff --git a/compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/serialization/IrFileDeserializer.kt b/compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/serialization/IrFileDeserializer.kt index 829765b45de..082fedb0f02 100644 --- a/compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/serialization/IrFileDeserializer.kt +++ b/compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/serialization/IrFileDeserializer.kt @@ -1100,13 +1100,53 @@ abstract class IrFileDeserializer( return result } - private inline fun T.withInlineGuard(block: T.() -> Unit) { - val oldInline = deserializeBodies + + /** + * In `declarations-only` mode in case of private property/function with inferred anonymous private type like this + * class C { + * private val p = object { + * fun foo() = 42 + * } + * + * private fun f() = object { + * fun bar() = "42" + * } + * + * private val pp = p.foo() + * private fun ff() = f().bar() + * } + * object's classifier is leaked outside p/f scopes and accessible on C's level so + * if their initializer/body weren't read we have unbound `foo/bar` symbol and unbound `object` symbols. + * To fix this make sure that such declaration forced to be deserialized completely. + * + * For more information see `anonymousClassLeak.kt` test and issue KT-40216 + */ + private fun IrType.checkObjectLeak(isPrivate: Boolean): Boolean { + return isPrivate && this is IrSimpleType && classifier.let { !it.isPublicApi && it !is IrTypeParameterSymbol } + } + + private fun T.withBodyGuard(block: T.() -> Unit) { + val oldBodiesPolicy = deserializeBodies + + fun checkInlineBody(): Boolean = deserializeInlineFunctions && this is IrSimpleFunction && isInline + try { - deserializeBodies = oldInline || (deserializeInlineFunctions && this is IrSimpleFunction && isInline) + deserializeBodies = oldBodiesPolicy || checkInlineBody() || returnType.checkObjectLeak(!symbol.isPublicApi) block() } finally { - deserializeBodies = oldInline + deserializeBodies = oldBodiesPolicy + } + } + + + private fun IrField.withInitializerGuard(isPrivateProperty: Boolean, f: IrField.() -> Unit) { + val oldBodiesPolicy = deserializeBodies + + try { + deserializeBodies = oldBodiesPolicy || type.checkObjectLeak(isPrivateProperty) + f() + } finally { + deserializeBodies = oldBodiesPolicy } } @@ -1116,13 +1156,12 @@ abstract class IrFileDeserializer( ): T = withDeserializedIrDeclarationBase(proto.base) { symbol, idSig, startOffset, endOffset, origin, fcode -> symbolTable.withScope(symbol.descriptor) { block(symbol as IrFunctionSymbol, idSig, startOffset, endOffset, origin, fcode).usingParent { - withInlineGuard { - typeParameters = deserializeTypeParameters(proto.typeParameterList, false) + typeParameters = deserializeTypeParameters(proto.typeParameterList, false) + val nameType = BinaryNameAndType.decode(proto.nameType) + returnType = deserializeIrType(nameType.typeIndex) + + withBodyGuard { valueParameters = deserializeValueParameters(proto.valueParameterList) - - val nameType = BinaryNameAndType.decode(proto.nameType) - returnType = deserializeIrType(nameType.typeIndex) - if (proto.hasDispatchReceiver()) dispatchReceiverParameter = deserializeIrValueParameter(proto.dispatchReceiver, -1) if (proto.hasExtensionReceiver()) @@ -1228,7 +1267,9 @@ abstract class IrFileDeserializer( } } - private fun deserializeIrField(proto: ProtoField): IrField = + + + private fun deserializeIrField(proto: ProtoField, isPrivateProperty: Boolean): IrField = withDeserializedIrDeclarationBase(proto.base) { symbol, uniqId, startOffset, endOffset, origin, fcode -> val nameType = BinaryNameAndType.decode(proto.nameType) val type = deserializeIrType(nameType.typeIndex) @@ -1245,8 +1286,11 @@ abstract class IrFileDeserializer( flags.isStatic, ) }.usingParent { - if (proto.hasInitializer()) - initializer = IrExpressionBodyImpl(deserializeExpressionBody(proto.initializer)) + if (proto.hasInitializer()) { + withInitializerGuard(isPrivateProperty) { + initializer = IrExpressionBodyImpl(deserializeExpressionBody(proto.initializer)) + } + } (descriptor as? WrappedFieldDescriptor)?.bind(this) } @@ -1302,7 +1346,7 @@ abstract class IrFileDeserializer( } } if (proto.hasBackingField()) { - backingField = deserializeIrField(proto.backingField).also { + backingField = deserializeIrField(proto.backingField, !symbol.isPublicApi).also { // A property symbol and its field symbol share the same descriptor. // Unfortunately symbol deserialization doesn't know anything about that. // So we can end up with two wrapped property descriptors for property and its field. @@ -1350,7 +1394,7 @@ abstract class IrFileDeserializer( val declaration: IrDeclaration = when (proto.declaratorCase!!) { IR_ANONYMOUS_INIT -> deserializeIrAnonymousInit(proto.irAnonymousInit) IR_CONSTRUCTOR -> deserializeIrConstructor(proto.irConstructor) - IR_FIELD -> deserializeIrField(proto.irField) + IR_FIELD -> deserializeIrField(proto.irField, false) IR_CLASS -> deserializeIrClass(proto.irClass) IR_FUNCTION -> deserializeIrFunction(proto.irFunction) IR_PROPERTY -> deserializeIrProperty(proto.irProperty) diff --git a/compiler/testData/codegen/box/ir/anonymousClassLeak.kt b/compiler/testData/codegen/box/ir/anonymousClassLeak.kt new file mode 100644 index 00000000000..48910947ecb --- /dev/null +++ b/compiler/testData/codegen/box/ir/anonymousClassLeak.kt @@ -0,0 +1,72 @@ + +// MODULE: lib +// FILE: lib.kt + +// KT-40216 + +inline fun T.also(f: (T) -> Unit): T { + f(this) + return this +} + +object FieldTest { + + var result = "" + + private val test = object { + fun bar() = object { + fun qux() = object { + fun biq() = object { + fun caz() = object { + }.also { result += "d" } + }.also { result += "c" } + }.also { result += "b" } + }.also { result += "a" } + }.also { result += "!" } + + private val ttt = test.bar() + + private val qqq = ttt.qux() + + val bbb = qqq.biq().also { it.caz() } +} + +object FunTest { + + var result = "" + + private fun bar() = object { + fun qux() = object { + fun biq() = object { + fun caz() = object { + }.also { result += "w" } + }.also { result += "x" } + }.also { result += "y" } + }.also { result += "z" } + + private fun ttt() = bar() + + private fun qqq() = ttt().qux() + + fun bbb() = qqq().biq().also { it.caz() } +} + +// MODULE: lib2(lib) +// FILE: lib2.kt +fun test1(): String { + return FieldTest.result +} + +fun test2(): String { + FunTest.bbb() + return FunTest.result +} + +// MODULE: main(lib2) +// FILE: main.kt + +fun box(): String { + if (test1() != "!abcd") return "FAIL 1: ${test1()}" + if (test2() != "zyxw") return "FAIL 2: ${test2()}" + return "OK" +} \ No newline at end of file diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index 340a6f62da0..1b9f41d7b1a 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -15820,6 +15820,11 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/ir"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM, true); } + @TestMetadata("anonymousClassLeak.kt") + public void testAnonymousClassLeak() throws Exception { + runTest("compiler/testData/codegen/box/ir/anonymousClassLeak.kt"); + } + @TestMetadata("anonymousObjectInForLoopIteratorAndBody.kt") public void testAnonymousObjectInForLoopIteratorAndBody() throws Exception { runTest("compiler/testData/codegen/box/ir/anonymousObjectInForLoopIteratorAndBody.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java index 249a6f433bf..30f5f2ca8f3 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java @@ -15820,6 +15820,11 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/ir"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM, true); } + @TestMetadata("anonymousClassLeak.kt") + public void testAnonymousClassLeak() throws Exception { + runTest("compiler/testData/codegen/box/ir/anonymousClassLeak.kt"); + } + @TestMetadata("anonymousObjectInForLoopIteratorAndBody.kt") public void testAnonymousObjectInForLoopIteratorAndBody() throws Exception { runTest("compiler/testData/codegen/box/ir/anonymousObjectInForLoopIteratorAndBody.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java index 2e7bdfd5a01..063c3f190d4 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java @@ -14595,6 +14595,11 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/ir"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true); } + @TestMetadata("anonymousClassLeak.kt") + public void testAnonymousClassLeak() throws Exception { + runTest("compiler/testData/codegen/box/ir/anonymousClassLeak.kt"); + } + @TestMetadata("anonymousObjectInForLoopIteratorAndBody.kt") public void testAnonymousObjectInForLoopIteratorAndBody() throws Exception { runTest("compiler/testData/codegen/box/ir/anonymousObjectInForLoopIteratorAndBody.kt"); diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/es6/semantics/IrJsCodegenBoxES6TestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/es6/semantics/IrJsCodegenBoxES6TestGenerated.java index e0ac69e837d..ac96c557cba 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/es6/semantics/IrJsCodegenBoxES6TestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/es6/semantics/IrJsCodegenBoxES6TestGenerated.java @@ -12710,6 +12710,11 @@ public class IrJsCodegenBoxES6TestGenerated extends AbstractIrJsCodegenBoxES6Tes KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/ir"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JS_IR_ES6, true); } + @TestMetadata("anonymousClassLeak.kt") + public void testAnonymousClassLeak() throws Exception { + runTest("compiler/testData/codegen/box/ir/anonymousClassLeak.kt"); + } + @TestMetadata("anonymousObjectInForLoopIteratorAndBody.kt") public void testAnonymousObjectInForLoopIteratorAndBody() throws Exception { runTest("compiler/testData/codegen/box/ir/anonymousObjectInForLoopIteratorAndBody.kt"); diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/IrJsCodegenBoxTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/IrJsCodegenBoxTestGenerated.java index 1e452436e78..eb2f6ff3061 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/IrJsCodegenBoxTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/IrJsCodegenBoxTestGenerated.java @@ -12710,6 +12710,11 @@ public class IrJsCodegenBoxTestGenerated extends AbstractIrJsCodegenBoxTest { KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/ir"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JS_IR, true); } + @TestMetadata("anonymousClassLeak.kt") + public void testAnonymousClassLeak() throws Exception { + runTest("compiler/testData/codegen/box/ir/anonymousClassLeak.kt"); + } + @TestMetadata("anonymousObjectInForLoopIteratorAndBody.kt") public void testAnonymousObjectInForLoopIteratorAndBody() throws Exception { runTest("compiler/testData/codegen/box/ir/anonymousObjectInForLoopIteratorAndBody.kt"); diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java index 1341e95d46c..9b15085f25f 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java @@ -12775,6 +12775,11 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest { KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/ir"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JS, true); } + @TestMetadata("anonymousClassLeak.kt") + public void testAnonymousClassLeak() throws Exception { + runTest("compiler/testData/codegen/box/ir/anonymousClassLeak.kt"); + } + @TestMetadata("anonymousObjectInForLoopIteratorAndBody.kt") public void testAnonymousObjectInForLoopIteratorAndBody() throws Exception { runTest("compiler/testData/codegen/box/ir/anonymousObjectInForLoopIteratorAndBody.kt");