Skip to content

Commit

Permalink
Preserve the original AST for closures in const-expressions
Browse files Browse the repository at this point in the history
This is necessary when stringifying a `ReflectionAttribute` that has a closure
as a parameter.
  • Loading branch information
TimWolla committed Dec 11, 2024
1 parent 151cd90 commit 05e868f
Show file tree
Hide file tree
Showing 9 changed files with 100 additions and 22 deletions.
24 changes: 24 additions & 0 deletions Zend/tests/closure_const_expr/attributes_ast_printing_runtime.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
--TEST--
AST printing for closures in attributes at runtime
--FILE--
<?php

#[Attr(static function ($foo) {
echo $foo;
})]
function foo() { }

$r = new ReflectionFunction('foo');
foreach ($r->getAttributes() as $attribute) {
echo $attribute;
}

?>
--EXPECT--
Attribute [ Attr ] {
- Arguments [1] {
Argument #0 [ static function ($foo) {
echo $foo;
} ]
}
}
38 changes: 29 additions & 9 deletions Zend/zend_ast.c
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,18 @@ ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_constant(zend_string *name, ze
return (zend_ast *) ast;
}

ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_op_array(zend_op_array *op_array, zend_ast *original_ast, zend_ast_attr attr) {
zend_ast_op_array *ast;

ast = zend_ast_alloc(sizeof(zend_ast_op_array));
ast->kind = ZEND_AST_OP_ARRAY;
ast->attr = attr;
ast->op_array = op_array;
ast->ast = original_ast;

return (zend_ast *) ast;
}

ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_class_const_or_name(zend_ast *class_name, zend_ast *name) {
zend_string *name_str = zend_ast_get_str(name);
if (zend_string_equals_ci(name_str, ZSTR_KNOWN(ZEND_STR_CLASS))) {
Expand Down Expand Up @@ -992,7 +1004,7 @@ ZEND_API zend_result ZEND_FASTCALL zend_ast_evaluate_inner(
}
case ZEND_AST_OP_ARRAY:
{
zend_function *func = Z_PTR_P(&((zend_ast_zval*)(ast))->val);
zend_function *func = (zend_function *)zend_ast_get_op_array(ast)->op_array;

zend_create_closure(result, func, scope, scope, NULL);
return SUCCESS;
Expand Down Expand Up @@ -1076,8 +1088,10 @@ static size_t ZEND_FASTCALL zend_ast_tree_size(zend_ast *ast)
{
size_t size;

if (ast->kind == ZEND_AST_ZVAL || ast->kind == ZEND_AST_CONSTANT || ast->kind == ZEND_AST_OP_ARRAY) {
if (ast->kind == ZEND_AST_ZVAL || ast->kind == ZEND_AST_CONSTANT) {
size = sizeof(zend_ast_zval);
} else if (ast->kind == ZEND_AST_OP_ARRAY) {
size = sizeof(zend_ast_op_array) + zend_ast_tree_size(zend_ast_get_op_array(ast)->ast);
} else if (zend_ast_is_list(ast)) {
uint32_t i;
zend_ast_list *list = zend_ast_get_list(ast);
Expand Down Expand Up @@ -1143,12 +1157,14 @@ static void* ZEND_FASTCALL zend_ast_tree_copy(zend_ast *ast, void *buf)
}
}
} else if (ast->kind == ZEND_AST_OP_ARRAY) {
zend_ast_zval *new = (zend_ast_zval*)buf;
new->kind = ZEND_AST_OP_ARRAY;
new->attr = ast->attr;
ZVAL_COPY(&new->val, &((zend_ast_zval *) ast)->val);
Z_LINENO(new->val) = zend_ast_get_lineno(ast);
buf = (void*)((char*)buf + sizeof(zend_ast_zval));
zend_ast_op_array *old = zend_ast_get_op_array(ast);
zend_ast_op_array *new = (zend_ast_op_array*)buf;
new->kind = old->kind;
new->attr = old->attr;
new->op_array = old->op_array;
buf = (void*)((char*)buf + sizeof(zend_ast_op_array));
new->ast = (zend_ast*)buf;
buf = zend_ast_tree_copy(old->ast, buf);
} else if (zend_ast_is_decl(ast)) {
zend_ast_decl *old = (zend_ast_decl*)ast;
zend_ast_decl *new = (zend_ast_decl*)buf;
Expand Down Expand Up @@ -1241,7 +1257,7 @@ ZEND_API void ZEND_FASTCALL zend_ast_destroy(zend_ast *ast)
} else if (EXPECTED(ast->kind == ZEND_AST_CONSTANT)) {
zend_string_release_ex(zend_ast_get_constant_name(ast), 0);
} else if (EXPECTED(ast->kind == ZEND_AST_OP_ARRAY)) {
ZEND_ASSERT(!Z_REFCOUNTED(((zend_ast_zval*)(ast))->val));
zend_ast_destroy(zend_ast_get_op_array(ast)->ast);
} else if (zend_ast_is_decl(ast)) {
zend_ast_decl *decl = (zend_ast_decl *) ast;

Expand Down Expand Up @@ -1859,6 +1875,10 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio
smart_str_appendl(str, ZSTR_VAL(name), ZSTR_LEN(name));
break;
}
case ZEND_AST_OP_ARRAY: {
zend_ast_export_ex(str, zend_ast_get_op_array(ast)->ast, priority, indent);
break;
}
case ZEND_AST_CONSTANT_CLASS:
smart_str_appendl(str, "__CLASS__", sizeof("__CLASS__")-1);
break;
Expand Down
20 changes: 19 additions & 1 deletion Zend/zend_ast.h
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,15 @@ typedef struct _zend_ast_zval {
zval val;
} zend_ast_zval;

typedef struct _zend_op_array zend_op_array;

typedef struct _zend_ast_op_array {
zend_ast_kind kind;
zend_ast_attr attr;
zend_op_array *op_array;
zend_ast *ast;
} zend_ast_op_array;

/* Separate structure for function and class declaration, as they need extra information. */
typedef struct _zend_ast_decl {
zend_ast_kind kind;
Expand All @@ -231,6 +240,8 @@ ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_zval_from_long(zend_long lval)
ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_constant(zend_string *name, zend_ast_attr attr);
ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_class_const_or_name(zend_ast *class_name, zend_ast *name);

ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_op_array(zend_op_array *op_array, zend_ast *original_ast, zend_ast_attr attr);

#if ZEND_AST_SPEC
# define ZEND_AST_SPEC_CALL(name, ...) \
ZEND_EXPAND_VA(ZEND_AST_SPEC_CALL_(name, __VA_ARGS__, _n, _5, _4, _3, _2, _1, _0)(__VA_ARGS__))
Expand Down Expand Up @@ -353,6 +364,11 @@ static zend_always_inline zend_string *zend_ast_get_str(zend_ast *ast) {
return Z_STR_P(zv);
}

static zend_always_inline zend_ast_op_array *zend_ast_get_op_array(zend_ast *ast) {
ZEND_ASSERT(ast->kind == ZEND_AST_OP_ARRAY);
return (zend_ast_op_array *) ast;
}

static zend_always_inline zend_string *zend_ast_get_constant_name(zend_ast *ast) {
ZEND_ASSERT(ast->kind == ZEND_AST_CONSTANT);
ZEND_ASSERT(Z_TYPE(((zend_ast_zval *) ast)->val) == IS_STRING);
Expand All @@ -367,9 +383,11 @@ static zend_always_inline uint32_t zend_ast_get_lineno(zend_ast *ast) {
if (ast->kind == ZEND_AST_ZVAL) {
zval *zv = zend_ast_get_zval(ast);
return Z_LINENO_P(zv);
} else if (ast->kind == ZEND_AST_CONSTANT || ast->kind == ZEND_AST_OP_ARRAY) {
} else if (ast->kind == ZEND_AST_CONSTANT) {
zval *zv = &((zend_ast_zval *) ast)->val;
return Z_LINENO_P(zv);
} else if (ast->kind == ZEND_AST_OP_ARRAY) {
return zend_ast_get_op_array(ast)->ast->lineno;
} else {
return ast->lineno;
}
Expand Down
8 changes: 2 additions & 6 deletions Zend/zend_compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -11220,13 +11220,9 @@ static void zend_compile_const_expr_closure(zend_ast **ast_ptr)
}

znode node;
zend_op_array *op = zend_compile_func_decl(&node, *ast_ptr, FUNC_DECL_LEVEL_CONSTEXPR);
zend_op_array *op = zend_compile_func_decl(&node, (zend_ast*)closure_ast, FUNC_DECL_LEVEL_CONSTEXPR);

zend_ast_destroy(*ast_ptr);
zval z;
ZVAL_PTR(&z, op);
*ast_ptr = zend_ast_create_zval(&z);
(*ast_ptr)->kind = ZEND_AST_OP_ARRAY;
*ast_ptr = zend_ast_create_op_array(op, (zend_ast*)closure_ast, 0);
}

static void zend_compile_const_expr_args(zend_ast **ast_ptr)
Expand Down
12 changes: 10 additions & 2 deletions ext/opcache/zend_file_cache.c
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,12 @@ static void zend_file_cache_serialize_ast(zend_ast *ast,
}
} else if (ast->kind == ZEND_AST_OP_ARRAY) {
/* The op_array itself will be serialized as part of the dynamic_func_defs. */
SERIALIZE_PTR(Z_PTR(((zend_ast_zval*)ast)->val));
SERIALIZE_PTR(((zend_ast_op_array*)ast)->op_array);

SERIALIZE_PTR(((zend_ast_op_array*)ast)->ast);
tmp = ((zend_ast_op_array*)ast)->ast;
UNSERIALIZE_PTR(tmp);
zend_file_cache_serialize_ast(tmp, script, info, buf);
} else if (zend_ast_is_decl(ast)) {
zend_ast_decl *decl = (zend_ast_decl*)ast;
for (i = 0; i < 5; i++) {
Expand Down Expand Up @@ -1257,7 +1262,10 @@ static void zend_file_cache_unserialize_ast(zend_ast *ast,
}
} else if (ast->kind == ZEND_AST_OP_ARRAY) {
/* The op_array itself will be unserialized as part of the dynamic_func_defs. */
UNSERIALIZE_PTR(Z_PTR(((zend_ast_zval*)ast)->val));
UNSERIALIZE_PTR(((zend_ast_op_array*)ast)->op_array);

UNSERIALIZE_PTR(((zend_ast_op_array*)ast)->ast);
zend_file_cache_unserialize_ast(((zend_ast_op_array*)ast)->ast, script, buf);
} else if (zend_ast_is_decl(ast)) {
zend_ast_decl *decl = (zend_ast_decl*)ast;
for (i = 0; i < 5; i++) {
Expand Down
9 changes: 7 additions & 2 deletions ext/opcache/zend_persist.c
Original file line number Diff line number Diff line change
Expand Up @@ -189,8 +189,13 @@ static zend_ast *zend_persist_ast(zend_ast *ast)
}
node = (zend_ast *) copy;
} else if (ast->kind == ZEND_AST_OP_ARRAY) {
zend_ast_zval *copy = zend_shared_memdup(ast, sizeof(zend_ast_zval));
zend_persist_op_array(&copy->val);
zend_ast_op_array *copy = zend_shared_memdup(ast, sizeof(zend_ast_op_array));
zval z;
ZVAL_PTR(&z, copy->op_array);
zend_persist_op_array(&z);
copy->op_array = Z_PTR(z);
copy->ast = zend_persist_ast(copy->ast);
node = (zend_ast *) copy;
} else if (zend_ast_is_decl(ast)) {
zend_ast_decl *copy = zend_shared_memdup(ast, sizeof(zend_ast_decl));
for (i = 0; i < 5; i++) {
Expand Down
7 changes: 5 additions & 2 deletions ext/opcache/zend_persist_calc.c
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,11 @@ static void zend_persist_ast_calc(zend_ast *ast)
}
}
} else if (ast->kind == ZEND_AST_OP_ARRAY) {
ADD_SIZE(sizeof(zend_ast_zval));
zend_persist_op_array_calc(&((zend_ast_zval*)(ast))->val);
ADD_SIZE(sizeof(zend_ast_op_array));
zval z;
ZVAL_PTR(&z, zend_ast_get_op_array(ast)->op_array);
zend_persist_op_array_calc(&z);
zend_persist_ast_calc(zend_ast_get_op_array(ast)->ast);
} else if (zend_ast_is_decl(ast)) {
zend_ast_decl *decl = (zend_ast_decl*)ast;
ADD_SIZE(sizeof(zend_ast_decl));
Expand Down
2 changes: 2 additions & 0 deletions main/debug_gdb_scripts.c
Original file line number Diff line number Diff line change
Expand Up @@ -860,6 +860,8 @@ asm(
".ascii \"\\n\"\n"
".ascii \" if kind == enum_value('ZEND_AST_ZVAL') or kind == enum_value('ZEND_AST_CONSTANT'):\\n\"\n"
".ascii \" return self.val.cast(gdb.lookup_type('zend_ast_zval'))\\n\"\n"
".ascii \" if kind == enum_value('ZEND_AST_OP_ARRAY'):\\n\"\n"
".ascii \" return self.val.cast(gdb.lookup_type('zend_ast_op_array'))\\n\"\n"
".ascii \" if kind == enum_value('ZEND_AST_ZNODE'):\\n\"\n"
".ascii \" return self.val.cast(gdb.lookup_type('zend_ast_znode'))\\n\"\n"
".ascii \" if self.is_decl():\\n\"\n"
Expand Down
2 changes: 2 additions & 0 deletions scripts/gdb/php_gdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,8 @@ def cast(self):

if kind == enum_value('ZEND_AST_ZVAL') or kind == enum_value('ZEND_AST_CONSTANT'):
return self.val.cast(gdb.lookup_type('zend_ast_zval'))
if kind == enum_value('ZEND_AST_OP_ARRAY'):
return self.val.cast(gdb.lookup_type('zend_ast_op_array'))
if kind == enum_value('ZEND_AST_ZNODE'):
return self.val.cast(gdb.lookup_type('zend_ast_znode'))
if self.is_decl():
Expand Down

0 comments on commit 05e868f

Please sign in to comment.