From c3bd629236c195e72601981b948b75449e092126 Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Sat, 18 Jul 2026 13:35:38 -0400 Subject: [PATCH] Free pending named positions on partial-argument compile errors A named placeholder in a partial application lazily allocates a named-positions table that is only consumed once argument compilation succeeds. A later argument that is itself a compile error (a positional or unpack after the named placeholder, or a misplaced variadic placeholder) longjmps out of zend_compile_args_ex before the table is consumed, leaking it. Free the table before those errors. --- .../partial_application/compile_errors_007.phpt | 14 ++++++++++++++ Zend/zend_compile.c | 12 ++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 Zend/tests/partial_application/compile_errors_007.phpt diff --git a/Zend/tests/partial_application/compile_errors_007.phpt b/Zend/tests/partial_application/compile_errors_007.phpt new file mode 100644 index 000000000000..831a0a739945 --- /dev/null +++ b/Zend/tests/partial_application/compile_errors_007.phpt @@ -0,0 +1,14 @@ +--TEST-- +PFA compile errors: positional after a named placeholder frees pending named positions +--DESCRIPTION-- +A named placeholder allocates the named-positions table; a following +positional placeholder is a compile error. The table must not leak on +that bailout. The leak is only visible under valgrind/ASAN; this test +exercises the path so CI catches a regression. +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use positional argument after named argument in %s on line %d diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index f44784239533..555cdb2a6682 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -3775,6 +3775,14 @@ static uint32_t zend_get_arg_num(const zend_function *fn, const zend_string *arg return (uint32_t) -1; } +static zend_always_inline void zend_free_pending_named_positions(zval *named_positions) +{ + if (named_positions != NULL && !Z_ISUNDEF_P(named_positions)) { + zend_array_destroy(Z_ARRVAL_P(named_positions)); + ZVAL_UNDEF(named_positions); + } +} + static uint32_t zend_compile_args_ex( zend_ast *ast, const zend_function *fbc, bool *may_have_extra_named_args, @@ -3806,6 +3814,7 @@ static uint32_t zend_compile_args_ex( if (arg->kind == ZEND_AST_UNPACK) { if (uses_named_args) { + zend_free_pending_named_positions(named_positions); zend_error_noreturn(E_COMPILE_ERROR, "Cannot use argument unpacking after named arguments"); } @@ -3857,6 +3866,7 @@ static uint32_t zend_compile_args_ex( } if (uses_variadic_placeholder) { + zend_free_pending_named_positions(named_positions); zend_error_noreturn(E_COMPILE_ERROR, "Variadic placeholder must be last"); } @@ -3870,12 +3880,14 @@ static uint32_t zend_compile_args_ex( && arg->attr == ZEND_PLACEHOLDER_VARIADIC; if (uses_named_args && !is_variadic_placeholder) { + zend_free_pending_named_positions(named_positions); zend_error_noreturn(E_COMPILE_ERROR, "Cannot use positional argument after named argument"); } if (uses_variadic_placeholder) { if (is_variadic_placeholder) { + zend_free_pending_named_positions(named_positions); zend_error_noreturn(E_COMPILE_ERROR, "Variadic placeholder may only appear once"); } else {