From: Douglas Gregor Date: Tue, 9 Feb 2010 19:21:46 +0000 (+0000) Subject: Introduce a testbed for merging multiple ASTs into a single AST X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=9bed8798964d9f07599c2c9199701f86fbc70e20;p=clang Introduce a testbed for merging multiple ASTs into a single AST context with the AST importer. WIP, still useless but at least it has a test. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@95683 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/include/clang/Driver/CC1Options.td b/include/clang/Driver/CC1Options.td index 23275b63ff..f0db6e7caf 100644 --- a/include/clang/Driver/CC1Options.td +++ b/include/clang/Driver/CC1Options.td @@ -200,6 +200,9 @@ def verify : Flag<"-verify">, // CompilerInvocation out of a driver-derived argument vector. def cc1 : Flag<"-cc1">; +def ast_merge : Separate<"-ast-merge">, + MetaVarName<"">, + HelpText<"Merge the given AST file into the translation unit being compiled.">; def code_completion_at : Separate<"-code-completion-at">, MetaVarName<"::">, HelpText<"Dump code-completion information at a location">; diff --git a/include/clang/Frontend/FrontendAction.h b/include/clang/Frontend/FrontendAction.h index 29a9302c5c..7b7db3785c 100644 --- a/include/clang/Frontend/FrontendAction.h +++ b/include/clang/Frontend/FrontendAction.h @@ -18,6 +18,7 @@ namespace clang { class ASTUnit; class ASTConsumer; class CompilerInstance; +class ASTMergeAction; /// FrontendAction - Abstract base class for actions which can be performed by /// the frontend. @@ -25,6 +26,7 @@ class FrontendAction { std::string CurrentFile; llvm::OwningPtr CurrentASTUnit; CompilerInstance *Instance; + friend class ASTMergeAction; protected: /// @name Implementation Action Interface @@ -104,6 +106,10 @@ public: return *CurrentASTUnit; } + ASTUnit *takeCurrentASTUnit() { + return CurrentASTUnit.take(); + } + void setCurrentFile(llvm::StringRef Value, ASTUnit *AST = 0); /// @} diff --git a/include/clang/Frontend/FrontendActions.h b/include/clang/Frontend/FrontendActions.h index 1eece64282..cbb3508c8a 100644 --- a/include/clang/Frontend/FrontendActions.h +++ b/include/clang/Frontend/FrontendActions.h @@ -11,6 +11,8 @@ #define LLVM_CLANG_FRONTEND_FRONTENDACTIONS_H #include "clang/Frontend/FrontendAction.h" +#include +#include namespace clang { class FixItRewriter; @@ -119,6 +121,43 @@ public: virtual bool hasCodeCompletionSupport() const { return true; } }; +/** + * \brief Frontend action adaptor that merges ASTs together. + * + * This action takes an existing AST file and "merges" it into the AST + * context, producing a merged context. This action is an action + * adaptor, which forwards most of its calls to another action that + * will consume the merged context. + */ +class ASTMergeAction : public FrontendAction { + /// \brief The action that the merge action adapts. + FrontendAction *AdaptedAction; + + /// \brief The set of AST files to merge. + std::vector ASTFiles; + +protected: + virtual ASTConsumer *CreateASTConsumer(CompilerInstance &CI, + llvm::StringRef InFile); + + virtual bool BeginSourceFileAction(CompilerInstance &CI, + llvm::StringRef Filename); + + virtual void ExecuteAction(); + virtual void EndSourceFileAction(); + +public: + ASTMergeAction(FrontendAction *AdaptedAction, + std::string *ASTFiles, unsigned NumASTFiles); + virtual ~ASTMergeAction(); + + virtual bool usesPreprocessorOnly() const; + virtual bool usesCompleteTranslationUnit(); + virtual bool hasPCHSupport() const; + virtual bool hasASTSupport() const; + virtual bool hasCodeCompletionSupport() const; +}; + //===----------------------------------------------------------------------===// // Code Gen AST Actions //===----------------------------------------------------------------------===// diff --git a/include/clang/Frontend/FrontendOptions.h b/include/clang/Frontend/FrontendOptions.h index 52f639aeb0..80ba77864a 100644 --- a/include/clang/Frontend/FrontendOptions.h +++ b/include/clang/Frontend/FrontendOptions.h @@ -110,6 +110,9 @@ public: /// The list of plugins to load. std::vector Plugins; + /// \brief The list of AST files to merge. + std::vector ASTMergeFiles; + public: FrontendOptions() { DebugCodeCompletionPrinter = 1; diff --git a/lib/AST/ASTImporter.cpp b/lib/AST/ASTImporter.cpp index 7668b7ba67..0d1a0dca75 100644 --- a/lib/AST/ASTImporter.cpp +++ b/lib/AST/ASTImporter.cpp @@ -30,6 +30,7 @@ namespace { explicit ASTNodeImporter(ASTImporter &Importer) : Importer(Importer) { } using TypeVisitor::Visit; + using DeclVisitor::Visit; // Importing types QualType VisitBuiltinType(BuiltinType *T); @@ -440,6 +441,13 @@ Decl *ASTNodeImporter::VisitVarDecl(VarDecl *D) { if (!DC) return 0; + DeclContext *LexicalDC = DC; + if (D->getDeclContext() != D->getLexicalDeclContext()) { + LexicalDC = Importer.ImportContext(D->getLexicalDeclContext()); + if (!LexicalDC) + return 0; + } + // Import the name of this declaration. DeclarationName Name = Importer.Import(D->getDeclName()); if (D->getDeclName() && !Name) @@ -455,11 +463,11 @@ Decl *ASTNodeImporter::VisitVarDecl(VarDecl *D) { // Try to find a variable in our own ("to") context with the same name and // in the same context as the variable we're importing. - if (!D->isFileVarDecl()) { + if (D->isFileVarDecl()) { VarDecl *MergeWithVar = 0; llvm::SmallVector ConflictingDecls; unsigned IDNS = Decl::IDNS_Ordinary; - for (DeclContext::lookup_result Lookup = DC->lookup(D->getDeclName()); + for (DeclContext::lookup_result Lookup = DC->lookup(Name); Lookup.first != Lookup.second; ++Lookup.first) { if (!(*Lookup.first)->isInIdentifierNamespace(IDNS)) @@ -517,16 +525,22 @@ Decl *ASTNodeImporter::VisitVarDecl(VarDecl *D) { TypeSourceInfo *TInfo = 0; if (TypeSourceInfo *FromTInfo = D->getTypeSourceInfo()) { TInfo = Importer.Import(FromTInfo); +#if 0 + // FIXME: Tolerate failures in translation type source + // information, at least until it is implemented. if (!TInfo) return 0; +#endif } // Create the imported variable. VarDecl *ToVar = VarDecl::Create(Importer.getToContext(), DC, Loc, Name.getAsIdentifierInfo(), T, TInfo, D->getStorageClass()); + ToVar->setLexicalDeclContext(LexicalDC); Importer.getImportedDecls()[D] = ToVar; - + LexicalDC->addDecl(ToVar); + // Merge the initializer. // FIXME: Can we really import any initializer? Alternatively, we could force // ourselves to import every declaration of a variable and then only use @@ -542,7 +556,12 @@ Decl *ASTNodeImporter::VisitVarDecl(VarDecl *D) { ASTImporter::ASTImporter(ASTContext &ToContext, Diagnostic &ToDiags, ASTContext &FromContext, Diagnostic &FromDiags) : ToContext(ToContext), FromContext(FromContext), - ToDiags(ToDiags), FromDiags(FromDiags) { } + ToDiags(ToDiags), FromDiags(FromDiags) { + ImportedDecls[FromContext.getTranslationUnitDecl()] + = ToContext.getTranslationUnitDecl(); +} + +ASTImporter::~ASTImporter() { } QualType ASTImporter::Import(QualType FromT) { if (FromT.isNull()) @@ -566,6 +585,73 @@ QualType ASTImporter::Import(QualType FromT) { return ToContext.getQualifiedType(ToT, FromT.getQualifiers()); } +TypeSourceInfo *ASTImporter::Import(TypeSourceInfo *FromTSI) { + // FIXME: Implement! + return 0; +} + +Decl *ASTImporter::Import(Decl *FromD) { + if (!FromD) + return 0; + + // Check whether we've already imported this declaration. + llvm::DenseMap::iterator Pos = ImportedDecls.find(FromD); + if (Pos != ImportedDecls.end()) + return Pos->second; + + // Import the type + ASTNodeImporter Importer(*this); + Decl *ToD = Importer.Visit(FromD); + if (!ToD) + return 0; + + // Record the imported declaration. + ImportedDecls[FromD] = ToD; + return ToD; +} + +DeclContext *ASTImporter::ImportContext(DeclContext *FromDC) { + if (!FromDC) + return FromDC; + + return cast_or_null(Import(cast(FromDC))); +} + +Expr *ASTImporter::Import(Expr *FromE) { + if (!FromE) + return 0; + + return cast_or_null(Import(cast(FromE))); +} + +Stmt *ASTImporter::Import(Stmt *FromS) { + if (!FromS) + return 0; + + // FIXME: Implement! + return 0; +} + +NestedNameSpecifier *ASTImporter::Import(NestedNameSpecifier *FromNNS) { + if (!FromNNS) + return 0; + + // FIXME: Implement! + return 0; +} + +SourceLocation ASTImporter::Import(SourceLocation FromLoc) { + if (FromLoc.isInvalid()) + return SourceLocation(); + + // FIXME: Implement! + return SourceLocation(); +} + +SourceRange ASTImporter::Import(SourceRange FromRange) { + return SourceRange(Import(FromRange.getBegin()), Import(FromRange.getEnd())); +} + DeclarationName ASTImporter::Import(DeclarationName FromName) { if (!FromName) return DeclarationName(); diff --git a/lib/Frontend/ASTMerge.cpp b/lib/Frontend/ASTMerge.cpp new file mode 100644 index 0000000000..649af9e4b8 --- /dev/null +++ b/lib/Frontend/ASTMerge.cpp @@ -0,0 +1,98 @@ +//===-- ASTMerge.cpp - AST Merging Frontent Action --------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +#include "clang/Frontend/ASTUnit.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendActions.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/ASTImporter.h" + +using namespace clang; + +ASTConsumer *ASTMergeAction::CreateASTConsumer(CompilerInstance &CI, + llvm::StringRef InFile) { + return AdaptedAction->CreateASTConsumer(CI, InFile); +} + +bool ASTMergeAction::BeginSourceFileAction(CompilerInstance &CI, + llvm::StringRef Filename) { + // FIXME: This is a hack. We need a better way to communicate the + // AST file, compiler instance, and file name than member variables + // of FrontendAction. + AdaptedAction->setCurrentFile(getCurrentFile(), takeCurrentASTUnit()); + AdaptedAction->setCompilerInstance(&CI); + return AdaptedAction->BeginSourceFileAction(CI, Filename); +} + +void ASTMergeAction::ExecuteAction() { + CompilerInstance &CI = getCompilerInstance(); + + for (unsigned I = 0, N = ASTFiles.size(); I != N; ++I) { + ASTUnit *Unit = ASTUnit::LoadFromPCHFile(ASTFiles[I], CI.getDiagnostics(), + false, true); + if (!Unit) + continue; + + ASTImporter Importer(CI.getASTContext(), CI.getDiagnostics(), + Unit->getASTContext(), CI.getDiagnostics()); + + TranslationUnitDecl *TU = Unit->getASTContext().getTranslationUnitDecl(); + for (DeclContext::decl_iterator D = TU->decls_begin(), + DEnd = TU->decls_end(); + D != DEnd; ++D) { + // FIXME: We only merge variables whose names start with x. Why + // would anyone want anything else? + if (VarDecl *VD = dyn_cast(*D)) + if (VD->getIdentifier() && + *VD->getIdentifier()->getNameStart() == 'x') { + Decl *Merged = Importer.Import(VD); + if (Merged) + Merged->dump(); + } + } + + delete Unit; + } + + + return AdaptedAction->ExecuteAction(); +} + +void ASTMergeAction::EndSourceFileAction() { + return AdaptedAction->EndSourceFileAction(); +} + +ASTMergeAction::ASTMergeAction(FrontendAction *AdaptedAction, + std::string *ASTFiles, unsigned NumASTFiles) + : AdaptedAction(AdaptedAction), ASTFiles(ASTFiles, ASTFiles + NumASTFiles) { + assert(AdaptedAction && "ASTMergeAction needs an action to adapt"); +} + +ASTMergeAction::~ASTMergeAction() { + delete AdaptedAction; +} + +bool ASTMergeAction::usesPreprocessorOnly() const { + return AdaptedAction->usesPreprocessorOnly(); +} + +bool ASTMergeAction::usesCompleteTranslationUnit() { + return AdaptedAction->usesCompleteTranslationUnit(); +} + +bool ASTMergeAction::hasPCHSupport() const { + return AdaptedAction->hasPCHSupport(); +} + +bool ASTMergeAction::hasASTSupport() const { + return AdaptedAction->hasASTSupport(); +} + +bool ASTMergeAction::hasCodeCompletionSupport() const { + return AdaptedAction->hasCodeCompletionSupport(); +} diff --git a/lib/Frontend/CMakeLists.txt b/lib/Frontend/CMakeLists.txt index 58aaa43aab..1d0b5c1204 100644 --- a/lib/Frontend/CMakeLists.txt +++ b/lib/Frontend/CMakeLists.txt @@ -2,6 +2,7 @@ set(LLVM_NO_RTTI 1) add_clang_library(clangFrontend ASTConsumers.cpp + ASTMerge.cpp ASTUnit.cpp AnalysisConsumer.cpp Backend.cpp diff --git a/lib/Frontend/CompilerInvocation.cpp b/lib/Frontend/CompilerInvocation.cpp index 5b64c7e639..006f3c095c 100644 --- a/lib/Frontend/CompilerInvocation.cpp +++ b/lib/Frontend/CompilerInvocation.cpp @@ -367,6 +367,10 @@ static void FrontendOptsToArgs(const FrontendOptions &Opts, Res.push_back("-load"); Res.push_back(Opts.Plugins[i]); } + for (unsigned i = 0, e = Opts.ASTMergeFiles.size(); i != e; ++i) { + Res.push_back("-ast-merge"); + Res.push_back(Opts.ASTMergeFiles[i]); + } } static void HeaderSearchOptsToArgs(const HeaderSearchOptions &Opts, @@ -929,6 +933,7 @@ ParseFrontendArgs(FrontendOptions &Opts, ArgList &Args, Diagnostic &Diags) { Opts.ShowTimers = Args.hasArg(OPT_ftime_report); Opts.ShowVersion = Args.hasArg(OPT_version); Opts.ViewClassInheritance = getLastArgValue(Args, OPT_cxx_inheritance_view); + Opts.ASTMergeFiles = getAllArgValues(Args, OPT_ast_merge); FrontendOptions::InputKind DashX = FrontendOptions::IK_None; if (const Arg *A = Args.getLastArg(OPT_x)) { diff --git a/test/ASTMerge/Inputs/var1.c b/test/ASTMerge/Inputs/var1.c new file mode 100644 index 0000000000..21fc419a8a --- /dev/null +++ b/test/ASTMerge/Inputs/var1.c @@ -0,0 +1,3 @@ +// RUN: true +int *x0; +float **x1; diff --git a/test/ASTMerge/Inputs/var2.c b/test/ASTMerge/Inputs/var2.c new file mode 100644 index 0000000000..34d67968e5 --- /dev/null +++ b/test/ASTMerge/Inputs/var2.c @@ -0,0 +1,3 @@ +// RUN: true +int *x0; +double *x1; diff --git a/test/ASTMerge/var.c b/test/ASTMerge/var.c new file mode 100644 index 0000000000..6e1559a399 --- /dev/null +++ b/test/ASTMerge/var.c @@ -0,0 +1,5 @@ +// RUN: %clang_cc1 -emit-pch -o %t.1.ast %S/Inputs/var1.c +// RUN: %clang_cc1 -emit-pch -o %t.2.ast %S/Inputs/var2.c +// RUN: %clang_cc1 -ast-merge %t.1.ast -ast-merge %t.2.ast -fsyntax-only %s 2>&1 | FileCheck %s + +// CHECK: declared with incompatible types diff --git a/tools/driver/cc1_main.cpp b/tools/driver/cc1_main.cpp index 3852b46a3e..345132ee7d 100644 --- a/tools/driver/cc1_main.cpp +++ b/tools/driver/cc1_main.cpp @@ -51,7 +51,7 @@ void LLVMErrorHandler(void *UserData, const std::string &Message) { exit(1); } -static FrontendAction *CreateFrontendAction(CompilerInstance &CI) { +static FrontendAction *CreateFrontendBaseAction(CompilerInstance &CI) { using namespace clang::frontend; switch (CI.getFrontendOpts().ProgramAction) { @@ -112,6 +112,21 @@ static FrontendAction *CreateFrontendAction(CompilerInstance &CI) { } } +static FrontendAction *CreateFrontendAction(CompilerInstance &CI) { + // Create the underlying action. + FrontendAction *Act = CreateFrontendBaseAction(CI); + if (!Act) + return 0; + + // If there are any AST files to merge, create a frontend action + // adaptor to perform the merge. + if (!CI.getFrontendOpts().ASTMergeFiles.empty()) + Act = new ASTMergeAction(Act, &CI.getFrontendOpts().ASTMergeFiles[0], + CI.getFrontendOpts().ASTMergeFiles.size()); + + return Act; +} + // FIXME: Define the need for this testing away. static int cc1_test(Diagnostic &Diags, const char **ArgBegin, const char **ArgEnd) {