From patchwork Sat Jul 27 00:46:07 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: David Malcolm X-Patchwork-Id: 1965592 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=O4U83U5W; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=gcc.gnu.org (client-ip=2620:52:3:1:0:246e:9693:128c; helo=server2.sourceware.org; envelope-from=gcc-patches-bounces~incoming=patchwork.ozlabs.org@gcc.gnu.org; receiver=patchwork.ozlabs.org) Received: from server2.sourceware.org (server2.sourceware.org [IPv6:2620:52:3:1:0:246e:9693:128c]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (secp384r1) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4WW5YL5KtFz1ybY for ; Sat, 27 Jul 2024 10:46:58 +1000 (AEST) Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 5E9603858428 for ; Sat, 27 Jul 2024 00:46:56 +0000 (GMT) X-Original-To: gcc-patches@gcc.gnu.org Delivered-To: gcc-patches@gcc.gnu.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by sourceware.org (Postfix) with ESMTP id 8FC783858D35 for ; Sat, 27 Jul 2024 00:46:15 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 8FC783858D35 Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 8FC783858D35 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1722041183; cv=none; b=gs3c6va86il68lS3KQW3C/eki7Vg9dlIfcFa3UFwBFOKfgaVHYBRApqa+pKtqqeEtd2USnYVDr3z7Db/9X3+cdUUUf3ZXu8TFw6FVc7mDcTBBKJvKiFKcMPP4zhCcdc5ndUiwo9g13LjndungVXP9JNPvlm9xlrMHvAgjPuV9GA= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1722041183; c=relaxed/simple; bh=glV1/DvTfLl/Xlc15L2iXrgjmQHVAHxE/BRsFObfglE=; h=DKIM-Signature:From:To:Subject:Date:Message-Id:MIME-Version; b=IRKhPceEoenxug01o6EJyT3iVCxWkYCZFGLLj0X8gr7rGDNOJ2gUU7I9Y+UP+xc7BZJ/ePHMNzEFoj+tgRx2R1mVDANwqxqKmFLrDI5RTqgad36vTNPu+2OFjD1BBUcnoOteqpLwJz2IbMDJdxFudIcaBadkhyUMLXX3Jywh9z0= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1722041175; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=0xzB/rq1D7Qeb8Mv/ZlEPP+vUy+d6Juo8EkYmJp00Qg=; b=O4U83U5WrpUXQSx29lIm5IuXtFLu+UC8JbIlAapzodWO0DP1YN/4a7BGfbkzpPZar0BuVZ C/bVBAaWmOx3Adeyiht7L8GrrWrA57wxfXIhdADjsrrvNe5QU0HgPCbAvjqnECaFjd3ZdG B/1fWLi2Z7tOk3cAmlZf4y1X0Of4eXM= Received: from mx-prod-mc-02.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-43-prSUDknkNf-HoeLSt97x0g-1; Fri, 26 Jul 2024 20:46:13 -0400 X-MC-Unique: prSUDknkNf-HoeLSt97x0g-1 Received: from mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.17]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-02.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 19F5D1955F0D for ; Sat, 27 Jul 2024 00:46:12 +0000 (UTC) Received: from t14s.localdomain.com (unknown [10.22.33.183]) by mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 78CAD1955E89; Sat, 27 Jul 2024 00:46:09 +0000 (UTC) From: David Malcolm To: gcc-patches@gcc.gnu.org Cc: David Malcolm Subject: [pushed] =?utf-8?q?diagnostics=3A_SARIF_output=3A_capture_=23includ?= =?utf-8?q?e_information_=28PR_107941=3B__=C2=A73=2E34=29?= Date: Fri, 26 Jul 2024 20:46:07 -0400 Message-Id: <20240727004607.709328-1-dmalcolm@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.0 on 10.30.177.17 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-11.3 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_ASCII_DIVIDERS, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H3, RCVD_IN_MSPIKE_WL, SPF_HELO_NONE, SPF_NONE, TXREP autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org X-BeenThere: gcc-patches@gcc.gnu.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Gcc-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: gcc-patches-bounces~incoming=patchwork.ozlabs.org@gcc.gnu.org This patch extends our SARIF output to capture relationships between locations within a result (ยง3.34). In particular, this captures chains of #includes relating to diagnostics and to events within diagnostic paths. For example, consider: include-chain-1.c: #include "include-chain-1.h" include-chain-1.h: /* First set of decls, which will be referenced in notes. */ #include "include-chain-1-1.h" /* Second set of decls, which will trigger the errors. */ #include "include-chain-1-2.h" include-chain-1-1.h: int p; int q; include-chain-1-1.h: char p; char q; GCC's textual output emits: In file included from PATH/include-chain-1.h:5, from PATH/include-chain-1.c:30: PATH/include-chain-1-2.h:1:6: error: conflicting types for 'p'; have 'char' 1 | char p; | ^ In file included from PATH/include-chain-1.h:2: PATH/include-chain-1-1.h:1:5: note: previous declaration of 'p' with type 'int' 1 | int p; | ^ PATH/include-chain-1-2.h:2:6: error: conflicting types for 'q'; have 'char' 2 | char q; | ^ PATH/include-chain-1-1.h:2:5: note: previous declaration of 'q' with type 'int' 2 | int q; | ^ With this patch, the SARIF output captures the include information for the two results, so that e.g. result[0]'s location[0] has: "relationships": [{"target": 0, "kinds": ["isIncludedBy"]}], "id": 0 and the "note" in relatedLocations[0] has: "message": {"text": "previous declaration of 'q' with type 'int'"}, "relationships": [{"target": 2, "kinds": ["isIncludedBy"]}], "id": 2}, where these reference new locations within relatedLocations, such as this for the "#include "include-chain-1-1.h" line in include-chain-1.h: {"physicalLocation": {"artifactLocation": {"uri": include-chain-1.h", "uriBaseId": "PWD"}, "region": {"startLine": 5}, "contextRegion": {"startLine": 5, "snippet": {"text": "#include \"include-chain-1-2.h\"\n"}}}, "id": 1, "relationships": [{"target": 0, "kinds": ["includes"]}, {"target": 4, "kinds": ["isIncludedBy"]}]}, effectively capturing the inclusion digraph in SARIF form: +-----------------------------------+ +----------------------------------+ |"id": 0 | |"id": 2 | | error: "conflicting types for 'p';| | note: previous declaration of 'p'| | have 'char'"| | | with type 'int'") | | in include-chain-1-2.h | | in include-chain-1-1.h | +-----------------------------------+ +----------------------------------+ | | | included-by | included-by V V +--------------------------------+ +--------------------------------+ |"id": 1 | |"id": 3 | | #include "include-chain-1-2.h" | | #include "include-chain-1-1.h" | | in include-chain-1.h | | in include-chain-1.h | +--------------------------------+ +--------------------------------+ | | | included-by | included-by V V +------------------------------------+ |"id": 4 | | The #include "include-chain-1.h" | | in include-chain-1.c | +------------------------------------+ Locations only gain "id" fields if they need one, and the precise numbering of the IDs within a result is an implementation detail (the order in which references to the locations are made). To test all this non-trivial JSON from DejaGnu I needed to adapt the python testing code used by gcov, adding a new run-sarif-pytest based on run-gcov-pytest. Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu. Successful run of analyzer integration tests on x86_64-pc-linux-gnu. Pushed to trunk as r15-2354-g4d1f71d49e396c. gcc/ChangeLog: PR middle-end/107941 * diagnostic-format-sarif.cc: Define INCLUDE_LIST and INCLUDE_MAP. (enum class location_relationship_kind): New. (diagnostic_artifact_role::scanned_file): New value. (class sarif_location_manager): New. (class sarif_result): Derive from sarif_location_manager rather than directly from sarif_object. (sarif_result::add_related_location): Convert to vfunc implementation. (sarif_location::m_relationships_map): New field. (class sarif_location_relationship): New. (class sarif_ice_notification): Derive from sarif_location_manager rather than directly from sarif_object. (sarif_builder::take_current_result): New. (sarif_builder::m_line_maps): New field. (sarif_builder::m_cur_group_result): Convert to std::unique_ptr. (sarif_artifact::add_role): Skip scanned_file. (get_artifact_role_string): Handle scanned_file. (sarif_location_manager::add_relationship_to_worklist): New. (sarif_location_manager::process_worklist): New. (sarif_location_manager::process_worklist_item): New. (sarif_result::on_nested_diagnostic): Pass *this to make_location_object. (sarif_location::lazily_add_id): New. (sarif_location::get_id): New. (get_string_for_location_relationship_kind): New. (sarif_location::lazily_add_relationship): New. (sarif_location::lazily_add_relationship_object): New. (sarif_location::lazily_add_relationships_array): New. (sarif_ice_notification::sarif_ice_notification): Fix overlong line. Pass *this to make_locations_arr. (sarif_ice_notification::add_related_location): New. (sarif_location_relationship::sarif_location_relationship): New. (sarif_location_relationship::get_target_id): New. (sarif_location_relationship::lazily_add_kind): New. (sarif_builder::sarif_builder): Add "line_maps" param and use it to initialize m_line_maps. (sarif_builder::end_diagnostic): Update for m_cur_group_result becoming a std::unique_ptr. Don't append to m_results_array yet. (sarif_builder::end_group): Append m_cur_group_result to m_results_array here, rather than in end_diagnostic. (sarif_builder::make_result_object): Pass result_obj to make_locations_arr and to make_code_flow_object. (sarif_builder::make_locations_arr): Add "loc_mgr" param and pass it to make_location_object. (sarif_builder::make_location_object): For two overloads, add "loc_mgr" param and call add_any_include_chain on the location. (sarif_builder::add_any_include_chain): New. (sarif_builder::make_location_object): New overload. (sarif_builder::make_code_flow_object): Add "result" param and pass it to make_thread_flow_location_object. (sarif_builder::make_thread_flow_location_object): Add "result" param and pass it to make_location_object. (sarif_builder::get_or_create_artifact): Handle scanned_file. (sarif_output_format::~sarif_output_format): Assert that there isn't a pending result. (sarif_output_format::sarif_output_format): Add "line_maps" param and pass it to m_builder's ctor. (sarif_stream_output_format::sarif_stream_output_format): Add "line_maps" param and pass it to base class ctor. (sarif_file_output_format::sarif_file_output_format): Likewise. (diagnostic_output_format_init_sarif_stderr): Pass "line_table" global to format. (diagnostic_output_format_init_sarif_file): Likewise. (diagnostic_output_format_init_sarif_stream): Likewise. (test_sarif_diagnostic_context::test_sarif_diagnostic_context): Likewise. (buffered_output_format::buffered_output_format): Likewise. (selftest::test_make_location_object): Likewise. (selftest::test_make_location_object): Create a sarif_result for use when calling make_location_object. * diagnostic.cc (diagnostic_context::finish): End any active diagnostic groups. (diagnostic_context::report_diagnostic): Assert that we're within a diagnostic group. * diagnostic.h (diagnostic_report_diagnostic): Add begin_group/end_group pair around call to diagnostic_context::report_diagnostic. * selftest-diagnostic.cc (test_diagnostic_context::report): Add begin_group/end_group pair around diagnostic_impl call. gcc/testsuite/ChangeLog: PR middle-end/107941 * gcc.dg/sarif-output/include-chain-1-1.h: New test. * gcc.dg/sarif-output/include-chain-1-2.h: New test. * gcc.dg/sarif-output/include-chain-1.c: New test. * gcc.dg/sarif-output/include-chain-1.h: New test. * gcc.dg/sarif-output/include-chain-2.c: New test. * gcc.dg/sarif-output/include-chain-2.h: New test. * gcc.dg/sarif-output/sarif-output.exp: New file. * gcc.dg/sarif-output/sarif.py: New test, adapted from g++.dg/gcov/gcov.py. * gcc.dg/sarif-output/test-include-chain-1.py: New test. * gcc.dg/sarif-output/test-include-chain-2.py: New test. * lib/scansarif.exp (sarif-pytest-format-line): New, taken from lib/gcov.exp. (run-sarif-pytest): New, adapted from run-gcov-pytest in lib/gcov.exp. Signed-off-by: David Malcolm --- gcc/diagnostic-format-sarif.cc | 680 ++++++++++++++++-- gcc/diagnostic.cc | 13 + gcc/diagnostic.h | 5 +- gcc/selftest-diagnostic.cc | 2 + .../gcc.dg/sarif-output/include-chain-1-1.h | 2 + .../gcc.dg/sarif-output/include-chain-1-2.h | 2 + .../gcc.dg/sarif-output/include-chain-1.c | 42 ++ .../gcc.dg/sarif-output/include-chain-1.h | 5 + .../gcc.dg/sarif-output/include-chain-2.c | 40 ++ .../gcc.dg/sarif-output/include-chain-2.h | 7 + .../gcc.dg/sarif-output/sarif-output.exp | 31 + gcc/testsuite/gcc.dg/sarif-output/sarif.py | 21 + .../sarif-output/test-include-chain-1.py | 125 ++++ .../sarif-output/test-include-chain-2.py | 124 ++++ gcc/testsuite/lib/scansarif.exp | 54 ++ 15 files changed, 1109 insertions(+), 44 deletions(-) create mode 100644 gcc/testsuite/gcc.dg/sarif-output/include-chain-1-1.h create mode 100644 gcc/testsuite/gcc.dg/sarif-output/include-chain-1-2.h create mode 100644 gcc/testsuite/gcc.dg/sarif-output/include-chain-1.c create mode 100644 gcc/testsuite/gcc.dg/sarif-output/include-chain-1.h create mode 100644 gcc/testsuite/gcc.dg/sarif-output/include-chain-2.c create mode 100644 gcc/testsuite/gcc.dg/sarif-output/include-chain-2.h create mode 100644 gcc/testsuite/gcc.dg/sarif-output/sarif-output.exp create mode 100644 gcc/testsuite/gcc.dg/sarif-output/sarif.py create mode 100644 gcc/testsuite/gcc.dg/sarif-output/test-include-chain-1.py create mode 100644 gcc/testsuite/gcc.dg/sarif-output/test-include-chain-2.py diff --git a/gcc/diagnostic-format-sarif.cc b/gcc/diagnostic-format-sarif.cc index 1fc45c9b4b39..84b3e651e462 100644 --- a/gcc/diagnostic-format-sarif.cc +++ b/gcc/diagnostic-format-sarif.cc @@ -20,6 +20,8 @@ along with GCC; see the file COPYING3. If not see #include "config.h" +#define INCLUDE_LIST +#define INCLUDE_MAP #define INCLUDE_MEMORY #define INCLUDE_VECTOR #include "system.h" @@ -60,11 +62,13 @@ class sarif_tool; // 3.18 class sarif_tool_component; // 3.19 class sarif_invocation; // 3.20 class sarif_artifact; // 3.24 +class sarif_location_manager; // not in the spec class sarif_result; // 3.27 class sarif_location; // 3.28 class sarif_physical_location; // 3.29 class sarif_region; // 3.30 class sarif_logical_location; // 3.33 +class sarif_location_relationship; // 3.34 class sarif_code_flow; // 3.36 class sarif_thread_flow; // 3.37 class sarif_thread_flow_location; // 3.38 @@ -76,6 +80,17 @@ class sarif_artifact_change; // 3.56 class sarif_replacement; // 3.57 class sarif_ice_notification; // 3.58 +// Valid values for locationRelationship's "kinds" property (3.34.3) + +enum class location_relationship_kind +{ + includes, + is_included_by, + relevant, + + NUM_KINDS +}; + /* Declarations of subclasses of sarif_object. Keep these in order of their descriptions in the specification. */ @@ -166,6 +181,11 @@ enum class diagnostic_artifact_role analysis_target, /* "analysisTarget". */ debug_output_file, /* "debugOutputFile". */ result_file, /* "resultFile". */ + + /* "scannedFile" added in 2.2; + see https://github.com/oasis-tcs/sarif-spec/issues/459 */ + scanned_file, + traced_file, /* "tracedFile". */ NUM_ROLES @@ -203,10 +223,160 @@ private: bool m_embed_contents; }; +/* A class for sarif_objects that own a "namespace" of numeric IDs for + managing location objects within them. Currently (SARIF v2.1.0) + this is just for sarif_result (section 3.28.2), but it will likely + eventually also be for notification objects; see + https://github.com/oasis-tcs/sarif-spec/issues/540 + + Consider locations with chains of include information e.g. + + > include-chain-1.c: + > #include "include-chain-1.h" + + include-chain-1.h: + | // First set of decls, which will be referenced in notes + | #include "include-chain-1-1.h" + | + | // Second set of decls, which will trigger the errors + | #include "include-chain-1-2.h" + + include-chain-1-1.h: + | int p; + | int q; + + include-chain-1-1.h: + | char p; + | char q; + + GCC's textual output emits: + | In file included from PATH/include-chain-1.h:5, + | from PATH/include-chain-1.c:30: + | PATH/include-chain-1-2.h:1:6: error: conflicting types for 'p'; have 'char' + | 1 | char p; + | | ^ + | In file included from PATH/include-chain-1.h:2: + | PATH/include-chain-1-1.h:1:5: note: previous declaration of 'p' with type 'int' + | 1 | int p; + | | ^ + | PATH/include-chain-1-2.h:2:6: error: conflicting types for 'q'; have 'char' + | 2 | char q; + | | ^ + | PATH/include-chain-1-1.h:2:5: note: previous declaration of 'q' with type 'int' + | 2 | int q; + | | ^ + + Whenever a SARIF location is added for a location_t that + was #included from somewhere, we queue up the creation of a SARIF + location for the location of the #include. The worklist of queued + locations is flushed when the result is finished, which lazily creates + any additional related locations for the include chain, and the + relationships between the locations. Doing so can lead to further + include locations being processed. The worklist approach allows us + to lazily explore the relevant part of the directed graph of location_t + values implicit in our line_maps structure, replicating it as a directed + graph of SARIF locations within the SARIF result object, like this: + + [0]: error in include-chain-1-2.h ("conflicting types for 'p'; have 'char'") + [1]: #include "include-chain-1-2.h" in include-chain-1.h + [2]: note in include-chain-1-2.h ("previous declaration of 'p' with type 'int'") + [3]: #include "include-chain-1-1.h" in include-chain-1.h + [4]: #include "include-chain-1.h" in include-chain-1.c + + where we want to capture this "includes" graph in SARIF form: + . +-----------------------------------+ +----------------------------------+ + . |"id": 0 | |"id": 2 | + . | error: "conflicting types for 'p';| | note: previous declaration of 'p'| + . | have 'char'"| | | with type 'int'") | + . | in include-chain-1-2.h | | in include-chain-1-1.h | + . +-----------------------------------+ +----------------------------------+ + . | | + . | included-by | included-by + . V V + . +--------------------------------+ +--------------------------------+ + . |"id": 1 | |"id": 3 | + . | #include "include-chain-1-2.h" | | #include "include-chain-1-1.h" | + . | in include-chain-1.h | | in include-chain-1.h | + . +--------------------------------+ +--------------------------------+ + . | | + . | included-by | included-by + . V V + . +------------------------------------+ + . |"id": 4 | + . | The #include "include-chain-1.h" | + . | in include-chain-1.c | + . +------------------------------------+ + */ + +class sarif_location_manager : public sarif_object +{ +public: + /* A worklist of pending actions needed to fully process this object. + + This lets us lazily walk our data structures to build the + directed graph of locations, whilst keeping "notes" at the top + of the "relatedLocations" array, and avoiding the need for + recursion. */ + struct worklist_item + { + enum class kind + { + /* Process a #include relationship where m_location_obj + was #included-d at m_where. */ + included_from + }; + + worklist_item (sarif_location &location_obj, + enum kind kind, + location_t where) + : m_location_obj (location_obj), + m_kind (kind), + m_where (where) + { + } + + sarif_location &m_location_obj; + enum kind m_kind; + location_t m_where; + }; + + sarif_location_manager () + : m_next_location_id (0) + { + } + + unsigned allocate_location_id () + { + return m_next_location_id++; + } + + virtual void + add_related_location (std::unique_ptr location_obj) = 0; + + void + add_relationship_to_worklist (sarif_location &location_obj, + enum worklist_item::kind kind, + location_t where); + + void + process_worklist (sarif_builder &builder); + + void + process_worklist_item (sarif_builder &builder, + const worklist_item &item); +private: + unsigned m_next_location_id; + + std::list m_worklist; + std::map m_included_from_locations; +}; + /* Subclass of sarif_object for SARIF "result" objects - (SARIF v2.1.0 section 3.27). */ + (SARIF v2.1.0 section 3.27). + Each SARIF result object has its own "namespace" of numeric IDs for + managing location objects (SARIF v2.1.0 section 3.28.2). */ -class sarif_result : public sarif_object +class sarif_result : public sarif_location_manager { public: sarif_result () : m_related_locations_arr (nullptr) {} @@ -220,17 +390,39 @@ public: const diagnostic_diagram &diagram, sarif_builder &builder); -private: void - add_related_location (std::unique_ptr location_obj); + add_related_location (std::unique_ptr location_obj) + final override; +private: json::array *m_related_locations_arr; // borrowed }; /* Subclass of sarif_object for SARIF "location" objects - (SARIF v2.1.0 section 3.28). */ + (SARIF v2.1.0 section 3.28). + A location object can have an "id" which must be unique within + the enclosing result, if any (see SARIF v2.1.0 section 3.28.2). */ + +class sarif_location : public sarif_object +{ +public: + long lazily_add_id (sarif_location_manager &loc_mgr); + long get_id () const; + + void lazily_add_relationship (sarif_location &target, + enum location_relationship_kind kind, + sarif_location_manager &loc_mgr); + +private: + sarif_location_relationship & + lazily_add_relationship_object (sarif_location &target, + sarif_location_manager &loc_mgr); + + json::array &lazily_add_relationships_array (); -class sarif_location : public sarif_object {}; + std::map m_relationships_map; +}; /* Subclass of sarif_object for SARIF "physicalLocation" objects (SARIF v2.1.0 section 3.29). */ @@ -242,6 +434,23 @@ class sarif_physical_location : public sarif_object {}; class sarif_region : public sarif_object {}; +/* Subclass of sarif_object for SARIF "locationRelationship" objects + (SARIF v2.1.0 section 3.34). */ + +class sarif_location_relationship : public sarif_object +{ +public: + sarif_location_relationship (sarif_location &target, + sarif_location_manager &loc_mgr); + + long get_target_id () const; + + void lazily_add_kind (enum location_relationship_kind kind); + +private: + auto_sbitmap m_kinds; +}; + /* Subclass of sarif_object for SARIF "codeFlow" objects (SARIF v2.1.0 section 3.36). */ @@ -304,12 +513,16 @@ class sarif_replacement : public sarif_object {}; This subclass is specifically for notifying when an internal compiler error occurs. */ -class sarif_ice_notification : public sarif_object +class sarif_ice_notification : public sarif_location_manager { public: sarif_ice_notification (diagnostic_context &context, const diagnostic_info &diagnostic, sarif_builder &builder); + + void + add_related_location (std::unique_ptr location_obj) + final override; }; /* Abstract base class for use when making an "artifactContent" @@ -370,6 +583,7 @@ class sarif_builder { public: sarif_builder (diagnostic_context &context, + const line_maps *line_maps, const char *main_input_filename_, bool formatted); @@ -380,16 +594,27 @@ public: const diagnostic_diagram &diagram); void end_group (); + std::unique_ptr take_current_result () + { + return std::move (m_cur_group_result); + } + std::unique_ptr flush_to_object (); void flush_to_file (FILE *outf); std::unique_ptr - make_locations_arr (const diagnostic_info &diagnostic, + make_locations_arr (sarif_location_manager &loc_mgr, + const diagnostic_info &diagnostic, enum diagnostic_artifact_role role); std::unique_ptr - make_location_object (const rich_location &rich_loc, + make_location_object (sarif_location_manager &loc_mgr, + const rich_location &rich_loc, const logical_location *logical_loc, enum diagnostic_artifact_role role); + std::unique_ptr + make_location_object (sarif_location_manager &loc_mgr, + location_t where, + enum diagnostic_artifact_role role); std::unique_ptr make_message_object (const char *msg) const; std::unique_ptr @@ -407,15 +632,22 @@ private: const diagnostic_info &diagnostic, diagnostic_t orig_diag_kind); void + add_any_include_chain (sarif_location_manager &loc_mgr, + sarif_location &location_obj, + location_t where); + void set_any_logical_locs_arr (sarif_location &location_obj, const logical_location *logical_loc); std::unique_ptr - make_location_object (const diagnostic_event &event, + make_location_object (sarif_location_manager &loc_mgr, + const diagnostic_event &event, enum diagnostic_artifact_role role); std::unique_ptr - make_code_flow_object (const diagnostic_path &path); + make_code_flow_object (sarif_result &result, + const diagnostic_path &path); std::unique_ptr - make_thread_flow_location_object (const diagnostic_event &event, + make_thread_flow_location_object (sarif_result &result, + const diagnostic_event &event, int path_event_idx); std::unique_ptr maybe_make_kinds_array (diagnostic_event::meaning m) const; @@ -486,6 +718,7 @@ private: int get_sarif_column (expanded_location exploc) const; diagnostic_context &m_context; + const line_maps *m_line_maps; /* The JSON object for the invocation object. */ std::unique_ptr m_invocation_obj; @@ -495,7 +728,7 @@ private: /* The JSON object for the result object (if any) in the current diagnostic group. */ - sarif_result *m_cur_group_result; // borrowed + std::unique_ptr m_cur_group_result; /* Ideally we'd use std::unique_ptr here, but I had trouble getting this to work when building with GCC 4.8. */ @@ -600,6 +833,15 @@ void sarif_artifact::add_role (enum diagnostic_artifact_role role, bool embed_contents) { + /* TODO(SARIF 2.2): "scannedFile" is to be added as a role in SARIF 2.2; + see https://github.com/oasis-tcs/sarif-spec/issues/459 + + For now, skip them. + Ultimately, we probably shouldn't bother embedding the contents + of such artifacts, just the snippets. */ + if (role == diagnostic_artifact_role::scanned_file) + return; + if (embed_contents) m_embed_contents = true; @@ -645,6 +887,8 @@ get_artifact_role_string (enum diagnostic_artifact_role role) return "debugOutputFile"; case diagnostic_artifact_role::result_file: return "resultFile"; + case diagnostic_artifact_role::scanned_file: + return "scannedFile"; case diagnostic_artifact_role::traced_file: return "tracedFile"; } @@ -670,7 +914,83 @@ sarif_artifact::populate_roles () set ("roles", std::move (roles_arr)); } -/* class sarif_result : public sarif_object. */ +/* class sarif_location_manager : public sarif_object. */ + +void +sarif_location_manager:: +add_relationship_to_worklist (sarif_location &location_obj, + enum worklist_item::kind kind, + location_t where) +{ + m_worklist.push_back (worklist_item (location_obj, + kind, + where)); +} + +/* Process all items in this result's worklist. + Doing so may temporarily add new items to the end + of the worklist. + Handling any item should be "lazy", and thus we should + eventually drain the queue and terminate. */ + +void +sarif_location_manager::process_worklist (sarif_builder &builder) +{ + while (!m_worklist.empty ()) + { + const worklist_item &item = m_worklist.front (); + process_worklist_item (builder, item); + m_worklist.pop_front (); + } +} + +/* Process one item in this result's worklist, potentially + adding new items to the end of the worklist. */ + +void +sarif_location_manager::process_worklist_item (sarif_builder &builder, + const worklist_item &item) +{ + switch (item.m_kind) + { + default: + gcc_unreachable (); + case worklist_item::kind::included_from: + { + sarif_location &included_loc_obj = item.m_location_obj; + sarif_location *includer_loc_obj = nullptr; + auto iter = m_included_from_locations.find (item.m_where); + if (iter != m_included_from_locations.end ()) + includer_loc_obj = iter->second; + else + { + std::unique_ptr new_loc_obj + = builder.make_location_object + (*this, + item.m_where, + diagnostic_artifact_role::scanned_file); + includer_loc_obj = new_loc_obj.get (); + add_related_location (std::move (new_loc_obj)); + auto kv + = std::pair (item.m_where, + includer_loc_obj); + m_included_from_locations.insert (kv); + } + + includer_loc_obj->lazily_add_relationship + (included_loc_obj, + location_relationship_kind::includes, + *this); + included_loc_obj.lazily_add_relationship + (*includer_loc_obj, + location_relationship_kind::is_included_by, + *this); + } + break; + } +} + +/* class sarif_result : public sarif_location_manager. */ /* Handle secondary diagnostics that occur within a diagnostic group. The closest SARIF seems to have to nested diagnostics is the @@ -688,7 +1008,7 @@ sarif_result::on_nested_diagnostic (diagnostic_context &context, sometimes these will related to current_function_decl, but often they won't. */ auto location_obj - = builder.make_location_object (*diagnostic.richloc, nullptr, + = builder.make_location_object (*this, *diagnostic.richloc, nullptr, diagnostic_artifact_role::result_file); auto message_obj = builder.make_message_object (pp_formatted_text (context.printer)); @@ -717,7 +1037,10 @@ sarif_result::on_diagram (diagnostic_context &context, add_related_location (std::move (location_obj)); } -/* Add LOCATION_OBJ to this result's "relatedLocations" array, +/* Implementation of sarif_location_manager::add_related_location vfunc + for result objects. + + Add LOCATION_OBJ to this result's "relatedLocations" array, creating it if it doesn't yet exist. */ void @@ -734,18 +1057,140 @@ add_related_location (std::unique_ptr location_obj) m_related_locations_arr->append (std::move (location_obj)); } -/* class sarif_ice_notification : public sarif_object. */ +/* class sarif_location : public sarif_object. */ + +/* Ensure this location has an "id" and return it. + Use LOC_MGR if an id needs to be allocated. + + See the "id" property (3.28.2). + + We use this to only assign ids to locations that are + referenced by another sarif object; others have no "id". */ + +long +sarif_location::lazily_add_id (sarif_location_manager &loc_mgr) +{ + long id = get_id (); + if (id != -1) + return id; + id = loc_mgr.allocate_location_id (); + set_integer ("id", id); + gcc_assert (id != -1); + return id; +} + +/* Get the id of this location, or -1 if it doesn't have one. */ + +long +sarif_location::get_id () const +{ + json::value *id = get ("id"); + if (!id) + return -1; + gcc_assert (id->get_kind () == json::JSON_INTEGER); + return static_cast (id)->get (); +} + +// 3.34.3 kinds property +static const char * +get_string_for_location_relationship_kind (enum location_relationship_kind kind) +{ + switch (kind) + { + default: + gcc_unreachable (); + case location_relationship_kind::includes: + return "includes"; + case location_relationship_kind::is_included_by: + return "isIncludedBy"; + case location_relationship_kind::relevant: + return "relevant"; + } +} + +/* Lazily populate this location's "relationships" property (3.28.7) + with the relationship of KIND to TARGET, creating objects + as necessary. + Use LOC_MGR for any locations that need "id" values. */ + +void +sarif_location::lazily_add_relationship (sarif_location &target, + enum location_relationship_kind kind, + sarif_location_manager &loc_mgr) +{ + sarif_location_relationship &relationship_obj + = lazily_add_relationship_object (target, loc_mgr); + + relationship_obj.lazily_add_kind (kind); +} + +/* Lazily populate this location's "relationships" property (3.28.7) + with a location_relationship to TARGET, creating objects + as necessary. + Use LOC_MGR for any locations that need "id" values. */ + +sarif_location_relationship & +sarif_location::lazily_add_relationship_object (sarif_location &target, + sarif_location_manager &loc_mgr) +{ + /* See if THIS already has a locationRelationship referencing TARGET. */ + auto iter = m_relationships_map.find (&target); + if (iter != m_relationships_map.end ()) + { + /* We already have a locationRelationship from THIS to TARGET. */ + sarif_location_relationship *relationship = iter->second; + gcc_assert (relationship->get_target_id() == target.get_id ()); + return *relationship; + } + + // Ensure that THIS has a "relationships" property (3.28.7). + json::array &relationships_arr = lazily_add_relationships_array (); + + /* No existing locationRelationship from THIS to TARGET; make one, + record it, and add it to the "relationships" array. */ + auto relationship_obj + = ::make_unique (target, loc_mgr); + sarif_location_relationship *relationship = relationship_obj.get (); + auto kv + = std::pair (&target, relationship); + m_relationships_map.insert (kv); + + relationships_arr.append (std::move (relationship_obj)); + + return *relationship; +} + +/* Ensure this location has a "relationships" array (3.28.7). */ + +json::array & +sarif_location::lazily_add_relationships_array () +{ + const char *const property_name = "relationships"; + if (json::value *relationships = get (property_name)) + { + gcc_assert (relationships->get_kind () == json::JSON_ARRAY); + return *static_cast (relationships); + } + json::array *relationships_arr = new json::array (); + set (property_name, relationships_arr); + return *relationships_arr; +} + +/* class sarif_ice_notification : public sarif_location_manager. */ /* sarif_ice_notification's ctor. DIAGNOSTIC is an internal compiler error. */ -sarif_ice_notification::sarif_ice_notification (diagnostic_context &context, - const diagnostic_info &diagnostic, - sarif_builder &builder) +sarif_ice_notification:: +sarif_ice_notification (diagnostic_context &context, + const diagnostic_info &diagnostic, + sarif_builder &builder) { /* "locations" property (SARIF v2.1.0 section 3.58.4). */ auto locations_arr - = builder.make_locations_arr (diagnostic, + = builder.make_locations_arr (*this, + diagnostic, diagnostic_artifact_role::result_file); set ("locations", std::move (locations_arr)); @@ -759,6 +1204,60 @@ sarif_ice_notification::sarif_ice_notification (diagnostic_context &context, set_string ("level", "error"); } +/* Implementation of sarif_location_manager::add_related_location vfunc + for notifications. */ + +void +sarif_ice_notification:: +add_related_location (std::unique_ptr location_obj) +{ + /* TODO(SARIF 2.2): see https://github.com/oasis-tcs/sarif-spec/issues/540 + For now, discard all related locations within a notification. */ + location_obj = nullptr; +} + +/* class sarif_location_relationship : public sarif_object. */ + +sarif_location_relationship:: +sarif_location_relationship (sarif_location &target, + sarif_location_manager &loc_mgr) +: m_kinds ((unsigned)location_relationship_kind::NUM_KINDS) +{ + bitmap_clear (m_kinds); + set_integer ("target", target.lazily_add_id (loc_mgr)); +} + +long +sarif_location_relationship::get_target_id () const +{ + json::value *id = get ("id"); + gcc_assert (id); + return static_cast (id)->get (); +} + +void +sarif_location_relationship:: +lazily_add_kind (enum location_relationship_kind kind) +{ + if (bitmap_bit_p (m_kinds, (int)kind)) + return; // already have this kind + bitmap_set_bit (m_kinds, (int)kind); + + // 3.34.3 kinds property + json::array *kinds_arr = nullptr; + if (json::value *kinds_val = get ("kinds")) + { + gcc_assert (kinds_val->get_kind () == json::JSON_ARRAY); + } + else + { + kinds_arr = new json::array (); + set ("kinds", kinds_arr); + } + const char *kind_str = get_string_for_location_relationship_kind (kind); + kinds_arr->append_string (kind_str); +} + /* class sarif_thread_flow : public sarif_object. */ sarif_thread_flow::sarif_thread_flow (const diagnostic_thread &thread) @@ -787,9 +1286,11 @@ add_location (std::unique_ptr thread_flow_loc_obj) /* sarif_builder's ctor. */ sarif_builder::sarif_builder (diagnostic_context &context, + const line_maps *line_maps, const char *main_input_filename_, bool formatted) : m_context (context), + m_line_maps (line_maps), m_invocation_obj (::make_unique (*this, context.get_original_argv ())), @@ -834,10 +1335,8 @@ sarif_builder::end_diagnostic (diagnostic_context &context, else { /* Top-level diagnostic. */ - std::unique_ptr result_obj + m_cur_group_result = make_result_object (context, diagnostic, orig_diag_kind); - m_cur_group_result = result_obj.get (); // borrowed - m_results_array->append (std::move (result_obj)); } } @@ -858,7 +1357,11 @@ sarif_builder::emit_diagram (diagnostic_context &context, void sarif_builder::end_group () { - m_cur_group_result = nullptr; + if (m_cur_group_result) + { + m_cur_group_result->process_worklist (*this); + m_results_array->append (std::move (m_cur_group_result)); + } } /* Create a top-level object, and add it to all the results @@ -999,13 +1502,17 @@ sarif_builder::make_result_object (diagnostic_context &context, /* "locations" property (SARIF v2.1.0 section 3.27.12). */ result_obj->set ("locations", - make_locations_arr (diagnostic, diagnostic_artifact_role::result_file)); + make_locations_arr (*result_obj.get (), + diagnostic, + diagnostic_artifact_role::result_file)); /* "codeFlows" property (SARIF v2.1.0 section 3.27.18). */ if (const diagnostic_path *path = diagnostic.richloc->get_path ()) { auto code_flows_arr = ::make_unique (); - code_flows_arr->append (make_code_flow_object (*path)); + code_flows_arr->append + (make_code_flow_object (*result_obj.get (), + *path)); result_obj->set ("codeFlows", std::move (code_flows_arr)); } @@ -1123,10 +1630,12 @@ make_tool_component_reference_object_for_cwe () const /* Make an array suitable for use as the "locations" property of: - a "result" object (SARIF v2.1.0 section 3.27.12), or - - a "notification" object (SARIF v2.1.0 section 3.58.4). */ + - a "notification" object (SARIF v2.1.0 section 3.58.4). + Use LOC_MGR for any locations that need "id" values. */ std::unique_ptr -sarif_builder::make_locations_arr (const diagnostic_info &diagnostic, +sarif_builder::make_locations_arr (sarif_location_manager &loc_mgr, + const diagnostic_info &diagnostic, enum diagnostic_artifact_role role) { auto locations_arr = ::make_unique (); @@ -1135,7 +1644,7 @@ sarif_builder::make_locations_arr (const diagnostic_info &diagnostic, logical_loc = client_data_hooks->get_current_logical_location (); auto location_obj - = make_location_object (*diagnostic.richloc, logical_loc, role); + = make_location_object (loc_mgr, *diagnostic.richloc, logical_loc, role); /* Don't add entirely empty location objects to the array. */ if (!location_obj->is_empty ()) locations_arr->append (std::move (location_obj)); @@ -1161,10 +1670,13 @@ set_any_logical_locs_arr (sarif_location &location_obj, } /* Make a "location" object (SARIF v2.1.0 section 3.28) for RICH_LOC - and LOGICAL_LOC. */ + and LOGICAL_LOC. + Use LOC_MGR for any locations that need "id" values, and for + any worklist items. */ std::unique_ptr -sarif_builder::make_location_object (const rich_location &rich_loc, +sarif_builder::make_location_object (sarif_location_manager &loc_mgr, + const rich_location &rich_loc, const logical_location *logical_loc, enum diagnostic_artifact_role role) { @@ -1257,6 +1769,8 @@ sarif_builder::make_location_object (const rich_location &rich_loc, std::move (annotations_arr)); } + add_any_include_chain (loc_mgr, *location_obj.get (), loc); + /* A flag for hinting that the diagnostic involves issues at the level of character encodings (such as homoglyphs, or misleading bidirectional control codes), and thus that it will be helpful @@ -1271,11 +1785,66 @@ sarif_builder::make_location_object (const rich_location &rich_loc, return location_obj; } +/* If WHERE was #included from somewhere, add a worklist item + to LOC_MGR to lazily add a location for the #include location, + and relationships between it and the LOCATION_OBJ. + Compare with diagnostic_context::report_current_module, but rather + than iterating the current chain, we add the next edge and iterate + in the worklist, so that edges are only added once. */ + +void +sarif_builder::add_any_include_chain (sarif_location_manager &loc_mgr, + sarif_location &location_obj, + location_t where) +{ + if (where <= BUILTINS_LOCATION) + return; + + const line_map_ordinary *map = nullptr; + linemap_resolve_location (m_line_maps, where, + LRK_MACRO_DEFINITION_LOCATION, + &map); + + if (!map) + return; + + location_t include_loc = linemap_included_from (map); + map = linemap_included_from_linemap (m_line_maps, map); + if (!map) + return; + loc_mgr.add_relationship_to_worklist + (location_obj, + sarif_result::worklist_item::kind::included_from, + include_loc); +} + +/* Make a "location" object (SARIF v2.1.0 section 3.28) for WHERE + within an include chain. */ + +std::unique_ptr +sarif_builder::make_location_object (sarif_location_manager &loc_mgr, + location_t loc, + enum diagnostic_artifact_role role) +{ + auto location_obj = ::make_unique (); + + /* "physicalLocation" property (SARIF v2.1.0 section 3.28.3). */ + if (auto phs_loc_obj + = maybe_make_physical_location_object (loc, role, 0, nullptr)) + location_obj->set ("physicalLocation", + std::move (phs_loc_obj)); + + add_any_include_chain (loc_mgr, *location_obj.get (), loc); + + return location_obj; +} + /* Make a "location" object (SARIF v2.1.0 section 3.28) for EVENT within a diagnostic_path. */ std::unique_ptr -sarif_builder::make_location_object (const diagnostic_event &event, +sarif_builder::make_location_object (sarif_location_manager &loc_mgr, + const diagnostic_event &event, enum diagnostic_artifact_role role) { auto location_obj = ::make_unique (); @@ -1296,6 +1865,8 @@ sarif_builder::make_location_object (const diagnostic_event &event, location_obj->set ("message", make_message_object (ev_desc.get ())); + add_any_include_chain (loc_mgr, *location_obj.get (), loc); + return location_obj; } @@ -1651,7 +2222,8 @@ make_sarif_logical_location_object (const logical_location &logical_loc) /* Make a "codeFlow" object (SARIF v2.1.0 section 3.36) for PATH. */ std::unique_ptr -sarif_builder::make_code_flow_object (const diagnostic_path &path) +sarif_builder::make_code_flow_object (sarif_result &result, + const diagnostic_path &path) { auto code_flow_obj = ::make_unique (); @@ -1680,7 +2252,7 @@ sarif_builder::make_code_flow_object (const diagnostic_path &path) /* Add event to thread's threadFlow object. */ std::unique_ptr thread_flow_loc_obj - = make_thread_flow_location_object (event, i); + = make_thread_flow_location_object (result, event, i); thread_flow_obj->add_location (std::move (thread_flow_loc_obj)); } code_flow_obj->set ("threadFlows", std::move (thread_flows_arr)); @@ -1691,7 +2263,8 @@ sarif_builder::make_code_flow_object (const diagnostic_path &path) /* Make a "threadFlowLocation" object (SARIF v2.1.0 section 3.38) for EVENT. */ std::unique_ptr -sarif_builder::make_thread_flow_location_object (const diagnostic_event &ev, +sarif_builder::make_thread_flow_location_object (sarif_result &result, + const diagnostic_event &ev, int path_event_idx) { auto thread_flow_loc_obj = ::make_unique (); @@ -1703,7 +2276,7 @@ sarif_builder::make_thread_flow_location_object (const diagnostic_event &ev, /* "location" property (SARIF v2.1.0 section 3.38.3). */ thread_flow_loc_obj->set ("location", - make_location_object (ev, diagnostic_artifact_role::traced_file)); + make_location_object (result, ev, diagnostic_artifact_role::traced_file)); /* "kinds" property (SARIF v2.1.0 section 3.38.8). */ diagnostic_event::meaning m = ev.get_meaning (); @@ -2078,6 +2651,7 @@ sarif_builder::get_or_create_artifact (const char *filename, gcc_unreachable (); case diagnostic_artifact_role::analysis_target: case diagnostic_artifact_role::result_file: + case diagnostic_artifact_role::scanned_file: case diagnostic_artifact_role::traced_file: /* Assume that these are in the source language. */ if (auto client_data_hooks = m_context.get_client_data_hooks ()) @@ -2277,6 +2851,16 @@ sarif_ice_handler (diagnostic_context *context) class sarif_output_format : public diagnostic_output_format { public: + ~sarif_output_format () + { + /* Any sarifResult objects should have been handled by now. + If not, then something's gone wrong with diagnostic + groupings. */ + std::unique_ptr pending_result + = m_builder.take_current_result (); + gcc_assert (!pending_result); + } + void on_begin_group () final override { /* No-op, */ @@ -2303,10 +2887,11 @@ public: protected: sarif_output_format (diagnostic_context &context, + const line_maps *line_maps, const char *main_input_filename_, bool formatted) : diagnostic_output_format (context), - m_builder (context, main_input_filename_, formatted) + m_builder (context, line_maps, main_input_filename_, formatted) {} sarif_builder m_builder; @@ -2316,10 +2901,11 @@ class sarif_stream_output_format : public sarif_output_format { public: sarif_stream_output_format (diagnostic_context &context, + const line_maps *line_maps, const char *main_input_filename_, bool formatted, FILE *stream) - : sarif_output_format (context, main_input_filename_, formatted), + : sarif_output_format (context, line_maps, main_input_filename_, formatted), m_stream (stream) { } @@ -2339,10 +2925,11 @@ class sarif_file_output_format : public sarif_output_format { public: sarif_file_output_format (diagnostic_context &context, + const line_maps *line_maps, const char *main_input_filename_, bool formatted, const char *base_file_name) - : sarif_output_format (context, main_input_filename_, formatted), + : sarif_output_format (context, line_maps, main_input_filename_, formatted), m_base_file_name (xstrdup (base_file_name)) { } @@ -2407,6 +2994,7 @@ diagnostic_output_format_init_sarif_stderr (diagnostic_context &context, diagnostic_output_format_init_sarif (context); context.set_output_format (new sarif_stream_output_format (context, + line_table, main_input_filename_, formatted, stderr)); @@ -2424,6 +3012,7 @@ diagnostic_output_format_init_sarif_file (diagnostic_context &context, diagnostic_output_format_init_sarif (context); context.set_output_format (new sarif_file_output_format (context, + line_table, main_input_filename_, formatted, base_file_name)); @@ -2440,6 +3029,7 @@ diagnostic_output_format_init_sarif_stream (diagnostic_context &context, diagnostic_output_format_init_sarif (context); context.set_output_format (new sarif_stream_output_format (context, + line_table, main_input_filename_, formatted, stream)); @@ -2461,6 +3051,7 @@ public: diagnostic_output_format_init_sarif (*this); m_format = new buffered_output_format (*this, + line_table, main_input_filename, true); set_output_format (m_format); // give ownership; @@ -2476,9 +3067,10 @@ private: { public: buffered_output_format (diagnostic_context &context, + const line_maps *line_maps, const char *main_input_filename_, bool formatted) - : sarif_output_format (context, main_input_filename_, formatted) + : sarif_output_format (context, line_maps, main_input_filename_, formatted) { } bool machine_readable_stderr_p () const final override @@ -2509,7 +3101,7 @@ test_make_location_object (const line_table_case &case_) test_diagnostic_context dc; - sarif_builder builder (dc, "MAIN_INPUT_FILENAME", true); + sarif_builder builder (dc, line_table, "MAIN_INPUT_FILENAME", true); /* These "columns" are byte offsets, whereas later on the columns in the generated SARIF use sarif_builder::get_sarif_column and @@ -2536,9 +3128,11 @@ test_make_location_object (const line_table_case &case_) richloc.add_range (field, SHOW_RANGE_WITHOUT_CARET, &label2); richloc.set_escape_on_output (true); + sarif_result result; + std::unique_ptr location_obj = builder.make_location_object - (richloc, nullptr, diagnostic_artifact_role::analysis_target); + (result, richloc, nullptr, diagnostic_artifact_role::analysis_target); ASSERT_NE (location_obj, nullptr); auto physical_location diff --git a/gcc/diagnostic.cc b/gcc/diagnostic.cc index c70c394f7ccd..46cddfe94d1d 100644 --- a/gcc/diagnostic.cc +++ b/gcc/diagnostic.cc @@ -346,6 +346,14 @@ initialize_input_context (diagnostic_input_charset_callback ccb, void diagnostic_context::finish () { + /* We might be handling a fatal error. + Close any active diagnostic groups, which may trigger flushing + the output format. */ + while (m_diagnostic_groups.m_nesting_depth > 0) + end_group (); + + /* Clean ups. */ + delete m_output_format; m_output_format= nullptr; @@ -1396,6 +1404,11 @@ diagnostic_context::report_diagnostic (diagnostic_info *diagnostic) gcc_assert (m_output_format); + /* Every call to report_diagnostic should be within a + begin_group/end_group pair so that output formats can reliably + flush diagnostics with on_end_group when the topmost group is ended. */ + gcc_assert (m_diagnostic_groups.m_nesting_depth > 0); + /* Give preference to being able to inhibit warnings, before they get reclassified to something else. */ bool was_warning = (diagnostic->kind == DK_WARNING diff --git a/gcc/diagnostic.h b/gcc/diagnostic.h index 1d83879c50ef..36a4c23b0737 100644 --- a/gcc/diagnostic.h +++ b/gcc/diagnostic.h @@ -1012,7 +1012,10 @@ inline bool diagnostic_report_diagnostic (diagnostic_context *context, diagnostic_info *diagnostic) { - return context->report_diagnostic (diagnostic); + context->begin_group (); + bool warned = context->report_diagnostic (diagnostic); + context->end_group (); + return warned; } #ifdef ATTRIBUTE_GCC_DIAG diff --git a/gcc/selftest-diagnostic.cc b/gcc/selftest-diagnostic.cc index c9e9d7094bd3..3a14739c1e3d 100644 --- a/gcc/selftest-diagnostic.cc +++ b/gcc/selftest-diagnostic.cc @@ -69,7 +69,9 @@ test_diagnostic_context::report (diagnostic_t kind, { va_list ap; va_start (ap, fmt); + begin_group (); bool result = diagnostic_impl (&richloc, metadata, option, fmt, &ap, kind); + end_group (); va_end (ap); return result; } diff --git a/gcc/testsuite/gcc.dg/sarif-output/include-chain-1-1.h b/gcc/testsuite/gcc.dg/sarif-output/include-chain-1-1.h new file mode 100644 index 000000000000..b292c7bb96b5 --- /dev/null +++ b/gcc/testsuite/gcc.dg/sarif-output/include-chain-1-1.h @@ -0,0 +1,2 @@ +int p; +int q; diff --git a/gcc/testsuite/gcc.dg/sarif-output/include-chain-1-2.h b/gcc/testsuite/gcc.dg/sarif-output/include-chain-1-2.h new file mode 100644 index 000000000000..664bfe6e1ffe --- /dev/null +++ b/gcc/testsuite/gcc.dg/sarif-output/include-chain-1-2.h @@ -0,0 +1,2 @@ +char p; +char q; diff --git a/gcc/testsuite/gcc.dg/sarif-output/include-chain-1.c b/gcc/testsuite/gcc.dg/sarif-output/include-chain-1.c new file mode 100644 index 000000000000..177f528cc62d --- /dev/null +++ b/gcc/testsuite/gcc.dg/sarif-output/include-chain-1.c @@ -0,0 +1,42 @@ +/* { dg-do compile } */ +/* { dg-options "-fdiagnostics-format=sarif-file" } */ + +/* Verify that SARIF output can capture chains of include files in + result locations. + + Generate two warning/note pairs, using a chain of header files. + In textual form, we'd expect something like: + +In file included from PATH/include-chain-1.h:5, + from PATH/include-chain-1.c:9: +PATH/include-chain-1-2.h:1:6: error: conflicting types for 'p'; have 'char' + 1 | char p; + | ^ +In file included from PATH/include-chain-1.h:2: +PATH/include-chain-1-1.h:1:5: note: previous declaration of 'p' with type 'int' + 1 | int p; + | ^ +PATH/include-chain-1-2.h:2:6: error: conflicting types for 'q'; have 'char' + 2 | char q; + | ^ +PATH/include-chain-1-1.h:2:5: note: previous declaration of 'q' with type 'int' + 2 | int q; + | ^ + + We should have two result objects (for each of 'p' and 'q'), each with + a related location for its note, and additional related locations describing + the include chains. */ + +#include "include-chain-1.h" + +/* We expect a failing compile due to the errors, but the use of + -fdiagnostics-format=sarif-file means there should be no output to stderr. + DejaGnu injects this message; ignore it: + { dg-prune-output "exit status is 1" } */ + +/* 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 include-chain-1.c "test-include-chain-1.py" } } */ diff --git a/gcc/testsuite/gcc.dg/sarif-output/include-chain-1.h b/gcc/testsuite/gcc.dg/sarif-output/include-chain-1.h new file mode 100644 index 000000000000..3fff40d49236 --- /dev/null +++ b/gcc/testsuite/gcc.dg/sarif-output/include-chain-1.h @@ -0,0 +1,5 @@ +/* First set of decls, which will be referenced in notes. */ +#include "include-chain-1-1.h" + +/* Second set of decls, which will trigger the errors. */ +#include "include-chain-1-2.h" diff --git a/gcc/testsuite/gcc.dg/sarif-output/include-chain-2.c b/gcc/testsuite/gcc.dg/sarif-output/include-chain-2.c new file mode 100644 index 000000000000..3f984f48979b --- /dev/null +++ b/gcc/testsuite/gcc.dg/sarif-output/include-chain-2.c @@ -0,0 +1,40 @@ +/* { dg-require-effective-target analyzer } */ +/* { dg-options "-fanalyzer -fdiagnostics-format=sarif-file" } */ +/* { dg-do compile } */ + +/* Verify that SARIF output can capture chains of include files in + diagnostic paths within result locations. + + Generate an analyzer warning with a path, using a chain of header files + both for the warning and for the events within its esxecution path. + In textual form, we'd expect something like: + +In file included from PATH/include-chain-2.c:28: +PATH/include-chain-2.h: In function 'test': +PATH/include-chain-2.h:6:3: warning: double-'free' of 'ptr' [CWE-415] [-Wanalyzer-double-free] + 6 | __builtin_free (ptr); + | ^~~~~~~~~~~~~~~~~~~~ + 'test': events 1-2 + 5 | __builtin_free (ptr); + | ^~~~~~~~~~~~~~~~~~~~ + | | + | (1) first 'free' here + 6 | __builtin_free (ptr); + | ~~~~~~~~~~~~~~~~~~~~ + | | + | (2) second 'free' here; first 'free' was at (1) +*/ + +#include "include-chain-2.h" + +/* We expect a failing compile due to the errors, but the use of + -fdiagnostics-format=sarif-file means there should be no output to stderr. + DejaGnu injects this message; ignore it: + { dg-prune-output "exit status is 1" } */ + +/* 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 include-chain-2.c "test-include-chain-2.py" } } */ diff --git a/gcc/testsuite/gcc.dg/sarif-output/include-chain-2.h b/gcc/testsuite/gcc.dg/sarif-output/include-chain-2.h new file mode 100644 index 000000000000..382ac026fe04 --- /dev/null +++ b/gcc/testsuite/gcc.dg/sarif-output/include-chain-2.h @@ -0,0 +1,7 @@ +/* Generate a warning with a diagnostic_path within a header. */ + +void test (void *ptr) +{ + __builtin_free (ptr); // 1st + __builtin_free (ptr); // 2nd +} diff --git a/gcc/testsuite/gcc.dg/sarif-output/sarif-output.exp b/gcc/testsuite/gcc.dg/sarif-output/sarif-output.exp new file mode 100644 index 000000000000..1f977ca1f568 --- /dev/null +++ b/gcc/testsuite/gcc.dg/sarif-output/sarif-output.exp @@ -0,0 +1,31 @@ +# Copyright (C) 2012-2024 Free Software Foundation, Inc. +# +# This file is part of GCC. +# +# GCC is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GCC is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCC; see the file COPYING3. If not see +# . + +# GCC testsuite that uses the `dg.exp' driver. + +# Load support procs. +load_lib gcc-dg.exp + +# Initialize `dg'. +dg-init + +# Main loop. +dg-runtest [lsort [glob -nocomplain $srcdir/$subdir/*.c]] "" "" + +# All done. +dg-finish diff --git a/gcc/testsuite/gcc.dg/sarif-output/sarif.py b/gcc/testsuite/gcc.dg/sarif-output/sarif.py new file mode 100644 index 000000000000..54c96a006b65 --- /dev/null +++ b/gcc/testsuite/gcc.dg/sarif-output/sarif.py @@ -0,0 +1,21 @@ +import gzip +import json +import os + +def sarif_from_env(): + # return parsed JSON content a SARIF_PATH file + json_filename = os.environ['SARIF_PATH'] + json_filename += '.sarif' + print('json_filename: %r' % json_filename) + with open(json_filename) as f: + json_data = f.read() + return json.loads(json_data) + +def get_location_artifact_uri(location): + return location['physicalLocation']['artifactLocation']['uri'] + +def get_location_snippet_text(location): + return location['physicalLocation']['contextRegion']['snippet']['text'] + +def get_location_relationships(location): + return location['relationships'] diff --git a/gcc/testsuite/gcc.dg/sarif-output/test-include-chain-1.py b/gcc/testsuite/gcc.dg/sarif-output/test-include-chain-1.py new file mode 100644 index 000000000000..16cd6a6ac4d9 --- /dev/null +++ b/gcc/testsuite/gcc.dg/sarif-output/test-include-chain-1.py @@ -0,0 +1,125 @@ +from sarif import * + +import pytest + +@pytest.fixture(scope='function', autouse=True) +def sarif(): + return sarif_from_env() + +def test_basics(sarif): + schema = sarif['$schema'] + assert schema == "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json" + + version = sarif['version'] + assert version == "2.1.0" + +def test_location_relationships(sarif): + runs = sarif['runs'] + run = runs[0] + results = run['results'] + + # We expect a pair of errors, each with a note, and include chains. + # The textual form of these four diagnostics would look like this: + # . In file included from PATH/include-chain-1.h:5, + # . from PATH/include-chain.c:9: + # . PATH/include-chain-1-2.h:1:6: error: conflicting types for 'p'; have 'char' + # . 1 | char p; + # . | ^ + # . In file included from PATH/include-chain-1.h:2: + # . PATH/include-chain-1-1.h:1:5: note: previous declaration of 'p' with type 'int' + # . 1 | int p; + # . | ^ + # . PATH/include-chain-1-2.h:2:6: error: conflicting types for 'q'; have 'char' + # . 2 | char q; + # . | ^ + # . PATH/include-chain-1-1.h:2:5: note: previous declaration of 'q' with type 'int' + # . 2 | int q; + # . | ^ + assert len(results) == 2 + + result = results[0] + assert result['level'] == 'error' + assert result['message']['text'] == "conflicting types for 'p'; have 'char'" + locations = result['locations'] + assert len(locations) == 1 + + location = locations[0] + assert 'id' in location + assert get_location_artifact_uri(location).endswith('include-chain-1-2.h') + assert get_location_snippet_text(location) == "char p;\n" + + # We expect 4 related locations: one for the "note" + # and three for describing include chains + relatedLocations = result['relatedLocations'] + assert len(relatedLocations) == 4 + + # We expect a related location representing the note: + # . In file included from PATH/include-chain-1.h:2: + # . from PATH/include-chain.c:9: + # . PATH/include-chain-1-1.h:1:5: note: previous declaration of 'p' with type 'int' + # . 1 | int p; + # . | ^ + note = relatedLocations[0] + assert 'id' in note + assert note['message']['text'] == "previous declaration of 'p' with type 'int'" + assert get_location_artifact_uri(note).endswith('include-chain-1-1.h') + assert get_location_snippet_text(note) == "int p;\n" + + # We expect three more related locations for the two include chains + + # The "#include "include-chain-1-2.h" line in include-chain-1.h: + hash_include_1_2_h = relatedLocations[1] + assert 'id' in hash_include_1_2_h + assert get_location_snippet_text(hash_include_1_2_h) == '#include "include-chain-1-2.h"\n' + assert get_location_artifact_uri(hash_include_1_2_h).endswith('include-chain-1.h') + + # The "#include "include-chain-1-1.h" line in include-chain-1.h: + hash_include_1_1_h = relatedLocations[2] + assert 'id' in hash_include_1_1_h + assert get_location_snippet_text(hash_include_1_1_h) == '#include "include-chain-1-1.h"\n' + assert get_location_artifact_uri(hash_include_1_1_h).endswith('include-chain-1.h') + + # The "#include "include-chain-1.h" line in include-chain-1.c: + hash_include_1_h = relatedLocations[3] + assert 'id' in hash_include_1_h + assert get_location_snippet_text(hash_include_1_h) == '#include "include-chain-1.h"\n' + assert get_location_artifact_uri(hash_include_1_h).endswith('include-chain-1.c') + + # Check the various relationships; we expect a directed graph of edges + # representing the various "isIncludedBy" and "includes" relationships. + + # The primary location should be "isIncludedBy" the "#include "include-chain-1-2.h" line + assert len(location['relationships']) == 1 + assert location['relationships'][0]['target'] == hash_include_1_2_h['id'] + assert location['relationships'][0]['kinds'] == ["isIncludedBy"] + + # The note should be "isIncludedBy" the "#include "include-chain-1-1.h" line + assert len(note['relationships']) == 1 + assert note['relationships'][0]['target'] == hash_include_1_1_h['id'] + assert note['relationships'][0]['kinds'] == ["isIncludedBy"] + + # The "#include "include-chain-1-2.h" line: + assert len(hash_include_1_2_h['relationships']) == 2 + assert hash_include_1_2_h['relationships'][0]['target'] == location['id'] + assert hash_include_1_2_h['relationships'][0]['kinds'] == ["includes"] + assert hash_include_1_2_h['relationships'][1]['target'] == hash_include_1_h['id'] + assert hash_include_1_2_h['relationships'][1]['kinds'] == ["isIncludedBy"] + + # The "#include "include-chain-1-1.h" line: + assert len(hash_include_1_1_h['relationships']) == 2 + assert hash_include_1_1_h['relationships'][0]['target'] == note['id'] + assert hash_include_1_1_h['relationships'][0]['kinds'] == ["includes"] + assert hash_include_1_1_h['relationships'][1]['target'] == hash_include_1_h['id'] + assert hash_include_1_1_h['relationships'][1]['kinds'] == ["isIncludedBy"] + + # The "#include "include-chain-1.h" line in include-chain-1.c: + assert len(hash_include_1_h['relationships']) == 2 + assert hash_include_1_h['relationships'][0]['target'] == hash_include_1_2_h['id'] + assert hash_include_1_h['relationships'][0]['kinds'] == ["includes"] + assert hash_include_1_h['relationships'][1]['target'] == hash_include_1_1_h['id'] + assert hash_include_1_h['relationships'][1]['kinds'] == ["includes"] + + # We expect similar for the 2nd error + assert results[1]['level'] == 'error' + assert results[1]['message']['text'] == "conflicting types for 'q'; have 'char'" + assert len(results[1]['relatedLocations']) == 4 diff --git a/gcc/testsuite/gcc.dg/sarif-output/test-include-chain-2.py b/gcc/testsuite/gcc.dg/sarif-output/test-include-chain-2.py new file mode 100644 index 000000000000..aea9aabb5ef5 --- /dev/null +++ b/gcc/testsuite/gcc.dg/sarif-output/test-include-chain-2.py @@ -0,0 +1,124 @@ +# We expect an aanalyzer warning with a path, referencing the #include +# both in the warning and in the events within its execution path. +# In textual form, we'd expect something like: +# . In file included from PATH/include-chain-2.c:28: +# . PATH/include-chain-2.h: In function 'test': +# . PATH/include-chain-2.h:6:3: warning: double-'free' of 'ptr' [CWE-415] [-Wanalyzer-double-free] +# . 6 | __builtin_free (ptr); +# . | ^~~~~~~~~~~~~~~~~~~~ +# . 'test': events 1-2 +# . 5 | __builtin_free (ptr); +# . | ^~~~~~~~~~~~~~~~~~~~ +# . | | +# . | (1) first 'free' here +# . 6 | __builtin_free (ptr); +# . | ~~~~~~~~~~~~~~~~~~~~ +# . | | +# . | (2) second 'free' here; first 'free' was at (1) + +from sarif import * + +import pytest + +@pytest.fixture(scope='function', autouse=True) +def sarif(): + return sarif_from_env() + +def test_basics(sarif): + schema = sarif['$schema'] + assert schema == "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json" + + version = sarif['version'] + assert version == "2.1.0" + +def test_result(sarif): + runs = sarif['runs'] + run = runs[0] + results = run['results'] + + assert len(results) == 1 + + result = results[0] + assert result['ruleId'] == '-Wanalyzer-double-free' + assert result['level'] == 'warning' + assert result['message']['text'] == "double-'free' of 'ptr'" + assert result["taxa"] == [{"id": "415", + "toolComponent": {"name": "cwe"}}] + +def test_location_relationships(sarif): + runs = sarif['runs'] + run = runs[0] + results = run['results'] + + assert len(results) == 1 + + result = results[0] + locations = result['locations'] + assert len(locations) == 1 + + location = locations[0] + assert 'id' in location + assert get_location_artifact_uri(location).endswith('include-chain-2.h') + assert get_location_snippet_text(location) == " __builtin_free (ptr); // 2nd\n" + + # We expect one related location, describing the #include + relatedLocations = result['relatedLocations'] + assert len(relatedLocations) == 1 + + # The "#include "include-chain-1.h" line in include-chain-1.c: + hash_include_2_h = relatedLocations[0] + assert 'id' in hash_include_2_h + assert get_location_snippet_text(hash_include_2_h) == '#include "include-chain-2.h"\n' + assert get_location_artifact_uri(hash_include_2_h).endswith('include-chain-2.c') + + # We expect an execution path + assert len(result['codeFlows']) == 1 + codeFlow = result['codeFlows'][0] + assert len(codeFlow['threadFlows']) == 1 + threadFlow = codeFlow['threadFlows'][0] + assert threadFlow['id'] == 'main' + assert len(threadFlow['locations']) == 2 + + assert threadFlow['locations'][0]['location']['message']['text'] \ + == "first 'free' here" + assert threadFlow['locations'][0]['location']['physicalLocation']['contextRegion']['snippet']['text'] \ + == " __builtin_free (ptr); // 1st\n" + assert threadFlow['locations'][0]['kinds'] == ['release', 'memory'] + assert threadFlow['locations'][0]['executionOrder'] == 1 + + assert threadFlow['locations'][1]['location']['message']['text'] \ + == "second 'free' here; first 'free' was at (1)" + assert threadFlow['locations'][1]['location']['physicalLocation']['contextRegion']['snippet']['text'] \ + == " __builtin_free (ptr); // 2nd\n" + assert threadFlow['locations'][1]['kinds'] == ['danger'] + assert threadFlow['locations'][1]['executionOrder'] == 2 + + # Check the various relationships; we expect a directed graph of edges + # representing the various "isIncludedBy" and "includes" relationships. + + # The primary location should be "isIncludedBy" the "#include "include-chain-2.h" line + assert len(location['relationships']) == 1 + assert location['relationships'][0]['target'] == hash_include_2_h['id'] + assert location['relationships'][0]['kinds'] == ["isIncludedBy"] + + # Similarly, so should the locations within the threadFlow + event0_rels = get_location_relationships(threadFlow['locations'][0]['location']) + assert len(event0_rels) == 1 + assert event0_rels[0]['target'] == hash_include_2_h['id'] + assert event0_rels[0]['kinds'] == ["isIncludedBy"] + + event1_rels = get_location_relationships(threadFlow['locations'][1]['location']) + assert len(event1_rels) == 1 + assert event1_rels[0]['target'] == hash_include_2_h['id'] + assert event1_rels[0]['kinds'] == ["isIncludedBy"] + + # The "#include "include-chain-2.h" line in include-chain-2.c should + # have an "includes" relationship to the main location and to the + # two locations in the execution path: + assert len(hash_include_2_h['relationships']) == 3 + assert hash_include_2_h['relationships'][0]['target'] == location['id'] + assert hash_include_2_h['relationships'][0]['kinds'] == ["includes"] + assert hash_include_2_h['relationships'][1]['target'] == threadFlow['locations'][0]['location']['id'] + assert hash_include_2_h['relationships'][1]['kinds'] == ["includes"] + assert hash_include_2_h['relationships'][2]['target'] == threadFlow['locations'][1]['location']['id'] + assert hash_include_2_h['relationships'][2]['kinds'] == ["includes"] diff --git a/gcc/testsuite/lib/scansarif.exp b/gcc/testsuite/lib/scansarif.exp index cc0890ef5d8b..e08f80c9ce18 100644 --- a/gcc/testsuite/lib/scansarif.exp +++ b/gcc/testsuite/lib/scansarif.exp @@ -103,3 +103,57 @@ proc verify-sarif-file { args } { pass "$what" } } + +proc sarif-pytest-format-line { args } { + global subdir + + set testcase [lindex $args 0] + set pytest_script [lindex $args 1] + set output_line [lindex $args 2] + + set index [string first "::" $output_line] + set test_output [string range $output_line [expr $index + 2] [string length $output_line]] + + return "$subdir/$testcase ${pytest_script}::${test_output}" +} + +# Call by dg-final to run a pytest Python script. +# We pass filename of a test via SARIF_PATH environment variable. + +proc run-sarif-pytest { args } { + global srcdir subdir + # Extract the test file name from the arguments. + set testcase [lindex $args 0] + + verbose "Running SARIF $testcase in $srcdir/$subdir" 2 + set testcase [remote_download host $testcase] + + set pytest_script [lindex $args 1] + if { ![check_effective_target_pytest3] } { + unsupported "$pytest_script pytest python3 is missing" + return + } + + setenv SARIF_PATH $testcase + spawn -noecho python3 -m pytest --color=no -rap -s --tb=no $srcdir/$subdir/$pytest_script + + set prefix "\[^\r\n\]*" + expect { + -re "FAILED($prefix)\[^\r\n\]+\r\n" { + set output [sarif-pytest-format-line $testcase $pytest_script $expect_out(1,string)] + fail $output + exp_continue + } + -re "ERROR($prefix)\[^\r\n\]+\r\n" { + set output [sarif-pytest-format-line $testcase $pytest_script $expect_out(1,string)] + fail $output + exp_continue + } + -re "PASSED($prefix)\[^\r\n\]+\r\n" { + set output [sarif-pytest-format-line $testcase $pytest_script $expect_out(1,string)] + pass $output + exp_continue + } + } +} +