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