mirror of
https://forge.sourceware.org/marek/gcc.git
synced 2026-02-21 19:35:36 -05:00
This patch adds a new key/value pair "cfgs={yes,no}" to diagnostics
sinks, "no" by default.
If set to "yes" for a SARIF sink, then GCC will add the internal state
of the CFG for all functions after each pertinent optimization pass in
graph form to theRun.graphs in the SARIF output.
If set to "yes" for an HTML sink, the generated HTML will contain SVG
displaying the graphs, adapted from code in graph.cc
Text sinks ignore it.
The SARIF output is thus a machine-readable serialization of (some of)
GCC's intermediate representation (as JSON), but it's much less than
GCC-XML used to provide. The precise form of the information is
documented as subject to change without notice.
Currently it shows both gimple statements and RTL instructions,
depending on the pass. My hope is that it should be possible to write a
"cfg-grep" tool that can read the SARIF and automatically identify
in which pass a particular piece of our IR appeared or disappeared,
for tracking down bugs in our optimization passes.
Implementation-wise:
* this uses the publish-subscribe mechanism from the earlier patch, by
having the diagnostics sink subscribe to pass_events::after_pass
messages from the pass_events_channel.
* the patch adds a new hook to cfghooks.h for dumping a basic block
into a SARIF property bag
gcc/ChangeLog:
* Makefile.in (OBJS): Add tree-diagnostic-cfg.o.
(OBJS-libcommon): Add custom-sarif-properties/cfg.o,
diagnostics/digraphs-to-dot.o, and
diagnostics/digraphs-to-dot-from-cfg.o.
* cfghooks.cc: Define INCLUDE_VECTOR. Add includes of
"diagnostics/sarif-sink.h" and "custom-sarif-properties/cfg.h".
(dump_bb_as_sarif_properties): New.
* cfghooks.h (diagnostics::sarif_builder): New forward decl.
(json::object): New forward decl.
(cfg_hooks::dump_bb_as_sarif_properties): New callback field.
(dump_bb_as_sarif_properties): New decl.
* cfgrtl.cc (rtl_cfg_hooks): Populate the new callback
field with rtl_dump_bb_as_sarif_properties.
(cfg_layout_rtl_cfg_hooks): Likewise.
* custom-sarif-properties/cfg.cc: New file.
* custom-sarif-properties/cfg.h: New file.
* diagnostics/digraphs-to-dot-from-cfg.cc: New file, partly
adapted from gcc/graph.cc.
* diagnostics/digraphs-to-dot.cc: New file.
* diagnostics/digraphs-to-dot.h: New file, based on material in...
* diagnostics/digraphs.cc: Include
"diagnostics/digraphs-to-dot.h".
(class conversion_to_dot): Rework and move to above.
(make_dot_graph_from_diagnostic_graph): Likewise.
(make_dot_node_from_digraph_node): Likewise.
(make_dot_edge_from_digraph_edge): Likewise.
(conversion_to_dot::get_dot_id_for_node): Likewise.
(conversion_to_dot::has_edges_p): Likewise.
(digraph::make_dot_graph): Use to_dot::converter::make and invoke
the result to make the dot graph.
* diagnostics/digraphs.h (digraph:get_all_nodes): New accessor.
* diagnostics/html-sink.cc
(html_builder::m_per_logical_loc_graphs): New field.
(html_builder::add_graph_for_logical_loc): New.
(html_sink::report_digraph_for_logical_location): New.
* diagnostics/sarif-sink.cc (sarif_array_of_unique::get_element):
New.
(sarif_builder::report_digraph_for_logical_location): New.
(sarif_sink::report_digraph_for_logical_location): New.
* diagnostics/sink.h: Include "diagnostics/logical-locations.h".
(sink::report_digraph_for_logical_location): New vfunc.
* diagnostics/text-sink.h
(text_sink::report_digraph_for_logical_location): New.
* doc/invoke.texi (fdiagnostics-add-output): Clarify wording.
Distinguish between scheme-specific vs GCC-specific keys, and add
"cfgs" as the first example of the latter.
* gimple-pretty-print.cc: Include "cfghooks.h", "json.h", and
"custom-sarif-properties/cfg.h".
(gimple_dump_bb_as_sarif_properties): New.
* gimple-pretty-print.h (diagnostics::sarif_builder): New forward
decl.
(json::object): Likewise.
(gimple_dump_bb_as_sarif_properties): New.
* graphviz.cc (get_compass_pt_from_string): New
* graphviz.h (get_compass_pt_from_string): New decl.
* libsarifreplay.cc (sarif_replayer::handle_graph_object): Fix
overlong line.
* opts-common.cc: Define INCLUDE_VECTOR.
* opts-diagnostic.cc: Define INCLUDE_LIST. Include
"diagnostics/sarif-sink.h", "tree-diagnostic-sink-extensions.h",
"opts-diagnostic.h", and "pub-sub.h".
(class gcc_extra_keys): New class.
(opt_spec_context::opt_spec_context): Add "client_keys" param and
pass to dc_spec_context.
(handle_gcc_specific_keys): New.
(try_to_make_sink): New.
(gcc_extension_factory::singleton): New.
(handle_OPT_fdiagnostics_add_output_): Rework to use
try_to_make_sink.
(handle_OPT_fdiagnostics_set_output_): Likewise.
* opts-diagnostic.h: Include "diagnostics/sink.h".
(class gcc_extension_factory): New.
* opts.cc: Define INCLUDE_LIST.
* print-rtl.cc: Include "dumpfile.h", "cfghooks.h", "json.h", and
"custom-sarif-properties/cfg.h".
(rtl_dump_bb_as_sarif_properties): New.
* print-rtl.h (diagnostics::sarif_builder): New forward decl.
(json::object): Likewise.
(rtl_dump_bb_as_sarif_properties): New decl.
* tree-cfg.cc (gimple_cfg_hooks): Use
gimple_dump_bb_as_sarif_properties for new callback field.
* tree-diagnostic-cfg.cc: New file, based on material in graph.cc.
* tree-diagnostic-sink-extensions.h: New file.
* tree-diagnostic.cc: Define INCLUDE_LIST. Include
"tree-diagnostic-sink-extensions.h".
(compiler_ext_factory): New.
(tree_diagnostics_defaults): Set gcc_extension_factory::singleton
to be compiler_ext_factory.
gcc/testsuite/ChangeLog:
* gcc.dg/diagnostic-cfgs-html.py: New test.
* gcc.dg/diagnostic-cfgs-sarif.py: New test.
* gcc.dg/diagnostic-cfgs.c: New test.
Signed-off-by: David Malcolm <dmalcolm@redhat.com>
2742 lines
80 KiB
C++
2742 lines
80 KiB
C++
/* A library for re-emitting diagnostics saved in SARIF form
|
|
via libgdiagnostics.
|
|
Copyright (C) 2022-2026 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
|
|
#define INCLUDE_MAP
|
|
#define INCLUDE_STRING
|
|
#include "system.h"
|
|
#include "coretypes.h"
|
|
#include "libgdiagnostics++.h"
|
|
#include "libgdiagnostics-private.h"
|
|
#include "json-parsing.h"
|
|
#include "intl.h"
|
|
#include "sarif-spec-urls.def"
|
|
#include "libsarifreplay.h"
|
|
#include "label-text.h"
|
|
|
|
namespace {
|
|
|
|
/* Read the contents of PATH into memory.
|
|
Issue an error to MGR and return nullptr if there are any problems. */
|
|
|
|
static std::unique_ptr<std::vector<char>>
|
|
read_file (const char *path, libgdiagnostics::manager &mgr)
|
|
{
|
|
FILE *f_in = fopen (path, "r");
|
|
if (!f_in)
|
|
{
|
|
char *errmsg = xstrerror (errno);
|
|
auto err (mgr.begin_diagnostic (DIAGNOSTIC_LEVEL_ERROR));
|
|
err.finish ("cannot open %qs: %s", path, errmsg);
|
|
return nullptr;
|
|
}
|
|
|
|
/* Read content, allocating a buffer for it. */
|
|
auto result = std::make_unique<std::vector<char>> ();
|
|
char buf[4096];
|
|
size_t iter_sz_in;
|
|
|
|
while ( (iter_sz_in = fread (buf, 1, sizeof (buf), f_in)) )
|
|
{
|
|
size_t old_total_sz = result->size ();
|
|
size_t new_total_sz = old_total_sz + iter_sz_in;
|
|
size_t old_alloc_sz = result->capacity ();
|
|
if (new_total_sz > old_alloc_sz)
|
|
{
|
|
size_t new_alloc_sz = std::max (old_alloc_sz * 2, new_total_sz);
|
|
result->reserve (new_alloc_sz);
|
|
}
|
|
gcc_assert (result->capacity () >= new_total_sz);
|
|
result->resize (new_total_sz);
|
|
memcpy (result->data () + old_total_sz, buf, iter_sz_in);
|
|
}
|
|
|
|
if (!feof (f_in))
|
|
{
|
|
char *errmsg = xstrerror (errno);
|
|
auto err (mgr.begin_diagnostic (DIAGNOSTIC_LEVEL_ERROR));
|
|
err.finish ("error reading from %qs: %s", path, errmsg);
|
|
return nullptr;
|
|
}
|
|
|
|
fclose (f_in);
|
|
|
|
return result;
|
|
}
|
|
|
|
static libgdiagnostics::physical_location
|
|
make_physical_location (libgdiagnostics::manager &mgr,
|
|
libgdiagnostics::file f,
|
|
const json::location_map::point &point)
|
|
{
|
|
/* json::location_map::point uses 0-based columns,
|
|
whereas libgdiagnostics uses 1-based columns. */
|
|
return mgr.new_location_from_file_line_column (f,
|
|
point.m_line,
|
|
point.m_column + 1);
|
|
}
|
|
|
|
static libgdiagnostics::physical_location
|
|
make_physical_location (libgdiagnostics::manager &mgr,
|
|
libgdiagnostics::file f,
|
|
const json::location_map::range &range)
|
|
{
|
|
libgdiagnostics::physical_location start
|
|
= make_physical_location (mgr, f, range.m_start);
|
|
libgdiagnostics::physical_location end
|
|
= make_physical_location (mgr, f, range.m_end);
|
|
return mgr.new_location_from_range (start, start, end);
|
|
}
|
|
|
|
static enum diagnostic_logical_location_kind_t
|
|
get_logical_location_kind_for_json_kind (enum json::kind json_kind)
|
|
{
|
|
switch (json_kind)
|
|
{
|
|
default:
|
|
gcc_unreachable ();
|
|
|
|
case json::JSON_OBJECT:
|
|
return DIAGNOSTIC_LOGICAL_LOCATION_KIND_OBJECT;
|
|
|
|
case json::JSON_ARRAY:
|
|
return DIAGNOSTIC_LOGICAL_LOCATION_KIND_ARRAY;
|
|
|
|
case json::JSON_INTEGER:
|
|
case json::JSON_FLOAT:
|
|
case json::JSON_STRING:
|
|
case json::JSON_TRUE:
|
|
case json::JSON_FALSE:
|
|
case json::JSON_NULL:
|
|
return DIAGNOSTIC_LOGICAL_LOCATION_KIND_PROPERTY;
|
|
/* Perhaps this should be DIAGNOSTIC_LOGICAL_LOCATION_KIND_VALUE,
|
|
but then we need to more carefully track context. */
|
|
}
|
|
}
|
|
|
|
static libgdiagnostics::logical_location
|
|
make_logical_location_from_jv (libgdiagnostics::manager &mgr,
|
|
const json::value &jv)
|
|
{
|
|
libgdiagnostics::logical_location parent;
|
|
const json::pointer::token &pointer_token = jv.get_pointer_token ();
|
|
|
|
if (pointer_token.m_parent)
|
|
/* Recursively ensure that we have ancestor locations. */
|
|
parent = make_logical_location_from_jv (mgr,
|
|
*jv.m_pointer_token.m_parent);
|
|
|
|
std::string short_name;
|
|
std::string fully_qualified_name;
|
|
switch (pointer_token.m_kind)
|
|
{
|
|
default:
|
|
gcc_unreachable ();
|
|
|
|
case json::pointer::token::kind::root_value:
|
|
short_name = "";
|
|
fully_qualified_name = "";
|
|
break;
|
|
|
|
case json::pointer::token::kind::object_member:
|
|
short_name = pointer_token.m_data.u_member;
|
|
gcc_assert (parent.m_inner);
|
|
fully_qualified_name
|
|
= std::string (parent.get_fully_qualified_name ()) + "/" + short_name;
|
|
break;
|
|
|
|
case json::pointer::token::kind::array_index:
|
|
short_name = std::to_string (pointer_token.m_data.u_index);
|
|
gcc_assert (parent.m_inner);
|
|
fully_qualified_name
|
|
= std::string (parent.get_fully_qualified_name ()) + "/" + short_name;
|
|
break;
|
|
}
|
|
|
|
enum diagnostic_logical_location_kind_t kind
|
|
= get_logical_location_kind_for_json_kind (jv.get_kind ());
|
|
|
|
return mgr.new_logical_location (kind,
|
|
parent,
|
|
short_name.c_str (),
|
|
fully_qualified_name.c_str (),
|
|
nullptr);
|
|
}
|
|
|
|
|
|
enum class status
|
|
{
|
|
ok,
|
|
err_reading_file,
|
|
err_malformed_json,
|
|
err_invalid_sarif,
|
|
err_unhandled_sarif
|
|
};
|
|
|
|
/* A reference to the SARIF specification. */
|
|
|
|
class spec_ref
|
|
{
|
|
public:
|
|
spec_ref (const char *section)
|
|
: m_section (section)
|
|
{}
|
|
|
|
virtual char *make_description () const
|
|
{
|
|
/* 'SECTION SIGN' (U+00A7). */
|
|
#define SECTION_SIGN_UTF8 "\xC2\xA7"
|
|
return xasprintf ("SARIF v2.1.0 " SECTION_SIGN_UTF8 "%s", m_section);
|
|
}
|
|
|
|
char *make_url () const
|
|
{
|
|
const char *anchor = get_anchor_for_section (m_section);
|
|
if (!anchor)
|
|
return nullptr;
|
|
return xasprintf ("%s#%s", sarif_spec_base_url, anchor);
|
|
}
|
|
|
|
private:
|
|
static const char *
|
|
get_anchor_for_section (const char *section)
|
|
{
|
|
/* Linear search, but the array is only a few hundred entries . */
|
|
for (size_t i = 0; i < ARRAY_SIZE (sarif_spec_anchor_arr); i++)
|
|
{
|
|
if (strcmp (sarif_spec_anchor_arr[i].m_ref, section) == 0)
|
|
return sarif_spec_anchor_arr[i].m_anchor;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
/* e.g. "3.1" for section 3.1 of the spec. */
|
|
const char *m_section;
|
|
};
|
|
|
|
/* A reference to the SARIF specification for a particular kind of object. */
|
|
|
|
class object_spec_ref : public spec_ref
|
|
{
|
|
public:
|
|
object_spec_ref (const char *obj_name, const char *section)
|
|
: spec_ref (section),
|
|
m_obj_name (obj_name)
|
|
{}
|
|
|
|
const char *get_obj_name () const { return m_obj_name; }
|
|
|
|
private:
|
|
const char *m_obj_name;
|
|
};
|
|
|
|
/* A reference to the SARIF specification for a particular property
|
|
of a particular kind of object. */
|
|
|
|
class property_spec_ref : public object_spec_ref
|
|
{
|
|
public:
|
|
property_spec_ref (const char *obj_name,
|
|
const char *property_name,
|
|
const char *section)
|
|
: object_spec_ref (obj_name, section),
|
|
m_property_name (property_name)
|
|
{}
|
|
|
|
const char *get_property_name () const { return m_property_name; }
|
|
|
|
private:
|
|
const char *m_property_name;
|
|
};
|
|
|
|
template <typename ValueType>
|
|
struct string_property_value
|
|
{
|
|
const char *m_string;
|
|
ValueType m_value;
|
|
};
|
|
|
|
/* A class for recording annotations seen in locations (§3.28.6) that
|
|
should be emitted as secondary locations on diagnostics. */
|
|
|
|
class annotation
|
|
{
|
|
public:
|
|
annotation (libgdiagnostics::physical_location phys_loc,
|
|
libgdiagnostics::message_buffer label)
|
|
: m_phys_loc (phys_loc),
|
|
m_label (std::move (label))
|
|
{
|
|
}
|
|
|
|
libgdiagnostics::physical_location m_phys_loc;
|
|
libgdiagnostics::message_buffer m_label;
|
|
};
|
|
|
|
using id_map = std::map<std::string, const json::string *>;
|
|
|
|
class sarif_replayer
|
|
{
|
|
public:
|
|
sarif_replayer (libgdiagnostics::manager &&output_manager,
|
|
libgdiagnostics::manager &&control_manager)
|
|
: m_output_mgr (std::move (output_manager)),
|
|
m_control_mgr (std::move (control_manager)),
|
|
m_driver_obj (nullptr),
|
|
m_artifacts_arr (nullptr)
|
|
{
|
|
}
|
|
|
|
enum status replay_file (const char *filename,
|
|
const replay_options &replay_opts);
|
|
|
|
private:
|
|
class replayer_location_map : public json::location_map
|
|
{
|
|
public:
|
|
void record_range_for_value (json::value *jv,
|
|
const range &r) final override
|
|
{
|
|
m_map_jv_to_range[jv] = r;
|
|
}
|
|
|
|
const json::location_map::range &
|
|
get_range_for_value (const json::value &jv) const
|
|
{
|
|
auto iter = m_map_jv_to_range.find (&jv);
|
|
gcc_assert (iter != m_map_jv_to_range.end ());
|
|
return iter->second;
|
|
}
|
|
|
|
private:
|
|
std::map<const json::value *, range> m_map_jv_to_range;
|
|
};
|
|
|
|
enum status emit_sarif_as_diagnostics (const json::value &jv);
|
|
|
|
libgdiagnostics::message_buffer
|
|
make_plain_text_within_result_message (const json::object *tool_component_obj,
|
|
const json::object &message_obj,
|
|
const json::object *rule_obj);
|
|
|
|
/* Handlers for specific parts of the SARIF spec.
|
|
Keep this in the same order as the spec. */
|
|
|
|
// "artifactLocation" object (§3.4)
|
|
enum status
|
|
handle_artifact_location_object (const json::object &artifact_loc,
|
|
libgdiagnostics::file &out);
|
|
|
|
// Message string lookup algorithm (§3.11.7)
|
|
const char *
|
|
lookup_plain_text_within_result_message (const json::object *tool_component_obj,
|
|
const json::object &message_obj,
|
|
const json::object *rule_obj,
|
|
const json::string *&out_js_str);
|
|
|
|
// "multiformatMessageString" object (§3.12).
|
|
const char *
|
|
get_plain_text_from_mfms (json::value &mfms_val,
|
|
const property_spec_ref &prop,
|
|
const json::string *&out_js_str);
|
|
|
|
// "run" object (§3.14)
|
|
enum status
|
|
handle_run_obj (const json::object &run_obj);
|
|
|
|
// "tool" object (§3.18)
|
|
enum status
|
|
handle_tool_obj (const json::object &tool_obj);
|
|
|
|
// "artifact" object (§3.24). */
|
|
void
|
|
handle_artifact_obj (const json::object &artifact_obj);
|
|
|
|
// "result" object (§3.27)
|
|
enum status
|
|
handle_result_obj (const json::object &result_obj,
|
|
const json::object &run_obj,
|
|
const json::object &tool_obj);
|
|
json::result<enum diagnostic_level, enum status>
|
|
get_level_from_level_str (const json::string &level_str);
|
|
|
|
// "location" object (§3.28)
|
|
enum status
|
|
handle_location_object (const json::object &location_obj,
|
|
const json::object &run_obj,
|
|
libgdiagnostics::physical_location &out_physical_loc,
|
|
libgdiagnostics::logical_location &out_logical_loc,
|
|
std::vector<annotation> *out_annotations);
|
|
|
|
// "physicalLocation" object (§3.29)
|
|
enum status
|
|
handle_physical_location_object (const json::object &phys_loc_obj,
|
|
libgdiagnostics::physical_location &out);
|
|
|
|
// "region" object (§3.30)
|
|
enum status
|
|
handle_region_object (const json::object ®ion_obj,
|
|
libgdiagnostics::file file,
|
|
libgdiagnostics::physical_location &out);
|
|
|
|
// "logicalLocation" object (§3.33)
|
|
enum status
|
|
handle_logical_location_object (const json::object &logical_loc_obj,
|
|
const json::object *run_obj,
|
|
libgdiagnostics::logical_location &out);
|
|
|
|
// "threadFlow" object (§3.37)
|
|
enum status
|
|
handle_thread_flow_object (const json::object &thread_flow_obj,
|
|
const json::object &run_obj,
|
|
libgdiagnostics::execution_path &out);
|
|
|
|
// "threadFlowLocation" object (§3.38)
|
|
enum status
|
|
handle_thread_flow_location_object (const json::object &tflow_loc_obj,
|
|
const json::object &run_obj,
|
|
libgdiagnostics::execution_path &out);
|
|
|
|
// "graph" object (§3.39)
|
|
enum status
|
|
handle_graph_object (const json::object &graph_obj,
|
|
const json::object &run_obj,
|
|
libgdiagnostics::graph &out);
|
|
// "node" object (§3.40)
|
|
libgdiagnostics::node
|
|
handle_node_object (const json::object &node_obj,
|
|
const json::object &run_obj,
|
|
libgdiagnostics::graph &graph,
|
|
libgdiagnostics::node parent_node,
|
|
id_map &node_id_map);
|
|
|
|
// "edge" object (§3.41)
|
|
libgdiagnostics::edge
|
|
handle_edge_object (const json::object &edge_obj,
|
|
libgdiagnostics::graph &graph,
|
|
id_map &edge_id_map);
|
|
|
|
libgdiagnostics::node
|
|
get_graph_node_by_id_property (const json::object &edge_json_object,
|
|
const property_spec_ref &id_prop,
|
|
libgdiagnostics::graph &graph);
|
|
|
|
// reportingDescriptor lookup (§3.52.3)
|
|
const json::object *
|
|
lookup_rule_by_id_in_tool (const char *rule_id,
|
|
const json::object &tool_obj,
|
|
const json::object *&tool_component_obj);
|
|
|
|
const json::object *
|
|
lookup_rule_by_id_in_component (const char *rule_id,
|
|
const json::object &tool_component_obj);
|
|
|
|
// "fix" object (§3.55)
|
|
enum status
|
|
handle_fix_object (libgdiagnostics::diagnostic &diag,
|
|
const json::object &fix_obj);
|
|
|
|
// "artifactChange" object (§3.56)
|
|
enum status
|
|
handle_artifact_change_object (libgdiagnostics::diagnostic &diag,
|
|
const json::object &change_obj);
|
|
|
|
/* Support functions. */
|
|
|
|
/* Report an error to m_control_mgr about JV violating REF,
|
|
and return status::err_invalid_sarif. */
|
|
|
|
enum status
|
|
report_invalid_sarif (const json::value &jv,
|
|
const spec_ref &ref,
|
|
const char *gmsgid, ...)
|
|
LIBGDIAGNOSTICS_PARAM_GCC_FORMAT_STRING (4, 5)
|
|
{
|
|
va_list ap;
|
|
va_start (ap, gmsgid);
|
|
report_problem (jv, &ref, gmsgid, &ap, DIAGNOSTIC_LEVEL_ERROR);
|
|
va_end (ap);
|
|
return status::err_invalid_sarif;
|
|
}
|
|
|
|
/* Report a "sorry" to m_control_mgr inability to handle JV and REF,
|
|
and return status::err_unhandled_sarif. */
|
|
|
|
enum status
|
|
report_unhandled_sarif (const json::value &jv,
|
|
const spec_ref &ref,
|
|
const char *gmsgid, ...)
|
|
LIBGDIAGNOSTICS_PARAM_GCC_FORMAT_STRING (4, 5)
|
|
{
|
|
va_list ap;
|
|
va_start (ap, gmsgid);
|
|
report_problem (jv, &ref, gmsgid, &ap, DIAGNOSTIC_LEVEL_SORRY);
|
|
va_end (ap);
|
|
return status::err_unhandled_sarif;
|
|
}
|
|
|
|
void
|
|
report_note (const json::value &jv,
|
|
const char *gmsgid, ...)
|
|
LIBGDIAGNOSTICS_PARAM_GCC_FORMAT_STRING (3, 4)
|
|
{
|
|
va_list ap;
|
|
va_start (ap, gmsgid);
|
|
report_problem (jv, nullptr, gmsgid, &ap, DIAGNOSTIC_LEVEL_NOTE);
|
|
va_end (ap);
|
|
}
|
|
|
|
void
|
|
report_problem (const json::value &jv,
|
|
const spec_ref *ref,
|
|
const char *gmsgid,
|
|
va_list *args,
|
|
enum diagnostic_level level)
|
|
LIBGDIAGNOSTICS_PARAM_GCC_FORMAT_STRING (4, 0)
|
|
{
|
|
auto diag (m_control_mgr.begin_diagnostic (level));
|
|
|
|
/* Add rule specifying the pertinent section of the specification.
|
|
There doesn't seem to be a systematic mapping from spec sections to
|
|
HTML anchors, so we can't provide URLs
|
|
(filed as https://github.com/oasis-tcs/sarif-spec/issues/533 ). */
|
|
if (ref)
|
|
{
|
|
char *ref_desc = ref->make_description ();
|
|
char *ref_url = ref->make_url ();
|
|
diag.add_rule (ref_desc, ref_url);
|
|
free (ref_desc);
|
|
free (ref_url);
|
|
}
|
|
|
|
auto loc_range
|
|
= make_physical_location (m_control_mgr,
|
|
m_loaded_file,
|
|
m_json_location_map.get_range_for_value (jv));
|
|
diag.set_location (loc_range);
|
|
|
|
diag.set_logical_location (make_logical_location_from_jv (m_control_mgr,
|
|
jv));
|
|
|
|
diag.finish_va (gmsgid, args);
|
|
}
|
|
|
|
/* Require OBJ to have at least one of OBJ_PROP1 or OBJ_PROP2.
|
|
If successful, result status::ok.
|
|
Otherwise, complain about OBJ_CONSTRAINTS and return
|
|
status::invalid_sarif. */
|
|
enum status
|
|
report_invalid_sarif_at_least_one_of (const json::object &obj,
|
|
const object_spec_ref &obj_constraints,
|
|
const property_spec_ref &obj_prop_1,
|
|
const property_spec_ref &obj_prop_2)
|
|
{
|
|
return report_invalid_sarif
|
|
(obj, obj_constraints,
|
|
"expected SARIF %qs object to contain at least one of %qs or %qs",
|
|
obj_constraints.get_obj_name (),
|
|
obj_prop_1.get_property_name (),
|
|
obj_prop_2.get_property_name ());
|
|
}
|
|
|
|
/* Require VAL to be a json::object.
|
|
If successful, return it as an object.
|
|
Otherwise, complain using REF and return nullptr. */
|
|
const json::object *
|
|
require_object (const json::value &val, const property_spec_ref &ref)
|
|
{
|
|
const json::object *obj = dyn_cast <const json::object *> (&val);
|
|
if (!obj)
|
|
{
|
|
report_invalid_sarif (val, ref, "expected %s.%s to be an object",
|
|
ref.get_obj_name (), ref.get_property_name ());
|
|
return nullptr;
|
|
}
|
|
return obj;
|
|
}
|
|
|
|
/* Require VAL to be a json::string
|
|
If successful, return it as an string.
|
|
Otherwise, complain using REF and return nullptr. */
|
|
const json::string *
|
|
require_string (const json::value &val, const property_spec_ref &ref)
|
|
{
|
|
const json::string *str = dyn_cast <const json::string *> (&val);
|
|
if (!str)
|
|
{
|
|
report_invalid_sarif (val, ref, "expected %s.%s to be an string",
|
|
ref.get_obj_name (), ref.get_property_name ());
|
|
return nullptr;
|
|
}
|
|
return str;
|
|
}
|
|
/* Look for an optional property within OBJ based on REF. */
|
|
const json::value *
|
|
get_optional_property (const json::object &obj,
|
|
const property_spec_ref &ref)
|
|
{
|
|
return obj.get (ref.get_property_name ());
|
|
}
|
|
|
|
/* Look for a property of VAL based on REF.
|
|
If present, it must be of kind JsonType.
|
|
If found and valid, return the property's value.
|
|
If not found, silently return nullptr.
|
|
Otherwise, complain and return nullptr. */
|
|
template <typename JsonType>
|
|
const JsonType *
|
|
get_optional_property (const json::object &obj,
|
|
const property_spec_ref &ref)
|
|
{
|
|
const json::value *property_val = get_optional_property (obj, ref);
|
|
if (!property_val)
|
|
return nullptr;
|
|
const JsonType *sub = dyn_cast<const JsonType *> (property_val);
|
|
if (!sub)
|
|
{
|
|
/* Property is wrong kind of value. */
|
|
report_bad_property_kind<JsonType> (obj, ref, *property_val);
|
|
return nullptr;
|
|
}
|
|
return sub;
|
|
}
|
|
|
|
/* Require VAL to be a json::object.
|
|
Look for a property of VAL based on REF, which must be of
|
|
kind JsonType.
|
|
If successful, return the property's value.
|
|
Otherwise, complain and return nullptr. */
|
|
template <typename JsonType>
|
|
const JsonType *
|
|
get_required_property (const json::value &val,
|
|
const property_spec_ref &ref)
|
|
{
|
|
const json::object *obj = require_object (val, ref);
|
|
if (!obj)
|
|
return nullptr;
|
|
return get_required_property<JsonType> (*obj, ref);
|
|
}
|
|
|
|
/* Look for a property of VAL based on REF, which must be of
|
|
kind JsonType.
|
|
If successful, return the property's value.
|
|
Otherwise, complain and return nullptr. */
|
|
template <typename JsonType>
|
|
const JsonType *
|
|
get_required_property (const json::object &obj,
|
|
const property_spec_ref &ref)
|
|
{
|
|
const json::value *property_val = get_optional_property (obj, ref);
|
|
if (!property_val)
|
|
{
|
|
/* Property not present. */
|
|
report_invalid_sarif (obj, ref,
|
|
"expected %s object to have a %qs property",
|
|
ref.get_obj_name (), ref.get_property_name ());
|
|
return nullptr;
|
|
}
|
|
const JsonType *sub = dyn_cast<const JsonType *> (property_val);
|
|
if (!sub)
|
|
{
|
|
/* Property is wrong kind of value. */
|
|
report_bad_property_kind<JsonType> (obj, ref, *property_val);
|
|
return nullptr;
|
|
}
|
|
return sub;
|
|
}
|
|
|
|
template <typename JsonType>
|
|
void
|
|
report_bad_property_kind (const json::object &obj,
|
|
const property_spec_ref &ref,
|
|
const json::value &property_val);
|
|
|
|
const json::object *
|
|
require_object_for_element (const json::value &jv,
|
|
const property_spec_ref &ref)
|
|
{
|
|
const json::object *obj = dyn_cast <const json::object *> (&jv);
|
|
if (!obj)
|
|
{
|
|
report_invalid_sarif (jv, ref,
|
|
"expected element of %s.%s array to be an object",
|
|
ref.get_obj_name (), ref.get_property_name ());
|
|
return nullptr;
|
|
}
|
|
return obj;
|
|
}
|
|
|
|
template <typename ValueType>
|
|
json::result<ValueType, enum status>
|
|
get_value_from_json_string (const json::string &json_str,
|
|
const property_spec_ref &prop,
|
|
const string_property_value<ValueType> *value_arr,
|
|
size_t num_values);
|
|
|
|
const json::object *
|
|
maybe_get_property_bag (const json::object &obj)
|
|
{
|
|
const property_spec_ref properties
|
|
("object", "properties", "3.8.1");
|
|
return get_optional_property<json::object> (obj, properties);
|
|
}
|
|
|
|
/* Look for a property bag within OBJ.
|
|
If found, look for a property within it named PROPERTY_NAME
|
|
of the given type.
|
|
If successful, return the property's value.
|
|
Otherwise, return nullptr without complaining (unless the property bag
|
|
is itself not an object). */
|
|
template <typename JsonType>
|
|
const JsonType *
|
|
maybe_get_property_bag_value (const json::object &obj,
|
|
const char *property_name)
|
|
{
|
|
auto property_bag_obj = maybe_get_property_bag (obj);
|
|
if (!property_bag_obj)
|
|
return nullptr;
|
|
const json::value *property_val = property_bag_obj->get (property_name);
|
|
if (!property_val)
|
|
return nullptr;
|
|
const JsonType *sub = dyn_cast<const JsonType *> (property_val);
|
|
if (!sub)
|
|
/* Property is wrong kind of value. Don't treat this as an error. */
|
|
return nullptr;
|
|
return sub;
|
|
}
|
|
|
|
/* The manager to replay the SARIF files to. */
|
|
libgdiagnostics::manager m_output_mgr;
|
|
|
|
/* The manager for reporting issues loading SARIF files. */
|
|
libgdiagnostics::manager m_control_mgr;
|
|
|
|
/* The file within m_control_mgr representing the .sarif file. */
|
|
libgdiagnostics::file m_loaded_file;
|
|
|
|
replayer_location_map m_json_location_map;
|
|
|
|
const json::object *m_driver_obj;
|
|
const json::array *m_artifacts_arr;
|
|
};
|
|
|
|
static const char *
|
|
describe_kind (const json::value &val)
|
|
{
|
|
switch (val.get_kind ())
|
|
{
|
|
default:
|
|
gcc_unreachable ();
|
|
case json::JSON_OBJECT:
|
|
return _("JSON object");
|
|
|
|
case json::JSON_ARRAY:
|
|
return _("JSON array");
|
|
|
|
case json::JSON_INTEGER:
|
|
case json::JSON_FLOAT:
|
|
return _("JSON number");
|
|
|
|
case json::JSON_STRING:
|
|
return _("JSON string");
|
|
|
|
case json::JSON_TRUE:
|
|
case json::JSON_FALSE:
|
|
case json::JSON_NULL:
|
|
return _("JSON literal");
|
|
}
|
|
}
|
|
|
|
/* class sarif_replayer. */
|
|
|
|
template <>
|
|
void
|
|
sarif_replayer::
|
|
report_bad_property_kind<json::value> (const json::object &,
|
|
const property_spec_ref &,
|
|
const json::value &)
|
|
{
|
|
gcc_unreachable ();
|
|
}
|
|
|
|
template <>
|
|
void
|
|
sarif_replayer::
|
|
report_bad_property_kind<json::integer_number> (const json::object &,
|
|
const property_spec_ref &ref,
|
|
const json::value &propval)
|
|
{
|
|
report_invalid_sarif (propval, ref, "expected %s.%s to be a JSON integer; got %s",
|
|
ref.get_obj_name (), ref.get_property_name (),
|
|
describe_kind (propval));
|
|
}
|
|
|
|
template <>
|
|
void
|
|
sarif_replayer::
|
|
report_bad_property_kind<json::string> (const json::object &,
|
|
const property_spec_ref &ref,
|
|
const json::value &propval)
|
|
{
|
|
report_invalid_sarif (propval, ref, "expected %s.%s to be a JSON string; got %s",
|
|
ref.get_obj_name (), ref.get_property_name (),
|
|
describe_kind (propval));
|
|
}
|
|
|
|
template <>
|
|
void
|
|
sarif_replayer::
|
|
report_bad_property_kind<json::array> (const json::object &,
|
|
const property_spec_ref &ref,
|
|
const json::value &propval)
|
|
{
|
|
report_invalid_sarif (propval, ref, "expected %s.%s to be a JSON array; got %s",
|
|
ref.get_obj_name (), ref.get_property_name (),
|
|
describe_kind (propval));
|
|
}
|
|
|
|
template <>
|
|
void
|
|
sarif_replayer::
|
|
report_bad_property_kind<json::object> (const json::object &,
|
|
const property_spec_ref &ref,
|
|
const json::value &propval)
|
|
{
|
|
report_invalid_sarif (propval, ref, "expected %s.%s to be a JSON object; got %s",
|
|
ref.get_obj_name (), ref.get_property_name (),
|
|
describe_kind (propval));
|
|
}
|
|
|
|
enum status
|
|
sarif_replayer::replay_file (const char *filename,
|
|
const replay_options &replay_opts)
|
|
{
|
|
std::unique_ptr<std::vector<char>> buf = read_file (filename, m_control_mgr);
|
|
if (!buf)
|
|
return status::err_reading_file;
|
|
|
|
/* Use "sarif" as the sourceLanguage for SARIF itself; see
|
|
https://github.com/oasis-tcs/sarif-spec/issues/654 */
|
|
const char * const source_language = "sarif";
|
|
m_loaded_file = m_control_mgr.new_file (filename, source_language);
|
|
|
|
if (replay_opts.m_echo_file)
|
|
{
|
|
fprintf (stderr, "%s: (%li bytes)\n",
|
|
filename, (long)buf->size ());
|
|
for (size_t i = 0; i < buf->size(); i++)
|
|
fputc ((*buf)[i], stderr);
|
|
}
|
|
|
|
json::parser_result_t result
|
|
(json::parse_utf8_string (buf->size (),
|
|
(const char *)buf->data (),
|
|
replay_opts.m_json_comments,
|
|
&m_json_location_map));
|
|
|
|
if (auto json_err = result.m_err.get ())
|
|
{
|
|
gcc_assert (!result.m_val.get ());
|
|
auto file = m_control_mgr.new_file (filename, source_language);
|
|
auto loc_range = make_physical_location (m_control_mgr,
|
|
file,
|
|
json_err->get_range ());
|
|
auto err (m_control_mgr.begin_diagnostic (DIAGNOSTIC_LEVEL_ERROR));
|
|
err.set_location (loc_range);
|
|
err.finish ("%s", json_err->get_msg ());
|
|
|
|
return status::err_malformed_json;
|
|
}
|
|
|
|
gcc_assert (result.m_val.get ());
|
|
return emit_sarif_as_diagnostics (*result.m_val.get ());
|
|
}
|
|
|
|
#define PROP_sarifLog_version \
|
|
property_spec_ref ("sarifLog", "version", "3.13.2")
|
|
|
|
#define PROP_sarifLog_runs \
|
|
property_spec_ref ("sarifLog", "runs", "3.13.4")
|
|
|
|
enum status
|
|
sarif_replayer::emit_sarif_as_diagnostics (const json::value &jv)
|
|
{
|
|
/* We expect a sarifLog object as the top-level value
|
|
(SARIF v2.1.0 section 3.13). */
|
|
const json::object *toplev_obj = dyn_cast <const json::object *> (&jv);
|
|
if (!toplev_obj)
|
|
return report_invalid_sarif
|
|
(jv, spec_ref ("3.1"),
|
|
"expected a sarifLog object as the top-level value");
|
|
|
|
/* sarifLog objects SHALL have a property named "version"
|
|
(SARIF v2.1.0 section 3.13.2) with a string value. */
|
|
if (!get_required_property<json::string> (*toplev_obj,
|
|
PROP_sarifLog_version))
|
|
return status::err_invalid_sarif;
|
|
|
|
/* sarifLog.runs must be null or be an array. */
|
|
const property_spec_ref prop_runs (PROP_sarifLog_runs);
|
|
const json::value *runs
|
|
= get_required_property<json::value> (*toplev_obj, prop_runs);
|
|
if (!runs)
|
|
return status::err_invalid_sarif;
|
|
|
|
switch (runs->get_kind ())
|
|
{
|
|
default:
|
|
return report_invalid_sarif (*runs, prop_runs,
|
|
"expected sarifLog.runs to be"
|
|
" %<null%> or an array");
|
|
|
|
case json::JSON_NULL:
|
|
/* Nothing to do. */
|
|
break;
|
|
|
|
case json::JSON_ARRAY:
|
|
{
|
|
const json::array &runs_arr = *as_a <const json::array *> (runs);
|
|
for (auto element : runs_arr)
|
|
{
|
|
const json::object *run_obj
|
|
= require_object_for_element (*element, prop_runs);
|
|
if (!run_obj)
|
|
return status::err_invalid_sarif;
|
|
enum status s = handle_run_obj (*run_obj);
|
|
if (s != status::ok)
|
|
return s;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
return status::ok;
|
|
}
|
|
|
|
/* Process a run object (SARIF v2.1.0 section 3.14). */
|
|
|
|
enum status
|
|
sarif_replayer::handle_run_obj (const json::object &run_obj)
|
|
{
|
|
const json::object *tool_obj
|
|
= get_required_property<json::object> (run_obj,
|
|
property_spec_ref ("run", "tool",
|
|
"3.14.6"));
|
|
if (!tool_obj)
|
|
return status::err_invalid_sarif;
|
|
{
|
|
enum status err = handle_tool_obj (*tool_obj);
|
|
if (err != status::ok)
|
|
return err;
|
|
}
|
|
|
|
m_driver_obj
|
|
= get_required_property<json::object> (*tool_obj,
|
|
property_spec_ref ("tool", "driver",
|
|
"3.18.2"));
|
|
if (!m_driver_obj)
|
|
return status::err_invalid_sarif;
|
|
|
|
const property_spec_ref prop_artifacts ("run", "artifacts", "3.14.15");
|
|
m_artifacts_arr
|
|
= get_optional_property<json::array> (run_obj, prop_artifacts);
|
|
if (m_artifacts_arr)
|
|
for (auto element : *m_artifacts_arr)
|
|
{
|
|
if (const json::object *artifact_obj
|
|
= require_object_for_element (*element, prop_artifacts))
|
|
handle_artifact_obj (*artifact_obj);
|
|
else
|
|
return status::err_invalid_sarif;
|
|
}
|
|
|
|
/* If present, run.results must be null or be an array. */
|
|
const property_spec_ref prop_results ("run", "results", "3.14.23");
|
|
if (const json::value *results = get_optional_property (run_obj,
|
|
prop_results))
|
|
switch (results->get_kind ())
|
|
{
|
|
default:
|
|
return report_invalid_sarif (*results, prop_results,
|
|
"expected run.results to be"
|
|
" %<null%> or an array");
|
|
|
|
case json::JSON_NULL:
|
|
/* Nothing to do. */
|
|
break;
|
|
case json::JSON_ARRAY:
|
|
{
|
|
const json::array *results_arr = as_a <const json::array *> (results);
|
|
for (auto element : *results_arr)
|
|
{
|
|
const json::object *result_obj
|
|
= require_object_for_element (*element, prop_results);
|
|
if (!result_obj)
|
|
return status::err_invalid_sarif;
|
|
enum status s = handle_result_obj (*result_obj,
|
|
run_obj,
|
|
*tool_obj);
|
|
if (s != status::ok)
|
|
return s;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
// §3.14.20 "graphs"
|
|
const property_spec_ref prop_graphs ("run", "graphs", "3.14.20");
|
|
if (const json::array *graphs
|
|
= get_optional_property<json::array> (run_obj,
|
|
prop_graphs))
|
|
{
|
|
for (auto element : *graphs)
|
|
{
|
|
if (const json::object *graph_json_obj
|
|
= require_object_for_element (*element, prop_graphs))
|
|
{
|
|
libgdiagnostics::graph graph;
|
|
enum status s = handle_graph_object (*graph_json_obj,
|
|
run_obj,
|
|
graph);
|
|
if (s != status::ok)
|
|
return s;
|
|
|
|
m_output_mgr.take_global_graph (std::move (graph));
|
|
}
|
|
else
|
|
return status::err_invalid_sarif;
|
|
}
|
|
}
|
|
|
|
return status::ok;
|
|
}
|
|
|
|
/* Process an artifact object (SARIF v2.1.0 section 3.24).
|
|
Create a libgdiagnostics::file for each artifact that has a uri,
|
|
effectively prepopulating a cache with source language and contents. */
|
|
|
|
void
|
|
sarif_replayer::handle_artifact_obj (const json::object &artifact_obj)
|
|
{
|
|
const property_spec_ref location ("artifact", "location", "3.24.2");
|
|
auto artifact_loc_obj
|
|
= get_optional_property<json::object> (artifact_obj, location);
|
|
if (!artifact_loc_obj)
|
|
return;
|
|
|
|
// we should now have an artifactLocation object (§3.4)
|
|
|
|
// 3.4.3 uri property
|
|
const property_spec_ref prop_uri ("artifactLocation", "uri", "3.4.3");
|
|
auto artifact_loc_uri
|
|
= get_optional_property<json::string> (*artifact_loc_obj, prop_uri);
|
|
if (!artifact_loc_uri)
|
|
return;
|
|
|
|
const char *sarif_source_language = nullptr;
|
|
const property_spec_ref prop_source_lang
|
|
("artifact", "sourceLanguage", "3.24.10");
|
|
if (auto source_lang_jstr
|
|
= get_optional_property<json::string> (artifact_obj,
|
|
prop_source_lang))
|
|
sarif_source_language = source_lang_jstr->get_string ();
|
|
|
|
/* Create the libgdiagnostics::file. */
|
|
auto file = m_output_mgr.new_file (artifact_loc_uri->get_string (),
|
|
sarif_source_language);
|
|
|
|
// 3.24.6 "roles" property
|
|
const property_spec_ref prop_roles
|
|
("artifact", "roles", "3.24.6");
|
|
if (auto roles_obj
|
|
= get_optional_property<json::array> (artifact_obj,
|
|
prop_roles))
|
|
for (auto iter : *roles_obj)
|
|
if (auto str = require_string (*iter, prop_roles))
|
|
if (!strcmp (str->get_string (), "analysisTarget"))
|
|
m_output_mgr.set_analysis_target (file);
|
|
|
|
// Set contents, if available
|
|
const property_spec_ref prop_contents
|
|
("artifact", "contents", "3.24.8");
|
|
if (auto content_obj
|
|
= get_optional_property<json::object> (artifact_obj,
|
|
prop_contents))
|
|
{
|
|
// We should have an artifactContent object (§3.3)
|
|
const property_spec_ref prop_text
|
|
("artifactContent", "text", "3.3.2");
|
|
if (auto text_jstr
|
|
= get_optional_property<json::string> (*content_obj,
|
|
prop_text))
|
|
file.set_buffered_content (text_jstr->get_string (),
|
|
text_jstr->get_length ());
|
|
}
|
|
}
|
|
|
|
/* Process a tool object (SARIF v2.1.0 section 3.18). */
|
|
|
|
enum status
|
|
sarif_replayer::handle_tool_obj (const json::object &tool_obj)
|
|
{
|
|
auto driver_obj
|
|
= get_required_property<json::object> (tool_obj,
|
|
property_spec_ref ("tool", "driver",
|
|
"3.18.2"));
|
|
if (!driver_obj)
|
|
return status::err_invalid_sarif;
|
|
|
|
const property_spec_ref name_prop ("toolComponent", "name", "3.19.8");
|
|
if (auto name_jstr = get_optional_property<json::string> (*driver_obj,
|
|
name_prop))
|
|
m_output_mgr.set_tool_name (name_jstr->get_string ());
|
|
|
|
const property_spec_ref full_name_prop
|
|
("toolComponent", "fullName", "3.19.9");
|
|
if (auto name_jstr = get_optional_property<json::string> (*driver_obj,
|
|
full_name_prop))
|
|
m_output_mgr.set_full_name (name_jstr->get_string ());
|
|
|
|
const property_spec_ref version_prop ("toolComponent", "version", "3.19.13");
|
|
if (auto name_jstr = get_optional_property<json::string> (*driver_obj,
|
|
version_prop))
|
|
m_output_mgr.set_version_string (name_jstr->get_string ());
|
|
|
|
const property_spec_ref
|
|
info_uri_prop ("toolComponent", "informationUri", "3.19.17");
|
|
if (auto name_jstr = get_optional_property<json::string> (*driver_obj,
|
|
info_uri_prop))
|
|
m_output_mgr.set_version_url (name_jstr->get_string ());
|
|
|
|
return status::ok;
|
|
}
|
|
|
|
/* Compare the value of JSON_STR to the values in VALUE_ARR.
|
|
If found, return it in the result's m_val.
|
|
Otherwise, complain using PROP and return status::invalid_sarif
|
|
in results's m_err. */
|
|
|
|
template <typename ValueType>
|
|
json::result<ValueType, enum status>
|
|
sarif_replayer::
|
|
get_value_from_json_string (const json::string &json_str,
|
|
const property_spec_ref &prop,
|
|
const string_property_value<ValueType> *value_arr,
|
|
size_t num_values)
|
|
{
|
|
const char *str = json_str.get_string ();
|
|
for (size_t i = 0; i < num_values; i++)
|
|
if (strcmp (str, value_arr[i].m_string) == 0)
|
|
return value_arr[i].m_value;
|
|
return report_invalid_sarif (json_str, prop,
|
|
"unrecognized value for %qs: %qs",
|
|
prop.get_property_name (),
|
|
str);
|
|
}
|
|
|
|
const property_spec_ref prop_result_level ("result", "level", "3.27.10");
|
|
|
|
/* Handle a value for result's "level" property (§3.27.10).
|
|
Limitation: doesn't yet support "none". */
|
|
json::result<enum diagnostic_level, enum status>
|
|
sarif_replayer::get_level_from_level_str (const json::string &level_str)
|
|
{
|
|
if (strcmp (level_str.get_string (), "none") == 0)
|
|
return report_unhandled_sarif (level_str, prop_result_level,
|
|
"unable to handle value for %qs: %qs",
|
|
prop_result_level.get_property_name (),
|
|
level_str.get_string ());
|
|
|
|
const string_property_value<enum diagnostic_level> level_values[]
|
|
= { {"warning",
|
|
DIAGNOSTIC_LEVEL_WARNING},
|
|
{"error",
|
|
DIAGNOSTIC_LEVEL_ERROR},
|
|
{"note",
|
|
DIAGNOSTIC_LEVEL_NOTE} };
|
|
return get_value_from_json_string<enum diagnostic_level>
|
|
(level_str,
|
|
prop_result_level,
|
|
level_values, ARRAY_SIZE (level_values));
|
|
}
|
|
|
|
static void
|
|
add_any_annotations (libgdiagnostics::diagnostic &diag,
|
|
std::vector<annotation> &annotations)
|
|
{
|
|
for (auto &annotation : annotations)
|
|
if (annotation.m_label.m_inner)
|
|
diag.add_location_with_label (annotation.m_phys_loc,
|
|
std::move (annotation.m_label));
|
|
else
|
|
diag.add_location (annotation.m_phys_loc);
|
|
}
|
|
|
|
static bool
|
|
should_add_rule_p (const char *rule_id_str, const char *url)
|
|
{
|
|
if (url)
|
|
return true;
|
|
|
|
/* GCC's sarif output uses "error" for "ruleId", which is already
|
|
captured in the "level", so don't add a rule for that. */
|
|
if (!strcmp (rule_id_str, "error"))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Process a result object (SARIF v2.1.0 section 3.27).
|
|
Known limitations:
|
|
- doesn't yet handle "ruleIndex" property (§3.27.6)
|
|
- doesn't yet handle "taxa" property (§3.27.8)
|
|
- handling of "level" property (§3.27.10) doesn't yet support the
|
|
full logic for when "level" is absent.
|
|
- doesn't yet support multithreaded flows (§3.36.3)
|
|
*/
|
|
|
|
#define PROP_result_ruleId \
|
|
property_spec_ref ("result", "ruleId", "3.27.5")
|
|
|
|
#define PROP_result_message \
|
|
property_spec_ref ("result", "message", "3.27.11")
|
|
|
|
enum status
|
|
sarif_replayer::handle_result_obj (const json::object &result_obj,
|
|
const json::object &run_obj,
|
|
const json::object &tool_obj)
|
|
{
|
|
const json::object *rule_obj = nullptr;
|
|
const json::object *tool_component_obj = nullptr;
|
|
const json::string *rule_id
|
|
= get_optional_property<json::string> (result_obj, PROP_result_ruleId);
|
|
if (rule_id)
|
|
{
|
|
rule_obj = lookup_rule_by_id_in_tool (rule_id->get_string (),
|
|
tool_obj,
|
|
tool_component_obj);
|
|
// TODO: error handling
|
|
}
|
|
|
|
enum diagnostic_level level = DIAGNOSTIC_LEVEL_WARNING;
|
|
if (auto level_str
|
|
= get_optional_property<json::string> (result_obj,
|
|
prop_result_level))
|
|
{
|
|
auto result = get_level_from_level_str (*level_str);
|
|
if (result.m_err != status::ok)
|
|
return result.m_err;
|
|
level = result.m_val;
|
|
}
|
|
|
|
// §3.27.11 "message" property
|
|
libgdiagnostics::message_buffer msg_buf;
|
|
if (auto message_obj
|
|
= get_optional_property<json::object> (result_obj, PROP_result_message))
|
|
msg_buf = make_plain_text_within_result_message (nullptr, // TODO: tool_component_obj,
|
|
*message_obj,
|
|
rule_obj);
|
|
if (!msg_buf.m_inner)
|
|
return status::err_invalid_sarif;
|
|
|
|
// §3.27.12 "locations" property
|
|
libgdiagnostics::physical_location physical_loc;
|
|
libgdiagnostics::logical_location logical_loc;
|
|
const property_spec_ref locations_prop ("result", "locations", "3.27.12");
|
|
const json::array *locations_arr
|
|
= get_required_property<json::array> (result_obj, locations_prop);
|
|
if (!locations_arr)
|
|
return status::err_invalid_sarif;
|
|
std::vector<annotation> annotations;
|
|
if (locations_arr->length () > 0)
|
|
{
|
|
/* Only look at the first, if there's more than one. */
|
|
// location objects (§3.28)
|
|
const json::object *location_obj
|
|
= require_object_for_element (*locations_arr->get (0), locations_prop);
|
|
if (!location_obj)
|
|
return status::err_invalid_sarif;
|
|
enum status s = handle_location_object (*location_obj, run_obj,
|
|
physical_loc,
|
|
logical_loc,
|
|
&annotations);
|
|
if (s != status::ok)
|
|
return s;
|
|
}
|
|
|
|
// §3.27.18 "codeFlows" property
|
|
libgdiagnostics::execution_path path;
|
|
const property_spec_ref code_flows ("result", "codeFlows", "3.27.18");
|
|
if (auto code_flows_arr = get_optional_property<json::array> (result_obj,
|
|
code_flows))
|
|
{
|
|
// TODO: what if more than one?
|
|
if (code_flows_arr->length () == 1)
|
|
{
|
|
const json::object *code_flow_obj
|
|
= require_object_for_element (*code_flows_arr->get (0), code_flows);
|
|
if (!code_flow_obj)
|
|
return status::err_invalid_sarif;
|
|
|
|
const property_spec_ref prop_thread_flows
|
|
("result", "threadFlows", "3.36.3");
|
|
if (auto thread_flows_arr
|
|
= get_optional_property<json::array> (*code_flow_obj,
|
|
prop_thread_flows))
|
|
{
|
|
if (thread_flows_arr->length () == 1)
|
|
{
|
|
const json::object *thread_flow_obj
|
|
= require_object_for_element (*thread_flows_arr->get (0),
|
|
prop_thread_flows);
|
|
if (!thread_flow_obj)
|
|
return status::err_invalid_sarif;
|
|
handle_thread_flow_object (*thread_flow_obj, run_obj, path);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
libgdiagnostics::group g (m_output_mgr);
|
|
auto err (m_output_mgr.begin_diagnostic (level));
|
|
if (rule_id)
|
|
{
|
|
const char *url = nullptr;
|
|
if (rule_obj)
|
|
{
|
|
/* rule_obj should be a reportingDescriptor object (3.49).
|
|
Get any §3.49.12 helpUri property. */
|
|
const property_spec_ref prop_help_uri
|
|
("reportingDescriptor", "helpUri", "3.49.12");
|
|
if (auto url_val = get_optional_property<json::string>(*rule_obj,
|
|
prop_help_uri))
|
|
url = url_val->get_string ();
|
|
}
|
|
|
|
const char *rule_id_str = rule_id->get_string ();
|
|
if (should_add_rule_p (rule_id_str, url))
|
|
err.add_rule (rule_id_str, url);
|
|
}
|
|
err.set_location (physical_loc);
|
|
err.set_logical_location (logical_loc);
|
|
add_any_annotations (err, annotations);
|
|
if (path.m_inner)
|
|
err.take_execution_path (std::move (path));
|
|
|
|
// §3.27.19 "graphs" property
|
|
const property_spec_ref prop_graphs ("result", "graphs", "3.27.19");
|
|
if (const json::array *graphs
|
|
= get_optional_property<json::array> (result_obj,
|
|
prop_graphs))
|
|
{
|
|
for (auto element : *graphs)
|
|
{
|
|
if (const json::object *graph_json_obj
|
|
= require_object_for_element (*element, prop_graphs))
|
|
{
|
|
libgdiagnostics::graph graph;
|
|
enum status s = handle_graph_object (*graph_json_obj,
|
|
run_obj,
|
|
graph);
|
|
if (s != status::ok)
|
|
return s;
|
|
|
|
err.take_graph (std::move (graph));
|
|
}
|
|
else
|
|
return status::err_invalid_sarif;
|
|
}
|
|
}
|
|
|
|
// §3.27.22 relatedLocations property
|
|
std::vector<std::pair<libgdiagnostics::diagnostic,
|
|
libgdiagnostics::message_buffer>> notes;
|
|
const property_spec_ref prop_related_locations
|
|
("result", "relatedLocations", "3.27.22");
|
|
if (auto related_locations_arr
|
|
= get_optional_property<json::array> (result_obj,
|
|
prop_related_locations))
|
|
{
|
|
for (auto rel_loc : *related_locations_arr)
|
|
{
|
|
libgdiagnostics::physical_location physical_loc;
|
|
libgdiagnostics::logical_location logical_loc;
|
|
const json::object *location_obj
|
|
= require_object_for_element (*rel_loc,
|
|
prop_related_locations);
|
|
if (!location_obj)
|
|
return status::err_invalid_sarif;
|
|
std::vector<annotation> annotations;
|
|
enum status s = handle_location_object (*location_obj, run_obj,
|
|
physical_loc,
|
|
logical_loc,
|
|
&annotations);
|
|
if (s != status::ok)
|
|
return s;
|
|
|
|
// §3.28.5 message property
|
|
const property_spec_ref prop_message
|
|
("location", "message", "3.28.5");
|
|
if (auto message_obj
|
|
= get_optional_property<json::object> (*location_obj,
|
|
prop_message))
|
|
{
|
|
/* Treat related locations with a message as a "note". */
|
|
libgdiagnostics::message_buffer msg_buf
|
|
(make_plain_text_within_result_message
|
|
(tool_component_obj,
|
|
*message_obj,
|
|
rule_obj));
|
|
if (!msg_buf.m_inner)
|
|
return status::err_invalid_sarif;
|
|
|
|
auto note (m_output_mgr.begin_diagnostic (DIAGNOSTIC_LEVEL_NOTE));
|
|
note.set_location (physical_loc);
|
|
note.set_logical_location (logical_loc);
|
|
add_any_annotations (note, annotations);
|
|
|
|
/* Look for "nestingLevel" property, as per
|
|
"P3358R0 SARIF for Structured Diagnostics"
|
|
https://wg21.link/P3358R0 */
|
|
if (auto nesting_level
|
|
= maybe_get_property_bag_value<json::integer_number>
|
|
(*location_obj,
|
|
"nestingLevel"))
|
|
private_diagnostic_set_nesting_level (note.m_inner,
|
|
nesting_level->get ());
|
|
|
|
notes.push_back ({std::move (note), std::move (msg_buf)});
|
|
}
|
|
else
|
|
{
|
|
/* Treat related locations without a message as a secondary
|
|
range. */
|
|
if (physical_loc.m_inner)
|
|
err.add_location (physical_loc);
|
|
}
|
|
}
|
|
}
|
|
|
|
// §3.27.30 "fixes" property
|
|
const property_spec_ref prop_fixes ("result", "fixes", "3.27.30");
|
|
if (auto fixes_arr
|
|
= get_optional_property<json::array> (result_obj, prop_fixes))
|
|
{
|
|
// We only support a single fix
|
|
if (fixes_arr->length () == 1)
|
|
if (auto fix_obj = require_object (*fixes_arr->get (0), prop_fixes))
|
|
handle_fix_object (err, *fix_obj);
|
|
}
|
|
|
|
err.finish_via_msg_buf (std::move (msg_buf));
|
|
|
|
// Flush any notes
|
|
for (auto &iter : notes)
|
|
{
|
|
auto ¬e = iter.first;
|
|
auto &msg_buf = iter.second;
|
|
note.finish_via_msg_buf (std::move (msg_buf));
|
|
}
|
|
|
|
return status::ok;
|
|
}
|
|
|
|
/* If ITER_SRC starts with a placeholder as per §3.11.5, advance ITER_SRC
|
|
to immediately beyond the placeholder, write to *OUT_ARG_IDX, and
|
|
return true.
|
|
|
|
Otherwise, leave ITER_SRC untouched and return false. */
|
|
|
|
static bool
|
|
maybe_consume_placeholder (const char *&iter_src, unsigned *out_arg_idx)
|
|
{
|
|
if (*iter_src != '{')
|
|
return false;
|
|
const char *first_digit = iter_src + 1;
|
|
const char *iter_digit = first_digit;
|
|
while (char ch = *iter_digit)
|
|
switch (ch)
|
|
{
|
|
default:
|
|
return false;
|
|
|
|
case '}':
|
|
if (iter_digit == first_digit)
|
|
{
|
|
/* No digits, we simply have "{}" which is not a placeholder
|
|
(and malformed: the braces should have been escaped). */
|
|
return false;
|
|
}
|
|
*out_arg_idx = atoi (first_digit);
|
|
iter_src = iter_digit + 1;
|
|
return true;
|
|
|
|
case '0': case '1': case '2': case '3': case '4':
|
|
case '5': case '6': case '7': case '8': case '9':
|
|
/* TODO: what about multiple leading zeroes? */
|
|
iter_digit++;
|
|
continue;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
struct embedded_link
|
|
{
|
|
std::string text;
|
|
std::string destination;
|
|
};
|
|
|
|
/* If ITER_SRC starts with an embedded link as per §3.11.6, advance ITER_SRC
|
|
to immediately beyond the link, and return the link.
|
|
|
|
Otherwise, leave ITER_SRC untouched and return nullptr. */
|
|
|
|
static std::unique_ptr<embedded_link>
|
|
maybe_consume_embedded_link (const char *&iter_src)
|
|
{
|
|
if (*iter_src != '[')
|
|
return nullptr;
|
|
|
|
/* This *might* be an embedded link.
|
|
See §3.11.6 ("Messages with embedded links") and
|
|
https://github.com/oasis-tcs/sarif-spec/issues/657 */
|
|
|
|
/* embedded link = "[", link text, "](", link destination, ")"; */
|
|
|
|
embedded_link result;
|
|
|
|
/* Try to get the link text. */
|
|
const char *iter = iter_src + 1;
|
|
while (char ch = *(iter++))
|
|
{
|
|
if (ch == '\\')
|
|
{
|
|
char next_ch = *iter;
|
|
switch (next_ch)
|
|
{
|
|
case '\\':
|
|
case '[':
|
|
case ']':
|
|
/* escaped link character = "\" | "[" | "]"; */
|
|
result.text += next_ch;
|
|
iter++;
|
|
continue;
|
|
|
|
default:
|
|
/* Malformed link text; assume this is not an
|
|
embedded link. */
|
|
return nullptr;
|
|
}
|
|
}
|
|
else if (ch == ']')
|
|
/* End of link text. */
|
|
break;
|
|
else
|
|
result.text += ch;
|
|
}
|
|
|
|
if (*iter++ != '(')
|
|
return nullptr;
|
|
|
|
/* Try to get the link destination. */
|
|
while (1)
|
|
{
|
|
char ch = *(iter++);
|
|
if (ch == '\0')
|
|
{
|
|
/* String ended before terminating ')'.
|
|
Assume this is not an embedded link. */
|
|
return nullptr;
|
|
}
|
|
else if (ch == ')')
|
|
/* Terminator. */
|
|
break;
|
|
else
|
|
result.destination += ch;
|
|
}
|
|
|
|
iter_src = iter;
|
|
return std::make_unique<embedded_link> (std::move (result));
|
|
}
|
|
|
|
/* Lookup the plain text string within a result.message (§3.27.11),
|
|
and substitute for any placeholders (§3.11.5) and handle any
|
|
embedded links (§3.11.6).
|
|
|
|
MESSAGE_OBJ is "theMessage"
|
|
RULE_OBJ is "theRule". */
|
|
|
|
libgdiagnostics::message_buffer
|
|
sarif_replayer::
|
|
make_plain_text_within_result_message (const json::object *tool_component_obj,
|
|
const json::object &message_obj,
|
|
const json::object *rule_obj)
|
|
{
|
|
const json::string *js_str = nullptr;
|
|
const char *original_text
|
|
= lookup_plain_text_within_result_message (tool_component_obj,
|
|
message_obj,
|
|
rule_obj,
|
|
js_str);
|
|
if (!original_text)
|
|
return libgdiagnostics::message_buffer ();
|
|
|
|
gcc_assert (js_str);
|
|
|
|
/* Look up any arguments for substituting into placeholders. */
|
|
const property_spec_ref arguments_prop ("message", "arguments", "3.11.11");
|
|
const json::array *arguments
|
|
= get_optional_property<json::array> (message_obj, arguments_prop);
|
|
|
|
/* Duplicate original_text, substituting any placeholders. */
|
|
libgdiagnostics::message_buffer result (diagnostic_message_buffer_new ());
|
|
|
|
const char *iter_src = original_text;
|
|
while (char ch = *iter_src)
|
|
{
|
|
unsigned arg_idx;
|
|
if (maybe_consume_placeholder (iter_src, &arg_idx))
|
|
{
|
|
if (!arguments)
|
|
{
|
|
report_invalid_sarif
|
|
(message_obj, arguments_prop,
|
|
"message string contains placeholder %<{%i}%>"
|
|
" but message object has no %qs property",
|
|
(int)arg_idx,
|
|
arguments_prop.get_property_name ());
|
|
return libgdiagnostics::message_buffer ();
|
|
}
|
|
if (arg_idx >= arguments->length ())
|
|
{
|
|
report_invalid_sarif
|
|
(message_obj, arguments_prop,
|
|
"not enough strings in %qs array for"
|
|
" placeholder %<{%i}%>",
|
|
arguments_prop.get_property_name (),
|
|
(int)arg_idx);
|
|
// TODO: might be nice to add a note showing the args
|
|
return libgdiagnostics::message_buffer ();
|
|
}
|
|
auto replacement_jstr
|
|
= require_string (*arguments->get (arg_idx), arguments_prop);
|
|
if (!replacement_jstr)
|
|
return libgdiagnostics::message_buffer ();
|
|
result += replacement_jstr->get_string ();
|
|
}
|
|
else if (ch == '{' || ch == '}')
|
|
{
|
|
/* '{' and '}' are escaped by repeating them. */
|
|
if (iter_src[1] == ch)
|
|
{
|
|
result += ch;
|
|
iter_src += 2;
|
|
}
|
|
else
|
|
{
|
|
const spec_ref msgs_with_placeholders ("3.11.5");
|
|
gcc_assert (js_str);
|
|
report_invalid_sarif (*js_str, msgs_with_placeholders,
|
|
"unescaped '%c' within message string",
|
|
ch);
|
|
return libgdiagnostics::message_buffer ();
|
|
}
|
|
}
|
|
else if (auto link = maybe_consume_embedded_link (iter_src))
|
|
{
|
|
result.begin_url (link->destination.c_str ());
|
|
result += link->text.c_str ();
|
|
result.end_url ();
|
|
/* TODO: potentially could try to convert
|
|
intra-sarif links into event ids. */
|
|
}
|
|
else
|
|
{
|
|
result += ch;
|
|
iter_src++;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/* Handle a value that should be a multiformatMessageString object (§3.12).
|
|
Complain using prop if MFMS_VAL is not an object.
|
|
Return get the "text" value (or nullptr, and complain).
|
|
If the result is non-null, write the json::string that was actually used
|
|
to OUT_JS_STR. */
|
|
|
|
const char *
|
|
sarif_replayer::get_plain_text_from_mfms (json::value &mfms_val,
|
|
const property_spec_ref &prop,
|
|
const json::string *&out_js_str)
|
|
{
|
|
auto mfms_obj = require_object (mfms_val, prop);
|
|
if (!mfms_obj)
|
|
return nullptr;
|
|
|
|
const property_spec_ref text_prop
|
|
("multiformatMessageString", "text", "3.12.3");
|
|
auto text_jstr = get_required_property<json::string> (*mfms_obj, text_prop);
|
|
if (!text_jstr)
|
|
return nullptr;
|
|
out_js_str = text_jstr;
|
|
return text_jstr->get_string ();
|
|
}
|
|
|
|
#define PROP_message_text \
|
|
property_spec_ref ("message", "text", "3.11.8")
|
|
|
|
#define PROP_message_id \
|
|
property_spec_ref ("message", "id", "3.11.10")
|
|
|
|
/* Implement the message string lookup algorithm from
|
|
SARIF v2.1.0 section 3.11.7, for the case where theMessage
|
|
is the value of result.message (§3.27.11).
|
|
|
|
MESSAGE_OBJ is "theMessage"
|
|
RULE_OBJ is "theRule".
|
|
|
|
If the result is non-null, write the json::string that was actually used
|
|
to OUT_JS_STR. */
|
|
|
|
const char *
|
|
sarif_replayer::
|
|
lookup_plain_text_within_result_message (const json::object *tool_component_obj,
|
|
const json::object &message_obj,
|
|
const json::object *rule_obj,
|
|
const json::string *&out_js_str)
|
|
{
|
|
// rule_obj can be NULL
|
|
|
|
/* IF theMessage.text is present and the desired language is theRun.language THEN
|
|
Use the text or markdown property of theMessage as appropriate. */
|
|
if (const json::string *str
|
|
= get_optional_property<json::string> (message_obj, PROP_message_text))
|
|
{
|
|
// TODO: check language
|
|
out_js_str = str;
|
|
return str->get_string ();
|
|
}
|
|
|
|
if (rule_obj)
|
|
if (auto message_id_jstr
|
|
= get_optional_property<json::string> (message_obj, PROP_message_id))
|
|
{
|
|
const char *message_id = message_id_jstr->get_string ();
|
|
const property_spec_ref message_strings
|
|
("reportingDescriptor", "messageStrings", "3.49.11");
|
|
if (auto message_strings_obj
|
|
= get_optional_property<json::object> (*rule_obj,
|
|
message_strings))
|
|
if (json::value *mfms = message_strings_obj->get (message_id))
|
|
return get_plain_text_from_mfms (*mfms, message_strings, out_js_str);
|
|
|
|
/* Look up by theMessage.id within theComponent.globalMessageStrings
|
|
(§3.19.22). */
|
|
if (tool_component_obj)
|
|
{
|
|
const property_spec_ref prop_gms
|
|
("toolComponent", "globalMessageStrings", "3.19.22");
|
|
if (auto global_message_strings
|
|
= get_optional_property<json::object> (*tool_component_obj,
|
|
prop_gms))
|
|
if (auto mfms = global_message_strings->get (message_id))
|
|
return get_plain_text_from_mfms (*mfms, prop_gms, out_js_str);
|
|
}
|
|
}
|
|
|
|
/* Failure. */
|
|
report_invalid_sarif (message_obj, spec_ref ("3.11.7"),
|
|
"could not find string for %<message%> object");
|
|
return nullptr;
|
|
}
|
|
|
|
/* Populate OUT for THREAD_FLOW_OBJ, a
|
|
SARIF threadFlow object (section 3.37). */
|
|
|
|
enum status
|
|
sarif_replayer::handle_thread_flow_object (const json::object &thread_flow_obj,
|
|
const json::object &run_obj,
|
|
libgdiagnostics::execution_path &out)
|
|
{
|
|
const property_spec_ref locations ("threadFlow", "locations", "3.37.6");
|
|
const json::array *locations_arr
|
|
= get_required_property<json::array> (thread_flow_obj, locations);
|
|
if (!locations_arr)
|
|
return status::err_invalid_sarif;
|
|
|
|
out = m_output_mgr.new_execution_path ();
|
|
for (auto location : *locations_arr)
|
|
{
|
|
/* threadFlowLocation object (§3.38). */
|
|
const json::object *tflow_loc_obj
|
|
= require_object_for_element (*location, locations);
|
|
if (!tflow_loc_obj)
|
|
return status::err_invalid_sarif;
|
|
handle_thread_flow_location_object (*tflow_loc_obj, run_obj, out);
|
|
}
|
|
|
|
return status::ok;
|
|
}
|
|
|
|
/* "threadFlowLocation" object (§3.38).
|
|
Attempt to add an event for TFLOW_LOC_OBJ to PATH. */
|
|
|
|
enum status
|
|
sarif_replayer::
|
|
handle_thread_flow_location_object (const json::object &tflow_loc_obj,
|
|
const json::object &run_obj,
|
|
libgdiagnostics::execution_path &path)
|
|
{
|
|
libgdiagnostics::physical_location physical_loc;
|
|
libgdiagnostics::logical_location logical_loc;
|
|
libgdiagnostics::message_buffer msg_buf;
|
|
int stack_depth = 0;
|
|
|
|
const property_spec_ref location_prop
|
|
("threadFlowLocation", "location", "3.38.3");
|
|
if (auto location_obj = get_optional_property<json::object> (tflow_loc_obj,
|
|
location_prop))
|
|
{
|
|
/* location object (§3.28). */
|
|
enum status s = handle_location_object (*location_obj, run_obj,
|
|
physical_loc, logical_loc,
|
|
nullptr);
|
|
if (s != status::ok)
|
|
return s;
|
|
|
|
/* Get any message from here. */
|
|
const property_spec_ref location_message
|
|
("location", "message", "3.28.5");
|
|
if (auto message_obj
|
|
= get_optional_property<json::object> (*location_obj,
|
|
location_message))
|
|
{
|
|
msg_buf = make_plain_text_within_result_message
|
|
(nullptr,
|
|
*message_obj,
|
|
nullptr/* TODO. */);
|
|
}
|
|
}
|
|
|
|
// §3.38.8 "kinds" property
|
|
const property_spec_ref kinds ("threadFlowLocation", "kinds", "3.38.8");
|
|
if (auto kinds_arr
|
|
= get_optional_property<json::array> (tflow_loc_obj, kinds))
|
|
{
|
|
std::vector<const char *> kind_strs;
|
|
for (auto iter : *kinds_arr)
|
|
{
|
|
const json::string *kind_str = dyn_cast <const json::string *> (iter);
|
|
if (!kind_str)
|
|
{
|
|
}
|
|
kind_strs.push_back (kind_str->get_string ());
|
|
// TODO: handle meaning?
|
|
/* TOOD: probably just want to add sarif kinds to
|
|
the libgdiagnostics event, and have libgdiagnostics
|
|
turn that back into a "meaning". */
|
|
}
|
|
}
|
|
|
|
/* nestingLevel property (§3.38.10). */
|
|
const property_spec_ref nesting_level
|
|
("threadFlowLocation", "nestingLevel", "3.38.10");
|
|
if (auto nesting_level_jv
|
|
= get_optional_property<json::integer_number> (tflow_loc_obj,
|
|
nesting_level))
|
|
{
|
|
stack_depth = nesting_level_jv->get ();
|
|
if (stack_depth < 0)
|
|
{
|
|
return report_invalid_sarif (tflow_loc_obj, nesting_level,
|
|
"expected a non-negative integer");
|
|
}
|
|
}
|
|
|
|
libgdiagnostics::graph state_graph;
|
|
if (auto sarif_state_graph
|
|
= maybe_get_property_bag_value<json::object> (tflow_loc_obj,
|
|
"gcc/diagnostics/paths/event/state_graph"))
|
|
{
|
|
enum status s
|
|
= handle_graph_object (*sarif_state_graph, run_obj, state_graph);
|
|
if (s != status::ok)
|
|
return s;
|
|
}
|
|
|
|
if (!msg_buf.m_inner)
|
|
msg_buf.m_inner = diagnostic_message_buffer_new ();
|
|
|
|
private_diagnostic_execution_path_add_event_3 (path.m_inner,
|
|
physical_loc.m_inner,
|
|
logical_loc.m_inner,
|
|
stack_depth,
|
|
state_graph.m_inner,
|
|
msg_buf.m_inner);
|
|
|
|
state_graph.m_owned = false;
|
|
msg_buf.m_inner = nullptr;
|
|
|
|
return status::ok;
|
|
}
|
|
|
|
/* Handle LOCATION_OBJ, a "location" (§3.28). */
|
|
|
|
enum status
|
|
sarif_replayer::
|
|
handle_location_object (const json::object &location_obj,
|
|
const json::object &run_obj,
|
|
libgdiagnostics::physical_location &out_physical_loc,
|
|
libgdiagnostics::logical_location &out_logical_loc,
|
|
std::vector<annotation> *out_annotations)
|
|
{
|
|
// §3.28.3 "physicalLocation" property
|
|
{
|
|
const property_spec_ref physical_location_prop
|
|
("location", "physicalLocation", "3.28.3");
|
|
if (const json::object *phys_loc_obj
|
|
= get_optional_property<json::object> (location_obj,
|
|
physical_location_prop))
|
|
{
|
|
enum status s = handle_physical_location_object (*phys_loc_obj,
|
|
out_physical_loc);
|
|
if (s!= status::ok)
|
|
return s;
|
|
}
|
|
}
|
|
|
|
// §3.28.4 "logicalLocations" property
|
|
{
|
|
const property_spec_ref logical_locations_prop
|
|
("location", "logicalLocations", "3.28.4");
|
|
if (const json::array *logical_loc_arr
|
|
= get_optional_property<json::array> (location_obj,
|
|
logical_locations_prop))
|
|
if (logical_loc_arr->length () > 0)
|
|
{
|
|
/* Only look at the first, if there's more than one. */
|
|
const json::object *logical_loc_obj
|
|
= require_object_for_element (*logical_loc_arr->get (0),
|
|
logical_locations_prop);
|
|
if (!logical_loc_obj)
|
|
return status::err_invalid_sarif;
|
|
enum status s = handle_logical_location_object (*logical_loc_obj,
|
|
&run_obj,
|
|
out_logical_loc);
|
|
if (s != status::ok)
|
|
return s;
|
|
}
|
|
}
|
|
|
|
// §3.28.6 "annotations" property
|
|
{
|
|
const property_spec_ref annotations_prop
|
|
("location", "annotations", "3.28.6");
|
|
if (const json::array *annotations_arr
|
|
= get_optional_property<json::array> (location_obj,
|
|
annotations_prop))
|
|
for (auto element : *annotations_arr)
|
|
{
|
|
const json::object *annotation_obj
|
|
= require_object_for_element (*element, annotations_prop);
|
|
if (!annotation_obj)
|
|
return status::err_invalid_sarif;
|
|
libgdiagnostics::file file = out_physical_loc.get_file ();
|
|
if (!file.m_inner)
|
|
return report_invalid_sarif
|
|
(*annotation_obj, annotations_prop,
|
|
"cannot find artifact for %qs property",
|
|
annotations_prop.get_property_name ());
|
|
libgdiagnostics::physical_location phys_loc;
|
|
enum status s = handle_region_object (*annotation_obj,
|
|
file,
|
|
phys_loc);
|
|
if (s != status::ok)
|
|
return s;
|
|
|
|
libgdiagnostics::message_buffer label;
|
|
|
|
// §3.30.14 message property
|
|
{
|
|
const property_spec_ref message_prop
|
|
("region", "message", "3.30.14");
|
|
|
|
if (const json::object *message_obj
|
|
= get_optional_property<json::object> (*annotation_obj,
|
|
message_prop))
|
|
label = make_plain_text_within_result_message (nullptr,
|
|
*message_obj,
|
|
nullptr);
|
|
}
|
|
|
|
if (out_annotations)
|
|
out_annotations->push_back ({phys_loc, std::move (label)});
|
|
}
|
|
}
|
|
|
|
return status::ok;
|
|
}
|
|
|
|
/* Handle PHYS_LOC_OBJ, a "physicalLocation" object (§3.29).
|
|
Limitations:
|
|
- we don't yet support the "contextRegion" property (§3.29.5) */
|
|
|
|
enum status
|
|
sarif_replayer::
|
|
handle_physical_location_object (const json::object &phys_loc_obj,
|
|
libgdiagnostics::physical_location &out)
|
|
{
|
|
libgdiagnostics::file artifact_file;
|
|
|
|
// §3.29.3 "artifactLocation" property
|
|
const property_spec_ref artifact_location_prop
|
|
("physicalLocation", "artifactLocation", "3.29.3");
|
|
if (const json::object *artifact_loc_obj
|
|
= get_optional_property<json::object> (phys_loc_obj,
|
|
artifact_location_prop))
|
|
{
|
|
enum status s = handle_artifact_location_object (*artifact_loc_obj,
|
|
artifact_file);
|
|
if (s != status::ok)
|
|
return s;
|
|
}
|
|
|
|
// §3.29.6 "address" property
|
|
const property_spec_ref artifact_address_prop
|
|
("physicalLocation", "address", "3.29.6");
|
|
|
|
if (!artifact_file.m_inner)
|
|
{
|
|
const object_spec_ref constraints ("physicalLocation", "3.29.2");
|
|
return report_invalid_sarif_at_least_one_of (phys_loc_obj,
|
|
constraints,
|
|
artifact_address_prop,
|
|
artifact_location_prop);
|
|
}
|
|
|
|
//3.29.4 region property
|
|
const property_spec_ref region_prop ("physicalLocation", "region", "3.29.4");
|
|
if (const json::object *region_obj
|
|
= get_optional_property<json::object> (phys_loc_obj, region_prop))
|
|
{
|
|
enum status s
|
|
= handle_region_object (*region_obj, artifact_file, out);
|
|
if (s != status::ok)
|
|
return s;
|
|
// TODO:
|
|
}
|
|
|
|
return status::ok;
|
|
}
|
|
|
|
/* Handle ARTIFACT_LOC, an "artifactLocation" object (§3.4). */
|
|
|
|
enum status
|
|
sarif_replayer::handle_artifact_location_object (const json::object &artifact_loc,
|
|
libgdiagnostics::file &out)
|
|
{
|
|
// §3.4.3 "uri" property
|
|
const property_spec_ref uri_prop ("artifactLocation", "uri", "3.4.3");
|
|
auto uri = get_optional_property<json::string> (artifact_loc, uri_prop);
|
|
|
|
// §3.4.5 "index" property
|
|
const property_spec_ref index_prop ("artifactLocation", "index", "3.4.5");
|
|
auto index = get_optional_property<json::integer_number> (artifact_loc,
|
|
index_prop);
|
|
if (uri == nullptr && index == nullptr)
|
|
{
|
|
object_spec_ref constraints ("artifactLocation", "3.4.2");
|
|
return report_invalid_sarif_at_least_one_of (artifact_loc,
|
|
constraints,
|
|
uri_prop,
|
|
index_prop);
|
|
}
|
|
|
|
if (uri)
|
|
{
|
|
// TODO: source language
|
|
out = m_output_mgr.new_file (uri->get_string (), nullptr);
|
|
return status::ok;
|
|
}
|
|
|
|
return status::ok;
|
|
}
|
|
|
|
/* Handle a "region" object (§3.30) within FILE, writing to OUT. */
|
|
|
|
enum status
|
|
sarif_replayer::
|
|
handle_region_object (const json::object ®ion_obj,
|
|
libgdiagnostics::file file,
|
|
libgdiagnostics::physical_location &out)
|
|
{
|
|
gcc_assert (file.m_inner);
|
|
|
|
// §3.30.5 "startLine" property
|
|
const property_spec_ref start_line_prop ("region", "startLine", "3.30.5");
|
|
libgdiagnostics::physical_location start;
|
|
libgdiagnostics::physical_location end;
|
|
if (auto start_line_jnum
|
|
= get_optional_property<json::integer_number> (region_obj,
|
|
start_line_prop))
|
|
{
|
|
/* Text region defined by line/column properties. */
|
|
const property_spec_ref start_column_prop
|
|
("region", "startColumn", "3.30.6");
|
|
if (auto start_column_jnum
|
|
= get_optional_property<json::integer_number> (region_obj,
|
|
start_column_prop))
|
|
{
|
|
start = m_output_mgr.new_location_from_file_line_column
|
|
(file, start_line_jnum->get (), start_column_jnum->get ());
|
|
}
|
|
else
|
|
start = m_output_mgr.new_location_from_file_and_line
|
|
(file, start_line_jnum->get ());
|
|
|
|
int end_line = start_line_jnum->get ();
|
|
const property_spec_ref end_line_prop ("region", "endLine", "3.30.7");
|
|
if (auto end_line_jnum
|
|
= get_optional_property<json::integer_number> (region_obj,
|
|
end_line_prop))
|
|
end_line = end_line_jnum->get ();
|
|
|
|
const property_spec_ref end_column_prop ("region", "endColumn", "3.30.8");
|
|
if (auto end_column_jnum
|
|
= get_optional_property<json::integer_number> (region_obj,
|
|
end_column_prop))
|
|
{
|
|
/* SARIF's endColumn is 1 beyond the final column in the region,
|
|
whereas GCC's end columns are inclusive. */
|
|
end = m_output_mgr.new_location_from_file_line_column
|
|
(file, end_line, end_column_jnum->get () - 1);
|
|
}
|
|
else
|
|
{
|
|
// missing "endColumn" means the whole of the rest of the row
|
|
end = m_output_mgr.new_location_from_file_and_line
|
|
(file, end_line);
|
|
}
|
|
|
|
out = m_output_mgr.new_location_from_range (start, start, end);
|
|
}
|
|
|
|
return status::ok;
|
|
}
|
|
|
|
/* Handle a "logicalLocation" object (§3.33), using it to populate OUT.
|
|
|
|
If RUN_OBJ is non-null, and has "logicalLocations", then use it if we
|
|
see an "index" property.
|
|
|
|
Known limitations:
|
|
- doesn't yet handle "parentIndex" property (§3.33.8)
|
|
*/
|
|
|
|
enum status
|
|
sarif_replayer::
|
|
handle_logical_location_object (const json::object &logical_loc_obj,
|
|
const json::object *run_obj,
|
|
libgdiagnostics::logical_location &out)
|
|
{
|
|
if (run_obj)
|
|
{
|
|
const property_spec_ref name_prop ("logicalLocation", "index", "3.33.3");
|
|
if (auto index_js_int
|
|
= get_optional_property<json::integer_number> (logical_loc_obj,
|
|
name_prop))
|
|
{
|
|
const long index = index_js_int->get ();
|
|
/* If "index" is present, then there must be theRun.logicalLocations
|
|
and it must have a cached object at that index. */
|
|
const property_spec_ref run_logical_locs_prop
|
|
("run", "logicalLocations", "3.14.17");
|
|
if (auto logical_locs_arr
|
|
= get_required_property<json::array> (*run_obj,
|
|
run_logical_locs_prop))
|
|
{
|
|
/* "index" must be in range */
|
|
if (index < 0 || index >= (long)logical_locs_arr->length ())
|
|
return report_invalid_sarif (*index_js_int, name_prop,
|
|
"index value %li is out of range"
|
|
" for theRun.logicalLocations",
|
|
index);
|
|
|
|
json::value *element = logical_locs_arr->get (index);
|
|
|
|
if (const json::object *obj
|
|
= require_object (*element,run_logical_locs_prop))
|
|
{
|
|
/* Use this "cached" object instead.
|
|
Pass nullptr for the run object so that we
|
|
don't recurse. */
|
|
return handle_logical_location_object (*obj, nullptr, out);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Otherwise use the properties from LOGICAL_LOC_OBJ.
|
|
|
|
const property_spec_ref name_prop ("logicalLocation", "name", "3.33.4");
|
|
const char *short_name = nullptr;
|
|
if (auto name_jstr = get_optional_property<json::string> (logical_loc_obj,
|
|
name_prop))
|
|
short_name = name_jstr->get_string ();
|
|
|
|
const property_spec_ref fqname_prop
|
|
("logicalLocation", "fullyQualifiedName", "3.33.5");
|
|
const char *fully_qualified_name = nullptr;
|
|
if (auto fully_qualified_name_jstr
|
|
= get_optional_property<json::string> (logical_loc_obj, fqname_prop))
|
|
fully_qualified_name = fully_qualified_name_jstr->get_string ();
|
|
|
|
const property_spec_ref decorated_name_prop
|
|
("logicalLocation", "decoratedName", "3.33.6");
|
|
const char *decorated_name = nullptr;
|
|
if (auto decorated_name_jstr
|
|
= get_optional_property<json::string> (logical_loc_obj,
|
|
decorated_name_prop))
|
|
decorated_name = decorated_name_jstr->get_string ();
|
|
|
|
// §3.33.7 "kind" property
|
|
const property_spec_ref kind_prop ("logicalLocation", "kind", "3.33.7");
|
|
enum diagnostic_logical_location_kind_t kind
|
|
= DIAGNOSTIC_LOGICAL_LOCATION_KIND_FUNCTION;
|
|
if (auto kind_str = get_optional_property<json::string> (logical_loc_obj,
|
|
kind_prop))
|
|
{
|
|
const string_property_value<enum diagnostic_logical_location_kind_t>
|
|
kind_values[]
|
|
= {
|
|
{ "function",
|
|
DIAGNOSTIC_LOGICAL_LOCATION_KIND_FUNCTION },
|
|
{ "member",
|
|
DIAGNOSTIC_LOGICAL_LOCATION_KIND_MEMBER },
|
|
{ "module",
|
|
DIAGNOSTIC_LOGICAL_LOCATION_KIND_MODULE },
|
|
{ "namespace",
|
|
DIAGNOSTIC_LOGICAL_LOCATION_KIND_NAMESPACE },
|
|
{ "type",
|
|
DIAGNOSTIC_LOGICAL_LOCATION_KIND_TYPE },
|
|
{ "returnType",
|
|
DIAGNOSTIC_LOGICAL_LOCATION_KIND_RETURN_TYPE },
|
|
{ "parameter",
|
|
DIAGNOSTIC_LOGICAL_LOCATION_KIND_PARAMETER },
|
|
{ "variable",
|
|
DIAGNOSTIC_LOGICAL_LOCATION_KIND_VARIABLE },
|
|
|
|
{ "element",
|
|
DIAGNOSTIC_LOGICAL_LOCATION_KIND_ELEMENT },
|
|
{ "attribute",
|
|
DIAGNOSTIC_LOGICAL_LOCATION_KIND_ATTRIBUTE },
|
|
{ "text",
|
|
DIAGNOSTIC_LOGICAL_LOCATION_KIND_TEXT },
|
|
{ "comment",
|
|
DIAGNOSTIC_LOGICAL_LOCATION_KIND_COMMENT },
|
|
{ "processingInstruction",
|
|
DIAGNOSTIC_LOGICAL_LOCATION_KIND_PROCESSING_INSTRUCTION },
|
|
{ "dtd",
|
|
DIAGNOSTIC_LOGICAL_LOCATION_KIND_DTD },
|
|
{ "declaration",
|
|
DIAGNOSTIC_LOGICAL_LOCATION_KIND_DECLARATION },
|
|
|
|
{ "object",
|
|
DIAGNOSTIC_LOGICAL_LOCATION_KIND_OBJECT },
|
|
{ "array",
|
|
DIAGNOSTIC_LOGICAL_LOCATION_KIND_ARRAY },
|
|
{ "property",
|
|
DIAGNOSTIC_LOGICAL_LOCATION_KIND_PROPERTY },
|
|
{ "value",
|
|
DIAGNOSTIC_LOGICAL_LOCATION_KIND_VALUE },
|
|
};
|
|
auto result
|
|
= get_value_from_json_string<enum diagnostic_logical_location_kind_t>
|
|
(*kind_str, kind_prop, kind_values, ARRAY_SIZE (kind_values));
|
|
if (result.m_err != status::ok)
|
|
return result.m_err;
|
|
kind = result.m_val;
|
|
}
|
|
|
|
libgdiagnostics::logical_location parent;
|
|
out = m_output_mgr.new_logical_location (kind,
|
|
parent,
|
|
short_name,
|
|
fully_qualified_name,
|
|
decorated_name);
|
|
|
|
return status::ok;
|
|
}
|
|
|
|
// "graph" object (§3.39)
|
|
|
|
enum status
|
|
sarif_replayer::handle_graph_object (const json::object &graph_json_obj,
|
|
const json::object &run_obj,
|
|
libgdiagnostics::graph &out_graph)
|
|
{
|
|
out_graph = libgdiagnostics::graph
|
|
(diagnostic_manager_new_graph (m_output_mgr.m_inner));
|
|
|
|
id_map node_id_map;
|
|
id_map edge_id_map;
|
|
|
|
if (auto properties = maybe_get_property_bag (graph_json_obj))
|
|
private_diagnostic_graph_set_property_bag
|
|
(*out_graph.m_inner,
|
|
properties->clone_as_object ());
|
|
|
|
// §3.39.2: MAY contain a "description" property
|
|
const property_spec_ref description_prop
|
|
("graph", "description", "3.39.2");
|
|
if (auto description_obj
|
|
= get_optional_property<json::object> (graph_json_obj, description_prop))
|
|
{
|
|
auto msg_buf
|
|
= make_plain_text_within_result_message (&run_obj,
|
|
*description_obj,
|
|
nullptr);
|
|
if (!msg_buf.m_inner)
|
|
return status::err_invalid_sarif;
|
|
out_graph.set_description (std::move (msg_buf));
|
|
}
|
|
|
|
// §3.39.3: MAY contain a "nodes" property
|
|
const property_spec_ref nodes_prop
|
|
("graph", "nodes", "3.39.3");
|
|
if (auto nodes_arr
|
|
= get_optional_property<json::array> (graph_json_obj, nodes_prop))
|
|
{
|
|
for (auto element : *nodes_arr)
|
|
{
|
|
const json::object *node_json_obj
|
|
= require_object_for_element (*element, nodes_prop);
|
|
if (!node_json_obj)
|
|
return status::err_invalid_sarif;
|
|
libgdiagnostics::node node
|
|
= handle_node_object (*node_json_obj, run_obj, out_graph,
|
|
nullptr, node_id_map);
|
|
if (node.m_inner == nullptr)
|
|
return status::err_invalid_sarif;
|
|
}
|
|
}
|
|
else
|
|
// If we have no nodes, we can't handle the edges.
|
|
return status::ok;
|
|
|
|
// §3.39.4 edges property
|
|
const property_spec_ref edges_prop
|
|
("graph", "edges", "3.39.4");
|
|
if (auto edges_arr
|
|
= get_optional_property<json::array> (graph_json_obj, edges_prop))
|
|
{
|
|
for (auto element : *edges_arr)
|
|
{
|
|
const json::object *edge_json_obj
|
|
= require_object_for_element (*element, edges_prop);
|
|
if (!edge_json_obj)
|
|
return status::err_invalid_sarif;
|
|
libgdiagnostics::edge edge
|
|
= handle_edge_object (*edge_json_obj, out_graph, edge_id_map);
|
|
if (edge.m_inner == nullptr)
|
|
return status::err_invalid_sarif;
|
|
}
|
|
}
|
|
|
|
return status::ok;
|
|
}
|
|
|
|
// "node" object (§3.40)
|
|
|
|
libgdiagnostics::node
|
|
sarif_replayer::handle_node_object (const json::object &node_json_obj,
|
|
const json::object &run_obj,
|
|
libgdiagnostics::graph &graph,
|
|
libgdiagnostics::node parent_node,
|
|
id_map &node_id_map)
|
|
{
|
|
// §3.40.2 "id" property
|
|
const property_spec_ref id_prop ("node", "id", "3.40.2");
|
|
auto id_str = get_required_property<json::string> (node_json_obj, id_prop);
|
|
if (!id_str)
|
|
return nullptr;
|
|
const char *id = id_str->get_string ();
|
|
if (diagnostic_graph_get_node_by_id (graph.m_inner, id))
|
|
{
|
|
// Duplicate id; fail:
|
|
libgdiagnostics::group g (m_control_mgr);
|
|
report_invalid_sarif (*id_str,
|
|
id_prop,
|
|
"duplicate node id %qs within graph",
|
|
id);
|
|
gcc_assert (node_id_map[id]);
|
|
report_note (*node_id_map[id],
|
|
"%qs already used as node id within graph here",
|
|
id);
|
|
return nullptr;
|
|
}
|
|
node_id_map[id] = id_str;
|
|
|
|
libgdiagnostics::node new_node
|
|
= libgdiagnostics::node (diagnostic_graph_add_node (graph.m_inner,
|
|
id,
|
|
parent_node.m_inner));
|
|
if (auto properties = maybe_get_property_bag (node_json_obj))
|
|
private_diagnostic_node_set_property_bag (*new_node.m_inner,
|
|
properties->clone_as_object ());
|
|
|
|
// §3.40.3 "label" property
|
|
const property_spec_ref label_prop
|
|
("node", "label", "3.40.3");
|
|
if (auto label_obj
|
|
= get_optional_property<json::object> (node_json_obj, label_prop))
|
|
{
|
|
auto msg_buf
|
|
= make_plain_text_within_result_message (&run_obj,
|
|
*label_obj,
|
|
nullptr);
|
|
if (!msg_buf.m_inner)
|
|
return nullptr;
|
|
new_node.set_label (std::move (msg_buf));
|
|
}
|
|
|
|
// §3.40.4 "location" property
|
|
const property_spec_ref location_prop ("node", "location", "3.40.4");
|
|
if (auto location_json_obj
|
|
= get_optional_property<json::object> (node_json_obj, location_prop))
|
|
{
|
|
libgdiagnostics::physical_location physical_loc;
|
|
libgdiagnostics::logical_location logical_loc;
|
|
enum status s = handle_location_object (*location_json_obj,
|
|
run_obj,
|
|
physical_loc,
|
|
logical_loc,
|
|
nullptr); // annotations
|
|
if (s != status::ok)
|
|
return nullptr;
|
|
|
|
new_node.set_location (physical_loc);
|
|
new_node.set_logical_location (logical_loc);
|
|
}
|
|
|
|
// §3.40.5: MAY contain a "children" property
|
|
const property_spec_ref children_prop
|
|
("graph", "children", "3.40.5");
|
|
if (auto children_json_arr
|
|
= get_optional_property<json::array> (node_json_obj, children_prop))
|
|
{
|
|
for (auto json_element : *children_json_arr)
|
|
{
|
|
const json::object *child_json_obj
|
|
= require_object_for_element (*json_element, children_prop);
|
|
if (!child_json_obj)
|
|
return nullptr;
|
|
libgdiagnostics::node child_node
|
|
= handle_node_object (*child_json_obj, run_obj, graph,
|
|
new_node, node_id_map);
|
|
if (child_node.m_inner == nullptr)
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
return new_node;
|
|
}
|
|
|
|
// "edge" object (§3.41)
|
|
|
|
libgdiagnostics::edge
|
|
sarif_replayer::handle_edge_object (const json::object &edge_json_obj,
|
|
libgdiagnostics::graph &graph,
|
|
id_map &edge_id_map)
|
|
{
|
|
// §3.41.2 "id" property
|
|
const property_spec_ref id_prop ("edge", "id", "3.41.2");
|
|
auto id_str = get_required_property<json::string> (edge_json_obj, id_prop);
|
|
if (!id_str)
|
|
return nullptr;
|
|
const char *id = id_str->get_string ();
|
|
if (diagnostic_graph_get_edge_by_id (graph.m_inner, id))
|
|
{
|
|
// Duplicate id; fail:
|
|
libgdiagnostics::group g (m_control_mgr);
|
|
report_invalid_sarif (*id_str,
|
|
id_prop,
|
|
"duplicate edge id %qs within graph",
|
|
id);
|
|
gcc_assert (edge_id_map[id]);
|
|
report_note (*edge_id_map[id],
|
|
"%qs already used as edge id within graph here",
|
|
id);
|
|
return nullptr;
|
|
}
|
|
edge_id_map[id] = id_str;
|
|
|
|
// §3.41.3 "label" property
|
|
libgdiagnostics::message_buffer label;
|
|
const property_spec_ref label_prop
|
|
("edge", "label", "3.41.3");
|
|
if (auto label_obj
|
|
= get_optional_property<json::object> (edge_json_obj, label_prop))
|
|
{
|
|
label = make_plain_text_within_result_message (nullptr,
|
|
*label_obj,
|
|
nullptr);
|
|
if (!label.m_inner)
|
|
return nullptr;
|
|
}
|
|
|
|
// §3.41.4 "sourceNodeId" property
|
|
const property_spec_ref src_id_prop ("edge", "sourceNodeId", "3.41.4");
|
|
auto src_node = get_graph_node_by_id_property (edge_json_obj,
|
|
src_id_prop,
|
|
graph);
|
|
if (!src_node.m_inner)
|
|
return nullptr;
|
|
|
|
// §3.41.5 "targetNodeId" property
|
|
const property_spec_ref dst_id_prop ("edge", "targetNodeId", "3.41.5");
|
|
auto dst_node = get_graph_node_by_id_property (edge_json_obj,
|
|
dst_id_prop,
|
|
graph);
|
|
if (!dst_node.m_inner)
|
|
return nullptr;
|
|
|
|
auto result = graph.add_edge (id, src_node, dst_node, std::move (label));
|
|
|
|
if (auto properties = maybe_get_property_bag (edge_json_obj))
|
|
private_diagnostic_edge_set_property_bag (*result.m_inner,
|
|
properties->clone_as_object ());
|
|
|
|
return result;
|
|
}
|
|
|
|
libgdiagnostics::node
|
|
sarif_replayer::
|
|
get_graph_node_by_id_property (const json::object &edge_json_obj,
|
|
const property_spec_ref &id_prop,
|
|
libgdiagnostics::graph &graph)
|
|
{
|
|
auto id_str = get_required_property<json::string> (edge_json_obj, id_prop);
|
|
if (!id_str)
|
|
return nullptr;
|
|
const char *id = id_str->get_string ();
|
|
auto node = graph.get_node_by_id (id);
|
|
if (!node.m_inner)
|
|
{
|
|
// id not found; complain:
|
|
report_invalid_sarif (*id_str,
|
|
id_prop,
|
|
"no node with id %qs in graph",
|
|
id);
|
|
return nullptr;
|
|
}
|
|
return node;
|
|
}
|
|
|
|
// 3.52.3 reportingDescriptor lookup
|
|
// "For an example of the interaction between ruleId and rule.id, see §3.52.4."
|
|
|
|
const json::object *
|
|
sarif_replayer::
|
|
lookup_rule_by_id_in_tool (const char *rule_id,
|
|
const json::object &tool_obj,
|
|
const json::object *&tool_component_obj)
|
|
{
|
|
auto driver_obj
|
|
= get_required_property<json::object> (tool_obj,
|
|
property_spec_ref ("tool", "driver",
|
|
"3.18.2"));
|
|
if (!driver_obj)
|
|
return nullptr;
|
|
|
|
if (auto rule_obj = lookup_rule_by_id_in_component (rule_id, *driver_obj))
|
|
{
|
|
tool_component_obj = driver_obj;
|
|
return rule_obj;
|
|
}
|
|
|
|
// TODO: also handle extensions
|
|
|
|
return NULL;
|
|
}
|
|
|
|
const json::object *
|
|
sarif_replayer::
|
|
lookup_rule_by_id_in_component (const char *rule_id,
|
|
const json::object &tool_component_obj)
|
|
{
|
|
const property_spec_ref rules ("toolComponent", "rules", "3.18.2");
|
|
|
|
auto rules_arr
|
|
= get_optional_property<json::array> (tool_component_obj, rules);
|
|
if (!rules_arr)
|
|
return nullptr;
|
|
|
|
for (auto element : *rules_arr)
|
|
{
|
|
const json::object *reporting_desc_obj
|
|
= require_object_for_element (*element, rules);
|
|
|
|
/* reportingDescriptor objects (§3.49). */
|
|
const property_spec_ref id ("reportingDescriptor", "id", "3.49.3");
|
|
auto desc_id_jstr
|
|
= get_required_property<json::string> (*reporting_desc_obj, id);
|
|
if (!desc_id_jstr)
|
|
return nullptr;
|
|
|
|
if (!strcmp (rule_id, desc_id_jstr->get_string ()))
|
|
return reporting_desc_obj;
|
|
}
|
|
|
|
/* Not found. */
|
|
return nullptr;
|
|
}
|
|
|
|
// "fix" object (§3.55)
|
|
|
|
enum status
|
|
sarif_replayer::handle_fix_object (libgdiagnostics::diagnostic &diag,
|
|
const json::object &fix_obj)
|
|
{
|
|
const property_spec_ref changes ("fix", "artifactChanges", "3.55.3");
|
|
auto changes_arr = get_required_property<json::array> (fix_obj, changes);
|
|
if (!changes_arr)
|
|
return status::err_invalid_sarif;
|
|
|
|
for (auto element : *changes_arr)
|
|
{
|
|
const json::object *change_obj
|
|
= require_object_for_element (*element, changes);
|
|
if (!change_obj)
|
|
return status::err_invalid_sarif;
|
|
enum status s = handle_artifact_change_object (diag, *change_obj);
|
|
if (s != status::ok)
|
|
return s;
|
|
}
|
|
return status::ok;
|
|
}
|
|
|
|
// "artifactChange" object (§3.56)
|
|
|
|
enum status
|
|
sarif_replayer::
|
|
handle_artifact_change_object (libgdiagnostics::diagnostic &diag,
|
|
const json::object &change_obj)
|
|
{
|
|
const property_spec_ref location
|
|
("artifactChange", "artifactLocation", "3.56.2");
|
|
auto artifact_loc_obj
|
|
= get_required_property<json::object> (change_obj, location);
|
|
if (!artifact_loc_obj)
|
|
return status::err_invalid_sarif;
|
|
|
|
libgdiagnostics::file file;
|
|
enum status s = handle_artifact_location_object (*artifact_loc_obj, file);
|
|
if (s != status::ok)
|
|
return s;
|
|
|
|
const property_spec_ref replacements
|
|
("artifactChange", "replacements", "3.56.3");
|
|
auto replacements_arr
|
|
= get_required_property<json::array> (change_obj, replacements);
|
|
if (!replacements_arr)
|
|
return status::err_invalid_sarif;
|
|
for (auto element : *replacements_arr)
|
|
{
|
|
// 3.57 replacement object
|
|
const json::object *replacement_obj
|
|
= require_object_for_element (*element, replacements);
|
|
if (!replacement_obj)
|
|
return status::err_invalid_sarif;
|
|
|
|
// 3.57.3 deletedRegion property
|
|
const property_spec_ref deleted_region
|
|
("replacement", "deletedRegion", "3.57.3");
|
|
auto deleted_region_obj
|
|
= get_required_property<json::object> (*replacement_obj,
|
|
deleted_region);
|
|
if (!deleted_region_obj)
|
|
return status::err_invalid_sarif;
|
|
|
|
libgdiagnostics::physical_location phys_loc;
|
|
enum status s = handle_region_object (*deleted_region_obj,
|
|
file,
|
|
phys_loc);
|
|
if (s != status::ok)
|
|
return s;
|
|
|
|
// 3.57.4 insertedContent property
|
|
const property_spec_ref inserted_content
|
|
("replacement", "insertedContent", "3.57.4");
|
|
const char *inserted_text = "";
|
|
if (auto inserted_content_obj
|
|
= get_optional_property<json::object> (*replacement_obj,
|
|
inserted_content))
|
|
{
|
|
const property_spec_ref prop_text
|
|
("artifactContent", "text", "3.3.2");
|
|
if (auto text_jstr
|
|
= get_optional_property<json::string> (*inserted_content_obj,
|
|
prop_text))
|
|
inserted_text = text_jstr->get_string ();
|
|
}
|
|
|
|
diag.add_fix_it_hint_replace (phys_loc, inserted_text);
|
|
}
|
|
return status::ok;
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
/* Error-checking at the API boundary. */
|
|
|
|
#define FAIL_IF_NULL(PTR_ARG) \
|
|
do { \
|
|
GCC_DIAGNOSTIC_PUSH_IGNORED(-Wnonnull-compare) \
|
|
if (!(PTR_ARG)) { \
|
|
fprintf (stderr, "%s: %s must be non-NULL\n", \
|
|
__func__, #PTR_ARG); \
|
|
abort (); \
|
|
} \
|
|
GCC_DIAGNOSTIC_POP \
|
|
} while (0)
|
|
|
|
/* Public entrypoint. */
|
|
|
|
int
|
|
sarif_replay_path (const char *sarif_file,
|
|
diagnostic_manager *output_manager,
|
|
diagnostic_manager *control_manager,
|
|
const replay_options *options)
|
|
{
|
|
FAIL_IF_NULL (sarif_file);
|
|
FAIL_IF_NULL (output_manager);
|
|
FAIL_IF_NULL (control_manager);
|
|
FAIL_IF_NULL (options);
|
|
|
|
sarif_replayer r (libgdiagnostics::manager (output_manager, false),
|
|
libgdiagnostics::manager (control_manager, false));
|
|
return (int)r.replay_file (sarif_file, *options);
|
|
}
|