1 | //===--- tools/extra/clang-tidy/ClangTidy.cpp - Clang tidy tool -----------===// |
---|---|
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 | /// \file This file implements a clang-tidy tool. |
10 | /// |
11 | /// This tool uses the Clang Tooling infrastructure, see |
12 | /// http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html |
13 | /// for details on setting it up with LLVM source tree. |
14 | /// |
15 | //===----------------------------------------------------------------------===// |
16 | |
17 | #include "ClangTidy.h" |
18 | #include "ClangTidyCheck.h" |
19 | #include "ClangTidyDiagnosticConsumer.h" |
20 | #include "ClangTidyModuleRegistry.h" |
21 | #include "ClangTidyProfiling.h" |
22 | #include "ExpandModularHeadersPPCallbacks.h" |
23 | #include "clang-tidy-config.h" |
24 | #include "clang/AST/ASTConsumer.h" |
25 | #include "clang/ASTMatchers/ASTMatchFinder.h" |
26 | #include "clang/Format/Format.h" |
27 | #include "clang/Frontend/ASTConsumers.h" |
28 | #include "clang/Frontend/CompilerInstance.h" |
29 | #include "clang/Frontend/FrontendDiagnostic.h" |
30 | #include "clang/Frontend/MultiplexConsumer.h" |
31 | #include "clang/Frontend/TextDiagnosticPrinter.h" |
32 | #include "clang/Lex/Preprocessor.h" |
33 | #include "clang/Lex/PreprocessorOptions.h" |
34 | #include "clang/Rewrite/Frontend/FixItRewriter.h" |
35 | #include "clang/Tooling/Core/Diagnostic.h" |
36 | #include "clang/Tooling/DiagnosticsYaml.h" |
37 | #include "clang/Tooling/Refactoring.h" |
38 | #include "clang/Tooling/Tooling.h" |
39 | #include "llvm/Support/Process.h" |
40 | #include <utility> |
41 | |
42 | #if CLANG_TIDY_ENABLE_STATIC_ANALYZER |
43 | #include "clang/Analysis/PathDiagnostic.h" |
44 | #include "clang/StaticAnalyzer/Frontend/AnalysisConsumer.h" |
45 | #endif // CLANG_TIDY_ENABLE_STATIC_ANALYZER |
46 | |
47 | using namespace clang::ast_matchers; |
48 | using namespace clang::driver; |
49 | using namespace clang::tooling; |
50 | using namespace llvm; |
51 | |
52 | LLVM_INSTANTIATE_REGISTRY(clang::tidy::ClangTidyModuleRegistry) |
53 | |
54 | namespace clang::tidy { |
55 | |
56 | namespace { |
57 | #if CLANG_TIDY_ENABLE_STATIC_ANALYZER |
58 | static const char *AnalyzerCheckNamePrefix = "clang-analyzer-"; |
59 | |
60 | class AnalyzerDiagnosticConsumer : public ento::PathDiagnosticConsumer { |
61 | public: |
62 | AnalyzerDiagnosticConsumer(ClangTidyContext &Context) : Context(Context) {} |
63 | |
64 | void FlushDiagnosticsImpl(std::vector<const ento::PathDiagnostic *> &Diags, |
65 | FilesMade *FilesMade) override { |
66 | for (const ento::PathDiagnostic *PD : Diags) { |
67 | SmallString<64> CheckName(AnalyzerCheckNamePrefix); |
68 | CheckName += PD->getCheckerName(); |
69 | Context.diag(CheckName, Loc: PD->getLocation().asLocation(), |
70 | Description: PD->getShortDescription()) |
71 | << PD->path.back()->getRanges(); |
72 | |
73 | for (const auto &DiagPiece : |
74 | PD->path.flatten(/*ShouldFlattenMacros=*/true)) { |
75 | Context.diag(CheckName, Loc: DiagPiece->getLocation().asLocation(), |
76 | Description: DiagPiece->getString(), Level: DiagnosticIDs::Note) |
77 | << DiagPiece->getRanges(); |
78 | } |
79 | } |
80 | } |
81 | |
82 | StringRef getName() const override { return "ClangTidyDiags"; } |
83 | bool supportsLogicalOpControlFlow() const override { return true; } |
84 | bool supportsCrossFileDiagnostics() const override { return true; } |
85 | |
86 | private: |
87 | ClangTidyContext &Context; |
88 | }; |
89 | #endif // CLANG_TIDY_ENABLE_STATIC_ANALYZER |
90 | |
91 | class ErrorReporter { |
92 | public: |
93 | ErrorReporter(ClangTidyContext &Context, FixBehaviour ApplyFixes, |
94 | llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> BaseFS) |
95 | : Files(FileSystemOptions(), std::move(BaseFS)), |
96 | DiagPrinter(new TextDiagnosticPrinter(llvm::outs(), DiagOpts)), |
97 | Diags(IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs), DiagOpts, |
98 | DiagPrinter), |
99 | SourceMgr(Diags, Files), Context(Context), ApplyFixes(ApplyFixes) { |
100 | DiagOpts.ShowColors = Context.getOptions().UseColor.value_or( |
101 | u: llvm::sys::Process::StandardOutHasColors()); |
102 | DiagPrinter->BeginSourceFile(LangOpts); |
103 | if (DiagOpts.ShowColors && !llvm::sys::Process::StandardOutIsDisplayed()) { |
104 | llvm::sys::Process::UseANSIEscapeCodes(enable: true); |
105 | } |
106 | } |
107 | |
108 | SourceManager &getSourceManager() { return SourceMgr; } |
109 | |
110 | void reportDiagnostic(const ClangTidyError &Error) { |
111 | const tooling::DiagnosticMessage &Message = Error.Message; |
112 | SourceLocation Loc = getLocation(FilePath: Message.FilePath, Offset: Message.FileOffset); |
113 | // Contains a pair for each attempted fix: location and whether the fix was |
114 | // applied successfully. |
115 | SmallVector<std::pair<SourceLocation, bool>, 4> FixLocations; |
116 | { |
117 | auto Level = static_cast<DiagnosticsEngine::Level>(Error.DiagLevel); |
118 | std::string Name = Error.DiagnosticName; |
119 | if (!Error.EnabledDiagnosticAliases.empty()) |
120 | Name += ","+ llvm::join(R: Error.EnabledDiagnosticAliases, Separator: ","); |
121 | if (Error.IsWarningAsError) { |
122 | Name += ",-warnings-as-errors"; |
123 | Level = DiagnosticsEngine::Error; |
124 | WarningsAsErrors++; |
125 | } |
126 | auto Diag = Diags.Report(Loc, DiagID: Diags.getCustomDiagID(L: Level, FormatString: "%0 [%1]")) |
127 | << Message.Message << Name; |
128 | for (const FileByteRange &FBR : Error.Message.Ranges) |
129 | Diag << getRange(Range: FBR); |
130 | // FIXME: explore options to support interactive fix selection. |
131 | const llvm::StringMap<Replacements> *ChosenFix = nullptr; |
132 | if (ApplyFixes != FB_NoFix && |
133 | (ChosenFix = getFixIt(Diagnostic: Error, AnyFix: ApplyFixes == FB_FixNotes))) { |
134 | for (const auto &FileAndReplacements : *ChosenFix) { |
135 | for (const auto &Repl : FileAndReplacements.second) { |
136 | ++TotalFixes; |
137 | bool CanBeApplied = false; |
138 | if (!Repl.isApplicable()) |
139 | continue; |
140 | SourceLocation FixLoc; |
141 | SmallString<128> FixAbsoluteFilePath = Repl.getFilePath(); |
142 | Files.makeAbsolutePath(Path&: FixAbsoluteFilePath); |
143 | tooling::Replacement R(FixAbsoluteFilePath, Repl.getOffset(), |
144 | Repl.getLength(), Repl.getReplacementText()); |
145 | auto &Entry = FileReplacements[R.getFilePath()]; |
146 | Replacements &Replacements = Entry.Replaces; |
147 | llvm::Error Err = Replacements.add(R); |
148 | if (Err) { |
149 | // FIXME: Implement better conflict handling. |
150 | llvm::errs() << "Trying to resolve conflict: " |
151 | << llvm::toString(E: std::move(Err)) << "\n"; |
152 | unsigned NewOffset = |
153 | Replacements.getShiftedCodePosition(Position: R.getOffset()); |
154 | unsigned NewLength = Replacements.getShiftedCodePosition( |
155 | Position: R.getOffset() + R.getLength()) - |
156 | NewOffset; |
157 | if (NewLength == R.getLength()) { |
158 | R = Replacement(R.getFilePath(), NewOffset, NewLength, |
159 | R.getReplacementText()); |
160 | Replacements = Replacements.merge(Replaces: tooling::Replacements(R)); |
161 | CanBeApplied = true; |
162 | ++AppliedFixes; |
163 | } else { |
164 | llvm::errs() |
165 | << "Can't resolve conflict, skipping the replacement.\n"; |
166 | } |
167 | } else { |
168 | CanBeApplied = true; |
169 | ++AppliedFixes; |
170 | } |
171 | FixLoc = getLocation(FilePath: FixAbsoluteFilePath, Offset: Repl.getOffset()); |
172 | FixLocations.push_back(Elt: std::make_pair(x&: FixLoc, y&: CanBeApplied)); |
173 | Entry.BuildDir = Error.BuildDirectory; |
174 | } |
175 | } |
176 | } |
177 | reportFix(Diag, Fix: Error.Message.Fix); |
178 | } |
179 | for (auto Fix : FixLocations) { |
180 | Diags.Report(Fix.first, Fix.second ? diag::note_fixit_applied |
181 | : diag::note_fixit_failed); |
182 | } |
183 | for (const auto &Note : Error.Notes) |
184 | reportNote(Message: Note); |
185 | } |
186 | |
187 | void finish() { |
188 | if (TotalFixes > 0) { |
189 | auto &VFS = Files.getVirtualFileSystem(); |
190 | auto OriginalCWD = VFS.getCurrentWorkingDirectory(); |
191 | bool AnyNotWritten = false; |
192 | |
193 | for (const auto &FileAndReplacements : FileReplacements) { |
194 | Rewriter Rewrite(SourceMgr, LangOpts); |
195 | StringRef File = FileAndReplacements.first(); |
196 | VFS.setCurrentWorkingDirectory(FileAndReplacements.second.BuildDir); |
197 | llvm::ErrorOr<std::unique_ptr<MemoryBuffer>> Buffer = |
198 | SourceMgr.getFileManager().getBufferForFile(Filename: File); |
199 | if (!Buffer) { |
200 | llvm::errs() << "Can't get buffer for file "<< File << ": " |
201 | << Buffer.getError().message() << "\n"; |
202 | // FIXME: Maybe don't apply fixes for other files as well. |
203 | continue; |
204 | } |
205 | StringRef Code = Buffer.get()->getBuffer(); |
206 | auto Style = format::getStyle( |
207 | StyleName: *Context.getOptionsForFile(File).FormatStyle, FileName: File, FallbackStyle: "none"); |
208 | if (!Style) { |
209 | llvm::errs() << llvm::toString(E: Style.takeError()) << "\n"; |
210 | continue; |
211 | } |
212 | llvm::Expected<tooling::Replacements> Replacements = |
213 | format::cleanupAroundReplacements( |
214 | Code, Replaces: FileAndReplacements.second.Replaces, Style: *Style); |
215 | if (!Replacements) { |
216 | llvm::errs() << llvm::toString(E: Replacements.takeError()) << "\n"; |
217 | continue; |
218 | } |
219 | if (llvm::Expected<tooling::Replacements> FormattedReplacements = |
220 | format::formatReplacements(Code, Replaces: *Replacements, Style: *Style)) { |
221 | Replacements = std::move(FormattedReplacements); |
222 | if (!Replacements) |
223 | llvm_unreachable("!Replacements"); |
224 | } else { |
225 | llvm::errs() << llvm::toString(E: FormattedReplacements.takeError()) |
226 | << ". Skipping formatting.\n"; |
227 | } |
228 | if (!tooling::applyAllReplacements(Replaces: Replacements.get(), Rewrite)) { |
229 | llvm::errs() << "Can't apply replacements for file "<< File << "\n"; |
230 | } |
231 | AnyNotWritten |= Rewrite.overwriteChangedFiles(); |
232 | } |
233 | |
234 | if (AnyNotWritten) { |
235 | llvm::errs() << "clang-tidy failed to apply suggested fixes.\n"; |
236 | } else { |
237 | llvm::errs() << "clang-tidy applied "<< AppliedFixes << " of " |
238 | << TotalFixes << " suggested fixes.\n"; |
239 | } |
240 | |
241 | if (OriginalCWD) |
242 | VFS.setCurrentWorkingDirectory(*OriginalCWD); |
243 | } |
244 | } |
245 | |
246 | unsigned getWarningsAsErrorsCount() const { return WarningsAsErrors; } |
247 | |
248 | private: |
249 | SourceLocation getLocation(StringRef FilePath, unsigned Offset) { |
250 | if (FilePath.empty()) |
251 | return {}; |
252 | |
253 | auto File = SourceMgr.getFileManager().getOptionalFileRef(Filename: FilePath); |
254 | if (!File) |
255 | return {}; |
256 | |
257 | FileID ID = SourceMgr.getOrCreateFileID(SourceFile: *File, FileCharacter: SrcMgr::C_User); |
258 | return SourceMgr.getLocForStartOfFile(FID: ID).getLocWithOffset(Offset); |
259 | } |
260 | |
261 | void reportFix(const DiagnosticBuilder &Diag, |
262 | const llvm::StringMap<Replacements> &Fix) { |
263 | for (const auto &FileAndReplacements : Fix) { |
264 | for (const auto &Repl : FileAndReplacements.second) { |
265 | if (!Repl.isApplicable()) |
266 | continue; |
267 | FileByteRange FBR; |
268 | FBR.FilePath = Repl.getFilePath().str(); |
269 | FBR.FileOffset = Repl.getOffset(); |
270 | FBR.Length = Repl.getLength(); |
271 | |
272 | Diag << FixItHint::CreateReplacement(RemoveRange: getRange(Range: FBR), |
273 | Code: Repl.getReplacementText()); |
274 | } |
275 | } |
276 | } |
277 | |
278 | void reportNote(const tooling::DiagnosticMessage &Message) { |
279 | SourceLocation Loc = getLocation(FilePath: Message.FilePath, Offset: Message.FileOffset); |
280 | auto Diag = |
281 | Diags.Report(Loc, DiagID: Diags.getCustomDiagID(L: DiagnosticsEngine::Note, FormatString: "%0")) |
282 | << Message.Message; |
283 | for (const FileByteRange &FBR : Message.Ranges) |
284 | Diag << getRange(Range: FBR); |
285 | reportFix(Diag, Fix: Message.Fix); |
286 | } |
287 | |
288 | CharSourceRange getRange(const FileByteRange &Range) { |
289 | SmallString<128> AbsoluteFilePath{Range.FilePath}; |
290 | Files.makeAbsolutePath(Path&: AbsoluteFilePath); |
291 | SourceLocation BeginLoc = getLocation(FilePath: AbsoluteFilePath, Offset: Range.FileOffset); |
292 | SourceLocation EndLoc = BeginLoc.getLocWithOffset(Offset: Range.Length); |
293 | // Retrieve the source range for applicable highlights and fixes. Macro |
294 | // definition on the command line have locations in a virtual buffer and |
295 | // don't have valid file paths and are therefore not applicable. |
296 | return CharSourceRange::getCharRange(B: BeginLoc, E: EndLoc); |
297 | } |
298 | |
299 | struct ReplacementsWithBuildDir { |
300 | StringRef BuildDir; |
301 | Replacements Replaces; |
302 | }; |
303 | |
304 | FileManager Files; |
305 | LangOptions LangOpts; // FIXME: use langopts from each original file |
306 | DiagnosticOptions DiagOpts; |
307 | DiagnosticConsumer *DiagPrinter; |
308 | DiagnosticsEngine Diags; |
309 | SourceManager SourceMgr; |
310 | llvm::StringMap<ReplacementsWithBuildDir> FileReplacements; |
311 | ClangTidyContext &Context; |
312 | FixBehaviour ApplyFixes; |
313 | unsigned TotalFixes = 0U; |
314 | unsigned AppliedFixes = 0U; |
315 | unsigned WarningsAsErrors = 0U; |
316 | }; |
317 | |
318 | class ClangTidyASTConsumer : public MultiplexConsumer { |
319 | public: |
320 | ClangTidyASTConsumer(std::vector<std::unique_ptr<ASTConsumer>> Consumers, |
321 | std::unique_ptr<ClangTidyProfiling> Profiling, |
322 | std::unique_ptr<ast_matchers::MatchFinder> Finder, |
323 | std::vector<std::unique_ptr<ClangTidyCheck>> Checks) |
324 | : MultiplexConsumer(std::move(Consumers)), |
325 | Profiling(std::move(Profiling)), Finder(std::move(Finder)), |
326 | Checks(std::move(Checks)) {} |
327 | |
328 | private: |
329 | // Destructor order matters! Profiling must be destructed last. |
330 | // Or at least after Finder. |
331 | std::unique_ptr<ClangTidyProfiling> Profiling; |
332 | std::unique_ptr<ast_matchers::MatchFinder> Finder; |
333 | std::vector<std::unique_ptr<ClangTidyCheck>> Checks; |
334 | void anchor() override {}; |
335 | }; |
336 | |
337 | } // namespace |
338 | |
339 | ClangTidyASTConsumerFactory::ClangTidyASTConsumerFactory( |
340 | ClangTidyContext &Context, |
341 | IntrusiveRefCntPtr<llvm::vfs::OverlayFileSystem> OverlayFS) |
342 | : Context(Context), OverlayFS(std::move(OverlayFS)), |
343 | CheckFactories(new ClangTidyCheckFactories) { |
344 | for (ClangTidyModuleRegistry::entry E : ClangTidyModuleRegistry::entries()) { |
345 | std::unique_ptr<ClangTidyModule> Module = E.instantiate(); |
346 | Module->addCheckFactories(CheckFactories&: *CheckFactories); |
347 | } |
348 | } |
349 | |
350 | #if CLANG_TIDY_ENABLE_STATIC_ANALYZER |
351 | static void |
352 | setStaticAnalyzerCheckerOpts(const ClangTidyOptions &Opts, |
353 | clang::AnalyzerOptions &AnalyzerOptions) { |
354 | StringRef AnalyzerPrefix(AnalyzerCheckNamePrefix); |
355 | for (const auto &Opt : Opts.CheckOptions) { |
356 | StringRef OptName(Opt.getKey()); |
357 | if (!OptName.consume_front(Prefix: AnalyzerPrefix)) |
358 | continue; |
359 | // Analyzer options are always local options so we can ignore priority. |
360 | AnalyzerOptions.Config[OptName] = Opt.getValue().Value; |
361 | } |
362 | } |
363 | |
364 | using CheckersList = std::vector<std::pair<std::string, bool>>; |
365 | |
366 | static CheckersList getAnalyzerCheckersAndPackages(ClangTidyContext &Context, |
367 | bool IncludeExperimental) { |
368 | CheckersList List; |
369 | |
370 | const auto &RegisteredCheckers = |
371 | AnalyzerOptions::getRegisteredCheckers(IncludeExperimental); |
372 | const bool AnalyzerChecksEnabled = |
373 | llvm::any_of(Range: RegisteredCheckers, P: [&](StringRef CheckName) -> bool { |
374 | return Context.isCheckEnabled( |
375 | CheckName: (AnalyzerCheckNamePrefix + CheckName).str()); |
376 | }); |
377 | |
378 | if (!AnalyzerChecksEnabled) |
379 | return List; |
380 | |
381 | // List all static analyzer checkers that our filter enables. |
382 | // |
383 | // Always add all core checkers if any other static analyzer check is enabled. |
384 | // This is currently necessary, as other path sensitive checks rely on the |
385 | // core checkers. |
386 | for (StringRef CheckName : RegisteredCheckers) { |
387 | std::string ClangTidyCheckName((AnalyzerCheckNamePrefix + CheckName).str()); |
388 | |
389 | if (CheckName.starts_with(Prefix: "core") || |
390 | Context.isCheckEnabled(CheckName: ClangTidyCheckName)) { |
391 | List.emplace_back(args: std::string(CheckName), args: true); |
392 | } |
393 | } |
394 | return List; |
395 | } |
396 | #endif // CLANG_TIDY_ENABLE_STATIC_ANALYZER |
397 | |
398 | std::unique_ptr<clang::ASTConsumer> |
399 | ClangTidyASTConsumerFactory::createASTConsumer( |
400 | clang::CompilerInstance &Compiler, StringRef File) { |
401 | // FIXME: Move this to a separate method, so that CreateASTConsumer doesn't |
402 | // modify Compiler. |
403 | SourceManager *SM = &Compiler.getSourceManager(); |
404 | Context.setSourceManager(SM); |
405 | Context.setCurrentFile(File); |
406 | Context.setASTContext(&Compiler.getASTContext()); |
407 | |
408 | auto WorkingDir = Compiler.getSourceManager() |
409 | .getFileManager() |
410 | .getVirtualFileSystem() |
411 | .getCurrentWorkingDirectory(); |
412 | if (WorkingDir) |
413 | Context.setCurrentBuildDirectory(WorkingDir.get()); |
414 | |
415 | std::vector<std::unique_ptr<ClangTidyCheck>> Checks = |
416 | CheckFactories->createChecksForLanguage(Context: &Context); |
417 | |
418 | ast_matchers::MatchFinder::MatchFinderOptions FinderOptions; |
419 | |
420 | std::unique_ptr<ClangTidyProfiling> Profiling; |
421 | if (Context.getEnableProfiling()) { |
422 | Profiling = |
423 | std::make_unique<ClangTidyProfiling>(args: Context.getProfileStorageParams()); |
424 | FinderOptions.CheckProfiling.emplace(args&: Profiling->Records); |
425 | } |
426 | |
427 | std::unique_ptr<ast_matchers::MatchFinder> Finder( |
428 | new ast_matchers::MatchFinder(std::move(FinderOptions))); |
429 | |
430 | Preprocessor *PP = &Compiler.getPreprocessor(); |
431 | Preprocessor *ModuleExpanderPP = PP; |
432 | |
433 | if (Context.canEnableModuleHeadersParsing() && |
434 | Context.getLangOpts().Modules && OverlayFS != nullptr) { |
435 | auto ModuleExpander = |
436 | std::make_unique<ExpandModularHeadersPPCallbacks>(args: &Compiler, args&: OverlayFS); |
437 | ModuleExpanderPP = ModuleExpander->getPreprocessor(); |
438 | PP->addPPCallbacks(C: std::move(ModuleExpander)); |
439 | } |
440 | |
441 | for (auto &Check : Checks) { |
442 | Check->registerMatchers(Finder: &*Finder); |
443 | Check->registerPPCallbacks(SM: *SM, PP, ModuleExpanderPP); |
444 | } |
445 | |
446 | std::vector<std::unique_ptr<ASTConsumer>> Consumers; |
447 | if (!Checks.empty()) |
448 | Consumers.push_back(x: Finder->newASTConsumer()); |
449 | |
450 | #if CLANG_TIDY_ENABLE_STATIC_ANALYZER |
451 | AnalyzerOptions &AnalyzerOptions = Compiler.getAnalyzerOpts(); |
452 | AnalyzerOptions.CheckersAndPackages = getAnalyzerCheckersAndPackages( |
453 | Context, IncludeExperimental: Context.canEnableAnalyzerAlphaCheckers()); |
454 | if (!AnalyzerOptions.CheckersAndPackages.empty()) { |
455 | setStaticAnalyzerCheckerOpts(Opts: Context.getOptions(), AnalyzerOptions); |
456 | AnalyzerOptions.AnalysisDiagOpt = PD_NONE; |
457 | std::unique_ptr<ento::AnalysisASTConsumer> AnalysisConsumer = |
458 | ento::CreateAnalysisConsumer(CI&: Compiler); |
459 | AnalysisConsumer->AddDiagnosticConsumer( |
460 | Consumer: std::make_unique<AnalyzerDiagnosticConsumer>(args&: Context)); |
461 | Consumers.push_back(x: std::move(AnalysisConsumer)); |
462 | } |
463 | #endif // CLANG_TIDY_ENABLE_STATIC_ANALYZER |
464 | return std::make_unique<ClangTidyASTConsumer>( |
465 | args: std::move(Consumers), args: std::move(Profiling), args: std::move(Finder), |
466 | args: std::move(Checks)); |
467 | } |
468 | |
469 | std::vector<std::string> ClangTidyASTConsumerFactory::getCheckNames() { |
470 | std::vector<std::string> CheckNames; |
471 | for (const auto &CheckFactory : *CheckFactories) { |
472 | if (Context.isCheckEnabled(CheckName: CheckFactory.getKey())) |
473 | CheckNames.emplace_back(args: CheckFactory.getKey()); |
474 | } |
475 | |
476 | #if CLANG_TIDY_ENABLE_STATIC_ANALYZER |
477 | for (const auto &AnalyzerCheck : getAnalyzerCheckersAndPackages( |
478 | Context, IncludeExperimental: Context.canEnableAnalyzerAlphaCheckers())) |
479 | CheckNames.push_back(x: AnalyzerCheckNamePrefix + AnalyzerCheck.first); |
480 | #endif // CLANG_TIDY_ENABLE_STATIC_ANALYZER |
481 | |
482 | llvm::sort(C&: CheckNames); |
483 | return CheckNames; |
484 | } |
485 | |
486 | ClangTidyOptions::OptionMap ClangTidyASTConsumerFactory::getCheckOptions() { |
487 | ClangTidyOptions::OptionMap Options; |
488 | std::vector<std::unique_ptr<ClangTidyCheck>> Checks = |
489 | CheckFactories->createChecks(Context: &Context); |
490 | for (const auto &Check : Checks) |
491 | Check->storeOptions(Options); |
492 | return Options; |
493 | } |
494 | |
495 | std::vector<std::string> |
496 | getCheckNames(const ClangTidyOptions &Options, |
497 | bool AllowEnablingAnalyzerAlphaCheckers) { |
498 | clang::tidy::ClangTidyContext Context( |
499 | std::make_unique<DefaultOptionsProvider>(args: ClangTidyGlobalOptions(), |
500 | args: Options), |
501 | AllowEnablingAnalyzerAlphaCheckers); |
502 | ClangTidyASTConsumerFactory Factory(Context); |
503 | return Factory.getCheckNames(); |
504 | } |
505 | |
506 | ClangTidyOptions::OptionMap |
507 | getCheckOptions(const ClangTidyOptions &Options, |
508 | bool AllowEnablingAnalyzerAlphaCheckers) { |
509 | clang::tidy::ClangTidyContext Context( |
510 | std::make_unique<DefaultOptionsProvider>(args: ClangTidyGlobalOptions(), |
511 | args: Options), |
512 | AllowEnablingAnalyzerAlphaCheckers); |
513 | ClangTidyDiagnosticConsumer DiagConsumer(Context); |
514 | auto DiagOpts = std::make_unique<DiagnosticOptions>(); |
515 | DiagnosticsEngine DE(llvm::makeIntrusiveRefCnt<DiagnosticIDs>(), *DiagOpts, |
516 | &DiagConsumer, /*ShouldOwnClient=*/false); |
517 | Context.setDiagnosticsEngine(DiagOpts: std::move(DiagOpts), DiagEngine: &DE); |
518 | ClangTidyASTConsumerFactory Factory(Context); |
519 | return Factory.getCheckOptions(); |
520 | } |
521 | |
522 | std::vector<ClangTidyError> |
523 | runClangTidy(clang::tidy::ClangTidyContext &Context, |
524 | const CompilationDatabase &Compilations, |
525 | ArrayRef<std::string> InputFiles, |
526 | llvm::IntrusiveRefCntPtr<llvm::vfs::OverlayFileSystem> BaseFS, |
527 | bool ApplyAnyFix, bool EnableCheckProfile, |
528 | llvm::StringRef StoreCheckProfile) { |
529 | ClangTool Tool(Compilations, InputFiles, |
530 | std::make_shared<PCHContainerOperations>(), BaseFS); |
531 | |
532 | // Add extra arguments passed by the clang-tidy command-line. |
533 | ArgumentsAdjuster PerFileExtraArgumentsInserter = |
534 | [&Context](const CommandLineArguments &Args, StringRef Filename) { |
535 | ClangTidyOptions Opts = Context.getOptionsForFile(File: Filename); |
536 | CommandLineArguments AdjustedArgs = Args; |
537 | if (Opts.ExtraArgsBefore) { |
538 | auto I = AdjustedArgs.begin(); |
539 | if (I != AdjustedArgs.end() && !StringRef(*I).starts_with(Prefix: "-")) |
540 | ++I; // Skip compiler binary name, if it is there. |
541 | AdjustedArgs.insert(position: I, first: Opts.ExtraArgsBefore->begin(), |
542 | last: Opts.ExtraArgsBefore->end()); |
543 | } |
544 | if (Opts.ExtraArgs) |
545 | AdjustedArgs.insert(position: AdjustedArgs.end(), first: Opts.ExtraArgs->begin(), |
546 | last: Opts.ExtraArgs->end()); |
547 | return AdjustedArgs; |
548 | }; |
549 | |
550 | Tool.appendArgumentsAdjuster(Adjuster: PerFileExtraArgumentsInserter); |
551 | Tool.appendArgumentsAdjuster(Adjuster: getStripPluginsAdjuster()); |
552 | Context.setEnableProfiling(EnableCheckProfile); |
553 | Context.setProfileStoragePrefix(StoreCheckProfile); |
554 | |
555 | ClangTidyDiagnosticConsumer DiagConsumer(Context, nullptr, true, ApplyAnyFix); |
556 | auto DiagOpts = std::make_unique<DiagnosticOptions>(); |
557 | DiagnosticsEngine DE(new DiagnosticIDs(), *DiagOpts, &DiagConsumer, |
558 | /*ShouldOwnClient=*/false); |
559 | Context.setDiagnosticsEngine(DiagOpts: std::move(DiagOpts), DiagEngine: &DE); |
560 | Tool.setDiagnosticConsumer(&DiagConsumer); |
561 | |
562 | class ActionFactory : public FrontendActionFactory { |
563 | public: |
564 | ActionFactory(ClangTidyContext &Context, |
565 | IntrusiveRefCntPtr<llvm::vfs::OverlayFileSystem> BaseFS) |
566 | : ConsumerFactory(Context, std::move(BaseFS)) {} |
567 | std::unique_ptr<FrontendAction> create() override { |
568 | return std::make_unique<Action>(args: &ConsumerFactory); |
569 | } |
570 | |
571 | bool runInvocation(std::shared_ptr<CompilerInvocation> Invocation, |
572 | FileManager *Files, |
573 | std::shared_ptr<PCHContainerOperations> PCHContainerOps, |
574 | DiagnosticConsumer *DiagConsumer) override { |
575 | // Explicitly ask to define __clang_analyzer__ macro. |
576 | Invocation->getPreprocessorOpts().SetUpStaticAnalyzer = true; |
577 | return FrontendActionFactory::runInvocation( |
578 | Invocation, Files, PCHContainerOps, DiagConsumer); |
579 | } |
580 | |
581 | private: |
582 | class Action : public ASTFrontendAction { |
583 | public: |
584 | Action(ClangTidyASTConsumerFactory *Factory) : Factory(Factory) {} |
585 | std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &Compiler, |
586 | StringRef File) override { |
587 | return Factory->createASTConsumer(Compiler, File); |
588 | } |
589 | |
590 | private: |
591 | ClangTidyASTConsumerFactory *Factory; |
592 | }; |
593 | |
594 | ClangTidyASTConsumerFactory ConsumerFactory; |
595 | }; |
596 | |
597 | ActionFactory Factory(Context, std::move(BaseFS)); |
598 | Tool.run(Action: &Factory); |
599 | return DiagConsumer.take(); |
600 | } |
601 | |
602 | void handleErrors(llvm::ArrayRef<ClangTidyError> Errors, |
603 | ClangTidyContext &Context, FixBehaviour Fix, |
604 | unsigned &WarningsAsErrorsCount, |
605 | llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> BaseFS) { |
606 | ErrorReporter Reporter(Context, Fix, std::move(BaseFS)); |
607 | llvm::vfs::FileSystem &FileSystem = |
608 | Reporter.getSourceManager().getFileManager().getVirtualFileSystem(); |
609 | auto InitialWorkingDir = FileSystem.getCurrentWorkingDirectory(); |
610 | if (!InitialWorkingDir) |
611 | llvm::report_fatal_error(reason: "Cannot get current working path."); |
612 | |
613 | for (const ClangTidyError &Error : Errors) { |
614 | if (!Error.BuildDirectory.empty()) { |
615 | // By default, the working directory of file system is the current |
616 | // clang-tidy running directory. |
617 | // |
618 | // Change the directory to the one used during the analysis. |
619 | FileSystem.setCurrentWorkingDirectory(Error.BuildDirectory); |
620 | } |
621 | Reporter.reportDiagnostic(Error); |
622 | // Return to the initial directory to correctly resolve next Error. |
623 | FileSystem.setCurrentWorkingDirectory(InitialWorkingDir.get()); |
624 | } |
625 | Reporter.finish(); |
626 | WarningsAsErrorsCount += Reporter.getWarningsAsErrorsCount(); |
627 | } |
628 | |
629 | void exportReplacements(const llvm::StringRef MainFilePath, |
630 | const std::vector<ClangTidyError> &Errors, |
631 | raw_ostream &OS) { |
632 | TranslationUnitDiagnostics TUD; |
633 | TUD.MainSourceFile = std::string(MainFilePath); |
634 | for (const auto &Error : Errors) { |
635 | tooling::Diagnostic Diag = Error; |
636 | if (Error.IsWarningAsError) |
637 | Diag.DiagLevel = tooling::Diagnostic::Error; |
638 | TUD.Diagnostics.insert(position: TUD.Diagnostics.end(), x: Diag); |
639 | } |
640 | |
641 | yaml::Output YAML(OS); |
642 | YAML << TUD; |
643 | } |
644 | |
645 | ChecksAndOptions |
646 | getAllChecksAndOptions(bool AllowEnablingAnalyzerAlphaCheckers) { |
647 | ChecksAndOptions Result; |
648 | ClangTidyOptions Opts; |
649 | Opts.Checks = "*"; |
650 | clang::tidy::ClangTidyContext Context( |
651 | std::make_unique<DefaultOptionsProvider>(args: ClangTidyGlobalOptions(), args&: Opts), |
652 | AllowEnablingAnalyzerAlphaCheckers); |
653 | ClangTidyCheckFactories Factories; |
654 | for (const ClangTidyModuleRegistry::entry &Module : |
655 | ClangTidyModuleRegistry::entries()) { |
656 | Module.instantiate()->addCheckFactories(CheckFactories&: Factories); |
657 | } |
658 | |
659 | for (const auto &Factory : Factories) |
660 | Result.Checks.insert(key: Factory.getKey()); |
661 | |
662 | #if CLANG_TIDY_ENABLE_STATIC_ANALYZER |
663 | SmallString<64> Buffer(AnalyzerCheckNamePrefix); |
664 | size_t DefSize = Buffer.size(); |
665 | for (const auto &AnalyzerCheck : AnalyzerOptions::getRegisteredCheckers( |
666 | IncludeExperimental: AllowEnablingAnalyzerAlphaCheckers)) { |
667 | Buffer.truncate(N: DefSize); |
668 | Buffer.append(RHS: AnalyzerCheck); |
669 | Result.Checks.insert(key: Buffer); |
670 | } |
671 | for (std::string OptionName : { |
672 | #define GET_CHECKER_OPTIONS |
673 | #define CHECKER_OPTION(TYPE, CHECKER, OPTION_NAME, DESCRIPTION, DEFAULT, \ |
674 | RELEASE, HIDDEN) \ |
675 | Twine(AnalyzerCheckNamePrefix).concat(CHECKER ":" OPTION_NAME).str(), |
676 | |
677 | #include "clang/StaticAnalyzer/Checkers/Checkers.inc" |
678 | #undef CHECKER_OPTION |
679 | #undef GET_CHECKER_OPTIONS |
680 | }) { |
681 | Result.Options.insert(OptionName); |
682 | } |
683 | #endif // CLANG_TIDY_ENABLE_STATIC_ANALYZER |
684 | |
685 | Context.setOptionsCollector(&Result.Options); |
686 | for (const auto &Factory : Factories) { |
687 | Factory.getValue()(Factory.getKey(), &Context); |
688 | } |
689 | |
690 | return Result; |
691 | } |
692 | } // namespace clang::tidy |
693 |
Definitions
- AnalyzerCheckNamePrefix
- AnalyzerDiagnosticConsumer
- AnalyzerDiagnosticConsumer
- FlushDiagnosticsImpl
- getName
- supportsLogicalOpControlFlow
- supportsCrossFileDiagnostics
- ErrorReporter
- ErrorReporter
- getSourceManager
- reportDiagnostic
- finish
- getWarningsAsErrorsCount
- getLocation
- reportFix
- reportNote
- getRange
- ReplacementsWithBuildDir
- ClangTidyASTConsumer
- ClangTidyASTConsumer
- anchor
- ClangTidyASTConsumerFactory
- setStaticAnalyzerCheckerOpts
- getAnalyzerCheckersAndPackages
- createASTConsumer
- getCheckNames
- getCheckOptions
- getCheckNames
- getCheckOptions
- runClangTidy
- handleErrors
- exportReplacements
Improve your Profiling and Debugging skills
Find out more