581 lines · cpp
1//===--- WalkASTTest.cpp ------------------------------------------- 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#include "AnalysisInternal.h"9#include "clang-include-cleaner/Types.h"10#include "clang/AST/ASTContext.h"11#include "clang/AST/Decl.h"12#include "clang/AST/DeclBase.h"13#include "clang/Basic/Diagnostic.h"14#include "clang/Basic/DiagnosticOptions.h"15#include "clang/Basic/FileManager.h"16#include "clang/Basic/SourceLocation.h"17#include "clang/Frontend/TextDiagnostic.h"18#include "clang/Testing/TestAST.h"19#include "llvm/ADT/STLExtras.h"20#include "llvm/ADT/StringRef.h"21#include "llvm/Support/Error.h"22#include "llvm/Support/ScopedPrinter.h"23#include "llvm/Support/raw_ostream.h"24#include "llvm/Testing/Annotations/Annotations.h"25#include "gmock/gmock.h"26#include "gtest/gtest.h"27#include <cstddef>28#include <string>29#include <unordered_map>30#include <utility>31#include <vector>32 33namespace clang::include_cleaner {34namespace {35using testing::ElementsAre;36 37// Specifies a test of which symbols are referenced by a piece of code.38// Target should contain points annotated with the reference kind.39// Example:40// Target: int $explicit^foo();41// Referencing: int x = ^foo();42// There must be exactly one referencing location marked.43// Returns target decls.44std::vector<Decl::Kind> testWalk(llvm::StringRef TargetCode,45 llvm::StringRef ReferencingCode) {46 llvm::Annotations Target(TargetCode);47 llvm::Annotations Referencing(ReferencingCode);48 49 TestInputs Inputs(Referencing.code());50 Inputs.ExtraFiles["target.h"] = Target.code().str();51 Inputs.ExtraArgs.push_back("-include");52 Inputs.ExtraArgs.push_back("target.h");53 Inputs.ExtraArgs.push_back("-std=c++20");54 TestAST AST(Inputs);55 const auto &SM = AST.sourceManager();56 57 // We're only going to record references from the nominated point,58 // to the target file.59 FileID ReferencingFile = SM.getMainFileID();60 SourceLocation ReferencingLoc =61 SM.getComposedLoc(ReferencingFile, Referencing.point());62 FileID TargetFile = SM.translateFile(63 llvm::cantFail(AST.fileManager().getFileRef("target.h")));64 65 std::vector<Decl::Kind> TargetDecls;66 // Perform the walk, and capture the offsets of the referenced targets.67 std::unordered_map<RefType, std::vector<size_t>> ReferencedOffsets;68 for (Decl *D : AST.context().getTranslationUnitDecl()->decls()) {69 if (ReferencingFile != SM.getDecomposedExpansionLoc(D->getLocation()).first)70 continue;71 walkAST(*D, [&](SourceLocation Loc, NamedDecl &ND, RefType RT) {72 if (SM.getFileLoc(Loc) != ReferencingLoc)73 return;74 auto NDLoc = SM.getDecomposedLoc(SM.getFileLoc(ND.getLocation()));75 if (NDLoc.first != TargetFile)76 return;77 ReferencedOffsets[RT].push_back(NDLoc.second);78 TargetDecls.push_back(ND.getKind());79 });80 }81 for (auto &Entry : ReferencedOffsets)82 llvm::sort(Entry.second);83 84 // Compare results to the expected points.85 // For each difference, show the target point in context, like a diagnostic.86 std::string DiagBuf;87 llvm::raw_string_ostream DiagOS(DiagBuf);88 DiagnosticOptions DiagOpts;89 DiagOpts.ShowLevel = 0;90 DiagOpts.ShowNoteIncludeStack = 0;91 TextDiagnostic Diag(DiagOS, AST.context().getLangOpts(), DiagOpts);92 auto DiagnosePoint = [&](llvm::StringRef Message, unsigned Offset) {93 Diag.emitDiagnostic(94 FullSourceLoc(SM.getComposedLoc(TargetFile, Offset), SM),95 DiagnosticsEngine::Note, Message, {}, {});96 };97 for (auto RT : {RefType::Explicit, RefType::Implicit, RefType::Ambiguous}) {98 auto RTStr = llvm::to_string(RT);99 for (auto Expected : Target.points(RTStr))100 if (!llvm::is_contained(ReferencedOffsets[RT], Expected))101 DiagnosePoint("location not marked used with type " + RTStr, Expected);102 for (auto Actual : ReferencedOffsets[RT])103 if (!llvm::is_contained(Target.points(RTStr), Actual))104 DiagnosePoint("location unexpectedly used with type " + RTStr, Actual);105 }106 107 // If there were any differences, we print the entire referencing code once.108 if (!DiagBuf.empty())109 ADD_FAILURE() << DiagBuf << "\nfrom code:\n" << ReferencingCode;110 return TargetDecls;111}112 113TEST(WalkAST, DeclRef) {114 testWalk("int $explicit^x;", "int y = ^x;");115 testWalk("int $explicit^foo();", "int y = ^foo();");116 testWalk("namespace ns { int $explicit^x; }", "int y = ns::^x;");117 testWalk("struct S { static int x; };", "int y = S::^x;");118 // Canonical declaration only.119 testWalk("extern int $explicit^x; int x;", "int y = ^x;");120 // Return type of `foo` isn't used.121 testWalk("struct S{}; S $explicit^foo();", "auto bar() { return ^foo(); }");122}123 124TEST(WalkAST, TagType) {125 testWalk("struct $explicit^S {};", "^S *y;");126 testWalk("enum $explicit^E {};", "^E *y;");127 testWalk("struct $explicit^S { static int x; };", "int y = ^S::x;");128 // One explicit call from the TypeLoc in constructor spelling, another129 // implicit reference through the constructor call.130 testWalk("struct $explicit^$implicit^S { static int x; };", "auto y = ^S();");131}132 133TEST(WalkAST, ClassTemplates) {134 // Explicit instantiation and (partial) specialization references primary135 // template.136 EXPECT_THAT(testWalk("template<typename> struct $explicit^Foo{};",137 "template struct ^Foo<int>;"),138 ElementsAre(Decl::CXXRecord));139 EXPECT_THAT(testWalk("template<typename> struct $explicit^Foo{};",140 "template<> struct ^Foo<int> {};"),141 ElementsAre(Decl::CXXRecord));142 EXPECT_THAT(testWalk("template<typename> struct $explicit^Foo{};",143 "template<typename T> struct ^Foo<T*> {};"),144 ElementsAre(Decl::CXXRecord));145 146 // Implicit instantiations references most relevant template.147 EXPECT_THAT(148 testWalk("template<typename> struct $explicit^Foo;", "^Foo<int> x();"),149 ElementsAre(Decl::Kind::ClassTemplate));150 EXPECT_THAT(151 testWalk("template<typename> struct $explicit^Foo {};", "^Foo<int> x;"),152 ElementsAre(Decl::CXXRecord));153 EXPECT_THAT(testWalk(R"cpp(154 template<typename> struct Foo {};155 template<> struct $explicit^Foo<int> {};)cpp",156 "^Foo<int> x;"),157 ElementsAre(Decl::ClassTemplateSpecialization));158 EXPECT_THAT(testWalk(R"cpp(159 template<typename> struct Foo {};160 template<typename T> struct $explicit^Foo<T*> {};)cpp",161 "^Foo<int *> x;"),162 ElementsAre(Decl::ClassTemplatePartialSpecialization));163 // Incomplete instantiations don't have a specific specialization associated.164 EXPECT_THAT(testWalk(R"cpp(165 template<typename> struct $explicit^Foo;166 template<typename T> struct Foo<T*>;)cpp",167 "^Foo<int *> x();"),168 ElementsAre(Decl::Kind::ClassTemplate));169 EXPECT_THAT(testWalk(R"cpp(170 template<typename> struct $explicit^Foo {};171 template struct Foo<int>;)cpp",172 "^Foo<int> x;"),173 ElementsAre(Decl::CXXRecord));174 // FIXME: This is broken due to175 // https://github.com/llvm/llvm-project/issues/42259.176 EXPECT_THAT(testWalk(R"cpp(177 template<typename T> struct $explicit^Foo { Foo(T); };178 template<> struct Foo<int> { Foo(int); };)cpp",179 "^Foo x(3);"),180 ElementsAre(Decl::ClassTemplate));181}182TEST(WalkAST, VarTemplates) {183 // Explicit instantiation and (partial) specialization references primary184 // template.185 // FIXME: Explicit instantiations has wrong source location, they point at the186 // primary template location (hence we drop the reference).187 EXPECT_THAT(188 testWalk("template<typename T> T Foo = 0;", "template int ^Foo<int>;"),189 ElementsAre());190 EXPECT_THAT(testWalk("template<typename T> T $explicit^Foo = 0;",191 "template<> int ^Foo<int> = 2;"),192 ElementsAre(Decl::Var));193 EXPECT_THAT(testWalk("template<typename T> T $explicit^Foo = 0;",194 "template<typename T> T* ^Foo<T*> = 1;"),195 ElementsAre(Decl::Var));196 197 // Implicit instantiations references most relevant template.198 // FIXME: This points at implicit specialization, instead we should point to199 // pattern.200 EXPECT_THAT(testWalk(R"cpp(201 template <typename T> T $explicit^Foo = 0;)cpp",202 "int z = ^Foo<int>;"),203 ElementsAre(Decl::VarTemplateSpecialization));204 EXPECT_THAT(testWalk(R"cpp(205 template<typename T> T Foo = 0;206 template<> int $explicit^Foo<int> = 1;)cpp",207 "int x = ^Foo<int>;"),208 ElementsAre(Decl::VarTemplateSpecialization));209 // FIXME: This points at implicit specialization, instead we should point to210 // explicit partial specializaiton pattern.211 EXPECT_THAT(testWalk(R"cpp(212 template<typename T> T Foo = 0;213 template<typename T> T* $explicit^Foo<T*> = nullptr;)cpp",214 "int *x = ^Foo<int *>;"),215 ElementsAre(Decl::VarTemplateSpecialization));216 // Implicit specializations through explicit instantiations has source217 // locations pointing at the primary template.218 EXPECT_THAT(testWalk(R"cpp(219 template<typename T> T $explicit^Foo = 0;220 template int Foo<int>;)cpp",221 "int x = ^Foo<int>;"),222 ElementsAre(Decl::VarTemplateSpecialization));223}224TEST(WalkAST, FunctionTemplates) {225 // Explicit instantiation and (partial) specialization references primary226 // template.227 // FIXME: Explicit instantiations has wrong source location, they point at the228 // primary template location (hence we drop the reference).229 EXPECT_THAT(testWalk("template<typename T> void foo(T) {}",230 "template void ^foo<int>(int);"),231 ElementsAre());232 EXPECT_THAT(testWalk("template<typename T> void $explicit^foo(T);",233 "template<> void ^foo<int>(int);"),234 ElementsAre(Decl::FunctionTemplate));235 236 // Implicit instantiations references most relevant template.237 EXPECT_THAT(testWalk(R"cpp(238 template <typename T> void $explicit^foo() {})cpp",239 "auto x = []{ ^foo<int>(); };"),240 ElementsAre(Decl::Function));241 EXPECT_THAT(testWalk(R"cpp(242 template<typename T> void foo() {}243 template<> void $explicit^foo<int>(){})cpp",244 "auto x = []{ ^foo<int>(); };"),245 ElementsAre(Decl::Function));246 // The decl is actually the specialization, but explicit instantations point247 // at the primary template.248 EXPECT_THAT(testWalk(R"cpp(249 template<typename T> void $explicit^foo() {};250 template void foo<int>();)cpp",251 "auto x = [] { ^foo<int>(); };"),252 ElementsAre(Decl::Function));253}254TEST(WalkAST, TemplateSpecializationsFromUsingDecl) {255 // Class templates256 testWalk(R"cpp(257namespace ns {258template<class T> class $explicit^Z {}; // primary template259template<class T> class $ambiguous^Z<T*> {}; // partial specialization260template<> class $ambiguous^Z<int> {}; // full specialization261}262 )cpp",263 "using ns::^Z;");264 265 // Var templates266 testWalk(R"cpp(267namespace ns {268template<class T> T $explicit^foo; // primary template269template<class T> T $ambiguous^foo<T*>; // partial specialization270template<> int* $ambiguous^foo<int>; // full specialization271}272 )cpp",273 "using ns::^foo;");274 // Function templates, no partial template specializations.275 testWalk(R"cpp(276namespace ns {277template<class T> void $ambiguous^function(T); // primary template278template<> void $ambiguous^function(int); // full specialization279}280 )cpp",281 "using ns::^function;");282}283 284TEST(WalkAST, Alias) {285 testWalk(R"cpp(286 namespace ns { int x; }287 using ns::$explicit^x;288 )cpp",289 "int y = ^x;");290 testWalk("using $explicit^foo = int;", "^foo x;");291 testWalk("struct S {}; using $explicit^foo = S;", "^foo x;");292 testWalk(R"cpp(293 template<typename> struct Foo {};294 template<> struct Foo<int> {};295 namespace ns { using ::$explicit^Foo; })cpp",296 "ns::^Foo<int> x;");297 testWalk(R"cpp(298 template<typename> struct Foo {};299 namespace ns { using ::Foo; }300 template<> struct ns::$explicit^Foo<int> {};)cpp",301 "^Foo<int> x;");302 // AST doesn't have enough information to figure out whether specialization303 // happened through an exported type or not. So err towards attributing use to304 // the using-decl, specializations on the exported type should be rare and305 // they're not permitted on type-aliases.306 testWalk(R"cpp(307 template<typename> struct Foo {};308 namespace ns { using ::$explicit^Foo; }309 template<> struct ns::Foo<int> {};)cpp",310 "ns::^Foo<int> x;");311 testWalk(R"cpp(312 namespace ns { enum class foo { bar }; }313 using ns::foo;)cpp",314 "auto x = foo::^bar;");315 testWalk(R"cpp(316 namespace ns { enum foo { bar }; }317 using ns::foo::$explicit^bar;)cpp",318 "auto x = ^bar;");319}320 321TEST(WalkAST, Using) {322 // We should report unused overloads as ambiguous.323 testWalk(R"cpp(324 namespace ns {325 void $explicit^x(); void $ambiguous^x(int); void $ambiguous^x(char);326 })cpp",327 "using ns::^x; void foo() { x(); }");328 testWalk(R"cpp(329 namespace ns {330 void $ambiguous^x(); void $ambiguous^x(int); void $ambiguous^x(char);331 })cpp",332 "using ns::^x;");333 testWalk("namespace ns { struct S; } using ns::$explicit^S;", "^S *s;");334 335 testWalk(R"cpp(336 namespace ns {337 template<class T>338 class $explicit^Y {};339 })cpp",340 "using ns::^Y;");341 testWalk(R"cpp(342 namespace ns {343 class $explicit^Y {};344 })cpp",345 "using ns::^Y;");346 testWalk(R"cpp(347 namespace ns {348 template<class T>349 class Y {};350 }351 using ns::$explicit^Y;)cpp",352 "^Y<int> x;");353 testWalk("namespace ns { enum E {A}; } using enum ns::$explicit^E;",354 "auto x = ^A;");355}356 357TEST(WalkAST, Namespaces) {358 testWalk("namespace ns { void x(); }", "using namespace ^ns;");359}360 361TEST(WalkAST, TemplateNames) {362 testWalk("template<typename> struct $explicit^S {};", "^S<int> s;");363 // FIXME: Template decl has the wrong primary location for type-alias template364 // decls.365 testWalk(R"cpp(366 template <typename> struct S {};367 template <typename T> $explicit^using foo = S<T>;)cpp",368 "^foo<int> x;");369 testWalk(R"cpp(370 namespace ns {template <typename> struct S {}; }371 using ns::$explicit^S;)cpp",372 "^S<int> x;");373 testWalk(R"cpp(374 namespace ns {375 template <typename T> struct S { S(T);};376 template <typename T> S(T t) -> S<T>;377 }378 using ns::$explicit^S;)cpp",379 "^S x(123);");380 testWalk("template<typename> struct $explicit^S {};",381 R"cpp(382 template <template <typename> typename> struct X {};383 X<^S> x;)cpp");384 testWalk("template<typename T> struct $explicit^S { S(T); };", "^S s(42);");385}386 387TEST(WalkAST, NestedTypes) {388 testWalk(R"cpp(389 struct Base { typedef int $implicit^a; };390 struct Derived : public Base {};)cpp",391 "void fun() { Derived::^a x; }");392 testWalk(R"cpp(393 struct Base { using $implicit^a = int; };394 struct Derived : public Base {};)cpp",395 "void fun() { Derived::^a x; }");396 testWalk(R"cpp(397 struct ns { struct a {}; };398 struct Base : public ns { using ns::$implicit^a; };399 struct Derived : public Base {};)cpp",400 "void fun() { Derived::^a x; }");401 testWalk(R"cpp(402 struct Base { struct $implicit^a {}; };403 struct Derived : public Base {};)cpp",404 "void fun() { Derived::^a x; }");405 testWalk("struct Base { struct $implicit^a {}; };",406 "struct Derived : public Base { ^a x; };");407 testWalk(R"cpp(408 struct Base { struct $implicit^a {}; };409 struct Derived : public Base {};410 struct SoDerived : public Derived {};411 )cpp",412 "void fun() { SoDerived::Derived::^a x; }");413}414 415TEST(WalkAST, MemberExprs) {416 testWalk("struct S { static int f; };", "void foo() { S::^f; }");417 testWalk("struct B { static int f; }; struct S : B {};",418 "void foo() { S::^f; }");419 testWalk("struct B { static void f(); }; struct S : B {};",420 "void foo() { S::^f; }");421 testWalk("struct B { static void f(); }; ",422 "struct S : B { void foo() { ^f(); } };");423 testWalk("struct $implicit^S { void foo(); };", "void foo() { S{}.^foo(); }");424 testWalk(425 "struct S { void foo(); }; struct $implicit^X : S { using S::foo; };",426 "void foo() { X{}.^foo(); }");427 testWalk("struct Base { int a; }; struct $implicit^Derived : public Base {};",428 "void fun(Derived d) { d.^a; }");429 testWalk("struct Base { int a; }; struct $implicit^Derived : public Base {};",430 "void fun(Derived* d) { d->^a; }");431 testWalk("struct Base { int a; }; struct $implicit^Derived : public Base {};",432 "void fun(Derived& d) { d.^a; }");433 testWalk("struct Base { int a; }; struct $implicit^Derived : public Base {};",434 "void fun() { Derived().^a; }");435 testWalk("struct Base { int a; }; struct $implicit^Derived : public Base {};",436 "Derived foo(); void fun() { foo().^a; }");437 testWalk("struct Base { int a; }; struct $implicit^Derived : public Base {};",438 "Derived& foo(); void fun() { foo().^a; }");439 testWalk(R"cpp(440 template <typename T>441 struct unique_ptr {442 T *operator->();443 };444 struct $implicit^Foo { int a; };)cpp",445 "void test(unique_ptr<Foo> &V) { V->^a; }");446 testWalk(R"cpp(447 template <typename T>448 struct $implicit^unique_ptr {449 void release();450 };451 struct Foo {};)cpp",452 "void test(unique_ptr<Foo> &V) { V.^release(); }");453 // Respect the sugar type (typedef, using-type).454 testWalk(R"cpp(455 namespace ns { struct Foo { int a; }; }456 using $implicit^Bar = ns::Foo;)cpp",457 "void test(Bar b) { b.^a; }");458 testWalk(R"cpp(459 namespace ns { struct Foo { int a; }; }460 using ns::$implicit^Foo;)cpp",461 "void test(Foo b) { b.^a; }");462 testWalk(R"cpp(463 namespace ns { struct Foo { int a; }; }464 namespace ns2 { using Bar = ns::Foo; }465 using ns2::$implicit^Bar;466 )cpp",467 "void test(Bar b) { b.^a; }");468 testWalk(R"cpp(469 namespace ns { template<typename> struct Foo { int a; }; }470 using ns::$implicit^Foo;)cpp",471 "void k(Foo<int> b) { b.^a; }");472 // Test the dependent-type case (CXXDependentScopeMemberExpr)473 testWalk("template<typename T> struct $implicit^Base { void method(); };",474 "template<typename T> void k(Base<T> t) { t.^method(); }");475 testWalk("template<typename T> struct $implicit^Base { void method(); };",476 "template<typename T> void k(Base<T>& t) { t.^method(); }");477 testWalk("template<typename T> struct $implicit^Base { void method(); };",478 "template<typename T> void k(Base<T>* t) { t->^method(); }");479}480 481TEST(WalkAST, ConstructExprs) {482 testWalk("struct $implicit^S {};", "S ^t;");483 testWalk("struct $implicit^S { S(); };", "S ^t;");484 testWalk("struct $implicit^S { S(int); };", "S ^t(42);");485 testWalk("struct $implicit^S { S(int); };", "S t = ^42;");486 testWalk("namespace ns { struct S{}; } using ns::$implicit^S;", "S ^t;");487}488 489TEST(WalkAST, Operator) {490 // Operator calls are marked as implicit references as they're ADL-used and491 // type should be providing them.492 testWalk(493 "struct string { friend int $implicit^operator+(string, string); }; ",494 "int k = string() ^+ string();");495 // Treat member operators as regular member expr calls.496 testWalk("struct $implicit^string {int operator+(string); }; ",497 "int k = string() ^+ string();");498 // Make sure usage is attributed to the alias.499 testWalk(500 "struct string {int operator+(string); }; using $implicit^foo = string;",501 "int k = foo() ^+ string();");502}503 504TEST(WalkAST, VarDecls) {505 // Definition uses declaration, not the other way around.506 testWalk("extern int $explicit^x;", "int ^x = 1;");507 testWalk("int x = 1;", "extern int ^x;");508}509 510TEST(WalkAST, Functions) {511 // Definition uses declaration, not the other way around.512 testWalk("void $explicit^foo();", "void ^foo() {}");513 testWalk("void foo() {}", "void ^foo();");514 testWalk("template <typename> void $explicit^foo();",515 "template <typename> void ^foo() {}");516 517 // Unresolved calls marks all the overloads.518 testWalk("void $ambiguous^foo(int); void $ambiguous^foo(char);",519 "template <typename T> void bar() { ^foo(T{}); }");520}521 522TEST(WalkAST, Enums) {523 testWalk("enum E { $explicit^A = 42 };", "int e = ^A;");524 testWalk("enum class $explicit^E : int;", "enum class ^E : int {};");525 testWalk("enum class E : int {};", "enum class ^E : int ;");526 testWalk("namespace ns { enum E { $explicit^A = 42 }; }", "int e = ns::^A;");527 testWalk("namespace ns { enum E { A = 42 }; } using ns::E::$explicit^A;",528 "int e = ^A;");529 testWalk("namespace ns { enum E { A = 42 }; } using enum ns::$explicit^E;",530 "int e = ^A;");531 testWalk(R"(namespace ns { enum E { A = 42 }; }532 struct S { using enum ns::E; };)",533 "int e = S::^A;");534 testWalk(R"(namespace ns { enum E { A = 42 }; }535 struct S { using ns::E::A; };)",536 "int e = S::^A;");537 testWalk(R"(namespace ns { enum E { $explicit^A = 42 }; })",538 "namespace z = ns; int e = z::^A;");539 testWalk(R"(enum E { $explicit^A = 42 };)", "int e = ::^A;");540}541 542TEST(WalkAST, InitializerList) {543 testWalk(R"cpp(544 namespace std {545 template <typename T> struct $implicit^initializer_list { const T *a, *b; };546 })cpp",547 R"cpp(548 const char* s = "";549 auto sx = ^{s};)cpp");550}551 552TEST(WalkAST, Concepts) {553 std::string Concept = "template<typename T> concept $explicit^Foo = true;";554 testWalk(Concept, "template<typename T>concept Bar = ^Foo<T> && true;");555 testWalk(Concept, "template<^Foo T>void func() {}");556 testWalk(Concept, "template<typename T> requires ^Foo<T> void func() {}");557 testWalk(Concept, "template<typename T> void func() requires ^Foo<T> {}");558 testWalk(Concept, "void func(^Foo auto x) {}");559 testWalk(Concept, "void func() { ^Foo auto x = 1; }");560}561 562TEST(WalkAST, FriendDecl) {563 testWalk("void $explicit^foo();", "struct Bar { friend void ^foo(); };");564 testWalk("struct $explicit^Foo {};", "struct Bar { friend struct ^Foo; };");565}566 567TEST(WalkAST, OperatorNewDelete) {568 testWalk("void* $ambiguous^operator new(decltype(sizeof(int)), void*);",569 "struct Bar { void foo() { Bar b; ^new (&b) Bar; } };");570 testWalk("struct A { static void $ambiguous^operator delete(void*); };",571 "void foo() { A a; ^delete &a; }");572}573 574TEST(WalkAST, CleanupAttr) {575 testWalk("void* $explicit^freep(void *p);",576 "void foo() { __attribute__((__cleanup__(^freep))) char* x = 0; }");577}578 579} // namespace580} // namespace clang::include_cleaner581