From e5b1ebd99eb17a532a9d7ae56e0ca676d9017411 Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Tue, 17 Oct 2023 20:30:24 +0200 Subject: [PATCH] feat(iterator): add iterable, add method to get traversable or iterable --- allowed_bindings.rs | 1 + src/describe/stub.rs | 1 + src/flags.rs | 20 +++++++++------- src/types/array.rs | 21 +++++++++-------- src/types/iterable.rs | 53 +++++++++++++++++++++++++++++++++++++++++++ src/types/iterator.rs | 27 +++++++++++++++------- src/types/mod.rs | 2 ++ src/types/object.rs | 20 ++++++++-------- src/types/zval.rs | 35 +++++++++++++++++++++++++++- src/zend/class.rs | 19 ++++++++++++++++ 10 files changed, 162 insertions(+), 37 deletions(-) create mode 100644 src/types/iterable.rs diff --git a/allowed_bindings.rs b/allowed_bindings.rs index 6d7ddd9..0ef1b33 100644 --- a/allowed_bindings.rs +++ b/allowed_bindings.rs @@ -160,6 +160,7 @@ bind! { IS_UNDEF, IS_VOID, IS_PTR, + IS_ITERABLE, MAY_BE_ANY, MAY_BE_BOOL, PHP_INI_USER, diff --git a/src/describe/stub.rs b/src/describe/stub.rs index 065b275..17cfbfd 100644 --- a/src/describe/stub.rs +++ b/src/describe/stub.rs @@ -172,6 +172,7 @@ impl ToStub for DataType { DataType::Reference => "reference", DataType::Callable => "callable", DataType::Bool => "bool", + DataType::Iterable => "iterable", _ => "mixed", } ) diff --git a/src/flags.rs b/src/flags.rs index 60897a0..473d823 100644 --- a/src/flags.rs +++ b/src/flags.rs @@ -8,14 +8,14 @@ use crate::ffi::{ CONST_CS, CONST_DEPRECATED, CONST_NO_FILE_CACHE, CONST_PERSISTENT, E_COMPILE_ERROR, E_COMPILE_WARNING, E_CORE_ERROR, E_CORE_WARNING, E_DEPRECATED, E_ERROR, E_NOTICE, E_PARSE, E_RECOVERABLE_ERROR, E_STRICT, E_USER_DEPRECATED, E_USER_ERROR, E_USER_NOTICE, E_USER_WARNING, - E_WARNING, IS_ARRAY, IS_CALLABLE, IS_CONSTANT_AST, IS_DOUBLE, IS_FALSE, IS_LONG, IS_MIXED, - IS_NULL, IS_OBJECT, IS_PTR, IS_REFERENCE, IS_RESOURCE, IS_STRING, IS_TRUE, IS_TYPE_COLLECTABLE, - IS_TYPE_REFCOUNTED, IS_UNDEF, IS_VOID, PHP_INI_ALL, PHP_INI_PERDIR, PHP_INI_SYSTEM, - PHP_INI_USER, ZEND_ACC_ABSTRACT, ZEND_ACC_ANON_CLASS, ZEND_ACC_CALL_VIA_TRAMPOLINE, - ZEND_ACC_CHANGED, ZEND_ACC_CLOSURE, ZEND_ACC_CONSTANTS_UPDATED, ZEND_ACC_CTOR, - ZEND_ACC_DEPRECATED, ZEND_ACC_DONE_PASS_TWO, ZEND_ACC_EARLY_BINDING, ZEND_ACC_FAKE_CLOSURE, - ZEND_ACC_FINAL, ZEND_ACC_GENERATOR, ZEND_ACC_HAS_FINALLY_BLOCK, ZEND_ACC_HAS_RETURN_TYPE, - ZEND_ACC_HAS_TYPE_HINTS, ZEND_ACC_HEAP_RT_CACHE, ZEND_ACC_IMMUTABLE, + E_WARNING, IS_ARRAY, IS_CALLABLE, IS_CONSTANT_AST, IS_DOUBLE, IS_FALSE, IS_ITERABLE, IS_LONG, + IS_MIXED, IS_NULL, IS_OBJECT, IS_PTR, IS_REFERENCE, IS_RESOURCE, IS_STRING, IS_TRUE, + IS_TYPE_COLLECTABLE, IS_TYPE_REFCOUNTED, IS_UNDEF, IS_VOID, PHP_INI_ALL, PHP_INI_PERDIR, + PHP_INI_SYSTEM, PHP_INI_USER, ZEND_ACC_ABSTRACT, ZEND_ACC_ANON_CLASS, + ZEND_ACC_CALL_VIA_TRAMPOLINE, ZEND_ACC_CHANGED, ZEND_ACC_CLOSURE, ZEND_ACC_CONSTANTS_UPDATED, + ZEND_ACC_CTOR, ZEND_ACC_DEPRECATED, ZEND_ACC_DONE_PASS_TWO, ZEND_ACC_EARLY_BINDING, + ZEND_ACC_FAKE_CLOSURE, ZEND_ACC_FINAL, ZEND_ACC_GENERATOR, ZEND_ACC_HAS_FINALLY_BLOCK, + ZEND_ACC_HAS_RETURN_TYPE, ZEND_ACC_HAS_TYPE_HINTS, ZEND_ACC_HEAP_RT_CACHE, ZEND_ACC_IMMUTABLE, ZEND_ACC_IMPLICIT_ABSTRACT_CLASS, ZEND_ACC_INTERFACE, ZEND_ACC_LINKED, ZEND_ACC_NEARLY_LINKED, ZEND_ACC_NEVER_CACHE, ZEND_ACC_NO_DYNAMIC_PROPERTIES, ZEND_ACC_PRELOADED, ZEND_ACC_PRIVATE, ZEND_ACC_PROMOTED, ZEND_ACC_PROTECTED, ZEND_ACC_PUBLIC, ZEND_ACC_RESOLVED_INTERFACES, @@ -49,6 +49,7 @@ bitflags! { const ConstantExpression = IS_CONSTANT_AST; const Void = IS_VOID; const Ptr = IS_PTR; + const Iterable = IS_ITERABLE; const InternedStringEx = Self::String.bits(); const StringEx = Self::String.bits() | Self::RefCounted.bits(); @@ -237,6 +238,7 @@ pub enum DataType { Double, String, Array, + Iterable, Object(Option<&'static str>), Resource, Reference, @@ -275,6 +277,7 @@ impl DataType { DataType::Mixed => IS_MIXED, DataType::Bool => _IS_BOOL, DataType::Ptr => IS_PTR, + DataType::Iterable => IS_ITERABLE, } } } @@ -379,6 +382,7 @@ impl Display for DataType { DataType::Bool => write!(f, "Bool"), DataType::Mixed => write!(f, "Mixed"), DataType::Ptr => write!(f, "Pointer"), + DataType::Iterable => write!(f, "Iterable"), } } } diff --git a/src/types/array.rs b/src/types/array.rs index 0920f8e..974272e 100644 --- a/src/types/array.rs +++ b/src/types/array.rs @@ -10,6 +10,8 @@ use std::{ u64, }; +use crate::types::iterator::IterKey; +use crate::types::ZendLong; use crate::{ boxed::{ZBox, ZBoxable}, convert::{FromZval, IntoZval}, @@ -25,8 +27,6 @@ use crate::{ flags::DataType, types::Zval, }; -use crate::types::iterator::IterKey; -use crate::types::ZendLong; /// A PHP hashtable. /// @@ -512,7 +512,7 @@ impl ZendHashTable { /// // ^ Optional string key, if inserted like a hashtable. /// // ^ Inserted value. /// - /// dbg!(idx, key, val); + /// dbg!(key, val); /// } #[inline] pub fn iter(&self) -> Iter { @@ -548,10 +548,7 @@ unsafe impl ZBoxable for ZendHashTable { impl Debug for ZendHashTable { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_map() - .entries( - self.iter() - .map(|(k, v)| (k.to_string(), v)), - ) + .entries(self.iter().map(|(k, v)| (k.to_string(), v))) .finish() } } @@ -626,7 +623,10 @@ impl<'a> Iterator for Iter<'a> { }; let r = match key.is_long() { - true => (IterKey::Long(key.long().unwrap_or(self.current_num as ZendLong) as u64), value), + true => ( + IterKey::Long(key.long().unwrap_or(self.current_num as ZendLong) as u64), + value, + ), false => match key.try_into() { Ok(key) => (IterKey::String(key), value), Err(_) => (IterKey::Long(self.current_num), value), @@ -687,7 +687,10 @@ impl<'a> DoubleEndedIterator for Iter<'a> { }; let r = match key.is_long() { - true => (IterKey::Long(key.long().unwrap_or(self.current_num as ZendLong) as u64), value), + true => ( + IterKey::Long(key.long().unwrap_or(self.current_num as ZendLong) as u64), + value, + ), false => match key.try_into() { Ok(key) => (IterKey::String(key), value), Err(_) => (IterKey::Long(self.current_num), value), diff --git a/src/types/iterable.rs b/src/types/iterable.rs new file mode 100644 index 0000000..2333302 --- /dev/null +++ b/src/types/iterable.rs @@ -0,0 +1,53 @@ +use super::array::Iter as ZendHashTableIter; +use super::iterator::Iter as ZendIteratorIter; +use crate::convert::FromZval; +use crate::flags::DataType; +use crate::types::iterator::IterKey; +use crate::types::{ZendHashTable, ZendIterator, Zval}; + +#[derive(Debug)] +pub enum Iterable<'a> { + Array(&'a ZendHashTable), + Traversable(&'a mut ZendIterator), +} + +impl<'a> Iterable<'a> { + pub fn iter(&mut self) -> Iter { + match self { + Iterable::Array(array) => Iter::Array(array.iter()), + Iterable::Traversable(traversable) => Iter::Traversable(traversable.iter()), + } + } +} + +impl<'a> FromZval<'a> for Iterable<'a> { + const TYPE: DataType = DataType::Iterable; + + fn from_zval(zval: &'a Zval) -> Option { + if let Some(array) = zval.array() { + return Some(Iterable::Array(array)); + } + + if let Some(traversable) = zval.traversable() { + return Some(Iterable::Traversable(traversable)); + } + + None + } +} + +pub enum Iter<'a> { + Array(ZendHashTableIter<'a>), + Traversable(ZendIteratorIter<'a>), +} + +impl<'a> Iterator for Iter<'a> { + type Item = (IterKey, &'a Zval); + + fn next(&mut self) -> Option { + match self { + Iter::Array(array) => array.next(), + Iter::Traversable(traversable) => traversable.next(), + } + } +} diff --git a/src/types/iterator.rs b/src/types/iterator.rs index de07ca8..f3a4a29 100644 --- a/src/types/iterator.rs +++ b/src/types/iterator.rs @@ -3,8 +3,14 @@ use crate::ffi::zend_object_iterator; use crate::flags::DataType; use crate::types::{ZendLong, Zval}; use std::convert::TryInto; -use std::fmt::Display; +use std::fmt::{Debug, Display, Formatter}; +/// A PHP Iterator. +/// +/// In PHP, iterators are represented as zend_object_iterator. This allow user to iterate +/// over object implementing Traversable interface using foreach. +/// +/// ``` pub type ZendIterator = zend_object_iterator; impl ZendIterator { @@ -57,7 +63,13 @@ impl ZendIterator { } } -#[derive(PartialEq)] +impl Debug for ZendIterator { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ZendIterator").finish() + } +} + +#[derive(Debug, PartialEq)] pub enum IterKey { Long(u64), String(String), @@ -110,7 +122,10 @@ impl<'a> Iterator for Iter<'a> { Ok(key) => (IterKey::String(key), value), Err(_) => (IterKey::Long(real_index), value), }, - true => (IterKey::Long(key.long().unwrap_or(real_index as ZendLong) as u64), value), + true => ( + IterKey::Long(key.long().unwrap_or(real_index as ZendLong) as u64), + value, + ), }, None => (IterKey::Long(real_index), value), }) @@ -121,10 +136,6 @@ impl<'a> FromZvalMut<'a> for &'a mut ZendIterator { const TYPE: DataType = DataType::Object(Some("Traversable")); fn from_zval_mut(zval: &'a mut Zval) -> Option { - let zend_object = zval.object()?; - let ce = zend_object.get_class_entry_mut(); - let iterator = unsafe { ce.get_iterator?(&mut *ce, &mut *zval, 0) }; - - unsafe { iterator.as_mut() } + zval.object()?.get_class_entry().get_iterator(zval, false) } } diff --git a/src/types/mod.rs b/src/types/mod.rs index 84785eb..99bfdb3 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -6,6 +6,7 @@ mod array; mod callable; mod class_object; +mod iterable; mod iterator; mod long; mod object; @@ -15,6 +16,7 @@ mod zval; pub use array::ZendHashTable; pub use callable::ZendCallable; pub use class_object::ZendClassObject; +pub use iterator::ZendIterator; pub use long::ZendLong; pub use object::{PropertyQuery, ZendObject}; pub use string::ZendStr; diff --git a/src/types/object.rs b/src/types/object.rs index ef1ccc6..138f31a 100644 --- a/src/types/object.rs +++ b/src/types/object.rs @@ -103,17 +103,6 @@ impl ZendObject { unsafe { self.ce.as_ref() }.expect("Could not retrieve class entry.") } - /// Returns the [`ClassEntry`] associated with this object. - /// - /// # Panics - /// - /// Panics if the class entry is invalid. - pub fn get_class_entry_mut(&self) -> &'static mut 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_mut() }.expect("Could not retrieve class entry.") - } - /// Attempts to retrieve the class name of the object. pub fn get_class_name(&self) -> Result { unsafe { @@ -144,6 +133,15 @@ impl ZendObject { (self.ce as *const ClassEntry).eq(&(T::get_metadata().ce() as *const _)) } + /// Returns whether this object is an instance of \Traversable + /// + /// # Panics + /// + /// Panics if the class entry is invalid. + pub fn is_traversable(&self) -> bool { + self.instance_of(ce::traversable()) + } + #[inline(always)] pub fn try_call_method(&self, name: &str, params: Vec<&dyn IntoZvalDyn>) -> Result { let mut retval = Zval::new(); diff --git a/src/types/zval.rs b/src/types/zval.rs index 94b62aa..ce4e55d 100644 --- a/src/types/zval.rs +++ b/src/types/zval.rs @@ -4,6 +4,8 @@ use std::{convert::TryInto, ffi::c_void, fmt::Debug, ptr}; +use crate::types::iterable::Iterable; +use crate::types::ZendIterator; use crate::{ binary::Pack, binary_slice::PackSlice, @@ -12,7 +14,7 @@ use crate::{ error::{Error, Result}, ffi::{ _zval_struct__bindgen_ty_1, _zval_struct__bindgen_ty_2, zend_is_callable, - zend_is_identical, zend_resource, zend_value, zval, zval_ptr_dtor, + zend_is_identical, zend_is_iterable, zend_resource, zend_value, zval, zval_ptr_dtor, }, flags::DataType, flags::ZvalTypeFlags, @@ -237,6 +239,22 @@ impl Zval { ZendCallable::new(self).ok() } + pub fn traversable(&self) -> Option<&mut ZendIterator> { + if self.is_traversable() { + self.object()?.get_class_entry().get_iterator(self, false) + } else { + None + } + } + + pub fn iterable(&self) -> Option { + if self.is_iterable() { + Iterable::from_zval(self) + } else { + None + } + } + /// Returns the value of the zval if it is a pointer. /// /// # Safety @@ -347,6 +365,20 @@ impl Zval { unsafe { zend_is_identical(self_p as *mut Self, other_p as *mut Self) } } + /// Returns true if the zval is traversable, false otherwise. + pub fn is_traversable(&self) -> bool { + match self.object() { + None => false, + Some(obj) => obj.is_traversable(), + } + } + + /// Returns true if the zval is iterable (array or traversable), false otherwise. + pub fn is_iterable(&self) -> bool { + let ptr: *const Self = self; + unsafe { zend_is_iterable(ptr as *mut Self) } + } + /// Returns true if the zval contains a pointer, false otherwise. pub fn is_ptr(&self) -> bool { self.get_type() == DataType::Ptr @@ -601,6 +633,7 @@ impl Debug for Zval { DataType::ConstantExpression => field!(Option::<()>::None), DataType::Void => field!(Option::<()>::None), DataType::Bool => field!(self.bool()), + DataType::Iterable => field!(self.iterable()), // SAFETY: We are not accessing the pointer. DataType::Ptr => field!(unsafe { self.ptr::() }), }; diff --git a/src/zend/class.rs b/src/zend/class.rs index 4c3c23d..d9765a3 100644 --- a/src/zend/class.rs +++ b/src/zend/class.rs @@ -1,5 +1,6 @@ //! Builder and objects for creating classes in the PHP world. +use crate::types::{ZendIterator, Zval}; use crate::{ boxed::ZBox, ffi::zend_class_entry, @@ -97,6 +98,24 @@ impl ClassEntry { } } + /// Returns the iterator for the class for a specific instance + /// + /// Returns [`None`] if there is no associated iterator for the class. + pub fn get_iterator<'a>(&self, zval: &'a Zval, by_ref: bool) -> Option<&'a mut ZendIterator> { + let ptr: *const Self = self; + let zval_ptr: *const Zval = zval; + + let iterator = unsafe { + (*ptr).get_iterator?( + ptr as *mut ClassEntry, + zval_ptr as *mut Zval, + if by_ref { 1 } else { 0 }, + ) + }; + + unsafe { iterator.as_mut() } + } + pub fn name(&self) -> Option<&str> { unsafe { self.name.as_ref().and_then(|s| s.as_str().ok()) } }