diff --git a/Zend/tests/gh20316-001.phpt b/Zend/tests/gh20316-001.phpt new file mode 100644 index 0000000000000..a028b31770e7d --- /dev/null +++ b/Zend/tests/gh20316-001.phpt @@ -0,0 +1,37 @@ +--TEST-- +GH-20316 001: Assign to ref: non-ref rvalue may be turned into a ref +--FILE-- +b = &$c->a; + $c->b = $c; + + var_dump($c); + unset($c); +} + +?> +--EXPECTF-- +object(C)#%d (2) { + ["a"]=> + &string(0) "" + ["b"]=> + &string(0) "" +} +object(C)#%d (2) { + ["a"]=> + &string(0) "" + ["b"]=> + &string(0) "" +} diff --git a/Zend/tests/gh20316-002.phpt b/Zend/tests/gh20316-002.phpt new file mode 100644 index 0000000000000..b7da2eec43695 --- /dev/null +++ b/Zend/tests/gh20316-002.phpt @@ -0,0 +1,28 @@ +--TEST-- +GH-20316 002: Assign to ref: ref rvalue may be unset +--FILE-- +b = &$c->a; + $c->b = $c; + + var_dump(isset($c)); +} + +?> +--EXPECT-- +bool(false) +bool(false) diff --git a/Zend/tests/gh20316-003.phpt b/Zend/tests/gh20316-003.phpt new file mode 100644 index 0000000000000..9e3d36d622fdd --- /dev/null +++ b/Zend/tests/gh20316-003.phpt @@ -0,0 +1,26 @@ +--TEST-- +GH-20316 003: Assign to ref: rvalue may be unset +--FILE-- +b = &$c->a; + $c->b = $c; + + var_dump(isset($c)); +} + +?> +--EXPECT-- +bool(false) +bool(false) diff --git a/Zend/tests/gh20318-001.phpt b/Zend/tests/gh20318-001.phpt new file mode 100644 index 0000000000000..b8108dbc3ea08 --- /dev/null +++ b/Zend/tests/gh20318-001.phpt @@ -0,0 +1,38 @@ +--TEST-- +GH-20318 001: Assign to 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/gh20318-002.phpt b/Zend/tests/gh20318-002.phpt new file mode 100644 index 0000000000000..b525358263730 --- /dev/null +++ b/Zend/tests/gh20318-002.phpt @@ -0,0 +1,242 @@ +--TEST-- +GH-20318 002: Assign to ref: 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 replace_single_ptr_source_incompatible() { + $obj = new class { + public int|string $a = 0; + public int $b = 0; + public $c; + function __toString() { + unset($this->a); + $this->b = &$this->c; + return str_repeat('a', getenv('LEN')); + } + }; + + $r = &$obj->a; + $obj->c = &$r; + try { + $r = $obj; + } catch (Error $e) { + echo $e::class, ": ", $e->getMessage(), "\n"; + } + + 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', + 'replace_single_ptr_source_incompatible', + '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" +# replace_single_ptr_source_incompatible: +TypeError: Cannot assign class@anonymous to reference held by property class@anonymous::$b of type int +object(class@anonymous)#1 (2) { + ["a"]=> + uninitialized(string|int) + ["b"]=> + &int(0) + ["c"]=> + &int(0) +} +int(0) +# remove_current_source_in_list: +object(class@anonymous)#2 (1) { + ["a"]=> + uninitialized(string) + ["b"]=> + &string(10) "aaaaaaaaaa" +} +string(10) "aaaaaaaaaa" +# remove_next_source_in_list: +object(class@anonymous)#2 (1) { + ["a"]=> + &string(10) "aaaaaaaaaa" + ["b"]=> + uninitialized(string) +} +string(10) "aaaaaaaaaa" +# remove_all_sources_in_list: +object(class@anonymous)#2 (0) { + ["a"]=> + uninitialized(string) + ["b"]=> + uninitialized(string) +} +string(10) "aaaaaaaaaa" +# cleanup_shrink: +object(class@anonymous)#2 (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_API.c b/Zend/zend_API.c index 1b97974686edb..fcaf70db46237 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -5068,17 +5068,15 @@ ZEND_API zend_result zend_update_static_property_ex(zend_class_entry *scope, zen } ZEND_ASSERT(!Z_ISREF_P(value)); - Z_TRY_ADDREF_P(value); + ZVAL_COPY(&tmp, value); if (ZEND_TYPE_IS_SET(prop_info->type)) { - ZVAL_COPY_VALUE(&tmp, value); if (!zend_verify_property_type(prop_info, &tmp, /* strict */ 0)) { - Z_TRY_DELREF_P(value); + zval_ptr_dtor(&tmp); return FAILURE; } - value = &tmp; } - zend_assign_to_variable(property, value, IS_TMP_VAR, /* strict */ 0); + zend_assign_to_variable(property, &tmp, IS_TMP_VAR, /* strict */ 0); return SUCCESS; } /* }}} */ diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index c8863a4b27ad5..bbd7caa510cb9 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -601,8 +601,9 @@ static zend_never_inline ZEND_COLD zval *zend_wrong_assign_to_variable_reference return &EG(uninitialized_zval); } + zval tmp; + ZVAL_COPY(&tmp, value_ptr); /* Use IS_TMP_VAR instead of IS_VAR to avoid ISREF check */ - Z_TRY_ADDREF_P(value_ptr); return zend_assign_to_variable_ex(variable_ptr, value_ptr, IS_TMP_VAR, EX_USES_STRICT_TYPES(), garbage_ptr); } @@ -731,7 +732,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 +758,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 +770,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 +4009,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 +4039,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 +4058,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 +4108,23 @@ ZEND_API zval* zend_assign_to_typed_ref_ex(zval *variable_ptr, zval *orig_value, } ZVAL_COPY(&value, orig_value); + + /* variable_ptr may be modified by zend_verify_ref_assignable_zval() */ + 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); + } + efree_size(variable_ref, sizeof(zend_reference)); + 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 +4134,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 +4206,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 +4223,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 +4231,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 it or to + * 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 +4259,71 @@ 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; + } + + /* Compact list by removing null slots, realloc down if necessary */ + + 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 920c702785ca4..912e647dd4874 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,6 +599,21 @@ 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; \ @@ -608,17 +624,50 @@ ZEND_COLD void zend_magic_get_property_type_inconsistency_error(const zend_prope _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); \ + _offset = 0; \ + } \ + 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_object_handlers.c b/Zend/zend_object_handlers.c index 17676e8edea48..1d866c8bc7457 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -1078,11 +1078,10 @@ ZEND_API zval *zend_std_write_property(zend_object *zobj, zend_string *name, zva } if (Z_TYPE_P(variable_ptr) != IS_UNDEF) { - Z_TRY_ADDREF_P(value); + ZVAL_COPY(&tmp, value); if (prop_info) { typed_property: - ZVAL_COPY_VALUE(&tmp, value); // Increase refcount to prevent object from being released in __toString() GC_ADDREF(zobj); bool type_matched = zend_verify_property_type(prop_info, &tmp, property_uses_strict_types()); @@ -1099,14 +1098,13 @@ ZEND_API zval *zend_std_write_property(zend_object *zobj, zend_string *name, zva goto exit; } Z_PROP_FLAG_P(variable_ptr) &= ~(IS_PROP_UNINIT|IS_PROP_REINITABLE); - value = &tmp; } found:; zend_refcounted *garbage = NULL; variable_ptr = zend_assign_to_variable_ex( - variable_ptr, value, IS_TMP_VAR, property_uses_strict_types(), &garbage); + variable_ptr, &tmp, IS_TMP_VAR, property_uses_strict_types(), &garbage); if (garbage) { if (GC_DELREF(garbage) == 0) { @@ -1146,7 +1144,7 @@ found:; zobj->properties = zend_array_dup(zobj->properties); } if ((variable_ptr = zend_hash_find(zobj->properties, name)) != NULL) { - Z_TRY_ADDREF_P(value); + ZVAL_COPY(&tmp, value); goto found; } } @@ -1241,12 +1239,12 @@ found:; if (EXPECTED(IS_VALID_PROPERTY_OFFSET(property_offset))) { variable_ptr = OBJ_PROP(zobj, property_offset); - Z_TRY_ADDREF_P(value); if (prop_info) { + ZVAL_COPY(&tmp, value); goto typed_property; } - ZVAL_COPY_VALUE(variable_ptr, value); + ZVAL_COPY(variable_ptr, value); } else { if (UNEXPECTED(zobj->ce->ce_flags & ZEND_ACC_NO_DYNAMIC_PROPERTIES)) { zend_forbidden_dynamic_property(zobj->ce, name); diff --git a/Zend/zend_types.h b/Zend/zend_types.h index a3d3e4da6362d..be049dcd3974b 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 3c3065e156871..68fbf168f84ae 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 50870ce463de3..b93300adb17cd 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);