Report argument number mismatch nicely on wrong 'call()' invocation

The default Java reflection message in such case is not very helpful: "wrong
number of arguments"
This commit is contained in:
Alexander Udalov
2015-08-04 19:39:09 +03:00
parent 9c522b4d49
commit 3091f2af3b
4 changed files with 174 additions and 45 deletions
@@ -0,0 +1,98 @@
import kotlin.platform.platformStatic as static
import kotlin.reflect.jvm.isAccessible
import kotlin.reflect.KCallable
var foo: String = ""
class A(private var bar: String = "") {
fun getBar() = A::bar
}
object O {
private static var baz: String = ""
static fun getBaz() = O::baz.apply { isAccessible = true }
}
fun check(callable: KCallable<*>, vararg args: Any?) {
val expected = callable.parameters.size()
val actual = args.size()
if (expected == actual) {
throw AssertionError("Bad test case: expected and actual number of arguments should differ (was $expected vs $actual)")
}
val expectedExceptionMessage = "Callable expects $expected arguments, but $actual were provided."
try {
callable.call(*args)
throw AssertionError("Fail: an IllegalArgumentException should have been thrown")
} catch (e: IllegalArgumentException) {
if (e.getMessage() != expectedExceptionMessage) {
// This most probably means that we don't check number of passed arguments in reflection
// and the default check from Java reflection yields an IllegalArgumentException, but with a not that helpful message
throw AssertionError("Fail: an exception with an unrecognized message was thrown: \"${e.getMessage()}\"" +
"\nExpected message was: $expectedExceptionMessage")
}
}
}
fun box(): String {
check(::box, null)
check(::box, "")
check(::A)
check(::A, null, "")
check(O::getBaz)
check(O::getBaz, null, "")
val f = ::foo
check(f, null)
check(f, null, null)
check(f, arrayOf<Any?>(null))
check(f, "")
check(f.getter, null)
check(f.getter, null, null)
check(f.getter, arrayOf<Any?>(null))
check(f.getter, "")
check(f.setter)
check(f.setter, null, null)
check(f.setter, null, "")
val b = A().getBar()
check(b)
check(b, null, null)
check(b, "", "")
check(b.getter)
check(b.getter, null, null)
check(b.getter, "", "")
check(b.setter)
check(b.setter, null)
check(b.setter, "")
val z = O.getBaz()
check(z)
check(z, null, null)
check(z, "", "")
check(z.getter)
check(z.getter, null, null)
check(z.getter, "", "")
check(z.setter)
check(z.setter, null)
check(z.setter, "")
return "OK"
}
@@ -2856,6 +2856,12 @@ public class BlackBoxWithStdlibCodegenTestGenerated extends AbstractBlackBoxCode
doTestWithStdlib(fileName);
}
@TestMetadata("incorrectNumberOfArguments.kt")
public void testIncorrectNumberOfArguments() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxWithStdlib/reflection/call/incorrectNumberOfArguments.kt");
doTestWithStdlib(fileName);
}
@TestMetadata("innerClassConstructor.kt")
public void testInnerClassConstructor() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxWithStdlib/reflection/call/innerClassConstructor.kt");
@@ -19,28 +19,40 @@ package kotlin.reflect.jvm.internal
import java.lang.reflect.Member
import java.lang.reflect.Constructor as ReflectConstructor
import java.lang.reflect.Field as ReflectField
import java.lang.reflect.Member as ReflectMember
import java.lang.reflect.Method as ReflectMethod
internal sealed class FunctionCaller {
abstract val member: ReflectMember
internal sealed class FunctionCaller(
private val expectedArgumentNumber: Int
) {
abstract val member: Member
abstract fun call(args: Array<*>): Any?
protected open fun checkArguments(args: Array<*>) {
if (expectedArgumentNumber != args.size()) {
throw IllegalArgumentException("Callable expects $expectedArgumentNumber arguments, but ${args.size()} were provided.")
}
}
protected fun checkObjectInstance(obj: Any?) {
if (obj == null || !member.declaringClass.isInstance(obj)) {
throw IllegalArgumentException("An object member requires the object instance passed as the first argument.")
}
}
class Constructor(val constructor: ReflectConstructor<*>) : FunctionCaller() {
class Constructor(val constructor: ReflectConstructor<*>) : FunctionCaller(constructor.parameterTypes.size()) {
override val member: Member get() = constructor
override fun call(args: Array<*>): Any? =
constructor.newInstance(*args)
override fun call(args: Array<*>): Any? {
checkArguments(args)
return constructor.newInstance(*args)
}
}
abstract class Method(val method: ReflectMethod) : FunctionCaller() {
abstract class Method(
val method: ReflectMethod,
requiresInstance: Boolean
) : FunctionCaller(method.parameterTypes.size() + (if (requiresInstance) 1 else 0)) {
override val member: Member get() = method
private val isVoidMethod = method.returnType == Void.TYPE
@@ -52,58 +64,71 @@ internal sealed class FunctionCaller {
}
}
class StaticMethod(method: ReflectMethod) : Method(method) {
override fun call(args: Array<*>): Any? =
callMethod(null, args)
}
class InstanceMethod(method: ReflectMethod) : Method(method) {
override fun call(args: Array<*>): Any? =
callMethod(args[0], args.asList().subList(1, args.size()).toTypedArray())
}
class PlatformStaticInObject(method: ReflectMethod) : Method(method) {
class StaticMethod(method: ReflectMethod) : Method(method, requiresInstance = false) {
override fun call(args: Array<*>): Any? {
checkArguments(args)
return callMethod(null, args)
}
}
class InstanceMethod(method: ReflectMethod) : Method(method, requiresInstance = true) {
override fun call(args: Array<*>): Any? {
checkArguments(args)
return callMethod(args[0], args.asList().subList(1, args.size()).toTypedArray())
}
}
class PlatformStaticInObject(method: ReflectMethod) : Method(method, requiresInstance = true) {
override fun call(args: Array<*>): Any? {
checkArguments(args)
checkObjectInstance(args.firstOrNull())
return callMethod(null, args.asList().subList(1, args.size()).toTypedArray())
}
}
abstract class Field(val field: ReflectField) : FunctionCaller() {
abstract class FieldAccessor(val field: ReflectField, expectedArgumentNumber: Int) : FunctionCaller(expectedArgumentNumber) {
override val member: Member get() = field
}
class StaticFieldGetter(field: ReflectField) : Field(field) {
override fun call(args: Array<*>): Any? =
field.get(null)
}
class InstanceFieldGetter(field: ReflectField) : Field(field) {
override fun call(args: Array<*>): Any? =
field.get(args[0])
}
class PlatformStaticInObjectFieldGetter(field: ReflectField) : Field(field) {
abstract class FieldGetter(
field: ReflectField,
private val requiresInstance: Boolean
) : FieldAccessor(field, if (requiresInstance) 1 else 0) {
override fun call(args: Array<*>): Any? {
checkObjectInstance(args.firstOrNull())
return field.get(null)
checkArguments(args)
return field.get(if (requiresInstance) args.first() else null)
}
}
class StaticFieldSetter(field: ReflectField) : Field(field) {
override fun call(args: Array<*>): Any? =
field.set(null, args[0])
}
class InstanceFieldSetter(field: ReflectField) : Field(field) {
override fun call(args: Array<*>): Any? =
field.set(args[0], args[1])
}
class PlatformStaticInObjectFieldSetter(field: ReflectField) : Field(field) {
abstract class FieldSetter(
field: ReflectField,
private val requiresInstance: Boolean
) : FieldAccessor(field, if (requiresInstance) 2 else 1) {
override fun call(args: Array<*>): Any? {
checkArguments(args)
return field.set(if (requiresInstance) args.first() else null, args.last())
}
}
class StaticFieldGetter(field: ReflectField) : FieldGetter(field, requiresInstance = false)
class InstanceFieldGetter(field: ReflectField) : FieldGetter(field, requiresInstance = true)
class PlatformStaticInObjectFieldGetter(field: ReflectField) : FieldGetter(field, requiresInstance = true) {
override fun checkArguments(args: Array<*>) {
super.checkArguments(args)
checkObjectInstance(args.firstOrNull())
}
}
class StaticFieldSetter(field: ReflectField) : FieldSetter(field, requiresInstance = false)
class InstanceFieldSetter(field: ReflectField) : FieldSetter(field, requiresInstance = true)
class PlatformStaticInObjectFieldSetter(field: ReflectField) : FieldSetter(field, requiresInstance = true) {
override fun checkArguments(args: Array<*>) {
super.checkArguments(args)
checkObjectInstance(args.firstOrNull())
return field.set(null, args[1])
}
}
}
@@ -81,7 +81,7 @@ private fun KPropertyImpl.Accessor<*>.computeCallerForAccessor(isGetter: Boolean
fun isPlatformStaticProperty() =
property.descriptor.annotations.findAnnotation(PLATFORM_STATIC) != null
fun computeFieldCaller(field: Field): FunctionCaller.Field = when {
fun computeFieldCaller(field: Field): FunctionCaller.FieldAccessor = when {
!Modifier.isStatic(field.modifiers) ->
if (isGetter) FunctionCaller.InstanceFieldGetter(field)
else FunctionCaller.InstanceFieldSetter(field)