Using C's va_list in Rust


(and why you never should)

rust / GitHub
Created by Dan Robertson / GitHub

Disclaimer

Here there be C and unsafe

va_list in C

Background

Type used as a parameter to retrieve a variable arguments passed to a function.


Defined in section 7.16 of the C Standard.


Available in <stdarg.h>.

The API

  • va_start() - Used to initialize the list
  • va_arg() - Retrieve the next argument of the given type from the list
  • va_end() - End using the list
  • va_copy() - Copy the lists state to a new list

Example

Define the function add_n that will return the sum of up to n integers passed to the function.

  add_n(3, 10, 15, 17) == 42

add_n


#include <stdarg.h>
int add_n(int n, ...) {












}

#include <stdarg.h>
int add_n(int n, ...) {
  // Declare local variables
  va_list ap;
  int ret = 0;









}

#include <stdarg.h>
int add_n(int n, ...) {
  // Declare local variables
  va_list ap;
  int ret = 0;

  // Initialize the list and
  // call the "real" add_n
  va_start(ap, n);
  ret = vadd_n(n, ap);




}

#include <stdarg.h>
int add_n(int n, ...) {
  // Declare local variables
  va_list ap;
  int ret = 0;

  // Initialize the list and
  // call the "real" add_n
  va_start(ap, n);
  ret = vadd_n(n, ap);

  // Cleanup and return
  va_end(ap);
  return ret;
}

vadd_n


// Worker function
int vadd_n(int n, va_list ap) {
  int sum = 0;






}

// Worker function
int vadd_n(int n, va_list ap) {
  int sum = 0;
  // Loop n times and add the given
  // argument of type int.
  for (i = 0; i < n; ++i) {

  }
  return sum;
}

// Worker function
int vadd_n(int n, va_list ap) {
  int sum = 0;
  // Loop n times and add the given
  // argument of type int.
  for (i = 0; i < n; ++i) {
    sum += va_arg(ap, int);
  }
  return sum;
}

va_list in Rust

Why?

Rust can currently call any possible C interface, and export almost any interface for C to call. Variadic functions represent one of the last remaining gaps in the latter.
  • fcntl()
  • printf()
  • exec()
  • ...

Why not?



va_list's are VERY unsafe

The Rust Implementation

RFC 2137

/// The argument list of a C-compatible variadic function,
/// corresponding to the underlying C `va_list`. Opaque.
pub struct VaList<'a>(/* fields omitted */)

// Note: the lifetime on VaList is invariant
impl<'a> VaList<'a> {
  /// Extract the next argument from the argument list. T must
  /// have a type usable in an FFI interface.
  pub unsafe fn arg<T>(&mut self) -> T;

  /// Copy the argument list. Destroys the copy after the closure
  /// returns.
  pub unsafe fn copy<F, R>(&mut self, f: F) -> R
    where F: for<'copy> FnOnce(VaList<'copy>) -> R;
}

vadd_n in Rust

#![feature(c_variadic)]
use std::ffi::VaList;














#![feature(c_variadic)]
use std::ffi::VaList;

// Worker function
#[no_mangle]
pub unsafe extern "C" fn vadd_n(
  n: c_int,
  ap: VaList
) -> c_int {





}

#![feature(c_variadic)]
use std::ffi::VaList;

// Worker function
#[no_mangle]
pub unsafe extern "C" fn vadd_n(
  n: c_int,
  ap: VaList
) -> c_int {
  let mut sum = 0;
  for i in 0..n {

  }
  sum
}

#![feature(c_variadic)]
use std::ffi::VaList;

// Worker function
#[no_mangle]
pub unsafe extern "C" fn vadd_n(
  n: c_int,
  ap: VaList
) -> c_int {
  let mut sum = 0;
  for i in 0..n {
    sum += ap.arg::<c_int>()
  }
  sum
}

Rust API considerations

The type of an argument in a variable argument list will never be an integer type smaller than int, nor will it ever be float.
C

  va_arg(ap, float);
Result: garbage or program abort()

Note: gcc and clang also throw a hefty warning
Rust

  ap.arg::<f32>();

Result: Compiler Error

The VaArgSafe Sealed Trait

Note: Not yet implemented.

#![no_std]
#![feature(c_variadic)]
use core::ffi::VaList;

#[no_mangle]
pub unsafe extern "C" fn func(fixed: u32, mut ap: ...) {
  let x: u8 = args.arg();
  let y: u16 = args.arg();
  let z: u32 = args.arg();
  println!("{} {} {} {}", fixed, x, y, z);
}

va_start() and va_end() injected automagically by compiler.

VaList should not be able to escape the function.


unsafe fn bad_idea(
  fixed: u32,
  mut ap: ...
) -> VaList {
  ap
}
Result: Compiler Error

