Skip to content

Commit 7330366

Browse files
authored
zend_ast: Wrap class names in parens during export when they are an expression (php#22389)
Fixes php#22387.
1 parent e758d88 commit 7330366

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
@@ -4,6 +4,8 @@ PHP NEWS
44

55
- Core:
66
. Sync Boost.Context assembly with 1.91.0. (kn1g78)
7+
. Fixed bug GH-22387 (AST pretty-printing drops meaningful parentheses around
8+
RHS of instanceof). (timwolla)
79

810
- DBA:
911
. Fixed OOB read on malformed length field in dba flatfile handler. (alhudz)

Zend/zend_ast.c

Lines changed: 44 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1686,6 +1686,19 @@ static ZEND_COLD void zend_ast_export_ns_name(smart_str *str, zend_ast *ast, int
16861686
}
16871687
zend_ast_export_ex(str, ast, priority, indent);
16881688
}
1689+
static ZEND_COLD void zend_ast_export_ns_name_or_expression(smart_str *str, zend_ast *ast, int priority, int indent)
1690+
{
1691+
switch (ast->kind) {
1692+
case ZEND_AST_ZVAL:
1693+
case ZEND_AST_VAR:
1694+
zend_ast_export_ns_name(str, ast, priority, indent);
1695+
break;
1696+
default:
1697+
smart_str_appendc(str, '(');
1698+
zend_ast_export_ex(str, ast, priority, indent);
1699+
smart_str_appendc(str, ')');
1700+
}
1701+
}
16891702

