diff --git a/Zend/tests/context_managers/basic.phpt b/Zend/tests/context_managers/basic.phpt new file mode 100644 index 000000000000..6061d9bbb53c --- /dev/null +++ b/Zend/tests/context_managers/basic.phpt @@ -0,0 +1,23 @@ +--TEST-- +Context manager basic +--FILE-- + $var) { + echo "Body is executed once\n"; + echo "\$var: "; + var_dump($var); +}; + +echo "\$var is unset() after the block: "; +var_dump(!isset($var)); + +?> +--EXPECT-- +Body is executed once +$var: string(5) "hello" +$var is unset() after the block: bool(true) diff --git a/Zend/tests/context_managers/basic_manager.inc b/Zend/tests/context_managers/basic_manager.inc new file mode 100644 index 000000000000..fdf4a694a1f5 --- /dev/null +++ b/Zend/tests/context_managers/basic_manager.inc @@ -0,0 +1,22 @@ +getMessage() . ')' : 'null'), ")\n"; + return !$this->rethrow; + } +} + +?> diff --git a/Zend/tests/context_managers/error_must_be_a_generator.phpt b/Zend/tests/context_managers/error_must_be_a_generator.phpt new file mode 100644 index 000000000000..36a3f78ad241 --- /dev/null +++ b/Zend/tests/context_managers/error_must_be_a_generator.phpt @@ -0,0 +1,16 @@ +--TEST-- +Context manager: manager must be a Generator +--FILE-- +getMessage(), "\n"; +} + +?> +--EXPECT-- +TypeError: Context Manager must be an instance of Generator (TODO: proper error message) diff --git a/Zend/tests/context_managers/error_must_not_yield_twice.phpt b/Zend/tests/context_managers/error_must_not_yield_twice.phpt new file mode 100644 index 000000000000..3698f9706fac --- /dev/null +++ b/Zend/tests/context_managers/error_must_not_yield_twice.phpt @@ -0,0 +1,22 @@ +--TEST-- +Context manager: manager must not yield twice +--FILE-- + $var) { + echo "Body executed\n"; + }; +} catch (Error $e) { + echo $e::class, ": ", $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +Body executed +Error: Context Manager must yield only once, but it yielded a second time when fetching the return value diff --git a/Zend/tests/context_managers/error_must_yield.phpt b/Zend/tests/context_managers/error_must_yield.phpt new file mode 100644 index 000000000000..23430bbcec51 --- /dev/null +++ b/Zend/tests/context_managers/error_must_yield.phpt @@ -0,0 +1,22 @@ +--TEST-- +Context manager: manager must yield once +--FILE-- + $var) { + echo "Not executed\n"; + }; +} catch (Error $e) { + echo $e::class, ": ", $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +Error: Context Manager must yield once, but it returned without yielding diff --git a/Zend/tests/context_managers/evaluates_to_manager_return_value.phpt b/Zend/tests/context_managers/evaluates_to_manager_return_value.phpt new file mode 100644 index 000000000000..dfadd1e962b2 --- /dev/null +++ b/Zend/tests/context_managers/evaluates_to_manager_return_value.phpt @@ -0,0 +1,22 @@ +--TEST-- +Context manager: manager must not yield twice +--FILE-- + $var) { + echo "Body executed: "; + var_dump($var); +}; + +echo "Result: "; +var_dump($result); + +?> +--EXPECT-- +Body executed: string(5) "hello" +Result: string(5) "world" diff --git a/Zend/tests/context_managers/exception_can_be_caught_by_manager.phpt b/Zend/tests/context_managers/exception_can_be_caught_by_manager.phpt new file mode 100644 index 000000000000..8513a6687bf8 --- /dev/null +++ b/Zend/tests/context_managers/exception_can_be_caught_by_manager.phpt @@ -0,0 +1,24 @@ +--TEST-- +Context manager: exception is forwarded to manager +--FILE-- +getMessage(), "\n"; + } +} + +using (some_context() => $var) { + throw new Exception('Thrown from body'); +}; + +echo "\$var is unset() after the block: "; +var_dump(!isset($var)); + +?> +--EXPECT-- +From context manager: Exception: Thrown from body +$var is unset() after the block: bool(true) diff --git a/Zend/tests/context_managers/exception_is_forwarded_to_manager.phpt b/Zend/tests/context_managers/exception_is_forwarded_to_manager.phpt new file mode 100644 index 000000000000..b043a3f9c42f --- /dev/null +++ b/Zend/tests/context_managers/exception_is_forwarded_to_manager.phpt @@ -0,0 +1,30 @@ +--TEST-- +Context manager: exception is forwarded to manager +--FILE-- +getMessage(), "\n"; + throw $e; + } +} + +try { + using (some_context() => $var) { + throw new Exception('Thrown from body'); + }; +} catch (Exception $e) { + echo $e::class, ": ", $e->getMessage(), "\n"; +} + +echo "\$var is unset() after the block: "; +var_dump(!isset($var)); + +?> +--EXPECT-- +From context manager: Exception: Thrown from body +Exception: Thrown from body +$var is unset() after the block: bool(true) diff --git a/Zend/tests/context_managers/var_is_optional.phpt b/Zend/tests/context_managers/var_is_optional.phpt new file mode 100644 index 000000000000..f9e31536dd7b --- /dev/null +++ b/Zend/tests/context_managers/var_is_optional.phpt @@ -0,0 +1,16 @@ +--TEST-- +Context manager: VAR is optional +--FILE-- + +--EXPECT-- +Body is executed once diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index 9b1006e9f5c1..41db3678d8f6 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -54,6 +54,12 @@ 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_copy_znode(const znode *node) { + zend_ast *ast = zend_ast_create_znode(node); + ast->attr = 1; + return ast; +} + ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_fcc(zend_ast *args) { zend_ast_fcc *ast; diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h index 5d646f428c1b..fa23bc736650 100644 --- a/Zend/zend_ast.h +++ b/Zend/zend_ast.h @@ -69,6 +69,7 @@ enum _zend_ast_kind { ZEND_AST_ATTRIBUTE_GROUP, ZEND_AST_MATCH_ARM_LIST, ZEND_AST_MODIFIER_LIST, + ZEND_AST_USING_LIST, /* 0 child nodes */ ZEND_AST_MAGIC_CONST = 0 << ZEND_AST_NUM_CHILDREN_SHIFT, @@ -153,6 +154,8 @@ enum _zend_ast_kind { ZEND_AST_MATCH_ARM, ZEND_AST_NAMED_ARG, ZEND_AST_PIPE, + ZEND_AST_USING, + ZEND_AST_USING_ELEM, /* 3 child nodes */ ZEND_AST_METHOD_CALL = 3 << ZEND_AST_NUM_CHILDREN_SHIFT, diff --git a/Zend/zend_builtin_functions.c b/Zend/zend_builtin_functions.c index ad2c0dd2009a..99d3c8f32881 100644 --- a/Zend/zend_builtin_functions.c +++ b/Zend/zend_builtin_functions.c @@ -32,6 +32,7 @@ #include "zend_autoload.h" #include "zend_builtin_functions_arginfo.h" #include "zend_smart_str.h" +#include "zend_context_managers.h" /* }}} */ @@ -41,6 +42,8 @@ ZEND_MINIT_FUNCTION(core) { /* {{{ */ zend_standard_class_def = register_class_stdClass(); + ZEND_MINIT(context_managers)(INIT_FUNC_ARGS_PASSTHRU); + return SUCCESS; } /* }}} */ diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index f71a245526a3..4c4cae2958f1 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -104,6 +104,7 @@ static zend_op *zend_delayed_compile_var(znode *result, zend_ast *ast, uint32_t static void zend_compile_expr(znode *result, zend_ast *ast); static void zend_compile_stmt(zend_ast *ast); static void zend_compile_assign(znode *result, zend_ast *ast, bool stmt, uint32_t type); +static void zend_compile_try(const zend_ast *ast); static zend_ast *zend_partial_apply(zend_ast *callable_ast, zend_ast *pipe_arg); @@ -3678,6 +3679,12 @@ static void zend_compile_assign(znode *result, zend_ast *ast, bool stmt, uint32_ result->op_type = IS_UNUSED; } return; + case ZEND_AST_ZNODE: + var_node = *zend_ast_get_znode(var_ast); + zend_compile_expr(&expr_node, expr_ast); + opline = zend_emit_op(NULL, ZEND_QM_ASSIGN, &expr_node, NULL); + SET_NODE(opline->result, &var_node); + break; default: ZEND_UNREACHABLE(); } } @@ -6397,6 +6404,235 @@ static void zend_compile_void_cast(znode *result, const zend_ast *ast) } } +static void zend_emit_copy_tmp(znode *result, znode *expr) +{ + if (expr->op_type == IS_VAR) { + zend_emit_op(result, ZEND_COPY_TMP, expr, NULL); + } else if (expr->op_type == IS_TMP_VAR) { + zend_emit_op_tmp(result, ZEND_COPY_TMP, expr, NULL); + } else { + if (expr->op_type == IS_CONST) { + Z_TRY_ADDREF(expr->u.constant); + } + *result = *expr; + } +} + +static void zend_compile_using(znode *result, zend_ast *ast) +{ + zend_ast_list *list_ast = zend_ast_get_list(ast->child[0]); + ZEND_ASSERT(list_ast->children > 0); + + zend_ast *expr_ast = list_ast->child[0]->child[0]; + zend_ast *var_ast = list_ast->child[0]->child[1]; + + zend_ast *stmts_ast = ast->child[1]; + bool is_last = true; + + result->op_type = IS_TMP_VAR; + result->u.op.var = get_temporary_variable(); + + znode null_node; + null_node.op_type = IS_CONST; + ZVAL_NULL(&null_node.u.constant); + zend_emit_op_tmp(result, ZEND_QM_ASSIGN, &null_node, NULL); + + /* For a using() with multiple context managers, set stmts_ast to the same + * USING_AST minus the first element. */ + if (list_ast->children > 1) { + + zend_ast *copy = zend_ast_dup(ast); + zend_ast_list *list_ast = zend_ast_get_list(copy->child[0]); + zend_ast_destroy(list_ast->child[0]); + memmove(list_ast->child, list_ast->child + 1, sizeof(*list_ast->child) * list_ast->children - 1); + list_ast->children--; + + stmts_ast = zend_ast_create_list(1, ZEND_AST_STMT_LIST, + zend_ast_create(ZEND_AST_ASSIGN, zend_ast_create_znode(result), copy)); + + is_last = false; + } + + /* manager = (expr); */ + znode manager; + zend_compile_expr(&manager, expr_ast); + if (manager.op_type != IS_TMP_VAR) { + zend_emit_op_tmp(&manager, ZEND_QM_ASSIGN, &manager, NULL); + } + + /* if (!(manager instanceof Generator)) { throw } */ + { + zend_ast *generator_class_ast = zend_ast_create_zval_from_str(ZSTR_KNOWN(ZEND_STR_GENERATOR)); + generator_class_ast->attr = ZEND_NAME_FQ; + + zend_ast *type_error_class_ast = zend_ast_create_zval_from_str(ZSTR_KNOWN(ZEND_STR_TYPE_ERROR)); + type_error_class_ast->attr = ZEND_NAME_FQ; + + zend_ast *invalid_type_fmt_ast = zend_ast_create_zval_from_str(ZSTR_INIT_LITERAL("Context Manager must be an instance of Generator (TODO: proper error message)", false)); + + zend_ast *check_type_ast = zend_ast_create_list(1, ZEND_AST_IF, + zend_ast_create(ZEND_AST_IF_ELEM, + /* !(manager instanceof Generator) */ + zend_ast_create_ex(ZEND_AST_UNARY_OP, ZEND_BOOL_NOT, + zend_ast_create(ZEND_AST_INSTANCEOF, zend_ast_create_copy_znode(&manager), generator_class_ast)), + /* throw */ + zend_ast_create(ZEND_AST_THROW, zend_ast_create(ZEND_AST_NEW, type_error_class_ast, + zend_ast_create_arg_list(1, ZEND_AST_ARG_LIST, invalid_type_fmt_ast))))); + zend_compile_stmt(check_type_ast); + zend_ast_destroy(check_type_ast); + } + + /** + * var = manager->current(); + * if (var === null && !manager->valid()) { throw } + * [$var = var;] + */ + { + zend_ast *current_method_ast = zend_ast_create_zval_from_str(ZSTR_KNOWN(ZEND_STR_CURRENT)); + + zend_ast *valid_method_ast = zend_ast_create_zval_from_str(ZSTR_KNOWN(ZEND_STR_VALID)); + + zend_ast *error_class_ast = zend_ast_create_zval_from_str(ZSTR_KNOWN(ZEND_STR_ERROR)); + error_class_ast->attr = ZEND_NAME_FQ; + + zend_ast *did_not_yield_fmt_ast = zend_ast_create_zval_from_str(ZSTR_INIT_LITERAL("Context Manager must yield once, but it returned without yielding", false)); + + zval zv; + ZVAL_NULL(&zv); + zend_ast *null_ast = zend_ast_create_zval(&zv); + + zend_ast *current_ast = zend_ast_create(ZEND_AST_METHOD_CALL, + zend_ast_create_copy_znode(&manager), + current_method_ast, + zend_ast_create_arg_list(0, ZEND_AST_ARG_LIST)); + + znode result; + zend_compile_expr(&result, current_ast); + zend_ast_destroy(current_ast); + + zend_ast *stmt = zend_ast_create_list(1, ZEND_AST_IF, + zend_ast_create(ZEND_AST_IF_ELEM, + /* $var === null && !manager->valid() */ + zend_ast_create(ZEND_AST_AND, + /* $var === null */ + zend_ast_create_ex(ZEND_AST_BINARY_OP, ZEND_IS_IDENTICAL, zend_ast_create_copy_znode(&result), null_ast), + /* && !manager->valid() */ + zend_ast_create_ex(ZEND_AST_UNARY_OP, ZEND_BOOL_NOT, + zend_ast_create(ZEND_AST_METHOD_CALL, zend_ast_create_copy_znode(&manager), valid_method_ast, zend_ast_create_arg_list(0, ZEND_AST_ARG_LIST)))), + /* throw */ + zend_ast_create(ZEND_AST_THROW, + zend_ast_create(ZEND_AST_NEW, error_class_ast, + zend_ast_create_arg_list(1, ZEND_AST_ARG_LIST, did_not_yield_fmt_ast))))); + zend_compile_stmt(stmt); + zend_ast_destroy(stmt); + + if (var_ast) { + zend_ast *assign_ast = zend_ast_create(ZEND_AST_ASSIGN, + zend_ast_dup(var_ast), + zend_ast_create_znode(&result)); + zend_compile_stmt(assign_ast); + zend_ast_destroy(assign_ast); + } else { + zend_do_free(&result); + } + } + + /** + * try { + * stmts + * } catch (Throwable $e) { + * manager->throw($e); + * } finally { + * [unset($var);] + * manager->next(); + * if (manager->valid()) { throw } + * try { result = manager->getReturn() } catch (Throwable $e) {} + * } + */ + { + zend_ast *throwable_class_ast = zend_ast_create_zval_from_str(ZSTR_KNOWN(ZEND_STR_THROWABLE)); + throwable_class_ast->attr = ZEND_NAME_FQ; + + znode exception; + exception.op_type = IS_TMP_VAR; + exception.u.op.var = get_temporary_variable(); + + /* } catch (Throwable exception) { manager->throw(exception); } */ + zend_ast *catch_list_ast = zend_ast_create_list(1, ZEND_AST_CATCH_LIST, + zend_ast_create(ZEND_AST_CATCH, + zend_ast_create_list(1, ZEND_AST_NAME_LIST, throwable_class_ast), + zend_ast_create_znode(&exception), + zend_ast_create_list(1, ZEND_AST_STMT_LIST, + zend_ast_create(ZEND_AST_METHOD_CALL, zend_ast_create_copy_znode(&manager), zend_ast_create_zval_from_str(ZSTR_KNOWN(ZEND_STR_THROW)), + zend_ast_create_arg_list(1, ZEND_AST_ARG_LIST, zend_ast_create_znode(&exception)))))); + + zend_ast *finally_ast = zend_ast_create_list(0, ZEND_AST_STMT_LIST); + + if (var_ast) { + /* unset($var); */ + finally_ast = zend_ast_list_add(finally_ast, + zend_ast_create(ZEND_AST_UNSET, zend_ast_dup(var_ast))); + } + + /* manager->next(); */ + finally_ast = zend_ast_list_add(finally_ast, + zend_ast_create(ZEND_AST_METHOD_CALL, + zend_ast_create_copy_znode(&manager), + zend_ast_create_zval_from_str(ZSTR_KNOWN(ZEND_STR_NEXT)), + zend_ast_create_arg_list(0, ZEND_AST_ARG_LIST))); + + /* if (manager->valid()) { throw } */ + { + zend_ast *valid_method_ast = zend_ast_create_zval_from_str(ZSTR_KNOWN(ZEND_STR_VALID)); + + zend_ast *error_class_ast = zend_ast_create_zval_from_str(ZSTR_KNOWN(ZEND_STR_ERROR)); + error_class_ast->attr = ZEND_NAME_FQ; + + zend_ast *yielded_twice_fmt_ast = zend_ast_create_zval_from_str(ZSTR_INIT_LITERAL("Context Manager must yield only once, but it yielded a second time when fetching the return value", false)); + + finally_ast = zend_ast_list_add(finally_ast, + zend_ast_create_list(1, ZEND_AST_IF, + zend_ast_create(ZEND_AST_IF_ELEM, + /* manager->valid() */ + zend_ast_create(ZEND_AST_METHOD_CALL, zend_ast_create_copy_znode(&manager), valid_method_ast, + zend_ast_create_arg_list(0, ZEND_AST_ARG_LIST)), + /* throw */ + zend_ast_create(ZEND_AST_THROW, + zend_ast_create(ZEND_AST_NEW, error_class_ast, + zend_ast_create_arg_list(1, ZEND_AST_ARG_LIST, yielded_twice_fmt_ast)))))); + } + + /* try { manager->getReturn(); } catch (Throwable) {} */ + if (is_last) { + zend_ast *throwable_class_ast2 = zend_ast_create_zval_from_str(ZSTR_KNOWN(ZEND_STR_THROWABLE)); + throwable_class_ast2->attr = ZEND_NAME_FQ; + + finally_ast = zend_ast_list_add(finally_ast, + zend_ast_create(ZEND_AST_TRY, + zend_ast_create_list(1, ZEND_AST_STMT_LIST, + zend_ast_create(ZEND_AST_ASSIGN, + zend_ast_create_znode(result), + zend_ast_create(ZEND_AST_METHOD_CALL, zend_ast_create_copy_znode(&manager), zend_ast_create_zval_from_str(ZSTR_KNOWN(ZEND_STR_GET_RETURN)), + zend_ast_create_arg_list(0, ZEND_AST_ARG_LIST)))), + zend_ast_create_list(1, ZEND_AST_CATCH_LIST, + zend_ast_create(ZEND_AST_CATCH, + zend_ast_create_list(1, ZEND_AST_NAME_LIST, throwable_class_ast2), + NULL, + zend_ast_create_list(0, ZEND_AST_STMT_LIST))), + NULL)); + } + + zend_compile_try(zend_ast_create(ZEND_AST_TRY, stmts_ast, catch_list_ast, finally_ast)); + if (!is_last) { + zend_ast_destroy(stmts_ast); + } + zend_ast_destroy(catch_list_ast); + zend_ast_destroy(finally_ast); + } + + zend_do_free(&manager); +} + static void zend_compile_echo(const zend_ast *ast) /* {{{ */ { zend_op *opline; @@ -7490,7 +7726,7 @@ static void zend_compile_try(const zend_ast *ast) /* {{{ */ const zend_ast_list *classes = zend_ast_get_list(catch_ast->child[0]); zend_ast *var_ast = catch_ast->child[1]; zend_ast *stmt_ast = catch_ast->child[2]; - zend_string *var_name = var_ast ? zval_make_interned_string(zend_ast_get_zval(var_ast)) : NULL; + zend_string *var_name = var_ast && var_ast->kind == ZEND_AST_ZVAL ? zval_make_interned_string(zend_ast_get_zval(var_ast)) : NULL; bool is_last_catch = (i + 1 == catches->children); uint32_t *jmp_multicatch = safe_emalloc(sizeof(uint32_t), classes->children - 1, 0); @@ -7522,8 +7758,18 @@ static void zend_compile_try(const zend_ast *ast) /* {{{ */ zend_error_noreturn(E_COMPILE_ERROR, "Cannot re-assign $this"); } - opline->result_type = var_name ? IS_CV : IS_UNUSED; - opline->result.var = var_name ? lookup_cv(var_name) : -1; + if (var_name) { + opline->result_type = var_name ? IS_CV : IS_UNUSED; + opline->result.var = var_name ? lookup_cv(var_name) : -1; + } else if (var_ast && var_ast->kind == ZEND_AST_ZNODE) { + znode *node = zend_ast_get_znode(var_ast); + ZEND_ASSERT(node->op_type == IS_TMP_VAR); + opline->result_type = IS_TMP_VAR; + opline->result = node->u.op; + } else { + opline->result_type = IS_UNUSED; + opline->result.var = -1; + } if (is_last_catch && is_last_class) { opline->extended_value |= ZEND_LAST_CATCH; @@ -12661,6 +12907,9 @@ static zend_never_inline void zend_compile_expr_inner(znode *result, zend_ast *a return; case ZEND_AST_ZNODE: *result = *zend_ast_get_znode(ast); + if (ast->attr) { + zend_emit_copy_tmp(result, result); + } return; case ZEND_AST_VAR: case ZEND_AST_DIM: @@ -12780,6 +13029,9 @@ static zend_never_inline void zend_compile_expr_inner(znode *result, zend_ast *a case ZEND_AST_MATCH: zend_compile_match(result, ast); return; + case ZEND_AST_USING: + zend_compile_using(result, ast); + break; default: ZEND_ASSERT(0 /* not supported */); } diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index ec6d8b09bbe7..38902e11b6a3 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -97,6 +97,7 @@ typedef struct _zend_ast_znode { } zend_ast_znode; ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_znode(const znode *node); +ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_copy_znode(const znode *node); static zend_always_inline znode *zend_ast_get_znode(zend_ast *ast) { return &((zend_ast_znode *) ast)->node; @@ -155,7 +156,6 @@ struct _zend_op { #endif }; - typedef struct _zend_brk_cont_element { int start; int cont; diff --git a/Zend/zend_context_managers.c b/Zend/zend_context_managers.c new file mode 100644 index 000000000000..2f476c02bfc1 --- /dev/null +++ b/Zend/zend_context_managers.c @@ -0,0 +1,28 @@ +/* + +----------------------------------------------------------------------+ + | Zend Engine | + +----------------------------------------------------------------------+ + | Copyright (c) Zend Technologies Ltd. (http://www.zend.com) | + +----------------------------------------------------------------------+ + | This source file is subject to version 2.00 of the Zend license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.zend.com/license/2_00.txt. | + | If you did not receive a copy of the Zend license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@zend.com so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ +*/ + +#include "zend.h" +#include "zend_API.h" +#include "zend_context_managers_arginfo.h" + +ZEND_API zend_class_entry *zend_ce_context_manager = NULL; + +ZEND_MINIT_FUNCTION(context_managers) +{ + zend_ce_context_manager = register_class_ContextManager(); + + return SUCCESS; +} diff --git a/Zend/zend_context_managers.h b/Zend/zend_context_managers.h new file mode 100644 index 000000000000..77ffd553ffac --- /dev/null +++ b/Zend/zend_context_managers.h @@ -0,0 +1,26 @@ +/* + +----------------------------------------------------------------------+ + | Zend Engine | + +----------------------------------------------------------------------+ + | Copyright (c) Zend Technologies Ltd. (http://www.zend.com) | + +----------------------------------------------------------------------+ + | This source file is subject to version 2.00 of the Zend license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.zend.com/license/2_00.txt. | + | If you did not receive a copy of the Zend license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@zend.com so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ +*/ + +#ifndef ZEND_CONTEXT_MANAGERS_H +#define ZEND_CONTEXT_MANAGERS_H + +#include "zend_API.h" + +extern ZEND_API zend_class_entry *zend_ce_context_manager; + +ZEND_MINIT_FUNCTION(context_managers); + +#endif /* ZEND_CONTEXT_MANAGERS_H */ diff --git a/Zend/zend_context_managers.stub.php b/Zend/zend_context_managers.stub.php new file mode 100644 index 000000000000..816cf95571f3 --- /dev/null +++ b/Zend/zend_context_managers.stub.php @@ -0,0 +1,8 @@ + T_FUNC_C "'__FUNCTION__'" %token T_PROPERTY_C "'__PROPERTY__'" %token T_NS_C "'__NAMESPACE__'" +%token T_USING "'using'" %token END 0 "end of file" %token T_ATTRIBUTE "'#['" @@ -270,7 +271,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %type encaps_var encaps_var_offset isset_variables %type top_statement_list use_declarations const_list inner_statement_list if_stmt %type alt_if_stmt for_cond_exprs for_exprs switch_case_list global_var_list static_var_list -%type echo_expr_list unset_variables catch_name_list catch_list optional_variable parameter_list class_statement_list +%type echo_expr_list unset_variables catch_name_list catch_list optional_variable optional_using_var using using_list parameter_list class_statement_list %type implements_list case_list if_stmt_without_else %type non_empty_parameter_list argument_list non_empty_argument_list property_list %type class_const_list class_const_decl class_name_list trait_adaptations method_body non_empty_for_exprs @@ -309,7 +310,7 @@ reserved_non_modifiers: | T_THROW | T_USE | T_INSTEADOF | T_GLOBAL | T_VAR | T_UNSET | T_ISSET | T_EMPTY | T_CONTINUE | T_GOTO | T_FUNCTION | T_CONST | T_RETURN | T_PRINT | T_YIELD | T_LIST | T_SWITCH | T_ENDSWITCH | T_CASE | T_DEFAULT | T_BREAK | T_ARRAY | T_CALLABLE | T_EXTENDS | T_IMPLEMENTS | T_NAMESPACE | T_TRAIT | T_INTERFACE | T_CLASS - | T_CLASS_C | T_TRAIT_C | T_FUNC_C | T_METHOD_C | T_LINE | T_FILE | T_DIR | T_NS_C | T_FN | T_MATCH | T_ENUM + | T_CLASS_C | T_TRAIT_C | T_FUNC_C | T_METHOD_C | T_LINE | T_FILE | T_DIR | T_NS_C | T_FN | T_MATCH | T_ENUM | T_USING | T_PROPERTY_C ; @@ -1389,8 +1390,22 @@ expr: | attributes T_STATIC inline_function { $$ = zend_ast_with_attributes($3, $1); ((zend_ast_decl *) $$)->flags |= ZEND_ACC_STATIC; } | match { $$ = $1; } + | using { $$ = $1; } ; +using: + T_USING '(' using_list ')' statement { $$ = zend_ast_create(ZEND_AST_USING, $3, $5); } + +optional_using_var: + %empty { $$ = NULL; } + | T_DOUBLE_ARROW T_VARIABLE { $$ = zend_ast_create(ZEND_AST_VAR, $2); } +; + +using_list: + expr optional_using_var + { $$ = zend_ast_create_list(1, ZEND_AST_USING_LIST, zend_ast_create(ZEND_AST_USING_ELEM, $1, $2)); } + | using_list ',' expr optional_using_var + { $$ = zend_ast_list_add($1, zend_ast_create(ZEND_AST_USING_ELEM, $3, $4)); } inline_function: function returns_ref backup_doc_comment '(' parameter_list ')' lexical_vars return_type diff --git a/Zend/zend_language_scanner.l b/Zend/zend_language_scanner.l index 60143bc800fe..adadedae49ad 100644 --- a/Zend/zend_language_scanner.l +++ b/Zend/zend_language_scanner.l @@ -1664,6 +1664,10 @@ OPTIONAL_WHITESPACE_OR_COMMENTS ({WHITESPACE}|{MULTI_LINE_COMMENT}|{SINGLE_LINE_ RETURN_TOKEN_WITH_IDENT(T_VAR); } +"using" { + RETURN_TOKEN_WITH_IDENT(T_USING); +} + "("{TABS_AND_SPACES}("int"){TABS_AND_SPACES}")" { RETURN_TOKEN(T_INT_CAST); } diff --git a/Zend/zend_list.c b/Zend/zend_list.c index 4e96ce0ec7da..87061de58e4c 100644 --- a/Zend/zend_list.c +++ b/Zend/zend_list.c @@ -57,7 +57,7 @@ ZEND_API void ZEND_FASTCALL zend_list_free(zend_resource *res) zend_hash_index_del(&EG(regular_list), res->handle); } -static void zend_resource_dtor(zend_resource *res) +static void ZEND_FASTCALL zend_resource_dtor(zend_resource *res) { zend_rsrc_list_dtors_entry *ld; zend_resource r = *res; diff --git a/Zend/zend_string.h b/Zend/zend_string.h index d5674f3ff1c4..d4649e4d4448 100644 --- a/Zend/zend_string.h +++ b/Zend/zend_string.h @@ -818,6 +818,15 @@ default: ZEND_UNREACHABLE(); _(ZEND_STR_ASSERT, "assert") \ _(ZEND_STR_CALL_USER_FUNC, "call_user_func") \ _(ZEND_STR_ARRAY_SLICE, "array_slice") \ + _(ZEND_STR_GENERATOR, "Generator") \ + _(ZEND_STR_TYPE_ERROR, "TypeError") \ + _(ZEND_STR_ERROR, "Error") \ + _(ZEND_STR_THROWABLE, "Throwable") \ + _(ZEND_STR_CURRENT, "current") \ + _(ZEND_STR_VALID, "valid") \ + _(ZEND_STR_NEXT, "next") \ + _(ZEND_STR_THROW, "throw") \ + _(ZEND_STR_GET_RETURN, "getReturn") \ _(ZEND_STR_SENSITIVEPARAMETER, "SensitiveParameter") \ _(ZEND_STR_CONST_EXPR_PLACEHOLDER, "[constant expression]") \ _(ZEND_STR_DEPRECATED_CAPITALIZED, "Deprecated") \ diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 95196c4007c4..b639411f75c7 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -4889,12 +4889,17 @@ ZEND_VM_HANDLER(107, ZEND_CATCH, CONST, JMP_ADDR, LAST_CATCH|CACHE_SLOT) exception = EG(exception); EG(exception) = NULL; if (RETURN_VALUE_USED(opline)) { - /* Always perform a strict assignment. There is a reasonable expectation that if you - * write "catch (Exception $e)" then $e will actually be instanceof Exception. As such, - * we should not permit coercion to string here. */ - zval tmp; - ZVAL_OBJ(&tmp, exception); - zend_assign_to_variable(EX_VAR(opline->result.var), &tmp, IS_TMP_VAR, /* strict */ 1); + if (opline->result_type == IS_CV) { + /* Always perform a strict assignment. There is a reasonable expectation that if you + * write "catch (Exception $e)" then $e will actually be instanceof Exception. As such, + * we should not permit coercion to string here. */ + zval tmp; + ZVAL_OBJ(&tmp, exception); + zend_assign_to_variable(EX_VAR(opline->result.var), &tmp, IS_TMP_VAR, /* strict */ 1); + } else { + ZEND_ASSERT(opline->result_type == IS_TMP_VAR); + ZVAL_OBJ(EX_VAR(opline->result.var), exception); + } } else { OBJ_RELEASE(exception); } diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 7e843bb6c1fe..d251d0e5974d 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -5217,12 +5217,17 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_CATCH_SPEC_CO exception = EG(exception); EG(exception) = NULL; if (RETURN_VALUE_USED(opline)) { - /* Always perform a strict assignment. There is a reasonable expectation that if you - * write "catch (Exception $e)" then $e will actually be instanceof Exception. As such, - * we should not permit coercion to string here. */ - zval tmp; - ZVAL_OBJ(&tmp, exception); - zend_assign_to_variable(EX_VAR(opline->result.var), &tmp, IS_TMP_VAR, /* strict */ 1); + if (opline->result_type == IS_CV) { + /* Always perform a strict assignment. There is a reasonable expectation that if you + * write "catch (Exception $e)" then $e will actually be instanceof Exception. As such, + * we should not permit coercion to string here. */ + zval tmp; + ZVAL_OBJ(&tmp, exception); + zend_assign_to_variable(EX_VAR(opline->result.var), &tmp, IS_TMP_VAR, /* strict */ 1); + } else { + ZEND_ASSERT(opline->result_type == IS_TMP_VAR); + ZVAL_OBJ(EX_VAR(opline->result.var), exception); + } } else { OBJ_RELEASE(exception); } @@ -58102,12 +58107,17 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_CATCH_SPEC_CONST_T exception = EG(exception); EG(exception) = NULL; if (RETURN_VALUE_USED(opline)) { - /* Always perform a strict assignment. There is a reasonable expectation that if you - * write "catch (Exception $e)" then $e will actually be instanceof Exception. As such, - * we should not permit coercion to string here. */ - zval tmp; - ZVAL_OBJ(&tmp, exception); - zend_assign_to_variable(EX_VAR(opline->result.var), &tmp, IS_TMP_VAR, /* strict */ 1); + if (opline->result_type == IS_CV) { + /* Always perform a strict assignment. There is a reasonable expectation that if you + * write "catch (Exception $e)" then $e will actually be instanceof Exception. As such, + * we should not permit coercion to string here. */ + zval tmp; + ZVAL_OBJ(&tmp, exception); + zend_assign_to_variable(EX_VAR(opline->result.var), &tmp, IS_TMP_VAR, /* strict */ 1); + } else { + ZEND_ASSERT(opline->result_type == IS_TMP_VAR); + ZVAL_OBJ(EX_VAR(opline->result.var), exception); + } } else { OBJ_RELEASE(exception); } diff --git a/configure.ac b/configure.ac index d0321145e492..38b6c4a0ad55 100644 --- a/configure.ac +++ b/configure.ac @@ -1786,6 +1786,7 @@ PHP_ADD_SOURCES([Zend], m4_normalize([ zend_closures.c zend_compile.c zend_constants.c + zend_context_managers.c zend_cpuinfo.c zend_default_classes.c zend_dtrace.c diff --git a/ext/tokenizer/tokenizer_data.c b/ext/tokenizer/tokenizer_data.c index 87b15b8bb345..a3413ade13f7 100644 --- a/ext/tokenizer/tokenizer_data.c +++ b/ext/tokenizer/tokenizer_data.c @@ -118,6 +118,7 @@ char *get_token_type_name(int token_type) case T_FUNC_C: return "T_FUNC_C"; case T_PROPERTY_C: return "T_PROPERTY_C"; case T_NS_C: return "T_NS_C"; + case T_WITH: return "T_WITH"; case T_ATTRIBUTE: return "T_ATTRIBUTE"; case T_PLUS_EQUAL: return "T_PLUS_EQUAL"; case T_MINUS_EQUAL: return "T_MINUS_EQUAL"; diff --git a/ext/tokenizer/tokenizer_data.stub.php b/ext/tokenizer/tokenizer_data.stub.php index 57c8edad8acb..11161bf3ff23 100644 --- a/ext/tokenizer/tokenizer_data.stub.php +++ b/ext/tokenizer/tokenizer_data.stub.php @@ -477,6 +477,11 @@ * @cvalue T_NS_C */ const T_NS_C = UNKNOWN; +/** + * @var int + * @cvalue T_USING + */ +const T_USING = UNKNOWN; /** * @var int * @cvalue T_ATTRIBUTE diff --git a/win32/build/config.w32 b/win32/build/config.w32 index 253dab653986..5a33b9a0221e 100644 --- a/win32/build/config.w32 +++ b/win32/build/config.w32 @@ -241,7 +241,7 @@ ADD_SOURCES("Zend", "zend_language_parser.c zend_language_scanner.c \ zend_float.c zend_string.c zend_generators.c zend_virtual_cwd.c zend_ast.c \ zend_inheritance.c zend_smart_str.c zend_cpuinfo.c zend_observer.c zend_system_id.c \ zend_enum.c zend_fibers.c zend_hrtime.c zend_frameless_function.c zend_property_hooks.c \ - zend_lazy_objects.c zend_autoload.c zend_partial.c"); + zend_lazy_objects.c zend_autoload.c zend_partial.c zend_context_managers.c"); ADD_SOURCES("Zend\\Optimizer", "zend_optimizer.c pass1.c pass3.c optimize_func_calls.c block_pass.c optimize_temp_vars_5.c nop_removal.c compact_literals.c zend_cfg.c zend_dfg.c dfa_pass.c zend_ssa.c zend_inference.c zend_func_info.c zend_call_graph.c zend_dump.c escape_analysis.c compact_vars.c dce.c sccp.c scdf.c"); var PHP_ASSEMBLER = PATH_PROG({