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
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 the
43// 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 are
51// 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 set
59 // 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(x: Arg);
66 }
67 return Args;
68 }
69};
70
71const auto DefaultTestArrayForRunOptions =
72 std::array<std::vector<std::string>, 4>{
73 ._M_elems: {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(container: 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.
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 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
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 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
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 ASTImporterOptionSpecificTestBase
200 : 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 TestImportBase
208 : 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' 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(ToAST: To, FileName: FromFileName,
221 Buffer: From->getBufferForFile(Filename: 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
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 Code: FromCode, Args: FromArgs, FileName: InputFileName),
253 ToAST = tooling::buildASTFromCodeWithArgs(
254 Code: ToCode, Args: ToArgs, FileName: 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
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(Name: DeclToImportID)).bind(ID: 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 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>
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(Lang: FromLang);
319 std::vector<std::string> ToArgs = getCommandLineArgsForLanguage(Lang: 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(Name: 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(Lang: CE.Lang);
354 auto AST = tooling::buildASTFromCodeWithArgs(Code: 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.
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(Key: Filename)) {
384 auto Found = CodeSamples.find(Key: Filename);
385 assert(Found != CodeSamples.end() && "Wrong file for import!");
386 AllASTs[Filename] = createASTUnit(FileName: Filename, CE: 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(p: 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(Matcher: Action.ImportPredicate.bind(ID: DeclToImportID),
407 Context&: From->getASTContext());
408 EXPECT_TRUE(FoundDecl.size() == 1);
409 const Decl *ToImport = selectFirst<Decl>(BoundTo: DeclToImportID, Results: FoundDecl);
410 auto Imported = importNode(From, To, Importer&: *ImporterRef, Node: ToImport);
411 EXPECT_TRUE(static_cast<bool>(Imported));
412 if (!Imported)
413 llvm::consumeError(Err: Imported.takeError());
414 }
415
416 // Find the declaration and import it.
417 auto FoundDecl = match(Matcher: FinalSelectPredicate.bind(ID: DeclToVerifyID),
418 Context&: AllASTs[FileForFinalCheck]->getASTContext());
419 EXPECT_TRUE(FoundDecl.size() == 1);
420 const Decl *ToVerify = selectFirst<Decl>(BoundTo: DeclToVerifyID, Results: 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 auto *ET = cast<ElaboratedType>(D->getType().getTypePtr());
429 return cast<RecordType>(ET->getNamedType().getTypePtr())->getDecl();
430}
431
432template <class T>
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
441template <class T>
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

source code of clang/unittests/AST/ASTImporterFixtures.h