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
This commit is contained in:
Matthew Flatt 2014-04-01 17:40:34 -06:00
parent 1273d0fbb3
commit 418ee07f4e
3 changed files with 56 additions and 2 deletions

View File

@ -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.

View File

@ -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?)
;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

View File

@ -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));
}