c-variadic functions in rustc_const_eval

This commit is contained in:
Folkert de Vries 2026-01-02 16:10:28 +01:00
parent ce693807f6
commit 02c4af397e
No known key found for this signature in database
GPG key ID: 1F17F6FFD112B97C
16 changed files with 908 additions and 33 deletions

View file

@ -9,7 +9,7 @@ use rustc_errors::msg;
use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_hir::{self as hir, CRATE_HIR_ID, LangItem};
use rustc_middle::mir::AssertMessage;
use rustc_middle::mir::interpret::{Pointer, ReportedErrorInfo};
use rustc_middle::mir::interpret::ReportedErrorInfo;
use rustc_middle::query::TyCtxtAt;
use rustc_middle::ty::layout::{HasTypingEnv, TyAndLayout, ValidityRequirement};
use rustc_middle::ty::{self, Ty, TyCtxt};
@ -22,7 +22,7 @@ use super::error::*;
use crate::errors::{LongRunning, LongRunningWarn};
use crate::interpret::{
self, AllocId, AllocInit, AllocRange, ConstAllocation, CtfeProvenance, FnArg, Frame,
GlobalAlloc, ImmTy, InterpCx, InterpResult, OpTy, PlaceTy, RangeSet, Scalar,
GlobalAlloc, ImmTy, InterpCx, InterpResult, OpTy, PlaceTy, Pointer, RangeSet, Scalar,
compile_time_machine, err_inval, interp_ok, throw_exhaust, throw_inval, throw_ub,
throw_ub_custom, throw_unsup, throw_unsup_format,
};

View file

