class region_model_context;
class impl_region_model_context;
class call_details;
+struct rejected_constraint;
class constraint_manager;
class equiv_class;
Common Var(flag_analyzer_fine_grained) Init(0)
Avoid combining multiple statements into one exploded edge.
+fanalyzer-feasibility
+Common Var(flag_analyzer_feasibility) Init(1)
+Verify that paths are feasible when emitting diagnostics.
+
fanalyzer-show-duplicate-count
Common Var(flag_analyzer_show_duplicate_count) Init(0)
Issue a note when diagnostics are deduplicated.
{
public:
path_builder (const exploded_graph &eg,
- const exploded_path &epath)
+ const exploded_path &epath,
+ const feasibility_problem *problem)
: m_eg (eg),
m_diag_enode (epath.get_final_enode ()),
- m_reachability (eg, m_diag_enode)
+ m_reachability (eg, m_diag_enode),
+ m_feasibility_problem (problem)
{}
const exploded_node *get_diag_node () const { return m_diag_enode; }
const extrinsic_state &get_ext_state () const { return m_eg.get_ext_state (); }
+ const feasibility_problem *get_feasibility_problem () const
+ {
+ return m_feasibility_problem;
+ }
+
private:
typedef reachability<eg_traits> enode_reachability;
/* Precompute all enodes from which the diagnostic is reachable. */
enode_reachability m_reachability;
+
+ const feasibility_problem *m_feasibility_problem;
};
/* class diagnostic_manager. */
sd->m_snode->m_index);
feasibility_problem *p = NULL;
- if (!dc->get_path ().feasible_p (logger, &p, m_engine, eg))
+ if (dc->get_path ().feasible_p (logger, &p, m_engine, eg))
{
if (logger)
- logger->log ("rejecting %qs at EN: %i, SN: %i"
- " due to infeasible path",
+ logger->log ("accepting %qs at EN: %i, SN: %i with feasible path",
sd->m_d->get_kind (), sd->m_enode->m_index,
sd->m_snode->m_index);
- sd->set_infeasible (p);
- delete dc;
- return;
+ sd->set_feasible ();
}
else
- if (logger)
- logger->log ("accepting %qs at EN: %i, SN: %i with feasible path",
- sd->m_d->get_kind (), sd->m_enode->m_index,
- sd->m_snode->m_index);
-
- sd->set_feasible ();
+ {
+ if (flag_analyzer_feasibility)
+ {
+ if (logger)
+ logger->log ("rejecting %qs at EN: %i, SN: %i"
+ " due to infeasible path",
+ sd->m_d->get_kind (), sd->m_enode->m_index,
+ sd->m_snode->m_index);
+ sd->set_infeasible (p);
+ delete dc;
+ return;
+ }
+ else
+ {
+ if (logger)
+ logger->log ("accepting %qs at EN: %i, SN: %i"
+ " despite infeasible path (due to %qs)",
+ sd->m_d->get_kind (), sd->m_enode->m_index,
+ sd->m_snode->m_index,
+ "-fno-analyzer-feasibility");
+ sd->set_infeasible (p);
+ }
+ }
dedupe_key *key = new dedupe_key (*sd, dc->get_path ());
if (dedupe_candidate **slot = m_map.get (key))
pretty_printer *pp = global_dc->printer->clone ();
/* Precompute all enodes from which the diagnostic is reachable. */
- path_builder pb (eg, epath);
+ path_builder pb (eg, epath, sd.get_feasibility_problem ());
/* This is the diagnostic_path subclass that will be built for
the diagnostic. */
}
break;
}
+
+ if (pb.get_feasibility_problem ()
+ && &pb.get_feasibility_problem ()->m_eedge == &eedge)
+ {
+ pretty_printer pp;
+ pp_format_decoder (&pp) = default_tree_printer;
+ pp_string (&pp,
+ "this path would have been rejected as infeasible"
+ " at this edge: ");
+ pb.get_feasibility_problem ()->dump_to_pp (&pp);
+ emission_path->add_event (new custom_event
+ (dst_point.get_location (),
+ dst_point.get_fndecl (),
+ dst_stack_depth,
+ pp_formatted_text (&pp)));
+ }
}
/* Return true if EEDGE is a significant edge in the path to the diagnostic
sedge->get_description (false));
const gimple *last_stmt = src_point.get_supernode ()->get_last_stmt ();
- if (!model.maybe_update_for_edge (*sedge, last_stmt, NULL))
+ rejected_constraint *rc = NULL;
+ if (!model.maybe_update_for_edge (*sedge, last_stmt, NULL, &rc))
{
if (logger)
{
model.dump_to_pp (logger->get_printer (), true, false);
}
if (out)
- *out = new feasibility_problem (edge_idx, model, *eedge,
- last_stmt);
+ *out = new feasibility_problem (edge_idx, *eedge,
+ last_stmt, rc);
+ else
+ delete rc;
return false;
}
}
dump (stderr);
}
+/* class feasibility_problem. */
+
+void
+feasibility_problem::dump_to_pp (pretty_printer *pp) const
+{
+ pp_printf (pp, "edge from EN: %i to EN: %i",
+ m_eedge.m_src->m_index, m_eedge.m_dest->m_index);
+ if (m_rc)
+ {
+ 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);
+ }
+}
+
/* A family of cluster subclasses for use when generating .dot output for
exploded graphs (-fdump-analyzer-exploded-graph), for grouping the
enodes into hierarchical boxes.
{
public:
feasibility_problem (unsigned eedge_idx,
- const region_model &model,
const exploded_edge &eedge,
- const gimple *last_stmt)
- : m_eedge_idx (eedge_idx), m_model (model), m_eedge (eedge),
- m_last_stmt (last_stmt)
+ const gimple *last_stmt,
+ rejected_constraint *rc)
+ : m_eedge_idx (eedge_idx), m_eedge (eedge),
+ m_last_stmt (last_stmt), m_rc (rc)
{}
+ ~feasibility_problem () { delete m_rc; }
+
+ void dump_to_pp (pretty_printer *pp) const;
unsigned m_eedge_idx;
- region_model m_model;
const exploded_edge &m_eedge;
const gimple *m_last_stmt;
+ rejected_constraint *m_rc;
};
/* Finding the shortest exploded_path within an exploded_graph. */
const gimple *stmt = get_stmt ();
if (stmt)
return stmt->location;
-
- return UNKNOWN_LOCATION;
+ if (m_kind == PK_BEFORE_SUPERNODE)
+ return m_supernode->get_start_location ();
+ else if (m_kind == PK_AFTER_SUPERNODE)
+ return m_supernode->get_end_location ();
+ else
+ return UNKNOWN_LOCATION;
}
/* Create a function_point representing the entrypoint of function FUN. */
last_stmt);
if (!m_region_model->maybe_update_for_edge (*succ,
last_stmt,
- &ctxt))
+ &ctxt, NULL))
{
logger * const logger = eg.get_logger ();
if (logger)
return true;
}
+/* As above, but when returning false, if OUT is non-NULL, write a
+ new rejected_constraint to *OUT. */
+
+bool
+region_model::add_constraint (tree lhs, enum tree_code op, tree rhs,
+ region_model_context *ctxt,
+ rejected_constraint **out)
+{
+ bool sat = add_constraint (lhs, op, rhs, ctxt);
+ if (!sat && out)
+ *out = new rejected_constraint (*this, lhs, op, rhs);
+ return sat;
+}
+
/* Subroutine of region_model::add_constraint for handling optimized
&& and || conditionals.
/* Attempt to update this model for taking EDGE (where the last statement
was LAST_STMT), returning true if the edge can be taken, false
otherwise.
+ When returning false, if OUT is non-NULL, write a new rejected_constraint
+ to it.
For CFG superedges where LAST_STMT is a conditional or a switch
statement, attempt to add the relevant conditions for EDGE to this
bool
region_model::maybe_update_for_edge (const superedge &edge,
const gimple *last_stmt,
- region_model_context *ctxt)
+ region_model_context *ctxt,
+ rejected_constraint **out)
{
/* Handle frame updates for interprocedural edges. */
switch (edge.m_kind)
if (const gcond *cond_stmt = dyn_cast <const gcond *> (last_stmt))
{
const cfg_superedge *cfg_sedge = as_a <const cfg_superedge *> (&edge);
- return apply_constraints_for_gcond (*cfg_sedge, cond_stmt, ctxt);
+ return apply_constraints_for_gcond (*cfg_sedge, cond_stmt, ctxt, out);
}
if (const gswitch *switch_stmt = dyn_cast <const gswitch *> (last_stmt))
{
const switch_cfg_superedge *switch_sedge
= as_a <const switch_cfg_superedge *> (&edge);
- return apply_constraints_for_gswitch (*switch_sedge, switch_stmt, ctxt);
+ return apply_constraints_for_gswitch (*switch_sedge, switch_stmt,
+ ctxt, out);
}
/* Apply any constraints due to an exception being thrown. */
if (const cfg_superedge *cfg_sedge = dyn_cast <const cfg_superedge *> (&edge))
if (cfg_sedge->get_flags () & EDGE_EH)
- return apply_constraints_for_exception (last_stmt, ctxt);
+ return apply_constraints_for_exception (last_stmt, ctxt, out);
return true;
}
If they are feasible, add the constraints and return true.
Return false if the constraints contradict existing knowledge
- (and so the edge should not be taken). */
+ (and so the edge should not be taken).
+ When returning false, if OUT is non-NULL, write a new rejected_constraint
+ to it. */
bool
region_model::apply_constraints_for_gcond (const cfg_superedge &sedge,
const gcond *cond_stmt,
- region_model_context *ctxt)
+ region_model_context *ctxt,
+ rejected_constraint **out)
{
::edge cfg_edge = sedge.get_cfg_edge ();
gcc_assert (cfg_edge != NULL);
tree rhs = gimple_cond_rhs (cond_stmt);
if (cfg_edge->flags & EDGE_FALSE_VALUE)
op = invert_tree_comparison (op, false /* honor_nans */);
- return add_constraint (lhs, op, rhs, ctxt);
+ return add_constraint (lhs, op, rhs, ctxt, out);
}
/* Given an EDGE guarded by SWITCH_STMT, determine appropriate constraints
If they are feasible, add the constraints and return true.
Return false if the constraints contradict existing knowledge
- (and so the edge should not be taken). */
+ (and so the edge should not be taken).
+ When returning false, if OUT is non-NULL, write a new rejected_constraint
+ to it. */
bool
region_model::apply_constraints_for_gswitch (const switch_cfg_superedge &edge,
const gswitch *switch_stmt,
- region_model_context *ctxt)
+ region_model_context *ctxt,
+ rejected_constraint **out)
{
tree index = gimple_switch_index (switch_stmt);
tree case_label = edge.get_case_label ();
if (upper_bound)
{
/* Range. */
- if (!add_constraint (index, GE_EXPR, lower_bound, ctxt))
+ if (!add_constraint (index, GE_EXPR, lower_bound, ctxt, out))
return false;
- return add_constraint (index, LE_EXPR, upper_bound, ctxt);
+ return add_constraint (index, LE_EXPR, upper_bound, ctxt, out);
}
else
/* Single-value. */
- return add_constraint (index, EQ_EXPR, lower_bound, ctxt);
+ return add_constraint (index, EQ_EXPR, lower_bound, ctxt, out);
}
else
{
/* 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))
+ if (!add_constraint (index, NE_EXPR, other_lower_bound,
+ ctxt, out))
return false;
- if (!add_constraint (index, NE_EXPR, other_upper_bound, ctxt))
+ 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))
+ if (!add_constraint (index, NE_EXPR, other_lower_bound, ctxt, out))
return false;
}
return true;
If they are feasible, add the constraints and return true.
Return false if the constraints contradict existing knowledge
- (and so the edge should not be taken). */
+ (and so the edge should not be taken).
+ When returning false, if OUT is non-NULL, write a new rejected_constraint
+ to it. */
bool
region_model::apply_constraints_for_exception (const gimple *last_stmt,
- region_model_context *ctxt)
+ region_model_context *ctxt,
+ rejected_constraint **out)
{
gcc_assert (last_stmt);
if (const gcall *call = dyn_cast <const gcall *> (last_stmt))
leak report due to the result being lost when following
the EH edge. */
if (tree lhs = gimple_call_lhs (call))
- return add_constraint (lhs, EQ_EXPR, null_pointer_node, ctxt);
+ return add_constraint (lhs, EQ_EXPR, null_pointer_node, ctxt, out);
return true;
}
return true;
rmodel.dump (false);
}
+/* struct rejected_constraint. */
+
+void
+rejected_constraint::dump_to_pp (pretty_printer *pp) const
+{
+ region_model m (m_model);
+ const svalue *lhs_sval = m.get_rvalue (m_lhs, NULL);
+ const svalue *rhs_sval = m.get_rvalue (m_rhs, NULL);
+ lhs_sval->dump_to_pp (pp, true);
+ pp_printf (pp, " %s ", op_symbol_code (m_op));
+ rhs_sval->dump_to_pp (pp, true);
+}
+
/* class engine. */
/* Dump the managed objects by class to LOGGER, and the per-class totals. */
bool maybe_update_for_edge (const superedge &edge,
const gimple *last_stmt,
- region_model_context *ctxt);
+ region_model_context *ctxt,
+ rejected_constraint **out);
const region *push_frame (function *fun, const vec<const svalue *> *arg_sids,
region_model_context *ctxt);
region_model_context *ctxt);
bool add_constraint (tree lhs, enum tree_code op, tree rhs,
region_model_context *ctxt);
+ bool add_constraint (tree lhs, enum tree_code op, tree rhs,
+ region_model_context *ctxt,
+ rejected_constraint **out);
const region *create_region_for_heap_alloc (const svalue *size_in_bytes);
const region *create_region_for_alloca (const svalue *size_in_bytes);
region_model_context *ctxt);
bool apply_constraints_for_gcond (const cfg_superedge &edge,
const gcond *cond_stmt,
- region_model_context *ctxt);
+ region_model_context *ctxt,
+ rejected_constraint **out);
bool apply_constraints_for_gswitch (const switch_cfg_superedge &edge,
const gswitch *switch_stmt,
- region_model_context *ctxt);
+ region_model_context *ctxt,
+ rejected_constraint **out);
bool apply_constraints_for_exception (const gimple *last_stmt,
- region_model_context *ctxt);
+ region_model_context *ctxt,
+ rejected_constraint **out);
int poison_any_pointers_to_descendents (const region *reg,
enum poison_kind pkind);
region_model *m_merged_model;
};
+/* A record that can (optionally) be written out when
+ region_model::add_constraint fails. */
+
+struct 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)
+ {}
+
+ void dump_to_pp (pretty_printer *pp) const;
+
+ region_model m_model;
+ tree m_lhs;
+ enum tree_code m_op;
+ tree m_rhs;
+};
+
/* A bundle of state. */
class engine
it can be reproduced without a caller). We apply a check that
each duplicate warning's shortest path is feasible, rejecting any
warnings for which the shortest path is infeasible (which could lead to
-false negatives).
+false negatives). This check can be suppressed (for debugging purposes)
+using @option{-fno-analyzer-feasibility}.
We use the shortest feasible @code{exploded_path} through the
@code{exploded_graph} (a list of @code{exploded_edge *}) to build a
-fanalyzer @gol
-fanalyzer-call-summaries @gol
-fanalyzer-checker=@var{name} @gol
+-fno-analyzer-feasibility @gol
-fanalyzer-fine-grained @gol
-fanalyzer-state-merge @gol
-fanalyzer-state-purge @gol
@option{-Wanalyzer-tainted-array-index}, and this option is required
to enable them.
+@item -fno-analyzer-feasibility
+@opindex fanalyzer-feasibility
+@opindex fno-analyzer-feasibility
+This option is intended for analyzer developers.
+
+By default the analyzer verifies that there is a feasible control flow path
+for each diagnostic it emits: that the conditions that hold are not mutually
+exclusive. Diagnostics for which no feasible path can be found are rejected.
+This filtering can be suppressed with @option{-fno-analyzer-feasibility}, for
+debugging issues in this code.
+
@item -fanalyzer-fine-grained
@opindex fanalyzer-fine-grained
@opindex fno-analyzer-fine-grained
--- /dev/null
+/* Verify that -fno-analyzer-feasibility works. */
+/* { dg-additional-options "-fno-analyzer-feasibility" } */
+
+#include "analyzer-decls.h"
+
+void test_1 (int flag)
+{
+ int a;
+ if (flag)
+ a = 1;
+ else
+ a = 2;
+
+ if (a == 1) /* (can only be the case when "flag" was true above). */
+ if (!flag)
+ {
+ __analyzer_dump_path (); /* { dg-message "note: path" "path diag" } */
+ /* { dg-message "infeasible" "infeasibility event" { target *-*-* } .-1 } */
+ }
+}