Skip to content

Commit cf6c362

Browse files
committed
feat(iterator): simply stop when there is an exception, do not take it from EG to avoid segfault
1 parent a8f15d8 commit cf6c362

File tree

8 files changed

+243
-52
lines changed

8 files changed

+243
-52
lines changed

docsrs_bindings.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ pub const IS_RESOURCE: u32 = 9;
1717
pub const IS_REFERENCE: u32 = 10;
1818
pub const IS_CONSTANT_AST: u32 = 11;
1919
pub const IS_CALLABLE: u32 = 12;
20+
pub const IS_ITERABLE: u32 = 13;
2021
pub const IS_VOID: u32 = 14;
2122
pub const IS_MIXED: u32 = 16;
2223
pub const IS_PTR: u32 = 13;
@@ -1455,6 +1456,9 @@ extern "C" {
14551456
named_params: *mut HashTable,
14561457
);
14571458
}
1459+
extern "C" {
1460+
pub fn zend_is_iterable(iterable: *mut zval) -> bool;
1461+
}
14581462
pub const _zend_expected_type_Z_EXPECTED_LONG: _zend_expected_type = 0;
14591463
pub const _zend_expected_type_Z_EXPECTED_LONG_OR_NULL: _zend_expected_type = 1;
14601464
pub const _zend_expected_type_Z_EXPECTED_BOOL: _zend_expected_type = 2;

