diff --git a/Zend/tests/closure_const_expr/fcc/attributes.phpt b/Zend/tests/closure_const_expr/fcc/attributes.phpt new file mode 100644 index 0000000000000..b011a7f678137 --- /dev/null +++ b/Zend/tests/closure_const_expr/fcc/attributes.phpt @@ -0,0 +1,50 @@ +--TEST-- +Allow defining FCC in attributes +--EXTENSIONS-- +reflection +--FILE-- +getAttributes() as $reflectionAttribute) { + var_dump($reflectionAttribute->newInstance()); +} + +?> +--EXPECTF-- +string(3) "cba" +object(Attr)#%d (1) { + ["value"]=> + object(Closure)#%d (2) { + ["function"]=> + string(6) "strrev" + ["parameter"]=> + array(1) { + ["$string"]=> + string(10) "" + } + } +} +int(3) +object(Attr)#%d (1) { + ["value"]=> + object(Closure)#%d (2) { + ["function"]=> + string(6) "strlen" + ["parameter"]=> + array(1) { + ["$string"]=> + string(10) "" + } + } +} diff --git a/Zend/tests/closure_const_expr/fcc/attributes_ast_printing.phpt b/Zend/tests/closure_const_expr/fcc/attributes_ast_printing.phpt new file mode 100644 index 0000000000000..2916c3dfff86d --- /dev/null +++ b/Zend/tests/closure_const_expr/fcc/attributes_ast_printing.phpt @@ -0,0 +1,33 @@ +--TEST-- +AST printing for FCC in attributes +--FILE-- +getMessage(), "\n"; +} + +try { + \assert( + ! + new #[Attr(strrev(...))] + class {} + ); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +assert(!#[Attr(strrev(...))] function () { +}) +assert(!new #[Attr(strrev(...))] class { +}) diff --git a/Zend/tests/closure_const_expr/fcc/attributes_scope_001.phpt b/Zend/tests/closure_const_expr/fcc/attributes_scope_001.phpt new file mode 100644 index 0000000000000..6e75004624191 --- /dev/null +++ b/Zend/tests/closure_const_expr/fcc/attributes_scope_001.phpt @@ -0,0 +1,28 @@ +--TEST-- +FCC in attribute may access private methods +--EXTENSIONS-- +reflection +--FILE-- +getAttributes() as $reflectionAttribute) { + ($reflectionAttribute->newInstance()->value)('abc'); +} + +?> +--EXPECT-- +Called C::myMethod +string(3) "abc" diff --git a/Zend/tests/closure_const_expr/fcc/attributes_scope_002.phpt b/Zend/tests/closure_const_expr/fcc/attributes_scope_002.phpt new file mode 100644 index 0000000000000..f93240c5fac8e --- /dev/null +++ b/Zend/tests/closure_const_expr/fcc/attributes_scope_002.phpt @@ -0,0 +1,34 @@ +--TEST-- +FCC in attribute may not access unrelated private methods +--EXTENSIONS-- +reflection +--FILE-- +getAttributes() as $reflectionAttribute) { + ($reflectionAttribute->newInstance()->value)('abc'); +} + +?> +--EXPECTF-- +Fatal error: Uncaught Error: Call to private method E::myMethod() from scope C in %s:%d +Stack trace: +#0 %s(%d): ReflectionAttribute->newInstance() +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/closure_const_expr/fcc/autoload.phpt b/Zend/tests/closure_const_expr/fcc/autoload.phpt new file mode 100644 index 0000000000000..248cdf14e407b --- /dev/null +++ b/Zend/tests/closure_const_expr/fcc/autoload.phpt @@ -0,0 +1,31 @@ +--TEST-- +Errors if the function does not exist. +--FILE-- + +--EXPECTF-- +Autoloading AutoloadedClass +object(Closure)#%d (1) { + ["function"]=> + string(16) "withStaticMethod" +} +Called AutoloadedClass::withStaticMethod diff --git a/Zend/tests/closure_const_expr/fcc/basic.phpt b/Zend/tests/closure_const_expr/fcc/basic.phpt new file mode 100644 index 0000000000000..ce60cba1aa84f --- /dev/null +++ b/Zend/tests/closure_const_expr/fcc/basic.phpt @@ -0,0 +1,22 @@ +--TEST-- +Allow defining FCC in const expressions. +--FILE-- + +--EXPECTF-- +object(Closure)#%d (2) { + ["function"]=> + string(%d) "%s" + ["parameter"]=> + array(1) { + ["$string"]=> + string(10) "" + } +} +string(3) "cba" diff --git a/Zend/tests/closure_const_expr/fcc/class_const.phpt b/Zend/tests/closure_const_expr/fcc/class_const.phpt new file mode 100644 index 0000000000000..ab0ea29d50369 --- /dev/null +++ b/Zend/tests/closure_const_expr/fcc/class_const.phpt @@ -0,0 +1,24 @@ +--TEST-- +Allow defining FCC in class constants. +--FILE-- + +--EXPECTF-- +object(Closure)#%d (2) { + ["function"]=> + string(6) "strrev" + ["parameter"]=> + array(1) { + ["$string"]=> + string(10) "" + } +} +string(3) "cba" diff --git a/Zend/tests/closure_const_expr/fcc/complex_array.phpt b/Zend/tests/closure_const_expr/fcc/complex_array.phpt new file mode 100644 index 0000000000000..388d09d056a42 --- /dev/null +++ b/Zend/tests/closure_const_expr/fcc/complex_array.phpt @@ -0,0 +1,39 @@ +--TEST-- +Allow defining FCC wrapped in an array in const expressions. +--FILE-- + +--EXPECTF-- +array(2) { + [0]=> + object(Closure)#%d (2) { + ["function"]=> + string(6) "strrev" + ["parameter"]=> + array(1) { + ["$string"]=> + string(10) "" + } + } + [1]=> + object(Closure)#%d (2) { + ["function"]=> + string(6) "strlen" + ["parameter"]=> + array(1) { + ["$string"]=> + string(10) "" + } + } +} +string(3) "cba" +int(3) diff --git a/Zend/tests/closure_const_expr/fcc/default_args.phpt b/Zend/tests/closure_const_expr/fcc/default_args.phpt new file mode 100644 index 0000000000000..a35efc8961c17 --- /dev/null +++ b/Zend/tests/closure_const_expr/fcc/default_args.phpt @@ -0,0 +1,18 @@ +--TEST-- +FCC in default argument +--FILE-- + +--EXPECT-- +string(3) "cba" +int(3) diff --git a/Zend/tests/closure_const_expr/fcc/error_dynamic_001.phpt b/Zend/tests/closure_const_expr/fcc/error_dynamic_001.phpt new file mode 100644 index 0000000000000..44821cf6e60ad --- /dev/null +++ b/Zend/tests/closure_const_expr/fcc/error_dynamic_001.phpt @@ -0,0 +1,12 @@ +--TEST-- +FCC in initializer errors for FCC on variable. +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use dynamic function name in constant expression in %s on line %d diff --git a/Zend/tests/closure_const_expr/fcc/error_dynamic_002.phpt b/Zend/tests/closure_const_expr/fcc/error_dynamic_002.phpt new file mode 100644 index 0000000000000..e373ff51620d5 --- /dev/null +++ b/Zend/tests/closure_const_expr/fcc/error_dynamic_002.phpt @@ -0,0 +1,12 @@ +--TEST-- +FCC in initializer errors for FCC on Closure. +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use dynamic function name in constant expression in %s on line %d diff --git a/Zend/tests/closure_const_expr/fcc/error_dynamic_003.phpt b/Zend/tests/closure_const_expr/fcc/error_dynamic_003.phpt new file mode 100644 index 0000000000000..41090d89556f3 --- /dev/null +++ b/Zend/tests/closure_const_expr/fcc/error_dynamic_003.phpt @@ -0,0 +1,14 @@ +--TEST-- +FCC in initializer errors for FCC on Constant. +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use dynamic function name in constant expression in %s on line %d diff --git a/Zend/tests/closure_const_expr/fcc/error_instance_call.phpt b/Zend/tests/closure_const_expr/fcc/error_instance_call.phpt new file mode 100644 index 0000000000000..cfee7c40b7844 --- /dev/null +++ b/Zend/tests/closure_const_expr/fcc/error_instance_call.phpt @@ -0,0 +1,20 @@ +--TEST-- +FCC in initializer errors for FCC on instance call. +--FILE-- +myMethod(...); + +var_dump(Closure); +(Closure)("abc"); + +?> +--EXPECTF-- +Fatal error: Constant expression contains invalid operations in %s on line %d diff --git a/Zend/tests/closure_const_expr/fcc/error_unknown_class.phpt b/Zend/tests/closure_const_expr/fcc/error_unknown_class.phpt new file mode 100644 index 0000000000000..39ce1736ddecd --- /dev/null +++ b/Zend/tests/closure_const_expr/fcc/error_unknown_class.phpt @@ -0,0 +1,15 @@ +--TEST-- +FCC in initializer errors for missing class. +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught Error: Class "ThisClassNotDoesExist" not found in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/Zend/tests/closure_const_expr/fcc/error_unknown_function.phpt b/Zend/tests/closure_const_expr/fcc/error_unknown_function.phpt new file mode 100644 index 0000000000000..38b291d9907ad --- /dev/null +++ b/Zend/tests/closure_const_expr/fcc/error_unknown_function.phpt @@ -0,0 +1,15 @@ +--TEST-- +FCC in initializer errors for missing function. +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught Error: Call to undefined function this_function_does_not_exist() in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/Zend/tests/closure_const_expr/fcc/error_unknown_method.phpt b/Zend/tests/closure_const_expr/fcc/error_unknown_method.phpt new file mode 100644 index 0000000000000..345d67ee74dae --- /dev/null +++ b/Zend/tests/closure_const_expr/fcc/error_unknown_method.phpt @@ -0,0 +1,17 @@ +--TEST-- +FCC in initializer errors for missing method. +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught Error: Call to undefined method ThisClassDoesExist::thisMethodDoesNotExist() in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/Zend/tests/closure_const_expr/fcc/property_initializer.phpt b/Zend/tests/closure_const_expr/fcc/property_initializer.phpt new file mode 100644 index 0000000000000..53cae06216bd1 --- /dev/null +++ b/Zend/tests/closure_const_expr/fcc/property_initializer.phpt @@ -0,0 +1,26 @@ +--TEST-- +FCC in property initializer +--FILE-- +d); +var_dump(($c->d)("abc")); + + +?> +--EXPECTF-- +object(Closure)#%d (2) { + ["function"]=> + string(6) "strrev" + ["parameter"]=> + array(1) { + ["$string"]=> + string(10) "" + } +} +string(3) "cba" diff --git a/Zend/tests/closure_const_expr/fcc/property_initializer_scope_001.phpt b/Zend/tests/closure_const_expr/fcc/property_initializer_scope_001.phpt new file mode 100644 index 0000000000000..37820835428f1 --- /dev/null +++ b/Zend/tests/closure_const_expr/fcc/property_initializer_scope_001.phpt @@ -0,0 +1,32 @@ +--TEST-- +Closure in property initializer may access private methods. +--FILE-- +d); +var_dump(($c->d)("abc")); + +?> +--EXPECTF-- +object(Closure)#%d (2) { + ["function"]=> + string(11) "C::myMethod" + ["parameter"]=> + array(1) { + ["$foo"]=> + string(10) "" + } +} +Called C::myMethod +string(3) "abc" +NULL diff --git a/Zend/tests/closure_const_expr/fcc/property_initializer_scope_002.phpt b/Zend/tests/closure_const_expr/fcc/property_initializer_scope_002.phpt new file mode 100644 index 0000000000000..a86b751a4a9fb --- /dev/null +++ b/Zend/tests/closure_const_expr/fcc/property_initializer_scope_002.phpt @@ -0,0 +1,27 @@ +--TEST-- +Closure in property initializer may not access unrelated private methods. +--FILE-- +d); +var_dump(($c->d)("abc")); + +?> +--EXPECTF-- +Fatal error: Uncaught Error: Call to private method E::myMethod() from scope C in %s:%d +Stack trace: +#0 %s(%d): [constant expression]() +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/closure_const_expr/fcc/static_call.phpt b/Zend/tests/closure_const_expr/fcc/static_call.phpt new file mode 100644 index 0000000000000..eacdcf280a4a8 --- /dev/null +++ b/Zend/tests/closure_const_expr/fcc/static_call.phpt @@ -0,0 +1,30 @@ +--TEST-- +Allow defining FCC for userland functions in const expressions. +--FILE-- + +--EXPECTF-- +object(Closure)#%d (2) { + ["function"]=> + string(8) "myMethod" + ["parameter"]=> + array(1) { + ["$foo"]=> + string(10) "" + } +} +Called Foo::myMethod +string(3) "abc" diff --git a/Zend/tests/closure_const_expr/fcc/static_property_initializer.phpt b/Zend/tests/closure_const_expr/fcc/static_property_initializer.phpt new file mode 100644 index 0000000000000..d46b88aae9be0 --- /dev/null +++ b/Zend/tests/closure_const_expr/fcc/static_property_initializer.phpt @@ -0,0 +1,25 @@ +--TEST-- +FCC in static property initializer +--FILE-- + +--EXPECTF-- +object(Closure)#%d (2) { + ["function"]=> + string(6) "strrev" + ["parameter"]=> + array(1) { + ["$string"]=> + string(10) "" + } +} +string(3) "cba" diff --git a/Zend/tests/closure_const_expr/fcc/userland.phpt b/Zend/tests/closure_const_expr/fcc/userland.phpt new file mode 100644 index 0000000000000..6ead1e51ff31c --- /dev/null +++ b/Zend/tests/closure_const_expr/fcc/userland.phpt @@ -0,0 +1,28 @@ +--TEST-- +Allow defining FCC for userland functions in const expressions. +--FILE-- + +--EXPECTF-- +object(Closure)#%d (2) { + ["function"]=> + string(11) "my_function" + ["parameter"]=> + array(1) { + ["$foo"]=> + string(10) "" + } +} +Called my_function +string(3) "abc" diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index 95a67a95b7dde..38879bee7f3f5 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -990,6 +990,58 @@ ZEND_API zend_result ZEND_FASTCALL zend_ast_evaluate_inner( } return SUCCESS; } + case ZEND_AST_CALL: + case ZEND_AST_STATIC_CALL: + { + zend_function *fptr; + switch (ast->kind) { + case ZEND_AST_CALL: { + ZEND_ASSERT(ast->child[1]->kind == ZEND_AST_CALLABLE_CONVERT); + + zend_string *function_name = zend_ast_get_str(ast->child[0]); + fptr = zend_fetch_function(function_name); + if (!fptr) { + zend_throw_error(NULL, "Call to undefined function %s()", ZSTR_VAL(function_name)); + return FAILURE; + } + break; + } + case ZEND_AST_STATIC_CALL: { + ZEND_ASSERT(ast->child[2]->kind == ZEND_AST_CALLABLE_CONVERT); + + zend_string *class_name = zend_ast_get_str(ast->child[0]); + zend_class_entry *ce = zend_fetch_class_with_scope(class_name, (ast->attr >> ZEND_CONST_EXPR_NEW_FETCH_TYPE_SHIFT) | ZEND_FETCH_CLASS_EXCEPTION, scope); + if (!ce) { + return FAILURE; + } + zend_string *method_name = zend_ast_get_str(ast->child[1]); + fptr = zend_hash_find_ptr_lc(&ce->function_table, method_name); + if (!fptr) { + zend_throw_error(NULL, "Call to undefined method %s::%s()", ZSTR_VAL(ce->name), ZSTR_VAL(method_name)); + return FAILURE; + } + if (!(fptr->common.fn_flags & ZEND_ACC_PUBLIC)) { + if (UNEXPECTED(fptr->common.scope != scope)) { + if ( + UNEXPECTED(fptr->op_array.fn_flags & ZEND_ACC_PRIVATE) + || UNEXPECTED(!zend_check_protected(zend_get_function_root_class(fptr), scope)) + ) { + zend_throw_error(NULL, "Call to %s method %s::%s() from %s%s", + zend_visibility_string(fptr->common.fn_flags), ZEND_FN_SCOPE_NAME(fptr), ZSTR_VAL(method_name), + scope ? "scope " : "global scope", + scope ? ZSTR_VAL(scope->name) : "" + ); + } + } + } + break; + } + } + + zend_create_fake_closure(result, fptr, scope, scope, NULL); + + return SUCCESS; + } case ZEND_AST_OP_ARRAY: { zend_function *func = Z_PTR_P(&((zend_ast_zval*)(ast))->val); diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 9533ef8dfaa44..ed4475240f790 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -11072,7 +11072,8 @@ static bool zend_is_allowed_in_const_expr(zend_ast_kind kind) /* {{{ */ || kind == ZEND_AST_NEW || kind == ZEND_AST_ARG_LIST || kind == ZEND_AST_NAMED_ARG || kind == ZEND_AST_PROP || kind == ZEND_AST_NULLSAFE_PROP - || kind == ZEND_AST_CLOSURE; + || kind == ZEND_AST_CLOSURE + || kind == ZEND_AST_CALL || kind == ZEND_AST_STATIC_CALL || kind == ZEND_AST_CALLABLE_CONVERT; } /* }}} */ @@ -11229,6 +11230,45 @@ static void zend_compile_const_expr_closure(zend_ast **ast_ptr) (*ast_ptr)->kind = ZEND_AST_OP_ARRAY; } +static void zend_compile_const_expr_fcc(zend_ast **ast_ptr) +{ + zend_ast *args_ast; + switch ((*ast_ptr)->kind) { + case ZEND_AST_CALL: + args_ast = (*ast_ptr)->child[1]; + break; + case ZEND_AST_STATIC_CALL: + args_ast = (*ast_ptr)->child[2]; + break; + EMPTY_SWITCH_DEFAULT_CASE(); + } + if (args_ast->kind != ZEND_AST_CALLABLE_CONVERT) { + zend_error_noreturn(E_COMPILE_ERROR, "Constant expression contains invalid operations"); + } + + switch ((*ast_ptr)->kind) { + case ZEND_AST_CALL: { + zend_ast *name_ast = (*ast_ptr)->child[0]; + if (name_ast->kind != ZEND_AST_ZVAL || Z_TYPE_P(zend_ast_get_zval(name_ast)) != IS_STRING) { + zend_error_noreturn(E_COMPILE_ERROR, "Cannot use dynamic function name in constant expression"); + } + break; + } + case ZEND_AST_STATIC_CALL: { + zend_ast *class_ast = (*ast_ptr)->child[0]; + if (class_ast->kind != ZEND_AST_ZVAL || Z_TYPE_P(zend_ast_get_zval(class_ast)) != IS_STRING) { + zend_error_noreturn(E_COMPILE_ERROR, "Cannot use dynamic class name in constant expression"); + } + zend_ast *method_ast = (*ast_ptr)->child[1]; + if (method_ast->kind != ZEND_AST_ZVAL || Z_TYPE_P(zend_ast_get_zval(method_ast)) != IS_STRING) { + zend_error_noreturn(E_COMPILE_ERROR, "Cannot use dynamic method name in constant expression"); + } + break; + } + EMPTY_SWITCH_DEFAULT_CASE(); + } +} + static void zend_compile_const_expr_args(zend_ast **ast_ptr) { zend_ast_list *list = zend_ast_get_list(*ast_ptr); @@ -11294,6 +11334,10 @@ static void zend_compile_const_expr(zend_ast **ast_ptr, void *context) /* {{{ */ case ZEND_AST_CLOSURE: zend_compile_const_expr_closure(ast_ptr); break; + case ZEND_AST_CALL: + case ZEND_AST_STATIC_CALL: + zend_compile_const_expr_fcc(ast_ptr); + break; } zend_ast_apply(ast, zend_compile_const_expr, context);