]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
analyzer: initial implementation of exception handling [PR97111]
authorDavid Malcolm <dmalcolm@redhat.com>
Mon, 28 Apr 2025 22:21:24 +0000 (18:21 -0400)
committerDavid Malcolm <dmalcolm@redhat.com>
Mon, 28 Apr 2025 22:21:24 +0000 (18:21 -0400)
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 <const eh_dispatch_cfg_superedge *>::test): New.
(class eh_dispatch_try_cfg_superedge): New.
(is_a_helper <const eh_dispatch_try_cfg_superedge *>::test): New.
(class eh_dispatch_allowed_cfg_superedge): New.
(is_a_helper <const eh_dispatch_allowed_cfg_superedge *>::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 <dmalcolm@redhat.com>
103 files changed:
gcc/analyzer/analyzer.cc
gcc/analyzer/analyzer.opt
gcc/analyzer/analyzer.opt.urls
gcc/analyzer/call-info.cc
gcc/analyzer/call-info.h
gcc/analyzer/checker-event.cc
gcc/analyzer/checker-event.h
gcc/analyzer/common.h
gcc/analyzer/diagnostic-manager.cc
gcc/analyzer/diagnostic-manager.h
gcc/analyzer/engine.cc
gcc/analyzer/exploded-graph.h
gcc/analyzer/kf-lang-cp.cc
gcc/analyzer/kf.cc
gcc/analyzer/region-model.cc
gcc/analyzer/region-model.h
gcc/analyzer/supergraph.cc
gcc/analyzer/supergraph.h
gcc/analyzer/svalue.cc
gcc/analyzer/svalue.h
gcc/doc/invoke.texi
gcc/gimple.h
gcc/testsuite/c-c++-common/analyzer/analyzer-verbosity-2a.c
gcc/testsuite/c-c++-common/analyzer/analyzer-verbosity-3a.c
gcc/testsuite/c-c++-common/analyzer/attr-const-2.c
gcc/testsuite/c-c++-common/analyzer/attr-malloc-4.c
gcc/testsuite/c-c++-common/analyzer/attr-malloc-5.c
gcc/testsuite/c-c++-common/analyzer/attr-malloc-6.c
gcc/testsuite/c-c++-common/analyzer/attr-malloc-CVE-2019-19078-usb-leak.c
gcc/testsuite/c-c++-common/analyzer/attr-malloc-exception.c [new file with mode: 0644]
gcc/testsuite/c-c++-common/analyzer/call-summaries-pr107158-2.c
gcc/testsuite/c-c++-common/analyzer/call-summaries-pr107158.c
gcc/testsuite/c-c++-common/analyzer/capacity-2.c
gcc/testsuite/c-c++-common/analyzer/coreutils-sum-pr108666.c
gcc/testsuite/c-c++-common/analyzer/data-model-22.c
gcc/testsuite/c-c++-common/analyzer/data-model-5d.c
gcc/testsuite/c-c++-common/analyzer/deref-before-check-pr108455-git-pack-revindex.c
gcc/testsuite/c-c++-common/analyzer/deref-before-check-pr108475-haproxy-tcpcheck.c
gcc/testsuite/c-c++-common/analyzer/edges-2.c
gcc/testsuite/c-c++-common/analyzer/fd-2.c
gcc/testsuite/c-c++-common/analyzer/fd-3.c
gcc/testsuite/c-c++-common/analyzer/fd-meaning.c
gcc/testsuite/c-c++-common/analyzer/file-1.c
gcc/testsuite/c-c++-common/analyzer/file-3.c
gcc/testsuite/c-c++-common/analyzer/file-meaning-1.c
gcc/testsuite/c-c++-common/analyzer/infinite-recursion.c
gcc/testsuite/c-c++-common/analyzer/leak-3.c
gcc/testsuite/c-c++-common/analyzer/malloc-dedupe-1.c
gcc/testsuite/c-c++-common/analyzer/malloc-in-loop.c
gcc/testsuite/c-c++-common/analyzer/malloc-many-paths-3.c
gcc/testsuite/c-c++-common/analyzer/malloc-paths-5.c
gcc/testsuite/c-c++-common/analyzer/malloc-paths-7.c
gcc/testsuite/c-c++-common/analyzer/malloc-paths-8.c
gcc/testsuite/c-c++-common/analyzer/malloc-vs-local-1a.c
gcc/testsuite/c-c++-common/analyzer/malloc-vs-local-2.c
gcc/testsuite/c-c++-common/analyzer/malloc-vs-local-3.c
gcc/testsuite/c-c++-common/analyzer/paths-7.c
gcc/testsuite/c-c++-common/analyzer/pr110830.c
gcc/testsuite/c-c++-common/analyzer/pr93032-mztools-simplified.c
gcc/testsuite/c-c++-common/analyzer/pr93355-localealias-feasibility-3.c
gcc/testsuite/c-c++-common/analyzer/pr93355-localealias-simplified.c
gcc/testsuite/c-c++-common/analyzer/pr96650-1-trans.c
gcc/testsuite/c-c++-common/analyzer/pr97072.c
gcc/testsuite/c-c++-common/analyzer/pr98575-1.c
gcc/testsuite/c-c++-common/analyzer/pr99716-1.c
gcc/testsuite/c-c++-common/analyzer/pr99716-2.c
gcc/testsuite/c-c++-common/analyzer/pr99716-3.c
gcc/testsuite/c-c++-common/analyzer/pragma-2.c
gcc/testsuite/c-c++-common/analyzer/rhbz1878600.c
gcc/testsuite/c-c++-common/analyzer/strndup-1.c
gcc/testsuite/c-c++-common/analyzer/write-to-string-literal-4-disabled.c
gcc/testsuite/c-c++-common/analyzer/write-to-string-literal-4.c
gcc/testsuite/c-c++-common/analyzer/write-to-string-literal-5.c
gcc/testsuite/c-c++-common/analyzer/zlib-5.c
gcc/testsuite/g++.dg/analyzer/exception-could-throw-1.C [new file with mode: 0644]
gcc/testsuite/g++.dg/analyzer/exception-could-throw-2.C [new file with mode: 0644]
gcc/testsuite/g++.dg/analyzer/exception-dynamic-spec.C [new file with mode: 0644]
gcc/testsuite/g++.dg/analyzer/exception-leak-1.C [new file with mode: 0644]
gcc/testsuite/g++.dg/analyzer/exception-leak-2.C [new file with mode: 0644]
gcc/testsuite/g++.dg/analyzer/exception-leak-3.C [new file with mode: 0644]
gcc/testsuite/g++.dg/analyzer/exception-leak-4.C [new file with mode: 0644]
gcc/testsuite/g++.dg/analyzer/exception-leak-5.C [new file with mode: 0644]
gcc/testsuite/g++.dg/analyzer/exception-leak-6.C [new file with mode: 0644]
gcc/testsuite/g++.dg/analyzer/exception-nothrow.C [new file with mode: 0644]
gcc/testsuite/g++.dg/analyzer/exception-path-1.C [new file with mode: 0644]
gcc/testsuite/g++.dg/analyzer/exception-path-catch-all-1.C [new file with mode: 0644]
gcc/testsuite/g++.dg/analyzer/exception-path-catch-all-2.C [new file with mode: 0644]
gcc/testsuite/g++.dg/analyzer/exception-path-unwind-multiple-2.C [new file with mode: 0644]
gcc/testsuite/g++.dg/analyzer/exception-path-unwind-multiple.C [new file with mode: 0644]
gcc/testsuite/g++.dg/analyzer/exception-path-unwind-single.C [new file with mode: 0644]
gcc/testsuite/g++.dg/analyzer/exception-path-with-cleanups.C [new file with mode: 0644]
gcc/testsuite/g++.dg/analyzer/exception-rethrow-1.C [new file with mode: 0644]
gcc/testsuite/g++.dg/analyzer/exception-rethrow-2.C [new file with mode: 0644]
gcc/testsuite/g++.dg/analyzer/exception-stack-1.C [new file with mode: 0644]
gcc/testsuite/g++.dg/analyzer/exception-stack-2.C [new file with mode: 0644]
gcc/testsuite/g++.dg/analyzer/exception-subclass-1.C [new file with mode: 0644]
gcc/testsuite/g++.dg/analyzer/exception-subclass-2.C [new file with mode: 0644]
gcc/testsuite/g++.dg/analyzer/exception-value-1.C [new file with mode: 0644]
gcc/testsuite/g++.dg/analyzer/exception-value-2.C [new file with mode: 0644]
gcc/testsuite/g++.dg/analyzer/fno-exception.C [new file with mode: 0644]
gcc/testsuite/g++.dg/analyzer/pr94028.C
gcc/testsuite/g++.dg/analyzer/std-unexpected.C [new file with mode: 0644]
gcc/testsuite/g++.dg/coroutines/pr105287.C

index c2814d2f19822680a2aae5e67f1234ac4fa91741..56cb370f147e93acad966287f5c6c9a6189c281a 100644 (file)
@@ -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).  */
index d5f82c6471a6f91ca3a536467f20cc682c0ec7b0..2ca905854bda612ce394ae8d2c10ef13d412f7d6 100644 (file)
@@ -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.
index 18a0d6926de7fb80881bd2f33a21f877a5b694f5..e76e6e5571d96d7191a68392eae738b3d9720939 100644 (file)
@@ -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)
 
index 0e48c1606cbe1fa37893c02f94d1fc1ec7ea8e51..9a698efdcafd038cb6edcc7ed20776a78ff29c55 100644 (file)
@@ -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.  */
index f61b9be30fe17ed21b2e5477b6edfa2dcb248830..6548d861bf2fa750bc18a46b66bc785c3aa1b3c0 100644 (file)
@@ -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; }
index 914a6c9dbbbfd1259b8edfb7b46eb92ccb28cf92..958cdbf394d0e259a69594eb727fa0cfa7a2278d 100644 (file)
@@ -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
index eb69e0f78938468c74c101e665ff1bb0b7017e6c..f3ab899841831e09487596997ad91b49b535e410 100644 (file)
@@ -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
index 3d02b6242b11199675ffe24ef82054b7a83aa883..cb030043d8ad6800d8761ac917bfa3f1aff98333 100644 (file)
@@ -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);
 
index 161ae62051a3d99285be768204aa7c9c610cb610..7575b16c86cd46015f7838f74bb298883dfb99ce 100644 (file)
@@ -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<catch_cfg_edge_event>
+                  (eedge,
+                   event_loc_info (dst_point.get_supernode ()->get_start_location (),
+                                   dst_point.get_fndecl (),
+                                   dst_stack_depth),
+                   type));
+               return;
+             }
+           else
+             {
+               /* We have the "uncaught exception" sedge, from eh_dispatch
+                  to a block containing resx.
+                  Don't add any events for this, so that we can consolidate
+                  adjacent stack unwinding events.  */
+               return;
+             }
+         }
+
        emission_path->add_event
          (std::make_unique<start_cfg_edge_event>
             (eedge,
@@ -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
index 06a8233eb6b7072adac6cb719d0e480ebe44ace3..b62fc7a258e57bdd0d9a6e00d90521261b62cdd2 100644 (file)
@@ -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;
index 1b819ffbbbbe7625dab93d32073d8a89537eaa72..0917306fe38c42d3461c6d493f49922f0de141fb 100644 (file)
@@ -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 <const gresx *> (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<explicit_throw_event>
+        (event_loc_info (call.location,
+                         dst_point.get_fndecl (),
+                         dst_stack_depth),
+         dst_node,
+         call,
+         m_type,
+         m_is_rethrow));
+  }
+
+private:
+  tree m_type;
+  bool m_is_rethrow;
+};
+
+/* Subclass of custom_edge_info for an exploded edge that expresses
+   unwinding one stack frame during exception handling.  */
+
+class unwind_custom_edge : public custom_edge_info
+{
+public:
+  unwind_custom_edge (location_t loc)
+  : m_loc (loc)
+  {
+  }
+
+  void print (pretty_printer *pp) const final override
+  {
+    pp_printf (pp, "unwinding frame");
+  }
+
+  bool update_model (region_model *model,
+                    const exploded_edge *,
+                    region_model_context *ctxt) const final override
+  {
+    model->pop_frame (NULL_TREE, nullptr, ctxt, nullptr, false);
+    return true;
+  }
+
+  void add_events_to_path (checker_path *emission_path,
+                          const exploded_edge &eedge) const final override
+  {
+    const exploded_node *src_node = eedge.m_src;
+    const program_point &src_point = src_node->get_point ();
+    const int src_stack_depth = src_point.get_stack_depth ();
+    emission_path->add_event
+      (std::make_unique<unwind_event> (event_loc_info (m_loc,
+                                                      src_point.get_fndecl (),
+                                                      src_stack_depth)));
+  }
+
+private:
+  location_t m_loc;
+};
+
+/* Locate an SNODE that's a CFG edge with the EH flag,
+   or return nullptr. */
+
+static const superedge *
+get_eh_outedge (const supernode &snode)
+{
+  for (auto out_sedge : snode.m_succs)
+    if (::edge cfg_edge = out_sedge->get_any_cfg_edge ())
+      if (cfg_edge->flags & EDGE_EH)
+       return out_sedge;
+
+  // Not found
+  return nullptr;
+}
+
+/* Given THROWN_ENODE, which expreses a throw or rethrow occurring at
+   THROW_STMT, unwind intraprocedurally and interprocedurally to find
+   the next eh_dispatch statement to handle exceptions, if any.
+
+   Add eedges and enodes to this graph expressing the actions taken
+   to reach an enode containing the eh_dispatch stmt, if any.
+   Only the final enode is added to this graph's worklist.
+
+   Use CTXT to warn about problems e.g. memory leaks due to stack frames
+   being unwound.  */
+
+void
+exploded_graph::unwind_from_exception (exploded_node &thrown_enode,
+                                      const gimple *throw_stmt,
+                                      region_model_context *ctxt)
+{
+  logger * const logger = get_logger ();
+  LOG_FUNC_1 (logger, "thrown EN: %i", thrown_enode.m_index);
+
+  /* Iteratively unwind the stack looking for an out-cfg-edge
+     flagged EH.  */
+  exploded_node *iter_enode = &thrown_enode;
+  while (iter_enode)
+    {
+      /* If we have an out-cfg-edge flagged EH, follow that,
+        presumably to a bb with a label and an eh_dispatch stmt.
+        Otherwise assume no out-cfgs-edges, and we are unwinding to the
+        caller.  */
+      if (auto sedge = get_eh_outedge (*iter_enode->get_supernode ()))
+       {
+         /* Intraprocedural case.
+            Assume we have an out-edge flagged with EH leading to
+            code for dispatch to catch handlers.  */
+         const program_point next_point
+           = program_point::before_supernode (sedge->m_dest,
+                                              sedge,
+                                              iter_enode->get_point ().get_call_string ());
+         exploded_node *next_enode
+           = get_or_create_node (next_point,
+                                 iter_enode->get_state (),
+                                 iter_enode,
+                                 /* Add this enode to the worklist.  */
+                                 true);
+         if (!next_enode)
+           return;
+
+         add_edge (iter_enode, next_enode, NULL, false, nullptr);
+         return;
+       }
+      else
+       {
+         /* Interprocedural case.
+            No out-cfg-edge.  Unwind one stack frame.  */
+         program_state unwound_state (iter_enode->get_state ());
+         location_t loc = throw_stmt ? throw_stmt->location : UNKNOWN_LOCATION;
+         auto unwind_edge_info
+           = std::make_unique<unwind_custom_edge> (loc);
+         unwind_edge_info->update_model (unwound_state.m_region_model, nullptr,
+                                         ctxt);
+
+         /* Detect leaks in the new state relative to the old state.
+            Use an alternate ctxt that uses the original enode and the stmt
+            (if any) for the location of any diagnostics.  */
+         {
+           uncertainty_t uncertainty;
+           impl_region_model_context ctxt (*this,
+                                           &thrown_enode,
+                                           &iter_enode->get_state (),
+                                           &unwound_state,
+                                           &uncertainty,
+                                           nullptr,
+                                           throw_stmt);
+           program_state::detect_leaks (iter_enode->get_state (),
+                                        unwound_state,
+                                        NULL,
+                                        get_ext_state (), &ctxt);
+         }
+         const call_string &cs = iter_enode->get_point ().get_call_string ();
+         if (cs.empty_p ())
+           {
+             /* Top-level stack frame in analysis: unwinding
+                to the outside world that called us.  */
+             return;
+           }
+         else
+           {
+             /* Nested function in analysis: unwinding to
+                the callsite in the analysis (or beyond).  */
+             program_point unwound_point
+               = program_point::after_supernode (cs.get_caller_node (), cs);
+             unwound_point.pop_from_call_stack ();
+
+             exploded_node *after_unwind_enode
+               = get_or_create_node (unwound_point,
+                                     std::move (unwound_state),
+                                     iter_enode,
+                                     /* Don't add this enode to the
+                                        worklist; we will process it
+                                        on the next iteration.  */
+                                     false);
+
+             if (!after_unwind_enode)
+               return;
+
+             add_edge (iter_enode, after_unwind_enode, NULL, true,
+                       std::move (unwind_edge_info));
+             iter_enode = after_unwind_enode;
+           }
+       }
+    }
+}
+
+/* Handle THROW_CALL, a call to __cxa_throw or __cxa_rethrow.
+
+   Create an eedge and destination enode for the throw/rethrow, adding
+   them to this egraph.  The new enode isn't added to the worklist, but
+   instead exploded_graph::unwind_from_exception is immediately called
+   on it, potentially creating more eedges and enodes leading to an
+   eh_handler stmt.  */
+
+void
+exploded_node::on_throw (exploded_graph &eg,
+                        const gcall &throw_call,
+                        program_state *new_state,
+                        bool is_rethrow,
+                        region_model_context *ctxt)
+{
+  region_model *model = new_state->m_region_model;
+  call_details cd (throw_call, model, ctxt);
+
+  /* Create an enode and eedge for the "throw".  */
+  tree type = NULL_TREE;
+  if (is_rethrow)
+    {
+      const exception_node *eh_node = model->get_current_caught_exception ();
+      gcc_assert (eh_node);
+      type = eh_node->maybe_get_type ();
+    }
+  else
+    {
+      const svalue *tinfo_sval = cd.get_arg_svalue (1);
+      type = tinfo_sval->maybe_get_type_from_typeinfo ();
+    }
+  auto throw_edge_info
+    = std::make_unique<throw_custom_edge> (cd, type, is_rethrow);
+  throw_edge_info->update_model (model, nullptr, ctxt);
+
+  program_point after_throw_point = get_point ().get_next ();
+
+  exploded_node *after_throw_enode
+    = eg.get_or_create_node (after_throw_point, *new_state, this,
+                            /* Don't add to worklist; we process
+                               this immediately below.  */
+                            false);
+
+  if (!after_throw_enode)
+    return;
+
+  /* Create custom exploded_edge for a throw.  */
+  eg.add_edge (this, after_throw_enode, NULL, true,
+              std::move (throw_edge_info));
+
+  eg.unwind_from_exception (*after_throw_enode, &throw_call, ctxt);
+}
+
+/* Handle a gimple "resx" statement by adding eedges and enode.
+   that unwind to the next eh_dispatch statement, if any.  Only
+   the final enode is added to the worklist.  */
+
+void
+exploded_node::on_resx (exploded_graph &eg,
+                       const gresx &/*resx*/,
+                       program_state */*new_state*/,
+                       region_model_context *ctxt)
+{
+  eg.unwind_from_exception (*this,
+                           nullptr,
+                           ctxt);
+}
+
+
 /* Subroutine of exploded_graph::process_node for finding the successors
    of the supernode for a function exit basic block.
 
@@ -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;
index aa6fb1f6036488c85698d8ce8bd1e2080580a5e1..32c72dc2076ab947eb230c69766f2093cc9aabb7 100644 (file)
@@ -214,6 +214,10 @@ class exploded_node : public dnode<eg_traits>
     /* 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<eg_traits>
                   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_edge_info> 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 ();
 
index 8e08dbc16dc9d340b1ef61ca57c4fffa962d2f93..01a98b0163e53439746fc621b41e6e95872a1f48 100644 (file)
@@ -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<throw_of_unexpected_type>
+{
+public:
+  throw_of_unexpected_type (tree exception_type,
+                           tree thrown_from_fndecl)
+  : m_exception_type (exception_type),
+    m_thrown_from_fndecl (thrown_from_fndecl)
+  {
+    gcc_assert (m_exception_type);
+    gcc_assert (m_thrown_from_fndecl);
+  }
+
+  const char *get_kind () const final override
+  {
+    return "throw_of_unexpected_type";
+  }
+
+  bool operator== (const throw_of_unexpected_type &other) const
+  {
+    return (m_exception_type == other.m_exception_type
+           && m_thrown_from_fndecl == other.m_thrown_from_fndecl);
+  }
+
+  int get_controlling_option () const final override
+  {
+    return OPT_Wanalyzer_throw_of_unexpected_type;
+  }
+
+  bool emit (diagnostic_emission_context &ctxt) final override
+  {
+    auto_diagnostic_group d;
+
+    bool warned
+      = ctxt.warn ("throwing exception of unexpected type %qT from %qE",
+                  m_exception_type, m_thrown_from_fndecl);
+    if (warned)
+      {
+       inform (DECL_SOURCE_LOCATION (m_thrown_from_fndecl),
+               "%qE declared here", m_thrown_from_fndecl);
+       // TODO: show specified types?
+      }
+    return warned;
+  }
+
+  bool
+  describe_final_event (pretty_printer &pp,
+                       const evdesc::final_event &) final override
+  {
+    pp_printf  (&pp,
+               "exception of unexpected type %qT thrown from %qE",
+               m_exception_type, m_thrown_from_fndecl);
+    return true;
+  }
+
+private:
+  tree m_exception_type;
+  tree m_thrown_from_fndecl;
+};
+
+/* See https://en.cppreference.com/w/cpp/language/except_spec  */
+
+class kf_cxa_call_unexpected : public known_function
+{
+public:
+  bool matches_call_types_p (const call_details &cd) const final override
+  {
+    return (cd.num_args () == 1
+           && POINTER_TYPE_P (cd.get_arg_type (0)));
+  }
+
+  void impl_call_pre (const call_details &cd) const final override
+  {
+    if (region_model_context *ctxt = cd.get_ctxt ())
+      {
+       region_model *model = cd.get_model ();
+       tree thrown_from_fndecl = model->get_current_function ()->decl;
+       /* We must have a thrown exception.  */
+       auto eh_node = model->get_current_thrown_exception ();
+       gcc_assert (eh_node);
+       tree exception_type = eh_node->maybe_get_type ();
+       ctxt->warn
+         (std::make_unique<throw_of_unexpected_type> (exception_type,
+                                                      thrown_from_fndecl));
+       ctxt->terminate_path ();
+      }
+  }
+};
+
 /* Populate KFM with instances of known functions relating to C++.  */
 
 void
