[clang-tools-extra] 41e3919 - [clang] Introduce diagnostics suppression mappings (#112517)

via cfe-commits cfe-commits at lists.llvm.org
Tue Nov 12 01:53:56 PST 2024


Author: kadir çetinkaya
Date: 2024-11-12T10:53:43+01:00
New Revision: 41e3919ded78d8870f7c95e9181c7f7e29aa3cc4

URL: https://github.com/llvm/llvm-project/commit/41e3919ded78d8870f7c95e9181c7f7e29aa3cc4
DIFF: https://github.com/llvm/llvm-project/commit/41e3919ded78d8870f7c95e9181c7f7e29aa3cc4.diff

LOG: [clang] Introduce diagnostics suppression mappings (#112517)

This implements

https://discourse.llvm.org/t/rfc-add-support-for-controlling-diagnostics-severities-at-file-level-granularity-through-command-line/81292.

Users now can suppress warnings for certain headers by providing a
mapping with globs, a sample file looks like:
```
[unused]
src:*
src:*clang/*=emit
```

This will suppress warnings from `-Wunused` group in all files that
aren't under `clang/` directory. This mapping file can be passed to
clang via `--warning-suppression-mappings=foo.txt`.

At a high level, mapping file is stored in DiagnosticOptions and then
processed with rest of the warning flags when creating a
DiagnosticsEngine. This is a functor that uses SpecialCaseLists
underneath to match against globs coming from the mappings file.

This implies processing warning options now performs IO, relevant
interfaces are updated to take in a VFS, falling back to RealFileSystem
when one is not available.

Added: 
    clang/docs/WarningSuppressionMappings.rst
    clang/test/Misc/Inputs/suppression-mapping.txt
    clang/test/Misc/warning-suppression-mappings-pragmas.cpp
    clang/test/Misc/warning-suppression-mappings.cpp

Modified: 
    clang-tools-extra/clang-tidy/ExpandModularHeadersPPCallbacks.cpp
    clang/docs/ReleaseNotes.rst
    clang/docs/UsersManual.rst
    clang/docs/index.rst
    clang/include/clang/Basic/Diagnostic.h
    clang/include/clang/Basic/DiagnosticDriverKinds.td
    clang/include/clang/Basic/DiagnosticOptions.h
    clang/include/clang/Driver/Options.td
    clang/include/clang/Frontend/CompilerInstance.h
    clang/lib/Basic/Diagnostic.cpp
    clang/lib/Basic/DiagnosticIDs.cpp
    clang/lib/Basic/Warnings.cpp
    clang/lib/Driver/ToolChains/Clang.cpp
    clang/lib/Frontend/ASTUnit.cpp
    clang/lib/Frontend/CompilerInstance.cpp
    clang/lib/Frontend/CompilerInvocation.cpp
    clang/lib/Frontend/PrecompiledPreamble.cpp
    clang/lib/Interpreter/CodeCompletion.cpp
    clang/lib/Serialization/ASTReader.cpp
    clang/tools/driver/cc1gen_reproducer_main.cpp
    clang/tools/driver/driver.cpp
    clang/unittests/Basic/DiagnosticTest.cpp
    clang/unittests/Frontend/CompilerInvocationTest.cpp
    llvm/include/llvm/Support/SpecialCaseList.h

Removed: 
    


################################################################################
diff  --git a/clang-tools-extra/clang-tidy/ExpandModularHeadersPPCallbacks.cpp b/clang-tools-extra/clang-tidy/ExpandModularHeadersPPCallbacks.cpp
index fef086c5a99d86..4c34f9ea122d9e 100644
--- a/clang-tools-extra/clang-tidy/ExpandModularHeadersPPCallbacks.cpp
+++ b/clang-tools-extra/clang-tidy/ExpandModularHeadersPPCallbacks.cpp
@@ -81,7 +81,8 @@ ExpandModularHeadersPPCallbacks::ExpandModularHeadersPPCallbacks(
   Diags.setSourceManager(&Sources);
   // FIXME: Investigate whatever is there better way to initialize DiagEngine
   // or whatever DiagEngine can be shared by multiple preprocessors
-  ProcessWarningOptions(Diags, Compiler.getDiagnosticOpts());
+  ProcessWarningOptions(Diags, Compiler.getDiagnosticOpts(),
+                        Compiler.getVirtualFileSystem());
 
   LangOpts.Modules = false;
 

diff  --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index c8aa053986cc00..7c210c17dda7d4 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -869,6 +869,9 @@ New features
   attribute, the compiler can generate warnings about the use of any language features, or calls to
   other functions, which may block.
 
+- Introduced ``-warning-suppression-mappings`` flag to control diagnostic
+  suppressions per file. See `documentation <https://clang.llvm.org/docs/WarningSuppressionMappings.html>_` for details.
+
 Crash and bug fixes
 ^^^^^^^^^^^^^^^^^^^
 

diff  --git a/clang/docs/UsersManual.rst b/clang/docs/UsersManual.rst
index 4f03388bc87bd0..f484c5aa8ca49c 100644
--- a/clang/docs/UsersManual.rst
+++ b/clang/docs/UsersManual.rst
@@ -151,6 +151,10 @@ Options to Control Error and Warning Messages
   instantiation backtrace for a single warning or error. The default is 10, and
   the limit can be disabled with `-ftemplate-backtrace-limit=0`.
 
+.. option:: --warning-suppression-mappings=foo.txt
+
+   :ref:`Suppress certain diagnostics for certain files. <warning_suppression_mappings>`
+
 .. _cl_diag_formatting:
 
 Formatting of Diagnostics
@@ -1315,6 +1319,34 @@ with its corresponding `Wno-` option.
 Note that when combined with :option:`-w` (which disables all warnings),
 disabling all warnings wins.
 
+.. _warning_suppression_mappings:
+
+Controlling Diagnostics via Suppression Mappings
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Warning suppression mappings enable users to suppress Clang's diagnostics in a
+per-file granular manner. Enabling enforcement of diagnostics in specific parts
+of the project, even if there are violations in some headers.
+
+.. code-block:: console
+
+  $ cat mappings.txt
+  [unused]
+  src:foo/*
+
+  $ clang --warning-suppression-mappings=mapping.txt -Wunused foo/bar.cc
+  # This compilation won't emit any unused findings for sources under foo/
+  # directory. But it'll still complain for all the other sources, e.g:
+  $ cat foo/bar.cc
+  #include "dir/include.h" // Clang flags unused declarations here.
+  #include "foo/include.h" // but unused warnings under this source is omitted.
+  #include "next_to_bar_cc.h" // as are unused warnings from this header file.
+  // Further, unused warnings in the remainder of bar.cc are also omitted.
+
+
+See :doc:`WarningSuppressionMappings` for details about the file format and
+functionality.
+
 Controlling Static Analyzer Diagnostics
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 

diff  --git a/clang/docs/WarningSuppressionMappings.rst b/clang/docs/WarningSuppressionMappings.rst
new file mode 100644
index 00000000000000..54f8697acf6793
--- /dev/null
+++ b/clang/docs/WarningSuppressionMappings.rst
@@ -0,0 +1,97 @@
+============================
+Warning suppression mappings
+============================
+
+.. contents::
+   :local:
+
+Introduction
+============
+
+Warning suppression mappings enable users to suppress Clang's diagnostics in a
+per-file granular manner. Enabling enforcement of diagnostics in specific parts
+of the project, even if there are violations in some headers.
+
+Goal and usage
+==============
+
+Clang allows diagnostics to be configured at a translation-unit granularity.
+If a ``foo.cpp`` is compiled with ``-Wfoo``, all transitively included headers
+also need to be clean. Hence turning on new warnings in large codebases requires
+cleaning up all the existing warnings. This might not be possible when some
+dependencies aren't in the project owner's control or because new violations are
+creeping up quicker than the clean up.
+
+Warning suppression mappings aim to alleviate some of these concerns by making
+diagnostic configuration granularity finer, at a source file level.
+
+To achieve this, user can create a file that lists which :doc:`diagnostic
+groups <DiagnosticsReference>` to suppress in which files or paths, and pass it
+as a command line argument to Clang with the ``--warning-suppression-mappings``
+flag.
+
+Note that this mechanism won't enable any diagnostics on its own. Users should
+still turn on warnings in their compilations with explicit ``-Wfoo`` flags.
+`Controlling diagnostics pragmas
+<https://clang.llvm.org/docs/UsersManual.html#controlling-diagnostics-via-pragmas>`_
+take precedence over suppression mappings. Ensuring code author's explicit
+intent is always preserved.
+
+Example
+=======
+
+.. code-block:: bash
+
+  $ cat my/user/code.cpp
+  #include <foo/bar.h>
+  namespace { void unused_func1(); }
+
+  $ cat foo/bar.h
+  namespace { void unused_func2(); }
+
+  $ cat suppression_mappings.txt
+  # Suppress -Wunused warnings in all files, apart from the ones under `foo/`.
+  [unused]
+  src:*
+  src:*foo/*=emit
+  $ clang -Wunused --warning-suppression-mappings=suppression_mappings.txt my/user/code.cpp
+  # prints warning: unused function 'unused_func2', but no warnings for `unused_func1`.
+
+Format
+======
+
+Warning suppression mappings uses the same format as
+:doc:`SanitizerSpecialCaseList`.
+
+Sections describe which diagnostic group's behaviour to change, e.g.
+``[unused]``. When a diagnostic is matched by multiple sections, the latest
+section takes precedence.
+
+Afterwards in each section, users can have multiple entities that match source
+files based on the globs. These entities look like ``src:*/my/dir/*``.
+Users can also use the ``emit`` category to exclude a subdirectory from
+suppression.
+Source files are matched against these globs either:
+
+- as paths relative to the current working directory
+- as absolute paths.
+
+When a source file matches multiple globs in a section, the longest one takes
+precedence.
+
+.. code-block:: bash
+
+    # Lines starting with # are ignored.
+    # Configure suppression globs for `-Wunused` warnings
+    [unused]
+    # Suppress on all files by default.
+    src:*
+    # But enforce for all the sources under foo/.
+    src:*foo/*=emit
+
+    # unused-function warnings are a subgroup of `-Wunused`. So this section
+    # takes precedence over the previous one for unused-function warnings, but
+    # not for unused-variable warnings.
+    [unused-function]
+    # Only suppress for sources under bar/.
+    src:*bar/*

diff  --git a/clang/docs/index.rst b/clang/docs/index.rst
index 3c473f93e5224a..cc070059eede5d 100644
--- a/clang/docs/index.rst
+++ b/clang/docs/index.rst
@@ -22,6 +22,7 @@ Using Clang as a Compiler
    ClangCommandLineReference
    AttributeReference
    DiagnosticsReference
+   WarningSuppressionMappings
    CrossCompilation
    ClangStaticAnalyzer
    ThreadSafetyAnalysis

diff  --git a/clang/include/clang/Basic/Diagnostic.h b/clang/include/clang/Basic/Diagnostic.h
index 3b1efdb12824c7..072392e8054374 100644
--- a/clang/include/clang/Basic/Diagnostic.h
+++ b/clang/include/clang/Basic/Diagnostic.h
@@ -20,6 +20,7 @@
 #include "clang/Basic/Specifiers.h"
 #include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/FunctionExtras.h"
 #include "llvm/ADT/IntrusiveRefCntPtr.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/StringRef.h"
@@ -40,6 +41,10 @@
 namespace llvm {
 class Error;
 class raw_ostream;
+class MemoryBuffer;
+namespace vfs {
+class FileSystem;
+} // namespace vfs
 } // namespace llvm
 
 namespace clang {
@@ -555,6 +560,10 @@ class DiagnosticsEngine : public RefCountedBase<DiagnosticsEngine> {
   void *ArgToStringCookie = nullptr;
   ArgToStringFnTy ArgToStringFn;
 
+  /// Whether the diagnostic should be suppressed in FilePath.
+  llvm::unique_function<bool(diag::kind, llvm::StringRef /*FilePath*/) const>
+      DiagSuppressionMapping;
+
 public:
   explicit DiagnosticsEngine(IntrusiveRefCntPtr<DiagnosticIDs> Diags,
                              IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts,
@@ -946,6 +955,27 @@ class DiagnosticsEngine : public RefCountedBase<DiagnosticsEngine> {
     return (Level)Diags->getDiagnosticLevel(DiagID, Loc, *this);
   }
 
+  /// Diagnostic suppression mappings can be used to suppress specific
+  /// diagnostics in specific files.
+  /// Mapping file is expected to be a special case list with sections denoting
+  /// diagnostic groups and `src` entries for globs to suppress. `emit` category
+  /// can be used to disable suppression. Longest glob that matches a filepath
+  /// takes precedence. For example:
+  ///   [unused]
+  ///   src:clang/*
+  ///   src:clang/foo/*=emit
+  ///   src:clang/foo/bar/*
+  ///
+  /// Such a mappings file suppress all diagnostics produced by -Wunused in all
+  /// sources under `clang/` directory apart from `clang/foo/`. Diagnostics
+  /// under `clang/foo/bar/` will also be suppressed. Note that the FilePath is
+  /// matched against the globs as-is.
+  /// These take presumed locations into account, and can still be overriden by
+  /// clang-diagnostics pragmas.
+  void setDiagSuppressionMapping(llvm::MemoryBuffer &Input);
+  bool isSuppressedViaMapping(diag::kind DiagId,
+                              llvm::StringRef FilePath) const;
+
   /// Issue the message to the client.
   ///
   /// This actually returns an instance of DiagnosticBuilder which emits the
@@ -1759,7 +1789,7 @@ const char ToggleHighlight = 127;
 /// warning options specified on the command line.
 void ProcessWarningOptions(DiagnosticsEngine &Diags,
                            const DiagnosticOptions &Opts,
-                           bool ReportDiags = true);
+                           llvm::vfs::FileSystem &VFS, bool ReportDiags = true);
 void EscapeStringForDiagnostic(StringRef Str, SmallVectorImpl<char> &OutStr);
 } // namespace clang
 

diff  --git a/clang/include/clang/Basic/DiagnosticDriverKinds.td b/clang/include/clang/Basic/DiagnosticDriverKinds.td
index cdfdaa01fb121d..76fdbdbfb01d94 100644
--- a/clang/include/clang/Basic/DiagnosticDriverKinds.td
+++ b/clang/include/clang/Basic/DiagnosticDriverKinds.td
@@ -834,4 +834,7 @@ def err_drv_triple_version_invalid : Error<
 
 def warn_missing_include_dirs : Warning<
   "no such include directory: '%0'">, InGroup<MissingIncludeDirs>, DefaultIgnore;
+
+def err_drv_malformed_warning_suppression_mapping : Error<
+  "failed to process suppression mapping file '%0': %1">;
 }

diff  --git a/clang/include/clang/Basic/DiagnosticOptions.h b/clang/include/clang/Basic/DiagnosticOptions.h
index 30141c2b8f4475..e2faf3d0df95cb 100644
--- a/clang/include/clang/Basic/DiagnosticOptions.h
+++ b/clang/include/clang/Basic/DiagnosticOptions.h
@@ -108,6 +108,9 @@ class DiagnosticOptions : public RefCountedBase<DiagnosticOptions>{
   /// The file to serialize diagnostics to (non-appending).
   std::string DiagnosticSerializationFile;
 
+  /// Path for the file that defines diagnostic suppression mappings.
+  std::string DiagnosticSuppressionMappingsFile;
+
   /// The list of -W... options used to alter the diagnostic mappings, with the
   /// prefixes removed.
   std::vector<std::string> Warnings;

diff  --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td
index 9fb7f8bb6489b0..70f9c75944b0db 100644
--- a/clang/include/clang/Driver/Options.td
+++ b/clang/include/clang/Driver/Options.td
@@ -9019,3 +9019,8 @@ def wasm_opt : Flag<["--"], "wasm-opt">,
   Group<m_Group>,
   HelpText<"Enable the wasm-opt optimizer (default)">,
   MarshallingInfoNegativeFlag<LangOpts<"NoWasmOpt">>;
+
+def warning_suppression_mappings_EQ : Joined<["--"],
+  "warning-suppression-mappings=">, Group<Diag_Group>,
+  HelpText<"File containing diagnostic suppresion mappings. See user manual "
+  "for file format.">, Visibility<[ClangOption, CC1Option]>;

diff  --git a/clang/include/clang/Frontend/CompilerInstance.h b/clang/include/clang/Frontend/CompilerInstance.h
index 3464654284f199..338eb3c6bd028d 100644
--- a/clang/include/clang/Frontend/CompilerInstance.h
+++ b/clang/include/clang/Frontend/CompilerInstance.h
@@ -24,6 +24,7 @@
 #include "llvm/ADT/StringRef.h"
 #include "llvm/Support/BuryPointer.h"
 #include "llvm/Support/FileSystem.h"
+#include "llvm/Support/VirtualFileSystem.h"
 #include <cassert>
 #include <list>
 #include <memory>
@@ -701,11 +702,10 @@ class CompilerInstance : public ModuleLoader {
   /// used by some diagnostics printers (for logging purposes only).
   ///
   /// \return The new object on success, or null on failure.
-  static IntrusiveRefCntPtr<DiagnosticsEngine>
-  createDiagnostics(DiagnosticOptions *Opts,
-                    DiagnosticConsumer *Client = nullptr,
-                    bool ShouldOwnClient = true,
-                    const CodeGenOptions *CodeGenOpts = nullptr);
+  static IntrusiveRefCntPtr<DiagnosticsEngine> createDiagnostics(
+      DiagnosticOptions *Opts, DiagnosticConsumer *Client = nullptr,
+      bool ShouldOwnClient = true, const CodeGenOptions *CodeGenOpts = nullptr,
+      IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS = nullptr);
 
   /// Create the file manager and replace any existing one with it.
   ///

diff  --git a/clang/lib/Basic/Diagnostic.cpp b/clang/lib/Basic/Diagnostic.cpp
index e23362fc7af00d..1034ac0c0036c5 100644
--- a/clang/lib/Basic/Diagnostic.cpp
+++ b/clang/lib/Basic/Diagnostic.cpp
@@ -12,7 +12,9 @@
 
 #include "clang/Basic/Diagnostic.h"
 #include "clang/Basic/CharInfo.h"
+#include "clang/Basic/DiagnosticDriver.h"
 #include "clang/Basic/DiagnosticError.h"
+#include "clang/Basic/DiagnosticFrontend.h"
 #include "clang/Basic/DiagnosticIDs.h"
 #include "clang/Basic/DiagnosticOptions.h"
 #include "clang/Basic/IdentifierTable.h"
@@ -21,13 +23,19 @@
 #include "clang/Basic/SourceManager.h"
 #include "clang/Basic/Specifiers.h"
 #include "clang/Basic/TokenKinds.h"
+#include "llvm/ADT/IntrusiveRefCntPtr.h"
 #include "llvm/ADT/SmallString.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/StringExtras.h"
+#include "llvm/ADT/StringMap.h"
 #include "llvm/ADT/StringRef.h"
 #include "llvm/Support/ConvertUTF.h"
 #include "llvm/Support/CrashRecoveryContext.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/SpecialCaseList.h"
 #include "llvm/Support/Unicode.h"
+#include "llvm/Support/VirtualFileSystem.h"
 #include "llvm/Support/raw_ostream.h"
 #include <algorithm>
 #include <cassert>
@@ -35,6 +43,7 @@
 #include <cstdint>
 #include <cstring>
 #include <limits>
+#include <memory>
 #include <string>
 #include <utility>
 #include <vector>
@@ -477,6 +486,136 @@ void DiagnosticsEngine::setSeverityForAll(diag::Flavor Flavor,
       setSeverity(Diag, Map, Loc);
 }
 
+namespace {
+// FIXME: We should isolate the parser from SpecialCaseList and just use it
+// here.
+class WarningsSpecialCaseList : public llvm::SpecialCaseList {
+public:
+  static std::unique_ptr<WarningsSpecialCaseList>
+  create(const llvm::MemoryBuffer &Input, std::string &Err);
+
+  // Section names refer to diagnostic groups, which cover multiple individual
+  // diagnostics. Expand diagnostic groups here to individual diagnostics.
+  // A diagnostic can have multiple diagnostic groups associated with it, we let
+  // the last section take precedence in such cases.
+  void processSections(DiagnosticsEngine &Diags);
+
+  bool isDiagSuppressed(diag::kind DiagId, llvm::StringRef FilePath) const;
+
+private:
+  // Find the longest glob pattern that matches FilePath amongst
+  // CategoriesToMatchers, return true iff the match exists and belongs to a
+  // positive category.
+  bool globsMatches(const llvm::StringMap<Matcher> &CategoriesToMatchers,
+                    llvm::StringRef FilePath) const;
+
+  llvm::DenseMap<diag::kind, const Section *> DiagToSection;
+};
+} // namespace
+
+std::unique_ptr<WarningsSpecialCaseList>
+WarningsSpecialCaseList::create(const llvm::MemoryBuffer &Input,
+                                std::string &Err) {
+  auto WarningSuppressionList = std::make_unique<WarningsSpecialCaseList>();
+  if (!WarningSuppressionList->createInternal(&Input, Err))
+    return nullptr;
+  return WarningSuppressionList;
+}
+
+void WarningsSpecialCaseList::processSections(DiagnosticsEngine &Diags) {
+  // Drop the default section introduced by special case list, we only support
+  // exact diagnostic group names.
+  // FIXME: We should make this configurable in the parser instead.
+  Sections.erase("*");
+  // Make sure we iterate sections by their line numbers.
+  std::vector<std::pair<unsigned, const llvm::StringMapEntry<Section> *>>
+      LineAndSectionEntry;
+  LineAndSectionEntry.reserve(Sections.size());
+  for (const auto &Entry : Sections) {
+    llvm::StringRef DiagName = Entry.getKey();
+    // Each section has a matcher with that section's name, attached to that
+    // line.
+    const auto &DiagSectionMatcher = Entry.getValue().SectionMatcher;
+    unsigned DiagLine = DiagSectionMatcher->Globs.at(DiagName).second;
+    LineAndSectionEntry.emplace_back(DiagLine, &Entry);
+  }
+  llvm::sort(LineAndSectionEntry);
+  static constexpr auto WarningFlavor = clang::diag::Flavor::WarningOrError;
+  for (const auto &[_, SectionEntry] : LineAndSectionEntry) {
+    SmallVector<diag::kind> GroupDiags;
+    llvm::StringRef DiagGroup = SectionEntry->getKey();
+    if (Diags.getDiagnosticIDs()->getDiagnosticsInGroup(
+            WarningFlavor, DiagGroup, GroupDiags)) {
+      StringRef Suggestion =
+          DiagnosticIDs::getNearestOption(WarningFlavor, DiagGroup);
+      Diags.Report(diag::warn_unknown_diag_option)
+          << static_cast<unsigned>(WarningFlavor) << DiagGroup
+          << !Suggestion.empty() << Suggestion;
+      continue;
+    }
+    for (diag::kind Diag : GroupDiags)
+      // We're intentionally overwriting any previous mappings here to make sure
+      // latest one takes precedence.
+      DiagToSection[Diag] = &SectionEntry->getValue();
+  }
+}
+
+void DiagnosticsEngine::setDiagSuppressionMapping(llvm::MemoryBuffer &Input) {
+  std::string Error;
+  auto WarningSuppressionList = WarningsSpecialCaseList::create(Input, Error);
+  if (!WarningSuppressionList) {
+    Report(diag::err_drv_malformed_warning_suppression_mapping)
+        << Input.getBufferIdentifier() << Error;
+    return;
+  }
+  WarningSuppressionList->processSections(*this);
+  DiagSuppressionMapping =
+      [WarningSuppressionList(std::move(WarningSuppressionList))](
+          diag::kind DiagId, llvm::StringRef Path) {
+        return WarningSuppressionList->isDiagSuppressed(DiagId, Path);
+      };
+}
+
+bool WarningsSpecialCaseList::isDiagSuppressed(diag::kind DiagId,
+                                               llvm::StringRef FilePath) const {
+  const Section *DiagSection = DiagToSection.lookup(DiagId);
+  if (!DiagSection)
+    return false;
+  const SectionEntries &EntityTypeToCategories = DiagSection->Entries;
+  auto SrcEntriesIt = EntityTypeToCategories.find("src");
+  if (SrcEntriesIt == EntityTypeToCategories.end())
+    return false;
+  const llvm::StringMap<llvm::SpecialCaseList::Matcher> &CategoriesToMatchers =
+      SrcEntriesIt->getValue();
+  return globsMatches(CategoriesToMatchers, FilePath);
+}
+
+bool WarningsSpecialCaseList::globsMatches(
+    const llvm::StringMap<Matcher> &CategoriesToMatchers,
+    llvm::StringRef FilePath) const {
+  llvm::StringRef LongestMatch;
+  bool LongestIsPositive = false;
+  for (const auto &Entry : CategoriesToMatchers) {
+    llvm::StringRef Category = Entry.getKey();
+    const llvm::SpecialCaseList::Matcher &Matcher = Entry.getValue();
+    bool IsPositive = Category != "emit";
+    for (const auto &[Pattern, Glob] : Matcher.Globs) {
+      if (Pattern.size() < LongestMatch.size())
+        continue;
+      if (!Glob.first.match(FilePath))
+        continue;
+      LongestMatch = Pattern;
+      LongestIsPositive = IsPositive;
+    }
+  }
+  return LongestIsPositive;
+}
+
+bool DiagnosticsEngine::isSuppressedViaMapping(diag::kind DiagId,
+                                               llvm::StringRef FilePath) const {
+  return DiagSuppressionMapping && DiagSuppressionMapping(DiagId, FilePath);
+}
+
 void DiagnosticsEngine::Report(const StoredDiagnostic &storedDiag) {
   DiagnosticStorage DiagStorage;
   DiagStorage.DiagRanges.append(storedDiag.range_begin(),

diff  --git a/clang/lib/Basic/DiagnosticIDs.cpp b/clang/lib/Basic/DiagnosticIDs.cpp
index fc8bd29faa080f..ccce70d18ebf54 100644
--- a/clang/lib/Basic/DiagnosticIDs.cpp
+++ b/clang/lib/Basic/DiagnosticIDs.cpp
@@ -17,6 +17,7 @@
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/Support/ErrorHandling.h"
+#include "llvm/Support/Path.h"
 #include <map>
 #include <optional>
 using namespace clang;
@@ -599,6 +600,17 @@ DiagnosticIDs::getDiagnosticSeverity(unsigned DiagID, SourceLocation Loc,
       SM.isInSystemMacro(Loc))
     return diag::Severity::Ignored;
 
+  // Clang-diagnostics pragmas always take precedence over suppression mapping.
+  if (!Mapping.isPragma()) {
+    // We also use presumed locations here to improve reproducibility for
+    // preprocessed inputs.
+    if (PresumedLoc PLoc = SM.getPresumedLoc(Loc);
+        PLoc.isValid() && Diag.isSuppressedViaMapping(
+                              DiagID, llvm::sys::path::remove_leading_dotslash(
+                                          PLoc.getFilename())))
+      return diag::Severity::Ignored;
+  }
+
   return Result;
 }
 

diff  --git a/clang/lib/Basic/Warnings.cpp b/clang/lib/Basic/Warnings.cpp
index 5a5ac555633886..7145a65fe5d26d 100644
--- a/clang/lib/Basic/Warnings.cpp
+++ b/clang/lib/Basic/Warnings.cpp
@@ -23,10 +23,15 @@
 // simpler because a remark can't be promoted to an error.
 #include "clang/Basic/AllDiagnostics.h"
 #include "clang/Basic/Diagnostic.h"
+#include "clang/Basic/DiagnosticDriver.h"
+#include "clang/Basic/DiagnosticIDs.h"
 #include "clang/Basic/DiagnosticOptions.h"
-#include <algorithm>
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/SpecialCaseList.h"
+#include "llvm/Support/VirtualFileSystem.h"
 #include <cstring>
-#include <utility>
+#include <memory>
+#include <vector>
 using namespace clang;
 
 // EmitUnknownDiagWarning - Emit a warning and typo hint for unknown warning
@@ -43,6 +48,7 @@ static void EmitUnknownDiagWarning(DiagnosticsEngine &Diags,
 
 void clang::ProcessWarningOptions(DiagnosticsEngine &Diags,
                                   const DiagnosticOptions &Opts,
+                                  llvm::vfs::FileSystem &VFS,
                                   bool ReportDiags) {
   Diags.setSuppressSystemWarnings(true);  // Default to -Wno-system-headers
   Diags.setIgnoreAllWarnings(Opts.IgnoreWarnings);
@@ -70,6 +76,16 @@ void clang::ProcessWarningOptions(DiagnosticsEngine &Diags,
   else
     Diags.setExtensionHandlingBehavior(diag::Severity::Ignored);
 
+  if (!Opts.DiagnosticSuppressionMappingsFile.empty()) {
+    if (auto FileContents =
+            VFS.getBufferForFile(Opts.DiagnosticSuppressionMappingsFile)) {
+      Diags.setDiagSuppressionMapping(**FileContents);
+    } else if (ReportDiags) {
+      Diags.Report(diag::err_drv_no_such_file)
+          << Opts.DiagnosticSuppressionMappingsFile;
+    }
+  }
+
   SmallVector<diag::kind, 10> _Diags;
   const IntrusiveRefCntPtr< DiagnosticIDs > DiagIDs =
     Diags.getDiagnosticIDs();

diff  --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp
index 0952262c360185..43c11318986422 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -44,6 +44,7 @@
 #include "llvm/ADT/ScopeExit.h"
 #include "llvm/ADT/SmallSet.h"
 #include "llvm/ADT/StringExtras.h"
+#include "llvm/ADT/StringRef.h"
 #include "llvm/BinaryFormat/Magic.h"
 #include "llvm/Config/llvm-config.h"
 #include "llvm/Frontend/Debug/Options.h"
@@ -4514,6 +4515,8 @@ static void RenderDiagnosticsOptions(const Driver &D, const ArgList &Args,
 
   Args.addOptOutFlag(CmdArgs, options::OPT_fspell_checking,
                      options::OPT_fno_spell_checking);
+
+  Args.addLastArg(CmdArgs, options::OPT_warning_suppression_mappings_EQ);
 }
 
 DwarfFissionKind tools::getDebugFissionKind(const Driver &D,

diff  --git a/clang/lib/Frontend/ASTUnit.cpp b/clang/lib/Frontend/ASTUnit.cpp
index f58e27a7979d1e..8df5465ad990da 100644
--- a/clang/lib/Frontend/ASTUnit.cpp
+++ b/clang/lib/Frontend/ASTUnit.cpp
@@ -1367,7 +1367,7 @@ ASTUnit::getMainBufferWithPrecompiledPreamble(
       // after parsing the preamble.
       getDiagnostics().Reset();
       ProcessWarningOptions(getDiagnostics(),
-                            PreambleInvocationIn.getDiagnosticOpts());
+                            PreambleInvocationIn.getDiagnosticOpts(), *VFS);
       getDiagnostics().setNumWarnings(NumWarningsInPreamble);
 
       PreambleRebuildCountdown = 1;
@@ -1593,7 +1593,8 @@ ASTUnit *ASTUnit::LoadFromCompilerInvocationAction(
   // We'll manage file buffers ourselves.
   CI->getPreprocessorOpts().RetainRemappedFileBuffers = true;
   CI->getFrontendOpts().DisableFree = false;
-  ProcessWarningOptions(AST->getDiagnostics(), CI->getDiagnosticOpts());
+  ProcessWarningOptions(AST->getDiagnostics(), CI->getDiagnosticOpts(),
+                        AST->getFileManager().getVirtualFileSystem());
 
   // Create the compiler instance to use for building the AST.
   std::unique_ptr<CompilerInstance> Clang(
@@ -1701,7 +1702,8 @@ bool ASTUnit::LoadFromCompilerInvocation(
   Invocation->getPreprocessorOpts().RetainRemappedFileBuffers = true;
   Invocation->getFrontendOpts().DisableFree = false;
   getDiagnostics().Reset();
-  ProcessWarningOptions(getDiagnostics(), Invocation->getDiagnosticOpts());
+  ProcessWarningOptions(getDiagnostics(), Invocation->getDiagnosticOpts(),
+                        *VFS);
 
   std::unique_ptr<llvm::MemoryBuffer> OverrideMainBuffer;
   if (PrecompilePreambleAfterNParses > 0) {
@@ -1709,7 +1711,8 @@ bool ASTUnit::LoadFromCompilerInvocation(
     OverrideMainBuffer =
         getMainBufferWithPrecompiledPreamble(PCHContainerOps, *Invocation, VFS);
     getDiagnostics().Reset();
-    ProcessWarningOptions(getDiagnostics(), Invocation->getDiagnosticOpts());
+    ProcessWarningOptions(getDiagnostics(), Invocation->getDiagnosticOpts(),
+                          *VFS);
   }
 
   SimpleTimer ParsingTimer(WantTiming);
@@ -1902,7 +1905,8 @@ bool ASTUnit::Reparse(std::shared_ptr<PCHContainerOperations> PCHContainerOps,
   // Clear out the diagnostics state.
   FileMgr.reset();
   getDiagnostics().Reset();
-  ProcessWarningOptions(getDiagnostics(), Invocation->getDiagnosticOpts());
+  ProcessWarningOptions(getDiagnostics(), Invocation->getDiagnosticOpts(),
+                        *VFS);
   if (OverrideMainBuffer)
     getDiagnostics().setNumWarnings(NumWarningsInPreamble);
 
@@ -2241,7 +2245,8 @@ void ASTUnit::CodeComplete(
   CaptureDroppedDiagnostics Capture(CaptureDiagsKind::All,
                                     Clang->getDiagnostics(),
                                     &StoredDiagnostics, nullptr);
-  ProcessWarningOptions(Diag, Inv.getDiagnosticOpts());
+  ProcessWarningOptions(Diag, Inv.getDiagnosticOpts(),
+                        FileMgr.getVirtualFileSystem());
 
   // Create the target instance.
   if (!Clang->createTarget()) {

diff  --git a/clang/lib/Frontend/CompilerInstance.cpp b/clang/lib/Frontend/CompilerInstance.cpp
index 240305b33824b8..ecc6782c7cb4fb 100644
--- a/clang/lib/Frontend/CompilerInstance.cpp
+++ b/clang/lib/Frontend/CompilerInstance.cpp
@@ -39,6 +39,7 @@
 #include "clang/Serialization/ASTReader.h"
 #include "clang/Serialization/GlobalModuleIndex.h"
 #include "clang/Serialization/InMemoryModuleCache.h"
+#include "llvm/ADT/IntrusiveRefCntPtr.h"
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/ScopeExit.h"
 #include "llvm/ADT/Statistic.h"
@@ -54,6 +55,7 @@
 #include "llvm/Support/Signals.h"
 #include "llvm/Support/TimeProfiler.h"
 #include "llvm/Support/Timer.h"
+#include "llvm/Support/VirtualFileSystem.h"
 #include "llvm/Support/raw_ostream.h"
 #include "llvm/TargetParser/Host.h"
 #include <optional>
@@ -332,19 +334,22 @@ static void SetupSerializedDiagnostics(DiagnosticOptions *DiagOpts,
 
 void CompilerInstance::createDiagnostics(DiagnosticConsumer *Client,
                                          bool ShouldOwnClient) {
-  Diagnostics = createDiagnostics(&getDiagnosticOpts(), Client,
-                                  ShouldOwnClient, &getCodeGenOpts());
+  Diagnostics = createDiagnostics(
+      &getDiagnosticOpts(), Client, ShouldOwnClient, &getCodeGenOpts(),
+      FileMgr ? FileMgr->getVirtualFileSystemPtr() : nullptr);
 }
 
-IntrusiveRefCntPtr<DiagnosticsEngine>
-CompilerInstance::createDiagnostics(DiagnosticOptions *Opts,
-                                    DiagnosticConsumer *Client,
-                                    bool ShouldOwnClient,
-                                    const CodeGenOptions *CodeGenOpts) {
+IntrusiveRefCntPtr<DiagnosticsEngine> CompilerInstance::createDiagnostics(
+    DiagnosticOptions *Opts, DiagnosticConsumer *Client, bool ShouldOwnClient,
+    const CodeGenOptions *CodeGenOpts,
+    llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS) {
   IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs());
   IntrusiveRefCntPtr<DiagnosticsEngine>
       Diags(new DiagnosticsEngine(DiagID, Opts));
 
+  if (!VFS)
+    VFS = llvm::vfs::getRealFileSystem();
+
   // Create the diagnostic client for reporting errors or for
   // implementing -verify.
   if (Client) {
@@ -367,7 +372,7 @@ CompilerInstance::createDiagnostics(DiagnosticOptions *Opts,
                                Opts->DiagnosticSerializationFile);
 
   // Configure our handling of diagnostics.
-  ProcessWarningOptions(*Diags, *Opts);
+  ProcessWarningOptions(*Diags, *Opts, *VFS);
 
   return Diags;
 }

diff  --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp
index b5fd35aaa1e841..e3145dcacf58d1 100644
--- a/clang/lib/Frontend/CompilerInvocation.cpp
+++ b/clang/lib/Frontend/CompilerInvocation.cpp
@@ -2521,6 +2521,11 @@ void CompilerInvocationBase::GenerateDiagnosticArgs(
 
     Consumer(StringRef("-R") + Remark);
   }
+
+  if (!Opts.DiagnosticSuppressionMappingsFile.empty()) {
+    GenerateArg(Consumer, OPT_warning_suppression_mappings_EQ,
+                Opts.DiagnosticSuppressionMappingsFile);
+  }
 }
 
 std::unique_ptr<DiagnosticOptions>
@@ -2597,6 +2602,9 @@ bool clang::ParseDiagnosticArgs(DiagnosticOptions &Opts, ArgList &Args,
     Opts.TabStop = DiagnosticOptions::DefaultTabStop;
   }
 
+  if (const Arg *A = Args.getLastArg(OPT_warning_suppression_mappings_EQ))
+    Opts.DiagnosticSuppressionMappingsFile = A->getValue();
+
   addDiagnosticArgs(Args, OPT_W_Group, OPT_W_value_Group, Opts.Warnings);
   addDiagnosticArgs(Args, OPT_R_Group, OPT_R_value_Group, Opts.Remarks);
 

diff  --git a/clang/lib/Frontend/PrecompiledPreamble.cpp b/clang/lib/Frontend/PrecompiledPreamble.cpp
index cab5838fceb24d..d1bac09830b04b 100644
--- a/clang/lib/Frontend/PrecompiledPreamble.cpp
+++ b/clang/lib/Frontend/PrecompiledPreamble.cpp
@@ -479,7 +479,7 @@ llvm::ErrorOr<PrecompiledPreamble> PrecompiledPreamble::Build(
 
   // Clear out old caches and data.
   Diagnostics.Reset();
-  ProcessWarningOptions(Diagnostics, Clang->getDiagnosticOpts());
+  ProcessWarningOptions(Diagnostics, Clang->getDiagnosticOpts(), *VFS);
 
   VFS =
       createVFSFromCompilerInvocation(Clang->getInvocation(), Diagnostics, VFS);

diff  --git a/clang/lib/Interpreter/CodeCompletion.cpp b/clang/lib/Interpreter/CodeCompletion.cpp
index 791426807cb91d..bbc8830d76bc00 100644
--- a/clang/lib/Interpreter/CodeCompletion.cpp
+++ b/clang/lib/Interpreter/CodeCompletion.cpp
@@ -380,8 +380,8 @@ void ReplCodeCompleter::codeComplete(CompilerInstance *InterpCI,
   AU->CodeComplete(CodeCompletionFileName, 1, Col, RemappedFiles, false, false,
                    false, consumer,
                    std::make_shared<clang::PCHContainerOperations>(), *diag,
-                   InterpCI->getLangOpts(), InterpCI->getSourceManager(),
-                   InterpCI->getFileManager(), sd, tb, std::move(Act));
+                   InterpCI->getLangOpts(), AU->getSourceManager(),
+                   AU->getFileManager(), sd, tb, std::move(Act));
 }
 
 } // namespace clang

diff  --git a/clang/lib/Serialization/ASTReader.cpp b/clang/lib/Serialization/ASTReader.cpp
index cf987df0e2154d..097e0eed63ce2a 100644
--- a/clang/lib/Serialization/ASTReader.cpp
+++ b/clang/lib/Serialization/ASTReader.cpp
@@ -603,7 +603,9 @@ bool PCHValidator::ReadDiagnosticOptions(
       new DiagnosticsEngine(DiagIDs, DiagOpts.get()));
   // This should never fail, because we would have processed these options
   // before writing them to an ASTFile.
-  ProcessWarningOptions(*Diags, *DiagOpts, /*Report*/false);
+  ProcessWarningOptions(*Diags, *DiagOpts,
+                        PP.getFileManager().getVirtualFileSystem(),
+                        /*Report*/ false);
 
   ModuleManager &ModuleMgr = Reader.getModuleManager();
   assert(ModuleMgr.size() >= 1 && "what ASTFile is this then");

diff  --git a/clang/test/Misc/Inputs/suppression-mapping.txt b/clang/test/Misc/Inputs/suppression-mapping.txt
new file mode 100644
index 00000000000000..abe4fde0c265d5
--- /dev/null
+++ b/clang/test/Misc/Inputs/suppression-mapping.txt
@@ -0,0 +1,13 @@
+# Suppress unused warnings in all files, apart from the ones under `foo/`.
+[unused]
+src:*
+src:*foo/*=emit
+
+# This should take precedence over `unused` group, as it's mentioned later.
+[unused-variable]
+# We don't suppress unused-variable warnings in "any" file.
+
+# Some warning groups can have strange spellings.
+[format=2]
+src:*
+src:*foo/*=emit

diff  --git a/clang/test/Misc/warning-suppression-mappings-pragmas.cpp b/clang/test/Misc/warning-suppression-mappings-pragmas.cpp
new file mode 100644
index 00000000000000..58a2537477166b
--- /dev/null
+++ b/clang/test/Misc/warning-suppression-mappings-pragmas.cpp
@@ -0,0 +1,16 @@
+// Check that clang-diagnostic pragmas take precedence over suppression mapping.
+// RUN: %clang -cc1 -verify -Wformat=2 --warning-suppression-mappings=%S/Inputs/suppression-mapping.txt -fsyntax-only %s
+
+__attribute__((__format__ (__printf__, 1, 2)))
+void format_err(const char* const pString, ...);
+
+void foo() {
+  const char *x;
+  format_err(x); // Warning suppressed here.
+  // check that pragmas take precedence
+#pragma clang diagnostic push
+#pragma clang diagnostic warning "-Wformat=2"
+  format_err(x); // expected-warning{{format string is not a string literal (potentially insecure)}} \
+                 // expected-note{{treat the string as an argument to avoid this}}
+#pragma clang diagnostic pop
+}

diff  --git a/clang/test/Misc/warning-suppression-mappings.cpp b/clang/test/Misc/warning-suppression-mappings.cpp
new file mode 100644
index 00000000000000..6e86bf9c0f8f18
--- /dev/null
+++ b/clang/test/Misc/warning-suppression-mappings.cpp
@@ -0,0 +1,31 @@
+// Check for warnings
+// RUN: not %clang --warning-suppression-mappings=foo.txt -fsyntax-only %s 2>&1 | FileCheck -check-prefix MISSING_MAPPING %s
+// RUN: not %clang -cc1 --warning-suppression-mappings=foo.txt -fsyntax-only %s 2>&1 | FileCheck -check-prefix MISSING_MAPPING %s
+// MISSING_MAPPING: error: no such file or directory: 'foo.txt'
+
+// Check that it's no-op when diagnostics aren't enabled.
+// RUN: %clang -cc1 -Wno-everything -Werror --warning-suppression-mappings=%S/Inputs/suppression-mapping.txt -fsyntax-only %s 2>&1 | FileCheck -check-prefix WARNINGS_DISABLED --allow-empty %s
+// WARNINGS_DISABLED-NOT: warning:
+// WARNINGS_DISABLED-NOT: error:
+
+// RUN: %clang -cc1 -verify -Wformat=2 -Wunused --warning-suppression-mappings=%S/Inputs/suppression-mapping.txt -fsyntax-only %s
+
+__attribute__((__format__ (__printf__, 1, 2)))
+void format_err(const char* const pString, ...);
+
+namespace {
+void foo() {
+  const char *x;
+  format_err(x); // Warning suppressed here.
+  const char *y; // expected-warning{{unused variable 'y'}}
+}
+}
+
+#line 42 "foo/bar.h"
+namespace {
+void bar() { // expected-warning{{unused function 'bar'}}
+  const char *x;
+  format_err(x); // expected-warning{{format string is not a string literal (potentially insecure)}} \
+                 // expected-note{{treat the string as an argument to avoid this}}
+}
+}

diff  --git a/clang/tools/driver/cc1gen_reproducer_main.cpp b/clang/tools/driver/cc1gen_reproducer_main.cpp
index be081cac8c03b1..df59b53f9ef186 100644
--- a/clang/tools/driver/cc1gen_reproducer_main.cpp
+++ b/clang/tools/driver/cc1gen_reproducer_main.cpp
@@ -121,9 +121,10 @@ generateReproducerForInvocationArguments(ArrayRef<const char *> Argv,
 
   IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs());
   DiagnosticsEngine Diags(DiagID, &*DiagOpts, new IgnoringDiagConsumer());
-  ProcessWarningOptions(Diags, *DiagOpts, /*ReportDiags=*/false);
-  Driver TheDriver(ToolContext.Path, llvm::sys::getDefaultTargetTriple(),
-                   Diags);
+  auto VFS = llvm::vfs::getRealFileSystem();
+  ProcessWarningOptions(Diags, *DiagOpts, *VFS, /*ReportDiags=*/false);
+  Driver TheDriver(ToolContext.Path, llvm::sys::getDefaultTargetTriple(), Diags,
+                   /*Title=*/"clang LLVM compiler", VFS);
   TheDriver.setTargetAndMode(TargetAndMode);
   if (ToolContext.NeedsPrependArg)
     TheDriver.setPrependArg(ToolContext.PrependArg);

diff  --git a/clang/tools/driver/driver.cpp b/clang/tools/driver/driver.cpp
index 686eaea0aa7c83..12038de476ace1 100644
--- a/clang/tools/driver/driver.cpp
+++ b/clang/tools/driver/driver.cpp
@@ -47,6 +47,7 @@
 #include "llvm/Support/StringSaver.h"
 #include "llvm/Support/TargetSelect.h"
 #include "llvm/Support/Timer.h"
+#include "llvm/Support/VirtualFileSystem.h"
 #include "llvm/Support/raw_ostream.h"
 #include "llvm/TargetParser/Host.h"
 #include <memory>
@@ -334,9 +335,11 @@ int clang_main(int Argc, char **Argv, const llvm::ToolContext &ToolContext) {
         Diags.takeClient(), std::move(SerializedConsumer)));
   }
 
-  ProcessWarningOptions(Diags, *DiagOpts, /*ReportDiags=*/false);
+  auto VFS = llvm::vfs::getRealFileSystem();
+  ProcessWarningOptions(Diags, *DiagOpts, *VFS, /*ReportDiags=*/false);
 
-  Driver TheDriver(Path, llvm::sys::getDefaultTargetTriple(), Diags);
+  Driver TheDriver(Path, llvm::sys::getDefaultTargetTriple(), Diags,
+                   /*Title=*/"clang LLVM compiler", VFS);
   auto TargetAndMode = ToolChain::getTargetAndModeFromProgramName(ProgName);
   TheDriver.setTargetAndMode(TargetAndMode);
   // If -canonical-prefixes is set, GetExecutablePath will have resolved Path

diff  --git a/clang/unittests/Basic/DiagnosticTest.cpp b/clang/unittests/Basic/DiagnosticTest.cpp
index d8d23e3b670097..36a77c7247655f 100644
--- a/clang/unittests/Basic/DiagnosticTest.cpp
+++ b/clang/unittests/Basic/DiagnosticTest.cpp
@@ -10,8 +10,18 @@
 #include "clang/Basic/DiagnosticError.h"
 #include "clang/Basic/DiagnosticIDs.h"
 #include "clang/Basic/DiagnosticLex.h"
+#include "clang/Basic/DiagnosticSema.h"
+#include "clang/Basic/FileManager.h"
+#include "clang/Basic/SourceManager.h"
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/IntrusiveRefCntPtr.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/VirtualFileSystem.h"
+#include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include <optional>
+#include <vector>
 
 using namespace llvm;
 using namespace clang;
@@ -28,6 +38,9 @@ void clang::DiagnosticsTestHelper(DiagnosticsEngine &diag) {
 }
 
 namespace {
+using testing::AllOf;
+using testing::ElementsAre;
+using testing::IsEmpty;
 
 // Check that DiagnosticErrorTrap works with SuppressAllDiagnostics.
 TEST(DiagnosticTest, suppressAndTrap) {
@@ -167,4 +180,160 @@ TEST(DiagnosticTest, storedDiagEmptyWarning) {
   // Make sure an empty warning can round-trip with \c StoredDiagnostic.
   Diags.Report(CaptureConsumer.StoredDiags.front());
 }
+
+class SuppressionMappingTest : public testing::Test {
+public:
+  SuppressionMappingTest() {
+    Diags.setClient(&CaptureConsumer, /*ShouldOwnClient=*/false);
+  }
+
+protected:
+  llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> FS =
+      llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>();
+  DiagnosticsEngine Diags{new DiagnosticIDs(), new DiagnosticOptions};
+
+  llvm::ArrayRef<StoredDiagnostic> diags() {
+    return CaptureConsumer.StoredDiags;
+  }
+
+private:
+  class CaptureDiagnosticConsumer : public DiagnosticConsumer {
+  public:
+    std::vector<StoredDiagnostic> StoredDiags;
+
+    void HandleDiagnostic(DiagnosticsEngine::Level level,
+                          const Diagnostic &Info) override {
+      StoredDiags.push_back(StoredDiagnostic(level, Info));
+    }
+  };
+  CaptureDiagnosticConsumer CaptureConsumer;
+};
+
+MATCHER_P(WithMessage, Msg, "has diagnostic message") {
+  return arg.getMessage() == Msg;
+}
+MATCHER(IsError, "has error severity") {
+  return arg.getLevel() == DiagnosticsEngine::Level::Error;
+}
+
+TEST_F(SuppressionMappingTest, MissingMappingFile) {
+  Diags.getDiagnosticOptions().DiagnosticSuppressionMappingsFile = "foo.txt";
+  clang::ProcessWarningOptions(Diags, Diags.getDiagnosticOptions(), *FS);
+  EXPECT_THAT(diags(), ElementsAre(AllOf(
+                           WithMessage("no such file or directory: 'foo.txt'"),
+                           IsError())));
+}
+
+TEST_F(SuppressionMappingTest, MalformedFile) {
+  Diags.getDiagnosticOptions().DiagnosticSuppressionMappingsFile = "foo.txt";
+  FS->addFile("foo.txt", /*ModificationTime=*/{},
+              llvm::MemoryBuffer::getMemBuffer("asdf", "foo.txt"));
+  clang::ProcessWarningOptions(Diags, Diags.getDiagnosticOptions(), *FS);
+  EXPECT_THAT(diags(),
+              ElementsAre(AllOf(
+                  WithMessage("failed to process suppression mapping file "
+                              "'foo.txt': malformed line 1: 'asdf'"),
+                  IsError())));
+}
+
+TEST_F(SuppressionMappingTest, UnknownDiagName) {
+  Diags.getDiagnosticOptions().DiagnosticSuppressionMappingsFile = "foo.txt";
+  FS->addFile("foo.txt", /*ModificationTime=*/{},
+              llvm::MemoryBuffer::getMemBuffer("[non-existing-warning]"));
+  clang::ProcessWarningOptions(Diags, Diags.getDiagnosticOptions(), *FS);
+  EXPECT_THAT(diags(), ElementsAre(WithMessage(
+                           "unknown warning option 'non-existing-warning'")));
+}
+
+TEST_F(SuppressionMappingTest, SuppressesGroup) {
+  llvm::StringLiteral SuppressionMappingFile = R"(
+  [unused]
+  src:*)";
+  Diags.getDiagnosticOptions().DiagnosticSuppressionMappingsFile = "foo.txt";
+  FS->addFile("foo.txt", /*ModificationTime=*/{},
+              llvm::MemoryBuffer::getMemBuffer(SuppressionMappingFile));
+  clang::ProcessWarningOptions(Diags, Diags.getDiagnosticOptions(), *FS);
+  EXPECT_THAT(diags(), IsEmpty());
+
+  EXPECT_TRUE(
+      Diags.isSuppressedViaMapping(diag::warn_unused_function, "foo.cpp"));
+  EXPECT_FALSE(Diags.isSuppressedViaMapping(diag::warn_deprecated, "foo.cpp"));
+}
+
+TEST_F(SuppressionMappingTest, EmitCategoryIsExcluded) {
+  llvm::StringLiteral SuppressionMappingFile = R"(
+  [unused]
+  src:*
+  src:*foo.cpp=emit)";
+  Diags.getDiagnosticOptions().DiagnosticSuppressionMappingsFile = "foo.txt";
+  FS->addFile("foo.txt", /*ModificationTime=*/{},
+              llvm::MemoryBuffer::getMemBuffer(SuppressionMappingFile));
+  clang::ProcessWarningOptions(Diags, Diags.getDiagnosticOptions(), *FS);
+  EXPECT_THAT(diags(), IsEmpty());
+
+  EXPECT_TRUE(
+      Diags.isSuppressedViaMapping(diag::warn_unused_function, "bar.cpp"));
+  EXPECT_FALSE(
+      Diags.isSuppressedViaMapping(diag::warn_unused_function, "foo.cpp"));
+}
+
+TEST_F(SuppressionMappingTest, LongestMatchWins) {
+  llvm::StringLiteral SuppressionMappingFile = R"(
+  [unused]
+  src:*clang/*
+  src:*clang/lib/Sema/*=emit
+  src:*clang/lib/Sema/foo*)";
+  Diags.getDiagnosticOptions().DiagnosticSuppressionMappingsFile = "foo.txt";
+  FS->addFile("foo.txt", /*ModificationTime=*/{},
+              llvm::MemoryBuffer::getMemBuffer(SuppressionMappingFile));
+  clang::ProcessWarningOptions(Diags, Diags.getDiagnosticOptions(), *FS);
+  EXPECT_THAT(diags(), IsEmpty());
+
+  EXPECT_TRUE(Diags.isSuppressedViaMapping(diag::warn_unused_function,
+                                           "clang/lib/Basic/foo.h"));
+  EXPECT_FALSE(Diags.isSuppressedViaMapping(diag::warn_unused_function,
+                                            "clang/lib/Sema/bar.h"));
+  EXPECT_TRUE(Diags.isSuppressedViaMapping(diag::warn_unused_function,
+                                           "clang/lib/Sema/foo.h"));
+}
+
+TEST_F(SuppressionMappingTest, IsIgnored) {
+  llvm::StringLiteral SuppressionMappingFile = R"(
+  [unused]
+  src:*clang/*)";
+  Diags.getDiagnosticOptions().DiagnosticSuppressionMappingsFile = "foo.txt";
+  Diags.getDiagnosticOptions().Warnings = {"unused"};
+  FS->addFile("foo.txt", /*ModificationTime=*/{},
+              llvm::MemoryBuffer::getMemBuffer(SuppressionMappingFile));
+  clang::ProcessWarningOptions(Diags, Diags.getDiagnosticOptions(), *FS);
+  ASSERT_THAT(diags(), IsEmpty());
+
+  FileManager FM({}, FS);
+  SourceManager SM(Diags, FM);
+
+  auto ClangID =
+      SM.createFileID(llvm::MemoryBuffer::getMemBuffer("", "clang/foo.h"));
+  auto NonClangID =
+      SM.createFileID(llvm::MemoryBuffer::getMemBuffer("", "llvm/foo.h"));
+  auto PresumedClangID =
+      SM.createFileID(llvm::MemoryBuffer::getMemBuffer("", "llvm/foo2.h"));
+  // Add a line directive to point into clang/foo.h
+  SM.AddLineNote(SM.getLocForStartOfFile(PresumedClangID), 42,
+                 SM.getLineTableFilenameID("clang/foo.h"), false, false,
+                 clang::SrcMgr::C_User);
+
+  EXPECT_TRUE(Diags.isIgnored(diag::warn_unused_function,
+                              SM.getLocForStartOfFile(ClangID)));
+  EXPECT_FALSE(Diags.isIgnored(diag::warn_unused_function,
+                               SM.getLocForStartOfFile(NonClangID)));
+  EXPECT_TRUE(Diags.isIgnored(diag::warn_unused_function,
+                              SM.getLocForStartOfFile(PresumedClangID)));
+
+  // Pretend we have a clang-diagnostic pragma to enforce the warning. Make sure
+  // suppressing mapping doesn't take over.
+  Diags.setSeverity(diag::warn_unused_function, diag::Severity::Error,
+                    SM.getLocForStartOfFile(ClangID));
+  EXPECT_FALSE(Diags.isIgnored(diag::warn_unused_function,
+                               SM.getLocForStartOfFile(ClangID)));
 }
+} // namespace

diff  --git a/clang/unittests/Frontend/CompilerInvocationTest.cpp b/clang/unittests/Frontend/CompilerInvocationTest.cpp
index 7912253b761e9b..45478de5e6f7c8 100644
--- a/clang/unittests/Frontend/CompilerInvocationTest.cpp
+++ b/clang/unittests/Frontend/CompilerInvocationTest.cpp
@@ -1046,4 +1046,15 @@ TEST_F(CommandLineTest, PluginArgsRoundTripDeterminism) {
 
   ASSERT_TRUE(CompilerInvocation::CreateFromArgs(Invocation, Args, *Diags));
 }
+
+TEST_F(CommandLineTest, WarningSuppressionMappings) {
+  const char *Args[] = {"--warning-suppression-mappings=foo.txt"};
+
+  EXPECT_TRUE(CompilerInvocation::CreateFromArgs(Invocation, Args, *Diags));
+  EXPECT_EQ(Invocation.getDiagnosticOpts().DiagnosticSuppressionMappingsFile,
+            "foo.txt");
+
+  Invocation.generateCC1CommandLine(GeneratedArgs, *this);
+  EXPECT_THAT(GeneratedArgs, Contains(StrEq(Args[0])));
+}
 } // anonymous namespace

diff  --git a/llvm/include/llvm/Support/SpecialCaseList.h b/llvm/include/llvm/Support/SpecialCaseList.h
index 6dc1a29c5a281d..30e3fc644bbc33 100644
--- a/llvm/include/llvm/Support/SpecialCaseList.h
+++ b/llvm/include/llvm/Support/SpecialCaseList.h
@@ -122,7 +122,6 @@ class SpecialCaseList {
     // Returns zero if no match is found.
     unsigned match(StringRef Query) const;
 
-  private:
     StringMap<std::pair<GlobPattern, unsigned>> Globs;
     std::vector<std::pair<std::unique_ptr<Regex>, unsigned>> RegExes;
   };


        


More information about the cfe-commits mailing list