diff --git a/ext-php-rs-derive/src/class.rs b/ext-php-rs-derive/src/class.rs index 61b62f86fb..06af18ea7c 100644 --- a/ext-php-rs-derive/src/class.rs +++ b/ext-php-rs-derive/src/class.rs @@ -117,7 +117,11 @@ pub fn parser(args: AttributeArgs, mut input: ItemStruct) -> Result state.classes.insert(ident.to_string(), class); - Ok(quote! { #input }) + Ok(quote! { + #input + + ::ext_php_rs::class_derives!(#ident); + }) } #[derive(Debug)] diff --git a/ext-php-rs-derive/src/function.rs b/ext-php-rs-derive/src/function.rs index edc7727233..be66ac77bb 100644 --- a/ext-php-rs-derive/src/function.rs +++ b/ext-php-rs-derive/src/function.rs @@ -56,7 +56,12 @@ pub fn parser(args: AttributeArgs, input: ItemFn) -> Result<(TokenStream, Functi let args = build_args(inputs, &attr_args.defaults)?; let optional = find_optional_parameter(args.iter(), attr_args.optional); let arg_definitions = build_arg_definitions(&args); - let arg_parser = build_arg_parser(args.iter(), &optional, "e! { return; })?; + let arg_parser = build_arg_parser( + args.iter(), + &optional, + "e! { return; }, + ParserType::Function, + )?; let arg_accessors = build_arg_accessors(&args); let return_type = get_return_type(output)?; @@ -154,26 +159,29 @@ pub fn find_optional_parameter<'a>( optional } +pub enum ParserType { + Function, + Method, + StaticMethod, +} + pub fn build_arg_parser<'a>( args: impl Iterator, optional: &Option, ret: &TokenStream, + ty: ParserType, ) -> Result { let mut rest_optional = false; let args = args .map(|arg| { let name = arg.get_name_ident(); - let prelude = if let Some(optional) = optional { - if *optional == arg.name { - rest_optional = true; - quote! { .not_required() } - } else { - quote! {} - } + let prelude = optional.as_ref().and_then(|opt| if *opt == arg.name { + rest_optional = true; + Some(quote! { .not_required() }) } else { - quote! {} - }; + None + }); if rest_optional && !arg.nullable && arg.default.is_none() { bail!( @@ -188,15 +196,37 @@ pub fn build_arg_parser<'a>( } }) .collect::>>()?; + let (parser, this) = match ty { + ParserType::Function | ParserType::StaticMethod => { + (quote! { let parser = ex.parser(); }, None) + } + ParserType::Method => ( + quote! { let (parser, this) = ex.parser_method::(); }, + Some(quote! { + let this = match this { + Some(this) => this, + None => { + ::ext_php_rs::php::exceptions::PhpException::default("Failed to retrieve reference to `$this`".into()) + .throw() + .unwrap(); + return; + }, + }; + }), + ), + }; Ok(quote! { - let parser = ::ext_php_rs::php::args::ArgParser::new(ex) + #parser + let parser = parser #(#args)* .parse(); if parser.is_err() { #ret } + + #this }) } diff --git a/ext-php-rs-derive/src/method.rs b/ext-php-rs-derive/src/method.rs index 73c68edeec..db122d5a9d 100644 --- a/ext-php-rs-derive/src/method.rs +++ b/ext-php-rs-derive/src/method.rs @@ -3,7 +3,7 @@ use quote::ToTokens; use std::collections::HashMap; use crate::{ - function, + function::{self, ParserType}, impl_::{parse_attribute, ParsedAttribute, PropAttrTy, RenameRule, Visibility}, }; use proc_macro2::{Ident, Span, TokenStream}; @@ -131,7 +131,16 @@ pub fn parser(input: &mut ImplItemMethod, rename_rule: RenameRule) -> Result (Vec, bool) { let mut _static = true; - (args.iter() - .map(|ty| match ty { - Arg::Receiver(mutability) => { - let mutability = mutability.then(|| quote! { mut }); - _static = false; - - quote! { - // SAFETY: We are calling this on an execution data from a class method. - let #mutability this = match unsafe { ex.get_object::() } { - Some(this) => this, - None => return ::ext_php_rs::php::exceptions::throw( - ::ext_php_rs::php::class::ClassEntry::exception(), - "Failed to retrieve reference to object function was called on." - ).expect("Failed to throw exception: Failed to retrieve reference to object function was called on."), - }; + ( + args.iter() + .filter_map(|ty| match ty { + Arg::Receiver(_) => { + _static = false; + + None } - } - Arg::Typed(arg) => { - let ident = arg.get_name_ident(); - let definition = arg.get_arg_definition(); - quote! { - let mut #ident = #definition; + Arg::Typed(arg) => { + let ident = arg.get_name_ident(); + let definition = arg.get_arg_definition(); + Some(quote! { + let mut #ident = #definition; + }) } - }, - }) - .collect(), _static) + }) + .collect(), + _static, + ) } fn build_arg_parser<'a>( args: impl Iterator, optional: &Option, ret: &TokenStream, + ty: ParserType, ) -> Result { function::build_arg_parser( args.filter_map(|arg| match arg { @@ -265,6 +268,7 @@ fn build_arg_parser<'a>( }), optional, ret, + ty, ) } diff --git a/src/errors.rs b/src/errors.rs index 8a6fd736a8..72eccca7ca 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -21,7 +21,7 @@ pub enum Error { /// The enum carries two integers - the first representing the minimum /// number of arguments expected, and the second representing the number of /// arguments that were received. - IncorrectArguments(u32, u32), + IncorrectArguments(usize, usize), /// There was an error converting a Zval into a primitive type. /// /// The enum carries the data type of the Zval. diff --git a/src/lib.rs b/src/lib.rs index cecf559c76..2b1aea2404 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -127,7 +127,7 @@ pub use ext_php_rs_derive::php_extern; /// /// pub extern "C" fn _internal_php_hello(ex: &mut ExecutionData, retval: &mut Zval) { /// let mut name = Arg::new("name", ::TYPE); -/// let parser = ArgParser::new(ex) +/// let parser = ex.parser() /// .arg(&mut name) /// .parse(); /// diff --git a/src/macros.rs b/src/macros.rs index 7ec4f30bbd..168f74cbbb 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -112,9 +112,7 @@ macro_rules! call_user_func { #[macro_export] macro_rules! parse_args { ($ed: expr, $($arg: expr),*) => {{ - use $crate::php::args::ArgParser; - - let parser = ArgParser::new($ed) + let parser = $ed.parser() $(.arg(&mut $arg))* .parse(); if parser.is_err() { @@ -125,7 +123,7 @@ macro_rules! parse_args { ($ed: expr, $($arg: expr),* ; $($opt: expr),*) => {{ use $crate::php::args::ArgParser; - let parser = ArgParser::new($ed) + let parser = $ed.parser() $(.arg(&mut $arg))* .not_required() $(.arg(&mut $opt))* @@ -163,3 +161,147 @@ macro_rules! throw { return; }; } + +/// Implements a set of traits required to convert types that implement [`RegisteredClass`] to and +/// from [`ZendObject`]s and [`Zval`]s. Generally, this macro should not be called directly, as it +/// is called on any type that uses the [`#[php_class]`] macro. +/// +/// The following traits are implemented: +/// +/// * `FromZendObject for &'a T` +/// * `FromZendObjectMut for &'a mut T` +/// * `FromZval for &'a T` +/// * `FromZvalMut for &'a mut T` +/// * `IntoZendObject for T` +/// * `IntoZval for T` +/// +/// These implementations are required while we wait on the stabilisation of specialisation. +/// +/// # Examples +/// +/// ``` +/// # use ext_php_rs::{php::types::{zval::{Zval, IntoZval, FromZval}, object::{ZendObject, RegisteredClass}}}; +/// use ext_php_rs::class_derives; +/// +/// struct Test { +/// a: i32, +/// b: i64 +/// } +/// +/// impl RegisteredClass for Test { +/// const CLASS_NAME: &'static str = "Test"; +/// +/// const CONSTRUCTOR: Option> = None; +/// +/// fn get_metadata() -> &'static ext_php_rs::php::types::object::ClassMetadata { +/// todo!() +/// } +/// +/// fn get_properties<'a>( +/// ) -> std::collections::HashMap<&'static str, ext_php_rs::php::types::props::Property<'a, Self>> +/// { +/// todo!() +/// } +/// } +/// +/// class_derives!(Test); +/// +/// fn into_zval_test() -> Zval { +/// let x = Test { a: 5, b: 10 }; +/// x.into_zval(false).unwrap() +/// } +/// +/// fn from_zval_test<'a>(zv: &'a Zval) -> &'a Test { +/// <&Test>::from_zval(zv).unwrap() +/// } +/// ``` +/// +/// [`RegisteredClass`]: crate::php::types::object::RegisteredClass +/// [`ZendObject`]: crate::php::types::object::ZendObject +/// [`Zval`]: crate::php::types::zval::Zval +/// [`#[php_class]`]: crate::php_class +#[macro_export] +macro_rules! class_derives { + ($type: ty) => { + impl<'a> $crate::php::types::object::FromZendObject<'a> for &'a $type { + #[inline] + fn from_zend_object( + obj: &'a $crate::php::types::object::ZendObject, + ) -> $crate::errors::Result { + let obj = $crate::php::types::object::ZendClassObject::<$type>::from_zend_obj(obj) + .ok_or($crate::errors::Error::InvalidScope)?; + Ok(&**obj) + } + } + + impl<'a> $crate::php::types::object::FromZendObjectMut<'a> for &'a mut $type { + #[inline] + fn from_zend_object_mut( + obj: &'a mut $crate::php::types::object::ZendObject, + ) -> $crate::errors::Result { + let obj = + $crate::php::types::object::ZendClassObject::<$type>::from_zend_obj_mut(obj) + .ok_or($crate::errors::Error::InvalidScope)?; + Ok(&mut **obj) + } + } + + impl<'a> $crate::php::types::zval::FromZval<'a> for &'a $type { + const TYPE: $crate::php::enums::DataType = $crate::php::enums::DataType::Object(Some( + <$type as $crate::php::types::object::RegisteredClass>::CLASS_NAME, + )); + + #[inline] + fn from_zval(zval: &'a $crate::php::types::zval::Zval) -> ::std::option::Option { + ::from_zend_object( + zval.object()?, + ) + .ok() + } + } + + impl<'a> $crate::php::types::zval::FromZvalMut<'a> for &'a mut $type { + const TYPE: $crate::php::enums::DataType = $crate::php::enums::DataType::Object(Some( + <$type as $crate::php::types::object::RegisteredClass>::CLASS_NAME, + )); + + #[inline] + fn from_zval_mut( + zval: &'a mut $crate::php::types::zval::Zval, + ) -> ::std::option::Option { + ::from_zend_object_mut( + zval.object_mut()?, + ) + .ok() + } + } + + impl $crate::php::types::object::IntoZendObject for $type { + #[inline] + fn into_zend_object( + self, + ) -> $crate::errors::Result< + $crate::php::boxed::ZBox<$crate::php::types::object::ZendObject>, + > { + Ok($crate::php::types::object::ZendClassObject::new(self).into()) + } + } + + impl $crate::php::types::zval::IntoZval for $type { + const TYPE: $crate::php::enums::DataType = $crate::php::enums::DataType::Object(Some( + <$type as $crate::php::types::object::RegisteredClass>::CLASS_NAME, + )); + + #[inline] + fn set_zval( + self, + zv: &mut $crate::php::types::zval::Zval, + persistent: bool, + ) -> $crate::errors::Result<()> { + use $crate::php::types::object::IntoZendObject; + + self.into_zend_object()?.set_zval(zv, persistent) + } + } + }; +} diff --git a/src/php/args.rs b/src/php/args.rs index 7fdeac7c49..e5d3ba683a 100644 --- a/src/php/args.rs +++ b/src/php/args.rs @@ -4,9 +4,8 @@ use std::{ffi::CString, ptr}; use super::{ enums::DataType, - execution_data::ExecutionData, types::{ - zval::{FromZval, IntoZvalDyn, Zval}, + zval::{FromZvalMut, IntoZvalDyn, Zval}, ZendType, }, }; @@ -23,7 +22,7 @@ use crate::{ }; /// Represents an argument to a function. -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct Arg<'a> { name: String, _type: DataType, @@ -31,7 +30,7 @@ pub struct Arg<'a> { allow_null: bool, variadic: bool, default_value: Option, - zval: Option<&'a Zval>, + zval: Option<&'a mut Zval>, } impl<'a> Arg<'a> { @@ -78,11 +77,31 @@ impl<'a> Arg<'a> { self } + /// Attempts to consume the argument, converting the inner type into `T`. Upon success, + /// the result is returned in a [`Result`]. + /// + /// If the conversion fails (or the argument contains no value), the argument is returned + /// in an [`Err`] variant. + /// + /// As this function consumes, it cannot return a reference to the underlying zval. + pub fn consume(mut self) -> Result + where + for<'b> T: FromZvalMut<'b>, + { + self.zval + .as_mut() + .and_then(|zv| T::from_zval_mut(zv)) + .ok_or(self) + } + /// Attempts to retrieve the value of the argument. /// This will be None until the ArgParser is used to parse /// the arguments. - pub fn val>(&self) -> Option { - self.zval.and_then(|zv| T::from_zval(zv)) + pub fn val(&'a mut self) -> Option + where + T: FromZvalMut<'a>, + { + self.zval.as_mut().and_then(|zv| T::from_zval_mut(zv)) } /// Attempts to return a reference to the arguments internal Zval. @@ -91,8 +110,8 @@ impl<'a> Arg<'a> { /// /// * `Some(&Zval)` - The internal zval. /// * `None` - The argument was empty. - pub fn zval(&self) -> Option<&'a Zval> { - self.zval + pub fn zval(&mut self) -> Option<&mut &'a mut Zval> { + self.zval.as_mut() } /// Attempts to call the argument as a callable with a list of arguments to pass to the function. @@ -106,7 +125,7 @@ impl<'a> Arg<'a> { /// /// * `params` - A list of parameters to call the function with. pub fn try_call(&self, params: Vec<&dyn IntoZvalDyn>) -> Result { - self.zval().ok_or(Error::Callable)?.try_call(params) + self.zval.as_ref().ok_or(Error::Callable)?.try_call(params) } /// Returns the internal PHP argument info. @@ -153,19 +172,19 @@ impl From> for _zend_expected_type { pub type ArgInfo = zend_internal_arg_info; /// Parses the arguments of a function. -pub struct ArgParser<'a, 'arg, 'zval> { - args: Vec<&'arg mut Arg<'zval>>, - min_num_args: Option, - execute_data: &'a ExecutionData, +pub struct ArgParser<'a, 'b> { + args: Vec<&'b mut Arg<'a>>, + min_num_args: Option, + arg_zvals: Vec>, } -impl<'a, 'arg, 'zval> ArgParser<'a, 'arg, 'zval> { +impl<'a, 'b> ArgParser<'a, 'b> { /// Builds a new function argument parser. - pub fn new(execute_data: &'a ExecutionData) -> Self { + pub fn new(arg_zvals: Vec>) -> Self { ArgParser { args: vec![], min_num_args: None, - execute_data, + arg_zvals, } } @@ -174,14 +193,14 @@ impl<'a, 'arg, 'zval> ArgParser<'a, 'arg, 'zval> { /// # Parameters /// /// * `arg` - The argument to add to the parser. - pub fn arg(mut self, arg: &'arg mut Arg<'zval>) -> Self { + pub fn arg(mut self, arg: &'b mut Arg<'a>) -> Self { self.args.push(arg); self } /// Sets the next arguments to be added as not required. pub fn not_required(mut self) -> Self { - self.min_num_args = Some(self.args.len() as u32); + self.min_num_args = Some(self.args.len()); self } @@ -200,22 +219,20 @@ impl<'a, 'arg, 'zval> ArgParser<'a, 'arg, 'zval> { /// function. The user has already been notified so you should break execution after seeing an /// error type. pub fn parse(mut self) -> Result<()> { - let num_args = unsafe { self.execute_data.This.u2.num_args }; - let max_num_args = self.args.len() as u32; - let min_num_args = match self.min_num_args { - Some(n) => n, - None => max_num_args, - }; + let max_num_args = self.args.len(); + let min_num_args = self.min_num_args.unwrap_or(max_num_args); + let num_args = self.arg_zvals.len(); if num_args < min_num_args || num_args > max_num_args { // SAFETY: Exported C function is safe, return value is unused and parameters are copied. - unsafe { zend_wrong_parameters_count_error(min_num_args, max_num_args) }; - + unsafe { zend_wrong_parameters_count_error(min_num_args as _, max_num_args as _) }; return Err(Error::IncorrectArguments(num_args, min_num_args)); } - for (i, arg) in self.args.iter_mut().enumerate() { - arg.zval = unsafe { self.execute_data.zend_call_arg(i) }; + for (i, arg_zval) in self.arg_zvals.into_iter().enumerate() { + if let Some(arg) = self.args.get_mut(i) { + arg.zval = arg_zval; + } } Ok(()) diff --git a/src/php/execution_data.rs b/src/php/execution_data.rs index 30cc7a035f..24bdeca8a5 100644 --- a/src/php/execution_data.rs +++ b/src/php/execution_data.rs @@ -3,44 +3,98 @@ use crate::bindings::{zend_execute_data, ZEND_MM_ALIGNMENT, ZEND_MM_ALIGNMENT_MASK}; -use super::types::{ - object::{RegisteredClass, ZendClassObject, ZendObject}, - zval::Zval, +use super::{ + args::ArgParser, + types::{ + object::{RegisteredClass, ZendClassObject, ZendObject}, + zval::Zval, + }, }; /// Execution data passed when a function is called from Zend. pub type ExecutionData = zend_execute_data; impl ExecutionData { + /// Returns an [`ArgParser`] pre-loaded with the arguments contained inside `self`. + pub fn parser<'a>(&'a mut self) -> ArgParser<'a, '_> { + self.parser_object().0 + } + + /// Returns an [`ArgParser`] pre-loaded with the arguments contained inside `self`. + /// + /// A reference to `$this` is also returned in an [`Option`], which resolves to [`None`] + /// if this function is not called inside a method. + pub fn parser_object<'a>(&'a mut self) -> (ArgParser<'a, '_>, Option<&'a mut ZendObject>) { + // SAFETY: All fields of the `u2` union are the same type. + let n_args = unsafe { self.This.u2.num_args }; + let mut args = vec![]; + + for i in 0..n_args { + // SAFETY: Function definition ensures arg lifetime doesn't exceed execution data lifetime. + let arg = unsafe { self.zend_call_arg(i as usize) }; + args.push(arg); + } + + let obj = self.This.object_mut(); + + (ArgParser::new(args), obj) + } + + /// Returns an [`ArgParser`] pre-loaded with the arguments contained inside `self`. + /// + /// A reference to `$this` is also returned in an [`Option`], which resolves to [`None`] + /// if this function is not called inside a method. + /// + /// This function differs from [`parse_object`] in the fact that it returns a reference to + /// a [`ZendClassObject`], which is an object that contains an arbitrary Rust type at the + /// start of the object. The object will also resolve to [`None`] if the function is called + /// inside a method that does not belong to an object with type `T`. + /// + /// [`parse_object`]: #method.parse_object + pub fn parser_method<'a, T: RegisteredClass>( + &'a mut self, + ) -> (ArgParser<'a, '_>, Option<&'a mut ZendClassObject>) { + let (parser, obj) = self.parser_object(); + ( + parser, + obj.and_then(|obj| ZendClassObject::from_zend_obj_mut(obj)), + ) + } + /// Attempts to retrieve a reference to the underlying class object of the Zend object. /// /// Returns a [`ZendClassObject`] if the execution data contained a valid object of type `T`, /// otherwise returns [`None`]. - pub fn get_object(&self) -> Option<&mut ZendClassObject> { + pub fn get_object(&mut self) -> Option<&mut ZendClassObject> { // TODO(david): This should be a `&mut self` function but we need to fix arg parser first. ZendClassObject::from_zend_obj_mut(self.get_self()?) } /// Attempts to retrieve the 'this' object, which can be used in class methods /// to retrieve the underlying Zend object. - pub fn get_self(&self) -> Option<&mut ZendObject> { + pub fn get_self(&mut self) -> Option<&mut ZendObject> { // TODO(david): This should be a `&mut self` function but we need to fix arg parser first. - unsafe { self.This.value.obj.as_mut() } + self.This.object_mut() } /// Translation of macro `ZEND_CALL_ARG(call, n)` /// zend_compile.h:578 + /// + /// The resultant zval reference has a lifetime equal to the lifetime of `self`. + /// This isn't specified because when you attempt to get a reference to args and + /// the `$this` object, Rust doesnt't let you. Since this is a private method it's + /// up to the caller to ensure the lifetime isn't exceeded. #[doc(hidden)] - pub(crate) unsafe fn zend_call_arg(&self, n: usize) -> Option<&'static Zval> { + unsafe fn zend_call_arg<'a>(&self, n: usize) -> Option<&'a mut Zval> { let ptr = self.zend_call_var_num(n as isize); - ptr.as_ref() + ptr.as_mut() } /// Translation of macro `ZEND_CALL_VAR_NUM(call, n)` /// zend_compile.h: 575 #[doc(hidden)] - unsafe fn zend_call_var_num(&self, n: isize) -> *const Zval { - let ptr = self as *const Self as *const Zval; + unsafe fn zend_call_var_num(&self, n: isize) -> *mut Zval { + let ptr = self as *const Self as *mut Zval; ptr.offset(Self::zend_call_frame_slot() + n as isize) } diff --git a/src/php/function.rs b/src/php/function.rs index f73b4eff7e..9dd5d00853 100644 --- a/src/php/function.rs +++ b/src/php/function.rs @@ -41,7 +41,7 @@ pub type FunctionHandler = extern "C" fn(execute_data: &mut ExecutionData, retva type FunctionPointerHandler = extern "C" fn(execute_data: *mut ExecutionData, retval: *mut Zval); /// Builds a function to be exported as a PHP function. -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct FunctionBuilder<'a> { name: String, function: FunctionEntry, diff --git a/src/php/types/closure.rs b/src/php/types/closure.rs index 2917485fcd..72c3a74535 100644 --- a/src/php/types/closure.rs +++ b/src/php/types/closure.rs @@ -3,8 +3,13 @@ use std::collections::HashMap; use crate::php::{ - args::Arg, class::ClassBuilder, enums::DataType, exceptions::PhpException, - execution_data::ExecutionData, flags::MethodFlags, function::FunctionBuilder, + args::{Arg, ArgParser}, + class::ClassBuilder, + enums::DataType, + exceptions::PhpException, + execution_data::ExecutionData, + flags::MethodFlags, + function::FunctionBuilder, types::object::ClassMetadata, }; @@ -132,11 +137,10 @@ impl Closure { /// External function used by the Zend interpreter to call the closure. extern "C" fn invoke(ex: &mut ExecutionData, ret: &mut Zval) { - let this = ex - .get_object::() - .expect("Internal closure function called on non-closure class"); + let (parser, this) = ex.parser_method::(); + let this = this.expect("Internal closure function called on non-closure class"); - this.0.invoke(ex, ret) + this.0.invoke(parser, ret) } } @@ -152,6 +156,8 @@ impl RegisteredClass for Closure { } } +class_derives!(Closure); + /// Implemented on types which can be used as PHP closures. /// /// Types must implement the `invoke` function which will be called when the closure is called @@ -161,7 +167,7 @@ impl RegisteredClass for Closure { /// This trait is automatically implemented on functions with up to 8 parameters. pub unsafe trait PhpClosure { /// Invokes the closure. - fn invoke(&mut self, ex: &ExecutionData, ret: &mut Zval); + fn invoke<'a>(&'a mut self, parser: ArgParser<'a, '_>, ret: &mut Zval); } /// Implemented on [`FnOnce`] types which can be used as PHP closures. See [`Closure`]. @@ -177,7 +183,7 @@ unsafe impl PhpClosure for Box R> where R: IntoZval, { - fn invoke(&mut self, _: &ExecutionData, ret: &mut Zval) { + fn invoke(&mut self, _: ArgParser, ret: &mut Zval) { if let Err(e) = self().set_zval(ret, false) { let _ = PhpException::default(format!("Failed to return closure result to PHP: {}", e)) .throw(); @@ -189,7 +195,7 @@ unsafe impl PhpClosure for Box R> where R: IntoZval, { - fn invoke(&mut self, _: &ExecutionData, ret: &mut Zval) { + fn invoke(&mut self, _: ArgParser, ret: &mut Zval) { if let Err(e) = self().set_zval(ret, false) { let _ = PhpException::default(format!("Failed to return closure result to PHP: {}", e)) .throw(); @@ -258,18 +264,24 @@ macro_rules! php_closure_impl { $(for<'a> $gen: FromZval<'a>,)* Ret: IntoZval { - fn invoke(&mut self, ex: &ExecutionData, ret: &mut Zval) { + fn invoke(&mut self, parser: ArgParser, ret: &mut Zval) { $( let mut $gen = Arg::new(stringify!($gen), $gen::TYPE); )* - parse_args!(ex, $($gen),*); + let parser = parser + $(.arg(&mut $gen))* + .parse(); + + if parser.is_err() { + return; + } let result = self( $( - match $gen.val() { - Some(val) => val, - None => { + match $gen.consume() { + Ok(val) => val, + _ => { let _ = PhpException::default(concat!("Invalid parameter type for `", stringify!($gen), "`.").into()).throw(); return; } diff --git a/src/php/types/object.rs b/src/php/types/object.rs index 425bc0cd38..dc15820f5e 100644 --- a/src/php/types/object.rs +++ b/src/php/types/object.rs @@ -7,7 +7,7 @@ use std::{ ffi::c_void, fmt::Debug, marker::PhantomData, - mem::{self, ManuallyDrop, MaybeUninit}, + mem::{self, MaybeUninit}, ops::{Deref, DerefMut}, os::raw::c_int, ptr::{self, NonNull}, @@ -41,7 +41,7 @@ use super::{ props::Property, rc::PhpRc, string::ZendStr, - zval::{FromZval, IntoZval, Zval}, + zval::{FromZval, FromZvalMut, IntoZval, Zval}, }; pub type ZendObject = zend_object; @@ -97,6 +97,14 @@ impl ZendObject { }) } + /// Converts the class object into an owned [`ZendObject`]. This removes any possibility of + /// accessing the underlying attached Rust struct. + pub fn from_class_object(obj: ZBox>) -> ZBox { + let this = obj.into_raw(); + // SAFETY: Consumed box must produce a well-aligned non-null pointer. + unsafe { ZBox::from_raw(&mut this.std) } + } + /// Attempts to retrieve the class name of the object. pub fn get_class_name(&self) -> Result { unsafe { @@ -287,6 +295,16 @@ pub trait FromZendObject<'a>: Sized { fn from_zend_object(obj: &'a ZendObject) -> Result; } +/// Implemented on types which can be extracted from a mutable zend object. +/// +/// If `Self` does not require the object to be mutable, it should implement +/// [`FromZendObject`] instead, as this trait is generically implemented for +/// any types that also implement [`FromZendObject`]. +pub trait FromZendObjectMut<'a>: Sized { + /// Extracts `Self` from the source `ZendObject`. + fn from_zend_object_mut(obj: &'a mut ZendObject) -> Result; +} + /// Implemented on types which can be converted into a Zend object. It is up to the implementation /// to determine the type of object which is produced. pub trait IntoZendObject { @@ -346,6 +364,7 @@ impl<'a, T: RegisteredClass> ClassRef<'a, T> { impl<'a, T: RegisteredClass> IntoZval for ClassRef<'a, T> { const TYPE: DataType = DataType::Object(Some(T::CLASS_NAME)); + #[inline] fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> { zv.set_object(&mut self.ptr.std); Ok(()) @@ -353,13 +372,14 @@ impl<'a, T: RegisteredClass> IntoZval for ClassRef<'a, T> { } impl From>> for ZBox { + #[inline] fn from(obj: ZBox>) -> Self { - let mut this = ManuallyDrop::new(obj); - unsafe { ZBox::from_raw(&mut this.std) } + ZendObject::from_class_object(obj) } } impl Default for ZBox> { + #[inline] fn default() -> Self { ZendClassObject::new(T::default()) } @@ -664,7 +684,21 @@ impl<'a, T: RegisteredClass> FromZval<'a> for &'a ZendClassObject { impl<'a, T: RegisteredClass> FromZendObject<'a> for &'a ZendClassObject { fn from_zend_object(obj: &'a ZendObject) -> Result { // TODO(david): replace with better error - ZendClassObject::from_zend_obj(obj).ok_or(Error::InvalidPointer) + ZendClassObject::from_zend_obj(obj).ok_or(Error::InvalidScope) + } +} + +impl<'a, T: RegisteredClass> FromZvalMut<'a> for &'a mut ZendClassObject { + const TYPE: DataType = DataType::Object(Some(T::CLASS_NAME)); + + fn from_zval_mut(zval: &'a mut Zval) -> Option { + Self::from_zend_object_mut(zval.object_mut()?).ok() + } +} + +impl<'a, T: RegisteredClass> FromZendObjectMut<'a> for &'a mut ZendClassObject { + fn from_zend_object_mut(obj: &'a mut ZendObject) -> Result { + ZendClassObject::from_zend_obj_mut(obj).ok_or(Error::InvalidScope) } } @@ -1038,50 +1072,3 @@ impl ZendObjectHandlers { } } } - -impl<'a, T: RegisteredClass> FromZendObject<'a> for &'a T { - fn from_zend_object(obj: &'a ZendObject) -> Result { - // TODO(david): Error is kinda wrong, should have something like `WrongObject` - let cobj = ZendClassObject::::from_zend_obj(obj).ok_or(Error::InvalidPointer)?; - Ok(&**cobj) - } -} - -impl<'a, T: RegisteredClass> FromZval<'a> for &'a T { - const TYPE: DataType = DataType::Object(Some(T::CLASS_NAME)); - - fn from_zval(zval: &'a Zval) -> Option { - Self::from_zend_object(zval.object()?).ok() - } -} - -// TODO(david): Need something like `FromZendObjectMut` and `FromZvalMut` -// impl<'a, T: RegisteredClass> FromZendObject<'a> for &'a mut T { -// fn from_zend_object(obj: &'a ZendObject) -> Result { -// // TODO(david): Error is kinda wrong, should have something like `WrongObject` -// let cobj = ZendClassObject::::from_zend_obj_mut(obj).ok_or(Error::InvalidPointer)?; -// Ok(unsafe { cobj.obj.assume_init_mut() }) -// } -// } -// -// impl<'a, T: RegisteredClass> FromZval<'a> for &'a mut T { -// const TYPE: DataType = DataType::Object(Some(T::CLASS_NAME)); - -// fn from_zval(zval: &'a Zval) -> Option { -// Self::from_zend_object(zval.object()?).ok() -// } -// } - -impl IntoZendObject for T { - fn into_zend_object(self) -> Result> { - Ok(ZendClassObject::new(self).into()) - } -} - -impl IntoZval for T { - const TYPE: DataType = DataType::Object(Some(T::CLASS_NAME)); - - fn set_zval(self, zv: &mut Zval, persistent: bool) -> Result<()> { - self.into_zend_object()?.set_zval(zv, persistent) - } -} diff --git a/src/php/types/zval.rs b/src/php/types/zval.rs index a4925edd74..5905c1acdf 100644 --- a/src/php/types/zval.rs +++ b/src/php/types/zval.rs @@ -733,6 +733,35 @@ pub trait FromZval<'a>: Sized { fn from_zval(zval: &'a Zval) -> Option; } +/// Allows mutable zvals to be converted into Rust types in a fallible way. +/// +/// If `Self` does not require the zval to be mutable to be extracted, you should implement +/// [`FromZval`] instead, as this trait is generically implemented for any type that implements +/// [`FromZval`]. +pub trait FromZvalMut<'a>: Sized { + /// The corresponding type of the implemented value in PHP. + const TYPE: DataType; + + /// Attempts to retrieve an instance of `Self` from a mutable reference to a [`Zval`]. + /// + /// # Parameters + /// + /// * `zval` - Zval to get value from. + fn from_zval_mut(zval: &'a mut Zval) -> Option; +} + +impl<'a, T> FromZvalMut<'a> for T +where + T: FromZval<'a>, +{ + const TYPE: DataType = ::TYPE; + + #[inline] + fn from_zval_mut(zval: &'a mut Zval) -> Option { + Self::from_zval(zval) + } +} + impl<'a, T> FromZval<'a> for Option where T: FromZval<'a>,