@@ -176,6 +331,17 @@ register_known_functions_lang_cp (known_function_manager &kfm)
   kfm.add ("operator new []", std::make_unique<kf_operator_new> ());
   kfm.add ("operator delete", std::make_unique<kf_operator_delete> ());
   kfm.add ("operator delete []", std::make_unique<kf_operator_delete> ());
+
+  /* Functions mentioned in "Itanium C++ ABI: Exception Handling"'s
+     "Level II: C++ ABI"
+     https://itanium-cxx-abi.github.io/cxx-abi/abi-eh.html#cxx-abi  */
+  kfm.add ("__cxa_allocate_exception",
+          std::make_unique<kf_cxa_allocate_exception> ());
+  // We treat __cxa_throw and __cxa_rethrow as special cases
+  kfm.add ("__cxa_begin_catch", std::make_unique<kf_cxa_begin_catch> ());
+  kfm.add ("__cxa_end_catch", std::make_unique<kf_cxa_end_catch> ());
+  kfm.add ("__cxa_call_unexpected",
+          std::make_unique<kf_cxa_call_unexpected> ());
 }
 
 } // namespace ana
index e9ad86514d648e56844f7d87bc4f8761d89e3c2a..75b627902f8cf53a8931a043abf2059c8f9394e7 100644 (file)
@@ -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<kf_stack_restore> ());
     kfm.add (BUILT_IN_STACK_SAVE, std::make_unique<kf_stack_save> ());
 
+    kfm.add (BUILT_IN_EH_POINTER, std::make_unique<kf_eh_pointer> ());
+
     register_atomic_builtins (kfm);
     register_sanitizer_builtins (kfm);
     register_varargs_builtins (kfm);
