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); 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") @TestMetadata("innerClassConstructor.kt")
public void testInnerClassConstructor() throws Exception { public void testInnerClassConstructor() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxWithStdlib/reflection/call/innerClassConstructor.kt"); 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.Member
import java.lang.reflect.Constructor as ReflectConstructor import java.lang.reflect.Constructor as ReflectConstructor
import java.lang.reflect.Field as ReflectField import java.lang.reflect.Field as ReflectField
import java.lang.reflect.Member as ReflectMember
import java.lang.reflect.Method as ReflectMethod import java.lang.reflect.Method as ReflectMethod
internal sealed class FunctionCaller { internal sealed class FunctionCaller(
abstract val member: ReflectMember private val expectedArgumentNumber: Int
) {
abstract val member: Member
abstract fun call(args: Array<*>): Any? 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?) { protected fun checkObjectInstance(obj: Any?) {
if (obj == null || !member.declaringClass.isInstance(obj)) { if (obj == null || !member.declaringClass.isInstance(obj)) {
throw IllegalArgumentException("An object member requires the object instance passed as the first argument.") 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 val member: Member get() = constructor
override fun call(args: Array<*>): Any? = override fun call(args: Array<*>): Any? {
constructor.newInstance(*args) 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 override val member: Member get() = method
private val isVoidMethod = method.returnType == Void.TYPE private val isVoidMethod = method.returnType == Void.TYPE
@@ -52,58 +64,71 @@ internal sealed class FunctionCaller {
} }
} }
class StaticMethod(method: ReflectMethod) : Method(method) { class StaticMethod(method: ReflectMethod) : Method(method, requiresInstance = false) {
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) {
override fun call(args: Array<*>): Any? { 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()) checkObjectInstance(args.firstOrNull())
return callMethod(null, args.asList().subList(1, args.size()).toTypedArray()) 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 override val member: Member get() = field
} }
class StaticFieldGetter(field: ReflectField) : Field(field) { abstract class FieldGetter(
override fun call(args: Array<*>): Any? = field: ReflectField,
field.get(null) private val requiresInstance: Boolean
} ) : FieldAccessor(field, if (requiresInstance) 1 else 0) {
class InstanceFieldGetter(field: ReflectField) : Field(field) {
override fun call(args: Array<*>): Any? =
field.get(args[0])
}
class PlatformStaticInObjectFieldGetter(field: ReflectField) : Field(field) {
override fun call(args: Array<*>): Any? { override fun call(args: Array<*>): Any? {
checkObjectInstance(args.firstOrNull()) checkArguments(args)
return field.get(null) return field.get(if (requiresInstance) args.first() else null)
} }
} }
class StaticFieldSetter(field: ReflectField) : Field(field) { abstract class FieldSetter(
override fun call(args: Array<*>): Any? = field: ReflectField,
field.set(null, args[0]) private val requiresInstance: Boolean
} ) : FieldAccessor(field, if (requiresInstance) 2 else 1) {
class InstanceFieldSetter(field: ReflectField) : Field(field) {
override fun call(args: Array<*>): Any? =
field.set(args[0], args[1])
}
class PlatformStaticInObjectFieldSetter(field: ReflectField) : Field(field) {
override fun call(args: Array<*>): Any? { 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()) checkObjectInstance(args.firstOrNull())
return field.set(null, args[1])
} }
} }
} }
@@ -81,7 +81,7 @@ private fun KPropertyImpl.Accessor<*>.computeCallerForAccessor(isGetter: Boolean
fun isPlatformStaticProperty() = fun isPlatformStaticProperty() =
property.descriptor.annotations.findAnnotation(PLATFORM_STATIC) != null 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) -> !Modifier.isStatic(field.modifiers) ->
if (isGetter) FunctionCaller.InstanceFieldGetter(field) if (isGetter) FunctionCaller.InstanceFieldGetter(field)
else FunctionCaller.InstanceFieldSetter(field) else FunctionCaller.InstanceFieldSetter(field)