From cebf67e77ad89fc783e2cf0bf20326bf966b6228 Mon Sep 17 00:00:00 2001 From: lacatoire Date: Thu, 30 Apr 2026 14:11:30 +0200 Subject: [PATCH] Fix GH-20482: heap use-after-free in ZEND_ASSIGN_DIM via re-entrant user output handler When ZEND_ASSIGN_DIM evaluates an undefined CV as the RHS, the warning emitted by zval_undefined_cv() can be routed through a user output handler (e.g. via ob_start with a small chunk size). The handler runs arbitrary PHP code, which may free, unset or rehash the array we are writing to, leaving the previously-fetched variable_ptr pointing into freed/relocated memory. The next zend_assign_to_variable_ex then performs a heap use-after-free. In the OP2_TYPE != IS_UNUSED branch: - Switch GET_OP_DATA_ZVAL_PTR to GET_OP_DATA_ZVAL_PTR_UNDEF so we no longer auto-emit the warning. Detect IS_UNDEF explicitly and call zval_undefined_cv around a temporary GC_ADDREF / GC_DELREF on the target HashTable, mirroring the IS_UNUSED branch and the previous fix in slow_index_convert (b594a95a2f6). - Reorder the pipeline: fetch the data value (and emit the warning) *before* resolving variable_ptr. Resolving the dim address only afterwards guarantees a fresh bucket pointer that cannot have been invalidated by reentrant user code freeing the array, rehashing it or unsetting keys. Includes phpt regressions covering the three reentrancy paths: - output handler reassigns the array, - output handler unsets the array, - output handler rehashes the array (would invalidate any pre-fetched bucket pointer). --- NEWS | 3 + ...gh20482_assign_dim_uaf_output_handler.phpt | 26 + ...dim_uaf_output_handler_mutate_via_ref.phpt | 29 + Zend/zend_vm_def.h | 27 +- Zend/zend_vm_execute.h | 1296 +++++++++++++++-- 5 files changed, 1234 insertions(+), 147 deletions(-) create mode 100644 Zend/tests/gh20482_assign_dim_uaf_output_handler.phpt create mode 100644 Zend/tests/gh20482_assign_dim_uaf_output_handler_mutate_via_ref.phpt diff --git a/NEWS b/NEWS index 3144cdf88c59..69550a754dbd 100644 --- a/NEWS +++ b/NEWS @@ -18,6 +18,9 @@ PHP NEWS initialization). (Arnaud) . Enabled the TAILCALL VM on Windows when compiling with Clang >= 19 x86_64. (henderkes) + . Fixed bug GH-20482 (Heap use-after-free in ZEND_ASSIGN_DIM when a user + output handler clobbers the array during the undefined-variable + warning). (lacatoire) - BCMath: . Added NUL-byte validation to BCMath functions. (jorgsowa) diff --git a/Zend/tests/gh20482_assign_dim_uaf_output_handler.phpt b/Zend/tests/gh20482_assign_dim_uaf_output_handler.phpt new file mode 100644 index 000000000000..ee7e55b97a5d --- /dev/null +++ b/Zend/tests/gh20482_assign_dim_uaf_output_handler.phpt @@ -0,0 +1,26 @@ +--TEST-- +GH-20482: heap use-after-free in ZEND_ASSIGN_DIM when an output handler clobbers the array during the undefined-variable warning +--FILE-- + +--EXPECT-- +string(5) "freed" +ok diff --git a/Zend/tests/gh20482_assign_dim_uaf_output_handler_mutate_via_ref.phpt b/Zend/tests/gh20482_assign_dim_uaf_output_handler_mutate_via_ref.phpt new file mode 100644 index 000000000000..c6f904833159 --- /dev/null +++ b/Zend/tests/gh20482_assign_dim_uaf_output_handler_mutate_via_ref.phpt @@ -0,0 +1,29 @@ +--TEST-- +GH-20482: ZEND_ASSIGN_DIM does not crash when the output handler mutates the array via a reference during the undefined-variable warning +--FILE-- + 0]; + +ob_start(function () use (&$a) { + // Mutate the array many times via the reference. Each nested + // ZEND_ASSIGN_DIM observes the temporary refcount bump from the outer + // handler and SEPARATEs the array, so the writes land in a duplicate; + // the outer assignment is then aborted cleanly via assign_dim_error + // when the original drops to refcount 0. The point of the test is + // simply that the whole thing must not UAF or crash, regardless of + // which array $a ends up pointing at. + for ($i = 0; $i < 64; $i++) { + $a["k$i"] = $i; + } + return ''; +}, 1); + +$a['target'] = $undef; + +ob_end_clean(); +var_dump(count($a) >= 65); +echo "ok\n"; +?> +--EXPECT-- +bool(true) +ok diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 1f7e09d1be35..283fa6d05065 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -2730,16 +2730,37 @@ ZEND_VM_C_LABEL(try_assign_dim_array): } } } else { + HashTable *ht = Z_ARRVAL_P(object_ptr); dim = GET_OP2_ZVAL_PTR_UNDEF(BP_VAR_R); + /* Order is critical: fetch the data value (which may emit an + * "Undefined variable" warning that can recurse into user code + * via a user output handler) before resolving variable_ptr. + * Resolving variable_ptr only afterwards guarantees a fresh + * bucket pointer that cannot have been invalidated by user + * code freeing the array, rehashing it, or unsetting keys. */ + value = GET_OP_DATA_ZVAL_PTR_UNDEF(BP_VAR_R); + if (OP_DATA_TYPE == IS_CV && UNEXPECTED(Z_TYPE_P(value) == IS_UNDEF)) { + /* Temporarily addref the array around the warning so we + * can detect a full destruction (last ref dropped by + * reentrant user code) and bail out cleanly. Mirrors the + * IS_UNUSED branch above. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + value = zval_undefined_cv((opline+1)->op1.var EXECUTE_DATA_CC); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) { + zend_array_destroy(ht); + ZEND_VM_C_GOTO(assign_dim_error); + } + } if (OP2_TYPE == IS_CONST) { - variable_ptr = zend_fetch_dimension_address_inner_W_CONST(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W_CONST(ht, dim EXECUTE_DATA_CC); } else { - variable_ptr = zend_fetch_dimension_address_inner_W(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W(ht, dim EXECUTE_DATA_CC); } if (UNEXPECTED(variable_ptr == NULL)) { ZEND_VM_C_GOTO(assign_dim_error); } - value = GET_OP_DATA_ZVAL_PTR(BP_VAR_R); value = zend_assign_to_variable_ex(variable_ptr, value, OP_DATA_TYPE, EX_USES_STRICT_TYPES(), &garbage); } if (UNEXPECTED(RETURN_VALUE_USED(opline))) { diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index d5860da23b4c..5ca7779807ca 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -24947,16 +24947,37 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP } } } else { + HashTable *ht = Z_ARRVAL_P(object_ptr); dim = RT_CONSTANT(opline, opline->op2); + /* Order is critical: fetch the data value (which may emit an + * "Undefined variable" warning that can recurse into user code + * via a user output handler) before resolving variable_ptr. + * Resolving variable_ptr only afterwards guarantees a fresh + * bucket pointer that cannot have been invalidated by user + * code freeing the array, rehashing it, or unsetting keys. */ + value = RT_CONSTANT((opline+1), (opline+1)->op1); + if (IS_CONST == IS_CV && UNEXPECTED(Z_TYPE_P(value) == IS_UNDEF)) { + /* Temporarily addref the array around the warning so we + * can detect a full destruction (last ref dropped by + * reentrant user code) and bail out cleanly. Mirrors the + * IS_UNUSED branch above. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + value = zval_undefined_cv((opline+1)->op1.var EXECUTE_DATA_CC); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) { + zend_array_destroy(ht); + goto assign_dim_error; + } + } if (IS_CONST == IS_CONST) { - variable_ptr = zend_fetch_dimension_address_inner_W_CONST(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W_CONST(ht, dim EXECUTE_DATA_CC); } else { - variable_ptr = zend_fetch_dimension_address_inner_W(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W(ht, dim EXECUTE_DATA_CC); } if (UNEXPECTED(variable_ptr == NULL)) { goto assign_dim_error; } - value = RT_CONSTANT((opline+1), (opline+1)->op1); value = zend_assign_to_variable_ex(variable_ptr, value, IS_CONST, EX_USES_STRICT_TYPES(), &garbage); } if (UNEXPECTED(RETURN_VALUE_USED(opline))) { @@ -25105,16 +25126,37 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP } } } else { + HashTable *ht = Z_ARRVAL_P(object_ptr); dim = RT_CONSTANT(opline, opline->op2); + /* Order is critical: fetch the data value (which may emit an + * "Undefined variable" warning that can recurse into user code + * via a user output handler) before resolving variable_ptr. + * Resolving variable_ptr only afterwards guarantees a fresh + * bucket pointer that cannot have been invalidated by user + * code freeing the array, rehashing it, or unsetting keys. */ + value = _get_zval_ptr_tmp((opline+1)->op1.var EXECUTE_DATA_CC); + if (IS_TMP_VAR == IS_CV && UNEXPECTED(Z_TYPE_P(value) == IS_UNDEF)) { + /* Temporarily addref the array around the warning so we + * can detect a full destruction (last ref dropped by + * reentrant user code) and bail out cleanly. Mirrors the + * IS_UNUSED branch above. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + value = zval_undefined_cv((opline+1)->op1.var EXECUTE_DATA_CC); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) { + zend_array_destroy(ht); + goto assign_dim_error; + } + } if (IS_CONST == IS_CONST) { - variable_ptr = zend_fetch_dimension_address_inner_W_CONST(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W_CONST(ht, dim EXECUTE_DATA_CC); } else { - variable_ptr = zend_fetch_dimension_address_inner_W(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W(ht, dim EXECUTE_DATA_CC); } if (UNEXPECTED(variable_ptr == NULL)) { goto assign_dim_error; } - value = _get_zval_ptr_tmp((opline+1)->op1.var EXECUTE_DATA_CC); value = zend_assign_to_variable_ex(variable_ptr, value, IS_TMP_VAR, EX_USES_STRICT_TYPES(), &garbage); } if (UNEXPECTED(RETURN_VALUE_USED(opline))) { @@ -25259,16 +25301,37 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP } } } else { + HashTable *ht = Z_ARRVAL_P(object_ptr); dim = RT_CONSTANT(opline, opline->op2); + /* Order is critical: fetch the data value (which may emit an + * "Undefined variable" warning that can recurse into user code + * via a user output handler) before resolving variable_ptr. + * Resolving variable_ptr only afterwards guarantees a fresh + * bucket pointer that cannot have been invalidated by user + * code freeing the array, rehashing it, or unsetting keys. */ + value = EX_VAR((opline+1)->op1.var); + if (IS_CV == IS_CV && UNEXPECTED(Z_TYPE_P(value) == IS_UNDEF)) { + /* Temporarily addref the array around the warning so we + * can detect a full destruction (last ref dropped by + * reentrant user code) and bail out cleanly. Mirrors the + * IS_UNUSED branch above. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + value = zval_undefined_cv((opline+1)->op1.var EXECUTE_DATA_CC); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) { + zend_array_destroy(ht); + goto assign_dim_error; + } + } if (IS_CONST == IS_CONST) { - variable_ptr = zend_fetch_dimension_address_inner_W_CONST(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W_CONST(ht, dim EXECUTE_DATA_CC); } else { - variable_ptr = zend_fetch_dimension_address_inner_W(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W(ht, dim EXECUTE_DATA_CC); } if (UNEXPECTED(variable_ptr == NULL)) { goto assign_dim_error; } - value = _get_zval_ptr_cv_BP_VAR_R((opline+1)->op1.var EXECUTE_DATA_CC); value = zend_assign_to_variable_ex(variable_ptr, value, IS_CV, EX_USES_STRICT_TYPES(), &garbage); } if (UNEXPECTED(RETURN_VALUE_USED(opline))) { @@ -27652,16 +27715,37 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP } } } else { + HashTable *ht = Z_ARRVAL_P(object_ptr); dim = _get_zval_ptr_tmp(opline->op2.var EXECUTE_DATA_CC); + /* Order is critical: fetch the data value (which may emit an + * "Undefined variable" warning that can recurse into user code + * via a user output handler) before resolving variable_ptr. + * Resolving variable_ptr only afterwards guarantees a fresh + * bucket pointer that cannot have been invalidated by user + * code freeing the array, rehashing it, or unsetting keys. */ + value = RT_CONSTANT((opline+1), (opline+1)->op1); + if (IS_CONST == IS_CV && UNEXPECTED(Z_TYPE_P(value) == IS_UNDEF)) { + /* Temporarily addref the array around the warning so we + * can detect a full destruction (last ref dropped by + * reentrant user code) and bail out cleanly. Mirrors the + * IS_UNUSED branch above. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + value = zval_undefined_cv((opline+1)->op1.var EXECUTE_DATA_CC); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) { + zend_array_destroy(ht); + goto assign_dim_error; + } + } if (IS_TMP_VAR == IS_CONST) { - variable_ptr = zend_fetch_dimension_address_inner_W_CONST(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W_CONST(ht, dim EXECUTE_DATA_CC); } else { - variable_ptr = zend_fetch_dimension_address_inner_W(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W(ht, dim EXECUTE_DATA_CC); } if (UNEXPECTED(variable_ptr == NULL)) { goto assign_dim_error; } - value = RT_CONSTANT((opline+1), (opline+1)->op1); value = zend_assign_to_variable_ex(variable_ptr, value, IS_CONST, EX_USES_STRICT_TYPES(), &garbage); } if (UNEXPECTED(RETURN_VALUE_USED(opline))) { @@ -27809,16 +27893,37 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP } } } else { + HashTable *ht = Z_ARRVAL_P(object_ptr); dim = _get_zval_ptr_tmp(opline->op2.var EXECUTE_DATA_CC); + /* Order is critical: fetch the data value (which may emit an + * "Undefined variable" warning that can recurse into user code + * via a user output handler) before resolving variable_ptr. + * Resolving variable_ptr only afterwards guarantees a fresh + * bucket pointer that cannot have been invalidated by user + * code freeing the array, rehashing it, or unsetting keys. */ + value = _get_zval_ptr_tmp((opline+1)->op1.var EXECUTE_DATA_CC); + if (IS_TMP_VAR == IS_CV && UNEXPECTED(Z_TYPE_P(value) == IS_UNDEF)) { + /* Temporarily addref the array around the warning so we + * can detect a full destruction (last ref dropped by + * reentrant user code) and bail out cleanly. Mirrors the + * IS_UNUSED branch above. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + value = zval_undefined_cv((opline+1)->op1.var EXECUTE_DATA_CC); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) { + zend_array_destroy(ht); + goto assign_dim_error; + } + } if (IS_TMP_VAR == IS_CONST) { - variable_ptr = zend_fetch_dimension_address_inner_W_CONST(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W_CONST(ht, dim EXECUTE_DATA_CC); } else { - variable_ptr = zend_fetch_dimension_address_inner_W(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W(ht, dim EXECUTE_DATA_CC); } if (UNEXPECTED(variable_ptr == NULL)) { goto assign_dim_error; } - value = _get_zval_ptr_tmp((opline+1)->op1.var EXECUTE_DATA_CC); value = zend_assign_to_variable_ex(variable_ptr, value, IS_TMP_VAR, EX_USES_STRICT_TYPES(), &garbage); } if (UNEXPECTED(RETURN_VALUE_USED(opline))) { @@ -27962,16 +28067,37 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP } } } else { + HashTable *ht = Z_ARRVAL_P(object_ptr); dim = _get_zval_ptr_tmp(opline->op2.var EXECUTE_DATA_CC); + /* Order is critical: fetch the data value (which may emit an + * "Undefined variable" warning that can recurse into user code + * via a user output handler) before resolving variable_ptr. + * Resolving variable_ptr only afterwards guarantees a fresh + * bucket pointer that cannot have been invalidated by user + * code freeing the array, rehashing it, or unsetting keys. */ + value = EX_VAR((opline+1)->op1.var); + if (IS_CV == IS_CV && UNEXPECTED(Z_TYPE_P(value) == IS_UNDEF)) { + /* Temporarily addref the array around the warning so we + * can detect a full destruction (last ref dropped by + * reentrant user code) and bail out cleanly. Mirrors the + * IS_UNUSED branch above. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + value = zval_undefined_cv((opline+1)->op1.var EXECUTE_DATA_CC); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) { + zend_array_destroy(ht); + goto assign_dim_error; + } + } if (IS_TMP_VAR == IS_CONST) { - variable_ptr = zend_fetch_dimension_address_inner_W_CONST(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W_CONST(ht, dim EXECUTE_DATA_CC); } else { - variable_ptr = zend_fetch_dimension_address_inner_W(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W(ht, dim EXECUTE_DATA_CC); } if (UNEXPECTED(variable_ptr == NULL)) { goto assign_dim_error; } - value = _get_zval_ptr_cv_BP_VAR_R((opline+1)->op1.var EXECUTE_DATA_CC); value = zend_assign_to_variable_ex(variable_ptr, value, IS_CV, EX_USES_STRICT_TYPES(), &garbage); } if (UNEXPECTED(RETURN_VALUE_USED(opline))) { @@ -28982,16 +29108,37 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP } } } else { + HashTable *ht = Z_ARRVAL_P(object_ptr); dim = NULL; + /* Order is critical: fetch the data value (which may emit an + * "Undefined variable" warning that can recurse into user code + * via a user output handler) before resolving variable_ptr. + * Resolving variable_ptr only afterwards guarantees a fresh + * bucket pointer that cannot have been invalidated by user + * code freeing the array, rehashing it, or unsetting keys. */ + value = RT_CONSTANT((opline+1), (opline+1)->op1); + if (IS_CONST == IS_CV && UNEXPECTED(Z_TYPE_P(value) == IS_UNDEF)) { + /* Temporarily addref the array around the warning so we + * can detect a full destruction (last ref dropped by + * reentrant user code) and bail out cleanly. Mirrors the + * IS_UNUSED branch above. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + value = zval_undefined_cv((opline+1)->op1.var EXECUTE_DATA_CC); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) { + zend_array_destroy(ht); + goto assign_dim_error; + } + } if (IS_UNUSED == IS_CONST) { - variable_ptr = zend_fetch_dimension_address_inner_W_CONST(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W_CONST(ht, dim EXECUTE_DATA_CC); } else { - variable_ptr = zend_fetch_dimension_address_inner_W(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W(ht, dim EXECUTE_DATA_CC); } if (UNEXPECTED(variable_ptr == NULL)) { goto assign_dim_error; } - value = RT_CONSTANT((opline+1), (opline+1)->op1); value = zend_assign_to_variable_ex(variable_ptr, value, IS_CONST, EX_USES_STRICT_TYPES(), &garbage); } if (UNEXPECTED(RETURN_VALUE_USED(opline))) { @@ -29140,16 +29287,37 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP } } } else { + HashTable *ht = Z_ARRVAL_P(object_ptr); dim = NULL; + /* Order is critical: fetch the data value (which may emit an + * "Undefined variable" warning that can recurse into user code + * via a user output handler) before resolving variable_ptr. + * Resolving variable_ptr only afterwards guarantees a fresh + * bucket pointer that cannot have been invalidated by user + * code freeing the array, rehashing it, or unsetting keys. */ + value = _get_zval_ptr_tmp((opline+1)->op1.var EXECUTE_DATA_CC); + if (IS_TMP_VAR == IS_CV && UNEXPECTED(Z_TYPE_P(value) == IS_UNDEF)) { + /* Temporarily addref the array around the warning so we + * can detect a full destruction (last ref dropped by + * reentrant user code) and bail out cleanly. Mirrors the + * IS_UNUSED branch above. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + value = zval_undefined_cv((opline+1)->op1.var EXECUTE_DATA_CC); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) { + zend_array_destroy(ht); + goto assign_dim_error; + } + } if (IS_UNUSED == IS_CONST) { - variable_ptr = zend_fetch_dimension_address_inner_W_CONST(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W_CONST(ht, dim EXECUTE_DATA_CC); } else { - variable_ptr = zend_fetch_dimension_address_inner_W(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W(ht, dim EXECUTE_DATA_CC); } if (UNEXPECTED(variable_ptr == NULL)) { goto assign_dim_error; } - value = _get_zval_ptr_tmp((opline+1)->op1.var EXECUTE_DATA_CC); value = zend_assign_to_variable_ex(variable_ptr, value, IS_TMP_VAR, EX_USES_STRICT_TYPES(), &garbage); } if (UNEXPECTED(RETURN_VALUE_USED(opline))) { @@ -29294,16 +29462,37 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP } } } else { + HashTable *ht = Z_ARRVAL_P(object_ptr); dim = NULL; + /* Order is critical: fetch the data value (which may emit an + * "Undefined variable" warning that can recurse into user code + * via a user output handler) before resolving variable_ptr. + * Resolving variable_ptr only afterwards guarantees a fresh + * bucket pointer that cannot have been invalidated by user + * code freeing the array, rehashing it, or unsetting keys. */ + value = EX_VAR((opline+1)->op1.var); + if (IS_CV == IS_CV && UNEXPECTED(Z_TYPE_P(value) == IS_UNDEF)) { + /* Temporarily addref the array around the warning so we + * can detect a full destruction (last ref dropped by + * reentrant user code) and bail out cleanly. Mirrors the + * IS_UNUSED branch above. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + value = zval_undefined_cv((opline+1)->op1.var EXECUTE_DATA_CC); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) { + zend_array_destroy(ht); + goto assign_dim_error; + } + } if (IS_UNUSED == IS_CONST) { - variable_ptr = zend_fetch_dimension_address_inner_W_CONST(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W_CONST(ht, dim EXECUTE_DATA_CC); } else { - variable_ptr = zend_fetch_dimension_address_inner_W(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W(ht, dim EXECUTE_DATA_CC); } if (UNEXPECTED(variable_ptr == NULL)) { goto assign_dim_error; } - value = _get_zval_ptr_cv_BP_VAR_R((opline+1)->op1.var EXECUTE_DATA_CC); value = zend_assign_to_variable_ex(variable_ptr, value, IS_CV, EX_USES_STRICT_TYPES(), &garbage); } if (UNEXPECTED(RETURN_VALUE_USED(opline))) { @@ -31479,16 +31668,37 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP } } } else { + HashTable *ht = Z_ARRVAL_P(object_ptr); dim = EX_VAR(opline->op2.var); + /* Order is critical: fetch the data value (which may emit an + * "Undefined variable" warning that can recurse into user code + * via a user output handler) before resolving variable_ptr. + * Resolving variable_ptr only afterwards guarantees a fresh + * bucket pointer that cannot have been invalidated by user + * code freeing the array, rehashing it, or unsetting keys. */ + value = RT_CONSTANT((opline+1), (opline+1)->op1); + if (IS_CONST == IS_CV && UNEXPECTED(Z_TYPE_P(value) == IS_UNDEF)) { + /* Temporarily addref the array around the warning so we + * can detect a full destruction (last ref dropped by + * reentrant user code) and bail out cleanly. Mirrors the + * IS_UNUSED branch above. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + value = zval_undefined_cv((opline+1)->op1.var EXECUTE_DATA_CC); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) { + zend_array_destroy(ht); + goto assign_dim_error; + } + } if (IS_CV == IS_CONST) { - variable_ptr = zend_fetch_dimension_address_inner_W_CONST(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W_CONST(ht, dim EXECUTE_DATA_CC); } else { - variable_ptr = zend_fetch_dimension_address_inner_W(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W(ht, dim EXECUTE_DATA_CC); } if (UNEXPECTED(variable_ptr == NULL)) { goto assign_dim_error; } - value = RT_CONSTANT((opline+1), (opline+1)->op1); value = zend_assign_to_variable_ex(variable_ptr, value, IS_CONST, EX_USES_STRICT_TYPES(), &garbage); } if (UNEXPECTED(RETURN_VALUE_USED(opline))) { @@ -31637,16 +31847,37 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP } } } else { + HashTable *ht = Z_ARRVAL_P(object_ptr); dim = EX_VAR(opline->op2.var); + /* Order is critical: fetch the data value (which may emit an + * "Undefined variable" warning that can recurse into user code + * via a user output handler) before resolving variable_ptr. + * Resolving variable_ptr only afterwards guarantees a fresh + * bucket pointer that cannot have been invalidated by user + * code freeing the array, rehashing it, or unsetting keys. */ + value = _get_zval_ptr_tmp((opline+1)->op1.var EXECUTE_DATA_CC); + if (IS_TMP_VAR == IS_CV && UNEXPECTED(Z_TYPE_P(value) == IS_UNDEF)) { + /* Temporarily addref the array around the warning so we + * can detect a full destruction (last ref dropped by + * reentrant user code) and bail out cleanly. Mirrors the + * IS_UNUSED branch above. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + value = zval_undefined_cv((opline+1)->op1.var EXECUTE_DATA_CC); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) { + zend_array_destroy(ht); + goto assign_dim_error; + } + } if (IS_CV == IS_CONST) { - variable_ptr = zend_fetch_dimension_address_inner_W_CONST(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W_CONST(ht, dim EXECUTE_DATA_CC); } else { - variable_ptr = zend_fetch_dimension_address_inner_W(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W(ht, dim EXECUTE_DATA_CC); } if (UNEXPECTED(variable_ptr == NULL)) { goto assign_dim_error; } - value = _get_zval_ptr_tmp((opline+1)->op1.var EXECUTE_DATA_CC); value = zend_assign_to_variable_ex(variable_ptr, value, IS_TMP_VAR, EX_USES_STRICT_TYPES(), &garbage); } if (UNEXPECTED(RETURN_VALUE_USED(opline))) { @@ -31791,16 +32022,37 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP } } } else { + HashTable *ht = Z_ARRVAL_P(object_ptr); dim = EX_VAR(opline->op2.var); + /* Order is critical: fetch the data value (which may emit an + * "Undefined variable" warning that can recurse into user code + * via a user output handler) before resolving variable_ptr. + * Resolving variable_ptr only afterwards guarantees a fresh + * bucket pointer that cannot have been invalidated by user + * code freeing the array, rehashing it, or unsetting keys. */ + value = EX_VAR((opline+1)->op1.var); + if (IS_CV == IS_CV && UNEXPECTED(Z_TYPE_P(value) == IS_UNDEF)) { + /* Temporarily addref the array around the warning so we + * can detect a full destruction (last ref dropped by + * reentrant user code) and bail out cleanly. Mirrors the + * IS_UNUSED branch above. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + value = zval_undefined_cv((opline+1)->op1.var EXECUTE_DATA_CC); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) { + zend_array_destroy(ht); + goto assign_dim_error; + } + } if (IS_CV == IS_CONST) { - variable_ptr = zend_fetch_dimension_address_inner_W_CONST(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W_CONST(ht, dim EXECUTE_DATA_CC); } else { - variable_ptr = zend_fetch_dimension_address_inner_W(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W(ht, dim EXECUTE_DATA_CC); } if (UNEXPECTED(variable_ptr == NULL)) { goto assign_dim_error; } - value = _get_zval_ptr_cv_BP_VAR_R((opline+1)->op1.var EXECUTE_DATA_CC); value = zend_assign_to_variable_ex(variable_ptr, value, IS_CV, EX_USES_STRICT_TYPES(), &garbage); } if (UNEXPECTED(RETURN_VALUE_USED(opline))) { @@ -43052,16 +43304,37 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP } } } else { + HashTable *ht = Z_ARRVAL_P(object_ptr); dim = RT_CONSTANT(opline, opline->op2); + /* Order is critical: fetch the data value (which may emit an + * "Undefined variable" warning that can recurse into user code + * via a user output handler) before resolving variable_ptr. + * Resolving variable_ptr only afterwards guarantees a fresh + * bucket pointer that cannot have been invalidated by user + * code freeing the array, rehashing it, or unsetting keys. */ + value = RT_CONSTANT((opline+1), (opline+1)->op1); + if (IS_CONST == IS_CV && UNEXPECTED(Z_TYPE_P(value) == IS_UNDEF)) { + /* Temporarily addref the array around the warning so we + * can detect a full destruction (last ref dropped by + * reentrant user code) and bail out cleanly. Mirrors the + * IS_UNUSED branch above. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + value = zval_undefined_cv((opline+1)->op1.var EXECUTE_DATA_CC); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) { + zend_array_destroy(ht); + goto assign_dim_error; + } + } if (IS_CONST == IS_CONST) { - variable_ptr = zend_fetch_dimension_address_inner_W_CONST(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W_CONST(ht, dim EXECUTE_DATA_CC); } else { - variable_ptr = zend_fetch_dimension_address_inner_W(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W(ht, dim EXECUTE_DATA_CC); } if (UNEXPECTED(variable_ptr == NULL)) { goto assign_dim_error; } - value = RT_CONSTANT((opline+1), (opline+1)->op1); value = zend_assign_to_variable_ex(variable_ptr, value, IS_CONST, EX_USES_STRICT_TYPES(), &garbage); } if (UNEXPECTED(RETURN_VALUE_USED(opline))) { @@ -43211,16 +43484,37 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP } } } else { + HashTable *ht = Z_ARRVAL_P(object_ptr); dim = RT_CONSTANT(opline, opline->op2); + /* Order is critical: fetch the data value (which may emit an + * "Undefined variable" warning that can recurse into user code + * via a user output handler) before resolving variable_ptr. + * Resolving variable_ptr only afterwards guarantees a fresh + * bucket pointer that cannot have been invalidated by user + * code freeing the array, rehashing it, or unsetting keys. */ + value = _get_zval_ptr_tmp((opline+1)->op1.var EXECUTE_DATA_CC); + if (IS_TMP_VAR == IS_CV && UNEXPECTED(Z_TYPE_P(value) == IS_UNDEF)) { + /* Temporarily addref the array around the warning so we + * can detect a full destruction (last ref dropped by + * reentrant user code) and bail out cleanly. Mirrors the + * IS_UNUSED branch above. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + value = zval_undefined_cv((opline+1)->op1.var EXECUTE_DATA_CC); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) { + zend_array_destroy(ht); + goto assign_dim_error; + } + } if (IS_CONST == IS_CONST) { - variable_ptr = zend_fetch_dimension_address_inner_W_CONST(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W_CONST(ht, dim EXECUTE_DATA_CC); } else { - variable_ptr = zend_fetch_dimension_address_inner_W(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W(ht, dim EXECUTE_DATA_CC); } if (UNEXPECTED(variable_ptr == NULL)) { goto assign_dim_error; } - value = _get_zval_ptr_tmp((opline+1)->op1.var EXECUTE_DATA_CC); value = zend_assign_to_variable_ex(variable_ptr, value, IS_TMP_VAR, EX_USES_STRICT_TYPES(), &garbage); } if (UNEXPECTED(RETURN_VALUE_USED(opline))) { @@ -43366,16 +43660,37 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP } } } else { + HashTable *ht = Z_ARRVAL_P(object_ptr); dim = RT_CONSTANT(opline, opline->op2); + /* Order is critical: fetch the data value (which may emit an + * "Undefined variable" warning that can recurse into user code + * via a user output handler) before resolving variable_ptr. + * Resolving variable_ptr only afterwards guarantees a fresh + * bucket pointer that cannot have been invalidated by user + * code freeing the array, rehashing it, or unsetting keys. */ + value = EX_VAR((opline+1)->op1.var); + if (IS_CV == IS_CV && UNEXPECTED(Z_TYPE_P(value) == IS_UNDEF)) { + /* Temporarily addref the array around the warning so we + * can detect a full destruction (last ref dropped by + * reentrant user code) and bail out cleanly. Mirrors the + * IS_UNUSED branch above. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + value = zval_undefined_cv((opline+1)->op1.var EXECUTE_DATA_CC); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) { + zend_array_destroy(ht); + goto assign_dim_error; + } + } if (IS_CONST == IS_CONST) { - variable_ptr = zend_fetch_dimension_address_inner_W_CONST(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W_CONST(ht, dim EXECUTE_DATA_CC); } else { - variable_ptr = zend_fetch_dimension_address_inner_W(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W(ht, dim EXECUTE_DATA_CC); } if (UNEXPECTED(variable_ptr == NULL)) { goto assign_dim_error; } - value = _get_zval_ptr_cv_BP_VAR_R((opline+1)->op1.var EXECUTE_DATA_CC); value = zend_assign_to_variable_ex(variable_ptr, value, IS_CV, EX_USES_STRICT_TYPES(), &garbage); } if (UNEXPECTED(RETURN_VALUE_USED(opline))) { @@ -46872,16 +47187,37 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP } } } else { + HashTable *ht = Z_ARRVAL_P(object_ptr); dim = _get_zval_ptr_tmp(opline->op2.var EXECUTE_DATA_CC); + /* Order is critical: fetch the data value (which may emit an + * "Undefined variable" warning that can recurse into user code + * via a user output handler) before resolving variable_ptr. + * Resolving variable_ptr only afterwards guarantees a fresh + * bucket pointer that cannot have been invalidated by user + * code freeing the array, rehashing it, or unsetting keys. */ + value = RT_CONSTANT((opline+1), (opline+1)->op1); + if (IS_CONST == IS_CV && UNEXPECTED(Z_TYPE_P(value) == IS_UNDEF)) { + /* Temporarily addref the array around the warning so we + * can detect a full destruction (last ref dropped by + * reentrant user code) and bail out cleanly. Mirrors the + * IS_UNUSED branch above. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + value = zval_undefined_cv((opline+1)->op1.var EXECUTE_DATA_CC); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) { + zend_array_destroy(ht); + goto assign_dim_error; + } + } if (IS_TMP_VAR == IS_CONST) { - variable_ptr = zend_fetch_dimension_address_inner_W_CONST(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W_CONST(ht, dim EXECUTE_DATA_CC); } else { - variable_ptr = zend_fetch_dimension_address_inner_W(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W(ht, dim EXECUTE_DATA_CC); } if (UNEXPECTED(variable_ptr == NULL)) { goto assign_dim_error; } - value = RT_CONSTANT((opline+1), (opline+1)->op1); value = zend_assign_to_variable_ex(variable_ptr, value, IS_CONST, EX_USES_STRICT_TYPES(), &garbage); } if (UNEXPECTED(RETURN_VALUE_USED(opline))) { @@ -47030,16 +47366,37 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP } } } else { + HashTable *ht = Z_ARRVAL_P(object_ptr); dim = _get_zval_ptr_tmp(opline->op2.var EXECUTE_DATA_CC); + /* Order is critical: fetch the data value (which may emit an + * "Undefined variable" warning that can recurse into user code + * via a user output handler) before resolving variable_ptr. + * Resolving variable_ptr only afterwards guarantees a fresh + * bucket pointer that cannot have been invalidated by user + * code freeing the array, rehashing it, or unsetting keys. */ + value = _get_zval_ptr_tmp((opline+1)->op1.var EXECUTE_DATA_CC); + if (IS_TMP_VAR == IS_CV && UNEXPECTED(Z_TYPE_P(value) == IS_UNDEF)) { + /* Temporarily addref the array around the warning so we + * can detect a full destruction (last ref dropped by + * reentrant user code) and bail out cleanly. Mirrors the + * IS_UNUSED branch above. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + value = zval_undefined_cv((opline+1)->op1.var EXECUTE_DATA_CC); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) { + zend_array_destroy(ht); + goto assign_dim_error; + } + } if (IS_TMP_VAR == IS_CONST) { - variable_ptr = zend_fetch_dimension_address_inner_W_CONST(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W_CONST(ht, dim EXECUTE_DATA_CC); } else { - variable_ptr = zend_fetch_dimension_address_inner_W(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W(ht, dim EXECUTE_DATA_CC); } if (UNEXPECTED(variable_ptr == NULL)) { goto assign_dim_error; } - value = _get_zval_ptr_tmp((opline+1)->op1.var EXECUTE_DATA_CC); value = zend_assign_to_variable_ex(variable_ptr, value, IS_TMP_VAR, EX_USES_STRICT_TYPES(), &garbage); } if (UNEXPECTED(RETURN_VALUE_USED(opline))) { @@ -47184,16 +47541,37 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP } } } else { + HashTable *ht = Z_ARRVAL_P(object_ptr); dim = _get_zval_ptr_tmp(opline->op2.var EXECUTE_DATA_CC); + /* Order is critical: fetch the data value (which may emit an + * "Undefined variable" warning that can recurse into user code + * via a user output handler) before resolving variable_ptr. + * Resolving variable_ptr only afterwards guarantees a fresh + * bucket pointer that cannot have been invalidated by user + * code freeing the array, rehashing it, or unsetting keys. */ + value = EX_VAR((opline+1)->op1.var); + if (IS_CV == IS_CV && UNEXPECTED(Z_TYPE_P(value) == IS_UNDEF)) { + /* Temporarily addref the array around the warning so we + * can detect a full destruction (last ref dropped by + * reentrant user code) and bail out cleanly. Mirrors the + * IS_UNUSED branch above. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + value = zval_undefined_cv((opline+1)->op1.var EXECUTE_DATA_CC); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) { + zend_array_destroy(ht); + goto assign_dim_error; + } + } if (IS_TMP_VAR == IS_CONST) { - variable_ptr = zend_fetch_dimension_address_inner_W_CONST(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W_CONST(ht, dim EXECUTE_DATA_CC); } else { - variable_ptr = zend_fetch_dimension_address_inner_W(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W(ht, dim EXECUTE_DATA_CC); } if (UNEXPECTED(variable_ptr == NULL)) { goto assign_dim_error; } - value = _get_zval_ptr_cv_BP_VAR_R((opline+1)->op1.var EXECUTE_DATA_CC); value = zend_assign_to_variable_ex(variable_ptr, value, IS_CV, EX_USES_STRICT_TYPES(), &garbage); } if (UNEXPECTED(RETURN_VALUE_USED(opline))) { @@ -48713,16 +49091,37 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP } } } else { + HashTable *ht = Z_ARRVAL_P(object_ptr); dim = NULL; + /* Order is critical: fetch the data value (which may emit an + * "Undefined variable" warning that can recurse into user code + * via a user output handler) before resolving variable_ptr. + * Resolving variable_ptr only afterwards guarantees a fresh + * bucket pointer that cannot have been invalidated by user + * code freeing the array, rehashing it, or unsetting keys. */ + value = RT_CONSTANT((opline+1), (opline+1)->op1); + if (IS_CONST == IS_CV && UNEXPECTED(Z_TYPE_P(value) == IS_UNDEF)) { + /* Temporarily addref the array around the warning so we + * can detect a full destruction (last ref dropped by + * reentrant user code) and bail out cleanly. Mirrors the + * IS_UNUSED branch above. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + value = zval_undefined_cv((opline+1)->op1.var EXECUTE_DATA_CC); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) { + zend_array_destroy(ht); + goto assign_dim_error; + } + } if (IS_UNUSED == IS_CONST) { - variable_ptr = zend_fetch_dimension_address_inner_W_CONST(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W_CONST(ht, dim EXECUTE_DATA_CC); } else { - variable_ptr = zend_fetch_dimension_address_inner_W(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W(ht, dim EXECUTE_DATA_CC); } if (UNEXPECTED(variable_ptr == NULL)) { goto assign_dim_error; } - value = RT_CONSTANT((opline+1), (opline+1)->op1); value = zend_assign_to_variable_ex(variable_ptr, value, IS_CONST, EX_USES_STRICT_TYPES(), &garbage); } if (UNEXPECTED(RETURN_VALUE_USED(opline))) { @@ -48872,16 +49271,37 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP } } } else { + HashTable *ht = Z_ARRVAL_P(object_ptr); dim = NULL; + /* Order is critical: fetch the data value (which may emit an + * "Undefined variable" warning that can recurse into user code + * via a user output handler) before resolving variable_ptr. + * Resolving variable_ptr only afterwards guarantees a fresh + * bucket pointer that cannot have been invalidated by user + * code freeing the array, rehashing it, or unsetting keys. */ + value = _get_zval_ptr_tmp((opline+1)->op1.var EXECUTE_DATA_CC); + if (IS_TMP_VAR == IS_CV && UNEXPECTED(Z_TYPE_P(value) == IS_UNDEF)) { + /* Temporarily addref the array around the warning so we + * can detect a full destruction (last ref dropped by + * reentrant user code) and bail out cleanly. Mirrors the + * IS_UNUSED branch above. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + value = zval_undefined_cv((opline+1)->op1.var EXECUTE_DATA_CC); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) { + zend_array_destroy(ht); + goto assign_dim_error; + } + } if (IS_UNUSED == IS_CONST) { - variable_ptr = zend_fetch_dimension_address_inner_W_CONST(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W_CONST(ht, dim EXECUTE_DATA_CC); } else { - variable_ptr = zend_fetch_dimension_address_inner_W(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W(ht, dim EXECUTE_DATA_CC); } if (UNEXPECTED(variable_ptr == NULL)) { goto assign_dim_error; } - value = _get_zval_ptr_tmp((opline+1)->op1.var EXECUTE_DATA_CC); value = zend_assign_to_variable_ex(variable_ptr, value, IS_TMP_VAR, EX_USES_STRICT_TYPES(), &garbage); } if (UNEXPECTED(RETURN_VALUE_USED(opline))) { @@ -49027,16 +49447,37 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP } } } else { + HashTable *ht = Z_ARRVAL_P(object_ptr); dim = NULL; + /* Order is critical: fetch the data value (which may emit an + * "Undefined variable" warning that can recurse into user code + * via a user output handler) before resolving variable_ptr. + * Resolving variable_ptr only afterwards guarantees a fresh + * bucket pointer that cannot have been invalidated by user + * code freeing the array, rehashing it, or unsetting keys. */ + value = EX_VAR((opline+1)->op1.var); + if (IS_CV == IS_CV && UNEXPECTED(Z_TYPE_P(value) == IS_UNDEF)) { + /* Temporarily addref the array around the warning so we + * can detect a full destruction (last ref dropped by + * reentrant user code) and bail out cleanly. Mirrors the + * IS_UNUSED branch above. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + value = zval_undefined_cv((opline+1)->op1.var EXECUTE_DATA_CC); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) { + zend_array_destroy(ht); + goto assign_dim_error; + } + } if (IS_UNUSED == IS_CONST) { - variable_ptr = zend_fetch_dimension_address_inner_W_CONST(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W_CONST(ht, dim EXECUTE_DATA_CC); } else { - variable_ptr = zend_fetch_dimension_address_inner_W(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W(ht, dim EXECUTE_DATA_CC); } if (UNEXPECTED(variable_ptr == NULL)) { goto assign_dim_error; } - value = _get_zval_ptr_cv_BP_VAR_R((opline+1)->op1.var EXECUTE_DATA_CC); value = zend_assign_to_variable_ex(variable_ptr, value, IS_CV, EX_USES_STRICT_TYPES(), &garbage); } if (UNEXPECTED(RETURN_VALUE_USED(opline))) { @@ -51991,16 +52432,37 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP } } } else { + HashTable *ht = Z_ARRVAL_P(object_ptr); dim = EX_VAR(opline->op2.var); + /* Order is critical: fetch the data value (which may emit an + * "Undefined variable" warning that can recurse into user code + * via a user output handler) before resolving variable_ptr. + * Resolving variable_ptr only afterwards guarantees a fresh + * bucket pointer that cannot have been invalidated by user + * code freeing the array, rehashing it, or unsetting keys. */ + value = RT_CONSTANT((opline+1), (opline+1)->op1); + if (IS_CONST == IS_CV && UNEXPECTED(Z_TYPE_P(value) == IS_UNDEF)) { + /* Temporarily addref the array around the warning so we + * can detect a full destruction (last ref dropped by + * reentrant user code) and bail out cleanly. Mirrors the + * IS_UNUSED branch above. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + value = zval_undefined_cv((opline+1)->op1.var EXECUTE_DATA_CC); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) { + zend_array_destroy(ht); + goto assign_dim_error; + } + } if (IS_CV == IS_CONST) { - variable_ptr = zend_fetch_dimension_address_inner_W_CONST(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W_CONST(ht, dim EXECUTE_DATA_CC); } else { - variable_ptr = zend_fetch_dimension_address_inner_W(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W(ht, dim EXECUTE_DATA_CC); } if (UNEXPECTED(variable_ptr == NULL)) { goto assign_dim_error; } - value = RT_CONSTANT((opline+1), (opline+1)->op1); value = zend_assign_to_variable_ex(variable_ptr, value, IS_CONST, EX_USES_STRICT_TYPES(), &garbage); } if (UNEXPECTED(RETURN_VALUE_USED(opline))) { @@ -52150,16 +52612,37 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP } } } else { + HashTable *ht = Z_ARRVAL_P(object_ptr); dim = EX_VAR(opline->op2.var); + /* Order is critical: fetch the data value (which may emit an + * "Undefined variable" warning that can recurse into user code + * via a user output handler) before resolving variable_ptr. + * Resolving variable_ptr only afterwards guarantees a fresh + * bucket pointer that cannot have been invalidated by user + * code freeing the array, rehashing it, or unsetting keys. */ + value = _get_zval_ptr_tmp((opline+1)->op1.var EXECUTE_DATA_CC); + if (IS_TMP_VAR == IS_CV && UNEXPECTED(Z_TYPE_P(value) == IS_UNDEF)) { + /* Temporarily addref the array around the warning so we + * can detect a full destruction (last ref dropped by + * reentrant user code) and bail out cleanly. Mirrors the + * IS_UNUSED branch above. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + value = zval_undefined_cv((opline+1)->op1.var EXECUTE_DATA_CC); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) { + zend_array_destroy(ht); + goto assign_dim_error; + } + } if (IS_CV == IS_CONST) { - variable_ptr = zend_fetch_dimension_address_inner_W_CONST(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W_CONST(ht, dim EXECUTE_DATA_CC); } else { - variable_ptr = zend_fetch_dimension_address_inner_W(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W(ht, dim EXECUTE_DATA_CC); } if (UNEXPECTED(variable_ptr == NULL)) { goto assign_dim_error; } - value = _get_zval_ptr_tmp((opline+1)->op1.var EXECUTE_DATA_CC); value = zend_assign_to_variable_ex(variable_ptr, value, IS_TMP_VAR, EX_USES_STRICT_TYPES(), &garbage); } if (UNEXPECTED(RETURN_VALUE_USED(opline))) { @@ -52305,16 +52788,37 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_DIM_SP } } } else { + HashTable *ht = Z_ARRVAL_P(object_ptr); dim = EX_VAR(opline->op2.var); + /* Order is critical: fetch the data value (which may emit an + * "Undefined variable" warning that can recurse into user code + * via a user output handler) before resolving variable_ptr. + * Resolving variable_ptr only afterwards guarantees a fresh + * bucket pointer that cannot have been invalidated by user + * code freeing the array, rehashing it, or unsetting keys. */ + value = EX_VAR((opline+1)->op1.var); + if (IS_CV == IS_CV && UNEXPECTED(Z_TYPE_P(value) == IS_UNDEF)) { + /* Temporarily addref the array around the warning so we + * can detect a full destruction (last ref dropped by + * reentrant user code) and bail out cleanly. Mirrors the + * IS_UNUSED branch above. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + value = zval_undefined_cv((opline+1)->op1.var EXECUTE_DATA_CC); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) { + zend_array_destroy(ht); + goto assign_dim_error; + } + } if (IS_CV == IS_CONST) { - variable_ptr = zend_fetch_dimension_address_inner_W_CONST(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W_CONST(ht, dim EXECUTE_DATA_CC); } else { - variable_ptr = zend_fetch_dimension_address_inner_W(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W(ht, dim EXECUTE_DATA_CC); } if (UNEXPECTED(variable_ptr == NULL)) { goto assign_dim_error; } - value = _get_zval_ptr_cv_BP_VAR_R((opline+1)->op1.var EXECUTE_DATA_CC); value = zend_assign_to_variable_ex(variable_ptr, value, IS_CV, EX_USES_STRICT_TYPES(), &garbage); } if (UNEXPECTED(RETURN_VALUE_USED(opline))) { @@ -77403,16 +77907,37 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_VA } } } else { + HashTable *ht = Z_ARRVAL_P(object_ptr); dim = RT_CONSTANT(opline, opline->op2); + /* Order is critical: fetch the data value (which may emit an + * "Undefined variable" warning that can recurse into user code + * via a user output handler) before resolving variable_ptr. + * Resolving variable_ptr only afterwards guarantees a fresh + * bucket pointer that cannot have been invalidated by user + * code freeing the array, rehashing it, or unsetting keys. */ + value = RT_CONSTANT((opline+1), (opline+1)->op1); + if (IS_CONST == IS_CV && UNEXPECTED(Z_TYPE_P(value) == IS_UNDEF)) { + /* Temporarily addref the array around the warning so we + * can detect a full destruction (last ref dropped by + * reentrant user code) and bail out cleanly. Mirrors the + * IS_UNUSED branch above. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + value = zval_undefined_cv((opline+1)->op1.var EXECUTE_DATA_CC); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) { + zend_array_destroy(ht); + goto assign_dim_error; + } + } if (IS_CONST == IS_CONST) { - variable_ptr = zend_fetch_dimension_address_inner_W_CONST(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W_CONST(ht, dim EXECUTE_DATA_CC); } else { - variable_ptr = zend_fetch_dimension_address_inner_W(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W(ht, dim EXECUTE_DATA_CC); } if (UNEXPECTED(variable_ptr == NULL)) { goto assign_dim_error; } - value = RT_CONSTANT((opline+1), (opline+1)->op1); value = zend_assign_to_variable_ex(variable_ptr, value, IS_CONST, EX_USES_STRICT_TYPES(), &garbage); } if (UNEXPECTED(RETURN_VALUE_USED(opline))) { @@ -77561,16 +78086,37 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_VA } } } else { + HashTable *ht = Z_ARRVAL_P(object_ptr); dim = RT_CONSTANT(opline, opline->op2); + /* Order is critical: fetch the data value (which may emit an + * "Undefined variable" warning that can recurse into user code + * via a user output handler) before resolving variable_ptr. + * Resolving variable_ptr only afterwards guarantees a fresh + * bucket pointer that cannot have been invalidated by user + * code freeing the array, rehashing it, or unsetting keys. */ + value = _get_zval_ptr_tmp((opline+1)->op1.var EXECUTE_DATA_CC); + if (IS_TMP_VAR == IS_CV && UNEXPECTED(Z_TYPE_P(value) == IS_UNDEF)) { + /* Temporarily addref the array around the warning so we + * can detect a full destruction (last ref dropped by + * reentrant user code) and bail out cleanly. Mirrors the + * IS_UNUSED branch above. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + value = zval_undefined_cv((opline+1)->op1.var EXECUTE_DATA_CC); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) { + zend_array_destroy(ht); + goto assign_dim_error; + } + } if (IS_CONST == IS_CONST) { - variable_ptr = zend_fetch_dimension_address_inner_W_CONST(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W_CONST(ht, dim EXECUTE_DATA_CC); } else { - variable_ptr = zend_fetch_dimension_address_inner_W(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W(ht, dim EXECUTE_DATA_CC); } if (UNEXPECTED(variable_ptr == NULL)) { goto assign_dim_error; } - value = _get_zval_ptr_tmp((opline+1)->op1.var EXECUTE_DATA_CC); value = zend_assign_to_variable_ex(variable_ptr, value, IS_TMP_VAR, EX_USES_STRICT_TYPES(), &garbage); } if (UNEXPECTED(RETURN_VALUE_USED(opline))) { @@ -77715,16 +78261,37 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_VA } } } else { + HashTable *ht = Z_ARRVAL_P(object_ptr); dim = RT_CONSTANT(opline, opline->op2); + /* Order is critical: fetch the data value (which may emit an + * "Undefined variable" warning that can recurse into user code + * via a user output handler) before resolving variable_ptr. + * Resolving variable_ptr only afterwards guarantees a fresh + * bucket pointer that cannot have been invalidated by user + * code freeing the array, rehashing it, or unsetting keys. */ + value = EX_VAR((opline+1)->op1.var); + if (IS_CV == IS_CV && UNEXPECTED(Z_TYPE_P(value) == IS_UNDEF)) { + /* Temporarily addref the array around the warning so we + * can detect a full destruction (last ref dropped by + * reentrant user code) and bail out cleanly. Mirrors the + * IS_UNUSED branch above. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + value = zval_undefined_cv((opline+1)->op1.var EXECUTE_DATA_CC); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) { + zend_array_destroy(ht); + goto assign_dim_error; + } + } if (IS_CONST == IS_CONST) { - variable_ptr = zend_fetch_dimension_address_inner_W_CONST(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W_CONST(ht, dim EXECUTE_DATA_CC); } else { - variable_ptr = zend_fetch_dimension_address_inner_W(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W(ht, dim EXECUTE_DATA_CC); } if (UNEXPECTED(variable_ptr == NULL)) { goto assign_dim_error; } - value = _get_zval_ptr_cv_BP_VAR_R((opline+1)->op1.var EXECUTE_DATA_CC); value = zend_assign_to_variable_ex(variable_ptr, value, IS_CV, EX_USES_STRICT_TYPES(), &garbage); } if (UNEXPECTED(RETURN_VALUE_USED(opline))) { @@ -80108,16 +80675,37 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_VA } } } else { + HashTable *ht = Z_ARRVAL_P(object_ptr); dim = _get_zval_ptr_tmp(opline->op2.var EXECUTE_DATA_CC); + /* Order is critical: fetch the data value (which may emit an + * "Undefined variable" warning that can recurse into user code + * via a user output handler) before resolving variable_ptr. + * Resolving variable_ptr only afterwards guarantees a fresh + * bucket pointer that cannot have been invalidated by user + * code freeing the array, rehashing it, or unsetting keys. */ + value = RT_CONSTANT((opline+1), (opline+1)->op1); + if (IS_CONST == IS_CV && UNEXPECTED(Z_TYPE_P(value) == IS_UNDEF)) { + /* Temporarily addref the array around the warning so we + * can detect a full destruction (last ref dropped by + * reentrant user code) and bail out cleanly. Mirrors the + * IS_UNUSED branch above. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + value = zval_undefined_cv((opline+1)->op1.var EXECUTE_DATA_CC); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) { + zend_array_destroy(ht); + goto assign_dim_error; + } + } if (IS_TMP_VAR == IS_CONST) { - variable_ptr = zend_fetch_dimension_address_inner_W_CONST(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W_CONST(ht, dim EXECUTE_DATA_CC); } else { - variable_ptr = zend_fetch_dimension_address_inner_W(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W(ht, dim EXECUTE_DATA_CC); } if (UNEXPECTED(variable_ptr == NULL)) { goto assign_dim_error; } - value = RT_CONSTANT((opline+1), (opline+1)->op1); value = zend_assign_to_variable_ex(variable_ptr, value, IS_CONST, EX_USES_STRICT_TYPES(), &garbage); } if (UNEXPECTED(RETURN_VALUE_USED(opline))) { @@ -80265,16 +80853,37 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_VA } } } else { + HashTable *ht = Z_ARRVAL_P(object_ptr); dim = _get_zval_ptr_tmp(opline->op2.var EXECUTE_DATA_CC); + /* Order is critical: fetch the data value (which may emit an + * "Undefined variable" warning that can recurse into user code + * via a user output handler) before resolving variable_ptr. + * Resolving variable_ptr only afterwards guarantees a fresh + * bucket pointer that cannot have been invalidated by user + * code freeing the array, rehashing it, or unsetting keys. */ + value = _get_zval_ptr_tmp((opline+1)->op1.var EXECUTE_DATA_CC); + if (IS_TMP_VAR == IS_CV && UNEXPECTED(Z_TYPE_P(value) == IS_UNDEF)) { + /* Temporarily addref the array around the warning so we + * can detect a full destruction (last ref dropped by + * reentrant user code) and bail out cleanly. Mirrors the + * IS_UNUSED branch above. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + value = zval_undefined_cv((opline+1)->op1.var EXECUTE_DATA_CC); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) { + zend_array_destroy(ht); + goto assign_dim_error; + } + } if (IS_TMP_VAR == IS_CONST) { - variable_ptr = zend_fetch_dimension_address_inner_W_CONST(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W_CONST(ht, dim EXECUTE_DATA_CC); } else { - variable_ptr = zend_fetch_dimension_address_inner_W(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W(ht, dim EXECUTE_DATA_CC); } if (UNEXPECTED(variable_ptr == NULL)) { goto assign_dim_error; } - value = _get_zval_ptr_tmp((opline+1)->op1.var EXECUTE_DATA_CC); value = zend_assign_to_variable_ex(variable_ptr, value, IS_TMP_VAR, EX_USES_STRICT_TYPES(), &garbage); } if (UNEXPECTED(RETURN_VALUE_USED(opline))) { @@ -80418,16 +81027,37 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_VA } } } else { + HashTable *ht = Z_ARRVAL_P(object_ptr); dim = _get_zval_ptr_tmp(opline->op2.var EXECUTE_DATA_CC); + /* Order is critical: fetch the data value (which may emit an + * "Undefined variable" warning that can recurse into user code + * via a user output handler) before resolving variable_ptr. + * Resolving variable_ptr only afterwards guarantees a fresh + * bucket pointer that cannot have been invalidated by user + * code freeing the array, rehashing it, or unsetting keys. */ + value = EX_VAR((opline+1)->op1.var); + if (IS_CV == IS_CV && UNEXPECTED(Z_TYPE_P(value) == IS_UNDEF)) { + /* Temporarily addref the array around the warning so we + * can detect a full destruction (last ref dropped by + * reentrant user code) and bail out cleanly. Mirrors the + * IS_UNUSED branch above. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + value = zval_undefined_cv((opline+1)->op1.var EXECUTE_DATA_CC); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) { + zend_array_destroy(ht); + goto assign_dim_error; + } + } if (IS_TMP_VAR == IS_CONST) { - variable_ptr = zend_fetch_dimension_address_inner_W_CONST(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W_CONST(ht, dim EXECUTE_DATA_CC); } else { - variable_ptr = zend_fetch_dimension_address_inner_W(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W(ht, dim EXECUTE_DATA_CC); } if (UNEXPECTED(variable_ptr == NULL)) { goto assign_dim_error; } - value = _get_zval_ptr_cv_BP_VAR_R((opline+1)->op1.var EXECUTE_DATA_CC); value = zend_assign_to_variable_ex(variable_ptr, value, IS_CV, EX_USES_STRICT_TYPES(), &garbage); } if (UNEXPECTED(RETURN_VALUE_USED(opline))) { @@ -81438,16 +82068,37 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_VA } } } else { + HashTable *ht = Z_ARRVAL_P(object_ptr); dim = NULL; + /* Order is critical: fetch the data value (which may emit an + * "Undefined variable" warning that can recurse into user code + * via a user output handler) before resolving variable_ptr. + * Resolving variable_ptr only afterwards guarantees a fresh + * bucket pointer that cannot have been invalidated by user + * code freeing the array, rehashing it, or unsetting keys. */ + value = RT_CONSTANT((opline+1), (opline+1)->op1); + if (IS_CONST == IS_CV && UNEXPECTED(Z_TYPE_P(value) == IS_UNDEF)) { + /* Temporarily addref the array around the warning so we + * can detect a full destruction (last ref dropped by + * reentrant user code) and bail out cleanly. Mirrors the + * IS_UNUSED branch above. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + value = zval_undefined_cv((opline+1)->op1.var EXECUTE_DATA_CC); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) { + zend_array_destroy(ht); + goto assign_dim_error; + } + } if (IS_UNUSED == IS_CONST) { - variable_ptr = zend_fetch_dimension_address_inner_W_CONST(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W_CONST(ht, dim EXECUTE_DATA_CC); } else { - variable_ptr = zend_fetch_dimension_address_inner_W(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W(ht, dim EXECUTE_DATA_CC); } if (UNEXPECTED(variable_ptr == NULL)) { goto assign_dim_error; } - value = RT_CONSTANT((opline+1), (opline+1)->op1); value = zend_assign_to_variable_ex(variable_ptr, value, IS_CONST, EX_USES_STRICT_TYPES(), &garbage); } if (UNEXPECTED(RETURN_VALUE_USED(opline))) { @@ -81596,16 +82247,37 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_VA } } } else { + HashTable *ht = Z_ARRVAL_P(object_ptr); dim = NULL; + /* Order is critical: fetch the data value (which may emit an + * "Undefined variable" warning that can recurse into user code + * via a user output handler) before resolving variable_ptr. + * Resolving variable_ptr only afterwards guarantees a fresh + * bucket pointer that cannot have been invalidated by user + * code freeing the array, rehashing it, or unsetting keys. */ + value = _get_zval_ptr_tmp((opline+1)->op1.var EXECUTE_DATA_CC); + if (IS_TMP_VAR == IS_CV && UNEXPECTED(Z_TYPE_P(value) == IS_UNDEF)) { + /* Temporarily addref the array around the warning so we + * can detect a full destruction (last ref dropped by + * reentrant user code) and bail out cleanly. Mirrors the + * IS_UNUSED branch above. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + value = zval_undefined_cv((opline+1)->op1.var EXECUTE_DATA_CC); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) { + zend_array_destroy(ht); + goto assign_dim_error; + } + } if (IS_UNUSED == IS_CONST) { - variable_ptr = zend_fetch_dimension_address_inner_W_CONST(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W_CONST(ht, dim EXECUTE_DATA_CC); } else { - variable_ptr = zend_fetch_dimension_address_inner_W(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W(ht, dim EXECUTE_DATA_CC); } if (UNEXPECTED(variable_ptr == NULL)) { goto assign_dim_error; } - value = _get_zval_ptr_tmp((opline+1)->op1.var EXECUTE_DATA_CC); value = zend_assign_to_variable_ex(variable_ptr, value, IS_TMP_VAR, EX_USES_STRICT_TYPES(), &garbage); } if (UNEXPECTED(RETURN_VALUE_USED(opline))) { @@ -81750,16 +82422,37 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_VA } } } else { + HashTable *ht = Z_ARRVAL_P(object_ptr); dim = NULL; + /* Order is critical: fetch the data value (which may emit an + * "Undefined variable" warning that can recurse into user code + * via a user output handler) before resolving variable_ptr. + * Resolving variable_ptr only afterwards guarantees a fresh + * bucket pointer that cannot have been invalidated by user + * code freeing the array, rehashing it, or unsetting keys. */ + value = EX_VAR((opline+1)->op1.var); + if (IS_CV == IS_CV && UNEXPECTED(Z_TYPE_P(value) == IS_UNDEF)) { + /* Temporarily addref the array around the warning so we + * can detect a full destruction (last ref dropped by + * reentrant user code) and bail out cleanly. Mirrors the + * IS_UNUSED branch above. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + value = zval_undefined_cv((opline+1)->op1.var EXECUTE_DATA_CC); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) { + zend_array_destroy(ht); + goto assign_dim_error; + } + } if (IS_UNUSED == IS_CONST) { - variable_ptr = zend_fetch_dimension_address_inner_W_CONST(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W_CONST(ht, dim EXECUTE_DATA_CC); } else { - variable_ptr = zend_fetch_dimension_address_inner_W(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W(ht, dim EXECUTE_DATA_CC); } if (UNEXPECTED(variable_ptr == NULL)) { goto assign_dim_error; } - value = _get_zval_ptr_cv_BP_VAR_R((opline+1)->op1.var EXECUTE_DATA_CC); value = zend_assign_to_variable_ex(variable_ptr, value, IS_CV, EX_USES_STRICT_TYPES(), &garbage); } if (UNEXPECTED(RETURN_VALUE_USED(opline))) { @@ -83935,16 +84628,37 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_VA } } } else { + HashTable *ht = Z_ARRVAL_P(object_ptr); dim = EX_VAR(opline->op2.var); + /* Order is critical: fetch the data value (which may emit an + * "Undefined variable" warning that can recurse into user code + * via a user output handler) before resolving variable_ptr. + * Resolving variable_ptr only afterwards guarantees a fresh + * bucket pointer that cannot have been invalidated by user + * code freeing the array, rehashing it, or unsetting keys. */ + value = RT_CONSTANT((opline+1), (opline+1)->op1); + if (IS_CONST == IS_CV && UNEXPECTED(Z_TYPE_P(value) == IS_UNDEF)) { + /* Temporarily addref the array around the warning so we + * can detect a full destruction (last ref dropped by + * reentrant user code) and bail out cleanly. Mirrors the + * IS_UNUSED branch above. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + value = zval_undefined_cv((opline+1)->op1.var EXECUTE_DATA_CC); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) { + zend_array_destroy(ht); + goto assign_dim_error; + } + } if (IS_CV == IS_CONST) { - variable_ptr = zend_fetch_dimension_address_inner_W_CONST(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W_CONST(ht, dim EXECUTE_DATA_CC); } else { - variable_ptr = zend_fetch_dimension_address_inner_W(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W(ht, dim EXECUTE_DATA_CC); } if (UNEXPECTED(variable_ptr == NULL)) { goto assign_dim_error; } - value = RT_CONSTANT((opline+1), (opline+1)->op1); value = zend_assign_to_variable_ex(variable_ptr, value, IS_CONST, EX_USES_STRICT_TYPES(), &garbage); } if (UNEXPECTED(RETURN_VALUE_USED(opline))) { @@ -84093,16 +84807,37 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_VA } } } else { + HashTable *ht = Z_ARRVAL_P(object_ptr); dim = EX_VAR(opline->op2.var); + /* Order is critical: fetch the data value (which may emit an + * "Undefined variable" warning that can recurse into user code + * via a user output handler) before resolving variable_ptr. + * Resolving variable_ptr only afterwards guarantees a fresh + * bucket pointer that cannot have been invalidated by user + * code freeing the array, rehashing it, or unsetting keys. */ + value = _get_zval_ptr_tmp((opline+1)->op1.var EXECUTE_DATA_CC); + if (IS_TMP_VAR == IS_CV && UNEXPECTED(Z_TYPE_P(value) == IS_UNDEF)) { + /* Temporarily addref the array around the warning so we + * can detect a full destruction (last ref dropped by + * reentrant user code) and bail out cleanly. Mirrors the + * IS_UNUSED branch above. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + value = zval_undefined_cv((opline+1)->op1.var EXECUTE_DATA_CC); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) { + zend_array_destroy(ht); + goto assign_dim_error; + } + } if (IS_CV == IS_CONST) { - variable_ptr = zend_fetch_dimension_address_inner_W_CONST(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W_CONST(ht, dim EXECUTE_DATA_CC); } else { - variable_ptr = zend_fetch_dimension_address_inner_W(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W(ht, dim EXECUTE_DATA_CC); } if (UNEXPECTED(variable_ptr == NULL)) { goto assign_dim_error; } - value = _get_zval_ptr_tmp((opline+1)->op1.var EXECUTE_DATA_CC); value = zend_assign_to_variable_ex(variable_ptr, value, IS_TMP_VAR, EX_USES_STRICT_TYPES(), &garbage); } if (UNEXPECTED(RETURN_VALUE_USED(opline))) { @@ -84247,16 +84982,37 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_VA } } } else { + HashTable *ht = Z_ARRVAL_P(object_ptr); dim = EX_VAR(opline->op2.var); + /* Order is critical: fetch the data value (which may emit an + * "Undefined variable" warning that can recurse into user code + * via a user output handler) before resolving variable_ptr. + * Resolving variable_ptr only afterwards guarantees a fresh + * bucket pointer that cannot have been invalidated by user + * code freeing the array, rehashing it, or unsetting keys. */ + value = EX_VAR((opline+1)->op1.var); + if (IS_CV == IS_CV && UNEXPECTED(Z_TYPE_P(value) == IS_UNDEF)) { + /* Temporarily addref the array around the warning so we + * can detect a full destruction (last ref dropped by + * reentrant user code) and bail out cleanly. Mirrors the + * IS_UNUSED branch above. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + value = zval_undefined_cv((opline+1)->op1.var EXECUTE_DATA_CC); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) { + zend_array_destroy(ht); + goto assign_dim_error; + } + } if (IS_CV == IS_CONST) { - variable_ptr = zend_fetch_dimension_address_inner_W_CONST(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W_CONST(ht, dim EXECUTE_DATA_CC); } else { - variable_ptr = zend_fetch_dimension_address_inner_W(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W(ht, dim EXECUTE_DATA_CC); } if (UNEXPECTED(variable_ptr == NULL)) { goto assign_dim_error; } - value = _get_zval_ptr_cv_BP_VAR_R((opline+1)->op1.var EXECUTE_DATA_CC); value = zend_assign_to_variable_ex(variable_ptr, value, IS_CV, EX_USES_STRICT_TYPES(), &garbage); } if (UNEXPECTED(RETURN_VALUE_USED(opline))) { @@ -95508,16 +96264,37 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_CV } } } else { + HashTable *ht = Z_ARRVAL_P(object_ptr); dim = RT_CONSTANT(opline, opline->op2); + /* Order is critical: fetch the data value (which may emit an + * "Undefined variable" warning that can recurse into user code + * via a user output handler) before resolving variable_ptr. + * Resolving variable_ptr only afterwards guarantees a fresh + * bucket pointer that cannot have been invalidated by user + * code freeing the array, rehashing it, or unsetting keys. */ + value = RT_CONSTANT((opline+1), (opline+1)->op1); + if (IS_CONST == IS_CV && UNEXPECTED(Z_TYPE_P(value) == IS_UNDEF)) { + /* Temporarily addref the array around the warning so we + * can detect a full destruction (last ref dropped by + * reentrant user code) and bail out cleanly. Mirrors the + * IS_UNUSED branch above. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + value = zval_undefined_cv((opline+1)->op1.var EXECUTE_DATA_CC); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) { + zend_array_destroy(ht); + goto assign_dim_error; + } + } if (IS_CONST == IS_CONST) { - variable_ptr = zend_fetch_dimension_address_inner_W_CONST(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W_CONST(ht, dim EXECUTE_DATA_CC); } else { - variable_ptr = zend_fetch_dimension_address_inner_W(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W(ht, dim EXECUTE_DATA_CC); } if (UNEXPECTED(variable_ptr == NULL)) { goto assign_dim_error; } - value = RT_CONSTANT((opline+1), (opline+1)->op1); value = zend_assign_to_variable_ex(variable_ptr, value, IS_CONST, EX_USES_STRICT_TYPES(), &garbage); } if (UNEXPECTED(RETURN_VALUE_USED(opline))) { @@ -95667,16 +96444,37 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_CV } } } else { + HashTable *ht = Z_ARRVAL_P(object_ptr); dim = RT_CONSTANT(opline, opline->op2); + /* Order is critical: fetch the data value (which may emit an + * "Undefined variable" warning that can recurse into user code + * via a user output handler) before resolving variable_ptr. + * Resolving variable_ptr only afterwards guarantees a fresh + * bucket pointer that cannot have been invalidated by user + * code freeing the array, rehashing it, or unsetting keys. */ + value = _get_zval_ptr_tmp((opline+1)->op1.var EXECUTE_DATA_CC); + if (IS_TMP_VAR == IS_CV && UNEXPECTED(Z_TYPE_P(value) == IS_UNDEF)) { + /* Temporarily addref the array around the warning so we + * can detect a full destruction (last ref dropped by + * reentrant user code) and bail out cleanly. Mirrors the + * IS_UNUSED branch above. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + value = zval_undefined_cv((opline+1)->op1.var EXECUTE_DATA_CC); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) { + zend_array_destroy(ht); + goto assign_dim_error; + } + } if (IS_CONST == IS_CONST) { - variable_ptr = zend_fetch_dimension_address_inner_W_CONST(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W_CONST(ht, dim EXECUTE_DATA_CC); } else { - variable_ptr = zend_fetch_dimension_address_inner_W(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W(ht, dim EXECUTE_DATA_CC); } if (UNEXPECTED(variable_ptr == NULL)) { goto assign_dim_error; } - value = _get_zval_ptr_tmp((opline+1)->op1.var EXECUTE_DATA_CC); value = zend_assign_to_variable_ex(variable_ptr, value, IS_TMP_VAR, EX_USES_STRICT_TYPES(), &garbage); } if (UNEXPECTED(RETURN_VALUE_USED(opline))) { @@ -95822,16 +96620,37 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_CV } } } else { + HashTable *ht = Z_ARRVAL_P(object_ptr); dim = RT_CONSTANT(opline, opline->op2); + /* Order is critical: fetch the data value (which may emit an + * "Undefined variable" warning that can recurse into user code + * via a user output handler) before resolving variable_ptr. + * Resolving variable_ptr only afterwards guarantees a fresh + * bucket pointer that cannot have been invalidated by user + * code freeing the array, rehashing it, or unsetting keys. */ + value = EX_VAR((opline+1)->op1.var); + if (IS_CV == IS_CV && UNEXPECTED(Z_TYPE_P(value) == IS_UNDEF)) { + /* Temporarily addref the array around the warning so we + * can detect a full destruction (last ref dropped by + * reentrant user code) and bail out cleanly. Mirrors the + * IS_UNUSED branch above. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + value = zval_undefined_cv((opline+1)->op1.var EXECUTE_DATA_CC); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) { + zend_array_destroy(ht); + goto assign_dim_error; + } + } if (IS_CONST == IS_CONST) { - variable_ptr = zend_fetch_dimension_address_inner_W_CONST(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W_CONST(ht, dim EXECUTE_DATA_CC); } else { - variable_ptr = zend_fetch_dimension_address_inner_W(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W(ht, dim EXECUTE_DATA_CC); } if (UNEXPECTED(variable_ptr == NULL)) { goto assign_dim_error; } - value = _get_zval_ptr_cv_BP_VAR_R((opline+1)->op1.var EXECUTE_DATA_CC); value = zend_assign_to_variable_ex(variable_ptr, value, IS_CV, EX_USES_STRICT_TYPES(), &garbage); } if (UNEXPECTED(RETURN_VALUE_USED(opline))) { @@ -99328,16 +100147,37 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_CV } } } else { + HashTable *ht = Z_ARRVAL_P(object_ptr); dim = _get_zval_ptr_tmp(opline->op2.var EXECUTE_DATA_CC); + /* Order is critical: fetch the data value (which may emit an + * "Undefined variable" warning that can recurse into user code + * via a user output handler) before resolving variable_ptr. + * Resolving variable_ptr only afterwards guarantees a fresh + * bucket pointer that cannot have been invalidated by user + * code freeing the array, rehashing it, or unsetting keys. */ + value = RT_CONSTANT((opline+1), (opline+1)->op1); + if (IS_CONST == IS_CV && UNEXPECTED(Z_TYPE_P(value) == IS_UNDEF)) { + /* Temporarily addref the array around the warning so we + * can detect a full destruction (last ref dropped by + * reentrant user code) and bail out cleanly. Mirrors the + * IS_UNUSED branch above. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + value = zval_undefined_cv((opline+1)->op1.var EXECUTE_DATA_CC); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) { + zend_array_destroy(ht); + goto assign_dim_error; + } + } if (IS_TMP_VAR == IS_CONST) { - variable_ptr = zend_fetch_dimension_address_inner_W_CONST(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W_CONST(ht, dim EXECUTE_DATA_CC); } else { - variable_ptr = zend_fetch_dimension_address_inner_W(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W(ht, dim EXECUTE_DATA_CC); } if (UNEXPECTED(variable_ptr == NULL)) { goto assign_dim_error; } - value = RT_CONSTANT((opline+1), (opline+1)->op1); value = zend_assign_to_variable_ex(variable_ptr, value, IS_CONST, EX_USES_STRICT_TYPES(), &garbage); } if (UNEXPECTED(RETURN_VALUE_USED(opline))) { @@ -99486,16 +100326,37 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_CV } } } else { + HashTable *ht = Z_ARRVAL_P(object_ptr); dim = _get_zval_ptr_tmp(opline->op2.var EXECUTE_DATA_CC); + /* Order is critical: fetch the data value (which may emit an + * "Undefined variable" warning that can recurse into user code + * via a user output handler) before resolving variable_ptr. + * Resolving variable_ptr only afterwards guarantees a fresh + * bucket pointer that cannot have been invalidated by user + * code freeing the array, rehashing it, or unsetting keys. */ + value = _get_zval_ptr_tmp((opline+1)->op1.var EXECUTE_DATA_CC); + if (IS_TMP_VAR == IS_CV && UNEXPECTED(Z_TYPE_P(value) == IS_UNDEF)) { + /* Temporarily addref the array around the warning so we + * can detect a full destruction (last ref dropped by + * reentrant user code) and bail out cleanly. Mirrors the + * IS_UNUSED branch above. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + value = zval_undefined_cv((opline+1)->op1.var EXECUTE_DATA_CC); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) { + zend_array_destroy(ht); + goto assign_dim_error; + } + } if (IS_TMP_VAR == IS_CONST) { - variable_ptr = zend_fetch_dimension_address_inner_W_CONST(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W_CONST(ht, dim EXECUTE_DATA_CC); } else { - variable_ptr = zend_fetch_dimension_address_inner_W(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W(ht, dim EXECUTE_DATA_CC); } if (UNEXPECTED(variable_ptr == NULL)) { goto assign_dim_error; } - value = _get_zval_ptr_tmp((opline+1)->op1.var EXECUTE_DATA_CC); value = zend_assign_to_variable_ex(variable_ptr, value, IS_TMP_VAR, EX_USES_STRICT_TYPES(), &garbage); } if (UNEXPECTED(RETURN_VALUE_USED(opline))) { @@ -99640,16 +100501,37 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_CV } } } else { + HashTable *ht = Z_ARRVAL_P(object_ptr); dim = _get_zval_ptr_tmp(opline->op2.var EXECUTE_DATA_CC); + /* Order is critical: fetch the data value (which may emit an + * "Undefined variable" warning that can recurse into user code + * via a user output handler) before resolving variable_ptr. + * Resolving variable_ptr only afterwards guarantees a fresh + * bucket pointer that cannot have been invalidated by user + * code freeing the array, rehashing it, or unsetting keys. */ + value = EX_VAR((opline+1)->op1.var); + if (IS_CV == IS_CV && UNEXPECTED(Z_TYPE_P(value) == IS_UNDEF)) { + /* Temporarily addref the array around the warning so we + * can detect a full destruction (last ref dropped by + * reentrant user code) and bail out cleanly. Mirrors the + * IS_UNUSED branch above. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + value = zval_undefined_cv((opline+1)->op1.var EXECUTE_DATA_CC); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) { + zend_array_destroy(ht); + goto assign_dim_error; + } + } if (IS_TMP_VAR == IS_CONST) { - variable_ptr = zend_fetch_dimension_address_inner_W_CONST(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W_CONST(ht, dim EXECUTE_DATA_CC); } else { - variable_ptr = zend_fetch_dimension_address_inner_W(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W(ht, dim EXECUTE_DATA_CC); } if (UNEXPECTED(variable_ptr == NULL)) { goto assign_dim_error; } - value = _get_zval_ptr_cv_BP_VAR_R((opline+1)->op1.var EXECUTE_DATA_CC); value = zend_assign_to_variable_ex(variable_ptr, value, IS_CV, EX_USES_STRICT_TYPES(), &garbage); } if (UNEXPECTED(RETURN_VALUE_USED(opline))) { @@ -101067,16 +101949,37 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_CV } } } else { + HashTable *ht = Z_ARRVAL_P(object_ptr); dim = NULL; + /* Order is critical: fetch the data value (which may emit an + * "Undefined variable" warning that can recurse into user code + * via a user output handler) before resolving variable_ptr. + * Resolving variable_ptr only afterwards guarantees a fresh + * bucket pointer that cannot have been invalidated by user + * code freeing the array, rehashing it, or unsetting keys. */ + value = RT_CONSTANT((opline+1), (opline+1)->op1); + if (IS_CONST == IS_CV && UNEXPECTED(Z_TYPE_P(value) == IS_UNDEF)) { + /* Temporarily addref the array around the warning so we + * can detect a full destruction (last ref dropped by + * reentrant user code) and bail out cleanly. Mirrors the + * IS_UNUSED branch above. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + value = zval_undefined_cv((opline+1)->op1.var EXECUTE_DATA_CC); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) { + zend_array_destroy(ht); + goto assign_dim_error; + } + } if (IS_UNUSED == IS_CONST) { - variable_ptr = zend_fetch_dimension_address_inner_W_CONST(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W_CONST(ht, dim EXECUTE_DATA_CC); } else { - variable_ptr = zend_fetch_dimension_address_inner_W(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W(ht, dim EXECUTE_DATA_CC); } if (UNEXPECTED(variable_ptr == NULL)) { goto assign_dim_error; } - value = RT_CONSTANT((opline+1), (opline+1)->op1); value = zend_assign_to_variable_ex(variable_ptr, value, IS_CONST, EX_USES_STRICT_TYPES(), &garbage); } if (UNEXPECTED(RETURN_VALUE_USED(opline))) { @@ -101226,16 +102129,37 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_CV } } } else { + HashTable *ht = Z_ARRVAL_P(object_ptr); dim = NULL; + /* Order is critical: fetch the data value (which may emit an + * "Undefined variable" warning that can recurse into user code + * via a user output handler) before resolving variable_ptr. + * Resolving variable_ptr only afterwards guarantees a fresh + * bucket pointer that cannot have been invalidated by user + * code freeing the array, rehashing it, or unsetting keys. */ + value = _get_zval_ptr_tmp((opline+1)->op1.var EXECUTE_DATA_CC); + if (IS_TMP_VAR == IS_CV && UNEXPECTED(Z_TYPE_P(value) == IS_UNDEF)) { + /* Temporarily addref the array around the warning so we + * can detect a full destruction (last ref dropped by + * reentrant user code) and bail out cleanly. Mirrors the + * IS_UNUSED branch above. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + value = zval_undefined_cv((opline+1)->op1.var EXECUTE_DATA_CC); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) { + zend_array_destroy(ht); + goto assign_dim_error; + } + } if (IS_UNUSED == IS_CONST) { - variable_ptr = zend_fetch_dimension_address_inner_W_CONST(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W_CONST(ht, dim EXECUTE_DATA_CC); } else { - variable_ptr = zend_fetch_dimension_address_inner_W(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W(ht, dim EXECUTE_DATA_CC); } if (UNEXPECTED(variable_ptr == NULL)) { goto assign_dim_error; } - value = _get_zval_ptr_tmp((opline+1)->op1.var EXECUTE_DATA_CC); value = zend_assign_to_variable_ex(variable_ptr, value, IS_TMP_VAR, EX_USES_STRICT_TYPES(), &garbage); } if (UNEXPECTED(RETURN_VALUE_USED(opline))) { @@ -101381,16 +102305,37 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_CV } } } else { + HashTable *ht = Z_ARRVAL_P(object_ptr); dim = NULL; + /* Order is critical: fetch the data value (which may emit an + * "Undefined variable" warning that can recurse into user code + * via a user output handler) before resolving variable_ptr. + * Resolving variable_ptr only afterwards guarantees a fresh + * bucket pointer that cannot have been invalidated by user + * code freeing the array, rehashing it, or unsetting keys. */ + value = EX_VAR((opline+1)->op1.var); + if (IS_CV == IS_CV && UNEXPECTED(Z_TYPE_P(value) == IS_UNDEF)) { + /* Temporarily addref the array around the warning so we + * can detect a full destruction (last ref dropped by + * reentrant user code) and bail out cleanly. Mirrors the + * IS_UNUSED branch above. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + value = zval_undefined_cv((opline+1)->op1.var EXECUTE_DATA_CC); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) { + zend_array_destroy(ht); + goto assign_dim_error; + } + } if (IS_UNUSED == IS_CONST) { - variable_ptr = zend_fetch_dimension_address_inner_W_CONST(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W_CONST(ht, dim EXECUTE_DATA_CC); } else { - variable_ptr = zend_fetch_dimension_address_inner_W(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W(ht, dim EXECUTE_DATA_CC); } if (UNEXPECTED(variable_ptr == NULL)) { goto assign_dim_error; } - value = _get_zval_ptr_cv_BP_VAR_R((opline+1)->op1.var EXECUTE_DATA_CC); value = zend_assign_to_variable_ex(variable_ptr, value, IS_CV, EX_USES_STRICT_TYPES(), &garbage); } if (UNEXPECTED(RETURN_VALUE_USED(opline))) { @@ -104345,16 +105290,37 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_CV } } } else { + HashTable *ht = Z_ARRVAL_P(object_ptr); dim = EX_VAR(opline->op2.var); + /* Order is critical: fetch the data value (which may emit an + * "Undefined variable" warning that can recurse into user code + * via a user output handler) before resolving variable_ptr. + * Resolving variable_ptr only afterwards guarantees a fresh + * bucket pointer that cannot have been invalidated by user + * code freeing the array, rehashing it, or unsetting keys. */ + value = RT_CONSTANT((opline+1), (opline+1)->op1); + if (IS_CONST == IS_CV && UNEXPECTED(Z_TYPE_P(value) == IS_UNDEF)) { + /* Temporarily addref the array around the warning so we + * can detect a full destruction (last ref dropped by + * reentrant user code) and bail out cleanly. Mirrors the + * IS_UNUSED branch above. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + value = zval_undefined_cv((opline+1)->op1.var EXECUTE_DATA_CC); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) { + zend_array_destroy(ht); + goto assign_dim_error; + } + } if (IS_CV == IS_CONST) { - variable_ptr = zend_fetch_dimension_address_inner_W_CONST(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W_CONST(ht, dim EXECUTE_DATA_CC); } else { - variable_ptr = zend_fetch_dimension_address_inner_W(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W(ht, dim EXECUTE_DATA_CC); } if (UNEXPECTED(variable_ptr == NULL)) { goto assign_dim_error; } - value = RT_CONSTANT((opline+1), (opline+1)->op1); value = zend_assign_to_variable_ex(variable_ptr, value, IS_CONST, EX_USES_STRICT_TYPES(), &garbage); } if (UNEXPECTED(RETURN_VALUE_USED(opline))) { @@ -104504,16 +105470,37 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_CV } } } else { + HashTable *ht = Z_ARRVAL_P(object_ptr); dim = EX_VAR(opline->op2.var); + /* Order is critical: fetch the data value (which may emit an + * "Undefined variable" warning that can recurse into user code + * via a user output handler) before resolving variable_ptr. + * Resolving variable_ptr only afterwards guarantees a fresh + * bucket pointer that cannot have been invalidated by user + * code freeing the array, rehashing it, or unsetting keys. */ + value = _get_zval_ptr_tmp((opline+1)->op1.var EXECUTE_DATA_CC); + if (IS_TMP_VAR == IS_CV && UNEXPECTED(Z_TYPE_P(value) == IS_UNDEF)) { + /* Temporarily addref the array around the warning so we + * can detect a full destruction (last ref dropped by + * reentrant user code) and bail out cleanly. Mirrors the + * IS_UNUSED branch above. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + value = zval_undefined_cv((opline+1)->op1.var EXECUTE_DATA_CC); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) { + zend_array_destroy(ht); + goto assign_dim_error; + } + } if (IS_CV == IS_CONST) { - variable_ptr = zend_fetch_dimension_address_inner_W_CONST(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W_CONST(ht, dim EXECUTE_DATA_CC); } else { - variable_ptr = zend_fetch_dimension_address_inner_W(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W(ht, dim EXECUTE_DATA_CC); } if (UNEXPECTED(variable_ptr == NULL)) { goto assign_dim_error; } - value = _get_zval_ptr_tmp((opline+1)->op1.var EXECUTE_DATA_CC); value = zend_assign_to_variable_ex(variable_ptr, value, IS_TMP_VAR, EX_USES_STRICT_TYPES(), &garbage); } if (UNEXPECTED(RETURN_VALUE_USED(opline))) { @@ -104659,16 +105646,37 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_DIM_SPEC_CV } } } else { + HashTable *ht = Z_ARRVAL_P(object_ptr); dim = EX_VAR(opline->op2.var); + /* Order is critical: fetch the data value (which may emit an + * "Undefined variable" warning that can recurse into user code + * via a user output handler) before resolving variable_ptr. + * Resolving variable_ptr only afterwards guarantees a fresh + * bucket pointer that cannot have been invalidated by user + * code freeing the array, rehashing it, or unsetting keys. */ + value = EX_VAR((opline+1)->op1.var); + if (IS_CV == IS_CV && UNEXPECTED(Z_TYPE_P(value) == IS_UNDEF)) { + /* Temporarily addref the array around the warning so we + * can detect a full destruction (last ref dropped by + * reentrant user code) and bail out cleanly. Mirrors the + * IS_UNUSED branch above. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + value = zval_undefined_cv((opline+1)->op1.var EXECUTE_DATA_CC); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) { + zend_array_destroy(ht); + goto assign_dim_error; + } + } if (IS_CV == IS_CONST) { - variable_ptr = zend_fetch_dimension_address_inner_W_CONST(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W_CONST(ht, dim EXECUTE_DATA_CC); } else { - variable_ptr = zend_fetch_dimension_address_inner_W(Z_ARRVAL_P(object_ptr), dim EXECUTE_DATA_CC); + variable_ptr = zend_fetch_dimension_address_inner_W(ht, dim EXECUTE_DATA_CC); } if (UNEXPECTED(variable_ptr == NULL)) { goto assign_dim_error; } - value = _get_zval_ptr_cv_BP_VAR_R((opline+1)->op1.var EXECUTE_DATA_CC); value = zend_assign_to_variable_ex(variable_ptr, value, IS_CV, EX_USES_STRICT_TYPES(), &garbage); } if (UNEXPECTED(RETURN_VALUE_USED(opline))) {