analyzer: rewrite of switch handling

When investigating false positives on the Linux kernel from
-Wanalyzer-use-of-uninitialized-value, I noticed that the existing
implementation of switch statements in the analyzer is broken.

Specifically, the existing implementation assumes a 1:1 association
between CFG out-edges from the basic block and case labels in the
gimple switch statement.  This happened to be the case in the
examples I had tested, but there is no such association in general.
In particular, in the motivating example:
  arch/x86/kernel/cpu/mtrr/if.c: mtrr_ioctl
the switch statement has 3 blocks, each covering multiple ranges of
ioctl command IDs for which different local variables are initialized,
which the existing implementation gets badly wrong. [1]

This patch reimplements switch handling in the analyzer to eliminate
this false assumption - instead, for each out-edge we gather the set
of case labels for that out-edge, and use that to determine the
set of value ranges for the edge.  Avoiding false positives for the
above example requires that we accurately track value ranges for
symbolic values, so the patch extends constraint_manager with a new
bounded_ranges_constraint, adding just enough information to capture the
ranges for switch statements whilst retaining combatility with the
existing constraint-handling (ultimately I'd prefer to simply throw
all of this into a SAT solver and let it track things).

Doing so fixes the false positives seen on the Linux kernel and an
existing xfail in the test suite.

The patch also fixes a long-standing bug in
constraint_manager::add_unknown_constraint when updating constraints
due to combining equivalence classes, spotted when debugging the
same logic for the new kind of constraints.

[1] a reduced version of this code is captured in this patch, in
gcc.dg/analyzer/torture/switch-3.c

gcc/analyzer/ChangeLog:
	* analyzer.h (struct rejected_constraint): Convert to...
	(class rejected_constraint): ...this.
	(class bounded_ranges): New forward decl.
	(class bounded_ranges_manager): New forward decl.
	* constraint-manager.cc: Include "analyzer/analyzer-logging.h" and
	"tree-pretty-print.h".
	(can_plus_one_p): New.
	(plus_one): New.
	(can_minus_one_p): New.
	(minus_one): New.
	(bounded_range::bounded_range): New.
	(dump_cst): New.
	(bounded_range::dump_to_pp): New.
	(bounded_range::dump): New.
	(bounded_range::to_json): New.
	(bounded_range::set_json_attr): New.
	(bounded_range::contains_p): New.
	(bounded_range::intersects_p): New.
	(bounded_range::operator==): New.
	(bounded_range::cmp): New.
	(bounded_ranges::bounded_ranges): New.
	(bounded_ranges::bounded_ranges): New.
	(bounded_ranges::bounded_ranges): New.
	(bounded_ranges::canonicalize): New.
	(bounded_ranges::validate): New.
	(bounded_ranges::operator==): New.
	(bounded_ranges::dump_to_pp): New.
	(bounded_ranges::dump): New.
	(bounded_ranges::to_json): New.
	(bounded_ranges::eval_condition): New.
	(bounded_ranges::contain_p): New.
	(bounded_ranges::cmp): New.
	(bounded_ranges_manager::~bounded_ranges_manager): New.
	(bounded_ranges_manager::get_or_create_empty): New.
	(bounded_ranges_manager::get_or_create_point): New.
	(bounded_ranges_manager::get_or_create_range): New.
	(bounded_ranges_manager::get_or_create_union): New.
	(bounded_ranges_manager::get_or_create_intersection): New.
	(bounded_ranges_manager::get_or_create_inverse): New.
	(bounded_ranges_manager::consolidate): New.
	(bounded_ranges_manager::get_or_create_ranges_for_switch): New.
	(bounded_ranges_manager::create_ranges_for_switch): New.
	(bounded_ranges_manager::make_case_label_ranges): New.
	(bounded_ranges_manager::log_stats): New.
	(bounded_ranges_constraint::print): New.
	(bounded_ranges_constraint::to_json): New.
	(bounded_ranges_constraint::operator==): New.
	(bounded_ranges_constraint::add_to_hash): New.
	(constraint_manager::constraint_manager): Update for new field
	m_bounded_ranges_constraints.
	(constraint_manager::operator=): Likewise.
	(constraint_manager::hash): Likewise.
	(constraint_manager::operator==): Likewise.
	(constraint_manager::print): Likewise.
	(constraint_manager::dump_to_pp): Likewise.
	(constraint_manager::to_json): Likewise.
	(constraint_manager::add_unknown_constraint): Update the lhs_ec_id
	if necessary in existing constraints when combining equivalence
	classes.  Add similar code for handling
	m_bounded_ranges_constraints.
	(constraint_manager::add_constraint_internal): Add comment.
	(constraint_manager::add_bounded_ranges): New.
	(constraint_manager::eval_condition): Use new field
	m_bounded_ranges_constraints.
	(constraint_manager::purge): Update bounded_ranges_constraint
	instances.
	(constraint_manager::canonicalize): Update for new field.
	(merger_fact_visitor::on_ranges): New.
	(constraint_manager::for_each_fact): Use new field
	m_bounded_ranges_constraints.
	(constraint_manager::validate):  Fix off-by-one error needed due
	to bug fixed above in add_unknown_constraint.  Validate the EC IDs
	in m_bounded_ranges_constraints.
	(constraint_manager::get_range_manager): New.
	(selftest::assert_dump_bounded_range_eq): New.
	(ASSERT_DUMP_BOUNDED_RANGE_EQ): New.
	(selftest::test_bounded_range): New.
	(selftest::assert_dump_bounded_ranges_eq): New.
	(ASSERT_DUMP_BOUNDED_RANGES_EQ): New.
	(selftest::test_bounded_ranges): New.
	(selftest::run_constraint_manager_tests): Call the new selftests.
	* constraint-manager.h (struct bounded_range): New.
	(struct bounded_ranges): New.
	(template <> struct default_hash_traits<bounded_ranges::key_t>): New.
	(class bounded_ranges_manager): New.
	(fact_visitor::on_ranges): New pure virtual function.
	(class bounded_ranges_constraint): New.
	(constraint_manager::add_bounded_ranges): New decl.
	(constraint_manager::get_range_manager): New decl.
	(constraint_manager::m_bounded_ranges_constraints): New field.
	* diagnostic-manager.cc (epath_finder::process_worklist_item):
	Transfer ownership of rc to add_feasibility_problem.
	* engine.cc (feasibility_problem::dump_to_pp): Use get_model.
	* feasible-graph.cc (infeasible_node::dump_dot): Update for
	conversion of m_rc to a pointer.
	(feasible_graph::add_feasibility_problem): Pass RC by pointer and
	take ownership.
	* feasible-graph.h (infeasible_node::infeasible_node): Pass RC by
	pointer and take ownership.
	(infeasible_node::~infeasible_node): New.
	(infeasible_node::m_rc): Convert to a pointer.
	(feasible_graph::add_feasibility_problem): Pass RC by pointer and
	take ownership.
	* region-model-manager.cc: Include
	"analyzer/constraint-manager.h".
	(region_model_manager::region_model_manager): Initializer new
	field m_range_mgr.
	(region_model_manager::~region_model_manager): Delete it.
	(region_model_manager::log_stats): Call log_stats on it.
	* region-model.cc (region_model::add_constraint): Use new subclass
	rejected_op_constraint.
	(region_model::apply_constraints_for_gswitch): Reimplement using
	bounded_ranges_manager.
	(rejected_constraint::dump_to_pp): Convert to...
	(rejected_op_constraint::dump_to_pp): ...this.
	(rejected_ranges_constraint::dump_to_pp): New.
	* region-model.h (struct purge_stats): Add field
	m_num_bounded_ranges_constraints.
	(region_model_manager::get_range_manager): New.
	(region_model_manager::m_range_mgr): New.
	(region_model::get_range_manager): New.
	(struct rejected_constraint): Split into...
	(class rejected_constraint):...this new abstract base class,
	and...
	(class rejected_op_constraint): ...this new concrete subclass.
	(class rejected_ranges_constraint): New.
	* supergraph.cc: Include "tree-cfg.h".
	(supergraph::supergraph): Drop idx param from add_cfg_edge.
	(supergraph::add_cfg_edge): Drop idx param.
	(switch_cfg_superedge::switch_cfg_superedge): Move here from
	header.  Populate m_case_labels with all cases which go to DST.
	(switch_cfg_superedge::dump_label_to_pp): Reimplement to use
	m_case_labels.
	(switch_cfg_superedge::get_case_label): Delete.
	* supergraph.h (supergraphadd_cfg_edge): Drop "idx" param.
	(switch_cfg_superedge::switch_cfg_superedge): Drop idx param and
	move implementation to supergraph.cc.
	(switch_cfg_superedge::get_case_label): Delete.
	(switch_cfg_superedge::get_case_labels): New.
	(switch_cfg_superedge::m_idx): Delete.
	(switch_cfg_superedge::m_case_labels): New field.

