Skip to content

Commit 0a77ec2

Browse files
committed
Evaluate const panic fmt arguments for builtin types
1 parent c54098f commit 0a77ec2

File tree

8 files changed

+250
-17
lines changed

8 files changed

+250
-17
lines changed

compiler/rustc_const_eval/src/const_eval/machine.rs

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -64,19 +64,12 @@ impl<'mir, 'tcx> InterpCx<'mir, 'tcx, CompileTimeInterpreter<'mir, 'tcx>> {
6464
let (file, line, col) = self.location_triple_for_span(span);
6565
return Err(ConstEvalErrKind::Panic { msg, file, line, col }.into());
6666
} else if Some(def_id) == self.tcx.lang_items().panic_fmt() {
67-
// For panic_fmt, call const_panic_fmt instead.
68-
if let Some(const_panic_fmt) = self.tcx.lang_items().const_panic_fmt() {
69-
return Ok(Some(
70-
ty::Instance::resolve(
71-
*self.tcx,
72-
ty::ParamEnv::reveal_all(),
73-
const_panic_fmt,
74-
self.tcx.intern_substs(&[]),
75-
)
76-
.unwrap()
77-
.unwrap(),
78-
));
79-
}
67+
assert!(args.len() == 1);
68+
let msg = self.eval_const_panic_fmt(args[0])?;
69+
let msg = Symbol::intern(&msg);
70+
let span = self.find_closest_untracked_caller_location();
71+
let (file, line, col) = self.location_triple_for_span(span);
72+
return Err(ConstEvalErrKind::Panic { msg, file, line, col }.into());
8073
}
8174
Ok(None)
8275
}

compiler/rustc_const_eval/src/const_eval/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ mod error;
1818
mod eval_queries;
1919
mod fn_queries;
2020
mod machine;
21+
mod panic;
2122

