Skip to content

Instantly share code, notes, and snippets.

@ChunMinChang
Created October 3, 2018 20:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ChunMinChang/3f380eaced6265ab6e8dbb224bfec732 to your computer and use it in GitHub Desktop.
Save ChunMinChang/3f380eaced6265ab6e8dbb224bfec732 to your computer and use it in GitHub Desktop.
A counterexample to use the memory allocated in external library
// A module containing all the types and APIs in the external library.
mod sys {
use std::cmp;
use std::ffi::CString;
use std::os::raw::{c_char, c_void};
pub type XString = *mut c_void;
extern "C" {
fn calloc(items: usize, size: usize) -> *mut c_void;
fn free(ptr: *mut c_void);
fn strcpy(dest: *mut c_char, src: *const c_char) -> *mut c_char;
fn strlen(ptr: *const c_char) -> usize;
fn memcpy(dest: *mut c_void, src: *const c_void, size: usize) -> *mut c_void;
}
pub fn get_xstring() -> XString {
let words = CString::new("⚙ Hello, Rustaceans 🦀!").unwrap();
let size = words.as_bytes().len() + 1; // + 1 for '\0'.
let string = allocate_xstring(size);
copy_xstring(string, words.as_ptr());
string
}
pub fn release_xstring(xstring: XString) {
if !xstring.is_null() {
unsafe {
free(xstring as *mut c_void);
}
}
}
pub fn get_xstring_bytes(xstring: XString, size: *mut usize, buffer: *mut u8) {
if xstring.is_null() {
return;
}
let str_size = unsafe {
strlen(xstring as *const c_char) + 1 // + 1 for '\0'
};
if buffer.is_null() {
unsafe {
*size = str_size;
}
return;
}
let buf_size = unsafe { *size };
let copy_size = cmp::min(buf_size, str_size);
unsafe {
memcpy(buffer as *mut c_void, xstring as *const c_void, copy_size);
}
}
pub fn allocate_xstring(size: usize) -> XString {
let ptr = unsafe { calloc(1, size) };
ptr as XString
}
fn copy_xstring(dest: XString, src: *const c_char) {
unsafe {
strcpy(dest as *mut c_char, src);
}
}
}
// An adapter layer to call APIs in the external library.
mod ext {
use super::sys;
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use std::ptr;
use std::string::FromUtf8Error;
pub fn get_xstring() -> sys::XString {
sys::get_xstring()
}
pub fn release_string(string: sys::XString) {
sys::release_xstring(string);
}
pub fn to_rust_string(string: sys::XString) -> String {
unsafe {
CStr::from_ptr(string as *mut c_char)
.to_string_lossy()
.into_owned()
}
}
pub fn get_string_wrong() -> String {
let xstring = get_xstring();
let cstring = unsafe {
// Take the ownership of the xstring:
// In it's implementation, it will convert the string pointer
// to a slice whose length is equal to the string length, and then
// move it into a box. When it's deconstructed, the box will be set
// to null so the memory of the box is released. Therefore, it's ok
// to ignore the calling for `release_string`.....
CString::from_raw(xstring as *mut c_char)
// => Wrong!!!
// The memory allocator in the external library may be different
// from the memory allocator implemented in Rust, so the memory
// can not be freed correctly!
//
// How does calloc/malloc/... and free work ?
// When allocating memory by *alloc, the memory allocator will
// actually allocate a bit more memory than we want, since the
// allocator needs to store some extra information like size of
// the allocated block, the link to next free block, ... etc.
// In addition, most allocator will align the memory address to
// a multiple of bytes. For example, on a 64-bit(8 bytes) CPU,
// the address will be aligned to multiple of 8. This is called
// padding.
//
// When we free the memory, the allocator will use that address
// to find those extra information for the allocated block and
// use them to release the block.
// See discussions here:
// https://stackoverflow.com/questions/1957099/how-do-free-and-malloc-work-in-c
//
// Thus, if the memory allocator in Rust is different from the
// memory allocator in external library, when the memory is freed
// in Rust, the Rust's memory allocator has no way to find the
// extra information for the allocated memory so its behavior is
// undefined and it's very likely to cause memory leaks!
};
cstring.into_string().unwrap()
}
pub fn get_string_correct() -> Result<String, FromUtf8Error> {
let xstring = get_xstring();
assert!(!xstring.is_null());
// 1. Get size of the buffer for string.
let mut size: usize = 0;
sys::get_xstring_bytes(xstring, &mut size, ptr::null_mut() as *mut u8);
// 2. Allocate buffer with the size, and then copy the string into it.
let mut buffer = vec![b'\x00'; size];
sys::get_xstring_bytes(xstring, &mut size, buffer.as_mut_ptr());
String::from_utf8(buffer)
}
}
fn main() {
let xstring = ext::get_xstring();
let rust_string = ext::to_rust_string(xstring);
ext::release_string(xstring);
// Now xstring is a dangling pointer ....
println!("1: {}", rust_string);
println!("2: {}", ext::get_string_wrong());
println!("3: {}", ext::get_string_correct().unwrap());
}
#include "ext.h"
#include <stdlib.h> // calloc, free
#include <string.h> // strcpy, strlen, memcpy
// Private APIs
// ============================================================================
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
XString allocate_xstring(size_t size) {
return (XString) calloc(1, size);
}
void copy_xstring(XString dest, const char* src) {
strcpy((char*)dest, src);
}
// Public APIs
// ============================================================================
// In real word, you may get different XStrings by passing different parameters.
XString get_xstring() {
const char* words = "⚙ Hello, Rustaceans 🦀!";
size_t size = strlen(words) + 1; // + 1 for '\0'.
XString str = allocate_xstring(size);
copy_xstring(str, words);
return str;
}
void release_xstring(XString str) {
if (str) {
free((void*) str);
}
}
void get_xstring_bytes(XString str, size_t* size, uint8_t* buffer) {
if (str == NULL) {
return;
}
// Calculate byte size until '\0'.
size_t str_size = strlen((const char*) str) + 1; // + 1 for '\0'.
if (buffer == NULL) {
*size = str_size;
return;
}
size_t copy_size = MIN(*size, str_size);
memcpy((void*) buffer, (const void *) str, copy_size);
}
#ifndef EXT_H
#define EXT_H
#include <stddef.h>
#include <stdint.h>
typedef void* XString;
// Returns a fixed string here. In real word, you may get different XStrings
// by passing different parameters. The returned XString is allocated in heap,
// and users should call release_xstring() to release the memory when they
// don't need the string.
XString get_xstring();
// Free the memory occupied by the string.
void release_xstring(XString str);
// Copy the underlying bytes of the string from str to the buffer.
// 1. Do nothing if str is NULL.
// 2. Otherwise,
// a. if the buffer is NULL, then size will be assigned to the size of the
// string, including nul-terminator('\0').
// b. if the buffer is not NULL, we will copy N bytes into the buffer,
// where N = min(string's size, size). The nul-terminator('\0') will
// only be copied into the buffer when size is greater than the string's
// size.
void get_xstring_bytes(XString str, size_t* size, uint8_t* buffer);
#endif // EXT_H
all:
# Sample in C:
gcc -c ext.c -o ext.o
ar rcs libext.a ext.o
gcc sample.c -L. -lext -o sample-c
./sample-c
# sample in Rust:
rustc sample.rs -L.
LD_LIBRARY_PATH=. RUST_BACKTRACE=1 ./sample
clean:
rm ext.o libext.a
rm sample-c sample
#include "ext.h"
#include <alloca.h>
#include <stdio.h>
int main() {
XString string = get_xstring();
printf("string: %s\n", (const char*) string);
release_xstring(string);
size_t size = 0;
get_xstring_bytes(string, &size, NULL);
printf("string length is %d bytes\n", size);
uint8_t* buffer = (uint8_t*) alloca (size);
get_xstring_bytes(string, &size, buffer);
printf("string: %s\n", (const char*) buffer);
return 0;
}
// A module containing all the types and APIs in the external library.
mod sys {
use std::os::raw::c_void;
pub type XString = *mut c_void;
#[link(name = "ext")]
extern "C" {
pub fn get_xstring() -> XString;
pub fn release_xstring(xstring: XString);
pub fn get_xstring_bytes(
xstring: XString,
size: *mut usize,
buffer: *mut u8
);
}
}
// An adapter layer to call APIs in the external library.
mod ext {
use super::sys;
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use std::ptr;
use std::string::FromUtf8Error;
pub fn get_xstring() -> sys::XString {
unsafe {
sys::get_xstring()
}
}
pub fn release_string(string: sys::XString) {
unsafe {
sys::release_xstring(string);
}
}
pub fn to_rust_string(string: sys::XString) -> String {
unsafe {
CStr::from_ptr(string as *mut c_char)
.to_string_lossy()
.into_owned()
}
}
pub fn get_string_wrong() -> String {
let xstring = get_xstring();
let cstring = unsafe {
// Take the ownership of the xstring:
// In it's implementation, it will convert the string pointer
// to a slice whose length is equal to the string length, and then
// move it into a box. When it's deconstructed, the box will be set
// to null so the memory of the box is released. Therefore, it's ok
// to ignore the calling for `release_string`.....
CString::from_raw(xstring as *mut c_char)
// => Wrong!!!
// The memory allocator in the external library may be different
// from the memory allocator implemented in Rust, so the memory
// can not be freed correctly!
//
// How does calloc/malloc/... and free work ?
// When allocating memory by *alloc, the memory allocator will
// actually allocate a bit more memory than we want, since the
// allocator needs to store some extra information like size of
// the allocated block, the link to next free block, ... etc.
// In addition, most allocator will align the memory address to
// a multiple of bytes. For example, on a 64-bit(8 bytes) CPU,
// the address will be aligned to multiple of 8. This is called
// padding.
//
// When we free the memory, the allocator will use that address
// to find those extra information for the allocated block and
// use them to release the block.
// See discussions here:
// https://stackoverflow.com/questions/1957099/how-do-free-and-malloc-work-in-c
//
// Thus, if the memory allocator in Rust is different from the
// memory allocator in external library, when the memory is freed
// in Rust, the Rust's memory allocator has no way to find the
// extra information for the allocated memory so its behavior is
// undefined and it's very likely to cause memory leaks!
};
cstring.into_string().unwrap()
}
pub fn get_string_correct() -> Result<String, FromUtf8Error> {
let xstring = get_xstring();
assert!(!xstring.is_null());
// 1. Get size of the buffer for string.
let mut size: usize = 0;
unsafe {
sys::get_xstring_bytes(
xstring,
&mut size,
ptr::null_mut() as *mut u8
);
}
// 2. Allocate buffer with the size, and then copy the string into it.
let mut buffer = vec![b'\x00'; size];
unsafe {
sys::get_xstring_bytes(
xstring,
&mut size,
buffer.as_mut_ptr()
);
}
String::from_utf8(buffer)
}
}
fn main() {
let xstring = ext::get_xstring();
let rust_string = ext::to_rust_string(xstring);
ext::release_string(xstring);
// Now xstring is a dangling pointer ....
println!("1: {}", rust_string);
println!("2: {}", ext::get_string_wrong());
println!("3: {}", ext::get_string_correct().unwrap());
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment