Check return type when locating method in reflection

#KT-11824 Fixed
This commit is contained in:
Alexander Udalov
2016-06-17 12:42:35 +03:00
parent bafe8e55ce
commit 311301f430
3 changed files with 62 additions and 36 deletions
@@ -0,0 +1,9 @@
// WITH_REFLECT
data class Foo(val id: String) {
fun getId() = -42 // Fail
}
fun box(): String {
return Foo::id.call(Foo("OK"))
}
@@ -11190,6 +11190,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
doTest(fileName); doTest(fileName);
} }
@TestMetadata("propertyGetterAndGetFunctionDifferentReturnType.kt")
public void testPropertyGetterAndGetFunctionDifferentReturnType() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/call/propertyGetterAndGetFunctionDifferentReturnType.kt");
doTest(fileName);
}
@TestMetadata("returnUnit.kt") @TestMetadata("returnUnit.kt")
public void testReturnUnit() throws Exception { public void testReturnUnit() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/call/returnUnit.kt"); String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/call/returnUnit.kt");
@@ -156,10 +156,24 @@ internal abstract class KDeclarationContainerImpl : ClassBasedDeclarationContain
return functions.single() return functions.single()
} }
private fun Class<*>.tryGetMethod(name: String, parameterTypes: List<Class<*>>, declared: Boolean) = private fun Class<*>.tryGetMethod(name: String, parameterTypes: List<Class<*>>, returnType: Class<*>, declared: Boolean): Method? =
try { try {
if (declared) getDeclaredMethod(name, *parameterTypes.toTypedArray()) val parametersArray = parameterTypes.toTypedArray()
else getMethod(name, *parameterTypes.toTypedArray()) val result = if (declared) getDeclaredMethod(name, *parametersArray) else getMethod(name, *parametersArray)
if (result.returnType == returnType) result
else {
// If we've found a method with an unexpected return type, it's likely that there are several methods in this class
// with the given parameter types and Java reflection API has returned not the one we're looking for.
// Falling back to enumerating all methods in the class in this (rather rare) case.
// Example: class A(val x: Int) { fun getX(): String = ... }
val allMethods = if (declared) declaredMethods else methods
allMethods.firstOrNull { method ->
method.name == name &&
method.returnType == returnType &&
Arrays.equals(method.parameterTypes, parametersArray)
}
}
} }
catch (e: NoSuchMethodException) { catch (e: NoSuchMethodException) {
null null
@@ -174,11 +188,10 @@ internal abstract class KDeclarationContainerImpl : ClassBasedDeclarationContain
null null
} }
// TODO: check resulting method's return type
fun findMethodBySignature(name: String, desc: String, declared: Boolean): Method? { fun findMethodBySignature(name: String, desc: String, declared: Boolean): Method? {
if (name == "<init>") return null if (name == "<init>") return null
return methodOwner.tryGetMethod(name, loadParameterTypes(desc), declared) return methodOwner.tryGetMethod(name, loadParameterTypes(desc), loadReturnType(desc), declared)
} }
fun findDefaultMethod(name: String, desc: String, isMember: Boolean, declared: Boolean): Method? { fun findDefaultMethod(name: String, desc: String, isMember: Boolean, declared: Boolean): Method? {
@@ -190,7 +203,7 @@ internal abstract class KDeclarationContainerImpl : ClassBasedDeclarationContain
} }
addParametersAndMasks(parameterTypes, desc, false) addParametersAndMasks(parameterTypes, desc, false)
return methodOwner.tryGetMethod(name + JvmAbi.DEFAULT_PARAMS_IMPL_SUFFIX, parameterTypes, declared) return methodOwner.tryGetMethod(name + JvmAbi.DEFAULT_PARAMS_IMPL_SUFFIX, parameterTypes, loadReturnType(desc), declared)
} }
fun findConstructorBySignature(desc: String, declared: Boolean): Constructor<*>? { fun findConstructorBySignature(desc: String, declared: Boolean): Constructor<*>? {
@@ -214,46 +227,44 @@ internal abstract class KDeclarationContainerImpl : ClassBasedDeclarationContain
} }
private fun loadParameterTypes(desc: String): List<Class<*>> { private fun loadParameterTypes(desc: String): List<Class<*>> {
val classLoader = jClass.safeClassLoader
val result = arrayListOf<Class<*>>() val result = arrayListOf<Class<*>>()
var i = 1 var begin = 1
while (desc[i] != ')') { while (desc[begin] != ')') {
var arrayDimension = 0 var end = begin
while (desc[i] == '[') { while (desc[end] == '[') end++
arrayDimension++ when (desc[end]) {
i++ in "VZCBSIFJD" -> end++
} 'L' -> end = desc.indexOf(';', begin) + 1
var type = when (desc[i++]) {
'L' -> {
val semicolon = desc.indexOf(';', i)
val internalName = desc.substring(i, semicolon)
i = semicolon + 1
classLoader.loadClass(internalName.replace('/', '.'))
}
'V' -> Void.TYPE
'Z' -> java.lang.Boolean.TYPE
'C' -> java.lang.Character.TYPE
'B' -> java.lang.Byte.TYPE
'S' -> java.lang.Short.TYPE
'I' -> java.lang.Integer.TYPE
'F' -> java.lang.Float.TYPE
'J' -> java.lang.Long.TYPE
'D' -> java.lang.Double.TYPE
else -> throw KotlinReflectionInternalError("Unknown type prefix in the method signature: $desc") else -> throw KotlinReflectionInternalError("Unknown type prefix in the method signature: $desc")
} }
repeat(arrayDimension) { result.add(parseType(desc, begin, end))
type = type.createArrayType() begin = end
}
result.add(type)
} }
return result return result
} }
private fun parseType(desc: String, begin: Int, end: Int): Class<*> =
when (desc[begin]) {
'L' -> jClass.safeClassLoader.loadClass(desc.substring(begin + 1, end - 1).replace('/', '.'))
'[' -> parseType(desc, begin + 1, end).createArrayType()
'V' -> Void.TYPE
'Z' -> Boolean::class.java
'C' -> Char::class.java
'B' -> Byte::class.java
'S' -> Short::class.java
'I' -> Int::class.java
'F' -> Float::class.java
'J' -> Long::class.java
'D' -> Double::class.java
else -> throw KotlinReflectionInternalError("Unknown type prefix in the method signature: $desc")
}
private fun loadReturnType(desc: String): Class<*> =
parseType(desc, desc.indexOf(')') + 1, desc.length)
companion object { companion object {
private val DEFAULT_CONSTRUCTOR_MARKER = Class.forName("kotlin.jvm.internal.DefaultConstructorMarker") private val DEFAULT_CONSTRUCTOR_MARKER = Class.forName("kotlin.jvm.internal.DefaultConstructorMarker")
} }