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