474 lines · c
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-exception6//7//===----------------------------------------------------------------------===//8 9#ifndef LLVM_CLANG_UNITTESTS_ASTMATCHERS_ASTMATCHERSTEST_H10#define LLVM_CLANG_UNITTESTS_ASTMATCHERS_ASTMATCHERSTEST_H11 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 of36// 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(&Result.Nodes, 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(AMatcher, &VerifyDynamicFound))101 return testing::AssertionFailure() << "Could not add dynamic matcher";102 std::unique_ptr<FrontendActionFactory> Factory(103 newFrontendActionFactory(&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 default111 // choices that we made above.112 llvm::copy(CompileArgs, std::back_inserter(Args));113 if (!llvm::is_contained(Args, "-target")) {114 // Use an unknown-unknown triple so we don't instantiate the full system115 // toolchain. On Linux, instantiating the toolchain involves stat'ing116 // large portions of /usr/lib, and this slows down not only this test, but117 // 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 do120 // the equivalent of runToolOnCodeWithArgs without instantiating a full121 // Driver. We should consider having a function, at least for tests, that122 // invokes cc1.123 Args.push_back("-target");124 Args.push_back("i386-unknown-unknown");125 }126 127 if (!runToolOnCodeWithArgs(128 Factory->create(), Code, Args, Filename, "clang-tool",129 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 (" << DynamicFound135 << ") 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::AssertionResult149matchesConditionally(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::AssertionResult164matches(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::AssertionResult171notMatches(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 and197// small CUDA header prepended to the code string.198template <typename T>199testing::AssertionResult200matchesConditionallyWithCuda(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(AMatcher, &VerifyDynamicFound))228 return testing::AssertionFailure() << "Could not add dynamic matcher";229 std::unique_ptr<FrontendActionFactory> Factory(230 newFrontendActionFactory(&Finder));231 // Some tests use typeof, which is a gnu extension. Using an explicit232 // unknown-unknown triple is good for a large speedup, because it lets us233 // 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(Factory->create(), CudaHeader + Code, Args)) {238 return testing::AssertionFailure() << "Parsing error in \"" << Code << "\"";239 }240 if (Found != DynamicFound) {241 return testing::AssertionFailure()242 << "Dynamic match result (" << DynamicFound243 << ") 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(&Finder));318 // Some tests use typeof, which is a gnu extension. Using an explicit319 // unknown-unknown triple is good for a large speedup, because it lets us320 // 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 default324 // choices that we made above.325 llvm::copy(Args, std::back_inserter(CompileArgs));326 327 if (!runToolOnCodeWithArgs(Factory->create(), Code, CompileArgs, 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.toStringRef(Buffer), CompileArgs, Filename));342 if (!AST)343 return testing::AssertionFailure()344 << "Parsing error in \"" << Code << "\" while building AST";345 Finder.matchAST(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 they358// do more precisely).359template <typename T>360testing::AssertionResult361matchAndVerifyResultTrue(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 a381// 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)) {424 Name = Named->getNameAsString();425 } else if (const NestedNameSpecifier *NNS =426 Nodes->getNodeAs<NestedNameSpecifier>(Id)) {427 llvm::raw_string_ostream OS(Name);428 NNS->print(OS, PrintingPolicy(LangOptions()));429 }430 BoundNodes::IDToNodeMap::const_iterator I = M.find(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(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(TestConfig.Language));467 }468};469 470} // namespace ast_matchers471} // namespace clang472 473#endif // LLVM_CLANG_UNITTESTS_AST_MATCHERS_AST_MATCHERS_TEST_H474