From 3d252c791be2cefa4a6f237a7bf0cfdd76556182 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Sun, 1 Nov 2026 13:45:05 -0800 Subject: [PATCH 1/3] More docs --- src/extensions/hash_table_ext.rs | 17 ++++ src/extensions/zend_arg_info_ext.rs | 21 +++++ src/extensions/zend_attribute_ext.rs | 2 + src/extensions/zend_class_constant_ext.rs | 4 + src/extensions/zend_class_entry_ext.rs | 6 ++ src/extensions/zend_constant_ext.rs | 8 ++ src/extensions/zend_extension_ext.rs | 10 +++ src/extensions/zend_function_ext.rs | 10 +++ src/extensions/zend_module_dep_ext.rs | 4 + src/extensions/zend_module_entry_ext.rs | 4 + src/extensions/zend_string_ext.rs | 4 + src/extensions/zend_type_ext.rs | 94 +++++++++++++++++++++++ src/extensions/zend_type_list_ext.rs | 2 + src/extensions/zval_ext.rs | 3 + src/stack_types/stack_smart_str.rs | 10 +-- src/stack_types/stack_zend_string.rs | 10 +-- src/stack_types/stack_zval.rs | 10 +-- src/wrappers/info_table_printer.rs | 1 + 18 files changed, 205 insertions(+), 15 deletions(-) diff --git a/src/extensions/hash_table_ext.rs b/src/extensions/hash_table_ext.rs index 9216592..d2d23dc 100644 --- a/src/extensions/hash_table_ext.rs +++ b/src/extensions/hash_table_ext.rs @@ -5,12 +5,29 @@ use rsphp_sys::{ }; use std::ptr::NonNull; +/// Extension trait for [`rsphp_sys::HashTable`]. pub trait HashTableExt { + /// Does the table contain a value with the given string key? + /// + /// Wrapper around [`rsphp_sys::zend_hash_exists()`]. fn contains(&self, key: &mut zend_string) -> bool; + + /// Insert the given value to the next numerical index, and return the + /// resulting pointer. + /// + /// Wrapper around [`rsphp_sys::zend_hash_next_index_insert()`]. fn next_index_insert(&mut self, value: T) -> *mut zval where T: Into; + + /// Try to find the value in the table with the given string key. + /// + /// Wrapper around [`rsphp_sys::zend_hash_find()`]. fn find(&self, key: &mut zend_string) -> Option>; + + /// Check the number of elements currently in the table. + /// + /// This is equivalent to [`rsphp_sys::zend_hash_num_elements()`]. fn num_elements(&self) -> u32; } diff --git a/src/extensions/zend_arg_info_ext.rs b/src/extensions/zend_arg_info_ext.rs index 309f079..783efdf 100644 --- a/src/extensions/zend_arg_info_ext.rs +++ b/src/extensions/zend_arg_info_ext.rs @@ -5,10 +5,31 @@ use rsphp_sys::{ zend_arg_info, }; +/// Extension trait for [`rsphp_sys::zend_arg_info`]. pub trait ZendArgInfoExt { + /// Get the "send" mode for the argument. + /// + /// The result should be one of + /// - [`rsphp_sys::ZEND_SEND_BY_VAL`] + /// - [`rsphp_sys::ZEND_SEND_BY_REF`] + /// - [`rsphp_sys::ZEND_SEND_PREFER_REF`] + /// + /// This is equivalent to [`rsphp_macros_c::ZEND_ARG_SEND_MODE!()`]. fn send_mode(&self) -> u8; + + /// Check if an argument is marked as variadic. + /// + /// This is equivalent to [`rsphp_macros_c::ZEND_ARG_IS_VARIADIC!()`]. fn is_variadic(&self) -> bool; + + /// Check if an argument is marked as promoted. + /// + /// This is equivalent to [`rsphp_macros_c::ZEND_ARG_IS_PROMOTED!()`]. fn is_promoted(&self) -> bool; + + /// Check if an argument is marked as having a tentative type. + /// + /// This is equivalent to [`rsphp_macros_c::ZEND_ARG_TYPE_IS_TENTATIVE!()`]. fn is_tentative(&self) -> bool; } diff --git a/src/extensions/zend_attribute_ext.rs b/src/extensions/zend_attribute_ext.rs index 357a040..3cd6516 100644 --- a/src/extensions/zend_attribute_ext.rs +++ b/src/extensions/zend_attribute_ext.rs @@ -2,7 +2,9 @@ use rsphp_sys::{zend_attribute, zend_attribute_arg}; use std::ops::Range; +/// Extension trait for [`rsphp_sys::zend_attribute`]. pub trait ZendAttributeExt { + /// Get an iterator over the arguments of the attribute. fn args(&self) -> ZendAttributeArgIter<'_>; } diff --git a/src/extensions/zend_class_constant_ext.rs b/src/extensions/zend_class_constant_ext.rs index 8e06e38..6f676d2 100644 --- a/src/extensions/zend_class_constant_ext.rs +++ b/src/extensions/zend_class_constant_ext.rs @@ -1,7 +1,11 @@ //! Extension trait for [`rsphp_sys::zend_class_constant`]. use rsphp_sys::zend_class_constant; +/// Extension trait for [`rsphp_sys::zend_class_constant`]. pub trait ZendClassConstantExt { + /// Get the flags of the constant. + /// + /// This is equivalent to [`rsphp_macros_c::ZEND_CLASS_CONST_FLAGS!()`]. fn get_flags(&self) -> u32; } diff --git a/src/extensions/zend_class_entry_ext.rs b/src/extensions/zend_class_entry_ext.rs index 20ffe79..25dc805 100644 --- a/src/extensions/zend_class_entry_ext.rs +++ b/src/extensions/zend_class_entry_ext.rs @@ -2,8 +2,14 @@ use rsphp_sys::{instanceof_function, zend_class_entry, zend_class_name}; use std::ops::Range; +/// Extension trait for [`rsphp_sys::zend_class_entry`]. pub trait ZendClassEntryExt { + /// Get an iterator over the names of the traits used by this class entry. fn get_trait_names(&self) -> ZendClassEntryTraitNamesIter<'_>; + + /// Check if this class is an instance of some other class. + /// + /// Wrapper around [`rsphp_sys::instanceof_function()`]. fn instance_of(&self, other: &zend_class_entry) -> bool; } diff --git a/src/extensions/zend_constant_ext.rs b/src/extensions/zend_constant_ext.rs index c402e1b..d2d3985 100644 --- a/src/extensions/zend_constant_ext.rs +++ b/src/extensions/zend_constant_ext.rs @@ -1,8 +1,16 @@ //! Extension trait for [`rsphp_sys::zend_constant`]. use rsphp_sys::zend_constant; +/// Extension trait for [`rsphp_sys::zend_constant`]. pub trait ZendConstantExt { + /// Get the flags of the constant. + /// + /// This is equivalent to [`rsphp_macros_c::ZEND_CONSTANT_FLAGS!()`]. fn get_flags(&self) -> u32; + + /// Get the module number of the constant. + /// + /// This is equivalent to [`rsphp_macros_c::ZEND_CONSTANT_MODULE_NUMBER!()`]. fn get_module_number(&self) -> u32; } diff --git a/src/extensions/zend_extension_ext.rs b/src/extensions/zend_extension_ext.rs index c23e326..1d4ef37 100644 --- a/src/extensions/zend_extension_ext.rs +++ b/src/extensions/zend_extension_ext.rs @@ -2,11 +2,21 @@ use rsphp_sys::zend_extension; use std::ffi::CStr; +/// Extension trait for [`rsphp_sys::zend_extension`]. pub trait ZendExtensionExt { + /// Get the name of the extension. fn get_name(&self) -> &str; + + /// Get the version of the extension, if set. fn get_version(&self) -> Option<&str>; + + /// Get the author of the extension, if set. fn get_author(&self) -> Option<&str>; + + /// Get the URL of the extension, if set. fn get_url(&self) -> Option<&str>; + + /// Get the copyright information of the extension, if set. fn get_copyright(&self) -> Option<&str>; } diff --git a/src/extensions/zend_function_ext.rs b/src/extensions/zend_function_ext.rs index 85f784f..414700e 100644 --- a/src/extensions/zend_function_ext.rs +++ b/src/extensions/zend_function_ext.rs @@ -1,8 +1,18 @@ //! Extension trait for [`rsphp_sys::zend_function`]. use rsphp_sys::{_zend_function__common, zend_function}; +/// Extension trait for [`rsphp_sys::zend_function`]. pub trait ZendFunctionExt { + /// Get the type of the function. + /// + /// The result should be one of + /// - [`rsphp_sys::ZEND_INTERNAL_FUNCTION`] + /// - [`rsphp_sys::ZEND_USER_FUNCTION`] + /// - [`rsphp_sys::ZEND_EVAL_CODE`] fn get_type(&self) -> u8; + + /// Safe access for the common information shared by internal and userland + /// functions. fn get_common(&self) -> &_zend_function__common; } diff --git a/src/extensions/zend_module_dep_ext.rs b/src/extensions/zend_module_dep_ext.rs index 46d1789..b477bcb 100644 --- a/src/extensions/zend_module_dep_ext.rs +++ b/src/extensions/zend_module_dep_ext.rs @@ -2,8 +2,12 @@ use rsphp_sys::zend_module_dep; use std::ffi::CStr; +/// Extension trait for [`rsphp_sys::zend_module_dep`]. pub trait ZendModuleDepExt { + /// Get the relationship constraint of the dependency, if set. fn get_relation(&self) -> Option<&str>; + + /// Get the version for the relationship constraint of the dependency, if set. fn get_version(&self) -> Option<&str>; } diff --git a/src/extensions/zend_module_entry_ext.rs b/src/extensions/zend_module_entry_ext.rs index faf105d..8bcde16 100644 --- a/src/extensions/zend_module_entry_ext.rs +++ b/src/extensions/zend_module_entry_ext.rs @@ -2,8 +2,12 @@ use rsphp_sys::zend_module_entry; use std::ffi::CStr; +/// Extension trait for [`rsphp_sys::zend_module_entry`]. pub trait ZendModuleEntryExt { + /// Get the name of the module entry. fn get_name(&self) -> &str; + + /// Get the version of the module entry, if set. fn get_version(&self) -> Option<&str>; } diff --git a/src/extensions/zend_string_ext.rs b/src/extensions/zend_string_ext.rs index 1b9d9f5..3e4e285 100644 --- a/src/extensions/zend_string_ext.rs +++ b/src/extensions/zend_string_ext.rs @@ -2,9 +2,13 @@ use rsphp_macros_c::{ZSTR_LEN, ZSTR_VAL}; use rsphp_sys::zend_string; +/// Extension trait for [`rsphp_sys::zend_string`]. pub trait ZendStringExt { + /// Read the string as a slice. fn as_slice(&self) -> &[u8]; + #[cfg(feature = "bstr")] + /// Read the string as a [`bstr::BStr`]. fn as_bstr(&self) -> &bstr::BStr { bstr::BStr::new(self.as_slice()) } diff --git a/src/extensions/zend_type_ext.rs b/src/extensions/zend_type_ext.rs index f963e63..b1db96d 100644 --- a/src/extensions/zend_type_ext.rs +++ b/src/extensions/zend_type_ext.rs @@ -7,30 +7,124 @@ use rsphp_sys::{ }; use std::ffi::{c_char, c_void}; +/// Extension trait for [`rsphp_sys::zend_type`]. pub trait ZendTypeExt { + /// Create a new type for the given mask. + /// + /// This is equivalent to [`rsphp_macros_c::ZEND_TYPE_INIT_MASK!()`]. fn init_mask(mask: u32) -> Self; + + /// Create a new type for the given code and options. + /// + /// This is equivalent to [`rsphp_macros_c::ZEND_TYPE_INIT_CODE!()`]. fn init_code(code: u8, allow_null: bool, extra_flags: u32) -> Self; + + /// Create a new type for the given opaque pointer and given options. + /// + /// This is equivalent to [`rsphp_macros_c::ZEND_TYPE_INIT_PTR!()`]. fn init_ptr(ptr: *mut c_void, type_kind: u32, allow_null: bool, extra_flags: u32) -> Self; + + /// Create a new type as a union of the given type list, with the given options. + /// + /// This is equivalent to [`rsphp_macros_c::ZEND_TYPE_INIT_UNION!()`]. fn init_union(ptr: *mut zend_type_list, extra_flags: u32) -> Self; + + /// Create a new type as an intersection of the given type list, with the given options. + /// + /// This is equivalent to [`rsphp_macros_c::ZEND_TYPE_INIT_INTERSECTION!()`]. fn init_intersection(ptr: *mut zend_type_list, extra_flags: u32) -> Self; + + /// Create a new type representing the exact class whose name was provided, with + /// the given options. + /// + /// This is equivalent to [`rsphp_macros_c::ZEND_TYPE_INIT_CLASS!()`]. fn init_class(ptr: *mut zend_string, allow_null: bool, extra_flags: u32) -> Self; + /// Check if a type is set. + /// + /// This is equivalent to [`rsphp_macros_c::ZEND_TYPE_IS_SET!()`]. fn is_set(&self) -> bool; + + /// Check if a type is complex, i.e. a list with a union or intersection, or + /// a pointer to a class name. + /// + /// This is equivalent to [`rsphp_macros_c::ZEND_TYPE_IS_COMPLEX!()`]. fn is_complex(&self) -> bool; + + /// Check if a type has a [`rsphp_sys::zend_string`] pointer to a name. + /// + /// This is equivalent to [`rsphp_macros_c::ZEND_TYPE_HAS_NAME!()`]. fn has_name(&self) -> bool; + + /// Check if a type has a [`std::ffi::c_char`] pointer to a name. + /// + /// This is equivalent to [`rsphp_macros_c::ZEND_TYPE_HAS_LITERAL_NAME!()`]. fn has_literal_name(&self) -> bool; + + /// Check if a type has an inner list of types. + /// + /// This is equivalent to [`rsphp_macros_c::ZEND_TYPE_HAS_LIST!()`]. fn has_list(&self) -> bool; + + /// Check if a type is the iterator fallback. + /// + /// This is equivalent to [`rsphp_macros_c::ZEND_TYPE_IS_ITERABLE_FALLBACK!()`]. fn is_iterable_fallback(&self) -> bool; + + /// Check if a type is an intersection. + /// + /// This is equivalent to [`rsphp_macros_c::ZEND_TYPE_IS_INTERSECTION!()`]. fn is_intersection(&self) -> bool; + + /// Check if a type is a union. + /// + /// This is equivalent to [`rsphp_macros_c::ZEND_TYPE_IS_UNION!()`]. fn is_union(&self) -> bool; + + /// Check if a type is arena allocated. + /// + /// This is equivalent to [`rsphp_macros_c::ZEND_TYPE_USES_ARENA!()`]. fn uses_arena(&self) -> bool; + + /// Check if a has a type mask that only refers to standard types. + /// + /// This is equivalent to [`rsphp_macros_c::ZEND_TYPE_IS_ONLY_MASK!()`]. fn is_only_mask(&self) -> bool; + + /// Get the name of the type as a [`rsphp_sys::zend_string`] pointer to a name. + /// + /// This is equivalent to [`rsphp_macros_c::ZEND_TYPE_NAME!()`]. fn get_name(&mut self) -> *mut zend_string; + + /// Get the name of the type as a [`std::ffi::c_char`] pointer to a name. + /// + /// This is equivalent to [`rsphp_macros_c::ZEND_TYPE_LITERAL_NAME!()`]. fn get_literal_name(&self) -> *const c_char; + + /// Get the list of the type's inner types as a [`rsphp_sys::zend_type_list`] reference. + /// + /// This is equivalent to [`rsphp_macros_c::ZEND_TYPE_LIST!()`]. fn get_list(&self) -> &zend_type_list; + + /// Get the full mask of the type. + /// + /// This is equivalent to [`rsphp_macros_c::ZEND_TYPE_FULL_MASK!()`]. fn full_mask(&self) -> u32; + + /// Get the pure mask of the type, i.e. just the `MAY_BE_*` flags. + /// + /// This is equivalent to [`rsphp_macros_c::ZEND_TYPE_PURE_MASK!()`]. fn pure_mask(&self) -> u32; + + /// Get the pure mask of the type, i.e. just the `MAY_BE_*` flags, excluding + /// [`rsphp_sys::_ZEND_TYPE_NULLABLE_BIT`]. + /// + /// This is equivalent to [`rsphp_macros_c::ZEND_TYPE_PURE_MASK_WITHOUT_NULL!()`]. fn pure_mask_without_null(&self) -> u32; + + /// Check if this type is satisfied by `null`. + /// + /// This is eqivalent to [`rsphp_macros_c::ZEND_TYPE_ALLOW_NULL!()`]. fn allows_null(&self) -> bool; } diff --git a/src/extensions/zend_type_list_ext.rs b/src/extensions/zend_type_list_ext.rs index 908220e..cc4fd67 100644 --- a/src/extensions/zend_type_list_ext.rs +++ b/src/extensions/zend_type_list_ext.rs @@ -3,7 +3,9 @@ use rsphp_sys::{zend_type, zend_type_list}; use std::ops::Range; // Cannot implement `Iterator` directly on `zend_type_list` so add a method instead +/// Extension trait for [`rsphp_sys::zend_type_list`]. pub trait ZendTypeListExt { + /// Get an iterator over the types of the type list. fn iter(&self) -> ZendTypeListIter<'_>; } diff --git a/src/extensions/zval_ext.rs b/src/extensions/zval_ext.rs index 62f7b0b..b5b33ab 100644 --- a/src/extensions/zval_ext.rs +++ b/src/extensions/zval_ext.rs @@ -2,7 +2,10 @@ use crate::PHPValue; use rsphp_sys::zval; +/// Extension trait for [`rsphp_sys::zval`]. pub trait ZvalExt { + /// A safe wrapper for [`PHPValue::apply_to()`] to apply a value to the + /// current zval, overwriting any existing value. fn set_value(&mut self, value: T) where T: Into; diff --git a/src/stack_types/stack_smart_str.rs b/src/stack_types/stack_smart_str.rs index 8c43fc2..88b188a 100644 --- a/src/stack_types/stack_smart_str.rs +++ b/src/stack_types/stack_smart_str.rs @@ -4,11 +4,6 @@ use rsphp_sys::{ }; use std::ops::Deref; -pub struct StackSmartStr { - /// The wrapped smart string. - inner: smart_str, -} - /// Rust handling of stack-allocated [`rsphp_sys::smart_str`] values. /// /// Can be dereferenced into the underlying `smart_str`. @@ -18,6 +13,11 @@ pub struct StackSmartStr { /// # Panics /// /// Panics when extracting a string that is not valid UTF8. +pub struct StackSmartStr { + /// The wrapped smart string. + inner: smart_str, +} + #[expect(clippy::new_without_default, reason = "Default is not appropriate")] impl StackSmartStr { /// Create a new empty [`rsphp_sys::smart_str`]. diff --git a/src/stack_types/stack_zend_string.rs b/src/stack_types/stack_zend_string.rs index 08ac0d5..1fa1f60 100644 --- a/src/stack_types/stack_zend_string.rs +++ b/src/stack_types/stack_zend_string.rs @@ -4,16 +4,16 @@ use rsphp_sys::{zend_string, zend_string_init, zend_string_release_ex}; use std::ffi::c_char; use std::ops::Deref; -pub struct StackZendString { - /// The wrapped string. - inner: *mut zend_string, -} - /// Rust handling of Rust-tracked [`rsphp_sys::zend_string`] pointer. /// /// Can be dereferenced into the underlying `zend_string`. /// /// Calls [`rsphp_sys::zend_string_release_ex`] automatically on drop. +pub struct StackZendString { + /// The wrapped string. + inner: *mut zend_string, +} + impl StackZendString { /// Create a new [`rsphp_sys::zend_string`] for the given `&str`. #[must_use] diff --git a/src/stack_types/stack_zval.rs b/src/stack_types/stack_zval.rs index 016b783..59c5ab4 100644 --- a/src/stack_types/stack_zval.rs +++ b/src/stack_types/stack_zval.rs @@ -2,6 +2,11 @@ use rsphp_sys::{zval, zval_ptr_dtor, zval_ptr_dtor_nogc}; use std::convert::AsRef; use std::ops::Deref; +/// Rust handling of stack-allocated [`rsphp_sys::zval`] values. +/// +/// Can be dereferenced into the underlying zval. +/// +/// Calls [`rsphp_sys::zval_ptr_dtor`] automatically on drop. pub struct StackZval { /// The wrapped zval. inner: zval, @@ -10,11 +15,6 @@ pub struct StackZval { gc: bool, } -/// Rust handling of stack-allocated [`rsphp_sys::zval`] values. -/// -/// Can be dereferenced into the underlying zval. -/// -/// Calls [`rsphp_sys::zval_ptr_dtor`] automatically on drop. #[expect(clippy::new_without_default, reason = "Default is not appropriate")] impl StackZval { /// Create a new uninitialized [`rsphp_sys::zval`]. diff --git a/src/wrappers/info_table_printer.rs b/src/wrappers/info_table_printer.rs index 5342178..ff26eac 100644 --- a/src/wrappers/info_table_printer.rs +++ b/src/wrappers/info_table_printer.rs @@ -2,6 +2,7 @@ use rsphp_sys::{php_info_print_table_end, php_info_print_table_row, php_info_pri use std::ffi::CString; +/// A utility for printing out extension information tables. pub struct InfoTablePrinter { /// If nothing is printed then we don't need to open and close the table. have_printed: bool, From 88bf4f58faf19b9f204c9f93a3d1461f1a4c27c6 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Sun, 1 Nov 2026 14:45:58 -0800 Subject: [PATCH 2/3] Actually run lints for main rsphp crate --- Cargo.lock | 1 - Cargo.toml | 5 ++-- crates/macros-c/src/zend_hash.rs | 14 +++++---- src/extensions/mod.rs | 6 ++-- src/extensions/zend_attribute_ext.rs | 11 ++++++++ src/extensions/zend_class_entry_ext.rs | 11 ++++++++ src/extensions/zend_type_list_ext.rs | 1 + src/lib.rs | 2 +- src/php_exceptions.rs | 14 +++++++-- src/php_object.rs | 8 +++++- src/php_value.rs | 39 +++++++++++++++++++++----- src/stack_types/stack_smart_str.rs | 10 +++++++ src/stack_types/stack_zend_string.rs | 1 + src/stack_types/stack_zval.rs | 14 ++++++++- src/wrappers/hash.rs | 13 +++++---- src/wrappers/info_table_printer.rs | 30 +++++++++++++++++--- 16 files changed, 147 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2b3d59f..2e2b535 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -446,7 +446,6 @@ version = "0.1.2" dependencies = [ "bstr", "rsphp-macros-c", - "rsphp-macros-rs", "rsphp-sys", ] diff --git a/Cargo.toml b/Cargo.toml index 0e7e800..de6f729 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,9 @@ repository.workspace = true rust-version.workspace = true version.workspace = true +[lints] +workspace = true + [package.metadata.docs.rs] # Show documentation for private items rustdoc-args = ["--document-private-items"] @@ -195,7 +198,6 @@ clap = { version = "4.6.7", features = ["derive"] } diff = "0.1.13" php-ast = "0.26.0" php-rs-parser = "0.26.0" -proc-macro2 = "1.0.107" regex = "1.13.1" rsphp-build-tools = { version = "0.1.2", path = "./crates/build-tools" } rsphp-macros-c = { version = "0.1.2", path = "./crates/macros-c" } @@ -209,7 +211,6 @@ walkdir = "2.5.0" [dependencies] bstr = { workspace = true, optional = true } rsphp-macros-c = { workspace = true } -rsphp-macros-rs = { workspace = true } rsphp-sys = { workspace = true } [features] diff --git a/crates/macros-c/src/zend_hash.rs b/crates/macros-c/src/zend_hash.rs index 2dc85ce..4c7634c 100644 --- a/crates/macros-c/src/zend_hash.rs +++ b/crates/macros-c/src/zend_hash.rs @@ -56,11 +56,15 @@ macro_rules! ZVAL_EMPTY_ARRAY { #[macro_export] macro_rules! ZEND_HASH_ELEMENT_SIZE { ($ht:expr) => { - std::mem::size_of::() - + ((!$crate::HT_FLAGS!($ht) & rsphp_sys::HASH_FLAG_PACKED) as usize) - * ((std::mem::size_of::() - - std::mem::size_of::()) - / (rsphp_sys::HASH_FLAG_PACKED as usize)) + #[expect(clippy::integer_division, reason = "Matches C version")] + // Need a block so that attribute can be applied + { + std::mem::size_of::() + + ((!$crate::HT_FLAGS!($ht) & rsphp_sys::HASH_FLAG_PACKED) as usize) + * ((std::mem::size_of::() + - std::mem::size_of::()) + / (rsphp_sys::HASH_FLAG_PACKED as usize)) + } }; } diff --git a/src/extensions/mod.rs b/src/extensions/mod.rs index 1f73f00..5ac5dfd 100644 --- a/src/extensions/mod.rs +++ b/src/extensions/mod.rs @@ -5,7 +5,7 @@ mod hash_table_ext; pub use hash_table_ext::HashTableExt; mod zend_attribute_ext; -pub use zend_attribute_ext::ZendAttributeExt; +pub use zend_attribute_ext::{ZendAttributeArgIter, ZendAttributeExt}; mod zend_arg_info_ext; pub use zend_arg_info_ext::ZendArgInfoExt; @@ -14,7 +14,7 @@ mod zend_class_constant_ext; pub use zend_class_constant_ext::ZendClassConstantExt; mod zend_class_entry_ext; -pub use zend_class_entry_ext::ZendClassEntryExt; +pub use zend_class_entry_ext::{ZendClassEntryExt, ZendClassEntryTraitNamesIter}; mod zend_constant_ext; pub use zend_constant_ext::ZendConstantExt; @@ -38,7 +38,7 @@ mod zend_type_ext; pub use zend_type_ext::ZendTypeExt; mod zend_type_list_ext; -pub use zend_type_list_ext::ZendTypeListExt; +pub use zend_type_list_ext::{ZendTypeListExt, ZendTypeListIter}; mod zval_ext; pub use zval_ext::ZvalExt; diff --git a/src/extensions/zend_attribute_ext.rs b/src/extensions/zend_attribute_ext.rs index 3cd6516..ce30dbe 100644 --- a/src/extensions/zend_attribute_ext.rs +++ b/src/extensions/zend_attribute_ext.rs @@ -1,5 +1,6 @@ //! Extension trait for [`rsphp_sys::zend_attribute`]. use rsphp_sys::{zend_attribute, zend_attribute_arg}; +use std::fmt; use std::ops::Range; /// Extension trait for [`rsphp_sys::zend_attribute`]. @@ -43,3 +44,13 @@ impl Iterator for ZendAttributeArgIter<'_> { self.range.size_hint() } } + +impl fmt::Debug for ZendAttributeArgIter<'_> { + #[expect(clippy::pointer_format, reason = "No better options")] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ZendAttributeArgIter") + .field("raw", &format!("{:p}", std::ptr::from_ref(self.raw))) + .field("range", &self.range) + .finish() + } +} diff --git a/src/extensions/zend_class_entry_ext.rs b/src/extensions/zend_class_entry_ext.rs index 25dc805..9a76f81 100644 --- a/src/extensions/zend_class_entry_ext.rs +++ b/src/extensions/zend_class_entry_ext.rs @@ -1,5 +1,6 @@ //! Extension trait for [`rsphp_sys::zend_class_entry`]. use rsphp_sys::{instanceof_function, zend_class_entry, zend_class_name}; +use std::fmt; use std::ops::Range; /// Extension trait for [`rsphp_sys::zend_class_entry`]. @@ -65,3 +66,13 @@ impl Iterator for ZendClassEntryTraitNamesIter<'_> { self.range.size_hint() } } + +impl fmt::Debug for ZendClassEntryTraitNamesIter<'_> { + #[expect(clippy::pointer_format, reason = "No better options")] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ZendClassEntryTraitNamesIter") + .field("raw", &format!("{:p}", std::ptr::from_ref(self.raw))) + .field("range", &self.range) + .finish() + } +} diff --git a/src/extensions/zend_type_list_ext.rs b/src/extensions/zend_type_list_ext.rs index cc4fd67..3a7843d 100644 --- a/src/extensions/zend_type_list_ext.rs +++ b/src/extensions/zend_type_list_ext.rs @@ -19,6 +19,7 @@ impl ZendTypeListExt for &zend_type_list { } /// Iterator over the types in a [`rsphp_sys::zend_type_list`]. +#[derive(Debug)] pub struct ZendTypeListIter<'list> { /// The target [`rsphp_sys::zend_type_list`]. raw: &'list zend_type_list, diff --git a/src/lib.rs b/src/lib.rs index 1d795a0..425325f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,7 +6,7 @@ pub use php_object::PHPObject; /// Module for `PHPValue` type and related. mod php_value; -pub use php_value::PHPValue; +pub use php_value::{PHPStringWrapper, PHPValue}; /// Implementation of `PHPExceptions`. mod php_exceptions; diff --git a/src/php_exceptions.rs b/src/php_exceptions.rs index 313f6b0..cab15f6 100644 --- a/src/php_exceptions.rs +++ b/src/php_exceptions.rs @@ -2,11 +2,14 @@ use crate::stack_types::StackZendString; use rsphp_macros_c::{CG, EG, Z_OBJ, ZSTR_KNOWN, ZVAL_LONG, ZVAL_STR}; use rsphp_sys::{ _zend_known_string_id::ZEND_STR_CODE, _zend_known_string_id::ZEND_STR_MESSAGE, E_ERROR, - instanceof_function, object_init_ex, zend_ce_error, zend_ce_throwable, zend_class_entry, - zend_error_noreturn, zend_throw_exception_internal, zend_update_property_ex, zval, + ZEND_RESULT_CODE, instanceof_function, object_init_ex, zend_ce_error, zend_ce_throwable, + zend_class_entry, zend_error_noreturn, zend_throw_exception_internal, zend_update_property_ex, + zval, }; /// Method holder for methods relating to throwing exceptions and errors. +#[derive(Copy, Clone, Debug)] +#[non_exhaustive] pub struct PHPExceptions; impl PHPExceptions { @@ -66,7 +69,12 @@ impl PHPExceptions { let mut exception_zv: zval = unsafe { std::mem::zeroed() }; // SAFETY: object_init_ex() is given a valid class entry and a valid // zval pointer - unsafe { object_init_ex(&raw mut exception_zv, ce) }; + let result = unsafe { object_init_ex(&raw mut exception_zv, ce) }; + assert_eq!( + ZEND_RESULT_CODE::SUCCESS, + result, + "Exception object initialization must succeed" + ); { // SAFETY: zvals can be initialized with zero bytes diff --git a/src/php_object.rs b/src/php_object.rs index d79dd5d..961cd41 100644 --- a/src/php_object.rs +++ b/src/php_object.rs @@ -1,10 +1,14 @@ use rsphp_macros_c::Z_OBJ; use rsphp_sys::{ZEND_RESULT_CODE, object_init_ex, zend_class_entry, zend_object, zval}; -use std::convert::{AsMut, AsRef}; use std::ptr::NonNull; /// Wrapper around a [`rsphp_sys::zend_object`] that is known to exist and /// be valid. +#[expect( + missing_copy_implementations, + reason = "Copies should not be made without considering reference counting" +)] +#[derive(Debug)] pub struct PHPObject { /// The wrapped object. ptr: NonNull, @@ -108,6 +112,7 @@ impl PHPObject { } } + /// Get the class entry for this object, if possible. #[must_use] pub fn get_class(&self) -> Option<&zend_class_entry> { // SAFETY: pointer must be valid @@ -116,6 +121,7 @@ impl PHPObject { unsafe { obj_ref.ce.as_ref() } } + /// Get a mutable pointer to the object, for passing to FFI. #[must_use] pub fn ptr(&self) -> *mut zend_object { self.ptr.as_ptr() diff --git a/src/php_value.rs b/src/php_value.rs index a0949d7..63ea9df 100644 --- a/src/php_value.rs +++ b/src/php_value.rs @@ -4,6 +4,7 @@ use rsphp_macros_c::{ }; use rsphp_sys::{zend_long, zend_string, zval}; use std::ffi::{CString, c_char}; +use std::fmt; use crate::PHPObject; @@ -26,14 +27,38 @@ enum PHPStringSource { BString(bstr::BString), } +impl fmt::Debug for PHPStringSource { + #[expect(clippy::pointer_format, reason = "No better options")] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + PHPStringSource::EmptyString => write!(f, "PHPStringSource::EmptyString"), + PHPStringSource::RustString(string) => { + write!(f, "PHPStringSource::RustString({string})") + } + PHPStringSource::ZendStringWithoutCopy(string) => { + write!(f, "PHPStringSource::ZendStringWithoutCopy({string:p})") + } + PHPStringSource::ZendStringToCopy(string) => { + write!(f, "PHPStringSource::ZendStringToCopy({string:p})") + } + PHPStringSource::CString(string) => write!(f, "PHPStringSource::CString({string:?})"), + #[cfg(feature = "bstr")] + PHPStringSource::BString(string) => write!(f, "PHPStringSource::BString({string:?})"), + } + } +} + /// Helper to force all string constructions to go through the API of this crate /// rather than being constructed manually. +#[derive(Debug)] pub struct PHPStringWrapper { /// The actual string. inner: PHPStringSource, } /// A value to apply to a PHP `zval`. +#[non_exhaustive] +#[derive(Debug)] pub enum PHPValue { /// None - do not change the value. _None, @@ -114,7 +139,7 @@ impl PHPValue { #[cfg(feature = "bstr")] // SAFETY: pointer must be valid, upheld by caller PHPStringSource::BString(string) => unsafe { - ZVAL_STRINGL!(zval_p, string.as_ptr().cast::(), string.len()) + ZVAL_STRINGL!(zval_p, string.as_ptr().cast::(), string.len()); }, }, // SAFETY: pointer must be valid, upheld by caller @@ -225,14 +250,14 @@ impl From for PHPValue { #[cfg(feature = "bstr")] impl From for PHPValue { - fn from(s: bstr::BString) -> Self { - if s.is_empty() { + fn from(string: bstr::BString) -> Self { + if string.is_empty() { PHPValue::String(PHPStringWrapper { inner: PHPStringSource::EmptyString, }) } else { PHPValue::String(PHPStringWrapper { - inner: PHPStringSource::BString(s), + inner: PHPStringSource::BString(string), }) } } @@ -240,14 +265,14 @@ impl From for PHPValue { #[cfg(feature = "bstr")] impl From<&bstr::BStr> for PHPValue { - fn from(s: &bstr::BStr) -> Self { - if s.is_empty() { + fn from(string: &bstr::BStr) -> Self { + if string.is_empty() { PHPValue::String(PHPStringWrapper { inner: PHPStringSource::EmptyString, }) } else { PHPValue::String(PHPStringWrapper { - inner: PHPStringSource::BString(s.into()), + inner: PHPStringSource::BString(string.into()), }) } } diff --git a/src/stack_types/stack_smart_str.rs b/src/stack_types/stack_smart_str.rs index 88b188a..cb546e5 100644 --- a/src/stack_types/stack_smart_str.rs +++ b/src/stack_types/stack_smart_str.rs @@ -2,6 +2,7 @@ use rsphp_macros_c::{ZSTR_LEN, ZSTR_VAL}; use rsphp_sys::{ smart_str, smart_str_0, smart_str_extract, smart_str_free, zend_string, zend_string_release, }; +use std::fmt; use std::ops::Deref; /// Rust handling of stack-allocated [`rsphp_sys::smart_str`] values. @@ -79,3 +80,12 @@ impl Deref for StackSmartStr { &self.inner } } + +impl fmt::Debug for StackSmartStr { + #[expect(clippy::pointer_format, reason = "No better options")] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("StackSmartStr") + .field("inner", &format!("{:p}", std::ptr::from_ref(&self.inner))) + .finish() + } +} diff --git a/src/stack_types/stack_zend_string.rs b/src/stack_types/stack_zend_string.rs index 1fa1f60..dd3ab91 100644 --- a/src/stack_types/stack_zend_string.rs +++ b/src/stack_types/stack_zend_string.rs @@ -9,6 +9,7 @@ use std::ops::Deref; /// Can be dereferenced into the underlying `zend_string`. /// /// Calls [`rsphp_sys::zend_string_release_ex`] automatically on drop. +#[derive(Debug)] pub struct StackZendString { /// The wrapped string. inner: *mut zend_string, diff --git a/src/stack_types/stack_zval.rs b/src/stack_types/stack_zval.rs index 59c5ab4..00bc41e 100644 --- a/src/stack_types/stack_zval.rs +++ b/src/stack_types/stack_zval.rs @@ -1,5 +1,5 @@ use rsphp_sys::{zval, zval_ptr_dtor, zval_ptr_dtor_nogc}; -use std::convert::AsRef; +use std::fmt; use std::ops::Deref; /// Rust handling of stack-allocated [`rsphp_sys::zval`] values. @@ -40,6 +40,8 @@ impl StackZval { std::ptr::from_mut(&mut self.inner) } + /// Run [`rsphp_sys::zval_ptr_dtor_nogc()`] rather than [`rsphp_sys::zval_ptr_dtor()`] + /// when the object is dropped. pub fn set_no_gc_dtor(&mut self) { self.gc = false; } @@ -70,3 +72,13 @@ impl AsRef for StackZval { &self.inner } } + +impl fmt::Debug for StackZval { + #[expect(clippy::pointer_format, reason = "No better options")] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("StackZval") + .field("inner", &format!("{:p}", std::ptr::from_ref(&self.inner))) + .field("gc", &self.gc) + .finish() + } +} diff --git a/src/wrappers/hash.rs b/src/wrappers/hash.rs index 2a1a1c2..7a9f8b4 100644 --- a/src/wrappers/hash.rs +++ b/src/wrappers/hash.rs @@ -6,6 +6,7 @@ use rsphp_sys::{Bucket, HashTable, IS_UNDEF, zend_string, zval}; /// Representation of a *non packed* hash table, for read-only iteration /// though the underlying values can be modified. +#[derive(Debug)] pub struct HashMapRepr<'table> { /// The underlying table. _raw: *const HashTable, @@ -105,6 +106,7 @@ impl<'table> Iterator for HashMapRepr<'table> { /// Representation of a *potentially packed* hash table, for read-only iteration /// though the underlying values can be modified. +#[derive(Debug)] pub struct HashRepr<'table> { /// The underlying table. raw: *const HashTable, @@ -119,6 +121,7 @@ pub struct HashRepr<'table> { } impl HashRepr<'_> { + /// Create a new [`HashRepr`] from the given reference. #[must_use] pub fn from_reference(reference: &HashTable) -> Self { // SAFETY: pointer created from a reference is valid @@ -164,15 +167,14 @@ impl<'table> Iterator for HashRepr<'table> { } let current_val: *mut zval = self.next_elem; let hash: u64; - let key: *mut zend_string; // SAFETY: pointer must be valid - if unsafe { HT_IS_PACKED!(self.raw) } { + let key: *mut zend_string = if unsafe { HT_IS_PACKED!(self.raw) } { // SAFETY: pointer addition fits in isize and is part of the same // allocation self.next_elem = unsafe { self.next_elem.add(1) }; hash = self.index; self.index += 1; - key = std::ptr::null_mut(); + std::ptr::null_mut() } else { let bucket: *mut Bucket = self.next_elem.cast::(); // SAFETY: pointer addition fits in isize and is part of the same @@ -183,8 +185,8 @@ impl<'table> Iterator for HashRepr<'table> { // SAFETY: known to be a valid bucket in the table hash = unsafe { (*bucket).h }; // SAFETY: known to be a valid bucket in the table - key = unsafe { (*bucket).key }; - } + unsafe { (*bucket).key } + }; self.count_remaining -= 1; // SAFETY: hash tables should only hold valid values @@ -200,6 +202,7 @@ impl<'table> Iterator for HashRepr<'table> { /// Representation of a *definitely packed* hash table, for read-only iteration /// though the underlying values can be modified. +#[derive(Debug)] pub struct HashPackedRepr<'table> { /// The underlying table. _raw: *const HashTable, diff --git a/src/wrappers/info_table_printer.rs b/src/wrappers/info_table_printer.rs index ff26eac..27749f1 100644 --- a/src/wrappers/info_table_printer.rs +++ b/src/wrappers/info_table_printer.rs @@ -3,12 +3,17 @@ use rsphp_sys::{php_info_print_table_end, php_info_print_table_row, php_info_pri use std::ffi::CString; /// A utility for printing out extension information tables. +#[derive(Debug)] pub struct InfoTablePrinter { /// If nothing is printed then we don't need to open and close the table. have_printed: bool, } impl InfoTablePrinter { + /// Print the given rows. + /// + /// See [`InfoTablePrinter::row()`] for the conditions under which this + /// method may panic. pub fn print(rows: &[&[&str]]) { let mut printer = InfoTablePrinter::new(); for row in rows { @@ -17,6 +22,7 @@ impl InfoTablePrinter { } #[must_use] + /// Create a new information table printer. pub fn new() -> Self { InfoTablePrinter { have_printed: false, @@ -28,8 +34,15 @@ impl InfoTablePrinter { /// # Panics /// /// Panics if any string contains interior null bytes, which cannot be - /// handled by a C function that uses null-terminated strings, or if - /// more than two values are printed for the same row. + /// handled by a C function that uses null-terminated strings. + /// + /// # Caveats + /// + /// Given that [`rsphp_sys::php_info_print_table_row()`] is variadic and + /// the Rust code here needs to call it with a known number of arguments, + /// if more than two values in the row are desired this method will print + /// the first two values and then a warning; do your own printing or request + /// that the limit be increased if needed. #[expect(clippy::missing_asserts_for_indexing, reason = "Length checked")] pub fn row(&mut self, values: &[&str]) { if values.is_empty() { @@ -50,14 +63,23 @@ impl InfoTablePrinter { return; } let str2 = CString::new(values[1]).expect("Should be valid"); - if values.len() == 2 { + if values.len() >= 2 { // SAFETY: all pointers are null-terminated strings, and the number // given in the first parameter matches the number of subsequent // arguments unsafe { php_info_print_table_row(2, str1.as_ptr(), str2.as_ptr()) }; return; } - panic!("Unhandled number of values: {}", values.len()); + if values.len() > 2 { + // Safety: pointer is for a null-terminated string, correct number + // of arguments given + unsafe { + php_info_print_table_row( + 1, + c"WARNING: rsphp::wrappers::InfoTablePrinter::row() truncated some values", + ); + } + } } } From 59f9314b58c97905665e7083300f910e745e7e51 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Tue, 3 Nov 2026 10:08:23 -0800 Subject: [PATCH 3/3] Typo in rsphp-sys README --- crates/sys/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/sys/README.md b/crates/sys/README.md index 5fdcb06..ab5dbed 100644 --- a/crates/sys/README.md +++ b/crates/sys/README.md @@ -1,5 +1,5 @@ # rsphp-sys -FFI bindings for PHP's ABIC, see the main [rsphp] README for more information. +FFI bindings for PHP's ABI, see the main [rsphp] README for more information. [rsphp]: https://github.com/DanielEScherzer/rsphp