From d5e05f04a10bed4777ce3328ead79ad680aa1b9b Mon Sep 17 00:00:00 2001 From: Joe Hoyle Date: Tue, 20 Dec 2022 14:19:51 +0000 Subject: [PATCH 001/100] Add support for throwing Exception objects Currently we only support throwing exception class entries, i.e. stateless exceptions. Ideally we also want to support throwing a ZVal which has a ClassEntry that extends the Exception PHP class. This can be used to throw stateful exceptions which is not uncommon. This PR is missing one piece: `throw_object` will currently `drop` the Zval when the function is completed, causing reference / null pointer errors. It seems `ZVal::Drop` doesn't actually free the zval currently, it just sets the type to NULL (which also breaks things.) Discussed briefly in https://discord.com/channels/115233111977099271/1025314959179120714 on how best to solve this, but I'm not totally clear still! Ideally I think we want `throw_object` to own the `zval` but not free it once the function returns. --- allowed_bindings.rs | 1 + src/exception.rs | 60 ++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/allowed_bindings.rs b/allowed_bindings.rs index f417e95..972e151 100644 --- a/allowed_bindings.rs +++ b/allowed_bindings.rs @@ -81,6 +81,7 @@ bind! { zend_string, zend_string_init_interned, zend_throw_exception_ex, + zend_throw_exception_object, zend_type, zend_value, zend_wrong_parameters_count_error, diff --git a/src/exception.rs b/src/exception.rs index d1504b4..e8a48fe 100644 --- a/src/exception.rs +++ b/src/exception.rs @@ -6,8 +6,9 @@ use crate::{ class::RegisteredClass, error::{Error, Result}, ffi::zend_throw_exception_ex, + ffi::zend_throw_exception_object, flags::ClassFlags, - zend::{ce, ClassEntry}, + zend::{ce, ClassEntry}, types::Zval, }; /// Result type with the error variant as a [`PhpException`]. @@ -25,6 +26,7 @@ pub struct PhpException { message: String, code: i32, ex: &'static ClassEntry, + object: Option, } impl PhpException { @@ -36,7 +38,7 @@ impl PhpException { /// * `code` - Integer code to go inside the exception. /// * `ex` - Exception type to throw. pub fn new(message: String, code: i32, ex: &'static ClassEntry) -> Self { - Self { message, code, ex } + Self { message, code, ex, object: None, } } /// Creates a new default exception instance, using the default PHP @@ -59,10 +61,27 @@ impl PhpException { Self::new(message, 0, T::get_metadata().ce()) } + /// Set the Zval object for the exception. + /// + /// Exceptions can be based of instantiated Zval objects when you are throwing a custom exception with + /// stateful properties. + /// + /// # Parameters + /// + /// * `object` - The Zval object. + pub fn set_object(&mut self, object: Option) { + self.object = object; + } + /// Throws the exception, returning nothing inside a result if successful /// and an error otherwise. pub fn throw(self) -> Result<()> { - throw_with_code(self.ex, self.code, &self.message) + match self.object { + Some(object) => { + throw_object(object) + }, + None => throw_with_code(self.ex, self.code, &self.message), + } } } @@ -146,3 +165,38 @@ pub fn throw_with_code(ex: &ClassEntry, code: i32, message: &str) -> Result<()> }; Ok(()) } + + +/// Throws an exception object. +/// +/// Returns a result containing nothing if the exception was successfully +/// thrown. +/// +/// # Parameters +/// +/// * `object` - The zval of type object +/// +/// # Examples +/// +/// ```no_run +/// use ext_php_rs::{zend::{ce, ClassEntry}, exception::throw_with_code}; +/// +/// #[php_class] +/// #[extends(ext_php_rs::zend::ce::exception())] +/// pub struct JsException { +/// #[prop(flags = ext_php_rs::flags::PropertyFlags::Public)] +/// message: String, +/// #[prop(flags = ext_php_rs::flags::PropertyFlags::Public)] +/// code: i32, +/// #[prop(flags = ext_php_rs::flags::PropertyFlags::Public)] +/// file: String, +/// } +/// let error = JsException { message: "A JS error occurred.", code: 100, file: "index.js" }; +/// throw_object( error.into_val(true) ); +/// ``` +pub fn throw_object(zval: Zval) -> Result<()> { + unsafe { + zend_throw_exception_object((&zval as *const _) as *mut _); + }; + Ok(()) +} From 94fb7c78a1326cf2374669e52b8dafc13adda778 Mon Sep 17 00:00:00 2001 From: Joe Hoyle Date: Tue, 20 Dec 2022 16:00:45 +0000 Subject: [PATCH 002/100] Fix docs example --- src/exception.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/exception.rs b/src/exception.rs index e8a48fe..f167c8d 100644 --- a/src/exception.rs +++ b/src/exception.rs @@ -179,7 +179,9 @@ pub fn throw_with_code(ex: &ClassEntry, code: i32, message: &str) -> Result<()> /// # Examples /// /// ```no_run -/// use ext_php_rs::{zend::{ce, ClassEntry}, exception::throw_with_code}; +/// use ext_php_rs::prelude::*; +/// use ext_php_rs::exception::throw_object; +/// use crate::ext_php_rs::convert::IntoZval; /// /// #[php_class] /// #[extends(ext_php_rs::zend::ce::exception())] @@ -191,8 +193,14 @@ pub fn throw_with_code(ex: &ClassEntry, code: i32, message: &str) -> Result<()> /// #[prop(flags = ext_php_rs::flags::PropertyFlags::Public)] /// file: String, /// } -/// let error = JsException { message: "A JS error occurred.", code: 100, file: "index.js" }; -/// throw_object( error.into_val(true) ); +/// +/// #[php_module] +/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder { +/// module +/// } +/// +/// let error = JsException { message: "A JS error occurred.".to_string(), code: 100, file: "index.js".to_string() }; +/// throw_object( error.into_zval(true).unwrap() ); /// ``` pub fn throw_object(zval: Zval) -> Result<()> { unsafe { From 8d6850afd07342fd9921f3ffab1e3dd8d3e3a54f Mon Sep 17 00:00:00 2001 From: Joe Hoyle Date: Tue, 20 Dec 2022 16:04:41 +0000 Subject: [PATCH 003/100] Format --- src/exception.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/exception.rs b/src/exception.rs index f167c8d..e1f4c63 100644 --- a/src/exception.rs +++ b/src/exception.rs @@ -8,7 +8,8 @@ use crate::{ ffi::zend_throw_exception_ex, ffi::zend_throw_exception_object, flags::ClassFlags, - zend::{ce, ClassEntry}, types::Zval, + types::Zval, + zend::{ce, ClassEntry}, }; /// Result type with the error variant as a [`PhpException`]. @@ -38,7 +39,12 @@ impl PhpException { /// * `code` - Integer code to go inside the exception. /// * `ex` - Exception type to throw. pub fn new(message: String, code: i32, ex: &'static ClassEntry) -> Self { - Self { message, code, ex, object: None, } + Self { + message, + code, + ex, + object: None, + } } /// Creates a new default exception instance, using the default PHP @@ -77,9 +83,7 @@ impl PhpException { /// and an error otherwise. pub fn throw(self) -> Result<()> { match self.object { - Some(object) => { - throw_object(object) - }, + Some(object) => throw_object(object), None => throw_with_code(self.ex, self.code, &self.message), } } @@ -166,7 +170,6 @@ pub fn throw_with_code(ex: &ClassEntry, code: i32, message: &str) -> Result<()> Ok(()) } - /// Throws an exception object. /// /// Returns a result containing nothing if the exception was successfully From 4fabeb2d5ee7f4830c6ac4e242e5bbb64dbda8ee Mon Sep 17 00:00:00 2001 From: Pierre Tondereau Date: Tue, 20 Dec 2022 17:36:16 +0100 Subject: [PATCH 004/100] Update doc bindings. --- docsrs_bindings.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docsrs_bindings.rs b/docsrs_bindings.rs index a2ecbc7..9714e61 100644 --- a/docsrs_bindings.rs +++ b/docsrs_bindings.rs @@ -1442,6 +1442,9 @@ extern "C" { ... ) -> *mut zend_object; } +extern "C" { + pub fn zend_throw_exception_object(exception: *mut zval); +} extern "C" { pub fn zend_do_implement_interface(ce: *mut zend_class_entry, iface: *mut zend_class_entry); } From cc50781903388960fbea2390c81a8c67e70b9932 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Tue, 16 May 2023 21:32:23 +0200 Subject: [PATCH 005/100] Add async support --- Cargo.toml | 3 +++ crates/macros/src/method.rs | 38 ++++++++++++++++++++++++++++++++++++- src/builders/module.rs | 2 +- src/exception.rs | 2 +- src/macros.rs | 27 ++++++++++++++++++++++++++ src/zend/class.rs | 2 +- src/zend/globals.rs | 8 ++++++++ src/zend/mod.rs | 1 + 8 files changed, 79 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2219cba..dd22923 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,9 @@ cfg-if = "1.0" once_cell = "1.17" anyhow = { version = "1", optional = true } ext-php-rs-derive = { version = "=0.10.0", path = "./crates/macros" } +tokio = { version = "1", features = ["full"] } +lazy_static = "1.4.0" +libc = "*" [dev-dependencies] skeptic = "0.13" diff --git a/crates/macros/src/method.rs b/crates/macros/src/method.rs index 7eb8a34..fcd10d6 100644 --- a/crates/macros/src/method.rs +++ b/crates/macros/src/method.rs @@ -1,7 +1,7 @@ use anyhow::{anyhow, bail, Result}; use quote::ToTokens; use std::collections::HashMap; -use syn::ReturnType; +use syn::{ReturnType, parse_quote}; use crate::helpers::get_docs; use crate::{ @@ -175,6 +175,42 @@ pub fn parser( } } } else { + let mut input = input.clone(); + if input.sig.asyncness.is_some() { + input.sig.asyncness = None; + let stmts = input.block; + input.block = parse_quote! {{ + let future = async #stmts ; + + let future = GLOBAL_CONNECTION.with_borrow_mut(|c| -> ::ext_php_rs::prelude::PhpResult<_> { + let c = c.as_mut().unwrap(); + let f = ::ext_php_rs::get_current_suspension!(); + let idx = c.fibers.len() as u64; + let mut callable = Zval::new(); + callable.set_array(vec![f, "resume".into_zval(false)?])?; + c.fibers.insert_at_index(idx, callable).unwrap(); + + let sender = c.sender.clone(); + let mut notifier = c.notify_sender.try_clone() + .map_err(|err| format!("Could not clone fd: {}", err))?; + + Ok(::ext_php_rs::zend::RUNTIME.spawn(async move { + let res = future.await; + sender.send(idx).unwrap(); + notifier.write_all(&[0]).unwrap(); + res + })) + })?; + + let mut callable = Zval::new(); + callable.set_array(vec![::ext_php_rs::get_current_suspension!(), "suspend".into_zval(false)?])?; + call_user_func!(callable)?; + + return Ok(::ext_php_rs::zend::RUNTIME + .block_on(future) + .map_err(|err| format!("Error while joining: {}", err))?); + }}; + } let this = match method_type { MethodType::Receiver { .. } => quote! { this. }, MethodType::ReceiverClassObject | MethodType::Static => quote! { Self:: }, diff --git a/src/builders/module.rs b/src/builders/module.rs index 6eaaf51..ebeac92 100644 --- a/src/builders/module.rs +++ b/src/builders/module.rs @@ -5,7 +5,7 @@ use crate::{ PHP_DEBUG, PHP_ZTS, }; -use std::{ffi::CString, mem, ptr}; +use std::{ffi::{CString, c_void}, mem, ptr}; /// Builds a Zend module extension to be registered with PHP. Must be called /// from within an external function called `get_module`, returning a mutable diff --git a/src/exception.rs b/src/exception.rs index d1504b4..ae694e0 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/macros.rs b/src/macros.rs index 47f255b..b2eee09 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -72,6 +72,31 @@ macro_rules! call_user_func { }; } +#[macro_export] +macro_rules! call_static_method { + ($clazz: expr, $fn: expr) => {{ + let mut callable = ext_php_rs::types::Zval::new(); + callable + .set_array(vec![$clazz, $fn]) + .unwrap(); + call_user_func!(callable) + }}; + + ($clazz: expr, $fn: expr, $($param: expr),*) => {{ + let mut callable = ext_php_rs::types::Zval::new(); + callable + .set_array(vec![$clazz, $fn]) + .unwrap(); + call_user_func!(callable, $(&$param),*) + }}; +} + +#[macro_export] +macro_rules! get_current_suspension { + () => { ext_php_rs::call_static_method!("\\Revolt\\EventLoop", "getSuspension").unwrap() } +} + + /// Parses a given list of arguments using the [`ArgParser`] class. /// /// # Examples @@ -410,3 +435,5 @@ macro_rules! php_println { pub(crate) use into_zval; pub(crate) use try_from_zval; + +use crate::types::Zval; diff --git a/src/zend/class.rs b/src/zend/class.rs index dc06a32..0a9610f 100644 --- a/src/zend/class.rs +++ b/src/zend/class.rs @@ -1,7 +1,7 @@ //! Builder and objects for creating classes in the PHP world. use crate::{ffi::zend_class_entry, flags::ClassFlags, types::ZendStr, zend::ExecutorGlobals}; -use std::{convert::TryInto, fmt::Debug, ops::DerefMut}; +use std::{convert::TryInto, fmt::Debug, ops::{DerefMut, Deref}}; /// A PHP class entry. /// diff --git a/src/zend/globals.rs b/src/zend/globals.rs index 7ba76c4..1f4a3a3 100644 --- a/src/zend/globals.rs +++ b/src/zend/globals.rs @@ -1,8 +1,12 @@ //! Types related to the PHP executor globals. +use std::any::Any; +use std::ffi::c_void; use std::ops::{Deref, DerefMut}; +use lazy_static::lazy_static; use parking_lot::{const_rwlock, RwLock, RwLockReadGuard, RwLockWriteGuard}; +use tokio::runtime::Runtime; use crate::boxed::ZBox; use crate::ffi::{_zend_executor_globals, ext_php_rs_executor_globals}; @@ -67,6 +71,10 @@ impl ExecutorGlobals { } } +lazy_static! { + pub static ref RUNTIME: Runtime = Runtime::new().expect("Could not allocate runtime"); +} + /// Executor globals rwlock. /// /// PHP provides no indication if the executor globals are being accessed so diff --git a/src/zend/mod.rs b/src/zend/mod.rs index 74547f0..54b4c2d 100644 --- a/src/zend/mod.rs +++ b/src/zend/mod.rs @@ -17,6 +17,7 @@ pub use class::ClassEntry; pub use ex::ExecuteData; pub use function::FunctionEntry; pub use globals::ExecutorGlobals; +pub use globals::RUNTIME; pub use handlers::ZendObjectHandlers; pub use module::ModuleEntry; From 10d3c50181657586600afdcbd0b3cf1eb144a44e Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Fri, 19 May 2023 23:16:12 +0200 Subject: [PATCH 006/100] Update --- crates/macros/src/method.rs | 24 +++++++++++------------- src/builders/module.rs | 2 +- src/macros.rs | 2 -- src/zend/class.rs | 2 +- src/zend/globals.rs | 2 -- 5 files changed, 13 insertions(+), 19 deletions(-) diff --git a/crates/macros/src/method.rs b/crates/macros/src/method.rs index fcd10d6..82293c2 100644 --- a/crates/macros/src/method.rs +++ b/crates/macros/src/method.rs @@ -180,35 +180,33 @@ pub fn parser( input.sig.asyncness = None; let stmts = input.block; input.block = parse_quote! {{ - let future = async #stmts ; + let future = async #stmts; - let future = GLOBAL_CONNECTION.with_borrow_mut(|c| -> ::ext_php_rs::prelude::PhpResult<_> { + let future = GLOBAL_CONNECTION.with_borrow_mut(|c| { let c = c.as_mut().unwrap(); let f = ::ext_php_rs::get_current_suspension!(); let idx = c.fibers.len() as u64; let mut callable = Zval::new(); - callable.set_array(vec![f, "resume".into_zval(false)?])?; + callable.set_array(vec![f, "resume".into_zval(false).unwrap()]).unwrap(); c.fibers.insert_at_index(idx, callable).unwrap(); let sender = c.sender.clone(); - let mut notifier = c.notify_sender.try_clone() - .map_err(|err| format!("Could not clone fd: {}", err))?; + let mut notifier = c.notify_sender.try_clone().unwrap(); - Ok(::ext_php_rs::zend::RUNTIME.spawn(async move { + ::ext_php_rs::zend::RUNTIME.spawn(async move { let res = future.await; sender.send(idx).unwrap(); notifier.write_all(&[0]).unwrap(); res - })) - })?; + }) + }); let mut callable = Zval::new(); - callable.set_array(vec![::ext_php_rs::get_current_suspension!(), "suspend".into_zval(false)?])?; - call_user_func!(callable)?; + callable.set_array(vec![::ext_php_rs::get_current_suspension!(), "suspend".into_zval(false).unwrap()]).unwrap(); + call_user_func!(callable).unwrap(); - return Ok(::ext_php_rs::zend::RUNTIME - .block_on(future) - .map_err(|err| format!("Error while joining: {}", err))?); + return ::ext_php_rs::zend::RUNTIME + .block_on(future).unwrap(); }}; } let this = match method_type { diff --git a/src/builders/module.rs b/src/builders/module.rs index ebeac92..6eaaf51 100644 --- a/src/builders/module.rs +++ b/src/builders/module.rs @@ -5,7 +5,7 @@ use crate::{ PHP_DEBUG, PHP_ZTS, }; -use std::{ffi::{CString, c_void}, mem, ptr}; +use std::{ffi::CString, mem, ptr}; /// Builds a Zend module extension to be registered with PHP. Must be called /// from within an external function called `get_module`, returning a mutable diff --git a/src/macros.rs b/src/macros.rs index b2eee09..99839f8 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -435,5 +435,3 @@ macro_rules! php_println { pub(crate) use into_zval; pub(crate) use try_from_zval; - -use crate::types::Zval; diff --git a/src/zend/class.rs b/src/zend/class.rs index 0a9610f..dc06a32 100644 --- a/src/zend/class.rs +++ b/src/zend/class.rs @@ -1,7 +1,7 @@ //! Builder and objects for creating classes in the PHP world. use crate::{ffi::zend_class_entry, flags::ClassFlags, types::ZendStr, zend::ExecutorGlobals}; -use std::{convert::TryInto, fmt::Debug, ops::{DerefMut, Deref}}; +use std::{convert::TryInto, fmt::Debug, ops::DerefMut}; /// A PHP class entry. /// diff --git a/src/zend/globals.rs b/src/zend/globals.rs index 1f4a3a3..967e3f6 100644 --- a/src/zend/globals.rs +++ b/src/zend/globals.rs @@ -1,7 +1,5 @@ //! Types related to the PHP executor globals. -use std::any::Any; -use std::ffi::c_void; use std::ops::{Deref, DerefMut}; use lazy_static::lazy_static; From 2088fadf381d26dbbcfea49880b35c40f9aa8236 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Sun, 21 May 2023 17:10:33 +0200 Subject: [PATCH 007/100] A small hack to workaround known good lifetimes --- crates/macros/src/method.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/crates/macros/src/method.rs b/crates/macros/src/method.rs index 82293c2..8b0d255 100644 --- a/crates/macros/src/method.rs +++ b/crates/macros/src/method.rs @@ -179,10 +179,15 @@ pub fn parser( if input.sig.asyncness.is_some() { input.sig.asyncness = None; let stmts = input.block; + let this = match method_type { + MethodType::Receiver { .. } => quote! { let this = unsafe { std::mem::transmute::<&Self, &'static Self>(self) }; }, + MethodType::ReceiverClassObject | MethodType::Static => quote! { }, + }; input.block = parse_quote! {{ - let future = async #stmts; + #this + let future = async move #stmts; - let future = GLOBAL_CONNECTION.with_borrow_mut(|c| { + let future = GLOBAL_CONNECTION.with_borrow_mut(move |c| { let c = c.as_mut().unwrap(); let f = ::ext_php_rs::get_current_suspension!(); let idx = c.fibers.len() as u64; From 3fb905afdb5d33f5b6e80c134bec83a50e53f7e6 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Mon, 19 Jun 2023 11:39:50 +0200 Subject: [PATCH 008/100] Fix borrowing of parameters in async functions --- crates/macros/src/method.rs | 34 ++++++++++++++++++++++++++---- src/zend/globals.rs | 42 +++++++++++++++++++++++++++++++++++++ src/zend/mod.rs | 2 ++ 3 files changed, 74 insertions(+), 4 deletions(-) diff --git a/crates/macros/src/method.rs b/crates/macros/src/method.rs index 8b0d255..6a0c308 100644 --- a/crates/macros/src/method.rs +++ b/crates/macros/src/method.rs @@ -11,6 +11,7 @@ use crate::{ use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; use syn::{punctuated::Punctuated, FnArg, ImplItemMethod, Lit, Pat, Token, Type}; +use quote::TokenStreamExt; #[derive(Debug, Clone)] pub enum Arg { @@ -136,8 +137,9 @@ pub fn parser( } else { quote! { return; } }; + let mut hack_tokens = quote! {}; let internal_ident = Ident::new(&format!("_internal_php_{ident}"), Span::call_site()); - let args = build_args(struct_ty, &mut input.sig.inputs, &defaults)?; + let args = build_args(struct_ty, &mut input.sig.inputs, &defaults, &mut hack_tokens)?; let optional = function::find_optional_parameter( args.iter().filter_map(|arg| match arg { Arg::Typed(arg) => Some(arg), @@ -180,11 +182,16 @@ pub fn parser( input.sig.asyncness = None; let stmts = input.block; let this = match method_type { - MethodType::Receiver { .. } => quote! { let this = unsafe { std::mem::transmute::<&Self, &'static Self>(self) }; }, + MethodType::Receiver { mutable } => if mutable { + quote! { let this = unsafe { std::mem::transmute::<&mut Self, &'static mut Self>(self) }; } + } else { + quote! { let this = unsafe { std::mem::transmute::<&Self, &'static Self>(self) }; } + }, MethodType::ReceiverClassObject | MethodType::Static => quote! { }, }; input.block = parse_quote! {{ #this + #hack_tokens let future = async move #stmts; let future = GLOBAL_CONNECTION.with_borrow_mut(move |c| { @@ -340,6 +347,7 @@ fn build_args( struct_ty: &Type, inputs: &mut Punctuated, defaults: &HashMap, + hack_tokens: &mut TokenStream ) -> Result> { inputs .iter_mut() @@ -367,10 +375,28 @@ fn build_args( if this { Ok(Arg::Receiver(MethodType::ReceiverClassObject)) } else { - let name = match &*ty.pat { - Pat::Ident(pat) => pat.ident.to_string(), + let param = match &*ty.pat { + Pat::Ident(pat) => &pat.ident, _ => bail!("Invalid parameter type."), }; + let name = param.to_string(); + + if let Type::Reference(t) = &*ty.ty { + if t.mutability.is_none() { + if let Type::Path(t) = &*t.elem { + if t.path.is_ident("str") { + hack_tokens.append_all( + quote! { let #param = unsafe { ::core::mem::transmute::<&str, &'static str>(#param) }; } + ); + } else { + hack_tokens.append_all( + quote! { let #param = unsafe { ::ext_php_rs::zend::borrow_unchecked(#param) }; } + ); + } + } + } + }; + let default = defaults.get(&name); let mut ty = ty.ty.clone(); replace_self(struct_ty, &mut ty); diff --git a/src/zend/globals.rs b/src/zend/globals.rs index 967e3f6..a8dec77 100644 --- a/src/zend/globals.rs +++ b/src/zend/globals.rs @@ -116,3 +116,45 @@ impl DerefMut for GlobalWriteGuard { self.globals } } + + + +#[inline(always)] +pub unsafe fn borrow_unchecked< + 'original, + 'unbounded, + Ref: BorrowUnchecked<'original, 'unbounded>, +>( + reference: Ref, +) -> Ref::Unbounded { + unsafe { BorrowUnchecked::borrow_unchecked(reference) } +} + +#[doc(hidden)] +pub unsafe trait BorrowUnchecked<'original, 'unbounded> { + type Unbounded; + + unsafe fn borrow_unchecked(self) -> Self::Unbounded; +} + +unsafe impl<'original, 'unbounded, T: 'unbounded> BorrowUnchecked<'original, 'unbounded> + for &'original T +{ + type Unbounded = &'unbounded T; + + #[inline(always)] + unsafe fn borrow_unchecked(self) -> Self::Unbounded { + unsafe { ::core::mem::transmute(self) } + } +} + +unsafe impl<'original, 'unbounded, T: 'unbounded> BorrowUnchecked<'original, 'unbounded> + for &'original mut T +{ + type Unbounded = &'unbounded mut T; + + #[inline(always)] + unsafe fn borrow_unchecked(self) -> Self::Unbounded { + unsafe { ::core::mem::transmute(self) } + } +} diff --git a/src/zend/mod.rs b/src/zend/mod.rs index 54b4c2d..5b9e622 100644 --- a/src/zend/mod.rs +++ b/src/zend/mod.rs @@ -18,6 +18,8 @@ pub use ex::ExecuteData; pub use function::FunctionEntry; pub use globals::ExecutorGlobals; pub use globals::RUNTIME; +pub use globals::borrow_unchecked; +pub use globals::BorrowUnchecked; pub use handlers::ZendObjectHandlers; pub use module::ModuleEntry; From 07ce8299c58a64d9d09f0d5b8e39b979fbd1221b Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Mon, 19 Jun 2023 13:07:55 +0200 Subject: [PATCH 009/100] Fix option args --- crates/macros/src/method.rs | 45 ++++++++++++++++++++++++++----------- src/zend/globals.rs | 12 ++++++++++ 2 files changed, 44 insertions(+), 13 deletions(-) diff --git a/crates/macros/src/method.rs b/crates/macros/src/method.rs index 6a0c308..381836d 100644 --- a/crates/macros/src/method.rs +++ b/crates/macros/src/method.rs @@ -1,7 +1,7 @@ use anyhow::{anyhow, bail, Result}; use quote::ToTokens; use std::collections::HashMap; -use syn::{ReturnType, parse_quote}; +use syn::{ReturnType, parse_quote, PathArguments, GenericArgument}; use crate::helpers::get_docs; use crate::{ @@ -380,22 +380,41 @@ fn build_args( _ => bail!("Invalid parameter type."), }; let name = param.to_string(); + + let mut ty_inner = &*ty.ty; + let mut is_option = false; - if let Type::Reference(t) = &*ty.ty { - if t.mutability.is_none() { - if let Type::Path(t) = &*t.elem { - if t.path.is_ident("str") { - hack_tokens.append_all( - quote! { let #param = unsafe { ::core::mem::transmute::<&str, &'static str>(#param) }; } - ); - } else { - hack_tokens.append_all( - quote! { let #param = unsafe { ::ext_php_rs::zend::borrow_unchecked(#param) }; } - ); + if let Type::Path(t) = ty_inner { + if t.path.segments[0].ident.to_string() == "Option" { + if let PathArguments::AngleBracketed(t) = &t.path.segments[0].arguments { + if let GenericArgument::Type(t) = &t.args[0] { + ty_inner = t; + is_option = true; } } } - }; + } + let mut is_str = false; + if let Type::Reference(t) = ty_inner { + if t.mutability.is_none() { + if let Type::Path(t) = &*t.elem { + is_str = t.path.is_ident("str"); + } + } + } + hack_tokens.append_all(if is_str { + if is_option { + quote! { let #param = #param.and_then(|__temp| Some(unsafe { ::core::mem::transmute::<&str, &'static str>(__temp) })); } + } else { + quote! { let #param = unsafe { ::core::mem::transmute::<&str, &'static str>(#param) }; } + } + } else { + if is_option { + quote! { let #param = #param.and_then(|__temp| Some(unsafe { ::ext_php_rs::zend::borrow_unchecked(__temp) })); } + } else { + quote! { let #param = unsafe { ::ext_php_rs::zend::borrow_unchecked(#param) }; } + } + }); let default = defaults.get(&name); let mut ty = ty.ty.clone(); diff --git a/src/zend/globals.rs b/src/zend/globals.rs index a8dec77..09cdb78 100644 --- a/src/zend/globals.rs +++ b/src/zend/globals.rs @@ -6,6 +6,7 @@ use lazy_static::lazy_static; use parking_lot::{const_rwlock, RwLock, RwLockReadGuard, RwLockWriteGuard}; use tokio::runtime::Runtime; +use crate::binary_slice::{BinarySlice, PackSlice}; use crate::boxed::ZBox; use crate::ffi::{_zend_executor_globals, ext_php_rs_executor_globals}; @@ -158,3 +159,14 @@ unsafe impl<'original, 'unbounded, T: 'unbounded> BorrowUnchecked<'original, 'un unsafe { ::core::mem::transmute(self) } } } + +unsafe impl<'original, 'unbounded, T: 'unbounded + PackSlice> BorrowUnchecked<'original, 'unbounded> + for BinarySlice<'original, T> +{ + type Unbounded = BinarySlice<'unbounded, T>; + + #[inline(always)] + unsafe fn borrow_unchecked(self) -> Self::Unbounded { + unsafe { ::core::mem::transmute(self) } + } +} \ No newline at end of file From 751d4f4d04805198f2291a8342976f30eaf1161a Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Thu, 22 Jun 2023 13:31:56 +0200 Subject: [PATCH 010/100] Refactoring --- allowed_bindings.rs | 2 + crates/macros/src/method.rs | 16 ++-- docsrs_bindings.rs | 48 +++++++----- src/builders/module.rs | 8 +- src/error.rs | 3 + src/lib.rs | 2 + src/macros.rs | 25 ------- src/types/array.rs | 53 +++++++++++++ src/zend/borrow_unchecked.rs | 52 +++++++++++++ src/zend/fibers.rs | 139 +++++++++++++++++++++++++++++++++++ src/zend/function.rs | 111 +++++++++++++++++++++++++++- src/zend/globals.rs | 59 --------------- src/zend/mod.rs | 12 ++- 13 files changed, 409 insertions(+), 121 deletions(-) create mode 100644 src/zend/borrow_unchecked.rs create mode 100644 src/zend/fibers.rs diff --git a/allowed_bindings.rs b/allowed_bindings.rs index 7943f33..51fd15a 100644 --- a/allowed_bindings.rs +++ b/allowed_bindings.rs @@ -54,6 +54,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/macros/src/method.rs b/crates/macros/src/method.rs index 381836d..fa145d5 100644 --- a/crates/macros/src/method.rs +++ b/crates/macros/src/method.rs @@ -194,28 +194,22 @@ pub fn parser( #hack_tokens let future = async move #stmts; - let future = GLOBAL_CONNECTION.with_borrow_mut(move |c| { + let future = ::ext_php_rs::zend::EVENTLOOP.with_borrow_mut(move |c| { let c = c.as_mut().unwrap(); - let f = ::ext_php_rs::get_current_suspension!(); - let idx = c.fibers.len() as u64; - let mut callable = Zval::new(); - callable.set_array(vec![f, "resume".into_zval(false).unwrap()]).unwrap(); - c.fibers.insert_at_index(idx, callable).unwrap(); + let idx = c.prepare_resume(); let sender = c.sender.clone(); let mut notifier = c.notify_sender.try_clone().unwrap(); - ::ext_php_rs::zend::RUNTIME.spawn(async move { + let res = ::ext_php_rs::zend::RUNTIME.spawn(async move { let res = future.await; sender.send(idx).unwrap(); - notifier.write_all(&[0]).unwrap(); + ::std::io::Write::write_all(&mut notifier, &[0]).unwrap(); res }) }); - let mut callable = Zval::new(); - callable.set_array(vec![::ext_php_rs::get_current_suspension!(), "suspend".into_zval(false).unwrap()]).unwrap(); - call_user_func!(callable).unwrap(); + ::ext_php_rs::zend::EVENTLOOP.suspend(); return ::ext_php_rs::zend::RUNTIME .block_on(future).unwrap(); diff --git a/docsrs_bindings.rs b/docsrs_bindings.rs index f4fb348..07e009d 100644 --- a/docsrs_bindings.rs +++ b/docsrs_bindings.rs @@ -1,6 +1,6 @@ /* automatically generated by rust-bindgen 0.65.1 */ -pub const ZEND_DEBUG: u32 = 1; +pub const ZEND_DEBUG: u32 = 0; pub const _ZEND_TYPE_NAME_BIT: u32 = 16777216; pub const _ZEND_TYPE_NULLABLE_BIT: u32 = 2; pub const HT_MIN_SIZE: u32 = 8; @@ -87,6 +87,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; @@ -285,22 +290,10 @@ pub struct _zend_ast_ref { pub gc: zend_refcounted_h, } extern "C" { - pub fn _emalloc( - size: usize, - __zend_filename: *const ::std::os::raw::c_char, - __zend_lineno: u32, - __zend_orig_filename: *const ::std::os::raw::c_char, - __zend_orig_lineno: u32, - ) -> *mut ::std::os::raw::c_void; + pub fn _emalloc(size: usize) -> *mut ::std::os::raw::c_void; } extern "C" { - pub fn _efree( - ptr: *mut ::std::os::raw::c_void, - __zend_filename: *const ::std::os::raw::c_char, - __zend_lineno: u32, - __zend_orig_filename: *const ::std::os::raw::c_char, - __zend_orig_lineno: u32, - ); + pub fn _efree(ptr: *mut ::std::os::raw::c_void); } extern "C" { pub fn __zend_malloc(len: usize) -> *mut ::std::os::raw::c_void; @@ -387,6 +380,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); } @@ -1027,7 +1027,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; @@ -1094,7 +1102,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, @@ -1237,6 +1245,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/src/builders/module.rs b/src/builders/module.rs index 6eaaf51..8e1b4a8 100644 --- a/src/builders/module.rs +++ b/src/builders/module.rs @@ -1,8 +1,8 @@ use crate::{ error::Result, ffi::{ext_php_rs_php_build_id, ZEND_MODULE_API_NO}, - zend::{FunctionEntry, ModuleEntry}, - PHP_DEBUG, PHP_ZTS, + zend::{FunctionEntry, ModuleEntry, request_shutdown, request_startup}, + PHP_DEBUG, PHP_ZTS, types::ZendClassObject, }; use std::{ffi::CString, mem, ptr}; @@ -64,8 +64,8 @@ impl ModuleBuilder { functions: ptr::null(), module_startup_func: None, module_shutdown_func: None, - request_startup_func: None, - request_shutdown_func: None, + request_startup_func: Some(request_startup), + request_shutdown_func: Some(request_shutdown), info_func: None, version: ptr::null(), globals_size: 0, diff --git a/src/error.rs b/src/error.rs index 841228e..270bdaf 100644 --- a/src/error.rs +++ b/src/error.rs @@ -52,6 +52,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. @@ -84,6 +86,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/lib.rs b/src/lib.rs index 720a23d..ac235eb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,7 @@ #![allow(non_snake_case)] #![cfg_attr(docs, feature(doc_cfg))] #![cfg_attr(windows, feature(abi_vectorcall))] +#![feature(thread_local, local_key_cell_methods)] pub mod alloc; pub mod args; @@ -35,6 +36,7 @@ pub mod zend; /// A module typically glob-imported containing the typically required macros /// and imports. pub mod prelude { + use crate::boxed::ZBox; pub use crate::builders::ModuleBuilder; #[cfg(any(docs, feature = "closure"))] #[cfg_attr(docs, doc(cfg(feature = "closure")))] diff --git a/src/macros.rs b/src/macros.rs index 99839f8..47f255b 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -72,31 +72,6 @@ macro_rules! call_user_func { }; } -#[macro_export] -macro_rules! call_static_method { - ($clazz: expr, $fn: expr) => {{ - let mut callable = ext_php_rs::types::Zval::new(); - callable - .set_array(vec![$clazz, $fn]) - .unwrap(); - call_user_func!(callable) - }}; - - ($clazz: expr, $fn: expr, $($param: expr),*) => {{ - let mut callable = ext_php_rs::types::Zval::new(); - callable - .set_array(vec![$clazz, $fn]) - .unwrap(); - call_user_func!(callable, $(&$param),*) - }}; -} - -#[macro_export] -macro_rules! get_current_suspension { - () => { ext_php_rs::call_static_method!("\\Revolt\\EventLoop", "getSuspension").unwrap() } -} - - /// Parses a given list of arguments using the [`ArgParser`] class. /// /// # Examples diff --git a/src/types/array.rs b/src/types/array.rs index 28bf47a..c9f7b41 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 diff --git a/src/zend/borrow_unchecked.rs b/src/zend/borrow_unchecked.rs new file mode 100644 index 0000000..46fd449 --- /dev/null +++ b/src/zend/borrow_unchecked.rs @@ -0,0 +1,52 @@ +use crate::binary_slice::{BinarySlice, PackSlice}; + +#[inline(always)] +pub unsafe fn borrow_unchecked< + 'original, + 'unbounded, + Ref: BorrowUnchecked<'original, 'unbounded>, +>( + reference: Ref, +) -> Ref::Unbounded { + unsafe { BorrowUnchecked::borrow_unchecked(reference) } +} + +#[doc(hidden)] +pub unsafe trait BorrowUnchecked<'original, 'unbounded> { + type Unbounded; + + unsafe fn borrow_unchecked(self) -> Self::Unbounded; +} + +unsafe impl<'original, 'unbounded, T: 'unbounded> BorrowUnchecked<'original, 'unbounded> + for &'original T +{ + type Unbounded = &'unbounded T; + + #[inline(always)] + unsafe fn borrow_unchecked(self) -> Self::Unbounded { + unsafe { ::core::mem::transmute(self) } + } +} + +unsafe impl<'original, 'unbounded, T: 'unbounded> BorrowUnchecked<'original, 'unbounded> + for &'original mut T +{ + type Unbounded = &'unbounded mut T; + + #[inline(always)] + unsafe fn borrow_unchecked(self) -> Self::Unbounded { + unsafe { ::core::mem::transmute(self) } + } +} + +unsafe impl<'original, 'unbounded, T: 'unbounded + PackSlice> BorrowUnchecked<'original, 'unbounded> + for BinarySlice<'original, T> +{ + type Unbounded = BinarySlice<'unbounded, T>; + + #[inline(always)] + unsafe fn borrow_unchecked(self) -> Self::Unbounded { + unsafe { ::core::mem::transmute(self) } + } +} \ No newline at end of file diff --git a/src/zend/fibers.rs b/src/zend/fibers.rs new file mode 100644 index 0000000..adc3077 --- /dev/null +++ b/src/zend/fibers.rs @@ -0,0 +1,139 @@ + +use crate::boxed::ZBox; +use crate::class::{ClassMetadata, RegisteredClass}; +use crate::prelude::PhpResult; +use crate::props::Property; +use crate::types::{ZendHashTable, ZendClassObject, Zval}; +use crate::zend::Function; + +use std::cell::RefCell; +use std::collections::HashMap; +use std::fs::File; +use std::io; +use std::os::fd::{RawFd, FromRawFd}; +use std::sync::mpsc::{Sender, Receiver, channel}; +use std::io::Read; +use lazy_static::lazy_static; +use tokio::runtime::Runtime; +use std::os::fd::AsRawFd; + +lazy_static! { + pub static ref RUNTIME: Runtime = Runtime::new().expect("Could not allocate runtime"); +} + +#[cfg(any(target_os = "linux", target_os = "solaris"))] +fn sys_pipe() -> io::Result<(RawFd, RawFd)> { + let mut pipefd = [0; 2]; + let ret = unsafe { libc::pipe2(pipefd.as_mut_ptr(), libc::O_CLOEXEC | libc::O_NONBLOCK) }; + if ret == -1 { + return Err(io::Error::last_os_error()); + } + Ok((pipefd[0], pipefd[1])) +} + +pub struct GlobalConnection { + fibers: ZBox, + + sender: Sender, + receiver: Receiver, + + notify_sender: File, + notify_receiver: File, + + get_current_suspension: Function, + suspend: Function, + resume: Function, + + dummy: [u8; 1], +} + +impl GlobalConnection { + pub fn getEventFd() -> u64 { + EVENTLOOP.with_borrow(|c| { + c.as_ref().unwrap().notify_receiver.as_raw_fd() as u64 + }) + } + pub fn wakeup() -> PhpResult<()> { + EVENTLOOP.with_borrow_mut(|c| { + c.as_mut().unwrap().wakeup_internal() + }) + } +} + +impl GlobalConnection { + fn new() -> PhpResult { + let (sender, receiver) = channel(); + let (notify_receiver, notify_sender) = + sys_pipe().map_err(|err| format!("Could not create pipe: {}", err))?; + + Ok(Self { + fibers: ZendHashTable::new(), + sender: sender, + receiver: receiver, + notify_sender: unsafe { File::from_raw_fd(notify_sender) }, + notify_receiver: unsafe { File::from_raw_fd(notify_receiver) }, + dummy: [0; 1], + get_current_suspension: Function::try_from_method("\\Revolt\\EventLoop", "getSuspension").unwrap(), + suspend: Function::try_from_method("\\Revolt\\EventLoop\\Suspension", "suspend").unwrap(), + resume: Function::try_from_method("\\Revolt\\EventLoop\\Suspension", "resume").unwrap() + }) + } + + fn wakeup_internal(&mut self) -> PhpResult<()> { + self.notify_receiver.read_exact(&mut self.dummy).unwrap(); + + for fiber_id in self.receiver.try_iter() { + if let Some(fiber) = self.fibers.get_index_mut(fiber_id) { + self.resume.try_call_obj(fiber, vec![])?; + self.fibers.remove_index(fiber_id); + } + } + Ok(()) + } + + pub fn prepare_resume(&mut self) -> u64 { + let idx = self.fibers.len() as u64; + self.fibers.insert_at_index(idx, call_user_func!(self.get_current_suspension).unwrap()).unwrap(); + + idx + } + + pub fn suspend() { + EVENTLOOP.with_borrow_mut(|c| { + let c = c.as_mut().unwrap(); + let mut suspension = call_user_func!(c.get_current_suspension).unwrap(); + c.suspend.try_call_obj(&mut suspension, vec![]) + }).unwrap(); + () + } +} + +class_derives!(GlobalConnection); + +static EVENTLOOP_META: ClassMetadata = ClassMetadata::new(); + +impl RegisteredClass for GlobalConnection { + const CLASS_NAME: &'static str = "GlobalConnection"; + + fn get_metadata() -> &'static ClassMetadata { + &EVENTLOOP_META + } + + fn get_properties<'a>() -> HashMap<&'static str, Property<'a, Self>> { + HashMap::new() + } +} + +thread_local! { + pub static EVENTLOOP: RefCell>>> = RefCell::new(None); +} + +pub extern "C" fn request_startup(_type: i32, _module_number: i32) -> i32 { + EVENTLOOP.set(Some(ZendClassObject::new(GlobalConnection::new().unwrap()))); + 0 +} + +pub extern "C" fn request_shutdown(_type: i32, _module_number: i32) -> i32 { + EVENTLOOP.set(None); + 0 +} \ No newline at end of file diff --git a/src/zend/function.rs b/src/zend/function.rs index ba889d7..4475051 100644 --- a/src/zend/function.rs +++ b/src/zend/function.rs @@ -1,8 +1,10 @@ //! Builder for creating functions and methods in PHP. -use std::{fmt::Debug, os::raw::c_char, ptr}; +use std::{fmt::Debug, os::raw::c_char, ptr::self}; -use crate::ffi::zend_function_entry; +use crate::{ffi::{zend_function_entry, zend_fetch_function_str, zend_function, zend_hash_str_find_ptr_lc, zend_call_known_function}, convert::IntoZvalDyn, types::Zval, error::{Result, Error}}; + +use super::ClassEntry; /// A Zend function entry. pub type FunctionEntry = zend_function_entry; @@ -36,3 +38,108 @@ 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; + } + return Some(*res); + } + } + pub fn try_from_method(class: &str, name: &str) -> Option { + return 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 + } + return Some(*res) + } + } + } + + /// 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(&mut 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 *mut _, + std::ptr::null_mut(), + std::ptr::null_mut(), + &mut retval, + len as _, + packed.as_ptr() as *mut _, + std::ptr::null_mut(), + ) + }; + + Ok(retval) + } + + #[inline(always)] + pub fn try_call_obj(&mut self, obj: &mut Zval, params: Vec<&dyn IntoZvalDyn>) -> Result { + let obj = obj.object_mut().ok_or(Error::Object)?; + 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 *mut _, + obj as *mut _, + obj.ce, + &mut retval, + len as _, + packed.as_ptr() as *mut _, + std::ptr::null_mut(), + ) + }; + + Ok(retval) + } +} \ No newline at end of file diff --git a/src/zend/globals.rs b/src/zend/globals.rs index 09cdb78..6773d10 100644 --- a/src/zend/globals.rs +++ b/src/zend/globals.rs @@ -2,9 +2,7 @@ use std::ops::{Deref, DerefMut}; -use lazy_static::lazy_static; use parking_lot::{const_rwlock, RwLock, RwLockReadGuard, RwLockWriteGuard}; -use tokio::runtime::Runtime; use crate::binary_slice::{BinarySlice, PackSlice}; use crate::boxed::ZBox; @@ -70,10 +68,6 @@ impl ExecutorGlobals { } } -lazy_static! { - pub static ref RUNTIME: Runtime = Runtime::new().expect("Could not allocate runtime"); -} - /// Executor globals rwlock. /// /// PHP provides no indication if the executor globals are being accessed so @@ -116,57 +110,4 @@ impl DerefMut for GlobalWriteGuard { fn deref_mut(&mut self) -> &mut Self::Target { self.globals } -} - - - -#[inline(always)] -pub unsafe fn borrow_unchecked< - 'original, - 'unbounded, - Ref: BorrowUnchecked<'original, 'unbounded>, ->( - reference: Ref, -) -> Ref::Unbounded { - unsafe { BorrowUnchecked::borrow_unchecked(reference) } -} - -#[doc(hidden)] -pub unsafe trait BorrowUnchecked<'original, 'unbounded> { - type Unbounded; - - unsafe fn borrow_unchecked(self) -> Self::Unbounded; -} - -unsafe impl<'original, 'unbounded, T: 'unbounded> BorrowUnchecked<'original, 'unbounded> - for &'original T -{ - type Unbounded = &'unbounded T; - - #[inline(always)] - unsafe fn borrow_unchecked(self) -> Self::Unbounded { - unsafe { ::core::mem::transmute(self) } - } -} - -unsafe impl<'original, 'unbounded, T: 'unbounded> BorrowUnchecked<'original, 'unbounded> - for &'original mut T -{ - type Unbounded = &'unbounded mut T; - - #[inline(always)] - unsafe fn borrow_unchecked(self) -> Self::Unbounded { - unsafe { ::core::mem::transmute(self) } - } -} - -unsafe impl<'original, 'unbounded, T: 'unbounded + PackSlice> BorrowUnchecked<'original, 'unbounded> - for BinarySlice<'original, T> -{ - type Unbounded = BinarySlice<'unbounded, T>; - - #[inline(always)] - unsafe fn borrow_unchecked(self) -> Self::Unbounded { - unsafe { ::core::mem::transmute(self) } - } } \ No newline at end of file diff --git a/src/zend/mod.rs b/src/zend/mod.rs index 5b9e622..f71dcfe 100644 --- a/src/zend/mod.rs +++ b/src/zend/mod.rs @@ -8,6 +8,8 @@ mod function; mod globals; mod handlers; mod module; +mod fibers; +mod borrow_unchecked; use crate::{error::Result, ffi::php_printf}; use std::ffi::CString; @@ -16,10 +18,14 @@ pub use _type::ZendType; pub use class::ClassEntry; pub use ex::ExecuteData; pub use function::FunctionEntry; +pub use function::Function; pub use globals::ExecutorGlobals; -pub use globals::RUNTIME; -pub use globals::borrow_unchecked; -pub use globals::BorrowUnchecked; +pub use fibers::RUNTIME; +pub use fibers::EVENTLOOP; +pub use fibers::request_shutdown; +pub use fibers::request_startup; +pub use borrow_unchecked::borrow_unchecked; +pub use borrow_unchecked::BorrowUnchecked; pub use handlers::ZendObjectHandlers; pub use module::ModuleEntry; From ee5c3e1e823cc88794b3f44f5647811164eaf61c Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Thu, 22 Jun 2023 13:33:09 +0200 Subject: [PATCH 011/100] Cleanup --- crates/macros/src/method.rs | 4 ++-- src/builders/module.rs | 2 +- src/lib.rs | 2 +- src/zend/fibers.rs | 10 +++++----- src/zend/globals.rs | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/crates/macros/src/method.rs b/crates/macros/src/method.rs index fa145d5..d5c94eb 100644 --- a/crates/macros/src/method.rs +++ b/crates/macros/src/method.rs @@ -198,8 +198,8 @@ pub fn parser( let c = c.as_mut().unwrap(); let idx = c.prepare_resume(); - let sender = c.sender.clone(); - let mut notifier = c.notify_sender.try_clone().unwrap(); + let sender = c._sender.clone(); + let mut notifier = c._notify_sender.try_clone().unwrap(); let res = ::ext_php_rs::zend::RUNTIME.spawn(async move { let res = future.await; diff --git a/src/builders/module.rs b/src/builders/module.rs index 8e1b4a8..080dc93 100644 --- a/src/builders/module.rs +++ b/src/builders/module.rs @@ -2,7 +2,7 @@ use crate::{ error::Result, ffi::{ext_php_rs_php_build_id, ZEND_MODULE_API_NO}, zend::{FunctionEntry, ModuleEntry, request_shutdown, request_startup}, - PHP_DEBUG, PHP_ZTS, types::ZendClassObject, + PHP_DEBUG, PHP_ZTS, }; use std::{ffi::CString, mem, ptr}; diff --git a/src/lib.rs b/src/lib.rs index ac235eb..1b45b3f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,7 +36,7 @@ pub mod zend; /// A module typically glob-imported containing the typically required macros /// and imports. pub mod prelude { - use crate::boxed::ZBox; + pub use crate::builders::ModuleBuilder; #[cfg(any(docs, feature = "closure"))] #[cfg_attr(docs, doc(cfg(feature = "closure")))] diff --git a/src/zend/fibers.rs b/src/zend/fibers.rs index adc3077..259135a 100644 --- a/src/zend/fibers.rs +++ b/src/zend/fibers.rs @@ -3,7 +3,7 @@ use crate::boxed::ZBox; use crate::class::{ClassMetadata, RegisteredClass}; use crate::prelude::PhpResult; use crate::props::Property; -use crate::types::{ZendHashTable, ZendClassObject, Zval}; +use crate::types::{ZendHashTable, ZendClassObject}; use crate::zend::Function; use std::cell::RefCell; @@ -34,10 +34,10 @@ fn sys_pipe() -> io::Result<(RawFd, RawFd)> { pub struct GlobalConnection { fibers: ZBox, - sender: Sender, + _sender: Sender, receiver: Receiver, - notify_sender: File, + _notify_sender: File, notify_receiver: File, get_current_suspension: Function, @@ -68,9 +68,9 @@ impl GlobalConnection { Ok(Self { fibers: ZendHashTable::new(), - sender: sender, + _sender: sender, receiver: receiver, - notify_sender: unsafe { File::from_raw_fd(notify_sender) }, + _notify_sender: unsafe { File::from_raw_fd(notify_sender) }, notify_receiver: unsafe { File::from_raw_fd(notify_receiver) }, dummy: [0; 1], get_current_suspension: Function::try_from_method("\\Revolt\\EventLoop", "getSuspension").unwrap(), diff --git a/src/zend/globals.rs b/src/zend/globals.rs index 6773d10..729b605 100644 --- a/src/zend/globals.rs +++ b/src/zend/globals.rs @@ -4,7 +4,7 @@ use std::ops::{Deref, DerefMut}; use parking_lot::{const_rwlock, RwLock, RwLockReadGuard, RwLockWriteGuard}; -use crate::binary_slice::{BinarySlice, PackSlice}; + use crate::boxed::ZBox; use crate::ffi::{_zend_executor_globals, ext_php_rs_executor_globals}; From ab55a491d72dcc6685a9d0f63a6320447ab6e4cb Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Thu, 22 Jun 2023 14:13:01 +0200 Subject: [PATCH 012/100] Cleanup --- crates/macros/src/method.rs | 8 ++--- src/builders/module.rs | 6 ++-- src/zend/fibers.rs | 70 +++++++++++-------------------------- src/zend/mod.rs | 3 +- 4 files changed, 29 insertions(+), 58 deletions(-) diff --git a/crates/macros/src/method.rs b/crates/macros/src/method.rs index d5c94eb..c7456dc 100644 --- a/crates/macros/src/method.rs +++ b/crates/macros/src/method.rs @@ -198,10 +198,10 @@ pub fn parser( let c = c.as_mut().unwrap(); let idx = c.prepare_resume(); - let sender = c._sender.clone(); - let mut notifier = c._notify_sender.try_clone().unwrap(); + let sender = c.get_sender(); + let mut notifier = c.get_notify_sender(); - let res = ::ext_php_rs::zend::RUNTIME.spawn(async move { + ::ext_php_rs::zend::RUNTIME.spawn(async move { let res = future.await; sender.send(idx).unwrap(); ::std::io::Write::write_all(&mut notifier, &[0]).unwrap(); @@ -209,7 +209,7 @@ pub fn parser( }) }); - ::ext_php_rs::zend::EVENTLOOP.suspend(); + ::ext_php_rs::zend::EventLoop::suspend(); return ::ext_php_rs::zend::RUNTIME .block_on(future).unwrap(); diff --git a/src/builders/module.rs b/src/builders/module.rs index 080dc93..6eaaf51 100644 --- a/src/builders/module.rs +++ b/src/builders/module.rs @@ -1,7 +1,7 @@ use crate::{ error::Result, ffi::{ext_php_rs_php_build_id, ZEND_MODULE_API_NO}, - zend::{FunctionEntry, ModuleEntry, request_shutdown, request_startup}, + zend::{FunctionEntry, ModuleEntry}, PHP_DEBUG, PHP_ZTS, }; @@ -64,8 +64,8 @@ impl ModuleBuilder { functions: ptr::null(), module_startup_func: None, module_shutdown_func: None, - request_startup_func: Some(request_startup), - request_shutdown_func: Some(request_shutdown), + request_startup_func: None, + request_shutdown_func: None, info_func: None, version: ptr::null(), globals_size: 0, diff --git a/src/zend/fibers.rs b/src/zend/fibers.rs index 259135a..be61caa 100644 --- a/src/zend/fibers.rs +++ b/src/zend/fibers.rs @@ -1,13 +1,10 @@ use crate::boxed::ZBox; -use crate::class::{ClassMetadata, RegisteredClass}; use crate::prelude::PhpResult; -use crate::props::Property; -use crate::types::{ZendHashTable, ZendClassObject}; +use crate::types::ZendHashTable; use crate::zend::Function; use std::cell::RefCell; -use std::collections::HashMap; use std::fs::File; use std::io; use std::os::fd::{RawFd, FromRawFd}; @@ -31,13 +28,13 @@ fn sys_pipe() -> io::Result<(RawFd, RawFd)> { Ok((pipefd[0], pipefd[1])) } -pub struct GlobalConnection { +pub struct EventLoop { fibers: ZBox, - _sender: Sender, + sender: Sender, receiver: Receiver, - _notify_sender: File, + notify_sender: File, notify_receiver: File, get_current_suspension: Function, @@ -47,30 +44,20 @@ pub struct GlobalConnection { dummy: [u8; 1], } -impl GlobalConnection { - pub fn getEventFd() -> u64 { - EVENTLOOP.with_borrow(|c| { - c.as_ref().unwrap().notify_receiver.as_raw_fd() as u64 - }) - } - pub fn wakeup() -> PhpResult<()> { - EVENTLOOP.with_borrow_mut(|c| { - c.as_mut().unwrap().wakeup_internal() - }) - } -} - -impl GlobalConnection { - fn new() -> PhpResult { +impl EventLoop { + pub fn new() -> PhpResult { let (sender, receiver) = channel(); let (notify_receiver, notify_sender) = sys_pipe().map_err(|err| format!("Could not create pipe: {}", err))?; + call_user_func!(Function::try_from_function("class_exists").unwrap(), "\\Revolt\\EventLoop").unwrap(); + call_user_func!(Function::try_from_function("interface_exists").unwrap(), "\\Revolt\\EventLoop\\Suspension").unwrap(); + Ok(Self { fibers: ZendHashTable::new(), - _sender: sender, + sender: sender, receiver: receiver, - _notify_sender: unsafe { File::from_raw_fd(notify_sender) }, + notify_sender: unsafe { File::from_raw_fd(notify_sender) }, notify_receiver: unsafe { File::from_raw_fd(notify_receiver) }, dummy: [0; 1], get_current_suspension: Function::try_from_method("\\Revolt\\EventLoop", "getSuspension").unwrap(), @@ -78,8 +65,12 @@ impl GlobalConnection { resume: Function::try_from_method("\\Revolt\\EventLoop\\Suspension", "resume").unwrap() }) } + + pub fn get_event_fd(&self)->u64 { + self.notify_receiver.as_raw_fd() as u64 + } - fn wakeup_internal(&mut self) -> PhpResult<()> { + pub fn wakeup_internal(&mut self) -> PhpResult<()> { self.notify_receiver.read_exact(&mut self.dummy).unwrap(); for fiber_id in self.receiver.try_iter() { @@ -106,34 +97,15 @@ impl GlobalConnection { }).unwrap(); () } -} -class_derives!(GlobalConnection); - -static EVENTLOOP_META: ClassMetadata = ClassMetadata::new(); - -impl RegisteredClass for GlobalConnection { - const CLASS_NAME: &'static str = "GlobalConnection"; - - fn get_metadata() -> &'static ClassMetadata { - &EVENTLOOP_META + pub fn get_sender(&self) -> Sender { + self.sender.clone() } - - fn get_properties<'a>() -> HashMap<&'static str, Property<'a, Self>> { - HashMap::new() + pub fn get_notify_sender(&self) -> File { + self.notify_sender.try_clone().unwrap() } } thread_local! { - pub static EVENTLOOP: RefCell>>> = RefCell::new(None); + pub static EVENTLOOP: RefCell> = RefCell::new(None); } - -pub extern "C" fn request_startup(_type: i32, _module_number: i32) -> i32 { - EVENTLOOP.set(Some(ZendClassObject::new(GlobalConnection::new().unwrap()))); - 0 -} - -pub extern "C" fn request_shutdown(_type: i32, _module_number: i32) -> i32 { - EVENTLOOP.set(None); - 0 -} \ No newline at end of file diff --git a/src/zend/mod.rs b/src/zend/mod.rs index f71dcfe..3e6ac25 100644 --- a/src/zend/mod.rs +++ b/src/zend/mod.rs @@ -22,8 +22,7 @@ pub use function::Function; pub use globals::ExecutorGlobals; pub use fibers::RUNTIME; pub use fibers::EVENTLOOP; -pub use fibers::request_shutdown; -pub use fibers::request_startup; +pub use fibers::EventLoop; pub use borrow_unchecked::borrow_unchecked; pub use borrow_unchecked::BorrowUnchecked; pub use handlers::ZendObjectHandlers; From 33f9d6e4b85758bbb99e3748081755a01ba835d6 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Thu, 22 Jun 2023 14:17:49 +0200 Subject: [PATCH 013/100] Improvement --- src/zend/fibers.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/zend/fibers.rs b/src/zend/fibers.rs index be61caa..bf71ce3 100644 --- a/src/zend/fibers.rs +++ b/src/zend/fibers.rs @@ -50,8 +50,8 @@ impl EventLoop { let (notify_receiver, notify_sender) = sys_pipe().map_err(|err| format!("Could not create pipe: {}", err))?; - call_user_func!(Function::try_from_function("class_exists").unwrap(), "\\Revolt\\EventLoop").unwrap(); - call_user_func!(Function::try_from_function("interface_exists").unwrap(), "\\Revolt\\EventLoop\\Suspension").unwrap(); + call_user_func!(Function::try_from_function("class_exists").unwrap(), "\\Revolt\\EventLoop")?; + call_user_func!(Function::try_from_function("interface_exists").unwrap(), "\\Revolt\\EventLoop\\Suspension")?; Ok(Self { fibers: ZendHashTable::new(), @@ -60,9 +60,9 @@ impl EventLoop { notify_sender: unsafe { File::from_raw_fd(notify_sender) }, notify_receiver: unsafe { File::from_raw_fd(notify_receiver) }, dummy: [0; 1], - get_current_suspension: Function::try_from_method("\\Revolt\\EventLoop", "getSuspension").unwrap(), - suspend: Function::try_from_method("\\Revolt\\EventLoop\\Suspension", "suspend").unwrap(), - resume: Function::try_from_method("\\Revolt\\EventLoop\\Suspension", "resume").unwrap() + get_current_suspension: Function::try_from_method("\\Revolt\\EventLoop", "getSuspension").ok_or("\\Revolt\\EventLoop::getSuspension does not exist")?, + suspend: Function::try_from_method("\\Revolt\\EventLoop\\Suspension", "suspend").ok_or("\\Revolt\\Suspension::suspend does not exist")?, + resume: Function::try_from_method("\\Revolt\\EventLoop\\Suspension", "resume").ok_or("\\Revolt\\Suspension::resume does not exist")? }) } From e0ae48175def70cf7a3592cc626ceb6ed3793e81 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Thu, 22 Jun 2023 15:12:26 +0200 Subject: [PATCH 014/100] Cleanup --- src/zend/fibers.rs | 5 ++--- src/zend/function.rs | 8 ++++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/zend/fibers.rs b/src/zend/fibers.rs index bf71ce3..3ce7595 100644 --- a/src/zend/fibers.rs +++ b/src/zend/fibers.rs @@ -50,8 +50,8 @@ impl EventLoop { let (notify_receiver, notify_sender) = sys_pipe().map_err(|err| format!("Could not create pipe: {}", err))?; - call_user_func!(Function::try_from_function("class_exists").unwrap(), "\\Revolt\\EventLoop")?; - call_user_func!(Function::try_from_function("interface_exists").unwrap(), "\\Revolt\\EventLoop\\Suspension")?; + call_user_func!(Function::from_function("class_exists"), "\\Revolt\\EventLoop")?; + call_user_func!(Function::from_function("interface_exists"), "\\Revolt\\EventLoop\\Suspension")?; Ok(Self { fibers: ZendHashTable::new(), @@ -95,7 +95,6 @@ impl EventLoop { let mut suspension = call_user_func!(c.get_current_suspension).unwrap(); c.suspend.try_call_obj(&mut suspension, vec![]) }).unwrap(); - () } pub fn get_sender(&self) -> Sender { diff --git a/src/zend/function.rs b/src/zend/function.rs index 4475051..59f4f6d 100644 --- a/src/zend/function.rs +++ b/src/zend/function.rs @@ -68,6 +68,14 @@ impl Function { } } + pub fn from_function(name: &str) -> Self { + Self::try_from_function(name).unwrap() + } + + pub fn from_method(class: &str, name: &str) -> Self { + Self::try_from_method(class, name).unwrap() + } + /// Attempts to call the callable with a list of arguments to pass to the /// function. /// From 064ef747c1988d2749d346ac3ec7b8ea1b962536 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Thu, 22 Jun 2023 16:14:49 +0200 Subject: [PATCH 015/100] Fixes --- src/types/object.rs | 37 +++++++++++++++++++++++++++++++++++-- src/types/zval.rs | 5 +++++ src/zend/fibers.rs | 13 ++++--------- src/zend/function.rs | 34 ++++------------------------------ 4 files changed, 48 insertions(+), 41 deletions(-) diff --git a/src/types/object.rs b/src/types/object.rs index 606c483..c32f601 100644 --- a/src/types/object.rs +++ b/src/types/object.rs @@ -6,11 +6,11 @@ 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, + HashTable, ZEND_ISEMPTY, ZEND_PROPERTY_EXISTS, ZEND_PROPERTY_ISSET, zend_hash_str_find_ptr_lc, zend_function, }, flags::DataType, rc::PhpRc, @@ -121,6 +121,39 @@ 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 d45cdad..12bff6a 100644 --- a/src/types/zval.rs +++ b/src/types/zval.rs @@ -206,6 +206,11 @@ 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() { diff --git a/src/zend/fibers.rs b/src/zend/fibers.rs index 3ce7595..d0a1a8a 100644 --- a/src/zend/fibers.rs +++ b/src/zend/fibers.rs @@ -38,9 +38,7 @@ pub struct EventLoop { notify_receiver: File, get_current_suspension: Function, - suspend: Function, - resume: Function, - + dummy: [u8; 1], } @@ -61,8 +59,6 @@ impl EventLoop { notify_receiver: unsafe { File::from_raw_fd(notify_receiver) }, dummy: [0; 1], get_current_suspension: Function::try_from_method("\\Revolt\\EventLoop", "getSuspension").ok_or("\\Revolt\\EventLoop::getSuspension does not exist")?, - suspend: Function::try_from_method("\\Revolt\\EventLoop\\Suspension", "suspend").ok_or("\\Revolt\\Suspension::suspend does not exist")?, - resume: Function::try_from_method("\\Revolt\\EventLoop\\Suspension", "resume").ok_or("\\Revolt\\Suspension::resume does not exist")? }) } @@ -75,7 +71,7 @@ impl EventLoop { for fiber_id in self.receiver.try_iter() { if let Some(fiber) = self.fibers.get_index_mut(fiber_id) { - self.resume.try_call_obj(fiber, vec![])?; + fiber.object_mut().unwrap().try_call_method("resume", vec![])?; self.fibers.remove_index(fiber_id); } } @@ -92,9 +88,8 @@ impl EventLoop { pub fn suspend() { EVENTLOOP.with_borrow_mut(|c| { let c = c.as_mut().unwrap(); - let mut suspension = call_user_func!(c.get_current_suspension).unwrap(); - c.suspend.try_call_obj(&mut suspension, vec![]) - }).unwrap(); + call_user_func!(c.get_current_suspension).unwrap().try_call_method("suspend", vec![]).unwrap(); + }); } pub fn get_sender(&self) -> Sender { diff --git a/src/zend/function.rs b/src/zend/function.rs index 59f4f6d..93f1903 100644 --- a/src/zend/function.rs +++ b/src/zend/function.rs @@ -2,7 +2,7 @@ use std::{fmt::Debug, os::raw::c_char, ptr::self}; -use crate::{ffi::{zend_function_entry, zend_fetch_function_str, zend_function, zend_hash_str_find_ptr_lc, zend_call_known_function}, convert::IntoZvalDyn, types::Zval, error::{Result, Error}}; +use crate::{ffi::{zend_function_entry, zend_fetch_function_str, zend_function, zend_hash_str_find_ptr_lc, zend_call_known_function}, convert::IntoZvalDyn, types::Zval, error::Result}; use super::ClassEntry; @@ -101,7 +101,7 @@ impl Function { /// assert_eq!(result.long(), Some(1)); /// ``` #[inline(always)] - pub fn try_call(&mut self, params: Vec<&dyn IntoZvalDyn>) -> Result { + pub fn try_call(&self, params: Vec<&dyn IntoZvalDyn>) -> Result { let mut retval = Zval::new(); let len = params.len(); let params = params @@ -112,7 +112,7 @@ impl Function { unsafe { zend_call_known_function( - self as *mut _, + self as *const _ as *mut _, std::ptr::null_mut(), std::ptr::null_mut(), &mut retval, @@ -124,30 +124,4 @@ impl Function { Ok(retval) } - - #[inline(always)] - pub fn try_call_obj(&mut self, obj: &mut Zval, params: Vec<&dyn IntoZvalDyn>) -> Result { - let obj = obj.object_mut().ok_or(Error::Object)?; - 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 *mut _, - obj as *mut _, - obj.ce, - &mut retval, - len as _, - packed.as_ptr() as *mut _, - std::ptr::null_mut(), - ) - }; - - Ok(retval) - } -} \ No newline at end of file +} From ae2bd550e8aa471aed1fe623234c2ff0d7540574 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Thu, 22 Jun 2023 16:16:17 +0200 Subject: [PATCH 016/100] Cleanup --- src/zend/fibers.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/zend/fibers.rs b/src/zend/fibers.rs index d0a1a8a..bdf96ad 100644 --- a/src/zend/fibers.rs +++ b/src/zend/fibers.rs @@ -86,10 +86,7 @@ impl EventLoop { } pub fn suspend() { - EVENTLOOP.with_borrow_mut(|c| { - let c = c.as_mut().unwrap(); - call_user_func!(c.get_current_suspension).unwrap().try_call_method("suspend", vec![]).unwrap(); - }); + call_user_func!(Function::try_from_method("\\Revolt\\EventLoop", "getSuspension").unwrap()).unwrap().try_call_method("suspend", vec![]).unwrap(); } pub fn get_sender(&self) -> Sender { From c517931726dec87d9465e23dffa5eb067290ebf7 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Thu, 22 Jun 2023 16:19:24 +0200 Subject: [PATCH 017/100] Cleanup --- src/zend/fibers.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zend/fibers.rs b/src/zend/fibers.rs index bdf96ad..302bcf3 100644 --- a/src/zend/fibers.rs +++ b/src/zend/fibers.rs @@ -86,7 +86,7 @@ impl EventLoop { } pub fn suspend() { - call_user_func!(Function::try_from_method("\\Revolt\\EventLoop", "getSuspension").unwrap()).unwrap().try_call_method("suspend", vec![]).unwrap(); + call_user_func!(Function::from_method("\\Revolt\\EventLoop", "getSuspension")).unwrap().try_call_method("suspend", vec![]).unwrap(); } pub fn get_sender(&self) -> Sender { From 66572230876885de96447ca1965712b4e3c2e660 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Thu, 22 Jun 2023 16:50:01 +0200 Subject: [PATCH 018/100] Further cleanup --- crates/macros/src/method.rs | 21 +--------- src/zend/fibers.rs | 83 +++++++++++++++++++++++++------------ src/zend/mod.rs | 1 - 3 files changed, 57 insertions(+), 48 deletions(-) diff --git a/crates/macros/src/method.rs b/crates/macros/src/method.rs index c7456dc..41b69d9 100644 --- a/crates/macros/src/method.rs +++ b/crates/macros/src/method.rs @@ -192,27 +192,8 @@ pub fn parser( input.block = parse_quote! {{ #this #hack_tokens - let future = async move #stmts; - let future = ::ext_php_rs::zend::EVENTLOOP.with_borrow_mut(move |c| { - let c = c.as_mut().unwrap(); - let idx = c.prepare_resume(); - - let sender = c.get_sender(); - let mut notifier = c.get_notify_sender(); - - ::ext_php_rs::zend::RUNTIME.spawn(async move { - let res = future.await; - sender.send(idx).unwrap(); - ::std::io::Write::write_all(&mut notifier, &[0]).unwrap(); - res - }) - }); - - ::ext_php_rs::zend::EventLoop::suspend(); - - return ::ext_php_rs::zend::RUNTIME - .block_on(future).unwrap(); + ::ext_php_rs::zend::EventLoop::suspend_on(async move #stmts) }}; } let this = match method_type { diff --git a/src/zend/fibers.rs b/src/zend/fibers.rs index 302bcf3..b01281a 100644 --- a/src/zend/fibers.rs +++ b/src/zend/fibers.rs @@ -6,6 +6,7 @@ use crate::zend::Function; use std::cell::RefCell; use std::fs::File; +use std::future::Future; use std::io; use std::os::fd::{RawFd, FromRawFd}; use std::sync::mpsc::{Sender, Receiver, channel}; @@ -18,6 +19,10 @@ lazy_static! { pub static ref RUNTIME: Runtime = Runtime::new().expect("Could not allocate runtime"); } +thread_local! { + static EVENTLOOP: RefCell> = RefCell::new(None); +} + #[cfg(any(target_os = "linux", target_os = "solaris"))] fn sys_pipe() -> io::Result<(RawFd, RawFd)> { let mut pipefd = [0; 2]; @@ -43,7 +48,50 @@ pub struct EventLoop { } impl EventLoop { - pub fn new() -> PhpResult { + pub fn init() -> PhpResult { + EVENTLOOP.with_borrow_mut(|e| { + Ok( + match e { + None => e.insert(Self::new()?), + Some(ev) => ev + }.notify_receiver.as_raw_fd() as u64 + ) + }) + } + + pub fn suspend_on + Send + 'static>(future: F) -> T { + let future = EVENTLOOP.with_borrow_mut(move |c| { + let c = c.as_mut().unwrap(); + let idx = c.fibers.len() as u64; + c.fibers.insert_at_index(idx, call_user_func!(c.get_current_suspension).unwrap()).unwrap(); + + let sender = c.sender.clone(); + let mut notifier = c.notify_sender.try_clone().unwrap(); + + RUNTIME.spawn(async move { + let res = future.await; + sender.send(idx).unwrap(); + ::std::io::Write::write_all(&mut notifier, &[0]).unwrap(); + res + }) + }); + + call_user_func!(Function::from_method("\\Revolt\\EventLoop", "getSuspension")).unwrap().try_call_method("suspend", vec![]).unwrap(); + + return RUNTIME.block_on(future).unwrap(); + } + + pub fn wakeup() -> PhpResult<()> { + EVENTLOOP.with_borrow_mut(|c| { + c.as_mut().unwrap().wakeup_internal() + }) + } + + pub fn shutdown() { + EVENTLOOP.set(None) + } + + fn new() -> PhpResult { let (sender, receiver) = channel(); let (notify_receiver, notify_sender) = sys_pipe().map_err(|err| format!("Could not create pipe: {}", err))?; @@ -62,11 +110,7 @@ impl EventLoop { }) } - pub fn get_event_fd(&self)->u64 { - self.notify_receiver.as_raw_fd() as u64 - } - - pub fn wakeup_internal(&mut self) -> PhpResult<()> { + fn wakeup_internal(&mut self) -> PhpResult<()> { self.notify_receiver.read_exact(&mut self.dummy).unwrap(); for fiber_id in self.receiver.try_iter() { @@ -77,26 +121,11 @@ impl EventLoop { } Ok(()) } - - pub fn prepare_resume(&mut self) -> u64 { - let idx = self.fibers.len() as u64; - self.fibers.insert_at_index(idx, call_user_func!(self.get_current_suspension).unwrap()).unwrap(); - - idx - } - - pub fn suspend() { - call_user_func!(Function::from_method("\\Revolt\\EventLoop", "getSuspension")).unwrap().try_call_method("suspend", vec![]).unwrap(); - } - - pub fn get_sender(&self) -> Sender { - self.sender.clone() - } - pub fn get_notify_sender(&self) -> File { - self.notify_sender.try_clone().unwrap() - } } -thread_local! { - pub static EVENTLOOP: RefCell> = RefCell::new(None); -} +impl Drop for EventLoop { + fn drop(&mut self) { + unsafe { libc::close(self.notify_receiver.as_raw_fd()) }; + unsafe { libc::close(self.notify_sender.as_raw_fd()) }; + } +} \ No newline at end of file diff --git a/src/zend/mod.rs b/src/zend/mod.rs index 3e6ac25..7757d23 100644 --- a/src/zend/mod.rs +++ b/src/zend/mod.rs @@ -21,7 +21,6 @@ pub use function::FunctionEntry; pub use function::Function; pub use globals::ExecutorGlobals; pub use fibers::RUNTIME; -pub use fibers::EVENTLOOP; pub use fibers::EventLoop; pub use borrow_unchecked::borrow_unchecked; pub use borrow_unchecked::BorrowUnchecked; From ca2bc9ae976f4295953927f09b03805cad0c6015 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Thu, 22 Jun 2023 17:13:17 +0200 Subject: [PATCH 019/100] Performance trick --- src/zend/fibers.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/zend/fibers.rs b/src/zend/fibers.rs index b01281a..9593814 100644 --- a/src/zend/fibers.rs +++ b/src/zend/fibers.rs @@ -7,7 +7,7 @@ use crate::zend::Function; use std::cell::RefCell; use std::fs::File; use std::future::Future; -use std::io; +use std::io::{self, Write}; use std::os::fd::{RawFd, FromRawFd}; use std::sync::mpsc::{Sender, Receiver, channel}; use std::io::Read; @@ -15,6 +15,8 @@ use lazy_static::lazy_static; use tokio::runtime::Runtime; use std::os::fd::AsRawFd; +use super::borrow_unchecked; + lazy_static! { pub static ref RUNTIME: Runtime = Runtime::new().expect("Could not allocate runtime"); } @@ -60,7 +62,7 @@ impl EventLoop { } pub fn suspend_on + Send + 'static>(future: F) -> T { - let future = EVENTLOOP.with_borrow_mut(move |c| { + let (future, get_current_suspension) = EVENTLOOP.with_borrow_mut(move |c| { let c = c.as_mut().unwrap(); let idx = c.fibers.len() as u64; c.fibers.insert_at_index(idx, call_user_func!(c.get_current_suspension).unwrap()).unwrap(); @@ -68,15 +70,17 @@ impl EventLoop { let sender = c.sender.clone(); let mut notifier = c.notify_sender.try_clone().unwrap(); - RUNTIME.spawn(async move { + (RUNTIME.spawn(async move { let res = future.await; sender.send(idx).unwrap(); - ::std::io::Write::write_all(&mut notifier, &[0]).unwrap(); + notifier.write_all(&[0]).unwrap(); res + }), unsafe { + borrow_unchecked(&c.get_current_suspension) }) }); - call_user_func!(Function::from_method("\\Revolt\\EventLoop", "getSuspension")).unwrap().try_call_method("suspend", vec![]).unwrap(); + call_user_func!(get_current_suspension).unwrap().try_call_method("suspend", vec![]).unwrap(); return RUNTIME.block_on(future).unwrap(); } From 8514614521aa62ce5f4044f7e63eb7981d4f66b1 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Thu, 22 Jun 2023 17:32:37 +0200 Subject: [PATCH 020/100] Remove redundant fclose --- src/zend/fibers.rs | 33 ++++++++++++--------------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/src/zend/fibers.rs b/src/zend/fibers.rs index 9593814..11b7219 100644 --- a/src/zend/fibers.rs +++ b/src/zend/fibers.rs @@ -15,7 +15,7 @@ use lazy_static::lazy_static; use tokio::runtime::Runtime; use std::os::fd::AsRawFd; -use super::borrow_unchecked; +use super::{borrow_unchecked, printf}; lazy_static! { pub static ref RUNTIME: Runtime = Runtime::new().expect("Could not allocate runtime"); @@ -87,7 +87,17 @@ impl EventLoop { pub fn wakeup() -> PhpResult<()> { EVENTLOOP.with_borrow_mut(|c| { - c.as_mut().unwrap().wakeup_internal() + let c = c.as_mut().unwrap(); + + c.notify_receiver.read_exact(&mut c.dummy).unwrap(); + + for fiber_id in c.receiver.try_iter() { + if let Some(fiber) = c.fibers.get_index_mut(fiber_id) { + fiber.object_mut().unwrap().try_call_method("resume", vec![])?; + c.fibers.remove_index(fiber_id); + } + } + Ok(()) }) } @@ -113,23 +123,4 @@ impl EventLoop { get_current_suspension: Function::try_from_method("\\Revolt\\EventLoop", "getSuspension").ok_or("\\Revolt\\EventLoop::getSuspension does not exist")?, }) } - - fn wakeup_internal(&mut self) -> PhpResult<()> { - self.notify_receiver.read_exact(&mut self.dummy).unwrap(); - - for fiber_id in self.receiver.try_iter() { - if let Some(fiber) = self.fibers.get_index_mut(fiber_id) { - fiber.object_mut().unwrap().try_call_method("resume", vec![])?; - self.fibers.remove_index(fiber_id); - } - } - Ok(()) - } -} - -impl Drop for EventLoop { - fn drop(&mut self) { - unsafe { libc::close(self.notify_receiver.as_raw_fd()) }; - unsafe { libc::close(self.notify_sender.as_raw_fd()) }; - } } \ No newline at end of file From e658ce890f8cf947418bab8376231f1c3e0c6a13 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Thu, 29 Jun 2023 12:45:29 +0200 Subject: [PATCH 021/100] Small fix --- crates/macros/src/method.rs | 26 +++++++++++++------------- src/zend/fibers.rs | 2 +- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/crates/macros/src/method.rs b/crates/macros/src/method.rs index 41b69d9..a1f8833 100644 --- a/crates/macros/src/method.rs +++ b/crates/macros/src/method.rs @@ -376,20 +376,20 @@ fn build_args( is_str = t.path.is_ident("str"); } } + hack_tokens.append_all(if is_str { + if is_option { + quote! { let #param = #param.and_then(|__temp| Some(unsafe { ::core::mem::transmute::<&str, &'static str>(__temp) })); } + } else { + quote! { let #param = unsafe { ::core::mem::transmute::<&str, &'static str>(#param) }; } + } + } else { + if is_option { + quote! { let #param = #param.and_then(|__temp| Some(unsafe { ::ext_php_rs::zend::borrow_unchecked(__temp) })); } + } else { + quote! { let #param = unsafe { ::ext_php_rs::zend::borrow_unchecked(#param) }; } + } + }); } - hack_tokens.append_all(if is_str { - if is_option { - quote! { let #param = #param.and_then(|__temp| Some(unsafe { ::core::mem::transmute::<&str, &'static str>(__temp) })); } - } else { - quote! { let #param = unsafe { ::core::mem::transmute::<&str, &'static str>(#param) }; } - } - } else { - if is_option { - quote! { let #param = #param.and_then(|__temp| Some(unsafe { ::ext_php_rs::zend::borrow_unchecked(__temp) })); } - } else { - quote! { let #param = unsafe { ::ext_php_rs::zend::borrow_unchecked(#param) }; } - } - }); let default = defaults.get(&name); let mut ty = ty.ty.clone(); diff --git a/src/zend/fibers.rs b/src/zend/fibers.rs index 11b7219..b283eb8 100644 --- a/src/zend/fibers.rs +++ b/src/zend/fibers.rs @@ -15,7 +15,7 @@ use lazy_static::lazy_static; use tokio::runtime::Runtime; use std::os::fd::AsRawFd; -use super::{borrow_unchecked, printf}; +use super::{borrow_unchecked}; lazy_static! { pub static ref RUNTIME: Runtime = Runtime::new().expect("Could not allocate runtime"); From eda544ceaf6535f85cc1a6aa39a0fda5abb8e7a4 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Thu, 29 Jun 2023 13:46:01 +0200 Subject: [PATCH 022/100] Simplify --- src/zend/class.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/zend/class.rs b/src/zend/class.rs index dc06a32..71375c1 100644 --- a/src/zend/class.rs +++ b/src/zend/class.rs @@ -1,6 +1,6 @@ //! Builder and objects for creating classes in the PHP world. -use crate::{ffi::zend_class_entry, flags::ClassFlags, types::ZendStr, zend::ExecutorGlobals}; +use crate::{ffi::{zend_class_entry}, flags::ClassFlags, types::{ZendStr, ZendObject}, zend::ExecutorGlobals, boxed::ZBox}; use std::{convert::TryInto, fmt::Debug, ops::DerefMut}; /// A PHP class entry. @@ -22,6 +22,16 @@ impl ClassEntry { } } + /// Creates a new [`ZendObject`], returned inside an [`ZBox`] + /// wrapper. + /// + /// # Panics + /// + /// Panics when allocating memory for the new object fails. + pub fn new(&self) -> ZBox { + ZendObject::new(self) + } + /// Returns the class flags. pub fn flags(&self) -> ClassFlags { ClassFlags::from_bits_truncate(self.ce_flags) From ab2af4581d2f92ad896361342654652c1b86fda3 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Thu, 29 Jun 2023 14:05:49 +0200 Subject: [PATCH 023/100] Fix class construction --- src/types/object.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/types/object.rs b/src/types/object.rs index c32f601..ee05b82 100644 --- a/src/types/object.rs +++ b/src/types/object.rs @@ -10,7 +10,7 @@ use crate::{ 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, zend_hash_str_find_ptr_lc, zend_function, + HashTable, ZEND_ISEMPTY, ZEND_PROPERTY_EXISTS, ZEND_PROPERTY_ISSET, zend_hash_str_find_ptr_lc, zend_function, object_properties_init, }, flags::DataType, rc::PhpRc, @@ -42,6 +42,7 @@ impl ZendObject { // `*mut` is valid as the function will not mutate `ce`. unsafe { let ptr = zend_objects_new(ce as *const _ as *mut _); + object_properties_init(ptr, ce as *const _ as *mut _); ZBox::from_raw( ptr.as_mut() .expect("Failed to allocate memory for Zend object"), From c8c1df06e28254a651adc6b35a2455603821eba7 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Thu, 29 Jun 2023 14:20:04 +0200 Subject: [PATCH 024/100] Fix construction of native classes and any class with properties --- src/types/object.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/types/object.rs b/src/types/object.rs index ee05b82..f48c205 100644 --- a/src/types/object.rs +++ b/src/types/object.rs @@ -41,8 +41,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 _); - object_properties_init(ptr, 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"), From a7780753dc940102cc7a3faaece9780a7e9dd702 Mon Sep 17 00:00:00 2001 From: Joe Hoyle Date: Sun, 5 Feb 2023 12:23:01 +0200 Subject: [PATCH 025/100] Support registering ini definitions for modules PHP extensions that want to provide ini settings can do so using `IniEntryDef::register()`; values can then be fetched via `GlobalExecutor::ini_values()`. --- allowed_bindings.rs | 6 +++++ docsrs_bindings.rs | 36 +++++++++++++++++++++++++ src/flags.rs | 19 ++++++++++--- src/wrapper.h | 1 + src/zend/globals.rs | 26 +++++++++++++++++- src/zend/ini_entry_def.rs | 57 +++++++++++++++++++++++++++++++++++++++ src/zend/mod.rs | 2 ++ 7 files changed, 142 insertions(+), 5 deletions(-) create mode 100644 src/zend/ini_entry_def.rs diff --git a/allowed_bindings.rs b/allowed_bindings.rs index 7943f33..d40bd9d 100644 --- a/allowed_bindings.rs +++ b/allowed_bindings.rs @@ -98,6 +98,8 @@ bind! { zend_objects_clone_members, zend_register_bool_constant, zend_register_double_constant, + zend_register_ini_entries, + zend_ini_entry_def, zend_register_internal_class_ex, zend_register_long_constant, zend_register_string_constant, @@ -141,6 +143,10 @@ bind! { IS_PTR, MAY_BE_ANY, MAY_BE_BOOL, + PHP_INI_USER, + PHP_INI_PERDIR, + PHP_INI_SYSTEM, + PHP_INI_ALL, USING_ZTS, ZEND_ACC_ABSTRACT, ZEND_ACC_ANON_CLASS, diff --git a/docsrs_bindings.rs b/docsrs_bindings.rs index f4fb348..052a14b 100644 --- a/docsrs_bindings.rs +++ b/docsrs_bindings.rs @@ -83,6 +83,10 @@ pub const ZEND_MODULE_API_NO: u32 = 20220829; pub const USING_ZTS: u32 = 0; pub const MAY_BE_BOOL: u32 = 12; pub const MAY_BE_ANY: u32 = 1022; +pub const PHP_INI_USER: u32 = 1; +pub const PHP_INI_PERDIR: u32 = 2; +pub const PHP_INI_SYSTEM: u32 = 4; +pub const PHP_INI_ALL: u32 = 7; pub const CONST_CS: u32 = 0; pub const CONST_PERSISTENT: u32 = 1; pub const CONST_NO_FILE_CACHE: u32 = 2; @@ -1343,6 +1347,32 @@ extern "C" { } #[repr(C)] #[derive(Debug, Copy, Clone)] +pub struct _zend_ini_entry_def { + pub name: *const ::std::os::raw::c_char, + pub on_modify: ::std::option::Option< + unsafe extern "C" fn( + entry: *mut zend_ini_entry, + new_value: *mut zend_string, + mh_arg1: *mut ::std::os::raw::c_void, + mh_arg2: *mut ::std::os::raw::c_void, + mh_arg3: *mut ::std::os::raw::c_void, + stage: ::std::os::raw::c_int, + ) -> ::std::os::raw::c_int, + >, + pub mh_arg1: *mut ::std::os::raw::c_void, + pub mh_arg2: *mut ::std::os::raw::c_void, + pub mh_arg3: *mut ::std::os::raw::c_void, + pub value: *const ::std::os::raw::c_char, + pub displayer: ::std::option::Option< + unsafe extern "C" fn(ini_entry: *mut zend_ini_entry, type_: ::std::os::raw::c_int), + >, + pub value_length: u32, + pub name_length: u16, + pub modifiable: u8, +} +pub type zend_ini_entry_def = _zend_ini_entry_def; +#[repr(C)] +#[derive(Debug, Copy, Clone)] pub struct _zend_ini_entry { pub name: *mut zend_string, pub on_modify: ::std::option::Option< @@ -1368,6 +1398,12 @@ pub struct _zend_ini_entry { pub orig_modifiable: u8, pub modified: u8, } +extern "C" { + pub fn zend_register_ini_entries( + ini_entry: *const zend_ini_entry_def, + module_number: ::std::os::raw::c_int, + ) -> zend_result; +} extern "C" { pub fn zend_register_bool_constant( name: *const ::std::os::raw::c_char, diff --git a/src/flags.rs b/src/flags.rs index 20378e6..1461126 100644 --- a/src/flags.rs +++ b/src/flags.rs @@ -8,10 +8,11 @@ use crate::ffi::{ CONST_CS, CONST_DEPRECATED, CONST_NO_FILE_CACHE, CONST_PERSISTENT, IS_ARRAY, IS_CALLABLE, IS_CONSTANT_AST, IS_DOUBLE, IS_FALSE, IS_LONG, IS_MIXED, IS_NULL, IS_OBJECT, IS_PTR, IS_REFERENCE, IS_RESOURCE, IS_STRING, IS_TRUE, IS_TYPE_COLLECTABLE, IS_TYPE_REFCOUNTED, - IS_UNDEF, IS_VOID, ZEND_ACC_ABSTRACT, ZEND_ACC_ANON_CLASS, ZEND_ACC_CALL_VIA_TRAMPOLINE, - ZEND_ACC_CHANGED, ZEND_ACC_CLOSURE, ZEND_ACC_CONSTANTS_UPDATED, ZEND_ACC_CTOR, - ZEND_ACC_DEPRECATED, ZEND_ACC_DONE_PASS_TWO, ZEND_ACC_EARLY_BINDING, ZEND_ACC_FAKE_CLOSURE, - ZEND_ACC_FINAL, ZEND_ACC_GENERATOR, ZEND_ACC_HAS_FINALLY_BLOCK, ZEND_ACC_HAS_RETURN_TYPE, + IS_UNDEF, IS_VOID, PHP_INI_ALL, PHP_INI_PERDIR, PHP_INI_SYSTEM, PHP_INI_USER, + ZEND_ACC_ABSTRACT, ZEND_ACC_ANON_CLASS, ZEND_ACC_CALL_VIA_TRAMPOLINE, ZEND_ACC_CHANGED, + ZEND_ACC_CLOSURE, ZEND_ACC_CONSTANTS_UPDATED, ZEND_ACC_CTOR, ZEND_ACC_DEPRECATED, + ZEND_ACC_DONE_PASS_TWO, ZEND_ACC_EARLY_BINDING, ZEND_ACC_FAKE_CLOSURE, ZEND_ACC_FINAL, + ZEND_ACC_GENERATOR, ZEND_ACC_HAS_FINALLY_BLOCK, ZEND_ACC_HAS_RETURN_TYPE, ZEND_ACC_HAS_TYPE_HINTS, ZEND_ACC_HEAP_RT_CACHE, ZEND_ACC_IMMUTABLE, ZEND_ACC_IMPLICIT_ABSTRACT_CLASS, ZEND_ACC_INTERFACE, ZEND_ACC_LINKED, ZEND_ACC_NEARLY_LINKED, ZEND_ACC_NEVER_CACHE, ZEND_ACC_NO_DYNAMIC_PROPERTIES, ZEND_ACC_PRELOADED, ZEND_ACC_PRIVATE, @@ -171,6 +172,16 @@ bitflags! { } } +bitflags! { + /// Represents permissions for where a configuration setting may be set. + pub struct IniEntryPermission: u32 { + const User = PHP_INI_USER; + const PerDir = PHP_INI_PERDIR; + const System = PHP_INI_SYSTEM; + const All = PHP_INI_ALL; + } +} + /// Valid data types for PHP. #[repr(C, u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] diff --git a/src/wrapper.h b/src/wrapper.h index 2813263..ed9dea6 100644 --- a/src/wrapper.h +++ b/src/wrapper.h @@ -20,6 +20,7 @@ #include "zend_exceptions.h" #include "zend_inheritance.h" #include "zend_interfaces.h" +#include "zend_ini.h" zend_string *ext_php_rs_zend_string_init(const char *str, size_t len, bool persistent); void ext_php_rs_zend_string_release(zend_string *zs); diff --git a/src/zend/globals.rs b/src/zend/globals.rs index 7ba76c4..236a888 100644 --- a/src/zend/globals.rs +++ b/src/zend/globals.rs @@ -1,11 +1,12 @@ //! Types related to the PHP executor globals. +use std::collections::HashMap; use std::ops::{Deref, DerefMut}; use parking_lot::{const_rwlock, RwLock, RwLockReadGuard, RwLockWriteGuard}; use crate::boxed::ZBox; -use crate::ffi::{_zend_executor_globals, ext_php_rs_executor_globals}; +use crate::ffi::{_zend_executor_globals, ext_php_rs_executor_globals, zend_ini_entry}; use crate::types::{ZendHashTable, ZendObject}; @@ -50,6 +51,29 @@ impl ExecutorGlobals { unsafe { self.class_table.as_ref() } } + /// Retrieves the ini values for all ini directives in the current executor context.. + pub fn ini_values(&self) -> HashMap> { + let hash_table = unsafe { self.ini_directives.as_ref().unwrap() }; + let mut ini_hash_map: HashMap> = HashMap::new(); + for (_index, key, value) in hash_table.iter() { + match key { + Some(key) => { + ini_hash_map.insert(key, unsafe { + let ini_entry = &*value.ptr::().unwrap(); + if ini_entry.value.is_null() { + None + } else { + Some((&*ini_entry.value).as_str().unwrap().to_owned()) + } + }); + () + } + None => (), + } + } + ini_hash_map + } + /// Attempts to extract the last PHP exception captured by the interpreter. /// Returned inside a [`ZBox`]. /// diff --git a/src/zend/ini_entry_def.rs b/src/zend/ini_entry_def.rs new file mode 100644 index 0000000..367ef2c --- /dev/null +++ b/src/zend/ini_entry_def.rs @@ -0,0 +1,57 @@ +//! Builder for creating inis and methods in PHP. +//! See https://www.phpinternalsbook.com/php7/extensions_design/ini_settings.html for details. + +use std::{ffi::CString, os::raw::c_char, ptr}; + +use crate::{ffi::zend_ini_entry_def, ffi::zend_register_ini_entries, flags::IniEntryPermission}; + +/// A Zend ini entry definition. +/// +/// To register ini definitions for extensions, the IniEntryDef builder should be used. Ini +/// entries should be registered in your module's startup_function via IniEntryDef::register(Vec). +pub type IniEntryDef = zend_ini_entry_def; + +impl IniEntryDef { + /// Returns an empty ini entry, signifying the end of a ini list. + pub fn new(name: String, default_value: String, permission: IniEntryPermission) -> Self { + let mut template = Self::end(); + let name = CString::new(name).unwrap(); + let value = CString::new(default_value).unwrap(); + template.name_length = name.as_bytes().len() as _; + template.name = name.into_raw(); + template.value_length = value.as_bytes().len() as _; + template.value = value.into_raw(); + template.modifiable = IniEntryPermission::PerDir.bits() as _; + template.modifiable = permission.bits() as _; + template + } + + /// Returns an empty ini entry def, signifying the end of a ini list. + pub fn end() -> Self { + Self { + name: ptr::null() as *const c_char, + on_modify: None, + mh_arg1: std::ptr::null_mut(), + mh_arg2: std::ptr::null_mut(), + mh_arg3: std::ptr::null_mut(), + value: std::ptr::null_mut(), + displayer: None, + modifiable: 0, + value_length: 0, + name_length: 0, + } + } + + /// Converts the ini entry into a raw and pointer, releasing it to the + /// C world. + pub fn into_raw(self) -> *mut Self { + Box::into_raw(Box::new(self)) + } + + pub fn register(mut entries: Vec, module_number: i32) { + entries.push(Self::end()); + let entries = Box::into_raw(entries.into_boxed_slice()) as *const Self; + + unsafe { zend_register_ini_entries(entries, module_number) }; + } +} diff --git a/src/zend/mod.rs b/src/zend/mod.rs index 74547f0..b69aa52 100644 --- a/src/zend/mod.rs +++ b/src/zend/mod.rs @@ -7,6 +7,7 @@ mod ex; mod function; mod globals; mod handlers; +mod ini_entry_def; mod module; use crate::{error::Result, ffi::php_printf}; @@ -18,6 +19,7 @@ pub use ex::ExecuteData; pub use function::FunctionEntry; pub use globals::ExecutorGlobals; pub use handlers::ZendObjectHandlers; +pub use ini_entry_def::IniEntryDef; pub use module::ModuleEntry; // Used as the format string for `php_printf`. From 614f5b42bec78d8491e0e4216d7670937ff81c9d Mon Sep 17 00:00:00 2001 From: Joe Hoyle Date: Mon, 13 Feb 2023 13:23:47 +0200 Subject: [PATCH 026/100] Support function args being passed by reference Currently it's not possible for a function to accept args by reference. This PR adds early support for doing so, by transforming any function args that are `&mut T` into args that are passed by reference. E.g.: ``` \#[php_function] fn my_function( arg: &mut Zval ) { arg.reference_mut().map( |zval| zval.set_bool(true) ); } ``` --- crates/macros/src/function.rs | 60 +++++++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 3 deletions(-) diff --git a/crates/macros/src/function.rs b/crates/macros/src/function.rs index 5e3def2..477d6fb 100644 --- a/crates/macros/src/function.rs +++ b/crates/macros/src/function.rs @@ -26,6 +26,7 @@ pub struct Arg { pub ty: String, pub nullable: bool, pub default: Option, + pub as_ref: bool, } #[derive(Debug, Clone)] @@ -249,12 +250,19 @@ pub fn get_return_type(output_type: &ReturnType) -> Result) -> Self { + pub fn new( + name: String, + ty: String, + nullable: bool, + default: Option, + as_ref: bool, + ) -> Self { Self { name, ty, nullable, default, + as_ref, } } @@ -268,6 +276,7 @@ impl Arg { match ty { Type::Path(TypePath { path, .. }) => { let mut path = path.clone(); + let mut pass_by_ref = false; path.drop_lifetimes(); let seg = path.segments.last()?; @@ -283,9 +292,45 @@ impl Arg { None } }); + + // For for types that are `Option<&mut T>` to turn them into `Option<&T>`, marking the Arg as + // as "passed by reference". + let option = Some(seg) + .filter(|seg| seg.ident == "Option") + .and_then(|seg| { + if let PathArguments::AngleBracketed(args) = &seg.arguments { + args.args + .iter() + .find(|arg| matches!(arg, GenericArgument::Type(_))) + .map(|ga| { + let new_ga = match ga { + GenericArgument::Type(ty) => { + let _rtype = match ty { + Type::Reference(r) => { + let mut new_ref = r.clone(); + new_ref.mutability = None; + pass_by_ref = true; + Type::Reference(new_ref) + }, + _ => ty.clone(), + }; + GenericArgument::Type(_rtype) + }, + _ => ga.clone(), + }; + new_ga.to_token_stream().to_string() + }) + } else { + None + } + }); + let stringified = match result { Some(result) if is_return => result, - _ => path.to_token_stream().to_string(), + _ => match option { + Some(result) => result, + None => path.to_token_stream().to_string(), + }, }; Some(Arg::new( @@ -293,15 +338,23 @@ impl Arg { stringified, seg.ident == "Option" || default.is_some(), default, + pass_by_ref, )) } Type::Reference(ref_) => { // Returning references is invalid, so let's just create our arg + + // Change any `&mut T` into `&T` and set the `as_ref` attribute on the Arg + // to marked it as a "passed by ref" PHP argument. + let mut ref_ = ref_.clone(); + let is_mutable_ref = ref_.mutability.is_some(); + ref_.mutability = None; Some(Arg::new( name, ref_.to_token_stream().to_string(), false, default, + is_mutable_ref, )) } _ => None, @@ -361,6 +414,7 @@ impl Arg { let ty = self.get_type_ident(); let null = self.nullable.then(|| quote! { .allow_null() }); + let passed_by_ref = self.as_ref.then(|| quote! { .as_ref() }); let default = self.default.as_ref().map(|val| { quote! { .default(#val) @@ -368,7 +422,7 @@ impl Arg { }); quote! { - ::ext_php_rs::args::Arg::new(#name, #ty) #null #default + ::ext_php_rs::args::Arg::new(#name, #ty) #null #passed_by_ref #default } } } From 99439bf7f5e233b444be5520dd5a65d770598787 Mon Sep 17 00:00:00 2001 From: Joe Hoyle Date: Thu, 13 Jul 2023 15:24:29 +0200 Subject: [PATCH 027/100] Clippy --- src/zend/globals.rs | 25 +++++++++++-------------- src/zend/ini_entry_def.rs | 4 ++-- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/src/zend/globals.rs b/src/zend/globals.rs index 236a888..c093b9c 100644 --- a/src/zend/globals.rs +++ b/src/zend/globals.rs @@ -53,23 +53,20 @@ impl ExecutorGlobals { /// Retrieves the ini values for all ini directives in the current executor context.. pub fn ini_values(&self) -> HashMap> { - let hash_table = unsafe { self.ini_directives.as_ref().unwrap() }; + let hash_table = unsafe { &*self.ini_directives }; let mut ini_hash_map: HashMap> = HashMap::new(); for (_index, key, value) in hash_table.iter() { - match key { - Some(key) => { - ini_hash_map.insert(key, unsafe { - let ini_entry = &*value.ptr::().unwrap(); - if ini_entry.value.is_null() { - None - } else { - Some((&*ini_entry.value).as_str().unwrap().to_owned()) - } - }); - () - } - None => (), + if let Some(key) = key { + ini_hash_map.insert(key, unsafe { + let ini_entry = &*value.ptr::().expect("Invalid ini entry"); + if ini_entry.value.is_null() { + None + } else { + Some((*ini_entry.value).as_str().expect("Ini value is not a string").to_owned()) + } + }); } + } ini_hash_map } diff --git a/src/zend/ini_entry_def.rs b/src/zend/ini_entry_def.rs index 367ef2c..88850fd 100644 --- a/src/zend/ini_entry_def.rs +++ b/src/zend/ini_entry_def.rs @@ -15,8 +15,8 @@ impl IniEntryDef { /// Returns an empty ini entry, signifying the end of a ini list. pub fn new(name: String, default_value: String, permission: IniEntryPermission) -> Self { let mut template = Self::end(); - let name = CString::new(name).unwrap(); - let value = CString::new(default_value).unwrap(); + let name = CString::new(name).expect("Unable to create CString from name"); + let value = CString::new(default_value).expect("Unable to create CString from value"); template.name_length = name.as_bytes().len() as _; template.name = name.into_raw(); template.value_length = value.as_bytes().len() as _; From 68059bdda157ce3e51f4b7353bc2d803b60048be Mon Sep 17 00:00:00 2001 From: Joe Hoyle Date: Thu, 13 Jul 2023 15:38:49 +0200 Subject: [PATCH 028/100] Fix mutable refs --- crates/macros/src/function.rs | 8 +------- guide/src/types/bool.md | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/crates/macros/src/function.rs b/crates/macros/src/function.rs index 477d6fb..4600e34 100644 --- a/crates/macros/src/function.rs +++ b/crates/macros/src/function.rs @@ -343,18 +343,12 @@ impl Arg { } Type::Reference(ref_) => { // Returning references is invalid, so let's just create our arg - - // Change any `&mut T` into `&T` and set the `as_ref` attribute on the Arg - // to marked it as a "passed by ref" PHP argument. - let mut ref_ = ref_.clone(); - let is_mutable_ref = ref_.mutability.is_some(); - ref_.mutability = None; Some(Arg::new( name, ref_.to_token_stream().to_string(), false, default, - is_mutable_ref, + ref_.mutability.is_some(), )) } _ => None, diff --git a/guide/src/types/bool.md b/guide/src/types/bool.md index 1974ff3..498b40e 100644 --- a/guide/src/types/bool.md +++ b/guide/src/types/bool.md @@ -45,3 +45,17 @@ pub fn test_bool(input: bool) -> String { var_dump(test_bool(true)); // string(4) "Yes!" var_dump(test_bool(false)); // string(3) "No!" ``` + +## Rust example, taking by reference + +```rust,no_run +# #![cfg_attr(windows, feature(abi_vectorcall))] +# extern crate ext_php_rs; +# use ext_php_rs::prelude::*; +# use ext_php_rs::types; +#[php_function] +pub fn test_bool(input: &mut types::Zval) { + input.reference_mut().unwrap().set_bool(false); +} +# fn main() {} +``` From 7d40ca544fd5836ec9330663d448ea6b7fe8edda Mon Sep 17 00:00:00 2001 From: Joe Hoyle Date: Thu, 13 Jul 2023 15:39:44 +0200 Subject: [PATCH 029/100] Formatting --- src/zend/globals.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/zend/globals.rs b/src/zend/globals.rs index c093b9c..f4db898 100644 --- a/src/zend/globals.rs +++ b/src/zend/globals.rs @@ -51,7 +51,8 @@ impl ExecutorGlobals { unsafe { self.class_table.as_ref() } } - /// Retrieves the ini values for all ini directives in the current executor context.. + /// Retrieves the ini values for all ini directives in the current executor + /// context.. pub fn ini_values(&self) -> HashMap> { let hash_table = unsafe { &*self.ini_directives }; let mut ini_hash_map: HashMap> = HashMap::new(); @@ -62,11 +63,15 @@ impl ExecutorGlobals { if ini_entry.value.is_null() { None } else { - Some((*ini_entry.value).as_str().expect("Ini value is not a string").to_owned()) + Some( + (*ini_entry.value) + .as_str() + .expect("Ini value is not a string") + .to_owned(), + ) } }); } - } ini_hash_map } From 64bf9e7366537a542eb5924c302806be039d7560 Mon Sep 17 00:00:00 2001 From: Joe Hoyle Date: Thu, 13 Jul 2023 15:44:14 +0200 Subject: [PATCH 030/100] fmt --- crates/macros/src/function.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/macros/src/function.rs b/crates/macros/src/function.rs index 4600e34..5a389f1 100644 --- a/crates/macros/src/function.rs +++ b/crates/macros/src/function.rs @@ -293,8 +293,8 @@ impl Arg { } }); - // For for types that are `Option<&mut T>` to turn them into `Option<&T>`, marking the Arg as - // as "passed by reference". + // For for types that are `Option<&mut T>` to turn them into `Option<&T>`, + // marking the Arg as as "passed by reference". let option = Some(seg) .filter(|seg| seg.ident == "Option") .and_then(|seg| { @@ -311,11 +311,11 @@ impl Arg { new_ref.mutability = None; pass_by_ref = true; Type::Reference(new_ref) - }, + } _ => ty.clone(), }; GenericArgument::Type(_rtype) - }, + } _ => ga.clone(), }; new_ga.to_token_stream().to_string() @@ -327,7 +327,7 @@ impl Arg { let stringified = match result { Some(result) if is_return => result, - _ => match option { + _ => match option { Some(result) => result, None => path.to_token_stream().to_string(), }, From b451d604df8da59b5b7c15e7b1611367fdfa15ae Mon Sep 17 00:00:00 2001 From: Joe Hoyle Date: Thu, 13 Jul 2023 16:24:00 +0200 Subject: [PATCH 031/100] URL formatting --- src/zend/ini_entry_def.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zend/ini_entry_def.rs b/src/zend/ini_entry_def.rs index 88850fd..f5f156d 100644 --- a/src/zend/ini_entry_def.rs +++ b/src/zend/ini_entry_def.rs @@ -1,5 +1,5 @@ //! Builder for creating inis and methods in PHP. -//! See https://www.phpinternalsbook.com/php7/extensions_design/ini_settings.html for details. +//! See for details. use std::{ffi::CString, os::raw::c_char, ptr}; From 0f4f6f731556c8e788dbabe425fddae8f11fc742 Mon Sep 17 00:00:00 2001 From: Joe Hoyle Date: Thu, 13 Jul 2023 17:16:30 +0200 Subject: [PATCH 032/100] Add docs / guide --- guide/src/SUMMARY.md | 1 + guide/src/ini-settings.md | 48 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 guide/src/ini-settings.md diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md index f725964..ed4507c 100644 --- a/guide/src/SUMMARY.md +++ b/guide/src/SUMMARY.md @@ -32,3 +32,4 @@ - [Constants](./macros/constant.md) - [`ZvalConvert`](./macros/zval_convert.md) - [Exceptions](./exceptions.md) +- [INI Settings](./ini-settings.md) diff --git a/guide/src/ini-settings.md b/guide/src/ini-settings.md new file mode 100644 index 0000000..22820c5 --- /dev/null +++ b/guide/src/ini-settings.md @@ -0,0 +1,48 @@ +# INI Settings + +Your PHP Extension may want to provide it's own PHP INI settings to configure behaviour. This can be done in the `#[php_startup]` annotated startup function. + +## Registering INI Settings + +All PHP INI definitions must be registered with PHP to get / set their values via the `php.ini` file or `ini_get() / ini_set()`. + + +```rust,no_run +# #![cfg_attr(windows, feature(abi_vectorcall))] +# extern crate ext_php_rs; +# use ext_php_rs::prelude::*; +# use ext_php_rs::zend::IniEntryDef; +# use ext_php_rs::flags::IniEntryPermission; + +#[php_startup] +pub fn startup_function(ty: i32, module_number: i32) { + let ini_entries: Vec = vec![ + IniEntryDef::new( + "my_extension.display_emoji".to_owned(), + "yes".to_owned(), + IniEntryPermission::All, + ), + ]; + IniEntryDef::register(ini_entries, module_number); +} +# fn main() {} +``` + +## Getting INI Settings + +The INI values are stored as part of the `GlobalExecutor`, and can be accessed via the `ini_values()` function. To retrieve the value for a registered INI setting + +```rust,no_run +# #![cfg_attr(windows, feature(abi_vectorcall))] +# extern crate ext_php_rs; +# use ext_php_rs::prelude::*; +# use ext_php_rs::zend::ExecutorGlobals; + +#[php_startup] +pub fn startup_function(ty: i32, module_number: i32) { + // Get all INI values + let ini_values = ExecutorGlobals::get().ini_values(); // HashMap> + let my_ini_value = ini_values.get("my_extension.display_emoji"); // Option> +} +# fn main() {} +``` From 6db91acae39df359ed3b25a54b63a68ef5192340 Mon Sep 17 00:00:00 2001 From: Joe Hoyle Date: Thu, 13 Jul 2023 20:29:48 +0200 Subject: [PATCH 033/100] formatting --- src/zend/ini_entry_def.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zend/ini_entry_def.rs b/src/zend/ini_entry_def.rs index f5f156d..8817300 100644 --- a/src/zend/ini_entry_def.rs +++ b/src/zend/ini_entry_def.rs @@ -8,7 +8,7 @@ use crate::{ffi::zend_ini_entry_def, ffi::zend_register_ini_entries, flags::IniE /// A Zend ini entry definition. /// /// To register ini definitions for extensions, the IniEntryDef builder should be used. Ini -/// entries should be registered in your module's startup_function via IniEntryDef::register(Vec). +/// entries should be registered in your module's startup_function via `IniEntryDef::register(Vec)`. pub type IniEntryDef = zend_ini_entry_def; impl IniEntryDef { From 89c77585e743901acfdfab3a8ddb50b186ead635 Mon Sep 17 00:00:00 2001 From: Joe Hoyle Date: Wed, 19 Jul 2023 15:13:50 +0200 Subject: [PATCH 034/100] Add helper method for function table --- src/zend/globals.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/zend/globals.rs b/src/zend/globals.rs index c9a7a0d..ac8701a 100644 --- a/src/zend/globals.rs +++ b/src/zend/globals.rs @@ -51,6 +51,16 @@ impl ExecutorGlobals { unsafe { self.class_table.as_ref() } } + /// Attempts to retrieve the global functions hash table. + pub fn function_table(&self) -> Option<&ZendHashTable> { + unsafe { self.function_table.as_ref() } + } + + /// Attempts to retrieve the global functions hash table as mutable. + pub fn function_table_mut(&self) -> Option<&mut ZendHashTable> { + unsafe { self.function_table.as_mut() } + } + /// Attempts to retrieve the global constants table. pub fn constants(&self) -> Option<&ZendHashTable> { unsafe { self.zend_constants.as_ref() } From 38cbba0457e637cc4e5410fb29a7964c2dfc40c0 Mon Sep 17 00:00:00 2001 From: Joe Hoyle Date: Wed, 19 Jul 2023 15:30:49 +0200 Subject: [PATCH 035/100] Add function type helper function Follow up to #254 --- src/zend/ex.rs | 12 ++++++++++++ src/zend/function.rs | 13 ++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/zend/ex.rs b/src/zend/ex.rs index dada00e..cb389cb 100644 --- a/src/zend/ex.rs +++ b/src/zend/ex.rs @@ -6,6 +6,8 @@ use crate::{ types::{ZendClassObject, ZendObject, Zval}, }; +use super::function::Function; + /// Execute data passed when a function is called from PHP. /// /// This generally contains things related to the call, including but not @@ -194,6 +196,16 @@ impl ExecuteData { self.This.object_mut() } + /// Attempt to retrieve the function that is being called. + pub fn function(&self) -> Option<&Function> { + unsafe { self.func.as_ref() } + } + + /// Attempt to retrieve the previous execute data on the call stack. + pub fn previous(&self) -> Option<&Self> { + unsafe { self.prev_execute_data.as_ref() } + } + /// Translation of macro `ZEND_CALL_ARG(call, n)` /// zend_compile.h:578 /// diff --git a/src/zend/function.rs b/src/zend/function.rs index ba889d7..9793e3e 100644 --- a/src/zend/function.rs +++ b/src/zend/function.rs @@ -2,7 +2,10 @@ use std::{fmt::Debug, os::raw::c_char, ptr}; -use crate::ffi::zend_function_entry; +use crate::{ + ffi::{zend_function, zend_function_entry}, + flags::FunctionType, +}; /// A Zend function entry. pub type FunctionEntry = zend_function_entry; @@ -36,3 +39,11 @@ impl FunctionEntry { Box::into_raw(Box::new(self)) } } + +pub type Function = zend_function; + +impl Function { + pub fn type_(&self) -> FunctionType { + FunctionType::from(unsafe { self.type_ }) + } +} From e7bbcfed5e45b9ed8ccc484c3f88a503a07b1055 Mon Sep 17 00:00:00 2001 From: Joe Hoyle Date: Wed, 19 Jul 2023 15:58:53 +0200 Subject: [PATCH 036/100] Add helper to get the sapi name Useful to determine if the context is CLI / FPM etc --- allowed_bindings.rs | 1 + docsrs_bindings.rs | 169 ++++++++++++++++++++++++++++++++++++++++++++ src/wrapper.h | 1 + src/zend/mod.rs | 11 ++- 4 files changed, 181 insertions(+), 1 deletion(-) diff --git a/allowed_bindings.rs b/allowed_bindings.rs index 7252bea..c715636 100644 --- a/allowed_bindings.rs +++ b/allowed_bindings.rs @@ -248,5 +248,6 @@ bind! { tsrm_get_ls_cache, executor_globals_offset, zend_atomic_bool_store, + sapi_module, zend_interrupt_function } diff --git a/docsrs_bindings.rs b/docsrs_bindings.rs index 1ba4175..7fb99ce 100644 --- a/docsrs_bindings.rs +++ b/docsrs_bindings.rs @@ -323,6 +323,28 @@ extern "C" { extern "C" { pub fn __zend_malloc(len: usize) -> *mut ::std::os::raw::c_void; } +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _zend_llist_element { + pub next: *mut _zend_llist_element, + pub prev: *mut _zend_llist_element, + pub data: [::std::os::raw::c_char; 1usize], +} +pub type zend_llist_element = _zend_llist_element; +pub type llist_dtor_func_t = + ::std::option::Option; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _zend_llist { + pub head: *mut zend_llist_element, + pub tail: *mut zend_llist_element, + pub count: usize, + pub size: usize, + pub dtor: llist_dtor_func_t, + pub persistent: ::std::os::raw::c_uchar, + pub traverse_ptr: *mut zend_llist_element, +} +pub type zend_llist = _zend_llist; pub type zend_string_init_interned_func_t = ::std::option::Option< unsafe extern "C" fn( str_: *const ::std::os::raw::c_char, @@ -473,6 +495,29 @@ pub struct _zend_class_arrayaccess_funcs { pub type zend_class_arrayaccess_funcs = _zend_class_arrayaccess_funcs; #[repr(C)] #[derive(Debug, Copy, Clone)] +pub struct stat { + pub st_dev: dev_t, + pub st_mode: mode_t, + pub st_nlink: nlink_t, + pub st_ino: __darwin_ino64_t, + pub st_uid: uid_t, + pub st_gid: gid_t, + pub st_rdev: dev_t, + pub st_atimespec: timespec, + pub st_mtimespec: timespec, + pub st_ctimespec: timespec, + pub st_birthtimespec: timespec, + pub st_size: off_t, + pub st_blocks: blkcnt_t, + pub st_blksize: blksize_t, + pub st_flags: __uint32_t, + pub st_gen: __uint32_t, + pub st_lspare: __int32_t, + pub st_qspare: [__int64_t; 2usize], +} +pub type zend_stat_t = stat; +#[repr(C)] +#[derive(Debug, Copy, Clone)] pub struct _zend_serialize_data { _unused: [u8; 0], } @@ -1514,3 +1559,127 @@ extern "C" { extern "C" { pub static mut zend_ce_stringable: *mut zend_class_entry; } +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct sapi_header_struct { + pub header: *mut ::std::os::raw::c_char, + pub header_len: usize, +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct sapi_headers_struct { + pub headers: zend_llist, + pub http_response_code: ::std::os::raw::c_int, + pub send_default_content_type: ::std::os::raw::c_uchar, + pub mimetype: *mut ::std::os::raw::c_char, + pub http_status_line: *mut ::std::os::raw::c_char, +} +pub type sapi_module_struct = _sapi_module_struct; +extern "C" { + pub static mut sapi_module: sapi_module_struct; +} +pub const sapi_header_op_enum_SAPI_HEADER_REPLACE: sapi_header_op_enum = 0; +pub const sapi_header_op_enum_SAPI_HEADER_ADD: sapi_header_op_enum = 1; +pub const sapi_header_op_enum_SAPI_HEADER_DELETE: sapi_header_op_enum = 2; +pub const sapi_header_op_enum_SAPI_HEADER_DELETE_ALL: sapi_header_op_enum = 3; +pub const sapi_header_op_enum_SAPI_HEADER_SET_STATUS: sapi_header_op_enum = 4; +pub type sapi_header_op_enum = ::std::os::raw::c_uint; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _sapi_module_struct { + pub name: *mut ::std::os::raw::c_char, + pub pretty_name: *mut ::std::os::raw::c_char, + pub startup: ::std::option::Option< + unsafe extern "C" fn(sapi_module: *mut _sapi_module_struct) -> ::std::os::raw::c_int, + >, + pub shutdown: ::std::option::Option< + unsafe extern "C" fn(sapi_module: *mut _sapi_module_struct) -> ::std::os::raw::c_int, + >, + pub activate: ::std::option::Option ::std::os::raw::c_int>, + pub deactivate: ::std::option::Option ::std::os::raw::c_int>, + pub ub_write: ::std::option::Option< + unsafe extern "C" fn(str_: *const ::std::os::raw::c_char, str_length: usize) -> usize, + >, + pub flush: + ::std::option::Option, + pub get_stat: ::std::option::Option *mut zend_stat_t>, + pub getenv: ::std::option::Option< + unsafe extern "C" fn( + name: *const ::std::os::raw::c_char, + name_len: usize, + ) -> *mut ::std::os::raw::c_char, + >, + pub sapi_error: ::std::option::Option< + unsafe extern "C" fn( + type_: ::std::os::raw::c_int, + error_msg: *const ::std::os::raw::c_char, + ... + ), + >, + pub header_handler: ::std::option::Option< + unsafe extern "C" fn( + sapi_header: *mut sapi_header_struct, + op: sapi_header_op_enum, + sapi_headers: *mut sapi_headers_struct, + ) -> ::std::os::raw::c_int, + >, + pub send_headers: ::std::option::Option< + unsafe extern "C" fn(sapi_headers: *mut sapi_headers_struct) -> ::std::os::raw::c_int, + >, + pub send_header: ::std::option::Option< + unsafe extern "C" fn( + sapi_header: *mut sapi_header_struct, + server_context: *mut ::std::os::raw::c_void, + ), + >, + pub read_post: ::std::option::Option< + unsafe extern "C" fn(buffer: *mut ::std::os::raw::c_char, count_bytes: usize) -> usize, + >, + pub read_cookies: ::std::option::Option *mut ::std::os::raw::c_char>, + pub register_server_variables: + ::std::option::Option, + pub log_message: ::std::option::Option< + unsafe extern "C" fn( + message: *const ::std::os::raw::c_char, + syslog_type_int: ::std::os::raw::c_int, + ), + >, + pub get_request_time: + ::std::option::Option zend_result>, + pub terminate_process: ::std::option::Option, + pub php_ini_path_override: *mut ::std::os::raw::c_char, + pub default_post_reader: ::std::option::Option, + pub treat_data: ::std::option::Option< + unsafe extern "C" fn( + arg: ::std::os::raw::c_int, + str_: *mut ::std::os::raw::c_char, + destArray: *mut zval, + ), + >, + pub executable_location: *mut ::std::os::raw::c_char, + pub php_ini_ignore: ::std::os::raw::c_int, + pub php_ini_ignore_cwd: ::std::os::raw::c_int, + pub get_fd: ::std::option::Option< + unsafe extern "C" fn(fd: *mut ::std::os::raw::c_int) -> ::std::os::raw::c_int, + >, + pub force_http_10: ::std::option::Option ::std::os::raw::c_int>, + pub get_target_uid: + ::std::option::Option ::std::os::raw::c_int>, + pub get_target_gid: + ::std::option::Option ::std::os::raw::c_int>, + pub input_filter: ::std::option::Option< + unsafe extern "C" fn( + arg: ::std::os::raw::c_int, + var: *const ::std::os::raw::c_char, + val: *mut *mut ::std::os::raw::c_char, + val_len: usize, + new_val_len: *mut usize, + ) -> ::std::os::raw::c_uint, + >, + pub ini_defaults: + ::std::option::Option, + pub phpinfo_as_text: ::std::os::raw::c_int, + pub ini_entries: *mut ::std::os::raw::c_char, + pub additional_functions: *const zend_function_entry, + pub input_filter_init: ::std::option::Option ::std::os::raw::c_uint>, +} diff --git a/src/wrapper.h b/src/wrapper.h index 2813263..02572c7 100644 --- a/src/wrapper.h +++ b/src/wrapper.h @@ -20,6 +20,7 @@ #include "zend_exceptions.h" #include "zend_inheritance.h" #include "zend_interfaces.h" +#include "SAPI.h" zend_string *ext_php_rs_zend_string_init(const char *str, size_t len, bool persistent); void ext_php_rs_zend_string_release(zend_string *zs); diff --git a/src/zend/mod.rs b/src/zend/mod.rs index 74547f0..b124f52 100644 --- a/src/zend/mod.rs +++ b/src/zend/mod.rs @@ -9,7 +9,10 @@ mod globals; mod handlers; mod module; -use crate::{error::Result, ffi::php_printf}; +use crate::{ + error::Result, + ffi::{php_printf, sapi_module}, +}; use std::ffi::CString; pub use _type::ZendType; @@ -42,3 +45,9 @@ pub fn printf(message: &str) -> Result<()> { }; Ok(()) } + +/// Get the name of the SAPI module. +pub fn php_sapi_name() -> String { + let c_str = unsafe { std::ffi::CStr::from_ptr(sapi_module.name) }; + c_str.to_str().expect("Unable to parse CStr").to_string() +} From acd336994c381c4a7c0f7571863863bd9df5fd75 Mon Sep 17 00:00:00 2001 From: Joe Hoyle Date: Wed, 19 Jul 2023 17:15:40 +0200 Subject: [PATCH 037/100] Add helper method to cancel VM interrupt (#260) Follow up to #257 --- src/zend/globals.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/zend/globals.rs b/src/zend/globals.rs index c9a7a0d..8b7a219 100644 --- a/src/zend/globals.rs +++ b/src/zend/globals.rs @@ -86,6 +86,19 @@ impl ExecutorGlobals { } } } + + /// Cancel a requested an interrupt of the PHP VM. + pub fn cancel_interrupt(&mut self) { + cfg_if::cfg_if! { + if #[cfg(php82)] { + unsafe { + zend_atomic_bool_store(&mut self.vm_interrupt, false); + } + } else { + self.vm_interrupt = true; + } + } + } } /// Executor globals rwlock. From 4cd4a20079400ad581851f051d49f65da7ea263f Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Mon, 21 Aug 2023 20:00:43 +0200 Subject: [PATCH 038/100] Cleanup --- crates/macros/src/method.rs | 3 ++- src/args.rs | 1 + src/types/callable.rs | 1 + src/types/zval.rs | 1 + 4 files changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/macros/src/method.rs b/crates/macros/src/method.rs index a1f8833..049e827 100644 --- a/crates/macros/src/method.rs +++ b/crates/macros/src/method.rs @@ -376,6 +376,7 @@ fn build_args( is_str = t.path.is_ident("str"); } } + } hack_tokens.append_all(if is_str { if is_option { quote! { let #param = #param.and_then(|__temp| Some(unsafe { ::core::mem::transmute::<&str, &'static str>(__temp) })); } @@ -389,7 +390,7 @@ fn build_args( quote! { let #param = unsafe { ::ext_php_rs::zend::borrow_unchecked(#param) }; } } }); - } + let default = defaults.get(&name); let mut ty = ty.ty.clone(); diff --git a/src/args.rs b/src/args.rs index ef32334..7cd3f62 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/types/callable.rs b/src/types/callable.rs index c1e446e..c22474b 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/zval.rs b/src/types/zval.rs index 12bff6a..721f354 100644 --- a/src/types/zval.rs +++ b/src/types/zval.rs @@ -262,6 +262,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) } From 725dfec0f207b81a68c07416ec807e855d8f62e9 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Sun, 27 Aug 2023 15:24:48 +0200 Subject: [PATCH 039/100] Cleanup --- crates/macros/src/method.rs | 68 +++---------------------------------- 1 file changed, 4 insertions(+), 64 deletions(-) diff --git a/crates/macros/src/method.rs b/crates/macros/src/method.rs index 049e827..7eb8a34 100644 --- a/crates/macros/src/method.rs +++ b/crates/macros/src/method.rs @@ -1,7 +1,7 @@ use anyhow::{anyhow, bail, Result}; use quote::ToTokens; use std::collections::HashMap; -use syn::{ReturnType, parse_quote, PathArguments, GenericArgument}; +use syn::ReturnType; use crate::helpers::get_docs; use crate::{ @@ -11,7 +11,6 @@ use crate::{ use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; use syn::{punctuated::Punctuated, FnArg, ImplItemMethod, Lit, Pat, Token, Type}; -use quote::TokenStreamExt; #[derive(Debug, Clone)] pub enum Arg { @@ -137,9 +136,8 @@ pub fn parser( } else { quote! { return; } }; - let mut hack_tokens = quote! {}; let internal_ident = Ident::new(&format!("_internal_php_{ident}"), Span::call_site()); - let args = build_args(struct_ty, &mut input.sig.inputs, &defaults, &mut hack_tokens)?; + let args = build_args(struct_ty, &mut input.sig.inputs, &defaults)?; let optional = function::find_optional_parameter( args.iter().filter_map(|arg| match arg { Arg::Typed(arg) => Some(arg), @@ -177,25 +175,6 @@ pub fn parser( } } } else { - let mut input = input.clone(); - if input.sig.asyncness.is_some() { - input.sig.asyncness = None; - let stmts = input.block; - let this = match method_type { - MethodType::Receiver { mutable } => if mutable { - quote! { let this = unsafe { std::mem::transmute::<&mut Self, &'static mut Self>(self) }; } - } else { - quote! { let this = unsafe { std::mem::transmute::<&Self, &'static Self>(self) }; } - }, - MethodType::ReceiverClassObject | MethodType::Static => quote! { }, - }; - input.block = parse_quote! {{ - #this - #hack_tokens - - ::ext_php_rs::zend::EventLoop::suspend_on(async move #stmts) - }}; - } let this = match method_type { MethodType::Receiver { .. } => quote! { this. }, MethodType::ReceiverClassObject | MethodType::Static => quote! { Self:: }, @@ -322,7 +301,6 @@ fn build_args( struct_ty: &Type, inputs: &mut Punctuated, defaults: &HashMap, - hack_tokens: &mut TokenStream ) -> Result> { inputs .iter_mut() @@ -350,48 +328,10 @@ fn build_args( if this { Ok(Arg::Receiver(MethodType::ReceiverClassObject)) } else { - let param = match &*ty.pat { - Pat::Ident(pat) => &pat.ident, + let name = match &*ty.pat { + Pat::Ident(pat) => pat.ident.to_string(), _ => bail!("Invalid parameter type."), }; - let name = param.to_string(); - - let mut ty_inner = &*ty.ty; - let mut is_option = false; - - if let Type::Path(t) = ty_inner { - if t.path.segments[0].ident.to_string() == "Option" { - if let PathArguments::AngleBracketed(t) = &t.path.segments[0].arguments { - if let GenericArgument::Type(t) = &t.args[0] { - ty_inner = t; - is_option = true; - } - } - } - } - let mut is_str = false; - if let Type::Reference(t) = ty_inner { - if t.mutability.is_none() { - if let Type::Path(t) = &*t.elem { - is_str = t.path.is_ident("str"); - } - } - } - hack_tokens.append_all(if is_str { - if is_option { - quote! { let #param = #param.and_then(|__temp| Some(unsafe { ::core::mem::transmute::<&str, &'static str>(__temp) })); } - } else { - quote! { let #param = unsafe { ::core::mem::transmute::<&str, &'static str>(#param) }; } - } - } else { - if is_option { - quote! { let #param = #param.and_then(|__temp| Some(unsafe { ::ext_php_rs::zend::borrow_unchecked(__temp) })); } - } else { - quote! { let #param = unsafe { ::ext_php_rs::zend::borrow_unchecked(#param) }; } - } - }); - - let default = defaults.get(&name); let mut ty = ty.ty.clone(); replace_self(struct_ty, &mut ty); From 253255b4703ce930e787e3d436a0dad6d2017a99 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Sun, 27 Aug 2023 16:26:31 +0200 Subject: [PATCH 040/100] Move async stuff to danog/php-tokio --- src/zend/borrow_unchecked.rs | 52 --------------- src/zend/fibers.rs | 126 ----------------------------------- src/zend/mod.rs | 6 -- 3 files changed, 184 deletions(-) delete mode 100644 src/zend/borrow_unchecked.rs delete mode 100644 src/zend/fibers.rs diff --git a/src/zend/borrow_unchecked.rs b/src/zend/borrow_unchecked.rs deleted file mode 100644 index 46fd449..0000000 --- a/src/zend/borrow_unchecked.rs +++ /dev/null @@ -1,52 +0,0 @@ -use crate::binary_slice::{BinarySlice, PackSlice}; - -#[inline(always)] -pub unsafe fn borrow_unchecked< - 'original, - 'unbounded, - Ref: BorrowUnchecked<'original, 'unbounded>, ->( - reference: Ref, -) -> Ref::Unbounded { - unsafe { BorrowUnchecked::borrow_unchecked(reference) } -} - -#[doc(hidden)] -pub unsafe trait BorrowUnchecked<'original, 'unbounded> { - type Unbounded; - - unsafe fn borrow_unchecked(self) -> Self::Unbounded; -} - -unsafe impl<'original, 'unbounded, T: 'unbounded> BorrowUnchecked<'original, 'unbounded> - for &'original T -{ - type Unbounded = &'unbounded T; - - #[inline(always)] - unsafe fn borrow_unchecked(self) -> Self::Unbounded { - unsafe { ::core::mem::transmute(self) } - } -} - -unsafe impl<'original, 'unbounded, T: 'unbounded> BorrowUnchecked<'original, 'unbounded> - for &'original mut T -{ - type Unbounded = &'unbounded mut T; - - #[inline(always)] - unsafe fn borrow_unchecked(self) -> Self::Unbounded { - unsafe { ::core::mem::transmute(self) } - } -} - -unsafe impl<'original, 'unbounded, T: 'unbounded + PackSlice> BorrowUnchecked<'original, 'unbounded> - for BinarySlice<'original, T> -{ - type Unbounded = BinarySlice<'unbounded, T>; - - #[inline(always)] - unsafe fn borrow_unchecked(self) -> Self::Unbounded { - unsafe { ::core::mem::transmute(self) } - } -} \ No newline at end of file diff --git a/src/zend/fibers.rs b/src/zend/fibers.rs deleted file mode 100644 index b283eb8..0000000 --- a/src/zend/fibers.rs +++ /dev/null @@ -1,126 +0,0 @@ - -use crate::boxed::ZBox; -use crate::prelude::PhpResult; -use crate::types::ZendHashTable; -use crate::zend::Function; - -use std::cell::RefCell; -use std::fs::File; -use std::future::Future; -use std::io::{self, Write}; -use std::os::fd::{RawFd, FromRawFd}; -use std::sync::mpsc::{Sender, Receiver, channel}; -use std::io::Read; -use lazy_static::lazy_static; -use tokio::runtime::Runtime; -use std::os::fd::AsRawFd; - -use super::{borrow_unchecked}; - -lazy_static! { - pub static ref RUNTIME: Runtime = Runtime::new().expect("Could not allocate runtime"); -} - -thread_local! { - static EVENTLOOP: RefCell> = RefCell::new(None); -} - -#[cfg(any(target_os = "linux", target_os = "solaris"))] -fn sys_pipe() -> io::Result<(RawFd, RawFd)> { - let mut pipefd = [0; 2]; - let ret = unsafe { libc::pipe2(pipefd.as_mut_ptr(), libc::O_CLOEXEC | libc::O_NONBLOCK) }; - if ret == -1 { - return Err(io::Error::last_os_error()); - } - Ok((pipefd[0], pipefd[1])) -} - -pub struct EventLoop { - fibers: ZBox, - - sender: Sender, - receiver: Receiver, - - notify_sender: File, - notify_receiver: File, - - get_current_suspension: Function, - - dummy: [u8; 1], -} - -impl EventLoop { - pub fn init() -> PhpResult { - EVENTLOOP.with_borrow_mut(|e| { - Ok( - match e { - None => e.insert(Self::new()?), - Some(ev) => ev - }.notify_receiver.as_raw_fd() as u64 - ) - }) - } - - pub fn suspend_on + Send + 'static>(future: F) -> T { - let (future, get_current_suspension) = EVENTLOOP.with_borrow_mut(move |c| { - let c = c.as_mut().unwrap(); - let idx = c.fibers.len() as u64; - c.fibers.insert_at_index(idx, call_user_func!(c.get_current_suspension).unwrap()).unwrap(); - - let sender = c.sender.clone(); - let mut notifier = c.notify_sender.try_clone().unwrap(); - - (RUNTIME.spawn(async move { - let res = future.await; - sender.send(idx).unwrap(); - notifier.write_all(&[0]).unwrap(); - res - }), unsafe { - borrow_unchecked(&c.get_current_suspension) - }) - }); - - call_user_func!(get_current_suspension).unwrap().try_call_method("suspend", vec![]).unwrap(); - - return RUNTIME.block_on(future).unwrap(); - } - - pub fn wakeup() -> PhpResult<()> { - EVENTLOOP.with_borrow_mut(|c| { - let c = c.as_mut().unwrap(); - - c.notify_receiver.read_exact(&mut c.dummy).unwrap(); - - for fiber_id in c.receiver.try_iter() { - if let Some(fiber) = c.fibers.get_index_mut(fiber_id) { - fiber.object_mut().unwrap().try_call_method("resume", vec![])?; - c.fibers.remove_index(fiber_id); - } - } - Ok(()) - }) - } - - pub fn shutdown() { - EVENTLOOP.set(None) - } - - fn new() -> PhpResult { - let (sender, receiver) = channel(); - let (notify_receiver, notify_sender) = - sys_pipe().map_err(|err| format!("Could not create pipe: {}", err))?; - - call_user_func!(Function::from_function("class_exists"), "\\Revolt\\EventLoop")?; - call_user_func!(Function::from_function("interface_exists"), "\\Revolt\\EventLoop\\Suspension")?; - - Ok(Self { - fibers: ZendHashTable::new(), - sender: sender, - receiver: receiver, - notify_sender: unsafe { File::from_raw_fd(notify_sender) }, - notify_receiver: unsafe { File::from_raw_fd(notify_receiver) }, - dummy: [0; 1], - get_current_suspension: Function::try_from_method("\\Revolt\\EventLoop", "getSuspension").ok_or("\\Revolt\\EventLoop::getSuspension does not exist")?, - }) - } -} \ No newline at end of file diff --git a/src/zend/mod.rs b/src/zend/mod.rs index 7757d23..89d1df5 100644 --- a/src/zend/mod.rs +++ b/src/zend/mod.rs @@ -8,8 +8,6 @@ mod function; mod globals; mod handlers; mod module; -mod fibers; -mod borrow_unchecked; use crate::{error::Result, ffi::php_printf}; use std::ffi::CString; @@ -20,10 +18,6 @@ pub use ex::ExecuteData; pub use function::FunctionEntry; pub use function::Function; pub use globals::ExecutorGlobals; -pub use fibers::RUNTIME; -pub use fibers::EventLoop; -pub use borrow_unchecked::borrow_unchecked; -pub use borrow_unchecked::BorrowUnchecked; pub use handlers::ZendObjectHandlers; pub use module::ModuleEntry; From 4b821715b18506be51771483d909fe5934108831 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Sun, 27 Aug 2023 17:17:42 +0200 Subject: [PATCH 041/100] Update docs --- Cargo.toml | 3 --- guide/src/SUMMARY.md | 1 + guide/src/types/functions.md | 30 ++++++++++++++++++++++++++++++ guide/src/types/object.md | 20 ++++++++++++++++++++ 4 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 guide/src/types/functions.md diff --git a/Cargo.toml b/Cargo.toml index dd22923..2219cba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,9 +18,6 @@ cfg-if = "1.0" once_cell = "1.17" anyhow = { version = "1", optional = true } ext-php-rs-derive = { version = "=0.10.0", path = "./crates/macros" } -tokio = { version = "1", features = ["full"] } -lazy_static = "1.4.0" -libc = "*" [dev-dependencies] skeptic = "0.13" diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md index f725964..cfbc003 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 0000000..ea2f42d --- /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 substr = Function::from_function("var_dump"); + let _ = substr.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 c953d99..abde18d 100644 --- a/guide/src/types/object.md +++ b/guide/src/types/object.md @@ -15,6 +15,26 @@ 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) -> &mut ZendObject { + let res = obj.try_call_method("hello", vec!["arg1", "arg2"]); + dbg!(res) +} +# #[php_module] +# pub fn get_module(module: ModuleBuilder) -> ModuleBuilder { +# module +# } +# fn main() {} +``` + ### Taking an object reference ```rust,no_run From 714b1555f83f813a5f7b6d5f5d81c6c400d7c935 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Sun, 27 Aug 2023 17:24:25 +0200 Subject: [PATCH 042/100] Fixes --- guide/src/types/functions.md | 6 +++--- guide/src/types/object.md | 5 ++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/guide/src/types/functions.md b/guide/src/types/functions.md index ea2f42d..c8d7b03 100644 --- a/guide/src/types/functions.md +++ b/guide/src/types/functions.md @@ -16,14 +16,14 @@ use ext_php_rs::zend::Function; #[php_function] pub fn test_function() -> () { - let substr = Function::from_function("var_dump"); - let _ = substr.try_call(vec!["abc"]); + 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"]); + let _ = f.try_call(vec![&"abc"]); } # fn main() {} diff --git a/guide/src/types/object.md b/guide/src/types/object.md index abde18d..57f9371 100644 --- a/guide/src/types/object.md +++ b/guide/src/types/object.md @@ -24,9 +24,8 @@ use ext_php_rs::{prelude::*, types::ZendObject}; // Take an object reference and also return it. #[php_function] -pub fn take_obj(obj: &mut ZendObject) -> &mut ZendObject { - let res = obj.try_call_method("hello", vec!["arg1", "arg2"]); - dbg!(res) +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 { From c87c0dcdb6f307036e72e5044e56789b42334b46 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Mon, 28 Aug 2023 09:55:35 +0200 Subject: [PATCH 043/100] Fix --- src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 1b45b3f..bc20ada 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,7 +5,6 @@ #![allow(non_snake_case)] #![cfg_attr(docs, feature(doc_cfg))] #![cfg_attr(windows, feature(abi_vectorcall))] -#![feature(thread_local, local_key_cell_methods)] pub mod alloc; pub mod args; From 891fa4ad3d308e99fa9c013a7ae27f9c4f0b4080 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Mon, 4 Sep 2023 10:48:06 +0200 Subject: [PATCH 044/100] Cargo fmt --- src/lib.rs | 2 +- src/types/object.rs | 14 +++++++------- src/types/zval.rs | 4 +++- src/zend/class.rs | 8 +++++++- src/zend/function.rs | 22 +++++++++++++++------- src/zend/globals.rs | 3 +-- src/zend/mod.rs | 2 +- 7 files changed, 35 insertions(+), 20 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index bc20ada..ba4c956 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,7 +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/object.rs b/src/types/object.rs index f48c205..ca8e526 100644 --- a/src/types/object.rs +++ b/src/types/object.rs @@ -9,8 +9,9 @@ use crate::{ 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, zend_hash_str_find_ptr_lc, zend_function, object_properties_init, + 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, @@ -49,8 +50,8 @@ impl ZendObject { } object_properties_init(ptr, ce as *const _ as *mut _); ptr - }, - Some(v) => v(ce as *const _ as *mut _) + } + Some(v) => v(ce as *const _ as *mut _), }; ZBox::from_raw( @@ -132,7 +133,6 @@ 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(); @@ -147,10 +147,10 @@ impl ZendObject { let res = zend_hash_str_find_ptr_lc( &(*self.ce).function_table, name.as_ptr() as *const i8, - name.len() + name.len(), ) as *mut zend_function; if res.is_null() { - return Err(Error::Callable) + return Err(Error::Callable); } zend_call_known_function( res, diff --git a/src/types/zval.rs b/src/types/zval.rs index 721f354..94b62aa 100644 --- a/src/types/zval.rs +++ b/src/types/zval.rs @@ -208,7 +208,9 @@ 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) + self.object() + .ok_or(Error::Object)? + .try_call_method(name, params) } /// Returns the value of the zval if it is a reference. diff --git a/src/zend/class.rs b/src/zend/class.rs index 5cb6ec0..b54ad4c 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, ZendObject}, zend::ExecutorGlobals, boxed::ZBox}; +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. diff --git a/src/zend/function.rs b/src/zend/function.rs index 93f1903..9321e1a 100644 --- a/src/zend/function.rs +++ b/src/zend/function.rs @@ -1,8 +1,16 @@ //! Builder for creating functions and methods in PHP. -use std::{fmt::Debug, os::raw::c_char, ptr::self}; +use std::{fmt::Debug, os::raw::c_char, ptr}; -use crate::{ffi::{zend_function_entry, zend_fetch_function_str, zend_function, zend_hash_str_find_ptr_lc, zend_call_known_function}, convert::IntoZvalDyn, types::Zval, error::Result}; +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; @@ -58,14 +66,14 @@ impl Function { let res = zend_hash_str_find_ptr_lc( &ce.function_table, name.as_ptr() as *const i8, - name.len() + name.len(), ) as *mut zend_function; if res.is_null() { - return None + return None; } - return Some(*res) - } - } + return Some(*res); + }, + }; } pub fn from_function(name: &str) -> Self { diff --git a/src/zend/globals.rs b/src/zend/globals.rs index 6843dfd..8b7a219 100644 --- a/src/zend/globals.rs +++ b/src/zend/globals.rs @@ -4,7 +4,6 @@ use std::ops::{Deref, DerefMut}; use parking_lot::{const_rwlock, RwLock, RwLockReadGuard, RwLockWriteGuard}; - use crate::boxed::ZBox; #[cfg(php82)] use crate::ffi::zend_atomic_bool_store; @@ -144,4 +143,4 @@ impl DerefMut for GlobalWriteGuard { fn deref_mut(&mut self) -> &mut Self::Target { self.globals } -} \ No newline at end of file +} diff --git a/src/zend/mod.rs b/src/zend/mod.rs index 89d1df5..872f65b 100644 --- a/src/zend/mod.rs +++ b/src/zend/mod.rs @@ -15,8 +15,8 @@ use std::ffi::CString; pub use _type::ZendType; pub use class::ClassEntry; pub use ex::ExecuteData; -pub use function::FunctionEntry; pub use function::Function; +pub use function::FunctionEntry; pub use globals::ExecutorGlobals; pub use handlers::ZendObjectHandlers; pub use module::ModuleEntry; From e1672beb98e849c8185037a172996f612553986e Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Tue, 10 Oct 2023 12:43:22 +0300 Subject: [PATCH 045/100] Update bindings --- docsrs_bindings.rs | 80 +++++++++++++++++----------------------------- 1 file changed, 30 insertions(+), 50 deletions(-) diff --git a/docsrs_bindings.rs b/docsrs_bindings.rs index 28341d5..73f4f1b 100644 --- a/docsrs_bindings.rs +++ b/docsrs_bindings.rs @@ -1,6 +1,6 @@ /* automatically generated by rust-bindgen 0.65.1 */ -pub const ZEND_DEBUG: u32 = 0; +pub const ZEND_DEBUG: u32 = 1; pub const _ZEND_TYPE_NAME_BIT: u32 = 16777216; pub const _ZEND_TYPE_NULLABLE_BIT: u32 = 2; pub const HT_MIN_SIZE: u32 = 8; @@ -69,6 +69,7 @@ pub const ZEND_ACC_USE_GUARDS: u32 = 2048; pub const ZEND_ACC_CONSTANTS_UPDATED: u32 = 4096; pub const ZEND_ACC_NO_DYNAMIC_PROPERTIES: u32 = 8192; pub const ZEND_HAS_STATIC_IN_METHODS: u32 = 16384; +pub const ZEND_ACC_REUSE_GET_ITERATOR: u32 = 65536; pub const ZEND_ACC_RESOLVED_PARENT: u32 = 131072; pub const ZEND_ACC_RESOLVED_INTERFACES: u32 = 262144; pub const ZEND_ACC_UNRESOLVED_VARIANCE: u32 = 524288; @@ -97,7 +98,7 @@ pub const ZEND_EVAL_CODE: u32 = 4; pub const ZEND_ISEMPTY: u32 = 1; pub const _ZEND_SEND_MODE_SHIFT: u32 = 25; pub const _ZEND_IS_VARIADIC_BIT: u32 = 134217728; -pub const ZEND_MODULE_API_NO: u32 = 20220829; +pub const ZEND_MODULE_API_NO: u32 = 20210902; pub const USING_ZTS: u32 = 0; pub const MAY_BE_BOOL: u32 = 12; pub const MAY_BE_ANY: u32 = 1022; @@ -236,7 +237,7 @@ pub struct _zend_array { pub gc: zend_refcounted_h, pub u: _zend_array__bindgen_ty_1, pub nTableMask: u32, - pub __bindgen_anon_1: _zend_array__bindgen_ty_2, + pub arData: *mut Bucket, pub nNumUsed: u32, pub nNumOfElements: u32, pub nTableSize: u32, @@ -258,13 +259,6 @@ pub struct _zend_array__bindgen_ty_1__bindgen_ty_1 { pub nIteratorsCount: zend_uchar, pub _unused2: zend_uchar, } -#[repr(C)] -#[derive(Copy, Clone)] -pub union _zend_array__bindgen_ty_2 { - pub arHash: *mut u32, - pub arData: *mut Bucket, - pub arPacked: *mut zval, -} pub type HashPosition = u32; #[repr(C)] #[derive(Debug, Copy, Clone)] @@ -308,10 +302,22 @@ pub struct _zend_ast_ref { pub gc: zend_refcounted_h, } extern "C" { - pub fn _emalloc(size: usize) -> *mut ::std::os::raw::c_void; + pub fn _emalloc( + size: usize, + __zend_filename: *const ::std::os::raw::c_char, + __zend_lineno: u32, + __zend_orig_filename: *const ::std::os::raw::c_char, + __zend_orig_lineno: u32, + ) -> *mut ::std::os::raw::c_void; } extern "C" { - pub fn _efree(ptr: *mut ::std::os::raw::c_void); + pub fn _efree( + ptr: *mut ::std::os::raw::c_void, + __zend_filename: *const ::std::os::raw::c_char, + __zend_lineno: u32, + __zend_orig_filename: *const ::std::os::raw::c_char, + __zend_orig_lineno: u32, + ); } extern "C" { pub fn __zend_malloc(len: usize) -> *mut ::std::os::raw::c_void; @@ -374,7 +380,7 @@ extern "C" { pub fn zend_hash_get_current_key_zval_ex( ht: *const HashTable, key: *mut zval, - pos: *const HashPosition, + pos: *mut HashPosition, ); } extern "C" { @@ -464,15 +470,6 @@ pub struct _zend_class_iterator_funcs { pub type zend_class_iterator_funcs = _zend_class_iterator_funcs; #[repr(C)] #[derive(Debug, Copy, Clone)] -pub struct _zend_class_arrayaccess_funcs { - pub zf_offsetget: *mut zend_function, - pub zf_offsetexists: *mut zend_function, - pub zf_offsetset: *mut zend_function, - pub zf_offsetunset: *mut zend_function, -} -pub type zend_class_arrayaccess_funcs = _zend_class_arrayaccess_funcs; -#[repr(C)] -#[derive(Debug, Copy, Clone)] pub struct _zend_serialize_data { _unused: [u8; 0], } @@ -521,7 +518,6 @@ pub struct _zend_class_mutable_data { pub default_properties_table: *mut zval, pub constants_table: *mut HashTable, pub ce_flags: u32, - pub backed_enum_table: *mut HashTable, } pub type zend_class_mutable_data = _zend_class_mutable_data; #[repr(C)] @@ -564,11 +560,11 @@ pub struct _zend_class_entry { pub default_static_members_count: ::std::os::raw::c_int, pub default_properties_table: *mut zval, pub default_static_members_table: *mut zval, - pub static_members_table__ptr: *mut zval, + pub static_members_table__ptr: *mut *mut zval, pub function_table: HashTable, pub properties_info: HashTable, pub constants_table: HashTable, - pub mutable_data__ptr: *mut zend_class_mutable_data, + pub mutable_data__ptr: *mut *mut zend_class_mutable_data, pub inheritance_cache: *mut zend_inheritance_cache_entry, pub properties_info_table: *mut *mut _zend_property_info, pub constructor: *mut zend_function, @@ -585,7 +581,6 @@ pub struct _zend_class_entry { pub __serialize: *mut zend_function, pub __unserialize: *mut zend_function, pub iterator_funcs_ptr: *mut zend_class_iterator_funcs, - pub arrayaccess_funcs_ptr: *mut zend_class_arrayaccess_funcs, pub __bindgen_anon_2: _zend_class_entry__bindgen_ty_2, pub get_iterator: ::std::option::Option< unsafe extern "C" fn( @@ -787,10 +782,10 @@ pub type zend_object_cast_t = ::std::option::Option< readobj: *mut zend_object, retval: *mut zval, type_: ::std::os::raw::c_int, - ) -> zend_result, + ) -> ::std::os::raw::c_int, >; pub type zend_object_count_elements_t = ::std::option::Option< - unsafe extern "C" fn(object: *mut zend_object, count: *mut zend_long) -> zend_result, + unsafe extern "C" fn(object: *mut zend_object, count: *mut zend_long) -> ::std::os::raw::c_int, >; pub type zend_object_get_closure_t = ::std::option::Option< unsafe extern "C" fn( @@ -799,7 +794,7 @@ pub type zend_object_get_closure_t = ::std::option::Option< fptr_ptr: *mut *mut zend_function, obj_ptr: *mut *mut zend_object, check_only: bool, - ) -> zend_result, + ) -> ::std::os::raw::c_int, >; pub type zend_object_get_gc_t = ::std::option::Option< unsafe extern "C" fn( @@ -814,7 +809,7 @@ pub type zend_object_do_operation_t = ::std::option::Option< result: *mut zval, op1: *mut zval, op2: *mut zval, - ) -> zend_result, + ) -> ::std::os::raw::c_int, >; #[repr(C)] #[derive(Debug, Copy, Clone)] @@ -965,13 +960,13 @@ pub struct _zend_op_array { pub required_num_args: u32, pub arg_info: *mut zend_arg_info, pub attributes: *mut HashTable, - pub T: u32, - pub run_time_cache__ptr: *mut *mut ::std::os::raw::c_void, pub cache_size: ::std::os::raw::c_int, pub last_var: ::std::os::raw::c_int, + pub T: u32, pub last: u32, pub opcodes: *mut zend_op, - pub static_variables_ptr__ptr: *mut HashTable, + pub run_time_cache__ptr: *mut *mut *mut ::std::os::raw::c_void, + pub static_variables_ptr__ptr: *mut *mut HashTable, pub static_variables: *mut HashTable, pub vars: *mut *mut zend_string, pub refcount: *mut u32, @@ -1005,8 +1000,6 @@ pub struct _zend_internal_function { pub required_num_args: u32, pub arg_info: *mut zend_internal_arg_info, pub attributes: *mut HashTable, - pub T: u32, - pub run_time_cache__ptr: *mut *mut ::std::os::raw::c_void, pub handler: zif_handler, pub module: *mut _zend_module_entry, pub reserved: [*mut ::std::os::raw::c_void; 6usize], @@ -1034,8 +1027,6 @@ pub struct _zend_function__bindgen_ty_1 { pub required_num_args: u32, pub arg_info: *mut zend_arg_info, pub attributes: *mut HashTable, - pub T: u32, - pub run_time_cache__ptr: *mut *mut ::std::os::raw::c_void, } #[repr(C)] pub struct _zend_execute_data { @@ -1064,15 +1055,6 @@ extern "C" { } #[repr(C)] #[derive(Debug, Copy, Clone)] -pub struct zend_atomic_bool_s { - pub value: u8, -} -pub type zend_atomic_bool = zend_atomic_bool_s; -extern "C" { - pub fn zend_atomic_bool_store(obj: *mut zend_atomic_bool, desired: bool); -} -#[repr(C)] -#[derive(Debug, Copy, Clone)] pub struct _zend_stack { pub size: ::std::os::raw::c_int, pub top: ::std::os::raw::c_int, @@ -1148,8 +1130,8 @@ pub struct _zend_executor_globals { pub in_autoload: *mut HashTable, pub full_tables_cleanup: bool, pub no_extensions: bool, - pub vm_interrupt: zend_atomic_bool, - pub timed_out: zend_atomic_bool, + pub vm_interrupt: bool, + pub timed_out: bool, pub hard_timeout: zend_long, pub regular_list: HashTable, pub persistent_list: HashTable, @@ -1193,8 +1175,6 @@ pub struct _zend_executor_globals { pub record_errors: bool, pub num_errors: u32, pub errors: *mut *mut zend_error_info, - pub filename_override: *mut zend_string, - pub lineno_override: zend_long, pub reserved: [*mut ::std::os::raw::c_void; 6usize], } pub type zend_module_entry = _zend_module_entry; From e820b2458aa5a425f8f135e4e71a451184c9392a Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Tue, 10 Oct 2023 14:55:40 +0300 Subject: [PATCH 046/100] Fix --- docsrs_bindings.rs | 62 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 47 insertions(+), 15 deletions(-) diff --git a/docsrs_bindings.rs b/docsrs_bindings.rs index 73f4f1b..351ab9d 100644 --- a/docsrs_bindings.rs +++ b/docsrs_bindings.rs @@ -69,7 +69,6 @@ pub const ZEND_ACC_USE_GUARDS: u32 = 2048; pub const ZEND_ACC_CONSTANTS_UPDATED: u32 = 4096; pub const ZEND_ACC_NO_DYNAMIC_PROPERTIES: u32 = 8192; pub const ZEND_HAS_STATIC_IN_METHODS: u32 = 16384; -pub const ZEND_ACC_REUSE_GET_ITERATOR: u32 = 65536; pub const ZEND_ACC_RESOLVED_PARENT: u32 = 131072; pub const ZEND_ACC_RESOLVED_INTERFACES: u32 = 262144; pub const ZEND_ACC_UNRESOLVED_VARIANCE: u32 = 524288; @@ -98,7 +97,7 @@ pub const ZEND_EVAL_CODE: u32 = 4; pub const ZEND_ISEMPTY: u32 = 1; pub const _ZEND_SEND_MODE_SHIFT: u32 = 25; pub const _ZEND_IS_VARIADIC_BIT: u32 = 134217728; -pub const ZEND_MODULE_API_NO: u32 = 20210902; +pub const ZEND_MODULE_API_NO: u32 = 20220829; pub const USING_ZTS: u32 = 0; pub const MAY_BE_BOOL: u32 = 12; pub const MAY_BE_ANY: u32 = 1022; @@ -237,7 +236,7 @@ pub struct _zend_array { pub gc: zend_refcounted_h, pub u: _zend_array__bindgen_ty_1, pub nTableMask: u32, - pub arData: *mut Bucket, + pub __bindgen_anon_1: _zend_array__bindgen_ty_2, pub nNumUsed: u32, pub nNumOfElements: u32, pub nTableSize: u32, @@ -259,6 +258,13 @@ pub struct _zend_array__bindgen_ty_1__bindgen_ty_1 { pub nIteratorsCount: zend_uchar, pub _unused2: zend_uchar, } +#[repr(C)] +#[derive(Copy, Clone)] +pub union _zend_array__bindgen_ty_2 { + pub arHash: *mut u32, + pub arData: *mut Bucket, + pub arPacked: *mut zval, +} pub type HashPosition = u32; #[repr(C)] #[derive(Debug, Copy, Clone)] @@ -380,7 +386,7 @@ extern "C" { pub fn zend_hash_get_current_key_zval_ex( ht: *const HashTable, key: *mut zval, - pos: *mut HashPosition, + pos: *const HashPosition, ); } extern "C" { @@ -470,6 +476,15 @@ pub struct _zend_class_iterator_funcs { pub type zend_class_iterator_funcs = _zend_class_iterator_funcs; #[repr(C)] #[derive(Debug, Copy, Clone)] +pub struct _zend_class_arrayaccess_funcs { + pub zf_offsetget: *mut zend_function, + pub zf_offsetexists: *mut zend_function, + pub zf_offsetset: *mut zend_function, + pub zf_offsetunset: *mut zend_function, +} +pub type zend_class_arrayaccess_funcs = _zend_class_arrayaccess_funcs; +#[repr(C)] +#[derive(Debug, Copy, Clone)] pub struct _zend_serialize_data { _unused: [u8; 0], } @@ -518,6 +533,7 @@ pub struct _zend_class_mutable_data { pub default_properties_table: *mut zval, pub constants_table: *mut HashTable, pub ce_flags: u32, + pub backed_enum_table: *mut HashTable, } pub type zend_class_mutable_data = _zend_class_mutable_data; #[repr(C)] @@ -560,11 +576,11 @@ pub struct _zend_class_entry { pub default_static_members_count: ::std::os::raw::c_int, pub default_properties_table: *mut zval, pub default_static_members_table: *mut zval, - pub static_members_table__ptr: *mut *mut zval, + pub static_members_table__ptr: *mut zval, pub function_table: HashTable, pub properties_info: HashTable, pub constants_table: HashTable, - pub mutable_data__ptr: *mut *mut zend_class_mutable_data, + pub mutable_data__ptr: *mut zend_class_mutable_data, pub inheritance_cache: *mut zend_inheritance_cache_entry, pub properties_info_table: *mut *mut _zend_property_info, pub constructor: *mut zend_function, @@ -581,6 +597,7 @@ pub struct _zend_class_entry { pub __serialize: *mut zend_function, pub __unserialize: *mut zend_function, pub iterator_funcs_ptr: *mut zend_class_iterator_funcs, + pub arrayaccess_funcs_ptr: *mut zend_class_arrayaccess_funcs, pub __bindgen_anon_2: _zend_class_entry__bindgen_ty_2, pub get_iterator: ::std::option::Option< unsafe extern "C" fn( @@ -782,10 +799,10 @@ pub type zend_object_cast_t = ::std::option::Option< readobj: *mut zend_object, retval: *mut zval, type_: ::std::os::raw::c_int, - ) -> ::std::os::raw::c_int, + ) -> zend_result, >; pub type zend_object_count_elements_t = ::std::option::Option< - unsafe extern "C" fn(object: *mut zend_object, count: *mut zend_long) -> ::std::os::raw::c_int, + unsafe extern "C" fn(object: *mut zend_object, count: *mut zend_long) -> zend_result, >; pub type zend_object_get_closure_t = ::std::option::Option< unsafe extern "C" fn( @@ -794,7 +811,7 @@ pub type zend_object_get_closure_t = ::std::option::Option< fptr_ptr: *mut *mut zend_function, obj_ptr: *mut *mut zend_object, check_only: bool, - ) -> ::std::os::raw::c_int, + ) -> zend_result, >; pub type zend_object_get_gc_t = ::std::option::Option< unsafe extern "C" fn( @@ -809,7 +826,7 @@ pub type zend_object_do_operation_t = ::std::option::Option< result: *mut zval, op1: *mut zval, op2: *mut zval, - ) -> ::std::os::raw::c_int, + ) -> zend_result, >; #[repr(C)] #[derive(Debug, Copy, Clone)] @@ -960,13 +977,13 @@ pub struct _zend_op_array { pub required_num_args: u32, pub arg_info: *mut zend_arg_info, pub attributes: *mut HashTable, + pub T: u32, + pub run_time_cache__ptr: *mut *mut ::std::os::raw::c_void, pub cache_size: ::std::os::raw::c_int, pub last_var: ::std::os::raw::c_int, - pub T: u32, pub last: u32, pub opcodes: *mut zend_op, - pub run_time_cache__ptr: *mut *mut *mut ::std::os::raw::c_void, - pub static_variables_ptr__ptr: *mut *mut HashTable, + pub static_variables_ptr__ptr: *mut HashTable, pub static_variables: *mut HashTable, pub vars: *mut *mut zend_string, pub refcount: *mut u32, @@ -1000,6 +1017,8 @@ pub struct _zend_internal_function { pub required_num_args: u32, pub arg_info: *mut zend_internal_arg_info, pub attributes: *mut HashTable, + pub T: u32, + pub run_time_cache__ptr: *mut *mut ::std::os::raw::c_void, pub handler: zif_handler, pub module: *mut _zend_module_entry, pub reserved: [*mut ::std::os::raw::c_void; 6usize], @@ -1027,6 +1046,8 @@ pub struct _zend_function__bindgen_ty_1 { pub required_num_args: u32, pub arg_info: *mut zend_arg_info, pub attributes: *mut HashTable, + pub T: u32, + pub run_time_cache__ptr: *mut *mut ::std::os::raw::c_void, } #[repr(C)] pub struct _zend_execute_data { @@ -1055,6 +1076,15 @@ extern "C" { } #[repr(C)] #[derive(Debug, Copy, Clone)] +pub struct zend_atomic_bool_s { + pub value: u8, +} +pub type zend_atomic_bool = zend_atomic_bool_s; +extern "C" { + pub fn zend_atomic_bool_store(obj: *mut zend_atomic_bool, desired: bool); +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] pub struct _zend_stack { pub size: ::std::os::raw::c_int, pub top: ::std::os::raw::c_int, @@ -1130,8 +1160,8 @@ pub struct _zend_executor_globals { pub in_autoload: *mut HashTable, pub full_tables_cleanup: bool, pub no_extensions: bool, - pub vm_interrupt: bool, - pub timed_out: bool, + pub vm_interrupt: zend_atomic_bool, + pub timed_out: zend_atomic_bool, pub hard_timeout: zend_long, pub regular_list: HashTable, pub persistent_list: HashTable, @@ -1175,6 +1205,8 @@ pub struct _zend_executor_globals { pub record_errors: bool, pub num_errors: u32, pub errors: *mut *mut zend_error_info, + pub filename_override: *mut zend_string, + pub lineno_override: zend_long, pub reserved: [*mut ::std::os::raw::c_void; 6usize], } pub type zend_module_entry = _zend_module_entry; From 85836dad04e2b30ad7476c1dcf823015c6c882cb Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Tue, 10 Oct 2023 15:07:44 +0300 Subject: [PATCH 047/100] Fix --- src/describe/abi.rs | 2 +- src/describe/stub.rs | 2 +- src/types/array.rs | 4 ++-- src/zend/class.rs | 1 + src/zend/function.rs | 8 ++++---- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/describe/abi.rs b/src/describe/abi.rs index a4e5885..1176710 100644 --- a/src/describe/abi.rs +++ b/src/describe/abi.rs @@ -32,7 +32,7 @@ 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 65c0e76..065b275 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/types/array.rs b/src/types/array.rs index c9f7b41..c40b8f3 100644 --- a/src/types/array.rs +++ b/src/types/array.rs @@ -768,7 +768,7 @@ impl<'a> FromZval<'a> for &'a ZendHashTable { } /////////////////////////////////////////// -//// HashMap +/// HashMap /////////////////////////////////////////// impl<'a, V> TryFrom<&'a ZendHashTable> for HashMap @@ -837,7 +837,7 @@ where } /////////////////////////////////////////// -//// Vec +/// Vec /////////////////////////////////////////// impl<'a, T> TryFrom<&'a ZendHashTable> for Vec diff --git a/src/zend/class.rs b/src/zend/class.rs index b54ad4c..4c3c23d 100644 --- a/src/zend/class.rs +++ b/src/zend/class.rs @@ -34,6 +34,7 @@ impl ClassEntry { /// # Panics /// /// Panics when allocating memory for the new object fails. + #[allow(clippy::new_ret_no_self)] pub fn new(&self) -> ZBox { ZendObject::new(self) } diff --git a/src/zend/function.rs b/src/zend/function.rs index 9321e1a..e7ff767 100644 --- a/src/zend/function.rs +++ b/src/zend/function.rs @@ -56,11 +56,11 @@ impl Function { if res.is_null() { return None; } - return Some(*res); + Some(*res) } } pub fn try_from_method(class: &str, name: &str) -> Option { - return match ClassEntry::try_find(class) { + match ClassEntry::try_find(class) { None => None, Some(ce) => unsafe { let res = zend_hash_str_find_ptr_lc( @@ -71,9 +71,9 @@ impl Function { if res.is_null() { return None; } - return Some(*res); + Some(*res) }, - }; + } } pub fn from_function(name: &str) -> Self { From a4e3485f36cc6cceec77ae8abd625e2b7fc0c12e Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Tue, 10 Oct 2023 14:13:41 +0200 Subject: [PATCH 048/100] Update --- src/zend/function.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/zend/function.rs b/src/zend/function.rs index e7ff767..97c9ec0 100644 --- a/src/zend/function.rs +++ b/src/zend/function.rs @@ -77,11 +77,17 @@ impl Function { } pub fn from_function(name: &str) -> Self { - Self::try_from_function(name).unwrap() + 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 { - Self::try_from_method(class, name).unwrap() + 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 From 0773d72e793b4a519673df400c1927f4955e5872 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Tue, 10 Oct 2023 15:18:06 +0300 Subject: [PATCH 049/100] fmt --- src/describe/abi.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/describe/abi.rs b/src/describe/abi.rs index 1176710..a184cdb 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 { let _ = 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)); + }; } } From e1a5260a6e0b42948ab27b591f410afa7d52583b Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Tue, 10 Oct 2023 14:39:30 +0200 Subject: [PATCH 050/100] Fixup warnings on latest nightly --- crates/cli/src/ext.rs | 1 + src/types/class_object.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/cli/src/ext.rs b/crates/cli/src/ext.rs index b529a75..91aaff5 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/src/types/class_object.rs b/src/types/class_object.rs index f926c02..612ac62 100644 --- a/src/types/class_object.rs +++ b/src/types/class_object.rs @@ -155,7 +155,7 @@ impl ZendClassObject { /// # Parameters /// /// * `obj` - The zend object to get the [`ZendClassObject`] for. - pub fn from_zend_obj_mut(std: &mut zend_object) -> Option<&mut Self> { + #[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) } From 4f1565f3e80ca8973382117376e2d7c100fb434b Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Tue, 10 Oct 2023 15:05:59 +0200 Subject: [PATCH 051/100] Fmt --- src/types/class_object.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/types/class_object.rs b/src/types/class_object.rs index 612ac62..e9b1165 100644 --- a/src/types/class_object.rs +++ b/src/types/class_object.rs @@ -155,7 +155,8 @@ 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> { + #[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) } From 4152ffca06bee05a610629d3ce74d29bfecb607c Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Tue, 10 Oct 2023 16:07:39 +0200 Subject: [PATCH 052/100] Remove infallible variants --- guide/src/types/functions.md | 6 ++---- src/zend/function.rs | 14 -------------- 2 files changed, 2 insertions(+), 18 deletions(-) diff --git a/guide/src/types/functions.md b/guide/src/types/functions.md index c8d7b03..475a27b 100644 --- a/guide/src/types/functions.md +++ b/guide/src/types/functions.md @@ -5,8 +5,6 @@ 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; @@ -16,13 +14,13 @@ use ext_php_rs::zend::Function; #[php_function] pub fn test_function() -> () { - let var_dump = Function::from_function("var_dump"); + let var_dump = Function::try_from_function("var_dump").unwrap(); let _ = var_dump.try_call(vec![&"abc"]); } #[php_function] pub fn test_method() -> () { - let f = Function::from_method("ClassName", "staticMethod"); + let f = Function::try_from_method("ClassName", "staticMethod").unwrap(); let _ = f.try_call(vec![&"abc"]); } diff --git a/src/zend/function.rs b/src/zend/function.rs index 97c9ec0..a1ef866 100644 --- a/src/zend/function.rs +++ b/src/zend/function.rs @@ -76,20 +76,6 @@ impl Function { } } - 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. /// From 0ae3d1b4fb503eac7f4fbe4e0fbb609aaf0a7cfd Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Wed, 11 Oct 2023 10:42:47 +0200 Subject: [PATCH 053/100] Bump tag --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 2219cba..08f9648 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/davidcole1340/ext-php-rs" homepage = "https://github.com/davidcole1340/ext-php-rs" license = "MIT OR Apache-2.0" keywords = ["php", "ffi", "zend"] -version = "0.10.1" +version = "0.10.2" authors = ["David Cole "] edition = "2018" categories = ["api-bindings"] From 6520e85c301e0000486e33c0be55cfaa5c2f11b9 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Thu, 12 Oct 2023 10:27:50 +0200 Subject: [PATCH 054/100] Bump macro version --- crates/macros/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/macros/Cargo.toml b/crates/macros/Cargo.toml index 44d89bd..0131d72 100644 --- a/crates/macros/Cargo.toml +++ b/crates/macros/Cargo.toml @@ -4,7 +4,7 @@ description = "Derive macros for ext-php-rs." repository = "https://github.com/davidcole1340/ext-php-rs" homepage = "https://github.com/davidcole1340/ext-php-rs" license = "MIT OR Apache-2.0" -version = "0.10.0" +version = "0.10.1" authors = ["David Cole "] edition = "2018" From 54ed06b6b45457e8b51795e3e4ad295970f99d67 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Thu, 12 Oct 2023 10:28:49 +0200 Subject: [PATCH 055/100] Bump version --- crates/cli/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index aa965d3..004a1cb 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/davidcole1340/ext-php-rs" homepage = "https://github.com/davidcole1340/ext-php-rs" license = "MIT OR Apache-2.0" keywords = ["php", "ffi", "zend"] -version = "0.1.8" +version = "0.1.9" authors = ["David Cole "] edition = "2018" categories = ["api-bindings", "command-line-interface"] From f59b398424f67606b0e45c3a33fb32dcb6e1212b Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Thu, 12 Oct 2023 10:29:53 +0200 Subject: [PATCH 056/100] Bump --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 08f9648..eaccb4d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/davidcole1340/ext-php-rs" homepage = "https://github.com/davidcole1340/ext-php-rs" license = "MIT OR Apache-2.0" keywords = ["php", "ffi", "zend"] -version = "0.10.2" +version = "0.10.3" authors = ["David Cole "] edition = "2018" categories = ["api-bindings"] @@ -17,7 +17,7 @@ parking_lot = "0.12" cfg-if = "1.0" once_cell = "1.17" anyhow = { version = "1", optional = true } -ext-php-rs-derive = { version = "=0.10.0", path = "./crates/macros" } +ext-php-rs-derive = { version = "=0.10.1", path = "./crates/macros" } [dev-dependencies] skeptic = "0.13" From 2d0e587c7ef08c3e59dc9d127896ca5150023e85 Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Fri, 20 Oct 2023 14:08:10 +0200 Subject: [PATCH 057/100] feat(embed): add embed features, add test example which run php inside it (#270) * feat(embed): add embed features, add test example which run php inside it * feat(embed): use a guard to prevent running in parallel * chore(ci): update actions to not build and test with embed, add a specific build for embed testing * feat(embed): correcly start / shutdown embed api * chore(ci): use stable for rust in embed test * feat(embed): add documentation, manage potential errors --- .github/actions/embed/Dockerfile | 15 ++ .github/actions/embed/action.yml | 5 + .github/workflows/build.yml | 12 +- Cargo.toml | 1 + allowed_bindings.rs | 6 +- build.rs | 28 +++- src/embed/embed.c | 11 ++ src/embed/embed.h | 4 + src/embed/ffi.rs | 16 ++ src/embed/mod.rs | 221 ++++++++++++++++++++++++++++ src/embed/test-script-exception.php | 3 + src/embed/test-script.php | 7 + src/lib.rs | 2 + src/types/string.rs | 20 +++ unix_build.rs | 7 + 15 files changed, 354 insertions(+), 4 deletions(-) create mode 100644 .github/actions/embed/Dockerfile create mode 100644 .github/actions/embed/action.yml create mode 100644 src/embed/embed.c create mode 100644 src/embed/embed.h create mode 100644 src/embed/ffi.rs create mode 100644 src/embed/mod.rs create mode 100644 src/embed/test-script-exception.php create mode 100644 src/embed/test-script.php diff --git a/.github/actions/embed/Dockerfile b/.github/actions/embed/Dockerfile new file mode 100644 index 0000000..a92ec5a --- /dev/null +++ b/.github/actions/embed/Dockerfile @@ -0,0 +1,15 @@ +FROM php:8.2-bullseye + +WORKDIR /tmp + +RUN apt update -y && apt upgrade -y +RUN apt install lsb-release wget gnupg software-properties-common -y +RUN bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)" + +ENV RUSTUP_HOME=/rust +ENV CARGO_HOME=/cargo +ENV PATH=/cargo/bin:/rust/bin:$PATH + +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --no-modify-path + +ENTRYPOINT [ "/cargo/bin/cargo", "test", "--lib", "--release", "--all-features" ] diff --git a/.github/actions/embed/action.yml b/.github/actions/embed/action.yml new file mode 100644 index 0000000..c99ebf5 --- /dev/null +++ b/.github/actions/embed/action.yml @@ -0,0 +1,5 @@ +name: 'PHP Embed and Rust' +description: 'Builds the crate after installing the latest PHP with php embed and stable Rust.' +runs: + using: 'docker' + image: 'Dockerfile' diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6176b86..fe68b07 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -83,10 +83,10 @@ jobs: - name: Build env: EXT_PHP_RS_TEST: "" - run: cargo build --release --all-features --all + run: cargo build --release --features closure,anyhow --all # Test & lint - name: Test inline examples - run: cargo test --release --all --all-features + run: cargo test --release --all --features closure,anyhow - name: Run rustfmt if: matrix.rust == 'stable' && matrix.os == 'ubuntu-latest' && matrix.php == '8.2' run: cargo fmt --all -- --check @@ -110,3 +110,11 @@ jobs: uses: actions/checkout@v3 - name: Build uses: ./.github/actions/zts + test-embed: + name: Test with embed + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + - name: Test + uses: ./.github/actions/embed diff --git a/Cargo.toml b/Cargo.toml index eaccb4d..385a5cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ zip = "0.6" [features] closure = [] +embed = [] [workspace] members = [ diff --git a/allowed_bindings.rs b/allowed_bindings.rs index a74d965..49b1585 100644 --- a/allowed_bindings.rs +++ b/allowed_bindings.rs @@ -256,5 +256,9 @@ bind! { tsrm_get_ls_cache, executor_globals_offset, zend_atomic_bool_store, - zend_interrupt_function + zend_interrupt_function, + zend_eval_string, + zend_file_handle, + zend_stream_init_filename, + php_execute_script } diff --git a/build.rs b/build.rs index 40250a9..4ee8041 100644 --- a/build.rs +++ b/build.rs @@ -151,9 +151,31 @@ fn build_wrapper(defines: &[(&str, &str)], includes: &[PathBuf]) -> Result<()> { Ok(()) } +#[cfg(feature = "embed")] +/// Builds the embed library. +fn build_embed(defines: &[(&str, &str)], includes: &[PathBuf]) -> Result<()> { + let mut build = cc::Build::new(); + for (var, val) in defines { + build.define(var, *val); + } + build + .file("src/embed/embed.c") + .includes(includes) + .try_compile("embed") + .context("Failed to compile ext-php-rs C embed interface")?; + Ok(()) +} + /// Generates bindings to the Zend API. fn generate_bindings(defines: &[(&str, &str)], includes: &[PathBuf]) -> Result { - let mut bindgen = bindgen::Builder::default() + let mut bindgen = bindgen::Builder::default(); + + #[cfg(feature = "embed")] + { + bindgen = bindgen.header("src/embed/embed.h"); + } + + bindgen = bindgen .header("src/wrapper.h") .clang_args( includes @@ -257,6 +279,10 @@ fn main() -> Result<()> { check_php_version(&info)?; build_wrapper(&defines, &includes)?; + + #[cfg(feature = "embed")] + build_embed(&defines, &includes)?; + let bindings = generate_bindings(&defines, &includes)?; let out_file = diff --git a/src/embed/embed.c b/src/embed/embed.c new file mode 100644 index 0000000..6aaa30c --- /dev/null +++ b/src/embed/embed.c @@ -0,0 +1,11 @@ +#include "embed.h" + +// We actually use the PHP embed API to run PHP code in test +// At some point we might want to use our own SAPI to do that +void ext_php_rs_embed_callback(int argc, char** argv, void (*callback)(void *), void *ctx) { + PHP_EMBED_START_BLOCK(argc, argv) + + callback(ctx); + + PHP_EMBED_END_BLOCK() +} \ No newline at end of file diff --git a/src/embed/embed.h b/src/embed/embed.h new file mode 100644 index 0000000..910dcd9 --- /dev/null +++ b/src/embed/embed.h @@ -0,0 +1,4 @@ +#include "zend.h" +#include "sapi/embed/php_embed.h" + +void ext_php_rs_embed_callback(int argc, char** argv, void (*callback)(void *), void *ctx); diff --git a/src/embed/ffi.rs b/src/embed/ffi.rs new file mode 100644 index 0000000..74124a9 --- /dev/null +++ b/src/embed/ffi.rs @@ -0,0 +1,16 @@ +//! Raw FFI bindings to the Zend API. + +#![allow(clippy::all)] +#![allow(warnings)] + +use std::ffi::{c_char, c_int, c_void}; + +#[link(name = "wrapper")] +extern "C" { + pub fn ext_php_rs_embed_callback( + argc: c_int, + argv: *mut *mut c_char, + func: unsafe extern "C" fn(*const c_void), + ctx: *const c_void, + ); +} diff --git a/src/embed/mod.rs b/src/embed/mod.rs new file mode 100644 index 0000000..93e973b --- /dev/null +++ b/src/embed/mod.rs @@ -0,0 +1,221 @@ +//! Provides implementations for running php code from rust. +//! It only works on linux for now and you should have `php-embed` installed +//! +//! This crate was only test with PHP 8.2 please report any issue with other version +//! You should only use this crate for test purpose, it's not production ready + +mod ffi; + +use crate::boxed::ZBox; +use crate::embed::ffi::ext_php_rs_embed_callback; +use crate::ffi::{ + _zend_file_handle__bindgen_ty_1, php_execute_script, zend_eval_string, zend_file_handle, + zend_stream_init_filename, ZEND_RESULT_CODE_SUCCESS, +}; +use crate::types::{ZendObject, Zval}; +use crate::zend::ExecutorGlobals; +use parking_lot::{const_rwlock, RwLock}; +use std::ffi::{c_char, c_void, CString, NulError}; +use std::path::Path; +use std::ptr::null_mut; + +pub struct Embed; + +#[derive(Debug)] +pub enum EmbedError { + InitError, + ExecuteError(Option>), + ExecuteScriptError, + InvalidEvalString(NulError), + InvalidPath, +} + +static RUN_FN_LOCK: RwLock<()> = const_rwlock(()); + +impl Embed { + /// Run a php script from a file + /// + /// This function will only work correctly when used inside the `Embed::run` function + /// otherwise behavior is unexpected + /// + /// # Returns + /// + /// * `Ok(())` - The script was executed successfully + /// * `Err(EmbedError)` - An error occured during the execution of the script + /// + /// # Example + /// + /// ``` + /// use ext_php_rs::embed::Embed; + /// + /// Embed::run(|| { + /// let result = Embed::run_script("src/embed/test-script.php"); + /// + /// assert!(result.is_ok()); + /// }); + /// ``` + pub fn run_script>(path: P) -> Result<(), EmbedError> { + let path = match path.as_ref().to_str() { + Some(path) => match CString::new(path) { + Ok(path) => path, + Err(err) => return Err(EmbedError::InvalidEvalString(err)), + }, + None => return Err(EmbedError::InvalidPath), + }; + + let mut file_handle = zend_file_handle { + handle: _zend_file_handle__bindgen_ty_1 { fp: null_mut() }, + filename: null_mut(), + opened_path: null_mut(), + type_: 0, + primary_script: false, + in_list: false, + buf: null_mut(), + len: 0, + }; + + unsafe { + zend_stream_init_filename(&mut file_handle, path.as_ptr()); + } + + if unsafe { php_execute_script(&mut file_handle) } { + Ok(()) + } else { + Err(EmbedError::ExecuteScriptError) + } + } + + /// Start and run embed sapi engine + /// + /// This function will allow to run php code from rust, the same PHP context is keep between calls + /// inside the function passed to this method. + /// Which means subsequent calls to `Embed::eval` or `Embed::run_script` will be able to access + /// variables defined in previous calls + /// + /// # Example + /// + /// ``` + /// use ext_php_rs::embed::Embed; + /// + /// Embed::run(|| { + /// let _ = Embed::eval("$foo = 'foo';"); + /// let foo = Embed::eval("$foo;"); + /// assert!(foo.is_ok()); + /// assert_eq!(foo.unwrap().string().unwrap(), "foo"); + /// }); + /// ``` + pub fn run(func: F) { + // @TODO handle php thread safe + // + // This is to prevent multiple threads from running php at the same time + // At some point we should detect if php is compiled with thread safety and avoid doing that in this case + let _guard = RUN_FN_LOCK.write(); + + unsafe extern "C" fn wrapper(ctx: *const c_void) { + (*(ctx as *const F))(); + } + + unsafe { + ext_php_rs_embed_callback( + 0, + null_mut(), + wrapper::, + &func as *const F as *const c_void, + ); + } + } + + /// Evaluate a php code + /// + /// This function will only work correctly when used inside the `Embed::run` function + /// + /// # Returns + /// + /// * `Ok(Zval)` - The result of the evaluation + /// * `Err(EmbedError)` - An error occured during the evaluation + /// + /// # Example + /// + /// ``` + /// use ext_php_rs::embed::Embed; + /// + /// Embed::run(|| { + /// let foo = Embed::eval("$foo = 'foo';"); + /// assert!(foo.is_ok()); + /// }); + /// ``` + pub fn eval(code: &str) -> Result { + let cstr = match CString::new(code) { + Ok(cstr) => cstr, + Err(err) => return Err(EmbedError::InvalidEvalString(err)), + }; + + let mut result = Zval::new(); + + // this eval is very limited as it only allow simple code, it's the same eval used by php -r + let exec_result = unsafe { + zend_eval_string( + cstr.as_ptr() as *const c_char, + &mut result, + b"run\0".as_ptr() as *const _, + ) + }; + + let exception = ExecutorGlobals::take_exception(); + + if exec_result != ZEND_RESULT_CODE_SUCCESS { + Err(EmbedError::ExecuteError(exception)) + } else { + Ok(result) + } + } +} + +#[cfg(test)] +mod tests { + use super::Embed; + + #[test] + fn test_run() { + Embed::run(|| { + let result = Embed::eval("$foo = 'foo';"); + + assert!(result.is_ok()); + }); + } + + #[test] + fn test_run_error() { + Embed::run(|| { + let result = Embed::eval("stupid code;"); + + assert!(!result.is_ok()); + }); + } + + #[test] + fn test_run_script() { + Embed::run(|| { + let result = Embed::run_script("src/embed/test-script.php"); + + assert!(result.is_ok()); + + let zval = Embed::eval("$foo;").unwrap(); + + assert!(zval.is_object()); + + let obj = zval.object().unwrap(); + + assert_eq!(obj.get_class_name().unwrap(), "Test"); + }); + } + + #[test] + fn test_run_script_error() { + Embed::run(|| { + let result = Embed::run_script("src/embed/test-script-exception.php"); + + assert!(!result.is_ok()); + }); + } +} diff --git a/src/embed/test-script-exception.php b/src/embed/test-script-exception.php new file mode 100644 index 0000000..6674846 --- /dev/null +++ b/src/embed/test-script-exception.php @@ -0,0 +1,3 @@ + FromZval<'a> for &'a str { zval.str() } } + +#[cfg(test)] +#[cfg(feature = "embed")] +mod tests { + use crate::embed::Embed; + + #[test] + fn test_string() { + Embed::run(|| { + let result = Embed::eval("'foo';"); + + assert!(result.is_ok()); + + let zval = result.as_ref().unwrap(); + + assert!(zval.is_string()); + assert_eq!(zval.string().unwrap(), "foo"); + }); + } +} diff --git a/unix_build.rs b/unix_build.rs index 9bdd446..8be6fd8 100644 --- a/unix_build.rs +++ b/unix_build.rs @@ -55,4 +55,11 @@ impl<'a> PHPProvider<'a> for Provider { fn get_defines(&self) -> Result> { Ok(vec![]) } + + fn print_extra_link_args(&self) -> Result<()> { + #[cfg(feature = "embed")] + println!("cargo:rustc-link-lib=php"); + + Ok(()) + } } From 1a8211c392ea33a6192e4556b99762d704991cab Mon Sep 17 00:00:00 2001 From: Pierre Tondereau Date: Fri, 20 Oct 2023 14:44:47 +0200 Subject: [PATCH 058/100] chore: Bump Bindgen to 0.68.1 (#271) --- Cargo.toml | 12 ++--- docsrs_bindings.rs | 125 ++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 118 insertions(+), 19 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 385a5cc..f2af962 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,12 +24,15 @@ skeptic = "0.13" [build-dependencies] anyhow = "1" -bindgen = "0.65.1" +bindgen = "0.68.1" cc = "1.0" skeptic = "0.13" [target.'cfg(windows)'.build-dependencies] -ureq = { version = "2.4", features = ["native-tls", "gzip"], default-features = false } +ureq = { version = "2.4", features = [ + "native-tls", + "gzip", +], default-features = false } native-tls = "0.2" zip = "0.6" @@ -38,10 +41,7 @@ closure = [] embed = [] [workspace] -members = [ - "crates/macros", - "crates/cli" -] +members = ["crates/macros", "crates/cli"] [package.metadata.docs.rs] rustdoc-args = ["--cfg", "docs"] diff --git a/docsrs_bindings.rs b/docsrs_bindings.rs index 40d153a..2d22490 100644 --- a/docsrs_bindings.rs +++ b/docsrs_bindings.rs @@ -1,4 +1,4 @@ -/* automatically generated by rust-bindgen 0.65.1 */ +/* automatically generated by rust-bindgen 0.68.1 */ pub const ZEND_DEBUG: u32 = 1; pub const _ZEND_TYPE_NAME_BIT: u32 = 16777216; @@ -109,11 +109,65 @@ 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; +pub type __int64_t = ::std::os::raw::c_longlong; +pub type __darwin_off_t = __int64_t; +pub type fpos_t = __darwin_off_t; #[repr(C)] #[derive(Debug, Copy, Clone)] -pub struct __sigset_t { - pub __val: [::std::os::raw::c_ulong; 16usize], +pub struct __sbuf { + pub _base: *mut ::std::os::raw::c_uchar, + pub _size: ::std::os::raw::c_int, } +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct __sFILEX { + _unused: [u8; 0], +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct __sFILE { + pub _p: *mut ::std::os::raw::c_uchar, + pub _r: ::std::os::raw::c_int, + pub _w: ::std::os::raw::c_int, + pub _flags: ::std::os::raw::c_short, + pub _file: ::std::os::raw::c_short, + pub _bf: __sbuf, + pub _lbfsize: ::std::os::raw::c_int, + pub _cookie: *mut ::std::os::raw::c_void, + pub _close: ::std::option::Option< + unsafe extern "C" fn(arg1: *mut ::std::os::raw::c_void) -> ::std::os::raw::c_int, + >, + pub _read: ::std::option::Option< + unsafe extern "C" fn( + arg1: *mut ::std::os::raw::c_void, + arg2: *mut ::std::os::raw::c_char, + arg3: ::std::os::raw::c_int, + ) -> ::std::os::raw::c_int, + >, + pub _seek: ::std::option::Option< + unsafe extern "C" fn( + arg1: *mut ::std::os::raw::c_void, + arg2: fpos_t, + arg3: ::std::os::raw::c_int, + ) -> fpos_t, + >, + pub _write: ::std::option::Option< + unsafe extern "C" fn( + arg1: *mut ::std::os::raw::c_void, + arg2: *const ::std::os::raw::c_char, + arg3: ::std::os::raw::c_int, + ) -> ::std::os::raw::c_int, + >, + pub _ub: __sbuf, + pub _extra: *mut __sFILEX, + pub _ur: ::std::os::raw::c_int, + pub _ubuf: [::std::os::raw::c_uchar; 3usize], + pub _nbuf: [::std::os::raw::c_uchar; 1usize], + pub _lb: __sbuf, + pub _blksize: ::std::os::raw::c_int, + pub _offset: fpos_t, +} +pub type FILE = __sFILE; pub type zend_long = i64; pub type zend_ulong = u64; pub type zend_uchar = ::std::os::raw::c_uchar; @@ -487,6 +541,52 @@ pub struct _zend_class_arrayaccess_funcs { pub zf_offsetunset: *mut zend_function, } pub type zend_class_arrayaccess_funcs = _zend_class_arrayaccess_funcs; +pub type zend_stream_fsizer_t = + ::std::option::Option usize>; +pub type zend_stream_reader_t = ::std::option::Option< + unsafe extern "C" fn( + handle: *mut ::std::os::raw::c_void, + buf: *mut ::std::os::raw::c_char, + len: usize, + ) -> isize, +>; +pub type zend_stream_closer_t = + ::std::option::Option; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _zend_stream { + pub handle: *mut ::std::os::raw::c_void, + pub isatty: ::std::os::raw::c_int, + pub reader: zend_stream_reader_t, + pub fsizer: zend_stream_fsizer_t, + pub closer: zend_stream_closer_t, +} +pub type zend_stream = _zend_stream; +#[repr(C)] +#[derive(Copy, Clone)] +pub struct _zend_file_handle { + pub handle: _zend_file_handle__bindgen_ty_1, + pub filename: *mut zend_string, + pub opened_path: *mut zend_string, + pub type_: zend_uchar, + pub primary_script: bool, + pub in_list: bool, + pub buf: *mut ::std::os::raw::c_char, + pub len: usize, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub union _zend_file_handle__bindgen_ty_1 { + pub fp: *mut FILE, + pub stream: zend_stream, +} +pub type zend_file_handle = _zend_file_handle; +extern "C" { + pub fn zend_stream_init_filename( + handle: *mut zend_file_handle, + filename: *const ::std::os::raw::c_char, + ); +} #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct _zend_serialize_data { @@ -1065,15 +1165,7 @@ 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 __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 sigjmp_buf = [::std::os::raw::c_int; 49usize]; pub type zend_executor_globals = _zend_executor_globals; extern "C" { pub static mut executor_globals: zend_executor_globals; @@ -1143,7 +1235,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 jmp_buf, + pub bailout: *mut sigjmp_buf, pub error_reporting: ::std::os::raw::c_int, pub exit_status: ::std::os::raw::c_int, pub function_table: *mut HashTable, @@ -1279,6 +1371,13 @@ extern "C" { flags: u32, ) -> *mut zend_class_entry; } +extern "C" { + pub fn zend_eval_string( + str_: *const ::std::os::raw::c_char, + retval_ptr: *mut zval, + string_name: *const ::std::os::raw::c_char, + ) -> zend_result; +} #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct _zend_vm_stack { From 1b55652b7ab51f37168970f68832412550ebea30 Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Fri, 20 Oct 2023 21:52:52 +0200 Subject: [PATCH 059/100] feat(embed): correctly handle panic inside embed (#272) --- src/embed/embed.c | 10 +++++++--- src/embed/embed.h | 2 +- src/embed/ffi.rs | 4 ++-- src/embed/mod.rs | 36 +++++++++++++++++++++++++++++++----- 4 files changed, 41 insertions(+), 11 deletions(-) diff --git a/src/embed/embed.c b/src/embed/embed.c index 6aaa30c..d8b3a78 100644 --- a/src/embed/embed.c +++ b/src/embed/embed.c @@ -2,10 +2,14 @@ // We actually use the PHP embed API to run PHP code in test // At some point we might want to use our own SAPI to do that -void ext_php_rs_embed_callback(int argc, char** argv, void (*callback)(void *), void *ctx) { +void* ext_php_rs_embed_callback(int argc, char** argv, void* (*callback)(void *), void *ctx) { + void *result; + PHP_EMBED_START_BLOCK(argc, argv) - callback(ctx); + result = callback(ctx); PHP_EMBED_END_BLOCK() -} \ No newline at end of file + + return result; +} diff --git a/src/embed/embed.h b/src/embed/embed.h index 910dcd9..beabffb 100644 --- a/src/embed/embed.h +++ b/src/embed/embed.h @@ -1,4 +1,4 @@ #include "zend.h" #include "sapi/embed/php_embed.h" -void ext_php_rs_embed_callback(int argc, char** argv, void (*callback)(void *), void *ctx); +void* ext_php_rs_embed_callback(int argc, char** argv, void* (*callback)(void *), void *ctx); diff --git a/src/embed/ffi.rs b/src/embed/ffi.rs index 74124a9..3a1f6d7 100644 --- a/src/embed/ffi.rs +++ b/src/embed/ffi.rs @@ -10,7 +10,7 @@ extern "C" { pub fn ext_php_rs_embed_callback( argc: c_int, argv: *mut *mut c_char, - func: unsafe extern "C" fn(*const c_void), + func: unsafe extern "C" fn(*const c_void) -> *mut c_void, ctx: *const c_void, - ); + ) -> *mut c_void; } diff --git a/src/embed/mod.rs b/src/embed/mod.rs index 93e973b..581e385 100644 --- a/src/embed/mod.rs +++ b/src/embed/mod.rs @@ -16,6 +16,7 @@ use crate::types::{ZendObject, Zval}; use crate::zend::ExecutorGlobals; use parking_lot::{const_rwlock, RwLock}; use std::ffi::{c_char, c_void, CString, NulError}; +use std::panic::{catch_unwind, resume_unwind, RefUnwindSafe}; use std::path::Path; use std::ptr::null_mut; @@ -104,24 +105,41 @@ impl Embed { /// assert_eq!(foo.unwrap().string().unwrap(), "foo"); /// }); /// ``` - pub fn run(func: F) { + pub fn run(func: F) { // @TODO handle php thread safe // // This is to prevent multiple threads from running php at the same time // At some point we should detect if php is compiled with thread safety and avoid doing that in this case let _guard = RUN_FN_LOCK.write(); - unsafe extern "C" fn wrapper(ctx: *const c_void) { - (*(ctx as *const F))(); + unsafe extern "C" fn wrapper(ctx: *const c_void) -> *mut c_void { + // we try to catch panic here so we correctly shutdown php if it happens + // mandatory when we do assert on test as other test would not run correctly + let panic = catch_unwind(|| { + (*(ctx as *const F))(); + }); + + let panic_ptr = Box::into_raw(Box::new(panic)); + + panic_ptr as *mut c_void } - unsafe { + let panic = unsafe { ext_php_rs_embed_callback( 0, null_mut(), wrapper::, &func as *const F as *const c_void, - ); + ) + }; + + if panic.is_null() { + return; + } + + if let Err(err) = unsafe { *Box::from_raw(panic as *mut std::thread::Result<()>) } { + // we resume the panic here so it can be catched correctly by the test framework + resume_unwind(err); } } @@ -218,4 +236,12 @@ mod tests { assert!(!result.is_ok()); }); } + + #[test] + #[should_panic] + fn test_panic() { + Embed::run(|| { + panic!("test panic"); + }); + } } From 55724c94724c73e4d6841a052877c7426b1408c8 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Sat, 21 Oct 2023 21:47:38 +0200 Subject: [PATCH 060/100] Add async support using php-tokio --- Cargo.toml | 1 + guide/src/macros/impl.md | 131 +++++++++++++++++++++++++++++++++++++++ src/lib.rs | 44 +++++++++++++ 3 files changed, 176 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index f2af962..dd826bf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ cfg-if = "1.0" once_cell = "1.17" anyhow = { version = "1", optional = true } ext-php-rs-derive = { version = "=0.10.1", path = "./crates/macros" } +php-tokio = "0.1.4" [dev-dependencies] skeptic = "0.13" diff --git a/guide/src/macros/impl.md b/guide/src/macros/impl.md index 7214ab9..522d17a 100644 --- a/guide/src/macros/impl.md +++ b/guide/src/macros/impl.md @@ -8,6 +8,8 @@ implementations cannot be exported to PHP. If you do not want a function exported to PHP, you should place it in a separate `impl` block. +If you want to use async Rust, use `#[php_async_impl]`, instead: see [here »](#async) for more info. + ## Methods Methods basically follow the same rules as functions, so read about the @@ -63,6 +65,20 @@ the attribute, the function is not exported to PHP like a regular method. Constructors cannot use the visibility or rename attributes listed above. +### Async + +Using `#[php_async_impl]` instead of `#[php_impl]` allows us to expose any async Rust library to PHP, using [PHP fibers](https://www.php.net/manual/en/language.fibers.php), [php-tokio](https://github.com/danog/php-tokio) and the [PHP Revolt event loop](https://revolt.run) under the hood to handle async interoperability. + +This allows full compatibility with [amphp](https://amphp.org), [PSL](https://github.com/azjezz/psl), [reactphp](https://reactphp.org) and any other async PHP library based on [Revolt](https://revolt.run). + +Traits annotated with `#[php_impl]` can freely expose any async function, using `await` and any async Rust library. + +Make sure to also expose the `php_tokio::EventLoop::init` and `php_tokio::EventLoop::wakeup` functions to PHP in order to initialize the event loop, as specified in the full example [here &rauquo;](#async-example). + +Also, make sure to invoke `EventLoop::shutdown` in the request shutdown handler to clean up the tokio event loop before finishing the request. + +See [here &rauquo;](#async-example) for the full example. + ## Constants Constants are defined as regular Rust `impl` constants. Any type that implements @@ -162,4 +178,119 @@ var_dump(Human::get_max_age()); // int(100) var_dump(Human::MAX_AGE); // int(100) ``` +### Async example + +In this example, we're exposing an async Rust HTTP client library called [reqwest](https://docs.rs/reqwest/latest/reqwest/) to PHP, using [PHP fibers](https://www.php.net/manual/en/language.fibers.php), [php-tokio](https://github.com/danog/php-tokio) and the [PHP Revolt event loop](https://revolt.run) under the hood to handle async interoperability. + +This allows full compatibility with [amphp](https://amphp.org), [PSL](https://github.com/azjezz/psl), [reactphp](https://reactphp.org) and any other async PHP library based on [Revolt](https://revolt.run). + +```rust,no_run +use ext_php_rs::prelude::*; +use php_tokio::EventLoop; + +#[php_class] +struct Client {} + +#[php_async_impl] +impl Client { + pub fn init() -> PhpResult { + EventLoop::init() + } + pub fn wakeup() -> PhpResult<()> { + EventLoop::wakeup() + } + pub async fn get(url: &str) -> anyhow::Result { + Ok(reqwest::get(url).await?.text().await?) + } +} + +pub extern "C" fn request_shutdown(_type: i32, _module_number: i32) -> i32 { + EventLoop::shutdown(); + 0 +} + +#[php_module] +pub fn get_module(module: ModuleBuilder) -> ModuleBuilder { + module.request_shutdown_function(request_shutdown) +} +``` + +Here's the async PHP code we use to interact with the Rust class we just exposed. + +The `Client::init` method needs to be called only once in order to initialize the Revolt event loop and link it to the Tokio event loop, as shwon by the following code. + +See [here »](https://amphp.org) for more info on async PHP using [amphp](https://amphp.org) + [revolt](https://revolt.run). + +```php + \Client::wakeup()); + } + + public static function reference(): void + { + EventLoop::reference(self::$id); + } + public static function unreference(): void + { + EventLoop::unreference(self::$id); + } + + public static function __callStatic(string $name, array $args): mixed + { + return \Client::$name(...$args); + } +} + + +Client::init(); + +function test(int $delay): void +{ + $url = "https://httpbin.org/delay/$delay"; + $t = time(); + echo "Making async reqwest to $url that will return after $delay seconds...".PHP_EOL; + Client::get($url); + $t = time() - $t; + echo "Got response from $url after ~".$t." seconds!".PHP_EOL; +}; + +$futures = []; +$futures []= async(test(...), 5); +$futures []= async(test(...), 5); +$futures []= async(test(...), 5); + +await($futures); +``` + +Result: + +``` +Making async reqwest to https://httpbin.org/delay/5 that will return after 5 seconds... +Making async reqwest to https://httpbin.org/delay/5 that will return after 5 seconds... +Making async reqwest to https://httpbin.org/delay/5 that will return after 5 seconds... +Got response from https://httpbin.org/delay/5 after ~5 seconds! +Got response from https://httpbin.org/delay/5 after ~5 seconds! +Got response from https://httpbin.org/delay/5 after ~5 seconds! +``` + [`php_function`]: ./function.md diff --git a/src/lib.rs b/src/lib.rs index ee1d68f..82375a9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,6 +48,7 @@ pub mod prelude { pub use crate::php_extern; pub use crate::php_function; pub use crate::php_impl; + pub use crate::php_async_impl; pub use crate::php_module; pub use crate::php_print; pub use crate::php_println; @@ -390,6 +391,49 @@ pub use ext_php_rs_derive::php_function; /// ``` pub use ext_php_rs_derive::php_impl; +/// Just like php_impl, annotates a structs `impl` block, declaring that +/// all methods and constants declared inside the `impl` block will be declared +/// as PHP methods and constants. +/// +/// This variant of php_impl supports async Rust methods, using [php-tokio](https://github.com/danog/php-tokio) +/// to integrate [tokio](https://tokio.rs) with PHP fibers and the [Revolt](https://revolt.run) event loop, +/// compatible with [Amphp](https://amphp.org), [PSL](https://github.com/azjezz/psl) and any other async PHP library based on Revolt. +/// +/// # Example +/// +/// ```no_run +/// # #![cfg_attr(windows, feature(abi_vectorcall))] +/// # use ext_php_rs::prelude::*; +/// use php_tokio::EventLoop; +/// +/// #[php_class] +/// struct Client {} +/// +/// #[php_async_impl] +/// impl Client { +/// pub fn init() -> PhpResult { +/// EventLoop::init() +/// } +/// pub fn wakeup() -> PhpResult<()> { +/// EventLoop::wakeup() +/// } +/// pub async fn get(url: &str) -> anyhow::Result { +/// Ok(reqwest::get(url).await?.text().await?) +/// } +/// } +/// +/// pub extern "C" fn request_shutdown(_type: i32, _module_number: i32) -> i32 { +/// EventLoop::shutdown(); +/// 0 +/// } +/// +/// #[php_module] +/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder { +/// module.request_shutdown_function(request_shutdown) +/// } +/// ``` +pub use php_tokio::php_async_impl; + /// Annotates a function that will be used by PHP to retrieve information about /// the module. /// From 596768c7df12b9fcaf7c01b18e71ee274b8f6038 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Sat, 21 Oct 2023 21:49:39 +0200 Subject: [PATCH 061/100] Fix typo --- guide/src/macros/impl.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guide/src/macros/impl.md b/guide/src/macros/impl.md index 522d17a..e1adc50 100644 --- a/guide/src/macros/impl.md +++ b/guide/src/macros/impl.md @@ -217,7 +217,7 @@ pub fn get_module(module: ModuleBuilder) -> ModuleBuilder { Here's the async PHP code we use to interact with the Rust class we just exposed. -The `Client::init` method needs to be called only once in order to initialize the Revolt event loop and link it to the Tokio event loop, as shwon by the following code. +The `Client::init` method needs to be called only once in order to initialize the Revolt event loop and link it to the Tokio event loop, as shown by the following code. See [here »](https://amphp.org) for more info on async PHP using [amphp](https://amphp.org) + [revolt](https://revolt.run). From e4a450f03af394cd7244d8e249585f3f2464f1a5 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Sat, 21 Oct 2023 22:08:21 +0200 Subject: [PATCH 062/100] Add reqwest to dev deps --- Cargo.toml | 1 + guide/src/SUMMARY.md | 1 + guide/src/macros/impl.md | 2 ++ 3 files changed, 4 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index dd826bf..092efff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ php-tokio = "0.1.4" [dev-dependencies] skeptic = "0.13" +reqwest = "0.11.22" [build-dependencies] anyhow = "1" diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md index 2ca6c5f..4108d1b 100644 --- a/guide/src/SUMMARY.md +++ b/guide/src/SUMMARY.md @@ -24,6 +24,7 @@ - [Class Object](./types/class_object.md) - [Closure](./types/closure.md) - [Functions & methods](./types/functions.md) + - [Async futures](./macros/impl.md#async) - [Macros](./macros/index.md) - [Module](./macros/module.md) - [Module Startup Function](./macros/module_startup.md) diff --git a/guide/src/macros/impl.md b/guide/src/macros/impl.md index e1adc50..61b0e70 100644 --- a/guide/src/macros/impl.md +++ b/guide/src/macros/impl.md @@ -213,6 +213,8 @@ pub extern "C" fn request_shutdown(_type: i32, _module_number: i32) -> i32 { pub fn get_module(module: ModuleBuilder) -> ModuleBuilder { module.request_shutdown_function(request_shutdown) } + +# fn main() {} ``` Here's the async PHP code we use to interact with the Rust class we just exposed. From 942ec64219552448c9c3873f44e8a34c0837aa45 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Sat, 21 Oct 2023 22:16:46 +0200 Subject: [PATCH 063/100] Test --- guide/src/macros/impl.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/guide/src/macros/impl.md b/guide/src/macros/impl.md index 61b0e70..25ed8f1 100644 --- a/guide/src/macros/impl.md +++ b/guide/src/macros/impl.md @@ -185,7 +185,9 @@ In this example, we're exposing an async Rust HTTP client library called [reqwes This allows full compatibility with [amphp](https://amphp.org), [PSL](https://github.com/azjezz/psl), [reactphp](https://reactphp.org) and any other async PHP library based on [Revolt](https://revolt.run). ```rust,no_run -use ext_php_rs::prelude::*; +# #![cfg_attr(windows, feature(abi_vectorcall))] +# extern crate ext_php_rs; +# use ext_php_rs::prelude::*; use php_tokio::EventLoop; #[php_class] From ea078bbd959b3bedd44186fa158869597cbcb8a6 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Sat, 21 Oct 2023 22:21:50 +0200 Subject: [PATCH 064/100] Test --- guide/src/macros/impl.md | 6 ++++-- src/lib.rs | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/guide/src/macros/impl.md b/guide/src/macros/impl.md index 25ed8f1..7ac826c 100644 --- a/guide/src/macros/impl.md +++ b/guide/src/macros/impl.md @@ -182,10 +182,12 @@ var_dump(Human::MAX_AGE); // int(100) In this example, we're exposing an async Rust HTTP client library called [reqwest](https://docs.rs/reqwest/latest/reqwest/) to PHP, using [PHP fibers](https://www.php.net/manual/en/language.fibers.php), [php-tokio](https://github.com/danog/php-tokio) and the [PHP Revolt event loop](https://revolt.run) under the hood to handle async interoperability. -This allows full compatibility with [amphp](https://amphp.org), [PSL](https://github.com/azjezz/psl), [reactphp](https://reactphp.org) and any other async PHP library based on [Revolt](https://revolt.run). +This allows full compatibility with [amphp](https://amphp.org), [PSL](https://github.com/azjezz/psl), [reactphp](https://reactphp.org) and any other async PHP library based on [Revolt](https://revolt.run). + +Currently, only POSIX platforms are supported by php-tokio (Linux & Mac OS). ```rust,no_run -# #![cfg_attr(windows, feature(abi_vectorcall))] +# #![cfg(unix)] # extern crate ext_php_rs; # use ext_php_rs::prelude::*; use php_tokio::EventLoop; diff --git a/src/lib.rs b/src/lib.rs index 82375a9..e232bca 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -402,7 +402,7 @@ pub use ext_php_rs_derive::php_impl; /// # Example /// /// ```no_run -/// # #![cfg_attr(windows, feature(abi_vectorcall))] +/// # #![cfg(unix))] /// # use ext_php_rs::prelude::*; /// use php_tokio::EventLoop; /// From 0dae466e82b9647a697aed2ad01ea69eafd1a4a9 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Sat, 21 Oct 2023 22:27:05 +0200 Subject: [PATCH 065/100] Bump --- Cargo.toml | 2 +- guide/src/macros/impl.md | 4 ++-- src/lib.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 092efff..c551a8f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/davidcole1340/ext-php-rs" homepage = "https://github.com/davidcole1340/ext-php-rs" license = "MIT OR Apache-2.0" keywords = ["php", "ffi", "zend"] -version = "0.10.3" +version = "0.10.4" authors = ["David Cole "] edition = "2018" categories = ["api-bindings"] diff --git a/guide/src/macros/impl.md b/guide/src/macros/impl.md index 7ac826c..2b1dce2 100644 --- a/guide/src/macros/impl.md +++ b/guide/src/macros/impl.md @@ -184,10 +184,10 @@ In this example, we're exposing an async Rust HTTP client library called [reqwes This allows full compatibility with [amphp](https://amphp.org), [PSL](https://github.com/azjezz/psl), [reactphp](https://reactphp.org) and any other async PHP library based on [Revolt](https://revolt.run). -Currently, only POSIX platforms are supported by php-tokio (Linux & Mac OS). +Currently, only Linux is supported by php-tokio. ```rust,no_run -# #![cfg(unix)] +# #![cfg(linux)] # extern crate ext_php_rs; # use ext_php_rs::prelude::*; use php_tokio::EventLoop; diff --git a/src/lib.rs b/src/lib.rs index e232bca..4d2abc8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -402,7 +402,7 @@ pub use ext_php_rs_derive::php_impl; /// # Example /// /// ```no_run -/// # #![cfg(unix))] +/// # #![cfg(linux))] /// # use ext_php_rs::prelude::*; /// use php_tokio::EventLoop; /// From c0206ce6bc2833de9760510045b33d03ef047f9b Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Sat, 21 Oct 2023 22:51:48 +0200 Subject: [PATCH 066/100] Add async feature --- .github/workflows/build.yml | 4 ++-- Cargo.toml | 1 + src/lib.rs | 6 +++++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fe68b07..22c1761 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -83,10 +83,10 @@ jobs: - name: Build env: EXT_PHP_RS_TEST: "" - run: cargo build --release --features closure,anyhow --all + run: cargo build --release --features closure,anyhow,async --all # Test & lint - name: Test inline examples - run: cargo test --release --all --features closure,anyhow + run: cargo test --release --all --features closure,anyhow,async - name: Run rustfmt if: matrix.rust == 'stable' && matrix.os == 'ubuntu-latest' && matrix.php == '8.2' run: cargo fmt --all -- --check diff --git a/Cargo.toml b/Cargo.toml index c551a8f..af62e17 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,7 @@ zip = "0.6" [features] closure = [] embed = [] +async = [] [workspace] members = ["crates/macros", "crates/cli"] diff --git a/src/lib.rs b/src/lib.rs index 4d2abc8..87220fd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,6 +48,7 @@ pub mod prelude { pub use crate::php_extern; pub use crate::php_function; pub use crate::php_impl; + #[cfg(any(docs, feature = "async"))] pub use crate::php_async_impl; pub use crate::php_module; pub use crate::php_print; @@ -402,7 +403,7 @@ pub use ext_php_rs_derive::php_impl; /// # Example /// /// ```no_run -/// # #![cfg(linux))] +/// # #![cfg(linux)] /// # use ext_php_rs::prelude::*; /// use php_tokio::EventLoop; /// @@ -431,7 +432,10 @@ pub use ext_php_rs_derive::php_impl; /// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder { /// module.request_shutdown_function(request_shutdown) /// } +/// +/// pub fn main() {} /// ``` +#[cfg(any(docs, feature = "async"))] pub use php_tokio::php_async_impl; /// Annotates a function that will be used by PHP to retrieve information about From ce4ca0664bbe207f08815c96e496d0677ae2b6d0 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Sat, 21 Oct 2023 22:58:38 +0200 Subject: [PATCH 067/100] Make dependency optional --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index af62e17..7a1c551 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ cfg-if = "1.0" once_cell = "1.17" anyhow = { version = "1", optional = true } ext-php-rs-derive = { version = "=0.10.1", path = "./crates/macros" } -php-tokio = "0.1.4" +php-tokio = { version = "=0.1.4", optional = true } [dev-dependencies] skeptic = "0.13" @@ -41,7 +41,7 @@ zip = "0.6" [features] closure = [] embed = [] -async = [] +async = ["dep:php-tokio"] [workspace] members = ["crates/macros", "crates/cli"] From 924a99e73b70db4a6ac66ac8fd1da6bcad6e3cad Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Sat, 21 Oct 2023 23:04:03 +0200 Subject: [PATCH 068/100] Platform-specific dependencies --- Cargo.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 7a1c551..c3a5949 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,8 @@ cfg-if = "1.0" once_cell = "1.17" anyhow = { version = "1", optional = true } ext-php-rs-derive = { version = "=0.10.1", path = "./crates/macros" } + +[target.'cfg(linux)'.dependencies] php-tokio = { version = "=0.1.4", optional = true } [dev-dependencies] From 8890767ba92c43ac8141d32f2b5f588f1456face Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Sat, 21 Oct 2023 23:21:23 +0200 Subject: [PATCH 069/100] Just add docs --- .github/workflows/build.yml | 4 ++-- Cargo.toml | 7 ++---- guide/src/macros/impl.md | 12 +++------- src/lib.rs | 48 ------------------------------------- 4 files changed, 7 insertions(+), 64 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 22c1761..fe68b07 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -83,10 +83,10 @@ jobs: - name: Build env: EXT_PHP_RS_TEST: "" - run: cargo build --release --features closure,anyhow,async --all + run: cargo build --release --features closure,anyhow --all # Test & lint - name: Test inline examples - run: cargo test --release --all --features closure,anyhow,async + run: cargo test --release --all --features closure,anyhow - name: Run rustfmt if: matrix.rust == 'stable' && matrix.os == 'ubuntu-latest' && matrix.php == '8.2' run: cargo fmt --all -- --check diff --git a/Cargo.toml b/Cargo.toml index c3a5949..727fb37 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/davidcole1340/ext-php-rs" homepage = "https://github.com/davidcole1340/ext-php-rs" license = "MIT OR Apache-2.0" keywords = ["php", "ffi", "zend"] -version = "0.10.4" +version = "0.10.3" authors = ["David Cole "] edition = "2018" categories = ["api-bindings"] @@ -19,12 +19,10 @@ once_cell = "1.17" anyhow = { version = "1", optional = true } ext-php-rs-derive = { version = "=0.10.1", path = "./crates/macros" } -[target.'cfg(linux)'.dependencies] -php-tokio = { version = "=0.1.4", optional = true } - [dev-dependencies] skeptic = "0.13" reqwest = "0.11.22" +php-tokio = "0.1.4" [build-dependencies] anyhow = "1" @@ -43,7 +41,6 @@ zip = "0.6" [features] closure = [] embed = [] -async = ["dep:php-tokio"] [workspace] members = ["crates/macros", "crates/cli"] diff --git a/guide/src/macros/impl.md b/guide/src/macros/impl.md index 2b1dce2..660f374 100644 --- a/guide/src/macros/impl.md +++ b/guide/src/macros/impl.md @@ -184,13 +184,9 @@ In this example, we're exposing an async Rust HTTP client library called [reqwes This allows full compatibility with [amphp](https://amphp.org), [PSL](https://github.com/azjezz/psl), [reactphp](https://reactphp.org) and any other async PHP library based on [Revolt](https://revolt.run). -Currently, only Linux is supported by php-tokio. - -```rust,no_run -# #![cfg(linux)] -# extern crate ext_php_rs; -# use ext_php_rs::prelude::*; -use php_tokio::EventLoop; +```rust,ignore +use ext_php_rs::prelude::*; +use php_tokio::{php_async_impl, EventLoop}; #[php_class] struct Client {} @@ -217,8 +213,6 @@ pub extern "C" fn request_shutdown(_type: i32, _module_number: i32) -> i32 { pub fn get_module(module: ModuleBuilder) -> ModuleBuilder { module.request_shutdown_function(request_shutdown) } - -# fn main() {} ``` Here's the async PHP code we use to interact with the Rust class we just exposed. diff --git a/src/lib.rs b/src/lib.rs index 87220fd..ee1d68f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,8 +48,6 @@ pub mod prelude { pub use crate::php_extern; pub use crate::php_function; pub use crate::php_impl; - #[cfg(any(docs, feature = "async"))] - pub use crate::php_async_impl; pub use crate::php_module; pub use crate::php_print; pub use crate::php_println; @@ -392,52 +390,6 @@ pub use ext_php_rs_derive::php_function; /// ``` pub use ext_php_rs_derive::php_impl; -/// Just like php_impl, annotates a structs `impl` block, declaring that -/// all methods and constants declared inside the `impl` block will be declared -/// as PHP methods and constants. -/// -/// This variant of php_impl supports async Rust methods, using [php-tokio](https://github.com/danog/php-tokio) -/// to integrate [tokio](https://tokio.rs) with PHP fibers and the [Revolt](https://revolt.run) event loop, -/// compatible with [Amphp](https://amphp.org), [PSL](https://github.com/azjezz/psl) and any other async PHP library based on Revolt. -/// -/// # Example -/// -/// ```no_run -/// # #![cfg(linux)] -/// # use ext_php_rs::prelude::*; -/// use php_tokio::EventLoop; -/// -/// #[php_class] -/// struct Client {} -/// -/// #[php_async_impl] -/// impl Client { -/// pub fn init() -> PhpResult { -/// EventLoop::init() -/// } -/// pub fn wakeup() -> PhpResult<()> { -/// EventLoop::wakeup() -/// } -/// pub async fn get(url: &str) -> anyhow::Result { -/// Ok(reqwest::get(url).await?.text().await?) -/// } -/// } -/// -/// pub extern "C" fn request_shutdown(_type: i32, _module_number: i32) -> i32 { -/// EventLoop::shutdown(); -/// 0 -/// } -/// -/// #[php_module] -/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder { -/// module.request_shutdown_function(request_shutdown) -/// } -/// -/// pub fn main() {} -/// ``` -#[cfg(any(docs, feature = "async"))] -pub use php_tokio::php_async_impl; - /// Annotates a function that will be used by PHP to retrieve information about /// the module. /// From a595e1d4371d7ca075dabe10a799823595b34737 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Sat, 21 Oct 2023 23:21:51 +0200 Subject: [PATCH 070/100] Cleanup --- Cargo.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 727fb37..f2af962 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,8 +21,6 @@ ext-php-rs-derive = { version = "=0.10.1", path = "./crates/macros" } [dev-dependencies] skeptic = "0.13" -reqwest = "0.11.22" -php-tokio = "0.1.4" [build-dependencies] anyhow = "1" From e80bebfc4c422f9313bb31d5f6d62b2a93dd9da2 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Sat, 21 Oct 2023 23:22:47 +0200 Subject: [PATCH 071/100] Cleanup --- guide/src/macros/impl.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/guide/src/macros/impl.md b/guide/src/macros/impl.md index 660f374..fa9c730 100644 --- a/guide/src/macros/impl.md +++ b/guide/src/macros/impl.md @@ -184,6 +184,8 @@ In this example, we're exposing an async Rust HTTP client library called [reqwes This allows full compatibility with [amphp](https://amphp.org), [PSL](https://github.com/azjezz/psl), [reactphp](https://reactphp.org) and any other async PHP library based on [Revolt](https://revolt.run). +Make sure to require [php-tokio](https://github.com/danog/php-tokio) as a dependency before proceeding. + ```rust,ignore use ext_php_rs::prelude::*; use php_tokio::{php_async_impl, EventLoop}; From 8462005af491c26cea47f0ab04be77d5c97f590d Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Sat, 21 Oct 2023 23:23:08 +0200 Subject: [PATCH 072/100] Cleanup --- guide/src/macros/impl.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/guide/src/macros/impl.md b/guide/src/macros/impl.md index fa9c730..1b872f1 100644 --- a/guide/src/macros/impl.md +++ b/guide/src/macros/impl.md @@ -73,11 +73,11 @@ This allows full compatibility with [amphp](https://amphp.org), [PSL](https://gi Traits annotated with `#[php_impl]` can freely expose any async function, using `await` and any async Rust library. -Make sure to also expose the `php_tokio::EventLoop::init` and `php_tokio::EventLoop::wakeup` functions to PHP in order to initialize the event loop, as specified in the full example [here &rauquo;](#async-example). +Make sure to also expose the `php_tokio::EventLoop::init` and `php_tokio::EventLoop::wakeup` functions to PHP in order to initialize the event loop, as specified in the full example [here »](#async-example). Also, make sure to invoke `EventLoop::shutdown` in the request shutdown handler to clean up the tokio event loop before finishing the request. -See [here &rauquo;](#async-example) for the full example. +See [here »](#async-example) for the full example. ## Constants From dddc07f587cd5d40f3b3ff64b9a59fba8299d953 Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Mon, 23 Oct 2023 11:42:01 +0200 Subject: [PATCH 073/100] feat(test): add an example on how to test a module (#276) --- allowed_bindings.rs | 3 ++- tests/module.rs | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 tests/module.rs diff --git a/allowed_bindings.rs b/allowed_bindings.rs index 49b1585..4a5dbfc 100644 --- a/allowed_bindings.rs +++ b/allowed_bindings.rs @@ -260,5 +260,6 @@ bind! { zend_eval_string, zend_file_handle, zend_stream_init_filename, - php_execute_script + php_execute_script, + zend_register_module_ex } diff --git a/tests/module.rs b/tests/module.rs new file mode 100644 index 0000000..f1ef22b --- /dev/null +++ b/tests/module.rs @@ -0,0 +1,44 @@ +#![cfg_attr(windows, feature(abi_vectorcall))] +extern crate ext_php_rs; + +#[cfg(feature = "embed")] +use ext_php_rs::embed::Embed; +#[cfg(feature = "embed")] +use ext_php_rs::ffi::zend_register_module_ex; +use ext_php_rs::prelude::*; + +#[test] +#[cfg(feature = "embed")] +fn test_module() { + Embed::run(|| { + // Allow to load the module + unsafe { zend_register_module_ex(get_module()) }; + + let result = Embed::eval("$foo = hello_world('foo');"); + + assert!(result.is_ok()); + + let zval = result.unwrap(); + + assert!(zval.is_string()); + + let string = zval.string().unwrap(); + + assert_eq!(string.to_string(), "Hello, foo!"); + }); +} + +/// Gives you a nice greeting! +/// +/// @param string $name Your name. +/// +/// @return string Nice greeting! +#[php_function] +pub fn hello_world(name: String) -> String { + format!("Hello, {}!", name) +} + +#[php_module] +pub fn module(module: ModuleBuilder) -> ModuleBuilder { + module +} From fbb0b41fdc029cc182102cd39806341d78a91ee3 Mon Sep 17 00:00:00 2001 From: Joe Hoyle Date: Tue, 24 Oct 2023 20:16:53 +0200 Subject: [PATCH 074/100] Forward ClassEntry in create_object See #138 --- src/builders/class.rs | 4 ++-- src/types/class_object.rs | 14 +++++++------- src/zend/handlers.rs | 6 +++--- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/builders/class.rs b/src/builders/class.rs index 91a6a01..28e8463 100644 --- a/src/builders/class.rs +++ b/src/builders/class.rs @@ -161,10 +161,10 @@ impl ClassBuilder { /// Panics if the class name associated with `T` is not the same as the /// class name specified when creating the builder. pub fn object_override(mut self) -> Self { - extern "C" fn create_object(_: *mut ClassEntry) -> *mut ZendObject { + extern "C" fn create_object(ce: *mut ClassEntry) -> *mut ZendObject { // SAFETY: After calling this function, PHP will always call the constructor // defined below, which assumes that the object is uninitialized. - let obj = unsafe { ZendClassObject::::new_uninit() }; + let obj = unsafe { ZendClassObject::::new_uninit(ce.as_ref()) }; obj.into_raw().get_mut_zend_obj() } diff --git a/src/types/class_object.rs b/src/types/class_object.rs index e9b1165..c9b8b48 100644 --- a/src/types/class_object.rs +++ b/src/types/class_object.rs @@ -15,10 +15,10 @@ use crate::{ error::{Error, Result}, ffi::{ ext_php_rs_zend_object_alloc, ext_php_rs_zend_object_release, object_properties_init, - zend_object, zend_object_std_init, zend_objects_clone_members, + zend_object, zend_object_std_init, zend_objects_clone_members, _zend_class_entry, }, flags::DataType, - types::{ZendObject, Zval}, + types::{ZendObject, Zval}, zend::ClassEntry, }; /// Representation of a Zend class object in memory. @@ -43,7 +43,7 @@ impl ZendClassObject { /// Panics if memory was unable to be allocated for the new object. pub fn new(val: T) -> ZBox { // SAFETY: We are providing a value to initialize the object with. - unsafe { Self::internal_new(Some(val)) } + unsafe { Self::internal_new(Some(val), None) } } /// Creates a new [`ZendClassObject`] of type `T`, with an uninitialized @@ -67,8 +67,8 @@ impl ZendClassObject { /// # Panics /// /// Panics if memory was unable to be allocated for the new object. - pub unsafe fn new_uninit() -> ZBox { - Self::internal_new(None) + pub unsafe fn new_uninit(ce: Option<&'static ClassEntry>) -> ZBox { + Self::internal_new(None, ce) } /// Creates a new [`ZendObject`] of type `T`, storing the given (and @@ -102,10 +102,10 @@ impl ZendClassObject { /// # Panics /// /// Panics if memory was unable to be allocated for the new object. - unsafe fn internal_new(val: Option) -> ZBox { + unsafe fn internal_new(val: Option, ce: Option<&'static ClassEntry>) -> ZBox { let size = mem::size_of::>(); let meta = T::get_metadata(); - let ce = meta.ce() as *const _ as *mut _; + let ce = ce.unwrap_or_else(||meta.ce()) as *const _ as *mut _; let obj = ext_php_rs_zend_object_alloc(size as _, ce) as *mut ZendClassObject; let obj = obj .as_mut() diff --git a/src/zend/handlers.rs b/src/zend/handlers.rs index 11d220b..f2d71b0 100644 --- a/src/zend/handlers.rs +++ b/src/zend/handlers.rs @@ -52,13 +52,13 @@ impl ZendObjectHandlers { } unsafe extern "C" fn free_obj(object: *mut ZendObject) { - let obj = object + object .as_mut() .and_then(|obj| ZendClassObject::::from_zend_obj_mut(obj)) - .expect("Invalid object pointer given for `free_obj`"); + .map(|obj|ptr::drop_in_place(&mut obj.obj)); // Manually drop the object as we don't want to free the underlying memory. - ptr::drop_in_place(&mut obj.obj); + zend_object_std_dtor(object) } From 5fdd8fac4481273e1e651a9433556a4785824744 Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Wed, 25 Oct 2023 15:15:43 +0200 Subject: [PATCH 075/100] feat(zend): add helper for try catch and bailout in PHP (#275) * feat(zend): add helper for try catch and bailout in PHP * feat(try): add bindings for bailout * fix(try): add missing feature flag for test * feat(try): add a test that expose memory leak problem * feat(try): make bailout unsafe and explain why * feat(bailout): flag bailout as a panic function * feat(embed): add try catch on script / eval --- allowed_bindings.rs | 3 +- build.rs | 2 + docsrs_bindings.rs | 3 + src/embed/embed.c | 2 +- src/embed/ffi.rs | 2 +- src/embed/mod.rs | 92 +++++++++++++++--------- src/ffi.rs | 6 ++ src/wrapper.c | 14 ++++ src/wrapper.h | 4 +- src/zend/mod.rs | 4 ++ src/zend/try_catch.rs | 164 ++++++++++++++++++++++++++++++++++++++++++ 11 files changed, 259 insertions(+), 37 deletions(-) create mode 100644 src/zend/try_catch.rs diff --git a/allowed_bindings.rs b/allowed_bindings.rs index 4a5dbfc..ca2195b 100644 --- a/allowed_bindings.rs +++ b/allowed_bindings.rs @@ -261,5 +261,6 @@ bind! { zend_file_handle, zend_stream_init_filename, php_execute_script, - zend_register_module_ex + zend_register_module_ex, + _zend_bailout } diff --git a/build.rs b/build.rs index 4ee8041..78c9cf3 100644 --- a/build.rs +++ b/build.rs @@ -248,6 +248,8 @@ fn main() -> Result<()> { for path in [ manifest.join("src").join("wrapper.h"), manifest.join("src").join("wrapper.c"), + manifest.join("src").join("embed").join("embed.h"), + manifest.join("src").join("embed").join("embed.c"), manifest.join("allowed_bindings.rs"), manifest.join("windows_build.rs"), manifest.join("unix_build.rs"), diff --git a/docsrs_bindings.rs b/docsrs_bindings.rs index 2d22490..f7e54f1 100644 --- a/docsrs_bindings.rs +++ b/docsrs_bindings.rs @@ -789,6 +789,9 @@ pub struct _zend_class_entry__bindgen_ty_4__bindgen_ty_2 { pub builtin_functions: *const _zend_function_entry, pub module: *mut _zend_module_entry, } +extern "C" { + pub fn _zend_bailout(filename: *const ::std::os::raw::c_char, lineno: u32) -> !; +} extern "C" { pub static mut zend_interrupt_function: ::std::option::Option; diff --git a/src/embed/embed.c b/src/embed/embed.c index d8b3a78..ae7d8bc 100644 --- a/src/embed/embed.c +++ b/src/embed/embed.c @@ -3,7 +3,7 @@ // We actually use the PHP embed API to run PHP code in test // At some point we might want to use our own SAPI to do that void* ext_php_rs_embed_callback(int argc, char** argv, void* (*callback)(void *), void *ctx) { - void *result; + void *result = NULL; PHP_EMBED_START_BLOCK(argc, argv) diff --git a/src/embed/ffi.rs b/src/embed/ffi.rs index 3a1f6d7..b52ce6a 100644 --- a/src/embed/ffi.rs +++ b/src/embed/ffi.rs @@ -10,7 +10,7 @@ extern "C" { pub fn ext_php_rs_embed_callback( argc: c_int, argv: *mut *mut c_char, - func: unsafe extern "C" fn(*const c_void) -> *mut c_void, + func: unsafe extern "C" fn(*const c_void) -> *const c_void, ctx: *const c_void, ) -> *mut c_void; } diff --git a/src/embed/mod.rs b/src/embed/mod.rs index 581e385..0ad64a6 100644 --- a/src/embed/mod.rs +++ b/src/embed/mod.rs @@ -13,10 +13,10 @@ use crate::ffi::{ zend_stream_init_filename, ZEND_RESULT_CODE_SUCCESS, }; use crate::types::{ZendObject, Zval}; -use crate::zend::ExecutorGlobals; +use crate::zend::{panic_wrapper, try_catch, ExecutorGlobals}; use parking_lot::{const_rwlock, RwLock}; use std::ffi::{c_char, c_void, CString, NulError}; -use std::panic::{catch_unwind, resume_unwind, RefUnwindSafe}; +use std::panic::{resume_unwind, RefUnwindSafe}; use std::path::Path; use std::ptr::null_mut; @@ -29,6 +29,13 @@ pub enum EmbedError { ExecuteScriptError, InvalidEvalString(NulError), InvalidPath, + CatchError, +} + +impl EmbedError { + pub fn is_bailout(&self) -> bool { + matches!(self, EmbedError::CatchError) + } } static RUN_FN_LOCK: RwLock<()> = const_rwlock(()); @@ -79,10 +86,12 @@ impl Embed { zend_stream_init_filename(&mut file_handle, path.as_ptr()); } - if unsafe { php_execute_script(&mut file_handle) } { - Ok(()) - } else { - Err(EmbedError::ExecuteScriptError) + let exec_result = try_catch(|| unsafe { php_execute_script(&mut file_handle) }); + + match exec_result { + Err(_) => Err(EmbedError::CatchError), + Ok(true) => Ok(()), + Ok(false) => Err(EmbedError::ExecuteScriptError), } } @@ -93,6 +102,12 @@ impl Embed { /// Which means subsequent calls to `Embed::eval` or `Embed::run_script` will be able to access /// variables defined in previous calls /// + /// # Returns + /// + /// * R - The result of the function passed to this method + /// + /// R must implement [`Default`] so it can be returned in case of a bailout + /// /// # Example /// /// ``` @@ -105,41 +120,36 @@ impl Embed { /// assert_eq!(foo.unwrap().string().unwrap(), "foo"); /// }); /// ``` - pub fn run(func: F) { + pub fn run R + RefUnwindSafe>(func: F) -> R + where + R: Default, + { // @TODO handle php thread safe // // This is to prevent multiple threads from running php at the same time // At some point we should detect if php is compiled with thread safety and avoid doing that in this case let _guard = RUN_FN_LOCK.write(); - unsafe extern "C" fn wrapper(ctx: *const c_void) -> *mut c_void { - // we try to catch panic here so we correctly shutdown php if it happens - // mandatory when we do assert on test as other test would not run correctly - let panic = catch_unwind(|| { - (*(ctx as *const F))(); - }); - - let panic_ptr = Box::into_raw(Box::new(panic)); - - panic_ptr as *mut c_void - } - let panic = unsafe { ext_php_rs_embed_callback( 0, null_mut(), - wrapper::, + panic_wrapper::, &func as *const F as *const c_void, ) }; + // This can happen if there is a bailout if panic.is_null() { - return; + return R::default(); } - if let Err(err) = unsafe { *Box::from_raw(panic as *mut std::thread::Result<()>) } { - // we resume the panic here so it can be catched correctly by the test framework - resume_unwind(err); + match unsafe { *Box::from_raw(panic as *mut std::thread::Result) } { + Ok(r) => r, + Err(err) => { + // we resume the panic here so it can be catched correctly by the test framework + resume_unwind(err); + } } } @@ -170,21 +180,18 @@ impl Embed { let mut result = Zval::new(); - // this eval is very limited as it only allow simple code, it's the same eval used by php -r - let exec_result = unsafe { + let exec_result = try_catch(|| unsafe { zend_eval_string( cstr.as_ptr() as *const c_char, &mut result, b"run\0".as_ptr() as *const _, ) - }; + }); - let exception = ExecutorGlobals::take_exception(); - - if exec_result != ZEND_RESULT_CODE_SUCCESS { - Err(EmbedError::ExecuteError(exception)) - } else { - Ok(result) + match exec_result { + Err(_) => Err(EmbedError::CatchError), + Ok(ZEND_RESULT_CODE_SUCCESS) => Ok(result), + Ok(_) => Err(EmbedError::ExecuteError(ExecutorGlobals::take_exception())), } } } @@ -244,4 +251,23 @@ mod tests { panic!("test panic"); }); } + + #[test] + fn test_return() { + let foo = Embed::run(|| { + return "foo"; + }); + + assert_eq!(foo, "foo"); + } + + #[test] + fn test_eval_bailout() { + Embed::run(|| { + let result = Embed::eval("str_repeat('a', 100_000_000_000_000);"); + + assert!(result.is_err()); + assert!(result.unwrap_err().is_bailout()); + }); + } } diff --git a/src/ffi.rs b/src/ffi.rs index 92614c4..a6c3d94 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -26,6 +26,12 @@ extern "C" { pub fn ext_php_rs_zend_object_alloc(obj_size: usize, ce: *mut zend_class_entry) -> *mut c_void; pub fn ext_php_rs_zend_object_release(obj: *mut zend_object); pub fn ext_php_rs_executor_globals() -> *mut zend_executor_globals; + pub fn ext_php_rs_zend_try_catch( + func: unsafe extern "C" fn(*const c_void) -> *const c_void, + ctx: *const c_void, + result: *mut *mut c_void, + ) -> bool; + pub fn ext_php_rs_zend_bailout() -> !; } include!(concat!(env!("OUT_DIR"), "/bindings.rs")); diff --git a/src/wrapper.c b/src/wrapper.c index faf585e..a1fd900 100644 --- a/src/wrapper.c +++ b/src/wrapper.c @@ -39,3 +39,17 @@ zend_executor_globals *ext_php_rs_executor_globals() { return &executor_globals; #endif } + +bool ext_php_rs_zend_try_catch(void* (*callback)(void *), void *ctx, void **result) { + zend_try { + *result = callback(ctx); + } zend_catch { + return true; + } zend_end_try(); + + return false; +} + +void ext_php_rs_zend_bailout() { + zend_bailout(); +} diff --git a/src/wrapper.h b/src/wrapper.h index ed9dea6..e4e5551 100644 --- a/src/wrapper.h +++ b/src/wrapper.h @@ -30,4 +30,6 @@ void ext_php_rs_set_known_valid_utf8(zend_string *zs); const char *ext_php_rs_php_build_id(); void *ext_php_rs_zend_object_alloc(size_t obj_size, zend_class_entry *ce); void ext_php_rs_zend_object_release(zend_object *obj); -zend_executor_globals *ext_php_rs_executor_globals(); \ No newline at end of file +zend_executor_globals *ext_php_rs_executor_globals(); +bool ext_php_rs_zend_try_catch(void* (*callback)(void *), void *ctx, void **result); +void ext_php_rs_zend_bailout(); diff --git a/src/zend/mod.rs b/src/zend/mod.rs index b3b1cfb..af8a5c2 100644 --- a/src/zend/mod.rs +++ b/src/zend/mod.rs @@ -9,6 +9,7 @@ mod globals; mod handlers; mod ini_entry_def; mod module; +mod try_catch; use crate::{error::Result, ffi::php_printf}; use std::ffi::CString; @@ -22,6 +23,9 @@ pub use globals::ExecutorGlobals; pub use handlers::ZendObjectHandlers; pub use ini_entry_def::IniEntryDef; pub use module::ModuleEntry; +#[cfg(feature = "embed")] +pub(crate) use try_catch::panic_wrapper; +pub use try_catch::{bailout, try_catch}; // Used as the format string for `php_printf`. const FORMAT_STR: &[u8] = b"%s\0"; diff --git a/src/zend/try_catch.rs b/src/zend/try_catch.rs new file mode 100644 index 0000000..f74b427 --- /dev/null +++ b/src/zend/try_catch.rs @@ -0,0 +1,164 @@ +use crate::ffi::{ext_php_rs_zend_bailout, ext_php_rs_zend_try_catch}; +use std::ffi::c_void; +use std::panic::{catch_unwind, resume_unwind, RefUnwindSafe}; +use std::ptr::null_mut; + +#[derive(Debug)] +pub struct CatchError; + +pub(crate) unsafe extern "C" fn panic_wrapper R + RefUnwindSafe>( + ctx: *const c_void, +) -> *const c_void { + // we try to catch panic here so we correctly shutdown php if it happens + // mandatory when we do assert on test as other test would not run correctly + let panic = catch_unwind(|| (*(ctx as *mut F))()); + + Box::into_raw(Box::new(panic)) as *mut c_void +} + +/// PHP propose a try catch mechanism in C using setjmp and longjmp (bailout) +/// It store the arg of setjmp into the bailout field of the global executor +/// If a bailout is triggered, the executor will jump to the setjmp and restore the previous setjmp +/// +/// try_catch allow to use this mechanism +/// +/// # Returns +/// +/// * `Ok(R)` - The result of the function +/// * `Err(CatchError)` - A bailout occurred during the execution +pub fn try_catch R + RefUnwindSafe>(func: F) -> Result { + let mut panic_ptr = null_mut(); + let has_bailout = unsafe { + ext_php_rs_zend_try_catch( + panic_wrapper::, + &func as *const F as *const c_void, + (&mut panic_ptr) as *mut *mut c_void, + ) + }; + + let panic = panic_ptr as *mut std::thread::Result; + + // can be null if there is a bailout + if panic.is_null() || has_bailout { + return Err(CatchError); + } + + match unsafe { *Box::from_raw(panic as *mut std::thread::Result) } { + Ok(r) => Ok(r), + Err(err) => { + // we resume the panic here so it can be catched correctly by the test framework + resume_unwind(err); + } + } +} + +/// Trigger a bailout +/// +/// This function will stop the execution of the current script +/// and jump to the last try catch block +/// +/// # Safety +/// +/// This function is unsafe because it can cause memory leaks +/// Since it will jump to the last try catch block, it will not call the destructor of the current scope +/// +/// When using this function you should ensure that all the memory allocated in the current scope is released +/// +pub unsafe fn bailout() -> ! { + ext_php_rs_zend_bailout(); +} + +#[cfg(feature = "embed")] +#[cfg(test)] +mod tests { + use crate::embed::Embed; + use crate::zend::{bailout, try_catch}; + use std::ptr::null_mut; + + #[test] + fn test_catch() { + Embed::run(|| { + let catch = try_catch(|| { + unsafe { + bailout(); + } + + #[allow(unreachable_code)] + { + assert!(false); + } + }); + + assert!(catch.is_err()); + }); + } + + #[test] + fn test_no_catch() { + Embed::run(|| { + let catch = try_catch(|| { + assert!(true); + }); + + assert!(catch.is_ok()); + }); + } + + #[test] + fn test_bailout() { + Embed::run(|| { + unsafe { + bailout(); + } + + #[allow(unreachable_code)] + { + assert!(false); + } + }); + } + + #[test] + #[should_panic] + fn test_panic() { + Embed::run(|| { + let _ = try_catch(|| { + panic!("should panic"); + }); + }); + } + + #[test] + fn test_return() { + let foo = Embed::run(|| { + let result = try_catch(|| { + return "foo"; + }); + + assert!(result.is_ok()); + + result.unwrap() + }); + + assert_eq!(foo, "foo"); + } + + #[test] + fn test_memory_leak() { + let mut ptr = null_mut(); + + let _ = try_catch(|| { + let mut result = "foo".to_string(); + ptr = &mut result; + + unsafe { + bailout(); + } + }); + + // Check that the string is never released + let result = unsafe { &*ptr as &str }; + + assert_eq!(result, "foo"); + } +} From 3688aac183369bb948e53230612c4571c2654fbd Mon Sep 17 00:00:00 2001 From: Joe Hoyle Date: Wed, 25 Oct 2023 20:34:49 +0200 Subject: [PATCH 076/100] Check instance_of() so subclasses also work --- src/types/class_object.rs | 2 +- src/zend/handlers.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/types/class_object.rs b/src/types/class_object.rs index c9b8b48..099b726 100644 --- a/src/types/class_object.rs +++ b/src/types/class_object.rs @@ -167,7 +167,7 @@ impl ZendClassObject { (ptr as *mut Self).as_mut()? }; - if ptr.std.is_instance::() { + if ptr.std.instance_of(T::get_metadata().ce()) { Some(ptr) } else { None diff --git a/src/zend/handlers.rs b/src/zend/handlers.rs index f2d71b0..11d220b 100644 --- a/src/zend/handlers.rs +++ b/src/zend/handlers.rs @@ -52,13 +52,13 @@ impl ZendObjectHandlers { } unsafe extern "C" fn free_obj(object: *mut ZendObject) { - object + let obj = object .as_mut() .and_then(|obj| ZendClassObject::::from_zend_obj_mut(obj)) - .map(|obj|ptr::drop_in_place(&mut obj.obj)); + .expect("Invalid object pointer given for `free_obj`"); // Manually drop the object as we don't want to free the underlying memory. - + ptr::drop_in_place(&mut obj.obj); zend_object_std_dtor(object) } From 8009a91e1389ec1c0598b85db8b120998bf8c28f Mon Sep 17 00:00:00 2001 From: Joe Hoyle Date: Wed, 25 Oct 2023 20:40:11 +0200 Subject: [PATCH 077/100] Cargo fmt --- src/types/class_object.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/types/class_object.rs b/src/types/class_object.rs index 099b726..811d446 100644 --- a/src/types/class_object.rs +++ b/src/types/class_object.rs @@ -14,11 +14,12 @@ use crate::{ convert::{FromZendObject, FromZendObjectMut, FromZval, FromZvalMut, IntoZval}, error::{Error, Result}, ffi::{ - ext_php_rs_zend_object_alloc, ext_php_rs_zend_object_release, object_properties_init, - zend_object, zend_object_std_init, zend_objects_clone_members, _zend_class_entry, + _zend_class_entry, ext_php_rs_zend_object_alloc, ext_php_rs_zend_object_release, + object_properties_init, zend_object, zend_object_std_init, zend_objects_clone_members, }, flags::DataType, - types::{ZendObject, Zval}, zend::ClassEntry, + types::{ZendObject, Zval}, + zend::ClassEntry, }; /// Representation of a Zend class object in memory. @@ -105,7 +106,7 @@ impl ZendClassObject { unsafe fn internal_new(val: Option, ce: Option<&'static ClassEntry>) -> ZBox { let size = mem::size_of::>(); let meta = T::get_metadata(); - let ce = ce.unwrap_or_else(||meta.ce()) as *const _ as *mut _; + let ce = ce.unwrap_or_else(|| meta.ce()) as *const _ as *mut _; let obj = ext_php_rs_zend_object_alloc(size as _, ce) as *mut ZendClassObject; let obj = obj .as_mut() From 20c0d9c8cc66d139241e20a8a524657b3982b523 Mon Sep 17 00:00:00 2001 From: Joe Hoyle Date: Thu, 26 Oct 2023 16:05:55 +0200 Subject: [PATCH 078/100] Remove unused class --- src/types/class_object.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/types/class_object.rs b/src/types/class_object.rs index 811d446..f6124e6 100644 --- a/src/types/class_object.rs +++ b/src/types/class_object.rs @@ -14,8 +14,8 @@ use crate::{ convert::{FromZendObject, FromZendObjectMut, FromZval, FromZvalMut, IntoZval}, error::{Error, Result}, ffi::{ - _zend_class_entry, ext_php_rs_zend_object_alloc, ext_php_rs_zend_object_release, - object_properties_init, zend_object, zend_object_std_init, zend_objects_clone_members, + ext_php_rs_zend_object_alloc, ext_php_rs_zend_object_release, object_properties_init, + zend_object, zend_object_std_init, zend_objects_clone_members, }, flags::DataType, types::{ZendObject, Zval}, From bd54b3879f5b67ba0b43d1d0ecb7362b2b5f1974 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Fri, 17 Nov 2023 18:38:06 +0100 Subject: [PATCH 079/100] Fix #279 --- src/types/class_object.rs | 4 ++-- src/types/object.rs | 4 ++-- src/zend/function.rs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/types/class_object.rs b/src/types/class_object.rs index e9b1165..753933d 100644 --- a/src/types/class_object.rs +++ b/src/types/class_object.rs @@ -5,7 +5,7 @@ use std::{ fmt::Debug, mem, ops::{Deref, DerefMut}, - ptr::{self, NonNull}, + ptr::{self, NonNull}, os::raw::c_char, }; use crate::{ @@ -161,7 +161,7 @@ impl ZendClassObject { } fn _from_zend_obj(std: &zend_object) -> Option<&mut Self> { - let std = std as *const zend_object as *const i8; + let std = std as *const zend_object as *const c_char; let ptr = unsafe { let ptr = std.offset(0 - Self::std_offset() as isize) as *const Self; (ptr as *mut Self).as_mut()? diff --git a/src/types/object.rs b/src/types/object.rs index ca8e526..f17b65f 100644 --- a/src/types/object.rs +++ b/src/types/object.rs @@ -1,7 +1,7 @@ //! Represents an object in PHP. Allows for overriding the internal object used //! by classes, allowing users to store Rust data inside a PHP object. -use std::{convert::TryInto, fmt::Debug, ops::DerefMut}; +use std::{convert::TryInto, fmt::Debug, ops::DerefMut, os::raw::c_char}; use crate::{ boxed::{ZBox, ZBoxable}, @@ -146,7 +146,7 @@ impl ZendObject { unsafe { let res = zend_hash_str_find_ptr_lc( &(*self.ce).function_table, - name.as_ptr() as *const i8, + name.as_ptr() as *const c_char, name.len(), ) as *mut zend_function; if res.is_null() { diff --git a/src/zend/function.rs b/src/zend/function.rs index a1ef866..fb5c4d4 100644 --- a/src/zend/function.rs +++ b/src/zend/function.rs @@ -52,7 +52,7 @@ 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()); + let res = zend_fetch_function_str(name.as_ptr() as *const c_char, name.len()); if res.is_null() { return None; } @@ -65,7 +65,7 @@ impl Function { Some(ce) => unsafe { let res = zend_hash_str_find_ptr_lc( &ce.function_table, - name.as_ptr() as *const i8, + name.as_ptr() as *const c_char, name.len(), ) as *mut zend_function; if res.is_null() { From 602a5830ae19787e27f1e669ba02a897b4a88d94 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Fri, 17 Nov 2023 18:39:02 +0100 Subject: [PATCH 080/100] Bump --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index f2af962..a484175 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/davidcole1340/ext-php-rs" homepage = "https://github.com/davidcole1340/ext-php-rs" license = "MIT OR Apache-2.0" keywords = ["php", "ffi", "zend"] -version = "0.10.3" +version = "0.10.4" authors = ["David Cole "] edition = "2018" categories = ["api-bindings"] From c87dc4b9d980e72eb760dcda3c869f7882c3c204 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Fri, 17 Nov 2023 18:42:03 +0100 Subject: [PATCH 081/100] Fmt --- src/embed/mod.rs | 26 +++++++++++++++----------- src/types/class_object.rs | 3 ++- src/zend/ini_entry_def.rs | 5 +++-- src/zend/try_catch.rs | 10 ++++++---- 4 files changed, 26 insertions(+), 18 deletions(-) diff --git a/src/embed/mod.rs b/src/embed/mod.rs index 0ad64a6..d8f914f 100644 --- a/src/embed/mod.rs +++ b/src/embed/mod.rs @@ -1,8 +1,9 @@ //! Provides implementations for running php code from rust. //! It only works on linux for now and you should have `php-embed` installed //! -//! This crate was only test with PHP 8.2 please report any issue with other version -//! You should only use this crate for test purpose, it's not production ready +//! This crate was only test with PHP 8.2 please report any issue with other +//! version You should only use this crate for test purpose, it's not production +//! ready mod ffi; @@ -43,13 +44,14 @@ static RUN_FN_LOCK: RwLock<()> = const_rwlock(()); impl Embed { /// Run a php script from a file /// - /// This function will only work correctly when used inside the `Embed::run` function - /// otherwise behavior is unexpected + /// This function will only work correctly when used inside the `Embed::run` + /// function otherwise behavior is unexpected /// /// # Returns /// /// * `Ok(())` - The script was executed successfully - /// * `Err(EmbedError)` - An error occured during the execution of the script + /// * `Err(EmbedError)` - An error occured during the execution of the + /// script /// /// # Example /// @@ -97,10 +99,10 @@ impl Embed { /// Start and run embed sapi engine /// - /// This function will allow to run php code from rust, the same PHP context is keep between calls - /// inside the function passed to this method. - /// Which means subsequent calls to `Embed::eval` or `Embed::run_script` will be able to access - /// variables defined in previous calls + /// This function will allow to run php code from rust, the same PHP context + /// is keep between calls inside the function passed to this method. + /// Which means subsequent calls to `Embed::eval` or `Embed::run_script` + /// will be able to access variables defined in previous calls /// /// # Returns /// @@ -127,7 +129,8 @@ impl Embed { // @TODO handle php thread safe // // This is to prevent multiple threads from running php at the same time - // At some point we should detect if php is compiled with thread safety and avoid doing that in this case + // At some point we should detect if php is compiled with thread safety and + // avoid doing that in this case let _guard = RUN_FN_LOCK.write(); let panic = unsafe { @@ -155,7 +158,8 @@ impl Embed { /// Evaluate a php code /// - /// This function will only work correctly when used inside the `Embed::run` function + /// This function will only work correctly when used inside the `Embed::run` + /// function /// /// # Returns /// diff --git a/src/types/class_object.rs b/src/types/class_object.rs index 753933d..9b63b50 100644 --- a/src/types/class_object.rs +++ b/src/types/class_object.rs @@ -5,7 +5,8 @@ use std::{ fmt::Debug, mem, ops::{Deref, DerefMut}, - ptr::{self, NonNull}, os::raw::c_char, + os::raw::c_char, + ptr::{self, NonNull}, }; use crate::{ diff --git a/src/zend/ini_entry_def.rs b/src/zend/ini_entry_def.rs index 8817300..d9c8f64 100644 --- a/src/zend/ini_entry_def.rs +++ b/src/zend/ini_entry_def.rs @@ -7,8 +7,9 @@ use crate::{ffi::zend_ini_entry_def, ffi::zend_register_ini_entries, flags::IniE /// A Zend ini entry definition. /// -/// To register ini definitions for extensions, the IniEntryDef builder should be used. Ini -/// entries should be registered in your module's startup_function via `IniEntryDef::register(Vec)`. +/// To register ini definitions for extensions, the IniEntryDef builder should +/// be used. Ini entries should be registered in your module's startup_function +/// via `IniEntryDef::register(Vec)`. pub type IniEntryDef = zend_ini_entry_def; impl IniEntryDef { diff --git a/src/zend/try_catch.rs b/src/zend/try_catch.rs index f74b427..37cd896 100644 --- a/src/zend/try_catch.rs +++ b/src/zend/try_catch.rs @@ -18,7 +18,8 @@ pub(crate) unsafe extern "C" fn panic_wrapper R + RefUnwindSafe /// PHP propose a try catch mechanism in C using setjmp and longjmp (bailout) /// It store the arg of setjmp into the bailout field of the global executor -/// If a bailout is triggered, the executor will jump to the setjmp and restore the previous setjmp +/// If a bailout is triggered, the executor will jump to the setjmp and restore +/// the previous setjmp /// /// try_catch allow to use this mechanism /// @@ -60,10 +61,11 @@ pub fn try_catch R + RefUnwindSafe>(func: F) -> Result ! { ext_php_rs_zend_bailout(); } From d8a5a09b1ad8ebd3ea3e9b0430e4d52bb7594ef8 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Fri, 17 Nov 2023 18:59:13 +0100 Subject: [PATCH 082/100] Cleanup docs --- guide/src/SUMMARY.md | 3 +- guide/src/macros/async_impl.md | 130 +++++++++++++++++++++++++++++++ guide/src/macros/impl.md | 135 +-------------------------------- 3 files changed, 134 insertions(+), 134 deletions(-) create mode 100644 guide/src/macros/async_impl.md diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md index 4108d1b..db61411 100644 --- a/guide/src/SUMMARY.md +++ b/guide/src/SUMMARY.md @@ -24,13 +24,14 @@ - [Class Object](./types/class_object.md) - [Closure](./types/closure.md) - [Functions & methods](./types/functions.md) - - [Async futures](./macros/impl.md#async) + - [Async futures](./macros/async_impl.md) - [Macros](./macros/index.md) - [Module](./macros/module.md) - [Module Startup Function](./macros/module_startup.md) - [Function](./macros/function.md) - [Classes](./macros/classes.md) - [`impl`s](./macros/impl.md) + - [async `impl`s](./macros/async_impl.md) - [Constants](./macros/constant.md) - [`ZvalConvert`](./macros/zval_convert.md) - [Exceptions](./exceptions.md) diff --git a/guide/src/macros/async_impl.md b/guide/src/macros/async_impl.md new file mode 100644 index 0000000..0912001 --- /dev/null +++ b/guide/src/macros/async_impl.md @@ -0,0 +1,130 @@ +# `#[php_async_impl]` + +Using `#[php_async_impl]` instead of `#[php_impl]` allows us to expose any async Rust library to PHP, using [PHP fibers](https://www.php.net/manual/en/language.fibers.php), [php-tokio](https://github.com/danog/php-tokio) and the [PHP Revolt event loop](https://revolt.run) under the hood to handle async interoperability. + +This allows full compatibility with [amphp](https://amphp.org), [PSL](https://github.com/azjezz/psl), [reactphp](https://reactphp.org) and any other async PHP library based on [Revolt](https://revolt.run). + +Traits annotated with `#[php_impl]` can freely expose any async function, using `await` and any async Rust library. + +Make sure to also expose the `php_tokio::EventLoop::init` and `php_tokio::EventLoop::wakeup` functions to PHP in order to initialize the event loop, as specified in the full example [here »](#async-example). + +Also, make sure to invoke `EventLoop::shutdown` in the request shutdown handler to clean up the tokio event loop before finishing the request. + +## Async example + +In this example, we're exposing an async Rust HTTP client library called [reqwest](https://docs.rs/reqwest/latest/reqwest/) to PHP, using [PHP fibers](https://www.php.net/manual/en/language.fibers.php), [php-tokio](https://github.com/danog/php-tokio) and the [PHP Revolt event loop](https://revolt.run) under the hood to handle async interoperability. + +This allows full compatibility with [amphp](https://amphp.org), [PSL](https://github.com/azjezz/psl), [reactphp](https://reactphp.org) and any other async PHP library based on [Revolt](https://revolt.run). + +Make sure to require [php-tokio](https://github.com/danog/php-tokio) as a dependency before proceeding. + +```rust,ignore +use ext_php_rs::prelude::*; +use php_tokio::{php_async_impl, EventLoop}; + +#[php_class] +struct Client {} + +#[php_async_impl] +impl Client { + pub fn init() -> PhpResult { + EventLoop::init() + } + pub fn wakeup() -> PhpResult<()> { + EventLoop::wakeup() + } + pub async fn get(url: &str) -> anyhow::Result { + Ok(reqwest::get(url).await?.text().await?) + } +} + +pub extern "C" fn request_shutdown(_type: i32, _module_number: i32) -> i32 { + EventLoop::shutdown(); + 0 +} + +#[php_module] +pub fn get_module(module: ModuleBuilder) -> ModuleBuilder { + module.request_shutdown_function(request_shutdown) +} +``` + +Here's the async PHP code we use to interact with the Rust class we just exposed. + +The `Client::init` method needs to be called only once in order to initialize the Revolt event loop and link it to the Tokio event loop, as shown by the following code. + +See [here »](https://amphp.org) for more info on async PHP using [amphp](https://amphp.org) + [revolt](https://revolt.run). + +```php + \Client::wakeup()); + } + + public static function reference(): void + { + EventLoop::reference(self::$id); + } + public static function unreference(): void + { + EventLoop::unreference(self::$id); + } + + public static function __callStatic(string $name, array $args): mixed + { + return \Client::$name(...$args); + } +} + + +Client::init(); + +function test(int $delay): void +{ + $url = "https://httpbin.org/delay/$delay"; + $t = time(); + echo "Making async reqwest to $url that will return after $delay seconds...".PHP_EOL; + Client::get($url); + $t = time() - $t; + echo "Got response from $url after ~".$t." seconds!".PHP_EOL; +}; + +$futures = []; +$futures []= async(test(...), 5); +$futures []= async(test(...), 5); +$futures []= async(test(...), 5); + +await($futures); +``` + +Result: + +``` +Making async reqwest to https://httpbin.org/delay/5 that will return after 5 seconds... +Making async reqwest to https://httpbin.org/delay/5 that will return after 5 seconds... +Making async reqwest to https://httpbin.org/delay/5 that will return after 5 seconds... +Got response from https://httpbin.org/delay/5 after ~5 seconds! +Got response from https://httpbin.org/delay/5 after ~5 seconds! +Got response from https://httpbin.org/delay/5 after ~5 seconds! +``` + +[`php_function`]: ./function.md diff --git a/guide/src/macros/impl.md b/guide/src/macros/impl.md index 1b872f1..db12eca 100644 --- a/guide/src/macros/impl.md +++ b/guide/src/macros/impl.md @@ -8,7 +8,7 @@ implementations cannot be exported to PHP. If you do not want a function exported to PHP, you should place it in a separate `impl` block. -If you want to use async Rust, use `#[php_async_impl]`, instead: see [here »](#async) for more info. +If you want to use async Rust, use `#[php_async_impl]`, instead: see [here »](./async_impl.md) for more info. ## Methods @@ -65,20 +65,6 @@ the attribute, the function is not exported to PHP like a regular method. Constructors cannot use the visibility or rename attributes listed above. -### Async - -Using `#[php_async_impl]` instead of `#[php_impl]` allows us to expose any async Rust library to PHP, using [PHP fibers](https://www.php.net/manual/en/language.fibers.php), [php-tokio](https://github.com/danog/php-tokio) and the [PHP Revolt event loop](https://revolt.run) under the hood to handle async interoperability. - -This allows full compatibility with [amphp](https://amphp.org), [PSL](https://github.com/azjezz/psl), [reactphp](https://reactphp.org) and any other async PHP library based on [Revolt](https://revolt.run). - -Traits annotated with `#[php_impl]` can freely expose any async function, using `await` and any async Rust library. - -Make sure to also expose the `php_tokio::EventLoop::init` and `php_tokio::EventLoop::wakeup` functions to PHP in order to initialize the event loop, as specified in the full example [here »](#async-example). - -Also, make sure to invoke `EventLoop::shutdown` in the request shutdown handler to clean up the tokio event loop before finishing the request. - -See [here »](#async-example) for the full example. - ## Constants Constants are defined as regular Rust `impl` constants. Any type that implements @@ -178,121 +164,4 @@ var_dump(Human::get_max_age()); // int(100) var_dump(Human::MAX_AGE); // int(100) ``` -### Async example - -In this example, we're exposing an async Rust HTTP client library called [reqwest](https://docs.rs/reqwest/latest/reqwest/) to PHP, using [PHP fibers](https://www.php.net/manual/en/language.fibers.php), [php-tokio](https://github.com/danog/php-tokio) and the [PHP Revolt event loop](https://revolt.run) under the hood to handle async interoperability. - -This allows full compatibility with [amphp](https://amphp.org), [PSL](https://github.com/azjezz/psl), [reactphp](https://reactphp.org) and any other async PHP library based on [Revolt](https://revolt.run). - -Make sure to require [php-tokio](https://github.com/danog/php-tokio) as a dependency before proceeding. - -```rust,ignore -use ext_php_rs::prelude::*; -use php_tokio::{php_async_impl, EventLoop}; - -#[php_class] -struct Client {} - -#[php_async_impl] -impl Client { - pub fn init() -> PhpResult { - EventLoop::init() - } - pub fn wakeup() -> PhpResult<()> { - EventLoop::wakeup() - } - pub async fn get(url: &str) -> anyhow::Result { - Ok(reqwest::get(url).await?.text().await?) - } -} - -pub extern "C" fn request_shutdown(_type: i32, _module_number: i32) -> i32 { - EventLoop::shutdown(); - 0 -} - -#[php_module] -pub fn get_module(module: ModuleBuilder) -> ModuleBuilder { - module.request_shutdown_function(request_shutdown) -} -``` - -Here's the async PHP code we use to interact with the Rust class we just exposed. - -The `Client::init` method needs to be called only once in order to initialize the Revolt event loop and link it to the Tokio event loop, as shown by the following code. - -See [here »](https://amphp.org) for more info on async PHP using [amphp](https://amphp.org) + [revolt](https://revolt.run). - -```php - \Client::wakeup()); - } - - public static function reference(): void - { - EventLoop::reference(self::$id); - } - public static function unreference(): void - { - EventLoop::unreference(self::$id); - } - - public static function __callStatic(string $name, array $args): mixed - { - return \Client::$name(...$args); - } -} - - -Client::init(); - -function test(int $delay): void -{ - $url = "https://httpbin.org/delay/$delay"; - $t = time(); - echo "Making async reqwest to $url that will return after $delay seconds...".PHP_EOL; - Client::get($url); - $t = time() - $t; - echo "Got response from $url after ~".$t." seconds!".PHP_EOL; -}; - -$futures = []; -$futures []= async(test(...), 5); -$futures []= async(test(...), 5); -$futures []= async(test(...), 5); - -await($futures); -``` - -Result: - -``` -Making async reqwest to https://httpbin.org/delay/5 that will return after 5 seconds... -Making async reqwest to https://httpbin.org/delay/5 that will return after 5 seconds... -Making async reqwest to https://httpbin.org/delay/5 that will return after 5 seconds... -Got response from https://httpbin.org/delay/5 after ~5 seconds! -Got response from https://httpbin.org/delay/5 after ~5 seconds! -Got response from https://httpbin.org/delay/5 after ~5 seconds! -``` - -[`php_function`]: ./function.md +[`php_async_impl`]: ./async_impl.md From b81128acafb118036e77afd9ad2a0266061abc5e Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Fri, 17 Nov 2023 19:04:02 +0100 Subject: [PATCH 083/100] Fix typo --- guide/src/macros/async_impl.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guide/src/macros/async_impl.md b/guide/src/macros/async_impl.md index 0912001..979c58b 100644 --- a/guide/src/macros/async_impl.md +++ b/guide/src/macros/async_impl.md @@ -4,7 +4,7 @@ Using `#[php_async_impl]` instead of `#[php_impl]` allows us to expose any async This allows full compatibility with [amphp](https://amphp.org), [PSL](https://github.com/azjezz/psl), [reactphp](https://reactphp.org) and any other async PHP library based on [Revolt](https://revolt.run). -Traits annotated with `#[php_impl]` can freely expose any async function, using `await` and any async Rust library. +Traits annotated with `#[php_async_impl]` can freely expose any async function, using `await` and any async Rust library. Make sure to also expose the `php_tokio::EventLoop::init` and `php_tokio::EventLoop::wakeup` functions to PHP in order to initialize the event loop, as specified in the full example [here »](#async-example). From f621e60be66425d3bf3ac1a5ebccf675cdda263c Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Tue, 21 Nov 2023 20:13:57 +0100 Subject: [PATCH 084/100] Add PHP 8.3, SAPI globals support --- .github/workflows/build.yml | 2 +- allowed_bindings.rs | 4 + build.rs | 2 +- docsrs_bindings.rs | 848 ++++++++++++++++++++++++++++++++---- src/ffi.rs | 1 + src/wrapper.c | 12 + src/wrapper.h | 4 +- src/zend/globals.rs | 45 +- src/zend/mod.rs | 1 + 9 files changed, 820 insertions(+), 99 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fe68b07..557d80c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,7 +15,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - php: ["8.0", "8.1", "8.2"] + php: ["8.0", "8.1", "8.2", "8.3"] rust: [stable, nightly] clang: ["14"] phpts: [ts, nts] diff --git a/allowed_bindings.rs b/allowed_bindings.rs index ca2195b..53e5a5e 100644 --- a/allowed_bindings.rs +++ b/allowed_bindings.rs @@ -26,6 +26,7 @@ bind! { _efree, _emalloc, _zend_executor_globals, + _sapi_globals_struct, _zend_expected_type, _zend_expected_type_Z_EXPECTED_ARRAY, _zend_expected_type_Z_EXPECTED_BOOL, @@ -241,6 +242,7 @@ bind! { zend_class_serialize_deny, zend_class_unserialize_deny, zend_executor_globals, + sapi_globals_struct, zend_objects_store_del, zend_hash_move_forward_ex, zend_hash_get_current_key_type_ex, @@ -251,10 +253,12 @@ bind! { gc_possible_root, ZEND_ACC_NOT_SERIALIZABLE, executor_globals, + sapi_globals, php_printf, __zend_malloc, tsrm_get_ls_cache, executor_globals_offset, + sapi_globals_offset, zend_atomic_bool_store, zend_interrupt_function, zend_eval_string, diff --git a/build.rs b/build.rs index 78c9cf3..601a0c3 100644 --- a/build.rs +++ b/build.rs @@ -16,7 +16,7 @@ use bindgen::RustTarget; use impl_::Provider; const MIN_PHP_API_VER: u32 = 20200930; -const MAX_PHP_API_VER: u32 = 20220829; +const MAX_PHP_API_VER: u32 = 20230831; pub trait PHPProvider<'a>: Sized { /// Create a new PHP provider. diff --git a/docsrs_bindings.rs b/docsrs_bindings.rs index f7e54f1..dd3978a 100644 --- a/docsrs_bindings.rs +++ b/docsrs_bindings.rs @@ -1,5 +1,85 @@ /* automatically generated by rust-bindgen 0.68.1 */ +#[repr(C)] +#[derive(Copy, Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct __BindgenBitfieldUnit { + storage: Storage, +} +impl __BindgenBitfieldUnit { + #[inline] + pub const fn new(storage: Storage) -> Self { + Self { storage } + } +} +impl __BindgenBitfieldUnit +where + Storage: AsRef<[u8]> + AsMut<[u8]>, +{ + #[inline] + pub fn get_bit(&self, index: usize) -> bool { + debug_assert!(index / 8 < self.storage.as_ref().len()); + let byte_index = index / 8; + let byte = self.storage.as_ref()[byte_index]; + let bit_index = if cfg!(target_endian = "big") { + 7 - (index % 8) + } else { + index % 8 + }; + let mask = 1 << bit_index; + byte & mask == mask + } + #[inline] + pub fn set_bit(&mut self, index: usize, val: bool) { + debug_assert!(index / 8 < self.storage.as_ref().len()); + let byte_index = index / 8; + let byte = &mut self.storage.as_mut()[byte_index]; + let bit_index = if cfg!(target_endian = "big") { + 7 - (index % 8) + } else { + index % 8 + }; + let mask = 1 << bit_index; + if val { + *byte |= mask; + } else { + *byte &= !mask; + } + } + #[inline] + pub fn get(&self, bit_offset: usize, bit_width: u8) -> u64 { + debug_assert!(bit_width <= 64); + debug_assert!(bit_offset / 8 < self.storage.as_ref().len()); + debug_assert!((bit_offset + (bit_width as usize)) / 8 <= self.storage.as_ref().len()); + let mut val = 0; + for i in 0..(bit_width as usize) { + if self.get_bit(i + bit_offset) { + let index = if cfg!(target_endian = "big") { + bit_width as usize - 1 - i + } else { + i + }; + val |= 1 << index; + } + } + val + } + #[inline] + pub fn set(&mut self, bit_offset: usize, bit_width: u8, val: u64) { + debug_assert!(bit_width <= 64); + debug_assert!(bit_offset / 8 < self.storage.as_ref().len()); + debug_assert!((bit_offset + (bit_width as usize)) / 8 <= self.storage.as_ref().len()); + for i in 0..(bit_width as usize) { + let mask = 1 << i; + let val_bit_is_set = val & mask == mask; + let index = if cfg!(target_endian = "big") { + bit_width as usize - 1 - i + } else { + i + }; + self.set_bit(index + bit_offset, val_bit_is_set); + } + } +} pub const ZEND_DEBUG: u32 = 1; pub const _ZEND_TYPE_NAME_BIT: u32 = 16777216; pub const _ZEND_TYPE_NULLABLE_BIT: u32 = 2; @@ -97,7 +177,7 @@ pub const ZEND_EVAL_CODE: u32 = 4; pub const ZEND_ISEMPTY: u32 = 1; pub const _ZEND_SEND_MODE_SHIFT: u32 = 25; pub const _ZEND_IS_VARIADIC_BIT: u32 = 134217728; -pub const ZEND_MODULE_API_NO: u32 = 20220829; +pub const ZEND_MODULE_API_NO: u32 = 20230831; pub const USING_ZTS: u32 = 0; pub const MAY_BE_BOOL: u32 = 12; pub const MAY_BE_ANY: u32 = 1022; @@ -109,68 +189,82 @@ 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; -pub type __int64_t = ::std::os::raw::c_longlong; -pub type __darwin_off_t = __int64_t; -pub type fpos_t = __darwin_off_t; +pub type __dev_t = ::std::os::raw::c_ulong; +pub type __uid_t = ::std::os::raw::c_uint; +pub type __gid_t = ::std::os::raw::c_uint; +pub type __ino_t = ::std::os::raw::c_ulong; +pub type __mode_t = ::std::os::raw::c_uint; +pub type __nlink_t = ::std::os::raw::c_ulong; +pub type __off_t = ::std::os::raw::c_long; +pub type __off64_t = ::std::os::raw::c_long; +pub type __time_t = ::std::os::raw::c_long; +pub type __blksize_t = ::std::os::raw::c_long; +pub type __blkcnt_t = ::std::os::raw::c_long; +pub type __syscall_slong_t = ::std::os::raw::c_long; #[repr(C)] #[derive(Debug, Copy, Clone)] -pub struct __sbuf { - pub _base: *mut ::std::os::raw::c_uchar, - pub _size: ::std::os::raw::c_int, +pub struct __sigset_t { + pub __val: [::std::os::raw::c_ulong; 16usize], } #[repr(C)] #[derive(Debug, Copy, Clone)] -pub struct __sFILEX { +pub struct timespec { + pub tv_sec: __time_t, + pub tv_nsec: __syscall_slong_t, +} +pub type FILE = _IO_FILE; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _IO_marker { _unused: [u8; 0], } #[repr(C)] #[derive(Debug, Copy, Clone)] -pub struct __sFILE { - pub _p: *mut ::std::os::raw::c_uchar, - pub _r: ::std::os::raw::c_int, - pub _w: ::std::os::raw::c_int, - pub _flags: ::std::os::raw::c_short, - pub _file: ::std::os::raw::c_short, - pub _bf: __sbuf, - pub _lbfsize: ::std::os::raw::c_int, - pub _cookie: *mut ::std::os::raw::c_void, - pub _close: ::std::option::Option< - unsafe extern "C" fn(arg1: *mut ::std::os::raw::c_void) -> ::std::os::raw::c_int, - >, - pub _read: ::std::option::Option< - unsafe extern "C" fn( - arg1: *mut ::std::os::raw::c_void, - arg2: *mut ::std::os::raw::c_char, - arg3: ::std::os::raw::c_int, - ) -> ::std::os::raw::c_int, - >, - pub _seek: ::std::option::Option< - unsafe extern "C" fn( - arg1: *mut ::std::os::raw::c_void, - arg2: fpos_t, - arg3: ::std::os::raw::c_int, - ) -> fpos_t, - >, - pub _write: ::std::option::Option< - unsafe extern "C" fn( - arg1: *mut ::std::os::raw::c_void, - arg2: *const ::std::os::raw::c_char, - arg3: ::std::os::raw::c_int, - ) -> ::std::os::raw::c_int, - >, - pub _ub: __sbuf, - pub _extra: *mut __sFILEX, - pub _ur: ::std::os::raw::c_int, - pub _ubuf: [::std::os::raw::c_uchar; 3usize], - pub _nbuf: [::std::os::raw::c_uchar; 1usize], - pub _lb: __sbuf, - pub _blksize: ::std::os::raw::c_int, - pub _offset: fpos_t, +pub struct _IO_codecvt { + _unused: [u8; 0], +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _IO_wide_data { + _unused: [u8; 0], +} +pub type _IO_lock_t = ::std::os::raw::c_void; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _IO_FILE { + pub _flags: ::std::os::raw::c_int, + pub _IO_read_ptr: *mut ::std::os::raw::c_char, + pub _IO_read_end: *mut ::std::os::raw::c_char, + pub _IO_read_base: *mut ::std::os::raw::c_char, + pub _IO_write_base: *mut ::std::os::raw::c_char, + pub _IO_write_ptr: *mut ::std::os::raw::c_char, + pub _IO_write_end: *mut ::std::os::raw::c_char, + pub _IO_buf_base: *mut ::std::os::raw::c_char, + pub _IO_buf_end: *mut ::std::os::raw::c_char, + pub _IO_save_base: *mut ::std::os::raw::c_char, + pub _IO_backup_base: *mut ::std::os::raw::c_char, + pub _IO_save_end: *mut ::std::os::raw::c_char, + pub _markers: *mut _IO_marker, + pub _chain: *mut _IO_FILE, + pub _fileno: ::std::os::raw::c_int, + pub _flags2: ::std::os::raw::c_int, + pub _old_offset: __off_t, + pub _cur_column: ::std::os::raw::c_ushort, + pub _vtable_offset: ::std::os::raw::c_schar, + pub _shortbuf: [::std::os::raw::c_char; 1usize], + pub _lock: *mut _IO_lock_t, + pub _offset: __off64_t, + pub _codecvt: *mut _IO_codecvt, + pub _wide_data: *mut _IO_wide_data, + pub _freeres_list: *mut _IO_FILE, + pub _freeres_buf: *mut ::std::os::raw::c_void, + pub __pad5: usize, + pub _mode: ::std::os::raw::c_int, + pub _unused2: [::std::os::raw::c_char; 20usize], } -pub type FILE = __sFILE; pub type zend_long = i64; pub type zend_ulong = u64; -pub type zend_uchar = ::std::os::raw::c_uchar; +pub type zend_off_t = i64; pub const ZEND_RESULT_CODE_SUCCESS: ZEND_RESULT_CODE = 0; pub const ZEND_RESULT_CODE_FAILURE: ZEND_RESULT_CODE = -1; pub type ZEND_RESULT_CODE = ::std::os::raw::c_int; @@ -234,8 +328,8 @@ pub union _zval_struct__bindgen_ty_1 { #[repr(C)] #[derive(Copy, Clone)] pub struct _zval_struct__bindgen_ty_1__bindgen_ty_1 { - pub type_: zend_uchar, - pub type_flags: zend_uchar, + pub type_: u8, + pub type_flags: u8, pub u: _zval_struct__bindgen_ty_1__bindgen_ty_1__bindgen_ty_1, } #[repr(C)] @@ -253,7 +347,7 @@ pub union _zval_struct__bindgen_ty_2 { pub num_args: u32, pub fe_pos: u32, pub fe_iter_idx: u32, - pub property_guard: u32, + pub guard: u32, pub constant_flags: u32, pub extra: u32, } @@ -311,10 +405,10 @@ pub union _zend_array__bindgen_ty_1 { #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct _zend_array__bindgen_ty_1__bindgen_ty_1 { - pub flags: zend_uchar, - pub _unused: zend_uchar, - pub nIteratorsCount: zend_uchar, - pub _unused2: zend_uchar, + pub flags: u8, + pub _unused: u8, + pub nIteratorsCount: u8, + pub _unused2: u8, } #[repr(C)] #[derive(Copy, Clone)] @@ -329,6 +423,7 @@ pub type HashPosition = u32; pub struct _HashTableIterator { pub ht: *mut HashTable, pub pos: HashPosition, + pub next_copy: u32, } pub type HashTableIterator = _HashTableIterator; #[repr(C)] @@ -386,6 +481,28 @@ extern "C" { extern "C" { pub fn __zend_malloc(len: usize) -> *mut ::std::os::raw::c_void; } +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _zend_llist_element { + pub next: *mut _zend_llist_element, + pub prev: *mut _zend_llist_element, + pub data: [::std::os::raw::c_char; 1usize], +} +pub type zend_llist_element = _zend_llist_element; +pub type llist_dtor_func_t = + ::std::option::Option; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _zend_llist { + pub head: *mut zend_llist_element, + pub tail: *mut zend_llist_element, + pub count: usize, + pub size: usize, + pub dtor: llist_dtor_func_t, + pub persistent: ::std::os::raw::c_uchar, + pub traverse_ptr: *mut zend_llist_element, +} +pub type zend_llist = _zend_llist; pub type zend_string_init_interned_func_t = ::std::option::Option< unsafe extern "C" fn( str_: *const ::std::os::raw::c_char, @@ -541,6 +658,25 @@ pub struct _zend_class_arrayaccess_funcs { pub zf_offsetunset: *mut zend_function, } pub type zend_class_arrayaccess_funcs = _zend_class_arrayaccess_funcs; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct stat { + pub st_dev: __dev_t, + pub st_ino: __ino_t, + pub st_nlink: __nlink_t, + pub st_mode: __mode_t, + pub st_uid: __uid_t, + pub st_gid: __gid_t, + pub __pad0: ::std::os::raw::c_int, + pub st_rdev: __dev_t, + pub st_size: __off_t, + pub st_blksize: __blksize_t, + pub st_blocks: __blkcnt_t, + pub st_atim: timespec, + pub st_mtim: timespec, + pub st_ctim: timespec, + pub __glibc_reserved: [__syscall_slong_t; 3usize], +} pub type zend_stream_fsizer_t = ::std::option::Option usize>; pub type zend_stream_reader_t = ::std::option::Option< @@ -568,7 +704,7 @@ pub struct _zend_file_handle { pub handle: _zend_file_handle__bindgen_ty_1, pub filename: *mut zend_string, pub opened_path: *mut zend_string, - pub type_: zend_uchar, + pub type_: u8, pub primary_script: bool, pub in_list: bool, pub buf: *mut ::std::os::raw::c_char, @@ -587,6 +723,7 @@ extern "C" { filename: *const ::std::os::raw::c_char, ); } +pub type zend_stat_t = stat; #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct _zend_serialize_data { @@ -700,6 +837,7 @@ pub struct _zend_class_entry { pub __debugInfo: *mut zend_function, pub __serialize: *mut zend_function, pub __unserialize: *mut zend_function, + pub default_object_handlers: *const zend_object_handlers, pub iterator_funcs_ptr: *mut zend_class_iterator_funcs, pub arrayaccess_funcs_ptr: *mut zend_class_arrayaccess_funcs, pub __bindgen_anon_2: _zend_class_entry__bindgen_ty_2, @@ -929,7 +1067,7 @@ pub type zend_object_get_gc_t = ::std::option::Option< >; pub type zend_object_do_operation_t = ::std::option::Option< unsafe extern "C" fn( - opcode: zend_uchar, + opcode: u8, result: *mut zval, op1: *mut zval, op2: *mut zval, @@ -996,10 +1134,10 @@ extern "C" { ) -> ::std::os::raw::c_int; } extern "C" { - pub fn zend_is_identical(op1: *mut zval, op2: *mut zval) -> bool; + pub fn zend_is_identical(op1: *const zval, op2: *const zval) -> bool; } extern "C" { - pub fn zend_is_true(op: *mut zval) -> ::std::os::raw::c_int; + pub fn zend_is_true(op: *const zval) -> ::std::os::raw::c_int; } pub type zend_op_array = _zend_op_array; pub type zend_op = _zend_op; @@ -1022,10 +1160,10 @@ pub struct _zend_op { pub result: znode_op, pub extended_value: u32, pub lineno: u32, - pub opcode: zend_uchar, - pub op1_type: zend_uchar, - pub op2_type: zend_uchar, - pub result_type: zend_uchar, + pub opcode: u8, + pub op1_type: u8, + pub op2_type: u8, + pub result_type: u8, } #[repr(C)] #[derive(Debug, Copy, Clone)] @@ -1074,8 +1212,8 @@ pub type zend_arg_info = _zend_arg_info; #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct _zend_op_array { - pub type_: zend_uchar, - pub arg_flags: [zend_uchar; 3usize], + pub type_: u8, + pub arg_flags: [u8; 3usize], pub fn_flags: u32, pub function_name: *mut zend_string, pub scope: *mut zend_class_entry, @@ -1084,8 +1222,8 @@ pub struct _zend_op_array { pub required_num_args: u32, pub arg_info: *mut zend_arg_info, pub attributes: *mut HashTable, - pub T: u32, pub run_time_cache__ptr: *mut *mut ::std::os::raw::c_void, + pub T: u32, pub cache_size: ::std::os::raw::c_int, pub last_var: ::std::os::raw::c_int, pub last: u32, @@ -1114,8 +1252,8 @@ pub type zif_handler = ::std::option::Option< #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct _zend_internal_function { - pub type_: zend_uchar, - pub arg_flags: [zend_uchar; 3usize], + pub type_: u8, + pub arg_flags: [u8; 3usize], pub fn_flags: u32, pub function_name: *mut zend_string, pub scope: *mut zend_class_entry, @@ -1124,8 +1262,8 @@ pub struct _zend_internal_function { pub required_num_args: u32, pub arg_info: *mut zend_internal_arg_info, pub attributes: *mut HashTable, - pub T: u32, pub run_time_cache__ptr: *mut *mut ::std::os::raw::c_void, + pub T: u32, pub handler: zif_handler, pub module: *mut _zend_module_entry, pub reserved: [*mut ::std::os::raw::c_void; 6usize], @@ -1134,7 +1272,7 @@ pub type zend_internal_function = _zend_internal_function; #[repr(C)] #[derive(Copy, Clone)] pub union _zend_function { - pub type_: zend_uchar, + pub type_: u8, pub quick_arg_flags: u32, pub common: _zend_function__bindgen_ty_1, pub op_array: zend_op_array, @@ -1143,8 +1281,8 @@ pub union _zend_function { #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct _zend_function__bindgen_ty_1 { - pub type_: zend_uchar, - pub arg_flags: [zend_uchar; 3usize], + pub type_: u8, + pub arg_flags: [u8; 3usize], pub fn_flags: u32, pub function_name: *mut zend_string, pub scope: *mut zend_class_entry, @@ -1153,8 +1291,8 @@ pub struct _zend_function__bindgen_ty_1 { pub required_num_args: u32, pub arg_info: *mut zend_arg_info, pub attributes: *mut HashTable, - pub T: u32, pub run_time_cache__ptr: *mut *mut ::std::os::raw::c_void, + pub T: u32, } #[repr(C)] pub struct _zend_execute_data { @@ -1168,7 +1306,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; @@ -1215,6 +1361,13 @@ pub type zend_objects_store = _zend_objects_store; extern "C" { pub fn zend_objects_store_del(object: *mut zend_object); } +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _zend_call_stack { + pub base: *mut ::std::os::raw::c_void, + pub max_size: usize, +} +pub type zend_call_stack = _zend_call_stack; pub type zend_vm_stack = *mut _zend_vm_stack; pub type zend_ini_entry = _zend_ini_entry; #[repr(C)] @@ -1238,7 +1391,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, @@ -1251,29 +1404,32 @@ pub struct _zend_executor_globals { pub current_execute_data: *mut _zend_execute_data, pub fake_scope: *mut zend_class_entry, pub jit_trace_num: u32, - pub precision: zend_long, pub ticks_count: ::std::os::raw::c_int, + pub precision: zend_long, pub persistent_constants_count: u32, pub persistent_functions_count: u32, pub persistent_classes_count: u32, - pub in_autoload: *mut HashTable, - pub full_tables_cleanup: bool, pub no_extensions: bool, + pub full_tables_cleanup: bool, pub vm_interrupt: zend_atomic_bool, pub timed_out: zend_atomic_bool, + pub in_autoload: *mut HashTable, pub hard_timeout: zend_long, + pub stack_base: *mut ::std::os::raw::c_void, + pub stack_limit: *mut ::std::os::raw::c_void, pub regular_list: HashTable, pub persistent_list: HashTable, pub user_error_handler_error_reporting: ::std::os::raw::c_int, + pub exception_ignore_args: bool, pub user_error_handler: zval, pub user_exception_handler: zval, pub user_error_handlers_error_reporting: zend_stack, pub user_error_handlers: zend_stack, pub user_exception_handlers: zend_stack, - pub error_handling: zend_error_handling_t, pub exception_class: *mut zend_class_entry, - pub timeout_seconds: zend_long, + pub error_handling: zend_error_handling_t, pub capture_warnings_during_sccp: ::std::os::raw::c_int, + pub timeout_seconds: zend_long, pub ini_directives: *mut HashTable, pub modified_ini_directives: *mut HashTable, pub error_reporting_ini_entry: *mut zend_ini_entry, @@ -1284,7 +1440,7 @@ pub struct _zend_executor_globals { pub exception_op: [zend_op; 3usize], pub current_module: *mut _zend_module_entry, pub active: bool, - pub flags: zend_uchar, + pub flags: u8, pub assertions: zend_long, pub ht_iterators_count: u32, pub ht_iterators_used: u32, @@ -1294,18 +1450,20 @@ pub struct _zend_executor_globals { pub trampoline: zend_function, pub call_trampoline_op: zend_op, pub weakrefs: HashTable, - pub exception_ignore_args: bool, pub exception_string_param_max_len: zend_long, pub get_gc_buffer: zend_get_gc_buffer, pub main_fiber_context: *mut zend_fiber_context, pub current_fiber_context: *mut zend_fiber_context, pub active_fiber: *mut zend_fiber, - pub fiber_stack_size: zend_long, + pub fiber_stack_size: usize, pub record_errors: bool, pub num_errors: u32, pub errors: *mut *mut zend_error_info, pub filename_override: *mut zend_string, pub lineno_override: zend_long, + pub call_stack: zend_call_stack, + pub max_allowed_stack_size: zend_long, + pub reserved_stack_size: zend_ulong, pub reserved: [*mut ::std::os::raw::c_void; 6usize], } pub type zend_module_entry = _zend_module_entry; @@ -1404,6 +1562,19 @@ pub struct _zend_function_entry { pub flags: u32, } pub type zend_function_entry = _zend_function_entry; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _zend_fcall_info_cache { + pub function_handler: *mut zend_function, + pub calling_scope: *mut zend_class_entry, + pub called_scope: *mut zend_class_entry, + pub object: *mut zend_object, + pub closure: *mut zend_object, +} +pub type zend_fcall_info_cache = _zend_fcall_info_cache; +extern "C" { + pub fn zend_register_module_ex(module: *mut zend_module_entry) -> *mut zend_module_entry; +} extern "C" { pub fn zend_register_internal_class_ex( class_entry: *mut zend_class_entry, @@ -1482,15 +1653,17 @@ pub const _zend_expected_type_Z_EXPECTED_DOUBLE: _zend_expected_type = 20; pub const _zend_expected_type_Z_EXPECTED_DOUBLE_OR_NULL: _zend_expected_type = 21; pub const _zend_expected_type_Z_EXPECTED_NUMBER: _zend_expected_type = 22; pub const _zend_expected_type_Z_EXPECTED_NUMBER_OR_NULL: _zend_expected_type = 23; -pub const _zend_expected_type_Z_EXPECTED_ARRAY_OR_STRING: _zend_expected_type = 24; -pub const _zend_expected_type_Z_EXPECTED_ARRAY_OR_STRING_OR_NULL: _zend_expected_type = 25; -pub const _zend_expected_type_Z_EXPECTED_STRING_OR_LONG: _zend_expected_type = 26; -pub const _zend_expected_type_Z_EXPECTED_STRING_OR_LONG_OR_NULL: _zend_expected_type = 27; -pub const _zend_expected_type_Z_EXPECTED_OBJECT_OR_CLASS_NAME: _zend_expected_type = 28; -pub const _zend_expected_type_Z_EXPECTED_OBJECT_OR_CLASS_NAME_OR_NULL: _zend_expected_type = 29; -pub const _zend_expected_type_Z_EXPECTED_OBJECT_OR_STRING: _zend_expected_type = 30; -pub const _zend_expected_type_Z_EXPECTED_OBJECT_OR_STRING_OR_NULL: _zend_expected_type = 31; -pub const _zend_expected_type_Z_EXPECTED_LAST: _zend_expected_type = 32; +pub const _zend_expected_type_Z_EXPECTED_NUMBER_OR_STRING: _zend_expected_type = 24; +pub const _zend_expected_type_Z_EXPECTED_NUMBER_OR_STRING_OR_NULL: _zend_expected_type = 25; +pub const _zend_expected_type_Z_EXPECTED_ARRAY_OR_STRING: _zend_expected_type = 26; +pub const _zend_expected_type_Z_EXPECTED_ARRAY_OR_STRING_OR_NULL: _zend_expected_type = 27; +pub const _zend_expected_type_Z_EXPECTED_STRING_OR_LONG: _zend_expected_type = 28; +pub const _zend_expected_type_Z_EXPECTED_STRING_OR_LONG_OR_NULL: _zend_expected_type = 29; +pub const _zend_expected_type_Z_EXPECTED_OBJECT_OR_CLASS_NAME: _zend_expected_type = 30; +pub const _zend_expected_type_Z_EXPECTED_OBJECT_OR_CLASS_NAME_OR_NULL: _zend_expected_type = 31; +pub const _zend_expected_type_Z_EXPECTED_OBJECT_OR_STRING: _zend_expected_type = 32; +pub const _zend_expected_type_Z_EXPECTED_OBJECT_OR_STRING_OR_NULL: _zend_expected_type = 33; +pub const _zend_expected_type_Z_EXPECTED_LAST: _zend_expected_type = 34; pub type _zend_expected_type = ::std::os::raw::c_uint; extern "C" { pub fn zend_wrong_parameters_count_error(min_num_args: u32, max_num_args: u32); @@ -1506,6 +1679,418 @@ extern "C" { ... ); } +pub type php_stream = _php_stream; +pub type php_stream_wrapper = _php_stream_wrapper; +pub type php_stream_context = _php_stream_context; +pub type php_stream_filter = _php_stream_filter; +pub type php_stream_notification_func = ::std::option::Option< + unsafe extern "C" fn( + context: *mut php_stream_context, + notifycode: ::std::os::raw::c_int, + severity: ::std::os::raw::c_int, + xmsg: *mut ::std::os::raw::c_char, + xcode: ::std::os::raw::c_int, + bytes_sofar: usize, + bytes_max: usize, + ptr: *mut ::std::os::raw::c_void, + ), +>; +pub type php_stream_notifier = _php_stream_notifier; +#[repr(C)] +pub struct _php_stream_notifier { + pub func: php_stream_notification_func, + pub dtor: ::std::option::Option, + pub ptr: zval, + pub mask: ::std::os::raw::c_int, + pub progress: usize, + pub progress_max: usize, +} +#[repr(C)] +pub struct _php_stream_context { + pub notifier: *mut php_stream_notifier, + pub options: zval, + pub res: *mut zend_resource, +} +pub type php_stream_bucket = _php_stream_bucket; +pub type php_stream_bucket_brigade = _php_stream_bucket_brigade; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _php_stream_bucket { + pub next: *mut php_stream_bucket, + pub prev: *mut php_stream_bucket, + pub brigade: *mut php_stream_bucket_brigade, + pub buf: *mut ::std::os::raw::c_char, + pub buflen: usize, + pub own_buf: u8, + pub is_persistent: u8, + pub refcount: ::std::os::raw::c_int, +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _php_stream_bucket_brigade { + pub head: *mut php_stream_bucket, + pub tail: *mut php_stream_bucket, +} +pub const php_stream_filter_status_t_PSFS_ERR_FATAL: php_stream_filter_status_t = 0; +pub const php_stream_filter_status_t_PSFS_FEED_ME: php_stream_filter_status_t = 1; +pub const php_stream_filter_status_t_PSFS_PASS_ON: php_stream_filter_status_t = 2; +pub type php_stream_filter_status_t = ::std::os::raw::c_uint; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _php_stream_filter_ops { + pub filter: ::std::option::Option< + unsafe extern "C" fn( + stream: *mut php_stream, + thisfilter: *mut php_stream_filter, + buckets_in: *mut php_stream_bucket_brigade, + buckets_out: *mut php_stream_bucket_brigade, + bytes_consumed: *mut usize, + flags: ::std::os::raw::c_int, + ) -> php_stream_filter_status_t, + >, + pub dtor: ::std::option::Option, + pub label: *const ::std::os::raw::c_char, +} +pub type php_stream_filter_ops = _php_stream_filter_ops; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _php_stream_filter_chain { + pub head: *mut php_stream_filter, + pub tail: *mut php_stream_filter, + pub stream: *mut php_stream, +} +pub type php_stream_filter_chain = _php_stream_filter_chain; +#[repr(C)] +pub struct _php_stream_filter { + pub fops: *const php_stream_filter_ops, + pub abstract_: zval, + pub next: *mut php_stream_filter, + pub prev: *mut php_stream_filter, + pub is_persistent: ::std::os::raw::c_int, + pub chain: *mut php_stream_filter_chain, + pub buffer: php_stream_bucket_brigade, + pub res: *mut zend_resource, +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _php_stream_statbuf { + pub sb: zend_stat_t, +} +pub type php_stream_statbuf = _php_stream_statbuf; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _php_stream_ops { + pub write: ::std::option::Option< + unsafe extern "C" fn( + stream: *mut php_stream, + buf: *const ::std::os::raw::c_char, + count: usize, + ) -> isize, + >, + pub read: ::std::option::Option< + unsafe extern "C" fn( + stream: *mut php_stream, + buf: *mut ::std::os::raw::c_char, + count: usize, + ) -> isize, + >, + pub close: ::std::option::Option< + unsafe extern "C" fn( + stream: *mut php_stream, + close_handle: ::std::os::raw::c_int, + ) -> ::std::os::raw::c_int, + >, + pub flush: ::std::option::Option< + unsafe extern "C" fn(stream: *mut php_stream) -> ::std::os::raw::c_int, + >, + pub label: *const ::std::os::raw::c_char, + pub seek: ::std::option::Option< + unsafe extern "C" fn( + stream: *mut php_stream, + offset: zend_off_t, + whence: ::std::os::raw::c_int, + newoffset: *mut zend_off_t, + ) -> ::std::os::raw::c_int, + >, + pub cast: ::std::option::Option< + unsafe extern "C" fn( + stream: *mut php_stream, + castas: ::std::os::raw::c_int, + ret: *mut *mut ::std::os::raw::c_void, + ) -> ::std::os::raw::c_int, + >, + pub stat: ::std::option::Option< + unsafe extern "C" fn( + stream: *mut php_stream, + ssb: *mut php_stream_statbuf, + ) -> ::std::os::raw::c_int, + >, + pub set_option: ::std::option::Option< + unsafe extern "C" fn( + stream: *mut php_stream, + option: ::std::os::raw::c_int, + value: ::std::os::raw::c_int, + ptrparam: *mut ::std::os::raw::c_void, + ) -> ::std::os::raw::c_int, + >, +} +pub type php_stream_ops = _php_stream_ops; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _php_stream_wrapper_ops { + pub stream_opener: ::std::option::Option< + unsafe extern "C" fn( + wrapper: *mut php_stream_wrapper, + filename: *const ::std::os::raw::c_char, + mode: *const ::std::os::raw::c_char, + options: ::std::os::raw::c_int, + opened_path: *mut *mut zend_string, + context: *mut php_stream_context, + __php_stream_call_depth: ::std::os::raw::c_int, + __zend_filename: *const ::std::os::raw::c_char, + __zend_lineno: u32, + __zend_orig_filename: *const ::std::os::raw::c_char, + __zend_orig_lineno: u32, + ) -> *mut php_stream, + >, + pub stream_closer: ::std::option::Option< + unsafe extern "C" fn( + wrapper: *mut php_stream_wrapper, + stream: *mut php_stream, + ) -> ::std::os::raw::c_int, + >, + pub stream_stat: ::std::option::Option< + unsafe extern "C" fn( + wrapper: *mut php_stream_wrapper, + stream: *mut php_stream, + ssb: *mut php_stream_statbuf, + ) -> ::std::os::raw::c_int, + >, + pub url_stat: ::std::option::Option< + unsafe extern "C" fn( + wrapper: *mut php_stream_wrapper, + url: *const ::std::os::raw::c_char, + flags: ::std::os::raw::c_int, + ssb: *mut php_stream_statbuf, + context: *mut php_stream_context, + ) -> ::std::os::raw::c_int, + >, + pub dir_opener: ::std::option::Option< + unsafe extern "C" fn( + wrapper: *mut php_stream_wrapper, + filename: *const ::std::os::raw::c_char, + mode: *const ::std::os::raw::c_char, + options: ::std::os::raw::c_int, + opened_path: *mut *mut zend_string, + context: *mut php_stream_context, + __php_stream_call_depth: ::std::os::raw::c_int, + __zend_filename: *const ::std::os::raw::c_char, + __zend_lineno: u32, + __zend_orig_filename: *const ::std::os::raw::c_char, + __zend_orig_lineno: u32, + ) -> *mut php_stream, + >, + pub label: *const ::std::os::raw::c_char, + pub unlink: ::std::option::Option< + unsafe extern "C" fn( + wrapper: *mut php_stream_wrapper, + url: *const ::std::os::raw::c_char, + options: ::std::os::raw::c_int, + context: *mut php_stream_context, + ) -> ::std::os::raw::c_int, + >, + pub rename: ::std::option::Option< + unsafe extern "C" fn( + wrapper: *mut php_stream_wrapper, + url_from: *const ::std::os::raw::c_char, + url_to: *const ::std::os::raw::c_char, + options: ::std::os::raw::c_int, + context: *mut php_stream_context, + ) -> ::std::os::raw::c_int, + >, + pub stream_mkdir: ::std::option::Option< + unsafe extern "C" fn( + wrapper: *mut php_stream_wrapper, + url: *const ::std::os::raw::c_char, + mode: ::std::os::raw::c_int, + options: ::std::os::raw::c_int, + context: *mut php_stream_context, + ) -> ::std::os::raw::c_int, + >, + pub stream_rmdir: ::std::option::Option< + unsafe extern "C" fn( + wrapper: *mut php_stream_wrapper, + url: *const ::std::os::raw::c_char, + options: ::std::os::raw::c_int, + context: *mut php_stream_context, + ) -> ::std::os::raw::c_int, + >, + pub stream_metadata: ::std::option::Option< + unsafe extern "C" fn( + wrapper: *mut php_stream_wrapper, + url: *const ::std::os::raw::c_char, + options: ::std::os::raw::c_int, + value: *mut ::std::os::raw::c_void, + context: *mut php_stream_context, + ) -> ::std::os::raw::c_int, + >, +} +pub type php_stream_wrapper_ops = _php_stream_wrapper_ops; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _php_stream_wrapper { + pub wops: *const php_stream_wrapper_ops, + pub abstract_: *mut ::std::os::raw::c_void, + pub is_url: ::std::os::raw::c_int, +} +#[repr(C)] +pub struct _php_stream { + pub ops: *const php_stream_ops, + pub abstract_: *mut ::std::os::raw::c_void, + pub readfilters: php_stream_filter_chain, + pub writefilters: php_stream_filter_chain, + pub wrapper: *mut php_stream_wrapper, + pub wrapperthis: *mut ::std::os::raw::c_void, + pub wrapperdata: zval, + pub _bitfield_align_1: [u8; 0], + pub _bitfield_1: __BindgenBitfieldUnit<[u8; 2usize]>, + pub mode: [::std::os::raw::c_char; 16usize], + pub flags: u32, + pub res: *mut zend_resource, + pub stdiocast: *mut FILE, + pub orig_path: *mut ::std::os::raw::c_char, + pub ctx: *mut zend_resource, + pub position: zend_off_t, + pub readbuf: *mut ::std::os::raw::c_uchar, + pub readbuflen: usize, + pub readpos: zend_off_t, + pub writepos: zend_off_t, + pub chunk_size: usize, + pub open_filename: *const ::std::os::raw::c_char, + pub open_lineno: u32, + pub enclosing_stream: *mut _php_stream, +} +impl _php_stream { + #[inline] + pub fn is_persistent(&self) -> u16 { + unsafe { ::std::mem::transmute(self._bitfield_1.get(0usize, 1u8) as u16) } + } + #[inline] + pub fn set_is_persistent(&mut self, val: u16) { + unsafe { + let val: u16 = ::std::mem::transmute(val); + self._bitfield_1.set(0usize, 1u8, val as u64) + } + } + #[inline] + pub fn in_free(&self) -> u16 { + unsafe { ::std::mem::transmute(self._bitfield_1.get(1usize, 2u8) as u16) } + } + #[inline] + pub fn set_in_free(&mut self, val: u16) { + unsafe { + let val: u16 = ::std::mem::transmute(val); + self._bitfield_1.set(1usize, 2u8, val as u64) + } + } + #[inline] + pub fn eof(&self) -> u16 { + unsafe { ::std::mem::transmute(self._bitfield_1.get(3usize, 1u8) as u16) } + } + #[inline] + pub fn set_eof(&mut self, val: u16) { + unsafe { + let val: u16 = ::std::mem::transmute(val); + self._bitfield_1.set(3usize, 1u8, val as u64) + } + } + #[inline] + pub fn __exposed(&self) -> u16 { + unsafe { ::std::mem::transmute(self._bitfield_1.get(4usize, 1u8) as u16) } + } + #[inline] + pub fn set___exposed(&mut self, val: u16) { + unsafe { + let val: u16 = ::std::mem::transmute(val); + self._bitfield_1.set(4usize, 1u8, val as u64) + } + } + #[inline] + pub fn fclose_stdiocast(&self) -> u16 { + unsafe { ::std::mem::transmute(self._bitfield_1.get(5usize, 2u8) as u16) } + } + #[inline] + pub fn set_fclose_stdiocast(&mut self, val: u16) { + unsafe { + let val: u16 = ::std::mem::transmute(val); + self._bitfield_1.set(5usize, 2u8, val as u64) + } + } + #[inline] + pub fn has_buffered_data(&self) -> u16 { + unsafe { ::std::mem::transmute(self._bitfield_1.get(7usize, 1u8) as u16) } + } + #[inline] + pub fn set_has_buffered_data(&mut self, val: u16) { + unsafe { + let val: u16 = ::std::mem::transmute(val); + self._bitfield_1.set(7usize, 1u8, val as u64) + } + } + #[inline] + pub fn fclose_stdiocast_flush_in_progress(&self) -> u16 { + unsafe { ::std::mem::transmute(self._bitfield_1.get(8usize, 1u8) as u16) } + } + #[inline] + pub fn set_fclose_stdiocast_flush_in_progress(&mut self, val: u16) { + unsafe { + let val: u16 = ::std::mem::transmute(val); + self._bitfield_1.set(8usize, 1u8, val as u64) + } + } + #[inline] + pub fn new_bitfield_1( + is_persistent: u16, + in_free: u16, + eof: u16, + __exposed: u16, + fclose_stdiocast: u16, + has_buffered_data: u16, + fclose_stdiocast_flush_in_progress: u16, + ) -> __BindgenBitfieldUnit<[u8; 2usize]> { + let mut __bindgen_bitfield_unit: __BindgenBitfieldUnit<[u8; 2usize]> = Default::default(); + __bindgen_bitfield_unit.set(0usize, 1u8, { + let is_persistent: u16 = unsafe { ::std::mem::transmute(is_persistent) }; + is_persistent as u64 + }); + __bindgen_bitfield_unit.set(1usize, 2u8, { + let in_free: u16 = unsafe { ::std::mem::transmute(in_free) }; + in_free as u64 + }); + __bindgen_bitfield_unit.set(3usize, 1u8, { + let eof: u16 = unsafe { ::std::mem::transmute(eof) }; + eof as u64 + }); + __bindgen_bitfield_unit.set(4usize, 1u8, { + let __exposed: u16 = unsafe { ::std::mem::transmute(__exposed) }; + __exposed as u64 + }); + __bindgen_bitfield_unit.set(5usize, 2u8, { + let fclose_stdiocast: u16 = unsafe { ::std::mem::transmute(fclose_stdiocast) }; + fclose_stdiocast as u64 + }); + __bindgen_bitfield_unit.set(7usize, 1u8, { + let has_buffered_data: u16 = unsafe { ::std::mem::transmute(has_buffered_data) }; + has_buffered_data as u64 + }); + __bindgen_bitfield_unit.set(8usize, 1u8, { + let fclose_stdiocast_flush_in_progress: u16 = + unsafe { ::std::mem::transmute(fclose_stdiocast_flush_in_progress) }; + fclose_stdiocast_flush_in_progress as u64 + }); + __bindgen_bitfield_unit + } +} #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct _zend_ini_entry_def { @@ -1678,3 +2263,76 @@ extern "C" { extern "C" { pub static mut zend_ce_stringable: *mut zend_class_entry; } +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct sapi_headers_struct { + pub headers: zend_llist, + pub http_response_code: ::std::os::raw::c_int, + pub send_default_content_type: ::std::os::raw::c_uchar, + pub mimetype: *mut ::std::os::raw::c_char, + pub http_status_line: *mut ::std::os::raw::c_char, +} +pub type sapi_post_entry = _sapi_post_entry; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct sapi_request_info { + pub request_method: *const ::std::os::raw::c_char, + pub query_string: *mut ::std::os::raw::c_char, + pub cookie_data: *mut ::std::os::raw::c_char, + pub content_length: zend_long, + pub path_translated: *mut ::std::os::raw::c_char, + pub request_uri: *mut ::std::os::raw::c_char, + pub request_body: *mut _php_stream, + pub content_type: *const ::std::os::raw::c_char, + pub headers_only: bool, + pub no_headers: bool, + pub headers_read: bool, + pub post_entry: *mut sapi_post_entry, + pub content_type_dup: *mut ::std::os::raw::c_char, + pub auth_user: *mut ::std::os::raw::c_char, + pub auth_password: *mut ::std::os::raw::c_char, + pub auth_digest: *mut ::std::os::raw::c_char, + pub argv0: *mut ::std::os::raw::c_char, + pub current_user: *mut ::std::os::raw::c_char, + pub current_user_length: ::std::os::raw::c_int, + pub argc: ::std::os::raw::c_int, + pub argv: *mut *mut ::std::os::raw::c_char, + pub proto_num: ::std::os::raw::c_int, +} +#[repr(C)] +pub struct _sapi_globals_struct { + pub server_context: *mut ::std::os::raw::c_void, + pub request_info: sapi_request_info, + pub sapi_headers: sapi_headers_struct, + pub read_post_bytes: i64, + pub post_read: ::std::os::raw::c_uchar, + pub headers_sent: ::std::os::raw::c_uchar, + pub global_stat: zend_stat_t, + pub default_mimetype: *mut ::std::os::raw::c_char, + pub default_charset: *mut ::std::os::raw::c_char, + pub rfc1867_uploaded_files: *mut HashTable, + pub post_max_size: zend_long, + pub options: ::std::os::raw::c_int, + pub sapi_started: bool, + pub global_request_time: f64, + pub known_post_content_types: HashTable, + pub callback_func: zval, + pub fci_cache: zend_fcall_info_cache, +} +pub type sapi_globals_struct = _sapi_globals_struct; +extern "C" { + pub static mut sapi_globals: sapi_globals_struct; +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _sapi_post_entry { + pub content_type: *mut ::std::os::raw::c_char, + pub content_type_len: u32, + pub post_reader: ::std::option::Option, + pub post_handler: ::std::option::Option< + unsafe extern "C" fn( + content_type_dup: *mut ::std::os::raw::c_char, + arg: *mut ::std::os::raw::c_void, + ), + >, +} diff --git a/src/ffi.rs b/src/ffi.rs index a6c3d94..c658239 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -26,6 +26,7 @@ extern "C" { pub fn ext_php_rs_zend_object_alloc(obj_size: usize, ce: *mut zend_class_entry) -> *mut c_void; pub fn ext_php_rs_zend_object_release(obj: *mut zend_object); pub fn ext_php_rs_executor_globals() -> *mut zend_executor_globals; + pub fn ext_php_rs_sapi_globals() -> *mut sapi_globals_struct; pub fn ext_php_rs_zend_try_catch( func: unsafe extern "C" fn(*const c_void) -> *const c_void, ctx: *const c_void, diff --git a/src/wrapper.c b/src/wrapper.c index a1fd900..a708e57 100644 --- a/src/wrapper.c +++ b/src/wrapper.c @@ -40,6 +40,18 @@ zend_executor_globals *ext_php_rs_executor_globals() { #endif } +sapi_globals_struct *ext_php_rs_sapi_globals() { +#ifdef ZTS +#ifdef ZEND_ENABLE_STATIC_TSRMLS_CACHE + return TSRMG_FAST_BULK_STATIC(sapi_globals_offset, sapi_globals_struct); +#else + return TSRMG_FAST_BULK(sapi_globals_offset, sapi_globals_struct *); +#endif +#else + return &sapi_globals; +#endif +} + bool ext_php_rs_zend_try_catch(void* (*callback)(void *), void *ctx, void **result) { zend_try { *result = callback(ctx); diff --git a/src/wrapper.h b/src/wrapper.h index e4e5551..cb5c667 100644 --- a/src/wrapper.h +++ b/src/wrapper.h @@ -21,6 +21,7 @@ #include "zend_inheritance.h" #include "zend_interfaces.h" #include "zend_ini.h" +#include "main/SAPI.h" zend_string *ext_php_rs_zend_string_init(const char *str, size_t len, bool persistent); void ext_php_rs_zend_string_release(zend_string *zs); @@ -30,6 +31,7 @@ void ext_php_rs_set_known_valid_utf8(zend_string *zs); const char *ext_php_rs_php_build_id(); void *ext_php_rs_zend_object_alloc(size_t obj_size, zend_class_entry *ce); void ext_php_rs_zend_object_release(zend_object *obj); -zend_executor_globals *ext_php_rs_executor_globals(); +zend_executor_globals *ext_php_rs_executor_globals();; +sapi_globals_struct *ext_php_rs_sapi_globals(); bool ext_php_rs_zend_try_catch(void* (*callback)(void *), void *ctx, void **result); void ext_php_rs_zend_bailout(); diff --git a/src/zend/globals.rs b/src/zend/globals.rs index 9761ab9..cf8ded8 100644 --- a/src/zend/globals.rs +++ b/src/zend/globals.rs @@ -8,12 +8,15 @@ use parking_lot::{const_rwlock, RwLock, RwLockReadGuard, RwLockWriteGuard}; use crate::boxed::ZBox; #[cfg(php82)] use crate::ffi::zend_atomic_bool_store; -use crate::ffi::{_zend_executor_globals, ext_php_rs_executor_globals, zend_ini_entry}; +use crate::ffi::{_zend_executor_globals, ext_php_rs_executor_globals, _sapi_globals_struct, ext_php_rs_sapi_globals, zend_ini_entry}; use crate::types::{ZendHashTable, ZendObject}; /// Stores global variables used in the PHP executor. pub type ExecutorGlobals = _zend_executor_globals; +/// Stores global SAPI variables used in the PHP executor. +pub type SapiGlobals = _sapi_globals_struct; + impl ExecutorGlobals { /// Returns a reference to the PHP executor globals. /// @@ -127,12 +130,52 @@ impl ExecutorGlobals { } } +impl SapiGlobals { + /// Returns a reference to the PHP SAPI globals. + /// + /// The executor globals are guarded by a RwLock. There can be multiple + /// immutable references at one time but only ever one mutable reference. + /// Attempting to retrieve the globals while already holding the global + /// guard will lead to a deadlock. Dropping the globals guard will release + /// the lock. + pub fn get() -> GlobalReadGuard { + // SAFETY: PHP executor globals are statically declared therefore should never + // return an invalid pointer. + let globals = unsafe { ext_php_rs_sapi_globals().as_ref() } + .expect("Static executor globals were invalid"); + let guard = SAPI_LOCK.read(); + GlobalReadGuard { globals, guard } + } + + /// Returns a mutable reference to the PHP executor globals. + /// + /// The executor globals are guarded by a RwLock. There can be multiple + /// immutable references at one time but only ever one mutable reference. + /// Attempting to retrieve the globals while already holding the global + /// guard will lead to a deadlock. Dropping the globals guard will release + /// the lock. + pub fn get_mut() -> GlobalWriteGuard { + // SAFETY: PHP executor globals are statically declared therefore should never + // return an invalid pointer. + let globals = unsafe { ext_php_rs_sapi_globals().as_mut() } + .expect("Static executor globals were invalid"); + let guard = SAPI_LOCK.write(); + GlobalWriteGuard { globals, guard } + } +} + /// Executor globals rwlock. /// /// PHP provides no indication if the executor globals are being accessed so /// this is only effective on the Rust side. static GLOBALS_LOCK: RwLock<()> = const_rwlock(()); +/// SAPI globals rwlock. +/// +/// PHP provides no indication if the executor globals are being accessed so +/// this is only effective on the Rust side. +static SAPI_LOCK: RwLock<()> = const_rwlock(()); + /// Wrapper guard that contains a reference to a given type `T`. Dropping a /// guard releases the lock on the relevant rwlock. pub struct GlobalReadGuard { diff --git a/src/zend/mod.rs b/src/zend/mod.rs index af8a5c2..67840f9 100644 --- a/src/zend/mod.rs +++ b/src/zend/mod.rs @@ -20,6 +20,7 @@ pub use ex::ExecuteData; pub use function::Function; pub use function::FunctionEntry; pub use globals::ExecutorGlobals; +pub use globals::SapiGlobals; pub use handlers::ZendObjectHandlers; pub use ini_entry_def::IniEntryDef; pub use module::ModuleEntry; From 53d72499638ca61ed0fef31d20451c4ea2b2a34d Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Tue, 21 Nov 2023 20:18:47 +0100 Subject: [PATCH 085/100] Fixup --- src/zend/globals.rs | 5 ++++- src/zend/handlers.rs | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/zend/globals.rs b/src/zend/globals.rs index cf8ded8..cbea80a 100644 --- a/src/zend/globals.rs +++ b/src/zend/globals.rs @@ -8,7 +8,10 @@ use parking_lot::{const_rwlock, RwLock, RwLockReadGuard, RwLockWriteGuard}; use crate::boxed::ZBox; #[cfg(php82)] use crate::ffi::zend_atomic_bool_store; -use crate::ffi::{_zend_executor_globals, ext_php_rs_executor_globals, _sapi_globals_struct, ext_php_rs_sapi_globals, zend_ini_entry}; +use crate::ffi::{ + _sapi_globals_struct, _zend_executor_globals, ext_php_rs_executor_globals, + ext_php_rs_sapi_globals, zend_ini_entry, +}; use crate::types::{ZendHashTable, ZendObject}; /// Stores global variables used in the PHP executor. diff --git a/src/zend/handlers.rs b/src/zend/handlers.rs index 11d220b..f8882a1 100644 --- a/src/zend/handlers.rs +++ b/src/zend/handlers.rs @@ -238,7 +238,7 @@ impl ZendObjectHandlers { let mut zv = Zval::new(); val.get(self_, &mut zv)?; - if zend_is_true(&mut zv) == 1 { + if zend_is_true(&zv) == 1 { return Ok(1); } } From fc6a8aee5e1f11c8613b693790d00c6238f5d46d Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Tue, 21 Nov 2023 20:23:18 +0100 Subject: [PATCH 086/100] Fixup --- src/zend/handlers.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/zend/handlers.rs b/src/zend/handlers.rs index f8882a1..7e88a7e 100644 --- a/src/zend/handlers.rs +++ b/src/zend/handlers.rs @@ -238,7 +238,8 @@ impl ZendObjectHandlers { let mut zv = Zval::new(); val.get(self_, &mut zv)?; - if zend_is_true(&zv) == 1 { + #[allow(clippy::unnecessary_mut_passed)] + if zend_is_true(&mut zv) == 1 { return Ok(1); } } From 116c6987b9f61bbda0ced0b6fc5211442eb810c4 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Tue, 21 Nov 2023 20:26:49 +0100 Subject: [PATCH 087/100] Add version --- build.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/build.rs b/build.rs index 601a0c3..5afef52 100644 --- a/build.rs +++ b/build.rs @@ -228,6 +228,8 @@ fn check_php_version(info: &PHPInfo) -> Result<()> { const PHP_82_API_VER: u32 = 20220829; + const PHP_83_API_VER: u32 = 20230831; + println!("cargo:rustc-cfg=php80"); if (PHP_81_API_VER..PHP_82_API_VER).contains(&version) { @@ -238,6 +240,10 @@ fn check_php_version(info: &PHPInfo) -> Result<()> { println!("cargo:rustc-cfg=php82"); } + if version >= PHP_83_API_VER { + println!("cargo:rustc-cfg=php83"); + } + Ok(()) } From 10eac2497add0974c805fef06b42d2401860118f Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Fri, 24 Nov 2023 11:53:25 +0100 Subject: [PATCH 088/100] Expose SAPI module --- allowed_bindings.rs | 3 +++ src/ffi.rs | 1 + src/wrapper.c | 4 ++++ src/wrapper.h | 1 + src/zend/globals.rs | 44 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 53 insertions(+) diff --git a/allowed_bindings.rs b/allowed_bindings.rs index 53e5a5e..2046ab5 100644 --- a/allowed_bindings.rs +++ b/allowed_bindings.rs @@ -27,6 +27,7 @@ bind! { _emalloc, _zend_executor_globals, _sapi_globals_struct, + _sapi_module_struct, _zend_expected_type, _zend_expected_type_Z_EXPECTED_ARRAY, _zend_expected_type_Z_EXPECTED_BOOL, @@ -243,6 +244,7 @@ bind! { zend_class_unserialize_deny, zend_executor_globals, sapi_globals_struct, + sapi_module_struct, zend_objects_store_del, zend_hash_move_forward_ex, zend_hash_get_current_key_type_ex, @@ -254,6 +256,7 @@ bind! { ZEND_ACC_NOT_SERIALIZABLE, executor_globals, sapi_globals, + sapi_module, php_printf, __zend_malloc, tsrm_get_ls_cache, diff --git a/src/ffi.rs b/src/ffi.rs index c658239..343b1fa 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -27,6 +27,7 @@ extern "C" { pub fn ext_php_rs_zend_object_release(obj: *mut zend_object); pub fn ext_php_rs_executor_globals() -> *mut zend_executor_globals; pub fn ext_php_rs_sapi_globals() -> *mut sapi_globals_struct; + pub fn ext_php_rs_sapi_module() -> *mut sapi_module_struct; pub fn ext_php_rs_zend_try_catch( func: unsafe extern "C" fn(*const c_void) -> *const c_void, ctx: *const c_void, diff --git a/src/wrapper.c b/src/wrapper.c index a708e57..109b5ae 100644 --- a/src/wrapper.c +++ b/src/wrapper.c @@ -52,6 +52,10 @@ sapi_globals_struct *ext_php_rs_sapi_globals() { #endif } +sapi_module_struct *ext_php_rs_sapi_module() { + return &sapi_module; +} + bool ext_php_rs_zend_try_catch(void* (*callback)(void *), void *ctx, void **result) { zend_try { *result = callback(ctx); diff --git a/src/wrapper.h b/src/wrapper.h index cb5c667..883f1b8 100644 --- a/src/wrapper.h +++ b/src/wrapper.h @@ -33,5 +33,6 @@ void *ext_php_rs_zend_object_alloc(size_t obj_size, zend_class_entry *ce); void ext_php_rs_zend_object_release(zend_object *obj); zend_executor_globals *ext_php_rs_executor_globals();; sapi_globals_struct *ext_php_rs_sapi_globals(); +sapi_module_struct *ext_php_rs_sapi_module(); bool ext_php_rs_zend_try_catch(void* (*callback)(void *), void *ctx, void **result); void ext_php_rs_zend_bailout(); diff --git a/src/zend/globals.rs b/src/zend/globals.rs index cbea80a..fc4864f 100644 --- a/src/zend/globals.rs +++ b/src/zend/globals.rs @@ -10,6 +10,7 @@ use crate::boxed::ZBox; use crate::ffi::zend_atomic_bool_store; use crate::ffi::{ _sapi_globals_struct, _zend_executor_globals, ext_php_rs_executor_globals, + _sapi_module_struct, ext_php_rs_sapi_module, ext_php_rs_sapi_globals, zend_ini_entry, }; use crate::types::{ZendHashTable, ZendObject}; @@ -20,6 +21,9 @@ pub type ExecutorGlobals = _zend_executor_globals; /// Stores global SAPI variables used in the PHP executor. pub type SapiGlobals = _sapi_globals_struct; +/// Stores the SAPI module used in the PHP executor. +pub type SapiModule = _sapi_module_struct; + impl ExecutorGlobals { /// Returns a reference to the PHP executor globals. /// @@ -167,6 +171,40 @@ impl SapiGlobals { } } +impl SapiModule { + /// Returns a reference to the PHP SAPI module. + /// + /// The executor globals are guarded by a RwLock. There can be multiple + /// immutable references at one time but only ever one mutable reference. + /// Attempting to retrieve the globals while already holding the global + /// guard will lead to a deadlock. Dropping the globals guard will release + /// the lock. + pub fn get() -> GlobalReadGuard { + // SAFETY: PHP executor globals are statically declared therefore should never + // return an invalid pointer. + let globals = unsafe { ext_php_rs_sapi_module().as_ref() } + .expect("Static executor globals were invalid"); + let guard = SAPI_MODULE_LOCK.read(); + GlobalReadGuard { globals, guard } + } + + /// Returns a mutable reference to the PHP executor globals. + /// + /// The executor globals are guarded by a RwLock. There can be multiple + /// immutable references at one time but only ever one mutable reference. + /// Attempting to retrieve the globals while already holding the global + /// guard will lead to a deadlock. Dropping the globals guard will release + /// the lock. + pub fn get_mut() -> GlobalWriteGuard { + // SAFETY: PHP executor globals are statically declared therefore should never + // return an invalid pointer. + let globals = unsafe { ext_php_rs_sapi_module().as_mut() } + .expect("Static executor globals were invalid"); + let guard = SAPI_MODULE_LOCK.write(); + GlobalWriteGuard { globals, guard } + } +} + /// Executor globals rwlock. /// /// PHP provides no indication if the executor globals are being accessed so @@ -179,6 +217,12 @@ static GLOBALS_LOCK: RwLock<()> = const_rwlock(()); /// this is only effective on the Rust side. static SAPI_LOCK: RwLock<()> = const_rwlock(()); +/// SAPI globals rwlock. +/// +/// PHP provides no indication if the executor globals are being accessed so +/// this is only effective on the Rust side. +static SAPI_MODULE_LOCK: RwLock<()> = const_rwlock(()); + /// Wrapper guard that contains a reference to a given type `T`. Dropping a /// guard releases the lock on the relevant rwlock. pub struct GlobalReadGuard { From 0fbf54a0969357e746e161c0c8d54d332692d89a Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Fri, 24 Nov 2023 11:55:33 +0100 Subject: [PATCH 089/100] Expose SAPI module --- src/zend/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/zend/mod.rs b/src/zend/mod.rs index 67840f9..5ef7161 100644 --- a/src/zend/mod.rs +++ b/src/zend/mod.rs @@ -21,6 +21,7 @@ pub use function::Function; pub use function::FunctionEntry; pub use globals::ExecutorGlobals; pub use globals::SapiGlobals; +pub use globals::SapiModule; pub use handlers::ZendObjectHandlers; pub use ini_entry_def::IniEntryDef; pub use module::ModuleEntry; From 917d7e2f1830386a0e6ee441c6739569d7d81329 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Fri, 24 Nov 2023 11:58:36 +0100 Subject: [PATCH 090/100] Fmt --- src/zend/globals.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/zend/globals.rs b/src/zend/globals.rs index fc4864f..6a6bf70 100644 --- a/src/zend/globals.rs +++ b/src/zend/globals.rs @@ -9,9 +9,8 @@ use crate::boxed::ZBox; #[cfg(php82)] use crate::ffi::zend_atomic_bool_store; use crate::ffi::{ - _sapi_globals_struct, _zend_executor_globals, ext_php_rs_executor_globals, - _sapi_module_struct, ext_php_rs_sapi_module, - ext_php_rs_sapi_globals, zend_ini_entry, + _sapi_globals_struct, _sapi_module_struct, _zend_executor_globals, ext_php_rs_executor_globals, + ext_php_rs_sapi_globals, ext_php_rs_sapi_module, zend_ini_entry, }; use crate::types::{ZendHashTable, ZendObject}; From 3081630a3325e13da6cae20bb874a276e7bece81 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Fri, 24 Nov 2023 12:10:40 +0100 Subject: [PATCH 091/100] Update docsrs_bindings.rs --- docsrs_bindings.rs | 117 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) diff --git a/docsrs_bindings.rs b/docsrs_bindings.rs index dd3978a..2617c4e 100644 --- a/docsrs_bindings.rs +++ b/docsrs_bindings.rs @@ -201,6 +201,8 @@ pub type __time_t = ::std::os::raw::c_long; pub type __blksize_t = ::std::os::raw::c_long; pub type __blkcnt_t = ::std::os::raw::c_long; pub type __syscall_slong_t = ::std::os::raw::c_long; +pub type gid_t = __gid_t; +pub type uid_t = __uid_t; #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct __sigset_t { @@ -2265,6 +2267,12 @@ extern "C" { } #[repr(C)] #[derive(Debug, Copy, Clone)] +pub struct sapi_header_struct { + pub header: *mut ::std::os::raw::c_char, + pub header_len: usize, +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] pub struct sapi_headers_struct { pub headers: zend_llist, pub http_response_code: ::std::os::raw::c_int, @@ -2273,6 +2281,10 @@ pub struct sapi_headers_struct { pub http_status_line: *mut ::std::os::raw::c_char, } pub type sapi_post_entry = _sapi_post_entry; +pub type sapi_module_struct = _sapi_module_struct; +extern "C" { + pub static mut sapi_module: sapi_module_struct; +} #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct sapi_request_info { @@ -2323,6 +2335,111 @@ pub type sapi_globals_struct = _sapi_globals_struct; extern "C" { pub static mut sapi_globals: sapi_globals_struct; } +pub const sapi_header_op_enum_SAPI_HEADER_REPLACE: sapi_header_op_enum = 0; +pub const sapi_header_op_enum_SAPI_HEADER_ADD: sapi_header_op_enum = 1; +pub const sapi_header_op_enum_SAPI_HEADER_DELETE: sapi_header_op_enum = 2; +pub const sapi_header_op_enum_SAPI_HEADER_DELETE_ALL: sapi_header_op_enum = 3; +pub const sapi_header_op_enum_SAPI_HEADER_SET_STATUS: sapi_header_op_enum = 4; +pub type sapi_header_op_enum = ::std::os::raw::c_uint; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct _sapi_module_struct { + pub name: *mut ::std::os::raw::c_char, + pub pretty_name: *mut ::std::os::raw::c_char, + pub startup: ::std::option::Option< + unsafe extern "C" fn(sapi_module: *mut _sapi_module_struct) -> ::std::os::raw::c_int, + >, + pub shutdown: ::std::option::Option< + unsafe extern "C" fn(sapi_module: *mut _sapi_module_struct) -> ::std::os::raw::c_int, + >, + pub activate: ::std::option::Option ::std::os::raw::c_int>, + pub deactivate: ::std::option::Option ::std::os::raw::c_int>, + pub ub_write: ::std::option::Option< + unsafe extern "C" fn(str_: *const ::std::os::raw::c_char, str_length: usize) -> usize, + >, + pub flush: + ::std::option::Option, + pub get_stat: ::std::option::Option *mut zend_stat_t>, + pub getenv: ::std::option::Option< + unsafe extern "C" fn( + name: *const ::std::os::raw::c_char, + name_len: usize, + ) -> *mut ::std::os::raw::c_char, + >, + pub sapi_error: ::std::option::Option< + unsafe extern "C" fn( + type_: ::std::os::raw::c_int, + error_msg: *const ::std::os::raw::c_char, + ... + ), + >, + pub header_handler: ::std::option::Option< + unsafe extern "C" fn( + sapi_header: *mut sapi_header_struct, + op: sapi_header_op_enum, + sapi_headers: *mut sapi_headers_struct, + ) -> ::std::os::raw::c_int, + >, + pub send_headers: ::std::option::Option< + unsafe extern "C" fn(sapi_headers: *mut sapi_headers_struct) -> ::std::os::raw::c_int, + >, + pub send_header: ::std::option::Option< + unsafe extern "C" fn( + sapi_header: *mut sapi_header_struct, + server_context: *mut ::std::os::raw::c_void, + ), + >, + pub read_post: ::std::option::Option< + unsafe extern "C" fn(buffer: *mut ::std::os::raw::c_char, count_bytes: usize) -> usize, + >, + pub read_cookies: ::std::option::Option *mut ::std::os::raw::c_char>, + pub register_server_variables: + ::std::option::Option, + pub log_message: ::std::option::Option< + unsafe extern "C" fn( + message: *const ::std::os::raw::c_char, + syslog_type_int: ::std::os::raw::c_int, + ), + >, + pub get_request_time: + ::std::option::Option zend_result>, + pub terminate_process: ::std::option::Option, + pub php_ini_path_override: *mut ::std::os::raw::c_char, + pub default_post_reader: ::std::option::Option, + pub treat_data: ::std::option::Option< + unsafe extern "C" fn( + arg: ::std::os::raw::c_int, + str_: *mut ::std::os::raw::c_char, + destArray: *mut zval, + ), + >, + pub executable_location: *mut ::std::os::raw::c_char, + pub php_ini_ignore: ::std::os::raw::c_int, + pub php_ini_ignore_cwd: ::std::os::raw::c_int, + pub get_fd: ::std::option::Option< + unsafe extern "C" fn(fd: *mut ::std::os::raw::c_int) -> ::std::os::raw::c_int, + >, + pub force_http_10: ::std::option::Option ::std::os::raw::c_int>, + pub get_target_uid: + ::std::option::Option ::std::os::raw::c_int>, + pub get_target_gid: + ::std::option::Option ::std::os::raw::c_int>, + pub input_filter: ::std::option::Option< + unsafe extern "C" fn( + arg: ::std::os::raw::c_int, + var: *const ::std::os::raw::c_char, + val: *mut *mut ::std::os::raw::c_char, + val_len: usize, + new_val_len: *mut usize, + ) -> ::std::os::raw::c_uint, + >, + pub ini_defaults: + ::std::option::Option, + pub phpinfo_as_text: ::std::os::raw::c_int, + pub ini_entries: *const ::std::os::raw::c_char, + pub additional_functions: *const zend_function_entry, + pub input_filter_init: ::std::option::Option ::std::os::raw::c_uint>, +} #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct _sapi_post_entry { From b59dcd91c73785a1bd9c87d24fa572edc5c99575 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Fri, 24 Nov 2023 12:33:56 +0100 Subject: [PATCH 092/100] Bump --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index a484175..0deea94 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/davidcole1340/ext-php-rs" homepage = "https://github.com/davidcole1340/ext-php-rs" license = "MIT OR Apache-2.0" keywords = ["php", "ffi", "zend"] -version = "0.10.4" +version = "0.10.5" authors = ["David Cole "] edition = "2018" categories = ["api-bindings"] From 6c0719c96b744edd1fcbf9a1259e7263d01a1227 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Fri, 24 Nov 2023 12:52:33 +0100 Subject: [PATCH 093/100] Run tests using clang 16 --- .github/workflows/build.yml | 2 +- .github/workflows/docs.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 557d80c..a21061e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,7 +17,7 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] php: ["8.0", "8.1", "8.2", "8.3"] rust: [stable, nightly] - clang: ["14"] + clang: ["16"] phpts: [ts, nts] exclude: # ext-php-rs requires nightly Rust when on Windows. diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 5f55df1..522682b 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -16,7 +16,7 @@ jobs: matrix: os: ["ubuntu-latest"] php: ["8.2"] - clang: ["14"] + clang: ["17"] mdbook: ["latest"] steps: - name: Checkout code From 8761816e23fa8b9cbb3ab77f2e44cc955e5a32ac Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Fri, 24 Nov 2023 13:09:30 +0100 Subject: [PATCH 094/100] Test more combos --- .github/workflows/build.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a21061e..409b016 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,7 +17,7 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] php: ["8.0", "8.1", "8.2", "8.3"] rust: [stable, nightly] - clang: ["16"] + clang: ["15", "17"] phpts: [ts, nts] exclude: # ext-php-rs requires nightly Rust when on Windows. @@ -26,6 +26,8 @@ jobs: # setup-php doesn't support thread safe PHP on Linux and macOS. - os: macos-latest phpts: ts + - os: macos-latest + clang: "17" - os: ubuntu-latest phpts: ts env: From 5bb9ab1e452dae7c44434fbfc4cf5abbb9ea3a27 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Fri, 24 Nov 2023 13:09:55 +0100 Subject: [PATCH 095/100] Test more combos --- .github/workflows/build.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 409b016..e324863 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -26,10 +26,14 @@ jobs: # setup-php doesn't support thread safe PHP on Linux and macOS. - os: macos-latest phpts: ts + - os: ubuntu-latest + phpts: ts - os: macos-latest clang: "17" - os: ubuntu-latest - phpts: ts + clang: "15" + - os: windows-latest + clang: "15" env: CARGO_TERM_COLOR: always steps: From 3cd7a8a1f75c92fc64910c0e4456c8cbd99dc00b Mon Sep 17 00:00:00 2001 From: Joe Hoyle Date: Sun, 5 Feb 2023 12:08:28 +0200 Subject: [PATCH 096/100] Add support for Zval IS_INDIRECT As mentioned in #219, I believe we're making incorrect use of IS_CALLABLE on Zval types right now. Zval type bits are never actually stored as IS_CALLABLE, which overlaps with the _actual_ type value of IS_INDIRECT. IS_INDIRECT is almost the same as IS_REFERENCE, but is a direct reference to another Zval rather than reference object. As `Zval::is_callable()` and `Zval::callable()` don't actually make use of `IS_CALLABLE` then I think it's safe to switch this out. --- allowed_bindings.rs | 1 + docsrs_bindings.rs | 1 + src/flags.rs | 28 ++++++++++++++++------------ src/types/zval.rs | 24 ++++++++++++++++++++++++ 4 files changed, 42 insertions(+), 12 deletions(-) diff --git a/allowed_bindings.rs b/allowed_bindings.rs index 2046ab5..40556e9 100644 --- a/allowed_bindings.rs +++ b/allowed_bindings.rs @@ -143,6 +143,7 @@ bind! { IS_CONSTANT_AST_EX, IS_DOUBLE, IS_FALSE, + IS_INDIRECT, IS_INTERNED_STRING_EX, IS_LONG, IS_MIXED, diff --git a/docsrs_bindings.rs b/docsrs_bindings.rs index 2617c4e..34da760 100644 --- a/docsrs_bindings.rs +++ b/docsrs_bindings.rs @@ -99,6 +99,7 @@ pub const IS_CONSTANT_AST: u32 = 11; pub const IS_CALLABLE: u32 = 12; pub const IS_VOID: u32 = 14; pub const IS_MIXED: u32 = 16; +pub const IS_INDIRECT: u32 = 12; pub const IS_PTR: u32 = 13; pub const _IS_BOOL: u32 = 18; pub const Z_TYPE_FLAGS_SHIFT: u32 = 8; diff --git a/src/flags.rs b/src/flags.rs index 60897a0..9e814d2 100644 --- a/src/flags.rs +++ b/src/flags.rs @@ -8,14 +8,14 @@ use crate::ffi::{ CONST_CS, CONST_DEPRECATED, CONST_NO_FILE_CACHE, CONST_PERSISTENT, E_COMPILE_ERROR, E_COMPILE_WARNING, E_CORE_ERROR, E_CORE_WARNING, E_DEPRECATED, E_ERROR, E_NOTICE, E_PARSE, E_RECOVERABLE_ERROR, E_STRICT, E_USER_DEPRECATED, E_USER_ERROR, E_USER_NOTICE, E_USER_WARNING, - E_WARNING, IS_ARRAY, IS_CALLABLE, IS_CONSTANT_AST, IS_DOUBLE, IS_FALSE, IS_LONG, IS_MIXED, - IS_NULL, IS_OBJECT, IS_PTR, IS_REFERENCE, IS_RESOURCE, IS_STRING, IS_TRUE, IS_TYPE_COLLECTABLE, - IS_TYPE_REFCOUNTED, IS_UNDEF, IS_VOID, PHP_INI_ALL, PHP_INI_PERDIR, PHP_INI_SYSTEM, - PHP_INI_USER, ZEND_ACC_ABSTRACT, ZEND_ACC_ANON_CLASS, ZEND_ACC_CALL_VIA_TRAMPOLINE, - ZEND_ACC_CHANGED, ZEND_ACC_CLOSURE, ZEND_ACC_CONSTANTS_UPDATED, ZEND_ACC_CTOR, - ZEND_ACC_DEPRECATED, ZEND_ACC_DONE_PASS_TWO, ZEND_ACC_EARLY_BINDING, ZEND_ACC_FAKE_CLOSURE, - ZEND_ACC_FINAL, ZEND_ACC_GENERATOR, ZEND_ACC_HAS_FINALLY_BLOCK, ZEND_ACC_HAS_RETURN_TYPE, - ZEND_ACC_HAS_TYPE_HINTS, ZEND_ACC_HEAP_RT_CACHE, ZEND_ACC_IMMUTABLE, + E_WARNING, IS_ARRAY, IS_CALLABLE, IS_CONSTANT_AST, IS_DOUBLE, IS_FALSE, IS_INDIRECT, IS_LONG, + IS_MIXED, IS_NULL, IS_OBJECT, IS_PTR, IS_REFERENCE, IS_RESOURCE, IS_STRING, IS_TRUE, + IS_TYPE_COLLECTABLE, IS_TYPE_REFCOUNTED, IS_UNDEF, IS_VOID, PHP_INI_ALL, PHP_INI_PERDIR, + PHP_INI_SYSTEM, PHP_INI_USER, ZEND_ACC_ABSTRACT, ZEND_ACC_ANON_CLASS, + ZEND_ACC_CALL_VIA_TRAMPOLINE, ZEND_ACC_CHANGED, ZEND_ACC_CLOSURE, ZEND_ACC_CONSTANTS_UPDATED, + ZEND_ACC_CTOR, ZEND_ACC_DEPRECATED, ZEND_ACC_DONE_PASS_TWO, ZEND_ACC_EARLY_BINDING, + ZEND_ACC_FAKE_CLOSURE, ZEND_ACC_FINAL, ZEND_ACC_GENERATOR, ZEND_ACC_HAS_FINALLY_BLOCK, + ZEND_ACC_HAS_RETURN_TYPE, ZEND_ACC_HAS_TYPE_HINTS, ZEND_ACC_HEAP_RT_CACHE, ZEND_ACC_IMMUTABLE, ZEND_ACC_IMPLICIT_ABSTRACT_CLASS, ZEND_ACC_INTERFACE, ZEND_ACC_LINKED, ZEND_ACC_NEARLY_LINKED, ZEND_ACC_NEVER_CACHE, ZEND_ACC_NO_DYNAMIC_PROPERTIES, ZEND_ACC_PRELOADED, ZEND_ACC_PRIVATE, ZEND_ACC_PROMOTED, ZEND_ACC_PROTECTED, ZEND_ACC_PUBLIC, ZEND_ACC_RESOLVED_INTERFACES, @@ -246,6 +246,7 @@ pub enum DataType { Mixed, Bool, Ptr, + Indirect, } impl Default for DataType { @@ -269,6 +270,7 @@ impl DataType { DataType::Object(_) => IS_OBJECT, DataType::Resource => IS_RESOURCE, DataType::Reference => IS_RESOURCE, + DataType::Indirect => IS_INDIRECT, DataType::Callable => IS_CALLABLE, DataType::ConstantExpression => IS_CONSTANT_AST, DataType::Void => IS_VOID, @@ -337,6 +339,7 @@ impl From for DataType { contains!(IS_VOID, Void); contains!(IS_PTR, Ptr); + contains!(IS_INDIRECT, Indirect); contains!(IS_CALLABLE, Callable); contains!(IS_CONSTANT_AST, ConstantExpression); contains!(IS_REFERENCE, Reference); @@ -379,6 +382,7 @@ impl Display for DataType { DataType::Bool => write!(f, "Bool"), DataType::Mixed => write!(f, "Mixed"), DataType::Ptr => write!(f, "Pointer"), + DataType::Indirect => write!(f, "Indirect"), } } } @@ -388,9 +392,9 @@ mod tests { use super::DataType; use crate::ffi::{ IS_ARRAY, IS_ARRAY_EX, IS_CALLABLE, IS_CONSTANT_AST, IS_CONSTANT_AST_EX, IS_DOUBLE, - IS_FALSE, IS_INTERNED_STRING_EX, IS_LONG, IS_NULL, IS_OBJECT, IS_OBJECT_EX, IS_PTR, - IS_REFERENCE, IS_REFERENCE_EX, IS_RESOURCE, IS_RESOURCE_EX, IS_STRING, IS_STRING_EX, - IS_TRUE, IS_UNDEF, IS_VOID, + IS_FALSE, IS_INDIRECT, IS_INTERNED_STRING_EX, IS_LONG, IS_NULL, IS_OBJECT, IS_OBJECT_EX, + IS_PTR, IS_REFERENCE, IS_REFERENCE_EX, IS_RESOURCE, IS_RESOURCE_EX, IS_STRING, + IS_STRING_EX, IS_TRUE, IS_UNDEF, IS_VOID, }; use std::convert::TryFrom; @@ -414,7 +418,7 @@ mod tests { test!(IS_RESOURCE, Resource); test!(IS_REFERENCE, Reference); test!(IS_CONSTANT_AST, ConstantExpression); - test!(IS_CALLABLE, Callable); + test!(IS_INDIRECT, Indirect); test!(IS_VOID, Void); test!(IS_PTR, Ptr); diff --git a/src/types/zval.rs b/src/types/zval.rs index 94b62aa..97def9a 100644 --- a/src/types/zval.rs +++ b/src/types/zval.rs @@ -213,6 +213,24 @@ impl Zval { .try_call_method(name, params) } + /// Returns the value of the zval if it is an internal indirect reference. + pub fn indirect(&self) -> Option<&Zval> { + if self.is_indirect() { + Some(unsafe { &*(self.value.zv as *mut Zval) }) + } else { + None + } + } + + /// Returns a mutable reference to the zval if it is an internal indirect reference. + pub fn indirect_mut(&self) -> Option<&mut Zval> { + if self.is_indirect() { + Some(unsafe { &mut *(self.value.zv as *mut Zval) }) + } else { + None + } + } + /// Returns the value of the zval if it is a reference. pub fn reference(&self) -> Option<&Zval> { if self.is_reference() { @@ -329,6 +347,11 @@ impl Zval { self.get_type() == DataType::Reference } + /// Returns true if the zval is a reference, false otherwise. + pub fn is_indirect(&self) -> bool { + self.get_type() == DataType::Indirect + } + /// Returns true if the zval is callable, false otherwise. pub fn is_callable(&self) -> bool { let ptr: *const Self = self; @@ -601,6 +624,7 @@ impl Debug for Zval { DataType::ConstantExpression => field!(Option::<()>::None), DataType::Void => field!(Option::<()>::None), DataType::Bool => field!(self.bool()), + DataType::Indirect => field!(self.indirect()), // SAFETY: We are not accessing the pointer. DataType::Ptr => field!(unsafe { self.ptr::() }), }; From 05eb9fc3eafba1325f44965822d9aed91b7e8789 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Fri, 24 Nov 2023 13:32:15 +0100 Subject: [PATCH 097/100] Use better name --- src/zend/function.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zend/function.rs b/src/zend/function.rs index 234fb62..d4aa20b 100644 --- a/src/zend/function.rs +++ b/src/zend/function.rs @@ -50,7 +50,7 @@ impl FunctionEntry { pub type Function = zend_function; impl Function { - pub fn type_(&self) -> FunctionType { + pub fn function_type(&self) -> FunctionType { FunctionType::from(unsafe { self.type_ }) } From 50dad0cec44dc0be7dda14d54bdab7299a22c8ca Mon Sep 17 00:00:00 2001 From: Joe Hoyle Date: Fri, 24 Nov 2023 13:38:46 +0100 Subject: [PATCH 098/100] Manually drop zval --- src/exception.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/exception.rs b/src/exception.rs index e1f4c63..aed768a 100644 --- a/src/exception.rs +++ b/src/exception.rs @@ -206,8 +206,9 @@ pub fn throw_with_code(ex: &ClassEntry, code: i32, message: &str) -> Result<()> /// throw_object( error.into_zval(true).unwrap() ); /// ``` pub fn throw_object(zval: Zval) -> Result<()> { + let mut zv = core::mem::ManuallyDrop::new(zval); unsafe { - zend_throw_exception_object((&zval as *const _) as *mut _); + zend_throw_exception_object(core::ptr::addr_of_mut!(zv).cast()) }; Ok(()) } From 10d5f3a41a9d02b93bd0afe7df4493e83cc1ce4b Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Fri, 24 Nov 2023 13:38:56 +0100 Subject: [PATCH 099/100] Fmt --- src/zend/function.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/zend/function.rs b/src/zend/function.rs index d4aa20b..6e6dd1a 100644 --- a/src/zend/function.rs +++ b/src/zend/function.rs @@ -9,7 +9,8 @@ use crate::{ zend_call_known_function, zend_fetch_function_str, zend_function, zend_function_entry, zend_hash_str_find_ptr_lc, }, - types::Zval, flags::FunctionType, + flags::FunctionType, + types::Zval, }; use super::ClassEntry; From 6fdd6a35bd3527c964553c58254d2aab1852ec25 Mon Sep 17 00:00:00 2001 From: Joe Hoyle Date: Fri, 24 Nov 2023 13:47:25 +0100 Subject: [PATCH 100/100] fmt --- src/exception.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/exception.rs b/src/exception.rs index aed768a..de7feba 100644 --- a/src/exception.rs +++ b/src/exception.rs @@ -207,8 +207,6 @@ pub fn throw_with_code(ex: &ClassEntry, code: i32, message: &str) -> Result<()> /// ``` pub fn throw_object(zval: Zval) -> Result<()> { let mut zv = core::mem::ManuallyDrop::new(zval); - unsafe { - zend_throw_exception_object(core::ptr::addr_of_mut!(zv).cast()) - }; + unsafe { zend_throw_exception_object(core::ptr::addr_of_mut!(zv).cast()) }; Ok(()) }