2223
pub use error::*;
2324
pub use eval_queries::*;
Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
use rustc_hir::def_id::DefId;
2+
use rustc_middle::ty::{self, layout::LayoutOf, subst::Subst};
3+
use std::cell::Cell;
4+
use std::fmt::{self, Debug, Formatter};
5+
6+
use crate::interpret::{FnVal, InterpCx, InterpErrorInfo, InterpResult, OpTy};
7+
8+
use super::CompileTimeInterpreter;
9+
10+
struct Arg<'mir, 'tcx, 'err> {
11+
cx: &'err InterpCx<'mir, 'tcx, CompileTimeInterpreter<'mir, 'tcx>>,
12+
arg: OpTy<'tcx>,
13+
fmt_trait: DefId,
14+
err: &'err Cell<Option<InterpErrorInfo<'tcx>>>,
15+
}
16+
17+
impl Debug for Arg<'_, '_, '_> {
18+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
19+
match self.cx.fmt_arg(self.arg, self.fmt_trait, f) {
20+
Ok(_) => Ok(()),
21+
Err(e) => {
22+
self.err.set(Some(e));
23+
Err(fmt::Error)
24+
}
25+
}
26+
}
27+
}
28+
29+
impl<'mir, 'tcx> InterpCx<'mir, 'tcx, CompileTimeInterpreter<'mir, 'tcx>> {
30+
fn fmt_arg(
31+
&self,
32+
arg: OpTy<'tcx>,
33+
fmt_trait: DefId,
34+
f: &mut Formatter<'_>,
35+
) -> InterpResult<'tcx> {
36+
let fmt_trait_sym = self.tcx.item_name(fmt_trait);
37+
let fmt_trait_name = fmt_trait_sym.as_str();
38+
39+
macro_rules! dispatch_fmt {
40+
($e: expr, $($t: ident)|*) => {
41+
let _ = match &*fmt_trait_name {
42+
$(stringify!($t) => fmt::$t::fmt($e, f),)*
43+
_ => Debug::fmt($e, f),
44+
};
45+
}
46+
}
47+
48+
match arg.layout.ty.kind() {
49+
ty::Bool => {
50+
let v = self.read_scalar(&arg)?.to_bool()?;
51+
dispatch_fmt!(&v, Display);
52+
}
53+
ty::Char => {
54+
let v = self.read_scalar(&arg)?.to_char()?;
55+
dispatch_fmt!(&v, Display);
56+
}
57+
ty::Int(int_ty) => {
58+
let v = self.read_scalar(&arg)?.check_init()?;
59+
let v = match int_ty {
60+
ty::IntTy::I8 => v.to_i8()?.into(),
61+
ty::IntTy::I16 => v.to_i16()?.into(),
62+
ty::IntTy::I32 => v.to_i32()?.into(),
63+
ty::IntTy::I64 => v.to_i64()?.into(),
64+
ty::IntTy::I128 => v.to_i128()?,
65+
ty::IntTy::Isize => v.to_machine_isize(self)?.into(),
66+
};
67+
dispatch_fmt!(
68+
&v,
69+
Display | Binary | Octal | LowerHex | UpperHex | LowerExp | UpperExp
70+
);
71+
}
72+
ty::Uint(int_ty) => {
73+
let v = self.read_scalar(&arg)?.check_init()?;
74+
let v = match int_ty {
75+
ty::UintTy::U8 => v.to_u8()?.into(),
76+
ty::UintTy::U16 => v.to_u16()?.into(),
77+
ty::UintTy::U32 => v.to_u32()?.into(),
78+
ty::UintTy::U64 => v.to_u64()?.into(),
79+
ty::UintTy::U128 => v.to_u128()?,
80+
ty::UintTy::Usize => v.to_machine_usize(self)?.into(),
81+
};
82+
dispatch_fmt!(
83+
&v,
84+
Display | Binary | Octal | LowerHex | UpperHex | LowerExp | UpperExp
85+
);
86+
}
87+
ty::Float(ty::FloatTy::F32) => {
88+
let v = f32::from_bits(self.read_scalar(&arg)?.to_u32()?);
89+
dispatch_fmt!(&v, Display);
90+
}
91+
ty::Float(ty::FloatTy::F64) => {
92+
let v = f64::from_bits(self.read_scalar(&arg)?.to_u64()?);
93+
dispatch_fmt!(&v, Display);
94+
}
95+
ty::Str => {
96+
let Ok(place) = arg.try_as_mplace() else {
97+
bug!("str is not in MemPlace");
98+
};
99+
let v = self.read_str(&place)?;
100+
dispatch_fmt!(v, Display);
101+
}
102+
ty::Array(..) | ty::Slice(..) => {
103+
let Ok(place) = arg.try_as_mplace() else {
104+
bug!("array/slice is not in MemPlace");
105+
};
106+
let err = Cell::new(None);
107+
let mut debug_list = f.debug_list();
108+
for field in self.mplace_array_fields(&place)? {
109+
debug_list.entry(&Arg { cx: self, arg: field?.into(), fmt_trait, err: &err });
110+
}
111+
let _ = debug_list.finish();
112+
if let Some(e) = err.into_inner() {
113+
return Err(e);
114+
}
115+
}
116+
ty::RawPtr(..) | ty::FnPtr(..) => {
117+
// This isn't precisely how Pointer is implemented, but it's best we can do.
118+
let ptr = self.read_pointer(&arg)?;
119+
let _ = write!(f, "{:?}", ptr);
120+
}
121+
ty::Tuple(substs) => {
122+
let err = Cell::new(None);
123+
let mut debug_tuple = f.debug_tuple("");
124+
for i in 0..substs.len() {
125+
debug_tuple.field(&Arg {
126+
cx: self,
127+
arg: self.operand_field(&arg, i)?,
128+
fmt_trait,
129+
err: &err,
130+
});
131+
}
132+
let _ = debug_tuple.finish();
133+
if let Some(e) = err.into_inner() {
134+
return Err(e);
135+
}
136+
}
137+
138+
// FIXME(nbdd0121): extend to allow fmt trait as super trait
139+
ty::Dynamic(list, _) if list.principal_def_id() == Some(fmt_trait) => {
140+
let Ok(place) = arg.try_as_mplace() else {
141+
bug!("dyn is not in MemPlace");
142+
};
143+
let place = self.unpack_dyn_trait(&place)?.1;
144+
return self.fmt_arg(place.into(), fmt_trait, f);
145+
}
146+
147+
ty::Ref(..) if fmt_trait_name == "Pointer" => {
148+
let ptr = self.read_pointer(&arg)?;
149+
let _ = write!(f, "{:?}", ptr);
150+
}
151+
ty::Ref(..) => {
152+
// FIXME(nbdd0121): User can implement trait on &UserType, so this isn't always correct.
153+
let place = self.deref_operand(&arg)?;
154+
return self.fmt_arg(place.into(), fmt_trait, f);
155+
}
156+
157+
// FIXME(nbdd0121): ty::Adt(..) => (),
158+
_ => {
159+
let _ = write!(f, "<failed to format {}>", arg.layout.ty);
160+
}
161+
}
162+
Ok(())
163+
}
164+
165+
fn fmt_arguments(&self, arguments: OpTy<'tcx>, f: &mut Formatter<'_>) -> InterpResult<'tcx> {
166+
// Check we are dealing with the simple form
167+
let fmt_variant_idx = self.read_discriminant(&self.operand_field(&arguments, 1)?)?.1;
168+
if fmt_variant_idx.as_usize() != 0 {
169+
// FIXME(nbdd0121): implement complex format
170+
let _ = write!(f, "<cannot evaluate complex format>");
171+
return Ok(());
172+
}
173+
174+
// `pieces: &[&str]`
175+
let pieces_place = self.deref_operand(&self.operand_field(&arguments, 0)?)?;
176+
let mut pieces = Vec::new();
177+
for piece in self.mplace_array_fields(&pieces_place)? {
178+
let piece: OpTy<'tcx> = piece?.into();
179+
pieces.push(self.read_str(&self.deref_operand(&piece)?)?);
180+
}
181+
182+
// `args: &[ArgumentV1]`
183+
let args_place = self.deref_operand(&self.operand_field(&arguments, 2)?)?;
184+
let mut args = Vec::new();
185+
let err = Cell::new(None);
186+
for arg in self.mplace_array_fields(&args_place)? {
187+
let arg: OpTy<'tcx> = arg?.into();
188+
189+
let fmt_fn = self.memory.get_fn(self.read_pointer(&self.operand_field(&arg, 1)?)?)?;
190+
let fmt_fn = match fmt_fn {
191+
FnVal::Instance(instance) => instance,
192+
FnVal::Other(o) => match o {},
193+
};
194+
195+
// The formatter must an instance of fmt method of a fmt trait.
196+
let Some(fmt_impl) = self.tcx.impl_of_method(fmt_fn.def_id()) else {
197+
throw_unsup_format!("fmt function is not from trait impl")
198+
};
199+
let Some(fmt_trait) = self.tcx.impl_trait_ref(fmt_impl) else {
200+
throw_unsup_format!("fmt function is not from trait impl")
201+
};
202+
203+
// Retrieve the trait ref with concrete self ty.
204+
let fmt_trait = fmt_trait.subst(*self.tcx, &fmt_fn.substs);
205+
206+
// Change the opaque type into the actual type.
207+
let mut value_place = self.deref_operand(&self.operand_field(&arg, 0)?)?;
208+
value_place.layout = self.layout_of(fmt_trait.self_ty())?;
209+
210+
args.push(Arg {
211+
cx: self,
212+
arg: value_place.into(),
213+
fmt_trait: fmt_trait.def_id,
214+
err: &err,
215+
});
216+
}
217+
218+
// SAFETY: This transmutes `&[&str]` to `&[&'static str]` so it can be used in
219+
// `core::fmt::Arguments`. The slice will not be used after `write_fmt`.
220+
let static_pieces = unsafe { core::mem::transmute(&pieces[..]) };
221+
let arg_v1s = args.iter().map(|x| fmt::ArgumentV1::new(x, Debug::fmt)).collect::<Vec<_>>();
222+
let fmt_args = fmt::Arguments::new_v1(static_pieces, &arg_v1s);
223+
let _ = f.write_fmt(fmt_args);
224+
if let Some(v) = err.into_inner() {
225+
return Err(v);
226+
}
227+
Ok(())
228+
}
229+
230+
pub(super) fn eval_const_panic_fmt(
231+
&mut self,
232+
arguments: OpTy<'tcx>,
233+
) -> InterpResult<'tcx, String> {
234+
let mut msg = String::new();
235+
let mut formatter = Formatter::new(&mut msg);
236+
self.fmt_arguments(arguments, &mut formatter)?;
237+
Ok(msg)
238+
}
239+
}

