Skip to content

Commit 5cf5f50

Browse files
committed
Handle bang functions correctly in typestate
The logic for how the "returns" constraint was handled was always dodgy, for reasons explained in the comments I added to auxiliary::fn_info in this commit. Fixed it by adding distinct "returns" and "diverges" constraints for each function, which are both handled positively (that is: for a ! function, the "diverges" constraint must be true on every exit path; for any other function, the "returns" constraint must be true on every exit path). Closes #779
1 parent c9b16ac commit 5cf5f50

File tree

6 files changed

+117
-59
lines changed

6 files changed

+117
-59
lines changed

src/comp/middle/tstate/auxiliary.rs

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -238,21 +238,58 @@ type norm_constraint = {bit_num: uint, c: sp_constr};
238238

239239
type constr_map = @std::map::hashmap[def_id, constraint];
240240

241+
/* Contains stuff that has to be computed up front */
241242
type fn_info =
242-
/* list, accumulated during pre/postcondition
243-
computation, of all local variables that may be
244-
used*/
245-
// Doesn't seem to work without the @ --
246-
// bug?
247243
{constrs: constr_map,
248244
num_constraints: uint,
249245
cf: controlflow,
246+
/* For easy access, the fn_info stores two special constraints for each
247+
function. i_return holds if all control paths in this function terminate
248+
in either a return expression, or an appropriate tail expression.
249+
i_diverge holds if all control paths in this function terminate in a fail
250+
or diverging call.
251+
252+
It might be tempting to use a single constraint C for both properties,
253+
where C represents i_return and !C represents i_diverge. This is
254+
inadvisable, because then the sense of the bit depends on context. If we're
255+
inside a ! function, that reverses the sense of the bit: C would be
256+
i_diverge and !C would be i_return. That's awkward, because we have to
257+
pass extra context around to functions that shouldn't care.
258+
259+
Okay, suppose C represents i_return and !C represents i_diverge, regardless
260+
of context. Consider this code:
261+
262+
if (foo) { ret; } else { fail; }
263+
264+
C is true in the consequent and false in the alternative. What's T `join`
265+
F, then? ? doesn't work, because this code should definitely-return if the
266+
context is a returning function (and be definitely-rejected if the context
267+
is a ! function). F doesn't work, because then the code gets incorrectly
268+
rejected if the context is a returning function. T would work, but it
269+
doesn't make sense for T `join` F to be T (consider init constraints, for
270+
example).;
271+
272+
So we need context. And so it seems clearer to just have separate
273+
constraints.
274+
*/
275+
i_return: tsconstr,
276+
i_diverge: tsconstr,
277+
/* list, accumulated during pre/postcondition
278+
computation, of all local variables that may be
279+
used */
280+
// Doesn't seem to work without the @ -- bug
250281
used_vars: @mutable node_id[]};
251282

252283
fn tsconstr_to_def_id(t: &tsconstr) -> def_id {
253284
alt t { ninit(id, _) { local_def(id) } npred(_, id, _) { id } }
254285
}
255286

287+
fn tsconstr_to_node_id(t: &tsconstr) -> node_id {
288+
alt t { ninit(id, _) { id }
289+
npred(_, id, _) {
290+
fail "tsconstr_to_node_id called on pred constraint" } }
291+
}
292+
256293
/* mapping from node ID to typestate annotation */
257294
type node_ann_table = @mutable ts_ann[mutable ];
258295

@@ -261,7 +298,10 @@ type node_ann_table = @mutable ts_ann[mutable ];
261298
type fn_info_map = @std::map::hashmap[node_id, fn_info];
262299

263300
type fn_ctxt =
264-
{enclosing: fn_info, id: node_id, name: ident, ccx: crate_ctxt};
301+
{enclosing: fn_info,
302+
id: node_id,
303+
name: ident,
304+
ccx: crate_ctxt};
265305

266306
type crate_ctxt = {tcx: ty::ctxt, node_anns: node_ann_table, fm: fn_info_map};
267307

