From 7cfa4d7443abacf09b606f465a07aa3f595d6cd0 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Fri, 2 Jan 2026 15:35:21 +0100 Subject: [PATCH 01/14] add `GlobalAlloc::VaList` --- .../rustc_codegen_cranelift/src/constant.rs | 7 ++++ compiler/rustc_codegen_gcc/src/common.rs | 3 ++ compiler/rustc_codegen_llvm/src/common.rs | 3 ++ compiler/rustc_const_eval/messages.ftl | 2 ++ compiler/rustc_const_eval/src/errors.rs | 2 ++ .../rustc_const_eval/src/interpret/memory.rs | 14 ++++++++ .../rustc_middle/src/mir/interpret/error.rs | 2 ++ .../rustc_middle/src/mir/interpret/mod.rs | 34 ++++++++++++++++--- compiler/rustc_middle/src/mir/pretty.rs | 1 + compiler/rustc_middle/src/ty/print/pretty.rs | 1 + compiler/rustc_monomorphize/src/collector.rs | 1 + compiler/rustc_passes/src/reachable.rs | 1 + compiler/rustc_public/src/mir/alloc.rs | 2 ++ .../src/unstable/convert/stable/mir.rs | 1 + 14 files changed, 70 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_codegen_cranelift/src/constant.rs b/compiler/rustc_codegen_cranelift/src/constant.rs index ff8e6744bd32c..346257d606628 100644 --- a/compiler/rustc_codegen_cranelift/src/constant.rs +++ b/compiler/rustc_codegen_cranelift/src/constant.rs @@ -175,6 +175,9 @@ pub(crate) fn codegen_const_value<'tcx>( let local_data_id = fx.module.declare_data_in_func(data_id, fx.bcx.func); fx.bcx.ins().symbol_value(fx.pointer_type, local_data_id) } + GlobalAlloc::VaList => { + bug!("valist allocation should never make it to codegen") + } GlobalAlloc::TypeId { .. } => { return CValue::const_val( fx, @@ -381,6 +384,7 @@ fn define_all_allocs(tcx: TyCtxt<'_>, module: &mut dyn Module, cx: &mut Constant GlobalAlloc::Function { .. } | GlobalAlloc::Static(_) | GlobalAlloc::TypeId { .. } + | GlobalAlloc::VaList | GlobalAlloc::VTable(..) => { unreachable!() } @@ -494,6 +498,9 @@ fn define_all_allocs(tcx: TyCtxt<'_>, module: &mut dyn Module, cx: &mut Constant .principal() .map(|principal| tcx.instantiate_bound_regions_with_erased(principal)), ), + GlobalAlloc::VaList => { + bug!("valist allocation should never make it to codegen") + } GlobalAlloc::TypeId { .. } => { // Nothing to do, the bytes/offset of this pointer have already been written together with all other bytes, // so we just need to drop this provenance. diff --git a/compiler/rustc_codegen_gcc/src/common.rs b/compiler/rustc_codegen_gcc/src/common.rs index 7c2969e587186..c7e7a2e5e6601 100644 --- a/compiler/rustc_codegen_gcc/src/common.rs +++ b/compiler/rustc_codegen_gcc/src/common.rs @@ -281,6 +281,9 @@ impl<'gcc, 'tcx> ConstCodegenMethods for CodegenCx<'gcc, 'tcx> { let init = self.const_data_from_alloc(alloc); self.static_addr_of(init, alloc.inner().align, None) } + GlobalAlloc::VaList => { + bug!("valist allocation should never make it to codegen") + } GlobalAlloc::TypeId { .. } => { let val = self.const_usize(offset.bytes()); // This is still a variable of pointer type, even though we only use the provenance diff --git a/compiler/rustc_codegen_llvm/src/common.rs b/compiler/rustc_codegen_llvm/src/common.rs index b0cf9925019d2..ebf2d3abe964b 100644 --- a/compiler/rustc_codegen_llvm/src/common.rs +++ b/compiler/rustc_codegen_llvm/src/common.rs @@ -325,6 +325,9 @@ impl<'ll, 'tcx> ConstCodegenMethods for CodegenCx<'ll, 'tcx> { let init = const_alloc_to_llvm(self, alloc.inner(), /*static*/ false); self.static_addr_of_impl(init, alloc.inner().align, None) } + GlobalAlloc::VaList => { + bug!("valist allocation should never make it to codegen") + } GlobalAlloc::Static(def_id) => { assert!(self.tcx.is_static(def_id)); assert!(!self.tcx.is_thread_local_static(def_id)); diff --git a/compiler/rustc_const_eval/messages.ftl b/compiler/rustc_const_eval/messages.ftl index 4aa0a0b2a96fc..559f2c04759ce 100644 --- a/compiler/rustc_const_eval/messages.ftl +++ b/compiler/rustc_const_eval/messages.ftl @@ -91,6 +91,8 @@ const_eval_deref_function_pointer = accessing {$allocation} which contains a function const_eval_deref_typeid_pointer = accessing {$allocation} which contains a `TypeId` +const_eval_deref_va_list_pointer = + accessing {$allocation} which contains a variable argument list const_eval_deref_vtable_pointer = accessing {$allocation} which contains a vtable const_eval_division_by_zero = diff --git a/compiler/rustc_const_eval/src/errors.rs b/compiler/rustc_const_eval/src/errors.rs index 50f5448ec20ad..13212676cb436 100644 --- a/compiler/rustc_const_eval/src/errors.rs +++ b/compiler/rustc_const_eval/src/errors.rs @@ -492,6 +492,7 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> { WriteToReadOnly(_) => const_eval_write_to_read_only, DerefFunctionPointer(_) => const_eval_deref_function_pointer, DerefVTablePointer(_) => const_eval_deref_vtable_pointer, + DerefVaListPointer(_) => const_eval_deref_va_list_pointer, DerefTypeIdPointer(_) => const_eval_deref_typeid_pointer, InvalidBool(_) => const_eval_invalid_bool, InvalidChar(_) => const_eval_invalid_char, @@ -609,6 +610,7 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> { WriteToReadOnly(alloc) | DerefFunctionPointer(alloc) | DerefVTablePointer(alloc) + | DerefVaListPointer(alloc) | DerefTypeIdPointer(alloc) => { diag.arg("allocation", alloc); } diff --git a/compiler/rustc_const_eval/src/interpret/memory.rs b/compiler/rustc_const_eval/src/interpret/memory.rs index a6c8b28cce9f4..405baefbf2e53 100644 --- a/compiler/rustc_const_eval/src/interpret/memory.rs +++ b/compiler/rustc_const_eval/src/interpret/memory.rs @@ -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. @@ -394,6 +396,13 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { kind = "vtable", ) } + Some(GlobalAlloc::VaList) => { + err_ub_custom!( + fluent::const_eval_invalid_dealloc, + alloc_id = alloc_id, + kind = "valist", + ) + } Some(GlobalAlloc::TypeId { .. }) => { err_ub_custom!( fluent::const_eval_invalid_dealloc, @@ -670,6 +679,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { } Some(GlobalAlloc::Function { .. }) => throw_ub!(DerefFunctionPointer(id)), Some(GlobalAlloc::VTable(..)) => throw_ub!(DerefVTablePointer(id)), + Some(GlobalAlloc::VaList) => throw_ub!(DerefVaListPointer(id)), Some(GlobalAlloc::TypeId { .. }) => throw_ub!(DerefTypeIdPointer(id)), None => throw_ub!(PointerUseAfterFree(id, CheckInAllocMsg::MemoryAccess)), Some(GlobalAlloc::Static(def_id)) => { @@ -960,6 +970,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { GlobalAlloc::Static { .. } | GlobalAlloc::Memory { .. } => AllocKind::LiveData, GlobalAlloc::Function { .. } => bug!("We already checked function pointers above"), GlobalAlloc::VTable { .. } => AllocKind::VTable, + GlobalAlloc::VaList { .. } => AllocKind::VaList, GlobalAlloc::TypeId { .. } => AllocKind::TypeId, }; return AllocInfo::new(size, align, kind, mutbl); @@ -1272,6 +1283,9 @@ impl<'a, 'tcx, M: Machine<'tcx>> std::fmt::Debug for DumpAllocs<'a, 'tcx, M> { Some(GlobalAlloc::VTable(ty, dyn_ty)) => { write!(fmt, " (vtable: impl {dyn_ty} for {ty})")?; } + Some(GlobalAlloc::VaList) => { + write!(fmt, " (valist)")?; + } Some(GlobalAlloc::TypeId { ty }) => { write!(fmt, " (typeid for {ty})")?; } diff --git a/compiler/rustc_middle/src/mir/interpret/error.rs b/compiler/rustc_middle/src/mir/interpret/error.rs index 66c928f518aa3..b3003f4c7bca0 100644 --- a/compiler/rustc_middle/src/mir/interpret/error.rs +++ b/compiler/rustc_middle/src/mir/interpret/error.rs @@ -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. diff --git a/compiler/rustc_middle/src/mir/interpret/mod.rs b/compiler/rustc_middle/src/mir/interpret/mod.rs index 9762e0f21da9f..5b037a67c81aa 100644 --- a/compiler/rustc_middle/src/mir/interpret/mod.rs +++ b/compiler/rustc_middle/src/mir/interpret/mod.rs @@ -102,6 +102,7 @@ enum AllocDiscriminant { Alloc, Fn, VTable, + VaList, Static, Type, } @@ -128,6 +129,9 @@ pub fn specialized_encode_alloc_id<'tcx, E: TyEncoder<'tcx>>( ty.encode(encoder); poly_trait_ref.encode(encoder); } + GlobalAlloc::VaList => { + AllocDiscriminant::VaList.encode(encoder); + } GlobalAlloc::TypeId { ty } => { trace!("encoding {alloc_id:?} with {ty:#?}"); AllocDiscriminant::Type.encode(encoder); @@ -234,6 +238,7 @@ impl<'s> AllocDecodingSession<'s> { trace!("decoded vtable alloc instance: {ty:?}, {poly_trait_ref:?}"); decoder.interner().reserve_and_set_vtable_alloc(ty, poly_trait_ref, CTFE_ALLOC_SALT) } + AllocDiscriminant::VaList => decoder.interner().reserve_and_set_va_list_alloc(), AllocDiscriminant::Type => { trace!("creating typeid alloc ID"); let ty = Decodable::decode(decoder); @@ -265,6 +270,8 @@ pub enum GlobalAlloc<'tcx> { /// const-eval and Miri can detect UB due to invalid transmutes of /// `dyn Trait` types. VTable(Ty<'tcx>, &'tcx ty::List>), + /// This alloc ID points to a variable argument list. + VaList, /// The alloc ID points to a "lazy" static variable that did not get computed (yet). /// This is also used to break the cycle in recursive statics. Static(DefId), @@ -314,7 +321,8 @@ impl<'tcx> GlobalAlloc<'tcx> { GlobalAlloc::TypeId { .. } | GlobalAlloc::Static(..) | GlobalAlloc::Memory(..) - | GlobalAlloc::VTable(..) => AddressSpace::ZERO, + | GlobalAlloc::VTable(..) + | GlobalAlloc::VaList => AddressSpace::ZERO, } } @@ -350,7 +358,10 @@ impl<'tcx> GlobalAlloc<'tcx> { } } GlobalAlloc::Memory(alloc) => alloc.inner().mutability, - GlobalAlloc::TypeId { .. } | GlobalAlloc::Function { .. } | GlobalAlloc::VTable(..) => { + GlobalAlloc::TypeId { .. } + | GlobalAlloc::Function { .. } + | GlobalAlloc::VTable(..) + | GlobalAlloc::VaList => { // These are immutable. Mutability::Not } @@ -407,8 +418,8 @@ 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 - GlobalAlloc::TypeId { .. } => (Size::ZERO, Align::ONE), + // Fake allocation, there's nothing to access here. + GlobalAlloc::VaList | GlobalAlloc::TypeId { .. } => (Size::ZERO, Align::ONE), } } } @@ -514,6 +525,13 @@ impl<'tcx> TyCtxt<'tcx> { self.reserve_and_set_dedup(GlobalAlloc::VTable(ty, dyn_ty), salt) } + /// Generates an `AllocId` for a va_list. Does not get deduplicated. + pub fn reserve_and_set_va_list_alloc(self) -> AllocId { + let id = self.reserve_alloc_id(); + self.set_alloc_id_va_list(id); + id + } + /// Generates an [AllocId] for a [core::any::TypeId]. Will get deduplicated. pub fn reserve_and_set_type_id_alloc(self, ty: Ty<'tcx>) -> AllocId { self.reserve_and_set_dedup(GlobalAlloc::TypeId { ty }, 0) @@ -561,6 +579,14 @@ impl<'tcx> TyCtxt<'tcx> { } } + /// Freezes an `AllocId` created with `reserve` by pointing it at an `Allocation`. Trying to + /// call this function twice, even with the same `Allocation` will ICE the compiler. + pub fn set_alloc_id_va_list(self, id: AllocId) { + if let Some(old) = self.alloc_map.to_alloc.insert(id, GlobalAlloc::VaList) { + bug!("tried to set allocation ID {id:?}, but it was already existing as {old:#?}"); + } + } + /// Freezes an `AllocId` created with `reserve` by pointing it at a static item. Trying to /// call this function twice, even with the same `DefId` will ICE the compiler. pub fn set_nested_alloc_id_static(self, id: AllocId, def_id: LocalDefId) { diff --git a/compiler/rustc_middle/src/mir/pretty.rs b/compiler/rustc_middle/src/mir/pretty.rs index ded02595563c9..23da2288fc250 100644 --- a/compiler/rustc_middle/src/mir/pretty.rs +++ b/compiler/rustc_middle/src/mir/pretty.rs @@ -1570,6 +1570,7 @@ pub fn write_allocations<'tcx>( Some(GlobalAlloc::VTable(ty, dyn_ty)) => { write!(w, " (vtable: impl {dyn_ty} for {ty})")? } + Some(GlobalAlloc::VaList) => write!(w, "(valist)")?, Some(GlobalAlloc::TypeId { ty }) => write!(w, " (typeid for {ty})")?, Some(GlobalAlloc::Static(did)) if !tcx.is_foreign_item(did) => { write!(w, " (static: {}", tcx.def_path_str(did))?; diff --git a/compiler/rustc_middle/src/ty/print/pretty.rs b/compiler/rustc_middle/src/ty/print/pretty.rs index fd0a5ca309a4c..04b0eafc0023f 100644 --- a/compiler/rustc_middle/src/ty/print/pretty.rs +++ b/compiler/rustc_middle/src/ty/print/pretty.rs @@ -1748,6 +1748,7 @@ pub trait PrettyPrinter<'tcx>: Printer<'tcx> + fmt::Write { } Some(GlobalAlloc::Function { .. }) => write!(self, "")?, Some(GlobalAlloc::VTable(..)) => write!(self, "")?, + Some(GlobalAlloc::VaList) => write!(self, "")?, Some(GlobalAlloc::TypeId { .. }) => write!(self, "")?, None => write!(self, "")?, } diff --git a/compiler/rustc_monomorphize/src/collector.rs b/compiler/rustc_monomorphize/src/collector.rs index ce17046969184..2151f0c4c940f 100644 --- a/compiler/rustc_monomorphize/src/collector.rs +++ b/compiler/rustc_monomorphize/src/collector.rs @@ -1296,6 +1296,7 @@ fn collect_alloc<'tcx>(tcx: TyCtxt<'tcx>, alloc_id: AllocId, output: &mut MonoIt )); collect_alloc(tcx, alloc_id, output) } + GlobalAlloc::VaList => {} GlobalAlloc::TypeId { .. } => {} } } diff --git a/compiler/rustc_passes/src/reachable.rs b/compiler/rustc_passes/src/reachable.rs index d9565e2dae0ef..8ab028fe5c530 100644 --- a/compiler/rustc_passes/src/reachable.rs +++ b/compiler/rustc_passes/src/reachable.rs @@ -334,6 +334,7 @@ impl<'tcx> ReachableContext<'tcx> { self.visit(args); } } + GlobalAlloc::VaList => {} GlobalAlloc::TypeId { ty, .. } => self.visit(ty), GlobalAlloc::Memory(alloc) => self.propagate_from_alloc(alloc), } diff --git a/compiler/rustc_public/src/mir/alloc.rs b/compiler/rustc_public/src/mir/alloc.rs index b267e3612d808..58cc9f8b1181a 100644 --- a/compiler/rustc_public/src/mir/alloc.rs +++ b/compiler/rustc_public/src/mir/alloc.rs @@ -18,6 +18,8 @@ pub enum GlobalAlloc { /// This alloc ID points to a symbolic (not-reified) vtable. /// The `None` trait ref is used to represent auto traits. VTable(Ty, Option>), + /// This alloc ID points to a variable argument list (used with c-variadic functions). + VaList, /// The alloc ID points to a "lazy" static variable that did not get computed (yet). /// This is also used to break the cycle in recursive statics. Static(StaticDef), diff --git a/compiler/rustc_public/src/unstable/convert/stable/mir.rs b/compiler/rustc_public/src/unstable/convert/stable/mir.rs index a77808cfb275d..26c440524dc6b 100644 --- a/compiler/rustc_public/src/unstable/convert/stable/mir.rs +++ b/compiler/rustc_public/src/unstable/convert/stable/mir.rs @@ -846,6 +846,7 @@ impl<'tcx> Stable<'tcx> for mir::interpret::GlobalAlloc<'tcx> { // FIXME: Should we record the whole vtable? GlobalAlloc::VTable(ty.stable(tables, cx), dyn_ty.principal().stable(tables, cx)) } + mir::interpret::GlobalAlloc::VaList => GlobalAlloc::VaList, mir::interpret::GlobalAlloc::Static(def) => { GlobalAlloc::Static(tables.static_def(*def)) } From f237efc0d989dbb16202843e8a288b93d96201b8 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Fri, 2 Jan 2026 15:55:35 +0100 Subject: [PATCH 02/14] allow `const fn` to be c-variadic however `Drop` for `VaList` is not yet available in const fn --- compiler/rustc_ast_passes/messages.ftl | 4 -- .../rustc_ast_passes/src/ast_validation.rs | 9 --- compiler/rustc_ast_passes/src/errors.rs | 11 ---- .../variadic-ffi-semantic-restrictions.rs | 10 +-- .../variadic-ffi-semantic-restrictions.stderr | 66 ++++++------------- 5 files changed, 23 insertions(+), 77 deletions(-) diff --git a/compiler/rustc_ast_passes/messages.ftl b/compiler/rustc_ast_passes/messages.ftl index b9117c83ae2f6..acc39fefbe16f 100644 --- a/compiler/rustc_ast_passes/messages.ftl +++ b/compiler/rustc_ast_passes/messages.ftl @@ -82,10 +82,6 @@ ast_passes_c_variadic_no_extern = `...` is not supported for non-extern function ast_passes_c_variadic_not_supported = the `{$target}` target does not support c-variadic functions -ast_passes_const_and_c_variadic = functions cannot be both `const` and C-variadic - .const = `const` because of this - .variadic = C-variadic because of this - ast_passes_const_and_coroutine = functions cannot be both `const` and `{$coroutine_kind}` .const = `const` because of this .coroutine = `{$coroutine_kind}` because of this diff --git a/compiler/rustc_ast_passes/src/ast_validation.rs b/compiler/rustc_ast_passes/src/ast_validation.rs index eddcf12fca29c..1a482d5b7c265 100644 --- a/compiler/rustc_ast_passes/src/ast_validation.rs +++ b/compiler/rustc_ast_passes/src/ast_validation.rs @@ -697,15 +697,6 @@ impl<'a> AstValidator<'a> { unreachable!("C variable argument list cannot be used in closures") }; - // C-variadics are not yet implemented in const evaluation. - if let Const::Yes(const_span) = sig.header.constness { - self.dcx().emit_err(errors::ConstAndCVariadic { - spans: vec![const_span, variadic_param.span], - const_span, - variadic_span: variadic_param.span, - }); - } - if let Some(coroutine_kind) = sig.header.coroutine_kind { self.dcx().emit_err(errors::CoroutineAndCVariadic { spans: vec![coroutine_kind.span(), variadic_param.span], diff --git a/compiler/rustc_ast_passes/src/errors.rs b/compiler/rustc_ast_passes/src/errors.rs index 22adaae8c6f2f..738a2f7840998 100644 --- a/compiler/rustc_ast_passes/src/errors.rs +++ b/compiler/rustc_ast_passes/src/errors.rs @@ -710,17 +710,6 @@ pub(crate) struct ConstAndCoroutine { pub coroutine_kind: &'static str, } -#[derive(Diagnostic)] -#[diag(ast_passes_const_and_c_variadic)] -pub(crate) struct ConstAndCVariadic { - #[primary_span] - pub spans: Vec, - #[label(ast_passes_const)] - pub const_span: Span, - #[label(ast_passes_variadic)] - pub variadic_span: Span, -} - #[derive(Diagnostic)] #[diag(ast_passes_coroutine_and_c_variadic)] pub(crate) struct CoroutineAndCVariadic { diff --git a/tests/ui/parser/variadic-ffi-semantic-restrictions.rs b/tests/ui/parser/variadic-ffi-semantic-restrictions.rs index 6f61425a8bd6c..41d9e73b69e8d 100644 --- a/tests/ui/parser/variadic-ffi-semantic-restrictions.rs +++ b/tests/ui/parser/variadic-ffi-semantic-restrictions.rs @@ -31,17 +31,14 @@ extern "C" fn f3_3(_: ..., x: isize) {} //~^ ERROR `...` must be the last argument of a C-variadic function const unsafe extern "C" fn f4_1(x: isize, _: ...) {} -//~^ ERROR functions cannot be both `const` and C-variadic -//~| ERROR destructor of `VaList<'_>` cannot be evaluated at compile-time +//~^ ERROR destructor of `VaList<'_>` cannot be evaluated at compile-time const extern "C" fn f4_2(x: isize, _: ...) {} -//~^ ERROR functions cannot be both `const` and C-variadic -//~| ERROR functions with a C variable argument list must be unsafe +//~^ ERROR functions with a C variable argument list must be unsafe //~| ERROR destructor of `VaList<'_>` cannot be evaluated at compile-time const extern "C" fn f4_3(_: ..., x: isize, _: ...) {} -//~^ ERROR functions cannot be both `const` and C-variadic -//~| ERROR functions with a C variable argument list must be unsafe +//~^ ERROR functions with a C variable argument list must be unsafe //~| ERROR `...` must be the last argument of a C-variadic function extern "C" { @@ -64,7 +61,6 @@ impl X { //~| ERROR `...` must be the last argument of a C-variadic function const fn i_f5(x: isize, _: ...) {} //~^ ERROR `...` is not supported for non-extern functions - //~| ERROR functions cannot be both `const` and C-variadic //~| ERROR destructor of `VaList<'_>` cannot be evaluated at compile-time } diff --git a/tests/ui/parser/variadic-ffi-semantic-restrictions.stderr b/tests/ui/parser/variadic-ffi-semantic-restrictions.stderr index 318015737fa1b..20a182b8c49f3 100644 --- a/tests/ui/parser/variadic-ffi-semantic-restrictions.stderr +++ b/tests/ui/parser/variadic-ffi-semantic-restrictions.stderr @@ -80,20 +80,8 @@ error: `...` must be the last argument of a C-variadic function LL | extern "C" fn f3_3(_: ..., x: isize) {} | ^^^^^^ -error: functions cannot be both `const` and C-variadic - --> $DIR/variadic-ffi-semantic-restrictions.rs:33:1 - | -LL | const unsafe extern "C" fn f4_1(x: isize, _: ...) {} - | ^^^^^ `const` because of this ^^^^^^ C-variadic because of this - -error: functions cannot be both `const` and C-variadic - --> $DIR/variadic-ffi-semantic-restrictions.rs:37:1 - | -LL | const extern "C" fn f4_2(x: isize, _: ...) {} - | ^^^^^ `const` because of this ^^^^^^ C-variadic because of this - error: functions with a C variable argument list must be unsafe - --> $DIR/variadic-ffi-semantic-restrictions.rs:37:36 + --> $DIR/variadic-ffi-semantic-restrictions.rs:36:36 | LL | const extern "C" fn f4_2(x: isize, _: ...) {} | ^^^^^^ @@ -104,19 +92,13 @@ 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:42:26 + --> $DIR/variadic-ffi-semantic-restrictions.rs:40:26 | LL | const extern "C" fn f4_3(_: ..., x: isize, _: ...) {} | ^^^^^^ -error: functions cannot be both `const` and C-variadic - --> $DIR/variadic-ffi-semantic-restrictions.rs:42:1 - | -LL | const extern "C" fn f4_3(_: ..., x: isize, _: ...) {} - | ^^^^^ `const` because of this ^^^^^^ C-variadic because of this - error: functions with a C variable argument list must be unsafe - --> $DIR/variadic-ffi-semantic-restrictions.rs:42:44 + --> $DIR/variadic-ffi-semantic-restrictions.rs:40:44 | LL | const extern "C" fn f4_3(_: ..., x: isize, _: ...) {} | ^^^^^^ @@ -127,13 +109,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:48:13 + --> $DIR/variadic-ffi-semantic-restrictions.rs:45:13 | LL | fn e_f2(..., x: isize); | ^^^ error: `...` is not supported for non-extern functions - --> $DIR/variadic-ffi-semantic-restrictions.rs:55:23 + --> $DIR/variadic-ffi-semantic-restrictions.rs:52:23 | LL | fn i_f1(x: isize, _: ...) {} | ^^^^^^ @@ -141,7 +123,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:57:13 + --> $DIR/variadic-ffi-semantic-restrictions.rs:54:13 | LL | fn i_f2(_: ...) {} | ^^^^^^ @@ -149,13 +131,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:59:13 + --> $DIR/variadic-ffi-semantic-restrictions.rs:56:13 | LL | fn i_f3(_: ..., x: isize, _: ...) {} | ^^^^^^ error: `...` is not supported for non-extern functions - --> $DIR/variadic-ffi-semantic-restrictions.rs:59:31 + --> $DIR/variadic-ffi-semantic-restrictions.rs:56:31 | LL | fn i_f3(_: ..., x: isize, _: ...) {} | ^^^^^^ @@ -163,29 +145,21 @@ 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:62:13 + --> $DIR/variadic-ffi-semantic-restrictions.rs:59:13 | LL | fn i_f4(_: ..., x: isize, _: ...) {} | ^^^^^^ error: `...` is not supported for non-extern functions - --> $DIR/variadic-ffi-semantic-restrictions.rs:62:31 + --> $DIR/variadic-ffi-semantic-restrictions.rs:59:31 | LL | fn i_f4(_: ..., x: isize, _: ...) {} | ^^^^^^ | = help: only `extern "C"` and `extern "C-unwind"` functions may have a C variable argument list -error: functions cannot be both `const` and C-variadic - --> $DIR/variadic-ffi-semantic-restrictions.rs:65:5 - | -LL | const fn i_f5(x: isize, _: ...) {} - | ^^^^^ ^^^^^^ C-variadic because of this - | | - | `const` because of this - error: `...` is not supported for non-extern functions - --> $DIR/variadic-ffi-semantic-restrictions.rs:65:29 + --> $DIR/variadic-ffi-semantic-restrictions.rs:62:29 | LL | const fn i_f5(x: isize, _: ...) {} | ^^^^^^ @@ -193,7 +167,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:72:23 + --> $DIR/variadic-ffi-semantic-restrictions.rs:68:23 | LL | fn t_f1(x: isize, _: ...) {} | ^^^^^^ @@ -201,7 +175,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:74:23 + --> $DIR/variadic-ffi-semantic-restrictions.rs:70:23 | LL | fn t_f2(x: isize, _: ...); | ^^^^^^ @@ -209,7 +183,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:76:13 + --> $DIR/variadic-ffi-semantic-restrictions.rs:72:13 | LL | fn t_f3(_: ...) {} | ^^^^^^ @@ -217,7 +191,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:78:13 + --> $DIR/variadic-ffi-semantic-restrictions.rs:74:13 | LL | fn t_f4(_: ...); | ^^^^^^ @@ -225,13 +199,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:80:13 + --> $DIR/variadic-ffi-semantic-restrictions.rs:76:13 | LL | fn t_f5(_: ..., x: isize) {} | ^^^^^^ error: `...` must be the last argument of a C-variadic function - --> $DIR/variadic-ffi-semantic-restrictions.rs:82:13 + --> $DIR/variadic-ffi-semantic-restrictions.rs:78:13 | LL | fn t_f6(_: ..., x: isize); | ^^^^^^ @@ -245,7 +219,7 @@ LL | const unsafe extern "C" fn f4_1(x: isize, _: ...) {} | the destructor for this type cannot be evaluated in constant functions error[E0493]: destructor of `VaList<'_>` cannot be evaluated at compile-time - --> $DIR/variadic-ffi-semantic-restrictions.rs:37:36 + --> $DIR/variadic-ffi-semantic-restrictions.rs:36:36 | LL | const extern "C" fn f4_2(x: isize, _: ...) {} | ^ - value is dropped here @@ -253,13 +227,13 @@ LL | const extern "C" fn f4_2(x: isize, _: ...) {} | the destructor for this type cannot be evaluated in constant functions error[E0493]: destructor of `VaList<'_>` cannot be evaluated at compile-time - --> $DIR/variadic-ffi-semantic-restrictions.rs:65:29 + --> $DIR/variadic-ffi-semantic-restrictions.rs:62:29 | LL | const fn i_f5(x: isize, _: ...) {} | ^ - value is dropped here | | | the destructor for this type cannot be evaluated in constant functions -error: aborting due to 33 previous errors +error: aborting due to 29 previous errors For more information about this error, try `rustc --explain E0493`. From 16519313e7fdab55c13aabe9ebc309fa86d525a5 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Fri, 2 Jan 2026 16:02:28 +0100 Subject: [PATCH 03/14] make `Va::arg` and `VaList::drop` `const fn`s --- library/core/src/ffi/va_list.rs | 6 ++++-- library/core/src/intrinsics/mod.rs | 4 ++-- .../parser/variadic-ffi-semantic-restrictions.stderr | 12 ++++++++++++ 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/library/core/src/ffi/va_list.rs b/library/core/src/ffi/va_list.rs index d0f155316a109..a761b24895cf5 100644 --- a/library/core/src/ffi/va_list.rs +++ b/library/core/src/ffi/va_list.rs @@ -216,7 +216,8 @@ impl Clone for VaList<'_> { } } -impl<'f> Drop for VaList<'f> { +#[rustc_const_unstable(feature = "c_variadic_const", issue = "none")] +impl<'f> const Drop for VaList<'f> { fn drop(&mut self) { // SAFETY: this variable argument list is being dropped, so won't be read from again. unsafe { va_end(self) } @@ -291,7 +292,8 @@ impl<'f> VaList<'f> { /// /// [valid]: https://doc.rust-lang.org/nightly/nomicon/what-unsafe-does.html #[inline] - pub unsafe fn arg(&mut self) -> T { + #[rustc_const_unstable(feature = "c_variadic_const", issue = "none")] + pub const unsafe fn arg(&mut self) -> T { // SAFETY: the caller must uphold the safety contract for `va_arg`. unsafe { va_arg(self) } } diff --git a/library/core/src/intrinsics/mod.rs b/library/core/src/intrinsics/mod.rs index b134c0ba4c8bb..72734febdce81 100644 --- a/library/core/src/intrinsics/mod.rs +++ b/library/core/src/intrinsics/mod.rs @@ -3467,7 +3467,7 @@ pub(crate) const fn miri_promise_symbolic_alignment(ptr: *const (), align: usize /// #[rustc_intrinsic] #[rustc_nounwind] -pub unsafe fn va_arg(ap: &mut VaList<'_>) -> T; +pub const unsafe fn va_arg(ap: &mut VaList<'_>) -> T; /// Duplicates a variable argument list. The returned list is initially at the same position as /// the one in `src`, but can be advanced independently. @@ -3498,6 +3498,6 @@ pub fn va_copy<'f>(src: &VaList<'f>) -> VaList<'f> { /// #[rustc_intrinsic] #[rustc_nounwind] -pub unsafe fn va_end(ap: &mut VaList<'_>) { +pub const unsafe fn va_end(ap: &mut VaList<'_>) { /* deliberately does nothing */ } diff --git a/tests/ui/parser/variadic-ffi-semantic-restrictions.stderr b/tests/ui/parser/variadic-ffi-semantic-restrictions.stderr index 20a182b8c49f3..26d5cdaf995aa 100644 --- a/tests/ui/parser/variadic-ffi-semantic-restrictions.stderr +++ b/tests/ui/parser/variadic-ffi-semantic-restrictions.stderr @@ -217,6 +217,10 @@ LL | const unsafe extern "C" fn f4_1(x: isize, _: ...) {} | ^ - value is dropped here | | | the destructor for this type cannot be evaluated in constant functions + | + = note: see issue #133214 for more information + = 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[E0493]: destructor of `VaList<'_>` cannot be evaluated at compile-time --> $DIR/variadic-ffi-semantic-restrictions.rs:36:36 @@ -225,6 +229,10 @@ LL | const extern "C" fn f4_2(x: isize, _: ...) {} | ^ - value is dropped here | | | the destructor for this type cannot be evaluated in constant functions + | + = note: see issue #133214 for more information + = 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[E0493]: destructor of `VaList<'_>` cannot be evaluated at compile-time --> $DIR/variadic-ffi-semantic-restrictions.rs:62:29 @@ -233,6 +241,10 @@ LL | const fn i_f5(x: isize, _: ...) {} | ^ - value is dropped here | | | the destructor for this type cannot be evaluated in constant functions + | + = note: see issue #133214 for more information + = 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 From ab1918fd056716030aeb79870513ce5099de61f1 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Fri, 2 Jan 2026 16:10:28 +0100 Subject: [PATCH 04/14] on call, split the standard and c-variadic arguments --- .../rustc_const_eval/src/interpret/call.rs | 44 +++++++++++++++---- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/compiler/rustc_const_eval/src/interpret/call.rs b/compiler/rustc_const_eval/src/interpret/call.rs index 9c8ca44c5e8f0..85f8ce6e0688f 100644 --- a/compiler/rustc_const_eval/src/interpret/call.rs +++ b/compiler/rustc_const_eval/src/interpret/call.rs @@ -17,7 +17,7 @@ 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, + throw_ub, throw_ub_custom, }; use crate::interpret::EnteredTraceSpan; use crate::{enter_trace_span, fluent_generated as fluent}; @@ -349,12 +349,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> = + 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 { @@ -436,8 +446,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); @@ -445,7 +461,17 @@ 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)?; + + let _ = 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)?; // Must be a tuple From 36759a261bae4b04ad954478e2e8d55ab5138df3 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Fri, 2 Jan 2026 16:41:58 +0100 Subject: [PATCH 05/14] set up `VaList` global storage --- .../rustc_const_eval/src/interpret/call.rs | 43 ++++++++++++++++--- .../rustc_const_eval/src/interpret/memory.rs | 7 ++- .../rustc_const_eval/src/interpret/stack.rs | 36 ++++++++++++++-- 3 files changed, 75 insertions(+), 11 deletions(-) diff --git a/compiler/rustc_const_eval/src/interpret/call.rs b/compiler/rustc_const_eval/src/interpret/call.rs index 85f8ce6e0688f..b726e5e8905ad 100644 --- a/compiler/rustc_const_eval/src/interpret/call.rs +++ b/compiler/rustc_const_eval/src/interpret/call.rs @@ -3,10 +3,10 @@ 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_hir::def_id::DefId; -use rustc_middle::ty::layout::{IntegerExt, TyAndLayout}; +use rustc_middle::ty::layout::{HasTyCtxt, IntegerExt, TyAndLayout}; use rustc_middle::ty::{self, AdtDef, Instance, Ty, VariantDef}; use rustc_middle::{bug, mir, span_bug}; use rustc_span::sym; @@ -15,9 +15,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, + CtfeProvenance, FnVal, ImmTy, InterpCx, InterpResult, MPlaceTy, Machine, MemoryKind, OpTy, + PlaceTy, Pointer, Projectable, Provenance, ReturnAction, ReturnContinuation, Scalar, + StackPopInfo, interp_ok, throw_ub, throw_ub_custom, }; use crate::interpret::EnteredTraceSpan; use crate::{enter_trace_span, fluent_generated as fluent}; @@ -470,7 +470,38 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { let place = self.eval_place(dest)?; let mplace = self.force_allocation(&place)?; - let _ = mplace; + // This global allocation is used as a key so `va_arg` can look up the variable + // argument list corresponding to a `VaList` value. + let alloc_id = self.tcx().reserve_and_set_va_list_alloc(); + + // 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 = Some(alloc_id); + + // A global map that is used to implement `va_arg`. + self.memory.va_list_map.insert(alloc_id, varargs); + + // A VaList is a global allocation, so make sure we get the right root pointer. + // We know this is not an `extern static` so this cannot fail. + let ptr = self.global_root_pointer(Pointer::from(alloc_id)).unwrap(); + let addr = Scalar::from_pointer(ptr, self); + + // 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)?; diff --git a/compiler/rustc_const_eval/src/interpret/memory.rs b/compiler/rustc_const_eval/src/interpret/memory.rs index 405baefbf2e53..5e74879eba967 100644 --- a/compiler/rustc_const_eval/src/interpret/memory.rs +++ b/compiler/rustc_const_eval/src/interpret/memory.rs @@ -22,8 +22,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; @@ -128,6 +128,8 @@ pub struct Memory<'tcx, M: Machine<'tcx>> { /// Map for "extra" function pointers. extra_fn_ptr_map: FxIndexMap, + pub(super) 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 /// that do not exist any more. @@ -163,6 +165,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), } diff --git a/compiler/rustc_const_eval/src/interpret/stack.rs b/compiler/rustc_const_eval/src/interpret/stack.rs index 1c1c59da9d886..d842d33edf205 100644 --- a/compiler/rustc_const_eval/src/interpret/stack.rs +++ b/compiler/rustc_const_eval/src/interpret/stack.rs @@ -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>, + /// Key into `Memory`'s `va_list_map` field. When this frame is popped the key should be + /// removed from `va_list_map` and the elements deallocated. + pub(super) va_list: Option, + /// 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: None, instance, tracing_span: SpanGuard::new(), extra: (), @@ -454,6 +460,14 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { self.deallocate_local(local.value)?; } + // Deallocate any c-variadic arguments. + if let Some(alloc_id) = frame.va_list { + let arguments = self.memory.va_list_map.shift_remove(&alloc_id).unwrap(); + for mplace in arguments { + 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 +613,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)] From ce456705ca874c79941b7c4c8d278591fcbb5b40 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Fri, 2 Jan 2026 16:53:18 +0100 Subject: [PATCH 06/14] implement `va_arg` in `rustc_const_eval` --- .../src/const_eval/machine.rs | 50 +++++++++++++++++-- .../rustc_const_eval/src/interpret/memory.rs | 2 +- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_const_eval/src/const_eval/machine.rs b/compiler/rustc_const_eval/src/const_eval/machine.rs index 719187b990122..0f6fcd4b899c3 100644 --- a/compiler/rustc_const_eval/src/const_eval/machine.rs +++ b/compiler/rustc_const_eval/src/const_eval/machine.rs @@ -22,9 +22,9 @@ use crate::errors::{LongRunning, LongRunningWarn}; use crate::fluent_generated as fluent; use crate::interpret::{ self, AllocId, AllocInit, AllocRange, ConstAllocation, CtfeProvenance, FnArg, Frame, - GlobalAlloc, ImmTy, InterpCx, InterpResult, OpTy, PlaceTy, RangeSet, Scalar, - compile_time_machine, err_inval, interp_ok, throw_exhaust, throw_inval, throw_ub, - throw_ub_custom, throw_unsup, throw_unsup_format, + GlobalAlloc, ImmTy, InterpCx, InterpResult, OpTy, PlaceTy, Pointer, RangeSet, Scalar, + compile_time_machine, err_inval, err_unsup_format, interp_ok, throw_exhaust, throw_inval, + throw_ub, throw_ub_custom, throw_unsup, throw_unsup_format, }; /// When hitting this many interpreted terminators we emit a deny by default lint @@ -590,6 +590,50 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> { let ty = ecx.read_type_id(&args[0])?; ecx.write_type_info(ty, dest)?; } + sym::va_arg => { + let ptr_size = ecx.tcx.data_layout.pointer_size(); + + // The only argument is a `&mut VaList`. + let ap_ref = ecx.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 = ecx + .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(ecx)? + }; + + let (prov, offset) = va_list_ptr.into_raw_parts(); + let alloc_id = prov.unwrap().alloc_id(); + let index = offset.bytes_usize(); + + // Update the offset in this `VaList` value so that a subsequent call to `va_arg` + // reads the next argument. + let new_va_list_ptr = va_list_ptr.wrapping_offset(Size::from_bytes(1), ecx); + let addr = Scalar::from_maybe_pointer(new_va_list_ptr, ecx); + let mut alloc = ecx + .get_ptr_alloc_mut(ap_ref, ptr_size)? + .expect("va_list storage should not be a ZST"); + alloc.write_ptr_sized(Size::ZERO, addr)?; + + let arguments = ecx.memory.va_list_map.get(&alloc_id).ok_or_else(|| { + err_unsup_format!("va_arg on unknown va_list allocation {:?}", alloc_id) + })?; + + let src_mplace = arguments + .get(index) + .ok_or_else(|| err_unsup_format!("va_arg out of bounds (index={index})"))? + .clone(); + + // 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. + ecx.copy_op(&src_mplace, dest)?; + } _ => { // We haven't handled the intrinsic, let's see if we can use a fallback body. diff --git a/compiler/rustc_const_eval/src/interpret/memory.rs b/compiler/rustc_const_eval/src/interpret/memory.rs index 5e74879eba967..e53ba65dd8698 100644 --- a/compiler/rustc_const_eval/src/interpret/memory.rs +++ b/compiler/rustc_const_eval/src/interpret/memory.rs @@ -128,7 +128,7 @@ pub struct Memory<'tcx, M: Machine<'tcx>> { /// Map for "extra" function pointers. extra_fn_ptr_map: FxIndexMap, - pub(super) va_list_map: FxIndexMap>>, + pub(crate) 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 From 2ca4269de7e4958cecfc6482c72a2309267ff9fb Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Fri, 2 Jan 2026 20:31:28 +0100 Subject: [PATCH 07/14] basic support for `AllocKind::VaList` in miri --- src/tools/miri/src/alloc_addresses/mod.rs | 6 +++--- src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs | 8 ++++++-- src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs | 6 +++++- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/tools/miri/src/alloc_addresses/mod.rs b/src/tools/miri/src/alloc_addresses/mod.rs index fed51ed86433c..59799ac613b81 100644 --- a/src/tools/miri/src/alloc_addresses/mod.rs +++ b/src/tools/miri/src/alloc_addresses/mod.rs @@ -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 => { diff --git a/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs b/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs index a21898c506ab9..c9f26d5fefaad 100644 --- a/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs +++ b/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs @@ -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. } } diff --git a/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs b/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs index 173145788ee39..d5d57115ebcbc 100644 --- a/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs +++ b/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs @@ -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. } } From 5d20e76e42e19b9fa3beb27974f3d446c8546c13 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Fri, 2 Jan 2026 16:53:56 +0100 Subject: [PATCH 08/14] add c-variadic const eval test --- compiler/rustc_codegen_gcc/src/common.rs | 1 + compiler/rustc_const_eval/messages.ftl | 2 + .../src/const_eval/machine.rs | 15 +- compiler/rustc_const_eval/src/errors.rs | 2 + .../rustc_middle/src/mir/interpret/error.rs | 2 + tests/ui/consts/const-eval/c-variadic-fail.rs | 66 ++++++ .../consts/const-eval/c-variadic-fail.stderr | 199 ++++++++++++++++++ tests/ui/consts/const-eval/c-variadic.rs | 155 ++++++++++++++ 8 files changed, 438 insertions(+), 4 deletions(-) create mode 100644 tests/ui/consts/const-eval/c-variadic-fail.rs create mode 100644 tests/ui/consts/const-eval/c-variadic-fail.stderr create mode 100644 tests/ui/consts/const-eval/c-variadic.rs diff --git a/compiler/rustc_codegen_gcc/src/common.rs b/compiler/rustc_codegen_gcc/src/common.rs index c7e7a2e5e6601..b941eade5eb65 100644 --- a/compiler/rustc_codegen_gcc/src/common.rs +++ b/compiler/rustc_codegen_gcc/src/common.rs @@ -4,6 +4,7 @@ use rustc_abi::{self as abi, HasDataLayout}; use rustc_codegen_ssa::traits::{ BaseTypeCodegenMethods, ConstCodegenMethods, MiscCodegenMethods, StaticCodegenMethods, }; +use rustc_middle::bug; use rustc_middle::mir::Mutability; use rustc_middle::mir::interpret::{ConstAllocation, GlobalAlloc, PointerArithmetic, Scalar}; use rustc_middle::ty::layout::LayoutOf; diff --git a/compiler/rustc_const_eval/messages.ftl b/compiler/rustc_const_eval/messages.ftl index 559f2c04759ce..72ec4cac697d6 100644 --- a/compiler/rustc_const_eval/messages.ftl +++ b/compiler/rustc_const_eval/messages.ftl @@ -432,6 +432,8 @@ const_eval_unterminated_c_string = const_eval_unwind_past_top = unwinding past the topmost frame of the stack +const_eval_va_arg_out_of_bounds = more C-variadic arguments read than were passed + ## The `front_matter`s here refer to either `const_eval_front_matter_invalid_value` or `const_eval_front_matter_invalid_value_with_path`. ## (We'd love to sort this differently to make that more clear but tidy won't let us...) const_eval_validation_box_to_uninhabited = {$front_matter}: encountered a box pointing to uninhabited type {$ty} diff --git a/compiler/rustc_const_eval/src/const_eval/machine.rs b/compiler/rustc_const_eval/src/const_eval/machine.rs index 0f6fcd4b899c3..dc15e11dcc212 100644 --- a/compiler/rustc_const_eval/src/const_eval/machine.rs +++ b/compiler/rustc_const_eval/src/const_eval/machine.rs @@ -624,14 +624,21 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> { err_unsup_format!("va_arg on unknown va_list allocation {:?}", alloc_id) })?; - let src_mplace = arguments - .get(index) - .ok_or_else(|| err_unsup_format!("va_arg out of bounds (index={index})"))? - .clone(); + let Some(src_mplace) = arguments.get(index).cloned() else { + throw_ub!(VaArgOutOfBounds) + }; // 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 + ); + } + ecx.copy_op(&src_mplace, dest)?; } diff --git a/compiler/rustc_const_eval/src/errors.rs b/compiler/rustc_const_eval/src/errors.rs index 13212676cb436..d61404c16a39c 100644 --- a/compiler/rustc_const_eval/src/errors.rs +++ b/compiler/rustc_const_eval/src/errors.rs @@ -510,6 +510,7 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> { InvalidNichedEnumVariantWritten { .. } => { const_eval_invalid_niched_enum_variant_written } + VaArgOutOfBounds => const_eval_va_arg_out_of_bounds, AbiMismatchArgument { .. } => const_eval_incompatible_arg_types, AbiMismatchReturn { .. } => const_eval_incompatible_return_types, } @@ -536,6 +537,7 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> { | InvalidMeta(InvalidMetaKind::TooBig) | InvalidUninitBytes(None) | DeadLocal + | VaArgOutOfBounds | UninhabitedEnumVariantWritten(_) | UninhabitedEnumVariantRead(_) => {} diff --git a/compiler/rustc_middle/src/mir/interpret/error.rs b/compiler/rustc_middle/src/mir/interpret/error.rs index b3003f4c7bca0..970dbd95f7cc3 100644 --- a/compiler/rustc_middle/src/mir/interpret/error.rs +++ b/compiler/rustc_middle/src/mir/interpret/error.rs @@ -436,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)] diff --git a/tests/ui/consts/const-eval/c-variadic-fail.rs b/tests/ui/consts/const-eval/c-variadic-fail.rs new file mode 100644 index 0000000000000..4a8ca58a38f19 --- /dev/null +++ b/tests/ui/consts/const-eval/c-variadic-fail.rs @@ -0,0 +1,66 @@ +//@ build-fail + +#![feature(c_variadic)] +#![feature(const_destruct)] +#![feature(c_variadic_const)] + +const unsafe extern "C" fn read_n(mut ap: ...) { + let mut i = N; + while i > 0 { + i -= 1; + let _ = ap.arg::(); + } +} + +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(mut ap: ...) -> T { + ap.arg::() +} + +unsafe fn read_cast() { + const { read_as::(1i32) }; + const { read_as::(1u32) }; + + const { read_as::(1i32, 2u64, 3.0f64) }; + const { read_as::(1u32, 2u64, 3.0f64) }; + + const { read_as::(1i64) }; + const { read_as::(1u64) }; + + const { read_as::(1i32) }; + //~^ ERROR va_arg type mismatch: requested `u32`, but next argument is `i32` + + const { read_as::(1u32) }; + //~^ ERROR va_arg type mismatch: requested `i32`, but next argument is `u32` + + const { read_as::(1u64) }; + //~^ ERROR va_arg type mismatch: requested `i32`, but next argument is `u64` + + const { read_as::(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 main() { + unsafe { + read_too_many(); + read_cast(); + } +} diff --git a/tests/ui/consts/const-eval/c-variadic-fail.stderr b/tests/ui/consts/const-eval/c-variadic-fail.stderr new file mode 100644 index 0000000000000..0c4abddd2c3b1 --- /dev/null +++ b/tests/ui/consts/const-eval/c-variadic-fail.stderr @@ -0,0 +1,199 @@ +error[E0080]: more C-variadic arguments read than were passed + --> $DIR/c-variadic-fail.rs:23: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:11:17 + | +LL | let _ = ap.arg::(); + | ^^^^^^^^^^^^^^^ +note: inside `VaList::<'_>::arg::` + --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL + +note: erroneous constant encountered + --> $DIR/c-variadic-fail.rs:23:5 + | +LL | const { read_n::<1>() } + | ^^^^^^^^^^^^^^^^^^^^^^^ + +note: erroneous constant encountered + --> $DIR/c-variadic-fail.rs:23: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:27: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:11:17 + | +LL | let _ = ap.arg::(); + | ^^^^^^^^^^^^^^^ +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 + | +LL | const { read_n::<2>(1) } + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +note: erroneous constant encountered + --> $DIR/c-variadic-fail.rs:27: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:45: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:32:5 + | +LL | ap.arg::() + | ^^^^^^^^^^^^^ +note: inside `VaList::<'_>::arg::` + --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL + +note: erroneous constant encountered + --> $DIR/c-variadic-fail.rs:45:5 + | +LL | const { read_as::(1i32) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +note: erroneous constant encountered + --> $DIR/c-variadic-fail.rs:45:5 + | +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:48: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:32:5 + | +LL | ap.arg::() + | ^^^^^^^^^^^^^ +note: inside `VaList::<'_>::arg::` + --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL + +note: erroneous constant encountered + --> $DIR/c-variadic-fail.rs:48:5 + | +LL | const { read_as::(1u32) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +note: erroneous constant encountered + --> $DIR/c-variadic-fail.rs:48:5 + | +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:51: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:32:5 + | +LL | ap.arg::() + | ^^^^^^^^^^^^^ +note: inside `VaList::<'_>::arg::` + --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL + +note: erroneous constant encountered + --> $DIR/c-variadic-fail.rs:51:5 + | +LL | const { read_as::(1u64) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +note: erroneous constant encountered + --> $DIR/c-variadic-fail.rs:51:5 + | +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:54: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:32:5 + | +LL | ap.arg::() + | ^^^^^^^^^^^^^ +note: inside `VaList::<'_>::arg::` + --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL + +note: erroneous constant encountered + --> $DIR/c-variadic-fail.rs:54:5 + | +LL | const { read_as::(1i32) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +note: erroneous constant encountered + --> $DIR/c-variadic-fail.rs:54:5 + | +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:57: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:32:5 + | +LL | ap.arg::() + | ^^^^^^^^^^^^^ +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:57:5 + | +LL | const { read_as::<*const u8>(1i32) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +note: erroneous constant encountered + --> $DIR/c-variadic-fail.rs:57:5 + | +LL | const { read_as::<*const u8>(1i32) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error: aborting due to 7 previous errors + +For more information about this error, try `rustc --explain E0080`. diff --git a/tests/ui/consts/const-eval/c-variadic.rs b/tests/ui/consts/const-eval/c-variadic.rs new file mode 100644 index 0000000000000..ec49b5cc5831d --- /dev/null +++ b/tests/ui/consts/const-eval/c-variadic.rs @@ -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::().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(), + ); + 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(); +} From 25e96ba3afb08364f562718e142d05af98fd4e5f Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Wed, 7 Jan 2026 14:43:01 +0100 Subject: [PATCH 09/14] basic `va_copy` implementation this does not detect UB yet when a `VaList` is copied manually --- .../src/const_eval/machine.rs | 26 ++++++++- library/core/src/ffi/va_list.rs | 7 ++- library/core/src/intrinsics/mod.rs | 2 +- tests/ui/consts/const-eval/c-variadic-fail.rs | 24 +++++++- .../consts/const-eval/c-variadic-fail.stderr | 56 +++++++++---------- 5 files changed, 81 insertions(+), 34 deletions(-) diff --git a/compiler/rustc_const_eval/src/const_eval/machine.rs b/compiler/rustc_const_eval/src/const_eval/machine.rs index dc15e11dcc212..511e794ee2f24 100644 --- a/compiler/rustc_const_eval/src/const_eval/machine.rs +++ b/compiler/rustc_const_eval/src/const_eval/machine.rs @@ -22,7 +22,7 @@ use crate::errors::{LongRunning, LongRunningWarn}; use crate::fluent_generated as fluent; use crate::interpret::{ self, AllocId, AllocInit, AllocRange, ConstAllocation, CtfeProvenance, FnArg, Frame, - GlobalAlloc, ImmTy, InterpCx, InterpResult, OpTy, PlaceTy, Pointer, RangeSet, Scalar, + GlobalAlloc, ImmTy, InterpCx, InterpResult, OpTy, PlaceTy, RangeSet, Scalar, compile_time_machine, err_inval, err_unsup_format, interp_ok, throw_exhaust, throw_inval, throw_ub, throw_ub_custom, throw_unsup, throw_unsup_format, }; @@ -590,6 +590,30 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> { let ty = ecx.read_type_id(&args[0])?; ecx.write_type_info(ty, dest)?; } + sym::va_copy => { + // pub const unsafe fn va_copy<'f>(dest: *mut VaList<'f>, src: &VaList<'f>); + + // `src` is `&VaList`, so grab the underlying `VaList` type/layout. + let src_va_list_ty = match args[1].layout.ty.kind() { + ty::Ref(_, ty, _) => *ty, + _ => bug!( + "va_copy: expected second argument to be `&VaList`, got {:?}", + args[1].layout.ty + ), + }; + let va_list_layout = ecx.layout_of(src_va_list_ty)?; + + // Get the pointers to the actual VaList storage. + let dest_ptr = ecx.read_pointer(&args[0])?; // *mut VaList + let src_ptr = ecx.read_pointer(&args[1])?; // &VaList + + // Turn them into places and copy the bytes. + let src_mplace = ecx.ptr_to_mplace(src_ptr, va_list_layout); + let dest_mplace = ecx.ptr_to_mplace(dest_ptr, va_list_layout); + + ecx.copy_op(&src_mplace, &dest_mplace)?; + } + sym::va_arg => { let ptr_size = ecx.tcx.data_layout.pointer_size(); diff --git a/library/core/src/ffi/va_list.rs b/library/core/src/ffi/va_list.rs index a761b24895cf5..45a9b7ba5293e 100644 --- a/library/core/src/ffi/va_list.rs +++ b/library/core/src/ffi/va_list.rs @@ -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 diff --git a/library/core/src/intrinsics/mod.rs b/library/core/src/intrinsics/mod.rs index 72734febdce81..968c272df4e25 100644 --- a/library/core/src/intrinsics/mod.rs +++ b/library/core/src/intrinsics/mod.rs @@ -3479,7 +3479,7 @@ pub const unsafe fn va_arg(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() } diff --git a/tests/ui/consts/const-eval/c-variadic-fail.rs b/tests/ui/consts/const-eval/c-variadic-fail.rs index 4a8ca58a38f19..bf951455d3dc9 100644 --- a/tests/ui/consts/const-eval/c-variadic-fail.rs +++ b/tests/ui/consts/const-eval/c-variadic-fail.rs @@ -1,8 +1,10 @@ //@ build-fail #![feature(c_variadic)] -#![feature(const_destruct)] #![feature(c_variadic_const)] +#![feature(const_trait_impl)] +#![feature(const_destruct)] +#![feature(const_clone)] const unsafe extern "C" fn read_n(mut ap: ...) { let mut i = N; @@ -58,9 +60,29 @@ unsafe fn read_cast() { //~^ ERROR va_arg type mismatch: requested `*const u8`, but next argument is `i32` } +fn manual_copy() { + const unsafe extern "C" fn helper(ap: ...) -> i32 { + // 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. + let mut copy = unsafe { u.assume_init() }; + + // Reading arguments from this copy is UB. + copy.arg::() + } + + const { unsafe { helper(1, 2, 3) } }; +} + fn main() { unsafe { read_too_many(); read_cast(); + manual_copy(); } } diff --git a/tests/ui/consts/const-eval/c-variadic-fail.stderr b/tests/ui/consts/const-eval/c-variadic-fail.stderr index 0c4abddd2c3b1..3f85f1e511e86 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:23:13 + --> $DIR/c-variadic-fail.rs:25: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:11:17 + --> $DIR/c-variadic-fail.rs:13: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:23:5 + --> $DIR/c-variadic-fail.rs:25:5 | LL | const { read_n::<1>() } | ^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:23:5 + --> $DIR/c-variadic-fail.rs:25: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:27:13 + --> $DIR/c-variadic-fail.rs:29: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:11:17 + --> $DIR/c-variadic-fail.rs:13: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:27:5 + --> $DIR/c-variadic-fail.rs:29:5 | LL | const { read_n::<2>(1) } | ^^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:27:5 + --> $DIR/c-variadic-fail.rs:29: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:45:13 + --> $DIR/c-variadic-fail.rs:47: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:32:5 + --> $DIR/c-variadic-fail.rs:34: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:45:5 + --> $DIR/c-variadic-fail.rs:47:5 | LL | const { read_as::(1i32) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:45:5 + --> $DIR/c-variadic-fail.rs:47: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:48:13 + --> $DIR/c-variadic-fail.rs:50: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:32:5 + --> $DIR/c-variadic-fail.rs:34: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:48:5 + --> $DIR/c-variadic-fail.rs:50:5 | LL | const { read_as::(1u32) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:48:5 + --> $DIR/c-variadic-fail.rs:50: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:51:13 + --> $DIR/c-variadic-fail.rs:53: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:32:5 + --> $DIR/c-variadic-fail.rs:34: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:51:5 + --> $DIR/c-variadic-fail.rs:53:5 | LL | const { read_as::(1u64) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:51:5 + --> $DIR/c-variadic-fail.rs:53: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:54:13 + --> $DIR/c-variadic-fail.rs:56: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:32:5 + --> $DIR/c-variadic-fail.rs:34: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:54:5 + --> $DIR/c-variadic-fail.rs:56:5 | LL | const { read_as::(1i32) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:54:5 + --> $DIR/c-variadic-fail.rs:56: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:57:13 + --> $DIR/c-variadic-fail.rs:59: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:32:5 + --> $DIR/c-variadic-fail.rs:34: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:57:5 + --> $DIR/c-variadic-fail.rs:59:5 | LL | const { read_as::<*const u8>(1i32) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:57:5 + --> $DIR/c-variadic-fail.rs:59:5 | LL | const { read_as::<*const u8>(1i32) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From c0ce0b0bc97f2625d143dc8e4f51419548c17f35 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Thu, 8 Jan 2026 10:41:23 +0100 Subject: [PATCH 10/14] move over to the shared interpreter --- .../src/const_eval/machine.rs | 79 +---------- .../rustc_const_eval/src/interpret/call.rs | 2 +- .../src/interpret/intrinsics.rs | 127 +++++++++++++++++- .../rustc_const_eval/src/interpret/stack.rs | 15 +-- tests/ui/consts/const-eval/c-variadic-fail.rs | 28 +++- .../consts/const-eval/c-variadic-fail.stderr | 123 +++++++++++++---- 6 files changed, 254 insertions(+), 120 deletions(-) diff --git a/compiler/rustc_const_eval/src/const_eval/machine.rs b/compiler/rustc_const_eval/src/const_eval/machine.rs index 511e794ee2f24..719187b990122 100644 --- a/compiler/rustc_const_eval/src/const_eval/machine.rs +++ b/compiler/rustc_const_eval/src/const_eval/machine.rs @@ -23,8 +23,8 @@ use crate::fluent_generated as fluent; use crate::interpret::{ self, AllocId, AllocInit, AllocRange, ConstAllocation, CtfeProvenance, FnArg, Frame, GlobalAlloc, ImmTy, InterpCx, InterpResult, OpTy, PlaceTy, RangeSet, Scalar, - compile_time_machine, err_inval, err_unsup_format, interp_ok, throw_exhaust, throw_inval, - throw_ub, throw_ub_custom, throw_unsup, throw_unsup_format, + compile_time_machine, err_inval, interp_ok, throw_exhaust, throw_inval, throw_ub, + throw_ub_custom, throw_unsup, throw_unsup_format, }; /// When hitting this many interpreted terminators we emit a deny by default lint @@ -590,81 +590,6 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> { let ty = ecx.read_type_id(&args[0])?; ecx.write_type_info(ty, dest)?; } - sym::va_copy => { - // pub const unsafe fn va_copy<'f>(dest: *mut VaList<'f>, src: &VaList<'f>); - - // `src` is `&VaList`, so grab the underlying `VaList` type/layout. - let src_va_list_ty = match args[1].layout.ty.kind() { - ty::Ref(_, ty, _) => *ty, - _ => bug!( - "va_copy: expected second argument to be `&VaList`, got {:?}", - args[1].layout.ty - ), - }; - let va_list_layout = ecx.layout_of(src_va_list_ty)?; - - // Get the pointers to the actual VaList storage. - let dest_ptr = ecx.read_pointer(&args[0])?; // *mut VaList - let src_ptr = ecx.read_pointer(&args[1])?; // &VaList - - // Turn them into places and copy the bytes. - let src_mplace = ecx.ptr_to_mplace(src_ptr, va_list_layout); - let dest_mplace = ecx.ptr_to_mplace(dest_ptr, va_list_layout); - - ecx.copy_op(&src_mplace, &dest_mplace)?; - } - - sym::va_arg => { - let ptr_size = ecx.tcx.data_layout.pointer_size(); - - // The only argument is a `&mut VaList`. - let ap_ref = ecx.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 = ecx - .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(ecx)? - }; - - let (prov, offset) = va_list_ptr.into_raw_parts(); - let alloc_id = prov.unwrap().alloc_id(); - let index = offset.bytes_usize(); - - // Update the offset in this `VaList` value so that a subsequent call to `va_arg` - // reads the next argument. - let new_va_list_ptr = va_list_ptr.wrapping_offset(Size::from_bytes(1), ecx); - let addr = Scalar::from_maybe_pointer(new_va_list_ptr, ecx); - let mut alloc = ecx - .get_ptr_alloc_mut(ap_ref, ptr_size)? - .expect("va_list storage should not be a ZST"); - alloc.write_ptr_sized(Size::ZERO, addr)?; - - let arguments = ecx.memory.va_list_map.get(&alloc_id).ok_or_else(|| { - err_unsup_format!("va_arg on unknown va_list allocation {:?}", alloc_id) - })?; - - let Some(src_mplace) = arguments.get(index).cloned() else { - throw_ub!(VaArgOutOfBounds) - }; - - // 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 - ); - } - - ecx.copy_op(&src_mplace, dest)?; - } _ => { // We haven't handled the intrinsic, let's see if we can use a fallback body. diff --git a/compiler/rustc_const_eval/src/interpret/call.rs b/compiler/rustc_const_eval/src/interpret/call.rs index b726e5e8905ad..fb608231dd782 100644 --- a/compiler/rustc_const_eval/src/interpret/call.rs +++ b/compiler/rustc_const_eval/src/interpret/call.rs @@ -485,7 +485,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { } // When the frame is dropped, this ID is used to deallocate the variable arguments list. - self.frame_mut().va_list = Some(alloc_id); + self.frame_mut().va_list = varargs.clone(); // A global map that is used to implement `va_arg`. self.memory.va_list_map.insert(alloc_id, varargs); diff --git a/compiler/rustc_const_eval/src/interpret/intrinsics.rs b/compiler/rustc_const_eval/src/interpret/intrinsics.rs index e526f6120689a..3c353537c2da7 100644 --- a/compiler/rustc_const_eval/src/interpret/intrinsics.rs +++ b/compiler/rustc_const_eval/src/interpret/intrinsics.rs @@ -23,7 +23,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::fluent_generated as fluent; use crate::interpret::Writeable; @@ -734,6 +734,131 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { self.float_muladd_intrinsic::(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_usize(); + + // Look up arguments without consuming src. + let arguments = self.memory.va_list_map.get(&src_alloc_id).ok_or_else(|| { + err_unsup_format!("va_copy on unknown va_list allocation {:?}", src_alloc_id) + })?; + + // Mint a fresh alloc_id for the destination and clone the argument vector. + let new_alloc_id = tcx.reserve_and_set_va_list_alloc(); + self.memory.va_list_map.insert(new_alloc_id, arguments.clone()); + + let tcx_ptr: Pointer = + Pointer::new(new_alloc_id.into(), Size::from_bytes(index)); + let new_ptr: Pointer = self.global_root_pointer(tcx_ptr)?; + + // Now overwrite the token pointer stored inside the VaList. + // VaList is a newtype: its only field is the pointer token. + let x = Scalar::from_pointer(new_ptr, self); + + let mplace = self.force_allocation(dest)?; + let mut alloc = self.get_place_alloc_mut(&mplace)?.unwrap(); + + alloc.write_ptr_sized(Size::ZERO, x)?; + } + + 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.memory.va_list_map.swap_remove(&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_usize(); + + let Some(va_list) = self.memory.va_list_map.swap_remove(&alloc_id) else { + throw_unsup_format!("va_arg on unknown va_list allocation {:?}", alloc_id) + }; + + let Some(src_mplace) = va_list.get(index).cloned() else { + throw_ub!(VaArgOutOfBounds) + }; + + // Mint a fresh alloc_id for the destination and clone the argument vector. + let new_alloc_id = self.tcx.reserve_and_set_va_list_alloc(); + self.memory.va_list_map.insert(new_alloc_id, va_list); + + // Update the offset in this `VaList` value so that a subsequent call to `va_arg` + // reads the next argument. + let tcx_ptr: Pointer = + Pointer::new(new_alloc_id.into(), Size::from_bytes(index + 1)); + let new_va_list_ptr: Pointer = self.global_root_pointer(tcx_ptr)?; + + let x = 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, x)?; + + // 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), } diff --git a/compiler/rustc_const_eval/src/interpret/stack.rs b/compiler/rustc_const_eval/src/interpret/stack.rs index d842d33edf205..9b2d99bb297f4 100644 --- a/compiler/rustc_const_eval/src/interpret/stack.rs +++ b/compiler/rustc_const_eval/src/interpret/stack.rs @@ -91,9 +91,9 @@ pub struct Frame<'tcx, Prov: Provenance = CtfeProvenance, Extra = ()> { /// Do *not* access this directly; always go through the machine hook! pub locals: IndexVec>, - /// Key into `Memory`'s `va_list_map` field. When this frame is popped the key should be - /// removed from `va_list_map` and the elements deallocated. - pub(super) va_list: Option, + /// The complete variable argument list of this frame. Its elements must be dropped when the + /// frame is popped. + pub(super) va_list: Vec>, /// The span of the `tracing` crate is stored here. /// When the guard is dropped, the span is exited. This gives us @@ -382,7 +382,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { return_cont, return_place: return_place.clone(), locals, - va_list: None, + va_list: vec![], instance, tracing_span: SpanGuard::new(), extra: (), @@ -461,11 +461,8 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { } // Deallocate any c-variadic arguments. - if let Some(alloc_id) = frame.va_list { - let arguments = self.memory.va_list_map.shift_remove(&alloc_id).unwrap(); - for mplace in arguments { - self.deallocate_vararg(&mplace)?; - } + for mplace in &frame.va_list { + self.deallocate_vararg(mplace)?; } // Call the machine hook, which determines the next steps. diff --git a/tests/ui/consts/const-eval/c-variadic-fail.rs b/tests/ui/consts/const-eval/c-variadic-fail.rs index bf951455d3dc9..a82bd34d25135 100644 --- a/tests/ui/consts/const-eval/c-variadic-fail.rs +++ b/tests/ui/consts/const-eval/c-variadic-fail.rs @@ -6,6 +6,8 @@ #![feature(const_destruct)] #![feature(const_clone)] +use std::ffi::VaList; + const unsafe extern "C" fn read_n(mut ap: ...) { let mut i = N; while i > 0 { @@ -60,8 +62,23 @@ unsafe fn read_cast() { //~^ 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::()] { + unsafe { std::mem::transmute(ap) } + } + + const { + unsafe { + let ap = helper(1, 2, 3); + let mut ap = std::mem::transmute::<_, VaList>(ap); + ap.arg::(); + //~^ ERROR memory access failed: ALLOC0 has been freed, so this pointer is dangling [E0080] + } + }; +} + fn manual_copy() { - const unsafe extern "C" fn helper(ap: ...) -> i32 { + const unsafe extern "C" fn helper(ap: ...) { // A copy created using Clone is valid, and can be used to read arguments. let mut copy = ap.clone(); assert!(copy.arg::() == 1i32); @@ -72,11 +89,16 @@ fn manual_copy() { // Manually creating the copy is fine. let mut copy = unsafe { u.assume_init() }; - // Reading arguments from this copy is UB. - copy.arg::() + // Using the copy is actually fine. + let _ = copy.arg::(); + 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 main() { diff --git a/tests/ui/consts/const-eval/c-variadic-fail.stderr b/tests/ui/consts/const-eval/c-variadic-fail.stderr index 3f85f1e511e86..38a1326cde6c9 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:25:13 + --> $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:13:17 + --> $DIR/c-variadic-fail.rs:15: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:25:5 + --> $DIR/c-variadic-fail.rs:27:5 | LL | const { read_n::<1>() } | ^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:25:5 + --> $DIR/c-variadic-fail.rs:27: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:29:13 + --> $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:13:17 + --> $DIR/c-variadic-fail.rs:15: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:29:5 + --> $DIR/c-variadic-fail.rs:31:5 | LL | const { read_n::<2>(1) } | ^^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:29:5 + --> $DIR/c-variadic-fail.rs:31: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:47:13 + --> $DIR/c-variadic-fail.rs:49: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:34:5 + --> $DIR/c-variadic-fail.rs:36: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:47:5 + --> $DIR/c-variadic-fail.rs:49:5 | LL | const { read_as::(1i32) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:47:5 + --> $DIR/c-variadic-fail.rs:49: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:50:13 + --> $DIR/c-variadic-fail.rs:52: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:34:5 + --> $DIR/c-variadic-fail.rs:36: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:50:5 + --> $DIR/c-variadic-fail.rs:52:5 | LL | const { read_as::(1u32) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:50:5 + --> $DIR/c-variadic-fail.rs:52: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:53:13 + --> $DIR/c-variadic-fail.rs:55: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:34:5 + --> $DIR/c-variadic-fail.rs:36: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:53:5 + --> $DIR/c-variadic-fail.rs:55:5 | LL | const { read_as::(1u64) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:53:5 + --> $DIR/c-variadic-fail.rs:55: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:56:13 + --> $DIR/c-variadic-fail.rs:58: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:34:5 + --> $DIR/c-variadic-fail.rs:36: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:56:5 + --> $DIR/c-variadic-fail.rs:58:5 | LL | const { read_as::(1i32) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:56:5 + --> $DIR/c-variadic-fail.rs:58: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:59:13 + --> $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:34:5 + --> $DIR/c-variadic-fail.rs:36:5 | LL | ap.arg::() | ^^^^^^^^^^^^^ @@ -181,19 +181,84 @@ 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:59:5 + --> $DIR/c-variadic-fail.rs:61:5 | LL | const { read_as::<*const u8>(1i32) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:59:5 + --> $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: aborting due to 7 previous errors +error[E0080]: memory access failed: ALLOC0 has been freed, so this pointer is dangling + --> $DIR/c-variadic-fail.rs:74:13 + | +LL | ap.arg::(); + | ^^^^^^^^^^^^^^^ evaluation of `use_after_free::{constant#0}` failed inside this call + | +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 + | +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:98:22 + | +LL | const { unsafe { helper(1, 2, 3) } }; + | ^^^^^^^^^^^^^^^ evaluation of `manual_copy::{constant#0}` failed inside this call + | +note: inside `manual_copy::helper` + --> $DIR/c-variadic-fail.rs:95:9 + | +LL | drop(ap); + | ^^^^^^^^ +note: inside `std::mem::drop::>` + --> $SRC_DIR/core/src/mem/mod.rs:LL:COL +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:98:5 + | +LL | const { unsafe { helper(1, 2, 3) } }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +note: erroneous constant encountered + --> $DIR/c-variadic-fail.rs:98:5 + | +LL | const { unsafe { helper(1, 2, 3) } }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error: aborting due to 9 previous errors For more information about this error, try `rustc --explain E0080`. From 9e57c736c46b7f356da2dbc90a9c2c78e0ab566c Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Tue, 20 Jan 2026 21:37:40 +0100 Subject: [PATCH 11/14] more copy UB tests --- tests/ui/consts/const-eval/c-variadic-fail.rs | 49 ++++++++++-- .../consts/const-eval/c-variadic-fail.stderr | 74 +++++++++++++++++-- 2 files changed, 110 insertions(+), 13 deletions(-) diff --git a/tests/ui/consts/const-eval/c-variadic-fail.rs b/tests/ui/consts/const-eval/c-variadic-fail.rs index a82bd34d25135..37810ddb356df 100644 --- a/tests/ui/consts/const-eval/c-variadic-fail.rs +++ b/tests/ui/consts/const-eval/c-variadic-fail.rs @@ -77,17 +77,23 @@ fn use_after_free() { }; } -fn manual_copy() { - const unsafe extern "C" fn helper(ap: ...) { +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(); + 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) }; + unsafe { core::ptr::copy_nonoverlapping(&$ap, u.as_mut_ptr(), 1) }; // Manually creating the copy is fine. - let mut copy = unsafe { u.assume_init() }; + 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::(); @@ -101,10 +107,41 @@ fn manual_copy() { //~^ 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::(); + 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::(); + let _ = ap.arg::(); + } + + 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(); + manual_copy_read(); + manual_copy_drop(); + manual_copy_forget(); } } diff --git a/tests/ui/consts/const-eval/c-variadic-fail.stderr b/tests/ui/consts/const-eval/c-variadic-fail.stderr index 38a1326cde6c9..4c569a4492d47 100644 --- a/tests/ui/consts/const-eval/c-variadic-fail.stderr +++ b/tests/ui/consts/const-eval/c-variadic-fail.stderr @@ -228,13 +228,13 @@ 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:98:22 + --> $DIR/c-variadic-fail.rs:106:22 | LL | const { unsafe { helper(1, 2, 3) } }; - | ^^^^^^^^^^^^^^^ evaluation of `manual_copy::{constant#0}` failed inside this call + | ^^^^^^^^^^^^^^^ evaluation of `manual_copy_drop::{constant#0}` failed inside this call | -note: inside `manual_copy::helper` - --> $DIR/c-variadic-fail.rs:95:9 +note: inside `manual_copy_drop::helper` + --> $DIR/c-variadic-fail.rs:103:9 | LL | drop(ap); | ^^^^^^^^ @@ -246,19 +246,79 @@ note: inside ` as Drop>::drop` --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:98:5 + --> $DIR/c-variadic-fail.rs:106:5 | LL | const { unsafe { helper(1, 2, 3) } }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:98:5 + --> $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: aborting due to 9 previous errors +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::>` + --> $SRC_DIR/core/src/mem/mod.rs:LL:COL +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: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::(); + | ^^^^^^^^^^^^^^^ +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 + | +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`. From 67aabb20c8bcaef396f4b6d2fa87e602043a5feb Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Thu, 22 Jan 2026 10:37:49 +0100 Subject: [PATCH 12/14] stop using `reserve_and_set_va_list_alloc` --- compiler/rustc_const_eval/src/interpret/call.rs | 2 +- compiler/rustc_const_eval/src/interpret/intrinsics.rs | 5 ++--- compiler/rustc_const_eval/src/interpret/memory.rs | 6 ++++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/compiler/rustc_const_eval/src/interpret/call.rs b/compiler/rustc_const_eval/src/interpret/call.rs index fb608231dd782..b14e2ca9c41ba 100644 --- a/compiler/rustc_const_eval/src/interpret/call.rs +++ b/compiler/rustc_const_eval/src/interpret/call.rs @@ -472,7 +472,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { // This global allocation is used as a key so `va_arg` can look up the variable // argument list corresponding to a `VaList` value. - let alloc_id = self.tcx().reserve_and_set_va_list_alloc(); + let alloc_id = self.tcx().reserve_alloc_id(); // Consume the remaining arguments and store them in a global allocation. let mut varargs = Vec::new(); diff --git a/compiler/rustc_const_eval/src/interpret/intrinsics.rs b/compiler/rustc_const_eval/src/interpret/intrinsics.rs index 3c353537c2da7..3c93ae71428c8 100644 --- a/compiler/rustc_const_eval/src/interpret/intrinsics.rs +++ b/compiler/rustc_const_eval/src/interpret/intrinsics.rs @@ -758,9 +758,8 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { })?; // Mint a fresh alloc_id for the destination and clone the argument vector. - let new_alloc_id = tcx.reserve_and_set_va_list_alloc(); + let new_alloc_id = tcx.reserve_alloc_id(); self.memory.va_list_map.insert(new_alloc_id, arguments.clone()); - let tcx_ptr: Pointer = Pointer::new(new_alloc_id.into(), Size::from_bytes(index)); let new_ptr: Pointer = self.global_root_pointer(tcx_ptr)?; @@ -830,7 +829,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { }; // Mint a fresh alloc_id for the destination and clone the argument vector. - let new_alloc_id = self.tcx.reserve_and_set_va_list_alloc(); + let new_alloc_id = self.tcx.reserve_alloc_id(); self.memory.va_list_map.insert(new_alloc_id, va_list); // Update the offset in this `VaList` value so that a subsequent call to `va_arg` diff --git a/compiler/rustc_const_eval/src/interpret/memory.rs b/compiler/rustc_const_eval/src/interpret/memory.rs index e53ba65dd8698..4dc14b002c636 100644 --- a/compiler/rustc_const_eval/src/interpret/memory.rs +++ b/compiler/rustc_const_eval/src/interpret/memory.rs @@ -204,9 +204,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" ); } _ => {} From bdc093503482ac886be7cdcf3ceff7311251be18 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Thu, 22 Jan 2026 11:26:07 +0100 Subject: [PATCH 13/14] make `va_list_map` private --- .../rustc_const_eval/src/interpret/call.rs | 18 +++----- .../src/interpret/intrinsics.rs | 44 +++++++------------ .../rustc_const_eval/src/interpret/memory.rs | 30 ++++++++++++- 3 files changed, 49 insertions(+), 43 deletions(-) diff --git a/compiler/rustc_const_eval/src/interpret/call.rs b/compiler/rustc_const_eval/src/interpret/call.rs index b14e2ca9c41ba..9a0337fc59f8b 100644 --- a/compiler/rustc_const_eval/src/interpret/call.rs +++ b/compiler/rustc_const_eval/src/interpret/call.rs @@ -6,7 +6,7 @@ use either::{Left, Right}; use rustc_abi::{self as abi, ExternAbi, FieldIdx, HasDataLayout, Integer, Size, VariantIdx}; use rustc_data_structures::assert_matches; use rustc_hir::def_id::DefId; -use rustc_middle::ty::layout::{HasTyCtxt, IntegerExt, TyAndLayout}; +use rustc_middle::ty::layout::{IntegerExt, TyAndLayout}; use rustc_middle::ty::{self, AdtDef, Instance, Ty, VariantDef}; use rustc_middle::{bug, mir, span_bug}; use rustc_span::sym; @@ -16,8 +16,8 @@ use tracing::{info, instrument, trace}; use super::{ CtfeProvenance, FnVal, ImmTy, InterpCx, InterpResult, MPlaceTy, Machine, MemoryKind, OpTy, - PlaceTy, Pointer, Projectable, Provenance, ReturnAction, ReturnContinuation, Scalar, - StackPopInfo, interp_ok, throw_ub, throw_ub_custom, + PlaceTy, Projectable, Provenance, ReturnAction, ReturnContinuation, Scalar, StackPopInfo, + interp_ok, throw_ub, throw_ub_custom, }; use crate::interpret::EnteredTraceSpan; use crate::{enter_trace_span, fluent_generated as fluent}; @@ -470,10 +470,6 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { let place = self.eval_place(dest)?; let mplace = self.force_allocation(&place)?; - // This global allocation is used as a key so `va_arg` can look up the variable - // argument list corresponding to a `VaList` value. - let alloc_id = self.tcx().reserve_alloc_id(); - // Consume the remaining arguments and store them in a global allocation. let mut varargs = Vec::new(); for (fn_arg, abi) in &mut caller_args { @@ -487,12 +483,8 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { // When the frame is dropped, this ID is used to deallocate the variable arguments list. self.frame_mut().va_list = varargs.clone(); - // A global map that is used to implement `va_arg`. - self.memory.va_list_map.insert(alloc_id, varargs); - - // A VaList is a global allocation, so make sure we get the right root pointer. - // We know this is not an `extern static` so this cannot fail. - let ptr = self.global_root_pointer(Pointer::from(alloc_id)).unwrap(); + // 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); // Store the pointer to the global variable arguments list allocation in the diff --git a/compiler/rustc_const_eval/src/interpret/intrinsics.rs b/compiler/rustc_const_eval/src/interpret/intrinsics.rs index 3c93ae71428c8..6cd732b57479b 100644 --- a/compiler/rustc_const_eval/src/interpret/intrinsics.rs +++ b/compiler/rustc_const_eval/src/interpret/intrinsics.rs @@ -750,28 +750,21 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { let (prov, offset) = src_va_list_ptr.into_raw_parts(); let src_alloc_id = prov.unwrap().get_alloc_id().unwrap(); - let index = offset.bytes_usize(); + let index = offset.bytes(); // Look up arguments without consuming src. - let arguments = self.memory.va_list_map.get(&src_alloc_id).ok_or_else(|| { - err_unsup_format!("va_copy on unknown va_list allocation {:?}", src_alloc_id) - })?; + 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); + }; - // Mint a fresh alloc_id for the destination and clone the argument vector. - let new_alloc_id = tcx.reserve_alloc_id(); - self.memory.va_list_map.insert(new_alloc_id, arguments.clone()); - let tcx_ptr: Pointer = - Pointer::new(new_alloc_id.into(), Size::from_bytes(index)); - let new_ptr: Pointer = self.global_root_pointer(tcx_ptr)?; + // 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. - // VaList is a newtype: its only field is the pointer token. - let x = Scalar::from_pointer(new_ptr, self); - let mplace = self.force_allocation(dest)?; let mut alloc = self.get_place_alloc_mut(&mplace)?.unwrap(); - - alloc.write_ptr_sized(Size::ZERO, x)?; + alloc.write_ptr_sized(Size::ZERO, addr)?; } sym::va_end => { @@ -794,7 +787,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { let (prov, _offset) = va_list_ptr.into_raw_parts(); let alloc_id = prov.unwrap().get_alloc_id().unwrap(); - let Some(_) = self.memory.va_list_map.swap_remove(&alloc_id) else { + let Some(_) = self.remove_va_list_alloc(alloc_id) else { throw_unsup_format!("va_end on unknown va_list allocation {:?}", alloc_id) }; } @@ -818,31 +811,24 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { let (prov, offset) = va_list_ptr.into_raw_parts(); let alloc_id = prov.unwrap().get_alloc_id().unwrap(); - let index = offset.bytes_usize(); + let index = offset.bytes(); - let Some(va_list) = self.memory.va_list_map.swap_remove(&alloc_id) else { + 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) = va_list.get(index).cloned() else { + let Some(src_mplace) = varargs.get(offset.bytes_usize()).cloned() else { throw_ub!(VaArgOutOfBounds) }; - // Mint a fresh alloc_id for the destination and clone the argument vector. - let new_alloc_id = self.tcx.reserve_alloc_id(); - self.memory.va_list_map.insert(new_alloc_id, va_list); - // Update the offset in this `VaList` value so that a subsequent call to `va_arg` // reads the next argument. - let tcx_ptr: Pointer = - Pointer::new(new_alloc_id.into(), Size::from_bytes(index + 1)); - let new_va_list_ptr: Pointer = self.global_root_pointer(tcx_ptr)?; - - let x = Scalar::from_pointer(new_va_list_ptr, self); + 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, x)?; + 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 diff --git a/compiler/rustc_const_eval/src/interpret/memory.rs b/compiler/rustc_const_eval/src/interpret/memory.rs index 4dc14b002c636..146a1c58db316 100644 --- a/compiler/rustc_const_eval/src/interpret/memory.rs +++ b/compiler/rustc_const_eval/src/interpret/memory.rs @@ -128,7 +128,8 @@ pub struct Memory<'tcx, M: Machine<'tcx>> { /// Map for "extra" function pointers. extra_fn_ptr_map: FxIndexMap, - pub(crate) va_list_map: FxIndexMap>>, + /// Map storing variable argument lists. + 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 @@ -236,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>, + index: u64, + ) -> 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() + } + pub fn allocate_ptr( &mut self, size: Size, @@ -927,6 +943,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() @@ -1014,6 +1031,17 @@ 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.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( From 83db12a58bc8f8d70e29b791902b46796a2afab3 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Thu, 22 Jan 2026 11:35:06 +0100 Subject: [PATCH 14/14] remove `GlobalAlloc::VaList` again --- .../rustc_codegen_cranelift/src/constant.rs | 7 ---- compiler/rustc_codegen_gcc/src/common.rs | 4 --- compiler/rustc_codegen_llvm/src/common.rs | 3 -- .../rustc_const_eval/src/interpret/memory.rs | 12 ------- .../rustc_middle/src/mir/interpret/mod.rs | 32 ++----------------- compiler/rustc_middle/src/mir/pretty.rs | 1 - compiler/rustc_middle/src/ty/print/pretty.rs | 1 - compiler/rustc_monomorphize/src/collector.rs | 1 - compiler/rustc_passes/src/reachable.rs | 1 - compiler/rustc_public/src/mir/alloc.rs | 2 -- .../src/unstable/convert/stable/mir.rs | 1 - 11 files changed, 3 insertions(+), 62 deletions(-) diff --git a/compiler/rustc_codegen_cranelift/src/constant.rs b/compiler/rustc_codegen_cranelift/src/constant.rs index 346257d606628..ff8e6744bd32c 100644 --- a/compiler/rustc_codegen_cranelift/src/constant.rs +++ b/compiler/rustc_codegen_cranelift/src/constant.rs @@ -175,9 +175,6 @@ pub(crate) fn codegen_const_value<'tcx>( let local_data_id = fx.module.declare_data_in_func(data_id, fx.bcx.func); fx.bcx.ins().symbol_value(fx.pointer_type, local_data_id) } - GlobalAlloc::VaList => { - bug!("valist allocation should never make it to codegen") - } GlobalAlloc::TypeId { .. } => { return CValue::const_val( fx, @@ -384,7 +381,6 @@ fn define_all_allocs(tcx: TyCtxt<'_>, module: &mut dyn Module, cx: &mut Constant GlobalAlloc::Function { .. } | GlobalAlloc::Static(_) | GlobalAlloc::TypeId { .. } - | GlobalAlloc::VaList | GlobalAlloc::VTable(..) => { unreachable!() } @@ -498,9 +494,6 @@ fn define_all_allocs(tcx: TyCtxt<'_>, module: &mut dyn Module, cx: &mut Constant .principal() .map(|principal| tcx.instantiate_bound_regions_with_erased(principal)), ), - GlobalAlloc::VaList => { - bug!("valist allocation should never make it to codegen") - } GlobalAlloc::TypeId { .. } => { // Nothing to do, the bytes/offset of this pointer have already been written together with all other bytes, // so we just need to drop this provenance. diff --git a/compiler/rustc_codegen_gcc/src/common.rs b/compiler/rustc_codegen_gcc/src/common.rs index b941eade5eb65..7c2969e587186 100644 --- a/compiler/rustc_codegen_gcc/src/common.rs +++ b/compiler/rustc_codegen_gcc/src/common.rs @@ -4,7 +4,6 @@ use rustc_abi::{self as abi, HasDataLayout}; use rustc_codegen_ssa::traits::{ BaseTypeCodegenMethods, ConstCodegenMethods, MiscCodegenMethods, StaticCodegenMethods, }; -use rustc_middle::bug; use rustc_middle::mir::Mutability; use rustc_middle::mir::interpret::{ConstAllocation, GlobalAlloc, PointerArithmetic, Scalar}; use rustc_middle::ty::layout::LayoutOf; @@ -282,9 +281,6 @@ impl<'gcc, 'tcx> ConstCodegenMethods for CodegenCx<'gcc, 'tcx> { let init = self.const_data_from_alloc(alloc); self.static_addr_of(init, alloc.inner().align, None) } - GlobalAlloc::VaList => { - bug!("valist allocation should never make it to codegen") - } GlobalAlloc::TypeId { .. } => { let val = self.const_usize(offset.bytes()); // This is still a variable of pointer type, even though we only use the provenance diff --git a/compiler/rustc_codegen_llvm/src/common.rs b/compiler/rustc_codegen_llvm/src/common.rs index ebf2d3abe964b..b0cf9925019d2 100644 --- a/compiler/rustc_codegen_llvm/src/common.rs +++ b/compiler/rustc_codegen_llvm/src/common.rs @@ -325,9 +325,6 @@ impl<'ll, 'tcx> ConstCodegenMethods for CodegenCx<'ll, 'tcx> { let init = const_alloc_to_llvm(self, alloc.inner(), /*static*/ false); self.static_addr_of_impl(init, alloc.inner().align, None) } - GlobalAlloc::VaList => { - bug!("valist allocation should never make it to codegen") - } GlobalAlloc::Static(def_id) => { assert!(self.tcx.is_static(def_id)); assert!(!self.tcx.is_thread_local_static(def_id)); diff --git a/compiler/rustc_const_eval/src/interpret/memory.rs b/compiler/rustc_const_eval/src/interpret/memory.rs index 146a1c58db316..17e4719dfa446 100644 --- a/compiler/rustc_const_eval/src/interpret/memory.rs +++ b/compiler/rustc_const_eval/src/interpret/memory.rs @@ -417,13 +417,6 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { kind = "vtable", ) } - Some(GlobalAlloc::VaList) => { - err_ub_custom!( - fluent::const_eval_invalid_dealloc, - alloc_id = alloc_id, - kind = "valist", - ) - } Some(GlobalAlloc::TypeId { .. }) => { err_ub_custom!( fluent::const_eval_invalid_dealloc, @@ -700,7 +693,6 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { } Some(GlobalAlloc::Function { .. }) => throw_ub!(DerefFunctionPointer(id)), Some(GlobalAlloc::VTable(..)) => throw_ub!(DerefVTablePointer(id)), - Some(GlobalAlloc::VaList) => throw_ub!(DerefVaListPointer(id)), Some(GlobalAlloc::TypeId { .. }) => throw_ub!(DerefTypeIdPointer(id)), None => throw_ub!(PointerUseAfterFree(id, CheckInAllocMsg::MemoryAccess)), Some(GlobalAlloc::Static(def_id)) => { @@ -992,7 +984,6 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { GlobalAlloc::Static { .. } | GlobalAlloc::Memory { .. } => AllocKind::LiveData, GlobalAlloc::Function { .. } => bug!("We already checked function pointers above"), GlobalAlloc::VTable { .. } => AllocKind::VTable, - GlobalAlloc::VaList { .. } => AllocKind::VaList, GlobalAlloc::TypeId { .. } => AllocKind::TypeId, }; return AllocInfo::new(size, align, kind, mutbl); @@ -1316,9 +1307,6 @@ impl<'a, 'tcx, M: Machine<'tcx>> std::fmt::Debug for DumpAllocs<'a, 'tcx, M> { Some(GlobalAlloc::VTable(ty, dyn_ty)) => { write!(fmt, " (vtable: impl {dyn_ty} for {ty})")?; } - Some(GlobalAlloc::VaList) => { - write!(fmt, " (valist)")?; - } Some(GlobalAlloc::TypeId { ty }) => { write!(fmt, " (typeid for {ty})")?; } diff --git a/compiler/rustc_middle/src/mir/interpret/mod.rs b/compiler/rustc_middle/src/mir/interpret/mod.rs index 5b037a67c81aa..e4ce39730ee55 100644 --- a/compiler/rustc_middle/src/mir/interpret/mod.rs +++ b/compiler/rustc_middle/src/mir/interpret/mod.rs @@ -102,7 +102,6 @@ enum AllocDiscriminant { Alloc, Fn, VTable, - VaList, Static, Type, } @@ -129,9 +128,6 @@ pub fn specialized_encode_alloc_id<'tcx, E: TyEncoder<'tcx>>( ty.encode(encoder); poly_trait_ref.encode(encoder); } - GlobalAlloc::VaList => { - AllocDiscriminant::VaList.encode(encoder); - } GlobalAlloc::TypeId { ty } => { trace!("encoding {alloc_id:?} with {ty:#?}"); AllocDiscriminant::Type.encode(encoder); @@ -238,7 +234,6 @@ impl<'s> AllocDecodingSession<'s> { trace!("decoded vtable alloc instance: {ty:?}, {poly_trait_ref:?}"); decoder.interner().reserve_and_set_vtable_alloc(ty, poly_trait_ref, CTFE_ALLOC_SALT) } - AllocDiscriminant::VaList => decoder.interner().reserve_and_set_va_list_alloc(), AllocDiscriminant::Type => { trace!("creating typeid alloc ID"); let ty = Decodable::decode(decoder); @@ -270,8 +265,6 @@ pub enum GlobalAlloc<'tcx> { /// const-eval and Miri can detect UB due to invalid transmutes of /// `dyn Trait` types. VTable(Ty<'tcx>, &'tcx ty::List>), - /// This alloc ID points to a variable argument list. - VaList, /// The alloc ID points to a "lazy" static variable that did not get computed (yet). /// This is also used to break the cycle in recursive statics. Static(DefId), @@ -321,8 +314,7 @@ impl<'tcx> GlobalAlloc<'tcx> { GlobalAlloc::TypeId { .. } | GlobalAlloc::Static(..) | GlobalAlloc::Memory(..) - | GlobalAlloc::VTable(..) - | GlobalAlloc::VaList => AddressSpace::ZERO, + | GlobalAlloc::VTable(..) => AddressSpace::ZERO, } } @@ -358,10 +350,7 @@ impl<'tcx> GlobalAlloc<'tcx> { } } GlobalAlloc::Memory(alloc) => alloc.inner().mutability, - GlobalAlloc::TypeId { .. } - | GlobalAlloc::Function { .. } - | GlobalAlloc::VTable(..) - | GlobalAlloc::VaList => { + GlobalAlloc::TypeId { .. } | GlobalAlloc::Function { .. } | GlobalAlloc::VTable(..) => { // These are immutable. Mutability::Not } @@ -419,7 +408,7 @@ impl<'tcx> GlobalAlloc<'tcx> { (Size::ZERO, tcx.data_layout.pointer_align().abi) } // Fake allocation, there's nothing to access here. - GlobalAlloc::VaList | GlobalAlloc::TypeId { .. } => (Size::ZERO, Align::ONE), + GlobalAlloc::TypeId { .. } => (Size::ZERO, Align::ONE), } } } @@ -525,13 +514,6 @@ impl<'tcx> TyCtxt<'tcx> { self.reserve_and_set_dedup(GlobalAlloc::VTable(ty, dyn_ty), salt) } - /// Generates an `AllocId` for a va_list. Does not get deduplicated. - pub fn reserve_and_set_va_list_alloc(self) -> AllocId { - let id = self.reserve_alloc_id(); - self.set_alloc_id_va_list(id); - id - } - /// Generates an [AllocId] for a [core::any::TypeId]. Will get deduplicated. pub fn reserve_and_set_type_id_alloc(self, ty: Ty<'tcx>) -> AllocId { self.reserve_and_set_dedup(GlobalAlloc::TypeId { ty }, 0) @@ -579,14 +561,6 @@ impl<'tcx> TyCtxt<'tcx> { } } - /// Freezes an `AllocId` created with `reserve` by pointing it at an `Allocation`. Trying to - /// call this function twice, even with the same `Allocation` will ICE the compiler. - pub fn set_alloc_id_va_list(self, id: AllocId) { - if let Some(old) = self.alloc_map.to_alloc.insert(id, GlobalAlloc::VaList) { - bug!("tried to set allocation ID {id:?}, but it was already existing as {old:#?}"); - } - } - /// Freezes an `AllocId` created with `reserve` by pointing it at a static item. Trying to /// call this function twice, even with the same `DefId` will ICE the compiler. pub fn set_nested_alloc_id_static(self, id: AllocId, def_id: LocalDefId) { diff --git a/compiler/rustc_middle/src/mir/pretty.rs b/compiler/rustc_middle/src/mir/pretty.rs index 23da2288fc250..ded02595563c9 100644 --- a/compiler/rustc_middle/src/mir/pretty.rs +++ b/compiler/rustc_middle/src/mir/pretty.rs @@ -1570,7 +1570,6 @@ pub fn write_allocations<'tcx>( Some(GlobalAlloc::VTable(ty, dyn_ty)) => { write!(w, " (vtable: impl {dyn_ty} for {ty})")? } - Some(GlobalAlloc::VaList) => write!(w, "(valist)")?, Some(GlobalAlloc::TypeId { ty }) => write!(w, " (typeid for {ty})")?, Some(GlobalAlloc::Static(did)) if !tcx.is_foreign_item(did) => { write!(w, " (static: {}", tcx.def_path_str(did))?; diff --git a/compiler/rustc_middle/src/ty/print/pretty.rs b/compiler/rustc_middle/src/ty/print/pretty.rs index 04b0eafc0023f..fd0a5ca309a4c 100644 --- a/compiler/rustc_middle/src/ty/print/pretty.rs +++ b/compiler/rustc_middle/src/ty/print/pretty.rs @@ -1748,7 +1748,6 @@ pub trait PrettyPrinter<'tcx>: Printer<'tcx> + fmt::Write { } Some(GlobalAlloc::Function { .. }) => write!(self, "")?, Some(GlobalAlloc::VTable(..)) => write!(self, "")?, - Some(GlobalAlloc::VaList) => write!(self, "")?, Some(GlobalAlloc::TypeId { .. }) => write!(self, "")?, None => write!(self, "")?, } diff --git a/compiler/rustc_monomorphize/src/collector.rs b/compiler/rustc_monomorphize/src/collector.rs index 2151f0c4c940f..ce17046969184 100644 --- a/compiler/rustc_monomorphize/src/collector.rs +++ b/compiler/rustc_monomorphize/src/collector.rs @@ -1296,7 +1296,6 @@ fn collect_alloc<'tcx>(tcx: TyCtxt<'tcx>, alloc_id: AllocId, output: &mut MonoIt )); collect_alloc(tcx, alloc_id, output) } - GlobalAlloc::VaList => {} GlobalAlloc::TypeId { .. } => {} } } diff --git a/compiler/rustc_passes/src/reachable.rs b/compiler/rustc_passes/src/reachable.rs index 8ab028fe5c530..d9565e2dae0ef 100644 --- a/compiler/rustc_passes/src/reachable.rs +++ b/compiler/rustc_passes/src/reachable.rs @@ -334,7 +334,6 @@ impl<'tcx> ReachableContext<'tcx> { self.visit(args); } } - GlobalAlloc::VaList => {} GlobalAlloc::TypeId { ty, .. } => self.visit(ty), GlobalAlloc::Memory(alloc) => self.propagate_from_alloc(alloc), } diff --git a/compiler/rustc_public/src/mir/alloc.rs b/compiler/rustc_public/src/mir/alloc.rs index 58cc9f8b1181a..b267e3612d808 100644 --- a/compiler/rustc_public/src/mir/alloc.rs +++ b/compiler/rustc_public/src/mir/alloc.rs @@ -18,8 +18,6 @@ pub enum GlobalAlloc { /// This alloc ID points to a symbolic (not-reified) vtable. /// The `None` trait ref is used to represent auto traits. VTable(Ty, Option>), - /// This alloc ID points to a variable argument list (used with c-variadic functions). - VaList, /// The alloc ID points to a "lazy" static variable that did not get computed (yet). /// This is also used to break the cycle in recursive statics. Static(StaticDef), diff --git a/compiler/rustc_public/src/unstable/convert/stable/mir.rs b/compiler/rustc_public/src/unstable/convert/stable/mir.rs index 26c440524dc6b..a77808cfb275d 100644 --- a/compiler/rustc_public/src/unstable/convert/stable/mir.rs +++ b/compiler/rustc_public/src/unstable/convert/stable/mir.rs @@ -846,7 +846,6 @@ impl<'tcx> Stable<'tcx> for mir::interpret::GlobalAlloc<'tcx> { // FIXME: Should we record the whole vtable? GlobalAlloc::VTable(ty.stable(tables, cx), dyn_ty.principal().stable(tables, cx)) } - mir::interpret::GlobalAlloc::VaList => GlobalAlloc::VaList, mir::interpret::GlobalAlloc::Static(def) => { GlobalAlloc::Static(tables.static_def(*def)) }