From f5bf3353e6dc78176e2f4f4324f526524e601a92 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Thu, 22 Jan 2026 22:24:26 +0100 Subject: [PATCH] move va_list operations into `InterpCx` --- compiler/rustc_const_eval/src/errors.rs | 16 +- .../rustc_const_eval/src/interpret/call.rs | 100 ++++++------- .../src/interpret/intrinsics.rs | 137 +++++++----------- .../rustc_const_eval/src/interpret/memory.rs | 61 +++++--- .../rustc_const_eval/src/interpret/stack.rs | 76 +++++++--- .../rustc_middle/src/mir/interpret/error.rs | 6 + src/tools/miri/src/alloc_addresses/mod.rs | 8 +- .../tests/fail/c-variadic-mismatch-count.rs | 13 ++ .../fail/c-variadic-mismatch-count.stderr | 13 ++ .../miri/tests/fail/c-variadic-mismatch.rs | 13 ++ .../tests/fail/c-variadic-mismatch.stderr | 13 ++ src/tools/miri/tests/fail/c-variadic.rs | 15 ++ src/tools/miri/tests/fail/c-variadic.stderr | 22 +++ src/tools/miri/tests/pass/c-variadic.rs | 115 +++++++++++++-- tests/ui/consts/const-eval/c-variadic-fail.rs | 40 ++--- .../consts/const-eval/c-variadic-fail.stderr | 125 ++++++++++------ .../variadic-ffi-semantic-restrictions.rs | 4 + .../variadic-ffi-semantic-restrictions.stderr | 83 ++++++++--- 18 files changed, 577 insertions(+), 283 deletions(-) create mode 100644 src/tools/miri/tests/fail/c-variadic-mismatch-count.rs create mode 100644 src/tools/miri/tests/fail/c-variadic-mismatch-count.stderr create mode 100644 src/tools/miri/tests/fail/c-variadic-mismatch.rs create mode 100644 src/tools/miri/tests/fail/c-variadic-mismatch.stderr create mode 100644 src/tools/miri/tests/fail/c-variadic.rs create mode 100644 src/tools/miri/tests/fail/c-variadic.stderr diff --git a/compiler/rustc_const_eval/src/errors.rs b/compiler/rustc_const_eval/src/errors.rs index 9de7d02184b3..061ed38c9df1 100644 --- a/compiler/rustc_const_eval/src/errors.rs +++ b/compiler/rustc_const_eval/src/errors.rs @@ -763,6 +763,7 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> { InvalidChar(_) => msg!("interpreting an invalid 32-bit value as a char: 0x{$value}"), InvalidTag(_) => msg!("enum value has invalid tag: {$tag}"), InvalidFunctionPointer(_) => msg!("using {$pointer} as function pointer but it does not point to a function"), + InvalidVaListPointer(_) => msg!("using {$pointer} as variable argument list pointer but it does not point to a variable argument list"), InvalidVTablePointer(_) => msg!("using {$pointer} as vtable pointer but it does not point to a vtable"), InvalidVTableTrait { .. } => msg!("using vtable for `{$vtable_dyn_type}` but `{$expected_dyn_type}` was expected"), InvalidStr(_) => msg!("this string is not valid UTF-8: {$err}"), @@ -778,6 +779,8 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> { AbiMismatchArgument { .. } => msg!("calling a function whose parameter #{$arg_idx} has type {$callee_ty} passing argument of type {$caller_ty}"), AbiMismatchReturn { .. } => msg!("calling a function with return type {$callee_ty} passing return place of type {$caller_ty}"), VaArgOutOfBounds => "more C-variadic arguments read than were passed".into(), + CVariadicMismatch { ..} => "calling a function where the caller and callee disagree on whether the function is C-variadic".into(), + CVariadicFixedCountMismatch { .. } => msg!("calling a C-variadic function with {$caller} fixed arguments, but the function expects {$callee}"), } } @@ -823,7 +826,10 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> { diag.arg("len", len); diag.arg("index", index); } - UnterminatedCString(ptr) | InvalidFunctionPointer(ptr) | InvalidVTablePointer(ptr) => { + UnterminatedCString(ptr) + | InvalidFunctionPointer(ptr) + | InvalidVaListPointer(ptr) + | InvalidVTablePointer(ptr) => { diag.arg("pointer", ptr); } InvalidVTableTrait { expected_dyn_type, vtable_dyn_type } => { @@ -914,6 +920,14 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> { diag.arg("caller_ty", caller_ty); diag.arg("callee_ty", callee_ty); } + CVariadicMismatch { caller_is_c_variadic, callee_is_c_variadic } => { + diag.arg("caller_is_c_variadic", caller_is_c_variadic); + diag.arg("callee_is_c_variadic", callee_is_c_variadic); + } + CVariadicFixedCountMismatch { caller, callee } => { + diag.arg("caller", caller); + diag.arg("callee", callee); + } } } } diff --git a/compiler/rustc_const_eval/src/interpret/call.rs b/compiler/rustc_const_eval/src/interpret/call.rs index 3efac883b9c4..dabee7fa19b0 100644 --- a/compiler/rustc_const_eval/src/interpret/call.rs +++ b/compiler/rustc_const_eval/src/interpret/call.rs @@ -4,7 +4,7 @@ use std::borrow::Cow; use either::{Left, Right}; -use rustc_abi::{self as abi, ExternAbi, FieldIdx, HasDataLayout, Integer, Size, VariantIdx}; +use rustc_abi::{self as abi, ExternAbi, FieldIdx, Integer, VariantIdx}; use rustc_data_structures::assert_matches; use rustc_errors::msg; use rustc_hir::def_id::DefId; @@ -17,9 +17,9 @@ use tracing::field::Empty; use tracing::{info, instrument, trace}; use super::{ - CtfeProvenance, FnVal, ImmTy, InterpCx, InterpResult, MPlaceTy, Machine, MemoryKind, OpTy, - PlaceTy, Projectable, Provenance, ReturnAction, ReturnContinuation, Scalar, StackPopInfo, - interp_ok, throw_ub, throw_ub_custom, + CtfeProvenance, FnVal, ImmTy, InterpCx, InterpResult, MPlaceTy, Machine, OpTy, PlaceTy, + Projectable, Provenance, ReturnAction, ReturnContinuation, Scalar, StackPopInfo, interp_ok, + throw_ub, throw_ub_custom, }; use crate::enter_trace_span; use crate::interpret::EnteredTraceSpan; @@ -354,23 +354,18 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { ) -> InterpResult<'tcx> { let _trace = enter_trace_span!(M, step::init_stack_frame, %instance, tracing_separate_thread = Empty); - let (fixed_count, c_variadic_args) = if caller_fn_abi.c_variadic { - let sig = self.tcx.fn_sig(instance.def_id()).skip_binder(); - let fixed_count = sig.inputs().skip_binder().len(); - assert!(caller_fn_abi.args.len() >= fixed_count); - let extra_tys: Vec> = - caller_fn_abi.args[fixed_count..].iter().map(|arg_abi| arg_abi.layout.ty).collect(); - - (fixed_count, self.tcx.mk_type_list(&extra_tys)) + // The first order of business is to figure out the callee signature. + // However, that requires the list of variadic arguments. + // We use the *caller* information to determine where to split the list of arguments, + // and then later check that the callee indeed has the same number of fixed arguments. + let extra_tys = if caller_fn_abi.c_variadic { + let fixed_count = usize::try_from(caller_fn_abi.fixed_count).unwrap(); + let extra_tys = args[fixed_count..].iter().map(|arg| arg.layout().ty); + self.tcx.mk_type_list_from_iter(extra_tys) } else { - (caller_fn_abi.args.len(), ty::List::empty()) + ty::List::empty() }; - - let callee_fn_abi = self.fn_abi_of_instance(instance, c_variadic_args)?; - - if callee_fn_abi.c_variadic ^ caller_fn_abi.c_variadic { - unreachable!("caller and callee disagree on being c-variadic"); - } + let callee_fn_abi = self.fn_abi_of_instance(instance, extra_tys)?; if caller_fn_abi.conv != callee_fn_abi.conv { throw_ub_custom!( @@ -382,6 +377,19 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { ) } + if caller_fn_abi.c_variadic != callee_fn_abi.c_variadic { + throw_ub!(CVariadicMismatch { + caller_is_c_variadic: caller_fn_abi.c_variadic, + callee_is_c_variadic: callee_fn_abi.c_variadic, + }); + } + if caller_fn_abi.c_variadic && caller_fn_abi.fixed_count != callee_fn_abi.fixed_count { + throw_ub!(CVariadicFixedCountMismatch { + caller: caller_fn_abi.fixed_count, + callee: callee_fn_abi.fixed_count, + }); + } + // Check that all target features required by the callee (i.e., from // the attribute `#[target_feature(enable = ...)]`) are enabled at // compile time. @@ -453,14 +461,12 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { // this is a single iterator (that handles `spread_arg`), then // `pass_argument` would be the loop body. It takes care to // not advance `caller_iter` for ignored arguments. - let mut callee_args_abis = if caller_fn_abi.c_variadic { - callee_fn_abi.args[..fixed_count].iter().enumerate() - } else { - callee_fn_abi.args.iter().enumerate() - }; - - let mut it = body.args_iter().peekable(); - while let Some(local) = it.next() { + let mut callee_args_abis = callee_fn_abi.args.iter().enumerate(); + // Determine whether there is a special VaList argument. This is always the + // last argument, and since arguments start at index 1 that's `arg_count`. + let va_list_arg = + callee_fn_abi.c_variadic.then(|| mir::Local::from_usize(body.arg_count)); + for local in body.args_iter() { // Construct the destination place for this argument. At this point all // locals are still dead, so we cannot construct a `PlaceTy`. let dest = mir::Place::from(local); @@ -468,44 +474,30 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { // type, but the result gets cached so this avoids calling the instantiation // query *again* the next time this local is accessed. let ty = self.layout_of_local(self.frame(), local, None)?.ty; - if caller_fn_abi.c_variadic && it.peek().is_none() { - // The callee's signature has an additional VaList argument, that the caller - // won't actually pass. Here we synthesize a `VaList` value, whose leading bytes - // are a pointer that can be mapped to the corresponding variable argument list. + if Some(local) == va_list_arg { + // This is the last callee-side argument of a variadic function. + // This argument is a VaList holding the remaining caller-side arguments. self.storage_live(local)?; let place = self.eval_place(dest)?; let mplace = self.force_allocation(&place)?; - // Consume the remaining arguments and store them in a global allocation. - let mut varargs = Vec::new(); - for (fn_arg, abi) in &mut caller_args { - let op = self.copy_fn_arg(fn_arg); - let mplace = self.allocate(abi.layout, MemoryKind::Stack)?; - self.copy_op(&op, &mplace)?; - - varargs.push(mplace); - } - - // When the frame is dropped, this ID is used to deallocate the variable arguments list. + // Consume the remaining arguments by putting them into the variable argument + // list. + let varargs = self.allocate_varargs(&mut caller_args, &mut callee_args_abis)?; + // When the frame is dropped, these variable arguments are deallocated. self.frame_mut().va_list = varargs.clone(); + let key = self.va_list_ptr(varargs.into()); - // This is a new VaList, so start at index 0. - let ptr = self.va_list_ptr(varargs, 0); - let addr = Scalar::from_pointer(ptr, self); - - // Zero the mplace, so it is fully initialized. + // Zero the VaList, so it is fully initialized. self.write_bytes_ptr( mplace.ptr(), (0..mplace.layout.size.bytes()).map(|_| 0u8), )?; - // Store the pointer to the global variable arguments list allocation in the - // first bytes of the `VaList` value. - let mut alloc = self - .get_ptr_alloc_mut(mplace.ptr(), self.data_layout().pointer_size())? - .expect("not a ZST"); - alloc.write_ptr_sized(Size::ZERO, addr)?; + // Store the "key" pointer in the right field. + let key_mplace = self.va_list_key_field(&mplace)?; + self.write_pointer(key, &key_mplace)?; } else if Some(local) == body.spread_arg { // Make the local live once, then fill in the value field by field. self.storage_live(local)?; @@ -545,7 +537,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { if instance.def.requires_caller_location(*self.tcx) { callee_args_abis.next().unwrap(); } - // Now we should have no more caller args or callee arg ABIs + // Now we should have no more caller args or callee arg ABIs. assert!( callee_args_abis.next().is_none(), "mismatch between callee ABI and callee body arguments" diff --git a/compiler/rustc_const_eval/src/interpret/intrinsics.rs b/compiler/rustc_const_eval/src/interpret/intrinsics.rs index aa7c889b3f3f..e0c75da87a4b 100644 --- a/compiler/rustc_const_eval/src/interpret/intrinsics.rs +++ b/compiler/rustc_const_eval/src/interpret/intrinsics.rs @@ -23,8 +23,8 @@ use super::memory::MemoryKind; use super::util::ensure_monomorphic_enough; use super::{ AllocId, CheckInAllocMsg, ImmTy, InterpCx, InterpResult, Machine, OpTy, PlaceTy, Pointer, - PointerArithmetic, Provenance, Scalar, err_ub_custom, err_unsup_format, interp_ok, throw_inval, - throw_ub, throw_ub_custom, throw_ub_format, throw_unsup_format, + PointerArithmetic, Projectable, Provenance, Scalar, err_ub_custom, err_unsup_format, interp_ok, + throw_inval, throw_ub, throw_ub_custom, throw_ub_format, throw_unsup_format, }; use crate::interpret::Writeable; @@ -751,113 +751,54 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { } sym::va_copy => { - // fn va_copy<'f>(src: &VaList<'f>) -> VaList<'f> - let src_ptr = self.read_pointer(&args[0])?; + let va_list = self.deref_pointer(&args[0])?; + let key_mplace = self.va_list_key_field(&va_list)?; + let key = self.read_pointer(&key_mplace)?; - // Read the token pointer from the src VaList (alloc_id + offset-as-index). - let src_va_list_ptr = { - let pointer_size = tcx.data_layout.pointer_size(); - let alloc = self - .get_ptr_alloc(src_ptr, pointer_size)? - .expect("va_list storage should not be a ZST"); - let scalar = alloc.read_pointer(Size::ZERO)?; - scalar.to_pointer(self)? - }; + let varargs = self.get_ptr_va_list(key)?; + let copy_key = self.va_list_ptr(varargs.clone()); - let (prov, offset) = src_va_list_ptr.into_raw_parts(); - let src_alloc_id = prov.unwrap().get_alloc_id().unwrap(); - let index = offset.bytes(); - - // Look up arguments without consuming src. - let Some(arguments) = self.get_va_list_alloc(src_alloc_id) else { - throw_unsup_format!("va_copy on unknown va_list allocation {:?}", src_alloc_id); - }; - - // Create a new allocation pointing at the same index. - let new_va_list_ptr = self.va_list_ptr(arguments.to_vec(), index); - let addr = Scalar::from_pointer(new_va_list_ptr, self); - - // Now overwrite the token pointer stored inside the VaList. - let mplace = self.force_allocation(dest)?; - let mut alloc = self.get_place_alloc_mut(&mplace)?.unwrap(); - alloc.write_ptr_sized(Size::ZERO, addr)?; + let copy_key_mplace = self.va_list_key_field(dest)?; + self.write_pointer(copy_key, ©_key_mplace)?; } sym::va_end => { - let ptr_size = self.tcx.data_layout.pointer_size(); + let va_list = self.deref_pointer(&args[0])?; + let key_mplace = self.va_list_key_field(&va_list)?; + let key = self.read_pointer(&key_mplace)?; - // The only argument is a `&mut VaList`. - let ap_ref = self.read_pointer(&args[0])?; - - // The first bytes of the `VaList` value store a pointer. The `AllocId` of this - // pointer is a key into a global map of variable argument lists. The offset is - // used as the index of the argument to read. - let va_list_ptr = { - let alloc = self - .get_ptr_alloc(ap_ref, ptr_size)? - .expect("va_list storage should not be a ZST"); - let scalar = alloc.read_pointer(Size::ZERO)?; - scalar.to_pointer(self)? - }; - - let (prov, _offset) = va_list_ptr.into_raw_parts(); - let alloc_id = prov.unwrap().get_alloc_id().unwrap(); - - let Some(_) = self.remove_va_list_alloc(alloc_id) else { - throw_unsup_format!("va_end on unknown va_list allocation {:?}", alloc_id) - }; + self.deallocate_va_list(key)?; } sym::va_arg => { - let ptr_size = self.tcx.data_layout.pointer_size(); + let va_list = self.deref_pointer(&args[0])?; + let key_mplace = self.va_list_key_field(&va_list)?; + let key = self.read_pointer(&key_mplace)?; - // The only argument is a `&mut VaList`. - let ap_ref = self.read_pointer(&args[0])?; + // Invalidate the old list and get its content. We'll recreate the + // new list (one element shorter) below. + let mut varargs = self.deallocate_va_list(key)?; - // The first bytes of the `VaList` value store a pointer. The `AllocId` of this - // pointer is a key into a global map of variable argument lists. The offset is - // used as the index of the argument to read. - let va_list_ptr = { - let alloc = self - .get_ptr_alloc(ap_ref, ptr_size)? - .expect("va_list storage should not be a ZST"); - let scalar = alloc.read_pointer(Size::ZERO)?; - scalar.to_pointer(self)? + let Some(arg_mplace) = varargs.pop_front() else { + throw_ub!(VaArgOutOfBounds); }; - let (prov, offset) = va_list_ptr.into_raw_parts(); - let alloc_id = prov.unwrap().get_alloc_id().unwrap(); - let index = offset.bytes(); - - let Some(varargs) = self.remove_va_list_alloc(alloc_id) else { - throw_unsup_format!("va_arg on unknown va_list allocation {:?}", alloc_id) - }; - - let Some(src_mplace) = varargs.get(offset.bytes_usize()).cloned() else { - throw_ub!(VaArgOutOfBounds) - }; - - // Update the offset in this `VaList` value so that a subsequent call to `va_arg` - // reads the next argument. - let new_va_list_ptr = self.va_list_ptr(varargs, index + 1); - let addr = Scalar::from_pointer(new_va_list_ptr, self); - let mut alloc = self - .get_ptr_alloc_mut(ap_ref, ptr_size)? - .expect("va_list storage should not be a ZST"); - alloc.write_ptr_sized(Size::ZERO, addr)?; - // NOTE: In C some type conversions are allowed (e.g. casting between signed and // unsigned integers). For now we require c-variadic arguments to be read with the // exact type they were passed as. - if src_mplace.layout.ty != dest.layout.ty { + if arg_mplace.layout.ty != dest.layout.ty { throw_unsup_format!( "va_arg type mismatch: requested `{}`, but next argument is `{}`", dest.layout.ty, - src_mplace.layout.ty + arg_mplace.layout.ty ); } + // Copy the argument. + self.copy_op(&arg_mplace, dest)?; - self.copy_op(&src_mplace, dest)?; + // Update the VaList pointer. + let new_key = self.va_list_ptr(varargs); + self.write_pointer(new_key, &key_mplace)?; } // Unsupported intrinsic: skip the return_to_block below. @@ -1340,4 +1281,26 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { interp_ok(Some(ImmTy::from_scalar(val, cast_to))) } } + + /// Get the MPlace of the key from the place storing the VaList. + pub(super) fn va_list_key_field>( + &self, + va_list: &P, + ) -> InterpResult<'tcx, P> { + // The struct wrapped by VaList. + let va_list_inner = self.project_field(va_list, FieldIdx::ZERO)?; + + // Find the first pointer field in this struct. The exact index is target-specific. + let ty::Adt(adt, substs) = va_list_inner.layout().ty.kind() else { + bug!("invalid VaListImpl layout"); + }; + + for (i, field) in adt.non_enum_variant().fields.iter().enumerate() { + if field.ty(*self.tcx, substs).is_raw_ptr() { + return self.project_field(&va_list_inner, FieldIdx::from_usize(i)); + } + } + + bug!("no VaListImpl field is a pointer"); + } } diff --git a/compiler/rustc_const_eval/src/interpret/memory.rs b/compiler/rustc_const_eval/src/interpret/memory.rs index aadbe84ca809..7b67d3acd555 100644 --- a/compiler/rustc_const_eval/src/interpret/memory.rs +++ b/compiler/rustc_const_eval/src/interpret/memory.rs @@ -129,7 +129,7 @@ pub struct Memory<'tcx, M: Machine<'tcx>> { extra_fn_ptr_map: FxIndexMap, /// Map storing variable argument lists. - va_list_map: FxIndexMap>>, + va_list_map: FxIndexMap>>, /// To be able to compare pointers with null, and to check alignment for accesses /// to ZSTs (where pointers may dangle), we keep track of the size even for allocations @@ -237,19 +237,17 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { self.global_root_pointer(Pointer::from(id)).unwrap() } + /// Insert a new variable argument list in the global map of variable argument lists. pub fn va_list_ptr( &mut self, - varargs: Vec>, - index: u64, + varargs: VecDeque>, ) -> Pointer { let id = self.tcx.reserve_alloc_id(); let old = self.memory.va_list_map.insert(id, varargs); assert!(old.is_none()); - // The offset is used to store the current index. - let ptr = Pointer::new(id.into(), Size::from_bytes(index)); // Variable argument lists are global allocations, so make sure we get the right root // pointer. We know this is not an `extern static` so this cannot fail. - self.global_root_pointer(ptr).unwrap() + self.global_root_pointer(Pointer::from(id)).unwrap() } pub fn allocate_ptr( @@ -1020,7 +1018,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { } // # Variable argument lists - if let Some(_) = self.get_va_list_alloc(id) { + if self.memory.va_list_map.contains_key(&id) { return AllocInfo::new(Size::ZERO, Align::ONE, AllocKind::VaList, Mutability::Not); } @@ -1071,18 +1069,6 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { } } - pub fn get_va_list_alloc(&self, id: AllocId) -> Option<&[MPlaceTy<'tcx, M::Provenance>]> { - self.memory.va_list_map.get(&id).map(|v| &**v) - } - - pub fn remove_va_list_alloc( - &mut self, - id: AllocId, - ) -> Option>> { - self.memory.dead_alloc_map.insert(id, (Size::ZERO, Align::ONE)); - self.memory.va_list_map.swap_remove(&id) - } - /// Takes a pointer that is the first chunk of a `TypeId` and return the type that its /// provenance refers to, as well as the segment of the hash that this pointer covers. pub fn get_ptr_type_id( @@ -1110,6 +1096,43 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { .into() } + pub fn get_ptr_va_list( + &self, + ptr: Pointer>, + ) -> InterpResult<'tcx, &VecDeque>> { + trace!("get_ptr_va_list({:?})", ptr); + let (alloc_id, offset, _prov) = self.ptr_get_alloc_id(ptr, 0)?; + if offset.bytes() != 0 { + throw_ub!(InvalidVaListPointer(Pointer::new(alloc_id, offset))) + } + + let Some(va_list) = self.memory.va_list_map.get(&alloc_id) else { + throw_ub!(InvalidVaListPointer(Pointer::new(alloc_id, offset))) + }; + + interp_ok(va_list) + } + + /// Removes this VaList from the global map of variable argument lists. This does not deallocate + /// the VaList elements, that happens when the Frame is popped. + pub fn deallocate_va_list( + &mut self, + ptr: Pointer>, + ) -> InterpResult<'tcx, VecDeque>> { + trace!("deallocate_va_list({:?})", ptr); + let (alloc_id, offset, _prov) = self.ptr_get_alloc_id(ptr, 0)?; + if offset.bytes() != 0 { + throw_ub!(InvalidVaListPointer(Pointer::new(alloc_id, offset))) + } + + let Some(va_list) = self.memory.va_list_map.swap_remove(&alloc_id) else { + throw_ub!(InvalidVaListPointer(Pointer::new(alloc_id, offset))) + }; + + self.memory.dead_alloc_map.insert(alloc_id, (Size::ZERO, Align::ONE)); + interp_ok(va_list) + } + /// Get the dynamic type of the given vtable pointer. /// If `expected_trait` is `Some`, it must be a vtable for the given trait. pub fn get_ptr_vtable_ty( diff --git a/compiler/rustc_const_eval/src/interpret/stack.rs b/compiler/rustc_const_eval/src/interpret/stack.rs index 9b2d99bb297f..1b8af1c44277 100644 --- a/compiler/rustc_const_eval/src/interpret/stack.rs +++ b/compiler/rustc_const_eval/src/interpret/stack.rs @@ -12,11 +12,12 @@ use rustc_middle::ty::{self, Ty, TyCtxt}; use rustc_middle::{bug, mir}; use rustc_mir_dataflow::impls::always_storage_live_locals; use rustc_span::Span; +use rustc_target::callconv::ArgAbi; use tracing::field::Empty; use tracing::{info_span, instrument, trace}; use super::{ - AllocId, CtfeProvenance, Immediate, InterpCx, InterpResult, MPlaceTy, Machine, MemPlace, + AllocId, CtfeProvenance, FnArg, Immediate, InterpCx, InterpResult, MPlaceTy, Machine, MemPlace, MemPlaceMeta, MemoryKind, Operand, PlaceTy, Pointer, Provenance, ReturnAction, Scalar, from_known_layout, interp_ok, throw_ub, throw_unsup, }; @@ -455,15 +456,12 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { }; let return_action = if cleanup { - // We need to take the locals out, since we need to mutate while iterating. for local in &frame.locals { self.deallocate_local(local.value)?; } // Deallocate any c-variadic arguments. - for mplace in &frame.va_list { - self.deallocate_vararg(mplace)?; - } + self.deallocate_varargs(&frame.va_list)?; // Call the machine hook, which determines the next steps. let return_action = M::after_stack_pop(self, frame, unwinding)?; @@ -610,22 +608,6 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { interp_ok(()) } - fn deallocate_vararg(&mut self, vararg: &MPlaceTy<'tcx, M::Provenance>) -> InterpResult<'tcx> { - let ptr = vararg.ptr(); - - // FIXME: is the `unwrap` valid here? - trace!( - "deallocating vararg {:?}: {:?}", - vararg, - // FIXME: what do we do with this comment? - // Locals always have a `alloc_id` (they are never the result of a int2ptr). - self.dump_alloc(ptr.provenance.unwrap().get_alloc_id().unwrap()) - ); - self.deallocate_ptr(ptr, None, MemoryKind::Stack)?; - - interp_ok(()) - } - /// This is public because it is used by [Aquascope](https://github.com/cognitive-engineering-lab/aquascope/) /// to analyze all the locals in a stack frame. #[inline(always)] @@ -653,6 +635,58 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { } } +impl<'a, 'tcx: 'a, M: Machine<'tcx>> InterpCx<'tcx, M> { + /// Consume the arguments provided by the iterator and store them as a list + /// of variadic arguments. Return a list of the places that hold those arguments. + pub(crate) fn allocate_varargs( + &mut self, + caller_args: &mut I, + callee_abis: &mut J, + ) -> InterpResult<'tcx, Vec>> + where + I: Iterator, &'a ArgAbi<'tcx, Ty<'tcx>>)>, + J: Iterator>)>, + { + // Consume the remaining arguments and store them in fresh allocations. + let mut varargs = Vec::new(); + for (fn_arg, caller_abi) in caller_args { + // The callee ABI is entirely computed based on which arguments the caller has + // provided so it should not be possible to get a mismatch here. + let (_idx, callee_abi) = callee_abis.next().unwrap(); + assert!(self.check_argument_compat(caller_abi, callee_abi)?); + // FIXME: do we have to worry about in-place argument passing? + let op = self.copy_fn_arg(fn_arg); + let mplace = self.allocate(op.layout, MemoryKind::Stack)?; + self.copy_op(&op, &mplace)?; + + varargs.push(mplace); + } + assert!(callee_abis.next().is_none()); + + interp_ok(varargs) + } + + /// Deallocate the variadic arguments in the list (that must have been created with `allocate_varargs`). + fn deallocate_varargs( + &mut self, + varargs: &[MPlaceTy<'tcx, M::Provenance>], + ) -> InterpResult<'tcx> { + for vararg in varargs { + let ptr = vararg.ptr(); + + trace!( + "deallocating vararg {:?}: {:?}", + vararg, + // Locals always have a `alloc_id` (they are never the result of a int2ptr). + self.dump_alloc(ptr.provenance.unwrap().get_alloc_id().unwrap()) + ); + self.deallocate_ptr(ptr, None, MemoryKind::Stack)?; + } + + interp_ok(()) + } +} + impl<'tcx, Prov: Provenance> LocalState<'tcx, Prov> { pub(super) fn print( &self, diff --git a/compiler/rustc_middle/src/mir/interpret/error.rs b/compiler/rustc_middle/src/mir/interpret/error.rs index 970dbd95f7cc..035ffd362a6b 100644 --- a/compiler/rustc_middle/src/mir/interpret/error.rs +++ b/compiler/rustc_middle/src/mir/interpret/error.rs @@ -404,6 +404,8 @@ pub enum UndefinedBehaviorInfo<'tcx> { InvalidTag(Scalar), /// Using a pointer-not-to-a-function as function pointer. InvalidFunctionPointer(Pointer), + /// Using a pointer-not-to-a-va-list as variable argument list pointer. + InvalidVaListPointer(Pointer), /// Using a pointer-not-to-a-vtable as vtable pointer. InvalidVTablePointer(Pointer), /// Using a vtable for the wrong trait. @@ -438,6 +440,10 @@ pub enum UndefinedBehaviorInfo<'tcx> { AbiMismatchReturn { caller_ty: Ty<'tcx>, callee_ty: Ty<'tcx> }, /// `va_arg` was called on an exhausted `VaList`. VaArgOutOfBounds, + /// The caller and callee disagree on whether they are c-variadic or not. + CVariadicMismatch { caller_is_c_variadic: bool, callee_is_c_variadic: bool }, + /// The caller and callee disagree on the number of fixed (i.e. non-c-variadic) arguments. + CVariadicFixedCountMismatch { caller: u32, callee: u32 }, } #[derive(Debug, Clone, Copy)] diff --git a/src/tools/miri/src/alloc_addresses/mod.rs b/src/tools/miri/src/alloc_addresses/mod.rs index 59799ac613b8..32897eb89a83 100644 --- a/src/tools/miri/src/alloc_addresses/mod.rs +++ b/src/tools/miri/src/alloc_addresses/mod.rs @@ -184,8 +184,8 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { } #[cfg(not(all(unix, feature = "native-lib")))] AllocKind::Function => dummy_alloc(params), - AllocKind::VTable => dummy_alloc(params), - AllocKind::TypeId | AllocKind::Dead | AllocKind::VaList => unreachable!(), + AllocKind::VTable | AllocKind::VaList => dummy_alloc(params), + AllocKind::TypeId | AllocKind::Dead => unreachable!(), }; // We don't have to expose this pointer yet, we do that in `prepare_for_native_call`. return interp_ok(base_ptr.addr().to_u64()); @@ -363,8 +363,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { ProvenanceMode::Default => { // The first time this happens at a particular location, print a warning. static DEDUP: SpanDedupDiagnostic = SpanDedupDiagnostic::new(); - this.dedup_diagnostic(&DEDUP, |first| NonHaltingDiagnostic::Int2Ptr { - details: first, + this.dedup_diagnostic(&DEDUP, |first| { + NonHaltingDiagnostic::Int2Ptr { details: first } }); } ProvenanceMode::Strict => { diff --git a/src/tools/miri/tests/fail/c-variadic-mismatch-count.rs b/src/tools/miri/tests/fail/c-variadic-mismatch-count.rs new file mode 100644 index 000000000000..c01860cd1df9 --- /dev/null +++ b/src/tools/miri/tests/fail/c-variadic-mismatch-count.rs @@ -0,0 +1,13 @@ +#![feature(c_variadic)] + +unsafe extern "C" fn helper(_: i32, _: ...) {} + +fn main() { + unsafe { + let f = helper as *const (); + let f = std::mem::transmute::<_, unsafe extern "C" fn(...)>(f); + + f(1); + //~^ ERROR: Undefined Behavior + } +} diff --git a/src/tools/miri/tests/fail/c-variadic-mismatch-count.stderr b/src/tools/miri/tests/fail/c-variadic-mismatch-count.stderr new file mode 100644 index 000000000000..9350b9986196 --- /dev/null +++ b/src/tools/miri/tests/fail/c-variadic-mismatch-count.stderr @@ -0,0 +1,13 @@ +error: Undefined Behavior: calling a C-variadic function with 0 fixed arguments, but the function expects 1 + --> tests/fail/c-variadic-mismatch-count.rs:LL:CC + | +LL | f(1); + | ^^^^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail/c-variadic-mismatch.rs b/src/tools/miri/tests/fail/c-variadic-mismatch.rs new file mode 100644 index 000000000000..bf44fb00bcee --- /dev/null +++ b/src/tools/miri/tests/fail/c-variadic-mismatch.rs @@ -0,0 +1,13 @@ +#![feature(c_variadic)] + +unsafe extern "C" fn helper(_: i32, _: ...) {} + +fn main() { + unsafe { + let f = helper as *const (); + let f = std::mem::transmute::<_, unsafe extern "C" fn(_: i32, _: i64)>(f); + + f(1i32, 1i64); + //~^ ERROR: Undefined Behavior: calling a function where the caller and callee disagree on whether the function is C-variadic + } +} diff --git a/src/tools/miri/tests/fail/c-variadic-mismatch.stderr b/src/tools/miri/tests/fail/c-variadic-mismatch.stderr new file mode 100644 index 000000000000..a68b96f738d2 --- /dev/null +++ b/src/tools/miri/tests/fail/c-variadic-mismatch.stderr @@ -0,0 +1,13 @@ +error: Undefined Behavior: calling a function where the caller and callee disagree on whether the function is C-variadic + --> tests/fail/c-variadic-mismatch.rs:LL:CC + | +LL | f(1i32, 1i64); + | ^^^^^^^^^^^^^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail/c-variadic.rs b/src/tools/miri/tests/fail/c-variadic.rs new file mode 100644 index 000000000000..38141c23d08d --- /dev/null +++ b/src/tools/miri/tests/fail/c-variadic.rs @@ -0,0 +1,15 @@ +#![feature(c_variadic)] + +//@error-in-other-file: Undefined Behavior: more C-variadic arguments read than were passed + +fn read_too_many() { + unsafe extern "C" fn variadic(mut ap: ...) { + ap.arg::(); + } + + unsafe { variadic() }; +} + +fn main() { + read_too_many(); +} diff --git a/src/tools/miri/tests/fail/c-variadic.stderr b/src/tools/miri/tests/fail/c-variadic.stderr new file mode 100644 index 000000000000..31695f1cab49 --- /dev/null +++ b/src/tools/miri/tests/fail/c-variadic.stderr @@ -0,0 +1,22 @@ +error: Undefined Behavior: more C-variadic arguments read than were passed + --> RUSTLIB/core/src/ffi/va_list.rs:LL:CC + | +LL | unsafe { va_arg(self) } + | ^^^^^^^^^^^^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: stack backtrace: + 0: std::ffi::VaList::<'_>::arg + at RUSTLIB/core/src/ffi/va_list.rs:LL:CC + 1: read_too_many::variadic + at tests/fail/c-variadic.rs:LL:CC + 2: read_too_many + at tests/fail/c-variadic.rs:LL:CC + 3: main + at tests/fail/c-variadic.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/pass/c-variadic.rs b/src/tools/miri/tests/pass/c-variadic.rs index a1f03db6872f..8b353885699e 100644 --- a/src/tools/miri/tests/pass/c-variadic.rs +++ b/src/tools/miri/tests/pass/c-variadic.rs @@ -1,18 +1,115 @@ #![feature(c_variadic)] -use core::ffi::VaList; +use std::ffi::{CStr, VaList, c_char, c_double, c_int, c_long}; -fn helper(ap: VaList) -> i32 { - // unsafe { ap.arg::() } - let _ = ap; - 0 +fn ignores_arguments() { + unsafe extern "C" fn variadic(_: ...) {} + + unsafe { variadic() }; + unsafe { variadic(1, 2, 3) }; } -unsafe extern "C" fn variadic(a: i32, ap: ...) -> i32 { - assert_eq!(a, 42); - helper(ap) +fn echo() { + unsafe extern "C" fn variadic(mut ap: ...) -> i32 { + ap.arg() + } + + assert_eq!(unsafe { variadic(1) }, 1); + assert_eq!(unsafe { variadic(3, 2, 1) }, 3); +} + +fn forward_by_val() { + unsafe fn helper(mut ap: VaList) -> i32 { + ap.arg() + } + + unsafe extern "C" fn variadic(ap: ...) -> i32 { + helper(ap) + } + + assert_eq!(unsafe { variadic(1) }, 1); + assert_eq!(unsafe { variadic(3, 2, 1) }, 3); +} + +fn forward_by_ref() { + unsafe fn helper(ap: &mut VaList) -> i32 { + ap.arg() + } + + unsafe extern "C" fn variadic(mut ap: ...) -> i32 { + helper(&mut ap) + } + + assert_eq!(unsafe { variadic(1) }, 1); + assert_eq!(unsafe { variadic(3, 2, 1) }, 3); +} + +#[allow(improper_ctypes_definitions)] +fn nested() { + unsafe fn helper(mut ap1: VaList, mut ap2: VaList) -> (i32, i32) { + (ap1.arg(), ap2.arg()) + } + + unsafe extern "C" fn variadic2(ap1: VaList, ap2: ...) -> (i32, i32) { + helper(ap1, ap2) + } + + unsafe extern "C" fn variadic1(ap1: ...) -> (i32, i32) { + variadic2(ap1, 2, 2) + } + + assert_eq!(unsafe { variadic1(1) }, (1, 2)); + + let (a, b) = unsafe { variadic1(1, 1) }; + + assert_eq!(a, 1); + assert_eq!(b, 2); +} + +fn various_types() { + unsafe extern "C" fn check_list_2(mut ap: ...) { + macro_rules! continue_if { + ($cond:expr) => { + if !($cond) { + panic!(); + } + }; + } + + unsafe fn compare_c_str(ptr: *const c_char, val: &str) -> bool { + match CStr::from_ptr(ptr).to_str() { + Ok(cstr) => cstr == val, + Err(_) => panic!(), + } + } + + continue_if!(ap.arg::().floor() == 3.14f64.floor()); + continue_if!(ap.arg::() == 12); + continue_if!(ap.arg::() == 'a' as c_int); + continue_if!(ap.arg::().floor() == 6.18f64.floor()); + continue_if!(compare_c_str(ap.arg::<*const c_char>(), "Hello")); + continue_if!(ap.arg::() == 42); + continue_if!(compare_c_str(ap.arg::<*const c_char>(), "World")); + } + + unsafe { + check_list_2( + 3.14 as c_double, + 12 as c_long, + b'a' as c_int, + 6.28 as c_double, + c"Hello".as_ptr(), + 42 as c_int, + c"World".as_ptr(), + ); + } } fn main() { - assert_eq!(unsafe { variadic(42, 1) }, 1); + ignores_arguments(); + echo(); + forward_by_val(); + forward_by_ref(); + nested(); + various_types(); } diff --git a/tests/ui/consts/const-eval/c-variadic-fail.rs b/tests/ui/consts/const-eval/c-variadic-fail.rs index 37810ddb356d..859dfed35719 100644 --- a/tests/ui/consts/const-eval/c-variadic-fail.rs +++ b/tests/ui/consts/const-eval/c-variadic-fail.rs @@ -7,6 +7,7 @@ #![feature(const_clone)] use std::ffi::VaList; +use std::mem::MaybeUninit; const unsafe extern "C" fn read_n(mut ap: ...) { let mut i = N; @@ -77,23 +78,13 @@ fn use_after_free() { }; } -macro_rules! va_list_copy { - ($ap:expr) => {{ - // A copy created using Clone is valid, and can be used to read arguments. - let mut copy = $ap.clone(); - assert!(copy.arg::() == 1i32); - - let mut u = core::mem::MaybeUninit::uninit(); - unsafe { core::ptr::copy_nonoverlapping(&$ap, u.as_mut_ptr(), 1) }; - - // Manually creating the copy is fine. - unsafe { u.assume_init() } - }}; -} - fn manual_copy_drop() { const unsafe extern "C" fn helper(ap: ...) { - let mut copy: VaList = va_list_copy!(ap); + // A copy created using Clone is valid, and can be used to read arguments. + let mut copy = ap.clone(); + assert!(copy.arg::() == 1i32); + + let mut copy: VaList = unsafe { std::mem::transmute_copy(&ap) }; // Using the copy is actually fine. let _ = copy.arg::(); @@ -104,12 +95,12 @@ fn manual_copy_drop() { } const { unsafe { helper(1, 2, 3) } }; - //~^ ERROR va_end on unknown va_list allocation ALLOC0 [E0080] + //~^ ERROR using ALLOC0 as variable argument list pointer but it does not point to a variable argument list [E0080] } fn manual_copy_forget() { const unsafe extern "C" fn helper(ap: ...) { - let mut copy: VaList = va_list_copy!(ap); + let mut copy: VaList = unsafe { std::mem::transmute_copy(&ap) }; // Using the copy is actually fine. let _ = copy.arg::(); @@ -120,12 +111,12 @@ fn manual_copy_forget() { } const { unsafe { helper(1, 2, 3) } }; - //~^ ERROR va_end on unknown va_list allocation ALLOC0 [E0080] + //~^ ERROR using ALLOC0 as variable argument list pointer but it does not point to a variable argument list [E0080] } fn manual_copy_read() { const unsafe extern "C" fn helper(mut ap: ...) { - let mut copy: VaList = va_list_copy!(ap); + let mut copy: VaList = unsafe { std::mem::transmute_copy(&ap) }; // Reading from `ap` after reading from `copy` is UB. let _ = copy.arg::(); @@ -133,7 +124,15 @@ fn manual_copy_read() { } const { unsafe { helper(1, 2, 3) } }; - //~^ ERROR va_arg on unknown va_list allocation ALLOC0 + //~^ ERROR using ALLOC0 as variable argument list pointer but it does not point to a variable argument list [E0080] +} + +fn drop_of_invalid() { + const { + let mut invalid: MaybeUninit = MaybeUninit::zeroed(); + let ap = unsafe { invalid.assume_init() }; + } + //~^ ERROR pointer not dereferenceable: pointer must point to some allocation, but got null pointer [E0080] } fn main() { @@ -143,5 +142,6 @@ fn main() { manual_copy_read(); manual_copy_drop(); manual_copy_forget(); + drop_of_invalid(); } } diff --git a/tests/ui/consts/const-eval/c-variadic-fail.stderr b/tests/ui/consts/const-eval/c-variadic-fail.stderr index 4c569a4492d4..14da5500cb1b 100644 --- a/tests/ui/consts/const-eval/c-variadic-fail.stderr +++ b/tests/ui/consts/const-eval/c-variadic-fail.stderr @@ -1,11 +1,11 @@ error[E0080]: more C-variadic arguments read than were passed - --> $DIR/c-variadic-fail.rs:27:13 + --> $DIR/c-variadic-fail.rs:28:13 | LL | const { read_n::<1>() } | ^^^^^^^^^^^^^ evaluation of `read_too_many::{constant#2}` failed inside this call | note: inside `read_n::<1>` - --> $DIR/c-variadic-fail.rs:15:17 + --> $DIR/c-variadic-fail.rs:16:17 | LL | let _ = ap.arg::(); | ^^^^^^^^^^^^^^^ @@ -13,13 +13,13 @@ note: inside `VaList::<'_>::arg::` --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:27:5 + --> $DIR/c-variadic-fail.rs:28:5 | LL | const { read_n::<1>() } | ^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:27:5 + --> $DIR/c-variadic-fail.rs:28:5 | LL | const { read_n::<1>() } | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -27,13 +27,13 @@ LL | const { read_n::<1>() } = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` error[E0080]: more C-variadic arguments read than were passed - --> $DIR/c-variadic-fail.rs:31:13 + --> $DIR/c-variadic-fail.rs:32:13 | LL | const { read_n::<2>(1) } | ^^^^^^^^^^^^^^ evaluation of `read_too_many::{constant#3}` failed inside this call | note: inside `read_n::<2>` - --> $DIR/c-variadic-fail.rs:15:17 + --> $DIR/c-variadic-fail.rs:16:17 | LL | let _ = ap.arg::(); | ^^^^^^^^^^^^^^^ @@ -41,13 +41,13 @@ note: inside `VaList::<'_>::arg::` --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:31:5 + --> $DIR/c-variadic-fail.rs:32:5 | LL | const { read_n::<2>(1) } | ^^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:31:5 + --> $DIR/c-variadic-fail.rs:32:5 | LL | const { read_n::<2>(1) } | ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -55,13 +55,13 @@ LL | const { read_n::<2>(1) } = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` error[E0080]: va_arg type mismatch: requested `u32`, but next argument is `i32` - --> $DIR/c-variadic-fail.rs:49:13 + --> $DIR/c-variadic-fail.rs:50:13 | LL | const { read_as::(1i32) }; | ^^^^^^^^^^^^^^^^^^^^ evaluation of `read_cast::{constant#6}` failed inside this call | note: inside `read_as::` - --> $DIR/c-variadic-fail.rs:36:5 + --> $DIR/c-variadic-fail.rs:37:5 | LL | ap.arg::() | ^^^^^^^^^^^^^ @@ -69,13 +69,13 @@ note: inside `VaList::<'_>::arg::` --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:49:5 + --> $DIR/c-variadic-fail.rs:50:5 | LL | const { read_as::(1i32) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:49:5 + --> $DIR/c-variadic-fail.rs:50:5 | LL | const { read_as::(1i32) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -83,13 +83,13 @@ LL | const { read_as::(1i32) }; = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` error[E0080]: va_arg type mismatch: requested `i32`, but next argument is `u32` - --> $DIR/c-variadic-fail.rs:52:13 + --> $DIR/c-variadic-fail.rs:53:13 | LL | const { read_as::(1u32) }; | ^^^^^^^^^^^^^^^^^^^^ evaluation of `read_cast::{constant#7}` failed inside this call | note: inside `read_as::` - --> $DIR/c-variadic-fail.rs:36:5 + --> $DIR/c-variadic-fail.rs:37:5 | LL | ap.arg::() | ^^^^^^^^^^^^^ @@ -97,13 +97,13 @@ note: inside `VaList::<'_>::arg::` --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:52:5 + --> $DIR/c-variadic-fail.rs:53:5 | LL | const { read_as::(1u32) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:52:5 + --> $DIR/c-variadic-fail.rs:53:5 | LL | const { read_as::(1u32) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -111,13 +111,13 @@ LL | const { read_as::(1u32) }; = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` error[E0080]: va_arg type mismatch: requested `i32`, but next argument is `u64` - --> $DIR/c-variadic-fail.rs:55:13 + --> $DIR/c-variadic-fail.rs:56:13 | LL | const { read_as::(1u64) }; | ^^^^^^^^^^^^^^^^^^^^ evaluation of `read_cast::{constant#8}` failed inside this call | note: inside `read_as::` - --> $DIR/c-variadic-fail.rs:36:5 + --> $DIR/c-variadic-fail.rs:37:5 | LL | ap.arg::() | ^^^^^^^^^^^^^ @@ -125,13 +125,13 @@ note: inside `VaList::<'_>::arg::` --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:55:5 + --> $DIR/c-variadic-fail.rs:56:5 | LL | const { read_as::(1u64) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:55:5 + --> $DIR/c-variadic-fail.rs:56:5 | LL | const { read_as::(1u64) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -139,13 +139,13 @@ LL | const { read_as::(1u64) }; = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` error[E0080]: va_arg type mismatch: requested `f64`, but next argument is `i32` - --> $DIR/c-variadic-fail.rs:58:13 + --> $DIR/c-variadic-fail.rs:59:13 | LL | const { read_as::(1i32) }; | ^^^^^^^^^^^^^^^^^^^^ evaluation of `read_cast::{constant#9}` failed inside this call | note: inside `read_as::` - --> $DIR/c-variadic-fail.rs:36:5 + --> $DIR/c-variadic-fail.rs:37:5 | LL | ap.arg::() | ^^^^^^^^^^^^^ @@ -153,13 +153,13 @@ note: inside `VaList::<'_>::arg::` --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:58:5 + --> $DIR/c-variadic-fail.rs:59:5 | LL | const { read_as::(1i32) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:58:5 + --> $DIR/c-variadic-fail.rs:59:5 | LL | const { read_as::(1i32) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -167,13 +167,13 @@ LL | const { read_as::(1i32) }; = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` error[E0080]: va_arg type mismatch: requested `*const u8`, but next argument is `i32` - --> $DIR/c-variadic-fail.rs:61:13 + --> $DIR/c-variadic-fail.rs:62:13 | LL | const { read_as::<*const u8>(1i32) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^ evaluation of `read_cast::{constant#10}` failed inside this call | note: inside `read_as::<*const u8>` - --> $DIR/c-variadic-fail.rs:36:5 + --> $DIR/c-variadic-fail.rs:37:5 | LL | ap.arg::() | ^^^^^^^^^^^^^ @@ -181,13 +181,13 @@ note: inside `VaList::<'_>::arg::<*const u8>` --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:61:5 + --> $DIR/c-variadic-fail.rs:62:5 | LL | const { read_as::<*const u8>(1i32) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:61:5 + --> $DIR/c-variadic-fail.rs:62:5 | LL | const { read_as::<*const u8>(1i32) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -195,7 +195,7 @@ LL | const { read_as::<*const u8>(1i32) }; = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` error[E0080]: memory access failed: ALLOC0 has been freed, so this pointer is dangling - --> $DIR/c-variadic-fail.rs:74:13 + --> $DIR/c-variadic-fail.rs:75:13 | LL | ap.arg::(); | ^^^^^^^^^^^^^^^ evaluation of `use_after_free::{constant#0}` failed inside this call @@ -204,7 +204,7 @@ note: inside `VaList::<'_>::arg::` --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:70:5 + --> $DIR/c-variadic-fail.rs:71:5 | LL | / const { LL | | unsafe { @@ -215,7 +215,7 @@ LL | | }; | |_____^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:70:5 + --> $DIR/c-variadic-fail.rs:71:5 | LL | / const { LL | | unsafe { @@ -227,14 +227,14 @@ LL | | }; | = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` -error[E0080]: va_end on unknown va_list allocation ALLOC1 - --> $DIR/c-variadic-fail.rs:106:22 +error[E0080]: using ALLOC1 as variable argument list pointer but it does not point to a variable argument list + --> $DIR/c-variadic-fail.rs:97:22 | LL | const { unsafe { helper(1, 2, 3) } }; | ^^^^^^^^^^^^^^^ evaluation of `manual_copy_drop::{constant#0}` failed inside this call | note: inside `manual_copy_drop::helper` - --> $DIR/c-variadic-fail.rs:103:9 + --> $DIR/c-variadic-fail.rs:94:9 | LL | drop(ap); | ^^^^^^^^ @@ -246,27 +246,27 @@ note: inside ` as Drop>::drop` --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:106:5 + --> $DIR/c-variadic-fail.rs:97:5 | LL | const { unsafe { helper(1, 2, 3) } }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:106:5 + --> $DIR/c-variadic-fail.rs:97:5 | LL | const { unsafe { helper(1, 2, 3) } }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` -error[E0080]: va_end on unknown va_list allocation ALLOC2 - --> $DIR/c-variadic-fail.rs:122:22 +error[E0080]: using ALLOC2 as variable argument list pointer but it does not point to a variable argument list + --> $DIR/c-variadic-fail.rs:113:22 | LL | const { unsafe { helper(1, 2, 3) } }; | ^^^^^^^^^^^^^^^ evaluation of `manual_copy_forget::{constant#0}` failed inside this call | note: inside `manual_copy_forget::helper` - --> $DIR/c-variadic-fail.rs:119:9 + --> $DIR/c-variadic-fail.rs:110:9 | LL | drop(ap); | ^^^^^^^^ @@ -278,27 +278,27 @@ note: inside ` as Drop>::drop` --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:122:5 + --> $DIR/c-variadic-fail.rs:113:5 | LL | const { unsafe { helper(1, 2, 3) } }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:122:5 + --> $DIR/c-variadic-fail.rs:113:5 | LL | const { unsafe { helper(1, 2, 3) } }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` -error[E0080]: va_arg on unknown va_list allocation ALLOC3 - --> $DIR/c-variadic-fail.rs:135:22 +error[E0080]: using ALLOC3 as variable argument list pointer but it does not point to a variable argument list + --> $DIR/c-variadic-fail.rs:126:22 | LL | const { unsafe { helper(1, 2, 3) } }; | ^^^^^^^^^^^^^^^ evaluation of `manual_copy_read::{constant#0}` failed inside this call | note: inside `manual_copy_read::helper` - --> $DIR/c-variadic-fail.rs:132:17 + --> $DIR/c-variadic-fail.rs:123:17 | LL | let _ = ap.arg::(); | ^^^^^^^^^^^^^^^ @@ -306,19 +306,50 @@ note: inside `VaList::<'_>::arg::` --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:135:5 + --> $DIR/c-variadic-fail.rs:126:5 | LL | const { unsafe { helper(1, 2, 3) } }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:135:5 + --> $DIR/c-variadic-fail.rs:126:5 | LL | const { unsafe { helper(1, 2, 3) } }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` -error: aborting due to 11 previous errors +error[E0080]: pointer not dereferenceable: pointer must point to some allocation, but got null pointer + --> $DIR/c-variadic-fail.rs:134:5 + | +LL | } + | ^ evaluation of `drop_of_invalid::{constant#0}` failed inside this call + | +note: inside `drop_in_place::> - shim(Some(VaList<'_>))` + --> $SRC_DIR/core/src/ptr/mod.rs:LL:COL +note: inside ` as Drop>::drop` + --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL + +note: erroneous constant encountered + --> $DIR/c-variadic-fail.rs:131:5 + | +LL | / const { +LL | | let mut invalid: MaybeUninit = MaybeUninit::zeroed(); +LL | | let ap = unsafe { invalid.assume_init() }; +LL | | } + | |_____^ + +note: erroneous constant encountered + --> $DIR/c-variadic-fail.rs:131:5 + | +LL | / const { +LL | | let mut invalid: MaybeUninit = MaybeUninit::zeroed(); +LL | | let ap = unsafe { invalid.assume_init() }; +LL | | } + | |_____^ + | + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error: aborting due to 12 previous errors For more information about this error, try `rustc --explain E0080`. diff --git a/tests/ui/parser/variadic-ffi-semantic-restrictions.rs b/tests/ui/parser/variadic-ffi-semantic-restrictions.rs index 41d9e73b69e8..4e038875d78f 100644 --- a/tests/ui/parser/variadic-ffi-semantic-restrictions.rs +++ b/tests/ui/parser/variadic-ffi-semantic-restrictions.rs @@ -32,14 +32,17 @@ extern "C" fn f3_3(_: ..., x: isize) {} const unsafe extern "C" fn f4_1(x: isize, _: ...) {} //~^ ERROR destructor of `VaList<'_>` cannot be evaluated at compile-time +//~| ERROR c-variadic const function definitions are unstable const extern "C" fn f4_2(x: isize, _: ...) {} //~^ ERROR functions with a C variable argument list must be unsafe //~| ERROR destructor of `VaList<'_>` cannot be evaluated at compile-time +//~| ERROR c-variadic const function definitions are unstable const extern "C" fn f4_3(_: ..., x: isize, _: ...) {} //~^ ERROR functions with a C variable argument list must be unsafe //~| ERROR `...` must be the last argument of a C-variadic function +//~| ERROR c-variadic const function definitions are unstable extern "C" { fn e_f2(..., x: isize); @@ -62,6 +65,7 @@ impl X { const fn i_f5(x: isize, _: ...) {} //~^ ERROR `...` is not supported for non-extern functions //~| ERROR destructor of `VaList<'_>` cannot be evaluated at compile-time + //~| ERROR c-variadic const function definitions are unstable } trait T { diff --git a/tests/ui/parser/variadic-ffi-semantic-restrictions.stderr b/tests/ui/parser/variadic-ffi-semantic-restrictions.stderr index 26d5cdaf995a..ea9f9baa58ba 100644 --- a/tests/ui/parser/variadic-ffi-semantic-restrictions.stderr +++ b/tests/ui/parser/variadic-ffi-semantic-restrictions.stderr @@ -80,8 +80,28 @@ error: `...` must be the last argument of a C-variadic function LL | extern "C" fn f3_3(_: ..., x: isize) {} | ^^^^^^ +error[E0658]: c-variadic const function definitions are unstable + --> $DIR/variadic-ffi-semantic-restrictions.rs:33:1 + | +LL | const unsafe extern "C" fn f4_1(x: isize, _: ...) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #151787 for more information + = help: add `#![feature(const_c_variadic)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0658]: c-variadic const function definitions are unstable + --> $DIR/variadic-ffi-semantic-restrictions.rs:37:1 + | +LL | const extern "C" fn f4_2(x: isize, _: ...) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #151787 for more information + = help: add `#![feature(const_c_variadic)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + error: functions with a C variable argument list must be unsafe - --> $DIR/variadic-ffi-semantic-restrictions.rs:36:36 + --> $DIR/variadic-ffi-semantic-restrictions.rs:37:36 | LL | const extern "C" fn f4_2(x: isize, _: ...) {} | ^^^^^^ @@ -92,13 +112,23 @@ LL | const unsafe extern "C" fn f4_2(x: isize, _: ...) {} | ++++++ error: `...` must be the last argument of a C-variadic function - --> $DIR/variadic-ffi-semantic-restrictions.rs:40:26 + --> $DIR/variadic-ffi-semantic-restrictions.rs:42:26 | LL | const extern "C" fn f4_3(_: ..., x: isize, _: ...) {} | ^^^^^^ +error[E0658]: c-variadic const function definitions are unstable + --> $DIR/variadic-ffi-semantic-restrictions.rs:42:1 + | +LL | const extern "C" fn f4_3(_: ..., x: isize, _: ...) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #151787 for more information + = help: add `#![feature(const_c_variadic)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + error: functions with a C variable argument list must be unsafe - --> $DIR/variadic-ffi-semantic-restrictions.rs:40:44 + --> $DIR/variadic-ffi-semantic-restrictions.rs:42:44 | LL | const extern "C" fn f4_3(_: ..., x: isize, _: ...) {} | ^^^^^^ @@ -109,13 +139,13 @@ LL | const unsafe extern "C" fn f4_3(_: ..., x: isize, _: ...) {} | ++++++ error: `...` must be the last argument of a C-variadic function - --> $DIR/variadic-ffi-semantic-restrictions.rs:45:13 + --> $DIR/variadic-ffi-semantic-restrictions.rs:48:13 | LL | fn e_f2(..., x: isize); | ^^^ error: `...` is not supported for non-extern functions - --> $DIR/variadic-ffi-semantic-restrictions.rs:52:23 + --> $DIR/variadic-ffi-semantic-restrictions.rs:55:23 | LL | fn i_f1(x: isize, _: ...) {} | ^^^^^^ @@ -123,7 +153,7 @@ LL | fn i_f1(x: isize, _: ...) {} = help: only `extern "C"` and `extern "C-unwind"` functions may have a C variable argument list error: `...` is not supported for non-extern functions - --> $DIR/variadic-ffi-semantic-restrictions.rs:54:13 + --> $DIR/variadic-ffi-semantic-restrictions.rs:57:13 | LL | fn i_f2(_: ...) {} | ^^^^^^ @@ -131,13 +161,13 @@ LL | fn i_f2(_: ...) {} = help: only `extern "C"` and `extern "C-unwind"` functions may have a C variable argument list error: `...` must be the last argument of a C-variadic function - --> $DIR/variadic-ffi-semantic-restrictions.rs:56:13 + --> $DIR/variadic-ffi-semantic-restrictions.rs:59:13 | LL | fn i_f3(_: ..., x: isize, _: ...) {} | ^^^^^^ error: `...` is not supported for non-extern functions - --> $DIR/variadic-ffi-semantic-restrictions.rs:56:31 + --> $DIR/variadic-ffi-semantic-restrictions.rs:59:31 | LL | fn i_f3(_: ..., x: isize, _: ...) {} | ^^^^^^ @@ -145,21 +175,31 @@ LL | fn i_f3(_: ..., x: isize, _: ...) {} = help: only `extern "C"` and `extern "C-unwind"` functions may have a C variable argument list error: `...` must be the last argument of a C-variadic function - --> $DIR/variadic-ffi-semantic-restrictions.rs:59:13 + --> $DIR/variadic-ffi-semantic-restrictions.rs:62:13 | LL | fn i_f4(_: ..., x: isize, _: ...) {} | ^^^^^^ error: `...` is not supported for non-extern functions - --> $DIR/variadic-ffi-semantic-restrictions.rs:59:31 + --> $DIR/variadic-ffi-semantic-restrictions.rs:62:31 | LL | fn i_f4(_: ..., x: isize, _: ...) {} | ^^^^^^ | = help: only `extern "C"` and `extern "C-unwind"` functions may have a C variable argument list +error[E0658]: c-variadic const function definitions are unstable + --> $DIR/variadic-ffi-semantic-restrictions.rs:65:5 + | +LL | const fn i_f5(x: isize, _: ...) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #151787 for more information + = help: add `#![feature(const_c_variadic)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + error: `...` is not supported for non-extern functions - --> $DIR/variadic-ffi-semantic-restrictions.rs:62:29 + --> $DIR/variadic-ffi-semantic-restrictions.rs:65:29 | LL | const fn i_f5(x: isize, _: ...) {} | ^^^^^^ @@ -167,7 +207,7 @@ LL | const fn i_f5(x: isize, _: ...) {} = help: only `extern "C"` and `extern "C-unwind"` functions may have a C variable argument list error: `...` is not supported for non-extern functions - --> $DIR/variadic-ffi-semantic-restrictions.rs:68:23 + --> $DIR/variadic-ffi-semantic-restrictions.rs:72:23 | LL | fn t_f1(x: isize, _: ...) {} | ^^^^^^ @@ -175,7 +215,7 @@ LL | fn t_f1(x: isize, _: ...) {} = help: only `extern "C"` and `extern "C-unwind"` functions may have a C variable argument list error: `...` is not supported for non-extern functions - --> $DIR/variadic-ffi-semantic-restrictions.rs:70:23 + --> $DIR/variadic-ffi-semantic-restrictions.rs:74:23 | LL | fn t_f2(x: isize, _: ...); | ^^^^^^ @@ -183,7 +223,7 @@ LL | fn t_f2(x: isize, _: ...); = help: only `extern "C"` and `extern "C-unwind"` functions may have a C variable argument list error: `...` is not supported for non-extern functions - --> $DIR/variadic-ffi-semantic-restrictions.rs:72:13 + --> $DIR/variadic-ffi-semantic-restrictions.rs:76:13 | LL | fn t_f3(_: ...) {} | ^^^^^^ @@ -191,7 +231,7 @@ LL | fn t_f3(_: ...) {} = help: only `extern "C"` and `extern "C-unwind"` functions may have a C variable argument list error: `...` is not supported for non-extern functions - --> $DIR/variadic-ffi-semantic-restrictions.rs:74:13 + --> $DIR/variadic-ffi-semantic-restrictions.rs:78:13 | LL | fn t_f4(_: ...); | ^^^^^^ @@ -199,13 +239,13 @@ LL | fn t_f4(_: ...); = help: only `extern "C"` and `extern "C-unwind"` functions may have a C variable argument list error: `...` must be the last argument of a C-variadic function - --> $DIR/variadic-ffi-semantic-restrictions.rs:76:13 + --> $DIR/variadic-ffi-semantic-restrictions.rs:80:13 | LL | fn t_f5(_: ..., x: isize) {} | ^^^^^^ error: `...` must be the last argument of a C-variadic function - --> $DIR/variadic-ffi-semantic-restrictions.rs:78:13 + --> $DIR/variadic-ffi-semantic-restrictions.rs:82:13 | LL | fn t_f6(_: ..., x: isize); | ^^^^^^ @@ -223,7 +263,7 @@ LL | const unsafe extern "C" fn f4_1(x: isize, _: ...) {} = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0493]: destructor of `VaList<'_>` cannot be evaluated at compile-time - --> $DIR/variadic-ffi-semantic-restrictions.rs:36:36 + --> $DIR/variadic-ffi-semantic-restrictions.rs:37:36 | LL | const extern "C" fn f4_2(x: isize, _: ...) {} | ^ - value is dropped here @@ -235,7 +275,7 @@ LL | const extern "C" fn f4_2(x: isize, _: ...) {} = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0493]: destructor of `VaList<'_>` cannot be evaluated at compile-time - --> $DIR/variadic-ffi-semantic-restrictions.rs:62:29 + --> $DIR/variadic-ffi-semantic-restrictions.rs:65:29 | LL | const fn i_f5(x: isize, _: ...) {} | ^ - value is dropped here @@ -246,6 +286,7 @@ LL | const fn i_f5(x: isize, _: ...) {} = help: add `#![feature(const_destruct)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date -error: aborting due to 29 previous errors +error: aborting due to 33 previous errors -For more information about this error, try `rustc --explain E0493`. +Some errors have detailed explanations: E0493, E0658. +For more information about an error, try `rustc --explain E0493`.