Author: Kadir Cetinkaya Date: 2024-11-13T10:35:22+01:00 New Revision: 5845688e91d85d46c0f47daaf4edfdfc772853cf
URL: https://github.com/llvm/llvm-project/commit/5845688e91d85d46c0f47daaf4edfdfc772853cf DIFF: https://github.com/llvm/llvm-project/commit/5845688e91d85d46c0f47daaf4edfdfc772853cf.diff LOG: Reapply "[clang] Introduce diagnostics suppression mappings (#112517)" This reverts commit 5f140ba54794fe6ca379362b133eb27780e363d7. 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 fef086c5a99d86f..4c34f9ea122d9e0 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 ffed972ed120d06..5bff58c64de1627 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -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 ^^^^^^^^^^^^^^^^^^^ diff --git a/clang/docs/UsersManual.rst b/clang/docs/UsersManual.rst index 4f03388bc87bd0f..2ef603d64557f31 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 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 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/clang/docs/WarningSuppressionMappings.rst b/clang/docs/WarningSuppressionMappings.rst new file mode 100644 index 000000000000000..d96341ac6e563bc --- /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 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/* diff --git a/clang/docs/index.rst b/clang/docs/index.rst index 3c473f93e5224a3..cc070059eede5db 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 3b1efdb12824c7a..d271accca3d3b79 100644 --- a/clang/include/clang/Basic/Diagnostic.h +++ b/clang/include/clang/Basic/Diagnostic.h @@ -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 @@ class DiagnosticsEngine : public RefCountedBase<DiagnosticsEngine> { 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 @@ 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, 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 diff --git a/clang/include/clang/Basic/DiagnosticDriverKinds.td b/clang/include/clang/Basic/DiagnosticDriverKinds.td index cdfdaa01fb121dc..76fdbdbfb01d94b 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 30141c2b8f44754..e2faf3d0df95cb2 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 9fb7f8bb6489b06..0a94a7185df8c71 100644 --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -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]>, diff --git a/clang/include/clang/Frontend/CompilerInstance.h b/clang/include/clang/Frontend/CompilerInstance.h index 3464654284f199c..338eb3c6bd028d3 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 c63ee70fb4d512a..2d0e358116362c0 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,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(), diff --git a/clang/lib/Basic/DiagnosticIDs.cpp b/clang/lib/Basic/DiagnosticIDs.cpp index fc8bd29faa080fe..44922aa7872dbf7 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() && 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; } diff --git a/clang/lib/Basic/Warnings.cpp b/clang/lib/Basic/Warnings.cpp index 5a5ac5556338867..da0304463007b65 100644 --- a/clang/lib/Basic/Warnings.cpp +++ b/clang/lib/Basic/Warnings.cpp @@ -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(); diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp index 0952262c360185e..8ca2d2006d6fb06 100644 --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -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, diff --git a/clang/lib/Frontend/ASTUnit.cpp b/clang/lib/Frontend/ASTUnit.cpp index f58e27a7979d1e4..8df5465ad990dab 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 240305b33824b81..ecc6782c7cb4fb8 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 b5fd35aaa1e8418..e3145dcacf58d1a 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 cab5838fceb24d9..d1bac09830b04b0 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 791426807cb91d1..bbc8830d76bc00b 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 873d12390699ddd..8b928ede395ae5e 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 000000000000000..abe4fde0c265d52 --- /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 000000000000000..58a2537477166bd --- /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 000000000000000..6e86bf9c0f8f182 --- /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 be081cac8c03b13..df59b53f9ef1866 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 686eaea0aa7c836..12038de476ace1a 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 d8d23e3b670097d..36a77c7247655fa 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 7912253b761e9ba..45478de5e6f7c8a 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 6dc1a29c5a281d0..30e3fc644bbc336 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; }; _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits