United StatesChange Country, Oracle Worldwide Web Sites Communities I am a... I want to...
Bug ID: 6610906 inexplicable IncompatibleClassChangeError
6610906 : inexplicable IncompatibleClassChangeError

Details
Type:
Bug
Submit Date:
2007-09-28
Status:
Closed
Updated Date:
2011-04-20
Project Name:
JDK
Resolved Date:
2011-04-20
Component:
hotspot
OS:
solaris,linux,generic
Sub-Component:
compiler
CPU:
x86,generic
Priority:
P2
Resolution:
Fixed
Affected Versions:
6u4p,6u10,7
Fixed Versions:
hs12

Related Reports
Backport:
Backport:
Duplicate:
Duplicate:
Duplicate:
Relates:

Sub Tasks

Description
Following test case is failing from JDK7 build 20

<testcase>
import java.util.*;

public class VectorIntegerTest {
    static Random rnd = new Random();
    public static void main(String[] args) throws Exception {
		List<Integer> list1 = new Vector<Integer>();
		AddRandoms(list1, 400);
		List<Integer> list2 = new Vector<Integer>();
		AddRandoms(list2, 400);

		List<Integer> copyofs2 = new Vector<Integer>();
		copyofs2.addAll(list2);

		if (!list2.equals(copyofs2))
			throw new Exception("Exception");

		list1.clear();
		list1.addAll(0,list2);

		if (!(list1.equals(list2) && list2.equals(list1)))
			throw new Exception ("Exception");

		if (!(list1.equals(list2) && list2.equals(list1)))
			throw new Exception("Exception");

		List<Integer> l = new Vector<Integer>();
		AddRandoms(l,400);
		Integer [] ia = l.toArray(new Integer[0]);
		if (!l.equals(Arrays.asList(ia)))
			 throw new Exception("Exception");
    }

    static void AddRandoms(List<Integer> s, int n) throws Exception {
		for (int i=0; i<n; i++) {
			int r = rnd.nextInt() % n;
			Integer e = new Integer(r < 0 ? -r : r);
			s.add(e);
		}
  	}
}
</testcase>

<output>
bash-3.00$ /net/sqindia/export/disk09/jdk/7/b17/binaries/solsparc/bin/java VectorIntegerTest
bash-3.00$ /net/sqindia/export/disk09/jdk/7/b18/binaries/solsparc/bin/java VectorIntegerTest
bash-3.00$ /net/sqindia/export/disk09/jdk/7/b19/binaries/solsparc/bin/java VectorIntegerTest

**** failing from build 20 ******

bash-3.00$ /net/sqindia/export/disk09/jdk/7/b20/binaries/solsparc/bin/java VectorIntegerTest
Exception in thread "main" java.lang.IncompatibleClassChangeError
        at java.util.AbstractList.equals(AbstractList.java:522)
        at java.util.Vector.equals(Vector.java:953)
        at VectorIntegerTest.VectorIntegerTestTest01(VectorIntegerTest.java:63)
        at VectorIntegerTest.main(VectorIntegerTest.java:9)
bash-3.00$ /net/sqindia/export/disk09/jdk/7/b21/binaries/solsparc/bin/java VectorIntegerTest
Exception in thread "main" java.lang.IncompatibleClassChangeError
        at java.util.AbstractList.equals(AbstractList.java:522)
        at java.util.Vector.equals(Vector.java:953)
        at VectorIntegerTest.VectorIntegerTestTest01(VectorIntegerTest.java:63)
        at VectorIntegerTest.main(VectorIntegerTest.java:9)
