Skip to content

Commit

Permalink
Merge pull request #264 from nicelocal/func
Browse files Browse the repository at this point in the history
Add zend_function wrapper, try_call_method zval/object methods
  • Loading branch information
danog authored Oct 10, 2023
2 parents acd3369 + 4f1565f commit 2bf8718
Show file tree
Hide file tree
Showing 20 changed files with 327 additions and 13 deletions.
2 changes: 2 additions & 0 deletions allowed_bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions crates/cli/src/ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() ->
Expand Down
30 changes: 28 additions & 2 deletions docsrs_bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions guide/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
30 changes: 30 additions & 0 deletions guide/src/types/functions.md
Original file line number Diff line number Diff line change
@@ -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() {}
```
19 changes: 19 additions & 0 deletions guide/src/types/object.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Zval> {
self.zval.as_ref().ok_or(Error::Callable)?.try_call(params)
}
Expand Down
4 changes: 3 additions & 1 deletion src/describe/abi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ impl<T> Deref for Vec<T> {

impl<T> Drop for Vec<T> {
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));
};
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/describe/stub.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
};

Expand Down
3 changes: 3 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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:?}")
}
Expand Down
2 changes: 1 addition & 1 deletion src/exception.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")))]
Expand Down
57 changes: 55 additions & 2 deletions src/types/array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -715,7 +768,7 @@ impl<'a> FromZval<'a> for &'a ZendHashTable {
}

///////////////////////////////////////////
//// HashMap
/// HashMap
///////////////////////////////////////////

impl<'a, V> TryFrom<&'a ZendHashTable> for HashMap<String, V>
Expand Down Expand Up @@ -784,7 +837,7 @@ where
}

///////////////////////////////////////////
//// Vec
/// Vec
///////////////////////////////////////////

impl<'a, T> TryFrom<&'a ZendHashTable> for Vec<T>
Expand Down
1 change: 1 addition & 0 deletions src/types/callable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Zval> {
if !self.0.is_callable() {
return Err(Error::Callable);
Expand Down
1 change: 1 addition & 0 deletions src/types/class_object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ impl<T: RegisteredClass> ZendClassObject<T> {
/// # 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)
}
Expand Down
52 changes: 48 additions & 4 deletions src/types/object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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"),
Expand Down Expand Up @@ -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<Zval> {
let mut retval = Zval::new();
let len = params.len();
let params = params
.into_iter()
.map(|val| val.as_zval(false))
.collect::<Result<Vec<_>>>()?;
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.
Expand Down
Loading

0 comments on commit 2bf8718

Please sign in to comment.