まずはモジュールがどういうものか、いくつか確認してみます。
モジュールは、シンボルを束縛へとマップするオブジェクト
モジュールは継承することもできます。 既存のモジュールを継承したモジュールに新しい束縛を足してexportすることにより、 既存のモジュールを拡張することができます。新しいモジュールの内部からは、 継承元のモジュールの束縛が(exportされていないものも含め)全て見えます。 (新しく作られるモジュールはデフォルトでgaucheモジュールを継承しています。 新しいモジュールからgaucheの組込み手続き等が使えるのはそのためです)。
モジュールは実行時データ構造です。実行時に任意の名前のモジュールを 手続き的に作成することができます。
define-moduleはモジュールに名前を付けるが、 Gaucheでは無名のモジュールを作ることもできる。
module
select-moduleによって一度nullやschemeモジュールに 入ると、そこから他のモジュールに移ることはできなくなることに注意してください。 これらのモジュールからは、あらゆるモジュール操作構文が不可視だからです。
らしい。
ということで、nullモジュール内にすでにある束縛を見てみる。
(hash-table-keys (module-table (find-module 'null)))
確かにモジュール系の手続きや特殊形式が一式存在しない。ということはselect-moduleをnullモジュールに入れてあげれば、またuserモジュールなどに戻れるわけですね。
define-in-module
nullモジュールにselect-module特殊形式を入れてみる。
(define-in-module null select-module select-module)
(hash-table-keys (module-table (find-module 'null)))
入ったようです。
select-moduleでnullモジュールに入ってみます。
(select-module null)
(module-name (current-module))
(select-module user)
(module-name (current-module))
nullモジュール内にcurrent-moduleは無いのでエラーになってます。でもselect-moduleはあるのでuserモジュールに戻ってくることができました。
他にも追加してみる。
(define-in-module null current-module current-module)
(define-in-module null receive receive)
(define-in-module null values values)
(select-module null)
(current-module)
(receive (a b)
(values 1 2)
(quasiquote ((unquote a)(unquote b))))
eval
ところで、gaucheのevalの第二引数は(今のところ)モジュールらしいです。
Function: eval expr env
[R5RS] exprを評価します。envは下に述べる手続きにより 返される値でなければなりません。現時点では、それは単なる オブジェクトですが、Gaucheが将来、ファースト クラスの環境オブジェクトを採用する可能性はあります。
(eval '(print #`"Hello, ,(module-name (current-module)) module !!")
(current-module))
(eval '(print #`"Hello, ,(module-name (current-module)) module !!")
(find-module 'gauche))
sandbox
サンドボックス(sandbox)とは、外部から受け取ったプログラムを保護された領域で動作させることによってシステムが不正に操作されるのを防ぐセキュリティモデルのこと。
nullモジュールを継承したモジュールを作れば、許可したい手続きだけを注入したsandboxが作れそうです。
(eval '(begin (define a 10)(define b 5)(print (+ a b)))
(find-module 'null))
+がないと怒られました。
+と、きっとprintもないのでnullモジュールに注入します。
(define-in-module null + +)
(define-in-module null print print)
(eval '(begin (define a 10)(define b 5)(print (+ a b)))
(find-module 'null))
sandboxを作ってみる。
(define-module sandbox)
(module-parents (find-module 'sandbox))
まずdefine-moduleでsandboxモジュールを定義します。sandboxモジュールの親モジュールはgauche.gfモジュールになっているようです。ということは、userモジュールと同じだけの手続きやマクロが使えてしまいそうです。昨日書いてみたall-symbols手続きを少し改造して確認してみます。
(define (all-parents module)
(let ((parents (module-parents module)))
(let rec ((parents parents)(acc '()))
(if (null? parents)
acc
(rec (cdr parents)(append (cons (car parents) acc)
(all-parents (car parents))))))))
(define (all-symbols module)
(map (lambda (m)
(sort (hash-table-keys (module-table m))
(lambda (x y)
(string<? (symbol->string x)
(symbol->string y)))))
(all-parents module)))
(length (apply append (all-symbols (find-module 'sandbox))))
1423個もありました。
これはsandboxとしてはまずそうです。sandboxモジュールはnullモジュールから直接継承してもらいたいので、継承関係をぶち壊すためにextendマクロを使います。
(eval '(extend null) (find-module 'sandbox))
(module-parents (find-module 'sandbox))
希望通りにsandboxモジュールはnullモジュールを直接継承したようです。
ホントに?
(module-parents (find-module 'sandbox))
(hash-table-keys (module-table (find-module 'sandbox)))
(length (apply append (all-symbols (find-module 'sandbox))))
(apply append (all-symbols (find-module 'sandbox)))
(module-parents (find-module 'sandbox))
よさそうです。
define-sandbox
こういう風に書きたいですよね。
(define-sandbox <name>)
これが下記のように展開されるマクロがあれば良さそうです。
(begin
(define-module <name>)
(define <name> (find-module (quote <name>)))
(eval '(extend null) <name>))
書いてみます。
(define-macro (define-sandbox name)
`(begin
(define-module ,name)
(define ,name (find-module (quote ,name)))
(eval '(extend null) ,name)))
期待通りに展開されるか%macroexpandマクロで確認してみます。
(%macroexpand (define-sandbox hoge))
動作も期待通りでしょうか?
(define-sandbox hoge)
hoge
(module-parents hoge)
(apply append (all-symbols hoge))
良さそうです。
ですが・・・、sandbox内で使いたい手続きやマクロを全てdefine-in-moduleで定義していくのも結構しんどそうです。define-sandboxで下記のように書けたら嬉しいかもしれません。
(define-sandbox <name>
(symbol ... ))
展開イメージはこんな感じ。
(begin
(define-module <name>)
(define <name> (find-module (quote <name>)))
(eval '(extend null) <name>)
(begin
(define-in-module <name> symbol symbol)
... ))
define-sandboxマクロを編集します。
(define-macro (define-sandbox name . symbols)
`(begin
(define-module ,name)
(define ,name (find-module (quote ,name)))
(eval '(extend null) ,name)
(begin
,@(map (lambda (sym)
`(define-in-module ,name ,sym ,sym))
symbols)
(undefined))))
動作を確認してみます。
(%macroexpand (define-sandbox hoge))
(%macroexpand (define-sandbox fuga + * - /))
(define-sandbox fuga + * - /)
(hash-table-keys (module-table fuga))
(module-parents fuga)
良さそうですね。
でも、sandbox内で使いたい手続きやマクロを毎度一つずつ追加するのも面倒です。指定したモジュールだけsandbox内でuseできたら便利そうです。
(define-sandbox <name> (using-module ...)
(symbol ...))
展開イメージはこうでしょうか。
(begin
(define-module <name>)
(define <name> (find-module (quote <name>)))
(eval '(begin (use using-module) ...) <name>)
(eval '(extend null) <name>)
(begin
(define-in-module <name> symbol symbol)
... ))
define-sandboxを拡張します。
(define-macro (define-sandbox name modules . symbols)
`(begin
(define-module ,name)
(define ,name (find-module (quote ,name)))
(eval (quote (begin ,@(map (lambda (m)
`(use ,m))
modules))) ,name)
(eval '(extend null) ,name)
(begin
,@(map (lambda (sym)
`(define-in-module ,name ,sym ,sym))
symbols)
(undefined))))
%macroexpandしてみます。
(%macroexpand (define-sandbox hoge (srfi-1 util.list gauche.sequence)
+ - * /))
期待通りの動きをするでしょうか。
(define-sandbox hoge (srfi-1 util.list gauche.sequence)
+ - * /)
(module-parents hoge)
(hash-table-keys (module-table hoge))
(eval 'iota hoge)
(eval 'assoc-ref hoge)
(eval 'fold-with-index hoge)
(eval 'list hoge)
(eval 'map hoge)
define-sandbox時に指定したモジュールからexportされている束縛はsandbox内から見えているようです。define-in-moduleで指定されていない束縛であるlistは見えていません。これで良さそうです。
取りあえずこれでdefine-sandboxは完成ということにします。
(define-macro (define-sandbox name modules . symbols)
`(begin
(define-module ,name)
(define ,name (find-module (quote ,name)))
(eval (quote (begin ,@(map (lambda (m)
`(use ,m))
modules))) ,name)
(eval '(extend null) ,name)
(begin
,@(map (lambda (sym)
`(define-in-module ,name ,sym ,sym))
symbols)
(undefined))))
あとは、define-sandboxでsandboxを定義した後に束縛を追加したくなったりuseされるモジュールを追加したくなった場合のために、inject-sandbox-symbols, inject-sandbox-modulesのようなマクロがあると便利かもしれません。
これでネットワーク越しに受け取ったS式などを心置きなくevalできますね!
参考
@kikuchan98さんに教えてもらった内容を参考にしました。
追記
探してみたらkahuaにsandbox.scmというものがあった。どうやら上記のsandboxとは違ってブラックリストを指定するタイプみたい。それとmake-moduleを使って無名モジュールを作ってそれをsandboxにしているみたい。
追記