diff mbox series

[committed] analyzer: add heuristics for switch on enum type [PR105273]

Message ID 20230113225428.380307-1-dmalcolm@redhat.com
State New
Headers show
Series [committed] analyzer: add heuristics for switch on enum type [PR105273] | expand

Commit Message

David Malcolm Jan. 13, 2023, 10:54 p.m. UTC
Assume that switch on an enum doesn't follow an implicit default
skipping all cases when all enum values are covered by cases.

Fixes various false positives from -Wanalyzer-use-of-uninitialized-value
such as this one seen in Doom:

p_maputl.c: In function 'P_BoxOnLineSide':
p_maputl.c:151:8: warning: use of uninitialized value 'p1' [CWE-457] [-Wanalyzer-use-of-uninitialized-value]
  151 |     if (p1 == p2)
      |        ^
  'P_BoxOnLineSide': events 1-5
    |
    |  115 |     int         p1;
    |      |                 ^~
    |      |                 |
    |      |                 (1) region created on stack here
    |      |                 (2) capacity: 4 bytes
    |......
    |  118 |     switch (ld->slopetype)
    |      |     ~~~~~~
    |      |     |
    |      |     (3) following 'default:' branch...
    |......
    |  151 |     if (p1 == p2)
    |      |        ~
    |      |        |
    |      |        (4) ...to here
    |      |        (5) use of uninitialized value 'p1' here
    |

where "ld->slopetype" is a "slopetype_t" enum, and for every value of
that enum the switch has a case that initializes "p1".

Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.
Pushed to trunk as r13-5159-gccd4df81aa6537.

gcc/analyzer/ChangeLog:
	PR analyzer/105273
	* region-model.cc (has_nondefault_case_for_value_p): New.
	(has_nondefault_cases_for_all_enum_values_p): New.
	(region_model::apply_constraints_for_gswitch): Skip
	implicitly-created "default" when switching on an enum
	and all enum values have non-default cases.
	(rejected_default_case::dump_to_pp): New.
	* region-model.h (region_model_context::possibly_tainted_p): New
	decl.
	(class rejected_default_case): New.
	* sm-taint.cc (region_model_context::possibly_tainted_p): New.
	* supergraph.cc (switch_cfg_superedge::dump_label_to_pp): Dump
	when implicitly_created_default_p.
	(switch_cfg_superedge::implicitly_created_default_p): New.
	* supergraph.h
	(switch_cfg_superedge::implicitly_created_default_p): New decl.

gcc/testsuite/ChangeLog:
	PR analyzer/105273
	* gcc.dg/analyzer/switch-enum-1.c: New test.
	* gcc.dg/analyzer/switch-enum-2.c: New test.
	* gcc.dg/analyzer/switch-enum-pr105273-git-vreportf-2.c: New test.
	* gcc.dg/analyzer/switch-enum-taint-1.c: New test.
	* gcc.dg/analyzer/switch-wrong-enum.c: New test.
	* gcc.dg/analyzer/torture/switch-enum-pr105273-doom-p_floor.c: New
	test.
	* gcc.dg/analyzer/torture/switch-enum-pr105273-doom-p_maputl.c:
	New test.
	* gcc.dg/analyzer/torture/switch-enum-pr105273-git-vreportf-1.c:
	New test.

Signed-off-by: David Malcolm <dmalcolm@redhat.com>
---
 gcc/analyzer/region-model.cc                  | 104 +++++++++++++-
 gcc/analyzer/region-model.h                   |  12 ++
 gcc/analyzer/sm-taint.cc                      |  25 ++++
 gcc/analyzer/supergraph.cc                    |  22 +++
 gcc/analyzer/supergraph.h                     |   2 +
 gcc/testsuite/gcc.dg/analyzer/switch-enum-1.c | 136 ++++++++++++++++++
 gcc/testsuite/gcc.dg/analyzer/switch-enum-2.c | 132 +++++++++++++++++
 .../switch-enum-pr105273-git-vreportf-2.c     |  40 ++++++
 .../gcc.dg/analyzer/switch-enum-taint-1.c     | 102 +++++++++++++
 .../gcc.dg/analyzer/switch-wrong-enum.c       |  27 ++++
 .../switch-enum-pr105273-doom-p_floor.c       |  89 ++++++++++++
 .../switch-enum-pr105273-doom-p_maputl.c      |  86 +++++++++++
 .../switch-enum-pr105273-git-vreportf-1.c     |  35 +++++
 13 files changed, 810 insertions(+), 2 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/switch-enum-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/switch-enum-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/switch-enum-pr105273-git-vreportf-2.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/switch-enum-taint-1.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/switch-wrong-enum.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/torture/switch-enum-pr105273-doom-p_floor.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/torture/switch-enum-pr105273-doom-p_maputl.c
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/torture/switch-enum-pr105273-git-vreportf-1.c
diff mbox series

