This adds support to sarif-replay to display fix-it hints
stored in GCC's SARIF output.

Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.
Pushed to trunk as r15-7564-gd0d5204afff226.

gcc/ChangeLog:
        * libsarifreplay.cc (sarif_replayer::handle_result_obj): Call
        handle_fix_object if we see a single-element "fixes" array.
        (sarif_replayer::handle_fix_object): New.
        (sarif_replayer::handle_artifact_change_object): New.

gcc/testsuite/ChangeLog:
        * sarif-replay.dg/2.1.0-valid/3.27.30-fixes-1.sarif: New test.
        * sarif-replay.dg/2.1.0-valid/3.27.30-fixes-2.sarif: New test.
        * sarif-replay.dg/2.1.0-valid/3.27.30-fixes-3.sarif: New test.

Signed-off-by: David Malcolm <dmalc...@redhat.com>
---
 gcc/libsarifreplay.cc                         | 117 +++++++++++++++++-
 .../2.1.0-valid/3.27.30-fixes-1.sarif         |  55 ++++++++
 .../2.1.0-valid/3.27.30-fixes-2.sarif         |  39 ++++++
 .../2.1.0-valid/3.27.30-fixes-3.sarif         |  39 ++++++
 4 files changed, 248 insertions(+), 2 deletions(-)
 create mode 100644 
gcc/testsuite/sarif-replay.dg/2.1.0-valid/3.27.30-fixes-1.sarif
 create mode 100644 
gcc/testsuite/sarif-replay.dg/2.1.0-valid/3.27.30-fixes-2.sarif
 create mode 100644 
gcc/testsuite/sarif-replay.dg/2.1.0-valid/3.27.30-fixes-3.sarif

diff --git a/gcc/libsarifreplay.cc b/gcc/libsarifreplay.cc
index 21d8e6ce7cf..cc051dcd485 100644
--- a/gcc/libsarifreplay.cc
+++ b/gcc/libsarifreplay.cc
@@ -346,6 +346,16 @@ private:
   lookup_rule_by_id_in_component (const char *rule_id,
                                  const json::object &tool_component_obj);
 
+  // "fix" object (§3.55)
+  enum status
+  handle_fix_object (libgdiagnostics::diagnostic &diag,
+                    const json::object &fix_obj);
+
+  // "artifactChange" object (§3.56)
+  enum status
+  handle_artifact_change_object (libgdiagnostics::diagnostic &diag,
+                                const json::object &change_obj);
+
   /* Support functions.  */
 
   /* Report an error to m_control_mgr about JV violating REF,
@@ -1012,7 +1022,6 @@ should_add_rule_p (const char *rule_id_str, const char 
*url)
    - doesn't yet handle "taxa" property (§3.27.8)
    - handling of "level" property (§3.27.10) doesn't yet support the
      full logic for when "level" is absent.
-   - doesn't yet handle "fixes" property (§3.27.30)
    - doesn't yet support multithreaded flows (§3.36.3)
 */
 
@@ -1200,6 +1209,17 @@ sarif_replayer::handle_result_obj (const json::object 
&result_obj,
        }
     }
 
+  // §3.27.30 "fixes" property
+  const property_spec_ref prop_fixes ("result", "fixes", "3.27.30");
+  if (auto fixes_arr
+      = get_optional_property<json::array> (result_obj, prop_fixes))
+    {
+      // We only support a single fix
+      if (fixes_arr->length () == 1)
+       if (auto fix_obj = require_object (*fixes_arr->get (0), prop_fixes))
+         handle_fix_object (err, *fix_obj);
+    }
+
   err.finish ("%s", text.get ());
 
   // Flush any notes
@@ -1211,7 +1231,6 @@ sarif_replayer::handle_result_obj (const json::object 
&result_obj,
     }
 
   return status::ok;
