Skip to content

Commit 0996526

Browse files
committed
Normalize to AST_STMT_LIST
Per @tpunt's suggestion.
1 parent fec4dd6 commit 0996526

File tree

3 files changed

+120
-6
lines changed

3 files changed

+120
-6
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,9 @@ Version changelog
429429
statements (`;`).
430430
* Type hints `int`, `float`, `string`, `bool` and `void` will now be represented as `AST_TYPE`
431431
nodes with a respective flag.
432+
* Many `stmts` children could previously hold one of `null`, a single node or an `AST_STMT_LIST`.
433+
These will now be normalized to always use an `AST_STMT_LIST`. A `null` is only allowed if it is
434+
semantically meaningful, e.g. in the case of `declare(ticks=1);` vs `declare(ticks=1) {}`.
432435

433436
### 30 (current)
434437

@@ -485,6 +488,9 @@ of consequences:
485488
while running on PHP 7.0). Similarly, it is not possible to parse code that is no longer
486489
syntactically valid on the used version (e.g. some PHP 5 code may no longer be parsed -- however
487490
most code will work). PHP-Parser supports parsing both newer and older (up to PHP 5.2) versions.
491+
* php-ast only provides the starting line number (and for declarations the ending line number) of
492+
nodes, because this is the only part that PHP itself stores. PHP-Parser provides precise file
493+
offsets.
488494

489495
There are a number of differences in the AST representation and available support code:
490496

ast.c

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,31 @@ static inline zend_bool ast_is_var_name(zend_ast *ast, zend_ast *parent, uint32_
157157
|| (parent->kind == ZEND_AST_CATCH && i == 1);
158158
}
159159

160+
/* Whether this node may need statement list normalization */
161+
static inline zend_bool ast_should_normalize_list(zend_ast *ast, zend_ast *parent, uint32_t i) {
162+
if (ast && ast->kind == ZEND_AST_STMT_LIST) {
163+
return 0;
164+
}
165+
166+
if (i == 0) {
167+
return parent->kind == ZEND_AST_DO_WHILE;
168+
}
169+
if (i == 1) {
170+
if (parent->kind == ZEND_AST_DECLARE) {
171+
/* declare(); and declare() {} are not the same */
172+
return ast != NULL;
173+
}
174+
return parent->kind == ZEND_AST_IF_ELEM || parent->kind == ZEND_AST_WHILE;
175+
}
176+
if (i == 2) {
177+
return parent->kind == ZEND_AST_CATCH;
178+
}
179+
if (i == 3) {
180+
return parent->kind == ZEND_AST_FOR || parent->kind == ZEND_AST_FOREACH;
181+
}
182+
return 0;
183+
}
184+
160185
/* Adopted from zend_compile.c */
161186
typedef struct _builtin_type_info {
162187
const char* name;
@@ -216,7 +241,8 @@ static inline zend_ast **ast_get_children(zend_ast *ast, uint32_t *count) {
216241
}
217242
}
218243

