Fix KCallable.call for protected members from base class
Class.getMethod does not return protected methods from super class, so we invoke getDeclaredMethod on each super class manually instead #KT-18480 Fixed
This commit is contained in:
@@ -0,0 +1,35 @@
|
|||||||
|
// IGNORE_BACKEND: JS, NATIVE
|
||||||
|
// WITH_REFLECT
|
||||||
|
|
||||||
|
import kotlin.reflect.*
|
||||||
|
import kotlin.reflect.jvm.*
|
||||||
|
import kotlin.test.*
|
||||||
|
|
||||||
|
abstract class Base {
|
||||||
|
protected val protectedVal: String
|
||||||
|
get() = "1"
|
||||||
|
|
||||||
|
var publicVarProtectedSet: String = ""
|
||||||
|
protected set
|
||||||
|
|
||||||
|
protected fun protectedFun(): String = "3"
|
||||||
|
}
|
||||||
|
|
||||||
|
class Derived : Base()
|
||||||
|
|
||||||
|
fun member(name: String): KCallable<*> = Derived::class.members.single { it.name == name }.apply { isAccessible = true }
|
||||||
|
|
||||||
|
fun box(): String {
|
||||||
|
val a = Derived()
|
||||||
|
|
||||||
|
assertEquals("1", member("protectedVal").call(a))
|
||||||
|
|
||||||
|
val publicVarProtectedSet = member("publicVarProtectedSet") as KMutableProperty1<Derived, String>
|
||||||
|
publicVarProtectedSet.setter.call(a, "2")
|
||||||
|
assertEquals("2", publicVarProtectedSet.getter.call(a))
|
||||||
|
assertEquals("2", publicVarProtectedSet.call(a))
|
||||||
|
|
||||||
|
assertEquals("3", member("protectedFun").call(a))
|
||||||
|
|
||||||
|
return "OK"
|
||||||
|
}
|
||||||
+6
@@ -14008,6 +14008,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("protectedMembers.kt")
|
||||||
|
public void testProtectedMembers() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/call/protectedMembers.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");
|
||||||
|
|||||||
@@ -14008,6 +14008,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("protectedMembers.kt")
|
||||||
|
public void testProtectedMembers() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/call/protectedMembers.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");
|
||||||
|
|||||||
@@ -14008,6 +14008,12 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("protectedMembers.kt")
|
||||||
|
public void testProtectedMembers() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/call/protectedMembers.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");
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import org.jetbrains.kotlin.name.Name
|
|||||||
import org.jetbrains.kotlin.resolve.scopes.MemberScope
|
import org.jetbrains.kotlin.resolve.scopes.MemberScope
|
||||||
import java.lang.reflect.Constructor
|
import java.lang.reflect.Constructor
|
||||||
import java.lang.reflect.Method
|
import java.lang.reflect.Method
|
||||||
import java.util.*
|
|
||||||
import kotlin.jvm.internal.ClassBasedDeclarationContainer
|
import kotlin.jvm.internal.ClassBasedDeclarationContainer
|
||||||
|
|
||||||
internal abstract class KDeclarationContainerImpl : ClassBasedDeclarationContainer {
|
internal abstract class KDeclarationContainerImpl : ClassBasedDeclarationContainer {
|
||||||
@@ -151,10 +150,28 @@ internal abstract class KDeclarationContainerImpl : ClassBasedDeclarationContain
|
|||||||
return functions.single()
|
return functions.single()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Class<*>.tryGetMethod(name: String, parameterTypes: List<Class<*>>, returnType: Class<*>, declared: Boolean): Method? =
|
private fun Class<*>.lookupMethod(name: String, parameterTypes: List<Class<*>>, 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 (isPublic) {
|
||||||
|
return tryGetMethod(name, parametersArray, returnType, declared = false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Class<*>.tryGetMethod(name: String, parameterTypes: Array<Class<*>>, returnType: Class<*>, declared: Boolean): Method? =
|
||||||
try {
|
try {
|
||||||
val parametersArray = parameterTypes.toTypedArray()
|
val result = if (declared) getDeclaredMethod(name, *parameterTypes) else getMethod(name, *parameterTypes)
|
||||||
val result = if (declared) getDeclaredMethod(name, *parametersArray) else getMethod(name, *parametersArray)
|
|
||||||
|
|
||||||
if (result.returnType == returnType) result
|
if (result.returnType == returnType) result
|
||||||
else {
|
else {
|
||||||
@@ -166,7 +183,7 @@ internal abstract class KDeclarationContainerImpl : ClassBasedDeclarationContain
|
|||||||
allMethods.firstOrNull { method ->
|
allMethods.firstOrNull { method ->
|
||||||
method.name == name &&
|
method.name == name &&
|
||||||
method.returnType == returnType &&
|
method.returnType == returnType &&
|
||||||
Arrays.equals(method.parameterTypes, parametersArray)
|
method.parameterTypes.contentEquals(parameterTypes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -174,7 +191,7 @@ internal abstract class KDeclarationContainerImpl : ClassBasedDeclarationContain
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Class<*>.tryGetConstructor(parameterTypes: List<Class<*>>, declared: Boolean) =
|
private fun Class<*>.tryGetConstructor(parameterTypes: List<Class<*>>, declared: Boolean): Constructor<*>? =
|
||||||
try {
|
try {
|
||||||
if (declared) getDeclaredConstructor(*parameterTypes.toTypedArray())
|
if (declared) getDeclaredConstructor(*parameterTypes.toTypedArray())
|
||||||
else getConstructor(*parameterTypes.toTypedArray())
|
else getConstructor(*parameterTypes.toTypedArray())
|
||||||
@@ -183,13 +200,13 @@ internal abstract class KDeclarationContainerImpl : ClassBasedDeclarationContain
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun findMethodBySignature(name: String, desc: String, declared: Boolean): Method? {
|
fun findMethodBySignature(name: String, desc: String, isPublic: Boolean): Method? {
|
||||||
if (name == "<init>") return null
|
if (name == "<init>") return null
|
||||||
|
|
||||||
return methodOwner.tryGetMethod(name, loadParameterTypes(desc), loadReturnType(desc), declared)
|
return methodOwner.lookupMethod(name, loadParameterTypes(desc), loadReturnType(desc), isPublic)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun findDefaultMethod(name: String, desc: String, isMember: Boolean, declared: Boolean): Method? {
|
fun findDefaultMethod(name: String, desc: String, isMember: Boolean, isPublic: Boolean): Method? {
|
||||||
if (name == "<init>") return null
|
if (name == "<init>") return null
|
||||||
|
|
||||||
val parameterTypes = arrayListOf<Class<*>>()
|
val parameterTypes = arrayListOf<Class<*>>()
|
||||||
@@ -198,18 +215,18 @@ internal abstract class KDeclarationContainerImpl : ClassBasedDeclarationContain
|
|||||||
}
|
}
|
||||||
addParametersAndMasks(parameterTypes, desc, false)
|
addParametersAndMasks(parameterTypes, desc, false)
|
||||||
|
|
||||||
return methodOwner.tryGetMethod(name + JvmAbi.DEFAULT_PARAMS_IMPL_SUFFIX, parameterTypes, loadReturnType(desc), declared)
|
return methodOwner.lookupMethod(name + JvmAbi.DEFAULT_PARAMS_IMPL_SUFFIX, parameterTypes, loadReturnType(desc), isPublic)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun findConstructorBySignature(desc: String, declared: Boolean): Constructor<*>? {
|
fun findConstructorBySignature(desc: String, isPublic: Boolean): Constructor<*>? {
|
||||||
return jClass.tryGetConstructor(loadParameterTypes(desc), declared)
|
return jClass.tryGetConstructor(loadParameterTypes(desc), declared = !isPublic)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun findDefaultConstructor(desc: String, declared: Boolean): Constructor<*>? {
|
fun findDefaultConstructor(desc: String, isPublic: Boolean): Constructor<*>? {
|
||||||
val parameterTypes = arrayListOf<Class<*>>()
|
val parameterTypes = arrayListOf<Class<*>>()
|
||||||
addParametersAndMasks(parameterTypes, desc, true)
|
addParametersAndMasks(parameterTypes, desc, true)
|
||||||
|
|
||||||
return jClass.tryGetConstructor(parameterTypes, declared)
|
return jClass.tryGetConstructor(parameterTypes, declared = !isPublic)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addParametersAndMasks(result: MutableList<Class<*>>, desc: String, isConstructor: Boolean) {
|
private fun addParametersAndMasks(result: MutableList<Class<*>>, desc: String, isConstructor: Boolean) {
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ package kotlin.reflect.jvm.internal
|
|||||||
|
|
||||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||||
import org.jetbrains.kotlin.descriptors.Visibilities
|
|
||||||
import org.jetbrains.kotlin.descriptors.annotations.isEffectivelyInlineOnly
|
|
||||||
import java.lang.reflect.Constructor
|
import java.lang.reflect.Constructor
|
||||||
import java.lang.reflect.Member
|
import java.lang.reflect.Member
|
||||||
import java.lang.reflect.Method
|
import java.lang.reflect.Method
|
||||||
@@ -58,21 +56,16 @@ internal class KFunctionImpl private constructor(
|
|||||||
|
|
||||||
override val name: String get() = descriptor.name.asString()
|
override val name: String get() = descriptor.name.asString()
|
||||||
|
|
||||||
private fun isPrivateInBytecode(): Boolean =
|
|
||||||
Visibilities.isPrivate(descriptor.visibility) ||
|
|
||||||
descriptor.isEffectivelyInlineOnly()
|
|
||||||
|
|
||||||
private fun isDeclared(): Boolean = isPrivateInBytecode()
|
|
||||||
|
|
||||||
override val caller: FunctionCaller<*> by ReflectProperties.lazySoft caller@ {
|
override val caller: FunctionCaller<*> by ReflectProperties.lazySoft caller@ {
|
||||||
val jvmSignature = RuntimeTypeMapper.mapSignature(descriptor)
|
val jvmSignature = RuntimeTypeMapper.mapSignature(descriptor)
|
||||||
val member: Member? = when (jvmSignature) {
|
val member: Member? = when (jvmSignature) {
|
||||||
is KotlinConstructor -> {
|
is KotlinConstructor -> {
|
||||||
if (isAnnotationConstructor)
|
if (isAnnotationConstructor)
|
||||||
return@caller AnnotationConstructorCaller(container.jClass, parameters.map { it.name!! }, POSITIONAL_CALL, KOTLIN)
|
return@caller AnnotationConstructorCaller(container.jClass, parameters.map { it.name!! }, POSITIONAL_CALL, KOTLIN)
|
||||||
container.findConstructorBySignature(jvmSignature.constructorDesc, isDeclared())
|
container.findConstructorBySignature(jvmSignature.constructorDesc, descriptor.isPublicInBytecode)
|
||||||
}
|
}
|
||||||
is KotlinFunction -> container.findMethodBySignature(jvmSignature.methodName, jvmSignature.methodDesc, isDeclared())
|
is KotlinFunction ->
|
||||||
|
container.findMethodBySignature(jvmSignature.methodName, jvmSignature.methodDesc, descriptor.isPublicInBytecode)
|
||||||
is JavaMethod -> jvmSignature.method
|
is JavaMethod -> jvmSignature.method
|
||||||
is JavaConstructor -> jvmSignature.constructor
|
is JavaConstructor -> jvmSignature.constructor
|
||||||
is FakeJavaAnnotationConstructor -> {
|
is FakeJavaAnnotationConstructor -> {
|
||||||
@@ -102,12 +95,12 @@ internal class KFunctionImpl private constructor(
|
|||||||
val member: Member? = when (jvmSignature) {
|
val member: Member? = when (jvmSignature) {
|
||||||
is KotlinFunction -> {
|
is KotlinFunction -> {
|
||||||
container.findDefaultMethod(jvmSignature.methodName, jvmSignature.methodDesc,
|
container.findDefaultMethod(jvmSignature.methodName, jvmSignature.methodDesc,
|
||||||
!Modifier.isStatic(caller.member!!.modifiers), isDeclared())
|
!Modifier.isStatic(caller.member!!.modifiers), descriptor.isPublicInBytecode)
|
||||||
}
|
}
|
||||||
is KotlinConstructor -> {
|
is KotlinConstructor -> {
|
||||||
if (isAnnotationConstructor)
|
if (isAnnotationConstructor)
|
||||||
return@defaultCaller AnnotationConstructorCaller(container.jClass, parameters.map { it.name!! }, CALL_BY_NAME, KOTLIN)
|
return@defaultCaller AnnotationConstructorCaller(container.jClass, parameters.map { it.name!! }, CALL_BY_NAME, KOTLIN)
|
||||||
container.findDefaultConstructor(jvmSignature.constructorDesc, isDeclared())
|
container.findDefaultConstructor(jvmSignature.constructorDesc, descriptor.isPublicInBytecode)
|
||||||
}
|
}
|
||||||
is FakeJavaAnnotationConstructor -> {
|
is FakeJavaAnnotationConstructor -> {
|
||||||
val methods = jvmSignature.methods
|
val methods = jvmSignature.methods
|
||||||
|
|||||||
@@ -235,12 +235,15 @@ private fun KPropertyImpl.Accessor<*, *>.computeCallerForAccessor(isGetter: Bool
|
|||||||
property.container.findMethodBySignature(
|
property.container.findMethodBySignature(
|
||||||
jvmSignature.nameResolver.getString(signature.name),
|
jvmSignature.nameResolver.getString(signature.name),
|
||||||
jvmSignature.nameResolver.getString(signature.desc),
|
jvmSignature.nameResolver.getString(signature.desc),
|
||||||
Visibilities.isPrivate(descriptor.visibility)
|
descriptor.isPublicInBytecode
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
when {
|
when {
|
||||||
accessor == null -> computeFieldCaller(property.javaField!!)
|
accessor == null -> computeFieldCaller(
|
||||||
|
property.javaField
|
||||||
|
?: throw KotlinReflectionInternalError("No accessors or field is found for property $property")
|
||||||
|
)
|
||||||
!Modifier.isStatic(accessor.modifiers) ->
|
!Modifier.isStatic(accessor.modifiers) ->
|
||||||
if (isBound) FunctionCaller.BoundInstanceMethod(accessor, property.boundReceiver)
|
if (isBound) FunctionCaller.BoundInstanceMethod(accessor, property.boundReceiver)
|
||||||
else FunctionCaller.InstanceMethod(accessor)
|
else FunctionCaller.InstanceMethod(accessor)
|
||||||
|
|||||||
@@ -16,10 +16,12 @@
|
|||||||
|
|
||||||
package kotlin.reflect.jvm.internal
|
package kotlin.reflect.jvm.internal
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
|
||||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||||
import org.jetbrains.kotlin.descriptors.Visibilities
|
import org.jetbrains.kotlin.descriptors.Visibilities
|
||||||
import org.jetbrains.kotlin.descriptors.Visibility
|
import org.jetbrains.kotlin.descriptors.Visibility
|
||||||
import org.jetbrains.kotlin.descriptors.annotations.Annotated
|
import org.jetbrains.kotlin.descriptors.annotations.Annotated
|
||||||
|
import org.jetbrains.kotlin.descriptors.annotations.isEffectivelyInlineOnly
|
||||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||||
import org.jetbrains.kotlin.load.java.components.RuntimeSourceElementFactory
|
import org.jetbrains.kotlin.load.java.components.RuntimeSourceElementFactory
|
||||||
import org.jetbrains.kotlin.load.java.reflect.tryLoadClass
|
import org.jetbrains.kotlin.load.java.reflect.tryLoadClass
|
||||||
@@ -145,3 +147,8 @@ internal val ReflectKotlinClass.packageModuleName: String?
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal val CallableMemberDescriptor.isPublicInBytecode: Boolean
|
||||||
|
get() {
|
||||||
|
val visibility = visibility
|
||||||
|
return (visibility == Visibilities.PUBLIC || visibility == Visibilities.INTERNAL) && !isEffectivelyInlineOnly()
|
||||||
|
}
|
||||||
|
|||||||
+12
@@ -15988,6 +15988,18 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
|
|||||||
throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that.");
|
throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("protectedMembers.kt")
|
||||||
|
public void testProtectedMembers() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/call/protectedMembers.kt");
|
||||||
|
try {
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
catch (Throwable ignore) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that.");
|
||||||
|
}
|
||||||
|
|
||||||
@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");
|
||||||
|
|||||||
Reference in New Issue
Block a user