From a53e816bb3f18bbc98aa9b28731702cc4103c430 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Thu, 2 Jan 2025 15:47:14 +0100 Subject: [PATCH] Fix GH-17317: ResourceBundle crash on undefined iterator key The next() and rewind() calls should be the ones fetching the data. We skip holes in the resource bundle in the same way that holes in arrays are skipped. --- .../resourcebundle/resourcebundle_iterator.c | 20 +++--- ext/intl/tests/gh17319.phpt | 63 +++++++++++++++++++ 2 files changed, 76 insertions(+), 7 deletions(-) create mode 100644 ext/intl/tests/gh17319.phpt diff --git a/ext/intl/resourcebundle/resourcebundle_iterator.c b/ext/intl/resourcebundle/resourcebundle_iterator.c index 9ec97f64f51d4..7d8d21a61fbab 100644 --- a/ext/intl/resourcebundle/resourcebundle_iterator.c +++ b/ext/intl/resourcebundle/resourcebundle_iterator.c @@ -29,10 +29,17 @@ /* {{{ resourcebundle_iterator_read */ static void resourcebundle_iterator_read( ResourceBundle_iterator *iterator ) { - UErrorCode icuerror = U_ZERO_ERROR; + UErrorCode icuerror; ResourceBundle_object *rb = iterator->subject; - rb->child = ures_getByIndex( rb->me, iterator->i, rb->child, &icuerror ); + ZEND_ASSERT(Z_ISUNDEF(iterator->current)); + + /* Skip non-existing resource entries in the bundle, similar to how holes in arrays are skipped + * in foreach loops. See also GH-17317. */ + do { + icuerror = U_ZERO_ERROR; + rb->child = ures_getByIndex( rb->me, iterator->i, rb->child, &icuerror ); + } while (icuerror == U_MISSING_RESOURCE_ERROR && iterator->i++ < iterator->length); if (U_SUCCESS(icuerror)) { /* ATTN: key extraction must be the first thing to do... rb->child might be reset in read! */ @@ -43,7 +50,6 @@ static void resourcebundle_iterator_read( ResourceBundle_iterator *iterator ) } else { // zend_throw_exception( spl_ce_OutOfRangeException, "Running past end of ResourceBundle", 0); - ZVAL_UNDEF(&iterator->current); } } /* }}} */ @@ -89,7 +95,7 @@ static zval *resourcebundle_iterator_current( zend_object_iterator *iter ) { ResourceBundle_iterator *iterator = (ResourceBundle_iterator *) iter; if (Z_ISUNDEF(iterator->current)) { - resourcebundle_iterator_read( iterator); + return NULL; } return &iterator->current; } @@ -100,9 +106,7 @@ static void resourcebundle_iterator_key( zend_object_iterator *iter, zval *key ) { ResourceBundle_iterator *iterator = (ResourceBundle_iterator *) iter; - if (Z_ISUNDEF(iterator->current)) { - resourcebundle_iterator_read( iterator); - } + ZEND_ASSERT(!Z_ISUNDEF(iterator->current)); if (iterator->is_table) { ZVAL_STRING(key, iterator->currentkey); @@ -119,6 +123,7 @@ static void resourcebundle_iterator_step( zend_object_iterator *iter ) iterator->i++; resourcebundle_iterator_invalidate( iter ); + resourcebundle_iterator_read(iterator); } /* }}} */ @@ -129,6 +134,7 @@ static void resourcebundle_iterator_reset( zend_object_iterator *iter ) iterator->i = 0; resourcebundle_iterator_invalidate( iter ); + resourcebundle_iterator_read(iterator); } /* }}} */ diff --git a/ext/intl/tests/gh17319.phpt b/ext/intl/tests/gh17319.phpt new file mode 100644 index 0000000000000..7ed0c1eed3702 --- /dev/null +++ b/ext/intl/tests/gh17319.phpt @@ -0,0 +1,63 @@ +--TEST-- +GH-17319 (ResourceBundle iterator crash on NULL key) +--EXTENSIONS-- +intl +--CREDITS-- +KidFlo +--FILE-- +get('calendar')->get('buddhist') as $key => $value) { + echo "KEY: "; var_dump($key); + echo "VALUE: "; var_dump($value); +} + +$bundle = (new ResourceBundle('', NULL))->get('calendar')->get('buddhist'); +$iterator = $bundle->getIterator(); +while ($iterator->valid()) { + var_dump($iterator->key()); + $iterator->next(); +} + +?> +--EXPECT-- +KEY: string(15) "AmPmMarkersAbbr" +VALUE: object(ResourceBundle)#3 (0) { +} +KEY: string(15) "AmPmMarkersAbbr" +VALUE: object(ResourceBundle)#4 (0) { +} +KEY: string(16) "DateTimePatterns" +VALUE: object(ResourceBundle)#3 (0) { +} +KEY: string(11) "appendItems" +VALUE: object(ResourceBundle)#4 (0) { +} +KEY: string(16) "availableFormats" +VALUE: object(ResourceBundle)#3 (0) { +} +KEY: string(8) "dayNames" +VALUE: object(ResourceBundle)#4 (0) { +} +KEY: string(4) "eras" +VALUE: object(ResourceBundle)#3 (0) { +} +KEY: string(15) "intervalFormats" +VALUE: object(ResourceBundle)#4 (0) { +} +KEY: string(10) "monthNames" +VALUE: object(ResourceBundle)#3 (0) { +} +KEY: string(8) "quarters" +VALUE: object(ResourceBundle)#4 (0) { +} +string(15) "AmPmMarkersAbbr" +string(15) "AmPmMarkersAbbr" +string(16) "DateTimePatterns" +string(11) "appendItems" +string(16) "availableFormats" +string(8) "dayNames" +string(4) "eras" +string(15) "intervalFormats" +string(10) "monthNames" +string(8) "quarters"