Janino

IncompatibleClassChangeError when invoking getClass() on interface references

Details

  • Type: Bug Bug
  • Status: Resolved Resolved
  • Priority: Critical Critical
  • Resolution: Fixed
  • Affects Version/s: None
  • Fix Version/s: None
  • Component/s: None
  • Labels:
    None
  • Environment:
    JDK 1.4.2 or JDK 1.5.0
    Janino 2.5.6
  • Number of attachments :
    0

Description

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.

Activity

Hide
Arno Unkrig added a comment -

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

Show
Arno Unkrig added a comment - 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
Hide
Judith Andres added a comment -

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

Show
Judith Andres added a comment - 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
Hide
Matthias Eichel added a comment -

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

Show
Matthias Eichel added a comment - 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
Hide
Arno Unkrig added a comment -

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.

Show
Arno Unkrig added a comment - 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.

People

Vote (0)
Watch (0)

Dates

  • Created:
    Updated:
    Resolved: