xref: /llvm-project/clang/unittests/AST/ASTImporterFixtures.h (revision db92fb8726fc09e6d76346a02169685025fd59ca)
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-exception
6 //
7 //===----------------------------------------------------------------------===//
8 //
9 /// \file
10 /// Fixture classes for testing the ASTImporter.
11 //
12 //===----------------------------------------------------------------------===//
13 
14 #ifndef LLVM_CLANG_UNITTESTS_AST_IMPORTER_FIXTURES_H
15 #define LLVM_CLANG_UNITTESTS_AST_IMPORTER_FIXTURES_H
16 
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 
31 namespace clang {
32 
33 class ASTImporter;
34 class ASTImporterSharedState;
35 class ASTUnit;
36 
37 namespace ast_matchers {
38 
39 const StringRef DeclToImportID = "declToImport";
40 const StringRef DeclToVerifyID = "declToVerify";
41 
42 // Creates a virtual file and assigns that to the context of given AST. If the
43 // file already exists then the file will not be created again as a duplicate.
44 void createVirtualFileIfNeeded(ASTUnit *ToAST, StringRef FileName,
45                                std::unique_ptr<llvm::MemoryBuffer> &&Buffer);
46 
47 void createVirtualFileIfNeeded(ASTUnit *ToAST, StringRef FileName,
48                                StringRef Code);
49 
50 // Common base for the different families of ASTImporter tests that are
51 // parameterized on the compiler options which may result a different AST. E.g.
52 // -fms-compatibility or -fdelayed-template-parsing.
53 class CompilerOptionSpecificTest : public ::testing::Test {
54 protected:
55   // Return the extra arguments appended to runtime options at compilation.
getExtraArgs()56   virtual std::vector<std::string> getExtraArgs() const { return {}; }
57 
58   // Returns the argument vector used for a specific language option, this set
59   // can be tweaked by the test parameters.
60   std::vector<std::string>
getCommandLineArgsForLanguage(TestLanguage Lang)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 
71 const 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 
79 const auto DefaultTestValuesForRunOptions =
80     ::testing::ValuesIn(DefaultTestArrayForRunOptions);
81 
82 // This class provides generic methods to write tests which can check internal
83 // attributes of AST nodes like getPreviousDecl(), isVirtual(), etc. Also,
84 // this fixture makes it possible to import from several "From" contexts.
85 class ASTImporterTestBase : public CompilerOptionSpecificTest {
86 
87   const char *const InputFileName = "input.cc";
88   const char *const OutputFileName = "output.cc";
89 
90 public:
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 
103 private:
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 we
108   // 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     void
126     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 each
138   // AST, the buffers for the source are handled via references and are set
139   // during the creation of the AST. These references must point to a valid
140   // buffer until the AST is alive. Thus, we must use a list in order to avoid
141   // moving of the stored objects because that would mean breaking the
142   // references in the AST. By using a vector a move could happen when the
143   // 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 
152 protected:
153   std::shared_ptr<ASTImporterSharedState> SharedStatePtr;
154 
155 public:
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 Decl
163   // 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 From
171   // context.  May be called several times in a given test (with different file
172   // 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 
Import(DeclT * From,TestLanguage Lang)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 
ASTImporterTestBase()194   ASTImporterTestBase()
195       : ODRHandling(ASTImporter::ODRHandlingType::Conservative) {}
196   ~ASTImporterTestBase();
197 };
198 
199 class ASTImporterOptionSpecificTestBase
200     : public ASTImporterTestBase,
201       public ::testing::WithParamInterface<std::vector<std::string>> {
202 protected:
getExtraArgs()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.
207 class TestImportBase
208     : public CompilerOptionSpecificTest,
209       public ::testing::WithParamInterface<std::vector<std::string>> {
210 
211   template <typename NodeType>
importNode(ASTUnit * From,ASTUnit * To,ASTImporter & Importer,NodeType Node)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' it
217     // while importing SourceLocations. It is safe to add same file multiple
218     // 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 locations
227       // 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 not
233       // implemented subtrees.
234       (*Imported)->dump(ToNothing);
235     }
236 
237     return Imported;
238   }
239 
240   template <typename NodeType>
241   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)242   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 as
274     // 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::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)294   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 
306 protected:
getExtraArgs()307   std::vector<std::string> getExtraArgs() const override { return GetParam(); }
308 
309 public:
310   /// Test how AST node named "declToImport" located in the translation unit
311   /// 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>
testImport(const std::string & FromCode,TestLanguage FromLang,const std::string & ToCode,TestLanguage ToLang,MatchVerifier<NodeType> & Verifier,const MatcherType & AMatcher)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 
ImportActionImportAction330     ImportAction(StringRef FromFilename, StringRef ToFilename,
331                  DeclarationMatcher ImportPredicate)
332         : FromFilename(FromFilename), ToFilename(ToFilename),
333           ImportPredicate(ImportPredicate) {}
334 
ImportActionImportAction335     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.
createASTUnit(StringRef FileName,const CodeEntry & CE)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 a
361   /// specified AST node inside of one of given files.
362   /// \param CodeSamples Map whose key is the file name and the value is the
363   /// file content.
364   /// \param ImportActions Sequence of imports. Each import in sequence
365   /// specifies "from file" and "to file" and a matcher that is used for
366   /// searching a declaration for import in "from file".
367   /// \param FileForFinalCheck Name of virtual file for which the final check is
368   /// applied.
369   /// \param FinalSelectPredicate Matcher that specifies the AST node in the
370   /// FileForFinalCheck for which the verification will be done.
371   /// \param VerificationMatcher Matcher that will be used for verification
372   /// after all imports in sequence are done.
testImportSequence(const CodeFiles & CodeSamples,const std::vector<ImportAction> & ImportActions,StringRef FileForFinalCheck,internal::BindableMatcher<Decl> FinalSelectPredicate,internal::BindableMatcher<Decl> VerificationMatcher)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 
getRecordDecl(T * D)427 template <typename T> RecordDecl *getRecordDecl(T *D) {
428   auto *ET = cast<ElaboratedType>(D->getType().getTypePtr());
429   return cast<RecordType>(ET->getNamedType().getTypePtr())->getDecl();
430 }
431 
432 template <class T>
isSuccess(llvm::Expected<T> & ValOrErr)433 ::testing::AssertionResult isSuccess(llvm::Expected<T> &ValOrErr) {
434   if (ValOrErr)
435     return ::testing::AssertionSuccess() << "Expected<> contains no error.";
436   else
437     return ::testing::AssertionFailure()
438            << "Expected<> contains error: " << toString(ValOrErr.takeError());
439 }
440 
441 template <class T>
isImportError(llvm::Expected<T> & ValOrErr,ASTImportError::ErrorKind Kind)442 ::testing::AssertionResult isImportError(llvm::Expected<T> &ValOrErr,
443                                          ASTImportError::ErrorKind Kind) {
444   if (ValOrErr) {
445     return ::testing::AssertionFailure() << "Expected<> is expected to contain "
446                                             "error but does contain value \""
447                                          << (*ValOrErr) << "\"";
448   } else {
449     std::ostringstream OS;
450     bool Result = false;
451     auto Err = llvm::handleErrors(
452         ValOrErr.takeError(), [&OS, &Result, Kind](clang::ASTImportError &IE) {
453           if (IE.Error == Kind) {
454             Result = true;
455             OS << "Expected<> contains an ImportError " << IE.toString();
456           } else {
457             OS << "Expected<> contains an ImportError " << IE.toString()
458                << " instead of kind " << Kind;
459           }
460         });
461     if (Err) {
462       OS << "Expected<> contains unexpected error: "
463          << toString(std::move(Err));
464     }
465     if (Result)
466       return ::testing::AssertionSuccess() << OS.str();
467     else
468       return ::testing::AssertionFailure() << OS.str();
469   }
470 }
471 
472 } // end namespace ast_matchers
473 } // end namespace clang
474 
475 #endif
476