285 lines · c
1//===--- TestVisitor.h ------------------------------------------*- C++ -*-===//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/// \file10/// \brief Defines utility templates for RecursiveASTVisitor related tests.11///12//===----------------------------------------------------------------------===//13 14#ifndef LLVM_CLANG_UNITTESTS_TOOLING_TESTVISITOR_H15#define LLVM_CLANG_UNITTESTS_TOOLING_TESTVISITOR_H16 17#include "clang/AST/ASTConsumer.h"18#include "clang/AST/ASTContext.h"19#include "clang/AST/DynamicRecursiveASTVisitor.h"20#include "clang/Frontend/CompilerInstance.h"21#include "clang/Frontend/FrontendAction.h"22#include "clang/Tooling/Tooling.h"23#include "gtest/gtest.h"24#include <vector>25 26namespace clang {27namespace detail {28// Use 'TestVisitor' or include 'CRTPTestVisitor.h' and use 'CRTPTestVisitor'29// instead of using this directly.30class TestVisitorHelper {31public:32 enum Language {33 Lang_C,34 Lang_CXX98,35 Lang_CXX11,36 Lang_CXX14,37 Lang_CXX17,38 Lang_CXX2a,39 Lang_OBJC,40 Lang_OBJCXX11,41 Lang_CXX = Lang_CXX9842 };43 44 /// \brief Runs the current AST visitor over the given code.45 bool runOver(StringRef Code, Language L = Lang_CXX) {46 std::vector<std::string> Args;47 switch (L) {48 case Lang_C:49 Args.push_back("-x");50 Args.push_back("c");51 break;52 case Lang_CXX98:53 Args.push_back("-std=c++98");54 break;55 case Lang_CXX11:56 Args.push_back("-std=c++11");57 break;58 case Lang_CXX14:59 Args.push_back("-std=c++14");60 break;61 case Lang_CXX17:62 Args.push_back("-std=c++17");63 break;64 case Lang_CXX2a:65 Args.push_back("-std=c++2a");66 break;67 case Lang_OBJC:68 Args.push_back("-ObjC");69 Args.push_back("-fobjc-runtime=macosx-10.12.0");70 break;71 case Lang_OBJCXX11:72 Args.push_back("-ObjC++");73 Args.push_back("-std=c++11");74 Args.push_back("-fblocks");75 break;76 }77 return tooling::runToolOnCodeWithArgs(CreateTestAction(), Code, Args);78 }79 80protected:81 TestVisitorHelper() = default;82 virtual ~TestVisitorHelper() = default;83 virtual void InvokeTraverseDecl(TranslationUnitDecl *D) = 0;84 85 virtual std::unique_ptr<ASTFrontendAction> CreateTestAction() {86 return std::make_unique<TestAction>(this);87 }88 89 class FindConsumer : public ASTConsumer {90 public:91 FindConsumer(TestVisitorHelper *Visitor) : Visitor(Visitor) {}92 93 void HandleTranslationUnit(clang::ASTContext &Context) override {94 Visitor->Context = &Context;95 Visitor->InvokeTraverseDecl(Context.getTranslationUnitDecl());96 }97 98 private:99 TestVisitorHelper *Visitor;100 };101 102 class TestAction : public ASTFrontendAction {103 public:104 TestAction(TestVisitorHelper *Visitor) : Visitor(Visitor) {}105 106 std::unique_ptr<clang::ASTConsumer>107 CreateASTConsumer(CompilerInstance &, llvm::StringRef dummy) override {108 /// TestConsumer will be deleted by the framework calling us.109 return std::make_unique<FindConsumer>(Visitor);110 }111 112 protected:113 TestVisitorHelper *Visitor;114 };115 116 ASTContext *Context;117};118 119class ExpectedLocationVisitorHelper {120public:121 /// \brief Expect 'Match' *not* to occur at the given 'Line' and 'Column'.122 ///123 /// Any number of matches can be disallowed.124 void DisallowMatch(Twine Match, unsigned Line, unsigned Column) {125 DisallowedMatches.push_back(MatchCandidate(Match, Line, Column));126 }127 128 /// \brief Expect 'Match' to occur at the given 'Line' and 'Column'.129 ///130 /// Any number of expected matches can be set by calling this repeatedly.131 /// Each is expected to be matched 'Times' number of times. (This is useful in132 /// cases in which different AST nodes can match at the same source code133 /// location.)134 void ExpectMatch(Twine Match, unsigned Line, unsigned Column,135 unsigned Times = 1) {136 ExpectedMatches.push_back(ExpectedMatch(Match, Line, Column, Times));137 }138 139 /// \brief Checks that all expected matches have been found.140 virtual ~ExpectedLocationVisitorHelper() {141 // FIXME: Range-based for loop.142 for (std::vector<ExpectedMatch>::const_iterator143 It = ExpectedMatches.begin(),144 End = ExpectedMatches.end();145 It != End; ++It) {146 It->ExpectFound();147 }148 }149 150protected:151 virtual ASTContext *getASTContext() = 0;152 153 /// \brief Checks an actual match against expected and disallowed matches.154 ///155 /// Implementations are required to call this with appropriate values156 /// for 'Name' during visitation.157 void Match(StringRef Name, SourceLocation Location) {158 const FullSourceLoc FullLocation = getASTContext()->getFullLoc(Location);159 160 // FIXME: Range-based for loop.161 for (std::vector<MatchCandidate>::const_iterator162 It = DisallowedMatches.begin(),163 End = DisallowedMatches.end();164 It != End; ++It) {165 EXPECT_FALSE(It->Matches(Name, FullLocation))166 << "Matched disallowed " << *It;167 }168 169 // FIXME: Range-based for loop.170 for (std::vector<ExpectedMatch>::iterator It = ExpectedMatches.begin(),171 End = ExpectedMatches.end();172 It != End; ++It) {173 It->UpdateFor(Name, FullLocation, getASTContext()->getSourceManager());174 }175 }176 177private:178 struct MatchCandidate {179 std::string ExpectedName;180 unsigned LineNumber;181 unsigned ColumnNumber;182 183 MatchCandidate(Twine Name, unsigned LineNumber, unsigned ColumnNumber)184 : ExpectedName(Name.str()), LineNumber(LineNumber),185 ColumnNumber(ColumnNumber) {186 }187 188 bool Matches(StringRef Name, FullSourceLoc const &Location) const {189 return MatchesName(Name) && MatchesLocation(Location);190 }191 192 bool PartiallyMatches(StringRef Name, FullSourceLoc const &Location) const {193 return MatchesName(Name) || MatchesLocation(Location);194 }195 196 bool MatchesName(StringRef Name) const {197 return Name == ExpectedName;198 }199 200 bool MatchesLocation(FullSourceLoc const &Location) const {201 return Location.isValid() &&202 Location.getSpellingLineNumber() == LineNumber &&203 Location.getSpellingColumnNumber() == ColumnNumber;204 }205 206 friend std::ostream &operator<<(std::ostream &Stream,207 MatchCandidate const &Match) {208 return Stream << Match.ExpectedName209 << " at " << Match.LineNumber << ":" << Match.ColumnNumber;210 }211 };212 213 struct ExpectedMatch {214 ExpectedMatch(Twine Name, unsigned LineNumber, unsigned ColumnNumber,215 unsigned Times)216 : Candidate(Name, LineNumber, ColumnNumber), TimesExpected(Times),217 TimesSeen(0) {}218 219 void UpdateFor(StringRef Name, FullSourceLoc Location, SourceManager &SM) {220 if (Candidate.Matches(Name, Location)) {221 EXPECT_LT(TimesSeen, TimesExpected);222 ++TimesSeen;223 } else if (TimesSeen < TimesExpected &&224 Candidate.PartiallyMatches(Name, Location)) {225 llvm::raw_string_ostream Stream(PartialMatches);226 Stream << ", partial match: \"" << Name << "\" at ";227 Location.print(Stream, SM);228 }229 }230 231 void ExpectFound() const {232 EXPECT_EQ(TimesExpected, TimesSeen)233 << "Expected \"" << Candidate.ExpectedName234 << "\" at " << Candidate.LineNumber235 << ":" << Candidate.ColumnNumber << PartialMatches;236 }237 238 MatchCandidate Candidate;239 std::string PartialMatches;240 unsigned TimesExpected;241 unsigned TimesSeen;242 };243 244 std::vector<MatchCandidate> DisallowedMatches;245 std::vector<ExpectedMatch> ExpectedMatches;246};247} // namespace detail248 249/// \brief Base class for simple (Dynamic)RecursiveASTVisitor based tests.250///251/// This is a drop-in replacement for DynamicRecursiveASTVisitor itself, with252/// the additional capability of running it over a snippet of code.253///254/// Visits template instantiations and implicit code by default.255///256/// For post-order traversal etc. use CTRPTestVisitor from257/// CTRPTestVisitor.h instead.258class TestVisitor : public DynamicRecursiveASTVisitor,259 public detail::TestVisitorHelper {260public:261 TestVisitor() {262 ShouldVisitTemplateInstantiations = true;263 ShouldVisitImplicitCode = true;264 }265 266 void InvokeTraverseDecl(TranslationUnitDecl *D) override { TraverseDecl(D); }267};268 269/// \brief A RecursiveASTVisitor to check that certain matches are (or are270/// not) observed during visitation.271///272/// This is a RecursiveASTVisitor for testing the RecursiveASTVisitor itself,273/// and allows simple creation of test visitors running matches on only a small274/// subset of the Visit* methods.275///276/// For post-order traversal etc. use CTRPExpectedLocationVisitor from277/// CTRPTestVisitor.h instead.278class ExpectedLocationVisitor : public TestVisitor,279 public detail::ExpectedLocationVisitorHelper {280 ASTContext *getASTContext() override { return Context; }281};282} // namespace clang283 284#endif285