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