diff --git a/example/skel/src/lib.rs b/example/skel/src/lib.rs index 94634f8..15ddfee 100644 --- a/example/skel/src/lib.rs +++ b/example/skel/src/lib.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::{collections::HashMap, convert::TryInto}; use ext_php_rs::{ call_user_func, info_table_end, info_table_row, info_table_start, parse_args, @@ -76,7 +76,7 @@ impl Test { let result = call_user_func!(_fn, "Hello", 5); - if let Some(r) = result { + if let Ok(r) = result { println!("{}", r.string().unwrap()); } @@ -105,37 +105,52 @@ pub extern "C" fn module_init(_type: i32, module_number: i32) -> i32 { ClassBuilder::new("TestClass") .method( - FunctionBuilder::constructor(Test::constructor).build(), + FunctionBuilder::constructor(Test::constructor) + .build() + .expect("could not build constructor"), MethodFlags::Public, ) .method( - FunctionBuilder::new("set", Test::set).build(), + FunctionBuilder::new("set", Test::set) + .build() + .expect("could not build set"), MethodFlags::Public, ) .method( - FunctionBuilder::new("get", Test::get).build(), + FunctionBuilder::new("get", Test::get) + .build() + .expect("could not build get"), MethodFlags::Public, ) .method( FunctionBuilder::new("debug", Test::debug) .arg(Arg::new("val", DataType::Object)) - .build(), + .build() + .expect("could not build debug"), MethodFlags::Public, ) .method( FunctionBuilder::new("call", Test::call) .arg(Arg::new("fn", DataType::Callable)) - .build(), + .build() + .expect("could not build call"), MethodFlags::Public, ) .property("asdf", "world", PropertyFlags::Public) + .expect("failed to add asdf property") .property("jhki", 12345, PropertyFlags::Public) + .expect("failed to add jhki property") .constant("TEST", "Hello world") + .expect("failed to add test constant") .object_override::() - .build(); + .build() + .expect("failed to build TestClass"); - "Test constant".register_constant("SKEL_TEST_CONST", module_number); - 1234.register_constant("SKEL_TEST_LONG_CONST", module_number); + "Test constant" + .register_constant("SKEL_TEST_CONST", module_number) + .expect("failed to add skel test const"); + 1234.register_constant("SKEL_TEST_LONG_CONST", module_number) + .expect("failed to add skel test long const"); 0 } @@ -148,20 +163,24 @@ pub extern "C" fn get_module() -> *mut ext_php_rs::php::module::ModuleEntry { .not_required() .arg(Arg::new("c", DataType::Double)) .returns(DataType::String, false, false) - .build(); + .build() + .expect("failed to build sheleton_version"); let array = FunctionBuilder::new("skel_array", skeleton_array) .arg(Arg::new("arr", DataType::Array)) - .build(); + .build() + .expect("failed to build skel_array"); let t = FunctionBuilder::new("test_array", test_array) .returns(DataType::Array, false, false) - .build(); + .build() + .expect("failed to build test_array"); let iter = FunctionBuilder::new("skel_unpack", skel_unpack) .arg(Arg::new("arr", DataType::String)) .returns(DataType::String, false, false) - .build(); + .build() + .expect("failed to build skel_unpack"); ModuleBuilder::new("ext-skel", "0.1.0") .info_function(php_module_info) @@ -171,6 +190,7 @@ pub extern "C" fn get_module() -> *mut ext_php_rs::php::module::ModuleEntry { .function(t) .function(iter) .build() + .expect("failed to build module") .into_raw() } @@ -182,7 +202,7 @@ pub extern "C" fn skeleton_version(execute_data: &mut ExecutionData, retval: &mu parse_args!(execute_data, x, y; z); dbg!(x); - retval.set_string("Hello"); + retval.set_string("Hello", false); } #[no_mangle] @@ -201,13 +221,18 @@ pub extern "C" fn skeleton_array(execute_data: &mut ExecutionData, _retval: &mut } let mut new = ZendHashTable::new(); - new.insert("Hello", "WOrld"); + new.insert("Hello", "WOrld") + .expect("couldnt insert into hashtable"); let _ = _retval.set_array(new); } #[no_mangle] pub extern "C" fn test_array(_execute_data: &mut ExecutionData, retval: &mut Zval) { - retval.set_array(vec![1, 2, 3, 4]); + retval.set_array( + vec![1, 2, 3, 4] + .try_into() + .expect("failed to convert vec to hashtable"), + ); } pub extern "C" fn skel_unpack(execute_data: &mut ExecutionData, retval: &mut Zval) { diff --git a/ext-php-rs-derive/src/lib.rs b/ext-php-rs-derive/src/lib.rs index 1306169..42441d4 100644 --- a/ext-php-rs-derive/src/lib.rs +++ b/ext-php-rs-derive/src/lib.rs @@ -32,10 +32,12 @@ pub fn object_handler_derive(input: TokenStream) -> TokenStream { #handlers = Some(::ext_php_rs::php::types::object::ZendObjectHandlers::init::<#name>()); } + // The handlers unwrap can never fail - we check that it is none above. + // Unwrapping the result from `new_ptr` is nessacary as C cannot handle results. ::ext_php_rs::php::types::object::ZendClassObject::<#name>::new_ptr( ce, #handlers.unwrap() - ) + ).expect("Failed to allocate memory for new Zend object.") } } } diff --git a/src/errors.rs b/src/errors.rs index 938964c..f55bcdc 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -36,6 +36,10 @@ pub enum Error { InvalidPointer, /// The given property name does not exist. InvalidProperty, + /// The string could not be converted into a C-string due to the presence of a NUL character. + InvalidCString, + /// Could not call the given function. + Callable, } impl Display for Error { @@ -58,6 +62,11 @@ impl Display for Error { Error::InvalidScope => write!(f, "Invalid scope."), Error::InvalidPointer => write!(f, "Invalid pointer."), Error::InvalidProperty => write!(f, "Property does not exist on object."), + Error::InvalidCString => write!( + f, + "String given contains NUL-bytes which cannot be present in a C string." + ), + Error::Callable => write!(f, "Could not call given function."), } } } diff --git a/src/functions.rs b/src/functions.rs index 833c588..c29492a 100644 --- a/src/functions.rs +++ b/src/functions.rs @@ -1,5 +1,6 @@ //! Helper functions useful for interacting with PHP and Zend. +use crate::errors::{Error, Result}; use std::ffi::CString; /// Takes a Rust string object, converts it into a C string @@ -14,7 +15,7 @@ use std::ffi::CString; /// use std::ffi::CString; /// use ext_php_rs::functions::c_str; /// -/// let mut ptr = c_str("Hello"); +/// let mut ptr = c_str("Hello").unwrap(); /// /// unsafe { /// assert_eq!(b'H', *ptr as u8); @@ -28,9 +29,16 @@ use std::ffi::CString; /// let _ = CString::from_raw(ptr as *mut i8); /// } /// ``` -pub fn c_str(s: S) -> *const i8 +/// +/// # Errors +/// +/// Returns an error if the given string contains a NUL byte, which for obvious reasons cannot be +/// contained inside a C string. +pub fn c_str(s: S) -> Result<*const i8> where S: AsRef, { - CString::into_raw(CString::new(s.as_ref()).unwrap()) + Ok(CString::into_raw( + CString::new(s.as_ref()).map_err(|_| Error::InvalidCString)?, + )) } diff --git a/src/lib.rs b/src/lib.rs index 3220f6e..17d5aef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +#![deny(clippy::unwrap_used)] #![allow(non_upper_case_globals)] #![allow(non_camel_case_types)] #![allow(non_snake_case)] diff --git a/src/macros.rs b/src/macros.rs index 1fcf0c2..bd8a266 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -58,7 +58,7 @@ macro_rules! _info_table_row { #[macro_export] macro_rules! call_user_func { ($fn: expr, $($param: expr),*) => { - $fn.try_call(vec![$($param.into()),*]) + $fn.try_call(vec![$(&$param),*]) }; } diff --git a/src/php/args.rs b/src/php/args.rs index ba744f9..91add85 100644 --- a/src/php/args.rs +++ b/src/php/args.rs @@ -2,7 +2,11 @@ use std::convert::{TryFrom, TryInto}; -use super::{enums::DataType, execution_data::ExecutionData, types::zval::Zval}; +use super::{ + enums::DataType, + execution_data::ExecutionData, + types::zval::{IntoZval, Zval}, +}; use crate::{ bindings::{ @@ -97,20 +101,16 @@ impl<'a> Arg<'a> { /// Attempts to call the argument as a callable with a list of arguments to pass to the function. /// Note that a thrown exception inside the callable is not detectable, therefore you should - /// check if the return value is valid rather than unwrapping. + /// check if the return value is valid rather than unwrapping. Returns a result containing the + /// return value of the function, or an error. /// /// You should not call this function directly, rather through the [`call_user_func`] macro. /// /// # Parameters /// /// * `params` - A list of parameters to call the function with. - /// - /// # Returns - /// - /// * `Some(Zval)` - The result of the function call. - /// * `None` - The argument was empty, the argument was not callable or the call failed. - pub fn try_call(&self, params: Vec) -> Option { - self.zval()?.try_call(params) + pub fn try_call(&self, params: Vec<&dyn IntoZval>) -> Result { + self.zval().ok_or(Error::Callable)?.try_call(params) } } @@ -139,15 +139,15 @@ impl From> for _zend_expected_type { pub type ArgInfo = zend_internal_arg_info; /// Parses the arguments of a function. -pub struct ArgParser<'a, 'b> { - args: Vec<&'a mut Arg<'b>>, +pub struct ArgParser<'a, 'arg, 'zval> { + args: Vec<&'arg mut Arg<'zval>>, min_num_args: Option, - execute_data: *mut ExecutionData, + execute_data: &'a mut ExecutionData, } -impl<'a, 'b> ArgParser<'a, 'b> { +impl<'a, 'arg, 'zval> ArgParser<'a, 'arg, 'zval> { /// Builds a new function argument parser. - pub fn new(execute_data: *mut ExecutionData) -> Self { + pub fn new(execute_data: &'a mut ExecutionData) -> Self { ArgParser { args: vec![], min_num_args: None, @@ -160,7 +160,7 @@ impl<'a, 'b> ArgParser<'a, 'b> { /// # Parameters /// /// * `arg` - The argument to add to the parser. - pub fn arg(mut self, arg: &'a mut Arg<'b>) -> Self { + pub fn arg(mut self, arg: &'arg mut Arg<'zval>) -> Self { self.args.push(arg); self } @@ -172,21 +172,21 @@ impl<'a, 'b> ArgParser<'a, 'b> { } /// Uses the argument parser to parse the arguments contained in the given - /// `ExecutionData` object. + /// `ExecutionData` object. Returns successfully if the arguments were parsed. + /// + /// This function can only be safely called from within an exported PHP function. /// /// # Parameters /// /// * `execute_data` - The execution data from the function. /// - /// # Returns + /// # Errors /// - /// * `Ok(())` - The arguments were successfully parsed. - /// * `Err(String)` - There were too many or too little arguments - /// passed to the function. The user has already been notified so you - /// can discard and return from the function if an `Err` is received. + /// Returns an [`Error`] type if there were too many or too little arguments passed to the + /// function. The user has already been notified so you should break execution after seeing an + /// error type. pub fn parse(mut self) -> Result<()> { - let execute_data = unsafe { self.execute_data.as_ref() }.unwrap(); - let num_args = unsafe { execute_data.This.u2.num_args }; + 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, @@ -194,13 +194,14 @@ impl<'a, 'b> ArgParser<'a, 'b> { }; 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) }; return Err(Error::IncorrectArguments(num_args, min_num_args)); } for (i, arg) in self.args.iter_mut().enumerate() { - arg.zval = unsafe { execute_data.zend_call_arg(i) }; + arg.zval = unsafe { self.execute_data.zend_call_arg(i) }; } Ok(()) diff --git a/src/php/class.rs b/src/php/class.rs index 03e6837..8f4fe70 100644 --- a/src/php/class.rs +++ b/src/php/class.rs @@ -1,11 +1,12 @@ //! Builder and objects for creating classes in the PHP world. +use crate::errors::{Error, Result}; use std::{mem, ptr}; use crate::{ bindings::{ - ext_php_rs_zend_string_release, zend_class_entry, zend_declare_class_constant, - zend_declare_property, zend_register_internal_class_ex, + zend_class_entry, zend_declare_class_constant, zend_declare_property, + zend_register_internal_class_ex, }, functions::c_str, }; @@ -16,7 +17,7 @@ use super::{ types::{ object::{ZendObject, ZendObjectOverride}, string::ZendString, - zval::Zval, + zval::{IntoZval, Zval}, }, }; @@ -25,8 +26,9 @@ pub type ClassEntry = zend_class_entry; /// Builds a class to be exported as a PHP class. pub struct ClassBuilder<'a> { + name: String, ptr: &'a mut ClassEntry, - extends: *mut ClassEntry, + extends: Option<&'static ClassEntry>, methods: Vec, object_override: Option *mut ZendObject>, properties: Vec<(String, Zval, PropertyFlags)>, @@ -40,20 +42,24 @@ impl<'a> ClassBuilder<'a> { /// # Parameters /// /// * `name` - The name of the class. + #[allow(clippy::unwrap_used)] pub fn new(name: N) -> Self where N: AsRef, { + // SAFETY: Allocating temporary class entry. Will return a null-ptr if allocation fails, + // which will cause the program to panic (standard in Rust). Unwrapping is OK - the ptr + // will either be valid or null. let ptr = unsafe { (libc::calloc(1, mem::size_of::()) as *mut ClassEntry) .as_mut() .unwrap() }; - ptr.name = ZendString::new_interned(name).release(); Self { + name: name.as_ref().to_string(), ptr, - extends: ptr::null_mut(), + extends: None, methods: vec![], object_override: None, properties: vec![], @@ -67,7 +73,7 @@ impl<'a> ClassBuilder<'a> { /// /// * `parent` - The parent class to extend. pub fn extends(mut self, parent: &'static ClassEntry) -> Self { - self.extends = (parent as *const _) as *mut _; + self.extends = Some(parent); self } @@ -86,6 +92,8 @@ impl<'a> ClassBuilder<'a> { /// Adds a property to the class. The initial type of the property is given by the type /// of the given default. Note that the user can change the type. /// + /// Returns a result containing the class builder if the property was successfully added. + /// /// # Parameters /// /// * `name` - The name of the property to add to the class. @@ -94,40 +102,30 @@ impl<'a> ClassBuilder<'a> { pub fn property( mut self, name: impl AsRef, - default: impl Into, + default: impl IntoZval, flags: PropertyFlags, - ) -> Self { - let mut default = default.into(); - - if default.is_string() { - let val = default.string().unwrap(); - unsafe { ext_php_rs_zend_string_release(default.value.str_) }; - default.set_persistent_string(val); - } + ) -> Result { + let default = default.as_zval(true)?; self.properties .push((name.as_ref().to_string(), default, flags)); - self + Ok(self) } - /// Adds a constant to the class. - /// The type of the constant is defined by the type of the given default. + /// Adds a constant to the class. The type of the constant is defined by the type of the given + /// default. + /// + /// Returns a result containing the class builder if the constant was successfully added. /// /// # Parameters /// /// * `name` - The name of the constant to add to the class. /// * `value` - The value of the constant. - pub fn constant(mut self, name: impl AsRef, value: impl Into) -> Self { - let mut value = value.into(); - - if value.is_string() { - let val = value.string().unwrap(); - unsafe { ext_php_rs_zend_string_release(value.value.str_) }; - value.set_persistent_string(val); - } + pub fn constant(mut self, name: impl AsRef, value: impl IntoZval) -> Result { + let value = value.as_zval(true)?; self.constants.push((name.as_ref().to_string(), value)); - self + Ok(self) } /// Sets the flags for the class. @@ -160,21 +158,34 @@ impl<'a> ClassBuilder<'a> { /// /// # Errors /// - /// Returns `None` if the class could not be built. - pub fn build(mut self) -> Option<&'static mut ClassEntry> { + /// Returns an [`Error`] variant if the class could not be registered. + pub fn build(mut self) -> Result<&'static mut ClassEntry> { + self.ptr.name = ZendString::new_interned(self.name)?.release(); + self.methods.push(FunctionEntry::end()); let func = Box::into_raw(self.methods.into_boxed_slice()) as *const FunctionEntry; self.ptr.info.internal.builtin_functions = func; - let class = unsafe { zend_register_internal_class_ex(self.ptr, self.extends).as_mut()? }; + let class = unsafe { + zend_register_internal_class_ex( + self.ptr, + match self.extends { + Some(ptr) => (ptr as *const _) as *mut _, + None => ptr::null_mut(), + }, + ) + .as_mut() + .ok_or(Error::InvalidPointer)? + }; + // SAFETY: We allocated memory for this pointer in `new`, so it is our job to free it when the builder has finished. unsafe { libc::free((self.ptr as *mut ClassEntry) as *mut libc::c_void) }; for (name, mut default, flags) in self.properties { unsafe { zend_declare_property( class, - c_str(&name), + c_str(&name)?, name.len() as _, &mut default, flags.bits() as _, @@ -184,13 +195,13 @@ impl<'a> ClassBuilder<'a> { for (name, value) in self.constants { let value = Box::into_raw(Box::new(value)); - unsafe { zend_declare_class_constant(class, c_str(&name), name.len() as u64, value) }; + unsafe { zend_declare_class_constant(class, c_str(&name)?, name.len() as u64, value) }; } if let Some(object_override) = self.object_override { class.__bindgen_anon_2.create_object = Some(object_override); } - Some(class) + Ok(class) } } diff --git a/src/php/constants.rs b/src/php/constants.rs index e0f908f..22d93d1 100644 --- a/src/php/constants.rs +++ b/src/php/constants.rs @@ -1,4 +1,5 @@ use super::flags::GlobalConstantFlags; +use crate::errors::Result; use crate::{ bindings::{ zend_register_bool_constant, zend_register_double_constant, zend_register_long_constant, @@ -14,6 +15,8 @@ pub trait IntoConst: Sized { /// module number. By default, the case-insensitive and persistent flags are set when /// registering the constant. /// + /// Returns a result containing nothing if the constant was successfully registered. + /// /// # Parameters /// /// * `name` - The name of the constant. @@ -30,7 +33,7 @@ pub trait IntoConst: Sized { /// 0 /// } /// ``` - fn register_constant>(&self, name: N, module_number: i32) { + fn register_constant>(&self, name: N, module_number: i32) -> Result<()> { self.register_constant_flags( name, module_number, @@ -45,6 +48,8 @@ pub trait IntoConst: Sized { /// Note that the case-sensitive and persistent flags *are not* set when you use this function, /// you must set these yourself. /// + /// Returns a result containing nothing if the constant was successfully registered. + /// /// # Parameters /// /// * `name` - The name of the constant. @@ -66,7 +71,7 @@ pub trait IntoConst: Sized { name: N, module_number: i32, flags: GlobalConstantFlags, - ); + ) -> Result<()>; } impl IntoConst for String { @@ -75,9 +80,9 @@ impl IntoConst for String { name: N, module_number: i32, flags: GlobalConstantFlags, - ) { + ) -> Result<()> { self.as_str() - .register_constant_flags(name, module_number, flags); + .register_constant_flags(name, module_number, flags) } } @@ -87,17 +92,18 @@ impl IntoConst for &str { name: N, module_number: i32, flags: GlobalConstantFlags, - ) { + ) -> Result<()> { let name = name.as_ref(); unsafe { zend_register_string_constant( - c_str(name), + c_str(name)?, name.len() as _, - c_str(self), + c_str(self)?, flags.bits() as _, module_number, ) }; + Ok(()) } } @@ -107,17 +113,18 @@ impl IntoConst for bool { name: N, module_number: i32, flags: GlobalConstantFlags, - ) { + ) -> Result<()> { let name = name.as_ref(); unsafe { zend_register_bool_constant( - c_str(name), + c_str(name)?, name.len() as _, *self, flags.bits() as _, module_number, ) - } + }; + Ok(()) } } @@ -130,17 +137,17 @@ macro_rules! into_const_num { name: N, module_number: i32, flags: GlobalConstantFlags, - ) { + ) -> Result<()> { let name = name.as_ref(); - unsafe { + Ok(unsafe { $fn( - c_str(name), + c_str(name)?, name.len() as _, *self as _, flags.bits() as _, module_number, ) - }; + }) } } }; diff --git a/src/php/exceptions.rs b/src/php/exceptions.rs index 37cf6c0..b405cc4 100644 --- a/src/php/exceptions.rs +++ b/src/php/exceptions.rs @@ -8,12 +8,15 @@ use crate::{ zend_ce_parse_error, zend_ce_throwable, zend_ce_type_error, zend_ce_unhandled_match_error, zend_ce_value_error, zend_throw_exception_ex, }, + errors::Result, functions::c_str, }; /// Throws an exception with a given message. See [`ClassEntry`] for some built-in exception /// types. /// +/// Returns a result containing nothing if the exception was successfully thown. +/// /// # Parameters /// /// * `ex` - The exception type to throw. @@ -26,13 +29,15 @@ use crate::{ /// /// throw(ClassEntry::compile_error(), "This is a CompileError."); /// ``` -pub fn throw(ex: &ClassEntry, message: &str) { - throw_with_code(ex, 0, message); +pub fn throw(ex: &ClassEntry, message: &str) -> Result<()> { + throw_with_code(ex, 0, message) } /// Throws an exception with a given message and status code. See [`ClassEntry`] for some built-in /// exception types. /// +/// Returns a result containing nothing if the exception was successfully thown. +/// /// # Parameters /// /// * `ex` - The exception type to throw. @@ -46,19 +51,23 @@ pub fn throw(ex: &ClassEntry, message: &str) { /// /// throw_with_code(ClassEntry::compile_error(), 123, "This is a CompileError."); /// ``` -pub fn throw_with_code(ex: &ClassEntry, code: i32, message: &str) { +pub fn throw_with_code(ex: &ClassEntry, code: i32, message: &str) -> Result<()> { // SAFETY: We are given a reference to a `ClassEntry` therefore when we cast it to a pointer it // will be valid. unsafe { zend_throw_exception_ex( (ex as *const _) as *mut _, code as _, - c_str("%s"), - c_str(message), + c_str("%s")?, + c_str(message)?, ) }; + Ok(()) } +// SAFETY: All default exceptions have been initialized by the time our extension has been loaded. +// Due to this, we can safely unwrap the results. +#[allow(clippy::unwrap_used)] impl ClassEntry { /// Returns the base `Throwable` class. pub fn throwable() -> &'static Self { diff --git a/src/php/function.rs b/src/php/function.rs index c02b59d..019b5e8 100644 --- a/src/php/function.rs +++ b/src/php/function.rs @@ -2,6 +2,7 @@ use std::{mem, os::raw::c_char, ptr}; +use crate::errors::Result; use crate::{bindings::zend_function_entry, functions::c_str}; use super::{ @@ -42,6 +43,7 @@ type FunctionPointerHandler = extern "C" fn(execute_data: *mut ExecutionData, re /// Builds a function to be exported as a PHP function. #[derive(Debug, Clone)] pub struct FunctionBuilder<'a> { + name: String, function: FunctionEntry, args: Vec>, n_req: Option, @@ -63,8 +65,9 @@ impl<'a> FunctionBuilder<'a> { N: AsRef, { Self { + name: name.as_ref().to_string(), function: FunctionEntry { - fname: c_str(name), + fname: ptr::null(), handler: Some(unsafe { mem::transmute::(handler) }), @@ -121,7 +124,9 @@ impl<'a> FunctionBuilder<'a> { } /// Builds the function converting it into a Zend function entry. - pub fn build(mut self) -> FunctionEntry { + /// + /// Returns a result containing the function entry if successful. + pub fn build(mut self) -> Result { let mut args = Vec::with_capacity(self.args.len() + 1); // argument header, retval etc @@ -142,17 +147,19 @@ impl<'a> FunctionBuilder<'a> { // arguments for arg in self.args.iter() { args.push(ArgInfo { - name: c_str(arg.name.clone()), + name: c_str(&arg.name)?, type_: ZendType::empty_from_type(arg._type, arg.as_ref, false, arg.allow_null), default_value: match &arg.default_value { - Some(val) => c_str(val), + Some(val) => c_str(val)?, None => ptr::null(), }, }); } + self.function.fname = c_str(self.name)?; self.function.num_args = (args.len() - 1) as u32; self.function.arg_info = Box::into_raw(args.into_boxed_slice()) as *const ArgInfo; - self.function + + Ok(self.function) } } diff --git a/src/php/module.rs b/src/php/module.rs index 15eb402..a99a91c 100644 --- a/src/php/module.rs +++ b/src/php/module.rs @@ -6,6 +6,7 @@ use crate::{ bindings::{ ext_php_rs_php_build_id, zend_module_entry, USING_ZTS, ZEND_DEBUG, ZEND_MODULE_API_NO, }, + errors::Result, functions::c_str, }; @@ -44,6 +45,8 @@ pub type InfoFunc = extern "C" fn(zend_module: *mut ModuleEntry); /// ``` #[derive(Debug, Clone)] pub struct ModuleBuilder { + name: String, + version: String, module: ModuleEntry, functions: Vec, } @@ -61,6 +64,8 @@ impl ModuleBuilder { V: AsRef, { Self { + name: name.as_ref().to_string(), + version: version.as_ref().to_string(), module: ModuleEntry { size: mem::size_of::() as u16, zend_api: ZEND_MODULE_API_NO, @@ -68,14 +73,14 @@ impl ModuleBuilder { zts: USING_ZTS as u8, ini_entry: ptr::null(), deps: ptr::null(), - name: c_str(name), + name: ptr::null(), functions: ptr::null(), module_startup_func: None, module_shutdown_func: None, request_startup_func: None, request_shutdown_func: None, info_func: None, - version: c_str(version), + version: ptr::null(), globals_size: 0, #[cfg(not(feature = "zts"))] globals_ptr: ptr::null::() as *mut c_void, @@ -155,12 +160,16 @@ impl ModuleBuilder { } /// Builds the extension and returns a `ModuleEntry`. - pub fn build(mut self) -> ModuleEntry { - // TODO: move to seperate function + /// + /// Returns a result containing the module entry if successful. + pub fn build(mut self) -> Result { self.functions.push(FunctionEntry::end()); self.module.functions = Box::into_raw(self.functions.into_boxed_slice()) as *const FunctionEntry; - self.module + self.module.name = c_str(self.name)?; + self.module.version = c_str(self.version)?; + + Ok(self.module) } } diff --git a/src/php/types/array.rs b/src/php/types/array.rs index e16e391..7d3891e 100644 --- a/src/php/types/array.rs +++ b/src/php/types/array.rs @@ -19,7 +19,19 @@ use crate::{ functions::c_str, }; -use super::{string::ZendString, zval::Zval}; +use super::{ + string::ZendString, + zval::{IntoZval, Zval}, +}; + +/// Result type returned after attempting to insert an element into a hash table. +#[derive(Debug)] +pub enum HashTableInsertResult<'a> { + /// The element was inserted into the hash table successfully. + Ok, + /// The element was inserted into the hash table successfully, over-writing an existing element. + OkWithOverwrite(&'a Zval), +} /// A PHP array, which internally is a hash table. pub struct ZendHashTable { @@ -97,7 +109,7 @@ impl ZendHashTable { { let _key = key.into(); let len = _key.len(); - unsafe { zend_hash_str_find(self.ptr, c_str(_key), len as u64).as_ref() } + unsafe { zend_hash_str_find(self.ptr, c_str(_key).ok()?, len as u64).as_ref() } } /// Attempts to retrieve a value from the hash table with an index. @@ -122,15 +134,15 @@ impl ZendHashTable { /// /// # Returns /// - /// * `Ok(())` - Key was successfully removed. - /// * `Err(())` - No key was removed, did not exist. + /// * `Some(())` - Key was successfully removed. + /// * `None` - No key was removed, did not exist. pub fn remove(&self, key: K) -> Option<()> where K: Into, { let _key = key.into(); let len = _key.len(); - let result = unsafe { zend_hash_str_del(self.ptr, c_str(_key), len as u64) }; + let result = unsafe { zend_hash_str_del(self.ptr, c_str(_key).ok()?, len as u64) }; if result < 0 { None @@ -160,29 +172,26 @@ impl ZendHashTable { } /// Attempts to insert an item into the hash table, or update if the key already exists. + /// Returns a result containing a [`HashTableInsertResult`], which will indicate a successful + /// insert, with the insert result variants either containing the overwritten value or nothing. /// /// # Parameters /// /// * `key` - The key to insert the value at in the hash table. /// * `value` - The value to insert into the hash table. - /// - /// # Returns - /// - /// * `Some(Zval)` - The existing value in the hash table that was overriden. - /// * `None` - The element was inserted. - pub fn insert(&mut self, key: K, val: V) -> Option<&Zval> + pub fn insert(&mut self, key: K, val: V) -> Result where K: Into, - V: Into, + V: IntoZval, { let key: String = key.into(); let len = key.len(); - let val: Zval = val.into(); + let val = val.as_zval(false)?; let existing_ptr = unsafe { zend_hash_str_update( self.ptr, - c_str(key), + c_str(key)?, len as u64, Box::into_raw(Box::new(val)), // Do we really want to allocate the value on the heap? // I read somewhere that zvals are't usually (or never) allocated on the heap. @@ -191,7 +200,13 @@ impl ZendHashTable { // Should we be claiming this Zval into rust? // I'm not sure if the PHP GC will collect this. - unsafe { existing_ptr.as_ref() } + + // SAFETY: The `zend_hash_str_update` function will either return a valid pointer or a null pointer. + // In the latter case, `as_ref()` will return `None`. + Ok(match unsafe { existing_ptr.as_ref() } { + Some(ptr) => HashTableInsertResult::OkWithOverwrite(ptr), + None => HashTableInsertResult::Ok, + }) } /// Inserts an item into the hash table at a specified index, @@ -206,31 +221,37 @@ impl ZendHashTable { /// /// * `Some(&Zval)` - The existing value in the hash table that was overriden. /// * `None` - The element was inserted. - pub fn insert_at_index(&mut self, key: u64, val: V) -> Option<&Zval> + pub fn insert_at_index(&mut self, key: u64, val: V) -> Result where - V: Into, + V: IntoZval, { - let val: Zval = val.into(); + let val = val.as_zval(false)?; let existing_ptr = unsafe { zend_hash_index_update(self.ptr, key, Box::into_raw(Box::new(val))) }; - // See `insert` function comment. - unsafe { existing_ptr.as_ref() } + // SAFETY: The `zend_hash_str_update` function will either return a valid pointer or a null pointer. + // In the latter case, `as_ref()` will return `None`. + Ok(match unsafe { existing_ptr.as_ref() } { + Some(ptr) => HashTableInsertResult::OkWithOverwrite(ptr), + None => HashTableInsertResult::Ok, + }) } - /// Pushes an item onto the end of the hash table. + /// Pushes an item onto the end of the hash table. Returns a result containing nothing if the + /// element was sucessfully inserted. /// /// # Parameters /// /// * `val` - The value to insert into the hash table. - pub fn push(&mut self, val: V) + pub fn push(&mut self, val: V) -> Result<()> where - V: Into, + V: IntoZval, { - let val: Zval = val.into(); + let val = val.as_zval(false)?; unsafe { zend_hash_next_index_insert(self.ptr, Box::into_raw(Box::new(val))) }; + Ok(()) } /// Returns an iterator over the hash table. @@ -343,19 +364,21 @@ impl<'a> From<&'a ZendHashTable> for HashMap { } /// Implementation converting a Rust HashTable into a ZendHashTable. -impl<'a, K, V> From<&'a HashMap> for ZendHashTable +impl<'a, K, V> TryFrom<&'a HashMap> for ZendHashTable where K: Into + Copy, - V: Into + Copy, + V: IntoZval + Copy, { - fn from(hm: &'a HashMap) -> Self { + type Error = Error; + + fn try_from(hm: &'a HashMap) -> Result { let mut ht = ZendHashTable::with_capacity(hm.len() as u32); for (k, v) in hm.iter() { - ht.insert(*k, *v); + ht.insert(*k, *v)?; } - ht + Ok(ht) } } @@ -374,17 +397,19 @@ where } /// Implementation for converting a Rust Vec into a ZendHashTable. -impl<'a, V> From> for ZendHashTable +impl<'a, V> TryFrom> for ZendHashTable where - V: Into, + V: IntoZval, { - fn from(vec: Vec) -> Self { + type Error = Error; + + fn try_from(vec: Vec) -> Result { let mut ht = ZendHashTable::with_capacity(vec.len() as u32); for v in vec { - ht.push(v); + ht.push(v)?; } - ht + Ok(ht) } } diff --git a/src/php/types/object.rs b/src/php/types/object.rs index 88f0351..655833d 100644 --- a/src/php/types/object.rs +++ b/src/php/types/object.rs @@ -18,7 +18,10 @@ use crate::{ php::{class::ClassEntry, execution_data::ExecutionData, types::string::ZendString}, }; -use super::{array::ZendHashTable, zval::Zval}; +use super::{ + array::ZendHashTable, + zval::{IntoZval, Zval}, +}; pub type ZendObject = zend_object; pub type ZendObjectHandlers = zend_object_handlers; @@ -63,7 +66,7 @@ impl ZendObject { return Err(Error::InvalidProperty); } - let name = ZendString::new(name, false); + let name = ZendString::new(name, false)?; let mut rv = Zval::new(); unsafe { @@ -86,9 +89,9 @@ impl ZendObject { /// /// * `name` - The name of the property. /// * `value` - The value to set the property to. - pub fn set_property(&mut self, name: impl AsRef, value: impl Into) -> Result<&Zval> { - let name = ZendString::new(name, false); - let mut value = value.into(); + pub fn set_property(&mut self, name: impl AsRef, value: impl IntoZval) -> Result<&Zval> { + let name = ZendString::new(name, false)?; + let mut value = value.as_zval(false)?; if value.is_string() { value.set_refcount(0); @@ -115,7 +118,7 @@ impl ZendObject { /// * `name` - The name of the property. /// * `query` - The 'query' to classify if a property exists. pub fn has_property(&self, name: impl AsRef, query: PropertyQuery) -> Result { - let name = ZendString::new(name.as_ref(), false); + let name = ZendString::new(name.as_ref(), false)?; Ok(unsafe { self.handlers()?.has_property.ok_or(Error::InvalidScope)?( @@ -215,12 +218,12 @@ impl ZendClassObject { pub unsafe fn new_ptr( ce: *mut ClassEntry, handlers: *mut ZendObjectHandlers, - ) -> *mut zend_object { + ) -> Result<*mut zend_object> { let obj = { let obj = (ext_php_rs_zend_object_alloc(std::mem::size_of::() as _, ce) as *mut Self) .as_mut() - .unwrap(); + .ok_or(Error::InvalidPointer)?; zend_object_std_init(&mut obj.std, ce); object_properties_init(&mut obj.std, ce); @@ -229,7 +232,7 @@ impl ZendClassObject { obj.obj = T::default(); obj.std.handlers = handlers; - &mut obj.std + Ok(&mut obj.std) } /// Attempts to retrieve the Zend class object container from the diff --git a/src/php/types/string.rs b/src/php/types/string.rs index 029cee3..4e2e809 100644 --- a/src/php/types/string.rs +++ b/src/php/types/string.rs @@ -24,33 +24,36 @@ pub struct ZendString { } impl ZendString { - /// Creates a new Zend string. + /// Creates a new Zend string. Returns a result containin the string. /// /// # Parameters /// /// * `str_` - The string to create a Zend string from. /// * `persistent` - Whether the request should relive the request boundary. - pub fn new(str_: impl AsRef, persistent: bool) -> Self { + pub fn new(str_: impl AsRef, persistent: bool) -> Result { let str_ = str_.as_ref(); - Self { - ptr: unsafe { ext_php_rs_zend_string_init(c_str(str_), str_.len() as _, persistent) }, + Ok(Self { + ptr: unsafe { ext_php_rs_zend_string_init(c_str(str_)?, str_.len() as _, persistent) }, free: true, - } + }) } - /// Creates a new interned Zend string. + /// Creates a new interned Zend string. Returns a result containing the interned string. /// /// # Parameters /// /// * `str_` - The string to create a Zend string from. - pub fn new_interned(str_: impl AsRef) -> Self { + #[allow(clippy::unwrap_used)] + pub fn new_interned(str_: impl AsRef) -> Result { let str_ = str_.as_ref(); - Self { - ptr: unsafe { zend_string_init_interned.unwrap()(c_str(str_), str_.len() as _, true) }, + // Unwrap is OK here - `zend_string_init_interned` will be a valid function ptr by the time + // our extension is loaded. + Ok(Self { + ptr: unsafe { zend_string_init_interned.unwrap()(c_str(str_)?, str_.len() as _, true) }, free: true, - } + }) } /// Creates a new [`ZendString`] wrapper from a raw pointer to a [`zend_string`]. diff --git a/src/php/types/zval.rs b/src/php/types/zval.rs index 3f1edaa..ba09491 100644 --- a/src/php/types/zval.rs +++ b/src/php/types/zval.rs @@ -147,26 +147,26 @@ impl<'a> Zval { /// Attempts to call the argument as a callable with a list of arguments to pass to the function. /// Note that a thrown exception inside the callable is not detectable, therefore you should - /// check if the return value is valid rather than unwrapping. + /// check if the return value is valid rather than unwrapping. Returns a result containing the + /// return value of the function, or an error. /// /// You should not call this function directly, rather through the [`call_user_func`] macro. /// /// # Parameters /// /// * `params` - A list of parameters to call the function with. - /// - /// # Returns - /// - /// * `Some(Zval)` - The result of the function call. - /// * `None` - The zval was not callable or the call failed. - pub fn try_call(&self, params: Vec) -> Option { + pub fn try_call(&self, params: Vec<&dyn IntoZval>) -> Result { let mut retval = Zval::new(); let len = params.len(); + let params = params + .into_iter() + .map(|val| val.as_zval(false)) + .collect::>>()?; let packed = Box::into_raw(params.into_boxed_slice()) as *mut Self; let ptr: *const Self = self; if !self.is_callable() { - return None; + return Err(Error::Callable); } let result = unsafe { @@ -194,9 +194,9 @@ impl<'a> Zval { }; if result < 0 { - None + Err(Error::Callable) } else { - Some(retval) + Ok(retval) } } @@ -266,18 +266,20 @@ impl<'a> Zval { unsafe { zend_is_callable(ptr as *mut Self, 0, std::ptr::null_mut()) } } - /// Sets the value of the zval as a string. + /// Sets the value of the zval as a string. Returns nothing in a result when successful. /// /// # Parameters /// /// * `val` - The value to set the zval as. - pub fn set_string(&mut self, val: S) + /// * `persistent` - Whether the string should persist between requests. + pub fn set_string(&mut self, val: S, persistent: bool) -> Result<()> where S: AsRef, { - let zend_str = ZendString::new(val, false); + let zend_str = ZendString::new(val, persistent)?; self.value.str_ = zend_str.release(); self.u1.type_info = ZvalTypeFlags::StringEx.bits(); + Ok(()) } /// Sets the value of the zval as a binary string. @@ -291,34 +293,19 @@ impl<'a> Zval { self.u1.type_info = ZvalTypeFlags::StringEx.bits(); } - /// Sets the value of the zval as a persistent string. - /// This means that the zend string will persist between - /// request lifetime. + /// Sets the value of the zval as a interned string. Returns nothing in a result when successful. /// /// # Parameters /// /// * `val` - The value to set the zval as. - pub fn set_persistent_string(&mut self, val: S) + pub fn set_interned_string(&mut self, val: S) -> Result<()> where S: AsRef, { - let zend_str = ZendString::new(val, true); - self.value.str_ = zend_str.release(); - self.u1.type_info = ZvalTypeFlags::StringEx.bits(); - } - - /// Sets the value of the zval as a interned string. - /// - /// # Parameters - /// - /// * `val` - The value to set the zval as. - pub fn set_interned_string(&mut self, val: S) - where - S: AsRef, - { - let zend_str = ZendString::new_interned(val); + let zend_str = ZendString::new_interned(val)?; self.value.str_ = zend_str.release(); self.u1.type_info = ZvalTypeFlags::InternedStringEx.bits(); + Ok(()) } /// Sets the value of the zval as a long. @@ -355,6 +342,7 @@ impl<'a> Zval { } /// Sets the value of the zval as null. + /// /// This is the default of a zval. pub fn set_null(&mut self) { self.u1.type_info = ZvalTypeFlags::Null.bits(); @@ -386,12 +374,9 @@ impl<'a> Zval { /// # Parameters /// /// * `val` - The value to set the zval as. - pub fn set_array(&mut self, val: V) - where - V: Into, - { + pub fn set_array(&mut self, val: ZendHashTable) { self.u1.type_info = ZvalTypeFlags::ArrayEx.bits(); - self.value.arr = val.into().into_ptr(); + self.value.arr = val.into_ptr(); } /// Sets the reference count of the Zval. @@ -435,10 +420,24 @@ impl Debug for Zval { } } +/// Provides implementations for converting Rust primitive types into PHP zvals. Alternative to the +/// built-in Rust [`From`] and [`TryFrom`] implementations, allowing the caller to specify whether +/// the Zval contents will persist between requests. +pub trait IntoZval { + /// Converts a Rust primitive type into a Zval. Returns a result containing the Zval if + /// successful. + /// + /// # Parameters + /// + /// * `persistent` - Whether the contents of the Zval will persist between requests. + fn as_zval(&self, persistent: bool) -> Result; +} + macro_rules! try_from_zval { ($type: ty, $fn: ident) => { impl TryFrom<&Zval> for $type { type Error = Error; + fn try_from(value: &Zval) -> Result { match value.$fn() { Some(v) => match <$type>::try_from(v) { @@ -480,6 +479,14 @@ macro_rules! into_zval { zv } } + + impl IntoZval for $type { + fn as_zval(&self, _: bool) -> Result { + let mut zv = Zval::new(); + zv.$fn(*self); + Ok(zv) + } + } }; } @@ -497,6 +504,28 @@ into_zval!(f64, set_double); into_zval!(bool, set_bool); -into_zval!(String, set_string); -into_zval!(&String, set_string); -into_zval!(&str, set_string); +macro_rules! try_into_zval_str { + ($type: ty) => { + impl TryFrom<$type> for Zval { + type Error = Error; + + fn try_from(value: $type) -> Result { + let mut zv = Self::new(); + zv.set_string(value, false)?; + Ok(zv) + } + } + + impl IntoZval for $type { + fn as_zval(&self, persistent: bool) -> Result { + let mut zv = Zval::new(); + zv.set_string(self, persistent)?; + Ok(zv) + } + } + }; +} + +try_into_zval_str!(String); +try_into_zval_str!(&String); +try_into_zval_str!(&str);