Skip to content

Commit 0aed2cc

Browse files
committed
Allow catching multiple exception types in a single catch statement
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) { // ... }
1 parent 770a6d1 commit 0aed2cc

File tree

9 files changed

+162
-23
lines changed

9 files changed

+162
-23
lines changed

Zend/tests/try/exceptions.inc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?php
2+
3+
class Exception1 extends Exception {}
4+
class Exception2 extends Exception {}
5+
class Exception3 extends Exception {}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
--TEST--
2+
Parsing test
3+
--FILE--
4+
<?php
5+
6+
require_once __DIR__ . '/exceptions.inc';
7+
8+
try {
9+
echo 'TRY' . PHP_EOL;
10+
} catch(Exception1 | Exception2 $e) {
11+
echo 'Exception';
12+
} finally {
13+
echo 'FINALLY' . PHP_EOL;
14+
}
15+
16+
?>
17+
--EXPECT--
18+
TRY
19+
FINALLY
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
--TEST--
2+
Catch first exception in the multicatch
3+
--FILE--
4+
<?php
5+
6+
require_once __DIR__ . '/exceptions.inc';
7+
8+
try {
9+
echo 'TRY' . PHP_EOL;
10+
throw new Exception1;
11+
} catch(Exception1 | Exception2 | Exception3 $e) {
12+
echo get_class($e) . PHP_EOL;
13+
} finally {
14+
echo 'FINALLY' . PHP_EOL;
15+
}
16+
17+
?>
18+
--EXPECT--
19+
TRY
20+
Exception1
21+
FINALLY
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
--TEST--
2+
Catch second exception in the multicatch
3+
--FILE--
4+
<?php
5+
6+
require_once __DIR__ . '/exceptions.inc';
7+
8+
try {
9+
echo 'TRY' . PHP_EOL;
10+
throw new Exception2;
11+
} catch(Exception1 | Exception2 | Exception3 $e) {
12+
echo get_class($e) . PHP_EOL;
13+
} finally {
14+
echo 'FINALLY' . PHP_EOL;
15+
}
16+
17+
?>
18+
--EXPECT--
19+
TRY
20+
Exception2
21+
FINALLY
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
--TEST--
2+
Catch last exception in the multicatch
3+
--FILE--
4+
<?php
5+
6+
require_once __DIR__ . '/exceptions.inc';
7+
8+
try {
9+
echo 'TRY' . PHP_EOL;
10+
throw new Exception3;
11+
} catch(Exception1 | Exception2 | Exception3 $e) {
12+
echo get_class($e) . PHP_EOL;
13+
} finally {
14+
echo 'FINALLY' . PHP_EOL;
15+
}
16+
17+
?>
18+
--EXPECT--
19+
TRY
20+
Exception3
21+
FINALLY
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
--TEST--
2+
Catch exception in the nested multicatch
3+
--FILE--
4+
<?php
5+
6+
require_once __DIR__ . '/exceptions.inc';
7+
8+
try {
9+
try {
10+
echo 'TRY' . PHP_EOL;
11+
throw new Exception3;
12+
} catch (Exception1 | Exception3 $e) {
13+
echo get_class($e) . PHP_EOL;
14+
}
15+
} catch(Exception2 | Exception3 $e) {
16+
echo 'Should never be executed';
17+
} finally {
18+
echo 'FINALLY' . PHP_EOL;
19+
}
20+
21+
?>
22+
--EXPECT--
23+
TRY
24+
Exception3
25+
FINALLY

Zend/zend_ast.c

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -776,19 +776,22 @@ static void zend_ast_export_encaps_list(smart_str *str, char quote, zend_ast_lis
776776
}
777777
}
778778

779-
static void zend_ast_export_name_list(smart_str *str, zend_ast_list *list, int indent)
779+
static void zend_ast_export_name_list_ex(smart_str *str, zend_ast_list *list, int indent, const char *separator)
780780
{
781781
uint32_t i = 0;
782782

783783
while (i < list->children) {
784784
if (i != 0) {
785-
smart_str_appends(str, ", ");
785+
smart_str_appends(str, separator);
786786
}
787787
zend_ast_export_name(str, list->child[i], 0, indent);
788788
i++;
789789
}
790790
}
791791

