[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
This commit is contained in:
Roman Artemev
2020-07-14 16:41:27 +03:00
committed by romanart
parent d31de6c8de
commit cd9f59325e
9 changed files with 166 additions and 15 deletions
@@ -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");
@@ -1100,13 +1100,53 @@ abstract class IrFileDeserializer(
return result
}
private inline fun <T : IrFunction> 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 : IrFunction> 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)
+72
View File
@@ -0,0 +1,72 @@
// MODULE: lib
// FILE: lib.kt
// KT-40216
inline fun <T> 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"
}
@@ -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");
@@ -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");
@@ -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");
@@ -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");
@@ -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");
@@ -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");