克洛朱尔的消失反射警告



一个简单的反射警告示例:

lein repl
user=> (set! *warn-on-reflection* true)
true
user=> (eval '(fn [x] (.length x)))
Reflection warning, NO_SOURCE_PATH:1:16 - reference to field length can't be resolved.
#object[user$eval2009$fn__2010 0x487ba4b8 "user$eval2009$fn__2010@487ba4b8"]

我想把它变成一个函数。但是反射警告去哪儿了呢?

//clojure/compile.java 63
RT.errPrintWriter()
.format("Reflection warning, %s:%d:%d - reference to field %s can't be resolved.n",
SOURCE_PATH.deref(), line, column, fieldName);
//clojure/RT.java 269
public static PrintWriter errPrintWriter(){
Writer w = (Writer) ERR.deref();
//clojure/RT.java 188
final static public Var ERR =
Var.intern(CLOJURE_NS, Symbol.intern("*err*"),
new PrintWriter(new OutputStreamWriter(System.err), true)).setDynamic();

好的,所以他们去系统错误。让我们捕获它的输出:

(def pipe-in (PipedInputStream.))
(def pipe-out (PipedOutputStream. pipe-in))
(System/setErr (PrintStream. pipe-out))
(defn reflection-check [fn-code]
(binding [*warn-on-reflection* true]
(let [x (eval fn-code)
;_ (.println (System/err) "foo") ; This correctly makes us return "foo".
n (.available pipe-in)
^bytes b (make-array Byte/TYPE n)
_ (.read pipe-in b)
s (apply str (mapv char b))]
s)))

但是,调用它不会发出警告,并且没有刷新似乎很有用:

(println "Reflection check:" (reflection-check '(fn [x] (.length x)))) ; no warning.

如何提取反射警告?

您已经正确发现了*err*是如何初始化的,但由于 var 是可重新绑定的,因此不能保证其当前值。REPL 经常将其重新绑定到其他东西,例如套接字。如果您想自己重定向它,您只需将*err*重新绑定到您选择的作家即可。

真的,我不确定即使*err*永远不会反弹,你的方法也会奏效。Clojure 运行时捕获了一个指向 System.err 原始值的指针,然后您要求 Java 运行时使用 System.err 的新值。Clojure当然不会知道这个新价值。JRE 是否保持额外的间接级别,以允许它在后台执行这些交换,即使对于已经捕获 System.err 的人也是如此?也许吧,但如果是这样,它没有记录在案。

不久前我遇到了类似的问题,并创建了一些以with-out-str为模型的辅助函数。 以下是您问题的解决方案:

(ns tst.demo.core
(:use tupelo.core tupelo.test) )
(defn reflection-check
[fn-code]
(let [err-str (with-err-str
(binding [*warn-on-reflection* true]
(eval fn-code)))]
(spyx err-str)))
(dotest
(reflection-check (quote (fn [x] (.length x)))))

结果:

-------------------------------
Clojure 1.10.1    Java 14
-------------------------------
err-str => "Reflection warning, /tmp/form-init3884945788481466752.clj:12:36 
- reference to field length can't be resolved.n"

请注意,bindinglet窗体可以按任一顺序排列,并且仍然有效。

以下是源代码:

(defmacro with-err-str
"Evaluates exprs in a context in which *err* is bound to a fresh
StringWriter.  Returns the string created by any nested printing
calls."
[& body]
`(let [s# (new java.io.StringWriter)]
(binding [*err* s#]
~@body
(str s#))))

如果需要捕获 JavaSystem.err流,则情况有所不同:

(defmacro with-system-err-str
"Evaluates exprs in a context in which JVM System/err is bound to a fresh
PrintStream.  Returns the string created by any nested printing calls."
[& body]
`(let [baos# (ByteArrayOutputStream.)
ps#   (PrintStream. baos#)]
(System/setErr ps#)
~@body
(System/setErr System/err)
(.close ps#)
(.toString baos#)))

请参阅此处的文档。

有 5 种变体(加上clojure.core/with-out-str

(:
  • with-err-str
  • with-system-out-str
  • with-system-err-str
  • discarding-system-out
  • discarding-system-err

源代码在这里。

最新更新