gcc/testsuite/ChangeLog:
	* gcc.dg/analyzer/switch.c: Remove xfail.  Add various tests.
	* gcc.dg/analyzer/torture/switch-2.c: New test.
	* gcc.dg/analyzer/torture/switch-3.c: New test.
	* gcc.dg/analyzer/torture/switch-4.c: New test.
	* gcc.dg/analyzer/torture/switch-5.c: New test.
This commit is contained in:
David Malcolm 2021-08-23 19:27:21 -04:00
parent 192d4edd15
commit 8ca7fa84a3
17 changed files with 2114 additions and 109 deletions

View file

@ -75,10 +75,12 @@ class region_model;
class region_model_context;
class impl_region_model_context;
class call_details;
struct rejected_constraint;
class rejected_constraint;
class constraint_manager;
class equiv_class;
class reachable_regions;
class bounded_ranges;
class bounded_ranges_manager;
class pending_diagnostic;
class state_change_event;

File diff suppressed because it is too large Load diff

View file

@ -64,6 +64,164 @@ struct range
bound m_upper_bound;
};
/* A closed range of values with constant integer bounds
e.g. [3, 5] for the set {3, 4, 5}. */
struct bounded_range
{
bounded_range (const_tree lower, const_tree upper);
void dump_to_pp (pretty_printer *pp, bool show_types) const;
void dump (bool show_types) const;
json::object *to_json () const;
bool contains_p (tree cst) const;
bool intersects_p (const bounded_range &other,
bounded_range *out) const;
bool operator== (const bounded_range &other) const;
bool operator!= (const bounded_range &other) const
{
return !(*this == other);
}
static int cmp (const bounded_range &a, const bounded_range &b);
tree m_lower;
tree m_upper;
private:
static void set_json_attr (json::object *obj, const char *name, tree value);
};
/* A collection of bounded_range instances, suitable
for representing the ranges on a case label within a switch
statement. */
struct bounded_ranges
{
public:
typedef bounded_ranges key_t;
bounded_ranges (const bounded_range &range);
bounded_ranges (const vec<bounded_range> &ranges);
bounded_ranges (enum tree_code op, tree rhs_const);
bool operator== (const bounded_ranges &other) const;
hashval_t get_hash () const { return m_hash; }
void dump_to_pp (pretty_printer *pp, bool show_types) const;
void dump (bool show_types) const;
json::value *to_json () const;
tristate eval_condition (enum tree_code op,
tree rhs_const,
bounded_ranges_manager *mgr) const;
bool contain_p (tree cst) const;
bool empty_p () const { return m_ranges.length () == 0; }
static int cmp (const bounded_ranges *a, const bounded_ranges *b);
private:
void canonicalize ();
void validate () const;
friend class bounded_ranges_manager;
auto_vec<bounded_range> m_ranges;
hashval_t m_hash;
};
} // namespace ana
template <> struct default_hash_traits<bounded_ranges::key_t>
: public member_function_hash_traits<bounded_ranges::key_t>
{
static const bool empty_zero_p = true;
};
namespace ana {
/* An object to own and consolidate bounded_ranges instances.
This also caches the mapping from switch_cfg_superedge
bounded_ranges instances, so that get_or_create_ranges_for_switch is
memoized. */
class bounded_ranges_manager
{
public:
~bounded_ranges_manager ();
const bounded_ranges *
get_or_create_ranges_for_switch (const switch_cfg_superedge *edge,
const gswitch *switch_stmt);
const bounded_ranges *get_or_create_empty ();
const bounded_ranges *get_or_create_point (const_tree value);
const bounded_ranges *get_or_create_range (const_tree lower_bound,
const_tree upper_bound);
const bounded_ranges *
get_or_create_union (const vec <const bounded_ranges *> &others);
const bounded_ranges *
get_or_create_intersection (const bounded_ranges *a,
const bounded_ranges *b);
const bounded_ranges *
get_or_create_inverse (const bounded_ranges *other, tree type);
void log_stats (logger *logger, bool show_objs) const;
private:
const bounded_ranges *
create_ranges_for_switch (const switch_cfg_superedge &edge,
const gswitch *switch_stmt);
const bounded_ranges *
make_case_label_ranges (const gswitch *switch_stmt,
tree case_label);
const bounded_ranges *consolidate (bounded_ranges *);
struct hash_traits_t : public typed_noop_remove<bounded_ranges *>
{
typedef bounded_ranges *key_type;
typedef bounded_ranges *value_type;
static inline bool
equal (const key_type &k1, const key_type &k2)
{
return *k1 == *k2;
}
static inline hashval_t
hash (const key_type &k)
{
return k->get_hash ();
}
static inline bool is_empty (key_type k) { return k == NULL; }
static inline void mark_empty (key_type &k) { k = NULL; }
static inline bool is_deleted (key_type k)
{
return k == reinterpret_cast<key_type> (1);
}
static const bool empty_zero_p = true;
};
struct traits_t : public simple_hashmap_traits<hash_traits_t,
bounded_ranges *>
{
};
typedef hash_map<bounded_ranges *, bounded_ranges *, traits_t> map_t;
map_t m_map;
typedef hash_map<const switch_cfg_superedge *,
const bounded_ranges *> edge_cache_t;
edge_cache_t m_edge_cache;
};
/* An equivalence class within a constraint manager: a set of
svalues that are known to all be equal to each other,
together with an optional tree constant that they are equal to. */
@ -190,6 +348,33 @@ class fact_visitor
virtual void on_fact (const svalue *lhs,
enum tree_code,
const svalue *rhs) = 0;
virtual void on_ranges (const svalue *lhs,
const bounded_ranges *ranges) = 0;
};
class bounded_ranges_constraint
{
public:
bounded_ranges_constraint (equiv_class_id ec_id,
const bounded_ranges *ranges)
: m_ec_id (ec_id), m_ranges (ranges)
{
}
void print (pretty_printer *pp, const constraint_manager &cm) const;
json::object *to_json () const;
bool operator== (const bounded_ranges_constraint &other) const;
bool operator!= (const bounded_ranges_constraint &other) const
{
return !(*this == other);
}
void add_to_hash (inchash::hash *hstate) const;
equiv_class_id m_ec_id;
const bounded_ranges *m_ranges;
};
/* A collection of equivalence classes and constraints on them.
@ -248,6 +433,9 @@ public:
enum tree_code op,
equiv_class_id rhs_ec_id);
bool add_bounded_ranges (const svalue *sval,
const bounded_ranges *ranges);
bool get_equiv_class_by_svalue (const svalue *sval,
equiv_class_id *out) const;
equiv_class_id get_or_add_equiv_class (const svalue *sval);
@ -281,8 +469,11 @@ public:
void validate () const;
bounded_ranges_manager *get_range_manager () const;
auto_delete_vec<equiv_class> m_equiv_classes;
auto_vec<constraint> m_constraints;
auto_vec<bounded_ranges_constraint> m_bounded_ranges_constraints;
private:
void add_constraint_internal (equiv_class_id lhs_id,

View file

@ -520,8 +520,7 @@ epath_finder::process_worklist_item (feasible_worklist *worklist,
gcc_assert (rc);
fg->add_feasibility_problem (fnode,
succ_eedge,
*rc);
delete rc;
rc);
/* Give up if there have been too many infeasible edges. */
if (fg->get_num_infeasible ()

View file

@ -3842,7 +3842,7 @@ feasibility_problem::dump_to_pp (pretty_printer *pp) const
pp_string (pp, "; rejected constraint: ");
m_rc->dump_to_pp (pp);
pp_string (pp, "; rmodel: ");
m_rc->m_model.dump_to_pp (pp, true, false);
m_rc->get_model ().dump_to_pp (pp, true, false);
}
}

