revised mzlib/sandbox in scheme/sandbox

svn: r7965
This commit is contained in:
Matthew Flatt 2007-12-12 13:47:02 +00:00
parent 05d372e4bf
commit 622cd0554d
21 changed files with 1361 additions and 640 deletions

View File

@ -118,7 +118,8 @@ labelled-menu-item<%>
list-box%
list-control<%>
make-eventspace
make-namespace-with-mred
make-gui-empty-namespace
make-gui-namespace
map-command-as-meta-key
menu%
menu-bar%

View File

@ -1,5 +1,10 @@
(module mred mzscheme
(require (lib "etc.ss")
(require (only scheme/base
define-namespace-anchor
namespace-anchor->empty-namespace
make-base-empty-namespace)
scheme/class
(lib "etc.ss")
(prefix wx: "private/kernel.ss")
"private/wxtop.ss"
"private/app.ss"
@ -37,23 +42,22 @@
;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define mred-module-name '(lib "mred/mred.ss"))
(define class-module-name '(lib "scheme/class.ss"))
(define-namespace-anchor anchor)
(define make-namespace-with-mred
(opt-lambda ([flag 'mred])
(unless (memq flag '(initial mred empty))
(raise-type-error 'make-namespace-with-mred
"flag symbol, one of 'mred, 'initial, or 'empty"
flag))
(let ([orig (current-namespace)]
[ns (make-namespace (if (eq? flag 'empty) 'empty 'initial))])
(define (make-gui-empty-namespace)
(let ([ns (make-base-empty-namespace)])
(namespace-attach-module (namespace-anchor->empty-namespace anchor)
'mred/mred
ns)
ns))
(define (make-gui-namespace)
(let ([ns (make-gui-empty-namespace)])
(parameterize ([current-namespace ns])
(namespace-attach-module orig mred-module-name)
(when (eq? flag 'mred)
(namespace-require mred-module-name)
(namespace-require class-module-name)))
ns)))
(namespace-require 'scheme/base)
(namespace-require 'mred/mred)
(namespace-require 'scheme/class))
ns))
;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -287,7 +291,8 @@
current-eventspace-has-standard-menus?
current-eventspace-has-menu-root?
eventspace-handler-thread
make-namespace-with-mred
make-gui-namespace
make-gui-empty-namespace
file-creator-and-type
current-ps-afm-file-paths
current-ps-cmap-file-paths

View File

@ -1,260 +1,89 @@
(module sandbox mzscheme
(require (lib "string.ss") (lib "list.ss") (lib "port.ss")
(lib "moddep.ss" "syntax"))
(provide mred?
sandbox-init-hook
sandbox-reader
sandbox-input
sandbox-output
sandbox-error-output
sandbox-propagate-breaks
sandbox-coverage-enabled
sandbox-namespace-specs
sandbox-override-collection-paths
sandbox-security-guard
sandbox-path-permissions
sandbox-network-guard
sandbox-eval-limits
kill-evaluator
break-evaluator
set-eval-limits
put-input
get-output
get-error-output
get-uncovered-expressions
(module sandbox scheme/base
(require scheme/sandbox
(prefix-in mz: (only-in mzscheme make-namespace)))
(provide (except-out (all-from-out scheme/sandbox)
make-evaluator
call-with-limits
with-limits
exn:fail:resource?
exn:fail:resource-resource)
make-module-evaluator
gui?)
(rename-out [*make-evaluator make-evaluator]
[gui? mred?]))
(define mred?
(with-handlers ([void (lambda (_) #f)])
(dynamic-require ''#%mred-kernel #f)
#t))
(define-syntax mz/mr ; use a value for mzscheme, or pull a mred binding
(syntax-rules ()
[(mz/mr mzval mrsym)
(if mred? (dynamic-require '(lib "mred.ss" "mred") 'mrsym) mzval)]))
(define-namespace-anchor anchor)
;; Configuration ------------------------------------------------------------
;; Compatbility:
;; * recognize 'r5rs, etc, and wrap them as a list.
;; * 'begin form of reqs
;; * more agressively extract requires from lang and reqs
(define *make-evaluator
(case-lambda
[(lang reqs . progs)
(with-ns-params
(lambda ()
(let ([beg-req? (and (list? reqs)
(pair? reqs)
(eq? 'begin (car reqs)))]
[reqs (or reqs '())]
[lang (or lang '(begin))])
(keyword-apply
make-evaluator
'(#:allow-read)
(list (extract-requires lang reqs))
(case lang
[(r5rs beginner beginner-abbr intermediate intermediate-lambda advanced)
(list 'special lang)]
[else lang])
(if beg-req? null reqs)
(append
(if beg-req? (cdr reqs) null)
progs)))))]
[(mod)
(with-ns-params
(lambda ()
(make-module-evaluator mod)))]))
(define sandbox-init-hook (make-parameter void))
(define sandbox-input (make-parameter #f))
(define sandbox-output (make-parameter #f))
(define sandbox-error-output (make-parameter current-error-port))
(define sandbox-eval-limits (make-parameter '(30 20))) ; 30sec, 20mb
(define sandbox-propagate-breaks (make-parameter #t))
(define sandbox-coverage-enabled (make-parameter #f))
(define (make-mz-namespace)
(let ([ns (mz:make-namespace)])
;; Because scheme/sandbox needs scheme/base:
(namespace-attach-module (namespace-anchor->namespace anchor)
'scheme/base
ns)
ns))
(define sandbox-namespace-specs
(make-parameter `(,(mz/mr make-namespace make-namespace-with-mred)
#| no modules here by default |#)))
(define (default-sandbox-reader source)
(let loop ([l '()])
(let ([expr (read-syntax source)])
(if (eof-object? expr)
(reverse l)
(loop (cons expr l))))))
(define sandbox-reader (make-parameter default-sandbox-reader))
(define sandbox-override-collection-paths (make-parameter '()))
(define teaching-langs
'(beginner beginner-abbr intermediate intermediate-lambda advanced))
;; Security Guard -----------------------------------------------------------
(define sep (bytes-ref (path->bytes (simplify-path "/")) 0)) ; '\' on windows
(define (simplify-path* path)
(simplify-path (expand-path (path->complete-path
(cond [(bytes? path) (bytes->path path)]
[(string? path) (string->path path)]
[else path])))))
(define permission-order '(execute write delete read exists))
(define (perm<=? p1 p2)
(memq p1 (memq p2 permission-order)))
;; gets a path (can be bytes/string), returns a regexp for that path that
;; matches also subdirs (if it's a directory)
(define path->bregexp
(let* ([sep-re (regexp-quote (bytes sep))]
[last-sep (byte-regexp (bytes-append sep-re #"?$"))]
[suffix-re (bytes-append #"(?:$|" sep-re #")")])
(lambda (path)
(if (byte-regexp? path)
path
(let* ([path (path->bytes (simplify-path* path))]
[path (regexp-quote (regexp-replace last-sep path #""))])
(byte-regexp (bytes-append #"^" path suffix-re)))))))
(define sandbox-path-permissions
(make-parameter '()
(lambda (new)
(map (lambda (perm) (cons (car perm) (map path->bregexp (cdr perm))))
new))))
(define sandbox-network-guard
(make-parameter (lambda (what . xs)
(error what "network access denied: ~e" xs))))
(define default-sandbox-guard
(let ([orig-security (current-security-guard)])
(make-security-guard
orig-security
(lambda (what path modes)
(when path
(let ([needed (let loop ([order permission-order])
(cond [(null? order)
(error 'default-sandbox-guard
"unknown access modes: ~e" modes)]
[(memq (car order) modes) (car order)]
[else (loop (cdr order))]))]
[bpath (parameterize ([current-security-guard orig-security])
(path->bytes (simplify-path* path)))])
(unless (ormap (lambda (perm)
(and (perm<=? needed (car perm))
(regexp-match (cadr perm) bpath)))
(sandbox-path-permissions))
(error what "file access denied ~a" (cons path modes))))))
(lambda args (apply (sandbox-network-guard) args)))))
(define sandbox-security-guard (make-parameter default-sandbox-guard))
;; computes permissions that are needed for require specs (`read' for all
;; files and "compiled" subdirs, `exists' for the base-dir)
(define (module-specs->path-permissions mods)
(define paths (module-specs->non-lib-paths mods))
(define bases
(let loop ([paths paths] [bases '()])
(if (null? paths)
(reverse bases)
(let-values ([(base name dir?) (split-path (car paths))])
(let ([base (simplify-path* base)])
(loop (cdr paths)
(if (member base bases) bases (cons base bases))))))))
(append (map (lambda (p) `(read ,(path->bytes p))) paths)
(map (lambda (b) `(read ,(build-path b "compiled"))) bases)
(map (lambda (b) `(exists ,b)) bases)))
(require (only (lib "modhelp.ss" "syntax" "private") module-path-v?))
;; takes a module-spec list and returns all module paths that are needed
;; ==> ignores (lib ...) modules
(define (module-specs->non-lib-paths mods)
(define (lib? x)
(if (module-path-index? x)
(let-values ([(m base) (module-path-index-split x)]) (lib? m))
(and (pair? x) (eq? 'lib (car x)))))
;; turns a module spec to a simple one (except for lib specs)
(define (simple-modspec mod)
(cond [(and (pair? mod) (eq? 'lib (car mod))) #f]
[(module-path-v? mod)
(simplify-path* (resolve-module-path mod #f))]
[(not (and (pair? mod) (pair? (cdr mod))))
;; don't know what this is, leave as is
#f]
[(eq? 'only (car mod))
(simple-modspec (cadr mod))]
[(eq? 'rename (car mod))
(simple-modspec (cadr mod))]
[(and (eq? 'prefix (car mod)) (pair? (cddr mod)))
(simple-modspec (caddr mod))]
[else #f]))
(let loop ([todo (filter values (map simple-modspec mods))]
[r '()])
(define (with-ns-params thunk)
(let ([v (sandbox-namespace-specs)])
(cond
[(null? todo) r]
[(member (car todo) r) (loop (cdr todo) r)]
[else
(let ([path (car todo)])
(loop (map (lambda (i)
(simplify-path* (resolve-module-path-index i path)))
(filter (lambda (i)
(and (module-path-index? i) (not (lib? i))))
(apply append
(call-with-values
(lambda ()
(module-compiled-imports
(get-module-code (car todo))))
list))))
(cons path r)))])))
;; Resources ----------------------------------------------------------------
(define-struct (exn:fail:resource exn:fail) (resource))
(define memory-accounting? (custodian-memory-accounting-available?))
(define (call-with-limits sec mb thunk)
(let ([r #f]
[c (make-custodian)]
;; used to copy parameter changes from the nested thread
[p current-preserved-thread-cell-values])
(when (and mb memory-accounting?)
(custodian-limit-memory c (* mb 1024 1024) c))
(parameterize ([current-custodian c])
;; The nested-thread can die on a time-out or memory-limit,
;; and never throws an exception, so we never throw an error,
;; just assume the a death means the custodian was shut down
;; due to memory limit. Note: cannot copy the
;; parameterization in this case.
(with-handlers ([exn:fail? (lambda (e)
(unless r (set! r (cons #f 'memory))))])
(call-in-nested-thread
(lambda ()
(define this (current-thread))
(define timer
(thread (lambda ()
(sleep sec)
;; even in this case there are no parameters
;; to copy, since it is on a different thread
(set! r (cons #f 'time))
(kill-thread this))))
(set! r
(with-handlers ([void (lambda (e) (list (p) raise e))])
(call-with-values thunk (lambda vs (list* (p) values vs)))))
(kill-thread timer))))
(custodian-shutdown-all c)
(unless r (error 'call-with-limits "internal error"))
;; apply parameter changes first
(when (car r) (p (car r)))
(if (pair? (cdr r))
(apply (cadr r) (cddr r))
(raise (make-exn:fail:resource (format "with-limit: out of ~a" (cdr r))
(current-continuation-marks)
(cdr r)))))))
(define-syntax with-limits
(syntax-rules ()
[(with-limits sec mb body ...)
(call-with-limits sec mb (lambda () body ...))]))
;; Execution ----------------------------------------------------------------
[(and (not gui?)
(eq? (car v) make-base-namespace))
(parameterize ([sandbox-namespace-specs
(cons make-mz-namespace
(cdr v))])
(thunk))]
[(and gui?
(eq? (car v) (dynamic-require 'mred/mred 'make-gui-namespace)))
(parameterize ([sandbox-namespace-specs
;; Simulate the old make-namespace-with-mred:
(cons (lambda ()
(let ([ns (make-mz-namespace)]
[ns2 ((dynamic-require 'mred/mred 'make-gui-namespace))])
(namespace-attach-module ns2
'mred/mred
ns)
(namespace-attach-module ns2
'scheme/class
ns)
(parameterize ([current-namespace ns])
(namespace-require 'mred)
(namespace-require 'scheme/class))
ns))
(cdr v))])
(thunk))]
[else (thunk)])))
(define (literal-identifier=? x y)
(or (module-identifier=? x y)
(eq? (if (syntax? x) (syntax-e x) x) (if (syntax? y) (syntax-e y) y))))
(or (free-identifier=? x y)
(eq? (syntax-e x) (syntax-e y))))
(define (make-evaluation-namespace)
(let* ([specs (sandbox-namespace-specs)]
[new-ns ((car specs))]
[orig-ns (current-namespace)]
[mods (cdr specs)]
[resolve (current-module-name-resolver)])
(for-each (lambda (mod) (dynamic-require mod #f)) mods)
(let ([modsyms (map (lambda (mod) (resolve mod #f #f)) mods)])
(parameterize ([current-namespace new-ns])
(for-each (lambda (ms) (namespace-attach-module orig-ns ms))
modsyms)))
new-ns))
(define (require-perms language requires)
(define (extract-requires language requires)
(define (find-requires forms)
(let loop ([forms (reverse forms)] [reqs '()])
(if (null? forms)
@ -262,334 +91,17 @@
(loop (cdr forms)
(syntax-case* (car forms) (require) literal-identifier=?
[(require specs ...)
(append (syntax-object->datum #'(specs ...)) reqs)]
(append (syntax->datum #'(specs ...)) reqs)]
[_else reqs])))))
(let* ([requires (if (and (pair? requires) (eq? 'begin (car requires)))
(find-requires (cdr requires))
requires)]
[requires (cond [(string? language) (cons language requires)]
null)]
[requires (cond [(string? language) requires]
[(not (pair? language)) requires]
[(memq (car language) '(lib file planet))
(cons language requires)]
[(memq (car language) '(lib file planet quote))
requires]
[(eq? (car language) 'begin)
(append (find-requires (cdr language)) requires)]
[else (error 'require-perms
[else (error 'extract-requires
"bad language spec: ~e" language)])])
(module-specs->path-permissions requires)))
(define (input->port inp)
;; returns #f when it can't create a port
(cond [(input-port? inp) inp]
[(string? inp) (open-input-string inp)]
[(bytes? inp) (open-input-bytes inp)]
[(path? inp) (open-input-file inp)]
[else #f]))
;; Gets an input spec returns a list of syntaxes. The input can be a list of
;; sexprs/syntaxes, or a list with a single input port spec
;; (path/string/bytes) value.
(define (input->code inps source n)
(if (null? inps)
'()
(let ([p (input->port (car inps))])
(cond [(and p (null? (cdr inps)))
(port-count-lines! p)
(parameterize ([current-input-port p])
((sandbox-reader) source))]
[p (error 'input->code "ambiguous inputs: ~e" inps)]
[else (let loop ([inps inps] [n n] [r '()])
(if (null? inps)
(reverse r)
(loop (cdr inps) (and n (add1 n))
;; 1st at line#1, pos#1, 2nd at line#2, pos#2 etc
;; (starting from the `n' argument)
(cons (datum->syntax-object
#f (car inps)
(list source n (and n 0) n (and n 1)))
r))))]))))
(define ((init-for-language language))
(cond [(eq? language 'r5rs)
(read-case-sensitive #f)
(read-square-bracket-as-paren #f)
(read-curly-brace-as-paren #f)
(read-accept-infix-dot #f)]
[(memq language teaching-langs)
(read-case-sensitive #t)
(read-decimal-as-inexact #f)]))
;; Returns a single (module ...) or (begin ...) expression (a `begin' list
;; will be evaluated one by one -- the language might not have a `begin').
(define (build-program language requires input-program)
(let* ([body (append (if (and (pair? requires) (eq? 'begin (car requires)))
(cdr requires)
(map (lambda (r) (list #'require r))
requires))
(input->code input-program 'program 1))]
[use-lang (lambda (lang) `(module program ,lang . ,body))])
(cond [(memq language teaching-langs)
(use-lang `(lib ,(format "htdp-~a.ss" language) "lang"))]
[(eq? language 'r5rs)
(use-lang `(lib "lang.ss" "r5rs"))]
[(or (and (pair? language) (memq (car language) '(lib file planet)))
(symbol? language) (string? language))
(use-lang language)]
[(and (pair? language) (eq? 'begin (car language)))
(append language body)]
[else (error 'make-evaluator "bad language spec: ~e" language)])))
;; Like a toplevel (eval `(begin ,@exprs)), but the language that is used may
;; not have a begin.
(define (eval* exprs)
(if (null? exprs)
(void)
(let ([deftag (default-continuation-prompt-tag)])
(let loop ([expr (car exprs)] [exprs (cdr exprs)])
(if (null? exprs)
(eval expr)
(begin
(call-with-continuation-prompt
(lambda () (eval expr))
deftag
(lambda (x) (abort-current-continuation deftag x)))
(loop (car exprs) (cdr exprs))))))))
(define (evaluate-program program limits uncovered!)
(when uncovered!
(eval `(,#'require (lib "sandbox-coverage.ss" "mzlib" "private"))))
;; the actual evaluation happens under specified limits, if given
(let ([run (if (and (pair? program) (eq? 'begin (car program)))
(lambda () (eval* (cdr program)))
(lambda () (eval program)))]
[sec (and limits (car limits))]
[mb (and limits (cadr limits))])
(if (or sec mb) (call-with-limits sec mb run) (run)))
(let ([ns (syntax-case* program (module) literal-identifier=?
[(module mod . body)
(identifier? #'mod)
(let ([mod #'mod])
(eval `(,#'require (quote ,mod)))
(module->namespace `(quote ,(syntax-e mod))))]
[_else #f])])
(when uncovered!
(let ([get (let ([ns (current-namespace)])
(lambda () (eval '(get-uncovered-expressions) ns)))])
(uncovered! (list (get) get))))
(when ns (current-namespace ns))))
(define current-eventspace (mz/mr (make-parameter #f) current-eventspace))
(define make-eventspace (mz/mr void make-eventspace))
(define run-in-bg (mz/mr thread queue-callback))
(define bg-run->thread (if mred?
(lambda (ignored)
((mz/mr void eventspace-handler-thread) (current-eventspace)))
values))
(define null-input (open-input-bytes #""))
(define (kill-evaluator eval) (eval kill-evaluator))
(define (break-evaluator eval) (eval break-evaluator))
(define (set-eval-limits eval . args) ((eval set-eval-limits) args))
(define (put-input eval . args) (apply (eval put-input) args))
(define (get-output eval) (eval get-output))
(define (get-error-output eval) (eval get-error-output))
(define (get-uncovered-expressions eval . args)
(apply (eval get-uncovered-expressions) args))
(define (make-evaluator* init-hook require-perms program-or-maker)
(define cust (make-custodian))
(define coverage? (sandbox-coverage-enabled))
(define uncovered #f)
(define input-ch (make-channel))
(define result-ch (make-channel))
(define input #f)
(define output #f)
(define error-output #f)
(define limits (sandbox-eval-limits))
(define user-thread #t) ; set later to the thread
(define orig-cust (current-custodian))
(define (user-kill)
(when user-thread
(let ([t user-thread])
(set! user-thread #f)
(custodian-shutdown-all cust)
(kill-thread t))) ; just in case
(void))
(define (user-break)
(when user-thread (break-thread user-thread)))
(define (user-process)
(with-handlers ([void (lambda (exn) (channel-put result-ch exn))])
;; first set up the environment
(init-hook)
((sandbox-init-hook))
;; now read and evaluate the input program
(evaluate-program
(if (procedure? program-or-maker) (program-or-maker) program-or-maker)
limits
(and coverage? (lambda (es+get) (set! uncovered es+get))))
(channel-put result-ch 'ok))
;; finally wait for interaction expressions
(let loop ([n 1])
(let ([expr (channel-get input-ch)])
(when (eof-object? expr) (channel-put result-ch expr) (user-kill))
(with-handlers ([void (lambda (exn)
(channel-put result-ch (cons 'exn exn)))])
(let* ([code (input->code (list expr) 'eval n)]
[sec (and limits (car limits))]
[mb (and limits (cadr limits))]
[run (if (or sec mb)
(lambda () (with-limits sec mb (eval* code)))
(lambda () (eval* code)))])
(channel-put result-ch
(cons 'vals (call-with-values run list)))))
(loop (add1 n)))))
(define (user-eval expr)
(let ([r (if user-thread
(begin (channel-put input-ch expr)
(let loop ()
(with-handlers ([(lambda (e)
(and (sandbox-propagate-breaks)
(exn:break? e)))
(lambda (e)
(user-break)
(loop))])
(channel-get result-ch))))
eof)])
(cond [(eof-object? r) (error 'evaluator "terminated")]
[(eq? (car r) 'exn) (raise (cdr r))]
[else (apply values (cdr r))])))
(define get-uncovered
(case-lambda
[() (get-uncovered #t)]
[(prog?) (get-uncovered prog? 'program)]
[(prog? src)
(unless uncovered
(error 'get-uncovered-expressions "no coverage information"))
(let ([uncovered (if prog? (car uncovered) ((cadr uncovered)))])
(if src
(filter (lambda (x) (equal? src (syntax-source x))) uncovered)
uncovered))]))
(define (output-getter p) (if (procedure? p) (user-eval `(,p)) p))
(define input-putter
(case-lambda
[() (input-putter input-putter)]
[(arg) (cond [(not input)
(error 'put-input "evaluator input is not 'pipe")]
[(or (string? arg) (bytes? arg))
(display arg input) (flush-output input)]
[(eof-object? arg) (close-output-port input)]
[(eq? arg input-putter) input]
[else (error 'put-input "bad input: ~e" arg)])]))
(define (evaluator expr)
(cond [(eq? expr kill-evaluator) (user-kill)]
[(eq? expr break-evaluator) (user-break)]
[(eq? expr set-eval-limits) (lambda (args) (set! limits args))]
[(eq? expr put-input) input-putter]
[(eq? expr get-output) (output-getter output)]
[(eq? expr get-error-output) (output-getter error-output)]
[(eq? expr get-uncovered-expressions) get-uncovered]
[else (user-eval expr)]))
(define linked-outputs? #f)
(define (make-output what out set-out! allow-link?)
(cond [(not out) (open-output-nowhere)]
[(and (procedure? out) (procedure-arity-includes? out 0)) (out)]
[(output-port? out) out]
[(eq? out 'pipe) (let-values ([(i o) (make-pipe)]) (set-out! i) o)]
[(memq out '(bytes string))
(let* ([bytes? (eq? 'bytes out)]
;; the following doesn't really matter: they're the same
[out ((if bytes? open-output-bytes open-output-string))])
(set-out!
(lambda ()
(parameterize ([current-custodian orig-cust])
(let ([buf (get-output-bytes out #t)])
(if bytes? buf (bytes->string/utf-8 buf #\?))))))
out)]
[else (error 'make-evaluator "bad sandox-~a spec: ~e" what out)]))
(parameterize* ; the order in these matters
(;; create a sandbox context first
[current-custodian cust]
[current-thread-group (make-thread-group)]
[current-namespace (make-evaluation-namespace)]
;; set up the IO context
[current-input-port
(let ([inp (sandbox-input)])
(cond
[(not inp) null-input]
[(input->port inp) => values]
[(and (procedure? inp) (procedure-arity-includes? inp 0)) (inp)]
[(eq? 'pipe inp)
(let-values ([(i o) (make-pipe)]) (set! input o) i)]
[else (error 'make-evaluator "bad sandbox-input: ~e" inp)]))]
[current-output-port (make-output 'output (sandbox-output)
(lambda (o) (set! output o))
#f)]
[current-error-port (make-output 'error-output (sandbox-error-output)
(lambda (o) (set! error-output o))
#t)]
;; paths
[current-library-collection-paths
(filter directory-exists?
(append (sandbox-override-collection-paths)
(current-library-collection-paths)))]
[sandbox-path-permissions
(append (map (lambda (p) `(read ,p))
(current-library-collection-paths))
require-perms
(sandbox-path-permissions))]
;; general info
[current-command-line-arguments '#()]
;; restrict the sandbox context from this point
[current-security-guard (sandbox-security-guard)]
[exit-handler (lambda x (error 'exit "user code cannot exit"))]
[current-inspector (make-inspector)]
;; This breaks: [current-code-inspector (make-inspector)]
;; Note the above definition of `current-eventspace': in MzScheme, it
;; is an unused parameter. Also note that creating an eventspace
;; starts a thread that will eventually run the callback code (which
;; evaluates the program in `run-in-bg') -- so this parameterization
;; must be nested in the above (which is what paramaterize* does), or
;; it will not use the new namespace.
[current-eventspace (make-eventspace)])
(set! user-thread (bg-run->thread (run-in-bg user-process)))
(let ([r (channel-get result-ch)])
(if (eq? r 'ok)
;; initial program executed ok, so return an evaluator
evaluator
;; program didn't execute
(raise r)))))
(define make-evaluator
(case-lambda
;; `input-program' is either a single argument specifying a file/string,
;; or multiple arguments for a sequence of expressions
[(language requires . input-program)
(let (;; make it possible to provide #f for no language and no requires
[lang (or language '(begin))]
;; make it possible to use simple paths to files to require
[reqs (cond [(not requires) '()]
[(not (list? requires))
(error 'make-evaluator "bad requires: ~e" requires)]
[else
(map (lambda (r)
(if (or (pair? r) (symbol? r))
r
`(file ,(path->string (simplify-path* r)))))
requires)])])
(make-evaluator* (init-for-language lang)
(require-perms lang reqs)
(lambda () (build-program lang reqs input-program))))]
;; this is for a complete module input program
[(input-program)
(let ([prog (input->code (list input-program) 'program #f)])
(unless (= 1 (length prog))
(error 'make-evaluator "expecting a single `module' program; ~a"
(if (zero? (length prog))
"no program expressions given"
"got more than a single expression")))
(syntax-case* (car prog) (module) literal-identifier=?
[(module modname lang body ...)
(make-evaluator* void '() (car prog))]
[_else (error 'make-evaluator "expecting a `module' program; got ~e"
(syntax-object->datum (car prog)))]))]))
)
requires)))

View File

@ -13,7 +13,7 @@
;; custodian and the given error display handler.
(define (require/sandbox+annotations custodian err-display-handler initial-module annotate-module? annotator)
(parameterize ([current-custodian custodian]
[current-namespace (make-namespace-with-mred)]
[current-namespace (make-gui-namespace)]
[error-display-handler err-display-handler])
(require/annotations initial-module annotate-module? annotator)))

View File

@ -0,0 +1,4 @@
#lang scheme/base
(require mred)
(provide (all-from-out mred))

View File

@ -0,0 +1,15 @@
#lang scheme/base
(provide gui-available?
gui-dynamic-require)
(define (gui-available?)
(with-handlers ([exn:fail? (lambda (exn) #f)])
(dynamic-require ''#%mred-kernel #f)
#t))
(define-namespace-anchor anchor)
(define (gui-dynamic-require sym)
(parameterize ([current-namespace (namespace-anchor->empty-namespace anchor)])
(dynamic-require 'mred/mred sym)))

View File

@ -24,7 +24,7 @@
[old (variable-reference->empty-namespace (#%variable-reference reflect-var))])
(namespace-attach-module old 'mzscheme new)
(parameterize ([current-namespace new])
(namespace-require 'mzscheme))
(namespace-require/copy 'mzscheme))
new)]))
(define (free-identifier=?* a b)

607
collects/scheme/sandbox.ss Normal file
View File

@ -0,0 +1,607 @@
#lang scheme/base
(require scheme/port
syntax/moddep
scheme/gui/dynamic)
(provide gui?
sandbox-init-hook
sandbox-reader
sandbox-input
sandbox-output
sandbox-error-output
sandbox-propagate-breaks
sandbox-coverage-enabled
sandbox-namespace-specs
sandbox-override-collection-paths
sandbox-security-guard
sandbox-path-permissions
sandbox-network-guard
sandbox-eval-limits
kill-evaluator
break-evaluator
set-eval-limits
put-input
get-output
get-error-output
get-uncovered-expressions
make-evaluator
make-module-evaluator
call-with-limits
with-limits
exn:fail:resource?
exn:fail:resource-resource)
(define gui? (gui-available?))
(define-syntax mz/mr ; use a value for mzscheme, or pull a mred binding
(syntax-rules ()
[(mz/mr mzval mrsym)
(if gui? (gui-dynamic-require 'mrsym) mzval)]))
;; Configuration ------------------------------------------------------------
(define sandbox-init-hook (make-parameter void))
(define sandbox-input (make-parameter #f))
(define sandbox-output (make-parameter #f))
(define sandbox-error-output (make-parameter current-error-port))
(define sandbox-eval-limits (make-parameter '(30 20))) ; 30sec, 20mb
(define sandbox-propagate-breaks (make-parameter #t))
(define sandbox-coverage-enabled (make-parameter #f))
(define sandbox-namespace-specs
(make-parameter `(,(mz/mr make-base-namespace make-gui-namespace)
#| no modules here by default |#)))
(define (default-sandbox-reader source)
(let loop ([l '()])
(let ([expr (read-syntax source)])
(if (eof-object? expr)
(reverse l)
(loop (cons expr l))))))
(define sandbox-reader (make-parameter default-sandbox-reader))
(define sandbox-override-collection-paths (make-parameter '()))
(define teaching-langs
'(beginner beginner-abbr intermediate intermediate-lambda advanced))
;; Security Guard -----------------------------------------------------------
(define sep (bytes-ref (path->bytes (simplify-path "/")) 0)) ; '\' on windows
(define (simplify-path* path)
(if (symbol? path)
#f
(simplify-path (cleanse-path (path->complete-path
(cond [(bytes? path) (bytes->path path)]
[(string? path) (string->path path)]
[else path]))))))
(define permission-order '(execute write delete read exists))
(define (perm<=? p1 p2)
(memq p1 (memq p2 permission-order)))
;; gets a path (can be bytes/string), returns a regexp for that path that
;; matches also subdirs (if it's a directory)
(define path->bregexp
(let* ([sep-re (regexp-quote (bytes sep))]
[last-sep (byte-regexp (bytes-append sep-re #"?$"))]
[suffix-re (bytes-append #"(?:$|" sep-re #")")])
(lambda (path)
(if (byte-regexp? path)
path
(let* ([path (path->bytes (simplify-path* path))]
[path (regexp-quote (regexp-replace last-sep path #""))])
(byte-regexp (bytes-append #"^" path suffix-re)))))))
(define sandbox-path-permissions
(make-parameter '()
(lambda (new)
(map (lambda (perm) (cons (car perm) (map path->bregexp (cdr perm))))
new))))
(define sandbox-network-guard
(make-parameter (lambda (what . xs)
(error what "network access denied: ~e" xs))))
(define default-sandbox-guard
(let ([orig-security (current-security-guard)])
(make-security-guard
orig-security
(lambda (what path modes)
(when path
(let ([needed (let loop ([order permission-order])
(cond [(null? order)
(error 'default-sandbox-guard
"unknown access modes: ~e" modes)]
[(memq (car order) modes) (car order)]
[else (loop (cdr order))]))]
[bpath (parameterize ([current-security-guard orig-security])
(path->bytes (simplify-path* path)))])
(unless (ormap (lambda (perm)
(and (perm<=? needed (car perm))
(regexp-match (cadr perm) bpath)))
(sandbox-path-permissions))
(error what "file access denied ~a" (cons path modes))))))
(lambda args (apply (sandbox-network-guard) args)))))
(define sandbox-security-guard (make-parameter default-sandbox-guard))
;; computes permissions that are needed for require specs (`read' for all
;; files and "compiled" subdirs, `exists' for the base-dir)
(define (module-specs->path-permissions mods)
(define paths (module-specs->non-lib-paths mods))
(define bases
(let loop ([paths paths] [bases '()])
(if (null? paths)
(reverse bases)
(let-values ([(base name dir?) (split-path (car paths))])
(let ([base (simplify-path* base)])
(loop (cdr paths)
(if (member base bases) bases (cons base bases))))))))
(append (map (lambda (p) `(read ,(path->bytes p))) paths)
(map (lambda (b) `(read ,(build-path b "compiled"))) bases)
(map (lambda (b) `(exists ,b)) bases)))
;; takes a module-spec list and returns all module paths that are needed
;; ==> ignores (lib ...) modules
(define (module-specs->non-lib-paths mods)
(define (lib? x)
(if (module-path-index? x)
(let-values ([(m base) (module-path-index-split x)]) (lib? m))
(and (pair? x) (eq? 'lib (car x)))))
;; turns a module spec to a simple one (except for lib specs)
(define (simple-modspec mod)
(cond [(and (pair? mod) (eq? 'lib (car mod))) #f]
[(module-path? mod)
(simplify-path* (resolve-module-path mod #f))]
[(not (and (pair? mod) (pair? (cdr mod))))
;; don't know what this is, leave as is
#f]
[(eq? 'only (car mod))
(simple-modspec (cadr mod))]
[(eq? 'rename (car mod))
(simple-modspec (cadr mod))]
[(and (eq? 'prefix (car mod)) (pair? (cddr mod)))
(simple-modspec (caddr mod))]
[else #f]))
(let loop ([todo (filter values (map simple-modspec mods))]
[r '()])
(cond
[(null? todo) r]
[(member (car todo) r) (loop (cdr todo) r)]
[else
(let ([path (car todo)])
(loop (filter values
(map (lambda (i)
(simplify-path* (resolve-module-path-index i path)))
(filter (lambda (i)
(and (module-path-index? i) (not (lib? i))))
(apply append
(call-with-values
(lambda ()
(module-compiled-imports
(get-module-code (car todo))))
list)))))
(cons path r)))])))
;; Resources ----------------------------------------------------------------
(define-struct (exn:fail:resource exn:fail) (resource))
(define memory-accounting? (custodian-memory-accounting-available?))
(define (call-with-limits sec mb thunk)
(let ([r #f]
[c (make-custodian)]
;; used to copy parameter changes from the nested thread
[p current-preserved-thread-cell-values])
(when (and mb memory-accounting?)
(custodian-limit-memory c (* mb 1024 1024) c))
(parameterize ([current-custodian c])
;; The nested-thread can die on a time-out or memory-limit,
;; and never throws an exception, so we never throw an error,
;; just assume the a death means the custodian was shut down
;; due to memory limit. Note: cannot copy the
;; parameterization in this case.
(with-handlers ([exn:fail? (lambda (e)
(unless r (set! r (cons #f 'memory))))])
(call-in-nested-thread
(lambda ()
(define this (current-thread))
(define timer
(thread (lambda ()
(sleep sec)
;; even in this case there are no parameters
;; to copy, since it is on a different thread
(set! r (cons #f 'time))
(kill-thread this))))
(set! r
(with-handlers ([void (lambda (e) (list (p) raise e))])
(call-with-values thunk (lambda vs (list* (p) values vs)))))
(kill-thread timer))))
(custodian-shutdown-all c)
(unless r (error 'call-with-limits "internal error"))
;; apply parameter changes first
(when (car r) (p (car r)))
(if (pair? (cdr r))
(apply (cadr r) (cddr r))
(raise (make-exn:fail:resource (format "with-limit: out of ~a" (cdr r))
(current-continuation-marks)
(cdr r)))))))
(define-syntax with-limits
(syntax-rules ()
[(with-limits sec mb body ...)
(call-with-limits sec mb (lambda () body ...))]))
;; Execution ----------------------------------------------------------------
(define (literal-identifier=? x y)
(or (free-identifier=? x y)
(eq? (syntax-e x) (syntax-e y))))
(define-namespace-anchor anchor)
(define (make-evaluation-namespace)
(let* ([specs (sandbox-namespace-specs)]
[new-ns ((car specs))]
[orig-ns (namespace-anchor->empty-namespace anchor)]
[mods (cdr specs)])
(parameterize ([current-namespace orig-ns])
(for-each (lambda (mod) (dynamic-require mod #f)) mods))
(parameterize ([current-namespace new-ns])
(for-each (lambda (ms) (namespace-attach-module orig-ns ms))
mods))
new-ns))
(define (extract-required language requires)
(let* ([requires (cond [(string? language) (cons language requires)]
[(not (pair? language)) requires]
[(memq (car language) '(lib file planet quote))
(cons language requires)]
[(eq? (car language) 'begin) requires]
[else (error 'extract-required
"bad language spec: ~e" language)])])
requires))
(define (input->port inp)
;; returns #f when it can't create a port
(cond [(input-port? inp) inp]
[(string? inp) (open-input-string inp)]
[(bytes? inp) (open-input-bytes inp)]
[(path? inp) (open-input-file inp)]
[else #f]))
;; Gets an input spec returns a list of syntaxes. The input can be a list of
;; sexprs/syntaxes, or a list with a single input port spec
;; (path/string/bytes) value.
(define (input->code inps source n)
(if (null? inps)
'()
(let ([p (input->port (car inps))])
(cond [(and p (null? (cdr inps)))
(port-count-lines! p)
(parameterize ([current-input-port p])
((sandbox-reader) source))]
[p (error 'input->code "ambiguous inputs: ~e" inps)]
[else (let loop ([inps inps] [n n] [r '()])
(if (null? inps)
(reverse r)
(loop (cdr inps) (and n (add1 n))
;; 1st at line#1, pos#1, 2nd at line#2, pos#2 etc
;; (starting from the `n' argument)
(cons (datum->syntax
#f (car inps)
(list source n (and n 0) n (and n 1)))
r))))]))))
(define ((init-for-language language))
(cond [(or (not (pair? language))
(not (eq? 'special (car language))))
(void)]
[(eq? (cadr language) 'r5rs)
(read-case-sensitive #f)
(read-square-bracket-as-paren #f)
(read-curly-brace-as-paren #f)
(read-accept-infix-dot #f)]
[(memq (cadr language) teaching-langs)
(read-case-sensitive #t)
(read-decimal-as-inexact #f)]))
;; Returns a single (module ...) or (begin ...) expression (a `begin' list
;; will be evaluated one by one -- the language might not have a `begin').
;;
;; FIXME: inserting `#%require's here is bad if the language has a
;; `#%module-begin' that processes top-level forms specially.
;; A more general solution would be to create anew module that exports
;; the given language plus all of the given extra requires.
;;
;; We use `#%requre' because, unlike the `require' of scheme/base,
;; it comes from `#%kernel', so it's always present through
;; transitive requires.
(define (build-program language requires input-program)
(let* ([body (append (if (and (pair? requires) (eq? 'begin (car requires)))
(cdr requires)
(map (lambda (r) (list #'#%require r))
requires))
(input->code input-program 'program 1))]
[use-lang (lambda (lang) `(module program ,lang . ,body))])
(cond [(decode-language language)
=> (lambda (l)
(use-lang l))]
[(module-path? language)
(use-lang language)]
[(and (list? language) (eq? 'begin (car language)))
(append language body)]
[else (error 'make-evaluator "bad language spec: ~e" language)])))
(define (decode-language language)
(cond [(and (list? language)
(= 2 (length language))
(eq? (car language) 'special)
(memq (cadr language) teaching-langs))
`(lib ,(format "htdp-~a.ss" (cadr language)) "lang")]
[(equal? language '(special r5rs))
`(lib "lang.ss" "r5rs")]
[else #f]))
;; Like a toplevel (eval `(begin ,@exprs)), but the language that is used may
;; not have a begin.
(define (eval* exprs)
(if (null? exprs)
(void)
(let ([deftag (default-continuation-prompt-tag)])
(let loop ([expr (car exprs)] [exprs (cdr exprs)])
(if (null? exprs)
(eval expr)
(begin
(call-with-continuation-prompt
(lambda () (eval expr))
deftag
(lambda (x) (abort-current-continuation deftag x)))
(loop (car exprs) (cdr exprs))))))))
(define (evaluate-program program limits uncovered!)
(when uncovered!
(eval `(,#'#%require (lib "sandbox-coverage.ss" "mzlib" "private"))))
;; the actual evaluation happens under specified limits, if given
(let ([run (if (and (pair? program) (eq? 'begin (car program)))
(lambda () (eval* (cdr program)))
(lambda () (eval program)))]
[sec (and limits (car limits))]
[mb (and limits (cadr limits))])
(if (or sec mb) (call-with-limits sec mb run) (run)))
(let ([ns (syntax-case* program (module) literal-identifier=?
[(module mod . body)
(identifier? #'mod)
(let ([mod #'mod])
(eval `(,#'require (quote ,mod)))
(module->namespace `(quote ,(syntax-e mod))))]
[_else #f])])
(when uncovered!
(let ([get (let ([ns (current-namespace)])
(lambda () (eval '(get-uncovered-expressions) ns)))])
(uncovered! (list (get) get))))
(when ns (current-namespace ns))))
(define current-eventspace (mz/mr (make-parameter #f) current-eventspace))
(define make-eventspace (mz/mr void make-eventspace))
(define run-in-bg (mz/mr thread queue-callback))
(define bg-run->thread (if gui?
(lambda (ignored)
((mz/mr void eventspace-handler-thread) (current-eventspace)))
values))
(define null-input (open-input-bytes #""))
(define (kill-evaluator eval) (eval kill-evaluator))
(define (break-evaluator eval) (eval break-evaluator))
(define (set-eval-limits eval . args) ((eval set-eval-limits) args))
(define (put-input eval . args) (apply (eval put-input) args))
(define (get-output eval) (eval get-output))
(define (get-error-output eval) (eval get-error-output))
(define (get-uncovered-expressions eval . args)
(apply (eval get-uncovered-expressions) args))
(define (make-evaluator* init-hook require-perms program-or-maker)
(define cust (make-custodian))
(define coverage? (sandbox-coverage-enabled))
(define uncovered #f)
(define input-ch (make-channel))
(define result-ch (make-channel))
(define input #f)
(define output #f)
(define error-output #f)
(define limits (sandbox-eval-limits))
(define user-thread #t) ; set later to the thread
(define orig-cust (current-custodian))
(define (user-kill)
(when user-thread
(let ([t user-thread])
(set! user-thread #f)
(custodian-shutdown-all cust)
(kill-thread t))) ; just in case
(void))
(define (user-break)
(when user-thread (break-thread user-thread)))
(define (user-process)
(with-handlers ([void (lambda (exn) (channel-put result-ch exn))])
;; first set up the environment
(init-hook)
((sandbox-init-hook))
;; now read and evaluate the input program
(evaluate-program
(if (procedure? program-or-maker) (program-or-maker) program-or-maker)
limits
(and coverage? (lambda (es+get) (set! uncovered es+get))))
(channel-put result-ch 'ok))
;; finally wait for interaction expressions
(let loop ([n 1])
(let ([expr (channel-get input-ch)])
(when (eof-object? expr) (channel-put result-ch expr) (user-kill))
(with-handlers ([void (lambda (exn)
(channel-put result-ch (cons 'exn exn)))])
(let* ([code (input->code (list expr) 'eval n)]
[sec (and limits (car limits))]
[mb (and limits (cadr limits))]
[run (if (or sec mb)
(lambda () (with-limits sec mb (eval* code)))
(lambda () (eval* code)))])
(channel-put result-ch
(cons 'vals (call-with-values run list)))))
(loop (add1 n)))))
(define (user-eval expr)
(let ([r (if user-thread
(begin (channel-put input-ch expr)
(let loop ()
(with-handlers ([(lambda (e)
(and (sandbox-propagate-breaks)
(exn:break? e)))
(lambda (e)
(user-break)
(loop))])
(channel-get result-ch))))
eof)])
(cond [(eof-object? r) (error 'evaluator "terminated")]
[(eq? (car r) 'exn) (raise (cdr r))]
[else (apply values (cdr r))])))
(define get-uncovered
(case-lambda
[() (get-uncovered #t)]
[(prog?) (get-uncovered prog? 'program)]
[(prog? src)
(unless uncovered
(error 'get-uncovered-expressions "no coverage information"))
(let ([uncovered (if prog? (car uncovered) ((cadr uncovered)))])
(if src
(filter (lambda (x) (equal? src (syntax-source x))) uncovered)
uncovered))]))
(define (output-getter p) (if (procedure? p) (user-eval `(,p)) p))
(define input-putter
(case-lambda
[() (input-putter input-putter)]
[(arg) (cond [(not input)
(error 'put-input "evaluator input is not 'pipe")]
[(or (string? arg) (bytes? arg))
(display arg input) (flush-output input)]
[(eof-object? arg) (close-output-port input)]
[(eq? arg input-putter) input]
[else (error 'put-input "bad input: ~e" arg)])]))
(define (evaluator expr)
(cond [(eq? expr kill-evaluator) (user-kill)]
[(eq? expr break-evaluator) (user-break)]
[(eq? expr set-eval-limits) (lambda (args) (set! limits args))]
[(eq? expr put-input) input-putter]
[(eq? expr get-output) (output-getter output)]
[(eq? expr get-error-output) (output-getter error-output)]
[(eq? expr get-uncovered-expressions) get-uncovered]
[else (user-eval expr)]))
(define linked-outputs? #f)
(define (make-output what out set-out! allow-link?)
(cond [(not out) (open-output-nowhere)]
[(and (procedure? out) (procedure-arity-includes? out 0)) (out)]
[(output-port? out) out]
[(eq? out 'pipe) (let-values ([(i o) (make-pipe)]) (set-out! i) o)]
[(memq out '(bytes string))
(let* ([bytes? (eq? 'bytes out)]
;; the following doesn't really matter: they're the same
[out ((if bytes? open-output-bytes open-output-string))])
(set-out!
(lambda ()
(parameterize ([current-custodian orig-cust])
(let ([buf (get-output-bytes out #t)])
(if bytes? buf (bytes->string/utf-8 buf #\?))))))
out)]
[else (error 'make-evaluator "bad sandox-~a spec: ~e" what out)]))
(parameterize* ; the order in these matters
(;; create a sandbox context first
[current-custodian cust]
[current-thread-group (make-thread-group)]
[current-namespace (make-evaluation-namespace)]
;; set up the IO context
[current-input-port
(let ([inp (sandbox-input)])
(cond
[(not inp) null-input]
[(input->port inp) => values]
[(and (procedure? inp) (procedure-arity-includes? inp 0)) (inp)]
[(eq? 'pipe inp)
(let-values ([(i o) (make-pipe)]) (set! input o) i)]
[else (error 'make-evaluator "bad sandbox-input: ~e" inp)]))]
[current-output-port (make-output 'output (sandbox-output)
(lambda (o) (set! output o))
#f)]
[current-error-port (make-output 'error-output (sandbox-error-output)
(lambda (o) (set! error-output o))
#t)]
;; paths
[current-library-collection-paths
(filter directory-exists?
(append (sandbox-override-collection-paths)
(current-library-collection-paths)))]
[sandbox-path-permissions
(append (map (lambda (p) `(read ,p))
(current-library-collection-paths))
(module-specs->path-permissions require-perms)
(sandbox-path-permissions))]
;; general info
[current-command-line-arguments '#()]
;; restrict the sandbox context from this point
[current-security-guard (sandbox-security-guard)]
[exit-handler (lambda x (error 'exit "user code cannot exit"))]
[current-inspector (make-inspector)]
;; This breaks: [current-code-inspector (make-inspector)]
;; Note the above definition of `current-eventspace': in MzScheme, it
;; is an unused parameter. Also note that creating an eventspace
;; starts a thread that will eventually run the callback code (which
;; evaluates the program in `run-in-bg') -- so this parameterization
;; must be nested in the above (which is what paramaterize* does), or
;; it will not use the new namespace.
[current-eventspace (make-eventspace)])
(set! user-thread (bg-run->thread (run-in-bg user-process)))
(let ([r (channel-get result-ch)])
(if (eq? r 'ok)
;; initial program executed ok, so return an evaluator
evaluator
;; program didn't execute
(raise r)))))
(define make-evaluator
(lambda (language requires #:allow-read [allow null] . input-program)
;; `input-program' is either a single argument specifying a file/string,
;; or multiple arguments for a sequence of expressions
(let (;; make it possible to provide #f for no language and no requires
[lang language]
;; make it possible to use simple paths to files to require
[reqs (cond [(not (list? requires))
(error 'make-evaluator "bad requires: ~e" requires)]
[else
(map (lambda (r)
(if (or (pair? r) (symbol? r))
r
`(file ,(path->string (simplify-path* r)))))
requires)])])
(make-evaluator* (init-for-language lang)
(append (extract-required (or (decode-language lang)
lang)
reqs)
allow)
(lambda () (build-program lang reqs input-program))))))
(define make-module-evaluator
(lambda (input-program #:allow-read [allow null])
;; this is for a complete module input program
(let ([prog (input->code (list input-program) 'program #f)])
(unless (= 1 (length prog))
(error 'make-evaluator "expecting a single `module' program; ~a"
(if (zero? (length prog))
"no program expressions given"
"got more than a single expression")))
(syntax-case* (car prog) (module) literal-identifier=?
[(module modname lang body ...)
(make-evaluator* void allow (car prog))]
[_else (error 'make-evaluator "expecting a `module' program; got ~e"
(syntax->datum (car prog)))]))))

View File

@ -0,0 +1,28 @@
#lang scribble/doc
@require["common.ss"
(for-label scheme/gui/dynamic)]
@title{Dynamic Loading}
@defmodule[scheme/gui/dynamic]{The @schememodname[scheme/gui/dynamic]
library provides functiosn for dynamically accessing the PLT Scheme
GUI toolbox, instead of directly requiring @scheme[scheme/gui] or
@scheme[scheme/gui/base].}
@defproc[(gui-available?) boolean?]{
Returns @scheme[#t] if dynamic access to the GUI bindings are
available---that is, that the program is being run as a
@exec{mred}-based application, as opposed to a pure
@exec{mzscheme}-based application, and that GUI modules are attached
to the namespace in which @scheme[scheme/gui/dynamic] was
instantiated.
This predicate can be used in code that optionally uses GUI elements
when they are available.}
@defproc[(gui-dynamic-require [sym symbol?]) any]{
Like @scheme[dynamic-require], but specifically to access exports of
@scheme[scheme/gui/base].}

View File

@ -4,20 +4,21 @@
@title[#:tag-prefix '(lib "scribblings/gui/gui.scrbl")
#:tag "top"]{PLT Scheme GUI: MrEd}
@declare-exporting[mred scheme/gui]
@declare-exporting[scheme/gui/base scheme/gui]
This reference manual describes the MrEd GUI toolbox that is part of
PLT Scheme. See @secref[#:doc '(lib "scribblings/guide/guide.scrbl")
"mred"] in @italic{@link["../guide/index.html"]{A Guide to PLT
Scheme}} for an introduction to MrEd.
@defmodule*/no-declare[(mred)]{The @schememodname[mred] module provides
all of the class, interface, and procedure bindings defined in this
manual.}
@defmodule*/no-declare[(scheme/gui/base)]{The
@schememodname[scheme/gui/base] module provides all of the class,
interface, and procedure bindings defined in this manual.}
@defmodulelang*/no-declare[(scheme/gui)]{The
@schememodname[scheme/gui] language combines all bindings of the
@schememodname[scheme] language and the @schememodname[mred] module.}
@schememodname[scheme] language and the
@schememodname[scheme/gui/base] modules.}
@table-of-contents[]
@ -27,6 +28,7 @@ manual.}
@include-section["guide.scrbl"]
@include-section["reference.scrbl"]
@include-section["config.scrbl"]
@include-section["dynamic.scrbl"]
@;------------------------------------------------------------------------

View File

@ -424,22 +424,16 @@ If the AppleEvent reply contains a value that cannot be
}
@defproc[(make-namespace-with-mred [flag (one-of/c 'mred 'initial 'empty) 'mred])
@defproc[(make-gui-empty-namespace)
namespace?]{
Like @scheme[make-namespace], but the @scheme[(lib "mred.ss"
"mred")] module of the current namespace is attached. In addition, by
default, the namespace is initialized by importing the @filepath{mred.ss}
module and MzLib's @indexed-file{class.ss} module into the
namespace's top-level environment.
Like @scheme[make-base-empty-namespace], but with
@scheme[scheme/class] and @schememodname[scheme/gui/base] also
attached to the result namespace.}
@defproc[(make-gui-namespace)
namespace?]{
The @scheme['initial] and @scheme['empty] flags control the namespace
creation in the same way as for @scheme[make-namespace], except that
the @filepath{mred.ss} module is attached to the created namespace (along
with the transitive closure of its imports). The @scheme['mred] flag
is like @scheme['initial], but also imports the @filepath{mred.ss} module
and MzLib's @indexed-file{class.ss} module into the namespace's
top-level environment.
}
Like @scheme[make-base-namespace], but with @scheme[scheme/class] and
@schememodname[scheme/gui/base] also required into the top-level
environment of the result namespace.}

View File

@ -10,10 +10,8 @@
(all-from-out scribble/eval)
(all-from-out scheme/contract))
(require (for-label scheme
"to-do.ss"))
(provide (for-label (all-from-out scheme)
(all-from-out "to-do.ss")))
(require (for-label scheme))
(provide (for-label (all-from-out scheme)))
(define AllUnix "Unix and Mac OS X")
(provide AllUnix)

View File

@ -0,0 +1,559 @@
#lang scribble/doc
@(require "mz.ss"
scheme/sandbox
(for-label scheme/sandbox
#;(only-in mred/mred make-gui-namespace)))
@title{Sandboxed Evaluation}
@note-lib-only[scheme/sandbox]
The @schememodname[scheme/sandbox] module provides utilities for
creating ``sandboxed'' evaluators, which are configured in a
particular way and can have restricted filesystem access, network
access, and memory use.
@defproc*[([(make-evaluator [language (or/c module-path?
(list/c (one-of/c 'special) symbol?)
(cons/c (one-of/c 'begin) list?))]
[requires (listof (or/c module-path? path?))]
[input-program any/c] ...
[#:allow-read allow (listof (or/c module-path? path?))])
(any/c . -> . any)]
[(make-module-evaluator [module-decl (or/c syntax? pair?)]
[#:allow-read allow (listof (or/c module-path? path?))])
(any/c . -> . any)])]{
The @scheme[make-evaluator] function creates an evaluator with a
@scheme[language] and @scheme[requires] specification, and starts
evaluating the given @scheme[input-program]s. The
@scheme[make-module-evaluator] function creates an evaluator that
works in the context of a given module. The result in either case is a
function for further evaluation.
The returned evaluator operates in an isolated and limited
environment. In particular, filesystem access is restricted. The
@scheme[allow] argument extends the set of files that are readable by
the evaluator to include the specified modules and their imports
(transitively). When @scheme[language] is a module path and when
@scheme[requires] is provided, the indicated modules are implicitly
included in the @scheme[allow] list.
Each @scheme[input-program] or @scheme[module-decl] argument provides
a program in one of the following forms:
@itemize{
@item{an input port used to read the program;}
@item{a string or a byte string holding the complete input;}
@item{a path that names a file holding the input; or}
@item{an S-expression or a @tech{syntax object}, which is evaluated
as with @scheme[eval]; see also
@scheme[get-uncovered-expressions].}
}
In the first three cases above, the program is read using
@scheme[sandbox-reader], with line-counting enabled for sensible error
messages, and with @scheme['program] as the source (used for testing
coverage). In the last case, the input is expected to be the complete
program, and is converted to a @tech{syntax object} (using
@scheme['program] as the source), unless it already is a @tech{syntax
object}.
The returned evaluator function accepts an additional expressions
(each time it is called) in essentially the same form: a string or
byte string holding a sequence of expressions, a path for a file
holding expressions, an S-expression, or a @tech{syntax object}. If
the evaluator receives an @scheme[eof] value, it is terminated and
raises errors thereafter. See also @scheme[kill-evaluator], which
terminates the evaluator without raising an exception.
For @scheme[make-evaluator], multiple @scheme[input-program]s are
effectively concatenated to form a single program. The way that the
@scheme[input-program]s are evaluated depends on the @scheme[language]
argument:
@itemize{
@item{The @scheme[language] argument can be a module path (i.e., a
datum that matches the grammar for @scheme[_module-path] of
@scheme[require]).
In this case, the @scheme[input-program]s are automatically
wrapped in a @scheme[module], and the resulting evaluator works
within the resulting module's namespace.}
@item{The @scheme[language] argument can be a list starting with
@scheme['special], which indicates a built-in language with
special input configuration. The possible values are
@scheme['(special r5rs)] or a value indicating a teaching
language: @scheme['(special beginner)], @scheme['(special
beginner-abbr)], @scheme['(special intermediate)],
@scheme['(special intermediate-lambda)], or @scheme['(special
advanced)].
In this case, the @scheme[input-program]s are automatically
wrapped in a @scheme[module], and the resulting evaluator works
within the resulting module's namespace. In addition, certain
parameters (such as such as @scheme[read-accept-infix-dot]) are
set to customize reading programs from strings and ports.}
@item{Finally, @scheme[language] can be a list whose first element is
@scheme['begin].
In this case, a new namespace is created using
@scheme[sandbox-namespace-specs], which by default creates a
new namespace using @scheme[make-base-namespace] or
@scheme[make-gui-namespace] (depending on @scheme[gui?]).
In the new namespace, @scheme[language] is evaluated as an
expression to further initialize the namespace.}
}
The @scheme[requires] list adds additional imports to the module or
namespace for the @scheme[input-program]s, even in the case that
@scheme[require] is not made available through the @scheme[language].
The following examples illustrate the difference between an evaluator
that puts the program in a module and one that merely initializes a
top-level namespace:
@interaction[
(define base-module-eval
(code:comment #, @t{a module cannot have free variables...})
(make-evaluator 'scheme/base '() '(define (f) later)))
(define base-module-eval
(make-evaluator 'scheme/base '() '(define (f) later)
'(define later 5)))
(base-module-eval '(f))
(define base-top-eval
(code:comment #, @t{non-module code can have free variables:})
(make-evaluator '(begin) '() '(define (f) later)))
(base-top-eval '(+ 1 2))
(base-top-eval '(define later 5))
(base-top-eval '(f))
]
The @scheme[make-module-evaluator] function is essentially a
restriction of @scheme[make-evaluator], where the program must be a
module, and all imports are part of the program:
@schemeblock[
(define base-module-eval2
(code:comment #, @t{equivalent to @scheme[base-module-eval]:})
(make-module-evaluator '(module m scheme/base
(define (f) later)
(define later 5))))
]
In all cases, the evaluator operates in an isolated and limited
environment:
@itemize{
@item{It uses a new custodian and namespace. When @scheme[gui?] is
true, it is also runs in its own eventspace.}
@item{The evaluator works under the @scheme[sandbox-security-guard],
which restricts file system and network access.}
@item{Each evaluation is wrapped in a @scheme[call-with-limits]; see
also @scheme[sandbox-eval-limits] and @scheme[set-eval-limits].}
}
Evaluation can also be instrumented to track evaluation information
when @scheme[sandbox-coverage-enabled] is set. Exceptions (both syntax
and run-time) are propagated in the usual way to the caller of the
evaluation function.}
@; ----------------------------------------------------------------------
@section{Customizing Evaluators}
The evaluators that @scheme[make-evaluator] creates can be customized
via several parameters. These parameters affect newly created
evaluators; changing them has no effect on already-running evaluators.
@defparam[sandbox-init-hook thunk (-> any)]{
A parameter that determines a thunk to be called for initializing a
new evaluator. The hook is called just before the program is
evaluated in a newly-created evaluator context. It can be used to
setup environment parameters related to reading, writing, evaluation,
and so on. Certain languages (@scheme['(r5rs)] and the teaching
languages) have initializations specific to the language; the hook is
used after that initialization, so it can override settings.}
@defparam[sandbox-reader proc (any/c . -> . any)]{
A parameter that determines a function to reads all expressions from
@scheme[(current-input-port)]. The function is used to read program
source for an evaluator when a string. byte string, or port is
supplies. The reader function receives a value to be used as input
source (i.e., the first argument to @scheme[read-syntax]), and it
should return a list of @tech{syntax objects}. The default reader
calls @scheme[read-syntax], accumulating results in a list until it
receives @scheme[eof].}
@defparam[sandbox-input in (or/c false/c
string? bytes?
input-port?
(one-of/c 'pipe)
(-> input-port?))]{
A parameter that determines the initial @scheme[current-input-port]
setting for a newly created evaluator. It defaults to @scheme[#f],
which creates an empty port. The following other values are allowed:
@itemize{
@item{a string or byte string, which is converted to a port using
@scheme[open-input-string] or @scheme[open-input-bytes];}
@item{an input port;}
@item{the symbol @scheme['pipe], which triggers the creation of a
pipe, where @scheme[put-input] can return the output end of the
pipe or write directly to it;}
@item{a thunk, which is called to obtain a port (e.g., using
@scheme[current-input-port] means that the evaluator input is
the same as the calling context's input).}
}}
@defparam[sandbox-output in (or/c false/c
output-port?
(one-of/c 'pipe 'bytes 'string)
(-> output-port?))]{
A parameter that determines the initial @scheme[current-output-port]
setting for a newly created evaluator. It defaults to @scheme[#f],
which creates a port that discrds all data. The following other
values are allowed:
@itemize{
@item{an output port, which is used as-is;}
@item{the symbol @scheme['bytes], which causes @scheme[get-output]
to return the complete output as a byte string;}
@item{the symbol @scheme['string], which is similar to
@scheme['bytes], but makes @scheme[get-output] produce a
string;}
@item{the symbol @scheme['pipe], which triggers the creation of a
pipe, where @scheme[get-output] returns the input end of the
pipe;}
@item{a thunk, which is called to obtain a port (e.g., using
@scheme[current-output-port] means that the evaluator output is
not diverted).}
}}
@defparam[sandbox-error-output in (or/c false/c
output-port?
(one-of/c 'pipe 'bytes 'string)
(-> output-port?))]{
Like @scheme[sandbox-output], but for the initial
@scheme[current-error-port] value. An evaluator's error output is set
after its output, so using @scheme[current-output-port] for this
parameter value means that the error port is the same as the
evaluator's initial output port.
The default is @scheme[current-error-port], which means that the error
output of the generated evaluator goes to the calling context's error
port.}
@defboolparam[sandbox-coverage-enabled enabled?]{
A parameter that controls whether syntactic coverage information is
collected by sandbox evaluators. Use
@scheme[get-uncovered-expressions] to retrieve coverage information.}
@defparam[sandbox-namespace-specs spec (cons/c (-> namespace?)
(listof module-path?))]{
A parameter that holds a list of values that specify how to create a
namespace for evaluation in @scheme[make-evaluator] or
@scheme[make-module-evaluator]. The first item in the list is a thunk
that creates the namespace, and the rest are module paths for modules
that to be attached to the created namespace using
@scheme[namespace-attach-module].
The default is @scheme[(list make-base-namespace)] if @scheme[gui?] is
@scheme[#f], @scheme[(list make-gui-namespace)] if @scheme[gui?] is
@scheme[#t].
The module paths are needed for sharing module instantiations between
the sandbox and the caller. For example, sandbox code that returns
@scheme[posn] values (from the @schemeidfont{lang/posn} module) will
not be recognized as such by your own code by default, since the
sandbox will have its own instance of @schemeidfont{lang/posn} and
thus its own struct type for @scheme[posn]s. To be able to use such
values, include @scheme['lang/posn] in the list of module paths.
When testing code that uses a teaching language, the following piece
of code can be helpful:
@schemeblock[
(sandbox-namespace-specs
(let ([specs (sandbox-namespace-specs)])
`(,(car specs)
,@(cdr specs)
lang/posn
,@(if mred? '(mrlib/cache0image-snip) '()))))
]}
@defparam[sandbox-override-collection-paths paths (listof path-string?)]{
A parameter that determines a list of collection directories to prefix
@scheme[current-library-collection-paths] in an evaluator. This
parameter useful for cases when you want to test code using an
alternate, test-friendly version of a collection, for example, testing
code that uses GUI (like the @schememodname[htdp/world] teachpack) can
be done using a fake library that provides the same interface but no
actual interaction. The default is @scheme[null].}
@defparam[sandbox-security-guard guard security-guard?]{
A parameter that determines the initial
@scheme[(current-security-guard)] for sandboxed evaluations. The
default forbids all filesystem I/O except for things in
@scheme[sandbox-path-permissions], and it uses
@scheme[sandbox-network-guard] for network connections.}
@defparam[sandbox-path-permissions perms
(listof (list/c (one-of/c 'execute 'write 'delete
'read 'exists)
(or/c byte-regexp? bytes? string? path?)))]{
A parameter that configures the behavior of the default sandbox
security guard by listing paths and access modes that are allowed for
them. The contents of this parameter is a list of specifications,
each an access mode and a byte-regexp for paths that are granted this
access.
The access mode symbol is one of: @scheme['execute], @scheme['write],
@scheme['delete], @scheme['read], or @scheme['exists]. These symbols are
in decreasing order: each implies access for the following modes too
(e.g., @scheme['read] allows reading or checking for existence).
The path regexp is used to identify paths that are granted access. It
can also be given as a path (or a string or a byte string), which is
(made into a complete path, cleansed, simplified, and then) converted
to a regexp that allows the path and sub-directories; e.g.,
@scheme["/foo/bar"] applies to @scheme["/foo/bar/baz"].
The default value is null, but when an evaluator is created, it is
augmented by @scheme['read] permissions that make it possible to use
collection libraries (including
@scheme[sandbox-override-collection-paths]). See
@scheme[make-evalautor] for more information.}
@defparam[sandbox-network-guard proc
(symbol?
(or/c (and/c string? immutable?) false/c)
(or/c (integer-in 1 65535) false/c)
(one-of/c 'server 'client)
. -> . any)]{
A parameter that specifieds a procedure to be used (as is) by the
default @scheme[sandbox-security-guard]. The default forbids all
network connection.}
@defparam[sandbox-eval-limits limits (or/c
(list/c (or/c exact-nonnegative-integer?
false/c)
(or/c exact-nonnegative-integer?
false/c))
false/c)]{
A parameter that determines the default limits on @italic{each} use of
a @scheme[make-evaluator] function, including the initial evaluation
of the input program. Its value should be a list of two numbers, the
first is a timeout value in seconds, and the second is a memory limit
in megabytes. Either one can be @scheme[#f] for disabling the
corresponding limit; alternately, the parameter can be set to
@scheme[#f] to disable all limits (in case more are available in
future versions).
When limits are set, @scheme[call-with-limits] (see below) is wrapped
around each use of the evaluator, so consuming too much time or memory
results in an exception. You can change the limits of a running
evaluator using @scheme[set-eval-limits].}
@; ----------------------------------------------------------------------
@section{Interacting with Evaluators}
The following functions actually pass themselves to the given
procedure. An evaluator procedure recognizes these procedures (using
@scheme[eq?]) to take an appropriate action.
@defproc[(kill-evaluator [evaluator (any/c . -> . any)]) void?]{
Releases the resources that are held by @scheme[evaluator] by shutting
down the evaluator's custodian. Attempting to use an evaluator after
killing raises an exception, and attempts to kill a dead evaluator are
ignored.
Killing an evaluator is similar to sending an @scheme[eof] value to
the evaluator, except that an @scheme[eof] value will raise an error
immediately.}
@defproc[(set-eval-limits [evaluator (any/c . -> . any)]
[secs (or/c exact-nonnegative-integer? false/c)]
[mb (or/c exact-nonnegative-integer? false/c)]) void?]{
Changes the per-expression limits that @scheme[evaluator] uses to
@scheme[sec] seconds and @scheme[mb] megabytes (either one can be
@scheme[#f], indicating no limit).
This procedure should be used to modify an existing evaluator limits,
because changing the @scheme[sandbox-eval-limits] parameter does not
affect existing evaluators. See also @scheme[call-with-limits].}
@defproc*[([(put-input [evaluator (any/c . -> . any)]) output-port?]
[(put-input [evaluator (any/c . -> . any)]
[i/o (or/c bytes? string? eof-object?)]) void?])]{
If @scheme[(sandbox-input)] is @scheme['pipe] when an evaluator is
created, then this procedure can be used to retrieve the output port
end of the pipe (when used with no arguments), or to add a string or a
byte string into the pipe. It can also be used with @scheme[eof],
which closes the pipe.}
@defproc*[([(get-output [evaluator (any/c . -> . any)]) (or/c input-port? bytes? string?)]
[(get-error-output [evaluator (any/c . -> . any)]) (or/c input-port? bytes? string?)])]{
Returns the output or error-output of the @scheme[evaluator],
in a way that depends on the setting of @scheme[(sandbox-output)] or
@scheme[(sandbox-error-output)] when the evaluator was created:
@itemize{
@item{if it was @scheme['pipe], then @scheme[get-output] returns the
input port end of the created pipe;}
@item{if it was @scheme['bytes] or @scheme['string], then the result
is the accumulated output, and the output is directed to a new
output string or byte string (so each call returns a different
piece of the evaluator's output);}
@item{otherwise, it returns @scheme[#f].}
}}
@defproc[(get-uncovered-expressions [evaluator (any/c . -> . any)]
[prog? any/c #t]
[src any/c 'program])
(listof syntax?)]{
Retrieves uncovered expression from an evaluator, as longs as the
@scheme[sandbox-coverage-enabled] parameter had a true value when the
evaluator was created. Otherwise, and exception is raised to indicate
that no coverage information is available.
The @scheme[prog?] argument specifies whether to obtain expressions that
were uncovered after only the original input program was evaluated
(@scheme[#t]) or after all later uses of the evaluator (@scheme[#f]).
Using @scheme[#t] retrieves a list that is saved after the input
program is evaluated, and before the evaluator is used, so the result is
always the same.
A @scheme[#t] value of @scheme[prog?] is useful for testing student
programs to find out whether a submission has sufficient test coverage
built in. A @scheme[#f] value is useful for writing test suites for a
program to ensure that your tests cover the whole code.
The second optional argument, @scheme[src], specifies that the result
should be filtered to hold only @tech{syntax objects} whose source
matches @scheme[src]. The default, @scheme['program], is the source
associated with the input program by the default
@scheme[sandbox-reader]---which provides only @tech{syntax objects}
from the input program (and not from required modules or expressions
that were passed to the evaluator). A @scheme[#f] avoids filtering.
The resulting list of @tech{syntax objects} has at most one expression
for each position and span. Thus, the contents may be unreliable, but
the position information is reliable (i.e., it always indicates source
code that would be painted red in DrScheme when coverage information
is used).
Note that if the input program is a sequence of syntax values, either
make sure that they have @scheme['program] as the source field, or use
the @scheme[src] argument. Using a sequence of S-expressions (not
@tech{syntax objects}) for an input program leads to unreliable
coverage results, since each expression may be assigned a single
source location.}
@; ----------------------------------------------------------------------
@section{Miscellaneous}
@defthing[gui? boolean?]{
True if the @schememodname[scheme/gui] module can be used, @scheme[#f]
otherwise. Various aspects of the @schememodname[scheme/sandbox]
library change when the GUI library is available, such as using a new
eventspace for each evaluator.}
@defproc[(call-with-limits [secs (or/c exact-nonnegative-integer? false/c)]
[mb (or/c exact-nonnegative-integer? false/c)]
[thunk (-> any)])
any]{
Executes the given @scheme[thunk] with memory and time restrictions:
if execution consumes more than @scheme[mb] megabytes or more than
@scheme[sec] seconds, then the computation is aborted and the
@exnraise[exn:fail:resource]. Otherwise the result of the thunk is
returned as usual (a value, multiple values, or an exception). Each
of the two limits can be @scheme[#f] to indicate the absence of a
limit. See also @scheme[custodian-limit-memory] for information on
memory limits.
Sandboxed evaluators use @scheme[call-with-limits], according to the
@scheme[sandbox-eval-limits] setting and uses of
@scheme[set-eval-limits]: each expression evaluation is protected from
timeouts and memory problems. Use @scheme[call-with-limits] directly
only to limit a whole testing session, instead of each expression.}
@defform[(with-limits mb-expr body-expr body ...)]{
A macro version of @scheme[call-with-limits].}
@defproc*[([(exn:fail:resource? [v any/c]) boolean?]
[(exn:fail:resource-resource [exn exn:fail:resource?])
(one-of/c 'time 'memory)])]{
A predicate and accessor for exceptions that are raised by
@scheme[call-with-limits]. The @scheme[resource] field holds a symbol,
either @scheme['time] or @scheme['memory].}

View File

@ -8,9 +8,10 @@
@;------------------------------------------------------------------------
@include-section["namespaces.scrbl"]
@include-section["eval.scrbl"]
@include-section["module-reflect.scrbl"]
@include-section["security-guards.scrbl"]
@include-section["custodians.scrbl"]
@include-section["thread-groups.scrbl"]
@include-section["struct-inspectors.scrbl"]
@include-section["code-inspectors.scrbl"]
@include-section["module-reflect.scrbl"]
@include-section["sandbox.scrbl"]

View File

@ -1,8 +0,0 @@
(module to-do mzscheme
(define list-mutableof #t)
(define list-mutable/c #t)
(define cons-mutable/c #t)
(provide (all-defined)))

View File

@ -45,6 +45,9 @@ variable, meta-variable, etc.---use @scheme[schemeidfont] (e.g., as in
not merely @scheme[schemefont] or @scheme[verbatim], to refer to a
specific sequence of characters.
Refrain from referring to documentation ``above'' or ``below,'' and
instead have a hyperlink point to the right place.
Use American style for quotation marks and punctuation at the end of
quotation marks (i.e., a sentence-terminating period goes inside the
quotation marks). Of course, this rule does not apply for quotation

View File

@ -13,7 +13,7 @@
(define get-slides-as-picts
(opt-lambda (file w h c? [stop-after #f])
(let ([ns (make-namespace-with-mred)]
(let ([ns (make-gui-namespace)]
[orig-ns (current-namespace)]
[param ((current-module-name-resolver) '(lib "param.ss" "slideshow") #f #f)]
[core ((current-module-name-resolver) '(lib "core.ss" "slideshow") #f #f)]

View File

@ -584,7 +584,7 @@
(define (run-example-talk f)
(let ([c (make-custodian)])
(parameterize ([current-namespace (make-namespace-with-mred)]
(parameterize ([current-namespace (make-gui-namespace)]
[current-command-line-arguments
(vector (path->string
(build-path (collection-path "slideshow")

View File

@ -1083,12 +1083,12 @@
(parameterize ([current-namespace n])
(namespace-mapped-symbols)))]
[expect-n (list* 'mred@ 'mred^
(append (get-all (let ([n (make-namespace)])
(append (get-all (let ([n (make-base-namespace)])
(parameterize ([current-namespace n])
(namespace-require '(lib "class.ss")))
n))
in-top-level))]
[actual-n (get-all (make-namespace-with-mred))])
[actual-n (get-all (make-gui-namespace))])
(for-each
(lambda (i)
(unless (memq i expect-n)

View File

@ -2481,7 +2481,7 @@ scheme_lookup_binding(Scheme_Object *find_id, Scheme_Comp_Env *env, int flags,
if (!genv) {
scheme_wrong_syntax("require", NULL, src_find_id,
"namespace mismatch; reference (phase %d) to a module"
" %D that is not instantiated (phase %d)",
" %D that is not available (phase %d)",
env->genv->phase, modname, mod_defn_phase);
return NULL;
}

View File

@ -1732,7 +1732,7 @@ static Scheme_Object *link_module_variable(Scheme_Object *modidx,
if (!menv) {
scheme_wrong_syntax("link", NULL, varname,
"namespace mismatch; reference (phase %d) to a module"
" %D that is not instantiated (phase %d); reference"
" %D that is not available (phase %d); reference"
" appears in module: %D",
env->phase,
modname,