guide/src/types/iterable.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ that implements the `Traversable` interface. This means that any value that can
1919
# use ext_php_rs::types::Iterable;
2020
#[php_function]
2121
pub fn test_iterable(mut iterable: Iterable) {
22-
for (k, v) in iterable.iter() {
22+
for (k, v) in iterable.iter().expect("cannot get iterable") {
2323
println!("k: {} v: {}", k, v.string().unwrap());
2424
}
2525
}

guide/src/types/iterator.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ used but also a the result of a `query` call with `PDO`.
2020
# use ext_php_rs::types::ZendIterator;
2121
#[php_function]
2222
pub fn test_iterator(iterator: &mut ZendIterator) {
23-
for (k, v) in iterator.iter() {
23+
for (k, v) in iterator.iter().expect("cannot get iterator") {
2424
println!("k: {} v: {}", k, v.string().unwrap());
2525
}
2626
}

src/types/iterable.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use super::array::Iter as ZendHashTableIter;
22
use super::iterator::Iter as ZendIteratorIter;
33
use crate::convert::FromZval;
4-
use crate::exception::PhpResult;
54
use crate::flags::DataType;
65
use crate::types::iterator::IterKey;
76
use crate::types::{ZendHashTable, ZendIterator, Zval};
@@ -16,10 +15,10 @@ pub enum Iterable<'a> {
1615

1716
impl<'a> Iterable<'a> {
1817
/// Creates a new rust iterator from a PHP iterable.
19-
pub fn iter(&mut self) -> PhpResult<Iter> {
18+
pub fn iter(&mut self) -> Option<Iter> {
2019
match self {
21-
Iterable::Array(array) => Ok(Iter::Array(array.iter())),
22-
Iterable::Traversable(traversable) => Ok(Iter::Traversable(traversable.iter()?)),
20+
Iterable::Array(array) => Some(Iter::Array(array.iter())),
21+
Iterable::Traversable(traversable) => Some(Iter::Traversable(traversable.iter()?)),
2322
}
2423
}
2524
}
@@ -47,11 +46,11 @@ pub enum Iter<'a> {
4746
}
4847

4948
impl<'a> Iterator for Iter<'a> {
50-
type Item = PhpResult<(IterKey, &'a Zval)>;
49+
type Item = (IterKey, &'a Zval);
5150

5251
fn next(&mut self) -> Option<Self::Item> {
5352
match self {
54-
Iter::Array(array) => array.next().map(Ok),
53+
Iter::Array(array) => array.next(),
5554
Iter::Traversable(traversable) => traversable.next(),
5655
}
5756
}

src/types/iterator.rs

Lines changed: 176 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use crate::convert::{FromZval, FromZvalMut};
22
use crate::ffi::{zend_object_iterator, ZEND_RESULT_CODE_SUCCESS};
33
use crate::flags::DataType;
4-
use crate::prelude::PhpResult;
54
use crate::types::Zval;
65
use crate::zend::ExecutorGlobals;
76
use std::fmt::{Debug, Display, Formatter};
@@ -18,55 +17,70 @@ impl ZendIterator {
1817
/// # Returns
1918
///
2019
/// Returns a iterator over the zend_object_iterator.
21-
pub fn iter(&mut self) -> PhpResult<Iter> {
20+
pub fn iter(&mut self) -> Option<Iter> {
2221
self.index = 0;
23-
self.rewind()?;
2422

25-
Ok(Iter { zi: self })
23+
if self.rewind() {
24+
return Some(Iter { zi: self });
25+
}
26+
27+
None
2628
}
2729

2830
/// Check if the current position of the iterator is valid.
2931
///
3032
/// As an example this will call the user defined valid method of the ['\Iterator'] interface.
3133
/// see <https://www.php.net/manual/en/iterator.valid.php>
32-
pub fn valid(&mut self) -> PhpResult<bool> {
34+
pub fn valid(&mut self) -> bool {
3335
if let Some(valid) = unsafe { (*self.funcs).valid } {
3436
let valid = unsafe { valid(&mut *self) == ZEND_RESULT_CODE_SUCCESS };
3537

36-
ExecutorGlobals::throw_if_exception()?;
38+
if ExecutorGlobals::has_exception() {
39+
return false;
40+
}
3741

38-
Ok(valid)
42+
valid
3943
} else {
40-
Ok(true)
44+
true
4145
}
4246
}
4347

4448
/// Rewind the iterator to the first element.
4549
///
4650
/// As an example this will call the user defined rewind method of the ['\Iterator'] interface.
4751
/// see <https://www.php.net/manual/en/iterator.rewind.php>
48-
pub fn rewind(&mut self) -> PhpResult<()> {
52+
///
53+
/// # Returns
54+
///
55+
/// Returns true if the iterator was successfully rewind, false otherwise. (when there is
56+
/// an exception during rewind)
57+
pub fn rewind(&mut self) -> bool {
4958
if let Some(rewind) = unsafe { (*self.funcs).rewind } {
5059
unsafe {
5160
rewind(&mut *self);
5261
}
5362
}
5463

55-
ExecutorGlobals::throw_if_exception()
64+
!ExecutorGlobals::has_exception()
5665
}
5766

5867
/// Move the iterator forward to the next element.
5968
///
6069
/// As an example this will call the user defined next method of the ['\Iterator'] interface.
6170
/// see <https://www.php.net/manual/en/iterator.next.php>
62-
pub fn move_forward(&mut self) -> PhpResult<()> {
71+
///
72+
/// # Returns
73+
///
74+
/// Returns true if the iterator was successfully move, false otherwise. (when there is
75+
/// an exception during next)
76+
pub fn move_forward(&mut self) -> bool {
6377
if let Some(move_forward) = unsafe { (*self.funcs).move_forward } {
6478
unsafe {
6579
move_forward(&mut *self);
6680
}
6781
}
6882

69-
ExecutorGlobals::throw_if_exception()
83+
!ExecutorGlobals::has_exception()
7084
}
7185

7286
/// Get the current data of the iterator.
@@ -75,16 +89,15 @@ impl ZendIterator {
7589
///
7690
/// Returns a reference to the current data of the iterator if available
7791
/// , ['None'] otherwise.
78-
pub fn get_current_data<'a>(&mut self) -> PhpResult<Option<&'a Zval>> {
79-
let get_current_data = match unsafe { (*self.funcs).get_current_data } {
80-
Some(get_current_data) => get_current_data,
81-
None => return Ok(None),
82-
};
92+
pub fn get_current_data<'a>(&mut self) -> Option<&'a Zval> {
93+
let get_current_data = unsafe { (*self.funcs).get_current_data }?;
8394
let value = unsafe { &*get_current_data(&mut *self) };
8495

85-
ExecutorGlobals::throw_if_exception()?;
96+
if ExecutorGlobals::has_exception() {
97+
return None;
98+
}
8699

87-
Ok(Some(value))
100+
Some(value)
88101
}
89102

90103
/// Get the current key of the iterator.
@@ -93,21 +106,19 @@ impl ZendIterator {
93106
///
94107
/// Returns a new ['Zval'] containing the current key of the iterator if available
95108
/// , ['None'] otherwise.
96-
pub fn get_current_key(&mut self) -> PhpResult<Option<Zval>> {
97-
let get_current_key = match unsafe { (*self.funcs).get_current_key } {
98-
Some(get_current_key) => get_current_key,
99-
None => return Ok(None),
100-
};
101-
109+
pub fn get_current_key(&mut self) -> Option<Zval> {
110+
let get_current_key = unsafe { (*self.funcs).get_current_key? };
102111
let mut key = Zval::new();
103112

104113
unsafe {
105114
get_current_key(&mut *self, &mut key);
106115
}
107116

108-
ExecutorGlobals::throw_if_exception()?;
117+
if ExecutorGlobals::has_exception() {
118+
return None;
119+
}
109120

110-
Ok(Some(key))
121+
Some(key)
111122
}
112123
}
113124

@@ -164,40 +175,31 @@ pub struct Iter<'a> {
164175
}
165176

166177
impl<'a> Iterator for Iter<'a> {
167-
type Item = PhpResult<(IterKey, &'a Zval)>;
178+
type Item = (IterKey, &'a Zval);
168179

169180
fn next(&mut self) -> Option<Self::Item> {
170181
// Call next when index > 0, so next is really called at the start of each iteration, which allow to work better with generator iterator
171-
if self.zi.index > 0 {
172-
if let Err(err) = self.zi.move_forward() {
173-
return Some(Err(err));
174-
}
182+
if self.zi.index > 0 && !self.zi.move_forward() {
183+
return None;
175184
}
176185

177-
match self.zi.valid() {
178-
Err(err) => return Some(Err(err)),
179-
Ok(false) => return None,
180-
Ok(true) => (),
186+
if !self.zi.valid() {
187+
return None;
181188
}
182189

183190
self.zi.index += 1;
184191

185192
let real_index = self.zi.index - 1;
186193

187194
let key = match self.zi.get_current_key() {
188-
Err(err) => return Some(Err(err)),
189-
Ok(None) => IterKey::Long(real_index),
190-
Ok(Some(key)) => match IterKey::from_zval(&key) {
195+
None => IterKey::Long(real_index),
196+
Some(key) => match IterKey::from_zval(&key) {
191197
Some(key) => key,
192198
None => IterKey::Long(real_index),
193199
},
194200
};
195201

196-
match self.zi.get_current_data() {
197-
Err(err) => Some(Err(err)),
198-
Ok(None) => None,
199-
Ok(Some(value)) => Some(Ok((key, value))),
200-
}
202+
self.zi.get_current_data().map(|value| (key, value))
201203
}
202204
}
203205

@@ -208,3 +210,133 @@ impl<'a> FromZvalMut<'a> for &'a mut ZendIterator {
208210
zval.object()?.get_class_entry().get_iterator(zval, false)
209211
}
210212
}
213+
214+
#[cfg(test)]
215+
#[cfg(feature = "embed")]
216+
mod tests {
217+
use crate::embed::Embed;
218+
use crate::types::iterator::IterKey;
219+
220+
#[test]
221+
fn test_generator() {
222+
Embed::run(|| {
223+
let result = Embed::run_script("src/types/iterator.test.php");
224+
225+
assert!(result.is_ok());
226+
227+
let generator = Embed::eval("$generator;");
228+
229+
assert!(generator.is_ok());
230+
231+
let zval = generator.unwrap();
232+
233+
assert!(zval.is_traversable());
234+
235+
let iterator = zval.traversable().unwrap();
236+
237+
assert!(iterator.valid());
238+
239+
{
240+
let mut iter = iterator.iter().unwrap();
241+
242+
let (key, value) = iter.next().unwrap();
243+
244+
assert_eq!(key, IterKey::Long(0));
245+
assert!(value.is_long());
246+
assert_eq!(value.long().unwrap(), 1);
247+
248+
let (key, value) = iter.next().unwrap();
249+
250+
assert_eq!(key, IterKey::Long(1));
251+
assert!(value.is_long());
252+
assert_eq!(value.long().unwrap(), 2);
253+
254+
let (key, value) = iter.next().unwrap();
255+
256+
assert_eq!(key, IterKey::Long(2));
257+
assert!(value.is_long());
258+
assert_eq!(value.long().unwrap(), 3);
259+
260+
let next = iter.next();
261+
262+
assert!(next.is_none());
263+
}
264+
});
265+
}
266+
267+
#[test]
268+
fn test_iterator() {
269+
Embed::run(|| {
270+
let result = Embed::run_script("src/types/iterator.test.php");
271+
272+
assert!(result.is_ok());
273+
274+
let generator = Embed::eval("$iterator;");
275+
276+
assert!(generator.is_ok());
277+
278+
let zval = generator.unwrap();
279+
280+
assert!(zval.is_traversable());
281+
282+
let iterator = zval.traversable().unwrap();
283+
284+
assert!(iterator.valid());
285+
286+
{
287+
let mut iter = iterator.iter().unwrap();
288+
289+
let (key, value) = iter.next().unwrap();
290+
291+
assert!(!key.is_numerical());
292+
assert_eq!(key, IterKey::String("key".to_string()));
293+
assert!(value.is_string());
294+
assert_eq!(value.string().unwrap(), "foo");
295+
296+
let (key, value) = iter.next().unwrap();
297+
298+
assert!(key.is_numerical());
299+
assert_eq!(key, IterKey::Long(10));
300+
assert!(value.is_string());
301+
assert_eq!(value.string().unwrap(), "bar");
302+
303+
let (key, value) = iter.next().unwrap();
304+
305+
assert_eq!(key, IterKey::Long(2));
306+
assert!(value.is_string());
307+
assert_eq!(value.string().unwrap(), "baz");
308+
309+
let next = iter.next();
310+
311+
assert!(next.is_none());
312+
}
313+
314+
// Test rewind
315+
{
316+
let mut iter = iterator.iter().unwrap();
317+
318+
let (key, value) = iter.next().unwrap();
319+
320+
assert_eq!(key, IterKey::String("key".to_string()));
321+
assert!(value.is_string());
322+
assert_eq!(value.string().unwrap(), "foo");
323+
324+
let (key, value) = iter.next().unwrap();
325+
326+
assert_eq!(key, IterKey::Long(10));
327+
assert!(value.is_string());
328+
assert_eq!(value.string().unwrap(), "bar");
329+
330+
let (key, value) = iter.next().unwrap();
331+
332+
assert_eq!(key, IterKey::Long(2));
333+
assert!(value.is_string());
334+
assert_eq!(value.string().unwrap(), "baz");
335+
336+
let next = iter.next();
337+
338+
assert!(next.is_none());
339+
}
340+
});
341+
}
342+
}

0 commit comments

Comments
 (0)