View file

@ -129,7 +129,7 @@ infeasible_node::dump_dot (graphviz_out *gv,
pp_string (pp, "rejected constraint:");
pp_newline (pp);
m_rc.dump_to_pp (pp);
m_rc->dump_to_pp (pp);
pp_write_text_as_dot_label_to_stream (pp, /*for_record=*/true);
@ -178,12 +178,13 @@ feasible_graph::add_node (const exploded_node *enode,
}
/* Add an infeasible_node to this graph and an infeasible_edge connecting
to it from SRC_FNODE, capturing a failure of RC along EEDGE. */
to it from SRC_FNODE, capturing a failure of RC along EEDGE.
Takes ownership of RC. */
void
feasible_graph::add_feasibility_problem (feasible_node *src_fnode,
const exploded_edge *eedge,
const rejected_constraint &rc)
rejected_constraint *rc)
{
infeasible_node *dst_fnode
= new infeasible_node (eedge->m_dest, m_nodes.length (), rc);

View file

@ -115,17 +115,18 @@ class infeasible_node : public base_feasible_node
{
public:
infeasible_node (const exploded_node *inner_node, unsigned index,
const rejected_constraint &rc)
rejected_constraint *rc)
: base_feasible_node (inner_node, index),
m_rc (rc)
{
}
~infeasible_node () { delete m_rc; }
void dump_dot (graphviz_out *gv,
const dump_args_t &args) const FINAL OVERRIDE;
private:
rejected_constraint m_rc;
rejected_constraint *m_rc;
};
/* Base class of edge within a feasible_graph. */
@ -192,7 +193,7 @@ class feasible_graph : public digraph <fg_traits>
void add_feasibility_problem (feasible_node *src_fnode,
const exploded_edge *eedge,
const rejected_constraint &rc);
rejected_constraint *rc);
exploded_path *make_epath (feasible_node *fnode) const;

