diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h index 3a956717ab6..9869e28a7c2 100644 --- a/Zend/zend_ast.h +++ b/Zend/zend_ast.h @@ -84,6 +84,9 @@ enum _zend_ast_kind { ZEND_AST_REF, ZEND_AST_IF, ZEND_AST_IF_ELEM, + ZEND_AST_SWITCH, + ZEND_AST_SWITCH_LIST, + ZEND_AST_SWITCH_CASE, }; typedef unsigned short zend_ast_kind; diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 8de27b4430b..aab3f7ebd86 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -7316,6 +7316,89 @@ void zend_compile_if(zend_ast *ast TSRMLS_DC) { efree(jmp_opnums); } +void zend_compile_switch(zend_ast *ast TSRMLS_DC) { + zend_ast *expr_ast = ast->child[0]; + zend_ast *cases_ast = ast->child[1]; + + zend_uint i; + zend_bool has_default_case = 0; + zend_switch_entry switch_entry; + + znode expr_node, case_node; + zend_op *opline; + zend_uint *jmpnz_opnums = safe_emalloc(sizeof(zend_uint), cases_ast->children, 0); + zend_uint opnum_default_jmp; + + zend_compile_expr(&expr_node, expr_ast TSRMLS_CC); + + switch_entry.cond = expr_node; + switch_entry.default_case = -1; + switch_entry.control_var = -1; + zend_stack_push(&CG(switch_cond_stack), (void *) &switch_entry); + + do_begin_loop(TSRMLS_C); + + case_node.op_type = IS_TMP_VAR; + case_node.u.op.var = get_temporary_variable(CG(active_op_array)); + + for (i = 0; i < cases_ast->children; ++i) { + zend_ast *case_ast = cases_ast->child[i]; + zend_ast *cond_ast = case_ast->child[0]; + znode cond_node; + + if (!cond_ast) { + has_default_case = 1; + continue; + } + + zend_compile_expr(&cond_node, cond_ast TSRMLS_CC); + + opline = emit_op(NULL, ZEND_CASE, &expr_node, &cond_node TSRMLS_CC); + SET_NODE(opline->result, &case_node); + if (opline->op1_type == IS_CONST) { + zval_copy_ctor(&CONSTANT(opline->op1.constant)); + } + + jmpnz_opnums[i] = get_next_op_number(CG(active_op_array)); + emit_op(NULL, ZEND_JMPNZ, &case_node, NULL TSRMLS_CC); + } + + opnum_default_jmp = get_next_op_number(CG(active_op_array)); + emit_op(NULL, ZEND_JMP, NULL, NULL TSRMLS_CC); + + for (i = 0; i < cases_ast->children; ++i) { + zend_ast *case_ast = cases_ast->child[i]; + zend_ast *cond_ast = case_ast->child[0]; + zend_ast *stmt_ast = case_ast->child[1]; + + if (cond_ast) { + opline = &CG(active_op_array)->opcodes[jmpnz_opnums[i]]; + opline->op2.opline_num = get_next_op_number(CG(active_op_array)); + } else { + opline = &CG(active_op_array)->opcodes[opnum_default_jmp]; + opline->op1.opline_num = get_next_op_number(CG(active_op_array)); + } + + zend_compile_stmt(stmt_ast TSRMLS_CC); + } + + if (!has_default_case) { + opline = &CG(active_op_array)->opcodes[opnum_default_jmp]; + opline->op1.opline_num = get_next_op_number(CG(active_op_array)); + } + + do_end_loop(get_next_op_number(CG(active_op_array)), 1 TSRMLS_CC); + + if (expr_node.op_type == IS_VAR || expr_node.op_type == IS_TMP_VAR) { + emit_op(NULL, expr_node.op_type == IS_TMP_VAR ? ZEND_FREE : ZEND_SWITCH_FREE, + &expr_node, NULL TSRMLS_CC); + } else if (expr_node.op_type == IS_CONST) { + zval_dtor(&expr_node.u.constant); + } + + zend_stack_del_top(&CG(switch_cond_stack)); +} + void zend_compile_stmt_list(zend_ast *ast TSRMLS_DC) { zend_uint i; for (i = 0; i < ast->children; ++i) { @@ -8116,6 +8199,9 @@ void zend_compile_stmt(zend_ast *ast TSRMLS_DC) { case ZEND_AST_IF: zend_compile_if(ast TSRMLS_CC); break; + case ZEND_AST_SWITCH: + zend_compile_switch(ast TSRMLS_CC); + break; default: { znode result; diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index eeead1e4145..c832129a7d9 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -334,7 +334,9 @@ unticked_statement: { $$.u.ast = zend_ast_create_binary(ZEND_AST_DO_WHILE, $2.u.ast, $4.u.ast); } | T_FOR '(' for_expr ';' for_expr ';' for_expr ')' for_statement { $$.u.ast = zend_ast_create(4, ZEND_AST_FOR, $3.u.ast, $5.u.ast, $7.u.ast, $9.u.ast); } - | T_SWITCH parenthesis_expr { AC($2); zend_do_switch_cond(&$2 TSRMLS_CC); } switch_case_list { zend_do_switch_end(&$4 TSRMLS_CC); AN($$); } + | T_SWITCH parenthesis_expr switch_case_list + { $$.u.ast = zend_ast_create_binary(ZEND_AST_SWITCH, $2.u.ast, $3.u.ast); } + /*| T_SWITCH parenthesis_expr { AC($2); zend_do_switch_cond(&$2 TSRMLS_CC); } switch_case_list { zend_do_switch_end(&$4 TSRMLS_CC); AN($$); }*/ | T_BREAK ';' { $$.u.ast = zend_ast_create_unary(ZEND_BRK, NULL); } | T_BREAK expr ';' { $$.u.ast = zend_ast_create_unary(ZEND_BRK, $2.u.ast); } | T_CONTINUE ';' { $$.u.ast = zend_ast_create_unary(ZEND_CONT, NULL); } @@ -356,10 +358,6 @@ unticked_statement: foreach_statement { $$.u.ast = zend_ast_create(4, ZEND_AST_FOREACH, $3.u.ast, $7.u.ast, $5.u.ast, $9.u.ast); } - /*| T_FOREACH '(' expr_without_variable T_AS - { AC($3); zend_do_foreach_begin(&$1, &$2, &$3, &$4, 0 TSRMLS_CC); } - foreach_variable foreach_optional_arg ')' { zend_do_foreach_cont(&$1, &$2, &$4, &$6, &$7 TSRMLS_CC); } - foreach_statement { zend_do_foreach_end(&$1, &$4 TSRMLS_CC); AN($$); }*/ | T_DECLARE { $1.u.op.opline_num = get_next_op_number(CG(active_op_array)); zend_do_declare_begin(TSRMLS_C); } '(' declare_list ')' declare_statement { zend_do_declare_end(&$1 TSRMLS_CC); AN($$); } | ';' /* empty statement */ { AN($$); } | T_TRY { zend_do_try(&$1 TSRMLS_CC); } '{' inner_statement_list '}' { AS($4); } @@ -507,17 +505,22 @@ declare_list: switch_case_list: - '{' case_list '}' { $$ = $2; } - | '{' ';' case_list '}' { $$ = $3; } - | ':' case_list T_ENDSWITCH ';' { $$ = $2; } - | ':' ';' case_list T_ENDSWITCH ';' { $$ = $3; } + '{' case_list '}' { $$.u.ast = $2.u.ast; } + | '{' ';' case_list '}' { $$.u.ast = $3.u.ast; } + | ':' case_list T_ENDSWITCH ';' { $$.u.ast = $2.u.ast; } + | ':' ';' case_list T_ENDSWITCH ';' { $$.u.ast = $3.u.ast; } ; case_list: - /* empty */ { $$.op_type = IS_UNUSED; } - | case_list T_CASE expr case_separator { AC($3); zend_do_extended_info(TSRMLS_C); zend_do_case_before_statement(&$1, &$2, &$3 TSRMLS_CC); } inner_statement_list { AS($6); zend_do_case_after_statement(&$$, &$2 TSRMLS_CC); $$.op_type = IS_CONST; } - | case_list T_DEFAULT case_separator { zend_do_extended_info(TSRMLS_C); zend_do_default_before_statement(&$1, &$2 TSRMLS_CC); } inner_statement_list { AS($5); zend_do_case_after_statement(&$$, &$2 TSRMLS_CC); $$.op_type = IS_CONST; } + /* empty */ { $$.u.ast = zend_ast_create_dynamic(ZEND_AST_SWITCH_LIST); } + | case_list T_CASE expr case_separator inner_statement_list + { $$.u.ast = zend_ast_dynamic_add($1.u.ast, + zend_ast_create_binary(ZEND_AST_SWITCH_CASE, $3.u.ast, $5.u.ast)); } + | case_list T_DEFAULT case_separator inner_statement_list + { $$.u.ast = zend_ast_dynamic_add($1.u.ast, + zend_ast_create_binary(ZEND_AST_SWITCH_CASE, NULL, $4.u.ast)); } + /*| case_list T_CASE expr case_separator { AC($3); zend_do_extended_info(TSRMLS_C); zend_do_case_before_statement(&$1, &$2, &$3 TSRMLS_CC); } inner_statement_list { AS($6); zend_do_case_after_statement(&$$, &$2 TSRMLS_CC); $$.op_type = IS_CONST; }*/ ;