Commit 61a987b5a19 for php.net
commit 61a987b5a1927dd3fe39b4744bba20de7ca73d33
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date: Mon Jul 20 10:58:59 2026 -0400
Fix crash when a partial skips a param with a constant default (#22804)
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
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 00000000000..12d25adedef
--- /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--
+<?php
+
+namespace App;
+
+const K = 7;
+
+function f($a, $b = K, $c = 0) {
+ return [$a, $b, $c];
+}
+
+function variadic($a, $b = K, $c = 0, ...$args) {
+ return [$a, $b, $c, $args];
+}
+
+function late($a, $b = LATE, $c = 0) {
+ return [$a, $b, $c];
+}
+
+function fallback($a, $b = GLOBAL_ONLY, $c = 0) {
+ return [$a, $b, $c];
+}
+
+class C {
+ const X = 42;
+ static function m($a, $b = self::X, $c = 0) {
+ return [$a, $b, $c];
+ }
+}
+
+enum E {
+ case A;
+ case B;
+}
+
+function g($a, $b = E::A, $c = 0) {
+ return [$a, $b->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 f4478423953..bb24d73fb24 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 ef335cd805c..e7a3e1c92b5 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);
}