View file

@ -56,6 +56,7 @@ along with GCC; see the file COPYING3. If not see
#include "analyzer/program-point.h"
#include "analyzer/store.h"
#include "analyzer/region-model.h"
#include "analyzer/constraint-manager.h"
#if ENABLE_ANALYZER
@ -77,7 +78,8 @@ region_model_manager::region_model_manager ()
m_fndecls_map (), m_labels_map (),
m_globals_region (alloc_region_id (), &m_root_region),
m_globals_map (),
m_store_mgr (this)
m_store_mgr (this),
m_range_mgr (new bounded_ranges_manager ())
{
}
@ -142,6 +144,8 @@ region_model_manager::~region_model_manager ()
for (string_map_t::iterator iter = m_string_map.begin ();
iter != m_string_map.end (); ++iter)
delete (*iter).second;
delete m_range_mgr;
}
/* Return true if C exceeds the complexity limit for svalues. */
@ -1574,6 +1578,7 @@ region_model_manager::log_stats (logger *logger, bool show_objs) const
logger->log (" # managed dynamic regions: %i",
m_managed_dynamic_regions.length ());
m_store_mgr.log_stats (logger, show_objs);
m_range_mgr->log_stats (logger, show_objs);
}
/* Dump the number of objects of each class that were managed by this

View file

@ -2773,7 +2773,7 @@ region_model::add_constraint (tree lhs, enum tree_code op, tree rhs,
{
bool sat = add_constraint (lhs, op, rhs, ctxt);
if (!sat && out)
*out = new rejected_constraint (*this, lhs, op, rhs);
*out = new rejected_op_constraint (*this, lhs, op, rhs);
return sat;
}
@ -3329,56 +3329,15 @@ region_model::apply_constraints_for_gswitch (const switch_cfg_superedge &edge,
region_model_context *ctxt,
rejected_constraint **out)
{
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);
tree case_label = edge.get_case_label ();
gcc_assert (TREE_CODE (case_label) == CASE_LABEL_EXPR);
tree lower_bound = CASE_LOW (case_label);
tree upper_bound = CASE_HIGH (case_label);
if (lower_bound)
{
if (upper_bound)
{
/* Range. */
if (!add_constraint (index, GE_EXPR, lower_bound, ctxt, out))
return false;
return add_constraint (index, LE_EXPR, upper_bound, ctxt, out);
}
else
/* Single-value. */
return add_constraint (index, EQ_EXPR, lower_bound, ctxt, out);
}
else
{
/* The default case.
Add exclusions based on the other cases. */
for (unsigned other_idx = 1;
other_idx < gimple_switch_num_labels (switch_stmt);
other_idx++)
{
tree other_label = gimple_switch_label (switch_stmt,
other_idx);
tree other_lower_bound = CASE_LOW (other_label);
tree other_upper_bound = CASE_HIGH (other_label);
gcc_assert (other_lower_bound);
if (other_upper_bound)
{
/* Exclude this range-valued case.
For now, we just exclude the boundary values.
TODO: exclude the values within the region. */
if (!add_constraint (index, NE_EXPR, other_lower_bound,
ctxt, out))
return false;
if (!add_constraint (index, NE_EXPR, other_upper_bound,
ctxt, out))
return false;
}
else
/* Exclude this single-valued case. */
if (!add_constraint (index, NE_EXPR, other_lower_bound, ctxt, out))
return false;
}
return true;
}
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);
return sat;
}
/* Apply any constraints due to an exception being thrown at LAST_STMT.
@ -3860,10 +3819,10 @@ debug (const region_model &rmodel)
rmodel.dump (false);
}
/* struct rejected_constraint. */
/* class rejected_op_constraint : public rejected_constraint. */
void
rejected_constraint::dump_to_pp (pretty_printer *pp) const
rejected_op_constraint::dump_to_pp (pretty_printer *pp) const
{
region_model m (m_model);
const svalue *lhs_sval = m.get_rvalue (m_lhs, NULL);
@ -3873,6 +3832,18 @@ rejected_constraint::dump_to_pp (pretty_printer *pp) const
rhs_sval->dump_to_pp (pp, true);
}
/* class rejected_ranges_constraint : public rejected_constraint. */
void
rejected_ranges_constraint::dump_to_pp (pretty_printer *pp) const
{
region_model m (m_model);
const svalue *sval = m.get_rvalue (m_expr, NULL);
sval->dump_to_pp (pp, true);
pp_string (pp, " in ");
m_ranges->dump_to_pp (pp, true);
}
/* class engine. */
/* Dump the managed objects by class to LOGGER, and the per-class totals. */

View file

