| /* SARIF output for diagnostics |
| Copyright (C) 2018-2022 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" |
| #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" |
| |
| class sarif_builder; |
| |
| /* Subclass of json::object for SARIF result objects |
| (SARIF v2.1.0 section 3.27. */ |
| |
| class sarif_result : public json::object |
| { |
| public: |
| sarif_result () : m_related_locations_arr (NULL) {} |
| |
| void |
| on_nested_diagnostic (diagnostic_context *context, |
| diagnostic_info *diagnostic, |
| diagnostic_t orig_diag_kind, |
| sarif_builder *builder); |
| |
| private: |
| json::array *m_related_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); |
| |
| void end_diagnostic (diagnostic_context *context, diagnostic_info *diagnostic, |
| diagnostic_t orig_diag_kind); |
| |
| void end_group (); |
| |
| void flush_to_file (FILE *outf); |
| |
| json::object *make_location_object (const rich_location &rich_loc, |
| const logical_location *logical_loc); |
| json::object *make_message_object (const char *msg) const; |
| |
| private: |
| sarif_result *make_result_object (diagnostic_context *context, |
| 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_object (const diagnostic_path &path); |
| json::object * |
| make_thread_flow_location_object (const diagnostic_event &event); |
| 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 (json::array *results); |
| json::object *make_run_object (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, |
| 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); |
| 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 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; |
| }; |
| |
| static sarif_builder *the_builder; |
| |
| /* class sarif_result : public json::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, |
| diagnostic_info *diagnostic, |
| diagnostic_t /*orig_diag_kind*/, |
| sarif_builder *builder) |
| { |
| if (!m_related_locations_arr) |
| { |
| m_related_locations_arr = new json::array (); |
| set ("relatedLocations", m_related_locations_arr); |
| } |
| |
| /* 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); |
| |
| m_related_locations_arr->append (location_obj); |
| } |
| |
| /* class sarif_builder. */ |
| |
| /* sarif_builder's ctor. */ |
| |
| sarif_builder::sarif_builder (diagnostic_context *context) |
| : m_context (context), |
| 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->tabstop) |
| { |
| } |
| |
| /* Implementation of "end_diagnostic" for SARIF output. */ |
| |
| void |
| sarif_builder::end_diagnostic (diagnostic_context *context, |
| diagnostic_info *diagnostic, |
| diagnostic_t orig_diag_kind) |
| { |
| |
| 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 "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) |
| { |
| json::object *top = make_top_level_object (m_results_array); |
| top->dump (outf); |
| 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, |
| 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->option_name (context, 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 ("ruleId", new json::string (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 ("ruleId", new json::string (rule_id)); |
| free (rule_id); |
| } |
| |
| /* "taxa" property (SARIF v2.1.0 section 3.27.8). */ |
| if (diagnostic->metadata) |
| 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); |
| } |
| |
| /* "level" property (SARIF v2.1.0 section 3.27.10). */ |
| if (const char *sarif_level = maybe_get_sarif_level (diagnostic->kind)) |
| result_obj->set ("level", new json::string (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 = new json::array (); |
| const logical_location *logical_loc = NULL; |
| if (m_context->m_client_data_hooks) |
| logical_loc |
| = m_context->m_client_data_hooks->get_current_logical_location (); |
| |
| json::object *location_obj |
| = make_location_object (*diagnostic->richloc, logical_loc); |
| locations_arr->append (location_obj); |
| 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, |
| 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 ("id", new json::string (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 (context->get_option_url) |
| { |
| char *option_url |
| = context->get_option_url (context, diagnostic->option_index); |
| if (option_url) |
| { |
| reporting_desc->set ("helpUri", new json::string (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 ("id", new json::string (pp_formatted_text (&pp))); |
| } |
| |
| /* "helpUri" property (SARIF v2.1.0 section 3.49.12). */ |
| { |
| char *url = get_cwe_url (cwe_id); |
| reporting_desc->set ("helpUri", new json::string (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 ("id", new json::string (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 ("name", new json::string ("cwe")); |
| |
| return comp_ref_obj; |
| } |
| |
| /* 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 ("uri", new json::string (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 ("uriBaseId", new json::string (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 ("uri", new json::string (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 (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 ("startLine", new json::integer_number (exploc_start.line)); |
| |
| /* "startColumn" property (SARIF v2.1.0 section 3.30.6) */ |
| region_obj->set ("startColumn", |
| new json::integer_number (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 ("endLine", new json::integer_number (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 ("endColumn", new json::integer_number (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 ("startLine", new json::integer_number (exploc_start.line)); |
| |
| /* "endLine" property (SARIF v2.1.0 section 3.30.7) */ |
| if (exploc_finish.line != exploc_start.line) |
| region_obj->set ("endLine", new json::integer_number (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 ("startLine", new json::integer_number (exploc_start.line)); |
| |
| /* "startColumn" property (SARIF v2.1.0 section 3.30.6) */ |
| int start_col = get_sarif_column (exploc_start); |
| region_obj->set ("startColumn", |
| new json::integer_number (start_col)); |
| |
| /* "endLine" property (SARIF v2.1.0 section 3.30.7) */ |
| if (exploc_next.line != exploc_start.line) |
| region_obj->set ("endLine", new json::integer_number (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 ("endColumn", new json::integer_number (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 ("name", new json::string (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 ("fullyQualifiedName", |
| new json::string (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 ("decoratedName", new json::string (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 ("kind", new json::string (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). |
| Currently we only support one thread per result. */ |
| json::array *thread_flows_arr = new json::array (); |
| json::object *thread_flow_obj = make_thread_flow_object (path); |
| thread_flows_arr->append (thread_flow_obj); |
| code_flow_obj->set ("threadFlows", thread_flows_arr); |
| |
| return code_flow_obj; |
| } |
| |
| /* Make a threadFlow object (SARIF v2.1.0 section 3.37) for PATH. */ |
| |
| json::object * |
| sarif_builder::make_thread_flow_object (const diagnostic_path &path) |
| { |
| json::object *thread_flow_obj = new json::object (); |
| |
| /* "locations" property (SARIF v2.1.0 section 3.37.6). */ |
| json::array *locations_arr = new json::array (); |
| for (unsigned i = 0; i < path.num_events (); i++) |
| { |
| const diagnostic_event &event = path.get_event (i); |
| json::object *thread_flow_loc_obj |
| = make_thread_flow_location_object (event); |
| locations_arr->append (thread_flow_loc_obj); |
| } |
| thread_flow_obj->set ("locations", locations_arr); |
| |
| return thread_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) |
| { |
| 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 ("nestingLevel", |
| new json::integer_number (ev.get_stack_depth ())); |
| |
| /* 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 ("text", new json::string (msg)); |
| |
| 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 ("text", new json::string (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 RESULTS. */ |
| |
| json::object * |
| sarif_builder::make_top_level_object (json::array *results) |
| { |
| json::object *log_obj = new json::object (); |
| |
| /* "$schema" property (SARIF v2.1.0 section 3.13.3) . */ |
| log_obj->set ("$schema", new json::string (SARIF_SCHEMA)); |
| |
| /* "version" property (SARIF v2.1.0 section 3.13.2). */ |
| log_obj->set ("version", new json::string (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 (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 RESULTS. */ |
| |
| json::object * |
| sarif_builder::make_run_object (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); |
| |
| /* "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 (m_context->m_client_data_hooks) |
| if (const client_version_info *vinfo |
| = m_context->m_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 ("name", new json::string (short_name)); |
| |
| /* "fullName" property (SARIF v2.1.0 section 3.19.9). */ |
| if (const char *full_name = p.get_full_name ()) |
| plugin_obj->set ("fullName", new json::string (full_name)); |
| |
| /* "version" property (SARIF v2.1.0 section 3.19.13). */ |
| if (const char *version = p.get_version ()) |
| plugin_obj->set ("version", new json::string (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 (m_context->m_client_data_hooks) |
| if (const client_version_info *vinfo |
| = m_context->m_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 ("name", new json::string (name)); |
| |
| /* "fullName" property (SARIF v2.1.0 section 3.19.9). */ |
| if (char *full_name = vinfo->maybe_make_full_name ()) |
| { |
| driver_obj->set ("fullName", new json::string (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 ("version", new json::string (version)); |
| |
| /* "informationUri" property (SARIF v2.1.0 section 3.19.17). */ |
| if (char *version_url = vinfo->maybe_make_version_url ()) |
| { |
| driver_obj->set ("informationUri", new json::string (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 ("name", new json::string ("CWE")); |
| |
| /* "version" property (SARIF v2.1.0 section 3.19.13). */ |
| taxonomy_obj->set ("version", new json::string ("4.7")); |
| |
| /* "organization" property (SARIF v2.1.0 section 3.19.18). */ |
| taxonomy_obj->set ("organization", new json::string ("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 (m_context->m_client_data_hooks) |
| if (const char *source_lang |
| = m_context->m_client_data_hooks->maybe_get_sarif_source_language |
| (filename)) |
| artifact_obj->set ("sourceLanguage", new json::string (source_lang)); |
| |
| return artifact_obj; |
| } |
| |
| /* Read all data from F_IN until EOF. |
| Return a NULL-terminated buffer containing the data, which must be |
| freed by the caller. |
| Return NULL on errors. */ |
| |
| static char * |
| read_until_eof (FILE *f_in) |
| { |
| /* Read content, allocating a buffer for it. */ |
| char *result = NULL; |
| size_t total_sz = 0; |
| size_t alloc_sz = 0; |
| char buf[4096]; |
| size_t iter_sz_in; |
| |
| while ( (iter_sz_in = fread (buf, 1, sizeof (buf), f_in)) ) |
| { |
| gcc_assert (alloc_sz >= total_sz); |
| size_t old_total_sz = total_sz; |
| total_sz += iter_sz_in; |
| /* Allow 1 extra byte for 0-termination. */ |
| if (alloc_sz < (total_sz + 1)) |
| { |
| size_t new_alloc_sz = alloc_sz ? alloc_sz * 2: total_sz + 1; |
| result = (char *)xrealloc (result, new_alloc_sz); |
| alloc_sz = new_alloc_sz; |
| } |
| memcpy (result + old_total_sz, buf, iter_sz_in); |
| } |
| |
| if (!feof (f_in)) |
| return NULL; |
| |
| /* 0-terminate the buffer. */ |
| gcc_assert (total_sz < alloc_sz); |
| result[total_sz] = '\0'; |
| |
| return result; |
| } |
| |
| /* Read all data from FILENAME until EOF. |
| Return a NULL-terminated buffer containing the data, which must be |
| freed by the caller. |
| Return NULL on errors. */ |
| |
| static char * |
| maybe_read_file (const char *filename) |
| { |
| FILE *f_in = fopen (filename, "r"); |
| if (!f_in) |
| return NULL; |
| char *result = read_until_eof (f_in); |
| fclose (f_in); |
| return result; |
| } |
| |
| /* 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 |
| { |
| char *text_utf8 = maybe_read_file (filename); |
| if (!text_utf8) |
| return NULL; |
| |
| json::object *artifact_content_obj = new json::object (); |
| artifact_content_obj->set ("text", new json::string (text_utf8)); |
| free (text_utf8); |
| |
| 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. */ |
| |
| static char * |
| get_source_lines (const char *filename, |
| int start_line, |
| int end_line) |
| { |
| auto_vec<char> result; |
| |
| for (int line = start_line; line <= end_line; line++) |
| { |
| char_span line_content = location_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; |
| |
| json::object *artifact_content_obj = new json::object (); |
| artifact_content_obj->set ("text", new json::string (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 ("text", new json::string (text)); |
| |
| return content_obj; |
| } |
| |
| /* No-op implementation of "begin_diagnostic" for SARIF output. */ |
| |
| static void |
| sarif_begin_diagnostic (diagnostic_context *, diagnostic_info *) |
| { |
| } |
| |
| /* Implementation of "end_diagnostic" for SARIF output. */ |
| |
| static void |
| sarif_end_diagnostic (diagnostic_context *context, diagnostic_info *diagnostic, |
| diagnostic_t orig_diag_kind) |
| { |
| gcc_assert (the_builder); |
| the_builder->end_diagnostic (context, diagnostic, orig_diag_kind); |
| } |
| |
| /* No-op implementation of "begin_group_cb" for SARIF output. */ |
| |
| static void |
| sarif_begin_group (diagnostic_context *) |
| { |
| } |
| |
| /* Implementation of "end_group_cb" for SARIF output. */ |
| |
| static void |
| sarif_end_group (diagnostic_context *) |
| { |
| gcc_assert (the_builder); |
| the_builder->end_group (); |
| } |
| |
| /* Flush the top-level array to OUTF. */ |
| |
| static void |
| sarif_flush_to_file (FILE *outf) |
| { |
| gcc_assert (the_builder); |
| the_builder->flush_to_file (outf); |
| delete the_builder; |
| the_builder = NULL; |
| } |
| |
| /* Callback for final cleanup for SARIF output to stderr. */ |
| |
| static void |
| sarif_stderr_final_cb (diagnostic_context *) |
| { |
| gcc_assert (the_builder); |
| sarif_flush_to_file (stderr); |
| } |
| |
| static char *sarif_output_base_file_name; |
| |
| /* Callback for final cleanup for SARIF output to a file. */ |
| |
| static void |
| sarif_file_final_cb (diagnostic_context *) |
| { |
| char *filename = concat (sarif_output_base_file_name, ".sarif", NULL); |
| 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; |
| } |
| gcc_assert (the_builder); |
| sarif_flush_to_file (outf); |
| fclose (outf); |
| free (filename); |
| } |
| |
| /* Populate CONTEXT in preparation for SARIF output (either to stderr, or |
| to a file). */ |
| |
| static void |
| diagnostic_output_format_init_sarif (diagnostic_context *context) |
| { |
| the_builder = new sarif_builder (context); |
| |
| /* Override callbacks. */ |
| context->begin_diagnostic = sarif_begin_diagnostic; |
| context->end_diagnostic = sarif_end_diagnostic; |
| context->begin_group_cb = sarif_begin_group; |
| context->end_group_cb = sarif_end_group; |
| context->print_path = NULL; /* handled in sarif_end_diagnostic. */ |
| |
| /* The metadata is handled in SARIF format, rather than as text. */ |
| context->show_cwe = false; |
| context->show_rules = false; |
| |
| /* The option is handled in SARIF format, rather than as text. */ |
| context->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) |
| { |
| diagnostic_output_format_init_sarif (context); |
| context->final_cb = sarif_stderr_final_cb; |
| } |
| |
| /* 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, |
| const char *base_file_name) |
| { |
| diagnostic_output_format_init_sarif (context); |
| context->final_cb = sarif_file_final_cb; |
| sarif_output_base_file_name = xstrdup (base_file_name); |
| } |