475 lines · c
1//===- unittest/AST/ASTImporterFixtures.h - AST unit test support ---------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8//9/// \file10/// Fixture classes for testing the ASTImporter.11//12//===----------------------------------------------------------------------===//13 14#ifndef LLVM_CLANG_UNITTESTS_AST_IMPORTER_FIXTURES_H15#define LLVM_CLANG_UNITTESTS_AST_IMPORTER_FIXTURES_H16 17#include "gmock/gmock.h"18 19#include "clang/AST/ASTImporter.h"20#include "clang/AST/ASTImporterSharedState.h"21#include "clang/Frontend/ASTUnit.h"22#include "clang/Testing/CommandLineArgs.h"23#include "llvm/Support/Error.h"24#include "llvm/Support/ErrorHandling.h"25 26#include "DeclMatcher.h"27#include "MatchVerifier.h"28 29#include <sstream>30 31namespace clang {32 33class ASTImporter;34class ASTImporterSharedState;35class ASTUnit;36 37namespace ast_matchers {38 39const StringRef DeclToImportID = "declToImport";40const StringRef DeclToVerifyID = "declToVerify";41 42// Creates a virtual file and assigns that to the context of given AST. If the43// file already exists then the file will not be created again as a duplicate.44void createVirtualFileIfNeeded(ASTUnit *ToAST, StringRef FileName,45 std::unique_ptr<llvm::MemoryBuffer> &&Buffer);46 47void createVirtualFileIfNeeded(ASTUnit *ToAST, StringRef FileName,48 StringRef Code);49 50// Common base for the different families of ASTImporter tests that are51// parameterized on the compiler options which may result a different AST. E.g.52// -fms-compatibility or -fdelayed-template-parsing.53class CompilerOptionSpecificTest : public ::testing::Test {54protected:55 // Return the extra arguments appended to runtime options at compilation.56 virtual std::vector<std::string> getExtraArgs() const { return {}; }57 58 // Returns the argument vector used for a specific language option, this set59 // can be tweaked by the test parameters.60 std::vector<std::string>61 getCommandLineArgsForLanguage(TestLanguage Lang) const {62 std::vector<std::string> Args = getCommandLineArgsForTesting(Lang);63 std::vector<std::string> ExtraArgs = getExtraArgs();64 for (const auto &Arg : ExtraArgs) {65 Args.push_back(Arg);66 }67 return Args;68 }69};70 71const auto DefaultTestArrayForRunOptions =72 std::array<std::vector<std::string>, 4>{73 {std::vector<std::string>(),74 std::vector<std::string>{"-fdelayed-template-parsing"},75 std::vector<std::string>{"-fms-compatibility"},76 std::vector<std::string>{"-fdelayed-template-parsing",77 "-fms-compatibility"}}};78 79const auto DefaultTestValuesForRunOptions =80 ::testing::ValuesIn(DefaultTestArrayForRunOptions);81 82// This class provides generic methods to write tests which can check internal83// attributes of AST nodes like getPreviousDecl(), isVirtual(), etc. Also,84// this fixture makes it possible to import from several "From" contexts.85class ASTImporterTestBase : public CompilerOptionSpecificTest {86 87 const char *const InputFileName = "input.cc";88 const char *const OutputFileName = "output.cc";89 90public:91 /// Allocates an ASTImporter (or one of its subclasses).92 typedef std::function<ASTImporter *(93 ASTContext &, FileManager &, ASTContext &, FileManager &, bool,94 const std::shared_ptr<ASTImporterSharedState> &SharedState)>95 ImporterConstructor;96 97 // ODR handling type for the AST importer.98 ASTImporter::ODRHandlingType ODRHandling;99 100 // The lambda that constructs the ASTImporter we use in this test.101 ImporterConstructor Creator;102 103private:104 // Buffer for the To context, must live in the test scope.105 std::string ToCode;106 107 // Represents a "From" translation unit and holds an importer object which we108 // use to import from this translation unit.109 struct TU {110 // Buffer for the context, must live in the test scope.111 std::string Code;112 std::string FileName;113 std::unique_ptr<ASTUnit> Unit;114 TranslationUnitDecl *TUDecl = nullptr;115 std::unique_ptr<ASTImporter> Importer;116 ImporterConstructor Creator;117 ASTImporter::ODRHandlingType ODRHandling;118 119 TU(StringRef Code, StringRef FileName, std::vector<std::string> Args,120 ImporterConstructor C = ImporterConstructor(),121 ASTImporter::ODRHandlingType ODRHandling =122 ASTImporter::ODRHandlingType::Conservative);123 ~TU();124 125 void126 lazyInitImporter(const std::shared_ptr<ASTImporterSharedState> &SharedState,127 ASTUnit *ToAST);128 Decl *import(const std::shared_ptr<ASTImporterSharedState> &SharedState,129 ASTUnit *ToAST, Decl *FromDecl);130 llvm::Expected<Decl *>131 importOrError(const std::shared_ptr<ASTImporterSharedState> &SharedState,132 ASTUnit *ToAST, Decl *FromDecl);133 QualType import(const std::shared_ptr<ASTImporterSharedState> &SharedState,134 ASTUnit *ToAST, QualType FromType);135 };136 137 // We may have several From contexts and related translation units. In each138 // AST, the buffers for the source are handled via references and are set139 // during the creation of the AST. These references must point to a valid140 // buffer until the AST is alive. Thus, we must use a list in order to avoid141 // moving of the stored objects because that would mean breaking the142 // references in the AST. By using a vector a move could happen when the143 // vector is expanding, with the list we won't have these issues.144 std::list<TU> FromTUs;145 146 // Initialize the shared state if not initialized already.147 void lazyInitSharedState(TranslationUnitDecl *ToTU);148 149 void lazyInitToAST(TestLanguage ToLang, StringRef ToSrcCode,150 StringRef FileName);151 152protected:153 std::shared_ptr<ASTImporterSharedState> SharedStatePtr;154 155public:156 // We may have several From context but only one To context.157 std::unique_ptr<ASTUnit> ToAST;158 159 // Returns with the TU associated with the given Decl.160 TU *findFromTU(Decl *From);161 162 // Creates an AST both for the From and To source code and imports the Decl163 // of the identifier into the To context.164 // Must not be called more than once within the same test.165 std::tuple<Decl *, Decl *>166 getImportedDecl(StringRef FromSrcCode, TestLanguage FromLang,167 StringRef ToSrcCode, TestLanguage ToLang,168 StringRef Identifier = DeclToImportID);169 170 // Creates a TU decl for the given source code which can be used as a From171 // context. May be called several times in a given test (with different file172 // name).173 TranslationUnitDecl *getTuDecl(StringRef SrcCode, TestLanguage Lang,174 StringRef FileName = "input.cc");175 176 // Creates the To context with the given source code and returns the TU decl.177 TranslationUnitDecl *getToTuDecl(StringRef ToSrcCode, TestLanguage ToLang);178 179 // Import the given Decl into the ToCtx.180 // May be called several times in a given test.181 // The different instances of the param From may have different ASTContext.182 Decl *Import(Decl *From, TestLanguage ToLang);183 184 template <class DeclT> DeclT *Import(DeclT *From, TestLanguage Lang) {185 return cast_or_null<DeclT>(Import(cast<Decl>(From), Lang));186 }187 188 // Import the given Decl into the ToCtx.189 // Same as Import but returns the result of the import which can be an error.190 llvm::Expected<Decl *> importOrError(Decl *From, TestLanguage ToLang);191 192 QualType ImportType(QualType FromType, Decl *TUDecl, TestLanguage ToLang);193 194 ASTImporterTestBase()195 : ODRHandling(ASTImporter::ODRHandlingType::Conservative) {}196 ~ASTImporterTestBase();197};198 199class ASTImporterOptionSpecificTestBase200 : public ASTImporterTestBase,201 public ::testing::WithParamInterface<std::vector<std::string>> {202protected:203 std::vector<std::string> getExtraArgs() const override { return GetParam(); }204};205 206// Base class for those tests which use the family of `testImport` functions.207class TestImportBase208 : public CompilerOptionSpecificTest,209 public ::testing::WithParamInterface<std::vector<std::string>> {210 211 template <typename NodeType>212 llvm::Expected<NodeType> importNode(ASTUnit *From, ASTUnit *To,213 ASTImporter &Importer, NodeType Node) {214 ASTContext &ToCtx = To->getASTContext();215 216 // Add 'From' file to virtual file system so importer can 'find' it217 // while importing SourceLocations. It is safe to add same file multiple218 // times - it just isn't replaced.219 StringRef FromFileName = From->getMainFileName();220 createVirtualFileIfNeeded(To, FromFileName,221 From->getBufferForFile(FromFileName));222 223 auto Imported = Importer.Import(Node);224 225 if (Imported) {226 // This should dump source locations and assert if some source locations227 // were not imported.228 SmallString<1024> ImportChecker;229 llvm::raw_svector_ostream ToNothing(ImportChecker);230 ToCtx.getTranslationUnitDecl()->print(ToNothing);231 232 // This traverses the AST to catch certain bugs like poorly or not233 // implemented subtrees.234 (*Imported)->dump(ToNothing);235 }236 237 return Imported;238 }239 240 template <typename NodeType>241 testing::AssertionResult242 testImport(const std::string &FromCode,243 const std::vector<std::string> &FromArgs,244 const std::string &ToCode, const std::vector<std::string> &ToArgs,245 MatchVerifier<NodeType> &Verifier,246 const internal::BindableMatcher<NodeType> &SearchMatcher,247 const internal::BindableMatcher<NodeType> &VerificationMatcher) {248 const char *const InputFileName = "input.cc";249 const char *const OutputFileName = "output.cc";250 251 std::unique_ptr<ASTUnit> FromAST = tooling::buildASTFromCodeWithArgs(252 FromCode, FromArgs, InputFileName),253 ToAST = tooling::buildASTFromCodeWithArgs(254 ToCode, ToArgs, OutputFileName);255 256 ASTContext &FromCtx = FromAST->getASTContext(),257 &ToCtx = ToAST->getASTContext();258 259 ASTImporter Importer(ToCtx, ToAST->getFileManager(), FromCtx,260 FromAST->getFileManager(), false);261 262 auto FoundNodes = match(SearchMatcher, FromCtx);263 if (FoundNodes.empty())264 return testing::AssertionFailure() << "No node was found!";265 if (FoundNodes.size() != 1)266 return testing::AssertionFailure()267 << "Multiple potential nodes were found!";268 269 auto ToImport = selectFirst<NodeType>(DeclToImportID, FoundNodes);270 if (!ToImport)271 return testing::AssertionFailure() << "Node type mismatch!";272 273 // The node being imported should match in the same way as274 // the result node.275 internal::BindableMatcher<NodeType> WrapperMatcher(VerificationMatcher);276 EXPECT_TRUE(Verifier.match(ToImport, WrapperMatcher));277 278 auto Imported = importNode(FromAST.get(), ToAST.get(), Importer, ToImport);279 if (!Imported) {280 std::string ErrorText;281 handleAllErrors(Imported.takeError(),282 [&ErrorText](const ASTImportError &Err) {283 ErrorText = Err.message();284 });285 return testing::AssertionFailure()286 << "Import failed, error: \"" << ErrorText << "\"!";287 }288 289 return Verifier.match(*Imported, WrapperMatcher);290 }291 292 template <typename NodeType>293 testing::AssertionResult294 testImport(const std::string &FromCode,295 const std::vector<std::string> &FromArgs,296 const std::string &ToCode, const std::vector<std::string> &ToArgs,297 MatchVerifier<NodeType> &Verifier,298 const internal::BindableMatcher<NodeType> &VerificationMatcher) {299 return testImport(300 FromCode, FromArgs, ToCode, ToArgs, Verifier,301 translationUnitDecl(302 has(namedDecl(hasName(DeclToImportID)).bind(DeclToImportID))),303 VerificationMatcher);304 }305 306protected:307 std::vector<std::string> getExtraArgs() const override { return GetParam(); }308 309public:310 /// Test how AST node named "declToImport" located in the translation unit311 /// of "FromCode" virtual file is imported to "ToCode" virtual file.312 /// The verification is done by running AMatcher over the imported node.313 template <typename NodeType, typename MatcherType>314 void testImport(const std::string &FromCode, TestLanguage FromLang,315 const std::string &ToCode, TestLanguage ToLang,316 MatchVerifier<NodeType> &Verifier,317 const MatcherType &AMatcher) {318 std::vector<std::string> FromArgs = getCommandLineArgsForLanguage(FromLang);319 std::vector<std::string> ToArgs = getCommandLineArgsForLanguage(ToLang);320 EXPECT_TRUE(321 testImport(FromCode, FromArgs, ToCode, ToArgs, Verifier, AMatcher));322 }323 324 struct ImportAction {325 StringRef FromFilename;326 StringRef ToFilename;327 // FIXME: Generalize this to support other node kinds.328 internal::BindableMatcher<Decl> ImportPredicate;329 330 ImportAction(StringRef FromFilename, StringRef ToFilename,331 DeclarationMatcher ImportPredicate)332 : FromFilename(FromFilename), ToFilename(ToFilename),333 ImportPredicate(ImportPredicate) {}334 335 ImportAction(StringRef FromFilename, StringRef ToFilename,336 const std::string &DeclName)337 : FromFilename(FromFilename), ToFilename(ToFilename),338 ImportPredicate(namedDecl(hasName(DeclName))) {}339 };340 341 using SingleASTUnit = std::unique_ptr<ASTUnit>;342 using AllASTUnits = llvm::StringMap<SingleASTUnit>;343 344 struct CodeEntry {345 std::string CodeSample;346 TestLanguage Lang;347 };348 349 using CodeFiles = llvm::StringMap<CodeEntry>;350 351 /// Builds an ASTUnit for one potential compile options set.352 SingleASTUnit createASTUnit(StringRef FileName, const CodeEntry &CE) const {353 std::vector<std::string> Args = getCommandLineArgsForLanguage(CE.Lang);354 auto AST = tooling::buildASTFromCodeWithArgs(CE.CodeSample, Args, FileName);355 EXPECT_TRUE(AST.get());356 return AST;357 }358 359 /// Test an arbitrary sequence of imports for a set of given in-memory files.360 /// The verification is done by running VerificationMatcher against a361 /// specified AST node inside of one of given files.362 /// \param CodeSamples Map whose key is the file name and the value is the363 /// file content.364 /// \param ImportActions Sequence of imports. Each import in sequence365 /// specifies "from file" and "to file" and a matcher that is used for366 /// searching a declaration for import in "from file".367 /// \param FileForFinalCheck Name of virtual file for which the final check is368 /// applied.369 /// \param FinalSelectPredicate Matcher that specifies the AST node in the370 /// FileForFinalCheck for which the verification will be done.371 /// \param VerificationMatcher Matcher that will be used for verification372 /// after all imports in sequence are done.373 void testImportSequence(const CodeFiles &CodeSamples,374 const std::vector<ImportAction> &ImportActions,375 StringRef FileForFinalCheck,376 internal::BindableMatcher<Decl> FinalSelectPredicate,377 internal::BindableMatcher<Decl> VerificationMatcher) {378 AllASTUnits AllASTs;379 using ImporterKey = std::pair<const ASTUnit *, const ASTUnit *>;380 llvm::DenseMap<ImporterKey, std::unique_ptr<ASTImporter>> Importers;381 382 auto GenASTsIfNeeded = [this, &AllASTs, &CodeSamples](StringRef Filename) {383 if (!AllASTs.count(Filename)) {384 auto Found = CodeSamples.find(Filename);385 assert(Found != CodeSamples.end() && "Wrong file for import!");386 AllASTs[Filename] = createASTUnit(Filename, Found->getValue());387 }388 };389 390 for (const ImportAction &Action : ImportActions) {391 StringRef FromFile = Action.FromFilename, ToFile = Action.ToFilename;392 GenASTsIfNeeded(FromFile);393 GenASTsIfNeeded(ToFile);394 395 ASTUnit *From = AllASTs[FromFile].get();396 ASTUnit *To = AllASTs[ToFile].get();397 398 // Create a new importer if needed.399 std::unique_ptr<ASTImporter> &ImporterRef = Importers[{From, To}];400 if (!ImporterRef)401 ImporterRef.reset(new ASTImporter(402 To->getASTContext(), To->getFileManager(), From->getASTContext(),403 From->getFileManager(), false));404 405 // Find the declaration and import it.406 auto FoundDecl = match(Action.ImportPredicate.bind(DeclToImportID),407 From->getASTContext());408 EXPECT_TRUE(FoundDecl.size() == 1);409 const Decl *ToImport = selectFirst<Decl>(DeclToImportID, FoundDecl);410 auto Imported = importNode(From, To, *ImporterRef, ToImport);411 EXPECT_TRUE(static_cast<bool>(Imported));412 if (!Imported)413 llvm::consumeError(Imported.takeError());414 }415 416 // Find the declaration and import it.417 auto FoundDecl = match(FinalSelectPredicate.bind(DeclToVerifyID),418 AllASTs[FileForFinalCheck]->getASTContext());419 EXPECT_TRUE(FoundDecl.size() == 1);420 const Decl *ToVerify = selectFirst<Decl>(DeclToVerifyID, FoundDecl);421 MatchVerifier<Decl> Verifier;422 EXPECT_TRUE(Verifier.match(423 ToVerify, internal::BindableMatcher<Decl>(VerificationMatcher)));424 }425};426 427template <typename T> RecordDecl *getRecordDecl(T *D) {428 return D->getType()->getAsRecordDecl();429}430 431template <class T>432::testing::AssertionResult isSuccess(llvm::Expected<T> &ValOrErr) {433 if (ValOrErr)434 return ::testing::AssertionSuccess() << "Expected<> contains no error.";435 else436 return ::testing::AssertionFailure()437 << "Expected<> contains error: " << toString(ValOrErr.takeError());438}439 440template <class T>441::testing::AssertionResult isImportError(llvm::Expected<T> &ValOrErr,442 ASTImportError::ErrorKind Kind) {443 if (ValOrErr) {444 return ::testing::AssertionFailure() << "Expected<> is expected to contain "445 "error but does contain value \""446 << (*ValOrErr) << "\"";447 } else {448 std::ostringstream OS;449 bool Result = false;450 auto Err = llvm::handleErrors(451 ValOrErr.takeError(), [&OS, &Result, Kind](clang::ASTImportError &IE) {452 if (IE.Error == Kind) {453 Result = true;454 OS << "Expected<> contains an ImportError " << IE.toString();455 } else {456 OS << "Expected<> contains an ImportError " << IE.toString()457 << " instead of kind " << Kind;458 }459 });460 if (Err) {461 OS << "Expected<> contains unexpected error: "462 << toString(std::move(Err));463 }464 if (Result)465 return ::testing::AssertionSuccess() << OS.str();466 else467 return ::testing::AssertionFailure() << OS.str();468 }469}470 471} // end namespace ast_matchers472} // end namespace clang473 474#endif475