-
 }
 
 /*  If ITER_SRC starts with a placeholder as per §3.11.5, advance ITER_SRC
@@ -2049,6 +2068,100 @@ lookup_rule_by_id_in_component (const char *rule_id,
   return nullptr;
 }
 
+// "fix" object (§3.55)
+
+enum status
+sarif_replayer::handle_fix_object (libgdiagnostics::diagnostic &diag,
+                                  const json::object &fix_obj)
+{
+  const property_spec_ref changes ("fix", "artifactChanges", "3.55.3");
+  auto changes_arr = get_required_property<json::array> (fix_obj, changes);
+  if (!changes_arr)
+    return status::err_invalid_sarif;
+
+  for (auto element : *changes_arr)
+    {
+      const json::object *change_obj
+       = require_object_for_element (*element, changes);
+      if (!change_obj)
+       return status::err_invalid_sarif;
+      enum status s = handle_artifact_change_object (diag, *change_obj);
+      if (s != status::ok)
+       return s;
+    }
+  return status::ok;
+}
+
+// "artifactChange" object (§3.56)
+
+enum status
+sarif_replayer::
+handle_artifact_change_object (libgdiagnostics::diagnostic &diag,
+                              const json::object &change_obj)
+{
+  const property_spec_ref location
+    ("artifactChange", "artifactLocation", "3.56.2");
+  auto artifact_loc_obj
+    = get_required_property<json::object> (change_obj, location);
+  if (!artifact_loc_obj)
+    return status::err_invalid_sarif;
+
+  libgdiagnostics::file file;
+  enum status s = handle_artifact_location_object (*artifact_loc_obj, file);
+  if (s != status::ok)
+    return s;
+
+  const property_spec_ref replacements
+    ("artifactChange", "replacements", "3.56.3");
+  auto replacements_arr
+    = get_required_property<json::array> (change_obj, replacements);
+  if (!replacements_arr)
+    return status::err_invalid_sarif;
+  for (auto element : *replacements_arr)
+    {
+      // 3.57 replacement object
+      const json::object *replacement_obj
+       = require_object_for_element (*element, replacements);
+      if (!replacement_obj)
+       return status::err_invalid_sarif;
+
+      // 3.57.3 deletedRegion property
+      const property_spec_ref deleted_region
+       ("replacement", "deletedRegion", "3.57.3");
+      auto deleted_region_obj
+       = get_required_property<json::object> (*replacement_obj,
+                                              deleted_region);
+      if (!deleted_region_obj)
+       return status::err_invalid_sarif;
+
+      libgdiagnostics::physical_location phys_loc;
+      enum status s = handle_region_object (*deleted_region_obj,
+                                           file,
+                                           phys_loc);
+      if (s != status::ok)
+       return s;
+
+      // 3.57.4 insertedContent property
+      const property_spec_ref inserted_content
+       ("replacement", "insertedContent", "3.57.4");
+      const char *inserted_text = "";
+      if (auto inserted_content_obj
+           = get_optional_property<json::object> (*replacement_obj,
+                                                  inserted_content))
+       {
+         const property_spec_ref prop_text
+           ("artifactContent", "text", "3.3.2");
+         if (auto text_jstr
+             = get_optional_property<json::string> (*inserted_content_obj,
+                                                    prop_text))
+           inserted_text = text_jstr->get_string ();
+       }
+
+      diag.add_fix_it_hint_replace (phys_loc, inserted_text);
+    }
+  return status::ok;
+}
+
 } // anonymous namespace
 
 /* Error-checking at the API boundary.  */
diff --git a/gcc/testsuite/sarif-replay.dg/2.1.0-valid/3.27.30-fixes-1.sarif 
b/gcc/testsuite/sarif-replay.dg/2.1.0-valid/3.27.30-fixes-1.sarif
new file mode 100644
index 00000000000..c37411af75b
--- /dev/null
+++ b/gcc/testsuite/sarif-replay.dg/2.1.0-valid/3.27.30-fixes-1.sarif
@@ -0,0 +1,55 @@
+/* Example of GCC SARIF output for a replacement fix-it hint.  */
+
+{"$schema": 
"https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json";,
+ "version": "2.1.0",
+ "runs": [{"tool": {"driver": {"name": "GNU C23",
+                               "fullName": "GNU C23 (GCC) version 15.0.1 
20250203 (experimental) (x86_64-pc-linux-gnu)",
+                               "version": "15.0.1 20250203 (experimental)",
+                               "informationUri": "https://gcc.gnu.org/gcc-15/";,
+                               "rules": [{"id": "-Wformat=",
+                                          "helpUri": 
"https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html#index-Wformat"}]}},
+           "invocations": [{"executionSuccessful": true,
+                            "toolExecutionNotifications": []}],
+           "originalUriBaseIds": {"PWD": {"uri": 
"file:///this/path/does/not/exist/"}},
+           "artifacts": [{"location": {"uri": 
"/this/path/does/not/exist/diagnostic-ranges.c",
+                                       "uriBaseId": "PWD"},
+                          "sourceLanguage": "c",
+                          "contents": {"text": "#include <stdio.h>\n\nvoid 
test_mismatching_types (const char *msg)\n{\n  printf(\"hello %i\", 
msg);\n}\n"},
+                          "roles": ["analysisTarget"]}],
+           "results": [{"ruleId": "-Wformat=",
+                        "level": "warning",
+                        "message": {"text": "format '%i' expects argument of 
type 'int', but argument 2 has type 'const char *'"},
+                        "locations": [{"physicalLocation": 
{"artifactLocation": {"uri": "/this/path/does/not/exist/diagnostic-ranges.c",
+                                                                               
  "uriBaseId": "PWD"},
+                                                            "region": 
{"startLine": 5,
+                                                                       
"startColumn": 17,
+                                                                       
"endColumn": 19},
+                                                            "contextRegion": 
{"startLine": 5,
+                                                                              
"snippet": {"text": "  printf(\"hello %i\", msg);\n"}}},
+                                       "logicalLocations": [{"name": 
"test_mismatching_types",
+                                                             
"fullyQualifiedName": "test_mismatching_types",
+                                                             "decoratedName": 
"test_mismatching_types",
+                                                             "kind": 
"function"}],
+                                       "annotations": [{"startLine": 5,
+                                                        "startColumn": 17,
+                                                        "endColumn": 19,
+                                                        "message": {"text": 
"int"}},
+                                                       {"startLine": 5,
+                                                        "startColumn": 22,
+                                                        "endColumn": 25,
+                                                        "message": {"text": 
"const char *"}}]}],
+                        "fixes": [{"artifactChanges": [{"artifactLocation": 
{"uri": "/this/path/does/not/exist/diagnostic-ranges.c",
+                                                                             
"uriBaseId": "PWD"},
+                                                        "replacements": 
[{"deletedRegion": {"startLine": 5,
+                                                                               
             "startColumn": 17,
+                                                                               
             "endColumn": 19},
+                                                                          
"insertedContent": {"text": "%s"}}]}]}]}]}]}
+
+/* { dg-begin-multiline-output "" }
+/this/path/does/not/exist/diagnostic-ranges.c:5:17: warning: format '%i' 
expects argument of type 'int', but argument 2 has type 'const char *' 
[-Wformat=]
+    5 |   printf("hello %i", msg);
+      |                 ^~   ~~~
+      |                 |    |
+      |                 int  const char *
+      |                 %s
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/sarif-replay.dg/2.1.0-valid/3.27.30-fixes-2.sarif 
b/gcc/testsuite/sarif-replay.dg/2.1.0-valid/3.27.30-fixes-2.sarif
new file mode 100644
index 00000000000..0334885447c
--- /dev/null
+++ b/gcc/testsuite/sarif-replay.dg/2.1.0-valid/3.27.30-fixes-2.sarif
@@ -0,0 +1,39 @@
+/* Example of GCC SARIF output for a deletion fix-it hint.  */
+
+{"$schema": 
"https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json";,
+ "version": "2.1.0",
+ "runs": [{"tool": {"driver": {"name": "GNU C++17",
+                               "fullName": "GNU C++17 (GCC) version 15.0.1 
20250203 (experimental) (x86_64-pc-linux-gnu)",
+                               "version": "15.0.1 20250203 (experimental)",
+                               "informationUri": "https://gcc.gnu.org/gcc-15/";,
+                               "rules": []}},
+           "invocations": [{"executionSuccessful": false,
+                            "toolExecutionNotifications": []}],
+           "artifacts": [{"location": {"uri": "t.cc",
+                                       "uriBaseId": "PWD"},
+                          "sourceLanguage": "cplusplus",
+                          "contents": {"text": "unsigned unsigned int i;\n"},
+                          "roles": ["analysisTarget"]}],
+           "results": [{"ruleId": "error",
+                        "level": "error",
+                        "message": {"text": "duplicate 'unsigned'"},
+                        "locations": [{"physicalLocation": 
{"artifactLocation": {"uri": "t.cc",
+                                                                               
  "uriBaseId": "PWD"},
+                                                            "region": 
{"startLine": 1,
+                                                                       
"startColumn": 10,
+                                                                       
"endColumn": 18},
+                                                            "contextRegion": 
{"startLine": 1,
+                                                                              
"snippet": {"text": "unsigned unsigned int i;\n"}}}}],
+                        "fixes": [{"artifactChanges": [{"artifactLocation": 
{"uri": "t.cc",
+                                                                             
"uriBaseId": "PWD"},
+                                                        "replacements": 
[{"deletedRegion": {"startLine": 1,
+                                                                               
             "startColumn": 10,
+                                                                               
             "endColumn": 18},
+                                                                          
"insertedContent": {"text": ""}}]}]}]}]}]}
+
+/* { dg-begin-multiline-output "" }
+t.cc:1:10: error: duplicate 'unsigned'
+    1 | unsigned unsigned int i;
+      |          ^~~~~~~~
+      |          --------
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/sarif-replay.dg/2.1.0-valid/3.27.30-fixes-3.sarif 
b/gcc/testsuite/sarif-replay.dg/2.1.0-valid/3.27.30-fixes-3.sarif
new file mode 100644
index 00000000000..d4e7d37a8e6
--- /dev/null
+++ b/gcc/testsuite/sarif-replay.dg/2.1.0-valid/3.27.30-fixes-3.sarif
@@ -0,0 +1,39 @@
+/* Example of GCC SARIF output for an insertion fix-it hint.  */
+
+{"$schema": 
"https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json";,
+ "version": "2.1.0",
+ "runs": [{"tool": {"driver": {"name": "GNU C23",
+                               "fullName": "GNU C23 (GCC) version 15.0.1 
20250203 (experimental) (x86_64-pc-linux-gnu)",
+                               "version": "15.0.1 20250203 (experimental)",
+                               "informationUri": "https://gcc.gnu.org/gcc-15/";,
+                               "rules": []}},
+           "invocations": [{"executionSuccessful": false,
+                            "toolExecutionNotifications": []}],
+           "artifacts": [{"location": {"uri": "t.c",
+                                       "uriBaseId": "PWD"},
+                          "sourceLanguage": "c",
+                          "contents": {"text": "struct foo {};\n\nfoo 
*ptr;\n"},
+                          "roles": ["analysisTarget"]}],
+           "results": [{"ruleId": "error",
+                        "level": "error",
+                        "message": {"text": "unknown type name 'foo'; use 
'struct' keyword to refer to the type"},
+                        "locations": [{"physicalLocation": 
{"artifactLocation": {"uri": "t.c",
+                                                                               
  "uriBaseId": "PWD"},
+                                                            "region": 
{"startLine": 3,
+                                                                       
"startColumn": 1,
+                                                                       
"endColumn": 4},
+                                                            "contextRegion": 
{"startLine": 3,
+                                                                              
"snippet": {"text": "foo *ptr;\n"}}}}],
+                        "fixes": [{"artifactChanges": [{"artifactLocation": 
{"uri": "t.c",
+                                                                             
"uriBaseId": "PWD"},
+                                                        "replacements": 
[{"deletedRegion": {"startLine": 3,
+                                                                               
             "startColumn": 1,
+                                                                               
             "endColumn": 1},
+                                                                          
"insertedContent": {"text": "struct "}}]}]}]}]}]}
+
+/* { dg-begin-multiline-output "" }
+t.c:3:1: error: unknown type name 'foo'; use 'struct' keyword to refer to the 
type
+    3 | foo *ptr;
+      | ^~~
+      | struct 
+   { dg-end-multiline-output "" } */
-- 
2.26.3

Reply via email to