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

vadd_n

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

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: va_start() and va_end() injected automagically by compiler.

Implemented in rust-lang/rust#57760

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

Expectation vs. Reality


Some OSes always use 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

Initial implementation:


Follow-up work:

THE END