From 8747e9ae180763cb9a95ae7920468149ebef7414 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Fri, 29 Aug 2025 16:06:34 +0100 Subject: [PATCH 1/2] Zend: Warn when destructuring non-array values (#19439) RFC: https://wiki.php.net/rfc/warnings-php-8-5#destructuring_non-array_values --- Zend/tests/foreach/foreach_list_002.phpt | 34 ++++++++++++- Zend/tests/list/bug39304.phpt | 4 ++ Zend/tests/list/destruct_bool.phpt | 18 +++++++ Zend/tests/list/destruct_float.phpt | 18 +++++++ Zend/tests/list/destruct_int.phpt | 18 +++++++ Zend/tests/list/destruct_null.phpt | 12 +++++ .../list/destruct_object_not_ArrayAccess.phpt | 17 +++++++ Zend/tests/list/destruct_resource.phpt | 18 +++++++ Zend/tests/list/destruct_string.phpt | 21 ++++++++ Zend/tests/list/list_003.phpt | 24 --------- Zend/tests/list/list_005.phpt | 50 ------------------- Zend/zend_execute.c | 5 +- ext/opcache/jit/zend_jit_helpers.c | 9 ++++ ext/opcache/jit/zend_jit_ir.c | 9 +++- ext/opcache/tests/jit/fetch_list_r_001.phpt | 3 +- ext/opcache/tests/opt/block_pass_003.phpt | 43 +++++++++++++++- .../lang/engine_assignExecutionOrder_002.phpt | 4 ++ 17 files changed, 226 insertions(+), 81 deletions(-) create mode 100644 Zend/tests/list/destruct_bool.phpt create mode 100644 Zend/tests/list/destruct_float.phpt create mode 100644 Zend/tests/list/destruct_int.phpt create mode 100644 Zend/tests/list/destruct_null.phpt create mode 100644 Zend/tests/list/destruct_object_not_ArrayAccess.phpt create mode 100644 Zend/tests/list/destruct_resource.phpt create mode 100644 Zend/tests/list/destruct_string.phpt delete mode 100644 Zend/tests/list/list_003.phpt delete mode 100644 Zend/tests/list/list_005.phpt diff --git a/Zend/tests/foreach/foreach_list_002.phpt b/Zend/tests/foreach/foreach_list_002.phpt index 1c693651fdc4b..62231f8471482 100644 --- a/Zend/tests/foreach/foreach_list_002.phpt +++ b/Zend/tests/foreach/foreach_list_002.phpt @@ -7,16 +7,48 @@ foreach (array(array(1,2), array(3,4)) as list($a, )) { var_dump($a); } +echo "Array of strings:\n"; $array = [['a', 'b'], 'c', 'd']; foreach($array as list(, $a)) { var_dump($a); } +echo "Array of ints:\n"; +$array = [[5, 6], 10, 20]; + +foreach($array as list(, $a)) { + var_dump($a); +} + +echo "Array of nulls:\n"; +$array = [[null, null], null, null]; + +foreach($array as list(, $a)) { + var_dump($a); +} + ?> ---EXPECT-- +--EXPECTF-- int(1) int(3) +Array of strings: string(1) "b" + +Warning: Cannot use string as array in %s on line %d +NULL + +Warning: Cannot use string as array in %s on line %d +NULL +Array of ints: +int(6) + +Warning: Cannot use int as array in %s on line %d +NULL + +Warning: Cannot use int as array in %s on line %d +NULL +Array of nulls: +NULL NULL NULL diff --git a/Zend/tests/list/bug39304.phpt b/Zend/tests/list/bug39304.phpt index a5422d2f4f316..353baa5df01df 100644 --- a/Zend/tests/list/bug39304.phpt +++ b/Zend/tests/list/bug39304.phpt @@ -8,5 +8,9 @@ Bug #39304 (Segmentation fault with list unpacking of string offset) ?> --EXPECTF-- Warning: Uninitialized string offset 0 in %s on line %d + +Warning: Cannot use string as array in %s on line %d + +Warning: Cannot use string as array in %s on line %d NULL NULL diff --git a/Zend/tests/list/destruct_bool.phpt b/Zend/tests/list/destruct_bool.phpt new file mode 100644 index 0000000000000..7553811220c99 --- /dev/null +++ b/Zend/tests/list/destruct_bool.phpt @@ -0,0 +1,18 @@ +--TEST-- +Destructuring with list() a value of type bool +--FILE-- + +--EXPECTF-- +Warning: Cannot use bool as array in %s on line %d + +Warning: Cannot use bool as array in %s on line %d +NULL +NULL diff --git a/Zend/tests/list/destruct_float.phpt b/Zend/tests/list/destruct_float.phpt new file mode 100644 index 0000000000000..6423a5b5668a4 --- /dev/null +++ b/Zend/tests/list/destruct_float.phpt @@ -0,0 +1,18 @@ +--TEST-- +Destructuring with list() a value of type float +--FILE-- + +--EXPECTF-- +Warning: Cannot use float as array in %s on line %d + +Warning: Cannot use float as array in %s on line %d +NULL +NULL diff --git a/Zend/tests/list/destruct_int.phpt b/Zend/tests/list/destruct_int.phpt new file mode 100644 index 0000000000000..e232ecb73982a --- /dev/null +++ b/Zend/tests/list/destruct_int.phpt @@ -0,0 +1,18 @@ +--TEST-- +Destructuring with list() a value of type int +--FILE-- + +--EXPECTF-- +Warning: Cannot use int as array in %s on line %d + +Warning: Cannot use int as array in %s on line %d +NULL +NULL diff --git a/Zend/tests/list/destruct_null.phpt b/Zend/tests/list/destruct_null.phpt new file mode 100644 index 0000000000000..e89de45a28e83 --- /dev/null +++ b/Zend/tests/list/destruct_null.phpt @@ -0,0 +1,12 @@ +--TEST-- +Destructuring with list() a value of type null +--FILE-- + +--EXPECT-- +NULL diff --git a/Zend/tests/list/destruct_object_not_ArrayAccess.phpt b/Zend/tests/list/destruct_object_not_ArrayAccess.phpt new file mode 100644 index 0000000000000..097c2cd2def3e --- /dev/null +++ b/Zend/tests/list/destruct_object_not_ArrayAccess.phpt @@ -0,0 +1,17 @@ +--TEST-- +Destructuring with list() a value of type object (that does not implement ArrayAccess) +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught Error: Cannot use object of type stdClass as array in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/Zend/tests/list/destruct_resource.phpt b/Zend/tests/list/destruct_resource.phpt new file mode 100644 index 0000000000000..8e5576767fe11 --- /dev/null +++ b/Zend/tests/list/destruct_resource.phpt @@ -0,0 +1,18 @@ +--TEST-- +Destructuring with list() a value of type resource +--FILE-- + +--EXPECTF-- +Warning: Cannot use resource as array in %s on line %d + +Warning: Cannot use resource as array in %s on line %d +NULL +NULL diff --git a/Zend/tests/list/destruct_string.phpt b/Zend/tests/list/destruct_string.phpt new file mode 100644 index 0000000000000..b2ea88c65a6cf --- /dev/null +++ b/Zend/tests/list/destruct_string.phpt @@ -0,0 +1,21 @@ +--TEST-- +Destructuring with list() a value of type string +--FILE-- + +--EXPECTF-- +Warning: Cannot use string as array in %s on line %d + +Warning: Cannot use string as array in %s on line %d + +Warning: Cannot use string as array in %s on line %d +NULL +NULL +NULL diff --git a/Zend/tests/list/list_003.phpt b/Zend/tests/list/list_003.phpt deleted file mode 100644 index 4a509f6a828b4..0000000000000 --- a/Zend/tests/list/list_003.phpt +++ /dev/null @@ -1,24 +0,0 @@ ---TEST-- -list() with non-array ---FILE-- - ---EXPECT-- -NULL -NULL -NULL -NULL -NULL diff --git a/Zend/tests/list/list_005.phpt b/Zend/tests/list/list_005.phpt deleted file mode 100644 index 7dc3bf6fa36a6..0000000000000 --- a/Zend/tests/list/list_005.phpt +++ /dev/null @@ -1,50 +0,0 @@ ---TEST-- -Testing list() with several variables ---FILE-- - ---EXPECTF-- -NULL -NULL -NULL ----- -NULL -NULL -NULL ----- - -Fatal error: Uncaught Error: Cannot use object of type stdClass as array in %s:%d -Stack trace: -#0 {main} - thrown in %s on line %d diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 3908299a41c3f..701a5e89a411e 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -3005,7 +3005,7 @@ static zend_never_inline void ZEND_FASTCALL zend_fetch_dimension_address_UNSET(z zend_fetch_dimension_address(result, container_ptr, dim, dim_type, BP_VAR_UNSET EXECUTE_DATA_CC); } -static zend_always_inline void zend_fetch_dimension_address_read(zval *result, zval *container, zval *dim, int dim_type, int type, bool is_list, int slow EXECUTE_DATA_DC) +static zend_always_inline void zend_fetch_dimension_address_read(zval *result, zval *container, zval *dim, int dim_type, int type, bool is_list, bool slow EXECUTE_DATA_DC) { zval *retval; @@ -3143,6 +3143,9 @@ static zend_always_inline void zend_fetch_dimension_address_read(zval *result, z if (ZEND_CONST_COND(dim_type == IS_CV, 1) && UNEXPECTED(Z_TYPE_P(dim) == IS_UNDEF)) { ZVAL_UNDEFINED_OP2(); } + if (is_list && Z_TYPE_P(container) > IS_NULL) { + zend_error(E_WARNING, "Cannot use %s as array", zend_zval_type_name(container)); + } if (!is_list && type != BP_VAR_IS) { zend_error(E_WARNING, "Trying to access array offset on %s", zend_zval_value_name(container)); diff --git a/ext/opcache/jit/zend_jit_helpers.c b/ext/opcache/jit/zend_jit_helpers.c index b20afffa47df0..c4e5211ef9079 100644 --- a/ext/opcache/jit/zend_jit_helpers.c +++ b/ext/opcache/jit/zend_jit_helpers.c @@ -16,6 +16,7 @@ +----------------------------------------------------------------------+ */ +#include "Zend/zend_types.h" #include "Zend/zend_API.h" static ZEND_COLD void undef_result_after_exception(void) { @@ -2561,6 +2562,14 @@ static void ZEND_FASTCALL zend_jit_only_vars_by_reference(zval *arg) zend_error(E_NOTICE, "Only variables should be passed by reference"); } +static void ZEND_FASTCALL zend_jit_invalid_array_use(const zval *container) +{ + /* Warning should not occur on null */ + if (Z_TYPE_P(container) != IS_NULL) { + zend_error(E_WARNING, "Cannot use %s as array", zend_zval_type_name(container)); + } +} + static void ZEND_FASTCALL zend_jit_invalid_array_access(zval *container) { zend_error(E_WARNING, "Trying to access array offset on %s", zend_zval_value_name(container)); diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index 842dbbf7c01df..fcfe1bffa744a 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -16,6 +16,7 @@ * +----------------------------------------------------------------------+ */ +#include "Zend/zend_type_info.h" #include "jit/ir/ir.h" #include "jit/ir/ir_builder.h" #include "jit/tls/zend_jit_tls.h" @@ -12786,7 +12787,7 @@ static int zend_jit_fetch_dim_read(zend_jit_ctx *jit, } } - if (opline->opcode != ZEND_FETCH_DIM_IS && opline->opcode != ZEND_FETCH_LIST_R) { + if (opline->opcode != ZEND_FETCH_DIM_IS) { ir_ref ref; may_throw = 1; @@ -12796,7 +12797,11 @@ static int zend_jit_fetch_dim_read(zend_jit_ctx *jit, jit_SET_EX_OPLINE(jit, opline); ref = jit_ZVAL_ADDR(jit, op1_addr); } - ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_jit_invalid_array_access), ref); + if (opline->opcode == ZEND_FETCH_LIST_R) { + ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_jit_invalid_array_use), ref); + } else { + ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_jit_invalid_array_access), ref); + } } jit_set_Z_TYPE_INFO(jit, res_addr, IS_NULL); diff --git a/ext/opcache/tests/jit/fetch_list_r_001.phpt b/ext/opcache/tests/jit/fetch_list_r_001.phpt index ec93b305ffafa..f5f0fee4084e5 100644 --- a/ext/opcache/tests/jit/fetch_list_r_001.phpt +++ b/ext/opcache/tests/jit/fetch_list_r_001.phpt @@ -13,5 +13,6 @@ function test() { } test(); ?> ---EXPECT-- +--EXPECTF-- +Warning: Cannot use string as array in %s on line %d NULL diff --git a/ext/opcache/tests/opt/block_pass_003.phpt b/ext/opcache/tests/opt/block_pass_003.phpt index 25d32e1e4d70e..c8690add9b839 100644 --- a/ext/opcache/tests/opt/block_pass_003.phpt +++ b/ext/opcache/tests/opt/block_pass_003.phpt @@ -1,5 +1,5 @@ --TEST-- -Block Pass 003: Inorrect constant substitution in FETCH_LIST_R +Block Pass 003: Incorrect constant substitution in FETCH_LIST_R --INI-- opcache.enable=1 opcache.enable_cli=1 @@ -16,5 +16,44 @@ function test() { test(); ?> DONE ---EXPECT-- +--EXPECTF-- +Warning: Cannot use int as array in %s on line %d + +Warning: Cannot use int as array in %s on line %d + +Warning: Cannot use int as array in %s on line %d + +Warning: Cannot use int as array in %s on line %d + +Warning: Cannot use int as array in %s on line %d + +Warning: Cannot use int as array in %s on line %d + +Warning: Cannot use int as array in %s on line %d + +Warning: Cannot use int as array in %s on line %d + +Warning: Cannot use int as array in %s on line %d + +Warning: Cannot use int as array in %s on line %d + +Warning: Cannot use int as array in %s on line %d + +Warning: Cannot use int as array in %s on line %d + +Warning: Cannot use int as array in %s on line %d + +Warning: Cannot use int as array in %s on line %d + +Warning: Cannot use int as array in %s on line %d + +Warning: Cannot use int as array in %s on line %d + +Warning: Cannot use int as array in %s on line %d + +Warning: Cannot use int as array in %s on line %d + +Warning: Cannot use int as array in %s on line %d + +Warning: Cannot use int as array in %s on line %d DONE diff --git a/tests/lang/engine_assignExecutionOrder_002.phpt b/tests/lang/engine_assignExecutionOrder_002.phpt index 956e3f9066d3d..a70cd5bafc8f2 100644 --- a/tests/lang/engine_assignExecutionOrder_002.phpt +++ b/tests/lang/engine_assignExecutionOrder_002.phpt @@ -116,6 +116,10 @@ array(3) { int(3000) } L=100 M=200 N=300 + +Warning: Cannot use int as array in %s on line %d + +Warning: Cannot use int as array in %s on line %d O= and P= 10 20 40 50 60 70 80 From 34a0bcf103f904a680615ef3b96a121a024a8364 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Fri, 29 Aug 2025 17:08:39 +0200 Subject: [PATCH 2/2] Update NEWS/UPGRADING for destructing non-array values --- NEWS | 4 ++++ UPGRADING | 3 +++ 2 files changed, 7 insertions(+) diff --git a/NEWS b/NEWS index ac003ca91aaae..35d5c34b6fcd9 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,10 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ?? ??? ????, PHP 8.5.0beta3 +- Core: + . Destructing non-array values (other than NULL) using [] or list() now + emits a warning. (Girgias) + - Opcache: . Fixed bug GH-19486 (Incorrect opline after deoptimization). (Arnaud) diff --git a/UPGRADING b/UPGRADING index eea2933ae6015..3bf3e6c09e1a4 100644 --- a/UPGRADING +++ b/UPGRADING @@ -52,6 +52,9 @@ PHP 8.5 UPGRADE NOTES . The disable_classes INI setting has been removed as it causes various engine assumptions to be broken. RFC: https://wiki.php.net/rfc/deprecations_php_8_5#remove_disable_classes_ini_setting + . Destructing non-array values (other than NULL) using [] or list() now + emits a warning. + RFC: https://wiki.php.net/rfc/warnings-php-8-5#destructuring_non-array_values - BZ2: . bzcompress() now throws a ValueError when $block_size is not between