Skip to content

Commit 6388c18

Browse files
committed
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
1 parent a25aa2f commit 6388c18

File tree

9 files changed

+113
-28
lines changed

9 files changed

+113
-28
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// IGNORE_BACKEND: JS, NATIVE
2+
// WITH_REFLECT
3+
4+
import kotlin.reflect.*
5+
import kotlin.reflect.jvm.*
6+
import kotlin.test.*
7+
8+
abstract class Base {
9+
protected val protectedVal: String
10+
get() = "1"
11+
12+
var publicVarProtectedSet: String = ""
13+
protected set
14+
15+
protected fun protectedFun(): String = "3"
16+
}
17+
18+
class Derived : Base()
19+
20+
fun member(name: String): KCallable<*> = Derived::class.members.single { it.name == name }.apply { isAccessible = true }
21+
22+
fun box(): String {
23+
val a = Derived()
24+
25+
assertEquals("1", member("protectedVal").call(a))
26+
27+
val publicVarProtectedSet = member("publicVarProtectedSet") as KMutableProperty1<Derived, String>
28+
publicVarProtectedSet.setter.call(a, "2")
29+
assertEquals("2", publicVarProtectedSet.getter.call(a))
30+
assertEquals("2", publicVarProtectedSet.call(a))
31+
32+
assertEquals("3", member("protectedFun").call(a))
33+
34+
return "OK"
35+
}

compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14008,6 +14008,12 @@ public void testPropertyGetterAndGetFunctionDifferentReturnType() throws Excepti
1400814008
doTest(fileName);
1400914009
}
1401014010

14011+
@TestMetadata("protectedMembers.kt")
14012+
public void testProtectedMembers() throws Exception {
14013+
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/call/protectedMembers.kt");
14014+
doTest(fileName);
14015+
}
14016+
1401114017
@TestMetadata("returnUnit.kt")
1401214018
public void testReturnUnit() throws Exception {
1401314019
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/call/returnUnit.kt");

compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14008,6 +14008,12 @@ public void testPropertyGetterAndGetFunctionDifferentReturnType() throws Excepti
1400814008
doTest(fileName);
1400914009
}
1401014010

14011+
@TestMetadata("protectedMembers.kt")
14012+
public void testProtectedMembers() throws Exception {
14013+
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/call/protectedMembers.kt");
14014+
doTest(fileName);
14015+
}
14016+
1401114017
@TestMetadata("returnUnit.kt")
1401214018
public void testReturnUnit() throws Exception {
1401314019
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/call/returnUnit.kt");

compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14008,6 +14008,12 @@ public void testPropertyGetterAndGetFunctionDifferentReturnType() throws Excepti
1400814008
doTest(fileName);
1400914009
}
1401014010

