【发布时间】:2021-11-14 14:56:11
【问题描述】:
使用 SBCL 的 Common Lisp 实现,是否有可能以某种方式呈现一个禁止使用特定关键字的 REPL(有效地提供对 Common Lisp 功能子集的访问)?
【问题讨论】:
标签: common-lisp keyword read-eval-print-loop sbcl restriction
使用 SBCL 的 Common Lisp 实现,是否有可能以某种方式呈现一个禁止使用特定关键字的 REPL(有效地提供对 Common Lisp 功能子集的访问)?
【问题讨论】:
标签: common-lisp keyword read-eval-print-loop sbcl restriction
检查defpackage 宏。
如果只想禁止部分功能,则使用:shadow关键字:
(defpackage "MY-PACKAGE"
(:use :common-lisp)
(:shadow :list :cons))
如果你想禁止一切,只允许某些功能,你将使用:import-from关键字:
(defpackage "ONLY-MATH"
(:use)
(:import-from :common-lisp :+ :- :* :/ :print))
然后在 REPL 中调用 (in-package "MY-PACKAGE") 或 (in-package "ONLY-MATH") 并尝试一些示例:
ONLY-MATH 2 > (+ 1 2)
3
ONLY-MATH 3 > (cons 1 2)
Error: Undefined operator CONS in form (CONS 1 2).
【讨论】:
虽然您只能导入特定符号,但这并不禁止它们的使用,因为您可以使用包前缀明确引用它们。
如果您想向用户公开他们不能滥用以操纵您的系统的 REPL,您将必须安全地阅读他们的输入并明确创建评估规则。
【讨论】:
(defpackage :limited-repl
(:use :cl :named-readtables)
(:export #:within-sandbox
#:limited-repl))
(in-package :limited-repl)
在这个答案中,我定义了一个 within-sandbox 宏和一个 limited-repl 函数,这样:
#\: 和#\# 等特殊字符,以防止用户访问其他包中的符号,并避免使用可能创建数组、位向量等以及读取器变量的语法(#1=和#1#),可用于创建在评估时无限循环的圆形结构。COMMON-LISP:NIL,以避免在自定义语言中引入此符号;相反,它读为零。我正在使用 Quicklisp 中提供的 named-readtables 库来定义自定义可读表。如有必要,这也可以通过 Common Lisp 实现。
(ql:quickload :named-readtables)
让我们为禁止字符定义一个自定义条件,这比使用(error "some string") 更简洁:
(define-condition forbidden-character (error)
((character :initarg :character :reader forbidden-character.character)
(stream :initarg :stream :reader forbidden-character.stream))
(:report (lambda (condition stream)
(format stream
"Forbidden character ~@c"
(forbidden-character.character condition)))))
让我们也定义一个读取器函数,该函数会为正在读取的字符发出错误信号:
(defun forbidden-character (stream character)
(error 'forbidden-character
:character character
:stream stream))
这是在空列表上返回零的列表读取器:
(defun read-zero-list (stream character)
(assert (char= character #\())
(let ((list (read-delimited-list #\) stream t)))
(etypecase list
(null 0)
(cons list))))
limited-repl 可读表基于标准可读表,除了两个禁止字符和一个自定义列表阅读器:
(defreadtable limited-repl
(:merge :standard)
(:macro-char #\( 'read-read-zero-list)
(:macro-char #\: 'forbidden-character nil)
(:macro-char #\# 'forbidden-character nil))
limited-language 包定义了用户可以访问的基本语言,这里只有基本的算术符号和quit 符号:
(defpackage limited-language
(:use)
(:export #:+
#:-
#:*
#:/
#:quit))
我还将函数重新定义为 CL 函数的包装器,如下所示:
(macrolet ((lift (s c) `(defun ,s (&rest args) (apply #',c args))))
(lift limited-language:- cl:-)
(lift limited-language:+ cl:+)
(lift limited-language:* cl:*)
(lift limited-language:/ cl:/))
这是必要的,因为仅重新导出 CL 符号可能会产生意想不到的效果,例如用户访问绑定在 Lisp 中的变量 * 或 /,我们不希望在此处这样做。
以下辅助函数在*package* 绑定到使用limited-language 的新临时包的上下文中调用function。我正在使用GENTEMP 在package-names 包中生成一个新符号:
(defpackage package-names
(:use))
(defun call-with-temporary-package (function)
(let* ((symbol (gentemp "SANDBOX-" 'package-names))
(package (make-package symbol :use '(limited-language))))
(unwind-protect (let ((*package* package))
(funcall function))
(delete-package package)
(unintern symbol 'package-names))))
当没有 REPL 运行时,package-names 是空的,因为生成的符号在展开时是非驻留的。临时包也被删除。可能有一种更强大的方法来定义包的临时名称,这应该足以作为初稿。
within-sandbox 宏建立上下文,其中 readtable 和包设置为我们想要的。我还将 *read-eval* 绑定到 nil,只是为了确保在执行 read 时不会评估代码:
(defmacro within-sandbox (&rest body)
`(call-with-temporary-package
(lambda ()
(let ((*readtable* (find-readtable 'limited-repl))
(*read-eval* nil))
,@body))))
最后,REPL定义如下,其中quit用于退出REPL:
(defun limited-repl ()
(within-sandbox
(loop
(format t "~&> ")
(finish-output)
(clear-input)
(handler-case (let ((form (read)))
(when (eq form 'limited-language:quit)
(return))
(eval form))
(cl:end-of-file ()
(return))
(:no-error (v)
(print v))
(error (e)
(format *error-output* "~&Error: ~a~%" e))))))
CL-USER> (limited-repl:limited-repl)
> 5
5
> (+ () (* 4 3) (/ 7 9))
115/9
> #1=(list 0 . #1#)
Error: Forbidden character #\#
> *
Error: The variable * is unbound.
> (cl:in-package 'cl-user)
Error: Forbidden character #\:
> quit
NIL
CL-USER>
当限制用户可以访问的符号时,您可以依赖eval。但是,这仍然可以在您的语言中引入 CL 特定的语义,例如单独编写 * 时会报告未定义的变量 *(可能您的语言没有变量概念)。然后,您需要编写自己的 evaluate 函数,该函数可以委托给 eval,但也可以在 Common Lisp 之外执行其他操作。
对于沙盒环境(执行时间等),我可能没有考虑其他一些事情,请小心,但这应该已经很有用了。
【讨论】: