Details
-
Type:
Defect
-
Status:
Closed
-
Priority:
Critical
-
Resolution: Declined
-
Affects Version/s: Release 1.3, Release 1.4
-
Fix Version/s: None
-
Component/s: None
-
Labels:None
Description
The following code:
(ns example.core) (defprotocol Foo (foo [x])) (extend-protocol Foo Object (foo [x] x) nil (foo [x] nil)) (defn apply-foo [f] (fn [& args] (foo (apply f args)))) (alter-var-root #'assoc apply-foo)
takes forever to compile from emacs+swank. Running from lein repl results in following:
user=> (use 'example.core) StackOverflowError clojure.lang.ArrayChunk.<init> (ArrayChunk.java:28)
If delete the foo call from the apply-foo:
(defn apply-foo [f]
(fn [& args]
(apply f args)))
then code compiles and executes fine.
This is true not only for assoc, but also for conj, into and possibly some other functions. Note also that crashes was seen even when the protocol function is not called, but there is call to satisfies? in the apply-foo function (although it's not clear for me how to reproduce it). Tested on clojure 1.3 and 1.4.
Is this bug a fairly well-constructed joke? I guess so.
But this is what I think happens: You change the value of #'assoc to the function returned by apply-foo. The alter-var-root call executes (apply-foo assoc) because assoc is the current value of #'assoc, thus the new value of #'assoc is {{(fn [& args] (foo (apply assoc args)))}}. However, fn is a macro that expands to fn* and calls destructure which uses assoc!!! So now foo gets called, and probably there's another assoc call during protocol dispatch, and there you have your non-terminating recursion.
So the lesson is: Don't alter core functions unless you're exactly knowing what you do.