diff --git a/collects/handin-server/extra-utils.ss b/collects/handin-server/checker.ss similarity index 100% rename from collects/handin-server/extra-utils.ss rename to collects/handin-server/checker.ss diff --git a/collects/handin-server/doc.txt b/collects/handin-server/doc.txt index d426487e97..0289294aa5 100644 --- a/collects/handin-server/doc.txt +++ b/collects/handin-server/doc.txt @@ -33,7 +33,7 @@ server and each user's password. Quick Start for a Test Drive: -============================================ +============================= 1. Create a new directory. @@ -83,10 +83,8 @@ Quick Start for a Test Drive: ------------------------------------------------------------------- -Client Customization -============================================ - -To customize the client: +Client Customization: +===================== 1. Rename (or make a copy of) the "handin-client" collection directory. The new name should describe your class uniquely. @@ -139,8 +137,8 @@ To customize the client: --all-users flag. -Server Setup -============================================ +Server Setup: +============= You must prepare a special directory to host the handin server. To run the server, you should either be in this directory, or you should @@ -556,26 +554,102 @@ to start with a specific assignment (named ASSIGNMENT). The default PORT is 7980. -Checker Utilities -============================================ +Checker Utilities: +================== -The _utils.ss_ module provides utilities helpful in implementing -`checker' functions: +The checker utilities are provided to make writing checker functions. +They are provided in a few layers, each layer provides new +functionality in addition to the lower one. These modules are (in +order): -> (get-conf key) - Returns a value from the configuration file (useful for reading - things like field names etc) +* sandbox.ss -- basic sandbox evaluation utilities, can be used + independently from the handin-server. -> (unpack-submission bytes) - Returns two text% objects corresponding to the submitted definitions - and interactions windows. +* utils.ss -- additional utilities for dealing with handin + submissions, as well as a few helpers for testing code. -> (make-evaluator language teachpack-paths program-port) - Returns a function of one required argument for evaluating - expressions in the designated language, and loading teachpacks that - are specified in `teachpack-paths'. The `program-port' is an input - port that produces the content of the definitions window; use - `(open-input-string "")' for an empty definitions window. +* checker.ss -- this layer automates the task of creating a checker + function (in "/checker.ss" modules) to cope with + common submission situations. + +The following sections describe each of these modules. + + +_sandbox.ss_ +------------ + +The main function that is implemented in this module is +`make-evaluator'. Most of the functionality that is provided is used +by this function. + +> mred? + A boolean that is bound to `#t' if we're currently running in MrEd, + `#f' if in plain MzScheme. The idea is that you can use this module + both from MzScheme or, if needed, from MrEd. (Higher levels + ("utils.ss" and "checker.ss"), need to be used with MrEd.) + +> coverage-enabled + A boolean parameter that controls whether coverage testing is + enabled in `make-evaluator'-created functions. If it set to true, + the "handin-server/private/coverage.ss" module will be used to + detect uncovered expressions. This information is collected after + the input port has been evaluated, so it is not affected by testing + code that is not part of the submission. To retrieve the collected + information, apply the evaluation function on the special + `get-uncovered-expressions' value below. The resulting value is a + list of uncovered expressions, with at most one per position+span + (which means that the contents may be unreliable, but the position + is). The default is `#f'. + +> get-uncovered-expressions + A special value that, when passed to an evaluator created by + `make-evaluator', will return a list of uncovered syntax objects. + +> namespace-specs + A parameter that holds a list of values that specify how to create a + namespace for evaluation in `make-evaluator'. The first item in the + list is a thunk that creates the namespace, and the rest are require + specs for modules that are to be attached to the created namespace. + The default is `make-namespace' and `(lib "posn.ss" "lang")' if + running in MzScheme, or `make-namespace-with-mred' and + `(lib "cache-image-snip.ss" "mrlib")' as well as the posn library. + (The module specs are needed for sharing module instantiations, for + example, without the above, posn values in testing code will be a + different type from posn values in tested code.) + +> sandbox-reader + A parameter that holds a function that reads all expressions from + the current-input-port. It is used to read the submission source. + It must return a list of syntax objects, and it must use the symbol + `program' as the input source (that is, something like a loop that + consumes the input using `(read-syntax 'program)'). The default + reader is using a plain `read-syntax' -- it does so while setting + `read-case-sensitive' to `#t', and `read-decimal-as-inexact' to `#f' + (both are sensible choices for testing code). + +> sandbox-security-guard + A parameter that holds a security guard that is used by all + evaluations that happen in a `make-evaluator' function. The default + value is a security guard that forbids writing, deleting, execution, + acessing any paths outside of the collection paths, or any kind of + network activity. + +> (make-evaluator language teachpack-paths input-program) + This is the main entry point for the sandbox module. + + This function Creates an evaluator function for evaluating + expressions in the designated `language', after loading teachpacks + that are specified in `teachpack-paths', and after evaluating the + code in the `input-program'. + + The `input-program' holds the input program in one of the following + ways: + * an input port that produces the content of the definitions window; + * a string or a byte string that contains the definitions window + (you can use "" for an empty definitions window); + * a path that names a file holding the input program. + The contents of the input program is read using the + `sandbox-reader', with line-counting enabled. The `language' can be: * a symbol indicating a built-in language (currently, only @@ -593,14 +667,34 @@ The _utils.ss_ module provides utilities helpful in implementing * a list that begins with a 'begin symbol is arbitrary code that is prefixed into the submitted program. - The actual evaluation of expressions happens in a newly created - eventspace and namespace, and under the supervision of a strict - security guard that reading files only from PLT collections, and no - other operations. + The actual evaluation of expressions (both the program and later + evaluations) happens under the `sandbox-security-guard' + restrictions, and if MrEd is used -- in a newly created eventspace. + See also `with-limits' below for adding resource limits, and + `get-uncovered-expressions' above for enforcing test coverage. - Additional arguments to the evaluator function are special messages - that retrieve additional information. Currently, only - 'uncovered-expressions is used (see below). +> (with-limits sec mb thunk) + This function executes the given thunk with memory and time + restrictions: if execution consumes more than `mb' megabytes or more + that `sec' seconds, then the computation is aborted and an error is + thrown. Otherwise the result of the thunk is returned (a value, + multiple values, or raise an exception). Each of the two limits can + be `#f' to disable it. + + (Note: memory limit requires running in a 3m executable; the limit + is only checked after a GC happens.) + + +_utils.ss_ +---------- + +> (get-conf key) + Returns a value from the configuration file (useful for reading + things like field names etc) + +> (unpack-submission bytes) + Returns two text% objects corresponding to the submitted definitions + and interactions windows. > (make-evaluator/submission language teachpack-paths bytes) Like `make-evaluator', but the definitions content is supplied as a @@ -626,17 +720,6 @@ The _utils.ss_ module provides utilities helpful in implementing > (evaluate-submission bytes eval) Like `load' on a submission byte string. -> coverage-enabled - Parameter that controls whether coverage testing is enabled. If it - set to true, the handin-server/private/coverage collection will be - used to detect uncovered expressions. This information is collected - before additional checker-evaluations. To retrieve the collected - information, apply the evaluation function with a second argument of - 'uncovered-expressions (the first argument will be ignored). The - resulting value is a list of uncovered expressions, with at most one - per position+span (so the contents is unreliable, but the position - is). - > (check-proc eval expect-v compare-proc proc-name arg ...) Calls the function named `proc-name' using the evaluator `eval', giving it the (unquoted) arguments `arg'... Let `result-v' be the @@ -717,15 +800,15 @@ The _utils.ss_ module provides utilities helpful in implementing specified), the timer will be reset to the 'session-timeout value.) -Extra Checker Utilities -============================================ +_checker.ss_ +------------ -The _extra-utils.ss_ module provides a higher-level of utilities, -helpful in implementing `checker' functions that are intended for a -more automated system. This module is a language module -- a typical +The "checker.ss" module provides a higher-level of utilities, helpful +in implementing `checker' functions that are intended for a more +automated system. This module is a language module -- a typical checker that uses it looks like this: - (module checker (lib "extra-utils.ss" "handin-server") + (module checker (lib "checker.ss" "handin-server") (check: :language 'intermediate :users pairs-or-singles-with-warning :coverage? #t @@ -998,11 +1081,13 @@ value from the submission code. you can call it earlier (eg, before testing) to show clients a coverage error first. -*** Multiple-file submissions + +Multiple-File Submissions: +========================== By default, the system is set up for submissions of single a single file, straight fom DrScheme using the handin-client. There is some -support for multi-file submissions in "extra-utils.ss" and in the +support for multi-file submissions in "checker.ss" and in the handin-client -- it is possible to submit multiple files, and have the system generate a single file that is the concatenation of all submission files (used only with text files). To set up multi-file @@ -1050,7 +1135,9 @@ submission utility -- the resulting executable can be used outside of DrScheme (but PLT Scheme is still required, so it cannot be uninstalled). -*** Auto-updater + +Auto-Updater: +============= The handin-client has code that can be used for automatic updating of clients. This can be useful for courses where you distribute some diff --git a/collects/handin-server/handin-server.ss b/collects/handin-server/handin-server.ss index 5b9fbfc625..3e32bc3b1b 100644 --- a/collects/handin-server/handin-server.ss +++ b/collects/handin-server/handin-server.ss @@ -550,13 +550,13 @@ (custodian-limit-memory session-cust (get-conf 'session-memory-limit) session-cust))) - (let* ([watcher - (parameterize ([current-custodian orig-custodian]) - (thread - (lambda () - (let ([session-thread (channel-get session-channel)]) - (let loop ([timed-out? #f]) - (cond + (let ([watcher + (parameterize ([current-custodian orig-custodian]) + (thread + (lambda () + (let ([session-thread (channel-get session-channel)]) + (let loop ([timed-out? #f]) + (cond [(sync/timeout 3 session-thread) (let* ([status (unbox status-box)] [status (if status @@ -574,9 +574,8 @@ [(let ([t timeout]) ; grab value to avoid races (and t ((current-inexact-milliseconds) . > . t))) ;; Shutdown here to get the handin-terminated error - ;; message, instead of relying on - ;; (get-conf 'session-timeout) - ;; at the run-server level + ;; message, instead of relying on a timeout at the + ;; run-server level (custodian-shutdown-all session-cust) (loop #t)] [else diff --git a/collects/handin-server/sandbox.ss b/collects/handin-server/sandbox.ss new file mode 100644 index 0000000000..901a829a17 --- /dev/null +++ b/collects/handin-server/sandbox.ss @@ -0,0 +1,222 @@ +(module sandbox mzscheme + (require (lib "string.ss") (lib "list.ss")) + + (provide mred? + coverage-enabled + namespace-specs + sandbox-reader + sandbox-security-guard + get-uncovered-expressions + make-evaluator) + + (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)])) + + ;; Configuration ------------------------------------------------------------ + + (define coverage-enabled (make-parameter #f)) + + (define namespace-specs + (make-parameter + (let ([mods '((lib "posn.ss" "lang"))] + [mred-mods '((lib "cache-image-snip.ss" "mrlib"))]) + `(,(mz/mr make-namespace make-namespace-with-mred) + ,@mods ,@(if mred? mred-mods '()))))) + + (define (default-sandbox-reader) + (parameterize ([read-case-sensitive #t] [read-decimal-as-inexact #f]) + (let loop ([l '()]) + (let ([expr (read-syntax 'program)]) + (if (eof-object? expr) + (reverse! l) + (loop (cons expr l))))))) + + (define sandbox-reader (make-parameter default-sandbox-reader)) + + (define ok-path-re + (byte-regexp + (bytes-append + #"^(?:" + (apply bytes-append + (cdr (apply append + (map (lambda (p) + (list #"|" (regexp-quote (path->bytes p)))) + (current-library-collection-paths))))) + #")(?:/|$)"))) + + (define sandbox-security-guard + (make-parameter + (make-security-guard + (current-security-guard) + (lambda (what path modes) + (when (or (memq 'write modes) + (memq 'execute modes) + (memq 'delete modes) + (and path + (not (regexp-match? ok-path-re (path->bytes path))))) + (error what "file access denied (~a)" path))) + (lambda (what host port mode) (error what "network access denied"))))) + + (define null-input (open-input-string "")) + (define (safe-eval expr) + (parameterize ([current-security-guard (sandbox-security-guard)] + [current-input-port null-input] + ;; breaks: [current-code-inspector (make-inspector)] + ) + (eval expr))) + + ;; Execution ---------------------------------------------------------------- + + (define (make-evaluation-namespace) + (let* ([specs (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 (read-code inp) + (parameterize ([current-input-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 (error 'read-code "bad input: ~e" inp)])]) + (port-count-lines! current-input-port) + ((sandbox-reader)))) + + (define (evaluate-program language teachpacks input-program uncovered!) + (let* ([body (read-code input-program)] + [body (append (if (and (pair? teachpacks) + (eq? 'begin (car teachpacks))) + (cdr teachpacks) + (map (lambda (tp) + `(,#'require ,(if (pair? tp) tp `(file ,tp)))) + teachpacks)) + body)] + [body (cond [(and (symbol? language) + (memq language '(beginner + beginner-abbr + intermediate + intermediate-lambda + advanced))) + `(module m + (lib ,(case language + [(beginner) "htdp-beginner.ss"] + [(beginner-abbr) "htdp-beginner-abbr.ss"] + [(intermediate) "htdp-intermediate.ss"] + [(intermediate-lambda) + "htdp-intermediate-lambda.ss"] + [(advanced) "htdp-advanced.ss"]) + "lang") + ,@body)] + [(or (and (pair? language) (eq? 'lib (car language))) + (symbol? language)) + `(module m ,language ,@body)] + [(and (pair? language) + (eq? 'begin (car language))) + `(begin ,language ,@body)] + [else (error 'make-evaluator + "Bad language specification: ~e" + language)])]) + (when uncovered! + (safe-eval '(require (lib "coverage.ss" "handin-server" "private")))) + (safe-eval body) + (when (and (pair? body) (eq? 'module (car body)) + (pair? (cdr body)) (symbol? (cadr body))) + (let ([mod (cadr body)]) + (safe-eval `(require ,mod)) + (current-namespace (module->namespace mod)))) + (when uncovered! + (uncovered! (filter (lambda (x) (eq? 'program (syntax-source x))) + (safe-eval '(get-uncovered-expressions))))))) + + (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 get-uncovered-expressions "get-uncovered-expressions") + + (define (make-evaluator language teachpacks input-program) + (let ([coverage-enabled (coverage-enabled)] + [uncovered-expressions #f] + [ns (make-evaluation-namespace)] + [input-ch (make-channel)] + [result-ch (make-channel)]) + (parameterize ([current-namespace ns] + [current-inspector (make-inspector)] + ;; bogus parameter and value if we're in mzscheme + [current-eventspace (make-eventspace)]) + (run-in-bg + (lambda () + ;; First read program and evaluate it as a module: + (with-handlers ([void (lambda (exn) (channel-put result-ch exn))]) + (evaluate-program + language teachpacks input-program + (and coverage-enabled + (lambda (exprs) (set! uncovered-expressions exprs)))) + (channel-put result-ch 'ok)) + ;; Now wait for interaction expressions: + (let loop () + (let ([expr (channel-get input-ch)]) + (unless (eof-object? expr) + (with-handlers ([void (lambda (exn) + (channel-put result-ch + (cons 'exn exn)))]) + (channel-put result-ch + (cons 'vals (call-with-values + (lambda () (safe-eval expr)) + list)))) + (loop)))) + (let loop () + (channel-put result-ch '(exn . no-more-to-evaluate)) + (loop)))) + (let ([r (channel-get result-ch)]) + (if (eq? r 'ok) + ;; Initial program executed ok, so return an evaluator: + (lambda (expr) + (if (eq? expr get-uncovered-expressions) + uncovered-expressions + (begin (channel-put input-ch expr) + (let ([r (channel-get result-ch)]) + (if (eq? (car r) 'exn) + (raise (cdr r)) + (apply values (cdr r))))))) + ;; Program didn't execute: + (raise r)))))) + + ;; Resources ---------------------------------------------------------------- + + (define (with-limits sec mb thunk) + (let ([cust (make-custodian)] + [ch (make-channel)]) + (when mb (custodian-limit-memory cust (* mb 1024 1024) cust)) + (let* ([work (parameterize ([current-custodian cust]) + (thread (lambda () + (channel-put ch + (with-handlers ([void (lambda (e) + (list raise e))]) + (call-with-values thunk + (lambda vs (cons values vs))))))))] + [watch (thread (lambda () + (channel-put ch + (if (sync/timeout sec work) 'memory 'time))))] + [r (channel-get ch)]) + (custodian-shutdown-all cust) + (kill-thread watch) + (if (list? r) + (apply (car r) (cdr r)) + (error 'with-limit "out of ~a" r))))) + + ) diff --git a/collects/handin-server/utils.ss b/collects/handin-server/utils.ss index db20db7815..3f1846effc 100644 --- a/collects/handin-server/utils.ss +++ b/collects/handin-server/utils.ss @@ -1,20 +1,21 @@ (module utils mzscheme - (require (lib "class.ss") + (require (lib "list.ss") + (lib "class.ss") (lib "mred.ss" "mred") (lib "posn.ss" "lang") - "private/run-status.ss" - "private/config.ss" (prefix pc: (lib "pconvert.ss")) (lib "pretty.ss") - (lib "list.ss") - (lib "string.ss") - (only "handin-server.ss" timeout-control)) + (only "handin-server.ss" timeout-control) + "private/run-status.ss" + "private/config.ss" + "sandbox.ss") - (provide get-conf + (provide (all-from "sandbox.ss") + + get-conf unpack-submission - make-evaluator make-evaluator/submission evaluate-all evaluate-submission @@ -26,8 +27,6 @@ message current-value-printer - coverage-enabled - check-proc check-defined look-for-tests @@ -48,170 +47,15 @@ (read-editor-global-footer stream) (values definitions-text interactions-text))) - ;; Protection --------------------------------------- - - (define ok-path-re - (regexp - (string-append - "^(?:" - (apply string-append - (cdr (apply append - (map (lambda (p) - (list "|" (regexp-quote (path->string p)))) - (current-library-collection-paths))))) - ")(?:/|$)"))) - - (define tight-security - (make-security-guard - (current-security-guard) - (lambda (what path modes) - (when (or (memq 'write modes) - (memq 'execute modes) - (memq 'delete modes) - (and path (not (regexp-match ok-path-re (path->string path))))) - (error what "file access denied (~a)" path))) - (lambda (what host port mode) (error what "network access denied")))) - - (define null-input (open-input-string "")) - (define (safe-eval expr . more) - (parameterize ([current-security-guard tight-security] - [current-input-port null-input] - ;; breaks: [current-code-inspector (make-inspector)] - ) - (apply eval expr more))) - ;; Execution ---------------------------------------- - (define coverage-enabled (make-parameter #f)) - - (define modules-to-attach - (list '(lib "posn.ss" "lang") - '(lib "cache-image-snip.ss" "mrlib"))) - - (define (make-evaluation-namespace) - (let ([new-ns (make-namespace-with-mred)] - [orig-ns (current-namespace)]) - (for-each (lambda (mod) (dynamic-require mod #f)) - modules-to-attach) - (let ([modsyms - (map (lambda (mod) ((current-module-name-resolver) mod #f #f)) - modules-to-attach)]) - (parameterize ((current-namespace new-ns)) - (for-each (lambda (ms) (namespace-attach-module orig-ns ms)) - modsyms))) - new-ns)) - - (define (make-evaluator language teachpacks program-port) - (let ([coverage-enabled (coverage-enabled)] - [uncovered-expressions #f] - [ns (make-evaluation-namespace)] - [orig-ns (current-namespace)]) - (parameterize ([current-namespace ns] - [read-case-sensitive #t] - [read-decimal-as-inexact #f] - [current-inspector (make-inspector)]) - (parameterize ([current-eventspace (make-eventspace)]) - (let ([ch (make-channel)] - [result-ch (make-channel)]) - (queue-callback - (lambda () - ;; First read program and evaluate it as a module: - (with-handlers ([void (lambda (exn) (channel-put result-ch (cons 'exn exn)))]) - (let* ([body - (parameterize ([read-case-sensitive #t] - [read-decimal-as-inexact #f]) - (let loop ([l null]) - (let ([expr (read-syntax 'program program-port)]) - (if (eof-object? expr) - (reverse l) - (loop (cons expr l))))))] - [body (append (if (and (pair? teachpacks) - (eq? 'begin (car teachpacks))) - (cdr teachpacks) - (map (lambda (tp) - `(,#'require - ,(if (pair? tp) - tp `(file ,tp)))) - teachpacks)) - body)] - [body - (cond - [(and (symbol? language) - (memq language '(beginner - beginner-abbr - intermediate - intermediate-lambda - advanced))) - `(module m - (lib ,(case language - [(beginner) "htdp-beginner.ss"] - [(beginner-abbr) "htdp-beginner-abbr.ss"] - [(intermediate) "htdp-intermediate.ss"] - [(intermediate-lambda) "htdp-intermediate-lambda.ss"] - [(advanced) "htdp-advanced.ss"]) - "lang") - ,@body)] - [(or (and (pair? language) (eq? 'lib (car language))) - (symbol? language)) - `(module m ,language ,@body)] - [(and (pair? language) - (eq? 'begin (car language))) - `(begin ,language ,@body)] - [else (error 'make-evaluator - "Bad language specification: ~e" - language)])]) - (when coverage-enabled - (safe-eval '(require (lib "coverage.ss" - "handin-server" "private")))) - (safe-eval body) - (when (and (pair? body) (eq? 'module (car body)) - (pair? (cdr body)) (symbol? (cadr body))) - (let ([mod (cadr body)]) - (safe-eval `(require ,mod)) - (current-namespace (module->namespace mod)))) - (when coverage-enabled - (set! uncovered-expressions - (filter (lambda (x) (eq? 'program (syntax-source x))) - (safe-eval '(get-uncovered-expressions) - ns))))) - (channel-put result-ch 'ok)) - ;; Now wait for interaction expressions: - (let loop () - (let ([expr (channel-get ch)]) - (unless (eof-object? expr) - (with-handlers ([void (lambda (exn) - (channel-put result-ch - (cons 'exn exn)))]) - (channel-put result-ch (cons 'val (safe-eval expr)))) - (loop)))) - (let loop () - (channel-put result-ch '(exn . no-more-to-evaluate)) - (loop)))) - (let ([r (channel-get result-ch)]) - (if (eq? r 'ok) - ;; Initial program executed ok, so return an evaluator: - (lambda (expr . more) - (if (pair? more) - (case (car more) - [(uncovered-expressions) uncovered-expressions] - [else (error 'make-evaluator - "Bad arguments: ~e" - (cons expr more))]) - (begin (channel-put ch expr) - (let ([r (channel-get result-ch)]) - (if (eq? (car r) 'exn) - (raise (cdr r)) - (cdr r)))))) - ;; Program didn't execute: - (raise (cdr r))))))))) - (define (open-input-text-editor/lines str) (let ([inp (open-input-text-editor str)]) (port-count-lines! inp) inp)) (define (make-evaluator/submission language teachpacks str) (let-values ([(defs interacts) (unpack-submission str)]) - (make-evaluator language teachpacks (open-input-text-editor/lines defs)))) + (make-evaluator language teachpacks (open-input-text-editor defs)))) (define (evaluate-all source port eval) (let loop () @@ -228,10 +72,9 @@ (define (reraise-exn-as-submission-problem thunk) (with-handlers ([void (lambda (exn) - (error - (if (exn? exn) - (exn-message exn) - (format "~s" exn))))]) + (error (if (exn? exn) + (exn-message exn) + (format "exception: ~e" exn))))]) (thunk))) ;; ---------------------------------------- @@ -337,6 +180,6 @@ (define (call-with-evaluator/submission lang teachpacks str go) (let-values ([(defs interacts) (unpack-submission str)]) - (call-with-evaluator lang teachpacks (open-input-text-editor/lines defs) go))) + (call-with-evaluator lang teachpacks (open-input-text-editor defs) go))) )