From 61e2fb39aff6707578787cdb4e5b39962793db15 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Fri, 2 Jan 2026 15:35:21 +0100 Subject: [PATCH 01/40] 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 | 509 ++++++++++++++++++ compiler/rustc_const_eval/src/errors.rs | 2 + .../rustc_const_eval/src/interpret/memory.rs | 21 + .../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, 584 insertions(+), 4 deletions(-) create mode 100644 compiler/rustc_const_eval/messages.ftl 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 f2261ab79340f..c835e8e8dda30 100644 --- a/compiler/rustc_codegen_llvm/src/common.rs +++ b/compiler/rustc_codegen_llvm/src/common.rs @@ -329,6 +329,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 new file mode 100644 index 0000000000000..559f2c04759ce --- /dev/null +++ b/compiler/rustc_const_eval/messages.ftl @@ -0,0 +1,509 @@ +const_eval_address_space_full = + there are no more free addresses in the address space + +const_eval_alignment_check_failed = + {$msg -> + [AccessedPtr] accessing memory + *[other] accessing memory based on pointer + } with alignment {$has}, but alignment {$required} is required + +const_eval_already_reported = + an error has already been reported elsewhere (this should not usually be printed) +const_eval_assume_false = + `assume` called with `false` + +const_eval_bad_pointer_op = {$operation -> + [MemoryAccess] memory access failed + [InboundsPointerArithmetic] in-bounds pointer arithmetic failed + *[Dereferenceable] pointer not dereferenceable +} +const_eval_bad_pointer_op_attempting = {const_eval_bad_pointer_op}: {$operation -> + [MemoryAccess] attempting to access {$inbounds_size -> + [1] 1 byte + *[x] {$inbounds_size} bytes + } + [InboundsPointerArithmetic] attempting to offset pointer by {$inbounds_size -> + [1] 1 byte + *[x] {$inbounds_size} bytes + } + *[Dereferenceable] pointer must {$inbounds_size -> + [0] point to some allocation + [1] be dereferenceable for 1 byte + *[x] be dereferenceable for {$inbounds_size} bytes + } + } + +const_eval_bounds_check_failed = + indexing out of bounds: the len is {$len} but the index is {$index} +const_eval_call_nonzero_intrinsic = + `{$name}` called on 0 + +const_eval_closure_call = + closures need an RFC before allowed to be called in {const_eval_const_context}s +const_eval_closure_fndef_not_const = + function defined here, but it is not `const` + +const_eval_consider_dereferencing = + consider dereferencing here + +const_eval_const_accesses_mut_global = + constant accesses mutable global memory + +const_eval_const_context = {$kind -> + [const] constant + [static] static + [const_fn] constant function + *[other] {""} +} + +const_eval_const_heap_ptr_in_final = encountered `const_allocate` pointer in final value that was not made global + .note = use `const_make_global` to turn allocated pointers into immutable globals before returning + +const_eval_const_make_global_ptr_already_made_global = attempting to call `const_make_global` twice on the same allocation {$alloc} + +const_eval_const_make_global_ptr_is_non_heap = pointer passed to `const_make_global` does not point to a heap allocation: {$ptr} + +const_eval_const_make_global_with_dangling_ptr = pointer passed to `const_make_global` is dangling: {$ptr} + +const_eval_const_make_global_with_offset = making {$ptr} global which does not point to the beginning of an object + +const_eval_copy_nonoverlapping_overlapping = + `copy_nonoverlapping` called on overlapping ranges + +const_eval_dangling_int_pointer = + {const_eval_bad_pointer_op_attempting}, but got {$pointer} which is a dangling pointer (it has no provenance) +const_eval_dangling_null_pointer = + {const_eval_bad_pointer_op_attempting}, but got null pointer + +const_eval_dangling_ptr_in_final = encountered dangling pointer in final value of {const_eval_intern_kind} +const_eval_dead_local = + accessing a dead local variable +const_eval_dealloc_immutable = + deallocating immutable allocation {$alloc} + +const_eval_dealloc_incorrect_layout = + incorrect layout on deallocation: {$alloc} has size {$size} and alignment {$align}, but gave size {$size_found} and alignment {$align_found} + +const_eval_dealloc_kind_mismatch = + deallocating {$alloc}, which is {$alloc_kind} memory, using {$kind} deallocation operation + +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 = + dividing by zero +const_eval_division_overflow = + overflow in signed division (dividing MIN by -1) + +const_eval_dyn_call_not_a_method = + `dyn` call trying to call something that is not a method + +const_eval_error = evaluation of `{$instance}` failed {$num_frames -> + [0] here + *[other] inside this call +} + +const_eval_exact_div_has_remainder = + exact_div: {$a} cannot be divided by {$b} without remainder + +const_eval_extern_static = + cannot access extern static `{$did}` +const_eval_extern_type_field = `extern type` field does not have a known offset + +const_eval_fn_ptr_call = + function pointers need an RFC before allowed to be called in {const_eval_const_context}s +const_eval_frame_note = {$times -> + [0] {const_eval_frame_note_inner} + *[other] [... {$times} additional calls {const_eval_frame_note_inner} ...] +} + +const_eval_frame_note_inner = inside {$where_ -> + [closure] closure + [instance] `{$instance}` + *[other] {""} +} + +const_eval_frame_note_last = the failure occurred here + +const_eval_incompatible_arg_types = + calling a function whose parameter #{$arg_idx} has type {$callee_ty} passing argument of type {$caller_ty} + +const_eval_incompatible_calling_conventions = + calling a function with calling convention "{$callee_conv}" using calling convention "{$caller_conv}" + +const_eval_incompatible_return_types = + calling a function with return type {$callee_ty} passing return place of type {$caller_ty} + +const_eval_interior_mutable_borrow_escaping = + interior mutable shared borrows of temporaries that have their lifetime extended until the end of the program are not allowed + .label = this borrow of an interior mutable value refers to such a temporary + .note = temporaries in constants and statics can have their lifetime extended until the end of the program + .note2 = to avoid accidentally creating global mutable state, such temporaries must be immutable + .help = if you really want global mutable state, try replacing the temporary by an interior mutable `static` or a `static mut` + +const_eval_intern_kind = {$kind -> + [static] static + [static_mut] mutable static + [const] constant + [promoted] promoted + *[other] {""} +} + +const_eval_interrupted = compilation was interrupted + +const_eval_invalid_align_details = + invalid align passed to `{$name}`: {$align} is {$err_kind -> + [not_power_of_two] not a power of 2 + [too_large] too large + *[other] {""} + } + +const_eval_invalid_bool = + interpreting an invalid 8-bit value as a bool: 0x{$value} +const_eval_invalid_char = + interpreting an invalid 32-bit value as a char: 0x{$value} +const_eval_invalid_dealloc = + deallocating {$alloc_id}, which is {$kind -> + [fn] a function + [vtable] a vtable + [static_mem] static memory + *[other] {""} + } + +const_eval_invalid_function_pointer = + using {$pointer} as function pointer but it does not point to a function +const_eval_invalid_meta = + invalid metadata in wide pointer: total size is bigger than largest supported object +const_eval_invalid_meta_slice = + invalid metadata in wide pointer: slice is bigger than largest supported object + +const_eval_invalid_niched_enum_variant_written = + trying to set discriminant of a {$ty} to the niched variant, but the value does not match + +const_eval_invalid_str = + this string is not valid UTF-8: {$err} +const_eval_invalid_tag = + enum value has invalid tag: {$tag} +const_eval_invalid_transmute = + transmuting from {$src_bytes}-byte type to {$dest_bytes}-byte type: `{$src}` -> `{$dest}` + +const_eval_invalid_uninit_bytes = + reading memory at {$alloc}{$access}, but memory is uninitialized at {$uninit}, and this operation requires initialized memory +const_eval_invalid_uninit_bytes_unknown = + using uninitialized data, but this operation requires initialized memory + +const_eval_invalid_vtable_pointer = + using {$pointer} as vtable pointer but it does not point to a vtable + +const_eval_invalid_vtable_trait = + using vtable for `{$vtable_dyn_type}` but `{$expected_dyn_type}` was expected + +const_eval_lazy_lock = + consider wrapping this expression in `std::sync::LazyLock::new(|| ...)` + +const_eval_live_drop = + destructor of `{$dropped_ty}` cannot be evaluated at compile-time + .label = the destructor for this type cannot be evaluated in {const_eval_const_context}s + .dropped_at_label = value is dropped here + +const_eval_long_running = + constant evaluation is taking a long time + .note = this lint makes sure the compiler doesn't get stuck due to infinite loops in const eval. + If your compilation actually takes a long time, you can safely allow the lint. + .label = the const evaluator is currently interpreting this expression + .help = the constant being evaluated + +const_eval_memory_exhausted = + tried to allocate more memory than available to compiler + +const_eval_modified_global = + modifying a static's initial value from another static's initializer + +const_eval_mutable_borrow_escaping = + mutable borrows of temporaries that have their lifetime extended until the end of the program are not allowed + .label = this mutable borrow refers to such a temporary + .note = temporaries in constants and statics can have their lifetime extended until the end of the program + .note2 = to avoid accidentally creating global mutable state, such temporaries must be immutable + .help = if you really want global mutable state, try replacing the temporary by an interior mutable `static` or a `static mut` + +const_eval_mutable_ptr_in_final = encountered mutable pointer in final value of {const_eval_intern_kind} + +const_eval_nested_static_in_thread_local = #[thread_local] does not support implicit nested statics, please create explicit static items and refer to them instead + +const_eval_non_const_await = + cannot convert `{$ty}` into a future in {const_eval_const_context}s + +const_eval_non_const_closure = + cannot call {$non_or_conditionally}-const closure in {const_eval_const_context}s + +const_eval_non_const_deref_coercion = + cannot perform {$non_or_conditionally}-const deref coercion on `{$ty}` in {const_eval_const_context}s + .note = attempting to deref into `{$target_ty}` + .target_note = deref defined here + +const_eval_non_const_fmt_macro_call = + cannot call {$non_or_conditionally}-const formatting macro in {const_eval_const_context}s + +const_eval_non_const_fn_call = + cannot call {$non_or_conditionally}-const {$def_descr} `{$def_path_str}` in {const_eval_const_context}s + +const_eval_non_const_for_loop_into_iter = + cannot use `for` loop on `{$ty}` in {const_eval_const_context}s + +const_eval_non_const_impl = + impl defined here, but it is not `const` + +const_eval_non_const_intrinsic = + cannot call non-const intrinsic `{$name}` in {const_eval_const_context}s + +const_eval_non_const_match_eq = cannot match on `{$ty}` in {const_eval_const_context}s + .note = `{$ty}` cannot be compared in compile-time, and therefore cannot be used in `match`es + +const_eval_non_const_operator = + cannot call {$non_or_conditionally}-const operator in {const_eval_const_context}s + +const_eval_non_const_question_branch = + `?` is not allowed on `{$ty}` in {const_eval_const_context}s +const_eval_non_const_question_from_residual = + `?` is not allowed on `{$ty}` in {const_eval_const_context}s + +const_eval_non_const_try_block_from_output = + `try` block cannot convert `{$ty}` to the result in {const_eval_const_context}s + +const_eval_not_enough_caller_args = + calling a function with fewer arguments than it requires + +const_eval_offset_from_different_allocations = + `{$name}` called on two different pointers that are not both derived from the same allocation +const_eval_offset_from_out_of_bounds = + `{$name}` called on two different pointers where the memory range between them is not in-bounds of an allocation +const_eval_offset_from_overflow = + `{$name}` called when first pointer is too far ahead of second +const_eval_offset_from_underflow = + `{$name}` called when first pointer is too far before second +const_eval_offset_from_unsigned_overflow = + `ptr_offset_from_unsigned` called when first pointer has smaller {$is_addr -> + [true] address + *[false] offset + } than second: {$a_offset} < {$b_offset} + +const_eval_overflow_arith = + arithmetic overflow in `{$intrinsic}` +const_eval_overflow_shift = + overflowing shift by {$shift_amount} in `{$intrinsic}` + +const_eval_panic = evaluation panicked: {$msg} + +const_eval_panic_non_str = argument to `panic!()` in a const context must have type `&str` + +const_eval_partial_pointer_in_final = encountered partial pointer in final value of {const_eval_intern_kind} + .note = while pointers can be broken apart into individual bytes during const-evaluation, only complete pointers (with all their bytes in the right order) are supported in the final value + +const_eval_partial_pointer_read = + unable to read parts of a pointer from memory at {$ptr} +const_eval_pointer_arithmetic_overflow = + overflowing pointer arithmetic: the total offset in bytes does not fit in an `isize` + +const_eval_pointer_out_of_bounds = + {const_eval_bad_pointer_op_attempting}, but got {$pointer} which {$ptr_offset_is_neg -> + [true] points to before the beginning of the allocation + *[false] {$inbounds_size_is_neg -> + [false] {$alloc_size_minus_ptr_offset -> + [0] is at or beyond the end of the allocation of size {$alloc_size -> + [1] 1 byte + *[x] {$alloc_size} bytes + } + [1] is only 1 byte from the end of the allocation + *[x] is only {$alloc_size_minus_ptr_offset} bytes from the end of the allocation + } + *[true] {$ptr_offset_abs -> + [0] is at the beginning of the allocation + *[other] is only {$ptr_offset_abs} bytes from the beginning of the allocation + } + } + } + +const_eval_pointer_use_after_free = + {const_eval_bad_pointer_op}: {$alloc_id} has been freed, so this pointer is dangling +const_eval_ptr_as_bytes_1 = + this code performed an operation that depends on the underlying bytes representing a pointer +const_eval_ptr_as_bytes_2 = + the absolute address of a pointer is not known at compile-time, so such operations are not supported + +const_eval_range = in the range {$lo}..={$hi} +const_eval_range_lower = greater or equal to {$lo} +const_eval_range_singular = equal to {$lo} +const_eval_range_upper = less or equal to {$hi} +const_eval_range_wrapping = less or equal to {$hi}, or greater or equal to {$lo} +const_eval_raw_bytes = the raw bytes of the constant (size: {$size}, align: {$align}) {"{"}{$bytes}{"}"} + +const_eval_raw_ptr_comparison = + pointers cannot be reliably compared during const eval + .note = see issue #53020 for more information + +const_eval_raw_ptr_to_int = + pointers cannot be cast to integers during const eval + .note = at compile-time, pointers do not have an integer value + .note2 = avoiding this restriction via `transmute`, `union`, or raw pointers leads to compile-time undefined behavior + +const_eval_read_pointer_as_int = + unable to turn pointer into integer +const_eval_realloc_or_alloc_with_offset = + {$kind -> + [dealloc] deallocating + [realloc] reallocating + *[other] {""} + } {$ptr} which does not point to the beginning of an object + +const_eval_recursive_static = encountered static that tried to access itself during initialization + +const_eval_remainder_by_zero = + calculating the remainder with a divisor of zero +const_eval_remainder_overflow = + overflow in signed remainder (dividing MIN by -1) +const_eval_scalar_size_mismatch = + scalar size mismatch: expected {$target_size} bytes but got {$data_size} bytes instead +const_eval_size_overflow = + overflow computing total size of `{$name}` + +const_eval_stack_frame_limit_reached = + reached the configured maximum number of stack frames + +const_eval_thread_local_access = + thread-local statics cannot be accessed at compile-time + +const_eval_thread_local_static = + cannot access thread local static `{$did}` +const_eval_too_generic = + encountered overly generic constant +const_eval_too_many_caller_args = + calling a function with more arguments than it expected + +const_eval_unallowed_fn_pointer_call = function pointer calls are not allowed in {const_eval_const_context}s + +const_eval_unallowed_heap_allocations = + allocations are not allowed in {const_eval_const_context}s + .label = allocation not allowed in {const_eval_const_context}s + .teach_note = + The runtime heap is not yet available at compile-time, so no runtime heap allocations can be created. + +const_eval_unallowed_inline_asm = + inline assembly is not allowed in {const_eval_const_context}s + +const_eval_unallowed_op_in_const_context = + {$msg} + +const_eval_uninhabited_enum_variant_read = + read discriminant of an uninhabited enum variant +const_eval_uninhabited_enum_variant_written = + writing discriminant of an uninhabited enum variant + +const_eval_unmarked_const_item_exposed = `{$def_path}` cannot be (indirectly) exposed to stable + .help = either mark the callee as `#[rustc_const_stable_indirect]`, or the caller as `#[rustc_const_unstable]` +const_eval_unmarked_intrinsic_exposed = intrinsic `{$def_path}` cannot be (indirectly) exposed to stable + .help = mark the caller as `#[rustc_const_unstable]`, or mark the intrinsic `#[rustc_intrinsic_const_stable_indirect]` (but this requires team approval) + +const_eval_unreachable = entering unreachable code +const_eval_unreachable_unwind = + unwinding past a stack frame that does not allow unwinding + +const_eval_unsized_local = unsized locals are not supported +const_eval_unstable_const_fn = `{$def_path}` is not yet stable as a const fn +const_eval_unstable_const_trait = `{$def_path}` is not yet stable as a const trait +const_eval_unstable_in_stable_exposed = + const function that might be (indirectly) exposed to stable cannot use `#[feature({$gate})]` + .is_function_call = mark the callee as `#[rustc_const_stable_indirect]` if it does not itself require any unstable features + .unstable_sugg = if the {$is_function_call2 -> + [true] caller + *[false] function + } is not (yet) meant to be exposed to stable const contexts, add `#[rustc_const_unstable]` + +const_eval_unstable_intrinsic = `{$name}` is not yet stable as a const intrinsic +const_eval_unstable_intrinsic_suggestion = add `#![feature({$feature})]` to the crate attributes to enable + +const_eval_unterminated_c_string = + reading a null-terminated string starting at {$pointer} with no null found before end of allocation + +const_eval_unwind_past_top = + unwinding past the topmost frame of the stack + +## 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} + +const_eval_validation_dangling_box_no_provenance = {$front_matter}: encountered a dangling box ({$pointer} has no provenance) +const_eval_validation_dangling_box_out_of_bounds = {$front_matter}: encountered a dangling box (going beyond the bounds of its allocation) +const_eval_validation_dangling_box_use_after_free = {$front_matter}: encountered a dangling box (use-after-free) +const_eval_validation_dangling_ref_no_provenance = {$front_matter}: encountered a dangling reference ({$pointer} has no provenance) +const_eval_validation_dangling_ref_out_of_bounds = {$front_matter}: encountered a dangling reference (going beyond the bounds of its allocation) +const_eval_validation_dangling_ref_use_after_free = {$front_matter}: encountered a dangling reference (use-after-free) + +const_eval_validation_expected_bool = expected a boolean +const_eval_validation_expected_box = expected a box +const_eval_validation_expected_char = expected a unicode scalar value +const_eval_validation_expected_enum_tag = expected a valid enum tag +const_eval_validation_expected_float = expected a floating point number +const_eval_validation_expected_fn_ptr = expected a function pointer +const_eval_validation_expected_init_scalar = expected initialized scalar value +const_eval_validation_expected_int = expected an integer +const_eval_validation_expected_raw_ptr = expected a raw pointer +const_eval_validation_expected_ref = expected a reference +const_eval_validation_expected_str = expected a string + +const_eval_validation_failure = + it is undefined behavior to use this value + +const_eval_validation_failure_note = + the rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rustc repository if you believe it should not be considered undefined behavior. + +const_eval_validation_front_matter_invalid_value = constructing invalid value +const_eval_validation_front_matter_invalid_value_with_path = constructing invalid value at {$path} + +const_eval_validation_invalid_bool = {$front_matter}: encountered {$value}, but expected a boolean +const_eval_validation_invalid_box_meta = {$front_matter}: encountered invalid box metadata: total size is bigger than largest supported object +const_eval_validation_invalid_box_slice_meta = {$front_matter}: encountered invalid box metadata: slice is bigger than largest supported object +const_eval_validation_invalid_char = {$front_matter}: encountered {$value}, but expected a valid unicode scalar value (in `0..=0x10FFFF` but not in `0xD800..=0xDFFF`) + +const_eval_validation_invalid_enum_tag = {$front_matter}: encountered {$value}, but expected a valid enum tag +const_eval_validation_invalid_fn_ptr = {$front_matter}: encountered {$value}, but expected a function pointer +const_eval_validation_invalid_ref_meta = {$front_matter}: encountered invalid reference metadata: total size is bigger than largest supported object +const_eval_validation_invalid_ref_slice_meta = {$front_matter}: encountered invalid reference metadata: slice is bigger than largest supported object +const_eval_validation_invalid_vtable_ptr = {$front_matter}: encountered {$value}, but expected a vtable pointer +const_eval_validation_invalid_vtable_trait = {$front_matter}: wrong trait in wide pointer vtable: expected `{$expected_dyn_type}`, but encountered `{$vtable_dyn_type}` +const_eval_validation_mutable_ref_to_immutable = {$front_matter}: encountered mutable reference or box pointing to read-only memory +const_eval_validation_never_val = {$front_matter}: encountered a value of the never type `!` +const_eval_validation_nonnull_ptr_out_of_range = {$front_matter}: encountered a maybe-null pointer, but expected something that is definitely non-zero +const_eval_validation_null_box = {$front_matter}: encountered a {$maybe -> + [true] maybe-null + *[false] null + } box +const_eval_validation_null_fn_ptr = {$front_matter}: encountered a {$maybe -> + [true] maybe-null + *[false] null + } function pointer +const_eval_validation_null_ref = {$front_matter}: encountered a {$maybe -> + [true] maybe-null + *[false] null + } reference +const_eval_validation_out_of_range = {$front_matter}: encountered {$value}, but expected something {$in_range} +const_eval_validation_partial_pointer = {$front_matter}: encountered a partial pointer or a mix of pointers +const_eval_validation_pointer_as_int = {$front_matter}: encountered a pointer, but {$expected} +const_eval_validation_ptr_out_of_range = {$front_matter}: encountered a pointer with unknown absolute address, but expected something that is definitely {$in_range} +const_eval_validation_ref_to_uninhabited = {$front_matter}: encountered a reference pointing to uninhabited type {$ty} +const_eval_validation_unaligned_box = {$front_matter}: encountered an unaligned box (required {$required_bytes} byte alignment but found {$found_bytes}) +const_eval_validation_unaligned_ref = {$front_matter}: encountered an unaligned reference (required {$required_bytes} byte alignment but found {$found_bytes}) +const_eval_validation_uninhabited_enum_variant = {$front_matter}: encountered an uninhabited enum variant +const_eval_validation_uninhabited_val = {$front_matter}: encountered a value of uninhabited type `{$ty}` +const_eval_validation_uninit = {$front_matter}: encountered uninitialized memory, but {$expected} +const_eval_validation_unsafe_cell = {$front_matter}: encountered `UnsafeCell` in read-only memory + +const_eval_write_through_immutable_pointer = + writing through a pointer that was derived from a shared (immutable) reference + +const_eval_write_to_read_only = + writing to {$allocation} which is read-only diff --git a/compiler/rustc_const_eval/src/errors.rs b/compiler/rustc_const_eval/src/errors.rs index 3b49dbd907c8f..928f3836fe159 100644 --- a/compiler/rustc_const_eval/src/errors.rs +++ b/compiler/rustc_const_eval/src/errors.rs @@ -749,6 +749,7 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> { WriteToReadOnly(_) => inline_fluent!("writing to {$allocation} which is read-only"), DerefFunctionPointer(_) => inline_fluent!("accessing {$allocation} which contains a function"), DerefVTablePointer(_) => inline_fluent!("accessing {$allocation} which contains a vtable"), + DerefVaListPointer(_) => inline_fluent!("accessing {$allocation} which contains a variable argument list"), DerefTypeIdPointer(_) => inline_fluent!("accessing {$allocation} which contains a `TypeId`"), InvalidBool(_) => inline_fluent!("interpreting an invalid 8-bit value as a bool: 0x{$value}"), InvalidChar(_) => inline_fluent!("interpreting an invalid 32-bit value as a char: 0x{$value}"), @@ -866,6 +867,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 28dae2ef3b8b7..203c7634d8dd0 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. @@ -420,6 +422,20 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { kind = "vtable", ) } + Some(GlobalAlloc::VaList) => { + err_ub_custom!( + inline_fluent!( + "deallocating {$alloc_id}, which is {$kind -> + [fn] a function + [vtable] a vtable + [static_mem] static memory + *[other] {\"\"} +}" + ), + alloc_id = alloc_id, + kind = "valist", + ) + } Some(GlobalAlloc::TypeId { .. }) => { err_ub_custom!( inline_fluent!( @@ -717,6 +733,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)) => { @@ -1007,6 +1024,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); @@ -1319,6 +1337,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 02b804c1ab29c..7a99c6cad8304 100644 --- a/compiler/rustc_middle/src/ty/print/pretty.rs +++ b/compiler/rustc_middle/src/ty/print/pretty.rs @@ -1746,6 +1746,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 4f6e2cc005160..5cf6769f5d62b 100644 --- a/compiler/rustc_monomorphize/src/collector.rs +++ b/compiler/rustc_monomorphize/src/collector.rs @@ -1299,6 +1299,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 6b7f67538e2cd91529d0672b96c0d1a8c823f281 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Fri, 2 Jan 2026 15:55:35 +0100 Subject: [PATCH 02/40] allow `const fn` to be c-variadic however `Drop` for `VaList` is not yet available in const fn --- .../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 ++++++------------- 4 files changed, 23 insertions(+), 73 deletions(-) diff --git a/compiler/rustc_ast_passes/src/ast_validation.rs b/compiler/rustc_ast_passes/src/ast_validation.rs index 2457e0a777e44..dc825d7e11ed3 100644 --- a/compiler/rustc_ast_passes/src/ast_validation.rs +++ b/compiler/rustc_ast_passes/src/ast_validation.rs @@ -698,15 +698,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 baf6f6beaeed7..5b00b47fbaa5c 100644 --- a/compiler/rustc_ast_passes/src/errors.rs +++ b/compiler/rustc_ast_passes/src/errors.rs @@ -823,17 +823,6 @@ pub(crate) struct ConstAndCoroutine { pub coroutine_kind: &'static str, } -#[derive(Diagnostic)] -#[diag("functions cannot be both `const` and C-variadic")] -pub(crate) struct ConstAndCVariadic { - #[primary_span] - pub spans: Vec, - #[label("`const` because of this")] - pub const_span: Span, - #[label("C-variadic because of this")] - pub variadic_span: Span, -} - #[derive(Diagnostic)] #[diag("functions cannot be both `{$coroutine_kind}` 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 335c8efa020bcea82744b3458e4619fe9ea1a6c4 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Fri, 2 Jan 2026 16:02:28 +0100 Subject: [PATCH 03/40] 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 3ddea90652d16..4aacafb75bd22 100644 --- a/library/core/src/intrinsics/mod.rs +++ b/library/core/src/intrinsics/mod.rs @@ -3472,7 +3472,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. @@ -3503,6 +3503,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 81b534dbe9c74e46a20cd4f3f04289a7fef0b592 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Fri, 2 Jan 2026 16:10:28 +0100 Subject: [PATCH 04/40] 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 c7ddcaa731579..563342d2dc7df 100644 --- a/compiler/rustc_const_eval/src/interpret/call.rs +++ b/compiler/rustc_const_eval/src/interpret/call.rs @@ -19,7 +19,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::enter_trace_span; use crate::interpret::EnteredTraceSpan; @@ -353,12 +353,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 { @@ -442,8 +452,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); @@ -451,7 +467,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 790a67a4736aae514f17d61a128523ec4479604f Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Fri, 2 Jan 2026 16:41:58 +0100 Subject: [PATCH 05/40] 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 563342d2dc7df..1aad53750cf90 100644 --- a/compiler/rustc_const_eval/src/interpret/call.rs +++ b/compiler/rustc_const_eval/src/interpret/call.rs @@ -4,11 +4,11 @@ use std::borrow::Cow; use either::{Left, Right}; -use rustc_abi::{self as abi, ExternAbi, FieldIdx, Integer, VariantIdx}; +use rustc_abi::{self as abi, ExternAbi, FieldIdx, HasDataLayout, Integer, Size, VariantIdx}; use rustc_data_structures::assert_matches; use rustc_errors::inline_fluent; 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; @@ -17,9 +17,9 @@ use tracing::field::Empty; use tracing::{info, instrument, trace}; use super::{ - CtfeProvenance, FnVal, ImmTy, InterpCx, InterpResult, MPlaceTy, Machine, OpTy, PlaceTy, - Projectable, Provenance, ReturnAction, ReturnContinuation, Scalar, StackPopInfo, interp_ok, - throw_ub, throw_ub_custom, + 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::enter_trace_span; use crate::interpret::EnteredTraceSpan; @@ -476,7 +476,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 203c7634d8dd0..cffd2e3da979b 100644 --- a/compiler/rustc_const_eval/src/interpret/memory.rs +++ b/compiler/rustc_const_eval/src/interpret/memory.rs @@ -23,8 +23,8 @@ use tracing::{debug, instrument, trace}; use super::{ AllocBytes, AllocId, AllocInit, AllocMap, AllocRange, Allocation, CheckAlignMsg, - CheckInAllocMsg, CtfeProvenance, GlobalAlloc, InterpCx, InterpResult, Machine, MayLeak, - Misalignment, Pointer, PointerArithmetic, Provenance, Scalar, alloc_range, err_ub, + CheckInAllocMsg, CtfeProvenance, GlobalAlloc, InterpCx, InterpResult, MPlaceTy, Machine, + MayLeak, Misalignment, Pointer, PointerArithmetic, Provenance, Scalar, alloc_range, err_ub, err_ub_custom, interp_ok, throw_ub, throw_ub_custom, throw_unsup, throw_unsup_format, }; use crate::const_eval::ConstEvalErrKind; @@ -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 52a64b78affa1fe961ef366cfbfa1da3f8d65572 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Fri, 2 Jan 2026 16:53:18 +0100 Subject: [PATCH 06/40] 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 da4f97db1c59c..eb97157a5d56f 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 super::error::*; use crate::errors::{LongRunning, LongRunningWarn}; use crate::interpret::{ self, AllocId, AllocInit, AllocRange, ConstAllocation, CtfeProvenance, FnArg, Frame, - GlobalAlloc, ImmTy, InterpCx, InterpResult, OpTy, PlaceTy, RangeSet, Scalar, - 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 @@ -602,6 +602,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 cffd2e3da979b..63c9c69698731 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 b8a24d6aa1a58c77e8aa95a23711a65d0dfd3b34 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Fri, 2 Jan 2026 20:31:28 +0100 Subject: [PATCH 07/40] 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 9d64415ce200ec900bb4a2cfe3703b38ab6857a0 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Fri, 2 Jan 2026 16:53:56 +0100 Subject: [PATCH 08/40] 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 | 17 +- 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, 439 insertions(+), 5 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 eb97157a5d56f..4015ae0c40d73 100644 --- a/compiler/rustc_const_eval/src/const_eval/machine.rs +++ b/compiler/rustc_const_eval/src/const_eval/machine.rs @@ -9,7 +9,7 @@ use rustc_errors::inline_fluent; use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_hir::{self as hir, CRATE_HIR_ID, LangItem}; use rustc_middle::mir::AssertMessage; -use rustc_middle::mir::interpret::{Pointer, ReportedErrorInfo}; +use rustc_middle::mir::interpret::ReportedErrorInfo; use rustc_middle::query::TyCtxtAt; use rustc_middle::ty::layout::{HasTypingEnv, TyAndLayout, ValidityRequirement}; use rustc_middle::ty::{self, Ty, TyCtxt}; @@ -636,14 +636,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 928f3836fe159..09eb41913fe50 100644 --- a/compiler/rustc_const_eval/src/errors.rs +++ b/compiler/rustc_const_eval/src/errors.rs @@ -769,6 +769,7 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> { } AbiMismatchArgument { .. } => inline_fluent!("calling a function whose parameter #{$arg_idx} has type {$callee_ty} passing argument of type {$caller_ty}"), AbiMismatchReturn { .. } => inline_fluent!("calling a function with return type {$callee_ty} passing return place of type {$caller_ty}"), + VaArgOutOfBounds => "more C-variadic arguments read than were passed".into(), } } @@ -793,6 +794,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 8618f25f8b50ce600fb0f48312c5b291301750de Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Wed, 7 Jan 2026 14:43:01 +0100 Subject: [PATCH 09/40] 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 4015ae0c40d73..7e74f331f7450 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 super::error::*; use crate::errors::{LongRunning, LongRunningWarn}; 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, }; @@ -602,6 +602,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 4aacafb75bd22..66e68cb866db9 100644 --- a/library/core/src/intrinsics/mod.rs +++ b/library/core/src/intrinsics/mod.rs @@ -3484,7 +3484,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 6d1c529b9688e871bb6189f0c83379c97c5632e7 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Thu, 8 Jan 2026 10:41:23 +0100 Subject: [PATCH 10/40] 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 7e74f331f7450..c211633f21899 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::errors::{LongRunning, LongRunningWarn}; 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 @@ -602,81 +602,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 1aad53750cf90..4e30a46fc97b5 100644 --- a/compiler/rustc_const_eval/src/interpret/call.rs +++ b/compiler/rustc_const_eval/src/interpret/call.rs @@ -491,7 +491,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 2ea5e4a25c116..944fabc281aa5 100644 --- a/compiler/rustc_const_eval/src/interpret/intrinsics.rs +++ b/compiler/rustc_const_eval/src/interpret/intrinsics.rs @@ -24,7 +24,7 @@ use super::util::ensure_monomorphic_enough; use super::{ AllocId, CheckInAllocMsg, ImmTy, InterpCx, InterpResult, Machine, OpTy, PlaceTy, Pointer, PointerArithmetic, Provenance, Scalar, err_ub_custom, err_unsup_format, interp_ok, throw_inval, - throw_ub_custom, throw_ub_format, + throw_ub, throw_ub_custom, throw_ub_format, throw_unsup_format, }; use crate::interpret::Writeable; @@ -743,6 +743,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 2acc7215898651f82475569a2f727a26ad5e9bc2 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Tue, 20 Jan 2026 21:37:40 +0100 Subject: [PATCH 11/40] 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 3dfaced2e615d0a98de714a189a1e914589a70bd Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Thu, 22 Jan 2026 10:37:49 +0100 Subject: [PATCH 12/40] 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 4e30a46fc97b5..9b6c133c55bb8 100644 --- a/compiler/rustc_const_eval/src/interpret/call.rs +++ b/compiler/rustc_const_eval/src/interpret/call.rs @@ -478,7 +478,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 944fabc281aa5..c2595983146f6 100644 --- a/compiler/rustc_const_eval/src/interpret/intrinsics.rs +++ b/compiler/rustc_const_eval/src/interpret/intrinsics.rs @@ -767,9 +767,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)?; @@ -839,7 +838,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 63c9c69698731..423a1f8b03fa5 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 0e78a06d4383675e836722d174ee91fdf5b81c49 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Thu, 22 Jan 2026 11:26:07 +0100 Subject: [PATCH 13/40] 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 9b6c133c55bb8..eb7b335a586ab 100644 --- a/compiler/rustc_const_eval/src/interpret/call.rs +++ b/compiler/rustc_const_eval/src/interpret/call.rs @@ -8,7 +8,7 @@ use rustc_abi::{self as abi, ExternAbi, FieldIdx, HasDataLayout, Integer, Size, use rustc_data_structures::assert_matches; use rustc_errors::inline_fluent; 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; @@ -18,8 +18,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::enter_trace_span; use crate::interpret::EnteredTraceSpan; @@ -476,10 +476,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 { @@ -493,12 +489,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 c2595983146f6..685cf5fcb29d2 100644 --- a/compiler/rustc_const_eval/src/interpret/intrinsics.rs +++ b/compiler/rustc_const_eval/src/interpret/intrinsics.rs @@ -759,28 +759,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 => { @@ -803,7 +796,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) }; } @@ -827,31 +820,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 423a1f8b03fa5..f1fe83ad1c912 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, @@ -981,6 +997,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() @@ -1068,6 +1085,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 8266cc13fae9151a83915e4f35e7edcb5567a3ec Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Thu, 22 Jan 2026 11:35:06 +0100 Subject: [PATCH 14/40] 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 -- .../src/const_eval/machine.rs | 2 +- .../rustc_const_eval/src/interpret/memory.rs | 19 ----------- .../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 - 12 files changed, 4 insertions(+), 70 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 c835e8e8dda30..f2261ab79340f 100644 --- a/compiler/rustc_codegen_llvm/src/common.rs +++ b/compiler/rustc_codegen_llvm/src/common.rs @@ -329,9 +329,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/const_eval/machine.rs b/compiler/rustc_const_eval/src/const_eval/machine.rs index c211633f21899..c06434044a795 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 super::error::*; use crate::errors::{LongRunning, LongRunningWarn}; use crate::interpret::{ self, AllocId, AllocInit, AllocRange, ConstAllocation, CtfeProvenance, FnArg, Frame, - GlobalAlloc, ImmTy, InterpCx, InterpResult, OpTy, PlaceTy, RangeSet, Scalar, + GlobalAlloc, ImmTy, InterpCx, InterpResult, OpTy, PlaceTy, Pointer, RangeSet, Scalar, compile_time_machine, err_inval, interp_ok, throw_exhaust, throw_inval, throw_ub, throw_ub_custom, throw_unsup, throw_unsup_format, }; diff --git a/compiler/rustc_const_eval/src/interpret/memory.rs b/compiler/rustc_const_eval/src/interpret/memory.rs index f1fe83ad1c912..495a3613bc44e 100644 --- a/compiler/rustc_const_eval/src/interpret/memory.rs +++ b/compiler/rustc_const_eval/src/interpret/memory.rs @@ -443,20 +443,6 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { kind = "vtable", ) } - Some(GlobalAlloc::VaList) => { - err_ub_custom!( - inline_fluent!( - "deallocating {$alloc_id}, which is {$kind -> - [fn] a function - [vtable] a vtable - [static_mem] static memory - *[other] {\"\"} -}" - ), - alloc_id = alloc_id, - kind = "valist", - ) - } Some(GlobalAlloc::TypeId { .. }) => { err_ub_custom!( inline_fluent!( @@ -754,7 +740,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)) => { @@ -1046,7 +1031,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); @@ -1370,9 +1354,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 7a99c6cad8304..02b804c1ab29c 100644 --- a/compiler/rustc_middle/src/ty/print/pretty.rs +++ b/compiler/rustc_middle/src/ty/print/pretty.rs @@ -1746,7 +1746,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 5cf6769f5d62b..4f6e2cc005160 100644 --- a/compiler/rustc_monomorphize/src/collector.rs +++ b/compiler/rustc_monomorphize/src/collector.rs @@ -1299,7 +1299,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)) } From 1c801db88b459b10d788d9e6b5b6e3bdeba22e8d Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Thu, 22 Jan 2026 12:09:06 +0100 Subject: [PATCH 15/40] add (dead) allocation bookkeeping --- compiler/rustc_const_eval/src/interpret/memory.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/compiler/rustc_const_eval/src/interpret/memory.rs b/compiler/rustc_const_eval/src/interpret/memory.rs index 495a3613bc44e..b7bac9d2ea542 100644 --- a/compiler/rustc_const_eval/src/interpret/memory.rs +++ b/compiler/rustc_const_eval/src/interpret/memory.rs @@ -1022,6 +1022,11 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { return AllocInfo::new(Size::ZERO, align, AllocKind::Function, Mutability::Not); } + // # Variable argument lists + if let Some(_) = self.get_va_list_alloc(id) { + return AllocInfo::new(Size::ZERO, Align::ONE, AllocKind::VaList, Mutability::Not); + } + // # Global allocations if let Some(global_alloc) = self.tcx.try_get_global_alloc(id) { // NOTE: `static` alignment from attributes has already been applied to the allocation. @@ -1077,6 +1082,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { &mut self, id: AllocId, ) -> Option>> { + self.memory.dead_alloc_map.insert(id, (Size::ZERO, Align::ONE)); self.memory.va_list_map.swap_remove(&id) } From 04721bc07f3f51a5cadb717324b2cc7c4148d315 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Thu, 22 Jan 2026 15:39:54 +0100 Subject: [PATCH 16/40] zero the `VaList` place so it is initialized --- compiler/rustc_const_eval/src/interpret/call.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_const_eval/src/interpret/call.rs b/compiler/rustc_const_eval/src/interpret/call.rs index eb7b335a586ab..4a700f5dec06d 100644 --- a/compiler/rustc_const_eval/src/interpret/call.rs +++ b/compiler/rustc_const_eval/src/interpret/call.rs @@ -493,12 +493,17 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { let ptr = self.va_list_ptr(varargs, 0); let addr = Scalar::from_pointer(ptr, self); + // Zero the mplace, so it is fully initialized. + self.write_bytes_ptr( + mplace.ptr(), + (0..mplace.layout.size.bytes()).map(|_| 0u8), + )?; + // Store the pointer to the global variable arguments list allocation in the // first bytes of the `VaList` value. let mut alloc = self .get_ptr_alloc_mut(mplace.ptr(), self.data_layout().pointer_size())? .expect("not a ZST"); - alloc.write_ptr_sized(Size::ZERO, addr)?; } else if Some(local) == body.spread_arg { // Make the local live once, then fill in the value field by field. From 83a12af7152e4a717167c335d4e521cd185de928 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Thu, 22 Jan 2026 19:23:51 +0100 Subject: [PATCH 17/40] add c-variadic miri test --- src/tools/miri/tests/pass/c-variadic.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 src/tools/miri/tests/pass/c-variadic.rs diff --git a/src/tools/miri/tests/pass/c-variadic.rs b/src/tools/miri/tests/pass/c-variadic.rs new file mode 100644 index 0000000000000..a1f03db6872fa --- /dev/null +++ b/src/tools/miri/tests/pass/c-variadic.rs @@ -0,0 +1,18 @@ +#![feature(c_variadic)] + +use core::ffi::VaList; + +fn helper(ap: VaList) -> i32 { + // unsafe { ap.arg::() } + let _ = ap; + 0 +} + +unsafe extern "C" fn variadic(a: i32, ap: ...) -> i32 { + assert_eq!(a, 42); + helper(ap) +} + +fn main() { + assert_eq!(unsafe { variadic(42, 1) }, 1); +} From b4a6323d3897ecd18862da953b1df575b8eac2d0 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Thu, 22 Jan 2026 22:24:26 +0100 Subject: [PATCH 18/40] WIP --- compiler/rustc_const_eval/messages.ftl | 3 ++ compiler/rustc_const_eval/src/errors.rs | 6 +++- .../rustc_const_eval/src/interpret/memory.rs | 28 +++++++++++++++++++ .../rustc_middle/src/mir/interpret/error.rs | 2 ++ 4 files changed, 38 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_const_eval/messages.ftl b/compiler/rustc_const_eval/messages.ftl index 72ec4cac697d6..0ad80d713fbb1 100644 --- a/compiler/rustc_const_eval/messages.ftl +++ b/compiler/rustc_const_eval/messages.ftl @@ -197,6 +197,9 @@ const_eval_invalid_uninit_bytes = const_eval_invalid_uninit_bytes_unknown = using uninitialized data, but this operation requires initialized memory +const_eval_invalid_va_list_pointer = + using {$pointer} as variable argument list pointer but it does not point to a variable argument list + const_eval_invalid_vtable_pointer = using {$pointer} as vtable pointer but it does not point to a vtable diff --git a/compiler/rustc_const_eval/src/errors.rs b/compiler/rustc_const_eval/src/errors.rs index 09eb41913fe50..18c9121c62933 100644 --- a/compiler/rustc_const_eval/src/errors.rs +++ b/compiler/rustc_const_eval/src/errors.rs @@ -755,6 +755,7 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> { InvalidChar(_) => inline_fluent!("interpreting an invalid 32-bit value as a char: 0x{$value}"), InvalidTag(_) => inline_fluent!("enum value has invalid tag: {$tag}"), InvalidFunctionPointer(_) => inline_fluent!("using {$pointer} as function pointer but it does not point to a function"), + InvalidVaListPointer(_) => inline_fluent!("using {$pointer} as variable argument list pointer but it does not point to a variable argument list"), InvalidVTablePointer(_) => inline_fluent!("using {$pointer} as vtable pointer but it does not point to a vtable"), InvalidVTableTrait { .. } => inline_fluent!("using vtable for `{$vtable_dyn_type}` but `{$expected_dyn_type}` was expected"), InvalidStr(_) => inline_fluent!("this string is not valid UTF-8: {$err}"), @@ -815,7 +816,10 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> { diag.arg("len", len); diag.arg("index", index); } - UnterminatedCString(ptr) | InvalidFunctionPointer(ptr) | InvalidVTablePointer(ptr) => { + UnterminatedCString(ptr) + | InvalidFunctionPointer(ptr) + | InvalidVaListPointer(ptr) + | InvalidVTablePointer(ptr) => { diag.arg("pointer", ptr); } InvalidVTableTrait { expected_dyn_type, vtable_dyn_type } => { diff --git a/compiler/rustc_const_eval/src/interpret/memory.rs b/compiler/rustc_const_eval/src/interpret/memory.rs index b7bac9d2ea542..2f9df45d3e308 100644 --- a/compiler/rustc_const_eval/src/interpret/memory.rs +++ b/compiler/rustc_const_eval/src/interpret/memory.rs @@ -1113,6 +1113,34 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { .into() } + pub fn insert_va_list( + &self, + va_list: Vec>, + ) -> InterpResult<'tcx, Pointer>> { + trace!("get_ptr_va_list({:?})", ptr); + let (alloc_id, offset, _prov) = self.ptr_get_alloc_id(ptr, 0)?; + // if offset.bytes() != 0 { + // throw_ub!(InvalidFunctionPointer(Pointer::new(alloc_id, offset))) + // } + self.remove_va_list_alloc(alloc_id) + .ok_or_else(|| err_ub!(InvalidVaListPointer(Pointer::new(alloc_id, offset)))) + .into() + } + + pub fn remove_va_list( + &self, + ptr: Pointer>, + ) -> InterpResult<'tcx, Vec>> { + trace!("get_ptr_va_list({:?})", ptr); + let (alloc_id, offset, _prov) = self.ptr_get_alloc_id(ptr, 0)?; + // if offset.bytes() != 0 { + // throw_ub!(InvalidFunctionPointer(Pointer::new(alloc_id, offset))) + // } + self.remove_va_list_alloc(alloc_id) + .ok_or_else(|| err_ub!(InvalidVaListPointer(Pointer::new(alloc_id, offset)))) + .into() + } + /// Get the dynamic type of the given vtable pointer. /// If `expected_trait` is `Some`, it must be a vtable for the given trait. pub fn get_ptr_vtable_ty( diff --git a/compiler/rustc_middle/src/mir/interpret/error.rs b/compiler/rustc_middle/src/mir/interpret/error.rs index 970dbd95f7cc3..c0d303bc1d5fb 100644 --- a/compiler/rustc_middle/src/mir/interpret/error.rs +++ b/compiler/rustc_middle/src/mir/interpret/error.rs @@ -404,6 +404,8 @@ pub enum UndefinedBehaviorInfo<'tcx> { InvalidTag(Scalar), /// Using a pointer-not-to-a-function as function pointer. InvalidFunctionPointer(Pointer), + /// Using a pointer-not-to-a-va-list as variable argument list pointer. + InvalidVaListPointer(Pointer), /// Using a pointer-not-to-a-vtable as vtable pointer. InvalidVTablePointer(Pointer), /// Using a vtable for the wrong trait. From 99b7ec2aa0705b45e24089ba09c9f7e486922695 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Fri, 23 Jan 2026 22:36:47 +0100 Subject: [PATCH 19/40] move va_list operations into `InterpCx` --- .../rustc_const_eval/src/interpret/call.rs | 3 +- .../src/interpret/intrinsics.rs | 38 +----- .../rustc_const_eval/src/interpret/memory.rs | 122 ++++++++++------- tests/ui/consts/const-eval/c-variadic-fail.rs | 17 ++- .../consts/const-eval/c-variadic-fail.stderr | 127 +++++++++++------- 5 files changed, 174 insertions(+), 133 deletions(-) diff --git a/compiler/rustc_const_eval/src/interpret/call.rs b/compiler/rustc_const_eval/src/interpret/call.rs index 4a700f5dec06d..83d86c6448252 100644 --- a/compiler/rustc_const_eval/src/interpret/call.rs +++ b/compiler/rustc_const_eval/src/interpret/call.rs @@ -489,8 +489,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 = varargs.clone(); - // This is a new VaList, so start at index 0. - let ptr = self.va_list_ptr(varargs, 0); + let ptr = self.va_list_insert(varargs); let addr = Scalar::from_pointer(ptr, self); // Zero the mplace, so it is fully initialized. diff --git a/compiler/rustc_const_eval/src/interpret/intrinsics.rs b/compiler/rustc_const_eval/src/interpret/intrinsics.rs index 685cf5fcb29d2..fe2ecab906921 100644 --- a/compiler/rustc_const_eval/src/interpret/intrinsics.rs +++ b/compiler/rustc_const_eval/src/interpret/intrinsics.rs @@ -24,7 +24,7 @@ use super::util::ensure_monomorphic_enough; use super::{ AllocId, CheckInAllocMsg, ImmTy, InterpCx, InterpResult, Machine, OpTy, PlaceTy, Pointer, PointerArithmetic, Provenance, Scalar, err_ub_custom, err_unsup_format, interp_ok, throw_inval, - throw_ub, throw_ub_custom, throw_ub_format, throw_unsup_format, + throw_ub_custom, throw_ub_format, throw_unsup_format, }; use crate::interpret::Writeable; @@ -757,18 +757,8 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { scalar.to_pointer(self)? }; - let (prov, offset) = src_va_list_ptr.into_raw_parts(); - let src_alloc_id = prov.unwrap().get_alloc_id().unwrap(); - let index = offset.bytes(); - - // Look up arguments without consuming src. - let Some(arguments) = self.get_va_list_alloc(src_alloc_id) else { - throw_unsup_format!("va_copy on unknown va_list allocation {:?}", src_alloc_id); - }; - - // Create a new allocation pointing at the same index. - let new_va_list_ptr = self.va_list_ptr(arguments.to_vec(), index); - let addr = Scalar::from_pointer(new_va_list_ptr, self); + let copy_va_list_ptr = self.va_list_copy(src_va_list_ptr)?; + let addr = Scalar::from_pointer(copy_va_list_ptr, self); // Now overwrite the token pointer stored inside the VaList. let mplace = self.force_allocation(dest)?; @@ -793,12 +783,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { scalar.to_pointer(self)? }; - let (prov, _offset) = va_list_ptr.into_raw_parts(); - let alloc_id = prov.unwrap().get_alloc_id().unwrap(); - - let Some(_) = self.remove_va_list_alloc(alloc_id) else { - throw_unsup_format!("va_end on unknown va_list allocation {:?}", alloc_id) - }; + self.va_list_remove(va_list_ptr)?; } sym::va_arg => { @@ -818,21 +803,8 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { scalar.to_pointer(self)? }; - let (prov, offset) = va_list_ptr.into_raw_parts(); - let alloc_id = prov.unwrap().get_alloc_id().unwrap(); - let index = offset.bytes(); - - let Some(varargs) = self.remove_va_list_alloc(alloc_id) else { - throw_unsup_format!("va_arg on unknown va_list allocation {:?}", alloc_id) - }; - - let Some(src_mplace) = varargs.get(offset.bytes_usize()).cloned() else { - throw_ub!(VaArgOutOfBounds) - }; + let (src_mplace, new_va_list_ptr) = self.va_list_read_arg(va_list_ptr)?; - // Update the offset in this `VaList` value so that a subsequent call to `va_arg` - // reads the next argument. - let new_va_list_ptr = self.va_list_ptr(varargs, index + 1); let addr = Scalar::from_pointer(new_va_list_ptr, self); let mut alloc = self .get_ptr_alloc_mut(ap_ref, ptr_size)? diff --git a/compiler/rustc_const_eval/src/interpret/memory.rs b/compiler/rustc_const_eval/src/interpret/memory.rs index 2f9df45d3e308..964d397c4d770 100644 --- a/compiler/rustc_const_eval/src/interpret/memory.rs +++ b/compiler/rustc_const_eval/src/interpret/memory.rs @@ -237,21 +237,6 @@ 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, @@ -1023,7 +1008,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { } // # Variable argument lists - if let Some(_) = self.get_va_list_alloc(id) { + if self.memory.va_list_map.contains_key(&id) { return AllocInfo::new(Size::ZERO, Align::ONE, AllocKind::VaList, Mutability::Not); } @@ -1074,18 +1059,6 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { } } - pub fn get_va_list_alloc(&self, id: AllocId) -> Option<&[MPlaceTy<'tcx, M::Provenance>]> { - self.memory.va_list_map.get(&id).map(|v| &**v) - } - - pub fn remove_va_list_alloc( - &mut self, - id: AllocId, - ) -> Option>> { - self.memory.dead_alloc_map.insert(id, (Size::ZERO, Align::ONE)); - self.memory.va_list_map.swap_remove(&id) - } - /// Takes a pointer that is the first chunk of a `TypeId` and return the type that its /// provenance refers to, as well as the segment of the hash that this pointer covers. pub fn get_ptr_type_id( @@ -1113,32 +1086,85 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { .into() } - pub fn insert_va_list( - &self, - va_list: Vec>, - ) -> InterpResult<'tcx, Pointer>> { - trace!("get_ptr_va_list({:?})", ptr); + pub fn va_list_insert( + &mut self, + varargs: Vec>, + ) -> Pointer { + let id = self.tcx.reserve_alloc_id(); + let old = self.memory.va_list_map.insert(id, varargs); + assert!(old.is_none()); + // 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(Pointer::from(id)).unwrap() + } + + pub fn va_list_copy( + &mut self, + ptr: Pointer>, + ) -> InterpResult<'tcx, Pointer> { let (alloc_id, offset, _prov) = self.ptr_get_alloc_id(ptr, 0)?; - // if offset.bytes() != 0 { - // throw_ub!(InvalidFunctionPointer(Pointer::new(alloc_id, offset))) - // } - self.remove_va_list_alloc(alloc_id) - .ok_or_else(|| err_ub!(InvalidVaListPointer(Pointer::new(alloc_id, offset)))) - .into() + if offset.bytes() != 0 { + throw_ub!(InvalidVaListPointer(Pointer::new(alloc_id, offset))) + } + + let Some(va_list) = self.memory.va_list_map.get(&alloc_id) else { + throw_ub!(InvalidVaListPointer(Pointer::new(alloc_id, offset))) + }; + + let alloc_id = self.tcx.reserve_alloc_id(); + let old = self.memory.va_list_map.insert(alloc_id, va_list.clone()); + assert!(old.is_none()); + + self.global_root_pointer(Pointer::from(alloc_id)) } - pub fn remove_va_list( - &self, + pub fn va_list_read_arg( + &mut self, + ptr: Pointer>, + ) -> InterpResult<'tcx, (MPlaceTy<'tcx, M::Provenance>, Pointer)> { + let (alloc_id, offset, _prov) = self.ptr_get_alloc_id(ptr, 0)?; + if offset.bytes() != 0 { + throw_ub!(InvalidVaListPointer(Pointer::new(alloc_id, offset))) + } + + let Some(mut va_list) = self.memory.va_list_map.swap_remove(&alloc_id) else { + throw_ub!(InvalidVaListPointer(Pointer::new(alloc_id, offset))) + }; + + if va_list.is_empty() { + throw_ub!(VaArgOutOfBounds) + } + + let arg = va_list.remove(0); + + let alloc_id = self.tcx.reserve_alloc_id(); + let old = self.memory.va_list_map.insert(alloc_id, va_list); + assert!(old.is_none()); + + let new_va_list_ptr = self.global_root_pointer(Pointer::from(alloc_id))?; + interp_ok((arg, new_va_list_ptr)) + } + + pub fn va_list_remove( + &mut self, ptr: Pointer>, ) -> InterpResult<'tcx, Vec>> { - trace!("get_ptr_va_list({:?})", ptr); let (alloc_id, offset, _prov) = self.ptr_get_alloc_id(ptr, 0)?; - // if offset.bytes() != 0 { - // throw_ub!(InvalidFunctionPointer(Pointer::new(alloc_id, offset))) - // } - self.remove_va_list_alloc(alloc_id) - .ok_or_else(|| err_ub!(InvalidVaListPointer(Pointer::new(alloc_id, offset)))) - .into() + if offset.bytes() != 0 { + throw_ub!(InvalidVaListPointer(Pointer::new(alloc_id, offset))) + } + + if offset.bytes() != 0 { + throw_ub!(InvalidVaListPointer(Pointer::new(alloc_id, offset))) + } + + let Some(va_list) = self.memory.va_list_map.swap_remove(&alloc_id) else { + throw_ub!(InvalidVaListPointer(Pointer::new(alloc_id, offset))) + }; + + self.memory.dead_alloc_map.insert(alloc_id, (Size::ZERO, Align::ONE)); + + interp_ok(va_list) } /// Get the dynamic type of the given vtable pointer. diff --git a/tests/ui/consts/const-eval/c-variadic-fail.rs b/tests/ui/consts/const-eval/c-variadic-fail.rs index 37810ddb356df..ddc4aafe11479 100644 --- a/tests/ui/consts/const-eval/c-variadic-fail.rs +++ b/tests/ui/consts/const-eval/c-variadic-fail.rs @@ -7,6 +7,7 @@ #![feature(const_clone)] use std::ffi::VaList; +use std::mem::MaybeUninit; const unsafe extern "C" fn read_n(mut ap: ...) { let mut i = N; @@ -104,7 +105,7 @@ fn manual_copy_drop() { } const { unsafe { helper(1, 2, 3) } }; - //~^ ERROR va_end on unknown va_list allocation ALLOC0 [E0080] + //~^ ERROR using ALLOC0 as variable argument list pointer but it does not point to a variable argument list [E0080] } fn manual_copy_forget() { @@ -120,7 +121,7 @@ fn manual_copy_forget() { } const { unsafe { helper(1, 2, 3) } }; - //~^ ERROR va_end on unknown va_list allocation ALLOC0 [E0080] + //~^ ERROR using ALLOC0 as variable argument list pointer but it does not point to a variable argument list [E0080] } fn manual_copy_read() { @@ -133,7 +134,16 @@ fn manual_copy_read() { } const { unsafe { helper(1, 2, 3) } }; - //~^ ERROR va_arg on unknown va_list allocation ALLOC0 + //~^ ERROR using ALLOC0 as variable argument list pointer but it does not point to a variable argument list [E0080] +} + +fn drop_of_invalid() { + const { + let mut invalid: MaybeUninit = MaybeUninit::zeroed(); + unsafe { invalid.as_mut_ptr().cast::().write(0xdeadbeef) }; + let ap = unsafe { invalid.assume_init() }; + } + //~^ ERROR pointer not dereferenceable: pointer must point to some allocation, but got 0xdeadbeef[noalloc] } fn main() { @@ -143,5 +153,6 @@ fn main() { manual_copy_read(); manual_copy_drop(); manual_copy_forget(); + drop_of_invalid(); } } diff --git a/tests/ui/consts/const-eval/c-variadic-fail.stderr b/tests/ui/consts/const-eval/c-variadic-fail.stderr index 4c569a4492d47..2625aee763cd3 100644 --- a/tests/ui/consts/const-eval/c-variadic-fail.stderr +++ b/tests/ui/consts/const-eval/c-variadic-fail.stderr @@ -1,11 +1,11 @@ error[E0080]: more C-variadic arguments read than were passed - --> $DIR/c-variadic-fail.rs:27:13 + --> $DIR/c-variadic-fail.rs:28:13 | LL | const { read_n::<1>() } | ^^^^^^^^^^^^^ evaluation of `read_too_many::{constant#2}` failed inside this call | note: inside `read_n::<1>` - --> $DIR/c-variadic-fail.rs:15:17 + --> $DIR/c-variadic-fail.rs:16:17 | LL | let _ = ap.arg::(); | ^^^^^^^^^^^^^^^ @@ -13,13 +13,13 @@ note: inside `VaList::<'_>::arg::` --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:27:5 + --> $DIR/c-variadic-fail.rs:28:5 | LL | const { read_n::<1>() } | ^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:27:5 + --> $DIR/c-variadic-fail.rs:28:5 | LL | const { read_n::<1>() } | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -27,13 +27,13 @@ LL | const { read_n::<1>() } = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` error[E0080]: more C-variadic arguments read than were passed - --> $DIR/c-variadic-fail.rs:31:13 + --> $DIR/c-variadic-fail.rs:32:13 | LL | const { read_n::<2>(1) } | ^^^^^^^^^^^^^^ evaluation of `read_too_many::{constant#3}` failed inside this call | note: inside `read_n::<2>` - --> $DIR/c-variadic-fail.rs:15:17 + --> $DIR/c-variadic-fail.rs:16:17 | LL | let _ = ap.arg::(); | ^^^^^^^^^^^^^^^ @@ -41,13 +41,13 @@ note: inside `VaList::<'_>::arg::` --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:31:5 + --> $DIR/c-variadic-fail.rs:32:5 | LL | const { read_n::<2>(1) } | ^^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:31:5 + --> $DIR/c-variadic-fail.rs:32:5 | LL | const { read_n::<2>(1) } | ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -55,13 +55,13 @@ LL | const { read_n::<2>(1) } = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` error[E0080]: va_arg type mismatch: requested `u32`, but next argument is `i32` - --> $DIR/c-variadic-fail.rs:49:13 + --> $DIR/c-variadic-fail.rs:50:13 | LL | const { read_as::(1i32) }; | ^^^^^^^^^^^^^^^^^^^^ evaluation of `read_cast::{constant#6}` failed inside this call | note: inside `read_as::` - --> $DIR/c-variadic-fail.rs:36:5 + --> $DIR/c-variadic-fail.rs:37:5 | LL | ap.arg::() | ^^^^^^^^^^^^^ @@ -69,13 +69,13 @@ note: inside `VaList::<'_>::arg::` --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:49:5 + --> $DIR/c-variadic-fail.rs:50:5 | LL | const { read_as::(1i32) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:49:5 + --> $DIR/c-variadic-fail.rs:50:5 | LL | const { read_as::(1i32) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -83,13 +83,13 @@ LL | const { read_as::(1i32) }; = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` error[E0080]: va_arg type mismatch: requested `i32`, but next argument is `u32` - --> $DIR/c-variadic-fail.rs:52:13 + --> $DIR/c-variadic-fail.rs:53:13 | LL | const { read_as::(1u32) }; | ^^^^^^^^^^^^^^^^^^^^ evaluation of `read_cast::{constant#7}` failed inside this call | note: inside `read_as::` - --> $DIR/c-variadic-fail.rs:36:5 + --> $DIR/c-variadic-fail.rs:37:5 | LL | ap.arg::() | ^^^^^^^^^^^^^ @@ -97,13 +97,13 @@ note: inside `VaList::<'_>::arg::` --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:52:5 + --> $DIR/c-variadic-fail.rs:53:5 | LL | const { read_as::(1u32) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:52:5 + --> $DIR/c-variadic-fail.rs:53:5 | LL | const { read_as::(1u32) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -111,13 +111,13 @@ LL | const { read_as::(1u32) }; = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` error[E0080]: va_arg type mismatch: requested `i32`, but next argument is `u64` - --> $DIR/c-variadic-fail.rs:55:13 + --> $DIR/c-variadic-fail.rs:56:13 | LL | const { read_as::(1u64) }; | ^^^^^^^^^^^^^^^^^^^^ evaluation of `read_cast::{constant#8}` failed inside this call | note: inside `read_as::` - --> $DIR/c-variadic-fail.rs:36:5 + --> $DIR/c-variadic-fail.rs:37:5 | LL | ap.arg::() | ^^^^^^^^^^^^^ @@ -125,13 +125,13 @@ note: inside `VaList::<'_>::arg::` --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:55:5 + --> $DIR/c-variadic-fail.rs:56:5 | LL | const { read_as::(1u64) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:55:5 + --> $DIR/c-variadic-fail.rs:56:5 | LL | const { read_as::(1u64) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -139,13 +139,13 @@ LL | const { read_as::(1u64) }; = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` error[E0080]: va_arg type mismatch: requested `f64`, but next argument is `i32` - --> $DIR/c-variadic-fail.rs:58:13 + --> $DIR/c-variadic-fail.rs:59:13 | LL | const { read_as::(1i32) }; | ^^^^^^^^^^^^^^^^^^^^ evaluation of `read_cast::{constant#9}` failed inside this call | note: inside `read_as::` - --> $DIR/c-variadic-fail.rs:36:5 + --> $DIR/c-variadic-fail.rs:37:5 | LL | ap.arg::() | ^^^^^^^^^^^^^ @@ -153,13 +153,13 @@ note: inside `VaList::<'_>::arg::` --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:58:5 + --> $DIR/c-variadic-fail.rs:59:5 | LL | const { read_as::(1i32) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:58:5 + --> $DIR/c-variadic-fail.rs:59:5 | LL | const { read_as::(1i32) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -167,13 +167,13 @@ LL | const { read_as::(1i32) }; = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` error[E0080]: va_arg type mismatch: requested `*const u8`, but next argument is `i32` - --> $DIR/c-variadic-fail.rs:61:13 + --> $DIR/c-variadic-fail.rs:62:13 | LL | const { read_as::<*const u8>(1i32) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^ evaluation of `read_cast::{constant#10}` failed inside this call | note: inside `read_as::<*const u8>` - --> $DIR/c-variadic-fail.rs:36:5 + --> $DIR/c-variadic-fail.rs:37:5 | LL | ap.arg::() | ^^^^^^^^^^^^^ @@ -181,13 +181,13 @@ note: inside `VaList::<'_>::arg::<*const u8>` --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:61:5 + --> $DIR/c-variadic-fail.rs:62:5 | LL | const { read_as::<*const u8>(1i32) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:61:5 + --> $DIR/c-variadic-fail.rs:62:5 | LL | const { read_as::<*const u8>(1i32) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -195,7 +195,7 @@ LL | const { read_as::<*const u8>(1i32) }; = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` error[E0080]: memory access failed: ALLOC0 has been freed, so this pointer is dangling - --> $DIR/c-variadic-fail.rs:74:13 + --> $DIR/c-variadic-fail.rs:75:13 | LL | ap.arg::(); | ^^^^^^^^^^^^^^^ evaluation of `use_after_free::{constant#0}` failed inside this call @@ -204,7 +204,7 @@ note: inside `VaList::<'_>::arg::` --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:70:5 + --> $DIR/c-variadic-fail.rs:71:5 | LL | / const { LL | | unsafe { @@ -215,7 +215,7 @@ LL | | }; | |_____^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:70:5 + --> $DIR/c-variadic-fail.rs:71:5 | LL | / const { LL | | unsafe { @@ -227,14 +227,14 @@ LL | | }; | = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` -error[E0080]: va_end on unknown va_list allocation ALLOC1 - --> $DIR/c-variadic-fail.rs:106:22 +error[E0080]: using ALLOC1 as variable argument list pointer but it does not point to a variable argument list + --> $DIR/c-variadic-fail.rs:107:22 | LL | const { unsafe { helper(1, 2, 3) } }; | ^^^^^^^^^^^^^^^ evaluation of `manual_copy_drop::{constant#0}` failed inside this call | note: inside `manual_copy_drop::helper` - --> $DIR/c-variadic-fail.rs:103:9 + --> $DIR/c-variadic-fail.rs:104:9 | LL | drop(ap); | ^^^^^^^^ @@ -246,27 +246,27 @@ note: inside ` as Drop>::drop` --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:106:5 + --> $DIR/c-variadic-fail.rs:107:5 | LL | const { unsafe { helper(1, 2, 3) } }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:106:5 + --> $DIR/c-variadic-fail.rs:107:5 | LL | const { unsafe { helper(1, 2, 3) } }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` -error[E0080]: va_end on unknown va_list allocation ALLOC2 - --> $DIR/c-variadic-fail.rs:122:22 +error[E0080]: using ALLOC2 as variable argument list pointer but it does not point to a variable argument list + --> $DIR/c-variadic-fail.rs:123:22 | LL | const { unsafe { helper(1, 2, 3) } }; | ^^^^^^^^^^^^^^^ evaluation of `manual_copy_forget::{constant#0}` failed inside this call | note: inside `manual_copy_forget::helper` - --> $DIR/c-variadic-fail.rs:119:9 + --> $DIR/c-variadic-fail.rs:120:9 | LL | drop(ap); | ^^^^^^^^ @@ -278,27 +278,27 @@ note: inside ` as Drop>::drop` --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:122:5 + --> $DIR/c-variadic-fail.rs:123:5 | LL | const { unsafe { helper(1, 2, 3) } }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:122:5 + --> $DIR/c-variadic-fail.rs:123:5 | LL | const { unsafe { helper(1, 2, 3) } }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` -error[E0080]: va_arg on unknown va_list allocation ALLOC3 - --> $DIR/c-variadic-fail.rs:135:22 +error[E0080]: using ALLOC3 as variable argument list pointer but it does not point to a variable argument list + --> $DIR/c-variadic-fail.rs:136:22 | LL | const { unsafe { helper(1, 2, 3) } }; | ^^^^^^^^^^^^^^^ evaluation of `manual_copy_read::{constant#0}` failed inside this call | note: inside `manual_copy_read::helper` - --> $DIR/c-variadic-fail.rs:132:17 + --> $DIR/c-variadic-fail.rs:133:17 | LL | let _ = ap.arg::(); | ^^^^^^^^^^^^^^^ @@ -306,19 +306,52 @@ note: inside `VaList::<'_>::arg::` --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:135:5 + --> $DIR/c-variadic-fail.rs:136:5 | LL | const { unsafe { helper(1, 2, 3) } }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:135:5 + --> $DIR/c-variadic-fail.rs:136:5 | LL | const { unsafe { helper(1, 2, 3) } }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` -error: aborting due to 11 previous errors +error[E0080]: pointer not dereferenceable: pointer must point to some allocation, but got 0xdeadbeef[noalloc] which is a dangling pointer (it has no provenance) + --> $DIR/c-variadic-fail.rs:145:5 + | +LL | } + | ^ evaluation of `drop_of_invalid::{constant#0}` failed inside this call + | +note: inside `drop_in_place::> - shim(Some(VaList<'_>))` + --> $SRC_DIR/core/src/ptr/mod.rs:LL:COL +note: inside ` as Drop>::drop` + --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL + +note: erroneous constant encountered + --> $DIR/c-variadic-fail.rs:141:5 + | +LL | / const { +LL | | let mut invalid: MaybeUninit = MaybeUninit::zeroed(); +LL | | unsafe { invalid.as_mut_ptr().cast::().write(0xdeadbeef) }; +LL | | let ap = unsafe { invalid.assume_init() }; +LL | | } + | |_____^ + +note: erroneous constant encountered + --> $DIR/c-variadic-fail.rs:141:5 + | +LL | / const { +LL | | let mut invalid: MaybeUninit = MaybeUninit::zeroed(); +LL | | unsafe { invalid.as_mut_ptr().cast::().write(0xdeadbeef) }; +LL | | let ap = unsafe { invalid.assume_init() }; +LL | | } + | |_____^ + | + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error: aborting due to 12 previous errors For more information about this error, try `rustc --explain E0080`. From b74771fed1d3240227edbc4f06b2f28ff0a10899 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Sat, 24 Jan 2026 16:17:44 +0100 Subject: [PATCH 20/40] `fn addr_from_alloc_id_uncached`: make va_list emit a dummy_alloc --- src/tools/miri/src/alloc_addresses/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tools/miri/src/alloc_addresses/mod.rs b/src/tools/miri/src/alloc_addresses/mod.rs index 59799ac613b81..37a5108b69bae 100644 --- a/src/tools/miri/src/alloc_addresses/mod.rs +++ b/src/tools/miri/src/alloc_addresses/mod.rs @@ -184,8 +184,8 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { } #[cfg(not(all(unix, feature = "native-lib")))] AllocKind::Function => dummy_alloc(params), - AllocKind::VTable => dummy_alloc(params), - AllocKind::TypeId | AllocKind::Dead | AllocKind::VaList => unreachable!(), + AllocKind::VTable | AllocKind::VaList => dummy_alloc(params), + AllocKind::TypeId | AllocKind::Dead => unreachable!(), }; // We don't have to expose this pointer yet, we do that in `prepare_for_native_call`. return interp_ok(base_ptr.addr().to_u64()); From 4d68068c3326ed7052a192c8191cf3dcab422980 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Sat, 24 Jan 2026 16:41:56 +0100 Subject: [PATCH 21/40] use places smarter --- .../rustc_const_eval/src/interpret/call.rs | 13 +-- .../src/interpret/intrinsics.rs | 79 +++++-------------- .../rustc_const_eval/src/interpret/mod.rs | 1 + .../rustc_const_eval/src/interpret/va_list.rs | 20 +++++ src/tools/miri/tests/pass/c-variadic.rs | 6 +- tests/ui/consts/const-eval/c-variadic-fail.rs | 3 +- .../consts/const-eval/c-variadic-fail.stderr | 6 +- 7 files changed, 50 insertions(+), 78 deletions(-) create mode 100644 compiler/rustc_const_eval/src/interpret/va_list.rs diff --git a/compiler/rustc_const_eval/src/interpret/call.rs b/compiler/rustc_const_eval/src/interpret/call.rs index 83d86c6448252..5fd78e9fc2cae 100644 --- a/compiler/rustc_const_eval/src/interpret/call.rs +++ b/compiler/rustc_const_eval/src/interpret/call.rs @@ -4,7 +4,7 @@ use std::borrow::Cow; use either::{Left, Right}; -use rustc_abi::{self as abi, ExternAbi, FieldIdx, HasDataLayout, Integer, Size, VariantIdx}; +use rustc_abi::{self as abi, ExternAbi, FieldIdx, Integer, VariantIdx}; use rustc_data_structures::assert_matches; use rustc_errors::inline_fluent; use rustc_hir::def_id::DefId; @@ -489,8 +489,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 = varargs.clone(); - let ptr = self.va_list_insert(varargs); - let addr = Scalar::from_pointer(ptr, self); + let key = self.va_list_insert(varargs); // Zero the mplace, so it is fully initialized. self.write_bytes_ptr( @@ -498,12 +497,8 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { (0..mplace.layout.size.bytes()).map(|_| 0u8), )?; - // Store the pointer to the global variable arguments list allocation in the - // first bytes of the `VaList` value. - let mut alloc = self - .get_ptr_alloc_mut(mplace.ptr(), self.data_layout().pointer_size())? - .expect("not a ZST"); - alloc.write_ptr_sized(Size::ZERO, addr)?; + let key_mplace = self.va_list_key_mplace(&mplace)?; + self.write_pointer(key, &key_mplace)?; } else if Some(local) == body.spread_arg { // Make the local live once, then fill in the value field by field. self.storage_live(local)?; diff --git a/compiler/rustc_const_eval/src/interpret/intrinsics.rs b/compiler/rustc_const_eval/src/interpret/intrinsics.rs index fe2ecab906921..32b24d8f229bd 100644 --- a/compiler/rustc_const_eval/src/interpret/intrinsics.rs +++ b/compiler/rustc_const_eval/src/interpret/intrinsics.rs @@ -744,85 +744,46 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { } sym::va_copy => { - // fn va_copy<'f>(src: &VaList<'f>) -> VaList<'f> - let src_ptr = self.read_pointer(&args[0])?; - - // 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 va_list = self.deref_pointer(&args[0])?; + let key_mplace = self.va_list_key_mplace(&va_list)?; + let key = self.read_pointer(&key_mplace)?; - let copy_va_list_ptr = self.va_list_copy(src_va_list_ptr)?; - let addr = Scalar::from_pointer(copy_va_list_ptr, self); + let copy_key = self.va_list_copy(key)?; - // Now overwrite the token pointer stored inside the VaList. - let mplace = self.force_allocation(dest)?; - let mut alloc = self.get_place_alloc_mut(&mplace)?.unwrap(); - alloc.write_ptr_sized(Size::ZERO, addr)?; + let dest_mplace = self.force_allocation(dest)?; + let copy_key_mplace = self.va_list_key_mplace(&dest_mplace)?; + self.write_pointer(copy_key, ©_key_mplace)?; } 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 va_list = self.deref_pointer(&args[0])?; + let key_mplace = self.va_list_key_mplace(&va_list)?; + let key = self.read_pointer(&key_mplace)?; - self.va_list_remove(va_list_ptr)?; + self.va_list_remove(key)?; } 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 va_list = self.deref_pointer(&args[0])?; + let key_mplace = self.va_list_key_mplace(&va_list)?; + let key = self.read_pointer(&key_mplace)?; - let (src_mplace, new_va_list_ptr) = self.va_list_read_arg(va_list_ptr)?; - - let addr = Scalar::from_pointer(new_va_list_ptr, self); - let mut alloc = self - .get_ptr_alloc_mut(ap_ref, ptr_size)? - .expect("va_list storage should not be a ZST"); - alloc.write_ptr_sized(Size::ZERO, addr)?; + let (arg_mplace, new_key) = self.va_list_read_arg(key)?; // NOTE: In C some type conversions are allowed (e.g. casting between signed and // unsigned integers). For now we require c-variadic arguments to be read with the // exact type they were passed as. - if src_mplace.layout.ty != dest.layout.ty { + if arg_mplace.layout.ty != dest.layout.ty { throw_unsup_format!( "va_arg type mismatch: requested `{}`, but next argument is `{}`", dest.layout.ty, - src_mplace.layout.ty + arg_mplace.layout.ty ); } - self.copy_op(&src_mplace, dest)?; + self.write_pointer(new_key, &key_mplace)?; + + self.copy_op(&arg_mplace, dest)?; } // Unsupported intrinsic: skip the return_to_block below. diff --git a/compiler/rustc_const_eval/src/interpret/mod.rs b/compiler/rustc_const_eval/src/interpret/mod.rs index 2f365ec77b33e..1c5e77259146f 100644 --- a/compiler/rustc_const_eval/src/interpret/mod.rs +++ b/compiler/rustc_const_eval/src/interpret/mod.rs @@ -16,6 +16,7 @@ mod stack; mod step; mod traits; mod util; +mod va_list; mod validity; mod visitor; diff --git a/compiler/rustc_const_eval/src/interpret/va_list.rs b/compiler/rustc_const_eval/src/interpret/va_list.rs new file mode 100644 index 0000000000000..d66e50620a830 --- /dev/null +++ b/compiler/rustc_const_eval/src/interpret/va_list.rs @@ -0,0 +1,20 @@ +use rustc_abi::FieldIdx; + +use crate::interpret::{InterpCx, InterpResult, Machine, Projectable}; + +impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { + fn va_list_key_index(&self) -> FieldIdx { + // FIXME: this is target-dependent. + FieldIdx::from_usize(2) + } + + /// Get the MPlace of the key from the place storing the VaList. + pub(super) fn va_list_key_mplace>( + &self, + va_list: &P, + ) -> InterpResult<'tcx, P> { + let va_list_inner = self.project_field(va_list, FieldIdx::ZERO)?; + let field_idx = self.va_list_key_index(); + self.project_field(&va_list_inner, field_idx) + } +} diff --git a/src/tools/miri/tests/pass/c-variadic.rs b/src/tools/miri/tests/pass/c-variadic.rs index a1f03db6872fa..a188a2c06cb71 100644 --- a/src/tools/miri/tests/pass/c-variadic.rs +++ b/src/tools/miri/tests/pass/c-variadic.rs @@ -2,10 +2,8 @@ use core::ffi::VaList; -fn helper(ap: VaList) -> i32 { - // unsafe { ap.arg::() } - let _ = ap; - 0 +fn helper(mut ap: VaList) -> i32 { + unsafe { ap.arg::() } } unsafe extern "C" fn variadic(a: i32, ap: ...) -> i32 { diff --git a/tests/ui/consts/const-eval/c-variadic-fail.rs b/tests/ui/consts/const-eval/c-variadic-fail.rs index ddc4aafe11479..0c957d6ba02f8 100644 --- a/tests/ui/consts/const-eval/c-variadic-fail.rs +++ b/tests/ui/consts/const-eval/c-variadic-fail.rs @@ -140,10 +140,9 @@ fn manual_copy_read() { fn drop_of_invalid() { const { let mut invalid: MaybeUninit = MaybeUninit::zeroed(); - unsafe { invalid.as_mut_ptr().cast::().write(0xdeadbeef) }; let ap = unsafe { invalid.assume_init() }; } - //~^ ERROR pointer not dereferenceable: pointer must point to some allocation, but got 0xdeadbeef[noalloc] + //~^ ERROR pointer not dereferenceable: pointer must point to some allocation, but got null pointer [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 2625aee763cd3..b42b7ed4b32cf 100644 --- a/tests/ui/consts/const-eval/c-variadic-fail.stderr +++ b/tests/ui/consts/const-eval/c-variadic-fail.stderr @@ -319,8 +319,8 @@ LL | const { unsafe { helper(1, 2, 3) } }; | = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` -error[E0080]: pointer not dereferenceable: pointer must point to some allocation, but got 0xdeadbeef[noalloc] which is a dangling pointer (it has no provenance) - --> $DIR/c-variadic-fail.rs:145:5 +error[E0080]: pointer not dereferenceable: pointer must point to some allocation, but got null pointer + --> $DIR/c-variadic-fail.rs:144:5 | LL | } | ^ evaluation of `drop_of_invalid::{constant#0}` failed inside this call @@ -335,7 +335,6 @@ note: erroneous constant encountered | LL | / const { LL | | let mut invalid: MaybeUninit = MaybeUninit::zeroed(); -LL | | unsafe { invalid.as_mut_ptr().cast::().write(0xdeadbeef) }; LL | | let ap = unsafe { invalid.assume_init() }; LL | | } | |_____^ @@ -345,7 +344,6 @@ note: erroneous constant encountered | LL | / const { LL | | let mut invalid: MaybeUninit = MaybeUninit::zeroed(); -LL | | unsafe { invalid.as_mut_ptr().cast::().write(0xdeadbeef) }; LL | | let ap = unsafe { invalid.assume_init() }; LL | | } | |_____^ From ba84fa405dd87da85d30ce7a4f6c22d525f45acc Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Sat, 24 Jan 2026 18:23:45 +0100 Subject: [PATCH 22/40] move things around --- .../rustc_const_eval/src/interpret/call.rs | 3 +- .../src/interpret/intrinsics.rs | 52 +++++++++++-- .../rustc_const_eval/src/interpret/memory.rs | 75 ++++++------------- .../rustc_const_eval/src/interpret/mod.rs | 1 - .../rustc_const_eval/src/interpret/va_list.rs | 20 ----- 5 files changed, 70 insertions(+), 81 deletions(-) delete mode 100644 compiler/rustc_const_eval/src/interpret/va_list.rs diff --git a/compiler/rustc_const_eval/src/interpret/call.rs b/compiler/rustc_const_eval/src/interpret/call.rs index 5fd78e9fc2cae..522fe9de6d642 100644 --- a/compiler/rustc_const_eval/src/interpret/call.rs +++ b/compiler/rustc_const_eval/src/interpret/call.rs @@ -489,8 +489,6 @@ 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(); - let key = self.va_list_insert(varargs); - // Zero the mplace, so it is fully initialized. self.write_bytes_ptr( mplace.ptr(), @@ -498,6 +496,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { )?; let key_mplace = self.va_list_key_mplace(&mplace)?; + let key = self.va_list(varargs); self.write_pointer(key, &key_mplace)?; } else if Some(local) == body.spread_arg { // Make the local live once, then fill in the value field by field. diff --git a/compiler/rustc_const_eval/src/interpret/intrinsics.rs b/compiler/rustc_const_eval/src/interpret/intrinsics.rs index 32b24d8f229bd..cb62001a932c3 100644 --- a/compiler/rustc_const_eval/src/interpret/intrinsics.rs +++ b/compiler/rustc_const_eval/src/interpret/intrinsics.rs @@ -8,6 +8,7 @@ use rustc_abi::{FIRST_VARIANT, FieldIdx, HasDataLayout, Size, VariantIdx}; use rustc_apfloat::ieee::{Double, Half, Quad, Single}; use rustc_data_structures::assert_matches; use rustc_errors::inline_fluent; +use rustc_hir::LangItem; use rustc_hir::def_id::CRATE_DEF_ID; use rustc_infer::infer::TyCtxtInferExt; use rustc_middle::mir::interpret::{CTFE_ALLOC_SALT, read_target_uint, write_target_uint}; @@ -23,8 +24,8 @@ use super::memory::MemoryKind; use super::util::ensure_monomorphic_enough; use super::{ AllocId, CheckInAllocMsg, ImmTy, InterpCx, InterpResult, Machine, OpTy, PlaceTy, Pointer, - PointerArithmetic, Provenance, Scalar, err_ub_custom, err_unsup_format, interp_ok, throw_inval, - throw_ub_custom, throw_ub_format, throw_unsup_format, + PointerArithmetic, Projectable, Provenance, Scalar, err_ub_custom, err_unsup_format, interp_ok, + throw_inval, throw_ub, throw_ub_custom, throw_ub_format, throw_unsup_format, }; use crate::interpret::Writeable; @@ -748,7 +749,8 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { let key_mplace = self.va_list_key_mplace(&va_list)?; let key = self.read_pointer(&key_mplace)?; - let copy_key = self.va_list_copy(key)?; + let varargs = self.get_ptr_va_list(key)?; + let copy_key = self.va_list(varargs.to_vec()); let dest_mplace = self.force_allocation(dest)?; let copy_key_mplace = self.va_list_key_mplace(&dest_mplace)?; @@ -760,7 +762,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { let key_mplace = self.va_list_key_mplace(&va_list)?; let key = self.read_pointer(&key_mplace)?; - self.va_list_remove(key)?; + self.deallocate_va_list(key)?; } sym::va_arg => { @@ -768,7 +770,13 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { let key_mplace = self.va_list_key_mplace(&va_list)?; let key = self.read_pointer(&key_mplace)?; - let (arg_mplace, new_key) = self.va_list_read_arg(key)?; + let mut varargs = self.deallocate_va_list(key)?; + + if varargs.is_empty() { + throw_ub!(VaArgOutOfBounds) + } + + let arg_mplace = varargs.remove(0); // 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 @@ -781,6 +789,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { ); } + let new_key = self.va_list(varargs); self.write_pointer(new_key, &key_mplace)?; self.copy_op(&arg_mplace, dest)?; @@ -1269,4 +1278,37 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { interp_ok(Some(ImmTy::from_scalar(val, cast_to))) } } + + fn va_list_key_index(&self) -> FieldIdx { + let def_id = self.tcx.lang_items().get(LangItem::VaList).unwrap(); + + // VaList is a transparent wrapper around a struct. + let va_list_ty = self.tcx.type_of(def_id).instantiate_identity(); + let layout = self.layout_of(va_list_ty).unwrap(); + let (_, inner) = layout.non_1zst_field(self).unwrap(); + + // Find the first pointer field in this struct. The exact index is target-specific. + let ty::Adt(adt, _substs) = inner.ty.kind() else { + bug!("invalid VaListImpl layout"); + }; + + for (i, field) in adt.non_enum_variant().fields.iter().enumerate() { + let field_ty = self.tcx.type_of(field.did); + if field_ty.skip_binder().is_raw_ptr() { + return FieldIdx::from_usize(i); + } + } + + bug!("no VaListImpl field is a pointer"); + } + + /// Get the MPlace of the key from the place storing the VaList. + pub(super) fn va_list_key_mplace>( + &self, + va_list: &P, + ) -> InterpResult<'tcx, P> { + let va_list_inner = self.project_field(va_list, FieldIdx::ZERO)?; + let field_idx = self.va_list_key_index(); + self.project_field(&va_list_inner, field_idx) + } } diff --git a/compiler/rustc_const_eval/src/interpret/memory.rs b/compiler/rustc_const_eval/src/interpret/memory.rs index 964d397c4d770..ad77b7cef581f 100644 --- a/compiler/rustc_const_eval/src/interpret/memory.rs +++ b/compiler/rustc_const_eval/src/interpret/memory.rs @@ -237,6 +237,19 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { self.global_root_pointer(Pointer::from(id)).unwrap() } + /// Insert a new variable argument list in the global map of variable argument lists. + pub fn va_list( + &mut self, + varargs: Vec>, + ) -> Pointer { + let id = self.tcx.reserve_alloc_id(); + let old = self.memory.va_list_map.insert(id, varargs); + assert!(old.is_none()); + // 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(Pointer::from(id)).unwrap() + } + pub fn allocate_ptr( &mut self, size: Size, @@ -1086,22 +1099,11 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { .into() } - pub fn va_list_insert( - &mut self, - varargs: Vec>, - ) -> Pointer { - let id = self.tcx.reserve_alloc_id(); - let old = self.memory.va_list_map.insert(id, varargs); - assert!(old.is_none()); - // 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(Pointer::from(id)).unwrap() - } - - pub fn va_list_copy( - &mut self, + pub fn get_ptr_va_list( + &self, ptr: Pointer>, - ) -> InterpResult<'tcx, Pointer> { + ) -> InterpResult<'tcx, &[MPlaceTy<'tcx, M::Provenance>]> { + trace!("get_ptr_va_list({:?})", ptr); let (alloc_id, offset, _prov) = self.ptr_get_alloc_id(ptr, 0)?; if offset.bytes() != 0 { throw_ub!(InvalidVaListPointer(Pointer::new(alloc_id, offset))) @@ -1111,59 +1113,26 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { throw_ub!(InvalidVaListPointer(Pointer::new(alloc_id, offset))) }; - let alloc_id = self.tcx.reserve_alloc_id(); - let old = self.memory.va_list_map.insert(alloc_id, va_list.clone()); - assert!(old.is_none()); - - self.global_root_pointer(Pointer::from(alloc_id)) - } - - pub fn va_list_read_arg( - &mut self, - ptr: Pointer>, - ) -> InterpResult<'tcx, (MPlaceTy<'tcx, M::Provenance>, Pointer)> { - let (alloc_id, offset, _prov) = self.ptr_get_alloc_id(ptr, 0)?; - if offset.bytes() != 0 { - throw_ub!(InvalidVaListPointer(Pointer::new(alloc_id, offset))) - } - - let Some(mut va_list) = self.memory.va_list_map.swap_remove(&alloc_id) else { - throw_ub!(InvalidVaListPointer(Pointer::new(alloc_id, offset))) - }; - - if va_list.is_empty() { - throw_ub!(VaArgOutOfBounds) - } - - let arg = va_list.remove(0); - - let alloc_id = self.tcx.reserve_alloc_id(); - let old = self.memory.va_list_map.insert(alloc_id, va_list); - assert!(old.is_none()); - - let new_va_list_ptr = self.global_root_pointer(Pointer::from(alloc_id))?; - interp_ok((arg, new_va_list_ptr)) + interp_ok(va_list.as_slice()) } - pub fn va_list_remove( + /// Removes this VaList from the global map of variable argument lists. This does not deallocate + /// the VaList elements, that happens when the Frame is popped. + pub fn deallocate_va_list( &mut self, ptr: Pointer>, ) -> InterpResult<'tcx, Vec>> { + trace!("deallocate_va_list({:?})", ptr); let (alloc_id, offset, _prov) = self.ptr_get_alloc_id(ptr, 0)?; if offset.bytes() != 0 { throw_ub!(InvalidVaListPointer(Pointer::new(alloc_id, offset))) } - if offset.bytes() != 0 { - throw_ub!(InvalidVaListPointer(Pointer::new(alloc_id, offset))) - } - let Some(va_list) = self.memory.va_list_map.swap_remove(&alloc_id) else { throw_ub!(InvalidVaListPointer(Pointer::new(alloc_id, offset))) }; self.memory.dead_alloc_map.insert(alloc_id, (Size::ZERO, Align::ONE)); - interp_ok(va_list) } diff --git a/compiler/rustc_const_eval/src/interpret/mod.rs b/compiler/rustc_const_eval/src/interpret/mod.rs index 1c5e77259146f..2f365ec77b33e 100644 --- a/compiler/rustc_const_eval/src/interpret/mod.rs +++ b/compiler/rustc_const_eval/src/interpret/mod.rs @@ -16,7 +16,6 @@ mod stack; mod step; mod traits; mod util; -mod va_list; mod validity; mod visitor; diff --git a/compiler/rustc_const_eval/src/interpret/va_list.rs b/compiler/rustc_const_eval/src/interpret/va_list.rs deleted file mode 100644 index d66e50620a830..0000000000000 --- a/compiler/rustc_const_eval/src/interpret/va_list.rs +++ /dev/null @@ -1,20 +0,0 @@ -use rustc_abi::FieldIdx; - -use crate::interpret::{InterpCx, InterpResult, Machine, Projectable}; - -impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { - fn va_list_key_index(&self) -> FieldIdx { - // FIXME: this is target-dependent. - FieldIdx::from_usize(2) - } - - /// Get the MPlace of the key from the place storing the VaList. - pub(super) fn va_list_key_mplace>( - &self, - va_list: &P, - ) -> InterpResult<'tcx, P> { - let va_list_inner = self.project_field(va_list, FieldIdx::ZERO)?; - let field_idx = self.va_list_key_index(); - self.project_field(&va_list_inner, field_idx) - } -} From 2594aeae31a0bda500c53a2c1751f375bb5c0b38 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Sat, 24 Jan 2026 22:09:24 +0100 Subject: [PATCH 23/40] fix formatting --- src/tools/miri/src/alloc_addresses/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tools/miri/src/alloc_addresses/mod.rs b/src/tools/miri/src/alloc_addresses/mod.rs index 37a5108b69bae..32897eb89a83c 100644 --- a/src/tools/miri/src/alloc_addresses/mod.rs +++ b/src/tools/miri/src/alloc_addresses/mod.rs @@ -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 => { From 40ef87c8fc481ba0461a06a48b930d9cd564bad1 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Sat, 24 Jan 2026 22:48:37 +0100 Subject: [PATCH 24/40] rename to `self.va_list_ptr` --- compiler/rustc_const_eval/src/interpret/call.rs | 2 +- compiler/rustc_const_eval/src/interpret/intrinsics.rs | 4 ++-- compiler/rustc_const_eval/src/interpret/memory.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_const_eval/src/interpret/call.rs b/compiler/rustc_const_eval/src/interpret/call.rs index 522fe9de6d642..e875838dc2ffe 100644 --- a/compiler/rustc_const_eval/src/interpret/call.rs +++ b/compiler/rustc_const_eval/src/interpret/call.rs @@ -496,7 +496,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { )?; let key_mplace = self.va_list_key_mplace(&mplace)?; - let key = self.va_list(varargs); + let key = self.va_list_ptr(varargs); self.write_pointer(key, &key_mplace)?; } else if Some(local) == body.spread_arg { // Make the local live once, then fill in the value field by field. diff --git a/compiler/rustc_const_eval/src/interpret/intrinsics.rs b/compiler/rustc_const_eval/src/interpret/intrinsics.rs index cb62001a932c3..d34cfad36104d 100644 --- a/compiler/rustc_const_eval/src/interpret/intrinsics.rs +++ b/compiler/rustc_const_eval/src/interpret/intrinsics.rs @@ -750,7 +750,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { let key = self.read_pointer(&key_mplace)?; let varargs = self.get_ptr_va_list(key)?; - let copy_key = self.va_list(varargs.to_vec()); + let copy_key = self.va_list_ptr(varargs.to_vec()); let dest_mplace = self.force_allocation(dest)?; let copy_key_mplace = self.va_list_key_mplace(&dest_mplace)?; @@ -789,7 +789,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { ); } - let new_key = self.va_list(varargs); + let new_key = self.va_list_ptr(varargs); self.write_pointer(new_key, &key_mplace)?; self.copy_op(&arg_mplace, dest)?; diff --git a/compiler/rustc_const_eval/src/interpret/memory.rs b/compiler/rustc_const_eval/src/interpret/memory.rs index ad77b7cef581f..efc4ef0b0ae9c 100644 --- a/compiler/rustc_const_eval/src/interpret/memory.rs +++ b/compiler/rustc_const_eval/src/interpret/memory.rs @@ -238,7 +238,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { } /// Insert a new variable argument list in the global map of variable argument lists. - pub fn va_list( + pub fn va_list_ptr( &mut self, varargs: Vec>, ) -> Pointer { From b1c39ec5ab4f7fc4f96d7048e233c5e45a094c4d Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Sat, 24 Jan 2026 22:49:33 +0100 Subject: [PATCH 25/40] add more miri tests --- src/tools/miri/tests/fail/c-variadic.rs | 15 +++ src/tools/miri/tests/fail/c-variadic.stderr | 22 ++++ src/tools/miri/tests/pass/c-variadic.rs | 113 ++++++++++++++++++-- 3 files changed, 143 insertions(+), 7 deletions(-) create mode 100644 src/tools/miri/tests/fail/c-variadic.rs create mode 100644 src/tools/miri/tests/fail/c-variadic.stderr diff --git a/src/tools/miri/tests/fail/c-variadic.rs b/src/tools/miri/tests/fail/c-variadic.rs new file mode 100644 index 0000000000000..38141c23d08de --- /dev/null +++ b/src/tools/miri/tests/fail/c-variadic.rs @@ -0,0 +1,15 @@ +#![feature(c_variadic)] + +//@error-in-other-file: Undefined Behavior: more C-variadic arguments read than were passed + +fn read_too_many() { + unsafe extern "C" fn variadic(mut ap: ...) { + ap.arg::(); + } + + unsafe { variadic() }; +} + +fn main() { + read_too_many(); +} diff --git a/src/tools/miri/tests/fail/c-variadic.stderr b/src/tools/miri/tests/fail/c-variadic.stderr new file mode 100644 index 0000000000000..31695f1cab49b --- /dev/null +++ b/src/tools/miri/tests/fail/c-variadic.stderr @@ -0,0 +1,22 @@ +error: Undefined Behavior: more C-variadic arguments read than were passed + --> RUSTLIB/core/src/ffi/va_list.rs:LL:CC + | +LL | unsafe { va_arg(self) } + | ^^^^^^^^^^^^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: stack backtrace: + 0: std::ffi::VaList::<'_>::arg + at RUSTLIB/core/src/ffi/va_list.rs:LL:CC + 1: read_too_many::variadic + at tests/fail/c-variadic.rs:LL:CC + 2: read_too_many + at tests/fail/c-variadic.rs:LL:CC + 3: main + at tests/fail/c-variadic.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/pass/c-variadic.rs b/src/tools/miri/tests/pass/c-variadic.rs index a188a2c06cb71..8b353885699e6 100644 --- a/src/tools/miri/tests/pass/c-variadic.rs +++ b/src/tools/miri/tests/pass/c-variadic.rs @@ -1,16 +1,115 @@ #![feature(c_variadic)] -use core::ffi::VaList; +use std::ffi::{CStr, VaList, c_char, c_double, c_int, c_long}; -fn helper(mut ap: VaList) -> i32 { - unsafe { ap.arg::() } +fn ignores_arguments() { + unsafe extern "C" fn variadic(_: ...) {} + + unsafe { variadic() }; + unsafe { variadic(1, 2, 3) }; +} + +fn echo() { + unsafe extern "C" fn variadic(mut ap: ...) -> i32 { + ap.arg() + } + + assert_eq!(unsafe { variadic(1) }, 1); + assert_eq!(unsafe { variadic(3, 2, 1) }, 3); +} + +fn forward_by_val() { + unsafe fn helper(mut ap: VaList) -> i32 { + ap.arg() + } + + unsafe extern "C" fn variadic(ap: ...) -> i32 { + helper(ap) + } + + assert_eq!(unsafe { variadic(1) }, 1); + assert_eq!(unsafe { variadic(3, 2, 1) }, 3); +} + +fn forward_by_ref() { + unsafe fn helper(ap: &mut VaList) -> i32 { + ap.arg() + } + + unsafe extern "C" fn variadic(mut ap: ...) -> i32 { + helper(&mut ap) + } + + assert_eq!(unsafe { variadic(1) }, 1); + assert_eq!(unsafe { variadic(3, 2, 1) }, 3); } -unsafe extern "C" fn variadic(a: i32, ap: ...) -> i32 { - assert_eq!(a, 42); - helper(ap) +#[allow(improper_ctypes_definitions)] +fn nested() { + unsafe fn helper(mut ap1: VaList, mut ap2: VaList) -> (i32, i32) { + (ap1.arg(), ap2.arg()) + } + + unsafe extern "C" fn variadic2(ap1: VaList, ap2: ...) -> (i32, i32) { + helper(ap1, ap2) + } + + unsafe extern "C" fn variadic1(ap1: ...) -> (i32, i32) { + variadic2(ap1, 2, 2) + } + + assert_eq!(unsafe { variadic1(1) }, (1, 2)); + + let (a, b) = unsafe { variadic1(1, 1) }; + + assert_eq!(a, 1); + assert_eq!(b, 2); +} + +fn various_types() { + unsafe extern "C" fn check_list_2(mut ap: ...) { + macro_rules! continue_if { + ($cond:expr) => { + if !($cond) { + panic!(); + } + }; + } + + unsafe fn compare_c_str(ptr: *const c_char, val: &str) -> bool { + match CStr::from_ptr(ptr).to_str() { + Ok(cstr) => cstr == val, + Err(_) => panic!(), + } + } + + continue_if!(ap.arg::().floor() == 3.14f64.floor()); + continue_if!(ap.arg::() == 12); + continue_if!(ap.arg::() == 'a' as c_int); + continue_if!(ap.arg::().floor() == 6.18f64.floor()); + continue_if!(compare_c_str(ap.arg::<*const c_char>(), "Hello")); + continue_if!(ap.arg::() == 42); + continue_if!(compare_c_str(ap.arg::<*const c_char>(), "World")); + } + + unsafe { + check_list_2( + 3.14 as c_double, + 12 as c_long, + b'a' as c_int, + 6.28 as c_double, + c"Hello".as_ptr(), + 42 as c_int, + c"World".as_ptr(), + ); + } } fn main() { - assert_eq!(unsafe { variadic(42, 1) }, 1); + ignores_arguments(); + echo(); + forward_by_val(); + forward_by_ref(); + nested(); + various_types(); } From 342025b1b200e84281ee166ca4724b40ece2be2e Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Sun, 25 Jan 2026 21:19:47 +0100 Subject: [PATCH 26/40] Apply suggestion from @RalfJung Co-authored-by: Ralf Jung --- compiler/rustc_const_eval/src/interpret/intrinsics.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_const_eval/src/interpret/intrinsics.rs b/compiler/rustc_const_eval/src/interpret/intrinsics.rs index d34cfad36104d..87d890d3743cb 100644 --- a/compiler/rustc_const_eval/src/interpret/intrinsics.rs +++ b/compiler/rustc_const_eval/src/interpret/intrinsics.rs @@ -788,11 +788,12 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { arg_mplace.layout.ty ); } + // Copy the argument. + self.copy_op(&arg_mplace, dest)?; + // Update the VaList pointer. let new_key = self.va_list_ptr(varargs); self.write_pointer(new_key, &key_mplace)?; - - self.copy_op(&arg_mplace, dest)?; } // Unsupported intrinsic: skip the return_to_block below. From ee17907410b43081cd46d958fb8a35e46ae09e8d Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Sun, 25 Jan 2026 21:11:46 +0100 Subject: [PATCH 27/40] low-hanging fruit after review --- .../rustc_const_eval/src/interpret/call.rs | 29 +++++++++---------- .../src/interpret/intrinsics.rs | 13 +++++---- .../rustc_const_eval/src/interpret/stack.rs | 3 -- 3 files changed, 20 insertions(+), 25 deletions(-) diff --git a/compiler/rustc_const_eval/src/interpret/call.rs b/compiler/rustc_const_eval/src/interpret/call.rs index e875838dc2ffe..f835492494061 100644 --- a/compiler/rustc_const_eval/src/interpret/call.rs +++ b/compiler/rustc_const_eval/src/interpret/call.rs @@ -357,17 +357,17 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { 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(); + let extra_tys = + caller_fn_abi.args[fixed_count..].iter().map(|arg_abi| arg_abi.layout.ty); - (fixed_count, self.tcx.mk_type_list(&extra_tys)) + (fixed_count, self.tcx.mk_type_list_from_iter(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 { + if callee_fn_abi.c_variadic != caller_fn_abi.c_variadic { unreachable!("caller and callee disagree on being c-variadic"); } @@ -452,11 +452,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { // this is a single iterator (that handles `spread_arg`), then // `pass_argument` would be the loop body. It takes care to // not advance `caller_iter` for ignored arguments. - let mut callee_args_abis = if caller_fn_abi.c_variadic { - callee_fn_abi.args[..fixed_count].iter().enumerate() - } else { - callee_fn_abi.args.iter().enumerate() - }; + let mut callee_args_abis = callee_fn_abi.args[..fixed_count].iter().enumerate(); let mut it = body.args_iter().peekable(); while let Some(local) = it.next() { @@ -468,17 +464,17 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { // query *again* the next time this local is accessed. let ty = self.layout_of_local(self.frame(), local, None)?.ty; if caller_fn_abi.c_variadic && it.peek().is_none() { - // The callee's signature has an additional VaList argument, that the caller - // won't actually pass. Here we synthesize a `VaList` value, whose leading bytes - // are a pointer that can be mapped to the corresponding variable argument list. + // This is the last callee-side argument of a variadic function. + // This argument is a VaList holding the remaining caller-side arguments. self.storage_live(local)?; let place = self.eval_place(dest)?; let mplace = self.force_allocation(&place)?; - // Consume the remaining arguments and store them in a global allocation. + // Consume the remaining arguments and store them in fresh allocations. let mut varargs = Vec::new(); for (fn_arg, abi) in &mut caller_args { + // FIXME: do we have to worry about in-place argument passing? let op = self.copy_fn_arg(fn_arg); let mplace = self.allocate(abi.layout, MemoryKind::Stack)?; self.copy_op(&op, &mplace)?; @@ -486,16 +482,17 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { varargs.push(mplace); } - // When the frame is dropped, this ID is used to deallocate the variable arguments list. + // When the frame is dropped, these variable arguments are deallocated. self.frame_mut().va_list = varargs.clone(); - // Zero the mplace, so it is fully initialized. + // Zero the VaList, so it is fully initialized. self.write_bytes_ptr( mplace.ptr(), (0..mplace.layout.size.bytes()).map(|_| 0u8), )?; - let key_mplace = self.va_list_key_mplace(&mplace)?; + // Store the "key" pointer in the right field. + let key_mplace = self.va_list_key_field(&mplace)?; let key = self.va_list_ptr(varargs); self.write_pointer(key, &key_mplace)?; } else if Some(local) == body.spread_arg { diff --git a/compiler/rustc_const_eval/src/interpret/intrinsics.rs b/compiler/rustc_const_eval/src/interpret/intrinsics.rs index 87d890d3743cb..e74cf5f19c150 100644 --- a/compiler/rustc_const_eval/src/interpret/intrinsics.rs +++ b/compiler/rustc_const_eval/src/interpret/intrinsics.rs @@ -746,20 +746,19 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { sym::va_copy => { let va_list = self.deref_pointer(&args[0])?; - let key_mplace = self.va_list_key_mplace(&va_list)?; + let key_mplace = self.va_list_key_field(&va_list)?; let key = self.read_pointer(&key_mplace)?; let varargs = self.get_ptr_va_list(key)?; let copy_key = self.va_list_ptr(varargs.to_vec()); - let dest_mplace = self.force_allocation(dest)?; - let copy_key_mplace = self.va_list_key_mplace(&dest_mplace)?; + let copy_key_mplace = self.va_list_key_field(dest)?; self.write_pointer(copy_key, ©_key_mplace)?; } sym::va_end => { let va_list = self.deref_pointer(&args[0])?; - let key_mplace = self.va_list_key_mplace(&va_list)?; + let key_mplace = self.va_list_key_field(&va_list)?; let key = self.read_pointer(&key_mplace)?; self.deallocate_va_list(key)?; @@ -767,9 +766,11 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { sym::va_arg => { let va_list = self.deref_pointer(&args[0])?; - let key_mplace = self.va_list_key_mplace(&va_list)?; + let key_mplace = self.va_list_key_field(&va_list)?; let key = self.read_pointer(&key_mplace)?; + // Invalidate the old list and get its content. We'll recreate the + // new list (one element shorter) below. let mut varargs = self.deallocate_va_list(key)?; if varargs.is_empty() { @@ -1304,7 +1305,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { } /// Get the MPlace of the key from the place storing the VaList. - pub(super) fn va_list_key_mplace>( + pub(super) fn va_list_key_field>( &self, va_list: &P, ) -> InterpResult<'tcx, P> { diff --git a/compiler/rustc_const_eval/src/interpret/stack.rs b/compiler/rustc_const_eval/src/interpret/stack.rs index 9b2d99bb297f4..b97f9d45bd4c5 100644 --- a/compiler/rustc_const_eval/src/interpret/stack.rs +++ b/compiler/rustc_const_eval/src/interpret/stack.rs @@ -455,7 +455,6 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { }; let return_action = if cleanup { - // We need to take the locals out, since we need to mutate while iterating. for local in &frame.locals { self.deallocate_local(local.value)?; } @@ -613,11 +612,9 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { 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()) ); From 72ced4217e5d02f7d7a0813cf7ce679e24802960 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Sun, 25 Jan 2026 23:53:17 +0100 Subject: [PATCH 28/40] consider the caller location argument --- compiler/rustc_const_eval/src/interpret/call.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_const_eval/src/interpret/call.rs b/compiler/rustc_const_eval/src/interpret/call.rs index f835492494061..2a7b5415ac9fc 100644 --- a/compiler/rustc_const_eval/src/interpret/call.rs +++ b/compiler/rustc_const_eval/src/interpret/call.rs @@ -452,7 +452,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[..fixed_count].iter().enumerate(); + let mut callee_args_abis = if caller_fn_abi.c_variadic { + // Only the fixed arguments are passed normally. + callee_fn_abi.args[..fixed_count].iter().enumerate() + } else { + // NOTE: this handles the extra caller location argument + // when `#[track_caller]` is used. + callee_fn_abi.args.iter().enumerate() + }; let mut it = body.args_iter().peekable(); while let Some(local) = it.next() { From 930527c98444e92e22e2a3073c544fb10abeec3f Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Sun, 25 Jan 2026 23:58:31 +0100 Subject: [PATCH 29/40] use `transmute_copy` --- tests/ui/consts/const-eval/c-variadic-fail.rs | 22 ++++---------- .../consts/const-eval/c-variadic-fail.stderr | 30 +++++++++---------- 2 files changed, 21 insertions(+), 31 deletions(-) diff --git a/tests/ui/consts/const-eval/c-variadic-fail.rs b/tests/ui/consts/const-eval/c-variadic-fail.rs index 0c957d6ba02f8..859dfed35719f 100644 --- a/tests/ui/consts/const-eval/c-variadic-fail.rs +++ b/tests/ui/consts/const-eval/c-variadic-fail.rs @@ -78,23 +78,13 @@ fn use_after_free() { }; } -macro_rules! va_list_copy { - ($ap:expr) => {{ +fn manual_copy_drop() { + 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(); + let mut copy = ap.clone(); assert!(copy.arg::() == 1i32); - let mut u = core::mem::MaybeUninit::uninit(); - unsafe { core::ptr::copy_nonoverlapping(&$ap, u.as_mut_ptr(), 1) }; - - // Manually creating the copy is fine. - unsafe { u.assume_init() } - }}; -} - -fn manual_copy_drop() { - const unsafe extern "C" fn helper(ap: ...) { - let mut copy: VaList = va_list_copy!(ap); + let mut copy: VaList = unsafe { std::mem::transmute_copy(&ap) }; // Using the copy is actually fine. let _ = copy.arg::(); @@ -110,7 +100,7 @@ fn manual_copy_drop() { fn manual_copy_forget() { const unsafe extern "C" fn helper(ap: ...) { - let mut copy: VaList = va_list_copy!(ap); + let mut copy: VaList = unsafe { std::mem::transmute_copy(&ap) }; // Using the copy is actually fine. let _ = copy.arg::(); @@ -126,7 +116,7 @@ fn manual_copy_forget() { fn manual_copy_read() { const unsafe extern "C" fn helper(mut ap: ...) { - let mut copy: VaList = va_list_copy!(ap); + let mut copy: VaList = unsafe { std::mem::transmute_copy(&ap) }; // Reading from `ap` after reading from `copy` is UB. let _ = copy.arg::(); diff --git a/tests/ui/consts/const-eval/c-variadic-fail.stderr b/tests/ui/consts/const-eval/c-variadic-fail.stderr index b42b7ed4b32cf..14da5500cb1b6 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]: using ALLOC1 as variable argument list pointer but it does not point to a variable argument list - --> $DIR/c-variadic-fail.rs:107:22 + --> $DIR/c-variadic-fail.rs:97:22 | LL | const { unsafe { helper(1, 2, 3) } }; | ^^^^^^^^^^^^^^^ evaluation of `manual_copy_drop::{constant#0}` failed inside this call | note: inside `manual_copy_drop::helper` - --> $DIR/c-variadic-fail.rs:104:9 + --> $DIR/c-variadic-fail.rs:94:9 | LL | drop(ap); | ^^^^^^^^ @@ -246,13 +246,13 @@ note: inside ` as Drop>::drop` --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:107:5 + --> $DIR/c-variadic-fail.rs:97:5 | LL | const { unsafe { helper(1, 2, 3) } }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:107:5 + --> $DIR/c-variadic-fail.rs:97:5 | LL | const { unsafe { helper(1, 2, 3) } }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -260,13 +260,13 @@ LL | const { unsafe { helper(1, 2, 3) } }; = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` error[E0080]: using ALLOC2 as variable argument list pointer but it does not point to a variable argument list - --> $DIR/c-variadic-fail.rs:123:22 + --> $DIR/c-variadic-fail.rs:113:22 | LL | const { unsafe { helper(1, 2, 3) } }; | ^^^^^^^^^^^^^^^ evaluation of `manual_copy_forget::{constant#0}` failed inside this call | note: inside `manual_copy_forget::helper` - --> $DIR/c-variadic-fail.rs:120:9 + --> $DIR/c-variadic-fail.rs:110:9 | LL | drop(ap); | ^^^^^^^^ @@ -278,13 +278,13 @@ note: inside ` as Drop>::drop` --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:123:5 + --> $DIR/c-variadic-fail.rs:113:5 | LL | const { unsafe { helper(1, 2, 3) } }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:123:5 + --> $DIR/c-variadic-fail.rs:113:5 | LL | const { unsafe { helper(1, 2, 3) } }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -292,13 +292,13 @@ LL | const { unsafe { helper(1, 2, 3) } }; = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` error[E0080]: using ALLOC3 as variable argument list pointer but it does not point to a variable argument list - --> $DIR/c-variadic-fail.rs:136:22 + --> $DIR/c-variadic-fail.rs:126:22 | LL | const { unsafe { helper(1, 2, 3) } }; | ^^^^^^^^^^^^^^^ evaluation of `manual_copy_read::{constant#0}` failed inside this call | note: inside `manual_copy_read::helper` - --> $DIR/c-variadic-fail.rs:133:17 + --> $DIR/c-variadic-fail.rs:123:17 | LL | let _ = ap.arg::(); | ^^^^^^^^^^^^^^^ @@ -306,13 +306,13 @@ note: inside `VaList::<'_>::arg::` --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:136:5 + --> $DIR/c-variadic-fail.rs:126:5 | LL | const { unsafe { helper(1, 2, 3) } }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:136:5 + --> $DIR/c-variadic-fail.rs:126:5 | LL | const { unsafe { helper(1, 2, 3) } }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -320,7 +320,7 @@ LL | const { unsafe { helper(1, 2, 3) } }; = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` error[E0080]: pointer not dereferenceable: pointer must point to some allocation, but got null pointer - --> $DIR/c-variadic-fail.rs:144:5 + --> $DIR/c-variadic-fail.rs:134:5 | LL | } | ^ evaluation of `drop_of_invalid::{constant#0}` failed inside this call @@ -331,7 +331,7 @@ note: inside ` as Drop>::drop` --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:141:5 + --> $DIR/c-variadic-fail.rs:131:5 | LL | / const { LL | | let mut invalid: MaybeUninit = MaybeUninit::zeroed(); @@ -340,7 +340,7 @@ LL | | } | |_____^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:141:5 + --> $DIR/c-variadic-fail.rs:131:5 | LL | / const { LL | | let mut invalid: MaybeUninit = MaybeUninit::zeroed(); From 6ac782e8bf0192b6d81f6a22d23cd4cd7922eac7 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Mon, 26 Jan 2026 00:22:56 +0100 Subject: [PATCH 30/40] split out `allocate_varargs` --- .../rustc_const_eval/src/interpret/call.rs | 24 ++----- .../rustc_const_eval/src/interpret/stack.rs | 66 ++++++++++++++----- 2 files changed, 55 insertions(+), 35 deletions(-) diff --git a/compiler/rustc_const_eval/src/interpret/call.rs b/compiler/rustc_const_eval/src/interpret/call.rs index 2a7b5415ac9fc..66617a8a29d08 100644 --- a/compiler/rustc_const_eval/src/interpret/call.rs +++ b/compiler/rustc_const_eval/src/interpret/call.rs @@ -17,9 +17,9 @@ use tracing::field::Empty; use tracing::{info, instrument, trace}; use super::{ - CtfeProvenance, FnVal, ImmTy, InterpCx, InterpResult, MPlaceTy, Machine, MemoryKind, OpTy, - PlaceTy, Projectable, Provenance, ReturnAction, ReturnContinuation, Scalar, StackPopInfo, - interp_ok, throw_ub, throw_ub_custom, + CtfeProvenance, FnVal, ImmTy, InterpCx, InterpResult, MPlaceTy, Machine, OpTy, PlaceTy, + Projectable, Provenance, ReturnAction, ReturnContinuation, Scalar, StackPopInfo, interp_ok, + throw_ub, throw_ub_custom, }; use crate::enter_trace_span; use crate::interpret::EnteredTraceSpan; @@ -478,19 +478,10 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { let place = self.eval_place(dest)?; let mplace = self.force_allocation(&place)?; - // Consume the remaining arguments and store them in fresh allocations. - let mut varargs = Vec::new(); - for (fn_arg, abi) in &mut caller_args { - // FIXME: do we have to worry about in-place argument passing? - 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, these variable arguments are deallocated. - self.frame_mut().va_list = varargs.clone(); + // Consume the remaining arguments by putting them into the variable argument + // list. + let varargs = self.allocate_varargs(&mut caller_args)?; + let key = self.va_list_ptr(varargs); // Zero the VaList, so it is fully initialized. self.write_bytes_ptr( @@ -500,7 +491,6 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { // Store the "key" pointer in the right field. let key_mplace = self.va_list_key_field(&mplace)?; - let key = self.va_list_ptr(varargs); self.write_pointer(key, &key_mplace)?; } else if Some(local) == body.spread_arg { // Make the local live once, then fill in the value field by field. diff --git a/compiler/rustc_const_eval/src/interpret/stack.rs b/compiler/rustc_const_eval/src/interpret/stack.rs index b97f9d45bd4c5..7b8dfbcc8ca0e 100644 --- a/compiler/rustc_const_eval/src/interpret/stack.rs +++ b/compiler/rustc_const_eval/src/interpret/stack.rs @@ -12,11 +12,12 @@ use rustc_middle::ty::{self, Ty, TyCtxt}; use rustc_middle::{bug, mir}; use rustc_mir_dataflow::impls::always_storage_live_locals; use rustc_span::Span; +use rustc_target::callconv::ArgAbi; use tracing::field::Empty; use tracing::{info_span, instrument, trace}; use super::{ - AllocId, CtfeProvenance, Immediate, InterpCx, InterpResult, MPlaceTy, Machine, MemPlace, + AllocId, CtfeProvenance, FnArg, Immediate, InterpCx, InterpResult, MPlaceTy, Machine, MemPlace, MemPlaceMeta, MemoryKind, Operand, PlaceTy, Pointer, Provenance, ReturnAction, Scalar, from_known_layout, interp_ok, throw_ub, throw_unsup, }; @@ -460,9 +461,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { } // Deallocate any c-variadic arguments. - for mplace in &frame.va_list { - self.deallocate_vararg(mplace)?; - } + self.deallocate_varargs(&frame.va_list)?; // Call the machine hook, which determines the next steps. let return_action = M::after_stack_pop(self, frame, unwinding)?; @@ -609,20 +608,6 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { interp_ok(()) } - fn deallocate_vararg(&mut self, vararg: &MPlaceTy<'tcx, M::Provenance>) -> InterpResult<'tcx> { - let ptr = vararg.ptr(); - - trace!( - "deallocating vararg {:?}: {:?}", - vararg, - // Locals always have a `alloc_id` (they are never the result of a int2ptr). - self.dump_alloc(ptr.provenance.unwrap().get_alloc_id().unwrap()) - ); - self.deallocate_ptr(ptr, None, MemoryKind::Stack)?; - - interp_ok(()) - } - /// 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)] @@ -650,6 +635,51 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { } } +impl<'a, 'tcx: 'a, M: Machine<'tcx>> InterpCx<'tcx, M> { + pub(crate) fn allocate_varargs( + &mut self, + caller_args: &mut I, + ) -> InterpResult<'tcx, Vec>> + where + I: Iterator, &'a ArgAbi<'tcx, Ty<'tcx>>)>, + { + // Consume the remaining arguments and store them in fresh allocations. + let mut varargs = Vec::new(); + for (fn_arg, abi) in caller_args { + // FIXME: do we have to worry about in-place argument passing? + 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, these variable arguments are deallocated. + self.frame_mut().va_list = varargs.clone(); + + interp_ok(varargs) + } + + fn deallocate_varargs( + &mut self, + varargs: &[MPlaceTy<'tcx, M::Provenance>], + ) -> InterpResult<'tcx> { + for vararg in varargs { + let ptr = vararg.ptr(); + + trace!( + "deallocating vararg {:?}: {:?}", + vararg, + // Locals always have a `alloc_id` (they are never the result of a int2ptr). + self.dump_alloc(ptr.provenance.unwrap().get_alloc_id().unwrap()) + ); + self.deallocate_ptr(ptr, None, MemoryKind::Stack)?; + } + + interp_ok(()) + } +} + impl<'tcx, Prov: Provenance> LocalState<'tcx, Prov> { pub(super) fn print( &self, From 0fa0719ea9f3ad72243c3a5a4be1b04bc4091643 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Wed, 28 Jan 2026 18:45:45 +0100 Subject: [PATCH 31/40] report UB when caller and callee signatures do not match --- compiler/rustc_const_eval/messages.ftl | 6 +++ compiler/rustc_const_eval/src/errors.rs | 10 ++++ .../rustc_const_eval/src/interpret/call.rs | 51 +++++++++++++------ .../rustc_middle/src/mir/interpret/error.rs | 4 ++ .../tests/fail/c-variadic-mismatch-count.rs | 13 +++++ .../fail/c-variadic-mismatch-count.stderr | 13 +++++ .../miri/tests/fail/c-variadic-mismatch.rs | 13 +++++ .../tests/fail/c-variadic-mismatch.stderr | 13 +++++ 8 files changed, 107 insertions(+), 16 deletions(-) create mode 100644 src/tools/miri/tests/fail/c-variadic-mismatch-count.rs create mode 100644 src/tools/miri/tests/fail/c-variadic-mismatch-count.stderr create mode 100644 src/tools/miri/tests/fail/c-variadic-mismatch.rs create mode 100644 src/tools/miri/tests/fail/c-variadic-mismatch.stderr diff --git a/compiler/rustc_const_eval/messages.ftl b/compiler/rustc_const_eval/messages.ftl index 0ad80d713fbb1..a7c6acdc74367 100644 --- a/compiler/rustc_const_eval/messages.ftl +++ b/compiler/rustc_const_eval/messages.ftl @@ -139,6 +139,12 @@ const_eval_incompatible_calling_conventions = const_eval_incompatible_return_types = calling a function with return type {$callee_ty} passing return place of type {$caller_ty} +const_eval_c_variadic_mismatch = + calling a function where the caller and callee disagree on whether the function is C-variadic + +const_eval_c_variadic_fixed_count_mismatch = + calling a C-variadic function with {$caller} fixed arguments, but the function expects {$callee} + const_eval_interior_mutable_borrow_escaping = interior mutable shared borrows of temporaries that have their lifetime extended until the end of the program are not allowed .label = this borrow of an interior mutable value refers to such a temporary diff --git a/compiler/rustc_const_eval/src/errors.rs b/compiler/rustc_const_eval/src/errors.rs index 18c9121c62933..a50af80069bde 100644 --- a/compiler/rustc_const_eval/src/errors.rs +++ b/compiler/rustc_const_eval/src/errors.rs @@ -771,6 +771,8 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> { AbiMismatchArgument { .. } => inline_fluent!("calling a function whose parameter #{$arg_idx} has type {$callee_ty} passing argument of type {$caller_ty}"), AbiMismatchReturn { .. } => inline_fluent!("calling a function with return type {$callee_ty} passing return place of type {$caller_ty}"), VaArgOutOfBounds => "more C-variadic arguments read than were passed".into(), + CVariadicMismatch { ..} => "calling a function where the caller and callee disagree on whether the function is C-variadic".into(), + CVariadicFixedCountMismatch { .. } => inline_fluent!("calling a C-variadic function with {$caller} fixed arguments, but the function expects {$callee}"), } } @@ -910,6 +912,14 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> { diag.arg("caller_ty", caller_ty); diag.arg("callee_ty", callee_ty); } + CVariadicMismatch { caller_is_c_variadic, callee_is_c_variadic } => { + diag.arg("caller_is_c_variadic", caller_is_c_variadic); + diag.arg("callee_is_c_variadic", callee_is_c_variadic); + } + CVariadicFixedCountMismatch { caller, callee } => { + diag.arg("caller", caller); + diag.arg("callee", callee); + } } } } diff --git a/compiler/rustc_const_eval/src/interpret/call.rs b/compiler/rustc_const_eval/src/interpret/call.rs index 66617a8a29d08..e97050653255c 100644 --- a/compiler/rustc_const_eval/src/interpret/call.rs +++ b/compiler/rustc_const_eval/src/interpret/call.rs @@ -7,6 +7,7 @@ use either::{Left, Right}; use rustc_abi::{self as abi, ExternAbi, FieldIdx, Integer, VariantIdx}; use rustc_data_structures::assert_matches; use rustc_errors::inline_fluent; +use rustc_hir::def::DefKind; use rustc_hir::def_id::DefId; use rustc_middle::ty::layout::{IntegerExt, TyAndLayout}; use rustc_middle::ty::{self, AdtDef, Instance, Ty, VariantDef}; @@ -353,23 +354,27 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { ) -> InterpResult<'tcx> { let _trace = enter_trace_span!(M, step::init_stack_frame, %instance, tracing_separate_thread = Empty); - let (fixed_count, c_variadic_args) = if caller_fn_abi.c_variadic { - let sig = self.tcx.fn_sig(instance.def_id()).skip_binder(); - let fixed_count = sig.inputs().skip_binder().len(); - assert!(caller_fn_abi.args.len() >= fixed_count); - let extra_tys = - caller_fn_abi.args[fixed_count..].iter().map(|arg_abi| arg_abi.layout.ty); - - (fixed_count, self.tcx.mk_type_list_from_iter(extra_tys)) - } else { - (caller_fn_abi.args.len(), ty::List::empty()) - }; - - let callee_fn_abi = self.fn_abi_of_instance(instance, c_variadic_args)?; + // The check for the DefKind is so that we don't requiest the fn_sig of a closure. + // Otherwise, we hit: + // + // DefId(1:180 ~ std[269c]::rt::lang_start_internal::{closure#0}) does not have a "fn_sig" + let (fixed_count, callee_c_variadic_args) = + if matches!(self.tcx.def_kind(instance.def_id()), DefKind::Fn) + && let callee_fn_sig = self.tcx.fn_sig(instance.def_id()).skip_binder() + && callee_fn_sig.c_variadic() + { + // A mismatch in caller and callee fixed_count will error below. + let fixed_count = callee_fn_sig.inputs().skip_binder().len(); + let extra_tys = caller_fn_abi.args[caller_fn_abi.fixed_count as usize..] + .iter() + .map(|arg_abi| arg_abi.layout.ty); + + (fixed_count, self.tcx.mk_type_list_from_iter(extra_tys)) + } else { + (caller_fn_abi.args.len(), ty::List::empty()) + }; - if callee_fn_abi.c_variadic != caller_fn_abi.c_variadic { - unreachable!("caller and callee disagree on being c-variadic"); - } + let callee_fn_abi = self.fn_abi_of_instance(instance, callee_c_variadic_args)?; if caller_fn_abi.conv != callee_fn_abi.conv { throw_ub_custom!( @@ -381,6 +386,20 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { ) } + if caller_fn_abi.c_variadic != callee_fn_abi.c_variadic { + throw_ub!(CVariadicMismatch { + caller_is_c_variadic: caller_fn_abi.c_variadic, + callee_is_c_variadic: callee_fn_abi.c_variadic, + }); + } + + if caller_fn_abi.fixed_count != callee_fn_abi.fixed_count { + throw_ub!(CVariadicFixedCountMismatch { + caller: caller_fn_abi.fixed_count, + callee: callee_fn_abi.fixed_count, + }); + } + // Check that all target features required by the callee (i.e., from // the attribute `#[target_feature(enable = ...)]`) are enabled at // compile time. diff --git a/compiler/rustc_middle/src/mir/interpret/error.rs b/compiler/rustc_middle/src/mir/interpret/error.rs index c0d303bc1d5fb..035ffd362a6bb 100644 --- a/compiler/rustc_middle/src/mir/interpret/error.rs +++ b/compiler/rustc_middle/src/mir/interpret/error.rs @@ -440,6 +440,10 @@ pub enum UndefinedBehaviorInfo<'tcx> { AbiMismatchReturn { caller_ty: Ty<'tcx>, callee_ty: Ty<'tcx> }, /// `va_arg` was called on an exhausted `VaList`. VaArgOutOfBounds, + /// The caller and callee disagree on whether they are c-variadic or not. + CVariadicMismatch { caller_is_c_variadic: bool, callee_is_c_variadic: bool }, + /// The caller and callee disagree on the number of fixed (i.e. non-c-variadic) arguments. + CVariadicFixedCountMismatch { caller: u32, callee: u32 }, } #[derive(Debug, Clone, Copy)] diff --git a/src/tools/miri/tests/fail/c-variadic-mismatch-count.rs b/src/tools/miri/tests/fail/c-variadic-mismatch-count.rs new file mode 100644 index 0000000000000..2bbb9ff4a557f --- /dev/null +++ b/src/tools/miri/tests/fail/c-variadic-mismatch-count.rs @@ -0,0 +1,13 @@ +#![feature(c_variadic)] + +unsafe extern "C" fn helper(_: i32, _: ...) {} + +fn main() { + unsafe { + let f = helper as *const (); + let f = std::mem::transmute::<_, unsafe extern "C" fn(...)>(f); + + f(); + //~^ ERROR: Undefined Behavior + } +} diff --git a/src/tools/miri/tests/fail/c-variadic-mismatch-count.stderr b/src/tools/miri/tests/fail/c-variadic-mismatch-count.stderr new file mode 100644 index 0000000000000..212b190595164 --- /dev/null +++ b/src/tools/miri/tests/fail/c-variadic-mismatch-count.stderr @@ -0,0 +1,13 @@ +error: Undefined Behavior: calling a C-variadic function with 0 fixed arguments, but the function expects 1 + --> tests/fail/c-variadic-mismatch-count.rs:LL:CC + | +LL | f(); + | ^^^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail/c-variadic-mismatch.rs b/src/tools/miri/tests/fail/c-variadic-mismatch.rs new file mode 100644 index 0000000000000..bf44fb00bceec --- /dev/null +++ b/src/tools/miri/tests/fail/c-variadic-mismatch.rs @@ -0,0 +1,13 @@ +#![feature(c_variadic)] + +unsafe extern "C" fn helper(_: i32, _: ...) {} + +fn main() { + unsafe { + let f = helper as *const (); + let f = std::mem::transmute::<_, unsafe extern "C" fn(_: i32, _: i64)>(f); + + f(1i32, 1i64); + //~^ ERROR: Undefined Behavior: calling a function where the caller and callee disagree on whether the function is C-variadic + } +} diff --git a/src/tools/miri/tests/fail/c-variadic-mismatch.stderr b/src/tools/miri/tests/fail/c-variadic-mismatch.stderr new file mode 100644 index 0000000000000..a68b96f738d22 --- /dev/null +++ b/src/tools/miri/tests/fail/c-variadic-mismatch.stderr @@ -0,0 +1,13 @@ +error: Undefined Behavior: calling a function where the caller and callee disagree on whether the function is C-variadic + --> tests/fail/c-variadic-mismatch.rs:LL:CC + | +LL | f(1i32, 1i64); + | ^^^^^^^^^^^^^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + From ed90238ebaef3153b294f347ab5ffdb3ff864867 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Wed, 28 Jan 2026 18:52:00 +0100 Subject: [PATCH 32/40] retrieve key field without the lang item --- .../src/interpret/intrinsics.rs | 29 ++++++------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/compiler/rustc_const_eval/src/interpret/intrinsics.rs b/compiler/rustc_const_eval/src/interpret/intrinsics.rs index e74cf5f19c150..e662beb950a4c 100644 --- a/compiler/rustc_const_eval/src/interpret/intrinsics.rs +++ b/compiler/rustc_const_eval/src/interpret/intrinsics.rs @@ -8,7 +8,6 @@ use rustc_abi::{FIRST_VARIANT, FieldIdx, HasDataLayout, Size, VariantIdx}; use rustc_apfloat::ieee::{Double, Half, Quad, Single}; use rustc_data_structures::assert_matches; use rustc_errors::inline_fluent; -use rustc_hir::LangItem; use rustc_hir::def_id::CRATE_DEF_ID; use rustc_infer::infer::TyCtxtInferExt; use rustc_middle::mir::interpret::{CTFE_ALLOC_SALT, read_target_uint, write_target_uint}; @@ -1281,36 +1280,26 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { } } - fn va_list_key_index(&self) -> FieldIdx { - let def_id = self.tcx.lang_items().get(LangItem::VaList).unwrap(); - - // VaList is a transparent wrapper around a struct. - let va_list_ty = self.tcx.type_of(def_id).instantiate_identity(); - let layout = self.layout_of(va_list_ty).unwrap(); - let (_, inner) = layout.non_1zst_field(self).unwrap(); + /// Get the MPlace of the key from the place storing the VaList. + pub(super) fn va_list_key_field>( + &self, + va_list: &P, + ) -> InterpResult<'tcx, P> { + // The struct wrapped by VaList. + let va_list_inner = self.project_field(va_list, FieldIdx::ZERO)?; // Find the first pointer field in this struct. The exact index is target-specific. - let ty::Adt(adt, _substs) = inner.ty.kind() else { + let ty::Adt(adt, _substs) = va_list_inner.layout().ty.kind() else { bug!("invalid VaListImpl layout"); }; for (i, field) in adt.non_enum_variant().fields.iter().enumerate() { let field_ty = self.tcx.type_of(field.did); if field_ty.skip_binder().is_raw_ptr() { - return FieldIdx::from_usize(i); + return self.project_field(&va_list_inner, FieldIdx::from_usize(i)); } } bug!("no VaListImpl field is a pointer"); } - - /// Get the MPlace of the key from the place storing the VaList. - pub(super) fn va_list_key_field>( - &self, - va_list: &P, - ) -> InterpResult<'tcx, P> { - let va_list_inner = self.project_field(va_list, FieldIdx::ZERO)?; - let field_idx = self.va_list_key_index(); - self.project_field(&va_list_inner, field_idx) - } } From 6d830c3133e0b1c9848a0050583d84f627c2d6e9 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Wed, 28 Jan 2026 21:03:41 +0100 Subject: [PATCH 33/40] feature-gate c-variadic definitions and calls in const contexts --- .../rustc_ast_passes/src/ast_validation.rs | 7 ++ compiler/rustc_const_eval/messages.ftl | 3 + .../src/check_consts/check.rs | 4 + .../rustc_const_eval/src/check_consts/ops.rs | 25 +++++ compiler/rustc_const_eval/src/errors.rs | 14 +++ compiler/rustc_feature/src/unstable.rs | 2 + compiler/rustc_span/src/symbol.rs | 1 + tests/ui/consts/const-eval/c-variadic-fail.rs | 1 + .../consts/const-eval/c-variadic-fail.stderr | 92 +++++++++---------- tests/ui/consts/const-eval/c-variadic.rs | 1 + .../feature-gate-const-c-variadic.rs | 11 +++ .../feature-gate-const-c-variadic.stderr | 24 +++++ 12 files changed, 139 insertions(+), 46 deletions(-) create mode 100644 tests/ui/feature-gates/feature-gate-const-c-variadic.rs create mode 100644 tests/ui/feature-gates/feature-gate-const-c-variadic.stderr diff --git a/compiler/rustc_ast_passes/src/ast_validation.rs b/compiler/rustc_ast_passes/src/ast_validation.rs index dc825d7e11ed3..3da262953ac34 100644 --- a/compiler/rustc_ast_passes/src/ast_validation.rs +++ b/compiler/rustc_ast_passes/src/ast_validation.rs @@ -698,6 +698,13 @@ impl<'a> AstValidator<'a> { unreachable!("C variable argument list cannot be used in closures") }; + if let Const::Yes(_) = sig.header.constness + && !self.features.enabled(sym::const_c_variadic) + { + let msg = format!("c-variadic const function definitions are unstable"); + feature_err(&self.sess, sym::const_c_variadic, sig.span, msg).emit(); + } + 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_const_eval/messages.ftl b/compiler/rustc_const_eval/messages.ftl index a7c6acdc74367..85cf27e4b2864 100644 --- a/compiler/rustc_const_eval/messages.ftl +++ b/compiler/rustc_const_eval/messages.ftl @@ -70,6 +70,9 @@ const_eval_const_make_global_with_offset = making {$ptr} global which does not p const_eval_copy_nonoverlapping_overlapping = `copy_nonoverlapping` called on overlapping ranges +const_eval_c_variadic_call = + calling const c-variadic functions is unstable in {const_eval_const_context}s + const_eval_dangling_int_pointer = {const_eval_bad_pointer_op_attempting}, but got {$pointer} which is a dangling pointer (it has no provenance) const_eval_dangling_null_pointer = diff --git a/compiler/rustc_const_eval/src/check_consts/check.rs b/compiler/rustc_const_eval/src/check_consts/check.rs index 57396b657a3fa..4ce0345f2e61c 100644 --- a/compiler/rustc_const_eval/src/check_consts/check.rs +++ b/compiler/rustc_const_eval/src/check_consts/check.rs @@ -815,6 +815,10 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { }); } + if self.tcx.fn_sig(callee).skip_binder().c_variadic() { + self.check_op(ops::FnCallCVariadic) + } + // At this point, we are calling a function, `callee`, whose `DefId` is known... // `begin_panic` and `panic_display` functions accept generic diff --git a/compiler/rustc_const_eval/src/check_consts/ops.rs b/compiler/rustc_const_eval/src/check_consts/ops.rs index ecf52e4aa6054..27728bfca9c6c 100644 --- a/compiler/rustc_const_eval/src/check_consts/ops.rs +++ b/compiler/rustc_const_eval/src/check_consts/ops.rs @@ -75,6 +75,31 @@ impl<'tcx> NonConstOp<'tcx> for FnCallIndirect { } } +/// A c-variadic function call. +#[derive(Debug)] +pub(crate) struct FnCallCVariadic; +impl<'tcx> NonConstOp<'tcx> for FnCallCVariadic { + fn status_in_item(&self, _ccx: &ConstCx<'_, 'tcx>) -> Status { + Status::Unstable { + gate: sym::const_c_variadic, + gate_already_checked: false, + safe_to_expose_on_stable: false, + is_function_call: true, + } + } + + fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> Diag<'tcx> { + ccx.tcx.sess.create_feature_err( + errors::NonConstCVariadicCall { + span, + kind: ccx.const_kind(), + non_or_conditionally: "non", + }, + sym::const_c_variadic, + ) + } +} + /// A call to a function that is in a trait, or has trait bounds that make it conditionally-const. #[derive(Debug)] pub(crate) struct ConditionallyConstCall<'tcx> { diff --git a/compiler/rustc_const_eval/src/errors.rs b/compiler/rustc_const_eval/src/errors.rs index a50af80069bde..68c5e5d68f584 100644 --- a/compiler/rustc_const_eval/src/errors.rs +++ b/compiler/rustc_const_eval/src/errors.rs @@ -530,6 +530,20 @@ pub struct NonConstClosure { pub non_or_conditionally: &'static str, } +#[derive(Diagnostic)] +#[diag(r#"calling const c-variadic functions is unstable in {$kind -> + [const] constant + [static] static + [const_fn] constant function + *[other] {""} +}s"#, code = E0015)] +pub struct NonConstCVariadicCall { + #[primary_span] + pub span: Span, + pub kind: ConstContext, + pub non_or_conditionally: &'static str, +} + #[derive(Subdiagnostic)] pub enum NonConstClosureNote { #[note("function defined here, but it is not `const`")] diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index 2ab7714f22ca9..518eaaf67d115 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -422,6 +422,8 @@ declare_features! ( (unstable, const_block_items, "CURRENT_RUSTC_VERSION", Some(149226)), /// Allows `const || {}` closures in const contexts. (incomplete, const_closures, "1.68.0", Some(106003)), + /// Allows defining and calling c-variadic functions in const contexts. + (unstable, const_c_variadic, "CURRENT_RUSTC_VERSION", Some(151787)), /// Allows using `[const] Destruct` bounds and calling drop impls in const contexts. (unstable, const_destruct, "1.85.0", Some(133214)), /// Allows `for _ in _` loops in const contexts. diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 1915ff0380fda..ed8f3694a0062 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -739,6 +739,7 @@ symbols! { const_compare_raw_pointers, const_constructor, const_continue, + const_c_variadic, const_deallocate, const_destruct, const_eval_limit, diff --git a/tests/ui/consts/const-eval/c-variadic-fail.rs b/tests/ui/consts/const-eval/c-variadic-fail.rs index 859dfed35719f..7664e993d0b7f 100644 --- a/tests/ui/consts/const-eval/c-variadic-fail.rs +++ b/tests/ui/consts/const-eval/c-variadic-fail.rs @@ -1,6 +1,7 @@ //@ build-fail #![feature(c_variadic)] +#![feature(const_c_variadic)] #![feature(c_variadic_const)] #![feature(const_trait_impl)] #![feature(const_destruct)] diff --git a/tests/ui/consts/const-eval/c-variadic-fail.stderr b/tests/ui/consts/const-eval/c-variadic-fail.stderr index 14da5500cb1b6..c715e5e118566 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:28:13 + --> $DIR/c-variadic-fail.rs:29: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:16:17 + --> $DIR/c-variadic-fail.rs:17: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:28:5 + --> $DIR/c-variadic-fail.rs:29:5 | LL | const { read_n::<1>() } | ^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:28:5 + --> $DIR/c-variadic-fail.rs:29: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:32:13 + --> $DIR/c-variadic-fail.rs:33: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:16:17 + --> $DIR/c-variadic-fail.rs:17: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:32:5 + --> $DIR/c-variadic-fail.rs:33:5 | LL | const { read_n::<2>(1) } | ^^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:32:5 + --> $DIR/c-variadic-fail.rs:33: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:50:13 + --> $DIR/c-variadic-fail.rs:51: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:37:5 + --> $DIR/c-variadic-fail.rs:38: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:50:5 + --> $DIR/c-variadic-fail.rs:51:5 | LL | const { read_as::(1i32) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:50:5 + --> $DIR/c-variadic-fail.rs:51: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:53:13 + --> $DIR/c-variadic-fail.rs:54: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:37:5 + --> $DIR/c-variadic-fail.rs:38: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:53:5 + --> $DIR/c-variadic-fail.rs:54:5 | LL | const { read_as::(1u32) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:53:5 + --> $DIR/c-variadic-fail.rs:54: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:56:13 + --> $DIR/c-variadic-fail.rs:57: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:37:5 + --> $DIR/c-variadic-fail.rs:38: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:56:5 + --> $DIR/c-variadic-fail.rs:57:5 | LL | const { read_as::(1u64) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:56:5 + --> $DIR/c-variadic-fail.rs:57: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:59:13 + --> $DIR/c-variadic-fail.rs:60: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:37:5 + --> $DIR/c-variadic-fail.rs:38: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:59:5 + --> $DIR/c-variadic-fail.rs:60:5 | LL | const { read_as::(1i32) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:59:5 + --> $DIR/c-variadic-fail.rs:60: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:62:13 + --> $DIR/c-variadic-fail.rs:63: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:37:5 + --> $DIR/c-variadic-fail.rs:38: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:62:5 + --> $DIR/c-variadic-fail.rs:63:5 | LL | const { read_as::<*const u8>(1i32) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:62:5 + --> $DIR/c-variadic-fail.rs:63:5 | LL | const { read_as::<*const u8>(1i32) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -195,7 +195,7 @@ LL | const { read_as::<*const u8>(1i32) }; = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` error[E0080]: memory access failed: ALLOC0 has been freed, so this pointer is dangling - --> $DIR/c-variadic-fail.rs:75:13 + --> $DIR/c-variadic-fail.rs:76:13 | LL | ap.arg::(); | ^^^^^^^^^^^^^^^ evaluation of `use_after_free::{constant#0}` failed inside this call @@ -204,7 +204,7 @@ note: inside `VaList::<'_>::arg::` --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:71:5 + --> $DIR/c-variadic-fail.rs:72:5 | LL | / const { LL | | unsafe { @@ -215,7 +215,7 @@ LL | | }; | |_____^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:71:5 + --> $DIR/c-variadic-fail.rs:72:5 | LL | / const { LL | | unsafe { @@ -228,13 +228,13 @@ LL | | }; = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` error[E0080]: using ALLOC1 as variable argument list pointer but it does not point to a variable argument list - --> $DIR/c-variadic-fail.rs:97:22 + --> $DIR/c-variadic-fail.rs:98:22 | LL | const { unsafe { helper(1, 2, 3) } }; | ^^^^^^^^^^^^^^^ evaluation of `manual_copy_drop::{constant#0}` failed inside this call | note: inside `manual_copy_drop::helper` - --> $DIR/c-variadic-fail.rs:94:9 + --> $DIR/c-variadic-fail.rs:95:9 | LL | drop(ap); | ^^^^^^^^ @@ -246,13 +246,13 @@ note: inside ` as Drop>::drop` --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:97:5 + --> $DIR/c-variadic-fail.rs:98:5 | LL | const { unsafe { helper(1, 2, 3) } }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:97:5 + --> $DIR/c-variadic-fail.rs:98:5 | LL | const { unsafe { helper(1, 2, 3) } }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -260,13 +260,13 @@ LL | const { unsafe { helper(1, 2, 3) } }; = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` error[E0080]: using ALLOC2 as variable argument list pointer but it does not point to a variable argument list - --> $DIR/c-variadic-fail.rs:113:22 + --> $DIR/c-variadic-fail.rs:114: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:110:9 + --> $DIR/c-variadic-fail.rs:111:9 | LL | drop(ap); | ^^^^^^^^ @@ -278,13 +278,13 @@ note: inside ` as Drop>::drop` --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:113:5 + --> $DIR/c-variadic-fail.rs:114:5 | LL | const { unsafe { helper(1, 2, 3) } }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:113:5 + --> $DIR/c-variadic-fail.rs:114:5 | LL | const { unsafe { helper(1, 2, 3) } }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -292,13 +292,13 @@ LL | const { unsafe { helper(1, 2, 3) } }; = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` error[E0080]: using ALLOC3 as variable argument list pointer but it does not point to a variable argument list - --> $DIR/c-variadic-fail.rs:126:22 + --> $DIR/c-variadic-fail.rs:127: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:123:17 + --> $DIR/c-variadic-fail.rs:124:17 | LL | let _ = ap.arg::(); | ^^^^^^^^^^^^^^^ @@ -306,13 +306,13 @@ note: inside `VaList::<'_>::arg::` --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:126:5 + --> $DIR/c-variadic-fail.rs:127:5 | LL | const { unsafe { helper(1, 2, 3) } }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:126:5 + --> $DIR/c-variadic-fail.rs:127:5 | LL | const { unsafe { helper(1, 2, 3) } }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -320,7 +320,7 @@ LL | const { unsafe { helper(1, 2, 3) } }; = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` error[E0080]: pointer not dereferenceable: pointer must point to some allocation, but got null pointer - --> $DIR/c-variadic-fail.rs:134:5 + --> $DIR/c-variadic-fail.rs:135:5 | LL | } | ^ evaluation of `drop_of_invalid::{constant#0}` failed inside this call @@ -331,7 +331,7 @@ note: inside ` as Drop>::drop` --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:131:5 + --> $DIR/c-variadic-fail.rs:132:5 | LL | / const { LL | | let mut invalid: MaybeUninit = MaybeUninit::zeroed(); @@ -340,7 +340,7 @@ LL | | } | |_____^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:131:5 + --> $DIR/c-variadic-fail.rs:132:5 | LL | / const { LL | | let mut invalid: MaybeUninit = MaybeUninit::zeroed(); diff --git a/tests/ui/consts/const-eval/c-variadic.rs b/tests/ui/consts/const-eval/c-variadic.rs index ec49b5cc5831d..2527bb7cd0c52 100644 --- a/tests/ui/consts/const-eval/c-variadic.rs +++ b/tests/ui/consts/const-eval/c-variadic.rs @@ -3,6 +3,7 @@ //@ ignore-backends: gcc #![feature(c_variadic)] +#![feature(const_c_variadic)] #![feature(const_destruct)] #![feature(c_variadic_const)] #![feature(const_cmp)] diff --git a/tests/ui/feature-gates/feature-gate-const-c-variadic.rs b/tests/ui/feature-gates/feature-gate-const-c-variadic.rs new file mode 100644 index 0000000000000..4e8b3c54b1fd1 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-const-c-variadic.rs @@ -0,0 +1,11 @@ +#![feature(c_variadic)] + +fn main() { + const unsafe extern "C" fn foo(ap: ...) { + //~^ ERROR c-variadic const function definitions are unstable + core::mem::forget(ap); + } + + const { unsafe { foo() } } + //~^ ERROR calling const c-variadic functions is unstable in constants +} diff --git a/tests/ui/feature-gates/feature-gate-const-c-variadic.stderr b/tests/ui/feature-gates/feature-gate-const-c-variadic.stderr new file mode 100644 index 0000000000000..8fd85be08fca4 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-const-c-variadic.stderr @@ -0,0 +1,24 @@ +error[E0658]: c-variadic const function definitions are unstable + --> $DIR/feature-gate-const-c-variadic.rs:4:5 + | +LL | const unsafe extern "C" fn foo(ap: ...) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #151787 for more information + = help: add `#![feature(const_c_variadic)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0015]: calling const c-variadic functions is unstable in constants + --> $DIR/feature-gate-const-c-variadic.rs:9:22 + | +LL | const { unsafe { foo() } } + | ^^^^^ + | + = note: see issue #151787 for more information + = help: add `#![feature(const_c_variadic)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error: aborting due to 2 previous errors + +Some errors have detailed explanations: E0015, E0658. +For more information about an error, try `rustc --explain E0015`. From 75e66bbef843cb2f699a85f210b29ae142e77b6b Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Thu, 29 Jan 2026 10:04:29 +0100 Subject: [PATCH 34/40] clean up feature gate --- compiler/rustc_const_eval/messages.ftl | 19 ++++++++++--------- .../rustc_const_eval/src/interpret/call.rs | 2 +- compiler/rustc_feature/src/unstable.rs | 4 ++-- compiler/rustc_span/src/symbol.rs | 2 +- library/core/src/ffi/va_list.rs | 6 +++--- tests/ui/consts/const-eval/c-variadic-fail.rs | 1 - tests/ui/consts/const-eval/c-variadic.rs | 1 - 7 files changed, 17 insertions(+), 18 deletions(-) diff --git a/compiler/rustc_const_eval/messages.ftl b/compiler/rustc_const_eval/messages.ftl index 85cf27e4b2864..845eba53f01dc 100644 --- a/compiler/rustc_const_eval/messages.ftl +++ b/compiler/rustc_const_eval/messages.ftl @@ -35,6 +35,16 @@ const_eval_bad_pointer_op_attempting = {const_eval_bad_pointer_op}: {$operation const_eval_bounds_check_failed = indexing out of bounds: the len is {$len} but the index is {$index} + +const_eval_c_variadic_call = + calling const c-variadic functions is unstable in {const_eval_const_context}s + +const_eval_c_variadic_fixed_count_mismatch = + calling a C-variadic function with {$caller} fixed arguments, but the function expects {$callee} + +const_eval_c_variadic_mismatch = + calling a function where the caller and callee disagree on whether the function is C-variadic + const_eval_call_nonzero_intrinsic = `{$name}` called on 0 @@ -70,9 +80,6 @@ const_eval_const_make_global_with_offset = making {$ptr} global which does not p const_eval_copy_nonoverlapping_overlapping = `copy_nonoverlapping` called on overlapping ranges -const_eval_c_variadic_call = - calling const c-variadic functions is unstable in {const_eval_const_context}s - const_eval_dangling_int_pointer = {const_eval_bad_pointer_op_attempting}, but got {$pointer} which is a dangling pointer (it has no provenance) const_eval_dangling_null_pointer = @@ -142,12 +149,6 @@ const_eval_incompatible_calling_conventions = const_eval_incompatible_return_types = calling a function with return type {$callee_ty} passing return place of type {$caller_ty} -const_eval_c_variadic_mismatch = - calling a function where the caller and callee disagree on whether the function is C-variadic - -const_eval_c_variadic_fixed_count_mismatch = - calling a C-variadic function with {$caller} fixed arguments, but the function expects {$callee} - const_eval_interior_mutable_borrow_escaping = interior mutable shared borrows of temporaries that have their lifetime extended until the end of the program are not allowed .label = this borrow of an interior mutable value refers to such a temporary diff --git a/compiler/rustc_const_eval/src/interpret/call.rs b/compiler/rustc_const_eval/src/interpret/call.rs index e97050653255c..3c97a2e99fa28 100644 --- a/compiler/rustc_const_eval/src/interpret/call.rs +++ b/compiler/rustc_const_eval/src/interpret/call.rs @@ -354,7 +354,7 @@ 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); - // The check for the DefKind is so that we don't requiest the fn_sig of a closure. + // The check for the DefKind is so that we don't request the fn_sig of a closure. // Otherwise, we hit: // // DefId(1:180 ~ std[269c]::rt::lang_start_internal::{closure#0}) does not have a "fn_sig" diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index 518eaaf67d115..e78596cb2ca03 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -420,10 +420,10 @@ declare_features! ( (unstable, const_async_blocks, "1.53.0", Some(85368)), /// Allows `const { ... }` as a shorthand for `const _: () = const { ... };` for module items. (unstable, const_block_items, "CURRENT_RUSTC_VERSION", Some(149226)), - /// Allows `const || {}` closures in const contexts. - (incomplete, const_closures, "1.68.0", Some(106003)), /// Allows defining and calling c-variadic functions in const contexts. (unstable, const_c_variadic, "CURRENT_RUSTC_VERSION", Some(151787)), + /// Allows `const || {}` closures in const contexts. + (incomplete, const_closures, "1.68.0", Some(106003)), /// Allows using `[const] Destruct` bounds and calling drop impls in const contexts. (unstable, const_destruct, "1.85.0", Some(133214)), /// Allows `for _ in _` loops in const contexts. diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index ed8f3694a0062..92c8036154e26 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -735,11 +735,11 @@ symbols! { const_allocate, const_async_blocks, const_block_items, + const_c_variadic, const_closures, const_compare_raw_pointers, const_constructor, const_continue, - const_c_variadic, const_deallocate, const_destruct, const_eval_limit, diff --git a/library/core/src/ffi/va_list.rs b/library/core/src/ffi/va_list.rs index 45a9b7ba5293e..4ed93f54d43c3 100644 --- a/library/core/src/ffi/va_list.rs +++ b/library/core/src/ffi/va_list.rs @@ -205,7 +205,7 @@ impl VaList<'_> { } } -#[rustc_const_unstable(feature = "c_variadic_const", issue = "none")] +#[rustc_const_unstable(feature = "const_c_variadic", issue = "151787")] impl<'f> const Clone for VaList<'f> { #[inline] fn clone(&self) -> Self { @@ -217,7 +217,7 @@ impl<'f> const Clone for VaList<'f> { } } -#[rustc_const_unstable(feature = "c_variadic_const", issue = "none")] +#[rustc_const_unstable(feature = "const_c_variadic", issue = "151787")] 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. @@ -293,7 +293,7 @@ impl<'f> VaList<'f> { /// /// [valid]: https://doc.rust-lang.org/nightly/nomicon/what-unsafe-does.html #[inline] - #[rustc_const_unstable(feature = "c_variadic_const", issue = "none")] + #[rustc_const_unstable(feature = "const_c_variadic", issue = "151787")] 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/tests/ui/consts/const-eval/c-variadic-fail.rs b/tests/ui/consts/const-eval/c-variadic-fail.rs index 7664e993d0b7f..b5a400a8bf255 100644 --- a/tests/ui/consts/const-eval/c-variadic-fail.rs +++ b/tests/ui/consts/const-eval/c-variadic-fail.rs @@ -2,7 +2,6 @@ #![feature(c_variadic)] #![feature(const_c_variadic)] -#![feature(c_variadic_const)] #![feature(const_trait_impl)] #![feature(const_destruct)] #![feature(const_clone)] diff --git a/tests/ui/consts/const-eval/c-variadic.rs b/tests/ui/consts/const-eval/c-variadic.rs index 2527bb7cd0c52..2f8d043fb5db6 100644 --- a/tests/ui/consts/const-eval/c-variadic.rs +++ b/tests/ui/consts/const-eval/c-variadic.rs @@ -5,7 +5,6 @@ #![feature(c_variadic)] #![feature(const_c_variadic)] #![feature(const_destruct)] -#![feature(c_variadic_const)] #![feature(const_cmp)] #![feature(const_trait_impl)] From 74ed89366b4271e0f83c84cb1af1405cb61322d6 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Fri, 30 Jan 2026 00:18:34 +0100 Subject: [PATCH 35/40] update tests --- .../rustc_const_eval/src/check_consts/ops.rs | 6 +- compiler/rustc_const_eval/src/errors.rs | 1 - .../rustc_const_eval/src/interpret/call.rs | 3 +- .../consts/const-eval/c-variadic-fail.stderr | 92 +++++++++---------- .../variadic-ffi-semantic-restrictions.rs | 4 + .../variadic-ffi-semantic-restrictions.stderr | 83 ++++++++++++----- 6 files changed, 115 insertions(+), 74 deletions(-) diff --git a/compiler/rustc_const_eval/src/check_consts/ops.rs b/compiler/rustc_const_eval/src/check_consts/ops.rs index 27728bfca9c6c..563f3b1abf485 100644 --- a/compiler/rustc_const_eval/src/check_consts/ops.rs +++ b/compiler/rustc_const_eval/src/check_consts/ops.rs @@ -90,11 +90,7 @@ impl<'tcx> NonConstOp<'tcx> for FnCallCVariadic { fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> Diag<'tcx> { ccx.tcx.sess.create_feature_err( - errors::NonConstCVariadicCall { - span, - kind: ccx.const_kind(), - non_or_conditionally: "non", - }, + errors::NonConstCVariadicCall { span, kind: ccx.const_kind() }, sym::const_c_variadic, ) } diff --git a/compiler/rustc_const_eval/src/errors.rs b/compiler/rustc_const_eval/src/errors.rs index 68c5e5d68f584..ed63971efa89a 100644 --- a/compiler/rustc_const_eval/src/errors.rs +++ b/compiler/rustc_const_eval/src/errors.rs @@ -541,7 +541,6 @@ pub struct NonConstCVariadicCall { #[primary_span] pub span: Span, pub kind: ConstContext, - pub non_or_conditionally: &'static str, } #[derive(Subdiagnostic)] diff --git a/compiler/rustc_const_eval/src/interpret/call.rs b/compiler/rustc_const_eval/src/interpret/call.rs index 3c97a2e99fa28..6deb537c7aade 100644 --- a/compiler/rustc_const_eval/src/interpret/call.rs +++ b/compiler/rustc_const_eval/src/interpret/call.rs @@ -476,7 +476,8 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { callee_fn_abi.args[..fixed_count].iter().enumerate() } else { // NOTE: this handles the extra caller location argument - // when `#[track_caller]` is used. + // when `#[track_caller]` is used. This attribute is only allowed on `extern "Rust"` + // functions, so the c-variadic case does not need to handle the extra argument. callee_fn_abi.args.iter().enumerate() }; diff --git a/tests/ui/consts/const-eval/c-variadic-fail.stderr b/tests/ui/consts/const-eval/c-variadic-fail.stderr index c715e5e118566..14da5500cb1b6 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:29:13 + --> $DIR/c-variadic-fail.rs:28:13 | LL | const { read_n::<1>() } | ^^^^^^^^^^^^^ evaluation of `read_too_many::{constant#2}` failed inside this call | note: inside `read_n::<1>` - --> $DIR/c-variadic-fail.rs:17:17 + --> $DIR/c-variadic-fail.rs:16:17 | LL | let _ = ap.arg::(); | ^^^^^^^^^^^^^^^ @@ -13,13 +13,13 @@ note: inside `VaList::<'_>::arg::` --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:29:5 + --> $DIR/c-variadic-fail.rs:28:5 | LL | const { read_n::<1>() } | ^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:29:5 + --> $DIR/c-variadic-fail.rs:28:5 | LL | const { read_n::<1>() } | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -27,13 +27,13 @@ LL | const { read_n::<1>() } = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` error[E0080]: more C-variadic arguments read than were passed - --> $DIR/c-variadic-fail.rs:33:13 + --> $DIR/c-variadic-fail.rs:32:13 | LL | const { read_n::<2>(1) } | ^^^^^^^^^^^^^^ evaluation of `read_too_many::{constant#3}` failed inside this call | note: inside `read_n::<2>` - --> $DIR/c-variadic-fail.rs:17:17 + --> $DIR/c-variadic-fail.rs:16:17 | LL | let _ = ap.arg::(); | ^^^^^^^^^^^^^^^ @@ -41,13 +41,13 @@ note: inside `VaList::<'_>::arg::` --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:33:5 + --> $DIR/c-variadic-fail.rs:32:5 | LL | const { read_n::<2>(1) } | ^^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:33:5 + --> $DIR/c-variadic-fail.rs:32:5 | LL | const { read_n::<2>(1) } | ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -55,13 +55,13 @@ LL | const { read_n::<2>(1) } = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` error[E0080]: va_arg type mismatch: requested `u32`, but next argument is `i32` - --> $DIR/c-variadic-fail.rs:51:13 + --> $DIR/c-variadic-fail.rs:50:13 | LL | const { read_as::(1i32) }; | ^^^^^^^^^^^^^^^^^^^^ evaluation of `read_cast::{constant#6}` failed inside this call | note: inside `read_as::` - --> $DIR/c-variadic-fail.rs:38:5 + --> $DIR/c-variadic-fail.rs:37:5 | LL | ap.arg::() | ^^^^^^^^^^^^^ @@ -69,13 +69,13 @@ note: inside `VaList::<'_>::arg::` --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:51:5 + --> $DIR/c-variadic-fail.rs:50:5 | LL | const { read_as::(1i32) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:51:5 + --> $DIR/c-variadic-fail.rs:50:5 | LL | const { read_as::(1i32) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -83,13 +83,13 @@ LL | const { read_as::(1i32) }; = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` error[E0080]: va_arg type mismatch: requested `i32`, but next argument is `u32` - --> $DIR/c-variadic-fail.rs:54:13 + --> $DIR/c-variadic-fail.rs:53:13 | LL | const { read_as::(1u32) }; | ^^^^^^^^^^^^^^^^^^^^ evaluation of `read_cast::{constant#7}` failed inside this call | note: inside `read_as::` - --> $DIR/c-variadic-fail.rs:38:5 + --> $DIR/c-variadic-fail.rs:37:5 | LL | ap.arg::() | ^^^^^^^^^^^^^ @@ -97,13 +97,13 @@ note: inside `VaList::<'_>::arg::` --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:54:5 + --> $DIR/c-variadic-fail.rs:53:5 | LL | const { read_as::(1u32) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:54:5 + --> $DIR/c-variadic-fail.rs:53:5 | LL | const { read_as::(1u32) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -111,13 +111,13 @@ LL | const { read_as::(1u32) }; = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` error[E0080]: va_arg type mismatch: requested `i32`, but next argument is `u64` - --> $DIR/c-variadic-fail.rs:57:13 + --> $DIR/c-variadic-fail.rs:56:13 | LL | const { read_as::(1u64) }; | ^^^^^^^^^^^^^^^^^^^^ evaluation of `read_cast::{constant#8}` failed inside this call | note: inside `read_as::` - --> $DIR/c-variadic-fail.rs:38:5 + --> $DIR/c-variadic-fail.rs:37:5 | LL | ap.arg::() | ^^^^^^^^^^^^^ @@ -125,13 +125,13 @@ note: inside `VaList::<'_>::arg::` --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:57:5 + --> $DIR/c-variadic-fail.rs:56:5 | LL | const { read_as::(1u64) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:57:5 + --> $DIR/c-variadic-fail.rs:56:5 | LL | const { read_as::(1u64) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -139,13 +139,13 @@ LL | const { read_as::(1u64) }; = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` error[E0080]: va_arg type mismatch: requested `f64`, but next argument is `i32` - --> $DIR/c-variadic-fail.rs:60:13 + --> $DIR/c-variadic-fail.rs:59:13 | LL | const { read_as::(1i32) }; | ^^^^^^^^^^^^^^^^^^^^ evaluation of `read_cast::{constant#9}` failed inside this call | note: inside `read_as::` - --> $DIR/c-variadic-fail.rs:38:5 + --> $DIR/c-variadic-fail.rs:37:5 | LL | ap.arg::() | ^^^^^^^^^^^^^ @@ -153,13 +153,13 @@ note: inside `VaList::<'_>::arg::` --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:60:5 + --> $DIR/c-variadic-fail.rs:59:5 | LL | const { read_as::(1i32) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:60:5 + --> $DIR/c-variadic-fail.rs:59:5 | LL | const { read_as::(1i32) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -167,13 +167,13 @@ LL | const { read_as::(1i32) }; = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` error[E0080]: va_arg type mismatch: requested `*const u8`, but next argument is `i32` - --> $DIR/c-variadic-fail.rs:63:13 + --> $DIR/c-variadic-fail.rs:62:13 | LL | const { read_as::<*const u8>(1i32) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^ evaluation of `read_cast::{constant#10}` failed inside this call | note: inside `read_as::<*const u8>` - --> $DIR/c-variadic-fail.rs:38:5 + --> $DIR/c-variadic-fail.rs:37:5 | LL | ap.arg::() | ^^^^^^^^^^^^^ @@ -181,13 +181,13 @@ note: inside `VaList::<'_>::arg::<*const u8>` --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:63:5 + --> $DIR/c-variadic-fail.rs:62:5 | LL | const { read_as::<*const u8>(1i32) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:63:5 + --> $DIR/c-variadic-fail.rs:62:5 | LL | const { read_as::<*const u8>(1i32) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -195,7 +195,7 @@ LL | const { read_as::<*const u8>(1i32) }; = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` error[E0080]: memory access failed: ALLOC0 has been freed, so this pointer is dangling - --> $DIR/c-variadic-fail.rs:76:13 + --> $DIR/c-variadic-fail.rs:75:13 | LL | ap.arg::(); | ^^^^^^^^^^^^^^^ evaluation of `use_after_free::{constant#0}` failed inside this call @@ -204,7 +204,7 @@ note: inside `VaList::<'_>::arg::` --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:72:5 + --> $DIR/c-variadic-fail.rs:71:5 | LL | / const { LL | | unsafe { @@ -215,7 +215,7 @@ LL | | }; | |_____^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:72:5 + --> $DIR/c-variadic-fail.rs:71:5 | LL | / const { LL | | unsafe { @@ -228,13 +228,13 @@ LL | | }; = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` error[E0080]: using ALLOC1 as variable argument list pointer but it does not point to a variable argument list - --> $DIR/c-variadic-fail.rs:98:22 + --> $DIR/c-variadic-fail.rs:97:22 | LL | const { unsafe { helper(1, 2, 3) } }; | ^^^^^^^^^^^^^^^ evaluation of `manual_copy_drop::{constant#0}` failed inside this call | note: inside `manual_copy_drop::helper` - --> $DIR/c-variadic-fail.rs:95:9 + --> $DIR/c-variadic-fail.rs:94:9 | LL | drop(ap); | ^^^^^^^^ @@ -246,13 +246,13 @@ 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:97:5 | LL | const { unsafe { helper(1, 2, 3) } }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:98:5 + --> $DIR/c-variadic-fail.rs:97:5 | LL | const { unsafe { helper(1, 2, 3) } }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -260,13 +260,13 @@ LL | const { unsafe { helper(1, 2, 3) } }; = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` error[E0080]: using ALLOC2 as variable argument list pointer but it does not point to a variable argument list - --> $DIR/c-variadic-fail.rs:114:22 + --> $DIR/c-variadic-fail.rs:113:22 | LL | const { unsafe { helper(1, 2, 3) } }; | ^^^^^^^^^^^^^^^ evaluation of `manual_copy_forget::{constant#0}` failed inside this call | note: inside `manual_copy_forget::helper` - --> $DIR/c-variadic-fail.rs:111:9 + --> $DIR/c-variadic-fail.rs:110:9 | LL | drop(ap); | ^^^^^^^^ @@ -278,13 +278,13 @@ note: inside ` as Drop>::drop` --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:114:5 + --> $DIR/c-variadic-fail.rs:113:5 | LL | const { unsafe { helper(1, 2, 3) } }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:114:5 + --> $DIR/c-variadic-fail.rs:113:5 | LL | const { unsafe { helper(1, 2, 3) } }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -292,13 +292,13 @@ LL | const { unsafe { helper(1, 2, 3) } }; = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` error[E0080]: using ALLOC3 as variable argument list pointer but it does not point to a variable argument list - --> $DIR/c-variadic-fail.rs:127:22 + --> $DIR/c-variadic-fail.rs:126:22 | LL | const { unsafe { helper(1, 2, 3) } }; | ^^^^^^^^^^^^^^^ evaluation of `manual_copy_read::{constant#0}` failed inside this call | note: inside `manual_copy_read::helper` - --> $DIR/c-variadic-fail.rs:124:17 + --> $DIR/c-variadic-fail.rs:123:17 | LL | let _ = ap.arg::(); | ^^^^^^^^^^^^^^^ @@ -306,13 +306,13 @@ note: inside `VaList::<'_>::arg::` --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:127:5 + --> $DIR/c-variadic-fail.rs:126:5 | LL | const { unsafe { helper(1, 2, 3) } }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:127:5 + --> $DIR/c-variadic-fail.rs:126:5 | LL | const { unsafe { helper(1, 2, 3) } }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -320,7 +320,7 @@ LL | const { unsafe { helper(1, 2, 3) } }; = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` error[E0080]: pointer not dereferenceable: pointer must point to some allocation, but got null pointer - --> $DIR/c-variadic-fail.rs:135:5 + --> $DIR/c-variadic-fail.rs:134:5 | LL | } | ^ evaluation of `drop_of_invalid::{constant#0}` failed inside this call @@ -331,7 +331,7 @@ note: inside ` as Drop>::drop` --> $SRC_DIR/core/src/ffi/va_list.rs:LL:COL note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:132:5 + --> $DIR/c-variadic-fail.rs:131:5 | LL | / const { LL | | let mut invalid: MaybeUninit = MaybeUninit::zeroed(); @@ -340,7 +340,7 @@ LL | | } | |_____^ note: erroneous constant encountered - --> $DIR/c-variadic-fail.rs:132:5 + --> $DIR/c-variadic-fail.rs:131:5 | LL | / const { LL | | let mut invalid: MaybeUninit = MaybeUninit::zeroed(); diff --git a/tests/ui/parser/variadic-ffi-semantic-restrictions.rs b/tests/ui/parser/variadic-ffi-semantic-restrictions.rs index 41d9e73b69e8d..4e038875d78f8 100644 --- a/tests/ui/parser/variadic-ffi-semantic-restrictions.rs +++ b/tests/ui/parser/variadic-ffi-semantic-restrictions.rs @@ -32,14 +32,17 @@ extern "C" fn f3_3(_: ..., x: isize) {} const unsafe extern "C" fn f4_1(x: isize, _: ...) {} //~^ ERROR destructor of `VaList<'_>` cannot be evaluated at compile-time +//~| ERROR c-variadic const function definitions are unstable const extern "C" fn f4_2(x: isize, _: ...) {} //~^ ERROR functions with a C variable argument list must be unsafe //~| ERROR destructor of `VaList<'_>` cannot be evaluated at compile-time +//~| ERROR c-variadic const function definitions are unstable const extern "C" fn f4_3(_: ..., x: isize, _: ...) {} //~^ ERROR functions with a C variable argument list must be unsafe //~| ERROR `...` must be the last argument of a C-variadic function +//~| ERROR c-variadic const function definitions are unstable extern "C" { fn e_f2(..., x: isize); @@ -62,6 +65,7 @@ impl X { const fn i_f5(x: isize, _: ...) {} //~^ ERROR `...` is not supported for non-extern functions //~| ERROR destructor of `VaList<'_>` cannot be evaluated at compile-time + //~| ERROR c-variadic const function definitions are unstable } trait T { diff --git a/tests/ui/parser/variadic-ffi-semantic-restrictions.stderr b/tests/ui/parser/variadic-ffi-semantic-restrictions.stderr index 26d5cdaf995aa..ea9f9baa58ba2 100644 --- a/tests/ui/parser/variadic-ffi-semantic-restrictions.stderr +++ b/tests/ui/parser/variadic-ffi-semantic-restrictions.stderr @@ -80,8 +80,28 @@ error: `...` must be the last argument of a C-variadic function LL | extern "C" fn f3_3(_: ..., x: isize) {} | ^^^^^^ +error[E0658]: c-variadic const function definitions are unstable + --> $DIR/variadic-ffi-semantic-restrictions.rs:33:1 + | +LL | const unsafe extern "C" fn f4_1(x: isize, _: ...) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #151787 for more information + = help: add `#![feature(const_c_variadic)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0658]: c-variadic const function definitions are unstable + --> $DIR/variadic-ffi-semantic-restrictions.rs:37:1 + | +LL | const extern "C" fn f4_2(x: isize, _: ...) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #151787 for more information + = help: add `#![feature(const_c_variadic)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + error: functions with a C variable argument list must be unsafe - --> $DIR/variadic-ffi-semantic-restrictions.rs:36:36 + --> $DIR/variadic-ffi-semantic-restrictions.rs:37:36 | LL | const extern "C" fn f4_2(x: isize, _: ...) {} | ^^^^^^ @@ -92,13 +112,23 @@ LL | const unsafe extern "C" fn f4_2(x: isize, _: ...) {} | ++++++ error: `...` must be the last argument of a C-variadic function - --> $DIR/variadic-ffi-semantic-restrictions.rs:40:26 + --> $DIR/variadic-ffi-semantic-restrictions.rs:42:26 | LL | const extern "C" fn f4_3(_: ..., x: isize, _: ...) {} | ^^^^^^ +error[E0658]: c-variadic const function definitions are unstable + --> $DIR/variadic-ffi-semantic-restrictions.rs:42:1 + | +LL | const extern "C" fn f4_3(_: ..., x: isize, _: ...) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #151787 for more information + = help: add `#![feature(const_c_variadic)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + error: functions with a C variable argument list must be unsafe - --> $DIR/variadic-ffi-semantic-restrictions.rs:40:44 + --> $DIR/variadic-ffi-semantic-restrictions.rs:42:44 | LL | const extern "C" fn f4_3(_: ..., x: isize, _: ...) {} | ^^^^^^ @@ -109,13 +139,13 @@ LL | const unsafe extern "C" fn f4_3(_: ..., x: isize, _: ...) {} | ++++++ error: `...` must be the last argument of a C-variadic function - --> $DIR/variadic-ffi-semantic-restrictions.rs:45:13 + --> $DIR/variadic-ffi-semantic-restrictions.rs:48:13 | LL | fn e_f2(..., x: isize); | ^^^ error: `...` is not supported for non-extern functions - --> $DIR/variadic-ffi-semantic-restrictions.rs:52:23 + --> $DIR/variadic-ffi-semantic-restrictions.rs:55:23 | LL | fn i_f1(x: isize, _: ...) {} | ^^^^^^ @@ -123,7 +153,7 @@ LL | fn i_f1(x: isize, _: ...) {} = help: only `extern "C"` and `extern "C-unwind"` functions may have a C variable argument list error: `...` is not supported for non-extern functions - --> $DIR/variadic-ffi-semantic-restrictions.rs:54:13 + --> $DIR/variadic-ffi-semantic-restrictions.rs:57:13 | LL | fn i_f2(_: ...) {} | ^^^^^^ @@ -131,13 +161,13 @@ LL | fn i_f2(_: ...) {} = help: only `extern "C"` and `extern "C-unwind"` functions may have a C variable argument list error: `...` must be the last argument of a C-variadic function - --> $DIR/variadic-ffi-semantic-restrictions.rs:56:13 + --> $DIR/variadic-ffi-semantic-restrictions.rs:59:13 | LL | fn i_f3(_: ..., x: isize, _: ...) {} | ^^^^^^ error: `...` is not supported for non-extern functions - --> $DIR/variadic-ffi-semantic-restrictions.rs:56:31 + --> $DIR/variadic-ffi-semantic-restrictions.rs:59:31 | LL | fn i_f3(_: ..., x: isize, _: ...) {} | ^^^^^^ @@ -145,21 +175,31 @@ LL | fn i_f3(_: ..., x: isize, _: ...) {} = help: only `extern "C"` and `extern "C-unwind"` functions may have a C variable argument list error: `...` must be the last argument of a C-variadic function - --> $DIR/variadic-ffi-semantic-restrictions.rs:59:13 + --> $DIR/variadic-ffi-semantic-restrictions.rs:62:13 | LL | fn i_f4(_: ..., x: isize, _: ...) {} | ^^^^^^ error: `...` is not supported for non-extern functions - --> $DIR/variadic-ffi-semantic-restrictions.rs:59:31 + --> $DIR/variadic-ffi-semantic-restrictions.rs:62:31 | LL | fn i_f4(_: ..., x: isize, _: ...) {} | ^^^^^^ | = help: only `extern "C"` and `extern "C-unwind"` functions may have a C variable argument list +error[E0658]: c-variadic const function definitions are unstable + --> $DIR/variadic-ffi-semantic-restrictions.rs:65:5 + | +LL | const fn i_f5(x: isize, _: ...) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #151787 for more information + = help: add `#![feature(const_c_variadic)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + error: `...` is not supported for non-extern functions - --> $DIR/variadic-ffi-semantic-restrictions.rs:62:29 + --> $DIR/variadic-ffi-semantic-restrictions.rs:65:29 | LL | const fn i_f5(x: isize, _: ...) {} | ^^^^^^ @@ -167,7 +207,7 @@ LL | const fn i_f5(x: isize, _: ...) {} = help: only `extern "C"` and `extern "C-unwind"` functions may have a C variable argument list error: `...` is not supported for non-extern functions - --> $DIR/variadic-ffi-semantic-restrictions.rs:68:23 + --> $DIR/variadic-ffi-semantic-restrictions.rs:72:23 | LL | fn t_f1(x: isize, _: ...) {} | ^^^^^^ @@ -175,7 +215,7 @@ LL | fn t_f1(x: isize, _: ...) {} = help: only `extern "C"` and `extern "C-unwind"` functions may have a C variable argument list error: `...` is not supported for non-extern functions - --> $DIR/variadic-ffi-semantic-restrictions.rs:70:23 + --> $DIR/variadic-ffi-semantic-restrictions.rs:74:23 | LL | fn t_f2(x: isize, _: ...); | ^^^^^^ @@ -183,7 +223,7 @@ LL | fn t_f2(x: isize, _: ...); = help: only `extern "C"` and `extern "C-unwind"` functions may have a C variable argument list error: `...` is not supported for non-extern functions - --> $DIR/variadic-ffi-semantic-restrictions.rs:72:13 + --> $DIR/variadic-ffi-semantic-restrictions.rs:76:13 | LL | fn t_f3(_: ...) {} | ^^^^^^ @@ -191,7 +231,7 @@ LL | fn t_f3(_: ...) {} = help: only `extern "C"` and `extern "C-unwind"` functions may have a C variable argument list error: `...` is not supported for non-extern functions - --> $DIR/variadic-ffi-semantic-restrictions.rs:74:13 + --> $DIR/variadic-ffi-semantic-restrictions.rs:78:13 | LL | fn t_f4(_: ...); | ^^^^^^ @@ -199,13 +239,13 @@ LL | fn t_f4(_: ...); = help: only `extern "C"` and `extern "C-unwind"` functions may have a C variable argument list error: `...` must be the last argument of a C-variadic function - --> $DIR/variadic-ffi-semantic-restrictions.rs:76:13 + --> $DIR/variadic-ffi-semantic-restrictions.rs:80:13 | LL | fn t_f5(_: ..., x: isize) {} | ^^^^^^ error: `...` must be the last argument of a C-variadic function - --> $DIR/variadic-ffi-semantic-restrictions.rs:78:13 + --> $DIR/variadic-ffi-semantic-restrictions.rs:82:13 | LL | fn t_f6(_: ..., x: isize); | ^^^^^^ @@ -223,7 +263,7 @@ LL | const unsafe extern "C" fn f4_1(x: isize, _: ...) {} = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0493]: destructor of `VaList<'_>` cannot be evaluated at compile-time - --> $DIR/variadic-ffi-semantic-restrictions.rs:36:36 + --> $DIR/variadic-ffi-semantic-restrictions.rs:37:36 | LL | const extern "C" fn f4_2(x: isize, _: ...) {} | ^ - value is dropped here @@ -235,7 +275,7 @@ LL | const extern "C" fn f4_2(x: isize, _: ...) {} = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0493]: destructor of `VaList<'_>` cannot be evaluated at compile-time - --> $DIR/variadic-ffi-semantic-restrictions.rs:62:29 + --> $DIR/variadic-ffi-semantic-restrictions.rs:65:29 | LL | const fn i_f5(x: isize, _: ...) {} | ^ - value is dropped here @@ -246,6 +286,7 @@ LL | const fn i_f5(x: isize, _: ...) {} = help: add `#![feature(const_destruct)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date -error: aborting due to 29 previous errors +error: aborting due to 33 previous errors -For more information about this error, try `rustc --explain E0493`. +Some errors have detailed explanations: E0493, E0658. +For more information about an error, try `rustc --explain E0493`. From 34683dbd81da4ab01c5a47c774d9e9f7bc63fef0 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Fri, 30 Jan 2026 00:41:49 +0100 Subject: [PATCH 36/40] use vecdeque --- compiler/rustc_const_eval/src/interpret/intrinsics.rs | 10 ++++------ compiler/rustc_const_eval/src/interpret/memory.rs | 10 +++++----- compiler/rustc_const_eval/src/interpret/stack.rs | 5 +++-- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/compiler/rustc_const_eval/src/interpret/intrinsics.rs b/compiler/rustc_const_eval/src/interpret/intrinsics.rs index e662beb950a4c..b109165ccb413 100644 --- a/compiler/rustc_const_eval/src/interpret/intrinsics.rs +++ b/compiler/rustc_const_eval/src/interpret/intrinsics.rs @@ -749,7 +749,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { let key = self.read_pointer(&key_mplace)?; let varargs = self.get_ptr_va_list(key)?; - let copy_key = self.va_list_ptr(varargs.to_vec()); + let copy_key = self.va_list_ptr(varargs.clone()); let copy_key_mplace = self.va_list_key_field(dest)?; self.write_pointer(copy_key, ©_key_mplace)?; @@ -772,11 +772,9 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { // new list (one element shorter) below. let mut varargs = self.deallocate_va_list(key)?; - if varargs.is_empty() { - throw_ub!(VaArgOutOfBounds) - } - - let arg_mplace = varargs.remove(0); + let Some(arg_mplace) = varargs.pop_front() 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 diff --git a/compiler/rustc_const_eval/src/interpret/memory.rs b/compiler/rustc_const_eval/src/interpret/memory.rs index efc4ef0b0ae9c..35aa292a130e9 100644 --- a/compiler/rustc_const_eval/src/interpret/memory.rs +++ b/compiler/rustc_const_eval/src/interpret/memory.rs @@ -129,7 +129,7 @@ pub struct Memory<'tcx, M: Machine<'tcx>> { extra_fn_ptr_map: FxIndexMap, /// Map storing variable argument lists. - va_list_map: FxIndexMap>>, + va_list_map: FxIndexMap>>, /// To be able to compare pointers with null, and to check alignment for accesses /// to ZSTs (where pointers may dangle), we keep track of the size even for allocations @@ -240,7 +240,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { /// Insert a new variable argument list in the global map of variable argument lists. pub fn va_list_ptr( &mut self, - varargs: Vec>, + varargs: VecDeque>, ) -> Pointer { let id = self.tcx.reserve_alloc_id(); let old = self.memory.va_list_map.insert(id, varargs); @@ -1102,7 +1102,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { pub fn get_ptr_va_list( &self, ptr: Pointer>, - ) -> InterpResult<'tcx, &[MPlaceTy<'tcx, M::Provenance>]> { + ) -> InterpResult<'tcx, &VecDeque>> { trace!("get_ptr_va_list({:?})", ptr); let (alloc_id, offset, _prov) = self.ptr_get_alloc_id(ptr, 0)?; if offset.bytes() != 0 { @@ -1113,7 +1113,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { throw_ub!(InvalidVaListPointer(Pointer::new(alloc_id, offset))) }; - interp_ok(va_list.as_slice()) + interp_ok(va_list) } /// Removes this VaList from the global map of variable argument lists. This does not deallocate @@ -1121,7 +1121,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { pub fn deallocate_va_list( &mut self, ptr: Pointer>, - ) -> InterpResult<'tcx, Vec>> { + ) -> InterpResult<'tcx, VecDeque>> { trace!("deallocate_va_list({:?})", ptr); let (alloc_id, offset, _prov) = self.ptr_get_alloc_id(ptr, 0)?; if offset.bytes() != 0 { diff --git a/compiler/rustc_const_eval/src/interpret/stack.rs b/compiler/rustc_const_eval/src/interpret/stack.rs index 7b8dfbcc8ca0e..519e4f5fcc19b 100644 --- a/compiler/rustc_const_eval/src/interpret/stack.rs +++ b/compiler/rustc_const_eval/src/interpret/stack.rs @@ -1,6 +1,7 @@ //! Manages the low-level pushing and popping of stack frames and the (de)allocation of local variables. //! For handling of argument passing and return values, see the `call` module. use std::cell::Cell; +use std::collections::VecDeque; use std::{fmt, mem}; use either::{Either, Left, Right}; @@ -639,7 +640,7 @@ impl<'a, 'tcx: 'a, M: Machine<'tcx>> InterpCx<'tcx, M> { pub(crate) fn allocate_varargs( &mut self, caller_args: &mut I, - ) -> InterpResult<'tcx, Vec>> + ) -> InterpResult<'tcx, VecDeque>> where I: Iterator, &'a ArgAbi<'tcx, Ty<'tcx>>)>, { @@ -657,7 +658,7 @@ impl<'a, 'tcx: 'a, M: Machine<'tcx>> InterpCx<'tcx, M> { // When the frame is dropped, these variable arguments are deallocated. self.frame_mut().va_list = varargs.clone(); - interp_ok(varargs) + interp_ok(varargs.into()) } fn deallocate_varargs( From 49a59679a521dd5782ad1defa627cd1e983a54f0 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Sat, 31 Jan 2026 14:40:29 +0100 Subject: [PATCH 37/40] changes after code review --- .../rustc_const_eval/src/interpret/call.rs | 20 ++++++++++++------- .../src/interpret/intrinsics.rs | 5 ++--- .../rustc_const_eval/src/interpret/stack.rs | 8 ++------ .../tests/fail/c-variadic-mismatch-count.rs | 2 +- .../fail/c-variadic-mismatch-count.stderr | 4 ++-- 5 files changed, 20 insertions(+), 19 deletions(-) diff --git a/compiler/rustc_const_eval/src/interpret/call.rs b/compiler/rustc_const_eval/src/interpret/call.rs index 6deb537c7aade..f256d1f29c3af 100644 --- a/compiler/rustc_const_eval/src/interpret/call.rs +++ b/compiler/rustc_const_eval/src/interpret/call.rs @@ -371,7 +371,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { (fixed_count, self.tcx.mk_type_list_from_iter(extra_tys)) } else { - (caller_fn_abi.args.len(), ty::List::empty()) + (caller_fn_abi.fixed_count.try_into().unwrap(), ty::List::empty()) }; let callee_fn_abi = self.fn_abi_of_instance(instance, callee_c_variadic_args)?; @@ -393,7 +393,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { }); } - if caller_fn_abi.fixed_count != callee_fn_abi.fixed_count { + if caller_fn_abi.c_variadic && caller_fn_abi.fixed_count != callee_fn_abi.fixed_count { throw_ub!(CVariadicFixedCountMismatch { caller: caller_fn_abi.fixed_count, callee: callee_fn_abi.fixed_count, @@ -472,12 +472,16 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { // `pass_argument` would be the loop body. It takes care to // not advance `caller_iter` for ignored arguments. let mut callee_args_abis = if caller_fn_abi.c_variadic { - // Only the fixed arguments are passed normally. + // Only the fixed arguments are passed normally. C-variadic functions cannot be + // `extern "Rust"` and `#[track_caller]` can only be applied to `extern "Rust"`, to + // the extra caller location argument is not relevant here. + assert!(!instance.def.requires_caller_location(*self.tcx)); callee_fn_abi.args[..fixed_count].iter().enumerate() } else { - // NOTE: this handles the extra caller location argument - // when `#[track_caller]` is used. This attribute is only allowed on `extern "Rust"` - // functions, so the c-variadic case does not need to handle the extra argument. + // NOTE: this handles the extra caller location argument that is passed when + // `#[track_caller]` is used. The `fixed_count` does not account for this argument. + // This attribute is only allowed on `extern "Rust"` functions, so the c-variadic + // case does not need to handle the extra argument. callee_fn_abi.args.iter().enumerate() }; @@ -501,7 +505,9 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { // Consume the remaining arguments by putting them into the variable argument // list. let varargs = self.allocate_varargs(&mut caller_args)?; - let key = self.va_list_ptr(varargs); + // When the frame is dropped, these variable arguments are deallocated. + self.frame_mut().va_list = varargs.clone(); + let key = self.va_list_ptr(varargs.into()); // Zero the VaList, so it is fully initialized. self.write_bytes_ptr( diff --git a/compiler/rustc_const_eval/src/interpret/intrinsics.rs b/compiler/rustc_const_eval/src/interpret/intrinsics.rs index b109165ccb413..6a06f7a0f0128 100644 --- a/compiler/rustc_const_eval/src/interpret/intrinsics.rs +++ b/compiler/rustc_const_eval/src/interpret/intrinsics.rs @@ -1287,13 +1287,12 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { let va_list_inner = self.project_field(va_list, FieldIdx::ZERO)?; // Find the first pointer field in this struct. The exact index is target-specific. - let ty::Adt(adt, _substs) = va_list_inner.layout().ty.kind() else { + let ty::Adt(adt, substs) = va_list_inner.layout().ty.kind() else { bug!("invalid VaListImpl layout"); }; for (i, field) in adt.non_enum_variant().fields.iter().enumerate() { - let field_ty = self.tcx.type_of(field.did); - if field_ty.skip_binder().is_raw_ptr() { + if field.ty(*self.tcx, substs).is_raw_ptr() { return self.project_field(&va_list_inner, FieldIdx::from_usize(i)); } } diff --git a/compiler/rustc_const_eval/src/interpret/stack.rs b/compiler/rustc_const_eval/src/interpret/stack.rs index 519e4f5fcc19b..60410a2d9a449 100644 --- a/compiler/rustc_const_eval/src/interpret/stack.rs +++ b/compiler/rustc_const_eval/src/interpret/stack.rs @@ -1,7 +1,6 @@ //! Manages the low-level pushing and popping of stack frames and the (de)allocation of local variables. //! For handling of argument passing and return values, see the `call` module. use std::cell::Cell; -use std::collections::VecDeque; use std::{fmt, mem}; use either::{Either, Left, Right}; @@ -640,7 +639,7 @@ impl<'a, 'tcx: 'a, M: Machine<'tcx>> InterpCx<'tcx, M> { pub(crate) fn allocate_varargs( &mut self, caller_args: &mut I, - ) -> InterpResult<'tcx, VecDeque>> + ) -> InterpResult<'tcx, Vec>> where I: Iterator, &'a ArgAbi<'tcx, Ty<'tcx>>)>, { @@ -655,10 +654,7 @@ impl<'a, 'tcx: 'a, M: Machine<'tcx>> InterpCx<'tcx, M> { varargs.push(mplace); } - // When the frame is dropped, these variable arguments are deallocated. - self.frame_mut().va_list = varargs.clone(); - - interp_ok(varargs.into()) + interp_ok(varargs) } fn deallocate_varargs( diff --git a/src/tools/miri/tests/fail/c-variadic-mismatch-count.rs b/src/tools/miri/tests/fail/c-variadic-mismatch-count.rs index 2bbb9ff4a557f..c01860cd1df90 100644 --- a/src/tools/miri/tests/fail/c-variadic-mismatch-count.rs +++ b/src/tools/miri/tests/fail/c-variadic-mismatch-count.rs @@ -7,7 +7,7 @@ fn main() { let f = helper as *const (); let f = std::mem::transmute::<_, unsafe extern "C" fn(...)>(f); - f(); + f(1); //~^ ERROR: Undefined Behavior } } diff --git a/src/tools/miri/tests/fail/c-variadic-mismatch-count.stderr b/src/tools/miri/tests/fail/c-variadic-mismatch-count.stderr index 212b190595164..9350b99861964 100644 --- a/src/tools/miri/tests/fail/c-variadic-mismatch-count.stderr +++ b/src/tools/miri/tests/fail/c-variadic-mismatch-count.stderr @@ -1,8 +1,8 @@ error: Undefined Behavior: calling a C-variadic function with 0 fixed arguments, but the function expects 1 --> tests/fail/c-variadic-mismatch-count.rs:LL:CC | -LL | f(); - | ^^^ Undefined Behavior occurred here +LL | f(1); + | ^^^^ Undefined Behavior occurred here | = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information From 6db8088e132ddc6d50c4633cf658260d1f037afd Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Sun, 8 Feb 2026 22:50:36 +0100 Subject: [PATCH 38/40] Update compiler/rustc_const_eval/src/interpret/call.rs Co-authored-by: Ralf Jung --- compiler/rustc_const_eval/src/interpret/call.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_const_eval/src/interpret/call.rs b/compiler/rustc_const_eval/src/interpret/call.rs index f256d1f29c3af..7ffc517dd3eb8 100644 --- a/compiler/rustc_const_eval/src/interpret/call.rs +++ b/compiler/rustc_const_eval/src/interpret/call.rs @@ -472,9 +472,11 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { // `pass_argument` would be the loop body. It takes care to // not advance `caller_iter` for ignored arguments. let mut callee_args_abis = if caller_fn_abi.c_variadic { - // Only the fixed arguments are passed normally. C-variadic functions cannot be - // `extern "Rust"` and `#[track_caller]` can only be applied to `extern "Rust"`, to - // the extra caller location argument is not relevant here. + // Only the fixed arguments are passed normally. All remaining arguments are + // put into the va_list. Crucially, there can't be a caller location among those + // remaining arguments, so the caller location handling below does not interfere + // with the variadic argument handling: C-variadic functions cannot be + // `extern "Rust"` and `#[track_caller]` can only be applied to `extern "Rust"`. assert!(!instance.def.requires_caller_location(*self.tcx)); callee_fn_abi.args[..fixed_count].iter().enumerate() } else { From bb063e18396ab3993d3a76026f65fadc437e20cd Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Sun, 8 Feb 2026 22:50:54 +0100 Subject: [PATCH 39/40] Update compiler/rustc_const_eval/src/interpret/stack.rs Co-authored-by: Ralf Jung --- compiler/rustc_const_eval/src/interpret/stack.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/rustc_const_eval/src/interpret/stack.rs b/compiler/rustc_const_eval/src/interpret/stack.rs index 60410a2d9a449..5cc93c4dff36c 100644 --- a/compiler/rustc_const_eval/src/interpret/stack.rs +++ b/compiler/rustc_const_eval/src/interpret/stack.rs @@ -657,6 +657,7 @@ impl<'a, 'tcx: 'a, M: Machine<'tcx>> InterpCx<'tcx, M> { interp_ok(varargs) } + /// Deallocate the variadic arguments in the list (that must have been created with `allocate_varargs`). fn deallocate_varargs( &mut self, varargs: &[MPlaceTy<'tcx, M::Provenance>], From 13f117894c96ae298fea309de02d07442cad6d48 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Sun, 8 Feb 2026 22:51:03 +0100 Subject: [PATCH 40/40] Update compiler/rustc_const_eval/src/interpret/stack.rs Co-authored-by: Ralf Jung --- compiler/rustc_const_eval/src/interpret/stack.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/compiler/rustc_const_eval/src/interpret/stack.rs b/compiler/rustc_const_eval/src/interpret/stack.rs index 5cc93c4dff36c..5885d10b001ac 100644 --- a/compiler/rustc_const_eval/src/interpret/stack.rs +++ b/compiler/rustc_const_eval/src/interpret/stack.rs @@ -636,6 +636,8 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { } impl<'a, 'tcx: 'a, M: Machine<'tcx>> InterpCx<'tcx, M> { + /// Consume the arguments provided by the iterator and store them as a list + /// of variadic arguments. Return a list of the places that hold those arguments. pub(crate) fn allocate_varargs( &mut self, caller_args: &mut I,