malloc
.malloc
is the standard allocator.free
."[…] behavior, upon use of a nonportable or erroneous program construct or of erroneous data, for which this International Standard imposes no requirements"
a[i] = i++; // undefined behavior
int foo(int x) { return x+1 > x; // either true or UB due to signed overflow }
may be compiled as:
foo(int): movl $1, %eax ret
See https://en.cppreference.com/w/cpp/language/ub#UB_and_optimization
"Rust is a systems programming language that runs blazingly fast, prevents segfaults, and guarantees thread safety." - rustlang.org
This is all enforced at compile-time. No run-time cost (unlike GC).
Does have run-time checks, including:
Box
, Vec
etc.)."Memory unsafety is doing something with invalid data, a memory leak is not doing something with valid data."
https://huonw.github.io/blog/2016/04/memory-leaks-are-memory-safe/
Memory leaks are considered "safe" and possible in Rust (e.g. mem::forget
).
What if you want to talk to another language?
char
and FFI c_char
.#[no_mangle] pub extern fn hello_rust() -> *const u8 { "Hello, world!\0".as_ptr() }
#[no_mangle] pub unsafe extern "C" fn public_id_from_bytes( bytes: *const u8, bytes_len: usize, o_public_id: *mut *const PublicId, ) -> i32 { utils::catch_unwind_err_set(|| -> Result<_, Error> { let public_id = slice::from_raw_parts(bytes, bytes_len); let peer_id = PeerId::new(str::from_utf8(public_id)?); *o_public_id = Box::into_raw(Box::new(PublicId(peer_id))); Ok(()) }) }
#[no_mangle] pub unsafe extern "C" fn public_id_as_bytes( public_id: *const PublicId, o_bytes: *mut *const u8, o_bytes_len: *mut usize, ) -> i32 { utils::catch_unwind_err_set(|| -> Result<_, Error> { let bytes = (*public_id).0.as_bytes(); *o_bytes = bytes.as_ptr(); *o_bytes_len = bytes.len(); Ok(()) }) }
#[no_mangle] pub unsafe extern "C" fn public_id_free(public_id: *const PublicId) -> i32 { utils::catch_unwind_err_set(|| -> Result<_, Error> { let _ = Box::from_raw(public_id as *mut PublicId); Ok(()) }) }
See https://doc.rust-lang.org/beta/reference/behavior-considered-undefined.html
Vec
, the standard "dynamic array" type in Rust. Allocates on heap.Vec
contains unsafe code to perform allocations.unsafe { // Allocate a value, of type required by FFI function. let mut output: T = mem::uninitialized(); // Call FFI function. let res = f(&mut output); // Check error code. if res == 0 { Ok(output) } else { Err(res) } }
unsafe { let mut output: T = mem::zeroed(); // Call FFI function. let res = f(&mut output); if res == 0 { Ok(output) } else { Err(res) } }
Compare memory before and after!
let payload = b"hello world"; unsafe { let mut block = mem::zeroed(); assert_ffi!(block_new( payload.as_ptr(), payload.len(), ptr::null(), ptr::null(), 0, &mut block, )); // assert_ffi!(block_free(block as *mut _)); }
let memory_before = unwrap!(procinfo::pid::statm_self()).resident; // Run the function some number of times. for _ in 1..num_iterations { f(); } let memory_after = unwrap!(procinfo::pid::statm_self()).resident;
It can find the following:
Set system allocator (valgrind doesn't work with jemalloc):
use std::alloc::System; #[global_allocator] static GLOBAL: System = System;
Build what you want to check (e.g. tests):
cargo test ffi --no-run
Run valgrind:
valgrind --leak-check=full executable
RUSTFLAGS="-Z sanitizer=address" cargo test ffi --target x86_64-apple-darwin -- --nocapture
Possible targets are limited to:
address
, leak
, memory
, and thread
sanitizers.address
and thread
sanitizers.let payload = b"hello world"; unsafe { let mut block = mem::zeroed(); assert_ffi!(block_new( payload.as_ptr(), payload.len(), ptr::null(), ptr::null(), 0, &mut block, )); assert_ffi!(block_free(block as *mut _)); let payload_output = unwrap!(utils::get_vec_u8(|output, len| block_payload( block, output, len ))); }
Examples of bugs found with this tech: https://huonw.github.io/blog/2016/04/myths-and-legends-about-integer-overflow-in-rust/#myth-the-checks-find-no-bugs