From: Max Semenik Date: Fri, 3 Apr 2020 21:22:17 +0000 (-0400) Subject: Support catching exceptions without capturing them to variables X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=23ee4d4b573101a8c005590397f9108861297dd6;p=php Support catching exceptions without capturing them to variables RFC: https://wiki.php.net/rfc/non-capturing_catches Closes GH-5345. --- diff --git a/UPGRADING b/UPGRADING index c67d619355..08f9558db8 100644 --- a/UPGRADING +++ b/UPGRADING @@ -509,6 +509,9 @@ PHP 8.0 UPGRADE NOTES RFC: https://wiki.php.net/rfc/throw_expression . An optional trailing comma is now allowed in parameter lists. RFC: https://wiki.php.net/rfc/trailing_comma_in_parameter_list + . It is now possible to write `catch (Exception)` to catch an exception + without storing it in a variable. + RFC: https://wiki.php.net/rfc/non-capturing_catches - Date: . Added DateTime::createFromInterface() and diff --git a/Zend/tests/try/catch_novar_1.phpt b/Zend/tests/try/catch_novar_1.phpt new file mode 100644 index 0000000000..80e93f3b95 --- /dev/null +++ b/Zend/tests/try/catch_novar_1.phpt @@ -0,0 +1,31 @@ +--TEST-- +catch without capturing a variable +--FILE-- + +--EXPECTF-- +Throwing + +Fatal error: Uncaught RuntimeException: ThrowsOnDestruct::__destruct in %s:%d +Stack trace: +#0 %s(%d): ThrowsOnDestruct->__destruct() +#1 {main} + thrown in %s on line %d diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index 2b5d1ea324..7451cf28e9 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -1993,8 +1993,10 @@ simple_list: case ZEND_AST_CATCH: smart_str_appends(str, "} catch ("); zend_ast_export_catch_name_list(str, zend_ast_get_list(ast->child[0]), indent); - smart_str_appends(str, " $"); - zend_ast_export_var(str, ast->child[1], 0, indent); + if (ast->child[1]) { + smart_str_appends(str, " $"); + zend_ast_export_var(str, ast->child[1], 0, indent); + } smart_str_appends(str, ") {\n"); zend_ast_export_stmt(str, ast->child[2], indent + 1); zend_ast_export_indent(str, indent); diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index cc7eb6f1d5..7638bd16fc 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -5213,7 +5213,7 @@ void zend_compile_try(zend_ast *ast) /* {{{ */ 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 = zval_make_interned_string(zend_ast_get_zval(var_ast)); + zend_string *var_name = var_ast ? zval_make_interned_string(zend_ast_get_zval(var_ast)) : NULL; zend_bool is_last_catch = (i + 1 == catches->children); uint32_t *jmp_multicatch = safe_emalloc(sizeof(uint32_t), classes->children - 1, 0); @@ -5241,12 +5241,12 @@ void zend_compile_try(zend_ast *ast) /* {{{ */ zend_resolve_class_name_ast(class_ast)); opline->extended_value = zend_alloc_cache_slot(); - if (zend_string_equals_literal(var_name, "this")) { + if (var_name && zend_string_equals_literal(var_name, "this")) { zend_error_noreturn(E_COMPILE_ERROR, "Cannot re-assign $this"); } - opline->result_type = IS_CV; - opline->result.var = lookup_cv(var_name); + opline->result_type = var_name ? IS_CV : IS_UNUSED; + opline->result.var = var_name ? lookup_cv(var_name) : -1; if (is_last_catch && is_last_class) { opline->extended_value |= ZEND_LAST_CATCH; diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index 78b6e758ba..18290acc44 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -248,7 +248,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_exprs switch_case_list global_var_list static_var_list -%type echo_expr_list unset_variables catch_name_list catch_list parameter_list class_statement_list +%type echo_expr_list unset_variables catch_name_list catch_list optional_variable 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 @@ -465,7 +465,7 @@ statement: catch_list: %empty { $$ = zend_ast_create_list(0, ZEND_AST_CATCH_LIST); } - | catch_list T_CATCH '(' catch_name_list T_VARIABLE ')' '{' inner_statement_list '}' + | catch_list T_CATCH '(' catch_name_list optional_variable ')' '{' inner_statement_list '}' { $$ = zend_ast_list_add($1, zend_ast_create(ZEND_AST_CATCH, $4, $5, $8)); } ; @@ -474,6 +474,11 @@ catch_name_list: | catch_name_list '|' class_name { $$ = zend_ast_list_add($1, $3); } ; +optional_variable: + %empty { $$ = NULL; } + | T_VARIABLE { $$ = $1; } +; + finally_statement: %empty { $$ = NULL; } | T_FINALLY '{' inner_statement_list '}' { $$ = $3; } diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 411ff15751..b8b0dc5e3a 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -4453,7 +4453,6 @@ ZEND_VM_HANDLER(107, ZEND_CATCH, CONST, JMP_ADDR, LAST_CATCH|CACHE_SLOT) USE_OPLINE zend_class_entry *ce, *catch_ce; zend_object *exception; - zval *ex; SAVE_OPLINE(); /* Check whether an exception has been thrown, if not, jump over code */ @@ -4486,17 +4485,18 @@ ZEND_VM_HANDLER(107, ZEND_CATCH, CONST, JMP_ADDR, LAST_CATCH|CACHE_SLOT) } exception = EG(exception); - ex = EX_VAR(opline->result.var); - { + 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); - EG(exception) = NULL; - zend_assign_to_variable(ex, &tmp, IS_TMP_VAR, /* strict */ 1); - ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); + zend_assign_to_variable(EX_VAR(opline->result.var), &tmp, IS_TMP_VAR, /* strict */ 1); + } else { + OBJ_RELEASE(exception); } + ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); } ZEND_VM_HOT_HANDLER(65, ZEND_SEND_VAL, CONST|TMPVAR, NUM) diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 4b517c4522..16779b6616 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -3669,7 +3669,6 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CATCH_SPEC_CONST_HANDLER(ZEND_ USE_OPLINE zend_class_entry *ce, *catch_ce; zend_object *exception; - zval *ex; SAVE_OPLINE(); /* Check whether an exception has been thrown, if not, jump over code */ @@ -3702,17 +3701,18 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CATCH_SPEC_CONST_HANDLER(ZEND_ } exception = EG(exception); - ex = EX_VAR(opline->result.var); - { + 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); - EG(exception) = NULL; - zend_assign_to_variable(ex, &tmp, IS_TMP_VAR, /* strict */ 1); - ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); + zend_assign_to_variable(EX_VAR(opline->result.var), &tmp, IS_TMP_VAR, /* strict */ 1); + } else { + OBJ_RELEASE(exception); } + ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); } static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_SEND_VAL_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS)