Skip to content

Commit 3403cba

Browse files
Add ZBox<T> to replace owned variants (#94)
* Added `ZBox` and `ZBoxable` * Implement `ZBoxable` for `ZendStr` Build for all f eatures on CI * Replace `OwnedZendObject` with `ZBox<ZendObject>` * Replace `ClassObject` with `ZBox<ZendClassObject>` Fixed deserialization bug Panic when uninitialized - better than UB * Replace `OwnedHashTable` with `ZBox<HashTable>` * Remove `MaybeUninit` from `ZendClassObject`
1 parent 125ec3b commit 3403cba

File tree

13 files changed

+522
-632
lines changed

13 files changed

+522
-632
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ jobs:
4646
env:
4747
LIBCLANG_PATH: ${{ runner.temp }}/llvm-${{ matrix.llvm }}/lib
4848
EXT_PHP_RS_TEST:
49-
run: cargo build --release --features alloc,closure
49+
run: cargo build --release --all-features
5050
- name: Test guide examples
5151
env:
5252
CARGO_PKG_NAME: mdbook-tests

Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ regex = "1"
2121
cc = "1.0"
2222

2323
[features]
24-
alloc = []
2524
closure = []
2625

2726
[workspace]

build.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ fn main() {
8686
.rustfmt_bindings(true)
8787
.no_copy("_zend_value")
8888
.no_copy("_zend_string")
89+
.no_copy("_zend_array")
8990
.layout_tests(env::var("EXT_PHP_RS_TEST").is_ok());
9091

9192
for binding in ALLOWED_BINDINGS.iter() {

example/skel/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ edition = "2018"
77
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
88

99
[dependencies]
10-
ext-php-rs = { path = "../../", features = ["alloc", "closure"] }
10+
ext-php-rs = { path = "../../", features = ["closure"] }
1111

1212
[lib]
1313
name = "skel"

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,11 +128,13 @@ fn parse_struct(
128128
Ok(quote! {
129129
impl #into_impl_generics ::ext_php_rs::php::types::object::IntoZendObject for #ident #ty_generics #into_where_clause {
130130
fn into_zend_object(self) -> ::ext_php_rs::errors::Result<
131-
::ext_php_rs::php::types::object::OwnedZendObject
131+
::ext_php_rs::php::boxed::ZBox<
132+
::ext_php_rs::php::types::object::ZendObject
133+
>
132134
> {
133135
use ::ext_php_rs::php::types::zval::IntoZval;
134136

135-
let mut obj = ::ext_php_rs::php::types::object::OwnedZendObject::new_stdclass();
137+
let mut obj = ::ext_php_rs::php::types::object::ZendObject::new_stdclass();
136138
#(#into_fields)*
137139
::ext_php_rs::errors::Result::Ok(obj)
138140
}

src/php/boxed.rs

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
//! A pointer type for heap allocation using the Zend memory manager.
2+
//!
3+
//! Heap memory in PHP is usually request-bound and allocated inside [memory arenas], which are cleared
4+
//! at the start and end of a PHP request. Allocating and freeing memory that is allocated on the Zend
5+
//! heap is done through two separate functions [`efree`] and [`emalloc`].
6+
//!
7+
//! As such, most heap-allocated PHP types **cannot** be allocated on the stack, such as [`ZendStr`], which
8+
//! is a dynamically-sized type, and therefore must be allocated on the heap. A regular [`Box`] would not
9+
//! work in this case, as the memory needs to be freed from a separate function `zend_string_release`. The
10+
//! [`ZBox`] type provides a wrapper which calls the relevant release functions based on the type and what is
11+
//! inside the implementation of [`ZBoxable`].
12+
//!
13+
//! This type is not created directly, but rather through a function implemented on the downstream type. For
14+
//! example, [`ZendStr`] has a function `new` which returns a [`ZBox<ZendStr>`].
15+
//!
16+
//! [memory arenas]: https://en.wikipedia.org/wiki/Region-based_memory_management
17+
//! [`ZendStr`]: super::types::string::ZendStr
18+
//! [`emalloc`]: super::alloc::efree
19+
20+
use std::{
21+
borrow::Borrow,
22+
fmt::Debug,
23+
mem::ManuallyDrop,
24+
ops::{Deref, DerefMut},
25+
ptr::NonNull,
26+
};
27+
28+
use super::alloc::efree;
29+
30+
/// A pointer type for heap allocation using the Zend memory manager.
31+
///
32+
/// See the [module level documentation](../index.html) for more.
33+
pub struct ZBox<T: ZBoxable>(NonNull<T>);
34+
35+
impl<T: ZBoxable> ZBox<T> {
36+
/// Creates a new box from a given pointer.
37+
///
38+
/// # Parameters
39+
///
40+
/// * `ptr` - A non-null, well-aligned pointer to a `T`.
41+
///
42+
/// # Safety
43+
///
44+
/// Caller must ensure that `ptr` is non-null, well-aligned and pointing to a `T`.
45+
pub unsafe fn from_raw(ptr: *mut T) -> Self {
46+
Self(NonNull::new_unchecked(ptr))
47+
}
48+
49+
/// Returns the pointer contained by the box, dropping the box in the process. The data pointed to by
50+
/// the returned pointer is not released.
51+
///
52+
/// # Safety
53+
///
54+
/// The caller is responsible for managing the memory pointed to by the returned pointer, including
55+
/// freeing the memory.
56+
pub fn into_raw(self) -> &'static mut T {
57+
let mut this = ManuallyDrop::new(self);
58+
// SAFETY: All constructors ensure the contained pointer is well-aligned and dereferencable.
59+
unsafe { this.0.as_mut() }
60+
}
61+
}
62+
63+
impl<T: ZBoxable> Drop for ZBox<T> {
64+
#[inline]
65+
fn drop(&mut self) {
66+
self.deref_mut().free()
67+
}
68+
}
69+
70+
impl<T: ZBoxable> Deref for ZBox<T> {
71+
type Target = T;
72+
73+
#[inline]
74+
fn deref(&self) -> &Self::Target {
75+
// SAFETY: All constructors ensure the contained pointer is well-aligned and dereferencable.
76+
unsafe { self.0.as_ref() }
77+
}
78+
}
79+
80+
impl<T: ZBoxable> DerefMut for ZBox<T> {
81+
#[inline]
82+
fn deref_mut(&mut self) -> &mut Self::Target {
83+
// SAFETY: All constructors ensure the contained pointer is well-aligned and dereferencable.
84+
unsafe { self.0.as_mut() }
85+
}
86+
}
87+
88+
impl<T: ZBoxable + Debug> Debug for ZBox<T> {
89+
#[inline]
90+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91+
(&**self).fmt(f)
92+
}
93+
}
94+
95+
impl<T: ZBoxable> Borrow<T> for ZBox<T> {
96+
#[inline]
97+
fn borrow(&self) -> &T {
98+
&**self
99+
}
100+
}
101+
102+
impl<T: ZBoxable> AsRef<T> for ZBox<T> {
103+
#[inline]
104+
fn as_ref(&self) -> &T {
105+
self
106+
}
107+
}
108+
109+
/// Implemented on types that can be heap allocated using the Zend memory manager. These types are stored
110+
/// inside a [`ZBox`] when heap-allocated, and the [`free`] method is called when the box is dropped.
111+
///
112+
/// # Safety
113+
///
114+
/// The default implementation of the [`free`] function uses the [`efree`] function to free the memory without
115+
/// calling any destructors.
116+
///
117+
/// The implementor must ensure that any time a pointer to the implementor is passed into a [`ZBox`] that the
118+
/// memory pointed to was allocated by the Zend memory manager.
119+
///
120+
/// [`free`]: #method.free
121+
pub unsafe trait ZBoxable {
122+
/// Frees the memory pointed to by `self`, calling any destructors required in the process.
123+
fn free(&mut self) {
124+
unsafe { efree(self as *mut _ as *mut u8) };
125+
}
126+
}

src/php/class.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ use crate::{
66
exceptions::PhpException,
77
execution_data::ExecutionData,
88
function::FunctionBuilder,
9-
types::object::{ClassObject, ConstructorMeta, ConstructorResult, ZendObject},
9+
types::object::{ConstructorMeta, ConstructorResult, ZendClassObject, ZendObject},
1010
},
1111
};
12-
use std::{alloc::Layout, convert::TryInto, ffi::CString, fmt::Debug};
12+
use std::{alloc::Layout, convert::TryInto, ffi::CString, fmt::Debug, ops::DerefMut};
1313

1414
use crate::bindings::{
1515
zend_class_entry, zend_declare_class_constant, zend_declare_property,
@@ -22,7 +22,7 @@ use super::{
2222
globals::ExecutorGlobals,
2323
types::{
2424
object::RegisteredClass,
25-
string::ZendString,
25+
string::ZendStr,
2626
zval::{IntoZval, Zval},
2727
},
2828
};
@@ -43,10 +43,10 @@ impl ClassEntry {
4343
/// not be found or the class table has not been initialized.
4444
pub fn try_find(name: &str) -> Option<&'static Self> {
4545
ExecutorGlobals::get().class_table()?;
46-
let mut name = ZendString::new(name, false).ok()?;
46+
let mut name = ZendStr::new(name, false).ok()?;
4747

4848
unsafe {
49-
crate::bindings::zend_lookup_class_ex(name.as_mut_zend_str(), std::ptr::null_mut(), 0)
49+
crate::bindings::zend_lookup_class_ex(name.deref_mut(), std::ptr::null_mut(), 0)
5050
.as_ref()
5151
}
5252
}
@@ -277,8 +277,8 @@ impl ClassBuilder {
277277
extern "C" fn create_object<T: RegisteredClass>(_: *mut ClassEntry) -> *mut ZendObject {
278278
// SAFETY: After calling this function, PHP will always call the constructor defined below,
279279
// which assumes that the object is uninitialized.
280-
let obj = unsafe { ClassObject::<T>::new_uninit() };
281-
obj.into_inner().get_mut_zend_obj()
280+
let obj = unsafe { ZendClassObject::<T>::new_uninit() };
281+
obj.into_raw().get_mut_zend_obj()
282282
}
283283

284284
extern "C" fn constructor<T: RegisteredClass>(ex: &mut ExecutionData, _: &mut Zval) {
@@ -337,7 +337,7 @@ impl ClassBuilder {
337337
///
338338
/// Returns an [`Error`] variant if the class could not be registered.
339339
pub fn build(mut self) -> Result<&'static mut ClassEntry> {
340-
self.ptr.name = ZendString::new_interned(&self.name, true)?.into_inner();
340+
self.ptr.name = ZendStr::new_interned(&self.name, true)?.into_raw();
341341

342342
self.methods.push(FunctionEntry::end());
343343
let func = Box::into_raw(self.methods.into_boxed_slice()) as *const FunctionEntry;

src/php/mod.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
//! Objects relating to PHP and the Zend engine.
22
3-
#[cfg(any(docs, feature = "alloc"))]
4-
#[cfg_attr(docs, doc(cfg(feature = "alloc")))]
53
pub mod alloc;
6-
74
pub mod args;
5+
pub mod boxed;
86
pub mod class;
97
pub mod constants;
108
pub mod enums;

src/php/pack.rs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,13 @@ use crate::bindings::{ext_php_rs_zend_string_init, zend_string};
1818
/// [`unpack`]: https://www.php.net/manual/en/function.unpack.php
1919
pub unsafe trait Pack: Clone {
2020
/// Packs a given vector into a Zend binary string. Can be passed to PHP and then unpacked
21-
/// using the [`unpack`] function. Note you should probably use the [`set_binary`] method on the
22-
/// [`Zval`] struct instead of this function directly, as there is currently no way to set a
23-
/// [`ZendString`] on a [`Zval`] directly.
21+
/// using the [`unpack`] function.
2422
///
2523
/// # Parameters
2624
///
2725
/// * `vec` - The vector to pack into a binary string.
2826
///
2927
/// [`unpack`]: https://www.php.net/manual/en/function.unpack.php
30-
/// [`Zval`]: crate::php::types::zval::Zval
31-
/// [`ZendString`]: crate::php::types::string::ZendString
32-
/// [`set_binary`]: crate::php::types::zval::Zval#method.set_binary
3328
fn pack_into(vec: Vec<Self>) -> *mut zend_string;
3429

3530
/// Unpacks a given Zend binary string into a Rust vector. Can be used to pass data from `pack`

0 commit comments

Comments
 (0)