Details
-
Type:
Defect
-
Status:
Closed
-
Priority:
Critical
-
Resolution: Completed
-
Affects Version/s: Release 1.3, Release 1.4, Release 1.5, Release 1.6, Release 1.7
-
Fix Version/s: Release 1.7
-
Component/s: None
-
Labels:
-
Patch:Code and Test
-
Approval:Ok
Description
Compiling a class via `deftype` during AOT compilation gives different results for the different constructors. These hashes should be identical.
user=> (binding [*compile-files* true] (eval '(deftype Abc [])))
user.Abc
user=> (hash Abc)
16446700
user=> (hash (class (->Abc)))
31966239 ;; should be 16446700
This also means that whenever there's a stale AOT compiled deftype class in the classpath, that class will be used rather then the JIT compiled one, breaking repl interaction.
Another demonstration of this classloader issue (from CLJ-1495) when reloading deftypes (no AOT) :
user> (defrecord Foo [bar]) user.Foo user> (= (->Foo 42) #user.Foo{:bar 42}) ;;expect this to evaluate to true true user> (defrecord Foo [bar]) user.Foo user> (= (->Foo 42) #user.Foo{:bar 42}) ;;expect this to evaluate to true also -- but it doesn't! false user>
This bug also affects AOT compilation of multimethods that dispatch on a class, this affected core.match for years see http://dev.clojure.org/jira/browse/MATCH-86, http://dev.clojure.org/jira/browse/MATCH-98. David had to work-around this issue by using a bunch of protocols instead of multimethods.
Cause of the bug: currently clojure uses Class.forName to resolve a class from a class name, which ignores the class cache from DynamicClassLoader thus reloading deftypes or mixing AOT compilation at the repl with deftypes breaks, resolving to the wrong class.
Approach: the current patch (CLJ-979-v7.patch) addresses this issue in multiple ways:
- it makes RT.classForName/classForNameNonLoading look in the class cache before delegating to Class/forName if the current classloader is not a DynamicClassLoader (this incidentally addresses also
CLJ-1457) - it makes clojure use RT.classForName/classForNameNonLoading instead of Class/forName
- it overrides Classloader/loadClass so that it's class cache aware – this method is used by the jvm to load classes
- it changes gen-interface to always emit an in-memory interface along with the [optional] on-disk interface so that the in-memory class is always updated.
Patch: CLJ-979-v7.patch
Screened by: Alex Miller
I can't reproduce this under Clojure 1.3 or 1.4, and Leiningen 1.7.1 on either Java 1.7.0-jdk7u4-b21 OpenJDK 64-Bit or Java 1.6.0_31 Java HotSpot 64-Bit. OS is Mac OS X 10.7.
Edmund, how are you running this AOT code? I wrapped your code in a main function and built an uberjar from it.