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