From: David Malcolm Date: Mon, 28 Apr 2025 22:21:24 +0000 (-0400) Subject: analyzer: initial implementation of exception handling [PR97111] X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=7a39e0ca0652ff84a31efa3c7d4c7a78d9bb95ae;p=thirdparty%2Fgcc.git analyzer: initial implementation of exception handling [PR97111] This patch adds initial support for exception-handling to -fanalyzer, handling eh_dispatch for regions of type ERT_TRY and ERT_ALLOWED_EXCEPTIONS. I haven't managed yet seen eh_dispatch for regions of type ERT_CLEANUP and ERT_MUST_NOT_THROW in the analyzer; with this patch it will ICE if it sees those. Additionally, this patch only checks for exact matches of exception types, rather than supporting subclasses and references. I'm deferring fixing this for now whilst figuring out how best to interact with the C++ type system; I'm tracking it as PR analyzer/119697. The patch adds event classes for throwing and catching exceptions, and seems to generate readable warnings for the kinds of leak that might occur due to trying to manage resources manually and forgetting about exceptions; for example: exception-leak-1.C: In function ‘int test()’: exception-leak-1.C:7:9: warning: leak of ‘ptr’ [CWE-401] [-Wanalyzer-malloc-leak] 7 | throw 42; | ^~ ‘int test()’: events 1-3 5 | void *ptr = __builtin_malloc (1024); | ~~~~~~~~~~~~~~~~~^~~~~~ | | | (1) allocated here 6 | 7 | throw 42; | ~~ | | | (2) throwing exception of type ‘int’ here... | (3) ⚠️ ‘ptr’ leaks here; was allocated at (1) Although dynamic exception specifications are only available in C++14 and earlier, the need to support them meant it seemed relatively easy to add a warning to check them, hence the patch adds a new warning for code paths that throw an exception that doesn't match a dynamic exception specification: -Wanalyzer-throw-of-unexpected-type. gcc/analyzer/ChangeLog: PR analyzer/97111 * analyzer.cc (is_cxa_throw_p): New. (is_cxa_rethrow_p): New. * analyzer.opt (Wanalyzer-throw-of-unexpected-type): New. * analyzer.opt.urls: Regenerate. * call-info.cc (custom_edge_info::create_enode): New. * call-info.h (call_info::print): Drop "final". (call_info::add_events_to_path): Likewise. * checker-event.cc (event_kind_to_string): Add cases for event_kind::catch_, event_kind::throw_, and event_kind::unwind. (explicit_throw_event::print_desc): New. (throw_from_call_to_external_fn_event::print_desc): New. (unwind_event::print_desc): New. * checker-event.h (enum class event_kind): Add catch_, throw_, and unwind. (class catch_cfg_edge_event): New. (class throw_event): New. (class explicit_throw_event): New. (class throw_from_call_to_external_fn_event): New. (class unwind_event): New. * common.h (class eh_dispatch_cfg_superedge): New forward decl. (class eh_dispatch_try_cfg_superedge): New forward decl. (class eh_dispatch_allowed_cfg_superedge): New forward decl. (custom_edge_info::create_enode): New vfunc decl. (is_cxa_throw_p): New decl. (is_cxa_rethrow_p): New decl. * diagnostic-manager.cc (diagnostic_manager::add_events_for_superedge): Special-case edges for eh_dispach_try. (diagnostic_manager::prune_path): Call consolidate_unwind_events. (diagnostic_manager::prune_for_sm_diagnostic): Don't filter the new event_kinds. (diagnostic_manager::consolidate_unwind_events): New. * diagnostic-manager.h (diagnostic_manager::consolidate_unwind_events): New decl. * engine.cc (exploded_node::on_stmt_pre): Handle "__cxa_throw", "__cxa_rethrow", and resx statements. (class throw_custom_edge): New. (class unwind_custom_edge): New. (get_eh_outedge): New. (exploded_graph::unwind_from_exception): New. (exploded_node::on_throw): New. (exploded_node::on_resx): New. (exploded_graph::get_or_create_node): Add "add_to_worklist" param and use it. (exploded_graph::process_node): Use edge_info's create_enode vfunc to create enodes, rather than calling get_or_create_node directly. Ignore CFG edges in the sgraph flagged with EH whilst we're exploring the egraph. (exploded_graph_annotator::print_enode): Handle case exploded_node::status::special. * exploded-graph.h (exploded_node::status): Add value "special". (exploded_node::on_throw): New decl. (exploded_node::on_resx): New decl. (exploded_graph::get_or_create_node): Add optional "add_to_worklist" param. (exploded_graph::unwind_from_exception): New decl. * kf-lang-cp.cc (class kf_cxa_allocate_exception): New. (class kf_cxa_begin_catch): New. (class kf_cxa_end_catch): New. (class throw_of_unexpected_type): New. (class kf_cxa_call_unexpected): New. (register_known_functions_lang_cp): Register known functions "__cxa_allocate_exception", "__cxa_begin_catch", "__cxa_end_catch", and "__cxa_call_unexpected". * kf.cc (class kf_eh_pointer): New. (register_known_functions): Register it for BUILT_IN_EH_POINTER. * region-model.cc: Include "analyzer/function-set.h". (exception_node::operator==): New. (exception_node::dump_to_pp): New. (exception_node::dump): New. (exception_node::to_json): New. (exception_node::make_dump_widget): New. (exception_node::maybe_get_type): New. (exception_node::add_to_reachable_regions): New. (region_model::region_model): Initialize m_thrown_exceptions_stack and m_caught_exceptions_stack. (region_model::operator=): Likewise. (region_model::operator==): Compare them. (region_model::dump_to_pp): Dump exception stacks. (region_model::to_json): Add exception stacks. (region_model::make_dump_widget): Likewise. (class exception_thrown_from_unrecognized_call): New. (get_fns_assumed_not_to_throw): New. (can_throw_p): New. (region_model::check_for_throw_inside_call): New. (region_model::on_call_pre): Call check_for_throw_inside_call on unknown fns or those we don't have a body for. (region_model::maybe_update_for_edge): Handle eh_dispatch_stmt statements. Drop old code that called apply_constraints_for_exception on EDGE_EH edges. (class rejected_eh_dispatch): New. (exception_matches_type_p): New. (matches_any_exception_type_p): New. (region_model::apply_constraints_for_eh_dispatch): New. (region_model::apply_constraints_for_eh_dispatch_try): New. (region_model::apply_constraints_for_eh_dispatch_allowed): New. (region_model::apply_constraints_for_exception): Delete. (region_model::can_merge_with_p): Don't merge models with non-equal exception stacks. (region_model::get_referenced_base_regions): Add regions from exception stacks. * region-model.h (struct exception_node): New. (region_model::push_thrown_exception): New. (region_model::get_current_thrown_exception): New. (region_model::pop_thrown_exception): New. (region_model::push_caught_exception): New. (region_model::get_current_caught_exception): New. (region_model::pop_caught_exception): New. (region_model::apply_constraints_for_eh_dispatch_try): New decl. (region_model::apply_constraints_for_eh_dispatch_allowed) New decl. (region_model::apply_constraints_for_exception): Delete. (region_model::apply_constraints_for_eh_dispatch): New decl. (region_model::check_for_throw_inside_call): New decl. (region_model::m_thrown_exceptions_stack): New field. (region_model::m_caught_exceptions_stack): New field. * supergraph.cc: Include "except.h" and "analyzer/region-model.h". (supergraph::add_cfg_edge): Special-case eh_dispatch edges. (superedge::get_description): Use default_tree_printer. (get_catch): New. (eh_dispatch_cfg_superedge::make): New. (eh_dispatch_cfg_superedge::eh_dispatch_cfg_superedge): New. (eh_dispatch_cfg_superedge::get_eh_status): New. (eh_dispatch_try_cfg_superedge::dump_label_to_pp): New. (eh_dispatch_try_cfg_superedge::apply_constraints): New. (eh_dispatch_allowed_cfg_superedge::eh_dispatch_allowed_cfg_superedge): New. (eh_dispatch_allowed_cfg_superedge::dump_label_to_pp): New. (eh_dispatch_allowed_cfg_superedge::apply_constraints): New. * supergraph.h: Include "except.h". (superedge::dyn_cast_eh_dispatch_cfg_superedge): New vfunc. (superedge::dyn_cast_eh_dispatch_try_cfg_superedge): New vfunc. (superedge::dyn_cast_eh_dispatch_allowed_cfg_superedge): New vfunc. (class eh_dispatch_cfg_superedge): New. (is_a_helper ::test): New. (class eh_dispatch_try_cfg_superedge): New. (is_a_helper ::test): New. (class eh_dispatch_allowed_cfg_superedge): New. (is_a_helper ::test): New. * svalue.cc (svalue::maybe_get_type_from_typeinfo): New. * svalue.h (svalue::maybe_get_type_from_typeinfo): New decl. gcc/ChangeLog: PR analyzer/97111 * doc/invoke.texi: Add -Wanalyzer-throw-of-unexpected-type. * gimple.h (gimple_call_nothrow_p): Make arg const. gcc/testsuite/ChangeLog: PR analyzer/97111 * c-c++-common/analyzer/analyzer-verbosity-2a.c: Add -fno-exceptions. * c-c++-common/analyzer/analyzer-verbosity-3a.c: Likewise. * c-c++-common/analyzer/attr-const-2.c: Add __attribute__((nothrow)). * c-c++-common/analyzer/attr-malloc-4.c: Likewise. * c-c++-common/analyzer/attr-malloc-5.c: Likewise. * c-c++-common/analyzer/attr-malloc-6.c: Add -fno-exceptions. * c-c++-common/analyzer/attr-malloc-CVE-2019-19078-usb-leak.c: Likewise. * c-c++-common/analyzer/attr-malloc-exception.c: New test. * c-c++-common/analyzer/call-summaries-pr107158-2.c: Add -fno-exceptions. * c-c++-common/analyzer/call-summaries-pr107158.c: Likewise. * c-c++-common/analyzer/capacity-2.c: Likewise. * c-c++-common/analyzer/coreutils-sum-pr108666.c: Likewise. * c-c++-common/analyzer/data-model-22.c: Likewise. * c-c++-common/analyzer/data-model-5d.c: Likewise. * c-c++-common/analyzer/deref-before-check-pr108455-git-pack-revindex.c: Likewise. * c-c++-common/analyzer/deref-before-check-pr108475-haproxy-tcpcheck.c: Likewise. * c-c++-common/analyzer/edges-2.c: Likewise. * c-c++-common/analyzer/fd-2.c: Likewise. * c-c++-common/analyzer/fd-3.c: Likewise. * c-c++-common/analyzer/fd-meaning.c: Likewise. * c-c++-common/analyzer/file-1.c: Likewise. * c-c++-common/analyzer/file-3.c: Likewise. * c-c++-common/analyzer/file-meaning-1.c: Likewise. * c-c++-common/analyzer/infinite-recursion.c: Likewise. * c-c++-common/analyzer/leak-3.c: Likewise. * c-c++-common/analyzer/malloc-dedupe-1.c: Likewise. * c-c++-common/analyzer/malloc-in-loop.c: Likewise. * c-c++-common/analyzer/malloc-many-paths-3.c: Likewise. * c-c++-common/analyzer/malloc-paths-5.c: Likewise. * c-c++-common/analyzer/malloc-paths-7.c: Likewise. * c-c++-common/analyzer/malloc-paths-8.c: Likewise. * c-c++-common/analyzer/malloc-vs-local-1a.c: Likewise. * c-c++-common/analyzer/malloc-vs-local-2.c: Likewise. * c-c++-common/analyzer/malloc-vs-local-3.c: Likewise. * c-c++-common/analyzer/paths-7.c: Likewise. * c-c++-common/analyzer/pr110830.c: Likewise. * c-c++-common/analyzer/pr93032-mztools-simplified.c: Likewise. * c-c++-common/analyzer/pr93355-localealias-feasibility-3.c: Likewise. * c-c++-common/analyzer/pr93355-localealias-simplified.c: Likewise. * c-c++-common/analyzer/pr96650-1-trans.c: Likewise. * c-c++-common/analyzer/pr97072.c: Add __attribute__((nothrow)). * c-c++-common/analyzer/pr98575-1.c: Likewise. * c-c++-common/analyzer/pr99716-1.c: Add -fno-exceptions. * c-c++-common/analyzer/pr99716-2.c: Likewise. * c-c++-common/analyzer/pr99716-3.c: Likewise. * c-c++-common/analyzer/pragma-2.c: Likewise. * c-c++-common/analyzer/rhbz1878600.c: Likewise. * c-c++-common/analyzer/strndup-1.c: Likewise. * c-c++-common/analyzer/write-to-string-literal-4-disabled.c: Likewise. * c-c++-common/analyzer/write-to-string-literal-4.c: Likewise. * c-c++-common/analyzer/write-to-string-literal-5.c: Likewise. * c-c++-common/analyzer/zlib-5.c: Likewise. * g++.dg/analyzer/exception-could-throw-1.C: New test. * g++.dg/analyzer/exception-could-throw-2.C: New test. * g++.dg/analyzer/exception-dynamic-spec.C: New test. * g++.dg/analyzer/exception-leak-1.C: New test. * g++.dg/analyzer/exception-leak-2.C: New test. * g++.dg/analyzer/exception-leak-3.C: New test. * g++.dg/analyzer/exception-leak-4.C: New test. * g++.dg/analyzer/exception-leak-5.C: New test. * g++.dg/analyzer/exception-leak-6.C: New test. * g++.dg/analyzer/exception-nothrow.C: New test. * g++.dg/analyzer/exception-path-1.C: New test. * g++.dg/analyzer/exception-path-catch-all-1.C: New test. * g++.dg/analyzer/exception-path-catch-all-2.C: New test. * g++.dg/analyzer/exception-path-unwind-multiple-2.C: New test. * g++.dg/analyzer/exception-path-unwind-multiple.C: New test. * g++.dg/analyzer/exception-path-unwind-single.C: New test. * g++.dg/analyzer/exception-path-with-cleanups.C: New test. * g++.dg/analyzer/exception-rethrow-1.C: New test. * g++.dg/analyzer/exception-rethrow-2.C: New test. * g++.dg/analyzer/exception-stack-1.C: New test. * g++.dg/analyzer/exception-stack-2.C: New test. * g++.dg/analyzer/exception-subclass-1.C: New test. * g++.dg/analyzer/exception-subclass-2.C: New test. * g++.dg/analyzer/exception-value-1.C: New test. * g++.dg/analyzer/exception-value-2.C: New test. * g++.dg/analyzer/fno-exception.C: New test. * g++.dg/analyzer/pr94028.C: Drop xfail. * g++.dg/analyzer/std-unexpected.C: New test. * g++.dg/coroutines/pr105287.C: Drop dg-excess-errors. Signed-off-by: David Malcolm --- diff --git a/gcc/analyzer/analyzer.cc b/gcc/analyzer/analyzer.cc index c2814d2f198..56cb370f147 100644 --- a/gcc/analyzer/analyzer.cc +++ b/gcc/analyzer/analyzer.cc @@ -449,6 +449,26 @@ is_longjmp_call_p (const gcall &call) 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). */ diff --git a/gcc/analyzer/analyzer.opt b/gcc/analyzer/analyzer.opt index d5f82c6471a..2ca905854bd 100644 --- a/gcc/analyzer/analyzer.opt +++ b/gcc/analyzer/analyzer.opt @@ -222,6 +222,10 @@ Wanalyzer-tainted-size 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. diff --git a/gcc/analyzer/analyzer.opt.urls b/gcc/analyzer/analyzer.opt.urls index 18a0d6926de..e76e6e5571d 100644 --- a/gcc/analyzer/analyzer.opt.urls +++ b/gcc/analyzer/analyzer.opt.urls @@ -114,6 +114,9 @@ UrlSuffix(gcc/Static-Analyzer-Options.html#index-Wanalyzer-tainted-offset) 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) diff --git a/gcc/analyzer/call-info.cc b/gcc/analyzer/call-info.cc index 0e48c1606cb..9a698efdcaf 100644 --- a/gcc/analyzer/call-info.cc +++ b/gcc/analyzer/call-info.cc @@ -58,6 +58,18 @@ custom_edge_info::update_state (program_state *state, 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. */ diff --git a/gcc/analyzer/call-info.h b/gcc/analyzer/call-info.h index f61b9be30fe..6548d861bf2 100644 --- a/gcc/analyzer/call-info.h +++ b/gcc/analyzer/call-info.h @@ -30,9 +30,9 @@ namespace ana { 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; } diff --git a/gcc/analyzer/checker-event.cc b/gcc/analyzer/checker-event.cc index 914a6c9dbbb..958cdbf394d 100644 --- a/gcc/analyzer/checker-event.cc +++ b/gcc/analyzer/checker-event.cc @@ -73,6 +73,8 @@ event_kind_to_string (enum event_kind ek) 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: @@ -89,6 +91,10 @@ event_kind_to_string (enum event_kind ek) 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"; } @@ -1104,6 +1110,50 @@ rewind_to_setjmp_event::prepare_for_emission (checker_path *path, &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 diff --git a/gcc/analyzer/checker-event.h b/gcc/analyzer/checker-event.h index eb69e0f7893..f3ab8998418 100644 --- a/gcc/analyzer/checker-event.h +++ b/gcc/analyzer/checker-event.h @@ -40,6 +40,7 @@ enum class event_kind state_change, start_cfg_edge, end_cfg_edge, + catch_, call_edge, return_edge, start_consolidated_cfg_edges, @@ -48,6 +49,8 @@ enum class event_kind setjmp_, rewind_from_longjmp, rewind_to_setjmp, + throw_, + unwind, warning }; @@ -71,6 +74,7 @@ extern const char *event_kind_to_string (enum event_kind ek); 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) @@ -80,6 +84,10 @@ extern const char *event_kind_to_string (enum event_kind ek); 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 @@ -471,6 +479,32 @@ public: } }; +/* 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 @@ -665,6 +699,88 @@ private: 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 diff --git a/gcc/analyzer/common.h b/gcc/analyzer/common.h index 3d02b6242b1..cb030043d8a 100644 --- a/gcc/analyzer/common.h +++ b/gcc/analyzer/common.h @@ -49,6 +49,9 @@ class supernode; 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; @@ -396,6 +399,12 @@ public: 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. @@ -472,6 +481,8 @@ extern bool is_std_named_call_p (const_tree fndecl, const char *funcname, 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); diff --git a/gcc/analyzer/diagnostic-manager.cc b/gcc/analyzer/diagnostic-manager.cc index 161ae62051a..7575b16c86c 100644 --- a/gcc/analyzer/diagnostic-manager.cc +++ b/gcc/analyzer/diagnostic-manager.cc @@ -2427,6 +2427,35 @@ diagnostic_manager::add_events_for_superedge (const path_builder &pb, { 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 + (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 (eedge, @@ -2502,6 +2531,7 @@ diagnostic_manager::prune_path (checker_path *path, 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"); } @@ -2690,6 +2720,12 @@ diagnostic_manager::prune_for_sm_diagnostic (checker_path *path, 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; @@ -3130,6 +3166,48 @@ diagnostic_manager::consolidate_conditions (checker_path *path) const } } +/* 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 diff --git a/gcc/analyzer/diagnostic-manager.h b/gcc/analyzer/diagnostic-manager.h index 06a8233eb6b..b62fc7a258e 100644 --- a/gcc/analyzer/diagnostic-manager.h +++ b/gcc/analyzer/diagnostic-manager.h @@ -229,6 +229,7 @@ private: 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; diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc index 1b819ffbbbb..0917306fe38 100644 --- a/gcc/analyzer/engine.cc +++ b/gcc/analyzer/engine.cc @@ -1599,6 +1599,24 @@ exploded_node::on_stmt_pre (exploded_graph &eg, 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 (stmt)) + { + on_resx (eg, *resx, state, ctxt); + *out_terminate_path = true; + return; } /* Otherwise, defer to m_region_model. */ @@ -2017,6 +2035,332 @@ exploded_node::on_longjmp (exploded_graph &eg, } } +/* 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 + (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 (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 (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 (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. @@ -2839,7 +3183,8 @@ exploded_graph::add_function_entry (const function &fun) } /* 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 @@ -2848,7 +3193,8 @@ exploded_graph::add_function_entry (const function &fun) 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); @@ -3023,7 +3369,10 @@ exploded_graph::get_or_create_node (const program_point &point, } /* 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; } @@ -4258,12 +4607,18 @@ exploded_graph::process_node (exploded_node *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 { @@ -4384,6 +4739,18 @@ exploded_graph::process_node (exploded_node *node) } } + /* 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)) { @@ -6002,6 +6369,9 @@ private: 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; diff --git a/gcc/analyzer/exploded-graph.h b/gcc/analyzer/exploded-graph.h index aa6fb1f6036..32c72dc2076 100644 --- a/gcc/analyzer/exploded-graph.h +++ b/gcc/analyzer/exploded-graph.h @@ -214,6 +214,10 @@ class exploded_node : public dnode /* 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, @@ -306,6 +310,15 @@ class exploded_node : public dnode 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); @@ -827,7 +840,8 @@ public: 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 = NULL); @@ -881,6 +895,10 @@ public: 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 (); diff --git a/gcc/analyzer/kf-lang-cp.cc b/gcc/analyzer/kf-lang-cp.cc index 8e08dbc16dc..01a98b0163e 100644 --- a/gcc/analyzer/kf-lang-cp.cc +++ b/gcc/analyzer/kf-lang-cp.cc @@ -167,6 +167,161 @@ public: }; +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 +{ +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 (exception_type, + thrown_from_fndecl)); + ctxt->terminate_path (); + } + } +}; + /* Populate KFM with instances of known functions relating to C++. */ void @@ -176,6 +331,17 @@ register_known_functions_lang_cp (known_function_manager &kfm) kfm.add ("operator new []", std::make_unique ()); kfm.add ("operator delete", std::make_unique ()); kfm.add ("operator delete []", std::make_unique ()); + + /* 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 ()); + // We treat __cxa_throw and __cxa_rethrow as special cases + kfm.add ("__cxa_begin_catch", std::make_unique ()); + kfm.add ("__cxa_end_catch", std::make_unique ()); + kfm.add ("__cxa_call_unexpected", + std::make_unique ()); } } // namespace ana diff --git a/gcc/analyzer/kf.cc b/gcc/analyzer/kf.cc index e9ad86514d6..75b627902f8 100644 --- a/gcc/analyzer/kf.cc +++ b/gcc/analyzer/kf.cc @@ -1276,6 +1276,27 @@ public: /* 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 @@ -2250,6 +2271,8 @@ register_known_functions (known_function_manager &kfm, kfm.add (BUILT_IN_STACK_RESTORE, std::make_unique ()); kfm.add (BUILT_IN_STACK_SAVE, std::make_unique ()); + kfm.add (BUILT_IN_EH_POINTER, std::make_unique ()); + register_atomic_builtins (kfm); register_sanitizer_builtins (kfm); register_varargs_builtins (kfm); diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc index ea0e6b0a518..fc586296ce5 100644 --- a/gcc/analyzer/region-model.cc +++ b/gcc/analyzer/region-model.cc @@ -64,6 +64,7 @@ along with GCC; see the file COPYING3. If not see #include "analyzer/checker-path.h" #include "analyzer/feasible-graph.h" #include "analyzer/record-layout.h" +#include "analyzer/function-set.h" #if ENABLE_ANALYZER @@ -314,12 +315,97 @@ region_to_value_map::purge_state_involving (const svalue *sval) 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 +exception_node::to_json () const +{ + auto obj = std::make_unique (); + 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 +exception_node::make_dump_widget (const text_art::dump_widget_info &dwi) const +{ + using text_art::tree_widget; + std::unique_ptr 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); @@ -331,6 +417,8 @@ 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_thrown_exceptions_stack (other.m_thrown_exceptions_stack), + m_caught_exceptions_stack (other.m_caught_exceptions_stack), m_dynamic_extents (other.m_dynamic_extents) { } @@ -357,6 +445,9 @@ region_model::operator= (const region_model &other) 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; @@ -383,6 +474,11 @@ region_model::operator== (const region_model &other) const 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; @@ -409,7 +505,7 @@ void 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); @@ -430,6 +526,50 @@ region_model::dump_to_pp (pretty_printer *pp, bool simple, 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, ", {"); @@ -502,6 +642,17 @@ region_model::to_json () const 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 (); + 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 (); + 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; } @@ -525,6 +676,26 @@ region_model::make_dump_widget (const text_art::dump_widget_info &dwi) const 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 ())); @@ -1906,6 +2077,170 @@ region_model::get_builtin_kf (const gcall &call, 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 + (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 (call, fndecl); + ctxt->bifurcate (std::move (throws_exception)); +} + /* Update this model for the CALL stmt, using CTXT to report any diagnostics - the first half. @@ -1951,6 +2286,7 @@ region_model::on_call_pre (const gcall &call, region_model_context *ctxt) if (!callee_fndecl) { + check_for_throw_inside_call (call, NULL_TREE, ctxt); cd.set_any_lhs_with_defaults (); return true; /* Unknown side effects. */ } @@ -1971,7 +2307,10 @@ region_model::on_call_pre (const gcall &call, region_model_context *ctxt) 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. */ } @@ -5884,17 +6223,22 @@ region_model::maybe_update_for_edge (const superedge &edge, ctxt, out); } + if (const geh_dispatch *eh_dispatch_stmt + = dyn_cast (last_stmt)) + { + const eh_dispatch_cfg_superedge *eh_dispatch_cfg_sedge + = as_a (&edge); + return apply_constraints_for_eh_dispatch (*eh_dispatch_cfg_sedge, + eh_dispatch_stmt, + ctxt, out); + } + if (const ggoto *goto_stmt = dyn_cast (last_stmt)) { const cfg_superedge *cfg_sedge = as_a (&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 (&edge)) - if (cfg_sedge->get_flags () & EDGE_EH) - return apply_constraints_for_exception (last_stmt, ctxt, out); - return true; } @@ -6171,6 +6515,173 @@ apply_constraints_for_gswitch (const switch_cfg_superedge &edge, 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 *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 *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 (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 (*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 (*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 *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 (*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 (*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 (*this); + return false; + } + return true; + } +} + /* Given an edge reached by GOTO_STMT, determine appropriate constraints for the edge to be taken. @@ -6202,38 +6713,6 @@ region_model::apply_constraints_for_ggoto (const cfg_superedge &edge, 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 *out) -{ - gcc_assert (last_stmt); - if (const gcall *call = dyn_cast (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" @@ -6640,6 +7119,14 @@ region_model::can_merge_with_p (const region_model &other_model, 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; } @@ -6894,6 +7381,12 @@ region_model::get_referenced_base_regions (auto_bitmap &out_ids) const 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 ()); diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h index 399947348dd..c1eb9468f4b 100644 --- a/gcc/analyzer/region-model.h +++ b/gcc/analyzer/region-model.h @@ -250,6 +250,41 @@ typedef void (*pop_frame_callback) (const region_model *model, 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 to_json () const; + + std::unique_ptr + 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 @@ -583,6 +618,56 @@ class region_model 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 *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 *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; @@ -621,9 +706,12 @@ private: 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 *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 *out); int poison_any_pointers_to_descendents (const region *reg, enum poison_kind pkind); @@ -689,6 +777,10 @@ private: 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_callbacks; /* Storing this here to avoid passing it around everywhere. */ region_model_manager *const m_mgr; @@ -699,6 +791,9 @@ private: const frame_region *m_current_frame; + std::vector m_thrown_exceptions_stack; + std::vector 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 diff --git a/gcc/analyzer/supergraph.cc b/gcc/analyzer/supergraph.cc index f4823c620b7..de2c330a258 100644 --- a/gcc/analyzer/supergraph.cc +++ b/gcc/analyzer/supergraph.cc @@ -30,9 +30,11 @@ along with GCC; see the file COPYING3. If not see #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 @@ -490,21 +492,25 @@ supergraph::add_node (function *fun, basic_block bb, gcall *returning_call, /* 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 new_edge; if (stmt && stmt->code == GIMPLE_SWITCH) - new_edge = new switch_cfg_superedge (src, dest, e); + new_edge = std::make_unique (src, dest, e); + else if (stmt && stmt->code == GIMPLE_EH_DISPATCH) + new_edge = eh_dispatch_cfg_superedge::make (src, dest, e, + as_a (stmt)); else - new_edge = new cfg_superedge (src, dest, e); - add_edge (new_edge); - return new_edge; + new_edge = std::make_unique (src, dest, e); + add_edge (new_edge.get ()); + return new_edge.release (); } /* Create and add a call_superedge representing an interprocedural call @@ -1009,6 +1015,7 @@ label_text 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))); } @@ -1078,6 +1085,8 @@ cfg_superedge::get_phi_arg (const gphi *phi) const 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) @@ -1185,6 +1194,203 @@ switch_cfg_superedge::implicitly_created_default_p () const 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::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 + (src_snode, dst_snode, + e, eh_dispatch_stmt, + eh_reg, ehc); + } + break; + case ERT_ALLOWED_EXCEPTIONS: + return std::make_unique + (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 *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 *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. */ diff --git a/gcc/analyzer/supergraph.h b/gcc/analyzer/supergraph.h index ddf721bde9d..8796ab77e29 100644 --- a/gcc/analyzer/supergraph.h +++ b/gcc/analyzer/supergraph.h @@ -27,6 +27,7 @@ along with GCC; see the file COPYING3. If not see #include "gimple.h" #include "gimple-iterator.h" #include "digraph.h" +#include "except.h" using namespace ana; @@ -42,6 +43,9 @@ class superedge; 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; @@ -330,6 +334,9 @@ class superedge : public dedge 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; } @@ -592,6 +599,164 @@ is_a_helper ::test (const superedge *sedge) 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 + 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 *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 ::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 *out) + const final override; + +private: + eh_catch m_eh_catch; +}; + +} // namespace ana + +template <> +template <> +inline bool +is_a_helper ::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 *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 ::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. */ diff --git a/gcc/analyzer/svalue.cc b/gcc/analyzer/svalue.cc index 513d9cc4bb2..f3f80d1dc4e 100644 --- a/gcc/analyzer/svalue.cc +++ b/gcc/analyzer/svalue.cc @@ -860,6 +860,19 @@ svalue::maybe_get_deref_base_region () const } } +/* 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. */ diff --git a/gcc/analyzer/svalue.h b/gcc/analyzer/svalue.h index f416c578f36..7a27cb66b24 100644 --- a/gcc/analyzer/svalue.h +++ b/gcc/analyzer/svalue.h @@ -188,6 +188,8 @@ public: 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) diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index 7fcf7dee497..d5a2bf6d02b 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -513,6 +513,7 @@ Objective-C and Objective-C++ Dialects}. -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 @@ -11008,6 +11009,7 @@ Enabling this option effectively enables the following warnings: -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 @@ -11659,6 +11661,17 @@ attacker could inject an out-of-bounds access. 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 diff --git a/gcc/gimple.h b/gcc/gimple.h index 7e3086f5632..032365f3da2 100644 --- a/gcc/gimple.h +++ b/gcc/gimple.h @@ -3561,7 +3561,7 @@ gimple_call_set_nothrow (gcall *s, bool nothrow_p) /* 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; } diff --git a/gcc/testsuite/c-c++-common/analyzer/analyzer-verbosity-2a.c b/gcc/testsuite/c-c++-common/analyzer/analyzer-verbosity-2a.c index cf014b0a3c8..175d9c358b3 100644 --- a/gcc/testsuite/c-c++-common/analyzer/analyzer-verbosity-2a.c +++ b/gcc/testsuite/c-c++-common/analyzer/analyzer-verbosity-2a.c @@ -1,4 +1,4 @@ -/* { dg-additional-options "-fanalyzer-verbosity=2" } */ +/* { dg-additional-options "-fanalyzer-verbosity=2 -fno-exceptions" } */ typedef struct FILE FILE; diff --git a/gcc/testsuite/c-c++-common/analyzer/analyzer-verbosity-3a.c b/gcc/testsuite/c-c++-common/analyzer/analyzer-verbosity-3a.c index b0ece203f56..8d66c8f4234 100644 --- a/gcc/testsuite/c-c++-common/analyzer/analyzer-verbosity-3a.c +++ b/gcc/testsuite/c-c++-common/analyzer/analyzer-verbosity-3a.c @@ -1,4 +1,4 @@ -/* { dg-additional-options "-fanalyzer-verbosity=3" } */ +/* { dg-additional-options "-fanalyzer-verbosity=3 -fno-exceptions" } */ typedef struct FILE FILE; diff --git a/gcc/testsuite/c-c++-common/analyzer/attr-const-2.c b/gcc/testsuite/c-c++-common/analyzer/attr-const-2.c index ab79514acf8..5329a89141b 100644 --- a/gcc/testsuite/c-c++-common/analyzer/attr-const-2.c +++ b/gcc/testsuite/c-c++-common/analyzer/attr-const-2.c @@ -1,5 +1,5 @@ extern int const_p (int) __attribute__((const)); -extern void do_stuff (void); +extern void do_stuff (void) __attribute__((nothrow)); void test (int a) { diff --git a/gcc/testsuite/c-c++-common/analyzer/attr-malloc-4.c b/gcc/testsuite/c-c++-common/analyzer/attr-malloc-4.c index 1517667dfb2..1e2e1f949aa 100644 --- a/gcc/testsuite/c-c++-common/analyzer/attr-malloc-4.c +++ b/gcc/testsuite/c-c++-common/analyzer/attr-malloc-4.c @@ -2,7 +2,7 @@ struct foo; extern void foo_release (struct foo *) - __attribute__((nonnull)); + __attribute__((nonnull, nothrow)); extern struct foo *foo_acquire (void) __attribute__ ((malloc (foo_release))); diff --git a/gcc/testsuite/c-c++-common/analyzer/attr-malloc-5.c b/gcc/testsuite/c-c++-common/analyzer/attr-malloc-5.c index 7ff4e57fcfb..8b7ffc1a358 100644 --- a/gcc/testsuite/c-c++-common/analyzer/attr-malloc-5.c +++ b/gcc/testsuite/c-c++-common/analyzer/attr-malloc-5.c @@ -1,7 +1,7 @@ /* 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))); diff --git a/gcc/testsuite/c-c++-common/analyzer/attr-malloc-6.c b/gcc/testsuite/c-c++-common/analyzer/attr-malloc-6.c index 1665d4128f5..45ee406f990 100644 --- a/gcc/testsuite/c-c++-common/analyzer/attr-malloc-6.c +++ b/gcc/testsuite/c-c++-common/analyzer/attr-malloc-6.c @@ -1,4 +1,5 @@ /* Adapted from gcc.dg/Wmismatched-dealloc.c. */ +/* { dg-additional-options "-fno-exceptions" } */ #define A(...) __attribute__ ((malloc (__VA_ARGS__))) diff --git a/gcc/testsuite/c-c++-common/analyzer/attr-malloc-CVE-2019-19078-usb-leak.c b/gcc/testsuite/c-c++-common/analyzer/attr-malloc-CVE-2019-19078-usb-leak.c index 87ad42a3c32..fd5163034c0 100644 --- a/gcc/testsuite/c-c++-common/analyzer/attr-malloc-CVE-2019-19078-usb-leak.c +++ b/gcc/testsuite/c-c++-common/analyzer/attr-malloc-CVE-2019-19078-usb-leak.c @@ -1,6 +1,8 @@ /* 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; diff --git a/gcc/testsuite/c-c++-common/analyzer/attr-malloc-exception.c b/gcc/testsuite/c-c++-common/analyzer/attr-malloc-exception.c new file mode 100644 index 00000000000..9f5e2e871ce --- /dev/null +++ b/gcc/testsuite/c-c++-common/analyzer/attr-malloc-exception.c @@ -0,0 +1,17 @@ +/* { 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); +} diff --git a/gcc/testsuite/c-c++-common/analyzer/call-summaries-pr107158-2.c b/gcc/testsuite/c-c++-common/analyzer/call-summaries-pr107158-2.c index b395623ccca..a15ab65c476 100644 --- a/gcc/testsuite/c-c++-common/analyzer/call-summaries-pr107158-2.c +++ b/gcc/testsuite/c-c++-common/analyzer/call-summaries-pr107158-2.c @@ -1,4 +1,4 @@ -/* { 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 diff --git a/gcc/testsuite/c-c++-common/analyzer/call-summaries-pr107158.c b/gcc/testsuite/c-c++-common/analyzer/call-summaries-pr107158.c index de705836249..6fbbce41af0 100644 --- a/gcc/testsuite/c-c++-common/analyzer/call-summaries-pr107158.c +++ b/gcc/testsuite/c-c++-common/analyzer/call-summaries-pr107158.c @@ -1,4 +1,4 @@ -/* { 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)) }; diff --git a/gcc/testsuite/c-c++-common/analyzer/capacity-2.c b/gcc/testsuite/c-c++-common/analyzer/capacity-2.c index 7d2de4ed8ad..3846239c494 100644 --- a/gcc/testsuite/c-c++-common/analyzer/capacity-2.c +++ b/gcc/testsuite/c-c++-common/analyzer/capacity-2.c @@ -1,5 +1,6 @@ /* { dg-skip-if "" { powerpc*-*-aix* } } */ /* { dg-skip-if "requires hosted libstdc++ for stdlib size_t" { ! hostedlib } } */ +/* { dg-additional-options "-fno-exceptions" } */ #include #include "analyzer-decls.h" diff --git a/gcc/testsuite/c-c++-common/analyzer/coreutils-sum-pr108666.c b/gcc/testsuite/c-c++-common/analyzer/coreutils-sum-pr108666.c index c41b61dd081..95091e712ce 100644 --- a/gcc/testsuite/c-c++-common/analyzer/coreutils-sum-pr108666.c +++ b/gcc/testsuite/c-c++-common/analyzer/coreutils-sum-pr108666.c @@ -1,5 +1,7 @@ /* 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; diff --git a/gcc/testsuite/c-c++-common/analyzer/data-model-22.c b/gcc/testsuite/c-c++-common/analyzer/data-model-22.c index 8429b2f4dc6..65bf34665eb 100644 --- a/gcc/testsuite/c-c++-common/analyzer/data-model-22.c +++ b/gcc/testsuite/c-c++-common/analyzer/data-model-22.c @@ -1,3 +1,5 @@ +/* { dg-additional-options "-fno-exceptions" } */ + #include #include "analyzer-decls.h" diff --git a/gcc/testsuite/c-c++-common/analyzer/data-model-5d.c b/gcc/testsuite/c-c++-common/analyzer/data-model-5d.c index a86d5060c7a..bb459174837 100644 --- a/gcc/testsuite/c-c++-common/analyzer/data-model-5d.c +++ b/gcc/testsuite/c-c++-common/analyzer/data-model-5d.c @@ -1,4 +1,5 @@ /* { dg-skip-if "requires hosted libstdc++ for stdlib malloc" { ! hostedlib } } */ +/* { dg-additional-options "-fno-exceptions" } */ /* A toy re-implementation of CPython's object model. */ diff --git a/gcc/testsuite/c-c++-common/analyzer/deref-before-check-pr108455-git-pack-revindex.c b/gcc/testsuite/c-c++-common/analyzer/deref-before-check-pr108455-git-pack-revindex.c index 7431bd11c42..b7909971044 100644 --- a/gcc/testsuite/c-c++-common/analyzer/deref-before-check-pr108455-git-pack-revindex.c +++ b/gcc/testsuite/c-c++-common/analyzer/deref-before-check-pr108455-git-pack-revindex.c @@ -1,4 +1,5 @@ /* 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; diff --git a/gcc/testsuite/c-c++-common/analyzer/deref-before-check-pr108475-haproxy-tcpcheck.c b/gcc/testsuite/c-c++-common/analyzer/deref-before-check-pr108475-haproxy-tcpcheck.c index 7123cf5196b..a7f8049e9d2 100644 --- a/gcc/testsuite/c-c++-common/analyzer/deref-before-check-pr108475-haproxy-tcpcheck.c +++ b/gcc/testsuite/c-c++-common/analyzer/deref-before-check-pr108475-haproxy-tcpcheck.c @@ -1,6 +1,7 @@ /* 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; diff --git a/gcc/testsuite/c-c++-common/analyzer/edges-2.c b/gcc/testsuite/c-c++-common/analyzer/edges-2.c index 7e4543c24c3..df4bfaa1615 100644 --- a/gcc/testsuite/c-c++-common/analyzer/edges-2.c +++ b/gcc/testsuite/c-c++-common/analyzer/edges-2.c @@ -1,5 +1,7 @@ /* { dg-skip-if "requires hosted libstdc++ for stdlib malloc" { ! hostedlib } } */ +/* { dg-additional-options "-fno-exceptions" } */ + #include int foo (); diff --git a/gcc/testsuite/c-c++-common/analyzer/fd-2.c b/gcc/testsuite/c-c++-common/analyzer/fd-2.c index 10c9ecdb09d..b6b0a57ef30 100644 --- a/gcc/testsuite/c-c++-common/analyzer/fd-2.c +++ b/gcc/testsuite/c-c++-common/analyzer/fd-2.c @@ -1,3 +1,5 @@ +/* { dg-additional-options "-fno-exceptions" } */ + int open(const char *, int mode); void close(int fd); #define O_RDONLY 0 @@ -61,4 +63,4 @@ test_5 (const char *path, mode_t mode) 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 +} diff --git a/gcc/testsuite/c-c++-common/analyzer/fd-3.c b/gcc/testsuite/c-c++-common/analyzer/fd-3.c index 8e71b14f71b..4894b6478f9 100644 --- a/gcc/testsuite/c-c++-common/analyzer/fd-3.c +++ b/gcc/testsuite/c-c++-common/analyzer/fd-3.c @@ -1,3 +1,5 @@ +/* { dg-additional-options "-fno-exceptions" } */ + int open(const char *, int mode); void close(int fd); int write (int fd, void *buf, int nbytes); diff --git a/gcc/testsuite/c-c++-common/analyzer/fd-meaning.c b/gcc/testsuite/c-c++-common/analyzer/fd-meaning.c index 6a9ec921fd3..bd0d458a4ae 100644 --- a/gcc/testsuite/c-c++-common/analyzer/fd-meaning.c +++ b/gcc/testsuite/c-c++-common/analyzer/fd-meaning.c @@ -1,4 +1,5 @@ - /* { 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); @@ -34,4 +35,4 @@ void test_3 (const char* path) 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 +} diff --git a/gcc/testsuite/c-c++-common/analyzer/file-1.c b/gcc/testsuite/c-c++-common/analyzer/file-1.c index 316cbb3d868..e87cf73a126 100644 --- a/gcc/testsuite/c-c++-common/analyzer/file-1.c +++ b/gcc/testsuite/c-c++-common/analyzer/file-1.c @@ -1,3 +1,5 @@ +/* { dg-additional-options "-fno-exceptions" } */ + typedef struct FILE FILE; FILE* fopen (const char*, const char*); diff --git a/gcc/testsuite/c-c++-common/analyzer/file-3.c b/gcc/testsuite/c-c++-common/analyzer/file-3.c index 8f93a986deb..ca992bfb749 100644 --- a/gcc/testsuite/c-c++-common/analyzer/file-3.c +++ b/gcc/testsuite/c-c++-common/analyzer/file-3.c @@ -1,3 +1,5 @@ +/* { dg-additional-options "-fno-exceptions" } */ + typedef struct _IO_FILE FILE; extern struct _IO_FILE *stderr; diff --git a/gcc/testsuite/c-c++-common/analyzer/file-meaning-1.c b/gcc/testsuite/c-c++-common/analyzer/file-meaning-1.c index 66b72a72daf..c9aee5ec5aa 100644 --- a/gcc/testsuite/c-c++-common/analyzer/file-meaning-1.c +++ b/gcc/testsuite/c-c++-common/analyzer/file-meaning-1.c @@ -1,3 +1,4 @@ +/* { dg-additional-options "-fno-exceptions" } */ /* { dg-additional-options "-fanalyzer-verbose-state-changes" } */ typedef struct FILE FILE; diff --git a/gcc/testsuite/c-c++-common/analyzer/infinite-recursion.c b/gcc/testsuite/c-c++-common/analyzer/infinite-recursion.c index 6b7d25cfabe..bbbb68ebf3c 100644 --- a/gcc/testsuite/c-c++-common/analyzer/infinite-recursion.c +++ b/gcc/testsuite/c-c++-common/analyzer/infinite-recursion.c @@ -1,3 +1,5 @@ +/* { dg-additional-options "-fno-exceptions" } */ + extern void marker_A(void); extern void marker_B(void); extern void marker_C(void); diff --git a/gcc/testsuite/c-c++-common/analyzer/leak-3.c b/gcc/testsuite/c-c++-common/analyzer/leak-3.c index a386d887f33..19d7501e81c 100644 --- a/gcc/testsuite/c-c++-common/analyzer/leak-3.c +++ b/gcc/testsuite/c-c++-common/analyzer/leak-3.c @@ -1,4 +1,5 @@ /* { dg-skip-if "requires hosted libstdc++ for stdlib malloc" { ! hostedlib } } */ +/* { dg-additional-options "-fno-exceptions" } */ #include diff --git a/gcc/testsuite/c-c++-common/analyzer/malloc-dedupe-1.c b/gcc/testsuite/c-c++-common/analyzer/malloc-dedupe-1.c index 8653c67b08b..c296061ce7a 100644 --- a/gcc/testsuite/c-c++-common/analyzer/malloc-dedupe-1.c +++ b/gcc/testsuite/c-c++-common/analyzer/malloc-dedupe-1.c @@ -1,4 +1,5 @@ /* { dg-skip-if "requires hosted libstdc++ for stdlib malloc" { ! hostedlib } } */ +/* { dg-additional-options "-fno-exceptions" } */ #include diff --git a/gcc/testsuite/c-c++-common/analyzer/malloc-in-loop.c b/gcc/testsuite/c-c++-common/analyzer/malloc-in-loop.c index e7179f09a1d..0d868017ce6 100644 --- a/gcc/testsuite/c-c++-common/analyzer/malloc-in-loop.c +++ b/gcc/testsuite/c-c++-common/analyzer/malloc-in-loop.c @@ -1,4 +1,5 @@ /* { dg-skip-if "requires hosted libstdc++ for stdlib malloc" { ! hostedlib } } */ +/* { dg-additional-options "-fno-exceptions" } */ #include #include "../../gcc.dg/analyzer/analyzer-decls.h" diff --git a/gcc/testsuite/c-c++-common/analyzer/malloc-many-paths-3.c b/gcc/testsuite/c-c++-common/analyzer/malloc-many-paths-3.c index 6ee30f3c86a..5daa696d507 100644 --- a/gcc/testsuite/c-c++-common/analyzer/malloc-many-paths-3.c +++ b/gcc/testsuite/c-c++-common/analyzer/malloc-many-paths-3.c @@ -1,4 +1,5 @@ /* { dg-skip-if "requires hosted libstdc++ for stdlib malloc" { ! hostedlib } } */ +/* { dg-additional-options "-fno-exceptions" } */ #include diff --git a/gcc/testsuite/c-c++-common/analyzer/malloc-paths-5.c b/gcc/testsuite/c-c++-common/analyzer/malloc-paths-5.c index f03f97845c3..4a1870d788e 100644 --- a/gcc/testsuite/c-c++-common/analyzer/malloc-paths-5.c +++ b/gcc/testsuite/c-c++-common/analyzer/malloc-paths-5.c @@ -1,4 +1,5 @@ /* { dg-skip-if "requires hosted libstdc++ for stdlib malloc" { ! hostedlib } } */ +/* { dg-additional-options "-fno-exceptions" } */ #include #include diff --git a/gcc/testsuite/c-c++-common/analyzer/malloc-paths-7.c b/gcc/testsuite/c-c++-common/analyzer/malloc-paths-7.c index 766bbe7d99a..12b93b272e4 100644 --- a/gcc/testsuite/c-c++-common/analyzer/malloc-paths-7.c +++ b/gcc/testsuite/c-c++-common/analyzer/malloc-paths-7.c @@ -1,4 +1,5 @@ /* { dg-skip-if "requires hosted libstdc++ for stdlib malloc" { ! hostedlib } } */ +/* { dg-additional-options "-fno-exceptions" } */ #include diff --git a/gcc/testsuite/c-c++-common/analyzer/malloc-paths-8.c b/gcc/testsuite/c-c++-common/analyzer/malloc-paths-8.c index 77e3e02b80d..8af84ed79ec 100644 --- a/gcc/testsuite/c-c++-common/analyzer/malloc-paths-8.c +++ b/gcc/testsuite/c-c++-common/analyzer/malloc-paths-8.c @@ -1,3 +1,4 @@ +/* { dg-additional-options "-fno-exceptions" } */ /* { dg-additional-options "-fanalyzer-transitivity" } */ /* { dg-skip-if "requires hosted libstdc++ for stdlib malloc" { ! hostedlib } } */ diff --git a/gcc/testsuite/c-c++-common/analyzer/malloc-vs-local-1a.c b/gcc/testsuite/c-c++-common/analyzer/malloc-vs-local-1a.c index 4e408339d67..ffb0ffe7d8b 100644 --- a/gcc/testsuite/c-c++-common/analyzer/malloc-vs-local-1a.c +++ b/gcc/testsuite/c-c++-common/analyzer/malloc-vs-local-1a.c @@ -1,3 +1,4 @@ +/* { dg-additional-options "-fno-exceptions" } */ /* { dg-additional-options "-fno-analyzer-call-summaries -fanalyzer-transitivity" } */ /* { dg-skip-if "requires hosted libstdc++ for stdlib malloc" { ! hostedlib } } */ diff --git a/gcc/testsuite/c-c++-common/analyzer/malloc-vs-local-2.c b/gcc/testsuite/c-c++-common/analyzer/malloc-vs-local-2.c index 36ec5109ac7..052b4014277 100644 --- a/gcc/testsuite/c-c++-common/analyzer/malloc-vs-local-2.c +++ b/gcc/testsuite/c-c++-common/analyzer/malloc-vs-local-2.c @@ -1,4 +1,5 @@ /* { dg-skip-if "requires hosted libstdc++ for stdlib malloc" { ! hostedlib } } */ +/* { dg-additional-options "-fno-exceptions" } */ #include #include "analyzer-decls.h" diff --git a/gcc/testsuite/c-c++-common/analyzer/malloc-vs-local-3.c b/gcc/testsuite/c-c++-common/analyzer/malloc-vs-local-3.c index 70b3edd38f5..258706c62d0 100644 --- a/gcc/testsuite/c-c++-common/analyzer/malloc-vs-local-3.c +++ b/gcc/testsuite/c-c++-common/analyzer/malloc-vs-local-3.c @@ -1,4 +1,5 @@ /* { dg-skip-if "requires hosted libstdc++ for stdlib malloc" { ! hostedlib } } */ +/* { dg-additional-options "-fno-exceptions" } */ #include #include "analyzer-decls.h" diff --git a/gcc/testsuite/c-c++-common/analyzer/paths-7.c b/gcc/testsuite/c-c++-common/analyzer/paths-7.c index 2743de5cd8c..f7c5cbf7829 100644 --- a/gcc/testsuite/c-c++-common/analyzer/paths-7.c +++ b/gcc/testsuite/c-c++-common/analyzer/paths-7.c @@ -1,4 +1,5 @@ /* { dg-skip-if "requires hosted libstdc++ for stdlib malloc" { ! hostedlib } } */ +/* { dg-additional-options "-fno-exceptions" } */ #include #include "analyzer-decls.h" diff --git a/gcc/testsuite/c-c++-common/analyzer/pr110830.c b/gcc/testsuite/c-c++-common/analyzer/pr110830.c index f5a39b7a067..ebfd38d8aaf 100644 --- a/gcc/testsuite/c-c++-common/analyzer/pr110830.c +++ b/gcc/testsuite/c-c++-common/analyzer/pr110830.c @@ -1,3 +1,5 @@ +/* { dg-additional-options "-fno-exceptions" } */ + typedef __SIZE_TYPE__ size_t; void free(void *); diff --git a/gcc/testsuite/c-c++-common/analyzer/pr93032-mztools-simplified.c b/gcc/testsuite/c-c++-common/analyzer/pr93032-mztools-simplified.c index 4a08f0f1f50..e389e438ea5 100644 --- a/gcc/testsuite/c-c++-common/analyzer/pr93032-mztools-simplified.c +++ b/gcc/testsuite/c-c++-common/analyzer/pr93032-mztools-simplified.c @@ -1,4 +1,5 @@ /* { dg-do "compile" } */ +/* { dg-additional-options "-fno-exceptions" } */ /* Minimal replacement of system headers. */ #define NULL ((void *) 0) diff --git a/gcc/testsuite/c-c++-common/analyzer/pr93355-localealias-feasibility-3.c b/gcc/testsuite/c-c++-common/analyzer/pr93355-localealias-feasibility-3.c index 50d338855bc..19a3023de2b 100644 --- a/gcc/testsuite/c-c++-common/analyzer/pr93355-localealias-feasibility-3.c +++ b/gcc/testsuite/c-c++-common/analyzer/pr93355-localealias-feasibility-3.c @@ -3,6 +3,7 @@ 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. diff --git a/gcc/testsuite/c-c++-common/analyzer/pr93355-localealias-simplified.c b/gcc/testsuite/c-c++-common/analyzer/pr93355-localealias-simplified.c index 6f65add346b..4551746869d 100644 --- a/gcc/testsuite/c-c++-common/analyzer/pr93355-localealias-simplified.c +++ b/gcc/testsuite/c-c++-common/analyzer/pr93355-localealias-simplified.c @@ -3,6 +3,7 @@ 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. diff --git a/gcc/testsuite/c-c++-common/analyzer/pr96650-1-trans.c b/gcc/testsuite/c-c++-common/analyzer/pr96650-1-trans.c index b20630bb806..fb194adde0e 100644 --- a/gcc/testsuite/c-c++-common/analyzer/pr96650-1-trans.c +++ b/gcc/testsuite/c-c++-common/analyzer/pr96650-1-trans.c @@ -1,4 +1,5 @@ /* { dg-additional-options "-O2 -fanalyzer-transitivity" } */ +/* { dg-additional-options "-fno-exceptions" } */ int *wf; diff --git a/gcc/testsuite/c-c++-common/analyzer/pr97072.c b/gcc/testsuite/c-c++-common/analyzer/pr97072.c index 40241248884..82411a11534 100644 --- a/gcc/testsuite/c-c++-common/analyzer/pr97072.c +++ b/gcc/testsuite/c-c++-common/analyzer/pr97072.c @@ -1,4 +1,4 @@ -void unknown_fn_1 (void *); +void unknown_fn_1 (void *) __attribute__((nothrow)); void test_1 (int co, int y) { diff --git a/gcc/testsuite/c-c++-common/analyzer/pr98575-1.c b/gcc/testsuite/c-c++-common/analyzer/pr98575-1.c index 6472e762f0c..b8ddf77ce54 100644 --- a/gcc/testsuite/c-c++-common/analyzer/pr98575-1.c +++ b/gcc/testsuite/c-c++-common/analyzer/pr98575-1.c @@ -4,7 +4,7 @@ void **g; -extern void unknown_fn (void); +extern void unknown_fn (void) __attribute__((nothrow)); /* Without a call to unknown_fn. */ diff --git a/gcc/testsuite/c-c++-common/analyzer/pr99716-1.c b/gcc/testsuite/c-c++-common/analyzer/pr99716-1.c index 41be8cab048..60f3a598224 100644 --- a/gcc/testsuite/c-c++-common/analyzer/pr99716-1.c +++ b/gcc/testsuite/c-c++-common/analyzer/pr99716-1.c @@ -1,3 +1,5 @@ +/* { dg-additional-options "-fno-exceptions" } */ + typedef struct FILE FILE; FILE* fopen (const char*, const char*); diff --git a/gcc/testsuite/c-c++-common/analyzer/pr99716-2.c b/gcc/testsuite/c-c++-common/analyzer/pr99716-2.c index ef7cc5fb494..caf5c863ecc 100644 --- a/gcc/testsuite/c-c++-common/analyzer/pr99716-2.c +++ b/gcc/testsuite/c-c++-common/analyzer/pr99716-2.c @@ -1,4 +1,5 @@ /* { 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 diff --git a/gcc/testsuite/c-c++-common/analyzer/pr99716-3.c b/gcc/testsuite/c-c++-common/analyzer/pr99716-3.c index 414d57e3a72..98f656f4c68 100644 --- a/gcc/testsuite/c-c++-common/analyzer/pr99716-3.c +++ b/gcc/testsuite/c-c++-common/analyzer/pr99716-3.c @@ -1,4 +1,5 @@ /* { dg-skip-if "requires hosted libstdc++ for stdlib malloc" { ! hostedlib } } */ +/* { dg-additional-options "-fno-exceptions" } */ #include diff --git a/gcc/testsuite/c-c++-common/analyzer/pragma-2.c b/gcc/testsuite/c-c++-common/analyzer/pragma-2.c index bd96a253854..f3098760fb0 100644 --- a/gcc/testsuite/c-c++-common/analyzer/pragma-2.c +++ b/gcc/testsuite/c-c++-common/analyzer/pragma-2.c @@ -1,6 +1,7 @@ /* { 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 diff --git a/gcc/testsuite/c-c++-common/analyzer/rhbz1878600.c b/gcc/testsuite/c-c++-common/analyzer/rhbz1878600.c index 9f6ccb6dc27..960604481b7 100644 --- a/gcc/testsuite/c-c++-common/analyzer/rhbz1878600.c +++ b/gcc/testsuite/c-c++-common/analyzer/rhbz1878600.c @@ -1,3 +1,5 @@ +/* { dg-additional-options "-fno-exceptions" } */ + #include #define INI_MAX_LINE 200 diff --git a/gcc/testsuite/c-c++-common/analyzer/strndup-1.c b/gcc/testsuite/c-c++-common/analyzer/strndup-1.c index 3f90afe2ebb..915f220dcdf 100644 --- a/gcc/testsuite/c-c++-common/analyzer/strndup-1.c +++ b/gcc/testsuite/c-c++-common/analyzer/strndup-1.c @@ -1,6 +1,7 @@ /* { 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 #include diff --git a/gcc/testsuite/c-c++-common/analyzer/write-to-string-literal-4-disabled.c b/gcc/testsuite/c-c++-common/analyzer/write-to-string-literal-4-disabled.c index 868c39368e1..1aa4159e514 100644 --- a/gcc/testsuite/c-c++-common/analyzer/write-to-string-literal-4-disabled.c +++ b/gcc/testsuite/c-c++-common/analyzer/write-to-string-literal-4-disabled.c @@ -2,6 +2,7 @@ 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; diff --git a/gcc/testsuite/c-c++-common/analyzer/write-to-string-literal-4.c b/gcc/testsuite/c-c++-common/analyzer/write-to-string-literal-4.c index 971e8f38574..b60fba013ed 100644 --- a/gcc/testsuite/c-c++-common/analyzer/write-to-string-literal-4.c +++ b/gcc/testsuite/c-c++-common/analyzer/write-to-string-literal-4.c @@ -1,3 +1,5 @@ +/* { dg-additional-options "-fno-exceptions" } */ + typedef __SIZE_TYPE__ size_t; int getrandom (void *__buffer, size_t __length, /* { dg-line getrandom } */ diff --git a/gcc/testsuite/c-c++-common/analyzer/write-to-string-literal-5.c b/gcc/testsuite/c-c++-common/analyzer/write-to-string-literal-5.c index 2ecad8c2f0b..78b2204e6f3 100644 --- a/gcc/testsuite/c-c++-common/analyzer/write-to-string-literal-5.c +++ b/gcc/testsuite/c-c++-common/analyzer/write-to-string-literal-5.c @@ -2,6 +2,7 @@ notes) works. */ /* { dg-additional-options "-fanalyzer-show-duplicate-count" } */ +/* { dg-additional-options "-fno-exceptions" } */ #include "../../gcc.dg/analyzer/analyzer-decls.h" diff --git a/gcc/testsuite/c-c++-common/analyzer/zlib-5.c b/gcc/testsuite/c-c++-common/analyzer/zlib-5.c index 1e3746d91fc..fa82e434f0e 100644 --- a/gcc/testsuite/c-c++-common/analyzer/zlib-5.c +++ b/gcc/testsuite/c-c++-common/analyzer/zlib-5.c @@ -1,4 +1,5 @@ /* { dg-additional-options "-O3" } */ +/* { dg-additional-options "-fno-exceptions" } */ #include "analyzer-decls.h" diff --git a/gcc/testsuite/g++.dg/analyzer/exception-could-throw-1.C b/gcc/testsuite/g++.dg/analyzer/exception-could-throw-1.C new file mode 100644 index 00000000000..4671e622df2 --- /dev/null +++ b/gcc/testsuite/g++.dg/analyzer/exception-could-throw-1.C @@ -0,0 +1,37 @@ +#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; +} diff --git a/gcc/testsuite/g++.dg/analyzer/exception-could-throw-2.C b/gcc/testsuite/g++.dg/analyzer/exception-could-throw-2.C new file mode 100644 index 00000000000..6572113788e --- /dev/null +++ b/gcc/testsuite/g++.dg/analyzer/exception-could-throw-2.C @@ -0,0 +1,32 @@ +#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; +} diff --git a/gcc/testsuite/g++.dg/analyzer/exception-dynamic-spec.C b/gcc/testsuite/g++.dg/analyzer/exception-dynamic-spec.C new file mode 100644 index 00000000000..98472037770 --- /dev/null +++ b/gcc/testsuite/g++.dg/analyzer/exception-dynamic-spec.C @@ -0,0 +1,62 @@ +// 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); +} diff --git a/gcc/testsuite/g++.dg/analyzer/exception-leak-1.C b/gcc/testsuite/g++.dg/analyzer/exception-leak-1.C new file mode 100644 index 00000000000..25467ea85d0 --- /dev/null +++ b/gcc/testsuite/g++.dg/analyzer/exception-leak-1.C @@ -0,0 +1,8 @@ +#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'" } +} diff --git a/gcc/testsuite/g++.dg/analyzer/exception-leak-2.C b/gcc/testsuite/g++.dg/analyzer/exception-leak-2.C new file mode 100644 index 00000000000..0e0eef82841 --- /dev/null +++ b/gcc/testsuite/g++.dg/analyzer/exception-leak-2.C @@ -0,0 +1,18 @@ +#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; +} diff --git a/gcc/testsuite/g++.dg/analyzer/exception-leak-3.C b/gcc/testsuite/g++.dg/analyzer/exception-leak-3.C new file mode 100644 index 00000000000..3684d661aa5 --- /dev/null +++ b/gcc/testsuite/g++.dg/analyzer/exception-leak-3.C @@ -0,0 +1,12 @@ +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; +} diff --git a/gcc/testsuite/g++.dg/analyzer/exception-leak-4.C b/gcc/testsuite/g++.dg/analyzer/exception-leak-4.C new file mode 100644 index 00000000000..7280d3233fd --- /dev/null +++ b/gcc/testsuite/g++.dg/analyzer/exception-leak-4.C @@ -0,0 +1,16 @@ +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; +} diff --git a/gcc/testsuite/g++.dg/analyzer/exception-leak-5.C b/gcc/testsuite/g++.dg/analyzer/exception-leak-5.C new file mode 100644 index 00000000000..d99e53ca764 --- /dev/null +++ b/gcc/testsuite/g++.dg/analyzer/exception-leak-5.C @@ -0,0 +1,51 @@ +/* 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; +} diff --git a/gcc/testsuite/g++.dg/analyzer/exception-leak-6.C b/gcc/testsuite/g++.dg/analyzer/exception-leak-6.C new file mode 100644 index 00000000000..cb7023047bb --- /dev/null +++ b/gcc/testsuite/g++.dg/analyzer/exception-leak-6.C @@ -0,0 +1,22 @@ +#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; +} diff --git a/gcc/testsuite/g++.dg/analyzer/exception-nothrow.C b/gcc/testsuite/g++.dg/analyzer/exception-nothrow.C new file mode 100644 index 00000000000..96257487da6 --- /dev/null +++ b/gcc/testsuite/g++.dg/analyzer/exception-nothrow.C @@ -0,0 +1,26 @@ +#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; +} diff --git a/gcc/testsuite/g++.dg/analyzer/exception-path-1.C b/gcc/testsuite/g++.dg/analyzer/exception-path-1.C new file mode 100644 index 00000000000..486ca193892 --- /dev/null +++ b/gcc/testsuite/g++.dg/analyzer/exception-path-1.C @@ -0,0 +1,34 @@ +/* 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; +} diff --git a/gcc/testsuite/g++.dg/analyzer/exception-path-catch-all-1.C b/gcc/testsuite/g++.dg/analyzer/exception-path-catch-all-1.C new file mode 100644 index 00000000000..8aa98143a89 --- /dev/null +++ b/gcc/testsuite/g++.dg/analyzer/exception-path-catch-all-1.C @@ -0,0 +1,16 @@ +#include "../../gcc.dg/analyzer/analyzer-decls.h" + +int test () +{ + try + { + throw 42; + } + catch (...) + { + __analyzer_dump_path (); + return -1; + } + __analyzer_dump_path (); + return 0; +} diff --git a/gcc/testsuite/g++.dg/analyzer/exception-path-catch-all-2.C b/gcc/testsuite/g++.dg/analyzer/exception-path-catch-all-2.C new file mode 100644 index 00000000000..d185056bbb5 --- /dev/null +++ b/gcc/testsuite/g++.dg/analyzer/exception-path-catch-all-2.C @@ -0,0 +1,22 @@ +#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; +} diff --git a/gcc/testsuite/g++.dg/analyzer/exception-path-unwind-multiple-2.C b/gcc/testsuite/g++.dg/analyzer/exception-path-unwind-multiple-2.C new file mode 100644 index 00000000000..2608f17079d --- /dev/null +++ b/gcc/testsuite/g++.dg/analyzer/exception-path-unwind-multiple-2.C @@ -0,0 +1,55 @@ +/* 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; +} diff --git a/gcc/testsuite/g++.dg/analyzer/exception-path-unwind-multiple.C b/gcc/testsuite/g++.dg/analyzer/exception-path-unwind-multiple.C new file mode 100644 index 00000000000..a52312ad8c6 --- /dev/null +++ b/gcc/testsuite/g++.dg/analyzer/exception-path-unwind-multiple.C @@ -0,0 +1,48 @@ +/* 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 diff --git a/gcc/testsuite/g++.dg/analyzer/exception-path-unwind-single.C b/gcc/testsuite/g++.dg/analyzer/exception-path-unwind-single.C new file mode 100644 index 00000000000..055fe271e5e --- /dev/null +++ b/gcc/testsuite/g++.dg/analyzer/exception-path-unwind-single.C @@ -0,0 +1,43 @@ +/* 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 diff --git a/gcc/testsuite/g++.dg/analyzer/exception-path-with-cleanups.C b/gcc/testsuite/g++.dg/analyzer/exception-path-with-cleanups.C new file mode 100644 index 00000000000..fc18a21eea9 --- /dev/null +++ b/gcc/testsuite/g++.dg/analyzer/exception-path-with-cleanups.C @@ -0,0 +1,27 @@ +#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; +} diff --git a/gcc/testsuite/g++.dg/analyzer/exception-rethrow-1.C b/gcc/testsuite/g++.dg/analyzer/exception-rethrow-1.C new file mode 100644 index 00000000000..10484fd3f47 --- /dev/null +++ b/gcc/testsuite/g++.dg/analyzer/exception-rethrow-1.C @@ -0,0 +1,13 @@ +#include "../../gcc.dg/analyzer/analyzer-decls.h" + +int test () +{ + try + { + throw 42; + } + catch (...) + { + throw; + } +} diff --git a/gcc/testsuite/g++.dg/analyzer/exception-rethrow-2.C b/gcc/testsuite/g++.dg/analyzer/exception-rethrow-2.C new file mode 100644 index 00000000000..22242a324e4 --- /dev/null +++ b/gcc/testsuite/g++.dg/analyzer/exception-rethrow-2.C @@ -0,0 +1,25 @@ +#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; +} diff --git a/gcc/testsuite/g++.dg/analyzer/exception-stack-1.C b/gcc/testsuite/g++.dg/analyzer/exception-stack-1.C new file mode 100644 index 00000000000..c6aa4154844 --- /dev/null +++ b/gcc/testsuite/g++.dg/analyzer/exception-stack-1.C @@ -0,0 +1,35 @@ +#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" } +} diff --git a/gcc/testsuite/g++.dg/analyzer/exception-stack-2.C b/gcc/testsuite/g++.dg/analyzer/exception-stack-2.C new file mode 100644 index 00000000000..5e6a3c01aad --- /dev/null +++ b/gcc/testsuite/g++.dg/analyzer/exception-stack-2.C @@ -0,0 +1,44 @@ +#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" } +} diff --git a/gcc/testsuite/g++.dg/analyzer/exception-subclass-1.C b/gcc/testsuite/g++.dg/analyzer/exception-subclass-1.C new file mode 100644 index 00000000000..79df33021dd --- /dev/null +++ b/gcc/testsuite/g++.dg/analyzer/exception-subclass-1.C @@ -0,0 +1,21 @@ +#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; +} diff --git a/gcc/testsuite/g++.dg/analyzer/exception-subclass-2.C b/gcc/testsuite/g++.dg/analyzer/exception-subclass-2.C new file mode 100644 index 00000000000..e9fb61753bb --- /dev/null +++ b/gcc/testsuite/g++.dg/analyzer/exception-subclass-2.C @@ -0,0 +1,25 @@ +#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 *-*-* } } +} diff --git a/gcc/testsuite/g++.dg/analyzer/exception-value-1.C b/gcc/testsuite/g++.dg/analyzer/exception-value-1.C new file mode 100644 index 00000000000..0d06dd87edc --- /dev/null +++ b/gcc/testsuite/g++.dg/analyzer/exception-value-1.C @@ -0,0 +1,20 @@ +/* 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; +} diff --git a/gcc/testsuite/g++.dg/analyzer/exception-value-2.C b/gcc/testsuite/g++.dg/analyzer/exception-value-2.C new file mode 100644 index 00000000000..ef9dd46b076 --- /dev/null +++ b/gcc/testsuite/g++.dg/analyzer/exception-value-2.C @@ -0,0 +1,36 @@ +#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" } +} diff --git a/gcc/testsuite/g++.dg/analyzer/fno-exception.C b/gcc/testsuite/g++.dg/analyzer/fno-exception.C new file mode 100644 index 00000000000..2ec4e06e231 --- /dev/null +++ b/gcc/testsuite/g++.dg/analyzer/fno-exception.C @@ -0,0 +1,12 @@ +/* { 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; +} diff --git a/gcc/testsuite/g++.dg/analyzer/pr94028.C b/gcc/testsuite/g++.dg/analyzer/pr94028.C index 0573d309ba2..53bfa2953ec 100644 --- a/gcc/testsuite/g++.dg/analyzer/pr94028.C +++ b/gcc/testsuite/g++.dg/analyzer/pr94028.C @@ -19,7 +19,7 @@ struct j throw() #endif { - return calloc (b, sizeof (int)); // { dg-bogus "leak" "" { xfail c++98_only } } + return calloc (b, sizeof (int)); } j (B *, int) { diff --git a/gcc/testsuite/g++.dg/analyzer/std-unexpected.C b/gcc/testsuite/g++.dg/analyzer/std-unexpected.C new file mode 100644 index 00000000000..4eb2672cb15 --- /dev/null +++ b/gcc/testsuite/g++.dg/analyzer/std-unexpected.C @@ -0,0 +1,9 @@ +// { dg-require-effective-target c++14_down } +// { dg-additional-options "-Wno-deprecated-declarations" } + +#include + +void test_1 () +{ + std::unexpected (); +} diff --git a/gcc/testsuite/g++.dg/coroutines/pr105287.C b/gcc/testsuite/g++.dg/coroutines/pr105287.C index c54d1fd5b84..0436572624d 100644 --- a/gcc/testsuite/g++.dg/coroutines/pr105287.C +++ b/gcc/testsuite/g++.dg/coroutines/pr105287.C @@ -1,5 +1,5 @@ // { dg-additional-options "-fanalyzer" } -// { dg-excess-errors "lots of analyzer output, but no ICE" } + namespace std { template struct coroutine_traits : _Result {}; template struct coroutine_handle {