| /* SARIF output for diagnostics |
| Copyright (C) 2018-2023 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_VECTOR |
| #include "system.h" |
| #include "coretypes.h" |
| #include "diagnostic.h" |
| #include "diagnostic-metadata.h" |
| #include "diagnostic-path.h" |
| #include "json.h" |
| #include "cpplib.h" |
| #include "logical-location.h" |
| #include "diagnostic-client-data-hooks.h" |
| #include "diagnostic-diagram.h" |
| #include "text-art/canvas.h" |
| #include "diagnostic-format-sarif.h" |
| |
| class sarif_builder; |
| |
| /* Subclass of json::object for SARIF invocation objects |
| (SARIF v2.1.0 section 3.20). */ |
| |
| class sarif_invocation : public sarif_object |
| { |
| public: |
| sarif_invocation () |
| : m_notifications_arr (new json::array ()), |
| m_success (true) |
| {} |
| |
| void add_notification_for_ice (diagnostic_context *context, |
| const diagnostic_info &diagnostic, |
| sarif_builder *builder); |
| void prepare_to_flush (diagnostic_context *context); |
| |
| private: |
| json::array *m_notifications_arr; |
| bool m_success; |
| }; |
| |
| /* Subclass of sarif_object for SARIF result objects |
| (SARIF v2.1.0 section 3.27). */ |
| |
| class sarif_result : public sarif_object |
| { |
| public: |
| sarif_result () : m_related_locations_arr (NULL) {} |
| |
| void |
| on_nested_diagnostic (diagnostic_context *context, |
| const diagnostic_info &diagnostic, |
| diagnostic_t orig_diag_kind, |
| sarif_builder *builder); |
| void on_diagram (diagnostic_context *context, |
| const diagnostic_diagram &diagram, |
| sarif_builder *builder); |
| |
| private: |
| void add_related_location (json::object *location_obj); |
| |
| json::array *m_related_locations_arr; |
| }; |
| |
| /* 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_object |
| { |
| public: |
| sarif_ice_notification (diagnostic_context *context, |
| const diagnostic_info &diagnostic, |
| sarif_builder *builder); |
| }; |
| |
| /* Subclass of sarif_object for SARIF threadFlow objects |
| (SARIF v2.1.0 section 3.37) for PATH. */ |
| |
| class sarif_thread_flow : public sarif_object |
| { |
| public: |
| sarif_thread_flow (const diagnostic_thread &thread); |
| |
| void add_location (json::object *thread_flow_loc_obj) |
| { |
| m_locations_arr->append (thread_flow_loc_obj); |
| } |
| |
| private: |
| json::array *m_locations_arr; |
| }; |
| |
| /* 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) |
| |
| Known limitations: |
| - GCC supports one-deep nesting of diagnostics (via auto_diagnostic_group), |
| but we only capture location and message information from such nested |
| diagnostics (e.g. we ignore fix-it hints on them) |
| - doesn't yet capture command-line arguments: would be run.invocations |
| property (SARIF v2.1.0 section 3.14.11), as invocation objects |
| (SARIF v2.1.0 section 3.20), but we'd want to capture the arguments to |
| toplev::main, and the response files. |
| - doesn't capture escape_on_output_p |
| - doesn't capture secondary locations within a rich_location |
| (perhaps we should use the "relatedLocations" property: SARIF v2.1.0 |
| section 3.27.22) |
| - 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 labelled ranges |
| - doesn't capture -Werror cleanly |
| - doesn't capture inlining information (can SARIF handle this?) |
| - doesn't capture macro expansion information (can SARIF handle this?). */ |
| |
| class sarif_builder |
| { |
| public: |
| sarif_builder (diagnostic_context *context, |
| bool formatted); |
| |
| void end_diagnostic (diagnostic_context *context, |
| const diagnostic_info &diagnostic, |
| diagnostic_t orig_diag_kind); |
| void emit_diagram (diagnostic_context *context, |
| const diagnostic_diagram &diagram); |
| void end_group (); |
| |
| void flush_to_file (FILE *outf); |
| |
| json::array *make_locations_arr (const diagnostic_info &diagnostic); |
| json::object *make_location_object (const rich_location &rich_loc, |
| const logical_location *logical_loc); |
| json::object *make_message_object (const char *msg) const; |
| json::object * |
| make_message_object_for_diagram (diagnostic_context *context, |
| const diagnostic_diagram &diagram); |
| |
| private: |
| sarif_result *make_result_object (diagnostic_context *context, |
| const diagnostic_info &diagnostic, |
| diagnostic_t orig_diag_kind); |
| void set_any_logical_locs_arr (json::object *location_obj, |
| const logical_location *logical_loc); |
| json::object *make_location_object (const diagnostic_event &event); |
| json::object * |
| make_logical_location_object (const logical_location &logical_loc) const; |
| json::object *make_code_flow_object (const diagnostic_path &path); |
| json::object * |
| make_thread_flow_location_object (const diagnostic_event &event, |
| int path_event_idx); |
| json::array *maybe_make_kinds_array (diagnostic_event::meaning m) const; |
| json::object *maybe_make_physical_location_object (location_t loc); |
| json::object *make_artifact_location_object (location_t loc); |
| json::object *make_artifact_location_object (const char *filename); |
| json::object *make_artifact_location_object_for_pwd () const; |
| json::object *maybe_make_region_object (location_t loc) const; |
| json::object *maybe_make_region_object_for_context (location_t loc) const; |
| json::object *make_region_object_for_hint (const fixit_hint &hint) const; |
| json::object *make_multiformat_message_string (const char *msg) const; |
| json::object *make_top_level_object (sarif_invocation *invocation_obj, |
| json::array *results); |
| json::object *make_run_object (sarif_invocation *invocation_obj, |
| json::array *results); |
| json::object *make_tool_object () const; |
| json::object *make_driver_tool_component_object () const; |
| json::array *maybe_make_taxonomies_array () const; |
| json::object *maybe_make_cwe_taxonomy_object () const; |
| json::object *make_tool_component_reference_object_for_cwe () const; |
| json::object * |
| make_reporting_descriptor_object_for_warning (diagnostic_context *context, |
| const diagnostic_info &diagnostic, |
| diagnostic_t orig_diag_kind, |
| const char *option_text); |
| json::object *make_reporting_descriptor_object_for_cwe_id (int cwe_id) const; |
| json::object * |
| make_reporting_descriptor_reference_object_for_cwe_id (int cwe_id); |
| json::object *make_artifact_object (const char *filename); |
| char *get_source_lines (const char *filename, |
| int start_line, |
| int end_line) const; |
| json::object *maybe_make_artifact_content_object (const char *filename) const; |
| json::object *maybe_make_artifact_content_object (const char *filename, |
| int start_line, |
| int end_line) const; |
| json::object *make_fix_object (const rich_location &rich_loc); |
| json::object *make_artifact_change_object (const rich_location &richloc); |
| json::object *make_replacement_object (const fixit_hint &hint) const; |
| json::object *make_artifact_content_object (const char *text) const; |
| int get_sarif_column (expanded_location exploc) const; |
| |
| diagnostic_context *m_context; |
| |
| /* The JSON object for the invocation object. */ |
| sarif_invocation *m_invocation_obj; |
| |
| /* The JSON array of pending diagnostics. */ |
| json::array *m_results_array; |
| |
| /* The JSON object for the result object (if any) in the current |
| diagnostic group. */ |
| sarif_result *m_cur_group_result; |
| |
| hash_set <const char *> m_filenames; |
| bool m_seen_any_relative_paths; |
| hash_set <free_string_hash> m_rule_id_set; |
| 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; |
| |
| int m_tabstop; |
| |
| bool m_formatted; |
| }; |
| |
| /* 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. */ |
| |
| /* Handle an internal compiler error DIAGNOSTIC occurring on CONTEXT. |
| Add an object representing the ICE to the notifications array. */ |
| |
| void |
| sarif_invocation::add_notification_for_ice (diagnostic_context *context, |
| const diagnostic_info &diagnostic, |
| sarif_builder *builder) |
| { |
| m_success = false; |
| |
| sarif_ice_notification *notification_obj |
| = new sarif_ice_notification (context, diagnostic, builder); |
| m_notifications_arr->append (notification_obj); |
| } |
| |
| void |
| sarif_invocation::prepare_to_flush (diagnostic_context *context) |
| { |
| /* "executionSuccessful" property (SARIF v2.1.0 section 3.20.14). */ |
| set_bool ("executionSuccessful", m_success); |
| |
| /* "toolExecutionNotifications" property (SARIF v2.1.0 section 3.20.21). */ |
| set ("toolExecutionNotifications", 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 = context->get_client_data_hooks ()) |
| client_data_hooks->add_sarif_invocation_properties (*this); |
| } |
| |
| /* class sarif_result : public sarif_object. */ |
| |
| /* 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 (diagnostic_context *context, |
| const diagnostic_info &diagnostic, |
| diagnostic_t /*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. */ |
| json::object *location_obj |
| = builder->make_location_object (*diagnostic.richloc, NULL); |
| json::object *message_obj |
| = builder->make_message_object (pp_formatted_text (context->printer)); |
| pp_clear_output_area (context->printer); |
| location_obj->set ("message", message_obj); |
| |
| add_related_location (location_obj); |
| } |
| |
| /* 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 (diagnostic_context *context, |
| const diagnostic_diagram &diagram, |
| sarif_builder *builder) |
| { |
| json::object *location_obj = new json::object (); |
| json::object *message_obj |
| = builder->make_message_object_for_diagram (context, diagram); |
| location_obj->set ("message", message_obj); |
| |
| add_related_location (location_obj); |
| } |
| |
| /* Add LOCATION_OBJ to this result's "relatedLocations" array, |
| creating it if it doesn't yet exist. */ |
| |
| void |
| sarif_result::add_related_location (json::object *location_obj) |
| { |
| if (!m_related_locations_arr) |
| { |
| m_related_locations_arr = new json::array (); |
| set ("relatedLocations", m_related_locations_arr); |
| } |
| m_related_locations_arr->append (location_obj); |
| } |
| |
| /* class sarif_ice_notification : public sarif_object. */ |
| |
| /* sarif_ice_notification's ctor. |
| DIAGNOSTIC is an internal compiler error. */ |
| |
| sarif_ice_notification::sarif_ice_notification (diagnostic_context *context, |
| const diagnostic_info &diagnostic, |
| sarif_builder *builder) |
| { |
| /* "locations" property (SARIF v2.1.0 section 3.58.4). */ |
| json::array *locations_arr = builder->make_locations_arr (diagnostic); |
| set ("locations", locations_arr); |
| |
| /* "message" property (SARIF v2.1.0 section 3.85.5). */ |
| json::object *message_obj |
| = builder->make_message_object (pp_formatted_text (context->printer)); |
| pp_clear_output_area (context->printer); |
| set ("message", message_obj); |
| |
| /* "level" property (SARIF v2.1.0 section 3.58.6). */ |
| set_string ("level", "error"); |
| } |
| |
| /* class sarif_thread_flow : public sarif_object. */ |
| |
| sarif_thread_flow::sarif_thread_flow (const diagnostic_thread &thread) |
| { |
| /* "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 (); |
| set ("locations", m_locations_arr); |
| } |
| |
| /* class sarif_builder. */ |
| |
| /* sarif_builder's ctor. */ |
| |
| sarif_builder::sarif_builder (diagnostic_context *context, |
| bool formatted) |
| : m_context (context), |
| m_invocation_obj (new sarif_invocation ()), |
| m_results_array (new json::array ()), |
| m_cur_group_result (NULL), |
| m_seen_any_relative_paths (false), |
| m_rule_id_set (), |
| m_rules_arr (new json::array ()), |
| m_tabstop (context->m_tabstop), |
| m_formatted (formatted) |
| { |
| } |
| |
| /* Implementation of "end_diagnostic" for SARIF output. */ |
| |
| void |
| sarif_builder::end_diagnostic (diagnostic_context *context, |
| const diagnostic_info &diagnostic, |
| diagnostic_t orig_diag_kind) |
| { |
| if (diagnostic.kind == DK_ICE || diagnostic.kind == DK_ICE_NOBT) |
| { |
| m_invocation_obj->add_notification_for_ice (context, diagnostic, this); |
| return; |
| } |
| |
| if (m_cur_group_result) |
| /* Nested diagnostic. */ |
| m_cur_group_result->on_nested_diagnostic (context, |
| diagnostic, |
| orig_diag_kind, |
| this); |
| else |
| { |
| /* Top-level diagnostic. */ |
| sarif_result *result_obj |
| = make_result_object (context, diagnostic, orig_diag_kind); |
| m_results_array->append (result_obj); |
| m_cur_group_result = result_obj; |
| } |
| } |
| |
| /* Implementation of diagnostic_context::m_diagrams.m_emission_cb |
| for SARIF output. */ |
| |
| void |
| sarif_builder::emit_diagram (diagnostic_context *context, |
| const diagnostic_diagram &diagram) |
| { |
| /* We must be within the emission of a top-level diagnostic. */ |
| gcc_assert (m_cur_group_result); |
| m_cur_group_result->on_diagram (context, diagram, this); |
| } |
| |
| /* Implementation of "end_group_cb" for SARIF output. */ |
| |
| void |
| sarif_builder::end_group () |
| { |
| m_cur_group_result = NULL; |
| } |
| |
| /* 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) |
| { |
| m_invocation_obj->prepare_to_flush (m_context); |
| json::object *top = make_top_level_object (m_invocation_obj, m_results_array); |
| top->dump (outf, m_formatted); |
| m_invocation_obj = NULL; |
| m_results_array = NULL; |
| fprintf (outf, "\n"); |
| delete top; |
| } |
| |
| /* Attempt to convert DIAG_KIND to a suitable value for the "level" |
| property (SARIF v2.1.0 section 3.27.10). |
| |
| Return NULL if there isn't one. */ |
| |
| static const char * |
| maybe_get_sarif_level (diagnostic_t diag_kind) |
| { |
| switch (diag_kind) |
| { |
| case DK_WARNING: |
| return "warning"; |
| case DK_ERROR: |
| return "error"; |
| case DK_NOTE: |
| case DK_ANACHRONISM: |
| return "note"; |
| default: |
| return NULL; |
| } |
| } |
| |
| /* 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 (diagnostic_t diag_kind) |
| { |
| static const char *const diagnostic_kind_text[] = { |
| #define DEFINE_DIAGNOSTIC_KIND(K, T, C) (T), |
| #include "diagnostic.def" |
| #undef DEFINE_DIAGNOSTIC_KIND |
| "must-not-happen" |
| }; |
| /* Lose the trailing ": ". */ |
| const char *kind_text = diagnostic_kind_text[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. */ |
| |
| sarif_result * |
| sarif_builder::make_result_object (diagnostic_context *context, |
| const diagnostic_info &diagnostic, |
| diagnostic_t orig_diag_kind) |
| { |
| sarif_result *result_obj = new sarif_result (); |
| |
| /* "ruleId" property (SARIF v2.1.0 section 3.27.5). */ |
| /* Ideally we'd have an option_name for these. */ |
| if (char *option_text |
| = context->make_option_name (diagnostic.option_index, |
| orig_diag_kind, diagnostic.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); |
| |
| json::object *reporting_desc_obj |
| = make_reporting_descriptor_object_for_warning (context, |
| diagnostic, |
| orig_diag_kind, |
| option_text); |
| m_rules_arr->append (reporting_desc_obj); |
| } |
| } |
| 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.metadata) |
| { |
| /* "taxa" property (SARIF v2.1.0 section 3.27.8). */ |
| if (int cwe_id = diagnostic.metadata->get_cwe ()) |
| { |
| json::array *taxa_arr = new json::array (); |
| json::object *cwe_id_obj |
| = make_reporting_descriptor_reference_object_for_cwe_id (cwe_id); |
| taxa_arr->append (cwe_id_obj); |
| result_obj->set ("taxa", taxa_arr); |
| } |
| |
| diagnostic.metadata->maybe_add_sarif_properties (*result_obj); |
| } |
| |
| /* "level" property (SARIF v2.1.0 section 3.27.10). */ |
| if (const char *sarif_level = maybe_get_sarif_level (diagnostic.kind)) |
| result_obj->set_string ("level", sarif_level); |
| |
| /* "message" property (SARIF v2.1.0 section 3.27.11). */ |
| json::object *message_obj |
| = make_message_object (pp_formatted_text (context->printer)); |
| pp_clear_output_area (context->printer); |
| result_obj->set ("message", message_obj); |
| |
| /* "locations" property (SARIF v2.1.0 section 3.27.12). */ |
| json::array *locations_arr = make_locations_arr (diagnostic); |
| result_obj->set ("locations", locations_arr); |
| |
| /* "codeFlows" property (SARIF v2.1.0 section 3.27.18). */ |
| if (const diagnostic_path *path = diagnostic.richloc->get_path ()) |
| { |
| json::array *code_flows_arr = new json::array (); |
| json::object *code_flow_obj = make_code_flow_object (*path); |
| code_flows_arr->append (code_flow_obj); |
| result_obj->set ("codeFlows", code_flows_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.richloc; |
| if (richloc->get_num_fixit_hints ()) |
| { |
| json::array *fix_arr = new json::array (); |
| json::object *fix_obj = make_fix_object (*richloc); |
| fix_arr->append (fix_obj); |
| result_obj->set ("fixes", fix_arr); |
| } |
| |
| return result_obj; |
| } |
| |
| /* Make a reportingDescriptor object (SARIF v2.1.0 section 3.49) |
| for a GCC warning. */ |
| |
| json::object * |
| sarif_builder:: |
| make_reporting_descriptor_object_for_warning (diagnostic_context *context, |
| const diagnostic_info &diagnostic, |
| diagnostic_t /*orig_diag_kind*/, |
| const char *option_text) |
| { |
| json::object *reporting_desc = new json::object (); |
| |
| /* "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 = context->make_option_url (diagnostic.option_index)) |
| { |
| 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. */ |
| |
| json::object * |
| sarif_builder::make_reporting_descriptor_object_for_cwe_id (int cwe_id) const |
| { |
| json::object *reporting_desc = new json::object (); |
| |
| /* "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. */ |
| |
| json::object * |
| sarif_builder:: |
| make_reporting_descriptor_reference_object_for_cwe_id (int cwe_id) |
| { |
| json::object *desc_ref_obj = new json::object (); |
| |
| /* "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). */ |
| json::object *comp_ref_obj = make_tool_component_reference_object_for_cwe (); |
| desc_ref_obj->set ("toolComponent", comp_ref_obj); |
| |
| /* 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. */ |
| |
| json::object * |
| sarif_builder:: |
| make_tool_component_reference_object_for_cwe () const |
| { |
| json::object *comp_ref_obj = new json::object (); |
| |
| /* "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). */ |
| |
| json::array * |
| sarif_builder::make_locations_arr (const diagnostic_info &diagnostic) |
| { |
| json::array *locations_arr = new json::array (); |
| const logical_location *logical_loc = NULL; |
| if (auto client_data_hooks = m_context->get_client_data_hooks ()) |
| logical_loc = client_data_hooks->get_current_logical_location (); |
| |
| json::object *location_obj |
| = make_location_object (*diagnostic.richloc, logical_loc); |
| locations_arr->append (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). */ |
| |
| void |
| sarif_builder:: |
| set_any_logical_locs_arr (json::object *location_obj, |
| const logical_location *logical_loc) |
| { |
| if (!logical_loc) |
| return; |
| json::object *logical_loc_obj = make_logical_location_object (*logical_loc); |
| json::array *location_locs_arr = new json::array (); |
| location_locs_arr->append (logical_loc_obj); |
| location_obj->set ("logicalLocations", location_locs_arr); |
| } |
| |
| /* Make a location object (SARIF v2.1.0 section 3.28) for RICH_LOC |
| and LOGICAL_LOC. */ |
| |
| json::object * |
| sarif_builder::make_location_object (const rich_location &rich_loc, |
| const logical_location *logical_loc) |
| { |
| json::object *location_obj = new json::object (); |
| |
| /* Get primary loc from RICH_LOC. */ |
| location_t loc = rich_loc.get_loc (); |
| |
| /* "physicalLocation" property (SARIF v2.1.0 section 3.28.3). */ |
| if (json::object *phs_loc_obj = maybe_make_physical_location_object (loc)) |
| location_obj->set ("physicalLocation", phs_loc_obj); |
| |
| /* "logicalLocations" property (SARIF v2.1.0 section 3.28.4). */ |
| set_any_logical_locs_arr (location_obj, logical_loc); |
| |
| return location_obj; |
| } |
| |
| /* Make a location object (SARIF v2.1.0 section 3.28) for EVENT |
| within a diagnostic_path. */ |
| |
| json::object * |
| sarif_builder::make_location_object (const diagnostic_event &event) |
| { |
| json::object *location_obj = new json::object (); |
| |
| /* "physicalLocation" property (SARIF v2.1.0 section 3.28.3). */ |
| location_t loc = event.get_location (); |
| if (json::object *phs_loc_obj = maybe_make_physical_location_object (loc)) |
| location_obj->set ("physicalLocation", phs_loc_obj); |
| |
| /* "logicalLocations" property (SARIF v2.1.0 section 3.28.4). */ |
| const logical_location *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). */ |
| label_text ev_desc = event.get_desc (false); |
| json::object *message_obj = make_message_object (ev_desc.get ()); |
| location_obj->set ("message", message_obj); |
| |
| return location_obj; |
| } |
| |
| /* Make a physicalLocation object (SARIF v2.1.0 section 3.29) for LOC, |
| or return NULL; |
| Add any filename to the m_artifacts. */ |
| |
| json::object * |
| sarif_builder::maybe_make_physical_location_object (location_t loc) |
| { |
| if (loc <= BUILTINS_LOCATION || LOCATION_FILE (loc) == NULL) |
| return NULL; |
| |
| json::object *phys_loc_obj = new json::object (); |
| |
| /* "artifactLocation" property (SARIF v2.1.0 section 3.29.3). */ |
| json::object *artifact_loc_obj = make_artifact_location_object (loc); |
| phys_loc_obj->set ("artifactLocation", artifact_loc_obj); |
| m_filenames.add (LOCATION_FILE (loc)); |
| |
| /* "region" property (SARIF v2.1.0 section 3.29.4). */ |
| if (json::object *region_obj = maybe_make_region_object (loc)) |
| phys_loc_obj->set ("region", region_obj); |
| |
| /* "contextRegion" property (SARIF v2.1.0 section 3.29.5). */ |
| if (json::object *context_region_obj |
| = maybe_make_region_object_for_context (loc)) |
| phys_loc_obj->set ("contextRegion", 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 NULL. */ |
| |
| json::object * |
| 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 NULL. */ |
| |
| json::object * |
| sarif_builder::make_artifact_location_object (const char *filename) |
| { |
| json::object *artifact_loc_obj = new json::object (); |
| |
| /* "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 NULL, 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 NULL; |
| size_t len = strlen (pwd); |
| if (len == 0 || pwd[len - 1] != '/') |
| return concat (FILE_PREFIX, pwd, "/", NULL); |
| else |
| { |
| gcc_assert (pwd[len - 1] == '/'); |
| return concat (FILE_PREFIX, pwd, NULL); |
| } |
| } |
| |
| /* 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. */ |
| |
| json::object * |
| sarif_builder::make_artifact_location_object_for_pwd () const |
| { |
| json::object *artifact_loc_obj = new json::object (); |
| |
| /* "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 NULL. */ |
| |
| json::object * |
| sarif_builder::maybe_make_region_object (location_t loc) const |
| { |
| location_t caret_loc = get_pure_location (loc); |
| |
| if (caret_loc <= BUILTINS_LOCATION) |
| return NULL; |
| |
| 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 NULL; |
| if (exploc_finish.file !=exploc_caret.file) |
| return NULL; |
| |
| json::object *region_obj = new json::object (); |
| |
| /* "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) */ |
| region_obj->set_integer ("startColumn", get_sarif_column (exploc_start)); |
| |
| /* "endLine" property (SARIF v2.1.0 section 3.30.7) */ |
| if (exploc_finish.line != exploc_start.line) |
| 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. */ |
| { |
| int next_column = sarif_builder::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. */ |
| |
| json::object * |
| sarif_builder::maybe_make_region_object_for_context (location_t loc) const |
| { |
| location_t caret_loc = get_pure_location (loc); |
| |
| if (caret_loc <= BUILTINS_LOCATION) |
| return NULL; |
| |
| 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 NULL; |
| if (exploc_finish.file !=exploc_caret.file) |
| return NULL; |
| |
| json::object *region_obj = new json::object (); |
| |
| /* "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) |
| region_obj->set_integer ("endLine", exploc_finish.line); |
| |
| /* "snippet" property (SARIF v2.1.0 section 3.30.13). */ |
| if (json::object *artifact_content_obj |
| = maybe_make_artifact_content_object (exploc_start.file, |
| exploc_start.line, |
| exploc_finish.line)) |
| region_obj->set ("snippet", 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). */ |
| |
| json::object * |
| 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); |
| |
| json::object *region_obj = new json::object (); |
| |
| /* "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 NULL if unknown. */ |
| |
| static const char * |
| maybe_get_sarif_kind (enum logical_location_kind kind) |
| { |
| switch (kind) |
| { |
| default: |
| gcc_unreachable (); |
| case LOGICAL_LOCATION_KIND_UNKNOWN: |
| return NULL; |
| |
| case LOGICAL_LOCATION_KIND_FUNCTION: |
| return "function"; |
| case LOGICAL_LOCATION_KIND_MEMBER: |
| return "member"; |
| case LOGICAL_LOCATION_KIND_MODULE: |
| return "module"; |
| case LOGICAL_LOCATION_KIND_NAMESPACE: |
| return "namespace"; |
| case LOGICAL_LOCATION_KIND_TYPE: |
| return "type"; |
| case LOGICAL_LOCATION_KIND_RETURN_TYPE: |
| return "returnType"; |
| case LOGICAL_LOCATION_KIND_PARAMETER: |
| return "parameter"; |
| case LOGICAL_LOCATION_KIND_VARIABLE: |
| return "variable"; |
| } |
| } |
| |
| /* Make a logicalLocation object (SARIF v2.1.0 section 3.33) for LOGICAL_LOC, |
| or return NULL. */ |
| |
| json::object * |
| sarif_builder:: |
| make_logical_location_object (const logical_location &logical_loc) const |
| { |
| json::object *logical_loc_obj = new json::object (); |
| |
| /* "name" property (SARIF v2.1.0 section 3.33.4). */ |
| if (const char *short_name = logical_loc.get_short_name ()) |
| logical_loc_obj->set_string ("name", short_name); |
| |
| /* "fullyQualifiedName" property (SARIF v2.1.0 section 3.33.5). */ |
| if (const char *name_with_scope = logical_loc.get_name_with_scope ()) |
| logical_loc_obj->set_string ("fullyQualifiedName", name_with_scope); |
| |
| /* "decoratedName" property (SARIF v2.1.0 section 3.33.6). */ |
| if (const char *internal_name = logical_loc.get_internal_name ()) |
| logical_loc_obj->set_string ("decoratedName", internal_name); |
| |
| /* "kind" property (SARIF v2.1.0 section 3.33.7). */ |
| enum logical_location_kind kind = logical_loc.get_kind (); |
| if (const char *sarif_kind_str = maybe_get_sarif_kind (kind)) |
| logical_loc_obj->set_string ("kind", sarif_kind_str); |
| |
| return logical_loc_obj; |
| } |
| |
| /* Make a codeFlow object (SARIF v2.1.0 section 3.36) for PATH. */ |
| |
| json::object * |
| sarif_builder::make_code_flow_object (const diagnostic_path &path) |
| { |
| json::object *code_flow_obj = new json::object (); |
| |
| /* "threadFlows" property (SARIF v2.1.0 section 3.36.3). */ |
| json::array *thread_flows_arr = new json::array (); |
| |
| /* Walk the events, consolidating into per-thread threadFlow objects, |
| using the index with PATH as the overall executionOrder. */ |
| hash_map<int_hash<diagnostic_thread_id_t, -1, -2>, |
| sarif_thread_flow *> thread_id_map; |
| for (unsigned i = 0; i < path.num_events (); i++) |
| { |
| const diagnostic_event &event = path.get_event (i); |
| const diagnostic_thread_id_t thread_id = event.get_thread_id (); |
| sarif_thread_flow *thread_flow_obj; |
| |
| if (sarif_thread_flow **slot = thread_id_map.get (thread_id)) |
| thread_flow_obj = *slot; |
| else |
| { |
| const diagnostic_thread &thread = path.get_thread (thread_id); |
| thread_flow_obj = new sarif_thread_flow (thread); |
| thread_flows_arr->append (thread_flow_obj); |
| thread_id_map.put (thread_id, thread_flow_obj); |
| } |
| |
| /* Add event to thread's threadFlow object. */ |
| json::object *thread_flow_loc_obj |
| = make_thread_flow_location_object (event, i); |
| thread_flow_obj->add_location (thread_flow_loc_obj); |
| } |
| code_flow_obj->set ("threadFlows", thread_flows_arr); |
| |
| return code_flow_obj; |
| } |
| |
| /* Make a threadFlowLocation object (SARIF v2.1.0 section 3.38) for EVENT. */ |
| |
| json::object * |
| sarif_builder::make_thread_flow_location_object (const diagnostic_event &ev, |
| int path_event_idx) |
| { |
| json::object *thread_flow_loc_obj = new json::object (); |
| |
| /* "location" property (SARIF v2.1.0 section 3.38.3). */ |
| json::object *location_obj = make_location_object (ev); |
| thread_flow_loc_obj->set ("location", location_obj); |
| |
| /* "kinds" property (SARIF v2.1.0 section 3.38.8). */ |
| diagnostic_event::meaning m = ev.get_meaning (); |
| if (json::array *kinds_arr = maybe_make_kinds_array (m)) |
| thread_flow_loc_obj->set ("kinds", kinds_arr); |
| |
| /* "nestingLevel" property (SARIF v2.1.0 section 3.38.10). */ |
| thread_flow_loc_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 %@. */ |
| thread_flow_loc_obj->set_integer ("executionOrder", path_event_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). */ |
| |
| return thread_flow_loc_obj; |
| } |
| |
| /* 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 NULL. */ |
| |
| json::array * |
| sarif_builder::maybe_make_kinds_array (diagnostic_event::meaning m) const |
| { |
| if (m.m_verb == diagnostic_event::VERB_unknown |
| && m.m_noun == diagnostic_event::NOUN_unknown |
| && m.m_property == diagnostic_event::PROPERTY_unknown) |
| return NULL; |
| |
| json::array *kinds_arr = new json::array (); |
| if (const char *verb_str |
| = diagnostic_event::meaning::maybe_get_verb_str (m.m_verb)) |
| kinds_arr->append (new json::string (verb_str)); |
| if (const char *noun_str |
| = diagnostic_event::meaning::maybe_get_noun_str (m.m_noun)) |
| kinds_arr->append (new json::string (noun_str)); |
| if (const char *property_str |
| = diagnostic_event::meaning::maybe_get_property_str (m.m_property)) |
| kinds_arr->append (new json::string (property_str)); |
| return kinds_arr; |
| } |
| |
| /* Make a message object (SARIF v2.1.0 section 3.11) for MSG. */ |
| |
| json::object * |
| sarif_builder::make_message_object (const char *msg) const |
| { |
| json::object *message_obj = new json::object (); |
| |
| /* "text" property (SARIF v2.1.0 section 3.11.8). */ |
| message_obj->set_string ("text", msg); |
| |
| return message_obj; |
| } |
| |
| /* Make a message object (SARIF v2.1.0 section 3.11) for DIAGRAM. |
| We emit the diagram as a code block within the Markdown part |
| of the message. */ |
| |
| json::object * |
| sarif_builder::make_message_object_for_diagram (diagnostic_context *context, |
| const diagnostic_diagram &diagram) |
| { |
| json::object *message_obj = new json::object (); |
| |
| /* "text" property (SARIF v2.1.0 section 3.11.8). */ |
| message_obj->set_string ("text", diagram.get_alt_text ()); |
| |
| char *saved_prefix = pp_take_prefix (context->printer); |
| pp_set_prefix (context->printer, NULL); |
| |
| /* "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. */ |
| diagram.get_canvas ().print_to_pp (context->printer, " "); |
| pp_set_prefix (context->printer, saved_prefix); |
| |
| /* "markdown" property (SARIF v2.1.0 section 3.11.9). */ |
| message_obj->set_string ("markdown", pp_formatted_text (context->printer)); |
| |
| pp_clear_output_area (context->printer); |
| |
| return message_obj; |
| } |
| |
| /* Make a multiformatMessageString object (SARIF v2.1.0 section 3.12) |
| for MSG. */ |
| |
| json::object * |
| sarif_builder::make_multiformat_message_string (const char *msg) const |
| { |
| json::object *message_obj = new json::object (); |
| |
| /* "text" property (SARIF v2.1.0 section 3.12.3). */ |
| message_obj->set_string ("text", msg); |
| |
| return message_obj; |
| } |
| |
| #define SARIF_SCHEMA "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json" |
| #define SARIF_VERSION "2.1.0" |
| |
| /* Make a top-level sarifLog object (SARIF v2.1.0 section 3.13). |
| Take ownership of INVOCATION_OBJ and RESULTS. */ |
| |
| json::object * |
| sarif_builder::make_top_level_object (sarif_invocation *invocation_obj, |
| json::array *results) |
| { |
| json::object *log_obj = new json::object (); |
| |
| /* "$schema" property (SARIF v2.1.0 section 3.13.3) . */ |
| log_obj->set_string ("$schema", SARIF_SCHEMA); |
| |
| /* "version" property (SARIF v2.1.0 section 3.13.2). */ |
| log_obj->set_string ("version", SARIF_VERSION); |
| |
| /* "runs" property (SARIF v2.1.0 section 3.13.4). */ |
| json::array *run_arr = new json::array (); |
| json::object *run_obj = make_run_object (invocation_obj, results); |
| run_arr->append (run_obj); |
| log_obj->set ("runs", run_arr); |
| |
| return log_obj; |
| } |
| |
| /* Make a run object (SARIF v2.1.0 section 3.14). |
| Take ownership of INVOCATION_OBJ and RESULTS. */ |
| |
| json::object * |
| sarif_builder::make_run_object (sarif_invocation *invocation_obj, |
| json::array *results) |
| { |
| json::object *run_obj = new json::object (); |
| |
| /* "tool" property (SARIF v2.1.0 section 3.14.6). */ |
| json::object *tool_obj = make_tool_object (); |
| run_obj->set ("tool", tool_obj); |
| |
| /* "taxonomies" property (SARIF v2.1.0 section 3.14.8). */ |
| if (json::array *taxonomies_arr = maybe_make_taxonomies_array ()) |
| run_obj->set ("taxonomies", taxonomies_arr); |
| |
| /* "invocations" property (SARIF v2.1.0 section 3.14.11). */ |
| { |
| json::array *invocations_arr = new json::array (); |
| invocations_arr->append (invocation_obj); |
| run_obj->set ("invocations", invocations_arr); |
| } |
| |
| /* "originalUriBaseIds (SARIF v2.1.0 section 3.14.14). */ |
| if (m_seen_any_relative_paths) |
| { |
| json::object *orig_uri_base_ids = new json::object (); |
| run_obj->set ("originalUriBaseIds", orig_uri_base_ids); |
| json::object *pwd_art_loc_obj = make_artifact_location_object_for_pwd (); |
| orig_uri_base_ids->set (PWD_PROPERTY_NAME, pwd_art_loc_obj); |
| } |
| |
| /* "artifacts" property (SARIF v2.1.0 section 3.14.15). */ |
| json::array *artifacts_arr = new json::array (); |
| for (auto iter : m_filenames) |
| { |
| json::object *artifact_obj = make_artifact_object (iter); |
| artifacts_arr->append (artifact_obj); |
| } |
| run_obj->set ("artifacts", artifacts_arr); |
| |
| /* "results" property (SARIF v2.1.0 section 3.14.23). */ |
| run_obj->set ("results", results); |
| |
| return run_obj; |
| } |
| |
| /* Make a tool object (SARIF v2.1.0 section 3.18). */ |
| |
| json::object * |
| sarif_builder::make_tool_object () const |
| { |
| json::object *tool_obj = new json::object (); |
| |
| /* "driver" property (SARIF v2.1.0 section 3.18.2). */ |
| json::object *driver_obj = make_driver_tool_component_object (); |
| tool_obj->set ("driver", driver_obj); |
| |
| /* 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 diagnostic_client_plugin_info &p) final override |
| { |
| /* Create a toolComponent object (SARIF v2.1.0 section 3.19) |
| for the plugin. */ |
| json::object *plugin_obj = new json::object (); |
| m_plugin_objs.safe_push (plugin_obj); |
| |
| /* "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); |
| } |
| auto_vec <json::object *> m_plugin_objs; |
| }; |
| my_plugin_visitor v; |
| vinfo->for_each_plugin (v); |
| if (v.m_plugin_objs.length () > 0) |
| { |
| json::array *extensions_arr = new json::array (); |
| tool_obj->set ("extensions", extensions_arr); |
| for (auto iter : v.m_plugin_objs) |
| extensions_arr->append (iter); |
| } |
| } |
| |
| /* 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). */ |
| |
| json::object * |
| sarif_builder::make_driver_tool_component_object () const |
| { |
| json::object *driver_obj = new json::object (); |
| |
| 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 ("rules", 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, containting a singl |
| toolComponent (3.19) as per 3.19.3, representing the CWE. |
| |
| Otherwise return NULL. */ |
| |
| json::array * |
| sarif_builder::maybe_make_taxonomies_array () const |
| { |
| json::object *cwe_obj = maybe_make_cwe_taxonomy_object (); |
| if (!cwe_obj) |
| return NULL; |
| |
| /* "taxonomies" property (SARIF v2.1.0 section 3.14.8). */ |
| json::array *taxonomies_arr = new json::array (); |
| taxonomies_arr->append (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 NULL. */ |
| |
| json::object * |
| sarif_builder::maybe_make_cwe_taxonomy_object () const |
| { |
| if (m_cwe_id_set.is_empty ()) |
| return NULL; |
| |
| json::object *taxonomy_obj = new json::object (); |
| |
| /* "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). */ |
| json::object *short_desc |
| = make_multiformat_message_string ("The MITRE" |
| " Common Weakness Enumeration"); |
| taxonomy_obj->set ("shortDescription", short_desc); |
| |
| /* "taxa" property (SARIF v2.1.0 3.section 3.19.25). */ |
| json::array *taxa_arr = new json::array (); |
| for (auto cwe_id : m_cwe_id_set) |
| { |
| json::object *cwe_taxon |
| = make_reporting_descriptor_object_for_cwe_id (cwe_id); |
| taxa_arr->append (cwe_taxon); |
| } |
| taxonomy_obj->set ("taxa", taxa_arr); |
| |
| return taxonomy_obj; |
| } |
| |
| /* Make an artifact object (SARIF v2.1.0 section 3.24). */ |
| |
| json::object * |
| sarif_builder::make_artifact_object (const char *filename) |
| { |
| json::object *artifact_obj = new json::object (); |
| |
| /* "location" property (SARIF v2.1.0 section 3.24.2). */ |
| json::object *artifact_loc_obj = make_artifact_location_object (filename); |
| artifact_obj->set ("location", artifact_loc_obj); |
| |
| /* "contents" property (SARIF v2.1.0 section 3.24.8). */ |
| if (json::object *artifact_content_obj |
| = maybe_make_artifact_content_object (filename)) |
| artifact_obj->set ("contents", artifact_content_obj); |
| |
| /* "sourceLanguage" property (SARIF v2.1.0 section 3.24.10). */ |
| 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); |
| |
| return artifact_obj; |
| } |
| |
| /* Make an artifactContent object (SARIF v2.1.0 section 3.3) for the |
| full contents of FILENAME. */ |
| |
| json::object * |
| 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 NULL; |
| |
| /* Don't add it if it's not valid UTF-8. */ |
| if (!cpp_valid_utf8_p(utf8_content.get_buffer (), utf8_content.length ())) |
| return NULL; |
| |
| json::object *artifact_content_obj = new json::object (); |
| artifact_content_obj->set ("text", |
| new 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 NULL. */ |
| |
| 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 NULL; |
| 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). */ |
| |
| json::object * |
| sarif_builder::maybe_make_artifact_content_object (const char *filename, |
| int start_line, |
| int end_line) const |
| { |
| char *text_utf8 = get_source_lines (filename, start_line, end_line); |
| |
| if (!text_utf8) |
| return NULL; |
| |
| /* 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 NULL; |
| } |
| |
| json::object *artifact_content_obj = new json::object (); |
| artifact_content_obj->set_string ("text", text_utf8); |
| free (text_utf8); |
| |
| return artifact_content_obj; |
| } |
| |
| /* Make a fix object (SARIF v2.1.0 section 3.55) for RICHLOC. */ |
| |
| json::object * |
| sarif_builder::make_fix_object (const rich_location &richloc) |
| { |
| json::object *fix_obj = new json::object (); |
| |
| /* "artifactChanges" property (SARIF v2.1.0 section 3.55.3). */ |
| /* We assume that all fix-it hints in RICHLOC affect the same file. */ |
| json::array *artifact_change_arr = new json::array (); |
| json::object *artifact_change_obj = make_artifact_change_object (richloc); |
| artifact_change_arr->append (artifact_change_obj); |
| fix_obj->set ("artifactChanges", artifact_change_arr); |
| |
| return fix_obj; |
| } |
| |
| /* Make an artifactChange object (SARIF v2.1.0 section 3.56) for RICHLOC. */ |
| |
| json::object * |
| sarif_builder::make_artifact_change_object (const rich_location &richloc) |
| { |
| json::object *artifact_change_obj = new json::object (); |
| |
| /* "artifactLocation" property (SARIF v2.1.0 section 3.56.2). */ |
| json::object *artifact_location_obj |
| = make_artifact_location_object (richloc.get_loc ()); |
| artifact_change_obj->set ("artifactLocation", artifact_location_obj); |
| |
| /* "replacements" property (SARIF v2.1.0 section 3.56.3). */ |
| json::array *replacement_arr = new json::array (); |
| for (unsigned int i = 0; i < richloc.get_num_fixit_hints (); i++) |
| { |
| const fixit_hint *hint = richloc.get_fixit_hint (i); |
| json::object *replacement_obj = make_replacement_object (*hint); |
| replacement_arr->append (replacement_obj); |
| } |
| artifact_change_obj->set ("replacements", replacement_arr); |
| |
| return artifact_change_obj; |
| } |
| |
| /* Make a replacement object (SARIF v2.1.0 section 3.57) for HINT. */ |
| |
| json::object * |
| sarif_builder::make_replacement_object (const fixit_hint &hint) const |
| { |
| json::object *replacement_obj = new json::object (); |
| |
| /* "deletedRegion" property (SARIF v2.1.0 section 3.57.3). */ |
| json::object *region_obj = make_region_object_for_hint (hint); |
| replacement_obj->set ("deletedRegion", region_obj); |
| |
| /* "insertedContent" property (SARIF v2.1.0 section 3.57.4). */ |
| json::object *content_obj = make_artifact_content_object (hint.get_string ()); |
| replacement_obj->set ("insertedContent", content_obj); |
| |
| return replacement_obj; |
| } |
| |
| /* Make an artifactContent object (SARIF v2.1.0 section 3.3) for TEXT. */ |
| |
| json::object * |
| sarif_builder::make_artifact_content_object (const char *text) const |
| { |
| json::object *content_obj = new json::object (); |
| |
| /* "text" property (SARIF v2.1.0 section 3.3.2). */ |
| content_obj->set_string ("text", text); |
| |
| return content_obj; |
| } |
| |
| /* Callback for diagnostic_context::ice_handler_cb for when an ICE |
| occurs. */ |
| |
| static void |
| sarif_ice_handler (diagnostic_context *context) |
| { |
| /* Attempt to ensure that a .sarif file is written out. */ |
| diagnostic_finish (context); |
| |
| /* 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"); |
| } |
| |
| class sarif_output_format : public diagnostic_output_format |
| { |
| public: |
| void on_begin_group () final override |
| { |
| /* No-op, */ |
| } |
| void on_end_group () final override |
| { |
| m_builder.end_group (); |
| } |
| void |
| on_begin_diagnostic (const diagnostic_info &) final override |
| { |
| /* No-op, */ |
| } |
| void |
| on_end_diagnostic (const diagnostic_info &diagnostic, |
| diagnostic_t orig_diag_kind) final override |
| { |
| m_builder.end_diagnostic (&m_context, diagnostic, orig_diag_kind); |
| } |
| void on_diagram (const diagnostic_diagram &diagram) final override |
| { |
| m_builder.emit_diagram (&m_context, diagram); |
| } |
| |
| protected: |
| sarif_output_format (diagnostic_context &context, |
| bool formatted) |
| : diagnostic_output_format (context), |
| m_builder (&context, formatted) |
| {} |
| |
| sarif_builder m_builder; |
| }; |
| |
| class sarif_stream_output_format : public sarif_output_format |
| { |
| public: |
| sarif_stream_output_format (diagnostic_context &context, |
| bool formatted, |
| FILE *stream) |
| : sarif_output_format (context, formatted), |
| m_stream (stream) |
| { |
| } |
| ~sarif_stream_output_format () |
| { |
| m_builder.flush_to_file (m_stream); |
| } |
| private: |
| FILE *m_stream; |
| }; |
| |
| class sarif_file_output_format : public sarif_output_format |
| { |
| public: |
| sarif_file_output_format (diagnostic_context &context, |
| bool formatted, |
| const char *base_file_name) |
| : sarif_output_format (context, formatted), |
| m_base_file_name (xstrdup (base_file_name)) |
| { |
| } |
| ~sarif_file_output_format () |
| { |
| char *filename = concat (m_base_file_name, ".sarif", NULL); |
| free (m_base_file_name); |
| m_base_file_name = nullptr; |
| FILE *outf = fopen (filename, "w"); |
| if (!outf) |
| { |
| const char *errstr = xstrerror (errno); |
| fnotice (stderr, "error: unable to open '%s' for writing: %s\n", |
| filename, errstr); |
| free (filename); |
| return; |
| } |
| m_builder.flush_to_file (outf); |
| fclose (outf); |
| free (filename); |
| } |
| |
| private: |
| char *m_base_file_name; |
| }; |
| |
| /* Populate CONTEXT in preparation for SARIF output (either to stderr, or |
| to a file). */ |
| |
| static void |
| diagnostic_output_format_init_sarif (diagnostic_context *context) |
| { |
| /* Override callbacks. */ |
| context->m_print_path = nullptr; /* handled in sarif_end_diagnostic. */ |
| context->set_ice_handler_callback (sarif_ice_handler); |
| |
| /* The metadata is handled in SARIF format, rather than as text. */ |
| context->set_show_cwe (false); |
| context->set_show_rules (false); |
| |
| /* The option is handled in SARIF format, rather than as text. */ |
| context->set_show_option_requested (false); |
| |
| /* Don't colorize the text. */ |
| pp_show_color (context->printer) = false; |
| } |
| |
| /* Populate CONTEXT in preparation for SARIF output to stderr. */ |
| |
| void |
| diagnostic_output_format_init_sarif_stderr (diagnostic_context *context, |
| bool formatted) |
| { |
| diagnostic_output_format_init_sarif (context); |
| context->set_output_format (new sarif_stream_output_format (*context, |
| formatted, |
| stderr)); |
| } |
| |
| /* Populate CONTEXT in preparation for SARIF output to a file named |
| BASE_FILE_NAME.sarif. */ |
| |
| void |
| diagnostic_output_format_init_sarif_file (diagnostic_context *context, |
| bool formatted, |
| const char *base_file_name) |
| { |
| diagnostic_output_format_init_sarif (context); |
| context->set_output_format (new sarif_file_output_format (*context, |
| formatted, |
| base_file_name)); |
| } |
| |
| /* Populate CONTEXT in preparation for SARIF output to STREAM. */ |
| |
| void |
| diagnostic_output_format_init_sarif_stream (diagnostic_context *context, |
| bool formatted, |
| FILE *stream) |
| { |
| diagnostic_output_format_init_sarif (context); |
| context->set_output_format (new sarif_stream_output_format (*context, |
| formatted, |
| stream)); |
| } |