Clojure

Clojure resolves to wrong deftype classes when AOT compiling or reloading

Details

  • Type: Defect Defect
  • Status: Open Open
  • Priority: Critical Critical
  • Resolution: Unresolved
  • 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
  • Patch:
    Code and Test
  • Approval:
    Vetted

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 the [optional] in disk interface so that the in-memory class is always updated.
  1. CLJ-979.patch
    11/Dec/14 1:40 PM
    8 kB
    Nicola Mometto
  2. clj-979-symptoms.patch
    29/Mar/14 2:27 PM
    2 kB
    Ambrose Bonnaire-Sergeant
  3. CLJ-979-v2.patch
    12/Dec/14 6:06 AM
    9 kB
    Nicola Mometto
  4. CLJ-979-v3.patch
    12/Dec/14 9:38 AM
    9 kB
    Nicola Mometto
  5. CLJ-979-v4.patch
    12/Dec/14 10:09 AM
    9 kB
    Nicola Mometto
  6. CLJ-979-v5.patch
    12/Dec/14 1:07 PM
    10 kB
    Nicola Mometto
  7. CLJ-979-v6.patch
    15/Dec/14 5:43 PM
    10 kB
    Nicola Mometto
  8. CLJ-979-v7.patch
    16/Dec/14 12:50 PM
    10 kB
    Nicola Mometto

Activity

People

Vote (13)
Watch (8)

Dates

  • Created:
    Updated: