Skip to content

Commit 4bd5f83

Browse files
committed
First take on an alias-safety checker
The alias checker works by ensuring that any value to which an alias is created is rooted in some way that ensures it outlives the alias. It is now disallowed to create an alias to the content of a mutable box, or to a box hanging off a mutable field. There is also machinery in place to prevent assignment to local variables whenever they are the root of a live alias.
1 parent 2b334f0 commit 4bd5f83

File tree

5 files changed

+285
-0
lines changed

5 files changed

+285
-0
lines changed

src/comp/driver/rustc.rs

+3
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,9 @@ fn compile_input(session::session sess,
109109
bind middle::tstate::ck::check_crate(ty_cx, crate));
110110
}
111111

112+
time(time_passes, "alias checking",
113+
bind middle::alias::check_crate(@ty_cx, def_map, crate));
114+
112115
auto llmod =
113116
time[llvm::llvm::ModuleRef](time_passes, "translation",
114117
bind trans::trans_crate(sess, crate,

src/comp/middle/alias.rs

+255
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
import front::ast;
2+
import front::ast::ident;
3+
import front::ast::def_id;
4+
import std::vec;
5+
import std::str;
6+
import std::option;
7+
import std::option::some;
8+
import std::option::none;
9+
10+
tag deref_t {
11+
field(ident);
12+
index;
13+
unbox;
14+
}
15+
type deref = rec(bool mut, deref_t t);
16+
17+
type ctx = @rec(@ty::ctxt tcx,
18+
resolve::def_map dm,
19+
// The current blacklisted (non-assignable) locals
20+
mutable vec[vec[def_id]] bl,
21+
// A stack of blacklists for outer function scopes
22+
mutable vec[vec[vec[def_id]]] blstack);
23+
24+
fn check_crate(@ty::ctxt tcx, resolve::def_map dm, &@ast::crate crate) {
25+
auto cx = @rec(tcx = tcx,
26+
dm = dm,
27+
mutable bl = vec::empty[vec[def_id]](),
28+
mutable blstack = vec::empty[vec[vec[def_id]]]());
29+
auto v = rec(visit_item_pre = bind enter_item(cx, _),
30+
visit_item_post = bind leave_item(cx, _),
31+
visit_method_pre = bind enter_method(cx, _),
32+
visit_method_post = bind leave_method(cx, _),
33+
visit_expr_pre = bind check_expr(cx, _),
34+
visit_expr_post = bind leave_expr(cx, _)
35+
with walk::default_visitor());
36+
walk::walk_crate(v, *crate);
37+
}
38+
39+
fn enter_item(ctx cx, &@ast::item it) {
40+
alt (it.node) {
41+
case (ast::item_fn(_, _, _, _, _)) {
42+
vec::push(cx.blstack, cx.bl);
43+
cx.bl = [];
44+
}
45+
case (_) {}
46+
}
47+
}
48+
fn leave_item(ctx cx, &@ast::item it) {
49+
alt (it.node) {
50+
case (ast::item_fn(_, _, _, _, _)) {
51+
cx.bl = vec::pop(cx.blstack);
52+
}
53+
case (_) {}
54+
}
55+
}
56+
57+
fn enter_method(ctx cx, &@ast::method mt) {
58+
vec::push(cx.blstack, cx.bl);
59+
cx.bl = [];
60+
}
61+
fn leave_method(ctx cx, &@ast::method mt) {
62+
cx.bl = vec::pop(cx.blstack);
63+
}
64+
65+
fn check_expr(ctx cx, &@ast::expr ex) {
66+
alt (ex.node) {
67+
case (ast::expr_call(?f, ?args, _)) {
68+
auto fty = ty::expr_ty(*cx.tcx, f);
69+
auto argtys = alt (ty::struct(*cx.tcx, fty)) {
70+
case (ty::ty_fn(_, ?args, _, _)) { args }
71+
case (ty::ty_native_fn(_, ?args, _)) { args }
72+
};
73+
auto i = 0u;
74+
let vec[def_id] listed = [];
75+
for (ty::arg argty in argtys) {
76+
// FIXME Treat mo_either specially here?
77+
if (argty.mode != ty::mo_val) {
78+
alt (check_rooted(cx, args.(i), false)) {
79+
case (some(?did)) {
80+
vec::push(listed, did);
81+
}
82+
case (_) {}
83+
}
84+
}
85+
i += 1u;
86+
}
87+
// FIXME when mutable aliases can be distinguished, go over the
88+
// args again and ensure that we're not passing a blacklisted
89+
// variable by mutable alias (using 'listed' and the context
90+
// blacklist).
91+
}
92+
case (ast::expr_put(?val, _)) {
93+
alt (val) {
94+
case (some(?ex)) { check_rooted(cx, ex, false); }
95+
case (_) {}
96+
}
97+
}
98+
case (ast::expr_alt(?input, _, _)) {
99+
vec::push(cx.bl, alt (check_rooted(cx, input, true)) {
100+
case (some(?did)) { [did] }
101+
case (_) { vec::empty[def_id]() }
102+
});
103+
}
104+
105+
case (ast::expr_move(?dest, _, _)) { check_assign(cx, dest); }
106+
case (ast::expr_assign(?dest, _, _)) { check_assign(cx, dest); }
107+
case (ast::expr_assign_op(_, ?dest, _, _)) { check_assign(cx, dest); }
108+
case (_) {}
109+
}
110+
}
111+
112+
fn leave_expr(ctx cx, &@ast::expr ex) {
113+
alt (ex.node) {
114+
case (ast::expr_alt(_, _, _)) { vec::pop(cx.bl); }
115+
case (_) {}
116+
}
117+
}
118+
119+
fn check_assign(&ctx cx, &@ast::expr ex) {
120+
alt (ex.node) {
121+
case (ast::expr_path(?pt, ?ann)) {
122+
auto did = ast::def_id_of_def(cx.dm.get(ann.id));
123+
for (vec[def_id] layer in cx.bl) {
124+
for (def_id black in layer) {
125+
if (did == black) {
126+
cx.tcx.sess.span_err
127+
(ex.span, str::connect(pt.node.idents, "::") +
128+
" is being aliased and may not be assigned to");
129+
}
130+
}
131+
}
132+
}
133+
case (_) {}
134+
}
135+
}
136+
137+
fn check_rooted(&ctx cx, &@ast::expr ex, bool autoderef)
138+
-> option::t[def_id] {
139+
auto root = expr_root(cx, ex, autoderef);
140+
if (has_unsafe_box(root.ds)) {
141+
cx.tcx.sess.span_err
142+
(ex.span, "can not create alias to improperly anchored value");
143+
}
144+
alt (root.ex.node) {
145+
case (ast::expr_path(_, ?ann)) {
146+
ret some(ast::def_id_of_def(cx.dm.get(ann.id)));
147+
}
148+
case (_) {
149+
ret none[def_id];
150+
}
151+
}
152+
}
153+
154+
fn expr_root(&ctx cx, @ast::expr ex, bool autoderef)
155+
-> rec(@ast::expr ex, vec[deref] ds) {
156+
let vec[deref] ds = [];
157+
if (autoderef) {
158+
auto auto_unbox = maybe_auto_unbox(cx, ex);
159+
if (auto_unbox.done) {
160+
vec::push(ds, rec(mut=auto_unbox.mut, t=unbox));
161+
}
162+
}
163+
while (true) {
164+
alt ({ex.node}) {
165+
case (ast::expr_field(?base, ?ident, _)) {
166+
auto auto_unbox = maybe_auto_unbox(cx, base);
167+
alt (auto_unbox.t) {
168+
case (ty::ty_tup(?fields)) {
169+
auto fnm = ty::field_num(cx.tcx.sess, ex.span, ident);
170+
auto mt = fields.(fnm).mut != ast::imm;
171+
vec::push(ds, rec(mut=mt, t=field(ident)));
172+
}
173+
case (ty::ty_rec(?fields)) {
174+
for (ty::field fld in fields) {
175+
if (str::eq(ident, fld.ident)) {
176+
auto mt = fld.mt.mut != ast::imm;
177+
vec::push(ds, rec(mut=mt, t=field(ident)));
178+
break;
179+
}
180+
}
181+
}
182+
case (ty::ty_obj(_)) {
183+
vec::push(ds, rec(mut=false, t=field(ident)));
184+
}
185+
}
186+
if (auto_unbox.done) {
187+
vec::push(ds, rec(mut=auto_unbox.mut, t=unbox));
188+
}
189+
ex = base;
190+
}
191+
case (ast::expr_index(?base, _, _)) {
192+
auto auto_unbox = maybe_auto_unbox(cx, base);
193+
alt (auto_unbox.t) {
194+
case (ty::ty_vec(?mt)) {
195+
vec::push(ds, rec(mut=mt.mut != ast::imm, t=index));
196+
}
197+
}
198+
if (auto_unbox.done) {
199+
vec::push(ds, rec(mut=auto_unbox.mut, t=unbox));
200+
}
201+
ex = base;
202+
}
203+
case (ast::expr_unary(?op, ?base, _)) {
204+
if (op == ast::deref) {
205+
alt (ty::struct(*cx.tcx, ty::expr_ty(*cx.tcx, base))) {
206+
case (ty::ty_box(?mt)) {
207+
vec::push(ds, rec(mut=mt.mut!=ast::imm, t=unbox));
208+
}
209+
}
210+
ex = base;
211+
} else {
212+
break;
213+
}
214+
}
215+
case (_) { break; }
216+
}
217+
}
218+
vec::reverse(ds);
219+
ret rec(ex = ex, ds = ds);
220+
}
221+
222+
fn maybe_auto_unbox(&ctx cx, &@ast::expr ex)
223+
-> rec(ty::sty t, bool done, bool mut) {
224+
auto tp = ty::struct(*cx.tcx, ty::expr_ty(*cx.tcx, ex));
225+
alt (tp) {
226+
case (ty::ty_box(?mt)) {
227+
ret rec(t=ty::struct(*cx.tcx, mt.ty),
228+
done=true, mut=mt.mut != ast::imm);
229+
}
230+
case (_) { ret rec(t=tp, done=false, mut=false); }
231+
}
232+
}
233+
234+
fn has_unsafe_box(&vec[deref] ds) -> bool {
235+
auto saw_mut = false;
236+
for (deref d in ds) {
237+
if (d.mut) { saw_mut = true; }
238+
if (d.t == unbox) {
239+
// Directly aliasing the content of a mutable box is never okay,
240+
// and any box living under mutable connection may be severed from
241+
// its root and freed.
242+
if (saw_mut) { ret true; }
243+
}
244+
}
245+
ret false;
246+
}
247+
248+
// Local Variables:
249+
// mode: rust
250+
// fill-column: 78;
251+
// indent-tabs-mode: nil
252+
// c-basic-offset: 4
253+
// buffer-file-coding-system: utf-8-unix
254+
// compile-command: "make -k -C $RBUILD 2>&1 | sed -e 's/\\/x\\//x:\\//g'";
255+
// End:

src/comp/rustc.rc

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ mod middle {
1616
mod metadata;
1717
mod resolve;
1818
mod typeck;
19+
mod alias;
1920

2021
mod tstate {
2122
mod ck;

src/test/compile-fail/unsafe-alias.rs

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// error-pattern:can not create alias
2+
3+
fn foo(&int x) {
4+
log x;
5+
}
6+
7+
fn main() {
8+
auto box = @mutable 1;
9+
foo(*box);
10+
}

src/test/compile-fail/unsafe-alt.rs

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// error-pattern:x is being aliased
2+
3+
tag foo {
4+
left(int);
5+
right(bool);
6+
}
7+
8+
fn main() {
9+
auto x = left(10);
10+
alt (x) {
11+
case (left(?i)) {
12+
x = right(false);
13+
}
14+
case (_) {}
15+
}
16+
}

0 commit comments

Comments
 (0)