</output>
Additional comments and test case from Christian Wimmer (###@###.###) from the C1 collaborative research project (also on the bugs.sun.com public database):

I get a similar bug: the "eclipse" benchmark of the DaCapo benchmark suite fails with the latest JDK 7 builds (starting with build b20 which changed the handling of dependencies). In debug builds, a message is printed that a method should have been marked for deoptimization, but was not marked by the optimized dependency checking code.

I could reduce the problem to the small testcase that is attached. The Interface has two implementations: the classes Impl1 and Impl2. At first, only Impl1 is loaded, so the method of the interface call can be inlined because there is only one implementation. When the second implementation is loaded, the method must be deoptimized.

The second class Impl2 implements the Interface, but the method is already defined in the base class BaseImpl2. The optimized dependency checking code only looks at the methods defined in Impl2, does not see the method, and therefore does not trigger deoptimization. The slow verification code of the debug build detects the inconsistency and prints an error (I would prefer an assertion in such cases).



A possible fix would be to also look at methods defined in superclasses. When the line 753 in the file dependencies.cpp is changed from

  methodOop m = instanceKlass::cast(k)->find_method(_name, _signature);
   
to

  methodOop m = instanceKlass::cast(k)->uncached_lookup_method(_name, _signature);
  
the dependency checking is correct in my example. However, I do not know if this change causes other problems (it could detect too many conflicts) or is too slow.

[Follow-up from Christian: My nightly benchmarks revealed that my possible fix of the bug is too conservative. It literally kills the performance of some benchmarks, e.g. _227_mtrt is more than 50% slower because many accessor methods are no longer inlined. So just forget my suggestion...]

public class DependencyBug {

  public static interface Interface {
    public void method();
  }
  
  public static class Impl1 implements Interface {
    public void method() {
      // Nothing to do here
    }
  }
  
  public static class BaseImpl2 {
    public void method() {
      System.out.print("#");
    }
  }

  public static class Impl2 extends BaseImpl2 implements Interface {
    // Interface method already implemented in base class.
  }

  
 
  public static void callMethod(Interface obj) {
    // method() is inlined as long as only class Impl1() is loaded.
    obj.method();
  }
  
  public static void main(String[] args) throws Exception {
    
    Interface obj = new Impl1();
    for (int i = 0; i < 2000; i++) {
      // Force compilation of the method callMethod()
      callMethod(obj);
    }
    
    System.out.println("**** Initiating class loading of Impl2 ****");
    
    obj = new Impl2();
    callMethod(obj);
  }
}
JDK: 7, 6u10 b09
testbase: /net/cady/export/dtf/unified/knight-ws/suites/6.0_cady/libs
Failing testcases:
java_util/generics/list/LinkedListDoubleTest
java_util/generics/list/LinkedListIntegerTest
failing cases: 
java_util/generics/list/VectorIntegerTest
java_util/generics/list/VectorDoubleTest
java_util/generics/list/ArrayListDoubleTest
java_util/generics/list/ArrayListIntegerTest
java_util/generics/list/StackDoubleTest
java_util/generics/list/StackIntegerTest

                                    

Comments
SUGGESTED FIX

The previous suggested fix is just a band-aid.

This fix is the real bug, and should have been part of the putback for 6471009.

diff -r f0a77608b96a src/share/vm/code/dependencies.cpp
--- a/src/share/vm/code/dependencies.cpp        Thu Nov 08 16:47:30 2007 -0800
+++ b/src/share/vm/code/dependencies.cpp        Tue Nov 27 15:55:00 2007 -0800
@@ -881,6 +881,14 @@ klassOop ClassHierarchyWalker::find_witn
   assert(must_be_in_vm(), "raw oops here");
   // Must not move the class hierarchy during this check:
   assert_locked_or_safepoint(Compile_lock);
+
+  int nof_impls = instanceKlass::cast(context_type)->nof_implementors();
+  if (nof_impls > 1) {
+    // Avoid this case: *I.m > { A.m, C }; B.m > C
+    // %%% Until this is fixed more systematically, bail out.
+    // See corresponding comment in find_witness_anywhere.
+    return context_type;
+  }
 
   assert(!is_participant(new_type), "only old classes are participants");
   if (participants_hide_witnesses) {
diff -r f0a77608b96a src/share/vm/code/nmethod.cpp
--- a/src/share/vm/code/nmethod.cpp     Thu Nov 08 16:47:30 2007 -0800
+++ b/src/share/vm/code/nmethod.cpp     Tue Nov 27 15:55:00 2007 -0800
@@ -1971,7 +1971,7 @@ void nmethod::print_dependencies() {
     if (ctxk != NULL) {
       Klass* k = Klass::cast(ctxk);
       if (k->oop_is_instance() && ((instanceKlass*)k)->is_dependent_nmethod(this)) {
-        tty->print("   [nmethod<=klass]%s", k->external_name());
+        tty->print_cr("   [nmethod<=klass]%s", k->external_name());
       }
     }
     deps.log_dependency();  // put it into the xml log also


Sample test run:

 -XX:+PrintCompilation -XX:+TraceDependencies DependencyBug
VM option '+PrintCompilation'
VM option '+TraceDependencies'
  1       java.lang.String::hashCode (60 bytes)
  2       java.lang.String::charAt (33 bytes)
  3       java.lang.String::indexOf (151 bytes)
  4       DependencyBug::callMethod (7 bytes)
Warning:  TraceDependencies results may be inflated by VerifyDependencies
**** Initiating class loading of Impl2 ****
  5       java.lang.String::lastIndexOf (156 bytes)
  6       java.lang.String::indexOf (166 bytes)
Failed dependency of type unique_concrete_method
  context = *DependencyBug$Interface
  method  = {method} 'method' '()V' in 'DependencyBug$Impl1'
  witness = *DependencyBug$Interface
  code:   4   nmethod DependencyBug::callMethod (7 bytes)
Marked for deoptimization
  context = DependencyBug$Interface
  dependee = DependencyBug$Impl2
  context supers = 2, interfaces = 1
Compiled (c1)   4   nmethod DependencyBug::callMethod (7 bytes)
 total in heap  [0xfbef1cc8,0xfbef1ec4] = 508
 relocation     [0xfbef1d7c,0xfbef1da4] = 40
 main code      [0xfbef1db0,0xfbef1e20] = 112
 stub code      [0xfbef1e20,0xfbef1e48] = 40
 scopes data    [0xfbef1e48,0xfbef1e60] = 24
 scopes pcs     [0xfbef1e60,0xfbef1ea8] = 72
 dependencies   [0xfbef1ea8,0xfbef1eac] = 4
 nul chk table  [0xfbef1eac,0xfbef1eb8] = 12
 oops           [0xfbef1eb8,0xfbef1ec4] = 12
Dependencies:
Dependency of type unique_concrete_method
  context = *DependencyBug$Interface
  method  = {method} 'method' '()V' in 'DependencyBug$Impl1'
   [nmethod<=klass]DependencyBug$Interface
  4   made not entrant  DependencyBug::callMethod (7 bytes)
#
                                     
2007-11-27
EVALUATION

My previous evaluation isn't quite right.  The original dependency issued by C1 is correct, and used to work, and was broken by the change that created "spot checks" for dependencies.

The bail-out on nof_impls > 1 in find_witness_anywhere needs to be duplicated in the optimized query find_witness at.  See suggested fix.
                                     
2007-11-27
EVALUATION

The test case is described in the comments in dependencies.cpp, around line 961:
    // Avoid this case: *I.m > { A.m, C }; B.m > C
    // Here, I.m has 2 concrete implementations, but m appears unique
    // as A.m, because the search misses B.m when checking C.
    // The inherited method B.m was getting missed by the walker
    // when interface 'I' was the starting point.
    // %%% Until this is fixed more systematically, bail out.
    // (Old CHA had the same limitation.)
    return context_type;

The question is, if the dependency module (apparently) knows about this case, why isn't it getting asked about it?

I think the code in c1_GraphBuilder.cpp (near 1614) needs an extra dependency to detect when nof_implementors changes.

      ciInstanceKlass* singleton = NULL;
      if (target->holder()->nof_implementors() == 1) {
        singleton = target->holder()->implementor(0);
      }
      if (singleton) {
        cha_monomorphic_target = target->find_monomorphic_target(calling_klass, target->holder(), singleton);
        if (cha_monomorphic_target != NULL) {
          // If CHA is able to bind this invoke then update the class
          // to match that class, otherwise klass will refer to the
          // interface.
          klass = cha_monomorphic_target->holder();
    +     dependency_recorder()->assert_unique_concrete_subtype(actual_recv, singleton);
          actual_recv = target->holder();

Since the C1 code updates actual_recv to a unique implementor, it is C1's responsibility to issue a dependency on that uniqueness.  The recent changes in b20 made the dependency checking less sloppy.  As a side effect, this missing dependency is now exposed.
                                     
2007-11-21
SUGGESTED FIX

--------
        hg diff
diff -r f0a77608b96a src/share/vm/c1/c1_GraphBuilder.cpp
--- a/src/share/vm/c1/c1_GraphBuilder.cpp       Thu Nov 08 16:47:30 2007 -0800
+++ b/src/share/vm/c1/c1_GraphBuilder.cpp       Wed Nov 21 12:07:25 2007 -0800
@@ -1610,7 +1610,7 @@ void GraphBuilder::invoke(Bytecodes::Cod
       if (target->holder()->nof_implementors() == 1) {
         singleton = target->holder()->implementor(0);
       }
-      if (singleton) {
+      if (singleton && !singleton->is_abstract()) {
         cha_monomorphic_target = target->find_monomorphic_target(calling_klass, target->holder(), singleton);
         if (cha_monomorphic_target != NULL) {
           // If CHA is able to bind this invoke then update the class
@@ -1618,6 +1618,7 @@ void GraphBuilder::invoke(Bytecodes::Cod
           // interface.
           klass = cha_monomorphic_target->holder();
           actual_recv = target->holder();
+          dependency_recorder()->assert_abstract_with_unique_concrete_subtype(actual_recv, singleton);
 
           // insert a check it's really the expected class.
           CheckCast* c = new CheckCast(klass, receiver, NULL);
--------


Sample test run:
--------
	java -XX:+PrintCompilation -XX:+TraceDependencies DependencyBug
VM option '+PrintCompilation'
VM option '+TraceDependencies'
  1       java.lang.String::hashCode (60 bytes)
  2       java.lang.String::charAt (33 bytes)
  3       java.lang.String::indexOf (151 bytes)
**** Initiating class loading of Impl2 ****
  4       DependencyBug::callMethod (7 bytes)
Warning:  TraceDependencies results may be inflated by VerifyDependencies
  5       java.lang.String::lastIndexOf (156 bytes)
Failed dependency of type abstract_with_unique_concrete_subtype
  context = *DependencyBug$Interface
  class   = DependencyBug$Impl1
  witness = DependencyBug$Impl2
  code:   4   nmethod DependencyBug::callMethod (7 bytes)
Marked for deoptimization
  context = DependencyBug$Interface
  dependee = DependencyBug$Impl2
  context supers = 2, interfaces = 1
Compiled (c1)   4   nmethod DependencyBug::callMethod (7 bytes)
 total in heap  [0xfbef1cc8,0xfbef1ecc] = 516
 relocation     [0xfbef1d7c,0xfbef1da4] = 40
 main code      [0xfbef1db0,0xfbef1e20] = 112
 stub code      [0xfbef1e20,0xfbef1e48] = 40
 scopes data    [0xfbef1e48,0xfbef1e60] = 24
 scopes pcs     [0xfbef1e60,0xfbef1ea8] = 72
 dependencies   [0xfbef1ea8,0xfbef1eb0] = 8
 nul chk table  [0xfbef1eb0,0xfbef1ebc] = 12
 oops           [0xfbef1ebc,0xfbef1ecc] = 16
Dependencies:
Dependency of type abstract_with_unique_concrete_subtype
  context = *DependencyBug$Interface
  class   = DependencyBug$Impl1
   [nmethod<=klass]DependencyBug$InterfaceDependency of type unique_concrete_method
  context = *DependencyBug$Interface
  method  = {method} 'method' '()V' in 'DependencyBug$Impl1'
   [nmethod<=klass]DependencyBug$Interface  4   made not entrant  DependencyBug::callMethod (7 bytes)
#
--------
                                     
2007-11-21
EVALUATION

The provided test case fails on solaris-sparc and on windows-x86, 
but only with the client compiler.
This looks very much like a hotspot compiler bug, so I am redispatching
to hotspot/compiler1 (although investigation to find the b20 change
that caused this should continue in any case)
                                     
2007-10-05
EVALUATION

It appears the changes for 6471009 cause us to miss a dependence.

% /java/re/jdk/1.7.0/promoted/all/b20/binaries/solaris-sparc/fastdebug/bin/java -client -Xbatch VectorIntegerTest                 Should have been marked for deoptimization:
  dependee = java.util.AbstractList$ListItr
  context supers = 2, interfaces = 2
Compiled (c1)  13   nmethod java.util.AbstractList::equals (117 bytes)
 total in heap  [0xfb10a608,0xfb10b28c] = 3204
 relocation     [0xfb10a6bc,0xfb10a798] = 220
 main code      [0xfb10a7a0,0xfb10ace0] = 1344
 stub code      [0xfb10ace0,0xfb10ad98] = 184
 scopes data    [0xfb10ad98,0xfb10af60] = 456
 scopes pcs     [0xfb10af60,0xfb10b218] = 696
 dependencies   [0xfb10b218,0xfb10b220] = 8
 nul chk table  [0xfb10b220,0xfb10b254] = 52
 oops           [0xfb10b254,0xfb10b28c] = 56
Dependencies:
Dependency of type unique_concrete_method
  context = *java.util.ListIterator
  method  = {method} 'hasNext' '()Z' in 'java/util/Vector$Itr'
   [nmethod<=klass]java.util.ListIteratorDependency of type unique_concrete_method
  context = *java.util.ListIterator
  method  = {method} 'next' '()Ljava/lang/Object;' in 'java/util/Vector$Itr'
   [nmethod<=klass]java.util.ListIteratorException in thread "main" java.lang.IncompatibleClassChangeError
        at java.util.AbstractList.equals(AbstractList.java:522)
        at java.util.Vector.equals(Vector.java:953)
        at VectorIntegerTest.main(VectorIntegerTest.java:29)
                                     
2007-10-05
EVALUATION

This is deeply mysterious.  On one machine the supplied test works perfectly,
while on another, using the *exact*same* binaries, it fails:


(mb29450@suttles) ~/src/toy/6610906 $ time jver -v /net/sqindia.india/export/disk09/jdk/7/b20/binaries/solsparc jr VectorIntegerTest
Using JDK /net/sqindia.india/export/disk09/jdk/7/b20/binaries/solsparc
==> javac -Xlint:all VectorIntegerTest.java
==> java -esa -ea VectorIntegerTest


(mb29450@seetharama) ~/src/toy/6610906 $ jver -v /net/sqindia.india/export/disk09/jdk/7/b20/binaries/solsparc java VectorIntegerTest
Using JDK /net/sqindia.india/export/disk09/jdk/7/b20/binaries/solsparc
Exception in thread "main" java.lang.IncompatibleClassChangeError
        at java.util.AbstractList.equals(AbstractList.java:522)
        at java.util.Vector.equals(Vector.java:953)
        at VectorIntegerTest.VectorIntegerTestTest01(VectorIntegerTest.java:62)
        at VectorIntegerTest.main(VectorIntegerTest.java:8)


Seetharam, could you reduce the test case to the minimum required to reproduce this?
It's starting to look like a hotspot or ClassLoader bug.
                                     
2007-10-03



Hardware and Software, Engineered to Work Together