Support catching exceptions without capturing them to variables

RFC: https://wiki.php.net/rfc/non-capturing_catches

Closes GH-5345.
This commit is contained in:
Max Semenik 2020-04-03 17:22:17 -04:00 committed by Nikita Popov
parent 1203bbf07e
commit 23ee4d4b57
8 changed files with 87 additions and 20 deletions

View File

@ -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

View File

@ -0,0 +1,31 @@
--TEST--
catch without capturing a variable
--FILE--
<?php
try {
throw new Exception();
} catch (Exception) {
echo "Exception\n";
}
try {
throw new Exception();
} catch (Exception) {
echo "Exception\n";
} catch (Error) {
echo "FAIL\n";
}
try {
throw new Exception();
} catch (Exception|Error) {
echo "Exception\n";
} catch (Throwable) {
echo "FAIL\n";
}
--EXPECT--
Exception
Exception
Exception

View File

@ -0,0 +1,26 @@
--TEST--
catch without capturing a variable - exception in destructor
--FILE--
<?php
class ThrowsOnDestruct extends Exception {
public function __destruct() {
echo "Throwing\n";
throw new RuntimeException(__METHOD__);
}
}
try {
throw new ThrowsOnDestruct();
} catch (Exception) {
echo "Unreachable catch\n";
}
echo "Unreachable fallthrough\n";
?>
--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

View File

@ -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);

View File

@ -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;

View File

@ -248,7 +248,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
%type <ast> encaps_var encaps_var_offset isset_variables
%type <ast> top_statement_list use_declarations const_list inner_statement_list if_stmt
%type <ast> alt_if_stmt for_exprs switch_case_list global_var_list static_var_list
%type <ast> echo_expr_list unset_variables catch_name_list catch_list parameter_list class_statement_list
%type <ast> echo_expr_list unset_variables catch_name_list catch_list optional_variable parameter_list class_statement_list
%type <ast> implements_list case_list if_stmt_without_else
%type <ast> non_empty_parameter_list argument_list non_empty_argument_list property_list
%type <ast> 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; }

View File

@ -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)

View File

@ -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)