Welcome! Please see the About page for a little more info on how this works.

+1 vote
in Compiler by

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?

1 Answer

+2 votes
by

Seems like a bug to me. Logged as https://clojure.atlassian.net/browse/CLJ-2861

...