Discovered by Ambrose Bonnaire-Sergeant (@ambrosebs).
Given this convoluted example:
(let [f +
p (promise)
d (delay (f) @@p)]
(deliver p d)
@d)
When executed, it fails with:
1. Unhandled java.lang.NullPointerException
Cannot invoke "clojure.lang.IFn.invoke()" because "this.f" is null
The same error happens with lazy seqs:
(let [f +
p (promise)
d (lazy-seq (f) (first @p))]
(deliver p d)
(first d))
The reason in both cases is that locals clearing assigns this.f = null;
right after it is first invoked, so when the recursive call reaches (f)
again, the reference is already nil.
(binding [*compiler-options* {:disable-locals-clearing false}]
(clj-java-decompiler.core/decompile
(defn repro []
(let [f +
p (promise)
d (delay (f) @@p)]
(deliver p d)
@d))))
=>
@Override
public Object invoke() {
final Object f = this.f;
this.f = null;
((IFn)f).invoke();
final IFn fn = (IFn)__deref.getRawRoot();
final IFn fn2 = (IFn)__deref.getRawRoot();
final Object p = this.p;
this.p = null;
final Object invoke = fn2.invoke(p);
this = null;
return fn.invoke(invoke);
}
Is this a bug or undefined behavior?