[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