Why does dotrace throw a StackOverflowError here?

(use '[clojure.contrib.trace]) (dotrace [str] (reduce str [\a \b])) 
+7
clojure
source share
1 answer

In a nutshell:

That trace-fn-call , which dotrace uses to wrap tracked functions, uses str to produce good TRACE foo => val output TRACE foo => val .

Extended explanation:

The dotrace macro does its magic by setting the thread binding for each Var with a tracking function; in this case, there is one such Var, clojure.core/str . The replacement looks something like this:

 (let [f @#'str] (fn [& args] (trace-fn-call 'str f args))) 

trace-fn-call to quote it docstring: "Traces one call to f with args." At the same time, it calls the monitored function, takes note of the return value, gives a nice informative message of the form TRACE foo => val and returns the value obtained from the trace function, so that regular execution can continue.

As mentioned above, this TRACE foo => val message TRACE foo => val used by str ; however, in this case, it is actually a tracked function, so calling it leads to another call to trace-fn-call , which makes its own attempt to create an output trace line using str , which leads to another call to trace-fn-call ... in ultimately leads to a stack explosion.

Workaround:

The following modified versions of dotrace and trace-fn-call should work fine even if there are strange bindings for the main Vars (note that futures may not be scheduled promptly if this is a problem, see below):

 (defn my-trace-fn-call "Traces a single call to a function f with args. 'name' is the symbol name of the function." [name f args] (let [id (gensym "t")] @(future (tracer id (str (trace-indent) (pr-str (cons name args))))) (let [value (binding [*trace-depth* (inc *trace-depth*)] (apply f args))] @(future (tracer id (str (trace-indent) "=> " (pr-str value)))) value))) (defmacro my-dotrace "Given a sequence of function identifiers, evaluate the body expressions in an environment in which the identifiers are bound to the traced functions. Does not work on inlined functions, such as clojure.core/+" [fnames & exprs] `(binding [~@(interleave fnames (for [fname fnames] `(let [f# @(var ~fname)] (fn [& args#] (my-trace-fn-call '~fname f# args#)))))] ~@exprs)) 

(Re-binding trace-fn-call around a regular dotrace does not seem to work, I assume that due to clojure.* Calls to Var it is still difficult to hook up the compiler, but this is a separate issue., Anyway.)

An alternative would be to use the above my-dotrace along with the my-trace-fn-call function, which does not use futures, but is modified to call custom replacements for the clojure.contrib.trace functions, using the following instead of str :

 (defn my-str [& args] (apply (.getRoot #'clojure.core/str) args)) 

The replacements are simple and tedious, and I omit them from the answer.

+9
source share

All Articles