Created
October 3, 2018 20:39
-
-
Save ChunMinChang/3f380eaced6265ab6e8dbb224bfec732 to your computer and use it in GitHub Desktop.
A counterexample to use the memory allocated in external library
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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()); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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