Skip to content

Commit

Permalink
Handle panicking like rustc CTFE does
Browse files Browse the repository at this point in the history
Instead of using `core::fmt::format` to format panic messages, which may in turn
panic too and cause recursive panics and other messy things, redirect
`panic_fmt` to `const_panic_fmt` like CTFE, which in turn goes to
`panic_display` and does the things normally. See the tests for the full
call stack.
  • Loading branch information
Nilstrieb committed Mar 24, 2024
1 parent e265e3d commit a03632f
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 47 deletions.
5 changes: 4 additions & 1 deletion crates/hir-ty/src/mir/eval.rs
Expand Up @@ -2317,7 +2317,7 @@ impl Evaluator<'_> {

fn exec_fn_with_args(
&mut self,
def: FunctionId,
mut def: FunctionId,
args: &[IntervalAndTy],
generic_args: Substitution,
locals: &Locals,
Expand All @@ -2335,6 +2335,9 @@ impl Evaluator<'_> {
)? {
return Ok(None);
}
if let Some(redirect_def) = self.detect_and_redirect_special_function(def)? {
def = redirect_def;
}
let arg_bytes = args.iter().map(|it| IntervalOrOwned::Borrowed(it.interval));
match self.get_mir_or_dyn_index(def, generic_args.clone(), locals, span)? {
MirOrDynIndex::Dyn(self_ty_idx) => {
Expand Down
63 changes: 25 additions & 38 deletions crates/hir-ty/src/mir/eval/shim.rs
Expand Up @@ -158,6 +158,25 @@ impl Evaluator<'_> {
Ok(false)
}

pub(super) fn detect_and_redirect_special_function(
&mut self,
def: FunctionId,
) -> Result<Option<FunctionId>> {
// `PanicFmt` is redirected to `ConstPanicFmt`
if let Some(LangItem::PanicFmt) = self.db.lang_attr(def.into()) {
let resolver =
self.db.crate_def_map(self.crate_id).crate_root().resolver(self.db.upcast());

let Some(hir_def::lang_item::LangItemTarget::Function(const_panic_fmt)) =
self.db.lang_item(resolver.krate(), LangItem::ConstPanicFmt)
else {
not_supported!("const_panic_fmt lang item not found or not a function");
};
return Ok(Some(const_panic_fmt));
}
Ok(None)
}

/// Clone has special impls for tuples and function pointers
fn exec_clone(
&mut self,
Expand Down Expand Up @@ -291,9 +310,14 @@ impl Evaluator<'_> {
use LangItem::*;
let candidate = self.db.lang_attr(def.into())?;
// We want to execute these functions with special logic
if [PanicFmt, BeginPanic, SliceLen, DropInPlace].contains(&candidate) {
// `PanicFmt` is not detected here as it's redirected later.
if [BeginPanic, SliceLen, DropInPlace].contains(&candidate) {
return Some(candidate);
}
if self.db.attrs(def.into()).by_key("rustc_const_panic_str").exists() {
// `#[rustc_const_panic_str]` is treated like `lang = "begin_panic"` by rustc CTFE.
return Some(LangItem::BeginPanic);
}
None
}

Expand All @@ -309,43 +333,6 @@ impl Evaluator<'_> {
let mut args = args.iter();
match it {
BeginPanic => Err(MirEvalError::Panic("<unknown-panic-payload>".to_owned())),
PanicFmt => {
let message = (|| {
let resolver = self
.db
.crate_def_map(self.crate_id)
.crate_root()
.resolver(self.db.upcast());
let Some(format_fn) = resolver.resolve_path_in_value_ns_fully(
self.db.upcast(),
&hir_def::path::Path::from_known_path_with_no_generic(
ModPath::from_segments(
hir_expand::mod_path::PathKind::Abs,
[name![std], name![fmt], name![format]],
),
),
) else {
not_supported!("std::fmt::format not found");
};
let hir_def::resolver::ValueNs::FunctionId(format_fn) = format_fn else {
not_supported!("std::fmt::format is not a function")
};
let interval = self.interpret_mir(
self.db
.mir_body(format_fn.into())
.map_err(|e| MirEvalError::MirLowerError(format_fn, e))?,
args.map(|x| IntervalOrOwned::Owned(x.clone())),
)?;
let message_string = interval.get(self)?;
let addr =
Address::from_bytes(&message_string[self.ptr_size()..2 * self.ptr_size()])?;
let size = from_bytes!(usize, message_string[2 * self.ptr_size()..]);
Ok(std::string::String::from_utf8_lossy(self.read_memory(addr, size)?)
.into_owned())
})()
.unwrap_or_else(|e| format!("Failed to render panic format args: {e:?}"));
Err(MirEvalError::Panic(message))
}
SliceLen => {
let arg = args.next().ok_or(MirEvalError::InternalError(
"argument of <[T]>::len() is not provided".into(),
Expand Down
37 changes: 37 additions & 0 deletions crates/hir-ty/src/mir/eval/tests.rs
Expand Up @@ -31,6 +31,7 @@ fn eval_main(db: &TestDB, file_id: FileId) -> Result<(String, String), MirEvalEr
db.trait_environment(func_id.into()),
)
.map_err(|e| MirEvalError::MirLowerError(func_id, e))?;

let (result, output) = interpret_mir(db, body, false, None);
result?;
Ok((output.stdout().into_owned(), output.stderr().into_owned()))
Expand Down Expand Up @@ -87,6 +88,42 @@ fn main() {
);
}

#[test]
fn panic_fmt() {
// panic!
// -> panic_2021 (builtin macro redirection)
// -> #[lang = "panic_fmt"] core::panicking::panic_fmt (hooked by CTFE for redirection)
// -> core::panicking::const_panic_fmt
// -> #[rustc_const_panic_str] core::panicking::panic_display (hooked by CTFE for builtin panic)
// -> Err(ConstEvalError::Panic)
check_pass(
r#"
//- minicore: fmt, panic
fn main() {
panic!("hello, world!");
}
"#,
);
panic!("a");
}

#[test]
fn panic_display() {
// panic!
// -> panic_2021 (builtin macro redirection)
// -> #[rustc_const_panic_str] core::panicking::panic_display (hooked by CTFE for builtin panic)
// -> Err(ConstEvalError::Panic)
check_pass(
r#"
//- minicore: fmt, panic
fn main() {
panic!("{}", "hello, world!");
}
"#,
);
}

#[test]
fn drop_basic() {
check_pass(
Expand Down
34 changes: 26 additions & 8 deletions crates/test-utils/src/minicore.rs
Expand Up @@ -1356,18 +1356,36 @@ pub mod iter {
// region:panic
mod panic {
pub macro panic_2021 {
() => (
$crate::panicking::panic("explicit panic")
),
($($t:tt)+) => (
$crate::panicking::panic_fmt($crate::const_format_args!($($t)+))
),
() => ({
const fn panic_cold_explicit() -> ! {
$crate::panicking::panic_explicit()
}
panic_cold_explicit();
}),
// Special-case the single-argument case for const_panic.
("{}", $arg:expr $(,)?) => ({
#[rustc_const_panic_str] // enforce a &&str argument in const-check and hook this by const-eval
const fn panic_cold_display<T: $crate::fmt::Display>(arg: &T) -> ! {
loop {}
}
panic_cold_display(&$arg);
}),
($($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)+));
}),
}
}

mod panicking {
#[lang = "panic_fmt"]
pub const fn panic_fmt(_fmt: crate::fmt::Arguments<'_>) -> ! {
#[rustc_const_panic_str] // enforce a &&str argument in const-check and hook this by const-eval
pub const fn panic_display<T: fmt::Display>(x: &T) -> ! {
panic_fmt(format_args!("{}", *x));
}

#[lang = "panic_fmt"] // needed for const-evaluated panics
pub const fn panic_fmt(fmt: fmt::Arguments<'_>) -> ! {
loop {}
}

Expand Down

0 comments on commit a03632f

Please sign in to comment.