mirror of
https://github.com/capstone-engine/llvm-capstone.git
synced 2025-01-11 10:26:44 +00:00
39c4246e1e
Summary: Motivated by [[ https://bugs.llvm.org/show_bug.cgi?id=45045 | Tune inspections to a specific C++ standard. ]] Moves the isLanguageVersionSupported virtual function from `MakeSmartPtrCheck` to the base `ClangTidyCheck` class. This will disable registering matchers or pp callbacks on unsupported language versions for a check. Having it as a standalone function is cleaner than manually disabling the check in the register function and should hopefully encourage check developers to actually restrict the check based on language version. As an added bonus this could enable automatic detection of what language version a check runs on for the purpose of documentation generation Reviewers: aaron.ballman, gribozavr2, Eugene.Zelenko, JonasToth, alexfh, hokein Reviewed By: gribozavr2 Subscribers: xazax.hun, jkorous, arphaman, kadircet, usaxena95, cfe-commits Tags: #clang Differential Revision: https://reviews.llvm.org/D75289
613 lines
24 KiB
C++
613 lines
24 KiB
C++
//===--- tools/extra/clang-tidy/ClangTidy.cpp - Clang tidy tool -----------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
///
|
|
/// \file This file implements a clang-tidy tool.
|
|
///
|
|
/// This tool uses the Clang Tooling infrastructure, see
|
|
/// http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html
|
|
/// for details on setting it up with LLVM source tree.
|
|
///
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "ClangTidy.h"
|
|
#include "ClangTidyDiagnosticConsumer.h"
|
|
#include "ClangTidyModuleRegistry.h"
|
|
#include "ClangTidyProfiling.h"
|
|
#include "ExpandModularHeadersPPCallbacks.h"
|
|
#include "clang/AST/ASTConsumer.h"
|
|
#include "clang/AST/ASTContext.h"
|
|
#include "clang/AST/Decl.h"
|
|
#include "clang/ASTMatchers/ASTMatchFinder.h"
|
|
#include "clang/Config/config.h"
|
|
#include "clang/Format/Format.h"
|
|
#include "clang/Frontend/ASTConsumers.h"
|
|
#include "clang/Frontend/CompilerInstance.h"
|
|
#include "clang/Frontend/FrontendActions.h"
|
|
#include "clang/Frontend/FrontendDiagnostic.h"
|
|
#include "clang/Frontend/MultiplexConsumer.h"
|
|
#include "clang/Frontend/TextDiagnosticPrinter.h"
|
|
#include "clang/Lex/PPCallbacks.h"
|
|
#include "clang/Lex/Preprocessor.h"
|
|
#include "clang/Lex/PreprocessorOptions.h"
|
|
#include "clang/Rewrite/Frontend/FixItRewriter.h"
|
|
#include "clang/Rewrite/Frontend/FrontendActions.h"
|
|
#include "clang/Tooling/Core/Diagnostic.h"
|
|
#include "clang/Tooling/DiagnosticsYaml.h"
|
|
#include "clang/Tooling/Refactoring.h"
|
|
#include "clang/Tooling/ReplacementsYaml.h"
|
|
#include "clang/Tooling/Tooling.h"
|
|
#include "llvm/Support/Process.h"
|
|
#include "llvm/Support/Signals.h"
|
|
#include <algorithm>
|
|
#include <utility>
|
|
|
|
#if CLANG_ENABLE_STATIC_ANALYZER
|
|
#include "clang/Analysis/PathDiagnostic.h"
|
|
#include "clang/StaticAnalyzer/Frontend/AnalysisConsumer.h"
|
|
#endif // CLANG_ENABLE_STATIC_ANALYZER
|
|
|
|
using namespace clang::ast_matchers;
|
|
using namespace clang::driver;
|
|
using namespace clang::tooling;
|
|
using namespace llvm;
|
|
|
|
LLVM_INSTANTIATE_REGISTRY(clang::tidy::ClangTidyModuleRegistry)
|
|
|
|
namespace clang {
|
|
namespace tidy {
|
|
|
|
namespace {
|
|
#if CLANG_ENABLE_STATIC_ANALYZER
|
|
static const char *AnalyzerCheckNamePrefix = "clang-analyzer-";
|
|
|
|
class AnalyzerDiagnosticConsumer : public ento::PathDiagnosticConsumer {
|
|
public:
|
|
AnalyzerDiagnosticConsumer(ClangTidyContext &Context) : Context(Context) {}
|
|
|
|
void FlushDiagnosticsImpl(std::vector<const ento::PathDiagnostic *> &Diags,
|
|
FilesMade *filesMade) override {
|
|
for (const ento::PathDiagnostic *PD : Diags) {
|
|
SmallString<64> CheckName(AnalyzerCheckNamePrefix);
|
|
CheckName += PD->getCheckerName();
|
|
Context.diag(CheckName, PD->getLocation().asLocation(),
|
|
PD->getShortDescription())
|
|
<< PD->path.back()->getRanges();
|
|
|
|
for (const auto &DiagPiece :
|
|
PD->path.flatten(/*ShouldFlattenMacros=*/true)) {
|
|
Context.diag(CheckName, DiagPiece->getLocation().asLocation(),
|
|
DiagPiece->getString(), DiagnosticIDs::Note)
|
|
<< DiagPiece->getRanges();
|
|
}
|
|
}
|
|
}
|
|
|
|
StringRef getName() const override { return "ClangTidyDiags"; }
|
|
bool supportsLogicalOpControlFlow() const override { return true; }
|
|
bool supportsCrossFileDiagnostics() const override { return true; }
|
|
|
|
private:
|
|
ClangTidyContext &Context;
|
|
};
|
|
#endif // CLANG_ENABLE_STATIC_ANALYZER
|
|
|
|
class ErrorReporter {
|
|
public:
|
|
ErrorReporter(ClangTidyContext &Context, bool ApplyFixes,
|
|
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> BaseFS)
|
|
: Files(FileSystemOptions(), BaseFS), DiagOpts(new DiagnosticOptions()),
|
|
DiagPrinter(new TextDiagnosticPrinter(llvm::outs(), &*DiagOpts)),
|
|
Diags(IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs), &*DiagOpts,
|
|
DiagPrinter),
|
|
SourceMgr(Diags, Files), Context(Context), ApplyFixes(ApplyFixes),
|
|
TotalFixes(0), AppliedFixes(0), WarningsAsErrors(0) {
|
|
DiagOpts->ShowColors = llvm::sys::Process::StandardOutHasColors();
|
|
DiagPrinter->BeginSourceFile(LangOpts);
|
|
}
|
|
|
|
SourceManager &getSourceManager() { return SourceMgr; }
|
|
|
|
void reportDiagnostic(const ClangTidyError &Error) {
|
|
const tooling::DiagnosticMessage &Message = Error.Message;
|
|
SourceLocation Loc = getLocation(Message.FilePath, Message.FileOffset);
|
|
// Contains a pair for each attempted fix: location and whether the fix was
|
|
// applied successfully.
|
|
SmallVector<std::pair<SourceLocation, bool>, 4> FixLocations;
|
|
{
|
|
auto Level = static_cast<DiagnosticsEngine::Level>(Error.DiagLevel);
|
|
std::string Name = Error.DiagnosticName;
|
|
if (Error.IsWarningAsError) {
|
|
Name += ",-warnings-as-errors";
|
|
Level = DiagnosticsEngine::Error;
|
|
WarningsAsErrors++;
|
|
}
|
|
auto Diag = Diags.Report(Loc, Diags.getCustomDiagID(Level, "%0 [%1]"))
|
|
<< Message.Message << Name;
|
|
// FIXME: explore options to support interactive fix selection.
|
|
const llvm::StringMap<Replacements> *ChosenFix = selectFirstFix(Error);
|
|
if (ApplyFixes && ChosenFix) {
|
|
for (const auto &FileAndReplacements : *ChosenFix) {
|
|
for (const auto &Repl : FileAndReplacements.second) {
|
|
++TotalFixes;
|
|
bool CanBeApplied = false;
|
|
if (!Repl.isApplicable())
|
|
continue;
|
|
SourceLocation FixLoc;
|
|
SmallString<128> FixAbsoluteFilePath = Repl.getFilePath();
|
|
Files.makeAbsolutePath(FixAbsoluteFilePath);
|
|
tooling::Replacement R(FixAbsoluteFilePath, Repl.getOffset(),
|
|
Repl.getLength(), Repl.getReplacementText());
|
|
Replacements &Replacements = FileReplacements[R.getFilePath()];
|
|
llvm::Error Err = Replacements.add(R);
|
|
if (Err) {
|
|
// FIXME: Implement better conflict handling.
|
|
llvm::errs() << "Trying to resolve conflict: "
|
|
<< llvm::toString(std::move(Err)) << "\n";
|
|
unsigned NewOffset =
|
|
Replacements.getShiftedCodePosition(R.getOffset());
|
|
unsigned NewLength = Replacements.getShiftedCodePosition(
|
|
R.getOffset() + R.getLength()) -
|
|
NewOffset;
|
|
if (NewLength == R.getLength()) {
|
|
R = Replacement(R.getFilePath(), NewOffset, NewLength,
|
|
R.getReplacementText());
|
|
Replacements = Replacements.merge(tooling::Replacements(R));
|
|
CanBeApplied = true;
|
|
++AppliedFixes;
|
|
} else {
|
|
llvm::errs()
|
|
<< "Can't resolve conflict, skipping the replacement.\n";
|
|
}
|
|
} else {
|
|
CanBeApplied = true;
|
|
++AppliedFixes;
|
|
}
|
|
FixLoc = getLocation(FixAbsoluteFilePath, Repl.getOffset());
|
|
FixLocations.push_back(std::make_pair(FixLoc, CanBeApplied));
|
|
}
|
|
}
|
|
}
|
|
reportFix(Diag, Error.Message.Fix);
|
|
}
|
|
for (auto Fix : FixLocations) {
|
|
Diags.Report(Fix.first, Fix.second ? diag::note_fixit_applied
|
|
: diag::note_fixit_failed);
|
|
}
|
|
for (const auto &Note : Error.Notes)
|
|
reportNote(Note);
|
|
}
|
|
|
|
void Finish() {
|
|
if (ApplyFixes && TotalFixes > 0) {
|
|
Rewriter Rewrite(SourceMgr, LangOpts);
|
|
for (const auto &FileAndReplacements : FileReplacements) {
|
|
StringRef File = FileAndReplacements.first();
|
|
llvm::ErrorOr<std::unique_ptr<MemoryBuffer>> Buffer =
|
|
SourceMgr.getFileManager().getBufferForFile(File);
|
|
if (!Buffer) {
|
|
llvm::errs() << "Can't get buffer for file " << File << ": "
|
|
<< Buffer.getError().message() << "\n";
|
|
// FIXME: Maybe don't apply fixes for other files as well.
|
|
continue;
|
|
}
|
|
StringRef Code = Buffer.get()->getBuffer();
|
|
auto Style = format::getStyle(
|
|
*Context.getOptionsForFile(File).FormatStyle, File, "none");
|
|
if (!Style) {
|
|
llvm::errs() << llvm::toString(Style.takeError()) << "\n";
|
|
continue;
|
|
}
|
|
llvm::Expected<tooling::Replacements> Replacements =
|
|
format::cleanupAroundReplacements(Code, FileAndReplacements.second,
|
|
*Style);
|
|
if (!Replacements) {
|
|
llvm::errs() << llvm::toString(Replacements.takeError()) << "\n";
|
|
continue;
|
|
}
|
|
if (llvm::Expected<tooling::Replacements> FormattedReplacements =
|
|
format::formatReplacements(Code, *Replacements, *Style)) {
|
|
Replacements = std::move(FormattedReplacements);
|
|
if (!Replacements)
|
|
llvm_unreachable("!Replacements");
|
|
} else {
|
|
llvm::errs() << llvm::toString(FormattedReplacements.takeError())
|
|
<< ". Skipping formatting.\n";
|
|
}
|
|
if (!tooling::applyAllReplacements(Replacements.get(), Rewrite)) {
|
|
llvm::errs() << "Can't apply replacements for file " << File << "\n";
|
|
}
|
|
}
|
|
if (Rewrite.overwriteChangedFiles()) {
|
|
llvm::errs() << "clang-tidy failed to apply suggested fixes.\n";
|
|
} else {
|
|
llvm::errs() << "clang-tidy applied " << AppliedFixes << " of "
|
|
<< TotalFixes << " suggested fixes.\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
unsigned getWarningsAsErrorsCount() const { return WarningsAsErrors; }
|
|
|
|
private:
|
|
SourceLocation getLocation(StringRef FilePath, unsigned Offset) {
|
|
if (FilePath.empty())
|
|
return SourceLocation();
|
|
|
|
auto File = SourceMgr.getFileManager().getFile(FilePath);
|
|
if (!File)
|
|
return SourceLocation();
|
|
|
|
FileID ID = SourceMgr.getOrCreateFileID(*File, SrcMgr::C_User);
|
|
return SourceMgr.getLocForStartOfFile(ID).getLocWithOffset(Offset);
|
|
}
|
|
|
|
void reportFix(const DiagnosticBuilder &Diag,
|
|
const llvm::StringMap<Replacements> &Fix) {
|
|
for (const auto &FileAndReplacements : Fix) {
|
|
for (const auto &Repl : FileAndReplacements.second) {
|
|
if (!Repl.isApplicable())
|
|
continue;
|
|
SmallString<128> FixAbsoluteFilePath = Repl.getFilePath();
|
|
Files.makeAbsolutePath(FixAbsoluteFilePath);
|
|
SourceLocation FixLoc =
|
|
getLocation(FixAbsoluteFilePath, Repl.getOffset());
|
|
SourceLocation FixEndLoc = FixLoc.getLocWithOffset(Repl.getLength());
|
|
// Retrieve the source range for applicable fixes. Macro definitions
|
|
// on the command line have locations in a virtual buffer and don't
|
|
// have valid file paths and are therefore not applicable.
|
|
CharSourceRange Range =
|
|
CharSourceRange::getCharRange(SourceRange(FixLoc, FixEndLoc));
|
|
Diag << FixItHint::CreateReplacement(Range, Repl.getReplacementText());
|
|
}
|
|
}
|
|
}
|
|
|
|
void reportNote(const tooling::DiagnosticMessage &Message) {
|
|
SourceLocation Loc = getLocation(Message.FilePath, Message.FileOffset);
|
|
auto Diag =
|
|
Diags.Report(Loc, Diags.getCustomDiagID(DiagnosticsEngine::Note, "%0"))
|
|
<< Message.Message;
|
|
reportFix(Diag, Message.Fix);
|
|
}
|
|
|
|
FileManager Files;
|
|
LangOptions LangOpts; // FIXME: use langopts from each original file
|
|
IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts;
|
|
DiagnosticConsumer *DiagPrinter;
|
|
DiagnosticsEngine Diags;
|
|
SourceManager SourceMgr;
|
|
llvm::StringMap<Replacements> FileReplacements;
|
|
ClangTidyContext &Context;
|
|
bool ApplyFixes;
|
|
unsigned TotalFixes;
|
|
unsigned AppliedFixes;
|
|
unsigned WarningsAsErrors;
|
|
};
|
|
|
|
class ClangTidyASTConsumer : public MultiplexConsumer {
|
|
public:
|
|
ClangTidyASTConsumer(std::vector<std::unique_ptr<ASTConsumer>> Consumers,
|
|
std::unique_ptr<ClangTidyProfiling> Profiling,
|
|
std::unique_ptr<ast_matchers::MatchFinder> Finder,
|
|
std::vector<std::unique_ptr<ClangTidyCheck>> Checks)
|
|
: MultiplexConsumer(std::move(Consumers)),
|
|
Profiling(std::move(Profiling)), Finder(std::move(Finder)),
|
|
Checks(std::move(Checks)) {}
|
|
|
|
private:
|
|
// Destructor order matters! Profiling must be destructed last.
|
|
// Or at least after Finder.
|
|
std::unique_ptr<ClangTidyProfiling> Profiling;
|
|
std::unique_ptr<ast_matchers::MatchFinder> Finder;
|
|
std::vector<std::unique_ptr<ClangTidyCheck>> Checks;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
ClangTidyASTConsumerFactory::ClangTidyASTConsumerFactory(
|
|
ClangTidyContext &Context,
|
|
IntrusiveRefCntPtr<llvm::vfs::OverlayFileSystem> OverlayFS)
|
|
: Context(Context), OverlayFS(OverlayFS),
|
|
CheckFactories(new ClangTidyCheckFactories) {
|
|
for (ClangTidyModuleRegistry::entry E : ClangTidyModuleRegistry::entries()) {
|
|
std::unique_ptr<ClangTidyModule> Module = E.instantiate();
|
|
Module->addCheckFactories(*CheckFactories);
|
|
}
|
|
}
|
|
|
|
#if CLANG_ENABLE_STATIC_ANALYZER
|
|
static void setStaticAnalyzerCheckerOpts(const ClangTidyOptions &Opts,
|
|
AnalyzerOptionsRef AnalyzerOptions) {
|
|
StringRef AnalyzerPrefix(AnalyzerCheckNamePrefix);
|
|
for (const auto &Opt : Opts.CheckOptions) {
|
|
StringRef OptName(Opt.first);
|
|
if (!OptName.startswith(AnalyzerPrefix))
|
|
continue;
|
|
AnalyzerOptions->Config[OptName.substr(AnalyzerPrefix.size())] = Opt.second;
|
|
}
|
|
}
|
|
|
|
typedef std::vector<std::pair<std::string, bool>> CheckersList;
|
|
|
|
static CheckersList getAnalyzerCheckersAndPackages(ClangTidyContext &Context,
|
|
bool IncludeExperimental) {
|
|
CheckersList List;
|
|
|
|
const auto &RegisteredCheckers =
|
|
AnalyzerOptions::getRegisteredCheckers(IncludeExperimental);
|
|
bool AnalyzerChecksEnabled = false;
|
|
for (StringRef CheckName : RegisteredCheckers) {
|
|
std::string ClangTidyCheckName((AnalyzerCheckNamePrefix + CheckName).str());
|
|
AnalyzerChecksEnabled |= Context.isCheckEnabled(ClangTidyCheckName);
|
|
}
|
|
|
|
if (!AnalyzerChecksEnabled)
|
|
return List;
|
|
|
|
// List all static analyzer checkers that our filter enables.
|
|
//
|
|
// Always add all core checkers if any other static analyzer check is enabled.
|
|
// This is currently necessary, as other path sensitive checks rely on the
|
|
// core checkers.
|
|
for (StringRef CheckName : RegisteredCheckers) {
|
|
std::string ClangTidyCheckName((AnalyzerCheckNamePrefix + CheckName).str());
|
|
|
|
if (CheckName.startswith("core") ||
|
|
Context.isCheckEnabled(ClangTidyCheckName)) {
|
|
List.emplace_back(std::string(CheckName), true);
|
|
}
|
|
}
|
|
return List;
|
|
}
|
|
#endif // CLANG_ENABLE_STATIC_ANALYZER
|
|
|
|
std::unique_ptr<clang::ASTConsumer>
|
|
ClangTidyASTConsumerFactory::CreateASTConsumer(
|
|
clang::CompilerInstance &Compiler, StringRef File) {
|
|
// FIXME: Move this to a separate method, so that CreateASTConsumer doesn't
|
|
// modify Compiler.
|
|
SourceManager *SM = &Compiler.getSourceManager();
|
|
Context.setSourceManager(SM);
|
|
Context.setCurrentFile(File);
|
|
Context.setASTContext(&Compiler.getASTContext());
|
|
|
|
auto WorkingDir = Compiler.getSourceManager()
|
|
.getFileManager()
|
|
.getVirtualFileSystem()
|
|
.getCurrentWorkingDirectory();
|
|
if (WorkingDir)
|
|
Context.setCurrentBuildDirectory(WorkingDir.get());
|
|
|
|
std::vector<std::unique_ptr<ClangTidyCheck>> Checks =
|
|
CheckFactories->createChecks(&Context);
|
|
|
|
ast_matchers::MatchFinder::MatchFinderOptions FinderOptions;
|
|
|
|
std::unique_ptr<ClangTidyProfiling> Profiling;
|
|
if (Context.getEnableProfiling()) {
|
|
Profiling = std::make_unique<ClangTidyProfiling>(
|
|
Context.getProfileStorageParams());
|
|
FinderOptions.CheckProfiling.emplace(Profiling->Records);
|
|
}
|
|
|
|
std::unique_ptr<ast_matchers::MatchFinder> Finder(
|
|
new ast_matchers::MatchFinder(std::move(FinderOptions)));
|
|
|
|
Preprocessor *PP = &Compiler.getPreprocessor();
|
|
Preprocessor *ModuleExpanderPP = PP;
|
|
|
|
if (Context.getLangOpts().Modules && OverlayFS != nullptr) {
|
|
auto ModuleExpander = std::make_unique<ExpandModularHeadersPPCallbacks>(
|
|
&Compiler, OverlayFS);
|
|
ModuleExpanderPP = ModuleExpander->getPreprocessor();
|
|
PP->addPPCallbacks(std::move(ModuleExpander));
|
|
}
|
|
|
|
for (auto &Check : Checks) {
|
|
if (!Check->isLanguageVersionSupported(Context.getLangOpts()))
|
|
continue;
|
|
Check->registerMatchers(&*Finder);
|
|
Check->registerPPCallbacks(*SM, PP, ModuleExpanderPP);
|
|
}
|
|
|
|
std::vector<std::unique_ptr<ASTConsumer>> Consumers;
|
|
if (!Checks.empty())
|
|
Consumers.push_back(Finder->newASTConsumer());
|
|
|
|
#if CLANG_ENABLE_STATIC_ANALYZER
|
|
AnalyzerOptionsRef AnalyzerOptions = Compiler.getAnalyzerOpts();
|
|
AnalyzerOptions->CheckersAndPackages = getAnalyzerCheckersAndPackages(
|
|
Context, Context.canEnableAnalyzerAlphaCheckers());
|
|
if (!AnalyzerOptions->CheckersAndPackages.empty()) {
|
|
setStaticAnalyzerCheckerOpts(Context.getOptions(), AnalyzerOptions);
|
|
AnalyzerOptions->AnalysisStoreOpt = RegionStoreModel;
|
|
AnalyzerOptions->AnalysisDiagOpt = PD_NONE;
|
|
AnalyzerOptions->AnalyzeNestedBlocks = true;
|
|
AnalyzerOptions->eagerlyAssumeBinOpBifurcation = true;
|
|
std::unique_ptr<ento::AnalysisASTConsumer> AnalysisConsumer =
|
|
ento::CreateAnalysisConsumer(Compiler);
|
|
AnalysisConsumer->AddDiagnosticConsumer(
|
|
new AnalyzerDiagnosticConsumer(Context));
|
|
Consumers.push_back(std::move(AnalysisConsumer));
|
|
}
|
|
#endif // CLANG_ENABLE_STATIC_ANALYZER
|
|
return std::make_unique<ClangTidyASTConsumer>(
|
|
std::move(Consumers), std::move(Profiling), std::move(Finder),
|
|
std::move(Checks));
|
|
}
|
|
|
|
std::vector<std::string> ClangTidyASTConsumerFactory::getCheckNames() {
|
|
std::vector<std::string> CheckNames;
|
|
for (const auto &CheckFactory : *CheckFactories) {
|
|
if (Context.isCheckEnabled(CheckFactory.first))
|
|
CheckNames.push_back(CheckFactory.first);
|
|
}
|
|
|
|
#if CLANG_ENABLE_STATIC_ANALYZER
|
|
for (const auto &AnalyzerCheck : getAnalyzerCheckersAndPackages(
|
|
Context, Context.canEnableAnalyzerAlphaCheckers()))
|
|
CheckNames.push_back(AnalyzerCheckNamePrefix + AnalyzerCheck.first);
|
|
#endif // CLANG_ENABLE_STATIC_ANALYZER
|
|
|
|
std::sort(CheckNames.begin(), CheckNames.end());
|
|
return CheckNames;
|
|
}
|
|
|
|
ClangTidyOptions::OptionMap ClangTidyASTConsumerFactory::getCheckOptions() {
|
|
ClangTidyOptions::OptionMap Options;
|
|
std::vector<std::unique_ptr<ClangTidyCheck>> Checks =
|
|
CheckFactories->createChecks(&Context);
|
|
for (const auto &Check : Checks)
|
|
Check->storeOptions(Options);
|
|
return Options;
|
|
}
|
|
|
|
std::vector<std::string>
|
|
getCheckNames(const ClangTidyOptions &Options,
|
|
bool AllowEnablingAnalyzerAlphaCheckers) {
|
|
clang::tidy::ClangTidyContext Context(
|
|
std::make_unique<DefaultOptionsProvider>(ClangTidyGlobalOptions(),
|
|
Options),
|
|
AllowEnablingAnalyzerAlphaCheckers);
|
|
ClangTidyASTConsumerFactory Factory(Context);
|
|
return Factory.getCheckNames();
|
|
}
|
|
|
|
ClangTidyOptions::OptionMap
|
|
getCheckOptions(const ClangTidyOptions &Options,
|
|
bool AllowEnablingAnalyzerAlphaCheckers) {
|
|
clang::tidy::ClangTidyContext Context(
|
|
std::make_unique<DefaultOptionsProvider>(ClangTidyGlobalOptions(),
|
|
Options),
|
|
AllowEnablingAnalyzerAlphaCheckers);
|
|
ClangTidyASTConsumerFactory Factory(Context);
|
|
return Factory.getCheckOptions();
|
|
}
|
|
|
|
std::vector<ClangTidyError>
|
|
runClangTidy(clang::tidy::ClangTidyContext &Context,
|
|
const CompilationDatabase &Compilations,
|
|
ArrayRef<std::string> InputFiles,
|
|
llvm::IntrusiveRefCntPtr<llvm::vfs::OverlayFileSystem> BaseFS,
|
|
bool EnableCheckProfile, llvm::StringRef StoreCheckProfile) {
|
|
ClangTool Tool(Compilations, InputFiles,
|
|
std::make_shared<PCHContainerOperations>(), BaseFS);
|
|
|
|
// Add extra arguments passed by the clang-tidy command-line.
|
|
ArgumentsAdjuster PerFileExtraArgumentsInserter =
|
|
[&Context](const CommandLineArguments &Args, StringRef Filename) {
|
|
ClangTidyOptions Opts = Context.getOptionsForFile(Filename);
|
|
CommandLineArguments AdjustedArgs = Args;
|
|
if (Opts.ExtraArgsBefore) {
|
|
auto I = AdjustedArgs.begin();
|
|
if (I != AdjustedArgs.end() && !StringRef(*I).startswith("-"))
|
|
++I; // Skip compiler binary name, if it is there.
|
|
AdjustedArgs.insert(I, Opts.ExtraArgsBefore->begin(),
|
|
Opts.ExtraArgsBefore->end());
|
|
}
|
|
if (Opts.ExtraArgs)
|
|
AdjustedArgs.insert(AdjustedArgs.end(), Opts.ExtraArgs->begin(),
|
|
Opts.ExtraArgs->end());
|
|
return AdjustedArgs;
|
|
};
|
|
|
|
Tool.appendArgumentsAdjuster(PerFileExtraArgumentsInserter);
|
|
Tool.appendArgumentsAdjuster(getStripPluginsAdjuster());
|
|
Context.setEnableProfiling(EnableCheckProfile);
|
|
Context.setProfileStoragePrefix(StoreCheckProfile);
|
|
|
|
ClangTidyDiagnosticConsumer DiagConsumer(Context);
|
|
DiagnosticsEngine DE(new DiagnosticIDs(), new DiagnosticOptions(),
|
|
&DiagConsumer, /*ShouldOwnClient=*/false);
|
|
Context.setDiagnosticsEngine(&DE);
|
|
Tool.setDiagnosticConsumer(&DiagConsumer);
|
|
|
|
class ActionFactory : public FrontendActionFactory {
|
|
public:
|
|
ActionFactory(ClangTidyContext &Context,
|
|
IntrusiveRefCntPtr<llvm::vfs::OverlayFileSystem> BaseFS)
|
|
: ConsumerFactory(Context, BaseFS) {}
|
|
std::unique_ptr<FrontendAction> create() override {
|
|
return std::make_unique<Action>(&ConsumerFactory);
|
|
}
|
|
|
|
bool runInvocation(std::shared_ptr<CompilerInvocation> Invocation,
|
|
FileManager *Files,
|
|
std::shared_ptr<PCHContainerOperations> PCHContainerOps,
|
|
DiagnosticConsumer *DiagConsumer) override {
|
|
// Explicitly ask to define __clang_analyzer__ macro.
|
|
Invocation->getPreprocessorOpts().SetUpStaticAnalyzer = true;
|
|
return FrontendActionFactory::runInvocation(
|
|
Invocation, Files, PCHContainerOps, DiagConsumer);
|
|
}
|
|
|
|
private:
|
|
class Action : public ASTFrontendAction {
|
|
public:
|
|
Action(ClangTidyASTConsumerFactory *Factory) : Factory(Factory) {}
|
|
std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &Compiler,
|
|
StringRef File) override {
|
|
return Factory->CreateASTConsumer(Compiler, File);
|
|
}
|
|
|
|
private:
|
|
ClangTidyASTConsumerFactory *Factory;
|
|
};
|
|
|
|
ClangTidyASTConsumerFactory ConsumerFactory;
|
|
};
|
|
|
|
ActionFactory Factory(Context, BaseFS);
|
|
Tool.run(&Factory);
|
|
return DiagConsumer.take();
|
|
}
|
|
|
|
void handleErrors(llvm::ArrayRef<ClangTidyError> Errors,
|
|
ClangTidyContext &Context, bool Fix,
|
|
unsigned &WarningsAsErrorsCount,
|
|
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> BaseFS) {
|
|
ErrorReporter Reporter(Context, Fix, BaseFS);
|
|
llvm::vfs::FileSystem &FileSystem =
|
|
Reporter.getSourceManager().getFileManager().getVirtualFileSystem();
|
|
auto InitialWorkingDir = FileSystem.getCurrentWorkingDirectory();
|
|
if (!InitialWorkingDir)
|
|
llvm::report_fatal_error("Cannot get current working path.");
|
|
|
|
for (const ClangTidyError &Error : Errors) {
|
|
if (!Error.BuildDirectory.empty()) {
|
|
// By default, the working directory of file system is the current
|
|
// clang-tidy running directory.
|
|
//
|
|
// Change the directory to the one used during the analysis.
|
|
FileSystem.setCurrentWorkingDirectory(Error.BuildDirectory);
|
|
}
|
|
Reporter.reportDiagnostic(Error);
|
|
// Return to the initial directory to correctly resolve next Error.
|
|
FileSystem.setCurrentWorkingDirectory(InitialWorkingDir.get());
|
|
}
|
|
Reporter.Finish();
|
|
WarningsAsErrorsCount += Reporter.getWarningsAsErrorsCount();
|
|
}
|
|
|
|
void exportReplacements(const llvm::StringRef MainFilePath,
|
|
const std::vector<ClangTidyError> &Errors,
|
|
raw_ostream &OS) {
|
|
TranslationUnitDiagnostics TUD;
|
|
TUD.MainSourceFile = std::string(MainFilePath);
|
|
for (const auto &Error : Errors) {
|
|
tooling::Diagnostic Diag = Error;
|
|
TUD.Diagnostics.insert(TUD.Diagnostics.end(), Diag);
|
|
}
|
|
|
|
yaml::Output YAML(OS);
|
|
YAML << TUD;
|
|
}
|
|
|
|
} // namespace tidy
|
|
} // namespace clang
|