diff --git a/NEWS b/NEWS index a9ccb65c10b..97144036d35 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,9 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ?? ??? ????, PHP 8.5.0alpha2 +- Core: + . Fix OSS-Fuzz #427814452 (pipe compilation fails with assert). (nielsdos) + - DOM: . Make cloning DOM node lists, maps, and collections fail. (nielsdos) diff --git a/Zend/tests/pipe_operator/gh18965.phpt b/Zend/tests/pipe_operator/gh18965.phpt new file mode 100644 index 00000000000..8f9eaaebb08 --- /dev/null +++ b/Zend/tests/pipe_operator/gh18965.phpt @@ -0,0 +1,12 @@ +--TEST-- +GH-18965: ASSERT_CHECK avoids pipe lhs free +--INI-- +zend.assertions=0 +--FILE-- + assert(...); +echo "No leak\n"; +?> +--EXPECT-- +No leak diff --git a/Zend/tests/pipe_operator/oss_fuzz_427814452.phpt b/Zend/tests/pipe_operator/oss_fuzz_427814452.phpt new file mode 100644 index 00000000000..2ecfbab6348 --- /dev/null +++ b/Zend/tests/pipe_operator/oss_fuzz_427814452.phpt @@ -0,0 +1,26 @@ +--TEST-- +OSS-Fuzz #427814452 +--FILE-- + assert(...); +} catch (\AssertionError $e) { + echo $e::class, ": '", $e->getMessage(), "'\n"; +} +try { + 0 |> "assert"(...); +} catch (\AssertionError $e) { + echo $e::class, ": '", $e->getMessage(), "'\n"; +} +try { + false |> ("a"."ssert")(...); +} catch (\AssertionError $e) { + echo $e::class, ": '", $e->getMessage(), "'\n"; +} + +?> +--EXPECT-- +AssertionError: '' +AssertionError: '' +AssertionError: '' diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index f3f6d1b75ae..ef167efc697 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -6426,6 +6426,20 @@ static bool can_match_use_jumptable(zend_ast_list *arms) { return 1; } +static bool zend_is_pipe_optimizable_callable_name(zend_ast *ast) +{ + if (ast->kind == ZEND_AST_ZVAL && Z_TYPE_P(zend_ast_get_zval(ast)) == IS_STRING) { + /* Assert compilation adds a message operand, but this is incompatible with the + * pipe optimization that uses a temporary znode for the reference elimination. + * Therefore, disable the optimization for assert. + * Note that "assert" as a name is always treated as fully qualified. */ + zend_string *str = zend_ast_get_str(ast); + return !zend_string_equals_literal_ci(str, "assert"); + } + + return true; +} + static void zend_compile_pipe(znode *result, zend_ast *ast) { zend_ast *operand_ast = ast->child[0]; @@ -6453,7 +6467,8 @@ static void zend_compile_pipe(znode *result, zend_ast *ast) /* Turn $foo |> bar(...) into bar($foo). */ if (callable_ast->kind == ZEND_AST_CALL - && callable_ast->child[1]->kind == ZEND_AST_CALLABLE_CONVERT) { + && callable_ast->child[1]->kind == ZEND_AST_CALLABLE_CONVERT + && zend_is_pipe_optimizable_callable_name(callable_ast->child[0])) { fcall_ast = zend_ast_create(ZEND_AST_CALL, callable_ast->child[0], arg_list_ast); /* Turn $foo |> bar::baz(...) into bar::baz($foo). */