Skip to content

Fix crash when a partial skips a param with a constant default#22804

Merged
iliaal merged 1 commit into
php:masterfrom
iliaal:fix/pfa-const-default-gap-crash
Jul 20, 2026
Merged

Fix crash when a partial skips a param with a constant default#22804
iliaal merged 1 commit into
php:masterfrom
iliaal:fix/pfa-const-default-gap-crash

Conversation

@iliaal

@iliaal iliaal commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

A partial that leaves an optional parameter unbound through a named-argument gap crashes the compiler when that parameter's default is a constant expression: f(a: 10, c: ?) against function f($a, $b = SOME_CONST, $c = 0). Const exprs represent a constant fetch as ZEND_AST_CONSTANT when it could not be evaluated at compile time, and zend_compile_expr_inner() has no case for that kind, so compiling the callee default into the generated forwarding call hits ZEND_ASSERT(0) in debug and __builtin_unreachable in release. A literal default is a ZEND_AST_ZVAL and is unaffected.

zend_compile_expr_inner() now compiles ZEND_AST_CONSTANT to ZEND_FETCH_CONSTANT, reusing the name zend_compile_const_expr_const() already resolved along with its unqualified-in-namespace flag. The constant is looked up when the partial is invoked, as in a direct call, and its value is never folded into the forwarding op_array's literals.

zp_compile_forwarding_call() also dropped the reference it takes on a constant-expression default value, which leaks on master for class-constant and enum-case defaults.

@arnaud-lb

arnaud-lb commented Jul 20, 2026

Copy link
Copy Markdown
Member

Interesting one. It looks like an edge case with ZEND_AST_CONSTANT vs ZEND_AST_CONST: The compiler knows how to compile ZEND_AST_CONST, but const exprs represent const fetches as ZEND_AST_CONSTANT, which the compiler doesn't understand.

Unfortunately, passing by name doesn't work in all cases (or requires more work), for example:

function f($a, $b = K, $c = 0, ...$args) {
    return [$a, $b, $c];
}

$p = f(a: 10, c: ?, foo: 1);

Will generate:

static function ($c) use($a, $extra_named_params) {
    return \f($a, c: $c, ...$extra_named_params);
}

which is invalid due to the use of unpacking after a named param. We would need to unpack at compile time, but we may run into other issues.

Possible alternative ways to fix this:

  • Support ZEND_AST_CONSTANT in zend_compile_expr_inner()
  • Convert ZEND_AST_CONSTANT to ZEND_AST_CONST nodes in zp_compile_forwarding_call() (recursively)
  • Remove ZEND_AST_CONSTANT to use only ZEND_AST_CONST in const exprs

I have a preference for the first one. wdyt?

@iliaal
iliaal force-pushed the fix/pfa-const-default-gap-crash branch from c2117da to b3c097d Compare July 20, 2026 11:17
iliaal added a commit to iliaal/php-src that referenced this pull request Jul 20, 2026
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 phpGH-22804
@iliaal

iliaal commented Jul 20, 2026

Copy link
Copy Markdown
Contributor Author

Went with the first one. zend_compile_expr_inner() now compiles ZEND_AST_CONSTANT to ZEND_FETCH_CONSTANT, reusing the name zend_compile_const_expr_const() already resolved plus its unqualified-in-namespace flag, so the constant is looked up when the partial is invoked, not when it is created. Your f(a: 10, c: ?, foo: 1) case is in the test, along with the namespace fallback and a constant defined after the partial is created.

Also released the reference zp_get_param_default_value() takes on the default value. The IS_CONSTANT_AST branch was dropping it, which leaks on master for self::X and enum-case defaults.

@arnaud-lb arnaud-lb left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me! Thank you!

Comment thread Zend/zend_compile.c Outdated
Comment thread Zend/zend_compile.c Outdated
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 phpGH-22804
@iliaal
iliaal force-pushed the fix/pfa-const-default-gap-crash branch from b3c097d to 6de6c31 Compare July 20, 2026 14:07
@iliaal
iliaal merged commit 61a987b into php:master Jul 20, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants