请分享您的想法,参加 2024 年 Clojure 状态调查!

欢迎!请查看 关于 页面以了解更多关于如何工作的信息。

+2
IO
重新标记

以下对 cl-format 的调用导致了 Java 级的空指针异常。

(cl-format false "
~@")

当然,这个代码示例没什么意义。导致这个最小测试用例的实际例子如下。

(cl-format false "~&~
                            item=~A~@
                            transitions=~A~@
                            lhs=~A~@
                            rhs=~A~@"
                      1
                      2
                      3
                      4
                      )

据我所知,问题似乎出在与最后的 ~@ 处理上,在 cl_format.clj 中的函数 compile-directive 中 Clojure 代码似乎(我在猜测)解构了 extract-paramsnil 返回值。
cl_format.clj 中的函数 compile-directive

(defn- compile-directive [s offset]
  (let [[raw-params [rest offset]] (extract-params s offset)
        [_ [rest offset flags]] (extract-flags rest offset)
        directive (first rest)
        def (get directive-table (Character/toUpperCase ^Character directive))
        params (if def (map-params def (map translate-param raw-params) flags offset))]
    (if (not directive)
      (format-error "Format string ended in the middle of a directive" offset))
    (if (not def)
      (format-error (str "Directive \"" directive "\" is undefined") offset))
    [(struct compiled-directive ((:generator-fn def) params offset) def params offset)
     (let [remainder (subs rest 1) 
           offset (inc offset)
           trim? (and (= \newline (:directive def))
                      (not (:colon params)))
           trim-count (if trim? (prefix-count remainder [\space \tab]) 0)
           remainder (subs remainder trim-count)
           offset (+ offset trim-count)]
       [remainder offset])]))

以下是看到的堆栈跟踪。

1. Unhandled java.lang.NullPointerException
   (No message)

             cl_format.clj: 1717  clojure.pprint/compile-directive
             cl_format.clj: 1861  clojure.pprint/compile-format/fn
                  AFn.java:  154  clojure.lang.AFn/applyToHelper
                  AFn.java:  144  clojure.lang.AFn/applyTo
                  core.clj:  665  clojure.core/apply
             utilities.clj:   37  clojure.pprint/consume
             cl_format.clj: 1845  clojure.pprint/compile-format
             cl_format.clj: 1845  clojure.pprint/compile-format
             cl_format.clj:   62  clojure.pprint/cl-format
             cl_format.clj:   27  clojure.pprint/cl-format
               RestFn.java:  521  clojure.lang.RestFn/invoke
                      REPL: 1391  clojure-rte.rte-core/eval24978
                      REPL: 1391  clojure-rte.rte-core/eval24978
             Compiler.java: 7176  clojure.lang.Compiler/eval
             Compiler.java: 7131  clojure.lang.Compiler/eval
                  core.clj: 3214  clojure.core/eval
                  core.clj: 3210  clojure.core/eval
    interruptible_eval.clj:   87  nrepl.middleware.interruptible-eval/evaluate/fn/fn
                  AFn.java:  152  clojure.lang.AFn/applyToHelper
                  AFn.java:  144  clojure.lang.AFn/applyTo
                  core.clj:  665  clojure.core/apply
                  core.clj: 1973  clojure.core/with-bindings*
                  core.clj: 1973  clojure.core/with-bindings*
               RestFn.java:  425  clojure.lang.RestFn/invoke
    interruptible_eval.clj:   87  nrepl.middleware.interruptible-eval/evaluate/fn
                  main.clj:  414  clojure.main/repl/read-eval-print/fn
                  main.clj:  414  clojure.main/repl/read-eval-print
                  main.clj:  435  clojure.main/repl/fn
                  main.clj:  435  clojure.main/repl
                  main.clj:  345  clojure.main/repl
               RestFn.java: 1523  clojure.lang.RestFn/invoke
    interruptible_eval.clj:   84  nrepl.middleware.interruptible-eval/evaluate
    interruptible_eval.clj:   56  nrepl.middleware.interruptible-eval/evaluate
    interruptible_eval.clj:  152  nrepl.middleware.interruptible-eval/interruptible-eval/fn/fn
                  AFn.java:   22  clojure.lang.AFn/run
               session.clj:  202  nrepl.middleware.session/session-exec/main-loop/fn
               session.clj:  201  nrepl.middleware.session/session-exec/main-loop
                  AFn.java:   22  clojure.lang.AFn/run
               Thread.java:  834  java.lang.Thread/run

1 个答案

+1

经过一番研究,我现在对错误有了更好的理解。提供给 cl-format 的格式字符串可以包含零个或多个 指令。每个指令以 ~ 开头,以 ~ 和分配字符(如 %as 等)之间的单个分配字符结束。在 ~ 和分配字符之间可以找到一些中间项,如 ::@ 顺序,以及可能的参数,如 ~2,4f~3,-4s~3,-4:@s::@ 只是简单的布尔标志,用于传递给分配函数。

在我的情况下,我给它加上了 ~@ 符号,然后是字符串的结束,因此理想的 cl-format 应该在找到发送字符之前检测到字符串的结束。

以下是我在 common-lisp 中遇到的错误

CL-USER> (format nil "~@")
error in FORMAT: String ended before directive was found
  ~@
  ^
   [Condition of type SB-FORMAT:FORMAT-ERROR]

Restarts:
 0: [RETRY] Retry SLIME REPL evaluation request.
 1: [*ABORT] Return to SLIME's top level.
 2: [ABORT] abort thread (#<THREAD "repl-thread" RUNNING {1004289BC3}>)

Backtrace:
  0: (SB-FORMAT::FORMAT-ERROR-AT* "~@" 0 "String ended before directive was found" NIL)
  1: (SB-FORMAT::FORMAT-ERROR-AT "~@" 0 "String ended before directive was found")
  2: ((FLET SB-FORMAT::GET-CHAR :IN SB-FORMAT::PARSE-DIRECTIVE))
  3: (SB-FORMAT::PARSE-DIRECTIVE "~@" 0 NIL)
  4: (SB-FORMAT::%TOKENIZE-CONTROL-STRING "~@" 0 2 NIL)
  5: (SB-FORMAT::TOKENIZE-CONTROL-STRING "~@")
  6: (SB-FORMAT::%FORMAT #<SB-IMPL::CHARACTER-STRING-OSTREAM {1004883B33}> "~@" NIL NIL)
  7: (FORMAT NIL "~@")
  8: (FORMAT NIL "~@") [more]
  9: (SB-INT:SIMPLE-EVAL-IN-LEXENV (FORMAT NIL "~@") #<NULL-LEXENV>)
 10: (EVAL (FORMAT NIL "~@"))
by
顺便说一句,`cl_format.clj` 中的 `compile-directive` 函数似乎有代码来检测 `"格式字符串在指令中间结束"` 并调用 `format-error` 以显示有用的错误信息。不幸的是,Java 错误的到来似乎阻止了代码到达这里。
by
修改 by
我相信这个问题可以通过对这个 `compile-directive` 函数的补丁来修复。注意,我使用了没有 `else` 的 `if` 习惯用法,而以前的作者就是这样用的。这个更改简单地检测 `let` 中声明的 `directive` 是否为 `nil` 与字符不同。

```
(defn- compile-directive [s offset]
  (let [[raw-params [rest offset]] (extract-params s offset)
        [_ [rest offset flags]] (extract-flags rest offset)
        directive (first rest)
        def (if directive (get directive-table (Character/toUpperCase ^Character directive)))
        params (if def (map-params def (map translate-param raw-params) flags offset))]
    (if (not directive)
      (format-error "格式字符串在指令中间结束" offset))
    (if (not def)
      (format-error (str "指令 \"" directive "\" 未定义") offset))
    [(struct compiled-directive ((:generator-fn def) params offset) def params offset)
     (let [remainder (subs rest 1)
           offset (inc offset)
           trim? (and (= \newline (:directive def))
                      (not (:colon params)))
           trim-count (if trim? (prefix-count remainder [\space \tab]) 0)
           remainder (subs remainder trim-count)
           offset (+ offset trim-count)]
       [remainder offset])]))
```

通过这次修改,我得到了一个有意义的错误信息,我相信这是原作者的意图。

```
(cl-format nil "~:@")

在nrepl.middleware.interruptible-eval/evaluate$fn$fn (interruptible_eval.clj:87)处发生执行错误。
格式字符串在指令的中间结束
~:@
   ^

```
by
我尝试使用这个简单的补丁创建一个pull请求。  然而,我发现这个项目不接受pull请求。  我尽力按照说明来做,但我承认我已经迷失了方向。
by
我已经收到并记录了你的贡献者协议,并向你发送了jira的邀请。我在https://clojure.atlassian.net/browse/CLJ-2653处提交了工单。如果你喜欢,你可以按照https://clojure.org/dev/developing_patches上的说明制作补丁。谢谢!
by
这个补丁被拒绝了吗?  今天我又遇到了相同的问题,看样子Clojure 1.11.1中的代码仍然有同样的bug。

也许我在补丁中做错了什么?   

有人能帮帮我吗?
by
我们还没有查看它,很抱歉。
这是一个非常低风险的修复,在我看来。
...