]> granicus.if.org Git - php/commitdiff
Allow catching multiple exception types in a single catch statement
authorPierrick Charron <pierrick@php.net>
Sun, 1 May 2016 22:47:08 +0000 (18:47 -0400)
committerPierrick Charron <pierrick@php.net>
Sun, 1 May 2016 22:47:08 +0000 (18:47 -0400)
This commit add the possibility to catch multiple exception types in
a single catch statement to avoid code duplication.

try {
   // Some code...
} catch (ExceptionType1 | ExceptionType2 $e) {
   // Code to handle the exception
} catch (\Exception $e) {
   // ...
}

Zend/tests/try/exceptions.inc [new file with mode: 0644]
Zend/tests/try/try_multicatch_001.phpt [new file with mode: 0644]
Zend/tests/try/try_multicatch_002.phpt [new file with mode: 0644]
Zend/tests/try/try_multicatch_003.phpt [new file with mode: 0644]
Zend/tests/try/try_multicatch_004.phpt [new file with mode: 0644]
Zend/tests/try/try_multicatch_005.phpt [new file with mode: 0644]
Zend/zend_ast.c
Zend/zend_compile.c
Zend/zend_language_parser.y

diff --git a/Zend/tests/try/exceptions.inc b/Zend/tests/try/exceptions.inc
new file mode 100644 (file)
index 0000000..8a87779
--- /dev/null
@@ -0,0 +1,5 @@
+<?php
+
+class Exception1 extends Exception {}
+class Exception2 extends Exception {}
+class Exception3 extends Exception {}
diff --git a/Zend/tests/try/try_multicatch_001.phpt b/Zend/tests/try/try_multicatch_001.phpt
new file mode 100644 (file)
index 0000000..0dffd32
--- /dev/null
@@ -0,0 +1,19 @@
+--TEST--
+Parsing test
+--FILE--
+<?php
+
+require_once __DIR__ . '/exceptions.inc';
+
+try {
+       echo 'TRY' . PHP_EOL;
+} catch(Exception1 | Exception2 $e) {
+       echo 'Exception';
+} finally {
+       echo 'FINALLY' . PHP_EOL;
+}
+
+?>
+--EXPECT--
+TRY
+FINALLY
diff --git a/Zend/tests/try/try_multicatch_002.phpt b/Zend/tests/try/try_multicatch_002.phpt
new file mode 100644 (file)
index 0000000..0e70fec
--- /dev/null
@@ -0,0 +1,21 @@
+--TEST--
+Catch first exception in the multicatch
+--FILE--
+<?php
+
+require_once __DIR__ . '/exceptions.inc';
+
+try {
+       echo 'TRY' . PHP_EOL;
+       throw new Exception1;
+} catch(Exception1 | Exception2 | Exception3 $e) {
+       echo get_class($e) . PHP_EOL;
+} finally {
+       echo 'FINALLY' . PHP_EOL;
+}
+
+?>
+--EXPECT--
+TRY
+Exception1
+FINALLY
diff --git a/Zend/tests/try/try_multicatch_003.phpt b/Zend/tests/try/try_multicatch_003.phpt
new file mode 100644 (file)
index 0000000..6aed1a2
--- /dev/null
@@ -0,0 +1,21 @@
+--TEST--
+Catch second exception in the multicatch
+--FILE--
+<?php
+
+require_once __DIR__ . '/exceptions.inc';
+
+try {
+       echo 'TRY' . PHP_EOL;
+       throw new Exception2;
+} catch(Exception1 | Exception2 | Exception3 $e) {
+       echo get_class($e) . PHP_EOL;
+} finally {
+       echo 'FINALLY' . PHP_EOL;
+}
+
+?>
+--EXPECT--
+TRY
+Exception2
+FINALLY
diff --git a/Zend/tests/try/try_multicatch_004.phpt b/Zend/tests/try/try_multicatch_004.phpt
new file mode 100644 (file)
index 0000000..d8b245a
--- /dev/null
@@ -0,0 +1,21 @@
+--TEST--
+Catch last exception in the multicatch
+--FILE--
+<?php
+
+require_once __DIR__ . '/exceptions.inc';
+
+try {
+       echo 'TRY' . PHP_EOL;
+       throw new Exception3;
+} catch(Exception1 | Exception2 | Exception3 $e) {
+       echo get_class($e) . PHP_EOL;
+} finally {
+       echo 'FINALLY' . PHP_EOL;
+}
+
+?>
+--EXPECT--
+TRY
+Exception3
+FINALLY
diff --git a/Zend/tests/try/try_multicatch_005.phpt b/Zend/tests/try/try_multicatch_005.phpt
new file mode 100644 (file)
index 0000000..cc3fc89
--- /dev/null
@@ -0,0 +1,25 @@
+--TEST--
+Catch exception in the nested multicatch
+--FILE--
+<?php
+
+require_once __DIR__ . '/exceptions.inc';
+
+try {
+       try {
+               echo 'TRY' . PHP_EOL;
+               throw new Exception3;
+       } catch (Exception1 | Exception3 $e) {
+               echo get_class($e) . PHP_EOL;
+       }
+} catch(Exception2 | Exception3 $e) {
+       echo 'Should never be executed';
+} finally {
+       echo 'FINALLY' . PHP_EOL;
+}
+
+?>
+--EXPECT--
+TRY
+Exception3
+FINALLY
index eee4a44e867e30e866748a91780e65030c633077..e7c61119197bedf6f3d9b8e6d13cd4b55bd4190e 100644 (file)
@@ -776,19 +776,22 @@ static void zend_ast_export_encaps_list(smart_str *str, char quote, zend_ast_lis
        }
 }
 
