Skip to content

improve core::ffi::VaList #141835

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

folkertdev
Copy link
Contributor

@folkertdev folkertdev commented May 31, 2025

tracking issue: #44930

This PR refactors the VaList API. The highlights are

  • VaListImpl is no longer user-visible (and internally renamed to VaListTag)
  • VaList now has only a single lifetime parameter
  • a new va_copy! macro is added to duplicate a VaList

This PR mostly touches the core implementation of VaList, but needs some changes in the rest of the compiler to make it all work.

This change was discussed in #141524.


This next section is a kind of pseudo-rfc: it describes what the c_variadic feature is, and what API surface we have. The lang team likely wants to see a new RFC or extensive stabilization report before stabilizing c_variadic.

Syntax

A c-variadic function looks like this:

#![feature(c_variadic)]

unsafe extern "C" fn foo(a: i32, b: i32, args: ...) {
    /* body */
}

The special ... argument stands in for an arbitrary number of arguments that the caller may pass. The compiler has no way of verifying that the arguments that are passed to a c-variadic function are correct, and therefore calling a c-variadic function is always unsafe.

The ... argument must be the last argument in the parameter list of a function. Contrary to earlier versions of C, but in line with C23, in rust the ... argument can be the first (and therefore only) argument to a function. The ... syntax is already stable in foreign functions, c_variadic additionally allows it in function definitions.

A function with a ... argument must be an unsafe function. Passing an incorrect number of arguments, or arguments of the wrong type is UB, and hence every call site should have a safety comment.

This document only considers functions with the "C" and "C-unwind" calling conventions. It is possible to in the future also support e.g. "sysv64" or "win64", but for now we consider that to be out-of-scope.

VaList

The type of ... is core::ffi::VaList: the list of variadic arguments. The rust VaList type on a given platform is equivalent to the C va_list type on that platform: values of this type can be passed to C via FFI, and used like any other va_list value.

Across ABIs, there are two ways that VaList is implemented:

  • the "pointer" approach: the VaList is just an opaque pointer. (in practice it is usually a pointer to the caller's stack where the variadic arguments are stored)
  • the "struct on stack" approach: the VaList is a mutable reference to a structure (called VaListTag) on the callee's stack.

In both cases, there are lifetime constraints on the VaList: it must not escape the function in which it was defined!

desugaring ...

A function

unsafe extern "C" fn foo(args: ...) {
    // ...
}

Semantically desugars in one of two ways, depending on the ABI of the target platform. The actual desugaring is built into the compiler, these examples just illustrate what happens.

the "pointer" approach

struct VaListTag<'a> { 
    ptr: *mut c_void,
    _marker: PhantomData<&'a mut ()>
}

struct VaList(VaListTag<'a>);

fn foo() {
    fn shorten_lifetime<'a>(&'a ()) -> PhantomData<'a> { PhantomData }

    let x = ();
    let reference_to_stack = &x;

    let tag = MaybeUninit::<VaListTag>::uninit();
    va_start(tag.as_mut_ptr())
    let args = unsafe { 
        VaList { 
            ptr: tag.assume_init()
            _marker: shorten_lifetime(reference_to_stack),
    };

    // ...

    va_end(&mut args.ptr)
}

the "struct on stack" approach

struct VaListTag<'a> { ... }

struct VaList(&'a mut VaListTag<'a>);

fn foo() {
    let tag = MaybeUninit::<VaListTag>::uninit();
    va_start(tag.as_mut_ptr())
    let args = unsafe { VaList(tag.assume_init_mut()) }

    // ...

    va_end(args.0)
}

The builtin desugaring process guarantees the correct lifetime is selected for VaList , and that the call to va_end is inserted on all return paths.

va_arg

In C, the va_arg macro is used to read the next variadic argument. Rust exposes this functionality as

impl VaList<'_> {
    unsafe fn arg<T: VaArgSafe>(&mut self) -> T { /* ... * / }
}

The arg method is unsafe because attempting to read more arguments than were passed or an argument of the wrong type is undefined behavior.

The type parameter T is constrained by the VaArgSafe trait: small numeric types are subject to implicit upcasting in C. Attempting to read a value of such a type (e.g. bool, i8, u16, f32) is undefined behavior. The VaArgSafe trait is only implemented for numeric types for which no implicit upcasting occurs, and for const/mut pointer types.

The VaArgSafe trait is public, but cannot currently be implemented outside of core.

A note on LLVM va_arg

The llvm va_arg intrinsic is known to silently miscompile. I believe this is due to a combination of:

  • LLVM does not have sufficient layout information to accurately implement the ABI
  • Clang provides its own implementation of va_arg, so the LLVM implementation is mostly untested

Hence, like clang, rustc implements va_arg for most commonly-used targets (specifically including all tier-1 targets) in va_arg.rs. If no custom implementation is provided, the LLVM implementation is used as a fallback. But again, it may silently miscompile the input program.

the va_copy! macro

The VaList type has a with_copy method, that provides access to a copy of the VaList. However, c2rust ran into this method not being flexible enough to translate C programs seen in the wild (immunant/c2rust#43). The va_copy! macro mirrors the C va_copy macro, enabling a straightforward translation from C to rust.

The concrete pattern that this macro enables is to (easily) iterate over two copies of the VaList in parallel.

The implementation of this macro uses super let to give the copied VaList the correct lifetime. Currently it relies on some #[doc(hidden)] methods on VaList, so that VaListTag does not need to be exposed.

C-variadics and const fn

C-variadics cannot be const fn at the time of writing. This is a limitation in MIRI that may be lifted at some point.

Other calling conventions

For now we only consider the "C" and "C-unwind" calling conventions. Other calling conventions are disallowed for now, but we have considered how support may be added in the future.

The difficulty is that currently the target determines the layout of VaList and VaListTag. Accepting multiple c-variadic functions with ABIs in the same program means that the layout of those types may be different depending on the exact ABI that is used in a function. We also must be clear about when a rust VaList is compatible with the C va_list type.

Speaking of C, clang and gcc won't compile a function that uses variadic arguments and a non-standard calling convention. See also #141618, in particular this comment

Nevertheless, two potential solutions for supporting multiple c-variadic ABIs in the same program have been proposed.

A generic parameter with a default

The VaList structure can be extended like so

trait VaListAbi {
	type Abi;
}

#[cfg(windows)]
struct VaListWin64<'a> { /* ... */ }

#[cfg(windows)]
type C<'a> = VaListWin64<'a>;

struct VaList<'a, Abi: VaListAbi = C<'a>> { 
    tag: &'a mut Abi::Tag,
    _abi: PhantomData<Abi>
}

The ... parameter in a c-variadic extern "C" fn will have type VaList<'a, C>, which is guaranteed to be compatible with C va_list type for the target platform. Functions using a non-standard abi get a type like VaList<'a, Win64>.

Multiple types

Alternatively, every abi could get its own VaList type, and ... in an extern "C" fn desugars to the type that is compatible with the C va_list type for the target platform.

A type alias could be used to always be able to refer to the C-compatible VaList type.


Review notes

  • The changes to the lang items could be split into their own PR, cleaning things up here
  • The part I'm least sure about is the va_copy! macro. Maybe there are some tricks in core to make that nicer? (e.g. make VaListTag public, but #[doc(hidden)] and perma-unstable somehow, but then allow that unstable feature in the va_copy macro expansion?)

r? @joshtriplett
cc @workingjubilee
@rustbot label: +F-c_variadic

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-libs Relevant to the library team, which will review and decide on the PR/issue. labels May 31, 2025
@rustbot
Copy link
Collaborator

rustbot commented May 31, 2025

Some changes occurred in compiler/rustc_codegen_ssa

cc @WaffleLapkin

@rustbot rustbot added the F-c_variadic `#![feature(c_variadic)]` label May 31, 2025
@rust-log-analyzer

This comment has been minimized.

@deltragon
Copy link
Contributor

Contrary to C, in rust the ... argument can be the first (and therefore only) argument to a function.

AFAICT, this is no longer true since N2975 was accepted into C23, so Rust is simply directly mirroring C here.

@workingjubilee
Copy link
Member

Correct, we decided to follow C after that + a lang decision a while back.

Comment on lines 147 to 166
pub fn copy(
&self,
tag: &mut MaybeUninit<[*const (); size_of::<VaListTag<'_>>() / size_of::<*const ()>()]>,
) -> Self {
const {
type A = [*const (); size_of::<VaListTag<'_>>() / size_of::<*const ()>()];
assert!(size_of::<A>() == size_of::<VaListTag<'_>>());
assert!(align_of::<A>() == align_of::<VaListTag<'_>>());
}

// SAFETY: we verified that the type has the right size and alignment.
let tag = unsafe { &mut *(tag as *mut MaybeUninit<_> as *mut MaybeUninit<VaListTag<'a>>) };

// SAFETY: `self` is a valid `va_list`, `tag` has sufficient space to store a `va_list`.
unsafe { va_copy_intrinsic::va_copy(tag.as_mut_ptr(), &self.inner) };

// SAFETY: `va_copy` initializes the tag.
VaList { inner: unsafe { tag.assume_init_mut() } }
}
Copy link
Contributor

@beetrees beetrees Jun 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The returned VaList has the lifetime 'a, which isn't connected to the lifetime of the &mut reference to the tag. This means that the returned VaList<'a> can outlive the tag, causing undefined behaviour. (This doesn't cause a compiler error because the unsafe { &mut *(tag as *mut MaybeUninit<_> as *mut MaybeUninit<VaListTag<'a>>) } pointer cast discards the lifetime.)

The lifetimes need to look something like pub fn copy<'b>(&self, tag: &'b mut MaybeUninit<VaListTag<'a>>) -> VaList<'b> where 'a: 'b.

With regards to the type of the tag parameter, I think it should be ok to just use &mut MaybeUninit<VaListTag<'a>> and just #[expect(private_interfaces)] the lint.

Copy link
Contributor Author

@folkertdev folkertdev Jun 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Allright, I've reworked that interface a bunch, the 'a: 'b idea worked out nicely, and makes a lot more sense in hindsight.

For the time being the va_copy intrinsic still equates the lifetimes of the input and output. Once we have the new stage0 (and hence no more #[cfg(bootstrap)]) that can maybe change, but for now that was too tricky (for me anyway).

@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer
Copy link
Collaborator

The job x86_64-gnu-llvm-19 failed! Check out the build log: (web) (plain)

Click to see the possible cause of the failure (guessed by this bot)
#18 exporting to docker image format
#18 sending tarball 27.4s done
#18 DONE 34.7s
##[endgroup]
Setting extra environment values for docker:  --env ENABLE_GCC_CODEGEN=1 --env GCC_EXEC_PREFIX=/usr/lib/gcc/
[CI_JOB_NAME=x86_64-gnu-llvm-19]
[CI_JOB_NAME=x86_64-gnu-llvm-19]
debug: `DISABLE_CI_RUSTC_IF_INCOMPATIBLE` configured.
---
sccache: Listening on address 127.0.0.1:4226
##[group]Configure the build
configure: processing command line
configure: 
configure: build.configure-args := ['--build=x86_64-unknown-linux-gnu', '--llvm-root=/usr/lib/llvm-19', '--enable-llvm-link-shared', '--set', 'rust.randomize-layout=true', '--set', 'rust.thin-lto-import-instr-limit=10', '--set', 'build.print-step-timings', '--enable-verbose-tests', '--set', 'build.metrics', '--enable-verbose-configure', '--enable-sccache', '--disable-manage-submodules', '--enable-locked-deps', '--enable-cargo-native-static', '--set', 'rust.codegen-units-std=1', '--set', 'dist.compression-profile=balanced', '--dist-compression-formats=xz', '--set', 'rust.lld=false', '--disable-dist-src', '--release-channel=nightly', '--enable-debug-assertions', '--enable-overflow-checks', '--enable-llvm-assertions', '--set', 'rust.verify-llvm-ir', '--set', 'rust.codegen-backends=llvm,cranelift,gcc', '--set', 'llvm.static-libstdcpp', '--set', 'gcc.download-ci-gcc=true', '--enable-new-symbol-mangling']
configure: build.build          := x86_64-unknown-linux-gnu
configure: target.x86_64-unknown-linux-gnu.llvm-config := /usr/lib/llvm-19/bin/llvm-config
configure: llvm.link-shared     := True
configure: rust.randomize-layout := True
configure: rust.thin-lto-import-instr-limit := 10
---

failures:

---- [ui] tests/ui/c-variadic/variadic-ffi-4.rs stdout ----
Saved the actual stderr to `/checkout/obj/build/x86_64-unknown-linux-gnu/test/ui/c-variadic/variadic-ffi-4/variadic-ffi-4.stderr`
diff of stderr:

59    |                                               |
60    |                                               has type `&mut VaList<'1>`
61 LL |     *ap0 = ap1;
-    |     ^^^^^^^^^^ assignment requires that `'1` must outlive `'2`
+    |     ^^^^ assignment requires that `'1` must outlive `'2`
63    |
64    = note: requirement occurs because of the type `VaList<'_>`, which makes the generic argument `'_` invariant
65    = note: the struct `VaList<'a>` is invariant over the parameter `'a`

73    |                                               |
74    |                                               has type `&mut VaList<'1>`
75 LL |     *ap0 = ap1;
-    |     ^^^^^^^^^^ assignment requires that `'2` must outlive `'1`
+    |     ^^^^ assignment requires that `'2` must outlive `'1`
77    |
78    = note: requirement occurs because of the type `VaList<'_>`, which makes the generic argument `'_` invariant
79    = note: the struct `VaList<'a>` is invariant over the parameter `'a`

121    |     assignment requires that `ap1` is borrowed for `'3`
122 ...
123 LL | }
-    |  - `ap1` dropped here while still borrowed
+    | - `ap1` dropped here while still borrowed
125 
126 error: lifetime may not live long enough
127   --> $DIR/variadic-ffi-4.rs:35:5

131    |                                               |
132    |                                               has type `&mut VaList<'2>`
133 LL |     *ap0 = va_copy!(ap1);
-    |     ^^^^^^^^^^^^^^^^^^^^ assignment requires that `'1` must outlive `'2`
+    |     ^^^^ assignment requires that `'1` must outlive `'2`
135    |
136    = note: requirement occurs because of the type `VaList<'_>`, which makes the generic argument `'_` invariant
137    = note: the struct `VaList<'a>` is invariant over the parameter `'a`

143 LL | pub unsafe extern "C" fn no_escape5(_: usize, mut ap0: &mut VaList, mut ap1: ...) {
144    |                                               ------- has type `&mut VaList<'2>`
145 LL |     *ap0 = va_copy!(ap1);
-    |     -------^^^^^^^^^^^^^- temporary value is freed at the end of this statement
+    |     ----   ^^^^^^^^^^^^^- temporary value is freed at the end of this statement
147    |     |      |
148    |     |      creates a temporary value which is freed while still in use
149    |     assignment requires that borrow lasts for `'2`


The actual stderr differed from the expected stderr
To update references, rerun the tests and pass the `--bless` flag
To only update this specific test, also pass `--test-args c-variadic/variadic-ffi-4.rs`

error: 1 errors occurred comparing output.
status: exit status: 1
command: env -u RUSTC_LOG_COLOR RUSTC_ICE="0" RUST_BACKTRACE="short" "/checkout/obj/build/x86_64-unknown-linux-gnu/stage2/bin/rustc" "/checkout/tests/ui/c-variadic/variadic-ffi-4.rs" "-Zthreads=1" "-Zsimulate-remapped-rust-src-base=/rustc/FAKE_PREFIX" "-Ztranslate-remapped-path-to-local-path=no" "-Z" "ignore-directory-in-diagnostics-source-blocks=/cargo" "-Z" "ignore-directory-in-diagnostics-source-blocks=/checkout/vendor" "--sysroot" "/checkout/obj/build/x86_64-unknown-linux-gnu/stage2" "--target=i686-unknown-linux-gnu" "--check-cfg" "cfg(test,FALSE)" "--error-format" "json" "--json" "future-incompat" "-Ccodegen-units=1" "-Zui-testing" "-Zdeduplicate-diagnostics=no" "-Zwrite-long-types-to-disk=no" "-Cstrip=debuginfo" "--emit" "metadata" "-C" "prefer-dynamic" "--out-dir" "/checkout/obj/build/x86_64-unknown-linux-gnu/test/ui/c-variadic/variadic-ffi-4" "-A" "unused" "-A" "internal_features" "-Crpath" "-Cdebuginfo=0" "-Lnative=/checkout/obj/build/i686-unknown-linux-gnu/native/rust-test-helpers" "-Clinker=x86_64-linux-gnu-gcc"
stdout: none
--- stderr -------------------------------
error: lifetime may not live long enough
##[error]  --> /checkout/tests/ui/c-variadic/variadic-ffi-4.rs:8:5
   |
LL | pub unsafe extern "C" fn no_escape0<'f>(_: usize, ap: ...) -> VaList<'f> {
   |                                     --            -- has type `VaList<'1>`
   |                                     |
   |                                     lifetime `'f` defined here
LL |     ap
   |     ^^ function was supposed to return data with lifetime `'1` but it is returning data with lifetime `'f`
   |
   = note: requirement occurs because of the type `VaList<'_>`, which makes the generic argument `'_` invariant
   = note: the struct `VaList<'a>` is invariant over the parameter `'a`
   = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance

error: lifetime may not live long enough
##[error]  --> /checkout/tests/ui/c-variadic/variadic-ffi-4.rs:8:5
   |
LL | pub unsafe extern "C" fn no_escape0<'f>(_: usize, ap: ...) -> VaList<'f> {
   |                                     --            -- has type `VaList<'1>`
   |                                     |
   |                                     lifetime `'f` defined here
LL |     ap
   |     ^^ function was supposed to return data with lifetime `'f` but it is returning data with lifetime `'1`
   |
   = note: requirement occurs because of the type `VaList<'_>`, which makes the generic argument `'_` invariant
   = note: the struct `VaList<'a>` is invariant over the parameter `'a`
   = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance

error: lifetime may not live long enough
##[error]  --> /checkout/tests/ui/c-variadic/variadic-ffi-4.rs:14:5
   |
LL | pub unsafe extern "C" fn no_escape1(_: usize, ap: ...) -> VaList<'static> {
   |                                               -- has type `VaList<'1>`
LL |     ap //~ ERROR: lifetime may not live long enough
   |     ^^ returning this value requires that `'1` must outlive `'static`
   |
   = note: requirement occurs because of the type `VaList<'_>`, which makes the generic argument `'_` invariant
   = note: the struct `VaList<'a>` is invariant over the parameter `'a`
   = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance

error: lifetime may not live long enough
##[error]  --> /checkout/tests/ui/c-variadic/variadic-ffi-4.rs:18:31
   |
LL |     let _ = ap.with_copy(|ap| ap); //~ ERROR: lifetime may not live long enough
   |                           --- ^^ returning this value requires that `'1` must outlive `'2`
   |                           | |
   |                           | return type of closure is VaList<'2>
   |                           has type `VaList<'1>`
   |
   = note: requirement occurs because of the type `VaList<'_>`, which makes the generic argument `'_` invariant
   = note: the struct `VaList<'a>` is invariant over the parameter `'a`
   = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance

error: lifetime may not live long enough
##[error]  --> /checkout/tests/ui/c-variadic/variadic-ffi-4.rs:22:5
   |
LL | pub unsafe extern "C" fn no_escape3(_: usize, mut ap0: &mut VaList, mut ap1: ...) {
   |                                               -------               ------- has type `VaList<'2>`
   |                                               |
   |                                               has type `&mut VaList<'1>`
LL |     *ap0 = ap1;
   |     ^^^^ assignment requires that `'1` must outlive `'2`
   |
   = note: requirement occurs because of the type `VaList<'_>`, which makes the generic argument `'_` invariant
   = note: the struct `VaList<'a>` is invariant over the parameter `'a`
   = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance

error: lifetime may not live long enough
##[error]  --> /checkout/tests/ui/c-variadic/variadic-ffi-4.rs:22:5
   |
LL | pub unsafe extern "C" fn no_escape3(_: usize, mut ap0: &mut VaList, mut ap1: ...) {
   |                                               -------               ------- has type `VaList<'2>`
   |                                               |
   |                                               has type `&mut VaList<'1>`
LL |     *ap0 = ap1;
   |     ^^^^ assignment requires that `'2` must outlive `'1`
   |
   = note: requirement occurs because of the type `VaList<'_>`, which makes the generic argument `'_` invariant
   = note: the struct `VaList<'a>` is invariant over the parameter `'a`
   = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance

error: lifetime may not live long enough
##[error]  --> /checkout/tests/ui/c-variadic/variadic-ffi-4.rs:28:5
   |
LL | pub unsafe extern "C" fn no_escape4(_: usize, mut ap0: &mut VaList, mut ap1: ...) {
   |                                               -------               ------- has type `VaList<'2>`
   |                                               |
   |                                               has type `&mut VaList<'1>`
LL |     ap0 = &mut ap1;
   |     ^^^^^^^^^^^^^^ assignment requires that `'1` must outlive `'2`
   |
   = note: requirement occurs because of a mutable reference to `VaList<'_>`
   = note: mutable references are invariant over their type parameter
   = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance

error: lifetime may not live long enough
##[error]  --> /checkout/tests/ui/c-variadic/variadic-ffi-4.rs:28:5
   |
LL | pub unsafe extern "C" fn no_escape4(_: usize, mut ap0: &mut VaList, mut ap1: ...) {
   |                                               -------               ------- has type `VaList<'2>`
   |                                               |
   |                                               has type `&mut VaList<'1>`
LL |     ap0 = &mut ap1;
   |     ^^^^^^^^^^^^^^ assignment requires that `'2` must outlive `'1`
   |
   = note: requirement occurs because of a mutable reference to `VaList<'_>`
   = note: mutable references are invariant over their type parameter
   = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance

error[E0597]: `ap1` does not live long enough
##[error]  --> /checkout/tests/ui/c-variadic/variadic-ffi-4.rs:28:11
   |
LL | pub unsafe extern "C" fn no_escape4(_: usize, mut ap0: &mut VaList, mut ap1: ...) {
   |                                                        -            ------- binding `ap1` declared here
   |                                                        |
   |                                                        let's call the lifetime of this reference `'3`
LL |     ap0 = &mut ap1;
   |     ------^^^^^^^^
   |     |     |
   |     |     borrowed value does not live long enough
   |     assignment requires that `ap1` is borrowed for `'3`
...
LL | }
   | - `ap1` dropped here while still borrowed

error: lifetime may not live long enough
##[error]  --> /checkout/tests/ui/c-variadic/variadic-ffi-4.rs:35:5
   |
LL | pub unsafe extern "C" fn no_escape5(_: usize, mut ap0: &mut VaList, mut ap1: ...) {
   |                                               -------               ------- has type `VaList<'1>`
   |                                               |
   |                                               has type `&mut VaList<'2>`
LL |     *ap0 = va_copy!(ap1);
   |     ^^^^ assignment requires that `'1` must outlive `'2`
   |
   = note: requirement occurs because of the type `VaList<'_>`, which makes the generic argument `'_` invariant
   = note: the struct `VaList<'a>` is invariant over the parameter `'a`
   = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance

error[E0716]: temporary value dropped while borrowed
##[error]  --> /checkout/tests/ui/c-variadic/variadic-ffi-4.rs:35:12
   |
LL | pub unsafe extern "C" fn no_escape5(_: usize, mut ap0: &mut VaList, mut ap1: ...) {
   |                                               ------- has type `&mut VaList<'2>`
LL |     *ap0 = va_copy!(ap1);
   |     ----   ^^^^^^^^^^^^^- temporary value is freed at the end of this statement
   |     |      |
   |     |      creates a temporary value which is freed while still in use
   |     assignment requires that borrow lasts for `'2`
   |
   = note: this error originates in the macro `va_copy` (in Nightly builds, run with -Z macro-backtrace for more info)

error: lifetime may not live long enough
##[error]  --> /checkout/tests/ui/c-variadic/variadic-ffi-4.rs:41:5
   |
LL | pub unsafe extern "C" fn no_escape6<'f>(ap: ...) -> VaList<'f> {
   |                                     --  -- has type `VaList<'1>`
   |                                     |
   |                                     lifetime `'f` defined here
LL |     va_copy!(ap)
   |     ^^^^^^^^^^^^ function was supposed to return data with lifetime `'f` but it is returning data with lifetime `'1`
   |
   = note: requirement occurs because of the type `VaList<'_>`, which makes the generic argument `'_` invariant
   = note: the struct `VaList<'a>` is invariant over the parameter `'a`
   = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
   = note: this error originates in the macro `va_copy` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0515]: cannot return value referencing local binding
##[error]  --> /checkout/tests/ui/c-variadic/variadic-ffi-4.rs:41:5
   |
LL |     va_copy!(ap)
   |     ^^^^^^^^^^^^
   |     |
   |     returns a value referencing data owned by the current function
   |     local binding introduced here
   |
   = note: this error originates in the macro `va_copy` (in Nightly builds, run with -Z macro-backtrace for more info)

error: lifetime may not live long enough
##[error]  --> /checkout/tests/ui/c-variadic/variadic-ffi-4.rs:47:5
   |
LL | pub unsafe extern "C" fn no_escape7(ap: ...) -> VaList<'static> {
   |                                     -- has type `VaList<'1>`
LL |     va_copy!(ap)
   |     ^^^^^^^^^^^^ returning this value requires that `'1` must outlive `'static`
   |
   = note: requirement occurs because of the type `VaList<'_>`, which makes the generic argument `'_` invariant
   = note: the struct `VaList<'a>` is invariant over the parameter `'a`
   = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
   = note: this error originates in the macro `va_copy` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0515]: cannot return value referencing local binding
##[error]  --> /checkout/tests/ui/c-variadic/variadic-ffi-4.rs:47:5
   |
LL |     va_copy!(ap)
   |     ^^^^^^^^^^^^
   |     |
   |     returns a value referencing data owned by the current function
   |     local binding introduced here
   |
---
---- [ui] tests/ui/parser/variadic-ffi-semantic-restrictions.rs stdout ----
Saved the actual stderr to `/checkout/obj/build/x86_64-unknown-linux-gnu/test/ui/parser/variadic-ffi-semantic-restrictions/variadic-ffi-semantic-restrictions.stderr`
diff of stderr:

201 LL |     fn t_f6(..., x: isize);
202    |             ^^^
203 
- error: aborting due to 33 previous errors
+ error[E0493]: destructor of `VaList<'_>` cannot be evaluated at compile-time
+   --> $DIR/variadic-ffi-semantic-restrictions.rs:32:43
+    |
+ 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
205 
+ error[E0493]: destructor of `VaList<'_>` cannot be evaluated at compile-time
+   --> $DIR/variadic-ffi-semantic-restrictions.rs:35:36
+    |
+ LL | const extern "C" fn f4_2(x: isize, ...) {}
+    |                                    ^^^   - value is dropped here
+    |                                    |
+    |                                    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: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 36 previous errors
---
-   --> /checkout/tests/ui/parser/variadic-ffi-semantic-restrictions.rs:62:29
+ error[E0493]: destructor of `VaList<'_>` cannot be evaluated at compile-time
+   --> $DIR/variadic-ffi-semantic-restrictions.rs:32:43
+    |
+ 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
+ error[E0493]: destructor of `VaList<'_>` cannot be evaluated at compile-time
+   --> $DIR/variadic-ffi-semantic-restrictions.rs:35:36
+    |
+ LL | const extern "C" fn f4_2(x: isize, ...) {}
+    |                                    ^^^   - value is dropped here
+    |                                    |
+    |                                    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: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 36 previous errors
---
To only update this specific test, also pass `--test-args parser/variadic-ffi-semantic-restrictions.rs`

error: 1 errors occurred comparing output.
status: exit status: 1
command: env -u RUSTC_LOG_COLOR RUSTC_ICE="0" RUST_BACKTRACE="short" "/checkout/obj/build/x86_64-unknown-linux-gnu/stage2/bin/rustc" "/checkout/tests/ui/parser/variadic-ffi-semantic-restrictions.rs" "-Zthreads=1" "-Zsimulate-remapped-rust-src-base=/rustc/FAKE_PREFIX" "-Ztranslate-remapped-path-to-local-path=no" "-Z" "ignore-directory-in-diagnostics-source-blocks=/cargo" "-Z" "ignore-directory-in-diagnostics-source-blocks=/checkout/vendor" "--sysroot" "/checkout/obj/build/x86_64-unknown-linux-gnu/stage2" "--target=i686-unknown-linux-gnu" "--check-cfg" "cfg(test,FALSE)" "--error-format" "json" "--json" "future-incompat" "-Ccodegen-units=1" "-Zui-testing" "-Zdeduplicate-diagnostics=no" "-Zwrite-long-types-to-disk=no" "-Cstrip=debuginfo" "--emit" "metadata" "-C" "prefer-dynamic" "--out-dir" "/checkout/obj/build/x86_64-unknown-linux-gnu/test/ui/parser/variadic-ffi-semantic-restrictions" "-A" "unused" "-A" "internal_features" "-Crpath" "-Cdebuginfo=0" "-Lnative=/checkout/obj/build/i686-unknown-linux-gnu/native/rust-test-helpers" "-Clinker=x86_64-linux-gnu-gcc"
stdout: none
--- stderr -------------------------------
error: only foreign, `unsafe extern "C"`, or `unsafe extern "C-unwind"` functions may have a C-variadic arg
##[error]  --> /checkout/tests/ui/parser/variadic-ffi-semantic-restrictions.rs:6:19
   |
LL | fn f1_1(x: isize, ...) {}
   |                   ^^^

error: only foreign, `unsafe extern "C"`, or `unsafe extern "C-unwind"` functions may have a C-variadic arg
##[error]  --> /checkout/tests/ui/parser/variadic-ffi-semantic-restrictions.rs:9:9
   |
LL | fn f1_2(...) {}
   |         ^^^

error: only foreign, `unsafe extern "C"`, or `unsafe extern "C-unwind"` functions may have a C-variadic arg
##[error]  --> /checkout/tests/ui/parser/variadic-ffi-semantic-restrictions.rs:12:30
   |
LL | extern "C" fn f2_1(x: isize, ...) {}
   |                              ^^^

error: only foreign, `unsafe extern "C"`, or `unsafe extern "C-unwind"` functions may have a C-variadic arg
##[error]  --> /checkout/tests/ui/parser/variadic-ffi-semantic-restrictions.rs:15:20
   |
LL | extern "C" fn f2_2(...) {}
   |                    ^^^

error: `...` must be the last argument of a C-variadic function
##[error]  --> /checkout/tests/ui/parser/variadic-ffi-semantic-restrictions.rs:18:20
   |
LL | extern "C" fn f2_3(..., x: isize) {}
   |                    ^^^

error: only foreign, `unsafe extern "C"`, or `unsafe extern "C-unwind"` functions may have a C-variadic arg
##[error]  --> /checkout/tests/ui/parser/variadic-ffi-semantic-restrictions.rs:18:20
   |
LL | extern "C" fn f2_3(..., x: isize) {}
   |                    ^^^

error: only foreign, `unsafe extern "C"`, or `unsafe extern "C-unwind"` functions may have a C-variadic arg
##[error]  --> /checkout/tests/ui/parser/variadic-ffi-semantic-restrictions.rs:22:30
   |
LL | extern "C" fn f3_1(x: isize, ...) {}
   |                              ^^^

error: only foreign, `unsafe extern "C"`, or `unsafe extern "C-unwind"` functions may have a C-variadic arg
##[error]  --> /checkout/tests/ui/parser/variadic-ffi-semantic-restrictions.rs:25:20
   |
LL | extern "C" fn f3_2(...) {}
   |                    ^^^

error: `...` must be the last argument of a C-variadic function
##[error]  --> /checkout/tests/ui/parser/variadic-ffi-semantic-restrictions.rs:28:20
   |
LL | extern "C" fn f3_3(..., x: isize) {}
   |                    ^^^

error: only foreign, `unsafe extern "C"`, or `unsafe extern "C-unwind"` functions may have a C-variadic arg
##[error]  --> /checkout/tests/ui/parser/variadic-ffi-semantic-restrictions.rs:28:20
   |
LL | extern "C" fn f3_3(..., x: isize) {}
   |                    ^^^

error: functions cannot be both `const` and C-variadic
##[error]  --> /checkout/tests/ui/parser/variadic-ffi-semantic-restrictions.rs:32: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
##[error]  --> /checkout/tests/ui/parser/variadic-ffi-semantic-restrictions.rs:35:1
   |
LL | const extern "C" fn f4_2(x: isize, ...) {}
   | ^^^^^ `const` because of this      ^^^ C-variadic because of this

error: only foreign, `unsafe extern "C"`, or `unsafe extern "C-unwind"` functions may have a C-variadic arg
##[error]  --> /checkout/tests/ui/parser/variadic-ffi-semantic-restrictions.rs:35:36
   |
LL | const extern "C" fn f4_2(x: isize, ...) {}
   |                                    ^^^

error: `...` must be the last argument of a C-variadic function
##[error]  --> /checkout/tests/ui/parser/variadic-ffi-semantic-restrictions.rs:39:26
   |
LL | const extern "C" fn f4_3(..., x: isize, ...) {}
   |                          ^^^

error: functions cannot be both `const` and C-variadic
##[error]  --> /checkout/tests/ui/parser/variadic-ffi-semantic-restrictions.rs:39:1
   |
LL | const extern "C" fn f4_3(..., x: isize, ...) {}
   | ^^^^^                    ^^^            ^^^ C-variadic because of this
   | |                        |
   | |                        C-variadic because of this
   | `const` because of this

error: only foreign, `unsafe extern "C"`, or `unsafe extern "C-unwind"` functions may have a C-variadic arg
##[error]  --> /checkout/tests/ui/parser/variadic-ffi-semantic-restrictions.rs:39:26
   |
LL | const extern "C" fn f4_3(..., x: isize, ...) {}
   |                          ^^^            ^^^

error: `...` must be the last argument of a C-variadic function
##[error]  --> /checkout/tests/ui/parser/variadic-ffi-semantic-restrictions.rs:45:13
   |
LL |     fn e_f2(..., x: isize);
   |             ^^^

error: only foreign, `unsafe extern "C"`, or `unsafe extern "C-unwind"` functions may have a C-variadic arg
##[error]  --> /checkout/tests/ui/parser/variadic-ffi-semantic-restrictions.rs:52:23
   |
LL |     fn i_f1(x: isize, ...) {}
   |                       ^^^

error: only foreign, `unsafe extern "C"`, or `unsafe extern "C-unwind"` functions may have a C-variadic arg
##[error]  --> /checkout/tests/ui/parser/variadic-ffi-semantic-restrictions.rs:54:13
   |
LL |     fn i_f2(...) {}
   |             ^^^

error: `...` must be the last argument of a C-variadic function
##[error]  --> /checkout/tests/ui/parser/variadic-ffi-semantic-restrictions.rs:56:13
   |
LL |     fn i_f3(..., x: isize, ...) {}
   |             ^^^

error: only foreign, `unsafe extern "C"`, or `unsafe extern "C-unwind"` functions may have a C-variadic arg
##[error]  --> /checkout/tests/ui/parser/variadic-ffi-semantic-restrictions.rs:56:13
   |
LL |     fn i_f3(..., x: isize, ...) {}
   |             ^^^            ^^^

error: `...` must be the last argument of a C-variadic function
##[error]  --> /checkout/tests/ui/parser/variadic-ffi-semantic-restrictions.rs:59:13
   |
LL |     fn i_f4(..., x: isize, ...) {}
   |             ^^^

error: only foreign, `unsafe extern "C"`, or `unsafe extern "C-unwind"` functions may have a C-variadic arg
##[error]  --> /checkout/tests/ui/parser/variadic-ffi-semantic-restrictions.rs:59:13
   |
LL |     fn i_f4(..., x: isize, ...) {}
   |             ^^^            ^^^

error: functions cannot be both `const` and C-variadic
##[error]  --> /checkout/tests/ui/parser/variadic-ffi-semantic-restrictions.rs:62:5
   |
LL |     const fn i_f5(x: isize, ...) {}
   |     ^^^^^                   ^^^ C-variadic because of this
   |     |
   |     `const` because of this

error: only foreign, `unsafe extern "C"`, or `unsafe extern "C-unwind"` functions may have a C-variadic arg
##[error]  --> /checkout/tests/ui/parser/variadic-ffi-semantic-restrictions.rs:62:29
   |
LL |     const fn i_f5(x: isize, ...) {}
   |                             ^^^

error: only foreign, `unsafe extern "C"`, or `unsafe extern "C-unwind"` functions may have a C-variadic arg
##[error]  --> /checkout/tests/ui/parser/variadic-ffi-semantic-restrictions.rs:68:23
   |
LL |     fn t_f1(x: isize, ...) {}
   |                       ^^^

error: only foreign, `unsafe extern "C"`, or `unsafe extern "C-unwind"` functions may have a C-variadic arg
##[error]  --> /checkout/tests/ui/parser/variadic-ffi-semantic-restrictions.rs:70:23
   |
LL |     fn t_f2(x: isize, ...);
   |                       ^^^

error: only foreign, `unsafe extern "C"`, or `unsafe extern "C-unwind"` functions may have a C-variadic arg
##[error]  --> /checkout/tests/ui/parser/variadic-ffi-semantic-restrictions.rs:72:13
   |
LL |     fn t_f3(...) {}
   |             ^^^

error: only foreign, `unsafe extern "C"`, or `unsafe extern "C-unwind"` functions may have a C-variadic arg
##[error]  --> /checkout/tests/ui/parser/variadic-ffi-semantic-restrictions.rs:74:13
   |
LL |     fn t_f4(...);
   |             ^^^

error: `...` must be the last argument of a C-variadic function
##[error]  --> /checkout/tests/ui/parser/variadic-ffi-semantic-restrictions.rs:76:13
   |
LL |     fn t_f5(..., x: isize) {}
   |             ^^^

error: only foreign, `unsafe extern "C"`, or `unsafe extern "C-unwind"` functions may have a C-variadic arg
##[error]  --> /checkout/tests/ui/parser/variadic-ffi-semantic-restrictions.rs:76:13
   |
LL |     fn t_f5(..., x: isize) {}
   |             ^^^

error: `...` must be the last argument of a C-variadic function
##[error]  --> /checkout/tests/ui/parser/variadic-ffi-semantic-restrictions.rs:79:13
   |
LL |     fn t_f6(..., x: isize);
   |             ^^^

error: only foreign, `unsafe extern "C"`, or `unsafe extern "C-unwind"` functions may have a C-variadic arg
##[error]  --> /checkout/tests/ui/parser/variadic-ffi-semantic-restrictions.rs:79:13
   |
LL |     fn t_f6(..., x: isize);
   |             ^^^

error[E0493]: destructor of `VaList<'_>` cannot be evaluated at compile-time
##[error]  --> /checkout/tests/ui/parser/variadic-ffi-semantic-restrictions.rs:32:43
   |
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

error[E0493]: destructor of `VaList<'_>` cannot be evaluated at compile-time
##[error]  --> /checkout/tests/ui/parser/variadic-ffi-semantic-restrictions.rs:35:36
   |
LL | const extern "C" fn f4_2(x: isize, ...) {}
   |                                    ^^^   - value is dropped here
   |                                    |
   |                                    the destructor for this type cannot be evaluated in constant functions

error[E0493]: destructor of `VaList<'_>` cannot be evaluated at compile-time
##[error]  --> /checkout/tests/ui/parser/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 36 previous errors

@bors
Copy link
Collaborator

bors commented Jun 6, 2025

☔ The latest upstream changes (presumably #142099) made this pull request unmergeable. Please resolve the merge conflicts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
F-c_variadic `#![feature(c_variadic)]` S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-libs Relevant to the library team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants