[pushed] diagnostics: attempt to capture crash info in SARIF output [PR109097]

Message ID 20230315222022.3505853-1-dmalcolm@redhat.com
State Committed
Commit 79aaba0a71f34ac1ac2c4cec907ff74740a6cf1a
Headers
Series [pushed] diagnostics: attempt to capture crash info in SARIF output [PR109097] |

Commit Message

David Malcolm March 15, 2023, 10:20 p.m. UTC
  As noted in PR analyzer/109097, if an internal compiler error occurs
when -fdiagnostics-format=sarif-file is specified, we currently fail
to write out a .sarif file, and the output to stderr doesn't contain
"internal compiler error" or "Internal compiler error"; just the
backtrace if we're lucky, and the "Please submit a full bug report"
messages.

This is a nuisance e.g. for my integration testing of -fanalyzer, where
I'm gathering the results of builds via the .sarif output: if it crashes
on a particular source file, then no output is generated, and it's
effectively silent about the crash.

This patch fixes things by adding a callback to diagnostic_context so
that the SARIF output code can make one final attempt to write its
output if an ICE occurs.  It also special-cases the output, so that an
ICE is treated as an "error"-level "notification" relating to the
operation of the tool (SARIF v2.1.0 section 3.58), rather than a
"result" about the code being analyzed by the tool.

The patch adds test coverage for this via a plugin that can inject:
* calls to internal_compiler_error, and
* writes through a NULL pointer
and verifying that a  .sarif file is written out capturing the crash
(and also that an ICE occurs via dg-ice, which seems to treat the ICE as
an XFAIL, which is reasonable).

I've added support for this to my integration-testing scripts: testing
shows that with this patch we capture analyzer crashes in .sarif files
(specifically, the analyzer crash on qemu: PR analyzer/109094), and I've
updated my scripts to work with and report such output.

I manually verified that the resulting .sarif files validate against the
schema.

Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.
Pushed to trunk as r13-6701-g79aaba0a71f34a.

gcc/ChangeLog:
	PR analyzer/109097
	* diagnostic-format-sarif.cc (class sarif_invocation): New.
	(class sarif_ice_notification): New.
	(sarif_builder::m_invocation_obj): New field.
	(sarif_invocation::add_notification_for_ice): New.
	(sarif_invocation::prepare_to_flush): New.
	(sarif_ice_notification::sarif_ice_notification): New.
	(sarif_builder::sarif_builder): Add m_invocation_obj.
	(sarif_builder::end_diagnostic): Special-case DK_ICE and
	DK_ICE_NOBT.
	(sarif_builder::flush_to_file): Call prepare_to_flush on
	m_invocation_obj.  Pass the latter to make_top_level_object.
	(sarif_builder::make_result_object): Move creation of "locations"
	array to...
	(sarif_builder::make_locations_arr): ...this new function.
	(sarif_builder::make_top_level_object): Add "invocation_obj" param
	and pass it to make_run_object.
	(sarif_builder::make_run_object): Add "invocation_obj" param and
	use it.
	(sarif_ice_handler): New callback.
	(diagnostic_output_format_init_sarif): Wire up sarif_ice_handler.
	* diagnostic.cc (diagnostic_initialize): Initialize new field
	"ice_handler_cb".
	(diagnostic_action_after_output): If it is set, make one attempt
	to call ice_handler_cb.
	* diagnostic.h (diagnostic_context::ice_handler_cb): New field.

gcc/testsuite/ChangeLog:
	PR analyzer/109097
	* c-c++-common/diagnostic-format-sarif-file-1.c: Verify that we
	have an invocation object marked as succeeding, with no
	notifications.
	* gcc.dg/plugin/crash-test-ice-sarif.c: New test.
	* gcc.dg/plugin/crash-test-ice-stderr.c: New test.
	* gcc.dg/plugin/crash-test-write-though-null-sarif.c: New test.
	* gcc.dg/plugin/crash-test-write-though-null-stderr.c: New test.
	* gcc.dg/plugin/crash_test_plugin.c: New plugin.
	* gcc.dg/plugin/plugin.exp (plugin_test_list): Add the new plugin
	and test cases.

