@@ -20,6 +20,8 @@ along with GCC; see the file COPYING3. If not see
#include "config.h"
+#define INCLUDE_LIST
+#define INCLUDE_MAP
#define INCLUDE_MEMORY
#define INCLUDE_VECTOR
#include "system.h"
@@ -60,11 +62,13 @@ class sarif_tool; // 3.18
class sarif_tool_component; // 3.19
class sarif_invocation; // 3.20
class sarif_artifact; // 3.24
+class sarif_location_manager; // not in the spec
class sarif_result; // 3.27
class sarif_location; // 3.28
class sarif_physical_location; // 3.29
class sarif_region; // 3.30
class sarif_logical_location; // 3.33
+class sarif_location_relationship; // 3.34
class sarif_code_flow; // 3.36
class sarif_thread_flow; // 3.37
class sarif_thread_flow_location; // 3.38
@@ -76,6 +80,17 @@ class sarif_artifact_change; // 3.56
class sarif_replacement; // 3.57
class sarif_ice_notification; // 3.58
+// Valid values for locationRelationship's "kinds" property (3.34.3)
+
+enum class location_relationship_kind
+{
+ includes,
+ is_included_by,
+ relevant,
+
+ NUM_KINDS
+};
+
/* Declarations of subclasses of sarif_object.
Keep these in order of their descriptions in the specification. */
@@ -166,6 +181,11 @@ enum class diagnostic_artifact_role
analysis_target, /* "analysisTarget". */
debug_output_file, /* "debugOutputFile". */
result_file, /* "resultFile". */
+
+ /* "scannedFile" added in 2.2;
+ see https://github.com/oasis-tcs/sarif-spec/issues/459 */
+ scanned_file,
+
traced_file, /* "tracedFile". */
NUM_ROLES
@@ -203,10 +223,160 @@ private:
bool m_embed_contents;
};
+/* A class for sarif_objects that own a "namespace" of numeric IDs for
+ managing location objects within them. Currently (SARIF v2.1.0)
+ this is just for sarif_result (section 3.28.2), but it will likely
+ eventually also be for notification objects; see
+ https://github.com/oasis-tcs/sarif-spec/issues/540
+
+ Consider locations with chains of include information e.g.
+
+ > include-chain-1.c:
+ > #include "include-chain-1.h"
+
+ include-chain-1.h:
+ | // First set of decls, which will be referenced in notes
+ | #include "include-chain-1-1.h"
+ |
+ | // Second set of decls, which will trigger the errors
+ | #include "include-chain-1-2.h"
+
+ include-chain-1-1.h:
+ | int p;
+ | int q;
+
+ include-chain-1-1.h:
+ | char p;
+ | char q;
+
+ GCC's textual output emits:
+ | In file included from PATH/include-chain-1.h:5,
+ | from PATH/include-chain-1.c:30:
+ | PATH/include-chain-1-2.h:1:6: error: conflicting types for 'p'; have 'char'
+ | 1 | char p;
+ | | ^
+ | In file included from PATH/include-chain-1.h:2:
+ | PATH/include-chain-1-1.h:1:5: note: previous declaration of 'p' with type 'int'
+ | 1 | int p;
+ | | ^
+ | PATH/include-chain-1-2.h:2:6: error: conflicting types for 'q'; have 'char'
+ | 2 | char q;
+ | | ^
+ | PATH/include-chain-1-1.h:2:5: note: previous declaration of 'q' with type 'int'
+ | 2 | int q;
+ | | ^
+
+ Whenever a SARIF location is added for a location_t that
+ was #included from somewhere, we queue up the creation of a SARIF
+ location for the location of the #include. The worklist of queued
+ locations is flushed when the result is finished, which lazily creates
+ any additional related locations for the include chain, and the
+ relationships between the locations. Doing so can lead to further
+ include locations being processed. The worklist approach allows us
+ to lazily explore the relevant part of the directed graph of location_t
+ values implicit in our line_maps structure, replicating it as a directed
+ graph of SARIF locations within the SARIF result object, like this:
+
+ [0]: error in include-chain-1-2.h ("conflicting types for 'p'; have 'char'")
+ [1]: #include "include-chain-1-2.h" in include-chain-1.h
+ [2]: note in include-chain-1-2.h ("previous declaration of 'p' with type 'int'")
+ [3]: #include "include-chain-1-1.h" in include-chain-1.h
+ [4]: #include "include-chain-1.h" in include-chain-1.c
+
+ where we want to capture this "includes" graph in SARIF form:
+ . +-----------------------------------+ +----------------------------------+
+ . |"id": 0 | |"id": 2 |
+ . | error: "conflicting types for 'p';| | note: previous declaration of 'p'|
+ . | have 'char'"| | | with type 'int'") |
+ . | in include-chain-1-2.h | | in include-chain-1-1.h |
+ . +-----------------------------------+ +----------------------------------+
+ . | |
+ . | included-by | included-by
+ . V V
+ . +--------------------------------+ +--------------------------------+
+ . |"id": 1 | |"id": 3 |
+ . | #include "include-chain-1-2.h" | | #include "include-chain-1-1.h" |
+ . | in include-chain-1.h | | in include-chain-1.h |
+ . +--------------------------------+ +--------------------------------+
+ . | |
+ . | included-by | included-by
+ . V V
+ . +------------------------------------+
+ . |"id": 4 |
+ . | The #include "include-chain-1.h" |
+ . | in include-chain-1.c |
+ . +------------------------------------+
+ */
+
+class sarif_location_manager : public sarif_object
+{
+public:
+ /* A worklist of pending actions needed to fully process this object.
+
+ This lets us lazily walk our data structures to build the
+ directed graph of locations, whilst keeping "notes" at the top
+ of the "relatedLocations" array, and avoiding the need for
+ recursion. */
+ struct worklist_item
+ {
+ enum class kind
+ {
+ /* Process a #include relationship where m_location_obj
+ was #included-d at m_where. */
+ included_from
+ };
+
+ worklist_item (sarif_location &location_obj,
+ enum kind kind,
+ location_t where)
+ : m_location_obj (location_obj),
+ m_kind (kind),
+ m_where (where)
+ {
+ }
+
+ sarif_location &m_location_obj;
+ enum kind m_kind;
+ location_t m_where;
+ };
+
+ sarif_location_manager ()
+ : m_next_location_id (0)
+ {
+ }
+
+ unsigned allocate_location_id ()
+ {
+ return m_next_location_id++;
+ }
+
+ virtual void
+ add_related_location (std::unique_ptr<sarif_location> location_obj) = 0;
+
+ void
+ add_relationship_to_worklist (sarif_location &location_obj,
+ enum worklist_item::kind kind,
+ location_t where);
+
+ void
+ process_worklist (sarif_builder &builder);
+
+ void
+ process_worklist_item (sarif_builder &builder,
+ const worklist_item &item);
+private:
+ unsigned m_next_location_id;
+
+ std::list<worklist_item> m_worklist;
+ std::map<location_t, sarif_location *> m_included_from_locations;
+};
+
/* Subclass of sarif_object for SARIF "result" objects
- (SARIF v2.1.0 section 3.27). */
+ (SARIF v2.1.0 section 3.27).
+ Each SARIF result object has its own "namespace" of numeric IDs for
+ managing location objects (SARIF v2.1.0 section 3.28.2). */
-class sarif_result : public sarif_object
+class sarif_result : public sarif_location_manager
{
public:
sarif_result () : m_related_locations_arr (nullptr) {}
@@ -220,17 +390,39 @@ public:
const diagnostic_diagram &diagram,
sarif_builder &builder);
-private:
void
- add_related_location (std::unique_ptr<sarif_location> location_obj);
+ add_related_location (std::unique_ptr<sarif_location> location_obj)
+ final override;
+private:
json::array *m_related_locations_arr; // borrowed
};
/* Subclass of sarif_object for SARIF "location" objects
- (SARIF v2.1.0 section 3.28). */
+ (SARIF v2.1.0 section 3.28).
+ A location object can have an "id" which must be unique within
+ the enclosing result, if any (see SARIF v2.1.0 section 3.28.2). */
+
+class sarif_location : public sarif_object
+{
+public:
+ long lazily_add_id (sarif_location_manager &loc_mgr);
+ long get_id () const;
+
+ void lazily_add_relationship (sarif_location &target,
+ enum location_relationship_kind kind,
+ sarif_location_manager &loc_mgr);
+
+private:
+ sarif_location_relationship &
+ lazily_add_relationship_object (sarif_location &target,
+ sarif_location_manager &loc_mgr);
+
+ json::array &lazily_add_relationships_array ();
-class sarif_location : public sarif_object {};
+ std::map<sarif_location *,
+ sarif_location_relationship *> m_relationships_map;
+};
/* Subclass of sarif_object for SARIF "physicalLocation" objects
(SARIF v2.1.0 section 3.29). */
@@ -242,6 +434,23 @@ class sarif_physical_location : public sarif_object {};
class sarif_region : public sarif_object {};
+/* Subclass of sarif_object for SARIF "locationRelationship" objects
+ (SARIF v2.1.0 section 3.34). */
+
+class sarif_location_relationship : public sarif_object
+{
+public:
+ sarif_location_relationship (sarif_location &target,
+ sarif_location_manager &loc_mgr);
+
+ long get_target_id () const;
+
+ void lazily_add_kind (enum location_relationship_kind kind);
+
+private:
+ auto_sbitmap m_kinds;
+};
+
/* Subclass of sarif_object for SARIF "codeFlow" objects
(SARIF v2.1.0 section 3.36). */
@@ -304,12 +513,16 @@ class sarif_replacement : public sarif_object {};
This subclass is specifically for notifying when an
internal compiler error occurs. */
-class sarif_ice_notification : public sarif_object
+class sarif_ice_notification : public sarif_location_manager
{
public:
sarif_ice_notification (diagnostic_context &context,
const diagnostic_info &diagnostic,
sarif_builder &builder);
+
+ void
+ add_related_location (std::unique_ptr<sarif_location> location_obj)
+ final override;
};
/* Abstract base class for use when making an "artifactContent"
@@ -370,6 +583,7 @@ class sarif_builder
{
public:
sarif_builder (diagnostic_context &context,
+ const line_maps *line_maps,
const char *main_input_filename_,
bool formatted);
@@ -380,16 +594,27 @@ public:
const diagnostic_diagram &diagram);
void end_group ();
+ std::unique_ptr<sarif_result> take_current_result ()
+ {
+ return std::move (m_cur_group_result);
+ }
+
std::unique_ptr<sarif_log> flush_to_object ();
void flush_to_file (FILE *outf);
std::unique_ptr<json::array>
- make_locations_arr (const diagnostic_info &diagnostic,
+ make_locations_arr (sarif_location_manager &loc_mgr,
+ const diagnostic_info &diagnostic,
enum diagnostic_artifact_role role);
std::unique_ptr<sarif_location>
- make_location_object (const rich_location &rich_loc,
+ make_location_object (sarif_location_manager &loc_mgr,
+ const rich_location &rich_loc,
const logical_location *logical_loc,
enum diagnostic_artifact_role role);
+ std::unique_ptr<sarif_location>
+ make_location_object (sarif_location_manager &loc_mgr,
+ location_t where,
+ enum diagnostic_artifact_role role);
std::unique_ptr<sarif_message>
make_message_object (const char *msg) const;
std::unique_ptr<sarif_message>
@@ -407,15 +632,22 @@ private:
const diagnostic_info &diagnostic,
diagnostic_t orig_diag_kind);
void
+ add_any_include_chain (sarif_location_manager &loc_mgr,
+ sarif_location &location_obj,
+ location_t where);
+ void
set_any_logical_locs_arr (sarif_location &location_obj,
const logical_location *logical_loc);
std::unique_ptr<sarif_location>
- make_location_object (const diagnostic_event &event,
+ make_location_object (sarif_location_manager &loc_mgr,
+ const diagnostic_event &event,
enum diagnostic_artifact_role role);
std::unique_ptr<sarif_code_flow>
- make_code_flow_object (const diagnostic_path &path);
+ make_code_flow_object (sarif_result &result,
+ const diagnostic_path &path);
std::unique_ptr<sarif_thread_flow_location>
- make_thread_flow_location_object (const diagnostic_event &event,
+ make_thread_flow_location_object (sarif_result &result,
+ const diagnostic_event &event,
int path_event_idx);
std::unique_ptr<json::array>
maybe_make_kinds_array (diagnostic_event::meaning m) const;
@@ -486,6 +718,7 @@ private:
int get_sarif_column (expanded_location exploc) const;
diagnostic_context &m_context;
+ const line_maps *m_line_maps;
/* The JSON object for the invocation object. */
std::unique_ptr<sarif_invocation> m_invocation_obj;
@@ -495,7 +728,7 @@ private:
/* The JSON object for the result object (if any) in the current
diagnostic group. */
- sarif_result *m_cur_group_result; // borrowed
+ std::unique_ptr<sarif_result> m_cur_group_result;
/* Ideally we'd use std::unique_ptr<sarif_artifact> here, but I had
trouble getting this to work when building with GCC 4.8. */
@@ -600,6 +833,15 @@ void
sarif_artifact::add_role (enum diagnostic_artifact_role role,
bool embed_contents)
{
+ /* TODO(SARIF 2.2): "scannedFile" is to be added as a role in SARIF 2.2;
+ see https://github.com/oasis-tcs/sarif-spec/issues/459
+
+ For now, skip them.
+ Ultimately, we probably shouldn't bother embedding the contents
+ of such artifacts, just the snippets. */
+ if (role == diagnostic_artifact_role::scanned_file)
+ return;
+
if (embed_contents)
m_embed_contents = true;
@@ -645,6 +887,8 @@ get_artifact_role_string (enum diagnostic_artifact_role role)
return "debugOutputFile";
case diagnostic_artifact_role::result_file:
return "resultFile";
+ case diagnostic_artifact_role::scanned_file:
+ return "scannedFile";
case diagnostic_artifact_role::traced_file:
return "tracedFile";
}
@@ -670,7 +914,83 @@ sarif_artifact::populate_roles ()
set<json::array> ("roles", std::move (roles_arr));
}
-/* class sarif_result : public sarif_object. */
+/* class sarif_location_manager : public sarif_object. */
+
+void
+sarif_location_manager::
+add_relationship_to_worklist (sarif_location &location_obj,
+ enum worklist_item::kind kind,
+ location_t where)
+{
+ m_worklist.push_back (worklist_item (location_obj,
+ kind,
+ where));
+}
+
+/* Process all items in this result's worklist.
+ Doing so may temporarily add new items to the end
+ of the worklist.
+ Handling any item should be "lazy", and thus we should
+ eventually drain the queue and terminate. */
+
+void
+sarif_location_manager::process_worklist (sarif_builder &builder)
+{
+ while (!m_worklist.empty ())
+ {
+ const worklist_item &item = m_worklist.front ();
+ process_worklist_item (builder, item);
+ m_worklist.pop_front ();
+ }
+}
+
+/* Process one item in this result's worklist, potentially
+ adding new items to the end of the worklist. */
+
+void
+sarif_location_manager::process_worklist_item (sarif_builder &builder,
+ const worklist_item &item)
+{
+ switch (item.m_kind)
+ {
+ default:
+ gcc_unreachable ();
+ case worklist_item::kind::included_from:
+ {
+ sarif_location &included_loc_obj = item.m_location_obj;
+ sarif_location *includer_loc_obj = nullptr;
+ auto iter = m_included_from_locations.find (item.m_where);
+ if (iter != m_included_from_locations.end ())
+ includer_loc_obj = iter->second;
+ else
+ {
+ std::unique_ptr<sarif_location> new_loc_obj
+ = builder.make_location_object
+ (*this,
+ item.m_where,
+ diagnostic_artifact_role::scanned_file);
+ includer_loc_obj = new_loc_obj.get ();
+ add_related_location (std::move (new_loc_obj));
+ auto kv
+ = std::pair<location_t, sarif_location *> (item.m_where,
+ includer_loc_obj);
+ m_included_from_locations.insert (kv);
+ }
+
+ includer_loc_obj->lazily_add_relationship
+ (included_loc_obj,
+ location_relationship_kind::includes,
+ *this);
+ included_loc_obj.lazily_add_relationship
+ (*includer_loc_obj,
+ location_relationship_kind::is_included_by,
+ *this);
+ }
+ break;
+ }
+}
+
+/* class sarif_result : public sarif_location_manager. */
/* Handle secondary diagnostics that occur within a diagnostic group.
The closest SARIF seems to have to nested diagnostics is the
@@ -688,7 +1008,7 @@ sarif_result::on_nested_diagnostic (diagnostic_context &context,
sometimes these will related to current_function_decl, but
often they won't. */
auto location_obj
- = builder.make_location_object (*diagnostic.richloc, nullptr,
+ = builder.make_location_object (*this, *diagnostic.richloc, nullptr,
diagnostic_artifact_role::result_file);
auto message_obj
= builder.make_message_object (pp_formatted_text (context.printer));
@@ -717,7 +1037,10 @@ sarif_result::on_diagram (diagnostic_context &context,
add_related_location (std::move (location_obj));
}
-/* Add LOCATION_OBJ to this result's "relatedLocations" array,
+/* Implementation of sarif_location_manager::add_related_location vfunc
+ for result objects.
+
+ Add LOCATION_OBJ to this result's "relatedLocations" array,
creating it if it doesn't yet exist. */
void
@@ -734,18 +1057,140 @@ add_related_location (std::unique_ptr<sarif_location> location_obj)
m_related_locations_arr->append (std::move (location_obj));
}
-/* class sarif_ice_notification : public sarif_object. */
+/* class sarif_location : public sarif_object. */
+
+/* Ensure this location has an "id" and return it.
+ Use LOC_MGR if an id needs to be allocated.
+
+ See the "id" property (3.28.2).
+
+ We use this to only assign ids to locations that are
+ referenced by another sarif object; others have no "id". */
+
+long
+sarif_location::lazily_add_id (sarif_location_manager &loc_mgr)
+{
+ long id = get_id ();
+ if (id != -1)
+ return id;
+ id = loc_mgr.allocate_location_id ();
+ set_integer ("id", id);
+ gcc_assert (id != -1);
+ return id;
+}
+
+/* Get the id of this location, or -1 if it doesn't have one. */
+
+long
+sarif_location::get_id () const
+{
+ json::value *id = get ("id");
+ if (!id)
+ return -1;
+ gcc_assert (id->get_kind () == json::JSON_INTEGER);
+ return static_cast <json::integer_number *> (id)->get ();
+}
+
+// 3.34.3 kinds property
+static const char *
+get_string_for_location_relationship_kind (enum location_relationship_kind kind)
+{
+ switch (kind)
+ {
+ default:
+ gcc_unreachable ();
+ case location_relationship_kind::includes:
+ return "includes";
+ case location_relationship_kind::is_included_by:
+ return "isIncludedBy";
+ case location_relationship_kind::relevant:
+ return "relevant";
+ }
+}
+
+/* Lazily populate this location's "relationships" property (3.28.7)
+ with the relationship of KIND to TARGET, creating objects
+ as necessary.
+ Use LOC_MGR for any locations that need "id" values. */
+
+void
+sarif_location::lazily_add_relationship (sarif_location &target,
+ enum location_relationship_kind kind,
+ sarif_location_manager &loc_mgr)
+{
+ sarif_location_relationship &relationship_obj
+ = lazily_add_relationship_object (target, loc_mgr);
+
+ relationship_obj.lazily_add_kind (kind);
+}
+
+/* Lazily populate this location's "relationships" property (3.28.7)
+ with a location_relationship to TARGET, creating objects
+ as necessary.
+ Use LOC_MGR for any locations that need "id" values. */
+
+sarif_location_relationship &
+sarif_location::lazily_add_relationship_object (sarif_location &target,
+ sarif_location_manager &loc_mgr)
+{
+ /* See if THIS already has a locationRelationship referencing TARGET. */
+ auto iter = m_relationships_map.find (&target);
+ if (iter != m_relationships_map.end ())
+ {
+ /* We already have a locationRelationship from THIS to TARGET. */
+ sarif_location_relationship *relationship = iter->second;
+ gcc_assert (relationship->get_target_id() == target.get_id ());
+ return *relationship;
+ }
+
+ // Ensure that THIS has a "relationships" property (3.28.7).
+ json::array &relationships_arr = lazily_add_relationships_array ();
+
+ /* No existing locationRelationship from THIS to TARGET; make one,
+ record it, and add it to the "relationships" array. */
+ auto relationship_obj
+ = ::make_unique<sarif_location_relationship> (target, loc_mgr);
+ sarif_location_relationship *relationship = relationship_obj.get ();
+ auto kv
+ = std::pair<sarif_location *,
+ sarif_location_relationship *> (&target, relationship);
+ m_relationships_map.insert (kv);
+
+ relationships_arr.append (std::move (relationship_obj));
+
+ return *relationship;
+}
+
+/* Ensure this location has a "relationships" array (3.28.7). */
+
+json::array &
+sarif_location::lazily_add_relationships_array ()
+{
+ const char *const property_name = "relationships";
+ if (json::value *relationships = get (property_name))
+ {
+ gcc_assert (relationships->get_kind () == json::JSON_ARRAY);
+ return *static_cast <json::array *> (relationships);
+ }
+ json::array *relationships_arr = new json::array ();
+ set (property_name, relationships_arr);
+ return *relationships_arr;
+}
+
+/* class sarif_ice_notification : public sarif_location_manager. */
/* sarif_ice_notification's ctor.
DIAGNOSTIC is an internal compiler error. */
-sarif_ice_notification::sarif_ice_notification (diagnostic_context &context,
- const diagnostic_info &diagnostic,
- sarif_builder &builder)
+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). */
auto locations_arr
- = builder.make_locations_arr (diagnostic,
+ = builder.make_locations_arr (*this,
+ diagnostic,
diagnostic_artifact_role::result_file);
set<json::array> ("locations", std::move (locations_arr));
@@ -759,6 +1204,60 @@ sarif_ice_notification::sarif_ice_notification (diagnostic_context &context,
set_string ("level", "error");
}
+/* Implementation of sarif_location_manager::add_related_location vfunc
+ for notifications. */
+
+void
+sarif_ice_notification::
+add_related_location (std::unique_ptr<sarif_location> location_obj)
+{
+ /* TODO(SARIF 2.2): see https://github.com/oasis-tcs/sarif-spec/issues/540
+ For now, discard all related locations within a notification. */
+ location_obj = nullptr;
+}
+
+/* class sarif_location_relationship : public sarif_object. */
+
+sarif_location_relationship::
+sarif_location_relationship (sarif_location &target,
+ sarif_location_manager &loc_mgr)
+: m_kinds ((unsigned)location_relationship_kind::NUM_KINDS)
+{
+ bitmap_clear (m_kinds);
+ set_integer ("target", target.lazily_add_id (loc_mgr));
+}
+
+long
+sarif_location_relationship::get_target_id () const
+{
+ json::value *id = get ("id");
+ gcc_assert (id);
+ return static_cast <json::integer_number *> (id)->get ();
+}
+
+void
+sarif_location_relationship::
+lazily_add_kind (enum location_relationship_kind kind)
+{
+ if (bitmap_bit_p (m_kinds, (int)kind))
+ return; // already have this kind
+ bitmap_set_bit (m_kinds, (int)kind);
+
+ // 3.34.3 kinds property
+ json::array *kinds_arr = nullptr;
+ if (json::value *kinds_val = get ("kinds"))
+ {
+ gcc_assert (kinds_val->get_kind () == json::JSON_ARRAY);
+ }
+ else
+ {
+ kinds_arr = new json::array ();
+ set ("kinds", kinds_arr);
+ }
+ const char *kind_str = get_string_for_location_relationship_kind (kind);
+ kinds_arr->append_string (kind_str);
+}
+
/* class sarif_thread_flow : public sarif_object. */
sarif_thread_flow::sarif_thread_flow (const diagnostic_thread &thread)
@@ -787,9 +1286,11 @@ add_location (std::unique_ptr<sarif_thread_flow_location> thread_flow_loc_obj)
/* sarif_builder's ctor. */
sarif_builder::sarif_builder (diagnostic_context &context,
+ const line_maps *line_maps,
const char *main_input_filename_,
bool formatted)
: m_context (context),
+ m_line_maps (line_maps),
m_invocation_obj
(::make_unique<sarif_invocation> (*this,
context.get_original_argv ())),
@@ -834,10 +1335,8 @@ sarif_builder::end_diagnostic (diagnostic_context &context,
else
{
/* Top-level diagnostic. */
- std::unique_ptr<sarif_result> result_obj
+ m_cur_group_result
= make_result_object (context, diagnostic, orig_diag_kind);
- m_cur_group_result = result_obj.get (); // borrowed
- m_results_array->append<sarif_result> (std::move (result_obj));
}
}
@@ -858,7 +1357,11 @@ sarif_builder::emit_diagram (diagnostic_context &context,
void
sarif_builder::end_group ()
{
- m_cur_group_result = nullptr;
+ if (m_cur_group_result)
+ {
+ m_cur_group_result->process_worklist (*this);
+ m_results_array->append<sarif_result> (std::move (m_cur_group_result));
+ }
}
/* Create a top-level object, and add it to all the results
@@ -999,13 +1502,17 @@ sarif_builder::make_result_object (diagnostic_context &context,
/* "locations" property (SARIF v2.1.0 section 3.27.12). */
result_obj->set<json::array>
("locations",
- make_locations_arr (diagnostic, diagnostic_artifact_role::result_file));
+ make_locations_arr (*result_obj.get (),
+ diagnostic,
+ diagnostic_artifact_role::result_file));
/* "codeFlows" property (SARIF v2.1.0 section 3.27.18). */
if (const diagnostic_path *path = diagnostic.richloc->get_path ())
{
auto code_flows_arr = ::make_unique<json::array> ();
- code_flows_arr->append<sarif_code_flow> (make_code_flow_object (*path));
+ code_flows_arr->append<sarif_code_flow>
+ (make_code_flow_object (*result_obj.get (),
+ *path));
result_obj->set<json::array> ("codeFlows", std::move (code_flows_arr));
}
@@ -1123,10 +1630,12 @@ make_tool_component_reference_object_for_cwe () const
/* 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). */
+ - a "notification" object (SARIF v2.1.0 section 3.58.4).
+ Use LOC_MGR for any locations that need "id" values. */
std::unique_ptr<json::array>
-sarif_builder::make_locations_arr (const diagnostic_info &diagnostic,
+sarif_builder::make_locations_arr (sarif_location_manager &loc_mgr,
+ const diagnostic_info &diagnostic,
enum diagnostic_artifact_role role)
{
auto locations_arr = ::make_unique<json::array> ();
@@ -1135,7 +1644,7 @@ sarif_builder::make_locations_arr (const diagnostic_info &diagnostic,
logical_loc = client_data_hooks->get_current_logical_location ();
auto location_obj
- = make_location_object (*diagnostic.richloc, logical_loc, role);
+ = make_location_object (loc_mgr, *diagnostic.richloc, logical_loc, role);
/* Don't add entirely empty location objects to the array. */
if (!location_obj->is_empty ())
locations_arr->append<sarif_location> (std::move (location_obj));
@@ -1161,10 +1670,13 @@ set_any_logical_locs_arr (sarif_location &location_obj,
}
/* Make a "location" object (SARIF v2.1.0 section 3.28) for RICH_LOC
- and LOGICAL_LOC. */
+ and LOGICAL_LOC.
+ Use LOC_MGR for any locations that need "id" values, and for
+ any worklist items. */
std::unique_ptr<sarif_location>
-sarif_builder::make_location_object (const rich_location &rich_loc,
+sarif_builder::make_location_object (sarif_location_manager &loc_mgr,
+ const rich_location &rich_loc,
const logical_location *logical_loc,
enum diagnostic_artifact_role role)
{
@@ -1257,6 +1769,8 @@ sarif_builder::make_location_object (const rich_location &rich_loc,
std::move (annotations_arr));
}
+ add_any_include_chain (loc_mgr, *location_obj.get (), loc);
+
/* A flag for hinting that the diagnostic involves issues at the
level of character encodings (such as homoglyphs, or misleading
bidirectional control codes), and thus that it will be helpful
@@ -1271,11 +1785,66 @@ sarif_builder::make_location_object (const rich_location &rich_loc,
return location_obj;
}
+/* If WHERE was #included from somewhere, add a worklist item
+ to LOC_MGR to lazily add a location for the #include location,
+ and relationships between it and the LOCATION_OBJ.
+ Compare with diagnostic_context::report_current_module, but rather
+ than iterating the current chain, we add the next edge and iterate
+ in the worklist, so that edges are only added once. */
+
+void
+sarif_builder::add_any_include_chain (sarif_location_manager &loc_mgr,
+ sarif_location &location_obj,
+ location_t where)
+{
+ if (where <= BUILTINS_LOCATION)
+ return;
+
+ const line_map_ordinary *map = nullptr;
+ linemap_resolve_location (m_line_maps, where,
+ LRK_MACRO_DEFINITION_LOCATION,
+ &map);
+
+ if (!map)
+ return;
+
+ location_t include_loc = linemap_included_from (map);
+ map = linemap_included_from_linemap (m_line_maps, map);
+ if (!map)
+ return;
+ loc_mgr.add_relationship_to_worklist
+ (location_obj,
+ sarif_result::worklist_item::kind::included_from,
+ include_loc);
+}
+
+/* Make a "location" object (SARIF v2.1.0 section 3.28) for WHERE
+ within an include chain. */
+
+std::unique_ptr<sarif_location>
+sarif_builder::make_location_object (sarif_location_manager &loc_mgr,
+ location_t loc,
+ enum diagnostic_artifact_role role)
+{
+ auto location_obj = ::make_unique<sarif_location> ();
+
+ /* "physicalLocation" property (SARIF v2.1.0 section 3.28.3). */
+ if (auto phs_loc_obj
+ = maybe_make_physical_location_object (loc, role, 0, nullptr))
+ location_obj->set<sarif_physical_location> ("physicalLocation",
+ std::move (phs_loc_obj));
+
+ add_any_include_chain (loc_mgr, *location_obj.get (), loc);
+
+ return location_obj;
+}
+
/* Make a "location" object (SARIF v2.1.0 section 3.28) for EVENT
within a diagnostic_path. */
std::unique_ptr<sarif_location>
-sarif_builder::make_location_object (const diagnostic_event &event,
+sarif_builder::make_location_object (sarif_location_manager &loc_mgr,
+ const diagnostic_event &event,
enum diagnostic_artifact_role role)
{
auto location_obj = ::make_unique<sarif_location> ();
@@ -1296,6 +1865,8 @@ sarif_builder::make_location_object (const diagnostic_event &event,
location_obj->set<sarif_message> ("message",
make_message_object (ev_desc.get ()));
+ add_any_include_chain (loc_mgr, *location_obj.get (), loc);
+
return location_obj;
}
@@ -1651,7 +2222,8 @@ make_sarif_logical_location_object (const logical_location &logical_loc)
/* Make a "codeFlow" object (SARIF v2.1.0 section 3.36) for PATH. */
std::unique_ptr<sarif_code_flow>
-sarif_builder::make_code_flow_object (const diagnostic_path &path)
+sarif_builder::make_code_flow_object (sarif_result &result,
+ const diagnostic_path &path)
{
auto code_flow_obj = ::make_unique <sarif_code_flow> ();
@@ -1680,7 +2252,7 @@ sarif_builder::make_code_flow_object (const diagnostic_path &path)
/* Add event to thread's threadFlow object. */
std::unique_ptr<sarif_thread_flow_location> thread_flow_loc_obj
- = make_thread_flow_location_object (event, i);
+ = make_thread_flow_location_object (result, event, i);
thread_flow_obj->add_location (std::move (thread_flow_loc_obj));
}
code_flow_obj->set<json::array> ("threadFlows", std::move (thread_flows_arr));
@@ -1691,7 +2263,8 @@ sarif_builder::make_code_flow_object (const diagnostic_path &path)
/* Make a "threadFlowLocation" object (SARIF v2.1.0 section 3.38) for EVENT. */
std::unique_ptr<sarif_thread_flow_location>
-sarif_builder::make_thread_flow_location_object (const diagnostic_event &ev,
+sarif_builder::make_thread_flow_location_object (sarif_result &result,
+ const diagnostic_event &ev,
int path_event_idx)
{
auto thread_flow_loc_obj = ::make_unique<sarif_thread_flow_location> ();
@@ -1703,7 +2276,7 @@ sarif_builder::make_thread_flow_location_object (const diagnostic_event &ev,
/* "location" property (SARIF v2.1.0 section 3.38.3). */
thread_flow_loc_obj->set<sarif_location>
("location",
- make_location_object (ev, diagnostic_artifact_role::traced_file));
+ make_location_object (result, ev, diagnostic_artifact_role::traced_file));
/* "kinds" property (SARIF v2.1.0 section 3.38.8). */
diagnostic_event::meaning m = ev.get_meaning ();
@@ -2078,6 +2651,7 @@ sarif_builder::get_or_create_artifact (const char *filename,
gcc_unreachable ();
case diagnostic_artifact_role::analysis_target:
case diagnostic_artifact_role::result_file:
+ case diagnostic_artifact_role::scanned_file:
case diagnostic_artifact_role::traced_file:
/* Assume that these are in the source language. */
if (auto client_data_hooks = m_context.get_client_data_hooks ())
@@ -2277,6 +2851,16 @@ sarif_ice_handler (diagnostic_context *context)
class sarif_output_format : public diagnostic_output_format
{
public:
+ ~sarif_output_format ()
+ {
+ /* Any sarifResult objects should have been handled by now.
+ If not, then something's gone wrong with diagnostic
+ groupings. */
+ std::unique_ptr<sarif_result> pending_result
+ = m_builder.take_current_result ();
+ gcc_assert (!pending_result);
+ }
+
void on_begin_group () final override
{
/* No-op, */
@@ -2303,10 +2887,11 @@ public:
protected:
sarif_output_format (diagnostic_context &context,
+ const line_maps *line_maps,
const char *main_input_filename_,
bool formatted)
: diagnostic_output_format (context),
- m_builder (context, main_input_filename_, formatted)
+ m_builder (context, line_maps, main_input_filename_, formatted)
{}
sarif_builder m_builder;
@@ -2316,10 +2901,11 @@ class sarif_stream_output_format : public sarif_output_format
{
public:
sarif_stream_output_format (diagnostic_context &context,
+ const line_maps *line_maps,
const char *main_input_filename_,
bool formatted,
FILE *stream)
- : sarif_output_format (context, main_input_filename_, formatted),
+ : sarif_output_format (context, line_maps, main_input_filename_, formatted),
m_stream (stream)
{
}
@@ -2339,10 +2925,11 @@ class sarif_file_output_format : public sarif_output_format
{
public:
sarif_file_output_format (diagnostic_context &context,
+ const line_maps *line_maps,
const char *main_input_filename_,
bool formatted,
const char *base_file_name)
- : sarif_output_format (context, main_input_filename_, formatted),
+ : sarif_output_format (context, line_maps, main_input_filename_, formatted),
m_base_file_name (xstrdup (base_file_name))
{
}
@@ -2407,6 +2994,7 @@ diagnostic_output_format_init_sarif_stderr (diagnostic_context &context,
diagnostic_output_format_init_sarif (context);
context.set_output_format
(new sarif_stream_output_format (context,
+ line_table,
main_input_filename_,
formatted,
stderr));
@@ -2424,6 +3012,7 @@ diagnostic_output_format_init_sarif_file (diagnostic_context &context,
diagnostic_output_format_init_sarif (context);
context.set_output_format
(new sarif_file_output_format (context,
+ line_table,
main_input_filename_,
formatted,
base_file_name));
@@ -2440,6 +3029,7 @@ diagnostic_output_format_init_sarif_stream (diagnostic_context &context,
diagnostic_output_format_init_sarif (context);
context.set_output_format
(new sarif_stream_output_format (context,
+ line_table,
main_input_filename_,
formatted,
stream));
@@ -2461,6 +3051,7 @@ public:
diagnostic_output_format_init_sarif (*this);
m_format = new buffered_output_format (*this,
+ line_table,
main_input_filename,
true);
set_output_format (m_format); // give ownership;
@@ -2476,9 +3067,10 @@ private:
{
public:
buffered_output_format (diagnostic_context &context,
+ const line_maps *line_maps,
const char *main_input_filename_,
bool formatted)
- : sarif_output_format (context, main_input_filename_, formatted)
+ : sarif_output_format (context, line_maps, main_input_filename_, formatted)
{
}
bool machine_readable_stderr_p () const final override
@@ -2509,7 +3101,7 @@ test_make_location_object (const line_table_case &case_)
test_diagnostic_context dc;
- sarif_builder builder (dc, "MAIN_INPUT_FILENAME", true);
+ sarif_builder builder (dc, line_table, "MAIN_INPUT_FILENAME", true);
/* These "columns" are byte offsets, whereas later on the columns
in the generated SARIF use sarif_builder::get_sarif_column and
@@ -2536,9 +3128,11 @@ test_make_location_object (const line_table_case &case_)
richloc.add_range (field, SHOW_RANGE_WITHOUT_CARET, &label2);
richloc.set_escape_on_output (true);
+ sarif_result result;
+
std::unique_ptr<sarif_location> location_obj
= builder.make_location_object
- (richloc, nullptr, diagnostic_artifact_role::analysis_target);
+ (result, richloc, nullptr, diagnostic_artifact_role::analysis_target);
ASSERT_NE (location_obj, nullptr);
auto physical_location
@@ -346,6 +346,14 @@ initialize_input_context (diagnostic_input_charset_callback ccb,
void
diagnostic_context::finish ()
{
+ /* We might be handling a fatal error.
+ Close any active diagnostic groups, which may trigger flushing
+ the output format. */
+ while (m_diagnostic_groups.m_nesting_depth > 0)
+ end_group ();
+
+ /* Clean ups. */
+
delete m_output_format;
m_output_format= nullptr;
@@ -1396,6 +1404,11 @@ diagnostic_context::report_diagnostic (diagnostic_info *diagnostic)
gcc_assert (m_output_format);
+ /* Every call to report_diagnostic should be within a
+ begin_group/end_group pair so that output formats can reliably
+ flush diagnostics with on_end_group when the topmost group is ended. */
+ gcc_assert (m_diagnostic_groups.m_nesting_depth > 0);
+
/* Give preference to being able to inhibit warnings, before they
get reclassified to something else. */
bool was_warning = (diagnostic->kind == DK_WARNING
@@ -1012,7 +1012,10 @@ inline bool
diagnostic_report_diagnostic (diagnostic_context *context,
diagnostic_info *diagnostic)
{
- return context->report_diagnostic (diagnostic);
+ context->begin_group ();
+ bool warned = context->report_diagnostic (diagnostic);
+ context->end_group ();
+ return warned;
}
#ifdef ATTRIBUTE_GCC_DIAG
@@ -69,7 +69,9 @@ test_diagnostic_context::report (diagnostic_t kind,
{
va_list ap;
va_start (ap, fmt);
+ begin_group ();
bool result = diagnostic_impl (&richloc, metadata, option, fmt, &ap, kind);
+ end_group ();
va_end (ap);
return result;
}
new file mode 100644
@@ -0,0 +1,2 @@
+int p;
+int q;
new file mode 100644
@@ -0,0 +1,2 @@
+char p;
+char q;
new file mode 100644
@@ -0,0 +1,42 @@
+/* { dg-do compile } */
+/* { dg-options "-fdiagnostics-format=sarif-file" } */
+
+/* Verify that SARIF output can capture chains of include files in
+ result locations.
+
+ Generate two warning/note pairs, using a chain of header files.
+ In textual form, we'd expect something like:
+
+In file included from PATH/include-chain-1.h:5,
+ from PATH/include-chain-1.c:9:
+PATH/include-chain-1-2.h:1:6: error: conflicting types for 'p'; have 'char'
+ 1 | char p;
+ | ^
+In file included from PATH/include-chain-1.h:2:
+PATH/include-chain-1-1.h:1:5: note: previous declaration of 'p' with type 'int'
+ 1 | int p;
+ | ^
+PATH/include-chain-1-2.h:2:6: error: conflicting types for 'q'; have 'char'
+ 2 | char q;
+ | ^
+PATH/include-chain-1-1.h:2:5: note: previous declaration of 'q' with type 'int'
+ 2 | int q;
+ | ^
+
+ We should have two result objects (for each of 'p' and 'q'), each with
+ a related location for its note, and additional related locations describing
+ the include chains. */
+
+#include "include-chain-1.h"
+
+/* We expect a failing compile due to the errors, but the use of
+ -fdiagnostics-format=sarif-file means there should be no output to stderr.
+ DejaGnu injects this message; ignore it:
+ { dg-prune-output "exit status is 1" } */
+
+/* Verify that some JSON was written to a file with the expected name:
+ { dg-final { verify-sarif-file } } */
+
+/* Use a Python script to verify various properties about the generated
+ .sarif file:
+ { dg-final { run-sarif-pytest include-chain-1.c "test-include-chain-1.py" } } */
new file mode 100644
@@ -0,0 +1,5 @@
+/* First set of decls, which will be referenced in notes. */
+#include "include-chain-1-1.h"
+
+/* Second set of decls, which will trigger the errors. */
+#include "include-chain-1-2.h"
new file mode 100644
@@ -0,0 +1,40 @@
+/* { dg-require-effective-target analyzer } */
+/* { dg-options "-fanalyzer -fdiagnostics-format=sarif-file" } */
+/* { dg-do compile } */
+
+/* Verify that SARIF output can capture chains of include files in
+ diagnostic paths within result locations.
+
+ Generate an analyzer warning with a path, using a chain of header files
+ both for the warning and for the events within its esxecution path.
+ In textual form, we'd expect something like:
+
+In file included from PATH/include-chain-2.c:28:
+PATH/include-chain-2.h: In function 'test':
+PATH/include-chain-2.h:6:3: warning: double-'free' of 'ptr' [CWE-415] [-Wanalyzer-double-free]
+ 6 | __builtin_free (ptr);
+ | ^~~~~~~~~~~~~~~~~~~~
+ 'test': events 1-2
+ 5 | __builtin_free (ptr);
+ | ^~~~~~~~~~~~~~~~~~~~
+ | |
+ | (1) first 'free' here
+ 6 | __builtin_free (ptr);
+ | ~~~~~~~~~~~~~~~~~~~~
+ | |
+ | (2) second 'free' here; first 'free' was at (1)
+*/
+
+#include "include-chain-2.h"
+
+/* We expect a failing compile due to the errors, but the use of
+ -fdiagnostics-format=sarif-file means there should be no output to stderr.
+ DejaGnu injects this message; ignore it:
+ { dg-prune-output "exit status is 1" } */
+
+/* Verify that some JSON was written to a file with the expected name:
+ { dg-final { verify-sarif-file } } */
+
+/* Use a Python script to verify various properties about the generated
+ .sarif file:
+ { dg-final { run-sarif-pytest include-chain-2.c "test-include-chain-2.py" } } */
new file mode 100644
@@ -0,0 +1,7 @@
+/* Generate a warning with a diagnostic_path within a header. */
+
+void test (void *ptr)
+{
+ __builtin_free (ptr); // 1st
+ __builtin_free (ptr); // 2nd
+}
new file mode 100644
@@ -0,0 +1,31 @@
+# Copyright (C) 2012-2024 Free Software Foundation, Inc.
+#
+# 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/>.
+
+# GCC testsuite that uses the `dg.exp' driver.
+
+# Load support procs.
+load_lib gcc-dg.exp
+
+# Initialize `dg'.
+dg-init
+
+# Main loop.
+dg-runtest [lsort [glob -nocomplain $srcdir/$subdir/*.c]] "" ""
+
+# All done.
+dg-finish
new file mode 100644
@@ -0,0 +1,21 @@
+import gzip
+import json
+import os
+
+def sarif_from_env():
+ # return parsed JSON content a SARIF_PATH file
+ json_filename = os.environ['SARIF_PATH']
+ json_filename += '.sarif'
+ print('json_filename: %r' % json_filename)
+ with open(json_filename) as f:
+ json_data = f.read()
+ return json.loads(json_data)
+
+def get_location_artifact_uri(location):
+ return location['physicalLocation']['artifactLocation']['uri']
+
+def get_location_snippet_text(location):
+ return location['physicalLocation']['contextRegion']['snippet']['text']
+
+def get_location_relationships(location):
+ return location['relationships']
new file mode 100644
@@ -0,0 +1,125 @@
+from sarif import *
+
+import pytest
+
+@pytest.fixture(scope='function', autouse=True)
+def sarif():
+ return sarif_from_env()
+
+def test_basics(sarif):
+ schema = sarif['$schema']
+ assert schema == "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json"
+
+ version = sarif['version']
+ assert version == "2.1.0"
+
+def test_location_relationships(sarif):
+ runs = sarif['runs']
+ run = runs[0]
+ results = run['results']
+
+ # We expect a pair of errors, each with a note, and include chains.
+ # The textual form of these four diagnostics would look like this:
+ # . In file included from PATH/include-chain-1.h:5,
+ # . from PATH/include-chain.c:9:
+ # . PATH/include-chain-1-2.h:1:6: error: conflicting types for 'p'; have 'char'
+ # . 1 | char p;
+ # . | ^
+ # . In file included from PATH/include-chain-1.h:2:
+ # . PATH/include-chain-1-1.h:1:5: note: previous declaration of 'p' with type 'int'
+ # . 1 | int p;
+ # . | ^
+ # . PATH/include-chain-1-2.h:2:6: error: conflicting types for 'q'; have 'char'
+ # . 2 | char q;
+ # . | ^
+ # . PATH/include-chain-1-1.h:2:5: note: previous declaration of 'q' with type 'int'
+ # . 2 | int q;
+ # . | ^
+ assert len(results) == 2
+
+ result = results[0]
+ assert result['level'] == 'error'
+ assert result['message']['text'] == "conflicting types for 'p'; have 'char'"
+ locations = result['locations']
+ assert len(locations) == 1
+
+ location = locations[0]
+ assert 'id' in location
+ assert get_location_artifact_uri(location).endswith('include-chain-1-2.h')
+ assert get_location_snippet_text(location) == "char p;\n"
+
+ # We expect 4 related locations: one for the "note"
+ # and three for describing include chains
+ relatedLocations = result['relatedLocations']
+ assert len(relatedLocations) == 4
+
+ # We expect a related location representing the note:
+ # . In file included from PATH/include-chain-1.h:2:
+ # . from PATH/include-chain.c:9:
+ # . PATH/include-chain-1-1.h:1:5: note: previous declaration of 'p' with type 'int'
+ # . 1 | int p;
+ # . | ^
+ note = relatedLocations[0]
+ assert 'id' in note
+ assert note['message']['text'] == "previous declaration of 'p' with type 'int'"
+ assert get_location_artifact_uri(note).endswith('include-chain-1-1.h')
+ assert get_location_snippet_text(note) == "int p;\n"
+
+ # We expect three more related locations for the two include chains
+
+ # The "#include "include-chain-1-2.h" line in include-chain-1.h:
+ hash_include_1_2_h = relatedLocations[1]
+ assert 'id' in hash_include_1_2_h
+ assert get_location_snippet_text(hash_include_1_2_h) == '#include "include-chain-1-2.h"\n'
+ assert get_location_artifact_uri(hash_include_1_2_h).endswith('include-chain-1.h')
+
+ # The "#include "include-chain-1-1.h" line in include-chain-1.h:
+ hash_include_1_1_h = relatedLocations[2]
+ assert 'id' in hash_include_1_1_h
+ assert get_location_snippet_text(hash_include_1_1_h) == '#include "include-chain-1-1.h"\n'
+ assert get_location_artifact_uri(hash_include_1_1_h).endswith('include-chain-1.h')
+
+ # The "#include "include-chain-1.h" line in include-chain-1.c:
+ hash_include_1_h = relatedLocations[3]
+ assert 'id' in hash_include_1_h
+ assert get_location_snippet_text(hash_include_1_h) == '#include "include-chain-1.h"\n'
+ assert get_location_artifact_uri(hash_include_1_h).endswith('include-chain-1.c')
+
+ # Check the various relationships; we expect a directed graph of edges
+ # representing the various "isIncludedBy" and "includes" relationships.
+
+ # The primary location should be "isIncludedBy" the "#include "include-chain-1-2.h" line
+ assert len(location['relationships']) == 1
+ assert location['relationships'][0]['target'] == hash_include_1_2_h['id']
+ assert location['relationships'][0]['kinds'] == ["isIncludedBy"]
+
+ # The note should be "isIncludedBy" the "#include "include-chain-1-1.h" line
+ assert len(note['relationships']) == 1
+ assert note['relationships'][0]['target'] == hash_include_1_1_h['id']
+ assert note['relationships'][0]['kinds'] == ["isIncludedBy"]
+
+ # The "#include "include-chain-1-2.h" line:
+ assert len(hash_include_1_2_h['relationships']) == 2
+ assert hash_include_1_2_h['relationships'][0]['target'] == location['id']
+ assert hash_include_1_2_h['relationships'][0]['kinds'] == ["includes"]
+ assert hash_include_1_2_h['relationships'][1]['target'] == hash_include_1_h['id']
+ assert hash_include_1_2_h['relationships'][1]['kinds'] == ["isIncludedBy"]
+
+ # The "#include "include-chain-1-1.h" line:
+ assert len(hash_include_1_1_h['relationships']) == 2
+ assert hash_include_1_1_h['relationships'][0]['target'] == note['id']
+ assert hash_include_1_1_h['relationships'][0]['kinds'] == ["includes"]
+ assert hash_include_1_1_h['relationships'][1]['target'] == hash_include_1_h['id']
+ assert hash_include_1_1_h['relationships'][1]['kinds'] == ["isIncludedBy"]
+
+ # The "#include "include-chain-1.h" line in include-chain-1.c:
+ assert len(hash_include_1_h['relationships']) == 2
+ assert hash_include_1_h['relationships'][0]['target'] == hash_include_1_2_h['id']
+ assert hash_include_1_h['relationships'][0]['kinds'] == ["includes"]
+ assert hash_include_1_h['relationships'][1]['target'] == hash_include_1_1_h['id']
+ assert hash_include_1_h['relationships'][1]['kinds'] == ["includes"]
+
+ # We expect similar for the 2nd error
+ assert results[1]['level'] == 'error'
+ assert results[1]['message']['text'] == "conflicting types for 'q'; have 'char'"
+ assert len(results[1]['relatedLocations']) == 4
new file mode 100644
@@ -0,0 +1,124 @@
+# We expect an aanalyzer warning with a path, referencing the #include
+# both in the warning and in the events within its execution path.
+# In textual form, we'd expect something like:
+# . In file included from PATH/include-chain-2.c:28:
+# . PATH/include-chain-2.h: In function 'test':
+# . PATH/include-chain-2.h:6:3: warning: double-'free' of 'ptr' [CWE-415] [-Wanalyzer-double-free]
+# . 6 | __builtin_free (ptr);
+# . | ^~~~~~~~~~~~~~~~~~~~
+# . 'test': events 1-2
+# . 5 | __builtin_free (ptr);
+# . | ^~~~~~~~~~~~~~~~~~~~
+# . | |
+# . | (1) first 'free' here
+# . 6 | __builtin_free (ptr);
+# . | ~~~~~~~~~~~~~~~~~~~~
+# . | |
+# . | (2) second 'free' here; first 'free' was at (1)
+
+from sarif import *
+
+import pytest
+
+@pytest.fixture(scope='function', autouse=True)
+def sarif():
+ return sarif_from_env()
+
+def test_basics(sarif):
+ schema = sarif['$schema']
+ assert schema == "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json"
+
+ version = sarif['version']
+ assert version == "2.1.0"
+
+def test_result(sarif):
+ runs = sarif['runs']
+ run = runs[0]
+ results = run['results']
+
+ assert len(results) == 1
+
+ result = results[0]
+ assert result['ruleId'] == '-Wanalyzer-double-free'
+ assert result['level'] == 'warning'
+ assert result['message']['text'] == "double-'free' of 'ptr'"
+ assert result["taxa"] == [{"id": "415",
+ "toolComponent": {"name": "cwe"}}]
+
+def test_location_relationships(sarif):
+ runs = sarif['runs']
+ run = runs[0]
+ results = run['results']
+
+ assert len(results) == 1
+
+ result = results[0]
+ locations = result['locations']
+ assert len(locations) == 1
+
+ location = locations[0]
+ assert 'id' in location
+ assert get_location_artifact_uri(location).endswith('include-chain-2.h')
+ assert get_location_snippet_text(location) == " __builtin_free (ptr); // 2nd\n"
+
+ # We expect one related location, describing the #include
+ relatedLocations = result['relatedLocations']
+ assert len(relatedLocations) == 1
+
+ # The "#include "include-chain-1.h" line in include-chain-1.c:
+ hash_include_2_h = relatedLocations[0]
+ assert 'id' in hash_include_2_h
+ assert get_location_snippet_text(hash_include_2_h) == '#include "include-chain-2.h"\n'
+ assert get_location_artifact_uri(hash_include_2_h).endswith('include-chain-2.c')
+
+ # We expect an execution path
+ assert len(result['codeFlows']) == 1
+ codeFlow = result['codeFlows'][0]
+ assert len(codeFlow['threadFlows']) == 1
+ threadFlow = codeFlow['threadFlows'][0]
+ assert threadFlow['id'] == 'main'
+ assert len(threadFlow['locations']) == 2
+
+ assert threadFlow['locations'][0]['location']['message']['text'] \
+ == "first 'free' here"
+ assert threadFlow['locations'][0]['location']['physicalLocation']['contextRegion']['snippet']['text'] \
+ == " __builtin_free (ptr); // 1st\n"
+ assert threadFlow['locations'][0]['kinds'] == ['release', 'memory']
+ assert threadFlow['locations'][0]['executionOrder'] == 1
+
+ assert threadFlow['locations'][1]['location']['message']['text'] \
+ == "second 'free' here; first 'free' was at (1)"
+ assert threadFlow['locations'][1]['location']['physicalLocation']['contextRegion']['snippet']['text'] \
+ == " __builtin_free (ptr); // 2nd\n"
+ assert threadFlow['locations'][1]['kinds'] == ['danger']
+ assert threadFlow['locations'][1]['executionOrder'] == 2
+
+ # Check the various relationships; we expect a directed graph of edges
+ # representing the various "isIncludedBy" and "includes" relationships.
+
+ # The primary location should be "isIncludedBy" the "#include "include-chain-2.h" line
+ assert len(location['relationships']) == 1
+ assert location['relationships'][0]['target'] == hash_include_2_h['id']
+ assert location['relationships'][0]['kinds'] == ["isIncludedBy"]
+
+ # Similarly, so should the locations within the threadFlow
+ event0_rels = get_location_relationships(threadFlow['locations'][0]['location'])
+ assert len(event0_rels) == 1
+ assert event0_rels[0]['target'] == hash_include_2_h['id']
+ assert event0_rels[0]['kinds'] == ["isIncludedBy"]
+
+ event1_rels = get_location_relationships(threadFlow['locations'][1]['location'])
+ assert len(event1_rels) == 1
+ assert event1_rels[0]['target'] == hash_include_2_h['id']
+ assert event1_rels[0]['kinds'] == ["isIncludedBy"]
+
+ # The "#include "include-chain-2.h" line in include-chain-2.c should
+ # have an "includes" relationship to the main location and to the
+ # two locations in the execution path:
+ assert len(hash_include_2_h['relationships']) == 3
+ assert hash_include_2_h['relationships'][0]['target'] == location['id']
+ assert hash_include_2_h['relationships'][0]['kinds'] == ["includes"]
+ assert hash_include_2_h['relationships'][1]['target'] == threadFlow['locations'][0]['location']['id']
+ assert hash_include_2_h['relationships'][1]['kinds'] == ["includes"]
+ assert hash_include_2_h['relationships'][2]['target'] == threadFlow['locations'][1]['location']['id']
+ assert hash_include_2_h['relationships'][2]['kinds'] == ["includes"]
@@ -103,3 +103,57 @@ proc verify-sarif-file { args } {
pass "$what"
}
}
+
+proc sarif-pytest-format-line { args } {
+ global subdir
+
+ set testcase [lindex $args 0]
+ set pytest_script [lindex $args 1]
+ set output_line [lindex $args 2]
+
+ set index [string first "::" $output_line]
+ set test_output [string range $output_line [expr $index + 2] [string length $output_line]]
+
+ return "$subdir/$testcase ${pytest_script}::${test_output}"
+}
+
+# Call by dg-final to run a pytest Python script.
+# We pass filename of a test via SARIF_PATH environment variable.
+
+proc run-sarif-pytest { args } {
+ global srcdir subdir
+ # Extract the test file name from the arguments.
+ set testcase [lindex $args 0]
+
+ verbose "Running SARIF $testcase in $srcdir/$subdir" 2
+ set testcase [remote_download host $testcase]
+
+ set pytest_script [lindex $args 1]
+ if { ![check_effective_target_pytest3] } {
+ unsupported "$pytest_script pytest python3 is missing"
+ return
+ }
+
+ setenv SARIF_PATH $testcase
+ spawn -noecho python3 -m pytest --color=no -rap -s --tb=no $srcdir/$subdir/$pytest_script
+
+ set prefix "\[^\r\n\]*"
+ expect {
+ -re "FAILED($prefix)\[^\r\n\]+\r\n" {
+ set output [sarif-pytest-format-line $testcase $pytest_script $expect_out(1,string)]
+ fail $output
+ exp_continue
+ }
+ -re "ERROR($prefix)\[^\r\n\]+\r\n" {
+ set output [sarif-pytest-format-line $testcase $pytest_script $expect_out(1,string)]
+ fail $output
+ exp_continue
+ }
+ -re "PASSED($prefix)\[^\r\n\]+\r\n" {
+ set output [sarif-pytest-format-line $testcase $pytest_script $expect_out(1,string)]
+ pass $output
+ exp_continue
+ }
+ }
+}
+
This patch extends our SARIF output to capture relationships between locations within a result (§3.34). In particular, this captures chains of #includes relating to diagnostics and to events within diagnostic paths. For example, consider: include-chain-1.c: #include "include-chain-1.h" include-chain-1.h: /* First set of decls, which will be referenced in notes. */ #include "include-chain-1-1.h" /* Second set of decls, which will trigger the errors. */ #include "include-chain-1-2.h" include-chain-1-1.h: int p; int q; include-chain-1-1.h: char p; char q; GCC's textual output emits: In file included from PATH/include-chain-1.h:5, from PATH/include-chain-1.c:30: PATH/include-chain-1-2.h:1:6: error: conflicting types for 'p'; have 'char' 1 | char p; | ^ In file included from PATH/include-chain-1.h:2: PATH/include-chain-1-1.h:1:5: note: previous declaration of 'p' with type 'int' 1 | int p; | ^ PATH/include-chain-1-2.h:2:6: error: conflicting types for 'q'; have 'char' 2 | char q; | ^ PATH/include-chain-1-1.h:2:5: note: previous declaration of 'q' with type 'int' 2 | int q; | ^ With this patch, the SARIF output captures the include information for the two results, so that e.g. result[0]'s location[0] has: "relationships": [{"target": 0, "kinds": ["isIncludedBy"]}], "id": 0 and the "note" in relatedLocations[0] has: "message": {"text": "previous declaration of 'q' with type 'int'"}, "relationships": [{"target": 2, "kinds": ["isIncludedBy"]}], "id": 2}, where these reference new locations within relatedLocations, such as this for the "#include "include-chain-1-1.h" line in include-chain-1.h: {"physicalLocation": {"artifactLocation": {"uri": include-chain-1.h", "uriBaseId": "PWD"}, "region": {"startLine": 5}, "contextRegion": {"startLine": 5, "snippet": {"text": "#include \"include-chain-1-2.h\"\n"}}}, "id": 1, "relationships": [{"target": 0, "kinds": ["includes"]}, {"target": 4, "kinds": ["isIncludedBy"]}]}, effectively capturing the inclusion digraph in SARIF form: +-----------------------------------+ +----------------------------------+ |"id": 0 | |"id": 2 | | error: "conflicting types for 'p';| | note: previous declaration of 'p'| | have 'char'"| | | with type 'int'") | | in include-chain-1-2.h | | in include-chain-1-1.h | +-----------------------------------+ +----------------------------------+ | | | included-by | included-by V V +--------------------------------+ +--------------------------------+ |"id": 1 | |"id": 3 | | #include "include-chain-1-2.h" | | #include "include-chain-1-1.h" | | in include-chain-1.h | | in include-chain-1.h | +--------------------------------+ +--------------------------------+ | | | included-by | included-by V V +------------------------------------+ |"id": 4 | | The #include "include-chain-1.h" | | in include-chain-1.c | +------------------------------------+ Locations only gain "id" fields if they need one, and the precise numbering of the IDs within a result is an implementation detail (the order in which references to the locations are made). To test all this non-trivial JSON from DejaGnu I needed to adapt the python testing code used by gcov, adding a new run-sarif-pytest based on run-gcov-pytest. Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu. Successful run of analyzer integration tests on x86_64-pc-linux-gnu. Pushed to trunk as r15-2354-g4d1f71d49e396c. gcc/ChangeLog: PR middle-end/107941 * diagnostic-format-sarif.cc: Define INCLUDE_LIST and INCLUDE_MAP. (enum class location_relationship_kind): New. (diagnostic_artifact_role::scanned_file): New value. (class sarif_location_manager): New. (class sarif_result): Derive from sarif_location_manager rather than directly from sarif_object. (sarif_result::add_related_location): Convert to vfunc implementation. (sarif_location::m_relationships_map): New field. (class sarif_location_relationship): New. (class sarif_ice_notification): Derive from sarif_location_manager rather than directly from sarif_object. (sarif_builder::take_current_result): New. (sarif_builder::m_line_maps): New field. (sarif_builder::m_cur_group_result): Convert to std::unique_ptr. (sarif_artifact::add_role): Skip scanned_file. (get_artifact_role_string): Handle scanned_file. (sarif_location_manager::add_relationship_to_worklist): New. (sarif_location_manager::process_worklist): New. (sarif_location_manager::process_worklist_item): New. (sarif_result::on_nested_diagnostic): Pass *this to make_location_object. (sarif_location::lazily_add_id): New. (sarif_location::get_id): New. (get_string_for_location_relationship_kind): New. (sarif_location::lazily_add_relationship): New. (sarif_location::lazily_add_relationship_object): New. (sarif_location::lazily_add_relationships_array): New. (sarif_ice_notification::sarif_ice_notification): Fix overlong line. Pass *this to make_locations_arr. (sarif_ice_notification::add_related_location): New. (sarif_location_relationship::sarif_location_relationship): New. (sarif_location_relationship::get_target_id): New. (sarif_location_relationship::lazily_add_kind): New. (sarif_builder::sarif_builder): Add "line_maps" param and use it to initialize m_line_maps. (sarif_builder::end_diagnostic): Update for m_cur_group_result becoming a std::unique_ptr. Don't append to m_results_array yet. (sarif_builder::end_group): Append m_cur_group_result to m_results_array here, rather than in end_diagnostic. (sarif_builder::make_result_object): Pass result_obj to make_locations_arr and to make_code_flow_object. (sarif_builder::make_locations_arr): Add "loc_mgr" param and pass it to make_location_object. (sarif_builder::make_location_object): For two overloads, add "loc_mgr" param and call add_any_include_chain on the location. (sarif_builder::add_any_include_chain): New. (sarif_builder::make_location_object): New overload. (sarif_builder::make_code_flow_object): Add "result" param and pass it to make_thread_flow_location_object. (sarif_builder::make_thread_flow_location_object): Add "result" param and pass it to make_location_object. (sarif_builder::get_or_create_artifact): Handle scanned_file. (sarif_output_format::~sarif_output_format): Assert that there isn't a pending result. (sarif_output_format::sarif_output_format): Add "line_maps" param and pass it to m_builder's ctor. (sarif_stream_output_format::sarif_stream_output_format): Add "line_maps" param and pass it to base class ctor. (sarif_file_output_format::sarif_file_output_format): Likewise. (diagnostic_output_format_init_sarif_stderr): Pass "line_table" global to format. (diagnostic_output_format_init_sarif_file): Likewise. (diagnostic_output_format_init_sarif_stream): Likewise. (test_sarif_diagnostic_context::test_sarif_diagnostic_context): Likewise. (buffered_output_format::buffered_output_format): Likewise. (selftest::test_make_location_object): Likewise. (selftest::test_make_location_object): Create a sarif_result for use when calling make_location_object. * diagnostic.cc (diagnostic_context::finish): End any active diagnostic groups. (diagnostic_context::report_diagnostic): Assert that we're within a diagnostic group. * diagnostic.h (diagnostic_report_diagnostic): Add begin_group/end_group pair around call to diagnostic_context::report_diagnostic. * selftest-diagnostic.cc (test_diagnostic_context::report): Add begin_group/end_group pair around diagnostic_impl call. gcc/testsuite/ChangeLog: PR middle-end/107941 * gcc.dg/sarif-output/include-chain-1-1.h: New test. * gcc.dg/sarif-output/include-chain-1-2.h: New test. * gcc.dg/sarif-output/include-chain-1.c: New test. * gcc.dg/sarif-output/include-chain-1.h: New test. * gcc.dg/sarif-output/include-chain-2.c: New test. * gcc.dg/sarif-output/include-chain-2.h: New test. * gcc.dg/sarif-output/sarif-output.exp: New file. * gcc.dg/sarif-output/sarif.py: New test, adapted from g++.dg/gcov/gcov.py. * gcc.dg/sarif-output/test-include-chain-1.py: New test. * gcc.dg/sarif-output/test-include-chain-2.py: New test. * lib/scansarif.exp (sarif-pytest-format-line): New, taken from lib/gcov.exp. (run-sarif-pytest): New, adapted from run-gcov-pytest in lib/gcov.exp. Signed-off-by: David Malcolm <dmalcolm@redhat.com> --- gcc/diagnostic-format-sarif.cc | 680 ++++++++++++++++-- gcc/diagnostic.cc | 13 + gcc/diagnostic.h | 5 +- gcc/selftest-diagnostic.cc | 2 + .../gcc.dg/sarif-output/include-chain-1-1.h | 2 + .../gcc.dg/sarif-output/include-chain-1-2.h | 2 + .../gcc.dg/sarif-output/include-chain-1.c | 42 ++ .../gcc.dg/sarif-output/include-chain-1.h | 5 + .../gcc.dg/sarif-output/include-chain-2.c | 40 ++ .../gcc.dg/sarif-output/include-chain-2.h | 7 + .../gcc.dg/sarif-output/sarif-output.exp | 31 + gcc/testsuite/gcc.dg/sarif-output/sarif.py | 21 + .../sarif-output/test-include-chain-1.py | 125 ++++ .../sarif-output/test-include-chain-2.py | 124 ++++ gcc/testsuite/lib/scansarif.exp | 54 ++ 15 files changed, 1109 insertions(+), 44 deletions(-) create mode 100644 gcc/testsuite/gcc.dg/sarif-output/include-chain-1-1.h create mode 100644 gcc/testsuite/gcc.dg/sarif-output/include-chain-1-2.h create mode 100644 gcc/testsuite/gcc.dg/sarif-output/include-chain-1.c create mode 100644 gcc/testsuite/gcc.dg/sarif-output/include-chain-1.h create mode 100644 gcc/testsuite/gcc.dg/sarif-output/include-chain-2.c create mode 100644 gcc/testsuite/gcc.dg/sarif-output/include-chain-2.h create mode 100644 gcc/testsuite/gcc.dg/sarif-output/sarif-output.exp create mode 100644 gcc/testsuite/gcc.dg/sarif-output/sarif.py create mode 100644 gcc/testsuite/gcc.dg/sarif-output/test-include-chain-1.py create mode 100644 gcc/testsuite/gcc.dg/sarif-output/test-include-chain-2.py