src/comp/middle/tstate/ck.rs

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ import std::option::none;
4040
import aux::*;
4141
import syntax::print::pprust::ty_to_str;
4242
import util::common::log_stmt_err;
43-
import bitvectors::promises;
43+
import bitvectors::*;
4444
import annotate::annotate_crate;
4545
import collect_locals::mk_f_to_fn_info;
4646
import pre_post_conditions::fn_pre_post;
@@ -141,24 +141,23 @@ fn check_states_against_conditions(fcx: &fn_ctxt, f: &_fn,
141141

142142
/* Check that the return value is initialized */
143143
let post = aux::block_poststate(fcx.ccx, f.body);
144-
let ret_c: tsconstr = ninit(fcx.id, fcx.name);
145-
if f.proto == ast::proto_fn && !promises(fcx, post, ret_c) &&
146-
!type_is_nil(fcx.ccx.tcx, ret_ty_of_fn(fcx.ccx.tcx, id)) &&
147-
f.decl.cf == return {
148-
fcx.ccx.tcx.sess.span_note(f.body.span,
144+
if f.proto == ast::proto_fn &&
145+
!promises(fcx, post, fcx.enclosing.i_return) &&
146+
!type_is_nil(fcx.ccx.tcx, ret_ty_of_fn(fcx.ccx.tcx, id)) &&
147+
f.decl.cf == return {
148+
fcx.ccx.tcx.sess.span_err(f.body.span,
149149
"In function " + fcx.name +
150150
", not all control paths \
151151
return a value");
152152
fcx.ccx.tcx.sess.span_fatal(f.decl.output.span,
153153
"see declared return type of '" +
154154
ty_to_str(*f.decl.output) + "'");
155155
} else if (f.decl.cf == noreturn) {
156-
157-
158156
// check that this really always fails
159-
// the fcx.id bit means "returns" for a returning fn,
160-
// "diverges" for a non-returning fn
161-
if !promises(fcx, post, ret_c) {
157+
// Note that it's ok for i_diverge and i_return to both be true.
158+
// In fact, i_diverge implies i_return. (But not vice versa!)
159+
160+
if !promises(fcx, post, fcx.enclosing.i_diverge) {
162161
fcx.ccx.tcx.sess.span_fatal(f.body.span,
163162
"In non-returning function " +
164163
fcx.name +

src/comp/middle/tstate/collect_locals.rs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,16 +109,28 @@ fn mk_fn_info(ccx: &crate_ctxt, f: &_fn, tp: &ty_param[], f_sp: &span,
109109
next = add_constraint(cx.tcx, sc, next, res_map);
110110
}
111111

112-
/* add a pseudo-entry for the function's return value
113-
we can safely use the function's name itself for this purpose */
112+
/* add the special i_diverge and i_return constraints
113+
(see the type definition for auxiliary::fn_info for an explanation) */
114+
115+
// use the name of the function for the "return" constraint
116+
next = add_constraint(cx.tcx, respan(f_sp, ninit(id, name)), next,
117+
res_map);
118+
// and the name of the function, with a '!' appended to it, for the
119+
// "diverges" constraint
120+
let diverges_id = ccx.tcx.sess.next_node_id();
121+
let diverges_name = name + "!";
122+
add_constraint(cx.tcx, respan(f_sp, ninit(diverges_id, diverges_name)),
123+
next, res_map);
114124

115-
add_constraint(cx.tcx, respan(f_sp, ninit(id, name)), next, res_map);
116125
let v: @mutable node_id[] = @mutable ~[];
117126
let rslt =
118127
{constrs: res_map,
119128
num_constraints:
120-
ivec::len(*cx.cs) + ivec::len(f.decl.constraints) + 1u,
129+
// add 2 to account for the i_return and i_diverge constraints
130+
ivec::len(*cx.cs) + ivec::len(f.decl.constraints) + 2u,
121131
cf: f.decl.cf,
132+
i_return: ninit(id, name),
133+
i_diverge: ninit(diverges_id, diverges_name),
122134
used_vars: v};
123135
ccx.fm.insert(id, rslt);
124136
log name + " has " + std::uint::str(num_constraints(rslt)) +

src/comp/middle/tstate/pre_post_conditions.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ fn find_pre_post_item(ccx: &crate_ctxt, i: &item) {
7979
{constrs: @new_def_hash[constraint](),
8080
num_constraints: 0u,
8181
cf: return,
82+
// just bogus
83+
i_return: ninit(0, ""),
84+
i_diverge: ninit(0, ""),
8285
used_vars: v},
8386
id: 0,
8487
name: "",
@@ -692,7 +695,8 @@ fn find_pre_post_block(fcx: &fn_ctxt, b: blk) {
692695

693696
fn find_pre_post_fn(fcx: &fn_ctxt, f: &_fn) {
694697
// hack
695-
use_var(fcx, fcx.id);
698+
use_var(fcx, tsconstr_to_node_id(fcx.enclosing.i_return));
699+
use_var(fcx, tsconstr_to_node_id(fcx.enclosing.i_diverge));
696700

697701
find_pre_post_block(fcx, f.body);
698702

src/comp/middle/tstate/states.rs

Lines changed: 36 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,13 @@
11
import syntax::print::pprust::path_to_str;
2+
import util::ppaux::ty_to_str;
23
import std::ivec;
34
import std::option;
45
import std::option::get;
56
import std::option::is_none;
67
import std::option::none;
78
import std::option::some;
89
import std::option::maybe;
9-
import tstate::ann::set_in_poststate_;
10-
import tstate::ann::pre_and_post;
11-
import tstate::ann::get_post;
12-
import tstate::ann::postcond;
13-
import tstate::ann::empty_pre_post;
14-
import tstate::ann::empty_poststate;
15-
import tstate::ann::clear_in_poststate;
16-
import tstate::ann::intersect;
17-
import tstate::ann::empty_prestate;
18-
import tstate::ann::prestate;
19-
import tstate::ann::poststate;
20-
import tstate::ann::false_postcond;
21-
import tstate::ann::ts_ann;
22-
import tstate::ann::set_prestate;
23-
import tstate::ann::set_poststate;
10+
import ann::*;
2411
import aux::*;
2512
import tritv::tritv_clone;
2613
import tritv::tritv_set;
@@ -189,9 +176,8 @@ fn find_pre_post_state_exprs(fcx: &fn_ctxt, pres: &prestate, id: node_id,
189176
/* if this is a failing call, it sets everything as initialized */
190177
alt cf {
191178
noreturn. {
192-
changed |=
193-
set_poststate_ann(fcx.ccx, id,
194-
false_postcond(num_constraints(fcx.enclosing)));
179+
let post = false_postcond(num_constraints(fcx.enclosing));
180+
changed |= set_poststate_ann(fcx.ccx, id, post);
195181
}
196182
_ { changed |= set_poststate_ann(fcx.ccx, id, rs.post); }
197183
}
@@ -403,15 +389,14 @@ fn find_pre_post_state_expr(fcx: &fn_ctxt, pres: &prestate, e: @expr) ->
403389
/* normally, everything is true if execution continues after
404390
a ret expression (since execution never continues locally
405391
after a ret expression */
392+
// FIXME should factor this out
393+
let post = false_postcond(num_constrs);
394+
// except for the "diverges" bit...
395+
kill_poststate_(fcx, fcx.enclosing.i_diverge, post);
406396

407-
set_poststate_ann(fcx.ccx, e.id, false_postcond(num_constrs));
408-
/* return from an always-failing function clears the return bit */
397+
set_poststate_ann(fcx.ccx, e.id, post);
409398

410-
alt fcx.enclosing.cf {
411-
noreturn. { kill_poststate(fcx, e.id, ninit(fcx.id, fcx.name)); }
412-
_ { }
413-
}
414-
alt maybe_ret_val {
399+
alt maybe_ret_val {
415400
none. {/* do nothing */ }
416401
some(ret_val) {
417402
changed |= find_pre_post_state_expr(fcx, pres, ret_val);
@@ -421,7 +406,10 @@ fn find_pre_post_state_expr(fcx: &fn_ctxt, pres: &prestate, e: @expr) ->
421406
}
422407
expr_be(val) {
423408
let changed = set_prestate_ann(fcx.ccx, e.id, pres);
424-
set_poststate_ann(fcx.ccx, e.id, false_postcond(num_constrs));
409+
let post = false_postcond(num_constrs);
410+
// except for the "diverges" bit...
411+
kill_poststate_(fcx, fcx.enclosing.i_diverge, post);
412+
set_poststate_ann(fcx.ccx, e.id, post);
425413
ret changed | find_pre_post_state_expr(fcx, pres, val);
426414
}
427415
expr_if(antec, conseq, maybe_alt) {
@@ -558,12 +546,20 @@ fn find_pre_post_state_expr(fcx: &fn_ctxt, pres: &prestate, e: @expr) ->
558546
ret find_pre_post_state_sub(fcx, pres, operand, e.id, none);
559547
}
560548
expr_fail(maybe_fail_val) {
549+
// FIXME Should factor out this code,
550+
// which also appears in find_pre_post_state_exprs
551+
/* if execution continues after fail, then everything is true!
552+
woo! */
553+
let post = false_postcond(num_constrs);
554+
alt fcx.enclosing.cf {
555+
noreturn. {
556+
kill_poststate_(fcx, ninit(fcx.id, fcx.name), post);
557+
}
558+
_ {}
559+
}
561560
ret set_prestate_ann(fcx.ccx, e.id, pres) |
562-
/* if execution continues after fail, then everything is true!
563-
woo! */
564-
set_poststate_ann(fcx.ccx, e.id, false_postcond(num_constrs))
565-
|
566-
alt maybe_fail_val {
561+
set_poststate_ann(fcx.ccx, e.id, post)
562+
| alt maybe_fail_val {
567563
none. { false }
568564
some(fail_val) {
569565
find_pre_post_state_expr(fcx, pres, fail_val)
@@ -710,9 +706,11 @@ fn find_pre_post_state_block(fcx: &fn_ctxt, pres0: &prestate, b: &blk) ->
710706

711707
fn find_pre_post_state_fn(fcx: &fn_ctxt, f: &_fn) -> bool {
712708

713-
let num_local_vars = num_constraints(fcx.enclosing);
714-
// make sure the return bit starts out False
715-
clear_in_prestate_ident(fcx, fcx.id, fcx.name, f.body.node.id);
709+
let num_constrs = num_constraints(fcx.enclosing);
710+
// make sure the return and diverge bits start out False
711+
kill_prestate(fcx, f.body.node.id, fcx.enclosing.i_return);
712+
kill_prestate(fcx, f.body.node.id, fcx.enclosing.i_diverge);
713+
716714
// Instantiate any constraints on the arguments so we can use them
717715
let block_pre = block_prestate(fcx.ccx, f.body);
718716
let tsc;
@@ -728,15 +726,16 @@ fn find_pre_post_state_fn(fcx: &fn_ctxt, f: &_fn) -> bool {
728726
some(tailexpr) {
729727
let tailty = expr_ty(fcx.ccx.tcx, tailexpr);
730728

731-
732729
// Since blocks and alts and ifs that don't have results
733730
// implicitly result in nil, we have to be careful to not
734731
// interpret nil-typed block results as the result of a
735732
// function with some other return type
736733
if !type_is_nil(fcx.ccx.tcx, tailty) &&
737734
!type_is_bot(fcx.ccx.tcx, tailty) {
738-
let p = false_postcond(num_local_vars);
739-
set_poststate_ann(fcx.ccx, f.body.node.id, p);
735+
let post = false_postcond(num_constrs);
736+
// except for the "diverges" bit...
737+
kill_poststate_(fcx, fcx.enclosing.i_diverge, post);
738+
set_poststate_ann(fcx.ccx, f.body.node.id, post);
740739
}
741740
}
742741
none. {/* fallthrough */ }
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// xfail-stage0
2+
// error-pattern: some control paths may return
3+
fn f() -> ! { 3 }
4+
fn main(){}

0 commit comments

Comments
 (0)