Unnamed repository; edit this file 'description' to name the repository.
fix: Fix `render_const_using_debug_impl` constructing outdated std layouts
Lukas Wirth 4 weeks ago
parent ee5de0e · commit 0e5379e
-rw-r--r--crates/hir-ty/src/consteval/tests/intrinsics.rs22
-rw-r--r--crates/hir-ty/src/mir/eval.rs94
-rw-r--r--crates/hir-ty/src/mir/eval/shim.rs85
-rw-r--r--crates/hir-ty/src/mir/eval/tests.rs69
-rw-r--r--crates/ide-completion/src/tests/attribute.rs4
-rw-r--r--crates/ide-completion/src/tests/expression.rs1
-rw-r--r--crates/ide/src/hover/render.rs7
-rw-r--r--crates/test-utils/src/minicore.rs34
8 files changed, 291 insertions, 25 deletions
diff --git a/crates/hir-ty/src/consteval/tests/intrinsics.rs b/crates/hir-ty/src/consteval/tests/intrinsics.rs
index 1772e3c172..96516d35d4 100644
--- a/crates/hir-ty/src/consteval/tests/intrinsics.rs
+++ b/crates/hir-ty/src/consteval/tests/intrinsics.rs
@@ -225,6 +225,28 @@ fn const_eval_select() {
}
#[test]
+fn const_allocate() {
+ check_number(
+ r#"
+ //- minicore: fn
+ #[rustc_intrinsic]
+ pub const unsafe fn const_allocate(size: usize, align: usize) -> *mut u8;
+ #[rustc_intrinsic]
+ pub const unsafe fn const_deallocate(ptr: *mut u8, size: usize, align: usize);
+
+ const GOAL: u8 = unsafe {
+ let ptr = const_allocate(4, 4);
+ *ptr = 5;
+ let value = *ptr;
+ const_deallocate(ptr, 4, 4);
+ value
+ };
+ "#,
+ 5,
+ );
+}
+
+#[test]
fn wrapping_add() {
check_number(
r#"
diff --git a/crates/hir-ty/src/mir/eval.rs b/crates/hir-ty/src/mir/eval.rs
index 855dafe2d9..b968f33e81 100644
--- a/crates/hir-ty/src/mir/eval.rs
+++ b/crates/hir-ty/src/mir/eval.rs
@@ -738,6 +738,42 @@ impl<'a, 'db: 'a> Evaluator<'a, 'db> {
self.cached_ptr_size
}
+ fn caller_location_fields(&self, owner: InferBodyId, span: MirSpan) -> (String, u32, u32) {
+ let Some((file_id, text_range)) = self.resolve_mir_span(owner, span) else {
+ return (String::new(), 0, 0);
+ };
+ let source_root = self.db.file_source_root(file_id).source_root_id(self.db);
+ let source_root = self.db.source_root(source_root).source_root(self.db);
+ let path = source_root.path_for_file(&file_id).map(|path| path.to_string());
+ let (line, col) = self.db.line_column(file_id, text_range.start()).unwrap_or((0, 0));
+ (path.unwrap_or_default(), line + 1, col + 1)
+ }
+
+ fn resolve_mir_span(&self, owner: InferBodyId, span: MirSpan) -> Option<(FileId, TextRange)> {
+ let (source_map, self_param_syntax) = match owner {
+ InferBodyId::DefWithBodyId(def) => {
+ let body = &Body::with_source_map(self.db, def).1;
+ (&**body, body.self_param_syntax())
+ }
+ InferBodyId::AnonConstId(def) => {
+ (ExpressionStore::with_source_map(self.db, def.loc(self.db).owner).1, None)
+ }
+ };
+ let span: InFile<SyntaxNodePtr> = match span {
+ MirSpan::ExprId(e) => source_map.expr_syntax(e).ok()?.map(|it| it.into()),
+ MirSpan::PatId(p) => source_map.pat_syntax(p).ok()?.map(|it| it.syntax_node_ptr()),
+ MirSpan::BindingId(b) => source_map
+ .patterns_for_binding(b)
+ .iter()
+ .find_map(|p| source_map.pat_syntax(*p).ok())?
+ .map(|it| it.syntax_node_ptr()),
+ MirSpan::SelfParam => self_param_syntax?.map(|it| it.syntax_node_ptr()),
+ MirSpan::Unknown => return None,
+ };
+ let file_id = span.file_id.original_file(self.db);
+ Some((file_id.file_id(self.db), span.value.text_range()))
+ }
+
fn projected_ty(&self, ty: PlaceTy<'db>, proj: PlaceElem) -> PlaceTy<'db> {
let pair = (ty, proj);
if let Some(r) = self.projected_ty_cache.borrow().get(&pair) {
@@ -3181,26 +3217,33 @@ pub fn render_const_using_debug_impl<'db>(
let Some(debug_fmt_fn) = lang_items.Debug_fmt else {
not_supported!("core::fmt::Debug::fmt not found");
};
- // a1 = &[""]
- let a1 = evaluator.heap_allocate(evaluator.ptr_size() * 2, evaluator.ptr_size())?;
- // a2 = &[::core::fmt::ArgumentV1::new(&(THE_CONST), ::core::fmt::Debug::fmt)]
- // FIXME: we should call the said function, but since its name is going to break in the next rustc version
- // and its ABI doesn't break yet, we put it in memory manually.
- let a2 = evaluator.heap_allocate(evaluator.ptr_size() * 2, evaluator.ptr_size())?;
- evaluator.write_memory(a2, &data.addr.to_bytes())?;
+ let ptr_size = evaluator.ptr_size();
+ // Construct the arguments of `format_args!("{:?}", THE_CONST)` directly in memory and hand
+ // them to `std::fmt::format`.
+ //
+ // `core::fmt::rt::Argument` is a niche-encoded `Placeholder { value: NonNull<()>, formatter }`,
+ // i.e. two words: a pointer to the value, and the type-erased `<T as Debug>::fmt` function.
+ // A non-null `value` is what distinguishes the `Placeholder` variant from `Count`.
+ let argument = evaluator.heap_allocate(ptr_size * 2, ptr_size)?;
+ evaluator.write_memory(argument, &data.addr.to_bytes())?;
let debug_fmt_fn_ptr = evaluator.vtable_map.id(Ty::new_fn_def(
evaluator.interner(),
CallableDefId::FunctionId(debug_fmt_fn).into(),
GenericArgs::new_from_slice(&[ty.into()]),
));
- evaluator.write_memory(a2.offset(evaluator.ptr_size()), &debug_fmt_fn_ptr.to_le_bytes())?;
- // a3 = ::core::fmt::Arguments::new_v1(a1, a2)
- // FIXME: similarly, we should call function here, not directly working with memory.
- let a3 = evaluator.heap_allocate(evaluator.ptr_size() * 6, evaluator.ptr_size())?;
- evaluator.write_memory(a3, &a1.to_bytes())?;
- evaluator.write_memory(a3.offset(evaluator.ptr_size()), &[1])?;
- evaluator.write_memory(a3.offset(2 * evaluator.ptr_size()), &a2.to_bytes())?;
- evaluator.write_memory(a3.offset(3 * evaluator.ptr_size()), &[1])?;
+ evaluator.write_memory(argument.offset(ptr_size), &debug_fmt_fn_ptr.to_le_bytes())?;
+ // Since Rust 1.93 `core::fmt::Arguments` is two words wide:
+ // struct Arguments<'a> { template: NonNull<u8>, args: NonNull<Argument<'a>> }
+ // `template` points at a byte-encoded format string; `format_args!("{:?}", x)` encodes to a
+ // single default placeholder (`0xC0`) followed by the end marker (`0x00`). `args` points at
+ // our one-element argument array, and must stay pointer-aligned: `core` uses the low bit of
+ // `args` as a tag (1 = inline `&str` form, 0 = placeholder form), and heap allocations here
+ // are pointer-aligned so the bit is 0 as required.
+ let template = evaluator.heap_allocate(2, 1)?;
+ evaluator.write_memory(template, &[0xC0, 0x00])?;
+ let arguments = evaluator.heap_allocate(ptr_size * 2, ptr_size)?;
+ evaluator.write_memory(arguments, &template.to_bytes())?;
+ evaluator.write_memory(arguments.offset(ptr_size), &argument.to_bytes())?;
let Some(ValueNs::FunctionId(format_fn)) = resolver.resolve_path_in_value_ns_fully(
db,
&hir_def::expr_store::path::Path::from_known_path_with_no_generic(path![std::fmt::format]),
@@ -3210,13 +3253,24 @@ pub fn render_const_using_debug_impl<'db>(
};
let interval = evaluator.interpret_mir(
db.mir_body(format_fn.into()).map_err(|e| MirEvalError::MirLowerError(format_fn, e))?,
- [IntervalOrOwned::Borrowed(Interval { addr: a3, size: evaluator.ptr_size() * 6 })]
- .into_iter(),
+ [IntervalOrOwned::Borrowed(Interval { addr: arguments, size: ptr_size * 2 })].into_iter(),
)?;
let message_string = interval.get(&evaluator)?;
- let addr =
- Address::from_bytes(&message_string[evaluator.ptr_size()..2 * evaluator.ptr_size()])?;
- let size = from_bytes!(usize, message_string[2 * evaluator.ptr_size()..]);
+ let words = [
+ from_bytes!(usize, message_string[0..ptr_size]),
+ from_bytes!(usize, message_string[ptr_size..2 * ptr_size]),
+ from_bytes!(usize, message_string[2 * ptr_size..3 * ptr_size]),
+ ];
+ let Some(addr) = words.into_iter().map(Address::from_usize).find(|it| matches!(it, Heap(_)))
+ else {
+ // No heap buffer means the formatted string is empty.
+ return Ok(String::new());
+ };
+ let size = words
+ .into_iter()
+ .filter(|&it| !matches!(Address::from_usize(it), Heap(_)))
+ .min()
+ .unwrap_or(0);
Ok(std::string::String::from_utf8_lossy(evaluator.read_memory(addr, size)?).into_owned())
}
diff --git a/crates/hir-ty/src/mir/eval/shim.rs b/crates/hir-ty/src/mir/eval/shim.rs
index 7a6cbb8ca3..d2a74f20a5 100644
--- a/crates/hir-ty/src/mir/eval/shim.rs
+++ b/crates/hir-ty/src/mir/eval/shim.rs
@@ -1062,12 +1062,15 @@ impl<'a, 'db: 'a> Evaluator<'a, 'db> {
let ans = ptr + offset * size;
destination.write_from_bytes(self, &ans.to_le_bytes()[0..destination.size])
}
- "assert_inhabited" | "assert_zero_valid" | "assert_uninit_valid" | "assume" => {
+ "assert_inhabited"
+ | "assert_zero_valid"
+ | "assert_uninit_valid"
+ | "assert_mem_uninitialized_valid" => {
// FIXME: We should actually implement these checks
Ok(())
}
"forget" => {
- // We don't call any drop glue yet, so there is nothing here
+ // FIXME
Ok(())
}
"transmute" | "transmute_unchecked" => {
@@ -1334,6 +1337,84 @@ impl<'a, 'db: 'a> Evaluator<'a, 'db> {
.write_from_interval(self, meta.interval)?;
Ok(())
}
+ "fabs" => {
+ let [arg] = args else {
+ return Err(MirEvalError::InternalError(
+ "fabs intrinsic signature doesn't match fn (T) -> T".into(),
+ ));
+ };
+ let mut bytes = arg.get(self)?.to_vec();
+ if let Some(sign_byte) = bytes.last_mut() {
+ *sign_byte &= 0x7f;
+ }
+ destination.write_from_bytes(self, &bytes)
+ }
+ "unreachable" => {
+ return Err(MirEvalError::UndefinedBehavior(
+ "`unreachable` intrinsic executed".to_owned(),
+ ));
+ }
+ "const_allocate" => {
+ let [size, align] = args else {
+ return Err(MirEvalError::InternalError(
+ "const_allocate args are not provided".into(),
+ ));
+ };
+ let size = from_bytes!(usize, size.get(self)?);
+ let align = from_bytes!(usize, align.get(self)?);
+ let result = self.heap_allocate(size, align)?;
+ destination.write_from_bytes(self, &result.to_bytes())
+ }
+ "const_deallocate" => Ok(()),
+ "caller_location" => {
+ let Some(location_adt) = self.lang_items().PanicLocation else {
+ not_supported!("`caller_location` requires the `panic_location` lang item");
+ };
+ let location_ty = self.db.ty(location_adt.into()).skip_binder();
+ let TyKind::Adt(_, subst) = location_ty.kind() else {
+ return Err(MirEvalError::InternalError(
+ "`panic_location` lang item is not an ADT".into(),
+ ));
+ };
+ let layout = self.layout(location_ty)?;
+ let (file, line, col) = self.caller_location_fields(locals.body.owner, span);
+ let file_len = file.len();
+ let file_addr = self.heap_allocate(file_len + 1, 1)?;
+ self.write_memory(file_addr, file.as_bytes())?;
+ let ptr_size = self.ptr_size();
+ let field_types = self.db.field_types(location_adt.into());
+ let mut line_col = [line, col].into_iter();
+ let mut fields = Vec::with_capacity(field_types.iter().count());
+ for (_, field) in field_types.iter() {
+ let field_ty = field.ty().instantiate(self.interner(), subst).skip_norm_wip();
+ let bytes =
+ if matches!(field_ty.kind(), TyKind::Uint(rustc_type_ir::UintTy::U32)) {
+ line_col.next().unwrap_or(0).to_le_bytes().to_vec()
+ } else {
+ let size =
+ self.size_of_sized(field_ty, locals, "caller_location field")?;
+ if size == ptr_size * 2 {
+ // The string slice pointing at the file name: (data pointer, length).
+ let mut bytes = file_addr.to_bytes()[..ptr_size].to_vec();
+ bytes.extend_from_slice(&file_len.to_le_bytes()[..ptr_size]);
+ bytes
+ } else {
+ vec![0; size]
+ }
+ };
+ fields.push(IntervalOrOwned::Owned(bytes));
+ }
+ let location = self.construct_with_layout(
+ layout.size.bytes_usize(),
+ &layout,
+ None,
+ fields.into_iter(),
+ )?;
+ let location_addr =
+ self.heap_allocate(layout.size.bytes_usize(), layout.align.bytes() as usize)?;
+ self.write_memory(location_addr, &location)?;
+ destination.write_from_bytes(self, &location_addr.to_bytes()[..ptr_size])
+ }
_ if needs_override => not_supported!("intrinsic {name} is not implemented"),
_ => return Ok(false),
}
diff --git a/crates/hir-ty/src/mir/eval/tests.rs b/crates/hir-ty/src/mir/eval/tests.rs
index ccc4815d6c..622519445c 100644
--- a/crates/hir-ty/src/mir/eval/tests.rs
+++ b/crates/hir-ty/src/mir/eval/tests.rs
@@ -1084,3 +1084,72 @@ fn main() {
"#,
);
}
+
+#[test]
+fn fabs_intrinsic() {
+ check_pass(
+ r#"
+//- minicore: copy, panic
+pub unsafe trait FloatPrimitive: Sized + Copy {}
+unsafe impl FloatPrimitive for f32 {}
+unsafe impl FloatPrimitive for f64 {}
+
+#[rustc_intrinsic]
+fn fabs<T: FloatPrimitive>(x: T) -> T;
+
+fn should_not_reach() { panic!() }
+
+fn main() {
+ if fabs(-3.5f32) != 3.5f32 {
+ should_not_reach();
+ }
+ if fabs(3.5f32) != 3.5f32 {
+ should_not_reach();
+ }
+ if fabs(-3.5f64) != 3.5f64 {
+ should_not_reach();
+ }
+}
+"#,
+ );
+}
+
+#[test]
+fn unreachable_intrinsic() {
+ check_error_with(
+ r#"
+#[rustc_intrinsic]
+fn unreachable() -> !;
+
+fn main() {
+ unreachable();
+}
+"#,
+ |e| {
+ let mut err = &e;
+ while let MirEvalError::InFunction(inner, _) = err {
+ err = inner;
+ }
+ matches!(err, MirEvalError::UndefinedBehavior(_))
+ },
+ );
+}
+
+#[test]
+fn caller_location_intrinsic() {
+ check_pass(
+ r#"
+//- minicore: panic_location
+fn should_not_reach() {
+ panic!()
+}
+
+fn main() {
+ let loc = core::panic::Location::caller();
+ if loc.line() != 1 || loc.column() != 1 {
+ should_not_reach();
+ }
+}
+"#,
+ );
+}
diff --git a/crates/ide-completion/src/tests/attribute.rs b/crates/ide-completion/src/tests/attribute.rs
index 300ea9bb11..bcc103b89f 100644
--- a/crates/ide-completion/src/tests/attribute.rs
+++ b/crates/ide-completion/src/tests/attribute.rs
@@ -1157,6 +1157,7 @@ mod derive {
de PartialEq, Eq, PartialOrd, Ord
de PartialEq, PartialOrd
md core::
+ md panic::
kw crate::
kw self::
"#]],
@@ -1179,6 +1180,7 @@ mod derive {
de Eq, PartialOrd, Ord
de PartialOrd
md core::
+ md panic::
kw crate::
kw self::
"#]],
@@ -1201,6 +1203,7 @@ mod derive {
de Eq, PartialOrd, Ord
de PartialOrd
md core::
+ md panic::
kw crate::
kw self::
"#]],
@@ -1222,6 +1225,7 @@ mod derive {
de PartialOrd
de PartialOrd, Ord
md core::
+ md panic::
kw crate::
kw self::
"#]],
diff --git a/crates/ide-completion/src/tests/expression.rs b/crates/ide-completion/src/tests/expression.rs
index adf4dda184..0e558cf6a2 100644
--- a/crates/ide-completion/src/tests/expression.rs
+++ b/crates/ide-completion/src/tests/expression.rs
@@ -3278,6 +3278,7 @@ fn bar() {
ma panic!(…) macro_rules! panic
ma print!(…) macro_rules! print
md core::
+ md panic::
md result:: (use core::result)
md rust_2015:: (use core::prelude::rust_2015)
md rust_2018:: (use core::prelude::rust_2018)
diff --git a/crates/ide/src/hover/render.rs b/crates/ide/src/hover/render.rs
index fe94e169ed..be133e40b2 100644
--- a/crates/ide/src/hover/render.rs
+++ b/crates/ide/src/hover/render.rs
@@ -525,7 +525,8 @@ pub(super) fn definition(
let body = it.eval(db);
Some(match body {
Ok(it) => match it.render_debug(db) {
- Ok(it) => it,
+ Ok(rendered) if rendered.is_empty() => it.render(db, display_target),
+ Ok(rendered) => rendered,
Err(err) => {
let it = it.render(db, display_target);
if env::var_os("RA_DEV").is_some() {
@@ -557,7 +558,9 @@ pub(super) fn definition(
let body = it.eval(db);
Some(match body {
Ok(it) => match it.render_debug(db) {
- Ok(it) => it,
+ Ok(rendered) if rendered.is_empty() => it.render(db, display_target),
+ Ok(rendered) => rendered,
+
Err(err) => {
let it = it.render(db, display_target);
if env::var_os("RA_DEV").is_some() {
diff --git a/crates/test-utils/src/minicore.rs b/crates/test-utils/src/minicore.rs
index 0588b1c77d..f9be47551c 100644
--- a/crates/test-utils/src/minicore.rs
+++ b/crates/test-utils/src/minicore.rs
@@ -59,6 +59,7 @@
//! option: panic
//! ord: eq, option
//! panic: fmt
+//! panic_location: panic
//! pat: panic
//! phantom_data:
//! pin:
@@ -2093,7 +2094,38 @@ pub mod str {
// endregion:str
// region:panic
-mod panic {
+pub mod panic {
+ // region:panic_location
+ #[rustc_intrinsic]
+ pub const fn caller_location() -> &'static Location<'static>;
+
+ #[lang = "panic_location"]
+ pub struct Location<'a> {
+ file: &'a str,
+ line: u32,
+ col: u32,
+ }
+
+ impl<'a> Location<'a> {
+ #[track_caller]
+ pub const fn caller() -> &'static Location<'static> {
+ caller_location()
+ }
+
+ pub const fn file(&self) -> &str {
+ self.file
+ }
+
+ pub const fn line(&self) -> u32 {
+ self.line
+ }
+
+ pub const fn column(&self) -> u32 {
+ self.col
+ }
+ }
+ // endregion:panic_location
+
pub macro panic_2021 {
() => ({
const fn panic_cold_explicit() -> ! {