From 0a2c4b6dd2019d144df49227fce950e10ae62b09 Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Mon, 3 Nov 2025 21:42:59 -0500 Subject: [PATCH] analyzer: add event kinds for special control flow [PR122544] The SARIF 3.38.8 "kinds" property has some verbs for expressing control flow, but is missing some of the awkward special cases. The spec says "If none of these values are appropriate, a SARIF producer MAY use any value." This patch adds the following new values: * "throw" for throwing an exception * "catch" for catching an exception * "unwind" for unwinding stack frame(s) during exception-handling * "setjmp" for calls to setjmp * "longjmp" for calls to longjmp that rewind the program counter/stack to the location of a previous setjmp call gcc/analyzer/ChangeLog: PR analyzer/122544 * checker-event.cc (catch_cfg_edge_event::get_meaning): New. (setjmp_event::get_meaning): New. (rewind_event::get_meaning): New. (throw_event::get_meaning): New. (unwind_event::get_meaning): New. * checker-event.h (catch_cfg_edge_event::get_meaning): New decl. (setjmp_event::get_meaning): New decl. (rewind_event::get_meaning): New decl. (throw_event::get_meaning): New decl. (unwind_event::get_meaning): New decl. gcc/ChangeLog: PR analyzer/122544 * diagnostics/paths.cc (event::meaning::maybe_get_verb_str): Handle the new verbs. * diagnostics/paths.h (event::meaning::verb): Add new values for special control flow operations. (event::meaning::meaning): Add ctor taking just a verb. gcc/testsuite/ChangeLog: PR analyzer/122544 * g++.dg/analyzer/exception-path-1-sarif.py: New test script. * g++.dg/analyzer/exception-path-1.C: Add SARIF output, and use the above to check it. * g++.dg/analyzer/exception-path-unwind-multiple-2-sarif.py: New test script. * g++.dg/analyzer/exception-path-unwind-multiple-2.C: Add SARIF output, and use the above to check it. * gcc.dg/analyzer/setjmp-3-sarif.py: New test script. * gcc.dg/analyzer/setjmp-3.c: Add SARIF output, and use the above to check it. Signed-off-by: David Malcolm --- gcc/analyzer/checker-event.cc | 32 +++++++++++++++++++ gcc/analyzer/checker-event.h | 10 ++++++ gcc/diagnostics/paths.cc | 16 ++++++++++ gcc/diagnostics/paths.h | 13 +++++++- .../g++.dg/analyzer/exception-path-1-sarif.py | 22 +++++++++++++ .../g++.dg/analyzer/exception-path-1.C | 9 ++++++ .../exception-path-unwind-multiple-2-sarif.py | 23 +++++++++++++ .../exception-path-unwind-multiple-2.C | 9 ++++++ .../gcc.dg/analyzer/setjmp-3-sarif.py | 23 +++++++++++++ gcc/testsuite/gcc.dg/analyzer/setjmp-3.c | 9 ++++++ 10 files changed, 165 insertions(+), 1 deletion(-) create mode 100644 gcc/testsuite/g++.dg/analyzer/exception-path-1-sarif.py create mode 100644 gcc/testsuite/g++.dg/analyzer/exception-path-unwind-multiple-2-sarif.py create mode 100644 gcc/testsuite/gcc.dg/analyzer/setjmp-3-sarif.py diff --git a/gcc/analyzer/checker-event.cc b/gcc/analyzer/checker-event.cc index 790ebc714380..3e54c2a408c0 100644 --- a/gcc/analyzer/checker-event.cc +++ b/gcc/analyzer/checker-event.cc @@ -833,6 +833,14 @@ start_cfg_edge_event::should_print_expr_p (tree expr) return false; } +/* class catch_cfg_edge_event : public cfg_edge_event. */ + +diagnostics::paths::event::meaning +catch_cfg_edge_event::get_meaning () const +{ + return meaning (verb::catch_); +} + /* class call_event : public superedge_event. */ /* call_event's ctor. */ @@ -1034,6 +1042,12 @@ setjmp_event::print_desc (pretty_printer &pp) const get_user_facing_name (m_setjmp_call)); } +diagnostics::paths::event::meaning +setjmp_event::get_meaning () const +{ + return meaning (verb::setjmp_); +} + /* Implementation of checker_event::prepare_for_emission vfunc for setjmp_event. Record this setjmp's event ID into the path, so that rewind events can @@ -1066,6 +1080,12 @@ rewind_event::get_setjmp_caller () const return m_eedge->m_dest->get_function ()->decl; } +diagnostics::paths::event::meaning +rewind_event::get_meaning () const +{ + return meaning (verb::longjmp_); +} + /* rewind_event's ctor. */ rewind_event::rewind_event (const exploded_edge *eedge, @@ -1163,6 +1183,12 @@ rewind_to_setjmp_event::prepare_for_emission (checker_path *path, /* class throw_event : public checker_event. */ +diagnostics::paths::event::meaning +throw_event::get_meaning () const +{ + return meaning (verb::throw_); +} + /* class explicit_throw_event : public throw_event. */ void explicit_throw_event::print_desc (pretty_printer &pp) const @@ -1205,6 +1231,12 @@ unwind_event::print_desc (pretty_printer &pp) const pp_printf (&pp, "unwinding stack frame"); } +diagnostics::paths::event::meaning +unwind_event::get_meaning () const +{ + return meaning (verb::unwind_); +} + /* class warning_event : public checker_event. */ /* Implementation of diagnostics::paths::event::print_desc vfunc for diff --git a/gcc/analyzer/checker-event.h b/gcc/analyzer/checker-event.h index 909e3889e28a..fc51be10c956 100644 --- a/gcc/analyzer/checker-event.h +++ b/gcc/analyzer/checker-event.h @@ -539,6 +539,8 @@ public: pp_string (&pp, "...catching exception here"); } + meaning get_meaning () const override; + private: tree m_type; }; @@ -666,6 +668,8 @@ public: void print_desc (pretty_printer &pp) const final override; + meaning get_meaning () const override; + void prepare_for_emission (checker_path *path, pending_diagnostic *pd, diagnostics::paths::event_id_t emission_id) final override; @@ -688,6 +692,8 @@ public: tree get_setjmp_caller () const; const exploded_edge *get_eedge () const { return m_eedge; } + meaning get_meaning () const override; + protected: rewind_event (const exploded_edge *eedge, enum event_kind kind, @@ -754,6 +760,8 @@ public: { } + meaning get_meaning () const override; + protected: const exploded_node *m_enode; const gcall &m_throw_call; @@ -817,6 +825,8 @@ public: { } + meaning get_meaning () const override; + void print_desc (pretty_printer &pp) const final override; int m_num_frames; diff --git a/gcc/diagnostics/paths.cc b/gcc/diagnostics/paths.cc index 824b810cb3bf..8e29dae2cd71 100644 --- a/gcc/diagnostics/paths.cc +++ b/gcc/diagnostics/paths.cc @@ -97,6 +97,22 @@ event::meaning::maybe_get_verb_str (enum verb v) return "branch"; case verb::danger: return "danger"; + + /* Special control flow operations. + + These are not part of SARIF v2.1.0 section 3.38.8, but the + spec allows other values; see + https://github.com/oasis-tcs/sarif-spec/issues/735 */ + case verb::throw_: + return "throw"; + case verb::catch_: + return "catch"; + case verb::unwind_: + return "unwind"; + case verb::setjmp_: + return "setjmp"; + case verb::longjmp_: + return "longjmp"; } } diff --git a/gcc/diagnostics/paths.h b/gcc/diagnostics/paths.h index d30c4203ec5e..f7dff8d7c691 100644 --- a/gcc/diagnostics/paths.h +++ b/gcc/diagnostics/paths.h @@ -96,7 +96,14 @@ class event return_, branch, - danger + danger, + + // Special control flow operations: + throw_, + catch_, + unwind_, // unwinding stack frame(s) during exception-handling + setjmp_, + longjmp_ }; enum class noun { @@ -131,6 +138,10 @@ class event m_property (property::unknown) { } + meaning (enum verb verb) + : m_verb (verb), m_noun (noun::unknown), m_property (property::unknown) + { + } meaning (enum verb verb, enum noun noun) : m_verb (verb), m_noun (noun), m_property (property::unknown) { diff --git a/gcc/testsuite/g++.dg/analyzer/exception-path-1-sarif.py b/gcc/testsuite/g++.dg/analyzer/exception-path-1-sarif.py new file mode 100644 index 000000000000..8958d96b81a8 --- /dev/null +++ b/gcc/testsuite/g++.dg/analyzer/exception-path-1-sarif.py @@ -0,0 +1,22 @@ +from sarif import * + +import pytest + +@pytest.fixture(scope='function', autouse=True) +def sarif(): + return sarif_from_env() + +def test_kinds(sarif): + result = get_result_by_index(sarif, 0) + + assert result['level'] == 'note' + + events = result["codeFlows"][0]["threadFlows"][0]['locations'] + + # Event "(1)": "throwing exception of type 'value_error' here..." (index == 0) + assert events[0]['location']['message']['text'] == "throwing exception of type 'value_error' here..." + assert events[0]['kinds'] == ["throw"] + + # Event "(2)": "...catching exception of type 'value_error' here" (index == 1) + assert events[1]['location']['message']['text'] == "...catching exception of type 'value_error' here" + assert events[1]['kinds'] == ["catch"] diff --git a/gcc/testsuite/g++.dg/analyzer/exception-path-1.C b/gcc/testsuite/g++.dg/analyzer/exception-path-1.C index 486ca1938923..d923d62121bf 100644 --- a/gcc/testsuite/g++.dg/analyzer/exception-path-1.C +++ b/gcc/testsuite/g++.dg/analyzer/exception-path-1.C @@ -1,3 +1,5 @@ +/* { dg-additional-options "-fdiagnostics-add-output=sarif" } */ + /* Verify that we follow the correct paths when we know the typeinfo of an exception. */ @@ -32,3 +34,10 @@ int test () __analyzer_dump_path (); // { dg-bogus "path" } return 0; } + +/* Verify that some JSON was written to a file with the expected name. */ +/* { dg-final { verify-sarif-file } } */ + +/* Use a Python script to verify various properties about the generated + .sarif file: + { dg-final { run-sarif-pytest exception-path-1.C "exception-path-1-sarif.py" } } */ diff --git a/gcc/testsuite/g++.dg/analyzer/exception-path-unwind-multiple-2-sarif.py b/gcc/testsuite/g++.dg/analyzer/exception-path-unwind-multiple-2-sarif.py new file mode 100644 index 000000000000..b817a6434b07 --- /dev/null +++ b/gcc/testsuite/g++.dg/analyzer/exception-path-unwind-multiple-2-sarif.py @@ -0,0 +1,23 @@ +from sarif import * + +import pytest + +@pytest.fixture(scope='function', autouse=True) +def sarif(): + return sarif_from_env() + +def test_kinds(sarif): + result = get_result_by_index(sarif, 0) + + assert result['level'] == 'note' + + events = result["codeFlows"][0]["threadFlows"][0]['locations'] + + assert events[-4]['location']['message']['text'] == "throwing exception of type 'value_error' here..." + assert events[-4]['kinds'] == ["throw"] + + assert events[-3]['location']['message']['text'] == "unwinding 2 stack frames" + assert events[-3]['kinds'] == ["unwind"] + + assert events[-2]['location']['message']['text'] == "...catching exception of type 'value_error' here" + assert events[-2]['kinds'] == ["catch"] 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 index 2608f17079d3..aa1ff89ffb51 100644 --- a/gcc/testsuite/g++.dg/analyzer/exception-path-unwind-multiple-2.C +++ b/gcc/testsuite/g++.dg/analyzer/exception-path-unwind-multiple-2.C @@ -1,3 +1,5 @@ +/* { dg-additional-options "-fdiagnostics-add-output=sarif" } */ + /* 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. */ @@ -53,3 +55,10 @@ int outer () __analyzer_dump_path (); // { dg-bogus "path" } return 0; } + +/* Verify that some JSON was written to a file with the expected name. */ +/* { dg-final { verify-sarif-file } } */ + +/* Use a Python script to verify various properties about the generated + .sarif file: + { dg-final { run-sarif-pytest exception-path-unwind-multiple-2.C "exception-path-unwind-multiple-2-sarif.py" } } */ diff --git a/gcc/testsuite/gcc.dg/analyzer/setjmp-3-sarif.py b/gcc/testsuite/gcc.dg/analyzer/setjmp-3-sarif.py new file mode 100644 index 000000000000..922d3380ecb8 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/setjmp-3-sarif.py @@ -0,0 +1,23 @@ +from sarif import * + +import pytest + +@pytest.fixture(scope='function', autouse=True) +def sarif(): + return sarif_from_env() + +def test_kinds(sarif): + result = get_result_by_index(sarif, 0) + + assert result['level'] == 'note' + + events = result["codeFlows"][0]["threadFlows"][0]['locations'] + + assert events[1]['location']['message']['text'] == "'setjmp' called here" + assert events[1]['kinds'] == ["setjmp"] + + assert events[6]['location']['message']['text'] == "rewinding from 'longjmp' in 'inner'..." + assert events[6]['kinds'] == ["longjmp"] + + assert events[7]['location']['message']['text'].startswith("...to 'setjmp' in 'outer'") + assert events[7]['kinds'] == ["longjmp"] diff --git a/gcc/testsuite/gcc.dg/analyzer/setjmp-3.c b/gcc/testsuite/gcc.dg/analyzer/setjmp-3.c index 3e4f870e5e8f..a19ce8412b8f 100644 --- a/gcc/testsuite/gcc.dg/analyzer/setjmp-3.c +++ b/gcc/testsuite/gcc.dg/analyzer/setjmp-3.c @@ -1,4 +1,6 @@ /* { dg-additional-options "-fdiagnostics-show-line-numbers -fdiagnostics-path-format=inline-events -fdiagnostics-show-caret" } */ +/* { dg-additional-options "-fdiagnostics-add-output=sarif" } */ + /* { dg-enable-nn-line-numbers "" } */ /* { dg-require-effective-target indirect_jumps } */ @@ -107,3 +109,10 @@ void outer (void) | | (11) here | { dg-end-multiline-output "" } */ + +/* Verify that some JSON was written to a file with the expected name. */ +/* { dg-final { verify-sarif-file } } */ + +/* Use a Python script to verify various properties about the generated + .sarif file: + { dg-final { run-sarif-pytest setjmp-3.c "setjmp-3-sarif.py" } } */ -- 2.47.3