diff options
Diffstat (limited to 'compiler/rustc_passes/src')
| -rw-r--r-- | compiler/rustc_passes/src/check_attr.rs | 339 | ||||
| -rw-r--r-- | compiler/rustc_passes/src/dead.rs | 38 | ||||
| -rw-r--r-- | compiler/rustc_passes/src/diagnostic_items.rs | 18 | ||||
| -rw-r--r-- | compiler/rustc_passes/src/entry.rs | 6 | ||||
| -rw-r--r-- | compiler/rustc_passes/src/hir_id_validator.rs | 12 | ||||
| -rw-r--r-- | compiler/rustc_passes/src/intrinsicck.rs | 2 | ||||
| -rw-r--r-- | compiler/rustc_passes/src/lang_items.rs | 2 | ||||
| -rw-r--r-- | compiler/rustc_passes/src/layout_test.rs | 1 | ||||
| -rw-r--r-- | compiler/rustc_passes/src/lib.rs | 3 | ||||
| -rw-r--r-- | compiler/rustc_passes/src/liveness.rs | 254 | ||||
| -rw-r--r-- | compiler/rustc_passes/src/liveness/rwu_table.rs | 144 | ||||
| -rw-r--r-- | compiler/rustc_passes/src/naked_functions.rs | 322 | ||||
| -rw-r--r-- | compiler/rustc_passes/src/reachable.rs | 6 | ||||
| -rw-r--r-- | compiler/rustc_passes/src/stability.rs | 4 |
14 files changed, 779 insertions, 372 deletions
diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 7679582f881..73fb28e5c9a 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -78,7 +78,7 @@ impl CheckAttrVisitor<'tcx> { } else if self.tcx.sess.check_name(attr, sym::track_caller) { self.check_track_caller(&attr.span, attrs, span, target) } else if self.tcx.sess.check_name(attr, sym::doc) { - self.check_doc_alias(attr, hir_id, target) + self.check_doc_attrs(attr, hir_id, target) } else if self.tcx.sess.check_name(attr, sym::no_link) { self.check_no_link(&attr, span, target) } else if self.tcx.sess.check_name(attr, sym::export_name) { @@ -89,6 +89,8 @@ impl CheckAttrVisitor<'tcx> { self.check_allow_internal_unstable(&attr, span, target, &attrs) } else if self.tcx.sess.check_name(attr, sym::rustc_allow_const_fn_unstable) { self.check_rustc_allow_const_fn_unstable(hir_id, &attr, span, target) + } else if self.tcx.sess.check_name(attr, sym::naked) { + self.check_naked(attr, span, target) } else { // lint-only checks if self.tcx.sess.check_name(attr, sym::cold) { @@ -162,6 +164,25 @@ impl CheckAttrVisitor<'tcx> { } } + /// Checks if `#[naked]` is applied to a function definition. + fn check_naked(&self, attr: &Attribute, span: &Span, target: Target) -> bool { + match target { + Target::Fn + | Target::Method(MethodKind::Trait { body: true } | MethodKind::Inherent) => true, + _ => { + self.tcx + .sess + .struct_span_err( + attr.span, + "attribute should be applied to a function definition", + ) + .span_label(*span, "not a function definition") + .emit(); + false + } + } + } + /// Checks if a `#[track_caller]` is applied to a non-naked function. Returns `true` if valid. fn check_track_caller( &self, @@ -171,7 +192,7 @@ impl CheckAttrVisitor<'tcx> { target: Target, ) -> bool { match target { - _ if self.tcx.sess.contains_name(attrs, sym::naked) => { + _ if attrs.iter().any(|attr| attr.has_name(sym::naked)) => { struct_span_err!( self.tcx.sess, *attr_span, @@ -266,99 +287,159 @@ impl CheckAttrVisitor<'tcx> { } } - fn doc_alias_str_error(&self, meta: &NestedMetaItem) { + fn doc_attr_str_error(&self, meta: &NestedMetaItem, attr_name: &str) { self.tcx .sess .struct_span_err( meta.span(), - "doc alias attribute expects a string: #[doc(alias = \"0\")]", + &format!("doc {0} attribute expects a string: #[doc({0} = \"a\")]", attr_name), ) .emit(); } - fn check_doc_alias(&self, attr: &Attribute, hir_id: HirId, target: Target) -> bool { + fn check_doc_alias(&self, meta: &NestedMetaItem, hir_id: HirId, target: Target) -> bool { + let doc_alias = meta.value_str().map(|s| s.to_string()).unwrap_or_else(String::new); + if doc_alias.is_empty() { + self.doc_attr_str_error(meta, "alias"); + return false; + } + if let Some(c) = + doc_alias.chars().find(|&c| c == '"' || c == '\'' || (c.is_whitespace() && c != ' ')) + { + self.tcx + .sess + .struct_span_err( + meta.name_value_literal_span().unwrap_or_else(|| meta.span()), + &format!("{:?} character isn't allowed in `#[doc(alias = \"...\")]`", c,), + ) + .emit(); + return false; + } + if doc_alias.starts_with(' ') || doc_alias.ends_with(' ') { + self.tcx + .sess + .struct_span_err( + meta.name_value_literal_span().unwrap_or_else(|| meta.span()), + "`#[doc(alias = \"...\")]` cannot start or end with ' '", + ) + .emit(); + return false; + } + if let Some(err) = match target { + Target::Impl => Some("implementation block"), + Target::ForeignMod => Some("extern block"), + Target::AssocTy => { + let parent_hir_id = self.tcx.hir().get_parent_item(hir_id); + let containing_item = self.tcx.hir().expect_item(parent_hir_id); + if Target::from_item(containing_item) == Target::Impl { + Some("type alias in implementation block") + } else { + None + } + } + Target::AssocConst => { + let parent_hir_id = self.tcx.hir().get_parent_item(hir_id); + let containing_item = self.tcx.hir().expect_item(parent_hir_id); + // We can't link to trait impl's consts. + let err = "associated constant in trait implementation block"; + match containing_item.kind { + ItemKind::Impl { of_trait: Some(_), .. } => Some(err), + _ => None, + } + } + _ => None, + } { + self.tcx + .sess + .struct_span_err( + meta.span(), + &format!("`#[doc(alias = \"...\")]` isn't allowed on {}", err), + ) + .emit(); + return false; + } + true + } + + fn check_doc_keyword(&self, meta: &NestedMetaItem, hir_id: HirId) -> bool { + let doc_keyword = meta.value_str().map(|s| s.to_string()).unwrap_or_else(String::new); + if doc_keyword.is_empty() { + self.doc_attr_str_error(meta, "keyword"); + return false; + } + match self.tcx.hir().expect_item(hir_id).kind { + ItemKind::Mod(ref module) => { + if !module.item_ids.is_empty() { + self.tcx + .sess + .struct_span_err( + meta.span(), + "`#[doc(keyword = \"...\")]` can only be used on empty modules", + ) + .emit(); + return false; + } + } + _ => { + self.tcx + .sess + .struct_span_err( + meta.span(), + "`#[doc(keyword = \"...\")]` can only be used on modules", + ) + .emit(); + return false; + } + } + if !rustc_lexer::is_ident(&doc_keyword) { + self.tcx + .sess + .struct_span_err( + meta.name_value_literal_span().unwrap_or_else(|| meta.span()), + &format!("`{}` is not a valid identifier", doc_keyword), + ) + .emit(); + return false; + } + true + } + + fn check_attr_crate_level( + &self, + meta: &NestedMetaItem, + hir_id: HirId, + attr_name: &str, + ) -> bool { + if CRATE_HIR_ID == hir_id { + self.tcx + .sess + .struct_span_err( + meta.span(), + &format!( + "`#![doc({} = \"...\")]` isn't allowed as a crate level attribute", + attr_name, + ), + ) + .emit(); + return false; + } + true + } + + fn check_doc_attrs(&self, attr: &Attribute, hir_id: HirId, target: Target) -> bool { if let Some(mi) = attr.meta() { if let Some(list) = mi.meta_item_list() { for meta in list { if meta.has_name(sym::alias) { - if !meta.is_value_str() { - self.doc_alias_str_error(meta); - return false; - } - let doc_alias = - meta.value_str().map(|s| s.to_string()).unwrap_or_else(String::new); - if doc_alias.is_empty() { - self.doc_alias_str_error(meta); - return false; - } - if let Some(c) = doc_alias - .chars() - .find(|&c| c == '"' || c == '\'' || (c.is_whitespace() && c != ' ')) + if !self.check_attr_crate_level(meta, hir_id, "alias") + || !self.check_doc_alias(meta, hir_id, target) { - self.tcx - .sess - .struct_span_err( - meta.span(), - &format!( - "{:?} character isn't allowed in `#[doc(alias = \"...\")]`", - c, - ), - ) - .emit(); - return false; - } - if doc_alias.starts_with(' ') || doc_alias.ends_with(' ') { - self.tcx - .sess - .struct_span_err( - meta.span(), - "`#[doc(alias = \"...\")]` cannot start or end with ' '", - ) - .emit(); - return false; - } - if let Some(err) = match target { - Target::Impl => Some("implementation block"), - Target::ForeignMod => Some("extern block"), - Target::AssocTy => { - let parent_hir_id = self.tcx.hir().get_parent_item(hir_id); - let containing_item = self.tcx.hir().expect_item(parent_hir_id); - if Target::from_item(containing_item) == Target::Impl { - Some("type alias in implementation block") - } else { - None - } - } - Target::AssocConst => { - let parent_hir_id = self.tcx.hir().get_parent_item(hir_id); - let containing_item = self.tcx.hir().expect_item(parent_hir_id); - // We can't link to trait impl's consts. - let err = "associated constant in trait implementation block"; - match containing_item.kind { - ItemKind::Impl { of_trait: Some(_), .. } => Some(err), - _ => None, - } - } - _ => None, - } { - self.tcx - .sess - .struct_span_err( - meta.span(), - &format!("`#[doc(alias = \"...\")]` isn't allowed on {}", err), - ) - .emit(); return false; } - if CRATE_HIR_ID == hir_id { - self.tcx - .sess - .struct_span_err( - meta.span(), - "`#![doc(alias = \"...\")]` isn't allowed as a crate \ - level attribute", - ) - .emit(); + } else if meta.has_name(sym::keyword) { + if !self.check_attr_crate_level(meta, hir_id, "keyword") + || !self.check_doc_keyword(meta, hir_id) + { return false; } } @@ -464,60 +545,68 @@ impl CheckAttrVisitor<'tcx> { target: Target, item: Option<ItemLike<'_>>, ) -> bool { - if let Target::Fn | Target::Method(..) | Target::ForeignFn = target { - let mut invalid_args = vec![]; - for meta in attr.meta_item_list().expect("no meta item list") { - if let Some(LitKind::Int(val, _)) = meta.literal().map(|lit| &lit.kind) { - if let Some(ItemLike::Item(Item { - kind: ItemKind::Fn(FnSig { decl, .. }, ..), - .. - })) - | Some(ItemLike::ForeignItem(ForeignItem { - kind: ForeignItemKind::Fn(decl, ..), - .. - })) = item - { - let arg_count = decl.inputs.len() as u128; - if *val >= arg_count { - let span = meta.span(); - self.tcx - .sess - .struct_span_err(span, "index exceeds number of arguments") - .span_label( - span, - format!( - "there {} only {} argument{}", - if arg_count != 1 { "are" } else { "is" }, - arg_count, - pluralize!(arg_count) - ), - ) - .emit(); - return false; - } - } else { - bug!("should be a function item"); + let is_function = matches!(target, Target::Fn | Target::Method(..) | Target::ForeignFn); + if !is_function { + self.tcx + .sess + .struct_span_err(attr.span, "attribute should be applied to a function") + .span_label(*span, "not a function") + .emit(); + return false; + } + + let list = match attr.meta_item_list() { + // The attribute form is validated on AST. + None => return false, + Some(it) => it, + }; + + let mut invalid_args = vec![]; + for meta in list { + if let Some(LitKind::Int(val, _)) = meta.literal().map(|lit| &lit.kind) { + if let Some(ItemLike::Item(Item { + kind: ItemKind::Fn(FnSig { decl, .. }, ..), + .. + })) + | Some(ItemLike::ForeignItem(ForeignItem { + kind: ForeignItemKind::Fn(decl, ..), + .. + })) = item + { + let arg_count = decl.inputs.len() as u128; + if *val >= arg_count { + let span = meta.span(); + self.tcx + .sess + .struct_span_err(span, "index exceeds number of arguments") + .span_label( + span, + format!( + "there {} only {} argument{}", + if arg_count != 1 { "are" } else { "is" }, + arg_count, + pluralize!(arg_count) + ), + ) + .emit(); + return false; } } else { - invalid_args.push(meta.span()); + bug!("should be a function item"); } - } - if !invalid_args.is_empty() { - self.tcx - .sess - .struct_span_err(invalid_args, "arguments should be non-negative integers") - .emit(); - false } else { - true + invalid_args.push(meta.span()); } - } else { + } + + if !invalid_args.is_empty() { self.tcx .sess - .struct_span_err(attr.span, "attribute should be applied to a function") - .span_label(*span, "not a function") + .struct_span_err(invalid_args, "arguments should be non-negative integers") .emit(); false + } else { + true } } diff --git a/compiler/rustc_passes/src/dead.rs b/compiler/rustc_passes/src/dead.rs index f567dd83bc1..00152878d6d 100644 --- a/compiler/rustc_passes/src/dead.rs +++ b/compiler/rustc_passes/src/dead.rs @@ -190,7 +190,7 @@ impl<'tcx> MarkSymbolVisitor<'tcx> { intravisit::walk_item(self, &item); } - hir::ItemKind::ForeignMod(..) => {} + hir::ItemKind::ForeignMod { .. } => {} _ => { intravisit::walk_item(self, &item); } @@ -396,24 +396,6 @@ impl<'v, 'k, 'tcx> ItemLikeVisitor<'v> for LifeSeeder<'k, 'tcx> { } } } - hir::ItemKind::Trait(.., trait_item_refs) => { - for trait_item_ref in trait_item_refs { - let trait_item = self.krate.trait_item(trait_item_ref.id); - match trait_item.kind { - hir::TraitItemKind::Const(_, Some(_)) - | hir::TraitItemKind::Fn(_, hir::TraitFn::Provided(_)) => { - if has_allow_dead_code_or_lang_attr( - self.tcx, - trait_item.hir_id, - &trait_item.attrs, - ) { - self.worklist.push(trait_item.hir_id); - } - } - _ => {} - } - } - } hir::ItemKind::Impl { ref of_trait, items, .. } => { if of_trait.is_some() { self.worklist.push(item.hir_id); @@ -440,13 +422,27 @@ impl<'v, 'k, 'tcx> ItemLikeVisitor<'v> for LifeSeeder<'k, 'tcx> { } } - fn visit_trait_item(&mut self, _item: &hir::TraitItem<'_>) { - // ignore: we are handling this in `visit_item` above + fn visit_trait_item(&mut self, trait_item: &hir::TraitItem<'_>) { + use hir::TraitItemKind::{Const, Fn}; + if matches!(trait_item.kind, Const(_, Some(_)) | Fn(_, hir::TraitFn::Provided(_))) + && has_allow_dead_code_or_lang_attr(self.tcx, trait_item.hir_id, &trait_item.attrs) + { + self.worklist.push(trait_item.hir_id); + } } fn visit_impl_item(&mut self, _item: &hir::ImplItem<'_>) { // ignore: we are handling this in `visit_item` above } + + fn visit_foreign_item(&mut self, foreign_item: &hir::ForeignItem<'_>) { + use hir::ForeignItemKind::{Fn, Static}; + if matches!(foreign_item.kind, Static(..) | Fn(..)) + && has_allow_dead_code_or_lang_attr(self.tcx, foreign_item.hir_id, &foreign_item.attrs) + { + self.worklist.push(foreign_item.hir_id); + } + } } fn create_and_seed_worklist<'tcx>( diff --git a/compiler/rustc_passes/src/diagnostic_items.rs b/compiler/rustc_passes/src/diagnostic_items.rs index 0f4aa72d5c4..699c96bc49d 100644 --- a/compiler/rustc_passes/src/diagnostic_items.rs +++ b/compiler/rustc_passes/src/diagnostic_items.rs @@ -37,6 +37,10 @@ impl<'v, 'tcx> ItemLikeVisitor<'v> for DiagnosticItemCollector<'tcx> { fn visit_impl_item(&mut self, impl_item: &hir::ImplItem<'_>) { self.observe_item(&impl_item.attrs, impl_item.hir_id); } + + fn visit_foreign_item(&mut self, foreign_item: &hir::ForeignItem<'_>) { + self.observe_item(foreign_item.attrs, foreign_item.hir_id); + } } impl<'tcx> DiagnosticItemCollector<'tcx> { @@ -100,17 +104,9 @@ fn collect<'tcx>(tcx: TyCtxt<'tcx>) -> FxHashMap<Symbol, DefId> { // Collect diagnostic items in this crate. tcx.hir().krate().visit_all_item_likes(&mut collector); - // FIXME(visit_all_item_likes): Foreign items are not visited - // here, so we have to manually look at them for now. - for (_, foreign_module) in tcx.foreign_modules(LOCAL_CRATE).iter() { - for &foreign_item in foreign_module.foreign_items.iter() { - match tcx.hir().get(tcx.hir().local_def_id_to_hir_id(foreign_item.expect_local())) { - hir::Node::ForeignItem(item) => { - collector.observe_item(item.attrs, item.hir_id); - } - item => bug!("unexpected foreign item {:?}", item), - } - } + + for m in tcx.hir().krate().exported_macros { + collector.observe_item(m.attrs, m.hir_id); } collector.items diff --git a/compiler/rustc_passes/src/entry.rs b/compiler/rustc_passes/src/entry.rs index e87adb378e7..5ff631a2457 100644 --- a/compiler/rustc_passes/src/entry.rs +++ b/compiler/rustc_passes/src/entry.rs @@ -2,7 +2,7 @@ use rustc_ast::entry::EntryPointType; use rustc_errors::struct_span_err; use rustc_hir::def_id::{CrateNum, LocalDefId, CRATE_DEF_INDEX, LOCAL_CRATE}; use rustc_hir::itemlikevisit::ItemLikeVisitor; -use rustc_hir::{HirId, ImplItem, Item, ItemKind, TraitItem}; +use rustc_hir::{ForeignItem, HirId, ImplItem, Item, ItemKind, TraitItem}; use rustc_middle::hir::map::Map; use rustc_middle::ty::query::Providers; use rustc_middle::ty::TyCtxt; @@ -45,6 +45,10 @@ impl<'a, 'tcx> ItemLikeVisitor<'tcx> for EntryContext<'a, 'tcx> { fn visit_impl_item(&mut self, _impl_item: &'tcx ImplItem<'tcx>) { // Entry fn is never a trait item. } + + fn visit_foreign_item(&mut self, _: &'tcx ForeignItem<'tcx>) { + // Entry fn is never a foreign item. + } } fn entry_fn(tcx: TyCtxt<'_>, cnum: CrateNum) -> Option<(LocalDefId, EntryFnType)> { diff --git a/compiler/rustc_passes/src/hir_id_validator.rs b/compiler/rustc_passes/src/hir_id_validator.rs index 6d1a5fcc10b..fdd6c238055 100644 --- a/compiler/rustc_passes/src/hir_id_validator.rs +++ b/compiler/rustc_passes/src/hir_id_validator.rs @@ -68,6 +68,11 @@ impl<'a, 'hir> ItemLikeVisitor<'hir> for OuterVisitor<'a, 'hir> { let mut inner_visitor = self.new_inner_visitor(self.hir_map); inner_visitor.check(i.hir_id, |this| intravisit::walk_impl_item(this, i)); } + + fn visit_foreign_item(&mut self, i: &'hir hir::ForeignItem<'hir>) { + let mut inner_visitor = self.new_inner_visitor(self.hir_map); + inner_visitor.check(i.hir_id, |this| intravisit::walk_foreign_item(this, i)); + } } impl<'a, 'hir> HirIdValidator<'a, 'hir> { @@ -164,6 +169,13 @@ impl<'a, 'hir> intravisit::Visitor<'hir> for HirIdValidator<'a, 'hir> { // different owner. } + fn visit_foreign_item_ref(&mut self, _: &'hir hir::ForeignItemRef<'hir>) { + // Explicitly do nothing here. ForeignItemRefs contain hir::Visibility + // values that actually belong to an ForeignItem instead of the ItemKind::ForeignMod + // we are currently in. So for those it's correct that they have a + // different owner. + } + fn visit_generic_param(&mut self, param: &'hir hir::GenericParam<'hir>) { if let hir::GenericParamKind::Type { synthetic: Some(hir::SyntheticTyParamKind::ImplTrait), diff --git a/compiler/rustc_passes/src/intrinsicck.rs b/compiler/rustc_passes/src/intrinsicck.rs index 956be925be8..711e8e87c6c 100644 --- a/compiler/rustc_passes/src/intrinsicck.rs +++ b/compiler/rustc_passes/src/intrinsicck.rs @@ -347,7 +347,7 @@ impl ExprVisitor<'tcx> { } fn check_asm(&self, asm: &hir::InlineAsm<'tcx>) { - for (idx, op) in asm.operands.iter().enumerate() { + for (idx, (op, _op_sp)) in asm.operands.iter().enumerate() { match *op { hir::InlineAsmOperand::In { reg, ref expr } => { self.check_asm_operand_type(idx, reg, expr, asm.template, None); diff --git a/compiler/rustc_passes/src/lang_items.rs b/compiler/rustc_passes/src/lang_items.rs index 0ae0c381a11..3132661e5f5 100644 --- a/compiler/rustc_passes/src/lang_items.rs +++ b/compiler/rustc_passes/src/lang_items.rs @@ -54,6 +54,8 @@ impl ItemLikeVisitor<'v> for LanguageItemCollector<'tcx> { impl_item.attrs, ) } + + fn visit_foreign_item(&mut self, _: &hir::ForeignItem<'_>) {} } impl LanguageItemCollector<'tcx> { diff --git a/compiler/rustc_passes/src/layout_test.rs b/compiler/rustc_passes/src/layout_test.rs index 504cbbfcb76..9e83cbd6680 100644 --- a/compiler/rustc_passes/src/layout_test.rs +++ b/compiler/rustc_passes/src/layout_test.rs @@ -40,6 +40,7 @@ impl ItemLikeVisitor<'tcx> for LayoutTest<'tcx> { fn visit_trait_item(&mut self, _: &'tcx hir::TraitItem<'tcx>) {} fn visit_impl_item(&mut self, _: &'tcx hir::ImplItem<'tcx>) {} + fn visit_foreign_item(&mut self, _: &'tcx hir::ForeignItem<'tcx>) {} } impl LayoutTest<'tcx> { diff --git a/compiler/rustc_passes/src/lib.rs b/compiler/rustc_passes/src/lib.rs index c32c9c8eaa6..9759a500e06 100644 --- a/compiler/rustc_passes/src/lib.rs +++ b/compiler/rustc_passes/src/lib.rs @@ -7,6 +7,7 @@ #![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")] #![feature(const_fn)] #![feature(const_panic)] +#![feature(crate_visibility_modifier)] #![feature(in_band_lifetimes)] #![feature(nll)] #![feature(or_patterns)] @@ -32,6 +33,7 @@ pub mod layout_test; mod lib_features; mod liveness; pub mod loops; +mod naked_functions; mod reachable; mod region; pub mod stability; @@ -46,6 +48,7 @@ pub fn provide(providers: &mut Providers) { lang_items::provide(providers); lib_features::provide(providers); loops::provide(providers); + naked_functions::provide(providers); liveness::provide(providers); intrinsicck::provide(providers); reachable::provide(providers); diff --git a/compiler/rustc_passes/src/liveness.rs b/compiler/rustc_passes/src/liveness.rs index debb873beb9..a161ad16b8c 100644 --- a/compiler/rustc_passes/src/liveness.rs +++ b/compiler/rustc_passes/src/liveness.rs @@ -105,6 +105,8 @@ use std::io; use std::io::prelude::*; use std::rc::Rc; +mod rwu_table; + rustc_index::newtype_index! { pub struct Variable { DEBUG_FORMAT = "v({})", @@ -468,101 +470,6 @@ impl<'tcx> Visitor<'tcx> for IrMaps<'tcx> { // Actually we compute just a bit more than just liveness, but we use // the same basic propagation framework in all cases. -#[derive(Clone, Copy)] -struct RWU { - reader: Option<LiveNode>, - writer: Option<LiveNode>, - used: bool, -} - -/// Conceptually, this is like a `Vec<RWU>`. But the number of `RWU`s can get -/// very large, so it uses a more compact representation that takes advantage -/// of the fact that when the number of `RWU`s is large, most of them have an -/// invalid reader and an invalid writer. -struct RWUTable { - /// Each entry in `packed_rwus` is either INV_INV_FALSE, INV_INV_TRUE, or - /// an index into `unpacked_rwus`. In the common cases, this compacts the - /// 65 bits of data into 32; in the uncommon cases, it expands the 65 bits - /// in 96. - /// - /// More compact representations are possible -- e.g., use only 2 bits per - /// packed `RWU` and make the secondary table a HashMap that maps from - /// indices to `RWU`s -- but this one strikes a good balance between size - /// and speed. - packed_rwus: Vec<u32>, - unpacked_rwus: Vec<RWU>, -} - -// A constant representing `RWU { reader: None; writer: None; used: false }`. -const INV_INV_FALSE: u32 = u32::MAX; - -// A constant representing `RWU { reader: None; writer: None; used: true }`. -const INV_INV_TRUE: u32 = u32::MAX - 1; - -impl RWUTable { - fn new(num_rwus: usize) -> RWUTable { - Self { packed_rwus: vec![INV_INV_FALSE; num_rwus], unpacked_rwus: vec![] } - } - - fn get(&self, idx: usize) -> RWU { - let packed_rwu = self.packed_rwus[idx]; - match packed_rwu { - INV_INV_FALSE => RWU { reader: None, writer: None, used: false }, - INV_INV_TRUE => RWU { reader: None, writer: None, used: true }, - _ => self.unpacked_rwus[packed_rwu as usize], - } - } - - fn get_reader(&self, idx: usize) -> Option<LiveNode> { - let packed_rwu = self.packed_rwus[idx]; - match packed_rwu { - INV_INV_FALSE | INV_INV_TRUE => None, - _ => self.unpacked_rwus[packed_rwu as usize].reader, - } - } - - fn get_writer(&self, idx: usize) -> Option<LiveNode> { - let packed_rwu = self.packed_rwus[idx]; - match packed_rwu { - INV_INV_FALSE | INV_INV_TRUE => None, - _ => self.unpacked_rwus[packed_rwu as usize].writer, - } - } - - fn get_used(&self, idx: usize) -> bool { - let packed_rwu = self.packed_rwus[idx]; - match packed_rwu { - INV_INV_FALSE => false, - INV_INV_TRUE => true, - _ => self.unpacked_rwus[packed_rwu as usize].used, - } - } - - #[inline] - fn copy_packed(&mut self, dst_idx: usize, src_idx: usize) { - self.packed_rwus[dst_idx] = self.packed_rwus[src_idx]; - } - - fn assign_unpacked(&mut self, idx: usize, rwu: RWU) { - if rwu.reader == None && rwu.writer == None { - // When we overwrite an indexing entry in `self.packed_rwus` with - // `INV_INV_{TRUE,FALSE}` we don't remove the corresponding entry - // from `self.unpacked_rwus`; it's not worth the effort, and we - // can't have entries shifting around anyway. - self.packed_rwus[idx] = if rwu.used { INV_INV_TRUE } else { INV_INV_FALSE } - } else { - // Add a new RWU to `unpacked_rwus` and make `packed_rwus[idx]` - // point to it. - self.packed_rwus[idx] = self.unpacked_rwus.len() as u32; - self.unpacked_rwus.push(rwu); - } - } - - fn assign_inv_inv(&mut self, idx: usize) { - self.packed_rwus[idx] = if self.get_used(idx) { INV_INV_TRUE } else { INV_INV_FALSE }; - } -} - const ACC_READ: u32 = 1; const ACC_WRITE: u32 = 2; const ACC_USE: u32 = 4; @@ -575,7 +482,7 @@ struct Liveness<'a, 'tcx> { upvars: Option<&'tcx FxIndexMap<hir::HirId, hir::Upvar>>, closure_captures: Option<&'tcx FxIndexMap<hir::HirId, ty::UpvarId>>, successors: IndexVec<LiveNode, Option<LiveNode>>, - rwu_table: RWUTable, + rwu_table: rwu_table::RWUTable, /// A live node representing a point of execution before closure entry & /// after closure exit. Used to calculate liveness of captured variables @@ -613,7 +520,7 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> { upvars, closure_captures, successors: IndexVec::from_elem_n(None, num_live_nodes), - rwu_table: RWUTable::new(num_live_nodes * num_vars), + rwu_table: rwu_table::RWUTable::new(num_live_nodes, num_vars), closure_ln, exit_ln, break_ln: Default::default(), @@ -652,61 +559,37 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> { succ } - fn idx(&self, ln: LiveNode, var: Variable) -> usize { - ln.index() * self.ir.var_kinds.len() + var.index() - } - - fn live_on_entry(&self, ln: LiveNode, var: Variable) -> Option<LiveNodeKind> { - if let Some(reader) = self.rwu_table.get_reader(self.idx(ln, var)) { - Some(self.ir.lnks[reader]) - } else { - None - } + fn live_on_entry(&self, ln: LiveNode, var: Variable) -> bool { + self.rwu_table.get_reader(ln, var) } // Is this variable live on entry to any of its successor nodes? - fn live_on_exit(&self, ln: LiveNode, var: Variable) -> Option<LiveNodeKind> { + fn live_on_exit(&self, ln: LiveNode, var: Variable) -> bool { let successor = self.successors[ln].unwrap(); self.live_on_entry(successor, var) } fn used_on_entry(&self, ln: LiveNode, var: Variable) -> bool { - self.rwu_table.get_used(self.idx(ln, var)) + self.rwu_table.get_used(ln, var) } - fn assigned_on_entry(&self, ln: LiveNode, var: Variable) -> Option<LiveNodeKind> { - if let Some(writer) = self.rwu_table.get_writer(self.idx(ln, var)) { - Some(self.ir.lnks[writer]) - } else { - None - } + fn assigned_on_entry(&self, ln: LiveNode, var: Variable) -> bool { + self.rwu_table.get_writer(ln, var) } - fn assigned_on_exit(&self, ln: LiveNode, var: Variable) -> Option<LiveNodeKind> { + fn assigned_on_exit(&self, ln: LiveNode, var: Variable) -> bool { let successor = self.successors[ln].unwrap(); self.assigned_on_entry(successor, var) } - fn indices2<F>(&mut self, ln: LiveNode, succ_ln: LiveNode, mut op: F) - where - F: FnMut(&mut Liveness<'a, 'tcx>, usize, usize), - { - let node_base_idx = self.idx(ln, Variable::from(0u32)); - let succ_base_idx = self.idx(succ_ln, Variable::from(0u32)); - for var_idx in 0..self.ir.var_kinds.len() { - op(self, node_base_idx + var_idx, succ_base_idx + var_idx); - } - } - - fn write_vars<F>(&self, wr: &mut dyn Write, ln: LiveNode, mut test: F) -> io::Result<()> + fn write_vars<F>(&self, wr: &mut dyn Write, mut test: F) -> io::Result<()> where - F: FnMut(usize) -> bool, + F: FnMut(Variable) -> bool, { - let node_base_idx = self.idx(ln, Variable::from(0u32)); for var_idx in 0..self.ir.var_kinds.len() { - let idx = node_base_idx + var_idx; - if test(idx) { - write!(wr, " {:?}", Variable::from(var_idx))?; + let var = Variable::from(var_idx); + if test(var) { + write!(wr, " {:?}", var)?; } } Ok(()) @@ -718,11 +601,11 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> { { let wr = &mut wr as &mut dyn Write; write!(wr, "[{:?} of kind {:?} reads", ln, self.ir.lnks[ln]); - self.write_vars(wr, ln, |idx| self.rwu_table.get_reader(idx).is_some()); + self.write_vars(wr, |var| self.rwu_table.get_reader(ln, var)); write!(wr, " writes"); - self.write_vars(wr, ln, |idx| self.rwu_table.get_writer(idx).is_some()); + self.write_vars(wr, |var| self.rwu_table.get_writer(ln, var)); write!(wr, " uses"); - self.write_vars(wr, ln, |idx| self.rwu_table.get_used(idx)); + self.write_vars(wr, |var| self.rwu_table.get_used(ln, var)); write!(wr, " precedes {:?}]", self.successors[ln]); } @@ -747,100 +630,57 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> { self.successors[ln] = Some(succ_ln); // It is not necessary to initialize the RWUs here because they are all - // set to INV_INV_FALSE when they are created, and the sets only grow - // during iterations. + // empty when created, and the sets only grow during iterations. } fn init_from_succ(&mut self, ln: LiveNode, succ_ln: LiveNode) { // more efficient version of init_empty() / merge_from_succ() self.successors[ln] = Some(succ_ln); - - self.indices2(ln, succ_ln, |this, idx, succ_idx| { - this.rwu_table.copy_packed(idx, succ_idx); - }); + self.rwu_table.copy(ln, succ_ln); debug!("init_from_succ(ln={}, succ={})", self.ln_str(ln), self.ln_str(succ_ln)); } - fn merge_from_succ(&mut self, ln: LiveNode, succ_ln: LiveNode, first_merge: bool) -> bool { + fn merge_from_succ(&mut self, ln: LiveNode, succ_ln: LiveNode) -> bool { if ln == succ_ln { return false; } - let mut any_changed = false; - self.indices2(ln, succ_ln, |this, idx, succ_idx| { - // This is a special case, pulled out from the code below, where we - // don't have to do anything. It occurs about 60-70% of the time. - if this.rwu_table.packed_rwus[succ_idx] == INV_INV_FALSE { - return; - } - - let mut changed = false; - let mut rwu = this.rwu_table.get(idx); - let succ_rwu = this.rwu_table.get(succ_idx); - if succ_rwu.reader.is_some() && rwu.reader.is_none() { - rwu.reader = succ_rwu.reader; - changed = true - } - - if succ_rwu.writer.is_some() && rwu.writer.is_none() { - rwu.writer = succ_rwu.writer; - changed = true - } - - if succ_rwu.used && !rwu.used { - rwu.used = true; - changed = true; - } - - if changed { - this.rwu_table.assign_unpacked(idx, rwu); - any_changed = true; - } - }); - - debug!( - "merge_from_succ(ln={:?}, succ={}, first_merge={}, changed={})", - ln, - self.ln_str(succ_ln), - first_merge, - any_changed - ); - any_changed + let changed = self.rwu_table.union(ln, succ_ln); + debug!("merge_from_succ(ln={:?}, succ={}, changed={})", ln, self.ln_str(succ_ln), changed); + changed } // Indicates that a local variable was *defined*; we know that no // uses of the variable can precede the definition (resolve checks // this) so we just clear out all the data. fn define(&mut self, writer: LiveNode, var: Variable) { - let idx = self.idx(writer, var); - self.rwu_table.assign_inv_inv(idx); - - debug!("{:?} defines {:?} (idx={}): {}", writer, var, idx, self.ln_str(writer)); + let used = self.rwu_table.get_used(writer, var); + self.rwu_table.set(writer, var, rwu_table::RWU { reader: false, writer: false, used }); + debug!("{:?} defines {:?}: {}", writer, var, self.ln_str(writer)); } // Either read, write, or both depending on the acc bitset fn acc(&mut self, ln: LiveNode, var: Variable, acc: u32) { debug!("{:?} accesses[{:x}] {:?}: {}", ln, acc, var, self.ln_str(ln)); - let idx = self.idx(ln, var); - let mut rwu = self.rwu_table.get(idx); + let mut rwu = self.rwu_table.get(ln, var); if (acc & ACC_WRITE) != 0 { - rwu.reader = None; - rwu.writer = Some(ln); + rwu.reader = false; + rwu.writer = true; } // Important: if we both read/write, must do read second // or else the write will override. if (acc & ACC_READ) != 0 { - rwu.reader = Some(ln); + rwu.reader = true; } if (acc & ACC_USE) != 0 { rwu.used = true; } - self.rwu_table.assign_unpacked(idx, rwu); + self.rwu_table.set(ln, var, rwu); } fn compute(&mut self, body: &hir::Body<'_>, hir_id: HirId) -> LiveNode { @@ -906,7 +746,6 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> { }; // Propagate through calls to the closure. - let mut first_merge = true; loop { self.init_from_succ(self.closure_ln, succ); for param in body.params { @@ -916,10 +755,9 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> { }) } - if !self.merge_from_succ(self.exit_ln, self.closure_ln, first_merge) { + if !self.merge_from_succ(self.exit_ln, self.closure_ln) { break; } - first_merge = false; assert_eq!(succ, self.propagate_through_expr(&body.value, self.exit_ln)); } @@ -1025,7 +863,6 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> { // let ln = self.live_node(expr.hir_id, expr.span); self.init_empty(ln, succ); - let mut first_merge = true; for arm in arms { let body_succ = self.propagate_through_expr(&arm.body, succ); @@ -1034,8 +871,7 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> { body_succ, ); let arm_succ = self.define_bindings_in_pat(&arm.pat, guard_succ); - self.merge_from_succ(ln, arm_succ, first_merge); - first_merge = false; + self.merge_from_succ(ln, arm_succ); } self.propagate_through_expr(&e, ln) } @@ -1146,7 +982,7 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> { let ln = self.live_node(expr.hir_id, expr.span); self.init_from_succ(ln, succ); - self.merge_from_succ(ln, r_succ, false); + self.merge_from_succ(ln, r_succ); self.propagate_through_expr(&l, ln) } @@ -1174,7 +1010,7 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> { }; // Do a first pass for writing outputs only - for op in asm.operands.iter().rev() { + for (op, _op_sp) in asm.operands.iter().rev() { match op { hir::InlineAsmOperand::In { .. } | hir::InlineAsmOperand::Const { .. } @@ -1197,7 +1033,7 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> { // Then do a second pass for inputs let mut succ = succ; - for op in asm.operands.iter().rev() { + for (op, _op_sp) in asm.operands.iter().rev() { match op { hir::InlineAsmOperand::In { expr, .. } | hir::InlineAsmOperand::Const { expr, .. } @@ -1390,7 +1226,6 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> { */ // first iteration: - let mut first_merge = true; let ln = self.live_node(expr.hir_id, expr.span); self.init_empty(ln, succ); debug!("propagate_through_loop: using id for loop body {} {:?}", expr.hir_id, body); @@ -1402,8 +1237,7 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> { let body_ln = self.propagate_through_block(body, ln); // repeat until fixed point is reached: - while self.merge_from_succ(ln, body_ln, first_merge) { - first_merge = false; + while self.merge_from_succ(ln, body_ln) { assert_eq!(body_ln, self.propagate_through_block(body, ln)); } @@ -1454,7 +1288,7 @@ fn check_expr<'tcx>(this: &mut Liveness<'_, 'tcx>, expr: &'tcx Expr<'tcx>) { } hir::ExprKind::InlineAsm(ref asm) => { - for op in asm.operands { + for (op, _op_sp) in asm.operands { match op { hir::InlineAsmOperand::Out { expr, .. } => { if let Some(expr) = expr { @@ -1575,7 +1409,7 @@ impl<'tcx> Liveness<'_, 'tcx> { ty::UpvarCapture::ByRef(..) => continue, }; if self.used_on_entry(entry_ln, var) { - if self.live_on_entry(entry_ln, var).is_none() { + if !self.live_on_entry(entry_ln, var) { if let Some(name) = self.should_warn(var) { self.ir.tcx.struct_span_lint_hir( lint::builtin::UNUSED_ASSIGNMENTS, @@ -1609,7 +1443,7 @@ impl<'tcx> Liveness<'_, 'tcx> { fn warn_about_unused_args(&self, body: &hir::Body<'_>, entry_ln: LiveNode) { for p in body.params { self.check_unused_vars_in_pat(&p.pat, Some(entry_ln), |spans, hir_id, ln, var| { - if self.live_on_entry(ln, var).is_none() { + if !self.live_on_entry(ln, var) { self.report_unsed_assign(hir_id, spans, var, |name| { format!("value passed to `{}` is never read", name) }); @@ -1658,7 +1492,7 @@ impl<'tcx> Liveness<'_, 'tcx> { // {ret}`, there is only one node, so asking about // assigned_on_exit() is not meaningful. let is_assigned = - if ln == self.exit_ln { false } else { self.assigned_on_exit(ln, var).is_some() }; + if ln == self.exit_ln { false } else { self.assigned_on_exit(ln, var) }; if is_assigned { self.ir.tcx.struct_span_lint_hir( @@ -1725,7 +1559,7 @@ impl<'tcx> Liveness<'_, 'tcx> { } fn warn_about_dead_assign(&self, spans: Vec<Span>, hir_id: HirId, ln: LiveNode, var: Variable) { - if self.live_on_exit(ln, var).is_none() { + if !self.live_on_exit(ln, var) { self.report_unsed_assign(hir_id, spans, var, |name| { format!("value assigned to `{}` is never read", name) }); diff --git a/compiler/rustc_passes/src/liveness/rwu_table.rs b/compiler/rustc_passes/src/liveness/rwu_table.rs new file mode 100644 index 00000000000..a1a6f27398e --- /dev/null +++ b/compiler/rustc_passes/src/liveness/rwu_table.rs @@ -0,0 +1,144 @@ +use crate::liveness::{LiveNode, Variable}; + +#[derive(Clone, Copy)] +pub(super) struct RWU { + pub(super) reader: bool, + pub(super) writer: bool, + pub(super) used: bool, +} + +/// Conceptually, this is like a `Vec<Vec<RWU>>`. But the number of +/// RWU`s can get very large, so it uses a more compact representation. +pub(super) struct RWUTable { + /// Total number of live nodes. + live_nodes: usize, + /// Total number of variables. + vars: usize, + + /// A compressed representation of `RWU`s. + /// + /// Each word represents 2 different `RWU`s packed together. Each packed RWU + /// is stored in 4 bits: a reader bit, a writer bit, a used bit and a + /// padding bit. + /// + /// The data for each live node is contiguous and starts at a word boundary, + /// so there might be an unused space left. + words: Vec<u8>, + /// Number of words per each live node. + live_node_words: usize, +} + +impl RWUTable { + const RWU_READER: u8 = 0b0001; + const RWU_WRITER: u8 = 0b0010; + const RWU_USED: u8 = 0b0100; + const RWU_MASK: u8 = 0b1111; + + /// Size of packed RWU in bits. + const RWU_BITS: usize = 4; + /// Size of a word in bits. + const WORD_BITS: usize = std::mem::size_of::<u8>() * 8; + /// Number of packed RWUs that fit into a single word. + const WORD_RWU_COUNT: usize = Self::WORD_BITS / Self::RWU_BITS; + + pub(super) fn new(live_nodes: usize, vars: usize) -> RWUTable { + let live_node_words = (vars + Self::WORD_RWU_COUNT - 1) / Self::WORD_RWU_COUNT; + Self { live_nodes, vars, live_node_words, words: vec![0u8; live_node_words * live_nodes] } + } + + fn word_and_shift(&self, ln: LiveNode, var: Variable) -> (usize, u32) { + assert!(ln.index() < self.live_nodes); + assert!(var.index() < self.vars); + + let var = var.index(); + let word = var / Self::WORD_RWU_COUNT; + let shift = Self::RWU_BITS * (var % Self::WORD_RWU_COUNT); + (ln.index() * self.live_node_words + word, shift as u32) + } + + fn pick2_rows_mut(&mut self, a: LiveNode, b: LiveNode) -> (&mut [u8], &mut [u8]) { + assert!(a.index() < self.live_nodes); + assert!(b.index() < self.live_nodes); + assert!(a != b); + + let a_start = a.index() * self.live_node_words; + let b_start = b.index() * self.live_node_words; + + unsafe { + let ptr = self.words.as_mut_ptr(); + ( + std::slice::from_raw_parts_mut(ptr.add(a_start), self.live_node_words), + std::slice::from_raw_parts_mut(ptr.add(b_start), self.live_node_words), + ) + } + } + + pub(super) fn copy(&mut self, dst: LiveNode, src: LiveNode) { + if dst == src { + return; + } + + let (dst_row, src_row) = self.pick2_rows_mut(dst, src); + dst_row.copy_from_slice(src_row); + } + + /// Sets `dst` to the union of `dst` and `src`, returns true if `dst` was + /// changed. + pub(super) fn union(&mut self, dst: LiveNode, src: LiveNode) -> bool { + if dst == src { + return false; + } + + let mut changed = false; + let (dst_row, src_row) = self.pick2_rows_mut(dst, src); + for (dst_word, src_word) in dst_row.iter_mut().zip(src_row.iter()) { + let old = *dst_word; + let new = *dst_word | src_word; + *dst_word = new; + changed |= old != new; + } + changed + } + + pub(super) fn get_reader(&self, ln: LiveNode, var: Variable) -> bool { + let (word, shift) = self.word_and_shift(ln, var); + (self.words[word] >> shift) & Self::RWU_READER != 0 + } + + pub(super) fn get_writer(&self, ln: LiveNode, var: Variable) -> bool { + let (word, shift) = self.word_and_shift(ln, var); + (self.words[word] >> shift) & Self::RWU_WRITER != 0 + } + + pub(super) fn get_used(&self, ln: LiveNode, var: Variable) -> bool { + let (word, shift) = self.word_and_shift(ln, var); + (self.words[word] >> shift) & Self::RWU_USED != 0 + } + + pub(super) fn get(&self, ln: LiveNode, var: Variable) -> RWU { + let (word, shift) = self.word_and_shift(ln, var); + let rwu_packed = self.words[word] >> shift; + RWU { + reader: rwu_packed & Self::RWU_READER != 0, + writer: rwu_packed & Self::RWU_WRITER != 0, + used: rwu_packed & Self::RWU_USED != 0, + } + } + + pub(super) fn set(&mut self, ln: LiveNode, var: Variable, rwu: RWU) { + let mut packed = 0; + if rwu.reader { + packed |= Self::RWU_READER; + } + if rwu.writer { + packed |= Self::RWU_WRITER; + } + if rwu.used { + packed |= Self::RWU_USED; + } + + let (word, shift) = self.word_and_shift(ln, var); + let word = &mut self.words[word]; + *word = (*word & !(Self::RWU_MASK << shift)) | (packed << shift) + } +} diff --git a/compiler/rustc_passes/src/naked_functions.rs b/compiler/rustc_passes/src/naked_functions.rs new file mode 100644 index 00000000000..5b50ef8627b --- /dev/null +++ b/compiler/rustc_passes/src/naked_functions.rs @@ -0,0 +1,322 @@ +//! Checks validity of naked functions. + +use rustc_ast::InlineAsmOptions; +use rustc_hir as hir; +use rustc_hir::def_id::LocalDefId; +use rustc_hir::intravisit::{ErasedMap, FnKind, NestedVisitorMap, Visitor}; +use rustc_hir::{ExprKind, HirId, InlineAsmOperand, StmtKind}; +use rustc_middle::ty::query::Providers; +use rustc_middle::ty::TyCtxt; +use rustc_session::lint::builtin::UNSUPPORTED_NAKED_FUNCTIONS; +use rustc_span::symbol::sym; +use rustc_span::Span; +use rustc_target::spec::abi::Abi; + +fn check_mod_naked_functions(tcx: TyCtxt<'_>, module_def_id: LocalDefId) { + tcx.hir().visit_item_likes_in_module( + module_def_id, + &mut CheckNakedFunctions { tcx }.as_deep_visitor(), + ); +} + +crate fn provide(providers: &mut Providers) { + *providers = Providers { check_mod_naked_functions, ..*providers }; +} + +struct CheckNakedFunctions<'tcx> { + tcx: TyCtxt<'tcx>, +} + +impl<'tcx> Visitor<'tcx> for CheckNakedFunctions<'tcx> { + type Map = ErasedMap<'tcx>; + + fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> { + NestedVisitorMap::None + } + + fn visit_fn( + &mut self, + fk: FnKind<'v>, + _fd: &'tcx hir::FnDecl<'tcx>, + body_id: hir::BodyId, + span: Span, + hir_id: HirId, + ) { + let ident_span; + let fn_header; + + match fk { + FnKind::Closure(..) => { + // Closures with a naked attribute are rejected during attribute + // check. Don't validate them any further. + return; + } + FnKind::ItemFn(ident, _, ref header, ..) => { + ident_span = ident.span; + fn_header = header; + } + + FnKind::Method(ident, ref sig, ..) => { + ident_span = ident.span; + fn_header = &sig.header; + } + } + + let naked = fk.attrs().iter().any(|attr| attr.has_name(sym::naked)); + if naked { + let body = self.tcx.hir().body(body_id); + check_abi(self.tcx, hir_id, fn_header.abi, ident_span); + check_no_patterns(self.tcx, body.params); + check_no_parameters_use(self.tcx, body); + check_asm(self.tcx, hir_id, body, span); + } + } +} + +/// Checks that function uses non-Rust ABI. +fn check_abi(tcx: TyCtxt<'_>, hir_id: HirId, abi: Abi, fn_ident_span: Span) { + if abi == Abi::Rust { + tcx.struct_span_lint_hir(UNSUPPORTED_NAKED_FUNCTIONS, hir_id, fn_ident_span, |lint| { + lint.build("Rust ABI is unsupported in naked functions").emit(); + }); + } +} + +/// Checks that parameters don't use patterns. Mirrors the checks for function declarations. +fn check_no_patterns(tcx: TyCtxt<'_>, params: &[hir::Param<'_>]) { + for param in params { + match param.pat.kind { + hir::PatKind::Wild + | hir::PatKind::Binding(hir::BindingAnnotation::Unannotated, _, _, None) => {} + _ => { + tcx.sess + .struct_span_err( + param.pat.span, + "patterns not allowed in naked function parameters", + ) + .emit(); + } + } + } +} + +/// Checks that function parameters aren't used in the function body. +fn check_no_parameters_use<'tcx>(tcx: TyCtxt<'tcx>, body: &'tcx hir::Body<'tcx>) { + let mut params = hir::HirIdSet::default(); + for param in body.params { + param.pat.each_binding(|_binding_mode, hir_id, _span, _ident| { + params.insert(hir_id); + }); + } + CheckParameters { tcx, params }.visit_body(body); +} + +struct CheckParameters<'tcx> { + tcx: TyCtxt<'tcx>, + params: hir::HirIdSet, +} + +impl<'tcx> Visitor<'tcx> for CheckParameters<'tcx> { + type Map = ErasedMap<'tcx>; + + fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> { + NestedVisitorMap::None + } + + fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) { + if let hir::ExprKind::Path(hir::QPath::Resolved( + _, + hir::Path { res: hir::def::Res::Local(var_hir_id), .. }, + )) = expr.kind + { + if self.params.contains(var_hir_id) { + self.tcx + .sess + .struct_span_err( + expr.span, + "referencing function parameters is not allowed in naked functions", + ) + .help("follow the calling convention in asm block to use parameters") + .emit(); + return; + } + } + hir::intravisit::walk_expr(self, expr); + } +} + +/// Checks that function body contains a single inline assembly block. +fn check_asm<'tcx>(tcx: TyCtxt<'tcx>, hir_id: HirId, body: &'tcx hir::Body<'tcx>, fn_span: Span) { + let mut this = CheckInlineAssembly { tcx, items: Vec::new() }; + this.visit_body(body); + if let &[(ItemKind::Asm, _)] = &this.items[..] { + // Ok. + } else { + tcx.struct_span_lint_hir(UNSUPPORTED_NAKED_FUNCTIONS, hir_id, fn_span, |lint| { + let mut diag = lint.build("naked functions must contain a single asm block"); + let mut has_asm = false; + for &(kind, span) in &this.items { + match kind { + ItemKind::Asm if has_asm => { + diag.span_label( + span, + "multiple asm blocks are unsupported in naked functions", + ); + } + ItemKind::Asm => has_asm = true, + ItemKind::NonAsm => { + diag.span_label(span, "non-asm is unsupported in naked functions"); + } + } + } + diag.emit(); + }); + } +} + +struct CheckInlineAssembly<'tcx> { + tcx: TyCtxt<'tcx>, + items: Vec<(ItemKind, Span)>, +} + +#[derive(Copy, Clone)] +enum ItemKind { + Asm, + NonAsm, +} + +impl<'tcx> CheckInlineAssembly<'tcx> { + fn check_expr(&mut self, expr: &'tcx hir::Expr<'tcx>, span: Span) { + match expr.kind { + ExprKind::Box(..) + | ExprKind::ConstBlock(..) + | ExprKind::Array(..) + | ExprKind::Call(..) + | ExprKind::MethodCall(..) + | ExprKind::Tup(..) + | ExprKind::Binary(..) + | ExprKind::Unary(..) + | ExprKind::Lit(..) + | ExprKind::Cast(..) + | ExprKind::Type(..) + | ExprKind::Loop(..) + | ExprKind::Match(..) + | ExprKind::Closure(..) + | ExprKind::Assign(..) + | ExprKind::AssignOp(..) + | ExprKind::Field(..) + | ExprKind::Index(..) + | ExprKind::Path(..) + | ExprKind::AddrOf(..) + | ExprKind::Break(..) + | ExprKind::Continue(..) + | ExprKind::Ret(..) + | ExprKind::Struct(..) + | ExprKind::Repeat(..) + | ExprKind::Yield(..) => { + self.items.push((ItemKind::NonAsm, span)); + } + + ExprKind::InlineAsm(ref asm) => { + self.items.push((ItemKind::Asm, span)); + self.check_inline_asm(expr.hir_id, asm, span); + } + + ExprKind::LlvmInlineAsm(..) => { + self.items.push((ItemKind::Asm, span)); + self.tcx.struct_span_lint_hir( + UNSUPPORTED_NAKED_FUNCTIONS, + expr.hir_id, + span, + |lint| { + lint.build( + "the LLVM-style inline assembly is unsupported in naked functions", + ) + .help("use the new asm! syntax specified in RFC 2873") + .emit(); + }, + ); + } + + ExprKind::DropTemps(..) | ExprKind::Block(..) | ExprKind::Err => { + hir::intravisit::walk_expr(self, expr); + } + } + } + + fn check_inline_asm(&self, hir_id: HirId, asm: &'tcx hir::InlineAsm<'tcx>, span: Span) { + let unsupported_operands: Vec<Span> = asm + .operands + .iter() + .filter_map(|&(ref op, op_sp)| match op { + InlineAsmOperand::Const { .. } | InlineAsmOperand::Sym { .. } => None, + InlineAsmOperand::In { .. } + | InlineAsmOperand::Out { .. } + | InlineAsmOperand::InOut { .. } + | InlineAsmOperand::SplitInOut { .. } => Some(op_sp), + }) + .collect(); + if !unsupported_operands.is_empty() { + self.tcx.struct_span_lint_hir( + UNSUPPORTED_NAKED_FUNCTIONS, + hir_id, + unsupported_operands, + |lint| { + lint.build("only `const` and `sym` operands are supported in naked functions") + .emit(); + }, + ); + } + + let unsupported_options: Vec<&'static str> = [ + (InlineAsmOptions::NOMEM, "`nomem`"), + (InlineAsmOptions::NOSTACK, "`nostack`"), + (InlineAsmOptions::PRESERVES_FLAGS, "`preserves_flags`"), + (InlineAsmOptions::PURE, "`pure`"), + (InlineAsmOptions::READONLY, "`readonly`"), + ] + .iter() + .filter_map(|&(option, name)| if asm.options.contains(option) { Some(name) } else { None }) + .collect(); + + if !unsupported_options.is_empty() { + self.tcx.struct_span_lint_hir(UNSUPPORTED_NAKED_FUNCTIONS, hir_id, span, |lint| { + lint.build(&format!( + "asm options unsupported in naked functions: {}", + unsupported_options.join(", ") + )) + .emit(); + }); + } + + if !asm.options.contains(InlineAsmOptions::NORETURN) { + self.tcx.struct_span_lint_hir(UNSUPPORTED_NAKED_FUNCTIONS, hir_id, span, |lint| { + lint.build("asm in naked functions must use `noreturn` option").emit(); + }); + } + } +} + +impl<'tcx> Visitor<'tcx> for CheckInlineAssembly<'tcx> { + type Map = ErasedMap<'tcx>; + + fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> { + NestedVisitorMap::None + } + + fn visit_stmt(&mut self, stmt: &'tcx hir::Stmt<'tcx>) { + match stmt.kind { + StmtKind::Item(..) => {} + StmtKind::Local(..) => { + self.items.push((ItemKind::NonAsm, stmt.span)); + } + StmtKind::Expr(ref expr) | StmtKind::Semi(ref expr) => { + self.check_expr(expr, stmt.span); + } + } + } + + fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) { + self.check_expr(&expr, expr.span); + } +} diff --git a/compiler/rustc_passes/src/reachable.rs b/compiler/rustc_passes/src/reachable.rs index 8d5c980609c..fde83af99a5 100644 --- a/compiler/rustc_passes/src/reachable.rs +++ b/compiler/rustc_passes/src/reachable.rs @@ -262,7 +262,7 @@ impl<'tcx> ReachableContext<'tcx> { | hir::ItemKind::TyAlias(..) | hir::ItemKind::Static(..) | hir::ItemKind::Mod(..) - | hir::ItemKind::ForeignMod(..) + | hir::ItemKind::ForeignMod { .. } | hir::ItemKind::Impl { .. } | hir::ItemKind::Trait(..) | hir::ItemKind::TraitAlias(..) @@ -378,6 +378,10 @@ impl<'a, 'tcx> ItemLikeVisitor<'tcx> for CollectPrivateImplItemsVisitor<'a, 'tcx fn visit_impl_item(&mut self, _impl_item: &hir::ImplItem<'_>) { // processed in visit_item above } + + fn visit_foreign_item(&mut self, _foreign_item: &hir::ForeignItem<'_>) { + // We never export foreign functions as they have no body to export. + } } fn reachable_set<'tcx>(tcx: TyCtxt<'tcx>, crate_num: CrateNum) -> FxHashSet<LocalDefId> { diff --git a/compiler/rustc_passes/src/stability.rs b/compiler/rustc_passes/src/stability.rs index 04b5c65e464..f6bbbd80bf1 100644 --- a/compiler/rustc_passes/src/stability.rs +++ b/compiler/rustc_passes/src/stability.rs @@ -326,7 +326,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Annotator<'a, 'tcx> { // they don't have their own stability. They still can be annotated as unstable // and propagate this unstability to children, but this annotation is completely // optional. They inherit stability from their parents when unannotated. - hir::ItemKind::Impl { of_trait: None, .. } | hir::ItemKind::ForeignMod(..) => { + hir::ItemKind::Impl { of_trait: None, .. } | hir::ItemKind::ForeignMod { .. } => { self.in_trait_impl = false; kind = AnnotationKind::Container; } @@ -499,7 +499,7 @@ impl<'tcx> Visitor<'tcx> for MissingStabilityAnnotations<'tcx> { // optional. They inherit stability from their parents when unannotated. if !matches!( i.kind, - hir::ItemKind::Impl { of_trait: None, .. } | hir::ItemKind::ForeignMod(..) + hir::ItemKind::Impl { of_trait: None, .. } | hir::ItemKind::ForeignMod { .. } ) { self.check_missing_stability(i.hir_id, i.span); } |
