Skip to content

Commit

Permalink
Partially outline code inside the panic! macro
Browse files Browse the repository at this point in the history
  • Loading branch information
Zoxc committed Sep 8, 2023
1 parent 15e52b0 commit ebd5841
Show file tree
Hide file tree
Showing 6 changed files with 214 additions and 58 deletions.
65 changes: 65 additions & 0 deletions library/core/src/panic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ pub macro panic_2015 {
#[allow_internal_unstable(core_panic, const_format_args)]
#[rustc_diagnostic_item = "core_panic_2021_macro"]
#[rustc_macro_transparency = "semitransparent"]
#[cfg(any(bootstrap, feature = "panic_immediate_abort"))]
pub macro panic_2021 {
() => (
$crate::panicking::panic("explicit panic")
Expand All @@ -62,6 +63,70 @@ pub macro panic_2021 {
}),
}

#[doc(hidden)]
#[unstable(feature = "edition_panic", issue = "none", reason = "use panic!() instead")]
#[allow_internal_unstable(
core_panic,
core_intrinsics,
const_dispatch,
const_eval_select,
const_format_args,
panic_args,
rustc_attrs
)]
#[rustc_diagnostic_item = "core_panic_2021_macro"]
#[rustc_macro_transparency = "semitransparent"]
#[cfg(not(any(bootstrap, feature = "panic_immediate_abort")))]
pub macro panic_2021 {
() => ({
// Create a function so that the argument for `track_caller`
// can be moved inside if possible.
#[cold]
#[track_caller]
#[inline(never)]
const fn panic_cold_explicit() -> ! {
$crate::panicking::panic_explicit()
}
panic_cold_explicit();
}),
// Special-case the single-argument case for const_panic.
("{}", $arg:expr $(,)?) => ({
#[cold]
#[track_caller]
#[inline(never)]
#[rustc_do_not_const_check] // Allow the call to `panic_display`.
const fn panic_cold_display<T: $crate::fmt::Display>(arg: &T) -> ! {
$crate::panicking::panic_display(arg)
}

let arg = &$arg;

// Ensure the caller could call `panic_display` itself directly.
// Const checking will ensure only &str is allowed.
if false {
$crate::panicking::panic_display(arg);
}

// Call `panic_display` directly for const eval since `panic_cold_display` can not be
// evaluated as it is marked with `rustc_do_not_const_check`.

// SAFETY: These branches are observably equivalent as `panic_cold_display` is just an
// indirect way of calling `panic_display`.
unsafe {
$crate::intrinsics::const_eval_select(
(arg,),
$crate::panicking::panic_display,
panic_cold_display
);
}
}),
($($t:tt)+) => ({
// Semicolon to prevent temporaries inside the formatting machinery from
// being considered alive in the caller after the panic_fmt call.
$crate::panicking::panic_fmt($crate::const_format_args!($($t)+));
}),
}

#[doc(hidden)]
#[unstable(feature = "edition_panic", issue = "none", reason = "use unreachable!() instead")]
#[allow_internal_unstable(core_panic)]
Expand Down
8 changes: 8 additions & 0 deletions library/core/src/panicking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,14 @@ pub const fn panic_str(expr: &str) -> ! {
panic_display(&expr);
}

#[track_caller]
#[cfg_attr(not(feature = "panic_immediate_abort"), inline(never), cold)]
#[cfg_attr(feature = "panic_immediate_abort", inline)]
#[rustc_const_unstable(feature = "core_panic", issue = "none")]
pub const fn panic_explicit() -> ! {
panic_display(&"explicit panic");
}