@ -757,6 +757,7 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> {
WriteToReadOnly(_) => msg!("writing to {$allocation} which is read-only"),
DerefFunctionPointer(_) => msg!("accessing {$allocation} which contains a function"),
DerefVTablePointer(_) => msg!("accessing {$allocation} which contains a vtable"),
DerefVaListPointer(_) => msg!("accessing {$allocation} which contains a variable argument list"),
DerefTypeIdPointer(_) => msg!("accessing {$allocation} which contains a `TypeId`"),
InvalidBool(_) => msg!("interpreting an invalid 8-bit value as a bool: 0x{$value}"),
InvalidChar(_) => msg!("interpreting an invalid 32-bit value as a char: 0x{$value}"),
@ -776,6 +777,7 @@ 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(),
}
}
@ -800,6 +802,7 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> {
| InvalidMeta(InvalidMetaKind::TooBig)
| InvalidUninitBytes(None)
| DeadLocal
| VaArgOutOfBounds
| UninhabitedEnumVariantWritten(_)
| UninhabitedEnumVariantRead(_) => {}
@ -874,6 +877,7 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> {
WriteToReadOnly(alloc)
| DerefFunctionPointer(alloc)
| DerefVTablePointer(alloc)
| DerefVaListPointer(alloc)
| DerefTypeIdPointer(alloc) => {
diag.arg("allocation", alloc);
}

View file

@ -4,7 +4,7 @@
use std::borrow::Cow;
use either::{Left, Right};
use rustc_abi::{self as abi, ExternAbi, FieldIdx, Integer, VariantIdx};
use rustc_abi::{self as abi, ExternAbi, FieldIdx, HasDataLayout, Integer, Size, 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, OpTy, PlaceTy,
Projectable, Provenance, ReturnAction, ReturnContinuation, Scalar, StackPopInfo, interp_ok,
throw_ub, throw_ub_custom, throw_unsup_format,
CtfeProvenance, FnVal, ImmTy, InterpCx, InterpResult, MPlaceTy, Machine, MemoryKind, 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,12 +354,22 @@ 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);
// Compute callee information.
// FIXME: for variadic support, do we have to somehow determine callee's extra_args?
let callee_fn_abi = self.fn_abi_of_instance(instance, ty::List::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<Ty<'tcx>> =
caller_fn_abi.args[fixed_count..].iter().map(|arg_abi| arg_abi.layout.ty).collect();
if callee_fn_abi.c_variadic || caller_fn_abi.c_variadic {
throw_unsup_format!("calling a c-variadic function is not supported");
(fixed_count, self.tcx.mk_type_list(&extra_tys))
} else {
(caller_fn_abi.args.len(), 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");
}
if caller_fn_abi.conv != callee_fn_abi.conv {
@ -443,8 +453,14 @@ 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 = callee_fn_abi.args.iter().enumerate();
for local in body.args_iter() {
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() {
// 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);
@ -452,7 +468,45 @@ 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 Some(local) == body.spread_arg {
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.
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.
self.frame_mut().va_list = varargs.clone();
// 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.
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)?;
} else if Some(local) == body.spread_arg {
// Make the local live once, then fill in the value field by field.
self.storage_live(local)?;
// Must be a tuple

View file

@ -24,7 +24,7 @@ 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_custom, throw_ub_format,
throw_ub, throw_ub_custom, throw_ub_format, throw_unsup_format,
};
use crate::interpret::Writeable;
@ -750,6 +750,116 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
self.float_muladd_intrinsic::<Quad>(args, dest, MulAddType::Nondeterministic)?
}
sym::va_copy => {
// fn va_copy<'f>(src: &VaList<'f>) -> VaList<'f>
let src_ptr = self.read_pointer(&args[0])?;
// 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 (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)?;
}
sym::va_end => {
let ptr_size = self.tcx.data_layout.pointer_size();
// 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)
};
}
sym::va_arg => {
let ptr_size = self.tcx.data_layout.pointer_size();
// 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 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 {
throw_unsup_format!(
"va_arg type mismatch: requested `{}`, but next argument is `{}`",
dest.layout.ty,
src_mplace.layout.ty
);
}
self.copy_op(&src_mplace, dest)?;
}
// Unsupported intrinsic: skip the return_to_block below.
_ => return interp_ok(false),
}

View file

@ -23,8 +23,8 @@ use tracing::{debug, instrument, trace};
use super::{
AllocBytes, AllocId, AllocInit, AllocMap, AllocRange, Allocation, CheckAlignMsg,
CheckInAllocMsg, CtfeProvenance, GlobalAlloc, InterpCx, InterpResult, Machine, MayLeak,
Misalignment, Pointer, PointerArithmetic, Provenance, Scalar, alloc_range, err_ub,
CheckInAllocMsg, CtfeProvenance, GlobalAlloc, InterpCx, InterpResult, MPlaceTy, Machine,
MayLeak, Misalignment, Pointer, PointerArithmetic, Provenance, Scalar, alloc_range, err_ub,
err_ub_custom, interp_ok, throw_ub, throw_ub_custom, throw_unsup, throw_unsup_format,
};
use crate::const_eval::ConstEvalErrKind;
@ -67,6 +67,8 @@ pub enum AllocKind {
LiveData,
/// A function allocation (that fn ptrs point to).
Function,
/// A variable argument list allocation (used by c-variadic functions).
VaList,
/// A vtable allocation.
VTable,
/// A TypeId allocation.
@ -126,6 +128,9 @@ pub struct Memory<'tcx, M: Machine<'tcx>> {
/// Map for "extra" function pointers.
extra_fn_ptr_map: FxIndexMap<AllocId, M::ExtraFnVal>,
/// Map storing variable argument lists.
va_list_map: FxIndexMap<AllocId, Vec<MPlaceTy<'tcx, M::Provenance>>>,
/// 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
/// that do not exist any more.
@ -161,6 +166,7 @@ impl<'tcx, M: Machine<'tcx>> Memory<'tcx, M> {
Memory {
alloc_map: M::MemoryMap::default(),
extra_fn_ptr_map: FxIndexMap::default(),
va_list_map: FxIndexMap::default(),
dead_alloc_map: FxIndexMap::default(),
validation_in_progress: Cell::new(false),
}
@ -199,9 +205,11 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
return M::extern_static_pointer(self, def_id);
}
None => {
let is_fn_ptr = self.memory.extra_fn_ptr_map.contains_key(&alloc_id);
let is_va_list = self.memory.va_list_map.contains_key(&alloc_id);
assert!(
self.memory.extra_fn_ptr_map.contains_key(&alloc_id),
"{alloc_id:?} is neither global nor a function pointer"
is_fn_ptr || is_va_list,
"{alloc_id:?} is neither global, va_list nor a function pointer"
);
}
_ => {}
@ -229,6 +237,21 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
self.global_root_pointer(Pointer::from(id)).unwrap()
}
pub fn va_list_ptr(
&mut self,
varargs: Vec<MPlaceTy<'tcx, M::Provenance>>,
index: u64,
) -> Pointer<M::Provenance> {
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()
}
pub fn allocate_ptr(
&mut self,
size: Size,
@ -956,6 +979,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
pub fn is_alloc_live(&self, id: AllocId) -> bool {
self.memory.alloc_map.contains_key_ref(&id)
|| self.memory.extra_fn_ptr_map.contains_key(&id)
|| self.memory.va_list_map.contains_key(&id)
// We check `tcx` last as that has to acquire a lock in `many-seeds` mode.
// This also matches the order in `get_alloc_info`.
|| self.tcx.try_get_global_alloc(id).is_some()
@ -995,6 +1019,11 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
return AllocInfo::new(Size::ZERO, align, AllocKind::Function, Mutability::Not);
}
// # Variable argument lists
if let Some(_) = self.get_va_list_alloc(id) {
return AllocInfo::new(Size::ZERO, Align::ONE, AllocKind::VaList, Mutability::Not);
}
// # Global allocations
if let Some(global_alloc) = self.tcx.try_get_global_alloc(id) {
// NOTE: `static` alignment from attributes has already been applied to the allocation.
@ -1042,6 +1071,18 @@ 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<Vec<MPlaceTy<'tcx, M::Provenance>>> {
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(

View file

@ -16,9 +16,9 @@ use tracing::field::Empty;
use tracing::{info_span, instrument, trace};
use super::{
AllocId, CtfeProvenance, Immediate, InterpCx, InterpResult, Machine, MemPlace, MemPlaceMeta,
MemoryKind, Operand, PlaceTy, Pointer, Provenance, ReturnAction, Scalar, from_known_layout,
interp_ok, throw_ub, throw_unsup,
AllocId, CtfeProvenance, Immediate, InterpCx, InterpResult, MPlaceTy, Machine, MemPlace,
MemPlaceMeta, MemoryKind, Operand, PlaceTy, Pointer, Provenance, ReturnAction, Scalar,
from_known_layout, interp_ok, throw_ub, throw_unsup,
};
use crate::{enter_trace_span, errors};
@ -91,6 +91,10 @@ pub struct Frame<'tcx, Prov: Provenance = CtfeProvenance, Extra = ()> {
/// Do *not* access this directly; always go through the machine hook!
pub locals: IndexVec<mir::Local, LocalState<'tcx, Prov>>,
/// The complete variable argument list of this frame. Its elements must be dropped when the
/// frame is popped.
pub(super) va_list: Vec<MPlaceTy<'tcx, Prov>>,
/// The span of the `tracing` crate is stored here.
/// When the guard is dropped, the span is exited. This gives us
/// a full stack trace on all tracing statements.
@ -259,6 +263,7 @@ impl<'tcx, Prov: Provenance> Frame<'tcx, Prov> {
return_cont: self.return_cont,
return_place: self.return_place,
locals: self.locals,
va_list: self.va_list,
loc: self.loc,
extra,
tracing_span: self.tracing_span,
@ -377,6 +382,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
return_cont,
return_place: return_place.clone(),
locals,
va_list: vec![],
instance,
tracing_span: SpanGuard::new(),
extra: (),
@ -454,6 +460,11 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
self.deallocate_local(local.value)?;
}
// Deallocate any c-variadic arguments.
for mplace in &frame.va_list {
self.deallocate_vararg(mplace)?;
}
// Call the machine hook, which determines the next steps.
let return_action = M::after_stack_pop(self, frame, unwinding)?;
assert_ne!(return_action, ReturnAction::NoCleanup);
@ -599,6 +610,22 @@ 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)]

View file

@ -392,6 +392,8 @@ pub enum UndefinedBehaviorInfo<'tcx> {
DerefFunctionPointer(AllocId),
/// Trying to access the data behind a vtable pointer.
DerefVTablePointer(AllocId),
/// Trying to access the data behind a va_list pointer.
DerefVaListPointer(AllocId),
/// Trying to access the actual type id.
DerefTypeIdPointer(AllocId),
/// Using a non-boolean `u8` as bool.
@ -434,6 +436,8 @@ pub enum UndefinedBehaviorInfo<'tcx> {
},
/// ABI-incompatible return types.
AbiMismatchReturn { caller_ty: Ty<'tcx>, callee_ty: Ty<'tcx> },
/// `va_arg` was called on an exhausted `VaList`.
VaArgOutOfBounds,
}
#[derive(Debug, Clone, Copy)]

View file

@ -396,7 +396,7 @@ impl<'tcx> GlobalAlloc<'tcx> {
// No data to be accessed here. But vtables are pointer-aligned.
(Size::ZERO, tcx.data_layout.pointer_align().abi)
}
// Fake allocation, there's nothing to access here
// Fake allocation, there's nothing to access here.
GlobalAlloc::TypeId { .. } => (Size::ZERO, Align::ONE),
}
}

View file

@ -200,12 +200,13 @@ impl fmt::Debug for VaList<'_> {
impl VaList<'_> {
// Helper used in the implementation of the `va_copy` intrinsic.
pub(crate) fn duplicate(&self) -> Self {
Self { inner: self.inner.clone(), _marker: self._marker }
pub(crate) const fn duplicate(&self) -> Self {
Self { inner: self.inner, _marker: self._marker }
}
}
impl Clone for VaList<'_> {
#[rustc_const_unstable(feature = "c_variadic_const", issue = "none")]
impl<'f> const Clone for VaList<'f> {
#[inline]
fn clone(&self) -> Self {
// We only implement Clone and not Copy because some future target might not be able to

View file

@ -3484,7 +3484,7 @@ pub const unsafe fn va_arg<T: VaArgSafe>(ap: &mut VaList<'_>) -> T;
/// when a variable argument list is used incorrectly.
#[rustc_intrinsic]
#[rustc_nounwind]
pub fn va_copy<'f>(src: &VaList<'f>) -> VaList<'f> {
pub const fn va_copy<'f>(src: &VaList<'f>) -> VaList<'f> {
src.duplicate()
}

View file

@ -185,7 +185,7 @@ 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 => unreachable!(),
AllocKind::TypeId | AllocKind::Dead | AllocKind::VaList => 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 => {

View file

@ -651,7 +651,7 @@ trait EvalContextPrivExt<'tcx, 'ecx>: crate::MiriInterpCxExt<'tcx> {
dcx.log_protector();
}
},
AllocKind::Function | AllocKind::VTable | AllocKind::TypeId | AllocKind::Dead => {
AllocKind::Function | AllocKind::VTable | AllocKind::TypeId | AllocKind::Dead | AllocKind::VaList => {
// No stacked borrows on these allocations.
}
}
@ -1010,7 +1010,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
trace!("Stacked Borrows tag {tag:?} exposed in {alloc_id:?}");
alloc_extra.borrow_tracker_sb().borrow_mut().exposed_tags.insert(tag);
}
AllocKind::Function | AllocKind::VTable | AllocKind::TypeId | AllocKind::Dead => {
AllocKind::Function
| AllocKind::VTable
| AllocKind::TypeId
| AllocKind::Dead
| AllocKind::VaList => {
// No stacked borrows on these allocations.
}
}

