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