1 //===--- ClangTidyDiagnosticConsumer.h - clang-tidy -------------*- C++ -*-===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYDIAGNOSTICCONSUMER_H
10 #define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYDIAGNOSTICCONSUMER_H
11 
12 #include "ClangTidyOptions.h"
13 #include "ClangTidyProfiling.h"
14 #include "FileExtensionsSet.h"
15 #include "NoLintDirectiveHandler.h"
16 #include "clang/Basic/Diagnostic.h"
17 #include "clang/Tooling/Core/Diagnostic.h"
18 #include "llvm/ADT/DenseMap.h"
19 #include "llvm/ADT/StringSet.h"
20 #include "llvm/Support/Regex.h"
21 #include <optional>
22 
23 namespace clang {
24 
25 class ASTContext;
26 class SourceManager;
27 
28 namespace tidy {
29 class CachedGlobList;
30 
31 /// A detected error complete with information to display diagnostic and
32 /// automatic fix.
33 ///
34 /// This is used as an intermediate format to transport Diagnostics without a
35 /// dependency on a SourceManager.
36 ///
37 /// FIXME: Make Diagnostics flexible enough to support this directly.
38 struct ClangTidyError : tooling::Diagnostic {
39   ClangTidyError(StringRef CheckName, Level DiagLevel, StringRef BuildDirectory,
40                  bool IsWarningAsError);
41 
42   bool IsWarningAsError;
43   std::vector<std::string> EnabledDiagnosticAliases;
44 };
45 
46 /// Contains displayed and ignored diagnostic counters for a ClangTidy run.
47 struct ClangTidyStats {
48   unsigned ErrorsDisplayed = 0;
49   unsigned ErrorsIgnoredCheckFilter = 0;
50   unsigned ErrorsIgnoredNOLINT = 0;
51   unsigned ErrorsIgnoredNonUserCode = 0;
52   unsigned ErrorsIgnoredLineFilter = 0;
53 
errorsIgnoredClangTidyStats54   unsigned errorsIgnored() const {
55     return ErrorsIgnoredNOLINT + ErrorsIgnoredCheckFilter +
56            ErrorsIgnoredNonUserCode + ErrorsIgnoredLineFilter;
57   }
58 };
59 
60 /// Every \c ClangTidyCheck reports errors through a \c DiagnosticsEngine
61 /// provided by this context.
62 ///
63 /// A \c ClangTidyCheck always has access to the active context to report
64 /// warnings like:
65 /// \code
66 /// Context->Diag(Loc, "Single-argument constructors must be explicit")
67 ///     << FixItHint::CreateInsertion(Loc, "explicit ");
68 /// \endcode
69 class ClangTidyContext {
70 public:
71   /// Initializes \c ClangTidyContext instance.
72   ClangTidyContext(std::unique_ptr<ClangTidyOptionsProvider> OptionsProvider,
73                    bool AllowEnablingAnalyzerAlphaCheckers = false,
74                    bool EnableModuleHeadersParsing = false);
75   /// Sets the DiagnosticsEngine that diag() will emit diagnostics to.
76   // FIXME: this is required initialization, and should be a constructor param.
77   // Fix the context -> diag engine -> consumer -> context initialization cycle.
setDiagnosticsEngine(DiagnosticsEngine * DiagEngine)78   void setDiagnosticsEngine(DiagnosticsEngine *DiagEngine) {
79     this->DiagEngine = DiagEngine;
80   }
81 
82   ~ClangTidyContext();
83 
84   /// Report any errors detected using this method.
85   ///
86   /// This is still under heavy development and will likely change towards using
87   /// tablegen'd diagnostic IDs.
88   /// FIXME: Figure out a way to manage ID spaces.
89   DiagnosticBuilder diag(StringRef CheckName, SourceLocation Loc,
90                          StringRef Description,
91                          DiagnosticIDs::Level Level = DiagnosticIDs::Warning);
92 
93   DiagnosticBuilder diag(StringRef CheckName, StringRef Description,
94                          DiagnosticIDs::Level Level = DiagnosticIDs::Warning);
95 
96   DiagnosticBuilder diag(const tooling::Diagnostic &Error);
97 
98   /// Report any errors to do with reading the configuration using this method.
99   DiagnosticBuilder
100   configurationDiag(StringRef Message,
101                     DiagnosticIDs::Level Level = DiagnosticIDs::Warning);
102 
103   /// Check whether a given diagnostic should be suppressed due to the presence
104   /// of a "NOLINT" suppression comment.
105   /// This is exposed so that other tools that present clang-tidy diagnostics
106   /// (such as clangd) can respect the same suppression rules as clang-tidy.
107   /// This does not handle suppression of notes following a suppressed
108   /// diagnostic; that is left to the caller as it requires maintaining state in
109   /// between calls to this function.
110   /// If any NOLINT is malformed, e.g. a BEGIN without a subsequent END, output
111   /// \param NoLintErrors will return an error about it.
112   /// If \param AllowIO is false, the function does not attempt to read source
113   /// files from disk which are not already mapped into memory; such files are
114   /// treated as not containing a suppression comment.
115   /// \param EnableNoLintBlocks controls whether to honor NOLINTBEGIN/NOLINTEND
116   /// blocks; if false, only considers line-level disabling.
117   bool
118   shouldSuppressDiagnostic(DiagnosticsEngine::Level DiagLevel,
119                            const Diagnostic &Info,
120                            SmallVectorImpl<tooling::Diagnostic> &NoLintErrors,
121                            bool AllowIO = true, bool EnableNoLintBlocks = true);
122 
123   /// Sets the \c SourceManager of the used \c DiagnosticsEngine.
124   ///
125   /// This is called from the \c ClangTidyCheck base class.
126   void setSourceManager(SourceManager *SourceMgr);
127 
128   /// Should be called when starting to process new translation unit.
129   void setCurrentFile(StringRef File);
130 
131   /// Returns the main file name of the current translation unit.
getCurrentFile()132   StringRef getCurrentFile() const { return CurrentFile; }
133 
134   /// Sets ASTContext for the current translation unit.
135   void setASTContext(ASTContext *Context);
136 
137   /// Gets the language options from the AST context.
getLangOpts()138   const LangOptions &getLangOpts() const { return LangOpts; }
139 
140   /// Returns the name of the clang-tidy check which produced this
141   /// diagnostic ID.
142   std::string getCheckName(unsigned DiagnosticID) const;
143 
144   /// Returns \c true if the check is enabled for the \c CurrentFile.
145   ///
146   /// The \c CurrentFile can be changed using \c setCurrentFile.
147   bool isCheckEnabled(StringRef CheckName) const;
148 
149   /// Returns \c true if the check should be upgraded to error for the
150   /// \c CurrentFile.
151   bool treatAsError(StringRef CheckName) const;
152 
153   /// Returns global options.
154   const ClangTidyGlobalOptions &getGlobalOptions() const;
155 
156   /// Returns options for \c CurrentFile.
157   ///
158   /// The \c CurrentFile can be changed using \c setCurrentFile.
159   const ClangTidyOptions &getOptions() const;
160 
161   /// Returns options for \c File. Does not change or depend on
162   /// \c CurrentFile.
163   ClangTidyOptions getOptionsForFile(StringRef File) const;
164 
getHeaderFileExtensions()165   const FileExtensionsSet &getHeaderFileExtensions() const {
166     return HeaderFileExtensions;
167   }
168 
getImplementationFileExtensions()169   const FileExtensionsSet &getImplementationFileExtensions() const {
170     return ImplementationFileExtensions;
171   }
172 
173   /// Returns \c ClangTidyStats containing issued and ignored diagnostic
174   /// counters.
getStats()175   const ClangTidyStats &getStats() const { return Stats; }
176 
177   /// Control profile collection in clang-tidy.
178   void setEnableProfiling(bool Profile);
getEnableProfiling()179   bool getEnableProfiling() const { return Profile; }
180 
181   /// Control storage of profile date.
182   void setProfileStoragePrefix(StringRef ProfilePrefix);
183   std::optional<ClangTidyProfiling::StorageParams>
184   getProfileStorageParams() const;
185 
186   /// Should be called when starting to process new translation unit.
setCurrentBuildDirectory(StringRef BuildDirectory)187   void setCurrentBuildDirectory(StringRef BuildDirectory) {
188     CurrentBuildDirectory = std::string(BuildDirectory);
189   }
190 
191   /// Returns build directory of the current translation unit.
getCurrentBuildDirectory()192   const std::string &getCurrentBuildDirectory() const {
193     return CurrentBuildDirectory;
194   }
195 
196   /// If the experimental alpha checkers from the static analyzer can be
197   /// enabled.
canEnableAnalyzerAlphaCheckers()198   bool canEnableAnalyzerAlphaCheckers() const {
199     return AllowEnablingAnalyzerAlphaCheckers;
200   }
201 
202   // This method determines whether preprocessor-level module header parsing is
203   // enabled using the `--experimental-enable-module-headers-parsing` option.
canEnableModuleHeadersParsing()204   bool canEnableModuleHeadersParsing() const {
205     return EnableModuleHeadersParsing;
206   }
207 
setSelfContainedDiags(bool Value)208   void setSelfContainedDiags(bool Value) { SelfContainedDiags = Value; }
209 
areDiagsSelfContained()210   bool areDiagsSelfContained() const { return SelfContainedDiags; }
211 
212   using DiagLevelAndFormatString = std::pair<DiagnosticIDs::Level, std::string>;
getDiagLevelAndFormatString(unsigned DiagnosticID,SourceLocation Loc)213   DiagLevelAndFormatString getDiagLevelAndFormatString(unsigned DiagnosticID,
214                                                        SourceLocation Loc) {
215     return {
216         static_cast<DiagnosticIDs::Level>(
217             DiagEngine->getDiagnosticLevel(DiagnosticID, Loc)),
218         std::string(
219             DiagEngine->getDiagnosticIDs()->getDescription(DiagnosticID))};
220   }
221 
setOptionsCollector(llvm::StringSet<> * Collector)222   void setOptionsCollector(llvm::StringSet<> *Collector) {
223     OptionsCollector = Collector;
224   }
getOptionsCollector()225   llvm::StringSet<> *getOptionsCollector() const { return OptionsCollector; }
226 
227 private:
228   // Writes to Stats.
229   friend class ClangTidyDiagnosticConsumer;
230 
231   DiagnosticsEngine *DiagEngine = nullptr;
232   std::unique_ptr<ClangTidyOptionsProvider> OptionsProvider;
233 
234   std::string CurrentFile;
235   ClangTidyOptions CurrentOptions;
236 
237   std::unique_ptr<CachedGlobList> CheckFilter;
238   std::unique_ptr<CachedGlobList> WarningAsErrorFilter;
239 
240   FileExtensionsSet HeaderFileExtensions;
241   FileExtensionsSet ImplementationFileExtensions;
242 
243   LangOptions LangOpts;
244 
245   ClangTidyStats Stats;
246 
247   std::string CurrentBuildDirectory;
248 
249   llvm::DenseMap<unsigned, std::string> CheckNamesByDiagnosticID;
250 
251   bool Profile = false;
252   std::string ProfilePrefix;
253 
254   bool AllowEnablingAnalyzerAlphaCheckers;
255   bool EnableModuleHeadersParsing;
256 
257   bool SelfContainedDiags = false;
258 
259   NoLintDirectiveHandler NoLintHandler;
260   llvm::StringSet<> *OptionsCollector = nullptr;
261 };
262 
263 /// Gets the Fix attached to \p Diagnostic.
264 /// If there isn't a Fix attached to the diagnostic and \p AnyFix is true, Check
265 /// to see if exactly one note has a Fix and return it. Otherwise return
266 /// nullptr.
267 const llvm::StringMap<tooling::Replacements> *
268 getFixIt(const tooling::Diagnostic &Diagnostic, bool AnyFix);
269 
270 /// A diagnostic consumer that turns each \c Diagnostic into a
271 /// \c SourceManager-independent \c ClangTidyError.
272 // FIXME: If we move away from unit-tests, this can be moved to a private
273 // implementation file.
274 class ClangTidyDiagnosticConsumer : public DiagnosticConsumer {
275 public:
276   /// \param EnableNolintBlocks Enables diagnostic-disabling inside blocks of
277   /// code, delimited by NOLINTBEGIN and NOLINTEND.
278   ClangTidyDiagnosticConsumer(ClangTidyContext &Ctx,
279                               DiagnosticsEngine *ExternalDiagEngine = nullptr,
280                               bool RemoveIncompatibleErrors = true,
281                               bool GetFixesFromNotes = false,
282                               bool EnableNolintBlocks = true);
283 
284   // FIXME: The concept of converting between FixItHints and Replacements is
285   // more generic and should be pulled out into a more useful Diagnostics
286   // library.
287   void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,
288                         const Diagnostic &Info) override;
289 
290   // Retrieve the diagnostics that were captured.
291   std::vector<ClangTidyError> take();
292 
293 private:
294   void finalizeLastError();
295   void removeIncompatibleErrors();
296   void removeDuplicatedDiagnosticsOfAliasCheckers();
297 
298   /// Returns the \c HeaderFilter constructed for the options set in the
299   /// context.
300   llvm::Regex *getHeaderFilter();
301 
302   /// Updates \c LastErrorRelatesToUserCode and LastErrorPassesLineFilter
303   /// according to the diagnostic \p Location.
304   void checkFilters(SourceLocation Location, const SourceManager &Sources);
305   bool passesLineFilter(StringRef FileName, unsigned LineNumber) const;
306 
307   void forwardDiagnostic(const Diagnostic &Info);
308 
309   ClangTidyContext &Context;
310   DiagnosticsEngine *ExternalDiagEngine;
311   bool RemoveIncompatibleErrors;
312   bool GetFixesFromNotes;
313   bool EnableNolintBlocks;
314   std::vector<ClangTidyError> Errors;
315   std::unique_ptr<llvm::Regex> HeaderFilter;
316   bool LastErrorRelatesToUserCode = false;
317   bool LastErrorPassesLineFilter = false;
318   bool LastErrorWasIgnored = false;
319 };
320 
321 } // end namespace tidy
322 } // end namespace clang
323 
324 #endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYDIAGNOSTICCONSUMER_H
325