From 418ee07f4e2f35a993d3c00c25f9b73035d0f8a6 Mon Sep 17 00:00:00 2001 From: Matthew Flatt Date: Tue, 1 Apr 2014 17:40:34 -0600 Subject: [PATCH] module: disallow definition skipping Invoking a non-composable, empty continuation during the right-hand side of a variable definition skips the definition --- while continuing the module body. The compiler assumes, however, that variable references later in the module do not need a check that the variable is undefined. Fix that mismatch by changing `module` to double-check that defined variables are really defined before continuing the module body. (The check and associated prompt are skipped in simple cases, such as function definitions.) A better choice is probably to move the prompt to the right-hand side of a definition, both in a module and at the top level. That's a much different language, though, so we should consider the point again in some future variant of Racket. Closes PR 14427 --- .../scribblings/reference/syntax.scrbl | 8 +++-- .../racket-test/tests/racket/module.rktl | 16 +++++++++ racket/src/racket/src/module.c | 34 +++++++++++++++++++ 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/pkgs/racket-pkgs/racket-doc/scribblings/reference/syntax.scrbl b/pkgs/racket-pkgs/racket-doc/scribblings/reference/syntax.scrbl index 485b0d73f8..5545fb060b 100644 --- a/pkgs/racket-pkgs/racket-doc/scribblings/reference/syntax.scrbl +++ b/pkgs/racket-pkgs/racket-doc/scribblings/reference/syntax.scrbl @@ -245,8 +245,12 @@ its order within a given module). Then, expressions and definitions are evaluated in order as they appear within the module. Each evaluation of an expression or definition is wrapped with a continuation prompt (see @racket[call-with-continuation-prompt]) for -the default continuation and using a prompt handler that re-aborts -and propagates its argument to the next enclosing prompt. +the default @tech{prompt tag} and using a prompt handler that re-aborts +and propagates its argument to the next enclosing prompt. Each evaluation +of a definition is followed, outside of the prompt, by a check that +each of the definition's variables has a value; if the portion of the +prompt-delimited continuation that installs values is skipped, then +the @exnraise[exn:fail:contract:variable?]. Accessing a @tech{module-level variable} before it is defined signals a run-time error, just like accessing an undefined global variable. diff --git a/pkgs/racket-pkgs/racket-test/tests/racket/module.rktl b/pkgs/racket-pkgs/racket-test/tests/racket/module.rktl index b488de4f25..c35256dde4 100644 --- a/pkgs/racket-pkgs/racket-test/tests/racket/module.rktl +++ b/pkgs/racket-pkgs/racket-test/tests/racket/module.rktl @@ -1071,6 +1071,22 @@ (namespace-require 'errortrace) (set! c (compile m)))))) (write c (open-output-bytes))) + +;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Check that skipping definitions (but continuing +;; with the rest of a module body) is disallowed. + +(module disallowed-definition-avoider racket/base + + (define fail + ((call-with-continuation-prompt + (lambda () + (call/cc values))))) + + (error "no")) + +(err/rt-test (dynamic-require ''disallowed-definition-avoider #f) + exn:fail:contract:variable?) ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/racket/src/racket/src/module.c b/racket/src/racket/src/module.c index 561faa2d27..6a826e2a09 100644 --- a/racket/src/racket/src/module.c +++ b/racket/src/racket/src/module.c @@ -5870,6 +5870,40 @@ void *scheme_module_run_finish(Scheme_Env *menv, Scheme_Env *env) (void)_scheme_call_with_prompt_multi(body_one_expr, scheme_make_raw_pair(save_prefix, body)); scheme_resume_prefix(save_prefix); + + /* Double-check that the definition-installing part of the + continuation was not skipped. Otherwise, the compiler would + not be able to assume that a variable reference that is + lexically later (incuding a reference to an imported + variable) always references a defined variable. Putting the + prompt around a definition's RHS might be a better + approach, but that would change the language (so mabe next + time). */ + if (SAME_TYPE(SCHEME_TYPE(body), scheme_define_values_type)) { + int vcnt, j; + + vcnt = SCHEME_VEC_SIZE(body) - 1; + for (j = 0; j < vcnt; j++) { + Scheme_Object *var; + Scheme_Prefix *toplevels; + Scheme_Bucket *b; + + var = SCHEME_VEC_ELS(body)[j+1]; + toplevels = (Scheme_Prefix *)MZ_RUNSTACK[SCHEME_TOPLEVEL_DEPTH(var)]; + b = (Scheme_Bucket *)toplevels->a[SCHEME_TOPLEVEL_POS(var)]; + + if (!b->val) { + scheme_raise_exn(MZEXN_FAIL_CONTRACT_VARIABLE, + b->key, + "define-values: skipped variable definition;\n" + " cannot continue without defining variable\n" + " variable: %S\n" + " in module: %D", + (Scheme_Object *)b->key, + menv->module->modsrc); + } + } + } } else scheme_ignore_result(_scheme_eval_linked_expr_multi(body)); }