From ee9b00c764eac934343d9871051345f39e7311fe Mon Sep 17 00:00:00 2001 From: Jorg Sowa Date: Fri, 30 Oct 2026 20:34:18 +0100 Subject: [PATCH] Fix AST export dropping parentheses in property fetch, method call and instanceof --- NEWS | 2 + .../tests/assert/ast_export_deref_parens.phpt | 41 +++++++++++++++++++ Zend/zend_ast.c | 8 ++-- 3 files changed, 48 insertions(+), 3 deletions(-) create mode 100644 Zend/tests/assert/ast_export_deref_parens.phpt diff --git a/NEWS b/NEWS index 798f849854c1..026949cd09b3 100644 --- a/NEWS +++ b/NEWS @@ -36,6 +36,8 @@ PHP NEWS (Marti2203). . Fixed bug GH-24989 (Use-after-free in GC when an exception is thrown). (David Carlier) + . Fixed AST export dropping parentheses around the left operand of property + fetches, method calls and instanceof. (Jorg Sowa) - CLI: . Fixed bug GH-24697 (PHP_HTTP_PARSER_STRICT build option doesn't work). diff --git a/Zend/tests/assert/ast_export_deref_parens.phpt b/Zend/tests/assert/ast_export_deref_parens.phpt new file mode 100644 index 000000000000..22530d46d630 --- /dev/null +++ b/Zend/tests/assert/ast_export_deref_parens.phpt @@ -0,0 +1,41 @@ +--TEST-- +assert() message keeps parentheses around dereferenced and instanceof operands +--INI-- +zend.assertions=1 +--FILE-- + assert(($a ?? $c)->x), + fn() => assert(($a ?? $c)?->x), + fn() => assert(($a ?? $c)->m()), + fn() => assert(($a ?? $c)?->m()), + fn() => assert(($a ?? $c) instanceof D), + fn() => assert(-($c instanceof D)), + fn() => assert(!$c instanceof C), +]; + +foreach ($tests as $test) { + try { + $test(); + } catch (AssertionError $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; + } +} +?> +--EXPECT-- +AssertionError: assert(($a ?? $c)->x) +AssertionError: assert(($a ?? $c)?->x) +AssertionError: assert(($a ?? $c)->m()) +AssertionError: assert(($a ?? $c)?->m()) +AssertionError: assert(($a ?? $c) instanceof D) +AssertionError: assert(-($c instanceof D)) +AssertionError: assert(!($c instanceof C)) diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index 4e640a321d1f..57677800eeab 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -2200,7 +2200,7 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio break; case ZEND_AST_PROP: case ZEND_AST_NULLSAFE_PROP: - zend_ast_export_ex(str, ast->child[0], 0, indent); + zend_ast_export_ex(str, ast->child[0], 260, indent); smart_str_appends(str, ast->kind == ZEND_AST_NULLSAFE_PROP ? "?->" : "->"); zend_ast_export_var(str, ast->child[1], 0, indent); break; @@ -2327,9 +2327,11 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio } break; case ZEND_AST_INSTANCEOF: - zend_ast_export_ex(str, ast->child[0], 0, indent); + if (priority > 230) smart_str_appendc(str, '('); + zend_ast_export_ex(str, ast->child[0], 231, indent); smart_str_appends(str, " instanceof "); zend_ast_export_ns_name(str, ast->child[1], 0, indent); + if (priority > 230) smart_str_appendc(str, ')'); break; case ZEND_AST_YIELD: if (priority > 70) smart_str_appendc(str, '('); @@ -2539,7 +2541,7 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio /* 3 child nodes */ case ZEND_AST_METHOD_CALL: case ZEND_AST_NULLSAFE_METHOD_CALL: - zend_ast_export_ex(str, ast->child[0], 0, indent); + zend_ast_export_ex(str, ast->child[0], 260, indent); smart_str_appends(str, ast->kind == ZEND_AST_NULLSAFE_METHOD_CALL ? "?->" : "->"); zend_ast_export_var(str, ast->child[1], 0, indent); smart_str_appendc(str, '(');