View file

@ -576,7 +576,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
let protected = protected_tags.contains_key(&tag);
alloc_extra.borrow_tracker_tb().borrow_mut().expose_tag(tag, protected);
}
AllocKind::Function | AllocKind::VTable | AllocKind::TypeId | AllocKind::Dead => {
AllocKind::Function
| AllocKind::VTable
| AllocKind::TypeId
| AllocKind::Dead
| AllocKind::VaList => {
// No tree borrows on these allocations.
}
}

View file

@ -0,0 +1,147 @@
//@ build-fail
#![feature(c_variadic)]
#![feature(c_variadic_const)]
#![feature(const_trait_impl)]
#![feature(const_destruct)]
#![feature(const_clone)]
use std::ffi::VaList;
const unsafe extern "C" fn read_n<const N: usize>(mut ap: ...) {
let mut i = N;
while i > 0 {
i -= 1;
let _ = ap.arg::<i32>();
}
}
unsafe fn read_too_many() {
// None passed, none read.
const { read_n::<0>() }
// One passed, none read. Ignoring arguments is fine.
const { read_n::<0>(1) }
// None passed, one read.
const { read_n::<1>() }
//~^ ERROR more C-variadic arguments read than were passed
// One passed, two read.
const { read_n::<2>(1) }
//~^ ERROR more C-variadic arguments read than were passed
}
const unsafe extern "C" fn read_as<T: core::ffi::VaArgSafe>(mut ap: ...) -> T {
ap.arg::<T>()
}
unsafe fn read_cast() {
const { read_as::<i32>(1i32) };
const { read_as::<u32>(1u32) };
const { read_as::<i32>(1i32, 2u64, 3.0f64) };
const { read_as::<u32>(1u32, 2u64, 3.0f64) };
const { read_as::<i64>(1i64) };
const { read_as::<u64>(1u64) };
const { read_as::<u32>(1i32) };
//~^ ERROR va_arg type mismatch: requested `u32`, but next argument is `i32`
const { read_as::<i32>(1u32) };
//~^ ERROR va_arg type mismatch: requested `i32`, but next argument is `u32`
const { read_as::<i32>(1u64) };
//~^ ERROR va_arg type mismatch: requested `i32`, but next argument is `u64`
const { read_as::<f64>(1i32) };
//~^ ERROR va_arg type mismatch: requested `f64`, but next argument is `i32`
const { read_as::<*const u8>(1i32) };
//~^ ERROR va_arg type mismatch: requested `*const u8`, but next argument is `i32`
}
fn use_after_free() {
const unsafe extern "C" fn helper(ap: ...) -> [u8; size_of::<VaList>()] {
unsafe { std::mem::transmute(ap) }
}
const {
unsafe {
let ap = helper(1, 2, 3);
let mut ap = std::mem::transmute::<_, VaList>(ap);
ap.arg::<i32>();
//~^ ERROR memory access failed: ALLOC0 has been freed, so this pointer is dangling [E0080]
}
};
}
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::<i32>() == 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);
// Using the copy is actually fine.
let _ = copy.arg::<i32>();
drop(copy);
// But then using the original is UB.
drop(ap);
}
const { unsafe { helper(1, 2, 3) } };
//~^ ERROR va_end on unknown va_list allocation ALLOC0 [E0080]
}
fn manual_copy_forget() {
const unsafe extern "C" fn helper(ap: ...) {
let mut copy: VaList = va_list_copy!(ap);
// Using the copy is actually fine.
let _ = copy.arg::<i32>();
std::mem::forget(copy);
// The read (via `copy`) deallocated the original allocation.
drop(ap);
}
const { unsafe { helper(1, 2, 3) } };
//~^ ERROR va_end on unknown va_list allocation ALLOC0 [E0080]
}
fn manual_copy_read() {
const unsafe extern "C" fn helper(mut ap: ...) {
let mut copy: VaList = va_list_copy!(ap);
// Reading from `ap` after reading from `copy` is UB.
let _ = copy.arg::<i32>();
let _ = ap.arg::<i32>();
}
const { unsafe { helper(1, 2, 3) } };
//~^ ERROR va_arg on unknown va_list allocation ALLOC0
}
fn main() {
unsafe {
read_too_many();
read_cast();
manual_copy_read();
manual_copy_drop();
manual_copy_forget();
}
}