@ -189,6 +189,7 @@ struct purge_stats
m_num_regions (0),
m_num_equiv_classes (0),
m_num_constraints (0),
m_num_bounded_ranges_constraints (0),
m_num_client_items (0)
{}
@ -196,6 +197,7 @@ struct purge_stats
int m_num_regions;
int m_num_equiv_classes;
int m_num_constraints;
int m_num_bounded_ranges_constraints;
int m_num_client_items;
};
@ -320,6 +322,7 @@ public:
unsigned alloc_region_id () { return m_next_region_id++; }
store_manager *get_store_manager () { return &m_store_mgr; }
bounded_ranges_manager *get_range_manager () const { return m_range_mgr; }
/* Dynamically-allocated region instances.
The number of these within the analysis can grow arbitrarily.
@ -456,6 +459,8 @@ private:
store_manager m_store_mgr;
bounded_ranges_manager *m_range_mgr;
/* "Dynamically-allocated" region instances.
The number of these within the analysis can grow arbitrarily.
They are still owned by the manager. */
@ -698,6 +703,10 @@ class region_model
void unset_dynamic_extents (const region *reg);
region_model_manager *get_manager () const { return m_mgr; }
bounded_ranges_manager *get_range_manager () const
{
return m_mgr->get_range_manager ();
}
void unbind_region_and_descendents (const region *reg,
enum poison_kind pkind);
@ -945,21 +954,54 @@ struct model_merger
/* A record that can (optionally) be written out when
region_model::add_constraint fails. */
struct rejected_constraint
class rejected_constraint
{
rejected_constraint (const region_model &model,
tree lhs, enum tree_code op, tree rhs)
: m_model (model), m_lhs (lhs), m_op (op), m_rhs (rhs)
public:
virtual ~rejected_constraint () {}
virtual void dump_to_pp (pretty_printer *pp) const = 0;
const region_model &get_model () const { return m_model; }
protected:
rejected_constraint (const region_model &model)
: m_model (model)
{}
void dump_to_pp (pretty_printer *pp) const;
region_model m_model;
};
class rejected_op_constraint : public rejected_constraint
{
public:
rejected_op_constraint (const region_model &model,
tree lhs, enum tree_code op, tree rhs)
: rejected_constraint (model),
m_lhs (lhs), m_op (op), m_rhs (rhs)
{}
void dump_to_pp (pretty_printer *pp) const FINAL OVERRIDE;
tree m_lhs;
enum tree_code m_op;
tree m_rhs;
};
class rejected_ranges_constraint : public rejected_constraint
{
public:
rejected_ranges_constraint (const region_model &model,
tree expr, const bounded_ranges *ranges)
: rejected_constraint (model),
m_expr (expr), m_ranges (ranges)
{}
void dump_to_pp (pretty_printer *pp) const FINAL OVERRIDE;
private:
tree m_expr;
const bounded_ranges *m_ranges;
};
/* A bundle of state. */
class engine

View file

@ -50,6 +50,7 @@ along with GCC; see the file COPYING3. If not see
#include "cgraph.h"
#include "cfg.h"
#include "digraph.h"
#include "tree-cfg.h"
#include "analyzer/supergraph.h"
#include "analyzer/analyzer-logging.h"
@ -246,7 +247,7 @@ supergraph::supergraph (logger *logger)
supernode *dest_supernode
= *m_bb_to_initial_node.get (dest_cfg_block);
cfg_superedge *cfg_sedge
= add_cfg_edge (src_supernode, dest_supernode, cfg_edge, idx);
= add_cfg_edge (src_supernode, dest_supernode, cfg_edge);
m_cfg_edge_to_cfg_superedge.put (cfg_edge, cfg_sedge);
}
}
@ -505,17 +506,16 @@ supergraph::add_node (function *fun, basic_block bb, gcall *returning_call,
adding it to this supergraph.
If the edge is for a switch statement, create a switch_cfg_superedge
subclass using IDX (the index of E within the out-edges from SRC's
underlying basic block). */
subclass. */
cfg_superedge *
supergraph::add_cfg_edge (supernode *src, supernode *dest, ::edge e, int idx)
supergraph::add_cfg_edge (supernode *src, supernode *dest, ::edge e)
{
/* Special-case switch edges. */
gimple *stmt = src->get_last_stmt ();
cfg_superedge *new_edge;
if (stmt && stmt->code == GIMPLE_SWITCH)
new_edge = new switch_cfg_superedge (src, dest, e, idx);
new_edge = new switch_cfg_superedge (src, dest, e);
else
new_edge = new cfg_superedge (src, dest, e);
add_edge (new_edge);
@ -1072,6 +1072,23 @@ cfg_superedge::get_phi_arg (const gphi *phi) const
return gimple_phi_arg_def (phi, index);
}
switch_cfg_superedge::switch_cfg_superedge (supernode *src,
supernode *dst,
::edge e)
: cfg_superedge (src, dst, e)
{
/* Populate m_case_labels with all cases which go to DST. */
const gswitch *gswitch = get_switch_stmt ();
for (unsigned i = 0; i < gimple_switch_num_labels (gswitch); i++)
{
tree case_ = gimple_switch_label (gswitch, i);
basic_block bb = label_to_block (src->get_function (),
CASE_LABEL (case_));
if (bb == dst->m_bb)
m_case_labels.safe_push (case_);
}
}
/* Implementation of superedge::dump_label_to_pp for CFG superedges for
"switch" statements.
@ -1081,31 +1098,63 @@ void
switch_cfg_superedge::dump_label_to_pp (pretty_printer *pp,
bool user_facing ATTRIBUTE_UNUSED) const
{
tree case_label = get_case_label ();
gcc_assert (TREE_CODE (case_label) == CASE_LABEL_EXPR);
tree lower_bound = CASE_LOW (case_label);
tree upper_bound = CASE_HIGH (case_label);
if (lower_bound)
if (user_facing)
{
pp_printf (pp, "case ");
dump_generic_node (pp, lower_bound, 0, (dump_flags_t)0, false);
if (upper_bound)
for (unsigned i = 0; i < m_case_labels.length (); ++i)
{
pp_printf (pp, " ... ");
dump_generic_node (pp, upper_bound, 0, (dump_flags_t)0, false);
if (i > 0)
pp_string (pp, ", ");
tree case_label = m_case_labels[i];
gcc_assert (TREE_CODE (case_label) == CASE_LABEL_EXPR);
tree lower_bound = CASE_LOW (case_label);
tree upper_bound = CASE_HIGH (case_label);
if (lower_bound)
{
pp_printf (pp, "case ");
dump_generic_node (pp, lower_bound, 0, (dump_flags_t)0, false);
if (upper_bound)
{
pp_printf (pp, " ... ");
dump_generic_node (pp, upper_bound, 0, (dump_flags_t)0,
false);
}
pp_printf (pp, ":");
}
else
pp_printf (pp, "default:");
}
pp_printf (pp, ":");
}
else
pp_printf (pp, "default:");
}
/* Get the case label for this "switch" superedge. */
tree
switch_cfg_superedge::get_case_label () const
{
return gimple_switch_label (get_switch_stmt (), m_idx);
{
pp_character (pp, '{');
for (unsigned i = 0; i < m_case_labels.length (); ++i)
{
if (i > 0)
pp_string (pp, ", ");
tree case_label = m_case_labels[i];
gcc_assert (TREE_CODE (case_label) == CASE_LABEL_EXPR);
tree lower_bound = CASE_LOW (case_label);
tree upper_bound = CASE_HIGH (case_label);
if (lower_bound)
{
if (upper_bound)
{
pp_character (pp, '[');
dump_generic_node (pp, lower_bound, 0, (dump_flags_t)0,
false);
pp_string (pp, ", ");
dump_generic_node (pp, upper_bound, 0, (dump_flags_t)0,
false);
pp_character (pp, ']');
}
else
dump_generic_node (pp, lower_bound, 0, (dump_flags_t)0, false);
}
else
pp_printf (pp, "default");
}
pp_character (pp, '}');
}
}
/* Implementation of superedge::dump_label_to_pp for interprocedural

View file

@ -181,7 +181,7 @@ public:
private:
supernode *add_node (function *fun, basic_block bb, gcall *returning_call,
gimple_seq phi_nodes);
cfg_superedge *add_cfg_edge (supernode *src, supernode *dest, ::edge e, int idx);
cfg_superedge *add_cfg_edge (supernode *src, supernode *dest, ::edge e);
call_superedge *add_call_superedge (supernode *src, supernode *dest,
cgraph_edge *cedge);
return_superedge *add_return_superedge (supernode *src, supernode *dest,
@ -539,15 +539,12 @@ is_a_helper <const cfg_superedge *>::test (const superedge *sedge)
namespace ana {
/* A subclass for edges from switch statements, retaining enough
information to identify the pertinent case, and for adding labels
information to identify the pertinent cases, and for adding labels
when rendering via graphviz. */
class switch_cfg_superedge : public cfg_superedge {
public:
switch_cfg_superedge (supernode *src, supernode *dst, ::edge e, int idx)
: cfg_superedge (src, dst, e),
m_idx (idx)
{}
switch_cfg_superedge (supernode *src, supernode *dst, ::edge e);
const switch_cfg_superedge *dyn_cast_switch_cfg_superedge () const
FINAL OVERRIDE
@ -563,10 +560,10 @@ class switch_cfg_superedge : public cfg_superedge {
return as_a <gswitch *> (m_src->get_last_stmt ());
}
tree get_case_label () const;
const vec<tree> &get_case_labels () const { return m_case_labels; }
private:
const int m_idx;
private:
auto_vec<tree> m_case_labels;
};
} // namespace ana

