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 are evaluated in order as they appear within the module. Each
evaluation of an expression or definition is wrapped with a evaluation of an expression or definition is wrapped with a
continuation prompt (see @racket[call-with-continuation-prompt]) for continuation prompt (see @racket[call-with-continuation-prompt]) for
the default continuation and using a prompt handler that re-aborts the default @tech{prompt tag} and using a prompt handler that re-aborts
and propagates its argument to the next enclosing prompt. 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 Accessing a @tech{module-level variable} before it is defined signals
a run-time error, just like accessing an undefined global variable. a run-time error, just like accessing an undefined global variable.

View File

@ -1071,6 +1071,22 @@
(namespace-require 'errortrace) (namespace-require 'errortrace)
(set! c (compile m)))))) (set! c (compile m))))))
(write c (open-output-bytes))) (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, (void)_scheme_call_with_prompt_multi(body_one_expr,
scheme_make_raw_pair(save_prefix, body)); scheme_make_raw_pair(save_prefix, body));
scheme_resume_prefix(save_prefix); 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 } else
scheme_ignore_result(_scheme_eval_linked_expr_multi(body)); scheme_ignore_result(_scheme_eval_linked_expr_multi(body));
} }