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