[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);
|
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")
|
@TestMetadata("anonymousObjectInForLoopIteratorAndBody.kt")
|
||||||
public void testAnonymousObjectInForLoopIteratorAndBody() throws Exception {
|
public void testAnonymousObjectInForLoopIteratorAndBody() throws Exception {
|
||||||
runTest("compiler/testData/codegen/box/ir/anonymousObjectInForLoopIteratorAndBody.kt");
|
runTest("compiler/testData/codegen/box/ir/anonymousObjectInForLoopIteratorAndBody.kt");
|
||||||
|
|||||||
+59
-15
@@ -1100,13 +1100,53 @@ abstract class IrFileDeserializer(
|
|||||||
return result
|
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 {
|
try {
|
||||||
deserializeBodies = oldInline || (deserializeInlineFunctions && this is IrSimpleFunction && isInline)
|
deserializeBodies = oldBodiesPolicy || checkInlineBody() || returnType.checkObjectLeak(!symbol.isPublicApi)
|
||||||
block()
|
block()
|
||||||
} finally {
|
} 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 ->
|
): T = withDeserializedIrDeclarationBase(proto.base) { symbol, idSig, startOffset, endOffset, origin, fcode ->
|
||||||
symbolTable.withScope(symbol.descriptor) {
|
symbolTable.withScope(symbol.descriptor) {
|
||||||
block(symbol as IrFunctionSymbol, idSig, startOffset, endOffset, origin, fcode).usingParent {
|
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)
|
valueParameters = deserializeValueParameters(proto.valueParameterList)
|
||||||
|
|
||||||
val nameType = BinaryNameAndType.decode(proto.nameType)
|
|
||||||
returnType = deserializeIrType(nameType.typeIndex)
|
|
||||||
|
|
||||||
if (proto.hasDispatchReceiver())
|
if (proto.hasDispatchReceiver())
|
||||||
dispatchReceiverParameter = deserializeIrValueParameter(proto.dispatchReceiver, -1)
|
dispatchReceiverParameter = deserializeIrValueParameter(proto.dispatchReceiver, -1)
|
||||||
if (proto.hasExtensionReceiver())
|
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 ->
|
withDeserializedIrDeclarationBase(proto.base) { symbol, uniqId, startOffset, endOffset, origin, fcode ->
|
||||||
val nameType = BinaryNameAndType.decode(proto.nameType)
|
val nameType = BinaryNameAndType.decode(proto.nameType)
|
||||||
val type = deserializeIrType(nameType.typeIndex)
|
val type = deserializeIrType(nameType.typeIndex)
|
||||||
@@ -1245,8 +1286,11 @@ abstract class IrFileDeserializer(
|
|||||||
flags.isStatic,
|
flags.isStatic,
|
||||||
)
|
)
|
||||||
}.usingParent {
|
}.usingParent {
|
||||||
if (proto.hasInitializer())
|
if (proto.hasInitializer()) {
|
||||||
initializer = IrExpressionBodyImpl(deserializeExpressionBody(proto.initializer))
|
withInitializerGuard(isPrivateProperty) {
|
||||||
|
initializer = IrExpressionBodyImpl(deserializeExpressionBody(proto.initializer))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
(descriptor as? WrappedFieldDescriptor)?.bind(this)
|
(descriptor as? WrappedFieldDescriptor)?.bind(this)
|
||||||
}
|
}
|
||||||
@@ -1302,7 +1346,7 @@ abstract class IrFileDeserializer(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (proto.hasBackingField()) {
|
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.
|
// A property symbol and its field symbol share the same descriptor.
|
||||||
// Unfortunately symbol deserialization doesn't know anything about that.
|
// Unfortunately symbol deserialization doesn't know anything about that.
|
||||||
// So we can end up with two wrapped property descriptors for property and its field.
|
// 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!!) {
|
val declaration: IrDeclaration = when (proto.declaratorCase!!) {
|
||||||
IR_ANONYMOUS_INIT -> deserializeIrAnonymousInit(proto.irAnonymousInit)
|
IR_ANONYMOUS_INIT -> deserializeIrAnonymousInit(proto.irAnonymousInit)
|
||||||
IR_CONSTRUCTOR -> deserializeIrConstructor(proto.irConstructor)
|
IR_CONSTRUCTOR -> deserializeIrConstructor(proto.irConstructor)
|
||||||
IR_FIELD -> deserializeIrField(proto.irField)
|
IR_FIELD -> deserializeIrField(proto.irField, false)
|
||||||
IR_CLASS -> deserializeIrClass(proto.irClass)
|
IR_CLASS -> deserializeIrClass(proto.irClass)
|
||||||
IR_FUNCTION -> deserializeIrFunction(proto.irFunction)
|
IR_FUNCTION -> deserializeIrFunction(proto.irFunction)
|
||||||
IR_PROPERTY -> deserializeIrProperty(proto.irProperty)
|
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);
|
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")
|
@TestMetadata("anonymousObjectInForLoopIteratorAndBody.kt")
|
||||||
public void testAnonymousObjectInForLoopIteratorAndBody() throws Exception {
|
public void testAnonymousObjectInForLoopIteratorAndBody() throws Exception {
|
||||||
runTest("compiler/testData/codegen/box/ir/anonymousObjectInForLoopIteratorAndBody.kt");
|
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);
|
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")
|
@TestMetadata("anonymousObjectInForLoopIteratorAndBody.kt")
|
||||||
public void testAnonymousObjectInForLoopIteratorAndBody() throws Exception {
|
public void testAnonymousObjectInForLoopIteratorAndBody() throws Exception {
|
||||||
runTest("compiler/testData/codegen/box/ir/anonymousObjectInForLoopIteratorAndBody.kt");
|
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);
|
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")
|
@TestMetadata("anonymousObjectInForLoopIteratorAndBody.kt")
|
||||||
public void testAnonymousObjectInForLoopIteratorAndBody() throws Exception {
|
public void testAnonymousObjectInForLoopIteratorAndBody() throws Exception {
|
||||||
runTest("compiler/testData/codegen/box/ir/anonymousObjectInForLoopIteratorAndBody.kt");
|
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);
|
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")
|
@TestMetadata("anonymousObjectInForLoopIteratorAndBody.kt")
|
||||||
public void testAnonymousObjectInForLoopIteratorAndBody() throws Exception {
|
public void testAnonymousObjectInForLoopIteratorAndBody() throws Exception {
|
||||||
runTest("compiler/testData/codegen/box/ir/anonymousObjectInForLoopIteratorAndBody.kt");
|
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);
|
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")
|
@TestMetadata("anonymousObjectInForLoopIteratorAndBody.kt")
|
||||||
public void testAnonymousObjectInForLoopIteratorAndBody() throws Exception {
|
public void testAnonymousObjectInForLoopIteratorAndBody() throws Exception {
|
||||||
runTest("compiler/testData/codegen/box/ir/anonymousObjectInForLoopIteratorAndBody.kt");
|
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);
|
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")
|
@TestMetadata("anonymousObjectInForLoopIteratorAndBody.kt")
|
||||||
public void testAnonymousObjectInForLoopIteratorAndBody() throws Exception {
|
public void testAnonymousObjectInForLoopIteratorAndBody() throws Exception {
|
||||||
runTest("compiler/testData/codegen/box/ir/anonymousObjectInForLoopIteratorAndBody.kt");
|
runTest("compiler/testData/codegen/box/ir/anonymousObjectInForLoopIteratorAndBody.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user