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