Skip to content

Commit ada455d

Browse files
committed
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.
1 parent e265e3d commit ada455d

File tree

6 files changed

+121
-50
lines changed

6 files changed

+121
-50
lines changed

crates/hir-ty/src/mir/eval.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -2317,7 +2317,7 @@ impl Evaluator<'_> {
23172317

23182318
fn exec_fn_with_args(
23192319
&mut self,
2320-
def: FunctionId,
2320+
mut def: FunctionId,
23212321
args: &[IntervalAndTy],
23222322
generic_args: Substitution,
23232323
locals: &Locals,
@@ -2335,6 +2335,9 @@ impl Evaluator<'_> {
23352335
)? {
23362336
return Ok(None);
23372337
}
2338+
if let Some(redirect_def) = self.detect_and_redirect_special_function(def)? {
2339+
def = redirect_def;
2340+
}
23382341
let arg_bytes = args.iter().map(|it| IntervalOrOwned::Borrowed(it.interval));
23392342
match self.get_mir_or_dyn_index(def, generic_args.clone(), locals, span)? {
23402343
MirOrDynIndex::Dyn(self_ty_idx) => {

crates/hir-ty/src/mir/eval/shim.rs

+26-39
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use crate::mir::eval::{
1313
name, pad16, static_lifetime, Address, AdtId, Arc, BuiltinType, Evaluator, FunctionId,
1414
HasModule, HirDisplay, Interned, InternedClosure, Interner, Interval, IntervalAndTy,
1515
IntervalOrOwned, ItemContainerId, LangItem, Layout, Locals, Lookup, MirEvalError, MirSpan,
16-
ModPath, Mutability, Result, Substitution, Ty, TyBuilder, TyExt,
16+
Mutability, Result, Substitution, Ty, TyBuilder, TyExt,
1717
};
1818

1919
mod simd;
@@ -158,6 +158,25 @@ impl Evaluator<'_> {
158158
Ok(false)
159159
}
160160

161+
pub(super) fn detect_and_redirect_special_function(
162+
&mut self,
163+
def: FunctionId,
164+
) -> Result<Option<FunctionId>> {
165+
// `PanicFmt` is redirected to `ConstPanicFmt`
166+
if let Some(LangItem::PanicFmt) = self.db.lang_attr(def.into()) {
167+
let resolver =
168+
self.db.crate_def_map(self.crate_id).crate_root().resolver(self.db.upcast());
169+
170+
let Some(hir_def::lang_item::LangItemTarget::Function(const_panic_fmt)) =
171+
self.db.lang_item(resolver.krate(), LangItem::ConstPanicFmt)
172+
else {
173+
not_supported!("const_panic_fmt lang item not found or not a function");
174+
};
175+
return Ok(Some(const_panic_fmt));
176+
}
177+
Ok(None)
178+
}
179+
161180
/// Clone has special impls for tuples and function pointers
162181
fn exec_clone(
163182
&mut self,
@@ -291,9 +310,14 @@ impl Evaluator<'_> {
291310
use LangItem::*;
292311
let candidate = self.db.lang_attr(def.into())?;
293312
// We want to execute these functions with special logic
294-
if [PanicFmt, BeginPanic, SliceLen, DropInPlace].contains(&candidate) {
313+
// `PanicFmt` is not detected here as it's redirected later.
314+
if [BeginPanic, SliceLen, DropInPlace].contains(&candidate) {
295315
return Some(candidate);
296316
}
317+
if self.db.attrs(def.into()).by_key("rustc_const_panic_str").exists() {
318+
// `#[rustc_const_panic_str]` is treated like `lang = "begin_panic"` by rustc CTFE.
319+
return Some(LangItem::BeginPanic);
320+
}
297321
None
298322
}
299323

@@ -309,43 +333,6 @@ impl Evaluator<'_> {
309333
let mut args = args.iter();
310334
match it {
311335
BeginPanic => Err(MirEvalError::Panic("<unknown-panic-payload>".to_owned())),
312-
PanicFmt => {
313-
let message = (|| {
314-
let resolver = self
315-
.db
316-
.crate_def_map(self.crate_id)
317-
.crate_root()
318-
.resolver(self.db.upcast());
319-
let Some(format_fn) = resolver.resolve_path_in_value_ns_fully(
320-
self.db.upcast(),
321-
&hir_def::path::Path::from_known_path_with_no_generic(
322-
ModPath::from_segments(
323-
hir_expand::mod_path::PathKind::Abs,
324-
[name![std], name![fmt], name![format]],
325-
),
326-
),
327-
) else {
328-
not_supported!("std::fmt::format not found");
329-
};
330-
let hir_def::resolver::ValueNs::FunctionId(format_fn) = format_fn else {
331-
not_supported!("std::fmt::format is not a function")
332-
};
333-
let interval = self.interpret_mir(
334-
self.db
335-
.mir_body(format_fn.into())
336-
.map_err(|e| MirEvalError::MirLowerError(format_fn, e))?,
337-
args.map(|x| IntervalOrOwned::Owned(x.clone())),
338-
)?;
339-
let message_string = interval.get(self)?;
340-
let addr =
341-
Address::from_bytes(&message_string[self.ptr_size()..2 * self.ptr_size()])?;
342-
let size = from_bytes!(usize, message_string[2 * self.ptr_size()..]);
343-
Ok(std::string::String::from_utf8_lossy(self.read_memory(addr, size)?)
344-
.into_owned())
345-
})()
346-
.unwrap_or_else(|e| format!("Failed to render panic format args: {e:?}"));
347-
Err(MirEvalError::Panic(message))
348-
}
349336
SliceLen => {
350337
let arg = args.next().ok_or(MirEvalError::InternalError(
351338
"argument of <[T]>::len() is not provided".into(),

crates/hir-ty/src/mir/eval/tests.rs

+37
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ fn eval_main(db: &TestDB, file_id: FileId) -> Result<(String, String), MirEvalEr
3131
db.trait_environment(func_id.into()),
3232
)
3333
.map_err(|e| MirEvalError::MirLowerError(func_id, e))?;
34+
3435
let (result, output) = interpret_mir(db, body, false, None);
3536
result?;
3637
Ok((output.stdout().into_owned(), output.stderr().into_owned()))
@@ -87,6 +88,42 @@ fn main() {
8788
);
8889
}
8990

91+
#[test]
92+
fn panic_fmt() {
93+
// panic!
94+
// -> panic_2021 (builtin macro redirection)
95+
// -> #[lang = "panic_fmt"] core::panicking::panic_fmt (hooked by CTFE for redirection)
96+
// -> core::panicking::const_panic_fmt
97+
// -> #[rustc_const_panic_str] core::panicking::panic_display (hooked by CTFE for builtin panic)
98+
// -> Err(ConstEvalError::Panic)
99+
check_pass(
100+
r#"
101+
//- minicore: fmt, panic
102+
fn main() {
103+
panic!("hello, world!");
104+
}
105+
"#,
106+
);
107+
panic!("a");
108+
}
109+
110+
#[test]
111+
fn panic_display() {
112+
// panic!
113+
// -> panic_2021 (builtin macro redirection)
114+
// -> #[rustc_const_panic_str] core::panicking::panic_display (hooked by CTFE for builtin panic)
115+
// -> Err(ConstEvalError::Panic)
116+
check_pass(
117+
r#"
118+
//- minicore: fmt, panic
119+
120+
fn main() {
121+
panic!("{}", "hello, world!");
122+
}
123+
"#,
124+
);
125+
}
126+
90127
#[test]
91128
fn drop_basic() {
92129
check_pass(

crates/ide/src/hover/tests.rs

+26
Original file line numberDiff line numberDiff line change
@@ -5105,6 +5105,32 @@ fn foo(e: E) {
51055105
);
51065106
}
51075107

5108+
#[test]
5109+
fn hover_const_value() {
5110+
check(
5111+
r#"
5112+
pub enum AA {
5113+
BB,
5114+
}
5115+
const CONST: AA = AA::BB;
5116+
pub fn the_function() -> AA {
5117+
CON$0ST
5118+
}
5119+
"#,
5120+
expect![[r#"
5121+
*CONST*
5122+
5123+
```rust
5124+
test
5125+
```
5126+
5127+
```rust
5128+
const CONST: AA = BB
5129+
```
5130+
"#]],
5131+
);
5132+
}
5133+
51085134
#[test]
51095135
fn array_repeat_exp() {
51105136
check(

crates/ide/src/syntax_highlighting/test_data/highlight_strings.html

+2-2
Original file line numberDiff line numberDiff line change
@@ -158,9 +158,9 @@
158158
<span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="variable">ничоси</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="comma macro">,</span> <span class="variable declaration macro">ничоси</span> <span class="operator macro">=</span> <span class="numeric_literal macro">92</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
159159

160160
<span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="variable">x</span><span class="format_specifier">?</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> "</span><span class="comma macro">,</span> <span class="unresolved_reference macro">thingy</span><span class="comma macro">,</span> <span class="unresolved_reference macro">n2</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
161-
<span class="macro default_library library">panic</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="comma macro">,</span> <span class="numeric_literal macro">0</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
161+
<span class="macro default_library library">panic</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"{}"</span><span class="comma macro">,</span> <span class="numeric_literal macro">0</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
162162
<span class="macro default_library library">panic</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"more </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="comma macro">,</span> <span class="numeric_literal macro">1</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
163-
<span class="macro default_library library">assert</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="bool_literal macro">true</span><span class="comma macro">,</span> <span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="comma macro">,</span> <span class="numeric_literal macro">1</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
163+
<span class="macro default_library library">assert</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="bool_literal macro">true</span><span class="comma macro">,</span> <span class="string_literal macro">"{}"</span><span class="comma macro">,</span> <span class="numeric_literal macro">1</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
164164
<span class="macro default_library library">assert</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="bool_literal macro">true</span><span class="comma macro">,</span> <span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> asdasd"</span><span class="comma macro">,</span> <span class="numeric_literal macro">1</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
165165
<span class="macro">toho</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"{}fmt"</span><span class="comma macro">,</span> <span class="numeric_literal macro">0</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
166166
<span class="keyword">let</span> <span class="variable declaration">i</span><span class="colon">:</span> <span class="builtin_type">u64</span> <span class="operator">=</span> <span class="numeric_literal">3</span><span class="semicolon">;</span>

crates/test-utils/src/minicore.rs

+26-8
Original file line numberDiff line numberDiff line change
@@ -1356,18 +1356,36 @@ pub mod iter {
13561356
// region:panic
13571357
mod panic {
13581358
pub macro panic_2021 {
1359-
() => (
1360-
$crate::panicking::panic("explicit panic")
1361-
),
1362-
($($t:tt)+) => (
1363-
$crate::panicking::panic_fmt($crate::const_format_args!($($t)+))
1364-
),
1359+
() => ({
1360+
const fn panic_cold_explicit() -> ! {
1361+
$crate::panicking::panic_explicit()
1362+
}
1363+
panic_cold_explicit();
1364+
}),
1365+
// Special-case the single-argument case for const_panic.
1366+
("{}", $arg:expr $(,)?) => ({
1367+
#[rustc_const_panic_str] // enforce a &&str argument in const-check and hook this by const-eval
1368+
const fn panic_cold_display<T: $crate::fmt::Display>(arg: &T) -> ! {
1369+
loop {}
1370+
}
1371+
panic_cold_display(&$arg);
1372+
}),
1373+
($($t:tt)+) => ({
1374+
// Semicolon to prevent temporaries inside the formatting machinery from
1375+
// being considered alive in the caller after the panic_fmt call.
1376+
$crate::panicking::panic_fmt($crate::const_format_args!($($t)+));
1377+
}),
13651378
}
13661379
}
13671380

13681381
mod panicking {
1369-
#[lang = "panic_fmt"]
1370-
pub const fn panic_fmt(_fmt: crate::fmt::Arguments<'_>) -> ! {
1382+
#[rustc_const_panic_str] // enforce a &&str argument in const-check and hook this by const-eval
1383+
pub const fn panic_display<T: fmt::Display>(x: &T) -> ! {
1384+
panic_fmt(format_args!("{}", *x));
1385+
}
1386+
1387+
#[lang = "panic_fmt"] // needed for const-evaluated panics
1388+
pub const fn panic_fmt(fmt: fmt::Arguments<'_>) -> ! {
13711389
loop {}
13721390
}
13731391

0 commit comments

Comments
 (0)