Index: clang-tidy/ClangTidy.h =================================================================== --- clang-tidy/ClangTidy.h +++ clang-tidy/ClangTidy.h @@ -15,6 +15,7 @@ #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/Basic/Diagnostic.h" #include "clang/Basic/SourceManager.h" +#include "clang/Tooling/Execution.h" #include "clang/Tooling/Refactoring.h" #include "llvm/ADT/StringExtras.h" #include "llvm/Support/raw_ostream.h" @@ -232,8 +233,9 @@ /// as a JSON file in the specified directory. std::vector runClangTidy(clang::tidy::ClangTidyContext &Context, - const tooling::CompilationDatabase &Compilations, - ArrayRef InputFiles, + std::unique_ptr Executor, + // const tooling::CompilationDatabase &Compilations, + // ArrayRef InputFiles, llvm::IntrusiveRefCntPtr BaseFS, bool EnableCheckProfile = false, llvm::StringRef StoreCheckProfile = StringRef()); Index: clang-tidy/ClangTidy.cpp =================================================================== --- clang-tidy/ClangTidy.cpp +++ clang-tidy/ClangTidy.cpp @@ -502,15 +502,13 @@ return Factory.getCheckOptions(); } +// FIXME: support FS overlay. +// FIXME: make ClangTidyContext thread safe. std::vector runClangTidy(clang::tidy::ClangTidyContext &Context, - const CompilationDatabase &Compilations, - ArrayRef InputFiles, + std::unique_ptr Executor, llvm::IntrusiveRefCntPtr BaseFS, bool EnableCheckProfile, llvm::StringRef StoreCheckProfile) { - ClangTool Tool(Compilations, InputFiles, - std::make_shared(), BaseFS); - // Add extra arguments passed by the clang-tidy command-line. ArgumentsAdjuster PerFileExtraArgumentsInserter = [&Context](const CommandLineArguments &Args, StringRef Filename) { @@ -545,19 +543,28 @@ return AdjustedArgs; }; - Tool.appendArgumentsAdjuster(PerFileExtraArgumentsInserter); - Tool.appendArgumentsAdjuster(PluginArgumentsRemover); Context.setEnableProfiling(EnableCheckProfile); Context.setProfileStoragePrefix(StoreCheckProfile); - ClangTidyDiagnosticConsumer DiagConsumer(Context); - - Tool.setDiagnosticConsumer(&DiagConsumer); - class ActionFactory : public FrontendActionFactory { public: - ActionFactory(ClangTidyContext &Context) : ConsumerFactory(Context) {} - FrontendAction *create() override { return new Action(&ConsumerFactory); } + ActionFactory(ClangTidyContext &Context, + std::vector &Results) + : ConsumerFactory(Context), Context(Context), Results(Results) {} + + FrontendAction *create() override { + return new Action(&ConsumerFactory, Context, + [&](std::vector Errors) { + Results.insert(Results.end(), Errors.begin(), + Errors.end()); + }); + } + + ~ActionFactory() { + // Deduplicate, and emit the final results. + std::sort(Results.begin(), Results.end()); + Results.erase(std::unique(Results.begin(), Results.end()), Results.end()); + } bool runInvocation(std::shared_ptr Invocation, FileManager *Files, @@ -574,22 +581,53 @@ private: class Action : public ASTFrontendAction { public: - Action(ClangTidyASTConsumerFactory *Factory) : Factory(Factory) {} + Action(ClangTidyASTConsumerFactory *Factory, ClangTidyContext &Context, + std::function)> + CTErrorsCallback) + : Factory(Factory), CTDiagConsumer(Context), + CTErrorsCallback(CTErrorsCallback) {} std::unique_ptr CreateASTConsumer(CompilerInstance &Compiler, StringRef File) override { return Factory->CreateASTConsumer(Compiler, File); } + bool BeginInvocation(CompilerInstance &CI) override { + // Inject clang-tidy diagnostic consumer to store all diagnostics. + // FIXME: at this point, we miss diagnostics (e.g. unknown argument) + // that emitted during a CompilerInstance is created from compile + // command arguments. + CI.getDiagnostics().setClient(&CTDiagConsumer, false); + return true; + } + + // Emits all collected diagnostics. + // We can't emit at EndSourceFileAction, because EndSourceFileAction will + // not be called when the compliation of the file should be aborted. + // clang-tidy wants all diagnostics, including errors. + ~Action() { CTErrorsCallback(CTDiagConsumer.take()); } + private: ClangTidyASTConsumerFactory *Factory; + ClangTidyDiagnosticConsumer CTDiagConsumer; + std::function)> CTErrorsCallback; }; ClangTidyASTConsumerFactory ConsumerFactory; + ClangTidyContext &Context; + std::vector &Results; }; - ActionFactory Factory(Context); - Tool.run(&Factory); - return DiagConsumer.take(); + ArgumentsAdjuster CTArgumentAjuster = + combineAdjusters(std::move(PerFileExtraArgumentsInserter), + std::move(PluginArgumentsRemover)); + std::vector Results; + auto Factory = llvm::make_unique(Context, Results); + auto Err = Executor->execute(std::move(Factory), CTArgumentAjuster); + if (Err) { + // Consume this error, as all errors will be reported by clang-tidy. + llvm::consumeError(std::move(Err)); + } + return Results; } void handleErrors(llvm::ArrayRef Errors, Index: clang-tidy/ClangTidyDiagnosticConsumer.h =================================================================== --- clang-tidy/ClangTidyDiagnosticConsumer.h +++ clang-tidy/ClangTidyDiagnosticConsumer.h @@ -47,6 +47,20 @@ bool IsWarningAsError; }; +inline bool operator<(const ClangTidyError &LHS, const ClangTidyError &RHS) { + const tooling::DiagnosticMessage &M1 = LHS.Message; + const tooling::DiagnosticMessage &M2 = RHS.Message; + + return std::tie(M1.FilePath, M1.FileOffset, M1.Message) < + std::tie(M2.FilePath, M2.FileOffset, M2.Message); +}; +inline bool operator==(const ClangTidyError &LHS, const ClangTidyError &RHS) { + const tooling::DiagnosticMessage &M1 = LHS.Message; + const tooling::DiagnosticMessage &M2 = RHS.Message; + + return std::tie(M1.FilePath, M1.FileOffset, M1.Message) == + std::tie(M2.FilePath, M2.FileOffset, M2.Message); +} /// \brief Read-only set of strings represented as a list of positive and /// negative globs. Positive globs add all matched strings to the set, negative Index: clang-tidy/ClangTidyDiagnosticConsumer.cpp =================================================================== --- clang-tidy/ClangTidyDiagnosticConsumer.cpp +++ clang-tidy/ClangTidyDiagnosticConsumer.cpp @@ -645,30 +645,11 @@ } } -namespace { -struct LessClangTidyError { - bool operator()(const ClangTidyError &LHS, const ClangTidyError &RHS) const { - const tooling::DiagnosticMessage &M1 = LHS.Message; - const tooling::DiagnosticMessage &M2 = RHS.Message; - - return std::tie(M1.FilePath, M1.FileOffset, M1.Message) < - std::tie(M2.FilePath, M2.FileOffset, M2.Message); - } -}; -struct EqualClangTidyError { - bool operator()(const ClangTidyError &LHS, const ClangTidyError &RHS) const { - LessClangTidyError Less; - return !Less(LHS, RHS) && !Less(RHS, LHS); - } -}; -} // end anonymous namespace - std::vector ClangTidyDiagnosticConsumer::take() { finalizeLastError(); - std::sort(Errors.begin(), Errors.end(), LessClangTidyError()); - Errors.erase(std::unique(Errors.begin(), Errors.end(), EqualClangTidyError()), - Errors.end()); + std::sort(Errors.begin(), Errors.end()); + Errors.erase(std::unique(Errors.begin(), Errors.end()), Errors.end()); if (RemoveIncompatibleErrors) removeIncompatibleErrors(); return std::move(Errors); Index: clang-tidy/tool/ClangTidyMain.cpp =================================================================== --- clang-tidy/tool/ClangTidyMain.cpp +++ clang-tidy/tool/ClangTidyMain.cpp @@ -18,6 +18,7 @@ #include "../ClangTidy.h" #include "clang/Config/config.h" #include "clang/Tooling/CommonOptionsParser.h" +#include "clang/Tooling/Execution.h" #include "llvm/Support/Process.h" #include "llvm/Support/TargetSelect.h" @@ -328,8 +329,14 @@ } static int clangTidyMain(int argc, const char **argv) { - CommonOptionsParser OptionsParser(argc, argv, ClangTidyCategory, - cl::ZeroOrMore); + // CommonOptionsParser OptionsParser(argc, argv, ClangTidyCategory, + // cl::ZeroOrMore); + auto Executor = clang::tooling::createExecutorFromCommandLineArgs( + argc, argv, ClangTidyCategory); + if (!Executor) { + llvm::errs() << llvm::toString(Executor.takeError()) << "\n"; + return 1; + } llvm::IntrusiveRefCntPtr BaseFS( VfsOverlay.empty() ? vfs::getRealFileSystem() : getVfsOverlayFromFile(VfsOverlay)); @@ -355,10 +362,10 @@ SmallString<256> ProfilePrefix = MakeAbsolute(StoreCheckProfile); StringRef FileName("dummy"); - auto PathList = OptionsParser.getSourcePathList(); - if (!PathList.empty()) { - FileName = PathList.front(); - } + // auto PathList = OptionsParser.getSourcePathList(); + // if (!PathList.empty()) { + // FileName = PathList.front(); + // } SmallString<256> FilePath = MakeAbsolute(FileName); @@ -410,11 +417,11 @@ return 1; } - if (PathList.empty()) { - llvm::errs() << "Error: no input files specified.\n"; - llvm::cl::PrintHelpMessage(/*Hidden=*/false, /*Categorized=*/true); - return 1; - } + // if (PathList.empty()) { + // llvm::errs() << "Error: no input files specified.\n"; + // llvm::cl::PrintHelpMessage(/*Hidden=*/false, /*Categorized=*/true); + // return 1; + // } llvm::InitializeAllTargetInfos(); llvm::InitializeAllTargetMCs(); @@ -422,9 +429,8 @@ ClangTidyContext Context(std::move(OwningOptionsProvider), AllowEnablingAnalyzerAlphaCheckers); - std::vector Errors = - runClangTidy(Context, OptionsParser.getCompilations(), PathList, BaseFS, - EnableCheckProfile, ProfilePrefix); + std::vector Errors = runClangTidy( + Context, std::move(*Executor), BaseFS, EnableCheckProfile, ProfilePrefix); bool FoundErrors = llvm::find_if(Errors, [](const ClangTidyError &E) { return E.DiagLevel == ClangTidyError::Error; }) != Errors.end();