Signed-off-by: David Malcolm <dmalcolm@redhat.com>
---
 gcc/diagnostic-format-sarif.cc                | 173 ++++++++++++++++--
 gcc/diagnostic.cc                             |  13 ++
 gcc/diagnostic.h                              |   3 +
 .../diagnostic-format-sarif-file-1.c          |   5 +
 .../gcc.dg/plugin/crash-test-ice-sarif.c      |  62 +++++++
 .../gcc.dg/plugin/crash-test-ice-stderr.c     |   9 +
 .../crash-test-write-though-null-sarif.c      |  62 +++++++
 .../crash-test-write-though-null-stderr.c     |   9 +
 .../gcc.dg/plugin/crash_test_plugin.c         | 135 ++++++++++++++
 gcc/testsuite/gcc.dg/plugin/plugin.exp        |   5 +
 10 files changed, 458 insertions(+), 18 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/plugin/crash-test-ice-sarif.c
 create mode 100644 gcc/testsuite/gcc.dg/plugin/crash-test-ice-stderr.c
 create mode 100644 gcc/testsuite/gcc.dg/plugin/crash-test-write-though-null-sarif.c
 create mode 100644 gcc/testsuite/gcc.dg/plugin/crash-test-write-though-null-stderr.c
 create mode 100644 gcc/testsuite/gcc.dg/plugin/crash_test_plugin.c
  

Patch

diff --git a/gcc/diagnostic-format-sarif.cc b/gcc/diagnostic-format-sarif.cc
index f8fdd586ff0..2c48cbd46e2 100644
--- a/gcc/diagnostic-format-sarif.cc
+++ b/gcc/diagnostic-format-sarif.cc
@@ -32,8 +32,29 @@  along with GCC; see the file COPYING3.  If not see
 
 class sarif_builder;
 
+/* Subclass of json::object for SARIF invocation objects
+   (SARIF v2.1.0 section 3.20).  */
+
+class sarif_invocation : public json::object
+{
+public:
+  sarif_invocation ()
+  : m_notifications_arr (new json::array ()),
+    m_success (true)
+  {}
+
+  void add_notification_for_ice (diagnostic_context *context,
+				 diagnostic_info *diagnostic,
+				 sarif_builder *builder);
+  void prepare_to_flush ();
+
+private:
+  json::array *m_notifications_arr;
+  bool m_success;
+};
+
 /* Subclass of json::object for SARIF result objects
-   (SARIF v2.1.0 section 3.27.  */
+   (SARIF v2.1.0 section 3.27).  */
 
 class sarif_result : public json::object
 {
@@ -50,6 +71,20 @@  private:
   json::array *m_related_locations_arr;
 };
 
+/* Subclass of json::object for SARIF notification objects
+   (SARIF v2.1.0 section 3.58).
+
+   This subclass is specifically for notifying when an
+   internal compiler error occurs.  */
+
+class sarif_ice_notification : public json::object
+{
+public:
+  sarif_ice_notification (diagnostic_context *context,
+			  diagnostic_info *diagnostic,
+			  sarif_builder *builder);
+};
+
 /* A class for managing SARIF output (for -fdiagnostics-format=sarif-stderr
    and -fdiagnostics-format=sarif-file).
 
@@ -105,6 +140,7 @@  public:
 
   void flush_to_file (FILE *outf);
 
+  json::array *make_locations_arr (diagnostic_info *diagnostic);
   json::object *make_location_object (const rich_location &rich_loc,
 				      const logical_location *logical_loc);
   json::object *make_message_object (const char *msg) const;
@@ -131,8 +167,10 @@  private:
   json::object *maybe_make_region_object_for_context (location_t loc) const;
   json::object *make_region_object_for_hint (const fixit_hint &hint) const;
   json::object *make_multiformat_message_string (const char *msg) const;
-  json::object *make_top_level_object (json::array *results);
-  json::object *make_run_object (json::array *results);
+  json::object *make_top_level_object (sarif_invocation *invocation_obj,
+				       json::array *results);
+  json::object *make_run_object (sarif_invocation *invocation_obj,
+				 json::array *results);
   json::object *make_tool_object () const;
   json::object *make_driver_tool_component_object () const;
   json::array *maybe_make_taxonomies_array () const;
@@ -159,6 +197,9 @@  private:
 
   diagnostic_context *m_context;
 
+  /* The JSON object for the invocation object.  */
+  sarif_invocation *m_invocation_obj;
+
   /* The JSON array of pending diagnostics.  */
   json::array *m_results_array;
 
@@ -179,6 +220,33 @@  private:
 
 static sarif_builder *the_builder;
 