index ea0e6b0a5184638b28fe6389c96ccf2876ae8d7f..fc586296ce5b4c849e9dd81a9746e763dd5f3844 100644 (file)
@@ -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<json::object>
+exception_node::to_json () const
+{
+  auto obj = std::make_unique<json::object> ();
+  obj->set ("exception", m_exception_sval->to_json ());
+  obj->set ("typeinfo", m_typeinfo_sval->to_json ());
+  obj->set ("destructor", m_destructor_sval->to_json ());
+  return obj;
+}
+
+std::unique_ptr<text_art::tree_widget>
+exception_node::make_dump_widget (const text_art::dump_widget_info &dwi) const
+{
+  using text_art::tree_widget;
+  std::unique_ptr<tree_widget> w
+    (tree_widget::from_fmt (dwi, nullptr, "Exception Node"));
+
+  w->add_child (m_exception_sval->make_dump_widget (dwi, "exception"));
+  w->add_child (m_typeinfo_sval->make_dump_widget (dwi, "typeinfo"));
+  w->add_child (m_destructor_sval->make_dump_widget (dwi, "destructor"));
+
+  return w;
+}
+
+tree
+exception_node::maybe_get_type () const
+{
+  return m_typeinfo_sval->maybe_get_type_from_typeinfo ();
+}
+
+void
+exception_node::add_to_reachable_regions (reachable_regions &regs) 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<json::array> ();
+  for (auto &node : m_thrown_exceptions_stack)
+    thrown_exceptions_arr->append (node.to_json ());
+  model_obj->set ("thrown_exception_stack", std::move (thrown_exceptions_arr));
+
+  auto caught_exceptions_arr = std::make_unique<json::array> ();
+  for (auto &node : m_caught_exceptions_stack)
+    caught_exceptions_arr->append (node.to_json ());
+  model_obj->set ("caught_exception_stack", std::move (caught_exceptions_arr));
+
   model_obj->set ("dynamic_extents", m_dynamic_extents.to_json ());
   return model_obj;
 }
@@ -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<throw_from_call_to_external_fn_event>
+        (event_loc_info (m_call.location,
+                         dst_point.get_fndecl (),
+                         dst_stack_depth),
+         dst_node,
+         m_call,
+         m_fndecl));
+  }
+
+  exploded_node *
+  create_enode (exploded_graph &eg,
+               const program_point &point,
+               program_state &&state,
+               exploded_node *enode_for_diag,
+               region_model_context *ctxt) const final override
+  {
+    exploded_node *thrown_enode
+      = eg.get_or_create_node (point, state, enode_for_diag,
+                              /* Don't add to worklist.  */
+                              false);
+    if (!thrown_enode)
+      return nullptr;
+
+    /* Add successor edges for thrown_enode "by hand" for the exception.  */
+    eg.unwind_from_exception (*thrown_enode,
+                             &m_call,
+                             ctxt);
+    return thrown_enode;
+  }
+
+private:
+  const gcall &m_call;
+  tree m_fndecl; // could be null
+};
+
+/* Get a set of functions that are assumed to not throw exceptions.  */
+
+static function_set
+get_fns_assumed_not_to_throw ()
+{
+  // TODO: populate this list more fully
+  static const char * const fn_names[] = {
+    /* This array must be kept sorted.  */
+
+    "fclose"
+  };
+  const size_t count = ARRAY_SIZE (fn_names);
+  function_set fs (fn_names, count);
+  return fs;
+}
+
+/* Return true if CALL could throw an exception.
+   FNDECL could be NULL_TREE.  */
+
+static bool
+can_throw_p (const gcall &call, tree fndecl)
+{
+  if (!flag_exceptions)
+    return false;
+
+  if (gimple_call_nothrow_p (&call))
+    return false;
+
+  if (fndecl)
+    {
+      const function_set fs = get_fns_assumed_not_to_throw ();
+      if (fs.contains_decl_p (fndecl))
+       return false;
+    }
+
+  return true;
+}
+
+/* Given CALL where we don't know what code is being called
+   (by not having the body of FNDECL, or having NULL_TREE for FNDECL),
+  potentially bifurcate control flow to simulate the call throwing
+  an exception.  */
+
+void
+region_model::check_for_throw_inside_call (const gcall &call,
+                                          tree fndecl,
+                                          region_model_context *ctxt)
+{
+  if (!ctxt)
+    return;
+
+  /* Could this function throw an exception?
+     If so, add an extra e-edge for that.  */
+  if (!can_throw_p (call, fndecl))
+    return;
+
+  auto throws_exception
+    = std::make_unique<exception_thrown_from_unrecognized_call> (call, fndecl);
+  ctxt->bifurcate (std::move (throws_exception));
+}
+
 /* Update this model for the CALL stmt, using CTXT to report any
    diagnostics - the first half.
 
@@ -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 <const geh_dispatch *> (last_stmt))
+    {
+      const eh_dispatch_cfg_superedge *eh_dispatch_cfg_sedge
+       = as_a <const eh_dispatch_cfg_superedge *> (&edge);
+      return apply_constraints_for_eh_dispatch (*eh_dispatch_cfg_sedge,
+                                               eh_dispatch_stmt,
+                                               ctxt, out);
+    }
+
   if (const ggoto *goto_stmt = dyn_cast <const ggoto *> (last_stmt))
     {
       const cfg_superedge *cfg_sedge = as_a <const cfg_superedge *> (&edge);
       return apply_constraints_for_ggoto (*cfg_sedge, goto_stmt, ctxt);
     }
 
-  /* Apply any constraints due to an exception being thrown.  */
-  if (const cfg_superedge *cfg_sedge = dyn_cast <const cfg_superedge *> (&edge))
-    if (cfg_sedge->get_flags () & EDGE_EH)
-      return apply_constraints_for_exception (last_stmt, ctxt, out);
-
   return true;
 }
 
