diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/BridgeLowering.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/BridgeLowering.kt index 07f17809ae1..cefb279c15f 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/BridgeLowering.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/BridgeLowering.kt @@ -34,7 +34,6 @@ import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.util.OperatorNameConventions -import org.jetbrains.kotlin.utils.addToStdlib.safeAs import org.jetbrains.org.objectweb.asm.Type import org.jetbrains.org.objectweb.asm.commons.Method import java.util.concurrent.ConcurrentHashMap @@ -246,6 +245,9 @@ internal class BridgeLowering(val context: JvmBackendContext) : FileLoweringPass private fun createBridges(irClass: IrClass, irFunction: IrSimpleFunction) { // Track final overrides and bridges to avoid clashes val blacklist = mutableSetOf() + val existingMethodSignatures = irClass.functions + .filter { !it.isFakeOverride && it.name == irFunction.name } + .mapTo(HashSet()) { it.jvmMethod } // Add the current method to the blacklist if it is concrete or final. val targetMethod = (irFunction.resolveFakeOverride() ?: irFunction).jvmMethod @@ -260,10 +262,14 @@ internal class BridgeLowering(val context: JvmBackendContext) : FileLoweringPass // bridge methods in a superclass. blacklist += irFunction.overriddenFinalSpecialBridges() - // We only generate a special bridge method if it does not clash with a final method in a superclass or the current method - val specialBridgeTarget = if ( - specialBridge.signature !in blacklist && (!irFunction.isFakeOverride || irFunction.jvmMethod != specialBridge.signature) - ) { + fun getSpecialBridgeTargetAddingExtraBridges(): IrSimpleFunction { + // We only generate a special bridge method if it does not clash with a final method in a superclass or the current method + if (specialBridge.signature in blacklist || + irFunction.isFakeOverride && irFunction.jvmMethod == specialBridge.signature + ) { + return irFunction + } + // If irFunction is a fake override, we replace it with a stub and redirect all calls to irFunction with // calls to the stub instead. Otherwise we'll end up calling the special method itself and get into an // infinite loop. @@ -306,12 +312,16 @@ internal class BridgeLowering(val context: JvmBackendContext) : FileLoweringPass blacklist += bridgeTarget.jvmMethod } + if (specialBridge.signature in existingMethodSignatures) { + return irFunction + } + blacklist += specialBridge.signature - irClass.addSpecialBridge(specialBridge, bridgeTarget) - } else { - irFunction + return irClass.addSpecialBridge(specialBridge, bridgeTarget) } + val specialBridgeTarget = getSpecialBridgeTargetAddingExtraBridges() + // Deal with existing function that override special bridge methods. if (!irFunction.isFakeOverride && specialBridge.methodInfo != null) { irFunction.rewriteSpecialMethodBody(targetMethod, specialBridge.signature, specialBridge.methodInfo) @@ -434,9 +444,8 @@ internal class BridgeLowering(val context: JvmBackendContext) : FileLoweringPass // This is technically wrong, but it's necessary to generate a method which maps to the same signature. // In case of 'fun foo(): T', where 'T' is a type parameter with primitive upper bound (e.g., 'T : Char'), // 'foo' is mapped to 'foo()C', regardless of its overrides. - val inheritedOverrides = bridge.overriddenSymbols.flatMapTo(mutableSetOf()) { function -> - function.owner.safeAs()?.overriddenSymbols ?: emptyList() - } + val inheritedOverrides = bridge.overriddenSymbols + .flatMapTo(mutableSetOf()) { it.owner.overriddenSymbols } val redundantOverrides = inheritedOverrides.flatMapTo(mutableSetOf()) { it.owner.allOverridden().map { override -> override.symbol } } diff --git a/compiler/testData/codegen/bytecodeListing/collectionStubs/abstractMapRedefiningGetAny.kt b/compiler/testData/codegen/bytecodeListing/collectionStubs/abstractMapRedefiningGetAny.kt new file mode 100644 index 00000000000..5e47ad63e03 --- /dev/null +++ b/compiler/testData/codegen/bytecodeListing/collectionStubs/abstractMapRedefiningGetAny.kt @@ -0,0 +1,3 @@ +abstract class MindMap : MutableMap { + operator fun get(key: Any?): String? = null +} diff --git a/compiler/testData/codegen/bytecodeListing/collectionStubs/abstractMapRedefiningGetAny.txt b/compiler/testData/codegen/bytecodeListing/collectionStubs/abstractMapRedefiningGetAny.txt new file mode 100644 index 00000000000..7f3a8a264a3 --- /dev/null +++ b/compiler/testData/codegen/bytecodeListing/collectionStubs/abstractMapRedefiningGetAny.txt @@ -0,0 +1,22 @@ +@kotlin.Metadata +public abstract class MindMap { + // source: 'abstractMapRedefiningGetAny.kt' + public method (): void + public abstract method containsKey(p0: int): boolean + public bridge final method containsKey(p0: java.lang.Object): boolean + public bridge final method containsValue(p0: java.lang.Object): boolean + public abstract method containsValue(p0: java.lang.String): boolean + public bridge final method entrySet(): java.util.Set + public final @org.jetbrains.annotations.Nullable method get(@org.jetbrains.annotations.Nullable p0: java.lang.Object): java.lang.String + public abstract method get(p0: int): java.lang.String + public bridge final method get(p0: java.lang.Object): java.lang.Object + public abstract method getEntries(): java.util.Set + public abstract method getKeys(): java.util.Set + public abstract method getSize(): int + public abstract method getValues(): java.util.Collection + public bridge final method keySet(): java.util.Set + public abstract method remove(p0: int): java.lang.String + public bridge final method remove(p0: java.lang.Object): java.lang.Object + public bridge final method size(): int + public bridge final method values(): java.util.Collection +} diff --git a/compiler/testData/codegen/bytecodeListing/collectionStubs/abstractMapRedefiningGetAny_ir.txt b/compiler/testData/codegen/bytecodeListing/collectionStubs/abstractMapRedefiningGetAny_ir.txt new file mode 100644 index 00000000000..74b636c0b90 --- /dev/null +++ b/compiler/testData/codegen/bytecodeListing/collectionStubs/abstractMapRedefiningGetAny_ir.txt @@ -0,0 +1,23 @@ +@kotlin.Metadata +public abstract class MindMap { + // source: 'abstractMapRedefiningGetAny.kt' + public method (): void + public abstract method containsKey(p0: int): boolean + public bridge final method containsKey(p0: java.lang.Object): boolean + public bridge final method containsValue(p0: java.lang.Object): boolean + public abstract method containsValue(p0: java.lang.String): boolean + public bridge final method entrySet(): java.util.Set + public final @org.jetbrains.annotations.Nullable method get(@org.jetbrains.annotations.Nullable p0: java.lang.Object): java.lang.String + public abstract method get(p0: int): java.lang.String + public synthetic bridge method get(p0: java.lang.Object): java.lang.Object + public abstract method getEntries(): java.util.Set + public abstract method getKeys(): java.util.Set + public abstract method getSize(): int + public abstract method getValues(): java.util.Collection + public bridge final method keySet(): java.util.Set + public abstract method remove(p0: int): java.lang.String + public synthetic bridge method remove(p0: java.lang.Object): java.lang.Object + public bridge final method remove(p0: java.lang.Object): java.lang.String + public bridge final method size(): int + public bridge final method values(): java.util.Collection +} diff --git a/compiler/tests-gen/org/jetbrains/kotlin/codegen/BytecodeListingTestGenerated.java b/compiler/tests-gen/org/jetbrains/kotlin/codegen/BytecodeListingTestGenerated.java index 91de574ba02..602093c5688 100644 --- a/compiler/tests-gen/org/jetbrains/kotlin/codegen/BytecodeListingTestGenerated.java +++ b/compiler/tests-gen/org/jetbrains/kotlin/codegen/BytecodeListingTestGenerated.java @@ -351,6 +351,11 @@ public class BytecodeListingTestGenerated extends AbstractBytecodeListingTest { KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM, testDataFilePath); } + @TestMetadata("abstractMapRedefiningGetAny.kt") + public void testAbstractMapRedefiningGetAny() throws Exception { + runTest("compiler/testData/codegen/bytecodeListing/collectionStubs/abstractMapRedefiningGetAny.kt"); + } + public void testAllFilesPresentInCollectionStubs() throws Exception { KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/bytecodeListing/collectionStubs"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM, true); } diff --git a/compiler/tests-gen/org/jetbrains/kotlin/codegen/ir/IrBytecodeListingTestGenerated.java b/compiler/tests-gen/org/jetbrains/kotlin/codegen/ir/IrBytecodeListingTestGenerated.java index 2b3eef26e2c..cc143cf7818 100644 --- a/compiler/tests-gen/org/jetbrains/kotlin/codegen/ir/IrBytecodeListingTestGenerated.java +++ b/compiler/tests-gen/org/jetbrains/kotlin/codegen/ir/IrBytecodeListingTestGenerated.java @@ -351,6 +351,11 @@ public class IrBytecodeListingTestGenerated extends AbstractIrBytecodeListingTes KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM_IR, testDataFilePath); } + @TestMetadata("abstractMapRedefiningGetAny.kt") + public void testAbstractMapRedefiningGetAny() throws Exception { + runTest("compiler/testData/codegen/bytecodeListing/collectionStubs/abstractMapRedefiningGetAny.kt"); + } + public void testAllFilesPresentInCollectionStubs() throws Exception { KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/bytecodeListing/collectionStubs"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true); }