blob: 97c5943cd3397e45ad075f7d2638e88dfc5bdd5f [file] [log] [blame]
/* SARIF output for diagnostics
Copyright (C) 2018-2024 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_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_sarif_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 *
make_sarif_logical_location_object (const logical_location &logical_loc)
{
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)
{
sarif_object *thread_flow_loc_obj = new sarif_object ();
/* Give diagnostic_event subclasses a chance to add custom properties
via a property bag. */
ev.maybe_add_sarif_properties (*thread_flow_loc_obj);
/* "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);
}
bool machine_readable_stderr_p () const final override
{
return m_stream == stderr;
}
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);
}
bool machine_readable_stderr_p () const final override
{
return false;
}
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));
}