History | Log In     View a printable version of the current page.  
Issue Details (XML | Word | Printable)

Key: JANINO-81
Type: Bug Bug
Status: Resolved Resolved
Resolution: Fixed
Priority: Critical Critical
Assignee: Arno Unkrig
Reporter: Judith Andres
Votes: 0
Watchers: 0
Operations

If you were logged in you would be able to see more operations.
Janino

IncompatibleClassChangeError when invoking getClass() on interface references

Created: 09/May/07 09:10 AM   Updated: 18/May/07 03:52 PM
Component/s: None
Affects Version/s: None
Fix Version/s: None

Time Tracking:
Not Specified

Environment:
JDK 1.4.2 or JDK 1.5.0
Janino 2.5.6


 Description  « Hide
package test;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class Test {
public static final Log LOG = LogFactory.getLog(Test.class);

public static void main(String[] args) { System.out.println(LOG.getClass()); }
}

when compiled by Janino (java -cp lib/janino.jar org.codehaus.janino.Compiler -classpath lib\commons-logging-1.1.jar test\Test.java)
and then executed (java -cp .;lib\commons-logging-1.1.jar test.Test)

Exception in thread "main" java.lang.IncompatibleClassChangeError
at test.Test.main(Test.java:10)

is thrown.



 All   Comments   Work Log   Change History      Sort Order: Ascending order - Click to sort in descending order
Arno Unkrig - 09/May/07 04:11 PM
Hi Judith,

which JDK version and JANINO version do you use? Which line is line 10? I reckon the "public static final LOG..." line.

CU

Arno


Judith Andres - 10/May/07 02:49 AM
Hi Arno,

as stated above under environment I tried with both JDK 1.4.2 and JDK 1.5.0. I also used Janino 2.5.6, 2.4.7, 2.4.3 and 2.3.16 with varying success, i.e. only Janino 2.3.16 yielded a class file I could execute without an exception being thrown.

As it seems I messed the formatting up when copying the code into the description. The offending piece of code is LOG.getClass().

bye
Judith


Matthias Eichel - 18/May/07 12:53 PM
Hi Arno,

I did some research on that issue and this is what I found:

Janino 2.5.6 seems to have a problem when generating bytecode for calling a method of an interface if this method is not declared in that interface
and is also not defined in the actual implementation of that interface but instead is inherited directly from java.lang.Object. This is always the
case with the method getClass() which is declared final in java.lang.Object.

Here is an example:

   package test;

   import java.util.ArrayList;
   import java.util.List;

   public class Janinotest { 
     public static void main(String[] args) {
       List list = new ArrayList();
       System.out.println(list.getClass());
     }
   }

When you compile this class with janino 2.5.6 and try to execute the code you get the exception:

   Exception in thread "main" java.lang.IncompatibleClassChangeError
   at test.Janinotest.main(Janinotest.java:9)

line 9 in the source code above is:

   System.out.println(list.getClass());

But when you compile this class with janino 2.3.16 or 2.3.17 or with Sun's javac 1.4.2 or 1.5.0 you get the correct behaviour and the program reports

   class java.util.ArrayList

when executed.

If you take a look at the generated bytecode utilising Sun's javap the problem is easy to see:
janino 2.5.6 compiles the method call getClass() as

   12:	invokevirtual	#27; //Method java/util/List.getClass:()Ljava/lang/Class;

while janino 2.3.16, 2.3.17 and javac 1.4.2, 1.5.0 translate it to

   12:	invokevirtual	#25; //Method java/lang/Object.getClass:()Ljava/lang/Class;

I think this is where the problem originates in. I suppose the bytecode generated with janino 2.3.16, 2.3.17 or javac 1.4.2, 1.5.0 is correct while the bytecode generated with janino 2.5.6 is incorrect because the getClass() method should be called on java.lang.Object.

As I discovered further, calling the getClass() method on java.util.List instead of on java.lang.Object occurs the first time (after janino 2.3.16) in janino 2.3.18.

I had a look into the janino 2.5.6 sources and I think the problem might be the code at UnitCompiler.java line 2823:

   targetType = this.compileGetValue(this.toRvalueOrCE(mi.optionalTarget));

The call of compileGetValue returns an IClass object for targetType that represents a java.util.List instead of a java.lang.Object.

targetType itself is used in line 2885:

   this.writeConstantMethodrefInfo(
       targetType.getDescriptor(), // locatable
       iMethod.getName(), // classFD
       iMethod.getDescriptor()     // methodMD
   );

If you replace line 2885 with

  this.writeConstantMethodrefInfo(
      iMethod.getDeclaringIClass().getDescriptor()
      iMethod.getName(), // classFD
      iMethod.getDescriptor()     // methodMD
  );

you get the following bytecode generated

   12:	invokevirtual	#25; //Method java/lang/Object.getClass:()Ljava/lang/Class;

and the exception doesn't occur.

However I'm not a janino expert and so I guess this approach has other undesirable side effects.

I think it may be better to change line 2823 or the method compileGetValue() that returns targetType.

Maybe it would be best to set

   targetType = iMethod.getDeclaringIClass().getDescriptor();

under certain circumstances, for instance when

   iMethod.getDeclaringIClass().getDescriptor()

represents java.lang.Object.

In this context I found something very interesting:

When you compile the following class

   package test;

   import java.util.ArrayList;
   import java.util.List;

   public class Janinotest2 { 
     public static void main(String[] args) {
       List list = new ArrayList();
       System.out.println(list.hashCode());
     }
   }

with janino 2.3.16, 2.3.17 or janino 2.5.6 you get list.hashCode() compiled to

   12:	invokeinterface	#27,  1; //InterfaceMethod java/util/List.hashCode:()I

and I think this is what everybody would expect because the method hashCode() is explicitly declared in the interfaces java.util.List and java.util.Collection.

The Sun compilers (javac 1.4.2 and 1.5.0) however translate list.hashCode() to

   12:	invokevirtual	#5; //Method java/lang/Object.hashCode:()I

The compiled programs work for all versions alike but it seems that javac codes a invokevirtual call for java.lang.Object whenever the declaring class is java.lang.Object.

bye
Matthias


Arno Unkrig - 18/May/07 03:52 PM
Hi Matthias,

your proposal to change line 2885 is correct. I did all kinds of regression tests, and everything works fine.

Fix will be released as 2.5.7.