diff --git a/ext/standard/array.c b/ext/standard/array.c index a233e6c4dcb5..b665157c0ec9 100644 --- a/ext/standard/array.c +++ b/ext/standard/array.c @@ -1764,8 +1764,10 @@ static zend_long php_extract_ref_if_exists(const zend_array *arr, const zend_arr } else { ZVAL_MAKE_REF_EX(entry, 2); } - zval_ptr_dtor(orig_var); + zval garbage; + ZVAL_COPY_VALUE(&garbage, orig_var); ZVAL_REF(orig_var, Z_REF_P(entry)); + zval_ptr_dtor(&garbage); count++; } } ZEND_HASH_FOREACH_END(); @@ -1957,8 +1959,10 @@ static zend_long php_extract_ref_prefix_if_exists(const zend_array *arr, zend_ar if (Z_TYPE_P(orig_var) == IS_INDIRECT) { orig_var = Z_INDIRECT_P(orig_var); } - zval_ptr_dtor(orig_var); + zval garbage; + ZVAL_COPY_VALUE(&garbage, orig_var); ZVAL_REF(orig_var, Z_REF_P(entry)); + zval_ptr_dtor(&garbage); } else { zend_hash_add_new(symbol_table, final_name, entry); } @@ -2070,8 +2074,10 @@ prefix:; if (Z_TYPE_P(orig_var) == IS_INDIRECT) { orig_var = Z_INDIRECT_P(orig_var); } - zval_ptr_dtor(orig_var); + zval garbage; + ZVAL_COPY_VALUE(&garbage, orig_var); ZVAL_REF(orig_var, Z_REF_P(entry)); + zval_ptr_dtor(&garbage); } else { zend_hash_add_new(symbol_table, final_name, entry); } @@ -2198,8 +2204,10 @@ static zend_long php_extract_ref_prefix_all(const zend_array *arr, zend_array *s if (Z_TYPE_P(orig_var) == IS_INDIRECT) { orig_var = Z_INDIRECT_P(orig_var); } - zval_ptr_dtor(orig_var); + zval garbage; + ZVAL_COPY_VALUE(&garbage, orig_var); ZVAL_REF(orig_var, Z_REF_P(entry)); + zval_ptr_dtor(&garbage); } else { zend_hash_add_new(symbol_table, final_name, entry); } @@ -2300,8 +2308,10 @@ static zend_long php_extract_ref_prefix_invalid(const zend_array *arr, zend_arra if (Z_TYPE_P(orig_var) == IS_INDIRECT) { orig_var = Z_INDIRECT_P(orig_var); } - zval_ptr_dtor(orig_var); + zval garbage; + ZVAL_COPY_VALUE(&garbage, orig_var); ZVAL_REF(orig_var, Z_REF_P(entry)); + zval_ptr_dtor(&garbage); } else { zend_hash_add_new(symbol_table, final_name, entry); } diff --git a/ext/standard/tests/array/extract_refs_reentrant_dtor.phpt b/ext/standard/tests/array/extract_refs_reentrant_dtor.phpt new file mode 100644 index 000000000000..060ad457532d --- /dev/null +++ b/ext/standard/tests/array/extract_refs_reentrant_dtor.phpt @@ -0,0 +1,32 @@ +--TEST-- +extract() EXTR_REFS: destructor freeing the target symbol during the ref write (GH-18209 siblings) +--FILE-- + &$r1], EXTR_REFS | EXTR_IF_EXISTS); +var_dump($b ?? 'b-unset'); + +// EXTR_PREFIX_ALL path: php_extract_ref_prefix_all +class B { + public function __destruct() { + unset($GLOBALS['p_c']); + } +} +$p_c = new B(); +$r2 = 'r2'; +extract(['c' => &$r2], EXTR_REFS | EXTR_PREFIX_ALL, 'p'); +var_dump($p_c ?? 'p_c-unset'); + +echo "done\n"; +?> +--EXPECT-- +string(7) "b-unset" +string(9) "p_c-unset" +done