diff --git a/Zend/tests/gh15938-005.phpt b/Zend/tests/gh15938-005.phpt new file mode 100644 index 000000000000..fb6d1d5cb091 --- /dev/null +++ b/Zend/tests/gh15938-005.phpt @@ -0,0 +1,38 @@ +--TEST-- +GH-15938 005: ASSIGN_OBJ on typed ref: Ref may be freed by __toString() +--CREDITS-- +iluuu1994 +--FILE-- +prop1); + unset($this->prop2); + return 'bar'; + } +} + +function test() { + $c = new C(); + $c->prop1 = 'foo'; + $c->prop1 = &$c->prop2; + $c->prop1 = $c; + var_dump($c); +} + +test(); + +?> +==DONE== +--EXPECTF-- +object(C)#%d (0) { + ["prop1"]=> + uninitialized(mixed) + ["prop2"]=> + uninitialized(?string) +} +==DONE== diff --git a/Zend/tests/gh15938-006.phpt b/Zend/tests/gh15938-006.phpt new file mode 100644 index 000000000000..d3dd20f53882 --- /dev/null +++ b/Zend/tests/gh15938-006.phpt @@ -0,0 +1,207 @@ +--TEST-- +GH-15938 006: Concurrent reference source list mutations variations +--ENV-- +LEN=10 +--FILE-- +a); + return str_repeat('a', getenv('LEN')); + } + }; + + $r = &$obj->a; + $r = $obj; + + var_dump($obj, $r); +} + +function remove_current_source_in_list() { + $obj = new class { + public string $a = ''; + public string $b = ''; + function __toString() { + unset($this->a); + return str_repeat('a', getenv('LEN')); + } + }; + + $r = &$obj->a; + $obj->b = &$obj->a; + $r = $obj; + + var_dump($obj, $r); +} + +function remove_next_source_in_list() { + $obj = new class { + public string $a = ''; + public string $b = ''; + function __toString() { + unset($this->b); + return str_repeat('a', getenv('LEN')); + } + }; + + $r = &$obj->a; + $obj->b = &$obj->a; + $r = $obj; + + var_dump($obj, $r); +} + +function remove_all_sources_in_list() { + $obj = new class { + public string $a = ''; + public string $b = ''; + function __toString() { + unset($this->a); + unset($this->b); + return str_repeat('a', getenv('LEN')); + } + }; + + $r = &$obj->a; + $obj->b = &$obj->a; + $r = $obj; + + var_dump($obj, $r); +} + +function add_sources() { + $obj = new class { + public string $a = ''; + public string $b = ''; + public string $c = ''; + public string $d = ''; + public string $e = ''; + public string $f = ''; + public string $g = ''; + public string $h = ''; + function __toString() { + var_dump(__METHOD__); + $this->b = &$this->a; + $this->c = &$this->a; + $this->d = &$this->a; + $this->e = &$this->a; + $this->f = &$this->a; + $this->g = &$this->a; + $this->h = &$this->a; + return str_repeat('a', getenv('LEN')); + } + }; + + $r = &$obj->a; + $r = $obj; + + var_dump($obj, $r); +} + +function cleanup_shrink() { + $obj = new class { + public string $a = ''; + }; + + $r = &$obj->a; + + $objs = []; + for ($i = 0; $i < 100; $i++) { + $objs[] = clone $obj; + } + + $r = new class($objs) { + function __construct(public mixed &$objs) {} + function __toString() { + $this->objs = array_slice($this->objs, 0, 2); + return str_repeat('a', getenv('LEN')); + } + }; + + var_dump($obj, $r); +} + +function add_incompatible() { + $obj = new class { + public int|string $a = 1; + public int $b = 2; + function __toString() { + $this->b = &$this->a; + return str_repeat('a', getenv('LEN')); + } + }; + + $r = &$obj->a; + try { + $r = $obj; + } catch (Error $e) { + echo $e::class, ": ", $e->getMessage(), "\n"; + } + + var_dump($obj, $r); +} + +foreach ([ + 'remove_single_ptr_source', + 'remove_current_source_in_list', + 'remove_next_source_in_list', + 'remove_all_sources_in_list', + 'cleanup_shrink', + 'add_incompatible', +] as $func) { + echo "# ", $func, ":\n"; + $func(); +} + +?> +==DONE== +--EXPECT-- +# remove_single_ptr_source: +object(class@anonymous)#1 (0) { + ["a"]=> + uninitialized(string) +} +string(10) "aaaaaaaaaa" +# remove_current_source_in_list: +object(class@anonymous)#1 (1) { + ["a"]=> + uninitialized(string) + ["b"]=> + &string(10) "aaaaaaaaaa" +} +string(10) "aaaaaaaaaa" +# remove_next_source_in_list: +object(class@anonymous)#1 (1) { + ["a"]=> + &string(10) "aaaaaaaaaa" + ["b"]=> + uninitialized(string) +} +string(10) "aaaaaaaaaa" +# remove_all_sources_in_list: +object(class@anonymous)#1 (0) { + ["a"]=> + uninitialized(string) + ["b"]=> + uninitialized(string) +} +string(10) "aaaaaaaaaa" +# cleanup_shrink: +object(class@anonymous)#1 (1) { + ["a"]=> + &string(10) "aaaaaaaaaa" +} +string(10) "aaaaaaaaaa" +# add_incompatible: +TypeError: Cannot assign class@anonymous to reference held by property class@anonymous::$b of type int +object(class@anonymous)#3 (2) { + ["a"]=> + &int(1) + ["b"]=> + &int(1) +} +int(1) +==DONE== diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index c8863a4b27ad..732bc5edb003 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -731,7 +731,10 @@ ZEND_API ZEND_COLD void zend_verify_arg_error( zend_string_release(need_msg); } -static bool zend_verify_weak_scalar_type_hint(uint32_t type_mask, zval *arg) +/* 'coerced_value' maybe a previous coercion of 'arg'. If it has the required + * type, it is assumed that coercion would have the same result, so it is copied + * to 'arg' without attempting coercion. */ +static zend_always_inline bool zend_verify_weak_scalar_type_hint_impl(uint32_t type_mask, zval *arg, zval *coerced_value) { zend_long lval; double dval; @@ -754,6 +757,10 @@ static bool zend_verify_weak_scalar_type_hint(uint32_t type_mask, zval *arg) ZVAL_DOUBLE(arg, dval); return true; } + } else if (coerced_value && Z_TYPE_P(coerced_value) == IS_LONG) { + zval_ptr_dtor(arg); + ZVAL_LONG(arg, Z_LVAL_P(coerced_value)); + return true; } else if (zend_parse_arg_long_weak(arg, &lval, 0)) { zval_ptr_dtor(arg); ZVAL_LONG(arg, lval); @@ -762,23 +769,55 @@ static bool zend_verify_weak_scalar_type_hint(uint32_t type_mask, zval *arg) return false; } } - if ((type_mask & MAY_BE_DOUBLE) && zend_parse_arg_double_weak(arg, &dval, 0)) { - zval_ptr_dtor(arg); - ZVAL_DOUBLE(arg, dval); - return true; + if (type_mask & MAY_BE_DOUBLE) { + if (coerced_value && Z_TYPE_P(coerced_value) == IS_DOUBLE) { + zval_ptr_dtor(arg); + ZVAL_DOUBLE(arg, Z_DVAL_P(coerced_value)); + return true; + } + if (zend_parse_arg_double_weak(arg, &dval, 0)) { + zval_ptr_dtor(arg); + ZVAL_DOUBLE(arg, dval); + return true; + } } - if ((type_mask & MAY_BE_STRING) && zend_parse_arg_str_weak(arg, &str, 0)) { - /* on success "arg" is converted to IS_STRING */ - return true; + if (type_mask & MAY_BE_STRING) { + if (coerced_value && Z_TYPE_P(coerced_value) == IS_STRING) { + zval_ptr_dtor(arg); + ZVAL_STR_COPY(arg, Z_STR_P(coerced_value)); + return true; + } + if (zend_parse_arg_str_weak(arg, &str, 0)) { + /* on success "arg" is converted to IS_STRING */ + return true; + } } - if ((type_mask & MAY_BE_BOOL) == MAY_BE_BOOL && zend_parse_arg_bool_weak(arg, &bval, 0)) { - zval_ptr_dtor(arg); - ZVAL_BOOL(arg, bval); - return true; + if ((type_mask & MAY_BE_BOOL) == MAY_BE_BOOL) { + if (coerced_value && (Z_TYPE_P(coerced_value) == IS_TRUE || Z_TYPE_P(coerced_value) == IS_FALSE)) { + zval_ptr_dtor(arg); + ZVAL_BOOL(arg, Z_LVAL_P(coerced_value)); + return true; + } + if (zend_parse_arg_bool_weak(arg, &bval, 0)) { + /* on success "arg" is converted to IS_BOOL */ + zval_ptr_dtor(arg); + ZVAL_BOOL(arg, bval); + return true; + } } return false; } +static bool zend_verify_weak_scalar_type_hint(uint32_t type_mask, zval *arg) +{ + return zend_verify_weak_scalar_type_hint_impl(type_mask, arg, NULL); +} + +static bool zend_verify_weak_scalar_type_hint_ex(uint32_t type_mask, zval *arg, zval *coerced_value) +{ + return zend_verify_weak_scalar_type_hint_impl(type_mask, arg, coerced_value); +} + #if ZEND_DEBUG static bool can_convert_to_string(const zval *zv) { /* We don't call cast_object here, because this check must be side-effect free. As this @@ -3969,13 +4008,19 @@ ZEND_API bool ZEND_FASTCALL zend_verify_ref_assignable_zval(zend_reference *ref, ZVAL_UNDEF(&coerced_value); ZEND_ASSERT(Z_TYPE_P(zv) != IS_REFERENCE); - ZEND_REF_FOREACH_TYPE_SOURCES(ref, prop) { + + bool was_iterated = ZEND_REF_TYPE_SOURCES_ITERATED(ref); + if (!was_iterated) { + ZEND_REF_SET_TYPE_SOURCES_ITERATED(ref); + } + + ZEND_REF_FOREACH_TYPE_SOURCES_CONCURRENT(ref, prop) { int result = i_zend_verify_type_assignable_zval(prop, zv, strict); if (result == 0) { type_error: zend_throw_ref_type_error_zval(prop, zv); zval_ptr_dtor(&coerced_value); - return 0; + goto fail; } if (result < 0) { @@ -3993,7 +4038,7 @@ ZEND_API bool ZEND_FASTCALL zend_verify_ref_assignable_zval(zend_reference *ref, } else { zval tmp; ZVAL_COPY(&tmp, zv); - if (!zend_verify_weak_scalar_type_hint(ZEND_TYPE_FULL_MASK(prop->type), &tmp)) { + if (!zend_verify_weak_scalar_type_hint_ex(ZEND_TYPE_FULL_MASK(prop->type), &tmp, &coerced_value)) { zval_ptr_dtor(&tmp); goto type_error; } @@ -4012,17 +4057,34 @@ ZEND_API bool ZEND_FASTCALL zend_verify_ref_assignable_zval(zend_reference *ref, conflicting_coercion_error: zend_throw_conflicting_coercion_error(first_prop, prop, zv); zval_ptr_dtor(&coerced_value); - return 0; + goto fail; } } } ZEND_REF_FOREACH_TYPE_SOURCES_END(); + if (!was_iterated) { + ZEND_REF_UNSET_TYPE_SOURCES_ITERATED(ref); + if (ZEND_REF_TYPE_SOURCES_DIRTY(ref)) { + ZEND_REF_CLEANUP_TYPE_SOURCES(ref); + } + } + if (!Z_ISUNDEF(coerced_value)) { zval_ptr_dtor(zv); ZVAL_COPY_VALUE(zv, &coerced_value); } return 1; + +fail: + if (!was_iterated) { + ZEND_REF_UNSET_TYPE_SOURCES_ITERATED(ref); + if (ZEND_REF_TYPE_SOURCES_DIRTY(ref)) { + ZEND_REF_CLEANUP_TYPE_SOURCES(ref); + } + } + + return 0; } static zend_always_inline void i_zval_ptr_dtor_noref(zval *zval_ptr) { @@ -4045,7 +4107,20 @@ ZEND_API zval* zend_assign_to_typed_ref_ex(zval *variable_ptr, zval *orig_value, } ZVAL_COPY(&value, orig_value); + + zend_reference *variable_ref = Z_REF_P(variable_ptr); + GC_ADDREF(variable_ref); ret = zend_verify_ref_assignable_zval(Z_REF_P(variable_ptr), &value, strict); + if (UNEXPECTED(GC_DELREF(variable_ref) == 0)) { + if (Z_REFCOUNTED(variable_ref->val)) { + *garbage_ptr = Z_COUNTED(variable_ref->val); + } + ZVAL_NULL(&variable_ref->val); + rc_dtor_func((zend_refcounted*)variable_ref); + zval_ptr_dtor(&value); + variable_ptr = &EG(uninitialized_zval); + goto out; + } variable_ptr = Z_REFVAL_P(variable_ptr); if (EXPECTED(ret)) { if (Z_REFCOUNTED_P(variable_ptr)) { @@ -4055,6 +4130,7 @@ ZEND_API zval* zend_assign_to_typed_ref_ex(zval *variable_ptr, zval *orig_value, } else { zval_ptr_dtor_nogc(&value); } +out: if (value_type & (IS_VAR|IS_TMP_VAR)) { if (UNEXPECTED(ref)) { if (UNEXPECTED(GC_DELREF(ref) == 0)) { @@ -4126,15 +4202,15 @@ ZEND_API bool ZEND_FASTCALL zend_verify_prop_assignable_by_ref(const zend_proper ZEND_API void ZEND_FASTCALL zend_ref_add_type_source(zend_property_info_source_list *source_list, zend_property_info *prop) { zend_property_info_list *list; - if (source_list->ptr == NULL) { - source_list->ptr = prop; + if (ZEND_PROPERTY_INFO_SOURCE_TO_PTR(source_list->ptr) == NULL) { + ZEND_PROPERTY_INFO_SOURCE_SET_PTR(source_list, prop); return; } list = ZEND_PROPERTY_INFO_SOURCE_TO_LIST(source_list->list); if (!ZEND_PROPERTY_INFO_SOURCE_IS_LIST(source_list->list)) { list = emalloc(sizeof(zend_property_info_list) + (4 - 1) * sizeof(zend_property_info *)); - list->ptr[0] = source_list->ptr; + list->ptr[0] = ZEND_PROPERTY_INFO_SOURCE_TO_PTR(source_list->ptr); list->num_allocated = 4; list->num = 1; } else if (list->num_allocated == list->num) { @@ -4143,7 +4219,7 @@ ZEND_API void ZEND_FASTCALL zend_ref_add_type_source(zend_property_info_source_l } list->ptr[list->num++] = prop; - source_list->list = ZEND_PROPERTY_INFO_SOURCE_FROM_LIST(list); + ZEND_PROPERTY_INFO_SOURCE_SET_LIST(source_list, list); } ZEND_API void ZEND_FASTCALL zend_ref_del_type_source(zend_property_info_source_list *source_list, const zend_property_info *prop) @@ -4151,17 +4227,22 @@ ZEND_API void ZEND_FASTCALL zend_ref_del_type_source(zend_property_info_source_l zend_property_info_list *list = ZEND_PROPERTY_INFO_SOURCE_TO_LIST(source_list->list); zend_property_info **ptr, **end; + /* In case the list is being iterated, we make sure to not free the list or + * reuse slots. Single-ptr lists can still be updated, as this can be + * detected during iteration. */ + bool iterated = ZEND_PROPERTY_INFO_SOURCE_IS_ITERATED(source_list); + ZEND_ASSERT(prop); if (!ZEND_PROPERTY_INFO_SOURCE_IS_LIST(source_list->list)) { - ZEND_ASSERT(source_list->ptr == prop); - source_list->ptr = NULL; + ZEND_ASSERT(ZEND_PROPERTY_INFO_SOURCE_TO_PTR(source_list->ptr) == prop); + ZEND_PROPERTY_INFO_SOURCE_SET_PTR(source_list, NULL); return; } - if (list->num == 1) { + if (list->num == 1 && !iterated) { ZEND_ASSERT(*list->ptr == prop); efree(list); - source_list->ptr = NULL; + ZEND_PROPERTY_INFO_SOURCE_SET_PTR(source_list, NULL); return; } @@ -4174,12 +4255,69 @@ ZEND_API void ZEND_FASTCALL zend_ref_del_type_source(zend_property_info_source_l } ZEND_ASSERT(*ptr == prop); + if (iterated) { + *ptr = NULL; + ZEND_PROPERTY_INFO_SOURCE_SET_DIRTY(source_list); + return; + } + /* Copy the last list element into the deleted slot. */ *ptr = list->ptr[--list->num]; if (list->num >= 4 && list->num * 4 == list->num_allocated) { list->num_allocated = list->num * 2; - source_list->list = ZEND_PROPERTY_INFO_SOURCE_FROM_LIST(erealloc(list, sizeof(zend_property_info_list) + (list->num_allocated - 1) * sizeof(zend_property_info *))); + ZEND_PROPERTY_INFO_SOURCE_SET_LIST(source_list, erealloc(list, sizeof(zend_property_info_list) + (list->num_allocated - 1) * sizeof(zend_property_info *))); + } +} + +ZEND_API void ZEND_FASTCALL zend_ref_cleanup_type_sources(zend_property_info_source_list *source_list) +{ + ZEND_ASSERT(!ZEND_PROPERTY_INFO_SOURCE_IS_ITERATED(source_list)); + + ZEND_PROPERTY_INFO_SOURCE_UNSET_DIRTY(source_list); + + if (!ZEND_PROPERTY_INFO_SOURCE_IS_LIST(source_list->list)) { + return; + } + + zend_property_info_list *list = ZEND_PROPERTY_INFO_SOURCE_TO_LIST(source_list->list); + + size_t actual_num = 0; + zend_property_info **p = &list->ptr[0]; + zend_property_info **end = p + list->num; + + while (p < end && *p) { + p++; + actual_num++; + } + + for (zend_property_info **q = p + 1; q < end; q++) { + if (*q) { + *p = *q; + p++; + actual_num++; + } + } + + if (actual_num == 0) { + ZEND_PROPERTY_INFO_SOURCE_SET_PTR(source_list, NULL); + efree(list); + return; + } + + if (actual_num == 1) { + ZEND_PROPERTY_INFO_SOURCE_SET_PTR(source_list, list->ptr[0]); + efree(list); + return; + } + + list->num = actual_num; + + if (list->num * 4 <= list->num_allocated) { + while (list->num_allocated > list->num * 4) { + list->num_allocated /= 2; + } + ZEND_PROPERTY_INFO_SOURCE_SET_LIST(source_list, erealloc(list, sizeof(zend_property_info_list) + (list->num_allocated - 1) * sizeof(zend_property_info *))); } } diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h index 920c702785ca..2f2aa661435e 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -121,16 +121,17 @@ ZEND_API bool zend_verify_internal_return_type(const zend_function *zf, zval *re (ref)->sources #define ZEND_REF_HAS_TYPE_SOURCES(ref) \ - (ZEND_REF_TYPE_SOURCES(ref).ptr != NULL) + (ZEND_PROPERTY_INFO_SOURCE_TO_PTR(ZEND_REF_TYPE_SOURCES(ref).ptr) != NULL) #define ZEND_REF_FIRST_SOURCE(ref) \ (ZEND_PROPERTY_INFO_SOURCE_IS_LIST((ref)->sources.list) \ ? ZEND_PROPERTY_INFO_SOURCE_TO_LIST((ref)->sources.list)->ptr[0] \ - : (ref)->sources.ptr) + : ZEND_PROPERTY_INFO_SOURCE_TO_PTR((ref)->sources.ptr)) ZEND_API void ZEND_FASTCALL zend_ref_add_type_source(zend_property_info_source_list *source_list, zend_property_info *prop); ZEND_API void ZEND_FASTCALL zend_ref_del_type_source(zend_property_info_source_list *source_list, const zend_property_info *prop); +ZEND_API void ZEND_FASTCALL zend_ref_cleanup_type_sources(zend_property_info_source_list *source_list); ZEND_API zval* zend_assign_to_typed_ref(zval *variable_ptr, zval *value, uint8_t value_type, bool strict); ZEND_API zval* zend_assign_to_typed_ref_ex(zval *variable_ptr, zval *value, uint8_t value_type, bool strict, zend_refcounted **garbage_ptr); @@ -598,27 +599,74 @@ ZEND_COLD void zend_magic_get_property_type_inconsistency_error(const zend_prope #define ZEND_REF_DEL_TYPE_SOURCE(ref, source) \ zend_ref_del_type_source(&ZEND_REF_TYPE_SOURCES(ref), source) +#define ZEND_REF_CLEANUP_TYPE_SOURCES(ref) \ + zend_ref_cleanup_type_sources(&ZEND_REF_TYPE_SOURCES(ref)) + +#define ZEND_REF_SET_TYPE_SOURCES_ITERATED(ref) \ + ZEND_PROPERTY_INFO_SOURCE_SET_ITERATED(&ZEND_REF_TYPE_SOURCES(ref)) + +#define ZEND_REF_UNSET_TYPE_SOURCES_ITERATED(ref) \ + ZEND_PROPERTY_INFO_SOURCE_UNSET_ITERATED(&ZEND_REF_TYPE_SOURCES(ref)) + +#define ZEND_REF_TYPE_SOURCES_ITERATED(ref) \ + ZEND_PROPERTY_INFO_SOURCE_IS_ITERATED(&ZEND_REF_TYPE_SOURCES(ref)) + +#define ZEND_REF_TYPE_SOURCES_DIRTY(ref) \ + ZEND_PROPERTY_INFO_SOURCE_IS_DIRTY(&ZEND_REF_TYPE_SOURCES(ref)) + #define ZEND_REF_FOREACH_TYPE_SOURCES(ref, prop) do { \ zend_property_info_source_list *_source_list = &ZEND_REF_TYPE_SOURCES(ref); \ zend_property_info **_prop, **_end; \ - zend_property_info_list *_list; \ + zend_property_info_list *_list = NULL; \ if (_source_list->ptr) { \ if (ZEND_PROPERTY_INFO_SOURCE_IS_LIST(_source_list->list)) { \ _list = ZEND_PROPERTY_INFO_SOURCE_TO_LIST(_source_list->list); \ _prop = _list->ptr; \ _end = _list->ptr + _list->num; \ } else { \ - _prop = &_source_list->ptr; \ + _prop = (zend_property_info**)&_source_list->ptr; \ _end = _prop + 1; \ } \ for (; _prop < _end; _prop++) { \ - prop = *_prop; \ + if (!*_prop) { \ + continue; \ + } \ + prop = ZEND_PROPERTY_INFO_SOURCE_TO_PTR(*_prop); \ #define ZEND_REF_FOREACH_TYPE_SOURCES_END() \ } \ } \ } while (0) +/* Same as ZEND_REF_FOREACH_TYPE_SOURCES(), but the source list may be modified + * safely during iteration. The ref must be marked with + * ZEND_REF_SET_TYPE_SOURCES_ITERATED() before iteration, and unmarked after */ +#define ZEND_REF_FOREACH_TYPE_SOURCES_CONCURRENT(ref, prop) do { \ + ZEND_ASSERT(ZEND_REF_TYPE_SOURCES_ITERATED(ref)); \ + { /* To match ZEND_REF_FOREACH_TYPE_SOURCES_END() */ \ + zend_property_info_source_list *_source_list = &ZEND_REF_TYPE_SOURCES(ref); \ + zend_property_info *_prop; \ + for (size_t _offset = 0; ; _offset++) { \ + if (!ZEND_PROPERTY_INFO_SOURCE_TO_PTR(_source_list->ptr)) { \ + break; \ + } \ + if (ZEND_PROPERTY_INFO_SOURCE_IS_LIST(_source_list->list)) { \ + zend_property_info_list *_list = ZEND_PROPERTY_INFO_SOURCE_TO_LIST(_source_list->list); \ + if (_offset >= _list->num) { \ + break; \ + } \ + _prop = _list->ptr[_offset]; \ + if (!_prop) { \ + continue; \ + } \ + } else { \ + if (_offset > 0 && ZEND_PROPERTY_INFO_SOURCE_TO_PTR(_source_list->ptr) == _prop) { \ + break; \ + } \ + _prop = ZEND_PROPERTY_INFO_SOURCE_TO_PTR(_source_list->ptr); \ + } \ + prop = _prop; \ + ZEND_COLD void zend_match_unhandled_error(const zval *value); /* Call this to handle the timeout or the interrupt function. It will set diff --git a/Zend/zend_types.h b/Zend/zend_types.h index a3d3e4da6362..d6c04e660818 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -597,13 +597,53 @@ typedef struct { } zend_property_info_list; typedef union { - struct _zend_property_info *ptr; - uintptr_t list; + uintptr_t ptr; /* zend_property_info* | ZEND_PROPERTY_INFO_SOURCE_FLAGS */ + uintptr_t list; /* zend_property_info_list* | ZEND_PROPERTY_INFO_SOURCE_FLAGS */ } zend_property_info_source_list; -#define ZEND_PROPERTY_INFO_SOURCE_FROM_LIST(list) (0x1 | (uintptr_t) (list)) -#define ZEND_PROPERTY_INFO_SOURCE_TO_LIST(list) ((zend_property_info_list *) ((list) & ~0x1)) -#define ZEND_PROPERTY_INFO_SOURCE_IS_LIST(list) ((list) & 0x1) +#define ZEND_PROPERTY_INFO_SOURCE_IS_LIST_FLAG (1<<0) +#define ZEND_PROPERTY_INFO_SOURCE_IS_ITERATED_FLAG (1<<1) /* zend_property_info_source_list is being iterated */ +#define ZEND_PROPERTY_INFO_SOURCE_IS_DIRTY_FLAG (1<<2) /* zend_property_info_source_list was modified while being iterated, and needs cleanup */ +#define ZEND_PROPERTY_INFO_SOURCE_FLAGS (ZEND_PROPERTY_INFO_SOURCE_IS_LIST_FLAG|ZEND_PROPERTY_INFO_SOURCE_IS_ITERATED_FLAG|ZEND_PROPERTY_INFO_SOURCE_IS_DIRTY_FLAG) + +#define ZEND_PROPERTY_INFO_SOURCE_SET_LIST(_source_list, _list) do { \ + zend_property_info_source_list *__source_list = (_source_list); \ + zend_property_info_list *__list = (_list); \ + ZEND_ASSERT(!((uintptr_t)__list & ZEND_PROPERTY_INFO_SOURCE_FLAGS)); \ + source_list->list = (uintptr_t)__list \ + | ZEND_PROPERTY_INFO_SOURCE_IS_LIST_FLAG \ + | (__source_list->list & (ZEND_PROPERTY_INFO_SOURCE_IS_ITERATED_FLAG|ZEND_PROPERTY_INFO_SOURCE_IS_DIRTY_FLAG)); \ + } while (0) + +#define ZEND_PROPERTY_INFO_SOURCE_SET_PTR(_source_list, _ptr) do { \ + zend_property_info_source_list *__source_list = (_source_list); \ + zend_property_info *__ptr = (_ptr); \ + ZEND_ASSERT(!((uintptr_t)__ptr & ZEND_PROPERTY_INFO_SOURCE_FLAGS)); \ + source_list->ptr = (uintptr_t)__ptr \ + | (__source_list->ptr & ZEND_PROPERTY_INFO_SOURCE_IS_ITERATED_FLAG); \ + } while (0) + +#define ZEND_PROPERTY_INFO_SOURCE_SET_ITERATED(_source_list) do { \ + (_source_list)->ptr |= ZEND_PROPERTY_INFO_SOURCE_IS_ITERATED_FLAG; \ + } while (0) + +#define ZEND_PROPERTY_INFO_SOURCE_UNSET_ITERATED(_source_list) do { \ + (_source_list)->ptr &= ~ZEND_PROPERTY_INFO_SOURCE_IS_ITERATED_FLAG; \ + } while (0) + +#define ZEND_PROPERTY_INFO_SOURCE_SET_DIRTY(_source_list) do { \ + (_source_list)->ptr |= ZEND_PROPERTY_INFO_SOURCE_IS_DIRTY_FLAG; \ + } while (0) + +#define ZEND_PROPERTY_INFO_SOURCE_UNSET_DIRTY(_source_list) do { \ + (_source_list)->ptr &= ~ZEND_PROPERTY_INFO_SOURCE_IS_DIRTY_FLAG; \ + } while (0) + +#define ZEND_PROPERTY_INFO_SOURCE_TO_LIST(list) ((zend_property_info_list *) ((list) & ~ZEND_PROPERTY_INFO_SOURCE_FLAGS)) +#define ZEND_PROPERTY_INFO_SOURCE_TO_PTR(list) ((zend_property_info *) (((uintptr_t)(list)) & ~ZEND_PROPERTY_INFO_SOURCE_FLAGS)) +#define ZEND_PROPERTY_INFO_SOURCE_IS_LIST(list) ((list) & ZEND_PROPERTY_INFO_SOURCE_IS_LIST_FLAG) +#define ZEND_PROPERTY_INFO_SOURCE_IS_ITERATED(source_list) ((source_list)->ptr & ZEND_PROPERTY_INFO_SOURCE_IS_ITERATED_FLAG) +#define ZEND_PROPERTY_INFO_SOURCE_IS_DIRTY(source_list) ((source_list)->ptr & ZEND_PROPERTY_INFO_SOURCE_IS_DIRTY_FLAG) struct _zend_reference { zend_refcounted_h gc; @@ -1222,7 +1262,7 @@ static zend_always_inline uint32_t zval_gc_info(uint32_t gc_type_info) { (zend_reference *) emalloc(sizeof(zend_reference)); \ GC_SET_REFCOUNT(_ref, 1); \ GC_TYPE_INFO(_ref) = GC_REFERENCE; \ - _ref->sources.ptr = NULL; \ + _ref->sources.ptr = 0; \ Z_REF_P(z) = _ref; \ Z_TYPE_INFO_P(z) = IS_REFERENCE_EX; \ } while (0) @@ -1233,7 +1273,7 @@ static zend_always_inline uint32_t zval_gc_info(uint32_t gc_type_info) { GC_SET_REFCOUNT(_ref, 1); \ GC_TYPE_INFO(_ref) = GC_REFERENCE; \ ZVAL_COPY_VALUE(&_ref->val, r); \ - _ref->sources.ptr = NULL; \ + _ref->sources.ptr = 0; \ Z_REF_P(z) = _ref; \ Z_TYPE_INFO_P(z) = IS_REFERENCE_EX; \ } while (0) @@ -1245,7 +1285,7 @@ static zend_always_inline uint32_t zval_gc_info(uint32_t gc_type_info) { GC_SET_REFCOUNT(_ref, (refcount)); \ GC_TYPE_INFO(_ref) = GC_REFERENCE; \ ZVAL_COPY_VALUE(&_ref->val, _z); \ - _ref->sources.ptr = NULL; \ + _ref->sources.ptr = 0; \ Z_REF_P(_z) = _ref; \ Z_TYPE_INFO_P(_z) = IS_REFERENCE_EX; \ } while (0) @@ -1257,7 +1297,7 @@ static zend_always_inline uint32_t zval_gc_info(uint32_t gc_type_info) { GC_TYPE_INFO(_ref) = GC_REFERENCE | \ (GC_PERSISTENT << GC_FLAGS_SHIFT); \ ZVAL_COPY_VALUE(&_ref->val, r); \ - _ref->sources.ptr = NULL; \ + _ref->sources.ptr = 0; \ Z_REF_P(z) = _ref; \ Z_TYPE_INFO_P(z) = IS_REFERENCE_EX; \ } while (0) diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 3c3065e15687..68fbf168f84a 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -9152,7 +9152,7 @@ ZEND_VM_HANDLER(183, ZEND_BIND_STATIC, CV, ANY, REF) ZVAL_COPY(&ref->val, GET_OP2_ZVAL_PTR_DEREF(BP_VAR_R)); FREE_OP2(); } - ref->sources.ptr = NULL; + ref->sources.ptr = 0; Z_REF_P(value) = ref; Z_TYPE_INFO_P(value) = IS_REFERENCE_EX; i_zval_ptr_dtor(variable_ptr); diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 50870ce463de..b93300adb17c 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -42663,7 +42663,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_BIND_STATIC_S ZVAL_COPY(&ref->val, get_zval_ptr_deref(opline->op2_type, opline->op2, BP_VAR_R)); FREE_OP(opline->op2_type, opline->op2.var); } - ref->sources.ptr = NULL; + ref->sources.ptr = 0; Z_REF_P(value) = ref; Z_TYPE_INFO_P(value) = IS_REFERENCE_EX; i_zval_ptr_dtor(variable_ptr); @@ -97905,7 +97905,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_BIND_STATIC_SPEC_C ZVAL_COPY(&ref->val, get_zval_ptr_deref(opline->op2_type, opline->op2, BP_VAR_R)); FREE_OP(opline->op2_type, opline->op2.var); } - ref->sources.ptr = NULL; + ref->sources.ptr = 0; Z_REF_P(value) = ref; Z_TYPE_INFO_P(value) = IS_REFERENCE_EX; i_zval_ptr_dtor(variable_ptr);