Skip to content

Commit 9d5eaa4

Browse files
authored
Detect and throw on infinte loops during presentation (#776)
1 parent 01058bb commit 9d5eaa4

File tree

3 files changed

+34
-23
lines changed

3 files changed

+34
-23
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Changes can be:
88

99
## Unreleased
1010

11-
...
11+
* 💫 Detect and throw on infinte loops during presentation
1212

1313
## 0.18.1158 (2025-11-17)
1414

src/nextjournal/clerk/viewer.cljc

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1498,27 +1498,32 @@
14981498
(merge x (hoist-nested-wrapped-value (get-safe x :nextjournal/value)))
14991499
x))
15001500

1501-
(defn apply-viewers* [wrapped-value]
1502-
(let [hoisted-wrapped-value (hoist-nested-wrapped-value wrapped-value)
1503-
viewers (->viewers hoisted-wrapped-value)
1504-
_ (when (empty? viewers)
1505-
(throw (ex-info "cannot apply empty viewers" {:wrapped-value wrapped-value})))
1506-
{:as viewer viewers-to-add :add-viewers :keys [render-fn transform-fn]}
1507-
(viewer-for viewers hoisted-wrapped-value)
1508-
transformed-value (cond-> (ensure-wrapped-with-viewers viewers
1509-
(cond-> (-> hoisted-wrapped-value
1510-
(dissoc :nextjournal/viewer)
1511-
(assoc :nextjournal/applied-viewer viewer))
1512-
transform-fn transform-fn))
1513-
viewers-to-add (update :nextjournal/viewers add-viewers viewers-to-add))
1514-
wrapped-value' (cond-> transformed-value
1515-
(-> transformed-value ->value wrapped-value?)
1516-
(merge (->value transformed-value)))]
1517-
(if (and transform-fn (not render-fn))
1518-
(recur wrapped-value')
1519-
(-> wrapped-value'
1520-
(assoc :nextjournal/viewer viewer)
1521-
(merge (->opts wrapped-value))))))
1501+
(defn apply-viewers*
1502+
[wrapped-value]
1503+
(loop [recursion-counter 0
1504+
wrapped-value wrapped-value]
1505+
(let [hoisted-wrapped-value (hoist-nested-wrapped-value wrapped-value)
1506+
viewers (->viewers hoisted-wrapped-value)
1507+
_ (when (empty? viewers)
1508+
(throw (ex-info "cannot apply empty viewers" {:wrapped-value wrapped-value})))
1509+
{:as viewer viewers-to-add :add-viewers :keys [render-fn transform-fn]}
1510+
(viewer-for viewers hoisted-wrapped-value)
1511+
transformed-value (cond-> (ensure-wrapped-with-viewers viewers
1512+
(cond-> (-> hoisted-wrapped-value
1513+
(dissoc :nextjournal/viewer)
1514+
(assoc :nextjournal/applied-viewer viewer))
1515+
transform-fn transform-fn))
1516+
viewers-to-add (update :nextjournal/viewers add-viewers viewers-to-add))
1517+
wrapped-value' (cond-> transformed-value
1518+
(-> transformed-value ->value wrapped-value?)
1519+
(merge (->value transformed-value)))]
1520+
(if (and transform-fn (not render-fn))
1521+
(if (<= 10 recursion-counter)
1522+
(throw (ex-info "infinity" wrapped-value'))
1523+
(recur (inc recursion-counter) wrapped-value'))
1524+
(-> wrapped-value'
1525+
(assoc :nextjournal/viewer viewer)
1526+
(merge (->opts wrapped-value)))))))
15221527

15231528
(defn apply-viewers [x]
15241529
(apply-viewers* (ensure-wrapped-with-viewers x)))

test/nextjournal/clerk/viewer_test.clj

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,13 @@
132132

133133
(testing "table viewer (with :transform-fn) width can be overriden"
134134
(is (= :full
135-
(:nextjournal/width (v/apply-viewers (v/table {:nextjournal.clerk/width :full} {:a [1] :b [2] :c [3]})))))))
135+
(:nextjournal/width (v/apply-viewers (v/table {:nextjournal.clerk/width :full} {:a [1] :b [2] :c [3]}))))))
136+
137+
(testing "infinite loop detection"
138+
(is (thrown? clojure.lang.ExceptionInfo
139+
(v/present (v/with-viewers [{:pred number?
140+
:transform-fn (v/update-val inc)}]
141+
1))))))
136142

137143
(deftest presenting-wrapped-values
138144
(testing "apply-viewers is invariant on wrapped values"

0 commit comments

Comments
 (0)