-static void zend_ast_export_name_list(smart_str *str, zend_ast_list *list, int indent)
+static void zend_ast_export_name_list_ex(smart_str *str, zend_ast_list *list, int indent, const char *separator)
 {
        uint32_t i = 0;
 
        while (i < list->children) {
                if (i != 0) {
-                       smart_str_appends(str, ", ");
+                       smart_str_appends(str, separator);
                }
                zend_ast_export_name(str, list->child[i], 0, indent);
                i++;
        }
 }
 
+#define zend_ast_export_name_list(s, l, i) zend_ast_export_name_list_ex(s, l, i, ", ")
+#define zend_ast_export_catch_name_list(s, l, i) zend_ast_export_name_list_ex(s, l, i, "|")
+
 static void zend_ast_export_var_list(smart_str *str, zend_ast_list *list, int indent)
 {
        uint32_t i = 0;
@@ -1584,7 +1587,7 @@ simple_list:
                        break;
                case ZEND_AST_CATCH:
                        smart_str_appends(str, "} catch (");
-                       zend_ast_export_ns_name(str, ast->child[0], 0, indent);
+                       zend_ast_export_catch_name_list(str, ast->child[0], indent);
                        smart_str_appends(str, " $");
                        zend_ast_export_var(str, ast->child[1], 0, indent);
                        smart_str_appends(str, ") {\n");
index 23781ba423f5c4e5a2dd3c7b2bc0d78b12cc2331..3adcc55f4e0c90f334b90dbcc0d7b08e6bd02345 100644 (file)
@@ -4542,7 +4542,7 @@ void zend_compile_try(zend_ast *ast) /* {{{ */
        zend_ast_list *catches = zend_ast_get_list(ast->child[1]);
        zend_ast *finally_ast = ast->child[2];
 
-       uint32_t i;
+       uint32_t i, j;
        zend_op *opline;
        uint32_t try_catch_offset;
        uint32_t *jmp_opnums = safe_emalloc(sizeof(uint32_t), catches->children, 0);
@@ -4587,34 +4587,53 @@ void zend_compile_try(zend_ast *ast) /* {{{ */
 
        for (i = 0; i < catches->children; ++i) {
                zend_ast *catch_ast = catches->child[i];
-               zend_ast *class_ast = catch_ast->child[0];
+               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];
                zval *var_name = zend_ast_get_zval(var_ast);
                zend_bool is_last_catch = (i + 1 == catches->children);
 
+               uint32_t *jmp_multicatch = safe_emalloc(sizeof(uint32_t), classes->children - 1, 0);
                uint32_t opnum_catch;
 
-               if (!zend_is_const_default_class_ref(class_ast)) {
-                       zend_error_noreturn(E_COMPILE_ERROR, "Bad class name in the catch statement");
-               }
+               CG(zend_lineno) = catch_ast->lineno;
 
-               opnum_catch = get_next_op_number(CG(active_op_array));
-               if (i == 0) {
-                       CG(active_op_array)->try_catch_array[try_catch_offset].catch_op = opnum_catch;
-               }
+               for (j = 0; j < classes->children; j++) {
 
-               CG(zend_lineno) = catch_ast->lineno;
+                       zend_ast *class_ast = classes->child[j];
+                       zend_bool is_last_class = (j + 1 == classes->children);
 
-               opline = get_next_op(CG(active_op_array));
-               opline->opcode = ZEND_CATCH;
-               opline->op1_type = IS_CONST;
-               opline->op1.constant = zend_add_class_name_literal(CG(active_op_array),
-                       zend_resolve_class_name_ast(class_ast));
+                       if (!zend_is_const_default_class_ref(class_ast)) {
+                               zend_error_noreturn(E_COMPILE_ERROR, "Bad class name in the catch statement");
+                       }
 
-               opline->op2_type = IS_CV;
-               opline->op2.var = lookup_cv(CG(active_op_array), zend_string_copy(Z_STR_P(var_name)));
-               opline->result.num = is_last_catch;
+                       opnum_catch = get_next_op_number(CG(active_op_array));
+                       if (i == 0 && j == 0) {
+                               CG(active_op_array)->try_catch_array[try_catch_offset].catch_op = opnum_catch;
+                       }
+
+                       opline = get_next_op(CG(active_op_array));
+                       opline->opcode = ZEND_CATCH;
+                       opline->op1_type = IS_CONST;
+                       opline->op1.constant = zend_add_class_name_literal(CG(active_op_array),
+                                       zend_resolve_class_name_ast(class_ast));
+
+                       opline->op2_type = IS_CV;
+                       opline->op2.var = lookup_cv(CG(active_op_array), zend_string_copy(Z_STR_P(var_name)));
+
+                       opline->result.num = is_last_catch && is_last_class;
+
+                       if (!is_last_class) {
+                               jmp_multicatch[j] = zend_emit_jump(0);
+                               opline->extended_value = get_next_op_number(CG(active_op_array));
+                       }
+               }
+
+               for (j = 0; j < classes->children - 1; j++) {
+                       zend_update_jump_target_to_next(jmp_multicatch[j]);
+               }
+
+               efree(jmp_multicatch);
 
                zend_compile_stmt(stmt_ast);
 
index 53b2f3f50bea1e96408936610c8f53fb8ebe7543..4722846ce7f5bf5a42b54d7835156a07f135e2f1 100644 (file)
@@ -245,7 +245,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_list parameter_list class_statement_list
+%type <ast> echo_expr_list unset_variables catch_name_list catch_list 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 name_list trait_adaptations method_body non_empty_for_exprs
@@ -456,10 +456,15 @@ statement:
 catch_list:
                /* empty */
                        { $$ = zend_ast_create_list(0, ZEND_AST_CATCH_LIST); }
-       |       catch_list T_CATCH '(' name T_VARIABLE ')' '{' inner_statement_list '}'
+       |       catch_list T_CATCH '(' catch_name_list T_VARIABLE ')' '{' inner_statement_list '}'
                        { $$ = zend_ast_list_add($1, zend_ast_create(ZEND_AST_CATCH, $4, $5, $8)); }
 ;
 
+catch_name_list:
+               name { $$ = zend_ast_create_list(1, ZEND_AST_NAME_LIST, $1); }
+       |       catch_name_list '|' name { $$ = zend_ast_list_add($1, $3); }
+;
+
 finally_statement:
                /* empty */ { $$ = NULL; }
        |       T_FINALLY '{' inner_statement_list '}' { $$ = $3; }