diff --git a/CHANGELOG.md b/CHANGELOG.md index 967e76eba1..038ddeec6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## Version 0.6.0 + +- Reorganized project. [#101] + - Changed (almost all) module paths. Too many changes to list them all, check + out the docs. + - Removed `skel` project. + +[#101]: https://github.com/davidcole1340/ext-php-rs/pull/101 + ## Version 0.5.3 - Fixed docs.rs PHP bindings file. diff --git a/Cargo.toml b/Cargo.toml index dde1a4beca..cf85e94e3a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,11 +9,12 @@ version = "0.5.3" authors = ["David Cole "] edition = "2018" categories = ["api-bindings"] +exclude = ["/.github", "/.crates", "/guide"] [dependencies] bitflags = "1.2.1" parking_lot = "0.11.2" -ext-php-rs-derive = { version = "=0.5.2", path = "./ext-php-rs-derive" } +ext-php-rs-derive = { version = "=0.5.2", path = "./crates/macros" } [build-dependencies] bindgen = { version = "0.59" } @@ -25,8 +26,7 @@ closure = [] [workspace] members = [ - "ext-php-rs-derive", - "example/skel" + "crates/macros", ] [package.metadata.docs.rs] diff --git a/README.md b/README.md index ae198ffe46..c9b4720def 100644 --- a/README.md +++ b/README.md @@ -85,8 +85,6 @@ easily), structs have to be hard coded in. Check out one of the example projects: -- [ext-skel](example/skel) - Testbed for testing the library. Check out previous - commits as well to see what else is possible. - [anonaddy-sequoia](https://gitlab.com/willbrowning/anonaddy-sequoia) - Sequoia encryption PHP extension. - [opus-php](https://github.com/davidcole1340/opus-php/tree/rewrite_rs) - diff --git a/build.rs b/build.rs index 4fbeed467f..110268d072 100644 --- a/build.rs +++ b/build.rs @@ -12,8 +12,8 @@ const MAX_PHP_API_VER: u32 = 20200930; fn main() { // rerun if wrapper header is changed - println!("cargo:rerun-if-changed=src/wrapper/wrapper.h"); - println!("cargo:rerun-if-changed=src/wrapper/wrapper.c"); + println!("cargo:rerun-if-changed=src/wrapper.h"); + println!("cargo:rerun-if-changed=src/wrapper.c"); let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("bindings.rs"); @@ -40,8 +40,8 @@ fn main() { } // Ensure the PHP API version is supported. - // We could easily use grep and sed here but eventually we want to support Windows, - // so it's easier to just use regex. + // We could easily use grep and sed here but eventually we want to support + // Windows, so it's easier to just use regex. let php_i_cmd = Command::new("php") .arg("-i") .output() @@ -71,7 +71,7 @@ fn main() { // Build `wrapper.c` and link to Rust. cc::Build::new() - .file("src/wrapper/wrapper.c") + .file("src/wrapper.c") .includes( str::replace(includes.as_ref(), "-I", "") .split(' ') @@ -80,7 +80,7 @@ fn main() { .compile("wrapper"); let mut bindgen = bindgen::Builder::default() - .header("src/wrapper/wrapper.h") + .header("src/wrapper.h") .clang_args(includes.split(' ')) .parse_callbacks(Box::new(bindgen::CargoCallbacks)) .rustfmt_bindings(true) @@ -143,8 +143,9 @@ impl Configure { } } -/// Array of functions/types used in `ext-php-rs` - used to allowlist when generating -/// bindings, as we don't want to generate bindings for everything (i.e. stdlib headers). +/// Array of functions/types used in `ext-php-rs` - used to allowlist when +/// generating bindings, as we don't want to generate bindings for everything +/// (i.e. stdlib headers). const ALLOWED_BINDINGS: &[&str] = &[ "HashTable", "_Bucket", diff --git a/crates/macros/.gitignore b/crates/macros/.gitignore new file mode 120000 index 0000000000..6ef08f9d15 --- /dev/null +++ b/crates/macros/.gitignore @@ -0,0 +1 @@ +../../.gitignore \ No newline at end of file diff --git a/ext-php-rs-derive/Cargo.toml b/crates/macros/Cargo.toml similarity index 100% rename from ext-php-rs-derive/Cargo.toml rename to crates/macros/Cargo.toml diff --git a/crates/macros/LICENSE_APACHE b/crates/macros/LICENSE_APACHE new file mode 120000 index 0000000000..bd7902319b --- /dev/null +++ b/crates/macros/LICENSE_APACHE @@ -0,0 +1 @@ +../../LICENSE_APACHE \ No newline at end of file diff --git a/crates/macros/LICENSE_MIT b/crates/macros/LICENSE_MIT new file mode 120000 index 0000000000..0304881f49 --- /dev/null +++ b/crates/macros/LICENSE_MIT @@ -0,0 +1 @@ +../../LICENSE_MIT \ No newline at end of file diff --git a/crates/macros/README.md b/crates/macros/README.md new file mode 120000 index 0000000000..fe84005413 --- /dev/null +++ b/crates/macros/README.md @@ -0,0 +1 @@ +../../README.md \ No newline at end of file diff --git a/ext-php-rs-derive/src/class.rs b/crates/macros/src/class.rs similarity index 97% rename from ext-php-rs-derive/src/class.rs rename to crates/macros/src/class.rs index 06af18ea7c..d5ea8d3551 100644 --- a/ext-php-rs-derive/src/class.rs +++ b/crates/macros/src/class.rs @@ -199,7 +199,7 @@ impl Property { PropertyType::Field { field_name } => { let field_name = Ident::new(field_name, Span::call_site()); quote! { - (#name, ::ext_php_rs::php::types::props::Property::field(|obj: &mut Self| &mut obj.#field_name)), + (#name, ::ext_php_rs::props::Property::field(|obj: &mut Self| &mut obj.#field_name)), } } PropertyType::Method { getter, setter } => { @@ -216,7 +216,7 @@ impl Property { quote! { None } }; quote! { - (#name, ::ext_php_rs::php::types::props::Property::method(#getter, #setter)), + (#name, ::ext_php_rs::props::Property::method(#getter, #setter)), } } } diff --git a/ext-php-rs-derive/src/constant.rs b/crates/macros/src/constant.rs similarity index 95% rename from ext-php-rs-derive/src/constant.rs rename to crates/macros/src/constant.rs index 09c2b44ddc..8418cf76e0 100644 --- a/ext-php-rs-derive/src/constant.rs +++ b/crates/macros/src/constant.rs @@ -46,6 +46,6 @@ impl Constant { // Visibility::Private => quote! { Private }, // }; - // quote! { ::ext_php_rs::php::flags::ConstantFlags} + // quote! { ::ext_php_rs::flags::ConstantFlags} // } } diff --git a/ext-php-rs-derive/src/extern_.rs b/crates/macros/src/extern_.rs similarity index 95% rename from ext-php-rs-derive/src/extern_.rs rename to crates/macros/src/extern_.rs index 8d47d09ec1..a629e82d01 100644 --- a/ext-php-rs-derive/src/extern_.rs +++ b/crates/macros/src/extern_.rs @@ -44,7 +44,7 @@ fn parse_function(mut func: ForeignItemFn) -> Result { #(#attrs)* #vis #sig { use ::std::convert::TryInto; - let callable = ::ext_php_rs::php::types::callable::Callable::try_from_name( + let callable = ::ext_php_rs::types::ZendCallable::try_from_name( #name ).expect(concat!("Unable to find callable function `", #name, "`.")); diff --git a/ext-php-rs-derive/src/function.rs b/crates/macros/src/function.rs similarity index 93% rename from ext-php-rs-derive/src/function.rs rename to crates/macros/src/function.rs index be66ac77bb..cae941f46a 100644 --- a/ext-php-rs-derive/src/function.rs +++ b/crates/macros/src/function.rs @@ -70,8 +70,8 @@ pub fn parser(args: AttributeArgs, input: ItemFn) -> Result<(TokenStream, Functi #input #[doc(hidden)] - pub extern "C" fn #internal_ident(ex: &mut ::ext_php_rs::php::execution_data::ExecutionData, retval: &mut ::ext_php_rs::php::types::zval::Zval) { - use ::ext_php_rs::php::types::zval::IntoZval; + pub extern "C" fn #internal_ident(ex: &mut ::ext_php_rs::zend::ExecuteData, retval: &mut ::ext_php_rs::types::Zval) { + use ::ext_php_rs::convert::IntoZval; #(#arg_definitions)* #arg_parser @@ -79,7 +79,7 @@ pub fn parser(args: AttributeArgs, input: ItemFn) -> Result<(TokenStream, Functi let result = #ident(#(#arg_accessors, )*); if let Err(e) = result.set_zval(retval, false) { - let e: ::ext_php_rs::php::exceptions::PhpException = e.into(); + let e: ::ext_php_rs::exception::PhpException = e.into(); e.throw().expect("Failed to throw exception"); } } @@ -206,7 +206,7 @@ pub fn build_arg_parser<'a>( let this = match this { Some(this) => this, None => { - ::ext_php_rs::php::exceptions::PhpException::default("Failed to retrieve reference to `$this`".into()) + ::ext_php_rs::exception::PhpException::default("Failed to retrieve reference to `$this`".into()) .throw() .unwrap(); return; @@ -309,7 +309,7 @@ impl Arg { pub fn get_type_ident(&self) -> TokenStream { let ty: Type = syn::parse_str(&self.ty).unwrap(); quote! { - <#ty as ::ext_php_rs::php::types::zval::FromZval>::TYPE + <#ty as ::ext_php_rs::convert::FromZval>::TYPE } } @@ -318,7 +318,8 @@ impl Arg { Ident::new(&self.name, Span::call_site()) } - /// Returns a [`TokenStream`] containing the line required to retrieve the value from the argument. + /// Returns a [`TokenStream`] containing the line required to retrieve the + /// value from the argument. pub fn get_accessor(&self, ret: &TokenStream) -> TokenStream { let name = &self.name; let name_ident = self.get_name_ident(); @@ -338,7 +339,7 @@ impl Arg { match #name_ident.val() { Some(val) => val, None => { - ::ext_php_rs::php::exceptions::PhpException::default( + ::ext_php_rs::exception::PhpException::default( concat!("Invalid value given for argument `", #name, "`.").into() ) .throw() @@ -350,7 +351,8 @@ impl Arg { } } - /// Returns a [`TokenStream`] containing the line required to instantiate the argument. + /// Returns a [`TokenStream`] containing the line required to instantiate + /// the argument. pub fn get_arg_definition(&self) -> TokenStream { let name = &self.name; let ty = self.get_type_ident(); @@ -363,7 +365,7 @@ impl Arg { }); quote! { - ::ext_php_rs::php::args::Arg::new(#name, #ty) #null #default + ::ext_php_rs::args::Arg::new(#name, #ty) #null #default } } } @@ -397,12 +399,12 @@ impl Function { // TODO allow reference returns? quote! { - .returns(<#ty as ::ext_php_rs::php::types::zval::IntoZval>::TYPE, false, #nullable) + .returns(<#ty as ::ext_php_rs::convert::IntoZval>::TYPE, false, #nullable) } }); quote! { - ::ext_php_rs::php::function::FunctionBuilder::new(#name, #name_ident) + ::ext_php_rs::builders::FunctionBuilder::new(#name, #name_ident) #(#args)* #output .build() diff --git a/ext-php-rs-derive/src/impl_.rs b/crates/macros/src/impl_.rs similarity index 100% rename from ext-php-rs-derive/src/impl_.rs rename to crates/macros/src/impl_.rs diff --git a/ext-php-rs-derive/src/lib.rs b/crates/macros/src/lib.rs similarity index 100% rename from ext-php-rs-derive/src/lib.rs rename to crates/macros/src/lib.rs diff --git a/ext-php-rs-derive/src/method.rs b/crates/macros/src/method.rs similarity index 91% rename from ext-php-rs-derive/src/method.rs rename to crates/macros/src/method.rs index db122d5a9d..9cc5de20ff 100644 --- a/ext-php-rs-derive/src/method.rs +++ b/crates/macros/src/method.rs @@ -154,10 +154,10 @@ pub fn parser(input: &mut ImplItemMethod, rename_rule: RenameRule) -> Result ::ext_php_rs::php::types::object::ConstructorResult { - use ::ext_php_rs::php::types::zval::IntoZval; - use ::ext_php_rs::php::types::object::ConstructorResult; + ex: &mut ::ext_php_rs::zend::ExecuteData + ) -> ::ext_php_rs::class::ConstructorResult { + use ::ext_php_rs::convert::IntoZval; + use ::ext_php_rs::class::ConstructorResult; #(#arg_definitions)* #arg_parser @@ -171,10 +171,10 @@ pub fn parser(input: &mut ImplItemMethod, rename_rule: RenameRule) -> Result Result::TYPE, false, #nullable) + .returns(<#ty as ::ext_php_rs::convert::IntoZval>::TYPE, false, #nullable) } }); quote! { - ::ext_php_rs::php::function::FunctionBuilder::new(#name, #class_path :: #name_ident) + ::ext_php_rs::builders::FunctionBuilder::new(#name, #class_path :: #name_ident) #(#args)* #output .build() @@ -338,7 +338,7 @@ impl Method { flags .iter() - .map(|flag| quote! { ::ext_php_rs::php::flags::MethodFlags::#flag }) + .map(|flag| quote! { ::ext_php_rs::flags::MethodFlags::#flag }) .collect::>() .to_token_stream() } diff --git a/ext-php-rs-derive/src/module.rs b/crates/macros/src/module.rs similarity index 83% rename from ext-php-rs-derive/src/module.rs rename to crates/macros/src/module.rs index fcaf7645e7..e591bbb614 100644 --- a/ext-php-rs-derive/src/module.rs +++ b/crates/macros/src/module.rs @@ -60,12 +60,12 @@ pub fn parser(input: ItemFn) -> Result { #[doc(hidden)] #[no_mangle] - pub extern "C" fn get_module() -> *mut ::ext_php_rs::php::module::ModuleEntry { + pub extern "C" fn get_module() -> *mut ::ext_php_rs::zend::ModuleEntry { fn internal(#inputs) #output { #(#stmts)* } - let mut builder = ::ext_php_rs::php::module::ModuleBuilder::new( + let mut builder = ::ext_php_rs::builders::ModuleBuilder::new( env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION") ) @@ -98,10 +98,10 @@ pub fn generate_registered_class_impl(class: &Class) -> Result { let func = Ident::new(&constructor.ident, Span::call_site()); let args = constructor.get_arg_definitions(); quote! { - Some(::ext_php_rs::php::types::object::ConstructorMeta { + Some(::ext_php_rs::class::ConstructorMeta { constructor: Self::#func, build_fn: { - use ext_php_rs::php::function::FunctionBuilder; + use ::ext_php_rs::builders::FunctionBuilder; fn build_fn(func: FunctionBuilder) -> FunctionBuilder { func #(#args)* @@ -115,19 +115,19 @@ pub fn generate_registered_class_impl(class: &Class) -> Result { }; Ok(quote! { - static #meta: ::ext_php_rs::php::types::object::ClassMetadata<#self_ty> = ::ext_php_rs::php::types::object::ClassMetadata::new(); + static #meta: ::ext_php_rs::class::ClassMetadata<#self_ty> = ::ext_php_rs::class::ClassMetadata::new(); - impl ::ext_php_rs::php::types::object::RegisteredClass for #self_ty { + impl ::ext_php_rs::class::RegisteredClass for #self_ty { const CLASS_NAME: &'static str = #class_name; const CONSTRUCTOR: ::std::option::Option< - ::ext_php_rs::php::types::object::ConstructorMeta + ::ext_php_rs::class::ConstructorMeta > = #constructor; - fn get_metadata() -> &'static ::ext_php_rs::php::types::object::ClassMetadata { + fn get_metadata() -> &'static ::ext_php_rs::class::ClassMetadata { &#meta } - fn get_properties<'a>() -> ::std::collections::HashMap<&'static str, ::ext_php_rs::php::types::props::Property<'a, Self>> { + fn get_properties<'a>() -> ::std::collections::HashMap<&'static str, ::ext_php_rs::props::Property<'a, Self>> { use ::std::iter::FromIterator; ::std::collections::HashMap::from_iter([ diff --git a/ext-php-rs-derive/src/startup_function.rs b/crates/macros/src/startup_function.rs similarity index 94% rename from ext-php-rs-derive/src/startup_function.rs rename to crates/macros/src/startup_function.rs index 0b9a7d6fd6..0a9a1354d6 100644 --- a/ext-php-rs-derive/src/startup_function.rs +++ b/crates/macros/src/startup_function.rs @@ -21,14 +21,14 @@ pub fn parser(input: ItemFn) -> Result { let func = quote! { #[doc(hidden)] pub extern "C" fn #ident(ty: i32, module_number: i32) -> i32 { - use ::ext_php_rs::php::constants::IntoConst; - use ::ext_php_rs::php::flags::PropertyFlags; + use ::ext_php_rs::constant::IntoConst; + use ::ext_php_rs::flags::PropertyFlags; fn internal() { #(#stmts)* } - ::ext_php_rs::php::module::ext_php_rs_startup(); + ::ext_php_rs::internal::ext_php_rs_startup(); #(#classes)* #(#constants)* @@ -116,7 +116,7 @@ fn build_classes(classes: &HashMap) -> Result> { // .collect::>>()?; Ok(quote! {{ - let class = ::ext_php_rs::php::class::ClassBuilder::new(#class_name) + let class = ::ext_php_rs::builders::ClassBuilder::new(#class_name) #(#methods)* #(#constants)* #(#interfaces)* diff --git a/ext-php-rs-derive/src/syn_ext.rs b/crates/macros/src/syn_ext.rs similarity index 100% rename from ext-php-rs-derive/src/syn_ext.rs rename to crates/macros/src/syn_ext.rs diff --git a/ext-php-rs-derive/src/zval.rs b/crates/macros/src/zval.rs similarity index 69% rename from ext-php-rs-derive/src/zval.rs rename to crates/macros/src/zval.rs index dad0dde40b..ca60bb2d80 100644 --- a/ext-php-rs-derive/src/zval.rs +++ b/crates/macros/src/zval.rs @@ -22,8 +22,9 @@ pub fn parser(input: DeriveInput) -> Result { }); let mut from_where_clause = into_where_clause.clone(); - // FIXME(david): work around since mutating `generics` will add the lifetime to the struct generics as well, - // leading to an error as we would have `impl<'a> FromZendObject<'a> for Struct<'a>` when `Struct` has no lifetime. + // FIXME(david): work around since mutating `generics` will add the lifetime to + // the struct generics as well, leading to an error as we would have + // `impl<'a> FromZendObject<'a> for Struct<'a>` when `Struct` has no lifetime. let from_impl_generics = { let tokens = into_impl_generics.to_token_stream(); let mut parsed: Generics = syn::parse2(tokens).expect("couldn't reparse generics"); @@ -42,13 +43,13 @@ pub fn parser(input: DeriveInput) -> Result { let ident = &ty.ident; into_where_clause.predicates.push( syn::parse2(quote! { - #ident: ::ext_php_rs::php::types::zval::IntoZval + #ident: ::ext_php_rs::convert::IntoZval }) .expect("couldn't parse where predicate"), ); from_where_clause.predicates.push( syn::parse2(quote! { - #ident: ::ext_php_rs::php::types::zval::FromZval<'_zval> + #ident: ::ext_php_rs::convert::FromZval<'_zval> }) .expect("couldn't parse where predicate"), ); @@ -126,44 +127,43 @@ fn parse_struct( .collect::>>()?; Ok(quote! { - impl #into_impl_generics ::ext_php_rs::php::types::object::IntoZendObject for #ident #ty_generics #into_where_clause { - fn into_zend_object(self) -> ::ext_php_rs::errors::Result< - ::ext_php_rs::php::boxed::ZBox< - ::ext_php_rs::php::types::object::ZendObject + impl #into_impl_generics ::ext_php_rs::convert::IntoZendObject for #ident #ty_generics #into_where_clause { + fn into_zend_object(self) -> ::ext_php_rs::error::Result< + ::ext_php_rs::boxed::ZBox< + ::ext_php_rs::types::ZendObject > > { - use ::ext_php_rs::php::types::zval::IntoZval; + use ::ext_php_rs::convert::IntoZval; - let mut obj = ::ext_php_rs::php::types::object::ZendObject::new_stdclass(); + let mut obj = ::ext_php_rs::types::ZendObject::new_stdclass(); #(#into_fields)* - ::ext_php_rs::errors::Result::Ok(obj) + ::ext_php_rs::error::Result::Ok(obj) } } - impl #into_impl_generics ::ext_php_rs::php::types::zval::IntoZval for #ident #ty_generics #into_where_clause { - const TYPE: ::ext_php_rs::php::enums::DataType = ::ext_php_rs::php::enums::DataType::Object(None); + impl #into_impl_generics ::ext_php_rs::convert::IntoZval for #ident #ty_generics #into_where_clause { + const TYPE: ::ext_php_rs::flags::DataType = ::ext_php_rs::flags::DataType::Object(None); - fn set_zval(self, zv: &mut ::ext_php_rs::php::types::zval::Zval, persistent: bool) -> ::ext_php_rs::errors::Result<()> { - use ::ext_php_rs::php::types::zval::IntoZval; - use ::ext_php_rs::php::types::object::IntoZendObject; + fn set_zval(self, zv: &mut ::ext_php_rs::types::Zval, persistent: bool) -> ::ext_php_rs::error::Result<()> { + use ::ext_php_rs::convert::{IntoZval, IntoZendObject}; self.into_zend_object()?.set_zval(zv, persistent) } } - impl #from_impl_generics ::ext_php_rs::php::types::object::FromZendObject<'_zval> for #ident #ty_generics #from_where_clause { - fn from_zend_object(obj: &'_zval ::ext_php_rs::php::types::object::ZendObject) -> ::ext_php_rs::errors::Result { - ::ext_php_rs::errors::Result::Ok(Self { + impl #from_impl_generics ::ext_php_rs::convert::FromZendObject<'_zval> for #ident #ty_generics #from_where_clause { + fn from_zend_object(obj: &'_zval ::ext_php_rs::types::ZendObject) -> ::ext_php_rs::error::Result { + ::ext_php_rs::error::Result::Ok(Self { #(#from_fields)* }) } } - impl #from_impl_generics ::ext_php_rs::php::types::zval::FromZval<'_zval> for #ident #ty_generics #from_where_clause { - const TYPE: ::ext_php_rs::php::enums::DataType = ::ext_php_rs::php::enums::DataType::Object(None); + impl #from_impl_generics ::ext_php_rs::convert::FromZval<'_zval> for #ident #ty_generics #from_where_clause { + const TYPE: ::ext_php_rs::flags::DataType = ::ext_php_rs::flags::DataType::Object(None); - fn from_zval(zv: &'_zval ::ext_php_rs::php::types::zval::Zval) -> ::std::option::Option { - use ::ext_php_rs::php::types::object::FromZendObject; + fn from_zval(zv: &'_zval ::ext_php_rs::types::Zval) -> ::std::option::Option { + use ::ext_php_rs::convert::FromZendObject; Self::from_zend_object(zv.object()?).ok() } @@ -230,30 +230,30 @@ fn parse_enum( let default = default.unwrap_or_else(|| quote! { None }); Ok(quote! { - impl #into_impl_generics ::ext_php_rs::php::types::zval::IntoZval for #ident #ty_generics #into_where_clause { - const TYPE: ::ext_php_rs::php::enums::DataType = ::ext_php_rs::php::enums::DataType::Mixed; + impl #into_impl_generics ::ext_php_rs::convert::IntoZval for #ident #ty_generics #into_where_clause { + const TYPE: ::ext_php_rs::flags::DataType = ::ext_php_rs::flags::DataType::Mixed; fn set_zval( self, - zv: &mut ::ext_php_rs::php::types::zval::Zval, + zv: &mut ::ext_php_rs::types::Zval, persistent: bool, - ) -> ::ext_php_rs::errors::Result<()> { - use ::ext_php_rs::php::types::zval::IntoZval; + ) -> ::ext_php_rs::error::Result<()> { + use ::ext_php_rs::convert::IntoZval; match self { #(#into_variants,)* _ => { zv.set_null(); - ::ext_php_rs::errors::Result::Ok(()) + ::ext_php_rs::error::Result::Ok(()) } } } } - impl #from_impl_generics ::ext_php_rs::php::types::zval::FromZval<'_zval> for #ident #ty_generics #from_where_clause { - const TYPE: ::ext_php_rs::php::enums::DataType = ::ext_php_rs::php::enums::DataType::Mixed; + impl #from_impl_generics ::ext_php_rs::convert::FromZval<'_zval> for #ident #ty_generics #from_where_clause { + const TYPE: ::ext_php_rs::flags::DataType = ::ext_php_rs::flags::DataType::Mixed; - fn from_zval(zval: &'_zval ::ext_php_rs::php::types::zval::Zval) -> ::std::option::Option { + fn from_zval(zval: &'_zval ::ext_php_rs::types::Zval) -> ::std::option::Option { #(#from_variants)* #default } diff --git a/example/skel/.gitignore b/example/skel/.gitignore deleted file mode 100644 index 43db77f061..0000000000 --- a/example/skel/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -/target -Cargo.lock -/.vscode -expand.rs -vendor -composer.lock diff --git a/example/skel/Cargo.toml b/example/skel/Cargo.toml deleted file mode 100644 index 426e778e52..0000000000 --- a/example/skel/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "skel" -version = "0.1.0" -authors = ["David Cole "] -edition = "2018" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -ext-php-rs = { path = "../../", features = ["closure"] } - -[lib] -name = "skel" -crate-type = ["cdylib"] diff --git a/example/skel/composer.json b/example/skel/composer.json deleted file mode 100644 index fb8425595f..0000000000 --- a/example/skel/composer.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "rust/skel", - "license": "MIT OR Apache-2.0", - "authors": [ - { - "name": "David Cole", - "email": "david.cole1340@gmail.com" - } - ], - "require": { - "psy/psysh": "^0.10.8" - } -} diff --git a/example/skel/php b/example/skel/php deleted file mode 100755 index bdc549bdfa..0000000000 --- a/example/skel/php +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash - -case "$(uname)" in - Darwin) EXT="dylib" ;; - Linux) EXT="so" ;; - *) EXT="so" ;; -esac - -cargo build && php -dextension=$PWD/../../target/debug/libskel.$EXT "${@}" diff --git a/example/skel/src/allocator.rs b/example/skel/src/allocator.rs deleted file mode 100644 index d7f221d8dd..0000000000 --- a/example/skel/src/allocator.rs +++ /dev/null @@ -1,31 +0,0 @@ -#![doc(hidden)] - -use ext_php_rs::php::alloc; -use std::alloc::GlobalAlloc; - -/// Global allocator which uses the Zend memory management APIs to allocate memory. -/// -/// At the moment, this should only be used for debugging memory leaks. You are not supposed to -/// allocate non-request-bound memory using the Zend memory management API. -#[derive(Default)] -pub struct PhpAllocator {} - -impl PhpAllocator { - /// Creates a new PHP allocator. - pub const fn new() -> Self { - Self {} - } -} - -unsafe impl GlobalAlloc for PhpAllocator { - unsafe fn alloc(&self, layout: std::alloc::Layout) -> *mut u8 { - let ptr = alloc::emalloc(layout); - eprintln!("allocating {:?}: {} bytes", ptr, layout.size()); - ptr - } - - unsafe fn dealloc(&self, ptr: *mut u8, layout: std::alloc::Layout) { - eprintln!("freeing {:?}: {} bytes", ptr, layout.size()); - alloc::efree(ptr) - } -} diff --git a/example/skel/src/lib.rs b/example/skel/src/lib.rs deleted file mode 100644 index 127f5d983c..0000000000 --- a/example/skel/src/lib.rs +++ /dev/null @@ -1,61 +0,0 @@ -use ext_php_rs::prelude::*; - -#[php_class] -struct TestClass { - #[prop(rename = "Hello")] - a: i32, - #[prop] - b: i64, - #[prop] - c: String, -} - -impl Default for TestClass { - fn default() -> Self { - Self { - a: 100, - b: 123, - c: "Hello, world!".into(), - } - } -} - -#[php_impl] -impl TestClass { - #[getter] - fn get_test_name(&self) -> String { - self.c.clone() - } - - #[setter] - fn set_test_name(&mut self, c: String) { - self.c = c; - } -} - -#[derive(Debug, ZvalConvert)] -pub struct TestStdClass -where - A: PartialEq, -{ - a: A, - b: B, - c: C, -} - -#[derive(Debug, ZvalConvert)] -pub enum UnionExample<'a, T> { - B(T), - C(&'a str), - None, -} - -#[php_function] -pub fn test_union(union: UnionExample) { - dbg!(union); -} - -#[php_module] -pub fn module(module: ModuleBuilder) -> ModuleBuilder { - module -} diff --git a/example/skel/test.php b/example/skel/test.php deleted file mode 100644 index a4abe2dafc..0000000000 --- a/example/skel/test.php +++ /dev/null @@ -1,2 +0,0 @@ -) -> Binary { for i in input.iter() { diff --git a/guide/src/types/bool.md b/guide/src/types/bool.md index 18b55eb974..11a8c797a9 100644 --- a/guide/src/types/bool.md +++ b/guide/src/types/bool.md @@ -2,9 +2,9 @@ A boolean. Not much else to say here. -| `T` parameter | `&T` parameter | `T` Return type | PHP representation | -| ------------- | -------------- | --------------- | ------------------ | -| Yes | No | Yes | Union flag | +| `T` parameter | `&T` parameter | `T` Return type | `&T` Return type | PHP representation | +| ------------- | -------------- | --------------- | ---------------- | ------------------ | +| Yes | No | Yes | No | Union flag | Booleans are not actually stored inside the zval. Instead, they are treated as two different union types (the zval can be in a true or false state). An diff --git a/guide/src/types/closure.md b/guide/src/types/closure.md index caccf16cc3..9a41a4b842 100644 --- a/guide/src/types/closure.md +++ b/guide/src/types/closure.md @@ -16,9 +16,9 @@ PHP callables (which includes closures) can be passed to Rust through the `Callable` type. When calling a callable, you must provide it with a `Vec` of arguemnts, all of which must implement `IntoZval` and `Clone`. -| `T` parameter | `&T` parameter | `T` Return type | `&T` Return type | PHP representation | -| ------------- | -------------- | ----------------------------------- | ---------------- | ------------------------------------------------------------------------------------------ | -| `Callable` | No | `Closure`, `Callable` for functions | No | Callables are implemented in PHP, closures are represented as an instance of `PhpClosure`. | +| `T` parameter | `&T` parameter | `T` Return type | `&T` Return type | PHP representation | +| ------------- | -------------- | -------------------------------------- | ---------------- | ------------------------------------------------------------------------------------------ | +| `Callable` | No | `Closure`, `Callable`for PHP functions | No | Callables are implemented in PHP, closures are represented as an instance of `PhpClosure`. | Internally, when you enable the `closure` feature, a class `PhpClosure` is registered alongside your other classes: @@ -112,7 +112,7 @@ function by its name, or as a parameter. They can be called through the use ext_php_rs::prelude::*; #[php_function] -pub fn callable_parameter(call: Callable) { +pub fn callable_parameter(call: ZendCallable) { let val = call.try_call(vec![&0, &1, &"Hello"]).expect("Failed to call function"); dbg!(val); } diff --git a/guide/src/types/hashmap.md b/guide/src/types/hashmap.md index 3b5721e6da..ddc06b0147 100644 --- a/guide/src/types/hashmap.md +++ b/guide/src/types/hashmap.md @@ -2,9 +2,9 @@ `HashMap`s are represented as associative arrays in PHP. -| `T` parameter | `&T` parameter | `T` Return type | PHP representation | -| ------------- | -------------- | --------------- | ------------------ | -| Yes | No | Yes | `HashTable` | +| `T` parameter | `&T` parameter | `T` Return type | `&T` Return type | PHP representation | +| ------------- | -------------- | --------------- | ---------------- | ------------------ | +| Yes | No | Yes | No | `ZendHashTable` | Converting from a zval to a `HashMap` is valid when the key is a `String`, and the value implements `FromZval`. The key and values are copied into Rust types diff --git a/guide/src/types/numbers.md b/guide/src/types/numbers.md index 2d83c6b8fd..b50636ad6c 100644 --- a/guide/src/types/numbers.md +++ b/guide/src/types/numbers.md @@ -3,9 +3,9 @@ Primitive integers include `i8`, `i16`, `i32`, `i64`, `u8`, `u16`, `u32`, `u64`, `isize`, `usize`, `f32` and `f64`. -| `T` parameter | `&T` parameter | `T` Return type | PHP representation | -| ------------- | -------------- | --------------- | -------------------------------------------------------------------------------- | -| Yes | No | Yes | `i32` on 32-bit platforms, `i64` on 64-bit platforms, `f64` platform-independent | +| `T` parameter | `&T` parameter | `T` Return type | `&T` Return type | PHP representation | +| ------------- | -------------- | --------------- | ---------------- | -------------------------------------------------------------------------------- | +| Yes | No | Yes | No | `i32` on 32-bit platforms, `i64` on 64-bit platforms, `f64` platform-independent | Note that internally, PHP treats **all** of these integers the same (a 'long'), and therefore it must be converted into a long to be stored inside the zval. A diff --git a/guide/src/types/object.md b/guide/src/types/object.md index baf8ce78c7..aaec16dab8 100644 --- a/guide/src/types/object.md +++ b/guide/src/types/object.md @@ -4,13 +4,13 @@ Objects can be returned from functions as instances or references. You can only return a reference when you are returning an immutable reference to the object the method is implemented on. -| `T` parameter | `&T` parameter | `T` Return type | `&T` Return type | PHP representation | -| ------------- | -------------- | --------------- | --------------------- | -------------------------------- | -| No | No | Yes | Yes, as `ClassRef` | A Rust struct and a Zend object. | +| `T` parameter | `&T` parameter | `T` Return type | `&T` Return type | PHP representation | +| ------------- | -------------- | --------------- | ---------------- | -------------------------------- | +| No | No | Yes | No - planned | A Rust struct and a Zend object. | ## Examples -### Returning a reference to `self` + ### Creating a new class instance diff --git a/guide/src/types/option.md b/guide/src/types/option.md index d1ae0d0e85..e2391d121e 100644 --- a/guide/src/types/option.md +++ b/guide/src/types/option.md @@ -4,9 +4,9 @@ Options are used for optional and nullable parameters, as well as null returns. It is valid to be converted to/from a zval as long as the underlying `T` generic is also able to be converted to/from a zval. -| `T` parameter | `&T` parameter | `T` Return type | PHP representation | -| ------------- | -------------- | --------------- | ------------------ | -| Yes | No | Yes | Depends on `T` | +| `T` parameter | `&T` parameter | `T` Return type | `&T` Return type | PHP representation | +| ------------- | -------------- | --------------- | ---------------- | ---------------------------------- | +| Yes | No | Yes | No | Depends on `T`, `null` for `None`. | Using `Option` as a parameter indicates that the parameter is nullable. If null is passed, a `None` value will be supplied. It is also used in the place of diff --git a/guide/src/types/str.md b/guide/src/types/str.md index d30be3f122..08759291ba 100644 --- a/guide/src/types/str.md +++ b/guide/src/types/str.md @@ -4,9 +4,9 @@ A borrowed string. When this type is encountered, you are given a reference to the actual zend string memory, rather than copying the contents like if you were taking an owned `String` argument. -| `T` parameter | `&T` parameter | `&T` Return type | PHP representation | -| ------------- | -------------- | ---------------- | ------------------------ | -| No | Yes | Yes | `zend_string` (C-string) | +| `T` parameter | `&T` parameter | `T` Return type | `&T` Return type | PHP representation | +| ------------- | -------------- | --------------- | ---------------- | ------------------------ | +| No | Yes | No | Yes | `zend_string` (C-string) | Note that you cannot expect the function to operate the same by swapping out `String` and `&str` - since the zend string memory is read directly, this diff --git a/guide/src/types/string.md b/guide/src/types/string.md index db44f9bb94..fa7fd137b7 100644 --- a/guide/src/types/string.md +++ b/guide/src/types/string.md @@ -4,9 +4,9 @@ When a `String` type is encountered, the zend string content is copied to/from a Rust `String` object. If the zval does not contain a string, it will attempt to read a `double` from the zval and convert it into a `String` object. -| `T` parameter | `&T` parameter | `T` Return type | PHP representation | -| ------------- | -------------- | --------------- | ------------------------ | -| Yes | No | Yes | `zend_string` (C-string) | +| `T` parameter | `&T` parameter | `T` Return type | `&T` Return type | PHP representation | +| ------------- | -------------- | --------------- | ---------------- | ------------------------ | +| Yes | No | Yes | No | `zend_string` (C-string) | Internally, PHP stores strings in `zend_string` objects, which is a refcounted C struct containing the string length with the content of the string appended to @@ -20,7 +20,7 @@ be thrown if one is encountered while converting a `String` to a zval. # extern crate ext_php_rs; # use ext_php_rs::prelude::*; #[php_function] -pub fn str_example(input: &str) -> String { +pub fn str_example(input: String) -> String { format!("Hello {}", input) } ``` diff --git a/guide/src/types/vec.md b/guide/src/types/vec.md index 3907e157db..03b5b81173 100644 --- a/guide/src/types/vec.md +++ b/guide/src/types/vec.md @@ -4,9 +4,9 @@ Vectors can contain any type that can be represented as a zval. Note that the data contained in the array will be copied into Rust types and stored inside the vector. The internal representation of a PHP array is discussed below. -| `T` parameter | `&T` parameter | `T` Return type | PHP representation | -| ------------- | -------------- | --------------- | ------------------ | -| Yes | No | Yes | `HashTable` | +| `T` parameter | `&T` parameter | `T` Return type | `&T` Return type | PHP representation | +| ------------- | -------------- | --------------- | ---------------- | ------------------ | +| Yes | No | Yes | No | `ZendHashTable` | Internally, PHP arrays are hash tables where the key can be an unsigned long or a string. Zvals are contained inside arrays therefore the data does not have to diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000000..606e29234d --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1 @@ +wrap_comments = true \ No newline at end of file diff --git a/src/php/alloc.rs b/src/alloc.rs similarity index 87% rename from src/php/alloc.rs rename to src/alloc.rs index 6edaad9366..3bad5ee954 100644 --- a/src/php/alloc.rs +++ b/src/alloc.rs @@ -1,6 +1,7 @@ -//! Functions relating to the Zend Memory Manager, used to allocate request-bound memory. +//! Functions relating to the Zend Memory Manager, used to allocate +//! request-bound memory. -use crate::bindings::{_efree, _emalloc}; +use crate::ffi::{_efree, _emalloc}; use std::{alloc::Layout, ffi::c_void}; /// Uses the PHP memory allocator to allocate request-bound memory. @@ -28,7 +29,8 @@ pub fn emalloc(layout: Layout) -> *mut u8 { }) as *mut u8 } -/// Frees a given memory pointer which was allocated through the PHP memory manager. +/// Frees a given memory pointer which was allocated through the PHP memory +/// manager. /// /// # Parameters /// @@ -36,8 +38,8 @@ pub fn emalloc(layout: Layout) -> *mut u8 { /// /// # Safety /// -/// Caller must guarantee that the given pointer is valid (aligned and non-null) and -/// was originally allocated through the Zend memory manager. +/// Caller must guarantee that the given pointer is valid (aligned and non-null) +/// and was originally allocated through the Zend memory manager. pub unsafe fn efree(ptr: *mut u8) { #[cfg(php_debug)] { diff --git a/src/php/args.rs b/src/args.rs similarity index 88% rename from src/php/args.rs rename to src/args.rs index e5d3ba683a..4bb5be0eac 100644 --- a/src/php/args.rs +++ b/src/args.rs @@ -2,23 +2,19 @@ use std::{ffi::CString, ptr}; -use super::{ - enums::DataType, - types::{ - zval::{FromZvalMut, IntoZvalDyn, Zval}, - ZendType, - }, -}; - use crate::{ - bindings::{ + convert::{FromZvalMut, IntoZvalDyn}, + error::{Error, Result}, + ffi::{ _zend_expected_type, _zend_expected_type_Z_EXPECTED_ARRAY, _zend_expected_type_Z_EXPECTED_BOOL, _zend_expected_type_Z_EXPECTED_DOUBLE, _zend_expected_type_Z_EXPECTED_LONG, _zend_expected_type_Z_EXPECTED_OBJECT, _zend_expected_type_Z_EXPECTED_RESOURCE, _zend_expected_type_Z_EXPECTED_STRING, zend_internal_arg_info, zend_wrong_parameters_count_error, }, - errors::{Error, Result}, + flags::DataType, + types::Zval, + zend::ZendType, }; /// Represents an argument to a function. @@ -77,13 +73,14 @@ impl<'a> Arg<'a> { self } - /// Attempts to consume the argument, converting the inner type into `T`. Upon success, - /// the result is returned in a [`Result`]. + /// Attempts to consume the argument, converting the inner type into `T`. + /// Upon success, the result is returned in a [`Result`]. /// - /// If the conversion fails (or the argument contains no value), the argument is returned - /// in an [`Err`] variant. + /// If the conversion fails (or the argument contains no value), the + /// argument is returned in an [`Err`] variant. /// - /// As this function consumes, it cannot return a reference to the underlying zval. + /// As this function consumes, it cannot return a reference to the + /// underlying zval. pub fn consume(mut self) -> Result where for<'b> T: FromZvalMut<'b>, @@ -114,12 +111,14 @@ impl<'a> Arg<'a> { self.zval.as_mut() } - /// Attempts to call the argument as a callable with a list of arguments to pass to the function. - /// Note that a thrown exception inside the callable is not detectable, therefore you should - /// check if the return value is valid rather than unwrapping. Returns a result containing the + /// Attempts to call the argument as a callable with a list of arguments to + /// pass to the function. Note that a thrown exception inside the + /// callable is not detectable, therefore you should check if the return + /// value is valid rather than unwrapping. Returns a result containing the /// return value of the function, or an error. /// - /// You should not call this function directly, rather through the [`call_user_func`] macro. + /// You should not call this function directly, rather through the + /// [`call_user_func`] macro. /// /// # Parameters /// @@ -205,9 +204,11 @@ impl<'a, 'b> ArgParser<'a, 'b> { } /// Uses the argument parser to parse the arguments contained in the given - /// `ExecutionData` object. Returns successfully if the arguments were parsed. + /// `ExecuteData` object. Returns successfully if the arguments were + /// parsed. /// - /// This function can only be safely called from within an exported PHP function. + /// This function can only be safely called from within an exported PHP + /// function. /// /// # Parameters /// @@ -215,16 +216,17 @@ impl<'a, 'b> ArgParser<'a, 'b> { /// /// # Errors /// - /// Returns an [`Error`] type if there were too many or too little arguments passed to the - /// function. The user has already been notified so you should break execution after seeing an - /// error type. + /// Returns an [`Error`] type if there were too many or too little arguments + /// passed to the function. The user has already been notified so you + /// should break execution after seeing an error type. pub fn parse(mut self) -> Result<()> { let max_num_args = self.args.len(); let min_num_args = self.min_num_args.unwrap_or(max_num_args); let num_args = self.arg_zvals.len(); if num_args < min_num_args || num_args > max_num_args { - // SAFETY: Exported C function is safe, return value is unused and parameters are copied. + // SAFETY: Exported C function is safe, return value is unused and parameters + // are copied. unsafe { zend_wrong_parameters_count_error(min_num_args as _, max_num_args as _) }; return Err(Error::IncorrectArguments(num_args, min_num_args)); } diff --git a/src/binary.rs b/src/binary.rs new file mode 100644 index 0000000000..60588f58a4 --- /dev/null +++ b/src/binary.rs @@ -0,0 +1,190 @@ +//! Provides implementations for converting to and from Zend binary strings, +//! commonly returned from functions such as [`pack`] and [`unpack`]. +//! +//! [`pack`]: https://www.php.net/manual/en/function.pack.php +//! [`unpack`]: https://www.php.net/manual/en/function.unpack.php + +use crate::ffi::{ext_php_rs_zend_string_init, zend_string}; + +use std::{ + convert::TryFrom, + iter::FromIterator, + ops::{Deref, DerefMut}, +}; + +use crate::{ + convert::{FromZval, IntoZval}, + error::{Error, Result}, + flags::DataType, + types::Zval, +}; + +/// Acts as a wrapper around [`Vec`] where `T` implements [`Pack`]. Primarily +/// used for passing binary data into Rust functions. Can be treated as a +/// [`Vec`] in most situations, or can be 'unwrapped' into a [`Vec`] through the +/// [`From`] implementation on [`Vec`]. +#[derive(Debug)] +pub struct Binary(Vec); + +impl Binary { + /// Creates a new binary wrapper from a set of data which can be converted + /// into a vector. + /// + /// # Parameters + /// + /// * `data` - Data to store inside the binary wrapper. + pub fn new(data: impl Into>) -> Self { + Self(data.into()) + } +} + +impl Deref for Binary { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Binary { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl FromZval<'_> for Binary { + const TYPE: DataType = DataType::String; + + fn from_zval(zval: &Zval) -> Option { + zval.binary().map(Binary) + } +} + +impl TryFrom for Binary { + type Error = Error; + + fn try_from(value: Zval) -> Result { + Self::from_zval(&value).ok_or_else(|| Error::ZvalConversion(value.get_type())) + } +} + +impl IntoZval for Binary { + const TYPE: DataType = DataType::String; + + fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> { + zv.set_binary(self.0); + Ok(()) + } +} + +impl From> for Vec { + fn from(value: Binary) -> Self { + value.0 + } +} + +impl From> for Binary { + fn from(value: Vec) -> Self { + Self::new(value) + } +} + +impl FromIterator for Binary { + fn from_iter>(iter: U) -> Self { + Self(iter.into_iter().collect::>()) + } +} + +/// Used to convert between Zend binary strings and vectors. Useful in +/// conjunction with the [`pack`] and [`unpack`] functions built-in to PHP. +/// +/// # Safety +/// +/// The types cannot be ensured between PHP and Rust, as the data is represented +/// as a string when crossing the language boundary. Exercise caution when using +/// these functions. +/// +/// [`pack`]: https://www.php.net/manual/en/function.pack.php +/// [`unpack`]: https://www.php.net/manual/en/function.unpack.php +pub unsafe trait Pack: Clone { + /// Packs a given vector into a Zend binary string. Can be passed to PHP and + /// then unpacked using the [`unpack`] function. + /// + /// # Parameters + /// + /// * `vec` - The vector to pack into a binary string. + /// + /// [`unpack`]: https://www.php.net/manual/en/function.unpack.php + fn pack_into(vec: Vec) -> *mut zend_string; + + /// Unpacks a given Zend binary string into a Rust vector. Can be used to + /// pass data from `pack` in PHP to Rust without encoding into another + /// format. Note that the data *must* be all one type, as this + /// implementation only unpacks one type. + /// + /// # Safety + /// + /// There is no way to tell if the data stored in the string is actually of + /// the given type. The results of this function can also differ from + /// platform-to-platform due to the different representation of some + /// types on different platforms. Consult the [`pack`] function + /// documentation for more details. + /// + /// # Parameters + /// + /// * `s` - The Zend string containing the binary data. + /// + /// [`pack`]: https://www.php.net/manual/en/function.pack.php + fn unpack_into(s: &zend_string) -> Vec; +} + +/// Implements the [`Pack`] trait for a given type. +macro_rules! pack_impl { + ($t: ty) => { + pack_impl!($t, <$t>::BITS); + }; + + ($t: ty, $d: expr) => { + unsafe impl Pack for $t { + fn pack_into(vec: Vec) -> *mut zend_string { + let len = vec.len() * ($d as usize / 8); + let ptr = Box::into_raw(vec.into_boxed_slice()); + unsafe { ext_php_rs_zend_string_init(ptr as *mut i8, len as _, false) } + } + + fn unpack_into(s: &zend_string) -> Vec { + let bytes = ($d / 8) as u64; + let len = (s.len as u64) / bytes; + let mut result = Vec::with_capacity(len as _); + let ptr = s.val.as_ptr() as *const $t; + + // SAFETY: We calculate the length of memory that we can legally read based on + // the side of the type, therefore we never read outside the memory we + // should. + for i in 0..len { + result.push(unsafe { *ptr.offset(i as _) }); + } + + result + } + } + }; +} + +pack_impl!(u8); +pack_impl!(i8); + +pack_impl!(u16); +pack_impl!(i16); + +pack_impl!(u32); +pack_impl!(i32); + +pack_impl!(u64); +pack_impl!(i64); + +pack_impl!(isize); +pack_impl!(usize); + +pack_impl!(f32, 32); +pack_impl!(f64, 64); diff --git a/src/php/boxed.rs b/src/boxed.rs similarity index 64% rename from src/php/boxed.rs rename to src/boxed.rs index 7db06d0774..ae076160c2 100644 --- a/src/php/boxed.rs +++ b/src/boxed.rs @@ -1,20 +1,24 @@ //! A pointer type for heap allocation using the Zend memory manager. //! -//! Heap memory in PHP is usually request-bound and allocated inside [memory arenas], which are cleared -//! at the start and end of a PHP request. Allocating and freeing memory that is allocated on the Zend -//! heap is done through two separate functions [`efree`] and [`emalloc`]. +//! Heap memory in PHP is usually request-bound and allocated inside [memory +//! arenas], which are cleared at the start and end of a PHP request. Allocating +//! and freeing memory that is allocated on the Zend heap is done through two +//! separate functions [`efree`] and [`emalloc`]. //! -//! As such, most heap-allocated PHP types **cannot** be allocated on the stack, such as [`ZendStr`], which -//! is a dynamically-sized type, and therefore must be allocated on the heap. A regular [`Box`] would not -//! work in this case, as the memory needs to be freed from a separate function `zend_string_release`. The -//! [`ZBox`] type provides a wrapper which calls the relevant release functions based on the type and what is -//! inside the implementation of [`ZBoxable`]. +//! As such, most heap-allocated PHP types **cannot** be allocated on the stack, +//! such as [`ZendStr`], which is a dynamically-sized type, and therefore must +//! be allocated on the heap. A regular [`Box`] would not work in this case, as +//! the memory needs to be freed from a separate function `zend_string_release`. +//! The [`ZBox`] type provides a wrapper which calls the relevant release +//! functions based on the type and what is inside the implementation of +//! [`ZBoxable`]. //! -//! This type is not created directly, but rather through a function implemented on the downstream type. For -//! example, [`ZendStr`] has a function `new` which returns a [`ZBox`]. +//! This type is not created directly, but rather through a function implemented +//! on the downstream type. For example, [`ZendStr`] has a function `new` which +//! returns a [`ZBox`]. //! //! [memory arenas]: https://en.wikipedia.org/wiki/Region-based_memory_management -//! [`ZendStr`]: super::types::string::ZendStr +//! [`ZendStr`]: crate::types::ZendStr //! [`emalloc`]: super::alloc::efree use std::{ @@ -41,21 +45,24 @@ impl ZBox { /// /// # Safety /// - /// Caller must ensure that `ptr` is non-null, well-aligned and pointing to a `T`. + /// Caller must ensure that `ptr` is non-null, well-aligned and pointing to + /// a `T`. pub unsafe fn from_raw(ptr: *mut T) -> Self { Self(NonNull::new_unchecked(ptr)) } - /// Returns the pointer contained by the box, dropping the box in the process. The data pointed to by - /// the returned pointer is not released. + /// Returns the pointer contained by the box, dropping the box in the + /// process. The data pointed to by the returned pointer is not + /// released. /// /// # Safety /// - /// The caller is responsible for managing the memory pointed to by the returned pointer, including - /// freeing the memory. + /// The caller is responsible for managing the memory pointed to by the + /// returned pointer, including freeing the memory. pub fn into_raw(self) -> &'static mut T { let mut this = ManuallyDrop::new(self); - // SAFETY: All constructors ensure the contained pointer is well-aligned and dereferencable. + // SAFETY: All constructors ensure the contained pointer is well-aligned and + // dereferencable. unsafe { this.0.as_mut() } } } @@ -72,7 +79,8 @@ impl Deref for ZBox { #[inline] fn deref(&self) -> &Self::Target { - // SAFETY: All constructors ensure the contained pointer is well-aligned and dereferencable. + // SAFETY: All constructors ensure the contained pointer is well-aligned and + // dereferencable. unsafe { self.0.as_ref() } } } @@ -80,7 +88,8 @@ impl Deref for ZBox { impl DerefMut for ZBox { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { - // SAFETY: All constructors ensure the contained pointer is well-aligned and dereferencable. + // SAFETY: All constructors ensure the contained pointer is well-aligned and + // dereferencable. unsafe { self.0.as_mut() } } } @@ -106,20 +115,23 @@ impl AsRef for ZBox { } } -/// Implemented on types that can be heap allocated using the Zend memory manager. These types are stored -/// inside a [`ZBox`] when heap-allocated, and the [`free`] method is called when the box is dropped. +/// Implemented on types that can be heap allocated using the Zend memory +/// manager. These types are stored inside a [`ZBox`] when heap-allocated, and +/// the [`free`] method is called when the box is dropped. /// /// # Safety /// -/// The default implementation of the [`free`] function uses the [`efree`] function to free the memory without -/// calling any destructors. +/// The default implementation of the [`free`] function uses the [`efree`] +/// function to free the memory without calling any destructors. /// -/// The implementor must ensure that any time a pointer to the implementor is passed into a [`ZBox`] that the -/// memory pointed to was allocated by the Zend memory manager. +/// The implementor must ensure that any time a pointer to the implementor is +/// passed into a [`ZBox`] that the memory pointed to was allocated by the Zend +/// memory manager. /// /// [`free`]: #method.free pub unsafe trait ZBoxable { - /// Frees the memory pointed to by `self`, calling any destructors required in the process. + /// Frees the memory pointed to by `self`, calling any destructors required + /// in the process. fn free(&mut self) { unsafe { efree(self as *mut _ as *mut u8) }; } diff --git a/src/php/class.rs b/src/builders/class.rs similarity index 58% rename from src/php/class.rs rename to src/builders/class.rs index 98e70bb0a0..5d9267b430 100644 --- a/src/php/class.rs +++ b/src/builders/class.rs @@ -1,135 +1,24 @@ -//! Builder and objects for creating classes in the PHP world. +use std::{ffi::CString, mem::MaybeUninit}; use crate::{ - errors::{Error, Result}, - php::{ - exceptions::PhpException, - execution_data::ExecutionData, - function::FunctionBuilder, - types::object::{ConstructorMeta, ConstructorResult, ZendClassObject, ZendObject}, + builders::FunctionBuilder, + class::{ConstructorMeta, ConstructorResult, RegisteredClass}, + convert::IntoZval, + error::{Error, Result}, + exception::PhpException, + ffi::{ + zend_declare_class_constant, zend_declare_property, zend_do_implement_interface, + zend_register_internal_class_ex, }, -}; -use std::{alloc::Layout, convert::TryInto, ffi::CString, fmt::Debug, ops::DerefMut}; - -use crate::bindings::{ - zend_class_entry, zend_declare_class_constant, zend_declare_property, - zend_do_implement_interface, zend_register_internal_class_ex, -}; - -use super::{ flags::{ClassFlags, MethodFlags, PropertyFlags}, - function::FunctionEntry, - globals::ExecutorGlobals, - types::{ - object::RegisteredClass, - string::ZendStr, - zval::{IntoZval, Zval}, - }, + types::{ZendClassObject, ZendObject, ZendStr, Zval}, + zend::{ClassEntry, ExecuteData, FunctionEntry}, }; -/// A Zend class entry. Alias. -pub type ClassEntry = zend_class_entry; - -impl PartialEq for ClassEntry { - fn eq(&self, other: &Self) -> bool { - std::ptr::eq(self, other) - } -} - -impl ClassEntry { - /// Attempts to find a reference to a class in the global class table. - /// - /// Returns a reference to the class if found, or [`None`] if the class could - /// not be found or the class table has not been initialized. - pub fn try_find(name: &str) -> Option<&'static Self> { - ExecutorGlobals::get().class_table()?; - let mut name = ZendStr::new(name, false).ok()?; - - unsafe { - crate::bindings::zend_lookup_class_ex(name.deref_mut(), std::ptr::null_mut(), 0) - .as_ref() - } - } - - /// Returns the class flags. - pub fn flags(&self) -> ClassFlags { - ClassFlags::from_bits_truncate(self.ce_flags) - } - - /// Returns `true` if the class entry is an interface, and `false` otherwise. - pub fn is_interface(&self) -> bool { - self.flags().contains(ClassFlags::Interface) - } - - /// Checks if the class is an instance of another class or interface. - /// - /// # Parameters - /// - /// * `ce` - The inherited class entry to check. - pub fn instance_of(&self, ce: &ClassEntry) -> bool { - if self == ce { - return true; - } - - if ce.flags().contains(ClassFlags::Interface) { - let interfaces = match self.interfaces() { - Some(interfaces) => interfaces, - None => return false, - }; - - for i in interfaces { - if ce == i { - return true; - } - } - } else { - loop { - let parent = match self.parent() { - Some(parent) => parent, - None => return false, - }; - - if parent == ce { - return true; - } - } - } - - false - } - - /// Returns an iterator of all the interfaces that the class implements. Returns [`None`] if - /// the interfaces have not been resolved on the class. - pub fn interfaces(&self) -> Option> { - self.flags() - .contains(ClassFlags::ResolvedInterfaces) - .then(|| unsafe { - (0..self.num_interfaces) - .into_iter() - .map(move |i| *self.__bindgen_anon_3.interfaces.offset(i as _)) - .filter_map(|ptr| ptr.as_ref()) - }) - } - - /// Returns the parent of the class. - /// - /// If the parent of the class has not been resolved, it attempts to find the parent by name. - /// Returns [`None`] if the parent was not resolved and the parent was not able to be found - /// by name. - pub fn parent(&self) -> Option<&Self> { - if self.flags().contains(ClassFlags::ResolvedParent) { - unsafe { self.__bindgen_anon_1.parent.as_ref() } - } else { - let name = unsafe { self.__bindgen_anon_1.parent_name.as_ref()? }; - Self::try_find(name.as_str()?) - } - } -} - -/// Builds a class to be exported as a PHP class. +/// Builder for registering a class in PHP. pub struct ClassBuilder { name: String, - ptr: &'static mut ClassEntry, + ptr: Box, extends: Option<&'static ClassEntry>, interfaces: Vec<&'static ClassEntry>, methods: Vec, @@ -145,16 +34,10 @@ impl ClassBuilder { /// # Parameters /// /// * `name` - The name of the class. - #[allow(clippy::unwrap_used)] pub fn new>(name: T) -> Self { - // SAFETY: Allocating temporary class entry. Will return a null-ptr if allocation fails, - // which will cause the program to panic (standard in Rust). Unwrapping is OK - the ptr - // will either be valid or null. - let ptr = unsafe { - (std::alloc::alloc_zeroed(Layout::new::()) as *mut ClassEntry) - .as_mut() - .unwrap() - }; + // SAFETY: A zeroed class entry is in an initalized state, as it is a raw C type + // whose fields do not have a drop implementation. + let ptr = unsafe { Box::new(MaybeUninit::zeroed().assume_init()) }; Self { name: name.into(), @@ -208,8 +91,9 @@ impl ClassBuilder { self } - /// Adds a property to the class. The initial type of the property is given by the type - /// of the given default. Note that the user can change the type. + /// Adds a property to the class. The initial type of the property is given + /// by the type of the given default. Note that the user can change the + /// type. /// /// # Parameters /// @@ -219,7 +103,8 @@ impl ClassBuilder { /// /// # Panics /// - /// Function will panic if the given `default` cannot be converted into a [`Zval`]. + /// Function will panic if the given `default` cannot be converted into a + /// [`Zval`]. pub fn property>( mut self, name: T, @@ -235,10 +120,11 @@ impl ClassBuilder { self } - /// Adds a constant to the class. The type of the constant is defined by the type of the given - /// default. + /// Adds a constant to the class. The type of the constant is defined by the + /// type of the given default. /// - /// Returns a result containing the class builder if the constant was successfully added. + /// Returns a result containing the class builder if the constant was + /// successfully added. /// /// # Parameters /// @@ -261,27 +147,29 @@ impl ClassBuilder { self } - /// Overrides the creation of the Zend object which will represent an instance - /// of this class. + /// Overrides the creation of the Zend object which will represent an + /// instance of this class. /// /// # Parameters /// - /// * `T` - The type which will override the Zend object. Must implement [`RegisteredClass`] - /// which can be derived using the [`php_class`](crate::php_class) attribute macro. + /// * `T` - The type which will override the Zend object. Must implement + /// [`RegisteredClass`] + /// which can be derived using the [`php_class`](crate::php_class) attribute + /// macro. /// /// # Panics /// - /// Panics if the class name associated with `T` is not the same as the class name specified - /// when creating the builder. + /// Panics if the class name associated with `T` is not the same as the + /// class name specified when creating the builder. pub fn object_override(mut self) -> Self { extern "C" fn create_object(_: *mut ClassEntry) -> *mut ZendObject { - // SAFETY: After calling this function, PHP will always call the constructor defined below, - // which assumes that the object is uninitialized. + // SAFETY: After calling this function, PHP will always call the constructor + // defined below, which assumes that the object is uninitialized. let obj = unsafe { ZendClassObject::::new_uninit() }; obj.into_raw().get_mut_zend_obj() } - extern "C" fn constructor(ex: &mut ExecutionData, _: &mut Zval) { + extern "C" fn constructor(ex: &mut ExecuteData, _: &mut Zval) { let ConstructorMeta { constructor, .. } = match T::CONSTRUCTOR { Some(c) => c, None => { @@ -345,7 +233,7 @@ impl ClassBuilder { let class = unsafe { zend_register_internal_class_ex( - self.ptr, + self.ptr.as_mut(), match self.extends { Some(ptr) => (ptr as *const _) as *mut _, None => std::ptr::null_mut(), @@ -355,11 +243,6 @@ impl ClassBuilder { .ok_or(Error::InvalidPointer)? }; - // SAFETY: We allocated memory for this pointer in `new`, so it is our job to free it when the builder has finished. - unsafe { - std::alloc::dealloc((self.ptr as *mut _) as *mut u8, Layout::new::()) - }; - for iface in self.interfaces { unsafe { zend_do_implement_interface(class, std::mem::transmute(iface)) }; } @@ -395,22 +278,3 @@ impl ClassBuilder { Ok(class) } } - -impl Debug for ClassEntry { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let name: String = unsafe { self.name.as_ref() } - .and_then(|s| s.try_into().ok()) - .ok_or(std::fmt::Error)?; - - f.debug_struct("ClassEntry") - .field("name", &name) - .field("flags", &self.flags()) - .field("is_interface", &self.is_interface()) - .field( - "interfaces", - &self.interfaces().map(|iter| iter.collect::>()), - ) - .field("parent", &self.parent()) - .finish() - } -} diff --git a/src/php/function.rs b/src/builders/function.rs similarity index 77% rename from src/php/function.rs rename to src/builders/function.rs index 9dd5d00853..d448d01ba6 100644 --- a/src/php/function.rs +++ b/src/builders/function.rs @@ -1,46 +1,19 @@ -//! Builder and objects used to create functions and methods in PHP. - -use std::{ffi::CString, mem, os::raw::c_char, ptr}; - -use crate::errors::Result; -use crate::{bindings::zend_function_entry, errors::Error}; - -use super::{ +use crate::{ args::{Arg, ArgInfo}, - enums::DataType, - execution_data::ExecutionData, - types::zval::Zval, - types::ZendType, + error::{Error, Result}, + flags::DataType, + types::Zval, + zend::{ExecuteData, FunctionEntry, ZendType}, }; - -/// A Zend function entry. Alias. -pub type FunctionEntry = zend_function_entry; - -impl FunctionEntry { - /// Returns an empty function entry, signifing the end of a function list. - pub fn end() -> Self { - Self { - fname: ptr::null() as *const c_char, - handler: None, - arg_info: ptr::null(), - num_args: 0, - flags: 0, - } - } - - /// Converts the function entry into a raw and pointer, releasing it to the C world. - pub fn into_raw(self) -> *mut Self { - Box::into_raw(Box::new(self)) - } -} +use std::{ffi::CString, mem, ptr}; /// Function representation in Rust. -pub type FunctionHandler = extern "C" fn(execute_data: &mut ExecutionData, retval: &mut Zval); +pub type FunctionHandler = extern "C" fn(execute_data: &mut ExecuteData, retval: &mut Zval); /// Function representation in Rust using pointers. -type FunctionPointerHandler = extern "C" fn(execute_data: *mut ExecutionData, retval: *mut Zval); +type FunctionPointerHandler = extern "C" fn(execute_data: *mut ExecuteData, retval: *mut Zval); -/// Builds a function to be exported as a PHP function. +/// Builder for registering a function in PHP. #[derive(Debug)] pub struct FunctionBuilder<'a> { name: String, @@ -59,12 +32,15 @@ impl<'a> FunctionBuilder<'a> { /// # Parameters /// /// * `name` - The name of the function. - /// * `handler` - The handler to be called when the function is invoked from PHP. + /// * `handler` - The handler to be called when the function is invoked from + /// PHP. pub fn new>(name: T, handler: FunctionHandler) -> Self { Self { name: name.into(), function: FunctionEntry { fname: ptr::null(), + // SAFETY: `*mut T` and `&mut T` have the same ABI as long as `*mut T` is non-null, + // aligned and pointing to a `T`. PHP guarantees that these conditions will be met. handler: Some(unsafe { mem::transmute::(handler) }), @@ -85,7 +61,8 @@ impl<'a> FunctionBuilder<'a> { /// /// # Parameters /// - /// * `handler` - The handler to be called when the function is invoked from PHP. + /// * `handler` - The handler to be called when the function is invoked from + /// PHP. pub fn constructor(handler: FunctionHandler) -> Self { Self::new("__construct", handler) } diff --git a/src/builders/mod.rs b/src/builders/mod.rs new file mode 100644 index 0000000000..66ad38942b --- /dev/null +++ b/src/builders/mod.rs @@ -0,0 +1,10 @@ +//! Structures that are used to construct other, more complicated types. +//! Generally zero-cost abstractions. + +mod class; +mod function; +mod module; + +pub use class::ClassBuilder; +pub use function::FunctionBuilder; +pub use module::ModuleBuilder; diff --git a/src/php/module.rs b/src/builders/module.rs similarity index 79% rename from src/php/module.rs rename to src/builders/module.rs index ae5c44a4d2..265e71580e 100644 --- a/src/php/module.rs +++ b/src/builders/module.rs @@ -1,32 +1,22 @@ -//! Builder and objects for creating modules in PHP. A module is the base of a PHP extension. +use crate::{ + error::Result, + ffi::{ext_php_rs_php_build_id, USING_ZTS, ZEND_DEBUG, ZEND_MODULE_API_NO}, + zend::{FunctionEntry, ModuleEntry}, +}; use std::{ ffi::{c_void, CString}, mem, ptr, }; -use crate::{ - bindings::{ - ext_php_rs_php_build_id, zend_module_entry, USING_ZTS, ZEND_DEBUG, ZEND_MODULE_API_NO, - }, - errors::Result, -}; - -use super::function::FunctionEntry; - -/// A Zend module entry. Alias. -pub type ModuleEntry = zend_module_entry; -/// A function to be called when the extension is starting up or shutting down. -pub type StartupShutdownFunc = extern "C" fn(_type: i32, _module_number: i32) -> i32; -/// A function to be called when `phpinfo();` is called. -pub type InfoFunc = extern "C" fn(zend_module: *mut ModuleEntry); - -/// Builds a Zend extension. Must be called from within an external function called `get_module`, -/// returning a mutable pointer to a `ModuleEntry`. +/// Builds a Zend module extension to be registered with PHP. Must be called +/// from within an external function called `get_module`, returning a mutable +/// pointer to a `ModuleEntry`. /// /// ``` /// use ext_php_rs::{ -/// php::module::{ModuleEntry, ModuleBuilder}, +/// builders::ModuleBuilder, +/// zend::ModuleEntry, /// info_table_start, info_table_end, info_table_row /// }; /// @@ -60,7 +50,7 @@ impl ModuleBuilder { /// # Arguments /// /// * `name` - The name of the extension. - /// * `version` - The current version of the extension. TBD: Deprecate in favour of the `Cargo.toml` version? + /// * `version` - The current version of the extension. pub fn new, U: Into>(name: T, version: U) -> Self { Self { name: name.into(), @@ -84,7 +74,7 @@ impl ModuleBuilder { #[cfg(not(php_zts))] globals_ptr: ptr::null::() as *mut c_void, #[cfg(php_zts)] - globals_id_ptr: ptr::null::() as *mut crate::bindings::ts_rsrc_id, + globals_id_ptr: ptr::null::() as *mut crate::ffi::ts_rsrc_id, globals_ctor: None, globals_dtor: None, post_deactivate_func: None, @@ -142,7 +132,8 @@ impl ModuleBuilder { /// /// # Arguments /// - /// * `func` - The function to be called to retrieve the information about the extension. + /// * `func` - The function to be called to retrieve the information about + /// the extension. pub fn info_function(mut self, func: InfoFunc) -> Self { self.module.info_func = Some(func); self @@ -172,20 +163,8 @@ impl ModuleBuilder { } } -impl ModuleEntry { - /// Converts the module entry into a raw pointer, releasing it to the C world. - pub fn into_raw(self) -> *mut Self { - Box::into_raw(Box::new(self)) - } -} +/// A function to be called when the extension is starting up or shutting down. +pub type StartupShutdownFunc = extern "C" fn(_type: i32, _module_number: i32) -> i32; -/// Called by startup functions registered with the `#[php_startup]` macro. Initializes all -/// classes that are defined by ext-php-rs (i.e. [`Closure`]). -/// -/// [`Closure`]: ext_php_rs::php::types::closure::Closure -#[doc(hidden)] -#[inline(always)] -pub fn ext_php_rs_startup() { - #[cfg(feature = "closure")] - crate::php::types::closure::Closure::build(); -} +/// A function to be called when `phpinfo();` is called. +pub type InfoFunc = extern "C" fn(zend_module: *mut ModuleEntry); diff --git a/src/class.rs b/src/class.rs new file mode 100644 index 0000000000..8c0f9aa8ab --- /dev/null +++ b/src/class.rs @@ -0,0 +1,211 @@ +//! Types and traits used for registering classes with PHP. + +use std::{ + collections::HashMap, + marker::PhantomData, + mem::MaybeUninit, + sync::atomic::{AtomicBool, AtomicPtr, Ordering}, +}; + +use crate::{ + builders::FunctionBuilder, + convert::{FromZval, IntoZval}, + exception::PhpException, + props::Property, + types::ZendClassObject, + zend::{ClassEntry, ExecuteData, ZendObjectHandlers}, +}; + +/// Implemented on Rust types which are exported to PHP. Allows users to get and +/// set PHP properties on the object. +pub trait RegisteredClass: Sized +where + Self: 'static, +{ + /// PHP class name of the registered class. + const CLASS_NAME: &'static str; + + /// Optional class constructor. + const CONSTRUCTOR: Option> = None; + + /// Returns a reference to the class metadata, which stores the class entry + /// and handlers. + /// + /// This must be statically allocated, and is usually done through the + /// [`macro@php_class`] macro. + /// + /// [`macro@php_class`]: crate::php_class + fn get_metadata() -> &'static ClassMetadata; + + /// Attempts to retrieve a property from the class object. + /// + /// # Parameters + /// + /// * `name` - The name of the property. + /// + /// # Returns + /// + /// Returns a given type `T` inside an option which is the value of the + /// zval, or [`None`] if the property could not be found. + /// + /// # Safety + /// + /// Caller must guarantee that the object the function is called on is + /// immediately followed by a [`ZendObject`], which is true when the + /// object was instantiated by PHP. + /// + /// [`ZendObject`]: crate::types::ZendObject + unsafe fn get_property<'a, T: FromZval<'a>>(&'a self, name: &str) -> Option { + let obj = ZendClassObject::::from_obj_ptr(self)?; + obj.std.get_property(name).ok() + } + + /// Attempts to set the value of a property on the class object. + /// + /// # Parameters + /// + /// * `name` - The name of the property to set. + /// * `value` - The value to set the property to. + /// + /// # Returns + /// + /// Returns nothing in an option if the property was successfully set. + /// Returns none if setting the value failed. + /// + /// # Safety + /// + /// Caller must guarantee that the object the function is called on is + /// immediately followed by a [`ZendObject`], which is true when the + /// object was instantiated by PHP. + /// + /// [`ZendObject`]: crate::types::ZendObject + unsafe fn set_property(&mut self, name: &str, value: impl IntoZval) -> Option<()> { + let obj = ZendClassObject::::from_obj_ptr(self)?; + obj.std.set_property(name, value).ok()?; + Some(()) + } + + /// Returns a hash table containing the properties of the class. + /// + /// The key should be the name of the property and the value should be a + /// reference to the property with reference to `self`. The value is a + /// [`Property`]. + fn get_properties<'a>() -> HashMap<&'static str, Property<'a, Self>>; +} + +/// Stores metadata about a classes Rust constructor, including the function +/// pointer and the arguments of the function. +pub struct ConstructorMeta { + /// Constructor function. + pub constructor: fn(&mut ExecuteData) -> ConstructorResult, + /// Function called to build the constructor function. Usually adds + /// arguments. + pub build_fn: fn(FunctionBuilder) -> FunctionBuilder, +} + +/// Result returned from a constructor of a class. +pub enum ConstructorResult { + /// Successfully constructed the class, contains the new class object. + Ok(T), + /// An exception occured while constructing the class. + Exception(PhpException), + /// Invalid arguments were given to the constructor. + ArgError, +} + +impl From> for ConstructorResult +where + E: Into, +{ + fn from(result: std::result::Result) -> Self { + match result { + Ok(x) => Self::Ok(x), + Err(e) => Self::Exception(e.into()), + } + } +} + +impl From for ConstructorResult { + fn from(result: T) -> Self { + Self::Ok(result) + } +} + +/// Stores the class entry and handlers for a Rust type which has been exported +/// to PHP. Usually allocated statically. +pub struct ClassMetadata { + handlers_init: AtomicBool, + handlers: MaybeUninit, + ce: AtomicPtr, + + phantom: PhantomData, +} + +impl ClassMetadata { + /// Creates a new class metadata instance. + pub const fn new() -> Self { + Self { + handlers_init: AtomicBool::new(false), + handlers: MaybeUninit::uninit(), + ce: AtomicPtr::new(std::ptr::null_mut()), + phantom: PhantomData, + } + } +} + +impl ClassMetadata { + /// Returns an immutable reference to the object handlers contained inside + /// the class metadata. + pub fn handlers(&self) -> &ZendObjectHandlers { + self.check_handlers(); + + // SAFETY: `check_handlers` guarantees that `handlers` has been initialized. + unsafe { &*self.handlers.as_ptr() } + } + + /// Checks if the class entry has been stored, returning a boolean. + pub fn has_ce(&self) -> bool { + !self.ce.load(Ordering::SeqCst).is_null() + } + + /// Retrieves a reference to the stored class entry. + /// + /// # Panics + /// + /// Panics if there is no class entry stored inside the class metadata. + pub fn ce(&self) -> &'static ClassEntry { + // SAFETY: There are only two values that can be stored in the atomic ptr: null + // or a static reference to a class entry. On the latter case, + // `as_ref()` will return `None` and the function will panic. + unsafe { self.ce.load(Ordering::SeqCst).as_ref() } + .expect("Attempted to retrieve class entry before it has been stored.") + } + + /// Stores a reference to a class entry inside the class metadata. + /// + /// # Parameters + /// + /// * `ce` - The class entry to store. + /// + /// # Panics + /// + /// Panics if the class entry has already been set in the class metadata. + /// This function should only be called once. + pub fn set_ce(&self, ce: &'static mut ClassEntry) { + if !self.ce.load(Ordering::SeqCst).is_null() { + panic!("Class entry has already been set."); + } + + self.ce.store(ce, Ordering::SeqCst); + } + + /// Checks if the handlers have been initialized, and initializes them if + /// they are not. + fn check_handlers(&self) { + if !self.handlers_init.load(Ordering::SeqCst) { + // SAFETY: `MaybeUninit` has the same size as the handlers. + unsafe { ZendObjectHandlers::init::(self.handlers.as_ptr() as *mut _) }; + self.handlers_init.store(true, Ordering::SeqCst); + } + } +} diff --git a/src/php/types/closure.rs b/src/closure.rs similarity index 79% rename from src/php/types/closure.rs rename to src/closure.rs index 72c3a74535..9767d9d3cf 100644 --- a/src/php/types/closure.rs +++ b/src/closure.rs @@ -1,22 +1,17 @@ -//! Types ans functions used for exporting Rust closures to PHP. +//! Types and functions used for exporting Rust closures to PHP. use std::collections::HashMap; -use crate::php::{ +use crate::{ args::{Arg, ArgParser}, - class::ClassBuilder, - enums::DataType, - exceptions::PhpException, - execution_data::ExecutionData, - flags::MethodFlags, - function::FunctionBuilder, - types::object::ClassMetadata, -}; - -use super::{ - object::RegisteredClass, + builders::{ClassBuilder, FunctionBuilder}, + class::{ClassMetadata, RegisteredClass}, + convert::{FromZval, IntoZval}, + exception::PhpException, + flags::{DataType, MethodFlags}, props::Property, - zval::{FromZval, IntoZval, Zval}, + types::Zval, + zend::ExecuteData, }; /// Class entry and handlers for Rust closures. @@ -24,12 +19,12 @@ static CLOSURE_META: ClassMetadata = ClassMetadata::new(); /// Wrapper around a Rust closure, which can be exported to PHP. /// -/// Closures can have up to 8 parameters, all must implement [`FromZval`], and can return anything -/// that implements [`IntoZval`]. Closures must have a static lifetime, and therefore cannot modify -/// any `self` references. +/// Closures can have up to 8 parameters, all must implement [`FromZval`], and +/// can return anything that implements [`IntoZval`]. Closures must have a +/// static lifetime, and therefore cannot modify any `self` references. /// -/// Internally, closures are implemented as a PHP class. A class `RustClosure` is registered with an -/// `__invoke` method: +/// Internally, closures are implemented as a PHP class. A class `RustClosure` +/// is registered with an `__invoke` method: /// /// ```php /// = ClassMetadata::new(); /// } /// ``` /// -/// The Rust closure is then double boxed, firstly as a `Box ...>` (depending on the -/// signature of the closure) and then finally boxed as a `Box`. This is a workaround, -/// as `PhpClosure` is not generically implementable on types that implement `Fn(T, ...) -> Ret`. Make +/// The Rust closure is then double boxed, firstly as a `Box +/// ...>` (depending on the signature of the closure) and then finally boxed as +/// a `Box`. This is a workaround, as `PhpClosure` is not +/// generically implementable on types that implement `Fn(T, ...) -> Ret`. Make /// a suggestion issue if you have a better idea of implementing this!. /// -/// When the `__invoke` method is called from PHP, the `invoke` method is called on the `dyn PhpClosure`\ -/// trait object, and from there everything is basically the same as a regular PHP function. +/// When the `__invoke` method is called from PHP, the `invoke` method is called +/// on the `dyn PhpClosure`\ trait object, and from there everything is +/// basically the same as a regular PHP function. pub struct Closure(Box); unsafe impl Send for Closure {} unsafe impl Sync for Closure {} impl Closure { - /// Wraps a [`Fn`] or [`FnMut`] Rust closure into a type which can be returned to PHP. + /// Wraps a [`Fn`] or [`FnMut`] Rust closure into a type which can be + /// returned to PHP. /// - /// The closure can accept up to 8 arguments which implement [`IntoZval`], and can return any - /// type which implements [`FromZval`]. The closure must have a static lifetime, so cannot - /// reference `self`. + /// The closure can accept up to 8 arguments which implement [`IntoZval`], + /// and can return any type which implements [`FromZval`]. The closure + /// must have a static lifetime, so cannot reference `self`. /// /// # Parameters /// - /// * `func` - The closure to wrap. Should be boxed in the form `Box ...>`. + /// * `func` - The closure to wrap. Should be boxed in the form `Box ...>`. /// /// # Example /// /// ```rust,no_run - /// use ext_php_rs::php::types::closure::Closure; + /// use ext_php_rs::closure::Closure; /// /// let closure = Closure::wrap(Box::new(|name| { /// format!("Hello {}", name) @@ -80,21 +79,23 @@ impl Closure { Self(Box::new(func) as Box) } - /// Wraps a [`FnOnce`] Rust closure into a type which can be returned to PHP. If the closure - /// is called more than once from PHP, an exception is thrown. + /// Wraps a [`FnOnce`] Rust closure into a type which can be returned to + /// PHP. If the closure is called more than once from PHP, an exception + /// is thrown. /// - /// The closure can accept up to 8 arguments which implement [`IntoZval`], and can return any - /// type which implements [`FromZval`]. The closure must have a static lifetime, so cannot - /// reference `self`. + /// The closure can accept up to 8 arguments which implement [`IntoZval`], + /// and can return any type which implements [`FromZval`]. The closure + /// must have a static lifetime, so cannot reference `self`. /// /// # Parameters /// - /// * `func` - The closure to wrap. Should be boxed in the form `Box ...>`. + /// * `func` - The closure to wrap. Should be boxed in the form `Box ...>`. /// /// # Example /// /// ```rust,no_run - /// use ext_php_rs::php::types::closure::Closure; + /// use ext_php_rs::closure::Closure; /// /// let name: String = "Hello world".into(); /// let closure = Closure::wrap_once(Box::new(|| { @@ -108,8 +109,9 @@ impl Closure { func.into_closure() } - /// Builds the class entry for [`Closure`], registering it with PHP. This function should - /// only be called once inside your module startup function. + /// Builds the class entry for [`Closure`], registering it with PHP. This + /// function should only be called once inside your module startup + /// function. /// /// # Panics /// @@ -136,7 +138,7 @@ impl Closure { } /// External function used by the Zend interpreter to call the closure. - extern "C" fn invoke(ex: &mut ExecutionData, ret: &mut Zval) { + extern "C" fn invoke(ex: &mut ExecuteData, ret: &mut Zval) { let (parser, this) = ex.parser_method::(); let this = this.expect("Internal closure function called on non-closure class"); @@ -147,7 +149,7 @@ impl Closure { impl RegisteredClass for Closure { const CLASS_NAME: &'static str = "RustClosure"; - fn get_metadata() -> &'static super::object::ClassMetadata { + fn get_metadata() -> &'static ClassMetadata { &CLOSURE_META } @@ -160,22 +162,25 @@ class_derives!(Closure); /// Implemented on types which can be used as PHP closures. /// -/// Types must implement the `invoke` function which will be called when the closure is called -/// from PHP. Arguments must be parsed from the [`ExecutionData`] and the return value is returned -/// through the [`Zval`]. +/// Types must implement the `invoke` function which will be called when the +/// closure is called from PHP. Arguments must be parsed from the +/// [`ExecuteData`] and the return value is returned through the [`Zval`]. /// -/// This trait is automatically implemented on functions with up to 8 parameters. +/// This trait is automatically implemented on functions with up to 8 +/// parameters. pub unsafe trait PhpClosure { /// Invokes the closure. fn invoke<'a>(&'a mut self, parser: ArgParser<'a, '_>, ret: &mut Zval); } -/// Implemented on [`FnOnce`] types which can be used as PHP closures. See [`Closure`]. +/// Implemented on [`FnOnce`] types which can be used as PHP closures. See +/// [`Closure`]. /// -/// Internally, this trait should wrap the [`FnOnce`] closure inside a [`FnMut`] closure, and prevent -/// the user from calling the closure more than once. +/// Internally, this trait should wrap the [`FnOnce`] closure inside a [`FnMut`] +/// closure, and prevent the user from calling the closure more than once. pub trait PhpOnceClosure { - /// Converts the Rust [`FnOnce`] closure into a [`FnMut`] closure, and then into a PHP closure. + /// Converts the Rust [`FnOnce`] closure into a [`FnMut`] closure, and then + /// into a PHP closure. fn into_closure(self) -> Closure; } diff --git a/src/php/constants.rs b/src/constant.rs similarity index 78% rename from src/php/constants.rs rename to src/constant.rs index 9a4d167120..03b5043226 100644 --- a/src/php/constants.rs +++ b/src/constant.rs @@ -1,32 +1,36 @@ -//! Types relating to registering constants in PHP. +//! Types and traits for registering constants in PHP. use std::ffi::CString; use super::flags::GlobalConstantFlags; -use crate::bindings::{ +use crate::error::Result; +use crate::ffi::{ zend_register_bool_constant, zend_register_double_constant, zend_register_long_constant, zend_register_string_constant, }; -use crate::errors::Result; +/// Implemented on types which can be registered as a constant in PHP. pub trait IntoConst: Sized { - /// Registers a global module constant in PHP, with the value as the content of self. - /// This function _must_ be called in the module startup function, which is called after - /// the module is initialized. The second parameter of the startup function will be the - /// module number. By default, the case-insensitive and persistent flags are set when + /// Registers a global module constant in PHP, with the value as the content + /// of self. This function _must_ be called in the module startup + /// function, which is called after the module is initialized. The + /// second parameter of the startup function will be the module number. + /// By default, the case-insensitive and persistent flags are set when /// registering the constant. /// - /// Returns a result containing nothing if the constant was successfully registered. + /// Returns a result containing nothing if the constant was successfully + /// registered. /// /// # Parameters /// /// * `name` - The name of the constant. - /// * `module_number` - The module number that we are registering the constant under. + /// * `module_number` - The module number that we are registering the + /// constant under. /// /// # Examples /// /// ```no_run - /// use ext_php_rs::php::{constants::IntoConst, flags::ZendResult}; + /// use ext_php_rs::constant::IntoConst; /// /// pub extern "C" fn startup_function(_type: i32, module_number: i32) -> i32 { /// 5.register_constant("MY_CONST_NAME", module_number); // MY_CONST_NAME == 5 @@ -42,25 +46,28 @@ pub trait IntoConst: Sized { ) } - /// Registers a global module constant in PHP, with the value as the content of self. - /// This function _must_ be called in the module startup function, which is called after - /// the module is initialized. The second parameter of the startup function will be the - /// module number. This function allows you to pass any extra flags in if you require. - /// Note that the case-sensitive and persistent flags *are not* set when you use this function, - /// you must set these yourself. + /// Registers a global module constant in PHP, with the value as the content + /// of self. This function _must_ be called in the module startup + /// function, which is called after the module is initialized. The + /// second parameter of the startup function will be the module number. + /// This function allows you to pass any extra flags in if you require. + /// Note that the case-sensitive and persistent flags *are not* set when you + /// use this function, you must set these yourself. /// - /// Returns a result containing nothing if the constant was successfully registered. + /// Returns a result containing nothing if the constant was successfully + /// registered. /// /// # Parameters /// /// * `name` - The name of the constant. - /// * `module_number` - The module number that we are registering the constant under. + /// * `module_number` - The module number that we are registering the + /// constant under. /// * `flags` - Flags to register the constant with. /// /// # Examples /// /// ```no_run - /// use ext_php_rs::php::{constants::IntoConst, flags::{GlobalConstantFlags, ZendResult}}; + /// use ext_php_rs::{constant::IntoConst, flags::GlobalConstantFlags}; /// /// pub extern "C" fn startup_function(_type: i32, module_number: i32) -> i32 { /// 42.register_constant_flags("MY_CONST_NAME", module_number, GlobalConstantFlags::Persistent | GlobalConstantFlags::Deprecated); @@ -127,7 +134,8 @@ impl IntoConst for bool { } } -/// Implements the `IntoConst` trait for a given number type using a given function. +/// Implements the `IntoConst` trait for a given number type using a given +/// function. macro_rules! into_const_num { ($type: ty, $fn: expr) => { impl IntoConst for $type { diff --git a/src/convert.rs b/src/convert.rs new file mode 100644 index 0000000000..1b0347726b --- /dev/null +++ b/src/convert.rs @@ -0,0 +1,220 @@ +//! Traits used to convert between Zend/PHP and Rust types. + +use crate::{ + boxed::ZBox, + error::Result, + exception::PhpException, + flags::DataType, + types::{ZendObject, Zval}, +}; + +/// Allows zvals to be converted into Rust types in a fallible way. Reciprocal +/// of the [`IntoZval`] trait. +pub trait FromZval<'a>: Sized { + /// The corresponding type of the implemented value in PHP. + const TYPE: DataType; + + /// Attempts to retrieve an instance of `Self` from a reference to a + /// [`Zval`]. + /// + /// # Parameters + /// + /// * `zval` - Zval to get value from. + fn from_zval(zval: &'a Zval) -> Option; +} + +impl<'a, T> FromZval<'a> for Option +where + T: FromZval<'a>, +{ + const TYPE: DataType = T::TYPE; + + fn from_zval(zval: &'a Zval) -> Option { + Some(T::from_zval(zval)) + } +} + +/// Allows mutable zvals to be converted into Rust types in a fallible way. +/// +/// If `Self` does not require the zval to be mutable to be extracted, you +/// should implement [`FromZval`] instead, as this trait is generically +/// implemented for any type that implements [`FromZval`]. +pub trait FromZvalMut<'a>: Sized { + /// The corresponding type of the implemented value in PHP. + const TYPE: DataType; + + /// Attempts to retrieve an instance of `Self` from a mutable reference to a + /// [`Zval`]. + /// + /// # Parameters + /// + /// * `zval` - Zval to get value from. + fn from_zval_mut(zval: &'a mut Zval) -> Option; +} + +impl<'a, T> FromZvalMut<'a> for T +where + T: FromZval<'a>, +{ + const TYPE: DataType = ::TYPE; + + #[inline] + fn from_zval_mut(zval: &'a mut Zval) -> Option { + Self::from_zval(zval) + } +} + +/// `FromZendObject` is implemented by types which can be extracted from a Zend +/// object. +/// +/// Normal usage is through the helper method `ZendObject::extract`: +/// +/// ```rust,ignore +/// let obj: ZendObject = ...; +/// let repr: String = obj.extract(); +/// let props: HashMap = obj.extract(); +/// ``` +/// +/// Should be functionally equivalent to casting an object to another compatable +/// type. +pub trait FromZendObject<'a>: Sized { + /// Extracts `Self` from the source `ZendObject`. + fn from_zend_object(obj: &'a ZendObject) -> Result; +} + +/// Implemented on types which can be extracted from a mutable zend object. +/// +/// If `Self` does not require the object to be mutable, it should implement +/// [`FromZendObject`] instead, as this trait is generically implemented for +/// any types that also implement [`FromZendObject`]. +pub trait FromZendObjectMut<'a>: Sized { + /// Extracts `Self` from the source `ZendObject`. + fn from_zend_object_mut(obj: &'a mut ZendObject) -> Result; +} + +impl<'a, T> FromZendObjectMut<'a> for T +where + T: FromZendObject<'a>, +{ + #[inline] + fn from_zend_object_mut(obj: &'a mut ZendObject) -> Result { + Self::from_zend_object(obj) + } +} + +/// Implemented on types which can be converted into a Zend object. It is up to +/// the implementation to determine the type of object which is produced. +pub trait IntoZendObject { + /// Attempts to convert `self` into a Zend object. + fn into_zend_object(self) -> Result>; +} + +/// Provides implementations for converting Rust primitive types into PHP zvals. +/// Alternative to the built-in Rust [`From`] and [`TryFrom`] implementations, +/// allowing the caller to specify whether the Zval contents will persist +/// between requests. +/// +/// [`TryFrom`]: std::convert::TryFrom +pub trait IntoZval: Sized { + /// The corresponding type of the implemented value in PHP. + const TYPE: DataType; + + /// Converts a Rust primitive type into a Zval. Returns a result containing + /// the Zval if successful. + /// + /// # Parameters + /// + /// * `persistent` - Whether the contents of the Zval will persist between + /// requests. + fn into_zval(self, persistent: bool) -> Result { + let mut zval = Zval::new(); + self.set_zval(&mut zval, persistent)?; + Ok(zval) + } + + /// Sets the content of a pre-existing zval. Returns a result containing + /// nothing if setting the content was successful. + /// + /// # Parameters + /// + /// * `zv` - The Zval to set the content of. + /// * `persistent` - Whether the contents of the Zval will persist between + /// requests. + fn set_zval(self, zv: &mut Zval, persistent: bool) -> Result<()>; +} + +impl IntoZval for () { + const TYPE: DataType = DataType::Void; + + #[inline] + fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> { + zv.set_null(); + Ok(()) + } +} + +impl IntoZval for Option +where + T: IntoZval, +{ + const TYPE: DataType = T::TYPE; + + #[inline] + fn set_zval(self, zv: &mut Zval, persistent: bool) -> Result<()> { + match self { + Some(val) => val.set_zval(zv, persistent), + None => { + zv.set_null(); + Ok(()) + } + } + } +} + +impl IntoZval for std::result::Result +where + T: IntoZval, + E: Into, +{ + const TYPE: DataType = T::TYPE; + + fn set_zval(self, zv: &mut Zval, persistent: bool) -> Result<()> { + match self { + Ok(val) => val.set_zval(zv, persistent), + Err(e) => { + let ex: PhpException = e.into(); + ex.throw() + } + } + } +} + +/// An object-safe version of the [`IntoZval`] trait. +/// +/// This trait is automatically implemented on any type that implements both +/// [`IntoZval`] and [`Clone`]. You avoid implementing this trait directly, +/// rather implement these two other traits. +pub trait IntoZvalDyn { + /// Converts a Rust primitive type into a Zval. Returns a result containing + /// the Zval if successful. `self` is cloned before being converted into + /// a zval. + /// + /// # Parameters + /// + /// * `persistent` - Whether the contents of the Zval will persist between + /// requests. + fn as_zval(&self, persistent: bool) -> Result; + + /// Returns the PHP type of the type. + fn get_type(&self) -> DataType; +} + +impl IntoZvalDyn for T { + fn as_zval(&self, persistent: bool) -> Result { + self.clone().into_zval(persistent) + } + + fn get_type(&self) -> DataType { + Self::TYPE + } +} diff --git a/src/errors.rs b/src/error.rs similarity index 87% rename from src/errors.rs rename to src/error.rs index 72eccca7ca..eb24096cb9 100644 --- a/src/errors.rs +++ b/src/error.rs @@ -2,10 +2,11 @@ use std::{error::Error as ErrorTrait, ffi::NulError, fmt::Display}; -use crate::php::{ - enums::DataType, - exceptions::PhpException, - flags::{ClassFlags, ZvalTypeFlags}, +use crate::{ + boxed::ZBox, + exception::PhpException, + flags::{ClassFlags, DataType, ZvalTypeFlags}, + types::ZendObject, }; /// The main result type which is passed by the library. @@ -13,7 +14,7 @@ pub type Result = std::result::Result; /// The main error type which is passed by the library inside the custom /// [`Result`] type. -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug)] #[non_exhaustive] pub enum Error { /// An incorrect number of arguments was given to a PHP function. @@ -33,16 +34,19 @@ pub enum Error { /// Attempted to convert a [`ZvalTypeFlags`] struct to a [`DataType`]. /// The flags did not contain a datatype. /// - /// The enum carries the flags that were attempted to be converted to a [`DataType`]. + /// The enum carries the flags that were attempted to be converted to a + /// [`DataType`]. InvalidTypeToDatatype(ZvalTypeFlags), - /// The function called was called in an invalid scope (calling class-related functions - /// inside of a non-class bound function). + /// The function called was called in an invalid scope (calling + /// class-related functions inside of a non-class bound function). InvalidScope, - /// The pointer inside a given type was invalid, either null or pointing to garbage. + /// The pointer inside a given type was invalid, either null or pointing to + /// garbage. InvalidPointer, /// The given property name does not exist. InvalidProperty, - /// The string could not be converted into a C-string due to the presence of a NUL character. + /// The string could not be converted into a C-string due to the presence of + /// a NUL character. InvalidCString, /// Could not call the given function. Callable, @@ -50,6 +54,8 @@ pub enum Error { InvalidException(ClassFlags), /// Converting integer arguments resulted in an overflow. IntegerOverflow, + /// An exception was thrown in a function. + Exception(ZBox), } impl Display for Error { @@ -83,6 +89,7 @@ impl Display for Error { Error::IntegerOverflow => { write!(f, "Converting integer arguments resulted in an overflow.") } + Error::Exception(e) => write!(f, "Exception was thrown: {:?}", e), } } } diff --git a/src/exception.rs b/src/exception.rs new file mode 100644 index 0000000000..7ec2d7060d --- /dev/null +++ b/src/exception.rs @@ -0,0 +1,139 @@ +//! Types and functions used for throwing exceptions from Rust to PHP. + +use std::ffi::CString; + +use crate::{ + class::RegisteredClass, + error::{Error, Result}, + ffi::zend_throw_exception_ex, + flags::ClassFlags, + zend::{ce, ClassEntry}, +}; + +/// Result type with the error variant as a [`PhpException`]. +pub type PhpResult = std::result::Result; + +/// Represents a PHP exception which can be thrown using the `throw()` function. +/// Primarily used to return from a [`Result`] which can +/// immediately be thrown by the `ext-php-rs` macro API. +/// +/// There are default [`From`] implementations for any type that implements +/// [`ToString`], so these can also be returned from these functions. You can +/// also implement [`From`] for your custom error type. +#[derive(Debug)] +pub struct PhpException { + message: String, + code: i32, + ex: &'static ClassEntry, +} + +impl PhpException { + /// Creates a new exception instance. + /// + /// # Parameters + /// + /// * `message` - Message to contain in the exception. + /// * `code` - Integer code to go inside the exception. + /// * `ex` - Exception type to throw. + pub fn new(message: String, code: i32, ex: &'static ClassEntry) -> Self { + Self { message, code, ex } + } + + /// Creates a new default exception instance, using the default PHP + /// `Exception` type as the exception type, with an integer code of + /// zero. + /// + /// # Parameters + /// + /// * `message` - Message to contain in the exception. + pub fn default(message: String) -> Self { + Self::new(message, 0, ce::exception()) + } + + /// Creates an instance of an exception from a PHP class type and a message. + /// + /// # Parameters + /// + /// * `message` - Message to contain in the exception. + pub fn from_class(message: String) -> Self { + Self::new(message, 0, T::get_metadata().ce()) + } + + /// Throws the exception, returning nothing inside a result if successful + /// and an error otherwise. + pub fn throw(self) -> Result<()> { + throw_with_code(self.ex, self.code, &self.message) + } +} + +impl From for PhpException { + fn from(str: String) -> Self { + Self::default(str) + } +} + +impl From<&str> for PhpException { + fn from(str: &str) -> Self { + Self::default(str.into()) + } +} + +/// Throws an exception with a given message. See [`ClassEntry`] for some +/// built-in exception types. +/// +/// Returns a result containing nothing if the exception was successfully thown. +/// +/// # Parameters +/// +/// * `ex` - The exception type to throw. +/// * `message` - The message to display when throwing the exception. +/// +/// # Examples +/// +/// ```no_run +/// use ext_php_rs::{zend::{ce, ClassEntry}, exception::throw}; +/// +/// throw(ce::compile_error(), "This is a CompileError."); +/// ``` +pub fn throw(ex: &ClassEntry, message: &str) -> Result<()> { + throw_with_code(ex, 0, message) +} + +/// Throws an exception with a given message and status code. See [`ClassEntry`] +/// for some built-in exception types. +/// +/// Returns a result containing nothing if the exception was successfully thown. +/// +/// # Parameters +/// +/// * `ex` - The exception type to throw. +/// * `code` - The status code to use when throwing the exception. +/// * `message` - The message to display when throwing the exception. +/// +/// # Examples +/// +/// ```no_run +/// use ext_php_rs::{zend::{ce, ClassEntry}, exception::throw_with_code}; +/// +/// throw_with_code(ce::compile_error(), 123, "This is a CompileError."); +/// ``` +pub fn throw_with_code(ex: &ClassEntry, code: i32, message: &str) -> Result<()> { + let flags = ex.flags(); + + // Can't throw an interface or abstract class. + if flags.contains(ClassFlags::Interface) || flags.contains(ClassFlags::Abstract) { + return Err(Error::InvalidException(flags)); + } + + // SAFETY: We are given a reference to a `ClassEntry` therefore when we cast it + // to a pointer it will be valid. + unsafe { + zend_throw_exception_ex( + (ex as *const _) as *mut _, + code as _, + CString::new("%s")?.as_ptr(), + CString::new(message)?.as_ptr(), + ) + }; + Ok(()) +} diff --git a/src/bindings.rs b/src/ffi.rs similarity index 71% rename from src/bindings.rs rename to src/ffi.rs index 93b67ecd58..46f9e7431a 100644 --- a/src/bindings.rs +++ b/src/ffi.rs @@ -1,4 +1,4 @@ -//! Raw FFI bindings to the PHP API. +//! Raw FFI bindings to the Zend API. #![allow(clippy::all)] #![allow(warnings)] diff --git a/src/flags.rs b/src/flags.rs new file mode 100644 index 0000000000..b9cf4f1498 --- /dev/null +++ b/src/flags.rs @@ -0,0 +1,363 @@ +//! Flags and enums used in PHP and the Zend engine. + +use bitflags::bitflags; + +use crate::ffi::{ + CONST_CS, CONST_DEPRECATED, CONST_NO_FILE_CACHE, CONST_PERSISTENT, IS_ARRAY, IS_CALLABLE, + IS_CONSTANT_AST, IS_DOUBLE, IS_FALSE, IS_LONG, IS_MIXED, IS_NULL, IS_OBJECT, IS_PTR, + IS_REFERENCE, IS_RESOURCE, IS_STRING, IS_TRUE, IS_TYPE_COLLECTABLE, IS_TYPE_REFCOUNTED, + IS_UNDEF, IS_VOID, ZEND_ACC_ABSTRACT, ZEND_ACC_ANON_CLASS, ZEND_ACC_CALL_VIA_TRAMPOLINE, + ZEND_ACC_CHANGED, ZEND_ACC_CLOSURE, ZEND_ACC_CONSTANTS_UPDATED, ZEND_ACC_CTOR, + ZEND_ACC_DEPRECATED, ZEND_ACC_DONE_PASS_TWO, ZEND_ACC_EARLY_BINDING, ZEND_ACC_FAKE_CLOSURE, + ZEND_ACC_FINAL, ZEND_ACC_GENERATOR, ZEND_ACC_HAS_FINALLY_BLOCK, ZEND_ACC_HAS_RETURN_TYPE, + ZEND_ACC_HAS_TYPE_HINTS, ZEND_ACC_HAS_UNLINKED_USES, ZEND_ACC_HEAP_RT_CACHE, + ZEND_ACC_IMMUTABLE, ZEND_ACC_IMPLICIT_ABSTRACT_CLASS, ZEND_ACC_INTERFACE, ZEND_ACC_LINKED, + ZEND_ACC_NEARLY_LINKED, ZEND_ACC_NEVER_CACHE, ZEND_ACC_NO_DYNAMIC_PROPERTIES, + ZEND_ACC_PRELOADED, ZEND_ACC_PRIVATE, ZEND_ACC_PROMOTED, ZEND_ACC_PROPERTY_TYPES_RESOLVED, + ZEND_ACC_PROTECTED, ZEND_ACC_PUBLIC, ZEND_ACC_RESOLVED_INTERFACES, ZEND_ACC_RESOLVED_PARENT, + ZEND_ACC_RETURN_REFERENCE, ZEND_ACC_REUSE_GET_ITERATOR, ZEND_ACC_STATIC, ZEND_ACC_STRICT_TYPES, + ZEND_ACC_TOP_LEVEL, ZEND_ACC_TRAIT, ZEND_ACC_TRAIT_CLONE, ZEND_ACC_UNRESOLVED_VARIANCE, + ZEND_ACC_USES_THIS, ZEND_ACC_USE_GUARDS, ZEND_ACC_VARIADIC, ZEND_HAS_STATIC_IN_METHODS, + Z_TYPE_FLAGS_SHIFT, _IS_BOOL, +}; + +use std::{convert::TryFrom, fmt::Display}; + +use crate::error::{Error, Result}; + +bitflags! { + /// Flags used for setting the type of Zval. + pub struct ZvalTypeFlags: u32 { + const Undef = IS_UNDEF; + const Null = IS_NULL; + const False = IS_FALSE; + const True = IS_TRUE; + const Long = IS_LONG; + const Double = IS_DOUBLE; + const String = IS_STRING; + const Array = IS_ARRAY; + const Object = IS_OBJECT; + const Resource = IS_RESOURCE; + const Reference = IS_REFERENCE; + const Callable = IS_CALLABLE; + const ConstantExpression = IS_CONSTANT_AST; + const Void = IS_VOID; + const Ptr = IS_PTR; + + const InternedStringEx = Self::String.bits; + const StringEx = Self::String.bits | Self::RefCounted.bits; + const ArrayEx = Self::Array.bits | Self::RefCounted.bits | Self::Collectable.bits; + const ObjectEx = Self::Object.bits | Self::RefCounted.bits | Self::Collectable.bits; + const ResourceEx = Self::Resource.bits | Self::RefCounted.bits; + const ReferenceEx = Self::Reference.bits | Self::RefCounted.bits; + const ConstantAstEx = Self::ConstantExpression.bits | Self::RefCounted.bits; + + const RefCounted = (IS_TYPE_REFCOUNTED << Z_TYPE_FLAGS_SHIFT); + const Collectable = (IS_TYPE_COLLECTABLE << Z_TYPE_FLAGS_SHIFT); + } +} + +bitflags! { + /// Flags for building classes. + pub struct ClassFlags: u32 { + const Final = ZEND_ACC_FINAL; + const Abstract = ZEND_ACC_ABSTRACT; + const Immutable = ZEND_ACC_IMMUTABLE; + const HasTypeHints = ZEND_ACC_HAS_TYPE_HINTS; + const TopLevel = ZEND_ACC_TOP_LEVEL; + const Preloaded = ZEND_ACC_PRELOADED; + + const Interface = ZEND_ACC_INTERFACE; + const Trait = ZEND_ACC_TRAIT; + const AnonymousClass = ZEND_ACC_ANON_CLASS; + const Linked = ZEND_ACC_LINKED; + const ImplicitAbstractClass = ZEND_ACC_IMPLICIT_ABSTRACT_CLASS; + const UseGuards = ZEND_ACC_USE_GUARDS; + const ConstantsUpdated = ZEND_ACC_CONSTANTS_UPDATED; + const NoDynamicProperties = ZEND_ACC_NO_DYNAMIC_PROPERTIES; + const HasStaticInMethods = ZEND_HAS_STATIC_IN_METHODS; + const PropertyTypesResolved = ZEND_ACC_PROPERTY_TYPES_RESOLVED; + const ReuseGetIterator = ZEND_ACC_REUSE_GET_ITERATOR; + const ResolvedParent = ZEND_ACC_RESOLVED_PARENT; + const ResolvedInterfaces = ZEND_ACC_RESOLVED_INTERFACES; + const UnresolvedVariance = ZEND_ACC_UNRESOLVED_VARIANCE; + const NearlyLinked = ZEND_ACC_NEARLY_LINKED; + const HasUnlinkedUses = ZEND_ACC_HAS_UNLINKED_USES; + } +} + +bitflags! { + /// Flags for building methods. + pub struct MethodFlags: u32 { + const Public = ZEND_ACC_PUBLIC; + const Protected = ZEND_ACC_PROTECTED; + const Private = ZEND_ACC_PRIVATE; + const Changed = ZEND_ACC_CHANGED; + const Static = ZEND_ACC_STATIC; + const Final = ZEND_ACC_FINAL; + const Abstract = ZEND_ACC_ABSTRACT; + const Immutable = ZEND_ACC_IMMUTABLE; + const HasTypeHints = ZEND_ACC_HAS_TYPE_HINTS; + const TopLevel = ZEND_ACC_TOP_LEVEL; + const Preloaded = ZEND_ACC_PRELOADED; + + const Deprecated = ZEND_ACC_DEPRECATED; + const ReturnReference = ZEND_ACC_RETURN_REFERENCE; + const HasReturnType = ZEND_ACC_HAS_RETURN_TYPE; + const Variadic = ZEND_ACC_VARIADIC; + const HasFinallyBlock = ZEND_ACC_HAS_FINALLY_BLOCK; + const EarlyBinding = ZEND_ACC_EARLY_BINDING; + const UsesThis = ZEND_ACC_USES_THIS; + const CallViaTrampoline = ZEND_ACC_CALL_VIA_TRAMPOLINE; + const NeverCache = ZEND_ACC_NEVER_CACHE; + const TraitClone = ZEND_ACC_TRAIT_CLONE; + const IsConstructor = ZEND_ACC_CTOR; + const Closure = ZEND_ACC_CLOSURE; + const FakeClosure = ZEND_ACC_FAKE_CLOSURE; + const Generator = ZEND_ACC_GENERATOR; + const DonePassTwo = ZEND_ACC_DONE_PASS_TWO; + const HeapRTCache = ZEND_ACC_HEAP_RT_CACHE; + const StrictTypes = ZEND_ACC_STRICT_TYPES; + } +} + +bitflags! { + /// Flags for building properties. + pub struct PropertyFlags: u32 { + const Public = ZEND_ACC_PUBLIC; + const Protected = ZEND_ACC_PROTECTED; + const Private = ZEND_ACC_PRIVATE; + const Changed = ZEND_ACC_CHANGED; + const Static = ZEND_ACC_STATIC; + const Promoted = ZEND_ACC_PROMOTED; + } +} + +bitflags! { + /// Flags for building constants. + pub struct ConstantFlags: u32 { + const Public = ZEND_ACC_PUBLIC; + const Protected = ZEND_ACC_PROTECTED; + const Private = ZEND_ACC_PRIVATE; + const Promoted = ZEND_ACC_PROMOTED; + } +} + +bitflags! { + /// Flags for building module global constants. + pub struct GlobalConstantFlags: u32 { + const CaseSensitive = CONST_CS; + const Persistent = CONST_PERSISTENT; + const NoFileCache = CONST_NO_FILE_CACHE; + const Deprecated = CONST_DEPRECATED; + } +} + +bitflags! { + /// Represents the result of a function. + pub struct ZendResult: i32 { + const Success = 0; + const Failure = -1; + } +} + +/// Valid data types for PHP. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum DataType { + Undef, + Null, + False, + True, + Long, + Double, + String, + Array, + Object(Option<&'static str>), + Resource, + Reference, + Callable, + ConstantExpression, + Void, + Mixed, + Bool, + Ptr, +} + +impl Default for DataType { + fn default() -> Self { + Self::Void + } +} + +impl DataType { + /// Returns the integer representation of the data type. + pub const fn as_u32(&self) -> u32 { + match self { + DataType::Undef => IS_UNDEF, + DataType::Null => IS_NULL, + DataType::False => IS_FALSE, + DataType::True => IS_TRUE, + DataType::Long => IS_LONG, + DataType::Double => IS_DOUBLE, + DataType::String => IS_STRING, + DataType::Array => IS_ARRAY, + DataType::Object(_) => IS_OBJECT, + DataType::Resource => IS_RESOURCE, + DataType::Reference => IS_RESOURCE, + DataType::Callable => IS_CALLABLE, + DataType::ConstantExpression => IS_CONSTANT_AST, + DataType::Void => IS_VOID, + DataType::Mixed => IS_MIXED, + DataType::Bool => _IS_BOOL, + DataType::Ptr => IS_PTR, + } + } +} + +// TODO: Ideally want something like this +// pub struct Type { +// data_type: DataType, +// is_refcounted: bool, +// is_collectable: bool, +// is_immutable: bool, +// is_persistent: bool, +// } +// +// impl From for Type { ... } + +impl TryFrom for DataType { + type Error = Error; + + fn try_from(value: ZvalTypeFlags) -> Result { + macro_rules! contains { + ($t: ident) => { + if value.contains(ZvalTypeFlags::$t) { + return Ok(DataType::$t); + } + }; + } + + contains!(Undef); + contains!(Null); + contains!(False); + contains!(True); + contains!(False); + contains!(Long); + contains!(Double); + contains!(String); + contains!(Array); + contains!(Resource); + contains!(Callable); + contains!(ConstantExpression); + contains!(Void); + + if value.contains(ZvalTypeFlags::Object) { + return Ok(DataType::Object(None)); + } + + Err(Error::UnknownDatatype(0)) + } +} + +impl From for DataType { + #[allow(clippy::bad_bit_mask)] + fn from(value: u32) -> Self { + macro_rules! contains { + ($c: ident, $t: ident) => { + if (value & $c) == $c { + return DataType::$t; + } + }; + } + + contains!(IS_VOID, Void); + contains!(IS_CALLABLE, Callable); + contains!(IS_CONSTANT_AST, ConstantExpression); + contains!(IS_REFERENCE, Reference); + contains!(IS_RESOURCE, Resource); + contains!(IS_ARRAY, Array); + contains!(IS_STRING, String); + contains!(IS_DOUBLE, Double); + contains!(IS_LONG, Long); + contains!(IS_TRUE, True); + contains!(IS_FALSE, False); + contains!(IS_NULL, Null); + contains!(IS_PTR, Ptr); + + if (value & IS_OBJECT) == IS_OBJECT { + return DataType::Object(None); + } + + contains!(IS_UNDEF, Undef); + + DataType::Mixed + } +} + +impl Display for DataType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + DataType::Undef => write!(f, "Undefined"), + DataType::Null => write!(f, "Null"), + DataType::False => write!(f, "False"), + DataType::True => write!(f, "True"), + DataType::Long => write!(f, "Long"), + DataType::Double => write!(f, "Double"), + DataType::String => write!(f, "String"), + DataType::Array => write!(f, "Array"), + DataType::Object(obj) => write!(f, "{}", obj.as_deref().unwrap_or("Object")), + DataType::Resource => write!(f, "Resource"), + DataType::Reference => write!(f, "Reference"), + DataType::Callable => write!(f, "Callable"), + DataType::ConstantExpression => write!(f, "Constant Expression"), + DataType::Void => write!(f, "Void"), + DataType::Bool => write!(f, "Bool"), + DataType::Mixed => write!(f, "Mixed"), + DataType::Ptr => write!(f, "Pointer"), + } + } +} + +#[cfg(test)] +mod tests { + use super::DataType; + use crate::ffi::{ + IS_ARRAY, IS_ARRAY_EX, IS_CALLABLE, IS_CONSTANT_AST, IS_CONSTANT_AST_EX, IS_DOUBLE, + IS_FALSE, IS_INTERNED_STRING_EX, IS_LONG, IS_NULL, IS_OBJECT, IS_OBJECT_EX, IS_REFERENCE, + IS_REFERENCE_EX, IS_RESOURCE, IS_RESOURCE_EX, IS_STRING, IS_STRING_EX, IS_TRUE, IS_UNDEF, + IS_VOID, + }; + use std::convert::TryFrom; + + #[test] + fn test_datatype() { + macro_rules! test { + ($c: ident, $t: ident) => { + assert_eq!(DataType::try_from($c), Ok(DataType::$t)); + }; + } + + test!(IS_UNDEF, Undef); + test!(IS_NULL, Null); + test!(IS_FALSE, False); + test!(IS_TRUE, True); + test!(IS_LONG, Long); + test!(IS_DOUBLE, Double); + test!(IS_STRING, String); + test!(IS_ARRAY, Array); + assert_eq!(DataType::try_from(IS_OBJECT), Ok(DataType::Object(None))); + test!(IS_RESOURCE, Resource); + test!(IS_REFERENCE, Reference); + test!(IS_CONSTANT_AST, ConstantExpression); + test!(IS_CALLABLE, Callable); + test!(IS_VOID, Void); + + test!(IS_INTERNED_STRING_EX, String); + test!(IS_STRING_EX, String); + test!(IS_ARRAY_EX, Array); + assert_eq!(DataType::try_from(IS_OBJECT_EX), Ok(DataType::Object(None))); + test!(IS_RESOURCE_EX, Resource); + test!(IS_REFERENCE_EX, Reference); + test!(IS_CONSTANT_AST_EX, ConstantExpression); + } +} diff --git a/src/internal.rs b/src/internal.rs new file mode 100644 index 0000000000..44d9af34d8 --- /dev/null +++ b/src/internal.rs @@ -0,0 +1,11 @@ +//! Internal, public functions that are called from downstream extensions. + +/// Called by startup functions registered with the [`#[php_startup]`] macro. +/// Initializes all classes that are defined by ext-php-rs (i.e. `Closure`). +/// +/// [`#[php_startup]`]: crate::php_startup +#[inline(always)] +pub fn ext_php_rs_startup() { + #[cfg(feature = "closure")] + crate::closure::Closure::build(); +} diff --git a/src/lib.rs b/src/lib.rs index 2b1aea2404..9aee462048 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,19 +5,58 @@ #![allow(non_snake_case)] #![cfg_attr(docs, feature(doc_cfg))] +pub mod alloc; +pub mod args; +pub mod binary; +pub mod builders; +pub mod convert; +pub mod error; +pub mod exception; +pub mod ffi; +pub mod flags; #[macro_use] pub mod macros; -pub mod bindings; -pub mod errors; -pub mod php; +pub mod boxed; +pub mod class; +#[cfg(any(docs, feature = "closure"))] +#[cfg_attr(docs, doc(cfg(feature = "closure")))] +pub mod closure; +pub mod constant; +#[doc(hidden)] +pub mod internal; +pub mod props; +pub mod rc; +pub mod types; +pub mod zend; + +/// A module typically glob-imported containing the typically required macros +/// and imports. +pub mod prelude { + pub use crate::builders::ModuleBuilder; + #[cfg(any(docs, feature = "closure"))] + #[cfg_attr(docs, doc(cfg(feature = "closure")))] + pub use crate::closure::Closure; + pub use crate::exception::{PhpException, PhpResult}; + pub use crate::php_class; + pub use crate::php_const; + pub use crate::php_extern; + pub use crate::php_function; + pub use crate::php_impl; + pub use crate::php_module; + pub use crate::php_startup; + pub use crate::types::ZendCallable; + pub use crate::ZvalConvert; +} /// Attribute used to annotate constants to be exported to PHP. /// -/// The declared constant is left intact (apart from the addition of the `#[allow(dead_code)]` -/// attribute in the case that you do not use the Rust constant). +/// The declared constant is left intact (apart from the addition of the +/// `#[allow(dead_code)]` attribute in the case that you do not use the Rust +/// constant). /// -/// These declarations must happen before you declare your [`macro@php_startup`] function (or -/// [`macro@php_module`] function if you do not have a startup function). +/// These declarations must happen before you declare your [`macro@php_startup`] +/// function (or [`macro@php_module`] function if you do not have a startup +/// function). /// /// # Example /// @@ -35,12 +74,14 @@ pub mod php; /// ``` pub use ext_php_rs_derive::php_const; -/// Attribute used to annotate `extern` blocks which are deemed as PHP functions. +/// Attribute used to annotate `extern` blocks which are deemed as PHP +/// functions. /// -/// This allows you to 'import' PHP functions into Rust so that they can be called like regular -/// Rust functions. Parameters can be any type that implements [`IntoZval`], and the return type -/// can be anything that implements [`From`] (notice how [`Zval`] is consumed rather than -/// borrowed in this case). +/// This allows you to 'import' PHP functions into Rust so that they can be +/// called like regular Rust functions. Parameters can be any type that +/// implements [`IntoZval`], and the return type can be anything that implements +/// [`From`] (notice how [`Zval`] is consumed rather than borrowed in this +/// case). /// /// # Panics /// @@ -51,19 +92,21 @@ pub use ext_php_rs_derive::php_const; /// * The actual function call failed internally. /// * The output [`Zval`] could not be parsed into the output type. /// -/// The last point can be important when interacting with functions that return unions, such as -/// [`strpos`] which can return an integer or a boolean. In this case, a [`Zval`] should be -/// returned as parsing a boolean to an integer is invalid, and vice versa. +/// The last point can be important when interacting with functions that return +/// unions, such as [`strpos`] which can return an integer or a boolean. In this +/// case, a [`Zval`] should be returned as parsing a boolean to an integer is +/// invalid, and vice versa. /// /// # Example /// -/// This `extern` block imports the [`strpos`] function from PHP. Notice that the string parameters -/// can take either [`String`] or [`&str`], the optional parameter `offset` is an [`Option`], -/// and the return value is a [`Zval`] as the return type is an integer-boolean union. +/// This `extern` block imports the [`strpos`] function from PHP. Notice that +/// the string parameters can take either [`String`] or [`&str`], the optional +/// parameter `offset` is an [`Option`], and the return value is a [`Zval`] +/// as the return type is an integer-boolean union. /// /// ``` /// # use ext_php_rs::prelude::*; -/// # use ext_php_rs::php::types::zval::Zval; +/// # use ext_php_rs::types::Zval; /// #[php_extern] /// extern "C" { /// fn strpos(haystack: &str, needle: &str, offset: Option) -> Zval; @@ -86,46 +129,53 @@ pub use ext_php_rs_derive::php_extern; /// Attribute used to annotate a function as a PHP function. /// -/// Only types that implement [`FromZval`] can be used as parameter and return types. These include -/// but are not limited to the following: +/// Only types that implement [`FromZval`] can be used as parameter and return +/// types. These include but are not limited to the following: /// -/// - Most primitive integers ([`i8`], [`i16`], [`i32`], [`i64`], [`u8`], [`u16`], [`u32`], [`u64`], +/// - Most primitive integers ([`i8`], [`i16`], [`i32`], [`i64`], [`u8`], +/// [`u16`], [`u32`], [`u64`], /// [`usize`], [`isize`]) /// - Double-precision floating point numbers ([`f64`]) /// - [`bool`] /// - [`String`] -/// - [`Vec`] and [`HashMap`](std::collections::HashMap) where `T: FromZval`. +/// - [`Vec`] and [`HashMap`](std::collections::HashMap) where `T: +/// FromZval`. /// - [`Binary`] for passing binary data as a string, where `T: Pack`. -/// - [`Callable`] for receiving PHP callables, not applicable for return values. -/// - [`Option`] where `T: FromZval`. When used as a parameter, the parameter will be -/// deemed nullable, and will contain [`None`] when `null` is passed. When used as a return type, -/// if [`None`] is returned the [`Zval`] will be set to null. Optional parameters *must* be of the -/// type [`Option`]. -/// -/// Additionally, you are able to return a variant of [`Result`]. `T` must implement -/// [`IntoZval`] and `E` must implement `Into`. If an error variant is returned, a -/// PHP exception is thrown using the [`PhpException`] struct contents. -/// -/// You are able to implement [`FromZval`] on your own custom types to have arguments passed in -/// seamlessly. Similarly, you can implement [`IntoZval`] on values that you want to be able to be -/// returned from PHP fucntions. -/// -/// Parameters may be deemed optional by passing the parameter name into the attribute options. -/// Note that all parameters that are optional (which includes the given optional parameter as well -/// as all parameters after) *must* be of the type [`Option`], where `T` is a valid type. +/// - [`ZendCallable`] for receiving PHP callables, not applicable for return +/// values. +/// - [`Option`] where `T: FromZval`. When used as a parameter, the parameter +/// will be +/// deemed nullable, and will contain [`None`] when `null` is passed. When used +/// as a return type, if [`None`] is returned the [`Zval`] will be set to null. +/// Optional parameters *must* be of the type [`Option`]. +/// +/// Additionally, you are able to return a variant of [`Result`]. `T` must +/// implement [`IntoZval`] and `E` must implement `Into`. If an +/// error variant is returned, a PHP exception is thrown using the +/// [`PhpException`] struct contents. +/// +/// You are able to implement [`FromZval`] on your own custom types to have +/// arguments passed in seamlessly. Similarly, you can implement [`IntoZval`] on +/// values that you want to be able to be returned from PHP fucntions. +/// +/// Parameters may be deemed optional by passing the parameter name into the +/// attribute options. Note that all parameters that are optional (which +/// includes the given optional parameter as well as all parameters after) +/// *must* be of the type [`Option`], where `T` is a valid type. /// /// Generics are *not* supported. /// -/// Behind the scenes, an `extern "C"` wrapper function is generated, which is actually called by -/// PHP. The first example function would be converted into a function which looks like so: +/// Behind the scenes, an `extern "C"` wrapper function is generated, which is +/// actually called by PHP. The first example function would be converted into a +/// function which looks like so: /// /// ```no_run -/// # use ext_php_rs::{prelude::*, php::{exceptions::PhpException, execution_data::ExecutionData, types::zval::{FromZval, IntoZval, Zval}, args::{Arg, ArgParser}}}; +/// # use ext_php_rs::{prelude::*, exception::PhpException, zend::ExecuteData, convert::{FromZval, IntoZval}, types::Zval, args::{Arg, ArgParser}}; /// pub fn hello(name: String) -> String { /// format!("Hello, {}!", name) /// } /// -/// pub extern "C" fn _internal_php_hello(ex: &mut ExecutionData, retval: &mut Zval) { +/// pub extern "C" fn _internal_php_hello(ex: &mut ExecuteData, retval: &mut Zval) { /// let mut name = Arg::new("name", ::TYPE); /// let parser = ex.parser() /// .arg(&mut name) @@ -155,13 +205,13 @@ pub use ext_php_rs_derive::php_extern; /// } /// ``` /// -/// This allows the original function to continue being used while also being exported as a PHP -/// function. +/// This allows the original function to continue being used while also being +/// exported as a PHP function. /// /// # Examples /// -/// Creating a simple function which will return a string. The function still must be declared in -/// the PHP module to be able to call. +/// Creating a simple function which will return a string. The function still +/// must be declared in the PHP module to be able to call. /// /// ``` /// # use ext_php_rs::prelude::*; @@ -175,9 +225,9 @@ pub use ext_php_rs_derive::php_extern; /// # } /// ``` /// -/// Parameters can also be deemed optional by passing the parameter name in the attribute options. -/// This function takes one required parameter (`hello`) and two optional parameters (`description` -/// and `age`). +/// Parameters can also be deemed optional by passing the parameter name in the +/// attribute options. This function takes one required parameter (`hello`) and +/// two optional parameters (`description` and `age`). /// /// ``` /// # use ext_php_rs::prelude::*; @@ -201,8 +251,9 @@ pub use ext_php_rs_derive::php_extern; /// # } /// ``` /// -/// Defaults can also be given in a similar fashion. For example, the above function could have -/// default values for `description` and `age` by changing the attribute to the following: +/// Defaults can also be given in a similar fashion. For example, the above +/// function could have default values for `description` and `age` by changing +/// the attribute to the following: /// /// ``` /// # use ext_php_rs::prelude::*; @@ -222,48 +273,55 @@ pub use ext_php_rs_derive::php_extern; /// [`IntoZval`]: crate::php::types::zval::IntoZval /// [`Zval`]: crate::php::types::zval::Zval /// [`Binary`]: crate::php::types::binary::Binary -/// [`Callable`]: crate::php::types::callable::Callable +/// [`ZendCallable`]: crate::php::types::callable::ZendCallable /// [`PhpException`]: crate::php::exceptions::PhpException pub use ext_php_rs_derive::php_function; -/// Annotates a structs `impl` block, declaring that all methods and constants declared -/// inside the `impl` block will be declared as PHP methods and constants. +/// Annotates a structs `impl` block, declaring that all methods and constants +/// declared inside the `impl` block will be declared as PHP methods and +/// constants. /// -/// If you do not want to export a method to PHP, declare it in another `impl` block that is not -/// tagged with this macro. +/// If you do not want to export a method to PHP, declare it in another `impl` +/// block that is not tagged with this macro. /// -/// The declared methods and functions are kept intact so they can continue to be called from Rust. -/// Methods do generate an additional function, with an identifier in the format -/// `_internal_php_#ident`. +/// The declared methods and functions are kept intact so they can continue to +/// be called from Rust. Methods do generate an additional function, with an +/// identifier in the format `_internal_php_#ident`. /// -/// Methods and constants are declared mostly the same as their global counterparts, so read the -/// documentation on the [`macro@php_function`] and [`macro@php_const`] macros for more details. +/// Methods and constants are declared mostly the same as their global +/// counterparts, so read the documentation on the [`macro@php_function`] and +/// [`macro@php_const`] macros for more details. /// -/// The main difference is that the contents of the `impl` block *do not* need to be tagged with -/// additional attributes - this macro assumes that all contents of the `impl` block are to be -/// exported to PHP. +/// The main difference is that the contents of the `impl` block *do not* need +/// to be tagged with additional attributes - this macro assumes that all +/// contents of the `impl` block are to be exported to PHP. /// -/// The only contrary to this is setting the visibility, optional argument and default arguments -/// for methods. These are done through seperate macros: +/// The only contrary to this is setting the visibility, optional argument and +/// default arguments for methods. These are done through seperate macros: /// -/// - `#[defaults(key = value, ...)]` for setting defaults of method variables, similar to the +/// - `#[defaults(key = value, ...)]` for setting defaults of method variables, +/// similar to the /// function macro. Arguments with defaults need to be optional. -/// - `#[optional(key)]` for setting `key` as an optional argument (and therefore the rest of the +/// - `#[optional(key)]` for setting `key` as an optional argument (and +/// therefore the rest of the /// arguments). -/// - `#[public]`, `#[protected]` and `#[private]` for setting the visibility of the method, -/// defaulting to public. The Rust visibility has no effect on the PHP visibility. +/// - `#[public]`, `#[protected]` and `#[private]` for setting the visibility of +/// the method, +/// defaulting to public. The Rust visibility has no effect on the PHP +/// visibility. /// -/// Methods can take a immutable or a mutable reference to `self`, but cannot consume `self`. They -/// can also take no reference to `self` which indicates a static method. +/// Methods can take a immutable or a mutable reference to `self`, but cannot +/// consume `self`. They can also take no reference to `self` which indicates a +/// static method. /// /// ## Constructors /// -/// You may add *one* constructor to the impl block. This method must be called `__construct` or be -/// tagged with the `#[constructor]` attribute, and it will not be exported to PHP like a regular -/// method. +/// You may add *one* constructor to the impl block. This method must be called +/// `__construct` or be tagged with the `#[constructor]` attribute, and it will +/// not be exported to PHP like a regular method. /// -/// The constructor method must not take a reference to `self` and must return `Self` or -/// [`Result`][`Result`], where `E: Into`. +/// The constructor method must not take a reference to `self` and must return +/// `Self` or [`Result`][`Result`], where `E: Into`. /// /// # Example /// @@ -308,29 +366,33 @@ pub use ext_php_rs_derive::php_function; /// ``` pub use ext_php_rs_derive::php_impl; -/// Annotates a function that will be used by PHP to retrieve information about the module. +/// Annotates a function that will be used by PHP to retrieve information about +/// the module. /// -/// In the process, the function is wrapped by an `extern "C"` function which is called from PHP, -/// which then calls the given function. +/// In the process, the function is wrapped by an `extern "C"` function which is +/// called from PHP, which then calls the given function. /// -/// As well as wrapping the function, the `ModuleBuilder` is initialized and functions which have -/// already been declared with the [`macro@php_function`] attribute will be registered with the -/// module, so ideally you won't have to do anything inside the function. +/// As well as wrapping the function, the `ModuleBuilder` is initialized and +/// functions which have already been declared with the [`macro@php_function`] +/// attribute will be registered with the module, so ideally you won't have to +/// do anything inside the function. /// -/// The attribute must be called on a function *last*, i.e. the last proc-macro to be compiled, as -/// the attribute relies on all other PHP attributes being compiled before the module. If another -/// PHP attribute is compiled after the module attribute, an error will be thrown. +/// The attribute must be called on a function *last*, i.e. the last proc-macro +/// to be compiled, as the attribute relies on all other PHP attributes being +/// compiled before the module. If another PHP attribute is compiled after the +/// module attribute, an error will be thrown. /// /// Note that if the function is not called `get_module`, it will be renamed. /// -/// If you have defined classes using the [`macro@php_class`] macro and you have not defined -/// a startup function, it will be automatically declared and registered. +/// If you have defined classes using the [`macro@php_class`] macro and you have +/// not defined a startup function, it will be automatically declared and +/// registered. /// /// # Example /// -/// The `get_module` function is required in every PHP extension. This is a bare minimum example, -/// since the function is declared above the module it will automatically be registered when the -/// module attribute is called. +/// The `get_module` function is required in every PHP extension. This is a bare +/// minimum example, since the function is declared above the module it will +/// automatically be registered when the module attribute is called. /// /// ``` /// # use ext_php_rs::prelude::*; @@ -348,29 +410,32 @@ pub use ext_php_rs_derive::php_module; /// Annotates a struct that will be exported to PHP as a class. /// -/// By default, the class cannot be constructed from PHP. You must add a constructor method in the -/// [`macro@php_impl`] impl block to be able to construct the object from PHP. +/// By default, the class cannot be constructed from PHP. You must add a +/// constructor method in the [`macro@php_impl`] impl block to be able to +/// construct the object from PHP. /// /// This attribute takes a set of optional arguments: /// -/// * `name` - The name of the exported class, if it is different from the Rust struct name. This -/// can be useful for namespaced classes, as you cannot place backslashes in Rust struct names. +/// * `name` - The name of the exported class, if it is different from the Rust +/// struct name. This can be useful for namespaced classes, as you cannot +/// place backslashes in Rust struct names. /// -/// Any struct that uses this attribute can also provide an optional set of extra attributes, used -/// to modify the class. These attributes must be used **underneath** this attribute, as they are -/// not valid Rust attributes, and instead are parsed by this attribute: +/// Any struct that uses this attribute can also provide an optional set of +/// extra attributes, used to modify the class. These attributes must be used +/// **underneath** this attribute, as they are not valid Rust attributes, and +/// instead are parsed by this attribute: /// -/// * `#[extends(ce)]` - Sets the parent class of this new class. Can only be used once, and `ce` -/// may be any valid expression. -/// * `#[implements(ce)]` - Implements an interface on the new class. Can be used multiple times, -/// and `ce` may be any valid expression. +/// * `#[extends(ce)]` - Sets the parent class of this new class. Can only be +/// used once, and `ce` may be any valid expression. +/// * `#[implements(ce)]` - Implements an interface on the new class. Can be +/// used multiple times, and `ce` may be any valid expression. /// -/// This attribute (and its associated structs) must be defined *above* the startup function (which -/// is annotated by the [`macro@php_startup`] macro, or automatically generated just above the -/// [`macro@php_module`] function). +/// This attribute (and its associated structs) must be defined *above* the +/// startup function (which is annotated by the [`macro@php_startup`] macro, or +/// automatically generated just above the [`macro@php_module`] function). /// -/// Fields defined on the struct *are not* the same as PHP properties, and are only accessible from -/// Rust. +/// Fields defined on the struct *are not* the same as PHP properties, and are +/// only accessible from Rust. /// /// # Example /// @@ -391,15 +456,16 @@ pub use ext_php_rs_derive::php_module; /// } /// ``` /// -/// Create a custom exception `RedisException` inside the namespace `Redis\Exception`: +/// Create a custom exception `RedisException` inside the namespace +/// `Redis\Exception`: /// /// ``` /// # use ext_php_rs::prelude::*; -/// use ext_php_rs::php::exceptions::PhpException; -/// use ext_php_rs::php::class::ClassEntry; +/// use ext_php_rs::exception::PhpException; +/// use ext_php_rs::zend::ce; /// /// #[php_class(name = "Redis\\Exception\\RedisException")] -/// #[extends(ClassEntry::exception())] +/// #[extends(ce::exception())] /// pub struct Example; /// /// #[php_function] @@ -414,18 +480,19 @@ pub use ext_php_rs_derive::php_module; /// ``` pub use ext_php_rs_derive::php_class; -/// Annotates a function that will be called by PHP when the module starts up. Generally used to -/// register classes and constants. +/// Annotates a function that will be called by PHP when the module starts up. +/// Generally used to register classes and constants. /// -/// As well as annotating the function, any classes and constants that had been declared using the -/// [`macro@php_class`], [`macro@php_const`] and [`macro@php_impl`] attributes will be registered -/// inside this function. +/// As well as annotating the function, any classes and constants that had been +/// declared using the [`macro@php_class`], [`macro@php_const`] and +/// [`macro@php_impl`] attributes will be registered inside this function. /// -/// This function *must* be declared before the [`macro@php_module`] function, as this function -/// needs to be declared when building the module. +/// This function *must* be declared before the [`macro@php_module`] function, +/// as this function needs to be declared when building the module. /// -/// This function will automatically be generated if not already declared with this macro if you -/// have registered any classes or constants when using the [`macro@php_module`] macro. +/// This function will automatically be generated if not already declared with +/// this macro if you have registered any classes or constants when using the +/// [`macro@php_module`] macro. /// /// # Example /// @@ -442,19 +509,22 @@ pub use ext_php_rs_derive::php_class; /// ``` pub use ext_php_rs_derive::php_startup; -/// Derives the traits required to convert a struct or enum to and from a [`Zval`]. -/// Both [`FromZval`] and [`IntoZval`] are implemented on types which use this macro. +/// Derives the traits required to convert a struct or enum to and from a +/// [`Zval`]. Both [`FromZval`] and [`IntoZval`] are implemented on types which +/// use this macro. /// /// # Structs /// -/// When the macro is used on a struct, the [`FromZendObject`] and [`IntoZendObject`] traits are also -/// implemented, and will attempt to retrieve values for the struct fields from the objects properties. -/// This can be useful when you expect some arbitrary object (of which the type does not matter), but -/// you care about the value of the properties. +/// When the macro is used on a struct, the [`FromZendObject`] and +/// [`IntoZendObject`] traits are also implemented, and will attempt to retrieve +/// values for the struct fields from the objects properties. This can be useful +/// when you expect some arbitrary object (of which the type does not matter), +/// but you care about the value of the properties. /// -/// All properties must implement [`FromZval`] and [`IntoZval`] themselves. Generics are supported, -/// however, a [`FromZval`] and [`IntoZval`] bound will be added. If one property cannot be retrieved -/// from the object, the whole conversion will fail. +/// All properties must implement [`FromZval`] and [`IntoZval`] themselves. +/// Generics are supported, however, a [`FromZval`] and [`IntoZval`] bound will +/// be added. If one property cannot be retrieved from the object, the whole +/// conversion will fail. /// /// ## Examples /// @@ -514,19 +584,22 @@ pub use ext_php_rs_derive::php_startup; /// /// # Enums /// -/// When the macro is used on an enum, the [`FromZval`] and [`IntoZval`] implementations will treat -/// the enum as a tagged union with a mixed datatype. This allows you to accept two different types -/// in a parameter, for example, a string and an integer. +/// When the macro is used on an enum, the [`FromZval`] and [`IntoZval`] +/// implementations will treat the enum as a tagged union with a mixed datatype. +/// This allows you to accept two different types in a parameter, for example, a +/// string and an integer. /// -/// The enum variants must not have named fields (i.e. not in the form of a struct), and must have -/// exactly one field, the type to extract from the [`Zval`]. Optionally, the enum may have a -/// single default, empty variant, which is used when the [`Zval`] did not contain any data to fill +/// The enum variants must not have named fields (i.e. not in the form of a +/// struct), and must have exactly one field, the type to extract from the +/// [`Zval`]. Optionally, the enum may have a single default, empty variant, +/// which is used when the [`Zval`] did not contain any data to fill /// the other variants. This empty variant is equivalent to `null` in PHP. /// -/// The ordering of the enum variants is important, as the [`Zval`] contents is matched in order of -/// the variants. For example, [`Zval::string`] will attempt to read a string from the [`Zval`], -/// and if the [`Zval`] contains a long, the long will be converted to a string. If a string -/// variant was placed above an integer variant in the enum, the integer would be converted into a +/// The ordering of the enum variants is important, as the [`Zval`] contents is +/// matched in order of the variants. For example, [`Zval::string`] will attempt +/// to read a string from the [`Zval`], and if the [`Zval`] contains a long, the +/// long will be converted to a string. If a string variant was placed above an +/// integer variant in the enum, the integer would be converted into a /// string and passed as the string variant. /// /// ## Examples @@ -571,21 +644,3 @@ pub use ext_php_rs_derive::php_startup; /// [`Zval`]: crate::php::types::zval::Zval /// [`Zval::string`]: crate::php::types::zval::Zval::string pub use ext_php_rs_derive::ZvalConvert; - -/// A module typically glob-imported containing the typically required macros and imports. -pub mod prelude { - pub use crate::php::exceptions::{PhpException, PhpResult}; - pub use crate::php::module::ModuleBuilder; - pub use crate::php::types::callable::Callable; - #[cfg(any(docs, feature = "closure"))] - #[cfg_attr(docs, doc(cfg(feature = "closure")))] - pub use crate::php::types::closure::Closure; - pub use crate::php_class; - pub use crate::php_const; - pub use crate::php_extern; - pub use crate::php_function; - pub use crate::php_impl; - pub use crate::php_module; - pub use crate::php_startup; - pub use crate::ZvalConvert; -} diff --git a/src/macros.rs b/src/macros.rs index 168f74cbbb..971adbeeff 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -1,43 +1,47 @@ -//! Macros for interacting with PHP, mainly when the function takes variadic arguments. -//! Unforutunately, this is the best way to handle these. +//! Macros for interacting with PHP, mainly when the function takes variadic +//! arguments. Unforutunately, this is the best way to handle these. //! Note that most of these will introduce unsafe into your code base. -/// Starts the PHP extension information table displayed when running `phpinfo();` -/// Must be run *before* rows are inserted into the table. +/// Starts the PHP extension information table displayed when running +/// `phpinfo();` Must be run *before* rows are inserted into the table. #[macro_export] macro_rules! info_table_start { () => { - unsafe { $crate::bindings::php_info_print_table_start() }; + unsafe { $crate::ffi::php_info_print_table_start() }; }; } -/// Ends the PHP extension information table. Must be run *after* all rows have been inserted into the table. +/// Ends the PHP extension information table. Must be run *after* all rows have +/// been inserted into the table. #[macro_export] macro_rules! info_table_end { () => { - unsafe { $crate::bindings::php_info_print_table_end() } + unsafe { $crate::ffi::php_info_print_table_end() } }; } -/// Sets the header for the PHP extension information table. Takes as many string arguments as required. +/// Sets the header for the PHP extension information table. Takes as many +/// string arguments as required. #[macro_export] macro_rules! info_table_header { ($($element:expr),*) => {$crate::_info_table_row!(php_info_print_table_header, $($element),*)}; } -/// Adds a row to the PHP extension information table. Takes as many string arguments as required. +/// Adds a row to the PHP extension information table. Takes as many string +/// arguments as required. #[macro_export] macro_rules! info_table_row { ($($element:expr),*) => {$crate::_info_table_row!(php_info_print_table_row, $($element),*)}; } -/// INTERNAL: Calls a variadic C function with the number of parameters, then following with the parameters. +/// INTERNAL: Calls a variadic C function with the number of parameters, then +/// following with the parameters. #[doc(hidden)] #[macro_export] macro_rules! _info_table_row { ($fn: ident, $($element: expr),*) => { unsafe { - $crate::bindings::$fn($crate::_info_table_row!(@COUNT; $($element),*) as i32, $(::std::ffi::CString::new($element).unwrap().as_ptr()),*); + $crate::ffi::$fn($crate::_info_table_row!(@COUNT; $($element),*) as i32, $(::std::ffi::CString::new($element).unwrap().as_ptr()),*); } }; @@ -51,10 +55,12 @@ macro_rules! _info_table_row { /// /// # Parameters /// -/// * `$fn` - The 'function' to call. Can be an [`Arg`](crate::php::args::Arg) or a -/// [`Zval`](crate::php::types::zval::Zval). -/// * ...`$param` - The parameters to pass to the function. Must be able to be converted into a -/// [`Zval`](crate::php::types::zval::Zval). +/// * `$fn` - The 'function' to call. Can be an [`Arg`] or a [`Zval`]. +/// * ...`$param` - The parameters to pass to the function. Must be able to be +/// converted into a [`Zval`]. +/// +/// [`Arg`]: crate::args::Arg +/// [`Zval`]: crate::types::Zval #[macro_export] macro_rules! call_user_func { ($fn: expr) => { @@ -66,24 +72,27 @@ macro_rules! call_user_func { }; } -/// Parses a given list of arguments using the [`ArgParser`](crate::php::args::ArgParser) class. +/// Parses a given list of arguments using the [`ArgParser`] class. /// /// # Examples /// -/// This example parses all of the arguments. If one is invalid, execution of the function will -/// stop at the `parse_args!` macro invocation. The user is notified via PHP's argument parsing -/// system. +/// This example parses all of the arguments. If one is invalid, execution of +/// the function will stop at the `parse_args!` macro invocation. The user is +/// notified via PHP's argument parsing system. /// /// In this case, all of the arguments are required. /// /// ``` /// # #[macro_use] extern crate ext_php_rs; /// use ext_php_rs::{ -/// parse_args, -/// php::{args::Arg, enums::DataType, execution_data::ExecutionData, types::zval::Zval}, +/// parse_args, +/// args::Arg, +/// flags::DataType, +/// zend::ExecuteData, +/// types::Zval, /// }; /// -/// pub extern "C" fn example_fn(execute_data: &mut ExecutionData, _: &mut Zval) { +/// pub extern "C" fn example_fn(execute_data: &mut ExecuteData, _: &mut Zval) { /// let mut x = Arg::new("x", DataType::Long); /// let mut y = Arg::new("y", DataType::Long); /// let mut z = Arg::new("z", DataType::Long); @@ -92,16 +101,20 @@ macro_rules! call_user_func { /// } /// ``` /// -/// This example is similar to the one above, apart from the fact that the `z` argument is not -/// required. Note the semicolon seperating the first two arguments from the second. +/// This example is similar to the one above, apart from the fact that the `z` +/// argument is not required. Note the semicolon seperating the first two +/// arguments from the second. /// /// ``` /// use ext_php_rs::{ -/// parse_args, -/// php::{args::Arg, enums::DataType, execution_data::ExecutionData, types::zval::Zval}, +/// parse_args, +/// args::Arg, +/// flags::DataType, +/// zend::ExecuteData, +/// types::Zval, /// }; /// -/// pub extern "C" fn example_fn(execute_data: &mut ExecutionData, _: &mut Zval) { +/// pub extern "C" fn example_fn(execute_data: &mut ExecuteData, _: &mut Zval) { /// let mut x = Arg::new("x", DataType::Long); /// let mut y = Arg::new("y", DataType::Long); /// let mut z = Arg::new("z", DataType::Long); @@ -109,6 +122,8 @@ macro_rules! call_user_func { /// parse_args!(execute_data, x, y; z); /// } /// ``` +/// +/// [`ArgParser`]: crate::args::ArgParser #[macro_export] macro_rules! parse_args { ($ed: expr, $($arg: expr),*) => {{ @@ -121,8 +136,6 @@ macro_rules! parse_args { }}; ($ed: expr, $($arg: expr),* ; $($opt: expr),*) => {{ - use $crate::php::args::ArgParser; - let parser = $ed.parser() $(.arg(&mut $arg))* .not_required() @@ -136,19 +149,24 @@ macro_rules! parse_args { /// Throws an exception and returns from the current function. /// -/// Wraps the [`throw`] function by inserting a `return` statement after throwing the exception. +/// Wraps the [`throw`] function by inserting a `return` statement after +/// throwing the exception. /// -/// [`throw`]: crate::php::exceptions::throw +/// [`throw`]: crate::exception::throw /// /// # Examples /// /// ``` -/// use ext_php_rs::{throw, php::{class::ClassEntry, execution_data::ExecutionData, types::zval::Zval}}; +/// use ext_php_rs::{ +/// throw, +/// zend::{ce, ClassEntry, ExecuteData}, +/// types::Zval, +/// }; /// -/// pub extern "C" fn example_fn(execute_data: &mut ExecutionData, _: &mut Zval) { +/// pub extern "C" fn example_fn(execute_data: &mut ExecuteData, _: &mut Zval) { /// let something_wrong = true; /// if something_wrong { -/// throw!(ClassEntry::exception(), "Something is wrong!"); +/// throw!(ce::exception(), "Something is wrong!"); /// } /// /// assert!(false); // This will not run. @@ -157,14 +175,15 @@ macro_rules! parse_args { #[macro_export] macro_rules! throw { ($ex: expr, $reason: expr) => { - $crate::php::exceptions::throw($ex, $reason); + $crate::exception::throw($ex, $reason); return; }; } -/// Implements a set of traits required to convert types that implement [`RegisteredClass`] to and -/// from [`ZendObject`]s and [`Zval`]s. Generally, this macro should not be called directly, as it -/// is called on any type that uses the [`#[php_class]`] macro. +/// Implements a set of traits required to convert types that implement +/// [`RegisteredClass`] to and from [`ZendObject`]s and [`Zval`]s. Generally, +/// this macro should not be called directly, as it is called on any type that +/// uses the [`php_class`] macro. /// /// The following traits are implemented: /// @@ -175,12 +194,13 @@ macro_rules! throw { /// * `IntoZendObject for T` /// * `IntoZval for T` /// -/// These implementations are required while we wait on the stabilisation of specialisation. +/// These implementations are required while we wait on the stabilisation of +/// specialisation. /// /// # Examples /// /// ``` -/// # use ext_php_rs::{php::types::{zval::{Zval, IntoZval, FromZval}, object::{ZendObject, RegisteredClass}}}; +/// # use ext_php_rs::{convert::{IntoZval, FromZval}, types::{Zval, ZendObject}, class::{RegisteredClass}}; /// use ext_php_rs::class_derives; /// /// struct Test { @@ -191,14 +211,14 @@ macro_rules! throw { /// impl RegisteredClass for Test { /// const CLASS_NAME: &'static str = "Test"; /// -/// const CONSTRUCTOR: Option> = None; +/// const CONSTRUCTOR: Option> = None; /// -/// fn get_metadata() -> &'static ext_php_rs::php::types::object::ClassMetadata { +/// fn get_metadata() -> &'static ext_php_rs::class::ClassMetadata { /// todo!() /// } /// /// fn get_properties<'a>( -/// ) -> std::collections::HashMap<&'static str, ext_php_rs::php::types::props::Property<'a, Self>> +/// ) -> std::collections::HashMap<&'static str, ext_php_rs::props::Property<'a, Self>> /// { /// todo!() /// } @@ -216,92 +236,131 @@ macro_rules! throw { /// } /// ``` /// -/// [`RegisteredClass`]: crate::php::types::object::RegisteredClass -/// [`ZendObject`]: crate::php::types::object::ZendObject -/// [`Zval`]: crate::php::types::zval::Zval -/// [`#[php_class]`]: crate::php_class +/// [`RegisteredClass`]: crate::class::RegisteredClass +/// [`ZendObject`]: crate::types::ZendObject +/// [`Zval`]: crate::types::Zval +/// [`php_class`]: crate::php_class #[macro_export] macro_rules! class_derives { ($type: ty) => { - impl<'a> $crate::php::types::object::FromZendObject<'a> for &'a $type { + impl<'a> $crate::convert::FromZendObject<'a> for &'a $type { #[inline] - fn from_zend_object( - obj: &'a $crate::php::types::object::ZendObject, - ) -> $crate::errors::Result { - let obj = $crate::php::types::object::ZendClassObject::<$type>::from_zend_obj(obj) - .ok_or($crate::errors::Error::InvalidScope)?; + fn from_zend_object(obj: &'a $crate::types::ZendObject) -> $crate::error::Result { + let obj = $crate::types::ZendClassObject::<$type>::from_zend_obj(obj) + .ok_or($crate::error::Error::InvalidScope)?; Ok(&**obj) } } - impl<'a> $crate::php::types::object::FromZendObjectMut<'a> for &'a mut $type { + impl<'a> $crate::convert::FromZendObjectMut<'a> for &'a mut $type { #[inline] fn from_zend_object_mut( - obj: &'a mut $crate::php::types::object::ZendObject, - ) -> $crate::errors::Result { - let obj = - $crate::php::types::object::ZendClassObject::<$type>::from_zend_obj_mut(obj) - .ok_or($crate::errors::Error::InvalidScope)?; + obj: &'a mut $crate::types::ZendObject, + ) -> $crate::error::Result { + let obj = $crate::types::ZendClassObject::<$type>::from_zend_obj_mut(obj) + .ok_or($crate::error::Error::InvalidScope)?; Ok(&mut **obj) } } - impl<'a> $crate::php::types::zval::FromZval<'a> for &'a $type { - const TYPE: $crate::php::enums::DataType = $crate::php::enums::DataType::Object(Some( - <$type as $crate::php::types::object::RegisteredClass>::CLASS_NAME, + impl<'a> $crate::convert::FromZval<'a> for &'a $type { + const TYPE: $crate::flags::DataType = $crate::flags::DataType::Object(Some( + <$type as $crate::class::RegisteredClass>::CLASS_NAME, )); #[inline] - fn from_zval(zval: &'a $crate::php::types::zval::Zval) -> ::std::option::Option { - ::from_zend_object( - zval.object()?, - ) - .ok() + fn from_zval(zval: &'a $crate::types::Zval) -> ::std::option::Option { + ::from_zend_object(zval.object()?).ok() } } - impl<'a> $crate::php::types::zval::FromZvalMut<'a> for &'a mut $type { - const TYPE: $crate::php::enums::DataType = $crate::php::enums::DataType::Object(Some( - <$type as $crate::php::types::object::RegisteredClass>::CLASS_NAME, + impl<'a> $crate::convert::FromZvalMut<'a> for &'a mut $type { + const TYPE: $crate::flags::DataType = $crate::flags::DataType::Object(Some( + <$type as $crate::class::RegisteredClass>::CLASS_NAME, )); #[inline] - fn from_zval_mut( - zval: &'a mut $crate::php::types::zval::Zval, - ) -> ::std::option::Option { - ::from_zend_object_mut( + fn from_zval_mut(zval: &'a mut $crate::types::Zval) -> ::std::option::Option { + ::from_zend_object_mut( zval.object_mut()?, ) .ok() } } - impl $crate::php::types::object::IntoZendObject for $type { + impl $crate::convert::IntoZendObject for $type { #[inline] fn into_zend_object( self, - ) -> $crate::errors::Result< - $crate::php::boxed::ZBox<$crate::php::types::object::ZendObject>, - > { - Ok($crate::php::types::object::ZendClassObject::new(self).into()) + ) -> $crate::error::Result<$crate::boxed::ZBox<$crate::types::ZendObject>> { + Ok($crate::types::ZendClassObject::new(self).into()) } } - impl $crate::php::types::zval::IntoZval for $type { - const TYPE: $crate::php::enums::DataType = $crate::php::enums::DataType::Object(Some( - <$type as $crate::php::types::object::RegisteredClass>::CLASS_NAME, + impl $crate::convert::IntoZval for $type { + const TYPE: $crate::flags::DataType = $crate::flags::DataType::Object(Some( + <$type as $crate::class::RegisteredClass>::CLASS_NAME, )); #[inline] fn set_zval( self, - zv: &mut $crate::php::types::zval::Zval, + zv: &mut $crate::types::Zval, persistent: bool, - ) -> $crate::errors::Result<()> { - use $crate::php::types::object::IntoZendObject; + ) -> $crate::error::Result<()> { + use $crate::convert::IntoZendObject; self.into_zend_object()?.set_zval(zv, persistent) } } }; } + +/// Derives `From for Zval` and `IntoZval` for a given type. +macro_rules! into_zval { + ($type: ty, $fn: ident, $dt: ident) => { + impl From<$type> for $crate::types::Zval { + fn from(val: $type) -> Self { + let mut zv = Self::new(); + zv.$fn(val); + zv + } + } + + impl $crate::convert::IntoZval for $type { + const TYPE: $crate::flags::DataType = $crate::flags::DataType::$dt; + + fn set_zval(self, zv: &mut $crate::types::Zval, _: bool) -> $crate::error::Result<()> { + zv.$fn(self); + Ok(()) + } + } + }; +} + +/// Derives `TryFrom for T` and `FromZval for T` on a given type. +macro_rules! try_from_zval { + ($type: ty, $fn: ident, $dt: ident) => { + impl $crate::convert::FromZval<'_> for $type { + const TYPE: $crate::flags::DataType = $crate::flags::DataType::$dt; + + fn from_zval(zval: &$crate::types::Zval) -> ::std::option::Option { + use ::std::convert::TryInto; + + zval.$fn().and_then(|val| val.try_into().ok()) + } + } + + impl ::std::convert::TryFrom<$crate::types::Zval> for $type { + type Error = $crate::error::Error; + + fn try_from(value: $crate::types::Zval) -> $crate::error::Result { + ::from_zval(&value) + .ok_or($crate::error::Error::ZvalConversion(value.get_type())) + } + } + }; +} + +pub(crate) use into_zval; +pub(crate) use try_from_zval; diff --git a/src/php/enums.rs b/src/php/enums.rs deleted file mode 100644 index 63a89481bb..0000000000 --- a/src/php/enums.rs +++ /dev/null @@ -1,214 +0,0 @@ -//! Wrapper for enums introduced in C. - -use std::{convert::TryFrom, fmt::Display}; - -use crate::{ - bindings::{ - IS_ARRAY, IS_CALLABLE, IS_CONSTANT_AST, IS_DOUBLE, IS_FALSE, IS_LONG, IS_MIXED, IS_NULL, - IS_OBJECT, IS_PTR, IS_REFERENCE, IS_RESOURCE, IS_STRING, IS_TRUE, IS_UNDEF, IS_VOID, - _IS_BOOL, - }, - errors::{Error, Result}, - php::flags::ZvalTypeFlags, -}; - -/// Valid data types for PHP. -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub enum DataType { - Undef, - Null, - False, - True, - Long, - Double, - String, - Array, - Object(Option<&'static str>), - Resource, - Reference, - Callable, - ConstantExpression, - Void, - Mixed, - Bool, - Ptr, -} - -impl Default for DataType { - fn default() -> Self { - Self::Void - } -} - -impl DataType { - /// Returns the integer representation of the data type. - pub const fn as_u32(&self) -> u32 { - match self { - DataType::Undef => IS_UNDEF, - DataType::Null => IS_NULL, - DataType::False => IS_FALSE, - DataType::True => IS_TRUE, - DataType::Long => IS_LONG, - DataType::Double => IS_DOUBLE, - DataType::String => IS_STRING, - DataType::Array => IS_ARRAY, - DataType::Object(_) => IS_OBJECT, - DataType::Resource => IS_RESOURCE, - DataType::Reference => IS_RESOURCE, - DataType::Callable => IS_CALLABLE, - DataType::ConstantExpression => IS_CONSTANT_AST, - DataType::Void => IS_VOID, - DataType::Mixed => IS_MIXED, - DataType::Bool => _IS_BOOL, - DataType::Ptr => IS_PTR, - } - } -} - -// TODO: Ideally want something like this -// pub struct Type { -// data_type: DataType, -// is_refcounted: bool, -// is_collectable: bool, -// is_immutable: bool, -// is_persistent: bool, -// } -// -// impl From for Type { ... } - -impl TryFrom for DataType { - type Error = Error; - - fn try_from(value: ZvalTypeFlags) -> Result { - macro_rules! contains { - ($t: ident) => { - if value.contains(ZvalTypeFlags::$t) { - return Ok(DataType::$t); - } - }; - } - - contains!(Undef); - contains!(Null); - contains!(False); - contains!(True); - contains!(False); - contains!(Long); - contains!(Double); - contains!(String); - contains!(Array); - contains!(Resource); - contains!(Callable); - contains!(ConstantExpression); - contains!(Void); - - if value.contains(ZvalTypeFlags::Object) { - return Ok(DataType::Object(None)); - } - - Err(Error::UnknownDatatype(0)) - } -} - -impl From for DataType { - #[allow(clippy::bad_bit_mask)] - fn from(value: u32) -> Self { - macro_rules! contains { - ($c: ident, $t: ident) => { - if (value & $c) == $c { - return DataType::$t; - } - }; - } - - contains!(IS_VOID, Void); - contains!(IS_CALLABLE, Callable); - contains!(IS_CONSTANT_AST, ConstantExpression); - contains!(IS_REFERENCE, Reference); - contains!(IS_RESOURCE, Resource); - contains!(IS_ARRAY, Array); - contains!(IS_STRING, String); - contains!(IS_DOUBLE, Double); - contains!(IS_LONG, Long); - contains!(IS_TRUE, True); - contains!(IS_FALSE, False); - contains!(IS_NULL, Null); - contains!(IS_PTR, Ptr); - - if (value & IS_OBJECT) == IS_OBJECT { - return DataType::Object(None); - } - - contains!(IS_UNDEF, Undef); - - DataType::Mixed - } -} - -impl Display for DataType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - DataType::Undef => write!(f, "Undefined"), - DataType::Null => write!(f, "Null"), - DataType::False => write!(f, "False"), - DataType::True => write!(f, "True"), - DataType::Long => write!(f, "Long"), - DataType::Double => write!(f, "Double"), - DataType::String => write!(f, "String"), - DataType::Array => write!(f, "Array"), - DataType::Object(obj) => write!(f, "{}", obj.as_deref().unwrap_or("Object")), - DataType::Resource => write!(f, "Resource"), - DataType::Reference => write!(f, "Reference"), - DataType::Callable => write!(f, "Callable"), - DataType::ConstantExpression => write!(f, "Constant Expression"), - DataType::Void => write!(f, "Void"), - DataType::Bool => write!(f, "Bool"), - DataType::Mixed => write!(f, "Mixed"), - DataType::Ptr => write!(f, "Pointer"), - } - } -} - -#[cfg(test)] -mod tests { - use super::DataType; - use crate::bindings::{ - IS_ARRAY, IS_ARRAY_EX, IS_CALLABLE, IS_CONSTANT_AST, IS_CONSTANT_AST_EX, IS_DOUBLE, - IS_FALSE, IS_INTERNED_STRING_EX, IS_LONG, IS_NULL, IS_OBJECT, IS_OBJECT_EX, IS_REFERENCE, - IS_REFERENCE_EX, IS_RESOURCE, IS_RESOURCE_EX, IS_STRING, IS_STRING_EX, IS_TRUE, IS_UNDEF, - IS_VOID, - }; - use std::convert::TryFrom; - - #[test] - fn test_datatype() { - macro_rules! test { - ($c: ident, $t: ident) => { - assert_eq!(DataType::try_from($c), Ok(DataType::$t)); - }; - } - - test!(IS_UNDEF, Undef); - test!(IS_NULL, Null); - test!(IS_FALSE, False); - test!(IS_TRUE, True); - test!(IS_LONG, Long); - test!(IS_DOUBLE, Double); - test!(IS_STRING, String); - test!(IS_ARRAY, Array); - assert_eq!(DataType::try_from(IS_OBJECT), Ok(DataType::Object(None))); - test!(IS_RESOURCE, Resource); - test!(IS_REFERENCE, Reference); - test!(IS_CONSTANT_AST, ConstantExpression); - test!(IS_CALLABLE, Callable); - test!(IS_VOID, Void); - - test!(IS_INTERNED_STRING_EX, String); - test!(IS_STRING_EX, String); - test!(IS_ARRAY_EX, Array); - assert_eq!(DataType::try_from(IS_OBJECT_EX), Ok(DataType::Object(None))); - test!(IS_RESOURCE_EX, Resource); - test!(IS_REFERENCE_EX, Reference); - test!(IS_CONSTANT_AST_EX, ConstantExpression); - } -} diff --git a/src/php/exceptions.rs b/src/php/exceptions.rs deleted file mode 100644 index 87ce1b3cd1..0000000000 --- a/src/php/exceptions.rs +++ /dev/null @@ -1,203 +0,0 @@ -//! Contains all the base PHP throwables, including `Throwable` and `Exception`. - -use std::ffi::CString; - -use super::{class::ClassEntry, types::object::RegisteredClass}; -use crate::{ - bindings::{ - zend_ce_argument_count_error, zend_ce_arithmetic_error, zend_ce_compile_error, - zend_ce_division_by_zero_error, zend_ce_error_exception, zend_ce_exception, - zend_ce_parse_error, zend_ce_throwable, zend_ce_type_error, zend_ce_unhandled_match_error, - zend_ce_value_error, zend_throw_exception_ex, - }, - errors::{Error, Result}, - php::flags::ClassFlags, -}; - -/// Result type with the error variant as a [`PhpException`]. -pub type PhpResult = std::result::Result; - -/// Represents a PHP exception which can be thrown using the `throw()` function. Primarily used to -/// return from a [`Result`] which can immediately be thrown by the `ext-php-rs` -/// macro API. -/// -/// There are default [`From`] implementations for any type that implements [`ToString`], so these -/// can also be returned from these functions. You can also implement [`From`] for your custom -/// error type. -#[derive(Debug)] -pub struct PhpException { - message: String, - code: i32, - ex: &'static ClassEntry, -} - -impl PhpException { - /// Creates a new exception instance. - /// - /// # Parameters - /// - /// * `message` - Message to contain in the exception. - /// * `code` - Integer code to go inside the exception. - /// * `ex` - Exception type to throw. - pub fn new(message: String, code: i32, ex: &'static ClassEntry) -> Self { - Self { message, code, ex } - } - - /// Creates a new default exception instance, using the default PHP `Exception` type as the - /// exception type, with an integer code of zero. - /// - /// # Parameters - /// - /// * `message` - Message to contain in the exception. - pub fn default(message: String) -> Self { - Self::new(message, 0, ClassEntry::exception()) - } - - /// Creates an instance of an exception from a PHP class type and a message. - /// - /// # Parameters - /// - /// * `message` - Message to contain in the exception. - pub fn from_class(message: String) -> Self { - Self::new(message, 0, T::get_metadata().ce()) - } - - /// Throws the exception, returning nothing inside a result if successful and an error - /// otherwise. - pub fn throw(self) -> Result<()> { - throw_with_code(self.ex, self.code, &self.message) - } -} - -impl From for PhpException { - fn from(str: String) -> Self { - Self::default(str) - } -} - -impl From<&str> for PhpException { - fn from(str: &str) -> Self { - Self::default(str.into()) - } -} - -/// Throws an exception with a given message. See [`ClassEntry`] for some built-in exception -/// types. -/// -/// Returns a result containing nothing if the exception was successfully thown. -/// -/// # Parameters -/// -/// * `ex` - The exception type to throw. -/// * `message` - The message to display when throwing the exception. -/// -/// # Examples -/// -/// ```no_run -/// use ext_php_rs::php::{class::ClassEntry, exceptions::throw}; -/// -/// throw(ClassEntry::compile_error(), "This is a CompileError."); -/// ``` -pub fn throw(ex: &ClassEntry, message: &str) -> Result<()> { - throw_with_code(ex, 0, message) -} - -/// Throws an exception with a given message and status code. See [`ClassEntry`] for some built-in -/// exception types. -/// -/// Returns a result containing nothing if the exception was successfully thown. -/// -/// # Parameters -/// -/// * `ex` - The exception type to throw. -/// * `code` - The status code to use when throwing the exception. -/// * `message` - The message to display when throwing the exception. -/// -/// # Examples -/// -/// ```no_run -/// use ext_php_rs::php::{class::ClassEntry, exceptions::throw_with_code}; -/// -/// throw_with_code(ClassEntry::compile_error(), 123, "This is a CompileError."); -/// ``` -pub fn throw_with_code(ex: &ClassEntry, code: i32, message: &str) -> Result<()> { - let flags = ex.flags(); - - // Can't throw an interface or abstract class. - if flags.contains(ClassFlags::Interface) || flags.contains(ClassFlags::Abstract) { - return Err(Error::InvalidException(flags)); - } - - // SAFETY: We are given a reference to a `ClassEntry` therefore when we cast it to a pointer it - // will be valid. - unsafe { - zend_throw_exception_ex( - (ex as *const _) as *mut _, - code as _, - CString::new("%s")?.as_ptr(), - CString::new(message)?.as_ptr(), - ) - }; - Ok(()) -} - -// SAFETY: All default exceptions have been initialized by the time we should use these (in the module -// startup function). Note that they are not valid during the module init function, but rather than -// wrapping everything -#[allow(clippy::unwrap_used)] -impl ClassEntry { - /// Returns the base `Throwable` class. - pub fn throwable() -> &'static Self { - unsafe { zend_ce_throwable.as_ref() }.unwrap() - } - - /// Returns the base `Exception` class. - pub fn exception() -> &'static Self { - unsafe { zend_ce_exception.as_ref() }.unwrap() - } - - /// Returns the base `ErrorException` class. - pub fn error_exception() -> &'static Self { - unsafe { zend_ce_error_exception.as_ref() }.unwrap() - } - - /// Returns the base `CompileError` class. - pub fn compile_error() -> &'static Self { - unsafe { zend_ce_compile_error.as_ref() }.unwrap() - } - - /// Returns the base `ParseError` class. - pub fn parse_error() -> &'static Self { - unsafe { zend_ce_parse_error.as_ref() }.unwrap() - } - - /// Returns the base `TypeError` class. - pub fn type_error() -> &'static Self { - unsafe { zend_ce_type_error.as_ref() }.unwrap() - } - - /// Returns the base `ArgumentCountError` class. - pub fn argument_count_error() -> &'static Self { - unsafe { zend_ce_argument_count_error.as_ref() }.unwrap() - } - - /// Returns the base `ValueError` class. - pub fn value_error() -> &'static Self { - unsafe { zend_ce_value_error.as_ref() }.unwrap() - } - - /// Returns the base `ArithmeticError` class. - pub fn arithmetic_error() -> &'static Self { - unsafe { zend_ce_arithmetic_error.as_ref() }.unwrap() - } - - /// Returns the base `DivisionByZeroError` class. - pub fn division_by_zero_error() -> &'static Self { - unsafe { zend_ce_division_by_zero_error.as_ref() }.unwrap() - } - - /// Returns the base `UnhandledMatchError` class. - pub fn unhandled_match_error() -> &'static Self { - unsafe { zend_ce_unhandled_match_error.as_ref() }.unwrap() - } -} diff --git a/src/php/execution_data.rs b/src/php/execution_data.rs deleted file mode 100644 index 24bdeca8a5..0000000000 --- a/src/php/execution_data.rs +++ /dev/null @@ -1,129 +0,0 @@ -//! Functions for interacting with the execution data passed to PHP functions\ -//! introduced in Rust. - -use crate::bindings::{zend_execute_data, ZEND_MM_ALIGNMENT, ZEND_MM_ALIGNMENT_MASK}; - -use super::{ - args::ArgParser, - types::{ - object::{RegisteredClass, ZendClassObject, ZendObject}, - zval::Zval, - }, -}; - -/// Execution data passed when a function is called from Zend. -pub type ExecutionData = zend_execute_data; - -impl ExecutionData { - /// Returns an [`ArgParser`] pre-loaded with the arguments contained inside `self`. - pub fn parser<'a>(&'a mut self) -> ArgParser<'a, '_> { - self.parser_object().0 - } - - /// Returns an [`ArgParser`] pre-loaded with the arguments contained inside `self`. - /// - /// A reference to `$this` is also returned in an [`Option`], which resolves to [`None`] - /// if this function is not called inside a method. - pub fn parser_object<'a>(&'a mut self) -> (ArgParser<'a, '_>, Option<&'a mut ZendObject>) { - // SAFETY: All fields of the `u2` union are the same type. - let n_args = unsafe { self.This.u2.num_args }; - let mut args = vec![]; - - for i in 0..n_args { - // SAFETY: Function definition ensures arg lifetime doesn't exceed execution data lifetime. - let arg = unsafe { self.zend_call_arg(i as usize) }; - args.push(arg); - } - - let obj = self.This.object_mut(); - - (ArgParser::new(args), obj) - } - - /// Returns an [`ArgParser`] pre-loaded with the arguments contained inside `self`. - /// - /// A reference to `$this` is also returned in an [`Option`], which resolves to [`None`] - /// if this function is not called inside a method. - /// - /// This function differs from [`parse_object`] in the fact that it returns a reference to - /// a [`ZendClassObject`], which is an object that contains an arbitrary Rust type at the - /// start of the object. The object will also resolve to [`None`] if the function is called - /// inside a method that does not belong to an object with type `T`. - /// - /// [`parse_object`]: #method.parse_object - pub fn parser_method<'a, T: RegisteredClass>( - &'a mut self, - ) -> (ArgParser<'a, '_>, Option<&'a mut ZendClassObject>) { - let (parser, obj) = self.parser_object(); - ( - parser, - obj.and_then(|obj| ZendClassObject::from_zend_obj_mut(obj)), - ) - } - - /// Attempts to retrieve a reference to the underlying class object of the Zend object. - /// - /// Returns a [`ZendClassObject`] if the execution data contained a valid object of type `T`, - /// otherwise returns [`None`]. - pub fn get_object(&mut self) -> Option<&mut ZendClassObject> { - // TODO(david): This should be a `&mut self` function but we need to fix arg parser first. - ZendClassObject::from_zend_obj_mut(self.get_self()?) - } - - /// Attempts to retrieve the 'this' object, which can be used in class methods - /// to retrieve the underlying Zend object. - pub fn get_self(&mut self) -> Option<&mut ZendObject> { - // TODO(david): This should be a `&mut self` function but we need to fix arg parser first. - self.This.object_mut() - } - - /// Translation of macro `ZEND_CALL_ARG(call, n)` - /// zend_compile.h:578 - /// - /// The resultant zval reference has a lifetime equal to the lifetime of `self`. - /// This isn't specified because when you attempt to get a reference to args and - /// the `$this` object, Rust doesnt't let you. Since this is a private method it's - /// up to the caller to ensure the lifetime isn't exceeded. - #[doc(hidden)] - unsafe fn zend_call_arg<'a>(&self, n: usize) -> Option<&'a mut Zval> { - let ptr = self.zend_call_var_num(n as isize); - ptr.as_mut() - } - - /// Translation of macro `ZEND_CALL_VAR_NUM(call, n)` - /// zend_compile.h: 575 - #[doc(hidden)] - unsafe fn zend_call_var_num(&self, n: isize) -> *mut Zval { - let ptr = self as *const Self as *mut Zval; - ptr.offset(Self::zend_call_frame_slot() + n as isize) - } - - /// Translation of macro `ZEND_CALL_FRAME_SLOT` - /// zend_compile:573 - #[doc(hidden)] - fn zend_call_frame_slot() -> isize { - (Self::zend_mm_aligned_size::() + Self::zend_mm_aligned_size::() - 1) - / Self::zend_mm_aligned_size::() - } - - /// Translation of macro `ZEND_MM_ALIGNED_SIZE(size)` - /// zend_alloc.h:41 - #[doc(hidden)] - fn zend_mm_aligned_size() -> isize { - let size = std::mem::size_of::(); - ((size as isize) + ZEND_MM_ALIGNMENT as isize - 1) & ZEND_MM_ALIGNMENT_MASK as isize - } -} - -#[cfg(test)] -mod tests { - use super::ExecutionData; - - #[test] - fn test_zend_call_frame_slot() { - // PHP 8.0.2 (cli) (built: Feb 21 2021 11:51:33) ( NTS ) - // Copyright (c) The PHP Group - // Zend Engine v4.0.2, Copyright (c) Zend Technologies - assert_eq!(ExecutionData::zend_call_frame_slot(), 5); - } -} diff --git a/src/php/flags.rs b/src/php/flags.rs deleted file mode 100644 index 8f52b50063..0000000000 --- a/src/php/flags.rs +++ /dev/null @@ -1,158 +0,0 @@ -//! Bitflags used in PHP and the Zend engine. - -use bitflags::bitflags; - -use crate::bindings::{ - CONST_CS, CONST_DEPRECATED, CONST_NO_FILE_CACHE, CONST_PERSISTENT, IS_ARRAY, IS_CALLABLE, - IS_CONSTANT_AST, IS_DOUBLE, IS_FALSE, IS_LONG, IS_NULL, IS_OBJECT, IS_PTR, IS_REFERENCE, - IS_RESOURCE, IS_STRING, IS_TRUE, IS_TYPE_COLLECTABLE, IS_TYPE_REFCOUNTED, IS_UNDEF, IS_VOID, - ZEND_ACC_ABSTRACT, ZEND_ACC_ANON_CLASS, ZEND_ACC_CALL_VIA_TRAMPOLINE, ZEND_ACC_CHANGED, - ZEND_ACC_CLOSURE, ZEND_ACC_CONSTANTS_UPDATED, ZEND_ACC_CTOR, ZEND_ACC_DEPRECATED, - ZEND_ACC_DONE_PASS_TWO, ZEND_ACC_EARLY_BINDING, ZEND_ACC_FAKE_CLOSURE, ZEND_ACC_FINAL, - ZEND_ACC_GENERATOR, ZEND_ACC_HAS_FINALLY_BLOCK, ZEND_ACC_HAS_RETURN_TYPE, - ZEND_ACC_HAS_TYPE_HINTS, ZEND_ACC_HAS_UNLINKED_USES, ZEND_ACC_HEAP_RT_CACHE, - ZEND_ACC_IMMUTABLE, ZEND_ACC_IMPLICIT_ABSTRACT_CLASS, ZEND_ACC_INTERFACE, ZEND_ACC_LINKED, - ZEND_ACC_NEARLY_LINKED, ZEND_ACC_NEVER_CACHE, ZEND_ACC_NO_DYNAMIC_PROPERTIES, - ZEND_ACC_PRELOADED, ZEND_ACC_PRIVATE, ZEND_ACC_PROMOTED, ZEND_ACC_PROPERTY_TYPES_RESOLVED, - ZEND_ACC_PROTECTED, ZEND_ACC_PUBLIC, ZEND_ACC_RESOLVED_INTERFACES, ZEND_ACC_RESOLVED_PARENT, - ZEND_ACC_RETURN_REFERENCE, ZEND_ACC_REUSE_GET_ITERATOR, ZEND_ACC_STATIC, ZEND_ACC_STRICT_TYPES, - ZEND_ACC_TOP_LEVEL, ZEND_ACC_TRAIT, ZEND_ACC_TRAIT_CLONE, ZEND_ACC_UNRESOLVED_VARIANCE, - ZEND_ACC_USES_THIS, ZEND_ACC_USE_GUARDS, ZEND_ACC_VARIADIC, ZEND_HAS_STATIC_IN_METHODS, - Z_TYPE_FLAGS_SHIFT, -}; - -bitflags! { - /// Flags used for setting the type of Zval. - pub struct ZvalTypeFlags: u32 { - const Undef = IS_UNDEF; - const Null = IS_NULL; - const False = IS_FALSE; - const True = IS_TRUE; - const Long = IS_LONG; - const Double = IS_DOUBLE; - const String = IS_STRING; - const Array = IS_ARRAY; - const Object = IS_OBJECT; - const Resource = IS_RESOURCE; - const Reference = IS_REFERENCE; - const Callable = IS_CALLABLE; - const ConstantExpression = IS_CONSTANT_AST; - const Void = IS_VOID; - const Ptr = IS_PTR; - - const InternedStringEx = Self::String.bits; - const StringEx = Self::String.bits | Self::RefCounted.bits; - const ArrayEx = Self::Array.bits | Self::RefCounted.bits | Self::Collectable.bits; - const ObjectEx = Self::Object.bits | Self::RefCounted.bits | Self::Collectable.bits; - const ResourceEx = Self::Resource.bits | Self::RefCounted.bits; - const ReferenceEx = Self::Reference.bits | Self::RefCounted.bits; - const ConstantAstEx = Self::ConstantExpression.bits | Self::RefCounted.bits; - - const RefCounted = (IS_TYPE_REFCOUNTED << Z_TYPE_FLAGS_SHIFT); - const Collectable = (IS_TYPE_COLLECTABLE << Z_TYPE_FLAGS_SHIFT); - } -} - -bitflags! { - /// Flags for building classes. - pub struct ClassFlags: u32 { - const Final = ZEND_ACC_FINAL; - const Abstract = ZEND_ACC_ABSTRACT; - const Immutable = ZEND_ACC_IMMUTABLE; - const HasTypeHints = ZEND_ACC_HAS_TYPE_HINTS; - const TopLevel = ZEND_ACC_TOP_LEVEL; - const Preloaded = ZEND_ACC_PRELOADED; - - const Interface = ZEND_ACC_INTERFACE; - const Trait = ZEND_ACC_TRAIT; - const AnonymousClass = ZEND_ACC_ANON_CLASS; - const Linked = ZEND_ACC_LINKED; - const ImplicitAbstractClass = ZEND_ACC_IMPLICIT_ABSTRACT_CLASS; - const UseGuards = ZEND_ACC_USE_GUARDS; - const ConstantsUpdated = ZEND_ACC_CONSTANTS_UPDATED; - const NoDynamicProperties = ZEND_ACC_NO_DYNAMIC_PROPERTIES; - const HasStaticInMethods = ZEND_HAS_STATIC_IN_METHODS; - const PropertyTypesResolved = ZEND_ACC_PROPERTY_TYPES_RESOLVED; - const ReuseGetIterator = ZEND_ACC_REUSE_GET_ITERATOR; - const ResolvedParent = ZEND_ACC_RESOLVED_PARENT; - const ResolvedInterfaces = ZEND_ACC_RESOLVED_INTERFACES; - const UnresolvedVariance = ZEND_ACC_UNRESOLVED_VARIANCE; - const NearlyLinked = ZEND_ACC_NEARLY_LINKED; - const HasUnlinkedUses = ZEND_ACC_HAS_UNLINKED_USES; - } -} - -bitflags! { - /// Flags for building methods. - pub struct MethodFlags: u32 { - const Public = ZEND_ACC_PUBLIC; - const Protected = ZEND_ACC_PROTECTED; - const Private = ZEND_ACC_PRIVATE; - const Changed = ZEND_ACC_CHANGED; - const Static = ZEND_ACC_STATIC; - const Final = ZEND_ACC_FINAL; - const Abstract = ZEND_ACC_ABSTRACT; - const Immutable = ZEND_ACC_IMMUTABLE; - const HasTypeHints = ZEND_ACC_HAS_TYPE_HINTS; - const TopLevel = ZEND_ACC_TOP_LEVEL; - const Preloaded = ZEND_ACC_PRELOADED; - - const Deprecated = ZEND_ACC_DEPRECATED; - const ReturnReference = ZEND_ACC_RETURN_REFERENCE; - const HasReturnType = ZEND_ACC_HAS_RETURN_TYPE; - const Variadic = ZEND_ACC_VARIADIC; - const HasFinallyBlock = ZEND_ACC_HAS_FINALLY_BLOCK; - const EarlyBinding = ZEND_ACC_EARLY_BINDING; - const UsesThis = ZEND_ACC_USES_THIS; - const CallViaTrampoline = ZEND_ACC_CALL_VIA_TRAMPOLINE; - const NeverCache = ZEND_ACC_NEVER_CACHE; - const TraitClone = ZEND_ACC_TRAIT_CLONE; - const IsConstructor = ZEND_ACC_CTOR; - const Closure = ZEND_ACC_CLOSURE; - const FakeClosure = ZEND_ACC_FAKE_CLOSURE; - const Generator = ZEND_ACC_GENERATOR; - const DonePassTwo = ZEND_ACC_DONE_PASS_TWO; - const HeapRTCache = ZEND_ACC_HEAP_RT_CACHE; - const StrictTypes = ZEND_ACC_STRICT_TYPES; - } -} - -bitflags! { - /// Flags for building properties. - pub struct PropertyFlags: u32 { - const Public = ZEND_ACC_PUBLIC; - const Protected = ZEND_ACC_PROTECTED; - const Private = ZEND_ACC_PRIVATE; - const Changed = ZEND_ACC_CHANGED; - const Static = ZEND_ACC_STATIC; - const Promoted = ZEND_ACC_PROMOTED; - } -} - -bitflags! { - /// Flags for building constants. - pub struct ConstantFlags: u32 { - const Public = ZEND_ACC_PUBLIC; - const Protected = ZEND_ACC_PROTECTED; - const Private = ZEND_ACC_PRIVATE; - const Promoted = ZEND_ACC_PROMOTED; - } -} - -bitflags! { - /// Flags for building module global constants. - pub struct GlobalConstantFlags: u32 { - const CaseSensitive = CONST_CS; - const Persistent = CONST_PERSISTENT; - const NoFileCache = CONST_NO_FILE_CACHE; - const Deprecated = CONST_DEPRECATED; - } -} - -bitflags! { - /// Represents the result of a function. - pub struct ZendResult: i32 { - const Success = 0; - const Failure = -1; - } -} diff --git a/src/php/globals.rs b/src/php/globals.rs deleted file mode 100644 index ee241a9eff..0000000000 --- a/src/php/globals.rs +++ /dev/null @@ -1,47 +0,0 @@ -//! Types related to the PHP executor globals. - -use crate::bindings::{_zend_executor_globals, ext_php_rs_executor_globals}; - -use super::types::{array::HashTable, object::ZendObject}; - -/// Stores global variables used in the PHP executor. -pub type ExecutorGlobals = _zend_executor_globals; - -impl ExecutorGlobals { - /// Returns a static reference to the PHP executor globals. - pub fn get() -> &'static Self { - // SAFETY: PHP executor globals are statically declared therefore should never - // return an invalid pointer. - unsafe { ext_php_rs_executor_globals().as_ref() } - .expect("Static executor globals were invalid") - } - - fn get_mut() -> &'static mut Self { - // SAFETY: PHP executor globals are statically declared therefore should never - // return an invalid pointer. - // TODO: Should this be syncronized? - unsafe { ext_php_rs_executor_globals().as_mut() } - .expect("Static executor globals were invalid") - } - - /// Attempts to retrieve the global class hash table. - pub fn class_table(&self) -> Option<&HashTable> { - unsafe { self.class_table.as_ref() } - } - - /// Attempts to extract the last PHP exception captured by the interpreter. - /// - /// Note that the caller is responsible for freeing the memory here or it'll leak. - pub fn take_exception() -> Option<*mut ZendObject> { - let globals = Self::get_mut(); - - let mut exception_ptr = std::ptr::null_mut(); - std::mem::swap(&mut exception_ptr, &mut globals.exception); - - if !exception_ptr.is_null() { - Some(exception_ptr) - } else { - None - } - } -} diff --git a/src/php/mod.rs b/src/php/mod.rs deleted file mode 100644 index 0c8f8b7f75..0000000000 --- a/src/php/mod.rs +++ /dev/null @@ -1,16 +0,0 @@ -//! Objects relating to PHP and the Zend engine. - -pub mod alloc; -pub mod args; -pub mod boxed; -pub mod class; -pub mod constants; -pub mod enums; -pub mod exceptions; -pub mod execution_data; -pub mod flags; -pub mod function; -pub mod globals; -pub mod module; -pub mod pack; -pub mod types; diff --git a/src/php/pack.rs b/src/php/pack.rs deleted file mode 100644 index 1572ac067b..0000000000 --- a/src/php/pack.rs +++ /dev/null @@ -1,97 +0,0 @@ -//! Provides implementations for converting to and from Zend binary strings, commonly returned -//! from functions such as [`pack`] and [`unpack`]. -//! -//! [`pack`]: https://www.php.net/manual/en/function.pack.php -//! [`unpack`]: https://www.php.net/manual/en/function.unpack.php - -use crate::bindings::{ext_php_rs_zend_string_init, zend_string}; - -/// Used to convert between Zend binary strings and vectors. Useful in conjunction with the -/// [`pack`] and [`unpack`] functions built-in to PHP. -/// -/// # Safety -/// -/// The types cannot be ensured between PHP and Rust, as the data is represented as a string when -/// crossing the language boundary. Exercise caution when using these functions. -/// -/// [`pack`]: https://www.php.net/manual/en/function.pack.php -/// [`unpack`]: https://www.php.net/manual/en/function.unpack.php -pub unsafe trait Pack: Clone { - /// Packs a given vector into a Zend binary string. Can be passed to PHP and then unpacked - /// using the [`unpack`] function. - /// - /// # Parameters - /// - /// * `vec` - The vector to pack into a binary string. - /// - /// [`unpack`]: https://www.php.net/manual/en/function.unpack.php - fn pack_into(vec: Vec) -> *mut zend_string; - - /// Unpacks a given Zend binary string into a Rust vector. Can be used to pass data from `pack` - /// in PHP to Rust without encoding into another format. Note that the data *must* be all one - /// type, as this implementation only unpacks one type. - /// - /// # Safety - /// - /// There is no way to tell if the data stored in the string is actually of the given type. - /// The results of this function can also differ from platform-to-platform due to the different - /// representation of some types on different platforms. Consult the [`pack`] function - /// documentation for more details. - /// - /// # Parameters - /// - /// * `s` - The Zend string containing the binary data. - /// - /// [`pack`]: https://www.php.net/manual/en/function.pack.php - fn unpack_into(s: &zend_string) -> Vec; -} - -/// Implements the [`Pack`] trait for a given type. -macro_rules! pack_impl { - ($t: ty) => { - pack_impl!($t, <$t>::BITS); - }; - - ($t: ty, $d: expr) => { - unsafe impl Pack for $t { - fn pack_into(vec: Vec) -> *mut zend_string { - let len = vec.len() * ($d as usize / 8); - let ptr = Box::into_raw(vec.into_boxed_slice()); - unsafe { ext_php_rs_zend_string_init(ptr as *mut i8, len as _, false) } - } - - fn unpack_into(s: &zend_string) -> Vec { - let bytes = ($d / 8) as u64; - let len = (s.len as u64) / bytes; - let mut result = Vec::with_capacity(len as _); - let ptr = s.val.as_ptr() as *const $t; - - // SAFETY: We calculate the length of memory that we can legally read based on the - // side of the type, therefore we never read outside the memory we should. - for i in 0..len { - result.push(unsafe { *ptr.offset(i as _) }); - } - - result - } - } - }; -} - -pack_impl!(u8); -pack_impl!(i8); - -pack_impl!(u16); -pack_impl!(i16); - -pack_impl!(u32); -pack_impl!(i32); - -pack_impl!(u64); -pack_impl!(i64); - -pack_impl!(isize); -pack_impl!(usize); - -pack_impl!(f32, 32); -pack_impl!(f64, 64); diff --git a/src/php/types/binary.rs b/src/php/types/binary.rs deleted file mode 100644 index 9bdfe1d4ca..0000000000 --- a/src/php/types/binary.rs +++ /dev/null @@ -1,88 +0,0 @@ -//! Types relating to binary data transmission between Rust and PHP. - -use std::{ - convert::TryFrom, - iter::FromIterator, - ops::{Deref, DerefMut}, -}; - -use crate::{ - errors::{Error, Result}, - php::{enums::DataType, pack::Pack}, -}; - -use super::zval::{FromZval, IntoZval, Zval}; - -/// Acts as a wrapper around [`Vec`] where `T` implements [`Pack`]. Primarily used for passing -/// binary data into Rust functions. Can be treated as a [`Vec`] in most situations, or can be -/// 'unwrapped' into a [`Vec`] through the [`From`] implementation on [`Vec`]. -#[derive(Debug)] -pub struct Binary(Vec); - -impl Binary { - /// Creates a new binary wrapper from a set of data which can be converted into a vector. - /// - /// # Parameters - /// - /// * `data` - Data to store inside the binary wrapper. - pub fn new(data: impl Into>) -> Self { - Self(data.into()) - } -} - -impl Deref for Binary { - type Target = Vec; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for Binary { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl FromZval<'_> for Binary { - const TYPE: DataType = DataType::String; - - fn from_zval(zval: &Zval) -> Option { - zval.binary().map(Binary) - } -} - -impl TryFrom for Binary { - type Error = Error; - - fn try_from(value: Zval) -> Result { - Self::from_zval(&value).ok_or_else(|| Error::ZvalConversion(value.get_type())) - } -} - -impl IntoZval for Binary { - const TYPE: DataType = DataType::String; - - fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> { - zv.set_binary(self.0); - Ok(()) - } -} - -impl From> for Vec { - fn from(value: Binary) -> Self { - value.0 - } -} - -impl From> for Binary { - fn from(value: Vec) -> Self { - Self::new(value) - } -} - -impl FromIterator for Binary { - fn from_iter>(iter: U) -> Self { - Self(iter.into_iter().collect::>()) - } -} diff --git a/src/php/types/long.rs b/src/php/types/long.rs deleted file mode 100644 index 1ba3d7af21..0000000000 --- a/src/php/types/long.rs +++ /dev/null @@ -1,9 +0,0 @@ -//! Represents an integer introduced in PHP. Note that the size of this integer differs. -//! On a 32-bit system, a ZendLong is 32-bits, while on a 64-bit system it is 64-bits. - -use crate::bindings::zend_long; - -/// Internal identifier used for a long. -/// The size depends on the system architecture. On 32-bit systems, -/// a ZendLong is 32-bits, while on a 64-bit system it is 64-bits. -pub type ZendLong = zend_long; diff --git a/src/php/types/object.rs b/src/php/types/object.rs deleted file mode 100644 index dc15820f5e..0000000000 --- a/src/php/types/object.rs +++ /dev/null @@ -1,1074 +0,0 @@ -//! Represents an object in PHP. Allows for overriding the internal object used by classes, -//! allowing users to store Rust data inside a PHP object. - -use std::{ - collections::HashMap, - convert::TryInto, - ffi::c_void, - fmt::Debug, - marker::PhantomData, - mem::{self, MaybeUninit}, - ops::{Deref, DerefMut}, - os::raw::c_int, - ptr::{self, NonNull}, - sync::atomic::{AtomicBool, AtomicPtr, Ordering}, -}; - -use crate::{ - bindings::{ - ext_php_rs_zend_object_alloc, ext_php_rs_zend_object_release, object_properties_init, - std_object_handlers, zend_call_known_function, zend_is_true, zend_object, - zend_object_handlers, zend_object_std_dtor, zend_object_std_init, - zend_objects_clone_members, zend_objects_new, zend_standard_class_def, - zend_std_get_properties, zend_std_has_property, zend_std_read_property, - zend_std_write_property, zend_string, HashTable, ZEND_ISEMPTY, ZEND_PROPERTY_EXISTS, - ZEND_PROPERTY_ISSET, - }, - errors::{Error, Result}, - php::{ - boxed::{ZBox, ZBoxable}, - class::ClassEntry, - enums::DataType, - exceptions::{PhpException, PhpResult}, - execution_data::ExecutionData, - flags::ZvalTypeFlags, - function::FunctionBuilder, - globals::ExecutorGlobals, - }, -}; - -use super::{ - props::Property, - rc::PhpRc, - string::ZendStr, - zval::{FromZval, FromZvalMut, IntoZval, Zval}, -}; - -pub type ZendObject = zend_object; -pub type ZendObjectHandlers = zend_object_handlers; - -/// Different ways to query if a property exists. -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -#[repr(u32)] -pub enum PropertyQuery { - /// Property exists and is not NULL. - Isset = ZEND_PROPERTY_ISSET, - /// Property is not empty. - NotEmpty = ZEND_ISEMPTY, - /// Property exists. - Exists = ZEND_PROPERTY_EXISTS, -} - -impl ZendObject { - /// Creates a new [`ZendObject`], returned inside an [`ZBox`] wrapper. - /// - /// # Parameters - /// - /// * `ce` - The type of class the new object should be an instance of. - /// - /// # Panics - /// - /// Panics when allocating memory for the new object fails. - pub fn new(ce: &ClassEntry) -> ZBox { - // SAFETY: Using emalloc to allocate memory inside Zend arena. Casting `ce` to `*mut` is valid - // as the function will not mutate `ce`. - unsafe { - let ptr = zend_objects_new(ce as *const _ as *mut _); - ZBox::from_raw( - ptr.as_mut() - .expect("Failed to allocate memory for Zend object"), - ) - } - } - - /// Creates a new `stdClass` instance, returned inside an [`ZBox`] wrapper. - /// - /// # Panics - /// - /// Panics if allocating memory for the object fails, or if the `stdClass` class entry has not been - /// registered with PHP yet. - pub fn new_stdclass() -> ZBox { - // SAFETY: This will be `NULL` until it is initialized. `as_ref()` checks for null, - // so we can panic if it's null. - Self::new(unsafe { - zend_standard_class_def - .as_ref() - .expect("`stdClass` class instance not initialized yet") - }) - } - - /// Converts the class object into an owned [`ZendObject`]. This removes any possibility of - /// accessing the underlying attached Rust struct. - pub fn from_class_object(obj: ZBox>) -> ZBox { - let this = obj.into_raw(); - // SAFETY: Consumed box must produce a well-aligned non-null pointer. - unsafe { ZBox::from_raw(&mut this.std) } - } - - /// Attempts to retrieve the class name of the object. - pub fn get_class_name(&self) -> Result { - unsafe { - self.handlers()? - .get_class_name - .and_then(|f| f(self).as_ref()) - .ok_or(Error::InvalidScope) - .and_then(|s| s.try_into()) - } - } - - /// Checks if the given object is an instance of a registered class with Rust - /// type `T`. - pub fn is_instance(&self) -> bool { - (self.ce as *const ClassEntry).eq(&(T::get_metadata().ce() as *const _)) - } - - /// Attempts to read a property from the Object. Returns a result containing the - /// value of the property if it exists and can be read, and an [`Error`] otherwise. - /// - /// # Parameters - /// - /// * `name` - The name of the property. - /// * `query` - The type of query to use when attempting to get a property. - pub fn get_property<'a, T>(&'a self, name: &str) -> Result - where - T: FromZval<'a>, - { - if !self.has_property(name, PropertyQuery::Exists)? { - return Err(Error::InvalidProperty); - } - - let mut name = ZendStr::new(name, false)?; - let mut rv = Zval::new(); - - let zv = unsafe { - self.handlers()?.read_property.ok_or(Error::InvalidScope)?( - self.mut_ptr(), - name.deref_mut(), - 1, - std::ptr::null_mut(), - &mut rv, - ) - .as_ref() - } - .ok_or(Error::InvalidScope)?; - - T::from_zval(zv).ok_or_else(|| Error::ZvalConversion(zv.get_type())) - } - - /// Attempts to set a property on the object. - /// - /// # Parameters - /// - /// * `name` - The name of the property. - /// * `value` - The value to set the property to. - pub fn set_property(&mut self, name: &str, value: impl IntoZval) -> Result<()> { - let mut name = ZendStr::new(name, false)?; - let mut value = value.into_zval(false)?; - - unsafe { - self.handlers()?.write_property.ok_or(Error::InvalidScope)?( - self, - name.deref_mut(), - &mut value, - std::ptr::null_mut(), - ) - .as_ref() - } - .ok_or(Error::InvalidScope)?; - Ok(()) - } - - /// Checks if a property exists on an object. Takes a property name and query parameter, - /// which defines what classifies if a property exists or not. See [`PropertyQuery`] for - /// more information. - /// - /// # Parameters - /// - /// * `name` - The name of the property. - /// * `query` - The 'query' to classify if a property exists. - pub fn has_property(&self, name: &str, query: PropertyQuery) -> Result { - let mut name = ZendStr::new(name, false)?; - - Ok(unsafe { - self.handlers()?.has_property.ok_or(Error::InvalidScope)?( - self.mut_ptr(), - name.deref_mut(), - query as _, - std::ptr::null_mut(), - ) - } > 0) - } - - /// Attempts to retrieve the properties of the object. Returned inside a Zend Hashtable. - pub fn get_properties(&self) -> Result<&HashTable> { - unsafe { - self.handlers()? - .get_properties - .and_then(|props| props(self.mut_ptr()).as_ref()) - .ok_or(Error::InvalidScope) - } - } - - /// Attempts to retrieve a reference to the object handlers. - #[inline] - unsafe fn handlers(&self) -> Result<&ZendObjectHandlers> { - self.handlers.as_ref().ok_or(Error::InvalidScope) - } - - /// Returns a mutable pointer to `self`, regardless of the type of reference. - /// Only to be used in situations where a C function requires a mutable pointer - /// but does not modify the underlying data. - #[inline] - fn mut_ptr(&self) -> *mut Self { - (self as *const Self) as *mut Self - } - - /// Extracts some type from a Zend object. - /// - /// This is a wrapper function around `FromZendObject::extract()`. - pub fn extract<'a, T>(&'a self) -> Result - where - T: FromZendObject<'a>, - { - T::from_zend_object(self) - } -} - -unsafe impl ZBoxable for ZendObject { - fn free(&mut self) { - unsafe { ext_php_rs_zend_object_release(self) } - } -} - -impl Debug for ZendObject { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut dbg = f.debug_struct( - self.get_class_name() - .unwrap_or_else(|_| "ZendObject".to_string()) - .as_str(), - ); - - if let Ok(props) = self.get_properties() { - for (id, key, val) in props.iter() { - dbg.field(key.unwrap_or_else(|| id.to_string()).as_str(), val); - } - } - - dbg.finish() - } -} - -impl<'a> FromZval<'a> for &'a ZendObject { - const TYPE: DataType = DataType::Object(None); - - fn from_zval(zval: &'a Zval) -> Option { - zval.object() - } -} - -impl IntoZval for ZBox { - const TYPE: DataType = DataType::Object(None); - - fn set_zval(mut self, zv: &mut Zval, _: bool) -> Result<()> { - // We must decrement the refcounter on the object before inserting into the zval, - // as the reference counter will be incremented on add. - // NOTE(david): again is this needed, we increment in `set_object`. - self.dec_count(); - zv.set_object(self.into_raw()); - Ok(()) - } -} - -/// `FromZendObject` is implemented by types which can be extracted from a Zend object. -/// -/// Normal usage is through the helper method `ZendObject::extract`: -/// -/// ```rust,ignore -/// let obj: ZendObject = ...; -/// let repr: String = obj.extract(); -/// let props: HashMap = obj.extract(); -/// ``` -/// -/// Should be functionally equivalent to casting an object to another compatable type. -pub trait FromZendObject<'a>: Sized { - /// Extracts `Self` from the source `ZendObject`. - fn from_zend_object(obj: &'a ZendObject) -> Result; -} - -/// Implemented on types which can be extracted from a mutable zend object. -/// -/// If `Self` does not require the object to be mutable, it should implement -/// [`FromZendObject`] instead, as this trait is generically implemented for -/// any types that also implement [`FromZendObject`]. -pub trait FromZendObjectMut<'a>: Sized { - /// Extracts `Self` from the source `ZendObject`. - fn from_zend_object_mut(obj: &'a mut ZendObject) -> Result; -} - -/// Implemented on types which can be converted into a Zend object. It is up to the implementation -/// to determine the type of object which is produced. -pub trait IntoZendObject { - /// Attempts to convert `self` into a Zend object. - fn into_zend_object(self) -> Result>; -} - -impl FromZendObject<'_> for String { - fn from_zend_object(obj: &ZendObject) -> Result { - let mut ret = Zval::new(); - unsafe { - zend_call_known_function( - (*obj.ce).__tostring, - obj as *const _ as *mut _, - obj.ce, - &mut ret, - 0, - std::ptr::null_mut(), - std::ptr::null_mut(), - ); - } - - if let Some(err) = ExecutorGlobals::take_exception() { - // TODO: become an error - let class_name = obj.get_class_name(); - panic!( - "Uncaught exception during call to {}::__toString(): {:?}", - class_name.expect("unable to determine class name"), - err - ); - } else if let Some(output) = ret.extract() { - Ok(output) - } else { - // TODO: become an error - let class_name = obj.get_class_name(); - panic!( - "{}::__toString() must return a string", - class_name.expect("unable to determine class name"), - ); - } - } -} - -/// Wrapper struct used to return a reference to a PHP object. -pub struct ClassRef<'a, T: RegisteredClass + 'a> { - ptr: &'a mut ZendClassObject, -} - -impl<'a, T: RegisteredClass> ClassRef<'a, T> { - /// Creates a new class reference from a Rust type reference. - pub fn from_ref(obj: &'a T) -> Option { - let ptr = unsafe { ZendClassObject::from_obj_ptr(obj)? }; - Some(Self { ptr }) - } -} - -impl<'a, T: RegisteredClass> IntoZval for ClassRef<'a, T> { - const TYPE: DataType = DataType::Object(Some(T::CLASS_NAME)); - - #[inline] - fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> { - zv.set_object(&mut self.ptr.std); - Ok(()) - } -} - -impl From>> for ZBox { - #[inline] - fn from(obj: ZBox>) -> Self { - ZendObject::from_class_object(obj) - } -} - -impl Default for ZBox> { - #[inline] - fn default() -> Self { - ZendClassObject::new(T::default()) - } -} - -impl Clone for ZBox> { - fn clone(&self) -> Self { - // SAFETY: All constructors of `NewClassObject` guarantee that it will contain a valid pointer. - // The constructor also guarantees that the internal `ZendClassObject` pointer will contain a valid, - // initialized `obj`, therefore we can dereference both safely. - unsafe { - let mut new = ZendClassObject::new((&***self).clone()); - zend_objects_clone_members(&mut new.std, &self.std as *const _ as *mut _); - new - } - } -} - -impl Debug for ZendClassObject { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - (&**self).fmt(f) - } -} - -impl IntoZval for ZBox> { - const TYPE: DataType = DataType::Object(Some(T::CLASS_NAME)); - - fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> { - let obj = self.into_raw(); - zv.set_object(&mut obj.std); - Ok(()) - } -} - -/// Object constructor metadata. -pub struct ConstructorMeta { - /// Constructor function. - pub constructor: fn(&mut ExecutionData) -> ConstructorResult, - /// Function called to build the constructor function. Usually adds arguments. - pub build_fn: fn(FunctionBuilder) -> FunctionBuilder, -} - -/// Implemented on Rust types which are exported to PHP. Allows users to get and set PHP properties on -/// the object. -pub trait RegisteredClass: Sized -where - Self: 'static, -{ - /// PHP class name of the registered class. - const CLASS_NAME: &'static str; - - /// Optional class constructor. - const CONSTRUCTOR: Option> = None; - - /// Returns a reference to the class metadata, which stores the class entry and handlers. - /// - /// This must be statically allocated, and is usually done through the [`macro@php_class`] - /// macro. - /// - /// [`macro@php_class`]: crate::php_class - fn get_metadata() -> &'static ClassMetadata; - - /// Attempts to retrieve a property from the class object. - /// - /// # Parameters - /// - /// * `name` - The name of the property. - /// - /// # Returns - /// - /// Returns a given type `T` inside an option which is the value of the zval, or [`None`] - /// if the property could not be found. - /// - /// # Safety - /// - /// Caller must guarantee that the object the function is called on is immediately followed - /// by a [`zend_object`], which is true when the object was instantiated by PHP. - unsafe fn get_property<'a, T: FromZval<'a>>(&'a self, name: &str) -> Option { - let obj = ZendClassObject::::from_obj_ptr(self)?; - obj.std.get_property(name).ok() - } - - /// Attempts to set the value of a property on the class object. - /// - /// # Parameters - /// - /// * `name` - The name of the property to set. - /// * `value` - The value to set the property to. - /// - /// # Returns - /// - /// Returns nothing in an option if the property was successfully set. Returns none if setting - /// the value failed. - /// - /// # Safety - /// - /// Caller must guarantee that the object the function is called on is immediately followed - /// by a [`zend_object`], which is true when the object was instantiated by PHP. - unsafe fn set_property(&mut self, name: &str, value: impl IntoZval) -> Option<()> { - let obj = ZendClassObject::::from_obj_ptr(self)?; - obj.std.set_property(name, value).ok()?; - Some(()) - } - - /// Returns a hash table containing the properties of the class. - /// - /// The key should be the name of the property and the value should be a reference to the property - /// with reference to `self`. The value is a [`Property`]. - fn get_properties<'a>() -> HashMap<&'static str, Property<'a, Self>>; -} - -/// Representation of a Zend class object in memory. -#[repr(C)] -pub struct ZendClassObject { - obj: Option, - std: zend_object, -} - -impl ZendClassObject { - /// Creates a new [`ZendClassObject`] of type `T`, where `T` is a [`RegisteredClass`] in PHP, storing the - /// given value `val` inside the object. - /// - /// # Parameters - /// - /// * `val` - The value to store inside the object. - /// - /// # Panics - /// - /// Panics if memory was unable to be allocated for the new object. - pub fn new(val: T) -> ZBox { - unsafe { Self::internal_new(Some(val)) } - } - - /// Creates a new [`ZendClassObject`] of type `T`, with an uninitialized internal object. - /// - /// # Safety - /// - /// As the object is uninitialized, the caller must ensure the following until the internal object is - /// initialized: - /// - /// * The object is never dereferenced to `T`. - /// * The [`Clone`] implementation is never called. - /// * The [`Debug`] implementation is never called. - /// - /// If any of these conditions are not met while not initialized, the corresponding function will panic. - /// Converting the object into its inner pointer with the [`into_raw`] function is valid, however. - /// - /// [`into_raw`]: #method.into_raw - /// - /// # Panics - /// - /// Panics if memory was unable to be allocated for the new object. - pub unsafe fn new_uninit() -> ZBox { - Self::internal_new(None) - } - - /// Creates a new [`ZendObject`] of type `T`, storing the given (and potentially uninitialized) `val` - /// inside the object. - /// - /// # Parameters - /// - /// * `val` - Value to store inside the object. See safety section. - /// * `init` - Whether the given `val` was initialized. - /// - /// # Safety - /// - /// Providing an initialized variant of [`MaybeUninit`] is safe. - /// - /// Providing an uninitalized variant of [`MaybeUninit`] is unsafe. As the object is uninitialized, - /// the caller must ensure the following until the internal object is initialized: - /// - /// * The object is never dereferenced to `T`. - /// * The [`Clone`] implementation is never called. - /// * The [`Debug`] implementation is never called. - /// - /// If any of these conditions are not met while not initialized, the corresponding function will panic. - /// Converting the object into its inner with the [`into_raw`] function is valid, however. You can initialize - /// the object with the [`initialize`] function. - /// - /// [`into_raw`]: #method.into_raw - /// [`initialize`]: #method.initialize - /// - /// # Panics - /// - /// Panics if memory was unable to be allocated for the new object. - unsafe fn internal_new(val: Option) -> ZBox { - let size = mem::size_of::>(); - let meta = T::get_metadata(); - let ce = meta.ce() as *const _ as *mut _; - let obj = ext_php_rs_zend_object_alloc(size as _, ce) as *mut ZendClassObject; - let obj = obj - .as_mut() - .expect("Failed to allocate for new Zend object"); - - zend_object_std_init(&mut obj.std, ce); - object_properties_init(&mut obj.std, ce); - - // SAFETY: `obj` is non-null and well aligned as it is a reference. - // As the data in `obj.obj` is uninitalized, we don't want to drop - // the data, but directly override it. - ptr::write(&mut obj.obj, val); - - obj.std.handlers = meta.handlers(); - ZBox::from_raw(obj) - } - - /// Initializes the class object with the value `val`. - /// - /// # Parameters - /// - /// * `val` - The value to initialize the object with. - /// - /// # Returns - /// - /// Returns the old value in an [`Option`] if the object had already been initialized, [`None`] - /// otherwise. - pub fn initialize(&mut self, val: T) -> Option { - self.obj.replace(val) - } - - /// Returns a reference to the [`ZendClassObject`] of a given object `T`. Returns [`None`] - /// if the given object is not of the type `T`. - /// - /// # Parameters - /// - /// * `obj` - The object to get the [`ZendClassObject`] for. - /// - /// # Safety - /// - /// Caller must guarantee that the given `obj` was created by Zend, which means that it - /// is immediately followed by a [`zend_object`]. - pub(crate) unsafe fn from_obj_ptr(obj: &T) -> Option<&mut Self> { - // TODO(david): Remove this function - let ptr = (obj as *const T as *mut Self).as_mut()?; - - if ptr.std.is_instance::() { - Some(ptr) - } else { - None - } - } - - /// Returns a mutable reference to the [`ZendClassObject`] of a given zend object `obj`. - /// Returns [`None`] if the given object is not of the type `T`. - /// - /// # Parameters - /// - /// * `obj` - The zend object to get the [`ZendClassObject`] for. - pub fn from_zend_obj(std: &zend_object) -> Option<&Self> { - Some(Self::_from_zend_obj(std)?) - } - - /// Returns a mutable reference to the [`ZendClassObject`] of a given zend object `obj`. - /// Returns [`None`] if the given object is not of the type `T`. - /// - /// # Parameters - /// - /// * `obj` - The zend object to get the [`ZendClassObject`] for. - pub fn from_zend_obj_mut(std: &mut zend_object) -> Option<&mut Self> { - Self::_from_zend_obj(std) - } - - fn _from_zend_obj(std: &zend_object) -> Option<&mut Self> { - let std = std as *const zend_object as *const i8; - let ptr = unsafe { - let ptr = std.offset(0 - Self::std_offset() as isize) as *const Self; - (ptr as *mut Self).as_mut()? - }; - - if ptr.std.is_instance::() { - Some(ptr) - } else { - None - } - } - - /// Returns a mutable reference to the underlying Zend object. - pub fn get_mut_zend_obj(&mut self) -> &mut zend_object { - &mut self.std - } - - /// Returns the offset of the `std` property in the class object. - pub(crate) fn std_offset() -> usize { - unsafe { - let null = NonNull::::dangling(); - let base = null.as_ref() as *const Self; - let std = &null.as_ref().std as *const zend_object; - - (std as usize) - (base as usize) - } - } -} - -impl<'a, T: RegisteredClass> FromZval<'a> for &'a ZendClassObject { - const TYPE: DataType = DataType::Object(Some(T::CLASS_NAME)); - - fn from_zval(zval: &'a Zval) -> Option { - Self::from_zend_object(zval.object()?).ok() - } -} - -impl<'a, T: RegisteredClass> FromZendObject<'a> for &'a ZendClassObject { - fn from_zend_object(obj: &'a ZendObject) -> Result { - // TODO(david): replace with better error - ZendClassObject::from_zend_obj(obj).ok_or(Error::InvalidScope) - } -} - -impl<'a, T: RegisteredClass> FromZvalMut<'a> for &'a mut ZendClassObject { - const TYPE: DataType = DataType::Object(Some(T::CLASS_NAME)); - - fn from_zval_mut(zval: &'a mut Zval) -> Option { - Self::from_zend_object_mut(zval.object_mut()?).ok() - } -} - -impl<'a, T: RegisteredClass> FromZendObjectMut<'a> for &'a mut ZendClassObject { - fn from_zend_object_mut(obj: &'a mut ZendObject) -> Result { - ZendClassObject::from_zend_obj_mut(obj).ok_or(Error::InvalidScope) - } -} - -unsafe impl ZBoxable for ZendClassObject { - fn free(&mut self) { - // SAFETY: All constructors guarantee that `self` contains a valid pointer. Further, all constructors - // guarantee that the `std` field of `ZendClassObject` will be initialized. - unsafe { ext_php_rs_zend_object_release(&mut self.std) } - } -} - -impl Deref for ZendClassObject { - type Target = T; - - fn deref(&self) -> &Self::Target { - self.obj - .as_ref() - .expect("Attempted to access uninitalized class object") - } -} - -impl DerefMut for ZendClassObject { - fn deref_mut(&mut self) -> &mut Self::Target { - self.obj - .as_mut() - .expect("Attempted to access uninitalized class object") - } -} - -/// Stores the class entry and handlers for a Rust type which has been exported to PHP. -pub struct ClassMetadata { - handlers_init: AtomicBool, - handlers: MaybeUninit, - ce: AtomicPtr, - - phantom: PhantomData, -} - -impl ClassMetadata { - /// Creates a new class metadata instance. - pub const fn new() -> Self { - Self { - handlers_init: AtomicBool::new(false), - handlers: MaybeUninit::uninit(), - ce: AtomicPtr::new(std::ptr::null_mut()), - phantom: PhantomData, - } - } -} - -impl ClassMetadata { - /// Returns an immutable reference to the object handlers contained inside the class metadata. - pub fn handlers(&self) -> &ZendObjectHandlers { - self.check_handlers(); - - // SAFETY: `check_handlers` guarantees that `handlers` has been initialized. - unsafe { &*self.handlers.as_ptr() } - } - - /// Checks if the class entry has been stored, returning a boolean. - pub fn has_ce(&self) -> bool { - !self.ce.load(Ordering::SeqCst).is_null() - } - - /// Retrieves a reference to the stored class entry. - /// - /// # Panics - /// - /// Panics if there is no class entry stored inside the class metadata. - pub fn ce(&self) -> &'static ClassEntry { - // SAFETY: There are only two values that can be stored in the atomic ptr: null or a static reference - // to a class entry. On the latter case, `as_ref()` will return `None` and the function will panic. - unsafe { self.ce.load(Ordering::SeqCst).as_ref() } - .expect("Attempted to retrieve class entry before it has been stored.") - } - - /// Stores a reference to a class entry inside the class metadata. - /// - /// # Parameters - /// - /// * `ce` - The class entry to store. - /// - /// # Panics - /// - /// Panics if the class entry has already been set in the class metadata. This function should - /// only be called once. - pub fn set_ce(&self, ce: &'static mut ClassEntry) { - if !self.ce.load(Ordering::SeqCst).is_null() { - panic!("Class entry has already been set."); - } - - self.ce.store(ce, Ordering::SeqCst); - } - - /// Checks if the handlers have been initialized, and initializes them if they are not. - fn check_handlers(&self) { - if !self.handlers_init.load(Ordering::Acquire) { - // SAFETY: `MaybeUninit` has the same size as the handlers. - unsafe { ZendObjectHandlers::init::(self.handlers.as_ptr() as *mut _) }; - self.handlers_init.store(true, Ordering::Release); - } - } -} - -/// Result returned from a constructor of a class. -pub enum ConstructorResult { - /// Successfully constructed the class, contains the new class object. - Ok(T), - /// An exception occured while constructing the class. - Exception(PhpException), - /// Invalid arguments were given to the constructor. - ArgError, -} - -impl From> for ConstructorResult -where - E: Into, -{ - fn from(result: std::result::Result) -> Self { - match result { - Ok(x) => Self::Ok(x), - Err(e) => Self::Exception(e.into()), - } - } -} - -impl From for ConstructorResult { - fn from(result: T) -> Self { - Self::Ok(result) - } -} - -impl ZendObjectHandlers { - /// Initializes a given set of object handlers by copying the standard object handlers into - /// the memory location, as well as setting up the `T` type destructor. - /// - /// # Parameters - /// - //// * `ptr` - Pointer to memory location to copy the standard handlers to. - /// - /// # Safety - /// - /// Caller must guarantee that the `ptr` given is a valid memory location. - pub unsafe fn init(ptr: *mut ZendObjectHandlers) { - std::ptr::copy_nonoverlapping(&std_object_handlers, ptr, 1); - let offset = ZendClassObject::::std_offset(); - (*ptr).offset = offset as _; - (*ptr).free_obj = Some(Self::free_obj::); - (*ptr).read_property = Some(Self::read_property::); - (*ptr).write_property = Some(Self::write_property::); - (*ptr).get_properties = Some(Self::get_properties::); - (*ptr).has_property = Some(Self::has_property::); - } - - unsafe extern "C" fn free_obj(object: *mut zend_object) { - let obj = object - .as_mut() - .and_then(|obj| ZendClassObject::::from_zend_obj_mut(obj)) - .expect("Invalid object pointer given for `free_obj`"); - - // Manually drop the object as we don't want to free the underlying memory. - ptr::drop_in_place(&mut obj.obj); - - zend_object_std_dtor(object) - } - - unsafe extern "C" fn read_property( - object: *mut zend_object, - member: *mut zend_string, - type_: c_int, - cache_slot: *mut *mut c_void, - rv: *mut Zval, - ) -> *mut Zval { - #[inline(always)] - unsafe fn internal( - object: *mut zend_object, - member: *mut zend_string, - type_: c_int, - cache_slot: *mut *mut c_void, - rv: *mut Zval, - ) -> PhpResult<*mut Zval> { - let obj = object - .as_mut() - .and_then(|obj| ZendClassObject::::from_zend_obj_mut(obj)) - .ok_or("Invalid object pointer given")?; - let prop_name = member - .as_ref() - .ok_or("Invalid property name pointer given")?; - let self_ = &mut **obj; - let mut props = T::get_properties(); - let prop = props.remove(prop_name.as_str().ok_or("Invalid property name given")?); - - // retval needs to be treated as initialized, so we set the type to null - let rv_mut = rv.as_mut().ok_or("Invalid return zval given")?; - rv_mut.u1.type_info = ZvalTypeFlags::Null.bits(); - - Ok(match prop { - Some(prop) => { - prop.get(self_, rv_mut)?; - rv - } - None => zend_std_read_property(object, member, type_, cache_slot, rv), - }) - } - - match internal::(object, member, type_, cache_slot, rv) { - Ok(rv) => rv, - Err(e) => { - let _ = e.throw(); - (&mut *rv).set_null(); - rv - } - } - } - - unsafe extern "C" fn write_property( - object: *mut zend_object, - member: *mut zend_string, - value: *mut Zval, - cache_slot: *mut *mut c_void, - ) -> *mut Zval { - #[inline(always)] - unsafe fn internal( - object: *mut zend_object, - member: *mut zend_string, - value: *mut Zval, - cache_slot: *mut *mut c_void, - ) -> PhpResult<*mut Zval> { - let obj = object - .as_mut() - .and_then(|obj| ZendClassObject::::from_zend_obj_mut(obj)) - .ok_or("Invalid object pointer given")?; - let prop_name = member - .as_ref() - .ok_or("Invalid property name pointer given")?; - let self_ = &mut **obj; - let mut props = T::get_properties(); - let prop = props.remove(prop_name.as_str().ok_or("Invalid property name given")?); - let value_mut = value.as_mut().ok_or("Invalid return zval given")?; - - Ok(match prop { - Some(prop) => { - prop.set(self_, value_mut)?; - value - } - None => zend_std_write_property(object, member, value, cache_slot), - }) - } - - match internal::(object, member, value, cache_slot) { - Ok(rv) => rv, - Err(e) => { - let _ = e.throw(); - value - } - } - } - - unsafe extern "C" fn get_properties( - object: *mut zend_object, - ) -> *mut HashTable { - #[inline(always)] - unsafe fn internal( - object: *mut zend_object, - props: &mut HashTable, - ) -> PhpResult { - let obj = object - .as_mut() - .and_then(|obj| ZendClassObject::::from_zend_obj_mut(obj)) - .ok_or("Invalid object pointer given")?; - let self_ = &mut **obj; - let struct_props = T::get_properties(); - - for (name, val) in struct_props.into_iter() { - let mut zv = Zval::new(); - if val.get(self_, &mut zv).is_err() { - continue; - } - props.insert(name, zv).map_err(|e| { - format!("Failed to insert value into properties hashtable: {:?}", e) - })?; - } - - Ok(()) - } - - let props = zend_std_get_properties(object) - .as_mut() - .or_else(|| Some(HashTable::new().into_raw())) - .expect("Failed to get property hashtable"); - - if let Err(e) = internal::(object, props) { - let _ = e.throw(); - } - - props - } - - unsafe extern "C" fn has_property( - object: *mut zend_object, - member: *mut zend_string, - has_set_exists: c_int, - cache_slot: *mut *mut c_void, - ) -> c_int { - #[inline(always)] - unsafe fn internal( - object: *mut zend_object, - member: *mut zend_string, - has_set_exists: c_int, - cache_slot: *mut *mut c_void, - ) -> PhpResult { - let obj = object - .as_mut() - .and_then(|obj| ZendClassObject::::from_zend_obj_mut(obj)) - .ok_or("Invalid object pointer given")?; - let prop_name = member - .as_ref() - .ok_or("Invalid property name pointer given")?; - let props = T::get_properties(); - let prop = props.get(prop_name.as_str().ok_or("Invalid property name given")?); - let self_ = &mut **obj; - - match has_set_exists { - // * 0 (has) whether property exists and is not NULL - 0 => { - if let Some(val) = prop { - let mut zv = Zval::new(); - val.get(self_, &mut zv)?; - if !zv.is_null() { - return Ok(1); - } - } - } - // * 1 (set) whether property exists and is true - 1 => { - if let Some(val) = prop { - let mut zv = Zval::new(); - val.get(self_, &mut zv)?; - - if zend_is_true(&mut zv) == 1 { - return Ok(1); - } - } - } - // * 2 (exists) whether property exists - 2 => { - if prop.is_some() { - return Ok(1); - } - } - _ => return Err( - "Invalid value given for `has_set_exists` in struct `has_property` function." - .into(), - ), - }; - - Ok(zend_std_has_property( - object, - member, - has_set_exists, - cache_slot, - )) - } - - match internal::(object, member, has_set_exists, cache_slot) { - Ok(rv) => rv, - Err(e) => { - let _ = e.throw(); - 0 - } - } - } -} diff --git a/src/php/types/props.rs b/src/props.rs similarity index 73% rename from src/php/types/props.rs rename to src/props.rs index acaa7694b5..d515833f25 100644 --- a/src/php/types/props.rs +++ b/src/props.rs @@ -1,17 +1,36 @@ -//! Utilities for adding properties to classes. +//! Types and traits for adding properties to PHP classes registered from Rust. +//! +//! There are two types of properties: +//! +//! * Field properties, referencing a property on a struct. +//! * Method properties, a getter and/or setter function called to get/set the +//! value of the property. +//! +//! Field types which can be used as a property implement [`Prop`]. This is +//! automatically implemented on any type which implements [`Clone`], +//! [`IntoZval`] and [`FromZval`]. +//! +//! Method property types only have to implement [`IntoZval`] for setters and +//! [`FromZval`] for getters. +//! +//! Properties are stored in the [`Property`] type, which allows us to store +//! field and method properties in one data structure. Properties are usually +//! retrieved via the [`RegisteredClass`] trait. +//! +//! [`RegisteredClass`]: crate::class::RegisteredClass use crate::{ - errors::{Error, Result}, - php::exceptions::PhpResult, + convert::{FromZval, IntoZval}, + error::{Error, Result}, + exception::PhpResult, + types::Zval, }; -use super::zval::{FromZval, IntoZval, Zval}; - /// Implemented on types which can be used as PHP properties. /// -/// Generally, this should not be directly implemented on types, as it is automatically implemented on -/// types that implement [`Clone`], [`IntoZval`] and [`FromZval`], which will be required to implement -/// this trait regardless. +/// Generally, this should not be directly implemented on types, as it is +/// automatically implemented on types that implement [`Clone`], [`IntoZval`] +/// and [`FromZval`], which will be required to implement this trait regardless. pub trait Prop<'a> { /// Gets the value of `self` by setting the value of `zv`. /// @@ -45,8 +64,8 @@ impl<'a, T: Clone + IntoZval + FromZval<'a>> Prop<'a> for T { /// There are two types of properties: /// /// * Field properties, where the data is stored inside a struct field. -/// * Method properties, where getter and/or setter functions are provided, which are used to get and set -/// the value of the property. +/// * Method properties, where getter and/or setter functions are provided, +/// which are used to get and set the value of the property. pub enum Property<'a, T> { Field(Box &mut dyn Prop>), Method { @@ -65,7 +84,7 @@ impl<'a, T: 'a> Property<'a, T> { /// # Examples /// /// ```no_run - /// # use ext_php_rs::php::types::props::Property; + /// # use ext_php_rs::props::Property; /// struct Test { /// pub a: i32, /// } @@ -81,18 +100,20 @@ impl<'a, T: 'a> Property<'a, T> { /// Creates a method property with getters and setters. /// - /// If either the getter or setter is not given, an exception will be thrown when attempting to - /// retrieve/set the property. + /// If either the getter or setter is not given, an exception will be thrown + /// when attempting to retrieve/set the property. /// /// # Parameters /// - /// * `get` - Function used to get the value of the property, in an [`Option`]. - /// * `set` - Function used to set the value of the property, in an [`Option`]. + /// * `get` - Function used to get the value of the property, in an + /// [`Option`]. + /// * `set` - Function used to set the value of the property, in an + /// [`Option`]. /// /// # Examples /// /// ```no_run - /// # use ext_php_rs::php::types::props::Property; + /// # use ext_php_rs::props::Property; /// struct Test; /// /// impl Test { @@ -133,10 +154,12 @@ impl<'a, T: 'a> Property<'a, T> { Self::Method { get, set } } - /// Attempts to retrieve the value of the property from the given object `self_`. + /// Attempts to retrieve the value of the property from the given object + /// `self_`. /// - /// The value of the property, if successfully retrieved, is loaded into the given [`Zval`] `retval`. If - /// unsuccessful, a [`PhpException`] is returned inside the error variant of a result. + /// The value of the property, if successfully retrieved, is loaded into the + /// given [`Zval`] `retval`. If unsuccessful, a [`PhpException`] is + /// returned inside the error variant of a result. /// /// # Parameters /// @@ -145,13 +168,14 @@ impl<'a, T: 'a> Property<'a, T> { /// /// # Returns /// - /// Nothing upon success, a [`PhpException`] inside an error variant when the property could not be retrieved. + /// Nothing upon success, a [`PhpException`] inside an error variant when + /// the property could not be retrieved. /// /// # Examples /// /// ```no_run - /// # use ext_php_rs::php::types::props::Property; - /// # use ext_php_rs::php::types::zval::Zval; + /// # use ext_php_rs::props::Property; + /// # use ext_php_rs::types::Zval; /// struct Test { /// pub a: i32, /// } @@ -164,7 +188,7 @@ impl<'a, T: 'a> Property<'a, T> { /// assert_eq!(zv.long(), Some(500)); /// ``` /// - /// [`PhpException`]: crate::php::exceptions::PhpException + /// [`PhpException`]: crate::exception::PhpException pub fn get(&self, self_: &'a mut T, retval: &mut Zval) -> PhpResult { match self { Property::Field(field) => field(self_) @@ -177,10 +201,12 @@ impl<'a, T: 'a> Property<'a, T> { } } - /// Attempts to set the value of the property inside the given object `self_`. + /// Attempts to set the value of the property inside the given object + /// `self_`. /// - /// The new value of the property is supplied inside the given [`Zval`] `value`. If unsuccessful, - /// a [`PhpException`] is returned inside the error variant of a result. + /// The new value of the property is supplied inside the given [`Zval`] + /// `value`. If unsuccessful, a [`PhpException`] is returned inside the + /// error variant of a result. /// /// # Parameters /// @@ -189,14 +215,15 @@ impl<'a, T: 'a> Property<'a, T> { /// /// # Returns /// - /// Nothing upon success, a [`PhpException`] inside an error variant when the property could not be set. + /// Nothing upon success, a [`PhpException`] inside an error variant when + /// the property could not be set. /// /// # Examples /// /// ```no_run - /// # use ext_php_rs::php::types::props::Property; - /// # use ext_php_rs::php::types::zval::Zval; - /// # use ext_php_rs::php::types::zval::IntoZval; + /// # use ext_php_rs::props::Property; + /// # use ext_php_rs::types::Zval; + /// # use ext_php_rs::convert::IntoZval; /// struct Test { /// pub a: i32, /// } @@ -209,7 +236,7 @@ impl<'a, T: 'a> Property<'a, T> { /// assert_eq!(test.a, 100); /// ``` /// - /// [`PhpException`]: crate::php::exceptions::PhpException + /// [`PhpException`]: crate::exception::PhpException pub fn set(&self, self_: &'a mut T, value: &Zval) -> PhpResult { match self { Property::Field(field) => field(self_) diff --git a/src/php/types/rc.rs b/src/rc.rs similarity index 88% rename from src/php/types/rc.rs rename to src/rc.rs index 167ae34fed..2842b2c247 100644 --- a/src/php/types/rc.rs +++ b/src/rc.rs @@ -1,8 +1,9 @@ -//! Utilities for interacting with refcounted PHP types. +//! Traits and types for interacting with reference counted PHP types. -use crate::bindings::{zend_refcounted_h, zend_string}; - -use super::object::ZendObject; +use crate::{ + ffi::{zend_refcounted_h, zend_string}, + types::ZendObject, +}; /// Object used to store Zend reference counter. pub type ZendRefcount = zend_refcounted_h; diff --git a/src/php/types/array.rs b/src/types/array.rs similarity index 57% rename from src/php/types/array.rs rename to src/types/array.rs index 0003fe8d7c..8417035406 100644 --- a/src/php/types/array.rs +++ b/src/types/array.rs @@ -1,5 +1,5 @@ -//! Represents an array in PHP. As all arrays in PHP are associative arrays, they are represented -//! by hash tables. +//! Represents an array in PHP. As all arrays in PHP are associative arrays, +//! they are represented by hash tables. use std::{ collections::HashMap, @@ -12,39 +12,89 @@ use std::{ }; use crate::{ - bindings::{ + boxed::{ZBox, ZBoxable}, + convert::{FromZval, IntoZval}, + error::{Error, Result}, + ffi::{ _Bucket, _zend_new_array, zend_array_destroy, zend_array_dup, zend_hash_clean, zend_hash_index_del, zend_hash_index_find, zend_hash_index_update, zend_hash_next_index_insert, zend_hash_str_del, zend_hash_str_find, zend_hash_str_update, HT_MIN_SIZE, }, - errors::{Error, Result}, - php::{ - boxed::{ZBox, ZBoxable}, - enums::DataType, - }, + flags::DataType, + types::Zval, }; -use super::zval::{FromZval, IntoZval, Zval}; - -/// PHP array, which is represented in memory as a hashtable. -pub use crate::bindings::HashTable; - -impl HashTable { - /// Creates a new, empty, PHP associative array. +/// A PHP hashtable. +/// +/// In PHP, arrays are represented as hashtables. This allows you to push values +/// onto the end of the array like a vector, while also allowing you to insert +/// at arbitrary string key indexes. +/// +/// A PHP hashtable stores values as [`Zval`]s. This allows you to insert +/// different types into the same hashtable. Types must implement [`IntoZval`] +/// to be able to be inserted into the hashtable. +/// +/// # Examples +/// +/// ```no_run +/// use ext_php_rs::types::ZendHashTable; +/// +/// let mut ht = ZendHashTable::new(); +/// ht.push(1); +/// ht.push("Hello, world!"); +/// ht.insert("Like", "Hashtable"); +/// +/// assert_eq!(ht.len(), 3); +/// assert_eq!(ht.get_index(0).and_then(|zv| zv.long()), Some(1)); +/// ``` +pub type ZendHashTable = crate::ffi::HashTable; + +// Clippy complains about there being no `is_empty` function when implementing +// on the alias `ZendStr` :( +#[allow(clippy::len_without_is_empty)] +impl ZendHashTable { + /// Creates a new, empty, PHP hashtable, returned inside a [`ZBox`]. + /// + /// # Example + /// + /// ```no_run + /// use ext_php_rs::types::ZendHashTable; + /// + /// let ht = ZendHashTable::new(); + /// ``` + /// + /// # Panics + /// + /// Panics if memory for the hashtable could not be allocated. pub fn new() -> ZBox { Self::with_capacity(HT_MIN_SIZE) } - /// Creates a new, empty, PHP associative array with an initial size. + /// Creates a new, empty, PHP hashtable with an initial size, returned + /// inside a [`ZBox`]. /// /// # Parameters /// /// * `size` - The size to initialize the array with. + /// + /// # Example + /// + /// ```no_run + /// use ext_php_rs::types::ZendHashTable; + /// + /// let ht = ZendHashTable::with_capacity(10); + /// ``` + /// + /// # Panics + /// + /// Panics if memory for the hashtable could not be allocated. pub fn with_capacity(size: u32) -> ZBox { - // SAFETY: PHP allocater handles the creation of the array. unsafe { + // SAFETY: PHP allocater handles the creation of the array. let ptr = _zend_new_array(size); + + // SAFETY: `as_mut()` checks if the pointer is null, and panics if it is not. ZBox::from_raw( ptr.as_mut() .expect("Failed to allocate memory for hashtable"), @@ -53,16 +103,58 @@ impl HashTable { } /// Returns the current number of elements in the array. + /// + /// # Example + /// + /// ```no_run + /// use ext_php_rs::types::ZendHashTable; + /// + /// let mut ht = ZendHashTable::new(); + /// + /// ht.push(1); + /// ht.push("Hello, world"); + /// + /// assert_eq!(ht.len(), 2); + /// ``` pub fn len(&self) -> usize { self.nNumOfElements as usize } /// Returns whether the hash table is empty. + /// + /// # Example + /// + /// ```no_run + /// use ext_php_rs::types::ZendHashTable; + /// + /// let mut ht = ZendHashTable::new(); + /// + /// assert_eq!(ht.is_empty(), true); + /// + /// ht.push(1); + /// ht.push("Hello, world"); + /// + /// assert_eq!(ht.is_empty(), false); + /// ``` pub fn is_empty(&self) -> bool { self.len() == 0 } /// Clears the hash table, removing all values. + /// + /// # Example + /// + /// ```no_run + /// use ext_php_rs::types::ZendHashTable; + /// + /// let mut ht = ZendHashTable::new(); + /// + /// ht.insert("test", "hello world"); + /// assert_eq!(ht.is_empty(), false); + /// + /// ht.clear(); + /// assert_eq!(ht.is_empty(), true); + /// ``` pub fn clear(&mut self) { unsafe { zend_hash_clean(self) } } @@ -75,8 +167,20 @@ impl HashTable { /// /// # Returns /// - /// * `Some(&Zval)` - A reference to the zval at the position in the hash table. + /// * `Some(&Zval)` - A reference to the zval at the position in the hash + /// table. /// * `None` - No value at the given position was found. + /// + /// # Example + /// + /// ```no_run + /// use ext_php_rs::types::ZendHashTable; + /// + /// let mut ht = ZendHashTable::new(); + /// + /// ht.insert("test", "hello world"); + /// assert_eq!(ht.get("test").and_then(|zv| zv.str()), Some("hello world")); + /// ``` pub fn get(&self, key: &'_ str) -> Option<&Zval> { let str = CString::new(key).ok()?; unsafe { zend_hash_str_find(self, str.as_ptr(), key.len() as _).as_ref() } @@ -90,8 +194,20 @@ impl HashTable { /// /// # Returns /// - /// * `Some(&Zval)` - A reference to the zval at the position in the hash table. + /// * `Some(&Zval)` - A reference to the zval at the position in the hash + /// table. /// * `None` - No value at the given position was found. + /// + /// # Example + /// + /// ```no_run + /// use ext_php_rs::types::ZendHashTable; + /// + /// let mut ht = ZendHashTable::new(); + /// + /// ht.push(100); + /// assert_eq!(ht.get_index(0).and_then(|zv| zv.long()), Some(100)); + /// ``` pub fn get_index(&self, key: u64) -> Option<&Zval> { unsafe { zend_hash_index_find(self, key).as_ref() } } @@ -106,7 +222,21 @@ impl HashTable { /// /// * `Some(())` - Key was successfully removed. /// * `None` - No key was removed, did not exist. - pub fn remove(&mut self, key: &str) -> Option<()> { + /// + /// # Example + /// + /// ```no_run + /// use ext_php_rs::types::ZendHashTable; + /// + /// let mut ht = ZendHashTable::new(); + /// + /// ht.insert("test", "hello world"); + /// assert_eq!(ht.len(), 1); + /// + /// ht.remove("test"); + /// assert_eq!(ht.len(), 0); + /// ``` + pub fn remove(&mut self, key: &str) -> Option<()> { let result = unsafe { zend_hash_str_del(self, CString::new(key).ok()?.as_ptr(), key.len() as _) }; @@ -127,6 +257,20 @@ impl HashTable { /// /// * `Ok(())` - Key was successfully removed. /// * `None` - No key was removed, did not exist. + /// + /// # Example + /// + /// ```no_run + /// use ext_php_rs::types::ZendHashTable; + /// + /// let mut ht = ZendHashTable::new(); + /// + /// ht.push("hello"); + /// assert_eq!(ht.len(), 1); + /// + /// ht.remove_index(0); + /// assert_eq!(ht.len(), 0); + /// ``` pub fn remove_index(&mut self, key: u64) -> Option<()> { let result = unsafe { zend_hash_index_del(self, key) }; @@ -137,13 +281,32 @@ impl HashTable { } } - /// Attempts to insert an item into the hash table, or update if the key already exists. - /// Returns nothing in a result if successful. + /// Attempts to insert an item into the hash table, or update if the key + /// already exists. Returns nothing in a result if successful. /// /// # Parameters /// /// * `key` - The key to insert the value at in the hash table. /// * `value` - The value to insert into the hash table. + /// + /// # Returns + /// + /// Returns nothing in a result on success. Returns an error if the key + /// could not be converted into a [`CString`], or converting the value into + /// a [`Zval`] failed. + /// + /// # Example + /// + /// ```no_run + /// use ext_php_rs::types::ZendHashTable; + /// + /// let mut ht = ZendHashTable::new(); + /// + /// ht.insert("a", "A"); + /// ht.insert("b", "B"); + /// ht.insert("c", "C"); + /// assert_eq!(ht.len(), 3); + /// ``` pub fn insert(&mut self, key: &str, val: V) -> Result<()> where V: IntoZval, @@ -161,13 +324,31 @@ impl HashTable { Ok(()) } - /// Inserts an item into the hash table at a specified index, or updates if the key already exists. - /// Returns nothing in a result if successful. + /// Inserts an item into the hash table at a specified index, or updates if + /// the key already exists. Returns nothing in a result if successful. /// /// # Parameters /// /// * `key` - The index at which the value should be inserted. /// * `val` - The value to insert into the hash table. + /// + /// # Returns + /// + /// Returns nothing in a result on success. Returns an error if converting + /// the value into a [`Zval`] failed. + /// + /// # Example + /// + /// ```no_run + /// use ext_php_rs::types::ZendHashTable; + /// + /// let mut ht = ZendHashTable::new(); + /// + /// ht.insert_at_index(0, "A"); + /// ht.insert_at_index(5, "B"); + /// ht.insert_at_index(0, "C"); // notice overriding index 0 + /// assert_eq!(ht.len(), 2); + /// ``` pub fn insert_at_index(&mut self, key: u64, val: V) -> Result<()> where V: IntoZval, @@ -178,12 +359,30 @@ impl HashTable { Ok(()) } - /// Pushes an item onto the end of the hash table. Returns a result containing nothing if the - /// element was sucessfully inserted. + /// Pushes an item onto the end of the hash table. Returns a result + /// containing nothing if the element was sucessfully inserted. /// /// # Parameters /// /// * `val` - The value to insert into the hash table. + /// + /// # Returns + /// + /// Returns nothing in a result on success. Returns an error if converting + /// the value into a [`Zval`] failed. + /// + /// # Example + /// + /// ```no_run + /// use ext_php_rs::types::ZendHashTable; + /// + /// let mut ht = ZendHashTable::new(); + /// + /// ht.push("a"); + /// ht.push("b"); + /// ht.push("c"); + /// assert_eq!(ht.len(), 3); + /// ``` pub fn push(&mut self, val: V) -> Result<()> where V: IntoZval, @@ -195,26 +394,55 @@ impl HashTable { Ok(()) } - /// Returns an iterator over the key(s) and value contained inside the hashtable. + /// Returns an iterator over the key(s) and value contained inside the + /// hashtable. + /// + /// # Example + /// + /// ```no_run + /// use ext_php_rs::types::ZendHashTable; + /// + /// let mut ht = ZendHashTable::new(); + /// + /// for (idx, key, val) in ht.iter() { + /// // ^ Index if inserted at an index. + /// // ^ Optional string key, if inserted like a hashtable. + /// // ^ Inserted value. + /// + /// dbg!(idx, key, val); + /// } #[inline] pub fn iter(&self) -> Iter { Iter::new(self) } - /// Returns an iterator over the values contained inside the hashtable, as if it was a set or list. + /// Returns an iterator over the values contained inside the hashtable, as + /// if it was a set or list. + /// + /// # Example + /// + /// ```no_run + /// use ext_php_rs::types::ZendHashTable; + /// + /// let mut ht = ZendHashTable::new(); + /// + /// for val in ht.values() { + /// dbg!(val); + /// } #[inline] pub fn values(&self) -> Values { Values::new(self) } } -unsafe impl ZBoxable for HashTable { +unsafe impl ZBoxable for ZendHashTable { fn free(&mut self) { + // SAFETY: ZBox has immutable access to `self`. unsafe { zend_array_destroy(self) } } } -impl Debug for HashTable { +impl Debug for ZendHashTable { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_map() .entries( @@ -225,20 +453,26 @@ impl Debug for HashTable { } } -impl ToOwned for HashTable { - type Owned = ZBox; +impl ToOwned for ZendHashTable { + type Owned = ZBox; fn to_owned(&self) -> Self::Owned { unsafe { - let ptr = zend_array_dup(self as *const HashTable as *mut HashTable); - ZBox::from_raw(ptr) + // SAFETY: FFI call does not modify `self`, returns a new hashtable. + let ptr = zend_array_dup(self as *const ZendHashTable as *mut ZendHashTable); + + // SAFETY: `as_mut()` checks if the pointer is null, and panics if it is not. + ZBox::from_raw( + ptr.as_mut() + .expect("Failed to allocate memory for hashtable"), + ) } } } /// Immutable iterator upon a reference to a hashtable. pub struct Iter<'a> { - ht: &'a HashTable, + ht: &'a ZendHashTable, pos: Option>, end: Option>, } @@ -249,7 +483,7 @@ impl<'a> Iter<'a> { /// # Parameters /// /// * `ht` - The hashtable to iterate. - pub fn new(ht: &'a HashTable) -> Self { + pub fn new(ht: &'a ZendHashTable) -> Self { Self { ht, pos: NonNull::new(ht.arData), @@ -307,7 +541,8 @@ impl<'a> DoubleEndedIterator for Iter<'a> { } } -/// Immutable iterator which iterates over the values of the hashtable, as it was a set or list. +/// Immutable iterator which iterates over the values of the hashtable, as it +/// was a set or list. pub struct Values<'a>(Iter<'a>); impl<'a> Values<'a> { @@ -316,7 +551,7 @@ impl<'a> Values<'a> { /// # Parameters /// /// * `ht` - The hashtable to iterate. - pub fn new(ht: &'a HashTable) -> Self { + pub fn new(ht: &'a ZendHashTable) -> Self { Self(Iter::new(ht)) } } @@ -348,19 +583,19 @@ impl<'a> DoubleEndedIterator for Values<'a> { } } -impl Default for ZBox { +impl Default for ZBox { fn default() -> Self { - HashTable::new() + ZendHashTable::new() } } -impl Clone for ZBox { +impl Clone for ZBox { fn clone(&self) -> Self { (**self).to_owned() } } -impl IntoZval for ZBox { +impl IntoZval for ZBox { const TYPE: DataType = DataType::Array; fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> { @@ -369,7 +604,7 @@ impl IntoZval for ZBox { } } -impl<'a> FromZval<'a> for &'a HashTable { +impl<'a> FromZval<'a> for &'a ZendHashTable { const TYPE: DataType = DataType::Array; fn from_zval(zval: &'a Zval) -> Option { @@ -381,13 +616,13 @@ impl<'a> FromZval<'a> for &'a HashTable { //// HashMap /////////////////////////////////////////// -impl TryFrom<&HashTable> for HashMap +impl TryFrom<&ZendHashTable> for HashMap where for<'a> V: FromZval<'a>, { type Error = Error; - fn try_from(value: &HashTable) -> Result { + fn try_from(value: &ZendHashTable) -> Result { let mut hm = HashMap::with_capacity(value.len()); for (idx, key, val) in value.iter() { @@ -401,7 +636,7 @@ where } } -impl TryFrom> for ZBox +impl TryFrom> for ZBox where K: AsRef, V: IntoZval, @@ -409,8 +644,9 @@ where type Error = Error; fn try_from(value: HashMap) -> Result { - let mut ht = - HashTable::with_capacity(value.len().try_into().map_err(|_| Error::IntegerOverflow)?); + let mut ht = ZendHashTable::with_capacity( + value.len().try_into().map_err(|_| Error::IntegerOverflow)?, + ); for (k, v) in value.into_iter() { ht.insert(k.as_ref(), v)?; @@ -449,13 +685,13 @@ where //// Vec /////////////////////////////////////////// -impl TryFrom<&HashTable> for Vec +impl TryFrom<&ZendHashTable> for Vec where for<'a> T: FromZval<'a>, { type Error = Error; - fn try_from(value: &HashTable) -> Result { + fn try_from(value: &ZendHashTable) -> Result { let mut vec = Vec::with_capacity(value.len()); for (_, _, val) in value.iter() { @@ -466,15 +702,16 @@ where } } -impl TryFrom> for ZBox +impl TryFrom> for ZBox where T: IntoZval, { type Error = Error; fn try_from(value: Vec) -> Result { - let mut ht = - HashTable::with_capacity(value.len().try_into().map_err(|_| Error::IntegerOverflow)?); + let mut ht = ZendHashTable::with_capacity( + value.len().try_into().map_err(|_| Error::IntegerOverflow)?, + ); for val in value.into_iter() { ht.push(val)?; @@ -508,36 +745,36 @@ where } } -impl FromIterator for ZBox { +impl FromIterator for ZBox { fn from_iter>(iter: T) -> Self { - let mut ht = HashTable::new(); + let mut ht = ZendHashTable::new(); for item in iter.into_iter() { - // Inserting a zval cannot fail, as `push` only returns `Err` if converting `val` to a zval - // fails. + // Inserting a zval cannot fail, as `push` only returns `Err` if converting + // `val` to a zval fails. let _ = ht.push(item); } ht } } -impl FromIterator<(u64, Zval)> for ZBox { +impl FromIterator<(u64, Zval)> for ZBox { fn from_iter>(iter: T) -> Self { - let mut ht = HashTable::new(); + let mut ht = ZendHashTable::new(); for (key, val) in iter.into_iter() { - // Inserting a zval cannot fail, as `push` only returns `Err` if converting `val` to a zval - // fails. + // Inserting a zval cannot fail, as `push` only returns `Err` if converting + // `val` to a zval fails. let _ = ht.insert_at_index(key, val); } ht } } -impl<'a> FromIterator<(&'a str, Zval)> for ZBox { +impl<'a> FromIterator<(&'a str, Zval)> for ZBox { fn from_iter>(iter: T) -> Self { - let mut ht = HashTable::new(); + let mut ht = ZendHashTable::new(); for (key, val) in iter.into_iter() { - // Inserting a zval cannot fail, as `push` only returns `Err` if converting `val` to a zval - // fails. + // Inserting a zval cannot fail, as `push` only returns `Err` if converting + // `val` to a zval fails. let _ = ht.insert(key, val); } ht diff --git a/src/php/types/callable.rs b/src/types/callable.rs similarity index 55% rename from src/php/types/callable.rs rename to src/types/callable.rs index 5a2f95d148..89e33149bd 100644 --- a/src/php/types/callable.rs +++ b/src/types/callable.rs @@ -1,44 +1,26 @@ //! Types related to callables in PHP (anonymous functions, functions, etc). -use std::ops::Deref; +use std::{convert::TryFrom, ops::Deref}; -use super::zval::{IntoZvalDyn, Zval}; use crate::{ - bindings::_call_user_function_impl, - errors::{Error, Result}, + convert::{FromZval, IntoZvalDyn}, + error::{Error, Result}, + ffi::_call_user_function_impl, + flags::DataType, + zend::ExecutorGlobals, }; -/// Acts as a wrapper around a callable [`Zval`]. Allows the owner to call the [`Zval`] as if it -/// was a PHP function through the [`try_call`](Callable::try_call) method. -#[derive(Debug)] -pub struct Callable<'a>(OwnedZval<'a>); +use super::Zval; -/// A container for a zval. Either contains a reference to a zval or an owned zval. +/// Acts as a wrapper around a callable [`Zval`]. Allows the owner to call the +/// [`Zval`] as if it was a PHP function through the [`try_call`] method. +/// +/// [`try_call`]: #method.try_call #[derive(Debug)] -enum OwnedZval<'a> { - Reference(&'a Zval), - Owned(Zval), -} - -impl<'a> OwnedZval<'a> { - fn as_ref(&self) -> &Zval { - match self { - OwnedZval::Reference(zv) => *zv, - OwnedZval::Owned(zv) => zv, - } - } -} +pub struct ZendCallable<'a>(OwnedZval<'a>); -impl<'a> Deref for OwnedZval<'a> { - type Target = Zval; - - fn deref(&self) -> &Self::Target { - self.as_ref() - } -} - -impl<'a> Callable<'a> { - /// Attempts to create a new [`Callable`] from a zval. +impl<'a> ZendCallable<'a> { + /// Attempts to create a new [`ZendCallable`] from a zval. /// /// # Parameters /// @@ -55,8 +37,8 @@ impl<'a> Callable<'a> { } } - /// Attempts to create a new [`Callable`] by taking ownership of a Zval. Returns a result - /// containing the callable if the zval was callable. + /// Attempts to create a new [`ZendCallable`] by taking ownership of a Zval. + /// Returns a result containing the callable if the zval was callable. /// /// # Parameters /// @@ -69,12 +51,23 @@ impl<'a> Callable<'a> { } } - /// Attempts to create a new [`Callable`] from a function name. Returns a result containing the - /// callable if the function existed and was callable. + /// Attempts to create a new [`ZendCallable`] from a function name. Returns + /// a result containing the callable if the function existed and was + /// callable. /// /// # Parameters /// /// * `name` - Name of the callable function. + /// + /// # Example + /// + /// ```no_run + /// use ext_php_rs::types::ZendCallable; + /// + /// let strpos = ZendCallable::try_from_name("strpos").unwrap(); + /// let result = strpos.try_call(vec![&"hello", &"e"]).unwrap(); + /// assert_eq!(result.long(), Some(1)); + /// ``` pub fn try_from_name(name: &str) -> Result { let mut callable = Zval::new(); callable.set_string(name, false)?; @@ -82,16 +75,30 @@ impl<'a> Callable<'a> { Self::new_owned(callable) } - /// Attempts to call the callable with a list of arguments to pass to the function. - /// Note that a thrown exception inside the callable is not detectable, therefore you should - /// check if the return value is valid rather than unwrapping. Returns a result containing the - /// return value of the function, or an error. + /// Attempts to call the callable with a list of arguments to pass to the + /// function. /// - /// You should not call this function directly, rather through the [`call_user_func`] macro. + /// You should not call this function directly, rather through the + /// [`call_user_func`] macro. /// /// # Parameters /// /// * `params` - A list of parameters to call the function with. + /// + /// # Returns + /// + /// Returns the result wrapped in [`Ok`] upon success. If calling the + /// callable fails, or an exception is thrown, an [`Err`] is returned. + /// + /// # Example + /// + /// ```no_run + /// use ext_php_rs::types::ZendCallable; + /// + /// let strpos = ZendCallable::try_from_name("strpos").unwrap(); + /// let result = strpos.try_call(vec![&"hello", &"e"]).unwrap(); + /// assert_eq!(result.long(), Some(1)); + /// ``` pub fn try_call(&self, params: Vec<&dyn IntoZvalDyn>) -> Result { if !self.0.is_callable() { return Err(Error::Callable); @@ -118,8 +125,51 @@ impl<'a> Callable<'a> { if result < 0 { Err(Error::Callable) + } else if let Some(e) = ExecutorGlobals::take_exception() { + Err(Error::Exception(e)) } else { Ok(retval) } } } + +impl<'a> FromZval<'a> for ZendCallable<'a> { + const TYPE: DataType = DataType::Callable; + + fn from_zval(zval: &'a Zval) -> Option { + ZendCallable::new(zval).ok() + } +} + +impl<'a> TryFrom for ZendCallable<'a> { + type Error = Error; + + fn try_from(value: Zval) -> Result { + ZendCallable::new_owned(value) + } +} + +/// A container for a zval. Either contains a reference to a zval or an owned +/// zval. +#[derive(Debug)] +enum OwnedZval<'a> { + Reference(&'a Zval), + Owned(Zval), +} + +impl<'a> OwnedZval<'a> { + fn as_ref(&self) -> &Zval { + match self { + OwnedZval::Reference(zv) => *zv, + OwnedZval::Owned(zv) => zv, + } + } +} + +impl<'a> Deref for OwnedZval<'a> { + type Target = Zval; + + fn deref(&self) -> &Self::Target { + self.as_ref() + } +} diff --git a/src/types/class_object.rs b/src/types/class_object.rs new file mode 100644 index 0000000000..84646a634e --- /dev/null +++ b/src/types/class_object.rs @@ -0,0 +1,305 @@ +//! Represents an object in PHP. Allows for overriding the internal object used +//! by classes, allowing users to store Rust data inside a PHP object. + +use std::{ + fmt::Debug, + mem, + ops::{Deref, DerefMut}, + ptr::{self, NonNull}, +}; + +use crate::{ + boxed::{ZBox, ZBoxable}, + class::RegisteredClass, + convert::{FromZendObject, FromZendObjectMut, FromZval, FromZvalMut, IntoZval}, + error::{Error, Result}, + ffi::{ + ext_php_rs_zend_object_alloc, ext_php_rs_zend_object_release, object_properties_init, + zend_object, zend_object_std_init, zend_objects_clone_members, + }, + flags::DataType, + types::{ZendObject, Zval}, +}; + +/// Representation of a Zend class object in memory. +#[repr(C)] +pub struct ZendClassObject { + pub obj: Option, + pub std: ZendObject, +} + +impl ZendClassObject { + /// Creates a new [`ZendClassObject`] of type `T`, where `T` is a + /// [`RegisteredClass`] in PHP, storing the given value `val` inside the + /// object. + /// + /// # Parameters + /// + /// * `val` - The value to store inside the object. + /// + /// # Panics + /// + /// Panics if memory was unable to be allocated for the new object. + pub fn new(val: T) -> ZBox { + // SAFETY: We are providing a value to initialize the object with. + unsafe { Self::internal_new(Some(val)) } + } + + /// Creates a new [`ZendClassObject`] of type `T`, with an uninitialized + /// internal object. + /// + /// # Safety + /// + /// As the object is uninitialized, the caller must ensure the following + /// until the internal object is initialized: + /// + /// * The object is never dereferenced to `T`. + /// * The [`Clone`] implementation is never called. + /// * The [`Debug`] implementation is never called. + /// + /// If any of these conditions are not met while not initialized, the + /// corresponding function will panic. Converting the object into its + /// inner pointer with the [`into_raw`] function is valid, however. + /// + /// [`into_raw`]: #method.into_raw + /// + /// # Panics + /// + /// Panics if memory was unable to be allocated for the new object. + pub unsafe fn new_uninit() -> ZBox { + Self::internal_new(None) + } + + /// Creates a new [`ZendObject`] of type `T`, storing the given (and + /// potentially uninitialized) `val` inside the object. + /// + /// # Parameters + /// + /// * `val` - Value to store inside the object. See safety section. + /// * `init` - Whether the given `val` was initialized. + /// + /// # Safety + /// + /// Providing an initialized variant of [`MaybeUninit`] is safe. + /// + /// Providing an uninitalized variant of [`MaybeUninit`] is unsafe. As + /// the object is uninitialized, the caller must ensure the following + /// until the internal object is initialized: + /// + /// * The object is never dereferenced to `T`. + /// * The [`Clone`] implementation is never called. + /// * The [`Debug`] implementation is never called. + /// + /// If any of these conditions are not met while not initialized, the + /// corresponding function will panic. Converting the object into its + /// inner with the [`into_raw`] function is valid, however. You can + /// initialize the object with the [`initialize`] function. + /// + /// [`into_raw`]: #method.into_raw + /// [`initialize`]: #method.initialize + /// + /// # Panics + /// + /// Panics if memory was unable to be allocated for the new object. + unsafe fn internal_new(val: Option) -> ZBox { + let size = mem::size_of::>(); + let meta = T::get_metadata(); + let ce = meta.ce() as *const _ as *mut _; + let obj = ext_php_rs_zend_object_alloc(size as _, ce) as *mut ZendClassObject; + let obj = obj + .as_mut() + .expect("Failed to allocate for new Zend object"); + + zend_object_std_init(&mut obj.std, ce); + object_properties_init(&mut obj.std, ce); + + // SAFETY: `obj` is non-null and well aligned as it is a reference. + // As the data in `obj.obj` is uninitalized, we don't want to drop + // the data, but directly override it. + ptr::write(&mut obj.obj, val); + + obj.std.handlers = meta.handlers(); + ZBox::from_raw(obj) + } + + /// Initializes the class object with the value `val`. + /// + /// # Parameters + /// + /// * `val` - The value to initialize the object with. + /// + /// # Returns + /// + /// Returns the old value in an [`Option`] if the object had already been + /// initialized, [`None`] otherwise. + pub fn initialize(&mut self, val: T) -> Option { + self.obj.replace(val) + } + + /// Returns a reference to the [`ZendClassObject`] of a given object `T`. + /// Returns [`None`] if the given object is not of the type `T`. + /// + /// # Parameters + /// + /// * `obj` - The object to get the [`ZendClassObject`] for. + /// + /// # Safety + /// + /// Caller must guarantee that the given `obj` was created by Zend, which + /// means that it is immediately followed by a [`zend_object`]. + pub(crate) unsafe fn from_obj_ptr(obj: &T) -> Option<&mut Self> { + // TODO(david): Remove this function + let ptr = (obj as *const T as *mut Self).as_mut()?; + + if ptr.std.is_instance::() { + Some(ptr) + } else { + None + } + } + + /// Returns a mutable reference to the [`ZendClassObject`] of a given zend + /// object `obj`. Returns [`None`] if the given object is not of the + /// type `T`. + /// + /// # Parameters + /// + /// * `obj` - The zend object to get the [`ZendClassObject`] for. + pub fn from_zend_obj(std: &zend_object) -> Option<&Self> { + Some(Self::_from_zend_obj(std)?) + } + + /// Returns a mutable reference to the [`ZendClassObject`] of a given zend + /// object `obj`. Returns [`None`] if the given object is not of the + /// type `T`. + /// + /// # Parameters + /// + /// * `obj` - The zend object to get the [`ZendClassObject`] for. + pub fn from_zend_obj_mut(std: &mut zend_object) -> Option<&mut Self> { + Self::_from_zend_obj(std) + } + + fn _from_zend_obj(std: &zend_object) -> Option<&mut Self> { + let std = std as *const zend_object as *const i8; + let ptr = unsafe { + let ptr = std.offset(0 - Self::std_offset() as isize) as *const Self; + (ptr as *mut Self).as_mut()? + }; + + if ptr.std.is_instance::() { + Some(ptr) + } else { + None + } + } + + /// Returns a mutable reference to the underlying Zend object. + pub fn get_mut_zend_obj(&mut self) -> &mut zend_object { + &mut self.std + } + + /// Returns the offset of the `std` property in the class object. + pub(crate) fn std_offset() -> usize { + unsafe { + let null = NonNull::::dangling(); + let base = null.as_ref() as *const Self; + let std = &null.as_ref().std as *const zend_object; + + (std as usize) - (base as usize) + } + } +} + +impl<'a, T: RegisteredClass> FromZval<'a> for &'a ZendClassObject { + const TYPE: DataType = DataType::Object(Some(T::CLASS_NAME)); + + fn from_zval(zval: &'a Zval) -> Option { + Self::from_zend_object(zval.object()?).ok() + } +} + +impl<'a, T: RegisteredClass> FromZendObject<'a> for &'a ZendClassObject { + fn from_zend_object(obj: &'a ZendObject) -> Result { + // TODO(david): replace with better error + ZendClassObject::from_zend_obj(obj).ok_or(Error::InvalidScope) + } +} + +impl<'a, T: RegisteredClass> FromZvalMut<'a> for &'a mut ZendClassObject { + const TYPE: DataType = DataType::Object(Some(T::CLASS_NAME)); + + fn from_zval_mut(zval: &'a mut Zval) -> Option { + Self::from_zend_object_mut(zval.object_mut()?).ok() + } +} + +impl<'a, T: RegisteredClass> FromZendObjectMut<'a> for &'a mut ZendClassObject { + fn from_zend_object_mut(obj: &'a mut ZendObject) -> Result { + ZendClassObject::from_zend_obj_mut(obj).ok_or(Error::InvalidScope) + } +} + +unsafe impl ZBoxable for ZendClassObject { + fn free(&mut self) { + // SAFETY: All constructors guarantee that `self` contains a valid pointer. + // Further, all constructors guarantee that the `std` field of + // `ZendClassObject` will be initialized. + unsafe { ext_php_rs_zend_object_release(&mut self.std) } + } +} + +impl Deref for ZendClassObject { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.obj + .as_ref() + .expect("Attempted to access uninitalized class object") + } +} + +impl DerefMut for ZendClassObject { + fn deref_mut(&mut self) -> &mut Self::Target { + self.obj + .as_mut() + .expect("Attempted to access uninitalized class object") + } +} + +impl Default for ZBox> { + #[inline] + fn default() -> Self { + ZendClassObject::new(T::default()) + } +} + +impl Clone for ZBox> { + fn clone(&self) -> Self { + // SAFETY: All constructors of `NewClassObject` guarantee that it will contain a + // valid pointer. The constructor also guarantees that the internal + // `ZendClassObject` pointer will contain a valid, initialized `obj`, + // therefore we can dereference both safely. + unsafe { + let mut new = ZendClassObject::new((&***self).clone()); + zend_objects_clone_members(&mut new.std, &self.std as *const _ as *mut _); + new + } + } +} + +impl Debug for ZendClassObject { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + (&**self).fmt(f) + } +} + +impl IntoZval for ZBox> { + const TYPE: DataType = DataType::Object(Some(T::CLASS_NAME)); + + fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> { + let obj = self.into_raw(); + zv.set_object(&mut obj.std); + Ok(()) + } +} diff --git a/src/types/long.rs b/src/types/long.rs new file mode 100644 index 0000000000..13cbb9b62c --- /dev/null +++ b/src/types/long.rs @@ -0,0 +1,72 @@ +//! Represents an integer introduced in PHP. Note that the size of this integer +//! differs. On a 32-bit system, a ZendLong is 32-bits, while on a 64-bit system +//! it is 64-bits. + +use crate::{ + convert::IntoZval, + error::{Error, Result}, + ffi::zend_long, + flags::DataType, + macros::{into_zval, try_from_zval}, + types::Zval, +}; + +use std::convert::{TryFrom, TryInto}; + +/// A PHP long. +/// +/// The type size depends on the system architecture. On 32-bit systems, it is +/// 32-bits, while on a 64-bit system, it is 64-bits. +pub type ZendLong = zend_long; + +into_zval!(i8, set_long, Long); +into_zval!(i16, set_long, Long); +into_zval!(i32, set_long, Long); + +into_zval!(u8, set_long, Long); +into_zval!(u16, set_long, Long); + +macro_rules! try_into_zval_int { + ($type: ty) => { + impl TryFrom<$type> for Zval { + type Error = Error; + + fn try_from(val: $type) -> Result { + let mut zv = Self::new(); + let val: ZendLong = val.try_into().map_err(|_| Error::IntegerOverflow)?; + zv.set_long(val); + Ok(zv) + } + } + + impl IntoZval for $type { + const TYPE: DataType = DataType::Long; + + fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> { + let val: ZendLong = self.try_into().map_err(|_| Error::IntegerOverflow)?; + zv.set_long(val); + Ok(()) + } + } + }; +} + +try_into_zval_int!(i64); +try_into_zval_int!(u32); +try_into_zval_int!(u64); + +try_into_zval_int!(isize); +try_into_zval_int!(usize); + +try_from_zval!(i8, long, Long); +try_from_zval!(i16, long, Long); +try_from_zval!(i32, long, Long); +try_from_zval!(i64, long, Long); + +try_from_zval!(u8, long, Long); +try_from_zval!(u16, long, Long); +try_from_zval!(u32, long, Long); +try_from_zval!(u64, long, Long); + +try_from_zval!(usize, long, Long); +try_from_zval!(isize, long, Long); diff --git a/src/types/mod.rs b/src/types/mod.rs new file mode 100644 index 0000000000..888e056e78 --- /dev/null +++ b/src/types/mod.rs @@ -0,0 +1,37 @@ +//! Types defined by the Zend engine used in PHP. +//! +//! Generally, it is easier to work directly with Rust types, converting into +//! these PHP types when required. + +mod array; +mod callable; +mod class_object; +mod long; +mod object; +mod string; +mod zval; + +pub use array::ZendHashTable; +pub use callable::ZendCallable; +pub use class_object::ZendClassObject; +pub use long::ZendLong; +pub use object::{PropertyQuery, ZendObject}; +pub use string::ZendStr; +pub use zval::Zval; + +use crate::{convert::FromZval, flags::DataType, macros::into_zval}; + +into_zval!(f32, set_double, Double); +into_zval!(f64, set_double, Double); +into_zval!(bool, set_bool, Bool); + +try_from_zval!(f64, double, Double); +try_from_zval!(bool, bool, Bool); + +impl FromZval<'_> for f32 { + const TYPE: DataType = DataType::Double; + + fn from_zval(zval: &Zval) -> Option { + zval.double().map(|v| v as f32) + } +} diff --git a/src/types/object.rs b/src/types/object.rs new file mode 100644 index 0000000000..d946ee6610 --- /dev/null +++ b/src/types/object.rs @@ -0,0 +1,312 @@ +//! Represents an object in PHP. Allows for overriding the internal object used +//! by classes, allowing users to store Rust data inside a PHP object. + +use std::{convert::TryInto, fmt::Debug, ops::DerefMut}; + +use crate::{ + boxed::{ZBox, ZBoxable}, + class::RegisteredClass, + convert::{FromZendObject, FromZval, IntoZval}, + error::{Error, Result}, + ffi::{ + ext_php_rs_zend_object_release, zend_call_known_function, zend_object, zend_objects_new, + HashTable, ZEND_ISEMPTY, ZEND_PROPERTY_EXISTS, ZEND_PROPERTY_ISSET, + }, + flags::DataType, + rc::PhpRc, + types::{ZendClassObject, ZendStr, Zval}, + zend::{ce, ClassEntry, ExecutorGlobals, ZendObjectHandlers}, +}; + +/// A PHP object. +/// +/// This type does not maintain any information about its type, for example, +/// classes with have associated Rust structs cannot be accessed through this +/// type. [`ZendClassObject`] is used for this purpose, and you can convert +/// between the two. +pub type ZendObject = zend_object; + +impl ZendObject { + /// Creates a new [`ZendObject`], returned inside an [`ZBox`] + /// wrapper. + /// + /// # Parameters + /// + /// * `ce` - The type of class the new object should be an instance of. + /// + /// # Panics + /// + /// Panics when allocating memory for the new object fails. + pub fn new(ce: &ClassEntry) -> ZBox { + // SAFETY: Using emalloc to allocate memory inside Zend arena. Casting `ce` to + // `*mut` is valid as the function will not mutate `ce`. + unsafe { + let ptr = zend_objects_new(ce as *const _ as *mut _); + ZBox::from_raw( + ptr.as_mut() + .expect("Failed to allocate memory for Zend object"), + ) + } + } + + /// Creates a new `stdClass` instance, returned inside an + /// [`ZBox`] wrapper. + /// + /// # Panics + /// + /// Panics if allocating memory for the object fails, or if the `stdClass` + /// class entry has not been registered with PHP yet. + /// + /// # Example + /// + /// ```no_run + /// use ext_php_rs::types::ZendObject; + /// + /// let mut obj = ZendObject::new_stdclass(); + /// + /// obj.set_property("hello", "world"); + /// ``` + pub fn new_stdclass() -> ZBox { + // SAFETY: This will be `NULL` until it is initialized. `as_ref()` checks for + // null, so we can panic if it's null. + Self::new(ce::stdclass()) + } + + /// Converts a class object into an owned [`ZendObject`]. This removes any + /// possibility of accessing the underlying attached Rust struct. + pub fn from_class_object(obj: ZBox>) -> ZBox { + let this = obj.into_raw(); + // SAFETY: Consumed box must produce a well-aligned non-null pointer. + unsafe { ZBox::from_raw(this.get_mut_zend_obj()) } + } + + /// Attempts to retrieve the class name of the object. + pub fn get_class_name(&self) -> Result { + unsafe { + self.handlers()? + .get_class_name + .and_then(|f| f(self).as_ref()) + .ok_or(Error::InvalidScope) + .and_then(|s| s.try_into()) + } + } + + /// Checks if the given object is an instance of a registered class with + /// Rust type `T`. + pub fn is_instance(&self) -> bool { + (self.ce as *const ClassEntry).eq(&(T::get_metadata().ce() as *const _)) + } + + /// Attempts to read a property from the Object. Returns a result containing + /// the value of the property if it exists and can be read, and an + /// [`Error`] otherwise. + /// + /// # Parameters + /// + /// * `name` - The name of the property. + /// * `query` - The type of query to use when attempting to get a property. + pub fn get_property<'a, T>(&'a self, name: &str) -> Result + where + T: FromZval<'a>, + { + if !self.has_property(name, PropertyQuery::Exists)? { + return Err(Error::InvalidProperty); + } + + let mut name = ZendStr::new(name, false)?; + let mut rv = Zval::new(); + + let zv = unsafe { + self.handlers()?.read_property.ok_or(Error::InvalidScope)?( + self.mut_ptr(), + name.deref_mut(), + 1, + std::ptr::null_mut(), + &mut rv, + ) + .as_ref() + } + .ok_or(Error::InvalidScope)?; + + T::from_zval(zv).ok_or_else(|| Error::ZvalConversion(zv.get_type())) + } + + /// Attempts to set a property on the object. + /// + /// # Parameters + /// + /// * `name` - The name of the property. + /// * `value` - The value to set the property to. + pub fn set_property(&mut self, name: &str, value: impl IntoZval) -> Result<()> { + let mut name = ZendStr::new(name, false)?; + let mut value = value.into_zval(false)?; + + unsafe { + self.handlers()?.write_property.ok_or(Error::InvalidScope)?( + self, + name.deref_mut(), + &mut value, + std::ptr::null_mut(), + ) + .as_ref() + } + .ok_or(Error::InvalidScope)?; + Ok(()) + } + + /// Checks if a property exists on an object. Takes a property name and + /// query parameter, which defines what classifies if a property exists + /// or not. See [`PropertyQuery`] for more information. + /// + /// # Parameters + /// + /// * `name` - The name of the property. + /// * `query` - The 'query' to classify if a property exists. + pub fn has_property(&self, name: &str, query: PropertyQuery) -> Result { + let mut name = ZendStr::new(name, false)?; + + Ok(unsafe { + self.handlers()?.has_property.ok_or(Error::InvalidScope)?( + self.mut_ptr(), + name.deref_mut(), + query as _, + std::ptr::null_mut(), + ) + } > 0) + } + + /// Attempts to retrieve the properties of the object. Returned inside a + /// Zend Hashtable. + pub fn get_properties(&self) -> Result<&HashTable> { + unsafe { + self.handlers()? + .get_properties + .and_then(|props| props(self.mut_ptr()).as_ref()) + .ok_or(Error::InvalidScope) + } + } + + /// Extracts some type from a Zend object. + /// + /// This is a wrapper function around `FromZendObject::extract()`. + pub fn extract<'a, T>(&'a self) -> Result + where + T: FromZendObject<'a>, + { + T::from_zend_object(self) + } + + /// Attempts to retrieve a reference to the object handlers. + #[inline] + unsafe fn handlers(&self) -> Result<&ZendObjectHandlers> { + self.handlers.as_ref().ok_or(Error::InvalidScope) + } + + /// Returns a mutable pointer to `self`, regardless of the type of + /// reference. Only to be used in situations where a C function requires + /// a mutable pointer but does not modify the underlying data. + #[inline] + fn mut_ptr(&self) -> *mut Self { + (self as *const Self) as *mut Self + } +} + +unsafe impl ZBoxable for ZendObject { + fn free(&mut self) { + unsafe { ext_php_rs_zend_object_release(self) } + } +} + +impl Debug for ZendObject { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut dbg = f.debug_struct( + self.get_class_name() + .unwrap_or_else(|_| "ZendObject".to_string()) + .as_str(), + ); + + if let Ok(props) = self.get_properties() { + for (id, key, val) in props.iter() { + dbg.field(key.unwrap_or_else(|| id.to_string()).as_str(), val); + } + } + + dbg.finish() + } +} + +impl<'a> FromZval<'a> for &'a ZendObject { + const TYPE: DataType = DataType::Object(None); + + fn from_zval(zval: &'a Zval) -> Option { + zval.object() + } +} + +impl IntoZval for ZBox { + const TYPE: DataType = DataType::Object(None); + + fn set_zval(mut self, zv: &mut Zval, _: bool) -> Result<()> { + // We must decrement the refcounter on the object before inserting into the + // zval, as the reference counter will be incremented on add. + // NOTE(david): again is this needed, we increment in `set_object`. + self.dec_count(); + zv.set_object(self.into_raw()); + Ok(()) + } +} + +impl FromZendObject<'_> for String { + fn from_zend_object(obj: &ZendObject) -> Result { + let mut ret = Zval::new(); + unsafe { + zend_call_known_function( + (*obj.ce).__tostring, + obj as *const _ as *mut _, + obj.ce, + &mut ret, + 0, + std::ptr::null_mut(), + std::ptr::null_mut(), + ); + } + + if let Some(err) = ExecutorGlobals::take_exception() { + // TODO: become an error + let class_name = obj.get_class_name(); + panic!( + "Uncaught exception during call to {}::__toString(): {:?}", + class_name.expect("unable to determine class name"), + err + ); + } else if let Some(output) = ret.extract() { + Ok(output) + } else { + // TODO: become an error + let class_name = obj.get_class_name(); + panic!( + "{}::__toString() must return a string", + class_name.expect("unable to determine class name"), + ); + } + } +} + +impl From>> for ZBox { + #[inline] + fn from(obj: ZBox>) -> Self { + ZendObject::from_class_object(obj) + } +} + +/// Different ways to query if a property exists. +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[repr(u32)] +pub enum PropertyQuery { + /// Property exists and is not NULL. + Isset = ZEND_PROPERTY_ISSET, + /// Property is not empty. + NotEmpty = ZEND_ISEMPTY, + /// Property exists. + Exists = ZEND_PROPERTY_EXISTS, +} diff --git a/src/php/types/string.rs b/src/types/string.rs similarity index 51% rename from src/php/types/string.rs rename to src/types/string.rs index b44403417e..6296c735fc 100644 --- a/src/php/types/string.rs +++ b/src/types/string.rs @@ -1,5 +1,5 @@ -//! Represents a string in the PHP world. Similar to a C string, but is reference counted and -//! contains the length of the string, meaning the string can contain the NUL character. +//! Represents a string in the PHP world. Similar to a C string, but is +//! reference counted and contains the length of the string. use std::{ borrow::Cow, @@ -9,37 +9,41 @@ use std::{ slice, }; -use parking_lot::{ - lock_api::{Mutex, RawMutex}, - RawMutex as RawMutexStruct, -}; +use parking_lot::{const_mutex, Mutex}; use crate::{ - bindings::{ + boxed::{ZBox, ZBoxable}, + convert::{FromZval, IntoZval}, + error::{Error, Result}, + ffi::{ ext_php_rs_zend_string_init, ext_php_rs_zend_string_release, zend_string, zend_string_init_interned, }, - errors::{Error, Result}, - php::boxed::{ZBox, ZBoxable}, + flags::DataType, + macros::try_from_zval, + types::Zval, }; -/// A borrowed Zend-string. +/// A borrowed Zend string. /// -/// Although this object does implement [`Sized`], it is in fact not sized. As C cannot represent unsized -/// types, an array of size 1 is used at the end of the type to represent the contents of the string, therefore -/// this type is actually unsized. All constructors return [`ZBox`], the owned varaint. +/// Although this object does implement [`Sized`], it is in fact not sized. As C +/// cannot represent unsized types, an array of size 1 is used at the end of the +/// type to represent the contents of the string, therefore this type is +/// actually unsized. All constructors return [`ZBox`], the owned +/// varaint. /// -/// Once the `ptr_metadata` feature lands in stable rust, this type can potentially be changed to a DST using -/// slices and metadata. See the tracking issue here: +/// Once the `ptr_metadata` feature lands in stable rust, this type can +/// potentially be changed to a DST using slices and metadata. See the tracking issue here: pub type ZendStr = zend_string; -// Adding to the Zend interned string hashtable is not atomic and can be contested when PHP is compiled with ZTS, -// so an empty mutex is used to ensure no collisions occur on the Rust side. Not much we can do about collisions -// on the PHP side. -static INTERNED_LOCK: Mutex = Mutex::const_new(RawMutex::INIT, ()); +// Adding to the Zend interned string hashtable is not atomic and can be +// contested when PHP is compiled with ZTS, so an empty mutex is used to ensure +// no collisions occur on the Rust side. Not much we can do about collisions +// on the PHP side, but some safety is better than none. +static INTERNED_LOCK: Mutex<()> = const_mutex(()); -// Clippy complains about there being no `is_empty` function when implementing on the alias `ZendStr` :( -// +// Clippy complains about there being no `is_empty` function when implementing +// on the alias `ZendStr` :( #[allow(clippy::len_without_is_empty)] impl ZendStr { /// Creates a new Zend string from a [`str`]. @@ -47,16 +51,35 @@ impl ZendStr { /// # Parameters /// /// * `str` - String content. - /// * `persistent` - Whether the string should persist through the request boundary. + /// * `persistent` - Whether the string should persist through the request + /// boundary. /// /// # Returns /// - /// Returns a result containing the Zend string if successful. Returns an error if the given - /// string contains NUL bytes, which cannot be contained inside a C string. + /// Returns a result containing the Zend string if successful. Returns an + /// error if the given string contains NUL bytes, which cannot be + /// contained inside a C string. /// /// # Panics /// - /// Panics if the function was unable to allocate memory for the Zend string. + /// Panics if the function was unable to allocate memory for the Zend + /// string. + /// + /// # Safety + /// + /// When passing `persistent` as `false`, the caller must ensure that the + /// object does not attempt to live after the request finishes. When a + /// request starts and finishes in PHP, the Zend heap is deallocated and a + /// new one is created, which would leave a dangling pointer in the + /// [`ZBox`]. + /// + /// # Example + /// + /// ```no_run + /// use ext_php_rs::types::ZendStr; + /// + /// let s = ZendStr::new("Hello, world!", false).unwrap(); + /// ``` pub fn new(str: &str, persistent: bool) -> Result> { Ok(Self::from_c_str(&CString::new(str)?, persistent)) } @@ -66,11 +89,31 @@ impl ZendStr { /// # Parameters /// /// * `str` - String content. - /// * `persistent` - Whether the string should persist through the request boundary. + /// * `persistent` - Whether the string should persist through the request + /// boundary. /// /// # Panics /// - /// Panics if the function was unable to allocate memory for the Zend string. + /// Panics if the function was unable to allocate memory for the Zend + /// string. + /// + /// # Safety + /// + /// When passing `persistent` as `false`, the caller must ensure that the + /// object does not attempt to live after the request finishes. When a + /// request starts and finishes in PHP, the Zend heap is deallocated and a + /// new one is created, which would leave a dangling pointer in the + /// [`ZBox`]. + /// + /// # Example + /// + /// ```no_run + /// use ext_php_rs::types::ZendStr; + /// use std::ffi::CString; + /// + /// let c_s = CString::new("Hello world!").unwrap(); + /// let s = ZendStr::from_c_str(&c_s, false); + /// ``` pub fn from_c_str(str: &CStr, persistent: bool) -> ZBox { unsafe { let ptr = @@ -85,41 +128,71 @@ impl ZendStr { /// Creates a new interned Zend string from a [`str`]. /// - /// An interned string is only ever stored once and is immutable. PHP stores the string in an - /// internal hashtable which stores the interned strings. + /// An interned string is only ever stored once and is immutable. PHP stores + /// the string in an internal hashtable which stores the interned + /// strings. /// - /// As Zend hashtables are not thread-safe, a mutex is used to prevent two interned strings from - /// being created at the same time. + /// As Zend hashtables are not thread-safe, a mutex is used to prevent two + /// interned strings from being created at the same time. + /// + /// Interned strings are not used very often. You should almost always use a + /// regular zend string, except in the case that you know you will use a + /// string that PHP will already have interned, such as "PHP". /// /// # Parameters /// /// * `str` - String content. - /// * `persistent` - Whether the string should persist through the request boundary. + /// * `persistent` - Whether the string should persist through the request + /// boundary. /// /// # Returns /// - /// Returns a result containing the Zend string if successful. Returns an error if the given - /// string contains NUL bytes, which cannot be contained inside a C string. + /// Returns a result containing the Zend string if successful. Returns an + /// error if the given string contains NUL bytes, which cannot be + /// contained inside a C string. /// /// # Panics /// - /// Panics if the function was unable to allocate memory for the Zend string. + /// Panics if the function was unable to allocate memory for the Zend + /// string. + /// + /// # Safety + /// + /// When passing `persistent` as `false`, the caller must ensure that the + /// object does not attempt to live after the request finishes. When a + /// request starts and finishes in PHP, the Zend heap is deallocated and a + /// new one is created, which would leave a dangling pointer in the + /// [`ZBox`]. + /// + /// # Example + /// + /// ```no_run + /// use ext_php_rs::types::ZendStr; + /// + /// let s = ZendStr::new_interned("PHP", true); + /// ``` pub fn new_interned(str: &str, persistent: bool) -> Result> { Ok(Self::interned_from_c_str(&CString::new(str)?, persistent)) } /// Creates a new interned Zend string from a [`CStr`]. /// - /// An interned string is only ever stored once and is immutable. PHP stores the string in an - /// internal hashtable which stores the interned strings. + /// An interned string is only ever stored once and is immutable. PHP stores + /// the string in an internal hashtable which stores the interned + /// strings. /// - /// As Zend hashtables are not thread-safe, a mutex is used to prevent two interned strings from - /// being created at the same time. + /// As Zend hashtables are not thread-safe, a mutex is used to prevent two + /// interned strings from being created at the same time. + /// + /// Interned strings are not used very often. You should almost always use a + /// regular zend string, except in the case that you know you will use a + /// string that PHP will already have interned, such as "PHP". /// /// # Parameters /// /// * `str` - String content. - /// * `persistent` - Whether the string should persist through the request boundary. + /// * `persistent` - Whether the string should persist through the request + /// boundary. /// /// # Panics /// @@ -127,6 +200,24 @@ impl ZendStr { /// /// * The function used to create interned strings has not been set. /// * The function could not allocate enough memory for the Zend string. + /// + /// # Safety + /// + /// When passing `persistent` as `false`, the caller must ensure that the + /// object does not attempt to live after the request finishes. When a + /// request starts and finishes in PHP, the Zend heap is deallocated and a + /// new one is created, which would leave a dangling pointer in the + /// [`ZBox`]. + /// + /// # Example + /// + /// ```no_run + /// use ext_php_rs::types::ZendStr; + /// use std::ffi::CString; + /// + /// let c_s = CString::new("PHP").unwrap(); + /// let s = ZendStr::interned_from_c_str(&c_s, true); + /// ``` pub fn interned_from_c_str(str: &CStr, persistent: bool) -> ZBox { let _lock = INTERNED_LOCK.lock(); @@ -145,11 +236,29 @@ impl ZendStr { } /// Returns the length of the string. + /// + /// # Example + /// + /// ```no_run + /// use ext_php_rs::types::ZendStr; + /// + /// let s = ZendStr::new("hello, world!", false).unwrap(); + /// assert_eq!(s.len(), 13); + /// ``` pub fn len(&self) -> usize { self.len as usize } /// Returns true if the string is empty, false otherwise. + /// + /// # Example + /// + /// ```no_run + /// use ext_php_rs::types::ZendStr; + /// + /// let s = ZendStr::new("hello, world!", false).unwrap(); + /// assert_eq!(s.is_empty(), false); + /// ``` pub fn is_empty(&self) -> bool { self.len() == 0 } @@ -163,9 +272,21 @@ impl ZendStr { } } - /// Attempts to return a reference to the underlying [`str`] inside the Zend string. + /// Attempts to return a reference to the underlying [`str`] inside the Zend + /// string. + /// + /// Returns the [`None`] variant if the [`CStr`] contains non-UTF-8 + /// characters. + /// + /// # Example /// - /// Returns the [`None`] variant if the [`CStr`] contains non-UTF-8 characters. + /// ```no_run + /// use ext_php_rs::types::ZendStr; + /// + /// let s = ZendStr::new("hello, world!", false).unwrap(); + /// let as_str = s.as_str(); + /// assert_eq!(as_str, Some("hello, world!")); + /// ``` pub fn as_str(&self) -> Option<&str> { self.as_c_str().to_str().ok() } @@ -268,3 +389,37 @@ impl From> for ZBox { value.into_owned() } } + +macro_rules! try_into_zval_str { + ($type: ty) => { + impl TryFrom<$type> for Zval { + type Error = Error; + + fn try_from(value: $type) -> Result { + let mut zv = Self::new(); + zv.set_string(&value, false)?; + Ok(zv) + } + } + + impl IntoZval for $type { + const TYPE: DataType = DataType::String; + + fn set_zval(self, zv: &mut Zval, persistent: bool) -> Result<()> { + zv.set_string(&self, persistent) + } + } + }; +} + +try_into_zval_str!(String); +try_into_zval_str!(&str); +try_from_zval!(String, string, String); + +impl<'a> FromZval<'a> for &'a str { + const TYPE: DataType = DataType::String; + + fn from_zval(zval: &'a Zval) -> Option { + zval.str() + } +} diff --git a/src/php/types/zval.rs b/src/types/zval.rs similarity index 55% rename from src/php/types/zval.rs rename to src/types/zval.rs index 5905c1acdf..0da658fba6 100644 --- a/src/php/types/zval.rs +++ b/src/types/zval.rs @@ -1,31 +1,38 @@ -//! The base value in PHP. A Zval can contain any PHP type, and the type that it contains is -//! determined by a property inside the struct. The content of the Zval is stored in a union. - -use std::{ - convert::{TryFrom, TryInto}, - ffi::c_void, - fmt::Debug, - ptr, -}; +//! The base value in PHP. A Zval can contain any PHP type, and the type that it +//! contains is determined by a property inside the struct. The content of the +//! Zval is stored in a union. + +use std::{convert::TryInto, ffi::c_void, fmt::Debug, ptr}; use crate::{ - bindings::{ + binary::Pack, + boxed::ZBox, + convert::{FromZval, IntoZval, IntoZvalDyn}, + error::{Error, Result}, + ffi::{ _zval_struct__bindgen_ty_1, _zval_struct__bindgen_ty_2, zend_is_callable, zend_resource, zend_value, zval, zval_ptr_dtor, }, - errors::{Error, Result}, - php::{boxed::ZBox, exceptions::PhpException, pack::Pack}, + flags::DataType, + flags::ZvalTypeFlags, + rc::PhpRc, + types::{ZendCallable, ZendHashTable, ZendLong, ZendObject, ZendStr}, }; -use crate::php::{enums::DataType, flags::ZvalTypeFlags, types::long::ZendLong}; - -use super::{array::HashTable, callable::Callable, object::ZendObject, rc::PhpRc, string::ZendStr}; - -/// Zend value. Represents most data types that are in the Zend engine. +/// A zend value. This is the primary storage container used throughout the Zend +/// engine. +/// +/// A zval can be thought of as a Rust enum, a type that can contain different +/// values such as integers, strings, objects etc. pub type Zval = zval; -unsafe impl Send for Zval {} -unsafe impl Sync for Zval {} +// TODO(david): can we make zval send+sync? main problem is that refcounted +// types do not have atomic refcounters, so technically two threads could +// reference the same object and attempt to modify refcounter at the same time. +// need to look into how ZTS works. + +// unsafe impl Send for Zval {} +// unsafe impl Sync for Zval {} impl Zval { /// Creates a new, empty zval. @@ -72,8 +79,9 @@ impl Zval { /// Returns the value of the zval as a zend string, if it is a string. /// - /// Note that this functions output will not be the same as [`string()`](#method.string), as - /// this function does not attempt to convert other types into a [`String`]. + /// Note that this functions output will not be the same as + /// [`string()`](#method.string), as this function does not attempt to + /// convert other types into a [`String`]. pub fn zend_str(&self) -> Option<&ZendStr> { if self.is_string() { unsafe { self.value.str_.as_ref() } @@ -84,10 +92,13 @@ impl Zval { /// Returns the value of the zval if it is a string. /// - /// If the zval does not contain a string, the function will check if it contains a - /// double or a long, and if so it will convert the value to a [`String`] and return it. - /// Don't rely on this logic, as there is potential for this to change to match the output - /// of the [`str()`](#method.str) function. + /// If the zval does not contain a string, the function will check if it + /// contains a double or a long, and if so it will convert the value to + /// a [`String`] and return it. Don't rely on this logic, as there is + /// potential for this to change to match the output of the [`str()`] + /// function. + /// + /// [`str()`]: #method.str pub fn string(&self) -> Option { self.str() .map(|s| s.to_string()) @@ -96,22 +107,24 @@ impl Zval { /// Returns the value of the zval if it is a string. /// - /// Note that this functions output will not be the same as [`string()`](#method.string), as - /// this function does not attempt to convert other types into a [`String`], as it could not - /// pass back a [`&str`] in those cases. + /// Note that this functions output will not be the same as + /// [`string()`](#method.string), as this function does not attempt to + /// convert other types into a [`String`], as it could not pass back a + /// [`&str`] in those cases. pub fn str(&self) -> Option<&str> { self.zend_str().and_then(|zs| zs.as_str()) } - /// Returns the value of the zval if it is a string and can be unpacked into a vector of a - /// given type. Similar to the [`unpack`](https://www.php.net/manual/en/function.unpack.php) + /// Returns the value of the zval if it is a string and can be unpacked into + /// a vector of a given type. Similar to the [`unpack`](https://www.php.net/manual/en/function.unpack.php) /// in PHP, except you can only unpack one type. /// /// # Safety /// - /// There is no way to tell if the data stored in the string is actually of the given type. - /// The results of this function can also differ from platform-to-platform due to the different - /// representation of some types on different platforms. Consult the [`pack`] function + /// There is no way to tell if the data stored in the string is actually of + /// the given type. The results of this function can also differ from + /// platform-to-platform due to the different representation of some + /// types on different platforms. Consult the [`pack`] function /// documentation for more details. /// /// [`pack`]: https://www.php.net/manual/en/function.pack.php @@ -135,8 +148,9 @@ impl Zval { } } - /// Returns an immutable reference to the underlying zval hashtable if the zval contains an array. - pub fn array(&self) -> Option<&HashTable> { + /// Returns an immutable reference to the underlying zval hashtable if the + /// zval contains an array. + pub fn array(&self) -> Option<&ZendHashTable> { if self.is_array() { unsafe { self.value.arr.as_ref() } } else { @@ -144,8 +158,9 @@ impl Zval { } } - /// Returns a mutable reference to the underlying zval hashtable if the zval contains an array. - pub fn array_mut(&mut self) -> Option<&mut HashTable> { + /// Returns a mutable reference to the underlying zval hashtable if the zval + /// contains an array. + pub fn array_mut(&mut self) -> Option<&mut ZendHashTable> { if self.is_array() { unsafe { self.value.arr.as_mut() } } else { @@ -162,7 +177,8 @@ impl Zval { } } - /// Returns a mutable reference to the object contained in the [`Zval`], if any. + /// Returns a mutable reference to the object contained in the [`Zval`], if + /// any. pub fn object_mut(&mut self) -> Option<&mut ZendObject> { if self.is_object() { unsafe { self.value.obj.as_mut() } @@ -190,17 +206,18 @@ impl Zval { } /// Returns the value of the zval if it is callable. - pub fn callable(&self) -> Option { + pub fn callable(&self) -> Option { // The Zval is checked if it is callable in the `new` function. - Callable::new(self).ok() + ZendCallable::new(self).ok() } /// Returns the value of the zval if it is a pointer. /// /// # Safety /// - /// The caller must ensure that the pointer contained in the zval is in fact a pointer to an - /// instance of `T`, as the zval has no way of defining the type of pointer. + /// The caller must ensure that the pointer contained in the zval is in fact + /// a pointer to an instance of `T`, as the zval has no way of defining + /// the type of pointer. pub unsafe fn ptr(&self) -> Option<*mut T> { if self.is_ptr() { Some(self.value.ptr as *mut T) @@ -209,12 +226,14 @@ impl Zval { } } - /// Attempts to call the zval as a callable with a list of arguments to pass to the function. - /// Note that a thrown exception inside the callable is not detectable, therefore you should - /// check if the return value is valid rather than unwrapping. Returns a result containing the - /// return value of the function, or an error. + /// Attempts to call the zval as a callable with a list of arguments to pass + /// to the function. Note that a thrown exception inside the callable is + /// not detectable, therefore you should check if the return value is + /// valid rather than unwrapping. Returns a result containing the return + /// value of the function, or an error. /// - /// You should not call this function directly, rather through the [`call_user_func`] macro. + /// You should not call this function directly, rather through the + /// [`call_user_func`] macro. /// /// # Parameters /// @@ -294,7 +313,8 @@ impl Zval { self.get_type() == DataType::Ptr } - /// Sets the value of the zval as a string. Returns nothing in a result when successful. + /// Sets the value of the zval as a string. Returns nothing in a result when + /// successful. /// /// # Parameters /// @@ -315,7 +335,8 @@ impl Zval { self.value.str_ = val.into_raw(); } - /// Sets the value of the zval as a binary string, which is represented in Rust as a vector. + /// Sets the value of the zval as a binary string, which is represented in + /// Rust as a vector. /// /// # Parameters /// @@ -326,7 +347,8 @@ impl Zval { self.value.str_ = ptr; } - /// Sets the value of the zval as a interned string. Returns nothing in a result when successful. + /// Sets the value of the zval as a interned string. Returns nothing in a + /// result when successful. /// /// # Parameters /// @@ -410,22 +432,27 @@ impl Zval { self.value.obj = (val as *const ZendObject) as *mut ZendObject; } - /// Sets the value of the zval as an array. Returns nothing in a result on success. + /// Sets the value of the zval as an array. Returns nothing in a result on + /// success. /// /// # Parameters /// /// * `val` - The value to set the zval as. - pub fn set_array, Error = Error>>(&mut self, val: T) -> Result<()> { + pub fn set_array, Error = Error>>( + &mut self, + val: T, + ) -> Result<()> { self.set_hashtable(val.try_into()?); Ok(()) } - /// Sets the value of the zval as an array. Returns nothing in a result on success. + /// Sets the value of the zval as an array. Returns nothing in a result on + /// success. /// /// # Parameters /// /// * `val` - The value to set the zval as. - pub fn set_hashtable(&mut self, val: ZBox) { + pub fn set_hashtable(&mut self, val: ZBox) { self.change_type(ZvalTypeFlags::ArrayEx); self.value.arr = val.into_raw(); } @@ -442,21 +469,24 @@ impl Zval { /// Used to drop the Zval but keep the value of the zval intact. /// - /// This is important when copying the value of the zval, as the actual value - /// will not be copied, but the pointer to the value (string for example) will be - /// copied. + /// This is important when copying the value of the zval, as the actual + /// value will not be copied, but the pointer to the value (string for + /// example) will be copied. pub(crate) fn release(mut self) { - // NOTE(david): don't use `change_type` here as we are wanting to keep the contents intact. + // NOTE(david): don't use `change_type` here as we are wanting to keep the + // contents intact. self.u1.type_info = ZvalTypeFlags::Null.bits(); } - /// Changes the type of the zval, freeing the current contents when applicable. + /// Changes the type of the zval, freeing the current contents when + /// applicable. /// /// # Parameters /// /// * `ty` - The new type of the zval. fn change_type(&mut self, ty: ZvalTypeFlags) { - // SAFETY: we have exclusive mutable access to this zval so can free the contents. + // SAFETY: we have exclusive mutable access to this zval so can free the + // contents. unsafe { zval_ptr_dtor(self) }; self.u1.type_info = ty.bits(); } @@ -520,35 +550,6 @@ impl Default for Zval { } } -/// Provides implementations for converting Rust primitive types into PHP zvals. Alternative to the -/// built-in Rust [`From`] and [`TryFrom`] implementations, allowing the caller to specify whether -/// the Zval contents will persist between requests. -pub trait IntoZval: Sized { - /// The corresponding type of the implemented value in PHP. - const TYPE: DataType; - - /// Converts a Rust primitive type into a Zval. Returns a result containing the Zval if - /// successful. - /// - /// # Parameters - /// - /// * `persistent` - Whether the contents of the Zval will persist between requests. - fn into_zval(self, persistent: bool) -> Result { - let mut zval = Zval::new(); - self.set_zval(&mut zval, persistent)?; - Ok(zval) - } - - /// Sets the content of a pre-existing zval. Returns a result containing nothing if setting - /// the content was successful. - /// - /// # Parameters - /// - /// * `zv` - The Zval to set the content of. - /// * `persistent` - Whether the contents of the Zval will persist between requests. - fn set_zval(self, zv: &mut Zval, persistent: bool) -> Result<()>; -} - impl IntoZval for Zval { const TYPE: DataType = DataType::Mixed; @@ -557,287 +558,3 @@ impl IntoZval for Zval { Ok(()) } } - -/// An object-safe version of the [`IntoZval`] trait. -/// -/// This trait is automatically implemented on any type that implements both [`IntoZval`] and [`Clone`]. -/// You avoid implementing this trait directly, rather implement these two other traits. -pub trait IntoZvalDyn { - /// Converts a Rust primitive type into a Zval. Returns a result containing the Zval if - /// successful. `self` is cloned before being converted into a zval. - /// - /// # Parameters - /// - /// * `persistent` - Whether the contents of the Zval will persist between requests. - fn as_zval(&self, persistent: bool) -> Result; - - /// Returns the PHP type of the type. - fn get_type(&self) -> DataType; -} - -impl IntoZvalDyn for T { - fn as_zval(&self, persistent: bool) -> Result { - self.clone().into_zval(persistent) - } - - fn get_type(&self) -> DataType { - Self::TYPE - } -} - -macro_rules! into_zval { - ($type: ty, $fn: ident, $dt: ident) => { - impl From<$type> for Zval { - fn from(val: $type) -> Self { - let mut zv = Self::new(); - zv.$fn(val); - zv - } - } - - impl IntoZval for $type { - const TYPE: DataType = DataType::$dt; - - fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> { - zv.$fn(self); - Ok(()) - } - } - }; -} - -into_zval!(i8, set_long, Long); -into_zval!(i16, set_long, Long); -into_zval!(i32, set_long, Long); - -into_zval!(u8, set_long, Long); -into_zval!(u16, set_long, Long); - -into_zval!(f32, set_double, Double); -into_zval!(f64, set_double, Double); - -into_zval!(bool, set_bool, Bool); - -macro_rules! try_into_zval_int { - ($type: ty) => { - impl TryFrom<$type> for Zval { - type Error = Error; - - fn try_from(val: $type) -> Result { - let mut zv = Self::new(); - let val: ZendLong = val.try_into().map_err(|_| Error::IntegerOverflow)?; - zv.set_long(val); - Ok(zv) - } - } - - impl IntoZval for $type { - const TYPE: DataType = DataType::Long; - - fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> { - let val: ZendLong = self.try_into().map_err(|_| Error::IntegerOverflow)?; - zv.set_long(val); - Ok(()) - } - } - }; -} - -try_into_zval_int!(i64); -try_into_zval_int!(u32); -try_into_zval_int!(u64); - -try_into_zval_int!(isize); -try_into_zval_int!(usize); - -macro_rules! try_into_zval_str { - ($type: ty) => { - impl TryFrom<$type> for Zval { - type Error = Error; - - fn try_from(value: $type) -> Result { - let mut zv = Self::new(); - zv.set_string(&value, false)?; - Ok(zv) - } - } - - impl IntoZval for $type { - const TYPE: DataType = DataType::String; - - fn set_zval(self, zv: &mut Zval, persistent: bool) -> Result<()> { - zv.set_string(&self, persistent) - } - } - }; -} - -try_into_zval_str!(String); -try_into_zval_str!(&str); - -impl IntoZval for () { - const TYPE: DataType = DataType::Void; - - fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> { - zv.set_null(); - Ok(()) - } -} - -impl IntoZval for Option -where - T: IntoZval, -{ - const TYPE: DataType = T::TYPE; - - fn set_zval(self, zv: &mut Zval, persistent: bool) -> Result<()> { - match self { - Some(val) => val.set_zval(zv, persistent), - None => { - zv.set_null(); - Ok(()) - } - } - } -} - -impl IntoZval for std::result::Result -where - T: IntoZval, - E: Into, -{ - const TYPE: DataType = T::TYPE; - - fn set_zval(self, zv: &mut Zval, persistent: bool) -> Result<()> { - match self { - Ok(val) => val.set_zval(zv, persistent), - Err(e) => { - let ex: PhpException = e.into(); - ex.throw() - } - } - } -} - -/// Allows zvals to be converted into Rust types in a fallible way. Reciprocal of the [`IntoZval`] -/// trait. -pub trait FromZval<'a>: Sized { - /// The corresponding type of the implemented value in PHP. - const TYPE: DataType; - - /// Attempts to retrieve an instance of `Self` from a reference to a [`Zval`]. - /// - /// # Parameters - /// - /// * `zval` - Zval to get value from. - fn from_zval(zval: &'a Zval) -> Option; -} - -/// Allows mutable zvals to be converted into Rust types in a fallible way. -/// -/// If `Self` does not require the zval to be mutable to be extracted, you should implement -/// [`FromZval`] instead, as this trait is generically implemented for any type that implements -/// [`FromZval`]. -pub trait FromZvalMut<'a>: Sized { - /// The corresponding type of the implemented value in PHP. - const TYPE: DataType; - - /// Attempts to retrieve an instance of `Self` from a mutable reference to a [`Zval`]. - /// - /// # Parameters - /// - /// * `zval` - Zval to get value from. - fn from_zval_mut(zval: &'a mut Zval) -> Option; -} - -impl<'a, T> FromZvalMut<'a> for T -where - T: FromZval<'a>, -{ - const TYPE: DataType = ::TYPE; - - #[inline] - fn from_zval_mut(zval: &'a mut Zval) -> Option { - Self::from_zval(zval) - } -} - -impl<'a, T> FromZval<'a> for Option -where - T: FromZval<'a>, -{ - const TYPE: DataType = T::TYPE; - - fn from_zval(zval: &'a Zval) -> Option { - Some(T::from_zval(zval)) - } -} - -macro_rules! try_from_zval { - ($type: ty, $fn: ident, $dt: ident) => { - impl FromZval<'_> for $type { - const TYPE: DataType = DataType::$dt; - - fn from_zval(zval: &Zval) -> Option { - zval.$fn().and_then(|val| val.try_into().ok()) - } - } - - impl TryFrom for $type { - type Error = Error; - - fn try_from(value: Zval) -> Result { - Self::from_zval(&value).ok_or(Error::ZvalConversion(value.get_type())) - } - } - }; -} - -try_from_zval!(i8, long, Long); -try_from_zval!(i16, long, Long); -try_from_zval!(i32, long, Long); -try_from_zval!(i64, long, Long); - -try_from_zval!(u8, long, Long); -try_from_zval!(u16, long, Long); -try_from_zval!(u32, long, Long); -try_from_zval!(u64, long, Long); - -try_from_zval!(usize, long, Long); -try_from_zval!(isize, long, Long); - -try_from_zval!(f64, double, Double); -try_from_zval!(bool, bool, Bool); -try_from_zval!(String, string, String); - -impl FromZval<'_> for f32 { - const TYPE: DataType = DataType::Double; - - fn from_zval(zval: &Zval) -> Option { - zval.double().map(|v| v as f32) - } -} - -impl<'a> FromZval<'a> for &'a str { - const TYPE: DataType = DataType::String; - - fn from_zval(zval: &'a Zval) -> Option { - zval.str() - } -} - -impl<'a> FromZval<'a> for Callable<'a> { - const TYPE: DataType = DataType::Callable; - - fn from_zval(zval: &'a Zval) -> Option { - Callable::new(zval).ok() - } -} - -impl<'a> TryFrom for Callable<'a> { - type Error = Error; - - fn try_from(value: Zval) -> Result { - Callable::new_owned(value) - } -} diff --git a/src/wrapper/wrapper.c b/src/wrapper.c similarity index 100% rename from src/wrapper/wrapper.c rename to src/wrapper.c diff --git a/src/wrapper/wrapper.h b/src/wrapper.h similarity index 100% rename from src/wrapper/wrapper.h rename to src/wrapper.h diff --git a/src/php/types/mod.rs b/src/zend/_type.rs similarity index 84% rename from src/php/types/mod.rs rename to src/zend/_type.rs index 1dfba576b3..4342e3fcf4 100644 --- a/src/php/types/mod.rs +++ b/src/zend/_type.rs @@ -1,31 +1,16 @@ -//! Contains all the different types that are introduced into PHP. -//! Introduces functions for converting between Zend values and Rust values. - -pub mod array; -pub mod binary; -pub mod callable; -#[cfg(any(docs, feature = "closure"))] -#[cfg_attr(docs, doc(cfg(feature = "closure")))] -pub mod closure; -pub mod long; -pub mod object; -pub mod props; -pub mod rc; -pub mod string; -pub mod zval; - use std::{ ffi::{c_void, CString}, ptr, }; -use crate::bindings::{ - zend_type, IS_MIXED, MAY_BE_ANY, MAY_BE_BOOL, _IS_BOOL, _ZEND_IS_VARIADIC_BIT, - _ZEND_SEND_MODE_SHIFT, _ZEND_TYPE_NAME_BIT, _ZEND_TYPE_NULLABLE_BIT, +use crate::{ + ffi::{ + zend_type, IS_MIXED, MAY_BE_ANY, MAY_BE_BOOL, _IS_BOOL, _ZEND_IS_VARIADIC_BIT, + _ZEND_SEND_MODE_SHIFT, _ZEND_TYPE_NAME_BIT, _ZEND_TYPE_NULLABLE_BIT, + }, + flags::DataType, }; -use super::enums::DataType; - /// Internal Zend type. pub type ZendType = zend_type; @@ -43,17 +28,20 @@ impl ZendType { } } - /// Attempts to create a zend type for a given datatype. Returns an option containing the type. + /// Attempts to create a zend type for a given datatype. Returns an option + /// containing the type. /// - /// Returns [`None`] if the data type was a class object where the class name could not be converted - /// into a C string (i.e. contained NUL-bytes). + /// Returns [`None`] if the data type was a class object where the class + /// name could not be converted into a C string (i.e. contained + /// NUL-bytes). /// /// # Parameters /// /// * `type_` - Data type to create zend type for. /// * `pass_by_ref` - Whether the type should be passed by reference. /// * `is_variadic` - Whether the type is for a variadic argument. - /// * `allow_null` - Whether the type should allow null to be passed in place. + /// * `allow_null` - Whether the type should allow null to be passed in + /// place. pub fn empty_from_type( type_: DataType, pass_by_ref: bool, @@ -73,17 +61,20 @@ impl ZendType { } } - /// Attempts to create a zend type for a class object type. Returns an option containing the type if successful. + /// Attempts to create a zend type for a class object type. Returns an + /// option containing the type if successful. /// - /// Returns [`None`] if the data type was a class object where the class name could not be converted - /// into a C string (i.e. contained NUL-bytes). + /// Returns [`None`] if the data type was a class object where the class + /// name could not be converted into a C string (i.e. contained + /// NUL-bytes). /// /// # Parameters /// /// * `class_name` - Name of the class parameter. /// * `pass_by_ref` - Whether the type should be passed by reference. /// * `is_variadic` - Whether the type is for a variadic argument. - /// * `allow_null` - Whether the type should allow null to be passed in place. + /// * `allow_null` - Whether the type should allow null to be passed in + /// place. fn empty_from_class_type( class_name: &str, pass_by_ref: bool, @@ -109,7 +100,8 @@ impl ZendType { /// * `type_` - Data type to create zend type for. /// * `pass_by_ref` - Whether the type should be passed by reference. /// * `is_variadic` - Whether the type is for a variadic argument. - /// * `allow_null` - Whether the type should allow null to be passed in place. + /// * `allow_null` - Whether the type should allow null to be passed in + /// place. /// /// # Panics /// diff --git a/src/zend/ce.rs b/src/zend/ce.rs new file mode 100644 index 0000000000..b2e9d4393f --- /dev/null +++ b/src/zend/ce.rs @@ -0,0 +1,72 @@ +//! Stock class entries registered with PHP, primarily exceptions. + +#![allow(clippy::unwrap_used)] + +use crate::ffi::{ + zend_ce_argument_count_error, zend_ce_arithmetic_error, zend_ce_compile_error, + zend_ce_division_by_zero_error, zend_ce_error_exception, zend_ce_exception, + zend_ce_parse_error, zend_ce_throwable, zend_ce_type_error, zend_ce_unhandled_match_error, + zend_ce_value_error, zend_standard_class_def, +}; + +use super::ClassEntry; + +/// Returns the base `stdClass` class. +pub fn stdclass() -> &'static ClassEntry { + unsafe { zend_standard_class_def.as_ref() }.unwrap() +} + +/// Returns the base `Throwable` class. +pub fn throwable() -> &'static ClassEntry { + unsafe { zend_ce_throwable.as_ref() }.unwrap() +} + +/// Returns the base `Exception` class. +pub fn exception() -> &'static ClassEntry { + unsafe { zend_ce_exception.as_ref() }.unwrap() +} + +/// Returns the base `ErrorException` class. +pub fn error_exception() -> &'static ClassEntry { + unsafe { zend_ce_error_exception.as_ref() }.unwrap() +} + +/// Returns the base `CompileError` class. +pub fn compile_error() -> &'static ClassEntry { + unsafe { zend_ce_compile_error.as_ref() }.unwrap() +} + +/// Returns the base `ParseError` class. +pub fn parse_error() -> &'static ClassEntry { + unsafe { zend_ce_parse_error.as_ref() }.unwrap() +} + +/// Returns the base `TypeError` class. +pub fn type_error() -> &'static ClassEntry { + unsafe { zend_ce_type_error.as_ref() }.unwrap() +} + +/// Returns the base `ArgumentCountError` class. +pub fn argument_count_error() -> &'static ClassEntry { + unsafe { zend_ce_argument_count_error.as_ref() }.unwrap() +} + +/// Returns the base `ValueError` class. +pub fn value_error() -> &'static ClassEntry { + unsafe { zend_ce_value_error.as_ref() }.unwrap() +} + +/// Returns the base `ArithmeticError` class. +pub fn arithmetic_error() -> &'static ClassEntry { + unsafe { zend_ce_arithmetic_error.as_ref() }.unwrap() +} + +/// Returns the base `DivisionByZeroError` class. +pub fn division_by_zero_error() -> &'static ClassEntry { + unsafe { zend_ce_division_by_zero_error.as_ref() }.unwrap() +} + +/// Returns the base `UnhandledMatchError` class. +pub fn unhandled_match_error() -> &'static ClassEntry { + unsafe { zend_ce_unhandled_match_error.as_ref() }.unwrap() +} diff --git a/src/zend/class.rs b/src/zend/class.rs new file mode 100644 index 0000000000..7e61c87a5a --- /dev/null +++ b/src/zend/class.rs @@ -0,0 +1,126 @@ +//! Builder and objects for creating classes in the PHP world. + +use crate::{ffi::zend_class_entry, flags::ClassFlags, types::ZendStr, zend::ExecutorGlobals}; +use std::{convert::TryInto, fmt::Debug, ops::DerefMut}; + +/// A PHP class entry. +/// +/// Represents a class registered with the PHP interpreter. +pub type ClassEntry = zend_class_entry; + +impl ClassEntry { + /// Attempts to find a reference to a class in the global class table. + /// + /// Returns a reference to the class if found, or [`None`] if the class + /// could not be found or the class table has not been initialized. + pub fn try_find(name: &str) -> Option<&'static Self> { + ExecutorGlobals::get().class_table()?; + let mut name = ZendStr::new(name, false).ok()?; + + unsafe { + crate::ffi::zend_lookup_class_ex(name.deref_mut(), std::ptr::null_mut(), 0).as_ref() + } + } + + /// Returns the class flags. + pub fn flags(&self) -> ClassFlags { + ClassFlags::from_bits_truncate(self.ce_flags) + } + + /// Returns `true` if the class entry is an interface, and `false` + /// otherwise. + pub fn is_interface(&self) -> bool { + self.flags().contains(ClassFlags::Interface) + } + + /// Checks if the class is an instance of another class or interface. + /// + /// # Parameters + /// + /// * `ce` - The inherited class entry to check. + pub fn instance_of(&self, ce: &ClassEntry) -> bool { + if self == ce { + return true; + } + + if ce.flags().contains(ClassFlags::Interface) { + let interfaces = match self.interfaces() { + Some(interfaces) => interfaces, + None => return false, + }; + + for i in interfaces { + if ce == i { + return true; + } + } + } else { + loop { + let parent = match self.parent() { + Some(parent) => parent, + None => return false, + }; + + if parent == ce { + return true; + } + } + } + + false + } + + /// Returns an iterator of all the interfaces that the class implements. + /// + /// Returns [`None`] if the interfaces have not been resolved on the + /// class. + pub fn interfaces(&self) -> Option> { + self.flags() + .contains(ClassFlags::ResolvedInterfaces) + .then(|| unsafe { + (0..self.num_interfaces) + .into_iter() + .map(move |i| *self.__bindgen_anon_3.interfaces.offset(i as _)) + .filter_map(|ptr| ptr.as_ref()) + }) + } + + /// Returns the parent of the class. + /// + /// If the parent of the class has not been resolved, it attempts to find + /// the parent by name. Returns [`None`] if the parent was not resolved + /// and the parent was not able to be found by name. + pub fn parent(&self) -> Option<&Self> { + if self.flags().contains(ClassFlags::ResolvedParent) { + unsafe { self.__bindgen_anon_1.parent.as_ref() } + } else { + let name = unsafe { self.__bindgen_anon_1.parent_name.as_ref()? }; + Self::try_find(name.as_str()?) + } + } +} + +impl PartialEq for ClassEntry { + fn eq(&self, other: &Self) -> bool { + std::ptr::eq(self, other) + } +} + +impl Debug for ClassEntry { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let name: String = unsafe { self.name.as_ref() } + .and_then(|s| s.try_into().ok()) + .ok_or(std::fmt::Error)?; + + f.debug_struct("ClassEntry") + .field("name", &name) + .field("flags", &self.flags()) + .field("is_interface", &self.is_interface()) + .field( + "interfaces", + &self.interfaces().map(|iter| iter.collect::>()), + ) + .field("parent", &self.parent()) + .finish() + } +} diff --git a/src/zend/ex.rs b/src/zend/ex.rs new file mode 100644 index 0000000000..2c433a3877 --- /dev/null +++ b/src/zend/ex.rs @@ -0,0 +1,247 @@ +use crate::ffi::{zend_execute_data, ZEND_MM_ALIGNMENT, ZEND_MM_ALIGNMENT_MASK}; + +use crate::{ + args::ArgParser, + class::RegisteredClass, + types::{ZendClassObject, ZendObject, Zval}, +}; + +/// Execute data passed when a function is called from PHP. +/// +/// This generally contains things related to the call, including but not +/// limited to: +/// +/// * Arguments +/// * `$this` object reference +/// * Reference to return value +/// * Previous execute data +pub type ExecuteData = zend_execute_data; + +impl ExecuteData { + /// Returns an [`ArgParser`] pre-loaded with the arguments contained inside + /// `self`. + /// + /// # Example + /// + /// ```no_run + /// use ext_php_rs::{types::Zval, zend::ExecuteData, args::Arg, flags::DataType}; + /// + /// #[no_mangle] + /// pub extern "C" fn example_fn(ex: &mut ExecuteData, retval: &mut Zval) { + /// let mut a = Arg::new("a", DataType::Long); + /// + /// // The `parse_args!()` macro can be used for this. + /// let parser = ex.parser() + /// .arg(&mut a) + /// .parse(); + /// + /// if parser.is_err() { + /// return; + /// } + /// + /// dbg!(a); + /// } + /// ``` + pub fn parser<'a>(&'a mut self) -> ArgParser<'a, '_> { + self.parser_object().0 + } + + /// Returns an [`ArgParser`] pre-loaded with the arguments contained inside + /// `self`. + /// + /// A reference to `$this` is also returned in an [`Option`], which resolves + /// to [`None`] if this function is not called inside a method. + /// + /// # Example + /// + /// ```no_run + /// use ext_php_rs::{types::Zval, zend::ExecuteData, args::Arg, flags::DataType}; + /// + /// #[no_mangle] + /// pub extern "C" fn example_fn(ex: &mut ExecuteData, retval: &mut Zval) { + /// let mut a = Arg::new("a", DataType::Long); + /// + /// let (parser, this) = ex.parser_object(); + /// let parser = parser + /// .arg(&mut a) + /// .parse(); + /// + /// if parser.is_err() { + /// return; + /// } + /// + /// dbg!(a, this); + /// } + /// ``` + pub fn parser_object<'a>(&'a mut self) -> (ArgParser<'a, '_>, Option<&'a mut ZendObject>) { + // SAFETY: All fields of the `u2` union are the same type. + let n_args = unsafe { self.This.u2.num_args }; + let mut args = vec![]; + + for i in 0..n_args { + // SAFETY: Function definition ensures arg lifetime doesn't exceed execution + // data lifetime. + let arg = unsafe { self.zend_call_arg(i as usize) }; + args.push(arg); + } + + let obj = self.This.object_mut(); + + (ArgParser::new(args), obj) + } + + /// Returns an [`ArgParser`] pre-loaded with the arguments contained inside + /// `self`. + /// + /// A reference to `$this` is also returned in an [`Option`], which resolves + /// to [`None`] if this function is not called inside a method. + /// + /// This function differs from [`parse_object`] in the fact that it returns + /// a reference to a [`ZendClassObject`], which is an object that + /// contains an arbitrary Rust type at the start of the object. The + /// object will also resolve to [`None`] if the function is called + /// inside a method that does not belong to an object with type `T`. + /// + /// # Example + /// + /// ```no_run + /// use ext_php_rs::{types::Zval, zend::ExecuteData, args::Arg, flags::DataType, prelude::*}; + /// + /// #[php_class] + /// #[derive(Debug)] + /// struct Example; + /// + /// #[no_mangle] + /// pub extern "C" fn example_fn(ex: &mut ExecuteData, retval: &mut Zval) { + /// let mut a = Arg::new("a", DataType::Long); + /// + /// let (parser, this) = ex.parser_method::(); + /// let parser = parser + /// .arg(&mut a) + /// .parse(); + /// + /// if parser.is_err() { + /// return; + /// } + /// + /// dbg!(a, this); + /// } + /// + /// #[php_module] + /// pub fn module(module: ModuleBuilder) -> ModuleBuilder { + /// module + /// } + /// ``` + /// + /// [`parse_object`]: #method.parse_object + pub fn parser_method<'a, T: RegisteredClass>( + &'a mut self, + ) -> (ArgParser<'a, '_>, Option<&'a mut ZendClassObject>) { + let (parser, obj) = self.parser_object(); + ( + parser, + obj.and_then(|obj| ZendClassObject::from_zend_obj_mut(obj)), + ) + } + + /// Attempts to retrieve a reference to the underlying class object of the + /// Zend object. + /// + /// Returns a [`ZendClassObject`] if the execution data contained a valid + /// object of type `T`, otherwise returns [`None`]. + /// + /// # Example + /// + /// ```no_run + /// use ext_php_rs::{types::Zval, zend::ExecuteData, prelude::*}; + /// + /// #[php_class] + /// #[derive(Debug)] + /// struct Example; + /// + /// #[no_mangle] + /// pub extern "C" fn example_fn(ex: &mut ExecuteData, retval: &mut Zval) { + /// let this = ex.get_object::(); + /// dbg!(this); + /// } + /// + /// #[php_module] + /// pub fn module(module: ModuleBuilder) -> ModuleBuilder { + /// module + /// } + /// ``` + pub fn get_object(&mut self) -> Option<&mut ZendClassObject> { + ZendClassObject::from_zend_obj_mut(self.get_self()?) + } + + /// Attempts to retrieve the 'this' object, which can be used in class + /// methods to retrieve the underlying Zend object. + /// + /// # Example + /// + /// ```no_run + /// use ext_php_rs::{types::Zval, zend::ExecuteData}; + /// + /// #[no_mangle] + /// pub extern "C" fn example_fn(ex: &mut ExecuteData, retval: &mut Zval) { + /// let this = ex.get_self(); + /// dbg!(this); + /// } + /// ``` + pub fn get_self(&mut self) -> Option<&mut ZendObject> { + // TODO(david): This should be a `&mut self` function but we need to fix arg + // parser first. + self.This.object_mut() + } + + /// Translation of macro `ZEND_CALL_ARG(call, n)` + /// zend_compile.h:578 + /// + /// The resultant zval reference has a lifetime equal to the lifetime of + /// `self`. This isn't specified because when you attempt to get a + /// reference to args and the `$this` object, Rust doesnt't let you. + /// Since this is a private method it's up to the caller to ensure the + /// lifetime isn't exceeded. + #[doc(hidden)] + unsafe fn zend_call_arg<'a>(&self, n: usize) -> Option<&'a mut Zval> { + let ptr = self.zend_call_var_num(n as isize); + ptr.as_mut() + } + + /// Translation of macro `ZEND_CALL_VAR_NUM(call, n)` + /// zend_compile.h: 575 + #[doc(hidden)] + unsafe fn zend_call_var_num(&self, n: isize) -> *mut Zval { + let ptr = self as *const Self as *mut Zval; + ptr.offset(Self::zend_call_frame_slot() + n as isize) + } + + /// Translation of macro `ZEND_CALL_FRAME_SLOT` + /// zend_compile:573 + #[doc(hidden)] + fn zend_call_frame_slot() -> isize { + (Self::zend_mm_aligned_size::() + Self::zend_mm_aligned_size::() - 1) + / Self::zend_mm_aligned_size::() + } + + /// Translation of macro `ZEND_MM_ALIGNED_SIZE(size)` + /// zend_alloc.h:41 + #[doc(hidden)] + fn zend_mm_aligned_size() -> isize { + let size = std::mem::size_of::(); + ((size as isize) + ZEND_MM_ALIGNMENT as isize - 1) & ZEND_MM_ALIGNMENT_MASK as isize + } +} + +#[cfg(test)] +mod tests { + use super::ExecuteData; + + #[test] + fn test_zend_call_frame_slot() { + // PHP 8.0.2 (cli) (built: Feb 21 2021 11:51:33) ( NTS ) + // Copyright (c) The PHP Group + // Zend Engine v4.0.2, Copyright (c) Zend Technologies + assert_eq!(ExecuteData::zend_call_frame_slot(), 5); + } +} diff --git a/src/zend/function.rs b/src/zend/function.rs new file mode 100644 index 0000000000..11c7fcd0c6 --- /dev/null +++ b/src/zend/function.rs @@ -0,0 +1,27 @@ +//! Builder for creating functions and methods in PHP. + +use std::{os::raw::c_char, ptr}; + +use crate::ffi::zend_function_entry; + +/// A Zend function entry. +pub type FunctionEntry = zend_function_entry; + +impl FunctionEntry { + /// Returns an empty function entry, signifing the end of a function list. + pub fn end() -> Self { + Self { + fname: ptr::null() as *const c_char, + handler: None, + arg_info: ptr::null(), + num_args: 0, + flags: 0, + } + } + + /// Converts the function entry into a raw and pointer, releasing it to the + /// C world. + pub fn into_raw(self) -> *mut Self { + Box::into_raw(Box::new(self)) + } +} diff --git a/src/zend/globals.rs b/src/zend/globals.rs new file mode 100644 index 0000000000..6a171180c8 --- /dev/null +++ b/src/zend/globals.rs @@ -0,0 +1,112 @@ +//! Types related to the PHP executor globals. + +use std::ops::{Deref, DerefMut}; + +use parking_lot::{const_rwlock, RwLock, RwLockReadGuard, RwLockWriteGuard}; + +use crate::boxed::ZBox; +use crate::ffi::{_zend_executor_globals, ext_php_rs_executor_globals}; + +use crate::types::{ZendHashTable, ZendObject}; + +/// Stores global variables used in the PHP executor. +pub type ExecutorGlobals = _zend_executor_globals; + +impl ExecutorGlobals { + /// Returns a reference to the PHP executor globals. + /// + /// The executor globals are guarded by a RwLock. There can be multiple + /// immutable references at one time but only ever one mutable reference. + /// Attempting to retrieve the globals while already holding the global + /// guard will lead to a deadlock. Dropping the globals guard will release + /// the lock. + pub fn get() -> GlobalReadGuard { + // SAFETY: PHP executor globals are statically declared therefore should never + // return an invalid pointer. + let globals = unsafe { ext_php_rs_executor_globals().as_ref() } + .expect("Static executor globals were invalid"); + let guard = GLOBALS_LOCK.read(); + GlobalReadGuard { globals, guard } + } + + /// Returns a mutable reference to the PHP executor globals. + /// + /// The executor globals are guarded by a RwLock. There can be multiple + /// immutable references at one time but only ever one mutable reference. + /// Attempting to retrieve the globals while already holding the global + /// guard will lead to a deadlock. Dropping the globals guard will release + /// the lock. + fn get_mut() -> GlobalWriteGuard { + // SAFETY: PHP executor globals are statically declared therefore should never + // return an invalid pointer. + let globals = unsafe { ext_php_rs_executor_globals().as_mut() } + .expect("Static executor globals were invalid"); + let guard = GLOBALS_LOCK.write(); + GlobalWriteGuard { globals, guard } + } + + /// Attempts to retrieve the global class hash table. + pub fn class_table(&self) -> Option<&ZendHashTable> { + unsafe { self.class_table.as_ref() } + } + + /// Attempts to extract the last PHP exception captured by the interpreter. + /// Returned inside a [`ZBox`]. + /// + /// This function requires the executor globals to be mutably held, which + /// could lead to a deadlock if the globals are already borrowed immutably + /// or mutably. + pub fn take_exception() -> Option> { + let mut globals = Self::get_mut(); + + let mut exception_ptr = std::ptr::null_mut(); + std::mem::swap(&mut exception_ptr, &mut globals.exception); + + // SAFETY: `as_mut` checks for null. + Some(unsafe { ZBox::from_raw(exception_ptr.as_mut()?) }) + } +} + +/// Executor globals rwlock. +/// +/// PHP provides no indication if the executor globals are being accessed so +/// this is only effective on the Rust side. +static GLOBALS_LOCK: RwLock<()> = const_rwlock(()); + +/// Wrapper guard that contains a reference to a given type `T`. Dropping a +/// guard releases the lock on the relevant rwlock. +pub struct GlobalReadGuard { + globals: &'static T, + #[allow(dead_code)] + guard: RwLockReadGuard<'static, ()>, +} + +impl Deref for GlobalReadGuard { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.globals + } +} + +/// Wrapper guard that contains a mutable reference to a given type `T`. +/// Dropping a guard releases the lock on the relevant rwlock. +pub struct GlobalWriteGuard { + globals: &'static mut T, + #[allow(dead_code)] + guard: RwLockWriteGuard<'static, ()>, +} + +impl Deref for GlobalWriteGuard { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.globals + } +} + +impl DerefMut for GlobalWriteGuard { + fn deref_mut(&mut self) -> &mut Self::Target { + self.globals + } +} diff --git a/src/zend/handlers.rs b/src/zend/handlers.rs new file mode 100644 index 0000000000..fede613937 --- /dev/null +++ b/src/zend/handlers.rs @@ -0,0 +1,262 @@ +use std::{ffi::c_void, os::raw::c_int, ptr}; + +use crate::{ + class::RegisteredClass, + exception::PhpResult, + ffi::{ + std_object_handlers, zend_is_true, zend_object_handlers, zend_object_std_dtor, + zend_std_get_properties, zend_std_has_property, zend_std_read_property, + zend_std_write_property, + }, + flags::ZvalTypeFlags, + types::{ZendClassObject, ZendHashTable, ZendObject, ZendStr, Zval}, +}; + +/// A set of functions associated with a PHP class. +pub type ZendObjectHandlers = zend_object_handlers; + +impl ZendObjectHandlers { + /// Initializes a given set of object handlers by copying the standard + /// object handlers into the memory location, as well as setting up the + /// `T` type destructor. + /// + /// # Parameters + /// + /// * `ptr` - Pointer to memory location to copy the standard handlers to. + /// + /// # Safety + /// + /// Caller must guarantee that the `ptr` given is a valid memory location. + pub unsafe fn init(ptr: *mut ZendObjectHandlers) { + std::ptr::copy_nonoverlapping(&std_object_handlers, ptr, 1); + let offset = ZendClassObject::::std_offset(); + (*ptr).offset = offset as _; + (*ptr).free_obj = Some(Self::free_obj::); + (*ptr).read_property = Some(Self::read_property::); + (*ptr).write_property = Some(Self::write_property::); + (*ptr).get_properties = Some(Self::get_properties::); + (*ptr).has_property = Some(Self::has_property::); + } + + unsafe extern "C" fn free_obj(object: *mut ZendObject) { + let obj = object + .as_mut() + .and_then(|obj| ZendClassObject::::from_zend_obj_mut(obj)) + .expect("Invalid object pointer given for `free_obj`"); + + // Manually drop the object as we don't want to free the underlying memory. + ptr::drop_in_place(&mut obj.obj); + + zend_object_std_dtor(object) + } + + unsafe extern "C" fn read_property( + object: *mut ZendObject, + member: *mut ZendStr, + type_: c_int, + cache_slot: *mut *mut c_void, + rv: *mut Zval, + ) -> *mut Zval { + #[inline(always)] + unsafe fn internal( + object: *mut ZendObject, + member: *mut ZendStr, + type_: c_int, + cache_slot: *mut *mut c_void, + rv: *mut Zval, + ) -> PhpResult<*mut Zval> { + let obj = object + .as_mut() + .and_then(|obj| ZendClassObject::::from_zend_obj_mut(obj)) + .ok_or("Invalid object pointer given")?; + let prop_name = member + .as_ref() + .ok_or("Invalid property name pointer given")?; + let self_ = &mut **obj; + let mut props = T::get_properties(); + let prop = props.remove(prop_name.as_str().ok_or("Invalid property name given")?); + + // retval needs to be treated as initialized, so we set the type to null + let rv_mut = rv.as_mut().ok_or("Invalid return zval given")?; + rv_mut.u1.type_info = ZvalTypeFlags::Null.bits(); + + Ok(match prop { + Some(prop) => { + prop.get(self_, rv_mut)?; + rv + } + None => zend_std_read_property(object, member, type_, cache_slot, rv), + }) + } + + match internal::(object, member, type_, cache_slot, rv) { + Ok(rv) => rv, + Err(e) => { + let _ = e.throw(); + (&mut *rv).set_null(); + rv + } + } + } + + unsafe extern "C" fn write_property( + object: *mut ZendObject, + member: *mut ZendStr, + value: *mut Zval, + cache_slot: *mut *mut c_void, + ) -> *mut Zval { + #[inline(always)] + unsafe fn internal( + object: *mut ZendObject, + member: *mut ZendStr, + value: *mut Zval, + cache_slot: *mut *mut c_void, + ) -> PhpResult<*mut Zval> { + let obj = object + .as_mut() + .and_then(|obj| ZendClassObject::::from_zend_obj_mut(obj)) + .ok_or("Invalid object pointer given")?; + let prop_name = member + .as_ref() + .ok_or("Invalid property name pointer given")?; + let self_ = &mut **obj; + let mut props = T::get_properties(); + let prop = props.remove(prop_name.as_str().ok_or("Invalid property name given")?); + let value_mut = value.as_mut().ok_or("Invalid return zval given")?; + + Ok(match prop { + Some(prop) => { + prop.set(self_, value_mut)?; + value + } + None => zend_std_write_property(object, member, value, cache_slot), + }) + } + + match internal::(object, member, value, cache_slot) { + Ok(rv) => rv, + Err(e) => { + let _ = e.throw(); + value + } + } + } + + unsafe extern "C" fn get_properties( + object: *mut ZendObject, + ) -> *mut ZendHashTable { + #[inline(always)] + unsafe fn internal( + object: *mut ZendObject, + props: &mut ZendHashTable, + ) -> PhpResult { + let obj = object + .as_mut() + .and_then(|obj| ZendClassObject::::from_zend_obj_mut(obj)) + .ok_or("Invalid object pointer given")?; + let self_ = &mut **obj; + let struct_props = T::get_properties(); + + for (name, val) in struct_props.into_iter() { + let mut zv = Zval::new(); + if val.get(self_, &mut zv).is_err() { + continue; + } + props.insert(name, zv).map_err(|e| { + format!("Failed to insert value into properties hashtable: {:?}", e) + })?; + } + + Ok(()) + } + + let props = zend_std_get_properties(object) + .as_mut() + .or_else(|| Some(ZendHashTable::new().into_raw())) + .expect("Failed to get property hashtable"); + + if let Err(e) = internal::(object, props) { + let _ = e.throw(); + } + + props + } + + unsafe extern "C" fn has_property( + object: *mut ZendObject, + member: *mut ZendStr, + has_set_exists: c_int, + cache_slot: *mut *mut c_void, + ) -> c_int { + #[inline(always)] + unsafe fn internal( + object: *mut ZendObject, + member: *mut ZendStr, + has_set_exists: c_int, + cache_slot: *mut *mut c_void, + ) -> PhpResult { + let obj = object + .as_mut() + .and_then(|obj| ZendClassObject::::from_zend_obj_mut(obj)) + .ok_or("Invalid object pointer given")?; + let prop_name = member + .as_ref() + .ok_or("Invalid property name pointer given")?; + let props = T::get_properties(); + let prop = props.get(prop_name.as_str().ok_or("Invalid property name given")?); + let self_ = &mut **obj; + + match has_set_exists { + // + // * 0 (has) whether property exists and is not NULL + 0 => { + if let Some(val) = prop { + let mut zv = Zval::new(); + val.get(self_, &mut zv)?; + if !zv.is_null() { + return Ok(1); + } + } + } + // + // * 1 (set) whether property exists and is true + 1 => { + if let Some(val) = prop { + let mut zv = Zval::new(); + val.get(self_, &mut zv)?; + + if zend_is_true(&mut zv) == 1 { + return Ok(1); + } + } + } + // + // * 2 (exists) whether property exists + 2 => { + if prop.is_some() { + return Ok(1); + } + } + _ => return Err( + "Invalid value given for `has_set_exists` in struct `has_property` function." + .into(), + ), + }; + + Ok(zend_std_has_property( + object, + member, + has_set_exists, + cache_slot, + )) + } + + match internal::(object, member, has_set_exists, cache_slot) { + Ok(rv) => rv, + Err(e) => { + let _ = e.throw(); + 0 + } + } + } +} diff --git a/src/zend/mod.rs b/src/zend/mod.rs new file mode 100644 index 0000000000..3bad6ace76 --- /dev/null +++ b/src/zend/mod.rs @@ -0,0 +1,18 @@ +//! Types used to interact with the Zend engine. + +mod _type; +pub mod ce; +mod class; +mod ex; +mod function; +mod globals; +mod handlers; +mod module; + +pub use _type::ZendType; +pub use class::ClassEntry; +pub use ex::ExecuteData; +pub use function::FunctionEntry; +pub use globals::ExecutorGlobals; +pub use handlers::ZendObjectHandlers; +pub use module::ModuleEntry; diff --git a/src/zend/module.rs b/src/zend/module.rs new file mode 100644 index 0000000000..d86d50375a --- /dev/null +++ b/src/zend/module.rs @@ -0,0 +1,15 @@ +//! Builder and objects for creating modules in PHP. A module is the base of a +//! PHP extension. + +use crate::ffi::zend_module_entry; + +/// A Zend module entry, also known as an extension. +pub type ModuleEntry = zend_module_entry; + +impl ModuleEntry { + /// Allocates the module entry on the heap, returning a pointer to the + /// memory location. The caller is responsible for the memory pointed to. + pub fn into_raw(self) -> *mut Self { + Box::into_raw(Box::new(self)) + } +}