diff --git a/examples/complex/src/lib.rs b/examples/complex/src/lib.rs index 7bc1cc9..4388a4b 100644 --- a/examples/complex/src/lib.rs +++ b/examples/complex/src/lib.rs @@ -10,7 +10,7 @@ use phper::{ arrays::ZArray, - classes::{ClassEntity, Visibility}, + classes::{entity::ClassEntity, Visibility}, functions::Argument, ini::{ini_get, Policy}, modules::Module, diff --git a/examples/http-client/src/client.rs b/examples/http-client/src/client.rs index 8e8c437..1edaff1 100644 --- a/examples/http-client/src/client.rs +++ b/examples/http-client/src/client.rs @@ -11,7 +11,7 @@ use crate::{errors::HttpClientError, request::REQUEST_BUILDER_CLASS}; use phper::{ alloc::ToRefOwned, - classes::{ClassEntity, StaticStateClass, Visibility}, + classes::{entity::ClassEntity, StaticStateClass, Visibility}, functions::Argument, }; use reqwest::blocking::{Client, ClientBuilder}; diff --git a/examples/http-client/src/errors.rs b/examples/http-client/src/errors.rs index 4e085aa..e389f8f 100644 --- a/examples/http-client/src/errors.rs +++ b/examples/http-client/src/errors.rs @@ -9,7 +9,7 @@ // See the Mulan PSL v2 for more details. use phper::{ - classes::{ClassEntity, ClassEntry}, + classes::{entity::ClassEntity, entry::ClassEntry}, errors::{exception_class, Throwable}, }; diff --git a/examples/http-client/src/request.rs b/examples/http-client/src/request.rs index d5b4a23..d07b07b 100644 --- a/examples/http-client/src/request.rs +++ b/examples/http-client/src/request.rs @@ -9,7 +9,7 @@ // See the Mulan PSL v2 for more details. use crate::{errors::HttpClientError, response::RESPONSE_CLASS}; -use phper::classes::{ClassEntity, StaticStateClass, Visibility}; +use phper::classes::{entity::ClassEntity, StaticStateClass, Visibility}; use reqwest::blocking::RequestBuilder; use std::{convert::Infallible, mem::take}; diff --git a/examples/http-client/src/response.rs b/examples/http-client/src/response.rs index e10b1b6..6f57c31 100644 --- a/examples/http-client/src/response.rs +++ b/examples/http-client/src/response.rs @@ -11,7 +11,7 @@ use crate::errors::HttpClientError; use phper::{ arrays::{InsertKey, ZArray}, - classes::{ClassEntity, StaticStateClass, Visibility}, + classes::{entity::ClassEntity, StaticStateClass, Visibility}, values::ZVal, }; use reqwest::blocking::Response; diff --git a/examples/http-server/src/errors.rs b/examples/http-server/src/errors.rs index ce7c4c9..c5fb151 100644 --- a/examples/http-server/src/errors.rs +++ b/examples/http-server/src/errors.rs @@ -9,7 +9,7 @@ // See the Mulan PSL v2 for more details. use phper::{ - classes::{ClassEntity, ClassEntry}, + classes::{entity::ClassEntity, entry::ClassEntry}, errors::{exception_class, Throwable}, }; use std::error::Error; diff --git a/examples/http-server/src/request.rs b/examples/http-server/src/request.rs index d9d8816..e4c22f8 100644 --- a/examples/http-server/src/request.rs +++ b/examples/http-server/src/request.rs @@ -10,7 +10,7 @@ use phper::{ arrays::ZArray, - classes::{ClassEntity, StaticStateClass, Visibility}, + classes::{entity::ClassEntity, StaticStateClass, Visibility}, objects::StateObject, }; use std::convert::Infallible; diff --git a/examples/http-server/src/response.rs b/examples/http-server/src/response.rs index b5eabfe..ed9b5f3 100644 --- a/examples/http-server/src/response.rs +++ b/examples/http-server/src/response.rs @@ -14,7 +14,7 @@ use axum::{ http::{HeaderName, HeaderValue, Response}, }; use phper::{ - classes::{ClassEntity, StaticStateClass, Visibility}, + classes::{entity::ClassEntity, StaticStateClass, Visibility}, functions::Argument, objects::StateObject, }; diff --git a/examples/http-server/src/server.rs b/examples/http-server/src/server.rs index 59f4366..8d44630 100644 --- a/examples/http-server/src/server.rs +++ b/examples/http-server/src/server.rs @@ -18,7 +18,7 @@ use axum::{ use hyper::body; use phper::{ alloc::ToRefOwned, - classes::{ClassEntity, Visibility}, + classes::{entity::ClassEntity, Visibility}, functions::Argument, values::ZVal, }; diff --git a/phper-sys/build.rs b/phper-sys/build.rs index 47f7bbd..5dbd8a4 100644 --- a/phper-sys/build.rs +++ b/phper-sys/build.rs @@ -13,6 +13,7 @@ use std::{env, ffi::OsStr, fmt::Debug, path::PathBuf, process::Command}; fn main() { println!("cargo:rerun-if-env-changed=PHP_CONFIG"); + println!("cargo:rerun-if-changed=build.rs"); let current_dir = std::env::current_dir().unwrap(); let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); @@ -41,9 +42,9 @@ fn main() { builder .cpp(false) - // .debug(false) + .debug(false) .files(&c_files) - // .extra_warnings(true) + .extra_warnings(true) .include("include") // .flag("-falign-functions") // .flag("-flto=auto") diff --git a/phper/src/arrays.rs b/phper/src/arrays.rs index 17b8f1e..d167f29 100644 --- a/phper/src/arrays.rs +++ b/phper/src/arrays.rs @@ -225,9 +225,7 @@ impl ZArr { let ptr = self.as_ptr() as *mut _; unsafe { match key { - Key::Index(i) => { - phper_zend_hash_index_exists(ptr, i) - }, + 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()) } diff --git a/phper/src/classes.rs b/phper/src/classes.rs deleted file mode 100644 index 15168ae..0000000 --- a/phper/src/classes.rs +++ /dev/null @@ -1,949 +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_class_entry]. - -use crate::{ - arrays::ZArr, - errors::{ClassNotFoundError, InitializeObjectError, Throwable}, - functions::{Function, FunctionEntry, Method, MethodEntity}, - objects::{StateObj, StateObject, ZObject}, - strings::ZStr, - sys::*, - types::Scalar, - utils::ensure_end_with_zero, - values::ZVal, -}; -use std::{ - any::Any, - borrow::ToOwned, - convert::TryInto, - ffi::{c_void, CString}, - fmt::Debug, - marker::PhantomData, - mem::{replace, size_of, zeroed, ManuallyDrop}, - os::raw::c_int, - ptr::null_mut, - rc::Rc, - slice, - sync::atomic::{AtomicPtr, Ordering}, -}; - -/// Predefined interface `Iterator`. -#[inline] -pub fn iterator_class<'a>() -> &'a ClassEntry { - unsafe { ClassEntry::from_ptr(zend_ce_iterator) } -} - -/// Predefined interface `ArrayAccess`. -#[inline] -pub fn array_access_class<'a>() -> &'a ClassEntry { - unsafe { ClassEntry::from_ptr(zend_ce_arrayaccess) } -} - -/// Wrapper of [zend_class_entry]. -#[repr(transparent)] -pub struct ClassEntry { - inner: zend_class_entry, - _p: PhantomData<*mut ()>, -} - -impl ClassEntry { - /// 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_class_entry) -> &'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_class_entry) -> 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_class_entry) -> &'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_class_entry) -> Option<&'a mut Self> { - (ptr as *mut Self).as_mut() - } - - /// Returns a raw pointer wrapped. - pub const fn as_ptr(&self) -> *const zend_class_entry { - &self.inner - } - - /// Returns a raw pointer wrapped. - #[inline] - pub fn as_mut_ptr(&mut self) -> *mut zend_class_entry { - &mut self.inner - } - - /// Create reference from global class name. - /// - /// # Examples - /// - /// ```no_run - /// use phper::classes::ClassEntry; - /// - /// let std_class = ClassEntry::from_globals("stdClass").unwrap(); - /// let _obj = std_class.new_object([]).unwrap(); - /// ``` - pub fn from_globals(class_name: impl AsRef) -> crate::Result<&'static Self> { - let name = class_name.as_ref(); - let ptr: *mut Self = find_global_class_entry_ptr(name).cast(); - unsafe { - ptr.as_ref().ok_or_else(|| { - crate::Error::ClassNotFound(ClassNotFoundError::new(name.to_string())) - }) - } - } - - /// Create the object from class and call `__construct` with arguments. - /// - /// If the `__construct` is private, or protected and the called scope isn't - /// parent class, it will throw PHP Error. - pub fn new_object(&self, arguments: impl AsMut<[ZVal]>) -> crate::Result { - let mut object = self.init_object()?; - object.call_construct(arguments)?; - Ok(object) - } - - /// Create the object from class, without calling `__construct`. - /// - /// **Be careful when `__construct` is necessary.** - pub fn init_object(&self) -> crate::Result { - unsafe { - let ptr = self.as_ptr() as *mut _; - let mut val = ZVal::default(); - if !phper_object_init_ex(val.as_mut_ptr(), ptr) { - Err(InitializeObjectError::new(self.get_name().to_str()?.to_owned()).into()) - } else { - // Can't drop val here! Otherwise the object will be dropped too (wasting me a - // day of debugging time here). - let mut val = ManuallyDrop::new(val); - let ptr = phper_z_obj_p(val.as_mut_ptr()); - Ok(ZObject::from_raw(ptr.cast_mut())) - } - } - } - - /// Get the class name. - pub fn get_name(&self) -> &ZStr { - unsafe { ZStr::from_ptr(self.inner.name) } - } - - /// Detect if the method is exists in class. - pub fn has_method(&self, method_name: &str) -> bool { - unsafe { - let function_table = ZArr::from_ptr(&self.inner.function_table); - function_table.exists(method_name) - } - } - - /// Detect if the class is instance of parent class. - pub fn is_instance_of(&self, parent: &ClassEntry) -> bool { - unsafe { phper_instanceof_function(self.as_ptr(), parent.as_ptr()) } - } - - /// Get the static property by name of class. - /// - /// Return None when static property hasn't register by - /// [ClassEntity::add_static_property]. - pub fn get_static_property(&self, name: impl AsRef) -> Option<&ZVal> { - let ptr = self.as_ptr() as *mut _; - let prop = Self::inner_get_static_property(ptr, name); - unsafe { ZVal::try_from_ptr(prop) } - } - - /// Set the static property by name of class. - /// - /// Return `Some(x)` where `x` is the previous value of static property, or - /// return `None` when static property hasn't register by - /// [ClassEntity::add_static_property]. - pub fn set_static_property(&self, name: impl AsRef, val: impl Into) -> Option { - let ptr = self.as_ptr() as *mut _; - let prop = Self::inner_get_static_property(ptr, name); - let prop = unsafe { ZVal::try_from_mut_ptr(prop) }; - prop.map(|prop| replace(prop, val.into())) - } - - fn inner_get_static_property(scope: *mut zend_class_entry, name: impl AsRef) -> *mut zval { - let name = name.as_ref(); - - unsafe { - #[allow(clippy::useless_conversion)] - zend_read_static_property(scope, name.as_ptr().cast(), name.len(), true.into()) - } - } -} - -impl Debug for ClassEntry { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_tuple("ClassEntry") - .field(&self.get_name().to_c_str()) - .finish() - } -} - -#[allow(clippy::useless_conversion)] -fn find_global_class_entry_ptr(name: impl AsRef) -> *mut zend_class_entry { - let name = name.as_ref(); - let name = name.to_lowercase(); - unsafe { - phper_zend_hash_str_find_ptr( - compiler_globals.class_table, - name.as_ptr().cast(), - name.len().try_into().unwrap(), - ) - .cast() - } -} - -/// The [StaticStateClass] holds -/// [zend_class_entry] and inner state, always as -/// the static variable, and then be bind to [ClassEntity]. -/// -/// When the class registered (module initialized), the [StaticStateClass] will -/// be initialized, so you can use the [StaticStateClass] to new stateful -/// object, etc. -/// -/// So, You shouldn't use [StaticStateClass] in `module_init` stage, because it -/// hasn't initialized. -/// -/// # Examples -/// -/// ```rust -/// use phper::classes::{ClassEntity, StaticStateClass}; -/// -/// pub static FOO_CLASS: StaticStateClass = StaticStateClass::null(); -/// -/// #[derive(Default)] -/// pub struct FooState; -/// -/// fn make_foo_class() -> ClassEntity { -/// let mut class = ClassEntity::new_with_default_state_constructor("Foo"); -/// class.bind(&FOO_CLASS); -/// class -/// } -/// ``` -#[repr(transparent)] -pub struct StaticStateClass { - inner: AtomicPtr, - _p: PhantomData, -} - -impl StaticStateClass { - /// Create empty [StaticStateClass], with null - /// [zend_class_entry]. - pub const fn null() -> Self { - Self { - inner: AtomicPtr::new(null_mut()), - _p: PhantomData, - } - } - - fn bind(&'static self, ptr: *mut zend_class_entry) { - self.inner.store(ptr, Ordering::Relaxed); - } - - /// Converts to class entry. - pub fn as_class_entry(&'static self) -> &'static ClassEntry { - unsafe { ClassEntry::from_mut_ptr(self.inner.load(Ordering::Relaxed)) } - } - - /// Create the object from class and call `__construct` with arguments. - /// - /// If the `__construct` is private, or protected and the called scope isn't - /// parent class, it will throw PHP Error. - pub fn new_object( - &'static self, arguments: impl AsMut<[ZVal]>, - ) -> crate::Result> { - self.as_class_entry() - .new_object(arguments) - .map(ZObject::into_raw) - .map(StateObject::::from_raw_object) - } - - /// Create the object from class, without calling `__construct`. - /// - /// **Be careful when `__construct` is necessary.** - pub fn init_object(&'static self) -> crate::Result> { - self.as_class_entry() - .init_object() - .map(ZObject::into_raw) - .map(StateObject::::from_raw_object) - } -} - -unsafe impl Sync for StaticStateClass {} - -/// The [StaticInterface] holds -/// [zend_class_entry], always as the static -/// variable, and then be bind to [InterfaceEntity]. -/// -/// When the interface registered (module initialized), the [StaticInterface] -/// will be initialized. -/// -/// So, You shouldn't use [StaticInterface] in `module_init` stage, because it -/// hasn't initialized. -/// -/// # Examples -/// -/// ```rust -/// use phper::classes::{InterfaceEntity, StaticInterface}; -/// -/// pub static FOO_INTERFACE: StaticInterface = StaticInterface::null(); -/// -/// fn make_foo_interface() -> InterfaceEntity { -/// let mut interface = InterfaceEntity::new("Foo"); -/// interface.bind(&FOO_INTERFACE); -/// interface -/// } -/// ``` -#[repr(transparent)] -pub struct StaticInterface { - inner: AtomicPtr, -} - -impl StaticInterface { - /// Create empty [StaticInterface], with null - /// [zend_class_entry]. - pub const fn null() -> Self { - Self { - inner: AtomicPtr::new(null_mut()), - } - } - - fn bind(&'static self, ptr: *mut zend_class_entry) { - self.inner.store(ptr, Ordering::Relaxed); - } - - /// Converts to class entry. - pub fn as_class_entry(&'static self) -> &'static ClassEntry { - unsafe { ClassEntry::from_mut_ptr(self.inner.load(Ordering::Relaxed)) } - } -} - -pub(crate) type StateConstructor = dyn Fn() -> *mut dyn Any; - -pub(crate) type StateCloner = dyn Fn(*const dyn Any) -> *mut dyn Any; - -/// Builder for registering class. -/// -/// `` means the type of holding state. -/// -/// *It is a common practice for PHP extensions to use PHP objects to package -/// third-party resources.* -pub struct ClassEntity { - class_name: CString, - state_constructor: Rc, - method_entities: Vec, - property_entities: Vec, - parent: Option &'static ClassEntry>>, - interfaces: Vec &'static ClassEntry>>, - bind_class: Option<&'static StaticStateClass>, - state_cloner: Option>, - _p: PhantomData<(*mut (), T)>, -} - -impl ClassEntity<()> { - /// Construct a new `ClassEntity` with class name, do not own state. - pub fn new(class_name: impl Into) -> Self { - Self::new_with_state_constructor(class_name, || ()) - } -} - -impl ClassEntity { - /// Construct a new `ClassEntity` with class name and default state - /// constructor. - pub fn new_with_default_state_constructor(class_name: impl Into) -> Self { - Self::new_with_state_constructor(class_name, Default::default) - } -} - -impl ClassEntity { - /// Construct a new `ClassEntity` with class name and the constructor to - /// build state. - pub fn new_with_state_constructor( - class_name: impl Into, state_constructor: impl Fn() -> T + 'static, - ) -> Self { - Self { - class_name: ensure_end_with_zero(class_name), - state_constructor: Rc::new(move || { - let state = state_constructor(); - let boxed = Box::new(state) as Box; - Box::into_raw(boxed) - }), - method_entities: Vec::new(), - property_entities: Vec::new(), - parent: None, - interfaces: Vec::new(), - bind_class: None, - state_cloner: None, - _p: PhantomData, - } - } - - /// Add member method to class, with visibility and method handler. - pub fn add_method( - &mut self, name: impl Into, 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, - )); - self.method_entities.last_mut().unwrap() - } - - /// Add static method to class, with visibility and method handler. - pub fn add_static_method( - &mut self, name: impl Into, 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); - 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 Into, vis: Visibility, - ) -> &mut MethodEntity { - let mut entity = MethodEntity::new(name, None, vis); - 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); - } - - /// Register class to `extends` the parent class. - /// - /// *Because in the `MINIT` phase, the class starts to register, so the* - /// *closure is used to return the `ClassEntry` to delay the acquisition of* - /// *the class.* - /// - /// # Examples - /// - /// ```no_run - /// use phper::classes::{ClassEntity, ClassEntry}; - /// - /// let mut class = ClassEntity::new("MyException"); - /// class.extends(|| ClassEntry::from_globals("Exception").unwrap()); - /// ``` - pub fn extends(&mut self, parent: impl Fn() -> &'static ClassEntry + 'static) { - self.parent = Some(Box::new(parent)); - } - - /// Register class to `implements` the interface, due to the class can - /// implement multi interface, so this method can be called multi time. - /// - /// *Because in the `MINIT` phase, the class starts to register, so the* - /// *closure is used to return the `ClassEntry` to delay the acquisition of* - /// *the class.* - /// - /// # Examples - /// - /// ```no_run - /// use phper::classes::{ClassEntity, ClassEntry}; - /// - /// let mut class = ClassEntity::new("MyClass"); - /// class.implements(|| ClassEntry::from_globals("Stringable").unwrap()); - /// - /// // Here you have to the implement the method `__toString()` in `Stringable` - /// // for `MyClass`, otherwise `MyClass` will become abstract class. - /// // ... - /// ``` - pub fn implements(&mut self, interface: impl Fn() -> &'static ClassEntry + 'static) { - self.interfaces.push(Box::new(interface)); - } - - /// Bind to static [StaticStateClass]. - /// - /// When the class registered, the [StaticStateClass] will be initialized, - /// so you can use the [StaticStateClass] to new stateful object, etc. - pub fn bind(&mut self, cls: &'static StaticStateClass) { - self.bind_class = Some(cls); - } - - /// Add the state clone function, called when cloning PHP object. - /// - /// By default, the object registered by `phper` is uncloneable, if you - /// clone the object in PHP like this: - /// - /// ```php - /// $foo = new Foo(); - /// $foo2 = clone $foo; - /// ``` - /// - /// Will throw the Error: `Uncaught Error: Trying to clone an uncloneable - /// object of class Foo`. - /// - /// And then, if you want the object to be cloneable, you should register - /// the state clone method for the class. - /// - /// # Examples - /// - /// ``` - /// use phper::classes::ClassEntity; - /// - /// fn make_foo_class() -> ClassEntity { - /// let mut class = ClassEntity::new_with_state_constructor("Foo", || 123456); - /// class.state_cloner(Clone::clone); - /// class - /// } - /// ``` - pub fn state_cloner(&mut self, clone_fn: impl Fn(&T) -> T + 'static) { - self.state_cloner = Some(Rc::new(move |src| { - let src = unsafe { - src.as_ref() - .unwrap() - .downcast_ref::() - .expect("cast Any to T failed") - }; - let dest = clone_fn(src); - let boxed = Box::new(dest) as Box; - Box::into_raw(boxed) - })); - } - - #[allow(clippy::useless_conversion)] - pub(crate) unsafe fn init(&self) -> *mut zend_class_entry { - let parent: *mut zend_class_entry = self - .parent - .as_ref() - .map(|parent| parent()) - .map(|entry| entry.as_ptr() as *mut _) - .unwrap_or(null_mut()); - - let class_ce = phper_init_class_entry_ex( - self.class_name.as_ptr().cast(), - self.class_name.as_bytes().len().try_into().unwrap(), - self.function_entries(), - Some(class_init_handler), - parent.cast(), - ); - - if let Some(bind_class) = self.bind_class { - bind_class.bind(class_ce); - } - - for interface in &self.interfaces { - let interface_ce = interface().as_ptr(); - zend_class_implements(class_ce, 1, interface_ce); - } - - *phper_get_create_object(class_ce) = Some(create_object); - - class_ce - } - - pub(crate) unsafe fn declare_properties(&self, ce: *mut zend_class_entry) { - for property in &self.property_entities { - property.declare(ce); - } - } - - unsafe fn function_entries(&self) -> *const zend_function_entry { - let mut methods = self - .method_entities - .iter() - .map(|method| FunctionEntry::from_method_entity(method)) - .collect::>(); - - methods.push(zeroed::()); - - // Store the state constructor pointer to zend_class_entry. - methods.push(self.take_state_constructor_into_function_entry()); - - // Store the state cloner pointer to zend_class_entry. - methods.push(self.take_state_cloner_into_function_entry()); - - Box::into_raw(methods.into_boxed_slice()).cast() - } - - unsafe fn take_state_constructor_into_function_entry(&self) -> zend_function_entry { - let mut entry = zeroed::(); - let ptr = &mut entry as *mut _ as *mut *const StateConstructor; - let state_constructor = Rc::into_raw(self.state_constructor.clone()); - ptr.write(state_constructor); - entry - } - - unsafe fn take_state_cloner_into_function_entry(&self) -> zend_function_entry { - let mut entry = zeroed::(); - let ptr = &mut entry as *mut _ as *mut *const StateCloner; - if let Some(state_cloner) = &self.state_cloner { - let state_constructor = Rc::into_raw(state_cloner.clone()); - ptr.write(state_constructor); - } - entry - } -} - -unsafe extern "C" fn class_init_handler( - class_ce: *mut zend_class_entry, argument: *mut c_void, -) -> *mut zend_class_entry { - let parent = argument as *mut zend_class_entry; - if parent.is_null() { - zend_register_internal_class(class_ce) - } else { - zend_register_internal_class_ex(class_ce, parent) - } -} - -/// Builder for registering interface. -pub struct InterfaceEntity { - interface_name: CString, - method_entities: Vec, - extends: Vec &'static ClassEntry>>, - bind_interface: Option<&'static StaticInterface>, -} - -impl InterfaceEntity { - /// Construct a new `InterfaceEntity` with interface name. - pub fn new(interface_name: impl Into) -> Self { - Self { - interface_name: ensure_end_with_zero(interface_name.into()), - method_entities: Vec::new(), - extends: Vec::new(), - bind_interface: None, - } - } - - /// Add member method to interface, with mandatory visibility public - /// abstract. - pub fn add_method(&mut self, name: impl Into) -> &mut MethodEntity { - let mut entity = MethodEntity::new(name, None, Visibility::Public); - entity.set_vis_abstract(); - self.method_entities.push(entity); - self.method_entities.last_mut().unwrap() - } - - /// Register interface to `extends` the interfaces, due to the interface can - /// extends multi interface, so this method can be called multi time. - /// - /// *Because in the `MINIT` phase, the class starts to register, so the* - /// *closure is used to return the `ClassEntry` to delay the acquisition of* - /// *the class.* - /// - /// # Examples - /// - /// ```no_run - /// use phper::classes::{ClassEntry, InterfaceEntity}; - /// - /// let mut interface = InterfaceEntity::new("MyInterface"); - /// interface.extends(|| ClassEntry::from_globals("Stringable").unwrap()); - /// ``` - pub fn extends(&mut self, interface: impl Fn() -> &'static ClassEntry + 'static) { - self.extends.push(Box::new(interface)); - } - - /// Bind to static [StaticInterface]. - /// - /// When the interface registered, the [StaticInterface] will be - /// initialized, so you can use the [StaticInterface] to be implemented - /// by other class. - pub fn bind(&mut self, i: &'static StaticInterface) { - self.bind_interface = Some(i); - } - - #[allow(clippy::useless_conversion)] - pub(crate) unsafe fn init(&self) -> *mut zend_class_entry { - let class_ce = phper_init_class_entry_ex( - self.interface_name.as_ptr().cast(), - self.interface_name.as_bytes().len().try_into().unwrap(), - self.function_entries(), - Some(interface_init_handler), - null_mut(), - ); - - if let Some(bind_interface) = self.bind_interface { - bind_interface.bind(class_ce); - } - - for interface in &self.extends { - let interface_ce = interface().as_ptr(); - zend_class_implements(class_ce, 1, interface_ce); - } - - class_ce - } - - unsafe fn function_entries(&self) -> *const zend_function_entry { - let mut methods = self - .method_entities - .iter() - .map(|method| FunctionEntry::from_method_entity(method)) - .collect::>(); - - methods.push(zeroed::()); - - Box::into_raw(methods.into_boxed_slice()).cast() - } -} - -unsafe extern "C" fn interface_init_handler( - class_ce: *mut zend_class_entry, _argument: *mut c_void, -) -> *mut zend_class_entry { - zend_register_internal_interface(class_ce) -} - -/// 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 - } - - #[allow(clippy::useless_conversion)] - pub(crate) fn declare(&self, ce: *mut zend_class_entry) { - let name = self.name.as_ptr().cast(); - let name_length = self.name.len().try_into().unwrap(); - 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().try_into().unwrap(), - access_type, - ); - } - Scalar::Bytes(b) => { - zend_declare_property_stringl( - ce, - name, - name_length, - b.as_ptr().cast(), - b.len().try_into().unwrap(), - 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, -} - -/// Raw visibility flag. -pub(crate) type RawVisibility = u32; - -#[allow(clippy::useless_conversion)] -unsafe extern "C" fn create_object(ce: *mut zend_class_entry) -> *mut zend_object { - // Alloc more memory size to store state data. - let state_object = phper_zend_object_alloc(size_of::>().try_into().unwrap(), ce); - let state_object = StateObj::<()>::from_mut_ptr(state_object); - - // Find the hack elements hidden behind null builtin_function. - let mut func_ptr = (*ce).info.internal.builtin_functions; - while !(*func_ptr).fname.is_null() { - func_ptr = func_ptr.offset(1); - } - - // Get state constructor. - func_ptr = func_ptr.offset(1); - let state_constructor = func_ptr as *mut *const StateConstructor; - 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>()]; - - // Common initialize process. - let object = state_object.as_mut_object().as_mut_ptr(); - zend_object_std_init(object, ce); - object_properties_init(object, ce); - rebuild_object_properties(object); - - // Set handlers - 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); - (*object).handlers = Box::into_raw(handlers); - - // Call the state constructor and store the state. - let data = (state_constructor)(); - *state_object.as_mut_any_state() = data; - - object -} - -#[cfg(phper_major_version = "8")] -unsafe extern "C" fn clone_object(object: *mut zend_object) -> *mut zend_object { - clone_object_common(object) -} - -#[cfg(phper_major_version = "7")] -unsafe extern "C" fn clone_object(object: *mut zval) -> *mut zend_object { - let object = phper_z_obj_p(object); - clone_object_common(object) -} - -#[allow(clippy::useless_conversion)] -unsafe fn clone_object_common(object: *mut zend_object) -> *mut zend_object { - let ce = (*object).ce; - - // Alloc more memory size to store state data. - let new_state_object = - phper_zend_object_alloc(size_of::>().try_into().unwrap(), ce); - let new_state_object = StateObj::<()>::from_mut_ptr(new_state_object); - - // Find the hack elements hidden behind null builtin_function. - let mut func_ptr = (*(*object).ce).info.internal.builtin_functions; - while !(*func_ptr).fname.is_null() { - func_ptr = func_ptr.offset(1); - } - - // Get state cloner. - func_ptr = func_ptr.offset(2); - let state_cloner = func_ptr as *mut *const StateCloner; - let state_cloner = state_cloner.read().as_ref().unwrap(); - - // Initialize and clone members - let new_object = new_state_object.as_mut_object().as_mut_ptr(); - zend_object_std_init(new_object, ce); - object_properties_init(new_object, ce); - zend_objects_clone_members(new_object, object); - - // Set handlers - (*new_object).handlers = (*object).handlers; - - // Call the state cloner and store the state. - let state_object = StateObj::<()>::from_mut_object_ptr(object); - let data = (state_cloner)(*state_object.as_mut_any_state()); - *new_state_object.as_mut_any_state() = data; - - new_object -} - -unsafe extern "C" fn free_object(object: *mut zend_object) { - let state_object = StateObj::<()>::from_mut_object_ptr(object); - - // Drop the state. - state_object.drop_state(); - - // Original destroy call. - zend_object_std_dtor(object); -} diff --git a/phper/src/classes/entity.rs b/phper/src/classes/entity.rs new file mode 100644 index 0000000..5da596b --- /dev/null +++ b/phper/src/classes/entity.rs @@ -0,0 +1,316 @@ +use std::{any::Any, ffi::CString, marker::PhantomData, mem::zeroed, ptr::null_mut, rc::Rc}; + +use phper_sys::{ + phper_get_create_object, phper_init_class_entry_ex, zend_class_entry, zend_class_implements, + zend_function_entry, +}; + +use crate::{ + errors::Throwable, + functions::{Function, FunctionEntry, Method, MethodEntity}, + objects::StateObj, + types::Scalar, + values::ZVal, +}; + +use super::{ + class_init_handler, create_object, entry::ClassEntry, PropertyEntity, StateCloner, + StateConstructor, StaticStateClass, Visibility, +}; + +/// Builder for registering class. +/// +/// `` means the type of holding state. +/// +/// *It is a common practice for PHP extensions to use PHP objects to package +/// third-party resources.* +pub struct ClassEntity { + class_name: CString, + state_constructor: Rc, + method_entities: Vec, + property_entities: Vec, + parent: Option &'static ClassEntry>>, + interfaces: Vec &'static ClassEntry>>, + bind_class: Option<&'static StaticStateClass>, + state_cloner: Option>, + _p: PhantomData<(*mut (), T)>, +} + +impl ClassEntity<()> { + /// Construct a new `ClassEntity` with class name, do not own state. + pub fn new(class_name: impl Into) -> Self { + Self::new_with_state_constructor(class_name, || ()) + } +} + +impl ClassEntity { + /// Construct a new `ClassEntity` with class name and default state + /// constructor. + pub fn new_with_default_state_constructor(class_name: impl Into) -> Self { + Self::new_with_state_constructor(class_name, Default::default) + } +} + +pub trait Handler { + fn execute(&self, state: &mut StateObj, args: &mut [ZVal]) -> Result; +} + +impl Handler for dyn Fn(&mut StateObj, &mut [ZVal]) -> Result + 'static { + fn execute(&self, state: &mut StateObj, args: &mut [ZVal]) -> Result { + self(state, args) + } +} + +impl ClassEntity { + /// Construct a new `ClassEntity` with class name and the constructor to + /// build state. + pub fn new_with_state_constructor( + class_name: impl Into, state_constructor: impl Fn() -> T + 'static, + ) -> Self { + Self { + class_name: crate::utils::ensure_end_with_zero(class_name), + state_constructor: Rc::new(move || { + let state = state_constructor(); + let boxed = Box::new(state) as Box; + Box::into_raw(boxed) + }), + method_entities: Vec::new(), + property_entities: Vec::new(), + parent: None, + interfaces: Vec::new(), + bind_class: None, + state_cloner: None, + _p: PhantomData, + } + } + + /// Add member method to class, with visibility and method handler. + pub fn add_method( + &mut self, name: impl Into, 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, + )); + self.method_entities.last_mut().unwrap() + } + + /// Add static method to class, with visibility and method handler. + pub fn add_static_method( + &mut self, name: impl Into, 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); + 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 Into, vis: Visibility, + ) -> &mut MethodEntity { + let mut entity = MethodEntity::new(name, None, vis); + 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); + } + + /// Register class to `extends` the parent class. + /// + /// *Because in the `MINIT` phase, the class starts to register, so the* + /// *closure is used to return the `ClassEntry` to delay the acquisition of* + /// *the class.* + /// + /// # Examples + /// + /// ```no_run + /// use phper::classes::{ClassEntity, ClassEntry}; + /// + /// let mut class = ClassEntity::new("MyException"); + /// class.extends(|| ClassEntry::from_globals("Exception").unwrap()); + /// ``` + pub fn extends(&mut self, parent: impl Fn() -> &'static ClassEntry + 'static) { + self.parent = Some(Box::new(parent)); + } + + /// Register class to `implements` the interface, due to the class can + /// implement multi interface, so this method can be called multi time. + /// + /// *Because in the `MINIT` phase, the class starts to register, so the* + /// *closure is used to return the `ClassEntry` to delay the acquisition of* + /// *the class.* + /// + /// # Examples + /// + /// ```no_run + /// use phper::classes::{ClassEntity, ClassEntry}; + /// + /// let mut class = ClassEntity::new("MyClass"); + /// class.implements(|| ClassEntry::from_globals("Stringable").unwrap()); + /// + /// // Here you have to the implement the method `__toString()` in `Stringable` + /// // for `MyClass`, otherwise `MyClass` will become abstract class. + /// // ... + /// ``` + pub fn implements(&mut self, interface: impl Fn() -> &'static ClassEntry + 'static) { + self.interfaces.push(Box::new(interface)); + } + + /// Bind to static [StaticStateClass]. + /// + /// When the class registered, the [StaticStateClass] will be initialized, + /// so you can use the [StaticStateClass] to new stateful object, etc. + pub fn bind(&mut self, cls: &'static StaticStateClass) { + self.bind_class = Some(cls); + } + + /// Add the state clone function, called when cloning PHP object. + /// + /// By default, the object registered by `phper` is uncloneable, if you + /// clone the object in PHP like this: + /// + /// ```php + /// $foo = new Foo(); + /// $foo2 = clone $foo; + /// ``` + /// + /// Will throw the Error: `Uncaught Error: Trying to clone an uncloneable + /// object of class Foo`. + /// + /// And then, if you want the object to be cloneable, you should register + /// the state clone method for the class. + /// + /// # Examples + /// + /// ``` + /// use phper::classes::ClassEntity; + /// + /// fn make_foo_class() -> ClassEntity { + /// let mut class = ClassEntity::new_with_state_constructor("Foo", || 123456); + /// class.state_cloner(Clone::clone); + /// class + /// } + /// ``` + pub fn state_cloner(&mut self, clone_fn: impl Fn(&T) -> T + 'static) { + self.state_cloner = Some(Rc::new(move |src| { + let src = unsafe { + src.as_ref() + .unwrap() + .downcast_ref::() + .expect("cast Any to T failed") + }; + let dest = clone_fn(src); + let boxed = Box::new(dest) as Box; + Box::into_raw(boxed) + })); + } + + #[allow(clippy::useless_conversion)] + pub(crate) unsafe fn init(&self) -> *mut zend_class_entry { + let parent: *mut zend_class_entry = self + .parent + .as_ref() + .map(|parent| parent()) + .map(|entry| entry.as_ptr() as *mut _) + .unwrap_or(null_mut()); + + let class_ce = phper_init_class_entry_ex( + self.class_name.as_ptr().cast(), + self.class_name.as_bytes().len().try_into().unwrap(), + self.function_entries(), + Some(class_init_handler), + parent.cast(), + ); + + if let Some(bind_class) = self.bind_class { + bind_class.bind(class_ce); + } + + for interface in &self.interfaces { + let interface_ce = interface().as_ptr(); + zend_class_implements(class_ce, 1, interface_ce); + } + + *phper_get_create_object(class_ce) = Some(create_object); + + class_ce + } + + pub(crate) unsafe fn declare_properties(&self, ce: *mut zend_class_entry) { + for property in &self.property_entities { + property.declare(ce); + } + } + + unsafe fn function_entries(&self) -> *const zend_function_entry { + let mut methods = self + .method_entities + .iter() + .map(|method| FunctionEntry::from_method_entity(method)) + .collect::>(); + + methods.push(zeroed::()); + + // Store the state constructor pointer to zend_class_entry. + methods.push(self.take_state_constructor_into_function_entry()); + + // Store the state cloner pointer to zend_class_entry. + methods.push(self.take_state_cloner_into_function_entry()); + + Box::into_raw(methods.into_boxed_slice()).cast() + } + + unsafe fn take_state_constructor_into_function_entry(&self) -> zend_function_entry { + let mut entry = zeroed::(); + let ptr = &mut entry as *mut _ as *mut *const StateConstructor; + let state_constructor = Rc::into_raw(self.state_constructor.clone()); + ptr.write(state_constructor); + entry + } + + unsafe fn take_state_cloner_into_function_entry(&self) -> zend_function_entry { + let mut entry = zeroed::(); + let ptr = &mut entry as *mut _ as *mut *const StateCloner; + if let Some(state_cloner) = &self.state_cloner { + let state_constructor = Rc::into_raw(state_cloner.clone()); + ptr.write(state_constructor); + } + entry + } +} diff --git a/phper/src/classes/entry.rs b/phper/src/classes/entry.rs new file mode 100644 index 0000000..bd1bc53 --- /dev/null +++ b/phper/src/classes/entry.rs @@ -0,0 +1,192 @@ +//! Apis relate to [zend_class_entry]. + +use crate::{ + arrays::ZArr, + errors::{ClassNotFoundError, InitializeObjectError}, + objects::ZObject, + strings::ZStr, + sys::*, + values::ZVal, +}; +use std::{ + borrow::ToOwned, + fmt::Debug, + marker::PhantomData, + mem::{replace, ManuallyDrop}, +}; + +use super::find_global_class_entry_ptr; + +/// Wrapper of [zend_class_entry]. +#[repr(transparent)] +pub struct ClassEntry { + inner: zend_class_entry, + _p: PhantomData<*mut ()>, +} + +impl ClassEntry { + /// 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_class_entry) -> &'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_class_entry) -> 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_class_entry) -> &'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_class_entry) -> Option<&'a mut Self> { + (ptr as *mut Self).as_mut() + } + + /// Returns a raw pointer wrapped. + pub const fn as_ptr(&self) -> *const zend_class_entry { + &self.inner + } + + /// Returns a raw pointer wrapped. + #[inline] + pub fn as_mut_ptr(&mut self) -> *mut zend_class_entry { + &mut self.inner + } + + /// Create reference from global class name. + /// + /// # Examples + /// + /// ```no_run + /// use phper::classes::ClassEntry; + /// + /// let std_class = ClassEntry::from_globals("stdClass").unwrap(); + /// let _obj = std_class.new_object([]).unwrap(); + /// ``` + pub fn from_globals(class_name: impl AsRef) -> crate::Result<&'static Self> { + let name = class_name.as_ref(); + let ptr: *mut Self = find_global_class_entry_ptr(name).cast(); + unsafe { + ptr.as_ref().ok_or_else(|| { + crate::Error::ClassNotFound(ClassNotFoundError::new(name.to_string())) + }) + } + } + + /// Create the object from class and call `__construct` with arguments. + /// + /// If the `__construct` is private, or protected and the called scope isn't + /// parent class, it will throw PHP Error. + pub fn new_object(&self, arguments: impl AsMut<[ZVal]>) -> crate::Result { + let mut object = self.init_object()?; + object.call_construct(arguments)?; + Ok(object) + } + + /// Create the object from class, without calling `__construct`. + /// + /// **Be careful when `__construct` is necessary.** + pub fn init_object(&self) -> crate::Result { + unsafe { + let ptr = self.as_ptr() as *mut _; + let mut val = ZVal::default(); + if !phper_object_init_ex(val.as_mut_ptr(), ptr) { + Err(InitializeObjectError::new(self.get_name().to_str()?.to_owned()).into()) + } else { + // Can't drop val here! Otherwise the object will be dropped too (wasting me a + // day of debugging time here). + let mut val = ManuallyDrop::new(val); + let ptr = phper_z_obj_p(val.as_mut_ptr()); + Ok(ZObject::from_raw(ptr.cast_mut())) + } + } + } + + /// Get the class name. + pub fn get_name(&self) -> &ZStr { + unsafe { ZStr::from_ptr(self.inner.name) } + } + + /// Detect if the method is exists in class. + pub fn has_method(&self, method_name: &str) -> bool { + unsafe { + let function_table = ZArr::from_ptr(&self.inner.function_table); + function_table.exists(method_name) + } + } + + /// Detect if the class is instance of parent class. + pub fn is_instance_of(&self, parent: &ClassEntry) -> bool { + unsafe { phper_instanceof_function(self.as_ptr(), parent.as_ptr()) } + } + + /// Get the static property by name of class. + /// + /// Return None when static property hasn't register by + /// [ClassEntity::add_static_property]. + pub fn get_static_property(&self, name: impl AsRef) -> Option<&ZVal> { + let ptr = self.as_ptr() as *mut _; + let prop = Self::inner_get_static_property(ptr, name); + unsafe { ZVal::try_from_ptr(prop) } + } + + /// Set the static property by name of class. + /// + /// Return `Some(x)` where `x` is the previous value of static property, or + /// return `None` when static property hasn't register by + /// [ClassEntity::add_static_property]. + pub fn set_static_property(&self, name: impl AsRef, val: impl Into) -> Option { + let ptr = self.as_ptr() as *mut _; + let prop = Self::inner_get_static_property(ptr, name); + let prop = unsafe { ZVal::try_from_mut_ptr(prop) }; + prop.map(|prop| replace(prop, val.into())) + } + + fn inner_get_static_property(scope: *mut zend_class_entry, name: impl AsRef) -> *mut zval { + let name = name.as_ref(); + + unsafe { + #[allow(clippy::useless_conversion)] + zend_read_static_property(scope, name.as_ptr().cast(), name.len(), true.into()) + } + } +} + +impl Debug for ClassEntry { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("ClassEntry") + .field(&self.get_name().to_c_str()) + .finish() + } +} diff --git a/phper/src/classes/mod.rs b/phper/src/classes/mod.rs new file mode 100644 index 0000000..f724287 --- /dev/null +++ b/phper/src/classes/mod.rs @@ -0,0 +1,466 @@ +// 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_class_entry]. + +pub mod entity; +/// Zend Class Entry +pub mod entry; +pub mod zend_classes; + +use crate::{ + functions::{FunctionEntry, MethodEntity}, + objects::{StateObj, StateObject, ZObject}, + sys::*, + types::Scalar, + utils::ensure_end_with_zero, + values::ZVal, +}; +use std::{ + any::Any, + convert::TryInto, + ffi::{c_void, CString}, + marker::PhantomData, + mem::{size_of, zeroed}, + os::raw::c_int, + ptr::null_mut, + slice, + sync::atomic::{AtomicPtr, Ordering}, +}; + +use self::entry::ClassEntry; + +#[allow(clippy::useless_conversion)] +fn find_global_class_entry_ptr(name: impl AsRef) -> *mut zend_class_entry { + let name = name.as_ref(); + let name = name.to_lowercase(); + unsafe { + phper_zend_hash_str_find_ptr( + compiler_globals.class_table, + name.as_ptr().cast(), + name.len().try_into().unwrap(), + ) + .cast() + } +} + +/// The [StaticStateClass] holds +/// [zend_class_entry] and inner state, always as +/// the static variable, and then be bind to [ClassEntity]. +/// +/// When the class registered (module initialized), the [StaticStateClass] will +/// be initialized, so you can use the [StaticStateClass] to new stateful +/// object, etc. +/// +/// So, You shouldn't use [StaticStateClass] in `module_init` stage, because it +/// hasn't initialized. +/// +/// # Examples +/// +/// ```rust +/// use phper::classes::{ClassEntity, StaticStateClass}; +/// +/// pub static FOO_CLASS: StaticStateClass = StaticStateClass::null(); +/// +/// #[derive(Default)] +/// pub struct FooState; +/// +/// fn make_foo_class() -> ClassEntity { +/// let mut class = ClassEntity::new_with_default_state_constructor("Foo"); +/// class.bind(&FOO_CLASS); +/// class +/// } +/// ``` +#[repr(transparent)] +pub struct StaticStateClass { + inner: AtomicPtr, + _p: PhantomData, +} + +impl StaticStateClass { + /// Create empty [StaticStateClass], with null + /// [zend_class_entry]. + pub const fn null() -> Self { + Self { + inner: AtomicPtr::new(null_mut()), + _p: PhantomData, + } + } + + fn bind(&'static self, ptr: *mut zend_class_entry) { + self.inner.store(ptr, Ordering::Relaxed); + } + + /// Converts to class entry. + pub fn as_class_entry(&'static self) -> &'static ClassEntry { + unsafe { ClassEntry::from_mut_ptr(self.inner.load(Ordering::Relaxed)) } + } + + /// Create the object from class and call `__construct` with arguments. + /// + /// If the `__construct` is private, or protected and the called scope isn't + /// parent class, it will throw PHP Error. + pub fn new_object( + &'static self, arguments: impl AsMut<[ZVal]>, + ) -> crate::Result> { + self.as_class_entry() + .new_object(arguments) + .map(ZObject::into_raw) + .map(StateObject::::from_raw_object) + } + + /// Create the object from class, without calling `__construct`. + /// + /// **Be careful when `__construct` is necessary.** + pub fn init_object(&'static self) -> crate::Result> { + self.as_class_entry() + .init_object() + .map(ZObject::into_raw) + .map(StateObject::::from_raw_object) + } +} + +unsafe impl Sync for StaticStateClass {} + +/// The [StaticInterface] holds +/// [zend_class_entry], always as the static +/// variable, and then be bind to [InterfaceEntity]. +/// +/// When the interface registered (module initialized), the [StaticInterface] +/// will be initialized. +/// +/// So, You shouldn't use [StaticInterface] in `module_init` stage, because it +/// hasn't initialized. +/// +/// # Examples +/// +/// ```rust +/// use phper::classes::{InterfaceEntity, StaticInterface}; +/// +/// pub static FOO_INTERFACE: StaticInterface = StaticInterface::null(); +/// +/// fn make_foo_interface() -> InterfaceEntity { +/// let mut interface = InterfaceEntity::new("Foo"); +/// interface.bind(&FOO_INTERFACE); +/// interface +/// } +/// ``` +#[repr(transparent)] +pub struct StaticInterface { + inner: AtomicPtr, +} + +impl StaticInterface { + /// Create empty [StaticInterface], with null + /// [zend_class_entry]. + pub const fn null() -> Self { + Self { + inner: AtomicPtr::new(null_mut()), + } + } + + fn bind(&'static self, ptr: *mut zend_class_entry) { + self.inner.store(ptr, Ordering::Relaxed); + } + + /// Converts to class entry. + pub fn as_class_entry(&'static self) -> &'static ClassEntry { + unsafe { ClassEntry::from_mut_ptr(self.inner.load(Ordering::Relaxed)) } + } +} + +pub(crate) type StateConstructor = dyn Fn() -> *mut dyn Any; + +pub(crate) type StateCloner = dyn Fn(*const dyn Any) -> *mut dyn Any; + +unsafe extern "C" fn class_init_handler( + class_ce: *mut zend_class_entry, argument: *mut c_void, +) -> *mut zend_class_entry { + let parent = argument as *mut zend_class_entry; + if parent.is_null() { + zend_register_internal_class(class_ce) + } else { + zend_register_internal_class_ex(class_ce, parent) + } +} + +/// Builder for registering interface. +pub struct InterfaceEntity { + interface_name: CString, + method_entities: Vec, + extends: Vec &'static ClassEntry>>, + bind_interface: Option<&'static StaticInterface>, +} + +impl InterfaceEntity { + /// Construct a new `InterfaceEntity` with interface name. + pub fn new(interface_name: impl Into) -> Self { + Self { + interface_name: ensure_end_with_zero(interface_name.into()), + method_entities: Vec::new(), + extends: Vec::new(), + bind_interface: None, + } + } + + /// Add member method to interface, with mandatory visibility public + /// abstract. + pub fn add_method(&mut self, name: impl Into) -> &mut MethodEntity { + let mut entity = MethodEntity::new(name, None, Visibility::Public); + entity.set_vis_abstract(); + self.method_entities.push(entity); + self.method_entities.last_mut().unwrap() + } + + /// Register interface to `extends` the interfaces, due to the interface can + /// extends multi interface, so this method can be called multi time. + /// + /// *Because in the `MINIT` phase, the class starts to register, so the* + /// *closure is used to return the `ClassEntry` to delay the acquisition of* + /// *the class.* + /// + /// # Examples + /// + /// ```no_run + /// use phper::classes::{ClassEntry, InterfaceEntity}; + /// + /// let mut interface = InterfaceEntity::new("MyInterface"); + /// interface.extends(|| ClassEntry::from_globals("Stringable").unwrap()); + /// ``` + pub fn extends(&mut self, interface: impl Fn() -> &'static ClassEntry + 'static) { + self.extends.push(Box::new(interface)); + } + + /// Bind to static [StaticInterface]. + /// + /// When the interface registered, the [StaticInterface] will be + /// initialized, so you can use the [StaticInterface] to be implemented + /// by other class. + pub fn bind(&mut self, i: &'static StaticInterface) { + self.bind_interface = Some(i); + } + + #[allow(clippy::useless_conversion)] + pub(crate) unsafe fn init(&self) -> *mut zend_class_entry { + let class_ce = phper_init_class_entry_ex( + self.interface_name.as_ptr().cast(), + self.interface_name.as_bytes().len().try_into().unwrap(), + self.function_entries(), + Some(interface_init_handler), + null_mut(), + ); + + if let Some(bind_interface) = self.bind_interface { + bind_interface.bind(class_ce); + } + + for interface in &self.extends { + let interface_ce = interface().as_ptr(); + zend_class_implements(class_ce, 1, interface_ce); + } + + class_ce + } + + unsafe fn function_entries(&self) -> *const zend_function_entry { + let mut methods = self + .method_entities + .iter() + .map(|method| FunctionEntry::from_method_entity(method)) + .collect::>(); + + methods.push(zeroed::()); + + Box::into_raw(methods.into_boxed_slice()).cast() + } +} + +unsafe extern "C" fn interface_init_handler( + class_ce: *mut zend_class_entry, _argument: *mut c_void, +) -> *mut zend_class_entry { + zend_register_internal_interface(class_ce) +} + +/// 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, +} + +/// Raw visibility flag. +pub(crate) type RawVisibility = u32; + +unsafe extern "C" fn create_object(ce: *mut zend_class_entry) -> *mut zend_object { + // Alloc more memory size to store state data. + let state_object = phper_zend_object_alloc(size_of::>(), ce); + let state_object = StateObj::<()>::from_mut_ptr(state_object); + + // Find the hack elements hidden behind null builtin_function. + let mut func_ptr = (*ce).info.internal.builtin_functions; + while !(*func_ptr).fname.is_null() { + func_ptr = func_ptr.offset(1); + } + + // Get state constructor. + func_ptr = func_ptr.offset(1); + let state_constructor = func_ptr as *mut *const StateConstructor; + 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>()]; + + // Common initialize process. + let object = state_object.as_mut_object().as_mut_ptr(); + zend_object_std_init(object, ce); + object_properties_init(object, ce); + rebuild_object_properties(object); + + // Set handlers + 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); + (*object).handlers = Box::into_raw(handlers); + + // Call the state constructor and store the state. + let data = (state_constructor)(); + *state_object.as_mut_any_state() = data; + + object +} + +unsafe extern "C" fn clone_object(object: *mut zend_object) -> *mut zend_object { + clone_object_common(object) +} + +unsafe fn clone_object_common(object: *mut zend_object) -> *mut zend_object { + let ce = (*object).ce; + + // Alloc more memory size to store state data. + let new_state_object = phper_zend_object_alloc(size_of::>(), ce); + let new_state_object = StateObj::<()>::from_mut_ptr(new_state_object); + + // Find the hack elements hidden behind null builtin_function. + let mut func_ptr = (*(*object).ce).info.internal.builtin_functions; + while !(*func_ptr).fname.is_null() { + func_ptr = func_ptr.offset(1); + } + + // Get state cloner. + func_ptr = func_ptr.offset(2); + let state_cloner = func_ptr as *mut *const StateCloner; + let state_cloner = state_cloner.read().as_ref().unwrap(); + + // Initialize and clone members + let new_object = new_state_object.as_mut_object().as_mut_ptr(); + zend_object_std_init(new_object, ce); + object_properties_init(new_object, ce); + zend_objects_clone_members(new_object, object); + + // Set handlers + (*new_object).handlers = (*object).handlers; + + // Call the state cloner and store the state. + let state_object = StateObj::<()>::from_mut_object_ptr(object); + let data = (state_cloner)(*state_object.as_mut_any_state()); + *new_state_object.as_mut_any_state() = data; + + new_object +} + +unsafe extern "C" fn free_object(object: *mut zend_object) { + let state_object = StateObj::<()>::from_mut_object_ptr(object); + + // Drop the state. + state_object.drop_state(); + + // Original destroy call. + zend_object_std_dtor(object); +} diff --git a/phper/src/classes/zend_classes.rs b/phper/src/classes/zend_classes.rs new file mode 100644 index 0000000..1f209ef --- /dev/null +++ b/phper/src/classes/zend_classes.rs @@ -0,0 +1,48 @@ +use phper_sys::{ + zend_ce_aggregate, zend_ce_arrayaccess, zend_ce_countable, zend_ce_iterator, + zend_ce_serializable, zend_ce_stringable, zend_ce_traversable, +}; + +use super::entry::ClassEntry; + +/// Predefined interface `Iterator`. +#[inline] +pub fn iterator_interface<'a>() -> &'a ClassEntry { + unsafe { ClassEntry::from_ptr(zend_ce_iterator) } +} + +/// Predefined interface `ArrayAccess`. +#[inline] +pub fn array_access_interface<'a>() -> &'a ClassEntry { + unsafe { ClassEntry::from_ptr(zend_ce_arrayaccess) } +} + +/// Predefined interface `Traversable`. +#[inline] +pub fn traversable_interface<'a>() -> &'a ClassEntry { + unsafe { ClassEntry::from_ptr(zend_ce_traversable) } +} + +/// Predefined interface `Aggregate`. +#[inline] +pub fn aggregate_interface<'a>() -> &'a ClassEntry { + unsafe { ClassEntry::from_ptr(zend_ce_aggregate) } +} + +/// Predefined interface `Serializable`. +#[inline] +pub fn serializable_interface<'a>() -> &'a ClassEntry { + unsafe { ClassEntry::from_ptr(zend_ce_serializable) } +} + +/// Predefined interface `Countable`. +#[inline] +pub fn countable_interface<'a>() -> &'a ClassEntry { + unsafe { ClassEntry::from_ptr(zend_ce_countable) } +} + +/// Predefined interface `Stringable`. +#[inline] +pub fn stringable_interface<'a>() -> &'a ClassEntry { + unsafe { ClassEntry::from_ptr(zend_ce_stringable) } +} diff --git a/phper/src/errors.rs b/phper/src/errors.rs index aa50f83..83161f5 100644 --- a/phper/src/errors.rs +++ b/phper/src/errors.rs @@ -10,7 +10,7 @@ //! The errors for crate and php. -use crate::{classes::ClassEntry, objects::ZObject, sys::*, types::TypeInfo, values::ZVal}; +use crate::{classes::entry::ClassEntry, objects::ZObject, sys::*, types::TypeInfo, values::ZVal}; use derive_more::Constructor; use phper_alloc::ToRefOwned; use std::{ diff --git a/phper/src/functions.rs b/phper/src/functions.rs index c6836e3..3bea298 100644 --- a/phper/src/functions.rs +++ b/phper/src/functions.rs @@ -14,7 +14,7 @@ use crate::{ cg, - classes::{ClassEntry, RawVisibility, Visibility}, + classes::{entry::ClassEntry, RawVisibility, Visibility}, errors::{throw, ArgumentCountError, ExceptionGuard, ThrowObject, Throwable}, objects::{StateObj, ZObj, ZObject}, strings::{ZStr, ZString}, diff --git a/phper/src/lib.rs b/phper/src/lib.rs index da7b864..31a58a5 100644 --- a/phper/src/lib.rs +++ b/phper/src/lib.rs @@ -8,7 +8,7 @@ // 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_2018_idioms)] #![warn(clippy::dbg_macro, clippy::print_stdout)] #![doc = include_str!("../README.md")] diff --git a/phper/src/modules.rs b/phper/src/modules.rs index 4f541ca..701f504 100644 --- a/phper/src/modules.rs +++ b/phper/src/modules.rs @@ -12,7 +12,7 @@ use crate::{ c_str_ptr, - classes::{ClassEntity, InterfaceEntity}, + classes::{entity::ClassEntity, InterfaceEntity}, constants::Constant, errors::Throwable, functions::{Function, FunctionEntity, FunctionEntry}, diff --git a/phper/src/objects.rs b/phper/src/objects.rs index 1212f79..a833dc4 100644 --- a/phper/src/objects.rs +++ b/phper/src/objects.rs @@ -11,7 +11,7 @@ //! Apis relate to [zend_object]. use crate::{ - classes::ClassEntry, + classes::entry::ClassEntry, functions::{call_internal, call_raw_common, ZFunc}, sys::*, values::ZVal, diff --git a/tests/integration/src/classes.rs b/tests/integration/src/classes.rs index b932be1..7b6397b 100644 --- a/tests/integration/src/classes.rs +++ b/tests/integration/src/classes.rs @@ -11,8 +11,10 @@ use phper::{ alloc::RefClone, classes::{ - array_access_class, iterator_class, ClassEntity, ClassEntry, InterfaceEntity, - StaticInterface, StaticStateClass, Visibility, + entity::ClassEntity, + entry::ClassEntry, + zend_classes::{array_access_interface, iterator_interface}, + InterfaceEntity, StaticInterface, StaticStateClass, Visibility, }, functions::Argument, modules::Module, @@ -72,8 +74,8 @@ fn integrate_foo(module: &mut Module) { class.bind(&FOO_CLASS); - class.implements(iterator_class); - class.implements(array_access_class); + class.implements(iterator_interface); + class.implements(array_access_interface); // Implement Iterator interface. class.add_method("current", Visibility::Public, |this, _arguments| { @@ -146,8 +148,8 @@ fn integrate_i_bar(module: &mut Module) { interface.bind(&I_BAR_INTERFACE); - interface.extends(|| array_access_class()); - interface.extends(|| iterator_class()); + interface.extends(|| array_access_interface()); + interface.extends(|| iterator_interface()); interface .add_method("doSomethings") diff --git a/tests/integration/src/objects.rs b/tests/integration/src/objects.rs index 948e643..7ca20b3 100644 --- a/tests/integration/src/objects.rs +++ b/tests/integration/src/objects.rs @@ -10,7 +10,7 @@ use phper::{ alloc::{RefClone, ToRefOwned}, - classes::{ClassEntity, ClassEntry, Visibility}, + classes::{entity::ClassEntity, entry::ClassEntry, Visibility}, functions::Argument, modules::Module, objects::ZObject,