diff --git a/allowed_bindings.rs b/allowed_bindings.rs index 7252beaa63..d527ff0d23 100644 --- a/allowed_bindings.rs +++ b/allowed_bindings.rs @@ -55,6 +55,8 @@ bind! { zend_array_destroy, zend_array_dup, zend_call_known_function, + zend_fetch_function_str, + zend_hash_str_find_ptr_lc, zend_ce_argument_count_error, zend_ce_arithmetic_error, zend_ce_compile_error, diff --git a/crates/cli/src/ext.rs b/crates/cli/src/ext.rs index b529a757eb..91aaff527f 100644 --- a/crates/cli/src/ext.rs +++ b/crates/cli/src/ext.rs @@ -4,6 +4,7 @@ use anyhow::{Context, Result}; use ext_php_rs::describe::Description; use libloading::os::unix::{Library, Symbol}; +#[allow(improper_ctypes_definitions)] pub struct Ext { // These need to be here to keep the libraries alive. The extension library needs to be alive // to access the describe function. Missing here is the lifetime on `Symbol<'a, fn() -> diff --git a/docsrs_bindings.rs b/docsrs_bindings.rs index 1ba4175991..351ab9d133 100644 --- a/docsrs_bindings.rs +++ b/docsrs_bindings.rs @@ -105,6 +105,11 @@ pub const CONST_CS: u32 = 0; pub const CONST_PERSISTENT: u32 = 1; pub const CONST_NO_FILE_CACHE: u32 = 2; pub const CONST_DEPRECATED: u32 = 4; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct __sigset_t { + pub __val: [::std::os::raw::c_ulong; 16usize], +} pub type zend_long = i64; pub type zend_ulong = u64; pub type zend_uchar = ::std::os::raw::c_uchar; @@ -405,6 +410,13 @@ extern "C" { extern "C" { pub fn zend_array_destroy(ht: *mut HashTable); } +extern "C" { + pub fn zend_hash_str_find_ptr_lc( + ht: *const HashTable, + str_: *const ::std::os::raw::c_char, + len: usize, + ) -> *mut ::std::os::raw::c_void; +} extern "C" { pub fn gc_possible_root(ref_: *mut zend_refcounted); } @@ -1049,7 +1061,15 @@ pub struct _zend_execute_data { pub run_time_cache: *mut *mut ::std::os::raw::c_void, pub extra_named_params: *mut zend_array, } -pub type sigjmp_buf = [::std::os::raw::c_int; 49usize]; +pub type __jmp_buf = [::std::os::raw::c_long; 8usize]; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct __jmp_buf_tag { + pub __jmpbuf: __jmp_buf, + pub __mask_was_saved: ::std::os::raw::c_int, + pub __saved_mask: __sigset_t, +} +pub type jmp_buf = [__jmp_buf_tag; 1usize]; pub type zend_executor_globals = _zend_executor_globals; extern "C" { pub static mut executor_globals: zend_executor_globals; @@ -1119,7 +1139,7 @@ pub struct _zend_executor_globals { pub symtable_cache_ptr: *mut *mut zend_array, pub symbol_table: zend_array, pub included_files: HashTable, - pub bailout: *mut sigjmp_buf, + pub bailout: *mut jmp_buf, pub error_reporting: ::std::os::raw::c_int, pub exit_status: ::std::os::raw::c_int, pub function_table: *mut HashTable, @@ -1262,6 +1282,12 @@ pub struct _zend_vm_stack { pub end: *mut zval, pub prev: zend_vm_stack, } +extern "C" { + pub fn zend_fetch_function_str( + name: *const ::std::os::raw::c_char, + len: usize, + ) -> *mut zend_function; +} #[repr(C)] #[derive(Copy, Clone)] pub struct _zend_function_entry { diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md index f725964069..cfbc003f01 100644 --- a/guide/src/SUMMARY.md +++ b/guide/src/SUMMARY.md @@ -23,6 +23,7 @@ - [Object](./types/object.md) - [Class Object](./types/class_object.md) - [Closure](./types/closure.md) + - [Functions & methods](./types/functions.md) - [Macros](./macros/index.md) - [Module](./macros/module.md) - [Module Startup Function](./macros/module_startup.md) diff --git a/guide/src/types/functions.md b/guide/src/types/functions.md new file mode 100644 index 0000000000..c8d7b03151 --- /dev/null +++ b/guide/src/types/functions.md @@ -0,0 +1,30 @@ +# Functions & methods + +PHP functions and methods are represented by the `Function` struct. + +You can use the `try_from_function` and `try_from_method` methods to obtain a Function struct corresponding to the passed function or static method name. +It's heavily recommended you reuse returned `Function` objects, to avoid the overhead of looking up the function/method name. + +You may also use the infallible `from_function` and `from_method` variants, for example when working with native PHP functions/classes that are guaranteed to be always available. + +```rust,no_run +# #![cfg_attr(windows, feature(abi_vectorcall))] +# extern crate ext_php_rs; +use ext_php_rs::prelude::*; + +use ext_php_rs::zend::Function; + +#[php_function] +pub fn test_function() -> () { + let var_dump = Function::from_function("var_dump"); + let _ = var_dump.try_call(vec![&"abc"]); +} + +#[php_function] +pub fn test_method() -> () { + let f = Function::from_method("ClassName", "staticMethod"); + let _ = f.try_call(vec![&"abc"]); +} + +# fn main() {} +``` diff --git a/guide/src/types/object.md b/guide/src/types/object.md index c953d99d36..57f9371d28 100644 --- a/guide/src/types/object.md +++ b/guide/src/types/object.md @@ -15,6 +15,25 @@ object. ## Examples +### Calling a method + +```rust,no_run +# #![cfg_attr(windows, feature(abi_vectorcall))] +# extern crate ext_php_rs; +use ext_php_rs::{prelude::*, types::ZendObject}; + +// Take an object reference and also return it. +#[php_function] +pub fn take_obj(obj: &mut ZendObject) -> () { + let _ = obj.try_call_method("hello", vec![&"arg1", &"arg2"]); +} +# #[php_module] +# pub fn get_module(module: ModuleBuilder) -> ModuleBuilder { +# module +# } +# fn main() {} +``` + ### Taking an object reference ```rust,no_run diff --git a/src/args.rs b/src/args.rs index ef3233472b..7cd3f62d4a 100644 --- a/src/args.rs +++ b/src/args.rs @@ -123,6 +123,7 @@ impl<'a> Arg<'a> { /// # Parameters /// /// * `params` - A list of parameters to call the function with. + #[inline(always)] pub fn try_call(&self, params: Vec<&dyn IntoZvalDyn>) -> Result { self.zval.as_ref().ok_or(Error::Callable)?.try_call(params) } diff --git a/src/describe/abi.rs b/src/describe/abi.rs index a4e5885998..a184cdba68 100644 --- a/src/describe/abi.rs +++ b/src/describe/abi.rs @@ -32,7 +32,9 @@ impl Deref for Vec { impl Drop for Vec { fn drop(&mut self) { - unsafe { Box::from_raw(std::ptr::slice_from_raw_parts_mut(self.ptr, self.len)) }; + unsafe { + let _ = Box::from_raw(std::ptr::slice_from_raw_parts_mut(self.ptr, self.len)); + }; } } diff --git a/src/describe/stub.rs b/src/describe/stub.rs index 65c0e76dfd..065b27549a 100644 --- a/src/describe/stub.rs +++ b/src/describe/stub.rs @@ -52,7 +52,7 @@ impl ToStub for Module { // Inserts a value into the entries hashmap. Takes a key and an entry, creating // the internal vector if it doesn't already exist. let mut insert = |ns, entry| { - let bucket = entries.entry(ns).or_insert_with(StdVec::new); + let bucket = entries.entry(ns).or_default(); bucket.push(entry); }; diff --git a/src/error.rs b/src/error.rs index 441912f640..5c4c9ceeda 100644 --- a/src/error.rs +++ b/src/error.rs @@ -57,6 +57,8 @@ pub enum Error { InvalidUtf8, /// Could not call the given function. Callable, + /// An object was expected. + Object, /// An invalid exception type was thrown. InvalidException(ClassFlags), /// Converting integer arguments resulted in an overflow. @@ -89,6 +91,7 @@ impl Display for Error { ), Error::InvalidUtf8 => write!(f, "Invalid Utf8 byte sequence."), Error::Callable => write!(f, "Could not call given function."), + Error::Object => write!(f, "An object was expected."), Error::InvalidException(flags) => { write!(f, "Invalid exception type was thrown: {flags:?}") } diff --git a/src/exception.rs b/src/exception.rs index d1504b46c5..ae694e0cc4 100644 --- a/src/exception.rs +++ b/src/exception.rs @@ -1,6 +1,6 @@ //! Types and functions used for throwing exceptions from Rust to PHP. -use std::ffi::CString; +use std::{ffi::CString, fmt::Debug}; use crate::{ class::RegisteredClass, diff --git a/src/lib.rs b/src/lib.rs index 720a23de98..ba4c9565d3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,6 +35,7 @@ pub mod zend; /// A module typically glob-imported containing the typically required macros /// and imports. pub mod prelude { + pub use crate::builders::ModuleBuilder; #[cfg(any(docs, feature = "closure"))] #[cfg_attr(docs, doc(cfg(feature = "closure")))] diff --git a/src/types/array.rs b/src/types/array.rs index 28bf47a897..c40b8f3993 100644 --- a/src/types/array.rs +++ b/src/types/array.rs @@ -187,6 +187,33 @@ impl ZendHashTable { unsafe { zend_hash_str_find(self, str.as_ptr(), key.len() as _).as_ref() } } + /// Attempts to retrieve a value from the hash table with a string key. + /// + /// # Parameters + /// + /// * `key` - The key to search for in the hash table. + /// + /// # Returns + /// + /// * `Some(&Zval)` - A reference to the zval at the position in the hash + /// table. + /// * `None` - No value at the given position was found. + /// + /// # Example + /// + /// ```no_run + /// use ext_php_rs::types::ZendHashTable; + /// + /// let mut ht = ZendHashTable::new(); + /// + /// ht.insert("test", "hello world"); + /// assert_eq!(ht.get("test").and_then(|zv| zv.str()), Some("hello world")); + /// ``` + pub fn get_mut(&self, key: &'_ str) -> Option<&mut Zval> { + let str = CString::new(key).ok()?; + unsafe { zend_hash_str_find(self, str.as_ptr(), key.len() as _).as_mut() } + } + /// Attempts to retrieve a value from the hash table with an index. /// /// # Parameters @@ -213,6 +240,32 @@ impl ZendHashTable { unsafe { zend_hash_index_find(self, key).as_ref() } } + /// Attempts to retrieve a value from the hash table with an index. + /// + /// # Parameters + /// + /// * `key` - The key to search for in the hash table. + /// + /// # Returns + /// + /// * `Some(&Zval)` - A reference to the zval at the position in the hash + /// table. + /// * `None` - No value at the given position was found. + /// + /// # Example + /// + /// ```no_run + /// use ext_php_rs::types::ZendHashTable; + /// + /// let mut ht = ZendHashTable::new(); + /// + /// ht.push(100); + /// assert_eq!(ht.get_index(0).and_then(|zv| zv.long()), Some(100)); + /// ``` + pub fn get_index_mut(&self, key: u64) -> Option<&mut Zval> { + unsafe { zend_hash_index_find(self, key).as_mut() } + } + /// Attempts to remove a value from the hash table with a string key. /// /// # Parameters @@ -715,7 +768,7 @@ impl<'a> FromZval<'a> for &'a ZendHashTable { } /////////////////////////////////////////// -//// HashMap +/// HashMap /////////////////////////////////////////// impl<'a, V> TryFrom<&'a ZendHashTable> for HashMap @@ -784,7 +837,7 @@ where } /////////////////////////////////////////// -//// Vec +/// Vec /////////////////////////////////////////// impl<'a, T> TryFrom<&'a ZendHashTable> for Vec diff --git a/src/types/callable.rs b/src/types/callable.rs index c1e446e6ea..c22474b75a 100644 --- a/src/types/callable.rs +++ b/src/types/callable.rs @@ -99,6 +99,7 @@ impl<'a> ZendCallable<'a> { /// let result = strpos.try_call(vec![&"hello", &"e"]).unwrap(); /// assert_eq!(result.long(), Some(1)); /// ``` + #[inline(always)] pub fn try_call(&self, params: Vec<&dyn IntoZvalDyn>) -> Result { if !self.0.is_callable() { return Err(Error::Callable); diff --git a/src/types/class_object.rs b/src/types/class_object.rs index f926c02b7d..e9b116529b 100644 --- a/src/types/class_object.rs +++ b/src/types/class_object.rs @@ -155,6 +155,7 @@ impl ZendClassObject { /// # Parameters /// /// * `obj` - The zend object to get the [`ZendClassObject`] for. + #[allow(clippy::needless_pass_by_ref_mut)] pub fn from_zend_obj_mut(std: &mut zend_object) -> Option<&mut Self> { Self::_from_zend_obj(std) } diff --git a/src/types/object.rs b/src/types/object.rs index 606c4838ed..ca8e526ebe 100644 --- a/src/types/object.rs +++ b/src/types/object.rs @@ -6,11 +6,12 @@ use std::{convert::TryInto, fmt::Debug, ops::DerefMut}; use crate::{ boxed::{ZBox, ZBoxable}, class::RegisteredClass, - convert::{FromZendObject, FromZval, FromZvalMut, IntoZval}, + convert::{FromZendObject, FromZval, FromZvalMut, IntoZval, IntoZvalDyn}, error::{Error, Result}, ffi::{ - ext_php_rs_zend_object_release, zend_call_known_function, zend_object, zend_objects_new, - HashTable, ZEND_ISEMPTY, ZEND_PROPERTY_EXISTS, ZEND_PROPERTY_ISSET, + ext_php_rs_zend_object_release, object_properties_init, zend_call_known_function, + zend_function, zend_hash_str_find_ptr_lc, zend_object, zend_objects_new, HashTable, + ZEND_ISEMPTY, ZEND_PROPERTY_EXISTS, ZEND_PROPERTY_ISSET, }, flags::DataType, rc::PhpRc, @@ -41,7 +42,18 @@ impl ZendObject { // SAFETY: Using emalloc to allocate memory inside Zend arena. Casting `ce` to // `*mut` is valid as the function will not mutate `ce`. unsafe { - let ptr = zend_objects_new(ce as *const _ as *mut _); + let ptr = match ce.__bindgen_anon_2.create_object { + None => { + let ptr = zend_objects_new(ce as *const _ as *mut _); + if ptr.is_null() { + panic!("Failed to allocate memory for Zend object") + } + object_properties_init(ptr, ce as *const _ as *mut _); + ptr + } + Some(v) => v(ce as *const _ as *mut _), + }; + ZBox::from_raw( ptr.as_mut() .expect("Failed to allocate memory for Zend object"), @@ -121,6 +133,38 @@ impl ZendObject { (self.ce as *const ClassEntry).eq(&(T::get_metadata().ce() as *const _)) } + #[inline(always)] + pub fn try_call_method(&self, name: &str, params: Vec<&dyn IntoZvalDyn>) -> Result { + let mut retval = Zval::new(); + let len = params.len(); + let params = params + .into_iter() + .map(|val| val.as_zval(false)) + .collect::>>()?; + let packed = params.into_boxed_slice(); + + unsafe { + let res = zend_hash_str_find_ptr_lc( + &(*self.ce).function_table, + name.as_ptr() as *const i8, + name.len(), + ) as *mut zend_function; + if res.is_null() { + return Err(Error::Callable); + } + zend_call_known_function( + res, + self as *const _ as *mut _, + self.ce, + &mut retval, + len as _, + packed.as_ptr() as *mut _, + std::ptr::null_mut(), + ) + }; + + Ok(retval) + } /// Attempts to read a property from the Object. Returns a result containing /// the value of the property if it exists and can be read, and an /// [`Error`] otherwise. diff --git a/src/types/zval.rs b/src/types/zval.rs index d45cdad79e..94b62aa985 100644 --- a/src/types/zval.rs +++ b/src/types/zval.rs @@ -206,6 +206,13 @@ impl Zval { } } + #[inline(always)] + pub fn try_call_method(&self, name: &str, params: Vec<&dyn IntoZvalDyn>) -> Result { + self.object() + .ok_or(Error::Object)? + .try_call_method(name, params) + } + /// Returns the value of the zval if it is a reference. pub fn reference(&self) -> Option<&Zval> { if self.is_reference() { @@ -257,6 +264,7 @@ impl Zval { /// # Parameters /// /// * `params` - A list of parameters to call the function with. + #[inline(always)] pub fn try_call(&self, params: Vec<&dyn IntoZvalDyn>) -> Result { self.callable().ok_or(Error::Callable)?.try_call(params) } diff --git a/src/zend/class.rs b/src/zend/class.rs index 6e7f7961d8..4c3c23dcd8 100644 --- a/src/zend/class.rs +++ b/src/zend/class.rs @@ -1,6 +1,12 @@ //! Builder and objects for creating classes in the PHP world. -use crate::{ffi::zend_class_entry, flags::ClassFlags, types::ZendStr, zend::ExecutorGlobals}; +use crate::{ + boxed::ZBox, + ffi::zend_class_entry, + flags::ClassFlags, + types::{ZendObject, ZendStr}, + zend::ExecutorGlobals, +}; use std::{convert::TryInto, fmt::Debug, ops::DerefMut}; /// A PHP class entry. @@ -22,6 +28,17 @@ impl ClassEntry { } } + /// Creates a new [`ZendObject`], returned inside an [`ZBox`] + /// wrapper. + /// + /// # Panics + /// + /// Panics when allocating memory for the new object fails. + #[allow(clippy::new_ret_no_self)] + pub fn new(&self) -> ZBox { + ZendObject::new(self) + } + /// Returns the class flags. pub fn flags(&self) -> ClassFlags { ClassFlags::from_bits_truncate(self.ce_flags) diff --git a/src/zend/function.rs b/src/zend/function.rs index ba889d775f..97c9ec0d67 100644 --- a/src/zend/function.rs +++ b/src/zend/function.rs @@ -2,7 +2,17 @@ use std::{fmt::Debug, os::raw::c_char, ptr}; -use crate::ffi::zend_function_entry; +use crate::{ + convert::IntoZvalDyn, + error::Result, + ffi::{ + zend_call_known_function, zend_fetch_function_str, zend_function, zend_function_entry, + zend_hash_str_find_ptr_lc, + }, + types::Zval, +}; + +use super::ClassEntry; /// A Zend function entry. pub type FunctionEntry = zend_function_entry; @@ -36,3 +46,96 @@ impl FunctionEntry { Box::into_raw(Box::new(self)) } } + +pub type Function = zend_function; + +impl Function { + pub fn try_from_function(name: &str) -> Option { + unsafe { + let res = zend_fetch_function_str(name.as_ptr() as *const i8, name.len()); + if res.is_null() { + return None; + } + Some(*res) + } + } + pub fn try_from_method(class: &str, name: &str) -> Option { + match ClassEntry::try_find(class) { + None => None, + Some(ce) => unsafe { + let res = zend_hash_str_find_ptr_lc( + &ce.function_table, + name.as_ptr() as *const i8, + name.len(), + ) as *mut zend_function; + if res.is_null() { + return None; + } + Some(*res) + }, + } + } + + pub fn from_function(name: &str) -> Self { + match Self::try_from_function(name) { + Some(v) => v, + None => panic!("Expected function `{}` to exist!", name), + } + } + + pub fn from_method(class: &str, name: &str) -> Self { + match Self::try_from_method(class, name) { + Some(v) => v, + None => panic!("Expected method `{}::{}` to exist!", class, name), + } + } + + /// Attempts to call the callable with a list of arguments to pass to the + /// function. + /// + /// 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 + /// + /// Returns the result wrapped in [`Ok`] upon success. If calling the + /// callable fails, or an exception is thrown, an [`Err`] is returned. + /// + /// # Example + /// + /// ```no_run + /// use ext_php_rs::types::ZendCallable; + /// + /// let strpos = ZendCallable::try_from_name("strpos").unwrap(); + /// let result = strpos.try_call(vec![&"hello", &"e"]).unwrap(); + /// assert_eq!(result.long(), Some(1)); + /// ``` + #[inline(always)] + pub fn try_call(&self, params: Vec<&dyn IntoZvalDyn>) -> Result { + let mut retval = Zval::new(); + let len = params.len(); + let params = params + .into_iter() + .map(|val| val.as_zval(false)) + .collect::>>()?; + let packed = params.into_boxed_slice(); + + unsafe { + zend_call_known_function( + self as *const _ as *mut _, + std::ptr::null_mut(), + std::ptr::null_mut(), + &mut retval, + len as _, + packed.as_ptr() as *mut _, + std::ptr::null_mut(), + ) + }; + + Ok(retval) + } +} diff --git a/src/zend/mod.rs b/src/zend/mod.rs index 74547f044a..872f65b91d 100644 --- a/src/zend/mod.rs +++ b/src/zend/mod.rs @@ -15,6 +15,7 @@ use std::ffi::CString; pub use _type::ZendType; pub use class::ClassEntry; pub use ex::ExecuteData; +pub use function::Function; pub use function::FunctionEntry; pub use globals::ExecutorGlobals; pub use handlers::ZendObjectHandlers;