JVM IR: fix incorrect detection of interface method impls in Java

The problem in the test case was that `JImpl.entrySet` was detected by
ReplaceDefaultImplsOverriddenSymbols as a class fake override, which
overrides non-abstract interface method. Thus, overriddenSymbols of
`MyMap.entrySet` were changed in
`ReplaceDefaultImplsOverriddenSymbols.visitSimpleFunction`.

Later, BridgeLowering tried to determine which special bridges to
generate for `MyMap.<get-entries>`, and for that it inspected existing
methods and their overrides.

Normally we would generate the following special bridge:

    getEntries()Ljava/util/Set;
        invokespecial JImpl.entrySet()Ljava/util/Set;

However, because of incorrect overrides, the generated class method was
selected here instead of the expected `Map.<get-entries>`:
06001fc091/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/BridgeLowering.kt (L282)
and since the JVM signature of the generated class method is the same as
that of the fake override in MyMap, we never got to generating the
special bridge.

The solution is to skip Java classes when looking for class methods
which override non-abstract interface methods. This logic only makes
sense for Kotlin code, because only Kotlin has DefaultImpls, and the
requirement to generate non-abstract fake overrides of interface methods
as actual methods in the bytecode, delegating to DefaultImpls.

 #KT-48167 Fixed
This commit is contained in:
Alexander Udalov
2021-08-11 22:44:51 +02:00
parent d124239025
commit 1b98723b3f
11 changed files with 220 additions and 1 deletions

View File