@@ -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<rejected_constraint> *out)
+{
+  const exception_node *current_node = get_current_thrown_exception ();
+  gcc_assert (current_node);
+  tree curr_exception_type = current_node->maybe_get_type ();
+  if (!curr_exception_type)
+    /* We don't know the specific type.  */
+    return true;
+
+  return edge.apply_constraints (this, ctxt, curr_exception_type, out);
+}
+
+bool
+region_model::
+apply_constraints_for_eh_dispatch_try (const eh_dispatch_try_cfg_superedge &edge,
+                                      region_model_context */*ctxt*/,
+                                      tree exception_type,
+                                      std::unique_ptr<rejected_constraint> *out)
+{
+  /* TODO: can we rely on this ordering?
+     or do we need to iterate through prev_catch ?  */
+  /* The exception must not match any of the previous edges.  */
+  for (auto sibling_sedge : edge.m_src->m_succs)
+    {
+      if (sibling_sedge == &edge)
+       break;
+
+      const eh_dispatch_try_cfg_superedge *sibling_eh_sedge
+       = as_a <const eh_dispatch_try_cfg_superedge *> (sibling_sedge);
+      if (eh_catch ehc = sibling_eh_sedge->get_eh_catch ())
+       if (matches_any_exception_type_p (ehc, exception_type))
+         {
+           /* The earlier sibling matches, so the "unhandled" edge is
+              not taken.  */
+           if (out)
+             *out = std::make_unique<rejected_eh_dispatch> (*this);
+           return false;
+         }
+    }
+
+  if (eh_catch ehc = edge.get_eh_catch ())
+    {
+      /* We have an edge that tried to match one or more types.  */
+
+      /* The exception must not match any of the previous edges.  */
+
+      /* It must match this type.  */
+      if (matches_any_exception_type_p (ehc, exception_type))
+       return true;
+      else
+       {
+         /* Exception type doesn't match.  */
+         if (out)
+           *out = std::make_unique<rejected_eh_dispatch> (*this);
+         return false;
+       }
+    }
+  else
+    {
+      /* This is the "unhandled exception" edge.
+        If we get here then no sibling edges matched;
+        we will follow this edge.  */
+      return true;
+    }
+}
+
+bool
+region_model::
+apply_constraints_for_eh_dispatch_allowed (const eh_dispatch_allowed_cfg_superedge &edge,
+                                          region_model_context */*ctxt*/,
+                                          tree exception_type,
+                                          std::unique_ptr<rejected_constraint> *out)
+{
+  auto curr_thrown_exception_node = get_current_thrown_exception ();
+  gcc_assert (curr_thrown_exception_node);
+  tree curr_exception_type = curr_thrown_exception_node->maybe_get_type ();
+  eh_region eh_reg = edge.get_eh_region ();
+  tree type_list = eh_reg->u.allowed.type_list;
+
+  switch (edge.get_eh_kind ())
+    {
+    default:
+      gcc_unreachable ();
+    case eh_dispatch_allowed_cfg_superedge::eh_kind::expected:
+      if (!curr_exception_type)
+       {
+         /* We don't know the specific type;
+            assume we have one of an expected type.  */
+         return true;
+       }
+      for (tree iter = type_list; iter; iter = TREE_CHAIN (iter))
+       if (exception_matches_type_p (TREE_VALUE (iter),
+                                     exception_type))
+         return true;
+      if (out)
+       *out = std::make_unique<rejected_eh_dispatch> (*this);
+      return false;
+
+    case eh_dispatch_allowed_cfg_superedge::eh_kind::unexpected:
+      if (!curr_exception_type)
+       {
+         /* We don't know the specific type;
+            assume we don't have one of an expected type.  */
+         if (out)
+           *out = std::make_unique<rejected_eh_dispatch> (*this);
+         return false;
+       }
+      for (tree iter = type_list; iter; iter = TREE_CHAIN (iter))
+       if (exception_matches_type_p (TREE_VALUE (iter),
+                                     exception_type))
+         {
+           if (out)
+             *out = std::make_unique<rejected_eh_dispatch> (*this);
+           return false;
+         }
+      return true;
+    }
+}
+
 /* Given an edge reached by GOTO_STMT, determine appropriate constraints
    for the edge to be taken.
 
@@ -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<rejected_constraint> *out)
-{
-  gcc_assert (last_stmt);
-  if (const gcall *call = dyn_cast <const gcall *> (last_stmt))
-    if (tree callee_fndecl = get_fndecl_for_call (*call, ctxt))
-      if (is_named_call_p (callee_fndecl, "operator new", *call, 1)
-         || is_named_call_p (callee_fndecl, "operator new []", *call, 1))
-       {
-         /* We have an exception thrown from operator new.
-            Add a constraint that the result was NULL, to avoid a false
-            leak report due to the result being lost when following
-            the EH edge.  */
-         if (tree lhs = gimple_call_lhs (call))
-           return add_constraint (lhs, EQ_EXPR, null_pointer_node, ctxt, out);
-         return true;
-       }
-  return true;
-}
-
 /* For use with push_frame when handling a top-level call within the analysis.
    PARAM has a defined but unknown initial value.
    Anything it points to has escaped, since the calling context "knows"
@@ -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 ());
index 399947348dd1e4aa462e0d6930f05b17d332dd7e..c1eb9468f4bd1f6c1fe5812bb718209c99009f23 100644 (file)
@@ -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<json::object> to_json () const;
+
+  std::unique_ptr<text_art::tree_widget>
+  make_dump_widget (const text_art::dump_widget_info &dwi) const;
+
+  tree maybe_get_type () const;
+
+  void add_to_reachable_regions (reachable_regions &) const;
+
+  const svalue *m_exception_sval;
+  const svalue *m_typeinfo_sval;
+  const svalue *m_destructor_sval;
+};
+
 /* A region_model encapsulates a representation of the state of memory, with
    a tree of regions, along with their associated values.
    The representation is graph-like because values can be pointers to
@@ -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<rejected_constraint> *out);
+
+  bool
+  apply_constraints_for_eh_dispatch_allowed
+    (const eh_dispatch_allowed_cfg_superedge &edge,
+     region_model_context *ctxt,
+     tree exception_type,
+     std::unique_ptr<rejected_constraint> *out);
+
 private:
   const region *get_lvalue_1 (path_var pv, region_model_context *ctxt) const;
   const svalue *get_rvalue_1 (path_var pv, region_model_context *ctxt) const;
@@ -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<rejected_constraint> *out);
+
+  bool
+  apply_constraints_for_eh_dispatch (const eh_dispatch_cfg_superedge &edge,
+                                    const geh_dispatch *eh_dispatch_stmt,
+                                    region_model_context *ctxt,
+                                    std::unique_ptr<rejected_constraint> *out);
 
   int poison_any_pointers_to_descendents (const region *reg,
                                          enum poison_kind pkind);
@@ -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_callback> 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<exception_node> m_thrown_exceptions_stack;
+  std::vector<exception_node> m_caught_exceptions_stack;
+
   /* Map from base region to size in bytes, for tracking the sizes of
      dynamically-allocated regions.
      This is part of the region_model rather than the region to allow for
index f4823c620b79a7cfd8e30b03f273f885c3e97dac..de2c330a258cd91a6f42dd00c06bbe515b9ea8a3 100644 (file)
@@ -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<cfg_superedge> new_edge;
   if (stmt && stmt->code == GIMPLE_SWITCH)
-    new_edge = new switch_cfg_superedge (src, dest, e);
+    new_edge = std::make_unique<switch_cfg_superedge> (src, dest, e);
+  else if (stmt && stmt->code == GIMPLE_EH_DISPATCH)
+    new_edge = eh_dispatch_cfg_superedge::make (src, dest, e,
+                                               as_a <geh_dispatch *> (stmt));
   else
-    new_edge = new cfg_superedge (src, dest, e);
-  add_edge (new_edge);
-  return new_edge;
+    new_edge = std::make_unique<cfg_superedge> (src, dest, e);
+  add_edge (new_edge.get ());
+  return new_edge.release ();
 }
 
 /* Create and add a call_superedge representing an interprocedural call
@@ -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>
+eh_dispatch_cfg_superedge::make (supernode *src_snode,
+                                supernode *dst_snode,
+                                ::edge e,
+                                const geh_dispatch *eh_dispatch_stmt)
+{
+  const eh_status *eh = src_snode->get_function ()->eh;
+  gcc_assert (eh);
+  int region_idx = gimple_eh_dispatch_region (eh_dispatch_stmt);
+  gcc_assert (region_idx > 0);
+  gcc_assert ((*eh->region_array)[region_idx]);
+  eh_region eh_reg = (*eh->region_array)[region_idx];
+  gcc_assert (eh_reg);
+  switch (eh_reg->type)
+    {
+    default:
+      gcc_unreachable ();
+    case ERT_CLEANUP:
+      // TODO
+      gcc_unreachable ();
+      break;
+    case ERT_TRY:
+      {
+       eh_catch ehc = get_catch (eh_reg, dst_snode);
+       return std::make_unique<eh_dispatch_try_cfg_superedge>
+         (src_snode, dst_snode,
+          e, eh_dispatch_stmt,
+          eh_reg, ehc);
+      }
+      break;
+    case ERT_ALLOWED_EXCEPTIONS:
+      return std::make_unique<eh_dispatch_allowed_cfg_superedge>
+       (src_snode, dst_snode,
+        e, eh_dispatch_stmt,
+        eh_reg);
+      break;
+    case ERT_MUST_NOT_THROW:
+      // TODO
+      gcc_unreachable ();
+      break;
+    }
+}
+
+eh_dispatch_cfg_superedge::
+eh_dispatch_cfg_superedge (supernode *src,
+                          supernode *dst,
+                          ::edge e,
+                          const geh_dispatch *eh_dispatch_stmt,
+                          eh_region eh_reg)
+: cfg_superedge (src, dst, e),
+  m_eh_dispatch_stmt (eh_dispatch_stmt),
+  m_eh_region (eh_reg)
+{
+  gcc_assert (m_eh_region);
+}
+
+const eh_status &
+eh_dispatch_cfg_superedge::get_eh_status () const
+{
+  const eh_status *eh = m_src->get_function ()->eh;
+  gcc_assert (eh);
+  return *eh;
+}
+
+// class eh_dispatch_try_cfg_superedge : public eh_dispatch_cfg_superedge
+
+/* Implementation of superedge::dump_label_to_pp for CFG superedges for
+   "eh_dispatch" statements for ERT_TRY regions.  */
+
+void
+eh_dispatch_try_cfg_superedge::dump_label_to_pp (pretty_printer *pp,
+                                                bool user_facing) const
+{
+  if (!user_facing)
+    pp_string (pp, "ERT_TRY: ");
+  if (m_eh_catch)
+    {
+      bool first = true;
+      for (tree iter = m_eh_catch->type_list; iter; iter = TREE_CHAIN (iter))
+       {
+         if (!first)
+           pp_string (pp, ", ");
+         pp_printf (pp, "on catch %qT", TREE_VALUE (iter));
+         first = false;
+       }
+    }
+  else
+    pp_string (pp, "on uncaught exception");
+}
+
+bool
+eh_dispatch_try_cfg_superedge::
+apply_constraints (region_model *model,
+                  region_model_context *ctxt,
+                  tree exception_type,
+                  std::unique_ptr<rejected_constraint> *out) const
+{
+  return model->apply_constraints_for_eh_dispatch_try
+    (*this, ctxt, exception_type, out);
+}
+
+// class eh_dispatch_allowed_cfg_superedge : public eh_dispatch_cfg_superedge
+
+eh_dispatch_allowed_cfg_superedge::
+eh_dispatch_allowed_cfg_superedge (supernode *src, supernode *dst, ::edge e,
+                                  const geh_dispatch *eh_dispatch_stmt,
+                                  eh_region eh_reg)
+: eh_dispatch_cfg_superedge (src, dst, e, eh_dispatch_stmt, eh_reg)
+{
+  gcc_assert (eh_reg->type == ERT_ALLOWED_EXCEPTIONS);
+
+  /* We expect two sibling out-edges at an eh_dispatch from such a region:
+
+     - one to a bb without a gimple label, with a resx,
+     for exceptions of expected types
+
+     - one to a bb with a gimple label, with a call to __cxa_unexpected,
+     for exceptions of unexpected types.
+
+     Set m_kind for this edge accordingly.  */
+  gcc_assert (e->src->succs->length () == 2);
+  tree label_for_unexpected_exceptions = eh_reg->u.allowed.label;
+  tree label_for_dest_enode = dst->get_label ();
+  if (label_for_dest_enode == label_for_unexpected_exceptions)
+    m_kind = eh_kind::unexpected;
+  else
+    {
+      gcc_assert (label_for_dest_enode == nullptr);
+      m_kind = eh_kind::expected;
+    }
+}
+
+void
+eh_dispatch_allowed_cfg_superedge::dump_label_to_pp (pretty_printer *pp,
+                                                    bool user_facing) const
+{
+  if (!user_facing)
+    {
+      switch (m_kind)
+       {
+       default:
+         gcc_unreachable ();
+       case eh_dispatch_allowed_cfg_superedge::eh_kind::expected:
+         pp_string (pp, "expected: ");
+         break;
+       case eh_dispatch_allowed_cfg_superedge::eh_kind::unexpected:
+         pp_string (pp, "unexpected: ");
+         break;
+       }
+      pp_string (pp, "ERT_ALLOWED_EXCEPTIONS: ");
+      eh_region eh_reg = get_eh_region ();
+      bool first = true;
+      for (tree iter = eh_reg->u.allowed.type_list; iter;
+          iter = TREE_CHAIN (iter))
+       {
+         if (!first)
+           pp_string (pp, ", ");
+         pp_printf (pp, "%qT", TREE_VALUE (iter));
+         first = false;
+       }
+    }
+}
+
+bool
+eh_dispatch_allowed_cfg_superedge::
+apply_constraints (region_model *model,
+                  region_model_context *ctxt,
+                  tree exception_type,
+                  std::unique_ptr<rejected_constraint> *out) const
+{
+  return model->apply_constraints_for_eh_dispatch_allowed
+    (*this, ctxt, exception_type, out);
+}
+
 /* Implementation of superedge::dump_label_to_pp for interprocedural
    superedges.  */
 
