Skip to content

Instantly share code, notes, and snippets.

@cgrand
Last active April 26, 2019 06:47
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save cgrand/259fa9ee3553d642850501ed431fc74d to your computer and use it in GitHub Desktop.
For toolsmithes really caring about isolation
;; Clojure 1.6.0
=> *clojure-version*
{:major 1, :minor 6, :incremental 0, :qualifier nil}
=> (def env1 (create-clojure-env))
#'net.cgrand.quarantine/env1
=> (env1 *in* *out*)
clojure.core=> *clojure-version*
{:major 1, :minor 8, :incremental 0, :qualifier nil}
clojure.core=>
(defproject net.cgrand/quarantine "0.1.0-SNAPSHOT"
:description "Clojure in a function."
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}
:plugins [[com.pupeno/jar-copier "0.4.0"]]
:prep-tasks ["javac" "compile" "jar-copier"]
:jar-copier {:jars [[org.clojure/clojure "1.8.0"]]
:destination "resources/jars"}
:dependencies [[org.clojure/clojure "1.6.0"]])
(ns net.cgrand.quarantine)
(defn jar-url []
(with-open [in (.getResourceAsStream (or (.getContextClassLoader (Thread/currentThread))
(ClassLoader/getSystemClassLoader))
"jars/org.clojure/clojure.jar")]
(let [bbuf (byte-array (* 4 1024 1024))
tmpfile (java.io.File/createTempFile "isolation-" ".jar")]
(with-open [out (java.io.FileOutputStream. tmpfile)]
(loop []
(let [got (.read in bbuf)]
(when (<= 0 got)
(.write out bbuf 0 got)))))
(-> tmpfile .toURI .toURL))))
(defn create-clojure-env
"Returns a clojure env. A clojure env is a function of two arguments: in and out.
When it is called it starts a REPL on these streams.
All REPL started from the same Clojure env share the same namespaces.
Namespaces are isolated between envs.
Envs are sol isolated that they wouldn't recognize a keyword from another env."
[]
(let [cl (java.net.URLClassLoader. (into-array [(jar-url)]) nil)
in-quarantine
(fn [stub]
(let [ccl (.getContextClassLoader (Thread/currentThread))]
(.setContextClassLoader (Thread/currentThread) cl)
(try
(stub)
(finally
(.setContextClassLoader (Thread/currentThread) ccl)))))
clj (in-quarantine #(Class/forName "clojure.java.api.Clojure" true cl))
IFn (Class/forName "clojure.lang.IFn" true cl)
varm (.getMethod clj "var" (into-array [Object]))
readm (.getMethod clj "read" (into-array [String]))
evalv (.invoke varm clj (to-array ["clojure.core/eval"]))
invoke1 (.getMethod IFn "invoke" (into-array [Object]))
invoke2 (.getMethod IFn "invoke" (into-array [Object Object]))
eval (fn [x]
(in-quarantine
#(let [form (.invoke readm clj (to-array [(pr-str x)]))]
(.invoke invoke1 evalv (to-array [form])))))
repl (eval '(fn [in out]
(binding [*in* (clojure.lang.LineNumberingPushbackReader. in)
*out* out]
(clojure.main/repl))))]
(fn [in out]
(in-quarantine
#(.invoke invoke2 repl (to-array [in out]))))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment