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