index ddf721bde9dda93db2992e45c1b869d165dba37d..8796ab77e29be789e0d89b4533868bdce16b8259 100644 (file)
@@ -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<supergraph_traits>
   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 <const switch_cfg_superedge *>::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<eh_dispatch_cfg_superedge>
+  make (supernode *src,
+       supernode *dest,
+       ::edge e,
+       const geh_dispatch *eh_dispatch_stmt);
+
+  const eh_dispatch_cfg_superedge *dyn_cast_eh_dispatch_cfg_superedge () const
+    final override
+  {
+    return this;
+  }
+
+  const geh_dispatch *
+  get_eh_dispatch_stmt () const
+  {
+    return m_eh_dispatch_stmt;
+  }
+
+  const eh_status &get_eh_status () const;
+  eh_region get_eh_region () const { return m_eh_region; }
+
+  virtual bool
+  apply_constraints (region_model *model,
+                    region_model_context *ctxt,
+                    tree exception_type,
+                    std::unique_ptr<rejected_constraint> *out) const = 0;
+
+protected:
+  eh_dispatch_cfg_superedge (supernode *src, supernode *dst, ::edge e,
+                            const geh_dispatch *eh_dispatch_stmt,
+                            eh_region eh_reg);
+
+private:
+  const geh_dispatch *m_eh_dispatch_stmt;
+  eh_region m_eh_region;
+};
+
+} // namespace ana
+
+template <>
+template <>
+inline bool
+is_a_helper <const eh_dispatch_cfg_superedge *>::test (const superedge *sedge)
+{
+  return sedge->dyn_cast_eh_dispatch_cfg_superedge () != NULL;
+}
+
+namespace ana {
+
+/* A concrete subclass for edges from an eh_dispatch statements
+   for ERT_TRY regions.  */
+
+class eh_dispatch_try_cfg_superedge : public eh_dispatch_cfg_superedge
+{
+ public:
+  eh_dispatch_try_cfg_superedge (supernode *src, supernode *dst, ::edge e,
+                                const geh_dispatch *eh_dispatch_stmt,
+                                eh_region eh_reg,
+                                eh_catch ehc)
+  : eh_dispatch_cfg_superedge (src, dst, e, eh_dispatch_stmt, eh_reg),
+    m_eh_catch (ehc)
+  {
+    gcc_assert (eh_reg->type == ERT_TRY);
+  }
+
+  const eh_dispatch_try_cfg_superedge *
+  dyn_cast_eh_dispatch_try_cfg_superedge () const final override
+  {
+    return this;
+  }
+
+  void dump_label_to_pp (pretty_printer *pp,
+                        bool user_facing) const final override;
+
+  eh_catch get_eh_catch () const { return m_eh_catch; }
+
+  bool
+  apply_constraints (region_model *model,
+                    region_model_context *ctxt,
+                    tree exception_type,
+                    std::unique_ptr<rejected_constraint> *out)
+    const final override;
+
+private:
+  eh_catch m_eh_catch;
+};
+
+} // namespace ana
+
+template <>
+template <>
+inline bool
+is_a_helper <const eh_dispatch_try_cfg_superedge *>::test (const superedge *sedge)
+{
+  return sedge->dyn_cast_eh_dispatch_try_cfg_superedge () != NULL;
+}
+
+namespace ana {
+
+/* A concrete subclass for edges from an eh_dispatch statements
+   for ERT_ALLOWED_EXCEPTIONS regions.  */
+
+class eh_dispatch_allowed_cfg_superedge : public eh_dispatch_cfg_superedge
+{
+ public:
+  enum eh_kind
+  {
+    expected,
+    unexpected
+  };
+
+  eh_dispatch_allowed_cfg_superedge (supernode *src, supernode *dst, ::edge e,
+                                    const geh_dispatch *eh_dispatch_stmt,
+                                    eh_region eh_reg);
+
+  const eh_dispatch_allowed_cfg_superedge *
+  dyn_cast_eh_dispatch_allowed_cfg_superedge () const final override
+  {
+    return this;
+  }
+
+  void dump_label_to_pp (pretty_printer *pp,
+                        bool user_facing) const final override;
+
+  bool
+  apply_constraints (region_model *model,
+                    region_model_context *ctxt,
+                    tree exception_type,
+                    std::unique_ptr<rejected_constraint> *out)
+    const final override;
+
+  enum eh_kind get_eh_kind () const { return m_kind; }
+
+private:
+  enum eh_kind m_kind;
+};
+
+} // namespace ana
+
+template <>
+template <>
+inline bool
+is_a_helper <const eh_dispatch_allowed_cfg_superedge *>::test (const superedge *sedge)
+{
+  return sedge->dyn_cast_eh_dispatch_allowed_cfg_superedge () != NULL;
+}
+
+namespace ana {
 /* Base class for adding additional content to the .dot output
    for a supergraph.  */
 
index 513d9cc4bb25c0ecfdf835aa7cf6fddea30f7d0f..f3f80d1dc4e644b05a6ea5ed1b3679af30d903c6 100644 (file)
@@ -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.  */
index f416c578f3688dec5f2cdc475a73a0cb513bc075..7a27cb66b2476df056252e61f87f5aa955bcf00b 100644 (file)
@@ -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)
index 7fcf7dee497710f8ac5baa1aa3b3d1c3f763850f..d5a2bf6d02b1dac5ceb17f26d3da947e6b2b8beb 100644 (file)
@@ -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
index 7e3086f5632e418d08bed37aed18a9c78ccb0cf4..032365f3da2d9e9fd989512a264840a294a6f595 100644 (file)
@@ -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;
 }
index cf014b0a3c8fa01b04f2e9e062bd7c3711eeeb6d..175d9c358b3137ce912966f990789f4ecddcf6ac 100644 (file)
@@ -1,4 +1,4 @@
-/* { dg-additional-options "-fanalyzer-verbosity=2" } */
+/* { dg-additional-options "-fanalyzer-verbosity=2 -fno-exceptions" } */
 
 typedef struct FILE   FILE;
 
index b0ece203f56de1322672e87a084ab3be058cb816..8d66c8f4234e28519125fff1c8c8807b99a15be0 100644 (file)
@@ -1,4 +1,4 @@
-/* { dg-additional-options "-fanalyzer-verbosity=3" } */
+/* { dg-additional-options "-fanalyzer-verbosity=3 -fno-exceptions" } */
 
 typedef struct FILE   FILE;
 
