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