return false;
}
+bool
+is_cxa_throw_p (const gcall &call)
+{
+ tree fndecl = gimple_call_fndecl (&call);
+ if (!fndecl)
+ return false;
+
+ return is_named_call_p (fndecl, "__cxa_throw");
+}
+
+bool
+is_cxa_rethrow_p (const gcall &call)
+{
+ tree fndecl = gimple_call_fndecl (&call);
+ if (!fndecl)
+ return false;
+
+ return is_named_call_p (fndecl, "__cxa_rethrow");
+}
+
/* For a CALL that matched is_special_named_call_p or is_named_call_p for
some name, return a name for the called function suitable for use in
diagnostics (stripping the leading underscores). */
Common Var(warn_analyzer_tainted_size) Init(1) Warning
Warn about code paths in which an unsanitized value is used as a size.
+Wanalyzer-throw-of-unexpected-type
+Common Var(warn_analyzer_throw_of_unexpected_type) Init(1) Warning
+Warn about code paths in which an exception of unexpected type is thrown.
+
Wanalyzer-undefined-behavior-ptrdiff
Common Var(warn_analyzer_undefined_behavior_ptrdiff) Init(1) Warning
Warn about code paths in which pointer subtraction involves undefined behavior.
Wanalyzer-tainted-size
UrlSuffix(gcc/Static-Analyzer-Options.html#index-Wanalyzer-tainted-size)
+Wanalyzer-throw-of-unexpected-type
+UrlSuffix(gcc/Static-Analyzer-Options.html#index-Wanalyzer-throw-of-unexpected-type)
+
Wanalyzer-undefined-behavior-ptrdiff
UrlSuffix(gcc/Static-Analyzer-Options.html#index-Wanalyzer-undefined-behavior-ptrdiff)
return update_model (state->m_region_model, eedge, ctxt);
}
+/* Base implementation of custom_edge_info::create_enode vfunc. */
+
+exploded_node *
+custom_edge_info::create_enode (exploded_graph &eg,
+ const program_point &point,
+ program_state &&state,
+ exploded_node *enode_for_diag,
+ region_model_context *) const
+{
+ return eg.get_or_create_node (point, state, enode_for_diag);
+}
+
/* class call_info : public custom_edge_info. */
/* Implementation of custom_edge_info::print vfunc for call_info. */
class call_info : public custom_edge_info
{
public:
- void print (pretty_printer *pp) const final override;
+ void print (pretty_printer *pp) const override;
void add_events_to_path (checker_path *emission_path,
- const exploded_edge &eedge) const final override;
+ const exploded_edge &eedge) const override;
const gcall &get_call_stmt () const { return m_call_stmt; }
tree get_fndecl () const { return m_fndecl; }
return "start_cfg_edge";
case event_kind::end_cfg_edge:
return "end_cfg_edge";
+ case event_kind::catch_:
+ return "catch";
case event_kind::call_edge:
return "call_edge";
case event_kind::return_edge:
return "rewind_from_longjmp";
case event_kind::rewind_to_setjmp:
return "rewind_to_setjmp";
+ case event_kind::throw_:
+ return "throw";
+ case event_kind::unwind:
+ return "unwind";
case event_kind::warning:
return "warning";
}
&m_original_setjmp_event_id);
}
+/* class throw_event : public checker_event. */
+
+/* class explicit_throw_event : public throw_event. */
+void
+explicit_throw_event::print_desc (pretty_printer &pp) const
+{
+ if (m_is_rethrow)
+ {
+ if (m_type)
+ pp_printf (&pp, "rethrowing exception of type %qT here...", m_type);
+ else
+ pp_printf (&pp, "rethrowing exception here...");
+ }
+ else
+ {
+ if (m_type)
+ pp_printf (&pp, "throwing exception of type %qT here...", m_type);
+ else
+ pp_printf (&pp, "throwing exception here...");
+ }
+}
+
+/* class throw_from_call_to_external_fn_event : public throw_event. */
+
+void
+throw_from_call_to_external_fn_event::print_desc (pretty_printer &pp) const
+{
+ if (m_fndecl)
+ pp_printf (&pp, "if %qD throws an exception...", m_fndecl);
+ else
+ pp_printf (&pp, "if the called function throws an exception...");
+}
+
+// class unwind_event : public checker_event
+
+void
+unwind_event::print_desc (pretty_printer &pp) const
+{
+ if (m_num_frames > 1)
+ pp_printf (&pp, "unwinding %i stack frames", m_num_frames);
+ else
+ pp_printf (&pp, "unwinding stack frame");
+}
+
/* class warning_event : public checker_event. */
/* Implementation of diagnostic_event::print_desc vfunc for
state_change,
start_cfg_edge,
end_cfg_edge,
+ catch_,
call_edge,
return_edge,
start_consolidated_cfg_edges,
setjmp_,
rewind_from_longjmp,
rewind_to_setjmp,
+ throw_,
+ unwind,
warning
};
cfg_edge_event
start_cfg_edge_event (event_kind::start_cfg_edge)
end_cfg_edge_event (event_kind::end_cfg_edge)
+ catch_cfg_edge_event (event_kind::catch_cfg_edge)
call_event (event_kind::call_edge)
return_edge (event_kind::return_edge)
start_consolidated_cfg_edges_event (event_kind::start_consolidated_cfg_edges)
rewind_event
rewind_from_longjmp_event (event_kind::rewind_from_longjmp)
rewind_to_setjmp_event (event_kind::rewind_to_setjmp)
+ throw_event (event_kind:throw_)
+ explicit_throw_event
+ throw_from_call_to_external_fn_event
+ unwind_event (event_kind::unwind)
warning_event (event_kind::warning). */
/* Abstract subclass of diagnostic_event; the base class for use in
}
};
+/* A concrete event subclass for catching an exception
+ e.g. "...catching 'struct io_error' here". */
+
+class catch_cfg_edge_event : public cfg_edge_event
+{
+public:
+ catch_cfg_edge_event (const exploded_edge &eedge,
+ const event_loc_info &loc_info,
+ tree type)
+ : cfg_edge_event (event_kind::catch_, eedge, loc_info),
+ m_type (type)
+ {
+ }
+
+ void print_desc (pretty_printer &pp) const final override
+ {
+ if (m_type)
+ pp_printf (&pp, "...catching exception of type %qT here", m_type);
+ else
+ pp_string (&pp, "...catching exception here");
+ }
+
+private:
+ tree m_type;
+};
+
/* A concrete event subclass for an interprocedural call. */
class call_event : public superedge_event
diagnostic_event_id_t m_original_setjmp_event_id;
};
+/* An abstract subclass for throwing/rethrowing an exception. */
+
+class throw_event : public checker_event
+{
+public:
+ throw_event (const event_loc_info &loc_info,
+ const exploded_node *enode,
+ const gcall &throw_call)
+ : checker_event (event_kind::throw_, loc_info),
+ m_enode (enode),
+ m_throw_call (throw_call)
+ {
+ }
+
+protected:
+ const exploded_node *m_enode;
+ const gcall &m_throw_call;
+};
+
+/* A concrete event subclass for an explicit "throw EXC;"
+ or "throw;" (actually, a call to __cxa_throw or __cxa_rethrow). */
+
+class explicit_throw_event : public throw_event
+{
+public:
+ explicit_throw_event (const event_loc_info &loc_info,
+ const exploded_node *enode,
+ const gcall &throw_call,
+ tree type,
+ bool is_rethrow)
+ : throw_event (loc_info, enode, throw_call),
+ m_type (type),
+ m_is_rethrow (is_rethrow)
+ {
+ }
+
+ void print_desc (pretty_printer &pp) const final override;
+
+private:
+ tree m_type;
+ bool m_is_rethrow;
+};
+
+/* A concrete event subclass for an exception being thrown
+ from within a call to a function we don't have the body of,
+ or where we don't know what function was called. */
+
+class throw_from_call_to_external_fn_event : public throw_event
+{
+public:
+ throw_from_call_to_external_fn_event (const event_loc_info &loc_info,
+ const exploded_node *enode,
+ const gcall &throw_call,
+ tree fndecl)
+ : throw_event (loc_info, enode, throw_call),
+ m_fndecl (fndecl)
+ {
+ }
+
+ void print_desc (pretty_printer &pp) const final override;
+
+private:
+ tree m_fndecl;
+};
+
+/* A concrete event subclass for unwinding a stack frame when
+ processing an exception. */
+
+class unwind_event : public checker_event
+{
+public:
+ unwind_event (const event_loc_info &loc_info)
+ : checker_event (event_kind::unwind, loc_info),
+ m_num_frames (1)
+ {
+ }
+
+ void print_desc (pretty_printer &pp) const final override;
+
+ int m_num_frames;
+};
+
/* Concrete subclass of checker_event for use at the end of a path:
a repeat of the warning message at the end of the path (perhaps with
references to pertinent events that occurred on the way), at the point
class superedge;
class cfg_superedge;
class switch_cfg_superedge;
+ class eh_dispatch_cfg_superedge;
+ class eh_dispatch_try_cfg_superedge;
+ class eh_dispatch_allowed_cfg_superedge;
class callgraph_superedge;
class call_superedge;
class return_superedge;
virtual void add_events_to_path (checker_path *emission_path,
const exploded_edge &eedge) const = 0;
+
+ virtual exploded_node *create_enode (exploded_graph &eg,
+ const program_point &point,
+ program_state &&state,
+ exploded_node *enode_for_diag,
+ region_model_context *ctxt) const;
};
/* Abstract base class for splitting state.
extern bool is_setjmp_call_p (const gcall &call);
extern bool is_longjmp_call_p (const gcall &call);
extern bool is_placement_new_p (const gcall &call);
+extern bool is_cxa_throw_p (const gcall &call);
+extern bool is_cxa_rethrow_p (const gcall &call);
extern const char *get_user_facing_name (const gcall &call);
{
case SUPEREDGE_CFG_EDGE:
{
+ if (auto eh_dispatch_try_sedge
+ = eedge.m_sedge->dyn_cast_eh_dispatch_try_cfg_superedge ())
+ {
+ if (eh_dispatch_try_sedge->get_eh_catch ())
+ {
+ const region_model *model = src_node->get_state ().m_region_model;
+ auto curr_thrown_exception_node
+ = model->get_current_thrown_exception ();
+ gcc_assert (curr_thrown_exception_node);
+ tree type = curr_thrown_exception_node->maybe_get_type ();
+ emission_path->add_event
+ (std::make_unique<catch_cfg_edge_event>
+ (eedge,
+ event_loc_info (dst_point.get_supernode ()->get_start_location (),
+ dst_point.get_fndecl (),
+ dst_stack_depth),
+ type));
+ return;
+ }
+ else
+ {
+ /* We have the "uncaught exception" sedge, from eh_dispatch
+ to a block containing resx.
+ Don't add any events for this, so that we can consolidate
+ adjacent stack unwinding events. */
+ return;
+ }
+ }
+
emission_path->add_event
(std::make_unique<start_cfg_edge_event>
(eedge,
if (! flag_analyzer_show_events_in_system_headers)
prune_system_headers (path);
consolidate_conditions (path);
+ consolidate_unwind_events (path);
finish_pruning (path);
path->maybe_log (get_logger (), "pruned");
}
filtered when their start event is filtered. */
break;
+ case event_kind::catch_:
+ case event_kind::throw_:
+ case event_kind::unwind:
+ /* Don't filter these. */
+ break;
+
case event_kind::call_edge:
{
call_event *event = (call_event *)base_event;
}
}
+/* Consolidate runs of consecutive unwind_event. */
+
+void
+diagnostic_manager::consolidate_unwind_events (checker_path *path) const
+{
+ /* Don't simplify edges if we're debugging them. */
+ if (flag_analyzer_verbose_edges)
+ return;
+
+ for (int start_idx = 0;
+ start_idx < (signed)path->num_events () - 1;
+ start_idx++)
+ {
+ /* Find a run of consecutive unwind_event instances. */
+ if (path->get_checker_event (start_idx)->m_kind != event_kind::unwind)
+ continue;
+ int iter_idx = start_idx + 1;
+ while (iter_idx < (int)path->num_events ())
+ if (path->get_checker_event (iter_idx)->m_kind == event_kind::unwind)
+ ++iter_idx;
+ else
+ break;
+
+ /* iter_idx should now be one after the last unwind_event in the run. */
+ const int last_idx = iter_idx - 1;
+ if (last_idx == start_idx)
+ continue;
+
+ gcc_assert (last_idx > start_idx);
+
+ log ("consolidating unwind events %i-%i into %i",
+ start_idx, last_idx, start_idx);
+
+ unwind_event *first_event
+ = (unwind_event *)path->get_checker_event (start_idx);
+ const unwind_event *last_event
+ = (const unwind_event *)path->get_checker_event (last_idx);
+ first_event->m_num_frames += last_event->m_num_frames;
+ path->delete_events (start_idx + 1, last_idx - start_idx);
+ }
+}
+
/* Final pass of diagnostic_manager::prune_path.
If all we're left with is in one function, then filter function entry
void prune_interproc_events (checker_path *path) const;
void prune_system_headers (checker_path *path) const;
void consolidate_conditions (checker_path *path) const;
+ void consolidate_unwind_events (checker_path *path) const;
void finish_pruning (checker_path *path) const;
engine *m_eng;
ctxt->maybe_did_work ();
return;
}
+ else if (is_cxa_throw_p (call))
+ {
+ on_throw (eg, call, state, false, ctxt);
+ *out_terminate_path = true;
+ return;
+ }
+ else if (is_cxa_rethrow_p (call))
+ {
+ on_throw (eg, call, state, true, ctxt);
+ *out_terminate_path = true;
+ return;
+ }
+ }
+ else if (const gresx *resx = dyn_cast <const gresx *> (stmt))
+ {
+ on_resx (eg, *resx, state, ctxt);
+ *out_terminate_path = true;
+ return;
}
/* Otherwise, defer to m_region_model. */
}
}
+/* Subclass of call_info for exploded edges that express
+ a throw or rethrow of an exception (actually a call
+ to __cxa_throw or __cxa_rethrow). */
+
+class throw_custom_edge : public call_info
+{
+public:
+ throw_custom_edge (const call_details &cd,
+ tree type,
+ bool is_rethrow)
+ : call_info (cd),
+ m_type (type),
+ m_is_rethrow (is_rethrow)
+ {
+ }
+
+ void print (pretty_printer *pp) const final override
+ {
+ if (m_is_rethrow)
+ {
+ if (m_type)
+ pp_printf (pp, "rethrowing %qT", m_type);
+ else
+ pp_printf (pp, "rethrowing");
+ }
+ else
+ {
+ if (m_type)
+ pp_printf (pp, "throwing %qT", m_type);
+ else
+ pp_printf (pp, "throwing");
+ }
+ }
+
+ void print_desc (pretty_printer &pp) const final override
+ {
+ print (&pp);
+ }
+
+ bool update_model (region_model *model,
+ const exploded_edge *,
+ region_model_context *ctxt) const final override
+ {
+ if (m_is_rethrow)
+ {
+ auto eh_node = model->get_current_caught_exception ();
+ gcc_assert (eh_node);
+ model->push_thrown_exception (*eh_node);
+ }
+ else
+ {
+ call_details cd (get_call_details (model, ctxt));
+
+ const svalue *exception_sval = cd.get_arg_svalue (0);
+ const svalue *tinfo_sval = cd.get_arg_svalue (1);
+ const svalue *destructor_sval = cd.get_arg_svalue (2);
+
+ /* Push a new exception_node on the model's m_exception_stack. */
+ exception_node eh_node (exception_sval, tinfo_sval, destructor_sval);
+ model->push_thrown_exception (eh_node);
+ }
+
+ return true;
+ }
+
+ void add_events_to_path (checker_path *emission_path,
+ const exploded_edge &eedge) const final override
+ {
+ const exploded_node *dst_node = eedge.m_dest;
+ const program_point &dst_point = dst_node->get_point ();
+ const int dst_stack_depth = dst_point.get_stack_depth ();
+
+ const gcall &call = get_call_stmt ();
+
+ emission_path->add_event
+ (std::make_unique<explicit_throw_event>
+ (event_loc_info (call.location,
+ dst_point.get_fndecl (),
+ dst_stack_depth),
+ dst_node,
+ call,
+ m_type,
+ m_is_rethrow));
+ }
+
+private:
+ tree m_type;
+ bool m_is_rethrow;
+};
+
+/* Subclass of custom_edge_info for an exploded edge that expresses
+ unwinding one stack frame during exception handling. */
+
+class unwind_custom_edge : public custom_edge_info
+{
+public:
+ unwind_custom_edge (location_t loc)
+ : m_loc (loc)
+ {
+ }
+
+ void print (pretty_printer *pp) const final override
+ {
+ pp_printf (pp, "unwinding frame");
+ }
+
+ bool update_model (region_model *model,
+ const exploded_edge *,
+ region_model_context *ctxt) const final override
+ {
+ model->pop_frame (NULL_TREE, nullptr, ctxt, nullptr, false);
+ return true;
+ }
+
+ void add_events_to_path (checker_path *emission_path,
+ const exploded_edge &eedge) const final override
+ {
+ const exploded_node *src_node = eedge.m_src;
+ const program_point &src_point = src_node->get_point ();
+ const int src_stack_depth = src_point.get_stack_depth ();
+ emission_path->add_event
+ (std::make_unique<unwind_event> (event_loc_info (m_loc,
+ src_point.get_fndecl (),
+ src_stack_depth)));
+ }
+
+private:
+ location_t m_loc;
+};
+
+/* Locate an SNODE that's a CFG edge with the EH flag,
+ or return nullptr. */
+
+static const superedge *
+get_eh_outedge (const supernode &snode)
+{
+ for (auto out_sedge : snode.m_succs)
+ if (::edge cfg_edge = out_sedge->get_any_cfg_edge ())
+ if (cfg_edge->flags & EDGE_EH)
+ return out_sedge;
+
+ // Not found
+ return nullptr;
+}
+
+/* Given THROWN_ENODE, which expreses a throw or rethrow occurring at
+ THROW_STMT, unwind intraprocedurally and interprocedurally to find
+ the next eh_dispatch statement to handle exceptions, if any.
+
+ Add eedges and enodes to this graph expressing the actions taken
+ to reach an enode containing the eh_dispatch stmt, if any.
+ Only the final enode is added to this graph's worklist.
+
+ Use CTXT to warn about problems e.g. memory leaks due to stack frames
+ being unwound. */
+
+void
+exploded_graph::unwind_from_exception (exploded_node &thrown_enode,
+ const gimple *throw_stmt,
+ region_model_context *ctxt)
+{
+ logger * const logger = get_logger ();
+ LOG_FUNC_1 (logger, "thrown EN: %i", thrown_enode.m_index);
+
+ /* Iteratively unwind the stack looking for an out-cfg-edge
+ flagged EH. */
+ exploded_node *iter_enode = &thrown_enode;
+ while (iter_enode)
+ {
+ /* If we have an out-cfg-edge flagged EH, follow that,
+ presumably to a bb with a label and an eh_dispatch stmt.
+ Otherwise assume no out-cfgs-edges, and we are unwinding to the
+ caller. */
+ if (auto sedge = get_eh_outedge (*iter_enode->get_supernode ()))
+ {
+ /* Intraprocedural case.
+ Assume we have an out-edge flagged with EH leading to
+ code for dispatch to catch handlers. */
+ const program_point next_point
+ = program_point::before_supernode (sedge->m_dest,
+ sedge,
+ iter_enode->get_point ().get_call_string ());
+ exploded_node *next_enode
+ = get_or_create_node (next_point,
+ iter_enode->get_state (),
+ iter_enode,
+ /* Add this enode to the worklist. */
+ true);
+ if (!next_enode)
+ return;
+
+ add_edge (iter_enode, next_enode, NULL, false, nullptr);
+ return;
+ }
+ else
+ {
+ /* Interprocedural case.
+ No out-cfg-edge. Unwind one stack frame. */
+ program_state unwound_state (iter_enode->get_state ());
+ location_t loc = throw_stmt ? throw_stmt->location : UNKNOWN_LOCATION;
+ auto unwind_edge_info
+ = std::make_unique<unwind_custom_edge> (loc);
+ unwind_edge_info->update_model (unwound_state.m_region_model, nullptr,
+ ctxt);
+
+ /* Detect leaks in the new state relative to the old state.
+ Use an alternate ctxt that uses the original enode and the stmt
+ (if any) for the location of any diagnostics. */
+ {
+ uncertainty_t uncertainty;
+ impl_region_model_context ctxt (*this,
+ &thrown_enode,
+ &iter_enode->get_state (),
+ &unwound_state,
+ &uncertainty,
+ nullptr,
+ throw_stmt);
+ program_state::detect_leaks (iter_enode->get_state (),
+ unwound_state,
+ NULL,
+ get_ext_state (), &ctxt);
+ }
+ const call_string &cs = iter_enode->get_point ().get_call_string ();
+ if (cs.empty_p ())
+ {
+ /* Top-level stack frame in analysis: unwinding
+ to the outside world that called us. */
+ return;
+ }
+ else
+ {
+ /* Nested function in analysis: unwinding to
+ the callsite in the analysis (or beyond). */
+ program_point unwound_point
+ = program_point::after_supernode (cs.get_caller_node (), cs);
+ unwound_point.pop_from_call_stack ();
+
+ exploded_node *after_unwind_enode
+ = get_or_create_node (unwound_point,
+ std::move (unwound_state),
+ iter_enode,
+ /* Don't add this enode to the
+ worklist; we will process it
+ on the next iteration. */
+ false);
+
+ if (!after_unwind_enode)
+ return;
+
+ add_edge (iter_enode, after_unwind_enode, NULL, true,
+ std::move (unwind_edge_info));
+ iter_enode = after_unwind_enode;
+ }
+ }
+ }
+}
+
+/* Handle THROW_CALL, a call to __cxa_throw or __cxa_rethrow.
+
+ Create an eedge and destination enode for the throw/rethrow, adding
+ them to this egraph. The new enode isn't added to the worklist, but
+ instead exploded_graph::unwind_from_exception is immediately called
+ on it, potentially creating more eedges and enodes leading to an
+ eh_handler stmt. */
+
+void
+exploded_node::on_throw (exploded_graph &eg,
+ const gcall &throw_call,
+ program_state *new_state,
+ bool is_rethrow,
+ region_model_context *ctxt)
+{
+ region_model *model = new_state->m_region_model;
+ call_details cd (throw_call, model, ctxt);
+
+ /* Create an enode and eedge for the "throw". */
+ tree type = NULL_TREE;
+ if (is_rethrow)
+ {
+ const exception_node *eh_node = model->get_current_caught_exception ();
+ gcc_assert (eh_node);
+ type = eh_node->maybe_get_type ();
+ }
+ else
+ {
+ const svalue *tinfo_sval = cd.get_arg_svalue (1);
+ type = tinfo_sval->maybe_get_type_from_typeinfo ();
+ }
+ auto throw_edge_info
+ = std::make_unique<throw_custom_edge> (cd, type, is_rethrow);
+ throw_edge_info->update_model (model, nullptr, ctxt);
+
+ program_point after_throw_point = get_point ().get_next ();
+
+ exploded_node *after_throw_enode
+ = eg.get_or_create_node (after_throw_point, *new_state, this,
+ /* Don't add to worklist; we process
+ this immediately below. */
+ false);
+
+ if (!after_throw_enode)
+ return;
+
+ /* Create custom exploded_edge for a throw. */
+ eg.add_edge (this, after_throw_enode, NULL, true,
+ std::move (throw_edge_info));
+
+ eg.unwind_from_exception (*after_throw_enode, &throw_call, ctxt);
+}
+
+/* Handle a gimple "resx" statement by adding eedges and enode.
+ that unwind to the next eh_dispatch statement, if any. Only
+ the final enode is added to the worklist. */
+
+void
+exploded_node::on_resx (exploded_graph &eg,
+ const gresx &/*resx*/,
+ program_state */*new_state*/,
+ region_model_context *ctxt)
+{
+ eg.unwind_from_exception (*this,
+ nullptr,
+ ctxt);
+}
+
+
/* Subroutine of exploded_graph::process_node for finding the successors
of the supernode for a function exit basic block.
}
/* Get or create an exploded_node for (POINT, STATE).
- If a new node is created, it is added to the worklist.
+ If a new node is created and ADD_TO_WORKLIST is true,
+ it is added to the worklist.
Use ENODE_FOR_DIAG, a pre-existing enode, for any diagnostics
that need to be emitted (e.g. when purging state *before* we have
exploded_node *
exploded_graph::get_or_create_node (const program_point &point,
const program_state &state,
- exploded_node *enode_for_diag)
+ exploded_node *enode_for_diag,
+ bool add_to_worklist)
{
logger * const logger = get_logger ();
LOG_FUNC (logger);
}
/* Add the new node to the worlist. */
- m_worklist.add_node (node);
+ if (add_to_worklist)
+ m_worklist.add_node (node);
+ else
+ node->set_status (exploded_node::status::special);
return node;
}
NULL, /* no exploded_edge yet. */
&bifurcation_ctxt))
{
- exploded_node *next2
- = get_or_create_node (next_point, bifurcated_new_state, node);
- if (next2)
- add_edge (node, next2, NULL,
- true /* assume that work could be done */,
- std::move (edge_info));
+ if (exploded_node *next2
+ = edge_info->create_enode
+ (*this,
+ next_point,
+ std::move (bifurcated_new_state),
+ node,
+ &bifurcation_ctxt))
+ {
+ add_edge (node, next2, NULL,
+ true /* assume that work could be done */,
+ std::move (edge_info));
+ }
}
else
{
}
}
+ /* Ignore CFG edges in the sgraph flagged with EH whilst
+ we're exploring the egraph.
+ We only use these sedges in special-case logic for
+ dealing with exception-handling. */
+ if (auto cfg_sedge = succ->dyn_cast_cfg_superedge ())
+ if (cfg_sedge->get_flags () & EDGE_EH)
+ {
+ if (logger)
+ logger->log ("rejecting EH edge");
+ continue;
+ }
+
if (!node->on_edge (*this, succ, &next_point, &next_state,
&uncertainty))
{
break;
case exploded_node::status::processed:
break;
+ case exploded_node::status::special:
+ pp_string (pp, "(S)");
+ break;
case exploded_node::status::merger:
pp_string (pp, "(M)");
break;
/* Node has had exploded_graph::process_node called on it. */
processed,
+ /* Node was excluded from worklist on creation.
+ e.g. for handling exception-unwinding. */
+ special,
+
/* Node was left unprocessed due to merger; it won't have had
exploded_graph::process_node called on it. */
merger,
const gcall &call,
program_state *new_state,
region_model_context *ctxt);
+ void on_throw (exploded_graph &eg,
+ const gcall &call,
+ program_state *new_state,
+ bool is_rethrow,
+ region_model_context *ctxt);
+ void on_resx (exploded_graph &eg,
+ const gresx &resx,
+ program_state *new_state,
+ region_model_context *ctxt);
void detect_leaks (exploded_graph &eg);
exploded_node *get_or_create_node (const program_point &point,
const program_state &state,
- exploded_node *enode_for_diag);
+ exploded_node *enode_for_diag,
+ bool add_to_worklist = true);
exploded_edge *add_edge (exploded_node *src, exploded_node *dest,
const superedge *sedge, bool could_do_work,
std::unique_ptr<custom_edge_info> custom = NULL);
void on_escaped_function (tree fndecl);
+ void unwind_from_exception (exploded_node &enode,
+ const gimple *stmt,
+ region_model_context *ctxt);
+
/* In infinite-loop.cc */
void detect_infinite_loops ();
};
+class kf_cxa_allocate_exception : public known_function
+{
+public:
+ bool matches_call_types_p (const call_details &cd) const final override
+ {
+ return cd.num_args () == 1 && cd.arg_is_size_p (0);
+ }
+
+ void impl_call_pre (const call_details &cd) const final override
+ {
+ region_model *model = cd.get_model ();
+ region_model_manager *mgr = cd.get_manager ();
+ const svalue *size_sval = cd.get_arg_svalue (0);
+ region_model_context *ctxt = cd.get_ctxt ();
+
+ /* Create a heap allocated region. */
+ const region *new_reg
+ = model->get_or_create_region_for_heap_alloc (size_sval, ctxt);
+ if (cd.get_lhs_type ())
+ {
+ const svalue *ptr_sval
+ = mgr->get_ptr_svalue (cd.get_lhs_type (), new_reg);
+ cd.maybe_set_lhs (ptr_sval);
+ }
+ }
+};
+
+class kf_cxa_begin_catch : public known_function
+{
+public:
+ bool matches_call_types_p (const call_details &cd) const final override
+ {
+ return (cd.num_args () == 1
+ && POINTER_TYPE_P (cd.get_arg_type (0)));
+ }
+
+ void impl_call_pre (const call_details &cd) const final override
+ {
+ region_model *model = cd.get_model ();
+
+ auto node = model->pop_thrown_exception ();
+ model->push_caught_exception (node);
+ cd.maybe_set_lhs (node.m_exception_sval);
+ }
+};
+
+class kf_cxa_end_catch : public known_function
+{
+public:
+ bool matches_call_types_p (const call_details &cd) const final override
+ {
+ return cd.num_args () == 0;
+ }
+
+ void impl_call_pre (const call_details &cd) const final override
+ {
+ region_model *model = cd.get_model ();
+ model->pop_caught_exception ();
+ }
+};
+
+/* A subclass of pending_diagnostic for complaining about an exception
+ of an unexpected type being thrown (due to a call to
+ __cxa_call_unexpected).
+ See https://en.cppreference.com/w/cpp/language/except_spec */
+
+class throw_of_unexpected_type
+: public pending_diagnostic_subclass<throw_of_unexpected_type>
+{
+public:
+ throw_of_unexpected_type (tree exception_type,
+ tree thrown_from_fndecl)
+ : m_exception_type (exception_type),
+ m_thrown_from_fndecl (thrown_from_fndecl)
+ {
+ gcc_assert (m_exception_type);
+ gcc_assert (m_thrown_from_fndecl);
+ }
+
+ const char *get_kind () const final override
+ {
+ return "throw_of_unexpected_type";
+ }
+
+ bool operator== (const throw_of_unexpected_type &other) const
+ {
+ return (m_exception_type == other.m_exception_type
+ && m_thrown_from_fndecl == other.m_thrown_from_fndecl);
+ }
+
+ int get_controlling_option () const final override
+ {
+ return OPT_Wanalyzer_throw_of_unexpected_type;
+ }
+
+ bool emit (diagnostic_emission_context &ctxt) final override
+ {
+ auto_diagnostic_group d;
+
+ bool warned
+ = ctxt.warn ("throwing exception of unexpected type %qT from %qE",
+ m_exception_type, m_thrown_from_fndecl);
+ if (warned)
+ {
+ inform (DECL_SOURCE_LOCATION (m_thrown_from_fndecl),
+ "%qE declared here", m_thrown_from_fndecl);
+ // TODO: show specified types?
+ }
+ return warned;
+ }
+
+ bool
+ describe_final_event (pretty_printer &pp,
+ const evdesc::final_event &) final override
+ {
+ pp_printf (&pp,
+ "exception of unexpected type %qT thrown from %qE",
+ m_exception_type, m_thrown_from_fndecl);
+ return true;
+ }
+
+private:
+ tree m_exception_type;
+ tree m_thrown_from_fndecl;
+};
+
+/* See https://en.cppreference.com/w/cpp/language/except_spec */
+
+class kf_cxa_call_unexpected : public known_function
+{
+public:
+ bool matches_call_types_p (const call_details &cd) const final override
+ {
+ return (cd.num_args () == 1
+ && POINTER_TYPE_P (cd.get_arg_type (0)));
+ }
+
+ void impl_call_pre (const call_details &cd) const final override
+ {
+ if (region_model_context *ctxt = cd.get_ctxt ())
+ {
+ region_model *model = cd.get_model ();
+ tree thrown_from_fndecl = model->get_current_function ()->decl;
+ /* We must have a thrown exception. */
+ auto eh_node = model->get_current_thrown_exception ();
+ gcc_assert (eh_node);
+ tree exception_type = eh_node->maybe_get_type ();
+ ctxt->warn
+ (std::make_unique<throw_of_unexpected_type> (exception_type,
+ thrown_from_fndecl));
+ ctxt->terminate_path ();
+ }
+ }
+};
+
/* Populate KFM with instances of known functions relating to C++. */
void
kfm.add ("operator new []", std::make_unique<kf_operator_new> ());
kfm.add ("operator delete", std::make_unique<kf_operator_delete> ());
kfm.add ("operator delete []", std::make_unique<kf_operator_delete> ());
+
+ /* Functions mentioned in "Itanium C++ ABI: Exception Handling"'s
+ "Level II: C++ ABI"
+ https://itanium-cxx-abi.github.io/cxx-abi/abi-eh.html#cxx-abi */
+ kfm.add ("__cxa_allocate_exception",
+ std::make_unique<kf_cxa_allocate_exception> ());
+ // We treat __cxa_throw and __cxa_rethrow as special cases
+ kfm.add ("__cxa_begin_catch", std::make_unique<kf_cxa_begin_catch> ());
+ kfm.add ("__cxa_end_catch", std::make_unique<kf_cxa_end_catch> ());
+ kfm.add ("__cxa_call_unexpected",
+ std::make_unique<kf_cxa_call_unexpected> ());
}
} // namespace ana
/* Currently a no-op. */
};
+/* Handler for "__builtin_eh_pointer". */
+
+class kf_eh_pointer : public builtin_known_function
+{
+public:
+ bool matches_call_types_p (const call_details &) const final override
+ {
+ return true;
+ }
+
+ enum built_in_function builtin_code () const final override
+ {
+ return BUILT_IN_EH_POINTER;
+ }
+
+ void impl_call_pre (const call_details &cd) const final override
+ {
+ cd.set_any_lhs_with_defaults ();
+ }
+};
+
/* Handler for "strcat" and "__builtin_strcat_chk". */
class kf_strcat : public builtin_known_function
kfm.add (BUILT_IN_STACK_RESTORE, std::make_unique<kf_stack_restore> ());
kfm.add (BUILT_IN_STACK_SAVE, std::make_unique<kf_stack_save> ());
+ kfm.add (BUILT_IN_EH_POINTER, std::make_unique<kf_eh_pointer> ());
+
register_atomic_builtins (kfm);
register_sanitizer_builtins (kfm);
register_varargs_builtins (kfm);
#include "analyzer/checker-path.h"
#include "analyzer/feasible-graph.h"
#include "analyzer/record-layout.h"
+#include "analyzer/function-set.h"
#if ENABLE_ANALYZER
m_hash_map.remove (iter);
}
+// struct exception_node
+
+bool
+exception_node::operator== (const exception_node &other) const
+{
+ return (m_exception_sval == other.m_exception_sval
+ && m_typeinfo_sval == other.m_typeinfo_sval
+ && m_destructor_sval == other.m_destructor_sval);
+}
+
+void
+exception_node::dump_to_pp (pretty_printer *pp,
+ bool simple) const
+{
+ pp_printf (pp, "{exception: ");
+ m_exception_sval->dump_to_pp (pp, simple);
+ pp_string (pp, ", typeinfo: ");
+ m_typeinfo_sval->dump_to_pp (pp, simple);
+ pp_string (pp, ", destructor: ");
+ m_destructor_sval->dump_to_pp (pp, simple);
+ pp_string (pp, "}");
+}
+
+void
+exception_node::dump (FILE *fp, bool simple) const
+{
+ tree_dump_pretty_printer pp (fp);
+ dump_to_pp (&pp, simple);
+ pp_newline (&pp);
+}
+
+/* Dump a multiline representation of this model to stderr. */
+
+DEBUG_FUNCTION void
+exception_node::dump (bool simple) const
+{
+ dump (stderr, simple);
+}
+
+DEBUG_FUNCTION void
+exception_node::dump () const
+{
+ text_art::dump (*this);
+}
+
+std::unique_ptr<json::object>
+exception_node::to_json () const
+{
+ auto obj = std::make_unique<json::object> ();
+ obj->set ("exception", m_exception_sval->to_json ());
+ obj->set ("typeinfo", m_typeinfo_sval->to_json ());
+ obj->set ("destructor", m_destructor_sval->to_json ());
+ return obj;
+}
+
+std::unique_ptr<text_art::tree_widget>
+exception_node::make_dump_widget (const text_art::dump_widget_info &dwi) const
+{
+ using text_art::tree_widget;
+ std::unique_ptr<tree_widget> w
+ (tree_widget::from_fmt (dwi, nullptr, "Exception Node"));
+
+ w->add_child (m_exception_sval->make_dump_widget (dwi, "exception"));
+ w->add_child (m_typeinfo_sval->make_dump_widget (dwi, "typeinfo"));
+ w->add_child (m_destructor_sval->make_dump_widget (dwi, "destructor"));
+
+ return w;
+}
+
+tree
+exception_node::maybe_get_type () const
+{
+ return m_typeinfo_sval->maybe_get_type_from_typeinfo ();
+}
+
+void
+exception_node::add_to_reachable_regions (reachable_regions ®s) const
+{
+ regs.handle_sval (m_exception_sval);
+ regs.handle_sval (m_typeinfo_sval);
+ regs.handle_sval (m_destructor_sval);
+}
+
/* class region_model. */
/* Ctor for region_model: construct an "empty" model. */
region_model::region_model (region_model_manager *mgr)
: m_mgr (mgr), m_store (), m_current_frame (NULL),
+ m_thrown_exceptions_stack (),
+ m_caught_exceptions_stack (),
m_dynamic_extents ()
{
m_constraints = new constraint_manager (mgr);
: m_mgr (other.m_mgr), m_store (other.m_store),
m_constraints (new constraint_manager (*other.m_constraints)),
m_current_frame (other.m_current_frame),
+ m_thrown_exceptions_stack (other.m_thrown_exceptions_stack),
+ m_caught_exceptions_stack (other.m_caught_exceptions_stack),
m_dynamic_extents (other.m_dynamic_extents)
{
}
m_current_frame = other.m_current_frame;
+ m_thrown_exceptions_stack = other.m_thrown_exceptions_stack;
+ m_caught_exceptions_stack = other.m_caught_exceptions_stack;
+
m_dynamic_extents = other.m_dynamic_extents;
return *this;
if (m_current_frame != other.m_current_frame)
return false;
+ if (m_thrown_exceptions_stack != other.m_thrown_exceptions_stack)
+ return false;
+ if (m_caught_exceptions_stack != other.m_caught_exceptions_stack)
+ return false;
+
if (m_dynamic_extents != other.m_dynamic_extents)
return false;
region_model::dump_to_pp (pretty_printer *pp, bool simple,
bool multiline) const
{
- /* Dump stack. */
+ /* Dump frame stack. */
pp_printf (pp, "stack depth: %i", get_stack_depth ());
if (multiline)
pp_newline (pp);
if (!multiline)
pp_string (pp, "}");
+ /* Dump exception stacks. */
+ if (m_thrown_exceptions_stack.size () > 0)
+ {
+ pp_printf (pp, "thrown exceptions: %i", (int)m_thrown_exceptions_stack.size ());
+ if (multiline)
+ pp_newline (pp);
+ else
+ pp_string (pp, " {");
+ for (size_t idx = 0; idx < m_thrown_exceptions_stack.size (); ++idx)
+ {
+ if (multiline)
+ pp_string (pp, " ");
+ else if (idx > 0)
+ pp_string (pp, ", ");
+ pp_printf (pp, "exception (index %i): ", (int)idx);
+ m_thrown_exceptions_stack[idx].dump_to_pp (pp, simple);
+ if (multiline)
+ pp_newline (pp);
+ }
+ if (!multiline)
+ pp_string (pp, "}");
+ }
+ if (m_caught_exceptions_stack.size () > 0)
+ {
+ pp_printf (pp, "caught exceptions: %i", (int)m_caught_exceptions_stack.size ());
+ if (multiline)
+ pp_newline (pp);
+ else
+ pp_string (pp, " {");
+ for (size_t idx = 0; idx < m_caught_exceptions_stack.size (); ++idx)
+ {
+ if (multiline)
+ pp_string (pp, " ");
+ else if (idx > 0)
+ pp_string (pp, ", ");
+ pp_printf (pp, "exception (index %i): ", (int)idx);
+ m_caught_exceptions_stack[idx].dump_to_pp (pp, simple);
+ if (multiline)
+ pp_newline (pp);
+ }
+ if (!multiline)
+ pp_string (pp, "}");
+ }
+
/* Dump store. */
if (!multiline)
pp_string (pp, ", {");
model_obj->set ("constraints", m_constraints->to_json ());
if (m_current_frame)
model_obj->set ("current_frame", m_current_frame->to_json ());
+
+ auto thrown_exceptions_arr = std::make_unique<json::array> ();
+ for (auto &node : m_thrown_exceptions_stack)
+ thrown_exceptions_arr->append (node.to_json ());
+ model_obj->set ("thrown_exception_stack", std::move (thrown_exceptions_arr));
+
+ auto caught_exceptions_arr = std::make_unique<json::array> ();
+ for (auto &node : m_caught_exceptions_stack)
+ caught_exceptions_arr->append (node.to_json ());
+ model_obj->set ("caught_exception_stack", std::move (caught_exceptions_arr));
+
model_obj->set ("dynamic_extents", m_dynamic_extents.to_json ());
return model_obj;
}
m_current_frame->dump_to_pp (pp, simple);
model_widget->add_child (tree_widget::make (dwi, pp));
}
+
+ if (m_thrown_exceptions_stack.size () > 0)
+ {
+ auto thrown_exceptions_widget
+ = tree_widget::make (dwi, "Thrown Exceptions");
+ for (auto &thrown_exception : m_thrown_exceptions_stack)
+ thrown_exceptions_widget->add_child
+ (thrown_exception.make_dump_widget (dwi));
+ model_widget->add_child (std::move (thrown_exceptions_widget));
+ }
+ if (m_caught_exceptions_stack.size () > 0)
+ {
+ auto caught_exceptions_widget
+ = tree_widget::make (dwi, "Caught Exceptions");
+ for (auto &caught_exception : m_caught_exceptions_stack)
+ caught_exceptions_widget->add_child
+ (caught_exception.make_dump_widget (dwi));
+ model_widget->add_child (std::move (caught_exceptions_widget));
+ }
+
model_widget->add_child
(m_store.make_dump_widget (dwi,
m_mgr->get_store_manager ()));
return NULL;
}
+/* Subclass of custom_edge_info for use by exploded_edges that represent
+ an exception being thrown from a call we don't have the code for. */
+
+class exception_thrown_from_unrecognized_call : public custom_edge_info
+{
+public:
+ exception_thrown_from_unrecognized_call (const gcall &call,
+ tree fndecl)
+ : m_call (call),
+ m_fndecl (fndecl)
+ {
+ }
+
+ void print (pretty_printer *pp) const
+ {
+ if (m_fndecl)
+ pp_printf (pp, "if %qD throws an exception...", m_fndecl);
+ else
+ pp_printf (pp, "if the called function throws an exception...");
+ };
+
+ bool
+ update_model (region_model *model,
+ const exploded_edge *,
+ region_model_context *ctxt) const final override
+ {
+ /* Allocate an exception and set it as the current exception. */
+ const region *exception_reg
+ = model->get_or_create_region_for_heap_alloc
+ (nullptr, /* We don't know the size of the region. */
+ ctxt);
+
+ region_model_manager *mgr = model->get_manager ();
+ conjured_purge p (model, ctxt);
+
+ /* The contents of the region are some conjured svalue. */
+ const svalue *exception_sval
+ = mgr->get_or_create_conjured_svalue (NULL_TREE,
+ &m_call,
+ exception_reg, p, 0);
+ model->set_value (exception_reg, exception_sval, ctxt);
+ const svalue *exception_ptr_sval
+ = mgr->get_ptr_svalue (ptr_type_node, exception_reg);
+ const svalue *tinfo_sval
+ = mgr->get_or_create_conjured_svalue (ptr_type_node,
+ &m_call,
+ exception_reg, p, 1);
+ const svalue *destructor_sval
+ = mgr->get_or_create_conjured_svalue (ptr_type_node,
+ &m_call,
+ exception_reg, p, 2);
+
+ /* Push a new exception_node on the model's thrown exception stack. */
+ exception_node eh_node (exception_ptr_sval, tinfo_sval, destructor_sval);
+ model->push_thrown_exception (eh_node);
+
+ return true;
+ }
+
+ void
+ add_events_to_path (checker_path *emission_path,
+ const exploded_edge &eedge) const final override
+ {
+ const exploded_node *dst_node = eedge.m_dest;
+ const program_point &dst_point = dst_node->get_point ();
+ const int dst_stack_depth = dst_point.get_stack_depth ();
+
+ emission_path->add_event
+ (std::make_unique<throw_from_call_to_external_fn_event>
+ (event_loc_info (m_call.location,
+ dst_point.get_fndecl (),
+ dst_stack_depth),
+ dst_node,
+ m_call,
+ m_fndecl));
+ }
+
+ exploded_node *
+ create_enode (exploded_graph &eg,
+ const program_point &point,
+ program_state &&state,
+ exploded_node *enode_for_diag,
+ region_model_context *ctxt) const final override
+ {
+ exploded_node *thrown_enode
+ = eg.get_or_create_node (point, state, enode_for_diag,
+ /* Don't add to worklist. */
+ false);
+ if (!thrown_enode)
+ return nullptr;
+
+ /* Add successor edges for thrown_enode "by hand" for the exception. */
+ eg.unwind_from_exception (*thrown_enode,
+ &m_call,
+ ctxt);
+ return thrown_enode;
+ }
+
+private:
+ const gcall &m_call;
+ tree m_fndecl; // could be null
+};
+
+/* Get a set of functions that are assumed to not throw exceptions. */
+
+static function_set
+get_fns_assumed_not_to_throw ()
+{
+ // TODO: populate this list more fully
+ static const char * const fn_names[] = {
+ /* This array must be kept sorted. */
+
+ "fclose"
+ };
+ const size_t count = ARRAY_SIZE (fn_names);
+ function_set fs (fn_names, count);
+ return fs;
+}
+
+/* Return true if CALL could throw an exception.
+ FNDECL could be NULL_TREE. */
+
+static bool
+can_throw_p (const gcall &call, tree fndecl)
+{
+ if (!flag_exceptions)
+ return false;
+
+ if (gimple_call_nothrow_p (&call))
+ return false;
+
+ if (fndecl)
+ {
+ const function_set fs = get_fns_assumed_not_to_throw ();
+ if (fs.contains_decl_p (fndecl))
+ return false;
+ }
+
+ return true;
+}
+
+/* Given CALL where we don't know what code is being called
+ (by not having the body of FNDECL, or having NULL_TREE for FNDECL),
+ potentially bifurcate control flow to simulate the call throwing
+ an exception. */
+
+void
+region_model::check_for_throw_inside_call (const gcall &call,
+ tree fndecl,
+ region_model_context *ctxt)
+{
+ if (!ctxt)
+ return;
+
+ /* Could this function throw an exception?
+ If so, add an extra e-edge for that. */
+ if (!can_throw_p (call, fndecl))
+ return;
+
+ auto throws_exception
+ = std::make_unique<exception_thrown_from_unrecognized_call> (call, fndecl);
+ ctxt->bifurcate (std::move (throws_exception));
+}
+
/* Update this model for the CALL stmt, using CTXT to report any
diagnostics - the first half.
if (!callee_fndecl)
{
+ check_for_throw_inside_call (call, NULL_TREE, ctxt);
cd.set_any_lhs_with_defaults ();
return true; /* Unknown side effects. */
}
return true; /* Unknown side effects. */
if (!fndecl_has_gimple_body_p (callee_fndecl))
- return true; /* Unknown side effects. */
+ {
+ check_for_throw_inside_call (call, callee_fndecl, ctxt);
+ return true; /* Unknown side effects. */
+ }
return false; /* No side effects. */
}
ctxt, out);
}
+ if (const geh_dispatch *eh_dispatch_stmt
+ = dyn_cast <const geh_dispatch *> (last_stmt))
+ {
+ const eh_dispatch_cfg_superedge *eh_dispatch_cfg_sedge
+ = as_a <const eh_dispatch_cfg_superedge *> (&edge);
+ return apply_constraints_for_eh_dispatch (*eh_dispatch_cfg_sedge,
+ eh_dispatch_stmt,
+ ctxt, out);
+ }
+
if (const ggoto *goto_stmt = dyn_cast <const ggoto *> (last_stmt))
{
const cfg_superedge *cfg_sedge = as_a <const cfg_superedge *> (&edge);
return apply_constraints_for_ggoto (*cfg_sedge, goto_stmt, ctxt);
}
- /* 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, out);
-
return true;
}
return sat;
}
+class rejected_eh_dispatch : public rejected_constraint
+{
+public:
+ rejected_eh_dispatch (const region_model &model)
+ : rejected_constraint (model)
+ {}
+
+ void dump_to_pp (pretty_printer *pp) const final override
+ {
+ pp_printf (pp, "rejected_eh_dispatch");
+ }
+};
+
+static bool
+exception_matches_type_p (tree exception_type,
+ tree catch_type)
+{
+ if (catch_type == exception_type)
+ return true;
+
+ /* TODO (PR analyzer/119697): we should also handle subclasses etc;
+ see the rules in https://en.cppreference.com/w/cpp/language/catch
+
+ It looks like we should be calling (or emulating)
+ can_convert_eh from the C++ FE, but that's specific to the C++ FE. */
+
+ return false;
+}
+
+static bool
+matches_any_exception_type_p (eh_catch ehc, tree exception_type)
+{
+ if (ehc->type_list == NULL_TREE)
+ /* All exceptions are caught here. */
+ return true;
+
+ for (tree iter = ehc->type_list; iter; iter = TREE_CHAIN (iter))
+ if (exception_matches_type_p (TREE_VALUE (iter),
+ exception_type))
+ return true;
+ return false;
+}
+
+bool
+region_model::
+apply_constraints_for_eh_dispatch (const eh_dispatch_cfg_superedge &edge,
+ const geh_dispatch *,
+ region_model_context *ctxt,
+ std::unique_ptr<rejected_constraint> *out)
+{
+ const exception_node *current_node = get_current_thrown_exception ();
+ gcc_assert (current_node);
+ tree curr_exception_type = current_node->maybe_get_type ();
+ if (!curr_exception_type)
+ /* We don't know the specific type. */
+ return true;
+
+ return edge.apply_constraints (this, ctxt, curr_exception_type, out);
+}
+
+bool
+region_model::
+apply_constraints_for_eh_dispatch_try (const eh_dispatch_try_cfg_superedge &edge,
+ region_model_context */*ctxt*/,
+ tree exception_type,
+ std::unique_ptr<rejected_constraint> *out)
+{
+ /* TODO: can we rely on this ordering?
+ or do we need to iterate through prev_catch ? */
+ /* The exception must not match any of the previous edges. */
+ for (auto sibling_sedge : edge.m_src->m_succs)
+ {
+ if (sibling_sedge == &edge)
+ break;
+
+ const eh_dispatch_try_cfg_superedge *sibling_eh_sedge
+ = as_a <const eh_dispatch_try_cfg_superedge *> (sibling_sedge);
+ if (eh_catch ehc = sibling_eh_sedge->get_eh_catch ())
+ if (matches_any_exception_type_p (ehc, exception_type))
+ {
+ /* The earlier sibling matches, so the "unhandled" edge is
+ not taken. */
+ if (out)
+ *out = std::make_unique<rejected_eh_dispatch> (*this);
+ return false;
+ }
+ }
+
+ if (eh_catch ehc = edge.get_eh_catch ())
+ {
+ /* We have an edge that tried to match one or more types. */
+
+ /* The exception must not match any of the previous edges. */
+
+ /* It must match this type. */
+ if (matches_any_exception_type_p (ehc, exception_type))
+ return true;
+ else
+ {
+ /* Exception type doesn't match. */
+ if (out)
+ *out = std::make_unique<rejected_eh_dispatch> (*this);
+ return false;
+ }
+ }
+ else
+ {
+ /* This is the "unhandled exception" edge.
+ If we get here then no sibling edges matched;
+ we will follow this edge. */
+ return true;
+ }
+}
+
+bool
+region_model::
+apply_constraints_for_eh_dispatch_allowed (const eh_dispatch_allowed_cfg_superedge &edge,
+ region_model_context */*ctxt*/,
+ tree exception_type,
+ std::unique_ptr<rejected_constraint> *out)
+{
+ auto curr_thrown_exception_node = get_current_thrown_exception ();
+ gcc_assert (curr_thrown_exception_node);
+ tree curr_exception_type = curr_thrown_exception_node->maybe_get_type ();
+ eh_region eh_reg = edge.get_eh_region ();
+ tree type_list = eh_reg->u.allowed.type_list;
+
+ switch (edge.get_eh_kind ())
+ {
+ default:
+ gcc_unreachable ();
+ case eh_dispatch_allowed_cfg_superedge::eh_kind::expected:
+ if (!curr_exception_type)
+ {
+ /* We don't know the specific type;
+ assume we have one of an expected type. */
+ return true;
+ }
+ for (tree iter = type_list; iter; iter = TREE_CHAIN (iter))
+ if (exception_matches_type_p (TREE_VALUE (iter),
+ exception_type))
+ return true;
+ if (out)
+ *out = std::make_unique<rejected_eh_dispatch> (*this);
+ return false;
+
+ case eh_dispatch_allowed_cfg_superedge::eh_kind::unexpected:
+ if (!curr_exception_type)
+ {
+ /* We don't know the specific type;
+ assume we don't have one of an expected type. */
+ if (out)
+ *out = std::make_unique<rejected_eh_dispatch> (*this);
+ return false;
+ }
+ for (tree iter = type_list; iter; iter = TREE_CHAIN (iter))
+ if (exception_matches_type_p (TREE_VALUE (iter),
+ exception_type))
+ {
+ if (out)
+ *out = std::make_unique<rejected_eh_dispatch> (*this);
+ return false;
+ }
+ return true;
+ }
+}
+
/* Given an edge reached by GOTO_STMT, determine appropriate constraints
for the edge to be taken.
return true;
}
-/* Apply any constraints due to an exception being thrown at LAST_STMT.
-
- 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).
- 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,
- std::unique_ptr<rejected_constraint> *out)
-{
- gcc_assert (last_stmt);
- if (const gcall *call = dyn_cast <const gcall *> (last_stmt))
- if (tree callee_fndecl = get_fndecl_for_call (*call, ctxt))
- if (is_named_call_p (callee_fndecl, "operator new", *call, 1)
- || is_named_call_p (callee_fndecl, "operator new []", *call, 1))
- {
- /* We have an exception thrown from operator new.
- Add a constraint that the result was NULL, to avoid a false
- 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, out);
- return true;
- }
- return true;
-}
-
/* For use with push_frame when handling a top-level call within the analysis.
PARAM has a defined but unknown initial value.
Anything it points to has escaped, since the calling context "knows"
for (auto iter : m.m_svals_changing_meaning)
out_model->m_constraints->purge_state_involving (iter);
+ if (m_thrown_exceptions_stack != other_model.m_thrown_exceptions_stack)
+ return false;
+ out_model->m_thrown_exceptions_stack = m_thrown_exceptions_stack;
+
+ if (m_caught_exceptions_stack != other_model.m_caught_exceptions_stack)
+ return false;
+ out_model->m_caught_exceptions_stack = m_caught_exceptions_stack;
+
return true;
}
reachable_regs.add (base_reg, false);
}
+ for (auto &eh_node : m_thrown_exceptions_stack)
+ eh_node.add_to_reachable_regions (reachable_regs);
+ for (auto &eh_node : m_caught_exceptions_stack)
+ eh_node.add_to_reachable_regions (reachable_regs);
+
+
bitmap_clear (out_ids);
for (auto iter_reg : reachable_regs)
bitmap_set_bit (out_ids, iter_reg->get_id ());
const svalue *retval,
region_model_context *ctxt);
+/* Roughly equivalent to a struct __cxa_exception, except we store a std::vector
+ rather than a linked list. */
+
+struct exception_node
+{
+ exception_node (const svalue *exception_sval,
+ const svalue *typeinfo_sval,
+ const svalue *destructor_sval)
+ : m_exception_sval (exception_sval),
+ m_typeinfo_sval (typeinfo_sval),
+ m_destructor_sval (destructor_sval)
+ {
+ }
+
+ bool operator== (const exception_node &other) const;
+
+ void dump_to_pp (pretty_printer *pp, bool simple) const;
+ void dump (FILE *fp, bool simple) const;
+ void dump (bool simple) const;
+ void dump () const;
+
+ std::unique_ptr<json::object> to_json () const;
+
+ std::unique_ptr<text_art::tree_widget>
+ make_dump_widget (const text_art::dump_widget_info &dwi) const;
+
+ tree maybe_get_type () const;
+
+ void add_to_reachable_regions (reachable_regions &) const;
+
+ const svalue *m_exception_sval;
+ const svalue *m_typeinfo_sval;
+ const svalue *m_destructor_sval;
+};
+
/* A region_model encapsulates a representation of the state of memory, with
a tree of regions, along with their associated values.
The representation is graph-like because values can be pointers to
bool called_from_main_p () const;
+ void push_thrown_exception (const exception_node &node)
+ {
+ m_thrown_exceptions_stack.push_back (node);
+ }
+ const exception_node *get_current_thrown_exception () const
+ {
+ if (m_thrown_exceptions_stack.empty ())
+ return nullptr;
+ return &m_thrown_exceptions_stack.back ();
+ }
+ exception_node pop_thrown_exception ()
+ {
+ gcc_assert (!m_thrown_exceptions_stack.empty ());
+ const exception_node retval = m_thrown_exceptions_stack.back ();
+ m_thrown_exceptions_stack.pop_back ();
+ return retval;
+ }
+
+ void push_caught_exception (const exception_node &node)
+ {
+ m_caught_exceptions_stack.push_back (node);
+ }
+ const exception_node *get_current_caught_exception () const
+ {
+ if (m_caught_exceptions_stack.empty ())
+ return nullptr;
+ return &m_caught_exceptions_stack.back ();
+ }
+ exception_node pop_caught_exception ()
+ {
+ gcc_assert (!m_caught_exceptions_stack.empty ());
+ const exception_node retval = m_caught_exceptions_stack.back ();
+ m_caught_exceptions_stack.pop_back ();
+ return retval;
+ }
+
+ bool
+ apply_constraints_for_eh_dispatch_try
+ (const eh_dispatch_try_cfg_superedge &edge,
+ region_model_context *ctxt,
+ tree exception_type,
+ std::unique_ptr<rejected_constraint> *out);
+
+ bool
+ apply_constraints_for_eh_dispatch_allowed
+ (const eh_dispatch_allowed_cfg_superedge &edge,
+ region_model_context *ctxt,
+ tree exception_type,
+ std::unique_ptr<rejected_constraint> *out);
+
private:
const region *get_lvalue_1 (path_var pv, region_model_context *ctxt) const;
const svalue *get_rvalue_1 (path_var pv, region_model_context *ctxt) const;
bool apply_constraints_for_ggoto (const cfg_superedge &edge,
const ggoto *goto_stmt,
region_model_context *ctxt);
- bool apply_constraints_for_exception (const gimple *last_stmt,
- region_model_context *ctxt,
- std::unique_ptr<rejected_constraint> *out);
+
+ bool
+ apply_constraints_for_eh_dispatch (const eh_dispatch_cfg_superedge &edge,
+ const geh_dispatch *eh_dispatch_stmt,
+ region_model_context *ctxt,
+ std::unique_ptr<rejected_constraint> *out);
int poison_any_pointers_to_descendents (const region *reg,
enum poison_kind pkind);
tree callee_fndecl,
region_model_context *ctxt);
+ void check_for_throw_inside_call (const gcall &call,
+ tree fndecl,
+ region_model_context *ctxt);
+
static auto_vec<pop_frame_callback> pop_frame_callbacks;
/* Storing this here to avoid passing it around everywhere. */
region_model_manager *const m_mgr;
const frame_region *m_current_frame;
+ std::vector<exception_node> m_thrown_exceptions_stack;
+ std::vector<exception_node> m_caught_exceptions_stack;
+
/* Map from base region to size in bytes, for tracking the sizes of
dynamically-allocated regions.
This is part of the region_model rather than the region to allow for
#include "tree-cfg.h"
#include "tree-dfa.h"
#include "cfganal.h"
+#include "except.h"
#include "analyzer/supergraph.h"
#include "analyzer/analyzer-logging.h"
+#include "analyzer/region-model.h"
#if ENABLE_ANALYZER
/* Create a new cfg_superedge from SRC to DEST for the underlying CFG edge E,
adding it to this supergraph.
- If the edge is for a switch statement, create a switch_cfg_superedge
- subclass. */
+ If the edge is for a switch or eh_dispatch statement, create a
+ switch_cfg_superedge or eh_dispatch_cfg_superedge subclass,
+ respectively */
cfg_superedge *
supergraph::add_cfg_edge (supernode *src, supernode *dest, ::edge e)
{
- /* Special-case switch edges. */
+ /* Special-case switch and eh_dispatch edges. */
gimple *stmt = src->get_last_stmt ();
- cfg_superedge *new_edge;
+ std::unique_ptr<cfg_superedge> new_edge;
if (stmt && stmt->code == GIMPLE_SWITCH)
- new_edge = new switch_cfg_superedge (src, dest, e);
+ new_edge = std::make_unique<switch_cfg_superedge> (src, dest, e);
+ else if (stmt && stmt->code == GIMPLE_EH_DISPATCH)
+ new_edge = eh_dispatch_cfg_superedge::make (src, dest, e,
+ as_a <geh_dispatch *> (stmt));
else
- new_edge = new cfg_superedge (src, dest, e);
- add_edge (new_edge);
- return new_edge;
+ new_edge = std::make_unique<cfg_superedge> (src, dest, e);
+ add_edge (new_edge.get ());
+ return new_edge.release ();
}
/* Create and add a call_superedge representing an interprocedural call
superedge::get_description (bool user_facing) const
{
pretty_printer pp;
+ pp_format_decoder (&pp) = default_tree_printer;
dump_label_to_pp (&pp, user_facing);
return label_text::take (xstrdup (pp_formatted_text (&pp)));
}
return gimple_phi_arg_def (phi, index);
}
+/* class switch_cfg_superedge : public cfg_superedge. */
+
switch_cfg_superedge::switch_cfg_superedge (supernode *src,
supernode *dst,
::edge e)
return EXPR_LOCATION (case_label) == UNKNOWN_LOCATION;
}
+/* class eh_dispatch_cfg_superedge : public cfg_superedge. */
+
+/* Given an ERT_TRY region, get the eh_catch corresponding to
+ the label of DST_SNODE, if any. */
+
+static eh_catch
+get_catch (eh_region eh_reg, supernode *dst_snode)
+{
+ gcc_assert (eh_reg->type == ERT_TRY);
+
+ tree dst_snode_label = dst_snode->get_label ();
+ if (!dst_snode_label)
+ return nullptr;
+
+ for (eh_catch iter = eh_reg->u.eh_try.first_catch;
+ iter;
+ iter = iter->next_catch)
+ if (iter->label == dst_snode_label)
+ return iter;
+
+ return nullptr;
+}
+
+std::unique_ptr<eh_dispatch_cfg_superedge>
+eh_dispatch_cfg_superedge::make (supernode *src_snode,
+ supernode *dst_snode,
+ ::edge e,
+ const geh_dispatch *eh_dispatch_stmt)
+{
+ const eh_status *eh = src_snode->get_function ()->eh;
+ gcc_assert (eh);
+ int region_idx = gimple_eh_dispatch_region (eh_dispatch_stmt);
+ gcc_assert (region_idx > 0);
+ gcc_assert ((*eh->region_array)[region_idx]);
+ eh_region eh_reg = (*eh->region_array)[region_idx];
+ gcc_assert (eh_reg);
+ switch (eh_reg->type)
+ {
+ default:
+ gcc_unreachable ();
+ case ERT_CLEANUP:
+ // TODO
+ gcc_unreachable ();
+ break;
+ case ERT_TRY:
+ {
+ eh_catch ehc = get_catch (eh_reg, dst_snode);
+ return std::make_unique<eh_dispatch_try_cfg_superedge>
+ (src_snode, dst_snode,
+ e, eh_dispatch_stmt,
+ eh_reg, ehc);
+ }
+ break;
+ case ERT_ALLOWED_EXCEPTIONS:
+ return std::make_unique<eh_dispatch_allowed_cfg_superedge>
+ (src_snode, dst_snode,
+ e, eh_dispatch_stmt,
+ eh_reg);
+ break;
+ case ERT_MUST_NOT_THROW:
+ // TODO
+ gcc_unreachable ();
+ break;
+ }
+}
+
+eh_dispatch_cfg_superedge::
+eh_dispatch_cfg_superedge (supernode *src,
+ supernode *dst,
+ ::edge e,
+ const geh_dispatch *eh_dispatch_stmt,
+ eh_region eh_reg)
+: cfg_superedge (src, dst, e),
+ m_eh_dispatch_stmt (eh_dispatch_stmt),
+ m_eh_region (eh_reg)
+{
+ gcc_assert (m_eh_region);
+}
+
+const eh_status &
+eh_dispatch_cfg_superedge::get_eh_status () const
+{
+ const eh_status *eh = m_src->get_function ()->eh;
+ gcc_assert (eh);
+ return *eh;
+}
+
+// class eh_dispatch_try_cfg_superedge : public eh_dispatch_cfg_superedge
+
+/* Implementation of superedge::dump_label_to_pp for CFG superedges for
+ "eh_dispatch" statements for ERT_TRY regions. */
+
+void
+eh_dispatch_try_cfg_superedge::dump_label_to_pp (pretty_printer *pp,
+ bool user_facing) const
+{
+ if (!user_facing)
+ pp_string (pp, "ERT_TRY: ");
+ if (m_eh_catch)
+ {
+ bool first = true;
+ for (tree iter = m_eh_catch->type_list; iter; iter = TREE_CHAIN (iter))
+ {
+ if (!first)
+ pp_string (pp, ", ");
+ pp_printf (pp, "on catch %qT", TREE_VALUE (iter));
+ first = false;
+ }
+ }
+ else
+ pp_string (pp, "on uncaught exception");
+}
+
+bool
+eh_dispatch_try_cfg_superedge::
+apply_constraints (region_model *model,
+ region_model_context *ctxt,
+ tree exception_type,
+ std::unique_ptr<rejected_constraint> *out) const
+{
+ return model->apply_constraints_for_eh_dispatch_try
+ (*this, ctxt, exception_type, out);
+}
+
+// class eh_dispatch_allowed_cfg_superedge : public eh_dispatch_cfg_superedge
+
+eh_dispatch_allowed_cfg_superedge::
+eh_dispatch_allowed_cfg_superedge (supernode *src, supernode *dst, ::edge e,
+ const geh_dispatch *eh_dispatch_stmt,
+ eh_region eh_reg)
+: eh_dispatch_cfg_superedge (src, dst, e, eh_dispatch_stmt, eh_reg)
+{
+ gcc_assert (eh_reg->type == ERT_ALLOWED_EXCEPTIONS);
+
+ /* We expect two sibling out-edges at an eh_dispatch from such a region:
+
+ - one to a bb without a gimple label, with a resx,
+ for exceptions of expected types
+
+ - one to a bb with a gimple label, with a call to __cxa_unexpected,
+ for exceptions of unexpected types.
+
+ Set m_kind for this edge accordingly. */
+ gcc_assert (e->src->succs->length () == 2);
+ tree label_for_unexpected_exceptions = eh_reg->u.allowed.label;
+ tree label_for_dest_enode = dst->get_label ();
+ if (label_for_dest_enode == label_for_unexpected_exceptions)
+ m_kind = eh_kind::unexpected;
+ else
+ {
+ gcc_assert (label_for_dest_enode == nullptr);
+ m_kind = eh_kind::expected;
+ }
+}
+
+void
+eh_dispatch_allowed_cfg_superedge::dump_label_to_pp (pretty_printer *pp,
+ bool user_facing) const
+{
+ if (!user_facing)
+ {
+ switch (m_kind)
+ {
+ default:
+ gcc_unreachable ();
+ case eh_dispatch_allowed_cfg_superedge::eh_kind::expected:
+ pp_string (pp, "expected: ");
+ break;
+ case eh_dispatch_allowed_cfg_superedge::eh_kind::unexpected:
+ pp_string (pp, "unexpected: ");
+ break;
+ }
+ pp_string (pp, "ERT_ALLOWED_EXCEPTIONS: ");
+ eh_region eh_reg = get_eh_region ();
+ bool first = true;
+ for (tree iter = eh_reg->u.allowed.type_list; iter;
+ iter = TREE_CHAIN (iter))
+ {
+ if (!first)
+ pp_string (pp, ", ");
+ pp_printf (pp, "%qT", TREE_VALUE (iter));
+ first = false;
+ }
+ }
+}
+
+bool
+eh_dispatch_allowed_cfg_superedge::
+apply_constraints (region_model *model,
+ region_model_context *ctxt,
+ tree exception_type,
+ std::unique_ptr<rejected_constraint> *out) const
+{
+ return model->apply_constraints_for_eh_dispatch_allowed
+ (*this, ctxt, exception_type, out);
+}
+
/* Implementation of superedge::dump_label_to_pp for interprocedural
superedges. */
#include "gimple.h"
#include "gimple-iterator.h"
#include "digraph.h"
+#include "except.h"
using namespace ana;
class return_superedge;
class cfg_superedge;
class switch_cfg_superedge;
+ class eh_dispatch_cfg_superedge;
+ class eh_dispatch_try_cfg_superedge;
+ class eh_dispatch_allowed_cfg_superedge;
class supercluster;
class dot_annotator;
virtual cfg_superedge *dyn_cast_cfg_superedge () { return NULL; }
virtual const cfg_superedge *dyn_cast_cfg_superedge () const { return NULL; }
virtual const switch_cfg_superedge *dyn_cast_switch_cfg_superedge () const { return NULL; }
+ virtual const eh_dispatch_cfg_superedge *dyn_cast_eh_dispatch_cfg_superedge () const { return nullptr; }
+ virtual const eh_dispatch_try_cfg_superedge *dyn_cast_eh_dispatch_try_cfg_superedge () const { return nullptr; }
+ virtual const eh_dispatch_allowed_cfg_superedge *dyn_cast_eh_dispatch_allowed_cfg_superedge () const { return nullptr; }
virtual callgraph_superedge *dyn_cast_callgraph_superedge () { return NULL; }
virtual const callgraph_superedge *dyn_cast_callgraph_superedge () const { return NULL; }
virtual call_superedge *dyn_cast_call_superedge () { return NULL; }
namespace ana {
+/* A subclass for edges from eh_dispatch statements, retaining enough
+ information to identify the various types being caught, vs the
+ "unhandled type" case, and for adding labels when rendering
+ via graphviz.
+ This is abstract; there are concrete subclasses based on the type
+ of the eh_region. */
+
+class eh_dispatch_cfg_superedge : public cfg_superedge
+{
+ public:
+ static std::unique_ptr<eh_dispatch_cfg_superedge>
+ make (supernode *src,
+ supernode *dest,
+ ::edge e,
+ const geh_dispatch *eh_dispatch_stmt);
+
+ const eh_dispatch_cfg_superedge *dyn_cast_eh_dispatch_cfg_superedge () const
+ final override
+ {
+ return this;
+ }
+
+ const geh_dispatch *
+ get_eh_dispatch_stmt () const
+ {
+ return m_eh_dispatch_stmt;
+ }
+
+ const eh_status &get_eh_status () const;
+ eh_region get_eh_region () const { return m_eh_region; }
+
+ virtual bool
+ apply_constraints (region_model *model,
+ region_model_context *ctxt,
+ tree exception_type,
+ std::unique_ptr<rejected_constraint> *out) const = 0;
+
+protected:
+ eh_dispatch_cfg_superedge (supernode *src, supernode *dst, ::edge e,
+ const geh_dispatch *eh_dispatch_stmt,
+ eh_region eh_reg);
+
+private:
+ const geh_dispatch *m_eh_dispatch_stmt;
+ eh_region m_eh_region;
+};
+
+} // namespace ana
+
+template <>
+template <>
+inline bool
+is_a_helper <const eh_dispatch_cfg_superedge *>::test (const superedge *sedge)
+{
+ return sedge->dyn_cast_eh_dispatch_cfg_superedge () != NULL;
+}
+
+namespace ana {
+
+/* A concrete subclass for edges from an eh_dispatch statements
+ for ERT_TRY regions. */
+
+class eh_dispatch_try_cfg_superedge : public eh_dispatch_cfg_superedge
+{
+ public:
+ eh_dispatch_try_cfg_superedge (supernode *src, supernode *dst, ::edge e,
+ const geh_dispatch *eh_dispatch_stmt,
+ eh_region eh_reg,
+ eh_catch ehc)
+ : eh_dispatch_cfg_superedge (src, dst, e, eh_dispatch_stmt, eh_reg),
+ m_eh_catch (ehc)
+ {
+ gcc_assert (eh_reg->type == ERT_TRY);
+ }
+
+ const eh_dispatch_try_cfg_superedge *
+ dyn_cast_eh_dispatch_try_cfg_superedge () const final override
+ {
+ return this;
+ }
+
+ void dump_label_to_pp (pretty_printer *pp,
+ bool user_facing) const final override;
+
+ eh_catch get_eh_catch () const { return m_eh_catch; }
+
+ bool
+ apply_constraints (region_model *model,
+ region_model_context *ctxt,
+ tree exception_type,
+ std::unique_ptr<rejected_constraint> *out)
+ const final override;
+
+private:
+ eh_catch m_eh_catch;
+};
+
+} // namespace ana
+
+template <>
+template <>
+inline bool
+is_a_helper <const eh_dispatch_try_cfg_superedge *>::test (const superedge *sedge)
+{
+ return sedge->dyn_cast_eh_dispatch_try_cfg_superedge () != NULL;
+}
+
+namespace ana {
+
+/* A concrete subclass for edges from an eh_dispatch statements
+ for ERT_ALLOWED_EXCEPTIONS regions. */
+
+class eh_dispatch_allowed_cfg_superedge : public eh_dispatch_cfg_superedge
+{
+ public:
+ enum eh_kind
+ {
+ expected,
+ unexpected
+ };
+
+ eh_dispatch_allowed_cfg_superedge (supernode *src, supernode *dst, ::edge e,
+ const geh_dispatch *eh_dispatch_stmt,
+ eh_region eh_reg);
+
+ const eh_dispatch_allowed_cfg_superedge *
+ dyn_cast_eh_dispatch_allowed_cfg_superedge () const final override
+ {
+ return this;
+ }
+
+ void dump_label_to_pp (pretty_printer *pp,
+ bool user_facing) const final override;
+
+ bool
+ apply_constraints (region_model *model,
+ region_model_context *ctxt,
+ tree exception_type,
+ std::unique_ptr<rejected_constraint> *out)
+ const final override;
+
+ enum eh_kind get_eh_kind () const { return m_kind; }
+
+private:
+ enum eh_kind m_kind;
+};
+
+} // namespace ana
+
+template <>
+template <>
+inline bool
+is_a_helper <const eh_dispatch_allowed_cfg_superedge *>::test (const superedge *sedge)
+{
+ return sedge->dyn_cast_eh_dispatch_allowed_cfg_superedge () != NULL;
+}
+
+namespace ana {
/* Base class for adding additional content to the .dot output
for a supergraph. */
}
}
+/* If this svalue is a pointer to the typeinfo instance for a particular
+ type, return that type. Otherwise return NULL_TREE. */
+
+tree
+svalue::maybe_get_type_from_typeinfo () const
+{
+ if (const region *reg = maybe_get_region ())
+ if (const decl_region *decl_reg = reg->dyn_cast_decl_region ())
+ return TREE_TYPE (DECL_NAME (decl_reg->get_decl ()));
+
+ return NULL_TREE;
+}
+
/* class region_svalue : public svalue. */
/* Implementation of svalue::dump_to_pp vfunc for region_svalue. */
const region_model &model,
const svalue *outer_sval = nullptr) const;
+ tree maybe_get_type_from_typeinfo () const;
+
protected:
svalue (complexity c, symbol::id_t id, tree type)
: symbol (c, id), m_type (type)
-Wno-analyzer-tainted-divisor
-Wno-analyzer-tainted-offset
-Wno-analyzer-tainted-size
+-Wno-analyzer-throw-of-unexpected-type
-Wanalyzer-symbol-too-complex
-Wanalyzer-too-complex
-Wno-analyzer-undefined-behavior-ptrdiff
-Wanalyzer-tainted-divisor
-Wanalyzer-tainted-offset
-Wanalyzer-tainted-size
+-Wanalyzer-throw-of-unexpected-type
-Wanalyzer-undefined-behavior-ptrdiff
-Wanalyzer-undefined-behavior-strtok
-Wanalyzer-unsafe-call-within-signal-handler
See @uref{https://cwe.mitre.org/data/definitions/129.html, CWE-129: Improper Validation of Array Index}.
+@opindex Wanalyzer-throw-of-unexpected-type
+@opindex Wno-analyzer-throw-of-unexpected-type
+@item -Wno-analyzer-throw-of-unexpected-type
+This warning requires @option{-fanalyzer} which enables it;
+use @option{-Wno-analyzer-throw-of-unexpected-type} to disable it.
+Dynamic exception specifications are only available in C++14 and earlier.
+
+This diagnostic warns for paths through the code in which a an exception
+is thrown from a function with a dynamic exception specification where
+the exception does not comply with the specification.
+
@opindex Wanalyzer-undefined-behavior-ptrdiff
@opindex Wno-analyzer-undefined-behavior-ptrdiff
@item -Wno-analyzer-undefined-behavior-ptrdiff
/* Return true if S is a nothrow call. */
inline bool
-gimple_call_nothrow_p (gcall *s)
+gimple_call_nothrow_p (const gcall *s)
{
return (gimple_call_flags (s) & ECF_NOTHROW) != 0;
}
-/* { dg-additional-options "-fanalyzer-verbosity=2" } */
+/* { dg-additional-options "-fanalyzer-verbosity=2 -fno-exceptions" } */
typedef struct FILE FILE;
-/* { dg-additional-options "-fanalyzer-verbosity=3" } */
+/* { dg-additional-options "-fanalyzer-verbosity=3 -fno-exceptions" } */
typedef struct FILE FILE;
extern int const_p (int) __attribute__((const));
-extern void do_stuff (void);
+extern void do_stuff (void) __attribute__((nothrow));
void test (int a)
{
struct foo;
extern void foo_release (struct foo *)
- __attribute__((nonnull));
+ __attribute__((nonnull, nothrow));
extern struct foo *foo_acquire (void)
__attribute__ ((malloc (foo_release)));
/* Example of extra argument to "malloc" attribute. */
struct foo;
-extern void foo_release (int, struct foo *);
+extern void foo_release (int, struct foo *) __attribute__((nothrow));
extern struct foo *foo_acquire (void)
__attribute__ ((malloc (foo_release, 2)));
/* Adapted from gcc.dg/Wmismatched-dealloc.c. */
+/* { dg-additional-options "-fno-exceptions" } */
#define A(...) __attribute__ ((malloc (__VA_ARGS__)))
/* Adapted from linux 5.3.11: drivers/net/wireless/ath/ath10k/usb.c
Reduced reproducer for CVE-2019-19078 (leak of struct urb). */
+/* { dg-additional-options "-fno-exceptions" } */
+
typedef unsigned char u8;
typedef unsigned short u16;
--- /dev/null
+/* { dg-additional-options "-fexceptions" } */
+
+extern void free (void *);
+
+/* Not marked "nothrow", so assume it could throw. */
+char *xstrdup (const char *)
+ __attribute__((malloc (free), returns_nonnull));
+
+void test_1 (const char *s, const char *t)
+{
+ char *p = xstrdup (s); /* { dg-message "allocated here" } */
+ char *q = xstrdup (t); /* { dg-warning "leak of 'p'" } */
+ /* { dg-message "if .* throws an exception\.\.\." "" { target *-*-* } .-1 } */
+
+ free (q);
+ free (p);
+}
-/* { dg-additional-options "-fanalyzer-call-summaries -Wno-analyzer-too-complex -Wno-analyzer-symbol-too-complex" } */
+/* { dg-additional-options "-fanalyzer-call-summaries -Wno-analyzer-too-complex -Wno-analyzer-symbol-too-complex -fno-exceptions" } */
/* { dg-skip-if "c++98 has no noreturn attribute" { c++98_only } } */
#ifdef __cplusplus
-/* { dg-additional-options "-fanalyzer-call-summaries -Wno-analyzer-symbol-too-complex" } */
+/* { dg-additional-options "-fanalyzer-call-summaries -Wno-analyzer-symbol-too-complex -fno-exceptions" } */
typedef __SIZE_TYPE__ size_t;
enum { _ISspace = ((5) < 8 ? ((1 << (5)) << 8) : ((1 << (5)) >> 8)) };
/* { dg-skip-if "" { powerpc*-*-aix* } } */
/* { dg-skip-if "requires hosted libstdc++ for stdlib size_t" { ! hostedlib } } */
+/* { dg-additional-options "-fno-exceptions" } */
#include <stdlib.h>
#include "analyzer-decls.h"
/* Reduced from coreutils's sum.c: bsd_sum_stream */
+/* { dg-additional-options "-fno-exceptions" } */
+
typedef __SIZE_TYPE__ size_t;
typedef unsigned char __uint8_t;
typedef unsigned long int __uintmax_t;
+/* { dg-additional-options "-fno-exceptions" } */
+
#include <string.h>
#include "analyzer-decls.h"
/* { dg-skip-if "requires hosted libstdc++ for stdlib malloc" { ! hostedlib } } */
+/* { dg-additional-options "-fno-exceptions" } */
/* A toy re-implementation of CPython's object model. */
/* Reduced from git-2.39.0's pack-revindex.c */
+/* { dg-additional-options "-fno-exceptions" } */
typedef unsigned int __uint32_t;
typedef unsigned long int __uintmax_t;
/* Reduced from haproxy-2.7.1: src/tcpcheck.c. */
/* { dg-additional-options "-Wno-analyzer-too-complex" } */
+/* { dg-additional-options "-fno-exceptions" } */
typedef __SIZE_TYPE__ size_t;
/* { dg-skip-if "requires hosted libstdc++ for stdlib malloc" { ! hostedlib } } */
+/* { dg-additional-options "-fno-exceptions" } */
+
#include <stdlib.h>
int foo ();
+/* { dg-additional-options "-fno-exceptions" } */
+
int open(const char *, int mode);
void close(int fd);
#define O_RDONLY 0
int fd = creat (path, mode);
close(fd);
close(fd); /* { dg-warning "double 'close' of file descriptor 'fd' \\\[CWE-1341\\\]" "warning" } */
-}
\ No newline at end of file
+}
+/* { dg-additional-options "-fno-exceptions" } */
+
int open(const char *, int mode);
void close(int fd);
int write (int fd, void *buf, int nbytes);
- /* { dg-additional-options "-fanalyzer-verbose-state-changes" } */
+/* { dg-additional-options "-fno-exceptions" } */
+/* { dg-additional-options "-fanalyzer-verbose-state-changes" } */
int open(const char *, int mode);
void close(int fd);
close(fd); /* { dg-message "meaning: \\{verb: 'release', noun: 'resource'\\}" } */
close(fd); /* { dg-warning "double 'close' of file descriptor 'fd' \\\[CWE-1341\\\]" } */
}
-}
\ No newline at end of file
+}
+/* { dg-additional-options "-fno-exceptions" } */
+
typedef struct FILE FILE;
FILE* fopen (const char*, const char*);
+/* { dg-additional-options "-fno-exceptions" } */
+
typedef struct _IO_FILE FILE;
extern struct _IO_FILE *stderr;
+/* { dg-additional-options "-fno-exceptions" } */
/* { dg-additional-options "-fanalyzer-verbose-state-changes" } */
typedef struct FILE FILE;
+/* { dg-additional-options "-fno-exceptions" } */
+
extern void marker_A(void);
extern void marker_B(void);
extern void marker_C(void);
/* { dg-skip-if "requires hosted libstdc++ for stdlib malloc" { ! hostedlib } } */
+/* { dg-additional-options "-fno-exceptions" } */
#include <stdlib.h>
/* { dg-skip-if "requires hosted libstdc++ for stdlib malloc" { ! hostedlib } } */
+/* { dg-additional-options "-fno-exceptions" } */
#include <stdlib.h>
/* { dg-skip-if "requires hosted libstdc++ for stdlib malloc" { ! hostedlib } } */
+/* { dg-additional-options "-fno-exceptions" } */
#include <stdlib.h>
#include "../../gcc.dg/analyzer/analyzer-decls.h"
/* { dg-skip-if "requires hosted libstdc++ for stdlib malloc" { ! hostedlib } } */
+/* { dg-additional-options "-fno-exceptions" } */
#include <stdlib.h>
/* { dg-skip-if "requires hosted libstdc++ for stdlib malloc" { ! hostedlib } } */
+/* { dg-additional-options "-fno-exceptions" } */
#include <stdio.h>
#include <stdlib.h>
/* { dg-skip-if "requires hosted libstdc++ for stdlib malloc" { ! hostedlib } } */
+/* { dg-additional-options "-fno-exceptions" } */
#include <stdlib.h>
+/* { dg-additional-options "-fno-exceptions" } */
/* { dg-additional-options "-fanalyzer-transitivity" } */
/* { dg-skip-if "requires hosted libstdc++ for stdlib malloc" { ! hostedlib } } */
+/* { dg-additional-options "-fno-exceptions" } */
/* { dg-additional-options "-fno-analyzer-call-summaries -fanalyzer-transitivity" } */
/* { dg-skip-if "requires hosted libstdc++ for stdlib malloc" { ! hostedlib } } */
/* { dg-skip-if "requires hosted libstdc++ for stdlib malloc" { ! hostedlib } } */
+/* { dg-additional-options "-fno-exceptions" } */
#include <stdlib.h>
#include "analyzer-decls.h"
/* { dg-skip-if "requires hosted libstdc++ for stdlib malloc" { ! hostedlib } } */
+/* { dg-additional-options "-fno-exceptions" } */
#include <stdlib.h>
#include "analyzer-decls.h"
/* { dg-skip-if "requires hosted libstdc++ for stdlib malloc" { ! hostedlib } } */
+/* { dg-additional-options "-fno-exceptions" } */
#include <stdlib.h>
#include "analyzer-decls.h"
+/* { dg-additional-options "-fno-exceptions" } */
+
typedef __SIZE_TYPE__ size_t;
void free(void *);
/* { dg-do "compile" } */
+/* { dg-additional-options "-fno-exceptions" } */
/* Minimal replacement of system headers. */
#define NULL ((void *) 0)
Adapted from intl/localealias.c, with all #includes removed. */
/* { dg-do "compile" } */
+/* { dg-additional-options "-fno-exceptions" } */
/* Handle aliases for locale names.
Copyright (C) 1995-1999, 2000-2001, 2003 Free Software Foundation, Inc.
Adapted from intl/localealias.c, with all #includes removed. */
/* { dg-do "compile" } */
+/* { dg-additional-options "-fno-exceptions" } */
/* Handle aliases for locale names.
Copyright (C) 1995-1999, 2000-2001, 2003 Free Software Foundation, Inc.
/* { dg-additional-options "-O2 -fanalyzer-transitivity" } */
+/* { dg-additional-options "-fno-exceptions" } */
int *wf;
-void unknown_fn_1 (void *);
+void unknown_fn_1 (void *) __attribute__((nothrow));
void test_1 (int co, int y)
{
void **g;
-extern void unknown_fn (void);
+extern void unknown_fn (void) __attribute__((nothrow));
/* Without a call to unknown_fn. */
+/* { dg-additional-options "-fno-exceptions" } */
+
typedef struct FILE FILE;
FILE* fopen (const char*, const char*);
/* { dg-skip-if "requires hosted libstdc++ for stdlib rand" { ! hostedlib } } */
+/* { dg-additional-options "-fno-exceptions" } */
/* Reduced from
https://github.com/libguestfs/libguestfs/blob/e0a11061035d47b118c95706240bcc17fd576edc/tests/mount-local/test-parallel-mount-local.c#L299-L335
/* { dg-skip-if "requires hosted libstdc++ for stdlib malloc" { ! hostedlib } } */
+/* { dg-additional-options "-fno-exceptions" } */
#include <stdlib.h>
/* { dg-skip-if "" { powerpc*-*-aix* } } */
/* Verify that we can disable -Wanalyzer-too-complex via pragmas. */
/* { dg-additional-options "-Wanalyzer-too-complex -Werror=analyzer-too-complex -fno-analyzer-state-merge -g" } */
+/* { dg-additional-options "-fno-exceptions" } */
/* { dg-skip-if "requires hosted libstdc++ for stdlib malloc" { ! hostedlib } } */
#include <stdlib.h>
+/* { dg-additional-options "-fno-exceptions" } */
+
#include <stdio.h>
#define INI_MAX_LINE 200
/* { dg-skip-if "no strndup in libc" { *-*-darwin[789]* *-*-darwin10* hppa*-*-hpux* *-*-mingw* *-*-vxworks* } } */
/* { dg-additional-options "-D_POSIX_C_SOURCE=200809L" } */
/* { dg-skip-if "requires hosted libstdc++ for stdlib free" { ! hostedlib } } */
+/* { dg-additional-options "-fno-exceptions" } */
#include <string.h>
#include <stdlib.h>
region_model_context_decorator::add_note. */
/* { dg-additional-options "-Wno-analyzer-write-to-string-literal" } */
+/* { dg-additional-options "-fno-exceptions" } */
typedef __SIZE_TYPE__ size_t;
+/* { dg-additional-options "-fno-exceptions" } */
+
typedef __SIZE_TYPE__ size_t;
int getrandom (void *__buffer, size_t __length, /* { dg-line getrandom } */
notes) works. */
/* { dg-additional-options "-fanalyzer-show-duplicate-count" } */
+/* { dg-additional-options "-fno-exceptions" } */
#include "../../gcc.dg/analyzer/analyzer-decls.h"
/* { dg-additional-options "-O3" } */
+/* { dg-additional-options "-fno-exceptions" } */
#include "analyzer-decls.h"
--- /dev/null
+#include "../../gcc.dg/analyzer/analyzer-decls.h"
+
+extern void do_something ();
+extern void do_something_nothrow () __attribute__ ((nothrow));;
+
+int test ()
+{
+ try
+ {
+ do_something ();
+ }
+ catch (int i)
+ {
+ int j = i;
+ __analyzer_eval (i == 42); // { dg-warning "UNKNOWN" }
+ __analyzer_eval (i == j); // { dg-warning "TRUE" }
+ __analyzer_dump_path (); // { dg-message "path" }
+ return 1;
+ }
+ __analyzer_dump_path (); // { dg-message "path" }
+ return 0;
+}
+
+int test_nothrow ()
+{
+ try
+ {
+ do_something_nothrow ();
+ }
+ catch (int i)
+ {
+ __analyzer_dump_path (); // { dg-bogus "path" }
+ return 1;
+ }
+ __analyzer_dump_path (); // { dg-message "path" }
+ return 0;
+}
--- /dev/null
+#include "../../gcc.dg/analyzer/analyzer-decls.h"
+
+struct io_error {};
+struct value_error {};
+struct runtime_error {};
+
+extern void do_something ();
+
+int test ()
+{
+ try
+ {
+ do_something ();
+ }
+ catch (const io_error &err)
+ {
+ __analyzer_dump_path (); // { dg-message "path" }
+ return 1;
+ }
+ catch (const value_error &err)
+ {
+ __analyzer_dump_path (); // { dg-message "path" }
+ return 2;
+ }
+ catch (const runtime_error &err)
+ {
+ __analyzer_dump_path (); // { dg-message "path" }
+ return 3;
+ }
+ __analyzer_dump_path (); // { dg-message "path" }
+ return 0;
+}
--- /dev/null
+// Tests of dynamic exception specifications
+// { dg-require-effective-target c++14_down }
+// { dg-prune-output "dynamic exception specifications are deprecated" }
+
+struct io_error {};
+struct file_io_error : public io_error {};
+struct mem_error {};
+
+// Valid intraprocedural:
+
+void test_1 (int flag) throw (io_error)
+{
+ if (flag)
+ throw io_error();
+}
+
+// Invalid intraprocedural:
+
+void test_2 (int flag) throw (io_error) // { dg-warning "throwing exception of unexpected type 'mem_error' from 'test_2'" }
+// { dg-message "exception of unexpected type 'mem_error' thrown from 'test_2'" "" { target *-*-* } .-1 }
+// { dg-message "'test_2' declared here" "" { target *-*-* } .-2 }
+{
+ if (flag)
+ throw mem_error(); // { dg-message "throwing exception of type 'mem_error' here\.\.\." }
+}
+
+// Valid intraprocedural with subclass:
+
+void test_3 (int flag) throw (io_error) // { dg-bogus "throwing exception of unexpected type 'file_io_error' from 'test_3'" "PR analyzer/119697" { xfail *-*-* } }
+{
+ if (flag)
+ throw file_io_error();
+}
+
+// Valid interprocedural:
+
+void test_4_inner (int flag)
+{
+ if (flag)
+ throw io_error ();
+}
+
+void test_4_outer (int flag) throw (io_error)
+{
+ test_4_inner (flag);
+}
+
+// Invalid interprocedural:
+
+void test_5_inner (int flag)
+{
+ if (flag)
+ throw mem_error (); // { dg-message "throwing exception of type 'mem_error' here\.\.\." }
+ // { dg-message "unwinding stack frame" "" { target *-*-* } .-1 }
+}
+
+void test_5_outer (int flag) throw (io_error) // { dg-warning "throwing exception of unexpected type 'mem_error' from 'test_5_outer'" }
+// { dg-message "exception of unexpected type 'mem_error' thrown from 'test_5_outer'" "" { target *-*-* } .-1 }
+// { dg-message "'test_5_outer' declared here" "" { target *-*-* } .-2 }
+{
+ test_5_inner (flag);
+}
--- /dev/null
+#include "../../gcc.dg/analyzer/analyzer-decls.h"
+
+int test ()
+{
+ void *ptr = __builtin_malloc (1024); // { dg-message "allocated here" }
+
+ throw 42; // { dg-warning "leak of 'ptr'" }
+}
--- /dev/null
+#include "../../gcc.dg/analyzer/analyzer-decls.h"
+
+int test ()
+{
+ void *ptr = __builtin_malloc (1024); // { dg-message "allocated here" }
+
+ try
+ {
+ throw 42;
+ }
+ catch (int i) // { dg-message "\.\.\.catching exception of type 'int' here" }
+ {
+ return -1;
+ } // { dg-warning "leak of 'ptr'" }
+
+ __builtin_free (ptr);
+ return 0;
+}
--- /dev/null
+extern void do_something ();
+
+int test ()
+{
+ void *ptr = __builtin_malloc (1024); // { dg-message "allocated here" }
+
+ do_something (); // { dg-message "if 'void do_something\\(\\)' throws an exception\.\.\." }
+ // { dg-warning "leak of 'ptr'" "" { target *-*-* } .-1 }
+
+ __builtin_free (ptr);
+ return 0;
+}
--- /dev/null
+static void
+do_something (int flag)
+{
+ if (flag)
+ throw 42; // { dg-warning "leak of 'ptr'" }
+}
+
+int test (int flag)
+{
+ void *ptr = __builtin_malloc (1024); // { dg-message "allocated here" }
+
+ do_something (flag);
+
+ __builtin_free (ptr);
+ return 0;
+}
--- /dev/null
+/* Verify that we detect a leak when unwinding multiple frames. */
+
+#include "../../gcc.dg/analyzer/analyzer-decls.h"
+
+struct io_error {};
+struct value_error {};
+struct runtime_error {};
+
+int inner (int flag)
+{
+ if (flag)
+ throw value_error (); // { dg-warning "leak" }
+ // { dg-message "throwing exception of type 'value_error' here\.\.\." "" { target *-*-* } .-1 }
+
+ return 0;
+}
+
+int __analyzer_middle (int flag)
+{
+ void *ptr = __builtin_malloc (1024); // { dg-message "allocated here" }
+
+ int rval = inner (flag);
+
+ __builtin_free (ptr);
+
+ return rval;
+}
+
+int outer ()
+{
+ try
+ {
+ __analyzer_middle (1);
+ }
+ catch (const io_error &err)
+ {
+ __analyzer_dump_path (); // { dg-bogus "path" }
+ return 1;
+ }
+ catch (const value_error &err)
+ {
+ return 2;
+ }
+ catch (const runtime_error &err)
+ {
+ __analyzer_dump_path (); // { dg-bogus "path" }
+ return 3;
+ }
+ __analyzer_dump_path (); // { dg-bogus "path" }
+ return 0;
+}
--- /dev/null
+#include "../../gcc.dg/analyzer/analyzer-decls.h"
+
+extern void do_something (int x);
+
+int inner (int x)
+{
+ do_something (x); // { dg-warning "leak" }
+ // { dg-message "if 'void do_something\\(int\\)' throws an exception\.\.\." "" { target *-*-* } .-1 }
+
+ return 0;
+}
+
+int outer (int x)
+{
+ void *ptr = __builtin_malloc (1024); // { dg-message "allocated here" }
+
+ int rval = inner (x);
+
+ __builtin_free (ptr);
+
+ return rval;
+}
--- /dev/null
+#include "../../gcc.dg/analyzer/analyzer-decls.h"
+
+extern void do_something () __attribute__((nothrow));
+
+/* A wrapper function to stop the try/catch being optimized away. */
+
+void wrapper () __attribute__((noinline));
+void wrapper ()
+{
+ do_something ();
+}
+
+int test ()
+{
+ try
+ {
+ wrapper ();
+ }
+ catch (int i)
+ {
+ __analyzer_dump_path (); // { dg-bogus "path" }
+ return 1;
+ }
+ __analyzer_dump_path (); // { dg-message "path" }
+ return 0;
+}
--- /dev/null
+/* Verify that we follow the correct paths when we know the typeinfo of
+ an exception. */
+
+#include "../../gcc.dg/analyzer/analyzer-decls.h"
+
+struct io_error {};
+struct value_error {};
+struct runtime_error {};
+
+int test ()
+{
+ try
+ {
+ throw value_error (); // { dg-message "\\(1\\) throwing exception of type 'value_error' here..." }
+ __analyzer_dump_path (); // { dg-bogus "path" }
+ }
+ catch (const io_error &err)
+ {
+ __analyzer_dump_path (); // { dg-bogus "path" }
+ return 1;
+ }
+ catch (const value_error &err) // { dg-message "\\(2\\) \.\.\.catching exception of type 'value_error' here" }
+ {
+ __analyzer_dump_path (); // { dg-message "path" }
+ return 2;
+ }
+ catch (const runtime_error &err)
+ {
+ __analyzer_dump_path (); // { dg-bogus "path" }
+ return 3;
+ }
+ __analyzer_dump_path (); // { dg-bogus "path" }
+ return 0;
+}
--- /dev/null
+#include "../../gcc.dg/analyzer/analyzer-decls.h"
+
+int test ()
+{
+ try
+ {
+ throw 42;
+ }
+ catch (...)
+ {
+ __analyzer_dump_path ();
+ return -1;
+ }
+ __analyzer_dump_path ();
+ return 0;
+}
--- /dev/null
+#include "../../gcc.dg/analyzer/analyzer-decls.h"
+
+int test ()
+{
+ try
+ {
+ throw 42; // { dg-message "throwing exception of type 'int' here\.\.\." }
+ }
+ catch (int i) // { dg-message "\.\.\.catching exception of type 'int' here" }
+ {
+ __analyzer_dump_path (); // { dg-message "path" }
+ __analyzer_eval (i == 42); // { dg-warning "TRUE" }
+ return -2;
+ }
+ catch (...)
+ {
+ __analyzer_dump_path (); // { dg-bogus "path" }
+ return -1;
+ }
+ __analyzer_dump_path (); // { dg-bogus "path" }
+ return 0;
+}
--- /dev/null
+/* Verify that we follow the correct paths when we know the typeinfo of
+ an exception: interprocedural case where unwind multiple frame,
+ failing to match the type. */
+
+#include "../../gcc.dg/analyzer/analyzer-decls.h"
+
+struct io_error {};
+struct value_error {};
+struct runtime_error {};
+
+int inner (int flag)
+{
+ if (flag)
+ throw value_error (); // { dg-message "throwing exception of type 'value_error' here..." }
+ // { dg-message "unwinding 2 stack frames" "" { target *-*-* } .-1 }
+
+ return 0;
+}
+
+int middle (int flag)
+{
+ try
+ {
+ return inner (flag);
+ }
+ catch (const io_error &err) // this shouldn't be matched
+ {
+ return -1;
+ }
+}
+
+int outer ()
+{
+ try
+ {
+ middle (1);
+ }
+ catch (const io_error &err)
+ {
+ __analyzer_dump_path (); // { dg-bogus "path" }
+ return 1;
+ }
+ catch (const value_error &err) // { dg-message "\.\.\.catching exception of type 'value_error' here" }
+ {
+ __analyzer_dump_path (); // { dg-message "path" }
+ return 2;
+ }
+ catch (const runtime_error &err)
+ {
+ __analyzer_dump_path (); // { dg-bogus "path" }
+ return 3;
+ }
+ __analyzer_dump_path (); // { dg-bogus "path" }
+ return 0;
+}
--- /dev/null
+/* Verify that we follow the correct paths when we know the typeinfo of
+ an exception: interprocedural case where we unwind one frame. */
+
+#include "../../gcc.dg/analyzer/analyzer-decls.h"
+
+struct io_error {};
+struct value_error {};
+struct runtime_error {};
+
+int inner (int flag)
+{
+ if (flag)
+ throw value_error (); // { dg-message "throwing exception of type 'value_error' here..." }
+
+ return 0;
+}
+
+int middle (int flag)
+{
+ return inner (flag);
+}
+
+int outer ()
+{
+ try
+ {
+ middle (1);
+ }
+ catch (const io_error &err)
+ {
+ __analyzer_dump_path (); // { dg-bogus "path" }
+ return 1;
+ }
+ catch (const value_error &err) // { dg-message "\.\.\.catching exception of type 'value_error' here" }
+ {
+ __analyzer_dump_path (); // { dg-message "path" }
+ return 2;
+ }
+ catch (const runtime_error &err)
+ {
+ __analyzer_dump_path (); // { dg-bogus "path" }
+ return 3;
+ }
+ __analyzer_dump_path (); // { dg-bogus "path" }
+ return 0;
+}
+
+// TODO: test coverage for unwinding stack frame events
--- /dev/null
+/* Verify that we follow the correct paths when we know the typeinfo of
+ an exception: interprocedural case where we unwind two frames. */
+
+#include "../../gcc.dg/analyzer/analyzer-decls.h"
+
+struct io_error {};
+struct value_error {};
+struct runtime_error {};
+
+int inner (int flag)
+{
+ if (flag)
+ throw value_error (); // { dg-message "throwing exception of type 'value_error' here..." }
+
+ return 0;
+}
+
+int outer ()
+{
+ try
+ {
+ inner (1);
+ }
+ catch (const io_error &err)
+ {
+ __analyzer_dump_path (); // { dg-bogus "path" }
+ return 1;
+ }
+ catch (const value_error &err) // { dg-message "\.\.\.catching exception of type 'value_error' here" }
+ {
+ __analyzer_dump_path (); // { dg-message "path" }
+ return 2;
+ }
+ catch (const runtime_error &err)
+ {
+ __analyzer_dump_path (); // { dg-bogus "path" }
+ return 3;
+ }
+ __analyzer_dump_path (); // { dg-bogus "path" }
+ return 0;
+}
+
+// TODO: test coverage for unwinding stack frame events
--- /dev/null
+#include "../../gcc.dg/analyzer/analyzer-decls.h"
+
+struct foo
+{
+ foo (int x) : m_x (x) {}
+ ~foo () __attribute__((nothrow));
+
+ int m_x;
+};
+
+int test (bool flag)
+{
+ foo outside (1);
+ try
+ {
+ foo inside_try (2);
+ if (flag)
+ throw foo (3); // { dg-message "throwing exception of type 'foo' here\.\.\." }
+ }
+ catch (foo &f) // { dg-message "\.\.\.catching exception of type 'foo' here" }
+ {
+ __analyzer_dump_path (); // { dg-message "path" }
+ __analyzer_eval (f.m_x == 3); // { dg-warning "TRUE" }
+ return f.m_x;
+ }
+ return 0;
+}
--- /dev/null
+#include "../../gcc.dg/analyzer/analyzer-decls.h"
+
+int test ()
+{
+ try
+ {
+ throw 42;
+ }
+ catch (...)
+ {
+ throw;
+ }
+}
--- /dev/null
+#include "../../gcc.dg/analyzer/analyzer-decls.h"
+
+int test ()
+{
+ try
+ {
+ try
+ {
+ throw 42; // { dg-message "\\(1\\) throwing exception of type 'int' here\.\.\." }
+ }
+ catch (...) // { dg-message "\\(2\\) \.\.\.catching exception of type 'int' here" }
+ {
+ throw; // { dg-message "\\(3\\) rethrowing exception of type 'int' here\.\.\." }
+ }
+ }
+ catch (int i) // { dg-message "\\(4\\) \.\.\.catching exception of type 'int' here" }
+ {
+ __analyzer_dump_path (); // { dg-message "path" }
+ __analyzer_eval (i == 42); // { dg-warning "TRUE" }
+ return -1;
+ }
+
+ __analyzer_dump_path (); // { dg-bogus "path" }
+ return 0;
+}
--- /dev/null
+#include "../../gcc.dg/analyzer/analyzer-decls.h"
+
+void test (void)
+{
+ try
+ {
+ try
+ {
+ throw 42;
+ __analyzer_dump_path (); // { dg-bogus "path" }
+ }
+ catch (...)
+ {
+ try
+ {
+ throw 1066; // throw an inner exception
+ __analyzer_dump_path (); // { dg-bogus "path" }
+ }
+ catch (int i)
+ {
+ __analyzer_eval (i == 1066); // { dg-warning "TRUE" }
+ }
+ throw; // rethrow the outer exception
+ __analyzer_dump_path (); // { dg-bogus "path" }
+ }
+ __analyzer_dump_path (); // { dg-bogus "path" }
+ }
+ catch (int j)
+ {
+ __analyzer_eval (j == 42); // { dg-warning "TRUE" }
+ __analyzer_dump_path (); // { dg-message "path" }
+ throw;
+ }
+ __analyzer_dump_path (); // { dg-bogus "path" }
+}
--- /dev/null
+#include "../../gcc.dg/analyzer/analyzer-decls.h"
+
+struct io_error {};
+struct value_error {};
+struct runtime_error {};
+
+void test (void)
+{
+ try
+ {
+ try
+ {
+ throw value_error (); // { dg-message "\\(1\\) throwing exception of type 'value_error' here..." }
+ __analyzer_dump_path (); // { dg-bogus "path" }
+ }
+ catch (...) // { dg-message "\\(2\\) \.\.\.catching exception of type 'value_error' here" }
+ {
+ try
+ {
+ throw io_error (); // { dg-message "\\(3\\) throwing exception of type 'io_error' here..." }
+ __analyzer_dump_path (); // { dg-bogus "path" }
+ }
+ catch (const io_error &err) // { dg-message "\\(4\\) \.\.\.catching exception of type 'io_error' here" }
+ {
+ /* discard it */
+ }
+
+ // rethrow the outer exception
+ throw; // { dg-message "\\(5\\) rethrowing exception of type 'value_error' here..." }
+ __analyzer_dump_path (); // { dg-bogus "path" }
+ }
+ __analyzer_dump_path (); // { dg-bogus "path" }
+ }
+ catch (const value_error &err) // { dg-message "\\(6\\) \.\.\.catching exception of type 'value_error' here" }
+ {
+ __analyzer_dump_path (); // { dg-message "path" }
+ throw;
+ }
+ catch (...)
+ {
+ __analyzer_dump_path (); // { dg-bogus "path" }
+ }
+ __analyzer_dump_path (); // { dg-bogus "path" }
+}
--- /dev/null
+#include "../../gcc.dg/analyzer/analyzer-decls.h"
+
+class exception
+{
+};
+
+class io_error : public exception
+{
+};
+
+int test ()
+{
+ try {
+ throw io_error();
+ } catch (exception &exc) {
+ __analyzer_dump_path (); // { dg-message "path" "PR analyzer/119697" { xfail *-*-* } }
+ return -1;
+ }
+ __analyzer_dump_path (); // { dg-bogus "path" }
+ return 0;
+}
--- /dev/null
+#include "../../gcc.dg/analyzer/analyzer-decls.h"
+
+class exception
+{
+};
+
+class io_error : public exception
+{
+};
+
+int __analyzer_inner ()
+{
+ try {
+ throw io_error();
+ } catch (exception &exc) {
+ return -1;
+ }
+ __analyzer_dump_path (); // { dg-bogus "path" }
+ return 0;
+}
+
+int test ()
+{
+ return __analyzer_inner (); // { dg-message "path" "PR analyzer/119697" { xfail *-*-* } }
+}
--- /dev/null
+/* Verify that we can access values in exceptions. */
+
+#include "../../gcc.dg/analyzer/analyzer-decls.h"
+
+int test ()
+{
+ try
+ {
+ throw 42; // { dg-message "\\(1\\) throwing exception of type 'int' here..." }
+ __analyzer_dump_path (); // { dg-bogus "path" }
+ }
+ catch (int i) // { dg-message "\\(2\\) \.\.\.catching exception of type 'int' here" }
+ {
+ __analyzer_dump_path (); // { dg-message "path" }
+ __analyzer_eval (i == 42); // { dg-warning "TRUE" }
+ return 1;
+ }
+ __analyzer_dump_path (); // { dg-bogus "path" }
+ return 0;
+}
--- /dev/null
+#include "../../gcc.dg/analyzer/analyzer-decls.h"
+
+struct foo {};
+
+int inner (bool flag)
+{
+ try
+ {
+ if (flag)
+ throw 42;
+ }
+ catch (foo &f)
+ {
+ return 1;
+ }
+ return 0;
+}
+
+int middle (bool flag)
+{
+ try
+ {
+ int rval = inner (flag);
+ return rval;
+ }
+ catch (int ei)
+ {
+ return ei;
+ }
+}
+
+void outer (void)
+{
+ __analyzer_eval (middle (false) == 0); // { dg-warning "TRUE" }
+ __analyzer_eval (middle (true) == 42); // { dg-warning "TRUE" }
+}
--- /dev/null
+/* { dg-additional-options "-fno-exceptions" } */
+
+#include "../../gcc.dg/analyzer/analyzer-decls.h"
+
+extern void do_something ();
+
+int test ()
+{
+ do_something ();
+ __analyzer_dump_path (); // { dg-message "path" }
+ return 0;
+}
throw()
#endif
{
- return calloc (b, sizeof (int)); // { dg-bogus "leak" "" { xfail c++98_only } }
+ return calloc (b, sizeof (int));
}
j (B *, int)
{
--- /dev/null
+// { dg-require-effective-target c++14_down }
+// { dg-additional-options "-Wno-deprecated-declarations" }
+
+#include <exception>
+
+void test_1 ()
+{
+ std::unexpected ();
+}
// { dg-additional-options "-fanalyzer" }
-// { dg-excess-errors "lots of analyzer output, but no ICE" }
+
namespace std {
template <typename _Result> struct coroutine_traits : _Result {};
template <typename = void> struct coroutine_handle {