219-
/* "child" may be AST_ZVAL or NULL */
244+
static void ast_to_zval(zval *zv, zend_ast *ast, zend_long version);
245+
220246
static void ast_create_virtual_node_ex(
221247
zval *zv, zend_ast_kind kind, zend_ast_attr attr, uint32_t lineno,
222248
zend_ast *child, zend_long version) {
@@ -237,9 +263,10 @@ static void ast_create_virtual_node_ex(
237263
ast_update_property(zv, AST_STR(str_children), &tmp_zv, AST_CACHE_SLOT_CHILDREN);
238264

239265
if (child) {
240-
ZVAL_COPY(&tmp_zv2, zend_ast_get_zval(child));
241-
if (version >= 30) {
242-
zend_hash_add_new(Z_ARRVAL(tmp_zv), ast_kind_child_name(kind, 0), &tmp_zv2);
266+
zend_string *child_name = version >= 30 ? ast_kind_child_name(kind, 0) : NULL;
267+
ast_to_zval(&tmp_zv2, child, version);
268+
if (child_name) {
269+
zend_hash_add_new(Z_ARRVAL(tmp_zv), child_name, &tmp_zv2);
243270
} else {
244271
zend_hash_next_index_insert(Z_ARRVAL(tmp_zv), &tmp_zv2);
245272
}
@@ -252,8 +279,6 @@ static void ast_create_virtual_node(
252279
zv, kind, child->attr, zend_ast_get_lineno(child), child, version);
253280
}
254281

255-
static void ast_to_zval(zval *zv, zend_ast *ast, zend_long version);
256-
257282
static void ast_fill_children_ht(HashTable *ht, zend_ast *ast, zend_long version) {
258283
uint32_t i, count;
259284
zend_bool is_list = zend_ast_is_list(ast);
@@ -300,6 +325,10 @@ static void ast_fill_children_ht(HashTable *ht, zend_ast *ast, zend_long version
300325
ast_create_virtual_node(&child_zv, AST_CLOSURE_VAR, child, version);
301326
} else if (version >= 20 && ast_is_var_name(child, ast, i)) {
302327
ast_create_virtual_node(&child_zv, ZEND_AST_VAR, child, version);
328+
} else if (version >= 40 && ast_should_normalize_list(child, ast, i)) {
329+
ast_create_virtual_node_ex(
330+
&child_zv, ZEND_AST_STMT_LIST, 0,
331+
zend_ast_get_lineno(child ? child : ast), child, version);
303332
} else if (i == 2
304333
&& (ast->kind == ZEND_AST_PROP_ELEM || ast->kind == ZEND_AST_CONST_ELEM)) {
305334
/* Skip docComment child -- It's handled separately */

tests/stmt_list.phpt

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
--TEST--
2+
Statement list normalization
3+
--FILE--
4+
<?php
5+
6+
require __DIR__ . '/../util.php';
7+
8+
$code = <<<'PHP'
9+
<?php
10+
while ($a);
11+
while ($a) $b;
12+
while ($a) { $b; }
13+
14+
declare(ticks=1);
15+
declare(ticks=1) {}
16+
PHP;
17+
18+
echo ast_dump(ast\parse_code($code, $version=30)), "\n";
19+
echo ast_dump(ast\parse_code($code, $version=40)), "\n";
20+
21+
?>
22+
--EXPECT--
23+
AST_STMT_LIST
24+
0: AST_WHILE
25+
cond: AST_VAR
26+
name: "a"
27+
stmts: null
28+
1: AST_WHILE
29+
cond: AST_VAR
30+
name: "a"
31+
stmts: AST_VAR
32+
name: "b"
33+
2: AST_WHILE
34+
cond: AST_VAR
35+
name: "a"
36+
stmts: AST_STMT_LIST
37+
0: AST_VAR
38+
name: "b"
39+
3: AST_DECLARE
40+
declares: AST_CONST_DECL
41+
0: AST_CONST_ELEM
42+
name: "ticks"
43+
value: 1
44+
stmts: null
45+
4: AST_DECLARE
46+
declares: AST_CONST_DECL
47+
0: AST_CONST_ELEM
48+
name: "ticks"
49+
value: 1
50+
stmts: AST_STMT_LIST
51+
AST_STMT_LIST
52+
0: AST_WHILE
53+
cond: AST_VAR
54+
name: "a"
55+
stmts: AST_STMT_LIST
56+
1: AST_WHILE
57+
cond: AST_VAR
58+
name: "a"
59+
stmts: AST_STMT_LIST
60+
0: AST_VAR
61+
name: "b"
62+
2: AST_WHILE
63+
cond: AST_VAR
64+
name: "a"
65+
stmts: AST_STMT_LIST
66+
0: AST_VAR
67+
name: "b"
68+
3: AST_DECLARE
69+
declares: AST_CONST_DECL
70+
0: AST_CONST_ELEM
71+
name: "ticks"
72+
value: 1
73+
stmts: null
74+
4: AST_DECLARE
75+
declares: AST_CONST_DECL
76+
0: AST_CONST_ELEM
77+
name: "ticks"
78+
value: 1
79+
stmts: AST_STMT_LIST

0 commit comments

Comments
 (0)