#[inline]
#[track_caller]
#[rustc_diagnostic_item = "unreachable_display"] // needed for `non-fmt-panics` lint
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
use clippy_utils::diagnostics::{span_lint, span_lint_and_note};
use clippy_utils::{get_parent_expr, path_to_local, path_to_local_id};
use if_chain::if_chain;
use rustc_ast::LitKind;
use rustc_hir::intravisit::{walk_expr, Visitor};
use rustc_hir::{BinOpKind, Block, Expr, ExprKind, Guard, HirId, Local, Node, Stmt, StmtKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::source_map::Spanned;

declare_clippy_lint! {
/// ### What it does
Expand Down Expand Up @@ -134,15 +136,56 @@ impl<'a, 'tcx> DivergenceVisitor<'a, 'tcx> {
}
}

fn expr_might_diverge(e: &Expr<'_>) -> bool {
match e.kind {
ExprKind::Path(..) => false,
ExprKind::AddrOf(.., e) => expr_might_diverge(e),
ExprKind::If(
&Expr {
kind:
ExprKind::Lit(Spanned {
node: LitKind::Bool(false),
..
}),
..
},
_,
None,
) => false,
_ => true,
}
}

fn stmt_might_diverge(stmt: &Stmt<'_>) -> bool {
match stmt.kind {
StmtKind::Item(..) => false,
StmtKind::Local(Local {
init: Some(e),
els: None,
..
}) => expr_might_diverge(e),
StmtKind::Expr(e) | StmtKind::Semi(e) => expr_might_diverge(e),
_ => true,
}
}

impl<'a, 'tcx> Visitor<'tcx> for DivergenceVisitor<'a, 'tcx> {
fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
match e.kind {
// fix #10776
ExprKind::Block(block, ..) => match (block.stmts, block.expr) {
([], Some(e)) => self.visit_expr(e),
([stmt], None) => match stmt.kind {
StmtKind::Expr(e) | StmtKind::Semi(e) => self.visit_expr(e),
_ => {},
(stmts, Some(e)) => {
if stmts.iter().all(|stmt| !stmt_might_diverge(stmt)) {
self.visit_expr(e)
}
},
([first @ .., stmt], None) => {
if first.iter().all(|stmt| !stmt_might_diverge(stmt)) {
match stmt.kind {
StmtKind::Expr(e) | StmtKind::Semi(e) => self.visit_expr(e),
_ => {},
}
}
},
_ => {},
},
Expand Down
112 changes: 74 additions & 38 deletions src/tools/clippy/clippy_utils/src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use crate::visitors::{for_each_expr, Descend};

use arrayvec::ArrayVec;
use hir::{Block, Local, Stmt, StmtKind};
use rustc_ast::{FormatArgs, FormatArgument, FormatPlaceholder};
use rustc_data_structures::fx::FxHashMap;
use rustc_hir::{self as hir, Expr, ExprKind, HirId, Node, QPath};
Expand Down Expand Up @@ -106,12 +107,7 @@ pub fn macro_backtrace(span: Span) -> impl Iterator<Item = MacroCall> {
macro_def_id: Some(def_id),
call_site: span,
..
} => Some(MacroCall {
def_id,
kind,
expn,
span,
}),
} => Some(MacroCall { def_id, kind, expn, span }),
_ => None,
})
}
Expand All @@ -133,11 +129,14 @@ pub fn root_macro_call_first_node(cx: &LateContext<'_>, node: &impl HirNode) ->

/// Like [`macro_backtrace`], but only returns macro calls where `node` is the "first node" of the
/// macro call, as in [`first_node_in_macro`].
pub fn first_node_macro_backtrace(cx: &LateContext<'_>, node: &impl HirNode) -> impl Iterator<Item = MacroCall> {
pub fn first_node_macro_backtrace(
cx: &LateContext<'_>,
node: &impl HirNode,
) -> impl Iterator<Item = MacroCall> {
let span = node.span();
first_node_in_macro(cx, node)
.into_iter()
.flat_map(move |expn| macro_backtrace(span).take_while(move |macro_call| macro_call.expn != expn))
first_node_in_macro(cx, node).into_iter().flat_map(move |expn| {
macro_backtrace(span).take_while(move |macro_call| macro_call.expn != expn)
})
}

/// If `node` is the "first node" in a macro expansion, returns `Some` with the `ExpnId` of the
Expand Down Expand Up @@ -222,17 +221,54 @@ pub enum PanicExpn<'a> {
/// A single argument that implements `Display` - `panic!("{}", object)`
Display(&'a Expr<'a>),
/// Anything else - `panic!("error {}: {}", a, b)`
Format(&'a Expr<'a>),
Format(Option<&'a Expr<'a>>),
}

impl<'a> PanicExpn<'a> {
pub fn parse(expr: &'a Expr<'a>) -> Option<Self> {
let ExprKind::Call(callee, [arg, rest @ ..]) = &expr.kind else {
// Pattern match on the special single-argument case for const_panic in `panic_2021`.
if let ExprKind::Block(
&Block {
stmts:
[
Stmt { kind: StmtKind::Item(..), .. },
Stmt {
kind:
StmtKind::Local(&Local {
init: Some(&Expr { kind: ExprKind::AddrOf(_, _, arg), .. }),
..
}),
..
},
Stmt { kind: StmtKind::Expr(&Expr { kind: ExprKind::If(..), .. }), .. },
..,
],
..
},
..,
) = &expr.kind
{
return Some(Self::Display(arg));
}

let ExprKind::Call(callee, args) = &expr.kind else {
return None;
};
let ExprKind::Path(QPath::Resolved(_, path)) = &callee.kind else {
return None;
};
let name = path.segments.last().unwrap().ident.as_str();

// These may have no arguments
match name {
"panic_cold_explicit" => return Some(Self::Empty),
"panic_cold" => return Some(Self::Format(None)),
_ => (),
};

let [arg, rest @ ..] = args else {
return None;
};
let result = match path.segments.last().unwrap().ident.as_str() {
"panic" if arg.span.ctxt() == expr.span.ctxt() => Self::Empty,
"panic" | "panic_str" => Self::Str(arg),
Expand All @@ -241,8 +277,8 @@ impl<'a> PanicExpn<'a> {
return None;
};
Self::Display(e)
},
"panic_fmt" => Self::Format(arg),
}
"panic_fmt" => Self::Format(Some(arg)),
// Since Rust 1.52, `assert_{eq,ne}` macros expand to use:
// `core::panicking::assert_failed(.., left_val, right_val, None | Some(format_args!(..)));`
"assert_failed" => {
Expand All @@ -254,10 +290,10 @@ impl<'a> PanicExpn<'a> {
// `msg_arg` is either `None` (no custom message) or `Some(format_args!(..))` (custom message)
let msg_arg = &rest[2];
match msg_arg.kind {
ExprKind::Call(_, [fmt_arg]) => Self::Format(fmt_arg),
ExprKind::Call(_, [fmt_arg]) => Self::Format(Some(fmt_arg)),
_ => Self::Empty,
}
},
}
_ => return None,
};
Some(result)
Expand Down Expand Up @@ -301,7 +337,9 @@ fn find_assert_args_inner<'a, const N: usize>(
let macro_id = expn.expn_data().macro_def_id?;
let (expr, expn) = match cx.tcx.item_name(macro_id).as_str().strip_prefix("debug_") {
None => (expr, expn),
Some(inner_name) => find_assert_within_debug_assert(cx, expr, expn, Symbol::intern(inner_name))?,
Some(inner_name) => {
find_assert_within_debug_assert(cx, expr, expn, Symbol::intern(inner_name))?
}
};
let mut args = ArrayVec::new();
let panic_expn = for_each_expr(expr, |e| {
Expand Down Expand Up @@ -337,7 +375,9 @@ fn find_assert_within_debug_assert<'a>(
let e_expn = e.span.ctxt().outer_expn();
if e_expn == expn {
ControlFlow::Continue(Descend::Yes)
} else if e_expn.expn_data().macro_def_id.map(|id| cx.tcx.item_name(id)) == Some(assert_name) {
} else if e_expn.expn_data().macro_def_id.map(|id| cx.tcx.item_name(id))
== Some(assert_name)
{
ControlFlow::Break((e, e_expn))
} else {
ControlFlow::Continue(Descend::No)
Expand Down Expand Up @@ -395,14 +435,20 @@ pub fn collect_ast_format_args(span: Span, format_args: &FormatArgs) {

/// Calls `callback` with an AST [`FormatArgs`] node if a `format_args` expansion is found as a
/// descendant of `expn_id`
pub fn find_format_args(cx: &LateContext<'_>, start: &Expr<'_>, expn_id: ExpnId, callback: impl FnOnce(&FormatArgs)) {
pub fn find_format_args(
cx: &LateContext<'_>,
start: &Expr<'_>,
expn_id: ExpnId,
callback: impl FnOnce(&FormatArgs),
) {
let format_args_expr = for_each_expr(start, |expr| {
let ctxt = expr.span.ctxt();
if ctxt.outer_expn().is_descendant_of(expn_id) {
if macro_backtrace(expr.span)
.map(|macro_call| cx.tcx.item_name(macro_call.def_id))
.any(|name| matches!(name, sym::const_format_args | sym::format_args | sym::format_args_nl))
{
if macro_backtrace(expr.span).map(|macro_call| cx.tcx.item_name(macro_call.def_id)).any(
|name| {
matches!(name, sym::const_format_args | sym::format_args | sym::format_args_nl)
},
) {
ControlFlow::Break(expr)
} else {
ControlFlow::Continue(Descend::Yes)
Expand All @@ -425,12 +471,7 @@ pub fn find_format_arg_expr<'hir, 'ast>(
start: &'hir Expr<'hir>,
target: &'ast FormatArgument,
) -> Result<&'hir rustc_hir::Expr<'hir>, &'ast rustc_ast::Expr> {
let SpanData {
lo,
hi,
ctxt,
parent: _,
} = target.expr.span.data();
let SpanData { lo, hi, ctxt, parent: _ } = target.expr.span.data();

for_each_expr(start, |expr| {
// When incremental compilation is enabled spans gain a parent during AST to HIR lowering,
Expand All @@ -456,12 +497,7 @@ pub fn format_placeholder_format_span(placeholder: &FormatPlaceholder) -> Option

// `base.hi` is `{...}|`, subtract 1 byte (the length of '}') so that it points before the closing
// brace `{...|}`
Some(Span::new(
placeholder.argument.span?.hi(),
base.hi - BytePos(1),
base.ctxt,
base.parent,
))
Some(Span::new(placeholder.argument.span?.hi(), base.hi - BytePos(1), base.ctxt, base.parent))
}

/// Span covering the format string and values
Expand All @@ -473,9 +509,9 @@ pub fn format_placeholder_format_span(placeholder: &FormatPlaceholder) -> Option
pub fn format_args_inputs_span(format_args: &FormatArgs) -> Span {
match format_args.arguments.explicit_args() {
[] => format_args.span,
[.., last] => format_args
.span
.to(hygiene::walk_chain(last.expr.span, format_args.span.ctxt())),
[.., last] => {
format_args.span.to(hygiene::walk_chain(last.expr.span, format_args.span.ctxt()))
}
}
}

Expand Down
Loading

0 comments on commit ebd5841

Please sign in to comment.