| //===- unittest/AST/ASTImporterFixtures.h - AST unit test support ---------===// |
| // |
| // 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 |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| /// \file |
| /// Fixture classes for testing the ASTImporter. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #ifndef LLVM_CLANG_UNITTESTS_AST_IMPORTER_FIXTURES_H |
| #define LLVM_CLANG_UNITTESTS_AST_IMPORTER_FIXTURES_H |
| |
| #include "gmock/gmock.h" |
| |
| #include "clang/AST/ASTImporter.h" |
| #include "clang/AST/ASTImporterSharedState.h" |
| #include "clang/Frontend/ASTUnit.h" |
| #include "clang/Testing/CommandLineArgs.h" |
| #include "llvm/Support/Error.h" |
| #include "llvm/Support/ErrorHandling.h" |
| |
| #include "DeclMatcher.h" |
| #include "MatchVerifier.h" |
| |
| #include <sstream> |
| |
| namespace clang { |
| |
| class ASTImporter; |
| class ASTImporterSharedState; |
| class ASTUnit; |
| |
| namespace ast_matchers { |
| |
| const StringRef DeclToImportID = "declToImport"; |
| const StringRef DeclToVerifyID = "declToVerify"; |
| |
| // Creates a virtual file and assigns that to the context of given AST. If the |
| // file already exists then the file will not be created again as a duplicate. |
| void createVirtualFileIfNeeded(ASTUnit *ToAST, StringRef FileName, |
| std::unique_ptr<llvm::MemoryBuffer> &&Buffer); |
| |
| void createVirtualFileIfNeeded(ASTUnit *ToAST, StringRef FileName, |
| StringRef Code); |
| |
| // Common base for the different families of ASTImporter tests that are |
| // parameterized on the compiler options which may result a different AST. E.g. |
| // -fms-compatibility or -fdelayed-template-parsing. |
| class CompilerOptionSpecificTest : public ::testing::Test { |
| protected: |
| // Return the extra arguments appended to runtime options at compilation. |
| virtual std::vector<std::string> getExtraArgs() const { return {}; } |
| |
| // Returns the argument vector used for a specific language option, this set |
| // can be tweaked by the test parameters. |
| std::vector<std::string> |
| getCommandLineArgsForLanguage(TestLanguage Lang) const { |
| std::vector<std::string> Args = getCommandLineArgsForTesting(Lang); |
| std::vector<std::string> ExtraArgs = getExtraArgs(); |
| for (const auto &Arg : ExtraArgs) { |
| Args.push_back(Arg); |
| } |
| return Args; |
| } |
| }; |
| |
| const auto DefaultTestArrayForRunOptions = |
| std::array<std::vector<std::string>, 4>{ |
| {std::vector<std::string>(), |
| std::vector<std::string>{"-fdelayed-template-parsing"}, |
| std::vector<std::string>{"-fms-compatibility"}, |
| std::vector<std::string>{"-fdelayed-template-parsing", |
| "-fms-compatibility"}}}; |
| |
| const auto DefaultTestValuesForRunOptions = |
| ::testing::ValuesIn(DefaultTestArrayForRunOptions); |
| |
| // This class provides generic methods to write tests which can check internal |
| // attributes of AST nodes like getPreviousDecl(), isVirtual(), etc. Also, |
| // this fixture makes it possible to import from several "From" contexts. |
| class ASTImporterTestBase : public CompilerOptionSpecificTest { |
| |
| const char *const InputFileName = "input.cc"; |
| const char *const OutputFileName = "output.cc"; |
| |
| public: |
| /// Allocates an ASTImporter (or one of its subclasses). |
| typedef std::function<ASTImporter *( |
| ASTContext &, FileManager &, ASTContext &, FileManager &, bool, |
| const std::shared_ptr<ASTImporterSharedState> &SharedState)> |
| ImporterConstructor; |
| |
| // ODR handling type for the AST importer. |
| ASTImporter::ODRHandlingType ODRHandling; |
| |
| // The lambda that constructs the ASTImporter we use in this test. |
| ImporterConstructor Creator; |
| |
| private: |
| // Buffer for the To context, must live in the test scope. |
| std::string ToCode; |
| |
| // Represents a "From" translation unit and holds an importer object which we |
| // use to import from this translation unit. |
| struct TU { |
| // Buffer for the context, must live in the test scope. |
| std::string Code; |
| std::string FileName; |
| std::unique_ptr<ASTUnit> Unit; |
| TranslationUnitDecl *TUDecl = nullptr; |
| std::unique_ptr<ASTImporter> Importer; |
| ImporterConstructor Creator; |
| ASTImporter::ODRHandlingType ODRHandling; |
| |
| TU(StringRef Code, StringRef FileName, std::vector<std::string> Args, |
| ImporterConstructor C = ImporterConstructor(), |
| ASTImporter::ODRHandlingType ODRHandling = |
| ASTImporter::ODRHandlingType::Conservative); |
| ~TU(); |
| |
| void |
| lazyInitImporter(const std::shared_ptr<ASTImporterSharedState> &SharedState, |
| ASTUnit *ToAST); |
| Decl *import(const std::shared_ptr<ASTImporterSharedState> &SharedState, |
| ASTUnit *ToAST, Decl *FromDecl); |
| llvm::Expected<Decl *> |
| importOrError(const std::shared_ptr<ASTImporterSharedState> &SharedState, |
| ASTUnit *ToAST, Decl *FromDecl); |
| QualType import(const std::shared_ptr<ASTImporterSharedState> &SharedState, |
| ASTUnit *ToAST, QualType FromType); |
| }; |
| |
| // We may have several From contexts and related translation units. In each |
| // AST, the buffers for the source are handled via references and are set |
| // during the creation of the AST. These references must point to a valid |
| // buffer until the AST is alive. Thus, we must use a list in order to avoid |
| // moving of the stored objects because that would mean breaking the |
| // references in the AST. By using a vector a move could happen when the |
| // vector is expanding, with the list we won't have these issues. |
| std::list<TU> FromTUs; |
| |
| // Initialize the shared state if not initialized already. |
| void lazyInitSharedState(TranslationUnitDecl *ToTU); |
| |
| void lazyInitToAST(TestLanguage ToLang, StringRef ToSrcCode, |
| StringRef FileName); |
| |
| protected: |
| std::shared_ptr<ASTImporterSharedState> SharedStatePtr; |
| |
| public: |
| // We may have several From context but only one To context. |
| std::unique_ptr<ASTUnit> ToAST; |
| |
| // Returns with the TU associated with the given Decl. |
| TU *findFromTU(Decl *From); |
| |
| // Creates an AST both for the From and To source code and imports the Decl |
| // of the identifier into the To context. |
| // Must not be called more than once within the same test. |
| std::tuple<Decl *, Decl *> |
| getImportedDecl(StringRef FromSrcCode, TestLanguage FromLang, |
| StringRef ToSrcCode, TestLanguage ToLang, |
| StringRef Identifier = DeclToImportID); |
| |
| // Creates a TU decl for the given source code which can be used as a From |
| // context. May be called several times in a given test (with different file |
| // name). |
| TranslationUnitDecl *getTuDecl(StringRef SrcCode, TestLanguage Lang, |
| StringRef FileName = "input.cc"); |
| |
| // Creates the To context with the given source code and returns the TU decl. |
| TranslationUnitDecl *getToTuDecl(StringRef ToSrcCode, TestLanguage ToLang); |
| |
| // Import the given Decl into the ToCtx. |
| // May be called several times in a given test. |
| // The different instances of the param From may have different ASTContext. |
| Decl *Import(Decl *From, TestLanguage ToLang); |
| |
| template <class DeclT> DeclT *Import(DeclT *From, TestLanguage Lang) { |
| return cast_or_null<DeclT>(Import(cast<Decl>(From), Lang)); |
| } |
| |
| // Import the given Decl into the ToCtx. |
| // Same as Import but returns the result of the import which can be an error. |
| llvm::Expected<Decl *> importOrError(Decl *From, TestLanguage ToLang); |
| |
| QualType ImportType(QualType FromType, Decl *TUDecl, TestLanguage ToLang); |
| |
| ASTImporterTestBase() |
| : ODRHandling(ASTImporter::ODRHandlingType::Conservative) {} |
| ~ASTImporterTestBase(); |
| }; |
| |
| class ASTImporterOptionSpecificTestBase |
| : public ASTImporterTestBase, |
| public ::testing::WithParamInterface<std::vector<std::string>> { |
| protected: |
| std::vector<std::string> getExtraArgs() const override { return GetParam(); } |
| }; |
| |
| // Base class for those tests which use the family of `testImport` functions. |
| class TestImportBase |
| : public CompilerOptionSpecificTest, |
| public ::testing::WithParamInterface<std::vector<std::string>> { |
| |
| template <typename NodeType> |
| llvm::Expected<NodeType> importNode(ASTUnit *From, ASTUnit *To, |
| ASTImporter &Importer, NodeType Node) { |
| ASTContext &ToCtx = To->getASTContext(); |
| |
| // Add 'From' file to virtual file system so importer can 'find' it |
| // while importing SourceLocations. It is safe to add same file multiple |
| // times - it just isn't replaced. |
| StringRef FromFileName = From->getMainFileName(); |
| createVirtualFileIfNeeded(To, FromFileName, |
| From->getBufferForFile(FromFileName)); |
| |
| auto Imported = Importer.Import(Node); |
| |
| if (Imported) { |
| // This should dump source locations and assert if some source locations |
| // were not imported. |
| SmallString<1024> ImportChecker; |
| llvm::raw_svector_ostream ToNothing(ImportChecker); |
| ToCtx.getTranslationUnitDecl()->print(ToNothing); |
| |
| // This traverses the AST to catch certain bugs like poorly or not |
| // implemented subtrees. |
| (*Imported)->dump(ToNothing); |
| } |
| |
| return Imported; |
| } |
| |
| template <typename NodeType> |
| testing::AssertionResult |
| testImport(const std::string &FromCode, |
| const std::vector<std::string> &FromArgs, |
| const std::string &ToCode, const std::vector<std::string> &ToArgs, |
| MatchVerifier<NodeType> &Verifier, |
| const internal::BindableMatcher<NodeType> &SearchMatcher, |
| const internal::BindableMatcher<NodeType> &VerificationMatcher) { |
| const char *const InputFileName = "input.cc"; |
| const char *const OutputFileName = "output.cc"; |
| |
| std::unique_ptr<ASTUnit> FromAST = tooling::buildASTFromCodeWithArgs( |
| FromCode, FromArgs, InputFileName), |
| ToAST = tooling::buildASTFromCodeWithArgs( |
| ToCode, ToArgs, OutputFileName); |
| |
| ASTContext &FromCtx = FromAST->getASTContext(), |
| &ToCtx = ToAST->getASTContext(); |
| |
| ASTImporter Importer(ToCtx, ToAST->getFileManager(), FromCtx, |
| FromAST->getFileManager(), false); |
| |
| auto FoundNodes = match(SearchMatcher, FromCtx); |
| if (FoundNodes.empty()) |
| return testing::AssertionFailure() << "No node was found!"; |
| if (FoundNodes.size() != 1) |
| return testing::AssertionFailure() |
| << "Multiple potential nodes were found!"; |
| |
| auto ToImport = selectFirst<NodeType>(DeclToImportID, FoundNodes); |
| if (!ToImport) |
| return testing::AssertionFailure() << "Node type mismatch!"; |
| |
| // The node being imported should match in the same way as |
| // the result node. |
| internal::BindableMatcher<NodeType> WrapperMatcher(VerificationMatcher); |
| EXPECT_TRUE(Verifier.match(ToImport, WrapperMatcher)); |
| |
| auto Imported = importNode(FromAST.get(), ToAST.get(), Importer, ToImport); |
| if (!Imported) { |
| std::string ErrorText; |
| handleAllErrors(Imported.takeError(), |
| [&ErrorText](const ASTImportError &Err) { |
| ErrorText = Err.message(); |
| }); |
| return testing::AssertionFailure() |
| << "Import failed, error: \"" << ErrorText << "\"!"; |
| } |
| |
| return Verifier.match(*Imported, WrapperMatcher); |
| } |
| |
| template <typename NodeType> |
| testing::AssertionResult |
| testImport(const std::string &FromCode, |
| const std::vector<std::string> &FromArgs, |
| const std::string &ToCode, const std::vector<std::string> &ToArgs, |
| MatchVerifier<NodeType> &Verifier, |
| const internal::BindableMatcher<NodeType> &VerificationMatcher) { |
| return testImport( |
| FromCode, FromArgs, ToCode, ToArgs, Verifier, |
| translationUnitDecl( |
| has(namedDecl(hasName(DeclToImportID)).bind(DeclToImportID))), |
| VerificationMatcher); |
| } |
| |
| protected: |
| std::vector<std::string> getExtraArgs() const override { return GetParam(); } |
| |
| public: |
| /// Test how AST node named "declToImport" located in the translation unit |
| /// of "FromCode" virtual file is imported to "ToCode" virtual file. |
| /// The verification is done by running AMatcher over the imported node. |
| template <typename NodeType, typename MatcherType> |
| void testImport(const std::string &FromCode, TestLanguage FromLang, |
| const std::string &ToCode, TestLanguage ToLang, |
| MatchVerifier<NodeType> &Verifier, |
| const MatcherType &AMatcher) { |
| std::vector<std::string> FromArgs = getCommandLineArgsForLanguage(FromLang); |
| std::vector<std::string> ToArgs = getCommandLineArgsForLanguage(ToLang); |
| EXPECT_TRUE( |
| testImport(FromCode, FromArgs, ToCode, ToArgs, Verifier, AMatcher)); |
| } |
| |
| struct ImportAction { |
| StringRef FromFilename; |
| StringRef ToFilename; |
| // FIXME: Generalize this to support other node kinds. |
| internal::BindableMatcher<Decl> ImportPredicate; |
| |
| ImportAction(StringRef FromFilename, StringRef ToFilename, |
| DeclarationMatcher ImportPredicate) |
| : FromFilename(FromFilename), ToFilename(ToFilename), |
| ImportPredicate(ImportPredicate) {} |
| |
| ImportAction(StringRef FromFilename, StringRef ToFilename, |
| const std::string &DeclName) |
| : FromFilename(FromFilename), ToFilename(ToFilename), |
| ImportPredicate(namedDecl(hasName(DeclName))) {} |
| }; |
| |
| using SingleASTUnit = std::unique_ptr<ASTUnit>; |
| using AllASTUnits = llvm::StringMap<SingleASTUnit>; |
| |
| struct CodeEntry { |
| std::string CodeSample; |
| TestLanguage Lang; |
| }; |
| |
| using CodeFiles = llvm::StringMap<CodeEntry>; |
| |
| /// Builds an ASTUnit for one potential compile options set. |
| SingleASTUnit createASTUnit(StringRef FileName, const CodeEntry &CE) const { |
| std::vector<std::string> Args = getCommandLineArgsForLanguage(CE.Lang); |
| auto AST = tooling::buildASTFromCodeWithArgs(CE.CodeSample, Args, FileName); |
| EXPECT_TRUE(AST.get()); |
| return AST; |
| } |
| |
| /// Test an arbitrary sequence of imports for a set of given in-memory files. |
| /// The verification is done by running VerificationMatcher against a |
| /// specified AST node inside of one of given files. |
| /// \param CodeSamples Map whose key is the file name and the value is the |
| /// file content. |
| /// \param ImportActions Sequence of imports. Each import in sequence |
| /// specifies "from file" and "to file" and a matcher that is used for |
| /// searching a declaration for import in "from file". |
| /// \param FileForFinalCheck Name of virtual file for which the final check is |
| /// applied. |
| /// \param FinalSelectPredicate Matcher that specifies the AST node in the |
| /// FileForFinalCheck for which the verification will be done. |
| /// \param VerificationMatcher Matcher that will be used for verification |
| /// after all imports in sequence are done. |
| void testImportSequence(const CodeFiles &CodeSamples, |
| const std::vector<ImportAction> &ImportActions, |
| StringRef FileForFinalCheck, |
| internal::BindableMatcher<Decl> FinalSelectPredicate, |
| internal::BindableMatcher<Decl> VerificationMatcher) { |
| AllASTUnits AllASTs; |
| using ImporterKey = std::pair<const ASTUnit *, const ASTUnit *>; |
| llvm::DenseMap<ImporterKey, std::unique_ptr<ASTImporter>> Importers; |
| |
| auto GenASTsIfNeeded = [this, &AllASTs, &CodeSamples](StringRef Filename) { |
| if (!AllASTs.count(Filename)) { |
| auto Found = CodeSamples.find(Filename); |
| assert(Found != CodeSamples.end() && "Wrong file for import!"); |
| AllASTs[Filename] = createASTUnit(Filename, Found->getValue()); |
| } |
| }; |
| |
| for (const ImportAction &Action : ImportActions) { |
| StringRef FromFile = Action.FromFilename, ToFile = Action.ToFilename; |
| GenASTsIfNeeded(FromFile); |
| GenASTsIfNeeded(ToFile); |
| |
| ASTUnit *From = AllASTs[FromFile].get(); |
| ASTUnit *To = AllASTs[ToFile].get(); |
| |
| // Create a new importer if needed. |
| std::unique_ptr<ASTImporter> &ImporterRef = Importers[{From, To}]; |
| if (!ImporterRef) |
| ImporterRef.reset(new ASTImporter( |
| To->getASTContext(), To->getFileManager(), From->getASTContext(), |
| From->getFileManager(), false)); |
| |
| // Find the declaration and import it. |
| auto FoundDecl = match(Action.ImportPredicate.bind(DeclToImportID), |
| From->getASTContext()); |
| EXPECT_TRUE(FoundDecl.size() == 1); |
| const Decl *ToImport = selectFirst<Decl>(DeclToImportID, FoundDecl); |
| auto Imported = importNode(From, To, *ImporterRef, ToImport); |
| EXPECT_TRUE(static_cast<bool>(Imported)); |
| if (!Imported) |
| llvm::consumeError(Imported.takeError()); |
| } |
| |
| // Find the declaration and import it. |
| auto FoundDecl = match(FinalSelectPredicate.bind(DeclToVerifyID), |
| AllASTs[FileForFinalCheck]->getASTContext()); |
| EXPECT_TRUE(FoundDecl.size() == 1); |
| const Decl *ToVerify = selectFirst<Decl>(DeclToVerifyID, FoundDecl); |
| MatchVerifier<Decl> Verifier; |
| EXPECT_TRUE(Verifier.match( |
| ToVerify, internal::BindableMatcher<Decl>(VerificationMatcher))); |
| } |
| }; |
| |
| template <typename T> RecordDecl *getRecordDecl(T *D) { |
| auto *ET = cast<ElaboratedType>(D->getType().getTypePtr()); |
| return cast<RecordType>(ET->getNamedType().getTypePtr())->getDecl(); |
| } |
| |
| template <class T> |
| ::testing::AssertionResult isSuccess(llvm::Expected<T> &ValOrErr) { |
| if (ValOrErr) |
| return ::testing::AssertionSuccess() << "Expected<> contains no error."; |
| else |
| return ::testing::AssertionFailure() |
| << "Expected<> contains error: " << toString(ValOrErr.takeError()); |
| } |
| |
| template <class T> |
| ::testing::AssertionResult isImportError(llvm::Expected<T> &ValOrErr, |
| ASTImportError::ErrorKind Kind) { |
| if (ValOrErr) { |
| return ::testing::AssertionFailure() << "Expected<> is expected to contain " |
| "error but does contain value \"" |
| << (*ValOrErr) << "\""; |
| } else { |
| std::ostringstream OS; |
| bool Result = false; |
| auto Err = llvm::handleErrors( |
| ValOrErr.takeError(), [&OS, &Result, Kind](clang::ASTImportError &IE) { |
| if (IE.Error == Kind) { |
| Result = true; |
| OS << "Expected<> contains an ImportError " << IE.toString(); |
| } else { |
| OS << "Expected<> contains an ImportError " << IE.toString() |
| << " instead of kind " << Kind; |
| } |
| }); |
| if (Err) { |
| OS << "Expected<> contains unexpected error: " |
| << toString(std::move(Err)); |
| } |
| if (Result) |
| return ::testing::AssertionSuccess() << OS.str(); |
| else |
| return ::testing::AssertionFailure() << OS.str(); |
| } |
| } |
| |
| } // end namespace ast_matchers |
| } // end namespace clang |
| |
| #endif |