compiler/rustc_const_eval/src/interpret/place.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -420,7 +420,7 @@ where
420420

421421
// Iterates over all fields of an array. Much more efficient than doing the
422422
// same by repeatedly calling `mplace_array`.
423-
pub(super) fn mplace_array_fields(
423+
pub(crate) fn mplace_array_fields(
424424
&self,
425425
base: &'a MPlaceTy<'tcx, Tag>,
426426
) -> InterpResult<'tcx, impl Iterator<Item = InterpResult<'tcx, MPlaceTy<'tcx, Tag>>> + 'a>
@@ -1082,7 +1082,7 @@ where
10821082

10831083
/// Turn a place with a `dyn Trait` type into a place with the actual dynamic type.
10841084
/// Also return some more information so drop doesn't have to run the same code twice.
1085-
pub(super) fn unpack_dyn_trait(
1085+
pub(crate) fn unpack_dyn_trait(
10861086
&self,
10871087
mplace: &MPlaceTy<'tcx, M::PointerTag>,
10881088
) -> InterpResult<'tcx, (ty::Instance<'tcx>, MPlaceTy<'tcx, M::PointerTag>)> {

compiler/rustc_const_eval/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ Rust MIR: a lowered representation of Rust.
2323
#![feature(trusted_len)]
2424
#![feature(trusted_step)]
2525
#![feature(try_blocks)]
26+
#![feature(fmt_internals)]
2627
#![recursion_limit = "256"]
2728

2829
#[macro_use]

compiler/rustc_hir/src/lang_items.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,6 @@ language_item_table! {
285285
PanicFmt, sym::panic_fmt, panic_fmt, Target::Fn, GenericRequirement::None;
286286
PanicDisplay, sym::panic_display, panic_display, Target::Fn, GenericRequirement::None;
287287
PanicStr, sym::panic_str, panic_str, Target::Fn, GenericRequirement::None;
288-
ConstPanicFmt, sym::const_panic_fmt, const_panic_fmt, Target::Fn, GenericRequirement::None;
289288
PanicBoundsCheck, sym::panic_bounds_check, panic_bounds_check_fn, Target::Fn, GenericRequirement::Exact(0);
290289
PanicInfo, sym::panic_info, panic_info, Target::Struct, GenericRequirement::None;
291290
PanicLocation, sym::panic_location, panic_location, Target::Struct, GenericRequirement::None;

compiler/rustc_span/src/symbol.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -465,7 +465,6 @@ symbols! {
465465
const_loop,
466466
const_mut_refs,
467467
const_panic,
468-
const_panic_fmt,
469468
const_precise_live_drops,
470469
const_ptr,
471470
const_raw_ptr_deref,

library/core/src/panicking.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ pub const fn panic_fmt(fmt: fmt::Arguments<'_>) -> ! {
107107
}
108108

109109
/// This function is used instead of panic_fmt in const eval.
110+
#[cfg(bootstrap)]
110111
#[lang = "const_panic_fmt"]
111112
pub const fn const_panic_fmt(fmt: fmt::Arguments<'_>) -> ! {
112113
if let Some(msg) = fmt.as_str() {

0 commit comments

Comments
 (0)