rust/compiler/rustc_mir_build/src/lints.rs

163 lines
6.2 KiB
Rust
Raw Normal View History

use rustc_data_structures::graph::iterate::{
NodeStatus, TriColorDepthFirstSearch, TriColorVisitor,
};
2020-03-29 10:19:48 -05:00
use rustc_hir::intravisit::FnKind;
use rustc_middle::mir::{BasicBlock, Body, Operand, TerminatorKind};
use rustc_middle::ty::subst::{GenericArg, InternalSubsts};
2020-03-29 09:41:09 -05:00
use rustc_middle::ty::{self, AssocItem, AssocItemContainer, Instance, TyCtxt};
use rustc_session::lint::builtin::UNCONDITIONAL_RECURSION;
use rustc_span::Span;
use std::ops::ControlFlow;
crate fn check<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>) {
let def_id = body.source.def_id().expect_local();
let hir_id = tcx.hir().local_def_id_to_hir_id(def_id);
2021-10-19 16:31:51 -05:00
if let Some(fn_kind) = tcx.hir().get(hir_id).fn_kind() {
if let FnKind::Closure = fn_kind {
2020-04-05 14:01:38 -05:00
// closures can't recur, so they don't matter.
return;
}
// If this is trait/impl method, extract the trait's substs.
let trait_substs = match tcx.opt_associated_item(def_id.to_def_id()) {
Some(AssocItem {
container: AssocItemContainer::TraitContainer(trait_def_id), ..
}) => {
2020-04-14 17:07:31 -05:00
let trait_substs_count = tcx.generics_of(*trait_def_id).count();
&InternalSubsts::identity_for_item(tcx, def_id.to_def_id())[..trait_substs_count]
}
_ => &[],
};
let mut vis = Search { tcx, body, reachable_recursive_calls: vec![], trait_substs };
if let Some(NonRecursive) = TriColorDepthFirstSearch::new(&body).run_from_start(&mut vis) {
return;
}
vis.reachable_recursive_calls.sort();
let hir_id = tcx.hir().local_def_id_to_hir_id(def_id);
Use smaller def span for functions Currently, the def span of a funtion encompasses the entire function signature and body. However, this is usually unnecessarily verbose - when we are pointing at an entire function in a diagnostic, we almost always want to point at the signature. The actual contents of the body tends to be irrelevant to the diagnostic we are emitting, and just takes up additional screen space. This commit changes the `def_span` of all function items (freestanding functions, `impl`-block methods, and `trait`-block methods) to be the span of the signature. For example, the function ```rust pub fn foo<T>(val: T) -> T { val } ``` now has a `def_span` corresponding to `pub fn foo<T>(val: T) -> T` (everything before the opening curly brace). Trait methods without a body have a `def_span` which includes the trailing semicolon. For example: ```rust trait Foo { fn bar(); }``` the function definition `Foo::bar` has a `def_span` of `fn bar();` This makes our diagnostic output much shorter, and emphasizes information that is relevant to whatever diagnostic we are reporting. We continue to use the full span (including the body) in a few of places: * MIR building uses the full span when building source scopes. * 'Outlives suggestions' use the full span to sort the diagnostics being emitted. * The `#[rustc_on_unimplemented(enclosing_scope="in this scope")]` attribute points the entire scope body. * The 'unconditional recursion' lint uses the full span to show additional context for the recursive call. All of these cases work only with local items, so we don't need to add anything extra to crate metadata.
2020-08-12 16:02:14 -05:00
let sp = tcx.sess.source_map().guess_head_span(tcx.hir().span_with_body(hir_id));
2020-02-01 17:47:58 -06:00
tcx.struct_span_lint_hir(UNCONDITIONAL_RECURSION, hir_id, sp, |lint| {
let mut db = lint.build("function cannot return without recursing");
db.span_label(sp, "cannot return without recursing");
// offer some help to the programmer.
for call_span in vis.reachable_recursive_calls {
db.span_label(call_span, "recursive call site");
2020-02-01 17:47:58 -06:00
}
db.help("a `loop` may express intention better if this is on purpose");
db.emit();
});
}
}
struct NonRecursive;
struct Search<'mir, 'tcx> {
tcx: TyCtxt<'tcx>,
body: &'mir Body<'tcx>,
trait_substs: &'tcx [GenericArg<'tcx>],
2020-04-05 18:52:51 -05:00
reachable_recursive_calls: Vec<Span>,
}
impl<'mir, 'tcx> Search<'mir, 'tcx> {
/// Returns `true` if `func` refers to the function we are searching in.
fn is_recursive_call(&self, func: &Operand<'tcx>) -> bool {
let Search { tcx, body, trait_substs, .. } = *self;
let caller = body.source.def_id();
let param_env = tcx.param_env(caller);
let func_ty = func.ty(body, tcx);
if let ty::FnDef(callee, substs) = *func_ty.kind() {
2020-10-20 12:43:21 -05:00
let normalized_substs = tcx.normalize_erasing_regions(param_env, substs);
2020-10-21 01:12:52 -05:00
let (callee, call_substs) = if let Ok(Some(instance)) =
Instance::resolve(tcx, param_env, callee, normalized_substs)
{
(instance.def_id(), instance.substs)
} else {
(callee, normalized_substs)
};
// FIXME(#57965): Make this work across function boundaries
// If this is a trait fn, the substs on the trait have to match, or we might be
// calling into an entirely different method (for example, a call from the default
// method in the trait to `<A as Trait<B>>::method`, where `A` and/or `B` are
// specific types).
return callee == caller && &call_substs[..trait_substs.len()] == trait_substs;
}
false
}
}
impl<'mir, 'tcx> TriColorVisitor<&'mir Body<'tcx>> for Search<'mir, 'tcx> {
type BreakVal = NonRecursive;
fn node_examined(
&mut self,
bb: BasicBlock,
prior_status: Option<NodeStatus>,
) -> ControlFlow<Self::BreakVal> {
// Back-edge in the CFG (loop).
if let Some(NodeStatus::Visited) = prior_status {
return ControlFlow::Break(NonRecursive);
}
match self.body[bb].terminator().kind {
// These terminators return control flow to the caller.
TerminatorKind::Abort
| TerminatorKind::GeneratorDrop
| TerminatorKind::Resume
| TerminatorKind::Return
| TerminatorKind::Unreachable
| TerminatorKind::Yield { .. } => ControlFlow::Break(NonRecursive),
// A diverging InlineAsm is treated as non-recursing
TerminatorKind::InlineAsm { destination, .. } => {
if destination.is_some() {
2020-09-04 02:59:41 -05:00
ControlFlow::CONTINUE
} else {
ControlFlow::Break(NonRecursive)
}
}
2020-02-14 12:17:50 -06:00
// These do not.
TerminatorKind::Assert { .. }
| TerminatorKind::Call { .. }
| TerminatorKind::Drop { .. }
| TerminatorKind::DropAndReplace { .. }
2020-06-02 02:15:24 -05:00
| TerminatorKind::FalseEdge { .. }
| TerminatorKind::FalseUnwind { .. }
| TerminatorKind::Goto { .. }
2020-09-04 02:59:41 -05:00
| TerminatorKind::SwitchInt { .. } => ControlFlow::CONTINUE,
}
}
fn node_settled(&mut self, bb: BasicBlock) -> ControlFlow<Self::BreakVal> {
// When we examine a node for the last time, remember it if it is a recursive call.
let terminator = self.body[bb].terminator();
if let TerminatorKind::Call { func, .. } = &terminator.kind {
if self.is_recursive_call(func) {
self.reachable_recursive_calls.push(terminator.source_info.span);
}
}
2020-09-04 02:59:41 -05:00
ControlFlow::CONTINUE
}
fn ignore_edge(&mut self, bb: BasicBlock, target: BasicBlock) -> bool {
// Don't traverse successors of recursive calls or false CFG edges.
match self.body[bb].terminator().kind {
TerminatorKind::Call { ref func, .. } => self.is_recursive_call(func),
TerminatorKind::FalseUnwind { unwind: Some(imaginary_target), .. }
2020-06-02 02:15:24 -05:00
| TerminatorKind::FalseEdge { imaginary_target, .. } => imaginary_target == target,
_ => false,
}
}
}