Rollup merge of #65105 - Mark-Simulacrum:split-librustc, r=nikomatsakis
Split out some passes from librustc This is just moving them out to librustc_passes -- I've not measured compile time or run time. I don't expect any significant impact, but this seems prudent regardless.
This commit is contained in:
commit
2c8cbcca54
11 changed files with 238 additions and 228 deletions
|
|
@ -466,66 +466,6 @@ fn main() {
|
|||
```
|
||||
"##,
|
||||
|
||||
// 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
|
||||
function `main()`. If there are multiple such functions, please rename one.
|
||||
"##,
|
||||
|
||||
E0137: r##"
|
||||
More than one function was declared with the `#[main]` attribute.
|
||||
|
||||
Erroneous code example:
|
||||
|
||||
```compile_fail,E0137
|
||||
#![feature(main)]
|
||||
|
||||
#[main]
|
||||
fn foo() {}
|
||||
|
||||
#[main]
|
||||
fn f() {} // error: multiple functions with a `#[main]` attribute
|
||||
```
|
||||
|
||||
This error indicates that the compiler found multiple functions with the
|
||||
`#[main]` attribute. This is an error because there must be a unique entry
|
||||
point into a Rust program. Example:
|
||||
|
||||
```
|
||||
#![feature(main)]
|
||||
|
||||
#[main]
|
||||
fn f() {} // ok!
|
||||
```
|
||||
"##,
|
||||
|
||||
E0138: r##"
|
||||
More than one function was declared with the `#[start]` attribute.
|
||||
|
||||
Erroneous code example:
|
||||
|
||||
```compile_fail,E0138
|
||||
#![feature(start)]
|
||||
|
||||
#[start]
|
||||
fn foo(argc: isize, argv: *const *const u8) -> isize {}
|
||||
|
||||
#[start]
|
||||
fn f(argc: isize, argv: *const *const u8) -> isize {}
|
||||
// error: multiple 'start' functions
|
||||
```
|
||||
|
||||
This error indicates that the compiler found multiple functions with the
|
||||
`#[start]` attribute. This is an error because there must be a unique entry
|
||||
point into a Rust program. Example:
|
||||
|
||||
```
|
||||
#![feature(start)]
|
||||
|
||||
#[start]
|
||||
fn foo(argc: isize, argv: *const *const u8) -> isize { 0 } // ok!
|
||||
```
|
||||
"##,
|
||||
|
||||
E0139: r##"
|
||||
#### Note: this error code is no longer emitted by the compiler.
|
||||
|
|
@ -1626,33 +1566,6 @@ It is not possible to use stability attributes outside of the standard library.
|
|||
Also, for now, it is not possible to write deprecation messages either.
|
||||
"##,
|
||||
|
||||
E0512: r##"
|
||||
Transmute with two differently sized types was attempted. Erroneous code
|
||||
example:
|
||||
|
||||
```compile_fail,E0512
|
||||
fn takes_u8(_: u8) {}
|
||||
|
||||
fn main() {
|
||||
unsafe { takes_u8(::std::mem::transmute(0u16)); }
|
||||
// error: cannot transmute between types of different sizes,
|
||||
// or dependently-sized types
|
||||
}
|
||||
```
|
||||
|
||||
Please use types with same size or use the expected type directly. Example:
|
||||
|
||||
```
|
||||
fn takes_u8(_: u8) {}
|
||||
|
||||
fn main() {
|
||||
unsafe { takes_u8(::std::mem::transmute(0i8)); } // ok!
|
||||
// or:
|
||||
unsafe { takes_u8(0u8); } // ok!
|
||||
}
|
||||
```
|
||||
"##,
|
||||
|
||||
E0517: r##"
|
||||
This error indicates that a `#[repr(..)]` attribute was placed on an
|
||||
unsupported item.
|
||||
|
|
@ -1847,84 +1760,6 @@ See [RFC 1522] for more details.
|
|||
[RFC 1522]: https://github.com/rust-lang/rfcs/blob/master/text/1522-conservative-impl-trait.md
|
||||
"##,
|
||||
|
||||
E0591: r##"
|
||||
Per [RFC 401][rfc401], if you have a function declaration `foo`:
|
||||
|
||||
```
|
||||
// For the purposes of this explanation, all of these
|
||||
// different kinds of `fn` declarations are equivalent:
|
||||
struct S;
|
||||
fn foo(x: S) { /* ... */ }
|
||||
# #[cfg(for_demonstration_only)]
|
||||
extern "C" { fn foo(x: S); }
|
||||
# #[cfg(for_demonstration_only)]
|
||||
impl S { fn foo(self) { /* ... */ } }
|
||||
```
|
||||
|
||||
the type of `foo` is **not** `fn(S)`, as one might expect.
|
||||
Rather, it is a unique, zero-sized marker type written here as `typeof(foo)`.
|
||||
However, `typeof(foo)` can be _coerced_ to a function pointer `fn(S)`,
|
||||
so you rarely notice this:
|
||||
|
||||
```
|
||||
# struct S;
|
||||
# fn foo(_: S) {}
|
||||
let x: fn(S) = foo; // OK, coerces
|
||||
```
|
||||
|
||||
The reason that this matter is that the type `fn(S)` is not specific to
|
||||
any particular function: it's a function _pointer_. So calling `x()` results
|
||||
in a virtual call, whereas `foo()` is statically dispatched, because the type
|
||||
of `foo` tells us precisely what function is being called.
|
||||
|
||||
As noted above, coercions mean that most code doesn't have to be
|
||||
concerned with this distinction. However, you can tell the difference
|
||||
when using **transmute** to convert a fn item into a fn pointer.
|
||||
|
||||
This is sometimes done as part of an FFI:
|
||||
|
||||
```compile_fail,E0591
|
||||
extern "C" fn foo(userdata: Box<i32>) {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
# fn callback(_: extern "C" fn(*mut i32)) {}
|
||||
# use std::mem::transmute;
|
||||
# unsafe {
|
||||
let f: extern "C" fn(*mut i32) = transmute(foo);
|
||||
callback(f);
|
||||
# }
|
||||
```
|
||||
|
||||
Here, transmute is being used to convert the types of the fn arguments.
|
||||
This pattern is incorrect because, because the type of `foo` is a function
|
||||
**item** (`typeof(foo)`), which is zero-sized, and the target type (`fn()`)
|
||||
is a function pointer, which is not zero-sized.
|
||||
This pattern should be rewritten. There are a few possible ways to do this:
|
||||
|
||||
- change the original fn declaration to match the expected signature,
|
||||
and do the cast in the fn body (the preferred option)
|
||||
- cast the fn item fo a fn pointer before calling transmute, as shown here:
|
||||
|
||||
```
|
||||
# extern "C" fn foo(_: Box<i32>) {}
|
||||
# use std::mem::transmute;
|
||||
# unsafe {
|
||||
let f: extern "C" fn(*mut i32) = transmute(foo as extern "C" fn(_));
|
||||
let f: extern "C" fn(*mut i32) = transmute(foo as usize); // works too
|
||||
# }
|
||||
```
|
||||
|
||||
The same applies to transmutes to `*mut fn()`, which were observed in practice.
|
||||
Note though that use of this type is generally incorrect.
|
||||
The intention is typically to describe a function pointer, but just `fn()`
|
||||
alone suffices for that. `*mut fn()` is a pointer to a fn pointer.
|
||||
(Since these values are typically just passed to C code, however, this rarely
|
||||
makes a difference in practice.)
|
||||
|
||||
[rfc401]: https://github.com/rust-lang/rfcs/blob/master/text/0401-coercions.md
|
||||
"##,
|
||||
|
||||
E0593: r##"
|
||||
You tried to supply an `Fn`-based type with an incorrect number of arguments
|
||||
than what was expected.
|
||||
|
|
@ -1941,21 +1776,6 @@ fn main() {
|
|||
```
|
||||
"##,
|
||||
|
||||
E0601: r##"
|
||||
No `main` function was found in a binary crate. To fix this error, add a
|
||||
`main` function. For example:
|
||||
|
||||
```
|
||||
fn main() {
|
||||
// Your program will start here.
|
||||
println!("Hello world!");
|
||||
}
|
||||
```
|
||||
|
||||
If you don't know the basics of Rust, you can go look to the Rust Book to get
|
||||
started: https://doc.rust-lang.org/book/
|
||||
"##,
|
||||
|
||||
E0602: r##"
|
||||
An unknown lint was used on the command line.
|
||||
|
||||
|
|
|
|||
|
|
@ -101,16 +101,12 @@ pub mod lint;
|
|||
pub mod middle {
|
||||
pub mod expr_use_visitor;
|
||||
pub mod cstore;
|
||||
pub mod dead;
|
||||
pub mod dependency_format;
|
||||
pub mod diagnostic_items;
|
||||
pub mod entry;
|
||||
pub mod exported_symbols;
|
||||
pub mod free_region;
|
||||
pub mod intrinsicck;
|
||||
pub mod lib_features;
|
||||
pub mod lang_items;
|
||||
pub mod liveness;
|
||||
pub mod mem_categorization;
|
||||
pub mod privacy;
|
||||
pub mod reachable;
|
||||
|
|
|
|||
|
|
@ -1,676 +0,0 @@
|
|||
// This implements the dead-code warning pass. It follows middle::reachable
|
||||
// closely. The idea is that all reachable symbols are live, codes called
|
||||
// from live codes are live, and everything else is dead.
|
||||
|
||||
use crate::hir::Node;
|
||||
use crate::hir::{self, PatKind, TyKind};
|
||||
use crate::hir::intravisit::{self, Visitor, NestedVisitorMap};
|
||||
use crate::hir::itemlikevisit::ItemLikeVisitor;
|
||||
|
||||
use crate::hir::def::{CtorOf, Res, DefKind};
|
||||
use crate::hir::CodegenFnAttrFlags;
|
||||
use crate::hir::def_id::{DefId, LOCAL_CRATE};
|
||||
use crate::lint;
|
||||
use crate::middle::privacy;
|
||||
use crate::ty::{self, DefIdTree, TyCtxt};
|
||||
use crate::util::nodemap::FxHashSet;
|
||||
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
|
||||
use syntax::{ast, attr};
|
||||
use syntax::symbol::sym;
|
||||
use syntax_pos;
|
||||
|
||||
// Any local node that may call something in its body block should be
|
||||
// explored. For example, if it's a live Node::Item that is a
|
||||
// function, then we should explore its block to check for codes that
|
||||
// may need to be marked as live.
|
||||
fn should_explore(tcx: TyCtxt<'_>, hir_id: hir::HirId) -> bool {
|
||||
match tcx.hir().find(hir_id) {
|
||||
Some(Node::Item(..)) |
|
||||
Some(Node::ImplItem(..)) |
|
||||
Some(Node::ForeignItem(..)) |
|
||||
Some(Node::TraitItem(..)) |
|
||||
Some(Node::Variant(..)) |
|
||||
Some(Node::AnonConst(..)) |
|
||||
Some(Node::Pat(..)) => true,
|
||||
_ => false
|
||||
}
|
||||
}
|
||||
|
||||
struct MarkSymbolVisitor<'a, 'tcx> {
|
||||
worklist: Vec<hir::HirId>,
|
||||
tcx: TyCtxt<'tcx>,
|
||||
tables: &'a ty::TypeckTables<'tcx>,
|
||||
live_symbols: FxHashSet<hir::HirId>,
|
||||
repr_has_repr_c: bool,
|
||||
in_pat: bool,
|
||||
inherited_pub_visibility: bool,
|
||||
ignore_variant_stack: Vec<DefId>,
|
||||
// maps from tuple struct constructors to tuple struct items
|
||||
struct_constructors: FxHashMap<hir::HirId, hir::HirId>,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> MarkSymbolVisitor<'a, 'tcx> {
|
||||
fn check_def_id(&mut self, def_id: DefId) {
|
||||
if let Some(hir_id) = self.tcx.hir().as_local_hir_id(def_id) {
|
||||
if should_explore(self.tcx, hir_id) || self.struct_constructors.contains_key(&hir_id) {
|
||||
self.worklist.push(hir_id);
|
||||
}
|
||||
self.live_symbols.insert(hir_id);
|
||||
}
|
||||
}
|
||||
|
||||
fn insert_def_id(&mut self, def_id: DefId) {
|
||||
if let Some(hir_id) = self.tcx.hir().as_local_hir_id(def_id) {
|
||||
debug_assert!(!should_explore(self.tcx, hir_id));
|
||||
self.live_symbols.insert(hir_id);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_res(&mut self, res: Res) {
|
||||
match res {
|
||||
Res::Def(DefKind::Const, _)
|
||||
| Res::Def(DefKind::AssocConst, _)
|
||||
| Res::Def(DefKind::TyAlias, _) => {
|
||||
self.check_def_id(res.def_id());
|
||||
}
|
||||
_ if self.in_pat => {},
|
||||
Res::PrimTy(..) | Res::SelfCtor(..) |
|
||||
Res::Local(..) => {}
|
||||
Res::Def(DefKind::Ctor(CtorOf::Variant, ..), ctor_def_id) => {
|
||||
let variant_id = self.tcx.parent(ctor_def_id).unwrap();
|
||||
let enum_id = self.tcx.parent(variant_id).unwrap();
|
||||
self.check_def_id(enum_id);
|
||||
if !self.ignore_variant_stack.contains(&ctor_def_id) {
|
||||
self.check_def_id(variant_id);
|
||||
}
|
||||
}
|
||||
Res::Def(DefKind::Variant, variant_id) => {
|
||||
let enum_id = self.tcx.parent(variant_id).unwrap();
|
||||
self.check_def_id(enum_id);
|
||||
if !self.ignore_variant_stack.contains(&variant_id) {
|
||||
self.check_def_id(variant_id);
|
||||
}
|
||||
}
|
||||
Res::SelfTy(t, i) => {
|
||||
if let Some(t) = t {
|
||||
self.check_def_id(t);
|
||||
}
|
||||
if let Some(i) = i {
|
||||
self.check_def_id(i);
|
||||
}
|
||||
}
|
||||
Res::ToolMod | Res::NonMacroAttr(..) | Res::Err => {}
|
||||
_ => {
|
||||
self.check_def_id(res.def_id());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn lookup_and_handle_method(&mut self, id: hir::HirId) {
|
||||
if let Some(def_id) = self.tables.type_dependent_def_id(id) {
|
||||
self.check_def_id(def_id);
|
||||
} else {
|
||||
bug!("no type-dependent def for method");
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_field_access(&mut self, lhs: &hir::Expr, hir_id: hir::HirId) {
|
||||
match self.tables.expr_ty_adjusted(lhs).kind {
|
||||
ty::Adt(def, _) => {
|
||||
let index = self.tcx.field_index(hir_id, self.tables);
|
||||
self.insert_def_id(def.non_enum_variant().fields[index].did);
|
||||
}
|
||||
ty::Tuple(..) => {}
|
||||
_ => span_bug!(lhs.span, "named field access on non-ADT"),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_field_pattern_match(&mut self, lhs: &hir::Pat, res: Res, pats: &[hir::FieldPat]) {
|
||||
let variant = match self.tables.node_type(lhs.hir_id).kind {
|
||||
ty::Adt(adt, _) => adt.variant_of_res(res),
|
||||
_ => span_bug!(lhs.span, "non-ADT in struct pattern")
|
||||
};
|
||||
for pat in pats {
|
||||
if let PatKind::Wild = pat.pat.kind {
|
||||
continue;
|
||||
}
|
||||
let index = self.tcx.field_index(pat.hir_id, self.tables);
|
||||
self.insert_def_id(variant.fields[index].did);
|
||||
}
|
||||
}
|
||||
|
||||
fn mark_live_symbols(&mut self) {
|
||||
let mut scanned = FxHashSet::default();
|
||||
while let Some(id) = self.worklist.pop() {
|
||||
if !scanned.insert(id) {
|
||||
continue
|
||||
}
|
||||
|
||||
// in the case of tuple struct constructors we want to check the item, not the generated
|
||||
// tuple struct constructor function
|
||||
let id = self.struct_constructors.get(&id).cloned().unwrap_or(id);
|
||||
|
||||
if let Some(node) = self.tcx.hir().find(id) {
|
||||
self.live_symbols.insert(id);
|
||||
self.visit_node(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_node(&mut self, node: Node<'tcx>) {
|
||||
let had_repr_c = self.repr_has_repr_c;
|
||||
self.repr_has_repr_c = false;
|
||||
let had_inherited_pub_visibility = self.inherited_pub_visibility;
|
||||
self.inherited_pub_visibility = false;
|
||||
match node {
|
||||
Node::Item(item) => {
|
||||
match item.kind {
|
||||
hir::ItemKind::Struct(..) | hir::ItemKind::Union(..) => {
|
||||
let def_id = self.tcx.hir().local_def_id(item.hir_id);
|
||||
let def = self.tcx.adt_def(def_id);
|
||||
self.repr_has_repr_c = def.repr.c();
|
||||
|
||||
intravisit::walk_item(self, &item);
|
||||
}
|
||||
hir::ItemKind::Enum(..) => {
|
||||
self.inherited_pub_visibility = item.vis.node.is_pub();
|
||||
|
||||
intravisit::walk_item(self, &item);
|
||||
}
|
||||
hir::ItemKind::ForeignMod(..) => {}
|
||||
_ => {
|
||||
intravisit::walk_item(self, &item);
|
||||
}
|
||||
}
|
||||
}
|
||||
Node::TraitItem(trait_item) => {
|
||||
intravisit::walk_trait_item(self, trait_item);
|
||||
}
|
||||
Node::ImplItem(impl_item) => {
|
||||
intravisit::walk_impl_item(self, impl_item);
|
||||
}
|
||||
Node::ForeignItem(foreign_item) => {
|
||||
intravisit::walk_foreign_item(self, &foreign_item);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
self.repr_has_repr_c = had_repr_c;
|
||||
self.inherited_pub_visibility = had_inherited_pub_visibility;
|
||||
}
|
||||
|
||||
fn mark_as_used_if_union(&mut self, adt: &ty::AdtDef, fields: &hir::HirVec<hir::Field>) {
|
||||
if adt.is_union() && adt.non_enum_variant().fields.len() > 1 && adt.did.is_local() {
|
||||
for field in fields {
|
||||
let index = self.tcx.field_index(field.hir_id, self.tables);
|
||||
self.insert_def_id(adt.non_enum_variant().fields[index].did);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> Visitor<'tcx> for MarkSymbolVisitor<'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;
|
||||
self.tables = self.tcx.body_tables(body);
|
||||
let body = self.tcx.hir().body(body);
|
||||
self.visit_body(body);
|
||||
self.tables = old_tables;
|
||||
}
|
||||
|
||||
fn visit_variant_data(&mut self, def: &'tcx hir::VariantData, _: ast::Name,
|
||||
_: &hir::Generics, _: hir::HirId, _: syntax_pos::Span) {
|
||||
let has_repr_c = self.repr_has_repr_c;
|
||||
let inherited_pub_visibility = self.inherited_pub_visibility;
|
||||
let live_fields = def.fields().iter().filter(|f| {
|
||||
has_repr_c || inherited_pub_visibility || f.vis.node.is_pub()
|
||||
});
|
||||
self.live_symbols.extend(live_fields.map(|f| f.hir_id));
|
||||
|
||||
intravisit::walk_struct_def(self, def);
|
||||
}
|
||||
|
||||
fn visit_expr(&mut self, expr: &'tcx hir::Expr) {
|
||||
match expr.kind {
|
||||
hir::ExprKind::Path(ref qpath @ hir::QPath::TypeRelative(..)) => {
|
||||
let res = self.tables.qpath_res(qpath, expr.hir_id);
|
||||
self.handle_res(res);
|
||||
}
|
||||
hir::ExprKind::MethodCall(..) => {
|
||||
self.lookup_and_handle_method(expr.hir_id);
|
||||
}
|
||||
hir::ExprKind::Field(ref lhs, ..) => {
|
||||
self.handle_field_access(&lhs, expr.hir_id);
|
||||
}
|
||||
hir::ExprKind::Struct(_, ref fields, _) => {
|
||||
if let ty::Adt(ref adt, _) = self.tables.expr_ty(expr).kind {
|
||||
self.mark_as_used_if_union(adt, fields);
|
||||
}
|
||||
}
|
||||
_ => ()
|
||||
}
|
||||
|
||||
intravisit::walk_expr(self, expr);
|
||||
}
|
||||
|
||||
fn visit_arm(&mut self, arm: &'tcx hir::Arm) {
|
||||
// Inside the body, ignore constructions of variants
|
||||
// necessary for the pattern to match. Those construction sites
|
||||
// can't be reached unless the variant is constructed elsewhere.
|
||||
let len = self.ignore_variant_stack.len();
|
||||
self.ignore_variant_stack.extend(arm.pat.necessary_variants());
|
||||
intravisit::walk_arm(self, arm);
|
||||
self.ignore_variant_stack.truncate(len);
|
||||
}
|
||||
|
||||
fn visit_pat(&mut self, pat: &'tcx hir::Pat) {
|
||||
match pat.kind {
|
||||
PatKind::Struct(ref path, ref fields, _) => {
|
||||
let res = self.tables.qpath_res(path, pat.hir_id);
|
||||
self.handle_field_pattern_match(pat, res, fields);
|
||||
}
|
||||
PatKind::Path(ref qpath) => {
|
||||
let res = self.tables.qpath_res(qpath, pat.hir_id);
|
||||
self.handle_res(res);
|
||||
}
|
||||
_ => ()
|
||||
}
|
||||
|
||||
self.in_pat = true;
|
||||
intravisit::walk_pat(self, pat);
|
||||
self.in_pat = false;
|
||||
}
|
||||
|
||||
fn visit_path(&mut self, path: &'tcx hir::Path, _: hir::HirId) {
|
||||
self.handle_res(path.res);
|
||||
intravisit::walk_path(self, path);
|
||||
}
|
||||
|
||||
fn visit_ty(&mut self, ty: &'tcx hir::Ty) {
|
||||
match ty.kind {
|
||||
TyKind::Def(item_id, _) => {
|
||||
let item = self.tcx.hir().expect_item(item_id.id);
|
||||
intravisit::walk_item(self, item);
|
||||
}
|
||||
_ => ()
|
||||
}
|
||||
intravisit::walk_ty(self, ty);
|
||||
}
|
||||
|
||||
fn visit_anon_const(&mut self, c: &'tcx hir::AnonConst) {
|
||||
self.live_symbols.insert(c.hir_id);
|
||||
intravisit::walk_anon_const(self, c);
|
||||
}
|
||||
}
|
||||
|
||||
fn has_allow_dead_code_or_lang_attr(
|
||||
tcx: TyCtxt<'_>,
|
||||
id: hir::HirId,
|
||||
attrs: &[ast::Attribute],
|
||||
) -> bool {
|
||||
if attr::contains_name(attrs, sym::lang) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Stable attribute for #[lang = "panic_impl"]
|
||||
if attr::contains_name(attrs, sym::panic_handler) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// (To be) stable attribute for #[lang = "oom"]
|
||||
if attr::contains_name(attrs, sym::alloc_error_handler) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let def_id = tcx.hir().local_def_id(id);
|
||||
let cg_attrs = tcx.codegen_fn_attrs(def_id);
|
||||
|
||||
// #[used], #[no_mangle], #[export_name], etc also keeps the item alive
|
||||
// forcefully, e.g., for placing it in a specific section.
|
||||
if cg_attrs.contains_extern_indicator() ||
|
||||
cg_attrs.flags.contains(CodegenFnAttrFlags::USED) {
|
||||
return true;
|
||||
}
|
||||
|
||||
tcx.lint_level_at_node(lint::builtin::DEAD_CODE, id).0 == lint::Allow
|
||||
}
|
||||
|
||||
// This visitor seeds items that
|
||||
// 1) We want to explicitly consider as live:
|
||||
// * Item annotated with #[allow(dead_code)]
|
||||
// - This is done so that if we want to suppress warnings for a
|
||||
// group of dead functions, we only have to annotate the "root".
|
||||
// For example, if both `f` and `g` are dead and `f` calls `g`,
|
||||
// then annotating `f` with `#[allow(dead_code)]` will suppress
|
||||
// warning for both `f` and `g`.
|
||||
// * Item annotated with #[lang=".."]
|
||||
// - This is because lang items are always callable from elsewhere.
|
||||
// or
|
||||
// 2) We are not sure to be live or not
|
||||
// * Implementation of a trait method
|
||||
struct LifeSeeder<'k, 'tcx> {
|
||||
worklist: Vec<hir::HirId>,
|
||||
krate: &'k hir::Crate,
|
||||
tcx: TyCtxt<'tcx>,
|
||||
// see `MarkSymbolVisitor::struct_constructors`
|
||||
struct_constructors: FxHashMap<hir::HirId, hir::HirId>,
|
||||
}
|
||||
|
||||
impl<'v, 'k, 'tcx> ItemLikeVisitor<'v> for LifeSeeder<'k, 'tcx> {
|
||||
fn visit_item(&mut self, item: &hir::Item) {
|
||||
let allow_dead_code = has_allow_dead_code_or_lang_attr(self.tcx,
|
||||
item.hir_id,
|
||||
&item.attrs);
|
||||
if allow_dead_code {
|
||||
self.worklist.push(item.hir_id);
|
||||
}
|
||||
match item.kind {
|
||||
hir::ItemKind::Enum(ref enum_def, _) => {
|
||||
if allow_dead_code {
|
||||
self.worklist.extend(enum_def.variants.iter().map(|variant| variant.id));
|
||||
}
|
||||
|
||||
for variant in &enum_def.variants {
|
||||
if let Some(ctor_hir_id) = variant.data.ctor_hir_id() {
|
||||
self.struct_constructors.insert(ctor_hir_id, variant.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
hir::ItemKind::Trait(.., ref 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::Method(_, hir::TraitMethod::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 opt_trait, _, ref impl_item_refs) => {
|
||||
for impl_item_ref in impl_item_refs {
|
||||
let impl_item = self.krate.impl_item(impl_item_ref.id);
|
||||
if opt_trait.is_some() ||
|
||||
has_allow_dead_code_or_lang_attr(self.tcx,
|
||||
impl_item.hir_id,
|
||||
&impl_item.attrs) {
|
||||
self.worklist.push(impl_item_ref.id.hir_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
hir::ItemKind::Struct(ref variant_data, _) => {
|
||||
if let Some(ctor_hir_id) = variant_data.ctor_hir_id() {
|
||||
self.struct_constructors.insert(ctor_hir_id, item.hir_id);
|
||||
}
|
||||
}
|
||||
_ => ()
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_trait_item(&mut self, _item: &hir::TraitItem) {
|
||||
// ignore: we are handling this in `visit_item` above
|
||||
}
|
||||
|
||||
fn visit_impl_item(&mut self, _item: &hir::ImplItem) {
|
||||
// ignore: we are handling this in `visit_item` above
|
||||
}
|
||||
}
|
||||
|
||||
fn create_and_seed_worklist<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
access_levels: &privacy::AccessLevels,
|
||||
krate: &hir::Crate,
|
||||
) -> (Vec<hir::HirId>, FxHashMap<hir::HirId, hir::HirId>) {
|
||||
let worklist = access_levels.map.iter().filter_map(|(&id, level)| {
|
||||
if level >= &privacy::AccessLevel::Reachable {
|
||||
Some(id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}).chain(
|
||||
// Seed entry point
|
||||
tcx.entry_fn(LOCAL_CRATE).map(|(def_id, _)| tcx.hir().as_local_hir_id(def_id).unwrap())
|
||||
).collect::<Vec<_>>();
|
||||
|
||||
// Seed implemented trait items
|
||||
let mut life_seeder = LifeSeeder {
|
||||
worklist,
|
||||
krate,
|
||||
tcx,
|
||||
struct_constructors: Default::default(),
|
||||
};
|
||||
krate.visit_all_item_likes(&mut life_seeder);
|
||||
|
||||
(life_seeder.worklist, life_seeder.struct_constructors)
|
||||
}
|
||||
|
||||
fn find_live<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
access_levels: &privacy::AccessLevels,
|
||||
krate: &hir::Crate,
|
||||
) -> FxHashSet<hir::HirId> {
|
||||
let (worklist, struct_constructors) = create_and_seed_worklist(tcx, access_levels, krate);
|
||||
let mut symbol_visitor = MarkSymbolVisitor {
|
||||
worklist,
|
||||
tcx,
|
||||
tables: &ty::TypeckTables::empty(None),
|
||||
live_symbols: Default::default(),
|
||||
repr_has_repr_c: false,
|
||||
in_pat: false,
|
||||
inherited_pub_visibility: false,
|
||||
ignore_variant_stack: vec![],
|
||||
struct_constructors,
|
||||
};
|
||||
symbol_visitor.mark_live_symbols();
|
||||
symbol_visitor.live_symbols
|
||||
}
|
||||
|
||||
struct DeadVisitor<'tcx> {
|
||||
tcx: TyCtxt<'tcx>,
|
||||
live_symbols: FxHashSet<hir::HirId>,
|
||||
}
|
||||
|
||||
impl DeadVisitor<'tcx> {
|
||||
fn should_warn_about_item(&mut self, item: &hir::Item) -> bool {
|
||||
let should_warn = match item.kind {
|
||||
hir::ItemKind::Static(..)
|
||||
| hir::ItemKind::Const(..)
|
||||
| hir::ItemKind::Fn(..)
|
||||
| hir::ItemKind::TyAlias(..)
|
||||
| hir::ItemKind::Enum(..)
|
||||
| hir::ItemKind::Struct(..)
|
||||
| hir::ItemKind::Union(..) => true,
|
||||
_ => false
|
||||
};
|
||||
should_warn && !self.symbol_is_live(item.hir_id)
|
||||
}
|
||||
|
||||
fn should_warn_about_field(&mut self, field: &hir::StructField) -> bool {
|
||||
let field_type = self.tcx.type_of(self.tcx.hir().local_def_id(field.hir_id));
|
||||
!field.is_positional()
|
||||
&& !self.symbol_is_live(field.hir_id)
|
||||
&& !field_type.is_phantom_data()
|
||||
&& !has_allow_dead_code_or_lang_attr(self.tcx, field.hir_id, &field.attrs)
|
||||
}
|
||||
|
||||
fn should_warn_about_variant(&mut self, variant: &hir::Variant) -> bool {
|
||||
!self.symbol_is_live(variant.id)
|
||||
&& !has_allow_dead_code_or_lang_attr(self.tcx,
|
||||
variant.id,
|
||||
&variant.attrs)
|
||||
}
|
||||
|
||||
fn should_warn_about_foreign_item(&mut self, fi: &hir::ForeignItem) -> bool {
|
||||
!self.symbol_is_live(fi.hir_id)
|
||||
&& !has_allow_dead_code_or_lang_attr(self.tcx, fi.hir_id, &fi.attrs)
|
||||
}
|
||||
|
||||
// id := HIR id of an item's definition.
|
||||
fn symbol_is_live(
|
||||
&mut self,
|
||||
id: hir::HirId,
|
||||
) -> bool {
|
||||
if self.live_symbols.contains(&id) {
|
||||
return true;
|
||||
}
|
||||
// If it's a type whose items are live, then it's live, too.
|
||||
// This is done to handle the case where, for example, the static
|
||||
// method of a private type is used, but the type itself is never
|
||||
// called directly.
|
||||
let def_id = self.tcx.hir().local_def_id(id);
|
||||
let inherent_impls = self.tcx.inherent_impls(def_id);
|
||||
for &impl_did in inherent_impls.iter() {
|
||||
for &item_did in &self.tcx.associated_item_def_ids(impl_did)[..] {
|
||||
if let Some(item_hir_id) = self.tcx.hir().as_local_hir_id(item_did) {
|
||||
if self.live_symbols.contains(&item_hir_id) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn warn_dead_code(&mut self,
|
||||
id: hir::HirId,
|
||||
span: syntax_pos::Span,
|
||||
name: ast::Name,
|
||||
node_type: &str,
|
||||
participle: &str) {
|
||||
if !name.as_str().starts_with("_") {
|
||||
self.tcx
|
||||
.lint_hir(lint::builtin::DEAD_CODE,
|
||||
id,
|
||||
span,
|
||||
&format!("{} is never {}: `{}`",
|
||||
node_type, participle, name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Visitor<'tcx> for DeadVisitor<'tcx> {
|
||||
/// Walk nested items in place so that we don't report dead-code
|
||||
/// on inner functions when the outer function is already getting
|
||||
/// an error. We could do this also by checking the parents, but
|
||||
/// this is how the code is setup and it seems harmless enough.
|
||||
fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
|
||||
NestedVisitorMap::All(&self.tcx.hir())
|
||||
}
|
||||
|
||||
fn visit_item(&mut self, item: &'tcx hir::Item) {
|
||||
if self.should_warn_about_item(item) {
|
||||
// For items that have a definition with a signature followed by a
|
||||
// block, point only at the signature.
|
||||
let span = match item.kind {
|
||||
hir::ItemKind::Fn(..) |
|
||||
hir::ItemKind::Mod(..) |
|
||||
hir::ItemKind::Enum(..) |
|
||||
hir::ItemKind::Struct(..) |
|
||||
hir::ItemKind::Union(..) |
|
||||
hir::ItemKind::Trait(..) |
|
||||
hir::ItemKind::Impl(..) => self.tcx.sess.source_map().def_span(item.span),
|
||||
_ => item.span,
|
||||
};
|
||||
let participle = match item.kind {
|
||||
hir::ItemKind::Struct(..) => "constructed", // Issue #52325
|
||||
_ => "used"
|
||||
};
|
||||
self.warn_dead_code(
|
||||
item.hir_id,
|
||||
span,
|
||||
item.ident.name,
|
||||
item.kind.descriptive_variant(),
|
||||
participle,
|
||||
);
|
||||
} else {
|
||||
// Only continue if we didn't warn
|
||||
intravisit::walk_item(self, item);
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_variant(&mut self,
|
||||
variant: &'tcx hir::Variant,
|
||||
g: &'tcx hir::Generics,
|
||||
id: hir::HirId) {
|
||||
if self.should_warn_about_variant(&variant) {
|
||||
self.warn_dead_code(variant.id, variant.span, variant.ident.name,
|
||||
"variant", "constructed");
|
||||
} else {
|
||||
intravisit::walk_variant(self, variant, g, id);
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_foreign_item(&mut self, fi: &'tcx hir::ForeignItem) {
|
||||
if self.should_warn_about_foreign_item(fi) {
|
||||
self.warn_dead_code(fi.hir_id, fi.span, fi.ident.name,
|
||||
fi.kind.descriptive_variant(), "used");
|
||||
}
|
||||
intravisit::walk_foreign_item(self, fi);
|
||||
}
|
||||
|
||||
fn visit_struct_field(&mut self, field: &'tcx hir::StructField) {
|
||||
if self.should_warn_about_field(&field) {
|
||||
self.warn_dead_code(field.hir_id, field.span, field.ident.name, "field", "used");
|
||||
}
|
||||
intravisit::walk_struct_field(self, field);
|
||||
}
|
||||
|
||||
fn visit_impl_item(&mut self, impl_item: &'tcx hir::ImplItem) {
|
||||
match impl_item.kind {
|
||||
hir::ImplItemKind::Const(_, body_id) => {
|
||||
if !self.symbol_is_live(impl_item.hir_id) {
|
||||
self.warn_dead_code(impl_item.hir_id,
|
||||
impl_item.span,
|
||||
impl_item.ident.name,
|
||||
"associated const",
|
||||
"used");
|
||||
}
|
||||
self.visit_nested_body(body_id)
|
||||
}
|
||||
hir::ImplItemKind::Method(_, body_id) => {
|
||||
if !self.symbol_is_live(impl_item.hir_id) {
|
||||
let span = self.tcx.sess.source_map().def_span(impl_item.span);
|
||||
self.warn_dead_code(impl_item.hir_id, span, impl_item.ident.name, "method",
|
||||
"used");
|
||||
}
|
||||
self.visit_nested_body(body_id)
|
||||
}
|
||||
hir::ImplItemKind::OpaqueTy(..) |
|
||||
hir::ImplItemKind::TyAlias(..) => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Overwrite so that we don't warn the trait item itself.
|
||||
fn visit_trait_item(&mut self, trait_item: &'tcx hir::TraitItem) {
|
||||
match trait_item.kind {
|
||||
hir::TraitItemKind::Const(_, Some(body_id)) |
|
||||
hir::TraitItemKind::Method(_, hir::TraitMethod::Provided(body_id)) => {
|
||||
self.visit_nested_body(body_id)
|
||||
}
|
||||
hir::TraitItemKind::Const(_, None) |
|
||||
hir::TraitItemKind::Method(_, hir::TraitMethod::Required(_)) |
|
||||
hir::TraitItemKind::Type(..) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_crate(tcx: TyCtxt<'_>) {
|
||||
let access_levels = &tcx.privacy_access_levels(LOCAL_CRATE);
|
||||
let krate = tcx.hir().krate();
|
||||
let live_symbols = find_live(tcx, access_levels, krate);
|
||||
let mut visitor = DeadVisitor {
|
||||
tcx,
|
||||
live_symbols,
|
||||
};
|
||||
intravisit::walk_crate(&mut visitor, krate);
|
||||
}
|
||||
|
|
@ -1,202 +0,0 @@
|
|||
use crate::hir::map as hir_map;
|
||||
use crate::hir::def_id::{CrateNum, CRATE_DEF_INDEX, DefId, LOCAL_CRATE};
|
||||
use crate::session::{config, Session};
|
||||
use crate::session::config::EntryFnType;
|
||||
use syntax::attr;
|
||||
use syntax::entry::EntryPointType;
|
||||
use syntax::symbol::sym;
|
||||
use syntax_pos::Span;
|
||||
use crate::hir::{HirId, Item, ItemKind, ImplItem, TraitItem};
|
||||
use crate::hir::itemlikevisit::ItemLikeVisitor;
|
||||
use crate::ty::TyCtxt;
|
||||
use crate::ty::query::Providers;
|
||||
|
||||
struct EntryContext<'a, 'tcx> {
|
||||
session: &'a Session,
|
||||
|
||||
map: &'a hir_map::Map<'tcx>,
|
||||
|
||||
/// The top-level function called `main`.
|
||||
main_fn: Option<(HirId, Span)>,
|
||||
|
||||
/// The function that has attribute named `main`.
|
||||
attr_main_fn: Option<(HirId, Span)>,
|
||||
|
||||
/// The function that has the attribute 'start' on it.
|
||||
start_fn: Option<(HirId, Span)>,
|
||||
|
||||
/// The functions that one might think are `main` but aren't, e.g.
|
||||
/// main functions not defined at the top level. For diagnostics.
|
||||
non_main_fns: Vec<(HirId, Span)> ,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> ItemLikeVisitor<'tcx> for EntryContext<'a, 'tcx> {
|
||||
fn visit_item(&mut self, item: &'tcx Item) {
|
||||
let def_id = self.map.local_def_id(item.hir_id);
|
||||
let def_key = self.map.def_key(def_id);
|
||||
let at_root = def_key.parent == Some(CRATE_DEF_INDEX);
|
||||
find_item(item, self, at_root);
|
||||
}
|
||||
|
||||
fn visit_trait_item(&mut self, _trait_item: &'tcx TraitItem) {
|
||||
// Entry fn is never a trait item.
|
||||
}
|
||||
|
||||
fn visit_impl_item(&mut self, _impl_item: &'tcx ImplItem) {
|
||||
// Entry fn is never a trait item.
|
||||
}
|
||||
}
|
||||
|
||||
fn entry_fn(tcx: TyCtxt<'_>, cnum: CrateNum) -> Option<(DefId, EntryFnType)> {
|
||||
assert_eq!(cnum, LOCAL_CRATE);
|
||||
|
||||
let any_exe = tcx.sess.crate_types.borrow().iter().any(|ty| {
|
||||
*ty == config::CrateType::Executable
|
||||
});
|
||||
if !any_exe {
|
||||
// No need to find a main function.
|
||||
return None;
|
||||
}
|
||||
|
||||
// If the user wants no main function at all, then stop here.
|
||||
if attr::contains_name(&tcx.hir().krate().attrs, sym::no_main) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut ctxt = EntryContext {
|
||||
session: tcx.sess,
|
||||
map: tcx.hir(),
|
||||
main_fn: None,
|
||||
attr_main_fn: None,
|
||||
start_fn: None,
|
||||
non_main_fns: Vec::new(),
|
||||
};
|
||||
|
||||
tcx.hir().krate().visit_all_item_likes(&mut ctxt);
|
||||
|
||||
configure_main(tcx, &ctxt)
|
||||
}
|
||||
|
||||
// Beware, this is duplicated in `libsyntax/entry.rs`, so make sure to keep
|
||||
// them in sync.
|
||||
fn entry_point_type(item: &Item, at_root: bool) -> EntryPointType {
|
||||
match item.kind {
|
||||
ItemKind::Fn(..) => {
|
||||
if attr::contains_name(&item.attrs, sym::start) {
|
||||
EntryPointType::Start
|
||||
} else if attr::contains_name(&item.attrs, sym::main) {
|
||||
EntryPointType::MainAttr
|
||||
} else if item.ident.name == sym::main {
|
||||
if at_root {
|
||||
// This is a top-level function so can be `main`.
|
||||
EntryPointType::MainNamed
|
||||
} else {
|
||||
EntryPointType::OtherMain
|
||||
}
|
||||
} else {
|
||||
EntryPointType::None
|
||||
}
|
||||
}
|
||||
_ => EntryPointType::None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn find_item(item: &Item, ctxt: &mut EntryContext<'_, '_>, at_root: bool) {
|
||||
match entry_point_type(item, at_root) {
|
||||
EntryPointType::MainNamed => {
|
||||
if ctxt.main_fn.is_none() {
|
||||
ctxt.main_fn = Some((item.hir_id, item.span));
|
||||
} else {
|
||||
span_err!(ctxt.session, item.span, E0136,
|
||||
"multiple `main` functions");
|
||||
}
|
||||
},
|
||||
EntryPointType::OtherMain => {
|
||||
ctxt.non_main_fns.push((item.hir_id, item.span));
|
||||
},
|
||||
EntryPointType::MainAttr => {
|
||||
if ctxt.attr_main_fn.is_none() {
|
||||
ctxt.attr_main_fn = Some((item.hir_id, item.span));
|
||||
} else {
|
||||
struct_span_err!(ctxt.session, item.span, E0137,
|
||||
"multiple functions with a `#[main]` attribute")
|
||||
.span_label(item.span, "additional `#[main]` function")
|
||||
.span_label(ctxt.attr_main_fn.unwrap().1, "first `#[main]` function")
|
||||
.emit();
|
||||
}
|
||||
},
|
||||
EntryPointType::Start => {
|
||||
if ctxt.start_fn.is_none() {
|
||||
ctxt.start_fn = Some((item.hir_id, item.span));
|
||||
} else {
|
||||
struct_span_err!(ctxt.session, item.span, E0138, "multiple `start` functions")
|
||||
.span_label(ctxt.start_fn.unwrap().1, "previous `start` function here")
|
||||
.span_label(item.span, "multiple `start` functions")
|
||||
.emit();
|
||||
}
|
||||
}
|
||||
EntryPointType::None => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn configure_main(tcx: TyCtxt<'_>, visitor: &EntryContext<'_, '_>) -> Option<(DefId, EntryFnType)> {
|
||||
if let Some((hir_id, _)) = visitor.start_fn {
|
||||
Some((tcx.hir().local_def_id(hir_id), EntryFnType::Start))
|
||||
} else if let Some((hir_id, _)) = visitor.attr_main_fn {
|
||||
Some((tcx.hir().local_def_id(hir_id), EntryFnType::Main))
|
||||
} else if let Some((hir_id, _)) = visitor.main_fn {
|
||||
Some((tcx.hir().local_def_id(hir_id), EntryFnType::Main))
|
||||
} else {
|
||||
no_main_err(tcx, visitor);
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn no_main_err(tcx: TyCtxt<'_>, visitor: &EntryContext<'_, '_>) {
|
||||
// There is no main function.
|
||||
let mut err = struct_err!(tcx.sess, E0601,
|
||||
"`main` function not found in crate `{}`", tcx.crate_name(LOCAL_CRATE));
|
||||
let filename = &tcx.sess.local_crate_source_file;
|
||||
let note = if !visitor.non_main_fns.is_empty() {
|
||||
for &(_, span) in &visitor.non_main_fns {
|
||||
err.span_note(span, "here is a function named `main`");
|
||||
}
|
||||
err.note("you have one or more functions named `main` not defined at the crate level");
|
||||
err.help("either move the `main` function definitions or attach the `#[main]` attribute \
|
||||
to one of them");
|
||||
// There were some functions named `main` though. Try to give the user a hint.
|
||||
format!("the main function must be defined at the crate level{}",
|
||||
filename.as_ref().map(|f| format!(" (in `{}`)", f.display())).unwrap_or_default())
|
||||
} else if let Some(filename) = filename {
|
||||
format!("consider adding a `main` function to `{}`", filename.display())
|
||||
} else {
|
||||
String::from("consider adding a `main` function at the crate level")
|
||||
};
|
||||
let sp = tcx.hir().krate().span;
|
||||
// The file may be empty, which leads to the diagnostic machinery not emitting this
|
||||
// note. This is a relatively simple way to detect that case and emit a span-less
|
||||
// note instead.
|
||||
if let Ok(_) = tcx.sess.source_map().lookup_line(sp.lo()) {
|
||||
err.set_span(sp);
|
||||
err.span_label(sp, ¬e);
|
||||
} else {
|
||||
err.note(¬e);
|
||||
}
|
||||
if tcx.sess.teach(&err.get_code().unwrap()) {
|
||||
err.note("If you don't know the basics of Rust, you can go look to the Rust Book \
|
||||
to get started: https://doc.rust-lang.org/book/");
|
||||
}
|
||||
err.emit();
|
||||
}
|
||||
|
||||
pub fn find_entry_point(tcx: TyCtxt<'_>) -> Option<(DefId, EntryFnType)> {
|
||||
tcx.entry_fn(LOCAL_CRATE)
|
||||
}
|
||||
|
||||
pub fn provide(providers: &mut Providers<'_>) {
|
||||
*providers = Providers {
|
||||
entry_fn,
|
||||
..*providers
|
||||
};
|
||||
}
|
||||
|
|
@ -1,170 +0,0 @@
|
|||
use crate::hir::def::{Res, DefKind};
|
||||
use crate::hir::def_id::DefId;
|
||||
use crate::ty::{self, Ty, TyCtxt};
|
||||
use crate::ty::layout::{LayoutError, Pointer, SizeSkeleton, VariantIdx};
|
||||
use crate::ty::query::Providers;
|
||||
|
||||
use rustc_target::spec::abi::Abi::RustIntrinsic;
|
||||
use rustc_index::vec::Idx;
|
||||
use syntax_pos::{Span, sym};
|
||||
use crate::hir::intravisit::{self, Visitor, NestedVisitorMap};
|
||||
use crate::hir;
|
||||
|
||||
fn check_mod_intrinsics(tcx: TyCtxt<'_>, module_def_id: DefId) {
|
||||
tcx.hir().visit_item_likes_in_module(
|
||||
module_def_id,
|
||||
&mut ItemVisitor { tcx }.as_deep_visitor()
|
||||
);
|
||||
}
|
||||
|
||||
pub fn provide(providers: &mut Providers<'_>) {
|
||||
*providers = Providers {
|
||||
check_mod_intrinsics,
|
||||
..*providers
|
||||
};
|
||||
}
|
||||
|
||||
struct ItemVisitor<'tcx> {
|
||||
tcx: TyCtxt<'tcx>,
|
||||
}
|
||||
|
||||
struct ExprVisitor<'tcx> {
|
||||
tcx: TyCtxt<'tcx>,
|
||||
tables: &'tcx ty::TypeckTables<'tcx>,
|
||||
param_env: ty::ParamEnv<'tcx>,
|
||||
}
|
||||
|
||||
/// If the type is `Option<T>`, it will return `T`, otherwise
|
||||
/// the type itself. Works on most `Option`-like types.
|
||||
fn unpack_option_like<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Ty<'tcx> {
|
||||
let (def, substs) = match ty.kind {
|
||||
ty::Adt(def, substs) => (def, substs),
|
||||
_ => return ty
|
||||
};
|
||||
|
||||
if def.variants.len() == 2 && !def.repr.c() && def.repr.int.is_none() {
|
||||
let data_idx;
|
||||
|
||||
let one = VariantIdx::new(1);
|
||||
let zero = VariantIdx::new(0);
|
||||
|
||||
if def.variants[zero].fields.is_empty() {
|
||||
data_idx = one;
|
||||
} else if def.variants[one].fields.is_empty() {
|
||||
data_idx = zero;
|
||||
} else {
|
||||
return ty;
|
||||
}
|
||||
|
||||
if def.variants[data_idx].fields.len() == 1 {
|
||||
return def.variants[data_idx].fields[0].ty(tcx, substs);
|
||||
}
|
||||
}
|
||||
|
||||
ty
|
||||
}
|
||||
|
||||
impl ExprVisitor<'tcx> {
|
||||
fn def_id_is_transmute(&self, def_id: DefId) -> bool {
|
||||
self.tcx.fn_sig(def_id).abi() == RustIntrinsic &&
|
||||
self.tcx.item_name(def_id) == sym::transmute
|
||||
}
|
||||
|
||||
fn check_transmute(&self, span: Span, from: Ty<'tcx>, to: Ty<'tcx>) {
|
||||
let sk_from = SizeSkeleton::compute(from, self.tcx, self.param_env);
|
||||
let sk_to = SizeSkeleton::compute(to, self.tcx, self.param_env);
|
||||
|
||||
// Check for same size using the skeletons.
|
||||
if let (Ok(sk_from), Ok(sk_to)) = (sk_from, sk_to) {
|
||||
if sk_from.same_size(sk_to) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Special-case transmutting from `typeof(function)` and
|
||||
// `Option<typeof(function)>` to present a clearer error.
|
||||
let from = unpack_option_like(self.tcx, from);
|
||||
if let (&ty::FnDef(..), SizeSkeleton::Known(size_to)) = (&from.kind, sk_to) {
|
||||
if size_to == Pointer.size(&self.tcx) {
|
||||
struct_span_err!(self.tcx.sess, span, E0591,
|
||||
"can't transmute zero-sized type")
|
||||
.note(&format!("source type: {}", from))
|
||||
.note(&format!("target type: {}", to))
|
||||
.help("cast with `as` to a pointer instead")
|
||||
.emit();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try to display a sensible error with as much information as possible.
|
||||
let skeleton_string = |ty: Ty<'tcx>, sk| {
|
||||
match sk {
|
||||
Ok(SizeSkeleton::Known(size)) => {
|
||||
format!("{} bits", size.bits())
|
||||
}
|
||||
Ok(SizeSkeleton::Pointer { tail, .. }) => {
|
||||
format!("pointer to `{}`", tail)
|
||||
}
|
||||
Err(LayoutError::Unknown(bad)) => {
|
||||
if bad == ty {
|
||||
"this type does not have a fixed size".to_owned()
|
||||
} else {
|
||||
format!("size can vary because of {}", bad)
|
||||
}
|
||||
}
|
||||
Err(err) => err.to_string()
|
||||
}
|
||||
};
|
||||
|
||||
let mut err = struct_span_err!(self.tcx.sess, span, E0512,
|
||||
"cannot transmute between types of different sizes, \
|
||||
or dependently-sized types");
|
||||
if from == to {
|
||||
err.note(&format!("`{}` does not have a fixed size", from));
|
||||
} else {
|
||||
err.note(&format!("source type: `{}` ({})", from, skeleton_string(from, sk_from)))
|
||||
.note(&format!("target type: `{}` ({})", to, skeleton_string(to, sk_to)));
|
||||
}
|
||||
err.emit()
|
||||
}
|
||||
}
|
||||
|
||||
impl Visitor<'tcx> for ItemVisitor<'tcx> {
|
||||
fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
|
||||
NestedVisitorMap::None
|
||||
}
|
||||
|
||||
fn visit_nested_body(&mut self, body_id: hir::BodyId) {
|
||||
let owner_def_id = self.tcx.hir().body_owner_def_id(body_id);
|
||||
let body = self.tcx.hir().body(body_id);
|
||||
let param_env = self.tcx.param_env(owner_def_id);
|
||||
let tables = self.tcx.typeck_tables_of(owner_def_id);
|
||||
ExprVisitor { tcx: self.tcx, param_env, tables }.visit_body(body);
|
||||
self.visit_body(body);
|
||||
}
|
||||
}
|
||||
|
||||
impl Visitor<'tcx> for ExprVisitor<'tcx> {
|
||||
fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
|
||||
NestedVisitorMap::None
|
||||
}
|
||||
|
||||
fn visit_expr(&mut self, expr: &'tcx hir::Expr) {
|
||||
let res = if let hir::ExprKind::Path(ref qpath) = expr.kind {
|
||||
self.tables.qpath_res(qpath, expr.hir_id)
|
||||
} else {
|
||||
Res::Err
|
||||
};
|
||||
if let Res::Def(DefKind::Fn, did) = res {
|
||||
if self.def_id_is_transmute(did) {
|
||||
let typ = self.tables.node_type(expr.hir_id);
|
||||
let sig = typ.fn_sig(self.tcx);
|
||||
let from = sig.inputs().skip_binder()[0];
|
||||
let to = *sig.output().skip_binder();
|
||||
self.check_transmute(expr.span, from, to);
|
||||
}
|
||||
}
|
||||
|
||||
intravisit::walk_expr(self, expr);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue