Author: Dave Bartolomeo
Date: 2026-03-06T13:50:10-05:00
New Revision: 3da28bfbce4d7ac8eaea6b8489031d01748d4fc5

URL: 
https://github.com/llvm/llvm-project/commit/3da28bfbce4d7ac8eaea6b8489031d01748d4fc5
DIFF: 
https://github.com/llvm/llvm-project/commit/3da28bfbce4d7ac8eaea6b8489031d01748d4fc5.diff

LOG: [clang][diagnostics] Stable IDs for Clang diagnostics (#168153)

Part of the implementation of [[RFC] Emitting Auditable SARIF Logs from
Clang](https://discourse.llvm.org/t/rfc-emitting-auditable-sarif-logs-from-clang/88624)

SARIF diagnostics require that each rule have a stable `id` property to
identify that rule across runs, even when the compiler or analysis tool
has changed. We were previously setting the `id` property to the numeric
value of the enum value for that diagnostic within the Clang
implementation; this value changes whenever an unrelated diagnostic is
inserted or removed earlier in the list.

This change sets the `id` property to the _text_ of that same enum
value. This value would only change if someone renames the enum value
for that diagnostic, which should happen much less frequently than
renumbering.

For now, we will just assume that renaming happens infrequently enough
that existing consumers of SARIF will not notice. In the future, we
could take advantage of SARIF's support for `deprecatedIds`, which let a
rule specify the IDs by which it was previously known. This would let us
rename, split, or combine diagnostics while still being able to
correlate the new diagnostic IDs with older SARIF logs and/or
suppressions.

Nothing in this change affects how warnings are configured on the
command line or in `#pragma clang diagnostic`. Those still use warning
groups, not the stable IDs.

### Potential discussion topics
>From @AaronBallman on the RFC:
>We believe some open questions remain (things like whether a unique ID
is on the per-diagnostic level or on the diagnostic group level, whether
the ID is explicitly spelled in the .td file or implicitly generated,
whether we document the IDs, etc), but we think those questions are best
decided in PR discussions with interested parties rather than an RFC.

As a starting point, this PR proposes the following answers to those
open questions:
- _whether a unique ID is on the per-diagnostic level or on the
diagnostic group level_ - per-diagnostic level. For my justification,
see [this portion of the RFC
discussion](https://discourse.llvm.org/t/rfc-emitting-auditable-sarif-logs-from-clang/88624/11?u=dbartol.).
- _whether the ID is explicitly spelled in the .td file or implicitly
generated_ - Implicitly generated, but I'd be happy to have a way to
explicitly specify it. I just think that the in-code identifier is a
reasonable default, and manually reviewing the IDs of thousands of
existing diagnostics would add little benefit.
- _whether we document the IDs_ - For now, the IDs are only exposed to
the user (and other tools) in the SARIF file, so I don't think we need
to document these. We could certainly add this information to the output
of `diagtool` in the future if users find it relevant.

Added: 
    clang/test/Frontend/Inputs/expected-sarif/sarif-diagnostics.cpp.sarif
    clang/test/Frontend/Inputs/expected-sarif/sarif-legacy-stable-ids.c.sarif
    clang/test/Frontend/sarif-legacy-stable-ids.c

Modified: 
    clang-tools-extra/clangd/Diagnostics.cpp
    clang/include/clang/Basic/CMakeLists.txt
    clang/include/clang/Basic/Diagnostic.td
    clang/include/clang/Basic/DiagnosticIDs.h
    clang/include/clang/Basic/DiagnosticSemaKinds.td
    clang/include/clang/Basic/Sarif.h
    clang/lib/Basic/DiagnosticIDs.cpp
    clang/lib/Basic/Sarif.cpp
    clang/lib/Frontend/SARIFDiagnostic.cpp
    clang/lib/Frontend/SARIFDiagnosticPrinter.cpp
    clang/test/Analysis/lit.local.cfg
    clang/test/Frontend/sarif-diagnostics.cpp
    clang/test/TableGen/DiagnosticBase.inc
    clang/test/TableGen/deferred-diag.td
    clang/test/lit.cfg.py
    clang/tools/diagtool/DiagnosticNames.cpp
    clang/utils/TableGen/ClangDiagnosticsEmitter.cpp
    clang/utils/TableGen/TableGen.cpp
    clang/utils/TableGen/TableGenBackends.h

Removed: 
    


################################################################################
diff  --git a/clang-tools-extra/clangd/Diagnostics.cpp 
b/clang-tools-extra/clangd/Diagnostics.cpp
index e10960ca5850f..47c3fab034587 100644
--- a/clang-tools-extra/clangd/Diagnostics.cpp
+++ b/clang-tools-extra/clangd/Diagnostics.cpp
@@ -53,7 +53,8 @@ namespace {
 const char *getDiagnosticCode(unsigned ID) {
   switch (ID) {
 #define DIAG(ENUM, CLASS, DEFAULT_MAPPING, DESC, GROPU, SFINAE, NOWERROR,      
\
-             SHOWINSYSHEADER, SHOWINSYSMACRO, DEFERRABLE, CATEGORY)            
\
+             SHOWINSYSHEADER, SHOWINSYSMACRO, DEFERRABLE, CATEGORY, STABLE_ID, 
\
+             LEGACY_STABLE_IDS)                                                
\
   case clang::diag::ENUM:                                                      
\
     return #ENUM;
 #include "clang/Basic/DiagnosticASTKinds.inc"

diff  --git a/clang/include/clang/Basic/CMakeLists.txt 
b/clang/include/clang/Basic/CMakeLists.txt
index 2412b021eb80a..fefddf997fe83 100644
--- a/clang/include/clang/Basic/CMakeLists.txt
+++ b/clang/include/clang/Basic/CMakeLists.txt
@@ -42,6 +42,10 @@ clang_tablegen(DiagnosticIndexName.inc 
-gen-clang-diags-index-name
   SOURCE Diagnostic.td
   TARGET ClangDiagnosticIndexName)
 
+clang_tablegen(DiagnosticStableIDs.inc -gen-clang-diags-stable-ids
+  SOURCE Diagnostic.td
+  TARGET ClangDiagnosticStableIDs)
+
 clang_tablegen(DiagnosticAllCompatIDs.inc
   -gen-clang-diags-compat-ids
   SOURCE Diagnostic.td

diff  --git a/clang/include/clang/Basic/Diagnostic.td 
b/clang/include/clang/Basic/Diagnostic.td
index 53b1db265ccd0..d56fb22b2f539 100644
--- a/clang/include/clang/Basic/Diagnostic.td
+++ b/clang/include/clang/Basic/Diagnostic.td
@@ -65,6 +65,23 @@ class DiagGroup<string Name, list<DiagGroup> subgroups = [], 
code docs = [{}]> {
 class InGroup<DiagGroup G> { DiagGroup Group = G; }
 //class IsGroup<string Name> { DiagGroup Group = DiagGroup<Name>; }
 
+// Specifies an explicit Stable ID for a diagnostic, rather than the default 
one
+// generated from the diagnostic's name.
+class StableId<string Id> { string StableId = Id; }
+
+// Specifies a list of Stable IDs by which the diagnostic was previously known.
+// If the Stable ID of a diagnostic is renamed, the previous Stable ID should 
be
+// added to the LegacyStableIds list.
+//
+// If a diagnostic is split into two or more diagnostics for specific cases, 
the
+// Stable ID of the original diagnostic should be added to the LegacyStableIds
+// list of each of the new diagnostics created from it.
+//
+// If two or more diagnostics are merged into a single diagnostic, the Stable 
ID
+// of each of the original diagnostics should be added to the LegacyStableIds
+// list of the new (merged) diagnostic.
+class LegacyStableIds<list<string> Ids> { list<string> LegacyStableIds = Ids; }
+
 // This defines documentation for diagnostic groups.
 include "DiagnosticDocs.td"
 
@@ -90,6 +107,10 @@ class Diagnostic<string summary, DiagClass DC, Severity 
defaultmapping> {
   Severity       DefaultSeverity = defaultmapping;
   DiagGroup      Group;
   string         CategoryName = "";
+  /// The default of "" means that the actual Stable ID should be generated 
from
+  /// the name of the diagnostic.
+  string         StableId = "";
+  list<string>   LegacyStableIds = [];
 }
 
 class SFINAEFailure {

diff  --git a/clang/include/clang/Basic/DiagnosticIDs.h 
b/clang/include/clang/Basic/DiagnosticIDs.h
index 06446cf580389..09e2d12dd040e 100644
--- a/clang/include/clang/Basic/DiagnosticIDs.h
+++ b/clang/include/clang/Basic/DiagnosticIDs.h
@@ -332,6 +332,12 @@ class DiagnosticIDs : public RefCountedBase<DiagnosticIDs> 
{
   /// Given a diagnostic ID, return a description of the issue.
   StringRef getDescription(unsigned DiagID) const;
 
+  /// Given a diagnostic ID, return the stable ID of the diagnostic.
+  std::string getStableID(unsigned DiagID) const;
+
+  /// Given a diagnostic ID, return the previous stable IDs of the diagnostic.
+  llvm::SmallVector<StringRef, 4> getLegacyStableIDs(unsigned DiagID) const;
+
   /// Return true if the unmapped diagnostic levelof the specified
   /// diagnostic ID is a Warning or Extension.
   ///

diff  --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td 
b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 99ab83f5e0218..0e6b3f51a5231 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -415,11 +415,12 @@ def warn_arm_interrupt_save_fp_without_vfp_unit : Warning<
    InGroup<DiagGroup<"arm-interrupt-save-fp-no-vfp-unit">>;
 def err_arm_interrupt_called : Error<
   "interrupt service routine cannot be called directly">;
-def warn_interrupt_signal_attribute_invalid : Warning<
-  "%select{MIPS|MSP430|RISC-V|AVR}0 '%select{interrupt|signal}1' "
-  "attribute only applies to functions that have "
-  "%select{no parameters|a 'void' return type}2">,
-  InGroup<IgnoredAttributes>;
+def warn_interrupt_signal_attribute_invalid
+    : Warning<"%select{MIPS|MSP430|RISC-V|AVR}0 '%select{interrupt|signal}1' "
+              "attribute only applies to functions that have "
+              "%select{no parameters|a 'void' return type}2">,
+      InGroup<IgnoredAttributes>,
+      LegacyStableIds<["warn_interrupt_attribute_invalid"]>;
 def warn_riscv_repeated_interrupt_attribute : Warning<
   "repeated RISC-V 'interrupt' attribute">, InGroup<IgnoredAttributes>;
 def note_riscv_repeated_interrupt_attribute : Note<

diff  --git a/clang/include/clang/Basic/Sarif.h 
b/clang/include/clang/Basic/Sarif.h
index 6f82d253876d9..7651d2ac7a768 100644
--- a/clang/include/clang/Basic/Sarif.h
+++ b/clang/include/clang/Basic/Sarif.h
@@ -261,6 +261,7 @@ class SarifRule {
   std::string Id;
   std::string Description;
   std::string HelpURI;
+  std::vector<std::string> DeprecatedIds;
   SarifReportingConfiguration DefaultConfiguration;
 
   SarifRule() : DefaultConfiguration(SarifReportingConfiguration::create()) {}
@@ -288,6 +289,12 @@ class SarifRule {
     return *this;
   }
 
+  SarifRule
+  setDeprecatedIds(llvm::ArrayRef<llvm::StringRef> RuleDeprecatedIds) {
+    DeprecatedIds.assign(RuleDeprecatedIds.begin(), RuleDeprecatedIds.end());
+    return *this;
+  }
+
   SarifRule
   setDefaultConfiguration(const SarifReportingConfiguration &Configuration) {
     DefaultConfiguration = Configuration;

diff  --git a/clang/lib/Basic/DiagnosticIDs.cpp 
b/clang/lib/Basic/DiagnosticIDs.cpp
index fcd2d9f34414e..dd0ba2a8b68cb 100644
--- a/clang/lib/Basic/DiagnosticIDs.cpp
+++ b/clang/lib/Basic/DiagnosticIDs.cpp
@@ -32,12 +32,17 @@ namespace {
 
 struct StaticDiagInfoRec;
 
+#define GET_DIAG_STABLE_ID_ARRAYS
+#include "clang/Basic/DiagnosticStableIDs.inc"
+#undef GET_DIAG_STABLE_ID_ARRAYS
+
 // Store the descriptions in a separate table to avoid pointers that need to
 // be relocated, and also decrease the amount of data needed on 64-bit
 // platforms. See "How To Write Shared Libraries" by Ulrich Drepper.
 struct StaticDiagInfoDescriptionStringTable {
 #define DIAG(ENUM, CLASS, DEFAULT_SEVERITY, DESC, GROUP, SFINAE, NOWERROR,     
\
-             SHOWINSYSHEADER, SHOWINSYSMACRO, DEFERRABLE, CATEGORY)            
\
+             SHOWINSYSHEADER, SHOWINSYSMACRO, DEFERRABLE, CATEGORY, STABLE_ID, 
\
+             LEGACY_STABLE_IDS)                                                
\
   char ENUM##_desc[sizeof(DESC)];
 #include "clang/Basic/AllDiagnosticKinds.inc"
 #undef DIAG
@@ -45,7 +50,8 @@ struct StaticDiagInfoDescriptionStringTable {
 
 const StaticDiagInfoDescriptionStringTable StaticDiagInfoDescriptions = {
 #define DIAG(ENUM, CLASS, DEFAULT_SEVERITY, DESC, GROUP, SFINAE, NOWERROR,     
\
-             SHOWINSYSHEADER, SHOWINSYSMACRO, DEFERRABLE, CATEGORY)            
\
+             SHOWINSYSHEADER, SHOWINSYSMACRO, DEFERRABLE, CATEGORY, STABLE_ID, 
\
+             LEGACY_STABLE_IDS)                                                
\
   DESC,
 #include "clang/Basic/AllDiagnosticKinds.inc"
 #undef DIAG
@@ -57,12 +63,31 @@ extern const StaticDiagInfoRec StaticDiagInfo[];
 // StaticDiagInfoRec would have extra padding on 64-bit platforms.
 const uint32_t StaticDiagInfoDescriptionOffsets[] = {
 #define DIAG(ENUM, CLASS, DEFAULT_SEVERITY, DESC, GROUP, SFINAE, NOWERROR,     
\
-             SHOWINSYSHEADER, SHOWINSYSMACRO, DEFERRABLE, CATEGORY)            
\
+             SHOWINSYSHEADER, SHOWINSYSMACRO, DEFERRABLE, CATEGORY, STABLE_ID, 
\
+             LEGACY_STABLE_IDS)                                                
\
   offsetof(StaticDiagInfoDescriptionStringTable, ENUM##_desc),
 #include "clang/Basic/AllDiagnosticKinds.inc"
 #undef DIAG
 };
 
+const uint32_t StaticDiagInfoStableIDOffsets[] = {
+#define DIAG(ENUM, CLASS, DEFAULT_SEVERITY, DESC, GROUP, SFINAE, NOWERROR,     
\
+             SHOWINSYSHEADER, SHOWINSYSMACRO, DEFERRABLE, CATEGORY, STABLE_ID, 
\
+             LEGACY_STABLE_IDS)                                                
\
+  STABLE_ID,
+#include "clang/Basic/AllDiagnosticKinds.inc"
+#undef DIAG
+};
+
+const uint32_t StaticDiagInfoLegacyStableIDStartOffsets[] = {
+#define DIAG(ENUM, CLASS, DEFAULT_SEVERITY, DESC, GROUP, SFINAE, NOWERROR,     
\
+             SHOWINSYSHEADER, SHOWINSYSMACRO, DEFERRABLE, CATEGORY, STABLE_ID, 
\
+             LEGACY_STABLE_IDS)                                                
\
+  LEGACY_STABLE_IDS,
+#include "clang/Basic/AllDiagnosticKinds.inc"
+#undef DIAG
+};
+
 enum DiagnosticClass {
   CLASS_NOTE = DiagnosticIDs::CLASS_NOTE,
   CLASS_REMARK = DiagnosticIDs::CLASS_REMARK,
@@ -107,6 +132,24 @@ struct StaticDiagInfoRec {
     return StringRef(&Table[StringOffset], DescriptionLen);
   }
 
+  StringRef getStableID() const {
+    size_t MyIndex = this - &StaticDiagInfo[0];
+    uint32_t StringOffset = StaticDiagInfoStableIDOffsets[MyIndex];
+    return DiagStableIDs[StringOffset];
+  }
+
+  llvm::SmallVector<StringRef, 4> getLegacyStableIDs() const {
+    llvm::SmallVector<StringRef, 4> Result;
+    size_t MyIndex = this - &StaticDiagInfo[0];
+    uint32_t StartOffset = StaticDiagInfoLegacyStableIDStartOffsets[MyIndex];
+    for (uint32_t Offset = StartOffset; DiagLegacyStableIDs[Offset] != 0;
+         ++Offset) {
+      Result.push_back(DiagStableIDs[DiagLegacyStableIDs[Offset]]);
+    }
+
+    return Result;
+  }
+
   diag::Flavor getFlavor() const {
     return Class == CLASS_REMARK ? diag::Flavor::Remark
                                  : diag::Flavor::WarningOrError;
@@ -147,7 +190,8 @@ VALIDATE_DIAG_SIZE(TRAP)
 const StaticDiagInfoRec StaticDiagInfo[] = {
 // clang-format off
 #define DIAG(ENUM, CLASS, DEFAULT_SEVERITY, DESC, GROUP, SFINAE, NOWERROR,     
\
-             SHOWINSYSHEADER, SHOWINSYSMACRO, DEFERRABLE, CATEGORY)            
\
+             SHOWINSYSHEADER, SHOWINSYSMACRO, DEFERRABLE, CATEGORY, STABLE_ID, 
\
+             LEGACY_STABLE_IDS)                                                
\
   {                                                                            
\
       diag::ENUM,                                                              
\
       DEFAULT_SEVERITY,                                                        
\
@@ -434,6 +478,30 @@ StringRef DiagnosticIDs::getDescription(unsigned DiagID) 
const {
   return CustomDiagInfo->getDescription(DiagID).GetDescription();
 }
 
+/// getStableID - Given a diagnostic ID, return the stable ID of the 
diagnostic.
+std::string DiagnosticIDs::getStableID(unsigned DiagID) const {
+  if (const StaticDiagInfoRec *Info = GetDiagInfo(DiagID))
+    return Info->getStableID().str();
+  assert(CustomDiagInfo && "Invalid CustomDiagInfo");
+  // TODO: Stable IDs for custom diagnostics?
+  // If we have to go through every custom diagnostic and add a stable ID, we
+  // should instead just go replace them all with declared diagnostics.
+  return std::to_string(DiagID);
+}
+
+/// getLegacyStableIDs - Given a diagnostic ID, return the previous stable IDs
+/// of the diagnostic.
+SmallVector<StringRef, 4>
+DiagnosticIDs::getLegacyStableIDs(unsigned DiagID) const {
+  if (const StaticDiagInfoRec *Info = GetDiagInfo(DiagID))
+    return Info->getLegacyStableIDs();
+  assert(CustomDiagInfo && "Invalid CustomDiagInfo");
+  // TODO: Stable IDs for custom diagnostics?
+  // If we have to go through every custom diagnostic and add a stable ID, we
+  // should instead just go replace them all with declared diagnostics.
+  return {};
+}
+
 static DiagnosticIDs::Level toLevel(diag::Severity SV) {
   switch (SV) {
   case diag::Severity::Ignored:

diff  --git a/clang/lib/Basic/Sarif.cpp b/clang/lib/Basic/Sarif.cpp
index 448de96d474af..e829b9bb47ad9 100644
--- a/clang/lib/Basic/Sarif.cpp
+++ b/clang/lib/Basic/Sarif.cpp
@@ -283,6 +283,9 @@ void SarifDocumentWriter::endRun() {
         {"defaultConfiguration", std::move(Config)}};
     if (!R.HelpURI.empty())
       Rule["helpUri"] = R.HelpURI;
+    if (!R.DeprecatedIds.empty())
+      Rule["deprecatedIds"] = json::Array(R.DeprecatedIds);
+
     Rules.emplace_back(std::move(Rule));
   }
   json::Object &Driver = *Tool.getObject("driver");

diff  --git a/clang/lib/Frontend/SARIFDiagnostic.cpp 
b/clang/lib/Frontend/SARIFDiagnostic.cpp
index 0179307bcbac3..04ee14edfe74c 100644
--- a/clang/lib/Frontend/SARIFDiagnostic.cpp
+++ b/clang/lib/Frontend/SARIFDiagnostic.cpp
@@ -46,7 +46,11 @@ void SARIFDiagnostic::emitDiagnosticMessage(
   if (!Diag)
     return;
 
-  SarifRule Rule = 
SarifRule::create().setRuleId(std::to_string(Diag->getID()));
+  const auto &DiagnosticIDs = *(Diag->getDiags()->getDiagnosticIDs());
+  std::string StableID = DiagnosticIDs.getStableID(Diag->getID());
+  auto LegacyStableIDs = DiagnosticIDs.getLegacyStableIDs(Diag->getID());
+  SarifRule Rule =
+      
SarifRule::create().setRuleId(StableID).setDeprecatedIds(LegacyStableIDs);
 
   Rule = addDiagnosticLevelToRule(Rule, Level);
 

diff  --git a/clang/lib/Frontend/SARIFDiagnosticPrinter.cpp 
b/clang/lib/Frontend/SARIFDiagnosticPrinter.cpp
index 988159693389b..72b796f8db798 100644
--- a/clang/lib/Frontend/SARIFDiagnosticPrinter.cpp
+++ b/clang/lib/Frontend/SARIFDiagnosticPrinter.cpp
@@ -40,7 +40,7 @@ void SARIFDiagnosticPrinter::EndSourceFile() {
   assert(SARIFDiag && "SARIFDiagnostic has not been set.");
   Writer->endRun();
   llvm::json::Value Value(Writer->createDocument());
-  OS << "\n" << Value << "\n\n";
+  OS << llvm::formatv("\n{0:2}\n\n", Value);
   OS.flush();
   SARIFDiag.reset();
 }

diff  --git a/clang/test/Analysis/lit.local.cfg 
b/clang/test/Analysis/lit.local.cfg
index 03ab418a5a4f7..3bc2f94809c85 100644
--- a/clang/test/Analysis/lit.local.cfg
+++ b/clang/test/Analysis/lit.local.cfg
@@ -17,24 +17,7 @@ config.substitutions.append(
     )
 )
 
-sed_cmd = "/opt/freeware/bin/sed" if "system-aix" in config.available_features 
else "sed"
-
-# Filtering command for testing SARIF output against reference output.
-config.substitutions.append(
-    (
-        "%normalize_sarif",
-        f"{sed_cmd} -r '%s;%s;%s;%s'"
-        % (
-            # Replace version strings that are likely to change.
-            r's/"version": ".* version .*"/"version": "[clang version]"/',
-            r's/"version": "2.1.0"/"version": "[SARIF version]"/',
-            # Strip directories from file URIs
-            r's/"file:(\/+)([^"\/]+\/)*([^"]+)"/"file:\1[...]\/\3"/',
-            # Set "length" to -1
-            r's/"length": [[:digit:]]+/"length": -1/'
-        ),
-    )
-)
+# SARIF filtering is now shared with the rest of Clang in ../lit.cfg.py
 
 if not config.root.clang_staticanalyzer:
     config.unsupported = True

diff  --git 
a/clang/test/Frontend/Inputs/expected-sarif/sarif-diagnostics.cpp.sarif 
b/clang/test/Frontend/Inputs/expected-sarif/sarif-diagnostics.cpp.sarif
new file mode 100644
index 0000000000000..21f06bf0a1b23
--- /dev/null
+++ b/clang/test/Frontend/Inputs/expected-sarif/sarif-diagnostics.cpp.sarif
@@ -0,0 +1,424 @@
+clang: warning: diagnostic formatting in SARIF mode is currently unstable 
[-Wsarif-format-unstable]
+
+{
+  "$schema": 
"https://docs.oasis-open.org/sarif/sarif/v2.1.0/cos02/schemas/sarif-schema-2.1.0.json";,
+  "runs": [
+    {
+      "artifacts": [
+        {
+          "length": -1,
+          "location": {
+            "index": 0,
+            "uri": "file:///[...]/sarif-diagnostics.cpp"
+          },
+          "mimeType": "text/plain",
+          "roles": [
+            "resultFile"
+          ]
+        }
+      ],
+      "columnKind": "unicodeCodePoints",
+      "results": [
+        {
+          "level": "error",
+          "locations": [
+            {
+              "physicalLocation": {
+                "artifactLocation": {
+                  "index": 0,
+                  "uri": "file:///[...]/sarif-diagnostics.cpp"
+                },
+                "region": {
+                  "endColumn": 1,
+                  "startColumn": 1,
+                  "startLine": 12
+                }
+              }
+            }
+          ],
+          "message": {
+            "text": "'main' must return 'int'"
+          },
+          "ruleId": "err_main_returns_nonint",
+          "ruleIndex": 0
+        },
+        {
+          "level": "error",
+          "locations": [
+            {
+              "physicalLocation": {
+                "artifactLocation": {
+                  "index": 0,
+                  "uri": "file:///[...]/sarif-diagnostics.cpp"
+                },
+                "region": {
+                  "endColumn": 16,
+                  "endLine": 13,
+                  "startColumn": 11,
+                  "startLine": 13
+                }
+              }
+            },
+            {
+              "physicalLocation": {
+                "artifactLocation": {
+                  "index": 0,
+                  "uri": "file:///[...]/sarif-diagnostics.cpp"
+                },
+                "region": {
+                  "endColumn": 11,
+                  "startColumn": 11,
+                  "startLine": 13
+                }
+              }
+            }
+          ],
+          "message": {
+            "text": "use of undeclared identifier 'hello'"
+          },
+          "ruleId": "err_undeclared_var_use",
+          "ruleIndex": 1
+        },
+        {
+          "level": "error",
+          "locations": [
+            {
+              "physicalLocation": {
+                "artifactLocation": {
+                  "index": 0,
+                  "uri": "file:///[...]/sarif-diagnostics.cpp"
+                },
+                "region": {
+                  "endColumn": 17,
+                  "startColumn": 17,
+                  "startLine": 15
+                }
+              }
+            }
+          ],
+          "message": {
+            "text": "invalid digit 'a' in decimal constant"
+          },
+          "ruleId": "err_invalid_digit",
+          "ruleIndex": 2
+        },
+        {
+          "level": "warning",
+          "locations": [
+            {
+              "physicalLocation": {
+                "artifactLocation": {
+                  "index": 0,
+                  "uri": "file:///[...]/sarif-diagnostics.cpp"
+                },
+                "region": {
+                  "endColumn": 5,
+                  "startColumn": 5,
+                  "startLine": 19
+                }
+              }
+            }
+          ],
+          "message": {
+            "text": "misleading indentation; statement is not part of the 
previous 'if'"
+          },
+          "ruleId": "warn_misleading_indentation",
+          "ruleIndex": 3
+        },
+        {
+          "level": "note",
+          "locations": [
+            {
+              "physicalLocation": {
+                "artifactLocation": {
+                  "index": 0,
+                  "uri": "file:///[...]/sarif-diagnostics.cpp"
+                },
+                "region": {
+                  "endColumn": 3,
+                  "startColumn": 3,
+                  "startLine": 17
+                }
+              }
+            }
+          ],
+          "message": {
+            "text": "previous statement is here"
+          },
+          "ruleId": "note_previous_statement",
+          "ruleIndex": 4
+        },
+        {
+          "level": "warning",
+          "locations": [
+            {
+              "physicalLocation": {
+                "artifactLocation": {
+                  "index": 0,
+                  "uri": "file:///[...]/sarif-diagnostics.cpp"
+                },
+                "region": {
+                  "endColumn": 13,
+                  "endLine": 18,
+                  "startColumn": 10,
+                  "startLine": 18
+                }
+              }
+            },
+            {
+              "physicalLocation": {
+                "artifactLocation": {
+                  "index": 0,
+                  "uri": "file:///[...]/sarif-diagnostics.cpp"
+                },
+                "region": {
+                  "endColumn": 10,
+                  "startColumn": 10,
+                  "startLine": 18
+                }
+              }
+            }
+          ],
+          "message": {
+            "text": "unused variable 'Yes'"
+          },
+          "ruleId": "warn_unused_variable",
+          "ruleIndex": 5
+        },
+        {
+          "level": "error",
+          "locations": [
+            {
+              "physicalLocation": {
+                "artifactLocation": {
+                  "index": 0,
+                  "uri": "file:///[...]/sarif-diagnostics.cpp"
+                },
+                "region": {
+                  "endColumn": 14,
+                  "endLine": 21,
+                  "startColumn": 12,
+                  "startLine": 21
+                }
+              }
+            },
+            {
+              "physicalLocation": {
+                "artifactLocation": {
+                  "index": 0,
+                  "uri": "file:///[...]/sarif-diagnostics.cpp"
+                },
+                "region": {
+                  "endColumn": 12,
+                  "startColumn": 12,
+                  "startLine": 21
+                }
+              }
+            }
+          ],
+          "message": {
+            "text": "use of undeclared identifier 'hi'"
+          },
+          "ruleId": "err_undeclared_var_use",
+          "ruleIndex": 6
+        },
+        {
+          "level": "error",
+          "locations": [
+            {
+              "physicalLocation": {
+                "artifactLocation": {
+                  "index": 0,
+                  "uri": "file:///[...]/sarif-diagnostics.cpp"
+                },
+                "region": {
+                  "endColumn": 1,
+                  "startColumn": 1,
+                  "startLine": 23
+                }
+              }
+            }
+          ],
+          "message": {
+            "text": "extraneous closing brace ('}')"
+          },
+          "ruleId": "err_extraneous_closing_brace",
+          "ruleIndex": 7
+        },
+        {
+          "level": "error",
+          "locations": [
+            {
+              "physicalLocation": {
+                "artifactLocation": {
+                  "index": 0,
+                  "uri": "file:///[...]/sarif-diagnostics.cpp"
+                },
+                "region": {
+                  "endColumn": 6,
+                  "endLine": 27,
+                  "startColumn": 5,
+                  "startLine": 27
+                }
+              }
+            },
+            {
+              "physicalLocation": {
+                "artifactLocation": {
+                  "index": 0,
+                  "uri": "file:///[...]/sarif-diagnostics.cpp"
+                },
+                "region": {
+                  "endColumn": 10,
+                  "endLine": 27,
+                  "startColumn": 9,
+                  "startLine": 27
+                }
+              }
+            },
+            {
+              "physicalLocation": {
+                "artifactLocation": {
+                  "index": 0,
+                  "uri": "file:///[...]/sarif-diagnostics.cpp"
+                },
+                "region": {
+                  "endColumn": 7,
+                  "startColumn": 7,
+                  "startLine": 27
+                }
+              }
+            }
+          ],
+          "message": {
+            "text": "invalid operands to binary expression ('t1' and 't1')"
+          },
+          "ruleId": "err_typecheck_invalid_operands",
+          "ruleIndex": 8
+        }
+      ],
+      "tool": {
+        "driver": {
+          "fullName": "",
+          "informationUri": "https://clang.llvm.org/docs/UsersManual.html";,
+          "language": "en-US",
+          "name": "clang",
+          "rules": [
+            {
+              "defaultConfiguration": {
+                "enabled": true,
+                "level": "error",
+                "rank": 50
+              },
+              "fullDescription": {
+                "text": ""
+              },
+              "id": "err_main_returns_nonint",
+              "name": ""
+            },
+            {
+              "defaultConfiguration": {
+                "enabled": true,
+                "level": "error",
+                "rank": 50
+              },
+              "fullDescription": {
+                "text": ""
+              },
+              "id": "err_undeclared_var_use",
+              "name": ""
+            },
+            {
+              "defaultConfiguration": {
+                "enabled": true,
+                "level": "error",
+                "rank": 50
+              },
+              "fullDescription": {
+                "text": ""
+              },
+              "id": "err_invalid_digit",
+              "name": ""
+            },
+            {
+              "defaultConfiguration": {
+                "enabled": true,
+                "level": "warning",
+                "rank": -1
+              },
+              "fullDescription": {
+                "text": ""
+              },
+              "id": "warn_misleading_indentation",
+              "name": ""
+            },
+            {
+              "defaultConfiguration": {
+                "enabled": true,
+                "level": "note",
+                "rank": -1
+              },
+              "fullDescription": {
+                "text": ""
+              },
+              "id": "note_previous_statement",
+              "name": ""
+            },
+            {
+              "defaultConfiguration": {
+                "enabled": true,
+                "level": "warning",
+                "rank": -1
+              },
+              "fullDescription": {
+                "text": ""
+              },
+              "id": "warn_unused_variable",
+              "name": ""
+            },
+            {
+              "defaultConfiguration": {
+                "enabled": true,
+                "level": "error",
+                "rank": 50
+              },
+              "fullDescription": {
+                "text": ""
+              },
+              "id": "err_undeclared_var_use",
+              "name": ""
+            },
+            {
+              "defaultConfiguration": {
+                "enabled": true,
+                "level": "error",
+                "rank": 50
+              },
+              "fullDescription": {
+                "text": ""
+              },
+              "id": "err_extraneous_closing_brace",
+              "name": ""
+            },
+            {
+              "defaultConfiguration": {
+                "enabled": true,
+                "level": "error",
+                "rank": 50
+              },
+              "fullDescription": {
+                "text": ""
+              },
+              "id": "err_typecheck_invalid_operands",
+              "name": ""
+            }
+          ],
+          "version": "[clang version]"
+        }
+      }
+    }
+  ],
+  "version": "[SARIF version]"
+}
+
+2 warnings and 6 errors generated.
\ No newline at end of file

diff  --git 
a/clang/test/Frontend/Inputs/expected-sarif/sarif-legacy-stable-ids.c.sarif 
b/clang/test/Frontend/Inputs/expected-sarif/sarif-legacy-stable-ids.c.sarif
new file mode 100644
index 0000000000000..903d141ffea24
--- /dev/null
+++ b/clang/test/Frontend/Inputs/expected-sarif/sarif-legacy-stable-ids.c.sarif
@@ -0,0 +1,76 @@
+
+{
+  "$schema": 
"https://docs.oasis-open.org/sarif/sarif/v2.1.0/cos02/schemas/sarif-schema-2.1.0.json";,
+  "runs": [
+    {
+      "artifacts": [
+        {
+          "length": -1,
+          "location": {
+            "index": 0,
+            "uri": "file:///[...]/sarif-legacy-stable-ids.c"
+          },
+          "mimeType": "text/plain",
+          "roles": [
+            "resultFile"
+          ]
+        }
+      ],
+      "columnKind": "unicodeCodePoints",
+      "results": [
+        {
+          "level": "warning",
+          "locations": [
+            {
+              "physicalLocation": {
+                "artifactLocation": {
+                  "index": 0,
+                  "uri": "file:///[...]/sarif-legacy-stable-ids.c"
+                },
+                "region": {
+                  "endColumn": 32,
+                  "startColumn": 32,
+                  "startLine": 8
+                }
+              }
+            }
+          ],
+          "message": {
+            "text": "AVR 'interrupt' attribute only applies to functions that 
have a 'void' return type"
+          },
+          "ruleId": "warn_interrupt_signal_attribute_invalid",
+          "ruleIndex": 0
+        }
+      ],
+      "tool": {
+        "driver": {
+          "fullName": "",
+          "informationUri": "https://clang.llvm.org/docs/UsersManual.html";,
+          "language": "en-US",
+          "name": "clang",
+          "rules": [
+            {
+              "defaultConfiguration": {
+                "enabled": true,
+                "level": "warning",
+                "rank": -1
+              },
+              "deprecatedIds": [
+                "warn_interrupt_attribute_invalid"
+              ],
+              "fullDescription": {
+                "text": ""
+              },
+              "id": "warn_interrupt_signal_attribute_invalid",
+              "name": ""
+            }
+          ],
+          "version": "[clang version]"
+        }
+      }
+    }
+  ],
+  "version": "[SARIF version]"
+}
+
+1 warning generated.
\ No newline at end of file

diff  --git a/clang/test/Frontend/sarif-diagnostics.cpp 
b/clang/test/Frontend/sarif-diagnostics.cpp
index 767c5802ca13d..04cd19516fc0a 100644
--- a/clang/test/Frontend/sarif-diagnostics.cpp
+++ b/clang/test/Frontend/sarif-diagnostics.cpp
@@ -1,14 +1,14 @@
 // RUN: %clang -fsyntax-only -Wall -Wextra -fdiagnostics-format=sarif %s > %t 
2>&1 || true
-// RUN: FileCheck -dump-input=always %s --input-file=%t
+// RUN: cat %t | %normalize_sarif | 
diff  -U1 -b %S/Inputs/expected-sarif/sarif-diagnostics.cpp.sarif -
 
 // FIXME: this test is incredibly fragile because the `main()` function
-// must be on line 12 in order for the CHECK lines to get the correct line
-// number values.
+// must be on line 12 in order for the line numbers in the SARIF output
+// to match the expected values
 //
 // So these comment lines are being used to ensure the code below happens
 // to work properly for the test coverage, which as you can imagine, is not
-// the best way to structure the test. We really need to introduce a better
-// tool than FileCheck for 
diff 'ing JSON output like SARIF.
+// the best way to structure the test. We should consider having a way to
+// tag line numbers in the test source to match in the SARIF output.
 void main() {
   int i = hello;
 
@@ -26,43 +26,3 @@ struct t1 { };
 void f1(t1 x, t1 y) {
     x + y;
 }
-
-// CHECK: warning: diagnostic formatting in SARIF mode is currently unstable 
[-Wsarif-format-unstable]
-// CHECK: 
{"$schema":"https://docs.oasis-open.org/sarif/sarif/v2.1.0/cos02/schemas/sarif-schema-2.1.0.json","runs":[{"artifacts":[{"length":
-// Omit exact length of this file
-// CHECK: ,"location":{"index":0,"uri":"file://
-// Omit filepath to llvm project directory
-// CHECK: 
test/Frontend/sarif-diagnostics.cpp"},"mimeType":"text/plain","roles":["resultFile"]}],"columnKind":"unicodeCodePoints","results":
-// CHECK: 
[{"level":"error","locations":[{"physicalLocation":{"artifactLocation":{"index":0,"uri":"file://
-// CHECK: 
{"endColumn":1,"startColumn":1,"startLine":12}}}],"message":{"text":"'main' 
must return 'int'"},"ruleId":"{{[0-9]+}}","ruleIndex":0},
-// CHECK: 
{"level":"error","locations":[{"physicalLocation":{"artifactLocation":{"index":0,"uri":"file://
-// CHECK: 
{"endColumn":11,"startColumn":11,"startLine":13}}}],"message":{"text":"use of 
undeclared identifier
-// CHECK: 
'hello'"},"ruleId":"{{[0-9]+}}","ruleIndex":1},{"level":"error","locations":[{"physicalLocation":{"artifactLocation":
-// CHECK: 
{"index":0,"uri":"file://{{.+}}"},"region":{"endColumn":17,"startColumn":17,"startLine":15}}}],"message":{"text":"invalid
 digit 'a' in decimal
-// CHECK: 
constant"},"ruleId":"{{[0-9]+}}","ruleIndex":2},{"level":"warning","locations":[{"physicalLocation":{"artifactLocation":
-// CHECK: 
{"index":0,"uri":"file://{{.+}}"},"region":{"endColumn":5,"startColumn":5,"startLine":19}}}],"message":{"text":"misleading
 indentation; statement is not part
-// CHECK: of the previous 
'if'"},"ruleId":"{{[0-9]+}}","ruleIndex":3},{"level":"note","locations":[{"physicalLocation":{"artifactLocation":
-// CHECK: 
{"index":0,"uri":"file://{{.+}}"},"region":{"endColumn":3,"startColumn":3,"startLine":17}}}],"message":{"text":"previous
 statement is
-// CHECK: 
here"},"ruleId":"{{[0-9]+}}","ruleIndex":4},{"level":"warning","locations":[{"physicalLocation":{"artifactLocation":
-// CHECK: 
{"index":0,"uri":"file://{{.+}}"},"region":{"endColumn":10,"startColumn":10,"startLine":18}}}],"message":{"text":"unused
 variable
-// CHECK: 
'Yes'"},"ruleId":"{{[0-9]+}}","ruleIndex":5},{"level":"error","locations":[{"physicalLocation":{"artifactLocation":{"index":0,"uri":"file://
-// CHECK: 
{"endColumn":12,"startColumn":12,"startLine":21}}}],"message":{"text":"use of 
undeclared identifier
-// CHECK: 
'hi'"},"ruleId":"{{[0-9]+}}","ruleIndex":6},{"level":"error","locations":[{"physicalLocation":{"artifactLocation":{"index":0,"uri":"file://
-// CHECK: 
{"endColumn":1,"startColumn":1,"startLine":23}}}],"message":{"text":"extraneous 
closing brace
-// CHECK: 
('}')"},"ruleId":"{{[0-9]+}}","ruleIndex":7},{"level":"error","locations":[{"physicalLocation":{"artifactLocation":{"index":0,"uri":"file://
-// CHECK: 
{"endColumn":6,"endLine":27,"startColumn":5,"startLine":27}}},{"physicalLocation":{"artifactLocation":{"index":0,"uri":"file://
-// CHECK: 
{"endColumn":10,"endLine":27,"startColumn":9,"startLine":27}}},{"physicalLocation":{"artifactLocation":{"index":0,"uri":"file://
-// CHECK: 
{"endColumn":7,"startColumn":7,"startLine":27}}}],"message":{"text":"invalid 
operands to binary expression ('t1' and
-// CHECK: 
't1')"},"ruleId":"{{[0-9]+}}","ruleIndex":8}],"tool":{"driver":{"fullName":"","informationUri":"https://clang.llvm.org/docs/
-// CHECK: 
UsersManual.html","language":"en-US","name":"clang","rules":[{"defaultConfiguration":
-// CHECK: 
{"enabled":true,"level":"error","rank":50},"fullDescription":{"text":""},"id":"{{[0-9]+}}","name":""},{"defaultConfiguration":
-// CHECK: 
{"enabled":true,"level":"error","rank":50},"fullDescription":{"text":""},"id":"{{[0-9]+}}","name":""},{"defaultConfiguration":
-// CHECK: 
{"enabled":true,"level":"error","rank":50},"fullDescription":{"text":""},"id":"{{[0-9]+}}","name":""},{"defaultConfiguration":
-// CHECK: 
{"enabled":true,"level":"warning","rank":-1},"fullDescription":{"text":""},"id":"{{[0-9]+}}","name":""},{"defaultConfiguration":
-// CHECK: 
{"enabled":true,"level":"note","rank":-1},"fullDescription":{"text":""},"id":"{{[0-9]+}}","name":""},{"defaultConfiguration":
-// CHECK: 
{"enabled":true,"level":"warning","rank":-1},"fullDescription":{"text":""},"id":"{{[0-9]+}}","name":""},{"defaultConfiguration":
-// CHECK: 
{"enabled":true,"level":"error","rank":50},"fullDescription":{"text":""},"id":"{{[0-9]+}}","name":""},{"defaultConfiguration":
-// CHECK: 
{"enabled":true,"level":"error","rank":50},"fullDescription":{"text":""},"id":"{{[0-9]+}}","name":""},{"defaultConfiguration":
-// CHECK: {"enabled":true,"level":"error","rank":50},"fullDescription":
-// CHECK: 
{"text":""},"id":"{{[0-9]+}}","name":""}],"version":"{{[0-9]+\.[0-9]+\.[0-9]+[^"
 ]*}}"}}}],"version":"2.1.0"}
-// CHECK: 2 warnings and 6 errors generated.

diff  --git a/clang/test/Frontend/sarif-legacy-stable-ids.c 
b/clang/test/Frontend/sarif-legacy-stable-ids.c
new file mode 100644
index 0000000000000..ec60458119945
--- /dev/null
+++ b/clang/test/Frontend/sarif-legacy-stable-ids.c
@@ -0,0 +1,8 @@
+// RUN: %clang_cc1 -triple avr-unknown-unknown -fsyntax-only 
-fdiagnostics-format sarif %s > %t 2>&1
+// RUN: cat %t | %normalize_sarif | 
diff  -U1 -b %S/Inputs/expected-sarif/sarif-legacy-stable-ids.c.sarif -
+
+struct a { int b; };
+
+// Warning 'warn_interrupt_signal_attribute_invalid' was previously known as 
'warn_interrupt_attribute_invalid'.
+// In SARIF, it will be referred to by its new ID, with a "deprecatedIds" 
entry specifying the old ID.
+__attribute__((interrupt)) int fooa(void) { return 0; }

diff  --git a/clang/test/TableGen/DiagnosticBase.inc 
b/clang/test/TableGen/DiagnosticBase.inc
index 2fc7bb4266edb..4f9e44e081076 100644
--- a/clang/test/TableGen/DiagnosticBase.inc
+++ b/clang/test/TableGen/DiagnosticBase.inc
@@ -81,6 +81,8 @@ class Diagnostic<string summary, DiagClass DC, Severity 
defaultmapping> {
   Severity       DefaultSeverity = defaultmapping;
   DiagGroup      Group;
   string         CategoryName = "";
+  string         StableId = "";
+  list<string>   LegacyStableIds = [];
 }
 
 class SFINAEFailure {

diff  --git a/clang/test/TableGen/deferred-diag.td 
b/clang/test/TableGen/deferred-diag.td
index d7e8e694c7b3e..53adc22fe94d3 100644
--- a/clang/test/TableGen/deferred-diag.td
+++ b/clang/test/TableGen/deferred-diag.td
@@ -5,23 +5,23 @@ include "DiagnosticBase.inc"
 // Test usage of Deferrable and NonDeferrable in diagnostics.
 
 def test_default : Error<"this error is non-deferrable by default">;
-// CHECK-DAG: DIAG(test_default, {{.*}}SFINAE_SubstitutionFailure, false, 
true, true, false, 0)
+// CHECK-DAG: DIAG(test_default, {{.*}}SFINAE_SubstitutionFailure, false, 
true, true, false, 0, {{[0-9]+}}, 0)
 
 def test_deferrable : Error<"this error is deferrable">, Deferrable;
-// CHECK-DAG: DIAG(test_deferrable, {{.*}} SFINAE_SubstitutionFailure, false, 
true, true, true, 0)
+// CHECK-DAG: DIAG(test_deferrable, {{.*}} SFINAE_SubstitutionFailure, false, 
true, true, true, 0, {{[0-9]+}}, 0)
 
 def test_non_deferrable : Error<"this error is non-deferrable">, NonDeferrable;
-// CHECK-DAG: DIAG(test_non_deferrable, {{.*}} SFINAE_SubstitutionFailure, 
false, true, true, false, 0)
+// CHECK-DAG: DIAG(test_non_deferrable, {{.*}} SFINAE_SubstitutionFailure, 
false, true, true, false, 0, {{[0-9]+}}, 0)
 
 let Deferrable = 1 in {
 
 def test_let : Error<"this error is deferrable by let">;
-// CHECK-DAG: DIAG(test_let, {{.*}} SFINAE_SubstitutionFailure, false, true, 
true, true, 0)
+// CHECK-DAG: DIAG(test_let, {{.*}} SFINAE_SubstitutionFailure, false, true, 
true, true, 0, {{[0-9]+}}, 0)
 
 // Make sure TextSubstitution is allowed in the let Deferrable block.
 def textsub : TextSubstitution<"%select{text1|text2}0">;
 
 def test_let2 : Error<"this error is deferrable by let %sub{textsub}0">;
-// CHECK-DAG: DIAG(test_let2, {{.*}} SFINAE_SubstitutionFailure, false, true, 
true, true, 0)
+// CHECK-DAG: DIAG(test_let2, {{.*}} SFINAE_SubstitutionFailure, false, true, 
true, true, 0, {{[0-9]+}}, 0)
 
 }

diff  --git a/clang/test/lit.cfg.py b/clang/test/lit.cfg.py
index 14fefbf0d5424..314563a3130d6 100644
--- a/clang/test/lit.cfg.py
+++ b/clang/test/lit.cfg.py
@@ -89,7 +89,26 @@
 
 config.substitutions.append(("%PATH%", config.environment["PATH"]))
 
+sed_cmd = (
+    "/opt/freeware/bin/sed" if "system-aix" in config.available_features else 
"sed"
+)
 
+# Filtering command for testing SARIF output against reference output.
+config.substitutions.append(
+    (
+        "%normalize_sarif",
+        f"{sed_cmd} -r '%s;%s;%s;%s'"
+        % (
+            # Replace version strings that are likely to change.
+            r's/"version": "2.1.0"/"version": "[SARIF version]"/',
+            r's/"version": ".*[0-9]+\.[0-9]+\.[0-9]+.*"/"version": "[clang 
version]"/',
+            # Strip directories from file URIs
+            r's/"file:(\/+)([^"\/]+\/)*([^"]+)"/"file:\1[...]\/\3"/',
+            # Set "length" to -1
+            r's/"length": [[:digit:]]+/"length": -1/',
+        ),
+    )
+)
 # For each occurrence of a clang tool name, replace it with the full path to
 # the build directory holding that tool.  We explicitly specify the directories
 # to search to ensure that we get the tools just built and not some random

diff  --git a/clang/tools/diagtool/DiagnosticNames.cpp 
b/clang/tools/diagtool/DiagnosticNames.cpp
index 4ac9825848ef3..1538167022aca 100644
--- a/clang/tools/diagtool/DiagnosticNames.cpp
+++ b/clang/tools/diagtool/DiagnosticNames.cpp
@@ -28,7 +28,8 @@ llvm::ArrayRef<DiagnosticRecord> 
diagtool::getBuiltinDiagnosticsByName() {
 // out of sync easily?
 static const DiagnosticRecord BuiltinDiagnosticsByID[] = {
 #define DIAG(ENUM, CLASS, DEFAULT_MAPPING, DESC, GROUP, SFINAE, NOWERROR,      
\
-             SHOWINSYSHEADER, SHOWINSYSMACRO, DEFER, CATEGORY)                 
\
+             SHOWINSYSHEADER, SHOWINSYSMACRO, DEFER, CATEGORY, STABLE_ID,      
\
+             LEGACY_STABLE_IDS)                                                
\
   {#ENUM, diag::ENUM, STR_SIZE(#ENUM, uint8_t)},
 #include "clang/Basic/AllDiagnosticKinds.inc"
 #undef DIAG

diff  --git a/clang/utils/TableGen/ClangDiagnosticsEmitter.cpp 
b/clang/utils/TableGen/ClangDiagnosticsEmitter.cpp
index f4d5023d1cb92..ba10be060c20a 100644
--- a/clang/utils/TableGen/ClangDiagnosticsEmitter.cpp
+++ b/clang/utils/TableGen/ClangDiagnosticsEmitter.cpp
@@ -1585,7 +1585,8 @@ namespace clang {
 namespace diag {
 enum {
 #define DIAG(ENUM, FLAGS, DEFAULT_MAPPING, DESC, GROUP, SFINAE, NOWERROR,      
\
-             SHOWINSYSHEADER, SHOWINSYSMACRO, DEFERRABLE, CATEGORY)            
\
+             SHOWINSYSHEADER, SHOWINSYSMACRO, DEFERRABLE, CATEGORY, STABLE_ID, 
\
+             LEGACY_STABLE_IDS) \
   ENUM,
 #define %sSTART
 #include "clang/Basic/Diagnostic%sKinds.inc"
@@ -1673,6 +1674,137 @@ void clang::EmitClangDiagsEnums(const RecordKeeper 
&Records, raw_ostream &OS,
   }
 }
 
+//===----------------------------------------------------------------------===//
+// Stable ID Tables generation
+//===----------------------------------------------------------------------===//
+
+namespace {
+
+/// Holds the string table for all Stable IDs, plus the arrays of legacy Stable
+/// IDs for renamed diagnostics.
+class DiagStableIDsMap {
+  StringToOffsetTable StableIDs;
+  std::vector<uint32_t> LegacyStableIDs;
+  llvm::StringMap<uint32_t> LegacyStableIDsStartOffsets;
+
+public:
+  DiagStableIDsMap(const RecordKeeper &Records) {
+    LegacyStableIDs.push_back(0); // Empty array at offset 0
+
+    for (const Record *Diag : Records.getAllDerivedDefinitions("Diagnostic")) {
+      StringRef StableID = getStableID(*Diag);
+      // Memoize the Stable ID
+      StableIDs.GetOrAddStringOffset(StableID);
+
+      auto LegacyIDList = Diag->getValueAsListOfStrings("LegacyStableIds");
+      if (!LegacyIDList.empty()) {
+        // Memoize any Legacy Stable IDs, and list their offsets in an array.
+        size_t StartOffset = LegacyStableIDs.size();
+        LegacyStableIDsStartOffsets.insert(
+            std::make_pair(Diag->getName(), StartOffset));
+        for (const auto LegacyID : LegacyIDList) {
+          unsigned Offset = StableIDs.GetOrAddStringOffset(LegacyID);
+          LegacyStableIDs.push_back(Offset);
+        }
+        LegacyStableIDs.push_back(0); // Terminate the array.
+      }
+    }
+  }
+
+  /// Gets the string table offset of the Stable ID for the specified 
Diagnostic
+  /// record.
+  uint32_t getStableIDOffset(const Record &R) const {
+    return StableIDs.GetStringOffset(getStableID(R)).value();
+  }
+
+  /// Gets the offset in the DiagLegacyStableIDs array of the first element of
+  /// the diagnostic's list of legacy Stable IDs.
+  uint32_t getLegacyStableIDsStartOffset(StringRef Name) const {
+    // `lookup()` will return zero if not found, which is exactly what we want
+    // anyway.
+    return LegacyStableIDsStartOffsets.lookup(Name);
+  }
+
+  /// Emit diagnostic stable ID arrays and related data structures.
+  ///
+  /// This creates the table of stable IDs, plus the array of arrays of old
+  /// stable IDs.
+  ///
+  /// \code
+  ///  #ifdef GET_DIAG_STABLE_ID_ARRAYS
+  ///     static const int32_t DiagOldStableIds[];
+  ///     static constexpr llvm::StringTable DiagStableIds;
+  ///  #endif
+  /// \endcode
+  void emit(raw_ostream &OS) const {
+    OS << "\n#ifdef GET_DIAG_STABLE_ID_ARRAYS\n";
+    emitStableIDs(OS);
+    emitLegacyStableIDs(OS);
+    OS << "#endif // GET_DIAG_STABLE_ID_ARRAYS\n\n";
+  }
+
+private:
+  /// Gets the Stable ID for the specified Diagnostic record.
+  /// The Stable ID can be explicitly specified via the "StableId"
+  /// property. If not specified explicitly, the Stable ID defaults
+  /// to the name of the diagnostic.
+  static StringRef getStableID(const Record &R) {
+    StringRef StableID = R.getValueAsString("StableId");
+    return StableID.empty() ? R.getName() : StableID;
+  }
+
+  /// Emit a list of stable IDs, used by both the "StableId" and
+  /// "LegacyStableIds" properties.
+  ///
+  /// This creates an `llvm::StringTable` of all the stable ids in use.
+  void emitStableIDs(raw_ostream &OS) const {
+    StableIDs.EmitStringTableDef(OS, "DiagStableIDs");
+    OS << "\n";
+  }
+
+  /// Emit the array of legacy stable IDs for diagnostics.
+  ///
+  /// The array of stable IDs contains for each diagnostic a list of its legacy
+  /// stable IDs. The individual lists are separated by '0'. Diagnostics with
+  /// no legacy stable IDs are skipped.
+  ///
+  /// \code
+  ///   static const uint16_t DiagOldStableIds[] = {
+  ///     /* Empty */ 0,
+  ///     /* Diag0 */ 142, 0,
+  ///     /* Diag13 */ 265, 322, 399, 0
+  ///   }
+  /// \endcode
+  ///
+  void emitLegacyStableIDs(raw_ostream &OS) const {
+    OS << "static const uint32_t DiagLegacyStableIDs[] = {\n";
+
+    bool StartOfLine = true;
+    for (auto Offset : LegacyStableIDs) {
+      if (StartOfLine) {
+        OS << "  ";
+        StartOfLine = false;
+      }
+      OS << Offset << ",";
+      if (Offset > 0) {
+        OS << " ";
+      } else {
+        OS << "\n";
+        StartOfLine = true;
+      }
+    }
+    OS << "};\n\n";
+  }
+};
+} // namespace
+
+/// Emit the definitions of the Stable ID and old Stable ID tables.
+void clang::EmitClangDiagsStableIDs(const RecordKeeper &Records,
+                                    raw_ostream &OS) {
+  DiagStableIDsMap StableIDs(Records);
+  StableIDs.emit(OS);
+}
+
 /// ClangDiagsDefsEmitter - The top-level class emits .def files containing
 /// declarations of Clang diagnostics.
 void clang::EmitClangDiagsDefs(const RecordKeeper &Records, raw_ostream &OS,
@@ -1700,6 +1832,7 @@ void clang::EmitClangDiagsDefs(const RecordKeeper 
&Records, raw_ostream &OS,
 
   DiagCategoryIDMap CategoryIDs(Records);
   DiagGroupParentMap DGParentMap(Records);
+  DiagStableIDsMap StableIDs(Records);
 
   // Compute the set of diagnostics that are in -Wpedantic.
   RecordSet DiagsInPedantic;
@@ -1782,6 +1915,16 @@ void clang::EmitClangDiagsDefs(const RecordKeeper 
&Records, raw_ostream &OS,
 
     // Category number.
     OS << ", " << CategoryIDs.getID(getDiagnosticCategory(&R, DGParentMap));
+
+    // Stable ID.
+    uint32_t StableIDOffset = StableIDs.getStableIDOffset(R);
+    OS << ", " << StableIDOffset;
+
+    // Previous Stable IDs.
+    uint32_t LegacyStableIDsStartOffset =
+        StableIDs.getLegacyStableIDsStartOffset(R.getName());
+    OS << ", " << LegacyStableIDsStartOffset;
+
     OS << ")\n";
   }
 }

diff  --git a/clang/utils/TableGen/TableGen.cpp 
b/clang/utils/TableGen/TableGen.cpp
index cc2e7fef79659..bd8573dcb940f 100644
--- a/clang/utils/TableGen/TableGen.cpp
+++ b/clang/utils/TableGen/TableGen.cpp
@@ -55,6 +55,7 @@ enum ActionType {
   GenClangDiagsEnums,
   GenClangDiagGroups,
   GenClangDiagsIndexName,
+  GenClangDiagsStableIDs,
   GenClangDiagsInterface,
   GenClangCommentNodes,
   GenClangDeclNodes,
@@ -202,6 +203,8 @@ cl::opt<ActionType> Action(
                    "Generate Clang diagnostic groups"),
         clEnumValN(GenClangDiagsIndexName, "gen-clang-diags-index-name",
                    "Generate Clang diagnostic name index"),
+        clEnumValN(GenClangDiagsStableIDs, "gen-clang-diags-stable-ids",
+                   "Generate Clang diagnostic stable IDs"),
         clEnumValN(GenClangDiagsInterface, "gen-clang-diags-iface",
                    "Generate Clang diagnostic interface headers"),
         clEnumValN(GenClangBasicReader, "gen-clang-basic-reader",
@@ -460,6 +463,9 @@ bool ClangTableGenMain(raw_ostream &OS, const RecordKeeper 
&Records) {
   case GenClangDiagsIndexName:
     EmitClangDiagsIndexName(Records, OS);
     break;
+  case GenClangDiagsStableIDs:
+    EmitClangDiagsStableIDs(Records, OS);
+    break;
   case GenClangDiagsInterface:
     EmitClangDiagsInterface(OS, ClangComponent);
     break;

diff  --git a/clang/utils/TableGen/TableGenBackends.h 
b/clang/utils/TableGen/TableGenBackends.h
index cea1af0532db7..98dc9f4611917 100644
--- a/clang/utils/TableGen/TableGenBackends.h
+++ b/clang/utils/TableGen/TableGenBackends.h
@@ -109,6 +109,8 @@ void EmitClangDiagGroups(const llvm::RecordKeeper &Records,
                          llvm::raw_ostream &OS);
 void EmitClangDiagsIndexName(const llvm::RecordKeeper &Records,
                              llvm::raw_ostream &OS);
+void EmitClangDiagsStableIDs(const llvm::RecordKeeper &Records,
+                             llvm::raw_ostream &OS);
 void EmitClangDiagsInterface(llvm::raw_ostream &OS,
                              const std::string &Component);
 


        
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to