diff --git a/Cargo.lock b/Cargo.lock index 0614d886..3ee7cd90 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1142,6 +1142,7 @@ dependencies = [ name = "phper-macros" version = "0.13.0" dependencies = [ + "phper-sys", "proc-macro2", "quote", "syn", diff --git a/examples/complex/src/args_bindings.rs b/examples/complex/src/args_bindings.rs index de61e1a3..e28116f3 100644 --- a/examples/complex/src/args_bindings.rs +++ b/examples/complex/src/args_bindings.rs @@ -3,4 +3,18 @@ #![allow(non_snake_case)] #![allow(deref_nullptr)] #![allow(clippy::all)] -include!(concat!(env!("OUT_DIR"), "/php_args_bindings.rs")); + +use phper::zend_create_fn; + +mod bindings { + include!(concat!(env!("OUT_DIR"), "/php_args_bindings.rs")); +} + +use bindings::register_class_Complex_Foo; + +pub use bindings::{ + arginfo_Complex_get_all_ini, arginfo_Complex_say_hello, arginfo_Complex_throw_exception, + arginfo_class_Complex_Foo_getFoo, arginfo_class_Complex_Foo_setFoo, +}; + +zend_create_fn!(register_class_Complex_Foo, CLASS_COMPLEX_FOO); diff --git a/examples/complex/src/lib.rs b/examples/complex/src/lib.rs index 271e1b49..323354e8 100644 --- a/examples/complex/src/lib.rs +++ b/examples/complex/src/lib.rs @@ -15,18 +15,22 @@ use std::ffi::CStr; use args_bindings::{ arginfo_Complex_get_all_ini, arginfo_Complex_say_hello, arginfo_Complex_throw_exception, - register_class_Complex_Foo, + arginfo_class_Complex_Foo_getFoo, arginfo_class_Complex_Foo_setFoo, }; +use crate::args_bindings::CLASS_COMPLEX_FOO; use phper::arrays::ZArray; +use phper::classes::methods::MethodEntityBuilder; use phper::classes::ClassEntity; use phper::ini::{ini_get, Policy}; -use phper::{modules::Module, php_get_module, values::ZVal, zend_args, zend_create_fn}; +use phper::objects::StateObj; +use phper::{modules::Module, php_get_module, values::ZVal, zend_args}; fn say_hello(arguments: &mut [ZVal]) -> phper::Result { let name = &mut arguments[0]; name.convert_to_string(); let name = name.as_z_str().unwrap().to_str()?; + Ok(format!("Hello, {name}!\n")) } @@ -58,58 +62,52 @@ pub fn get_module() -> Module { module.on_request_init(|_info| {}); module.on_request_shutdown(|_info| {}); - module - .add_function( - "Comples\\say_hello", - zend_args!(arginfo_Complex_say_hello), - say_hello, - ) - .add_function( - "Complex\\throw_exception", - zend_args!(arginfo_Complex_throw_exception), - throw_exception, - ) - .add_function( - "Comples\\get_all_ini", - zend_args!(arginfo_Complex_get_all_ini), - |_: &mut [ZVal]| { - let mut arr = ZArray::new(); - - let complex_enable = ZVal::from(ini_get::("complex.enable")); - arr.insert("complex.enable", complex_enable); - - let complex_description = - ZVal::from(ini_get::>("complex.description")); - arr.insert("complex.description", complex_description); - Ok::<_, Infallible>(arr) - }, - ); - + module.add_function( + "Complex\\say_hello", + zend_args!(arginfo_Complex_say_hello), + say_hello, + ); + // .add_function( + // "Complex\\throw_exception", + // zend_args!(arginfo_Complex_throw_exception), + // throw_exception, + // ) + // .add_function( + // "Complex\\get_all_ini", + // zend_args!(arginfo_Complex_get_all_ini), + // |_: &mut [ZVal]| { + // let mut arr = ZArray::new(); + // + // let complex_enable = ZVal::from(ini_get::("complex.enable")); + // arr.insert("complex.enable", complex_enable); + // + // let complex_description = + // ZVal::from(ini_get::>("complex.description")); + // arr.insert("complex.description", complex_description); + // Ok::<_, Infallible>(arr) + // }, + // ); + // + // let mut foo_class = ClassEntity::new(CLASS_COMPLEX_FOO); // - // // register classes - let mut foo_class = ClassEntity::new(zend_create_fn!(register_class_Complex_Foo)); - // foo_class.add_property("foo", Visibility::Private, 100); // foo_class.add_method( - // "getFoo", - // Visibility::Public, // |this: &mut StateObj, _: &mut [ZVal]| { - // let prop = this.get_property("foo"); - // Ok::<_, phper::Error>(prop.clone()) + // Ok::<_, phper::Error>(this.get_property("foo").clone()) // }, + // MethodEntityBuilder::new("getFoo", zend_args!(arginfo_class_Complex_Foo_getFoo)) + // .set_public(), // ); - // foo_class - // .add_method( - // "setFoo", - // Visibility::Public, - // |this: &mut StateObj, arguments: &mut [ZVal]| -> phper::Result<()> { - // this.set_property("foo", arguments[0].clone()); - // Ok(()) - // }, - // ) - // .argument(Argument::by_val("foo")); - // module.add_class(foo_class); // - // // register extra info + // foo_class.add_method( + // |this: &mut StateObj, arguments: &mut [ZVal]| -> phper::Result<()> { + // this.set_property("foo", arguments[0].clone()); + // Ok(()) + // }, + // MethodEntityBuilder::new("setFoo", zend_args!(arginfo_class_Complex_Foo_setFoo)) + // .set_public(), + // ); + // module.add_class(foo_class); + module.add_info("extra info key", "extra info value"); module diff --git a/examples/complex/stubs/say_hello.stub.php b/examples/complex/stubs/say_hello.stub.php index fd5e7b35..5764aa2d 100644 --- a/examples/complex/stubs/say_hello.stub.php +++ b/examples/complex/stubs/say_hello.stub.php @@ -3,6 +3,8 @@ /** @generate-class-entries */ namespace Complex { +// const HELLO_WORLD = 'hello world'; + function say_hello(string $name): string {} function throw_exception(): void @@ -19,6 +21,7 @@ function get_all_ini(): array */ class Foo { private int|\JsonSerializable|\ArrayAccess $foo = 100; + public function getFoo(): int {} public function setFoo(int $foo): void {} diff --git a/examples/complex/tests/php/test.php b/examples/complex/tests/php/test.php index f7ac5b4d..a421d548 100644 --- a/examples/complex/tests/php/test.php +++ b/examples/complex/tests/php/test.php @@ -15,24 +15,26 @@ ini_set("display_startup_errors", "On"); error_reporting(E_ALL); -assert_eq(complex_say_hello("world"), "Hello, world!\n"); - -try { - complex_throw_exception(); -} catch (ErrorException $e) { - assert_eq($e->getMessage(), "I am sorry"); -} - -assert_eq(complex_get_all_ini(), [ - "complex.enable" => false, - "complex.description" => "hello world.", -]); - -$foo = new FooClass(); -assert_eq($foo->getFoo(), 100); - -$foo->setFoo("Hello"); -assert_eq($foo->getFoo(), "Hello"); +// print_r(get_defined_functions()); +// print_r(ini_get_all()); +assert_eq(Complex\say_hello("world"), "Hello, world!\n"); +// +// try { +// Complex\throw_exception(); +// } catch (ErrorException $e) { +// assert_eq($e->getMessage(), "I am sorry"); +// } +// +// assert_eq(Complex\get_all_ini(), [ +// "complex.enable" => false, +// "complex.description" => "hello world.", +// ]); + +// $foo = new Complex\\FooClass(); +// assert_eq($foo->getFoo(), 100); +// +// $foo->setFoo(200); +// assert_eq($foo->getFoo(), 200); function assert_eq($left, $right) { if ($left !== $right) { diff --git a/phper-alloc/src/lib.rs b/phper-alloc/src/lib.rs index 9920a627..6ad44d99 100644 --- a/phper-alloc/src/lib.rs +++ b/phper-alloc/src/lib.rs @@ -58,8 +58,8 @@ impl EBox { /// Consumes and returning a wrapped raw pointer. /// /// Will leak memory. - pub fn into_raw(b: EBox) -> *mut T { - ManuallyDrop::new(b).ptr + pub fn into_raw(self) -> *mut T { + ManuallyDrop::new(self).ptr } /// Consumes the `EBox`, returning the wrapped value. diff --git a/phper-macros/Cargo.toml b/phper-macros/Cargo.toml index a9cb6aff..7c4514c3 100644 --- a/phper-macros/Cargo.toml +++ b/phper-macros/Cargo.toml @@ -27,5 +27,7 @@ quote = "1.0.31" syn = { version = "2.0.26", features = ["full"] } proc-macro2 = "1.0.66" +phper-sys = { workspace = true } + [dev-dependencies] syn = { version = "2.0.26", features = ["full", "extra-traits"] } diff --git a/phper-macros/src/alloc.rs b/phper-macros/src/alloc.rs deleted file mode 100644 index 0efeeb85..00000000 --- a/phper-macros/src/alloc.rs +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) 2022 PHPER Framework Team -// PHPER is licensed under Mulan PSL v2. -// You can use this software according to the terms and conditions of the Mulan -// PSL v2. You may obtain a copy of Mulan PSL v2 at: -// http://license.coscl.org.cn/MulanPSL2 -// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY -// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. -// See the Mulan PSL v2 for more details. diff --git a/phper-macros/src/derives.rs b/phper-macros/src/derives.rs deleted file mode 100644 index 0efeeb85..00000000 --- a/phper-macros/src/derives.rs +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) 2022 PHPER Framework Team -// PHPER is licensed under Mulan PSL v2. -// You can use this software according to the terms and conditions of the Mulan -// PSL v2. You may obtain a copy of Mulan PSL v2 at: -// http://license.coscl.org.cn/MulanPSL2 -// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY -// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. -// See the Mulan PSL v2 for more details. diff --git a/phper-macros/src/globals.rs b/phper-macros/src/globals.rs deleted file mode 100644 index 0efeeb85..00000000 --- a/phper-macros/src/globals.rs +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) 2022 PHPER Framework Team -// PHPER is licensed under Mulan PSL v2. -// You can use this software according to the terms and conditions of the Mulan -// PSL v2. You may obtain a copy of Mulan PSL v2 at: -// http://license.coscl.org.cn/MulanPSL2 -// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY -// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. -// See the Mulan PSL v2 for more details. diff --git a/phper-macros/src/lib.rs b/phper-macros/src/lib.rs index f8387cfe..c48d73f3 100644 --- a/phper-macros/src/lib.rs +++ b/phper-macros/src/lib.rs @@ -8,19 +8,14 @@ // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. // See the Mulan PSL v2 for more details. -#![warn(rust_2018_idioms, missing_docs)] +#![warn(rust_2021_compatibility, rust_2018_idioms)] +#![allow(missing_docs)] #![warn(clippy::dbg_macro, clippy::print_stdout)] #![doc = include_str!("../README.md")] -// TODO Write a bridge macro for easy usage about register functions and -// classes, like `cxx`. - -mod alloc; -mod derives; -mod globals; mod inner; -mod log; mod utils; +mod zend_create_fn; use proc_macro::TokenStream; @@ -52,6 +47,11 @@ pub fn c_str_ptr(input: TokenStream) -> TokenStream { utils::c_str_ptr(input) } +#[proc_macro] +pub fn zend_create_fn(input: TokenStream) -> TokenStream { + zend_create_fn::zend_create_fn(input) +} + /// PHP module entry, wrap the `phper::modules::Module` write operation. /// /// # Examples diff --git a/phper-macros/src/log.rs b/phper-macros/src/log.rs deleted file mode 100644 index 0efeeb85..00000000 --- a/phper-macros/src/log.rs +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) 2022 PHPER Framework Team -// PHPER is licensed under Mulan PSL v2. -// You can use this software according to the terms and conditions of the Mulan -// PSL v2. You may obtain a copy of Mulan PSL v2 at: -// http://license.coscl.org.cn/MulanPSL2 -// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY -// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. -// See the Mulan PSL v2 for more details. diff --git a/phper-macros/src/zend_create_fn.rs b/phper-macros/src/zend_create_fn.rs new file mode 100644 index 00000000..ebd614f9 --- /dev/null +++ b/phper-macros/src/zend_create_fn.rs @@ -0,0 +1,44 @@ +use proc_macro::TokenStream; +use proc_macro2::Span; +use quote::quote; +use syn::parse::{Parse, ParseBuffer}; +use syn::Ident; +use syn::{parse_macro_input, Token}; + +struct ZendFn { + original_zend_fn: Ident, + const_name: Ident, +} + +impl Parse for ZendFn { + fn parse(input: &'_ ParseBuffer<'_>) -> syn::Result { + let original_zend_fn: Ident = input.parse()?; + input.parse::()?; + let const_name: Ident = input.parse()?; + Ok(ZendFn { + original_zend_fn, + const_name, + }) + } +} + +pub(crate) fn zend_create_fn(input: TokenStream) -> TokenStream { + let ZendFn { + original_zend_fn, + const_name, + } = parse_macro_input!(input as ZendFn); + + let fn_name = format!("zend_create_fn_{}", original_zend_fn.to_string()); + let new_fn_name = Ident::new(&fn_name, Span::call_site()); + + let result = quote! { + unsafe extern "C" fn #new_fn_name() -> *mut phper::sys::zend_class_entry + { + unsafe { #original_zend_fn() as *mut phper::sys::zend_class_entry } + } + + pub(crate) const #const_name: unsafe extern "C" fn() -> *mut phper::sys::zend_class_entry = #new_fn_name; + }; + + result.into() +} diff --git a/phper/src/arrays.rs b/phper/src/arrays.rs deleted file mode 100644 index d167f298..00000000 --- a/phper/src/arrays.rs +++ /dev/null @@ -1,621 +0,0 @@ -// Copyright (c) 2022 PHPER Framework Team -// PHPER is licensed under Mulan PSL v2. -// You can use this software according to the terms and conditions of the Mulan -// PSL v2. You may obtain a copy of Mulan PSL v2 at: -// http://license.coscl.org.cn/MulanPSL2 -// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY -// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. -// See the Mulan PSL v2 for more details. - -//! Apis relate to [zend_array]. - -use crate::{alloc::ToRefOwned, strings::ZStr, sys::*, values::ZVal}; -use derive_more::From; -use std::{ - borrow::Borrow, - convert::TryInto, - fmt::{self, Debug}, - marker::PhantomData, - mem::ManuallyDrop, - ops::{Deref, DerefMut}, - ptr::null_mut, -}; - -/// Key for [ZArr]. -#[derive(Debug, Clone, PartialEq, From)] -pub enum Key<'a> { - /// Index type key. - Index(u64), - /// String type key. - Str(&'a str), - /// String type key. - Bytes(&'a [u8]), - /// String type key. - ZStr(&'a ZStr), -} - -/// Insert key for [ZArr]. -#[derive(Debug, Clone, PartialEq, From)] -pub enum InsertKey<'a> { - /// Insert with next index type key, like `$farr[] = "oo"` in PHP. - NextIndex, - /// Insert with index type key, like `$farr[0] = "oo"` in PHP. - Index(u64), - /// Insert with string type key, like `$farr["string"] = "oo"` in PHP. - Str(&'a str), - /// Insert with string type key. - Bytes(&'a [u8]), - /// Insert with string type key. - ZStr(&'a ZStr), -} - -impl<'a> From> for InsertKey<'a> { - fn from(k: Key<'a>) -> Self { - match k { - Key::Index(i) => InsertKey::Index(i), - Key::Str(s) => InsertKey::Str(s), - Key::Bytes(b) => InsertKey::Bytes(b), - Key::ZStr(s) => InsertKey::ZStr(s), - } - } -} - -/// Wrapper of [zend_array]. -#[repr(transparent)] -pub struct ZArr { - inner: zend_array, - _p: PhantomData<*mut ()>, -} - -impl ZArr { - /// Wraps a raw pointer. - /// - /// # Safety - /// - /// Create from raw pointer. - /// - /// # Panics - /// - /// Panics if pointer is null. - #[inline] - pub unsafe fn from_ptr<'a>(ptr: *const zend_array) -> &'a Self { - (ptr as *const Self).as_ref().expect("ptr should't be null") - } - - /// Wraps a raw pointer, return None if pointer is null. - /// - /// # Safety - /// - /// Create from raw pointer. - #[inline] - pub unsafe fn try_from_ptr<'a>(ptr: *const zend_array) -> Option<&'a Self> { - (ptr as *const Self).as_ref() - } - - /// Wraps a raw pointer. - /// - /// # Safety - /// - /// Create from raw pointer. - /// - /// # Panics - /// - /// Panics if pointer is null. - #[inline] - pub unsafe fn from_mut_ptr<'a>(ptr: *mut zend_array) -> &'a mut Self { - (ptr as *mut Self).as_mut().expect("ptr should't be null") - } - - /// Wraps a raw pointer, return None if pointer is null. - /// - /// # Safety - /// - /// Create from raw pointer. - #[inline] - pub unsafe fn try_from_mut_ptr<'a>(ptr: *mut zend_array) -> Option<&'a mut Self> { - (ptr as *mut Self).as_mut() - } - - /// Returns a raw pointer wrapped. - pub const fn as_ptr(&self) -> *const zend_array { - &self.inner - } - - /// Returns a raw pointer wrapped. - #[inline] - pub fn as_mut_ptr(&mut self) -> *mut zend_array { - &mut self.inner - } - - /// Returns true if the array has a length of 0. - #[inline] - pub fn is_empty(&mut self) -> bool { - self.len() == 0 - } - - /// Get array items length. - #[inline] - pub fn len(&mut self) -> usize { - unsafe { zend_array_count(self.as_mut_ptr()).try_into().unwrap() } - } - - /// Add or update item by key. - #[allow(clippy::useless_conversion)] - pub fn insert<'a>(&mut self, key: impl Into>, value: impl Into) { - let key = key.into(); - let mut value = ManuallyDrop::new(value.into()); - let val = value.as_mut_ptr(); - - unsafe { - match key { - InsertKey::NextIndex => { - phper_zend_hash_next_index_insert(self.as_mut_ptr(), val); - } - InsertKey::Index(i) => { - phper_zend_hash_index_update(self.as_mut_ptr(), i, val); - } - InsertKey::Str(s) => { - phper_zend_str_update( - self.as_mut_ptr(), - s.as_ptr().cast(), - s.len().try_into().unwrap(), - val, - ); - } - InsertKey::Bytes(b) => { - phper_zend_str_update( - self.as_mut_ptr(), - b.as_ptr().cast(), - b.len().try_into().unwrap(), - val, - ); - } - InsertKey::ZStr(s) => { - phper_zend_str_update( - self.as_mut_ptr(), - s.as_c_str_ptr().cast(), - s.len().try_into().unwrap(), - val, - ); - } - } - } - } - - /// Get item by key. - pub fn get<'a>(&self, key: impl Into>) -> Option<&'a ZVal> { - self.inner_get(key).map(|v| &*v) - } - - /// Get item by key. - pub fn get_mut<'a>(&mut self, key: impl Into>) -> Option<&'a mut ZVal> { - self.inner_get(key) - } - - #[allow(clippy::useless_conversion)] - fn inner_get<'a>(&self, key: impl Into>) -> Option<&'a mut ZVal> { - let key = key.into(); - let ptr = self.as_ptr() as *mut _; - unsafe { - let value = match key { - Key::Index(i) => phper_zend_hash_index_find(ptr, i), - Key::Str(s) => { - phper_zend_str_find(ptr, s.as_ptr().cast(), s.len().try_into().unwrap()) - } - Key::Bytes(b) => { - phper_zend_str_find(ptr, b.as_ptr().cast(), b.len().try_into().unwrap()) - } - Key::ZStr(s) => { - phper_zend_str_find(ptr, s.as_c_str_ptr(), s.len().try_into().unwrap()) - } - }; - if value.is_null() { - None - } else { - Some(ZVal::from_mut_ptr(value)) - } - } - } - - /// Check if the key exists. - #[allow(clippy::useless_conversion)] - pub fn exists<'a>(&self, key: impl Into>) -> bool { - let key = key.into(); - let ptr = self.as_ptr() as *mut _; - unsafe { - match key { - Key::Index(i) => phper_zend_hash_index_exists(ptr, i), - Key::Str(s) => { - phper_zend_str_exists(ptr, s.as_ptr().cast(), s.len().try_into().unwrap()) - } - Key::Bytes(b) => { - phper_zend_str_exists(ptr, b.as_ptr().cast(), b.len().try_into().unwrap()) - } - Key::ZStr(s) => phper_zend_str_exists( - ptr, - s.to_bytes().as_ptr().cast(), - s.len().try_into().unwrap(), - ), - } - } - } - - /// Remove the item under the key - #[allow(clippy::useless_conversion)] - pub fn remove<'a>(&mut self, key: impl Into>) -> bool { - let key = key.into(); - unsafe { - match key { - Key::Index(i) => phper_zend_hash_index_del(&mut self.inner, i), - Key::Str(s) => phper_zend_str_del( - &mut self.inner, - s.as_ptr().cast(), - s.len().try_into().unwrap(), - ), - Key::Bytes(b) => phper_zend_str_del( - &mut self.inner, - b.as_ptr().cast(), - b.len().try_into().unwrap(), - ), - Key::ZStr(s) => phper_zend_str_del( - &mut self.inner, - s.as_c_str_ptr().cast(), - s.len().try_into().unwrap(), - ), - } - } - } - - /// Gets the given key’s corresponding entry in the array for in-place - /// manipulation. - /// - /// # Examples - /// - /// ```no_run - /// use phper::arrays::ZArray; - /// - /// let mut arr = ZArray::new(); - /// - /// // count the number of occurrences of letters in the vec - /// for x in ["a", "b", "a", "c", "a", "b"] { - /// arr.entry(x) - /// .and_modify(|cur| *cur.as_mut_long().unwrap() += 1) - /// .or_insert(1); - /// } - /// ``` - pub fn entry<'a>(&'a mut self, key: impl Into>) -> Entry<'a> { - let key = key.into(); - match self.get_mut(key.clone()) { - Some(val) => Entry::Occupied(OccupiedEntry(val)), - None => Entry::Vacant(VacantEntry { arr: self, key }), - } - } - - /// Provides a forward iterator. - #[inline] - pub fn iter(&self) -> Iter<'_> { - Iter::new(self) - } - - /// Provides a forward iterator with mutable references. - #[inline] - pub fn iter_mut(&mut self) -> IterMut<'_> { - IterMut::new(self) - } -} - -impl Debug for ZArr { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - common_fmt(self, f, "ZArr") - } -} - -impl ToOwned for ZArr { - type Owned = ZArray; - - fn to_owned(&self) -> Self::Owned { - unsafe { - // TODO The source really immutable? - let dest = phper_zend_array_dup(self.as_ptr() as *mut _); - ZArray::from_raw(dest) - } - } -} - -impl ToRefOwned for ZArr { - type Owned = ZArray; - - fn to_ref_owned(&mut self) -> Self::Owned { - let mut val = ManuallyDrop::new(ZVal::default()); - unsafe { - phper_zval_arr(val.as_mut_ptr(), self.as_mut_ptr()); - phper_z_addref_p(val.as_mut_ptr()); - ZArray::from_raw(val.as_mut_z_arr().unwrap().as_mut_ptr()) - } - } -} - -/// Wrapper of [zend_array]. -#[repr(transparent)] -pub struct ZArray { - inner: *mut ZArr, -} - -impl ZArray { - /// Creates an empty `ZArray`. - #[inline] - pub fn new() -> Self { - Self::with_capacity(0) - } - - /// Creates an empty `ZArray` with at least the specified capacity. - /// - /// Note that the actual capacity is always a power of two, so if you have - /// 12 elements in a hashtable the actual table capacity will be 16. - pub fn with_capacity(n: usize) -> Self { - unsafe { - let ptr = phper_zend_new_array(n.try_into().unwrap()); - Self::from_raw(ptr) - } - } - - /// Create owned object From raw pointer, usually used in pairs with - /// `into_raw`. - /// - /// # Safety - /// - /// This function is unsafe because improper use may lead to memory - /// problems. For example, a double-free may occur if the function is called - /// twice on the same raw pointer. - #[inline] - pub unsafe fn from_raw(ptr: *mut zend_array) -> Self { - Self { - inner: ZArr::from_mut_ptr(ptr), - } - } - - /// Consumes the `ZArray` and transfers ownership to a raw pointer. - /// - /// Failure to call [`ZArray::from_raw`] will lead to a memory leak. - #[inline] - pub fn into_raw(self) -> *mut zend_array { - ManuallyDrop::new(self).as_mut_ptr() - } -} - -impl Debug for ZArray { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - common_fmt(self, f, "ZArray") - } -} - -impl Default for ZArray { - fn default() -> Self { - Self::new() - } -} - -impl Deref for ZArray { - type Target = ZArr; - - fn deref(&self) -> &Self::Target { - unsafe { self.inner.as_ref().unwrap() } - } -} - -impl DerefMut for ZArray { - fn deref_mut(&mut self) -> &mut Self::Target { - unsafe { self.inner.as_mut().unwrap() } - } -} - -impl Borrow for ZArray { - fn borrow(&self) -> &ZArr { - self.deref() - } -} - -impl Clone for ZArray { - fn clone(&self) -> Self { - self.deref().to_owned() - } -} - -impl Drop for ZArray { - fn drop(&mut self) { - unsafe { - zend_array_destroy(self.as_mut_ptr()); - } - } -} - -/// Iterator key for [`ZArr::iter`] and [`ZArr::iter_mut`]. -#[derive(Debug, Clone, PartialEq, From)] -pub enum IterKey<'a> { - /// Index type iterator key. - Index(u64), - /// String type iterator key. - ZStr(&'a ZStr), -} - -struct RawIter<'a> { - arr: *mut zend_array, - pos: HashPosition, - finished: bool, - _p: PhantomData<&'a ()>, -} - -impl<'a> RawIter<'a> { - fn new(arr: *mut zend_array) -> Self { - let mut pos: HashPosition = 0; - unsafe { - zend_hash_internal_pointer_reset_ex(arr, &mut pos); - } - Self { - arr, - pos, - finished: false, - _p: PhantomData, - } - } -} - -impl<'a> Iterator for RawIter<'a> { - type Item = (IterKey<'a>, *mut zval); - - fn next(&mut self) -> Option { - unsafe { - if self.finished { - return None; - } - - let mut str_index: *mut zend_string = null_mut(); - let mut num_index: zend_ulong = 0; - - #[allow(clippy::unnecessary_mut_passed)] - let result = zend_hash_get_current_key_ex( - self.arr, - &mut str_index, - &mut num_index, - &mut self.pos, - ) as u32; - - let iter_key = if result == HASH_KEY_IS_STRING { - IterKey::ZStr(ZStr::from_mut_ptr(str_index)) - } else if result == HASH_KEY_IS_LONG { - #[allow(clippy::unnecessary_cast)] - IterKey::Index(num_index as u64) - } else { - self.finished = true; - return None; - }; - - let val = zend_hash_get_current_data_ex(self.arr, &mut self.pos); - if val.is_null() { - self.finished = true; - return None; - } - - if zend_hash_move_forward_ex(self.arr, &mut self.pos) == ZEND_RESULT_CODE_FAILURE { - self.finished = true; - } - - Some((iter_key, val)) - } - } -} - -/// An iterator over the elements of a `ZArr`. -/// -/// This is created by [`iter`]. -/// -/// [`iter`]: ZArr::iter -pub struct Iter<'a>(RawIter<'a>); - -impl<'a> Iter<'a> { - fn new(arr: &'a ZArr) -> Self { - Self(RawIter::new(arr.as_ptr() as *mut _)) - } -} - -impl<'a> Iterator for Iter<'a> { - type Item = (IterKey<'a>, &'a ZVal); - - fn next(&mut self) -> Option { - self.0 - .next() - .map(|(key, val)| (key, unsafe { ZVal::from_ptr(val) })) - } -} - -/// An mutable iterator over the elements of a `ZArr`. -/// -/// This is created by [`iter_mut`]. -/// -/// [`iter_mut`]: ZArr::iter_mut -pub struct IterMut<'a>(RawIter<'a>); - -impl<'a> IterMut<'a> { - fn new(arr: &'a mut ZArr) -> Self { - Self(RawIter::new(arr.as_mut_ptr())) - } -} - -impl<'a> Iterator for IterMut<'a> { - type Item = (IterKey<'a>, &'a mut ZVal); - - fn next(&mut self) -> Option { - self.0 - .next() - .map(|(key, val)| (key, unsafe { ZVal::from_mut_ptr(val) })) - } -} - -/// A view into a single entry in an array, which may either be vacant or -/// occupied. -/// -/// This `enum` is constructed from the [`entry`] method on [`ZArr`]. -/// -/// [`entry`]: ZArr::entry -pub enum Entry<'a> { - /// An occupied entry. - Occupied(OccupiedEntry<'a>), - /// A vacant entry. - Vacant(VacantEntry<'a>), -} - -/// A view into an occupied entry in a `ZArr`. -/// It is part of the [`Entry`] enum. -pub struct OccupiedEntry<'a>(&'a mut ZVal); - -/// A view into a vacant entry in a `ZArr`. -/// It is part of the [`Entry`] enum. -pub struct VacantEntry<'a> { - arr: &'a mut ZArr, - key: Key<'a>, -} - -impl<'a> Entry<'a> { - /// Provides in-place mutable access to an occupied entry before any - /// potential inserts into the array. - pub fn and_modify(self, f: F) -> Self - where - F: FnOnce(&mut ZVal), - { - match self { - Entry::Occupied(entry) => { - f(entry.0); - Entry::Occupied(entry) - } - entry => entry, - } - } - - /// Ensures a value is in the entry by inserting the default if empty, and - /// returns a mutable reference to the value in the entry. - pub fn or_insert(self, val: impl Into) -> &'a mut ZVal { - match self { - Entry::Occupied(entry) => entry.0, - Entry::Vacant(entry) => { - let insert_key: InsertKey<'_> = entry.key.clone().into(); - entry.arr.insert(insert_key, val); - entry.arr.get_mut(entry.key).unwrap() - } - } - } -} - -fn common_fmt(this: &ZArr, f: &mut fmt::Formatter<'_>, name: &str) -> fmt::Result { - struct Debugger<'a>(&'a ZArr); - - impl Debug for Debugger<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_map().entries(self.0.iter()).finish() - } - } - - let zd = Debugger(this); - - f.debug_tuple(name).field(&zd).finish() -} diff --git a/phper/src/arrays/entry.rs b/phper/src/arrays/entry.rs new file mode 100644 index 00000000..14e08ecb --- /dev/null +++ b/phper/src/arrays/entry.rs @@ -0,0 +1,56 @@ +use crate::arrays::{InsertKey, Key, ZArr}; +use crate::values::ZVal; + +/// A view into a single entry in an array, which may either be vacant or +/// occupied. +/// +/// This `enum` is constructed from the [`entry`] method on [`ZArr`]. +/// +/// [`entry`]: ZArr::entry +pub enum Entry<'a> { + /// An occupied entry. + Occupied(OccupiedEntry<'a>), + /// A vacant entry. + Vacant(VacantEntry<'a>), +} + +/// A view into an occupied entry in a `ZArr`. +/// It is part of the [`Entry`] enum. +pub struct OccupiedEntry<'a>(pub(super) &'a mut ZVal); + +/// A view into a vacant entry in a `ZArr`. +/// It is part of the [`Entry`] enum. +pub struct VacantEntry<'a> { + pub(super) arr: &'a mut ZArr, + pub(super) key: Key<'a>, +} + +impl<'a> Entry<'a> { + /// Provides in-place mutable access to an occupied entry before any + /// potential inserts into the array. + pub fn and_modify(self, f: F) -> Self + where + F: FnOnce(&mut ZVal), + { + match self { + Entry::Occupied(entry) => { + f(entry.0); + Entry::Occupied(entry) + } + entry => entry, + } + } + + /// Ensures a value is in the entry by inserting the default if empty, and + /// returns a mutable reference to the value in the entry. + pub fn or_insert(self, val: impl Into) -> &'a mut ZVal { + match self { + Entry::Occupied(entry) => entry.0, + Entry::Vacant(entry) => { + let insert_key: InsertKey<'_> = entry.key.clone().into(); + entry.arr.insert(insert_key, val); + entry.arr.get_mut(entry.key).unwrap() + } + } + } +} diff --git a/phper/src/arrays/iterators.rs b/phper/src/arrays/iterators.rs new file mode 100644 index 00000000..e952abf8 --- /dev/null +++ b/phper/src/arrays/iterators.rs @@ -0,0 +1,133 @@ +use derive_more::From; +use phper_sys::{ + zend_array, zend_hash_get_current_data_ex, zend_hash_get_current_key_ex, + zend_hash_internal_pointer_reset_ex, zend_hash_move_forward_ex, zend_string, zend_ulong, zval, + HashPosition, HASH_KEY_IS_LONG, HASH_KEY_IS_STRING, ZEND_RESULT_CODE_FAILURE, +}; +use std::marker::PhantomData; +use std::ptr::null_mut; + +use crate::arrays::ZArr; +use crate::strings::ZStr; +use crate::values::ZVal; + +/// Iterator key for [`ZArr::iter`] and [`ZArr::iter_mut`]. +#[derive(Debug, Clone, PartialEq, From)] +pub enum IterKey<'a> { + /// Index type iterator key. + Index(u64), + /// String type iterator key. + ZStr(&'a ZStr), +} + +/// An iterator over the elements of a `ZArr`. +/// +/// This is created by [`iter`]. +/// +/// [`iter`]: ZArr::iter +pub struct Iter<'a>(RawIter<'a>); + +impl<'a> Iter<'a> { + pub(super) fn new(arr: &'a ZArr) -> Self { + Self(RawIter::new(arr.as_ptr() as *mut _)) + } +} + +impl<'a> Iterator for Iter<'a> { + type Item = (IterKey<'a>, &'a ZVal); + + fn next(&mut self) -> Option { + self.0 + .next() + .map(|(key, val)| (key, unsafe { ZVal::from_ptr(val) })) + } +} + +/// An mutable iterator over the elements of a `ZArr`. +/// +/// This is created by [`iter_mut`]. +/// +/// [`iter_mut`]: ZArr::iter_mut +pub struct IterMut<'a>(RawIter<'a>); + +impl<'a> IterMut<'a> { + pub(super) fn new(arr: &'a mut ZArr) -> Self { + Self(RawIter::new(arr.as_mut_ptr())) + } +} + +impl<'a> Iterator for IterMut<'a> { + type Item = (IterKey<'a>, &'a mut ZVal); + + fn next(&mut self) -> Option { + self.0 + .next() + .map(|(key, val)| (key, unsafe { ZVal::from_mut_ptr(val) })) + } +} + +struct RawIter<'a> { + arr: *mut zend_array, + pos: HashPosition, + finished: bool, + _p: PhantomData<&'a ()>, +} + +impl<'a> RawIter<'a> { + fn new(arr: *mut zend_array) -> Self { + let mut pos: HashPosition = 0; + unsafe { + zend_hash_internal_pointer_reset_ex(arr, &mut pos); + } + + Self { + arr, + pos, + finished: false, + _p: PhantomData, + } + } +} + +impl<'a> Iterator for RawIter<'a> { + type Item = (IterKey<'a>, *mut zval); + + fn next(&mut self) -> Option { + unsafe { + if self.finished { + return None; + } + + let mut str_index: *mut zend_string = null_mut(); + let mut num_index: zend_ulong = 0; + + let result = zend_hash_get_current_key_ex( + self.arr, + &mut str_index, + &mut num_index, + &mut self.pos, + ) as u32; + + let iter_key = if result == HASH_KEY_IS_STRING { + IterKey::ZStr(ZStr::from_mut_ptr(str_index)) + } else if result == HASH_KEY_IS_LONG { + IterKey::Index(num_index as u64) + } else { + self.finished = true; + return None; + }; + + let val = zend_hash_get_current_data_ex(self.arr, &mut self.pos); + if val.is_null() { + self.finished = true; + return None; + } + + if zend_hash_move_forward_ex(self.arr, &mut self.pos) == ZEND_RESULT_CODE_FAILURE { + self.finished = true; + } + + Some((iter_key, val)) + } + } +} diff --git a/phper/src/arrays/keys.rs b/phper/src/arrays/keys.rs new file mode 100644 index 00000000..c6c00c9d --- /dev/null +++ b/phper/src/arrays/keys.rs @@ -0,0 +1,41 @@ +use crate::strings::ZStr; +use derive_more::From; + +/// Key for [ZArr]. +#[derive(Debug, Clone, PartialEq, From)] +pub enum Key<'a> { + /// Index type key. + Index(u64), + /// String type key. + Str(&'a str), + /// String type key. + Bytes(&'a [u8]), + /// String type key. + ZStr(&'a ZStr), +} + +/// Insert key for [ZArr]. +#[derive(Debug, Clone, PartialEq, From)] +pub enum InsertKey<'a> { + /// Insert with next index type key, like `$farr[] = "oo"` in PHP. + NextIndex, + /// Insert with index type key, like `$farr[0] = "oo"` in PHP. + Index(u64), + /// Insert with string type key, like `$farr["string"] = "oo"` in PHP. + Str(&'a str), + /// Insert with string type key. + Bytes(&'a [u8]), + /// Insert with zend string type key. + ZStr(&'a ZStr), +} + +impl<'a> From> for InsertKey<'a> { + fn from(k: Key<'a>) -> Self { + match k { + Key::Index(i) => InsertKey::Index(i), + Key::Str(s) => InsertKey::Str(s), + Key::Bytes(b) => InsertKey::Bytes(b), + Key::ZStr(s) => InsertKey::ZStr(s), + } + } +} diff --git a/phper/src/arrays/mod.rs b/phper/src/arrays/mod.rs new file mode 100644 index 00000000..59084cc1 --- /dev/null +++ b/phper/src/arrays/mod.rs @@ -0,0 +1,47 @@ +// Copyright (c) 2022 PHPER Framework Team +// PHPER is licensed under Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan +// PSL v2. You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY +// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +// See the Mulan PSL v2 for more details. + +//! Apis relate to [zend_array]. + +mod entry; +mod iterators; +mod keys; +mod zarr; +mod zarray; + +use std::{ + borrow::Borrow, + convert::TryInto, + fmt::{self, Debug}, + ops::{Deref, DerefMut}, +}; + +use derive_more::From; + +use crate::{alloc::ToRefOwned, sys::*}; + +pub use iterators::{Iter, IterKey, IterMut}; +pub use keys::{InsertKey, Key}; +pub use zarr::ZArr; +pub use zarray::ZArray; + +fn common_fmt(this: &ZArr, f: &mut fmt::Formatter<'_>, name: &str) -> fmt::Result { + struct Debugger<'a>(&'a ZArr); + + impl Debug for Debugger<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_map().entries(self.0.iter()).finish() + } + } + + let zd = Debugger(this); + + f.debug_tuple(name).field(&zd).finish() +} diff --git a/phper/src/arrays/zarr.rs b/phper/src/arrays/zarr.rs new file mode 100644 index 00000000..4c40390e --- /dev/null +++ b/phper/src/arrays/zarr.rs @@ -0,0 +1,315 @@ +use crate::arrays::entry::{Entry, OccupiedEntry, VacantEntry}; +use crate::arrays::zarray::ZArray; +use crate::arrays::{common_fmt, InsertKey, Iter, IterMut, Key}; +use crate::values::ZVal; +use phper_alloc::ToRefOwned; +use phper_sys::{ + phper_z_addref_p, phper_zend_array_dup, phper_zend_hash_index_del, + phper_zend_hash_index_exists, phper_zend_hash_index_find, phper_zend_hash_index_update, + phper_zend_hash_next_index_insert, phper_zend_str_del, phper_zend_str_exists, + phper_zend_str_find, phper_zend_str_update, phper_zval_arr, zend_array, zend_array_count, +}; +use std::fmt; +use std::fmt::Debug; +use std::marker::PhantomData; +use std::mem::ManuallyDrop; + +/// Wrapper of [zend_array]. +#[repr(transparent)] +pub struct ZArr { + inner: zend_array, + _p: PhantomData<*mut ()>, +} + +impl ZArr { + /// Wraps a raw pointer. + /// + /// # Safety + /// + /// Create from raw pointer. + /// + /// # Panics + /// + /// Panics if pointer is null. + #[inline] + pub unsafe fn from_ptr<'a>(ptr: *const zend_array) -> &'a Self { + (ptr as *const Self) + .as_ref() + .expect("ptr shouldn't be null") + } + + /// Wraps a raw pointer, return None if pointer is null. + /// + /// # Safety + /// + /// Create from raw pointer. + #[inline] + pub unsafe fn try_from_ptr<'a>(ptr: *const zend_array) -> Option<&'a Self> { + (ptr as *const Self).as_ref() + } + + /// Wraps a raw pointer. + /// + /// # Safety + /// + /// Create from raw pointer. + /// + /// # Panics + /// + /// Panics if pointer is null. + #[inline] + pub unsafe fn from_mut_ptr<'a>(ptr: *mut zend_array) -> &'a mut Self { + (ptr as *mut Self).as_mut().expect("ptr should't be null") + } + + /// Wraps a raw pointer, return None if pointer is null. + /// + /// # Safety + /// + /// Create from raw pointer. + #[inline] + pub unsafe fn try_from_mut_ptr<'a>(ptr: *mut zend_array) -> Option<&'a mut Self> { + (ptr as *mut Self).as_mut() + } + + /// Returns a raw pointer wrapped. + #[inline] + pub const fn as_ptr(&self) -> *const zend_array { + &self.inner + } + + /// Returns a raw pointer wrapped. + #[inline] + pub fn as_mut_ptr(&mut self) -> *mut zend_array { + &mut self.inner + } + + /// Returns true if the array has a length of 0. + #[inline] + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Get array items length. + #[inline] + pub fn len(&self) -> usize { + unsafe { zend_array_count(self.as_ptr() as *mut _) as usize } + } + + /// Add or update item by key. + pub fn insert<'a>(&mut self, key: impl Into>, value: impl Into) { + let key = key.into(); + let mut value = ManuallyDrop::new(value.into()); + let val = value.as_mut_ptr(); + + unsafe { + match key { + InsertKey::NextIndex => { + phper_zend_hash_next_index_insert(self.as_mut_ptr(), val); + } + InsertKey::Index(i) => { + phper_zend_hash_index_update(self.as_mut_ptr(), i, val); + } + InsertKey::Str(s) => { + phper_zend_str_update( + self.as_mut_ptr(), + s.as_ptr().cast(), + s.len().try_into().unwrap(), + val, + ); + } + InsertKey::Bytes(b) => { + phper_zend_str_update( + self.as_mut_ptr(), + b.as_ptr().cast(), + b.len().try_into().unwrap(), + val, + ); + } + InsertKey::ZStr(s) => { + phper_zend_str_update( + self.as_mut_ptr(), + s.as_c_str_ptr().cast(), + s.len().try_into().unwrap(), + val, + ); + } + } + } + } + + /// Get item by key. + #[inline] + pub fn get<'a>(&self, key: impl Into>) -> Option<&'a ZVal> { + self.inner_get(key).map(|v| &*v) + } + + /// Get item by key. + #[inline] + pub fn get_mut<'a>(&mut self, key: impl Into>) -> Option<&'a mut ZVal> { + self.inner_get(key) + } + + fn inner_get<'a>(&self, key: impl Into>) -> Option<&'a mut ZVal> { + let key = key.into(); + let ptr = self.as_ptr() as *mut _; + unsafe { + let value = match key { + Key::Index(i) => phper_zend_hash_index_find(ptr, i), + Key::Str(s) => { + phper_zend_str_find(ptr, s.as_ptr().cast(), s.len().try_into().unwrap()) + } + Key::Bytes(b) => { + phper_zend_str_find(ptr, b.as_ptr().cast(), b.len().try_into().unwrap()) + } + Key::ZStr(s) => { + phper_zend_str_find(ptr, s.as_c_str_ptr(), s.len().try_into().unwrap()) + } + }; + if value.is_null() { + None + } else { + Some(ZVal::from_mut_ptr(value)) + } + } + } + + /// Check if the key exists. + pub fn exists<'a>(&self, key: impl Into>) -> bool { + let key = key.into(); + let ptr = self.as_ptr() as *mut _; + unsafe { + match key { + Key::Index(i) => phper_zend_hash_index_exists(ptr, i), + Key::Str(s) => { + phper_zend_str_exists(ptr, s.as_ptr().cast(), s.len().try_into().unwrap()) + } + Key::Bytes(b) => { + phper_zend_str_exists(ptr, b.as_ptr().cast(), b.len().try_into().unwrap()) + } + Key::ZStr(s) => phper_zend_str_exists( + ptr, + s.to_bytes().as_ptr().cast(), + s.len().try_into().unwrap(), + ), + } + } + } + + /// Remove the item under the key + pub fn remove<'a>(&mut self, key: impl Into>) -> bool { + let key = key.into(); + unsafe { + match key { + Key::Index(i) => phper_zend_hash_index_del(&mut self.inner, i), + Key::Str(s) => phper_zend_str_del( + &mut self.inner, + s.as_ptr().cast(), + s.len().try_into().unwrap(), + ), + Key::Bytes(b) => phper_zend_str_del( + &mut self.inner, + b.as_ptr().cast(), + b.len().try_into().unwrap(), + ), + Key::ZStr(s) => phper_zend_str_del( + &mut self.inner, + s.as_c_str_ptr().cast(), + s.len().try_into().unwrap(), + ), + } + } + } + + /// Gets the given key’s corresponding entry in the array for in-place + /// manipulation. + /// + /// # Examples + /// + /// ```no_run + /// use phper::arrays::ZArray; + /// + /// let mut arr = ZArray::new(); + /// + /// // count the number of occurrences of letters in the vec + /// for x in ["a", "b", "a", "c", "a", "b"] { + /// arr.entry(x) + /// .and_modify(|cur| *cur.as_mut_long().unwrap() += 1) + /// .or_insert(1); + /// } + /// ``` + #[inline] + pub fn entry<'a>(&'a mut self, key: impl Into>) -> Entry<'a> { + let key = key.into(); + match self.get_mut(key.clone()) { + Some(val) => Entry::Occupied(OccupiedEntry(val)), + None => Entry::Vacant(VacantEntry { arr: self, key }), + } + } + + /// Provides a forward iterator. + #[inline] + pub fn iter(&self) -> Iter<'_> { + Iter::new(self) + } + + /// Provides a forward iterator with mutable references. + #[inline] + pub fn iter_mut(&mut self) -> IterMut<'_> { + IterMut::new(self) + } + + /// Gets Rust slice from PHP Packed Array + /// + /// # Safety: + /// Called must be sure that array is PACKED + #[inline] + pub unsafe fn as_slice(&self) -> &[ZVal] { + std::slice::from_raw_parts( + self.inner.__bindgen_anon_1.arPacked as *const ZVal, + self.len(), + ) + } + + /// Gets Mutable Rust slice from PHP Packed Array + /// + /// # Safety: + /// Called must be sure that array is PACKED + #[inline] + pub unsafe fn as_mut_slice(&mut self) -> &mut [ZVal] { + std::slice::from_raw_parts_mut( + self.inner.__bindgen_anon_1.arPacked as *mut ZVal, + self.len(), + ) + } +} + +impl Debug for ZArr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + common_fmt(self, f, "ZArr") + } +} + +impl ToOwned for ZArr { + type Owned = ZArray; + + fn to_owned(&self) -> Self::Owned { + unsafe { + let dest = phper_zend_array_dup(self.as_ptr() as *mut _); + ZArray::from_raw(dest) + } + } +} + +impl ToRefOwned for ZArr { + type Owned = ZArray; + + fn to_ref_owned(&mut self) -> Self::Owned { + let mut val = ManuallyDrop::new(ZVal::default()); + unsafe { + phper_zval_arr(val.as_mut_ptr(), self.as_mut_ptr()); + phper_z_addref_p(val.as_mut_ptr()); + ZArray::from_raw(val.as_mut_z_arr().unwrap().as_mut_ptr()) + } + } +} diff --git a/phper/src/arrays/zarray.rs b/phper/src/arrays/zarray.rs new file mode 100644 index 00000000..5d0a6c78 --- /dev/null +++ b/phper/src/arrays/zarray.rs @@ -0,0 +1,111 @@ +use crate::arrays::{common_fmt, ZArr}; +use crate::values::ZVal; +use phper_sys::{phper_z_arr_p, phper_zend_new_array, zend_array, zend_array_destroy}; +use std::borrow::Borrow; +use std::fmt; +use std::fmt::Debug; +use std::mem::ManuallyDrop; +use std::ops::{Deref, DerefMut}; + +/// Wrapper of [zend_array]. +#[repr(transparent)] +pub struct ZArray(*mut ZArr); + +impl ZArray { + /// Creates an empty `ZArray`. + #[inline] + pub fn new() -> Self { + Self::with_capacity(0) + } + + /// Creates an empty `ZArray` with at least the specified capacity. + /// + /// Note that the actual capacity is always a power of two, so if you have + /// 12 elements in a hashtable the actual table capacity will be 16. + pub fn with_capacity(n: usize) -> Self { + unsafe { + let ptr = phper_zend_new_array(n.try_into().unwrap()); + Self::from_raw(ptr) + } + } + + /// Create owned object From raw pointer, usually used in pairs with + /// `into_raw`. + /// + /// # Safety + /// + /// This function is unsafe because improper use may lead to memory + /// problems. For example, a double-free may occur if the function is called + /// twice on the same raw pointer. + #[inline] + pub unsafe fn from_raw(ptr: *mut zend_array) -> Self { + Self(ZArr::from_mut_ptr(ptr)) + } + + /// Consumes the `ZArray` and transfers ownership to a raw pointer. + /// + /// Failure to call [`ZArray::from_raw`] will lead to a memory leak. + #[inline] + pub fn into_raw(self) -> *mut zend_array { + ManuallyDrop::new(self).as_mut_ptr() + } + + pub(crate) unsafe fn from_zval(value: ZVal) -> Self { + let value = ManuallyDrop::new(value); + ZArray::from_raw(phper_z_arr_p(value.as_ptr()) as *mut zend_array) + } + + pub unsafe fn as_slice(&self) -> &[ZVal] { + unsafe { &*self.0 }.as_slice() + } + + pub unsafe fn as_mut_slice(&mut self) -> &mut [ZVal] { + unsafe { &mut *self.0 }.as_mut_slice() + } +} + +impl Debug for ZArray { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + common_fmt(self, f, "ZArray") + } +} + +impl Default for ZArray { + fn default() -> Self { + Self::new() + } +} + +impl Deref for ZArray { + type Target = ZArr; + + fn deref(&self) -> &Self::Target { + unsafe { self.0.as_ref().unwrap() } + } +} + +impl DerefMut for ZArray { + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { self.0.as_mut().unwrap() } + } +} + +impl Borrow for ZArray { + fn borrow(&self) -> &ZArr { + self.deref() + } +} + +impl Clone for ZArray { + fn clone(&self) -> Self { + self.deref().to_owned() + } +} + +impl Drop for ZArray { + fn drop(&mut self) { + unsafe { + zend_array_destroy(self.as_mut_ptr()); + } + } +} diff --git a/phper/src/classes/entity.rs b/phper/src/classes/entity.rs index e26c5e45..b2e9a6aa 100644 --- a/phper/src/classes/entity.rs +++ b/phper/src/classes/entity.rs @@ -1,12 +1,16 @@ +use std::ffi::c_char; use std::{any::Any, marker::PhantomData, mem::zeroed, rc::Rc}; use smallvec::SmallVec; use phper_sys::{phper_register_class_entry, zend_class_entry, zend_function_entry}; +use crate::classes::methods::MethodEntityBuilder; +use crate::errors::Throwable; +use crate::functions::{Function, Method}; use crate::{functions::FunctionEntry, objects::StateObj, values::ZVal}; -use super::{create_object, entry::ClassEntry, StateCloner, StateConstructor, StaticStateClass}; +use super::{create_object, StateConstructor, StaticStateClass}; /// Builder for registering class. /// @@ -61,11 +65,7 @@ impl ClassEntity { where T: 'static, { - // let class_name = class_name.as_ref(); - // let class_name_len = class_name.len(); - Self { - // class: unsafe { phper_init_class_entry(class_name.as_ptr().cast(), class_name_len) }, state_constructor: Rc::new(move || Box::new(state_constructor()) as Box), method_entities: SmallVec::default(), class_create, @@ -76,86 +76,31 @@ impl ClassEntity { } /// Add member method to class, with visibility and method handler. - // pub fn add_method( - // &mut self, - // name: impl AsRef, - // vis: Visibility, - // handler: F, - // ) -> &mut MethodEntity - // where - // F: Fn(&mut StateObj, &mut [ZVal]) -> Result + 'static, - // Z: Into + 'static, - // E: Throwable + 'static, - // { - // self.method_entities.push(MethodEntity::new( - // name, - // Some(Rc::new(Method::::new(handler))), - // vis, - // null(), - // )); - // self.method_entities.last_mut().unwrap() - // } + pub fn add_method(&mut self, handler: F, method_builder: MethodEntityBuilder) + where + F: Fn(&mut StateObj, &mut [ZVal]) -> Result + 'static, + Z: Into + 'static, + E: Throwable + 'static, + { + let entity = method_builder + .set_handler(Method::::new(handler)) + .build(); + self.method_entities.push(entity.into()); + } /// Add static method to class, with visibility and method handler. - // pub fn add_static_method( - // &mut self, - // name: impl AsRef, - // vis: Visibility, - // handler: F, - // ) -> &mut MethodEntity - // where - // F: Fn(&mut [ZVal]) -> Result + 'static, - // Z: Into + 'static, - // E: Throwable + 'static, - // { - // let mut entity = MethodEntity::new(name, Some(Rc::new(Function::new(handler))), vis, null()); - // entity.set_vis_static(); - // self.method_entities.push(entity); - // self.method_entities.last_mut().unwrap() - // } - - /// Add abstract method to class, with visibility (shouldn't be private). - // pub fn add_abstract_method( - // &mut self, - // name: impl AsRef, - // vis: Visibility, - // ) -> &mut MethodEntity { - // let mut entity = MethodEntity::new(name, None, vis, null()); - // entity.set_vis_abstract(); - // self.method_entities.push(entity); - // self.method_entities.last_mut().unwrap() - // } - - /// Declare property. - /// - /// The argument `value` should be `Copy` because 'zend_declare_property' - /// receive only scalar zval , otherwise will report fatal error: - /// "Internal zvals cannot be refcounted". - // pub fn add_property( - // &mut self, - // name: impl Into, - // visibility: Visibility, - // value: impl Into, - // ) { - // self.property_entities - // .push(PropertyEntity::new(name, visibility, value)); - // } - - /// Declare static property. - /// - /// The argument `value` should be `Copy` because 'zend_declare_property' - /// receive only scalar zval , otherwise will report fatal error: - /// "Internal zvals cannot be refcounted". - // pub fn add_static_property( - // &mut self, - // name: impl Into, - // visibility: Visibility, - // value: impl Into, - // ) { - // let mut entity = PropertyEntity::new(name, visibility, value); - // entity.set_vis_static(); - // self.property_entities.push(entity); - // } + pub fn add_static_method(&mut self, handler: F, method_builder: MethodEntityBuilder) + where + F: Fn(&mut [ZVal]) -> Result + 'static, + Z: Into + 'static, + E: Throwable + 'static, + { + let entity = method_builder + .set_abstract() + .set_handler(Function::new(handler)) + .build(); + self.method_entities.push(entity.into()); + } /// Bind to static [StaticStateClass]. /// @@ -220,8 +165,9 @@ impl crate::modules::Registerer for ClassEntity { { let mut entry = zeroed::(); - let ptr = &mut entry as *mut _ as *mut *const StateConstructor; - let state_constructor = Rc::into_raw(self.state_constructor); + + let ptr = std::ptr::from_mut(&mut entry.fname); + let state_constructor = Rc::into_raw(self.state_constructor) as *const c_char; ptr.write(state_constructor); methods.push(FunctionEntry(entry)); } diff --git a/phper/src/classes/methods.rs b/phper/src/classes/methods.rs index 9b34ab6e..7e635c2d 100644 --- a/phper/src/classes/methods.rs +++ b/phper/src/classes/methods.rs @@ -6,12 +6,11 @@ use phper_sys::{ ZEND_ACC_PROTECTED, ZEND_ACC_PUBLIC, }; use std::ffi::CString; -use std::rc::Rc; /// Builder for registering class method. pub struct MethodEntity { pub(crate) name: CString, - pub(crate) handler: Option>, + pub(crate) handler: Option>, pub(crate) arguments: &'static [zend_internal_arg_info], pub(crate) visibility: RawVisibility, } @@ -19,7 +18,7 @@ pub struct MethodEntity { #[derive(Default)] pub struct MethodEntityBuilder { name: CString, - handler: Option>, + handler: Option>, arguments: &'static [zend_internal_arg_info], visibility: RawVisibility, } @@ -32,7 +31,7 @@ impl Into for MethodEntity { impl MethodEntityBuilder { #[inline] - fn new(name: impl AsRef, arguments: &'static [zend_internal_arg_info]) -> Self { + pub fn new(name: impl AsRef, arguments: &'static [zend_internal_arg_info]) -> Self { Self { name: ensure_end_with_zero(name), handler: None, @@ -42,8 +41,8 @@ impl MethodEntityBuilder { } #[inline] - pub fn set_handler(mut self, handler: impl Into>) -> Self { - self.handler = Some(handler.into()); + pub(crate) fn set_handler(mut self, handler: impl Callable + 'static) -> Self { + self.handler = Some(Box::new(handler)); self } @@ -78,6 +77,8 @@ impl MethodEntityBuilder { } pub(crate) fn build(self) -> MethodEntity { + assert!(self.handler.is_some()); + MethodEntity { name: self.name, handler: self.handler, diff --git a/phper/src/classes/mod.rs b/phper/src/classes/mod.rs index ddaca309..a481dab4 100644 --- a/phper/src/classes/mod.rs +++ b/phper/src/classes/mod.rs @@ -15,13 +15,11 @@ pub mod entity; pub mod entry; pub mod interfaces; pub mod methods; -pub mod properties; pub mod zend_classes; pub use entity::*; pub use interfaces::*; pub use methods::MethodEntity; -pub use properties::*; pub use zend_classes::*; use crate::{ @@ -173,10 +171,10 @@ unsafe extern "C" fn create_object(ce: *mut zend_class_entry) -> *mut zend_objec let state_constructor = state_constructor.read().as_ref().unwrap(); // Get state cloner. - func_ptr = func_ptr.offset(1); - let has_state_cloner = - slice::from_raw_parts(func_ptr as *const u8, size_of::<*const StateCloner>()) - != [0u8; size_of::<*const StateCloner>()]; + // func_ptr = func_ptr.offset(1); + // let has_state_cloner = + // slice::from_raw_parts(func_ptr as *const u8, size_of::<*const StateCloner>()) + // != [0u8; size_of::<*const StateCloner>()]; // Common initialize process. let object = state_object.as_mut_object().as_mut_ptr(); @@ -188,7 +186,7 @@ unsafe extern "C" fn create_object(ce: *mut zend_class_entry) -> *mut zend_objec let mut handlers = Box::new(std_object_handlers); handlers.offset = StateObj::offset() as c_int; handlers.free_obj = Some(free_object); - handlers.clone_obj = has_state_cloner.then_some(clone_object); + // handlers.clone_obj = has_state_cloner.then_some(clone_object); (*object).handlers = Box::into_raw(handlers); // Call the state constructor and store the state. diff --git a/phper/src/classes/properties.rs b/phper/src/classes/properties.rs deleted file mode 100644 index ebe761f8..00000000 --- a/phper/src/classes/properties.rs +++ /dev/null @@ -1,90 +0,0 @@ -use crate::classes::RawVisibility; -use crate::types::Scalar; -use phper_sys::{ - zend_class_entry, zend_declare_property_bool, zend_declare_property_double, - zend_declare_property_long, zend_declare_property_null, zend_declare_property_stringl, - zend_long, ZEND_ACC_PRIVATE, ZEND_ACC_PROTECTED, ZEND_ACC_PUBLIC, ZEND_ACC_STATIC, -}; - -/// Builder for declare class property. -struct PropertyEntity { - name: String, - visibility: RawVisibility, - value: Scalar, -} - -impl PropertyEntity { - fn new(name: impl Into, visibility: Visibility, value: impl Into) -> Self { - Self { - name: name.into(), - visibility: visibility as RawVisibility, - value: value.into(), - } - } - - #[inline] - pub(crate) fn set_vis_static(&mut self) -> &mut Self { - self.visibility |= ZEND_ACC_STATIC; - self - } - - pub(crate) fn declare(&self, ce: *mut zend_class_entry) { - let name = self.name.as_ptr().cast(); - let name_length = self.name.len(); - let access_type = self.visibility as i32; - - unsafe { - match &self.value { - Scalar::Null => { - zend_declare_property_null(ce, name, name_length, access_type); - } - Scalar::Bool(b) => { - zend_declare_property_bool(ce, name, name_length, *b as zend_long, access_type); - } - Scalar::I64(i) => { - zend_declare_property_long(ce, name, name_length, *i, access_type); - } - Scalar::F64(f) => { - zend_declare_property_double(ce, name, name_length, *f, access_type); - } - Scalar::String(s) => { - // If the `ce` is `ZEND_INTERNAL_CLASS`, then the `zend_string` is allocated - // as persistent. - zend_declare_property_stringl( - ce, - name, - name_length, - s.as_ptr().cast(), - s.len(), - access_type, - ); - } - Scalar::Bytes(b) => { - zend_declare_property_stringl( - ce, - name, - name_length, - b.as_ptr().cast(), - b.len(), - access_type, - ); - } - } - } - } -} - -/// Visibility of class properties and methods. -#[repr(u32)] -#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] -pub enum Visibility { - /// Public. - #[default] - Public = ZEND_ACC_PUBLIC, - - /// Protected. - Protected = ZEND_ACC_PROTECTED, - - /// Private. - Private = ZEND_ACC_PRIVATE, -} diff --git a/phper/src/constants.rs b/phper/src/constants.rs index c15eecc6..1939e852 100644 --- a/phper/src/constants.rs +++ b/phper/src/constants.rs @@ -20,7 +20,6 @@ pub struct Constant { flags: i32, } -// #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[repr(transparent)] pub struct Flags(u32); @@ -45,7 +44,7 @@ impl Constant { let name = name.as_ref(); let length = name.len(); let ptr = name.as_bytes().as_ptr() as *const i8; - let flags = flags.unwrap_or(Flags::Cs | Flags::Persistent).bits() as i32; + let flags = flags.unwrap_or_default().bits() as i32; Self { constant: unsafe { phper_create_constant(ptr, length, value.into().inner, flags) }, flags, diff --git a/phper/src/functions/invoke.rs b/phper/src/functions/invoke.rs new file mode 100644 index 00000000..42f0f872 --- /dev/null +++ b/phper/src/functions/invoke.rs @@ -0,0 +1,42 @@ +use crate::errors::{throw, ArgumentCountError}; +use crate::values::{ExecuteData, ZVal}; +use phper_sys::{zend_execute_data, zval}; + +/// The entry for all registered PHP functions. +pub(super) unsafe extern "C" fn call_function_handler( + execute_data: *mut zend_execute_data, + return_value: *mut zval, +) { + let execute_data = ExecuteData::from_mut_ptr(execute_data); + let return_value = ZVal::from_mut_ptr(return_value); + + // Check arguments count. + let num_args = execute_data.num_args(); + let required_num_args = execute_data.common_required_num_args(); + if num_args < required_num_args { + let func_name = execute_data.func().get_function_or_method_name(); + let err: crate::Error = match func_name.to_str() { + Ok(func_name) => { + ArgumentCountError::new(func_name.to_owned(), required_num_args, num_args).into() + } + Err(e) => e.into(), + }; + throw(err); + *return_value = ().into(); + return; + } + + let handler = execute_data.get_handler(); + if let Some(mut params) = execute_data.get_parameters_array() { + handler.call(execute_data, params.as_mut_slice(), return_value); + } else { + let func_name = execute_data.func().get_function_or_method_name(); + let err: crate::Error = match func_name.to_str() { + Ok(func_name) => { + ArgumentCountError::new(func_name.to_owned(), required_num_args, num_args).into() + } + Err(e) => e.into(), + }; + throw(err); + } +} diff --git a/phper/src/functions.rs b/phper/src/functions/mod.rs similarity index 70% rename from phper/src/functions.rs rename to phper/src/functions/mod.rs index 12227afd..4d440abb 100644 --- a/phper/src/functions.rs +++ b/phper/src/functions/mod.rs @@ -12,25 +12,24 @@ //! //! TODO Add lambda. +mod invoke; + use crate::classes::MethodEntity; use crate::{ - classes::{entry::ClassEntry, RawVisibility, Visibility}, - errors::{throw, ArgumentCountError, ExceptionGuard, ThrowObject, Throwable}, + classes::{entry::ClassEntry, RawVisibility}, + errors::{throw, ExceptionGuard, ThrowObject, Throwable}, objects::{StateObj, ZObj, ZObject}, strings::{ZStr, ZString}, sys::*, utils::ensure_end_with_zero, values::{ExecuteData, ZVal}, }; + use phper_alloc::ToRefOwned; -use std::mem::zeroed; -use std::{ - ffi::{CStr, CString}, - marker::PhantomData, - mem::transmute, - ptr::{self, null_mut}, - rc::Rc, -}; + +use std::ffi::{c_char, CStr}; +use std::mem::ManuallyDrop; +use std::{ffi::CString, marker::PhantomData, mem::zeroed, ptr::null_mut}; pub(crate) trait Callable { fn call(&self, execute_data: &mut ExecuteData, arguments: &mut [ZVal], return_value: &mut ZVal); @@ -101,7 +100,7 @@ where } /// Wrapper of [`zend_function_entry`]. -#[repr(C)] +#[repr(transparent)] pub struct FunctionEntry(pub(crate) zend_function_entry); impl FunctionEntry { @@ -110,55 +109,58 @@ impl FunctionEntry { } pub(crate) unsafe fn from_function_entity(entity: FunctionEntity) -> FunctionEntry { - Self::entry( - &entity.name, - entity.arguments, - Some(entity.handler.clone()), - None, - ) + Self::entry(entity.name, entity.arguments, entity.handler, None) } pub(crate) unsafe fn from_method_entity(entity: MethodEntity) -> FunctionEntry { Self::entry( - &entity.name, + entity.name, entity.arguments, - entity.handler.clone(), + entity.handler.expect("Handler must be set on Method"), Some(entity.visibility), ) } unsafe fn entry( - name: &CStr, + name: CString, arguments: &'static [zend_internal_arg_info], - handler: Option>, + handler: Box, visibility: Option, ) -> FunctionEntry { - let raw_handler = handler.as_ref().map(|_| invoke as _); - - if let Some(handler) = handler { - let translator = CallableTranslator { - callable: Rc::into_raw(handler), - }; - let last_arg_info: zend_internal_arg_info = translator.internal_arg_info; - // infos.push(last_arg_info); - } - - let flags = visibility.unwrap_or(Visibility::default() as u32); + let (args, count) = ExecuteData::write_handler(handler, arguments); FunctionEntry(zend_function_entry { - fname: name.as_ptr().cast(), - handler: raw_handler, - arg_info: null_mut(), - num_args: 0u32, - flags, + fname: name.into_raw(), + handler: Some(invoke::call_function_handler), + arg_info: args, + num_args: count, + flags: visibility.unwrap_or(ZEND_ACC_PUBLIC), }) } } +impl Drop for FunctionEntry { + fn drop(&mut self) { + let name = unsafe { CStr::from_ptr(self.0.fname) }.to_str().unwrap(); + println!("Called drop for FunctionEntry {}", name); + + unsafe { + // + // drop(Vec::from_raw_parts( + // self.0.arg_info.offset(-1) as *mut zend_internal_arg_info, + // self.0.num_args as usize, + // self.0.num_args as usize, + // )); + // + // drop(CString::from_raw(self.0.fname as *mut c_char)) + } + } +} + /// Builder for registering php function. pub struct FunctionEntity { name: CString, - handler: Rc, + handler: Box, arguments: &'static [zend_internal_arg_info], } @@ -166,7 +168,7 @@ impl FunctionEntity { #[inline] pub(crate) fn new( name: impl AsRef, - handler: Rc, + handler: Box, arguments: &'static [zend_internal_arg_info], ) -> Self { FunctionEntity { @@ -179,9 +181,7 @@ impl FunctionEntity { /// Wrapper of [`zend_function`]. #[repr(transparent)] -pub struct ZFunc { - inner: zend_function, -} +pub struct ZFunc(zend_function); impl ZFunc { /// Wraps a raw pointer. @@ -200,13 +200,13 @@ impl ZFunc { /// Returns a raw pointer wrapped. pub const fn as_ptr(&self) -> *const zend_function { - &self.inner + &self.0 } /// Returns a raw pointer wrapped. #[inline] pub fn as_mut_ptr(&mut self) -> *mut zend_function { - &mut self.inner + &mut self.0 } /// Get the function name if exists. @@ -228,16 +228,15 @@ impl ZFunc { /// Get the function related class if exists. pub fn get_class(&self) -> Option<&ClassEntry> { unsafe { - let ptr = self.inner.common.scope; + let ptr = self.0.common.scope; if ptr.is_null() { None } else { - Some(ClassEntry::from_ptr(self.inner.common.scope)) + Some(ClassEntry::from_ptr(self.0.common.scope)) } } } - #[allow(clippy::useless_conversion)] pub(crate) fn call( &mut self, mut object: Option<&mut ZObj>, @@ -270,50 +269,6 @@ impl ZFunc { } } -/// Just for type transmutation. -pub(crate) union CallableTranslator { - pub(crate) callable: *const dyn Callable, - pub(crate) internal_arg_info: zend_internal_arg_info, - pub(crate) arg_info: zend_arg_info, -} - -/// The entry for all registered PHP functions. -unsafe extern "C" fn invoke(execute_data: *mut zend_execute_data, return_value: *mut zval) { - let execute_data = ExecuteData::from_mut_ptr(execute_data); - let return_value = ZVal::from_mut_ptr(return_value); - - let num_args = execute_data.common_num_args(); - let arg_info = execute_data.common_arg_info(); - - let last_arg_info = arg_info.offset((num_args + 1) as isize); - let translator = CallableTranslator { - arg_info: *last_arg_info, - }; - let handler = translator.callable; - let handler = handler.as_ref().expect("handler is null"); - - // Check arguments count. - let num_args = execute_data.num_args(); - let required_num_args = execute_data.common_required_num_args(); - if num_args < required_num_args { - let func_name = execute_data.func().get_function_or_method_name(); - let err: crate::Error = match func_name.to_str() { - Ok(func_name) => { - ArgumentCountError::new(func_name.to_owned(), required_num_args, num_args).into() - } - Err(e) => e.into(), - }; - throw(err); - *return_value = ().into(); - return; - } - - let mut arguments = execute_data.get_parameters_array(); - let arguments = arguments.as_mut_slice(); - - handler.call(execute_data, transmute(arguments), return_value); -} - /// Call user function by name. /// /// # Examples @@ -374,12 +329,12 @@ pub(crate) fn call_raw_common(call_fn: impl FnOnce(&mut ZVal)) -> crate::Result< unsafe { if !eg!(exception).is_null() { - let e = ptr::replace(&mut eg!(exception), null_mut()); + let e = std::ptr::replace(&mut eg!(exception), null_mut()); let obj = ZObject::from_raw(e); - match ThrowObject::new(obj) { - Ok(e) => return Err(e.into()), - Err(e) => return Err(e.into()), - } + return match ThrowObject::new(obj) { + Ok(e) => Err(e.into()), + Err(e) => Err(e.into()), + }; } } diff --git a/phper/src/lib.rs b/phper/src/lib.rs index 6fa3d4db..e1709c0e 100644 --- a/phper/src/lib.rs +++ b/phper/src/lib.rs @@ -31,6 +31,7 @@ pub mod strings; pub mod types; mod utils; pub mod values; +mod zend_result; pub use crate::errors::{ok, Error, Result}; pub use phper_alloc as alloc; diff --git a/phper/src/macros.rs b/phper/src/macros.rs index bbddae57..6fee2190 100644 --- a/phper/src/macros.rs +++ b/phper/src/macros.rs @@ -27,7 +27,7 @@ macro_rules! echo { /// # Examples /// /// ```no_test -/// phper::errro!("Hello, {}!", message) +/// phper::error!("Hello, {}!", message) /// ``` #[macro_export] macro_rules! error { @@ -145,18 +145,3 @@ macro_rules! zend_args { }; }; } -#[macro_export] -macro_rules! zend_create_fn { - ($x:ident) => {{ - paste::paste! { - unsafe extern "C" fn []() -> *mut phper::sys::zend_class_entry - { - unsafe { $x() as *mut phper::sys::zend_class_entry } - } - } - - paste::paste! { - [] - } - }}; -} diff --git a/phper/src/modules/extern_c.rs b/phper/src/modules/extern_c.rs new file mode 100644 index 00000000..256140d4 --- /dev/null +++ b/phper/src/modules/extern_c.rs @@ -0,0 +1,107 @@ +use crate::functions::FunctionEntry; +use crate::ini; +use crate::modules::function_entry::FEntry; +use crate::modules::{ + get_module, Module, ModuleInfo, Registerer, GLOBAL_MODULE, GLOBAL_MODULE_ENTRY, + GLOBAL_MODULE_NUMBER, +}; +use crate::zend_result::ZResult; +use phper_macros::c_str_ptr; +use phper_sys::{ + display_ini_entries, php_info_print_table_end, php_info_print_table_row, + php_info_print_table_start, zend_function_entry, zend_module_entry, zend_register_functions, +}; +use std::mem::take; +use std::os::raw::c_int; + +pub(super) unsafe extern "C" fn module_info(zend_module: *mut zend_module_entry) { + let module = get_module(); + + php_info_print_table_start(); + if !module.version.as_bytes().is_empty() { + php_info_print_table_row(2, c_str_ptr!("version"), module.version.as_ptr()); + } + if !module.author.as_bytes().is_empty() { + php_info_print_table_row(2, c_str_ptr!("authors"), module.author.as_ptr()); + } + for (key, value) in &module.infos { + php_info_print_table_row(2, key.as_ptr(), value.as_ptr()); + } + php_info_print_table_end(); + + display_ini_entries(zend_module); +} + +pub(super) unsafe extern "C" fn module_startup(_type: c_int, module_number: c_int) -> c_int { + let module: &mut Module = get_module(); + GLOBAL_MODULE_NUMBER = module_number; + + ini::register(take(&mut module.ini_entities), module_number); + + for entity in take(&mut module.entities).into_iter() { + if let Err(err) = entity.register(module_number) { + crate::output::log( + crate::output::LogLevel::Error, + format!("Failed to register: {err:?}"), + ); + return ZResult::Failure.into(); + } + } + + if let Some(f) = take(&mut module.module_init) { + f(ModuleInfo { + ty: _type, + number: module_number, + }); + } + + ZResult::Success.into() +} + +pub(super) unsafe extern "C" fn module_shutdown(_type: c_int, module_number: c_int) -> c_int { + ini::unregister(module_number); + + // let module = get_module(); + // + // if let Some(f) = take(&mut module.module_shutdown) { + // f(ModuleInfo { + // ty: _type, + // number: module_number, + // }); + // } + // + // if let Some(ref mut f) = take(&mut module.request_init) { + // let _b = Box::from_raw(f); + // } + // + // if let Some(ref mut f) = take(&mut module.request_shutdown) { + // let _b = Box::from_raw(f); + // } + // + // drop(Box::from_raw(GLOBAL_MODULE)); + // GLOBAL_MODULE = null_mut(); + + ZResult::Success.into() +} + +pub(super) unsafe extern "C" fn request_startup(_type: c_int, module_number: c_int) -> c_int { + let f = get_module().request_init.unwrap_unchecked(); + + f(ModuleInfo { + ty: _type, + number: module_number, + }); + + ZResult::Success.into() +} + +pub(super) unsafe extern "C" fn request_shutdown(_type: c_int, module_number: c_int) -> c_int { + let f = get_module().request_shutdown.unwrap_unchecked(); + + f(ModuleInfo { + ty: _type, + number: module_number, + }); + + ZResult::Success.into() +} diff --git a/phper/src/modules/function_entry.rs b/phper/src/modules/function_entry.rs new file mode 100644 index 00000000..fa521388 --- /dev/null +++ b/phper/src/modules/function_entry.rs @@ -0,0 +1,37 @@ +use crate::functions::FunctionEntry; +use phper_sys::zend_function_entry; +use std::mem::ManuallyDrop; +use std::ops::{Deref, DerefMut}; + +pub(super) struct FEntry(pub(super) Vec); + +unsafe impl Send for FEntry {} +unsafe impl Sync for FEntry {} + +impl FEntry { + pub(super) const fn new_const() -> Self { + Self(Vec::new()) + } + + pub(super) unsafe fn from_zend_functions(fns: *mut zend_function_entry, size: usize) -> Self { + FEntry(Vec::from_raw_parts(fns, size, size)) + } +} + +impl Deref for FEntry { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for FEntry { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// Safety: This is used as a global variable, initialization is always +/// guaranteed by PHP to be from one thread in ZTS, and on NTS its always one thread +pub(super) static mut PHP_FUNCTIONS: ManuallyDrop = ManuallyDrop::new(FEntry::new_const()); diff --git a/phper/src/modules.rs b/phper/src/modules/mod.rs similarity index 67% rename from phper/src/modules.rs rename to phper/src/modules/mod.rs index bfe1076e..c5c1e3ce 100644 --- a/phper/src/modules.rs +++ b/phper/src/modules/mod.rs @@ -10,26 +10,32 @@ //! Apis relate to [zend_module_entry]. -use crate::constants; +mod extern_c; +mod function_entry; + +use smallvec::SmallVec; +use std::mem::ManuallyDrop; +use std::{ + collections::HashMap, + ffi::CString, + mem::{size_of, zeroed}, + os::raw::{c_uchar, c_uint, c_ushort}, + ptr::{null, null_mut}, +}; + use crate::{ - c_str_ptr, classes::{entity::ClassEntity, InterfaceEntity}, - constants::Constant, + constants::{Constant, Flags}, errors::Throwable, functions::{Function, FunctionEntity, FunctionEntry}, ini, sys::*, values::ZVal, }; -use smallvec::SmallVec; -use std::{ - collections::HashMap, - ffi::CString, - mem::{size_of, take, zeroed}, - os::raw::{c_int, c_uchar, c_uint, c_ushort}, - ptr::{null, null_mut}, - rc::Rc, -}; + +use crate::functions::Callable; +use crate::modules::function_entry::PHP_FUNCTIONS; +use extern_c::{module_info, module_shutdown, module_startup, request_shutdown, request_startup}; /// Global pointer hold the Module builder. /// Because PHP is single threaded, so there is no lock here. @@ -42,15 +48,6 @@ unsafe fn get_module() -> &'static mut Module { unsafe { GLOBAL_MODULE.as_mut().unwrap_unchecked() } } -/// Safety: This is used as a global variable, initialization is always -/// guaranteed by PHP to be from one thread in ZTS, and on NTS its always one thread -struct FEntry(SmallVec<[FunctionEntry; 64]>); - -unsafe impl Send for FEntry {} -unsafe impl Sync for FEntry {} - -static mut PHP_FUNCTIONS: FEntry = FEntry(SmallVec::new_const()); - /// PHP Module information pub struct ModuleInfo { /// Module Type @@ -64,99 +61,7 @@ pub(crate) trait Registerer { fn register(self, module_number: i32) -> Result<(), Box>; } -unsafe extern "C" fn module_startup(_type: c_int, module_number: c_int) -> c_int { - let module: &mut Module = get_module(); - GLOBAL_MODULE_NUMBER = module_number; - - ini::register(take(&mut module.ini_entities), module_number); - - for entity in take(&mut module.entities).into_iter() { - if let Err(err) = entity.register(module_number) { - crate::output::log( - crate::output::LogLevel::Error, - format!("Failed to register: {err:?}"), - ); - return ZEND_RESULT_CODE_FAILURE; - } - } - - if let Some(f) = take(&mut module.module_init) { - f(ModuleInfo { - ty: _type, - number: module_number, - }); - } - - ZEND_RESULT_CODE_SUCCESS -} - -unsafe extern "C" fn module_shutdown(_type: c_int, module_number: c_int) -> c_int { - { - let module = get_module(); - - ini::unregister(module_number); - - if let Some(f) = take(&mut module.module_shutdown) { - f(ModuleInfo { - ty: _type, - number: module_number, - }); - } - - if let Some(ref mut f) = take(&mut module.request_init) { - let _b = Box::from_raw(f); - } - - if let Some(ref mut f) = take(&mut module.request_shutdown) { - let _b = Box::from_raw(f); - } - } - - ZEND_RESULT_CODE_SUCCESS -} - -unsafe extern "C" fn request_startup(_type: c_int, module_number: c_int) -> c_int { - let f = get_module().request_init.unwrap_unchecked(); - - f(ModuleInfo { - ty: _type, - number: module_number, - }); - - ZEND_RESULT_CODE_SUCCESS -} - -unsafe extern "C" fn request_shutdown(_type: c_int, module_number: c_int) -> c_int { - let f = get_module().request_shutdown.unwrap_unchecked(); - - f(ModuleInfo { - ty: _type, - number: module_number, - }); - - ZEND_RESULT_CODE_SUCCESS -} - -unsafe extern "C" fn module_info(zend_module: *mut zend_module_entry) { - let module = get_module(); - - php_info_print_table_start(); - if !module.version.as_bytes().is_empty() { - php_info_print_table_row(2, c_str_ptr!("version"), module.version.as_ptr()); - } - if !module.author.as_bytes().is_empty() { - php_info_print_table_row(2, c_str_ptr!("authors"), module.author.as_ptr()); - } - for (key, value) in &module.infos { - php_info_print_table_row(2, key.as_ptr(), value.as_ptr()); - } - php_info_print_table_end(); - - display_ini_entries(zend_module); -} - /// Builder for registering PHP Module. -#[allow(clippy::type_complexity)] #[derive(Default)] pub struct Module { name: CString, @@ -169,6 +74,7 @@ pub struct Module { entities: Vec, ini_entities: Vec, infos: HashMap, + functions: SmallVec<[zend_function_entry; 64]>, } pub(crate) enum Entities { @@ -232,12 +138,12 @@ impl Module { Z: Into + 'static, E: Throwable + 'static, { - let entry = FunctionEntity::new(name, Rc::new(Function::new(handler)), arguments); + let handler = Box::new(Function::new(handler)); + let entry = FunctionEntity::new(name, handler, arguments); unsafe { - PHP_FUNCTIONS - .0 - .push(FunctionEntry::from_function_entity(entry)); + let val = ManuallyDrop::new(FunctionEntry::from_function_entity(entry)); + self.functions.push(val.0); } self @@ -264,7 +170,7 @@ impl Module { &mut self, name: impl AsRef, value: impl Into, - flags: Option, + flags: Option, ) -> &mut Self { self.entities .push(Entities::Constant(Constant::new(name, value, flags))); @@ -304,11 +210,15 @@ impl Module { } #[doc(hidden)] - pub unsafe fn module_entry(self) -> *const zend_module_entry { + pub unsafe fn module_entry(mut self) -> *const zend_module_entry { if !GLOBAL_MODULE_ENTRY.is_null() { return GLOBAL_MODULE_ENTRY; } + self.functions.push(zeroed::()); + self.functions.shrink_to_fit(); + let fns = ManuallyDrop::new(std::mem::take(&mut self.functions).into_boxed_slice()); + let module = Box::leak(Box::new(self)); let entry = Box::new(zend_module_entry { @@ -319,7 +229,7 @@ impl Module { ini_entry: null(), deps: null(), name: module.name.as_ptr(), - functions: module.function_entries(), + functions: fns.as_ptr(), module_startup_func: Some(module_startup), module_shutdown_func: Some(module_shutdown), request_startup_func: if module.request_init.is_some() { @@ -354,14 +264,4 @@ impl Module { GLOBAL_MODULE_ENTRY } - - unsafe fn function_entries(&self) -> *const zend_function_entry { - if PHP_FUNCTIONS.0.is_empty() { - return null(); - } - - PHP_FUNCTIONS.0.push(zeroed::()); - - PHP_FUNCTIONS.0.as_ptr() as *const zend_function_entry - } } diff --git a/phper/src/objects.rs b/phper/src/objects.rs index 84d5506e..d762c7d7 100644 --- a/phper/src/objects.rs +++ b/phper/src/objects.rs @@ -480,7 +480,7 @@ impl StateObject { /// therefore, you can only obtain state ownership when the refcount of the /// [zend_object] is `1`, otherwise, it will return /// `None`. - pub fn into_state(mut self) -> Option { + pub fn into_state(self) -> Option { // if self.gc_refcount() == 1 { // let val = unsafe { std::mem::zeroed::() }; // Some(replace(&mut self.any_state, val)) diff --git a/phper/src/values.rs b/phper/src/values.rs index b86b39df..0e74507a 100644 --- a/phper/src/values.rs +++ b/phper/src/values.rs @@ -1,4 +1,5 @@ // Copyright (c) 2022 PHPER Framework Team +#![allow(clippy::ptr_offset_with_cast)] // PHPER is licensed under Mulan PSL v2. // You can use this software according to the terms and conditions of the Mulan // PSL v2. You may obtain a copy of Mulan PSL v2 at: @@ -10,6 +11,7 @@ //! Apis relate to [zval]. +use crate::functions::Callable; use crate::{ alloc::EBox, arrays::{ZArr, ZArray}, @@ -24,20 +26,18 @@ use crate::{ }; use phper_alloc::RefClone; use std::{ - convert::TryInto, - ffi::CStr, - fmt, - fmt::Debug, - marker::PhantomData, - mem::{transmute, zeroed, ManuallyDrop, MaybeUninit}, - str, + convert::TryInto, ffi::CStr, fmt, fmt::Debug, marker::PhantomData, mem::MaybeUninit, str, }; +#[repr(C)] +union ToZendInternalArg { + pub(crate) callable: *const dyn Callable, + pub(crate) internal_arg_info: zend_internal_arg_info, +} + /// Wrapper of [zend_execute_data]. #[repr(transparent)] -pub struct ExecuteData { - inner: zend_execute_data, -} +pub struct ExecuteData(zend_execute_data); impl ExecuteData { /// Wraps a raw pointer. @@ -93,49 +93,94 @@ impl ExecuteData { /// Returns a raw pointer wrapped. pub const fn as_ptr(&self) -> *const zend_execute_data { - &self.inner + &self.0 } /// Returns a raw pointer wrapped. #[inline] #[allow(dead_code)] pub fn as_mut_ptr(&mut self) -> *mut zend_execute_data { - &mut self.inner + &mut self.0 } /// Gets common arguments count. #[inline] pub fn common_num_args(&self) -> u32 { - unsafe { (*self.inner.func).common.num_args } + unsafe { (*self.0.func).common.num_args } } /// Gets common required arguments count. #[inline] pub fn common_required_num_args(&self) -> usize { - unsafe { (*self.inner.func).common.required_num_args as usize } + unsafe { (*self.0.func).common.required_num_args as usize } } /// Gets first common argument info. #[inline] pub fn common_arg_info(&self) -> *mut zend_arg_info { - unsafe { (*self.inner.func).common.arg_info } + unsafe { (*self.0.func).common.arg_info } + } + + #[inline] + pub(crate) unsafe fn get_handler(&self) -> &'static dyn Callable { + let len = self.common_num_args() as usize; + let arg_info = (*self.0.func) + .internal_function + .arg_info + .offset((len - 1) as isize) + .cast_const(); + + let items = std::slice::from_raw_parts(arg_info, 1); + + let val = ToZendInternalArg { + internal_arg_info: items[0], + }; + + val.callable.as_ref().unwrap_unchecked() + } + + pub(crate) unsafe fn write_handler( + handler: Box, + arguments: &'static [zend_internal_arg_info], + ) -> (*const zend_internal_arg_info, u32) { + let callable = Box::into_raw(handler); + + let val = ToZendInternalArg { callable }; + + let mut data = Vec::with_capacity(arguments.len()); + data.extend_from_slice(arguments); + data.push(val.internal_arg_info); + data.shrink_to_fit(); + + let val = Box::into_raw(data.into_boxed_slice()); + + (val as *const zend_internal_arg_info, arguments.len() as u32) + // let handler_ptr = Box::into_raw(handler) as *const u8; + // + // + // let arg_info_ptr: *mut u8 = std::mem::transmute(&mut handler_info); + // + // std::ptr::copy_nonoverlapping(handler_ptr, arg_info_ptr, CALLABLE_SIZE); + // + // data.push(handler_info); } /// Gets arguments count. #[inline] pub fn num_args(&self) -> usize { - unsafe { phper_zend_num_args(self.as_ptr()).try_into().unwrap() } + unsafe { self.0.This.u2.num_args as usize } } /// Gets associated function. pub fn func(&self) -> &ZFunc { - unsafe { ZFunc::from_mut_ptr(self.inner.func) } + unsafe { ZFunc::from_mut_ptr(self.0.func) } } /// Gets associated `$this` object if exists. + #[inline] pub fn get_this(&self) -> Option<&ZObj> { unsafe { - let val = ZVal::from_ptr(phper_get_this(&self.inner)); + let val = ZVal::from_ptr(&self.0.This); val.as_z_obj() } } @@ -143,21 +188,23 @@ impl ExecuteData { /// Gets associated mutable `$this` object if exists. pub fn get_this_mut(&mut self) -> Option<&mut ZObj> { unsafe { - let val = ZVal::from_mut_ptr(phper_get_this_mut(&mut self.inner)); + let val = ZVal::from_mut_ptr(&mut self.0.This); val.as_mut_z_obj() } } - pub(crate) unsafe fn get_parameters_array(&mut self) -> Vec> { - let num_args = self.num_args(); - let mut arguments = vec![zeroed::(); num_args]; - if num_args > 0 { - phper_zend_get_parameters_array_ex( - num_args.try_into().unwrap(), - arguments.as_mut_ptr(), - ); + #[inline] + pub(crate) unsafe fn get_parameters_array(&mut self) -> Option { + let args_count = self.num_args(); + let mut arr = ZVal::from(ZArray::with_capacity(args_count)); + + if zend_copy_parameters_array(args_count as u32, arr.as_mut_ptr()) + == ZEND_RESULT_CODE_FAILURE + { + None + } else { + Some(ZArray::from_zval(arr)) } - transmute(arguments) } /// Gets parameter by index. @@ -556,8 +603,6 @@ impl ZVal { } /// Internally convert to long. - /// - /// TODO To fix assertion failed. pub fn convert_to_long(&mut self) { unsafe { phper_convert_to_long(self.as_mut_ptr()); @@ -565,8 +610,6 @@ impl ZVal { } /// Internally convert to string. - /// - /// TODO To fix assertion failed. pub fn convert_to_string(&mut self) { unsafe { phper_convert_to_string(self.as_mut_ptr()); diff --git a/phper/src/zend_result.rs b/phper/src/zend_result.rs new file mode 100644 index 00000000..0e47e2fa --- /dev/null +++ b/phper/src/zend_result.rs @@ -0,0 +1,14 @@ +use phper_sys::{ZEND_RESULT_CODE, ZEND_RESULT_CODE_FAILURE, ZEND_RESULT_CODE_SUCCESS}; +use std::ffi::c_int; + +#[repr(i32)] +pub enum ZResult { + Success = ZEND_RESULT_CODE_SUCCESS, + Failure = ZEND_RESULT_CODE_FAILURE, +} + +impl Into for ZResult { + fn into(self) -> ZEND_RESULT_CODE { + self as c_int + } +}