Skip to content

Commit 1829fa5

Browse files
committed
Hack for "unsafety hygiene" -- push_unsafe! and pop_unsafe!.
Even after expansion, the generated expressions still track depth of such pushes (i.e. how often you have "pushed" without a corresponding "pop"), and we add a rule that in a context with a positive `push_unsafe!` depth, it is effectively an `unsafe` block context. (This way, we can inject code that uses `unsafe` features, but still contains within it a sub-expression that should inherit the outer safety checking setting, outside of the injected code.) This is a total hack; it not only needs a feature-gate, but probably should be feature-gated forever (if possible). ignore-pretty in test/run-pass/pushpop-unsafe-okay.rs
1 parent 25281b1 commit 1829fa5

File tree

12 files changed

+301
-16
lines changed

12 files changed

+301
-16
lines changed

src/librustc/middle/effect.rs

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
//! Enforces the Rust effect system. Currently there is just one effect,
1212
//! `unsafe`.
13-
use self::UnsafeContext::*;
13+
use self::RootUnsafeContext::*;
1414

1515
use middle::def;
1616
use middle::ty::{self, Ty};
@@ -21,8 +21,20 @@ use syntax::codemap::Span;
2121
use syntax::visit;
2222
use syntax::visit::Visitor;
2323

24+
#[derive(Copy, Clone)]
25+
struct UnsafeContext {
26+
push_unsafe_count: usize,
27+
root: RootUnsafeContext,
28+
}
29+
30+
impl UnsafeContext {
31+
fn new(root: RootUnsafeContext) -> UnsafeContext {
32+
UnsafeContext { root: root, push_unsafe_count: 0 }
33+
}
34+
}
35+
2436
#[derive(Copy, Clone, PartialEq)]
25-
enum UnsafeContext {
37+
enum RootUnsafeContext {
2638
SafeContext,
2739
UnsafeFn,
2840
UnsafeBlock(ast::NodeId),
@@ -44,7 +56,8 @@ struct EffectCheckVisitor<'a, 'tcx: 'a> {
4456

4557
impl<'a, 'tcx> EffectCheckVisitor<'a, 'tcx> {
4658
fn require_unsafe(&mut self, span: Span, description: &str) {
47-
match self.unsafe_context {
59+
if self.unsafe_context.push_unsafe_count > 0 { return; }
60+
match self.unsafe_context.root {
4861
SafeContext => {
4962
// Report an error.
5063
span_err!(self.tcx.sess, span, E0133,
@@ -75,9 +88,9 @@ impl<'a, 'tcx, 'v> Visitor<'v> for EffectCheckVisitor<'a, 'tcx> {
7588

7689
let old_unsafe_context = self.unsafe_context;
7790
if is_unsafe_fn {
78-
self.unsafe_context = UnsafeFn
91+
self.unsafe_context = UnsafeContext::new(UnsafeFn)
7992
} else if is_item_fn {
80-
self.unsafe_context = SafeContext
93+
self.unsafe_context = UnsafeContext::new(SafeContext)
8194
}
8295

8396
visit::walk_fn(self, fn_kind, fn_decl, block, span);
@@ -105,10 +118,18 @@ impl<'a, 'tcx, 'v> Visitor<'v> for EffectCheckVisitor<'a, 'tcx> {
105118
// external blocks (e.g. `unsafe { println("") }`,
106119
// expands to `unsafe { ... unsafe { ... } }` where
107120
// the inner one is compiler generated).
108-
if self.unsafe_context == SafeContext || source == ast::CompilerGenerated {
109-
self.unsafe_context = UnsafeBlock(block.id)
121+
if self.unsafe_context.root == SafeContext || source == ast::CompilerGenerated {
122+
self.unsafe_context.root = UnsafeBlock(block.id)
110123
}
111124
}
125+
ast::PushUnsafeBlock(..) => {
126+
self.unsafe_context.push_unsafe_count =
127+
self.unsafe_context.push_unsafe_count.saturating_add(1);
128+
}
129+
ast::PopUnsafeBlock(..) => {
130+
self.unsafe_context.push_unsafe_count =
131+
self.unsafe_context.push_unsafe_count.saturating_sub(1);
132+
}
112133
}
113134

114135
visit::walk_block(self, block);
@@ -162,7 +183,7 @@ impl<'a, 'tcx, 'v> Visitor<'v> for EffectCheckVisitor<'a, 'tcx> {
162183
pub fn check_crate(tcx: &ty::ctxt) {
163184
let mut visitor = EffectCheckVisitor {
164185
tcx: tcx,
165-
unsafe_context: SafeContext,
186+
unsafe_context: UnsafeContext::new(SafeContext),
166187
};
167188

168189
visit::walk_crate(&mut visitor, tcx.map.krate());

src/librustc_typeck/check/mod.rs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -231,12 +231,13 @@ impl<'tcx> Expectation<'tcx> {
231231
pub struct UnsafetyState {
232232
pub def: ast::NodeId,
233233
pub unsafety: ast::Unsafety,
234+
pub unsafe_push_count: u32,
234235
from_fn: bool
235236
}
236237

237238
impl UnsafetyState {
238239
pub fn function(unsafety: ast::Unsafety, def: ast::NodeId) -> UnsafetyState {
239-
UnsafetyState { def: def, unsafety: unsafety, from_fn: true }
240+
UnsafetyState { def: def, unsafety: unsafety, unsafe_push_count: 0, from_fn: true }
240241
}
241242

242243
pub fn recurse(&mut self, blk: &ast::Block) -> UnsafetyState {
@@ -248,13 +249,20 @@ impl UnsafetyState {
248249
ast::Unsafety::Unsafe if self.from_fn => *self,
249250

250251
unsafety => {
251-
let (unsafety, def) = match blk.rules {
252-
ast::UnsafeBlock(..) => (ast::Unsafety::Unsafe, blk.id),
253-
ast::DefaultBlock => (unsafety, self.def),
252+
let (unsafety, def, count) = match blk.rules {
253+
ast::PushUnsafeBlock(..) =>
254+
(unsafety, blk.id, self.unsafe_push_count.saturating_add(1)),
255+
ast::PopUnsafeBlock(..) =>
256+
(unsafety, blk.id, self.unsafe_push_count.saturating_sub(1)),
257+
ast::UnsafeBlock(..) =>
258+
(ast::Unsafety::Unsafe, blk.id, self.unsafe_push_count),
259+
ast::DefaultBlock =>
260+
(unsafety, self.def, self.unsafe_push_count),
254261
};
255262
UnsafetyState{ def: def,
256-
unsafety: unsafety,
257-
from_fn: false }
263+
unsafety: unsafety,
264+
unsafe_push_count: count,
265+
from_fn: false }
258266
}
259267
}
260268
}

src/libsyntax/ast.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -810,6 +810,8 @@ pub type SpannedIdent = Spanned<Ident>;
810810
pub enum BlockCheckMode {
811811
DefaultBlock,
812812
UnsafeBlock(UnsafeSource),
813+
PushUnsafeBlock(UnsafeSource),
814+
PopUnsafeBlock(UnsafeSource),
813815
}
814816

815817
#[derive(Clone, PartialEq, Eq, RustcEncodable, RustcDecodable, Hash, Debug, Copy)]

src/libsyntax/ext/base.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -591,6 +591,12 @@ fn initial_syntax_expander_table<'feat>(ecfg: &expand::ExpansionConfig<'feat>)
591591
syntax_expanders.insert(intern("cfg"),
592592
builtin_normal_expander(
593593
ext::cfg::expand_cfg));
594+
syntax_expanders.insert(intern("push_unsafe"),
595+
builtin_normal_expander(
596+
ext::pushpop_safe::expand_push_unsafe));
597+
syntax_expanders.insert(intern("pop_unsafe"),
598+
builtin_normal_expander(
599+
ext::pushpop_safe::expand_pop_unsafe));
594600
syntax_expanders.insert(intern("trace_macros"),
595601
builtin_normal_expander(
596602
ext::trace_macros::expand_trace_macros));

src/libsyntax/ext/expand.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1504,6 +1504,7 @@ impl<'feat> ExpansionConfig<'feat> {
15041504
fn enable_trace_macros = allow_trace_macros,
15051505
fn enable_allow_internal_unstable = allow_internal_unstable,
15061506
fn enable_custom_derive = allow_custom_derive,
1507+
fn enable_pushpop_unsafe = allow_pushpop_unsafe,
15071508
}
15081509
}
15091510

src/libsyntax/ext/pushpop_safe.rs

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
/*
12+
* The compiler code necessary to support the `push_unsafe!` and
13+
* `pop_unsafe!` macros.
14+
*
15+
* This is a hack to allow a kind of "safety hygiene", where a macro
16+
* can generate code with an interior expression that inherits the
17+
* safety of some outer context.
18+
*
19+
* For example, in:
20+
*
21+
* ```rust
22+
* fn foo() { push_unsafe!( { EXPR_1; pop_unsafe!( EXPR_2 ) } ) }
23+
* ```
24+
*
25+
* the `EXPR_1` is considered to be in an `unsafe` context,
26+
* but `EXPR_2` is considered to be in a "safe" (i.e. checked) context.
27+
*
28+
* For comparison, in:
29+
*
30+
* ```rust
31+
* fn foo() { unsafe { push_unsafe!( { EXPR_1; pop_unsafe!( EXPR_2 ) } ) } }
32+
* ```
33+
*
34+
* both `EXPR_1` and `EXPR_2` are considered to be in `unsafe`
35+
* contexts.
36+
*
37+
*/
38+
39+
use ast;
40+
use codemap::Span;
41+
use ext::base::*;
42+
use ext::base;
43+
use ext::build::AstBuilder;
44+
use feature_gate;
45+
use ptr::P;
46+
47+
enum PushPop { Push, Pop }
48+
49+
pub fn expand_push_unsafe<'cx>(cx: &'cx mut ExtCtxt, sp: Span, tts: &[ast::TokenTree])
50+
-> Box<base::MacResult+'cx> {
51+
feature_gate::check_for_pushpop_syntax(
52+
cx.ecfg.features, &cx.parse_sess.span_diagnostic, sp);
53+
expand_pushpop_unsafe(cx, sp, tts, PushPop::Push)
54+
}
55+
56+
pub fn expand_pop_unsafe<'cx>(cx: &'cx mut ExtCtxt, sp: Span, tts: &[ast::TokenTree])
57+
-> Box<base::MacResult+'cx> {
58+
feature_gate::check_for_pushpop_syntax(
59+
cx.ecfg.features, &cx.parse_sess.span_diagnostic, sp);
60+
expand_pushpop_unsafe(cx, sp, tts, PushPop::Pop)
61+
}
62+
63+
fn expand_pushpop_unsafe<'cx>(cx: &'cx mut ExtCtxt, sp: Span, tts: &[ast::TokenTree],
64+
pp: PushPop) -> Box<base::MacResult+'cx> {
65+
let mut exprs = match get_exprs_from_tts(cx, sp, tts) {
66+
Some(exprs) => exprs.into_iter(),
67+
None => return DummyResult::expr(sp),
68+
};
69+
let expr = match (exprs.next(), exprs.next()) {
70+
(Some(expr), None) => expr,
71+
_ => {
72+
let msg = match pp {
73+
PushPop::Push => "push_unsafe! takes 1 arguments",
74+
PushPop::Pop => "pop_unsafe! takes 1 arguments",
75+
};
76+
cx.span_err(sp, msg);
77+
return DummyResult::expr(sp);
78+
}
79+
};
80+
81+
let source = ast::UnsafeSource::CompilerGenerated;
82+
let check_mode = match pp {
83+
PushPop::Push => ast::BlockCheckMode::PushUnsafeBlock(source),
84+
PushPop::Pop => ast::BlockCheckMode::PopUnsafeBlock(source),
85+
};
86+
87+
MacEager::expr(cx.expr_block(P(ast::Block {
88+
stmts: vec![],
89+
expr: Some(expr),
90+
id: ast::DUMMY_NODE_ID,
91+
rules: check_mode,
92+
span: sp
93+
})))
94+
}

src/libsyntax/feature_gate.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ const KNOWN_FEATURES: &'static [(&'static str, &'static str, Status)] = &[
8080
("visible_private_types", "1.0.0", Active),
8181
("slicing_syntax", "1.0.0", Accepted),
8282
("box_syntax", "1.0.0", Active),
83+
("pushpop_unsafe", "1.2.0", Active),
8384
("on_unimplemented", "1.0.0", Active),
8485
("simd_ffi", "1.0.0", Active),
8586
("allocator", "1.0.0", Active),
@@ -325,6 +326,7 @@ pub struct Features {
325326
pub allow_trace_macros: bool,
326327
pub allow_internal_unstable: bool,
327328
pub allow_custom_derive: bool,
329+
pub allow_pushpop_unsafe: bool,
328330
pub simd_ffi: bool,
329331
pub unmarked_api: bool,
330332
pub negate_unsigned: bool,
@@ -348,6 +350,7 @@ impl Features {
348350
allow_trace_macros: false,
349351
allow_internal_unstable: false,
350352
allow_custom_derive: false,
353+
allow_pushpop_unsafe: false,
351354
simd_ffi: false,
352355
unmarked_api: false,
353356
negate_unsigned: false,
@@ -358,6 +361,13 @@ impl Features {
358361
}
359362
}
360363

364+
pub fn check_for_pushpop_syntax(f: Option<&Features>, diag: &SpanHandler, span: Span) {
365+
if let Some(&Features { allow_pushpop_unsafe: true, .. }) = f {
366+
return;
367+
}
368+
emit_feature_err(diag, "pushpop_unsafe", span, EXPLAIN_PUSHPOP_UNSAFE);
369+
}
370+
361371
struct Context<'a> {
362372
features: Vec<&'static str>,
363373
span_handler: &'a SpanHandler,
@@ -787,6 +797,7 @@ fn check_crate_inner<F>(cm: &CodeMap, span_handler: &SpanHandler,
787797
allow_trace_macros: cx.has_feature("trace_macros"),
788798
allow_internal_unstable: cx.has_feature("allow_internal_unstable"),
789799
allow_custom_derive: cx.has_feature("custom_derive"),
800+
allow_pushpop_unsafe: cx.has_feature("pushpop_unsafe"),
790801
simd_ffi: cx.has_feature("simd_ffi"),
791802
unmarked_api: cx.has_feature("unmarked_api"),
792803
negate_unsigned: cx.has_feature("negate_unsigned"),

src/libsyntax/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ pub mod ext {
120120
pub mod log_syntax;
121121
pub mod mtwt;
122122
pub mod quote;
123+
pub mod pushpop_safe;
123124
pub mod source_util;
124125
pub mod trace_macros;
125126

src/libsyntax/print/pprust.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1434,8 +1434,8 @@ impl<'a> State<'a> {
14341434
attrs: &[ast::Attribute],
14351435
close_box: bool) -> io::Result<()> {
14361436
match blk.rules {
1437-
ast::UnsafeBlock(..) => try!(self.word_space("unsafe")),
1438-
ast::DefaultBlock => ()
1437+
ast::UnsafeBlock(..) | ast::PushUnsafeBlock(..) => try!(self.word_space("unsafe")),
1438+
ast::DefaultBlock | ast::PopUnsafeBlock(..) => ()
14391439
}
14401440
try!(self.maybe_print_comment(blk.span.lo));
14411441
try!(self.ann.pre(self, NodeBlock(blk)));
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
fn main() {
12+
let c = push_unsafe!('c'); //~ ERROR push/pop_unsafe macros are experimental
13+
let c = pop_unsafe!('c'); //~ ERROR push/pop_unsafe macros are experimental
14+
}

0 commit comments

Comments
 (0)