//! Enforces the Rust effect system. Currently there is just one effect,
//! `unsafe`.
-use self::UnsafeContext::*;
+use self::RootUnsafeContext::*;
use middle::def;
use middle::ty::{self, Ty};
use syntax::visit;
use syntax::visit::Visitor;
+#[derive(Copy, Clone)]
+struct UnsafeContext {
+ push_unsafe_count: usize,
+ root: RootUnsafeContext,
+}
+
+impl UnsafeContext {
+ fn new(root: RootUnsafeContext) -> UnsafeContext {
+ UnsafeContext { root: root, push_unsafe_count: 0 }
+ }
+}
+
#[derive(Copy, Clone, PartialEq)]
-enum UnsafeContext {
+enum RootUnsafeContext {
SafeContext,
UnsafeFn,
UnsafeBlock(ast::NodeId),
impl<'a, 'tcx> EffectCheckVisitor<'a, 'tcx> {
fn require_unsafe(&mut self, span: Span, description: &str) {
- match self.unsafe_context {
+ if self.unsafe_context.push_unsafe_count > 0 { return; }
+ match self.unsafe_context.root {
SafeContext => {
// Report an error.
span_err!(self.tcx.sess, span, E0133,
let old_unsafe_context = self.unsafe_context;
if is_unsafe_fn {
- self.unsafe_context = UnsafeFn
+ self.unsafe_context = UnsafeContext::new(UnsafeFn)
} else if is_item_fn {
- self.unsafe_context = SafeContext
+ self.unsafe_context = UnsafeContext::new(SafeContext)
}
visit::walk_fn(self, fn_kind, fn_decl, block, span);
// external blocks (e.g. `unsafe { println("") }`,
// expands to `unsafe { ... unsafe { ... } }` where
// the inner one is compiler generated).
- if self.unsafe_context == SafeContext || source == ast::CompilerGenerated {
- self.unsafe_context = UnsafeBlock(block.id)
+ if self.unsafe_context.root == SafeContext || source == ast::CompilerGenerated {
+ self.unsafe_context.root = UnsafeBlock(block.id)
}
}
+ ast::PushUnsafeBlock(..) => {
+ self.unsafe_context.push_unsafe_count =
+ self.unsafe_context.push_unsafe_count.saturating_add(1);
+ }
+ ast::PopUnsafeBlock(..) => {
+ self.unsafe_context.push_unsafe_count =
+ self.unsafe_context.push_unsafe_count.saturating_sub(1);
+ }
}
visit::walk_block(self, block);
pub fn check_crate(tcx: &ty::ctxt) {
let mut visitor = EffectCheckVisitor {
tcx: tcx,
- unsafe_context: SafeContext,
+ unsafe_context: UnsafeContext::new(SafeContext),
};
visit::walk_crate(&mut visitor, tcx.map.krate());
pub struct UnsafetyState {
pub def: ast::NodeId,
pub unsafety: ast::Unsafety,
+ pub unsafe_push_count: u32,
from_fn: bool
}
impl UnsafetyState {
pub fn function(unsafety: ast::Unsafety, def: ast::NodeId) -> UnsafetyState {
- UnsafetyState { def: def, unsafety: unsafety, from_fn: true }
+ UnsafetyState { def: def, unsafety: unsafety, unsafe_push_count: 0, from_fn: true }
}
pub fn recurse(&mut self, blk: &ast::Block) -> UnsafetyState {
ast::Unsafety::Unsafe if self.from_fn => *self,
unsafety => {
- let (unsafety, def) = match blk.rules {
- ast::UnsafeBlock(..) => (ast::Unsafety::Unsafe, blk.id),
- ast::DefaultBlock => (unsafety, self.def),
+ let (unsafety, def, count) = match blk.rules {
+ ast::PushUnsafeBlock(..) =>
+ (unsafety, blk.id, self.unsafe_push_count.saturating_add(1)),
+ ast::PopUnsafeBlock(..) =>
+ (unsafety, blk.id, self.unsafe_push_count.saturating_sub(1)),
+ ast::UnsafeBlock(..) =>
+ (ast::Unsafety::Unsafe, blk.id, self.unsafe_push_count),
+ ast::DefaultBlock =>
+ (unsafety, self.def, self.unsafe_push_count),
};
UnsafetyState{ def: def,
- unsafety: unsafety,
- from_fn: false }
+ unsafety: unsafety,
+ unsafe_push_count: count,
+ from_fn: false }
}
}
}
pub enum BlockCheckMode {
DefaultBlock,
UnsafeBlock(UnsafeSource),
+ PushUnsafeBlock(UnsafeSource),
+ PopUnsafeBlock(UnsafeSource),
}
#[derive(Clone, PartialEq, Eq, RustcEncodable, RustcDecodable, Hash, Debug, Copy)]
syntax_expanders.insert(intern("cfg"),
builtin_normal_expander(
ext::cfg::expand_cfg));
+ syntax_expanders.insert(intern("push_unsafe"),
+ builtin_normal_expander(
+ ext::pushpop_safe::expand_push_unsafe));
+ syntax_expanders.insert(intern("pop_unsafe"),
+ builtin_normal_expander(
+ ext::pushpop_safe::expand_pop_unsafe));
syntax_expanders.insert(intern("trace_macros"),
builtin_normal_expander(
ext::trace_macros::expand_trace_macros));
fn enable_trace_macros = allow_trace_macros,
fn enable_allow_internal_unstable = allow_internal_unstable,
fn enable_custom_derive = allow_custom_derive,
+ fn enable_pushpop_unsafe = allow_pushpop_unsafe,
}
}
--- /dev/null
+// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+/*
+ * The compiler code necessary to support the `push_unsafe!` and
+ * `pop_unsafe!` macros.
+ *
+ * This is a hack to allow a kind of "safety hygiene", where a macro
+ * can generate code with an interior expression that inherits the
+ * safety of some outer context.
+ *
+ * For example, in:
+ *
+ * ```rust
+ * fn foo() { push_unsafe!( { EXPR_1; pop_unsafe!( EXPR_2 ) } ) }
+ * ```
+ *
+ * the `EXPR_1` is considered to be in an `unsafe` context,
+ * but `EXPR_2` is considered to be in a "safe" (i.e. checked) context.
+ *
+ * For comparison, in:
+ *
+ * ```rust
+ * fn foo() { unsafe { push_unsafe!( { EXPR_1; pop_unsafe!( EXPR_2 ) } ) } }
+ * ```
+ *
+ * both `EXPR_1` and `EXPR_2` are considered to be in `unsafe`
+ * contexts.
+ *
+ */
+
+use ast;
+use codemap::Span;
+use ext::base::*;
+use ext::base;
+use ext::build::AstBuilder;
+use feature_gate;
+use ptr::P;
+
+enum PushPop { Push, Pop }
+
+pub fn expand_push_unsafe<'cx>(cx: &'cx mut ExtCtxt, sp: Span, tts: &[ast::TokenTree])
+ -> Box<base::MacResult+'cx> {
+ feature_gate::check_for_pushpop_syntax(
+ cx.ecfg.features, &cx.parse_sess.span_diagnostic, sp);
+ expand_pushpop_unsafe(cx, sp, tts, PushPop::Push)
+}
+
+pub fn expand_pop_unsafe<'cx>(cx: &'cx mut ExtCtxt, sp: Span, tts: &[ast::TokenTree])
+ -> Box<base::MacResult+'cx> {
+ feature_gate::check_for_pushpop_syntax(
+ cx.ecfg.features, &cx.parse_sess.span_diagnostic, sp);
+ expand_pushpop_unsafe(cx, sp, tts, PushPop::Pop)
+}
+
+fn expand_pushpop_unsafe<'cx>(cx: &'cx mut ExtCtxt, sp: Span, tts: &[ast::TokenTree],
+ pp: PushPop) -> Box<base::MacResult+'cx> {
+ let mut exprs = match get_exprs_from_tts(cx, sp, tts) {
+ Some(exprs) => exprs.into_iter(),
+ None => return DummyResult::expr(sp),
+ };
+ let expr = match (exprs.next(), exprs.next()) {
+ (Some(expr), None) => expr,
+ _ => {
+ let msg = match pp {
+ PushPop::Push => "push_unsafe! takes 1 arguments",
+ PushPop::Pop => "pop_unsafe! takes 1 arguments",
+ };
+ cx.span_err(sp, msg);
+ return DummyResult::expr(sp);
+ }
+ };
+
+ let source = ast::UnsafeSource::CompilerGenerated;
+ let check_mode = match pp {
+ PushPop::Push => ast::BlockCheckMode::PushUnsafeBlock(source),
+ PushPop::Pop => ast::BlockCheckMode::PopUnsafeBlock(source),
+ };
+
+ MacEager::expr(cx.expr_block(P(ast::Block {
+ stmts: vec![],
+ expr: Some(expr),
+ id: ast::DUMMY_NODE_ID,
+ rules: check_mode,
+ span: sp
+ })))
+}
("visible_private_types", "1.0.0", Active),
("slicing_syntax", "1.0.0", Accepted),
("box_syntax", "1.0.0", Active),
+ ("pushpop_unsafe", "1.2.0", Active),
("on_unimplemented", "1.0.0", Active),
("simd_ffi", "1.0.0", Active),
("allocator", "1.0.0", Active),
pub allow_trace_macros: bool,
pub allow_internal_unstable: bool,
pub allow_custom_derive: bool,
+ pub allow_pushpop_unsafe: bool,
pub simd_ffi: bool,
pub unmarked_api: bool,
pub negate_unsigned: bool,
allow_trace_macros: false,
allow_internal_unstable: false,
allow_custom_derive: false,
+ allow_pushpop_unsafe: false,
simd_ffi: false,
unmarked_api: false,
negate_unsigned: false,
}
}
+pub fn check_for_pushpop_syntax(f: Option<&Features>, diag: &SpanHandler, span: Span) {
+ if let Some(&Features { allow_pushpop_unsafe: true, .. }) = f {
+ return;
+ }
+ emit_feature_err(diag, "pushpop_unsafe", span, EXPLAIN_PUSHPOP_UNSAFE);
+}
+
struct Context<'a> {
features: Vec<&'static str>,
span_handler: &'a SpanHandler,
allow_trace_macros: cx.has_feature("trace_macros"),
allow_internal_unstable: cx.has_feature("allow_internal_unstable"),
allow_custom_derive: cx.has_feature("custom_derive"),
+ allow_pushpop_unsafe: cx.has_feature("pushpop_unsafe"),
simd_ffi: cx.has_feature("simd_ffi"),
unmarked_api: cx.has_feature("unmarked_api"),
negate_unsigned: cx.has_feature("negate_unsigned"),
pub mod log_syntax;
pub mod mtwt;
pub mod quote;
+ pub mod pushpop_safe;
pub mod source_util;
pub mod trace_macros;
attrs: &[ast::Attribute],
close_box: bool) -> io::Result<()> {
match blk.rules {
- ast::UnsafeBlock(..) => try!(self.word_space("unsafe")),
- ast::DefaultBlock => ()
+ ast::UnsafeBlock(..) | ast::PushUnsafeBlock(..) => try!(self.word_space("unsafe")),
+ ast::DefaultBlock | ast::PopUnsafeBlock(..) => ()
}
try!(self.maybe_print_comment(blk.span.lo));
try!(self.ann.pre(self, NodeBlock(blk)));
--- /dev/null
+// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+fn main() {
+ let c = push_unsafe!('c'); //~ ERROR push/pop_unsafe macros are experimental
+ let c = pop_unsafe!('c'); //~ ERROR push/pop_unsafe macros are experimental
+}
--- /dev/null
+// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+// Basic sanity check for `push_unsafe!(EXPR)` and
+// `pop_unsafe!(EXPR)`: we can call unsafe code when there are a
+// positive number of pushes in the stack, or if we are within a
+// normal `unsafe` block, but otherwise cannot.
+
+static mut X: i32 = 0;
+
+unsafe fn f() { X += 1; return; }
+fn g() { unsafe { X += 1_000; } return; }
+
+fn main() {
+ push_unsafe!( {
+ f(); pop_unsafe!({
+ f() //~ ERROR: call to unsafe function
+ })
+ } );
+
+ push_unsafe!({
+ f();
+ pop_unsafe!({
+ g();
+ f(); //~ ERROR: call to unsafe function
+ })
+ } );
+
+ push_unsafe!({
+ g(); pop_unsafe!({
+ unsafe {
+ f();
+ }
+ f(); //~ ERROR: call to unsafe function
+ })
+ });
+
+
+ // Note: For implementation simplicity I have chosen to just have
+ // the stack do "saturated pop", but perhaps we would prefer to
+ // have cases like these two here be errors:
+
+ pop_unsafe!{ g() };
+
+ push_unsafe!({
+ pop_unsafe!(pop_unsafe!{ g() })
+ });
+
+
+ // Okay, back to examples that do error, even in the presence of
+ // "saturated pop"
+
+ push_unsafe!({
+ g();
+ pop_unsafe!(pop_unsafe!({
+ f() //~ ERROR: call to unsafe function
+ }))
+ });
+
+ pop_unsafe!({
+ f(); //~ ERROR: call to unsafe function
+ })
+
+}
--- /dev/null
+// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+// Basic sanity check for `push_unsafe!(EXPR)` and
+// `pop_unsafe!(EXPR)`: we can call unsafe code when there are a
+// positive number of pushes in the stack, or if we are within a
+// normal `unsafe` block, but otherwise cannot.
+
+// ignore-pretty because the `push_unsafe!` and `pop_unsafe!` macros
+// are not integrated with the pretty-printer.
+
+#![feature(pushpop_unsafe)]
+
+static mut X: i32 = 0;
+
+unsafe fn f() { X += 1; return; }
+fn g() { unsafe { X += 1_000; } return; }
+
+fn check_reset_x(x: i32) -> bool {
+ #![allow(unused_parens)] // dont you judge my style choices!
+ unsafe {
+ let ret = (x == X);
+ X = 0;
+ ret
+ }
+}
+
+fn main() {
+ // double-check test infrastructure
+ assert!(check_reset_x(0));
+ unsafe { f(); }
+ assert!(check_reset_x(1));
+ assert!(check_reset_x(0));
+ { g(); }
+ assert!(check_reset_x(1000));
+ assert!(check_reset_x(0));
+ unsafe { f(); g(); g(); }
+ assert!(check_reset_x(2001));
+
+ push_unsafe!( { f(); pop_unsafe!( g() ) } );
+ assert!(check_reset_x(1_001));
+ push_unsafe!( { g(); pop_unsafe!( unsafe { f(); f(); } ) } );
+ assert!(check_reset_x(1_002));
+
+ unsafe { push_unsafe!( { f(); pop_unsafe!( { f(); f(); } ) } ); }
+ assert!(check_reset_x(3));
+ push_unsafe!( { f(); push_unsafe!( { pop_unsafe!( { f(); f(); f(); } ) } ); } );
+ assert!(check_reset_x(4));
+}