From 66b383349f795a072baafc30be8efe68deaaf14e Mon Sep 17 00:00:00 2001 From: Alexander Udalov Date: Wed, 11 Jul 2018 17:21:21 +0200 Subject: [PATCH] Fix regression in reflection on looking up equals/hashCode/toString in interface Caused by 4266e50be8 and 8ccbbf71ec. Previously it worked because we used hardcoded signatures of equals/hashCode/toString and always looked them up in java.lang.Object #KT-25404 Fixed --- .../reflection/mapping/methodsFromObject.kt | 37 +++++++++++++++++++ .../ir/IrBlackBoxCodegenTestGenerated.java | 5 +++ .../codegen/BlackBoxCodegenTestGenerated.java | 5 +++ .../LightAnalysisModeTestGenerated.java | 5 +++ .../jvm/internal/KDeclarationContainerImpl.kt | 11 +++++- 5 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 compiler/testData/codegen/box/reflection/mapping/methodsFromObject.kt diff --git a/compiler/testData/codegen/box/reflection/mapping/methodsFromObject.kt b/compiler/testData/codegen/box/reflection/mapping/methodsFromObject.kt new file mode 100644 index 00000000000..3817253e1b5 --- /dev/null +++ b/compiler/testData/codegen/box/reflection/mapping/methodsFromObject.kt @@ -0,0 +1,37 @@ +// TARGET_BACKEND: JVM +// WITH_REFLECT + +import kotlin.reflect.KClass +import kotlin.reflect.full.memberFunctions +import kotlin.reflect.jvm.javaMethod +import kotlin.test.assertEquals + +annotation class A +interface I +class C + +interface MyCustomMembers { + fun equals(): Boolean + fun hashCode(hehe: Int): Int + fun toString(hehe: String): Any +} + +interface MyCloneable : Cloneable + +fun KClass<*>.functions() = memberFunctions.map { it.javaMethod!!.name }.sorted() + +fun box(): String { + assertEquals(listOf("equals", "hashCode", "toString"), A::class.functions()) + assertEquals(listOf("equals", "hashCode", "toString"), I::class.functions()) + assertEquals(listOf("equals", "hashCode", "toString"), C::class.functions()) + + assertEquals( + listOf("equals", "equals", "hashCode", "hashCode", "toString", "toString"), + MyCustomMembers::class.functions() + ) + + // TODO: KT-22923 + // assertEquals(listOf("clone", "equals", "hashCode", "toString"), MyCloneable::class.functions()) + + return "OK" +} diff --git a/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java b/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java index bf0abf9e195..e5879444c5c 100644 --- a/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java +++ b/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java @@ -17633,6 +17633,11 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes runTest("compiler/testData/codegen/box/reflection/mapping/memberProperty.kt"); } + @TestMetadata("methodsFromObject.kt") + public void testMethodsFromObject() throws Exception { + runTest("compiler/testData/codegen/box/reflection/mapping/methodsFromObject.kt"); + } + @TestMetadata("openSuspendFun.kt") public void testOpenSuspendFun() throws Exception { runTest("compiler/testData/codegen/box/reflection/mapping/openSuspendFun.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index 94ed468b445..ad1ab179b8e 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -17633,6 +17633,11 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { runTest("compiler/testData/codegen/box/reflection/mapping/memberProperty.kt"); } + @TestMetadata("methodsFromObject.kt") + public void testMethodsFromObject() throws Exception { + runTest("compiler/testData/codegen/box/reflection/mapping/methodsFromObject.kt"); + } + @TestMetadata("openSuspendFun.kt") public void testOpenSuspendFun() throws Exception { runTest("compiler/testData/codegen/box/reflection/mapping/openSuspendFun.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java index 98c52fed2b9..02b013f13c6 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java @@ -17633,6 +17633,11 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes runTest("compiler/testData/codegen/box/reflection/mapping/memberProperty.kt"); } + @TestMetadata("methodsFromObject.kt") + public void testMethodsFromObject() throws Exception { + runTest("compiler/testData/codegen/box/reflection/mapping/methodsFromObject.kt"); + } + @TestMetadata("openSuspendFun.kt") public void testOpenSuspendFun() throws Exception { runTest("compiler/testData/codegen/box/reflection/mapping/openSuspendFun.kt"); diff --git a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KDeclarationContainerImpl.kt b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KDeclarationContainerImpl.kt index faff7a06749..da9f207fd8f 100644 --- a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KDeclarationContainerImpl.kt +++ b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KDeclarationContainerImpl.kt @@ -168,9 +168,16 @@ internal abstract class KDeclarationContainerImpl : ClassBasedDeclarationContain private fun Class<*>.lookupMethod(name: String, parameterTypes: List>, returnType: Class<*>, isPublic: Boolean): Method? { val parametersArray = parameterTypes.toTypedArray() - // If we're looking for a public method, just use Java reflection's getMethod/getMethods + // If we're looking for a public method, use Java reflection's getMethod/getMethods first if (isPublic) { - return tryGetMethod(name, parametersArray, returnType, declared = false) + val result = tryGetMethod(name, parametersArray, returnType, declared = false) + if (result != null) return result + + // Methods from java.lang.Object cannot be found in the interface via Class.getMethod/getDeclaredMethod + if (isInterface) { + val fromObject = Any::class.java.lookupMethod(name, parameterTypes, returnType, isPublic) + if (fromObject != null) return fromObject + } } // If we're looking for a non-public method, it might be located not only in this class, but also in any of its superclasses