Do not use getMethod/getConstructor in reflection

Use only getDeclaredMethod/getDeclaredConstructor instead. The reason is
that getMethod/getConstructor only finds public-API (public or protected
on JVM) declarations, and to determine if a declaration is public-API in
the class file we used isPublicInBytecode, which was trying to load
annotations on the declaration to see if it was InlineOnly, and that
required lots of time-consuming actions and worsened the stack trace (as
can be seen e.g. in KT-27878). In fact, the implementation of
Class.getMethod is not supposed to do anything complicated except
loading annotations from each superclass and superinterface of the given
class. Doing it in our codebase simplifies implementation and probably
improves performance
This commit is contained in:
Alexander Udalov
2018-10-30 18:35:45 +01:00
parent 2f72f68e1a
commit c5275f178a
9 changed files with 85 additions and 61 deletions
@@ -0,0 +1,27 @@
// TARGET_BACKEND: JVM
// WITH_REFLECT
import kotlin.reflect.jvm.javaMethod
import kotlin.test.assertEquals
interface A1 {
fun a1()
}
interface A2 {
fun a2()
}
interface B1 : A1
interface B2 : A1, A2
interface C : B2
abstract class D : B1, C
fun box(): String {
assertEquals("public abstract void A1.a1()", D::a1.javaMethod!!.toString())
assertEquals("public abstract void A2.a2()", D::a2.javaMethod!!.toString())
return "OK"
}
@@ -20130,6 +20130,11 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
runTest("compiler/testData/codegen/box/reflection/mapping/methodsFromObject.kt");
}
@TestMetadata("methodsFromSuperInterface.kt")
public void testMethodsFromSuperInterface() throws Exception {
runTest("compiler/testData/codegen/box/reflection/mapping/methodsFromSuperInterface.kt");
}
@TestMetadata("openSuspendFun.kt")
public void testOpenSuspendFun() throws Exception {
runTest("compiler/testData/codegen/box/reflection/mapping/openSuspendFun.kt");
@@ -20130,6 +20130,11 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
runTest("compiler/testData/codegen/box/reflection/mapping/methodsFromObject.kt");
}
@TestMetadata("methodsFromSuperInterface.kt")
public void testMethodsFromSuperInterface() throws Exception {
runTest("compiler/testData/codegen/box/reflection/mapping/methodsFromSuperInterface.kt");
}
@TestMetadata("openSuspendFun.kt")
public void testOpenSuspendFun() throws Exception {
runTest("compiler/testData/codegen/box/reflection/mapping/openSuspendFun.kt");
@@ -20135,6 +20135,11 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
runTest("compiler/testData/codegen/box/reflection/mapping/methodsFromObject.kt");
}
@TestMetadata("methodsFromSuperInterface.kt")
public void testMethodsFromSuperInterface() throws Exception {
runTest("compiler/testData/codegen/box/reflection/mapping/methodsFromSuperInterface.kt");
}
@TestMetadata("openSuspendFun.kt")
public void testOpenSuspendFun() throws Exception {
runTest("compiler/testData/codegen/box/reflection/mapping/openSuspendFun.kt");
@@ -73,6 +73,7 @@ private val INLINE_ONLY_ANNOTATION_FQ_NAME = FqName("kotlin.internal.InlineOnly"
fun MemberDescriptor.isInlineOnlyOrReifiable(): Boolean =
this is CallableMemberDescriptor && (isReifiable() || DescriptorUtils.getDirectMember(this).isReifiable() || isInlineOnly())
// TODO: move to compiler modules
fun MemberDescriptor.isEffectivelyInlineOnly(): Boolean =
isInlineOnlyOrReifiable() || (this is FunctionDescriptor && isSuspend && isInline &&
(valueParameters.any { it.isCrossinline } || visibility == Visibilities.PRIVATE))
@@ -165,35 +165,34 @@ internal abstract class KDeclarationContainerImpl : ClassBasedDeclarationContain
return functions.single()
}
private fun Class<*>.lookupMethod(name: String, parameterTypes: List<Class<*>>, returnType: Class<*>, isPublic: Boolean): Method? {
val parametersArray = parameterTypes.toTypedArray()
private fun Class<*>.lookupMethod(name: String, parameterTypes: List<Class<*>>, returnType: Class<*>): Method? {
lookupMethod(name, parameterTypes.toTypedArray(), returnType)?.let { return it }
// If we're looking for a public method, use Java reflection's getMethod/getMethods first
if (isPublic) {
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
var klass: Class<*>? = this
while (klass != null) {
val method = klass.tryGetMethod(name, parametersArray, returnType, declared = true)
if (method != null) return method
klass = klass.superclass
// Methods from java.lang.Object (equals, hashCode, toString) cannot be found in the interface via
// Class.getMethod/getDeclaredMethod, so for interfaces, we also look in java.lang.Object.
if (isInterface) {
Any::class.java.lookupMethod(name, parameterTypes.toTypedArray(), returnType)?.let { return it }
}
return null
}
private fun Class<*>.tryGetMethod(name: String, parameterTypes: Array<Class<*>>, returnType: Class<*>, declared: Boolean): Method? =
private fun Class<*>.lookupMethod(name: String, parameterTypes: Array<Class<*>>, returnType: Class<*>): Method? {
tryGetMethod(name, parameterTypes, returnType)?.let { return it }
superclass?.lookupMethod(name, parameterTypes, returnType)?.let { return it }
// TODO: avoid exponential complexity here
for (superInterface in interfaces) {
superInterface.lookupMethod(name, parameterTypes, returnType)?.let { return it }
}
return null
}
private fun Class<*>.tryGetMethod(name: String, parameterTypes: Array<Class<*>>, returnType: Class<*>): Method? =
try {
val result = if (declared) getDeclaredMethod(name, *parameterTypes) else getMethod(name, *parameterTypes)
val result = getDeclaredMethod(name, *parameterTypes)
if (result.returnType == returnType) result
else {
@@ -201,8 +200,7 @@ internal abstract class KDeclarationContainerImpl : ClassBasedDeclarationContain
// 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 ->
declaredMethods.firstOrNull { method ->
method.name == name &&
method.returnType == returnType &&
method.parameterTypes!!.contentEquals(parameterTypes)
@@ -212,21 +210,20 @@ internal abstract class KDeclarationContainerImpl : ClassBasedDeclarationContain
null
}
private fun Class<*>.tryGetConstructor(parameterTypes: List<Class<*>>, declared: Boolean): Constructor<*>? =
private fun Class<*>.tryGetConstructor(parameterTypes: List<Class<*>>): Constructor<*>? =
try {
if (declared) getDeclaredConstructor(*parameterTypes.toTypedArray())
else getConstructor(*parameterTypes.toTypedArray())
getDeclaredConstructor(*parameterTypes.toTypedArray())
} catch (e: NoSuchMethodException) {
null
}
fun findMethodBySignature(name: String, desc: String, isPublic: Boolean): Method? {
fun findMethodBySignature(name: String, desc: String): Method? {
if (name == "<init>") return null
return methodOwner.lookupMethod(name, loadParameterTypes(desc), loadReturnType(desc), isPublic)
return methodOwner.lookupMethod(name, loadParameterTypes(desc), loadReturnType(desc))
}
fun findDefaultMethod(name: String, desc: String, isMember: Boolean, isPublic: Boolean): Method? {
fun findDefaultMethod(name: String, desc: String, isMember: Boolean): Method? {
if (name == "<init>") return null
val parameterTypes = arrayListOf<Class<*>>()
@@ -235,19 +232,16 @@ internal abstract class KDeclarationContainerImpl : ClassBasedDeclarationContain
}
addParametersAndMasks(parameterTypes, desc, false)
return methodOwner.lookupMethod(name + JvmAbi.DEFAULT_PARAMS_IMPL_SUFFIX, parameterTypes, loadReturnType(desc), isPublic)
return methodOwner.lookupMethod(name + JvmAbi.DEFAULT_PARAMS_IMPL_SUFFIX, parameterTypes, loadReturnType(desc))
}
fun findConstructorBySignature(desc: String, isPublic: Boolean): Constructor<*>? {
return jClass.tryGetConstructor(loadParameterTypes(desc), declared = !isPublic)
}
fun findConstructorBySignature(desc: String): Constructor<*>? =
jClass.tryGetConstructor(loadParameterTypes(desc))
fun findDefaultConstructor(desc: String, isPublic: Boolean): Constructor<*>? {
val parameterTypes = arrayListOf<Class<*>>()
addParametersAndMasks(parameterTypes, desc, true)
return jClass.tryGetConstructor(parameterTypes, declared = !isPublic)
}
fun findDefaultConstructor(desc: String): Constructor<*>? =
jClass.tryGetConstructor(arrayListOf<Class<*>>().also { parameterTypes ->
addParametersAndMasks(parameterTypes, desc, true)
})
private fun addParametersAndMasks(result: MutableList<Class<*>>, desc: String, isConstructor: Boolean) {
val valueParameters = loadParameterTypes(desc)
@@ -63,10 +63,9 @@ internal class KFunctionImpl private constructor(
is KotlinConstructor -> {
if (isAnnotationConstructor)
return@caller AnnotationConstructorCaller(container.jClass, parameters.map { it.name!! }, POSITIONAL_CALL, KOTLIN)
container.findConstructorBySignature(jvmSignature.constructorDesc, descriptor.isPublicInBytecode)
container.findConstructorBySignature(jvmSignature.constructorDesc)
}
is KotlinFunction ->
container.findMethodBySignature(jvmSignature.methodName, jvmSignature.methodDesc, descriptor.isPublicInBytecode)
is KotlinFunction -> container.findMethodBySignature(jvmSignature.methodName, jvmSignature.methodDesc)
is JavaMethod -> jvmSignature.method
is JavaConstructor -> jvmSignature.constructor
is FakeJavaAnnotationConstructor -> {
@@ -94,15 +93,12 @@ internal class KFunctionImpl private constructor(
val jvmSignature = RuntimeTypeMapper.mapSignature(descriptor)
val member: Member? = when (jvmSignature) {
is KotlinFunction -> {
container.findDefaultMethod(
jvmSignature.methodName, jvmSignature.methodDesc,
!Modifier.isStatic(caller.member!!.modifiers), descriptor.isPublicInBytecode
)
container.findDefaultMethod(jvmSignature.methodName, jvmSignature.methodDesc, !Modifier.isStatic(caller.member!!.modifiers))
}
is KotlinConstructor -> {
if (isAnnotationConstructor)
return@defaultCaller AnnotationConstructorCaller(container.jClass, parameters.map { it.name!! }, CALL_BY_NAME, KOTLIN)
container.findDefaultConstructor(jvmSignature.constructorDesc, descriptor.isPublicInBytecode)
container.findDefaultConstructor(jvmSignature.constructorDesc)
}
is FakeJavaAnnotationConstructor -> {
val methods = jvmSignature.methods
@@ -221,8 +221,7 @@ private fun KPropertyImpl.Accessor<*, *>.computeCallerForAccessor(isGetter: Bool
val accessor = accessorSignature?.let { signature ->
property.container.findMethodBySignature(
jvmSignature.nameResolver.getString(signature.name),
jvmSignature.nameResolver.getString(signature.desc),
descriptor.isPublicInBytecode
jvmSignature.nameResolver.getString(signature.desc)
)
}
@@ -257,10 +256,9 @@ private fun KPropertyImpl.Accessor<*, *>.computeCallerForAccessor(isGetter: Bool
val signature =
if (isGetter) jvmSignature.getterSignature
else (jvmSignature.setterSignature ?: throw KotlinReflectionInternalError("No setter found for property $property"))
val accessor = property.container.findMethodBySignature(
signature.methodName, signature.methodDesc, descriptor.isPublicInBytecode
) ?: throw KotlinReflectionInternalError("No accessor found for property $property")
val accessor =
property.container.findMethodBySignature(signature.methodName, signature.methodDesc)
?: throw KotlinReflectionInternalError("No accessor found for property $property")
assert(!Modifier.isStatic(accessor.modifiers)) { "Mapped property cannot have a static accessor: $property" }
return if (isBound) CallerImpl.Method.BoundInstance(accessor, boundReceiver)
@@ -20,7 +20,6 @@ import org.jetbrains.kotlin.builtins.jvm.JavaToKotlinClassMap
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.annotations.Annotated
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
import org.jetbrains.kotlin.descriptors.annotations.isEffectivelyInlineOnly
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.load.kotlin.KotlinJvmBinarySourceElement
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader
@@ -187,12 +186,6 @@ internal val ReflectKotlinClass.packageModuleName: String?
}
}
internal val CallableMemberDescriptor.isPublicInBytecode: Boolean
get() {
val visibility = visibility
return (visibility == Visibilities.PUBLIC || visibility == Visibilities.INTERNAL) && !isEffectivelyInlineOnly()
}
internal val CallableDescriptor.instanceReceiverParameter: ReceiverParameterDescriptor?
get() =
if (dispatchReceiverParameter != null) (containingDeclaration as ClassDescriptor).thisAsReceiverParameter