| /* SARIF output for diagnostics |
| Copyright (C) 2018-2025 Free Software Foundation, Inc. |
| Contributed by David Malcolm <dmalcolm@redhat.com>. |
| |
| 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 |
| <http://www.gnu.org/licenses/>. */ |
| |
| |
| #include "config.h" |
| #define INCLUDE_LIST |
| #define INCLUDE_MAP |
| #define INCLUDE_STRING |
| #define INCLUDE_VECTOR |
| #include "system.h" |
| #include "coretypes.h" |
| #include "diagnostic.h" |
| #include "diagnostics/metadata.h" |
| #include "diagnostics/digraphs.h" |
| #include "diagnostics/state-graphs.h" |
| #include "diagnostics/paths.h" |
| #include "diagnostics/sink.h" |
| #include "diagnostics/buffering.h" |
| #include "json.h" |
| #include "cpplib.h" |
| #include "diagnostics/logical-locations.h" |
| #include "diagnostics/client-data-hooks.h" |
| #include "diagnostics/diagram.h" |
| #include "text-art/canvas.h" |
| #include "diagnostics/sarif-sink.h" |
| #include "diagnostics/text-sink.h" |
| #include "ordered-hash-map.h" |
| #include "sbitmap.h" |
| #include "selftest.h" |
| #include "diagnostics/selftest-context.h" |
| #include "diagnostics/selftest-source-printing.h" |
| #include "selftest-json.h" |
| #include "text-range-label.h" |
| #include "pretty-print-format-impl.h" |
| #include "pretty-print-urlifier.h" |
| #include "demangle.h" |
| #include "backtrace.h" |
| #include "xml.h" |
| |
| namespace diagnostics { |
| |
| /* A json::array where the values are "unique" as per |
| SARIF v2.1.0 section 3.7.3 ("Array properties with unique values"). */ |
| |
| template <typename JsonElementType> |
| class sarif_array_of_unique : public json::array |
| { |
| public: |
| size_t append_uniquely (std::unique_ptr<JsonElementType> val) |
| { |
| /* This should be O(log(n)) due to the std::map. */ |
| auto search = m_index_by_value.find (val.get ()); |
| if (search != m_index_by_value.end()) |
| return (*search).second; |
| |
| const size_t insertion_idx = size (); |
| m_index_by_value.insert ({val.get (), insertion_idx}); |
| append (std::move (val)); |
| return insertion_idx; |
| } |
| |
| /* For ease of reading output, add "index": idx to all |
| objects in the array. |
| We don't do this until we've added everything, since |
| the "index" property would otherwise confuse the |
| comparison against new elements. */ |
| void add_explicit_index_values () |
| { |
| for (size_t idx = 0; idx < length (); ++idx) |
| if (json::object *obj = get (idx)->dyn_cast_object ()) |
| obj->set_integer ("index", idx); |
| } |
| |
| private: |
| struct comparator_t { |
| bool operator () (const json::value *a, const json::value *b) const |
| { |
| gcc_assert (a); |
| gcc_assert (b); |
| return json::value::compare (*a, *b) < 0; |
| } |
| }; |
| |
| // json::value * here is borrowed from m_elements |
| std::map<json::value *, int, comparator_t> m_index_by_value; |
| }; |
| |
| /* Forward decls. */ |
| class sarif_builder; |
| class content_renderer; |
| class escape_nonascii_renderer; |
| |
| /* Subclasses of sarif_object. |
| Keep these in order of their descriptions in the specification. */ |
| class sarif_artifact_content; // 3.3 |
| class sarif_artifact_location; // 3.4 |
| class sarif_message; // 3.11 |
| class sarif_multiformat_message_string; // 3.12 |
| class sarif_log; // 3.13 |
| class sarif_run; // 3.14 |
| 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 |
| class sarif_reporting_descriptor; // 3.49 |
| class sarif_reporting_descriptor_reference; // 3.53 |
| class sarif_tool_component_reference; // 3.54 |
| class sarif_fix; // 3.55 |
| 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. */ |
| |
| /* Subclass of sarif_object for SARIF "artifactContent" objects |
| (SARIF v2.1.0 section 3.3). */ |
| |
| class sarif_artifact_content : public sarif_object {}; |
| |
| /* Subclass of sarif_object for SARIF "artifactLocation" objects |
| (SARIF v2.1.0 section 3.4). */ |
| |
| class sarif_artifact_location : public sarif_object {}; |
| |
| /* Subclass of sarif_object for SARIF "message" objects |
| (SARIF v2.1.0 section 3.11). */ |
| |
| class sarif_message : public sarif_object {}; |
| |
| /* Subclass of sarif_object for SARIF "multiformatMessageString" objects |
| (SARIF v2.1.0 section 3.12). */ |
| |
| class sarif_multiformat_message_string : public sarif_object {}; |
| |
| /* Subclass of sarif_object for SARIF "log" objects |
| (SARIF v2.1.0 section 3.13). */ |
| |
| class sarif_log : public sarif_object {}; |
| |
| /* Subclass of sarif_object for SARIF "run" objects |
| (SARIF v2.1.0 section 3.14). */ |
| |
| class sarif_run : public sarif_object {}; |
| |
| /* Subclass of sarif_object for SARIF "tool" objects |
| (SARIF v2.1.0 section 3.18). */ |
| |
| class sarif_tool : public sarif_object {}; |
| |
| /* Subclass of sarif_object for SARIF "toolComponent" objects |
| (SARIF v2.1.0 section 3.19). */ |
| |
| class sarif_tool_component : public sarif_object {}; |
| |
| /* Make a JSON string for the current date and time. |
| See SARIF v2.1.0 section 3.9 "Date/time properties". |
| Given that we don't run at the very beginning/end of the |
| process, it doesn't make sense to be more accurate than |
| the current second. */ |
| |
| static std::unique_ptr<json::string> |
| make_date_time_string_for_current_time () |
| { |
| time_t t = time (nullptr); |
| struct tm *tm = gmtime (&t); |
| char buf[256]; |
| snprintf (buf, sizeof (buf) - 1, |
| ("%04i-%02i-%02iT" |
| "%02i:%02i:%02iZ"), |
| tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, |
| tm->tm_hour, tm->tm_min, tm->tm_sec); |
| return std::make_unique<json::string> (buf); |
| } |
| |
| /* Subclass of sarif_object for SARIF "invocation" objects |
| (SARIF v2.1.0 section 3.20). */ |
| |
| class sarif_invocation : public sarif_object |
| { |
| public: |
| sarif_invocation (sarif_builder &builder, |
| const char * const *original_argv); |
| |
| void add_notification_for_ice (const diagnostic_info &diagnostic, |
| sarif_builder &builder, |
| std::unique_ptr<json::object> backtrace); |
| void prepare_to_flush (sarif_builder &builder); |
| |
| private: |
| std::unique_ptr<json::array> m_notifications_arr; |
| bool m_success; |
| }; |
| |
| /* Corresponds to values for the SARIF artifact objects "roles" property. |
| (SARIF v2.1.0 section 3.24.6). */ |
| |
| 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 |
| }; |
| |
| /* Subclass of sarif_object for SARIF artifact objects |
| (SARIF v2.1.0 section 3.24). */ |
| |
| class sarif_artifact : public sarif_object |
| { |
| public: |
| sarif_artifact (const char *filename) |
| : m_filename (filename), |
| m_roles ((unsigned)diagnostic_artifact_role::NUM_ROLES), |
| m_embed_contents (false) |
| { |
| bitmap_clear (m_roles); |
| } |
| |
| void add_role (enum diagnostic_artifact_role role, |
| bool embed_contents); |
| |
| bool embed_contents_p () const { return m_embed_contents; } |
| void populate_contents (sarif_builder &builder); |
| void populate_roles (); |
| |
| private: |
| const char *m_filename; |
| auto_sbitmap m_roles; |
| |
| /* Flag to track whether this artifact should have a "contents" property |
| (SARIF v2.1.0 section 3.24.8). |
| We only add the contents for those artifacts that have a location |
| referencing them (so that a consumer might want to quote the source). */ |
| 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 | |
| . +-----------------------------------+ +----------------------------------+ |
| . ^ | ^ | |
| . includes | | included-by includes | | 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 | |
| . +--------------------------------+ +--------------------------------+ |
| . ^ | ^ | |
| . includes | | included-by includes | | 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, |
| |
| /* Process a location_t that was added as a secondary location |
| to a rich_location without a label. */ |
| unlabelled_secondary_location |
| }; |
| |
| 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_related_locations_arr (nullptr), |
| m_next_location_id (0) |
| { |
| } |
| |
| unsigned allocate_location_id () |
| { |
| return m_next_location_id++; |
| } |
| |
| virtual void |
| add_related_location (std::unique_ptr<sarif_location> location_obj, |
| sarif_builder &builder); |
| |
| 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: |
| json::array *m_related_locations_arr; // borrowed |
| unsigned m_next_location_id; |
| |
| std::list<worklist_item> m_worklist; |
| std::map<location_t, sarif_location *> m_included_from_locations; |
| std::map<location_t, sarif_location *> m_unlabelled_secondary_locations; |
| }; |
| |
| /* Subclass of sarif_object for SARIF "result" objects |
| (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_location_manager |
| { |
| public: |
| sarif_result (unsigned idx_within_parent) |
| : m_idx_within_parent (idx_within_parent) |
| {} |
| |
| unsigned get_index_within_parent () const { return m_idx_within_parent; } |
| |
| void |
| on_nested_diagnostic (const diagnostic_info &diagnostic, |
| enum kind orig_diag_kind, |
| sarif_builder &builder); |
| void on_diagram (const diagram &d, |
| sarif_builder &builder); |
| |
| private: |
| const unsigned m_idx_within_parent; |
| }; |
| |
| /* Subclass of sarif_object for SARIF "location" objects |
| (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 (); |
| |
| std::map<sarif_location *, |
| sarif_location_relationship *> m_relationships_map; |
| }; |
| |
| /* Subclass of sarif_object for SARIF "physicalLocation" objects |
| (SARIF v2.1.0 section 3.29). */ |
| |
| class sarif_physical_location : public sarif_object {}; |
| |
| /* Subclass of sarif_object for SARIF "region" objects |
| (SARIF v2.1.0 section 3.30). */ |
| |
| class sarif_region : public sarif_object {}; |
| |
| /* Subclass of sarif_object for SARIF "logicalLocation" objects |
| (SARIF v2.1.0 section 3.33). */ |
| |
| class sarif_logical_location : 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). */ |
| |
| class sarif_code_flow : public sarif_object |
| { |
| public: |
| sarif_code_flow (sarif_result &parent, |
| unsigned idx_within_parent); |
| |
| sarif_result &get_parent () const { return m_parent; } |
| unsigned get_index_within_parent () const { return m_idx_within_parent; } |
| |
| sarif_thread_flow & |
| get_or_append_thread_flow (const paths::thread &thread, |
| paths::thread_id_t thread_id); |
| |
| sarif_thread_flow & |
| get_thread_flow (paths::thread_id_t thread_id); |
| |
| void add_location (sarif_thread_flow_location &); |
| |
| sarif_thread_flow_location & |
| get_thread_flow_loc_obj (paths::event_id_t event_id) const; |
| |
| private: |
| sarif_result &m_parent; |
| const unsigned m_idx_within_parent; |
| |
| hash_map<int_hash<paths::thread_id_t, -1, -2>, |
| sarif_thread_flow *> m_thread_id_map; // borrowed ptr |
| json::array *m_thread_flows_arr; // borrowed |
| |
| /* Vec of borrowed ptr, allowing for going easily from |
| an event_id to the corresponding threadFlowLocation object. */ |
| std::vector<sarif_thread_flow_location *> m_all_tfl_objs; |
| }; |
| |
| /* Subclass of sarif_object for SARIF "threadFlow" objects |
| (SARIF v2.1.0 section 3.37). */ |
| |
| class sarif_thread_flow : public sarif_object |
| { |
| public: |
| sarif_thread_flow (sarif_code_flow &parent, |
| const paths::thread &thread, |
| unsigned idx_within_parent); |
| |
| sarif_code_flow &get_parent () const { return m_parent; } |
| unsigned get_index_within_parent () const { return m_idx_within_parent; } |
| |
| sarif_thread_flow_location &add_location (); |
| |
| private: |
| sarif_code_flow &m_parent; |
| json::array *m_locations_arr; // borrowed |
| const unsigned m_idx_within_parent; |
| }; |
| |
| /* Subclass of sarif_object for SARIF "threadFlowLocation" objects |
| (SARIF v2.1.0 section 3.38). */ |
| |
| class sarif_thread_flow_location : public sarif_object |
| { |
| public: |
| sarif_thread_flow_location (sarif_thread_flow &parent, |
| unsigned idx_within_parent) |
| : m_parent (parent), |
| m_idx_within_parent (idx_within_parent) |
| { |
| } |
| |
| sarif_thread_flow &get_parent () const { return m_parent; } |
| unsigned get_index_within_parent () const { return m_idx_within_parent; } |
| |
| private: |
| sarif_thread_flow &m_parent; |
| const unsigned m_idx_within_parent; |
| }; |
| |
| /* Subclass of sarif_object for SARIF "reportingDescriptor" objects |
| (SARIF v2.1.0 section 3.49). */ |
| |
| class sarif_reporting_descriptor : public sarif_object {}; |
| |
| /* Subclass of sarif_object for SARIF "reportingDescriptorReference" objects |
| (SARIF v2.1.0 section 3.53). */ |
| |
| class sarif_reporting_descriptor_reference : public sarif_object {}; |
| |
| /* Subclass of sarif_object for SARIF "toolComponentReference" objects |
| (SARIF v2.1.0 section 3.54). */ |
| |
| class sarif_tool_component_reference : public sarif_object {}; |
| |
| /* Subclass of sarif_object for SARIF "fix" objects |
| (SARIF v2.1.0 section 3.55). */ |
| |
| class sarif_fix : public sarif_object {}; |
| |
| /* Subclass of sarif_object for SARIF "artifactChange" objects |
| (SARIF v2.1.0 section 3.56). */ |
| |
| class sarif_artifact_change : public sarif_object {}; |
| |
| /* Subclass of sarif_object for SARIF "replacement" objects |
| (SARIF v2.1.0 section 3.57). */ |
| |
| class sarif_replacement : public sarif_object {}; |
| |
| /* Subclass of sarif_object for SARIF "notification" objects |
| (SARIF v2.1.0 section 3.58). |
| |
| This subclass is specifically for notifying when an |
| internal compiler error occurs. */ |
| |
| class sarif_ice_notification : public sarif_location_manager |
| { |
| public: |
| sarif_ice_notification (const diagnostic_info &diagnostic, |
| sarif_builder &builder, |
| std::unique_ptr<json::object> backtrace); |
| |
| void |
| add_related_location (std::unique_ptr<sarif_location> location_obj, |
| sarif_builder &builder) final override; |
| }; |
| |
| /* Abstract base class for use when making an "artifactContent" |
| object (SARIF v2.1.0 section 3.3): generate a value for the |
| 3.3.4 "rendered" property. |
| Can return nullptr, for "no property". */ |
| |
| class content_renderer |
| { |
| public: |
| virtual ~content_renderer () {} |
| |
| virtual std::unique_ptr<sarif_multiformat_message_string> |
| render (const sarif_builder &builder) const = 0; |
| }; |
| |
| /* Concrete buffering implementation subclass for SARIF output. */ |
| |
| class sarif_sink_buffer : public per_sink_buffer |
| { |
| public: |
| friend class sarif_sink; |
| |
| sarif_sink_buffer (sarif_builder &builder) |
| : m_builder (builder) |
| {} |
| |
| void dump (FILE *out, int indent) const final override; |
| bool empty_p () const final override; |
| void move_to (per_sink_buffer &dest) final override; |
| void clear () final override; |
| void flush () final override; |
| |
| void add_result (std::unique_ptr<sarif_result> result) |
| { |
| m_results.push_back (std::move (result)); |
| } |
| |
| size_t num_results () const { return m_results.size (); } |
| sarif_result &get_result (size_t idx) { return *m_results[idx]; } |
| |
| private: |
| sarif_builder &m_builder; |
| std::vector<std::unique_ptr<sarif_result>> m_results; |
| }; |
| |
| /* Classes for abstracting away JSON vs other serialization formats. */ |
| |
| // class sarif_serialization_format_json : public sarif_serialization_format |
| |
| void |
| sarif_serialization_format_json::write_to_file (FILE *outf, |
| const json::value &top) |
| { |
| top.dump (outf, m_formatted); |
| fprintf (outf, "\n"); |
| } |
| |
| /* A class for managing SARIF output (for -fdiagnostics-format=sarif-stderr |
| and -fdiagnostics-format=sarif-file). |
| |
| As diagnostics occur, we build "result" JSON objects, and |
| accumulate state: |
| - which source files are referenced |
| - which warnings are emitted |
| - which CWEs are used |
| |
| At the end of the compile, we use the above to build the full SARIF |
| object tree, adding the result objects to the correct place, and |
| creating objects for the various source files, warnings and CWEs |
| referenced. |
| |
| Implemented: |
| - fix-it hints |
| - CWE metadata |
| - diagnostic groups (see limitations below) |
| - logical locations (e.g. cfun) |
| - labelled ranges (as annotations) |
| - secondary ranges without labels (as related locations) |
| |
| Known limitations: |
| - GCC supports nesting of diagnostics (one-deep nesting via |
| auto_diagnostic_group, and arbitrary nesting via |
| auto_diagnostic_nesting_level). These are captured in the SARIF |
| as related locations, and so we only capture location and message |
| information from such nested diagnostics (e.g. we ignore fix-it |
| hints on them). Diagnostics within an auto_diagnostic_nesting_level |
| have their nesting level captured as a property. |
| - although we capture command-line arguments (section 3.20.2), we don't |
| yet capture response files. |
| - doesn't capture "artifact.encoding" property |
| (SARIF v2.1.0 section 3.24.9). |
| - doesn't capture hashes of the source files |
| ("artifact.hashes" property (SARIF v2.1.0 section 3.24.11). |
| - doesn't capture the "analysisTarget" property |
| (SARIF v2.1.0 section 3.27.13). |
| - doesn't capture -Werror cleanly |
| - doesn't capture inlining information (can SARIF handle this?) |
| - doesn't capture macro expansion information (can SARIF handle this?). |
| - doesn't capture any diagnostics::metadata::rules associated with |
| a diagnostic. */ |
| |
| class sarif_builder |
| { |
| public: |
| friend class sarif_sink_buffer; |
| |
| sarif_builder (diagnostics::context &dc, |
| pretty_printer &printer, |
| const line_maps *line_maps, |
| std::unique_ptr<sarif_serialization_format> serialization_format, |
| const sarif_generation_options &sarif_gen_opts); |
| ~sarif_builder (); |
| |
| void set_printer (pretty_printer &printer) |
| { |
| m_printer = &printer; |
| } |
| |
| const logical_locations::manager * |
| get_logical_location_manager () const |
| { |
| return m_logical_loc_mgr; |
| } |
| |
| void |
| set_main_input_filename (const char *name); |
| |
| void on_report_diagnostic (const diagnostic_info &diagnostic, |
| enum kind orig_diag_kind, |
| sarif_sink_buffer *buffer); |
| void emit_diagram (const diagram &d); |
| void end_group (); |
| |
| void |
| report_global_digraph (const lazily_created<digraphs::digraph> &); |
| |
| std::unique_ptr<sarif_result> take_current_result () |
| { |
| return std::move (m_cur_group_result); |
| } |
| |
| std::unique_ptr<sarif_log> flush_to_object (); |
| void flush_to_file (FILE *outf); |
| |
| std::unique_ptr<json::array> |
| make_locations_arr (sarif_location_manager &loc_mgr, |
| const diagnostic_info &diagnostic, |
| enum diagnostic_artifact_role role); |
| std::unique_ptr<sarif_location> |
| make_location_object (sarif_location_manager *loc_mgr, |
| const rich_location &rich_loc, |
| logical_locations::key logical_loc, |
| enum diagnostic_artifact_role role); |
| std::unique_ptr<sarif_location> |
| make_location_object (sarif_location_manager &loc_mgr, |
| location_t where, |
| enum diagnostic_artifact_role role); |
| std::unique_ptr<sarif_message> |
| make_message_object (const char *msg) const; |
| std::unique_ptr<sarif_message> |
| make_message_object_for_diagram (const diagram &d); |
| std::unique_ptr<sarif_artifact_content> |
| maybe_make_artifact_content_object (const char *filename) const; |
| |
| std::unique_ptr<sarif_artifact_location> |
| make_artifact_location_object (const char *filename); |
| |
| const sarif_code_flow * |
| get_code_flow_for_event_ids () const |
| { |
| return m_current_code_flow; |
| } |
| |
| diagnostics::context &get_context () const { return m_context; } |
| pretty_printer *get_printer () const { return m_printer; } |
| token_printer &get_token_printer () { return m_token_printer; } |
| enum sarif_version get_version () const { return m_sarif_gen_opts.m_version; } |
| |
| size_t num_results () const { return m_results_array->size (); } |
| sarif_result &get_result (size_t idx) |
| { |
| auto element = (*m_results_array)[idx]; |
| gcc_assert (element); |
| return *static_cast<sarif_result *> (element); |
| } |
| |
| const sarif_generation_options &get_opts () const { return m_sarif_gen_opts; } |
| |
| std::unique_ptr<sarif_logical_location> |
| make_minimal_sarif_logical_location (logical_locations::key); |
| |
| private: |
| class sarif_token_printer : public token_printer |
| { |
| public: |
| sarif_token_printer (sarif_builder &builder) |
| : m_builder (builder) |
| { |
| } |
| void print_tokens (pretty_printer *pp, |
| const pp_token_list &tokens) final override; |
| private: |
| sarif_builder &m_builder; |
| }; |
| |
| std::unique_ptr<sarif_result> |
| make_result_object (const diagnostic_info &diagnostic, |
| enum kind orig_diag_kind, |
| unsigned idx_within_parent); |
| 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, |
| logical_locations::key logical_loc); |
| std::unique_ptr<sarif_location> |
| make_location_object (sarif_location_manager &loc_mgr, |
| const paths::event &event, |
| enum diagnostic_artifact_role role); |
| std::unique_ptr<sarif_code_flow> |
| make_code_flow_object (sarif_result &result, |
| unsigned idx_within_parent, |
| const paths::path &path); |
| void |
| populate_thread_flow_location_object (sarif_result &result, |
| sarif_thread_flow_location &thread_flow_loc_obj, |
| const paths::event &event, |
| int event_execution_idx); |
| std::unique_ptr<json::array> |
| maybe_make_kinds_array (paths::event::meaning m) const; |
| std::unique_ptr<sarif_physical_location> |
| maybe_make_physical_location_object (location_t loc, |
| enum diagnostic_artifact_role role, |
| int column_override, |
| const content_renderer *snippet_renderer); |
| std::unique_ptr<sarif_artifact_location> |
| make_artifact_location_object (location_t loc); |
| std::unique_ptr<sarif_artifact_location> |
| make_artifact_location_object_for_pwd () const; |
| std::unique_ptr<sarif_region> |
| maybe_make_region_object (location_t loc, |
| int column_override) const; |
| std::unique_ptr<sarif_region> |
| maybe_make_region_object_for_context (location_t loc, |
| const content_renderer *snippet_renderer) const; |
| std::unique_ptr<sarif_region> |
| make_region_object_for_hint (const fixit_hint &hint) const; |
| |
| int |
| ensure_sarif_logical_location_for (logical_locations::key k); |
| |
| std::unique_ptr<sarif_multiformat_message_string> |
| make_multiformat_message_string (const char *msg) const; |
| std::unique_ptr<sarif_log> |
| make_top_level_object (std::unique_ptr<sarif_invocation> invocation_obj, |
| std::unique_ptr<json::array> results); |
| std::unique_ptr<sarif_run> |
| make_run_object (std::unique_ptr<sarif_invocation> invocation_obj, |
| std::unique_ptr<json::array> results); |
| std::unique_ptr<sarif_tool> |
| make_tool_object (); |
| std::unique_ptr<sarif_tool_component> |
| make_driver_tool_component_object (); |
| std::unique_ptr<json::array> maybe_make_taxonomies_array () const; |
| std::unique_ptr<sarif_tool_component> |
| maybe_make_cwe_taxonomy_object () const; |
| std::unique_ptr<sarif_tool_component_reference> |
| make_tool_component_reference_object_for_cwe () const; |
| std::unique_ptr<sarif_reporting_descriptor> |
| make_reporting_descriptor_object_for_warning (const diagnostic_info &diagnostic, |
| enum kind orig_diag_kind, |
| const char *option_text); |
| std::unique_ptr<sarif_reporting_descriptor> |
| make_reporting_descriptor_object_for_cwe_id (int cwe_id) const; |
| std::unique_ptr<sarif_reporting_descriptor_reference> |
| make_reporting_descriptor_reference_object_for_cwe_id (int cwe_id); |
| sarif_artifact & |
| get_or_create_artifact (const char *filename, |
| enum diagnostic_artifact_role role, |
| bool embed_contents); |
| char * |
| get_source_lines (const char *filename, |
| int start_line, |
| int end_line) const; |
| std::unique_ptr<sarif_artifact_content> |
| maybe_make_artifact_content_object (const char *filename, |
| int start_line, |
| int end_line, |
| const content_renderer *r) const; |
| std::unique_ptr<sarif_fix> |
| make_fix_object (const rich_location &rich_loc); |
| std::unique_ptr<sarif_artifact_change> |
| make_artifact_change_object (const rich_location &richloc); |
| std::unique_ptr<sarif_replacement> |
| make_replacement_object (const fixit_hint &hint) const; |
| std::unique_ptr<sarif_artifact_content> |
| make_artifact_content_object (const char *text) const; |
| int get_sarif_column (expanded_location exploc) const; |
| |
| std::unique_ptr<json::object> |
| make_stack_from_backtrace (); |
| |
| diagnostics::context &m_context; |
| pretty_printer *m_printer; |
| const line_maps *m_line_maps; |
| sarif_token_printer m_token_printer; |
| |
| const logical_locations::manager *m_logical_loc_mgr; |
| |
| /* The JSON object for the invocation object. */ |
| std::unique_ptr<sarif_invocation> m_invocation_obj; |
| |
| /* The JSON array of pending diagnostics. */ |
| std::unique_ptr<json::array> m_results_array; |
| |
| /* The JSON object for the result object (if any) in the current |
| diagnostic group. */ |
| std::unique_ptr<sarif_result> m_cur_group_result; |
| |
| /* Ideally we'd use std::unique_ptr<sarif_artifact> here, but I had |
| trouble getting this to work when building with GCC 4.8. */ |
| ordered_hash_map <nofree_string_hash, |
| sarif_artifact *> m_filename_to_artifact_map; |
| |
| bool m_seen_any_relative_paths; |
| hash_set <free_string_hash> m_rule_id_set; |
| std::unique_ptr<json::array> m_rules_arr; |
| |
| /* The set of all CWE IDs we've seen, if any. */ |
| hash_set <int_hash <int, 0, 1> > m_cwe_id_set; |
| |
| std::unique_ptr<sarif_array_of_unique<sarif_logical_location>> m_cached_logical_locs; |
| |
| std::unique_ptr<sarif_array_of_unique<sarif_graph>> m_run_graphs; |
| |
| int m_tabstop; |
| |
| std::unique_ptr<sarif_serialization_format> m_serialization_format; |
| const sarif_generation_options m_sarif_gen_opts; |
| |
| unsigned m_next_result_idx; |
| sarif_code_flow *m_current_code_flow; |
| }; |
| |
| /* class sarif_object : public json::object. */ |
| |
| sarif_property_bag & |
| sarif_object::get_or_create_properties () |
| { |
| json::value *properties_val = get ("properties"); |
| if (properties_val) |
| { |
| if (properties_val->get_kind () == json::JSON_OBJECT) |
| return *static_cast <sarif_property_bag *> (properties_val); |
| } |
| |
| sarif_property_bag *bag = new sarif_property_bag (); |
| set ("properties", bag); |
| return *bag; |
| } |
| |
| /* class sarif_invocation : public sarif_object. */ |
| |
| sarif_invocation::sarif_invocation (sarif_builder &builder, |
| const char * const *original_argv) |
| : m_notifications_arr (std::make_unique<json::array> ()), |
| m_success (true) |
| { |
| // "arguments" property (SARIF v2.1.0 section 3.20.2) |
| if (original_argv) |
| { |
| auto arguments_arr = std::make_unique<json::array> (); |
| for (size_t i = 0; original_argv[i]; ++i) |
| arguments_arr->append_string (original_argv[i]); |
| set<json::array> ("arguments", std::move (arguments_arr)); |
| } |
| |
| // "workingDirectory" property (SARIF v2.1.0 section 3.20.19) |
| if (const char *pwd = getpwd ()) |
| set<sarif_artifact_location> ("workingDirectory", |
| builder.make_artifact_location_object (pwd)); |
| |
| // "startTimeUtc" property (SARIF v2.1.0 section 3.20.7) |
| set<json::string> ("startTimeUtc", |
| make_date_time_string_for_current_time ()); |
| } |
| |
| /* Handle an internal compiler error DIAGNOSTIC. |
| Add an object representing the ICE to the notifications array. */ |
| |
| void |
| sarif_invocation::add_notification_for_ice (const diagnostic_info &diagnostic, |
| sarif_builder &builder, |
| std::unique_ptr<json::object> backtrace) |
| { |
| m_success = false; |
| |
| auto notification |
| = std::make_unique<sarif_ice_notification> (diagnostic, |
| builder, |
| std::move (backtrace)); |
| |
| /* Support for related locations within a notification was added |
| in SARIF 2.2; see https://github.com/oasis-tcs/sarif-spec/issues/540 */ |
| if (builder.get_version () >= sarif_version::v2_2_prerelease_2024_08_08) |
| notification->process_worklist (builder); |
| |
| m_notifications_arr->append<sarif_ice_notification> |
| (std::move (notification)); |
| } |
| |
| void |
| sarif_invocation::prepare_to_flush (sarif_builder &builder) |
| { |
| const context &dc = builder.get_context (); |
| |
| /* "executionSuccessful" property (SARIF v2.1.0 section 3.20.14). */ |
| if (dc.execution_failed_p ()) |
| m_success = false; |
| set_bool ("executionSuccessful", m_success); |
| |
| /* "toolExecutionNotifications" property (SARIF v2.1.0 section 3.20.21). */ |
| set ("toolExecutionNotifications", std::move (m_notifications_arr)); |
| |
| /* Call client hook, allowing it to create a custom property bag for |
| this object (SARIF v2.1.0 section 3.8) e.g. for recording time vars. */ |
| if (auto client_data_hooks = dc.get_client_data_hooks ()) |
| client_data_hooks->add_sarif_invocation_properties (*this); |
| |
| // "endTimeUtc" property (SARIF v2.1.0 section 3.20.8); |
| set<json::string> ("endTimeUtc", |
| make_date_time_string_for_current_time ()); |
| } |
| |
| /* class sarif_artifact : public sarif_object. */ |
| |
| /* Add ROLE to this artifact's roles. |
| If EMBED_CONTENTS is true, then flag that we will attempt to embed the |
| contents of this artifact when writing it out. */ |
| |
| 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; |
| |
| /* In SARIF v2.1.0 section 3.24.6 "roles" property: |
| "resultFile" is for an artifact |
| "which the analysis tool was not explicitly instructed to scan", |
| whereas "analysisTarget" is for one where the |
| "analysis tool was instructed to scan this artifact". |
| Hence the latter excludes the former. */ |
| if (role == diagnostic_artifact_role::result_file) |
| if (bitmap_bit_p (m_roles, (int)diagnostic_artifact_role::analysis_target)) |
| return; |
| |
| bitmap_set_bit (m_roles, (int)role); |
| } |
| |
| /* Populate the "contents" property (SARIF v2.1.0 section 3.24.8). |
| We do this after initialization to |
| (a) ensure that any charset options have been set |
| (b) only populate it for artifacts that are referenced by a location. */ |
| |
| void |
| sarif_artifact::populate_contents (sarif_builder &builder) |
| { |
| if (auto artifact_content_obj |
| = builder.maybe_make_artifact_content_object (m_filename)) |
| set<sarif_artifact_content> ("contents", std::move (artifact_content_obj)); |
| } |
| |
| /* Get a string for ROLE corresponding to the |
| SARIF v2.1.0 section 3.24.6 "roles" property. */ |
| |
| static const char * |
| get_artifact_role_string (enum diagnostic_artifact_role role) |
| { |
| switch (role) |
| { |
| default: |
| gcc_unreachable (); |
| case diagnostic_artifact_role::analysis_target: |
| return "analysisTarget"; |
| case diagnostic_artifact_role::debug_output_file: |
| 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"; |
| } |
| } |
| |
| /* Populate the "roles" property of this sarif_artifact with a new |
| json::array for the artifact.roles property (SARIF v2.1.0 section 3.24.6) |
| containing strings such as "analysisTarget", "resultFile" |
| and/or "tracedFile". */ |
| |
| void |
| sarif_artifact::populate_roles () |
| { |
| if (bitmap_empty_p (m_roles)) |
| return; |
| auto roles_arr (std::make_unique<json::array> ()); |
| for (int i = 0; i < (int)diagnostic_artifact_role::NUM_ROLES; i++) |
| if (bitmap_bit_p (m_roles, i)) |
| { |
| enum diagnostic_artifact_role role = (enum diagnostic_artifact_role)i; |
| roles_arr->append_string (get_artifact_role_string (role)); |
| } |
| set<json::array> ("roles", std::move (roles_arr)); |
| } |
| |
| /* class sarif_location_manager : public sarif_object. */ |
| |
| /* Base implementation of sarif_location_manager::add_related_location vfunc. |
| |
| Add LOCATION_OBJ to this object's "relatedLocations" array, |
| creating it if it doesn't yet exist. */ |
| |
| void |
| sarif_location_manager:: |
| add_related_location (std::unique_ptr<sarif_location> location_obj, |
| sarif_builder &) |
| { |
| if (!m_related_locations_arr) |
| { |
| m_related_locations_arr = new json::array (); |
| /* Give ownership of m_related_locations_arr to json::object; |
| keep a borrowed ptr. */ |
| set ("relatedLocations", m_related_locations_arr); |
| } |
| m_related_locations_arr->append (std::move (location_obj)); |
| } |
| |
| 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<sarif_location> 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), builder); |
| auto kv |
| = std::pair<location_t, sarif_location *> (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; |
| case worklist_item::kind::unlabelled_secondary_location: |
| { |
| sarif_location &primary_loc_obj = item.m_location_obj; |
| sarif_location *secondary_loc_obj = nullptr; |
| auto iter = m_unlabelled_secondary_locations.find (item.m_where); |
| if (iter != m_unlabelled_secondary_locations.end ()) |
| secondary_loc_obj = iter->second; |
| else |
| { |
| std::unique_ptr<sarif_location> new_loc_obj |
| = builder.make_location_object |
| (*this, |
| item.m_where, |
| diagnostic_artifact_role::scanned_file); |
| secondary_loc_obj = new_loc_obj.get (); |
| add_related_location (std::move (new_loc_obj), builder); |
| auto kv |
| = std::pair<location_t, sarif_location *> (item.m_where, |
| secondary_loc_obj); |
| m_unlabelled_secondary_locations.insert (kv); |
| } |
| gcc_assert (secondary_loc_obj); |
| primary_loc_obj.lazily_add_relationship |
| (*secondary_loc_obj, |
| location_relationship_kind::relevant, |
| *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 |
| "relatedLocations" property of result objects (SARIF v2.1.0 section 3.27.22), |
| so we lazily set this property and populate the array if and when |
| secondary diagnostics occur (such as notes to a warning). */ |
| |
| void |
| sarif_result::on_nested_diagnostic (const diagnostic_info &diagnostic, |
| enum kind /*orig_diag_kind*/, |
| sarif_builder &builder) |
| { |
| /* We don't yet generate meaningful logical locations for notes; |
| sometimes these will related to current_function_decl, but |
| often they won't. */ |
| auto location_obj |
| = builder.make_location_object (this, *diagnostic.m_richloc, |
| logical_locations::key (), |
| diagnostic_artifact_role::result_file); |
| auto message_obj |
| = builder.make_message_object (pp_formatted_text (builder.get_printer ())); |
| pp_clear_output_area (builder.get_printer ()); |
| location_obj->set<sarif_message> ("message", std::move (message_obj)); |
| |
| /* Add nesting level, as per "P3358R0 SARIF for Structured Diagnostics" |
| https://wg21.link/P3358R0 */ |
| sarif_property_bag &bag = location_obj->get_or_create_properties (); |
| bag.set_integer ("nestingLevel", |
| builder.get_context ().get_diagnostic_nesting_level ()); |
| |
| add_related_location (std::move (location_obj), builder); |
| } |
| |
| /* Handle diagrams that occur within a diagnostic group. |
| The closest thing in SARIF seems to be to add a location to the |
| "releatedLocations" property (SARIF v2.1.0 section 3.27.22), |
| and to put the diagram into the "message" property of that location |
| (SARIF v2.1.0 section 3.28.5). */ |
| |
| void |
| sarif_result::on_diagram (const diagram &d, |
| sarif_builder &builder) |
| { |
| auto location_obj = std::make_unique<sarif_location> (); |
| auto message_obj = builder.make_message_object_for_diagram (d); |
| location_obj->set<sarif_message> ("message", std::move (message_obj)); |
| |
| add_related_location (std::move (location_obj), builder); |
| } |
| |
| /* 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 <json::integer_number *> (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 |
| = std::make_unique<sarif_location_relationship> (target, loc_mgr); |
| sarif_location_relationship *relationship = relationship_obj.get (); |
| auto kv |
| = std::pair<sarif_location *, |
| sarif_location_relationship *> (&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 <json::array *> (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 (const diagnostic_info &diagnostic, |
| sarif_builder &builder, |
| std::unique_ptr<json::object> backtrace) |
| { |
| /* "locations" property (SARIF v2.1.0 section 3.58.4). */ |
| auto locations_arr |
| = builder.make_locations_arr (*this, |
| diagnostic, |
| diagnostic_artifact_role::result_file); |
| set<json::array> ("locations", std::move (locations_arr)); |
| |
| /* "message" property (SARIF v2.1.0 section 3.85.5). */ |
| auto message_obj |
| = builder.make_message_object (pp_formatted_text (builder.get_printer ())); |
| pp_clear_output_area (builder.get_printer ()); |
| set<sarif_message> ("message", std::move (message_obj)); |
| |
| /* "level" property (SARIF v2.1.0 section 3.58.6). */ |
| set_string ("level", "error"); |
| |
| /* If we have backtrace information, add it as part of a property bag. */ |
| if (backtrace) |
| { |
| sarif_property_bag &bag = get_or_create_properties (); |
| bag.set ("gcc/backtrace", std::move (backtrace)); |
| } |
| } |
| |
| /* Implementation of sarif_location_manager::add_related_location vfunc |
| for notifications. */ |
| |
| void |
| sarif_ice_notification:: |
| add_related_location (std::unique_ptr<sarif_location> location_obj, |
| sarif_builder &builder) |
| { |
| /* Support for related locations within a notification was added |
| in SARIF 2.2; see https://github.com/oasis-tcs/sarif-spec/issues/540 */ |
| if (builder.get_version () >= sarif_version::v2_2_prerelease_2024_08_08) |
| sarif_location_manager::add_related_location (std::move (location_obj), |
| builder); |
| /* Otherwise implicitly discard LOCATION_OBJ. */ |
| } |
| |
| /* 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 <json::integer_number *> (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_code_flow : public sarif_object. */ |
| |
| sarif_code_flow::sarif_code_flow (sarif_result &parent, |
| unsigned idx_within_parent) |
| : m_parent (parent), |
| m_idx_within_parent (idx_within_parent) |
| { |
| /* "threadFlows" property (SARIF v2.1.0 section 3.36.3). */ |
| auto thread_flows_arr = std::make_unique<json::array> (); |
| m_thread_flows_arr = thread_flows_arr.get (); // borrowed |
| set<json::array> ("threadFlows", std::move (thread_flows_arr)); |
| } |
| |
| sarif_thread_flow & |
| sarif_code_flow::get_or_append_thread_flow (const paths::thread &thread, |
| paths::thread_id_t thread_id) |
| { |
| sarif_thread_flow **slot = m_thread_id_map.get (thread_id); |
| if (slot) |
| return **slot; |
| |
| unsigned next_thread_flow_idx = m_thread_flows_arr->size (); |
| auto thread_flow_obj |
| = std::make_unique<sarif_thread_flow> (*this, thread, next_thread_flow_idx); |
| m_thread_id_map.put (thread_id, thread_flow_obj.get ()); // borrowed |
| sarif_thread_flow *result = thread_flow_obj.get (); |
| m_thread_flows_arr->append<sarif_thread_flow> (std::move (thread_flow_obj)); |
| return *result; |
| } |
| |
| sarif_thread_flow & |
| sarif_code_flow::get_thread_flow (paths::thread_id_t thread_id) |
| { |
| sarif_thread_flow **slot = m_thread_id_map.get (thread_id); |
| gcc_assert (slot); // it must already have one |
| return **slot; |
| } |
| |
| void |
| sarif_code_flow::add_location (sarif_thread_flow_location &tfl_obj) |
| { |
| m_all_tfl_objs.push_back (&tfl_obj); |
| } |
| |
| sarif_thread_flow_location & |
| sarif_code_flow::get_thread_flow_loc_obj (paths::event_id_t event_id) const |
| { |
| gcc_assert (event_id.known_p ()); |
| gcc_assert ((size_t)event_id.zero_based () < m_all_tfl_objs.size ()); |
| sarif_thread_flow_location *tfl_obj = m_all_tfl_objs[event_id.zero_based ()]; |
| gcc_assert (tfl_obj); |
| return *tfl_obj; |
| } |
| |
| /* class sarif_thread_flow : public sarif_object. */ |
| |
| sarif_thread_flow::sarif_thread_flow (sarif_code_flow &parent, |
| const paths::thread &thread, |
| unsigned idx_within_parent) |
| : m_parent (parent), |
| m_idx_within_parent (idx_within_parent) |
| { |
| /* "id" property (SARIF v2.1.0 section 3.37.2). */ |
| label_text name (thread.get_name (false)); |
| set_string ("id", name.get ()); |
| |
| /* "locations" property (SARIF v2.1.0 section 3.37.6). */ |
| m_locations_arr = new json::array (); |
| |
| /* Give ownership of m_locations_arr to json::object; |
| keep a borrowed ptr. */ |
| set ("locations", m_locations_arr); |
| } |
| |
| /* Add a sarif_thread_flow_location to this threadFlow object, but |
| don't populate it yet. */ |
| |
| sarif_thread_flow_location & |
| sarif_thread_flow::add_location () |
| { |
| const unsigned thread_flow_location_idx = m_locations_arr->size (); |
| sarif_thread_flow_location *thread_flow_loc_obj |
| = new sarif_thread_flow_location (*this, thread_flow_location_idx); |
| m_locations_arr->append (thread_flow_loc_obj); |
| m_parent.add_location (*thread_flow_loc_obj); |
| return *thread_flow_loc_obj; |
| } |
| |
| /* class sarif_builder. */ |
| |
| /* sarif_builder's ctor. */ |
| |
| sarif_builder::sarif_builder (diagnostics::context &dc, |
| pretty_printer &printer, |
| const line_maps *line_maps, |
| std::unique_ptr<sarif_serialization_format> serialization_format, |
| const sarif_generation_options &sarif_gen_opts) |
| : m_context (dc), |
| m_printer (&printer), |
| m_line_maps (line_maps), |
| m_token_printer (*this), |
| m_logical_loc_mgr (nullptr), |
| m_invocation_obj |
| (std::make_unique<sarif_invocation> (*this, |
| dc.get_original_argv ())), |
| m_results_array (new json::array ()), |
| m_cur_group_result (nullptr), |
| m_seen_any_relative_paths (false), |
| m_rule_id_set (), |
| m_rules_arr (new json::array ()), |
| m_cached_logical_locs |
| (std::make_unique<sarif_array_of_unique<sarif_logical_location>> ()), |
| m_run_graphs |
| (std::make_unique<sarif_array_of_unique<sarif_graph>> ()), |
| m_tabstop (dc.m_tabstop), |
| m_serialization_format (std::move (serialization_format)), |
| m_sarif_gen_opts (sarif_gen_opts), |
| m_next_result_idx (0), |
| m_current_code_flow (nullptr) |
| { |
| gcc_assert (m_line_maps); |
| gcc_assert (m_serialization_format); |
| |
| if (auto client_data_hooks = dc.get_client_data_hooks ()) |
| m_logical_loc_mgr = client_data_hooks->get_logical_location_manager (); |
| } |
| |
| sarif_builder::~sarif_builder () |
| { |
| /* Normally m_filename_to_artifact_map will have been emptied as part |
| of make_run_object, but this isn't run by all the selftests. |
| Ensure the artifact objects are cleaned up for such cases. */ |
| for (auto iter : m_filename_to_artifact_map) |
| { |
| sarif_artifact *artifact_obj = iter.second; |
| delete artifact_obj; |
| } |
| } |
| |
| /* Functions at which to stop the backtrace print. It's not |
| particularly helpful to print the callers of these functions. */ |
| |
| static const char * const bt_stop[] = |
| { |
| "main", |
| "toplev::main", |
| "execute_one_pass", |
| "compile_file", |
| }; |
| |
| struct bt_closure |
| { |
| bt_closure (sarif_builder &builder, |
| json::array *frames_arr) |
| : m_builder (builder), |
| m_frames_arr (frames_arr) |
| { |
| } |
| |
| sarif_builder &m_builder; |
| json::array *m_frames_arr; |
| }; |
| |
| /* A callback function passed to the backtrace_full function. */ |
| |
| static int |
| bt_callback (void *data, uintptr_t pc, const char *filename, int lineno, |
| const char *function) |
| { |
| bt_closure *closure = (bt_closure *)data; |
| |
| /* If we don't have any useful information, don't print |
| anything. */ |
| if (filename == nullptr && function == nullptr) |
| return 0; |
| |
| /* Skip functions in context.cc or diagnostic-global-context.cc. */ |
| if (closure->m_frames_arr->size () == 0 |
| && filename != nullptr |
| && (strcmp (lbasename (filename), "context.cc") == 0 |
| || strcmp (lbasename (filename), |
| "diagnostic-global-context.cc") == 0)) |
| return 0; |
| |
| /* Print up to 20 functions. We could make this a --param, but |
| since this is only for debugging just use a constant for now. */ |
| if (closure->m_frames_arr->size () >= 20) |
| { |
| /* Returning a non-zero value stops the backtrace. */ |
| return 1; |
| } |
| |
| char *alc = nullptr; |
| if (function != nullptr) |
| { |
| char *str = cplus_demangle_v3 (function, |
| (DMGL_VERBOSE | DMGL_ANSI |
| | DMGL_GNU_V3 | DMGL_PARAMS)); |
| if (str != nullptr) |
| { |
| alc = str; |
| function = str; |
| } |
| |
| for (size_t i = 0; i < ARRAY_SIZE (bt_stop); ++i) |
| { |
| size_t len = strlen (bt_stop[i]); |
| if (strncmp (function, bt_stop[i], len) == 0 |
| && (function[len] == '\0' || function[len] == '(')) |
| { |
| if (alc != nullptr) |
| free (alc); |
| /* Returning a non-zero value stops the backtrace. */ |
| return 1; |
| } |
| } |
| } |
| |
| auto frame_obj = std::make_unique<json::object> (); |
| |
| /* I tried using sarifStack and sarifStackFrame for this |
| but it's not a good fit e.g. PC information. */ |
| char buf[128]; |
| snprintf (buf, sizeof (buf) - 1, "0x%lx", (unsigned long)pc); |
| frame_obj->set_string ("pc", buf); |
| if (function) |
| frame_obj->set_string ("function", function); |
| if (filename) |
| frame_obj->set_string ("filename", filename); |
| frame_obj->set_integer ("lineno", lineno); |
| closure->m_frames_arr->append (std::move (frame_obj)); |
| |
| if (alc != nullptr) |
| free (alc); |
| |
| return 0; |
| } |
| |
| /* Attempt to generate a JSON object representing a backtrace, |
| for adding to ICE notifications. */ |
| |
| std::unique_ptr<json::object> |
| sarif_builder::make_stack_from_backtrace () |
| { |
| auto frames_arr = std::make_unique<json::array> (); |
| |
| backtrace_state *state = nullptr; |
| state = backtrace_create_state (nullptr, 0, nullptr, nullptr); |
| bt_closure closure (*this, frames_arr.get ()); |
| const int frames_to_skip = 5; |
| if (state != nullptr) |
| backtrace_full (state, frames_to_skip, bt_callback, nullptr, |
| (void *) &closure); |
| |
| if (frames_arr->size () == 0) |
| return nullptr; |
| |
| auto stack = std::make_unique<json::object> (); |
| stack->set ("frames", std::move (frames_arr)); |
| return stack; |
| } |
| |
| void |
| sarif_builder::set_main_input_filename (const char *name) |
| { |
| /* Mark NAME as the artifact that the tool was instructed to scan. |
| Only quote the contents if it gets referenced by physical locations, |
| since otherwise the "no diagnostics" case would quote the main input |
| file, and doing so noticeably bloated the output seen in analyzer |
| integration testing (build directory went from 20G -> 21G). */ |
| if (name) |
| get_or_create_artifact (name, |
| diagnostic_artifact_role::analysis_target, |
| false); |
| } |
| |
| /* Implementation of "on_report_diagnostic" for SARIF output. */ |
| |
| void |
| sarif_builder::on_report_diagnostic (const diagnostic_info &diagnostic, |
| enum kind orig_diag_kind, |
| sarif_sink_buffer *buffer) |
| { |
| pp_output_formatted_text (m_printer, m_context.get_urlifier ()); |
| |
| if (diagnostic.m_kind == kind::ice || diagnostic.m_kind == kind::ice_nobt) |
| { |
| std::unique_ptr<json::object> stack = make_stack_from_backtrace (); |
| m_invocation_obj->add_notification_for_ice (diagnostic, *this, |
| std::move (stack)); |
| |
| /* Print a header for the remaining output to stderr, and |
| return, attempting to print the usual ICE messages to |
| stderr. Hopefully this will be helpful to the user in |
| indicating what's gone wrong (also for DejaGnu, for pruning |
| those messages). */ |
| fnotice (stderr, "Internal compiler error:\n"); |
| |
| return; |
| } |
| |
| if (buffer) |
| { |
| /* When buffering, we can only handle top-level results. */ |
| gcc_assert (!m_cur_group_result); |
| buffer->add_result (make_result_object (diagnostic, orig_diag_kind, |
| m_next_result_idx++)); |
| return; |
| } |
| |
| if (m_cur_group_result) |
| /* Nested diagnostic. */ |
| m_cur_group_result->on_nested_diagnostic (diagnostic, |
| orig_diag_kind, |
| *this); |
| else |
| { |
| /* Top-level diagnostic. */ |
| m_cur_group_result = make_result_object (diagnostic, orig_diag_kind, |
| m_next_result_idx++); |
| } |
| } |
| |
| /* Implementation of diagnostics::context::m_diagrams.m_emission_cb |
| for SARIF output. */ |
| |
| void |
| sarif_builder::emit_diagram (const diagram &d) |
| { |
| /* We must be within the emission of a top-level diagnostic. */ |
| gcc_assert (m_cur_group_result); |
| m_cur_group_result->on_diagram (d, *this); |
| } |
| |
| /* Implementation of "end_group_cb" for SARIF output. */ |
| |
| void |
| sarif_builder::end_group () |
| { |
| if (m_cur_group_result) |
| { |
| m_cur_group_result->process_worklist (*this); |
| m_results_array->append<sarif_result> (std::move (m_cur_group_result)); |
| } |
| } |
| |
| void |
| sarif_builder:: |
| report_global_digraph (const lazily_created<digraphs::digraph> &ldg) |
| { |
| auto &dg = ldg.get_or_create (); |
| |
| /* Presumably the location manager must be nullptr; see |
| https://github.com/oasis-tcs/sarif-spec/issues/712 */ |
| m_run_graphs->append (make_sarif_graph (dg, this, nullptr)); |
| } |
| |
| /* Create a top-level object, and add it to all the results |
| (and other entities) we've seen so far, moving ownership |
| to the object. */ |
| |
| std::unique_ptr<sarif_log> |
| sarif_builder::flush_to_object () |
| { |
| m_invocation_obj->prepare_to_flush (*this); |
| std::unique_ptr<sarif_log> top |
| = make_top_level_object (std::move (m_invocation_obj), |
| std::move (m_results_array)); |
| return top; |
| } |
| |
| /* Create a top-level object, and add it to all the results |
| (and other entities) we've seen so far. |
| |
| Flush it all to OUTF. */ |
| |
| void |
| sarif_builder::flush_to_file (FILE *outf) |
| { |
| std::unique_ptr<sarif_log> top = flush_to_object (); |
| m_serialization_format->write_to_file (outf, *top); |
| } |
| |
| /* Attempt to convert DIAG_KIND to a suitable value for the "level" |
| property (SARIF v2.1.0 section 3.27.10). |
| |
| Return nullptr if there isn't one. */ |
| |
| static const char * |
| maybe_get_sarif_level (enum kind diag_kind) |
| { |
| switch (diag_kind) |
| { |
| case kind::warning: |
| return "warning"; |
| case kind::error: |
| return "error"; |
| case kind::note: |
| case kind::anachronism: |
| return "note"; |
| default: |
| return nullptr; |
| } |
| } |
| |
| /* Make a string for DIAG_KIND suitable for use a ruleId |
| (SARIF v2.1.0 section 3.27.5) as a fallback for when we don't |
| have anything better to use. */ |
| |
| static char * |
| make_rule_id_for_diagnostic_kind (enum kind diag_kind) |
| { |
| /* Lose the trailing ": ". */ |
| const char *kind_text = get_text_for_kind (diag_kind); |
| size_t len = strlen (kind_text); |
| gcc_assert (len > 2); |
| gcc_assert (kind_text[len - 2] == ':'); |
| gcc_assert (kind_text[len - 1] == ' '); |
| char *rstrip = xstrdup (kind_text); |
| rstrip[len - 2] = '\0'; |
| return rstrip; |
| } |
| |
| /* Make a "result" object (SARIF v2.1.0 section 3.27) for DIAGNOSTIC. */ |
| |
| std::unique_ptr<sarif_result> |
| sarif_builder::make_result_object (const diagnostic_info &diagnostic, |
| enum kind orig_diag_kind, |
| unsigned idx_within_parent) |
| { |
| auto result_obj = std::make_unique<sarif_result> (idx_within_parent); |
| |
| /* "ruleId" property (SARIF v2.1.0 section 3.27.5). */ |
| /* Ideally we'd have an option_name for these. */ |
| if (char *option_text |
| = m_context.make_option_name (diagnostic.m_option_id, |
| orig_diag_kind, diagnostic.m_kind)) |
| { |
| /* Lazily create reportingDescriptor objects for and add to m_rules_arr. |
| Set ruleId referencing them. */ |
| result_obj->set_string ("ruleId", option_text); |
| if (m_rule_id_set.contains (option_text)) |
| free (option_text); |
| else |
| { |
| /* This is the first time we've seen this ruleId. */ |
| /* Add to set, taking ownership. */ |
| m_rule_id_set.add (option_text); |
| |
| m_rules_arr->append<sarif_reporting_descriptor> |
| (make_reporting_descriptor_object_for_warning (diagnostic, |
| orig_diag_kind, |
| option_text)); |
| } |
| } |
| else |
| { |
| /* Otherwise, we have an "error" or a stray "note"; use the |
| diagnostic kind as the ruleId, so that the result object at least |
| has a ruleId. |
| We don't bother creating reportingDescriptor objects for these. */ |
| char *rule_id = make_rule_id_for_diagnostic_kind (orig_diag_kind); |
| result_obj->set_string ("ruleId", rule_id); |
| free (rule_id); |
| } |
| |
| if (diagnostic.m_metadata) |
| { |
| /* "taxa" property (SARIF v2.1.0 section 3.27.8). */ |
| if (int cwe_id = diagnostic.m_metadata->get_cwe ()) |
| { |
| auto taxa_arr = std::make_unique<json::array> (); |
| taxa_arr->append<sarif_reporting_descriptor_reference> |
| (make_reporting_descriptor_reference_object_for_cwe_id (cwe_id)); |
| result_obj->set<json::array> ("taxa", std::move (taxa_arr)); |
| } |
| |
| diagnostic.m_metadata->maybe_add_sarif_properties (*result_obj); |
| |
| /* We don't yet support diagnostics::metadata::rule. */ |
| } |
| |
| /* "level" property (SARIF v2.1.0 section 3.27.10). */ |
| if (const char *sarif_level = maybe_get_sarif_level (diagnostic.m_kind)) |
| result_obj->set_string ("level", sarif_level); |
| |
| /* "message" property (SARIF v2.1.0 section 3.27.11). */ |
| auto message_obj |
| = make_message_object (pp_formatted_text (m_printer)); |
| pp_clear_output_area (m_printer); |
| result_obj->set<sarif_message> ("message", std::move (message_obj)); |
| |
| /* "locations" property (SARIF v2.1.0 section 3.27.12). */ |
| result_obj->set<json::array> |
| ("locations", |
| make_locations_arr (*result_obj.get (), |
| diagnostic, |
| diagnostic_artifact_role::result_file)); |
| |
| /* "codeFlows" property (SARIF v2.1.0 section 3.27.18). */ |
| if (const paths::path *path = diagnostic.m_richloc->get_path ()) |
| { |
| auto code_flows_arr = std::make_unique<json::array> (); |
| const unsigned code_flow_index = 0; |
| code_flows_arr->append<sarif_code_flow> |
| (make_code_flow_object (*result_obj.get (), |
| code_flow_index, |
| *path)); |
| result_obj->set<json::array> ("codeFlows", std::move (code_flows_arr)); |
| } |
| |
| // "graphs" property (SARIF v2.1.0 section 3.27.19). */ |
| if (diagnostic.m_metadata) |
| if (auto ldg = diagnostic.m_metadata->get_lazy_digraphs ()) |
| { |
| auto &digraphs = ldg->get_or_create (); |
| auto graphs_arr = std::make_unique<json::array> (); |
| for (auto &iter : digraphs) |
| graphs_arr->append (make_sarif_graph (*iter, this, |
| result_obj.get ())); |
| if (graphs_arr->size () > 0) |
| result_obj->set<json::array> ("graphs", std::move (graphs_arr)); |
| } |
| |
| /* The "relatedLocations" property (SARIF v2.1.0 section 3.27.22) is |
| set up later, if any nested diagnostics occur within this diagnostic |
| group. */ |
| |
| /* "fixes" property (SARIF v2.1.0 section 3.27.30). */ |
| const rich_location *richloc = diagnostic.m_richloc; |
| if (richloc->get_num_fixit_hints ()) |
| { |
| auto fix_arr = std::make_unique<json::array> (); |
| fix_arr->append<sarif_fix> (make_fix_object (*richloc)); |
| result_obj->set<json::array> ("fixes", std::move (fix_arr)); |
| } |
| |
| return result_obj; |
| } |
| |
| /* Make a "reportingDescriptor" object (SARIF v2.1.0 section 3.49) |
| for a GCC warning. */ |
| |
| std::unique_ptr<sarif_reporting_descriptor> |
| sarif_builder:: |
| make_reporting_descriptor_object_for_warning (const diagnostic_info &diagnostic, |
| enum kind /*orig_diag_kind*/, |
| const char *option_text) |
| { |
| auto reporting_desc = std::make_unique<sarif_reporting_descriptor> (); |
| |
| /* "id" property (SARIF v2.1.0 section 3.49.3). */ |
| reporting_desc->set_string ("id", option_text); |
| |
| /* We don't implement "name" property (SARIF v2.1.0 section 3.49.7), since |
| it seems redundant compared to "id". */ |
| |
| /* "helpUri" property (SARIF v2.1.0 section 3.49.12). */ |
| if (char *option_url = m_context.make_option_url (diagnostic.m_option_id)) |
| { |
| reporting_desc->set_string ("helpUri", option_url); |
| free (option_url); |
| } |
| |
| return reporting_desc; |
| } |
| |
| /* Make a "reportingDescriptor" object (SARIF v2.1.0 section 3.49) |
| for CWE_ID, for use within the CWE taxa array. */ |
| |
| std::unique_ptr<sarif_reporting_descriptor> |
| sarif_builder::make_reporting_descriptor_object_for_cwe_id (int cwe_id) const |
| { |
| auto reporting_desc = std::make_unique<sarif_reporting_descriptor> (); |
| |
| /* "id" property (SARIF v2.1.0 section 3.49.3). */ |
| { |
| pretty_printer pp; |
| pp_printf (&pp, "%i", cwe_id); |
| reporting_desc->set_string ("id", pp_formatted_text (&pp)); |
| } |
| |
| /* "helpUri" property (SARIF v2.1.0 section 3.49.12). */ |
| { |
| char *url = get_cwe_url (cwe_id); |
| reporting_desc->set_string ("helpUri", url); |
| free (url); |
| } |
| |
| return reporting_desc; |
| } |
| |
| /* Make a "reportingDescriptorReference" object (SARIF v2.1.0 section 3.52) |
| referencing CWE_ID, for use within a result object. |
| Also, add CWE_ID to m_cwe_id_set. */ |
| |
| std::unique_ptr<sarif_reporting_descriptor_reference> |
| sarif_builder:: |
| make_reporting_descriptor_reference_object_for_cwe_id (int cwe_id) |
| { |
| auto desc_ref_obj = std::make_unique<sarif_reporting_descriptor_reference> (); |
| |
| /* "id" property (SARIF v2.1.0 section 3.52.4). */ |
| { |
| pretty_printer pp; |
| pp_printf (&pp, "%i", cwe_id); |
| desc_ref_obj->set_string ("id", pp_formatted_text (&pp)); |
| } |
| |
| /* "toolComponent" property (SARIF v2.1.0 section 3.52.7). */ |
| desc_ref_obj->set<sarif_tool_component_reference> |
| ("toolComponent", make_tool_component_reference_object_for_cwe ()); |
| |
| /* Add CWE_ID to our set. */ |
| gcc_assert (cwe_id > 0); |
| m_cwe_id_set.add (cwe_id); |
| |
| return desc_ref_obj; |
| } |
| |
| /* Make a "toolComponentReference" object (SARIF v2.1.0 section 3.54) that |
| references the CWE taxonomy. */ |
| |
| std::unique_ptr<sarif_tool_component_reference> |
| sarif_builder:: |
| make_tool_component_reference_object_for_cwe () const |
| { |
| auto comp_ref_obj = std::make_unique<sarif_tool_component_reference> (); |
| |
| /* "name" property (SARIF v2.1.0 section 3.54.3). */ |
| comp_ref_obj->set_string ("name", "cwe"); |
| |
| return comp_ref_obj; |
| } |
| |
| /* 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). |
| Use LOC_MGR for any locations that need "id" values. */ |
| |
| std::unique_ptr<json::array> |
| sarif_builder::make_locations_arr (sarif_location_manager &loc_mgr, |
| const diagnostic_info &diagnostic, |
| enum diagnostic_artifact_role role) |
| { |
| auto locations_arr = std::make_unique<json::array> (); |
| logical_locations::key logical_loc; |
| if (auto client_data_hooks = m_context.get_client_data_hooks ()) |
| logical_loc = client_data_hooks->get_current_logical_location (); |
| |
| auto location_obj |
| = make_location_object (&loc_mgr, *diagnostic.m_richloc, logical_loc, role); |
| /* Don't add entirely empty location objects to the array. */ |
| if (!location_obj->is_empty ()) |
| locations_arr->append<sarif_location> (std::move (location_obj)); |
| |
| return locations_arr; |
| } |
| |
| /* If LOGICAL_LOC is non-null, use it to create a "logicalLocations" property |
| within LOCATION_OBJ (SARIF v2.1.0 section 3.28.4) with a minimal logical |
| location object referencing theRuns.logicalLocations (3.33.3). */ |
| |
| void |
| sarif_builder:: |
| set_any_logical_locs_arr (sarif_location &location_obj, |
| logical_locations::key logical_loc) |
| { |
| if (!logical_loc) |
| return; |
| gcc_assert (m_logical_loc_mgr); |
| auto location_locs_arr = std::make_unique<json::array> (); |
| |
| auto logical_loc_obj = make_minimal_sarif_logical_location (logical_loc); |
| |
| location_locs_arr->append<sarif_logical_location> |
| (std::move (logical_loc_obj)); |
| |
| location_obj.set<json::array> ("logicalLocations", |
| std::move (location_locs_arr)); |
| } |
| |
| /* Make a "location" object (SARIF v2.1.0 section 3.28) for RICH_LOC |
| and LOGICAL_LOC. |
| Use LOC_MGR for any locations that need "id" values, and for |
| any worklist items. |
| Note that we might not always have a LOC_MGR; see |
| https://github.com/oasis-tcs/sarif-spec/issues/712 */ |
| |
| std::unique_ptr<sarif_location> |
| sarif_builder::make_location_object (sarif_location_manager *loc_mgr, |
| const rich_location &rich_loc, |
| logical_locations::key logical_loc, |
| enum diagnostic_artifact_role role) |
| { |
| class escape_nonascii_renderer : public content_renderer |
| { |
| public: |
| escape_nonascii_renderer (const rich_location &richloc, |
| enum diagnostics_escape_format escape_format) |
| : m_richloc (richloc), |
| m_escape_format (escape_format) |
| {} |
| |
| std::unique_ptr<sarif_multiformat_message_string> |
| render (const sarif_builder &builder) const final override |
| { |
| diagnostics::context dc; |
| diagnostic_initialize (&dc, 0); |
| auto &source_printing_opts = dc.get_source_printing_options (); |
| source_printing_opts.enabled = true; |
| source_printing_opts.colorize_source_p = false; |
| source_printing_opts.show_labels_p = true; |
| source_printing_opts.show_line_numbers_p = true; |
| |
| rich_location my_rich_loc (m_richloc); |
| my_rich_loc.set_escape_on_output (true); |
| |
| source_print_policy source_policy (dc); |
| dc.set_escape_format (m_escape_format); |
| text_sink text_output (dc); |
| source_policy.print (*text_output.get_printer (), |
| my_rich_loc, kind::error, nullptr); |
| |
| const char *buf = pp_formatted_text (text_output.get_printer ()); |
| std::unique_ptr<sarif_multiformat_message_string> result |
| = builder.make_multiformat_message_string (buf); |
| |
| diagnostic_finish (&dc); |
| |
| return result; |
| } |
| private: |
| const rich_location &m_richloc; |
| enum diagnostics_escape_format m_escape_format; |
| } the_renderer (rich_loc, |
| m_context.get_escape_format ()); |
| |
| auto location_obj = std::make_unique<sarif_location> (); |
| |
| /* Get primary loc from RICH_LOC. */ |
| location_t loc = rich_loc.get_loc (); |
| |
| /* "physicalLocation" property (SARIF v2.1.0 section 3.28.3). */ |
| const content_renderer *snippet_renderer |
| = rich_loc.escape_on_output_p () ? &the_renderer : nullptr; |
| if (auto phs_loc_obj |
| = maybe_make_physical_location_object (loc, role, |
| rich_loc.get_column_override (), |
| snippet_renderer)) |
| location_obj->set<sarif_physical_location> ("physicalLocation", |
| std::move (phs_loc_obj)); |
| |
| /* "logicalLocations" property (SARIF v2.1.0 section 3.28.4). */ |
| set_any_logical_locs_arr (*location_obj, logical_loc); |
| |
| /* Handle labelled ranges and/or secondary locations. */ |
| { |
| std::unique_ptr<json::array> annotations_arr = nullptr; |
| for (unsigned int i = 0; i < rich_loc.get_num_locations (); i++) |
| { |
| const location_range *range = rich_loc.get_range (i); |
| bool handled = false; |
| if (const range_label *label = range->m_label) |
| { |
| label_text text = label->get_text (i); |
| if (text.get ()) |
| { |
| /* Create annotations for any labelled ranges. */ |
| location_t range_loc = rich_loc.get_loc (i); |
| auto region |
| = maybe_make_region_object (range_loc, |
| rich_loc.get_column_override ()); |
| if (region) |
| { |
| if (!annotations_arr) |
| annotations_arr = std::make_unique<json::array> (); |
| region->set<sarif_message> |
| ("message", make_message_object (text.get ())); |
| annotations_arr->append<sarif_region> (std::move (region)); |
| handled = true; |
| } |
| } |
| } |
| |
| /* Add related locations for any secondary locations in RICH_LOC |
| that don't have labels (and thus aren't added to "annotations"). */ |
| if (loc_mgr && i > 0 && !handled) |
| loc_mgr->add_relationship_to_worklist |
| (*location_obj.get (), |
| sarif_location_manager::worklist_item::kind::unlabelled_secondary_location, |
| range->m_loc); |
| } |
| if (annotations_arr) |
| /* "annotations" property (SARIF v2.1.0 section 3.28.6). */ |
| location_obj->set<json::array> ("annotations", |
| std::move (annotations_arr)); |
| } |
| |
| if (loc_mgr) |
| 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 |
| to the user if we show some representation of |
| how the characters in the pertinent source lines are encoded. */ |
| if (rich_loc.escape_on_output_p ()) |
| { |
| sarif_property_bag &bag = location_obj->get_or_create_properties (); |
| bag.set_bool ("gcc/escapeNonAscii", rich_loc.escape_on_output_p ()); |
| } |
| |
| 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 diagnostics::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_location> |
| sarif_builder::make_location_object (sarif_location_manager &loc_mgr, |
| location_t loc, |
| enum diagnostic_artifact_role role) |
| { |
| auto location_obj = std::make_unique<sarif_location> (); |
| |
| /* "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<sarif_physical_location> ("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 paths::path. */ |
| |
| std::unique_ptr<sarif_location> |
| sarif_builder::make_location_object (sarif_location_manager &loc_mgr, |
| const paths::event &event, |
| enum diagnostic_artifact_role role) |
| { |
| auto location_obj = std::make_unique<sarif_location> (); |
| |
| /* "physicalLocation" property (SARIF v2.1.0 section 3.28.3). */ |
| location_t loc = event.get_location (); |
| if (auto phs_loc_obj |
| = maybe_make_physical_location_object (loc, role, 0, nullptr)) |
| location_obj->set<sarif_physical_location> ("physicalLocation", |
| std::move (phs_loc_obj)); |
| |
| /* "logicalLocations" property (SARIF v2.1.0 section 3.28.4). */ |
| logical_locations::key logical_loc = event.get_logical_location (); |
| set_any_logical_locs_arr (*location_obj, logical_loc); |
| |
| /* "message" property (SARIF v2.1.0 section 3.28.5). */ |
| std::unique_ptr<pretty_printer> pp = get_printer ()->clone (); |
| event.print_desc (*pp); |
| location_obj->set<sarif_message> |
| ("message", |
| make_message_object (pp_formatted_text (pp.get ()))); |
| |
| add_any_include_chain (loc_mgr, *location_obj.get (), loc); |
| |
| return location_obj; |
| } |
| |
| /* Make a "physicalLocation" object (SARIF v2.1.0 section 3.29) for LOC. |
| |
| If COLUMN_OVERRIDE is non-zero, then use it as the column number |
| if LOC has no column information. |
| |
| Ensure that we have an artifact object for the file, adding ROLE to it, |
| and flagging that we will attempt to embed the contents of the artifact |
| when writing it out. */ |
| |
| std::unique_ptr<sarif_physical_location> |
| sarif_builder:: |
| maybe_make_physical_location_object (location_t loc, |
| enum diagnostic_artifact_role role, |
| int column_override, |
| const content_renderer *snippet_renderer) |
| { |
| if (loc <= BUILTINS_LOCATION || LOCATION_FILE (loc) == nullptr) |
| return nullptr; |
| |
| auto phys_loc_obj = std::make_unique<sarif_physical_location> (); |
| |
| /* "artifactLocation" property (SARIF v2.1.0 section 3.29.3). */ |
| phys_loc_obj->set<sarif_artifact_location> |
| ("artifactLocation", make_artifact_location_object (loc)); |
| get_or_create_artifact (LOCATION_FILE (loc), role, true); |
| |
| /* "region" property (SARIF v2.1.0 section 3.29.4). */ |
| if (auto region_obj = maybe_make_region_object (loc, column_override)) |
| phys_loc_obj->set<sarif_region> ("region", std::move (region_obj)); |
| |
| /* "contextRegion" property (SARIF v2.1.0 section 3.29.5). */ |
| if (auto context_region_obj |
| = maybe_make_region_object_for_context (loc, snippet_renderer)) |
| phys_loc_obj->set<sarif_region> ("contextRegion", |
| std::move (context_region_obj)); |
| |
| /* Instead, we add artifacts to the run as a whole, |
| with artifact.contents. |
| Could do both, though. */ |
| |
| return phys_loc_obj; |
| } |
| |
| /* Make an "artifactLocation" object (SARIF v2.1.0 section 3.4) for LOC, |
| or return nullptr. */ |
| |
| std::unique_ptr<sarif_artifact_location> |
| sarif_builder::make_artifact_location_object (location_t loc) |
| { |
| return make_artifact_location_object (LOCATION_FILE (loc)); |
| } |
| |
| /* The ID value for use in "uriBaseId" properties (SARIF v2.1.0 section 3.4.4) |
| for when we need to express paths relative to PWD. */ |
| |
| #define PWD_PROPERTY_NAME ("PWD") |
| |
| /* Make an "artifactLocation" object (SARIF v2.1.0 section 3.4) for FILENAME, |
| or return nullptr. */ |
| |
| std::unique_ptr<sarif_artifact_location> |
| sarif_builder::make_artifact_location_object (const char *filename) |
| { |
| auto artifact_loc_obj = std::make_unique<sarif_artifact_location> (); |
| |
| /* "uri" property (SARIF v2.1.0 section 3.4.3). */ |
| artifact_loc_obj->set_string ("uri", filename); |
| |
| if (filename[0] != '/') |
| { |
| /* If we have a relative path, set the "uriBaseId" property |
| (SARIF v2.1.0 section 3.4.4). */ |
| artifact_loc_obj->set_string ("uriBaseId", PWD_PROPERTY_NAME); |
| m_seen_any_relative_paths = true; |
| } |
| |
| return artifact_loc_obj; |
| } |
| |
| /* Get the PWD, or nullptr, as an absolute file-based URI, |
| adding a trailing forward slash (as required by SARIF v2.1.0 |
| section 3.14.14). */ |
| |
| static char * |
| make_pwd_uri_str () |
| { |
| /* The prefix of a file-based URI, up to, but not including the path. */ |
| #define FILE_PREFIX ("file://") |
| |
| const char *pwd = getpwd (); |
| if (!pwd) |
| return nullptr; |
| size_t len = strlen (pwd); |
| if (len == 0 || pwd[len - 1] != '/') |
| return concat (FILE_PREFIX, pwd, "/", nullptr); |
| else |
| { |
| gcc_assert (pwd[len - 1] == '/'); |
| return concat (FILE_PREFIX, pwd, nullptr); |
| } |
| } |
| |
| /* Make an "artifactLocation" object (SARIF v2.1.0 section 3.4) for the pwd, |
| for use in the "run.originalUriBaseIds" property (SARIF v2.1.0 |
| section 3.14.14) when we have any relative paths. */ |
| |
| std::unique_ptr<sarif_artifact_location> |
| sarif_builder::make_artifact_location_object_for_pwd () const |
| { |
| auto artifact_loc_obj = std::make_unique<sarif_artifact_location> (); |
| |
| /* "uri" property (SARIF v2.1.0 section 3.4.3). */ |
| if (char *pwd = make_pwd_uri_str ()) |
| { |
| gcc_assert (strlen (pwd) > 0); |
| gcc_assert (pwd[strlen (pwd) - 1] == '/'); |
| artifact_loc_obj->set_string ("uri", pwd); |
| free (pwd); |
| } |
| |
| return artifact_loc_obj; |
| } |
| |
| /* Get the column number within EXPLOC. */ |
| |
| int |
| sarif_builder::get_sarif_column (expanded_location exploc) const |
| { |
| cpp_char_column_policy policy (m_tabstop, cpp_wcwidth); |
| return location_compute_display_column (m_context.get_file_cache (), |
| exploc, policy); |
| } |
| |
| /* Make a "region" object (SARIF v2.1.0 section 3.30) for LOC, |
| or return nullptr. |
| |
| If COLUMN_OVERRIDE is non-zero, then use it as the column number |
| if LOC has no column information. |
| |
| We only support text properties of regions ("text regions"), |
| not binary properties ("binary regions"); see 3.30.1. */ |
| |
| std::unique_ptr<sarif_region> |
| sarif_builder::maybe_make_region_object (location_t loc, |
| int column_override) const |
| { |
| location_t caret_loc = get_pure_location (loc); |
| |
| if (caret_loc <= BUILTINS_LOCATION) |
| return nullptr; |
| |
| location_t start_loc = get_start (loc); |
| location_t finish_loc = get_finish (loc); |
| |
| expanded_location exploc_caret = expand_location (caret_loc); |
| expanded_location exploc_start = expand_location (start_loc); |
| expanded_location exploc_finish = expand_location (finish_loc); |
| |
| if (exploc_start.file !=exploc_caret.file) |
| return nullptr; |
| if (exploc_finish.file !=exploc_caret.file) |
| return nullptr; |
| |
| /* We can have line == 0 in the presence of "#" lines. |
| SARIF requires lines > 0, so if we hit this case we don't have a |
| way of validly representing the region as SARIF; bail out. */ |
| if (exploc_start.line <= 0) |
| return nullptr; |
| |
| auto region_obj = std::make_unique<sarif_region> (); |
| |
| /* "startLine" property (SARIF v2.1.0 section 3.30.5) */ |
| region_obj->set_integer ("startLine", exploc_start.line); |
| |
| /* "startColumn" property (SARIF v2.1.0 section 3.30.6). |
| |
| We use column == 0 to mean the whole line, so omit the column |
| information for this case, unless COLUMN_OVERRIDE is non-zero, |
| (for handling certain awkward lexer diagnostics) */ |
| |
| if (exploc_start.column == 0 && column_override) |
| /* Use the provided column number. */ |
| exploc_start.column = column_override; |
| |
| if (exploc_start.column > 0) |
| { |
| int start_column = get_sarif_column (exploc_start); |
| region_obj->set_integer ("startColumn", start_column); |
| } |
| |
| /* "endLine" property (SARIF v2.1.0 section 3.30.7) */ |
| if (exploc_finish.line != exploc_start.line |
| && exploc_finish.line > 0) |
| region_obj->set_integer ("endLine", exploc_finish.line); |
| |
| /* "endColumn" property (SARIF v2.1.0 section 3.30.8). |
| This expresses the column immediately beyond the range. |
| |
| We use column == 0 to mean the whole line, so omit the column |
| information for this case. */ |
| if (exploc_finish.column > 0) |
| { |
| int next_column = get_sarif_column (exploc_finish) + 1; |
| region_obj->set_integer ("endColumn", next_column); |
| } |
| |
| return region_obj; |
| } |
| |
| /* Make a "region" object (SARIF v2.1.0 section 3.30) for the "contextRegion" |
| property (SARIF v2.1.0 section 3.29.5) of a "physicalLocation". |
| |
| This is similar to maybe_make_region_object, but ignores column numbers, |
| covering the line(s) as a whole, and including a "snippet" property |
| embedding those source lines, making it easier for consumers to show |
| the pertinent source. */ |
| |
| std::unique_ptr<sarif_region> |
| sarif_builder:: |
| maybe_make_region_object_for_context (location_t loc, |
| const content_renderer *snippet_renderer) |
| const |
| { |
| location_t caret_loc = get_pure_location (loc); |
| |
| if (caret_loc <= BUILTINS_LOCATION) |
| return nullptr; |
| |
| location_t start_loc = get_start (loc); |
| location_t finish_loc = get_finish (loc); |
| |
| expanded_location exploc_caret = expand_location (caret_loc); |
| expanded_location exploc_start = expand_location (start_loc); |
| expanded_location exploc_finish = expand_location (finish_loc); |
| |
| if (exploc_start.file !=exploc_caret.file) |
| return nullptr; |
| if (exploc_finish.file !=exploc_caret.file) |
| return nullptr; |
| |
| /* We can have line == 0 in the presence of "#" lines. |
| SARIF requires lines > 0, so if we hit this case we don't have a |
| way of validly representing the region as SARIF; bail out. */ |
| if (exploc_start.line <= 0) |
| return nullptr; |
| |
| auto region_obj = std::make_unique<sarif_region> (); |
| |
| /* "startLine" property (SARIF v2.1.0 section 3.30.5) */ |
| region_obj->set_integer ("startLine", exploc_start.line); |
| |
| /* "endLine" property (SARIF v2.1.0 section 3.30.7) */ |
| if (exploc_finish.line != exploc_start.line |
| && exploc_finish.line > 0) |
| region_obj->set_integer ("endLine", exploc_finish.line); |
| |
| /* "snippet" property (SARIF v2.1.0 section 3.30.13). */ |
| if (auto artifact_content_obj |
| = maybe_make_artifact_content_object (exploc_start.file, |
| exploc_start.line, |
| exploc_finish.line, |
| snippet_renderer)) |
| region_obj->set<sarif_artifact_content> ("snippet", |
| std::move (artifact_content_obj)); |
| |
| return region_obj; |
| } |
| |
| /* Make a "region" object (SARIF v2.1.0 section 3.30) for the deletion region |
| of HINT (as per SARIF v2.1.0 section 3.57.3). */ |
| |
| std::unique_ptr<sarif_region> |
| sarif_builder::make_region_object_for_hint (const fixit_hint &hint) const |
| { |
| location_t start_loc = hint.get_start_loc (); |
| location_t next_loc = hint.get_next_loc (); |
| |
| expanded_location exploc_start = expand_location (start_loc); |
| expanded_location exploc_next = expand_location (next_loc); |
| |
| auto region_obj = std::make_unique<sarif_region> (); |
| |
| /* "startLine" property (SARIF v2.1.0 section 3.30.5) */ |
| region_obj->set_integer ("startLine", exploc_start.line); |
| |
| /* "startColumn" property (SARIF v2.1.0 section 3.30.6) */ |
| int start_col = get_sarif_column (exploc_start); |
| region_obj->set_integer ("startColumn", start_col); |
| |
| /* "endLine" property (SARIF v2.1.0 section 3.30.7) */ |
| if (exploc_next.line != exploc_start.line) |
| region_obj->set_integer ("endLine", exploc_next.line); |
| |
| /* "endColumn" property (SARIF v2.1.0 section 3.30.8). |
| This expresses the column immediately beyond the range. */ |
| int next_col = get_sarif_column (exploc_next); |
| region_obj->set_integer ("endColumn", next_col); |
| |
| return region_obj; |
| } |
| |
| /* Attempt to get a string for a logicalLocation's "kind" property |
| (SARIF v2.1.0 section 3.33.7). |
| Return nullptr if unknown. */ |
| |
| static const char * |
| maybe_get_sarif_kind (enum logical_locations::kind kind) |
| { |
| using namespace logical_locations; |
| |
| switch (kind) |
| { |
| default: |
| gcc_unreachable (); |
| case logical_locations::kind::unknown: |
| return nullptr; |
| |
| /* Kinds within executable code. */ |
| case logical_locations::kind::function: |
| return "function"; |
| case logical_locations::kind::member: |
| return "member"; |
| case logical_locations::kind::module_: |
| return "module"; |
| case logical_locations::kind::namespace_: |
| return "namespace"; |
| case logical_locations::kind::type: |
| return "type"; |
| case logical_locations::kind::return_type: |
| return "returnType"; |
| case logical_locations::kind::parameter: |
| return "parameter"; |
| case logical_locations::kind::variable: |
| return "variable"; |
| |
| /* Kinds within XML or HTML documents. */ |
| case logical_locations::kind::element: |
| return "element"; |
| case logical_locations::kind::attribute: |
| return "attribute"; |
| case logical_locations::kind::text: |
| return "text"; |
| case logical_locations::kind::comment: |
| return "comment"; |
| case logical_locations::kind::processing_instruction: |
| return "processingInstruction"; |
| case logical_locations::kind::dtd: |
| return "dtd"; |
| case logical_locations::kind::declaration: |
| return "declaration"; |
| |
| /* Kinds within JSON documents. */ |
| case logical_locations::kind::object: |
| return "object"; |
| case logical_locations::kind::array: |
| return "array"; |
| case logical_locations::kind::property: |
| return "property"; |
| case logical_locations::kind::value: |
| return "value"; |
| } |
| } |
| |
| /* Set PROPERTY_NAME within this bag to a "logicalLocation" object (SARIF v2.1.0 |
| section 3.33) for LOGICAL_LOC. The object has an "index" property to refer to |
| theRuns.logicalLocations (3.33.3). */ |
| |
| void |
| sarif_property_bag::set_logical_location (const char *property_name, |
| sarif_builder &builder, |
| logical_locations::key logical_loc) |
| { |
| set<sarif_logical_location> |
| (property_name, |
| builder.make_minimal_sarif_logical_location (logical_loc)); |
| } |
| |
| static void |
| copy_any_property_bag (const digraphs::object &input_obj, |
| sarif_object &output_obj) |
| { |
| if (input_obj.get_property_bag ()) |
| { |
| const json::object &old_bag = *input_obj.get_property_bag (); |
| sarif_property_bag &new_bag = output_obj.get_or_create_properties (); |
| for (size_t i = 0; i < old_bag.get_num_keys (); ++i) |
| { |
| const char *key = old_bag.get_key (i); |
| json::value *val = old_bag.get (key); |
| new_bag.set (key, val->clone ()); |
| } |
| } |
| } |
| |
| std::unique_ptr<sarif_graph> |
| make_sarif_graph (const digraphs::digraph &g, |
| sarif_builder *builder, |
| sarif_location_manager *sarif_location_mgr) |
| { |
| auto result = std::make_unique<sarif_graph> (); |
| |
| // 3.39.2 description property |
| if (const char *desc = g.get_description ()) |
| if (builder) |
| result->set<sarif_message> ("description", |
| builder->make_message_object (desc)); |
| |
| copy_any_property_bag (g, *result); |
| |
| // 3.39.3 nodes property |
| auto nodes_arr = std::make_unique<json::array> (); |
| const int num_nodes = g.get_num_nodes (); |
| for (int i = 0; i < num_nodes; ++i) |
| nodes_arr->append (make_sarif_node (g.get_node (i), |
| builder, |
| sarif_location_mgr)); |
| result->set ("nodes", std::move (nodes_arr)); |
| |
| // 3.39.4 edges property |
| auto edges_arr = std::make_unique<json::array> (); |
| const int num_edges = g.get_num_edges (); |
| for (int i = 0; i < num_edges; ++i) |
| edges_arr->append (make_sarif_edge (g.get_edge (i), builder)); |
| result->set ("edges", std::move (edges_arr)); |
| |
| return result; |
| } |
| |
| std::unique_ptr<sarif_node> |
| make_sarif_node (const digraphs::node &n, |
| sarif_builder *builder, |
| sarif_location_manager *sarif_location_mgr) |
| { |
| auto result = std::make_unique<sarif_node> (); |
| |
| // 3.40.2 id property |
| result->set_string ("id", n.get_id ().c_str ()); |
| |
| copy_any_property_bag (n, *result); |
| |
| // 3.40.3 label property |
| if (const char *label = n.get_label ()) |
| if (builder) |
| result->set<sarif_message> ("label", |
| builder->make_message_object (label)); |
| |
| // 3.40.4 location property |
| if (n.get_logical_loc () |
| || n.get_physical_loc () != UNKNOWN_LOCATION) |
| if (builder) |
| { |
| rich_location rich_loc |
| (line_table, n.get_physical_loc ()); |
| auto loc_obj |
| = builder->make_location_object |
| (sarif_location_mgr, |
| rich_loc, |
| n.get_logical_loc (), |
| diagnostic_artifact_role::scanned_file); |
| result->set<sarif_location> ("location", |
| std::move (loc_obj)); |
| } |
| |
| // 3.40.5 children property |
| if (const int num_children = n.get_num_children ()) |
| { |
| auto children_arr = std::make_unique<json::array> (); |
| for (int i = 0; i < num_children; ++i) |
| children_arr->append (make_sarif_node (n.get_child (i), |
| builder, |
| sarif_location_mgr)); |
| result->set ("children", std::move (children_arr)); |
| } |
| |
| return result; |
| } |
| |
| std::unique_ptr<sarif_edge> |
| make_sarif_edge (const digraphs::edge &e, |
| sarif_builder *builder) |
| { |
| auto result = std::make_unique<sarif_edge> (); |
| |
| // 3.41.2 id property |
| result->set_string ("id", e.get_id ().c_str ()); |
| |
| copy_any_property_bag (e, *result); |
| |
| // 3.41.3 label property |
| if (const char *label = e.get_label ()) |
| if (builder) |
| result->set<sarif_message> ("label", |
| builder->make_message_object (label)); |
| |
| // 3.41.4 sourceNodeId property |
| result->set_string ("sourceNodeId", e.get_src_node ().get_id ().c_str ()); |
| |
| // 3.41.5 targetNodeId property |
| result->set_string ("targetNodeId", e.get_dst_node ().get_id ().c_str ()); |
| |
| return result; |
| } |
| |
| void |
| sarif_property_bag::set_graph (const char *property_name, |
| sarif_builder &builder, |
| sarif_location_manager *sarif_location_mgr, |
| const digraphs::digraph &g) |
| { |
| set<sarif_graph> (property_name, |
| make_sarif_graph (g, &builder, sarif_location_mgr)); |
| } |
| |
| /* Ensure that m_cached_logical_locs has a "logicalLocation" object |
| (SARIF v2.1.0 section 3.33) for K, and return its index within the |
| array. */ |
| |
| int |
| sarif_builder:: |
| ensure_sarif_logical_location_for (logical_locations::key k) |
| { |
| gcc_assert (m_logical_loc_mgr); |
| |
| auto sarif_logical_loc = std::make_unique<sarif_logical_location> (); |
| |
| if (const char *short_name = m_logical_loc_mgr->get_short_name (k)) |
| sarif_logical_loc->set_string ("name", short_name); |
| |
| /* "fullyQualifiedName" property (SARIF v2.1.0 section 3.33.5). */ |
| if (const char *name_with_scope = m_logical_loc_mgr->get_name_with_scope (k)) |
| sarif_logical_loc->set_string ("fullyQualifiedName", name_with_scope); |
| |
| /* "decoratedName" property (SARIF v2.1.0 section 3.33.6). */ |
| if (const char *internal_name = m_logical_loc_mgr->get_internal_name (k)) |
| sarif_logical_loc->set_string ("decoratedName", internal_name); |
| |
| /* "kind" property (SARIF v2.1.0 section 3.33.7). */ |
| enum logical_locations::kind kind = m_logical_loc_mgr->get_kind (k); |
| if (const char *sarif_kind_str = maybe_get_sarif_kind (kind)) |
| sarif_logical_loc->set_string ("kind", sarif_kind_str); |
| |
| /* "parentIndex" property (SARIF v2.1.0 section 3.33.8). */ |
| if (auto parent_key = m_logical_loc_mgr->get_parent (k)) |
| { |
| /* Recurse upwards. */ |
| int parent_index = ensure_sarif_logical_location_for (parent_key); |
| sarif_logical_loc->set_integer ("parentIndex", parent_index); |
| } |
| |
| /* Consolidate if this logical location already exists. */ |
| int index |
| = m_cached_logical_locs->append_uniquely (std::move (sarif_logical_loc)); |
| |
| return index; |
| } |
| |
| /* Ensure that theRuns.logicalLocations (3.14.17) has a "logicalLocation" object |
| (SARIF v2.1.0 section 3.33) for LOGICAL_LOC. |
| Create and return a minimal logicalLocation object referring to the |
| full object by index. */ |
| |
| std::unique_ptr<sarif_logical_location> |
| sarif_builder:: |
| make_minimal_sarif_logical_location (logical_locations::key logical_loc) |
| { |
| gcc_assert (m_logical_loc_mgr); |
| |
| /* Ensure that m_cached_logical_locs has a "logicalLocation" object |
| (SARIF v2.1.0 section 3.33) for LOGICAL_LOC, and return its index within |
| the array. */ |
| |
| auto sarif_logical_loc = std::make_unique <sarif_logical_location> (); |
| |
| int index = ensure_sarif_logical_location_for (logical_loc); |
| |
| // 3.33.3 index property |
| sarif_logical_loc->set_integer ("index", index); |
| |
| /* "fullyQualifiedName" property (SARIF v2.1.0 section 3.33.5). */ |
| if (const char *name_with_scope |
| = m_logical_loc_mgr->get_name_with_scope (logical_loc)) |
| sarif_logical_loc->set_string ("fullyQualifiedName", name_with_scope); |
| |
| return sarif_logical_loc; |
| } |
| |
| label_text |
| make_sarif_url_for_event (const sarif_code_flow *code_flow, |
| paths::event_id_t event_id) |
| { |
| gcc_assert (event_id.known_p ()); |
| |
| if (!code_flow) |
| return label_text (); |
| |
| const sarif_thread_flow_location &tfl_obj |
| = code_flow->get_thread_flow_loc_obj (event_id); |
| const int location_idx = tfl_obj.get_index_within_parent (); |
| |
| const sarif_thread_flow &thread_flow_obj = tfl_obj.get_parent (); |
| const int thread_flow_idx = thread_flow_obj.get_index_within_parent (); |
| |
| const sarif_code_flow &code_flow_obj = thread_flow_obj.get_parent (); |
| const int code_flow_idx = code_flow_obj.get_index_within_parent (); |
| |
| const sarif_result &result_obj = code_flow_obj.get_parent (); |
| const int result_idx = result_obj.get_index_within_parent (); |
| |
| /* We only support a single run object in the log. */ |
| const int run_idx = 0; |
| |
| char *buf = xasprintf |
| ("sarif:/runs/%i/results/%i/codeFlows/%i/threadFlows/%i/locations/%i", |
| run_idx, result_idx, code_flow_idx, thread_flow_idx, location_idx); |
| return label_text::take (buf); |
| } |
| |
| /* Make a "codeFlow" object (SARIF v2.1.0 section 3.36) for PATH. */ |
| |
| std::unique_ptr<sarif_code_flow> |
| sarif_builder::make_code_flow_object (sarif_result &result, |
| unsigned idx_within_parent, |
| const paths::path &path) |
| { |
| auto code_flow_obj |
| = std::make_unique <sarif_code_flow> (result, idx_within_parent); |
| |
| /* First pass: |
| Create threadFlows and threadFlowLocation objects within them, |
| effectively recording a mapping from event_id to threadFlowLocation |
| so that we can later go from an event_id to a URI within the |
| SARIF file. */ |
| for (unsigned i = 0; i < path.num_events (); i++) |
| { |
| const paths::event &event = path.get_event (i); |
| const paths::thread_id_t thread_id = event.get_thread_id (); |
| |
| sarif_thread_flow &thread_flow_obj |
| = code_flow_obj->get_or_append_thread_flow (path.get_thread (thread_id), |
| thread_id); |
| thread_flow_obj.add_location (); |
| } |
| |
| /* Second pass: walk the events, populating the tfl objs. */ |
| m_current_code_flow = code_flow_obj.get (); |
| for (unsigned i = 0; i < path.num_events (); i++) |
| { |
| const paths::event &event = path.get_event (i); |
| sarif_thread_flow_location &thread_flow_loc_obj |
| = code_flow_obj->get_thread_flow_loc_obj (i); |
| populate_thread_flow_location_object (result, |
| thread_flow_loc_obj, |
| event, |
| i); |
| } |
| m_current_code_flow = nullptr; |
| |
| return code_flow_obj; |
| } |
| |
| /* Populate TFL_OBJ, a "threadFlowLocation" object (SARIF v2.1.0 section 3.38) |
| based on EVENT. */ |
| |
| void |
| sarif_builder:: |
| populate_thread_flow_location_object (sarif_result &result, |
| sarif_thread_flow_location &tfl_obj, |
| const paths::event &ev, |
| int event_execution_idx) |
| { |
| /* Give paths::event subclasses a chance to add custom properties |
| via a property bag. */ |
| ev.maybe_add_sarif_properties (*this, tfl_obj); |
| |
| if (get_opts ().m_state_graph) |
| if (auto state_graph = ev.maybe_make_diagnostic_state_graph (true)) |
| { |
| sarif_property_bag &props = tfl_obj.get_or_create_properties (); |
| |
| #define PROPERTY_PREFIX "gcc/diagnostics/paths/event/" |
| props.set_graph (PROPERTY_PREFIX "state_graph", |
| *this, |
| /* Use RESULT for any related locations in the graph's |
| nodes. |
| It's not clear if this is correct; see: |
| https://github.com/oasis-tcs/sarif-spec/issues/712 |
| */ |
| &result, |
| *state_graph); |
| #undef PROPERTY_PREFIX |
| } |
| |
| /* "location" property (SARIF v2.1.0 section 3.38.3). */ |
| tfl_obj.set<sarif_location> |
| ("location", |
| make_location_object (result, ev, diagnostic_artifact_role::traced_file)); |
| |
| /* "kinds" property (SARIF v2.1.0 section 3.38.8). */ |
| paths::event::meaning m = ev.get_meaning (); |
| if (auto kinds_arr = maybe_make_kinds_array (m)) |
| tfl_obj.set<json::array> ("kinds", std::move (kinds_arr)); |
| |
| /* "nestingLevel" property (SARIF v2.1.0 section 3.38.10). */ |
| tfl_obj.set_integer ("nestingLevel", ev.get_stack_depth ()); |
| |
| /* "executionOrder" property (SARIF v2.1.0 3.38.11). |
| Offset by 1 to match the human-readable values emitted by %@. */ |
| tfl_obj.set_integer ("executionOrder", event_execution_idx + 1); |
| |
| /* It might be nice to eventually implement the following for -fanalyzer: |
| - the "stack" property (SARIF v2.1.0 section 3.38.5) |
| - the "state" property (SARIF v2.1.0 section 3.38.9) |
| - the "importance" property (SARIF v2.1.0 section 3.38.13). */ |
| } |
| |
| /* If M has any known meaning, make a json array suitable for the "kinds" |
| property of a "threadFlowLocation" object (SARIF v2.1.0 section 3.38.8). |
| |
| Otherwise, return nullptr. */ |
| |
| std::unique_ptr<json::array> |
| sarif_builder:: |
| maybe_make_kinds_array (paths::event::meaning m) const |
| { |
| using namespace paths; |
| |
| if (m.m_verb == event::verb::unknown |
| && m.m_noun == event::noun::unknown |
| && m.m_property == event::property::unknown) |
| return nullptr; |
| |
| auto kinds_arr = std::make_unique<json::array> (); |
| if (const char *verb_str |
| = event::meaning::maybe_get_verb_str (m.m_verb)) |
| kinds_arr->append_string (verb_str); |
| if (const char *noun_str |
| = event::meaning::maybe_get_noun_str (m.m_noun)) |
| kinds_arr->append_string (noun_str); |
| if (const char *property_str |
| = event::meaning::maybe_get_property_str (m.m_property)) |
| kinds_arr->append_string (property_str); |
| return kinds_arr; |
| } |
| |
| /* In "3.11.5 Messages with placeholders": |
| "Within both plain text and formatted message strings, the characters |
| "{" and "}" SHALL be represented by the character sequences |
| "{{" and "}}" respectively." */ |
| |
| static std::string |
| escape_braces (const char *text) |
| { |
| std::string result; |
| while (char ch = *text++) |
| switch (ch) |
| { |
| case '{': |
| case '}': |
| result += ch; |
| /* Fall through. */ |
| default: |
| result += ch; |
| break; |
| } |
| return result; |
| } |
| |
| static void |
| set_string_property_escaping_braces (json::object &obj, |
| const char *property_name, |
| const char *value) |
| { |
| std::string escaped (escape_braces (value)); |
| obj.set_string (property_name, escaped.c_str ()); |
| } |
| |
| /* Make a "message" object (SARIF v2.1.0 section 3.11) for MSG. */ |
| |
| std::unique_ptr<sarif_message> |
| sarif_builder::make_message_object (const char *msg) const |
| { |
| auto message_obj = std::make_unique<sarif_message> (); |
| |
| /* "text" property (SARIF v2.1.0 section 3.11.8). */ |
| set_string_property_escaping_braces (*message_obj, |
| "text", msg); |
| |
| return message_obj; |
| } |
| |
| /* Make a "message" object (SARIF v2.1.0 section 3.11) for D. |
| We emit the diagram as a code block within the Markdown part |
| of the message. */ |
| |
| std::unique_ptr<sarif_message> |
| sarif_builder::make_message_object_for_diagram (const diagram &d) |
| { |
| auto message_obj = std::make_unique<sarif_message> (); |
| |
| /* "text" property (SARIF v2.1.0 section 3.11.8). */ |
| set_string_property_escaping_braces (*message_obj, |
| "text", d.get_alt_text ()); |
| |
| pretty_printer *const pp = m_printer; |
| char *saved_prefix = pp_take_prefix (pp); |
| pp_set_prefix (pp, nullptr); |
| |
| /* "To produce a code block in Markdown, simply indent every line of |
| the block by at least 4 spaces or 1 tab." |
| Here we use 4 spaces. */ |
| d.get_canvas ().print_to_pp (pp, " "); |
| pp_set_prefix (pp, saved_prefix); |
| |
| /* "markdown" property (SARIF v2.1.0 section 3.11.9). */ |
| set_string_property_escaping_braces (*message_obj, |
| "markdown", pp_formatted_text (pp)); |
| |
| pp_clear_output_area (pp); |
| |
| return message_obj; |
| } |
| |
| /* Make a "multiformatMessageString object" (SARIF v2.1.0 section 3.12) |
| for MSG. */ |
| |
| std::unique_ptr<sarif_multiformat_message_string> |
| sarif_builder::make_multiformat_message_string (const char *msg) const |
| { |
| auto message_obj = std::make_unique<sarif_multiformat_message_string> (); |
| |
| /* "text" property (SARIF v2.1.0 section 3.12.3). */ |
| set_string_property_escaping_braces (*message_obj, |
| "text", msg); |
| |
| return message_obj; |
| } |
| |
| /* Convert VERSION to a value for the "$schema" property |
| of a "sarifLog" object (SARIF v2.1.0 section 3.13.3). */ |
| |
| static const char * |
| sarif_version_to_url (enum sarif_version version) |
| { |
| switch (version) |
| { |
| default: |
| gcc_unreachable (); |
| case sarif_version::v2_1_0: |
| return "https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json"; |
| case sarif_version::v2_2_prerelease_2024_08_08: |
| return "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/refs/tags/2.2-prerelease-2024-08-08/sarif-2.2/schema/sarif-2-2.schema.json"; |
| } |
| } |
| |
| /* Convert VERSION to a value for the "version" property |
| of a "sarifLog" object (SARIF v2.1.0 section 3.13.2). */ |
| |
| static const char * |
| sarif_version_to_property (enum sarif_version version) |
| { |
| switch (version) |
| { |
| default: |
| gcc_unreachable (); |
| case sarif_version::v2_1_0: |
| return "2.1.0"; |
| case sarif_version::v2_2_prerelease_2024_08_08: |
| /* I would have used "2.2-prerelease-2024-08-08", |
| but the schema only accepts "2.2". */ |
| return "2.2"; |
| } |
| } |
| |
| /* Make a top-level "sarifLog" object (SARIF v2.1.0 section 3.13). */ |
| |
| std::unique_ptr<sarif_log> |
| sarif_builder:: |
| make_top_level_object (std::unique_ptr<sarif_invocation> invocation_obj, |
| std::unique_ptr<json::array> results) |
| { |
| auto log_obj = std::make_unique<sarif_log> (); |
| |
| /* "$schema" property (SARIF v2.1.0 section 3.13.3) . */ |
| log_obj->set_string ("$schema", sarif_version_to_url (get_version ())); |
| |
| /* "version" property (SARIF v2.1.0 section 3.13.2). */ |
| log_obj->set_string ("version", sarif_version_to_property (get_version ())); |
| |
| /* "runs" property (SARIF v2.1.0 section 3.13.4). */ |
| auto run_arr = std::make_unique<json::array> (); |
| auto run_obj = make_run_object (std::move (invocation_obj), |
| std::move (results)); |
| run_arr->append<sarif_run> (std::move (run_obj)); |
| log_obj->set<json::array> ("runs", std::move (run_arr)); |
| |
| return log_obj; |
| } |
| |
| /* Make a "run" object (SARIF v2.1.0 section 3.14). */ |
| |
| std::unique_ptr<sarif_run> |
| sarif_builder:: |
| make_run_object (std::unique_ptr<sarif_invocation> invocation_obj, |
| std::unique_ptr<json::array> results) |
| { |
| auto run_obj = std::make_unique<sarif_run> (); |
| |
| /* "tool" property (SARIF v2.1.0 section 3.14.6). */ |
| run_obj->set<sarif_tool> ("tool", make_tool_object ()); |
| |
| /* "taxonomies" property (SARIF v2.1.0 section 3.14.8). */ |
| if (auto taxonomies_arr = maybe_make_taxonomies_array ()) |
| run_obj->set<json::array> ("taxonomies", std::move (taxonomies_arr)); |
| |
| /* "invocations" property (SARIF v2.1.0 section 3.14.11). */ |
| { |
| auto invocations_arr = std::make_unique<json::array> (); |
| invocations_arr->append (std::move (invocation_obj)); |
| run_obj->set<json::array> ("invocations", std::move (invocations_arr)); |
| } |
| |
| /* "originalUriBaseIds (SARIF v2.1.0 section 3.14.14). */ |
| if (m_seen_any_relative_paths) |
| { |
| auto orig_uri_base_ids = std::make_unique<json::object> (); |
| orig_uri_base_ids->set<sarif_artifact_location> |
| (PWD_PROPERTY_NAME, make_artifact_location_object_for_pwd ()); |
| run_obj->set<json::object> ("originalUriBaseIds", |
| std::move (orig_uri_base_ids)); |
| } |
| |
| /* "artifacts" property (SARIF v2.1.0 section 3.14.15). */ |
| auto artifacts_arr = std::make_unique<json::array> (); |
| for (auto iter : m_filename_to_artifact_map) |
| { |
| sarif_artifact *artifact_obj = iter.second; |
| if (artifact_obj->embed_contents_p ()) |
| artifact_obj->populate_contents (*this); |
| artifact_obj->populate_roles (); |
| artifacts_arr->append (artifact_obj); |
| } |
| run_obj->set<json::array> ("artifacts", std::move (artifacts_arr)); |
| m_filename_to_artifact_map.empty (); |
| |
| /* "results" property (SARIF v2.1.0 section 3.14.23). */ |
| run_obj->set<json::array> ("results", std::move (results)); |
| |
| /* "logicalLocations" property (SARIF v2.1.0 3.14.17). */ |
| if (m_cached_logical_locs->size () > 0) |
| { |
| m_cached_logical_locs->add_explicit_index_values (); |
| run_obj->set<json::array> ("logicalLocations", |
| std::move (m_cached_logical_locs)); |
| } |
| |
| // "graphs" property (SARIF v2.1.0 3.14.20) |
| if (m_run_graphs->size () > 0) |
| run_obj->set<json::array> ("graphs", |
| std::move (m_run_graphs)); |
| |
| return run_obj; |
| } |
| |
| /* Make a "tool" object (SARIF v2.1.0 section 3.18). */ |
| |
| std::unique_ptr<sarif_tool> |
| sarif_builder::make_tool_object () |
| { |
| auto tool_obj = std::make_unique<sarif_tool> (); |
| |
| /* "driver" property (SARIF v2.1.0 section 3.18.2). */ |
| tool_obj->set<sarif_tool_component> ("driver", |
| make_driver_tool_component_object ()); |
| |
| /* Report plugins via the "extensions" property |
| (SARIF v2.1.0 section 3.18.3). */ |
| if (auto client_data_hooks = m_context.get_client_data_hooks ()) |
| if (const client_version_info *vinfo |
| = client_data_hooks->get_any_version_info ()) |
| { |
| class my_plugin_visitor : public client_version_info :: plugin_visitor |
| { |
| public: |
| void |
| on_plugin (const client_plugin_info &p) final override |
| { |
| /* Create a "toolComponent" object (SARIF v2.1.0 section 3.19) |
| for the plugin. */ |
| auto plugin_obj = std::make_unique<sarif_tool_component> (); |
| |
| /* "name" property (SARIF v2.1.0 section 3.19.8). */ |
| if (const char *short_name = p.get_short_name ()) |
| plugin_obj->set_string ("name", short_name); |
| |
| /* "fullName" property (SARIF v2.1.0 section 3.19.9). */ |
| if (const char *full_name = p.get_full_name ()) |
| plugin_obj->set_string ("fullName", full_name); |
| |
| /* "version" property (SARIF v2.1.0 section 3.19.13). */ |
| if (const char *version = p.get_version ()) |
| plugin_obj->set_string ("version", version); |
| |
| m_plugin_objs.push_back (std::move (plugin_obj)); |
| } |
| std::vector<std::unique_ptr<sarif_tool_component>> m_plugin_objs; |
| }; |
| my_plugin_visitor v; |
| vinfo->for_each_plugin (v); |
| if (v.m_plugin_objs.size () > 0) |
| { |
| auto extensions_arr = std::make_unique<json::array> (); |
| for (auto &iter : v.m_plugin_objs) |
| extensions_arr->append<sarif_tool_component> (std::move (iter)); |
| tool_obj->set<json::array> ("extensions", |
| std::move (extensions_arr)); |
| } |
| } |
| |
| /* Perhaps we could also show GMP, MPFR, MPC, isl versions as other |
| "extensions" (see toplev.cc: print_version). */ |
| |
| return tool_obj; |
| } |
| |
| /* Make a "toolComponent" object (SARIF v2.1.0 section 3.19) for what SARIF |
| calls the "driver" (see SARIF v2.1.0 section 3.18.1). */ |
| |
| std::unique_ptr<sarif_tool_component> |
| sarif_builder::make_driver_tool_component_object () |
| { |
| auto driver_obj = std::make_unique<sarif_tool_component> (); |
| |
| if (auto client_data_hooks = m_context.get_client_data_hooks ()) |
| if (const client_version_info *vinfo |
| = client_data_hooks->get_any_version_info ()) |
| { |
| /* "name" property (SARIF v2.1.0 section 3.19.8). */ |
| if (const char *name = vinfo->get_tool_name ()) |
| driver_obj->set_string ("name", name); |
| |
| /* "fullName" property (SARIF v2.1.0 section 3.19.9). */ |
| if (char *full_name = vinfo->maybe_make_full_name ()) |
| { |
| driver_obj->set_string ("fullName", full_name); |
| free (full_name); |
| } |
| |
| /* "version" property (SARIF v2.1.0 section 3.19.13). */ |
| if (const char *version = vinfo->get_version_string ()) |
| driver_obj->set_string ("version", version); |
| |
| /* "informationUri" property (SARIF v2.1.0 section 3.19.17). */ |
| if (char *version_url = vinfo->maybe_make_version_url ()) |
| { |
| driver_obj->set_string ("informationUri", version_url); |
| free (version_url); |
| } |
| } |
| |
| /* "rules" property (SARIF v2.1.0 section 3.19.23). */ |
| driver_obj->set<json::array> ("rules", std::move (m_rules_arr)); |
| |
| return driver_obj; |
| } |
| |
| /* If we've seen any CWE IDs, make an array for the "taxonomies" property |
| (SARIF v2.1.0 section 3.14.8) of a run object, containing a single |
| "toolComponent" (3.19) as per 3.19.3, representing the CWE. |
| |
| Otherwise return nullptr. */ |
| |
| std::unique_ptr<json::array> |
| sarif_builder::maybe_make_taxonomies_array () const |
| { |
| auto cwe_obj = maybe_make_cwe_taxonomy_object (); |
| if (!cwe_obj) |
| return nullptr; |
| |
| /* "taxonomies" property (SARIF v2.1.0 section 3.14.8). */ |
| auto taxonomies_arr = std::make_unique<json::array> (); |
| taxonomies_arr->append<sarif_tool_component> (std::move (cwe_obj)); |
| return taxonomies_arr; |
| } |
| |
| /* If we've seen any CWE IDs, make a "toolComponent" object |
| (SARIF v2.1.0 section 3.19) representing the CWE taxonomy, as per 3.19.3. |
| Populate the "taxa" property with all of the CWE IDs in m_cwe_id_set. |
| |
| Otherwise return nullptr. */ |
| |
| std::unique_ptr<sarif_tool_component> |
| sarif_builder::maybe_make_cwe_taxonomy_object () const |
| { |
| if (m_cwe_id_set.is_empty ()) |
| return nullptr; |
| |
| auto taxonomy_obj = std::make_unique<sarif_tool_component> (); |
| |
| /* "name" property (SARIF v2.1.0 section 3.19.8). */ |
| taxonomy_obj->set_string ("name", "CWE"); |
| |
| /* "version" property (SARIF v2.1.0 section 3.19.13). */ |
| taxonomy_obj->set_string ("version", "4.7"); |
| |
| /* "organization" property (SARIF v2.1.0 section 3.19.18). */ |
| taxonomy_obj->set_string ("organization", "MITRE"); |
| |
| /* "shortDescription" property (SARIF v2.1.0 section 3.19.19). */ |
| taxonomy_obj->set<sarif_multiformat_message_string> |
| ("shortDescription", |
| make_multiformat_message_string ("The MITRE" |
| " Common Weakness Enumeration")); |
| |
| /* "taxa" property (SARIF v2.1.0 3.section 3.19.25). */ |
| auto taxa_arr = std::make_unique<json::array> (); |
| for (auto cwe_id : m_cwe_id_set) |
| taxa_arr->append<sarif_reporting_descriptor> |
| (make_reporting_descriptor_object_for_cwe_id (cwe_id)); |
| taxonomy_obj->set<json::array> ("taxa", std::move (taxa_arr)); |
| |
| return taxonomy_obj; |
| } |
| |
| /* Ensure that we have an "artifact" object (SARIF v2.1.0 section 3.24) |
| for FILENAME, adding it to m_filename_to_artifact_map if not already |
| found, and adding ROLE to it. |
| If EMBED_CONTENTS is true, then flag that we will attempt to embed the |
| contents of this artifact when writing it out. */ |
| |
| sarif_artifact & |
| sarif_builder::get_or_create_artifact (const char *filename, |
| enum diagnostic_artifact_role role, |
| bool embed_contents) |
| { |
| if (auto *slot = m_filename_to_artifact_map.get (filename)) |
| { |
| (*slot)->add_role (role, embed_contents); |
| return **slot; |
| } |
| |
| sarif_artifact *artifact_obj = new sarif_artifact (filename); |
| artifact_obj->add_role (role, embed_contents); |
| m_filename_to_artifact_map.put (filename, artifact_obj); |
| |
| /* "location" property (SARIF v2.1.0 section 3.24.2). */ |
| artifact_obj->set<sarif_artifact_location> |
| ("location", make_artifact_location_object (filename)); |
| |
| /* "sourceLanguage" property (SARIF v2.1.0 section 3.24.10). */ |
| switch (role) |
| { |
| default: |
| 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 ()) |
| if (const char *source_lang |
| = client_data_hooks->maybe_get_sarif_source_language (filename)) |
| artifact_obj->set_string ("sourceLanguage", source_lang); |
| break; |
| |
| case diagnostic_artifact_role::debug_output_file: |
| /* Assume that these are not in the source language. */ |
| break; |
| } |
| |
| return *artifact_obj; |
| } |
| |
| /* Make an "artifactContent" object (SARIF v2.1.0 section 3.3) for the |
| full contents of FILENAME. */ |
| |
| std::unique_ptr<sarif_artifact_content> |
| sarif_builder::maybe_make_artifact_content_object (const char *filename) const |
| { |
| /* Let input.cc handle any charset conversion. */ |
| char_span utf8_content |
| = m_context.get_file_cache ().get_source_file_content (filename); |
| if (!utf8_content) |
| return nullptr; |
| |
| /* Don't add it if it's not valid UTF-8. */ |
| if (!cpp_valid_utf8_p(utf8_content.get_buffer (), utf8_content.length ())) |
| return nullptr; |
| |
| auto artifact_content_obj = std::make_unique<sarif_artifact_content> (); |
| artifact_content_obj->set<json::string> |
| ("text", |
| std::make_unique <json::string> (utf8_content.get_buffer (), |
| utf8_content.length ())); |
| return artifact_content_obj; |
| } |
| |
| /* Attempt to read the given range of lines from FILENAME; return |
| a freshly-allocated 0-terminated buffer containing them, or nullptr. */ |
| |
| char * |
| sarif_builder::get_source_lines (const char *filename, |
| int start_line, |
| int end_line) const |
| { |
| auto_vec<char> result; |
| |
| for (int line = start_line; line <= end_line; line++) |
| { |
| char_span line_content |
| = m_context.get_file_cache ().get_source_line (filename, line); |
| if (!line_content.get_buffer ()) |
| return nullptr; |
| result.reserve (line_content.length () + 1); |
| for (size_t i = 0; i < line_content.length (); i++) |
| result.quick_push (line_content[i]); |
| result.quick_push ('\n'); |
| } |
| result.safe_push ('\0'); |
| |
| return xstrdup (result.address ()); |
| } |
| |
| /* Make an "artifactContent" object (SARIF v2.1.0 section 3.3) for the given |
| run of lines within FILENAME (including the endpoints). |
| If R is non-NULL, use it to potentially set the "rendered" |
| property (3.3.4). */ |
| |
| std::unique_ptr<sarif_artifact_content> |
| sarif_builder:: |
| maybe_make_artifact_content_object (const char *filename, |
| int start_line, |
| int end_line, |
| const content_renderer *r) const |
| { |
| char *text_utf8 = get_source_lines (filename, start_line, end_line); |
| |
| if (!text_utf8) |
| return nullptr; |
| |
| /* Don't add it if it's not valid UTF-8. */ |
| if (!cpp_valid_utf8_p(text_utf8, strlen(text_utf8))) |
| { |
| free (text_utf8); |
| return nullptr; |
| } |
| |
| auto artifact_content_obj = std::make_unique<sarif_artifact_content> (); |
| artifact_content_obj->set_string ("text", text_utf8); |
| free (text_utf8); |
| |
| /* 3.3.4 "rendered" property. */ |
| if (r) |
| if (std::unique_ptr<sarif_multiformat_message_string> rendered |
| = r->render (*this)) |
| artifact_content_obj->set ("rendered", std::move (rendered)); |
| |
| return artifact_content_obj; |
| } |
| |
| /* Make a "fix" object (SARIF v2.1.0 section 3.55) for RICHLOC. */ |
| |
| std::unique_ptr<sarif_fix> |
| sarif_builder::make_fix_object (const rich_location &richloc) |
| { |
| auto fix_obj = std::make_unique<sarif_fix> (); |
| |
| /* "artifactChanges" property (SARIF v2.1.0 section 3.55.3). */ |
| /* We assume that all fix-it hints in RICHLOC affect the same file. */ |
| auto artifact_change_arr = std::make_unique<json::array> (); |
| artifact_change_arr->append<sarif_artifact_change> |
| (make_artifact_change_object (richloc)); |
| fix_obj->set<json::array> ("artifactChanges", |
| std::move (artifact_change_arr)); |
| |
| return fix_obj; |
| } |
| |
| /* Make an "artifactChange" object (SARIF v2.1.0 section 3.56) for RICHLOC. */ |
| |
| std::unique_ptr<sarif_artifact_change> |
| sarif_builder::make_artifact_change_object (const rich_location &richloc) |
| { |
| auto artifact_change_obj = std::make_unique<sarif_artifact_change> (); |
| |
| /* "artifactLocation" property (SARIF v2.1.0 section 3.56.2). */ |
| artifact_change_obj->set<sarif_artifact_location> |
| ("artifactLocation", |
| make_artifact_location_object (richloc.get_loc ())); |
| |
| /* "replacements" property (SARIF v2.1.0 section 3.56.3). */ |
| auto replacement_arr = std::make_unique<json::array> (); |
| for (unsigned int i = 0; i < richloc.get_num_fixit_hints (); i++) |
| { |
| const fixit_hint *hint = richloc.get_fixit_hint (i); |
| replacement_arr->append<sarif_replacement> |
| (make_replacement_object (*hint)); |
| } |
| artifact_change_obj->set<json::array> ("replacements", |
| std::move (replacement_arr)); |
| |
| return artifact_change_obj; |
| } |
| |
| /* Make a "replacement" object (SARIF v2.1.0 section 3.57) for HINT. */ |
| |
| std::unique_ptr<sarif_replacement> |
| sarif_builder::make_replacement_object (const fixit_hint &hint) const |
| { |
| auto replacement_obj = std::make_unique<sarif_replacement> (); |
| |
| /* "deletedRegion" property (SARIF v2.1.0 section 3.57.3). */ |
| replacement_obj->set<sarif_region> ("deletedRegion", |
| make_region_object_for_hint (hint)); |
| |
| /* "insertedContent" property (SARIF v2.1.0 section 3.57.4). */ |
| replacement_obj->set<sarif_artifact_content> |
| ("insertedContent", |
| make_artifact_content_object (hint.get_string ())); |
| |
| return replacement_obj; |
| } |
| |
| /* Make an "artifactContent" object (SARIF v2.1.0 section 3.3) for TEXT. */ |
| |
| std::unique_ptr<sarif_artifact_content> |
| sarif_builder::make_artifact_content_object (const char *text) const |
| { |
| auto content_obj = std::make_unique<sarif_artifact_content> (); |
| |
| /* "text" property (SARIF v2.1.0 section 3.3.2). */ |
| content_obj->set_string ("text", text); |
| |
| return content_obj; |
| } |
| |
| /* class sarif_sink_buffer : public per_sink_buffer. */ |
| |
| void |
| sarif_sink_buffer::dump (FILE *out, int indent) const |
| { |
| fprintf (out, "%*ssarif_sink_buffer:\n", indent, ""); |
| int idx = 0; |
| for (auto &result : m_results) |
| { |
| fprintf (out, "%*sresult[%i]:\n", indent + 2, "", idx); |
| result->dump (out, true); |
| fprintf (out, "\n"); |
| ++idx; |
| } |
| } |
| |
| bool |
| sarif_sink_buffer::empty_p () const |
| { |
| return m_results.empty (); |
| } |
| |
| void |
| sarif_sink_buffer::move_to (per_sink_buffer &base) |
| { |
| sarif_sink_buffer &dest |
| = static_cast<sarif_sink_buffer &> (base); |
| for (auto &&result : m_results) |
| dest.m_results.push_back (std::move (result)); |
| m_results.clear (); |
| } |
| |
| void |
| sarif_sink_buffer::clear () |
| { |
| m_results.clear (); |
| } |
| |
| void |
| sarif_sink_buffer::flush () |
| { |
| for (auto &&result : m_results) |
| { |
| result->process_worklist (m_builder); |
| m_builder.m_results_array->append<sarif_result> (std::move (result)); |
| } |
| m_results.clear (); |
| } |
| |
| class sarif_sink : public sink |
| { |
| public: |
| ~sarif_sink () |
| { |
| /* Any sarifResult objects should have been handled by now. |
| If not, then something's gone wrong with diagnostic |
| groupings. */ |
| std::unique_ptr<sarif_result> pending_result |
| = m_builder.take_current_result (); |
| gcc_assert (!pending_result); |
| } |
| |
| void dump (FILE *out, int indent) const override |
| { |
| fprintf (out, "%*ssarif_sink\n", indent, ""); |
| sink::dump (out, indent); |
| } |
| |
| void |
| set_main_input_filename (const char *name) final override |
| { |
| m_builder.set_main_input_filename (name); |
| } |
| |
| std::unique_ptr<per_sink_buffer> |
| make_per_sink_buffer () final override |
| { |
| return std::make_unique<sarif_sink_buffer> (m_builder); |
| } |
| void set_buffer (per_sink_buffer *base_buffer) final override |
| { |
| sarif_sink_buffer *buffer |
| = static_cast<sarif_sink_buffer *> (base_buffer); |
| m_buffer = buffer; |
| } |
| |
| bool follows_reference_printer_p () const final override |
| { |
| return false; |
| } |
| |
| void update_printer () final override |
| { |
| m_printer = m_context.clone_printer (); |
| |
| /* Don't colorize the text. */ |
| pp_show_color (m_printer.get ()) = false; |
| |
| /* No textual URLs. */ |
| m_printer->set_url_format (URL_FORMAT_NONE); |
| |
| /* Use builder's token printer. */ |
| get_printer ()->set_token_printer (&m_builder.get_token_printer ()); |
| |
| /* Update the builder to use the new printer. */ |
| m_builder.set_printer (*get_printer ()); |
| } |
| |
| void on_begin_group () final override |
| { |
| /* No-op, */ |
| } |
| void on_end_group () final override |
| { |
| m_builder.end_group (); |
| } |
| void |
| on_report_diagnostic (const diagnostic_info &diagnostic, |
| enum kind orig_diag_kind) final override |
| { |
| m_builder.on_report_diagnostic (diagnostic, orig_diag_kind, m_buffer); |
| } |
| void on_diagram (const diagram &d) final override |
| { |
| m_builder.emit_diagram (d); |
| } |
| void after_diagnostic (const diagnostic_info &) final override |
| { |
| /* No-op. */ |
| } |
| |
| void |
| report_global_digraph (const lazily_created<digraphs::digraph> &ldg) |
| final override |
| { |
| m_builder.report_global_digraph (ldg); |
| } |
| |
| sarif_builder &get_builder () { return m_builder; } |
| |
| size_t num_results () const { return m_builder.num_results (); } |
| sarif_result &get_result (size_t idx) { return m_builder.get_result (idx); } |
| |
| protected: |
| sarif_sink (context &dc, |
| const line_maps *line_maps, |
| std::unique_ptr<sarif_serialization_format> serialization_format, |
| const sarif_generation_options &sarif_gen_opts) |
| : sink (dc), |
| m_builder (dc, *get_printer (), line_maps, |
| std::move (serialization_format), sarif_gen_opts), |
| m_buffer (nullptr) |
| {} |
| |
| sarif_builder m_builder; |
| sarif_sink_buffer *m_buffer; |
| }; |
| |
| class sarif_stream_sink : public sarif_sink |
| { |
| public: |
| sarif_stream_sink (context &dc, |
| const line_maps *line_maps, |
| std::unique_ptr<sarif_serialization_format> serialization_format, |
| const sarif_generation_options &sarif_gen_opts, |
| FILE *stream) |
| : sarif_sink (dc, line_maps, |
| std::move (serialization_format), sarif_gen_opts), |
| m_stream (stream) |
| { |
| } |
| ~sarif_stream_sink () |
| { |
| m_builder.flush_to_file (m_stream); |
| } |
| bool machine_readable_stderr_p () const final override |
| { |
| return m_stream == stderr; |
| } |
| private: |
| FILE *m_stream; |
| }; |
| |
| class sarif_file_sink : public sarif_sink |
| { |
| public: |
| sarif_file_sink (context &dc, |
| const line_maps *line_maps, |
| std::unique_ptr<sarif_serialization_format> serialization_format, |
| const sarif_generation_options &sarif_gen_opts, |
| output_file output_file_) |
| : sarif_sink (dc, line_maps, |
| std::move (serialization_format), |
| sarif_gen_opts), |
| m_output_file (std::move (output_file_)) |
| { |
| gcc_assert (m_output_file.get_open_file ()); |
| gcc_assert (m_output_file.get_filename ()); |
| } |
| ~sarif_file_sink () |
| { |
| m_builder.flush_to_file (m_output_file.get_open_file ()); |
| } |
| void dump (FILE *out, int indent) const override |
| { |
| fprintf (out, "%*ssarif_file_sink: %s\n", |
| indent, "", |
| m_output_file.get_filename ()); |
| sink::dump (out, indent); |
| } |
| bool machine_readable_stderr_p () const final override |
| { |
| return false; |
| } |
| |
| private: |
| output_file m_output_file; |
| }; |
| |
| /* Print the start of an embedded link to PP, as per 3.11.6. */ |
| |
| static void |
| sarif_begin_embedded_link (pretty_printer *pp) |
| { |
| pp_character (pp, '['); |
| } |
| |
| /* Print the end of an embedded link to PP, as per 3.11.6. */ |
| |
| static void |
| sarif_end_embedded_link (pretty_printer *pp, |
| const char *url) |
| { |
| pp_string (pp, "]("); |
| /* TODO: does the URI need escaping? |
| See https://github.com/oasis-tcs/sarif-spec/issues/657 */ |
| pp_string (pp, url); |
| pp_character (pp, ')'); |
| } |
| |
| /* class sarif_token_printer : public token_printer. */ |
| |
| /* Implementation of pretty_printer::token_printer for SARIF output. |
| Emit URLs as per 3.11.6 ("Messages with embedded links"). */ |
| |
| void |
| sarif_builder::sarif_token_printer::print_tokens (pretty_printer *pp, |
| const pp_token_list &tokens) |
| { |
| /* Convert to text, possibly with colorization, URLs, etc. */ |
| label_text current_url; |
| for (auto iter = tokens.m_first; iter; iter = iter->m_next) |
| switch (iter->m_kind) |
| { |
| default: |
| gcc_unreachable (); |
| |
| case pp_token::kind::text: |
| { |
| const pp_token_text *sub = as_a <const pp_token_text *> (iter); |
| const char * const str = sub->m_value.get (); |
| if (current_url.get ()) |
| { |
| /* Write iter->m_value, but escaping any |
| escaped link characters as per 3.11.6. */ |
| for (const char *ptr = str; *ptr; ptr++) |
| { |
| const char ch = *ptr; |
| switch (ch) |
| { |
| default: |
| pp_character (pp, ch); |
| break; |
| case '\\': |
| case '[': |
| case ']': |
| pp_character (pp, '\\'); |
| pp_character (pp, ch); |
| break; |
| } |
| } |
| } |
| else |
| /* TODO: is other escaping needed? (e.g. of '[') |
| See https://github.com/oasis-tcs/sarif-spec/issues/658 */ |
| pp_string (pp, str); |
| } |
| break; |
| |
| case pp_token::kind::begin_color: |
| case pp_token::kind::end_color: |
| /* These are no-ops. */ |
| break; |
| |
| case pp_token::kind::begin_quote: |
| pp_begin_quote (pp, pp_show_color (pp)); |
| break; |
| case pp_token::kind::end_quote: |
| pp_end_quote (pp, pp_show_color (pp)); |
| break; |
| |
| /* Emit URLs as per 3.11.6 ("Messages with embedded links"). */ |
| case pp_token::kind::begin_url: |
| { |
| pp_token_begin_url *sub = as_a <pp_token_begin_url *> (iter); |
| sarif_begin_embedded_link (pp); |
| current_url = std::move (sub->m_value); |
| } |
| break; |
| case pp_token::kind::end_url: |
| gcc_assert (current_url.get ()); |
| sarif_end_embedded_link (pp, current_url.get ()); |
| current_url = label_text::borrow (nullptr); |
| break; |
| |
| case pp_token::kind::event_id: |
| { |
| pp_token_event_id *sub = as_a <pp_token_event_id *> (iter); |
| gcc_assert (sub->m_event_id.known_p ()); |
| const sarif_code_flow *code_flow |
| = m_builder.get_code_flow_for_event_ids (); |
| label_text url = make_sarif_url_for_event (code_flow, |
| sub->m_event_id); |
| if (url.get ()) |
| sarif_begin_embedded_link (pp); |
| pp_character (pp, '('); |
| pp_decimal_int (pp, sub->m_event_id.one_based ()); |
| pp_character (pp, ')'); |
| if (url.get ()) |
| sarif_end_embedded_link (pp, url.get ()); |
| } |
| break; |
| } |
| } |
| |
| /* Populate CONTEXT in preparation for SARIF output (either to stderr, or |
| to a file). |
| Return a reference to *FMT. */ |
| |
| static sink & |
| init_sarif_sink (context &dc, |
| std::unique_ptr<sarif_sink> fmt) |
| { |
| gcc_assert (fmt); |
| sink &out = *fmt; |
| |
| fmt->update_printer (); |
| |
| dc.set_sink (std::move (fmt)); |
| |
| return out; |
| } |
| |
| /* Populate DC in preparation for SARIF output to stderr. |
| Return a reference to the new sink. */ |
| |
| sink & |
| init_sarif_stderr (context &dc, |
| const line_maps *line_maps, |
| bool formatted) |
| { |
| gcc_assert (line_maps); |
| const sarif_generation_options sarif_gen_opts; |
| auto serialization |
| = std::make_unique<sarif_serialization_format_json> (formatted); |
| return init_sarif_sink |
| (dc, |
| std::make_unique<sarif_stream_sink> (dc, |
| line_maps, |
| std::move (serialization), |
| sarif_gen_opts, |
| stderr)); |
| } |
| |
| /* Attempt to open "BASE_FILE_NAME""EXTENSION" for writing. |
| Return a non-null output_file, |
| or return a null output_file and complain to DC |
| using LINE_MAPS. */ |
| |
| output_file |
| output_file::try_to_open (context &dc, |
| line_maps *line_maps, |
| const char *base_file_name, |
| const char *extension, |
| bool is_binary) |
| { |
| gcc_assert (extension); |
| gcc_assert (extension[0] == '.'); |
| |
| if (!base_file_name) |
| { |
| rich_location richloc (line_maps, UNKNOWN_LOCATION); |
| dc.emit_diagnostic_with_group |
| (kind::error, richloc, nullptr, 0, |
| "unable to determine filename for SARIF output"); |
| return output_file (); |
| } |
| |
| label_text filename = label_text::take (concat (base_file_name, |
| extension, |
| nullptr)); |
| FILE *outf = fopen (filename.get (), is_binary ? "wb" : "w"); |
| if (!outf) |
| { |
| rich_location richloc (line_maps, UNKNOWN_LOCATION); |
| dc.emit_diagnostic_with_group |
| (kind::error, richloc, nullptr, 0, |
| "unable to open %qs for diagnostic output: %m", |
| filename.get ()); |
| return output_file (); |
| } |
| return output_file (outf, true, std::move (filename)); |
| } |
| |
| /* Attempt to open BASE_FILE_NAME.sarif for writing JSON. |
| Return a non-null output_file, |
| or return a null output_file and complain to DC |
| using LINE_MAPS. */ |
| |
| output_file |
| open_sarif_output_file (context &dc, |
| line_maps *line_maps, |
| const char *base_file_name, |
| enum sarif_serialization_kind serialization_kind) |
| { |
| const char *suffix; |
| bool is_binary; |
| switch (serialization_kind) |
| { |
| default: |
| gcc_unreachable (); |
| case sarif_serialization_kind::json: |
| suffix = ".sarif"; |
| is_binary = false; |
| break; |
| } |
| |
| return output_file::try_to_open (dc, |
| line_maps, |
| base_file_name, |
| suffix, |
| is_binary); |
| } |
| |
| /* Populate DC in preparation for SARIF output to a file named |
| BASE_FILE_NAME.sarif. |
| Return a reference to the new sink. */ |
| |
| sink & |
| init_sarif_file (context &dc, |
| line_maps *line_maps, |
| bool formatted, |
| const char *base_file_name) |
| { |
| gcc_assert (line_maps); |
| |
| output_file output_file_ |
| = open_sarif_output_file (dc, |
| line_maps, |
| base_file_name, |
| sarif_serialization_kind::json); |
| auto serialization |
| = std::make_unique<sarif_serialization_format_json> (formatted); |
| |
| const sarif_generation_options sarif_gen_opts; |
| return init_sarif_sink |
| (dc, |
| std::make_unique<sarif_file_sink> (dc, |
| line_maps, |
| std::move (serialization), |
| sarif_gen_opts, |
| std::move (output_file_))); |
| } |
| |
| /* Populate DC in preparation for SARIF output to STREAM. |
| Return a reference to the new sink. */ |
| |
| sink & |
| init_sarif_stream (context &dc, |
| const line_maps *line_maps, |
| bool formatted, |
| FILE *stream) |
| { |
| gcc_assert (line_maps); |
| const sarif_generation_options sarif_gen_opts; |
| auto serialization |
| = std::make_unique<sarif_serialization_format_json> (formatted); |
| return init_sarif_sink |
| (dc, |
| std::make_unique<sarif_stream_sink> (dc, |
| line_maps, |
| std::move (serialization), |
| sarif_gen_opts, |
| stream)); |
| } |
| |
| std::unique_ptr<sink> |
| make_sarif_sink (context &dc, |
| const line_maps &line_maps, |
| std::unique_ptr<sarif_serialization_format> serialization, |
| const sarif_generation_options &sarif_gen_opts, |
| output_file output_file_) |
| { |
| auto sink |
| = std::make_unique<sarif_file_sink> (dc, |
| &line_maps, |
| std::move (serialization), |
| sarif_gen_opts, |
| std::move (output_file_)); |
| sink->update_printer (); |
| return sink; |
| } |
| |
| // struct sarif_generation_options |
| |
| sarif_generation_options::sarif_generation_options () |
| : m_version (sarif_version::v2_1_0), |
| m_state_graph (false) |
| { |
| } |
| |
| #if CHECKING_P |
| |
| namespace selftest { |
| |
| using auto_fix_quotes = ::selftest::auto_fix_quotes; |
| using line_table_case = ::selftest::line_table_case; |
| |
| static void |
| test_sarif_array_of_unique_1 () |
| { |
| sarif_array_of_unique<json::string> arr; |
| |
| ASSERT_EQ (arr.length (), 0); |
| |
| { |
| size_t idx = arr.append_uniquely (std::make_unique<json::string> ("foo")); |
| ASSERT_EQ (idx, 0); |
| ASSERT_EQ (arr.length (), 1); |
| } |
| { |
| size_t idx = arr.append_uniquely (std::make_unique<json::string> ("bar")); |
| ASSERT_EQ (idx, 1); |
| ASSERT_EQ (arr.length (), 2); |
| } |
| |
| /* Try adding them again, should be idempotent. */ |
| { |
| size_t idx = arr.append_uniquely (std::make_unique<json::string> ("foo")); |
| ASSERT_EQ (idx, 0); |
| ASSERT_EQ (arr.length (), 2); |
| } |
| { |
| size_t idx = arr.append_uniquely (std::make_unique<json::string> ("bar")); |
| ASSERT_EQ (idx, 1); |
| ASSERT_EQ (arr.length (), 2); |
| } |
| } |
| |
| static void |
| test_sarif_array_of_unique_2 () |
| { |
| sarif_array_of_unique<json::object> arr; |
| |
| ASSERT_EQ (arr.length (), 0); |
| |
| { |
| auto obj0 = std::make_unique<json::object> (); |
| size_t idx = arr.append_uniquely (std::move (obj0)); |
| ASSERT_EQ (idx, 0); |
| ASSERT_EQ (arr.length (), 1); |
| |
| // Attempting to add another empty objects should be idempotent. |
| idx = arr.append_uniquely (std::make_unique<json::object> ()); |
| ASSERT_EQ (idx, 0); |
| ASSERT_EQ (arr.length (), 1); |
| } |
| { |
| auto obj1 = std::make_unique<json::object> (); |
| obj1->set_string ("foo", "bar"); |
| size_t idx = arr.append_uniquely (std::move (obj1)); |
| ASSERT_EQ (idx, 1); |
| ASSERT_EQ (arr.length (), 2); |
| |
| // Attempting to add an equivalent object should be idempotent. |
| auto other = std::make_unique<json::object> (); |
| other->set_string ("foo", "bar"); |
| idx = arr.append_uniquely (std::move (other)); |
| ASSERT_EQ (idx, 1); |
| ASSERT_EQ (arr.length (), 2); |
| } |
| |
| // Verify behavior of add_explicit_index_values. |
| arr.add_explicit_index_values (); |
| ASSERT_JSON_INT_PROPERTY_EQ (arr[0], "index", 0); |
| ASSERT_JSON_INT_PROPERTY_EQ (arr[1], "index", 1); |
| } |
| |
| /* A subclass of sarif_sink for writing selftests. |
| The JSON output is cached internally, rather than written |
| out to a file. */ |
| |
| class test_sarif_diagnostic_context : public test_context |
| { |
| public: |
| test_sarif_diagnostic_context (const char *main_input_filename, |
| const sarif_generation_options &sarif_gen_opts) |
| { |
| auto sink_ = std::make_unique<buffered_sink> (*this, |
| line_table, |
| true, |
| sarif_gen_opts); |
| m_sink = sink_.get (); // borrowed |
| init_sarif_sink (*this, std::move (sink_)); |
| m_sink->set_main_input_filename (main_input_filename); |
| } |
| |
| std::unique_ptr<sarif_log> flush_to_object () |
| { |
| return m_sink->flush_to_object (); |
| } |
| |
| size_t num_results () const { return m_sink->num_results (); } |
| sarif_result &get_result (size_t idx) { return m_sink->get_result (idx); } |
| |
| private: |
| class buffered_sink : public sarif_sink |
| { |
| public: |
| buffered_sink (context &dc, |
| const line_maps *line_maps, |
| bool formatted, |
| const sarif_generation_options &sarif_gen_opts) |
| : sarif_sink (dc, line_maps, |
| std::make_unique<sarif_serialization_format_json> (formatted), |
| sarif_gen_opts) |
| { |
| } |
| bool machine_readable_stderr_p () const final override |
| { |
| return false; |
| } |
| std::unique_ptr<sarif_log> flush_to_object () |
| { |
| return m_builder.flush_to_object (); |
| } |
| }; |
| |
| buffered_sink *m_sink; // borrowed |
| }; |
| |
| /* Test making a sarif_location for a complex rich_location |
| with labels and escape-on-output. */ |
| |
| static void |
| test_make_location_object (const sarif_generation_options &sarif_gen_opts, |
| const ::selftest::line_table_case &case_) |
| { |
| source_printing_fixture_one_liner_utf8 f (case_); |
| location_t line_end = linemap_position_for_column (line_table, 31); |
| |
| /* Don't attempt to run the tests if column data might be unavailable. */ |
| if (line_end > LINE_MAP_MAX_LOCATION_WITH_COLS) |
| return; |
| |
| test_context dc; |
| pretty_printer pp; |
| sarif_builder builder |
| (dc, pp, line_table, |
| std::make_unique<sarif_serialization_format_json> (true), |
| sarif_gen_opts); |
| |
| /* These "columns" are byte offsets, whereas later on the columns |
| in the generated SARIF use sarif_builder::get_sarif_column and |
| thus respect tabs, encoding. */ |
| const location_t foo |
| = make_location (linemap_position_for_column (line_table, 1), |
| linemap_position_for_column (line_table, 1), |
| linemap_position_for_column (line_table, 8)); |
| const location_t bar |
| = make_location (linemap_position_for_column (line_table, 12), |
| linemap_position_for_column (line_table, 12), |
| linemap_position_for_column (line_table, 17)); |
| const location_t field |
| = make_location (linemap_position_for_column (line_table, 19), |
| linemap_position_for_column (line_table, 19), |
| linemap_position_for_column (line_table, 30)); |
| |
| text_range_label label0 ("label0"); |
| text_range_label label1 ("label1"); |
| text_range_label label2 ("label2"); |
| |
| rich_location richloc (line_table, foo, &label0, nullptr); |
| richloc.add_range (bar, SHOW_RANGE_WITHOUT_CARET, &label1); |
| richloc.add_range (field, SHOW_RANGE_WITHOUT_CARET, &label2); |
| richloc.set_escape_on_output (true); |
| |
| sarif_result result (0); |
| |
| std::unique_ptr<sarif_location> location_obj |
| = builder.make_location_object |
| (&result, richloc, logical_locations::key (), |
| diagnostic_artifact_role::analysis_target); |
| ASSERT_NE (location_obj, nullptr); |
| |
| auto physical_location |
| = EXPECT_JSON_OBJECT_WITH_OBJECT_PROPERTY (location_obj.get (), |
| "physicalLocation"); |
| { |
| auto region |
| = EXPECT_JSON_OBJECT_WITH_OBJECT_PROPERTY (physical_location, "region"); |
| ASSERT_JSON_INT_PROPERTY_EQ (region, "startLine", 1); |
| ASSERT_JSON_INT_PROPERTY_EQ (region, "startColumn", 1); |
| ASSERT_JSON_INT_PROPERTY_EQ (region, "endColumn", 7); |
| } |
| { |
| auto context_region |
| = EXPECT_JSON_OBJECT_WITH_OBJECT_PROPERTY (physical_location, |
| "contextRegion"); |
| ASSERT_JSON_INT_PROPERTY_EQ (context_region, "startLine", 1); |
| |
| { |
| auto snippet |
| = EXPECT_JSON_OBJECT_WITH_OBJECT_PROPERTY (context_region, "snippet"); |
| |
| /* We expect the snippet's "text" to be a copy of the content. */ |
| ASSERT_JSON_STRING_PROPERTY_EQ (snippet, "text", f.m_content); |
| |
| /* We expect the snippet to have a "rendered" whose "text" has a |
| pure ASCII escaped copy of the line (with labels, etc). */ |
| { |
| auto rendered |
| = EXPECT_JSON_OBJECT_WITH_OBJECT_PROPERTY (snippet, "rendered"); |
| ASSERT_JSON_STRING_PROPERTY_EQ |
| (rendered, "text", |
| "1 | <U+1F602>_foo = <U+03C0>_bar.<U+1F602>_field<U+03C0>;\n" |
| " | ^~~~~~~~~~~~~ ~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~\n" |
| " | | | |\n" |
| " | label0 label1 label2\n"); |
| } |
| } |
| } |
| auto annotations |
| = EXPECT_JSON_OBJECT_WITH_ARRAY_PROPERTY (location_obj.get (), |
| "annotations"); |
| ASSERT_EQ (annotations->size (), 3); |
| { |
| { |
| auto a0 = (*annotations)[0]; |
| ASSERT_JSON_INT_PROPERTY_EQ (a0, "startLine", 1); |
| ASSERT_JSON_INT_PROPERTY_EQ (a0, "startColumn", 1); |
| ASSERT_JSON_INT_PROPERTY_EQ (a0, "endColumn", 7); |
| auto message |
| = EXPECT_JSON_OBJECT_WITH_OBJECT_PROPERTY (a0, "message"); |
| ASSERT_JSON_STRING_PROPERTY_EQ (message, "text", "label0"); |
| } |
| { |
| auto a1 = (*annotations)[1]; |
| ASSERT_JSON_INT_PROPERTY_EQ (a1, "startLine", 1); |
| ASSERT_JSON_INT_PROPERTY_EQ (a1, "startColumn", 10); |
| ASSERT_JSON_INT_PROPERTY_EQ (a1, "endColumn", 15); |
| auto message |
| = EXPECT_JSON_OBJECT_WITH_OBJECT_PROPERTY (a1, "message"); |
| ASSERT_JSON_STRING_PROPERTY_EQ (message, "text", "label1"); |
| } |
| { |
| auto a2 = (*annotations)[2]; |
| ASSERT_JSON_INT_PROPERTY_EQ (a2, "startLine", 1); |
| ASSERT_JSON_INT_PROPERTY_EQ (a2, "startColumn", 16); |
| ASSERT_JSON_INT_PROPERTY_EQ (a2, "endColumn", 25); |
| auto message |
| = EXPECT_JSON_OBJECT_WITH_OBJECT_PROPERTY (a2, "message"); |
| ASSERT_JSON_STRING_PROPERTY_EQ (message, "text", "label2"); |
| } |
| } |
| } |
| |
| /* Test of reporting a diagnostic at UNKNOWN_LOCATION to a |
| diagnostics::context and examining the generated sarif_log. |
| Verify various basic properties. */ |
| |
| static void |
| test_simple_log (const sarif_generation_options &sarif_gen_opts) |
| { |
| test_sarif_diagnostic_context dc ("MAIN_INPUT_FILENAME", sarif_gen_opts); |
| |
| rich_location richloc (line_table, UNKNOWN_LOCATION); |
| dc.report (kind::error, richloc, nullptr, 0, "this is a test: %i", 42); |
| |
| auto log_ptr = dc.flush_to_object (); |
| |
| // 3.13 sarifLog: |
| auto log = log_ptr.get (); |
| const enum sarif_version version = sarif_gen_opts.m_version; |
| ASSERT_JSON_STRING_PROPERTY_EQ (log, "$schema", |
| sarif_version_to_url (version)); |
| ASSERT_JSON_STRING_PROPERTY_EQ (log, "version", |
| sarif_version_to_property (version)); |
| |
| auto runs = EXPECT_JSON_OBJECT_WITH_ARRAY_PROPERTY (log, "runs"); // 3.13.4 |
| ASSERT_EQ (runs->size (), 1); |
| |
| // 3.14 "run" object: |
| auto run = (*runs)[0]; |
| |
| { |
| // 3.14.6: |
| auto tool = EXPECT_JSON_OBJECT_WITH_OBJECT_PROPERTY (run, "tool"); |
| |
| EXPECT_JSON_OBJECT_WITH_OBJECT_PROPERTY (tool, "driver"); // 3.18.2 |
| } |
| |
| { |
| // 3.14.11 |
| auto invocations |
| = EXPECT_JSON_OBJECT_WITH_ARRAY_PROPERTY (run, "invocations"); |
| ASSERT_EQ (invocations->size (), 1); |
| |
| { |
| // 3.20 "invocation" object: |
| auto invocation = (*invocations)[0]; |
| |
| // 3.20.3 arguments property |
| |
| // 3.20.7 startTimeUtc property |
| EXPECT_JSON_OBJECT_WITH_STRING_PROPERTY (invocation, "startTimeUtc"); |
| |
| // 3.20.8 endTimeUtc property |
| EXPECT_JSON_OBJECT_WITH_STRING_PROPERTY (invocation, "endTimeUtc"); |
| |
| // 3.20.19 workingDirectory property |
| { |
| auto wd_obj |
| = EXPECT_JSON_OBJECT_WITH_OBJECT_PROPERTY (invocation, |
| "workingDirectory"); |
| EXPECT_JSON_OBJECT_WITH_STRING_PROPERTY (wd_obj, "uri"); |
| } |
| |
| // 3.20.21 toolExecutionNotifications property |
| auto notifications |
| = EXPECT_JSON_OBJECT_WITH_ARRAY_PROPERTY |
| (invocation, "toolExecutionNotifications"); |
| ASSERT_EQ (notifications->size (), 0); |
| } |
| } |
| |
| { |
| // 3.14.15: |
| auto artifacts = EXPECT_JSON_OBJECT_WITH_ARRAY_PROPERTY (run, "artifacts"); |
| ASSERT_EQ (artifacts->size (), 1); |
| |
| { |
| // 3.24 "artifact" object: |
| auto artifact = (*artifacts)[0]; |
| |
| // 3.24.2: |
| auto location |
| = EXPECT_JSON_OBJECT_WITH_OBJECT_PROPERTY (artifact, "location"); |
| ASSERT_JSON_STRING_PROPERTY_EQ (location, "uri", "MAIN_INPUT_FILENAME"); |
| |
| // 3.24.6: |
| auto roles = EXPECT_JSON_OBJECT_WITH_ARRAY_PROPERTY (artifact, "roles"); |
| ASSERT_EQ (roles->size (), 1); |
| { |
| auto role = (*roles)[0]; |
| ASSERT_JSON_STRING_EQ (role, "analysisTarget"); |
| } |
| } |
| } |
| |
| { |
| // 3.14.23: |
| auto results = EXPECT_JSON_OBJECT_WITH_ARRAY_PROPERTY (run, "results"); |
| ASSERT_EQ (results->size (), 1); |
| |
| { |
| // 3.27 "result" object: |
| auto result = (*results)[0]; |
| ASSERT_JSON_STRING_PROPERTY_EQ (result, "ruleId", "error"); |
| ASSERT_JSON_STRING_PROPERTY_EQ (result, "level", "error"); // 3.27.10 |
| |
| { |
| // 3.27.11: |
| auto message |
| = EXPECT_JSON_OBJECT_WITH_OBJECT_PROPERTY (result, "message"); |
| ASSERT_JSON_STRING_PROPERTY_EQ (message, "text", |
| "this is a test: 42"); |
| } |
| |
| // 3.27.12: |
| auto locations |
| = EXPECT_JSON_OBJECT_WITH_ARRAY_PROPERTY (result, "locations"); |
| ASSERT_EQ (locations->size (), 0); |
| } |
| } |
| } |
| |
| /* As above, but with a "real" location_t. */ |
| |
| static void |
| test_simple_log_2 (const sarif_generation_options &sarif_gen_opts, |
| const line_table_case &case_) |
| { |
| auto_fix_quotes fix_quotes; |
| |
| const char *const content |
| /* 000000000111111 |
| 123456789012345. */ |
| = "unsinged int i;\n"; |
| source_printing_fixture f (case_, content); |
| location_t line_end = linemap_position_for_column (line_table, 31); |
| |
| /* Don't attempt to run the tests if column data might be unavailable. */ |
| if (line_end > LINE_MAP_MAX_LOCATION_WITH_COLS) |
| return; |
| |
| test_sarif_diagnostic_context dc (f.get_filename (), sarif_gen_opts); |
| |
| const location_t typo_loc |
| = make_location (linemap_position_for_column (line_table, 1), |
| linemap_position_for_column (line_table, 1), |
| linemap_position_for_column (line_table, 8)); |
| |
| rich_location richloc (line_table, typo_loc); |
| dc.report (kind::error, richloc, nullptr, 0, |
| "did you misspell %qs again?", |
| "unsigned"); |
| |
| auto log_ptr = dc.flush_to_object (); |
| |
| // 3.13 sarifLog: |
| auto log = log_ptr.get (); |
| |
| auto runs = EXPECT_JSON_OBJECT_WITH_ARRAY_PROPERTY (log, "runs"); // 3.13.4 |
| ASSERT_EQ (runs->size (), 1); |
| |
| // 3.14 "run" object: |
| auto run = (*runs)[0]; |
| |
| { |
| // 3.14.23: |
| auto results = EXPECT_JSON_OBJECT_WITH_ARRAY_PROPERTY (run, "results"); |
| ASSERT_EQ (results->size (), 1); |
| |
| { |
| // 3.27 "result" object: |
| auto result = (*results)[0]; |
| ASSERT_JSON_STRING_PROPERTY_EQ (result, "ruleId", "error"); |
| ASSERT_JSON_STRING_PROPERTY_EQ (result, "level", "error"); // 3.27.10 |
| |
| { |
| // 3.27.11: |
| auto message |
| = EXPECT_JSON_OBJECT_WITH_OBJECT_PROPERTY (result, "message"); |
| ASSERT_JSON_STRING_PROPERTY_EQ (message, "text", |
| "did you misspell `unsigned' again?"); |
| } |
| |
| // 3.27.12: |
| auto locations |
| = EXPECT_JSON_OBJECT_WITH_ARRAY_PROPERTY (result, "locations"); |
| ASSERT_EQ (locations->size (), 1); |
| |
| { |
| // 3.28 "location" object: |
| auto location = (*locations)[0]; |
| |
| auto physical_location |
| = EXPECT_JSON_OBJECT_WITH_OBJECT_PROPERTY (location, |
| "physicalLocation"); |
| { |
| auto region |
| = EXPECT_JSON_OBJECT_WITH_OBJECT_PROPERTY (physical_location, |
| "region"); |
| ASSERT_JSON_INT_PROPERTY_EQ (region, "startLine", 1); |
| ASSERT_JSON_INT_PROPERTY_EQ (region, "startColumn", 1); |
| ASSERT_JSON_INT_PROPERTY_EQ (region, "endColumn", 9); |
| } |
| { |
| auto context_region |
| = EXPECT_JSON_OBJECT_WITH_OBJECT_PROPERTY (physical_location, |
| "contextRegion"); |
| ASSERT_JSON_INT_PROPERTY_EQ (context_region, "startLine", 1); |
| |
| { |
| auto snippet |
| = EXPECT_JSON_OBJECT_WITH_OBJECT_PROPERTY (context_region, |
| "snippet"); |
| |
| /* We expect the snippet's "text" to be a copy of the content. */ |
| ASSERT_JSON_STRING_PROPERTY_EQ (snippet, "text", f.m_content); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| /* Assuming that a single diagnostic has been emitted within |
| LOG, get a json::object for the result object. */ |
| |
| static const json::object * |
| get_result_from_log (const sarif_log *log) |
| { |
| auto runs = EXPECT_JSON_OBJECT_WITH_ARRAY_PROPERTY (log, "runs"); // 3.13.4 |
| ASSERT_EQ (runs->size (), 1); |
| |
| // 3.14 "run" object: |
| auto run = (*runs)[0]; |
| |
| // 3.14.23: |
| auto results = EXPECT_JSON_OBJECT_WITH_ARRAY_PROPERTY (run, "results"); |
| ASSERT_EQ (results->size (), 1); |
| |
| // 3.27 "result" object: |
| auto result = (*results)[0]; |
| return expect_json_object (SELFTEST_LOCATION, result); |
| } |
| |
| static const json::object * |
| get_message_from_result (const sarif_result &result) |
| { |
| // 3.27.11: |
| auto message_obj |
| = EXPECT_JSON_OBJECT_WITH_OBJECT_PROPERTY (&result, "message"); |
| return message_obj; |
| } |
| |
| /* Assuming that a single diagnostic has been emitted to |
| DC, get a json::object for the messsage object within |
| the result. */ |
| |
| static const json::object * |
| get_message_from_log (const sarif_log *log) |
| { |
| auto result_obj = get_result_from_log (log); |
| |
| // 3.27.11: |
| auto message_obj |
| = EXPECT_JSON_OBJECT_WITH_OBJECT_PROPERTY (result_obj, "message"); |
| return message_obj; |
| } |
| |
| /* Tests of messages with embedded links; see SARIF v2.1.0 3.11.6. */ |
| |
| static void |
| test_message_with_embedded_link (const sarif_generation_options &sarif_gen_opts) |
| { |
| auto_fix_quotes fix_quotes; |
| { |
| test_sarif_diagnostic_context dc ("test.c", sarif_gen_opts); |
| rich_location richloc (line_table, UNKNOWN_LOCATION); |
| dc.report (kind::error, richloc, nullptr, 0, |
| "before %{text%} after", |
| "http://example.com"); |
| std::unique_ptr<sarif_log> log = dc.flush_to_object (); |
| |
| auto message_obj = get_message_from_log (log.get ()); |
| ASSERT_JSON_STRING_PROPERTY_EQ |
| (message_obj, "text", |
| "before [text](http://example.com) after"); |
| } |
| |
| /* Escaping in message text. |
| This is "EXAMPLE 1" from 3.11.6. */ |
| { |
| test_sarif_diagnostic_context dc ("test.c", sarif_gen_opts); |
| rich_location richloc (line_table, UNKNOWN_LOCATION); |
| |
| /* Disable "unquoted sequence of 2 consecutive punctuation |
| characters `]\' in format" warning. */ |
| #if __GNUC__ >= 10 |
| # pragma GCC diagnostic push |
| # pragma GCC diagnostic ignored "-Wformat-diag" |
| #endif |
| dc.report (kind::error, richloc, nullptr, 0, |
| "Prohibited term used in %{para[0]\\spans[2]%}.", |
| "1"); |
| #if __GNUC__ >= 10 |
| # pragma GCC diagnostic pop |
| #endif |
| |
| std::unique_ptr<sarif_log> log = dc.flush_to_object (); |
| |
| auto message_obj = get_message_from_log (log.get ()); |
| ASSERT_JSON_STRING_PROPERTY_EQ |
| (message_obj, "text", |
| "Prohibited term used in [para\\[0\\]\\\\spans\\[2\\]](1)."); |
| /* This isn't exactly what EXAMPLE 1 of the spec has; reported as |
| https://github.com/oasis-tcs/sarif-spec/issues/656 */ |
| } |
| |
| /* Urlifier. */ |
| { |
| class test_urlifier : public urlifier |
| { |
| public: |
| char * |
| get_url_for_quoted_text (const char *p, size_t sz) const final override |
| { |
| if (!strncmp (p, "-foption", sz)) |
| return xstrdup ("http://example.com"); |
| return nullptr; |
| } |
| }; |
| |
| test_sarif_diagnostic_context dc ("test.c", sarif_gen_opts); |
| dc.push_owned_urlifier (std::make_unique<test_urlifier> ()); |
| rich_location richloc (line_table, UNKNOWN_LOCATION); |
| dc.report (kind::error, richloc, nullptr, 0, |
| "foo %<-foption%> %<unrecognized%> bar"); |
| std::unique_ptr<sarif_log> log = dc.flush_to_object (); |
| |
| auto message_obj = get_message_from_log (log.get ()); |
| ASSERT_JSON_STRING_PROPERTY_EQ |
| (message_obj, "text", |
| "foo `[-foption](http://example.com)' `unrecognized' bar"); |
| } |
| } |
| |
| /* Verify that braces in messages get escaped, as per |
| 3.11.5 ("Messages with placeholders"). */ |
| |
| static void |
| test_message_with_braces (const sarif_generation_options &sarif_gen_opts) |
| { |
| auto_fix_quotes fix_quotes; |
| { |
| test_sarif_diagnostic_context dc ("test.c", sarif_gen_opts); |
| rich_location richloc (line_table, UNKNOWN_LOCATION); |
| dc.report (kind::error, richloc, nullptr, 0, |
| "open brace: %qs close brace: %qs", |
| "{", "}"); |
| std::unique_ptr<sarif_log> log = dc.flush_to_object (); |
| |
| auto message_obj = get_message_from_log (log.get ()); |
| ASSERT_JSON_STRING_PROPERTY_EQ |
| (message_obj, "text", |
| "open brace: `{{' close brace: `}}'"); |
| } |
| } |
| |
| static void |
| test_buffering (const sarif_generation_options &sarif_gen_opts) |
| { |
| test_sarif_diagnostic_context dc ("test.c", sarif_gen_opts); |
| |
| diagnostics::buffer buf_a (dc); |
| diagnostics::buffer buf_b (dc); |
| |
| rich_location rich_loc (line_table, UNKNOWN_LOCATION); |
| |
| ASSERT_EQ (dc.diagnostic_count (kind::error), 0); |
| ASSERT_EQ (buf_a.diagnostic_count (kind::error), 0); |
| ASSERT_EQ (buf_b.diagnostic_count (kind::error), 0); |
| ASSERT_EQ (dc.num_results (), 0); |
| ASSERT_TRUE (buf_a.empty_p ()); |
| ASSERT_TRUE (buf_b.empty_p ()); |
| |
| /* Unbuffered diagnostic. */ |
| { |
| dc.report (kind::error, rich_loc, nullptr, 0, |
| "message 1"); |
| |
| ASSERT_EQ (dc.diagnostic_count (kind::error), 1); |
| ASSERT_EQ (buf_a.diagnostic_count (kind::error), 0); |
| ASSERT_EQ (buf_b.diagnostic_count (kind::error), 0); |
| ASSERT_EQ (dc.num_results (), 1); |
| sarif_result &result_obj = dc.get_result (0); |
| auto message_obj = get_message_from_result (result_obj); |
| ASSERT_JSON_STRING_PROPERTY_EQ (message_obj, "text", |
| "message 1"); |
| ASSERT_TRUE (buf_a.empty_p ()); |
| ASSERT_TRUE (buf_b.empty_p ()); |
| } |
| |
| /* Buffer diagnostic into buffer A. */ |
| { |
| dc.set_diagnostic_buffer (&buf_a); |
| dc.report (kind::error, rich_loc, nullptr, 0, |
| "message in buffer a"); |
| ASSERT_EQ (dc.diagnostic_count (kind::error), 1); |
| ASSERT_EQ (buf_a.diagnostic_count (kind::error), 1); |
| ASSERT_EQ (buf_b.diagnostic_count (kind::error), 0); |
| ASSERT_EQ (dc.num_results (), 1); |
| ASSERT_FALSE (buf_a.empty_p ()); |
| ASSERT_TRUE (buf_b.empty_p ()); |
| } |
| |
| /* Buffer diagnostic into buffer B. */ |
| { |
| dc.set_diagnostic_buffer (&buf_b); |
| dc.report (kind::error, rich_loc, nullptr, 0, |
| "message in buffer b"); |
| ASSERT_EQ (dc.diagnostic_count (kind::error), 1); |
| ASSERT_EQ (buf_a.diagnostic_count (kind::error), 1); |
| ASSERT_EQ (buf_b.diagnostic_count (kind::error), 1); |
| ASSERT_EQ (dc.num_results (), 1); |
| ASSERT_FALSE (buf_a.empty_p ()); |
| ASSERT_FALSE (buf_b.empty_p ()); |
| } |
| |
| /* Flush buffer B to dc. */ |
| { |
| dc.flush_diagnostic_buffer (buf_b); |
| ASSERT_EQ (dc.diagnostic_count (kind::error), 2); |
| ASSERT_EQ (buf_a.diagnostic_count (kind::error), 1); |
| ASSERT_EQ (buf_b.diagnostic_count (kind::error), 0); |
| ASSERT_EQ (dc.num_results (), 2); |
| sarif_result &result_1_obj = dc.get_result (1); |
| auto message_1_obj = get_message_from_result (result_1_obj); |
| ASSERT_JSON_STRING_PROPERTY_EQ (message_1_obj, "text", |
| "message in buffer b"); |
| ASSERT_FALSE (buf_a.empty_p ()); |
| ASSERT_TRUE (buf_b.empty_p ()); |
| } |
| |
| /* Clear buffer A. */ |
| { |
| dc.clear_diagnostic_buffer (buf_a); |
| ASSERT_EQ (dc.diagnostic_count (kind::error), 2); |
| ASSERT_EQ (buf_a.diagnostic_count (kind::error), 0); |
| ASSERT_EQ (buf_b.diagnostic_count (kind::error), 0); |
| ASSERT_EQ (dc.num_results (), 2); |
| ASSERT_TRUE (buf_a.empty_p ()); |
| ASSERT_TRUE (buf_b.empty_p ()); |
| } |
| } |
| |
| template <class ...ArgTypes> |
| static void |
| for_each_sarif_gen_option (void (*callback) (const sarif_generation_options &, |
| ArgTypes ...), |
| ArgTypes ...args) |
| { |
| sarif_generation_options sarif_gen_opts; |
| for (int version_idx = 0; |
| version_idx < (int)sarif_version::num_versions; |
| ++version_idx) |
| { |
| sarif_gen_opts.m_version = static_cast<enum sarif_version> (version_idx); |
| |
| callback (sarif_gen_opts, args...); |
| } |
| } |
| |
| static void |
| run_line_table_case_tests_per_version (const line_table_case &case_) |
| { |
| for_each_sarif_gen_option<const line_table_case &> |
| (test_make_location_object, case_); |
| |
| for_each_sarif_gen_option<const line_table_case &> |
| (test_simple_log_2, case_); |
| } |
| |
| /* Run all of the selftests within this file. */ |
| |
| void |
| sarif_sink_cc_tests () |
| { |
| test_sarif_array_of_unique_1 (); |
| test_sarif_array_of_unique_2 (); |
| |
| for_each_sarif_gen_option (test_simple_log); |
| for_each_sarif_gen_option (test_message_with_embedded_link); |
| for_each_sarif_gen_option (test_message_with_braces); |
| for_each_sarif_gen_option (test_buffering); |
| |
| /* Run tests per (SARIF gen-option, line-table-case) pair. */ |
| for_each_line_table_case (run_line_table_case_tests_per_version); |
| } |
| |
| } // namespace diagnostics::selftest |
| |
| #endif /* CHECKING_P */ |
| |
| } // namespace diagnostics |