From 4197294987cdd85a8c909328c6fc5d5ba92fa7c6 Mon Sep 17 00:00:00 2001 From: Michiel Borkent Date: Sat, 17 Oct 2020 15:30:19 +0200 Subject: [PATCH] [#429] Drop support for :realize-max / :preset :termination-safe --- CHANGELOG.md | 12 ++++++ README.md | 21 ++--------- src/sci/impl/interpreter.cljc | 4 +- src/sci/impl/max_or_throw.cljc | 67 ---------------------------------- src/sci/impl/opts.cljc | 15 ++------ test/sci/core_test.cljc | 31 ---------------- 6 files changed, 21 insertions(+), 129 deletions(-) delete mode 100644 src/sci/impl/max_or_throw.cljc diff --git a/CHANGELOG.md b/CHANGELOG.md index b0b60c86..6832fc59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ For a list of breaking changes, check [here](#breaking-changes) +### >= 0.1.0 (unreleased) + +- Removed `:realize-max` and `:preset :termination-safe`. In the light of + [#348](https://github.com/borkdude/sci/issues/348) it would be misleading to + claim that sci can guarantee termination within reasonable time. + ## v0.1.0 (2020-06-16) Thank to [@jeroenvandijk](https://github.com/jeroenvandijk), [@jjttjj](https://github.com/jjttjj), [@justone](https://github.com/justone), [@sogaiu](https://github.com/sogaiu) and [@armincerf](https://github.com/armincerf) for @@ -85,6 +91,12 @@ Details about releases prior to v0.1.0 can be found ## Breaking changes +### >= 0.1.0 + +- Removed `:realize-max` and `:preset :termination-safe`. In the light of + [#348](https://github.com/borkdude/sci/issues/348) it would be misleading to + claim that sci can guarantee termination within reasonable time. + ### v0.0.12 - `:row` and `:col` metadata have been renamed to `:line` and `:column` to be diff --git a/README.md b/README.md index f32d9995..cad1922b 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,9 @@ This library works with: - Clojure compiled with GraalVM native - ClojureScript, even when compiled with `:advanced`, and (as a consequence) JavaScript -It is used in: +## Projects using sci + +Sci is used in: - [Babashka](https://github.com/borkdude/babashka). A Clojure scripting tool that plays well with Bash. - [Bootleg](https://github.com/retrogradeorbit/bootleg). An HTML templating CLI. @@ -49,6 +51,7 @@ It is used in: - [Logseq](https://logseq.com). A local-only outliner notebook which supports both Markdown and Org mode. - [Malli](https://github.com/metosin/malli). Plain data Schemas for Clojure/Script. - [PCP](https://github.com/alekcz/pcp). Clojure Processor (PHP replacement). +- [Prose](https://github.com/JeremS/prose). Alternate syntax for Clojure, similar to what Pollen brings to Racket. - [Spire](https://github.com/epiccastle/spire). Pragmatic provisioning using Clojure. ## Status @@ -114,22 +117,6 @@ user=> (sci/eval-string "(inc 1)" {:deny '[inc]}) ExceptionInfo inc is not allowed! [at line 1, column 2] clojure.core/ex-info (core.clj:4739) ``` -Preventing forever lasting evaluation of infinite sequences can be achieved with -`:realize-max`: - -``` clojure -user=> (sci/eval-string "(vec (range))" {:realize-max 10}) -ExceptionInfo Maximum number of elements realized: 10 [at line 1, column 1] clojure.core/ex-info (core.clj:4739) -``` - -The preset `:termination-safe`, which is currently `{:deny '[loop recur -trampoline] :realize-max 100}`, is helpful for making expressions terminate: - -``` clojure -user=> (sci/eval-string "(loop [] (recur))" {:preset :termination-safe}) -ExceptionInfo loop is not allowed! [at line 1, column 2] clojure.core/ex-info (core.clj:4739) -``` - Providing a macro as a binding can be done by providing a normal function that: - has `:sci/macro` on the metadata set to `true` - has two extra arguments at the start for `&form` and `&env`: diff --git a/src/sci/impl/interpreter.cljc b/src/sci/impl/interpreter.cljc index 47c6bb40..77ea1408 100644 --- a/src/sci/impl/interpreter.cljc +++ b/src/sci/impl/interpreter.cljc @@ -8,7 +8,6 @@ [sci.impl.fns :as fns] [sci.impl.interop :as interop] [sci.impl.macros :as macros] - [sci.impl.max-or-throw :refer [max-or-throw]] [sci.impl.opts :as opts] [sci.impl.parser :as p] [sci.impl.records :as records] @@ -639,7 +638,8 @@ ret)] ;; for debugging: ;; (prn expr (meta expr) '-> ret) - (if-let [n (.get ^java.util.Map ctx :realize-max)] + ret + #_(if-let [n (.get ^java.util.Map ctx :realize-max)] (max-or-throw ret (assoc ctx :expression expr) n) diff --git a/src/sci/impl/max_or_throw.cljc b/src/sci/impl/max_or_throw.cljc deleted file mode 100644 index b060d3d3..00000000 --- a/src/sci/impl/max_or_throw.cljc +++ /dev/null @@ -1,67 +0,0 @@ -(ns sci.impl.max-or-throw - {:no-doc true}) - -(defprotocol MaxOrThrow - (max-or-throw [this ctx n])) - -(defn bottom [n data] - (lazy-seq (throw (ex-info (str "Maximum number of elements realized: " n) - data)))) - -(defn take* - ([n coll err-val] - (lazy-seq - (if (pos? n) - (when-let [s (seq coll)] - (cons (first s) (take* (dec n) (rest s) err-val))) - err-val)))) - -(defn take-or-throw [coll ctx n] - (take* n coll - (bottom n (merge {:type :sci.error/realized-beyond-max - :realize-max n - :expression (:expression ctx)} - (select-keys (meta (:expression ctx)) - [:file :line :column]))))) - -(extend-protocol MaxOrThrow - - nil - (max-or-throw - ([this ctx n] this)) - - #?(:clj Object :cljs default) - (max-or-throw - ([this ctx n] this)) - - #?(:clj clojure.lang.LazySeq :cljs LazySeq) - (max-or-throw - ([this ctx n] - (take-or-throw this ctx n))) - - #?(:clj clojure.lang.Cons :cljs Cons) - (max-or-throw - ([this ctx n] - (take-or-throw this ctx n))) - - #?(:clj clojure.lang.Range :cljs Range) - (max-or-throw - ([this ctx n] - (take-or-throw this ctx n))) - - #?@(:clj [clojure.lang.LongRange - (max-or-throw - ([this ctx n] - (take-or-throw this ctx n)))]) - - #?(:clj clojure.lang.Iterate :cljs Iterate) - (max-or-throw - ([this ctx n] - (take-or-throw this ctx n))) - - #?(:clj clojure.lang.Repeat :cljs Repeat) - (max-or-throw - ([this ctx n] - ;; (prn "TYPE" (type this)) - (take-or-throw this ctx n))) - ) diff --git a/src/sci/impl/opts.cljc b/src/sci/impl/opts.cljc index 13b8b62e..314e570c 100644 --- a/src/sci/impl/opts.cljc +++ b/src/sci/impl/opts.cljc @@ -25,11 +25,6 @@ :imports imports :load-fn load-fn))))) -(def presets - {:termination-safe - {:deny '[loop recur trampoline resolve] - :realize-max 100}}) - (defn process-permissions [& permissions] (not-empty (into #{} (comp cat (map strip-core-ns)) permissions))) @@ -93,8 +88,6 @@ "Initializes options" [{:keys [:bindings :env :allow :deny - :realize-max - :preset ;; used by malli :aliases :namespaces :classes @@ -104,16 +97,14 @@ :uberscript ;; used by babashka, not public! :readers :reify]}] - (let [preset (get presets preset) - env (or env (atom {})) + (let [env (or env (atom {})) imports (merge default-imports imports) bindings bindings _ (init-env! env bindings aliases namespaces imports load-fn) ctx (merge {:env env :bindings {} - :allow (process-permissions (:allow preset) allow) - :deny (process-permissions (:deny preset) deny) - :realize-max (or realize-max (:realize-max preset)) + :allow (process-permissions allow) + :deny (process-permissions deny) :features features :readers readers ::ctx true diff --git a/test/sci/core_test.cljc b/test/sci/core_test.cljc index 086f0ff9..19664622 100644 --- a/test/sci/core_test.cljc +++ b/test/sci/core_test.cljc @@ -319,16 +319,6 @@ (is (thrown-with-msg? #?(:clj Exception :cljs js/Error) #"allowed" (tu/eval* "(clojure.core/inc 1)" {:deny '[clojure.core/inc]}))) - (is (thrown-with-msg? #?(:clj Exception :cljs js/Error) - #"allowed" - (tu/eval* "(clojure.core/loop [] (recur))" {:preset :termination-safe}))) - (is (thrown-with-msg? #?(:clj Exception :cljs js/Error) - #"allowed" - (tu/eval* "(eval '(loop [] (recur)))" {:preset :termination-safe}))) - (is (thrown-with-msg? #?(:clj Exception :cljs js/Error) - #"allowed" - (tu/eval* "(resolve 'trampoline)" {:preset :termination-safe}))) - (testing "for/doseq/dotimes use loop in a safe manner, so `{:deny '[loop recur]}` should not forbid it, see #141" (is '(1 2 3) (tu/eval* "(for [i [1 2 3] j [4 5 6]] [i j])" {:deny '[loop recur]})) (is (nil? (tu/eval* "(doseq [i [1 2 3]] i)" {:deny '[loop recur]}))) @@ -351,24 +341,6 @@ (testing "users cannot hack around sci.impl/needs-ctx" (is (= [1 2] (eval* "(def f (with-meta (fn [ctx x] [ctx x]) {:sci.impl/needs-ctx true})) (f 1 2)"))))) -(deftest realize-max-test - (when-not tu/native? - (let [d (try (tu/eval* "(reduce (fn [_ _]) (range 1000))" {:realize-max 100}) - (catch #?(:clj clojure.lang.ExceptionInfo :cljs ExceptionInfo) e - (ex-data e)))] - (is (= :sci.error/realized-beyond-max (:type d))))) - (is (thrown-with-msg? #?(:clj clojure.lang.ExceptionInfo :cljs ExceptionInfo) - #"realized" - (tu/eval* "(reduce (fn [_ _]) (range 1000))" {:realize-max 100}))) - (is (thrown-with-msg? #?(:clj clojure.lang.ExceptionInfo :cljs ExceptionInfo) - #"realized" - (doall (tu/eval* "(repeat 1)" {:realize-max 100})))) - (is (thrown-with-msg? #?(:clj clojure.lang.ExceptionInfo :cljs ExceptionInfo) - #"realized" - (doall (tu/eval* "(repeat 1)" {:preset :termination-safe})))) - (is (= (range 10) (tu/eval* "(range 10)" {:realize-max 100}))) - (is (= (map #(* % %) (range 99)) (tu/eval* "(map #(* % %) (range 99))" {:realize-max 100})))) - (deftest idempotent-eval-test (is (= '(foo/f1 foo/f2) (eval* "(map #(let [[ns v] %] (symbol (str ns) (str v))) '[[foo f1] [foo f2]])"))) @@ -382,9 +354,6 @@ (is (thrown-with-data? {:line 1 :column 11} (with-out-str (eval* nil "(+ 1 2 3) (conj 1 0)")))) - (is (thrown-with-data? - {:line 1 :column 18} - (tu/eval* "(+ 1 2 3 4) (vec (range))" {:realize-max 100}))) (is (thrown-with-data? {:line 1 :column 19} (eval* "(+ 1 2 3 4 5) (do x)")))