View file

@ -8,23 +8,156 @@ void test (int i)
{
case 0:
__analyzer_eval (i == 0); /* { dg-warning "TRUE" } */
__analyzer_eval (i != -1); /* { dg-warning "TRUE" } */
__analyzer_eval (i != 0); /* { dg-warning "FALSE" } */
__analyzer_eval (i != 1); /* { dg-warning "TRUE" } */
break;
case 3 ... 5:
__analyzer_eval (i != 0); /* { dg-warning "TRUE" } */
__analyzer_eval (i > 1); /* { dg-warning "TRUE" } */
__analyzer_eval (i > 2); /* { dg-warning "TRUE" } */
__analyzer_eval (i >= 2); /* { dg-warning "TRUE" } */
__analyzer_eval (i >= 3); /* { dg-warning "TRUE" } */
__analyzer_eval (i <= 5); /* { dg-warning "TRUE" } */
__analyzer_eval (i < 6); /* { dg-warning "TRUE" } */
__analyzer_eval (i <= 6); /* { dg-warning "TRUE" } */
__analyzer_eval (i < 7); /* { dg-warning "TRUE" } */
__analyzer_eval (i != 6); /* { dg-warning "TRUE" } */
__analyzer_eval (i != 3); /* { dg-warning "UNKNOWN" } */
__analyzer_eval (i != 4); /* { dg-warning "UNKNOWN" } */
__analyzer_eval (i != 5); /* { dg-warning "UNKNOWN" } */
__analyzer_eval (i >= 4); /* { dg-warning "UNKNOWN" } */
__analyzer_eval (i >= 5); /* { dg-warning "UNKNOWN" } */
__analyzer_eval (i <= 3); /* { dg-warning "UNKNOWN" } */
__analyzer_eval (i <= 4); /* { dg-warning "UNKNOWN" } */
break;
default:
__analyzer_eval (i == -1); /* { dg-warning "UNKNOWN" } */
__analyzer_eval (i == 0); /* { dg-warning "FALSE" } */
__analyzer_eval (i == 2); /* { dg-warning "UNKNOWN" } */
__analyzer_eval (i == 3); /* { dg-warning "FALSE" } */
__analyzer_eval (i == 4); /* { dg-warning "FALSE" "desired" { xfail *-*-* } } */
/* { dg-warning "UNKNOWN" "status quo" { target *-*-* } .-1 } */
/* TODO(xfail^^^): we're only checking against endpoints of case
ranges, not the insides. */
__analyzer_eval (i == 4); /* { dg-warning "FALSE" } */
__analyzer_eval (i == 5); /* { dg-warning "FALSE" } */
__analyzer_eval (i == 6); /* { dg-warning "UNKNOWN" } */
__analyzer_eval (i != 0); /* { dg-warning "TRUE" } */
__analyzer_eval (i != 1); /* { dg-warning "UNKNOWN" } */
__analyzer_eval (i != 3); /* { dg-warning "TRUE" } */
__analyzer_eval (i != 4); /* { dg-warning "TRUE" } */
__analyzer_eval (i != 5); /* { dg-warning "TRUE" } */
__analyzer_eval (i != 6); /* { dg-warning "UNKNOWN" } */
break;
}
}
/* Verify that the analyzer follows the correct paths on a
switch statement guarded by an if, using noinline to defeat
optimizations. */
static void __attribute__((noinline))
__analyzer_called_by_test_2 (int y)
{
switch (y)
{
case 0:
__analyzer_dump_path (); /* { dg-bogus "path" } */
break;
case 1:
__analyzer_dump_path (); /* { dg-message "path" } */
break;
case 2:
__analyzer_dump_path (); /* { dg-bogus "path" } */
break;
default:
__analyzer_dump_path (); /* { dg-bogus "path" } */
break;
}
}
void test_2 (int x)
{
if (x == 1)
__analyzer_called_by_test_2 (x);
}
void test_3 (int x, int y)
{
if (y == 3)
switch (x)
{
case 0 ... 9:
case 20 ... 29:
if (x == y)
__analyzer_dump_path (); /* { dg-message "path" } */
else
__analyzer_dump_path (); /* { dg-message "path" } */
}
}
struct s4
{
unsigned char level:3;
unsigned char key_id_mode:2;
unsigned char reserved:3;
};
void test_4 (struct s4 *p)
{
switch (p->key_id_mode)
{
case 0:
__analyzer_dump_path (); /* { dg-message "path" } */
break;
case 1:
__analyzer_dump_path (); /* { dg-message "path" } */
break;
case 2:
__analyzer_dump_path (); /* { dg-message "path" } */
break;
case 3:
__analyzer_dump_path (); /* { dg-message "path" } */
break;
}
__analyzer_dump_path (); /* { dg-message "path" } */
}
int test_5 (unsigned v)
{
switch (v)
{
case 0:
return 7;
break;
case 1:
return 23;
break;
default:
return v * 2;
}
}
int test_6 (unsigned v)
{
switch (v)
{
case 0:
return 3;
case -1:
return 22;
}
return -3;
}
int g7 = -1;
int test_7 ()
{
switch (g7++) {
case 0:
return 32;
case 100:
return 42;
}
return 0;
}

