Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
--TEST--
First class callable error: more than one argument
--FILE--
<?php

foo(1, ...);

?>
--EXPECTF--
Fatal error: Cannot create a Closure for call expression with more than one argument, or non-variadic placeholders in %s on line %d
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
--TEST--
First class callable error: non-variadic placeholder
--FILE--
<?php

foo(?);

?>
--EXPECTF--
Fatal error: Cannot create a Closure for call expression with more than one argument, or non-variadic placeholders in %s on line %d
128 changes: 121 additions & 7 deletions Zend/zend_ast.c
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,14 @@ ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_znode(const znode *node) {
return (zend_ast *) ast;
}

ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_fcc(void) {
ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_fcc(zend_ast *args) {
zend_ast_fcc *ast;

ast = zend_ast_alloc(sizeof(zend_ast_fcc));
ast->kind = ZEND_AST_CALLABLE_CONVERT;
ast->attr = 0;
ast->lineno = CG(zend_lineno);
ast->args = args;
ZEND_MAP_PTR_INIT(ast->fptr, NULL);

return (zend_ast *) ast;
Expand Down Expand Up @@ -157,6 +158,12 @@ ZEND_API zend_ast *zend_ast_create_decl(
return (zend_ast *) ast;
}

static bool zend_ast_is_placeholder_arg(zend_ast *arg) {
return arg->kind == ZEND_AST_PLACEHOLDER_ARG
|| (arg->kind == ZEND_AST_NAMED_ARG
&& arg->child[1]->kind == ZEND_AST_PLACEHOLDER_ARG);
}

#if ZEND_AST_SPEC
ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_0(zend_ast_kind kind) {
zend_ast *ast;
Expand Down Expand Up @@ -400,6 +407,30 @@ ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_list_2(zend_ast_kind kind, zen

return ast;
}

ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_arg_list_0(zend_ast_kind kind) {
return zend_ast_create_list(0, kind);
}

ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_arg_list_1(zend_ast_kind kind, zend_ast *arg) {
zend_ast *list = zend_ast_create_list(1, kind, arg);

if (zend_ast_is_placeholder_arg(arg)) {
return zend_ast_create_fcc(list);
}

return list;
}

ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_arg_list_2(zend_ast_kind kind, zend_ast *arg1, zend_ast *arg2) {
zend_ast *list = zend_ast_create_list(2, kind, arg1, arg2);

if (zend_ast_is_placeholder_arg(arg1) || zend_ast_is_placeholder_arg(arg2)) {
return zend_ast_create_fcc(list);
}

return list;
}
#else
static zend_ast *zend_ast_create_from_va_list(zend_ast_kind kind, zend_ast_attr attr, va_list va) {
uint32_t i, children = kind >> ZEND_AST_NUM_CHILDREN_SHIFT;
Expand Down Expand Up @@ -479,6 +510,41 @@ ZEND_API zend_ast *zend_ast_create_list(uint32_t init_children, zend_ast_kind ki

return ast;
}

ZEND_API zend_ast *zend_ast_create_arg_list(uint32_t init_children, zend_ast_kind kind, ...) {
zend_ast *ast;
zend_ast_list *list;
bool has_placeholders = false;

ast = zend_ast_alloc(zend_ast_list_size(4));
list = (zend_ast_list *) ast;
list->kind = kind;
list->attr = 0;
list->lineno = CG(zend_lineno);
list->children = 0;

{
va_list va;
uint32_t i;
va_start(va, kind);
for (i = 0; i < init_children; ++i) {
zend_ast *child = va_arg(va, zend_ast *);
ast = zend_ast_list_add(ast, child);
uint32_t lineno = zend_ast_get_lineno(child);
if (lineno < ast->lineno) {
ast->lineno = lineno;
}
has_placeholders = has_placeholders || zend_ast_is_placeholder_arg(child);
}
va_end(va);
}

if (has_placeholders) {
return zend_ast_create_fcc(list);
}

return ast;
}
#endif

zend_ast *zend_ast_create_concat_op(zend_ast *op0, zend_ast *op1) {
Expand Down Expand Up @@ -508,6 +574,23 @@ ZEND_ATTRIBUTE_NODISCARD ZEND_API zend_ast * ZEND_FASTCALL zend_ast_list_add(zen
return (zend_ast *) list;
}

ZEND_API zend_ast * ZEND_FASTCALL zend_ast_arg_list_add(zend_ast *list, zend_ast *arg)
{
if (list->kind == ZEND_AST_CALLABLE_CONVERT) {
zend_ast_fcc *fcc_ast = (zend_ast_fcc*)list;
fcc_ast->args = zend_ast_list_add(fcc_ast->args, arg);
return (zend_ast*)fcc_ast;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if this is clearer (the fcc_ast doesn't actually change):

Suggested change
return (zend_ast*)fcc_ast;
return list;

}

ZEND_ASSERT(list->kind == ZEND_AST_ARG_LIST);

if (zend_ast_is_placeholder_arg(arg)) {
return zend_ast_create_fcc(zend_ast_list_add(list, arg));
}

return zend_ast_list_add(list, arg);
}

static zend_result zend_ast_add_array_element(const zval *result, zval *offset, zval *expr)
{
if (Z_TYPE_P(offset) == IS_UNDEF) {
Expand Down Expand Up @@ -1060,6 +1143,14 @@ static zend_result ZEND_FASTCALL zend_ast_evaluate_inner(
case ZEND_AST_CALL: {
ZEND_ASSERT(ast->child[1]->kind == ZEND_AST_CALLABLE_CONVERT);
zend_ast_fcc *fcc_ast = (zend_ast_fcc*)ast->child[1];

zend_ast_list *args = zend_ast_get_list(fcc_ast->args);
ZEND_ASSERT(args->children > 0);
if (args->children != 1 || args->child[0]->attr != _IS_PLACEHOLDER_VARIADIC) {
/* TODO: PFAs */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/* TODO: PFAs */
zend_throw_error(NULL, "Cannot create a Closure for call expression with more than one argument, or non-variadic placeholders");

return FAILURE;
}

fptr = ZEND_MAP_PTR_GET(fcc_ast->fptr);

if (!fptr) {
Expand Down Expand Up @@ -1087,6 +1178,13 @@ static zend_result ZEND_FASTCALL zend_ast_evaluate_inner(
ZEND_ASSERT(ast->child[2]->kind == ZEND_AST_CALLABLE_CONVERT);
zend_ast_fcc *fcc_ast = (zend_ast_fcc*)ast->child[2];

zend_ast_list *args = zend_ast_get_list(fcc_ast->args);
ZEND_ASSERT(args->children > 0);
if (args->children != 1 || args->child[0]->attr != _IS_PLACEHOLDER_VARIADIC) {
/* TODO: PFAs */
return FAILURE;
Comment on lines +1184 to +1185
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe the expectation for return FAILURE; is that there also is an Exception active:

Suggested change
/* TODO: PFAs */
return FAILURE;
zend_throw_error(NULL, "Cannot create a Closure for call expression with more than one argument, or non-variadic placeholders");
return FAILURE;

}

zend_class_entry *ce = zend_ast_fetch_class(ast->child[0], scope);
if (!ce) {
return FAILURE;
Expand Down Expand Up @@ -1124,12 +1222,12 @@ static zend_result ZEND_FASTCALL zend_ast_evaluate_inner(

if (!(fptr->common.fn_flags & ZEND_ACC_STATIC)) {
zend_non_static_method_call(fptr);

Comment on lines -1127 to +1225
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whoops. Sorry for introducing these. Can you push the a commit to trim the trailing spaces straight to master to denoise the diff?

return FAILURE;
}
if ((fptr->common.fn_flags & ZEND_ACC_ABSTRACT)) {
zend_abstract_method_call(fptr);

return FAILURE;
} else if (fptr->common.scope->ce_flags & ZEND_ACC_TRAIT) {
zend_error(E_DEPRECATED,
Expand Down Expand Up @@ -1243,7 +1341,8 @@ static size_t ZEND_FASTCALL zend_ast_tree_size(zend_ast *ast)
} else if (ast->kind == ZEND_AST_OP_ARRAY) {
size = sizeof(zend_ast_op_array);
} else if (ast->kind == ZEND_AST_CALLABLE_CONVERT) {
size = sizeof(zend_ast_fcc);
zend_ast *args_ast = ((zend_ast_fcc*)ast)->args;
size = sizeof(zend_ast_fcc) + zend_ast_tree_size(args_ast);
} else if (zend_ast_is_list(ast)) {
uint32_t i;
const zend_ast_list *list = zend_ast_get_list(ast);
Expand Down Expand Up @@ -1320,6 +1419,8 @@ static void* ZEND_FASTCALL zend_ast_tree_copy(zend_ast *ast, void *buf)
new->lineno = old->lineno;
ZEND_MAP_PTR_INIT(new->fptr, ZEND_MAP_PTR(old->fptr));
buf = (void*)((char*)buf + sizeof(zend_ast_fcc));
new->args = buf;
buf = zend_ast_tree_copy(old->args, buf);
} else if (zend_ast_is_decl(ast)) {
/* Not implemented. */
ZEND_UNREACHABLE();
Expand Down Expand Up @@ -1403,6 +1504,10 @@ ZEND_API void ZEND_FASTCALL zend_ast_destroy(zend_ast *ast)
zend_ast_destroy(decl->child[3]);
ast = decl->child[4];
goto tail_call;
} else if (EXPECTED(ast->kind == ZEND_AST_CALLABLE_CONVERT)) {
zend_ast_fcc *fcc_ast = (zend_ast_fcc*) ast;

zend_ast_destroy(fcc_ast->args);
Comment on lines +1508 to +1510
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
zend_ast_fcc *fcc_ast = (zend_ast_fcc*) ast;
zend_ast_destroy(fcc_ast->args);
zend_ast_fcc *fcc_ast = (zend_ast_fcc*) ast;
ast = fcc_ast->args;
goto tail_call;

?

}
}

Expand Down Expand Up @@ -2299,6 +2404,13 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio
EMPTY_SWITCH_DEFAULT_CASE();
}
break;
case ZEND_AST_PLACEHOLDER_ARG:
if (ast->attr == _IS_PLACEHOLDER_ARG) {
APPEND_STR("?");
} else if (ast->attr == _IS_PLACEHOLDER_VARIADIC) {
APPEND_STR("...");
}
Comment on lines +2408 to +2412
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (ast->attr == _IS_PLACEHOLDER_ARG) {
APPEND_STR("?");
} else if (ast->attr == _IS_PLACEHOLDER_VARIADIC) {
APPEND_STR("...");
}
if (ast->attr == _IS_PLACEHOLDER_ARG) {
APPEND_STR("?");
} else if (ast->attr == _IS_PLACEHOLDER_VARIADIC) {
APPEND_STR("...");
} else {
ZEND_UNREACHABLE();
}

break;

/* 1 child node */
case ZEND_AST_VAR:
Expand Down Expand Up @@ -2445,9 +2557,11 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio
zend_ast_export_ex(str, ast->child[1], 0, indent);
smart_str_appendc(str, ')');
break;
case ZEND_AST_CALLABLE_CONVERT:
smart_str_appends(str, "...");
break;
case ZEND_AST_CALLABLE_CONVERT: {
zend_ast_fcc *fcc_ast = (zend_ast_fcc*)ast;
ast = fcc_ast->args;
goto simple_list;
}
case ZEND_AST_CLASS_CONST:
zend_ast_export_ns_name(str, ast->child[0], 0, indent);
smart_str_appends(str, "::");
Expand Down
17 changes: 16 additions & 1 deletion Zend/zend_ast.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ enum _zend_ast_kind {
ZEND_AST_TYPE,
ZEND_AST_CONSTANT_CLASS,
ZEND_AST_CALLABLE_CONVERT,
ZEND_AST_PLACEHOLDER_ARG,

/* 1 child node */
ZEND_AST_VAR = 1 << ZEND_AST_NUM_CHILDREN_SHIFT,
Expand Down Expand Up @@ -229,10 +230,12 @@ typedef struct _zend_ast_decl {
zend_ast *child[5];
} zend_ast_decl;

// TODO: rename
typedef struct _zend_ast_fcc {
zend_ast_kind kind; /* Type of the node (ZEND_AST_* enum constant) */
zend_ast_attr attr; /* Additional attribute, use depending on node type */
uint32_t lineno; /* Line number */
zend_ast *args;
ZEND_MAP_PTR_DEF(zend_function *, fptr);
} zend_ast_fcc;

Expand Down Expand Up @@ -307,27 +310,39 @@ ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_list_0(zend_ast_kind kind);
ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_list_1(zend_ast_kind kind, zend_ast *child);
ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_list_2(zend_ast_kind kind, zend_ast *child1, zend_ast *child2);

ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_arg_list_0(zend_ast_kind kind);
ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_arg_list_1(zend_ast_kind kind, zend_ast *child);
ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_arg_list_2(zend_ast_kind kind, zend_ast *child1, zend_ast *child2);

# define zend_ast_create(...) \
ZEND_AST_SPEC_CALL(zend_ast_create, __VA_ARGS__)
# define zend_ast_create_ex(...) \
ZEND_AST_SPEC_CALL_EX(zend_ast_create_ex, __VA_ARGS__)
# define zend_ast_create_list(init_children, ...) \
ZEND_AST_SPEC_CALL(zend_ast_create_list, __VA_ARGS__)
# define zend_ast_create_arg_list(init_children, ...) \
ZEND_AST_SPEC_CALL(zend_ast_create_arg_list, __VA_ARGS__)

#else
ZEND_API zend_ast *zend_ast_create(zend_ast_kind kind, ...);
ZEND_API zend_ast *zend_ast_create_ex(zend_ast_kind kind, zend_ast_attr attr, ...);
ZEND_API zend_ast *zend_ast_create_list(uint32_t init_children, zend_ast_kind kind, ...);
ZEND_API zend_ast *zend_ast_create_arg_list(uint32_t init_children, zend_ast_kind kind, ...);
#endif

ZEND_ATTRIBUTE_NODISCARD ZEND_API zend_ast * ZEND_FASTCALL zend_ast_list_add(zend_ast *list, zend_ast *op);

/* Like zend_ast_list_add(), but wraps the list into a ZEND_AST_CALLABLE_CONVERT
* if any arg is a ZEND_AST_PLACEHOLDER_ARG. list can be a zend_ast_list, or a
* zend_ast_fcc. */
ZEND_API zend_ast * ZEND_FASTCALL zend_ast_arg_list_add(zend_ast *list, zend_ast *arg);

ZEND_API zend_ast *zend_ast_create_decl(
zend_ast_kind kind, uint32_t flags, uint32_t start_lineno, zend_string *doc_comment,
zend_string *name, zend_ast *child0, zend_ast *child1, zend_ast *child2, zend_ast *child3, zend_ast *child4
);

ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_fcc(void);
ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_fcc(zend_ast *args);

typedef struct {
bool had_side_effects;
Expand Down
5 changes: 5 additions & 0 deletions Zend/zend_compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -3950,6 +3950,11 @@ static bool zend_compile_call_common(znode *result, zend_ast *args_ast, const ze
zend_error_noreturn(E_COMPILE_ERROR, "Cannot create Closure for new expression");
}

zend_ast_list *args = zend_ast_get_list(((zend_ast_fcc*)args_ast)->args);
if (args->children != 1 || args->child[0]->attr != _IS_PLACEHOLDER_VARIADIC) {
zend_error_noreturn(E_COMPILE_ERROR, "Cannot create a Closure for call expression with more than one argument, or non-variadic placeholders");
}

if (opcode == ZEND_INIT_FCALL) {
opline->op1.num = zend_vm_calc_used_stack(0, fbc);
}
Expand Down
27 changes: 16 additions & 11 deletions Zend/zend_language_parser.y
Original file line number Diff line number Diff line change
Expand Up @@ -901,16 +901,15 @@ return_type:
;

argument_list:
'(' ')' { $$ = zend_ast_create_list(0, ZEND_AST_ARG_LIST); }
'(' ')' { $$ = zend_ast_create_arg_list(0, ZEND_AST_ARG_LIST); }
| '(' non_empty_argument_list possible_comma ')' { $$ = $2; }
| '(' T_ELLIPSIS ')' { $$ = zend_ast_create_fcc(); }
;

non_empty_argument_list:
argument
{ $$ = zend_ast_create_list(1, ZEND_AST_ARG_LIST, $1); }
{ $$ = zend_ast_create_arg_list(1, ZEND_AST_ARG_LIST, $1); }
| non_empty_argument_list ',' argument
{ $$ = zend_ast_list_add($1, $3); }
{ $$ = zend_ast_arg_list_add($1, $3); }
;

/* `clone_argument_list` is necessary to resolve a parser ambiguity (shift-reduce conflict)
Expand All @@ -923,25 +922,31 @@ non_empty_argument_list:
* syntax.
*/
clone_argument_list:
'(' ')' { $$ = zend_ast_create_list(0, ZEND_AST_ARG_LIST); }
'(' ')' { $$ = zend_ast_create_arg_list(0, ZEND_AST_ARG_LIST); }
| '(' non_empty_clone_argument_list possible_comma ')' { $$ = $2; }
| '(' expr ',' ')' { $$ = zend_ast_create_list(1, ZEND_AST_ARG_LIST, $2); }
| '(' T_ELLIPSIS ')' { $$ = zend_ast_create_fcc(); }
| '(' expr ',' ')' { $$ = zend_ast_create_arg_list(1, ZEND_AST_ARG_LIST, $2); }
;

non_empty_clone_argument_list:
expr ',' argument
{ $$ = zend_ast_create_list(2, ZEND_AST_ARG_LIST, $1, $3); }
{ $$ = zend_ast_create_arg_list(2, ZEND_AST_ARG_LIST, $1, $3); }
| argument_no_expr
{ $$ = zend_ast_create_list(1, ZEND_AST_ARG_LIST, $1); }
{ $$ = zend_ast_create_arg_list(1, ZEND_AST_ARG_LIST, $1); }
| non_empty_clone_argument_list ',' argument
{ $$ = zend_ast_list_add($1, $3); }
{ $$ = zend_ast_arg_list_add($1, $3); }
;

argument_no_expr:
identifier ':' expr
{ $$ = zend_ast_create(ZEND_AST_NAMED_ARG, $1, $3); }
| T_ELLIPSIS expr { $$ = zend_ast_create(ZEND_AST_UNPACK, $2); }
| T_ELLIPSIS
{ $$ = zend_ast_create_ex(ZEND_AST_PLACEHOLDER_ARG, _IS_PLACEHOLDER_VARIADIC); }
| '?'
{ $$ = zend_ast_create_ex(ZEND_AST_PLACEHOLDER_ARG, _IS_PLACEHOLDER_ARG); }
| identifier ':' '?'
{ $$ = zend_ast_create(ZEND_AST_NAMED_ARG, $1, zend_ast_create_ex(ZEND_AST_PLACEHOLDER_ARG, _IS_PLACEHOLDER_ARG)); }
| T_ELLIPSIS expr
{ $$ = zend_ast_create(ZEND_AST_UNPACK, $2); }
;

argument:
Expand Down
4 changes: 4 additions & 0 deletions Zend/zend_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -638,6 +638,10 @@ struct _zend_ast_ref {
#define _IS_BOOL 18
#define _IS_NUMBER 19

/* used for PFAs/FCCs */
#define _IS_PLACEHOLDER_ARG 20
#define _IS_PLACEHOLDER_VARIADIC 21
Comment on lines +641 to +643
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these required to be a zval type for the following implementation? Other AST attributes use a “distinct” set of values.


/* guard flags */
#define ZEND_GUARD_PROPERTY_GET (1<<0)
#define ZEND_GUARD_PROPERTY_SET (1<<1)
Expand Down
Loading