From d5e05f04a10bed4777ce3328ead79ad680aa1b9b Mon Sep 17 00:00:00 2001 From: Joe Hoyle Date: Tue, 20 Dec 2022 14:19:51 +0000 Subject: [PATCH 1/6] Add support for throwing Exception objects Currently we only support throwing exception class entries, i.e. stateless exceptions. Ideally we also want to support throwing a ZVal which has a ClassEntry that extends the Exception PHP class. This can be used to throw stateful exceptions which is not uncommon. This PR is missing one piece: `throw_object` will currently `drop` the Zval when the function is completed, causing reference / null pointer errors. It seems `ZVal::Drop` doesn't actually free the zval currently, it just sets the type to NULL (which also breaks things.) Discussed briefly in https://discord.com/channels/115233111977099271/1025314959179120714 on how best to solve this, but I'm not totally clear still! Ideally I think we want `throw_object` to own the `zval` but not free it once the function returns. --- allowed_bindings.rs | 1 + src/exception.rs | 60 ++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/allowed_bindings.rs b/allowed_bindings.rs index f417e95..972e151 100644 --- a/allowed_bindings.rs +++ b/allowed_bindings.rs @@ -81,6 +81,7 @@ bind! { zend_string, zend_string_init_interned, zend_throw_exception_ex, + zend_throw_exception_object, zend_type, zend_value, zend_wrong_parameters_count_error, diff --git a/src/exception.rs b/src/exception.rs index d1504b4..e8a48fe 100644 --- a/src/exception.rs +++ b/src/exception.rs @@ -6,8 +6,9 @@ use crate::{ class::RegisteredClass, error::{Error, Result}, ffi::zend_throw_exception_ex, + ffi::zend_throw_exception_object, flags::ClassFlags, - zend::{ce, ClassEntry}, + zend::{ce, ClassEntry}, types::Zval, }; /// Result type with the error variant as a [`PhpException`]. @@ -25,6 +26,7 @@ pub struct PhpException { message: String, code: i32, ex: &'static ClassEntry, + object: Option, } impl PhpException { @@ -36,7 +38,7 @@ impl PhpException { /// * `code` - Integer code to go inside the exception. /// * `ex` - Exception type to throw. pub fn new(message: String, code: i32, ex: &'static ClassEntry) -> Self { - Self { message, code, ex } + Self { message, code, ex, object: None, } } /// Creates a new default exception instance, using the default PHP @@ -59,10 +61,27 @@ impl PhpException { Self::new(message, 0, T::get_metadata().ce()) } + /// Set the Zval object for the exception. + /// + /// Exceptions can be based of instantiated Zval objects when you are throwing a custom exception with + /// stateful properties. + /// + /// # Parameters + /// + /// * `object` - The Zval object. + pub fn set_object(&mut self, object: Option) { + self.object = object; + } + /// Throws the exception, returning nothing inside a result if successful /// and an error otherwise. pub fn throw(self) -> Result<()> { - throw_with_code(self.ex, self.code, &self.message) + match self.object { + Some(object) => { + throw_object(object) + }, + None => throw_with_code(self.ex, self.code, &self.message), + } } } @@ -146,3 +165,38 @@ pub fn throw_with_code(ex: &ClassEntry, code: i32, message: &str) -> Result<()> }; Ok(()) } + + +/// Throws an exception object. +/// +/// Returns a result containing nothing if the exception was successfully +/// thrown. +/// +/// # Parameters +/// +/// * `object` - The zval of type object +/// +/// # Examples +/// +/// ```no_run +/// use ext_php_rs::{zend::{ce, ClassEntry}, exception::throw_with_code}; +/// +/// #[php_class] +/// #[extends(ext_php_rs::zend::ce::exception())] +/// pub struct JsException { +/// #[prop(flags = ext_php_rs::flags::PropertyFlags::Public)] +/// message: String, +/// #[prop(flags = ext_php_rs::flags::PropertyFlags::Public)] +/// code: i32, +/// #[prop(flags = ext_php_rs::flags::PropertyFlags::Public)] +/// file: String, +/// } +/// let error = JsException { message: "A JS error occurred.", code: 100, file: "index.js" }; +/// throw_object( error.into_val(true) ); +/// ``` +pub fn throw_object(zval: Zval) -> Result<()> { + unsafe { + zend_throw_exception_object((&zval as *const _) as *mut _); + }; + Ok(()) +} From 94fb7c78a1326cf2374669e52b8dafc13adda778 Mon Sep 17 00:00:00 2001 From: Joe Hoyle Date: Tue, 20 Dec 2022 16:00:45 +0000 Subject: [PATCH 2/6] Fix docs example --- src/exception.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/exception.rs b/src/exception.rs index e8a48fe..f167c8d 100644 --- a/src/exception.rs +++ b/src/exception.rs @@ -179,7 +179,9 @@ pub fn throw_with_code(ex: &ClassEntry, code: i32, message: &str) -> Result<()> /// # Examples /// /// ```no_run -/// use ext_php_rs::{zend::{ce, ClassEntry}, exception::throw_with_code}; +/// use ext_php_rs::prelude::*; +/// use ext_php_rs::exception::throw_object; +/// use crate::ext_php_rs::convert::IntoZval; /// /// #[php_class] /// #[extends(ext_php_rs::zend::ce::exception())] @@ -191,8 +193,14 @@ pub fn throw_with_code(ex: &ClassEntry, code: i32, message: &str) -> Result<()> /// #[prop(flags = ext_php_rs::flags::PropertyFlags::Public)] /// file: String, /// } -/// let error = JsException { message: "A JS error occurred.", code: 100, file: "index.js" }; -/// throw_object( error.into_val(true) ); +/// +/// #[php_module] +/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder { +/// module +/// } +/// +/// let error = JsException { message: "A JS error occurred.".to_string(), code: 100, file: "index.js".to_string() }; +/// throw_object( error.into_zval(true).unwrap() ); /// ``` pub fn throw_object(zval: Zval) -> Result<()> { unsafe { From 8d6850afd07342fd9921f3ffab1e3dd8d3e3a54f Mon Sep 17 00:00:00 2001 From: Joe Hoyle Date: Tue, 20 Dec 2022 16:04:41 +0000 Subject: [PATCH 3/6] Format --- src/exception.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/exception.rs b/src/exception.rs index f167c8d..e1f4c63 100644 --- a/src/exception.rs +++ b/src/exception.rs @@ -8,7 +8,8 @@ use crate::{ ffi::zend_throw_exception_ex, ffi::zend_throw_exception_object, flags::ClassFlags, - zend::{ce, ClassEntry}, types::Zval, + types::Zval, + zend::{ce, ClassEntry}, }; /// Result type with the error variant as a [`PhpException`]. @@ -38,7 +39,12 @@ impl PhpException { /// * `code` - Integer code to go inside the exception. /// * `ex` - Exception type to throw. pub fn new(message: String, code: i32, ex: &'static ClassEntry) -> Self { - Self { message, code, ex, object: None, } + Self { + message, + code, + ex, + object: None, + } } /// Creates a new default exception instance, using the default PHP @@ -77,9 +83,7 @@ impl PhpException { /// and an error otherwise. pub fn throw(self) -> Result<()> { match self.object { - Some(object) => { - throw_object(object) - }, + Some(object) => throw_object(object), None => throw_with_code(self.ex, self.code, &self.message), } } @@ -166,7 +170,6 @@ pub fn throw_with_code(ex: &ClassEntry, code: i32, message: &str) -> Result<()> Ok(()) } - /// Throws an exception object. /// /// Returns a result containing nothing if the exception was successfully From 4fabeb2d5ee7f4830c6ac4e242e5bbb64dbda8ee Mon Sep 17 00:00:00 2001 From: Pierre Tondereau Date: Tue, 20 Dec 2022 17:36:16 +0100 Subject: [PATCH 4/6] Update doc bindings. --- docsrs_bindings.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docsrs_bindings.rs b/docsrs_bindings.rs index a2ecbc7..9714e61 100644 --- a/docsrs_bindings.rs +++ b/docsrs_bindings.rs @@ -1442,6 +1442,9 @@ extern "C" { ... ) -> *mut zend_object; } +extern "C" { + pub fn zend_throw_exception_object(exception: *mut zval); +} extern "C" { pub fn zend_do_implement_interface(ce: *mut zend_class_entry, iface: *mut zend_class_entry); } From 50dad0cec44dc0be7dda14d54bdab7299a22c8ca Mon Sep 17 00:00:00 2001 From: Joe Hoyle Date: Fri, 24 Nov 2023 13:38:46 +0100 Subject: [PATCH 5/6] Manually drop zval --- src/exception.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/exception.rs b/src/exception.rs index e1f4c63..aed768a 100644 --- a/src/exception.rs +++ b/src/exception.rs @@ -206,8 +206,9 @@ pub fn throw_with_code(ex: &ClassEntry, code: i32, message: &str) -> Result<()> /// throw_object( error.into_zval(true).unwrap() ); /// ``` pub fn throw_object(zval: Zval) -> Result<()> { + let mut zv = core::mem::ManuallyDrop::new(zval); unsafe { - zend_throw_exception_object((&zval as *const _) as *mut _); + zend_throw_exception_object(core::ptr::addr_of_mut!(zv).cast()) }; Ok(()) } From 6fdd6a35bd3527c964553c58254d2aab1852ec25 Mon Sep 17 00:00:00 2001 From: Joe Hoyle Date: Fri, 24 Nov 2023 13:47:25 +0100 Subject: [PATCH 6/6] fmt --- src/exception.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/exception.rs b/src/exception.rs index aed768a..de7feba 100644 --- a/src/exception.rs +++ b/src/exception.rs @@ -207,8 +207,6 @@ pub fn throw_with_code(ex: &ClassEntry, code: i32, message: &str) -> Result<()> /// ``` pub fn throw_object(zval: Zval) -> Result<()> { let mut zv = core::mem::ManuallyDrop::new(zval); - unsafe { - zend_throw_exception_object(core::ptr::addr_of_mut!(zv).cast()) - }; + unsafe { zend_throw_exception_object(core::ptr::addr_of_mut!(zv).cast()) }; Ok(()) }