Skip to content

Commit e7a027f

Browse files
authored
Fix some uses of eval'ed code using string interpolation (#939)
Fixes #938
1 parent e535e78 commit e7a027f

File tree

5 files changed

+147
-104
lines changed

5 files changed

+147
-104
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2424
* Fix a bug where Basilisp vectors were not callable (#932)
2525
* Fix a bug where `basilisp.lang.seq.LazySeq` instances were not thread-safe (#934)
2626
* Fix a bug where Seqs wrapping Python Iterable instances were not thread-safe (#936)
27+
* Fix several bugs where code was being executed from a string with interpolated variables, which could've allowed for code (#938)
2728

2829
### Other
2930
* Add several sections to Concepts documentation module (#666)

src/basilisp/cli.py

Lines changed: 28 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,21 @@
1010

1111
from basilisp import main as basilisp
1212
from basilisp.lang import compiler as compiler
13+
from basilisp.lang import keyword as kw
14+
from basilisp.lang import list as llist
15+
from basilisp.lang import map as lmap
1316
from basilisp.lang import reader as reader
1417
from basilisp.lang import runtime as runtime
1518
from basilisp.lang import symbol as sym
1619
from basilisp.lang import vector as vec
1720
from basilisp.lang.exception import print_exception
21+
from basilisp.lang.util import munge
1822
from basilisp.prompt import get_prompter
1923

2024
CLI_INPUT_FILE_PATH = "<CLI Input>"
2125
REPL_INPUT_FILE_PATH = "<REPL Input>"
2226
REPL_NS = "basilisp.repl"
27+
NREPL_SERVER_NS = "basilisp.contrib.nrepl-server"
2328
STDIN_INPUT_FILE_PATH = "<stdin>"
2429
STDIN_FILE_NAME = "-"
2530

@@ -49,10 +54,8 @@ def eval_str(s: str, ctx: compiler.CompilerContext, ns: runtime.Namespace, eof:
4954

5055
def eval_file(filename: str, ctx: compiler.CompilerContext, ns: runtime.Namespace):
5156
"""Evaluate a file with the given name into a Python module AST node."""
52-
if os.path.exists(filename):
53-
return eval_str(
54-
f'(load-file "{Path(filename).as_posix()}")', ctx, ns, eof=object()
55-
)
57+
if (path := Path(filename)).exists():
58+
return compiler.load_file(path, ctx, ns)
5659
else:
5760
raise FileNotFoundError(f"Error: The file {filename} does not exist.")
5861

@@ -62,14 +65,23 @@ def eval_namespace(
6265
):
6366
"""Evaluate a file with the given name into a Python module AST node."""
6467
path = "/" + "/".join(namespace.split("."))
65-
return eval_str(f'(load "{path}")', ctx, ns, eof=object())
68+
return compiler.load(path, ctx, ns)
6669

6770

6871
def bootstrap_repl(ctx: compiler.CompilerContext, which_ns: str) -> types.ModuleType:
6972
"""Bootstrap the REPL with a few useful vars and returned the bootstrapped
7073
module so it's functions can be used by the REPL command."""
71-
ns = runtime.Namespace.get_or_create(sym.symbol(which_ns))
72-
eval_str(f"(ns {sym.symbol(which_ns)} (:use basilisp.repl))", ctx, ns, object())
74+
which_ns_sym = sym.symbol(which_ns)
75+
ns = runtime.Namespace.get_or_create(which_ns_sym)
76+
compiler.compile_and_exec_form(
77+
llist.l(
78+
sym.symbol("ns", ns=runtime.CORE_NS),
79+
which_ns_sym,
80+
llist.l(kw.keyword("use"), sym.symbol(REPL_NS)),
81+
),
82+
ctx,
83+
ns,
84+
)
7385
return importlib.import_module(REPL_NS)
7486

7587

@@ -345,22 +357,15 @@ def nrepl_server(
345357
) -> None:
346358
opts = compiler.compiler_opts()
347359
basilisp.init(opts)
348-
349-
ctx = compiler.CompilerContext(filename=REPL_INPUT_FILE_PATH, opts=opts)
350-
eof = object()
351-
352-
ns = runtime.Namespace.get_or_create(runtime.CORE_NS_SYM)
353-
host = runtime.lrepr(args.host)
354-
port = args.port
355-
port_filepath = runtime.lrepr(args.port_filepath)
356-
eval_str(
357-
(
358-
"(require '[basilisp.contrib.nrepl-server :as nr])"
359-
f"(nr/start-server! {{:host {host} :port {port} :nrepl-port-file {port_filepath}}})"
360-
),
361-
ctx,
362-
ns,
363-
eof,
360+
nrepl_server_mod = importlib.import_module(munge(NREPL_SERVER_NS))
361+
nrepl_server_mod.start_server__BANG__(
362+
lmap.map(
363+
{
364+
kw.keyword("host"): args.host,
365+
kw.keyword("port"): args.port,
366+
kw.keyword("nrepl-port-file"): args.port_filepath,
367+
}
368+
)
364369
)
365370

366371

src/basilisp/contrib/nrepl_server.lpy

Lines changed: 80 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -66,42 +66,40 @@
6666
(declare ops)
6767

6868
(defn- handle-describe [request send-fn]
69-
(send-fn request
70-
{"versions" {"basilisp" (let [version basilisp.lang.runtime/BASILISP-VERSION-STRING]
71-
(assoc (zipmap ["major" "minor" "incremental"]
72-
version)
73-
"version-string" (str/join "." version)))
74-
"python" (let [version (get (.split sys/version " ") 0)]
75-
(assoc (zipmap ["major" "minor" "incremental"]
76-
(py->lisp (.split version ".")))
77-
"version-string" (py->lisp sys/version)))}
78-
"ops" (zipmap (map name (keys ops)) (repeat {}))
79-
"status" ["done"]}))
69+
(let [basilisp-version (-> (zipmap ["major" "minor" "incremental"] *basilisp-version*)
70+
(assoc "version-string" (str/join "." *basilisp-version*)))
71+
python-version (-> (zipmap ["major" "minor" "incremental"] *python-version*)
72+
(assoc "version-string" (str/join "." *python-version*)))]
73+
(send-fn request
74+
{"versions" {"basilisp" basilisp-version
75+
"python" python-version}
76+
"ops" (zipmap (map name (keys ops)) (repeat {}))
77+
"status" ["done"]})))
8078

8179
(defn- format-value [_nrepl-pprint _pprint-options value]
8280
(pr-str value))
8381

8482
(defn- send-value [request send-fn v]
85-
(let [{:keys [client*]} request
86-
{:keys [*1 *2]} @client*
87-
[v opts] v
88-
ns (:ns opts)]
83+
(let [{:keys [client*]} request
84+
{:keys [*1 *2]} @client*
85+
[v opts] v
86+
ns (:ns opts)]
8987
(swap! client* assoc :*1 v :*2 *1 :*3 *2)
9088
(let [v (format-value (:nrepl.middleware.print/print request)
9189
(:nrepl.middleware.print/options request)
9290
v)]
9391
(send-fn request {"value" (str v)
94-
"ns" (str ns)}))))
92+
"ns" (str ns)}))))
9593

9694
(defn- handle-error [send-fn request e]
9795
(let [{:keys [client* ns]} request
9896
data (ex-data e)
9997
message (or (:message data) (str e))]
10098
(swap! client* assoc :*e e)
10199
(send-fn request {"err" (str message)})
102-
(send-fn request {"ex" (traceback/format-exc)
100+
(send-fn request {"ex" (traceback/format-exc)
103101
"status" ["eval-error"]
104-
"ns" ns})))
102+
"ns" ns})))
105103