Patch

diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index 2e59fbaadd7..6a3a1b474bf 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -4341,6 +4341,72 @@  region_model::apply_constraints_for_gcond (const cfg_superedge &sedge,
   return add_constraint (lhs, op, rhs, ctxt, out);
 }
 
+/* Return true iff SWITCH_STMT has a non-default label that contains
+   INT_CST.  */
+
+static bool
+has_nondefault_case_for_value_p (const gswitch *switch_stmt, tree int_cst)
+{
+  /* We expect the initial label to be the default; skip it.  */
+  gcc_assert (CASE_LOW (gimple_switch_label (switch_stmt, 0)) == NULL);
+  unsigned min_idx = 1;
+  unsigned max_idx = gimple_switch_num_labels (switch_stmt) - 1;
+
+  /* Binary search: try to find the label containing INT_CST.
+     This requires the cases to be sorted by CASE_LOW (done by the
+     gimplifier).  */
+  while (max_idx >= min_idx)
+    {
+      unsigned case_idx = (min_idx + max_idx) / 2;
+      tree label =  gimple_switch_label (switch_stmt, case_idx);
+      tree low = CASE_LOW (label);
+      gcc_assert (low);
+      tree high = CASE_HIGH (label);
+      if (!high)
+	high = low;
+      if (tree_int_cst_compare (int_cst, low) < 0)
+	{
+	  /* INT_CST is below the range of this label.  */
+	  gcc_assert (case_idx > 0);
+	  max_idx = case_idx - 1;
+	}
+      else if (tree_int_cst_compare (int_cst, high) > 0)
+	{
+	  /* INT_CST is above the range of this case.  */
+	  min_idx = case_idx + 1;
+	}
+      else
+	/* This case contains INT_CST.  */
+	return true;
+    }
+  /* Not found.  */
+  return false;
+}
+
+/* Return true iff SWITCH_STMT (which must be on an enum value)
+   has nondefault cases handling all values in the enum.  */
+
+static bool
+has_nondefault_cases_for_all_enum_values_p (const gswitch *switch_stmt)
+{
+  gcc_assert (switch_stmt);
+  tree type = TREE_TYPE (gimple_switch_index (switch_stmt));
+  gcc_assert (TREE_CODE (type) == ENUMERAL_TYPE);
+
+  for (tree enum_val_iter = TYPE_VALUES (type);
+       enum_val_iter;
+       enum_val_iter = TREE_CHAIN (enum_val_iter))
+    {
+      tree enum_val = TREE_VALUE (enum_val_iter);
+      gcc_assert (TREE_CODE (enum_val) == CONST_DECL);
+      gcc_assert (TREE_CODE (DECL_INITIAL (enum_val)) == INTEGER_CST);
+      if (!has_nondefault_case_for_value_p (switch_stmt,
+					    DECL_INITIAL (enum_val)))
+	return false;
+    }
+  return true;
+}
+
 /* Given an EDGE guarded by SWITCH_STMT, determine appropriate constraints
    for the edge to be taken.
 
@@ -4357,11 +4423,37 @@  region_model::apply_constraints_for_gswitch (const switch_cfg_superedge &edge,
 					     region_model_context *ctxt,
 					     rejected_constraint **out)
 {
+  tree index  = gimple_switch_index (switch_stmt);
+  const svalue *index_sval = get_rvalue (index, ctxt);
+
+  /* If we're switching based on an enum type, assume that the user is only
+     working with values from the enum.  Hence if this is an
+     implicitly-created "default", assume it doesn't get followed.
+     This fixes numerous "uninitialized" false positives where we otherwise
+     consider jumping past the initialization cases.  */
+
+  if (/* Don't check during feasibility-checking (when ctxt is NULL).  */
+      ctxt
+      /* Must be an enum value.  */
+      && index_sval->get_type ()
+      && TREE_CODE (TREE_TYPE (index)) == ENUMERAL_TYPE
+      && TREE_CODE (index_sval->get_type ()) == ENUMERAL_TYPE
+      /* If we have a constant, then we can check it directly.  */
+      && index_sval->get_kind () != SK_CONSTANT
+      && edge.implicitly_created_default_p ()
+      && has_nondefault_cases_for_all_enum_values_p (switch_stmt)
+      /* Don't do this if there's a chance that the index is
+	 attacker-controlled.  */
+      && !ctxt->possibly_tainted_p (index_sval))
+    {
+      if (out)
+	*out = new rejected_default_case (*this);
+      return false;
+    }
+
   bounded_ranges_manager *ranges_mgr = get_range_manager ();
   const bounded_ranges *all_cases_ranges
     = ranges_mgr->get_or_create_ranges_for_switch (&edge, switch_stmt);
