@@ -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
@@ -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:
@@ -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 */
@@ -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. */
@@ -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;
};
new file mode 100644
@@ -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;
+}
new file mode 100644
@@ -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;
+}
new file mode 100644
@@ -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;
+}
new file mode 100644
@@ -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;
+}
new file mode 100644
@@ -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;
+}
new file mode 100644
@@ -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 = §ors[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;
+}
new file mode 100644
@@ -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;
+}
new file mode 100644
@@ -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;
+}