On 05/08/2014 11:18 AM, Richard Biener wrote:

I plan to follow the Java rules, with necessary adjustments due to language
differences:

<http://docs.oracle.com/javase/specs/jls/se7/html/jls-14.html#jls-14.21>

They are based on syntax (except for the infinite loop case), so they are
much more predictable from a developer perspective.

There are other warnings which benefit greatly from information collected
during optimization, but unreachable code doesn't.

Then I suggest to implement this warning in the frontends - as surely
you'll have differently modified "Java rules" for different frontends.

Go, Java (through ECJ) and possibly Ada already have such diagnostics anyway.

At the time a frontend calls cgraph_finalize_function () its AST is
supposed to be in GENERIC (+ FE extensions to GENERIC) form
(so for Fortran it's already lowered too much I guess).  So that could
be a (kind-of) common point to implement this for C/C++ at least.

It turns out that in C, all conditional control flow is lowered to COND_EXPR and gotos, too, so I had to hack a prototype into the parser. With hindsight, C++ would have been the better starting point (despite the much more complex language) because the pre-GENERIC trees seem to preserve more control flow statements.

I'm attaching the patch I've been playing with, but I feel that it takes us into the wrong direction, towards more parser hacks and global state.

The patch implements a single-pass analysis over source code constructs, implementing the following rules:

  * Code after an if statement is unreachable if neither branch
    completes normally.  (Compile-time constants are not evaluated, just
    as in Java.)
  * Code after an infinite loop (with a constant condition and no break
    statements) is unreachable.
  * Code after a return statement is unreachable.
  * Code after an Objective-C try/catch/finally statement is unreachable
    if the try and catch branches do not complete normally.
  * Function starts and (case) labels are always unreachable.
  * Loop starts are reachable if the loop contains a label or case
    label.

I compiled some code with this patch and, in addition to real issues in the code being compiled, identified a couple of patterns that need recognizing and fixing for a production-quality implementation (the patch has failing test cases for many of these):

  * noreturn function calls and expressions need warnings (if there
    value is used, but see below for an exception) and trigger
    loss of reachability (if used as a statement expression).
  * Warn about an unreachable conditional expression in do-while,
    with a special case for do-...-while (0).
  * An unreachable dummy return in functions not returning void to
    suppress warnings from other compilers should not receive a warning.
    (A dummy return would have zero constant or null pointer constant
    as argument.  More involved expressions would still trigger
    a warning, and so would returns which aren't the last statement
    in a function.)
  * A return statement containing a call expression targeting a
    noreturn function should not receive a warning (again suppression
    of compiler warnings).
  * Warnings should not be generated for common assert()-style macros
    in unreachable positions.

Expression-related warnings could be implemented on top of the existing trees, I think (but statement expressions might complicate matters). I could add more state tracking for the assert() handling, but it would be so much easier to implement on ASTs with a strong relationship to source code.

I'm not sure how to proceed. Are there plans to move the C and C++ front ends to more concrete ASTs? Would we be willing to accept a slight performance hit from that?

--
Florian Weimer / Red Hat Product Security Team
diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index 7aa9e23..d10c3d0 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -777,6 +777,10 @@ Wunknown-pragmas
 C ObjC C++ ObjC++ Warning Var(warn_unknown_pragmas) LangEnabledBy(C ObjC C++ ObjC++,Wall, 1, 0)
 Warn about unrecognized pragmas
 
+Wunreachable-code
+C ObjC Warning Var(warn_unreachable_code) LangEnabledBy(C ObjC,Wall, 1, 0)
+Warn about unreachable code
+
 Wunsuffixed-float-constants
 C ObjC Var(warn_unsuffixed_float_constants) Warning
 Warn about unsuffixed float constants
diff --git a/gcc/c/c-decl.c b/gcc/c/c-decl.c
index 3abf6b9..907bdef 100644
--- a/gcc/c/c-decl.c
+++ b/gcc/c/c-decl.c
@@ -118,10 +118,6 @@ struct obstack parser_obstack;
 
 static GTY(()) struct stmt_tree_s c_stmt_tree;
 
-/* State saving variables.  */
-tree c_break_label;
-tree c_cont_label;
-
 /* A list of decls to be made automatically visible in each file scope.  */
 static GTY(()) tree visible_builtins;
 
@@ -7869,11 +7865,8 @@ start_function (struct c_declspecs *declspecs, struct c_declarator *declarator,
   current_function_returns_abnormally = 0;
   warn_about_return_type = 0;
   c_switch_stack = NULL;
-
-  /* Indicate no valid break/continue context by setting these variables
-     to some non-null, non-label value.  We'll notice and emit the proper
-     error message in c_finish_bc_stmt.  */
-  c_break_label = c_cont_label = size_zero_node;
+  c_loop_state = loop_state ();
+  c_reachability_state.mark_reachable ();
 
   decl1 = grokdeclarator (declarator, declspecs, FUNCDEF, true, NULL,
 			  &attributes, NULL, NULL, DEPRECATED_NORMAL);
@@ -8804,8 +8797,8 @@ c_push_function_context (void)
 
   p->base.x_stmt_tree = c_stmt_tree;
   c_stmt_tree.x_cur_stmt_list = vec_safe_copy (c_stmt_tree.x_cur_stmt_list);
-  p->x_break_label = c_break_label;
-  p->x_cont_label = c_cont_label;
+  p->x_loop_state = c_loop_state;
+  p->x_reachability_state = c_reachability_state;
   p->x_switch_stack = c_switch_stack;
   p->arg_info = current_function_arg_info;
   p->returns_value = current_function_returns_value;
@@ -8844,8 +8837,8 @@ c_pop_function_context (void)
 
   c_stmt_tree = p->base.x_stmt_tree;
   p->base.x_stmt_tree.x_cur_stmt_list = NULL;
-  c_break_label = p->x_break_label;
-  c_cont_label = p->x_cont_label;
+  c_loop_state = p->x_loop_state;
+  c_reachability_state = p->x_reachability_state;
   c_switch_stack = p->x_switch_stack;
   current_function_arg_info = p->arg_info;
   current_function_returns_value = p->returns_value;
diff --git a/gcc/c/c-lang.c b/gcc/c/c-lang.c
index 97c0443..a9e6d94 100644
--- a/gcc/c/c-lang.c
+++ b/gcc/c/c-lang.c
@@ -32,6 +32,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "diagnostic-core.h"
 #include "c-objc-common.h"
 #include "c-family/c-pragma.h"
+#include "c-lang.h"
 
 enum c_language_kind c_language = clk_c;
 
@@ -48,4 +49,125 @@ enum c_language_kind c_language = clk_c;
 /* Each front end provides its own lang hook initializer.  */
 struct lang_hooks lang_hooks = LANG_HOOKS_INITIALIZER;
 
+reachability_state c_reachability_state;
+
+void
+reachability_state::warn_unreachable(location_t unreachable_location,
+				     const char *description)
+{
+  if (!warn_unreachable_code)
+    return;
+  warning_at (unreachable_location, OPT_Wunreachable_code, "%s", description);
+  switch (state)
+    {
+    case reachable:
+      gcc_unreachable();
+    case after_if:
+      inform (location_of_cause,
+	      "neither branches of %<if%> statement completes normally");
+      break;
+    case after_loop:
+      inform (location_of_cause, "loop does not exit normally");
+      break;
+    case after_switch_start:
+      inform (location_of_cause, "switch statement skips initial code");
+      break;
+    case after_goto:
+      inform (location_of_cause,
+	      "%<goto%> statement redirects control flow");
+      break;
+    case after_break:
+      inform (location_of_cause,
+	      "%<break%> statement redirects control flow");
+      break;
+    case after_continue:
+      inform (location_of_cause,
+	      "%<continue%> statement redirects control flow");
+      break;
+    case after_return:
+      inform (location_of_cause,
+	      "%<return%> statement redirects control flow");
+      break;
+    case after_call:
+      inform (location_of_cause, "no-return function called");
+      break;
+    case after_throw:
+      inform (location_of_cause,
+	      "%<throw%> statement redirects control flow");
+      break;
+    case after_try:
+      inform (location_of_cause,
+	      "no branch in %<try%> statement completes normally");
+      break;
+    default:
+      gcc_unreachable ();
+    }
+  /* Prevent further warnings. */
+  mark_reachable ();
+}
+
+void
+reachability_state::warn_unreachable (location_t unreachable_loc,
+				      enum rid keyword)
+{
+  switch (keyword) {
+  case RID_BREAK:
+    if (c_loop_state.in_switch)
+      return;
+    break;
+  case RID_DO:
+  case RID_FOR:
+  case RID_WHILE:
+    /* Unreachable loops are detected on loop exit. */
+    return;
+  default:
+    /* Fall through. */
+    ;
+  }
+  warn_unreachable (unreachable_loc, "statement is unreachable");
+}
+
+loop_state c_loop_state;
+
+void
+loop_state::enter_switch (loop_state *save)
+{
+  *save = *this;
+  break_label = NULL_TREE;
+  in_switch = true;
+}
+void
+loop_state::enter_loop (loop_state *save)
+{
+  *save = *this;
+  break_label = NULL_TREE;
+  cont_label = NULL_TREE;
+  in_switch = false;
+  at_start = c_reachability_state;
+  c_reachability_state.mark_reachable ();
+}
+
+void
+loop_state::leave_loop (location_t loop_start, const loop_state *save)
+{
+  if (label_seen)
+    {
+      *this = *save;
+      label_seen = true;
+    }
+  else
+    {
+      at_start.warn_if_unreachable
+	(loop_start, "loop statement is unreachable");
+      *this = *save;
+    }
+}
+
+void
+loop_state::leave_switch (const loop_state *save)
+{
+  break_label = save->break_label;
+  in_switch = save->in_switch;
+}
+
 #include "gtype-c.h"
diff --git a/gcc/c/c-lang.h b/gcc/c/c-lang.h
index 7fcf333..8cdbd58 100644
--- a/gcc/c/c-lang.h
+++ b/gcc/c/c-lang.h
@@ -39,14 +39,122 @@ struct GTY((variable_size)) lang_decl {
   char dummy;
 };
 
+/* State of the statement reachability analysis in the parser.  */
+struct reachability_state
+{
+  /* Location of the construct that caused unreachability.  */
+  location_t location_of_cause;
+
+  /* Reason code for unreachability.  */
+  enum state {
+    reachable,
+    after_if,
+    after_loop,
+    after_switch_start,
+    after_goto,
+    after_break,
+    after_continue,
+    after_return,
+    after_call,
+    after_throw,
+    after_try
+  } state;
+
+  reachability_state ()
+  {
+    mark_reachable ();
+  }
+
+  /* Marks the current state as reachable.  */
+  void mark_reachable ()
+  {
+    state = reachable;
+    location_of_cause = UNKNOWN_LOCATION;
+  }
+
+  /* Marks the state as unreachable, recording the reason.  */
+  void mark_unreachable (location_t loc, enum state reason)
+  {
+    state = reason;
+    location_of_cause = loc;
+  }
+
+  /* Returns true if the currrent state is reachable.  */
+  bool reachable_p () const
+  {
+    return state == reachable;
+  }
+
+  void warn_if_unreachable (location_t unreachable_location,
+			    const char *description)
+  {
+    if (!reachable_p ())
+      warn_unreachable (unreachable_location, description);
+  }
+
+  /* Emits a warning if the statement with the keyword is unreachable,
+     unless it is a "break" in a switch.  */
+  void warn_if_unreachable (location_t unreachable_location, enum rid keyword)
+  {
+    if (!reachable_p ())
+      warn_unreachable (unreachable_location, keyword);
+  }
+
+private:
+  void warn_unreachable (location_t, const char *);
+  void warn_unreachable (location_t, enum rid);
+};
+
+/* Loop/switch state in the parser, keeping track of labels used for
+   break/continue statements.  */
+struct loop_state
+{
+  tree break_label;
+  tree cont_label;
+  reachability_state at_start;
+  bool in_switch;
+  bool label_seen;
+
+  /* Indicate no valid break/continue context by setting these variables
+     to some non-null, non-label value.  We'll notice and emit the proper
+     error message in c_finish_bc_stmt.  */
+  loop_state ()
+    : break_label (size_zero_node), cont_label (size_zero_node),
+      in_switch (false), label_seen (false)
+  {
+  }
+
+  /* Enters a loop, resetting the labels, and saves the previous state
+     to *SAVE.  */
+  void enter_loop (loop_state *save);
+
+  /* Enters a switch statement, resetting the break label, and saves
+     the previous state to *SAVE.  */
+  void enter_switch (loop_state *save);
+
+  /* Leave the loop, keeping the labels seen state.  Warn if the loop
+     is unreachable.  */
+  void leave_loop (location_t loop_start, const loop_state *save);
+
+  /* Leave the switch statement, restoring the old in-switch status. */
+  void leave_switch (const loop_state *save);
+};
+
+/* The loop state of the construct currently being parsed.  */
+extern loop_state c_loop_state;
+
+/* Reachability information for the current function being parsed.  */
+extern reachability_state c_reachability_state;
+
+
 /* Save and restore the variables in this file and elsewhere
    that keep track of the progress of compilation of the current function.
    Used for nested functions.  */
 
 struct GTY(()) language_function {
   struct c_language_function base;
-  tree x_break_label;
-  tree x_cont_label;
+  struct loop_state GTY((skip)) x_loop_state;
+  struct reachability_state GTY((skip)) x_reachability_state;
   struct c_switch * GTY((skip)) x_switch_stack;
   struct c_arg_info * GTY((skip)) arg_info;
   int returns_value;
diff --git a/gcc/c/c-parser.c b/gcc/c/c-parser.c
index 6e8f33b..fbe437f 100644
--- a/gcc/c/c-parser.c
+++ b/gcc/c/c-parser.c
@@ -4674,6 +4674,8 @@ c_parser_label (c_parser *parser)
 {
   location_t loc1 = c_parser_peek_token (parser)->location;
   tree label = NULL_TREE;
+  c_reachability_state.mark_reachable ();
+  c_loop_state.label_seen = true;
   if (c_parser_next_token_is_keyword (parser, RID_CASE))
     {
       tree exp1, exp2;
@@ -4869,6 +4871,7 @@ c_parser_statement_after_labels (c_parser *parser)
   location_t loc = c_parser_peek_token (parser)->location;
   tree stmt = NULL_TREE;
   bool in_if_block = parser->in_if_block;
+  enum rid keyword;
   parser->in_if_block = false;
   switch (c_parser_peek_token (parser)->type)
     {
@@ -4876,7 +4879,9 @@ c_parser_statement_after_labels (c_parser *parser)
       add_stmt (c_parser_compound_statement (parser));
       break;
     case CPP_KEYWORD:
-      switch (c_parser_peek_token (parser)->keyword)
+      keyword = c_parser_peek_token (parser)->keyword;
+      c_reachability_state.warn_if_unreachable (loc, keyword);
+      switch (keyword)
 	{
 	case RID_IF:
 	  c_parser_if_statement (parser);
@@ -4923,11 +4928,11 @@ c_parser_statement_after_labels (c_parser *parser)
 	  goto expect_semicolon;
 	case RID_CONTINUE:
 	  c_parser_consume_token (parser);
-	  stmt = c_finish_bc_stmt (loc, &c_cont_label, false);
+	  stmt = c_finish_bc_stmt (loc, &c_loop_state.cont_label, false);
 	  goto expect_semicolon;
 	case RID_BREAK:
 	  c_parser_consume_token (parser);
-	  stmt = c_finish_bc_stmt (loc, &c_break_label, true);
+	  stmt = c_finish_bc_stmt (loc, &c_loop_state.break_label, true);
 	  goto expect_semicolon;
 	case RID_RETURN:
 	  c_parser_consume_token (parser);
@@ -4962,6 +4967,8 @@ c_parser_statement_after_labels (c_parser *parser)
 	    {
 	      stmt = objc_build_throw_stmt (loc, NULL_TREE);
 	      c_parser_consume_token (parser);
+	      c_reachability_state.mark_unreachable
+		(loc, reachability_state::after_throw);
 	    }
 	  else
 	    {
@@ -4969,6 +4976,8 @@ c_parser_statement_after_labels (c_parser *parser)
 	      expr = convert_lvalue_to_rvalue (loc, expr, false, false);
 	      expr.value = c_fully_fold (expr.value, false, NULL);
 	      stmt = objc_build_throw_stmt (loc, expr.value);
+	      c_reachability_state.mark_unreachable
+		(loc, reachability_state::after_throw);
 	      goto expect_semicolon;
 	    }
 	  break;
@@ -5001,6 +5010,8 @@ c_parser_statement_after_labels (c_parser *parser)
       break;
     default:
     expr_stmt:
+      c_reachability_state.warn_if_unreachable
+	(loc, "statement expression is unreachable");
       stmt = c_finish_expr_stmt (loc, c_parser_expression_conv (parser).value);
     expect_semicolon:
       c_parser_skip_until_found (parser, CPP_SEMICOLON, "expected %<;%>");
@@ -5145,6 +5156,7 @@ c_parser_if_statement (c_parser *parser)
   tree first_body, second_body;
   bool in_if_block;
   tree if_stmt;
+  bool fallthrough;
 
   gcc_assert (c_parser_next_token_is_keyword (parser, RID_IF));
   c_parser_consume_token (parser);
@@ -5154,14 +5166,20 @@ c_parser_if_statement (c_parser *parser)
   in_if_block = parser->in_if_block;
   parser->in_if_block = true;
   first_body = c_parser_if_body (parser, &first_if);
+  fallthrough = c_reachability_state.reachable_p ();
   parser->in_if_block = in_if_block;
   if (c_parser_next_token_is_keyword (parser, RID_ELSE))
     {
       c_parser_consume_token (parser);
+      c_reachability_state.mark_reachable ();
       second_body = c_parser_else_body (parser);
+      fallthrough |= c_reachability_state.reachable_p ();
     }
   else
-    second_body = NULL_TREE;
+    {
+      second_body = NULL_TREE;
+      fallthrough = true;
+    }
   c_finish_if_stmt (loc, cond, first_body, second_body, first_if);
   if_stmt = c_end_compound_stmt (loc, block, flag_isoc99);
 
@@ -5169,6 +5187,11 @@ c_parser_if_statement (c_parser *parser)
   if (flag_cilkplus && contains_array_notation_expr (if_stmt))
     if_stmt = fix_conditional_array_notations (if_stmt);
   add_stmt (if_stmt);
+  if (fallthrough)
+    c_reachability_state.mark_reachable ();
+  else
+    c_reachability_state.mark_unreachable
+      (loc, reachability_state::after_if);
 }
 
 /* Parse a switch statement (C90 6.6.4, C99 6.8.4).
@@ -5181,7 +5204,8 @@ static void
 c_parser_switch_statement (c_parser *parser)
 {
   struct c_expr ce;
-  tree block, expr, body, save_break;
+  tree block, expr, body;
+  loop_state save_loop;
   location_t switch_loc = c_parser_peek_token (parser)->location;
   location_t switch_cond_loc;
   gcc_assert (c_parser_next_token_is_keyword (parser, RID_SWITCH));
@@ -5191,6 +5215,8 @@ c_parser_switch_statement (c_parser *parser)
     {
       switch_cond_loc = c_parser_peek_token (parser)->location;
       ce = c_parser_expression (parser);
+      c_reachability_state.warn_if_unreachable
+	(switch_cond_loc, "result of switch condition is unreachable");
       ce = convert_lvalue_to_rvalue (switch_cond_loc, ce, true, false);
       expr = ce.value;
       if (flag_cilkplus && contains_array_notation_expr (expr))
@@ -5208,18 +5234,21 @@ c_parser_switch_statement (c_parser *parser)
       expr = error_mark_node;
     }
   c_start_case (switch_loc, switch_cond_loc, expr);
-  save_break = c_break_label;
-  c_break_label = NULL_TREE;
+  c_reachability_state.mark_unreachable
+    (switch_loc, reachability_state::after_switch_start);
+  c_loop_state.enter_switch (&save_loop);
   body = c_parser_c99_block_statement (parser);
   c_finish_case (body);
-  if (c_break_label)
+  if (c_loop_state.break_label)
     {
       location_t here = c_parser_peek_token (parser)->location;
-      tree t = build1 (LABEL_EXPR, void_type_node, c_break_label);
+      tree t = build1 (LABEL_EXPR, void_type_node, c_loop_state.break_label);
       SET_EXPR_LOCATION (t, here);
       add_stmt (t);
     }
-  c_break_label = save_break;
+  c_loop_state.leave_switch (&save_loop);
+  /* ??? We should recognize if all cases do not complete normally.  */
+  c_reachability_state.mark_reachable ();
   add_stmt (c_end_compound_stmt (switch_loc, block, flag_isoc99));
 }
 
@@ -5232,7 +5261,8 @@ c_parser_switch_statement (c_parser *parser)
 static void
 c_parser_while_statement (c_parser *parser, bool ivdep)
 {
-  tree block, cond, body, save_break, save_cont;
+  tree block, cond, body;
+  loop_state save_loop;
   location_t loc;
   gcc_assert (c_parser_next_token_is_keyword (parser, RID_WHILE));
   c_parser_consume_token (parser);
@@ -5250,15 +5280,12 @@ c_parser_while_statement (c_parser *parser, bool ivdep)
     cond = build2 (ANNOTATE_EXPR, TREE_TYPE (cond), cond,
 		   build_int_cst (integer_type_node,
 		   annot_expr_ivdep_kind));
-  save_break = c_break_label;
-  c_break_label = NULL_TREE;
-  save_cont = c_cont_label;
-  c_cont_label = NULL_TREE;
+  c_loop_state.enter_loop (&save_loop);
   body = c_parser_c99_block_statement (parser);
-  c_finish_loop (loc, cond, NULL, body, c_break_label, c_cont_label, true);
+  c_finish_loop (loc, cond, NULL, body,
+		 c_loop_state.break_label, c_loop_state.cont_label, true);
   add_stmt (c_end_compound_stmt (loc, block, flag_isoc99));
-  c_break_label = save_break;
-  c_cont_label = save_cont;
+  c_loop_state.leave_loop (loc, &save_loop);
 }
 
 /* Parse a do statement (C90 6.6.5, C99 6.8.5).
@@ -5270,7 +5297,8 @@ c_parser_while_statement (c_parser *parser, bool ivdep)
 static void
 c_parser_do_statement (c_parser *parser, bool ivdep)
 {
-  tree block, cond, body, save_break, save_cont, new_break, new_cont;
+  tree block, cond, body, new_break, new_cont;
+  loop_state save_loop;
   location_t loc;
   gcc_assert (c_parser_next_token_is_keyword (parser, RID_DO));
   c_parser_consume_token (parser);
@@ -5280,16 +5308,15 @@ c_parser_do_statement (c_parser *parser, bool ivdep)
 		"suggest braces around empty body in %<do%> statement");
   block = c_begin_compound_stmt (flag_isoc99);
   loc = c_parser_peek_token (parser)->location;
-  save_break = c_break_label;
-  c_break_label = NULL_TREE;
-  save_cont = c_cont_label;
-  c_cont_label = NULL_TREE;
+  c_loop_state.enter_loop (&save_loop);
   body = c_parser_c99_block_statement (parser);
   c_parser_require_keyword (parser, RID_WHILE, "expected %<while%>");
-  new_break = c_break_label;
-  c_break_label = save_break;
-  new_cont = c_cont_label;
-  c_cont_label = save_cont;
+  new_break = c_loop_state.break_label;
+  new_cont = c_loop_state.cont_label;
+  c_loop_state.leave_loop (loc, &save_loop);
+  /* ??? This is neeeded to support the common do ... while (0)
+     construct.  We should recognize that explicitly.  */
+  c_reachability_state.mark_reachable ();
   cond = c_parser_paren_condition (parser);
   if (flag_cilkplus && contains_array_notation_expr (cond))
     {
@@ -5366,7 +5393,8 @@ c_parser_do_statement (c_parser *parser, bool ivdep)
 static void
 c_parser_for_statement (c_parser *parser, bool ivdep)
 {
-  tree block, cond, incr, save_break, save_cont, body;
+  tree block, cond, incr, body;
+  loop_state save_loop;
   /* The following are only used when parsing an ObjC foreach statement.  */
   tree object_expression;
   /* Silence the bogus uninitialized warning.  */
@@ -5530,18 +5558,17 @@ c_parser_for_statement (c_parser *parser, bool ivdep)
 	}
       c_parser_skip_until_found (parser, CPP_CLOSE_PAREN, "expected %<)%>");
     }
-  save_break = c_break_label;
-  c_break_label = NULL_TREE;
-  save_cont = c_cont_label;
-  c_cont_label = NULL_TREE;
+  c_loop_state.enter_loop (&save_loop);
   body = c_parser_c99_block_statement (parser);
   if (is_foreach_statement)
-    objc_finish_foreach_loop (loc, object_expression, collection_expression, body, c_break_label, c_cont_label);
+    objc_finish_foreach_loop
+      (loc, object_expression, collection_expression, body,
+       c_loop_state.break_label, c_loop_state.cont_label);
   else
-    c_finish_loop (loc, cond, incr, body, c_break_label, c_cont_label, true);
+    c_finish_loop (loc, cond, incr, body,
+		   c_loop_state.break_label, c_loop_state.cont_label, true);
   add_stmt (c_end_compound_stmt (loc, block, flag_isoc99 || c_dialect_objc ()));
-  c_break_label = save_break;
-  c_cont_label = save_cont;
+  c_loop_state.leave_loop (loc, &save_loop);
 }
 
 /* Parse an asm statement, a GNU extension.  This is a full-blown asm
@@ -8762,15 +8789,17 @@ c_parser_objc_protocol_refs (c_parser *parser)
 static void
 c_parser_objc_try_catch_finally_statement (c_parser *parser)
 {
-  location_t location;
+  location_t start_location, location;
   tree stmt;
+  bool fallthrough;
 
   gcc_assert (c_parser_next_token_is_keyword (parser, RID_AT_TRY));
   c_parser_consume_token (parser);
-  location = c_parser_peek_token (parser)->location;
+  start_location = location = c_parser_peek_token (parser)->location;
   objc_maybe_warn_exceptions (location);
   stmt = c_parser_compound_statement (parser);
   objc_begin_try_stmt (location, stmt);
+  fallthrough = c_reachability_state.reachable_p();
 
   while (c_parser_next_token_is_keyword (parser, RID_AT_CATCH))
     {
@@ -8779,6 +8808,7 @@ c_parser_objc_try_catch_finally_statement (c_parser *parser)
       bool seen_open_paren = false;
 
       c_parser_consume_token (parser);
+      c_reachability_state.mark_reachable ();
       if (!c_parser_require (parser, CPP_OPEN_PAREN, "expected %<(%>"))
 	seen_open_paren = true;
       if (c_parser_next_token_is (parser, CPP_ELLIPSIS))
@@ -8825,15 +8855,24 @@ c_parser_objc_try_catch_finally_statement (c_parser *parser)
       if (c_parser_require (parser, CPP_OPEN_BRACE, "expected %<{%>"))
 	c_parser_compound_statement_nostart (parser);
       objc_finish_catch_clause ();
+      fallthrough |= c_reachability_state.reachable_p ();
     }
   if (c_parser_next_token_is_keyword (parser, RID_AT_FINALLY))
     {
       c_parser_consume_token (parser);
+      c_reachability_state.mark_reachable ();
       location = c_parser_peek_token (parser)->location;
       stmt = c_parser_compound_statement (parser);
       objc_build_finally_clause (location, stmt);
+      /* The finally part follows the control flow of the try/catch
+	 parts, so it does not affect fallthrough behavior. */
     }
   objc_finish_try_stmt ();
+  if (fallthrough)
+    c_reachability_state.mark_reachable ();
+  else
+    c_reachability_state.mark_unreachable
+      (start_location, reachability_state::after_try);
 }
 
 /* Parse an objc-synchronized-statement.
@@ -11629,7 +11668,8 @@ static tree
 c_parser_omp_for_loop (location_t loc, c_parser *parser, enum tree_code code,
 		       tree clauses, tree *cclauses)
 {
-  tree decl, cond, incr, save_break, save_cont, body, init, stmt, cl;
+  tree decl, cond, incr, body, init, stmt, cl;
+  loop_state save_loop;
   tree declv, condv, incrv, initv, ret = NULL;
   bool fail = false, open_brace_parsed = false;
   int i, collapse = 1, nbraces = 0;
@@ -11804,13 +11844,11 @@ c_parser_omp_for_loop (location_t loc, c_parser *parser, enum tree_code code,
       nbraces += bracecount;
     }
 
-  save_break = c_break_label;
+  c_loop_state.enter_loop (&save_loop);
   if (code == CILK_SIMD)
-    c_break_label = build_int_cst (size_type_node, 2);
+    c_loop_state.break_label = build_int_cst (size_type_node, 2);
   else
-    c_break_label = size_one_node;
-  save_cont = c_cont_label;
-  c_cont_label = NULL_TREE;
+    c_loop_state.break_label = size_one_node;
   body = push_stmt_list ();
 
   if (open_brace_parsed)
@@ -11822,16 +11860,15 @@ c_parser_omp_for_loop (location_t loc, c_parser *parser, enum tree_code code,
     }
   else
     add_stmt (c_parser_c99_block_statement (parser));
-  if (c_cont_label)
+  if (c_loop_state.cont_label)
     {
-      tree t = build1 (LABEL_EXPR, void_type_node, c_cont_label);
+      tree t = build1 (LABEL_EXPR, void_type_node, c_loop_state.cont_label);
       SET_EXPR_LOCATION (t, loc);
       add_stmt (t);
     }
 
   body = pop_stmt_list (body);
-  c_break_label = save_break;
-  c_cont_label = save_cont;
+  c_loop_state.leave_loop (loc, &save_loop);
 
   while (nbraces)
     {
diff --git a/gcc/c/c-tree.h b/gcc/c/c-tree.h
index a6e7327..71bbff8 100644
--- a/gcc/c/c-tree.h
+++ b/gcc/c/c-tree.h
@@ -461,8 +461,6 @@ extern void gen_aux_info_record (tree, int, int, int);
 struct c_spot_bindings;
 struct c_struct_parse_info;
 extern struct obstack parser_obstack;
-extern tree c_break_label;
-extern tree c_cont_label;
 
 extern bool global_bindings_p (void);
 extern void push_scope (void);
diff --git a/gcc/c/c-typeck.c b/gcc/c/c-typeck.c
index 7d2df6b..01ea808 100644
--- a/gcc/c/c-typeck.c
+++ b/gcc/c/c-typeck.c
@@ -9116,6 +9116,8 @@ c_finish_goto_label (location_t loc, tree label)
   if (!decl)
     return NULL_TREE;
   TREE_USED (decl) = 1;
+  c_reachability_state.mark_unreachable
+    (loc, reachability_state::after_goto);
   {
     tree t = build1 (GOTO_EXPR, void_type_node, decl);
     SET_EXPR_LOCATION (t, loc);
@@ -9135,6 +9137,8 @@ c_finish_goto_ptr (location_t loc, tree expr)
   expr = convert (ptr_type_node, expr);
   t = build1 (GOTO_EXPR, void_type_node, expr);
   SET_EXPR_LOCATION (t, loc);
+  c_reachability_state.mark_unreachable
+    (loc, reachability_state::after_goto);
   return add_stmt (t);
 }
 
@@ -9299,6 +9303,8 @@ c_finish_return (location_t loc, tree retval, tree origtype)
 
   ret_stmt = build_stmt (loc, RETURN_EXPR, retval);
   TREE_NO_WARNING (ret_stmt) |= no_warning;
+  c_reachability_state.mark_unreachable
+    (loc, reachability_state::after_return);
   return add_stmt (ret_stmt);
 }
 
@@ -9548,6 +9554,7 @@ c_finish_loop (location_t start_locus, tree cond, tree incr, tree body,
 	       tree blab, tree clab, bool cond_is_first)
 {
   tree entry = NULL, exit = NULL, t;
+  bool fallthrough = false;
 
   if (flag_cilkplus && contains_array_notation_expr (cond))
     {
@@ -9565,6 +9572,7 @@ c_finish_loop (location_t start_locus, tree cond, tree incr, tree body,
 	  SET_EXPR_LOCATION (t, start_locus);
 	  add_stmt (t);
 	}
+      fallthrough = true;
     }
   else
     {
@@ -9600,6 +9608,7 @@ c_finish_loop (location_t start_locus, tree cond, tree incr, tree body,
 	  else
 	    exit = fold_build3_loc (input_location,
 				COND_EXPR, void_type_node, cond, exit, t);
+	  fallthrough = true;
 	}
 
       add_stmt (top);
@@ -9616,7 +9625,15 @@ c_finish_loop (location_t start_locus, tree cond, tree incr, tree body,
   if (exit)
     add_stmt (exit);
   if (blab)
-    add_stmt (build1 (LABEL_EXPR, void_type_node, blab));
+    {
+      add_stmt (build1 (LABEL_EXPR, void_type_node, blab));
+      fallthrough = true;
+    }
+  if (fallthrough)
+    c_reachability_state.mark_reachable ();
+  else
+    c_reachability_state.mark_unreachable
+      (start_locus, reachability_state::after_loop);
 }
 
 tree
@@ -9669,8 +9686,15 @@ c_finish_bc_stmt (location_t loc, tree *label_p, bool is_break)
   if (skip)
     return NULL_TREE;
 
-  if (!is_break)
-    add_stmt (build_predict_expr (PRED_CONTINUE, NOT_TAKEN));
+  if (is_break)
+    c_reachability_state.mark_unreachable
+      (loc, reachability_state::after_break);
+  else
+    {
+      c_reachability_state.mark_unreachable
+	(loc, reachability_state::after_continue);
+      add_stmt (build_predict_expr (PRED_CONTINUE, NOT_TAKEN));
+    }
 
   return add_stmt (build1 (GOTO_EXPR, void_type_node, label));
 }
diff --git a/gcc/common.opt b/gcc/common.opt
index 5c3f834..afbdbfa 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -663,10 +663,6 @@ Wmaybe-uninitialized
 Common Var(warn_maybe_uninitialized) Warning EnabledBy(Wuninitialized)
 Warn about maybe uninitialized automatic variables
 
-Wunreachable-code
-Common Ignore
-Does nothing. Preserved for backward compatibility.
-
 Wunused
 Common Var(warn_unused) Init(0) Warning
 Enable all -Wunused- warnings
diff --git a/gcc/testsuite/gcc.dg/Wunreachable-code-1.c b/gcc/testsuite/gcc.dg/Wunreachable-code-1.c
new file mode 100644
index 0000000..e7b86ae
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wunreachable-code-1.c
@@ -0,0 +1,103 @@
+/* { dg-do compile } */
+/* { dg-options "-Wunreachable-code" } */
+
+void f(void);
+int g(void);
+
+void jump_into_loop (void)
+{
+  if (1)
+    goto L1;
+  return;
+  while (1) {
+  L1:
+    goto L2;
+  }
+ L2:
+  ;
+}
+
+void switch_header (int i)
+{
+  switch (i) /* { dg-message "note:.*switch.*statement skips initial code" } */
+    {
+      f (); /* { dg-warning "statement expression is unreachable" } */
+    case 1:
+      return;
+    }
+}
+
+void switch_into_loop (int i)
+{
+  switch (i)
+    while (1)
+      {
+      case 1:
+	return;
+      }
+}
+
+int regular_break_in_switch (int i)
+{
+  switch (i)
+    {
+    case 0:
+      break;
+    case 1:
+      return 1;
+      break;
+    case 2:
+      if (i)
+	return 1;
+      else
+	return 2;
+      break;
+    case 3:
+      while (1)
+	{
+	}
+    }
+  return -1;
+}
+
+void unreachable_after_while_loop (void)
+{
+  while (1) /* { dg-message "note: loop does not exit normally" } */
+    f ();
+  f (); /* { dg-warning "statement expression is unreachable" } */
+}
+
+void unreachable_after_do_loop (void)
+{
+  do
+    f (); /* { dg-message "note: loop does not exit normally" } */
+  while (1);
+  f (); /* { dg-warning "statement expression is unreachable" } */
+}
+
+void unreachable_after_for_loop (void)
+{
+  for (;;) /* { dg-message "note: loop does not exit normally" } */
+    f ();
+  f (); /* { dg-warning "statement expression is unreachable" } */
+}
+
+int unreachable_return_after_for_loop (void)
+{
+  for (;;) /* { dg-message "note: loop does not exit normally" } */
+    f ();
+  return g (); /* { dg-warning "statement is unreachable" } */
+}
+
+void break_nested_in_loop_in_switch (int i)
+{
+  switch (i)
+    {
+    case 1:
+      while (1) {
+	return; /* { dg-message "note:.*return.*redirects control flow" } */
+	break; /* { dg-warning "statement is unreachable" } */
+      }
+      break;
+    }
+}
diff --git a/gcc/testsuite/gcc.dg/Wunreachable-code-2.c b/gcc/testsuite/gcc.dg/Wunreachable-code-2.c
new file mode 100644
index 0000000..408ce50
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wunreachable-code-2.c
@@ -0,0 +1,86 @@
+/* { dg-do compile } */
+/* { dg-options "-Wunreachable-code" } */
+
+/* The test cases in this file should not produce any warnings because
+   they demonstrate common coding patterns involving (trivially)
+   unreachable code.  */
+
+int f(void);
+int g(void);
+int noreturn(void) __attribute__((noreturn));
+
+void abort(void) __attribute__((noreturn));
+#define assert(expr) do { if (!(expr)) abort (); } while (0)
+
+void abort_after_infinite_loop (void)
+{
+  for (;;)
+    f ();
+  abort ();
+}
+
+void abort_after_if (int flag)
+{
+  if (flag)
+    return f ();
+  else
+    return g ();
+  abort ();
+}
+
+void assert_after_infinite_loop (void)
+{
+  for (;;)
+    f ();
+  assert(0 && "unreachable");
+}
+
+void assert_after_if (int flag)
+{
+  if (flag)
+    return f ();
+  else
+    return g ();
+  assert(0 && "unreachable");
+}
+
+int return_after_infinite_loop (void)
+{
+  for (;;)
+    f ();
+  return 0;
+}
+
+int abort_return_after_infinite_loop (void)
+{
+  for (;;)
+    f ();
+  abort ();
+  return 0;
+}
+
+int noreturn_in_return (void)
+{
+  f ();
+  return noreturn ();
+}
+
+int abort_after_noreturn (void)
+{
+  noreturn ();
+  abort ();
+}
+
+int abort_in_do (void)
+{
+  do {
+    abort (0);
+  } while (0);
+}
+
+int assert_in_do (void)
+{
+  do {
+    assert(0 && "abort");
+  } while (0);
+}
diff --git a/gcc/testsuite/gcc.dg/Wunreachable-code-3.c b/gcc/testsuite/gcc.dg/Wunreachable-code-3.c
new file mode 100644
index 0000000..ef0c6bf
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wunreachable-code-3.c
@@ -0,0 +1,47 @@
+/* { dg-do compile } */
+/* { dg-options "-Wunreachable-code" } */
+
+/* These tests should produce unreachable warnings.  */
+
+int f(void);
+int g(int);
+int noreturn(void) __attribute__((noreturn));
+
+void abort(void) __attribute__((noreturn));
+#define assert(expr) do { if (!(expr)) abort (); } while (0)
+
+void noreturn_unreachable (void)
+{
+  abort ();
+  f (); /* { dg-warning "unreachable" } */
+}
+
+void noreturn_expression (int flag)
+{
+  int x;
+  if (flag)
+    g (noreturn ()); /* { dg-warning "unreachable" } */
+  if (flag)
+    x = noreturn (); /* { dg-warning "unreachable" } */
+}
+
+int noreturn_if_condition (void)
+{
+  if (noreturn ()) /* { dg-warning "unreachable" } */
+    return f ();
+  else
+    return  g(1);
+}
+
+void noreturn_while (void)
+{
+  while (noreturn ()) /* { dg-warning "unreachable" } */
+    f ();
+}
+
+void noreturn_do_while (void)
+{
+  do
+    f ();
+  while (noreturn ()); /* { dg-warning "unreachable" } */
+}
diff --git a/libgomp/config/linux/affinity.c b/libgomp/config/linux/affinity.c
index 2f8a300..cbfd281 100644
--- a/libgomp/config/linux/affinity.c
+++ b/libgomp/config/linux/affinity.c
@@ -320,7 +320,6 @@ gomp_affinity_init_level (int level, unsigned long count, bool quiet)
 	}
       return true;
     }
-  return false;
 }
 
 void

Reply via email to