]> granicus.if.org Git - php/commitdiff
Support catching exceptions without capturing them to variables
authorMax Semenik <maxsem.wiki@gmail.com>
Fri, 3 Apr 2020 21:22:17 +0000 (17:22 -0400)
committerNikita Popov <nikita.ppv@gmail.com>
Tue, 26 May 2020 13:12:18 +0000 (15:12 +0200)
RFC: https://wiki.php.net/rfc/non-capturing_catches

Closes GH-5345.

UPGRADING
Zend/tests/try/catch_novar_1.phpt [new file with mode: 0644]
Zend/tests/try/catch_novar_2.phpt [new file with mode: 0644]
Zend/zend_ast.c
Zend/zend_compile.c
Zend/zend_language_parser.y
Zend/zend_vm_def.h
Zend/zend_vm_execute.h

index c67d6193556df26a251ad3c9ab9d834db8193ae8..08f9558db8b901450ee248b1ead1737b09ba456a 100644 (file)
--- 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 (file)
index 0000000..80e93f3
--- /dev/null
@@ -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
diff --git a/Zend/tests/try/catch_novar_2.phpt b/Zend/tests/try/catch_novar_2.phpt
new file mode 100644 (file)
index 0000000..fa1ada9
--- /dev/null
@@ -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
index 2b5d1ea3242e47aad75a7a8bd8b364e96dd87ec9..7451cf28e92be1abb390392ddbe787655ccfbd8a 100644 (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);
index cc7eb6f1d59d356b6efa0444e4c69b07fe761725..7638bd16fc5ebaae359e661c46bdbdbb358ad2b4 100644 (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;
index 78b6e758ba9dcd7e1a669b5aa2bcee45243598e2..18290acc446f59c6475df31d032a1e05885ff473 100644 (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; }
index 411ff15751064360c8b293aaed681bb5a1530b5b..b8b0dc5e3a804f92ddca7e300f23b5785f6ee2ed 100644 (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)
index 4b517c4522c7e8278fac5420a7fb3d79ac22ddd3..16779b66167d6af59c67963f6d05c2cd7618a5c9 100644 (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)