I'm trying to understand how to check for memory leaks in C code that calls Rust and am not sure if the approach I am taking is correct or, if it is not even possible to do what I am after.
As an example, from a previous question on this board (Passing vector of vectors buffer to C), I have the following implementation:
lib.rs
use ::core::{convert::TryInto, slice};
use ::libc::size_t;
use ::scopeguard::defer_on_unwind;
#[repr(C)]
pub struct DynArray {
array: *mut i32,
array_len: size_t,
component_sizes: *mut size_t,
component_sizes_len: size_t,
}
#[no_mangle]
pub extern "C" fn rust_alloc() -> DynArray {
defer_on_unwind!({
::std::process::abort();
});
let v: Vec<Vec<i32>> = vec![vec![1, 2, 3], vec![1, 2, 3]];
let l: Vec<size_t> = v
.iter()
.map(|v| v.len().try_into().expect("Integer Overflow"))
.collect();
let l: Box<[size_t]> = l.into_boxed_slice();
let v: Box<[i32]> = v.concat().into_boxed_slice();
DynArray {
array_len: v.len().try_into().expect("Integer Overflow"),
array: Box::into_raw(v) as _,
component_sizes_len: l.len().try_into().expect("Integer Overflow"),
component_sizes: Box::into_raw(l) as _,
}
}
#[no_mangle]
pub unsafe extern "C" fn rust_free(array: DynArray) {
defer_on_unwind!({
::std::process::abort();
});
let DynArray {
array,
array_len,
component_sizes,
component_sizes_len,
} = array;
drop::<Box<[i32]>>(Box::from_raw(slice::from_raw_parts_mut(
array,
array_len.try_into().expect("Integer Overflow"),
)));
drop::<Box<[usize]>>(Box::from_raw(slice::from_raw_parts_mut(
component_sizes,
component_sizes_len.try_into().expect("Integer Overflow"),
)));
}
Cargo.toml
[package]
name = "example"
version = "0.1.0"
authors = ["example <example@example.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
libc = "0.2.67"
scopeguard = "1.1.0"
[lib]
crate-type = ["cdylib"]
main.c
#include <stdint.h>
#include <stdio.h>
typedef struct {
int32_t * array;
size_t array_length;
size_t * component_sizes;
size_t component_sizes_length;
} DynArray_t;
DynArray_t rust_alloc (void);
void rust_free (DynArray_t);
int main ()
{
DynArray_t vec = rust_alloc();
// Nothing shows in valgrind when this line is commented out
rust_free(vec);
}
What I'd like to check is that the last call to rust_free(vec)
is working as expected. I thought that by running Valgrind with this final line commented out I'd see some memory leaks highlighted. However Valgrind shows the same output irrespective of the inclusion of the final line. I compiled the code and ran valgrind with the following commands:
cargo build --release
gcc -o main main.c -L target/release -lexample
valgrind --leak-check=full env LD_LIBRARY_PATH=target/release/ ./main
with the output being similar to below (both with and without the final line in the c file)
==12574== Memcheck, a memory error detector
==12574== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==12574== Using Valgrind-3.14.0 and LibVEX; rerun with -h for copyright info
==12574== Command: env LD_LIBRARY_PATH=target/release/ ./main
==12574==
Am I misunderstanding how to use Valgrind in this scenario? If so, is there anyway I can check that memory is being freed as expected when interfacing between C and Rust?
I hope this makes sense.