From 0aed2cc2a440e7be17552cc669d71fdd24d1204a Mon Sep 17 00:00:00 2001 From: Pierrick Charron Date: Sun, 1 May 2016 18:47:08 -0400 Subject: [PATCH] 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) { // ... } --- Zend/tests/try/exceptions.inc | 5 +++ Zend/tests/try/try_multicatch_001.phpt | 19 +++++++++ Zend/tests/try/try_multicatch_002.phpt | 21 ++++++++++ Zend/tests/try/try_multicatch_003.phpt | 21 ++++++++++ Zend/tests/try/try_multicatch_004.phpt | 21 ++++++++++ Zend/tests/try/try_multicatch_005.phpt | 25 ++++++++++++ Zend/zend_ast.c | 9 +++-- Zend/zend_compile.c | 55 +++++++++++++++++--------- Zend/zend_language_parser.y | 9 ++++- 9 files changed, 162 insertions(+), 23 deletions(-) create mode 100644 Zend/tests/try/exceptions.inc create mode 100644 Zend/tests/try/try_multicatch_001.phpt create mode 100644 Zend/tests/try/try_multicatch_002.phpt create mode 100644 Zend/tests/try/try_multicatch_003.phpt create mode 100644 Zend/tests/try/try_multicatch_004.phpt create mode 100644 Zend/tests/try/try_multicatch_005.phpt diff --git a/Zend/tests/try/exceptions.inc b/Zend/tests/try/exceptions.inc new file mode 100644 index 0000000000..8a8777914c --- /dev/null +++ b/Zend/tests/try/exceptions.inc @@ -0,0 +1,5 @@ + +--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 index 0000000000..0e70fec7eb --- /dev/null +++ b/Zend/tests/try/try_multicatch_002.phpt @@ -0,0 +1,21 @@ +--TEST-- +Catch first exception in the multicatch +--FILE-- + +--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 index 0000000000..6aed1a2b09 --- /dev/null +++ b/Zend/tests/try/try_multicatch_003.phpt @@ -0,0 +1,21 @@ +--TEST-- +Catch second exception in the multicatch +--FILE-- + +--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 index 0000000000..d8b245a767 --- /dev/null +++ b/Zend/tests/try/try_multicatch_004.phpt @@ -0,0 +1,21 @@ +--TEST-- +Catch last exception in the multicatch +--FILE-- + +--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 index 0000000000..cc3fc890fa --- /dev/null +++ b/Zend/tests/try/try_multicatch_005.phpt @@ -0,0 +1,25 @@ +--TEST-- +Catch exception in the nested multicatch +--FILE-- + +--EXPECT-- +TRY +Exception3 +FINALLY diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index eee4a44e86..e7c6111919 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -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"); diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 23781ba423..3adcc55f4e 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -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); diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index 53b2f3f50b..4722846ce7 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -245,7 +245,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %type encaps_var encaps_var_offset isset_variables %type top_statement_list use_declarations const_list inner_statement_list if_stmt %type alt_if_stmt for_exprs switch_case_list global_var_list static_var_list -%type echo_expr_list unset_variables catch_list parameter_list class_statement_list +%type echo_expr_list unset_variables catch_name_list catch_list parameter_list class_statement_list %type implements_list case_list if_stmt_without_else %type non_empty_parameter_list argument_list non_empty_argument_list property_list %type 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; } -- 2.50.1