//===-- clang-import-test.cpp - ASTImporter/ExternalASTSource testbed -----===// // // 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 // //===----------------------------------------------------------------------===// #include "clang/AST/ASTContext.h" #include "clang/AST/ASTImporter.h" #include "clang/AST/DeclObjC.h" #include "clang/AST/ExternalASTMerger.h" #include "clang/Basic/Builtins.h" #include "clang/Basic/FileManager.h" #include "clang/Basic/IdentifierTable.h" #include "clang/Basic/SourceLocation.h" #include "clang/Basic/TargetInfo.h" #include "clang/Basic/TargetOptions.h" #include "clang/CodeGen/ModuleBuilder.h" #include "clang/Driver/Types.h" #include "clang/Frontend/ASTConsumers.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/MultiplexConsumer.h" #include "clang/Frontend/TextDiagnosticBuffer.h" #include "clang/Lex/Lexer.h" #include "clang/Lex/Preprocessor.h" #include "clang/Parse/ParseAST.h" #include "llvm/IR/LLVMContext.h" #include "llvm/IR/Module.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/Error.h" #include "llvm/Support/Host.h" #include "llvm/Support/Signals.h" #include #include using namespace clang; static llvm::cl::opt Expression( "expression", llvm::cl::Required, llvm::cl::desc("Path to a file containing the expression to parse")); static llvm::cl::list Imports("import", llvm::cl::ZeroOrMore, llvm::cl::desc("Path to a file containing declarations to import")); static llvm::cl::opt Direct("direct", llvm::cl::Optional, llvm::cl::desc("Use the parsed declarations without indirection")); static llvm::cl::opt UseOrigins( "use-origins", llvm::cl::Optional, llvm::cl::desc( "Use DeclContext origin information for more accurate lookups")); static llvm::cl::list ClangArgs("Xcc", llvm::cl::ZeroOrMore, llvm::cl::desc("Argument to pass to the CompilerInvocation"), llvm::cl::CommaSeparated); static llvm::cl::opt Input("x", llvm::cl::Optional, llvm::cl::desc("The language to parse (default: c++)"), llvm::cl::init("c++")); static llvm::cl::opt ObjCARC("objc-arc", llvm::cl::init(false), llvm::cl::desc("Emable ObjC ARC")); static llvm::cl::opt DumpAST("dump-ast", llvm::cl::init(false), llvm::cl::desc("Dump combined AST")); static llvm::cl::opt DumpIR("dump-ir", llvm::cl::init(false), llvm::cl::desc("Dump IR from final parse")); namespace init_convenience { class TestDiagnosticConsumer : public DiagnosticConsumer { private: std::unique_ptr Passthrough; const LangOptions *LangOpts = nullptr; public: TestDiagnosticConsumer() : Passthrough(std::make_unique()) {} virtual void BeginSourceFile(const LangOptions &LangOpts, const Preprocessor *PP = nullptr) override { this->LangOpts = &LangOpts; return Passthrough->BeginSourceFile(LangOpts, PP); } virtual void EndSourceFile() override { this->LangOpts = nullptr; Passthrough->EndSourceFile(); } virtual bool IncludeInDiagnosticCounts() const override { return Passthrough->IncludeInDiagnosticCounts(); } private: static void PrintSourceForLocation(const SourceLocation &Loc, SourceManager &SM) { const char *LocData = SM.getCharacterData(Loc, /*Invalid=*/nullptr); unsigned LocColumn = SM.getSpellingColumnNumber(Loc, /*Invalid=*/nullptr) - 1; FileID FID = SM.getFileID(Loc); llvm::MemoryBufferRef Buffer = SM.getBufferOrFake(FID, Loc); assert(LocData >= Buffer.getBufferStart() && LocData < Buffer.getBufferEnd()); const char *LineBegin = LocData - LocColumn; assert(LineBegin >= Buffer.getBufferStart()); const char *LineEnd = nullptr; for (LineEnd = LineBegin; *LineEnd != '\n' && *LineEnd != '\r' && LineEnd < Buffer.getBufferEnd(); ++LineEnd) ; llvm::StringRef LineString(LineBegin, LineEnd - LineBegin); llvm::errs() << LineString << '\n'; llvm::errs().indent(LocColumn); llvm::errs() << '^'; llvm::errs() << '\n'; } virtual void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, const Diagnostic &Info) override { if (Info.hasSourceManager() && LangOpts) { SourceManager &SM = Info.getSourceManager(); if (Info.getLocation().isValid()) { Info.getLocation().print(llvm::errs(), SM); llvm::errs() << ": "; } SmallString<16> DiagText; Info.FormatDiagnostic(DiagText); llvm::errs() << DiagText << '\n'; if (Info.getLocation().isValid()) { PrintSourceForLocation(Info.getLocation(), SM); } for (const CharSourceRange &Range : Info.getRanges()) { bool Invalid = true; StringRef Ref = Lexer::getSourceText(Range, SM, *LangOpts, &Invalid); if (!Invalid) { llvm::errs() << Ref << '\n'; } } } DiagnosticConsumer::HandleDiagnostic(DiagLevel, Info); } }; std::unique_ptr BuildCompilerInstance() { auto Ins = std::make_unique(); auto DC = std::make_unique(); const bool ShouldOwnClient = true; Ins->createDiagnostics(DC.release(), ShouldOwnClient); auto Inv = std::make_unique(); std::vector ClangArgv(ClangArgs.size()); std::transform(ClangArgs.begin(), ClangArgs.end(), ClangArgv.begin(), [](const std::string &s) -> const char * { return s.data(); }); CompilerInvocation::CreateFromArgs(*Inv, ClangArgv, Ins->getDiagnostics()); { using namespace driver::types; ID Id = lookupTypeForTypeSpecifier(Input.c_str()); assert(Id != TY_INVALID); if (isCXX(Id)) { Inv->getLangOpts()->CPlusPlus = true; Inv->getLangOpts()->CPlusPlus11 = true; Inv->getHeaderSearchOpts().UseLibcxx = true; } if (isObjC(Id)) { Inv->getLangOpts()->ObjC = 1; } } Inv->getLangOpts()->ObjCAutoRefCount = ObjCARC; Inv->getLangOpts()->Bool = true; Inv->getLangOpts()->WChar = true; Inv->getLangOpts()->Blocks = true; Inv->getLangOpts()->DebuggerSupport = true; Inv->getLangOpts()->SpellChecking = false; Inv->getLangOpts()->ThreadsafeStatics = false; Inv->getLangOpts()->AccessControl = false; Inv->getLangOpts()->DollarIdents = true; Inv->getLangOpts()->Exceptions = true; Inv->getLangOpts()->CXXExceptions = true; // Needed for testing dynamic_cast. Inv->getLangOpts()->RTTI = true; Inv->getCodeGenOpts().setDebugInfo(codegenoptions::FullDebugInfo); Inv->getTargetOpts().Triple = llvm::sys::getDefaultTargetTriple(); Ins->setInvocation(std::move(Inv)); TargetInfo *TI = TargetInfo::CreateTargetInfo( Ins->getDiagnostics(), Ins->getInvocation().TargetOpts); Ins->setTarget(TI); Ins->getTarget().adjust(Ins->getLangOpts()); Ins->createFileManager(); Ins->createSourceManager(Ins->getFileManager()); Ins->createPreprocessor(TU_Complete); return Ins; } std::unique_ptr BuildASTContext(CompilerInstance &CI, SelectorTable &ST, Builtin::Context &BC) { auto AST = std::make_unique( CI.getLangOpts(), CI.getSourceManager(), CI.getPreprocessor().getIdentifierTable(), ST, BC); AST->InitBuiltinTypes(CI.getTarget()); return AST; } std::unique_ptr BuildCodeGen(CompilerInstance &CI, llvm::LLVMContext &LLVMCtx) { StringRef ModuleName("$__module"); return std::unique_ptr(CreateLLVMCodeGen( CI.getDiagnostics(), ModuleName, CI.getHeaderSearchOpts(), CI.getPreprocessorOpts(), CI.getCodeGenOpts(), LLVMCtx)); } } // namespace init_convenience namespace { /// A container for a CompilerInstance (possibly with an ExternalASTMerger /// attached to its ASTContext). /// /// Provides an accessor for the DeclContext origins associated with the /// ExternalASTMerger (or an empty list of origins if no ExternalASTMerger is /// attached). /// /// This is the main unit of parsed source code maintained by clang-import-test. struct CIAndOrigins { using OriginMap = clang::ExternalASTMerger::OriginMap; std::unique_ptr CI; ASTContext &getASTContext() { return CI->getASTContext(); } FileManager &getFileManager() { return CI->getFileManager(); } const OriginMap &getOriginMap() { static const OriginMap EmptyOriginMap{}; if (ExternalASTSource *Source = CI->getASTContext().getExternalSource()) return static_cast(Source)->GetOrigins(); return EmptyOriginMap; } DiagnosticConsumer &getDiagnosticClient() { return CI->getDiagnosticClient(); } CompilerInstance &getCompilerInstance() { return *CI; } }; void AddExternalSource(CIAndOrigins &CI, llvm::MutableArrayRef Imports) { ExternalASTMerger::ImporterTarget Target( {CI.getASTContext(), CI.getFileManager()}); llvm::SmallVector Sources; for (CIAndOrigins &Import : Imports) Sources.emplace_back(Import.getASTContext(), Import.getFileManager(), Import.getOriginMap()); auto ES = std::make_unique(Target, Sources); CI.getASTContext().setExternalSource(ES.release()); CI.getASTContext().getTranslationUnitDecl()->setHasExternalVisibleStorage(); } CIAndOrigins BuildIndirect(CIAndOrigins &CI) { CIAndOrigins IndirectCI{init_convenience::BuildCompilerInstance()}; auto ST = std::make_unique(); auto BC = std::make_unique(); std::unique_ptr AST = init_convenience::BuildASTContext( IndirectCI.getCompilerInstance(), *ST, *BC); IndirectCI.getCompilerInstance().setASTContext(AST.release()); AddExternalSource(IndirectCI, CI); return IndirectCI; } llvm::Error ParseSource(const std::string &Path, CompilerInstance &CI, ASTConsumer &Consumer) { SourceManager &SM = CI.getSourceManager(); auto FE = CI.getFileManager().getFileRef(Path); if (!FE) { llvm::consumeError(FE.takeError()); return llvm::make_error( llvm::Twine("No such file or directory: ", Path), std::error_code()); } SM.setMainFileID(SM.createFileID(*FE, SourceLocation(), SrcMgr::C_User)); ParseAST(CI.getPreprocessor(), &Consumer, CI.getASTContext()); return llvm::Error::success(); } llvm::Expected Parse(const std::string &Path, llvm::MutableArrayRef Imports, bool ShouldDumpAST, bool ShouldDumpIR) { CIAndOrigins CI{init_convenience::BuildCompilerInstance()}; auto ST = std::make_unique(); auto BC = std::make_unique(); std::unique_ptr AST = init_convenience::BuildASTContext(CI.getCompilerInstance(), *ST, *BC); CI.getCompilerInstance().setASTContext(AST.release()); if (Imports.size()) AddExternalSource(CI, Imports); std::vector> ASTConsumers; auto LLVMCtx = std::make_unique(); ASTConsumers.push_back( init_convenience::BuildCodeGen(CI.getCompilerInstance(), *LLVMCtx)); auto &CG = *static_cast(ASTConsumers.back().get()); if (ShouldDumpAST) ASTConsumers.push_back(CreateASTDumper(nullptr /*Dump to stdout.*/, "", true, false, false, false, clang::ADOF_Default)); CI.getDiagnosticClient().BeginSourceFile( CI.getCompilerInstance().getLangOpts(), &CI.getCompilerInstance().getPreprocessor()); MultiplexConsumer Consumers(std::move(ASTConsumers)); Consumers.Initialize(CI.getASTContext()); if (llvm::Error PE = ParseSource(Path, CI.getCompilerInstance(), Consumers)) return std::move(PE); CI.getDiagnosticClient().EndSourceFile(); if (ShouldDumpIR) CG.GetModule()->print(llvm::outs(), nullptr); if (CI.getDiagnosticClient().getNumErrors()) return llvm::make_error( "Errors occurred while parsing the expression.", std::error_code()); return std::move(CI); } void Forget(CIAndOrigins &CI, llvm::MutableArrayRef Imports) { llvm::SmallVector Sources; for (CIAndOrigins &Import : Imports) Sources.push_back({Import.getASTContext(), Import.getFileManager(), Import.getOriginMap()}); ExternalASTSource *Source = CI.CI->getASTContext().getExternalSource(); auto *Merger = static_cast(Source); Merger->RemoveSources(Sources); } } // end namespace int main(int argc, const char **argv) { const bool DisableCrashReporting = true; llvm::sys::PrintStackTraceOnErrorSignal(argv[0], DisableCrashReporting); llvm::cl::ParseCommandLineOptions(argc, argv); std::vector ImportCIs; for (auto I : Imports) { llvm::Expected ImportCI = Parse(I, {}, false, false); if (auto E = ImportCI.takeError()) { llvm::errs() << "error: " << llvm::toString(std::move(E)) << "\n"; exit(-1); } ImportCIs.push_back(std::move(*ImportCI)); } std::vector IndirectCIs; if (!Direct || UseOrigins) { for (auto &ImportCI : ImportCIs) { CIAndOrigins IndirectCI = BuildIndirect(ImportCI); IndirectCIs.push_back(std::move(IndirectCI)); } } if (UseOrigins) for (auto &ImportCI : ImportCIs) IndirectCIs.push_back(std::move(ImportCI)); llvm::Expected ExpressionCI = Parse(Expression, (Direct && !UseOrigins) ? ImportCIs : IndirectCIs, DumpAST, DumpIR); if (auto E = ExpressionCI.takeError()) { llvm::errs() << "error: " << llvm::toString(std::move(E)) << "\n"; exit(-1); } Forget(*ExpressionCI, (Direct && !UseOrigins) ? ImportCIs : IndirectCIs); return 0; }