move unsafety checking to MIR

No functional changes intended.
This commit is contained in:
Ariel Ben-Yehuda 2017-09-19 16:20:02 +03:00
parent 8c7500f9b6
commit c72a979979
24 changed files with 662 additions and 388 deletions

View file

@ -445,6 +445,7 @@ define_dep_nodes!( <'tcx>
[] BorrowCheckKrate,
[] BorrowCheck(DefId),
[] MirBorrowCheck(DefId),
[] UnsafetyViolations(DefId),
[] RvalueCheck(DefId),
[] Reachability,

View file

@ -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

View file

@ -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,

View file

@ -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;

View file

@ -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());
}

View file

@ -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> {

View file

@ -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>,

View file

@ -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

View file

@ -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));
}
}
}

View file

@ -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()

View file

@ -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 {

View file

@ -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;

View file

@ -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
}

View file

@ -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:

View file

@ -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
},
}
}
}

View file

@ -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)]

View 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();
}
}
}

View file

@ -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);

View file

@ -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);
}

View file

@ -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;
}

View file

@ -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> =

View file

@ -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

View file

@ -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;
}

View 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
}