Reapply "[clang] Introduce diagnostics suppression mappings (#112517)"

This reverts commit 5f140ba54794fe6ca379362b133eb27780e363d7.
This commit is contained in:
Kadir Cetinkaya 2024-11-13 09:34:23 +01:00
parent 42da81582e
commit 5845688e91
No known key found for this signature in database
GPG Key ID: E39E36B8D2057ED6
28 changed files with 637 additions and 34 deletions

View File

@ -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;

View File

@ -872,6 +872,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
^^^^^^^^^^^^^^^^^^^

View File

@ -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 at a
per-file granularity. This allows enforcing 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
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -0,0 +1,97 @@
============================
Warning suppression mappings
============================
.. contents::
:local:
Introduction
============
Warning suppression mappings enable users to suppress Clang's diagnostics at a
per-file granularity. This allows enforcing 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/*

View File

@ -22,6 +22,7 @@ Using Clang as a Compiler
ClangCommandLineReference
AttributeReference
DiagnosticsReference
WarningSuppressionMappings
CrossCompilation
ClangStaticAnalyzer
ThreadSafetyAnalysis

View File

@ -20,9 +20,9 @@
#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"
#include "llvm/ADT/iterator_range.h"
#include "llvm/Support/Compiler.h"
#include <cassert>
@ -40,6 +40,10 @@
namespace llvm {
class Error;
class raw_ostream;
class MemoryBuffer;
namespace vfs {
class FileSystem;
} // namespace vfs
} // namespace llvm
namespace clang {
@ -555,6 +559,10 @@ private:
void *ArgToStringCookie = nullptr;
ArgToStringFnTy ArgToStringFn;
/// Whether the diagnostic should be suppressed in FilePath.
llvm::unique_function<bool(diag::kind, StringRef /*FilePath*/) const>
DiagSuppressionMapping;
public:
explicit DiagnosticsEngine(IntrusiveRefCntPtr<DiagnosticIDs> Diags,
IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts,
@ -946,6 +954,26 @@ public:
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, StringRef FilePath) const;
/// Issue the message to the client.
///
/// This actually returns an instance of DiagnosticBuilder which emits the
@ -1759,7 +1787,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

View File

@ -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">;
}

View File

@ -108,6 +108,9 @@ public:
/// 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;

View File

@ -965,6 +965,10 @@ def V : JoinedOrSeparate<["-"], "V">, Flags<[NoXarchOption, Unsupported]>;
def Wa_COMMA : CommaJoined<["-"], "Wa,">,
HelpText<"Pass the comma separated arguments in <arg> to the assembler">,
MetaVarName<"<arg>">;
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]>;
def Wall : Flag<["-"], "Wall">, Group<W_Group>, Flags<[HelpHidden]>,
Visibility<[ClangOption, CC1Option, FlangOption]>;
def WCL4 : Flag<["-"], "WCL4">, Group<W_Group>, Flags<[HelpHidden]>,

View File

@ -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 @@ public:
/// 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.
///

View File

@ -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,138 @@ 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, 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,
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) {
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;
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) {
// FIXME: Use a `%select` statement instead of printing `Error` as-is. This
// should help localization.
Report(diag::err_drv_malformed_warning_suppression_mapping)
<< Input.getBufferIdentifier() << Error;
return;
}
WarningSuppressionList->processSections(*this);
DiagSuppressionMapping =
[WarningSuppressionList(std::move(WarningSuppressionList))](
diag::kind DiagId, StringRef Path) {
return WarningSuppressionList->isDiagSuppressed(DiagId, Path);
};
}
bool WarningsSpecialCaseList::isDiagSuppressed(diag::kind DiagId,
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,
StringRef FilePath) const {
StringRef LongestMatch;
bool LongestIsPositive = false;
for (const auto &Entry : CategoriesToMatchers) {
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,
StringRef FilePath) const {
return DiagSuppressionMapping && DiagSuppressionMapping(DiagId, FilePath);
}
void DiagnosticsEngine::Report(const StoredDiagnostic &storedDiag) {
DiagnosticStorage DiagStorage;
DiagStorage.DiagRanges.append(storedDiag.range_begin(),

View File

@ -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() && Diag.DiagSuppressionMapping) {
// 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;
}

View File

@ -23,10 +23,12 @@
// 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/VirtualFileSystem.h"
#include <cstring>
#include <utility>
using namespace clang;
// EmitUnknownDiagWarning - Emit a warning and typo hint for unknown warning
@ -43,6 +45,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 +73,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();

View File

@ -4514,6 +4514,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,

View File

@ -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()) {

View File

@ -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;
}

View File

@ -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);

View File

@ -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);

View File

@ -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

View File

@ -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");

View File

@ -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

View File

@ -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
}

View File

@ -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}}
}
}

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -122,7 +122,6 @@ protected:
// 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;
};