Skip to content

Commit 4a1186b

Browse files
committed
zend_ast: Wrap class names in parens during export when they are an expression
Fixes #22387.
1 parent cad6ed2 commit 4a1186b

5 files changed

Lines changed: 112 additions & 31 deletions

File tree

NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ PHP NEWS
3030
in string interpolation). (timwolla)
3131
. Fixed bug GH-22373 (AST pretty-printing drops meaningful parentheses
3232
surrounding property access). (timwolla)
33+
. Fixed bug GH-22387 (AST pretty-printing drops meaningful parentheses around
34+
RHS of instanceof). (timwolla)
3335

3436
- BCMath:
3537
. Added NUL-byte validation to BCMath functions. (jorgsowa)

Zend/zend_ast.c

Lines changed: 44 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1691,6 +1691,19 @@ static ZEND_COLD void zend_ast_export_ns_name(smart_str *str, zend_ast *ast, int
16911691
}
16921692
zend_ast_export_ex(str, ast, priority, indent);
16931693
}
1694+
static ZEND_COLD void zend_ast_export_ns_name_or_expression(smart_str *str, zend_ast *ast, int priority, int indent)
1695+
{
1696+
switch (ast->kind) {
1697+
case ZEND_AST_ZVAL:
1698+
case ZEND_AST_VAR:
1699+
zend_ast_export_ns_name(str, ast, priority, indent);
1700+
break;
1701+
default:
1702+
smart_str_appendc(str, '(');
1703+
zend_ast_export_ex(str, ast, priority, indent);
1704+
smart_str_appendc(str, ')');
1705+
}
1706+
}
16941707

16951708
static ZEND_COLD bool zend_ast_valid_var_name(const char *s, size_t len)
16961709
{
@@ -2529,25 +2542,12 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio
25292542
zend_ast_export_var(str, ast->child[1], indent);
25302543
break;
25312544
case ZEND_AST_STATIC_PROP:
2532-
zend_ast_export_ns_name(str, ast->child[0], 0, indent);
2545+
zend_ast_export_ns_name_or_expression(str, ast->child[0], 0, indent);
25332546
smart_str_appends(str, "::$");
25342547
zend_ast_export_var(str, ast->child[1], indent);
25352548
break;
25362549
case ZEND_AST_CALL: {
2537-
zend_ast *left = ast->child[0];
2538-
switch (left->kind) {
2539-
/* ZEND_AST_ZVAL is a regular function call. */
2540-
case ZEND_AST_ZVAL:
2541-
/* ZEND_AST_VAR ($foo()) is unambiguous without parens. */
2542-
case ZEND_AST_VAR:
2543-
zend_ast_export_ns_name(str, left, 0, indent);
2544-
break;
2545-
default:
2546-
smart_str_appendc(str, '(');
2547-
zend_ast_export_ex(str, left, 0, indent);
2548-
smart_str_appendc(str, ')');
2549-
break;
2550-
}
2550+
zend_ast_export_ns_name_or_expression(str, ast->child[0], 0, indent);
25512551
smart_str_appendc(str, '(');
25522552
zend_ast_export_ex(str, ast->child[1], 0, indent);
25532553
smart_str_appendc(str, ')');
@@ -2559,7 +2559,7 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio
25592559
goto simple_list;
25602560
}
25612561
case ZEND_AST_CLASS_CONST:
2562-
zend_ast_export_ns_name(str, ast->child[0], 0, indent);
2562+
zend_ast_export_ns_name_or_expression(str, ast->child[0], 0, indent);
25632563
smart_str_appends(str, "::");
25642564
zend_ast_export_name(str, ast->child[1], 0, indent);
25652565
break;
@@ -2576,7 +2576,7 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio
25762576
default: ZEND_UNREACHABLE();
25772577
}
25782578
} else {
2579-
zend_ast_export_ns_name(str, ast->child[0], 0, indent);
2579+
zend_ast_export_ns_name_or_expression(str, ast->child[0], 0, indent);
25802580
}
25812581
smart_str_appends(str, "::class");
25822582
break;
@@ -2655,7 +2655,7 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio
26552655
}
26562656
zend_ast_export_class_no_header(str, decl, indent);
26572657
} else {
2658-
zend_ast_export_ns_name(str, ast->child[0], 0, indent);
2658+
zend_ast_export_ns_name_or_expression(str, ast->child[0], 0, indent);
26592659
smart_str_appendc(str, '(');
26602660
zend_ast_export_ex(str, ast->child[1], 0, indent);
26612661
smart_str_appendc(str, ')');
@@ -2664,7 +2664,7 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio
26642664
case ZEND_AST_INSTANCEOF:
26652665
zend_ast_export_ex(str, ast->child[0], 0, indent);
26662666
smart_str_appends(str, " instanceof ");
2667-
zend_ast_export_ns_name(str, ast->child[1], 0, indent);
2667+
zend_ast_export_ns_name_or_expression(str, ast->child[1], 0, indent);
26682668
break;
26692669
case ZEND_AST_YIELD:
26702670
if (priority > 70) smart_str_appendc(str, '(');
@@ -2857,7 +2857,12 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio
28572857
smart_str_appendc(str, ')');
28582858
break;
28592859
case ZEND_AST_STATIC_CALL:
2860-
zend_ast_export_ns_name(str, ast->child[0], 0, indent);
2860+
if (zend_ast_is_parent_hook_call(ast)) {
2861+
zend_ast_export_ns_name(str, ast->child[0], 0, indent);
2862+
} else {
2863+
zend_ast_export_ns_name_or_expression(str, ast->child[0], 0, indent);
2864+
}
2865+
28612866
smart_str_appends(str, "::");
28622867
zend_ast_export_var(str, ast->child[1], indent);
28632868
smart_str_appendc(str, '(');
@@ -3088,3 +3093,22 @@ zend_ast * ZEND_FASTCALL zend_ast_call_get_args(zend_ast *ast)
30883093
ZEND_UNREACHABLE();
30893094
return NULL;
30903095
}
3096+
3097+
bool zend_ast_is_parent_hook_call(const zend_ast *ast)
3098+
{
3099+
ZEND_ASSERT(ast->kind == ZEND_AST_STATIC_CALL);
3100+
3101+
const zend_ast *class_ast = ast->child[0];
3102+
zend_ast *method_ast = ast->child[1];
3103+
3104+
return class_ast->kind == ZEND_AST_STATIC_PROP
3105+
&& !(class_ast->attr & ZEND_PARENTHESIZED_STATIC_PROP)
3106+
&& class_ast->child[0]->kind == ZEND_AST_ZVAL
3107+
&& Z_TYPE_P(zend_ast_get_zval(class_ast->child[0])) == IS_STRING
3108+
&& zend_get_class_fetch_type(zend_ast_get_str(class_ast->child[0])) == ZEND_FETCH_CLASS_PARENT
3109+
&& class_ast->child[1]->kind == ZEND_AST_ZVAL
3110+
&& method_ast->kind == ZEND_AST_ZVAL
3111+
&& Z_TYPE_P(zend_ast_get_zval(method_ast)) == IS_STRING
3112+
&& (zend_string_equals_literal_ci(zend_ast_get_str(method_ast), "get")
3113+
|| zend_string_equals_literal_ci(zend_ast_get_str(method_ast), "set"));
3114+
}

