move unsafety checking to MIR
No functional changes intended.
This commit is contained in:
parent
8c7500f9b6
commit
c72a979979
24 changed files with 662 additions and 388 deletions
|
|
@ -445,6 +445,7 @@ define_dep_nodes!( <'tcx>
|
|||
[] BorrowCheckKrate,
|
||||
[] BorrowCheck(DefId),
|
||||
[] MirBorrowCheck(DefId),
|
||||
[] UnsafetyViolations(DefId),
|
||||
|
||||
[] RvalueCheck(DefId),
|
||||
[] Reachability,
|
||||
|
|
|
|||
|
|
@ -479,40 +479,6 @@ fn main() {
|
|||
```
|
||||
"##,
|
||||
|
||||
E0133: r##"
|
||||
Unsafe code was used outside of an unsafe function or block.
|
||||
|
||||
Erroneous code example:
|
||||
|
||||
```compile_fail,E0133
|
||||
unsafe fn f() { return; } // This is the unsafe code
|
||||
|
||||
fn main() {
|
||||
f(); // error: call to unsafe function requires unsafe function or block
|
||||
}
|
||||
```
|
||||
|
||||
Using unsafe functionality is potentially dangerous and disallowed by safety
|
||||
checks. Examples:
|
||||
|
||||
* Dereferencing raw pointers
|
||||
* Calling functions via FFI
|
||||
* Calling functions marked unsafe
|
||||
|
||||
These safety checks can be relaxed for a section of the code by wrapping the
|
||||
unsafe instructions with an `unsafe` block. For instance:
|
||||
|
||||
```
|
||||
unsafe fn f() { return; }
|
||||
|
||||
fn main() {
|
||||
unsafe { f(); } // ok!
|
||||
}
|
||||
```
|
||||
|
||||
See also https://doc.rust-lang.org/book/first-edition/unsafe.html
|
||||
"##,
|
||||
|
||||
// This shouldn't really ever trigger since the repeated value error comes first
|
||||
E0136: r##"
|
||||
A binary can only have one entry point, and by default that entry point is the
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ impl_stable_hash_for!(struct mir::LocalDecl<'tcx> {
|
|||
});
|
||||
impl_stable_hash_for!(struct mir::UpvarDecl { debug_name, by_ref });
|
||||
impl_stable_hash_for!(struct mir::BasicBlockData<'tcx> { statements, terminator, is_cleanup });
|
||||
impl_stable_hash_for!(struct mir::UnsafetyViolation { source_info, description, lint_node_id });
|
||||
|
||||
impl<'gcx> HashStable<StableHashingContext<'gcx>>
|
||||
for mir::Terminator<'gcx> {
|
||||
|
|
@ -364,7 +365,26 @@ for mir::ProjectionElem<'gcx, V, T>
|
|||
}
|
||||
|
||||
impl_stable_hash_for!(struct mir::VisibilityScopeData { span, parent_scope });
|
||||
impl_stable_hash_for!(struct mir::VisibilityScopeInfo { lint_root });
|
||||
impl_stable_hash_for!(struct mir::VisibilityScopeInfo {
|
||||
lint_root, safety
|
||||
});
|
||||
|
||||
impl<'gcx> HashStable<StableHashingContext<'gcx>> for mir::Safety {
|
||||
fn hash_stable<W: StableHasherResult>(&self,
|
||||
hcx: &mut StableHashingContext<'gcx>,
|
||||
hasher: &mut StableHasher<W>) {
|
||||
mem::discriminant(self).hash_stable(hcx, hasher);
|
||||
|
||||
match *self {
|
||||
mir::Safety::Safe |
|
||||
mir::Safety::BuiltinUnsafe |
|
||||
mir::Safety::FnUnsafe => {}
|
||||
mir::Safety::ExplicitUnsafe(node_id) => {
|
||||
node_id.hash_stable(hcx, hasher);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'gcx> HashStable<StableHashingContext<'gcx>> for mir::Operand<'gcx> {
|
||||
fn hash_stable<W: StableHasherResult>(&self,
|
||||
|
|
|
|||
|
|
@ -111,7 +111,6 @@ pub mod middle {
|
|||
pub mod dataflow;
|
||||
pub mod dead;
|
||||
pub mod dependency_format;
|
||||
pub mod effect;
|
||||
pub mod entry;
|
||||
pub mod exported_symbols;
|
||||
pub mod free_region;
|
||||
|
|
|
|||
|
|
@ -1,316 +0,0 @@
|
|||
// Copyright 2012-2013 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.
|
||||
|
||||
//! Enforces the Rust effect system. Currently there is just one effect,
|
||||
//! `unsafe`.
|
||||
use self::RootUnsafeContext::*;
|
||||
|
||||
use ty::{self, TyCtxt};
|
||||
use lint;
|
||||
use lint::builtin::UNUSED_UNSAFE;
|
||||
|
||||
use hir::def::Def;
|
||||
use hir::intravisit::{self, FnKind, Visitor, NestedVisitorMap};
|
||||
use hir::{self, PatKind};
|
||||
use syntax::ast;
|
||||
use syntax_pos::Span;
|
||||
use util::nodemap::FxHashSet;
|
||||
|
||||
#[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 RootUnsafeContext {
|
||||
SafeContext,
|
||||
UnsafeFn,
|
||||
UnsafeBlock(ast::NodeId),
|
||||
}
|
||||
|
||||
struct EffectCheckVisitor<'a, 'tcx: 'a> {
|
||||
tcx: TyCtxt<'a, 'tcx, 'tcx>,
|
||||
tables: &'a ty::TypeckTables<'tcx>,
|
||||
body_id: hir::BodyId,
|
||||
|
||||
/// Whether we're in an unsafe context.
|
||||
unsafe_context: UnsafeContext,
|
||||
used_unsafe: FxHashSet<ast::NodeId>,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> EffectCheckVisitor<'a, 'tcx> {
|
||||
fn require_unsafe_ext(&mut self, node_id: ast::NodeId, span: Span,
|
||||
description: &str, is_lint: bool) {
|
||||
if self.unsafe_context.push_unsafe_count > 0 { return; }
|
||||
match self.unsafe_context.root {
|
||||
SafeContext => {
|
||||
if is_lint {
|
||||
self.tcx.lint_node(lint::builtin::SAFE_EXTERN_STATICS,
|
||||
node_id,
|
||||
span,
|
||||
&format!("{} requires unsafe function or \
|
||||
block (error E0133)", description));
|
||||
} else {
|
||||
// Report an error.
|
||||
struct_span_err!(
|
||||
self.tcx.sess, span, E0133,
|
||||
"{} requires unsafe function or block", description)
|
||||
.span_label(span, description)
|
||||
.emit();
|
||||
}
|
||||
}
|
||||
UnsafeBlock(block_id) => {
|
||||
// OK, but record this.
|
||||
debug!("effect: recording unsafe block as used: {}", block_id);
|
||||
self.used_unsafe.insert(block_id);
|
||||
}
|
||||
UnsafeFn => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn require_unsafe(&mut self, span: Span, description: &str) {
|
||||
self.require_unsafe_ext(ast::DUMMY_NODE_ID, span, description, false)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> Visitor<'tcx> for EffectCheckVisitor<'a, 'tcx> {
|
||||
fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
|
||||
NestedVisitorMap::None
|
||||
}
|
||||
|
||||
fn visit_nested_body(&mut self, body: hir::BodyId) {
|
||||
let old_tables = self.tables;
|
||||
let old_body_id = self.body_id;
|
||||
self.tables = self.tcx.body_tables(body);
|
||||
self.body_id = body;
|
||||
let body = self.tcx.hir.body(body);
|
||||
self.visit_body(body);
|
||||
self.tables = old_tables;
|
||||
self.body_id = old_body_id;
|
||||
}
|
||||
|
||||
fn visit_fn(&mut self, fn_kind: FnKind<'tcx>, fn_decl: &'tcx hir::FnDecl,
|
||||
body_id: hir::BodyId, span: Span, id: ast::NodeId) {
|
||||
|
||||
let (is_item_fn, is_unsafe_fn) = match fn_kind {
|
||||
FnKind::ItemFn(_, _, unsafety, ..) =>
|
||||
(true, unsafety == hir::Unsafety::Unsafe),
|
||||
FnKind::Method(_, sig, ..) =>
|
||||
(true, sig.unsafety == hir::Unsafety::Unsafe),
|
||||
_ => (false, false),
|
||||
};
|
||||
|
||||
let old_unsafe_context = self.unsafe_context;
|
||||
if is_unsafe_fn {
|
||||
self.unsafe_context = UnsafeContext::new(UnsafeFn)
|
||||
} else if is_item_fn {
|
||||
self.unsafe_context = UnsafeContext::new(SafeContext)
|
||||
}
|
||||
|
||||
intravisit::walk_fn(self, fn_kind, fn_decl, body_id, span, id);
|
||||
|
||||
self.unsafe_context = old_unsafe_context
|
||||
}
|
||||
|
||||
fn visit_block(&mut self, block: &'tcx hir::Block) {
|
||||
let old_unsafe_context = self.unsafe_context;
|
||||
match block.rules {
|
||||
hir::UnsafeBlock(source) => {
|
||||
// By default only the outermost `unsafe` block is
|
||||
// "used" and so nested unsafe blocks are pointless
|
||||
// (the inner ones are unnecessary and we actually
|
||||
// warn about them). As such, there are two cases when
|
||||
// we need to create a new context, when we're
|
||||
// - outside `unsafe` and found a `unsafe` block
|
||||
// (normal case)
|
||||
// - inside `unsafe`, found an `unsafe` block
|
||||
// created internally to the compiler
|
||||
//
|
||||
// The second case is necessary to ensure that the
|
||||
// compiler `unsafe` blocks don't accidentally "use"
|
||||
// external blocks (e.g. `unsafe { println("") }`,
|
||||
// expands to `unsafe { ... unsafe { ... } }` where
|
||||
// the inner one is compiler generated).
|
||||
if self.unsafe_context.root == SafeContext || source == hir::CompilerGenerated {
|
||||
self.unsafe_context.root = UnsafeBlock(block.id)
|
||||
}
|
||||
}
|
||||
hir::PushUnsafeBlock(..) => {
|
||||
self.unsafe_context.push_unsafe_count =
|
||||
self.unsafe_context.push_unsafe_count.checked_add(1).unwrap();
|
||||
}
|
||||
hir::PopUnsafeBlock(..) => {
|
||||
self.unsafe_context.push_unsafe_count =
|
||||
self.unsafe_context.push_unsafe_count.checked_sub(1).unwrap();
|
||||
}
|
||||
hir::DefaultBlock => {}
|
||||
}
|
||||
|
||||
intravisit::walk_block(self, block);
|
||||
|
||||
self.unsafe_context = old_unsafe_context;
|
||||
|
||||
// Don't warn about generated blocks, that'll just pollute the output.
|
||||
match block.rules {
|
||||
hir::UnsafeBlock(hir::UserProvided) => {}
|
||||
_ => return,
|
||||
}
|
||||
if self.used_unsafe.contains(&block.id) {
|
||||
return
|
||||
}
|
||||
|
||||
/// Return the NodeId for an enclosing scope that is also `unsafe`
|
||||
fn is_enclosed(tcx: TyCtxt,
|
||||
used_unsafe: &FxHashSet<ast::NodeId>,
|
||||
id: ast::NodeId) -> Option<(String, ast::NodeId)> {
|
||||
let parent_id = tcx.hir.get_parent_node(id);
|
||||
if parent_id != id {
|
||||
if used_unsafe.contains(&parent_id) {
|
||||
Some(("block".to_string(), parent_id))
|
||||
} else if let Some(hir::map::NodeItem(&hir::Item {
|
||||
node: hir::ItemFn(_, hir::Unsafety::Unsafe, _, _, _, _),
|
||||
..
|
||||
})) = tcx.hir.find(parent_id) {
|
||||
Some(("fn".to_string(), parent_id))
|
||||
} else {
|
||||
is_enclosed(tcx, used_unsafe, parent_id)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
let mut db = self.tcx.struct_span_lint_node(UNUSED_UNSAFE,
|
||||
block.id,
|
||||
block.span,
|
||||
"unnecessary `unsafe` block");
|
||||
db.span_label(block.span, "unnecessary `unsafe` block");
|
||||
if let Some((kind, id)) = is_enclosed(self.tcx, &self.used_unsafe, block.id) {
|
||||
db.span_note(self.tcx.hir.span(id),
|
||||
&format!("because it's nested under this `unsafe` {}", kind));
|
||||
}
|
||||
db.emit();
|
||||
}
|
||||
|
||||
fn visit_expr(&mut self, expr: &'tcx hir::Expr) {
|
||||
match expr.node {
|
||||
hir::ExprMethodCall(..) => {
|
||||
let def_id = self.tables.type_dependent_defs()[expr.hir_id].def_id();
|
||||
let sig = self.tcx.fn_sig(def_id);
|
||||
debug!("effect: method call case, signature is {:?}",
|
||||
sig);
|
||||
|
||||
if sig.0.unsafety == hir::Unsafety::Unsafe {
|
||||
self.require_unsafe(expr.span,
|
||||
"invocation of unsafe method")
|
||||
}
|
||||
}
|
||||
hir::ExprCall(ref base, _) => {
|
||||
let base_type = self.tables.expr_ty_adjusted(base);
|
||||
debug!("effect: call case, base type is {:?}",
|
||||
base_type);
|
||||
match base_type.sty {
|
||||
ty::TyFnDef(..) | ty::TyFnPtr(_) => {
|
||||
if base_type.fn_sig(self.tcx).unsafety() == hir::Unsafety::Unsafe {
|
||||
self.require_unsafe(expr.span, "call to unsafe function")
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
hir::ExprUnary(hir::UnDeref, ref base) => {
|
||||
let base_type = self.tables.expr_ty_adjusted(base);
|
||||
debug!("effect: unary case, base type is {:?}",
|
||||
base_type);
|
||||
if let ty::TyRawPtr(_) = base_type.sty {
|
||||
self.require_unsafe(expr.span, "dereference of raw pointer")
|
||||
}
|
||||
}
|
||||
hir::ExprInlineAsm(..) => {
|
||||
self.require_unsafe(expr.span, "use of inline assembly");
|
||||
}
|
||||
hir::ExprPath(hir::QPath::Resolved(_, ref path)) => {
|
||||
if let Def::Static(def_id, mutbl) = path.def {
|
||||
if mutbl {
|
||||
self.require_unsafe(expr.span, "use of mutable static");
|
||||
} else if match self.tcx.hir.get_if_local(def_id) {
|
||||
Some(hir::map::NodeForeignItem(..)) => true,
|
||||
Some(..) => false,
|
||||
None => self.tcx.is_foreign_item(def_id),
|
||||
} {
|
||||
self.require_unsafe_ext(expr.id, expr.span, "use of extern static", true);
|
||||
}
|
||||
}
|
||||
}
|
||||
hir::ExprField(ref base_expr, field) => {
|
||||
if let ty::TyAdt(adt, ..) = self.tables.expr_ty_adjusted(base_expr).sty {
|
||||
if adt.is_union() {
|
||||
self.require_unsafe(field.span, "access to union field");
|
||||
}
|
||||
}
|
||||
}
|
||||
hir::ExprAssign(ref lhs, ref rhs) => {
|
||||
if let hir::ExprField(ref base_expr, field) = lhs.node {
|
||||
if let ty::TyAdt(adt, ..) = self.tables.expr_ty_adjusted(base_expr).sty {
|
||||
if adt.is_union() {
|
||||
let field_ty = self.tables.expr_ty_adjusted(lhs);
|
||||
let owner_def_id = self.tcx.hir.body_owner_def_id(self.body_id);
|
||||
let param_env = self.tcx.param_env(owner_def_id);
|
||||
if field_ty.moves_by_default(self.tcx, param_env, field.span) {
|
||||
self.require_unsafe(field.span,
|
||||
"assignment to non-`Copy` union field");
|
||||
}
|
||||
// Do not walk the field expr again.
|
||||
intravisit::walk_expr(self, base_expr);
|
||||
intravisit::walk_expr(self, rhs);
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
intravisit::walk_expr(self, expr);
|
||||
}
|
||||
|
||||
fn visit_pat(&mut self, pat: &'tcx hir::Pat) {
|
||||
if let PatKind::Struct(_, ref fields, _) = pat.node {
|
||||
if let ty::TyAdt(adt, ..) = self.tables.pat_ty(pat).sty {
|
||||
if adt.is_union() {
|
||||
for field in fields {
|
||||
self.require_unsafe(field.span, "matching on union field");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
intravisit::walk_pat(self, pat);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_crate<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>) {
|
||||
let mut visitor = EffectCheckVisitor {
|
||||
tcx,
|
||||
tables: &ty::TypeckTables::empty(None),
|
||||
body_id: hir::BodyId { node_id: ast::CRATE_NODE_ID },
|
||||
unsafe_context: UnsafeContext::new(SafeContext),
|
||||
used_unsafe: FxHashSet(),
|
||||
};
|
||||
|
||||
tcx.hir.krate().visit_all_item_likes(&mut visitor.as_deep_visitor());
|
||||
}
|
||||
|
|
@ -290,6 +290,19 @@ impl<'tcx> Mir<'tcx> {
|
|||
pub struct VisibilityScopeInfo {
|
||||
/// A NodeId with lint levels equivalent to this scope's lint levels.
|
||||
pub lint_root: ast::NodeId,
|
||||
/// The unsafe block that contains this node.
|
||||
pub safety: Safety,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum Safety {
|
||||
Safe,
|
||||
/// Unsafe because of a PushUnsafeBlock
|
||||
BuiltinUnsafe,
|
||||
/// Unsafe because of an unsafe fn
|
||||
FnUnsafe,
|
||||
/// Unsafe because of an `unsafe` block
|
||||
ExplicitUnsafe(ast::NodeId)
|
||||
}
|
||||
|
||||
impl_stable_hash_for!(struct Mir<'tcx> {
|
||||
|
|
@ -346,7 +359,7 @@ impl<T> serialize::Decodable for ClearOnDecode<T> {
|
|||
/// Grouped information about the source code origin of a MIR entity.
|
||||
/// Intended to be inspected by diagnostics and debuginfo.
|
||||
/// Most passes can work with it as a whole, within a single function.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, RustcEncodable, RustcDecodable)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, RustcEncodable, RustcDecodable, Hash)]
|
||||
pub struct SourceInfo {
|
||||
/// Source span for the AST pertaining to this MIR entity.
|
||||
pub span: Span,
|
||||
|
|
@ -447,7 +460,7 @@ pub struct LocalDecl<'tcx> {
|
|||
/// True if this is an internal local
|
||||
///
|
||||
/// These locals are not based on types in the source code and are only used
|
||||
/// for drop flags at the moment.
|
||||
/// for a few desugarings at the moment.
|
||||
///
|
||||
/// The generator transformation will sanity check the locals which are live
|
||||
/// across a suspension point against the type components of the generator
|
||||
|
|
@ -455,6 +468,9 @@ pub struct LocalDecl<'tcx> {
|
|||
/// flag drop flags to avoid triggering this check as they are introduced
|
||||
/// after typeck.
|
||||
///
|
||||
/// Unsafety checking will also ignore dereferences of these locals,
|
||||
/// so they can be used for raw pointers only used in a desugaring.
|
||||
///
|
||||
/// This should be sound because the drop flags are fully algebraic, and
|
||||
/// therefore don't affect the OIBIT or outlives properties of the
|
||||
/// generator.
|
||||
|
|
@ -1634,6 +1650,13 @@ impl Location {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct UnsafetyViolation {
|
||||
pub source_info: SourceInfo,
|
||||
pub description: &'static str,
|
||||
pub lint_node_id: Option<ast::NodeId>,
|
||||
}
|
||||
|
||||
/// The layout of generator state
|
||||
#[derive(Clone, Debug, RustcEncodable, RustcDecodable)]
|
||||
pub struct GeneratorLayout<'tcx> {
|
||||
|
|
|
|||
|
|
@ -159,6 +159,10 @@ define_maps! { <'tcx>
|
|||
/// expression defining the closure.
|
||||
[] fn closure_kind: ClosureKind(DefId) -> ty::ClosureKind,
|
||||
|
||||
/// Unsafety violations for this def ID.
|
||||
[] fn unsafety_violations: UnsafetyViolations(DefId)
|
||||
-> Rc<[mir::UnsafetyViolation]>,
|
||||
|
||||
/// The signature of functions and closures.
|
||||
[] fn fn_sig: FnSignature(DefId) -> ty::PolyFnSig<'tcx>,
|
||||
|
||||
|
|
|
|||
|
|
@ -1081,10 +1081,6 @@ pub fn phase_3_run_analysis_passes<'tcx, F, R>(sess: &'tcx Session,
|
|||
"intrinsic checking",
|
||||
|| middle::intrinsicck::check_crate(tcx));
|
||||
|
||||
time(time_passes,
|
||||
"effect checking",
|
||||
|| middle::effect::check_crate(tcx));
|
||||
|
||||
time(time_passes,
|
||||
"match checking",
|
||||
|| check_match::check_crate(tcx));
|
||||
|
|
@ -1105,6 +1101,11 @@ pub fn phase_3_run_analysis_passes<'tcx, F, R>(sess: &'tcx Session,
|
|||
"MIR borrow checking",
|
||||
|| for def_id in tcx.body_owners() { tcx.mir_borrowck(def_id) });
|
||||
|
||||
time(time_passes,
|
||||
"MIR effect checking",
|
||||
|| for def_id in tcx.body_owners() {
|
||||
mir::transform::check_unsafety::check_unsafety(tcx, def_id)
|
||||
});
|
||||
// Avoid overwhelming user with errors if type checking failed.
|
||||
// I'm not sure how helpful this is, to be honest, but it avoids
|
||||
// a
|
||||
|
|
|
|||
|
|
@ -21,7 +21,15 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
|
|||
ast_block: &'tcx hir::Block,
|
||||
source_info: SourceInfo)
|
||||
-> BlockAnd<()> {
|
||||
let Block { region_scope, opt_destruction_scope, span, stmts, expr, targeted_by_break } =
|
||||
let Block {
|
||||
region_scope,
|
||||
opt_destruction_scope,
|
||||
span,
|
||||
stmts,
|
||||
expr,
|
||||
targeted_by_break,
|
||||
safety_mode
|
||||
} =
|
||||
self.hir.mirror(ast_block);
|
||||
self.in_opt_scope(opt_destruction_scope.map(|de|(de, source_info)), block, move |this| {
|
||||
this.in_scope((region_scope, source_info), LintLevel::Inherited, block, move |this| {
|
||||
|
|
@ -30,13 +38,15 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
|
|||
let exit_block = this.cfg.start_new_block();
|
||||
let block_exit = this.in_breakable_scope(
|
||||
None, exit_block, destination.clone(), |this| {
|
||||
this.ast_block_stmts(destination, block, span, stmts, expr)
|
||||
this.ast_block_stmts(destination, block, span, stmts, expr,
|
||||
safety_mode)
|
||||
});
|
||||
this.cfg.terminate(unpack!(block_exit), source_info,
|
||||
TerminatorKind::Goto { target: exit_block });
|
||||
exit_block.unit()
|
||||
} else {
|
||||
this.ast_block_stmts(destination, block, span, stmts, expr)
|
||||
this.ast_block_stmts(destination, block, span, stmts, expr,
|
||||
safety_mode)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
@ -47,7 +57,8 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
|
|||
mut block: BasicBlock,
|
||||
span: Span,
|
||||
stmts: Vec<StmtRef<'tcx>>,
|
||||
expr: Option<ExprRef<'tcx>>)
|
||||
expr: Option<ExprRef<'tcx>>,
|
||||
safety_mode: BlockSafety)
|
||||
-> BlockAnd<()> {
|
||||
let this = self;
|
||||
|
||||
|
|
@ -69,6 +80,10 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
|
|||
// First we build all the statements in the block.
|
||||
let mut let_scope_stack = Vec::with_capacity(8);
|
||||
let outer_visibility_scope = this.visibility_scope;
|
||||
let outer_push_unsafe_count = this.push_unsafe_count;
|
||||
let outer_unpushed_unsafe = this.unpushed_unsafe;
|
||||
this.update_visibility_scope_for_safety_mode(span, safety_mode);
|
||||
|
||||
let source_info = this.source_info(span);
|
||||
for stmt in stmts {
|
||||
let Stmt { kind, opt_destruction_scope } = this.hir.mirror(stmt);
|
||||
|
|
@ -137,6 +152,48 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
|
|||
}
|
||||
// Restore the original visibility scope.
|
||||
this.visibility_scope = outer_visibility_scope;
|
||||
this.push_unsafe_count = outer_push_unsafe_count;
|
||||
this.unpushed_unsafe = outer_unpushed_unsafe;
|
||||
block.unit()
|
||||
}
|
||||
|
||||
/// If we are changing the safety mode, create a new visibility scope
|
||||
fn update_visibility_scope_for_safety_mode(&mut self,
|
||||
span: Span,
|
||||
safety_mode: BlockSafety)
|
||||
{
|
||||
debug!("update_visibility_scope_for({:?}, {:?})", span, safety_mode);
|
||||
let new_unsafety = match safety_mode {
|
||||
BlockSafety::Safe => None,
|
||||
BlockSafety::ExplicitUnsafe(node_id) => {
|
||||
assert_eq!(self.push_unsafe_count, 0);
|
||||
match self.unpushed_unsafe {
|
||||
Safety::Safe => {}
|
||||
_ => return
|
||||
}
|
||||
self.unpushed_unsafe = Safety::ExplicitUnsafe(node_id);
|
||||
Some(Safety::ExplicitUnsafe(node_id))
|
||||
}
|
||||
BlockSafety::PushUnsafe => {
|
||||
self.push_unsafe_count += 1;
|
||||
Some(Safety::BuiltinUnsafe)
|
||||
}
|
||||
BlockSafety::PopUnsafe => {
|
||||
self.push_unsafe_count =
|
||||
self.push_unsafe_count.checked_sub(1).unwrap_or_else(|| {
|
||||
span_bug!(span, "unsafe count underflow")
|
||||
});
|
||||
if self.push_unsafe_count == 0 {
|
||||
Some(self.unpushed_unsafe)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(unsafety) = new_unsafety {
|
||||
self.visibility_scope = self.new_visibility_scope(
|
||||
span, LintLevel::Inherited, Some(unsafety));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -228,9 +228,22 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
|
|||
let val = args.next().expect("1 argument to `move_val_init`");
|
||||
assert!(args.next().is_none(), ">2 arguments to `move_val_init`");
|
||||
|
||||
let topmost_scope = this.topmost_scope();
|
||||
let ptr = unpack!(block = this.as_temp(block, Some(topmost_scope), ptr));
|
||||
this.into(&Lvalue::Local(ptr).deref(), block, val)
|
||||
let ptr = this.hir.mirror(ptr);
|
||||
let ptr_ty = ptr.ty;
|
||||
// Create an *internal* temp for the pointer, so that unsafety
|
||||
// checking won't complain about the raw pointer assignment.
|
||||
let ptr_temp = this.local_decls.push(LocalDecl {
|
||||
mutability: Mutability::Mut,
|
||||
ty: ptr_ty,
|
||||
name: None,
|
||||
source_info,
|
||||
lexical_scope: source_info.scope,
|
||||
internal: true,
|
||||
is_user_variable: false
|
||||
});
|
||||
let ptr_temp = Lvalue::Local(ptr_temp);
|
||||
let block = unpack!(this.into(&ptr_temp, block, ptr));
|
||||
this.into(&ptr_temp.deref(), block, val)
|
||||
} else {
|
||||
let args: Vec<_> =
|
||||
args.into_iter()
|
||||
|
|
|
|||
|
|
@ -182,11 +182,13 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
|
|||
self.visit_bindings(pattern, &mut |this, mutability, name, var, span, ty| {
|
||||
if var_scope.is_none() {
|
||||
var_scope = Some(this.new_visibility_scope(scope_span,
|
||||
LintLevel::Inherited));
|
||||
LintLevel::Inherited,
|
||||
None));
|
||||
// If we have lints, create a new visibility scope
|
||||
// that marks the lints for the locals.
|
||||
if lint_level.is_explicit() {
|
||||
this.new_visibility_scope(scope_span, lint_level);
|
||||
this.visibility_scope =
|
||||
this.new_visibility_scope(scope_span, lint_level, None);
|
||||
}
|
||||
}
|
||||
let source_info = SourceInfo {
|
||||
|
|
|
|||
|
|
@ -72,14 +72,14 @@ pub fn mir_build<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, def_id: DefId) -> Mir<'t
|
|||
// is a constant "initializer" expression.
|
||||
match expr.node {
|
||||
hir::ExprClosure(_, _, body, _, _) => body,
|
||||
_ => hir::BodyId { node_id: expr.id }
|
||||
_ => hir::BodyId { node_id: expr.id },
|
||||
}
|
||||
}
|
||||
hir::map::NodeVariant(variant) =>
|
||||
return create_constructor_shim(tcx, id, &variant.node.data),
|
||||
hir::map::NodeStructCtor(ctor) =>
|
||||
return create_constructor_shim(tcx, id, ctor),
|
||||
_ => unsupported()
|
||||
_ => unsupported(),
|
||||
};
|
||||
|
||||
let src = MirSource::from_node(tcx, id);
|
||||
|
|
@ -109,6 +109,12 @@ pub fn mir_build<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, def_id: DefId) -> Mir<'t
|
|||
_ => None,
|
||||
};
|
||||
|
||||
// FIXME: safety in closures
|
||||
let safety = match fn_sig.unsafety {
|
||||
hir::Unsafety::Normal => Safety::Safe,
|
||||
hir::Unsafety::Unsafe => Safety::FnUnsafe,
|
||||
};
|
||||
|
||||
let body = tcx.hir.body(body_id);
|
||||
let explicit_arguments =
|
||||
body.arguments
|
||||
|
|
@ -127,7 +133,8 @@ pub fn mir_build<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, def_id: DefId) -> Mir<'t
|
|||
(None, fn_sig.output())
|
||||
};
|
||||
|
||||
build::construct_fn(cx, id, arguments, abi, return_ty, yield_ty, body)
|
||||
build::construct_fn(cx, id, arguments, safety, abi,
|
||||
return_ty, yield_ty, body)
|
||||
} else {
|
||||
build::construct_const(cx, body_id)
|
||||
};
|
||||
|
|
@ -271,6 +278,13 @@ struct Builder<'a, 'gcx: 'a+'tcx, 'tcx: 'a> {
|
|||
/// see the `scope` module for more details
|
||||
scopes: Vec<scope::Scope<'tcx>>,
|
||||
|
||||
/// The current unsafe block in scope, even if it is hidden by
|
||||
/// a PushUnsafeBlock
|
||||
unpushed_unsafe: Safety,
|
||||
|
||||
/// The number of `push_unsafe_block` levels in scope
|
||||
push_unsafe_count: usize,
|
||||
|
||||
/// the current set of breakables; see the `scope` module for more
|
||||
/// details
|
||||
breakable_scopes: Vec<scope::BreakableScope<'tcx>>,
|
||||
|
|
@ -360,6 +374,7 @@ macro_rules! unpack {
|
|||
fn construct_fn<'a, 'gcx, 'tcx, A>(hir: Cx<'a, 'gcx, 'tcx>,
|
||||
fn_id: ast::NodeId,
|
||||
arguments: A,
|
||||
safety: Safety,
|
||||
abi: Abi,
|
||||
return_ty: Ty<'gcx>,
|
||||
yield_ty: Option<Ty<'gcx>>,
|
||||
|
|
@ -374,6 +389,7 @@ fn construct_fn<'a, 'gcx, 'tcx, A>(hir: Cx<'a, 'gcx, 'tcx>,
|
|||
let mut builder = Builder::new(hir.clone(),
|
||||
span,
|
||||
arguments.len(),
|
||||
safety,
|
||||
return_ty);
|
||||
|
||||
let call_site_scope = region::Scope::CallSite(body.value.hir_id.local_id);
|
||||
|
|
@ -444,7 +460,7 @@ fn construct_const<'a, 'gcx, 'tcx>(hir: Cx<'a, 'gcx, 'tcx>,
|
|||
let ty = hir.tables().expr_ty_adjusted(ast_expr);
|
||||
let owner_id = tcx.hir.body_owner(body_id);
|
||||
let span = tcx.hir.span(owner_id);
|
||||
let mut builder = Builder::new(hir.clone(), span, 0, ty);
|
||||
let mut builder = Builder::new(hir.clone(), span, 0, Safety::Safe, ty);
|
||||
|
||||
let mut block = START_BLOCK;
|
||||
let expr = builder.hir.mirror(ast_expr);
|
||||
|
|
@ -465,7 +481,7 @@ fn construct_error<'a, 'gcx, 'tcx>(hir: Cx<'a, 'gcx, 'tcx>,
|
|||
let owner_id = hir.tcx().hir.body_owner(body_id);
|
||||
let span = hir.tcx().hir.span(owner_id);
|
||||
let ty = hir.tcx().types.err;
|
||||
let mut builder = Builder::new(hir, span, 0, ty);
|
||||
let mut builder = Builder::new(hir, span, 0, Safety::Safe, ty);
|
||||
let source_info = builder.source_info(span);
|
||||
builder.cfg.terminate(START_BLOCK, source_info, TerminatorKind::Unreachable);
|
||||
builder.finish(vec![], ty, None)
|
||||
|
|
@ -475,6 +491,7 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
|
|||
fn new(hir: Cx<'a, 'gcx, 'tcx>,
|
||||
span: Span,
|
||||
arg_count: usize,
|
||||
safety: Safety,
|
||||
return_ty: Ty<'tcx>)
|
||||
-> Builder<'a, 'gcx, 'tcx> {
|
||||
let lint_level = LintLevel::Explicit(hir.root_lint_level);
|
||||
|
|
@ -487,6 +504,8 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
|
|||
visibility_scopes: IndexVec::new(),
|
||||
visibility_scope: ARGUMENT_VISIBILITY_SCOPE,
|
||||
visibility_scope_info: IndexVec::new(),
|
||||
push_unsafe_count: 0,
|
||||
unpushed_unsafe: safety,
|
||||
breakable_scopes: vec![],
|
||||
local_decls: IndexVec::from_elem_n(LocalDecl::new_return_pointer(return_ty,
|
||||
span), 1),
|
||||
|
|
@ -498,7 +517,7 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
|
|||
|
||||
assert_eq!(builder.cfg.start_new_block(), START_BLOCK);
|
||||
assert_eq!(
|
||||
builder.new_visibility_scope(span, lint_level),
|
||||
builder.new_visibility_scope(span, lint_level, Some(safety)),
|
||||
ARGUMENT_VISIBILITY_SCOPE);
|
||||
builder.visibility_scopes[ARGUMENT_VISIBILITY_SCOPE].parent_scope = None;
|
||||
|
||||
|
|
|
|||
|
|
@ -330,7 +330,8 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
|
|||
|
||||
if !same_lint_scopes {
|
||||
self.visibility_scope =
|
||||
self.new_visibility_scope(region_scope.1.span, lint_level);
|
||||
self.new_visibility_scope(region_scope.1.span, lint_level,
|
||||
None);
|
||||
}
|
||||
}
|
||||
self.push_scope(region_scope);
|
||||
|
|
@ -500,19 +501,27 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
|
|||
/// Creates a new visibility scope, nested in the current one.
|
||||
pub fn new_visibility_scope(&mut self,
|
||||
span: Span,
|
||||
lint_level: LintLevel) -> VisibilityScope {
|
||||
debug!("new_visibility_scope({:?}, {:?})", span, lint_level);
|
||||
lint_level: LintLevel,
|
||||
safety: Option<Safety>) -> VisibilityScope {
|
||||
let parent = self.visibility_scope;
|
||||
let info = if let LintLevel::Explicit(lint_level) = lint_level {
|
||||
VisibilityScopeInfo { lint_root: lint_level }
|
||||
} else {
|
||||
self.visibility_scope_info[parent].clone()
|
||||
};
|
||||
debug!("new_visibility_scope({:?}, {:?}, {:?}) - parent({:?})={:?}",
|
||||
span, lint_level, safety,
|
||||
parent, self.visibility_scope_info.get(parent));
|
||||
let scope = self.visibility_scopes.push(VisibilityScopeData {
|
||||
span,
|
||||
parent_scope: Some(parent),
|
||||
});
|
||||
self.visibility_scope_info.push(info);
|
||||
let scope_info = VisibilityScopeInfo {
|
||||
lint_root: if let LintLevel::Explicit(lint_root) = lint_level {
|
||||
lint_root
|
||||
} else {
|
||||
self.visibility_scope_info[parent].lint_root
|
||||
},
|
||||
safety: safety.unwrap_or_else(|| {
|
||||
self.visibility_scope_info[parent].safety
|
||||
})
|
||||
};
|
||||
self.visibility_scope_info.push(scope_info);
|
||||
scope
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -195,6 +195,40 @@ instead of using a `const fn`, or refactoring the code to a functional style to
|
|||
avoid mutation if possible.
|
||||
"##,
|
||||
|
||||
E0133: r##"
|
||||
Unsafe code was used outside of an unsafe function or block.
|
||||
|
||||
Erroneous code example:
|
||||
|
||||
```compile_fail,E0133
|
||||
unsafe fn f() { return; } // This is the unsafe code
|
||||
|
||||
fn main() {
|
||||
f(); // error: call to unsafe function requires unsafe function or block
|
||||
}
|
||||
```
|
||||
|
||||
Using unsafe functionality is potentially dangerous and disallowed by safety
|
||||
checks. Examples:
|
||||
|
||||
* Dereferencing raw pointers
|
||||
* Calling functions via FFI
|
||||
* Calling functions marked unsafe
|
||||
|
||||
These safety checks can be relaxed for a section of the code by wrapping the
|
||||
unsafe instructions with an `unsafe` block. For instance:
|
||||
|
||||
```
|
||||
unsafe fn f() { return; }
|
||||
|
||||
fn main() {
|
||||
unsafe { f(); } // ok!
|
||||
}
|
||||
```
|
||||
|
||||
See also https://doc.rust-lang.org/book/first-edition/unsafe.html
|
||||
"##,
|
||||
|
||||
E0381: r##"
|
||||
It is not allowed to use or capture an uninitialized variable. For example:
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,16 @@ impl<'tcx> Mirror<'tcx> for &'tcx hir::Block {
|
|||
span: self.span,
|
||||
stmts,
|
||||
expr: self.expr.to_ref(),
|
||||
safety_mode: match self.rules {
|
||||
hir::BlockCheckMode::DefaultBlock =>
|
||||
BlockSafety::Safe,
|
||||
hir::BlockCheckMode::UnsafeBlock(..) =>
|
||||
BlockSafety::ExplicitUnsafe(self.id),
|
||||
hir::BlockCheckMode::PushUnsafeBlock(..) =>
|
||||
BlockSafety::PushUnsafe,
|
||||
hir::BlockCheckMode::PopUnsafeBlock(..) =>
|
||||
BlockSafety::PopUnsafe
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,6 +52,15 @@ pub struct Block<'tcx> {
|
|||
pub span: Span,
|
||||
pub stmts: Vec<StmtRef<'tcx>>,
|
||||
pub expr: Option<ExprRef<'tcx>>,
|
||||
pub safety_mode: BlockSafety,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum BlockSafety {
|
||||
Safe,
|
||||
ExplicitUnsafe(ast::NodeId),
|
||||
PushUnsafe,
|
||||
PopUnsafe
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
|
|
|||
387
src/librustc_mir/transform/check_unsafety.rs
Normal file
387
src/librustc_mir/transform/check_unsafety.rs
Normal file
|
|
@ -0,0 +1,387 @@
|
|||
// Copyright 2017 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.
|
||||
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_data_structures::indexed_vec::IndexVec;
|
||||
|
||||
use rustc::ty::maps::Providers;
|
||||
use rustc::ty::{self, TyCtxt};
|
||||
use rustc::hir;
|
||||
use rustc::hir::def::Def;
|
||||
use rustc::hir::def_id::DefId;
|
||||
use rustc::hir::map::{DefPathData, Node};
|
||||
use rustc::lint::builtin::{SAFE_EXTERN_STATICS, UNUSED_UNSAFE};
|
||||
use rustc::mir::*;
|
||||
use rustc::mir::visit::{LvalueContext, Visitor};
|
||||
|
||||
use syntax::ast;
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
|
||||
pub struct UnsafetyChecker<'a, 'tcx: 'a> {
|
||||
mir: &'a Mir<'tcx>,
|
||||
visibility_scope_info: &'a IndexVec<VisibilityScope, VisibilityScopeInfo>,
|
||||
violations: Vec<UnsafetyViolation>,
|
||||
source_info: SourceInfo,
|
||||
tcx: TyCtxt<'a, 'tcx, 'tcx>,
|
||||
param_env: ty::ParamEnv<'tcx>,
|
||||
used_unsafe: FxHashSet<ast::NodeId>,
|
||||
}
|
||||
|
||||
impl<'a, 'gcx, 'tcx> UnsafetyChecker<'a, 'tcx> {
|
||||
fn new(mir: &'a Mir<'tcx>,
|
||||
visibility_scope_info: &'a IndexVec<VisibilityScope, VisibilityScopeInfo>,
|
||||
tcx: TyCtxt<'a, 'tcx, 'tcx>,
|
||||
param_env: ty::ParamEnv<'tcx>) -> Self {
|
||||
Self {
|
||||
mir,
|
||||
visibility_scope_info,
|
||||
violations: vec![],
|
||||
source_info: SourceInfo {
|
||||
span: mir.span,
|
||||
scope: ARGUMENT_VISIBILITY_SCOPE
|
||||
},
|
||||
tcx,
|
||||
param_env,
|
||||
used_unsafe: FxHashSet(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> Visitor<'tcx> for UnsafetyChecker<'a, 'tcx> {
|
||||
fn visit_terminator(&mut self,
|
||||
block: BasicBlock,
|
||||
terminator: &Terminator<'tcx>,
|
||||
location: Location)
|
||||
{
|
||||
self.source_info = terminator.source_info;
|
||||
match terminator.kind {
|
||||
TerminatorKind::Goto { .. } |
|
||||
TerminatorKind::SwitchInt { .. } |
|
||||
TerminatorKind::Drop { .. } |
|
||||
TerminatorKind::Yield { .. } |
|
||||
TerminatorKind::Assert { .. } |
|
||||
TerminatorKind::DropAndReplace { .. } |
|
||||
TerminatorKind::GeneratorDrop |
|
||||
TerminatorKind::Resume |
|
||||
TerminatorKind::Return |
|
||||
TerminatorKind::Unreachable => {
|
||||
// safe (at least as emitted during MIR construction)
|
||||
}
|
||||
|
||||
TerminatorKind::Call { ref func, .. } => {
|
||||
let func_ty = func.ty(self.mir, self.tcx);
|
||||
let sig = func_ty.fn_sig(self.tcx);
|
||||
if let hir::Unsafety::Unsafe = sig.unsafety() {
|
||||
self.require_unsafe("call to unsafe function")
|
||||
}
|
||||
}
|
||||
}
|
||||
self.super_terminator(block, terminator, location);
|
||||
}
|
||||
|
||||
fn visit_statement(&mut self,
|
||||
block: BasicBlock,
|
||||
statement: &Statement<'tcx>,
|
||||
location: Location)
|
||||
{
|
||||
self.source_info = statement.source_info;
|
||||
match statement.kind {
|
||||
StatementKind::Assign(..) |
|
||||
StatementKind::SetDiscriminant { .. } |
|
||||
StatementKind::StorageLive(..) |
|
||||
StatementKind::StorageDead(..) |
|
||||
StatementKind::EndRegion(..) |
|
||||
StatementKind::Validate(..) |
|
||||
StatementKind::Nop => {
|
||||
// safe (at least as emitted during MIR construction)
|
||||
}
|
||||
|
||||
StatementKind::InlineAsm { .. } => {
|
||||
self.require_unsafe("use of inline assembly")
|
||||
},
|
||||
}
|
||||
self.super_statement(block, statement, location);
|
||||
}
|
||||
|
||||
fn visit_rvalue(&mut self,
|
||||
rvalue: &Rvalue<'tcx>,
|
||||
location: Location)
|
||||
{
|
||||
if let &Rvalue::Aggregate(
|
||||
box AggregateKind::Closure(def_id, _),
|
||||
_
|
||||
) = rvalue {
|
||||
let unsafety_violations = self.tcx.unsafety_violations(def_id);
|
||||
self.register_violations(&unsafety_violations);
|
||||
}
|
||||
self.super_rvalue(rvalue, location);
|
||||
}
|
||||
|
||||
fn visit_lvalue(&mut self,
|
||||
lvalue: &Lvalue<'tcx>,
|
||||
context: LvalueContext<'tcx>,
|
||||
location: Location) {
|
||||
match lvalue {
|
||||
&Lvalue::Projection(box Projection {
|
||||
ref base, ref elem
|
||||
}) => {
|
||||
let old_source_info = self.source_info;
|
||||
if let &Lvalue::Local(local) = base {
|
||||
if self.mir.local_decls[local].internal {
|
||||
// Internal locals are used in the `move_val_init` desugaring.
|
||||
// We want to check unsafety against the source info of the
|
||||
// desugaring, rather than the source info of the RHS.
|
||||
self.source_info = self.mir.local_decls[local].source_info;
|
||||
}
|
||||
}
|
||||
let base_ty = base.ty(self.mir, self.tcx).to_ty(self.tcx);
|
||||
match base_ty.sty {
|
||||
ty::TyRawPtr(..) => {
|
||||
self.require_unsafe("dereference of raw pointer")
|
||||
}
|
||||
ty::TyAdt(adt, _) if adt.is_union() => {
|
||||
if context == LvalueContext::Store ||
|
||||
context == LvalueContext::Drop
|
||||
{
|
||||
let elem_ty = match elem {
|
||||
&ProjectionElem::Field(_, ty) => ty,
|
||||
_ => span_bug!(
|
||||
self.source_info.span,
|
||||
"non-field projection {:?} from union?",
|
||||
lvalue)
|
||||
};
|
||||
if elem_ty.moves_by_default(self.tcx, self.param_env,
|
||||
self.source_info.span) {
|
||||
self.require_unsafe(
|
||||
"assignment to non-`Copy` union field")
|
||||
} else {
|
||||
// write to non-move union, safe
|
||||
}
|
||||
} else {
|
||||
self.require_unsafe("access to union field")
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
self.source_info = old_source_info;
|
||||
}
|
||||
&Lvalue::Local(..) => {
|
||||
// locals are safe
|
||||
}
|
||||
&Lvalue::Static(box Static { def_id, ty: _ }) => {
|
||||
if self.is_static_mut(def_id) {
|
||||
self.require_unsafe("use of mutable static");
|
||||
} else if self.tcx.is_foreign_item(def_id) {
|
||||
let source_info = self.source_info;
|
||||
let lint_root =
|
||||
self.visibility_scope_info[source_info.scope].lint_root;
|
||||
self.register_violations(&[UnsafetyViolation {
|
||||
source_info,
|
||||
description: "use of extern static",
|
||||
lint_node_id: Some(lint_root)
|
||||
}]);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.super_lvalue(lvalue, context, location);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> UnsafetyChecker<'a, 'tcx> {
|
||||
fn is_static_mut(&self, def_id: DefId) -> bool {
|
||||
if let Some(node) = self.tcx.hir.get_if_local(def_id) {
|
||||
match node {
|
||||
Node::NodeItem(&hir::Item {
|
||||
node: hir::ItemStatic(_, hir::MutMutable, _), ..
|
||||
}) => true,
|
||||
Node::NodeForeignItem(&hir::ForeignItem {
|
||||
node: hir::ForeignItemStatic(_, mutbl), ..
|
||||
}) => mutbl,
|
||||
_ => false
|
||||
}
|
||||
} else {
|
||||
match self.tcx.describe_def(def_id) {
|
||||
Some(Def::Static(_, mutbl)) => mutbl,
|
||||
_ => false
|
||||
}
|
||||
}
|
||||
}
|
||||
fn require_unsafe(&mut self,
|
||||
description: &'static str)
|
||||
{
|
||||
let source_info = self.source_info;
|
||||
self.register_violations(&[UnsafetyViolation {
|
||||
source_info, description, lint_node_id: None
|
||||
}]);
|
||||
}
|
||||
|
||||
fn register_violations(&mut self, violations: &[UnsafetyViolation]) {
|
||||
match self.visibility_scope_info[self.source_info.scope].safety {
|
||||
Safety::Safe => {
|
||||
for violation in violations {
|
||||
if !self.violations.contains(violation) {
|
||||
self.violations.push(violation.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
Safety::BuiltinUnsafe | Safety::FnUnsafe => {}
|
||||
Safety::ExplicitUnsafe(node_id) => {
|
||||
if !violations.is_empty() {
|
||||
self.used_unsafe.insert(node_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn provide(providers: &mut Providers) {
|
||||
*providers = Providers {
|
||||
unsafety_violations,
|
||||
..*providers
|
||||
};
|
||||
}
|
||||
|
||||
struct UnusedUnsafeVisitor<'a, 'tcx: 'a> {
|
||||
tcx: TyCtxt<'a, 'tcx, 'tcx>,
|
||||
used_unsafe: FxHashSet<ast::NodeId>
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> hir::intravisit::Visitor<'tcx> for UnusedUnsafeVisitor<'a, 'tcx> {
|
||||
fn nested_visit_map<'this>(&'this mut self) ->
|
||||
hir::intravisit::NestedVisitorMap<'this, 'tcx>
|
||||
{
|
||||
hir::intravisit::NestedVisitorMap::None
|
||||
}
|
||||
|
||||
fn visit_block(&mut self, block: &'tcx hir::Block) {
|
||||
hir::intravisit::walk_block(self, block);
|
||||
|
||||
if let hir::UnsafeBlock(hir::UserProvided) = block.rules {
|
||||
if !self.used_unsafe.contains(&block.id) {
|
||||
self.report_unused_unsafe(block);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> UnusedUnsafeVisitor<'a, 'tcx> {
|
||||
/// Return the NodeId for an enclosing scope that is also `unsafe`
|
||||
fn is_enclosed(&self, id: ast::NodeId) -> Option<(String, ast::NodeId)> {
|
||||
let parent_id = self.tcx.hir.get_parent_node(id);
|
||||
if parent_id != id {
|
||||
if self.used_unsafe.contains(&parent_id) {
|
||||
Some(("block".to_string(), parent_id))
|
||||
} else if let Some(hir::map::NodeItem(&hir::Item {
|
||||
node: hir::ItemFn(_, hir::Unsafety::Unsafe, _, _, _, _),
|
||||
..
|
||||
})) = self.tcx.hir.find(parent_id) {
|
||||
Some(("fn".to_string(), parent_id))
|
||||
} else {
|
||||
self.is_enclosed(parent_id)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn report_unused_unsafe(&self, block: &'tcx hir::Block) {
|
||||
let mut db = self.tcx.struct_span_lint_node(UNUSED_UNSAFE,
|
||||
block.id,
|
||||
block.span,
|
||||
"unnecessary `unsafe` block");
|
||||
db.span_label(block.span, "unnecessary `unsafe` block");
|
||||
if let Some((kind, id)) = self.is_enclosed(block.id) {
|
||||
db.span_note(self.tcx.hir.span(id),
|
||||
&format!("because it's nested under this `unsafe` {}", kind));
|
||||
}
|
||||
db.emit();
|
||||
}
|
||||
}
|
||||
|
||||
fn check_unused_unsafe<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
|
||||
def_id: DefId,
|
||||
used_unsafe: FxHashSet<ast::NodeId>)
|
||||
{
|
||||
let body_id =
|
||||
tcx.hir.as_local_node_id(def_id).and_then(|node_id| {
|
||||
tcx.hir.maybe_body_owned_by(node_id)
|
||||
});
|
||||
|
||||
let body_id = match body_id {
|
||||
Some(body) => body,
|
||||
None => {
|
||||
debug!("check_unused_unsafe({:?}) - no body found", def_id);
|
||||
return
|
||||
}
|
||||
};
|
||||
let body = tcx.hir.body(body_id);
|
||||
debug!("check_unused_unsafe({:?}, body={:?}, used_unsafe={:?})",
|
||||
def_id, body, used_unsafe);
|
||||
|
||||
hir::intravisit::Visitor::visit_body(
|
||||
&mut UnusedUnsafeVisitor { tcx, used_unsafe },
|
||||
body);
|
||||
}
|
||||
|
||||
fn unsafety_violations<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, def_id: DefId) ->
|
||||
Rc<[UnsafetyViolation]>
|
||||
{
|
||||
debug!("unsafety_violations({:?})", def_id);
|
||||
|
||||
// NB: this borrow is valid because all the consumers of
|
||||
// `mir_const` force this.
|
||||
let mir = &tcx.mir_const(def_id).borrow();
|
||||
|
||||
let visibility_scope_info = match mir.visibility_scope_info {
|
||||
ClearOnDecode::Set(ref data) => data,
|
||||
ClearOnDecode::Clear => {
|
||||
debug!("unsafety_violations: {:?} - remote, skipping", def_id);
|
||||
return Rc::new([])
|
||||
}
|
||||
};
|
||||
|
||||
let param_env = tcx.param_env(def_id);
|
||||
let mut checker = UnsafetyChecker::new(
|
||||
mir, visibility_scope_info, tcx, param_env);
|
||||
checker.visit_mir(mir);
|
||||
|
||||
check_unused_unsafe(tcx, def_id, checker.used_unsafe);
|
||||
checker.violations.into()
|
||||
}
|
||||
|
||||
pub fn check_unsafety<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, def_id: DefId) {
|
||||
debug!("check_unsafety({:?})", def_id);
|
||||
match tcx.def_key(def_id).disambiguated_data.data {
|
||||
// closures are handled by their parent fn.
|
||||
DefPathData::ClosureExpr => return,
|
||||
_ => {}
|
||||
};
|
||||
|
||||
for &UnsafetyViolation {
|
||||
source_info, description, lint_node_id
|
||||
} in &*tcx.unsafety_violations(def_id) {
|
||||
// Report an error.
|
||||
if let Some(lint_node_id) = lint_node_id {
|
||||
tcx.lint_node(SAFE_EXTERN_STATICS,
|
||||
lint_node_id,
|
||||
source_info.span,
|
||||
&format!("{} requires unsafe function or \
|
||||
block (error E0133)", description));
|
||||
} else {
|
||||
struct_span_err!(
|
||||
tcx.sess, source_info.span, E0133,
|
||||
"{} requires unsafe function or block", description)
|
||||
.span_label(source_info.span, description)
|
||||
.emit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -26,6 +26,7 @@ use transform;
|
|||
|
||||
pub mod add_validation;
|
||||
pub mod clean_end_regions;
|
||||
pub mod check_unsafety;
|
||||
pub mod simplify_branches;
|
||||
pub mod simplify;
|
||||
pub mod erase_regions;
|
||||
|
|
@ -46,6 +47,7 @@ pub mod nll;
|
|||
|
||||
pub(crate) fn provide(providers: &mut Providers) {
|
||||
self::qualify_consts::provide(providers);
|
||||
self::check_unsafety::provide(providers);
|
||||
*providers = Providers {
|
||||
mir_keys,
|
||||
mir_const,
|
||||
|
|
@ -116,6 +118,7 @@ fn mir_validated<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, def_id: DefId) -> &'tcx
|
|||
// directly need the result or `mir_const_qualif`, so we can just force it.
|
||||
ty::queries::mir_const_qualif::force(tcx, DUMMY_SP, def_id);
|
||||
}
|
||||
ty::queries::unsafety_violations::force(tcx, DUMMY_SP, def_id);
|
||||
|
||||
let mut mir = tcx.mir_const(def_id).steal();
|
||||
transform::run_suite(tcx, source, MIR_VALIDATED, &mut mir);
|
||||
|
|
|
|||
|
|
@ -487,6 +487,16 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
|
|||
|
||||
if self.is_box_free(func) {
|
||||
self.check_box_free_inputs(mir, term, &sig, args);
|
||||
// THIS IS A TEST. TEST TEST.
|
||||
if let ClearOnDecode::Set(ref data) = mir.visibility_scope_info {
|
||||
let lint_node_id = data[term.source_info.scope].lint_root;
|
||||
tcx.struct_span_lint_node(
|
||||
::rustc::lint::builtin::TRIVIAL_NUMERIC_CASTS,
|
||||
lint_node_id,
|
||||
term.source_info.span,
|
||||
"hi I'm a lint")
|
||||
.emit();
|
||||
}
|
||||
} else {
|
||||
self.check_call_inputs(mir, term, &sig, args);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@
|
|||
// except according to those terms.
|
||||
|
||||
|
||||
fn foo(x: *const Box<isize>) -> Box<isize> {
|
||||
let y = *x; //~ ERROR dereference of raw pointer requires unsafe function or block
|
||||
unsafe fn foo(x: *const Box<isize>) -> Box<isize> {
|
||||
let y = *x; //~ ERROR cannot move out of dereference of raw pointer
|
||||
return y;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ type Foo = std::cell::RefCell<String>;
|
|||
#[cfg(target_thread_local)]
|
||||
static __KEY: std::thread::__FastLocalKeyInner<Foo> =
|
||||
std::thread::__FastLocalKeyInner::new();
|
||||
//~^^ ERROR Sync` is not satisfied
|
||||
//~^^^ ERROR Sync` is not satisfied
|
||||
|
||||
#[cfg(not(target_thread_local))]
|
||||
static __KEY: std::thread::__OsLocalKeyInner<Foo> =
|
||||
|
|
@ -25,7 +27,7 @@ fn __getit() -> std::option::Option<
|
|||
&'static std::cell::UnsafeCell<
|
||||
std::option::Option<Foo>>>
|
||||
{
|
||||
__KEY.get() //~ ERROR invocation of unsafe method requires unsafe
|
||||
__KEY.get() //~ ERROR call to unsafe function requires unsafe
|
||||
}
|
||||
|
||||
static FOO: std::thread::LocalKey<Foo> =
|
||||
|
|
|
|||
|
|
@ -42,8 +42,8 @@ fn main() {
|
|||
let mut u1 = U1 { a: 10 }; // OK
|
||||
let a = u1.a; //~ ERROR access to union field requires unsafe
|
||||
u1.a = 11; // OK
|
||||
let U1 { a } = u1; //~ ERROR matching on union field requires unsafe
|
||||
if let U1 { a: 12 } = u1 {} //~ ERROR matching on union field requires unsafe
|
||||
let U1 { a } = u1; //~ ERROR access to union field requires unsafe
|
||||
if let U1 { a: 12 } = u1 {} //~ ERROR access to union field requires unsafe
|
||||
// let U1 { .. } = u1; // OK
|
||||
|
||||
let mut u2 = U2 { a: String::from("old") }; // OK
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
|
||||
// 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.
|
||||
|
|
@ -9,7 +10,7 @@
|
|||
// except according to those terms.
|
||||
|
||||
|
||||
fn f(p: *const u8) {
|
||||
fn f(p: *mut u8) {
|
||||
*p = 0; //~ ERROR dereference of raw pointer requires unsafe function or block
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
20
src/test/compile-fail/unsafe-move-val-init.rs
Normal file
20
src/test/compile-fail/unsafe-move-val-init.rs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
// Copyright 2017 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.
|
||||
|
||||
#![feature(core_intrinsics)]
|
||||
|
||||
use std::intrinsics;
|
||||
|
||||
// `move_val_init` has an odd desugaring, check that it is still treated
|
||||
// as unsafe.
|
||||
fn main() {
|
||||
intrinsics::move_val_init(1 as *mut u32, 1);
|
||||
//~^ ERROR dereference of raw pointer requires unsafe function or block
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue