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. |
56 | virtual std::vector<std::string> () 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> = getExtraArgs(); |
64 | for (const auto &Arg : ExtraArgs) { |
65 | Args.push_back(x: Arg); |
66 | } |
67 | return Args; |
68 | } |
69 | }; |
70 | |
71 | const 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 | |
79 | const 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. |
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 | |
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 | |
199 | class ASTImporterOptionSpecificTestBase |
200 | : public ASTImporterTestBase, |
201 | public ::testing::WithParamInterface<std::vector<std::string>> { |
202 | protected: |
203 | std::vector<std::string> () 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> |
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 | |
306 | protected: |
307 | std::vector<std::string> () 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> |
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 | |
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> |
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> |
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 | |