index ab79514acf8613e9fe787c9e17baf3653936d432..5329a89141bfa44ff30d8389a7a6e0095e412481 100644 (file)
@@ -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)
 {
index 1517667dfb21e4fa8fda2a4d0a66bce1ed311d1d..1e2e1f949aa8cdaedead5672af8014defc67764a 100644 (file)
@@ -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)));
 
index 7ff4e57fcfbfd4f7d79efbf095b385832db2ca9b..8b7ffc1a3582c6ad468593e692d40984f5a397f9 100644 (file)
@@ -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)));
 
index 1665d4128f59fb3e2e123a254220d173096e44eb..45ee406f990d97aa26cd81180b025336c2a872fe 100644 (file)
@@ -1,4 +1,5 @@
 /* Adapted from gcc.dg/Wmismatched-dealloc.c.  */
+/* { dg-additional-options "-fno-exceptions" } */
 
 #define A(...) __attribute__ ((malloc (__VA_ARGS__)))
 
index 87ad42a3c3286b28e4712de92d4af089412cedc3..fd5163034c0ba73dc568b3dfce956d91aab050a1 100644 (file)
@@ -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 (file)
index 0000000..9f5e2e8
--- /dev/null
@@ -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);
+}
index b395623cccacc89a9a39690161eb7a438f6e0c89..a15ab65c476ad80efc5e98bd4ca51d5dfde53253 100644 (file)
@@ -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
index de705836249111377f2984b05d69674262074524..6fbbce41af04677670f218c2ca368370d59f3a91 100644 (file)
@@ -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)) };
index 7d2de4ed8ad6c88033bab1760b45e230697c68ee..3846239c4948b099743f98f5cbc1bdf39671058b 100644 (file)
@@ -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 <stdlib.h>
 #include "analyzer-decls.h"
index c41b61dd081d404bb0f9906064637d32609cf1ea..95091e712cef390a98d55e56614ab80deeca8986 100644 (file)
@@ -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;
index 8429b2f4dc68de23c295e6841fa7530c294043cc..65bf34665eb591c4052ba372f370b5e839786d81 100644 (file)
@@ -1,3 +1,5 @@
+/* { dg-additional-options "-fno-exceptions" } */
+
 #include <string.h>
 #include "analyzer-decls.h"
 
index a86d5060c7a6e312263eb45ba968ef66fd112aa8..bb45917483741ab5de008d21c5da94087fdb3d6f 100644 (file)
@@ -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.  */
 
index 7431bd11c42843564c69c3d7702c3d598fd2b601..b79099710441262305290bd45a39e29359924008 100644 (file)
@@ -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;
index 7123cf5196b742672858599f0f7bb262bf3d8277..a7f8049e9d2f6e5458f3d11e34f796141c45fad7 100644 (file)
@@ -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;
 
index 7e4543c24c30faab022383343a27a7a658539027..df4bfaa1615d5eea2e768a1a675464f76f7c3672 100644 (file)
@@ -1,5 +1,7 @@
 /* { dg-skip-if "requires hosted libstdc++ for stdlib malloc" { ! hostedlib } } */
 
+/* { dg-additional-options "-fno-exceptions" } */
+
 #include <stdlib.h>
 
 int foo ();
index 10c9ecdb09d97873f12ecca6dabefc0b8169ec85..b6b0a57ef3015ecdee0c49ca21180b8cf72781e3 100644 (file)
@@ -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
+}
index 8e71b14f71b69502390fd100d13b166a034c3dfa..4894b6478f9e8e61ce55a6421d2155f25f320a54 100644 (file)
@@ -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);
index 6a9ec921fd39c9005038af3f2408013aeb140f43..bd0d458a4ae091b40daf9a4515f643cde8bc9213 100644 (file)
@@ -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
+}
index 316cbb3d8680b358bbfda33230b89b368c0beb47..e87cf73a126b8f9481e5d1315a842acac3c6993f 100644 (file)
@@ -1,3 +1,5 @@
+/* { dg-additional-options "-fno-exceptions" } */
+
 typedef struct FILE   FILE;
 
 FILE* fopen (const char*, const char*);
index 8f93a986deb5bca91deb06e61a83b631c80e47dc..ca992bfb7496ec4c48beab1df9c3508d74895d91 100644 (file)
@@ -1,3 +1,5 @@
+/* { dg-additional-options "-fno-exceptions" } */
+
 typedef struct _IO_FILE FILE;
 extern struct _IO_FILE *stderr;
 
index 66b72a72daff0a33961c7c517cc9824ae8b4fd3f..c9aee5ec5aad2c535cdf656e07d7c29dff5c0a33 100644 (file)
@@ -1,3 +1,4 @@
+/* { dg-additional-options "-fno-exceptions" } */
 /* { dg-additional-options "-fanalyzer-verbose-state-changes" } */
 
 typedef struct FILE   FILE;
index 6b7d25cfabe6c576b0e13964813020fe7d6b7779..bbbb68ebf3c962dee7ee678d32270601f1973186 100644 (file)
@@ -1,3 +1,5 @@
+/* { dg-additional-options "-fno-exceptions" } */
+
 extern void marker_A(void);
 extern void marker_B(void);
 extern void marker_C(void);
index a386d887f33292a833c90802bdbd3da295b26a18..19d7501e81c1fb12d60a986ac295567201f30f61 100644 (file)
@@ -1,4 +1,5 @@
 /* { dg-skip-if "requires hosted libstdc++ for stdlib malloc" { ! hostedlib } } */
+/* { dg-additional-options "-fno-exceptions" } */
 
 #include <stdlib.h>
 
index 8653c67b08b56848efccfcb99062851e117f0884..c296061ce7a465c94a53b0056133ea7ad579f165 100644 (file)
@@ -1,4 +1,5 @@
 /* { dg-skip-if "requires hosted libstdc++ for stdlib malloc" { ! hostedlib } } */
+/* { dg-additional-options "-fno-exceptions" } */
 
 #include <stdlib.h>
 
index e7179f09a1d1d7e385e4dcd97721018ef3a04100..0d868017ce69a99d2069c952eeea9db610b5c019 100644 (file)
@@ -1,4 +1,5 @@
 /* { dg-skip-if "requires hosted libstdc++ for stdlib malloc" { ! hostedlib } } */
+/* { dg-additional-options "-fno-exceptions" } */
 
 #include <stdlib.h>
 #include "../../gcc.dg/analyzer/analyzer-decls.h"
index 6ee30f3c86aba7c7581248c79ab9a4ba254e06c3..5daa696d507754c0c0830929d254f82d60f15ba8 100644 (file)
@@ -1,4 +1,5 @@
 /* { dg-skip-if "requires hosted libstdc++ for stdlib malloc" { ! hostedlib } } */
+/* { dg-additional-options "-fno-exceptions" } */
 
 #include <stdlib.h>
 
index f03f97845c389a5368189731036485265497a19b..4a1870d788ea78e9344e3d008f573ce5afb95484 100644 (file)
@@ -1,4 +1,5 @@
 /* { dg-skip-if "requires hosted libstdc++ for stdlib malloc" { ! hostedlib } } */
+/* { dg-additional-options "-fno-exceptions" } */
 
 #include <stdio.h>
 #include <stdlib.h>
index 766bbe7d99ad8d6aae1a562cd4aa0e32052a10e1..12b93b272e4ee171bbc3a04dc61cbaceeb3161a5 100644 (file)
@@ -1,4 +1,5 @@
 /* { dg-skip-if "requires hosted libstdc++ for stdlib malloc" { ! hostedlib } } */
+/* { dg-additional-options "-fno-exceptions" } */
 
 #include <stdlib.h>
 
index 77e3e02b80d2e2464b6873cbaf7559e90b424094..8af84ed79ec587cef79933a868dde1f8205e9186 100644 (file)
@@ -1,3 +1,4 @@
+/* { dg-additional-options "-fno-exceptions" } */
 /* { dg-additional-options "-fanalyzer-transitivity" } */
 /* { dg-skip-if "requires hosted libstdc++ for stdlib malloc" { ! hostedlib } } */
 
index 4e408339d67d84c85ff2c6a79bc48680da9efc67..ffb0ffe7d8b9b5e761274a90faa3a4b7abf87fc7 100644 (file)
@@ -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 } } */
 
index 36ec5109ac7def2a712af8950024dbde0302e333..052b4014277ffa7f27e0e1fab1e21e6c4f61d09f 100644 (file)
@@ -1,4 +1,5 @@
 /* { dg-skip-if "requires hosted libstdc++ for stdlib malloc" { ! hostedlib } } */
