1//===- unittest/Tooling/ASTMatchersTest.h - Matcher tests helpers ------===//
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#ifndef LLVM_CLANG_UNITTESTS_ASTMATCHERS_ASTMATCHERSTEST_H
10#define LLVM_CLANG_UNITTESTS_ASTMATCHERS_ASTMATCHERSTEST_H
11
12#include "clang/ASTMatchers/ASTMatchFinder.h"
13#include "clang/Frontend/ASTUnit.h"
14#include "clang/Testing/CommandLineArgs.h"
15#include "clang/Testing/TestClangConfig.h"
16#include "clang/Tooling/Tooling.h"
17#include "gtest/gtest.h"
18
19namespace clang {
20namespace ast_matchers {
21
22using clang::tooling::buildASTFromCodeWithArgs;
23using clang::tooling::FileContentMappings;
24using clang::tooling::FrontendActionFactory;
25using clang::tooling::newFrontendActionFactory;
26using clang::tooling::runToolOnCodeWithArgs;
27
28class BoundNodesCallback {
29public:
30 virtual ~BoundNodesCallback() {}
31 virtual bool run(const BoundNodes *BoundNodes, ASTContext *Context) = 0;
32 virtual void onEndOfTranslationUnit() {}
33};
34
35// If 'FindResultVerifier' is not NULL, sets *Verified to the result of
36// running 'FindResultVerifier' with the bound nodes as argument.
37// If 'FindResultVerifier' is NULL, sets *Verified to true when Run is called.
38class VerifyMatch : public MatchFinder::MatchCallback {
39public:
40 VerifyMatch(std::unique_ptr<BoundNodesCallback> FindResultVerifier,
41 bool *Verified)
42 : Verified(Verified), FindResultReviewer(std::move(FindResultVerifier)) {}
43
44 void run(const MatchFinder::MatchResult &Result) override {
45 if (FindResultReviewer != nullptr) {
46 *Verified |= FindResultReviewer->run(BoundNodes: &Result.Nodes, Context: Result.Context);
47 } else {
48 *Verified = true;
49 }
50 }
51
52 void onEndOfTranslationUnit() override {
53 if (FindResultReviewer)
54 FindResultReviewer->onEndOfTranslationUnit();
55 }
56
57private:
58 bool *const Verified;
59 const std::unique_ptr<BoundNodesCallback> FindResultReviewer;
60};
61
62inline ArrayRef<TestLanguage> langCxx11OrLater() {
63 static const TestLanguage Result[] = {Lang_CXX11, Lang_CXX14, Lang_CXX17,
64 Lang_CXX20, Lang_CXX23};
65 return ArrayRef<TestLanguage>(Result);
66}
67
68inline ArrayRef<TestLanguage> langCxx14OrLater() {
69 static const TestLanguage Result[] = {Lang_CXX14, Lang_CXX17, Lang_CXX20,
70 Lang_CXX23};
71 return ArrayRef<TestLanguage>(Result);
72}
73
74inline ArrayRef<TestLanguage> langCxx17OrLater() {
75 static const TestLanguage Result[] = {Lang_CXX17, Lang_CXX20, Lang_CXX23};
76 return ArrayRef<TestLanguage>(Result);
77}
78
79inline ArrayRef<TestLanguage> langCxx20OrLater() {
80 static const TestLanguage Result[] = {Lang_CXX20, Lang_CXX23};
81 return ArrayRef<TestLanguage>(Result);
82}
83
84inline ArrayRef<TestLanguage> langCxx23OrLater() {
85 static const TestLanguage Result[] = {Lang_CXX23};
86 return ArrayRef<TestLanguage>(Result);
87}
88
89template <typename T>
90testing::AssertionResult matchesConditionally(
91 const Twine &Code, const T &AMatcher, bool ExpectMatch,
92 ArrayRef<std::string> CompileArgs,
93 const FileContentMappings &VirtualMappedFiles = FileContentMappings(),
94 StringRef Filename = "input.cc") {
95 bool Found = false, DynamicFound = false;
96 MatchFinder Finder;
97 VerifyMatch VerifyFound(nullptr, &Found);
98 Finder.addMatcher(AMatcher, &VerifyFound);
99 VerifyMatch VerifyDynamicFound(nullptr, &DynamicFound);
100 if (!Finder.addDynamicMatcher(NodeMatch: AMatcher, Action: &VerifyDynamicFound))
101 return testing::AssertionFailure() << "Could not add dynamic matcher";
102 std::unique_ptr<FrontendActionFactory> Factory(
103 newFrontendActionFactory(ConsumerFactory: &Finder));
104 std::vector<std::string> Args = {
105 // Some tests need rtti/exceptions on.
106 "-frtti", "-fexceptions",
107 // Ensure that tests specify the C++ standard version that they need.
108 "-Werror=c++14-extensions", "-Werror=c++17-extensions",
109 "-Werror=c++20-extensions"};
110 // Append additional arguments at the end to allow overriding the default
111 // choices that we made above.
112 llvm::copy(Range&: CompileArgs, Out: std::back_inserter(x&: Args));
113 if (!llvm::is_contained(Range&: Args, Element: "-target")) {
114 // Use an unknown-unknown triple so we don't instantiate the full system
115 // toolchain. On Linux, instantiating the toolchain involves stat'ing
116 // large portions of /usr/lib, and this slows down not only this test, but
117 // all other tests, via contention in the kernel.
118 //
119 // FIXME: This is a hack to work around the fact that there's no way to do
120 // the equivalent of runToolOnCodeWithArgs without instantiating a full
121 // Driver. We should consider having a function, at least for tests, that
122 // invokes cc1.
123 Args.push_back(x: "-target");
124 Args.push_back(x: "i386-unknown-unknown");
125 }
126
127 if (!runToolOnCodeWithArgs(
128 ToolAction: Factory->create(), Code, Args, FileName: Filename, ToolName: "clang-tool",
129 PCHContainerOps: std::make_shared<PCHContainerOperations>(), VirtualMappedFiles)) {
130 return testing::AssertionFailure() << "Parsing error in \"" << Code << "\"";
131 }
132 if (Found != DynamicFound) {
133 return testing::AssertionFailure()
134 << "Dynamic match result (" << DynamicFound
135 << ") does not match static result (" << Found << ")";
136 }
137 if (!Found && ExpectMatch) {
138 return testing::AssertionFailure()
139 << "Could not find match in \"" << Code << "\"";
140 } else if (Found && !ExpectMatch) {
141 return testing::AssertionFailure()
142 << "Found unexpected match in \"" << Code << "\"";
143 }
144 return testing::AssertionSuccess();
145}
146
147template <typename T>
148testing::AssertionResult
149matchesConditionally(const Twine &Code, const T &AMatcher, bool ExpectMatch,
150 ArrayRef<TestLanguage> TestLanguages) {
151 for (auto Lang : TestLanguages) {
152 auto Result = matchesConditionally(
153 Code, AMatcher, ExpectMatch, getCommandLineArgsForTesting(Lang),
154 FileContentMappings(), getFilenameForTesting(Lang));
155 if (!Result)
156 return Result;
157 }
158
159 return testing::AssertionSuccess();
160}
161
162template <typename T>
163testing::AssertionResult
164matches(const Twine &Code, const T &AMatcher,
165 ArrayRef<TestLanguage> TestLanguages = {Lang_CXX11}) {
166 return matchesConditionally(Code, AMatcher, true, TestLanguages);
167}
168
169template <typename T>
170testing::AssertionResult
171notMatches(const Twine &Code, const T &AMatcher,
172 ArrayRef<TestLanguage> TestLanguages = {Lang_CXX11}) {
173 return matchesConditionally(Code, AMatcher, false, TestLanguages);
174}
175
176template <typename T>
177testing::AssertionResult matchesObjC(const Twine &Code, const T &AMatcher,
178 bool ExpectMatch = true) {
179 return matchesConditionally(Code, AMatcher, ExpectMatch,
180 {"-fobjc-nonfragile-abi", "-Wno-objc-root-class",
181 "-fblocks", "-Wno-incomplete-implementation"},
182 FileContentMappings(), "input.m");
183}
184
185template <typename T>
186testing::AssertionResult matchesC(const Twine &Code, const T &AMatcher) {
187 return matchesConditionally(Code, AMatcher, true, {}, FileContentMappings(),
188 "input.c");
189}
190
191template <typename T>
192testing::AssertionResult notMatchesObjC(const Twine &Code, const T &AMatcher) {
193 return matchesObjC(Code, AMatcher, false);
194}
195
196// Function based on matchesConditionally with "-x cuda" argument added and
197// small CUDA header prepended to the code string.
198template <typename T>
199testing::AssertionResult
200matchesConditionallyWithCuda(const Twine &Code, const T &AMatcher,
201 bool ExpectMatch, llvm::StringRef CompileArg) {
202 const std::string CudaHeader =
203 "typedef unsigned int size_t;\n"
204 "#define __constant__ __attribute__((constant))\n"
205 "#define __device__ __attribute__((device))\n"
206 "#define __global__ __attribute__((global))\n"
207 "#define __host__ __attribute__((host))\n"
208 "#define __shared__ __attribute__((shared))\n"
209 "struct dim3 {"
210 " unsigned x, y, z;"
211 " __host__ __device__ dim3(unsigned x, unsigned y = 1, unsigned z = 1)"
212 " : x(x), y(y), z(z) {}"
213 "};"
214 "typedef struct cudaStream *cudaStream_t;"
215 "int cudaConfigureCall(dim3 gridSize, dim3 blockSize,"
216 " size_t sharedSize = 0,"
217 " cudaStream_t stream = 0);"
218 "extern \"C\" unsigned __cudaPushCallConfiguration("
219 " dim3 gridDim, dim3 blockDim, size_t sharedMem = 0, void *stream = "
220 "0);";
221
222 bool Found = false, DynamicFound = false;
223 MatchFinder Finder;
224 VerifyMatch VerifyFound(nullptr, &Found);
225 Finder.addMatcher(AMatcher, &VerifyFound);
226 VerifyMatch VerifyDynamicFound(nullptr, &DynamicFound);
227 if (!Finder.addDynamicMatcher(NodeMatch: AMatcher, Action: &VerifyDynamicFound))
228 return testing::AssertionFailure() << "Could not add dynamic matcher";
229 std::unique_ptr<FrontendActionFactory> Factory(
230 newFrontendActionFactory(ConsumerFactory: &Finder));
231 // Some tests use typeof, which is a gnu extension. Using an explicit
232 // unknown-unknown triple is good for a large speedup, because it lets us
233 // avoid constructing a full system triple.
234 std::vector<std::string> Args = {
235 "-xcuda", "-fno-ms-extensions", "--cuda-host-only", "-nocudainc",
236 "-target", "x86_64-unknown-unknown", std::string(CompileArg)};
237 if (!runToolOnCodeWithArgs(ToolAction: Factory->create(), Code: CudaHeader + Code, Args)) {
238 return testing::AssertionFailure() << "Parsing error in \"" << Code << "\"";
239 }
240 if (Found != DynamicFound) {
241 return testing::AssertionFailure()
242 << "Dynamic match result (" << DynamicFound
243 << ") does not match static result (" << Found << ")";
244 }
245 if (!Found && ExpectMatch) {
246 return testing::AssertionFailure()
247 << "Could not find match in \"" << Code << "\"";
248 } else if (Found && !ExpectMatch) {
249 return testing::AssertionFailure()
250 << "Found unexpected match in \"" << Code << "\"";
251 }
252 return testing::AssertionSuccess();
253}
254
255template <typename T>
256testing::AssertionResult matchesWithCuda(const Twine &Code, const T &AMatcher) {
257 return matchesConditionallyWithCuda(Code, AMatcher, true, "-std=c++11");
258}
259
260template <typename T>
261testing::AssertionResult notMatchesWithCuda(const Twine &Code,
262 const T &AMatcher) {
263 return matchesConditionallyWithCuda(Code, AMatcher, false, "-std=c++11");
264}
265
266template <typename T>
267testing::AssertionResult matchesWithOpenMP(const Twine &Code,
268 const T &AMatcher) {
269 return matchesConditionally(Code, AMatcher, true, {"-fopenmp=libomp"});
270}
271
272template <typename T>
273testing::AssertionResult notMatchesWithOpenMP(const Twine &Code,
274 const T &AMatcher) {
275 return matchesConditionally(Code, AMatcher, false, {"-fopenmp=libomp"});
276}
277
278template <typename T>
279testing::AssertionResult matchesWithOpenMP51(const Twine &Code,
280 const T &AMatcher) {
281 return matchesConditionally(Code, AMatcher, true,
282 {"-fopenmp=libomp", "-fopenmp-version=51"});
283}
284
285template <typename T>
286testing::AssertionResult notMatchesWithOpenMP51(const Twine &Code,
287 const T &AMatcher) {
288 return matchesConditionally(Code, AMatcher, false,
289 {"-fopenmp=libomp", "-fopenmp-version=51"});
290}
291
292template <typename T>
293testing::AssertionResult matchesWithFixedpoint(const std::string &Code,
294 const T &AMatcher) {
295 return matchesConditionally(Code, AMatcher, true, {"-ffixed-point"},
296 FileContentMappings(), "input.c");
297}
298
299template <typename T>
300testing::AssertionResult notMatchesWithFixedpoint(const std::string &Code,
301 const T &AMatcher) {
302 return matchesConditionally(Code, AMatcher, false, {"-ffixed-point"},
303 FileContentMappings(), "input.c");
304}
305
306template <typename T>
307testing::AssertionResult matchAndVerifyResultConditionally(
308 const Twine &Code, const T &AMatcher,
309 std::unique_ptr<BoundNodesCallback> FindResultVerifier, bool ExpectResult,
310 ArrayRef<std::string> Args = {}, StringRef Filename = "input.cc") {
311 bool VerifiedResult = false;
312 MatchFinder Finder;
313 VerifyMatch VerifyVerifiedResult(std::move(FindResultVerifier),
314 &VerifiedResult);
315 Finder.addMatcher(AMatcher, &VerifyVerifiedResult);
316 std::unique_ptr<FrontendActionFactory> Factory(
317 newFrontendActionFactory(ConsumerFactory: &Finder));
318 // Some tests use typeof, which is a gnu extension. Using an explicit
319 // unknown-unknown triple is good for a large speedup, because it lets us
320 // avoid constructing a full system triple.
321 std::vector<std::string> CompileArgs = {"-std=gnu++11", "-target",
322 "i386-unknown-unknown"};
323 // Append additional arguments at the end to allow overriding the default
324 // choices that we made above.
325 llvm::copy(Range&: Args, Out: std::back_inserter(x&: CompileArgs));
326
327 if (!runToolOnCodeWithArgs(ToolAction: Factory->create(), Code, Args: CompileArgs, FileName: Filename)) {
328 return testing::AssertionFailure() << "Parsing error in \"" << Code << "\"";
329 }
330 if (!VerifiedResult && ExpectResult) {
331 return testing::AssertionFailure()
332 << "Could not verify result in \"" << Code << "\"";
333 } else if (VerifiedResult && !ExpectResult) {
334 return testing::AssertionFailure()
335 << "Verified unexpected result in \"" << Code << "\"";
336 }
337
338 VerifiedResult = false;
339 SmallString<256> Buffer;
340 std::unique_ptr<ASTUnit> AST(buildASTFromCodeWithArgs(
341 Code: Code.toStringRef(Out&: Buffer), Args: CompileArgs, FileName: Filename));
342 if (!AST)
343 return testing::AssertionFailure()
344 << "Parsing error in \"" << Code << "\" while building AST";
345 Finder.matchAST(Context&: AST->getASTContext());
346 if (!VerifiedResult && ExpectResult) {
347 return testing::AssertionFailure()
348 << "Could not verify result in \"" << Code << "\" with AST";
349 } else if (VerifiedResult && !ExpectResult) {
350 return testing::AssertionFailure()
351 << "Verified unexpected result in \"" << Code << "\" with AST";
352 }
353
354 return testing::AssertionSuccess();
355}
356
357// FIXME: Find better names for these functions (or document what they
358// do more precisely).
359template <typename T>
360testing::AssertionResult
361matchAndVerifyResultTrue(const Twine &Code, const T &AMatcher,
362 std::unique_ptr<BoundNodesCallback> FindResultVerifier,
363 ArrayRef<std::string> Args = {},
364 StringRef Filename = "input.cc") {
365 return matchAndVerifyResultConditionally(
366 Code, AMatcher, std::move(FindResultVerifier),
367 /*ExpectResult=*/true, Args, Filename);
368}
369
370template <typename T>
371testing::AssertionResult matchAndVerifyResultFalse(
372 const Twine &Code, const T &AMatcher,
373 std::unique_ptr<BoundNodesCallback> FindResultVerifier,
374 ArrayRef<std::string> Args = {}, StringRef Filename = "input.cc") {
375 return matchAndVerifyResultConditionally(
376 Code, AMatcher, std::move(FindResultVerifier),
377 /*ExpectResult=*/false, Args, Filename);
378}
379
380// Implements a run method that returns whether BoundNodes contains a
381// Decl bound to Id that can be dynamically cast to T.
382// Optionally checks that the check succeeded a specific number of times.
383template <typename T> class VerifyIdIsBoundTo : public BoundNodesCallback {
384public:
385 // Create an object that checks that a node of type \c T was bound to \c Id.
386 // Does not check for a certain number of matches.
387 explicit VerifyIdIsBoundTo(llvm::StringRef Id)
388 : Id(std::string(Id)), ExpectedCount(-1), Count(0) {}
389
390 // Create an object that checks that a node of type \c T was bound to \c Id.
391 // Checks that there were exactly \c ExpectedCount matches.
392 VerifyIdIsBoundTo(llvm::StringRef Id, int ExpectedCount)
393 : Id(std::string(Id)), ExpectedCount(ExpectedCount), Count(0) {}
394
395 // Create an object that checks that a node of type \c T was bound to \c Id.
396 // Checks that there was exactly one match with the name \c ExpectedName.
397 // Note that \c T must be a NamedDecl for this to work.
398 VerifyIdIsBoundTo(llvm::StringRef Id, llvm::StringRef ExpectedName,
399 int ExpectedCount = 1)
400 : Id(std::string(Id)), ExpectedCount(ExpectedCount), Count(0),
401 ExpectedName(std::string(ExpectedName)) {}
402
403 void onEndOfTranslationUnit() override {
404 if (ExpectedCount != -1) {
405 EXPECT_EQ(ExpectedCount, Count);
406 }
407 if (!ExpectedName.empty()) {
408 EXPECT_EQ(ExpectedName, Name);
409 }
410 Count = 0;
411 Name.clear();
412 }
413
414 ~VerifyIdIsBoundTo() override {
415 EXPECT_EQ(0, Count);
416 EXPECT_EQ("", Name);
417 }
418
419 bool run(const BoundNodes *Nodes, ASTContext * /*Context*/) override {
420 const BoundNodes::IDToNodeMap &M = Nodes->getMap();
421 if (Nodes->getNodeAs<T>(Id)) {
422 ++Count;
423 if (const NamedDecl *Named = Nodes->getNodeAs<NamedDecl>(ID: Id)) {
424 Name = Named->getNameAsString();
425 } else if (const NestedNameSpecifier *NNS =
426 Nodes->getNodeAs<NestedNameSpecifier>(ID: Id)) {
427 llvm::raw_string_ostream OS(Name);
428 NNS->print(OS, Policy: PrintingPolicy(LangOptions()));
429 }
430 BoundNodes::IDToNodeMap::const_iterator I = M.find(x: Id);
431 EXPECT_NE(M.end(), I);
432 if (I != M.end()) {
433 EXPECT_EQ(Nodes->getNodeAs<T>(Id), I->second.get<T>());
434 }
435 return true;
436 }
437 EXPECT_TRUE(M.count(Id) == 0 ||
438 M.find(Id)->second.template get<T>() == nullptr);
439 return false;
440 }
441
442private:
443 const std::string Id;
444 const int ExpectedCount;
445 int Count;
446 const std::string ExpectedName;
447 std::string Name;
448};
449
450class ASTMatchersTest : public ::testing::Test,
451 public ::testing::WithParamInterface<TestClangConfig> {
452protected:
453 template <typename T>
454 testing::AssertionResult matches(const Twine &Code, const T &AMatcher) {
455 const TestClangConfig &TestConfig = GetParam();
456 return clang::ast_matchers::matchesConditionally(
457 Code, AMatcher, /*ExpectMatch=*/true, TestConfig.getCommandLineArgs(),
458 FileContentMappings(), getFilenameForTesting(Lang: TestConfig.Language));
459 }
460
461 template <typename T>
462 testing::AssertionResult notMatches(const Twine &Code, const T &AMatcher) {
463 const TestClangConfig &TestConfig = GetParam();
464 return clang::ast_matchers::matchesConditionally(
465 Code, AMatcher, /*ExpectMatch=*/false, TestConfig.getCommandLineArgs(),
466 FileContentMappings(), getFilenameForTesting(Lang: TestConfig.Language));
467 }
468};
469
470} // namespace ast_matchers
471} // namespace clang
472
473#endif // LLVM_CLANG_UNITTESTS_AST_MATCHERS_AST_MATCHERS_TEST_H
474

Provided by KDAB

Privacy Policy
Update your C++ knowledge – Modern C++11/14/17 Training
Find out more

source code of clang/unittests/ASTMatchers/ASTMatchersTest.h