Zend/zend_ast.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,4 +440,7 @@ zend_ast * ZEND_FASTCALL zend_ast_with_attributes(zend_ast *ast, zend_ast *attr)
440440

441441
zend_ast * ZEND_FASTCALL zend_ast_call_get_args(zend_ast *ast);
442442

443+
/* Recognize parent::$prop::get() pattern. */
444+
bool zend_ast_is_parent_hook_call(const zend_ast *ast);
445+
443446
#endif

Zend/zend_compile.c

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5348,17 +5348,7 @@ static bool zend_compile_parent_property_hook_call(znode *result, const zend_ast
53485348
const zend_ast *class_ast = ast->child[0];
53495349
zend_ast *method_ast = ast->child[1];
53505350

5351-
/* Recognize parent::$prop::get() pattern. */
5352-
if (class_ast->kind != ZEND_AST_STATIC_PROP
5353-
|| (class_ast->attr & ZEND_PARENTHESIZED_STATIC_PROP)
5354-
|| class_ast->child[0]->kind != ZEND_AST_ZVAL
5355-
|| Z_TYPE_P(zend_ast_get_zval(class_ast->child[0])) != IS_STRING
5356-
|| zend_get_class_fetch_type(zend_ast_get_str(class_ast->child[0])) != ZEND_FETCH_CLASS_PARENT
5357-
|| class_ast->child[1]->kind != ZEND_AST_ZVAL
5358-
|| method_ast->kind != ZEND_AST_ZVAL
5359-
|| Z_TYPE_P(zend_ast_get_zval(method_ast)) != IS_STRING
5360-
|| (!zend_string_equals_literal_ci(zend_ast_get_str(method_ast), "get")
5361-
&& !zend_string_equals_literal_ci(zend_ast_get_str(method_ast), "set"))) {
5351+
if (!zend_ast_is_parent_hook_call(ast)) {
53625352
return false;
53635353
}
53645354

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
--TEST--
2+
GH-22387: AST pretty-printing drops meaningful parentheses around RHS of instanceof
3+
--FILE--
4+
<?php
5+
6+
class Foo {
7+
public static $p = true;
8+
public const C = true;
9+
10+
public static function m() {
11+
return true;
12+
}
13+
}
14+
15+
$foo = new Foo();
16+
const bar = 'Foo';
17+
const baz = new stdClass();
18+
19+
try {
20+
assert(!$foo instanceof (bar));
21+
} catch (AssertionError $e) {
22+
echo $e->getMessage(), PHP_EOL;
23+
}
24+
25+
try {
26+
assert(!new (bar)());
27+
} catch (AssertionError $e) {
28+
echo $e->getMessage(), PHP_EOL;
29+
}
30+
31+
try {
32+
assert(!(bar)::m());
33+
} catch (AssertionError $e) {
34+
echo $e->getMessage(), PHP_EOL;
35+
}
36+
37+
try {
38+
assert(!(bar)::$p);
39+
} catch (AssertionError $e) {
40+
echo $e->getMessage(), PHP_EOL;
41+
}
42+
43+
try {
44+
assert(!(bar)::C);
45+
} catch (AssertionError $e) {
46+
echo $e->getMessage(), PHP_EOL;
47+
}
48+
49+
try {
50+
assert((baz)::class !== 'stdClass');
51+
} catch (AssertionError $e) {
52+
echo $e->getMessage(), PHP_EOL;
53+
}
54+
55+
?>
56+
--EXPECT--
57+
assert(!$foo instanceof (bar))
58+
assert(!new (bar)())
59+
assert(!(bar)::m())
60+
assert(!(bar)::$p)
61+
assert(!(bar)::C)
62+
assert((baz)::class !== 'stdClass')

0 commit comments

Comments
 (0)