Bug in Method.invoke() / Patch

kaffe@rufus.w3.org kaffe@rufus.w3.org
Tue, 24 Jul 2001 18:52:09 +0200



--gatW/ieO32f1wygP
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

Hi,

when executing the following small java-program, I noticed some
strange behaviour:

interface A {
	void foo();
}

class B implements A {
	public void foo() {
		System.out.println("foo");
	}

	public static void main(String[] args) {
		Object o = new B();

		// now calling o.foo() using reflection

		// this works fine, calls B.foo()
		B.class.getMethod("foo", null).invoke( o, null );

		// this will execute o.clone() instead!!!!
		A.class.getMethod("foo", null).invoke( o, null );

	}
}

According to the Java-Spezification, both reflective calls should
resolve to B.foo(). But the output is

foo
java.lang.reflect.InvocationTargetException: java.lang.CloneNotSupportedException: B
	at java.lang.Object.clone(Object.java:native)
	at java.lang.reflect.Method.invoke(Method.java:native)
	at B.main(B.java:22)

As this didn't make any sense, I looked into the code of
libraries/clib/native/Method.c and found the following explanation:
when the Method-Object is constructed (via getMethod(...)), it saves
the class it's constructed from and it's "method slot"-number. So in
the second case, we have a Method that believes to be "Method #0 in
class A", while the first Method object is "Method #6 in B".
Unfortunately, when Method.invoke() is called, the following happens
(in a nutshell): the runtime class of the object-argument of
Method.invoke() and the slot number (index) in the Method are combined
to find the code to be executed: so in the first case, we call method
#6 in class B, which *really is* foo(). But in the second case we
execute method #0 (!!!!) in class B, which happens to be
object.clone(), leading to the shown exception. 

Attached is a patch, which to my opinion should fix this (using
getInheritedMethodIndex() in classMethod.c), but I would appreciate
if somebody more familiar with this code could check my explanation/fix.

One more bug in the reflection code:
Using the interface A from above again, the following will erroneously *not* flag
an error:
	Object cloneable = new Cloneable() {};
	A.class.getMethod("foo", null).invoke( cloneable, null);

while Suns VM will throw
java.lang.IllegalArgumentException: object is not an instance of
declaring class
        at D.main(D.java:12)

which is correct. The patch mentioned above will - as a side-effect -
also fix this. Anybody any comments on this?

I am working with the kaffe-version out of the CVS on Linux/i386.

Best regards and thanks for any comment ...

Enno Brehm

--
enno@convergence.de
( convergence      )
( integrated media )


--gatW/ieO32f1wygP
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="diff.Method.c"

Index: Method.c
===================================================================
RCS file: /cvs/kaffe/kaffe/libraries/clib/native/Method.c,v
retrieving revision 1.30
diff -u -r1.30 Method.c
--- Method.c	2000/12/03 03:25:09	1.30
+++ Method.c	1999/12/20 22:42:06
@@ -281,6 +281,10 @@
 		rettype = 'L';
 	}
 	else {			/* nonstatic method */
+		Hjava_lang_Class* objClazz = (*env)->GetObjectClass(env, obj);
+	        if( !getInheritedMethodIndex( objClazz, meth )) {
+		  SignalError("java.lang.IllegalArgumentException", "object is not an instance of declaring class");
+	        }
 		switch (rettype) {
 
 		/* Why Call<Type>MethodA and not CallNonvirtual<Type>MethodA?

--gatW/ieO32f1wygP
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="B.java"



interface A {
        void foo();
}
 
class B implements A {
    public void foo() {
	System.out.println("foo");
    }
    
    public static void main(String[] args) 
	throws Exception
    {
	Object o = new B();
	
	// this works fine:
	B.class.getMethod("foo", null).invoke( o, null );

	// call foo() on o via reflection
	// (will execute o.clone() instead!!!!)
	A.class.getMethod("foo", null).invoke( o, null );
    }
}

--gatW/ieO32f1wygP
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="D.java"



interface A {
        void foo();
}
 
class D {
    public static void main(String[] args) 
	throws Exception
    {
	Object o = new Cloneable() {};
	A.class.getMethod("foo", null).invoke( o, null );
    }
}

--gatW/ieO32f1wygP--