-  tree index  = gimple_switch_index (switch_stmt);
-  const svalue *index_sval = get_rvalue (index, ctxt);
   bool sat = m_constraints->add_bounded_ranges (index_sval, all_cases_ranges);
   if (!sat && out)
     *out = new rejected_ranges_constraint (*this, index, all_cases_ranges);
@@ -5686,6 +5778,14 @@  rejected_op_constraint::dump_to_pp (pretty_printer *pp) const
   rhs_sval->dump_to_pp (pp, true);
 }
 
+/* class rejected_default_case : public rejected_constraint.  */
+
+void
+rejected_default_case::dump_to_pp (pretty_printer *pp) const
+{
+  pp_string (pp, "implicit default for enum");
+}
+
 /* class rejected_ranges_constraint : public rejected_constraint.  */
 
 void
diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h
index e8767e5ed41..4e1a5c679ec 100644
--- a/gcc/analyzer/region-model.h
+++ b/gcc/analyzer/region-model.h
@@ -703,6 +703,8 @@  class region_model_context
     return get_state_map_by_name ("taint", out_smap, out_sm, out_sm_idx, NULL);
   }
 
+  bool possibly_tainted_p (const svalue *sval);
+
   /* Get the current statement, if any.  */
   virtual const gimple *get_stmt () const = 0;
 };
@@ -1010,6 +1012,16 @@  public:
   tree m_rhs;
 };
 
+class rejected_default_case : public rejected_constraint
+{
+public:
+  rejected_default_case (const region_model &model)
+  : rejected_constraint (model)
+  {}
+
+  void dump_to_pp (pretty_printer *pp) const final override;
+};
+
 class rejected_ranges_constraint : public rejected_constraint
 {
 public:
diff --git a/gcc/analyzer/sm-taint.cc b/gcc/analyzer/sm-taint.cc
index a2b442a4ef2..3a619b12d5c 100644
--- a/gcc/analyzer/sm-taint.cc
+++ b/gcc/analyzer/sm-taint.cc
@@ -1549,6 +1549,31 @@  region_model::mark_as_tainted (const svalue *sval,
   smap->set_state (this, sval, taint_sm.m_tainted, NULL, *ext_state);
 }
 
+/* Return true if SVAL could possibly be attacker-controlled.  */
+
+bool
+region_model_context::possibly_tainted_p (const svalue *sval)
+{
+  sm_state_map *smap;
+  const state_machine *sm;
+  unsigned sm_idx;
+  if (!get_taint_map (&smap, &sm, &sm_idx))
+      return false;
+
+  const taint_state_machine &taint_sm = (const taint_state_machine &)*sm;
+
+  const extrinsic_state *ext_state = get_ext_state ();
+  if (!ext_state)
+    return false;
+
+  const state_machine::state_t state = smap->get_state (sval, *ext_state);
+  gcc_assert (state);
+
+  return (state == taint_sm.m_tainted
+	  || state == taint_sm.m_has_lb
+	  || state == taint_sm.m_has_ub);
+}
+
 } // namespace ana
 
 #endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/supergraph.cc b/gcc/analyzer/supergraph.cc
