From 18ede9771280e45ab6b73efde3450eeebb70f7a1 Mon Sep 17 00:00:00 2001 From: Joe Hoyle Date: Mon, 19 Sep 2022 15:53:27 +0200 Subject: [PATCH 01/34] Add ability to set function name on php_function macro This is much the same as `php_class(name="ABC")` --- crates/macros/src/function.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/macros/src/function.rs b/crates/macros/src/function.rs index 4c9520c..9efa5b1 100644 --- a/crates/macros/src/function.rs +++ b/crates/macros/src/function.rs @@ -17,6 +17,7 @@ pub struct AttrArgs { optional: Option, ignore_module: bool, defaults: HashMap, + name: Option, } #[derive(Debug, Clone)] @@ -93,7 +94,7 @@ pub fn parser(args: AttributeArgs, input: ItemFn) -> Result<(TokenStream, Functi } let function = Function { - name: ident.to_string(), + name: attr_args.name.unwrap_or_else(|| ident.to_string()), docs: get_docs(&input.attrs), ident: internal_ident.to_string(), args, From 90cbbc0fca3a54c58cdf234b9358aca9a790d156 Mon Sep 17 00:00:00 2001 From: Joe Hoyle Date: Sat, 1 Oct 2022 00:23:27 +0200 Subject: [PATCH 02/34] Specify classes as fully-qualified names in stubs (#156) * Specify classes as fully-qualified names in stubs When stubs are generated, the type annotations don't use a loading `\`, which means they are interpreted as relative to the current namespace. That's wrong, as all types are relative to the root namespace. * rustfmt --- src/describe/stub.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/describe/stub.rs b/src/describe/stub.rs index ab1660c..4c501f6 100644 --- a/src/describe/stub.rs +++ b/src/describe/stub.rs @@ -153,6 +153,7 @@ impl ToStub for Parameter { impl ToStub for DataType { fn fmt_stub(&self, buf: &mut String) -> FmtResult { + let mut fqdn = "\\".to_owned(); write!( buf, "{}", @@ -162,7 +163,10 @@ impl ToStub for DataType { DataType::Double => "float", DataType::String => "string", DataType::Array => "array", - DataType::Object(Some(ty)) => ty, + DataType::Object(Some(ty)) => { + fqdn.push_str(ty); + fqdn.as_str() + } DataType::Object(None) => "object", DataType::Resource => "resource", DataType::Reference => "reference", From 5d1fda4666b621c317731175ca385f501daebc35 Mon Sep 17 00:00:00 2001 From: Joe Hoyle Date: Sat, 1 Oct 2022 00:23:48 +0200 Subject: [PATCH 03/34] Support marking classes as interfaces (#155) * Support marking classes as interfaces This allows passing flags as part of `#[php_class(flags=Interface]` etc, which allows one to mark a class as being an interface. When a class is an interface, it also shouldn't get a constructor created for it. * rustfmt --- crates/macros/src/class.rs | 4 ++++ crates/macros/src/startup_function.rs | 29 ++++++++++++++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/crates/macros/src/class.rs b/crates/macros/src/class.rs index 4e5e81e..c10a015 100644 --- a/crates/macros/src/class.rs +++ b/crates/macros/src/class.rs @@ -22,6 +22,7 @@ pub struct Class { /// A function name called when creating the class entry. Given an instance /// of `ClassBuilder` and must return it. pub modifier: Option, + pub flags: Option, } #[derive(Debug)] @@ -37,6 +38,7 @@ pub enum ParsedAttribute { pub struct AttrArgs { name: Option, modifier: Option, + flags: Option, } pub fn parser(args: AttributeArgs, mut input: ItemStruct) -> Result { @@ -117,6 +119,7 @@ pub fn parser(args: AttributeArgs, mut input: ItemStruct) -> Result let ItemStruct { ident, .. } = &input; let class_name = args.name.unwrap_or_else(|| ident.to_string()); let struct_path = ident.to_string(); + let flags = args.flags.map(|flags| flags.to_token_stream().to_string()); let class = Class { class_name, struct_path, @@ -125,6 +128,7 @@ pub fn parser(args: AttributeArgs, mut input: ItemStruct) -> Result docs: comments, properties, modifier: args.modifier, + flags, ..Default::default() }; diff --git a/crates/macros/src/startup_function.rs b/crates/macros/src/startup_function.rs index c6f078c..47fcfeb 100644 --- a/crates/macros/src/startup_function.rs +++ b/crates/macros/src/startup_function.rs @@ -121,6 +121,31 @@ fn build_classes(classes: &HashMap) -> Result> { } }); + let flags = { + if let Some(flags) = &class.flags { + let mut name = "::ext_php_rs::flags::ClassFlags::".to_owned(); + name.push_str(flags); + let expr: Expr = syn::parse_str(&name).map_err(|_| { + anyhow!("Invalid expression given for `{}` flags", class_name) + })?; + Some(quote! { .flags(#expr) }) + } else { + None + } + }; + + let object_override = { + if let Some(flags) = &class.flags { + if flags == "Interface" { + None + } else { + Some(quote! { .object_override::<#ident>() }) + } + } else { + Some(quote! { .object_override::<#ident>() }) + } + }; + Ok(quote! {{ let builder = ::ext_php_rs::builders::ClassBuilder::new(#class_name) #(#methods)* @@ -128,7 +153,9 @@ fn build_classes(classes: &HashMap) -> Result> { #(#interfaces)* // #(#properties)* #parent - .object_override::<#ident>(); + #flags + #object_override + ; #class_modifier let class = builder.build() .expect(concat!("Unable to build class `", #class_name, "`")); From d4ef116b31b2fcb80241349a2eb476ffc0d6179d Mon Sep 17 00:00:00 2001 From: Joe Hoyle Date: Sat, 1 Oct 2022 00:24:04 +0200 Subject: [PATCH 04/34] Support marking methods as abstract (#154) For classes that are registered with `#[php_impl]` this allows functions to be marked as abstract. --- crates/macros/src/impl_.rs | 2 ++ crates/macros/src/method.rs | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/crates/macros/src/impl_.rs b/crates/macros/src/impl_.rs index 2fe45df..ca7851b 100644 --- a/crates/macros/src/impl_.rs +++ b/crates/macros/src/impl_.rs @@ -85,6 +85,7 @@ pub enum ParsedAttribute { }, Constructor, This, + Abstract, } #[derive(Default, Debug, FromMeta)] @@ -212,6 +213,7 @@ pub fn parse_attribute(attr: &Attribute) -> Result> { "public" => ParsedAttribute::Visibility(Visibility::Public), "protected" => ParsedAttribute::Visibility(Visibility::Protected), "private" => ParsedAttribute::Visibility(Visibility::Private), + "abstract_method" => ParsedAttribute::Abstract, "rename" => { let ident = if let Meta::List(list) = meta { if let Some(NestedMeta::Lit(lit)) = list.nested.first() { diff --git a/crates/macros/src/method.rs b/crates/macros/src/method.rs index d6bbaab..87e12c7 100644 --- a/crates/macros/src/method.rs +++ b/crates/macros/src/method.rs @@ -38,6 +38,7 @@ pub struct Method { pub optional: Option, pub output: Option<(String, bool)>, pub _static: bool, + pub _abstract: bool, pub visibility: Visibility, } @@ -81,6 +82,7 @@ pub fn parser( let mut visibility = Visibility::Public; let mut as_prop = None; let mut identifier = None; + let mut is_abstract = false; let mut is_constructor = false; let docs = get_docs(&input.attrs); @@ -90,6 +92,7 @@ pub fn parser( ParsedAttribute::Default(list) => defaults = list, ParsedAttribute::Optional(name) => optional = Some(name), ParsedAttribute::Visibility(vis) => visibility = vis, + ParsedAttribute::Abstract => is_abstract = true, ParsedAttribute::Rename(ident) => identifier = Some(ident), ParsedAttribute::Property { prop_name, ty } => { if as_prop.is_some() { @@ -211,6 +214,7 @@ pub fn parser( optional, output: get_return_type(struct_ty, &input.sig.output)?, _static: matches!(method_type, MethodType::Static), + _abstract: is_abstract, visibility, }; @@ -447,6 +451,10 @@ impl Method { flags.push(quote! { Static }); } + if self._abstract { + flags.push(quote! { Abstract }); + } + flags .iter() .map(|flag| quote! { ::ext_php_rs::flags::MethodFlags::#flag }) From 1da812cf781da5113e15d894df4a3b54e9ae1def Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Fri, 30 Sep 2022 23:24:19 +0100 Subject: [PATCH 05/34] Add php-scrypt as a example project (#146) --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 3e546c9..d199760 100644 --- a/README.md +++ b/README.md @@ -163,6 +163,8 @@ Check out one of the example projects: - [opus-php](https://github.com/davidcole1340/opus-php) - Audio encoder for the Opus codec in PHP. - [tomlrs-php](https://github.com/jphenow/tomlrs-php) - TOML data format parser. +- [php-scrypt](https://github.com/appwrite/php-scrypt) - PHP wrapper for the + scrypt password hashing algorithm. ## Contributions From 7dac4010a07516f26698f93c9b4a42e25ee9fa77 Mon Sep 17 00:00:00 2001 From: Robert O'Rourke Date: Fri, 30 Sep 2022 23:24:36 +0100 Subject: [PATCH 06/34] Fix INI file truncation and duplication (#136) Addresses https://github.com/davidcole1340/ext-php-rs/issues/134 Also prevents adding the extension include line twice. --- crates/cli/src/lib.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/crates/cli/src/lib.rs b/crates/cli/src/lib.rs index 639a7d9..cbac739 100644 --- a/crates/cli/src/lib.rs +++ b/crates/cli/src/lib.rs @@ -10,7 +10,7 @@ use dialoguer::{Confirm, Select}; use std::{ fs::OpenOptions, - io::{BufRead, BufReader, Write}, + io::{BufRead, BufReader, Seek, SeekFrom, Write}, path::PathBuf, process::{Command, Stdio}, }; @@ -207,6 +207,8 @@ impl Install { let line = line.with_context(|| "Failed to read line from `php.ini`")?; if !line.contains(&ext_line) { new_lines.push(line); + } else { + bail!("Extension already enabled."); } } @@ -216,6 +218,8 @@ impl Install { } new_lines.push(ext_line); + file.seek(SeekFrom::Start(0))?; + file.set_len(0)?; file.write(new_lines.join("\n").as_bytes()) .with_context(|| "Failed to update `php.ini`")?; } @@ -318,7 +322,6 @@ impl Remove { .read(true) .write(true) .create(true) - .truncate(true) .open(php_ini) .with_context(|| "Failed to open `php.ini`")?; @@ -330,6 +333,8 @@ impl Remove { } } + file.seek(SeekFrom::Start(0))?; + file.set_len(0)?; file.write(new_lines.join("\n").as_bytes()) .with_context(|| "Failed to update `php.ini`")?; } From 8a81c4b7f8d1d711806c2b09c99ff7fe25ef213e Mon Sep 17 00:00:00 2001 From: Robert O'Rourke Date: Sat, 1 Oct 2022 00:14:15 +0100 Subject: [PATCH 07/34] Allow passing `--yes` parameter to bypass prompts (#135) * Allow passing `--yes` parameter to bypass prompts Makes this tool usable in automated builds such as Docker containers. Addresses https://github.com/davidcole1340/ext-php-rs/issues/133 * Update readme and guides * rustfmt Co-authored-by: David Cole --- crates/cli/README.md | 14 ++++++++++---- crates/cli/src/lib.rs | 32 ++++++++++++++++++++------------ guide/src/cargo-php.md | 14 ++++++++++---- 3 files changed, 40 insertions(+), 20 deletions(-) diff --git a/crates/cli/README.md b/crates/cli/README.md index 4fd2a04..8d71caf 100644 --- a/crates/cli/README.md +++ b/crates/cli/README.md @@ -40,7 +40,7 @@ SUBCOMMANDS: Generates stub PHP files for the extension $ cargo php install --help -cargo-php-install +cargo-php-install Installs the extension in the current PHP installation. @@ -71,8 +71,11 @@ OPTIONS: --release Whether to install the release version of the extension + --yes + Bypasses the confirmation prompt + $ cargo php remove --help -cargo-php-remove +cargo-php-remove Removes the extension in the current PHP installation. @@ -97,8 +100,11 @@ OPTIONS: Path to the Cargo manifest of the extension. Defaults to the manifest in the directory the command is called + --yes + Bypasses the confirmation prompt + $ cargo php stubs --help -cargo-php-stubs +cargo-php-stubs Generates stub PHP files for the extension. @@ -120,7 +126,7 @@ OPTIONS: --manifest Path to the Cargo manifest of the extension. Defaults to the manifest in the directory the command is called. - + This cannot be provided alongside the `ext` option, as that option provides a direct path to the extension shared library. diff --git a/crates/cli/src/lib.rs b/crates/cli/src/lib.rs index cbac739..c145bbd 100644 --- a/crates/cli/src/lib.rs +++ b/crates/cli/src/lib.rs @@ -105,6 +105,9 @@ struct Install { /// the directory the command is called. #[arg(long)] manifest: Option, + /// Whether to bypass the install prompt. + #[clap(long)] + yes: bool, } #[derive(Parser)] @@ -121,6 +124,9 @@ struct Remove { /// the directory the command is called. #[arg(long)] manifest: Option, + /// Whether to bypass the remove prompt. + #[clap(long)] + yes: bool, } #[cfg(not(windows))] @@ -172,12 +178,13 @@ impl Install { php_ini = Some(ini_path); } - if !Confirm::new() - .with_prompt(format!( - "Are you sure you want to install the extension `{}`?", - artifact.name - )) - .interact()? + if !self.yes + && !Confirm::new() + .with_prompt(format!( + "Are you sure you want to install the extension `{}`?", + artifact.name + )) + .interact()? { bail!("Installation cancelled."); } @@ -305,12 +312,13 @@ impl Remove { bail!("Unable to find extension installed."); } - if !Confirm::new() - .with_prompt(format!( - "Are you sure you want to remove the extension `{}`?", - artifact.name - )) - .interact()? + if !self.yes + && !Confirm::new() + .with_prompt(format!( + "Are you sure you want to remove the extension `{}`?", + artifact.name + )) + .interact()? { bail!("Installation cancelled."); } diff --git a/guide/src/cargo-php.md b/guide/src/cargo-php.md index d1640ef..147e8cc 100644 --- a/guide/src/cargo-php.md +++ b/guide/src/cargo-php.md @@ -84,7 +84,7 @@ personally recommend for use in Visual Studio Code). ```text $ cargo php stubs --help -cargo-php-stubs +cargo-php-stubs Generates stub PHP files for the extension. @@ -106,7 +106,7 @@ OPTIONS: --manifest Path to the Cargo manifest of the extension. Defaults to the manifest in the directory the command is called. - + This cannot be provided alongside the `ext` option, as that option provides a direct path to the extension shared library. @@ -130,7 +130,7 @@ so you are able to restore if you run into any issues. ```text $ cargo php install --help -cargo-php-install +cargo-php-install Installs the extension in the current PHP installation. @@ -164,6 +164,9 @@ OPTIONS: --release Whether to install the release version of the extension + + --yes + Bypasses the confirmation prompt ``` ## Extension Removal @@ -175,7 +178,7 @@ from your `php.ini` if present. ```text $ cargo php remove --help -cargo-php-remove +cargo-php-remove Removes the extension in the current PHP installation. @@ -203,6 +206,9 @@ OPTIONS: --manifest Path to the Cargo manifest of the extension. Defaults to the manifest in the directory the command is called + + --yes + Bypasses the confirmation prompt ``` [`cargo-php`]: https://crates.io/crates/cargo-php From 669e024feb5693031c5c2ab15c72a53f94514d45 Mon Sep 17 00:00:00 2001 From: David Cole Date: Wed, 12 Oct 2022 11:18:22 +1300 Subject: [PATCH 08/34] Bump version to v0.8.1 --- Cargo.toml | 4 ++-- crates/cli/Cargo.toml | 2 +- crates/macros/Cargo.toml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 54462d6..fc164e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/davidcole1340/ext-php-rs" homepage = "https://github.com/davidcole1340/ext-php-rs" license = "MIT OR Apache-2.0" keywords = ["php", "ffi", "zend"] -version = "0.8.0" +version = "0.8.1" authors = ["David Cole "] edition = "2018" categories = ["api-bindings"] @@ -17,7 +17,7 @@ parking_lot = "0.12.1" cfg-if = "1.0" once_cell = "1.8.0" anyhow = { version = "1", optional = true } -ext-php-rs-derive = { version = "=0.8.0", path = "./crates/macros" } +ext-php-rs-derive = { version = "=0.8.1", path = "./crates/macros" } [dev-dependencies] skeptic = "0.13" diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index ccfd546..6a9085b 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/davidcole1340/ext-php-rs" homepage = "https://github.com/davidcole1340/ext-php-rs" license = "MIT OR Apache-2.0" keywords = ["php", "ffi", "zend"] -version = "0.1.5" +version = "0.1.6" authors = ["David Cole "] edition = "2018" categories = ["api-bindings", "command-line-interface"] diff --git a/crates/macros/Cargo.toml b/crates/macros/Cargo.toml index 780b1f5..9ab5118 100644 --- a/crates/macros/Cargo.toml +++ b/crates/macros/Cargo.toml @@ -4,7 +4,7 @@ description = "Derive macros for ext-php-rs." repository = "https://github.com/davidcole1340/ext-php-rs" homepage = "https://github.com/davidcole1340/ext-php-rs" license = "MIT OR Apache-2.0" -version = "0.8.0" +version = "0.8.1" authors = ["David Cole "] edition = "2018" From 6766786db52e4725701eec425308eee8bc8edae6 Mon Sep 17 00:00:00 2001 From: Dirk Stolle Date: Wed, 12 Oct 2022 23:37:17 +0200 Subject: [PATCH 09/34] Update changelog for latest versions (#161) [ci skip] --- CHANGELOG.md | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 457613a..28f746a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,67 @@ # Changelog +## Version 0.8.1 + +- 404 /guide doesn't exists. by @denzyldick in [#149] +- Fixed some typos by @denzyldick in [#148] +- Fix a few typos by @striezel in [#150] +- fix causes of some clippy warnings by @striezel in [#152] +- fix more causes of clippy warnings by @striezel in [#157] +- attempt to fix errors related to clap by @striezel in [#158] +- ci: run clippy only on stable Rust channel by @striezel in [#159] +- update actions/checkout in GitHub Actions workflows to v3 by @striezel in + [#151] +- Add ability to set function name on php_function macro by @joehoyle in [#153] +- Specify classes as fully-qualified names in stubs by @joehoyle in [#156] +- Support marking classes as interfaces by @joehoyle in [#155] +- Support marking methods as abstract by @joehoyle in [#154] +- Add php-scrypt as a example project by @PineappleIOnic in [#146] +- Fix ini file duplication and truncation when using cargo-php command by + @roborourke in [#136] +- Allow passing --yes parameter to bypass prompts by @roborourke in [#135] + +[#135]: https://github.com/davidcole1340/ext-php-rs/pull/135 +[#136]: https://github.com/davidcole1340/ext-php-rs/pull/136 +[#146]: https://github.com/davidcole1340/ext-php-rs/pull/146 +[#148]: https://github.com/davidcole1340/ext-php-rs/pull/148 +[#149]: https://github.com/davidcole1340/ext-php-rs/pull/149 +[#150]: https://github.com/davidcole1340/ext-php-rs/pull/150 +[#151]: https://github.com/davidcole1340/ext-php-rs/pull/151 +[#152]: https://github.com/davidcole1340/ext-php-rs/pull/152 +[#153]: https://github.com/davidcole1340/ext-php-rs/pull/153 +[#154]: https://github.com/davidcole1340/ext-php-rs/pull/154 +[#155]: https://github.com/davidcole1340/ext-php-rs/pull/155 +[#156]: https://github.com/davidcole1340/ext-php-rs/pull/156 +[#157]: https://github.com/davidcole1340/ext-php-rs/pull/157 +[#158]: https://github.com/davidcole1340/ext-php-rs/pull/158 +[#159]: https://github.com/davidcole1340/ext-php-rs/pull/159 + +## Version 0.8.0 + +- Windows support by @davidcole1340 in [#128] +- Support for binary slice to avoid extra allocation by @TobiasBengtsson in + [#139] +- Bump dependencies by @ptondereau in [#144] + +[#128]: https://github.com/davidcole1340/ext-php-rs/pull/128 +[#139]: https://github.com/davidcole1340/ext-php-rs/pull/139 +[#144]: https://github.com/davidcole1340/ext-php-rs/pull/144 + +## Version 0.7.4 + +- Fix is_true() / is_false() in Zval by @joehoyle in [#116] +- readme: fix link to guide by @TorstenDittmann in [#120] +- Fix request_(startup|shutdown)_function in ModuleBuilder by @glyphpoch in + [#119] +- Fix CI on macOS by @davidcole1340 in [#126] +- Add ability to pass modifier function for classes by @davidcole1340 in [#127] + +[#116]: https://github.com/davidcole1340/ext-php-rs/pull/116 +[#119]: https://github.com/davidcole1340/ext-php-rs/pull/119 +[#120]: https://github.com/davidcole1340/ext-php-rs/pull/120 +[#126]: https://github.com/davidcole1340/ext-php-rs/pull/126 +[#127]: https://github.com/davidcole1340/ext-php-rs/pull/127 + ## Version 0.7.3 - Upgrade `clap` to `3.0.0-rc3`. [#113] From 6a598ded3da2e262289ba669dba348e78c1fe1cd Mon Sep 17 00:00:00 2001 From: David Cole Date: Sun, 16 Oct 2022 12:49:02 +1300 Subject: [PATCH 10/34] fix building docs on docs.rs (#165) * fix building docs on docs.rs accidentally removed the docs.rs stub bindings feature in 664981f4fb1786493629b829c5c973b63983b512. docs.rs only has php 7.4 and therefore cannot build ext-php-rs, so stub bindings are generated prior. * update docs.rs stub bindings --- build.rs | 14 ++++++++++++-- docsrs_bindings.rs | 31 ++----------------------------- 2 files changed, 14 insertions(+), 31 deletions(-) diff --git a/build.rs b/build.rs index 518b494..7617096 100644 --- a/build.rs +++ b/build.rs @@ -206,6 +206,8 @@ fn check_php_version(info: &PHPInfo) -> Result<()> { } fn main() -> Result<()> { + let out_dir = env::var_os("OUT_DIR").context("Failed to get OUT_DIR")?; + let out_path = PathBuf::from(out_dir).join("bindings.rs"); let manifest: PathBuf = std::env::var("CARGO_MANIFEST_DIR").unwrap().into(); for path in [ manifest.join("src").join("wrapper.h"), @@ -217,6 +219,16 @@ fn main() -> Result<()> { println!("cargo:rerun-if-changed={}", path.to_string_lossy()); } + // docs.rs runners only have PHP 7.4 - use pre-generated bindings + if env::var("DOCS_RS").is_ok() { + println!("cargo:warning=docs.rs detected - using stub bindings"); + println!("cargo:rustc-cfg=php_debug"); + println!("cargo:rustc-cfg=php81"); + std::fs::copy("docsrs_bindings.rs", out_path) + .expect("failed to copy docs.rs stub bindings to out directory"); + return Ok(()); + } + let php = find_php()?; let info = PHPInfo::get(&php)?; let provider = Provider::new(&info)?; @@ -228,8 +240,6 @@ fn main() -> Result<()> { build_wrapper(&defines, &includes)?; let bindings = generate_bindings(&defines, &includes)?; - let out_dir = env::var_os("OUT_DIR").context("Failed to get OUT_DIR")?; - let out_path = PathBuf::from(out_dir).join("bindings.rs"); let out_file = File::create(&out_path).context("Failed to open output bindings file for writing")?; let mut out_writer = BufWriter::new(out_file); diff --git a/docsrs_bindings.rs b/docsrs_bindings.rs index a6d7795..03eefee 100644 --- a/docsrs_bindings.rs +++ b/docsrs_bindings.rs @@ -1,7 +1,6 @@ -/* automatically generated by rust-bindgen 0.59.1 */ +/* automatically generated by rust-bindgen 0.60.1 */ pub const ZEND_DEBUG: u32 = 1; -pub const ZEND_MM_ALIGNMENT: u32 = 8; pub const _ZEND_TYPE_NAME_BIT: u32 = 16777216; pub const _ZEND_TYPE_NULLABLE_BIT: u32 = 2; pub const HT_MIN_SIZE: u32 = 8; @@ -32,7 +31,6 @@ pub const IS_OBJECT_EX: u32 = 776; pub const IS_RESOURCE_EX: u32 = 265; pub const IS_REFERENCE_EX: u32 = 266; pub const IS_CONSTANT_AST_EX: u32 = 267; -pub const ZEND_MM_ALIGNMENT_MASK: i32 = -8; pub const ZEND_PROPERTY_ISSET: u32 = 0; pub const ZEND_PROPERTY_EXISTS: u32 = 2; pub const ZEND_ACC_PUBLIC: u32 = 1; @@ -1185,7 +1183,7 @@ pub struct _zend_vm_stack { pub prev: zend_vm_stack, } #[repr(C)] -#[derive(Debug, Copy, Clone)] +#[derive(Copy, Clone)] pub struct _zend_function_entry { pub fname: *const ::std::os::raw::c_char, pub handler: zif_handler, @@ -1407,28 +1405,3 @@ extern "C" { extern "C" { pub fn zend_do_implement_interface(ce: *mut zend_class_entry, iface: *mut zend_class_entry); } -extern "C" { - pub fn ext_php_rs_zend_string_init( - str_: *const ::std::os::raw::c_char, - len: size_t, - persistent: bool, - ) -> *mut zend_string; -} -extern "C" { - pub fn ext_php_rs_zend_string_release(zs: *mut zend_string); -} -extern "C" { - pub fn ext_php_rs_php_build_id() -> *const ::std::os::raw::c_char; -} -extern "C" { - pub fn ext_php_rs_zend_object_alloc( - obj_size: size_t, - ce: *mut zend_class_entry, - ) -> *mut ::std::os::raw::c_void; -} -extern "C" { - pub fn ext_php_rs_zend_object_release(obj: *mut zend_object); -} -extern "C" { - pub fn ext_php_rs_executor_globals() -> *mut zend_executor_globals; -} From 24d703d955fe3d069cb597552587afc9c2936b2e Mon Sep 17 00:00:00 2001 From: Niklas Mollenhauer Date: Sun, 16 Oct 2022 02:13:09 +0200 Subject: [PATCH 11/34] Add some standard zend interfaces (#164) * Add some standard zend interfaces The zend API also has some standard interfaces it exposes: https://github.com/php/php-src/blob/c8c09b4aaee7ac447828564c6267e62eb304135b/Zend/zend_interfaces.h#L27-L33 ``` extern ZEND_API zend_class_entry *zend_ce_traversable; extern ZEND_API zend_class_entry *zend_ce_aggregate; extern ZEND_API zend_class_entry *zend_ce_iterator; extern ZEND_API zend_class_entry *zend_ce_arrayaccess; extern ZEND_API zend_class_entry *zend_ce_serializable; extern ZEND_API zend_class_entry *zend_ce_countable; extern ZEND_API zend_class_entry *zend_ce_stringable; ``` This surfaced in #163 and should make it possible to implement these interfaces. * Add some links to the php documentation * update docs.rs bindings Co-authored-by: David Cole --- allowed_bindings.rs | 7 +++++ docsrs_bindings.rs | 21 ++++++++++++++ src/zend/ce.rs | 69 ++++++++++++++++++++++++++++++++++----------- 3 files changed, 81 insertions(+), 16 deletions(-) diff --git a/allowed_bindings.rs b/allowed_bindings.rs index f417e95..fa9882f 100644 --- a/allowed_bindings.rs +++ b/allowed_bindings.rs @@ -49,6 +49,13 @@ bind! { zend_ce_type_error, zend_ce_unhandled_match_error, zend_ce_value_error, + zend_ce_traversable, + zend_ce_aggregate, + zend_ce_iterator, + zend_ce_arrayaccess, + zend_ce_serializable, + zend_ce_countable, + zend_ce_stringable, zend_class_entry, zend_declare_class_constant, zend_declare_property, diff --git a/docsrs_bindings.rs b/docsrs_bindings.rs index 03eefee..0971cc5 100644 --- a/docsrs_bindings.rs +++ b/docsrs_bindings.rs @@ -1405,3 +1405,24 @@ extern "C" { extern "C" { pub fn zend_do_implement_interface(ce: *mut zend_class_entry, iface: *mut zend_class_entry); } +extern "C" { + pub static mut zend_ce_traversable: *mut zend_class_entry; +} +extern "C" { + pub static mut zend_ce_aggregate: *mut zend_class_entry; +} +extern "C" { + pub static mut zend_ce_iterator: *mut zend_class_entry; +} +extern "C" { + pub static mut zend_ce_arrayaccess: *mut zend_class_entry; +} +extern "C" { + pub static mut zend_ce_serializable: *mut zend_class_entry; +} +extern "C" { + pub static mut zend_ce_countable: *mut zend_class_entry; +} +extern "C" { + pub static mut zend_ce_stringable: *mut zend_class_entry; +} diff --git a/src/zend/ce.rs b/src/zend/ce.rs index b2e9d43..57b1877 100644 --- a/src/zend/ce.rs +++ b/src/zend/ce.rs @@ -3,70 +3,107 @@ #![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, + zend_ce_aggregate, zend_ce_argument_count_error, zend_ce_arithmetic_error, zend_ce_arrayaccess, + zend_ce_compile_error, zend_ce_countable, zend_ce_division_by_zero_error, + zend_ce_error_exception, zend_ce_exception, zend_ce_iterator, zend_ce_parse_error, + zend_ce_serializable, zend_ce_stringable, zend_ce_throwable, zend_ce_traversable, + 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. +/// Returns the base [`stdClass`](https://www.php.net/manual/en/class.stdclass.php) class. pub fn stdclass() -> &'static ClassEntry { unsafe { zend_standard_class_def.as_ref() }.unwrap() } -/// Returns the base `Throwable` class. +/// Returns the base [`Throwable`](https://www.php.net/manual/en/class.throwable.php) class. pub fn throwable() -> &'static ClassEntry { unsafe { zend_ce_throwable.as_ref() }.unwrap() } -/// Returns the base `Exception` class. +/// Returns the base [`Exception`](https://www.php.net/manual/en/class.exception.php) class. pub fn exception() -> &'static ClassEntry { unsafe { zend_ce_exception.as_ref() }.unwrap() } -/// Returns the base `ErrorException` class. +/// Returns the base [`ErrorException`](https://www.php.net/manual/en/class.errorexception.php) class. pub fn error_exception() -> &'static ClassEntry { unsafe { zend_ce_error_exception.as_ref() }.unwrap() } -/// Returns the base `CompileError` class. +/// Returns the base [`CompileError`](https://www.php.net/manual/en/class.compileerror.php) class. pub fn compile_error() -> &'static ClassEntry { unsafe { zend_ce_compile_error.as_ref() }.unwrap() } -/// Returns the base `ParseError` class. +/// Returns the base [`ParseError`](https://www.php.net/manual/en/class.parseerror.php) class. pub fn parse_error() -> &'static ClassEntry { unsafe { zend_ce_parse_error.as_ref() }.unwrap() } -/// Returns the base `TypeError` class. +/// Returns the base [`TypeError`](https://www.php.net/manual/en/class.typeerror.php) class. pub fn type_error() -> &'static ClassEntry { unsafe { zend_ce_type_error.as_ref() }.unwrap() } -/// Returns the base `ArgumentCountError` class. +/// Returns the base [`ArgumentCountError`](https://www.php.net/manual/en/class.argumentcounterror.php) class. pub fn argument_count_error() -> &'static ClassEntry { unsafe { zend_ce_argument_count_error.as_ref() }.unwrap() } -/// Returns the base `ValueError` class. +/// Returns the base [`ValueError`](https://www.php.net/manual/en/class.valueerror.php) class. pub fn value_error() -> &'static ClassEntry { unsafe { zend_ce_value_error.as_ref() }.unwrap() } -/// Returns the base `ArithmeticError` class. +/// Returns the base [`ArithmeticError`](https://www.php.net/manual/en/class.arithmeticerror.php) class. pub fn arithmetic_error() -> &'static ClassEntry { unsafe { zend_ce_arithmetic_error.as_ref() }.unwrap() } -/// Returns the base `DivisionByZeroError` class. +/// Returns the base [`DivisionByZeroError`](https://www.php.net/manual/en/class.divisionbyzeroerror.php) class. pub fn division_by_zero_error() -> &'static ClassEntry { unsafe { zend_ce_division_by_zero_error.as_ref() }.unwrap() } -/// Returns the base `UnhandledMatchError` class. +/// Returns the base [`UnhandledMatchError`](https://www.php.net/manual/en/class.unhandledmatcherror.php) class. pub fn unhandled_match_error() -> &'static ClassEntry { unsafe { zend_ce_unhandled_match_error.as_ref() }.unwrap() } + +/// Returns the [`Traversable`](https://www.php.net/manual/en/class.traversable.php) interface. +pub fn traversable() -> &'static ClassEntry { + unsafe { zend_ce_traversable.as_ref() }.unwrap() +} + +/// Returns the [`IteratorAggregate`](https://www.php.net/manual/en/class.iteratoraggregate.php) interface. +pub fn aggregate() -> &'static ClassEntry { + unsafe { zend_ce_aggregate.as_ref() }.unwrap() +} + +/// Returns the [`Iterator`](https://www.php.net/manual/en/class.iterator.php) interface. +pub fn iterator() -> &'static ClassEntry { + unsafe { zend_ce_iterator.as_ref() }.unwrap() +} + +/// Returns the [`ArrayAccess`](https://www.php.net/manual/en/class.arrayaccess.php) interface. +pub fn arrayaccess() -> &'static ClassEntry { + unsafe { zend_ce_arrayaccess.as_ref() }.unwrap() +} + +/// Returns the [`Serializable`](https://www.php.net/manual/en/class.serializable.php) interface. +pub fn serializable() -> &'static ClassEntry { + unsafe { zend_ce_serializable.as_ref() }.unwrap() +} + +/// Returns the [`Countable`](https://www.php.net/manual/en/class.countable.php) interface. +pub fn countable() -> &'static ClassEntry { + unsafe { zend_ce_countable.as_ref() }.unwrap() +} + +/// Returns the [`Stringable`](https://www.php.net/manual/en/class.stringable.php) interface. +pub fn stringable() -> &'static ClassEntry { + unsafe { zend_ce_stringable.as_ref() }.unwrap() +} From 296c3add84578338baf3ea97f30080c1451d3dfa Mon Sep 17 00:00:00 2001 From: David Cole Date: Sun, 16 Oct 2022 13:13:17 +1300 Subject: [PATCH 12/34] add note to update docs.rs bindings [ci skip] --- allowed_bindings.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/allowed_bindings.rs b/allowed_bindings.rs index fa9882f..827c750 100644 --- a/allowed_bindings.rs +++ b/allowed_bindings.rs @@ -4,6 +4,20 @@ // exist in the bindings file. Which ever script include!s the bindings must // define the `bind` macro. This allows us to have the list in string format // inside the build script and in macro format inside the CLI crate. +// +// NOTE TO EDITORS: +// When updating this file, you must re-generate the `docsrs_bindings.rs` +// file used by docs.rs to build documentation. To perform this: +// +// $ cargo clean +// $ cargo build +// $ cp target/debug/build/ext-php-rs-e2cb315d27898d01/out/bindings.rs +// docsrs_bindings.rs +// $ git add . && git commit -m "update docs.rs bindings" +// +// The hash after `ext-php-rs-` in the bindings path may change. There should +// be two folders beginning with `ext-php-rs-` in `target/debug/build`, so +// check both for the presense of the bindings file. bind! { HashTable, From ad048d0e058f0dd3dec3f01d654fd1be21796d81 Mon Sep 17 00:00:00 2001 From: Denzyl Dick <2477646+denzyldick@users.noreply.github.com> Date: Sat, 22 Oct 2022 10:33:48 +0200 Subject: [PATCH 13/34] Update lib.rs (#168) Correct field name. [ci skip] --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index f056133..41216a3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -244,7 +244,7 @@ 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 +/// attribute options. This function takes one required parameter (`name`) and /// two optional parameters (`description` and `age`). /// /// ``` From 1f0582b10d7fa93090d0d231b08feba2d3b92406 Mon Sep 17 00:00:00 2001 From: David Cole Date: Sun, 23 Oct 2022 12:02:00 +1300 Subject: [PATCH 14/34] fix describe when using `#[implements]` (#169) [ci skip] --- crates/macros/src/module.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/macros/src/module.rs b/crates/macros/src/module.rs index 44fe17a..01dd0fe 100644 --- a/crates/macros/src/module.rs +++ b/crates/macros/src/module.rs @@ -230,7 +230,7 @@ impl Describe for Class { let interfaces = self .interfaces .iter() - .map(|iface| quote! { #iface.into(), }); + .map(|iface| quote! { #iface.into() }); let properties = self.properties.iter().map(|d| d.describe()); let mut methods: Vec<_> = self.methods.iter().map(Describe::describe).collect(); let docs = self.docs.iter().map(|c| { From 76358ede3c4e75bf9a5495546d7e66381f83897c Mon Sep 17 00:00:00 2001 From: David Cole Date: Sun, 23 Oct 2022 12:06:22 +1300 Subject: [PATCH 15/34] rustfmt... --- crates/macros/src/module.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/crates/macros/src/module.rs b/crates/macros/src/module.rs index 01dd0fe..9eb2df3 100644 --- a/crates/macros/src/module.rs +++ b/crates/macros/src/module.rs @@ -227,10 +227,7 @@ impl Describe for Class { } else { quote! { None } }; - let interfaces = self - .interfaces - .iter() - .map(|iface| quote! { #iface.into() }); + let interfaces = self.interfaces.iter().map(|iface| quote! { #iface.into() }); let properties = self.properties.iter().map(|d| d.describe()); let mut methods: Vec<_> = self.methods.iter().map(Describe::describe).collect(); let docs = self.docs.iter().map(|c| { From 997fded71523250f34a512bcaf7067384cf48e41 Mon Sep 17 00:00:00 2001 From: Niklas Mollenhauer Date: Sun, 23 Oct 2022 02:09:56 +0200 Subject: [PATCH 16/34] Add example that shows how to implement an interface (#167) * Add example that shows how to implement an interface * Add missing uses * Fix some compilation issues --- guide/src/macros/classes.md | 53 +++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/guide/src/macros/classes.md b/guide/src/macros/classes.md index 9e6a2b0..3a423d1 100644 --- a/guide/src/macros/classes.md +++ b/guide/src/macros/classes.md @@ -79,3 +79,56 @@ pub fn throw_exception() -> PhpResult { # } # fn main() {} ``` + +## Implementing an Interface + +To implement an interface, use `#[implements(ce)]` where `ce` is an expression returning a `ClassEntry`. +The following example implements [`ArrayAccess`](https://www.php.net/manual/en/class.arrayaccess.php): +```rust,no_run +# #![cfg_attr(windows, feature(abi_vectorcall))] +# extern crate ext_php_rs; +use ext_php_rs::prelude::*; +use ext_php_rs::{exception::PhpResult, types::Zval, zend::ce}; + +#[php_class] +#[implements(ce::arrayaccess())] +#[derive(Default)] +pub struct EvenNumbersArray; + +/// Returns `true` if the array offset is an even number. +/// Usage: +/// ```php +/// $arr = new EvenNumbersArray(); +/// var_dump($arr[0]); // true +/// var_dump($arr[1]); // false +/// var_dump($arr[2]); // true +/// var_dump($arr[3]); // false +/// var_dump($arr[4]); // true +/// var_dump($arr[5] = true); // Fatal error: Uncaught Exception: Setting values is not supported +/// ``` +#[php_impl] +impl EvenNumbersArray { + pub fn __construct() -> EvenNumbersArray { + EvenNumbersArray {} + } + // We need to use `Zval` because ArrayAccess needs $offset to be a `mixed` + pub fn offset_exists(&self, offset: &'_ Zval) -> bool { + offset.is_long() + } + pub fn offset_get(&self, offset: &'_ Zval) -> PhpResult { + let integer_offset = offset.long().ok_or("Expected integer offset")?; + Ok(integer_offset % 2 == 0) + } + pub fn offset_set(&mut self, _offset: &'_ Zval, _value: &'_ Zval) -> PhpResult { + Err("Setting values is not supported".into()) + } + pub fn offset_unset(&mut self, _offset: &'_ Zval) -> PhpResult { + Err("Setting values is not supported".into()) + } +} +# #[php_module] +# pub fn get_module(module: ModuleBuilder) -> ModuleBuilder { +# module +# } +# fn main() {} +``` From 5f33598fcfe071b617efdcc7ff7d5d0aa183cc1a Mon Sep 17 00:00:00 2001 From: David Cole Date: Thu, 10 Nov 2022 13:51:05 +1300 Subject: [PATCH 17/34] add `before` flag to `#[php_startup]` (#170) * add `before` flag to `#[php_startup]` this calls the user-provided startup function _before_ the classes and constants registered by the macro system are registered with PHP. by default the behaviour is to run this function after, which means you cannot define an interface and use it on a struct. * cargo fmt --- crates/macros/src/lib.rs | 5 +++-- crates/macros/src/module.rs | 2 +- crates/macros/src/startup_function.rs | 28 ++++++++++++++++++++++----- src/lib.rs | 5 +++++ 4 files changed, 32 insertions(+), 8 deletions(-) diff --git a/crates/macros/src/lib.rs b/crates/macros/src/lib.rs index e744703..2e6deaf 100644 --- a/crates/macros/src/lib.rs +++ b/crates/macros/src/lib.rs @@ -87,10 +87,11 @@ pub fn php_module(_: TokenStream, input: TokenStream) -> TokenStream { } #[proc_macro_attribute] -pub fn php_startup(_: TokenStream, input: TokenStream) -> TokenStream { +pub fn php_startup(args: TokenStream, input: TokenStream) -> TokenStream { + let args = parse_macro_input!(args as AttributeArgs); let input = parse_macro_input!(input as ItemFn); - match startup_function::parser(input) { + match startup_function::parser(Some(args), input) { Ok(parsed) => parsed, Err(e) => syn::Error::new(Span::call_site(), e).to_compile_error(), } diff --git a/crates/macros/src/module.rs b/crates/macros/src/module.rs index 9eb2df3..2c118c0 100644 --- a/crates/macros/src/module.rs +++ b/crates/macros/src/module.rs @@ -34,7 +34,7 @@ pub fn parser(input: ItemFn) -> Result { fn php_module_startup() {} }) .map_err(|_| anyhow!("Unable to generate PHP module startup function."))?; - let startup = startup_function::parser(parsed)?; + let startup = startup_function::parser(None, parsed)?; state = STATE.lock(); Some(startup) diff --git a/crates/macros/src/startup_function.rs b/crates/macros/src/startup_function.rs index 47fcfeb..76b9877 100644 --- a/crates/macros/src/startup_function.rs +++ b/crates/macros/src/startup_function.rs @@ -1,13 +1,27 @@ use std::collections::HashMap; use anyhow::{anyhow, Result}; +use darling::FromMeta; use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; -use syn::{Expr, ItemFn, Signature}; +use syn::{AttributeArgs, Expr, ItemFn, Signature}; use crate::{class::Class, constant::Constant, STATE}; -pub fn parser(input: ItemFn) -> Result { +#[derive(Default, Debug, FromMeta)] +#[darling(default)] +struct StartupArgs { + before: bool, +} + +pub fn parser(args: Option, input: ItemFn) -> Result { + let args = if let Some(args) = args { + StartupArgs::from_list(&args) + .map_err(|e| anyhow!("Unable to parse attribute arguments: {:?}", e))? + } else { + StartupArgs::default() + }; + let ItemFn { sig, block, .. } = input; let Signature { ident, .. } = sig; let stmts = &block.stmts; @@ -17,6 +31,11 @@ pub fn parser(input: ItemFn) -> Result { let classes = build_classes(&state.classes)?; let constants = build_constants(&state.constants); + let (before, after) = if args.before { + (Some(quote! { internal(); }), None) + } else { + (None, Some(quote! { internal(); })) + }; let func = quote! { #[doc(hidden)] @@ -30,11 +49,10 @@ pub fn parser(input: ItemFn) -> Result { ::ext_php_rs::internal::ext_php_rs_startup(); + #before #(#classes)* #(#constants)* - - // TODO return result? - internal(); + #after 0 } diff --git a/src/lib.rs b/src/lib.rs index 41216a3..7d9f5b2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -518,6 +518,11 @@ pub use ext_php_rs_derive::php_class; /// this macro if you have registered any classes or constants when using the /// [`macro@php_module`] macro. /// +/// The attribute accepts one optional flag -- `#[php_startup(before)]` -- +/// which forces the annotated function to be called _before_ the other classes +/// and constants are registered. By default the annotated function is called +/// after these classes and constants are registered. +/// /// # Example /// /// ``` From 4133a0fe78d0046d705e3e3e1c5fc364e76e950b Mon Sep 17 00:00:00 2001 From: David Cole Date: Thu, 10 Nov 2022 13:51:20 +1300 Subject: [PATCH 18/34] add ability to define abstract methods (#171) * add ability to define abstract methods `new_abstract` can be used for interface and abstract class methods. * rustfmt --- src/builders/class.rs | 2 +- src/builders/function.rs | 26 +++++++++++++++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/builders/class.rs b/src/builders/class.rs index bc03847..bfc7453 100644 --- a/src/builders/class.rs +++ b/src/builders/class.rs @@ -85,7 +85,7 @@ impl ClassBuilder { /// * `func` - The function entry to add to the class. /// * `flags` - Flags relating to the function. See [`MethodFlags`]. pub fn method(mut self, mut func: FunctionEntry, flags: MethodFlags) -> Self { - func.flags = flags.bits(); + func.flags |= flags.bits(); self.methods.push(func); self } diff --git a/src/builders/function.rs b/src/builders/function.rs index 363aad2..e75a90d 100644 --- a/src/builders/function.rs +++ b/src/builders/function.rs @@ -1,7 +1,7 @@ use crate::{ args::{Arg, ArgInfo}, error::{Error, Result}, - flags::DataType, + flags::{DataType, MethodFlags}, types::Zval, zend::{ExecuteData, FunctionEntry, ZendType}, }; @@ -64,6 +64,30 @@ impl<'a> FunctionBuilder<'a> { } } + /// Create a new function builder for an abstract function that can be used + /// on an abstract class or an interface. + /// + /// # Parameters + /// + /// * `name` - The name of the function. + pub fn new_abstract>(name: T) -> Self { + Self { + name: name.into(), + function: FunctionEntry { + fname: ptr::null(), + handler: None, + arg_info: ptr::null(), + num_args: 0, + flags: MethodFlags::Abstract.bits(), + }, + args: vec![], + n_req: None, + retval: None, + ret_as_ref: false, + ret_as_null: false, + } + } + /// Creates a constructor builder, used to build the constructor /// for classes. /// From b72d0555d6131ef2ecab9f9cd630357298b86429 Mon Sep 17 00:00:00 2001 From: Pierre Tondereau Date: Thu, 10 Nov 2022 10:59:49 +0100 Subject: [PATCH 19/34] chore(cli): Bump Clap for CLI tool (#177) --- crates/cli/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 6a9085b..611dfb5 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -13,9 +13,9 @@ categories = ["api-bindings", "command-line-interface"] [dependencies] ext-php-rs = { version = ">=0.7.1", path = "../../" } -clap = { version = ">=3.2.5", features = ["derive"] } +clap = { version = "4.0", features = ["derive"] } anyhow = "1" dialoguer = "0.10" libloading = "0.7" -cargo_metadata = "0.14" +cargo_metadata = "0.15" semver = "1.0" From eafd3b44710f92191152efd08f2cf60f67753d68 Mon Sep 17 00:00:00 2001 From: David Cole Date: Fri, 11 Nov 2022 12:08:23 +1300 Subject: [PATCH 20/34] fix type links in docs.rs (#179) [ci skip] --- src/args.rs | 2 +- src/lib.rs | 28 ++++++++++++++-------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/args.rs b/src/args.rs index 4bb5be0..ef32334 100644 --- a/src/args.rs +++ b/src/args.rs @@ -118,7 +118,7 @@ impl<'a> Arg<'a> { /// return value of the function, or an error. /// /// You should not call this function directly, rather through the - /// [`call_user_func`] macro. + /// [`call_user_func`](crate::call_user_func) macro. /// /// # Parameters /// diff --git a/src/lib.rs b/src/lib.rs index 7d9f5b2..720a23d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -139,8 +139,8 @@ pub use ext_php_rs_derive::php_const; /// ``` /// /// [`strpos`]: https://www.php.net/manual/en/function.strpos.php -/// [`IntoZval`]: ext_php_rs::php::types::zval::IntoZval -/// [`Zval`]: ext_php_rs::php::types::zval::Zval +/// [`IntoZval`]: crate::convert::IntoZval +/// [`Zval`]: crate::types::Zval pub use ext_php_rs_derive::php_extern; /// Attribute used to annotate a function as a PHP function. @@ -289,12 +289,12 @@ pub use ext_php_rs_derive::php_extern; /// /// [`Result`]: std::result::Result /// [`FunctionBuilder`]: crate::php::function::FunctionBuilder -/// [`FromZval`]: crate::php::types::zval::FromZval -/// [`IntoZval`]: crate::php::types::zval::IntoZval -/// [`Zval`]: crate::php::types::zval::Zval -/// [`Binary`]: crate::php::types::binary::Binary -/// [`ZendCallable`]: crate::php::types::callable::ZendCallable -/// [`PhpException`]: crate::php::exceptions::PhpException +/// [`FromZval`]: crate::convert::FromZval +/// [`IntoZval`]: crate::convert::IntoZval +/// [`Zval`]: crate::types::Zval. +/// [`Binary`]: crate::binary::Binary +/// [`ZendCallable`]: crate::types::ZendCallable +/// [`PhpException`]: crate::exception::PhpException pub use ext_php_rs_derive::php_function; /// Annotates a structs `impl` block, declaring that all methods and constants @@ -670,12 +670,12 @@ pub use ext_php_rs_derive::php_startup; /// var_dump(give_union()); // int(5) /// ``` /// -/// [`FromZval`]: crate::php::types::zval::FromZval -/// [`IntoZval`]: crate::php::types::zval::IntoZval -/// [`FromZendObject`]: crate::php::types::object::FromZendObject -/// [`IntoZendObject`]: crate::php::types::object::IntoZendObject -/// [`Zval`]: crate::php::types::zval::Zval -/// [`Zval::string`]: crate::php::types::zval::Zval::string +/// [`FromZval`]: crate::convert::FromZval +/// [`IntoZval`]: crate::convert::IntoZval +/// [`FromZendObject`]: crate::convert::FromZendObject +/// [`IntoZendObject`]: crate::convert::IntoZendObject +/// [`Zval`]: crate::types::Zval. +/// [`Zval::string`]: crate::types::Zval.::string pub use ext_php_rs_derive::ZvalConvert; /// Defines an `extern` function with the Zend fastcall convention based on From a160f2a2a0253999b4f42b1f1c93edb8c9c3b20f Mon Sep 17 00:00:00 2001 From: David Cole Date: Fri, 11 Nov 2022 12:11:33 +1300 Subject: [PATCH 21/34] Bump version to v0.8.2 --- CHANGELOG.md | 24 ++++++++++++++++++++++++ Cargo.toml | 4 ++-- crates/cli/Cargo.toml | 2 +- crates/macros/Cargo.toml | 2 +- 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 28f746a..047fad4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,29 @@ # Changelog +## Version 0.8.2 + +- Update changelog for latest versions by @striezel in [#161] +- fix building docs on docs.rs by @davidcole1340 in [#165] +- Add some standard zend interfaces by @nikeee in [#164] +- Correct parameter name. by @denzyldick in [#168] +- fix describe when using `#[implements]` by @davidcole1340 in [#169] +- Add example that shows how to implement an interface by @nikeee in [#167] +- add `before` flag to `#[php_startup]` by @davidcole1340 in [#170] +- add ability to define abstract methods by @davidcole1340 in [#171] +- chore(cli): Bump Clap for CLI tool by @ptondereau in [#177] +- fix type links in docs.rs by @davidcole1340 in [#179] + +[#161]: https://github.com/davidcole1340/ext-php-rs/pull/161 +[#165]: https://github.com/davidcole1340/ext-php-rs/pull/165 +[#164]: https://github.com/davidcole1340/ext-php-rs/pull/164 +[#168]: https://github.com/davidcole1340/ext-php-rs/pull/168 +[#169]: https://github.com/davidcole1340/ext-php-rs/pull/169 +[#167]: https://github.com/davidcole1340/ext-php-rs/pull/167 +[#170]: https://github.com/davidcole1340/ext-php-rs/pull/170 +[#171]: https://github.com/davidcole1340/ext-php-rs/pull/171 +[#177]: https://github.com/davidcole1340/ext-php-rs/pull/177 +[#179]: https://github.com/davidcole1340/ext-php-rs/pull/179 + ## Version 0.8.1 - 404 /guide doesn't exists. by @denzyldick in [#149] diff --git a/Cargo.toml b/Cargo.toml index fc164e9..520a779 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/davidcole1340/ext-php-rs" homepage = "https://github.com/davidcole1340/ext-php-rs" license = "MIT OR Apache-2.0" keywords = ["php", "ffi", "zend"] -version = "0.8.1" +version = "0.8.2" authors = ["David Cole "] edition = "2018" categories = ["api-bindings"] @@ -17,7 +17,7 @@ parking_lot = "0.12.1" cfg-if = "1.0" once_cell = "1.8.0" anyhow = { version = "1", optional = true } -ext-php-rs-derive = { version = "=0.8.1", path = "./crates/macros" } +ext-php-rs-derive = { version = "=0.8.2", path = "./crates/macros" } [dev-dependencies] skeptic = "0.13" diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 611dfb5..e2f15b0 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/davidcole1340/ext-php-rs" homepage = "https://github.com/davidcole1340/ext-php-rs" license = "MIT OR Apache-2.0" keywords = ["php", "ffi", "zend"] -version = "0.1.6" +version = "0.1.7" authors = ["David Cole "] edition = "2018" categories = ["api-bindings", "command-line-interface"] diff --git a/crates/macros/Cargo.toml b/crates/macros/Cargo.toml index 9ab5118..8137816 100644 --- a/crates/macros/Cargo.toml +++ b/crates/macros/Cargo.toml @@ -4,7 +4,7 @@ description = "Derive macros for ext-php-rs." repository = "https://github.com/davidcole1340/ext-php-rs" homepage = "https://github.com/davidcole1340/ext-php-rs" license = "MIT OR Apache-2.0" -version = "0.8.1" +version = "0.8.2" authors = ["David Cole "] edition = "2018" From 3b1e2e3848b4deaf40cb09dd8eef3ed5a70f7126 Mon Sep 17 00:00:00 2001 From: David Cole Date: Sun, 13 Nov 2022 21:13:25 +1300 Subject: [PATCH 22/34] check docs warnings in CI (#180) * check docs warnings in CI * works * format --- .github/workflows/build.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 315d2f8..8a98f1d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -68,18 +68,25 @@ jobs: with: command: fmt args: --all -- --check + if: matrix.rust == 'stable' && matrix.os == 'ubuntu-latest' && matrix.php == '8.1' - name: Run clippy uses: actions-rs/cargo@v1 with: command: clippy args: --all -- -D warnings - if: matrix.rust == 'stable' + if: matrix.rust == 'stable' && matrix.os == 'ubuntu-latest' && matrix.php == '8.1' + - name: Run rustdoc + uses: actions-rs/cargo@v1 + with: + command: rustdoc + args: -- -D warnings + if: matrix.rust == 'stable' && matrix.os == 'ubuntu-latest' && matrix.php == '8.1' - name: Build with docs stub - if: "contains(matrix.os, 'ubuntu') && matrix.php == '8.1'" env: DOCS_RS: run: cargo clean && cargo build + if: matrix.rust == 'stable' && matrix.os == 'ubuntu-latest' && matrix.php == '8.1' build-zts: name: Build with ZTS runs-on: ubuntu-latest From 4ea01f8d98524ca0c27ab76f7fc47cdc8484e51f Mon Sep 17 00:00:00 2001 From: ju1ius Date: Wed, 16 Nov 2022 07:25:42 +0100 Subject: [PATCH 23/34] fixes inifinte loop in ClassEntry::instance_of (#188) --- src/zend/class.rs | 34 ++++++++-------------------------- 1 file changed, 8 insertions(+), 26 deletions(-) diff --git a/src/zend/class.rs b/src/zend/class.rs index 7e61c87..952302a 100644 --- a/src/zend/class.rs +++ b/src/zend/class.rs @@ -37,37 +37,19 @@ impl ClassEntry { /// /// # Parameters /// - /// * `ce` - The inherited class entry to check. - pub fn instance_of(&self, ce: &ClassEntry) -> bool { - if self == ce { + /// * `other` - The inherited class entry to check. + pub fn instance_of(&self, other: &ClassEntry) -> bool { + if self == other { 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; - } - } + if other.is_interface() { + return self + .interfaces() + .map_or(false, |mut it| it.any(|ce| ce == other)); } - false + std::iter::successors(self.parent(), |p| p.parent()).any(|ce| ce == other) } /// Returns an iterator of all the interfaces that the class implements. From 8d2ad7d4186d3bad8e62611458361aeed3779faa Mon Sep 17 00:00:00 2001 From: David Cole Date: Sat, 19 Nov 2022 20:58:18 +1300 Subject: [PATCH 24/34] fix binary slice lifetimes (#181) --- src/binary_slice.rs | 30 +++++++----------------------- src/types/zval.rs | 31 ++++++++++++++++++------------- 2 files changed, 25 insertions(+), 36 deletions(-) diff --git a/src/binary_slice.rs b/src/binary_slice.rs index 058d3a7..223f480 100644 --- a/src/binary_slice.rs +++ b/src/binary_slice.rs @@ -6,16 +6,11 @@ use crate::ffi::zend_string; -use std::{convert::TryFrom, ops::Deref, slice::from_raw_parts}; +use std::{ops::Deref, slice::from_raw_parts}; -use crate::{ - convert::FromZval, - error::{Error, Result}, - flags::DataType, - types::Zval, -}; +use crate::{convert::FromZval, flags::DataType, types::Zval}; -/// Acts as a wrapper around [`&[T]`] where `T` implements [`PackSlice`]. +/// Acts as a wrapper around `&[T]` where `T` implements [`PackSlice`]. /// Primarily used for passing read-only binary data into Rust functions. #[derive(Debug)] pub struct BinarySlice<'a, T>(&'a [T]) @@ -47,28 +42,17 @@ where } } -impl FromZval<'_> for BinarySlice<'_, T> +impl<'a, T> FromZval<'a> for BinarySlice<'a, T> where T: PackSlice, { const TYPE: DataType = DataType::String; - fn from_zval(zval: &Zval) -> Option { + fn from_zval(zval: &'a Zval) -> Option { zval.binary_slice().map(BinarySlice) } } -impl TryFrom for BinarySlice<'_, T> -where - T: PackSlice, -{ - type Error = Error; - - fn try_from(value: Zval) -> Result { - Self::from_zval(&value).ok_or_else(|| Error::ZvalConversion(value.get_type())) - } -} - impl<'a, T> From> for &'a [T] where T: PackSlice, @@ -117,7 +101,7 @@ pub unsafe trait PackSlice: Clone { /// * `s` - The Zend string containing the binary data. /// /// [`pack`]: https://www.php.net/manual/en/function.pack.php - fn unpack_into<'a>(s: &zend_string) -> &'a [Self]; + fn unpack_into(s: &zend_string) -> &[Self]; } /// Implements the [`PackSlice`] trait for a given type. @@ -128,7 +112,7 @@ macro_rules! pack_slice_impl { ($t: ty, $d: expr) => { unsafe impl PackSlice for $t { - fn unpack_into<'a>(s: &zend_string) -> &'a [Self] { + fn unpack_into(s: &zend_string) -> &[Self] { let bytes = ($d / 8) as usize; let len = (s.len as usize) / bytes; let ptr = s.val.as_ptr() as *const $t; diff --git a/src/types/zval.rs b/src/types/zval.rs index f6c13c5..aa190d2 100644 --- a/src/types/zval.rs +++ b/src/types/zval.rs @@ -130,21 +130,26 @@ impl Zval { /// /// [`pack`]: https://www.php.net/manual/en/function.pack.php pub fn binary(&self) -> Option> { - if self.is_string() { - // SAFETY: Type is string therefore we are able to take a reference. - Some(T::unpack_into(unsafe { self.value.str_.as_ref() }?)) - } else { - None - } + self.zend_str().map(T::unpack_into) } - pub fn binary_slice<'a, T: PackSlice>(&self) -> Option<&'a [T]> { - if self.is_string() { - // SAFETY: Type is string therefore we are able to take a reference. - Some(T::unpack_into(unsafe { self.value.str_.as_ref() }?)) - } else { - None - } + /// Returns the value of the zval if it is a string and can be unpacked into + /// a slice 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. + /// + /// This function is similar to [`Zval::binary`] except that a slice is + /// returned instead of a vector, meaning the contents of the string is + /// not copied. + /// + /// # 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. + pub fn binary_slice(&self) -> Option<&[T]> { + self.zend_str().map(T::unpack_into) } /// Returns the value of the zval if it is a resource. From 9a105abb6362ae31e41c34edfc526f46048a03b1 Mon Sep 17 00:00:00 2001 From: ju1ius Date: Thu, 24 Nov 2022 09:05:36 +0100 Subject: [PATCH 25/34] fixes CI workflow configuration (#195) * upgrades LLVM to v14 * migrates from the unmaintained `action-rs/*` actions to [dtolnay/rust-toolchain](https://github.com/dtolnay/rust-toolchain), using [Swatinem/rust-cache](https://github.com/Swatinem/rust-cache/) as a cache layer. * adds a cache layer for LLVM build * adds a weekly cron schedule for all workflows * fixes an issue in the docblocks --- .github/workflows/build.yml | 70 ++++++++++++++++++++++--------------- .github/workflows/docs.yml | 29 ++++++++++----- src/types/zval.rs | 12 ++++--- 3 files changed, 71 insertions(+), 40 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8a98f1d..73feb9b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,5 +1,8 @@ name: Build and Lint on: + schedule: + # runs every monday at midnight + - cron: "0 0 * * 1" push: branches: - master @@ -12,8 +15,9 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - php: ['8.0', '8.1'] + php: ["8.0", "8.1"] rust: [stable, nightly] + clang: ["14"] phpts: [ts, nts] exclude: # ext-php-rs requires nightly Rust when on Windows. @@ -24,6 +28,8 @@ jobs: phpts: ts - os: ubuntu-latest phpts: ts + env: + CARGO_TERM_COLOR: always steps: - name: Checkout code uses: actions/checkout@v3 @@ -34,59 +40,67 @@ jobs: env: phpts: ${{ matrix.phpts }} - name: Setup Rust - uses: actions-rs/toolchain@v1 + uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust }} - override: true components: rustfmt, clippy - - name: Setup LLVM & Clang + - run: rustup show + - name: Cache cargo dependencies + uses: Swatinem/rust-cache@v2 + # Uncomment the following if statement if caching nightly deps + # ends up causing too much cache invalidation. + # if: matrix.rust == 'stable' + with: + # increment this manually to force cache eviction + prefix-key: "v0-rust" + # LLVM & Clang + - name: Cache LLVM and Clang + id: cache-llvm + uses: actions/cache@v3 if: "!contains(matrix.os, 'windows')" + with: + path: ${{ runner.temp }}/llvm-${{ matrix.clang }} + key: ${{ matrix.os }}-llvm-${{ matrix.clang }} + - name: Setup LLVM & Clang id: clang uses: KyleMayes/install-llvm-action@v1 + if: "!contains(matrix.os, 'windows')" with: - version: '13.0' - directory: ${{ runner.temp }}/llvm + version: ${{ matrix.clang }} + directory: ${{ runner.temp }}/llvm-${{ matrix.clang }} + cached: ${{ steps.cache-llvm.outputs.cache-hit }} - name: Configure Clang if: "!contains(matrix.os, 'windows')" run: | - echo "LIBCLANG_PATH=${{ runner.temp }}/llvm/lib" >> $GITHUB_ENV + echo "LIBCLANG_PATH=${{ runner.temp }}/llvm-${{ matrix.clang }}/lib" >> $GITHUB_ENV echo "LLVM_VERSION=${{ steps.clang.outputs.version }}" >> $GITHUB_ENV + echo "LLVM_CONFIG_PATH=${{ runner.temp }}/llvm-${{ matrix.clang }}/bin/llvm-config" >> $GITHUB_ENV - name: Configure Clang (macOS only) if: "contains(matrix.os, 'macos')" run: echo "SDKROOT=$(xcrun --show-sdk-path)" >> $GITHUB_ENV + # Build - name: Build env: - EXT_PHP_RS_TEST: + EXT_PHP_RS_TEST: "" run: cargo build --release --all-features --all + # Test & lint - name: Test inline examples - uses: actions-rs/cargo@v1 - with: - command: test - args: --release --all --all-features + run: cargo test --release --all --all-features - name: Run rustfmt - uses: actions-rs/cargo@v1 - with: - command: fmt - args: --all -- --check if: matrix.rust == 'stable' && matrix.os == 'ubuntu-latest' && matrix.php == '8.1' + run: cargo fmt --all -- --check - name: Run clippy - uses: actions-rs/cargo@v1 - with: - command: clippy - args: --all -- -D warnings if: matrix.rust == 'stable' && matrix.os == 'ubuntu-latest' && matrix.php == '8.1' + run: cargo clippy --all -- -D warnings + # Docs - name: Run rustdoc - uses: actions-rs/cargo@v1 - with: - command: rustdoc - args: -- -D warnings if: matrix.rust == 'stable' && matrix.os == 'ubuntu-latest' && matrix.php == '8.1' + run: cargo rustdoc -- -D warnings - name: Build with docs stub - env: - DOCS_RS: - run: - cargo clean && cargo build if: matrix.rust == 'stable' && matrix.os == 'ubuntu-latest' && matrix.php == '8.1' + env: + DOCS_RS: "" + run: cargo clean && cargo build build-zts: name: Build with ZTS runs-on: ubuntu-latest diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 8e06f52..0719b64 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -1,6 +1,9 @@ name: Deploy documentation on: workflow_dispatch: + # runs every monday at midnight + schedule: + - cron: "0 0 * * 1" push: branches: - master @@ -8,28 +11,38 @@ on: jobs: docs: name: Build and Deploy - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: ["ubuntu-latest"] + php: ["8.0"] + clang: ["14"] + mdbook: ["latest"] steps: - name: Checkout code uses: actions/checkout@v3 - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: 8.0 + php-version: ${{ matrix.php }} - name: Setup Rust - uses: actions-rs/toolchain@v1 + uses: dtolnay/rust-toolchain@nightly + - name: Cache LLVM and Clang + id: cache-llvm + uses: actions/cache@v3 with: - toolchain: nightly - override: true + path: ${{ runner.temp }}/llvm-${{ matrix.clang }} + key: ${{ matrix.os }}-llvm-${{ matrix.clang }} - name: Setup LLVM & Clang uses: KyleMayes/install-llvm-action@v1 with: - version: 11.0 - directory: ${{ runner.temp }}/llvm-11.0 + version: ${{ matrix.clang }} + directory: ${{ runner.temp }}/llvm-${{ matrix.clang }} + cached: ${{ steps.cache-llvm.outputs.cache-hit }} - name: Install mdbook uses: peaceiris/actions-mdbook@v1 with: - mdbook-version: latest + mdbook-version: ${{ matrix.mdbook }} - name: Build guide run: mdbook build guide - name: Publish docs diff --git a/src/types/zval.rs b/src/types/zval.rs index aa190d2..12292e6 100644 --- a/src/types/zval.rs +++ b/src/types/zval.rs @@ -117,8 +117,8 @@ impl Zval { } /// 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. + /// a vector of a given type. Similar to the [`unpack`] function in PHP, + /// except you can only unpack one type. /// /// # Safety /// @@ -129,13 +129,14 @@ impl Zval { /// documentation for more details. /// /// [`pack`]: https://www.php.net/manual/en/function.pack.php + /// [`unpack`]: https://www.php.net/manual/en/function.unpack.php pub fn binary(&self) -> Option> { self.zend_str().map(T::unpack_into) } /// Returns the value of the zval if it is a string and can be unpacked into - /// a slice 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. + /// a slice of a given type. Similar to the [`unpack`] function in PHP, + /// except you can only unpack one type. /// /// This function is similar to [`Zval::binary`] except that a slice is /// returned instead of a vector, meaning the contents of the string is @@ -148,6 +149,9 @@ impl Zval { /// 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 + /// [`unpack`]: https://www.php.net/manual/en/function.unpack.php pub fn binary_slice(&self) -> Option<&[T]> { self.zend_str().map(T::unpack_into) } From 3d742262c8f57e3f86fb26efc33290c8697ad7a0 Mon Sep 17 00:00:00 2001 From: ju1ius Date: Thu, 24 Nov 2022 11:07:37 +0100 Subject: [PATCH 26/34] Add get_id() and hash() methods on ZendObject (#196) --- src/types/object.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/types/object.rs b/src/types/object.rs index 6b73ffe..62fd148 100644 --- a/src/types/object.rs +++ b/src/types/object.rs @@ -196,6 +196,29 @@ impl ZendObject { T::from_zend_object(self) } + /// Returns an unique identifier for the object. + /// + /// The id is guaranteed to be unique for the lifetime of the object. + /// Once the object is destroyed, it may be reused for other objects. + /// This is equivalent to calling the [`spl_object_id`] PHP function. + /// + /// [`spl_object_id`]: https://www.php.net/manual/function.spl-object-id + #[inline] + pub fn get_id(&self) -> u32 { + self.handle + } + + /// Computes an unique hash for the object. + /// + /// The hash is guaranteed to be unique for the lifetime of the object. + /// Once the object is destroyed, it may be reused for other objects. + /// This is equivalent to calling the [`spl_object_hash`] PHP function. + /// + /// [`spl_object_hash`]: https://www.php.net/manual/function.spl-object-hash.php + pub fn hash(&self) -> String { + format!("{:016x}0000000000000000", self.handle) + } + /// Attempts to retrieve a reference to the object handlers. #[inline] unsafe fn handlers(&self) -> Result<&ZendObjectHandlers> { From 580ad9f46240b03a0d209f8a1ff93b0e56780749 Mon Sep 17 00:00:00 2001 From: ju1ius Date: Thu, 24 Nov 2022 11:11:23 +0100 Subject: [PATCH 27/34] Describes restrictions on generic parameters for `php_class` (#194) --- guide/src/macros/classes.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/guide/src/macros/classes.md b/guide/src/macros/classes.md index 3a423d1..ad62294 100644 --- a/guide/src/macros/classes.md +++ b/guide/src/macros/classes.md @@ -32,6 +32,34 @@ You can rename the property with options: - `rename` - Allows you to rename the property, e.g. `#[prop(rename = "new_name")]` +## Restrictions + +### No lifetime parameters + +Rust lifetimes are used by the Rust compiler to reason about a program's memory safety. +They are a compile-time only concept; +there is no way to access Rust lifetimes at runtime from a dynamic language like PHP. + +As soon as Rust data is exposed to PHP, +there is no guarantee which the Rust compiler can make on how long the data will live. +PHP is a reference-counted language and those references can be held +for an arbitrarily long time, which is untraceable by the Rust compiler. +The only possible way to express this correctly is to require that any `#[php_class]` +does not borrow data for any lifetime shorter than the `'static` lifetime, +i.e. the `#[php_class]` cannot have any lifetime parameters. + +When you need to share ownership of data between PHP and Rust, +instead of using borrowed references with lifetimes, consider using +reference-counted smart pointers such as [Arc](https://doc.rust-lang.org/std/sync/struct.Arc.html). + +### No generic parameters + +A Rust struct `Foo` with a generic parameter `T` generates new compiled implementations +each time it is used with a different concrete type for `T`. +These new implementations are generated by the compiler at each usage site. +This is incompatible with wrapping `Foo` in PHP, +where there needs to be a single compiled implementation of `Foo` which is integrated with the PHP interpreter. + ## Example This example creates a PHP class `Human`, adding a PHP property `address`. From a3312136708c7ba164ab7932b67be99efc721ac8 Mon Sep 17 00:00:00 2001 From: ju1ius Date: Thu, 24 Nov 2022 11:17:12 +0100 Subject: [PATCH 28/34] Add instance_of() and get_class_entry() methods on ZendObject (#197) * adds `ZendObject::get_class_entry()` to retrieve the class entry of an object without casting pointers * adds `ZendObject::instance_of()` to allow more idiomatic instanceof checks. * adds a mention that `ZendObject::is_instance::()` does not check the parent classes or interfaces. This bit me when I tried to check if `my_object.is_instance::()` and it didn't work. --- src/types/object.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/types/object.rs b/src/types/object.rs index 62fd148..24c70d8 100644 --- a/src/types/object.rs +++ b/src/types/object.rs @@ -80,6 +80,17 @@ impl ZendObject { unsafe { ZBox::from_raw(this.get_mut_zend_obj()) } } + /// Returns the [`ClassEntry`] associated with this object. + /// + /// # Panics + /// + /// Panics if the class entry is invalid. + pub fn get_class_entry(&self) -> &'static ClassEntry { + // SAFETY: it is OK to panic here since PHP would segfault anyway + // when encountering an object with no class entry. + unsafe { self.ce.as_ref() }.expect("Could not retrieve class entry.") + } + /// Attempts to retrieve the class name of the object. pub fn get_class_name(&self) -> Result { unsafe { @@ -91,8 +102,21 @@ impl ZendObject { } } + /// Returns whether this object is an instance of the given [`ClassEntry`]. + /// + /// This method checks the class and interface inheritance chain. + /// + /// # Panics + /// + /// Panics if the class entry is invalid. + pub fn instance_of(&self, ce: &ClassEntry) -> bool { + self.get_class_entry().instance_of(ce) + } + /// Checks if the given object is an instance of a registered class with /// Rust type `T`. + /// + /// This method doesn't check the class and interface inheritance chain. pub fn is_instance(&self) -> bool { (self.ce as *const ClassEntry).eq(&(T::get_metadata().ce() as *const _)) } From 31712066c8dc05900831460b1da9bcf47210564d Mon Sep 17 00:00:00 2001 From: Saif Eddin Gmati <29315886+azjezz@users.noreply.github.com> Date: Sat, 26 Nov 2022 22:09:59 +0100 Subject: [PATCH 29/34] chore: use php-discovery to find matching PHP build (#201) --- Cargo.toml | 1 + build.rs | 170 +++++++++++++++-------------------------------- unix_build.rs | 26 +++++--- windows_build.rs | 71 ++++++-------------- 4 files changed, 93 insertions(+), 175 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 520a779..99b626f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ anyhow = "1" bindgen = "0.60" cc = "1.0" skeptic = "0.13" +php-discovery = "0.1.2" [target.'cfg(windows)'.build-dependencies] ureq = { version = "2.4", features = ["native-tls", "gzip"], default-features = false } diff --git a/build.rs b/build.rs index 7617096..50e363f 100644 --- a/build.rs +++ b/build.rs @@ -6,21 +6,20 @@ use std::{ env, fs::File, io::{BufWriter, Write}, - path::{Path, PathBuf}, - process::Command, - str::FromStr, + path::PathBuf, }; use anyhow::{anyhow, bail, Context, Result}; use bindgen::RustTarget; use impl_::Provider; +use php_discovery::build::Build as PhpBuild; const MIN_PHP_API_VER: u32 = 20200930; const MAX_PHP_API_VER: u32 = 20210902; pub trait PHPProvider<'a>: Sized { /// Create a new PHP provider. - fn new(info: &'a PHPInfo) -> Result; + fn new(info: &'a PhpBuild) -> Result; /// Retrieve a list of absolute include paths. fn get_includes(&self) -> Result>; @@ -33,6 +32,7 @@ pub trait PHPProvider<'a>: Sized { for line in bindings.lines() { writeln!(writer, "{}", line)?; } + Ok(()) } @@ -42,89 +42,49 @@ pub trait PHPProvider<'a>: Sized { } } -/// Finds the location of an executable `name`. -fn find_executable(name: &str) -> Option { - const WHICH: &str = if cfg!(windows) { "where" } else { "which" }; - let cmd = Command::new(WHICH).arg(name).output().ok()?; - if cmd.status.success() { - let stdout = String::from_utf8_lossy(&cmd.stdout); - Some(stdout.trim().into()) - } else { - None - } -} - /// Finds the location of the PHP executable. -fn find_php() -> Result { - // If PHP path is given via env, it takes priority. - let env = std::env::var("PHP"); - if let Ok(env) = env { - return Ok(env.into()); - } - - find_executable("php").context("Could not find PHP path. Please ensure `php` is in your PATH or the `PHP` environment variable is set.") -} - -pub struct PHPInfo(String); - -impl PHPInfo { - pub fn get(php: &Path) -> Result { - let cmd = Command::new(php) - .arg("-i") - .output() - .context("Failed to call `php -i`")?; - if !cmd.status.success() { - bail!("Failed to call `php -i` status code {}", cmd.status); - } - let stdout = String::from_utf8_lossy(&cmd.stdout); - Ok(Self(stdout.to_string())) - } - - // Only present on Windows. - #[cfg(windows)] - pub fn architecture(&self) -> Result { - use std::convert::TryInto; - - self.get_key("Architecture") - .context("Could not find architecture of PHP")? - .try_into() - } - - pub fn thread_safety(&self) -> Result { - Ok(self - .get_key("Thread Safety") - .context("Could not find thread safety of PHP")? - == "enabled") - } - - pub fn debug(&self) -> Result { - Ok(self - .get_key("Debug Build") - .context("Could not find debug build of PHP")? - == "yes") - } - - pub fn version(&self) -> Result<&str> { - self.get_key("PHP Version") - .context("Failed to get PHP version") - } - - pub fn zend_version(&self) -> Result { - self.get_key("PHP API") - .context("Failed to get Zend version") - .and_then(|s| u32::from_str(s).context("Failed to convert Zend version to integer")) - } - - fn get_key(&self, key: &str) -> Option<&str> { - let split = format!("{} => ", key); - for line in self.0.lines() { - let components: Vec<_> = line.split(&split).collect(); - if components.len() > 1 { - return Some(components[1]); +fn find_php() -> Result { + php_discovery::discover() + .map_err(|e| anyhow!("failed to discover available PHP builds: {:?}", e)) + .and_then(|builds| { + if builds.is_empty() { + bail!("Could not find any PHP builds in the system, please ensure that PHP is installed.") } - } - None - } + + Ok(builds) + }) + .and_then(|builds| { + let mut available = Vec::new(); + let mut matching = Vec::new(); + + for build in builds { + available.push(build.php_api.to_string()); + if build.php_api >= MIN_PHP_API_VER && build.php_api <= MAX_PHP_API_VER { + matching.push(build); + } + } + + if matching.is_empty() { + bail!( + "Unable to find matching PHP binary, available PHP API version(s): '{}', requires a version between {} and {}", + available.join(", "), + MIN_PHP_API_VER, + MAX_PHP_API_VER, + ) + } + + let mut index = 0; + if let Ok(version) = env::var("RUST_PHP_VERSION") { + for (i, build) in matching.iter().enumerate() { + if build.version.to_string() == version { + index = i; + break; + } + } + } + + Ok(matching.remove(index)) + }) } /// Builds the wrapper library. @@ -178,33 +138,6 @@ fn generate_bindings(defines: &[(&str, &str)], includes: &[PathBuf]) -> Result Result<()> { - let version = info.zend_version()?; - - if !(MIN_PHP_API_VER..=MAX_PHP_API_VER).contains(&version) { - bail!("The current version of PHP is not supported. Current PHP API version: {}, requires a version between {} and {}", version, MIN_PHP_API_VER, MAX_PHP_API_VER); - } - - // Infra cfg flags - use these for things that change in the Zend API that don't - // rely on a feature and the crate user won't care about (e.g. struct field - // changes). Use a feature flag for an actual feature (e.g. enums being - // introduced in PHP 8.1). - // - // PHP 8.0 is the baseline - no feature flags will be introduced here. - // - // The PHP version cfg flags should also stack - if you compile on PHP 8.2 you - // should get both the `php81` and `php82` flags. - const PHP_81_API_VER: u32 = 20210902; - - if version >= PHP_81_API_VER { - println!("cargo:rustc-cfg=php81"); - } - - Ok(()) -} - fn main() -> Result<()> { let out_dir = env::var_os("OUT_DIR").context("Failed to get OUT_DIR")?; let out_path = PathBuf::from(out_dir).join("bindings.rs"); @@ -229,14 +162,12 @@ fn main() -> Result<()> { return Ok(()); } - let php = find_php()?; - let info = PHPInfo::get(&php)?; - let provider = Provider::new(&info)?; + let php_build = find_php()?; + let provider = Provider::new(&php_build)?; let includes = provider.get_includes()?; let defines = provider.get_defines()?; - check_php_version(&info)?; build_wrapper(&defines, &includes)?; let bindings = generate_bindings(&defines, &includes)?; @@ -245,10 +176,13 @@ fn main() -> Result<()> { let mut out_writer = BufWriter::new(out_file); provider.write_bindings(bindings, &mut out_writer)?; - if info.debug()? { + if php_build.version.major == 8 && php_build.version.minor == 1 { + println!("cargo:rustc-cfg=php81"); + } + if php_build.is_debug { println!("cargo:rustc-cfg=php_debug"); } - if info.thread_safety()? { + if php_build.is_thread_safety_enabled { println!("cargo:rustc-cfg=php_zts"); } provider.print_extra_link_args()?; diff --git a/unix_build.rs b/unix_build.rs index 623d356..d1ba674 100644 --- a/unix_build.rs +++ b/unix_build.rs @@ -1,15 +1,25 @@ use std::{path::PathBuf, process::Command}; -use anyhow::{bail, Context, Result}; +use anyhow::{anyhow, bail, Context, Result}; +use php_discovery::build::Build; -use crate::{PHPInfo, PHPProvider}; +use crate::PHPProvider; -pub struct Provider {} +pub struct Provider<'a> { + build: &'a Build, +} -impl Provider { +impl<'a> Provider<'a> { /// Runs `php-config` with one argument, returning the stdout. fn php_config(&self, arg: &str) -> Result { - let cmd = Command::new("php-config") + let config = self.build.config().ok_or_else(|| { + anyhow!( + "unable to locate `php-config` binary for `{}`.", + self.build.binary.to_string_lossy() + ) + })?; + + let cmd = Command::new(config) .arg(arg) .output() .context("Failed to run `php-config`")?; @@ -22,9 +32,9 @@ impl Provider { } } -impl<'a> PHPProvider<'a> for Provider { - fn new(_: &'a PHPInfo) -> Result { - Ok(Self {}) +impl<'a> PHPProvider<'a> for Provider<'a> { + fn new(build: &'a Build) -> Result { + Ok(Self { build }) } fn get_includes(&self) -> Result> { diff --git a/windows_build.rs b/windows_build.rs index bc0e75d..f137ce7 100644 --- a/windows_build.rs +++ b/windows_build.rs @@ -1,20 +1,20 @@ +use std::io::Write; use std::{ - convert::TryFrom, - fmt::Display, - io::{Cursor, Read, Write}, + io::{Cursor, Read}, path::{Path, PathBuf}, process::Command, sync::Arc, }; -use anyhow::{bail, Context, Result}; +use anyhow::{Context, Result}; +use php_discovery::build::Build; -use crate::{PHPInfo, PHPProvider}; +use crate::PHPProvider; const USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")); pub struct Provider<'a> { - info: &'a PHPInfo, + build: &'a Build, devel: DevelPack, } @@ -32,18 +32,25 @@ impl<'a> Provider<'a> { } impl<'a> PHPProvider<'a> for Provider<'a> { - fn new(info: &'a PHPInfo) -> Result { - let version = info.version()?; - let is_zts = info.thread_safety()?; - let arch = info.architecture()?; - let devel = DevelPack::new(version, is_zts, arch)?; + fn new(build: &'a Build) -> Result { + // don't use `build.version.to_string()` as it includes extra part which is not + // needed. + let version = format!( + "{}.{}.{}", + build.version.major, build.version.minor, build.version.release + ); + let devel = DevelPack::new( + &version, + build.is_thread_safety_enabled, + build.architecture.to_string(), + )?; if let Ok(linker) = get_rustc_linker() { if looks_like_msvc_linker(&linker) { println!("cargo:warning=It looks like you are using a MSVC linker. You may encounter issues when attempting to load your compiled extension into PHP if your MSVC linker version is not compatible with the linker used to compile your PHP. It is recommended to use `rust-lld` as your linker."); } } - Ok(Self { info, devel }) + Ok(Self { build, devel }) } fn get_includes(&self) -> Result> { @@ -56,9 +63,9 @@ impl<'a> PHPProvider<'a> for Provider<'a> { ("PHP_WIN32", "1"), ("WINDOWS", "1"), ("WIN32", "1"), - ("ZEND_DEBUG", if self.info.debug()? { "1" } else { "0" }), + ("ZEND_DEBUG", if self.build.is_debug { "1" } else { "0" }), ]; - if self.info.thread_safety()? { + if self.build.is_thread_safety_enabled { defines.push(("ZTS", "")); } Ok(defines) @@ -123,46 +130,12 @@ fn looks_like_msvc_linker(linker: &Path) -> bool { false } -#[derive(Debug, PartialEq, Eq)] -pub enum Arch { - X86, - X64, - AArch64, -} - -impl TryFrom<&str> for Arch { - type Error = anyhow::Error; - - fn try_from(value: &str) -> Result { - Ok(match value { - "x86" => Self::X86, - "x64" => Self::X64, - "arm64" => Self::AArch64, - a => bail!("Unknown architecture {}", a), - }) - } -} - -impl Display for Arch { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}", - match self { - Arch::X86 => "x86", - Arch::X64 => "x64", - Arch::AArch64 => "arm64", - } - ) - } -} - struct DevelPack(PathBuf); impl DevelPack { /// Downloads a new PHP development pack, unzips it in the build script /// temporary directory. - fn new(version: &str, is_zts: bool, arch: Arch) -> Result { + fn new(version: &str, is_zts: bool, arch: String) -> Result { let zip_name = format!( "php-devel-pack-{}{}-Win32-{}-{}.zip", version, From 7423da060df061e501f8002fe54c076134c7a2bc Mon Sep 17 00:00:00 2001 From: Pierre Tondereau Date: Mon, 28 Nov 2022 05:57:34 -0800 Subject: [PATCH 30/34] Revert "chore: use php-discovery to find matching PHP build" (#206) --- Cargo.toml | 1 - build.rs | 170 ++++++++++++++++++++++++++++++++--------------- unix_build.rs | 26 +++----- windows_build.rs | 71 ++++++++++++++------ 4 files changed, 175 insertions(+), 93 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 99b626f..520a779 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,6 @@ anyhow = "1" bindgen = "0.60" cc = "1.0" skeptic = "0.13" -php-discovery = "0.1.2" [target.'cfg(windows)'.build-dependencies] ureq = { version = "2.4", features = ["native-tls", "gzip"], default-features = false } diff --git a/build.rs b/build.rs index 50e363f..7617096 100644 --- a/build.rs +++ b/build.rs @@ -6,20 +6,21 @@ use std::{ env, fs::File, io::{BufWriter, Write}, - path::PathBuf, + path::{Path, PathBuf}, + process::Command, + str::FromStr, }; use anyhow::{anyhow, bail, Context, Result}; use bindgen::RustTarget; use impl_::Provider; -use php_discovery::build::Build as PhpBuild; const MIN_PHP_API_VER: u32 = 20200930; const MAX_PHP_API_VER: u32 = 20210902; pub trait PHPProvider<'a>: Sized { /// Create a new PHP provider. - fn new(info: &'a PhpBuild) -> Result; + fn new(info: &'a PHPInfo) -> Result; /// Retrieve a list of absolute include paths. fn get_includes(&self) -> Result>; @@ -32,7 +33,6 @@ pub trait PHPProvider<'a>: Sized { for line in bindings.lines() { writeln!(writer, "{}", line)?; } - Ok(()) } @@ -42,49 +42,89 @@ pub trait PHPProvider<'a>: Sized { } } +/// Finds the location of an executable `name`. +fn find_executable(name: &str) -> Option { + const WHICH: &str = if cfg!(windows) { "where" } else { "which" }; + let cmd = Command::new(WHICH).arg(name).output().ok()?; + if cmd.status.success() { + let stdout = String::from_utf8_lossy(&cmd.stdout); + Some(stdout.trim().into()) + } else { + None + } +} + /// Finds the location of the PHP executable. -fn find_php() -> Result { - php_discovery::discover() - .map_err(|e| anyhow!("failed to discover available PHP builds: {:?}", e)) - .and_then(|builds| { - if builds.is_empty() { - bail!("Could not find any PHP builds in the system, please ensure that PHP is installed.") +fn find_php() -> Result { + // If PHP path is given via env, it takes priority. + let env = std::env::var("PHP"); + if let Ok(env) = env { + return Ok(env.into()); + } + + find_executable("php").context("Could not find PHP path. Please ensure `php` is in your PATH or the `PHP` environment variable is set.") +} + +pub struct PHPInfo(String); + +impl PHPInfo { + pub fn get(php: &Path) -> Result { + let cmd = Command::new(php) + .arg("-i") + .output() + .context("Failed to call `php -i`")?; + if !cmd.status.success() { + bail!("Failed to call `php -i` status code {}", cmd.status); + } + let stdout = String::from_utf8_lossy(&cmd.stdout); + Ok(Self(stdout.to_string())) + } + + // Only present on Windows. + #[cfg(windows)] + pub fn architecture(&self) -> Result { + use std::convert::TryInto; + + self.get_key("Architecture") + .context("Could not find architecture of PHP")? + .try_into() + } + + pub fn thread_safety(&self) -> Result { + Ok(self + .get_key("Thread Safety") + .context("Could not find thread safety of PHP")? + == "enabled") + } + + pub fn debug(&self) -> Result { + Ok(self + .get_key("Debug Build") + .context("Could not find debug build of PHP")? + == "yes") + } + + pub fn version(&self) -> Result<&str> { + self.get_key("PHP Version") + .context("Failed to get PHP version") + } + + pub fn zend_version(&self) -> Result { + self.get_key("PHP API") + .context("Failed to get Zend version") + .and_then(|s| u32::from_str(s).context("Failed to convert Zend version to integer")) + } + + fn get_key(&self, key: &str) -> Option<&str> { + let split = format!("{} => ", key); + for line in self.0.lines() { + let components: Vec<_> = line.split(&split).collect(); + if components.len() > 1 { + return Some(components[1]); } - - Ok(builds) - }) - .and_then(|builds| { - let mut available = Vec::new(); - let mut matching = Vec::new(); - - for build in builds { - available.push(build.php_api.to_string()); - if build.php_api >= MIN_PHP_API_VER && build.php_api <= MAX_PHP_API_VER { - matching.push(build); - } - } - - if matching.is_empty() { - bail!( - "Unable to find matching PHP binary, available PHP API version(s): '{}', requires a version between {} and {}", - available.join(", "), - MIN_PHP_API_VER, - MAX_PHP_API_VER, - ) - } - - let mut index = 0; - if let Ok(version) = env::var("RUST_PHP_VERSION") { - for (i, build) in matching.iter().enumerate() { - if build.version.to_string() == version { - index = i; - break; - } - } - } - - Ok(matching.remove(index)) - }) + } + None + } } /// Builds the wrapper library. @@ -138,6 +178,33 @@ fn generate_bindings(defines: &[(&str, &str)], includes: &[PathBuf]) -> Result Result<()> { + let version = info.zend_version()?; + + if !(MIN_PHP_API_VER..=MAX_PHP_API_VER).contains(&version) { + bail!("The current version of PHP is not supported. Current PHP API version: {}, requires a version between {} and {}", version, MIN_PHP_API_VER, MAX_PHP_API_VER); + } + + // Infra cfg flags - use these for things that change in the Zend API that don't + // rely on a feature and the crate user won't care about (e.g. struct field + // changes). Use a feature flag for an actual feature (e.g. enums being + // introduced in PHP 8.1). + // + // PHP 8.0 is the baseline - no feature flags will be introduced here. + // + // The PHP version cfg flags should also stack - if you compile on PHP 8.2 you + // should get both the `php81` and `php82` flags. + const PHP_81_API_VER: u32 = 20210902; + + if version >= PHP_81_API_VER { + println!("cargo:rustc-cfg=php81"); + } + + Ok(()) +} + fn main() -> Result<()> { let out_dir = env::var_os("OUT_DIR").context("Failed to get OUT_DIR")?; let out_path = PathBuf::from(out_dir).join("bindings.rs"); @@ -162,12 +229,14 @@ fn main() -> Result<()> { return Ok(()); } - let php_build = find_php()?; - let provider = Provider::new(&php_build)?; + let php = find_php()?; + let info = PHPInfo::get(&php)?; + let provider = Provider::new(&info)?; let includes = provider.get_includes()?; let defines = provider.get_defines()?; + check_php_version(&info)?; build_wrapper(&defines, &includes)?; let bindings = generate_bindings(&defines, &includes)?; @@ -176,13 +245,10 @@ fn main() -> Result<()> { let mut out_writer = BufWriter::new(out_file); provider.write_bindings(bindings, &mut out_writer)?; - if php_build.version.major == 8 && php_build.version.minor == 1 { - println!("cargo:rustc-cfg=php81"); - } - if php_build.is_debug { + if info.debug()? { println!("cargo:rustc-cfg=php_debug"); } - if php_build.is_thread_safety_enabled { + if info.thread_safety()? { println!("cargo:rustc-cfg=php_zts"); } provider.print_extra_link_args()?; diff --git a/unix_build.rs b/unix_build.rs index d1ba674..623d356 100644 --- a/unix_build.rs +++ b/unix_build.rs @@ -1,25 +1,15 @@ use std::{path::PathBuf, process::Command}; -use anyhow::{anyhow, bail, Context, Result}; -use php_discovery::build::Build; +use anyhow::{bail, Context, Result}; -use crate::PHPProvider; +use crate::{PHPInfo, PHPProvider}; -pub struct Provider<'a> { - build: &'a Build, -} +pub struct Provider {} -impl<'a> Provider<'a> { +impl Provider { /// Runs `php-config` with one argument, returning the stdout. fn php_config(&self, arg: &str) -> Result { - let config = self.build.config().ok_or_else(|| { - anyhow!( - "unable to locate `php-config` binary for `{}`.", - self.build.binary.to_string_lossy() - ) - })?; - - let cmd = Command::new(config) + let cmd = Command::new("php-config") .arg(arg) .output() .context("Failed to run `php-config`")?; @@ -32,9 +22,9 @@ impl<'a> Provider<'a> { } } -impl<'a> PHPProvider<'a> for Provider<'a> { - fn new(build: &'a Build) -> Result { - Ok(Self { build }) +impl<'a> PHPProvider<'a> for Provider { + fn new(_: &'a PHPInfo) -> Result { + Ok(Self {}) } fn get_includes(&self) -> Result> { diff --git a/windows_build.rs b/windows_build.rs index f137ce7..bc0e75d 100644 --- a/windows_build.rs +++ b/windows_build.rs @@ -1,20 +1,20 @@ -use std::io::Write; use std::{ - io::{Cursor, Read}, + convert::TryFrom, + fmt::Display, + io::{Cursor, Read, Write}, path::{Path, PathBuf}, process::Command, sync::Arc, }; -use anyhow::{Context, Result}; -use php_discovery::build::Build; +use anyhow::{bail, Context, Result}; -use crate::PHPProvider; +use crate::{PHPInfo, PHPProvider}; const USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")); pub struct Provider<'a> { - build: &'a Build, + info: &'a PHPInfo, devel: DevelPack, } @@ -32,25 +32,18 @@ impl<'a> Provider<'a> { } impl<'a> PHPProvider<'a> for Provider<'a> { - fn new(build: &'a Build) -> Result { - // don't use `build.version.to_string()` as it includes extra part which is not - // needed. - let version = format!( - "{}.{}.{}", - build.version.major, build.version.minor, build.version.release - ); - let devel = DevelPack::new( - &version, - build.is_thread_safety_enabled, - build.architecture.to_string(), - )?; + fn new(info: &'a PHPInfo) -> Result { + let version = info.version()?; + let is_zts = info.thread_safety()?; + let arch = info.architecture()?; + let devel = DevelPack::new(version, is_zts, arch)?; if let Ok(linker) = get_rustc_linker() { if looks_like_msvc_linker(&linker) { println!("cargo:warning=It looks like you are using a MSVC linker. You may encounter issues when attempting to load your compiled extension into PHP if your MSVC linker version is not compatible with the linker used to compile your PHP. It is recommended to use `rust-lld` as your linker."); } } - Ok(Self { build, devel }) + Ok(Self { info, devel }) } fn get_includes(&self) -> Result> { @@ -63,9 +56,9 @@ impl<'a> PHPProvider<'a> for Provider<'a> { ("PHP_WIN32", "1"), ("WINDOWS", "1"), ("WIN32", "1"), - ("ZEND_DEBUG", if self.build.is_debug { "1" } else { "0" }), + ("ZEND_DEBUG", if self.info.debug()? { "1" } else { "0" }), ]; - if self.build.is_thread_safety_enabled { + if self.info.thread_safety()? { defines.push(("ZTS", "")); } Ok(defines) @@ -130,12 +123,46 @@ fn looks_like_msvc_linker(linker: &Path) -> bool { false } +#[derive(Debug, PartialEq, Eq)] +pub enum Arch { + X86, + X64, + AArch64, +} + +impl TryFrom<&str> for Arch { + type Error = anyhow::Error; + + fn try_from(value: &str) -> Result { + Ok(match value { + "x86" => Self::X86, + "x64" => Self::X64, + "arm64" => Self::AArch64, + a => bail!("Unknown architecture {}", a), + }) + } +} + +impl Display for Arch { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Arch::X86 => "x86", + Arch::X64 => "x64", + Arch::AArch64 => "arm64", + } + ) + } +} + struct DevelPack(PathBuf); impl DevelPack { /// Downloads a new PHP development pack, unzips it in the build script /// temporary directory. - fn new(version: &str, is_zts: bool, arch: String) -> Result { + fn new(version: &str, is_zts: bool, arch: Arch) -> Result { let zip_name = format!( "php-devel-pack-{}{}-Win32-{}-{}.zip", version, From 9e08e253dc9142f53ab0479ebaac2a59c7668531 Mon Sep 17 00:00:00 2001 From: Pierre Tondereau Date: Mon, 28 Nov 2022 12:08:00 -0800 Subject: [PATCH 31/34] Update Cargo.toml (#207) --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 520a779..a0ca45e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/davidcole1340/ext-php-rs" homepage = "https://github.com/davidcole1340/ext-php-rs" license = "MIT OR Apache-2.0" keywords = ["php", "ffi", "zend"] -version = "0.8.2" +version = "0.8.3" authors = ["David Cole "] edition = "2018" categories = ["api-bindings"] From d52a878e7ba857aea23d6cfc2daec18d9b7bffc3 Mon Sep 17 00:00:00 2001 From: ju1ius Date: Fri, 9 Dec 2022 10:54:17 +0100 Subject: [PATCH 32/34] feat: allows ZendStr to contain null bytes (#202) Closes https://github.com/davidcole1340/ext-php-rs/issues/200 ## Rationale In PHP zend_strings are binary strings with no encoding information. They can contain any byte at any position. The current implementation use `CString` to transfer zend_strings between Rust and PHP, which prevents zend_strings containing null-bytes to roundtrip through the ffi layer. Moreover, `ZendStr::new()` accepts only a `&str`, which is incorrect since a zend_string is not required to be valid UTF8. When reading the PHP source code, it is apparent that most functions marked with `ZEND_API` that accept a `const *char` are convenience wrappers that convert the `const *char` to a zend_string and delegate to another function. For example [zend_throw_exception()](https://github.com/php/php-src/blob/eb83e0206c9b9261d786943bf2c5ad61dca287e2/Zend/zend_exceptions.c#L823) takes a `const *char message`, and just converts it to a zend_string before delegating to [zend_throw_exception_zstr()](https://github.com/php/php-src/blob/eb83e0206c9b9261d786943bf2c5ad61dca287e2/Zend/zend_exceptions.c#L795). I kept this PR focused around `ZendStr` and it's usages in the library, but it should be seen as the first step of a more global effort to remove usages of `CString` everywhere possible. Also, I didn't change the return type of the string related methods of `Zval` (e.g. I could have made `Zval::set_string()` accept an `impl AsRef<[u8]>` instead of `&str` and return `()` instead of `Result<()>`). If I get feedback that it should be done in this PR, I'll do it. ## Summary of the changes: ### ZendStr * [BC break]: `ZendStr::new()` and `ZendStr::new_interned()` now accept an `impl AsRef<[u8]>` instead of just `&str`, and are therefore infaillible (outside of the cases where we panic, e.g. when allocation fails). This is a BC break, but it's impact shouldn't be huge (users will most likely just have to remove a bunch of `?` or add a few `Ok()`). * [BC break]: Conversely, `ZendStr::as_c_str()` now returns a `Result<&CStr>` since it can fail on strings containing null bytes. * [BC break]: `ZensStr::as_str()` now returns a `Result<&str>` instead of an `Option<&str>` since we have to return an error in case of invalid UTF8. * adds method `ZendStr::as_bytes()` to return the underlying byte slice. * adds convenience methods `ZendStr::as_ptr()` and `ZendStr::as_mut_ptr()` to return raw pointers to the zend_string. ### ZendStr conversion traits * adds `impl AsRef<[u8]> for ZendStr` * [BC break]: replaces `impl TryFrom for ZBox` by `impl From for ZBox`. * [BC break]: replaces `impl TryFrom<&str> for ZBox` by `impl From<&str> for ZBox`. * [BC break]: replaces `impl From<&ZendStr> for &CStr` by `impl TryFrom<&ZendStr> for &CStr`. ### Error * adds new enum member `Error::InvalidUtf8` used when converting a `ZendStr` to `String` or `&str` --- .gitignore | 3 +- allowed_bindings.rs | 2 + src/builders/class.rs | 2 +- src/error.rs | 3 + src/ffi.rs | 3 + src/types/object.rs | 6 +- src/types/string.rs | 166 +++++++++++++++++++++++++----------------- src/types/zval.rs | 6 +- src/wrapper.c | 13 +++- src/wrapper.h | 6 +- src/zend/class.rs | 4 +- src/zend/handlers.rs | 10 +-- 12 files changed, 135 insertions(+), 89 deletions(-) diff --git a/.gitignore b/.gitignore index 4f29e17..89e3a70 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ Cargo.lock /.vscode /.idea -expand.rs +/tmp +expand.rs \ No newline at end of file diff --git a/allowed_bindings.rs b/allowed_bindings.rs index 827c750..6afa7ca 100644 --- a/allowed_bindings.rs +++ b/allowed_bindings.rs @@ -43,6 +43,8 @@ bind! { // ext_php_rs_zend_object_release, // ext_php_rs_zend_string_init, // ext_php_rs_zend_string_release, + // ext_php_rs_is_kown_valid_utf8, + // ext_php_rs_set_kown_valid_utf8, object_properties_init, php_info_print_table_end, php_info_print_table_header, diff --git a/src/builders/class.rs b/src/builders/class.rs index bfc7453..b249506 100644 --- a/src/builders/class.rs +++ b/src/builders/class.rs @@ -226,7 +226,7 @@ impl ClassBuilder { /// /// Returns an [`Error`] variant if the class could not be registered. pub fn build(mut self) -> Result<&'static mut ClassEntry> { - self.ce.name = ZendStr::new_interned(&self.name, true)?.into_raw(); + self.ce.name = ZendStr::new_interned(&self.name, true).into_raw(); self.methods.push(FunctionEntry::end()); let func = Box::into_raw(self.methods.into_boxed_slice()) as *const FunctionEntry; diff --git a/src/error.rs b/src/error.rs index eb24096..eaa0773 100644 --- a/src/error.rs +++ b/src/error.rs @@ -48,6 +48,8 @@ pub enum Error { /// The string could not be converted into a C-string due to the presence of /// a NUL character. InvalidCString, + /// The string could not be converted into a valid Utf8 string + InvalidUtf8, /// Could not call the given function. Callable, /// An invalid exception type was thrown. @@ -82,6 +84,7 @@ impl Display for Error { f, "String given contains NUL-bytes which cannot be present in a C string." ), + Error::InvalidUtf8 => write!(f, "Invalid Utf8 byte sequence."), Error::Callable => write!(f, "Could not call given function."), Error::InvalidException(flags) => { write!(f, "Invalid exception type was thrown: {:?}", flags) diff --git a/src/ffi.rs b/src/ffi.rs index e750f47..92614c4 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -19,6 +19,9 @@ extern "C" { persistent: bool, ) -> *mut zend_string; pub fn ext_php_rs_zend_string_release(zs: *mut zend_string); + pub fn ext_php_rs_is_known_valid_utf8(zs: *const zend_string) -> bool; + pub fn ext_php_rs_set_known_valid_utf8(zs: *mut zend_string); + pub fn ext_php_rs_php_build_id() -> *const c_char; pub fn ext_php_rs_zend_object_alloc(obj_size: usize, ce: *mut zend_class_entry) -> *mut c_void; pub fn ext_php_rs_zend_object_release(obj: *mut zend_object); diff --git a/src/types/object.rs b/src/types/object.rs index 24c70d8..606c483 100644 --- a/src/types/object.rs +++ b/src/types/object.rs @@ -137,7 +137,7 @@ impl ZendObject { return Err(Error::InvalidProperty); } - let mut name = ZendStr::new(name, false)?; + let mut name = ZendStr::new(name, false); let mut rv = Zval::new(); let zv = unsafe { @@ -162,7 +162,7 @@ impl ZendObject { /// * `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 name = ZendStr::new(name, false); let mut value = value.into_zval(false)?; unsafe { @@ -187,7 +187,7 @@ impl ZendObject { /// * `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)?; + let mut name = ZendStr::new(name, false); Ok(unsafe { self.handlers()?.has_property.ok_or(Error::InvalidScope)?( diff --git a/src/types/string.rs b/src/types/string.rs index dd9624a..6256ee2 100644 --- a/src/types/string.rs +++ b/src/types/string.rs @@ -16,6 +16,7 @@ use crate::{ convert::{FromZval, IntoZval}, error::{Error, Result}, ffi::{ + ext_php_rs_is_known_valid_utf8, ext_php_rs_set_known_valid_utf8, ext_php_rs_zend_string_init, ext_php_rs_zend_string_release, zend_string, zend_string_init_interned, }, @@ -30,7 +31,7 @@ use crate::{ /// 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. +/// variant. /// /// 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: @@ -46,7 +47,7 @@ static INTERNED_LOCK: Mutex<()> = const_mutex(()); // on the alias `ZendStr` :( #[allow(clippy::len_without_is_empty)] impl ZendStr { - /// Creates a new Zend string from a [`str`]. + /// Creates a new Zend string from a slice of bytes. /// /// # Parameters /// @@ -54,12 +55,6 @@ impl ZendStr { /// * `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. - /// /// # Panics /// /// Panics if the function was unable to allocate memory for the Zend @@ -78,10 +73,19 @@ impl ZendStr { /// ```no_run /// use ext_php_rs::types::ZendStr; /// - /// let s = ZendStr::new("Hello, world!", false).unwrap(); + /// let s = ZendStr::new("Hello, world!", false); + /// let php = ZendStr::new([80, 72, 80], false); /// ``` - pub fn new(str: &str, persistent: bool) -> Result> { - Ok(Self::from_c_str(&CString::new(str)?, persistent)) + pub fn new(str: impl AsRef<[u8]>, persistent: bool) -> ZBox { + let s = str.as_ref(); + // TODO: we should handle the special cases when length is either 0 or 1 + // see `zend_string_init_fast()` in `zend_string.h` + unsafe { + let ptr = ext_php_rs_zend_string_init(s.as_ptr().cast(), s.len(), persistent) + .as_mut() + .expect("Failed to allocate memory for new Zend string"); + ZBox::from_raw(ptr) + } } /// Creates a new Zend string from a [`CStr`]. @@ -126,7 +130,7 @@ impl ZendStr { } } - /// Creates a new interned Zend string from a [`str`]. + /// Creates a new interned Zend string from a slice of bytes. /// /// An interned string is only ever stored once and is immutable. PHP stores /// the string in an internal hashtable which stores the interned @@ -145,16 +149,12 @@ impl ZendStr { /// * `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. - /// /// # Panics /// - /// Panics if the function was unable to allocate memory for the Zend - /// string. + /// Panics under the following circumstances: + /// + /// * The function used to create interned strings has not been set. + /// * The function could not allocate enough memory for the Zend string. /// /// # Safety /// @@ -171,8 +171,16 @@ impl 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)) + pub fn new_interned(str: impl AsRef<[u8]>, persistent: bool) -> ZBox { + let _lock = INTERNED_LOCK.lock(); + let s = str.as_ref(); + unsafe { + let init = zend_string_init_interned.expect("`zend_string_init_interned` not ready"); + let ptr = init(s.as_ptr().cast(), s.len() as _, persistent) + .as_mut() + .expect("Failed to allocate memory for new Zend string"); + ZBox::from_raw(ptr) + } } /// Creates a new interned Zend string from a [`CStr`]. @@ -222,11 +230,8 @@ impl ZendStr { let _lock = INTERNED_LOCK.lock(); unsafe { - let ptr = zend_string_init_interned.expect("`zend_string_init_interned` not ready")( - str.as_ptr(), - str.to_bytes().len() as _, - persistent, - ); + let init = zend_string_init_interned.expect("`zend_string_init_interned` not ready"); + let ptr = init(str.as_ptr(), str.to_bytes().len() as _, persistent); ZBox::from_raw( ptr.as_mut() @@ -242,7 +247,7 @@ impl ZendStr { /// ```no_run /// use ext_php_rs::types::ZendStr; /// - /// let s = ZendStr::new("hello, world!", false).unwrap(); + /// let s = ZendStr::new("hello, world!", false); /// assert_eq!(s.len(), 13); /// ``` pub fn len(&self) -> usize { @@ -256,39 +261,61 @@ impl ZendStr { /// ```no_run /// use ext_php_rs::types::ZendStr; /// - /// let s = ZendStr::new("hello, world!", false).unwrap(); + /// let s = ZendStr::new("hello, world!", false); /// assert_eq!(s.is_empty(), false); /// ``` pub fn is_empty(&self) -> bool { self.len() == 0 } - /// Returns a reference to the underlying [`CStr`] inside the Zend string. - pub fn as_c_str(&self) -> &CStr { - // SAFETY: Zend strings store their readable length in a fat pointer. - unsafe { - let slice = slice::from_raw_parts(self.val.as_ptr() as *const u8, self.len() + 1); - CStr::from_bytes_with_nul_unchecked(slice) - } + /// Attempts to return a reference to the underlying bytes inside the Zend + /// string as a [`CStr`]. + /// + /// Returns an [Error::InvalidCString] variant if the string contains null + /// bytes. + pub fn as_c_str(&self) -> Result<&CStr> { + let bytes_with_null = + unsafe { slice::from_raw_parts(self.val.as_ptr().cast(), self.len() + 1) }; + CStr::from_bytes_with_nul(bytes_with_null).map_err(|_| Error::InvalidCString) } - /// Attempts to return a reference to the underlying [`str`] inside the Zend + /// Attempts to return a reference to the underlying bytes inside the Zend /// string. /// - /// Returns the [`None`] variant if the [`CStr`] contains non-UTF-8 - /// characters. + /// Returns an [Error::InvalidUtf8] variant if the [`str`] contains + /// non-UTF-8 characters. /// /// # Example /// /// ```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!")); + /// let s = ZendStr::new("hello, world!", false); + /// assert!(s.as_str().is_ok()); /// ``` - pub fn as_str(&self) -> Option<&str> { - self.as_c_str().to_str().ok() + pub fn as_str(&self) -> Result<&str> { + if unsafe { ext_php_rs_is_known_valid_utf8(self.as_ptr()) } { + let str = unsafe { std::str::from_utf8_unchecked(self.as_bytes()) }; + return Ok(str); + } + let str = std::str::from_utf8(self.as_bytes()).map_err(|_| Error::InvalidUtf8)?; + unsafe { ext_php_rs_set_known_valid_utf8(self.as_ptr() as *mut _) }; + Ok(str) + } + + /// Returns a reference to the underlying bytes inside the Zend string. + pub fn as_bytes(&self) -> &[u8] { + unsafe { slice::from_raw_parts(self.val.as_ptr().cast(), self.len()) } + } + + /// Returns a raw pointer to this object + pub fn as_ptr(&self) -> *const ZendStr { + self as *const _ + } + + /// Returns a mutable pointer to this object + pub fn as_mut_ptr(&mut self) -> *mut ZendStr { + self as *mut _ } } @@ -300,7 +327,22 @@ unsafe impl ZBoxable for ZendStr { impl Debug for ZendStr { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.as_c_str().fmt(f) + self.as_str().fmt(f) + } +} + +impl AsRef<[u8]> for ZendStr { + fn as_ref(&self) -> &[u8] { + self.as_bytes() + } +} + +impl PartialEq for ZendStr +where + T: AsRef<[u8]>, +{ + fn eq(&self, other: &T) -> bool { + self.as_ref() == other.as_ref() } } @@ -308,19 +350,14 @@ impl ToOwned for ZendStr { type Owned = ZBox; fn to_owned(&self) -> Self::Owned { - Self::from_c_str(self.as_c_str(), false) + Self::new(self.as_bytes(), false) } } -impl PartialEq for ZendStr { - #[inline] - fn eq(&self, other: &Self) -> bool { - self.as_c_str().eq(other.as_c_str()) - } -} +impl<'a> TryFrom<&'a ZendStr> for &'a CStr { + type Error = Error; -impl<'a> From<&'a ZendStr> for &'a CStr { - fn from(value: &'a ZendStr) -> Self { + fn try_from(value: &'a ZendStr) -> Result { value.as_c_str() } } @@ -329,7 +366,7 @@ impl<'a> TryFrom<&'a ZendStr> for &'a str { type Error = Error; fn try_from(value: &'a ZendStr) -> Result { - value.as_str().ok_or(Error::InvalidCString) + value.as_str() } } @@ -337,10 +374,7 @@ impl TryFrom<&ZendStr> for String { type Error = Error; fn try_from(value: &ZendStr) -> Result { - value - .as_str() - .map(|s| s.to_string()) - .ok_or(Error::InvalidCString) + value.as_str().map(ToString::to_string) } } @@ -362,18 +396,14 @@ impl From for ZBox { } } -impl TryFrom<&str> for ZBox { - type Error = Error; - - fn try_from(value: &str) -> Result { - ZendStr::new(value, false) +impl From<&str> for ZBox { + fn from(value: &str) -> Self { + ZendStr::new(value.as_bytes(), false) } } -impl TryFrom for ZBox { - type Error = Error; - - fn try_from(value: String) -> Result { +impl From for ZBox { + fn from(value: String) -> Self { ZendStr::new(value.as_str(), false) } } diff --git a/src/types/zval.rs b/src/types/zval.rs index 12292e6..34c8c81 100644 --- a/src/types/zval.rs +++ b/src/types/zval.rs @@ -113,7 +113,7 @@ impl Zval { /// 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()) + self.zend_str().and_then(|zs| zs.as_str().ok()) } /// Returns the value of the zval if it is a string and can be unpacked into @@ -340,7 +340,7 @@ impl Zval { /// * `val` - The value to set the zval as. /// * `persistent` - Whether the string should persist between requests. pub fn set_string(&mut self, val: &str, persistent: bool) -> Result<()> { - self.set_zend_string(ZendStr::new(val, persistent)?); + self.set_zend_string(ZendStr::new(val, persistent)); Ok(()) } @@ -374,7 +374,7 @@ impl Zval { /// * `val` - The value to set the zval as. /// * `persistent` - Whether the string should persist between requests. pub fn set_interned_string(&mut self, val: &str, persistent: bool) -> Result<()> { - self.set_zend_string(ZendStr::new_interned(val, persistent)?); + self.set_zend_string(ZendStr::new_interned(val, persistent)); Ok(()) } diff --git a/src/wrapper.c b/src/wrapper.c index 240b2d6..faf585e 100644 --- a/src/wrapper.c +++ b/src/wrapper.c @@ -1,7 +1,6 @@ #include "wrapper.h" -zend_string *ext_php_rs_zend_string_init(const char *str, size_t len, - bool persistent) { +zend_string *ext_php_rs_zend_string_init(const char *str, size_t len, bool persistent) { return zend_string_init(str, len, persistent); } @@ -9,6 +8,16 @@ void ext_php_rs_zend_string_release(zend_string *zs) { zend_string_release(zs); } +bool ext_php_rs_is_known_valid_utf8(const zend_string *zs) { + return GC_FLAGS(zs) & IS_STR_VALID_UTF8; +} + +void ext_php_rs_set_known_valid_utf8(zend_string *zs) { + if (!ZSTR_IS_INTERNED(zs)) { + GC_ADD_FLAGS(zs, IS_STR_VALID_UTF8); + } +} + const char *ext_php_rs_php_build_id() { return ZEND_MODULE_BUILD_ID; } void *ext_php_rs_zend_object_alloc(size_t obj_size, zend_class_entry *ce) { diff --git a/src/wrapper.h b/src/wrapper.h index f55f3ec..2813263 100644 --- a/src/wrapper.h +++ b/src/wrapper.h @@ -21,9 +21,11 @@ #include "zend_inheritance.h" #include "zend_interfaces.h" -zend_string *ext_php_rs_zend_string_init(const char *str, size_t len, - bool persistent); +zend_string *ext_php_rs_zend_string_init(const char *str, size_t len, bool persistent); void ext_php_rs_zend_string_release(zend_string *zs); +bool ext_php_rs_is_known_valid_utf8(const zend_string *zs); +void ext_php_rs_set_known_valid_utf8(zend_string *zs); + const char *ext_php_rs_php_build_id(); void *ext_php_rs_zend_object_alloc(size_t obj_size, zend_class_entry *ce); void ext_php_rs_zend_object_release(zend_object *obj); diff --git a/src/zend/class.rs b/src/zend/class.rs index 952302a..e3f6cee 100644 --- a/src/zend/class.rs +++ b/src/zend/class.rs @@ -15,7 +15,7 @@ impl ClassEntry { /// 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()?; + let mut name = ZendStr::new(name, false); unsafe { crate::ffi::zend_lookup_class_ex(name.deref_mut(), std::ptr::null_mut(), 0).as_ref() @@ -77,7 +77,7 @@ impl ClassEntry { 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()?) + Self::try_find(name.as_str().ok()?) } } } diff --git a/src/zend/handlers.rs b/src/zend/handlers.rs index 3d1371a..4ec4ef9 100644 --- a/src/zend/handlers.rs +++ b/src/zend/handlers.rs @@ -87,11 +87,7 @@ impl ZendObjectHandlers { .ok_or("Invalid property name pointer given")?; let self_ = &mut **obj; let props = T::get_metadata().get_properties(); - let prop = props.get( - prop_name - .as_str() - .ok_or("Invalid property name was given")?, - ); + let prop = props.get(prop_name.as_str()?); // 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")?; @@ -138,7 +134,7 @@ impl ZendObjectHandlers { .ok_or("Invalid property name pointer given")?; let self_ = &mut **obj; let props = T::get_metadata().get_properties(); - let prop = props.get(prop_name.as_str().ok_or("Invalid property name given")?); + let prop = props.get(prop_name.as_str()?); let value_mut = value.as_mut().ok_or("Invalid return zval given")?; Ok(match prop { @@ -220,7 +216,7 @@ impl ZendObjectHandlers { .as_ref() .ok_or("Invalid property name pointer given")?; let props = T::get_metadata().get_properties(); - let prop = props.get(prop_name.as_str().ok_or("Invalid property name given")?); + let prop = props.get(prop_name.as_str()?); let self_ = &mut **obj; match has_set_exists { From 4ca5c0d06e503ec8d1c3e2764494c97e8e9a91fe Mon Sep 17 00:00:00 2001 From: ju1ius Date: Sun, 11 Dec 2022 20:08:50 +0100 Subject: [PATCH 33/34] honour PHP_CONFIG & rebuild automatically when env vars change (#210) Closes https://github.com/davidcole1340/ext-php-rs/issues/208 Closes https://github.com/davidcole1340/ext-php-rs/issues/209 ## Summary of the changes ### Build scripts * the `unix_build.rs` script now honors the `PHP_CONFIG` environment variable, like `cargo php install` * use `cargo:rerun-if-env-changed` for the `PHP`, `PHP_CONFIG` and `PATH` environment variables, to avoid needless recompilation of the whole dependency tree. ### Documentation While trying to document the aforementioned changes, I realized that there was no chapter about installing and setting up a PHP environment to develop PHP extensions. So, I refactored the first chapters of the book into a `Getting Started` section, including instructions on how to quickly set up a PHP environment. --- build.rs | 27 +++- guide/src/SUMMARY.md | 14 +- guide/src/examples/hello_world.md | 103 ------------ guide/src/examples/index.md | 3 - guide/src/{ => getting-started}/cargo-php.md | 0 guide/src/getting-started/hello_world.md | 162 +++++++++++++++++++ guide/src/getting-started/installation.md | 68 ++++++++ unix_build.rs | 20 ++- 8 files changed, 278 insertions(+), 119 deletions(-) delete mode 100644 guide/src/examples/hello_world.md delete mode 100644 guide/src/examples/index.md rename guide/src/{ => getting-started}/cargo-php.md (100%) create mode 100644 guide/src/getting-started/hello_world.md create mode 100644 guide/src/getting-started/installation.md diff --git a/build.rs b/build.rs index 7617096..8a06ba2 100644 --- a/build.rs +++ b/build.rs @@ -43,7 +43,7 @@ pub trait PHPProvider<'a>: Sized { } /// Finds the location of an executable `name`. -fn find_executable(name: &str) -> Option { +pub fn find_executable(name: &str) -> Option { const WHICH: &str = if cfg!(windows) { "where" } else { "which" }; let cmd = Command::new(WHICH).arg(name).output().ok()?; if cmd.status.success() { @@ -54,15 +54,25 @@ fn find_executable(name: &str) -> Option { } } +/// Returns an environment variable's value as a PathBuf +pub fn path_from_env(key: &str) -> Option { + std::env::var_os(key).map(PathBuf::from) +} + /// Finds the location of the PHP executable. fn find_php() -> Result { - // If PHP path is given via env, it takes priority. - let env = std::env::var("PHP"); - if let Ok(env) = env { - return Ok(env.into()); + // If path is given via env, it takes priority. + if let Some(path) = path_from_env("PHP") { + if !path.try_exists()? { + // If path was explicitly given and it can't be found, this is a hard error + bail!("php executable not found at {:?}", path); + } + return Ok(path); } - - find_executable("php").context("Could not find PHP path. Please ensure `php` is in your PATH or the `PHP` environment variable is set.") + find_executable("php").with_context(|| { + "Could not find PHP executable. \ + Please ensure `php` is in your PATH or the `PHP` environment variable is set." + }) } pub struct PHPInfo(String); @@ -218,6 +228,9 @@ fn main() -> Result<()> { ] { println!("cargo:rerun-if-changed={}", path.to_string_lossy()); } + for env_var in ["PHP", "PHP_CONFIG", "PATH"] { + println!("cargo:rerun-if-env-changed={}", env_var); + } // docs.rs runners only have PHP 7.4 - use pre-generated bindings if env::var("DOCS_RS").is_ok() { diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md index c050b60..f725964 100644 --- a/guide/src/SUMMARY.md +++ b/guide/src/SUMMARY.md @@ -1,9 +1,15 @@ # Summary -- [Introduction](./introduction.md) -- [`cargo php`](./cargo-php.md) -- [Examples](./examples/index.md) - - [Hello World](./examples/hello_world.md) +[Introduction](./introduction.md) + +# Getting Started + +- [Installation](./getting-started/installation.md) +- [Hello World](./getting-started/hello_world.md) +- [`cargo php`](./getting-started/cargo-php.md) + +# Reference Guide + - [Types](./types/index.md) - [Primitive Numbers](./types/numbers.md) - [`String`](./types/string.md) diff --git a/guide/src/examples/hello_world.md b/guide/src/examples/hello_world.md deleted file mode 100644 index b650bc4..0000000 --- a/guide/src/examples/hello_world.md +++ /dev/null @@ -1,103 +0,0 @@ -# Hello World - -Let's create a basic PHP extension. We will start by creating a new Rust library -crate: - -```sh -$ cargo new hello_world --lib -$ cd hello_world -``` - -### `Cargo.toml` - -Let's set up our crate by adding `ext-php-rs` as a dependency and setting the -crate type to `cdylib`. Update the `Cargo.toml` to look something like so: - -```toml -[package] -name = "hello_world" -version = "0.1.0" -edition = "2018" - -[dependencies] -ext-php-rs = "*" - -[lib] -crate-type = ["cdylib"] -``` - -### `.cargo/config.toml` - -When compiling for Linux and macOS, we do not link directly to PHP, rather PHP -will dynamically load the library. We need to tell the linker it's ok to have -undefined symbols (as they will be resolved when loaded by PHP). - -On Windows, we also need to switch to using the `rust-lld` linker. - -> Microsoft Visual C++'s `link.exe` is supported, however you may run into -> issues if your linker is not compatible with the linker used to compile PHP. - -We do this by creating a Cargo config file in `.cargo/config.toml` with the -following contents: - -```toml -{{#include ../../../.cargo/config.toml}} -``` - -### `src/lib.rs` - -Let's actually write the extension code now. We start by importing the -`ext-php-rs` prelude, which contains most of the imports required to make a -basic extension. We will then write our basic `hello_world` function, which will -take a string argument for the callers name, and we will return another string. -Finally, we write a `get_module` function which is used by PHP to find out about -your module. The `#[php_module]` attribute automatically registers your new -function so we don't need to do anything except return the `ModuleBuilder` that -we were given. - -We also need to enable the `abi_vectorcall` feature when compiling for Windows. -This is a nightly-only feature so it is recommended to use the `#[cfg_attr]` -macro to not enable the feature on other operating systems. - -```rust,ignore -#![cfg_attr(windows, feature(abi_vectorcall))] -use ext_php_rs::prelude::*; - -#[php_function] -pub fn hello_world(name: &str) -> String { - format!("Hello, {}!", name) -} - -#[php_module] -pub fn get_module(module: ModuleBuilder) -> ModuleBuilder { - module -} -``` - -### `test.php` - -Let's make a test script. - -```php - Microsoft Visual C++'s `link.exe` is supported, however you may run into +> issues if your linker is not compatible with the linker used to compile PHP. + +We do this by creating a Cargo config file in `.cargo/config.toml` with the +following contents: + +```toml +{{#include ../../../.cargo/config.toml}} +``` + +## Writing our extension + +### `src/lib.rs` + +Let's actually write the extension code now. We start by importing the +`ext-php-rs` prelude, which contains most of the imports required to make a +basic extension. We will then write our basic `hello_world` function, which will +take a string argument for the callers name, and we will return another string. +Finally, we write a `get_module` function which is used by PHP to find out about +your module. The `#[php_module]` attribute automatically registers your new +function so we don't need to do anything except return the `ModuleBuilder` that +we were given. + +We also need to enable the `abi_vectorcall` feature when compiling for Windows. +This is a nightly-only feature so it is recommended to use the `#[cfg_attr]` +macro to not enable the feature on other operating systems. + +```rust,ignore +#![cfg_attr(windows, feature(abi_vectorcall))] +use ext_php_rs::prelude::*; + +#[php_function] +pub fn hello_world(name: &str) -> String { + format!("Hello, {}!", name) +} + +#[php_module] +pub fn get_module(module: ModuleBuilder) -> ModuleBuilder { + module +} +``` + +## Building the extension + +Now let's build our extension. +This is done through `cargo` like any other Rust crate. + +If you installed php using a package manager in the previous chapter +(or if the `php` and `php-config` binaries are already in your `$PATH`), +then you can just run + +```sh +cargo build +``` + +If you have multiple PHP versions in your PATH, or your installation +resides in a custom location, you can use the following environment variables: + +```sh +# explicitly specifies the path to the PHP executable: +export PHP=/path/to/php +# explicitly specifies the path to the php-config executable: +export PHP_CONFIG=/path/to/php-config +``` + +As an alternative, if you compiled PHP from source and installed it under +it's own prefix (`configure --prefix=/my/prefix`), you can just put +this prefix in front of your PATH: + +```sh +export PATH="/my/prefix:${PATH}" +``` + +Once you've setup these variables, you can just run + +```sh +cargo build +``` + +Cargo will track changes to these environment variables and rebuild the library accordingly. + +## Testing our extension + +The extension we just built is stored inside the cargo target directory: +`target/debug` if you did a debug build, `target/release` for release builds. + +The extension file name is OS-dependent. The naming works as follows: + +- let `S` be the empty string +- append to `S` the value of [std::env::consts::DLL_PREFIX](https://doc.rust-lang.org/std/env/consts/constant.DLL_PREFIX.html) + (empty on windows, `lib` on unixes) +- append to `S` the lower-snake-case version of your crate name +- append to `S` the value of [std::env::consts::DLL_SUFFIX](https://doc.rust-lang.org/std/env/consts/constant.DLL_SUFFIX.html) + (`.dll` on windows, `.dylib` on macOS, `.so` on other unixes). +- set the filename to the value of `S` + +Which in our case would give us: + +- linux: `libhello_world.so` +- macOS: `libhello_world.dylib` +- windows: `hello_world.dll` + +Now we need a way to tell the PHP CLI binary to load our extension. +There are [several ways to do that](https://www.phpinternalsbook.com/php7/build_system/building_extensions.html#loading-shared-extensions). +For now we'll simply pass the `-d extension=/path/to/extension` option to the PHP CLI binary. + +Let's make a test script: + +### `test.php` + +```php + Result { - let cmd = Command::new("php-config") + let cmd = Command::new(self.find_bin()?) .arg(arg) .output() .context("Failed to run `php-config`")?; @@ -20,6 +20,22 @@ impl Provider { } Ok(stdout.to_string()) } + + fn find_bin(&self) -> Result { + // If path is given via env, it takes priority. + if let Some(path) = path_from_env("PHP_CONFIG") { + if !path.try_exists()? { + // If path was explicitly given and it can't be found, this is a hard error + bail!("php-config executable not found at {:?}", path); + } + return Ok(path); + } + find_executable("php-config").with_context(|| { + "Could not find `php-config` executable. \ + Please ensure `php-config` is in your PATH or the \ + `PHP_CONFIG` environment variable is set." + }) + } } impl<'a> PHPProvider<'a> for Provider { From 6965f4a198982649c1c3766adc0a74d7c9610b0a Mon Sep 17 00:00:00 2001 From: Pierre Tondereau Date: Sun, 11 Dec 2022 22:10:25 +0100 Subject: [PATCH 34/34] Prepare v0.9.0 (#211) --- .github/actions/zts/Dockerfile | 2 +- CHANGELOG.md | 34 ++++++++++++++ Cargo.toml | 7 ++- build.rs | 2 + crates/macros/Cargo.toml | 2 +- docsrs_bindings.rs | 57 ++++++++++++++--------- guide/src/getting-started/installation.md | 2 +- src/builders/class.rs | 2 +- src/types/array.rs | 9 +--- 9 files changed, 78 insertions(+), 39 deletions(-) diff --git a/.github/actions/zts/Dockerfile b/.github/actions/zts/Dockerfile index 75c6ffc..1a87d08 100644 --- a/.github/actions/zts/Dockerfile +++ b/.github/actions/zts/Dockerfile @@ -1,4 +1,4 @@ -FROM php:zts +FROM php:8.1-zts WORKDIR /tmp diff --git a/CHANGELOG.md b/CHANGELOG.md index 047fad4..37c0a3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,39 @@ # Changelog +## Version 0.9.0 + +- ci+docs: honour PHP_CONFIG & rebuild automatically when env vars change by @julius [#210] +- chore: Update generated FFI bindings with bindgen 0.63 by @ptondereau [#211] + +**BC changes** +- feat: allows ZendStr to contain null bytes by @julius [#202] + +**Migration** +See: [#202] + +[#202]: https://github.com/davidcole1340/ext-php-rs/pull/202 +[#210]: https://github.com/davidcole1340/ext-php-rs/pull/210 +[#211]: https://github.com/davidcole1340/ext-php-rs/pull/211 + + +## Version 0.8.3 + +- build: Check docs warnings in CI by @davidcole1340 in [#180] +- fix: Fixes inifinte loop in ClassEntry::instance_of() by @ju1ius in [#188] +- fix: Fix binary slice lifetimes by @davidcole1340 in [#181] +- build: Fixes CI workflow configuration by @ju1ius in [#195] +- feat: Add get_id() and hash() methods on ZendObject by @ju1ius in [#196] +- docs: Describes restrictions on generic parameters for `php_class` by @ju1ius in [#194] +- feat: Add instance_of() and get_class_entry() methods on ZendObject by @ju1ius in [#197] + +[#180]: https://github.com/davidcole1340/ext-php-rs/pull/180 +[#188]: https://github.com/davidcole1340/ext-php-rs/pull/188 +[#181]: https://github.com/davidcole1340/ext-php-rs/pull/181 +[#195]: https://github.com/davidcole1340/ext-php-rs/pull/195 +[#196]: https://github.com/davidcole1340/ext-php-rs/pull/196 +[#194]: https://github.com/davidcole1340/ext-php-rs/pull/194 +[#197]: https://github.com/davidcole1340/ext-php-rs/pull/197 + ## Version 0.8.2 - Update changelog for latest versions by @striezel in [#161] diff --git a/Cargo.toml b/Cargo.toml index a0ca45e..8744093 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/davidcole1340/ext-php-rs" homepage = "https://github.com/davidcole1340/ext-php-rs" license = "MIT OR Apache-2.0" keywords = ["php", "ffi", "zend"] -version = "0.8.3" +version = "0.9.0" authors = ["David Cole "] edition = "2018" categories = ["api-bindings"] @@ -17,15 +17,14 @@ parking_lot = "0.12.1" cfg-if = "1.0" once_cell = "1.8.0" anyhow = { version = "1", optional = true } -ext-php-rs-derive = { version = "=0.8.2", path = "./crates/macros" } +ext-php-rs-derive = { version = "=0.9.0", path = "./crates/macros" } [dev-dependencies] skeptic = "0.13" [build-dependencies] anyhow = "1" -# bindgen = { version = "0.59" } -bindgen = "0.60" +bindgen = "0.63" cc = "1.0" skeptic = "0.13" diff --git a/build.rs b/build.rs index 8a06ba2..c2c5b9e 100644 --- a/build.rs +++ b/build.rs @@ -232,6 +232,8 @@ fn main() -> Result<()> { println!("cargo:rerun-if-env-changed={}", env_var); } + println!("cargo:rerun-if-changed=build.rs"); + // docs.rs runners only have PHP 7.4 - use pre-generated bindings if env::var("DOCS_RS").is_ok() { println!("cargo:warning=docs.rs detected - using stub bindings"); diff --git a/crates/macros/Cargo.toml b/crates/macros/Cargo.toml index 8137816..dadcb83 100644 --- a/crates/macros/Cargo.toml +++ b/crates/macros/Cargo.toml @@ -4,7 +4,7 @@ description = "Derive macros for ext-php-rs." repository = "https://github.com/davidcole1340/ext-php-rs" homepage = "https://github.com/davidcole1340/ext-php-rs" license = "MIT OR Apache-2.0" -version = "0.8.2" +version = "0.9.0" authors = ["David Cole "] edition = "2018" diff --git a/docsrs_bindings.rs b/docsrs_bindings.rs index 0971cc5..ff121e3 100644 --- a/docsrs_bindings.rs +++ b/docsrs_bindings.rs @@ -1,4 +1,4 @@ -/* automatically generated by rust-bindgen 0.60.1 */ +/* automatically generated by rust-bindgen 0.63.0 */ pub const ZEND_DEBUG: u32 = 1; pub const _ZEND_TYPE_NAME_BIT: u32 = 16777216; @@ -88,8 +88,11 @@ pub const CONST_CS: u32 = 0; pub const CONST_PERSISTENT: u32 = 1; pub const CONST_NO_FILE_CACHE: u32 = 2; pub const CONST_DEPRECATED: u32 = 4; -pub type __darwin_size_t = ::std::os::raw::c_ulong; -pub type size_t = __darwin_size_t; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct __sigset_t { + pub __val: [::std::os::raw::c_ulong; 16usize], +} pub type zend_long = i64; pub type zend_ulong = u64; pub type zend_uchar = ::std::os::raw::c_uchar; @@ -200,7 +203,7 @@ pub struct _zend_refcounted { pub struct _zend_string { pub gc: zend_refcounted_h, pub h: zend_ulong, - pub len: size_t, + pub len: usize, pub val: [::std::os::raw::c_char; 1usize], } #[repr(C)] @@ -282,7 +285,7 @@ pub struct _zend_ast_ref { } extern "C" { pub fn _emalloc( - size: size_t, + size: usize, __zend_filename: *const ::std::os::raw::c_char, __zend_lineno: u32, __zend_orig_filename: *const ::std::os::raw::c_char, @@ -299,12 +302,12 @@ extern "C" { ); } extern "C" { - pub fn __zend_malloc(len: size_t) -> *mut ::std::os::raw::c_void; + pub fn __zend_malloc(len: usize) -> *mut ::std::os::raw::c_void; } pub type zend_string_init_interned_func_t = ::std::option::Option< unsafe extern "C" fn( str_: *const ::std::os::raw::c_char, - size: size_t, + size: usize, permanent: bool, ) -> *mut zend_string, >; @@ -318,7 +321,7 @@ extern "C" { pub fn zend_hash_str_update( ht: *mut HashTable, key: *const ::std::os::raw::c_char, - len: size_t, + len: usize, pData: *mut zval, ) -> *mut zval; } @@ -333,7 +336,7 @@ extern "C" { pub fn zend_hash_str_del( ht: *mut HashTable, key: *const ::std::os::raw::c_char, - len: size_t, + len: usize, ) -> zend_result; } extern "C" { @@ -343,7 +346,7 @@ extern "C" { pub fn zend_hash_str_find( ht: *const HashTable, key: *const ::std::os::raw::c_char, - len: size_t, + len: usize, ) -> *mut zval; } extern "C" { @@ -546,7 +549,7 @@ pub struct _zend_class_entry { unsafe extern "C" fn( object: *mut zval, buffer: *mut *mut ::std::os::raw::c_uchar, - buf_len: *mut size_t, + buf_len: *mut usize, data: *mut zend_serialize_data, ) -> ::std::os::raw::c_int, >, @@ -555,7 +558,7 @@ pub struct _zend_class_entry { object: *mut zval, ce: *mut zend_class_entry, buf: *const ::std::os::raw::c_uchar, - buf_len: size_t, + buf_len: usize, data: *mut zend_unserialize_data, ) -> ::std::os::raw::c_int, >, @@ -980,7 +983,15 @@ pub struct _zend_execute_data { pub run_time_cache: *mut *mut ::std::os::raw::c_void, pub extra_named_params: *mut zend_array, } -pub type sigjmp_buf = [::std::os::raw::c_int; 49usize]; +pub type __jmp_buf = [::std::os::raw::c_long; 8usize]; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct __jmp_buf_tag { + pub __jmpbuf: __jmp_buf, + pub __mask_was_saved: ::std::os::raw::c_int, + pub __saved_mask: __sigset_t, +} +pub type jmp_buf = [__jmp_buf_tag; 1usize]; pub type zend_executor_globals = _zend_executor_globals; extern "C" { pub static mut executor_globals: zend_executor_globals; @@ -1041,7 +1052,7 @@ pub struct _zend_executor_globals { pub symtable_cache_ptr: *mut *mut zend_array, pub symbol_table: zend_array, pub included_files: HashTable, - pub bailout: *mut sigjmp_buf, + pub bailout: *mut jmp_buf, pub error_reporting: ::std::os::raw::c_int, pub exit_status: ::std::os::raw::c_int, pub function_table: *mut HashTable, @@ -1050,7 +1061,7 @@ pub struct _zend_executor_globals { pub vm_stack_top: *mut zval, pub vm_stack_end: *mut zval, pub vm_stack: zend_vm_stack, - pub vm_stack_page_size: size_t, + pub vm_stack_page_size: usize, pub current_execute_data: *mut _zend_execute_data, pub fake_scope: *mut zend_class_entry, pub jit_trace_num: u32, @@ -1147,7 +1158,7 @@ pub struct _zend_module_entry { >, pub info_func: ::std::option::Option, pub version: *const ::std::os::raw::c_char, - pub globals_size: size_t, + pub globals_size: usize, pub globals_ptr: *mut ::std::os::raw::c_void, pub globals_ctor: ::std::option::Option, @@ -1209,7 +1220,7 @@ extern "C" { pub fn zend_declare_property( ce: *mut zend_class_entry, name: *const ::std::os::raw::c_char, - name_length: size_t, + name_length: usize, property: *mut zval, access_type: ::std::os::raw::c_int, ); @@ -1218,7 +1229,7 @@ extern "C" { pub fn zend_declare_class_constant( ce: *mut zend_class_entry, name: *const ::std::os::raw::c_char, - name_length: size_t, + name_length: usize, value: *mut zval, ); } @@ -1284,7 +1295,7 @@ extern "C" { pub fn zend_wrong_parameters_count_error(min_num_args: u32, max_num_args: u32); } extern "C" { - pub fn php_printf(format: *const ::std::os::raw::c_char, ...) -> size_t; + pub fn php_printf(format: *const ::std::os::raw::c_char, ...) -> usize; } #[repr(C)] #[derive(Debug, Copy, Clone)] @@ -1316,7 +1327,7 @@ pub struct _zend_ini_entry { extern "C" { pub fn zend_register_bool_constant( name: *const ::std::os::raw::c_char, - name_len: size_t, + name_len: usize, bval: bool, flags: ::std::os::raw::c_int, module_number: ::std::os::raw::c_int, @@ -1325,7 +1336,7 @@ extern "C" { extern "C" { pub fn zend_register_long_constant( name: *const ::std::os::raw::c_char, - name_len: size_t, + name_len: usize, lval: zend_long, flags: ::std::os::raw::c_int, module_number: ::std::os::raw::c_int, @@ -1334,7 +1345,7 @@ extern "C" { extern "C" { pub fn zend_register_double_constant( name: *const ::std::os::raw::c_char, - name_len: size_t, + name_len: usize, dval: f64, flags: ::std::os::raw::c_int, module_number: ::std::os::raw::c_int, @@ -1343,7 +1354,7 @@ extern "C" { extern "C" { pub fn zend_register_string_constant( name: *const ::std::os::raw::c_char, - name_len: size_t, + name_len: usize, strval: *const ::std::os::raw::c_char, flags: ::std::os::raw::c_int, module_number: ::std::os::raw::c_int, diff --git a/guide/src/getting-started/installation.md b/guide/src/getting-started/installation.md index f3089f6..d286c34 100644 --- a/guide/src/getting-started/installation.md +++ b/guide/src/getting-started/installation.md @@ -52,7 +52,7 @@ cd php-src git checkout PHP-8.1 ./buildconf PREFIX="${HOME}/build/php" -.configure --prefix="${PREFIX}" \ +./configure --prefix="${PREFIX}" \ --enable-debug \ --disable-all --disable-cgi make -j "$(nproc)" diff --git a/src/builders/class.rs b/src/builders/class.rs index b249506..7609445 100644 --- a/src/builders/class.rs +++ b/src/builders/class.rs @@ -284,7 +284,7 @@ impl ClassBuilder { zend_declare_class_constant( class, CString::new(name.as_str())?.as_ptr(), - name.len() as u64, + name.len(), value, ) }; diff --git a/src/types/array.rs b/src/types/array.rs index d39105e..cfd99cd 100644 --- a/src/types/array.rs +++ b/src/types/array.rs @@ -312,14 +312,7 @@ impl ZendHashTable { V: IntoZval, { let mut val = val.into_zval(false)?; - unsafe { - zend_hash_str_update( - self, - CString::new(key)?.as_ptr(), - key.len() as u64, - &mut val, - ) - }; + unsafe { zend_hash_str_update(self, CString::new(key)?.as_ptr(), key.len(), &mut val) }; val.release(); Ok(()) }