View file

@ -0,0 +1,324 @@
error[E0080]: more C-variadic arguments read than were passed
--> $DIR/c-variadic-fail.rs:27: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
|
LL | let _ = ap.arg::<i32>();
| ^^^^^^^^^^^^^^^
note: inside `VaList::<'_>::arg::<i32>`
--> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL
note: erroneous constant encountered
--> $DIR/c-variadic-fail.rs:27:5
|
LL | const { read_n::<1>() }
| ^^^^^^^^^^^^^^^^^^^^^^^
note: erroneous constant encountered
--> $DIR/c-variadic-fail.rs:27:5
|
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
|
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
|
LL | let _ = ap.arg::<i32>();
| ^^^^^^^^^^^^^^^
note: inside `VaList::<'_>::arg::<i32>`
--> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL
note: erroneous constant encountered
--> $DIR/c-variadic-fail.rs:31:5
|
LL | const { read_n::<2>(1) }
| ^^^^^^^^^^^^^^^^^^^^^^^^
note: erroneous constant encountered
--> $DIR/c-variadic-fail.rs:31:5
|
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
|
LL | const { read_as::<u32>(1i32) };
| ^^^^^^^^^^^^^^^^^^^^ evaluation of `read_cast::{constant#6}` failed inside this call
|
note: inside `read_as::<u32>`
--> $DIR/c-variadic-fail.rs:36:5
|
LL | ap.arg::<T>()
| ^^^^^^^^^^^^^
note: inside `VaList::<'_>::arg::<u32>`
--> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL
note: erroneous constant encountered
--> $DIR/c-variadic-fail.rs:49:5
|
LL | const { read_as::<u32>(1i32) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: erroneous constant encountered
--> $DIR/c-variadic-fail.rs:49:5
|
LL | const { read_as::<u32>(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
|
LL | const { read_as::<i32>(1u32) };
| ^^^^^^^^^^^^^^^^^^^^ evaluation of `read_cast::{constant#7}` failed inside this call
|
note: inside `read_as::<i32>`
--> $DIR/c-variadic-fail.rs:36:5
|
LL | ap.arg::<T>()
| ^^^^^^^^^^^^^
note: inside `VaList::<'_>::arg::<i32>`
--> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL
note: erroneous constant encountered
--> $DIR/c-variadic-fail.rs:52:5
|
LL | const { read_as::<i32>(1u32) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: erroneous constant encountered
--> $DIR/c-variadic-fail.rs:52:5
|
LL | const { read_as::<i32>(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
|
LL | const { read_as::<i32>(1u64) };
| ^^^^^^^^^^^^^^^^^^^^ evaluation of `read_cast::{constant#8}` failed inside this call
|
note: inside `read_as::<i32>`
--> $DIR/c-variadic-fail.rs:36:5
|
LL | ap.arg::<T>()
| ^^^^^^^^^^^^^
note: inside `VaList::<'_>::arg::<i32>`
--> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL
note: erroneous constant encountered
--> $DIR/c-variadic-fail.rs:55:5
|
LL | const { read_as::<i32>(1u64) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: erroneous constant encountered
--> $DIR/c-variadic-fail.rs:55:5
|
LL | const { read_as::<i32>(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
|
LL | const { read_as::<f64>(1i32) };
| ^^^^^^^^^^^^^^^^^^^^ evaluation of `read_cast::{constant#9}` failed inside this call
|
note: inside `read_as::<f64>`
--> $DIR/c-variadic-fail.rs:36:5
|
LL | ap.arg::<T>()
| ^^^^^^^^^^^^^
note: inside `VaList::<'_>::arg::<f64>`
--> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL
note: erroneous constant encountered
--> $DIR/c-variadic-fail.rs:58:5
|
LL | const { read_as::<f64>(1i32) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: erroneous constant encountered
--> $DIR/c-variadic-fail.rs:58:5
|
LL | const { read_as::<f64>(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
|
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
|
LL | ap.arg::<T>()
| ^^^^^^^^^^^^^
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
|
LL | const { read_as::<*const u8>(1i32) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: erroneous constant encountered
--> $DIR/c-variadic-fail.rs:61:5
|
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
|
LL | ap.arg::<i32>();
| ^^^^^^^^^^^^^^^ evaluation of `use_after_free::{constant#0}` failed inside this call
|
note: inside `VaList::<'_>::arg::<i32>`
--> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL
note: erroneous constant encountered
--> $DIR/c-variadic-fail.rs:70:5
|
LL | / const {
LL | | unsafe {
LL | | let ap = helper(1, 2, 3);
LL | | let mut ap = std::mem::transmute::<_, VaList>(ap);
... |
LL | | };
| |_____^
note: erroneous constant encountered
--> $DIR/c-variadic-fail.rs:70:5
|
LL | / const {
LL | | unsafe {
LL | | let ap = helper(1, 2, 3);
LL | | let mut ap = std::mem::transmute::<_, VaList>(ap);
... |
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
|
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
|
LL | drop(ap);
| ^^^^^^^^
note: inside `std::mem::drop::<VaList<'_>>`
--> $SRC_DIR/core/src/mem/mod.rs:LL:COL
note: inside `drop_in_place::<VaList<'_>> - shim(Some(VaList<'_>))`
--> $SRC_DIR/core/src/ptr/mod.rs:LL:COL
note: inside `<VaList<'_> as Drop>::drop`
--> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL
note: erroneous constant encountered
--> $DIR/c-variadic-fail.rs:106:5
|
LL | const { unsafe { helper(1, 2, 3) } };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: erroneous constant encountered
--> $DIR/c-variadic-fail.rs:106: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
|
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
|
LL | drop(ap);
| ^^^^^^^^
note: inside `std::mem::drop::<VaList<'_>>`
--> $SRC_DIR/core/src/mem/mod.rs:LL:COL
note: inside `drop_in_place::<VaList<'_>> - shim(Some(VaList<'_>))`
--> $SRC_DIR/core/src/ptr/mod.rs:LL:COL
note: inside `<VaList<'_> as Drop>::drop`
--> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL
note: erroneous constant encountered
--> $DIR/c-variadic-fail.rs:122:5
|
LL | const { unsafe { helper(1, 2, 3) } };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: erroneous constant encountered
--> $DIR/c-variadic-fail.rs:122: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
|
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
|
LL | let _ = ap.arg::<i32>();
| ^^^^^^^^^^^^^^^
note: inside `VaList::<'_>::arg::<i32>`
--> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL
note: erroneous constant encountered
--> $DIR/c-variadic-fail.rs:135:5
|
LL | const { unsafe { helper(1, 2, 3) } };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: erroneous constant encountered
--> $DIR/c-variadic-fail.rs:135: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
For more information about this error, try `rustc --explain E0080`.

View file

@ -0,0 +1,155 @@
//@ edition: 2021
//@ run-pass
//@ ignore-backends: gcc
#![feature(c_variadic)]
#![feature(const_destruct)]
#![feature(c_variadic_const)]
#![feature(const_cmp)]
#![feature(const_trait_impl)]
use std::ffi::*;
fn ignores_arguments() {
const unsafe extern "C" fn variadic(_: ...) {}
const {
unsafe { variadic() };
unsafe { variadic(1, 2, 3) };
}
}
fn echo() {
const unsafe extern "C" fn variadic(mut ap: ...) -> i32 {
ap.arg()
}
assert_eq!(unsafe { variadic(1) }, 1);
assert_eq!(unsafe { variadic(3, 2, 1) }, 3);
const {
assert!(unsafe { variadic(1) } == 1);
assert!(unsafe { variadic(3, 2, 1) } == 3);
}
}
fn forward_by_val() {
const unsafe fn helper(mut ap: VaList) -> i32 {
ap.arg()
}
const unsafe extern "C" fn variadic(ap: ...) -> i32 {
helper(ap)
}
assert_eq!(unsafe { variadic(1) }, 1);
assert_eq!(unsafe { variadic(3, 2, 1) }, 3);
const {
assert!(unsafe { variadic(1) } == 1);
assert!(unsafe { variadic(3, 2, 1) } == 3);
}
}
fn forward_by_ref() {
const unsafe fn helper(ap: &mut VaList) -> i32 {
ap.arg()
}
const 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);
const {
assert!(unsafe { variadic(1) } == 1);
assert!(unsafe { variadic(3, 2, 1) } == 3);
}
}
#[allow(improper_ctypes_definitions)]
fn nested() {
const unsafe fn helper(mut ap1: VaList, mut ap2: VaList) -> (i32, i32) {
(ap1.arg(), ap2.arg())
}
const unsafe extern "C" fn variadic2(ap1: VaList, ap2: ...) -> (i32, i32) {
helper(ap1, ap2)
}
const unsafe extern "C" fn variadic1(ap1: ...) -> (i32, i32) {
variadic2(ap1, 2, 2)
}
assert_eq!(unsafe { variadic1(1) }, (1, 2));
const {
let (a, b) = unsafe { variadic1(1, 1) };
assert!(a != 2);
assert!(a == 1);
assert!(b != 1);
assert!(b == 2);
}
}
fn various_types() {
const unsafe extern "C" fn check_list_2(mut ap: ...) {
macro_rules! continue_if {
($cond:expr) => {
if !($cond) {
panic!();
}
};
}
const 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::<c_double>().floor() == 3.14f64.floor());
continue_if!(ap.arg::<c_long>() == 12);
continue_if!(ap.arg::<c_int>() == 'a' as c_int);
continue_if!(ap.arg::<c_double>().floor() == 6.18f64.floor());
continue_if!(compare_c_str(ap.arg::<*const c_char>(), "Hello"));
continue_if!(ap.arg::<c_int>() == 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(),
);
const {
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() {
ignores_arguments();
echo();
forward_by_val();
forward_by_ref();
nested();
various_types();
}