View file

@ -0,0 +1,42 @@
struct s
{
int f0;
int f1;
};
int test (int cmd)
{
int err = 0;
struct s foo;
struct s bar;
switch (cmd)
{
case 0:
foo.f0 = 0;
break;
case 1:
foo.f0 = 1;
break;
case 30 ... 50:
case 70 ... 80:
__builtin_memset (&bar, 0, sizeof (bar));
break;
}
switch (cmd)
{
default:
return -1;
case 0 ... 1:
return foo.f0;
break;
case 42:
return bar.f1;
break;
case 65:
return bar.f1;
break;
}
return err;
}

View file

@ -0,0 +1,158 @@
typedef unsigned int __u32;
__extension__ typedef unsigned long long __u64;
extern unsigned long
copy_from_user(void *to, const void *from, unsigned long n);
extern unsigned long
copy_to_user(void *to, const void *from, unsigned long n);
struct mtrr_sentry {
__u64 base;
__u32 size;
__u32 type;
};
struct mtrr_gentry {
__u64 base;
__u32 size;
__u32 regnum;
__u32 type;
__u32 _pad;
};
#define _IOC_NRBITS 8
#define _IOC_TYPEBITS 8
#define _IOC_SIZEBITS 14
#define _IOC_DIRBITS 2
#define _IOC_NRSHIFT 0
#define _IOC_TYPESHIFT (_IOC_NRSHIFT+_IOC_NRBITS)
#define _IOC_SIZESHIFT (_IOC_TYPESHIFT+_IOC_TYPEBITS)
#define _IOC_DIRSHIFT (_IOC_SIZESHIFT+_IOC_SIZEBITS)
#define _IOC_WRITE 1U
#define _IOC_READ 2U
#define _IOC(dir,type,nr,size) \
(((dir) << _IOC_DIRSHIFT) | \
((type) << _IOC_TYPESHIFT) | \
((nr) << _IOC_NRSHIFT) | \
((size) << _IOC_SIZESHIFT))
#define _IOC_TYPECHECK(t) (sizeof(t))
#define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
#define MTRR_IOCTL_BASE 'M'
#define EFAULT 14
#define EINVAL 22
#define ENOTTY 25
#define MTRRIOC_ADD_ENTRY _IOW(MTRR_IOCTL_BASE, 0, struct mtrr_sentry)
#define MTRRIOC_SET_ENTRY _IOW(MTRR_IOCTL_BASE, 1, struct mtrr_sentry)
#define MTRRIOC_DEL_ENTRY _IOW(MTRR_IOCTL_BASE, 2, struct mtrr_sentry)
#define MTRRIOC_GET_ENTRY _IOWR(MTRR_IOCTL_BASE, 3, struct mtrr_gentry)
#define MTRRIOC_KILL_ENTRY _IOW(MTRR_IOCTL_BASE, 4, struct mtrr_sentry)
#define MTRRIOC_ADD_PAGE_ENTRY _IOW(MTRR_IOCTL_BASE, 5, struct mtrr_sentry)
#define MTRRIOC_SET_PAGE_ENTRY _IOW(MTRR_IOCTL_BASE, 6, struct mtrr_sentry)
#define MTRRIOC_DEL_PAGE_ENTRY _IOW(MTRR_IOCTL_BASE, 7, struct mtrr_sentry)
#define MTRRIOC_GET_PAGE_ENTRY _IOWR(MTRR_IOCTL_BASE, 8, struct mtrr_gentry)
#define MTRRIOC_KILL_PAGE_ENTRY _IOW(MTRR_IOCTL_BASE, 9, struct mtrr_sentry)
extern void check_init_u64 (__u64 v);
extern void check_init_u32 (__u32 v);
/* Adapted/reduced from arch/x86/kernel/cpu/mtrr/if.c: mtrr_ioctl,
which is GPL-2.0 */
long mtrr_ioctl(unsigned int cmd, unsigned long __arg) {
int err = 0;
struct mtrr_sentry sentry;
struct mtrr_gentry gentry;
void *arg = (void *)__arg;
__builtin_memset(&gentry, 0, sizeof(gentry));
switch (cmd) {
case MTRRIOC_ADD_ENTRY:
case MTRRIOC_SET_ENTRY:
case MTRRIOC_DEL_ENTRY:
case MTRRIOC_KILL_ENTRY:
case MTRRIOC_ADD_PAGE_ENTRY:
case MTRRIOC_SET_PAGE_ENTRY:
case MTRRIOC_DEL_PAGE_ENTRY:
case MTRRIOC_KILL_PAGE_ENTRY:
if (copy_from_user(&sentry, arg, sizeof(sentry)))
return -EFAULT;
break;
case MTRRIOC_GET_ENTRY:
case MTRRIOC_GET_PAGE_ENTRY:
if (copy_from_user(&gentry, arg, sizeof(gentry)))
return -EFAULT;
break;
}
switch (cmd) {
default:
return -ENOTTY;
case MTRRIOC_ADD_ENTRY:
check_init_u64 (sentry.base);
check_init_u32 (sentry.size);
check_init_u32 (sentry.type);
break;
case MTRRIOC_SET_ENTRY:
check_init_u64 (sentry.base);
check_init_u32 (sentry.size);
check_init_u32 (sentry.type);
break;
case MTRRIOC_DEL_ENTRY:
check_init_u64 (sentry.base);
check_init_u32 (sentry.size);
check_init_u32 (sentry.type);
break;
case MTRRIOC_KILL_ENTRY:
check_init_u64 (sentry.base);
check_init_u32 (sentry.size);
check_init_u32 (sentry.type);
break;
case MTRRIOC_GET_ENTRY:
check_init_u64 (gentry.base);
check_init_u32 (gentry.size);
check_init_u32 (gentry.regnum);
check_init_u32 (gentry.type);
check_init_u32 (gentry._pad);
break;
case MTRRIOC_ADD_PAGE_ENTRY:
check_init_u64 (sentry.base);
check_init_u32 (sentry.size);
check_init_u32 (sentry.type);
break;
case MTRRIOC_SET_PAGE_ENTRY:
check_init_u64 (sentry.base);
check_init_u32 (sentry.size);
check_init_u32 (sentry.type);
break;
case MTRRIOC_DEL_PAGE_ENTRY:
check_init_u64 (sentry.base);
check_init_u32 (sentry.size);
check_init_u32 (sentry.type);
break;
case MTRRIOC_KILL_PAGE_ENTRY:
check_init_u64 (sentry.base);
check_init_u32 (sentry.size);
check_init_u32 (sentry.type);
break;
case MTRRIOC_GET_PAGE_ENTRY:
check_init_u64 (gentry.base);
check_init_u32 (gentry.size);
check_init_u32 (gentry.regnum);
check_init_u32 (gentry.type);
check_init_u32 (gentry._pad);
break;
}
return err;
}