index 8195fe8e7f8..c0bb2c6af94 100644
--- a/gcc/analyzer/supergraph.cc
+++ b/gcc/analyzer/supergraph.cc
@@ -1153,9 +1153,31 @@  switch_cfg_superedge::dump_label_to_pp (pretty_printer *pp,
 	    pp_printf (pp, "default");
 	}
       pp_character (pp, '}');
+      if (implicitly_created_default_p ())
+	{
+	  pp_string (pp, " IMPLICITLY CREATED");
+	}
     }
 }
 
+/* Return true iff this edge is purely for an implicitly-created "default".  */
+
+bool
+switch_cfg_superedge::implicitly_created_default_p () const
+{
+  if (m_case_labels.length () != 1)
+    return false;
+
+  tree case_label = m_case_labels[0];
+  gcc_assert (TREE_CODE (case_label) == CASE_LABEL_EXPR);
+  if (CASE_LOW (case_label))
+    return false;
+
+  /* We have a single "default" case.
+     Assume that it was implicitly created if it has UNKNOWN_LOCATION.  */
+  return EXPR_LOCATION (case_label) == UNKNOWN_LOCATION;
+}
+
 /* Implementation of superedge::dump_label_to_pp for interprocedural
    superedges.  */
 
diff --git a/gcc/analyzer/supergraph.h b/gcc/analyzer/supergraph.h
index f66058cc3ec..d359e95b12d 100644
--- a/gcc/analyzer/supergraph.h
+++ b/gcc/analyzer/supergraph.h
@@ -570,6 +570,8 @@  class switch_cfg_superedge : public cfg_superedge {
 
   const vec<tree> &get_case_labels () const { return m_case_labels; }
 
+  bool implicitly_created_default_p () const;
+
 private:
   auto_vec<tree> m_case_labels;
 };
