mirror of
https://github.com/php/php-src.git
synced 2025-08-16 05:58:45 +02:00

The segfault happens because zoi->wrapping_obj points to an object that has been freed. This wrapping_obj is set in IntlIterator_from_StringEnumeration(). Notice how the refcount is not increased in this function. By switching to ZVAL_OBJ_COPY, the segfault disappears. We also need to move the responsibility of destroying the iterator to the iterator itself and keep the object data destruction in the object destruction. The existing code used a weird recursive destruction between the iterator and object that was too hard to understand to be honest. This patch simplifies everything and in the process gets rid of the leak. Iterators that are embedded are now responsible for their own memory cleanup. Closes GH-17343.
290 lines
8.3 KiB
C++
290 lines
8.3 KiB
C++
/*
|
|
+----------------------------------------------------------------------+
|
|
| This source file is subject to version 3.01 of the PHP license, |
|
|
| that is bundled with this package in the file LICENSE, and is |
|
|
| available through the world-wide-web at the following url: |
|
|
| https://www.php.net/license/3_01.txt |
|
|
| If you did not receive a copy of the PHP license and are unable to |
|
|
| obtain it through the world-wide-web, please send a note to |
|
|
| license@php.net so we can mail you a copy immediately. |
|
|
+----------------------------------------------------------------------+
|
|
| Authors: Gustavo Lopes <cataphract@php.net> |
|
|
+----------------------------------------------------------------------+
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <unicode/brkiter.h>
|
|
|
|
#include "breakiterator_iterators.h"
|
|
#include "../common/common_enum.h"
|
|
|
|
extern "C" {
|
|
#define USE_BREAKITERATOR_POINTER
|
|
#include "breakiterator_class.h"
|
|
#include "breakiterator_iterators_arginfo.h"
|
|
#include "../intl_convert.h"
|
|
#include "../locale/locale.h"
|
|
#include <zend_exceptions.h>
|
|
#include <zend_interfaces.h>
|
|
}
|
|
|
|
static zend_class_entry *IntlPartsIterator_ce_ptr;
|
|
|
|
/* BreakIterator's iterator */
|
|
|
|
inline BreakIterator *_breakiter_prolog(zend_object_iterator *iter)
|
|
{
|
|
BreakIterator_object *bio;
|
|
bio = Z_INTL_BREAKITERATOR_P(&iter->data);
|
|
intl_errors_reset(BREAKITER_ERROR_P(bio));
|
|
if (bio->biter == NULL) {
|
|
intl_errors_set(BREAKITER_ERROR_P(bio), U_INVALID_STATE_ERROR,
|
|
"The BreakIterator object backing the PHP iterator is not "
|
|
"properly constructed", 0);
|
|
}
|
|
return bio->biter;
|
|
}
|
|
|
|
static void _breakiterator_destroy_it(zend_object_iterator *iter)
|
|
{
|
|
zval_ptr_dtor(&iter->data);
|
|
/* Don't free iter here because it is allocated as an object on its own, not embedded. */
|
|
}
|
|
|
|
static void _breakiterator_move_forward(zend_object_iterator *iter)
|
|
{
|
|
BreakIterator *biter = _breakiter_prolog(iter);
|
|
zoi_with_current *zoi_iter = (zoi_with_current*)iter;
|
|
|
|
iter->funcs->invalidate_current(iter);
|
|
|
|
if (biter == NULL) {
|
|
return;
|
|
}
|
|
|
|
int32_t pos = biter->next();
|
|
if (pos != BreakIterator::DONE) {
|
|
ZVAL_LONG(&zoi_iter->current, (zend_long)pos);
|
|
} //else we've reached the end of the enum, nothing more is required
|
|
}
|
|
|
|
static void _breakiterator_rewind(zend_object_iterator *iter)
|
|
{
|
|
BreakIterator *biter = _breakiter_prolog(iter);
|
|
zoi_with_current *zoi_iter = (zoi_with_current*)iter;
|
|
|
|
int32_t pos = biter->first();
|
|
ZVAL_LONG(&zoi_iter->current, (zend_long)pos);
|
|
}
|
|
|
|
static void zoi_with_current_dtor_self(zend_object_iterator *iter)
|
|
{
|
|
// Note: wrapping_obj is unused, call to zoi_with_current_dtor() not necessary
|
|
zoi_with_current *zoi_iter = (zoi_with_current*)iter;
|
|
ZEND_ASSERT(Z_ISUNDEF(zoi_iter->wrapping_obj));
|
|
|
|
// Unlike the other iterators, this iterator is a new, standalone instance
|
|
zoi_iter->destroy_it(iter);
|
|
}
|
|
|
|
static const zend_object_iterator_funcs breakiterator_iterator_funcs = {
|
|
zoi_with_current_dtor_self,
|
|
zoi_with_current_valid,
|
|
zoi_with_current_get_current_data,
|
|
NULL,
|
|
_breakiterator_move_forward,
|
|
_breakiterator_rewind,
|
|
zoi_with_current_invalidate_current,
|
|
NULL, /* get_gc */
|
|
};
|
|
|
|
U_CFUNC zend_object_iterator *_breakiterator_get_iterator(
|
|
zend_class_entry *ce, zval *object, int by_ref)
|
|
{
|
|
BreakIterator_object *bio;
|
|
if (by_ref) {
|
|
zend_throw_exception(NULL,
|
|
"Iteration by reference is not supported", 0);
|
|
return NULL;
|
|
}
|
|
|
|
bio = Z_INTL_BREAKITERATOR_P(object);
|
|
BreakIterator *biter = bio->biter;
|
|
|
|
if (biter == NULL) {
|
|
zend_throw_exception(NULL,
|
|
"The BreakIterator is not properly constructed", 0);
|
|
return NULL;
|
|
}
|
|
|
|
zoi_with_current *zoi_iter = static_cast<zoi_with_current*>(emalloc(sizeof *zoi_iter));
|
|
zend_iterator_init(&zoi_iter->zoi);
|
|
ZVAL_OBJ_COPY(&zoi_iter->zoi.data, Z_OBJ_P(object));
|
|
zoi_iter->zoi.funcs = &breakiterator_iterator_funcs;
|
|
zoi_iter->zoi.index = 0;
|
|
zoi_iter->destroy_it = _breakiterator_destroy_it;
|
|
ZVAL_UNDEF(&zoi_iter->wrapping_obj); /* not used; object is in zoi.data */
|
|
ZVAL_UNDEF(&zoi_iter->current);
|
|
|
|
return reinterpret_cast<zend_object_iterator *>(zoi_iter);
|
|
}
|
|
|
|
/* BreakIterator parts iterator */
|
|
|
|
typedef struct zoi_break_iter_parts {
|
|
zoi_with_current zoi_cur;
|
|
parts_iter_key_type key_type;
|
|
BreakIterator_object *bio; /* so we don't have to fetch it all the time */
|
|
zend_ulong index_right;
|
|
} zoi_break_iter_parts;
|
|
|
|
static void _breakiterator_parts_destroy_it(zend_object_iterator *iter)
|
|
{
|
|
zval_ptr_dtor(&iter->data);
|
|
efree(iter);
|
|
}
|
|
|
|
static void _breakiterator_parts_get_current_key(zend_object_iterator *iter, zval *key)
|
|
{
|
|
// The engine resets the iterator index to -1 after rewinding. When using
|
|
// PARTS_ITERATOR_KEY_RIGHT we store it in zoi_break_iter_parts.index_right
|
|
// so it doesn't get lost.
|
|
zoi_break_iter_parts *zoi_bit = (zoi_break_iter_parts*)iter;
|
|
|
|
if (zoi_bit->key_type == PARTS_ITERATOR_KEY_RIGHT && iter->index == 0) {
|
|
ZVAL_LONG(key, zoi_bit->index_right);
|
|
} else {
|
|
ZVAL_LONG(key, iter->index);
|
|
}
|
|
}
|
|
|
|
static void _breakiterator_parts_move_forward(zend_object_iterator *iter)
|
|
{
|
|
zoi_break_iter_parts *zoi_bit = (zoi_break_iter_parts*)iter;
|
|
BreakIterator_object *bio = zoi_bit->bio;
|
|
|
|
iter->funcs->invalidate_current(iter);
|
|
|
|
int32_t cur,
|
|
next;
|
|
|
|
cur = bio->biter->current();
|
|
if (cur == BreakIterator::DONE) {
|
|
return;
|
|
}
|
|
next = bio->biter->next();
|
|
if (next == BreakIterator::DONE) {
|
|
return;
|
|
}
|
|
|
|
if (zoi_bit->key_type == PARTS_ITERATOR_KEY_LEFT) {
|
|
iter->index = cur;
|
|
} else if (zoi_bit->key_type == PARTS_ITERATOR_KEY_RIGHT) {
|
|
iter->index = next;
|
|
zoi_bit->index_right = next;
|
|
}
|
|
/* else zoi_bit->key_type == PARTS_ITERATOR_KEY_SEQUENTIAL
|
|
* No need to do anything, the engine increments ->index */
|
|
|
|
const char *s = Z_STRVAL(bio->text);
|
|
zend_string *res;
|
|
|
|
assert(next <= Z_STRLEN(bio->text) && next >= cur);
|
|
res = zend_string_alloc(next - cur, 0);
|
|
|
|
memcpy(ZSTR_VAL(res), &s[cur], ZSTR_LEN(res));
|
|
ZSTR_VAL(res)[ZSTR_LEN(res)] = '\0';
|
|
|
|
ZVAL_STR(&zoi_bit->zoi_cur.current, res);
|
|
}
|
|
|
|
static void _breakiterator_parts_rewind(zend_object_iterator *iter)
|
|
{
|
|
zoi_break_iter_parts *zoi_bit = (zoi_break_iter_parts*)iter;
|
|
BreakIterator_object *bio = zoi_bit->bio;
|
|
|
|
if (!Z_ISUNDEF(zoi_bit->zoi_cur.current)) {
|
|
iter->funcs->invalidate_current(iter);
|
|
}
|
|
|
|
bio->biter->first();
|
|
|
|
iter->funcs->move_forward(iter);
|
|
}
|
|
|
|
static const zend_object_iterator_funcs breakiterator_parts_it_funcs = {
|
|
zoi_with_current_dtor,
|
|
zoi_with_current_valid,
|
|
zoi_with_current_get_current_data,
|
|
_breakiterator_parts_get_current_key,
|
|
_breakiterator_parts_move_forward,
|
|
_breakiterator_parts_rewind,
|
|
zoi_with_current_invalidate_current,
|
|
NULL, /* get_gc */
|
|
};
|
|
|
|
void IntlIterator_from_BreakIterator_parts(zval *break_iter_zv,
|
|
zval *object,
|
|
parts_iter_key_type key_type)
|
|
{
|
|
IntlIterator_object *ii;
|
|
|
|
object_init_ex(object, IntlPartsIterator_ce_ptr);
|
|
ii = Z_INTL_ITERATOR_P(object);
|
|
|
|
ii->iterator = (zend_object_iterator*)emalloc(sizeof(zoi_break_iter_parts));
|
|
zend_iterator_init(ii->iterator);
|
|
|
|
ZVAL_COPY(&ii->iterator->data, break_iter_zv);
|
|
ii->iterator->funcs = &breakiterator_parts_it_funcs;
|
|
ii->iterator->index = 0;
|
|
|
|
((zoi_with_current*)ii->iterator)->destroy_it = _breakiterator_parts_destroy_it;
|
|
ZVAL_OBJ_COPY(&((zoi_with_current*)ii->iterator)->wrapping_obj, Z_OBJ_P(object));
|
|
ZVAL_UNDEF(&((zoi_with_current*)ii->iterator)->current);
|
|
|
|
((zoi_break_iter_parts*)ii->iterator)->bio = Z_INTL_BREAKITERATOR_P(break_iter_zv);
|
|
|
|
assert(((zoi_break_iter_parts*)ii->iterator)->bio->biter != NULL);
|
|
|
|
((zoi_break_iter_parts*)ii->iterator)->key_type = key_type;
|
|
((zoi_break_iter_parts*)ii->iterator)->index_right = 0;
|
|
}
|
|
|
|
U_CFUNC PHP_METHOD(IntlPartsIterator, getBreakIterator)
|
|
{
|
|
INTLITERATOR_METHOD_INIT_VARS;
|
|
|
|
if (zend_parse_parameters_none() == FAILURE) {
|
|
RETURN_THROWS();
|
|
}
|
|
|
|
INTLITERATOR_METHOD_FETCH_OBJECT;
|
|
|
|
RETURN_COPY_DEREF(&ii->iterator->data);
|
|
}
|
|
|
|
U_CFUNC PHP_METHOD(IntlPartsIterator, getRuleStatus)
|
|
{
|
|
INTLITERATOR_METHOD_INIT_VARS;
|
|
|
|
if (zend_parse_parameters_none() == FAILURE) {
|
|
RETURN_THROWS();
|
|
}
|
|
|
|
INTLITERATOR_METHOD_FETCH_OBJECT;
|
|
|
|
zval *iter = &ii->iterator->data;
|
|
ZEND_ASSERT(Z_TYPE_P(iter) == IS_OBJECT);
|
|
zend_call_method_with_0_params(
|
|
Z_OBJ_P(iter), Z_OBJCE_P(iter), NULL, "getrulestatus", return_value);
|
|
}
|
|
|
|
U_CFUNC void breakiterator_register_IntlPartsIterator_class(void)
|
|
{
|
|
/* Create and register 'BreakIterator' class. */
|
|
IntlPartsIterator_ce_ptr = register_class_IntlPartsIterator(IntlIterator_ce_ptr);
|
|
}
|