@@ -40578,6 +40578,12 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT
runTest("compiler/testData/codegen/box/specialBuiltins/irrelevantRemoveAtOverride.kt");
}
@Test
@TestMetadata("javaMapWithCustomEntries.kt")
public void testJavaMapWithCustomEntries() throws Exception {
runTest("compiler/testData/codegen/box/specialBuiltins/javaMapWithCustomEntries.kt");
}
@Test
@TestMetadata("mapGetOrDefault.kt")
public void testMapGetOrDefault() throws Exception {

View File

@@ -262,7 +262,9 @@ private class InterfaceObjectCallsLowering(val context: JvmBackendContext) : IrE
*/
internal fun IrSimpleFunction.findInterfaceImplementation(jvmDefaultMode: JvmDefaultMode): IrSimpleFunction? {
if (!isFakeOverride) return null
parent.let { if (it is IrClass && it.isJvmInterface) return null }
val parent = parent
if (parent is IrClass && (parent.isJvmInterface || parent.isFromJava())) return null
val implementation = resolveFakeOverride(toSkip = ::isDefaultImplsBridge) ?: return null

View File

@@ -0,0 +1,81 @@
// TARGET_BACKEND: JVM
// JVM_TARGET: 1.8
// FILE: box.kt
class MyMap<K, V> : JImpl<K, V>()
fun box(): String {
val a = MyMap<Int, String>()
a.put(42, "OK")
return a.entries.iterator().next().value
}
// FILE: J.java
import java.util.*;
public interface J<K, V> extends Map<K, V> {
@Override
default Set<Entry<K, V>> entrySet() {
return myEntrySet();
}
Set<Entry<K,V>> myEntrySet();
}
// FILE: JImpl.java
import java.util.*;
public class JImpl<K, V> implements J<K, V> {
private final Map<K, V> delegate = new HashMap<>();
@Override
public Set<Entry<K, V>> myEntrySet() {
return delegate.entrySet();
}
@Override
public int size() {
return delegate.size();
}
@Override
public boolean isEmpty() {
return delegate.isEmpty();
}
@Override
public boolean containsKey(Object key) {
return delegate.containsKey(key);
}
@Override
public boolean containsValue(Object value) {
return delegate.containsValue(value);
}
@Override
public V get(Object key) {
return delegate.get(key);
}
@Override
public V put(K key, V value) {
return delegate.put(key, value);
}
@Override
public V remove(Object key) {
return delegate.remove(key);
}
@Override
public void putAll(Map<? extends K, ? extends V> m) {
delegate.putAll(m);
}
@Override
public void clear() {
delegate.clear();
}
@Override
public Set<K> keySet() {
return delegate.keySet();
}
@Override
public Collection<V> values() {
return delegate.values();
}
}

View File

@@ -0,0 +1,75 @@
// WITH_SIGNATURES
// JVM_TARGET: 1.8
// FILE: test.kt
class MyMap<K, V> : JImpl<K, V>()
// FILE: J.java
import java.util.*;
public interface J<K, V> extends Map<K, V> {
@Override
default Set<Entry<K, V>> entrySet() {
return myEntrySet();
}
Set<Entry<K,V>> myEntrySet();
}
// FILE: JImpl.java
import java.util.*;
public class JImpl<K, V> implements J<K, V> {
private final Map<K, V> delegate = new HashMap<>();
@Override
public Set<Entry<K, V>> myEntrySet() {
return delegate.entrySet();
}
@Override
public int size() {
return delegate.size();
}
@Override
public boolean isEmpty() {
return delegate.isEmpty();
}
@Override
public boolean containsKey(Object key) {
return delegate.containsKey(key);
}
@Override
public boolean containsValue(Object value) {
return delegate.containsValue(value);
}
@Override
public V get(Object key) {
return delegate.get(key);
}
@Override
public V put(K key, V value) {
return delegate.put(key, value);
}
@Override
public V remove(Object key) {
return delegate.remove(key);
}
@Override
public void putAll(Map<? extends K, ? extends V> m) {
delegate.putAll(m);
}
@Override
public void clear() {
delegate.clear();
}
@Override
public Set<K> keySet() {
return delegate.keySet();
}
@Override
public Collection<V> values() {
return delegate.values();
}
}

View File

@@ -0,0 +1,13 @@
@kotlin.Metadata
public final class<<K:Ljava/lang/Object;V:Ljava/lang/Object;>LJImpl<TK;TV;>;> MyMap {
// source: 'test.kt'
public bridge final <()Ljava/util/Collection<TV;>;> method values(): java.util.Collection
public bridge final <()Ljava/util/Set<Ljava/util/Map$Entry<TK;TV;>;>;> method entrySet(): java.util.Set
public bridge final <()Ljava/util/Set<TK;>;> method keySet(): java.util.Set
public <null> method <init>(): void
public bridge <null> method getEntries(): java.util.Set
public bridge <null> method getKeys(): java.util.Set
public bridge <null> method getSize(): int
public bridge <null> method getValues(): java.util.Collection
public bridge final <null> method size(): int
}

View File

@@ -0,0 +1,13 @@
@kotlin.Metadata
public final class<<K:Ljava/lang/Object;V:Ljava/lang/Object;>LJImpl<TK;TV;>;> MyMap {
// source: 'test.kt'
public bridge <()Ljava/util/Collection<Ljava/lang/Object;>;> method getValues(): java.util.Collection
public bridge final <()Ljava/util/Collection<TV;>;> method values(): java.util.Collection
public bridge <()Ljava/util/Set<Ljava/lang/Object;>;> method getKeys(): java.util.Set
public bridge <()Ljava/util/Set<Ljava/util/Map$Entry<Ljava/lang/Object;Ljava/lang/Object;>;>;> method getEntries(): java.util.Set
public bridge final <()Ljava/util/Set<Ljava/util/Map$Entry<TK;TV;>;>;> method entrySet(): java.util.Set
public bridge final <()Ljava/util/Set<TK;>;> method keySet(): java.util.Set
public <null> method <init>(): void
public bridge <null> method getSize(): int
public bridge final <null> method size(): int
}

View File

@@ -40416,6 +40416,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
runTest("compiler/testData/codegen/box/specialBuiltins/irrelevantRemoveAtOverride.kt");
}
@Test
@TestMetadata("javaMapWithCustomEntries.kt")
public void testJavaMapWithCustomEntries() throws Exception {
runTest("compiler/testData/codegen/box/specialBuiltins/javaMapWithCustomEntries.kt");
}
@Test
@TestMetadata("mapGetOrDefault.kt")
public void testMapGetOrDefault() throws Exception {

View File

@@ -2255,6 +2255,12 @@ public class BytecodeListingTestGenerated extends AbstractBytecodeListingTest {
runTest("compiler/testData/codegen/bytecodeListing/specialBridges/signatures/implementsJavaMap.kt");
}
@Test
@TestMetadata("implementsJavaMapWithCustomEntries.kt")
public void testImplementsJavaMapWithCustomEntries() throws Exception {
runTest("compiler/testData/codegen/bytecodeListing/specialBridges/signatures/implementsJavaMapWithCustomEntries.kt");
}
@Test
@TestMetadata("implementsMap.kt")
public void testImplementsMap() throws Exception {

View File

@@ -40578,6 +40578,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
runTest("compiler/testData/codegen/box/specialBuiltins/irrelevantRemoveAtOverride.kt");
}
@Test
@TestMetadata("javaMapWithCustomEntries.kt")
public void testJavaMapWithCustomEntries() throws Exception {
runTest("compiler/testData/codegen/box/specialBuiltins/javaMapWithCustomEntries.kt");
}
@Test
@TestMetadata("mapGetOrDefault.kt")
public void testMapGetOrDefault() throws Exception {

View File

@@ -2303,6 +2303,12 @@ public class IrBytecodeListingTestGenerated extends AbstractIrBytecodeListingTes
runTest("compiler/testData/codegen/bytecodeListing/specialBridges/signatures/implementsJavaMap.kt");
}
@Test
@TestMetadata("implementsJavaMapWithCustomEntries.kt")
public void testImplementsJavaMapWithCustomEntries() throws Exception {
runTest("compiler/testData/codegen/bytecodeListing/specialBridges/signatures/implementsJavaMapWithCustomEntries.kt");
}
@Test
@TestMetadata("implementsMap.kt")
public void testImplementsMap() throws Exception {

View File

@@ -32423,6 +32423,11 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
runTest("compiler/testData/codegen/box/specialBuiltins/irrelevantRemoveAtOverride.kt");
}
@TestMetadata("javaMapWithCustomEntries.kt")
public void testJavaMapWithCustomEntries() throws Exception {
runTest("compiler/testData/codegen/box/specialBuiltins/javaMapWithCustomEntries.kt");
}
@TestMetadata("mapGetOrDefault.kt")
public void testMapGetOrDefault() throws Exception {
runTest("compiler/testData/codegen/box/specialBuiltins/mapGetOrDefault.kt");