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