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   /// Report any errors to do with reading the configuration using this method.
103   DiagnosticBuilder
104   configurationDiag(StringRef Message,
105                     DiagnosticIDs::Level Level = DiagnosticIDs::Warning);
106 
107   /// Sets the \c SourceManager of the used \c DiagnosticsEngine.
108   ///
109   /// This is called from the \c ClangTidyCheck base class.
110   void setSourceManager(SourceManager *SourceMgr);
111 
112   /// Should be called when starting to process new translation unit.
113   void setCurrentFile(StringRef File);
114 
115   /// Returns the main file name of the current translation unit.
getCurrentFile()116   StringRef getCurrentFile() const { return CurrentFile; }
117 
118   /// Sets ASTContext for the current translation unit.
119   void setASTContext(ASTContext *Context);
120 
121   /// Gets the language options from the AST context.
getLangOpts()122   const LangOptions &getLangOpts() const { return LangOpts; }
123 
124   /// Returns the name of the clang-tidy check which produced this
125   /// diagnostic ID.
126   std::string getCheckName(unsigned DiagnosticID) const;
127 
128   /// Returns \c true if the check is enabled for the \c CurrentFile.
129   ///
130   /// The \c CurrentFile can be changed using \c setCurrentFile.
131   bool isCheckEnabled(StringRef CheckName) const;
132 
133   /// Returns \c true if the check should be upgraded to error for the
134   /// \c CurrentFile.
135   bool treatAsError(StringRef CheckName) const;
136 
137   /// Returns global options.
138   const ClangTidyGlobalOptions &getGlobalOptions() const;
139 
140   /// Returns options for \c CurrentFile.
141   ///
142   /// The \c CurrentFile can be changed using \c setCurrentFile.
143   const ClangTidyOptions &getOptions() const;
144 
145   /// Returns options for \c File. Does not change or depend on
146   /// \c CurrentFile.
147   ClangTidyOptions getOptionsForFile(StringRef File) const;
148 
149   /// Returns \c ClangTidyStats containing issued and ignored diagnostic
150   /// counters.
getStats()151   const ClangTidyStats &getStats() const { return Stats; }
152 
153   /// Control profile collection in clang-tidy.
154   void setEnableProfiling(bool Profile);
getEnableProfiling()155   bool getEnableProfiling() const { return Profile; }
156 
157   /// Control storage of profile date.
158   void setProfileStoragePrefix(StringRef ProfilePrefix);
159   llvm::Optional<ClangTidyProfiling::StorageParams>
160   getProfileStorageParams() const;
161 
162   /// Should be called when starting to process new translation unit.
setCurrentBuildDirectory(StringRef BuildDirectory)163   void setCurrentBuildDirectory(StringRef BuildDirectory) {
164     CurrentBuildDirectory = std::string(BuildDirectory);
165   }
166 
167   /// Returns build directory of the current translation unit.
getCurrentBuildDirectory()168   const std::string &getCurrentBuildDirectory() {
169     return CurrentBuildDirectory;
170   }
171 
172   /// If the experimental alpha checkers from the static analyzer can be
173   /// enabled.
canEnableAnalyzerAlphaCheckers()174   bool canEnableAnalyzerAlphaCheckers() const {
175     return AllowEnablingAnalyzerAlphaCheckers;
176   }
177 
178   using DiagLevelAndFormatString = std::pair<DiagnosticIDs::Level, std::string>;
getDiagLevelAndFormatString(unsigned DiagnosticID,SourceLocation Loc)179   DiagLevelAndFormatString getDiagLevelAndFormatString(unsigned DiagnosticID,
180                                                        SourceLocation Loc) {
181     return DiagLevelAndFormatString(
182         static_cast<DiagnosticIDs::Level>(
183             DiagEngine->getDiagnosticLevel(DiagnosticID, Loc)),
184         std::string(
185             DiagEngine->getDiagnosticIDs()->getDescription(DiagnosticID)));
186   }
187 
188 private:
189   // Writes to Stats.
190   friend class ClangTidyDiagnosticConsumer;
191 
192   DiagnosticsEngine *DiagEngine;
193   std::unique_ptr<ClangTidyOptionsProvider> OptionsProvider;
194 
195   std::string CurrentFile;
196   ClangTidyOptions CurrentOptions;
197   class CachedGlobList;
198   std::unique_ptr<CachedGlobList> CheckFilter;
199   std::unique_ptr<CachedGlobList> WarningAsErrorFilter;
200 
201   LangOptions LangOpts;
202 
203   ClangTidyStats Stats;
204 
205   std::string CurrentBuildDirectory;
206 
207   llvm::DenseMap<unsigned, std::string> CheckNamesByDiagnosticID;
208 
209   bool Profile;
210   std::string ProfilePrefix;
211 
212   bool AllowEnablingAnalyzerAlphaCheckers;
213 };
214 
215 /// Check whether a given diagnostic should be suppressed due to the presence
216 /// of a "NOLINT" suppression comment.
217 /// This is exposed so that other tools that present clang-tidy diagnostics
218 /// (such as clangd) can respect the same suppression rules as clang-tidy.
219 /// This does not handle suppression of notes following a suppressed diagnostic;
220 /// that is left to the caller is it requires maintaining state in between calls
221 /// to this function.
222 /// If `AllowIO` is false, the function does not attempt to read source files
223 /// from disk which are not already mapped into memory; such files are treated
224 /// as not containing a suppression comment.
225 bool shouldSuppressDiagnostic(DiagnosticsEngine::Level DiagLevel,
226                               const Diagnostic &Info, ClangTidyContext &Context,
227                               bool AllowIO = true);
228 
229 /// Gets the Fix attached to \p Diagnostic.
230 /// If there isn't a Fix attached to the diagnostic and \p AnyFix is true, Check
231 /// to see if exactly one note has a Fix and return it. Otherwise return
232 /// nullptr.
233 const llvm::StringMap<tooling::Replacements> *
234 getFixIt(const tooling::Diagnostic &Diagnostic, bool AnyFix);
235 
236 /// A diagnostic consumer that turns each \c Diagnostic into a
237 /// \c SourceManager-independent \c ClangTidyError.
238 //
239 // FIXME: If we move away from unit-tests, this can be moved to a private
240 // implementation file.
241 class ClangTidyDiagnosticConsumer : public DiagnosticConsumer {
242 public:
243   ClangTidyDiagnosticConsumer(ClangTidyContext &Ctx,
244                               DiagnosticsEngine *ExternalDiagEngine = nullptr,
245                               bool RemoveIncompatibleErrors = true,
246                               bool GetFixesFromNotes = false);
247 
248   // FIXME: The concept of converting between FixItHints and Replacements is
249   // more generic and should be pulled out into a more useful Diagnostics
250   // library.
251   void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,
252                         const Diagnostic &Info) override;
253 
254   // Retrieve the diagnostics that were captured.
255   std::vector<ClangTidyError> take();
256 
257 private:
258   void finalizeLastError();
259   void removeIncompatibleErrors();
260   void removeDuplicatedDiagnosticsOfAliasCheckers();
261 
262   /// Returns the \c HeaderFilter constructed for the options set in the
263   /// context.
264   llvm::Regex *getHeaderFilter();
265 
266   /// Updates \c LastErrorRelatesToUserCode and LastErrorPassesLineFilter
267   /// according to the diagnostic \p Location.
268   void checkFilters(SourceLocation Location, const SourceManager &Sources);
269   bool passesLineFilter(StringRef FileName, unsigned LineNumber) const;
270 
271   void forwardDiagnostic(const Diagnostic &Info);
272 
273   ClangTidyContext &Context;
274   DiagnosticsEngine *ExternalDiagEngine;
275   bool RemoveIncompatibleErrors;
276   bool GetFixesFromNotes;
277   std::vector<ClangTidyError> Errors;
278   std::unique_ptr<llvm::Regex> HeaderFilter;
279   bool LastErrorRelatesToUserCode;
280   bool LastErrorPassesLineFilter;
281   bool LastErrorWasIgnored;
282 };
283 
284 } // end namespace tidy
285 } // end namespace clang
286 
287 #endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CLANGTIDYDIAGNOSTICCONSUMER_H
288