Index: test/CMakeLists.txt =================================================================== --- test/CMakeLists.txt +++ test/CMakeLists.txt @@ -39,6 +39,7 @@ c-index-test diagtool clang-tblgen clang-offload-bundler + clang-import-test ) if(CLANG_ENABLE_STATIC_ANALYZER) Index: test/Import/clang-flags/Inputs/S.c =================================================================== --- test/Import/clang-flags/Inputs/S.c +++ test/Import/clang-flags/Inputs/S.c @@ -0,0 +1,2 @@ +STRUCT S { +}; Index: test/Import/clang-flags/test.c =================================================================== --- test/Import/clang-flags/test.c +++ test/Import/clang-flags/test.c @@ -0,0 +1,5 @@ +// RUN: clang-import-test -import %S/Inputs/S.c -expression %s -Xcc -DSTRUCT=struct +void expr() { + STRUCT S MyS; + void *MyPtr = &MyS; +} Index: test/Import/empty-struct/Inputs/S.c =================================================================== --- test/Import/empty-struct/Inputs/S.c +++ test/Import/empty-struct/Inputs/S.c @@ -0,0 +1,2 @@ +struct S { +}; Index: test/Import/empty-struct/test.c =================================================================== --- test/Import/empty-struct/test.c +++ test/Import/empty-struct/test.c @@ -0,0 +1,5 @@ +// RUN: clang-import-test -import %S/Inputs/S.c -expression %s +void expr() { + struct S MyS; + void *MyPtr = &MyS; +} Index: test/Import/error-in-expression/Inputs/S.c =================================================================== --- test/Import/error-in-expression/Inputs/S.c +++ test/Import/error-in-expression/Inputs/S.c @@ -0,0 +1,2 @@ +struct S { +}; Index: test/Import/error-in-expression/test.c =================================================================== --- test/Import/error-in-expression/test.c +++ test/Import/error-in-expression/test.c @@ -0,0 +1,6 @@ +// RUN: not clang-import-test -import %S/Inputs/S.c -expression %s 2>&1 | FileCheck %s +// CHECK: {{.*}}no viable conversion{{.*}} +void expr() { + struct S MyS; + void *MyPtr = MyS; +} Index: test/Import/error-in-import/Inputs/S.c =================================================================== --- test/Import/error-in-import/Inputs/S.c +++ test/Import/error-in-import/Inputs/S.c @@ -0,0 +1,2 @@ +struct S [ +]; Index: test/Import/error-in-import/test.c =================================================================== --- test/Import/error-in-import/test.c +++ test/Import/error-in-import/test.c @@ -0,0 +1,6 @@ +// RUN: not clang-import-test -import %S/Inputs/S.c -expression %s 2>&1 | FileCheck %s +// CHECK: {{.*}}expected unqualified-id{{.*}} +void expr() { + struct S MyS; + void *MyPtr = &MyS; +} Index: test/Import/missing-import/test.c =================================================================== --- test/Import/missing-import/test.c +++ test/Import/missing-import/test.c @@ -0,0 +1,6 @@ +// RUN: not clang-import-test -import %S/Inputs/S.c -expression %s 2>&1 | FileCheck %s +// CHECK: {{.*}}Couldn't open{{.*}}Inputs/S.c{{.*}} +void expr() { + struct S MyS; + void *MyPtr = &MyS; +} Index: tools/CMakeLists.txt =================================================================== --- tools/CMakeLists.txt +++ tools/CMakeLists.txt @@ -5,6 +5,7 @@ add_clang_subdirectory(clang-format) add_clang_subdirectory(clang-format-vs) add_clang_subdirectory(clang-fuzzer) +add_clang_subdirectory(clang-import-test) add_clang_subdirectory(clang-offload-bundler) add_clang_subdirectory(c-index-test) Index: tools/clang-import-test/CMakeLists.txt =================================================================== --- tools/clang-import-test/CMakeLists.txt +++ tools/clang-import-test/CMakeLists.txt @@ -0,0 +1,24 @@ +set(LLVM_LINK_COMPONENTS + support +) + +if(NOT CLANG_BUILT_STANDALONE) + set(tablegen_deps intrinsics_gen) +endif() + +add_clang_tool(clang-import-test + clang-import-test.cpp + DEPENDS + ${tablegen_deps} + ) + +set(CLANG_IMPORT_TEST_LIB_DEPS + clangAST + clangBasic + clangCodeGen + clangFrontend + ) + +target_link_libraries(clang-import-test + ${CLANG_IMPORT_TEST_LIB_DEPS} + ) Index: tools/clang-import-test/clang-import-test.cpp =================================================================== --- tools/clang-import-test/clang-import-test.cpp +++ tools/clang-import-test/clang-import-test.cpp @@ -0,0 +1,319 @@ +//===-- import-test.cpp - ASTImporter/ExternalASTSource testbed -----------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/AST/ASTContext.h" +#include "clang/AST/ASTImporter.h" +#include "clang/Basic/Builtins.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/Frontend/CompilerInstance.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/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::list + ClangArgs("Xcc", llvm::cl::ZeroOrMore, + llvm::cl::desc("Argument to pass to the CompilerInvocation"), + llvm::cl::CommaSeparated); + +namespace init_convenience { +class TestDiagnosticConsumer : public DiagnosticConsumer { +private: + std::unique_ptr Passthrough; + const LangOptions *LangOpts = nullptr; + +public: + TestDiagnosticConsumer() + : Passthrough(llvm::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::MemoryBuffer *Buffer = SM.getBuffer(FID, Loc, /*Invalid=*/nullptr); + + 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'; + std::string Space(LocColumn, ' '); + llvm::errs() << Space.c_str() << '\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.str() << '\n'; + } + } + } + DiagnosticConsumer::HandleDiagnostic(DiagLevel, Info); + } +}; + +std::unique_ptr +BuildCompilerInstance(ArrayRef ClangArgv) { + auto Ins = llvm::make_unique(); + auto DC = llvm::make_unique(); + const bool ShouldOwnClient = true; + Ins->createDiagnostics(DC.release(), ShouldOwnClient); + + auto Inv = llvm::make_unique(); + + CompilerInvocation::CreateFromArgs(*Inv, ClangArgv.data(), + &ClangArgv.data()[ClangArgv.size()], + Ins->getDiagnostics()); + + Inv->getLangOpts()->CPlusPlus = true; + Inv->getLangOpts()->CPlusPlus11 = true; + Inv->getHeaderSearchOpts().UseLibcxx = true; + 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->getCodeGenOpts().setDebugInfo(codegenoptions::FullDebugInfo); + Inv->getTargetOpts().Triple = llvm::sys::getDefaultTargetTriple(); + + Ins->setInvocation(Inv.release()); + + 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 = llvm::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)); +} +} // end namespace + +namespace { +class TestExternalASTSource : public ExternalASTSource { +private: + llvm::ArrayRef> ImportCIs; + std::map> ForwardImporters; + std::map> ReverseImporters; + +public: + TestExternalASTSource( + CompilerInstance &ExpressionCI, + llvm::ArrayRef> ImportCIs) + : ImportCIs(ImportCIs) { + for (const std::unique_ptr &ImportCI : ImportCIs) { + ForwardImporters[ImportCI.get()] = llvm::make_unique( + ExpressionCI.getASTContext(), ExpressionCI.getFileManager(), + ImportCI->getASTContext(), ImportCI->getFileManager(), + /*MinimalImport=*/true); + ReverseImporters[ImportCI.get()] = llvm::make_unique( + ImportCI->getASTContext(), ImportCI->getFileManager(), + ExpressionCI.getASTContext(), ExpressionCI.getFileManager(), + /*MinimalImport=*/true); + } + } + + bool FindExternalVisibleDeclsByName(const DeclContext *DC, + DeclarationName Name) override { + llvm::SmallVector Decls; + + if (isa(DC)) { + for (const std::unique_ptr &I : ImportCIs) { + DeclarationName FromName = ReverseImporters[I.get()]->Import(Name); + DeclContextLookupResult Result = + I->getASTContext().getTranslationUnitDecl()->lookup(FromName); + for (NamedDecl *FromD : Result) { + NamedDecl *D = + llvm::cast(ForwardImporters[I.get()]->Import(FromD)); + Decls.push_back(D); + } + } + } + if (Decls.empty()) { + return false; + } else { + SetExternalVisibleDeclsForName(DC, Name, Decls); + return true; + } + } +}; + +void AddExternalSource( + CompilerInstance &CI, + llvm::ArrayRef> Imports) { + ASTContext &AST = CI.getASTContext(); + auto ES = llvm::make_unique(CI, Imports); + AST.setExternalSource(ES.release()); + AST.getTranslationUnitDecl()->setHasExternalVisibleStorage(); +} + +llvm::Error ParseSource(const std::string &Path, CompilerInstance &CI, + CodeGenerator &CG) { + SourceManager &SM = CI.getSourceManager(); + const FileEntry *FE = CI.getFileManager().getFile(Path); + if (!FE) { + return llvm::make_error( + llvm::Twine("Couldn't open ", Path), std::error_code()); + } + SM.setMainFileID(SM.createFileID(FE, SourceLocation(), SrcMgr::C_User)); + ParseAST(CI.getPreprocessor(), &CG, CI.getASTContext()); + return llvm::Error::success(); +} + +llvm::Expected> +Parse(const std::string &Path, + llvm::ArrayRef> Imports) { + std::vector ClangArgv(ClangArgs.size()); + std::transform(ClangArgs.begin(), ClangArgs.end(), ClangArgv.begin(), + [](const std::string &s) -> const char * { return s.data(); }); + std::unique_ptr CI = + init_convenience::BuildCompilerInstance(ClangArgv); + auto ST = llvm::make_unique(); + auto BC = llvm::make_unique(); + std::unique_ptr AST = + init_convenience::BuildASTContext(*CI, *ST, *BC); + CI->setASTContext(AST.release()); + AddExternalSource(*CI, Imports); + + auto LLVMCtx = llvm::make_unique(); + std::unique_ptr CG = + init_convenience::BuildCodeGen(*CI, *LLVMCtx); + CG->Initialize(CI->getASTContext()); + + CI->getDiagnosticClient().BeginSourceFile(CI->getLangOpts(), + &CI->getPreprocessor()); + if (llvm::Error PE = ParseSource(Path, *CI, *CG)) { + return std::move(PE); + } + CI->getDiagnosticClient().EndSourceFile(); + if (CI->getDiagnosticClient().getNumErrors()) { + return llvm::make_error( + "Errors occured while parsing the expression.", std::error_code()); + } else { + return std::move(CI); + } +} +} // 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, {}); + if (auto E = ImportCI.takeError()) { + llvm::errs() << llvm::toString(std::move(E)); + exit(-1); + } else { + ImportCIs.push_back(std::move(*ImportCI)); + } + } + llvm::Expected> ExpressionCI = + Parse(Expression, ImportCIs); + if (auto E = ExpressionCI.takeError()) { + llvm::errs() << llvm::toString(std::move(E)); + exit(-1); + } else { + return 0; + } +}