14011+
@TestMetadata("protectedMembers.kt")
14012+
public void testProtectedMembers() throws Exception {
14013+
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/call/protectedMembers.kt");
14014+
doTest(fileName);
14015+
}
14016+
1401114017
@TestMetadata("returnUnit.kt")
1401214018
public void testReturnUnit() throws Exception {
1401314019
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/call/returnUnit.kt");

core/reflection.jvm/src/kotlin/reflect/jvm/internal/KDeclarationContainerImpl.kt

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ import org.jetbrains.kotlin.name.Name
2626
import org.jetbrains.kotlin.resolve.scopes.MemberScope
2727
import java.lang.reflect.Constructor
2828
import java.lang.reflect.Method
29-
import java.util.*
3029
import kotlin.jvm.internal.ClassBasedDeclarationContainer
3130

3231
internal abstract class KDeclarationContainerImpl : ClassBasedDeclarationContainer {
@@ -151,10 +150,28 @@ internal abstract class KDeclarationContainerImpl : ClassBasedDeclarationContain
151150
return functions.single()
152151
}
153152

154-
private fun Class<*>.tryGetMethod(name: String, parameterTypes: List<Class<*>>, returnType: Class<*>, declared: Boolean): Method? =
153+
private fun Class<*>.lookupMethod(name: String, parameterTypes: List<Class<*>>, returnType: Class<*>, isPublic: Boolean): Method? {
154+
val parametersArray = parameterTypes.toTypedArray()
155+
156+
// If we're looking for a public method, just use Java reflection's getMethod/getMethods
157+
if (isPublic) {
158+
return tryGetMethod(name, parametersArray, returnType, declared = false)
159+
}
160+
161+
// 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
162+
var klass: Class<*>? = this
163+
while (klass != null) {
164+
val method = klass.tryGetMethod(name, parametersArray, returnType, declared = true)
165+
if (method != null) return method
166+
klass = klass.superclass
167+
}
168+
169+
return null
170+
}
171+
172+
private fun Class<*>.tryGetMethod(name: String, parameterTypes: Array<Class<*>>, returnType: Class<*>, declared: Boolean): Method? =
155173
try {
156-
val parametersArray = parameterTypes.toTypedArray()
157-
val result = if (declared) getDeclaredMethod(name, *parametersArray) else getMethod(name, *parametersArray)
174+
val result = if (declared) getDeclaredMethod(name, *parameterTypes) else getMethod(name, *parameterTypes)
158175

159176
if (result.returnType == returnType) result
160177
else {
@@ -166,15 +183,15 @@ internal abstract class KDeclarationContainerImpl : ClassBasedDeclarationContain
166183
allMethods.firstOrNull { method ->
167184
method.name == name &&
168185
method.returnType == returnType &&
169-
Arrays.equals(method.parameterTypes, parametersArray)
186+
method.parameterTypes.contentEquals(parameterTypes)
170187
}
171188
}
172189
}
173190
catch (e: NoSuchMethodException) {
174191
null
175192
}
176193

177-
private fun Class<*>.tryGetConstructor(parameterTypes: List<Class<*>>, declared: Boolean) =
194+
private fun Class<*>.tryGetConstructor(parameterTypes: List<Class<*>>, declared: Boolean): Constructor<*>? =
178195
try {
179196
if (declared) getDeclaredConstructor(*parameterTypes.toTypedArray())
180197
else getConstructor(*parameterTypes.toTypedArray())
@@ -183,13 +200,13 @@ internal abstract class KDeclarationContainerImpl : ClassBasedDeclarationContain
183200
null
184201
}
185202

186-
fun findMethodBySignature(name: String, desc: String, declared: Boolean): Method? {
203+
fun findMethodBySignature(name: String, desc: String, isPublic: Boolean): Method? {
187204
if (name == "<init>") return null
188205

189-
return methodOwner.tryGetMethod(name, loadParameterTypes(desc), loadReturnType(desc), declared)
206+
return methodOwner.lookupMethod(name, loadParameterTypes(desc), loadReturnType(desc), isPublic)
190207
}
191208

192-
fun findDefaultMethod(name: String, desc: String, isMember: Boolean, declared: Boolean): Method? {
209+
fun findDefaultMethod(name: String, desc: String, isMember: Boolean, isPublic: Boolean): Method? {
193210
if (name == "<init>") return null
194211

195212
val parameterTypes = arrayListOf<Class<*>>()
@@ -198,18 +215,18 @@ internal abstract class KDeclarationContainerImpl : ClassBasedDeclarationContain
198215
}
199216
addParametersAndMasks(parameterTypes, desc, false)
200217

201-
return methodOwner.tryGetMethod(name + JvmAbi.DEFAULT_PARAMS_IMPL_SUFFIX, parameterTypes, loadReturnType(desc), declared)
218+
return methodOwner.lookupMethod(name + JvmAbi.DEFAULT_PARAMS_IMPL_SUFFIX, parameterTypes, loadReturnType(desc), isPublic)
202219
}
203220

204-
fun findConstructorBySignature(desc: String, declared: Boolean): Constructor<*>? {
205-
return jClass.tryGetConstructor(loadParameterTypes(desc), declared)
221+
fun findConstructorBySignature(desc: String, isPublic: Boolean): Constructor<*>? {
222+
return jClass.tryGetConstructor(loadParameterTypes(desc), declared = !isPublic)
206223
}
207224

208-
fun findDefaultConstructor(desc: String, declared: Boolean): Constructor<*>? {
225+
fun findDefaultConstructor(desc: String, isPublic: Boolean): Constructor<*>? {
209226
val parameterTypes = arrayListOf<Class<*>>()
210227
addParametersAndMasks(parameterTypes, desc, true)
211228

212-
return jClass.tryGetConstructor(parameterTypes, declared)
229+
return jClass.tryGetConstructor(parameterTypes, declared = !isPublic)
213230
}
214231

215232
private fun addParametersAndMasks(result: MutableList<Class<*>>, desc: String, isConstructor: Boolean) {

core/reflection.jvm/src/kotlin/reflect/jvm/internal/KFunctionImpl.kt

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@ package kotlin.reflect.jvm.internal
1818

1919
import org.jetbrains.kotlin.descriptors.ClassDescriptor
2020
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
21-
import org.jetbrains.kotlin.descriptors.Visibilities
22-
import org.jetbrains.kotlin.descriptors.annotations.isEffectivelyInlineOnly
2321
import java.lang.reflect.Constructor
2422
import java.lang.reflect.Member
2523
import java.lang.reflect.Method
@@ -58,21 +56,16 @@ internal class KFunctionImpl private constructor(
5856

5957
override val name: String get() = descriptor.name.asString()
6058

61-
private fun isPrivateInBytecode(): Boolean =
62-
Visibilities.isPrivate(descriptor.visibility) ||
63-
descriptor.isEffectivelyInlineOnly()
64-
65-
private fun isDeclared(): Boolean = isPrivateInBytecode()
66-
6759
override val caller: FunctionCaller<*> by ReflectProperties.lazySoft caller@ {
6860
val jvmSignature = RuntimeTypeMapper.mapSignature(descriptor)
6961
val member: Member? = when (jvmSignature) {
7062
is KotlinConstructor -> {
7163
if (isAnnotationConstructor)
7264
return@caller AnnotationConstructorCaller(container.jClass, parameters.map { it.name!! }, POSITIONAL_CALL, KOTLIN)
73-
container.findConstructorBySignature(jvmSignature.constructorDesc, isDeclared())
65+
container.findConstructorBySignature(jvmSignature.constructorDesc, descriptor.isPublicInBytecode)
7466
}
75-
is KotlinFunction -> container.findMethodBySignature(jvmSignature.methodName, jvmSignature.methodDesc, isDeclared())
67+
is KotlinFunction ->
68+
container.findMethodBySignature(jvmSignature.methodName, jvmSignature.methodDesc, descriptor.isPublicInBytecode)
7669
is JavaMethod -> jvmSignature.method
7770
is JavaConstructor -> jvmSignature.constructor
7871
is FakeJavaAnnotationConstructor -> {
@@ -102,12 +95,12 @@ internal class KFunctionImpl private constructor(
10295
val member: Member? = when (jvmSignature) {
10396
is KotlinFunction -> {
10497
container.findDefaultMethod(jvmSignature.methodName, jvmSignature.methodDesc,
105-
!Modifier.isStatic(caller.member!!.modifiers), isDeclared())
98+
!Modifier.isStatic(caller.member!!.modifiers), descriptor.isPublicInBytecode)
10699
}
107100
is KotlinConstructor -> {
108101
if (isAnnotationConstructor)
109102
return@defaultCaller AnnotationConstructorCaller(container.jClass, parameters.map { it.name!! }, CALL_BY_NAME, KOTLIN)
110-
container.findDefaultConstructor(jvmSignature.constructorDesc, isDeclared())
103+
container.findDefaultConstructor(jvmSignature.constructorDesc, descriptor.isPublicInBytecode)
111104
}
112105
is FakeJavaAnnotationConstructor -> {
113106
val methods = jvmSignature.methods

core/reflection.jvm/src/kotlin/reflect/jvm/internal/KPropertyImpl.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -235,12 +235,15 @@ private fun KPropertyImpl.Accessor<*, *>.computeCallerForAccessor(isGetter: Bool
235235
property.container.findMethodBySignature(
236236
jvmSignature.nameResolver.getString(signature.name),
237237
jvmSignature.nameResolver.getString(signature.desc),
238-
Visibilities.isPrivate(descriptor.visibility)
238+
descriptor.isPublicInBytecode
239239
)
240240
}
241241

242242
when {
243-
accessor == null -> computeFieldCaller(property.javaField!!)
243+
accessor == null -> computeFieldCaller(
244+
property.javaField
245+
?: throw KotlinReflectionInternalError("No accessors or field is found for property $property")
246+
)
244247
!Modifier.isStatic(accessor.modifiers) ->
245248
if (isBound) FunctionCaller.BoundInstanceMethod(accessor, property.boundReceiver)
246249
else FunctionCaller.InstanceMethod(accessor)

core/reflection.jvm/src/kotlin/reflect/jvm/internal/util.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@
1616

1717
package kotlin.reflect.jvm.internal
1818

19+
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
1920
import org.jetbrains.kotlin.descriptors.ClassDescriptor
2021
import org.jetbrains.kotlin.descriptors.Visibilities
2122
import org.jetbrains.kotlin.descriptors.Visibility
2223
import org.jetbrains.kotlin.descriptors.annotations.Annotated
24+
import org.jetbrains.kotlin.descriptors.annotations.isEffectivelyInlineOnly
2325
import org.jetbrains.kotlin.load.java.JvmAbi
2426
import org.jetbrains.kotlin.load.java.components.RuntimeSourceElementFactory
2527
import org.jetbrains.kotlin.load.java.reflect.tryLoadClass
@@ -145,3 +147,8 @@ internal val ReflectKotlinClass.packageModuleName: String?
145147
}
146148
}
147149

150+
internal val CallableMemberDescriptor.isPublicInBytecode: Boolean
151+
get() {
152+
val visibility = visibility
153+
return (visibility == Visibilities.PUBLIC || visibility == Visibilities.INTERNAL) && !isEffectivelyInlineOnly()
154+
}

js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15988,6 +15988,18 @@ public void testPropertyGetterAndGetFunctionDifferentReturnType() throws Excepti
1598815988
throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that.");
1598915989
}
1599015990

15991+
@TestMetadata("protectedMembers.kt")
15992+
public void testProtectedMembers() throws Exception {
15993+
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/call/protectedMembers.kt");
15994+
try {
15995+
doTest(fileName);
15996+
}
15997+
catch (Throwable ignore) {
15998+
return;
15999+
}
16000+
throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that.");
16001+
}
16002+
1599116003
@TestMetadata("returnUnit.kt")
1599216004
public void testReturnUnit() throws Exception {
1599316005
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/call/returnUnit.kt");

0 commit comments

Comments
 (0)