diff --git a/gcc/diagnostic-format-sarif.cc b/gcc/diagnostic-format-sarif.cc
index 1fc45c9b4b39..84b3e651e462 100644
--- a/gcc/diagnostic-format-sarif.cc
+++ b/gcc/diagnostic-format-sarif.cc
@@ -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
diff --git a/gcc/diagnostic.cc b/gcc/diagnostic.cc
index c70c394f7ccd..46cddfe94d1d 100644
--- a/gcc/diagnostic.cc
+++ b/gcc/diagnostic.cc
@@ -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
diff --git a/gcc/diagnostic.h b/gcc/diagnostic.h
index 1d83879c50ef..36a4c23b0737 100644
--- a/gcc/diagnostic.h
+++ b/gcc/diagnostic.h
@@ -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
diff --git a/gcc/selftest-diagnostic.cc b/gcc/selftest-diagnostic.cc
index c9e9d7094bd3..3a14739c1e3d 100644
--- a/gcc/selftest-diagnostic.cc
+++ b/gcc/selftest-diagnostic.cc
@@ -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;
 }
diff --git a/gcc/testsuite/gcc.dg/sarif-output/include-chain-1-1.h b/gcc/testsuite/gcc.dg/sarif-output/include-chain-1-1.h
new file mode 100644
index 000000000000..b292c7bb96b5
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/sarif-output/include-chain-1-1.h
@@ -0,0 +1,2 @@
+int p;
+int q;
diff --git a/gcc/testsuite/gcc.dg/sarif-output/include-chain-1-2.h b/gcc/testsuite/gcc.dg/sarif-output/include-chain-1-2.h
new file mode 100644
index 000000000000..664bfe6e1ffe
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/sarif-output/include-chain-1-2.h
@@ -0,0 +1,2 @@
+char p;
+char q;
diff --git a/gcc/testsuite/gcc.dg/sarif-output/include-chain-1.c b/gcc/testsuite/gcc.dg/sarif-output/include-chain-1.c
new file mode 100644
index 000000000000..177f528cc62d
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/sarif-output/include-chain-1.c
@@ -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" } } */
diff --git a/gcc/testsuite/gcc.dg/sarif-output/include-chain-1.h b/gcc/testsuite/gcc.dg/sarif-output/include-chain-1.h
new file mode 100644
index 000000000000..3fff40d49236
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/sarif-output/include-chain-1.h
@@ -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"
diff --git a/gcc/testsuite/gcc.dg/sarif-output/include-chain-2.c b/gcc/testsuite/gcc.dg/sarif-output/include-chain-2.c
new file mode 100644
index 000000000000..3f984f48979b
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/sarif-output/include-chain-2.c
@@ -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" } } */
diff --git a/gcc/testsuite/gcc.dg/sarif-output/include-chain-2.h b/gcc/testsuite/gcc.dg/sarif-output/include-chain-2.h
new file mode 100644
index 000000000000..382ac026fe04
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/sarif-output/include-chain-2.h
@@ -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
+}
diff --git a/gcc/testsuite/gcc.dg/sarif-output/sarif-output.exp b/gcc/testsuite/gcc.dg/sarif-output/sarif-output.exp
new file mode 100644
index 000000000000..1f977ca1f568
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/sarif-output/sarif-output.exp
@@ -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
diff --git a/gcc/testsuite/gcc.dg/sarif-output/sarif.py b/gcc/testsuite/gcc.dg/sarif-output/sarif.py
new file mode 100644
index 000000000000..54c96a006b65
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/sarif-output/sarif.py
@@ -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']
diff --git a/gcc/testsuite/gcc.dg/sarif-output/test-include-chain-1.py b/gcc/testsuite/gcc.dg/sarif-output/test-include-chain-1.py
new file mode 100644
index 000000000000..16cd6a6ac4d9
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/sarif-output/test-include-chain-1.py
@@ -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
diff --git a/gcc/testsuite/gcc.dg/sarif-output/test-include-chain-2.py b/gcc/testsuite/gcc.dg/sarif-output/test-include-chain-2.py
new file mode 100644
index 000000000000..aea9aabb5ef5
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/sarif-output/test-include-chain-2.py
@@ -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"]
diff --git a/gcc/testsuite/lib/scansarif.exp b/gcc/testsuite/lib/scansarif.exp
index cc0890ef5d8b..e08f80c9ce18 100644
--- a/gcc/testsuite/lib/scansarif.exp
+++ b/gcc/testsuite/lib/scansarif.exp
@@ -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
+      }
+    }
+}
+
