From 6de6c31192503a9e8a76014d5046d100441c563a Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Sat, 18 Jul 2026 12:58:10 -0400 Subject: [PATCH] Fix crash when a partial skips a param with a constant default Const exprs represent a constant fetch that could not be evaluated at compile time as ZEND_AST_CONSTANT, which zend_compile_expr_inner() had no case for, so compiling such a default into a partial's forwarding call hit ZEND_ASSERT(0). Compile it to ZEND_FETCH_CONSTANT using the name already resolved by zend_compile_const_expr_const(). zp_compile_forwarding_call() also dropped the reference it took on a constant-expression default value. Closes GH-22804 --- .../default_const_expr.phpt | 116 ++++++++++++++++++ Zend/zend_compile.c | 43 ++++--- Zend/zend_partial.c | 1 + 3 files changed, 145 insertions(+), 15 deletions(-) create mode 100644 Zend/tests/partial_application/default_const_expr.phpt diff --git a/Zend/tests/partial_application/default_const_expr.phpt b/Zend/tests/partial_application/default_const_expr.phpt new file mode 100644 index 000000000000..12d25adedeff --- /dev/null +++ b/Zend/tests/partial_application/default_const_expr.phpt @@ -0,0 +1,116 @@ +--TEST-- +PFA: constant-expression default for a skipped optional parameter +--FILE-- +name, $c]; +} + +$p = f(a: 10, c: ?); +var_dump($p(99)); + +$v = variadic(a: 10, c: ?, foo: 1); +var_dump($v(99)); + +$q = C::m(a: 1, c: ?); +var_dump($q(9)); + +$r = g(a: 1, c: ?); +var_dump($r(9)); + +$l = late(a: 1, c: ?); +define('App\LATE', 'defined after the partial was created'); +var_dump($l(9)); + +define('GLOBAL_ONLY', 'global fallback'); +$s = fallback(a: 1, c: ?); +var_dump($s(9)); + +?> +--EXPECT-- +array(3) { + [0]=> + int(10) + [1]=> + int(7) + [2]=> + int(99) +} +array(4) { + [0]=> + int(10) + [1]=> + int(7) + [2]=> + int(99) + [3]=> + array(1) { + ["foo"]=> + int(1) + } +} +array(3) { + [0]=> + int(1) + [1]=> + int(42) + [2]=> + int(9) +} +array(3) { + [0]=> + int(1) + [1]=> + string(1) "A" + [2]=> + int(9) +} +array(3) { + [0]=> + int(1) + [1]=> + string(37) "defined after the partial was created" + [2]=> + int(9) +} +array(3) { + [0]=> + int(1) + [1]=> + string(15) "global fallback" + [2]=> + int(9) +} diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index f44784239533..bb24d73fb240 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -11519,12 +11519,25 @@ static void zend_compile_array(znode *result, zend_ast *ast) /* {{{ */ } /* }}} */ +static void zend_emit_fetch_constant(znode *result, zend_string *resolved_name, bool unqualified_in_namespace) +{ + zend_op *opline = zend_emit_op_tmp(result, ZEND_FETCH_CONSTANT, NULL, NULL); + opline->op2_type = IS_CONST; + + if (unqualified_in_namespace) { + opline->op1.num = IS_CONSTANT_UNQUALIFIED_IN_NAMESPACE; + opline->op2.constant = zend_add_const_name_literal(resolved_name, true); + } else { + opline->op1.num = 0; + opline->op2.constant = zend_add_const_name_literal(resolved_name, false); + } + opline->extended_value = zend_alloc_cache_slot(); +} + static void zend_compile_const(znode *result, const zend_ast *ast) /* {{{ */ { zend_ast *name_ast = ast->child[0]; - zend_op *opline; - bool is_fully_qualified; zend_string *orig_name = zend_ast_get_str(name_ast); zend_string *resolved_name = zend_resolve_const_name(orig_name, name_ast->attr, &is_fully_qualified); @@ -11553,22 +11566,19 @@ static void zend_compile_const(znode *result, const zend_ast *ast) /* {{{ */ return; } - opline = zend_emit_op_tmp(result, ZEND_FETCH_CONSTANT, NULL, NULL); - opline->op2_type = IS_CONST; - - if (is_fully_qualified || !FC(current_namespace)) { - opline->op1.num = 0; - opline->op2.constant = zend_add_const_name_literal( - resolved_name, false); - } else { - opline->op1.num = IS_CONSTANT_UNQUALIFIED_IN_NAMESPACE; - opline->op2.constant = zend_add_const_name_literal( - resolved_name, true); - } - opline->extended_value = zend_alloc_cache_slot(); + zend_emit_fetch_constant(result, resolved_name, + !is_fully_qualified && FC(current_namespace)); } /* }}} */ +static void zend_compile_constant(znode *result, zend_ast *ast) +{ + zend_string *name = zend_ast_get_constant_name(ast); + + zend_emit_fetch_constant(result, zend_string_copy(name), + (ast->attr & IS_CONSTANT_UNQUALIFIED_IN_NAMESPACE) != 0); +} + static void zend_compile_class_const(znode *result, zend_ast *ast) /* {{{ */ { zend_ast *class_ast; @@ -12436,6 +12446,9 @@ static void zend_compile_expr_inner(znode *result, zend_ast *ast) /* {{{ */ case ZEND_AST_CONST: zend_compile_const(result, ast); return; + case ZEND_AST_CONSTANT: + zend_compile_constant(result, ast); + return; case ZEND_AST_CLASS_CONST: zend_compile_class_const(result, ast); return; diff --git a/Zend/zend_partial.c b/Zend/zend_partial.c index ef335cd805c3..e7a3e1c92b58 100644 --- a/Zend/zend_partial.c +++ b/Zend/zend_partial.c @@ -588,6 +588,7 @@ static zend_ast *zp_compile_forwarding_call( if (Z_TYPE(default_value) == IS_CONSTANT_AST) { /* Must dup AST because we are going to destroy it */ default_value_ast = zend_ast_dup(Z_ASTVAL(default_value)); + zval_ptr_dtor_nogc(&default_value); } else { default_value_ast = zend_ast_create_zval(&default_value); }