From 4b3906980ac60781cddae5a787bf2908dd0920b7 Mon Sep 17 00:00:00 2001 From: Jakub Jelinek Date: Wed, 28 Sep 2016 21:21:47 +0200 Subject: [PATCH] re PR c++/77467 (Segmentation fault with switch statement in constexpr function) PR c++/77467 * constexpr.c (enum constexpr_switch_state): New. (struct constexpr_ctx): Add css_state field. (label_matches): Add CTX and STMT arguments, remove I and DEFAULT_LABEL. For CASE_LABEL_EXPR assert ctx->css_state != NULL, handle default labels according to css_state. (cxx_eval_statement_list): Remove statement skipping, label_matches and default_label handling code. (cxx_eval_loop_expr): Exit after first iteration even if switches (jump_target). (cxx_eval_switch_expr): Set up css_state field in ctx, if default label has been seen in the body, but no cases matched, evaluate the body second time. (cxx_eval_constant_expression): Handle stmt skipping and label_matches here. Handle PREDICT_EXPR. For MODIFY_EXPR or INIT_EXPR, assert statement is not skipped. For COND_EXPR during skipping, don't evaluate condition, just the then block and if still skipping at the end also the else block. (cxx_eval_outermost_constant_expr): Adjust constexpr_ctx initializer. (is_sub_constant_expr): Likewise. * g++.dg/cpp1y/constexpr-77467.C: New test. From-SVN: r240591 --- gcc/cp/ChangeLog | 23 ++++ gcc/cp/constexpr.c | 116 ++++++++++++----- gcc/testsuite/ChangeLog | 5 + gcc/testsuite/g++.dg/cpp1y/constexpr-77467.C | 128 +++++++++++++++++++ 4 files changed, 243 insertions(+), 29 deletions(-) create mode 100644 gcc/testsuite/g++.dg/cpp1y/constexpr-77467.C diff --git a/gcc/cp/ChangeLog b/gcc/cp/ChangeLog index 1a1f186e726..0e18eb7fd17 100644 --- a/gcc/cp/ChangeLog +++ b/gcc/cp/ChangeLog @@ -1,3 +1,26 @@ +2016-09-28 Jakub Jelinek + + PR c++/77467 + * constexpr.c (enum constexpr_switch_state): New. + (struct constexpr_ctx): Add css_state field. + (label_matches): Add CTX and STMT arguments, remove I and + DEFAULT_LABEL. For CASE_LABEL_EXPR assert ctx->css_state != NULL, + handle default labels according to css_state. + (cxx_eval_statement_list): Remove statement skipping, label_matches + and default_label handling code. + (cxx_eval_loop_expr): Exit after first iteration even if + switches (jump_target). + (cxx_eval_switch_expr): Set up css_state field in ctx, if default + label has been seen in the body, but no cases matched, evaluate + the body second time. + (cxx_eval_constant_expression): Handle stmt skipping and label_matches + here. Handle PREDICT_EXPR. For MODIFY_EXPR or INIT_EXPR, assert + statement is not skipped. For COND_EXPR during skipping, don't + evaluate condition, just the then block and if still skipping at the + end also the else block. + (cxx_eval_outermost_constant_expr): Adjust constexpr_ctx initializer. + (is_sub_constant_expr): Likewise. + 2016-09-27 Jakub Jelinek Implement P0018R3, C++17 lambda capture of *this by value as [=,*this] diff --git a/gcc/cp/constexpr.c b/gcc/cp/constexpr.c index bd4068e84c5..2db13d2e187 100644 --- a/gcc/cp/constexpr.c +++ b/gcc/cp/constexpr.c @@ -900,6 +900,18 @@ struct constexpr_call_hasher : ggc_ptr_hash static bool equal (constexpr_call *, constexpr_call *); }; +enum constexpr_switch_state { + /* Used when processing a switch for the first time by cxx_eval_switch_expr + and default: label for that switch has not been seen yet. */ + css_default_not_seen, + /* Used when processing a switch for the first time by cxx_eval_switch_expr + and default: label for that switch has been seen already. */ + css_default_seen, + /* Used when processing a switch for the second time by + cxx_eval_switch_expr, where default: label should match. */ + css_default_processing +}; + /* The constexpr expansion context. CALL is the current function expansion, CTOR is the current aggregate initializer, OBJECT is the object being initialized by CTOR, either a VAR_DECL or a _REF. VALUES @@ -919,6 +931,8 @@ struct constexpr_ctx { tree ctor; /* The object we're building the CONSTRUCTOR for. */ tree object; + /* If inside SWITCH_EXPR. */ + constexpr_switch_state *css_state; /* Whether we should error on a non-constant expression or fail quietly. */ bool quiet; /* Whether we are strictly conforming to constant expression rules or @@ -3484,14 +3498,12 @@ switches (tree *jump_target) } /* Subroutine of cxx_eval_statement_list. Determine whether the statement - at I matches *jump_target. If we're looking for a case label and we see - the default label, copy I into DEFAULT_LABEL. */ + STMT matches *jump_target. If we're looking for a case label and we see + the default label, note it in ctx->css_state. */ static bool -label_matches (tree *jump_target, tree_stmt_iterator i, - tree_stmt_iterator& default_label) +label_matches (const constexpr_ctx *ctx, tree *jump_target, tree stmt) { - tree stmt = tsi_stmt (i); switch (TREE_CODE (*jump_target)) { case LABEL_DECL: @@ -3503,8 +3515,18 @@ label_matches (tree *jump_target, tree_stmt_iterator i, case INTEGER_CST: if (TREE_CODE (stmt) == CASE_LABEL_EXPR) { + gcc_assert (ctx->css_state != NULL); if (!CASE_LOW (stmt)) - default_label = i; + { + /* default: should appear just once in a SWITCH_EXPR + body (excluding nested SWITCH_EXPR). */ + gcc_assert (*ctx->css_state != css_default_seen); + /* When evaluating SWITCH_EXPR body for the second time, + return true for the default: label. */ + if (*ctx->css_state == css_default_processing) + return true; + *ctx->css_state = css_default_seen; + } else if (CASE_HIGH (stmt)) { if (tree_int_cst_le (CASE_LOW (stmt), *jump_target) @@ -3531,7 +3553,6 @@ cxx_eval_statement_list (const constexpr_ctx *ctx, tree t, tree *jump_target) { tree_stmt_iterator i; - tree_stmt_iterator default_label = tree_stmt_iterator(); tree local_target; /* In a statement-expression we want to return the last value. */ tree r = NULL_TREE; @@ -3542,18 +3563,7 @@ cxx_eval_statement_list (const constexpr_ctx *ctx, tree t, } for (i = tsi_start (t); !tsi_end_p (i); tsi_next (&i)) { - reenter: tree stmt = tsi_stmt (i); - if (*jump_target) - { - if (TREE_CODE (stmt) == STATEMENT_LIST) - /* The label we want might be inside. */; - else if (label_matches (jump_target, i, default_label)) - /* Found it. */ - *jump_target = NULL_TREE; - else - continue; - } r = cxx_eval_constant_expression (ctx, stmt, false, non_constant_p, overflow_p, jump_target); @@ -3562,12 +3572,6 @@ cxx_eval_statement_list (const constexpr_ctx *ctx, tree t, if (returns (jump_target) || breaks (jump_target)) break; } - if (switches (jump_target) && !tsi_end_p (default_label)) - { - i = default_label; - *jump_target = NULL_TREE; - goto reenter; - } return r; } @@ -3606,7 +3610,10 @@ cxx_eval_loop_expr (const constexpr_ctx *ctx, tree t, break; } } - while (!returns (jump_target) && !breaks (jump_target) && !*non_constant_p); + while (!returns (jump_target) + && !breaks (jump_target) + && !switches (jump_target) + && !*non_constant_p); if (breaks (jump_target)) *jump_target = NULL_TREE; @@ -3629,8 +3636,20 @@ cxx_eval_switch_expr (const constexpr_ctx *ctx, tree t, *jump_target = cond; tree body = TREE_OPERAND (t, 1); - cxx_eval_statement_list (ctx, body, - non_constant_p, overflow_p, jump_target); + constexpr_ctx new_ctx = *ctx; + constexpr_switch_state css = css_default_not_seen; + new_ctx.css_state = &css; + cxx_eval_constant_expression (&new_ctx, body, false, + non_constant_p, overflow_p, jump_target); + if (switches (jump_target) && css == css_default_seen) + { + /* If the SWITCH_EXPR body has default: label, process it once again, + this time instructing label_matches to return true for default: + label on switches (jump_target). */ + css = css_default_processing; + cxx_eval_constant_expression (&new_ctx, body, false, + non_constant_p, overflow_p, jump_target); + } if (breaks (jump_target) || switches (jump_target)) *jump_target = NULL_TREE; return NULL_TREE; @@ -3650,6 +3669,27 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t, constexpr_ctx new_ctx; tree r = t; + if (jump_target && *jump_target) + { + /* If we are jumping, ignore all statements/expressions except those + that could have LABEL_EXPR or CASE_LABEL_EXPR in their bodies. */ + switch (TREE_CODE (t)) + { + case BIND_EXPR: + case STATEMENT_LIST: + case LOOP_EXPR: + case COND_EXPR: + break; + case LABEL_EXPR: + case CASE_LABEL_EXPR: + if (label_matches (ctx, jump_target, t)) + /* Found it. */ + *jump_target = NULL_TREE; + return NULL_TREE; + default: + return NULL_TREE; + } + } if (t == error_mark_node) { *non_constant_p = true; @@ -3730,6 +3770,7 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t, case LABEL_DECL: case LABEL_EXPR: case CASE_LABEL_EXPR: + case PREDICT_EXPR: return t; case PARM_DECL: @@ -3835,6 +3876,7 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t, case INIT_EXPR: case MODIFY_EXPR: + gcc_assert (jump_target == NULL || *jump_target == NULL_TREE); r = cxx_eval_store_expression (ctx, t, lval, non_constant_p, overflow_p); break; @@ -4065,6 +4107,22 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t, break; case COND_EXPR: + if (jump_target && *jump_target) + { + /* When jumping to a label, the label might be either in the + then or else blocks, so process then block first in skipping + mode first, and if we are still in the skipping mode at its end, + process the else block too. */ + r = cxx_eval_constant_expression (ctx, TREE_OPERAND (t, 1), + lval, non_constant_p, overflow_p, + jump_target); + if (*jump_target) + r = cxx_eval_constant_expression (ctx, TREE_OPERAND (t, 2), + lval, non_constant_p, overflow_p, + jump_target); + break; + } + /* FALLTHRU */ case VEC_COND_EXPR: r = cxx_eval_conditional_expression (ctx, t, lval, non_constant_p, overflow_p, @@ -4340,7 +4398,7 @@ cxx_eval_outermost_constant_expr (tree t, bool allow_non_constant, bool overflow_p = false; hash_map map; - constexpr_ctx ctx = { NULL, &map, NULL, NULL, NULL, + constexpr_ctx ctx = { NULL, &map, NULL, NULL, NULL, NULL, allow_non_constant, strict }; tree type = initialized_type (t); @@ -4460,7 +4518,7 @@ is_sub_constant_expr (tree t) bool overflow_p = false; hash_map map; - constexpr_ctx ctx = { NULL, &map, NULL, NULL, NULL, true, true }; + constexpr_ctx ctx = { NULL, &map, NULL, NULL, NULL, NULL, true, true }; cxx_eval_constant_expression (&ctx, t, false, &non_constant_p, &overflow_p); diff --git a/gcc/testsuite/ChangeLog b/gcc/testsuite/ChangeLog index ee788ee2082..22a001b1445 100644 --- a/gcc/testsuite/ChangeLog +++ b/gcc/testsuite/ChangeLog @@ -1,3 +1,8 @@ +2016-09-28 Jakub Jelinek + + PR c++/77467 + * g++.dg/cpp1y/constexpr-77467.C: New test. + 2016-09-28 Martin Sebor PR c/77762 diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-77467.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-77467.C new file mode 100644 index 00000000000..fd94e78a03b --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-77467.C @@ -0,0 +1,128 @@ +// PR c++/77467 +// { dg-do compile { target c++14 } } + +constexpr int +foo (const int x, const unsigned n) noexcept +{ + switch (n) + { + case 0: + return 1; + case 1: + return x; + default: + const auto m = (n >> 1); + const auto y = foo (x, m); + return ((m << 1) == n) ? y * y : x * y * y; + } +} + +static_assert (foo (3, 2) == 9, ""); +static_assert (foo (2, 3) == 8, ""); + +constexpr int +bar (int x) +{ + int a = x; + switch (x) + a = x + 1; + return a; +} + +static_assert (bar (0) == 0, ""); +static_assert (bar (1) == 1, ""); + +constexpr int +baz (const int x, int y) noexcept +{ + switch (x) + { + case 0: + return 1; + case 1: + return x; + case 2: + if ((y += 2) == 0) + { + case 3: + y += 4; + break; + } + else + { + case 4: + y += 8; + break; + } + break; + case 5: + for (y = 0; y < 3; y++) + { + case 7: + if (y == -4) + y += 3; + if (y == -3) + continue; + if (y == -2) + { + y += 18; + break; + } + if (y == 2) + { + case 6: + y += 12; + default: + y++; + break; + } + } + break; + case -1: + case -2: + switch (y) + { + case 19: + y += 2; + break; + case 20: + y += 3; + if (x == 2) + case 21:; + y += 2; + if (x == 3) + default:; + y += 4; + break; + } + return x + y + 1; + } + return x + y; +} + +static_assert (baz (0, 7) == 1, ""); +static_assert (baz (1, 7) == 1, ""); +static_assert (baz (2, -2) == 6, ""); +static_assert (baz (2, 0) == 12, ""); +static_assert (baz (3, 1) == 8, ""); +static_assert (baz (4, 2) == 14, ""); +static_assert (baz (5, -20) == 20, ""); +static_assert (baz (6, 5) == 24, ""); +static_assert (baz (7, -5) == 22, ""); +static_assert (baz (7, -4) == 22, ""); +static_assert (baz (7, -3) == 23, ""); +static_assert (baz (7, -2) == 23, ""); +static_assert (baz (7, -1) == 22, ""); +static_assert (baz (7, 0) == 22, ""); +static_assert (baz (7, 2) == 22, ""); +static_assert (baz (7, 6) == 14, ""); +static_assert (baz (8, 9) == 18, ""); +static_assert (baz (8, -2) == 7, ""); +static_assert (baz (-1, 19) == 21, ""); +static_assert (baz (-1, 20) == 29, ""); +static_assert (baz (-1, 21) == 27, ""); +static_assert (baz (-1, 5) == 9, ""); +static_assert (baz (-2, 19) == 20, ""); +static_assert (baz (-2, 20) == 28, ""); +static_assert (baz (-2, 21) == 26, ""); +static_assert (baz (-2, 5) == 8, ""); -- 2.30.2