View file

@ -0,0 +1,27 @@
struct snd_ac97 {
// snip
unsigned int id;
// snip
};
int snd_ac97_valid_reg(struct snd_ac97 *ac97, unsigned short reg) {
switch (ac97->id) {
case 0x53544d02:
if (reg == 0x22 || reg == 0x7a)
return 1;
__attribute__((__fallthrough__));
case 0x414b4d00:
return 0;
}
return 1;
}
int snd_ac97_update_bits(struct snd_ac97 *ac97, unsigned short reg) {
if (ac97->id == 0x414c4781)
{
if (!snd_ac97_valid_reg(ac97, reg))
return -22;
}
return 0;
}

View file

@ -0,0 +1,68 @@
/* { dg-additional-options "-fno-analyzer-call-summaries" } */
typedef unsigned char u8;
typedef signed int s32;
typedef unsigned int u32;
enum v4l2_mpeg_video_hevc_profile {
V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN = 0,
V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN_STILL_PICTURE = 1,
V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN_10 = 2
};
enum v4l2_buf_type {
V4L2_BUF_TYPE_VIDEO_CAPTURE = 1,
V4L2_BUF_TYPE_VIDEO_OUTPUT = 2
};
struct v4l2_fmtdesc {
u32 index;
u32 type;
};
struct v4l2_ctrl;
s32 v4l2_ctrl_g_ctrl(struct v4l2_ctrl *ctrl);
struct create_channel_param {
u8 profile;
};
u8
hevc_profile_to_mcu_profile(enum v4l2_mpeg_video_hevc_profile profile) {
switch (profile) {
default:
case V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN:
return 1;
case V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN_10:
return 2;
case V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN_STILL_PICTURE:
return 3;
}
}
int fill_create_channel_param(struct v4l2_ctrl *ctrl,
struct create_channel_param *param) {
enum v4l2_mpeg_video_hevc_profile profile;
profile = v4l2_ctrl_g_ctrl(ctrl);
param->profile = hevc_profile_to_mcu_profile(profile);
return 0;
}
int allegro_enum_fmt_vid(struct v4l2_fmtdesc *f) {
switch (f->type) {
case V4L2_BUF_TYPE_VIDEO_OUTPUT:
if (f->index >= 1)
return -22;
break;
case V4L2_BUF_TYPE_VIDEO_CAPTURE:
if (f->index >= 2)
return -22;
break;
default:
return -22;
}
return 0;
}
int allegro_ioctl_streamon(struct v4l2_ctrl *ctrl,
struct create_channel_param *param) {
fill_create_channel_param(ctrl, param);
return 0;
}