unsafe fn bad_idea(
  mut ap: VaList
) {
  let new_copy = ap.copy(|mut ap| {
    ap
  });
}
Result: Compiler Error

The Gritty Details

va_list implementations

Based on the CPU architecture the structure varies.

  • Character/Void Pointer

  • x86_64 ABI

  • Aarch64 ABI

  • PowerPC ABI

Enter Windows
(and iOS)

always Character/Void Pointer

A case-study

va_list ap contains 10, (1ULL << 63)

Memory Layout


     First Arg           Second Arg
     type: int           type: unsigned long long
     value: 10           value: 0x8000000000000000 (1 << 63)
     0xXXXXXXXXXX00      0xXXXXXXXXXX04
    |                   |                                       |
    |----|----|----|----|----|----|----|----|----|----|----|----|
    |                   |                                       |
     0x0a 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x80


  let x = ap.arg::<i32>(); // <--
  let y = ap.arg::<u64>();
  let bad = ap.arg::<i32>();

     First Arg           Second Arg
     type: int           type: unsigned long long
     value: 10           value: 0x8000000000000000 (1 << 63)
     0xXXXXXXXXXX00      0xXXXXXXXXXX04
    |                   |                                       |
    |----|----|----|----|----|----|----|----|----|----|----|----|
    |                   |                                       |
     0x0a 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x80
    ^
    |
    location

  let x = ap.arg::<i32>(); // x == 10
  let y = ap.arg::<u64>(); // <--
  let bad = ap.arg::<i32>();

     First Arg           Second Arg
     type: int           type: unsigned long long
     value: 10           value: 0x8000000000000000 (1 << 63)
     0xXXXXXXXXXX00      0xXXXXXXXXXX04
    |                   |                                       |
    |----|----|----|----|----|----|----|----|----|----|----|----|
    |                   |                                       |
     0x0a 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x80
                        ^
                        |
                     location

  let x = ap.arg::<i32>(); // x == 10
  let y = ap.arg::<u64>(); // y == 0x8000000000000000
  let bad = ap.arg::<i32>(); // <--

     First Arg           Second Arg
     type: int           type: unsigned long long
     value: 10           value: 0x8000000000000000 (1 << 63)
     0xXXXXXXXXXX00      0xXXXXXXXXXX04
    |                   |                                       |
    |----|----|----|----|----|----|----|----|----|----|----|----|
    |                   |                                       |
     0x0a 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x80
                                                                ^
                                                                |
                                                             location

  let x = ap.arg::<i32>(); // x == 10
  let y = ap.arg::<u64>(); // y == 0x8000000000000000
  let bad = ap.arg::<i32>(); // OOB

     First Arg           Second Arg
     type: int           type: unsigned long long
     value: 10           value: 0x8000000000000000 (1 << 63)
     0xXXXXXXXXXX00      0xXXXXXXXXXX04                          0xXXXXXXXXXX0c
    |                   |                                       |                   |
    |----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|
    |                   |                                       |                   |
     0x0a 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x80         OOB
                                                                                    ^
                                                                                    |
                                                                                 location

Mixing Types

Architecture: i686

  let x = ap.arg::<i32>(); // x == 10
  let y = ap.arg::<f64>(); // <--

    |                   |                                       |
    |----|----|----|----|----|----|----|----|----|----|----|----|
    |                   |                                       |
     0x0a 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x80
                        ^
                        |
                     location
Architecture: i686

  let x = ap.arg::<i32>(); // x == 10
  let y = ap.arg::<f64>(); // y == -0.00

    |                   |                                       |
    |----|----|----|----|----|----|----|----|----|----|----|----|
    |                   |                                       |
     0x0a 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x80
                                                                ^
                                                                |
                                                             location
Architecture: Aarch64

  let x = ap.arg::<i32>(); // x == 10
  let y = ap.arg::<f64>(); // <--

gr_top |                   |                                       |
       |----|----|----|----|----|----|----|----|----|----|----|----|
       |                   |                                       |
        0x0a 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x80
                           ^
                           |
                        location
vr_top |                                                           |
       |----|----|----|----|----|----|----|----|----|----|----|----|
       |                         OOB                               |
       ^
       |
    location
Architecture: Aarch64

  let x = ap.arg::<i32>(); // x == 10
  let y = ap.arg::<f64>(); // OOB

gr_top |                   |                                       |
       |----|----|----|----|----|----|----|----|----|----|----|----|
       |                   |                                       |
        0x0a 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x80
                           ^
                           |
                        location
vr_top |                                                           |
       |----|----|----|----|----|----|----|----|----|----|----|----|
       |                         OOB                               |
                                               ^
                                               |
                                            location


Be nice to your computer. Be careful with va_list's

Acknowledgements

And everyone else that helped me with rust-lang/rust#49878

THE END