106104
(defn- do-handle-eval
107105
"Evaluate the ``request`` ``code`` of ``file`` in the ``ns`` namespace according
@@ -125,9 +123,12 @@
125123
eval-ns (if ns
126124
(create-ns (symbol ns))
127125
eval-ns)]
128-
(binding [*ns* eval-ns
126+
(binding [*ns* eval-ns
129127
*out* out-stream
130-
*1 *1 *2 *2 *3 *3 *e *e]
128+
*1 *1
129+
*2 *2
130+
*3 *3
131+
*e *e]
131132
(try
132133
(let [results (for [form (seq (basilisp.lang.reader/read reader
133134
*resolver*
@@ -142,7 +143,7 @@
142143
(handle-error send-fn (assoc request :ns (str *ns*)) e))
143144
(finally
144145
(swap! client* assoc :eval-ns *ns*)
145-
(send-fn request {"ns" (str *ns*)
146+
(send-fn request {"ns" (str *ns*)
146147
"status" ["done"]}))))))
147148

148149
(defn- handle-eval [request send-fn]
@@ -179,9 +180,9 @@
179180
(first (seq (basilisp.lang.reader/read reader
180181
*resolver*
181182
*data-readers*))))}
182-
(catch python/Exception e
183-
(info :symbol-identify-reader-error :input symbol-str :exception e)
184-
{:error (str e)}))]
183+
(catch python/Exception e
184+
(info :symbol-identify-reader-error :input symbol-str :exception e)
185+
{:error (str e)}))]
185186

186187
(cond
187188
error
@@ -201,12 +202,9 @@
201202
(catch python/Exception e
202203
{:error (str e)}))]
203204
(cond
204-
var
205-
[:var var]
206-
error
207-
[:error error]
208-
:else
209-
[:other form])))))
205+
var [:var var]
206+
error [:error error]
207+
:else [:other form])))))
210208

211209
(defn- forms-join [forms]
212210
(->> (map pr-str forms)
@@ -221,36 +219,40 @@
221219
(let [mapping-type (-> request :op)
222220
{:keys [eval-ns]} @client*]
223221
(try
224-
(let [lookup-ns (if ns
225-
(create-ns (symbol ns))
226-
eval-ns)
227-
sym-str (or (:sym request) ;; cider
228-
(:symbol request) ;; calva
229-
)
222+
(let [lookup-ns (if ns
223+
(create-ns (symbol ns))
224+
eval-ns)
225+
sym-str (or (:sym request) ;; cider
226+
(:symbol request) ;; calva
227+
)
228+
230229
[tp var-maybe] (symbol-identify lookup-ns sym-str)
231230
var-meta (when (= tp :var) (meta var-maybe))
231+
232232
{:keys [arglists doc file ns line] symname :name} var-meta
233-
ref (when (= tp :var) (var-get var-maybe))
234-
response (when symname (case mapping-type
235-
:eldoc (cond->
236-
{"eldoc" (mapv #(mapv str %) arglists)
237-
"ns" (str ns)
238-
"type" (if (fn? ref)
239-
"function"
240-
"variable")
241-
"name" (str symname)
242-
"status" ["done"]}
243-
doc (assoc "docstring" doc))
244-
:info {"doc" doc
245-
"ns" (str ns)
246-
"name" (str symname)
247-
"file" file
248-
"line" line
249-
"arglists-str" (forms-join arglists)
250-
"status" ["done"]}))
251-
status (if (and (nil? symname) (= mapping-type :eldoc) )
252-
["done" "no-eldoc"]
253-
["done"])]
233+
234+
ref (when (= tp :var) (var-get var-maybe))
235+
response (when symname
236+
(case mapping-type
237+
:eldoc (cond->
238+
{"eldoc" (mapv #(mapv str %) arglists)
239+
"ns" (str ns)
240+
"type" (if (fn? ref)
241+
"function"
242+
"variable")
243+
"name" (str symname)
244+
"status" ["done"]}
245+
doc (assoc "docstring" doc))
246+
:info {"doc" doc
247+
"ns" (str ns)
248+
"name" (str symname)
249+
"file" file
250+
"line" line
251+
"arglists-str" (forms-join arglists)
252+
"status" ["done"]}))
253+
status (if (and (nil? symname) (= mapping-type :eldoc) )
254+
["done" "no-eldoc"]
255+
["done"])]
254256
(debug :lookup :sym sym-str :doc doc :args arglists)
255257
(send-fn request (assoc response :status status)))
256258
(catch python/Exception e
@@ -308,7 +310,8 @@
308310
"status" ["done"]})))
309311

310312
(def ops
311-
"A list of operations supported by the nREPL server."
313+
"A map of operations supported by the nREPL server (as keywords) to function
314+
handlers for those operations."
312315
{:eval handle-eval
313316
:describe handle-describe
314317
:info handle-lookup
@@ -353,22 +356,26 @@
353356

354357
- ``:recv-buffer-size`` The buffer size to using for incoming nREPL messages."
355358
(let [{:keys [recv-buffer-size]
356-
:or {recv-buffer-size 1024}} opts
357-
socket (.-request tcp-req-handler)
358-
handler (make-request-handler opts)
359-
response-handler (make-reponse-handler socket)
360-
pending (atom nil)
361-
zero-bytes #b ""
362-
client-info (py->lisp (.getsockname socket))
363-
client* (atom {:*1 nil :*2 nil ;; keeps track of the latest
364-
:*3 nil :*e nil ;; evaluation results
365-
:eval-ns nil ;; the last eval ns
366-
})]
359+
:or {recv-buffer-size 1024}} opts
360+
socket (.-request tcp-req-handler)
361+
handler (make-request-handler opts)
362+
response-handler (make-reponse-handler socket)
363+
pending (atom nil)
364+
zero-bytes #b ""
365+
client-info (py->lisp (.getsockname socket))
366+
client* (atom {;; keeps track of latest evaluation results
367+
:*1 nil
368+
:*2 nil
369+
:*3 nil
370+
:*e nil
371+
;; the last eval ns
372+
:eval-ns nil
373+
})]
367374
(try
368375
(info "Connection accepted" :info client-info)
369376
;; Need to load the `clojure.core` alias because cider uses it
370377
;; to test for availability of features.
371-
(eval (read-string "(ns user (:require clojure.core))"))
378+
(eval '(ns user (:require clojure.core)))
372379
(swap! client* assoc :eval-ns *ns*)
373380
(loop [data (.recv socket recv-buffer-size)]
374381
(if (= data zero-bytes)
@@ -380,7 +387,7 @@
380387
b)
381388
data)
382389
[requests unprocessed] (bc/decode-all data {:keywordize-keys true
383-
:string-fn #(.decode % "utf-8")})]
390+
:string-fn #(.decode % "utf-8")})]
384391
(debug :requests requests)
385392
(when (not (str/blank? unprocessed))
386393
(reset! pending unprocessed))
@@ -423,7 +430,8 @@
423430
The session UUIDs are ignored and only created to satisfy the initial clone op."
424431
[opts]
425432
(let [{:keys [host port] :or {host "127.0.0.1" port 0}} opts
426-
handler (python/type (name (gensym "nREPLTCPHandler")) (python/tuple [socketserver/StreamRequestHandler])
433+
handler (python/type (name (gensym "nREPLTCPHandler"))
434+
#py (socketserver/StreamRequestHandler)
427435
#py {"handle" #(on-connect % opts)})]
428436
(socketserver/ThreadingTCPServer (python/tuple [host port]) handler)))
429437

src/basilisp/importer.py

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -265,16 +265,9 @@ def get_code(self, fullname: str) -> Optional[types.CodeType]:
265265
# The target namespace is free to interpret
266266
code: List[types.CodeType] = []
267267
path = "/" + "/".join(fullname.split("."))
268-
forms = cast(
269-
List[ReaderForm],
270-
list(
271-
reader.read_str(f'(load "{path}")', resolver=runtime.resolve_alias)
272-
),
273-
)
274-
assert len(forms) == 1
275268
try:
276-
compiler.compile_and_exec_form(
277-
forms[0],
269+
compiler.load(
270+
path,
278271
compiler.CompilerContext(
279272
filename="<Basilisp Namespace Executor>",
280273
opts=runtime.get_compiler_opts(),

0 commit comments

Comments
 (0)