From 83ab0a1aec4134d25b3f1844d5fc39b7edbba458 Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Tue, 1 Sep 2020 12:57:18 -0400 Subject: [PATCH 41/49] FIXME: WIP on exception-handling (PR analyzer/97111) Old commit messages: * add WIP except-1.C * exceptions WIP: implement __cxa_allocate_exception * exceptions: WIP on __cxa_throw * exceptions: WIP on __builtin_eh_pointer and __cxa_begin_catch * exceptions WIP on filtering paths at EH_DISPATCH * further WIP on exceptions (handle try/except in same fn) * exceptions WIP: combine dispatch and catch into a single eedge * exceptions WIP: throw_event and catch_event * more WIP on exception test cases * exceptions: handle the 'no catch' case * start moving testcases to correct location and cleaning them up --- except-1.C | 34 ++ except-4.C | 23 ++ except-6.C | 23 ++ gcc/analyzer/analyzer.h | 1 + gcc/analyzer/checker-path.cc | 64 +++ gcc/analyzer/checker-path.h | 65 +++ gcc/analyzer/diagnostic-manager.cc | 5 + gcc/analyzer/engine.cc | 374 +++++++++++++++++- gcc/analyzer/exploded-graph.h | 4 + gcc/analyzer/program-state.cc | 1 + gcc/analyzer/region-model-impl-calls.cc | 41 ++ gcc/analyzer/region-model-manager.cc | 14 + gcc/analyzer/region-model.cc | 211 +++++++++- gcc/analyzer/region-model.h | 70 +++- gcc/analyzer/svalue.cc | 93 +++++ gcc/testsuite/g++.dg/analyzer/except-2.C | 33 ++ gcc/testsuite/g++.dg/analyzer/except-3.C | 25 ++ gcc/testsuite/g++.dg/analyzer/except-5.C | 13 + .../gcc.dg/analyzer/analyzer-decls.h | 14 +- 19 files changed, 1094 insertions(+), 14 deletions(-) create mode 100644 except-1.C create mode 100644 except-4.C create mode 100644 except-6.C create mode 100644 gcc/testsuite/g++.dg/analyzer/except-2.C create mode 100644 gcc/testsuite/g++.dg/analyzer/except-3.C create mode 100644 gcc/testsuite/g++.dg/analyzer/except-5.C diff --git a/except-1.C b/except-1.C new file mode 100644 index 00000000000..905fa491979 --- /dev/null +++ b/except-1.C @@ -0,0 +1,34 @@ +#include "gcc/testsuite/gcc.dg/analyzer/analyzer-decls.h" + +struct range_err +{ + range_err (int val) : m_val (val) {} + int m_val; + char m_dummy[1024]; +}; + +void foo (int n); + +#if 0 +void test_1 (int n) +{ + if (n < 0 || n > 10) throw range_err (n); + foo (n); +} +#endif + +static void __attribute__((noinline)) +called_by_test_2 (int n) +{ + if (n < 0 || n > 10) throw range_err (n); + foo (n); +} + +void test_2 (int n) +{ + try { + called_by_test_2 (42); + } catch (range_err &e) { + __analyzer_eval (e.m_val == 42); + } +} diff --git a/except-4.C b/except-4.C new file mode 100644 index 00000000000..22c368d615b --- /dev/null +++ b/except-4.C @@ -0,0 +1,23 @@ +#include "gcc/testsuite/gcc.dg/analyzer/analyzer-decls.h" +#include + +struct range_err +{ + range_err (int val) : m_val (val) {} + int m_val; + char m_dummy[1024]; +}; + +struct other_err +{ +}; + +void test_1 (int n) +{ + try { + void *ptr = malloc (1024); // FIXME: should warn about this! + throw range_err (n); + } catch (other_err &e) { + __analyzer_dump_path (); // { dg-bogus "" } + } +} diff --git a/except-6.C b/except-6.C new file mode 100644 index 00000000000..e1921cc7cd4 --- /dev/null +++ b/except-6.C @@ -0,0 +1,23 @@ +#include "gcc/testsuite/gcc.dg/analyzer/analyzer-decls.h" +#include + +struct base_err +{ +}; + +struct range_err : public base_err +{ + range_err (int val) : m_val (val) {} + int m_val; + char m_dummy[1024]; +}; + +void test_1 (int n) +{ + try { + void *ptr = malloc (1024); // FIXME: should warn about this! + throw range_err (n); + } catch (base_err &e) { + __analyzer_dump_path (); + } +} diff --git a/gcc/analyzer/analyzer.h b/gcc/analyzer/analyzer.h index 5d01d022f3c..0388979a7b6 100644 --- a/gcc/analyzer/analyzer.h +++ b/gcc/analyzer/analyzer.h @@ -51,6 +51,7 @@ class svalue; class widening_svalue; class compound_svalue; class conjured_svalue; + class exception_state; typedef hash_set svalue_set; class region; class frame_region; diff --git a/gcc/analyzer/checker-path.cc b/gcc/analyzer/checker-path.cc index 3e4266a6aae..cb9434918e1 100644 --- a/gcc/analyzer/checker-path.cc +++ b/gcc/analyzer/checker-path.cc @@ -99,6 +99,10 @@ event_kind_to_string (enum event_kind ek) return "EK_REWIND_FROM_LONGJMP"; case EK_REWIND_TO_SETJMP: return "EK_REWIND_TO_SETJMP"; + case EK_THROW: + return "EK_THROW"; + case EK_CATCH: + return "EK_CATCH"; case EK_WARNING: return "EK_WARNING"; } @@ -850,6 +854,66 @@ rewind_to_setjmp_event::prepare_for_emission (checker_path *path, &m_original_setjmp_event_id); } +/* class exception_event : public checker_event. */ + +exception_event::exception_event (const exploded_edge *eedge, + enum event_kind kind, + location_t loc, tree fndecl, int depth) +: checker_event (kind, loc, fndecl, depth), + //m_rewind_info (rewind_info), + m_eedge (eedge) +{ + //gcc_assert (m_eedge->m_custom_info == m_rewind_info); +} + +/* Get the fndecl containing the site of the longjmp call. */ + +tree +exception_event::get_throw_fndecl () const +{ + return m_eedge->m_src->get_function ()->decl; +} + +/* Get the fndecl containing the site of the setjmp call. */ + +tree +exception_event::get_catch_fndecl () const +{ + return m_eedge->m_dest->get_function ()->decl; +} + +/* class throw_event : public exception_event. */ + +label_text +throw_event::get_desc (bool can_colorize) const +{ + if (get_throw_fndecl () == get_catch_fndecl ()) + /* Special-case: purely intraprocedural throw/catch. */ + return make_label_text (can_colorize, + "throwing %qE...", + m_thrown_type); + else + return make_label_text (can_colorize, + "throwing %qE from %qE...", + m_thrown_type, + get_throw_fndecl ()); +} + +/* class catch_event : public exception_event. */ + +label_text +catch_event::get_desc (bool can_colorize) const +{ + if (get_throw_fndecl () == get_catch_fndecl ()) + /* Special-case: purely intraprocedural throw/catch. */ + return make_label_text (can_colorize, + "...caught here"); + else + return make_label_text (can_colorize, + "...caught here in %qE", + get_catch_fndecl ()); +} + /* class warning_event : public checker_event. */ /* Implementation of diagnostic_event::get_desc vfunc for diff --git a/gcc/analyzer/checker-path.h b/gcc/analyzer/checker-path.h index 93b9f20f075..6a4c8c3dbbc 100644 --- a/gcc/analyzer/checker-path.h +++ b/gcc/analyzer/checker-path.h @@ -40,6 +40,8 @@ enum event_kind EK_SETJMP, EK_REWIND_FROM_LONGJMP, EK_REWIND_TO_SETJMP, + EK_THROW, + EK_CATCH, EK_WARNING }; @@ -67,6 +69,9 @@ extern const char *event_kind_to_string (enum event_kind ek); rewind_event rewind_from_longjmp_event (EK_REWIND_FROM_LONGJMP) rewind_to_setjmp_event (EK_REWIND_TO_SETJMP) + exception_event + throw_event (EK_THROW) + catch_event (EK_CATCH) warning_event (EK_WARNING). */ /* Abstract subclass of diagnostic_event; the base class for use in @@ -419,6 +424,66 @@ private: diagnostic_event_id_t m_original_setjmp_event_id; }; +/* An abstract event subclass for handling exceptions. + + FIXME: + Base class for two from/to subclasses, showing the two halves of the + rewind. */ + +class exception_event : public checker_event +{ +public: + tree get_throw_fndecl () const; + tree get_catch_fndecl () const; + const exploded_edge *get_eedge () const { return m_eedge; } + + protected: + exception_event (const exploded_edge *eedge, + enum event_kind kind, + location_t loc, tree fndecl, int depth); + /*const rewind_info_t *m_rewind_info;*/ + + private: + const exploded_edge *m_eedge; +}; + +// FIXME: +/* A concrete event subclass for rewinding from a longjmp to a setjmp, + showing the longjmp (or siglongjmp). */ + +class throw_event : public exception_event +{ +public: + throw_event (const exploded_edge *eedge, + location_t loc, tree fndecl, int depth, + tree thrown_type) + : exception_event (eedge, EK_THROW, loc, fndecl, depth), + m_thrown_type (thrown_type) + { + } + + label_text get_desc (bool can_colorize) const FINAL OVERRIDE; + +private: + tree m_thrown_type; +}; + +// FIXME: +/* A concrete event subclass for rewinding from a longjmp to a setjmp, + showing the setjmp (or sigsetjmp). */ + +class catch_event : public exception_event +{ +public: + catch_event (const exploded_edge *eedge, + location_t loc, tree fndecl, int depth) + : exception_event (eedge, EK_CATCH, loc, fndecl, depth) + { + } + + label_text get_desc (bool can_colorize) const FINAL OVERRIDE; +}; + /* 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 diff --git a/gcc/analyzer/diagnostic-manager.cc b/gcc/analyzer/diagnostic-manager.cc index a3cbb97d6dc..3294839ebb9 100644 --- a/gcc/analyzer/diagnostic-manager.cc +++ b/gcc/analyzer/diagnostic-manager.cc @@ -1524,6 +1524,11 @@ diagnostic_manager::prune_for_sm_diagnostic (checker_path *path, case EK_REWIND_TO_SETJMP: break; + case EK_THROW: + case EK_CATCH: + /* Always show exception-handling events for now. */ + break; + case EK_WARNING: /* Always show the final "warning" event in the path. */ break; diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc index cd22640d685..de70d683cbf 100644 --- a/gcc/analyzer/engine.cc +++ b/gcc/analyzer/engine.cc @@ -64,6 +64,8 @@ along with GCC; see the file COPYING3. If not see #include "analyzer/bar-chart.h" #include #include "plugin.h" +#include "except.h" +#include "tree-cfg.h" /* For an overview, see gcc/doc/analyzer.texi. */ @@ -1151,6 +1153,11 @@ exploded_node::on_stmt (exploded_graph &eg, on_longjmp (eg, call, state, &ctxt); return on_stmt_flags::terminate_path (); } + else if (is_special_named_call_p (call, "__cxa_throw", 3)) + { + on_cxa_throw (eg, call, state, &ctxt); + return on_stmt_flags::terminate_path (); + } else unknown_side_effects = state->m_region_model->on_call_pre (call, &ctxt); @@ -1394,6 +1401,369 @@ exploded_node::on_longjmp (exploded_graph &eg, } } +/* Get the outgoing CFG superedge from SNODE flagged with EDGE_EH, if any. */ + +static const cfg_superedge * +get_eh_sedge (const supernode *snode) +{ + unsigned edge_idx; + superedge *sedge; + FOR_EACH_VEC_ELT (snode->m_succs, edge_idx, sedge) + { + if (cfg_superedge *cfg_sedge = sedge->dyn_cast_cfg_superedge ()) + if (cfg_sedge->get_flags () & EDGE_EH) + return cfg_sedge; + } + return NULL; +} + +/* Get the outgoing CFG superedge from SNODE flagged with EDGE_FALLTHRU, + if any. */ + +static const cfg_superedge * +get_fallthrough_sedge (const supernode *snode) +{ + unsigned edge_idx; + superedge *sedge; + FOR_EACH_VEC_ELT (snode->m_succs, edge_idx, sedge) + { + if (cfg_superedge *cfg_sedge = sedge->dyn_cast_cfg_superedge ()) + if (cfg_sedge->get_flags () & EDGE_FALLTHRU) + return cfg_sedge; + } + return NULL; +} + +/* FIXME. + Update CS in place to give the call_string of the result. */ +/* FIXME: + This seems to take us to an EH_DISPATCH stmt for a region, + which we later need to process. + Alternatively, should we simply locate that stmt, and combine + the handling? */ + +static const supernode * +get_eh_dest_supernode (const supernode *curr_snode, call_string *cs) +{ + if (const cfg_superedge *cfg_sedge = get_eh_sedge (curr_snode)) + return cfg_sedge->m_dest; + + // FIXME: for now, just pop one frame off the stack + while (cs->length () > 0) + { + const return_superedge *return_sedge = cs->pop (); + const supernode *return_dest = return_sedge->m_dest; + /* Does return_dest have an EH edge?. + If so, go to that snode. + FIXME: Otherwise, what should we do? */ + if (const cfg_superedge *cfg_sedge = get_eh_sedge (return_dest)) + return cfg_sedge->m_dest; + } + return NULL; +} + +/* FIXME. */ + +static geh_dispatch * +get_geh_dispatch_at_snode (const supernode *snode) +{ + gcc_assert (snode->m_stmts.length () == 2); + gcc_assert (snode->m_stmts[0]->code == GIMPLE_LABEL); + gcc_assert (snode->m_stmts[1]->code == GIMPLE_EH_DISPATCH); + return as_a (snode->get_last_stmt ()); +} + +// FIXME: copied from region-model.cc: +/* FIXME. */ + +static const supernode * +get_supernode_for_eh_catch (eh_catch_d *ehc, + const function *fun, + const supergraph &sg) +{ + tree label = ehc->label; + basic_block bb = label_to_block (const_cast (fun), label); + gcc_assert (bb); + return sg.get_node_for_block (bb); +} + +/* Determine which successor snode should be followed from DISPATCH_SNODE + based on EXC_STATE. + + We expect DISPATCH_SNODE to have just a label and then an EH_DISPATCH + stmt. The EH_DISPATCH is a placeholder for a runtime type comparison + that should be made in order to select the action to perform among + different CATCH and EH_FILTER regions.. */ + +static const supernode * +get_dest_snode_for_eh_dispatch (const supernode *dispatch_snode, + const exception_state *exc_state, + const supergraph &sg) +{ + gcc_assert (dispatch_snode); + gcc_assert (exc_state); + + geh_dispatch *dispatch_stmt = get_geh_dispatch_at_snode (dispatch_snode); + gcc_assert (dispatch_stmt); + + const function *fun = dispatch_snode->get_function (); + + int region_num = gimple_eh_dispatch_region (dispatch_stmt); + eh_region region = (*fun->eh->region_array)[region_num]; + gcc_assert (region->index == region_num); + + switch (region->type) + { + default: + gcc_unreachable (); + case ERT_CLEANUP: + gcc_unreachable (); // TODO + break; + case ERT_TRY: + { + gcc_assert (exc_state->m_tinfo_ptr_sval); + tree rtti_type = exc_state->get_rtti_type (); + gcc_assert (rtti_type); // TODO: what if it fails? + + /* Look for a matching catch handler. */ + for (eh_catch_d *iter_catch = region->u.eh_try.first_catch; + iter_catch; iter_catch = iter_catch->next_catch) + { + for (tree iter = iter_catch->type_list; iter; + iter = TREE_CHAIN (iter)) + { + tree type = TREE_VALUE (iter); + if (type == rtti_type) + return get_supernode_for_eh_catch (iter_catch, fun, sg); + } + } + /* Not found; use the FALLTHRU edge. */ + const cfg_superedge *fallthru_sedge + = get_fallthrough_sedge (dispatch_snode); + gcc_assert (fallthru_sedge); + return fallthru_sedge->m_dest; + } + break; + case ERT_ALLOWED_EXCEPTIONS: + gcc_unreachable (); // TODO + break; + case ERT_MUST_NOT_THROW: + gcc_unreachable (); // TODO + break; + } + + //TODO + return NULL; +} + + +/* FIXME. */ + +class cxa_throw_info_t : public exploded_edge::custom_info_t +{ +public: + cxa_throw_info_t (const gcall *cxa_throw_call, + const exception_state *exc_state, + int dest_stack_depth) + : m_cxa_throw_call (cxa_throw_call), + m_exc_state (exc_state), + m_dest_stack_depth (dest_stack_depth) + {} + + void print (pretty_printer *pp) FINAL OVERRIDE + { + pp_string (pp, "throw"); + } + + void update_model (region_model *model, + const exploded_edge &eedge) FINAL OVERRIDE + { + model->m_current_exception = m_exc_state; + model->unwind_stack (m_dest_stack_depth, NULL); + } + + void add_events_to_path (checker_path *emission_path, + const exploded_edge &eedge) 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 (); + 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 (); + + tree thrown_type = m_exc_state->get_rtti_type (); + gcc_assert (thrown_type); + + emission_path->add_event + (new throw_event + (&eedge, m_cxa_throw_call->location, + src_point.get_fndecl (), + src_stack_depth, thrown_type)); + emission_path->add_event + (new catch_event + (&eedge, dst_node->get_supernode ()->get_start_location (), + dst_point.get_fndecl (), + dst_stack_depth)); // FIXME: show type that is caught? + } + +private: + const gcall *m_cxa_throw_call; + const exception_state *m_exc_state; + int m_dest_stack_depth; +}; + + +/* Handle CXA_THROW_CALL, a call to __cxa_throw. + + See e.g. https://itanium-cxx-abi.github.io/cxx-abi/abi-eh.html + "2.4.3 Throwing the Exception Object". + + We expect this prototype: + void + __cxa_throw (void *thrown_exception, + std::type_info *tinfo, + void (*dest) (void *) ); + + First, follow ah EH edge to locate a "dispatch_snode" containing + an EH_DISPATCH stmt, unwinding the stack if necessary. + + Use the current exception state to determine which CFG edge to follow + after the EH_DISPATCH. + + Create an enode and eedge expressing going directly from the __cxa_throw + to that successor block (e.g. the relevant catch handler), in the + appropriate frame. + This makes some assumptions about where EH_DISPATCH stmts are located, + and bypasses the EH_DISPATCH as seen from the exploded_graph, but + hopefully leads to easier-to-read diagnostic paths. */ + +void +exploded_node::on_cxa_throw (exploded_graph &eg, + const gcall *cxa_throw_call, + program_state *new_state, + region_model_context *ctxt) const +{ + tree exception_ptr = gimple_call_arg (cxa_throw_call, 0); + tree tinfo_ptr = gimple_call_arg (cxa_throw_call, 1); + tree dest_ptr = gimple_call_arg (cxa_throw_call, 2); + + region_model *new_region_model = new_state->m_region_model; + + const svalue *exception_ptr_sval + = new_region_model->get_rvalue (exception_ptr, ctxt); + const svalue *tinfo_ptr_sval + = new_region_model->get_rvalue (tinfo_ptr, ctxt); + const svalue *dest_ptr_sval + = new_region_model->get_rvalue (dest_ptr, ctxt); + + const exception_state *exc_state + = new_region_model->get_manager ()->get_or_create_exception_state + (exception_ptr_sval, + tinfo_ptr_sval, + dest_ptr_sval); + + /* Where to unwind to? */ +#if 0 + // FIXME: + debug_eh_tree (get_function ()); +#endif + + /* First, follow ah EH edge to locate a "dispatch_snode" containing + an EH_DISPATCH stmt, unwinding the stack if necessary. */ + call_string cs (get_point ().get_call_string ()); + const supernode *dispatch_snode + = get_eh_dest_supernode (get_supernode (), &cs); + if (dispatch_snode == NULL) + { + /* If we have a throw with nothing to catch it, then try unwinding + the stack to see if we have any leaks. */ + new_region_model->unwind_stack (0, ctxt); + /* Detect leaks in the new state relative to the old state. */ + program_state::detect_leaks (get_state (), *new_state, NULL, + eg.get_ext_state (), ctxt); + return; + } + + /* Use exc_state to determine which edge to follow after the EH_DISPATCH. */ + const supernode *dest_snode + = get_dest_snode_for_eh_dispatch (dispatch_snode, exc_state, + eg.get_supergraph ()); + gcc_assert (dest_snode); + + /* Create an enode and eedge expressing going directly from the __cxa_throw + to the relevant catch handler. This bypasses the EH_DISPATCH as seen + from the exploded_graph, but hopefully leads to easier-to-read + diagnostic paths. */ + + const program_point &next_point + = program_point::before_supernode (dest_snode, NULL, cs); + + /* Update the state for use by the destination node. */ + + /* Stash the current number of diagnostics so that we can update + any that this adds to show where the throw is rewinding to. */ + + diagnostic_manager *dm = &eg.get_diagnostic_manager (); + unsigned prev_num_diagnostics = dm->get_num_diagnostics (); + + new_region_model->m_current_exception = exc_state; + new_region_model->unwind_stack (next_point.get_stack_depth (), ctxt); + + /* TODO: need to stash the exception information somewhere in the + new state, for use by eh_dispatch. */ + + /* Detect leaks in the new state relative to the old state. */ + program_state::detect_leaks (get_state (), *new_state, NULL, + eg.get_ext_state (), ctxt); + + exploded_node *next + = eg.get_or_create_node (next_point, *new_state, this); + /* Create custom exploded_edge for a __cxa_throw. */ + if (next) + { + exploded_edge *eedge + = eg.add_edge (const_cast (this), next, NULL, + new cxa_throw_info_t (cxa_throw_call, exc_state, + next_point.get_stack_depth ())); + + // FIXME: consolidate this with the longjmp case. + /* For any diagnostics that were queued here (such as leaks) we want + the checker_path to show the rewinding events after the "final event" + so that the user sees where the longjmp is rewinding to (otherwise the + path is meaningless). + + For example, we want to emit something like: + | NN | { + | NN | longjmp (env, 1); + | | ~~~~~~~~~~~~~~~~ + | | | + | | (10) 'ptr' leaks here; was allocated at (7) + | | (11) rewinding from 'longjmp' in 'inner'... + | + <-------------+ + | + 'outer': event 12 + | + | NN | i = setjmp(env); + | | ^~~~~~ + | | | + | | (12) ...to 'setjmp' in 'outer' (saved at (2)) + + where the "final" event above is event (10), but we want to append + events (11) and (12) afterwards. + + Do this by setting m_trailing_eedge on any diagnostics that were + just saved. */ + unsigned num_diagnostics = dm->get_num_diagnostics (); + for (unsigned i = prev_num_diagnostics; i < num_diagnostics; i++) + { + saved_diagnostic *sd = dm->get_saved_diagnostic (i); + sd->m_trailing_eedge = eedge; + } + } +} + /* Subroutine of exploded_graph::process_node for finding the successors of the supernode for a function exit basic block. @@ -3423,7 +3793,9 @@ exploded_path::feasible_p (logger *logger, feasibility_problem **out, const gimple *last_stmt = src_point.get_supernode ()->get_last_stmt (); rejected_constraint *rc = NULL; - if (!model.maybe_update_for_edge (*sedge, last_stmt, NULL, &rc)) + if (!model.maybe_update_for_edge (*sedge, last_stmt, + &eg->get_supergraph (), + NULL, &rc)) { if (logger) { diff --git a/gcc/analyzer/exploded-graph.h b/gcc/analyzer/exploded-graph.h index b07752756f8..dd54aa4bd2c 100644 --- a/gcc/analyzer/exploded-graph.h +++ b/gcc/analyzer/exploded-graph.h @@ -229,6 +229,10 @@ class exploded_node : public dnode const gcall *call, program_state *new_state, region_model_context *ctxt) const; + void on_cxa_throw (exploded_graph &eg, + const gcall *call, + program_state *new_state, + region_model_context *ctxt) const; void detect_leaks (exploded_graph &eg) const; diff --git a/gcc/analyzer/program-state.cc b/gcc/analyzer/program-state.cc index c01a94b5887..fab1b76af5d 100644 --- a/gcc/analyzer/program-state.cc +++ b/gcc/analyzer/program-state.cc @@ -872,6 +872,7 @@ program_state::on_edge (exploded_graph &eg, last_stmt); if (!m_region_model->maybe_update_for_edge (*succ, last_stmt, + &eg.get_supergraph (), &ctxt, NULL)) { logger * const logger = eg.get_logger (); diff --git a/gcc/analyzer/region-model-impl-calls.cc b/gcc/analyzer/region-model-impl-calls.cc index c71e134109d..e7339a89b7a 100644 --- a/gcc/analyzer/region-model-impl-calls.cc +++ b/gcc/analyzer/region-model-impl-calls.cc @@ -207,6 +207,16 @@ region_model::impl_call_analyzer_eval (const gcall *call, warning_at (call->location, 0, "%s", t.as_string ()); } +/* Handle the on_call_pre part of "__builtin_eh_pointer". */ + +bool +region_model::impl_call_builtin_eh_pointer (const call_details &cd) +{ + gcc_assert (m_current_exception); // maybe UNKNOWN for this case? + cd.maybe_set_lhs (m_current_exception); + return false; +} + /* Handle the on_call_pre part of "__builtin_expect" etc. */ bool @@ -240,6 +250,37 @@ region_model::impl_call_calloc (const call_details &cd) return true; } +/* Handle the on_call_pre part of "__cxa_allocate_exception". */ + +bool +region_model::impl_call_cxa_allocate_exception (const call_details &cd) +{ + const svalue *size_sval = cd.get_arg_svalue (0); + const region *new_reg = create_region_for_heap_alloc (size_sval); + if (cd.get_lhs_type ()) + { + const svalue *ptr_sval + = m_mgr->get_ptr_svalue (cd.get_lhs_type (), new_reg); + cd.maybe_set_lhs (ptr_sval); + } + return false; +} + +/* Handle the on_call_pre part of "__cxa_begin_catch". */ + +bool +region_model::impl_call_cxa_begin_catch (const call_details &cd) +{ + const svalue *sval = cd.get_arg_svalue (0); + if (const svalue *cast_sval = sval->maybe_undo_cast ()) + sval = cast_sval; + if (const exception_state *exc_state = sval->dyn_cast_exception_state ()) + cd.maybe_set_lhs (exc_state->m_exception_ptr_sval); + else + gcc_unreachable (); // TODO: maybe UNKNOWN? + return false; +} + /* Handle the on_call_post part of "free", after sm-handling. If the ptr points to an underlying heap region, delete the region, diff --git a/gcc/analyzer/region-model-manager.cc b/gcc/analyzer/region-model-manager.cc index f7fa33b028c..3c21b2daee4 100644 --- a/gcc/analyzer/region-model-manager.cc +++ b/gcc/analyzer/region-model-manager.cc @@ -974,6 +974,20 @@ get_region_for_unexpected_tree_code (region_model_context *ctxt, return new_reg; } +/* FIXME. */ + +const exception_state * +region_model_manager:: +get_or_create_exception_state (const svalue *exception_ptr_sval, + const svalue *tinfo_ptr_sval, + const svalue *dest_ptr_sval) +{ + // FIXME: for now just new, no consolidation, and leak + return new exception_state (exception_ptr_sval, + tinfo_ptr_sval, + dest_ptr_sval); +} + /* Return a new region describing a heap-allocated block of memory. */ const region * diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc index 997b98b2698..24806819b25 100644 --- a/gcc/analyzer/region-model.cc +++ b/gcc/analyzer/region-model.cc @@ -66,6 +66,8 @@ along with GCC; see the file COPYING3. If not see #include "analyzer/analyzer-selftests.h" #include "stor-layout.h" #include "attribs.h" +#include "except.h" +#include "tree-cfg.h" #if ENABLE_ANALYZER @@ -115,7 +117,8 @@ print_quoted_type (pretty_printer *pp, tree t) /* 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_mgr (mgr), m_store (), m_current_frame (NULL), + m_current_exception (NULL) { m_constraints = new constraint_manager (mgr); } @@ -125,7 +128,8 @@ region_model::region_model (region_model_manager *mgr) region_model::region_model (const region_model &other) : 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_current_frame (other.m_current_frame), + m_current_exception (other.m_current_exception) { } @@ -236,6 +240,18 @@ region_model::dump_to_pp (pretty_printer *pp, bool simple, m_constraints->dump_to_pp (pp, multiline); if (!multiline) pp_string (pp, "}"); + + if (m_current_exception) + { + pp_string (pp, "exception:"); + if (multiline) + pp_newline (pp); + else + pp_string (pp, " {"); + m_current_exception->dump_to_pp (pp, simple, multiline); + if (!multiline) + pp_string (pp, "}"); + } } /* Dump a representation of this model to FILE. */ @@ -718,6 +734,8 @@ region_model::on_call_pre (const gcall *call, region_model_context *ctxt) return impl_call_alloca (cd); case BUILT_IN_CALLOC: return impl_call_calloc (cd); + case BUILT_IN_EH_POINTER: + return impl_call_builtin_eh_pointer (cd); case BUILT_IN_EXPECT: case BUILT_IN_EXPECT_WITH_PROBABILITY: return impl_call_builtin_expect (cd); @@ -782,6 +800,12 @@ region_model::on_call_pre (const gcall *call, region_model_context *ctxt) return impl_call_malloc (cd); else if (is_named_call_p (callee_fndecl, "calloc", call, 2)) return impl_call_calloc (cd); + else if (is_named_call_p (callee_fndecl, + "__cxa_allocate_exception", call, 1)) + return impl_call_cxa_allocate_exception (cd); + else if (is_named_call_p (callee_fndecl, + "__cxa_begin_catch", call, 1)) + return impl_call_cxa_begin_catch (cd); else if (is_named_call_p (callee_fndecl, "alloca", call, 1)) return impl_call_alloca (cd); else if (is_named_call_p (callee_fndecl, "getchar", call, 0)) @@ -1052,13 +1076,7 @@ region_model::on_longjmp (const gcall *longjmp_call, const gcall *setjmp_call, tree fake_retval = gimple_call_arg (longjmp_call, 1); const svalue *fake_retval_sval = get_rvalue (fake_retval, ctxt); - /* Pop any frames until we reach the stack depth of the function where - setjmp was called. */ - gcc_assert (get_stack_depth () >= setjmp_stack_depth); - while (get_stack_depth () > setjmp_stack_depth) - pop_frame (NULL, NULL, ctxt); - - gcc_assert (get_stack_depth () == setjmp_stack_depth); + unwind_stack (setjmp_stack_depth, ctxt); /* Assign to LHS of "setjmp" in new_state. */ if (tree lhs = gimple_call_lhs (setjmp_call)) @@ -1093,6 +1111,20 @@ region_model::on_longjmp (const gcall *longjmp_call, const gcall *setjmp_call, } } +/* Pop any frames until we reach DEST_STACK_DEPTH. + Leak detection is *not* done, and should be done by the caller. */ + +void +region_model::unwind_stack (int dest_stack_depth, region_model_context *ctxt) +{ + gcc_assert (get_stack_depth () >= dest_stack_depth); + + while (get_stack_depth () > dest_stack_depth) + pop_frame (NULL, NULL, ctxt); + + gcc_assert (get_stack_depth () == dest_stack_depth); +} + /* Update this region_model for a phi stmt of the form LHS = PHI <...RHS...>. where RHS is for the appropriate edge. */ @@ -2405,6 +2437,7 @@ region_model::update_for_phis (const supernode *snode, bool region_model::maybe_update_for_edge (const superedge &edge, const gimple *last_stmt, + const supergraph *sg, region_model_context *ctxt, rejected_constraint **out) { @@ -2462,6 +2495,15 @@ region_model::maybe_update_for_edge (const superedge &edge, if (cfg_sedge->get_flags () & EDGE_EH) return apply_constraints_for_exception (last_stmt, ctxt, out); + if (const geh_dispatch *eh_dispatch_stmt + = dyn_cast (last_stmt)) + { + const cfg_superedge *cfg_sedge = as_a (&edge); + return apply_constraints_for_geh_dispatch (*cfg_sedge, eh_dispatch_stmt, + sg, ctxt); + // TODO: maybe even a custom kind of superedge for these? + } + return true; } @@ -2659,6 +2701,157 @@ region_model::apply_constraints_for_exception (const gimple *last_stmt, return true; } +#if 0 +/* FIXME. */ + +cfg_edge * +region_model:: +get_out_edge_for_geh_dispatch (const geh_dispatch *geh_dispatch_stmt) +{ + function *fun = get_current_function (); + gcc_assert (fun); + + int region_num = gimple_eh_dispatch_region (geh_dispatch_stmt); + eh_region region = (*fun->eh->region_array)[region_num]; + gcc_assert (region->index == region_num); + + switch (region->type) + { + default: + gcc_unreachable (); + case ERT_CLEANUP: + gcc_unreachable (); // TODO + break; + case ERT_TRY: + /* TODO: I think in theory we ought to be iterating through the + catch handlers and comparing the type of the current exception + to them, and filtering accordingly. */ + // gcc_unreachable (); // TODO + for (eh_catch_d *iter_catch = region->u.eh_try.first_catch; + iter_catch; iter_catch = iter_catch->next_catch) + { + gcc_assert (m_current_exception->m_tinfo_ptr_sval); + gcc_unreachable (); + //iter_catch->label; + // TODO + } + break; + case ERT_ALLOWED_EXCEPTIONS: + gcc_unreachable (); // TODO + break; + case ERT_MUST_NOT_THROW: + gcc_unreachable (); // TODO + break; + } +} +#endif + +/* FIXME. */ + +static const supernode * +get_supernode_for_eh_catch (eh_catch_d *ehc, + const function *fun, + const supergraph *sg) +{ + tree label = ehc->label; + basic_block bb = label_to_block (const_cast (fun), label); + gcc_assert (bb); + return sg->get_node_for_block (bb); +} + +/* FIXME. + Determine which eh_catch_d within an ERT_TRY reaches + DEST_SNODE. */ +static eh_catch_d * +get_eh_catch_for_supernode (eh_region region, + const supernode *dest_snode, + const function *fun, + const supergraph *sg) +{ + gcc_assert (region->type == ERT_TRY); + for (eh_catch_d *iter_catch = region->u.eh_try.first_catch; + iter_catch; iter_catch = iter_catch->next_catch) + { + const supernode *iter_dest_snode + = get_supernode_for_eh_catch (iter_catch, fun, sg); + if (iter_dest_snode == dest_snode) + return iter_catch; + } + return NULL; +} + +/* FIXME. */ + +bool +region_model:: +apply_constraints_for_geh_dispatch (const cfg_superedge &sedge, + const geh_dispatch *geh_dispatch_stmt, + const supergraph *sg, + region_model_context *ctxt) +{ + ::edge cfg_edge = sedge.get_cfg_edge (); + gcc_assert (cfg_edge != NULL); + //gcc_assert (cfg_edge->flags & (EDGE_TRUE_VALUE | EDGE_FALSE_VALUE)); + + const function *fun = sedge.m_src->get_function (); + + int region_num = gimple_eh_dispatch_region (geh_dispatch_stmt); + eh_region region = (*fun->eh->region_array)[region_num]; + gcc_assert (region->index == region_num); + + switch (region->type) + { + default: + gcc_unreachable (); + case ERT_CLEANUP: + gcc_unreachable (); // TODO + break; + case ERT_TRY: + { + gcc_assert (m_current_exception->m_tinfo_ptr_sval); + tree rtti_type = m_current_exception->get_rtti_type (); + gcc_assert (rtti_type); // TODO: what if it fails? + + /* TODO: I think in theory we ought to be iterating through the + catch handlers and comparing the type of the current exception + to them, and filtering accordingly. */ + if (eh_catch_d *ehc + = get_eh_catch_for_supernode (region, sedge.m_dest, fun, sg)) + { + for (tree iter = ehc->type_list; iter; iter = TREE_CHAIN (iter)) + { + tree type = TREE_VALUE (iter); + if (type == rtti_type) + return true; + } + return false; + } + else + { + // TODO: presumably the edge for the "unmatched" case + return false; // FIXME + } + } + break; + case ERT_ALLOWED_EXCEPTIONS: + gcc_unreachable (); // TODO + break; + case ERT_MUST_NOT_THROW: + gcc_unreachable (); // TODO + break; + } + + //gcc_unreachable (); +#if 0 + enum tree_code op = gimple_cond_code (cond_stmt); + tree lhs = gimple_cond_lhs (cond_stmt); + tree rhs = gimple_cond_rhs (cond_stmt); + if (cfg_edge->flags & EDGE_FALSE_VALUE) + op = invert_tree_comparison (op, false /* honor_nans */); + return add_constraint (lhs, op, rhs, ctxt); +#endif +} + /* 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" diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h index a5fa9f63086..5c855c6f386 100644 --- a/gcc/analyzer/region-model.h +++ b/gcc/analyzer/region-model.h @@ -190,6 +190,7 @@ public: virtual void visit_widening_svalue (const widening_svalue *) {} virtual void visit_compound_svalue (const compound_svalue *) {} virtual void visit_conjured_svalue (const conjured_svalue *) {} + virtual void visit_exception_state (const exception_state *) {} virtual void visit_region (const region *) {} }; @@ -212,7 +213,8 @@ enum svalue_kind SK_PLACEHOLDER, SK_WIDENING, SK_COMPOUND, - SK_CONJURED + SK_CONJURED, + SK_EXCEPTION_STATE }; /* svalue and its subclasses. @@ -236,7 +238,8 @@ enum svalue_kind widening_svalue (SK_WIDENING): a merger of two svalues (possibly in an iteration). compound_svalue (SK_COMPOUND): a mapping of bit-ranges to svalues - conjured_svalue (SK_CONJURED): a value arising from a stmt. */ + conjured_svalue (SK_CONJURED): a value arising from a stmt + exception_state (SK_EXPEPTION_STATE): FIXME. */ /* An abstract base class representing a value held by a region of memory. */ @@ -282,6 +285,8 @@ public: dyn_cast_compound_svalue () const { return NULL; } virtual const conjured_svalue * dyn_cast_conjured_svalue () const { return NULL; } + virtual const exception_state * + dyn_cast_exception_state () const { return NULL; } tree maybe_get_constant () const; const svalue *maybe_undo_cast () const; @@ -2305,6 +2310,48 @@ public: void dump_to_pp (pretty_printer *pp, bool simple) const FINAL OVERRIDE; }; +/* FIXME. + + An abstraction around struct __cxa_exception from e.g. + https://itanium-cxx-abi.github.io/cxx-abi/abi-eh.html + "2.2.1 C++ Exception Objects". */ + +class exception_state : public svalue +{ +public: + exception_state (const svalue *exception_ptr_sval, + const svalue *tinfo_ptr_sval, + const svalue *dest_ptr_sval) + : svalue (complexity (0, 0), NULL_TREE), + m_exception_ptr_sval (exception_ptr_sval), + m_tinfo_ptr_sval (tinfo_ptr_sval), + m_dest_ptr_sval (dest_ptr_sval) + {} + + enum svalue_kind get_kind () const FINAL OVERRIDE + { + return SK_EXCEPTION_STATE; + } + + const exception_state * dyn_cast_exception_state () const FINAL OVERRIDE + { + return this; + } + + void dump_to_pp (pretty_printer *pp, bool simple) const FINAL OVERRIDE; + void accept (visitor *v) const FINAL OVERRIDE; + + void dump_to_pp (pretty_printer *pp, bool simple, bool multiline) const; + + tree get_rtti_var_decl () const; + tree get_rtti_type () const; + + //private: + const svalue *m_exception_ptr_sval; + const svalue *m_tinfo_ptr_sval; + const svalue *m_dest_ptr_sval; +}; + /* A class responsible for owning and consolidating region and svalue instances. region and svalue instances are immutable as far as clients are @@ -2379,6 +2426,11 @@ public: unsigned alloc_region_id () { return m_next_region_id++; } + const exception_state * + get_or_create_exception_state (const svalue *exception_ptr_sval, + const svalue *tinfo_ptr_sval, + const svalue *dest_ptr_sval); + store_manager *get_store_manager () { return &m_store_mgr; } /* Dynamically-allocated region instances. @@ -2580,8 +2632,11 @@ class region_model region_model_context *ctxt); void impl_call_analyzer_eval (const gcall *call, region_model_context *ctxt); + bool impl_call_builtin_eh_pointer (const call_details &cd); bool impl_call_builtin_expect (const call_details &cd); bool impl_call_calloc (const call_details &cd); + bool impl_call_cxa_allocate_exception (const call_details &cd); + bool impl_call_cxa_begin_catch (const call_details &cd); void impl_call_free (const call_details &cd); bool impl_call_malloc (const call_details &cd); void impl_call_memcpy (const call_details &cd); @@ -2603,6 +2658,8 @@ class region_model void on_longjmp (const gcall *longjmp_call, const gcall *setjmp_call, int setjmp_stack_depth, region_model_context *ctxt); + void unwind_stack (int dest_stack_depth, region_model_context *ctxt); + void update_for_phis (const supernode *snode, const cfg_superedge *last_cfg_superedge, region_model_context *ctxt); @@ -2612,6 +2669,7 @@ class region_model bool maybe_update_for_edge (const superedge &edge, const gimple *last_stmt, + const supergraph *sg, region_model_context *ctxt, rejected_constraint **out); @@ -2738,6 +2796,10 @@ class region_model bool apply_constraints_for_exception (const gimple *last_stmt, region_model_context *ctxt, rejected_constraint **out); + bool apply_constraints_for_geh_dispatch (const cfg_superedge &edge, + const geh_dispatch *stmt, + const supergraph *sg, + region_model_context *ctxt); int poison_any_pointers_to_descendents (const region *reg, enum poison_kind pkind); @@ -2761,6 +2823,10 @@ class region_model constraint_manager *m_constraints; // TODO: embed, rather than dynalloc? const frame_region *m_current_frame; + +public: // FIXME + const exception_state *m_current_exception; + // TODO: ^^ make this affect reachability etc }; /* Some region_model activity could lead to warnings (e.g. attempts to use an diff --git a/gcc/analyzer/svalue.cc b/gcc/analyzer/svalue.cc index 45f55893b74..97c3ca79134 100644 --- a/gcc/analyzer/svalue.cc +++ b/gcc/analyzer/svalue.cc @@ -1215,6 +1215,99 @@ conjured_svalue::accept (visitor *v) const m_id_reg->accept (v); } +/* class exception_state : public svalue. */ + +/* FIXME. */ + +void +exception_state::dump_to_pp (pretty_printer *pp, bool simple) const +{ + dump_to_pp (pp, simple, false); +} + +/* FIXME. */ + +void +exception_state::accept (visitor *v) const +{ + v->visit_exception_state (this); + m_exception_ptr_sval->accept (v); + m_tinfo_ptr_sval->accept (v); + m_dest_ptr_sval->accept (v); +} + +/* FIXME. */ + +void +exception_state::dump_to_pp (pretty_printer *pp, + bool simple, bool multiline) const +{ + if (!multiline) + { + if (simple) + pp_string (pp, "{"); + else + pp_string (pp, "exception_state("); + } + + if (multiline) + pp_string (pp, " "); + pp_string (pp, "m_exception_ptr: "); + m_exception_ptr_sval->dump_to_pp (pp, simple); + if (multiline) + pp_newline (pp); + + if (multiline) + pp_string (pp, " "); + pp_string (pp, "m_tinfo_ptr: "); + m_tinfo_ptr_sval->dump_to_pp (pp, simple); + if (multiline) + pp_newline (pp); + + if (multiline) + pp_string (pp, " "); + pp_string (pp, "m_dest_ptr: "); + m_dest_ptr_sval->dump_to_pp (pp, simple); + if (multiline) + pp_newline (pp); + + if (!multiline) + { + if (simple) + pp_string (pp, "}"); + else + pp_string (pp, ")"); + } +} + +/* FIXME. */ + +tree +exception_state::get_rtti_var_decl () const +{ + if (const region_svalue *region_sval + = m_tinfo_ptr_sval->dyn_cast_region_svalue ()) + if (const decl_region *decl_reg + = region_sval->get_pointee ()->dyn_cast_decl_region ()) + return decl_reg->get_decl (); + return NULL_TREE; +} + +/* Get the type of the current exception, or NULL_TREE. */ + +tree +exception_state::get_rtti_type () const +{ + if (tree rtti_var_decl = get_rtti_var_decl ()) + { + /* cp/rtti.c:get_tinfo_decl stashes the type as the type + of the mangled name + TREE_TYPE (name) = type; */ + return TREE_TYPE (DECL_NAME (rtti_var_decl)); + } + return NULL_TREE; +} + } // namespace ana #endif /* #if ENABLE_ANALYZER */ diff --git a/gcc/testsuite/g++.dg/analyzer/except-2.C b/gcc/testsuite/g++.dg/analyzer/except-2.C new file mode 100644 index 00000000000..e05df77797a --- /dev/null +++ b/gcc/testsuite/g++.dg/analyzer/except-2.C @@ -0,0 +1,33 @@ +#include "../../gcc.dg/analyzer/analyzer-decls.h" + +struct some_err +{ +}; + +struct range_err +{ + range_err (int val) : m_val (val) {} + int m_val; +}; + +/* Interprocedural throw/catch. */ + +static void __attribute__((noinline)) +called_by_test_1 (int n) +{ + if (n < 0 || n > 10) throw range_err (n); // { dg-message "throwing 'range_err' from 'called_by_test_1'" } + + __analyzer_dump_path (); // { dg-bogus "" } +} + +void test_1 (int n) +{ + try { + called_by_test_1 (42); + } catch (some_err &e) { + __analyzer_dump_path (); // { dg-bogus "" } + } catch (range_err &e) { // { dg-message "caught here in 'test_1'" } + __analyzer_eval (e.m_val == 42); // { dg-warning "TRUE" } + __analyzer_dump_path (); // { dg-message "path" } + } +} diff --git a/gcc/testsuite/g++.dg/analyzer/except-3.C b/gcc/testsuite/g++.dg/analyzer/except-3.C new file mode 100644 index 00000000000..ddedf4a9d87 --- /dev/null +++ b/gcc/testsuite/g++.dg/analyzer/except-3.C @@ -0,0 +1,25 @@ +#include "../../gcc.dg/analyzer/analyzer-decls.h" + +struct some_err +{ +}; + +struct range_err +{ + range_err (int val) : m_val (val) {} + int m_val; +}; + +/* Intraprocedural throw/catch. */ + +void test_1 (int n) +{ + try { + if (n < 0 || n > 10) + throw range_err (n); // { dg-message "throwing 'range_err'" } + } catch (some_err &e) { + __analyzer_dump_path (); // { dg-bogus "" } + } catch (range_err &e) { // { dg-message "caught here" } + __analyzer_dump_path (); // { dg-message "path" } + } +} diff --git a/gcc/testsuite/g++.dg/analyzer/except-5.C b/gcc/testsuite/g++.dg/analyzer/except-5.C new file mode 100644 index 00000000000..bc695c92f07 --- /dev/null +++ b/gcc/testsuite/g++.dg/analyzer/except-5.C @@ -0,0 +1,13 @@ +#include + +struct range_err +{ + range_err (int val) : m_val (val) {} + int m_val; +}; + +void test_1 (int n) +{ + void *ptr = malloc (1024); // { dg-message "allocated here" } + throw range_err (n); // { dg-warning "leak of 'ptr'" } +} diff --git a/gcc/testsuite/gcc.dg/analyzer/analyzer-decls.h b/gcc/testsuite/gcc.dg/analyzer/analyzer-decls.h index d96b3f2f29a..efd37479d66 100644 --- a/gcc/testsuite/gcc.dg/analyzer/analyzer-decls.h +++ b/gcc/testsuite/gcc.dg/analyzer/analyzer-decls.h @@ -1,6 +1,16 @@ #ifndef ANALYZER_DECLS_H #define ANALYZER_DECLS_H +#ifdef __cplusplus +# if __cplusplus >= 201103L +# define NOEXCEPT noexcept +# else +# define NOEXCEPT +# endif +#else +# define NOEXCEPT +#endif + /* Function decls with special meaning to the analyzer. None of these are actually implemented. */ @@ -27,13 +37,13 @@ extern void __analyzer_dump_exploded_nodes (int); /* Emit a placeholder "note" diagnostic with a path to this call site, if the analyzer finds a feasible path to it. */ -extern void __analyzer_dump_path (void); +extern void __analyzer_dump_path (void) NOEXCEPT; /* Dump the region_model's state to stderr. */ extern void __analyzer_dump_region_model (void); /* Emit a warning with text "TRUE", FALSE" or "UNKNOWN" based on the truthfulness of the argument. */ -extern void __analyzer_eval (int); +extern void __analyzer_eval (int) NOEXCEPT; #endif /* #ifndef ANALYZER_DECLS_H. */ -- 2.26.2