diff --git a/gcc/testsuite/gcc.dg/analyzer/switch-enum-1.c b/gcc/testsuite/gcc.dg/analyzer/switch-enum-1.c
new file mode 100644
index 00000000000..7cd871c894e
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/switch-enum-1.c
@@ -0,0 +1,136 @@ 
+#include "analyzer-decls.h"
+
+/* Verify the handling of "switch (enum_value)".  */
+
+enum e
+{
+ E_VAL0,
+ E_VAL1,
+ E_VAL2
+};
+
+/* Verify that we assume that "switch (enum)" doesn't follow implicit
+   "default" if all enum values have cases  */
+
+int test_all_values_covered_implicit_default_1 (enum e x)
+{
+  switch (x)
+    {
+    case E_VAL0:
+      return 1066;
+    case E_VAL1:
+      return 1776;
+    case E_VAL2:
+      return 1945;
+    }
+  __analyzer_dump_path (); /* { dg-bogus "path" } */
+}
+
+int test_all_values_covered_implicit_default_2 (enum e x)
+{
+  int result;
+  switch (x)
+    {
+    case E_VAL0:
+      result = 1066;
+      break;
+    case E_VAL1:
+      result = 1776;
+      break;
+    case E_VAL2:
+      result = 1945;
+      break;
+    }
+  return result; /* { dg-bogus "uninitialized" } */
+}
+
+/* Verify that we consider paths that use the implicit default when not
+   all enum values are covered by cases.  */
+
+int test_missing_values_implicit_default_1 (enum e x)
+{
+  switch (x) /* { dg-message "following 'default:' branch" } */
+    {
+    case E_VAL0:
+      return 1066;
+    case E_VAL1:
+      return 1776;
+    }
+  __analyzer_dump_path (); /* { dg-message "path" } */
+  return 0;
+}
+
+int test_missing_values_implicit_default_2 (enum e x)
+{
+  int result;
+  switch (x) /* { dg-message "following 'default:' branch" } */
+    {
+    case E_VAL0:
+      result = 1066;
+      break;
+    case E_VAL1:
+      result = 1776;
+      break;
+    }
+  return result; /* { dg-warning "uninitialized" } */
+}
+
+/* Verify that explicit "default" isn't rejected.  */
+
+int test_all_values_covered_explicit_default_1 (enum e x)
+{
+  switch (x)
+    {
+    case E_VAL0:
+      return 1066;
+    case E_VAL1:
+      return 1776;
+    case E_VAL2:
+      return 1945;
+    default:
+      __analyzer_dump_path (); /* { dg-message "path" } */
+      return 0;
+    }
+}
+
+int test_missing_values_explicit_default_1 (enum e x)
+{
+  switch (x)
+    {
+    default:
+    case E_VAL0:
+      return 1066;
+    case E_VAL1:
+      return 1776;
+    }
+  __analyzer_dump_path (); /* { dg-bogus "path" } */
+  return 0;
+}
+
+int test_missing_values_explicit_default_2 (enum e x)
+{
+  switch (x)
+    {
+    case E_VAL0:
+      return 1066;
+    case E_VAL1:
+      return 1776;
+    default:
+      __analyzer_dump_path (); /* { dg-message "path" } */
+      return 1945;
+    }
+  __analyzer_dump_path (); /* { dg-bogus "path" } */
+  return 0;
+}
+
+int test_just_default (enum e x)
+{
+  switch (x)
+    {
+    default:
+      __analyzer_dump_path (); /* { dg-message "path" } */
+      return 42;
+    }
+  __analyzer_dump_path (); /* { dg-bogus "path" } */
+  return 0;  
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/switch-enum-2.c b/gcc/testsuite/gcc.dg/analyzer/switch-enum-2.c
new file mode 100644
index 00000000000..c3c7ae38aa7
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/switch-enum-2.c
@@ -0,0 +1,132 @@ 
+#include "analyzer-decls.h"
+
+/* Verify the handling of "switch (enum_value)".  */
+
+enum e
+{
+ E_VAL0,
+ E_VAL1,
+ E_VAL2,
+
+ E_VAL10 = 10,
+ E_VAL11 = 11,
+ E_VAL12 = 12,
+
+ E_VAL20 = 20,
+ E_VAL21 = 21,
+ E_VAL22 = 22
+};
+
+/* Verify that we assume that "switch (enum)" doesn't follow implicit
+   "default" if all enum values have cases  */
+
+int test_all_values_covered_implicit_default_1 (enum e x)
+{
+  switch (x)
+    {
+    case E_VAL0...E_VAL2:
+      return 1066;
+    case E_VAL10...E_VAL12:
+      return 1776;
+    case E_VAL20...E_VAL22:
+      return 1945;
+    }
+  __analyzer_dump_path (); /* { dg-bogus "path" } */
+}
+
+int test_all_values_covered_implicit_default_2 (enum e x)
+{
+  int result;
+  switch (x)
+    {
+    case E_VAL0...E_VAL2:
+      result = 1066;
+      break;
+    case E_VAL10...E_VAL12:
+      result = 1776;
+      break;
+    case E_VAL20...E_VAL22:
+      result = 1945;
+      break;
+    }
+  return result; /* { dg-bogus "uninitialized" } */
+}
+
+/* Verify that we consider paths that use the implicit default when not
+   all enum values are covered by cases.  */
+
+int test_missing_values_implicit_default_1 (enum e x)
+{
+  switch (x) /* { dg-message "following 'default:' branch" } */
+    {
+    case E_VAL0...E_VAL2:
+      return 1066;
+    case E_VAL10...E_VAL12:
+      return 1776;
+    }
+  __analyzer_dump_path (); /* { dg-message "path" } */
+  return 0;
+}
+
+int test_missing_values_implicit_default_2 (enum e x)
+{
+  int result;
+  switch (x) /* { dg-message "following 'default:' branch" } */
+    {
+    case E_VAL0...E_VAL2:
+      result = 1066;
+      break;
+    case E_VAL10...E_VAL12:
+      result = 1776;
+      break;
+    }
+  return result; /* { dg-warning "uninitialized" } */
+}
+
+/* Verify that explicit "default" isn't rejected.  */
+
+int test_all_values_covered_explicit_default_1 (enum e x)
+{
+  switch (x)
+    {
+    case E_VAL0...E_VAL2:
+      return 1066;
+    case E_VAL10...E_VAL12:
+      return 1776;
+    case E_VAL20...E_VAL22:
+      return 1945;
+    default:
+      __analyzer_dump_path (); /* { dg-message "path" } */
+      return 0;
+    }
+}
+
+int test_missing_values_explicit_default_1 (enum e x)
+{
+  switch (x)
+    {
+    default:
+    case E_VAL0...E_VAL2:
+      return 1066;
+    case E_VAL10...E_VAL12:
+      return 1776;
+    }
+  __analyzer_dump_path (); /* { dg-bogus "path" } */
+  return 0;
+}
+
+int test_missing_values_explicit_default_2 (enum e x)
+{
+  switch (x)
+    {
+    case E_VAL0...E_VAL2:
+      return 1066;
+    case E_VAL10...E_VAL12:
+      return 1776;
+    default:
+      __analyzer_dump_path (); /* { dg-message "path" } */
+      return 1945;
+    }
+  __analyzer_dump_path (); /* { dg-bogus "path" } */
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/switch-enum-pr105273-git-vreportf-2.c b/gcc/testsuite/gcc.dg/analyzer/switch-enum-pr105273-git-vreportf-2.c
new file mode 100644
index 00000000000..336222759e3
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/switch-enum-pr105273-git-vreportf-2.c
@@ -0,0 +1,40 @@ 
+/* Currently the warning only fires at -O0
+   (needs to inline the call without optimizing the
+   implicit default of the switch).  */
+
+/* { dg-additional-options "-O0" } */
+
+typedef __SIZE_TYPE__ size_t;
+int snprintf(char *str, size_t size, const char *format, ...);
+
+enum usage_kind {
+	USAGE_ERROR,
+	USAGE_BUG,
+};
+
+static void __analyzer_vreportf(enum usage_kind kind)
+{
+	char buf[256];
+	const char *pfx;
+
+	switch (kind) { /* { dg-message "following 'default:' branch" } */
+	case USAGE_ERROR:
+		pfx = "error: ";
+		break;
+	case USAGE_BUG:
+		pfx = "BUG: ";
+		break;
+	}
+
+	if (kind == USAGE_BUG)
+		snprintf(buf, sizeof(buf), "%s%s:%d: ", pfx, "file", 123);
+	else
+		snprintf(buf, sizeof(buf), "%s", pfx); /* { dg-warning "uninitialized" } */
+}
+
+int main(void)
+{
+	__analyzer_vreportf(42);
+
+	return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/switch-enum-taint-1.c b/gcc/testsuite/gcc.dg/analyzer/switch-enum-taint-1.c
new file mode 100644
index 00000000000..db3bb5b4947
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/switch-enum-taint-1.c
@@ -0,0 +1,102 @@ 
+// TODO: remove need for this option
+/* { dg-additional-options "-fanalyzer-checker=taint" } */
+
+#include "analyzer-decls.h"
+
+/* Verify the handling of "switch (enum_value)".  */
+
+enum e
+{
+ E_VAL0,
+ E_VAL1,
+ E_VAL2
+};
+
+/* Verify that we consider that "switch (enum)" could follow implicit
+   "default" even when all enum values have cases if the value is
+   attacker-controlled.  */
+
+int  __attribute__((tainted_args))
+test_all_values_covered_implicit_default_1 (enum e x)
+{
+  switch (x) /* { dg-message "following 'default:' branch" } */
+    {
+    case E_VAL0:
+      return 1066;
+    case E_VAL1:
+      return 1776;
+    case E_VAL2:
+      return 1945;
+    }
+  __analyzer_dump_path (); /* { dg-message "path" } */
+}
+
+int  __attribute__((tainted_args))
+test_all_values_covered_implicit_default_2 (enum e x)
+{
+  int result;
+  switch (x) /* { dg-message "following 'default:' branch" } */
+    {
+    case E_VAL0:
+      result = 1066;
+      break;
+    case E_VAL1:
+      result = 1776;
+      break;
+    case E_VAL2:
+      result = 1945;
+      break;
+    }
+  return result; /* { dg-message "uninitialized" } */
+}
+
+/* Verify that explicit "default" isn't rejected.  */
+
+int __attribute__((tainted_args))
+test_all_values_covered_explicit_default_1 (enum e x)
+{
+  switch (x)
+    {
+    case E_VAL0:
+      return 1066;
+    case E_VAL1:
+      return 1776;
+    case E_VAL2:
+      return 1945;
+    default:
+      __analyzer_dump_path (); /* { dg-message "path" } */
+      return 0;
+    }
+}
+
+int  __attribute__((tainted_args))
+test_missing_values_explicit_default_1 (enum e x)
+{
+  switch (x)
+    {
+    default:
+    case E_VAL0:
+      return 1066;
+    case E_VAL1:
+      return 1776;
+    }
+  __analyzer_dump_path (); /* { dg-bogus "path" } */
+  return 0;
+}
+
+int __attribute__((tainted_args))
+test_missing_values_explicit_default_2 (enum e x)
+{
+  switch (x)
+    {
+    case E_VAL0:
+      return 1066;
+    case E_VAL1:
+      return 1776;
+    default:
+      __analyzer_dump_path (); /* { dg-message "path" } */
+      return 1945;
+    }
+  __analyzer_dump_path (); /* { dg-bogus "path" } */
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/switch-wrong-enum.c b/gcc/testsuite/gcc.dg/analyzer/switch-wrong-enum.c
new file mode 100644
index 00000000000..0de25168a65
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/switch-wrong-enum.c
@@ -0,0 +1,27 @@ 
+#include "analyzer-decls.h"
+
+enum color
+{
+ RED,
+ GREEN,
+ BLUE
+};
+
+enum fruit
+{
+ APPLE,
+ BANANA
+};
+
+int test_wrong_enum (enum color x)
+{
+  switch (x)
+    {
+    case APPLE:
+      return 1066;
+    case BANANA:
+      return 1776;
+    }
+  __analyzer_dump_path (); /* { dg-message "path" } */
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/torture/switch-enum-pr105273-doom-p_floor.c b/gcc/testsuite/gcc.dg/analyzer/torture/switch-enum-pr105273-doom-p_floor.c
new file mode 100644
index 00000000000..100586cdc88
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/torture/switch-enum-pr105273-doom-p_floor.c
@@ -0,0 +1,89 @@ 
+/* Reduced from linuxdoom-1.10's p_floor.c (GPLv2).  */
+
+#define FRACBITS		16
+#define FRACUNIT		(1<<FRACBITS)
+#define PU_LEVSPEC		51
+#define FLOORSPEED		FRACUNIT
+
+typedef int fixed_t;
+typedef struct line_s line_t;
+
+typedef struct
+{
+    fixed_t floorheight;
+    /* [...snip...] */
+} sector_t;
+
+typedef enum
+{
+    build8,
+    turbo16
+
+} stair_e;
+
+typedef struct
+{
+    /* [...snip...] */
+    fixed_t floordestheight;
+    fixed_t speed;
+} floormove_t;
+
+extern sector_t* sectors;
+
+void* Z_Malloc (int size, int tag, void *ptr);
+
+int
+P_FindSectorFromLineTag
+( line_t* line,
+  int start );
+
+int
+EV_BuildStairs
+( line_t*	line,
+  stair_e	type )
+{
+    int			secnum;
+    int			height;
+    /* [...snip...] */
+    int			rtn;
+    
+    sector_t*		sec;
+    /* [...snip...] */
+
+    floormove_t*	floor;
+    
+    fixed_t		stairsize;
+    fixed_t		speed;
+
+    secnum = -1;
+    rtn = 0;
+    while ((secnum = P_FindSectorFromLineTag(line,secnum)) >= 0)
+    {
+	sec = &sectors[secnum];
+
+	/* [...snip...] */
+
+	rtn = 1;
+	floor = Z_Malloc (sizeof(*floor), PU_LEVSPEC, 0);
+	
+	/* [...snip...] */
+
+	switch(type)
+	{
+	  case build8:
+	    speed = FLOORSPEED/4;
+	    stairsize = 8*FRACUNIT;
+	    break;
+	  case turbo16:
+	    speed = FLOORSPEED*4;
+	    stairsize = 16*FRACUNIT;
+	    break;
+	}
+	floor->speed = speed; /* { dg-bogus "use of uninitialized value 'speed'" } */
+	height = sec->floorheight + stairsize; /* { dg-bogus "use of uninitialized value 'stairsize'" } */
+	floor->floordestheight = height;
+
+	/* [...snip...] */
+    }
+    return rtn;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/torture/switch-enum-pr105273-doom-p_maputl.c b/gcc/testsuite/gcc.dg/analyzer/torture/switch-enum-pr105273-doom-p_maputl.c
new file mode 100644
index 00000000000..f686c731eff
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/torture/switch-enum-pr105273-doom-p_maputl.c
@@ -0,0 +1,86 @@ 
+/* Reduced from linuxdoom-1.10's p_maputl.c (GPLv2).  */
+
+typedef int fixed_t;
+
+enum
+{
+    BOXTOP,
+    BOXBOTTOM,
+    BOXLEFT,
+    BOXRIGHT
+};
+
+typedef struct
+{
+    fixed_t x;
+    fixed_t y;
+} vertex_t;
+
+typedef enum
+{
+    ST_HORIZONTAL,
+    ST_VERTICAL,
+    ST_POSITIVE,
+    ST_NEGATIVE
+} slopetype_t;
+
+typedef struct line_s
+{
+    vertex_t* v1;
+    /* [...snip...] */
+
+    fixed_t dx;
+    fixed_t dy;
+
+    /* [...snip...] */
+    slopetype_t slopetype;
+    /* [...snip...] */
+} line_t;
+
+int P_PointOnLineSide (fixed_t x, fixed_t y, line_t* line);
+
+int
+P_BoxOnLineSide
+( fixed_t*	tmbox,
+  line_t*	ld )
+{
+    int		p1;
+    int		p2;
+	
+    switch (ld->slopetype)
+    {
+      case ST_HORIZONTAL:
+	p1 = tmbox[BOXTOP] > ld->v1->y;
+	p2 = tmbox[BOXBOTTOM] > ld->v1->y;
+	if (ld->dx < 0)
+	{
+	    p1 ^= 1;
+	    p2 ^= 1;
+	}
+	break;
+	
+      case ST_VERTICAL:
+	p1 = tmbox[BOXRIGHT] < ld->v1->x;
+	p2 = tmbox[BOXLEFT] < ld->v1->x;
+	if (ld->dy < 0)
+	{
+	    p1 ^= 1;
+	    p2 ^= 1;
+	}
+	break;
+	
+      case ST_POSITIVE:
+	p1 = P_PointOnLineSide (tmbox[BOXLEFT], tmbox[BOXTOP], ld);
+	p2 = P_PointOnLineSide (tmbox[BOXRIGHT], tmbox[BOXBOTTOM], ld);
+	break;
+	
+      case ST_NEGATIVE:
+	p1 = P_PointOnLineSide (tmbox[BOXRIGHT], tmbox[BOXTOP], ld);
+	p2 = P_PointOnLineSide (tmbox[BOXLEFT], tmbox[BOXBOTTOM], ld);
+	break;
+    }
+
+    if (p1 == p2) /* { dg-bogus "use of uninitialized value" } */
+	return p1; /* { dg-bogus "use of uninitialized value" } */
+    return -1;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/torture/switch-enum-pr105273-git-vreportf-1.c b/gcc/testsuite/gcc.dg/analyzer/torture/switch-enum-pr105273-git-vreportf-1.c
new file mode 100644
index 00000000000..13e1ad289b2
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/torture/switch-enum-pr105273-git-vreportf-1.c
@@ -0,0 +1,35 @@ 
+typedef __SIZE_TYPE__ size_t;
+int snprintf(char *str, size_t size, const char *format, ...);
+
+enum usage_kind {
+	USAGE_ERROR,
+	USAGE_BUG,
+};
+
+static void __analyzer_vreportf(enum usage_kind kind)
+{
+	char buf[256];
+	const char *pfx;
+
+	switch (kind) {
+	case USAGE_ERROR:
+		pfx = "error: ";
+		break;
+	case USAGE_BUG:
+		pfx = "BUG: ";
+		break;
+	}
+
+	if (kind == USAGE_BUG)
+		snprintf(buf, sizeof(buf), "%s%s:%d: ", pfx, "file", 123);
+	else
+		snprintf(buf, sizeof(buf), "%s", pfx);
+}
+
+int main(void)
+{
+	__analyzer_vreportf(USAGE_ERROR);
+	__analyzer_vreportf(USAGE_BUG);
+
+	return 0;
+}