16901703
static ZEND_COLD bool zend_ast_valid_var_name(const char *s, size_t len)
16911704
{
@@ -2523,25 +2536,12 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio
25232536
zend_ast_export_var(str, ast->child[1], indent);
25242537
break;
25252538
case ZEND_AST_STATIC_PROP:
2526-
zend_ast_export_ns_name(str, ast->child[0], 0, indent);
2539+
zend_ast_export_ns_name_or_expression(str, ast->child[0], 0, indent);
25272540
smart_str_appends(str, "::$");
25282541
zend_ast_export_var(str, ast->child[1], indent);
25292542
break;
25302543
case ZEND_AST_CALL: {
2531-
zend_ast *left = ast->child[0];
2532-
switch (left->kind) {
2533-
/* ZEND_AST_ZVAL is a regular function call. */
2534-
case ZEND_AST_ZVAL:
2535-
/* ZEND_AST_VAR ($foo()) is unambiguous without parens. */
2536-
case ZEND_AST_VAR:
2537-
zend_ast_export_ns_name(str, left, 0, indent);
2538-
break;
2539-
default:
2540-
smart_str_appendc(str, '(');
2541-
zend_ast_export_ex(str, left, 0, indent);
2542-
smart_str_appendc(str, ')');
2543-
break;
2544-
}
2544+
zend_ast_export_ns_name_or_expression(str, ast->child[0], 0, indent);
25452545
smart_str_appendc(str, '(');
25462546
zend_ast_export_ex(str, ast->child[1], 0, indent);
25472547
smart_str_appendc(str, ')');
@@ -2553,7 +2553,7 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio
25532553
goto simple_list;
25542554
}
25552555
case ZEND_AST_CLASS_CONST:
2556-
zend_ast_export_ns_name(str, ast->child[0], 0, indent);
2556+
zend_ast_export_ns_name_or_expression(str, ast->child[0], 0, indent);
25572557
smart_str_appends(str, "::");
25582558
zend_ast_export_name(str, ast->child[1], 0, indent);
25592559
break;
@@ -2570,7 +2570,7 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio
25702570
default: ZEND_UNREACHABLE();
25712571
}
25722572
} else {
2573-
zend_ast_export_ns_name(str, ast->child[0], 0, indent);
2573+
zend_ast_export_ns_name_or_expression(str, ast->child[0], 0, indent);
25742574
}
25752575
smart_str_appends(str, "::class");
25762576
break;
@@ -2649,7 +2649,7 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio
26492649
}
26502650
zend_ast_export_class_no_header(str, decl, indent);
26512651
} else {
2652-
zend_ast_export_ns_name(str, ast->child[0], 0, indent);
2652+
zend_ast_export_ns_name_or_expression(str, ast->child[0], 0, indent);
26532653
smart_str_appendc(str, '(');
26542654
zend_ast_export_ex(str, ast->child[1], 0, indent);
26552655
smart_str_appendc(str, ')');
@@ -2658,7 +2658,7 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio
26582658
case ZEND_AST_INSTANCEOF:
26592659
zend_ast_export_ex(str, ast->child[0], 0, indent);
26602660
smart_str_appends(str, " instanceof ");
2661-
zend_ast_export_ns_name(str, ast->child[1], 0, indent);
2661+
zend_ast_export_ns_name_or_expression(str, ast->child[1], 0, indent);
26622662
break;
26632663
case ZEND_AST_YIELD:
26642664
if (priority > 70) smart_str_appendc(str, '(');
@@ -2851,7 +2851,12 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio
28512851
smart_str_appendc(str, ')');
28522852
break;
28532853
case ZEND_AST_STATIC_CALL:
2854-
zend_ast_export_ns_name(str, ast->child[0], 0, indent);
2854+
if (zend_ast_is_parent_hook_call(ast)) {
2855+
zend_ast_export_ns_name(str, ast->child[0], 0, indent);
2856+
} else {
2857+
zend_ast_export_ns_name_or_expression(str, ast->child[0], 0, indent);
2858+
}
2859+
28552860
smart_str_appends(str, "::");
28562861
zend_ast_export_var(str, ast->child[1], indent);
28572862
smart_str_appendc(str, '(');
@@ -3082,3 +3087,22 @@ zend_ast * ZEND_FASTCALL zend_ast_call_get_args(zend_ast *ast)
30823087
ZEND_UNREACHABLE();
30833088
return NULL;
30843089
}
3090+
3091+
bool zend_ast_is_parent_hook_call(const zend_ast *ast)
3092+
{
3093+
ZEND_ASSERT(ast->kind == ZEND_AST_STATIC_CALL);
3094+
3095+
const zend_ast *class_ast = ast->child[0];
3096+
zend_ast *method_ast = ast->child[1];
3097+
3098+
return class_ast->kind == ZEND_AST_STATIC_PROP
3099+
&& !(class_ast->attr & ZEND_PARENTHESIZED_STATIC_PROP)
3100+
&& class_ast->child[0]->kind == ZEND_AST_ZVAL
3101+
&& Z_TYPE_P(zend_ast_get_zval(class_ast->child[0])) == IS_STRING
3102+
&& zend_get_class_fetch_type(zend_ast_get_str(class_ast->child[0])) == ZEND_FETCH_CLASS_PARENT
3103+
&& class_ast->child[1]->kind == ZEND_AST_ZVAL
3104+
&& method_ast->kind == ZEND_AST_ZVAL
3105+
&& Z_TYPE_P(zend_ast_get_zval(method_ast)) == IS_STRING
3106+
&& (zend_string_equals_literal_ci(zend_ast_get_str(method_ast), "get")
3107+
|| zend_string_equals_literal_ci(zend_ast_get_str(method_ast), "set"));
3108+
}

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
@@ -5353,17 +5353,7 @@ static bool zend_compile_parent_property_hook_call(znode *result, const zend_ast
53535353
const zend_ast *class_ast = ast->child[0];
53545354
zend_ast *method_ast = ast->child[1];
53555355

5356-
/* Recognize parent::$prop::get() pattern. */
5357-
if (class_ast->kind != ZEND_AST_STATIC_PROP
5358-
|| (class_ast->attr & ZEND_PARENTHESIZED_STATIC_PROP)
5359-
|| class_ast->child[0]->kind != ZEND_AST_ZVAL
5360-
|| Z_TYPE_P(zend_ast_get_zval(class_ast->child[0])) != IS_STRING
5361-
|| zend_get_class_fetch_type(zend_ast_get_str(class_ast->child[0])) != ZEND_FETCH_CLASS_PARENT
5362-
|| class_ast->child[1]->kind != ZEND_AST_ZVAL
5363-
|| method_ast->kind != ZEND_AST_ZVAL
5364-
|| Z_TYPE_P(zend_ast_get_zval(method_ast)) != IS_STRING
5365-
|| (!zend_string_equals_literal_ci(zend_ast_get_str(method_ast), "get")
5366-
&& !zend_string_equals_literal_ci(zend_ast_get_str(method_ast), "set"))) {
5356+
if (!zend_ast_is_parent_hook_call(ast)) {
53675357
return false;
53685358
}
53695359

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)