diff --git a/clang-tools-extra/include-cleaner/lib/WalkAST.cpp b/clang-tools-extra/include-cleaner/lib/WalkAST.cpp index aae3eda519ffd..7a140c991925c 100644 --- a/clang-tools-extra/include-cleaner/lib/WalkAST.cpp +++ b/clang-tools-extra/include-cleaner/lib/WalkAST.cpp @@ -22,6 +22,7 @@ #include "clang/AST/Type.h" #include "clang/AST/TypeLoc.h" #include "clang/Basic/IdentifierTable.h" +#include "clang/Basic/OperatorKinds.h" #include "clang/Basic/SourceLocation.h" #include "clang/Basic/Specifiers.h" #include "llvm/ADT/STLExtras.h" @@ -32,6 +33,11 @@ namespace clang::include_cleaner { namespace { +bool isOperatorNewDelete(OverloadedOperatorKind OpKind) { + return OpKind == OO_New || OpKind == OO_Delete || OpKind == OO_Array_New || + OpKind == OO_Array_Delete; +} + using DeclCallback = llvm::function_ref; @@ -158,7 +164,15 @@ class ASTWalker : public RecursiveASTVisitor { // the container decl instead, which is preferred as it'll handle // aliases/exports properly. if (!FD->isCXXClassMember() && !llvm::isa(FD)) { - report(DRE->getLocation(), FD); + // Global operator new/delete [] is available implicitly in every + // translation unit, even without including any explicit headers. So treat + // those as ambigious to not force inclusion in TUs that transitively + // depend on those. + RefType RT = + isOperatorNewDelete(FD->getDeclName().getCXXOverloadedOperator()) + ? RefType::Ambiguous + : RefType::Explicit; + report(DRE->getLocation(), FD, RT); return true; } // If the ref is without a qualifier, and is a member, ignore it. As it is diff --git a/clang-tools-extra/include-cleaner/unittests/AnalysisTest.cpp b/clang-tools-extra/include-cleaner/unittests/AnalysisTest.cpp index d2d137a0dfb42..74321c312cb71 100644 --- a/clang-tools-extra/include-cleaner/unittests/AnalysisTest.cpp +++ b/clang-tools-extra/include-cleaner/unittests/AnalysisTest.cpp @@ -397,6 +397,55 @@ TEST_F(AnalyzeTest, SpellingIncludesWithSymlinks) { } } +// Make sure that the references to implicit operator new/delete are reported as +// ambigious. +TEST_F(AnalyzeTest, ImplicitOperatorNewDeleteNotMissing) { + ExtraFS = llvm::makeIntrusiveRefCnt(); + ExtraFS->addFile("header.h", + /*ModificationTime=*/{}, + llvm::MemoryBuffer::getMemBufferCopy(guard(R"cpp( + void* operator new(decltype(sizeof(int))); + )cpp"))); + ExtraFS->addFile("wrapper.h", + /*ModificationTime=*/{}, + llvm::MemoryBuffer::getMemBufferCopy(guard(R"cpp( + #include "header.h" + )cpp"))); + + Inputs.Code = R"cpp( + #include "wrapper.h" + void bar() { + operator new(3); + })cpp"; + TestAST AST(Inputs); + std::vector DeclsInTU; + for (auto *D : AST.context().getTranslationUnitDecl()->decls()) + DeclsInTU.push_back(D); + auto Results = analyze(DeclsInTU, {}, PP.Includes, &PI, AST.preprocessor()); + EXPECT_THAT(Results.Missing, testing::IsEmpty()); +} + +TEST_F(AnalyzeTest, ImplicitOperatorNewDeleteNotUnused) { + ExtraFS = llvm::makeIntrusiveRefCnt(); + ExtraFS->addFile("header.h", + /*ModificationTime=*/{}, + llvm::MemoryBuffer::getMemBufferCopy(guard(R"cpp( + void* operator new(decltype(sizeof(int))); + )cpp"))); + + Inputs.Code = R"cpp( + #include "header.h" + void bar() { + operator new(3); + })cpp"; + TestAST AST(Inputs); + std::vector DeclsInTU; + for (auto *D : AST.context().getTranslationUnitDecl()->decls()) + DeclsInTU.push_back(D); + auto Results = analyze(DeclsInTU, {}, PP.Includes, &PI, AST.preprocessor()); + EXPECT_THAT(Results.Unused, testing::IsEmpty()); +} + TEST(FixIncludes, Basic) { llvm::StringRef Code = R"cpp(#include "d.h" #include "a.h"