Handle panicking like rustc CTFE does

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.
This commit is contained in:
Nilstrieb 2024-03-24 15:39:15 +01:00
parent e265e3d518
commit 2dfe7de8b6
3 changed files with 56 additions and 40 deletions

View File

@ -2317,7 +2317,7 @@ fn get_mir_or_dyn_index(
fn exec_fn_with_args( fn exec_fn_with_args(
&mut self, &mut self,
def: FunctionId, mut def: FunctionId,
args: &[IntervalAndTy], args: &[IntervalAndTy],
generic_args: Substitution, generic_args: Substitution,
locals: &Locals, locals: &Locals,
@ -2335,6 +2335,9 @@ fn exec_fn_with_args(
)? { )? {
return Ok(None); 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)); let arg_bytes = args.iter().map(|it| IntervalOrOwned::Borrowed(it.interval));
match self.get_mir_or_dyn_index(def, generic_args.clone(), locals, span)? { match self.get_mir_or_dyn_index(def, generic_args.clone(), locals, span)? {
MirOrDynIndex::Dyn(self_ty_idx) => { MirOrDynIndex::Dyn(self_ty_idx) => {

View File

@ -13,7 +13,7 @@
name, pad16, static_lifetime, Address, AdtId, Arc, BuiltinType, Evaluator, FunctionId, name, pad16, static_lifetime, Address, AdtId, Arc, BuiltinType, Evaluator, FunctionId,
HasModule, HirDisplay, Interned, InternedClosure, Interner, Interval, IntervalAndTy, HasModule, HirDisplay, Interned, InternedClosure, Interner, Interval, IntervalAndTy,
IntervalOrOwned, ItemContainerId, LangItem, Layout, Locals, Lookup, MirEvalError, MirSpan, IntervalOrOwned, ItemContainerId, LangItem, Layout, Locals, Lookup, MirEvalError, MirSpan,
ModPath, Mutability, Result, Substitution, Ty, TyBuilder, TyExt, Mutability, Result, Substitution, Ty, TyBuilder, TyExt,
}; };
mod simd; mod simd;
@ -158,6 +158,25 @@ pub(super) fn detect_and_exec_special_function(
Ok(false) 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 /// Clone has special impls for tuples and function pointers
fn exec_clone( fn exec_clone(
&mut self, &mut self,
@ -291,9 +310,14 @@ fn detect_lang_function(&self, def: FunctionId) -> Option<LangItem> {
use LangItem::*; use LangItem::*;
let candidate = self.db.lang_attr(def.into())?; let candidate = self.db.lang_attr(def.into())?;
// We want to execute these functions with special logic // 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); 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 None
} }
@ -309,43 +333,6 @@ fn exec_lang_item(
let mut args = args.iter(); let mut args = args.iter();
match it { match it {
BeginPanic => Err(MirEvalError::Panic("<unknown-panic-payload>".to_owned())), 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 => { SliceLen => {
let arg = args.next().ok_or(MirEvalError::InternalError( let arg = args.next().ok_or(MirEvalError::InternalError(
"argument of <[T]>::len() is not provided".into(), "argument of <[T]>::len() is not provided".into(),

View File

@ -5105,6 +5105,32 @@ fn foo(e: E) {
); );
} }
#[test]
fn hover_const_value() {
check(
r#"
pub enum AA {
BB,
}
const CONST: AA = AA::BB;
pub fn the_function() -> AA {
CON$0ST
}
"#,
expect![[r#"
*CONST*
```rust
test
```
```rust
const CONST: AA = BB
```
"#]],
);
}
#[test] #[test]
fn array_repeat_exp() { fn array_repeat_exp() {
check( check(