Unnamed repository; edit this file 'description' to name the repository.
fix: Fix `render_const_using_debug_impl` constructing outdated std layouts
| -rw-r--r-- | crates/hir-ty/src/consteval/tests/intrinsics.rs | 22 | ||||
| -rw-r--r-- | crates/hir-ty/src/mir/eval.rs | 94 | ||||
| -rw-r--r-- | crates/hir-ty/src/mir/eval/shim.rs | 85 | ||||
| -rw-r--r-- | crates/hir-ty/src/mir/eval/tests.rs | 69 | ||||
| -rw-r--r-- | crates/ide-completion/src/tests/attribute.rs | 4 | ||||
| -rw-r--r-- | crates/ide-completion/src/tests/expression.rs | 1 | ||||
| -rw-r--r-- | crates/ide/src/hover/render.rs | 7 | ||||
| -rw-r--r-- | crates/test-utils/src/minicore.rs | 34 |
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() -> ! { |