+/* { dg-additional-options "-fno-exceptions" } */
 
 #include <stdlib.h>
 #include "analyzer-decls.h"
index 70b3edd38f5b5670f50d3417b91e73256d9bdd33..258706c62d080929fe85be9daf2bc9daa59d7cdc 100644 (file)
@@ -1,4 +1,5 @@
 /* { dg-skip-if "requires hosted libstdc++ for stdlib malloc" { ! hostedlib } } */
+/* { dg-additional-options "-fno-exceptions" } */
 
 #include <stdlib.h>
 #include "analyzer-decls.h"
index 2743de5cd8c06b0090d3ee04e6ce2b173b82cdf7..f7c5cbf7829985a31400472e551510cb63a6c216 100644 (file)
@@ -1,4 +1,5 @@
 /* { dg-skip-if "requires hosted libstdc++ for stdlib malloc" { ! hostedlib } } */
+/* { dg-additional-options "-fno-exceptions" } */
 
 #include <stdlib.h>
 #include "analyzer-decls.h"
index f5a39b7a067e93e652c284c0cc1fddd4f0d7f668..ebfd38d8aaf2fc55ad36a169e42586da85fe8e01 100644 (file)
@@ -1,3 +1,5 @@
+/* { dg-additional-options "-fno-exceptions" } */
+
 typedef __SIZE_TYPE__ size_t;
 
 void free(void *);
index 4a08f0f1f50fb778236ae3940fc26fd4b0b9748f..e389e438ea5b4181c7f11f9e3071b70497bb7157 100644 (file)
@@ -1,4 +1,5 @@
 /* { dg-do "compile" } */
+/* { dg-additional-options "-fno-exceptions" } */
 
 /* Minimal replacement of system headers.  */
 #define NULL ((void *) 0)
index 50d338855bcb2f16f6e39513b5e38bdbaa31d81c..19a3023de2bf7f7e5131f7875d6e79e406733a27 100644 (file)
@@ -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.
index 6f65add346b88f81c8c896571084a683ccc592b8..4551746869daa0faedd7b20baf1fae33c5c0fd0f 100644 (file)
@@ -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.
index b20630bb8069ad72a13d5c7b1f2a119a260e2674..fb194adde0e1c68926b82150cf99e24c5dce6b36 100644 (file)
@@ -1,4 +1,5 @@
 /* { dg-additional-options "-O2 -fanalyzer-transitivity" } */
+/* { dg-additional-options "-fno-exceptions" } */
 
 int *wf;
 
index 40241248884865343c3c8a31b91c365cdb7a8099..82411a11534754d7440cf8aad10a2eb77381b2ce 100644 (file)
@@ -1,4 +1,4 @@
-void unknown_fn_1 (void *);
+void unknown_fn_1 (void *) __attribute__((nothrow));
 
 void test_1 (int co, int y)
 {
index 6472e762f0cfa68b586cd5cf7d6e9d839aebb857..b8ddf77ce54a21f4fa3799c9e4946655ed7b8d32 100644 (file)
@@ -4,7 +4,7 @@
 
 void **g;
 
-extern void unknown_fn (void);
+extern void unknown_fn (void) __attribute__((nothrow));
 
 /* Without a call to unknown_fn.  */
 
index 41be8cab0480e433a679b47d4c208a7b71824299..60f3a59822436d8d2609697c92d66b3568b34b2b 100644 (file)
@@ -1,3 +1,5 @@
+/* { dg-additional-options "-fno-exceptions" } */
+
 typedef struct FILE   FILE;
 
 FILE* fopen (const char*, const char*);
index ef7cc5fb49454fdf7abe1f281da8e04cc8c36733..caf5c863eccea39d38df04a078753996cf8ba012 100644 (file)
@@ -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
index 414d57e3a728fb6bac37933f92e6da7a31952b8c..98f656f4c68b6d5e49c7bc2cd7a97f5b1f6be256 100644 (file)
@@ -1,4 +1,5 @@
 /* { dg-skip-if "requires hosted libstdc++ for stdlib malloc" { ! hostedlib } } */
+/* { dg-additional-options "-fno-exceptions" } */
 
 #include <stdlib.h>
 
index bd96a2538540f2442c2457b72c5ac29cd906e1c5..f3098760fb05c612261866ad1c6b908a8b9327cf 100644 (file)
@@ -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 <stdlib.h>
index 9f6ccb6dc275922a49dfb15687c414f5dc477799..960604481b7549cb99463926f8397435564ea543 100644 (file)
@@ -1,3 +1,5 @@
+/* { dg-additional-options "-fno-exceptions" } */
+
 #include <stdio.h>
 
 #define INI_MAX_LINE 200
index 3f90afe2ebb61b0950bf77e817545aa199bac871..915f220dcdf3f608f3c03208d6a312686be86076 100644 (file)
@@ -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 <string.h>
 #include <stdlib.h>
index 868c39368e1df7880a18fbe5207741329f9d42f1..1aa4159e51462c61ab4ac356e7c3d72f530a6a0c 100644 (file)
@@ -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;
 
index 971e8f385740edb68cf7ea4ca7369feacc4bce7f..b60fba013edc5e18391d44bba30606de9de0b10c 100644 (file)
@@ -1,3 +1,5 @@
+/* { dg-additional-options "-fno-exceptions" } */
+
 typedef __SIZE_TYPE__ size_t;
 
 int getrandom (void *__buffer, size_t __length, /* { dg-line getrandom } */
index 2ecad8c2f0ba1e53872ac56ff3df44d7845eec01..78b2204e6f3023263387a7daca777e1cd58dc714 100644 (file)
@@ -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"
 
index 1e3746d91fc96c006091f5f70bbe6f1e004a904b..fa82e434f0e1143310c68ff00d7819410d80b521 100644 (file)
@@ -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 (file)
index 0000000..4671e62
--- /dev/null
@@ -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 (file)
index 0000000..6572113
--- /dev/null
@@ -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 (file)
index 0000000..9847203
--- /dev/null
@@ -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 (file)
index 0000000..25467ea
--- /dev/null
@@ -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 (file)
index 0000000..0e0eef8
--- /dev/null
@@ -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 (file)
index 0000000..3684d66
--- /dev/null
@@ -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 (file)
index 0000000..7280d32
--- /dev/null
@@ -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 (file)
index 0000000..d99e53c
--- /dev/null
@@ -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 (file)
index 0000000..cb70230
--- /dev/null
@@ -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 (file)
index 0000000..9625748
--- /dev/null
@@ -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 (file)
index 0000000..486ca19
--- /dev/null
@@ -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 (file)
index 0000000..8aa9814
--- /dev/null
@@ -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 (file)
index 0000000..d185056
--- /dev/null
@@ -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 (file)
index 0000000..2608f17
--- /dev/null
@@ -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 (file)
index 0000000..a52312a
--- /dev/null
@@ -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 (file)
index 0000000..055fe27
--- /dev/null
@@ -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 (file)
index 0000000..fc18a21
--- /dev/null
@@ -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 (file)
index 0000000..10484fd
--- /dev/null
@@ -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 (file)
index 0000000..22242a3
--- /dev/null
@@ -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 (file)
index 0000000..c6aa415
--- /dev/null
@@ -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 (file)
index 0000000..5e6a3c0
--- /dev/null
@@ -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 (file)
index 0000000..79df330
--- /dev/null
@@ -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 (file)
index 0000000..e9fb617
--- /dev/null
@@ -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 (file)
index 0000000..0d06dd8
--- /dev/null
@@ -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 (file)
index 0000000..ef9dd46
--- /dev/null
@@ -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 (file)
index 0000000..2ec4e06
--- /dev/null
@@ -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;
+}
index 0573d309ba25e95d8dbf3156694014ca9c35f42f..53bfa2953ec059d8e691afe0a941d97d6638c646 100644 (file)
@@ -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 (file)
index 0000000..4eb2672
--- /dev/null
@@ -0,0 +1,9 @@
+// { dg-require-effective-target c++14_down }
+// { dg-additional-options "-Wno-deprecated-declarations" }
+
+#include <exception>
+
+void test_1 ()
+{
+  std::unexpected ();
+}
index c54d1fd5b8456b523084cf658678994de67a92d7..0436572624d64033abf9f97e5657bc626e7d4c66 100644 (file)
@@ -1,5 +1,5 @@
 // { dg-additional-options "-fanalyzer" }
-// { dg-excess-errors "lots of analyzer output, but no ICE" }
+
 namespace std {
 template <typename _Result> struct coroutine_traits : _Result {};
 template <typename = void> struct coroutine_handle {