Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
26 changes: 26 additions & 0 deletions Zend/tests/try/catch_novar_2.phpt
Original file line number Diff line number Diff line change
@@ -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
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.


Fatal error: Uncaught RuntimeException: ThrowsOnDestruct::__destruct in %s:%d
Stack trace:
#0 %s(%d): ThrowsOnDestruct->__destruct()
#1 {main}
thrown in %s on line %d
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
27 changes: 16 additions & 11 deletions Zend/zend_vm_def.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -4486,18 +4485,24 @@ 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);
}
zval_ptr_dtor(ex);
ZVAL_OBJ(ex, EG(exception));
if (UNEXPECTED(EG(exception) != exception)) {
ZVAL_UNDEF(ex);
HANDLE_EXCEPTION();
if (RETURN_VALUE_USED(opline)) {
zval *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));
if (UNEXPECTED(EG(exception) != exception)) {
ZVAL_UNDEF(ex);
HANDLE_EXCEPTION();
} else {
EG(exception) = NULL;
ZEND_VM_NEXT_OPCODE();
}
} else {
EG(exception) = NULL;
ZEND_VM_NEXT_OPCODE();
OBJ_RELEASE(exception);
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
}
}

Expand Down
27 changes: 16 additions & 11 deletions Zend/zend_vm_execute.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -3702,18 +3701,24 @@ 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);
}
zval_ptr_dtor(ex);
ZVAL_OBJ(ex, EG(exception));
if (UNEXPECTED(EG(exception) != exception)) {
ZVAL_UNDEF(ex);
HANDLE_EXCEPTION();
if (RETURN_VALUE_USED(opline)) {
zval *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));
if (UNEXPECTED(EG(exception) != exception)) {
ZVAL_UNDEF(ex);
HANDLE_EXCEPTION();
} else {
EG(exception) = NULL;
ZEND_VM_NEXT_OPCODE();
}
} else {
EG(exception) = NULL;
ZEND_VM_NEXT_OPCODE();
OBJ_RELEASE(exception);
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
}
}

Expand Down