Skip to content

Commit b3c097d

Browse files
committed
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
1 parent d37a820 commit b3c097d

3 files changed

Lines changed: 146 additions & 14 deletions

File tree

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
--TEST--
2+
PFA: constant-expression default for a skipped optional parameter
3+
--FILE--
4+
<?php
5+
6+
namespace App;
7+
8+
const K = 7;
9+
10+
function f($a, $b = K, $c = 0) {
11+
return [$a, $b, $c];
12+
}
13+
14+
function variadic($a, $b = K, $c = 0, ...$args) {
15+
return [$a, $b, $c, $args];
16+
}
17+
18+
function late($a, $b = LATE, $c = 0) {
19+
return [$a, $b, $c];
20+
}
21+
22+
function fallback($a, $b = GLOBAL_ONLY, $c = 0) {
23+
return [$a, $b, $c];
24+
}
25+
26+
class C {
27+
const X = 42;
28+
static function m($a, $b = self::X, $c = 0) {
29+
return [$a, $b, $c];
30+
}
31+
}
32+
33+
enum E {
34+
case A;
35+
case B;
36+
}
37+
38+
function g($a, $b = E::A, $c = 0) {
39+
return [$a, $b->name, $c];
40+
}
41+
42+
$p = f(a: 10, c: ?);
43+
var_dump($p(99));
44+
45+
$v = variadic(a: 10, c: ?, foo: 1);
46+
var_dump($v(99));
47+
48+
$q = C::m(a: 1, c: ?);
49+
var_dump($q(9));
50+
51+
$r = g(a: 1, c: ?);
52+
var_dump($r(9));
53+
54+
$l = late(a: 1, c: ?);
55+
define('App\LATE', 'defined after the partial was created');
56+
var_dump($l(9));
57+
58+
define('GLOBAL_ONLY', 'global fallback');
59+
$s = fallback(a: 1, c: ?);
60+
var_dump($s(9));
61+
62+
?>
63+
--EXPECT--
64+
array(3) {
65+
[0]=>
66+
int(10)
67+
[1]=>
68+
int(7)
69+
[2]=>
70+
int(99)
71+
}
72+
array(4) {
73+
[0]=>
74+
int(10)
75+
[1]=>
76+
int(7)
77+
[2]=>
78+
int(99)
79+
[3]=>
80+
array(1) {
81+
["foo"]=>
82+
int(1)
83+
}
84+
}
85+
array(3) {
86+
[0]=>
87+
int(1)
88+
[1]=>
89+
int(42)
90+
[2]=>
91+
int(9)
92+
}
93+
array(3) {
94+
[0]=>
95+
int(1)
96+
[1]=>
97+
string(1) "A"
98+
[2]=>
99+
int(9)
100+
}
101+
array(3) {
102+
[0]=>
103+
int(1)
104+
[1]=>
105+
string(37) "defined after the partial was created"
106+
[2]=>
107+
int(9)
108+
}
109+
array(3) {
110+
[0]=>
111+
int(1)
112+
[1]=>
113+
string(15) "global fallback"
114+
[2]=>
115+
int(9)
116+
}

Zend/zend_compile.c

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11519,12 +11519,26 @@ static void zend_compile_array(znode *result, zend_ast *ast) /* {{{ */
1151911519
}
1152011520
/* }}} */
1152111521

11522+
static void zend_emit_fetch_constant(znode *result, zend_string *resolved_name, bool unqualified_in_namespace) /* {{{ */
11523+
{
11524+
zend_op *opline = zend_emit_op_tmp(result, ZEND_FETCH_CONSTANT, NULL, NULL);
11525+
opline->op2_type = IS_CONST;
11526+
11527+
if (unqualified_in_namespace) {
11528+
opline->op1.num = IS_CONSTANT_UNQUALIFIED_IN_NAMESPACE;
11529+
opline->op2.constant = zend_add_const_name_literal(resolved_name, true);
11530+
} else {
11531+
opline->op1.num = 0;
11532+
opline->op2.constant = zend_add_const_name_literal(resolved_name, false);
11533+
}
11534+
opline->extended_value = zend_alloc_cache_slot();
11535+
}
11536+
/* }}} */
11537+
1152211538
static void zend_compile_const(znode *result, const zend_ast *ast) /* {{{ */
1152311539
{
1152411540
zend_ast *name_ast = ast->child[0];
1152511541

11526-
zend_op *opline;
11527-
1152811542
bool is_fully_qualified;
1152911543
zend_string *orig_name = zend_ast_get_str(name_ast);
1153011544
zend_string *resolved_name = zend_resolve_const_name(orig_name, name_ast->attr, &is_fully_qualified);
@@ -11553,19 +11567,17 @@ static void zend_compile_const(znode *result, const zend_ast *ast) /* {{{ */
1155311567
return;
1155411568
}
1155511569

11556-
opline = zend_emit_op_tmp(result, ZEND_FETCH_CONSTANT, NULL, NULL);
11557-
opline->op2_type = IS_CONST;
11570+
zend_emit_fetch_constant(result, resolved_name,
11571+
!is_fully_qualified && FC(current_namespace));
11572+
}
11573+
/* }}} */
1155811574

11559-
if (is_fully_qualified || !FC(current_namespace)) {
11560-
opline->op1.num = 0;
11561-
opline->op2.constant = zend_add_const_name_literal(
11562-
resolved_name, false);
11563-
} else {
11564-
opline->op1.num = IS_CONSTANT_UNQUALIFIED_IN_NAMESPACE;
11565-
opline->op2.constant = zend_add_const_name_literal(
11566-
resolved_name, true);
11567-
}
11568-
opline->extended_value = zend_alloc_cache_slot();
11575+
static void zend_compile_constant(znode *result, zend_ast *ast) /* {{{ */
11576+
{
11577+
zend_string *name = zend_ast_get_constant_name(ast);
11578+
11579+
zend_emit_fetch_constant(result, zend_string_copy(name),
11580+
(ast->attr & IS_CONSTANT_UNQUALIFIED_IN_NAMESPACE) != 0);
1156911581
}
1157011582
/* }}} */
1157111583

@@ -12436,6 +12448,9 @@ static void zend_compile_expr_inner(znode *result, zend_ast *ast) /* {{{ */
1243612448
case ZEND_AST_CONST:
1243712449
zend_compile_const(result, ast);
1243812450
return;
12451+
case ZEND_AST_CONSTANT:
12452+
zend_compile_constant(result, ast);
12453+
return;
1243912454
case ZEND_AST_CLASS_CONST:
1244012455
zend_compile_class_const(result, ast);
1244112456
return;

Zend/zend_partial.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -588,6 +588,7 @@ static zend_ast *zp_compile_forwarding_call(
588588
if (Z_TYPE(default_value) == IS_CONSTANT_AST) {
589589
/* Must dup AST because we are going to destroy it */
590590
default_value_ast = zend_ast_dup(Z_ASTVAL(default_value));
591+
zval_ptr_dtor_nogc(&default_value);
591592
} else {
592593
default_value_ast = zend_ast_create_zval(&default_value);
593594
}

0 commit comments

Comments
 (0)