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.size() != 1)
264 return testing::AssertionFailure()
265 << "Multiple potential nodes were found!";
266
267 auto ToImport = selectFirst<NodeType>(DeclToImportID, FoundNodes);
268 if (!ToImport)
269 return testing::AssertionFailure() << "Node type mismatch!";
270
271 // Sanity check: the node being imported should match in the same way as
272 // the result node.
273 internal::BindableMatcher<NodeType> WrapperMatcher(VerificationMatcher);
274 EXPECT_TRUE(Verifier.match(ToImport, WrapperMatcher));
275
276 auto Imported = importNode(FromAST.get(), ToAST.get(), Importer, ToImport);
277 if (!Imported) {
278 std::string ErrorText;
279 handleAllErrors(
280 Imported.takeError(),
281 [&ErrorText](const ImportError &Err) { ErrorText = Err.message(); });
282 return testing::AssertionFailure()
283 << "Import failed, error: \"" << ErrorText << "\"!";
284 }
285
286 return Verifier.match(*Imported, WrapperMatcher);
287 }
288
289 template <typename NodeType>
290 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)291 testImport(const std::string &FromCode,
292 const std::vector<std::string> &FromArgs,
293 const std::string &ToCode, const std::vector<std::string> &ToArgs,
294 MatchVerifier<NodeType> &Verifier,
295 const internal::BindableMatcher<NodeType> &VerificationMatcher) {
296 return testImport(
297 FromCode, FromArgs, ToCode, ToArgs, Verifier,
298 translationUnitDecl(
299 has(namedDecl(hasName(DeclToImportID)).bind(DeclToImportID))),
300 VerificationMatcher);
301 }
302
303 protected:
getExtraArgs()304 std::vector<std::string> getExtraArgs() const override { return GetParam(); }
305
306 public:
307 /// Test how AST node named "declToImport" located in the translation unit
308 /// of "FromCode" virtual file is imported to "ToCode" virtual file.
309 /// The verification is done by running AMatcher over the imported node.
310 template <typename NodeType, typename MatcherType>
testImport(const std::string & FromCode,TestLanguage FromLang,const std::string & ToCode,TestLanguage ToLang,MatchVerifier<NodeType> & Verifier,const MatcherType & AMatcher)311 void testImport(const std::string &FromCode, TestLanguage FromLang,
312 const std::string &ToCode, TestLanguage ToLang,
313 MatchVerifier<NodeType> &Verifier,
314 const MatcherType &AMatcher) {
315 std::vector<std::string> FromArgs = getCommandLineArgsForLanguage(FromLang);
316 std::vector<std::string> ToArgs = getCommandLineArgsForLanguage(ToLang);
317 EXPECT_TRUE(
318 testImport(FromCode, FromArgs, ToCode, ToArgs, Verifier, AMatcher));
319 }
320
321 struct ImportAction {
322 StringRef FromFilename;
323 StringRef ToFilename;
324 // FIXME: Generalize this to support other node kinds.
325 internal::BindableMatcher<Decl> ImportPredicate;
326
ImportActionImportAction327 ImportAction(StringRef FromFilename, StringRef ToFilename,
328 DeclarationMatcher ImportPredicate)
329 : FromFilename(FromFilename), ToFilename(ToFilename),
330 ImportPredicate(ImportPredicate) {}
331
ImportActionImportAction332 ImportAction(StringRef FromFilename, StringRef ToFilename,
333 const std::string &DeclName)
334 : FromFilename(FromFilename), ToFilename(ToFilename),
335 ImportPredicate(namedDecl(hasName(DeclName))) {}
336 };
337
338 using SingleASTUnit = std::unique_ptr<ASTUnit>;
339 using AllASTUnits = llvm::StringMap<SingleASTUnit>;
340
341 struct CodeEntry {
342 std::string CodeSample;
343 TestLanguage Lang;
344 };
345
346 using CodeFiles = llvm::StringMap<CodeEntry>;
347
348 /// Builds an ASTUnit for one potential compile options set.
createASTUnit(StringRef FileName,const CodeEntry & CE)349 SingleASTUnit createASTUnit(StringRef FileName, const CodeEntry &CE) const {
350 std::vector<std::string> Args = getCommandLineArgsForLanguage(CE.Lang);
351 auto AST = tooling::buildASTFromCodeWithArgs(CE.CodeSample, Args, FileName);
352 EXPECT_TRUE(AST.get());
353 return AST;
354 }
355
356 /// Test an arbitrary sequence of imports for a set of given in-memory files.
357 /// The verification is done by running VerificationMatcher against a
358 /// specified AST node inside of one of given files.
359 /// \param CodeSamples Map whose key is the file name and the value is the
360 /// file content.
361 /// \param ImportActions Sequence of imports. Each import in sequence
362 /// specifies "from file" and "to file" and a matcher that is used for
363 /// searching a declaration for import in "from file".
364 /// \param FileForFinalCheck Name of virtual file for which the final check is
365 /// applied.
366 /// \param FinalSelectPredicate Matcher that specifies the AST node in the
367 /// FileForFinalCheck for which the verification will be done.
368 /// \param VerificationMatcher Matcher that will be used for verification
369 /// 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)370 void testImportSequence(const CodeFiles &CodeSamples,
371 const std::vector<ImportAction> &ImportActions,
372 StringRef FileForFinalCheck,
373 internal::BindableMatcher<Decl> FinalSelectPredicate,
374 internal::BindableMatcher<Decl> VerificationMatcher) {
375 AllASTUnits AllASTs;
376 using ImporterKey = std::pair<const ASTUnit *, const ASTUnit *>;
377 llvm::DenseMap<ImporterKey, std::unique_ptr<ASTImporter>> Importers;
378
379 auto GenASTsIfNeeded = [this, &AllASTs, &CodeSamples](StringRef Filename) {
380 if (!AllASTs.count(Filename)) {
381 auto Found = CodeSamples.find(Filename);
382 assert(Found != CodeSamples.end() && "Wrong file for import!");
383 AllASTs[Filename] = createASTUnit(Filename, Found->getValue());
384 }
385 };
386
387 for (const ImportAction &Action : ImportActions) {
388 StringRef FromFile = Action.FromFilename, ToFile = Action.ToFilename;
389 GenASTsIfNeeded(FromFile);
390 GenASTsIfNeeded(ToFile);
391
392 ASTUnit *From = AllASTs[FromFile].get();
393 ASTUnit *To = AllASTs[ToFile].get();
394
395 // Create a new importer if needed.
396 std::unique_ptr<ASTImporter> &ImporterRef = Importers[{From, To}];
397 if (!ImporterRef)
398 ImporterRef.reset(new ASTImporter(
399 To->getASTContext(), To->getFileManager(), From->getASTContext(),
400 From->getFileManager(), false));
401
402 // Find the declaration and import it.
403 auto FoundDecl = match(Action.ImportPredicate.bind(DeclToImportID),
404 From->getASTContext());
405 EXPECT_TRUE(FoundDecl.size() == 1);
406 const Decl *ToImport = selectFirst<Decl>(DeclToImportID, FoundDecl);
407 auto Imported = importNode(From, To, *ImporterRef, ToImport);
408 EXPECT_TRUE(static_cast<bool>(Imported));
409 if (!Imported)
410 llvm::consumeError(Imported.takeError());
411 }
412
413 // Find the declaration and import it.
414 auto FoundDecl = match(FinalSelectPredicate.bind(DeclToVerifyID),
415 AllASTs[FileForFinalCheck]->getASTContext());
416 EXPECT_TRUE(FoundDecl.size() == 1);
417 const Decl *ToVerify = selectFirst<Decl>(DeclToVerifyID, FoundDecl);
418 MatchVerifier<Decl> Verifier;
419 EXPECT_TRUE(Verifier.match(
420 ToVerify, internal::BindableMatcher<Decl>(VerificationMatcher)));
421 }
422 };
423
getRecordDecl(T * D)424 template <typename T> RecordDecl *getRecordDecl(T *D) {
425 auto *ET = cast<ElaboratedType>(D->getType().getTypePtr());
426 return cast<RecordType>(ET->getNamedType().getTypePtr())->getDecl();
427 }
428
429 template <class T>
isSuccess(llvm::Expected<T> & ValOrErr)430 ::testing::AssertionResult isSuccess(llvm::Expected<T> &ValOrErr) {
431 if (ValOrErr)
432 return ::testing::AssertionSuccess() << "Expected<> contains no error.";
433 else
434 return ::testing::AssertionFailure()
435 << "Expected<> contains error: " << toString(ValOrErr.takeError());
436 }
437
438 template <class T>
isImportError(llvm::Expected<T> & ValOrErr,ImportError::ErrorKind Kind)439 ::testing::AssertionResult isImportError(llvm::Expected<T> &ValOrErr,
440 ImportError::ErrorKind Kind) {
441 if (ValOrErr) {
442 return ::testing::AssertionFailure() << "Expected<> is expected to contain "
443 "error but does contain value \""
444 << (*ValOrErr) << "\"";
445 } else {
446 std::ostringstream OS;
447 bool Result = false;
448 auto Err = llvm::handleErrors(
449 ValOrErr.takeError(), [&OS, &Result, Kind](clang::ImportError &IE) {
450 if (IE.Error == Kind) {
451 Result = true;
452 OS << "Expected<> contains an ImportError " << IE.toString();
453 } else {
454 OS << "Expected<> contains an ImportError " << IE.toString()
455 << " instead of kind " << Kind;
456 }
457 });
458 if (Err) {
459 OS << "Expected<> contains unexpected error: "
460 << toString(std::move(Err));
461 }
462 if (Result)
463 return ::testing::AssertionSuccess() << OS.str();
464 else
465 return ::testing::AssertionFailure() << OS.str();
466 }
467 }
468
469 } // end namespace ast_matchers
470 } // end namespace clang
471
472 #endif
473