+/* class sarif_invocation : public json::object.  */
+
+/* Handle an internal compiler error DIAGNOSTIC occurring on CONTEXT.
+   Add an object representing the ICE to the notifications array.  */
+
+void
+sarif_invocation::add_notification_for_ice (diagnostic_context *context,
+					    diagnostic_info *diagnostic,
+					    sarif_builder *builder)
+{
+  m_success = false;
+
+  sarif_ice_notification *notification_obj
+    = new sarif_ice_notification (context, diagnostic, builder);
+  m_notifications_arr->append (notification_obj);
+}
+
+void
+sarif_invocation::prepare_to_flush ()
+{
+  /* "executionSuccessful" property (SARIF v2.1.0 section 3.20.14).  */
+  set ("executionSuccessful", new json::literal (m_success));
+
+  /* "toolExecutionNotifications" property (SARIF v2.1.0 section 3.20.21).  */
+  set ("toolExecutionNotifications", m_notifications_arr);
+}
+
 /* class sarif_result : public json::object.  */
 
 /* Handle secondary diagnostics that occur within a diagnostic group.
@@ -212,12 +280,36 @@  sarif_result::on_nested_diagnostic (diagnostic_context *context,
   m_related_locations_arr->append (location_obj);
 }
 
+/* class sarif_ice_notification : public json::object.  */
+
+/* sarif_ice_notification's ctor.
+   DIAGNOSTIC is an internal compiler error.  */
+
+sarif_ice_notification::sarif_ice_notification (diagnostic_context *context,
+						diagnostic_info *diagnostic,
+						sarif_builder *builder)
+{
+  /* "locations" property (SARIF v2.1.0 section 3.58.4).  */
+  json::array *locations_arr = builder->make_locations_arr (diagnostic);
+  set ("locations", locations_arr);
+
+  /* "message" property (SARIF v2.1.0 section 3.85.5).  */
+  json::object *message_obj
+    = builder->make_message_object (pp_formatted_text (context->printer));
+  pp_clear_output_area (context->printer);
+  set ("message", message_obj);
+
+  /* "level" property (SARIF v2.1.0 section 3.58.6).  */
+  set ("level", new json::string ("error"));
+}
+
 /* class sarif_builder.  */
 
 /* sarif_builder's ctor.  */
 
 sarif_builder::sarif_builder (diagnostic_context *context)
 : m_context (context),
+  m_invocation_obj (new sarif_invocation ()),
   m_results_array (new json::array ()),
   m_cur_group_result (NULL),
   m_seen_any_relative_paths (false),
