Rollup merge of #153398 - folkertdev:const-c-variadic-trailing-zst, r=RalfJung

fix ICE in `const_c_variadic` when passing ZSTs

fixes https://github.com/rust-lang/rust/issues/153351
r? RalfJung

There was a mismatch between the caller and callee ABI where the caller does not pass ZST arguments, but the callee does expect them. Because ZSTs don't implement `VaArgSafe` the program must already be invalid if this comes up.
This commit is contained in:
Jonathan Brouwer
2026-03-09 17:56:16 +01:00
committed by GitHub
4 changed files with 50 additions and 6 deletions
@@ -12,7 +12,7 @@
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_target::callconv::{ArgAbi, FnAbi, PassMode};
use rustc_target::callconv::{ArgAbi, FnAbi};
use tracing::field::Empty;
use tracing::{info, instrument, trace};
@@ -284,7 +284,7 @@ fn pass_argument<'x, 'y>(
'tcx: 'y,
{
assert_eq!(callee_ty, callee_abi.layout.ty);
if callee_abi.mode == PassMode::Ignore {
if callee_abi.is_ignore() {
// This one is skipped. Still must be made live though!
if !already_live {
self.storage_live(callee_arg.as_local().unwrap())?;
@@ -450,7 +450,7 @@ pub fn init_stack_frame(
let mut caller_args = args
.iter()
.zip(caller_fn_abi.args.iter())
.filter(|arg_and_abi| !matches!(arg_and_abi.1.mode, PassMode::Ignore));
.filter(|arg_and_abi| !arg_and_abi.1.is_ignore());
// Now we have to spread them out across the callee's locals,
// taking into account the `spread_arg`. If we could write
@@ -480,7 +480,12 @@ pub fn init_stack_frame(
// Consume the remaining arguments by putting them into the variable argument
// list.
let varargs = self.allocate_varargs(&mut caller_args, &mut callee_args_abis)?;
let varargs = self.allocate_varargs(
&mut caller_args,
// "Ignored" arguments aren't actually passed, so the callee should also
// ignore them. (`pass_argument` does this for regular arguments.)
(&mut callee_args_abis).filter(|(_, abi)| !abi.is_ignore()),
)?;
// 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());
@@ -631,8 +631,8 @@ impl<'a, 'tcx: 'a, M: Machine<'tcx>> InterpCx<'tcx, M> {
/// of variadic arguments. Return a list of the places that hold those arguments.
pub(crate) fn allocate_varargs<I, J>(
&mut self,
caller_args: &mut I,
callee_abis: &mut J,
caller_args: I,
mut callee_abis: J,
) -> InterpResult<'tcx, Vec<MPlaceTy<'tcx, M::Provenance>>>
where
I: Iterator<Item = (&'a FnArg<'tcx, M::Provenance>, &'a ArgAbi<'tcx, Ty<'tcx>>)>,
@@ -0,0 +1,21 @@
//@ ignore-target: windows # does not ignore ZST arguments
//@ ignore-target: powerpc # does not ignore ZST arguments
//@ ignore-target: s390x # does not ignore ZST arguments
//@ ignore-target: sparc # does not ignore ZST arguments
#![feature(c_variadic)]
// Some platforms ignore ZSTs, meaning that the argument is not passed, even though it is part
// of the callee's ABI. Test that this doesn't trip any asserts.
//
// NOTE: this test only succeeds when the `()` argument uses `Passmode::Ignore`. For some targets,
// notably msvc, such arguments are not ignored, which would cause UB when attempting to read the
// second `i32` argument while the next item in the variable argument list is `()`.
fn main() {
unsafe extern "C" fn variadic(mut ap: ...) {
ap.arg::<i32>();
ap.arg::<i32>();
}
unsafe { variadic(0i32, (), 1i32) }
}
@@ -0,0 +1,18 @@
//@ build-pass
//@ compile-flags: --emit=obj
#![feature(c_variadic)]
#![feature(const_c_variadic)]
#![feature(const_destruct)]
#![crate_type = "lib"]
// Regression test for when a c-variadic argument is `PassMode::Ignore`. The caller won't pass the
// argument, but the callee ABI does have the argument. Ensure that const-eval is able to handle
// this case without tripping any asserts.
const unsafe extern "C" fn read_n<const N: usize>(_: ...) {}
unsafe fn read_too_many() {
const { read_n::<0>((), 1i32) }
}
fn read_as<T>() -> () {}