Skip to content

Commit ca50b98

Browse files
committed
Fix class deallocation memory leak
Store handlers statically like they always should have been
1 parent 07e8d32 commit ca50b98

File tree

3 files changed

+87
-46
lines changed

3 files changed

+87
-46
lines changed

ext-php-rs-derive/src/class.rs

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -19,31 +19,14 @@ pub fn parser(input: DeriveInput) -> Result<TokenStream> {
1919
);
2020

2121
let output = quote! {
22-
static mut #handlers: Option<
23-
*mut ::ext_php_rs::php::types::object::ZendObjectHandlers
24-
> = None;
22+
static #handlers: ::ext_php_rs::php::types::object::Handlers<#name> = ::ext_php_rs::php::types::object::Handlers::new();
2523

2624
impl ::ext_php_rs::php::types::object::ZendObjectOverride for #name {
27-
// Allow clippy ptr-deref lint because PHP guarantees that we are passed a valid pointer.
28-
#[allow(clippy::not_unsafe_ptr_arg_deref)]
29-
extern "C" fn create_object(
25+
unsafe extern "C" fn create_object(
3026
ce: *mut ::ext_php_rs::php::class::ClassEntry,
3127
) -> *mut ::ext_php_rs::php::types::object::ZendObject {
32-
// SAFETY: The handlers are only modified once, when they are first accessed.
33-
// At the moment we only support single-threaded PHP installations therefore the pointer contained
34-
// inside the option can be passed around.
35-
unsafe {
36-
if #handlers.is_none() {
37-
#handlers = Some(::ext_php_rs::php::types::object::ZendObjectHandlers::init::<#name>());
38-
}
39-
40-
// The handlers unwrap can never fail - we check that it is none above.
41-
// Unwrapping the result from `new_ptr` is nessacary as C cannot handle results.
42-
::ext_php_rs::php::types::object::ZendClassObject::<#name>::new_ptr(
43-
ce,
44-
#handlers.unwrap()
45-
).expect("Failed to allocate memory for new Zend object.")
46-
}
28+
::ext_php_rs::php::types::object::ZendClassObject::<#name>::new_ptr(ce, #handlers.get())
29+
.expect("Failed to allocate memory for new Zend object.")
4730
}
4831
}
4932
};

src/php/class.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Builder and objects for creating classes in the PHP world.
22
33
use crate::errors::{Error, Result};
4-
use std::{convert::TryInto, ffi::CString, fmt::Debug, mem};
4+
use std::{alloc::Layout, convert::TryInto, ffi::CString, fmt::Debug};
55