@@ -234,6 +326,11 @@  sarif_builder::end_diagnostic (diagnostic_context *context,
 			       diagnostic_info *diagnostic,
 			       diagnostic_t orig_diag_kind)
 {
+  if (diagnostic->kind == DK_ICE || diagnostic->kind == DK_ICE_NOBT)
+    {
+      m_invocation_obj->add_notification_for_ice (context, diagnostic, this);
+      return;
+    }
 
   if (m_cur_group_result)
     /* Nested diagnostic.  */
@@ -267,8 +364,10 @@  sarif_builder::end_group ()
 void
 sarif_builder::flush_to_file (FILE *outf)
 {
-  json::object *top = make_top_level_object (m_results_array);
+  m_invocation_obj->prepare_to_flush ();
+  json::object *top = make_top_level_object (m_invocation_obj, m_results_array);
   top->dump (outf);
+  m_invocation_obj = NULL;
   m_results_array = NULL;
   fprintf (outf, "\n");
   delete top;
@@ -387,15 +486,7 @@  sarif_builder::make_result_object (diagnostic_context *context,
   result_obj->set ("message", message_obj);
 
   /* "locations" property (SARIF v2.1.0 section 3.27.12).  */
-  json::array *locations_arr = new json::array ();
-  const logical_location *logical_loc = NULL;
-  if (m_context->m_client_data_hooks)
-    logical_loc
-      = m_context->m_client_data_hooks->get_current_logical_location ();
-
-  json::object *location_obj
-    = make_location_object (*diagnostic->richloc, logical_loc);
-  locations_arr->append (location_obj);
+  json::array *locations_arr = make_locations_arr (diagnostic);
   result_obj->set ("locations", locations_arr);
 
   /* "codeFlows" property (SARIF v2.1.0 section 3.27.18).  */
@@ -525,6 +616,25 @@  make_tool_component_reference_object_for_cwe () const
   return comp_ref_obj;
 }
 
+/* Make an array suitable for use as the "locations" property of:
+   - a "result" object (SARIF v2.1.0 section 3.27.12), or
+   - a "notification" object (SARIF v2.1.0 section 3.58.4).  */
+
+json::array *
+sarif_builder::make_locations_arr (diagnostic_info *diagnostic)
+{
+  json::array *locations_arr = new json::array ();
+  const logical_location *logical_loc = NULL;
+  if (m_context->m_client_data_hooks)
+    logical_loc
+      = m_context->m_client_data_hooks->get_current_logical_location ();
+
+  json::object *location_obj
+    = make_location_object (*diagnostic->richloc, logical_loc);
+  locations_arr->append (location_obj);
+  return locations_arr;
+}
+
 /* If LOGICAL_LOC is non-NULL, use it to create a "logicalLocations" property
    within LOCATION_OBJ (SARIF v2.1.0 section 3.28.4).  */
 
@@ -1023,10 +1133,11 @@  sarif_builder::make_multiformat_message_string (const char *msg) const
 #define SARIF_VERSION "2.1.0"
 
 /* Make a top-level sarifLog object (SARIF v2.1.0 section 3.13).
-   Take ownership of RESULTS.  */
+   Take ownership of INVOCATION_OBJ and RESULTS.  */
 
 json::object *
-sarif_builder::make_top_level_object (json::array *results)
+sarif_builder::make_top_level_object (sarif_invocation *invocation_obj,
+				      json::array *results)
 {
   json::object *log_obj = new json::object ();
 
@@ -1038,7 +1149,7 @@  sarif_builder::make_top_level_object (json::array *results)
 
   /* "runs" property (SARIF v2.1.0 section 3.13.4).  */
   json::array *run_arr = new json::array ();
-  json::object *run_obj = make_run_object (results);
+  json::object *run_obj = make_run_object (invocation_obj, results);
   run_arr->append (run_obj);
   log_obj->set ("runs", run_arr);
 
@@ -1046,10 +1157,11 @@  sarif_builder::make_top_level_object (json::array *results)
 }
 
 /* Make a run object (SARIF v2.1.0 section 3.14).
-   Take ownership of RESULTS.  */
+   Take ownership of INVOCATION_OBJ and RESULTS.  */
 
 json::object *
-sarif_builder::make_run_object (json::array *results)
+sarif_builder::make_run_object (sarif_invocation *invocation_obj,
+				json::array *results)
 {
   json::object *run_obj = new json::object ();
 
@@ -1061,6 +1173,13 @@  sarif_builder::make_run_object (json::array *results)
   if (json::array *taxonomies_arr = maybe_make_taxonomies_array ())
     run_obj->set ("taxonomies", taxonomies_arr);
 
+  /* "invocations" property (SARIF v2.1.0 section 3.14.11).  */
+  {
+    json::array *invocations_arr = new json::array ();
+    invocations_arr->append (invocation_obj);
+    run_obj->set ("invocations", invocations_arr);
+  }
+
   /* "originalUriBaseIds (SARIF v2.1.0 section 3.14.14).  */
   if (m_seen_any_relative_paths)
     {
@@ -1538,6 +1657,23 @@  sarif_file_final_cb (diagnostic_context *)
   free (filename);
 }
 
+/* Callback for diagnostic_context::ice_handler_cb for when an ICE
+   occurs.  */
+
+static void
+sarif_ice_handler (diagnostic_context *context)
+{
+  /* Attempt to ensure that a .sarif file is written out.  */
+  diagnostic_finish (context);
+
+  /* Print a header for the remaining output to stderr, and
+     return, attempting to print the usual ICE messages to
+     stderr.  Hopefully this will be helpful to the user in
+     indicating what's gone wrong (also for DejaGnu, for pruning
+     those messages).   */
+  fnotice (stderr, "Internal compiler error:\n");
+}
+
 /* Populate CONTEXT in preparation for SARIF output (either to stderr, or
    to a file).  */
 
@@ -1552,6 +1688,7 @@  diagnostic_output_format_init_sarif (diagnostic_context *context)
   context->begin_group_cb = sarif_begin_group;
   context->end_group_cb =  sarif_end_group;
   context->print_path = NULL; /* handled in sarif_end_diagnostic.  */
+  context->ice_handler_cb = sarif_ice_handler;
 
   /* The metadata is handled in SARIF format, rather than as text.  */
   context->show_cwe = false;
diff --git a/gcc/diagnostic.cc b/gcc/diagnostic.cc
index c90c14e993e..0f093081161 100644
--- a/gcc/diagnostic.cc
+++ b/gcc/diagnostic.cc
@@ -241,6 +241,7 @@  diagnostic_initialize (diagnostic_context *context, int n_opts)
   context->begin_group_cb = NULL;
   context->end_group_cb = NULL;
   context->final_cb = default_diagnostic_final_cb;
+  context->ice_handler_cb = NULL;
   context->includes_seen = NULL;
   context->m_client_data_hooks = NULL;
 }
@@ -665,6 +666,18 @@  diagnostic_action_after_output (diagnostic_context *context,
     case DK_ICE:
     case DK_ICE_NOBT:
       {
+	/* Optional callback for attempting to handle ICEs gracefully.  */
+	if (void (*ice_handler_cb) (diagnostic_context *)
+	      = context->ice_handler_cb)
+	  {
+	    /* Clear the callback, to avoid potentially re-entering
+	       the routine if there's a crash within the handler.  */
+	    context->ice_handler_cb = NULL;
+	    ice_handler_cb (context);
+	  }
+	/* The context might have had diagnostic_finish called on
+	   it at this point.  */
+
 	struct backtrace_state *state = NULL;
 	if (diag_kind == DK_ICE)
 	  state = backtrace_create_state (NULL, 0, bt_err_callback, NULL);
diff --git a/gcc/diagnostic.h b/gcc/diagnostic.h
index e7390e49554..9a51097f146 100644
--- a/gcc/diagnostic.h
+++ b/gcc/diagnostic.h
@@ -405,6 +405,9 @@  struct diagnostic_context
      of a diagnostic's location.  */
   void (*set_locations_cb)(diagnostic_context *, diagnostic_info *);
 
+  /* Optional callback for attempting to handle ICEs gracefully.  */
+  void (*ice_handler_cb) (diagnostic_context *context);
+
   /* Include files that diagnostic_report_current_module has already listed the
      include path for.  */
   hash_set<location_t, false, location_hash> *includes_seen;
diff --git a/gcc/testsuite/c-c++-common/diagnostic-format-sarif-file-1.c b/gcc/testsuite/c-c++-common/diagnostic-format-sarif-file-1.c
index 4d19ae14bb4..cad53095478 100644
--- a/gcc/testsuite/c-c++-common/diagnostic-format-sarif-file-1.c
+++ b/gcc/testsuite/c-c++-common/diagnostic-format-sarif-file-1.c
@@ -24,6 +24,11 @@ 
          { dg-final { scan-sarif-file "\"name\": \"GNU C" } }
          { dg-final { scan-sarif-file "\"fullName\": \"GNU C" } }
          { dg-final { scan-sarif-file "\"informationUri\": \"" } }
+
+     { dg-final { scan-sarif-file "\"invocations\": \\\[" } }
+       { dg-final { scan-sarif-file "\"toolExecutionNotifications\": \\\[\\\]" } }
+       { dg-final { scan-sarif-file "\"executionSuccessful\": true" } }
+
      { dg-final { scan-sarif-file "\"results\": \\\[" } }
        { dg-final { scan-sarif-file "\"level\": \"warning\"" } }
        { dg-final { scan-sarif-file "\"ruleId\": \"-Wcpp\"" } }
diff --git a/gcc/testsuite/gcc.dg/plugin/crash-test-ice-sarif.c b/gcc/testsuite/gcc.dg/plugin/crash-test-ice-sarif.c
new file mode 100644
index 00000000000..9186a3262ca
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/crash-test-ice-sarif.c
@@ -0,0 +1,62 @@ 
+/* { dg-do compile } */
+/* { dg-options "-fdiagnostics-format=sarif-file" } */
+
+extern void inject_ice (void);
+
+void test_inject_ice (void)
+{
+  inject_ice (); /* { dg-ice "" } */
+  /* { dg-regexp "during GIMPLE pass: crash_test" } */
+}
+
+/* Verify that some JSON was written to a file with the expected name.  */
+
+/* We expect various properties.
+   The indentation here reflects the expected hierarchy, though these tests
+   don't check for that, merely the string fragments we expect.
+
+   { dg-final { scan-sarif-file "\"version\": \"2.1.0\"" } }
+   { dg-final { scan-sarif-file "\"runs\": \\\[" } }
+     { dg-final { scan-sarif-file "\"artifacts\": \\\[" } } 
+       { dg-final { scan-sarif-file "\"location\": " } }
+         { dg-final { scan-sarif-file "\"uri\": " } }
+
+       { dg-final { scan-sarif-file "\"sourceLanguage\": \"c\"" { target c } } }
+       { dg-final { scan-sarif-file "\"sourceLanguage\": \"cplusplus\"" { target c++ } } }
+
+       { dg-final { scan-sarif-file "\"contents\": " } }
+         { dg-final { scan-sarif-file "\"text\": " } }
+     { dg-final { scan-sarif-file "\"tool\": " } }
+       { dg-final { scan-sarif-file "\"driver\": " } }
+         { dg-final { scan-sarif-file "\"name\": \"GNU C" } }
+         { dg-final { scan-sarif-file "\"fullName\": \"GNU C" } }
+         { dg-final { scan-sarif-file "\"informationUri\": \"" } }
+       { dg-final { scan-sarif-file "\"extensions\": \\\[" } }
+         { dg-final { scan-sarif-file "\"name\": \"crash_test_plugin\"" } }
+
+     We expect no results:
+     { dg-final { scan-sarif-file "\"results\": \\\[\\\]" } }
+
+     but instead should have an invocations array...
+
+     { dg-final { scan-sarif-file "\"invocations\": \\\[" } }
+
+     ...containing this:
+       { dg-final { scan-sarif-file "\"executionSuccessful\": false" } }
+       { dg-final { scan-sarif-file "\"toolExecutionNotifications\": \\\[" } }
+
+       ...containing this notification:
+         { dg-final { scan-sarif-file "\"level\": \"error\"" } }
+         { dg-final { scan-sarif-file "\"locations\": \\\[" } }
+           { dg-final { scan-sarif-file "\"logicalLocations\": \\\[" } }
+             { dg-final { scan-sarif-file "\"kind\": \"function\"" } }
+             { dg-final { scan-sarif-file "\"name\": \"test_inject_ice\"" } }
+           { dg-final { scan-sarif-file "\"physicalLocation\": " } }
+             { dg-final { scan-sarif-file "\"contextRegion\": " } }
+             { dg-final { scan-sarif-file "\"artifactLocation\": " } }
+             { dg-final { scan-sarif-file "\"region\": " } }
+               { dg-final { scan-sarif-file "\"startLine\": 8" } }
+               { dg-final { scan-sarif-file "\"startColumn\": 3" } }
+               { dg-final { scan-sarif-file "\"endColumn\": 16" } }
+         { dg-final { scan-sarif-file "\"message\": " } }
+           { dg-final { scan-sarif-file "\"text\": \"I'm sorry Dave, I'm afraid I can't do that\"" } } */
diff --git a/gcc/testsuite/gcc.dg/plugin/crash-test-ice-stderr.c b/gcc/testsuite/gcc.dg/plugin/crash-test-ice-stderr.c
new file mode 100644
index 00000000000..cee701b135c
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/crash-test-ice-stderr.c
@@ -0,0 +1,9 @@ 
+/* { dg-do compile } */
+
+extern void inject_ice (void);
+
+void test_1 (void)
+{
+  inject_ice (); /* { dg-ice "I'm sorry Dave, I'm afraid I can't do that" } */
+  /* { dg-regexp "during GIMPLE pass: crash_test" } */
+}
diff --git a/gcc/testsuite/gcc.dg/plugin/crash-test-write-though-null-sarif.c b/gcc/testsuite/gcc.dg/plugin/crash-test-write-though-null-sarif.c
new file mode 100644
index 00000000000..99de3f888d4
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/crash-test-write-though-null-sarif.c
@@ -0,0 +1,62 @@ 
+/* { dg-do compile } */
+/* { dg-options "-fdiagnostics-format=sarif-file" } */
+
+extern void inject_write_through_null (void);
+
+void test_inject_write_through_null (void)
+{
+  inject_write_through_null (); /* { dg-ice "" } */
+  /* { dg-regexp "during GIMPLE pass: crash_test" } */
+}
+
+/* Verify that some JSON was written to a file with the expected name.  */
+
+/* We expect various properties.
+   The indentation here reflects the expected hierarchy, though these tests
+   don't check for that, merely the string fragments we expect.
+
+   { dg-final { scan-sarif-file "\"version\": \"2.1.0\"" } }
+   { dg-final { scan-sarif-file "\"runs\": \\\[" } }
+     { dg-final { scan-sarif-file "\"artifacts\": \\\[" } } 
+       { dg-final { scan-sarif-file "\"location\": " } }
+         { dg-final { scan-sarif-file "\"uri\": " } }
+
+       { dg-final { scan-sarif-file "\"sourceLanguage\": \"c\"" { target c } } }
+       { dg-final { scan-sarif-file "\"sourceLanguage\": \"cplusplus\"" { target c++ } } }
+
+       { dg-final { scan-sarif-file "\"contents\": " } }
+         { dg-final { scan-sarif-file "\"text\": " } }
+     { dg-final { scan-sarif-file "\"tool\": " } }
+       { dg-final { scan-sarif-file "\"driver\": " } }
+         { dg-final { scan-sarif-file "\"name\": \"GNU C" } }
+         { dg-final { scan-sarif-file "\"fullName\": \"GNU C" } }
+         { dg-final { scan-sarif-file "\"informationUri\": \"" } }
+       { dg-final { scan-sarif-file "\"extensions\": \\\[" } }
+         { dg-final { scan-sarif-file "\"name\": \"crash_test_plugin\"" } }
+
+     We expect no results:
+     { dg-final { scan-sarif-file "\"results\": \\\[\\\]" } }
+
+     but instead should have an invocations array...
+
+     { dg-final { scan-sarif-file "\"invocations\": \\\[" } }
+
+     ...containing this:
+       { dg-final { scan-sarif-file "\"executionSuccessful\": false" } }
+       { dg-final { scan-sarif-file "\"toolExecutionNotifications\": \\\[" } }
+
+       ...containing this notification:
+         { dg-final { scan-sarif-file "\"level\": \"error\"" } }
+         { dg-final { scan-sarif-file "\"locations\": \\\[" } }
+           { dg-final { scan-sarif-file "\"logicalLocations\": \\\[" } }
+             { dg-final { scan-sarif-file "\"kind\": \"function\"" } }
+             { dg-final { scan-sarif-file "\"name\": \"test_inject_write_through_null\"" } }
+           { dg-final { scan-sarif-file "\"physicalLocation\": " } }
+             { dg-final { scan-sarif-file "\"contextRegion\": " } }
+             { dg-final { scan-sarif-file "\"artifactLocation\": " } }
+             { dg-final { scan-sarif-file "\"region\": " } }
+               { dg-final { scan-sarif-file "\"startLine\": 8" } }
+               { dg-final { scan-sarif-file "\"startColumn\": 3" } }
+               { dg-final { scan-sarif-file "\"endColumn\": 31" } }
+         { dg-final { scan-sarif-file "\"message\": " } }
+           { dg-final { scan-sarif-file "\"text\": \"Segmentation fault\"" } } */
diff --git a/gcc/testsuite/gcc.dg/plugin/crash-test-write-though-null-stderr.c b/gcc/testsuite/gcc.dg/plugin/crash-test-write-though-null-stderr.c
new file mode 100644
index 00000000000..7b43e423633
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/crash-test-write-though-null-stderr.c
@@ -0,0 +1,9 @@ 
+/* { dg-do compile } */
+
+extern void inject_write_through_null (void);
+
+void test_inject_write_through_null (void)
+{
+  inject_write_through_null (); /* { dg-ice "Segmentation fault" } */ 
+  /* { dg-regexp "during GIMPLE pass: crash_test" } */
+}
diff --git a/gcc/testsuite/gcc.dg/plugin/crash_test_plugin.c b/gcc/testsuite/gcc.dg/plugin/crash_test_plugin.c
new file mode 100644
index 00000000000..03ad096964b
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/plugin/crash_test_plugin.c
@@ -0,0 +1,135 @@ 
+/* { dg-options "-O" } */
+
+#include "gcc-plugin.h"
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tm.h"
+#include "tree.h"
+#include "stringpool.h"
+#include "toplev.h"
+#include "basic-block.h"
+#include "hash-table.h"
+#include "vec.h"
+#include "ggc.h"
+#include "basic-block.h"
+#include "tree-ssa-alias.h"
+#include "internal-fn.h"
+#include "gimple.h"
+#include "gimple-iterator.h"
+#include "gimple-fold.h"
+#include "tree-eh.h"
+#include "gimple-expr.h"
+#include "is-a.h"
+#include "tree.h"
+#include "tree-pass.h"
+#include "intl.h"
+#include "plugin-version.h"
+#include "c-family/c-common.h"
+#include "diagnostic.h"
+#include "context.h"
+
+int plugin_is_GPL_compatible;
+
+/* A custom pass for injecting a crash in the middle-end when compiling
+   certain functions.  */
+
+const pass_data pass_data_crash_test =
+{
+  GIMPLE_PASS, /* type */
+  "crash_test", /* name */
+  OPTGROUP_NONE, /* optinfo_flags */
+  TV_NONE, /* tv_id */
+  PROP_ssa, /* properties_required */
+  0, /* properties_provided */
+  0, /* properties_destroyed */
+  0, /* todo_flags_start */
+  0, /* todo_flags_finish */
+};
+
+class pass_crash_test : public gimple_opt_pass
+{
+public:
+  pass_crash_test(gcc::context *ctxt)
+    : gimple_opt_pass(pass_data_crash_test, ctxt)
+  {}
+
+  /* opt_pass methods: */
+  bool gate (function *) final override { return true; }
+  unsigned int execute (function *) final override;
+
+}; // class pass_test_groups
+
+/* Determine if STMT is a call to a function named FUNCNAME.
+   If so, return STMT as a gcall *.  Otherwise return NULL.  */
+
+static gcall *
+check_for_named_call (gimple *stmt, const char *funcname)
+{
+  gcc_assert (funcname);
+
+  gcall *call = dyn_cast <gcall *> (stmt);
+  if (!call)
+    return NULL;
+
+  tree fndecl = gimple_call_fndecl (call);
+  if (!fndecl)
+    return NULL;
+
+  if (strcmp (IDENTIFIER_POINTER (DECL_NAME (fndecl)), funcname))
+    return NULL;
+
+  return call;
+}
+
+unsigned int
+pass_crash_test::execute (function *fun)
+{
+  gimple_stmt_iterator gsi;
+  basic_block bb;
+
+  FOR_EACH_BB_FN (bb, fun)
+    for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi))
+      {
+	gimple *stmt = gsi_stmt (gsi);
+	if (gcall *call = check_for_named_call (stmt, "inject_ice"))
+	  {
+	    input_location = stmt->location;
+	    internal_error ("I'm sorry Dave, I'm afraid I can't do that");
+	  }
+	if (gcall *call = check_for_named_call (stmt,
+						"inject_write_through_null"))
+	  {
+	    input_location = stmt->location;
+	    int *p = NULL;
+	    *p = 42;
+	  }
+      }
+
+  return 0;
+}
+
+/* Entrypoint for the plugin.
+   Create and register the custom pass.  */
+
+int
+plugin_init (struct plugin_name_args *plugin_info,
+	     struct plugin_gcc_version *version)
+{
+  struct register_pass_info pass_info;
+  const char *plugin_name = plugin_info->base_name;
+  int argc = plugin_info->argc;
+  struct plugin_argument *argv = plugin_info->argv;
+
+  if (!plugin_default_version_check (version, &gcc_version))
+    return 1;
+
+  pass_info.pass = new pass_crash_test (g);
+  pass_info.reference_pass_name = "*warn_function_noreturn";
+  pass_info.ref_pass_instance_number = 1;
+  pass_info.pos_op = PASS_POS_INSERT_AFTER;
+  register_callback (plugin_name, PLUGIN_PASS_MANAGER_SETUP, NULL,
+		     &pass_info);
+
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/plugin/plugin.exp b/gcc/testsuite/gcc.dg/plugin/plugin.exp
index 7038a7152ac..4d6304cd100 100644
--- a/gcc/testsuite/gcc.dg/plugin/plugin.exp
+++ b/gcc/testsuite/gcc.dg/plugin/plugin.exp
@@ -69,6 +69,11 @@  set plugin_test_list [list \
     { poly-int-05_plugin.c poly-int-test-1.c } \
     { poly-int-06_plugin.c poly-int-test-1.c } \
     { poly-int-07_plugin.c poly-int-test-1.c } \
+    { crash_test_plugin.c \
+	  crash-test-ice-stderr.c \
+	  crash-test-write-though-null-stderr.c \
+	  crash-test-ice-sarif.c \
+	  crash-test-write-though-null-sarif.c } \
     { diagnostic_group_plugin.c \
 	  diagnostic-group-test-1.c } \
     { diagnostic_plugin_test_show_locus.c \