Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
[RFC] Catch exceptions without capturing them to variables
The RFC is currently being drafted at
https://wiki.php.net/rfc/non-capturing_catches
  • Loading branch information
MaxSem authored and nikic committed May 26, 2020
commit 78c599b1b74f87d0c61f432133f828b5af323439
31 changes: 31 additions & 0 deletions Zend/tests/try/catch_novar_1.phpt
Original file line number Diff line number Diff line change
@@ -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
19 changes: 19 additions & 0 deletions Zend/tests/try/catch_novar_2.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
--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";

--EXPECT--
Throwing
Copy link
Contributor

Choose a reason for hiding this comment

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

This looks as if the php process exited - normally, if it rethrew, php's output would include a message about an uncaught exception and a stack trace.

6 changes: 4 additions & 2 deletions Zend/zend_ast.c
Original file line number Diff line number Diff line change
Expand Up @@ -1993,8 +1993,10 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio
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);
Expand Down
8 changes: 4 additions & 4 deletions Zend/zend_compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down
9 changes: 7 additions & 2 deletions Zend/zend_language_parser.y
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)); }
;

Expand All @@ -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; }
Expand Down
14 changes: 9 additions & 5 deletions Zend/zend_vm_def.h
Original file line number Diff line number Diff line change
Expand Up @@ -4486,12 +4486,16 @@ ZEND_VM_HANDLER(107, ZEND_CATCH, CONST, JMP_ADDR, LAST_CATCH|CACHE_SLOT)
}

exception = EG(exception);
ex = EX_VAR(opline->result.var);
if (UNEXPECTED(Z_ISREF_P(ex))) {
ex = Z_REFVAL_P(ex);
if (RETURN_VALUE_USED(opline)) {
ex = EX_VAR(opline->result.var);
if (UNEXPECTED(Z_ISREF_P(ex))) {
ex = Z_REFVAL_P(ex);
}
Copy link
Member

Choose a reason for hiding this comment

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

Hm... not related to your patch, but I just realized that this can bypass typed references.

Copy link
Member

Choose a reason for hiding this comment

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

This turned into something of a rabbit hole, but is now addressed with 314ab47 and 4a08ca1.

zval_ptr_dtor(ex);
ZVAL_OBJ(ex, EG(exception));
} else {
OBJ_RELEASE(EG(exception));
}
zval_ptr_dtor(ex);
ZVAL_OBJ(ex, EG(exception));
if (UNEXPECTED(EG(exception) != exception)) {
ZVAL_UNDEF(ex);
HANDLE_EXCEPTION();
Expand Down
14 changes: 9 additions & 5 deletions Zend/zend_vm_execute.h
Original file line number Diff line number Diff line change
Expand Up @@ -3702,12 +3702,16 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CATCH_SPEC_CONST_HANDLER(ZEND_
}

exception = EG(exception);
ex = EX_VAR(opline->result.var);
if (UNEXPECTED(Z_ISREF_P(ex))) {
ex = Z_REFVAL_P(ex);
if (RETURN_VALUE_USED(opline)) {
ex = EX_VAR(opline->result.var);
if (UNEXPECTED(Z_ISREF_P(ex))) {
ex = Z_REFVAL_P(ex);
}
zval_ptr_dtor(ex);
ZVAL_OBJ(ex, EG(exception));
} else {
OBJ_RELEASE(EG(exception));
}
zval_ptr_dtor(ex);
ZVAL_OBJ(ex, EG(exception));
if (UNEXPECTED(EG(exception) != exception)) {
ZVAL_UNDEF(ex);
HANDLE_EXCEPTION();
Expand Down