#include "TidyImpl.h" #include "AST/Utility.h" #include "CompilationUnitImpl.h" #include "Compiler/Command.h" #include "Compiler/Compilation.h" #include "Compiler/Diagnostic.h" #include "Compiler/Tidy.h" #include "clang/Lex/PreprocessorOptions.h" #include "clang/Frontend/TextDiagnosticPrinter.h" #include "clang/Frontend/MultiplexConsumer.h" #include "Support/Logging.h" namespace clice { namespace { /// A wrapper ast consumer, so that we can cancel the ast parse class ProxyASTConsumer final : public clang::MultiplexConsumer { public: ProxyASTConsumer(std::unique_ptr consumer, clang::CompilerInstance& instance, std::vector* top_level_decls, std::shared_ptr stop) : clang::MultiplexConsumer(std::move(consumer)), instance(instance), src_mgr(instance.getSourceManager()), top_level_decls(top_level_decls), stop(stop) {} void collect_decl(clang::Decl* decl) { if(!(ast::is_inside_main_file(decl->getLocation(), src_mgr))) { return; } if(const clang::NamedDecl* named_decl = dyn_cast(decl)) { if(ast::is_implicit_template_instantiation(named_decl)) { return; } } top_level_decls->push_back(decl); } auto HandleTopLevelDecl(clang::DeclGroupRef group) -> bool final { if(top_level_decls) { if(group.isDeclGroup()) { for(auto decl: group) { collect_decl(decl); } } else { collect_decl(group.getSingleDecl()); } } /// TODO: check atomic variable after the parse of each declaration /// may result in performance issue, benchmark in the future. if(stop && stop->load()) { return false; } return clang::MultiplexConsumer::HandleTopLevelDecl(group); } private: clang::CompilerInstance& instance; clang::SourceManager& src_mgr; /// Non-nullptr if we need collect the top level declarations. std::vector* top_level_decls; std::shared_ptr stop; }; class ProxyAction final : public clang::WrapperFrontendAction { public: ProxyAction(std::unique_ptr action, std::vector* top_level_decls, std::shared_ptr stop) : clang::WrapperFrontendAction(std::move(action)), top_level_decls(top_level_decls), stop(std::move(stop)) {} auto CreateASTConsumer(clang::CompilerInstance& instance, llvm::StringRef file) -> std::unique_ptr final { return std::make_unique( WrapperFrontendAction::CreateASTConsumer(instance, file), instance, top_level_decls, std::move(stop)); } /// Make this public. using clang::WrapperFrontendAction::EndSourceFile; private: std::vector* top_level_decls; std::shared_ptr stop; }; /// create a `clang::CompilerInvocation` for compilation, it set and reset /// all necessary arguments and flags for clice compilation. auto create_invocation(CompilationParams& params, llvm::IntrusiveRefCntPtr& diagnostic_engine) -> std::unique_ptr { if(params.arguments.empty()) { LOG_ERROR_RET(nullptr, "Fail to create invocation: empty argument list from database"); } std::unique_ptr invocation; /// Arguments from compilation database are already cc1 if(params.arguments_from_database) { invocation = std::make_unique(); if(!clang::CompilerInvocation::CreateFromArgs(*invocation, llvm::ArrayRef(params.arguments).drop_front(), *diagnostic_engine, params.arguments[0])) { LOG_ERROR_RET(nullptr, " Fail to create invocation, arguments list is: {}", print_argv(params.arguments)); } } else { /// Create clang invocation. clang::CreateInvocationOptions options = { .Diags = diagnostic_engine, .VFS = params.vfs, /// Avoid replacing -include with -include-pch, also /// see https://github.com/clangd/clangd/issues/856. .ProbePrecompiled = false, }; invocation = clang::createInvocation(params.arguments, options); if(!invocation) { LOG_ERROR_RET(nullptr, " Fail to create invocation, arguments list is: {}", print_argv(params.arguments)); } } auto& pp_opts = invocation->getPreprocessorOpts(); assert(!pp_opts.RetainRemappedFileBuffers && "RetainRemappedFileBuffers should be false"); for(auto& [file, buffer]: params.buffers) { pp_opts.addRemappedFile(file, buffer.release()); } params.buffers.clear(); auto [pch, bound] = params.pch; pp_opts.ImplicitPCHInclude = std::move(pch); if(bound != 0) { pp_opts.PrecompiledPreambleBytes = {bound, false}; } // We don't want to write comment locations into PCM. They are racy and slow // to read back. We rely on dynamic index for the comments instead. pp_opts.WriteCommentListToPCH = false; auto& header_search_opts = invocation->getHeaderSearchOpts(); for(auto& [name, path]: params.pcms) { header_search_opts.PrebuiltModuleFiles.try_emplace(name.str(), std::move(path)); } auto& front_opts = invocation->getFrontendOpts(); front_opts.DisableFree = false; /// Compiler flags (like gcc/clang's -M, -MD, -MMD, -H, or msvc's /showIncludes) /// can generate dependency files or print included headers to stdout/stderr. /// /// This output can interfere with or corrupt the Language Server Protocol (LSP) /// communication if the server is configured to use stdio for its JSON-RPC transport. /// We explicitly disables all related options to ensure no side-effect output is /// generated during parsing. auto& deps_opts = invocation->getDependencyOutputOpts(); deps_opts.IncludeSystemHeaders = false; deps_opts.ShowSkippedHeaderIncludes = false; deps_opts.UsePhonyTargets = false; deps_opts.AddMissingHeaderDeps = false; deps_opts.IncludeModuleFiles = false; deps_opts.ShowIncludesDest = clang::ShowIncludesDestination::None; deps_opts.OutputFile.clear(); deps_opts.HeaderIncludeOutputFile.clear(); deps_opts.Targets.clear(); deps_opts.ExtraDeps.clear(); deps_opts.DOTOutputFile.clear(); deps_opts.ModuleDependencyOutputDir.clear(); auto& lang_opts = invocation->getLangOpts(); lang_opts.CommentOpts.ParseAllComments = true; lang_opts.RetainCommentsFromSystemHeaders = true; return invocation; } /// Do nothing before or after compile state. constexpr static auto no_hook = [](auto& /*ignore*/) { }; template CompilationResult run_clang(CompilationParams& params, const BeforeExecute& before_execute = no_hook, const AfterExecute& after_execute = no_hook) { namespace chrono = std::chrono; auto build_at = chrono::system_clock::now().time_since_epoch(); auto build_start = chrono::steady_clock::now().time_since_epoch(); auto diagnostics = params.diagnostics ? params.diagnostics : std::make_shared>(); auto diagnostic_consumer = Diagnostic::create(diagnostics); /// Temporary diagnostic engine, only used for command line parsing. /// For compilation, we need to create a new diagnostic engine. See also /// https://github.com/llvm/llvm-project/pull/139584#issuecomment-2920704282. clang::DiagnosticOptions options; auto diagnostic_engine = clang::CompilerInstance::createDiagnostics(*params.vfs, options, diagnostic_consumer.get(), false); auto invocation = create_invocation(params, diagnostic_engine); if(!invocation) { return std::unexpected("Fail to create compilation invocation!"); } auto instance = std::make_unique(std::move(invocation)); instance->createDiagnostics(*params.vfs, diagnostic_consumer.release(), true); if(auto remapping = clang::createVFSFromCompilerInvocation(instance->getInvocation(), instance->getDiagnostics(), params.vfs)) { instance->createFileManager(std::move(remapping)); } if(!instance->createTarget()) { return std::unexpected("Fail to create target!"); } /// Adjust the compiler instance, for example, set preamble or modules. before_execute(*instance); /// Frontend information ... std::vector top_level_decls; llvm::DenseMap directives; std::optional token_collector; auto action = std::make_unique( std::make_unique(), /// We only collect top level declarations for parse main file. (params.clang_tidy || params.kind == CompilationUnit::Content) ? &top_level_decls : nullptr, params.stop); if(!action->BeginSourceFile(*instance, instance->getFrontendOpts().Inputs[0])) { return std::unexpected("Fail to begin source file"); } auto& pp = instance->getPreprocessor(); /// FIXME: include-fixer, etc? /// Setup clang-tidy std::unique_ptr checker; if(params.clang_tidy) { tidy::TidyParams tidy_params; checker = tidy::configure(*instance, tidy_params); /// TODO: We should make the lifetime of diagnostic consumer more explicit. static_cast(instance->getDiagnosticClient()).checker = checker.get(); } /// `BeginSourceFile` may create new preprocessor, so all operations related to preprocessor /// should be done after `BeginSourceFile`. Directive::attach(pp, directives); /// It is not necessary to collect tokens if we are running code completion. /// And in fact will cause assertion failure. if(!instance->hasCodeCompletionConsumer()) { token_collector.emplace(pp); } if(auto error = action->Execute()) { return std::unexpected(std::format("Failed to execute action, because {} ", error)); } /// If the output file is not empty, it represents that we are /// generating a PCH or PCM. If error occurs, the AST must be /// invalid to some extent, serialization of such AST may result /// in crash frequently. So forbidden it here and return as error. if(!instance->getFrontendOpts().OutputFile.empty() && instance->getDiagnostics().hasErrorOccurred()) { action->EndSourceFile(); return std::unexpected("Fail to build PCH or PCM, error occurs in compilation."); } /// Check whether the compilation is canceled, if so we think /// it is an error. if(params.stop && params.stop->load()) { action->EndSourceFile(); return std::unexpected("Compilation is canceled."); } std::optional token_buffer; if(token_collector) { token_buffer = std::move(*token_collector).consume(); } // Must be called before EndSourceFile because the ast context can be destroyed later. if(checker) { // AST traversals should exclude the preamble, to avoid performance cliffs. // TODO: is it okay to affect the unit-level traversal scope here? instance->getASTContext().setTraversalScope(top_level_decls); checker->finder.matchAST(instance->getASTContext()); } /// XXX: This is messy: clang-tidy checks flush some diagnostics at EOF. /// However Action->EndSourceFile() would destroy the ASTContext! /// So just inform the preprocessor of EOF, while keeping everything alive. pp.EndSourceFile(); /// FIXME: getDependencies currently return ArrayRef, which actually results in /// extra copy. It would be great to avoid this copy. std::optional resolver; if(instance->hasSema()) { resolver.emplace(instance->getSema()); } if(checker) { /// Avoid dangling pointer. static_cast(instance->getDiagnosticClient()).checker = nullptr; } auto build_end = chrono::steady_clock::now().time_since_epoch(); auto impl = new CompilationUnit::Impl{ .interested = instance->getSourceManager().getMainFileID(), .src_mgr = instance->getSourceManager(), .action = std::move(action), .instance = std::move(instance), .resolver = std::move(resolver), .buffer = std::move(token_buffer), .directives = std::move(directives), .path_cache = llvm::DenseMap(), .symbol_hash_cache = llvm::DenseMap(), .diagnostics = std::move(diagnostics), .top_level_decls = std::move(top_level_decls), .build_at = chrono::duration_cast(build_at), .build_duration = chrono::duration_cast(build_end - build_start), }; CompilationUnit unit(params.kind, impl); after_execute(unit); return unit; } } // namespace CompilationResult preprocess(CompilationParams& params) { return run_clang(params); } CompilationResult compile(CompilationParams& params) { return run_clang(params, [](clang::CompilerInstance& instance) { /// Make sure the output file is empty. instance.getFrontendOpts().OutputFile.clear(); }); } CompilationResult compile(CompilationParams& params, PCHInfo& out) { assert(!params.output_file.empty() && "PCH file path cannot be empty"); /// Record the begin time of PCH building. auto now = std::chrono::system_clock::now().time_since_epoch(); out.mtime = std::chrono::duration_cast(now).count(); return run_clang( params, [&](clang::CompilerInstance& instance) { /// Set options to generate PCH. instance.getFrontendOpts().OutputFile = params.output_file.str(); instance.getFrontendOpts().ProgramAction = clang::frontend::GeneratePCH; instance.getPreprocessorOpts().GeneratePreamble = true; // We don't want to write comment locations into PCH. They are racy and slow // to read back. We rely on dynamic index for the comments instead. instance.getPreprocessorOpts().WriteCommentListToPCH = false; instance.getLangOpts().CompilingPCH = true; }, [&](CompilationUnit& unit) { out.path = params.output_file.str(); out.preamble = unit.interested_content(); out.deps = unit.deps(); out.arguments = params.arguments; }); } CompilationResult compile(CompilationParams& params, PCMInfo& out) { assert(!params.output_file.empty() && "PCM file path cannot be empty"); return run_clang( params, [&](clang::CompilerInstance& instance) { /// Set options to generate PCH. instance.getFrontendOpts().OutputFile = params.output_file.str(); instance.getFrontendOpts().ProgramAction = clang::frontend::GenerateReducedModuleInterface; out.srcPath = instance.getFrontendOpts().Inputs[0].getFile(); }, [&](CompilationUnit& unit) { out.path = params.output_file.str(); for(auto& [name, path]: params.pcms) { out.mods.emplace_back(name); } }); } CompilationResult complete(CompilationParams& params, clang::CodeCompleteConsumer* consumer) { auto& [file, offset] = params.completion; /// The location of clang is 1-1 based. std::uint32_t line = 1; std::uint32_t column = 1; /// FIXME: assert(params.buffers.size() == 1); llvm::StringRef content = params.buffers.begin()->second->getBuffer(); for(auto c: content.substr(0, offset)) { if(c == '\n') { line += 1; column = 1; continue; } column += 1; } return run_clang(params, [&](clang::CompilerInstance& instance) { /// Set options to run code completion. instance.getFrontendOpts().CodeCompletionAt.FileName = std::move(file); instance.getFrontendOpts().CodeCompletionAt.Line = line; instance.getFrontendOpts().CodeCompletionAt.Column = column; instance.setCodeCompletionConsumer(consumer); }); } } // namespace clice