[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:
Generated
+5
@@ -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");
|
||||
|
||||
+59
-15
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
+5
@@ -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");
|
||||
|
||||
+5
@@ -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");
|
||||
|
||||
+5
@@ -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");
|
||||
|
||||
Generated
+5
@@ -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");
|
||||
|
||||
Generated
+5
@@ -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");
|
||||
|
||||
+5
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user