66
use crate::bindings::{
77
zend_class_entry, zend_declare_class_constant, zend_declare_property,
@@ -138,7 +138,7 @@ impl<'a> ClassBuilder<'a> {
138138
// which will cause the program to panic (standard in Rust). Unwrapping is OK - the ptr
139139
// will either be valid or null.
140140
let ptr = unsafe {
141-
(libc::calloc(1, mem::size_of::<ClassEntry>()) as *mut ClassEntry)
141+
(std::alloc::alloc_zeroed(Layout::new::<ClassEntry>()) as *mut ClassEntry)
142142
.as_mut()
143143
.unwrap()
144144
};
@@ -262,7 +262,9 @@ impl<'a> ClassBuilder<'a> {
262262
};
263263

264264
// SAFETY: We allocated memory for this pointer in `new`, so it is our job to free it when the builder has finished.
265-
unsafe { libc::free((self.ptr as *mut ClassEntry) as *mut libc::c_void) };
265+
unsafe {
266+
std::alloc::dealloc((self.ptr as *mut _) as *mut u8, Layout::new::<ClassEntry>())
267+
};
266268

267269
for (name, mut default, flags) in self.properties {
268270
unsafe {

src/php/types/object.rs

Lines changed: 78 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@
22
//! allowing users to store Rust data inside a PHP object.
33
44
use std::{
5+
alloc::Layout,
56
convert::TryInto,
67
fmt::Debug,
7-
mem,
8+
marker::PhantomData,
9+
mem::MaybeUninit,
810
ops::{Deref, DerefMut},
11+
sync::atomic::{AtomicBool, Ordering},
912
};
1013

1114
use crate::{
@@ -182,7 +185,11 @@ pub trait ZendObjectOverride {
182185
/// # Parameters
183186
///
184187
/// * `ce` - The class entry that we are creating an object for.
185-
extern "C" fn create_object(ce: *mut ClassEntry) -> *mut ZendObject;
188+
///
189+
/// # Safety
190+
///
191+
/// Caller needs to ensure that the given `ce` is a valid pointer to a [`ClassEntry`].
192+
unsafe extern "C" fn create_object(ce: *mut ClassEntry) -> *mut ZendObject;
186193
}
187194

188195
/// A Zend class object which is allocated when a PHP
@@ -215,7 +222,7 @@ impl<T: Default> ZendClassObject<T> {
215222
/// them with safety.
216223
pub unsafe fn new_ptr(
217224
ce: *mut ClassEntry,
218-
handlers: *mut ZendObjectHandlers,
225+
handlers: &ZendObjectHandlers,
219226
) -> Result<*mut zend_object> {
220227
let obj = {
221228
let obj = (ext_php_rs_zend_object_alloc(std::mem::size_of::<Self>() as _, ce)
@@ -244,8 +251,8 @@ impl<T: Default> ZendClassObject<T> {
244251
let ptr = (ex.This.object()? as *const ZendObject) as *mut u8;
245252
let offset = std::mem::size_of::<T>();
246253
unsafe {
247-
let ptr = ptr.offset(0 - offset as isize);
248-
(ptr as *mut Self).as_mut()
254+
let ptr = ptr.offset(0 - offset as isize) as *mut Self;
255+
ptr.as_mut()
249256
}
250257
}
251258
}
@@ -270,24 +277,73 @@ impl<T: Default> DerefMut for ZendClassObject<T> {
270277
}
271278
}
272279

280+
/// Lazy-loading wrapper around the [`ZendObjectHandlers`] type.
281+
pub struct Handlers<T> {
282+
init: AtomicBool,
283+
handlers: MaybeUninit<ZendObjectHandlers>,
284+
phantom: PhantomData<T>,
285+
}
286+
287+
impl<T> Handlers<T> {
288+
/// Creates a new instance of object handlers.
289+
pub const fn new() -> Self {
290+
Self {
291+
init: AtomicBool::new(false),
292+
handlers: MaybeUninit::uninit(),
293+
phantom: PhantomData,
294+
}
295+
}
296+
297+
/// Returns an immutable reference to the object handlers, initializing them in the process
298+
/// if they have not already been initialized.
299+
pub fn get(&self) -> &ZendObjectHandlers {
300+
self.check_uninit();
301+
302+
// SAFETY: `check_uninit` guarantees that the handlers have been initialized.
303+
unsafe { self.handlers.assume_init_ref() }
304+
}
305+
306+
/// Checks if the handlers have been initialized, and initializes them if they have not been.
307+
fn check_uninit(&self) {
308+
if !self.init.load(Ordering::Acquire) {
309+
// SAFETY: Memory location has been initialized therefore given pointer is valid.
310+
unsafe {
311+
ZendObjectHandlers::init::<T>(std::mem::transmute(self.handlers.assume_init_ref()));
312+
};
313+
self.init.store(true, Ordering::Release);
314+
}
315+
}
316+
}
317+
273318
impl ZendObjectHandlers {
274-
/// Creates a new set of object handlers from the standard object handlers, returning a pointer
275-
/// to the handlers.
276-
pub fn init<T>() -> *mut ZendObjectHandlers {
277-
// SAFETY: We are allocating memory for the handlers ourselves, which ensures that
278-
// we can copy to the allocated memory. We can also copy from the standard handlers
279-
// as the `std_object_handlers` are not modified.
280-
unsafe {
281-
let s = mem::size_of::<Self>();
282-
let ptr = libc::malloc(s) as *mut Self;
283-
libc::memcpy(
284-
ptr as *mut _,
285-
(&std_object_handlers as *const Self) as *mut _,
286-
s,
287-
);
288-
let offset = mem::size_of::<T>();
289-
(*ptr).offset = offset as i32;
290-
ptr
319+
/// Initializes a given set of object handlers by copying the standard object handlers into
320+
/// the memory location, as well as setting up the `T` type destructor.
321+
///
322+
/// # Parameters
323+
///
324+
//// * `ptr` - Pointer to memory location to copy the standard handlers to.
325+
///
326+
/// # Safety
327+
///
328+
/// Caller must guarantee that the `ptr` given is a valid memory location.
329+
pub unsafe fn init<T>(ptr: *mut ZendObjectHandlers) {
330+
pub unsafe extern "C" fn free_obj<T>(object: *mut zend_object) {
331+
let layout = Layout::new::<T>();
332+
let offset = layout.size();
333+
334+
// Cast to *mut u8 to work in byte offsets
335+
let ptr = (object as *mut u8).offset(0 - offset as isize) as *mut T;
336+
let _ = Box::from_raw(ptr);
337+
338+
match std_object_handlers.free_obj {
339+
Some(free) => free(object),
340+
None => core::hint::unreachable_unchecked(),
341+
}
291342
}
343+
344+
std::ptr::copy_nonoverlapping(&std_object_handlers, ptr, 1);
345+
let offset = std::mem::size_of::<T>();
346+
(*ptr).offset = offset as _;
347+
(*ptr).free_obj = Some(free_obj::<T>);
292348
}
293349
}

0 commit comments

Comments
 (0)