792+
#define zend_ast_export_name_list(s, l, i) zend_ast_export_name_list_ex(s, l, i, ", ")
793+
#define zend_ast_export_catch_name_list(s, l, i) zend_ast_export_name_list_ex(s, l, i, "|")
794+
792795
static void zend_ast_export_var_list(smart_str *str, zend_ast_list *list, int indent)
793796
{
794797
uint32_t i = 0;
@@ -1584,7 +1587,7 @@ static void zend_ast_export_ex(smart_str *str, zend_ast *ast, int priority, int
15841587
break;
15851588
case ZEND_AST_CATCH:
15861589
smart_str_appends(str, "} catch (");
1587-
zend_ast_export_ns_name(str, ast->child[0], 0, indent);
1590+
zend_ast_export_catch_name_list(str, ast->child[0], indent);
15881591
smart_str_appends(str, " $");
15891592
zend_ast_export_var(str, ast->child[1], 0, indent);
15901593
smart_str_appends(str, ") {\n");

Zend/zend_compile.c

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4542,7 +4542,7 @@ void zend_compile_try(zend_ast *ast) /* {{{ */
45424542
zend_ast_list *catches = zend_ast_get_list(ast->child[1]);
45434543
zend_ast *finally_ast = ast->child[2];
45444544

4545-
uint32_t i;
4545+
uint32_t i, j;
45464546
zend_op *opline;
45474547
uint32_t try_catch_offset;
45484548
uint32_t *jmp_opnums = safe_emalloc(sizeof(uint32_t), catches->children, 0);
@@ -4587,34 +4587,53 @@ void zend_compile_try(zend_ast *ast) /* {{{ */
45874587

45884588
for (i = 0; i < catches->children; ++i) {
45894589
zend_ast *catch_ast = catches->child[i];
4590-
zend_ast *class_ast = catch_ast->child[0];
4590+
zend_ast_list *classes = zend_ast_get_list(catch_ast->child[0]);
45914591
zend_ast *var_ast = catch_ast->child[1];
45924592
zend_ast *stmt_ast = catch_ast->child[2];
45934593
zval *var_name = zend_ast_get_zval(var_ast);
45944594
zend_bool is_last_catch = (i + 1 == catches->children);
45954595

4596+
uint32_t *jmp_multicatch = safe_emalloc(sizeof(uint32_t), classes->children - 1, 0);
45964597
uint32_t opnum_catch;
45974598

4598-
if (!zend_is_const_default_class_ref(class_ast)) {
4599-
zend_error_noreturn(E_COMPILE_ERROR, "Bad class name in the catch statement");
4600-
}
4599+
CG(zend_lineno) = catch_ast->lineno;
46014600

4602-
opnum_catch = get_next_op_number(CG(active_op_array));
4603-
if (i == 0) {
4604-
CG(active_op_array)->try_catch_array[try_catch_offset].catch_op = opnum_catch;
4605-
}
4601+
for (j = 0; j < classes->children; j++) {
46064602

4607-
CG(zend_lineno) = catch_ast->lineno;
4603+
zend_ast *class_ast = classes->child[j];
4604+
zend_bool is_last_class = (j + 1 == classes->children);
46084605

4609-
opline = get_next_op(CG(active_op_array));
4610-
opline->opcode = ZEND_CATCH;
4611-
opline->op1_type = IS_CONST;
4612-
opline->op1.constant = zend_add_class_name_literal(CG(active_op_array),
4613-
zend_resolve_class_name_ast(class_ast));
4606+
if (!zend_is_const_default_class_ref(class_ast)) {
4607+
zend_error_noreturn(E_COMPILE_ERROR, "Bad class name in the catch statement");
4608+
}
46144609

4615-
opline->op2_type = IS_CV;
4616-
opline->op2.var = lookup_cv(CG(active_op_array), zend_string_copy(Z_STR_P(var_name)));
4617-
opline->result.num = is_last_catch;
4610+
opnum_catch = get_next_op_number(CG(active_op_array));
4611+
if (i == 0 && j == 0) {
4612+
CG(active_op_array)->try_catch_array[try_catch_offset].catch_op = opnum_catch;
4613+
}
4614+
4615+
opline = get_next_op(CG(active_op_array));
4616+
opline->opcode = ZEND_CATCH;
4617+
opline->op1_type = IS_CONST;
4618+
opline->op1.constant = zend_add_class_name_literal(CG(active_op_array),
4619+
zend_resolve_class_name_ast(class_ast));
4620+
4621+
opline->op2_type = IS_CV;
4622+
opline->op2.var = lookup_cv(CG(active_op_array), zend_string_copy(Z_STR_P(var_name)));
4623+
4624+
opline->result.num = is_last_catch && is_last_class;
4625+
4626+
if (!is_last_class) {
4627+
jmp_multicatch[j] = zend_emit_jump(0);
4628+
opline->extended_value = get_next_op_number(CG(active_op_array));
4629+
}
4630+
}
4631+
4632+
for (j = 0; j < classes->children - 1; j++) {
4633+
zend_update_jump_target_to_next(jmp_multicatch[j]);
4634+
}
4635+
4636+
efree(jmp_multicatch);
46184637

46194638
zend_compile_stmt(stmt_ast);
46204639

Zend/zend_language_parser.y

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
245245
%type <ast> encaps_var encaps_var_offset isset_variables
246246
%type <ast> top_statement_list use_declarations const_list inner_statement_list if_stmt
247247
%type <ast> alt_if_stmt for_exprs switch_case_list global_var_list static_var_list
248-
%type <ast> echo_expr_list unset_variables catch_list parameter_list class_statement_list
248+
%type <ast> echo_expr_list unset_variables catch_name_list catch_list parameter_list class_statement_list
249249
%type <ast> implements_list case_list if_stmt_without_else
250250
%type <ast> non_empty_parameter_list argument_list non_empty_argument_list property_list
251251
%type <ast> class_const_list class_const_decl name_list trait_adaptations method_body non_empty_for_exprs
@@ -456,10 +456,15 @@ statement:
456456
catch_list:
457457
/* empty */
458458
{ $$ = zend_ast_create_list(0, ZEND_AST_CATCH_LIST); }
459-
| catch_list T_CATCH '(' name T_VARIABLE ')' '{' inner_statement_list '}'
459+
| catch_list T_CATCH '(' catch_name_list T_VARIABLE ')' '{' inner_statement_list '}'
460460
{ $$ = zend_ast_list_add($1, zend_ast_create(ZEND_AST_CATCH, $4, $5, $8)); }
461461
;
462462

463+
catch_name_list:
464+
name { $$ = zend_ast_create_list(1, ZEND_AST_NAME_LIST, $1); }
465+
| catch_name_list '|' name { $$ = zend_ast_list_add($1, $3); }
466+
;
467+
463468
finally_statement:
464469
/* empty */ { $$ = NULL; }
465470
| T_FINALLY '{' inner_statement_list '}' { $$ = $3; }

0 commit comments

Comments
 (0)