Author: Ziqing Luo Date: 2026-07-08T11:50:06-07:00 New Revision: 6560fef7724bbd9baa57d3f09d077c4d2e69c868
URL: https://github.com/llvm/llvm-project/commit/6560fef7724bbd9baa57d3f09d077c4d2e69c868 DIFF: https://github.com/llvm/llvm-project/commit/6560fef7724bbd9baa57d3f09d077c4d2e69c868.diff LOG: [SSAF][Extractor] Extract operator new/delete overload entities that shall retain their types (#206600) This commit creates an extractor for operator new/delete overloads. Overloads of operator new shall retain their void* return type, regardless of whether they are propagated by unsafe buffers. The same applies to the parameters of operator delete overloads. Therefore, clang-reforge eventually need this information. rdar://179151541 --------- Co-authored-by: Balázs Benics <[email protected]> Added: clang/include/clang/ScalableStaticAnalysis/Analyses/OperatorNewDelete/OperatorNewDeletePointers.h clang/lib/ScalableStaticAnalysis/Analyses/OperatorNewDelete/OperatorNewDeletePointersExtractor.cpp clang/unittests/ScalableStaticAnalysis/Analyses/OperatorNewDelete/OperatorNewDeletePointersExtractorTest.cpp Modified: clang/include/clang/ScalableStaticAnalysis/BuiltinAnchorSources.def clang/lib/ScalableStaticAnalysis/Analyses/CMakeLists.txt clang/lib/ScalableStaticAnalysis/Analyses/SSAFAnalysesCommon.h clang/unittests/ScalableStaticAnalysis/CMakeLists.txt Removed: ################################################################################ diff --git a/clang/include/clang/ScalableStaticAnalysis/Analyses/OperatorNewDelete/OperatorNewDeletePointers.h b/clang/include/clang/ScalableStaticAnalysis/Analyses/OperatorNewDelete/OperatorNewDeletePointers.h new file mode 100644 index 0000000000000..0e55cecf15e54 --- /dev/null +++ b/clang/include/clang/ScalableStaticAnalysis/Analyses/OperatorNewDelete/OperatorNewDeletePointers.h @@ -0,0 +1,62 @@ +//===- OperatorNewDeletePointers.h ------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Declares data structures for analysis that identifies pointer entities in +// operator new/delete overloads that must have a 'void*' type +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_SCALABLESTATICANALYSIS_ANALYSES_OPERATORNEWDELETE_OPERATORNEWDELETEPOINTERS_H +#define LLVM_CLANG_SCALABLESTATICANALYSIS_ANALYSES_OPERATORNEWDELETE_OPERATORNEWDELETEPOINTERS_H + +#include "clang/ScalableStaticAnalysis/Core/Model/EntityId.h" +#include "clang/ScalableStaticAnalysis/Core/Model/SummaryName.h" +#include "clang/ScalableStaticAnalysis/Core/TUSummary/EntitySummary.h" +#include "llvm/ADT/StringRef.h" +#include <set> + +namespace clang::ssaf { + +/// \brief Collects specific pointer entities related to operator new and delete +/// overloads within a contributor. +/// +/// OperatorNewDeletePointersEntitySummary collects the following entities: +/// -# The returned entities of `operator new` overloads. +/// -# The parameter (optionally the second) of `operator new` overloads +/// representing the pointer to a memory area at which to initialize the +/// object. +/// -# The first parameter of `operator delete` overloads representing the +/// pointer +/// to a memory block to deallocate (or a null pointer). +/// -# The parameter (optionally the second) of `operator delete` overloads +/// representing the pointer used as the placement parameter in the matching +/// placement `new`. +struct OperatorNewDeletePointersEntitySummary final : public EntitySummary { + static constexpr llvm::StringLiteral Name = "OperatorNewDeletePointers"; + + static SummaryName summaryName() { return SummaryName(Name.str()); } + + SummaryName getSummaryName() const override { return summaryName(); } + + bool friend operator==(const OperatorNewDeletePointersEntitySummary &This, + const OperatorNewDeletePointersEntitySummary &Other) { + return This.Entities == Other.Entities; + } + + bool operator==(const std::set<EntityId> &OtherEntities) const { + return Entities == OtherEntities; + } + + bool empty() const { return Entities.empty(); } + + std::set<EntityId> Entities; +}; + +} // namespace clang::ssaf + +#endif // LLVM_CLANG_SCALABLESTATICANALYSIS_ANALYSES_OPERATORNEWDELETE_OPERATORNEWDELETEPOINTERS_H diff --git a/clang/include/clang/ScalableStaticAnalysis/BuiltinAnchorSources.def b/clang/include/clang/ScalableStaticAnalysis/BuiltinAnchorSources.def index ba4944ea984cb..57b70f37cf61d 100644 --- a/clang/include/clang/ScalableStaticAnalysis/BuiltinAnchorSources.def +++ b/clang/include/clang/ScalableStaticAnalysis/BuiltinAnchorSources.def @@ -20,6 +20,7 @@ ANCHOR(AnalysisRegistryAnchorSource) ANCHOR(CallGraphExtractorAnchorSource) ANCHOR(CallGraphJSONFormatAnchorSource) ANCHOR(JSONFormatAnchorSource) +ANCHOR(OperatorNewDeletePointersExtractorAnchorSource) ANCHOR(PointerFlowAnalysisAnchorSource) ANCHOR(PointerFlowExtractorAnchorSource) ANCHOR(PointerFlowJSONFormatAnchorSource) diff --git a/clang/lib/ScalableStaticAnalysis/Analyses/CMakeLists.txt b/clang/lib/ScalableStaticAnalysis/Analyses/CMakeLists.txt index dc72eb6d645d5..e1d14d13c2156 100644 --- a/clang/lib/ScalableStaticAnalysis/Analyses/CMakeLists.txt +++ b/clang/lib/ScalableStaticAnalysis/Analyses/CMakeLists.txt @@ -8,6 +8,7 @@ add_clang_library(clangScalableStaticAnalysisAnalyses CallGraph/CallGraphJSONFormat.cpp EntityPointerLevel/EntityPointerLevel.cpp EntityPointerLevel/EntityPointerLevelFormat.cpp + OperatorNewDelete/OperatorNewDeletePointersExtractor.cpp PointerFlow/PointerFlow.cpp PointerFlow/PointerFlowAnalysis.cpp PointerFlow/PointerFlowExtractor.cpp diff --git a/clang/lib/ScalableStaticAnalysis/Analyses/OperatorNewDelete/OperatorNewDeletePointersExtractor.cpp b/clang/lib/ScalableStaticAnalysis/Analyses/OperatorNewDelete/OperatorNewDeletePointersExtractor.cpp new file mode 100644 index 0000000000000..86e3607b2302f --- /dev/null +++ b/clang/lib/ScalableStaticAnalysis/Analyses/OperatorNewDelete/OperatorNewDeletePointersExtractor.cpp @@ -0,0 +1,118 @@ +//===- OperatorNewDeletePointersExtractor.cpp -----------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Extractor implementation for extracting from user-provided operator +// new/delete overloadings: +// 1 return entities of operator new overloads; +// 2 the parameter (optionally the 2nd) of operator new overloads +// representing the pointer to a memory area to initialize the object at; +// 3 the first parameter of operator delete overloads representing the pointer +// to a memory block to deallocate or a null pointer; +// 4 the parameter (optionally the 2nd) of operator delete overloads +// representing the pointer used as the placement parameter in the matching +// placement new. +// +//===----------------------------------------------------------------------===// + +#include "../SSAFAnalysesCommon.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Decl.h" +#include "clang/AST/DeclCXX.h" +#include "clang/AST/Type.h" +#include "clang/Basic/OperatorKinds.h" +#include "clang/ScalableStaticAnalysis/Analyses/OperatorNewDelete/OperatorNewDeletePointers.h" +#include "clang/ScalableStaticAnalysis/Core/TUSummary/ExtractorRegistry.h" +#include "clang/ScalableStaticAnalysis/Core/TUSummary/TUSummaryExtractor.h" +#include "llvm/ADT/StringRef.h" +#include <memory> +#include <optional> +#include <vector> + +using namespace clang; +using namespace ssaf; + +namespace { + +class OperatorNewDeletePointersExtractor final : public TUSummaryExtractor { +public: + using TUSummaryExtractor::TUSummaryExtractor; + +private: + void HandleTranslationUnit(ASTContext &Ctx) override; + + std::unique_ptr<OperatorNewDeletePointersEntitySummary> + extractEntitySummary(const std::vector<const NamedDecl *> &Decls); +}; + +void OperatorNewDeletePointersExtractor::HandleTranslationUnit( + ASTContext &Ctx) { + extractAndAddSummaries( + *this, SummaryBuilder, Ctx, + [&](const std::vector<const NamedDecl *> &Decls) { + return extractEntitySummary(Decls); + }, + OperatorNewDeletePointersEntitySummary::Name); +} + +std::unique_ptr<OperatorNewDeletePointersEntitySummary> +OperatorNewDeletePointersExtractor::extractEntitySummary( + const std::vector<const NamedDecl *> &ContributorDecls) { + auto Summary = std::make_unique<OperatorNewDeletePointersEntitySummary>(); + auto Matcher = [&Summary, this](const DynTypedNode &Node) { + const auto *FD = Node.get<FunctionDecl>(); + + if (!FD) + return; + + OverloadedOperatorKind OO = FD->getOverloadedOperator(); + + switch (OO) { + case OO_New: + case OO_Array_New: + // Extract case 1: + if (auto Id = addEntityForReturn(FD)) + Summary->Entities.insert(*Id); + break; + case OO_Delete: + case OO_Array_Delete: + // Extract case 3; ignore ill-formed ones (first param not a pointer). + if (!FD->getNumParams() || !hasPtrOrArrType(FD->getParamDecl(0))) + return; + if (auto Id = addEntity(FD->getParamDecl(0))) + Summary->Entities.insert(*Id); + break; + default: + return; + }; + // Extract case 2 & 4: only `operator new(size_t, void*)` and + // `operator delete(void*, void*)` are standard-defined with a void* 2nd + // param; for user-defined 3+ param overloads the 2nd param type is + // unconstrained, so we conservatively skip them. + if (FD->getNumParams() == 2 && hasPtrOrArrType(FD->getParamDecl(1))) { + if (auto Id = addEntity(FD->getParamDecl(1))) + Summary->Entities.insert(*Id); + } + }; + + for (const NamedDecl *Decl : ContributorDecls) + findMatchesIn(Decl, Matcher); + return Summary; +} + +} // namespace + +namespace clang::ssaf { +// NOLINTNEXTLINE(misc-use-internal-linkage) +volatile int OperatorNewDeletePointersExtractorAnchorSource = 0; +} // namespace clang::ssaf + +static TUSummaryExtractorRegistry::Add<OperatorNewDeletePointersExtractor> + RegisterOperatorNewDeletePointersExtractor( + OperatorNewDeletePointersEntitySummary::Name, + "Extract pointer entities in operator new/delete overloads that must " + "have a 'void*' type"); diff --git a/clang/lib/ScalableStaticAnalysis/Analyses/SSAFAnalysesCommon.h b/clang/lib/ScalableStaticAnalysis/Analyses/SSAFAnalysesCommon.h index 674ccfcc58313..152168ea73b54 100644 --- a/clang/lib/ScalableStaticAnalysis/Analyses/SSAFAnalysesCommon.h +++ b/clang/lib/ScalableStaticAnalysis/Analyses/SSAFAnalysesCommon.h @@ -104,15 +104,20 @@ template <typename ExtractorFnT> void extractAndAddSummaries(TUSummaryExtractor &Extractor, TUSummaryBuilder &Builder, ASTContext &Ctx, ExtractorFnT ExtractFn, - const char *ExtractorName = "") { + llvm::StringRef ExtractorName = "") { llvm::DenseMap<const NamedDecl *, std::vector<const NamedDecl *>> Contributors; findContributors(Ctx, Contributors); for (const auto &[Cano, Decls] : Contributors) { + assert(!Decls.empty() && + "'findContributors' guarantess that 'Decls' are non-empty"); + assert(!Decls[0]->isImplicit() && + "'findContributors' guarantess that 'Decls' are non-implicit"); + // Templates are skipped, but their instantiations are handled. The idea // is that we can conclude facts about a template through all of its // instantiations. - if (Cano->isTemplated()) + if (Decls[0]->isTemplated()) continue; auto Summary = ExtractFn(Decls); @@ -120,13 +125,13 @@ void extractAndAddSummaries(TUSummaryExtractor &Extractor, if (Summary->empty()) continue; - if (auto Id = Extractor.addEntity(Cano)) { + if (auto Id = Extractor.addEntity(Decls[0])) { if (!Builder.addSummary(*Id, std::move(Summary)).second) logWarningFromError(makeErrAtNode( - Ctx, Cano, "dropping duplicate %s summary for entity %s", - ExtractorName, Cano->getNameAsString().c_str())); + Ctx, Decls[0], "dropping duplicate %s summary for entity %s", + ExtractorName.str().c_str(), Decls[0]->getNameAsString().c_str())); } else - logWarningFromError(makeEntityNameErr(Ctx, Cano)); + logWarningFromError(makeEntityNameErr(Ctx, Decls[0])); } } diff --git a/clang/unittests/ScalableStaticAnalysis/Analyses/OperatorNewDelete/OperatorNewDeletePointersExtractorTest.cpp b/clang/unittests/ScalableStaticAnalysis/Analyses/OperatorNewDelete/OperatorNewDeletePointersExtractorTest.cpp new file mode 100644 index 0000000000000..d8b9bef9b65bb --- /dev/null +++ b/clang/unittests/ScalableStaticAnalysis/Analyses/OperatorNewDelete/OperatorNewDeletePointersExtractorTest.cpp @@ -0,0 +1,260 @@ +//===- OperatorNewDeletePointersExtractorTest.cpp -------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "FindDecl.h" +#include "TestFixture.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Decl.h" +#include "clang/Frontend/ASTUnit.h" +#include "clang/Frontend/SSAFOptions.h" +#include "clang/ScalableStaticAnalysis/Analyses/OperatorNewDelete/OperatorNewDeletePointers.h" +#include "clang/ScalableStaticAnalysis/Core/Model/EntityId.h" +#include "clang/ScalableStaticAnalysis/Core/TUSummary/ExtractorRegistry.h" +#include "clang/ScalableStaticAnalysis/Core/TUSummary/TUSummary.h" +#include "clang/ScalableStaticAnalysis/Core/TUSummary/TUSummaryBuilder.h" +#include "clang/ScalableStaticAnalysis/Core/TUSummary/TUSummaryExtractor.h" +#include "clang/Tooling/Tooling.h" +#include "gtest/gtest.h" +#include <memory> +#include <optional> + +using namespace clang; +using namespace ssaf; + +namespace { + +/// Look up the \p SummaryT entity summary for the contributor named +/// \p ContributorName. +/// +/// \tparam SummaryT The concrete EntitySummary subtype to return. +/// \tparam ContributorT The NamedDecl subtype to search for (defaults to +/// FunctionDecl). +/// \tparam TUSummaryDataT The type of the TUSummary data map, deduced from +/// the \p TUSummaryData argument. +/// +/// Returns null without emitting a failure when no summary was recorded for +/// the entity — that is a valid outcome in tests that assert absence. +/// Emits ADD_FAILURE and returns null when the decl or its EntityId cannot +/// be resolved, as those indicate test-infrastructure errors. +template <typename SummaryT, typename ContributorT = FunctionDecl, + typename TUSummaryDataT> +const SummaryT *getEntitySummary(llvm::StringRef ContributorName, + ASTContext &Ctx, TUSummaryExtractor &Extractor, + const TUSummaryDataT &TUSummaryData) { + const ContributorT *D = findDeclByName<ContributorT>(ContributorName, Ctx); + + if (!D) { + ADD_FAILURE() << "failed to find decl '" << ContributorName << "'"; + return nullptr; + } + + std::optional<EntityId> Id = Extractor.addEntity(D); + + if (!Id) { + ADD_FAILURE() << "failed to get EntityId for '" << ContributorName << "'"; + return nullptr; + } + auto SumIt = TUSummaryData.find(SummaryT::summaryName()); + + if (SumIt == TUSummaryData.end()) + return nullptr; + + auto EntIt = SumIt->second.find(*Id); + + if (EntIt == SumIt->second.end()) + return nullptr; + return static_cast<const SummaryT *>(EntIt->second.get()); +} + +class OperatorNewDeletePointersExtractorTest : public ssaf::TestFixture { +protected: + SSAFOptions Opts; + TUSummary TUSum; + TUSummaryBuilder Builder; + std::unique_ptr<TUSummaryExtractor> Extractor; + std::unique_ptr<ASTUnit> AST; + + OperatorNewDeletePointersExtractorTest() + : TUSum(llvm::Triple("arm64-apple-macosx"), + BuildNamespace(BuildNamespaceKind::CompilationUnit, "Mock.cpp")), + Builder(TUSum, Opts), Extractor(nullptr) {} + + bool setUpTest(llvm::StringRef Code) { + AST = tooling::buildASTFromCodeWithArgs(Code, {"-std=c++20"}); + if (!AST) { + ADD_FAILURE() << "failed to build AST"; + return false; + } + for (auto &E : TUSummaryExtractorRegistry::entries()) { + if (E.getName() == OperatorNewDeletePointersEntitySummary::Name) { + Extractor = E.instantiate(Builder); + break; + } + } + if (!Extractor) { + ADD_FAILURE() << "failed to find OperatorNewDeletePointersExtractor"; + return false; + } + Extractor->HandleTranslationUnit(AST->getASTContext()); + return true; + } + + const OperatorNewDeletePointersEntitySummary * + getEntitySummary(llvm::StringRef FnName) { + return ::getEntitySummary<OperatorNewDeletePointersEntitySummary>( + FnName, AST->getASTContext(), *Extractor, getData(TUSum)); + } + + std::optional<EntityId> getEntityId(llvm::StringRef Name) { + if (const auto *D = findDeclByName(Name, AST->getASTContext())) + return Extractor->addEntity(D); + return std::nullopt; + } + + std::optional<EntityId> getEntityIdForReturn(llvm::StringRef FnName) { + if (const FunctionDecl *FD = findFnByName(FnName, AST->getASTContext())) + return Extractor->addEntityForReturn(FD); + return std::nullopt; + } +}; + +//===----------------------------------------------------------------------===// +// Registration sanity +//===----------------------------------------------------------------------===// + +TEST(OperatorNewDeletePointersExtractorRegistration, ExtractorRegistered) { + EXPECT_TRUE(isTUSummaryExtractorRegistered( + OperatorNewDeletePointersEntitySummary::Name)); +} + +//===----------------------------------------------------------------------===// +// Extractor cases +//===----------------------------------------------------------------------===// + +TEST_F(OperatorNewDeletePointersExtractorTest, FreeOperatorDelete) { + ASSERT_TRUE(setUpTest(R"cpp( + void operator delete(void *ptr) noexcept; + void operator delete(void *ptr) noexcept { (void)ptr; } + )cpp")); + + const auto *S = getEntitySummary("operator delete"); + + ASSERT_TRUE(S); + + auto PtrId = getEntityId("ptr"); + + ASSERT_TRUE(PtrId); + + EXPECT_EQ(*S, std::set{*PtrId}); +} + +TEST_F(OperatorNewDeletePointersExtractorTest, MemberOperatorDelete) { + ASSERT_TRUE(setUpTest(R"cpp( + class T { + public: + void operator delete(void *p) noexcept { (void)p; } + }; + )cpp")); + + const auto *S = getEntitySummary("operator delete"); + + ASSERT_TRUE(S); + + auto PId = getEntityId("p"); + ASSERT_TRUE(PId); + + EXPECT_EQ(*S, std::set{*PId}); +} + +TEST_F(OperatorNewDeletePointersExtractorTest, OperatorDeleteArray) { + ASSERT_TRUE(setUpTest(R"cpp( + void operator delete[](void *p) noexcept; + void operator delete[](void *p) noexcept { (void)p; } + )cpp")); + + const auto *S = getEntitySummary("operator delete[]"); + + ASSERT_TRUE(S); + + auto PId = getEntityId("p"); + + ASSERT_TRUE(PId); + EXPECT_EQ(*S, std::set{*PId}); +} + +TEST_F(OperatorNewDeletePointersExtractorTest, OperatorNew) { + ASSERT_TRUE(setUpTest(R"cpp( + typedef unsigned long size_t; + void *operator new(size_t size); + void *operator new(size_t size) { (void)size; return nullptr; } + )cpp")); + + const auto *S = getEntitySummary("operator new"); + + ASSERT_TRUE(S); + + auto RetId = getEntityIdForReturn("operator new"); + + ASSERT_TRUE(RetId); + EXPECT_EQ(*S, std::set{*RetId}); +} + +TEST_F(OperatorNewDeletePointersExtractorTest, PlacementNew) { + ASSERT_TRUE(setUpTest(R"cpp( + typedef unsigned long size_t; + void *operator new(size_t size, void *placement) noexcept; + void *operator new(size_t size, void *placement) noexcept { + (void)size; return placement; + } + )cpp")); + + const auto *S = getEntitySummary("operator new"); + + ASSERT_TRUE(S); + + auto PlacementId = getEntityId("placement"); + auto RetId = getEntityIdForReturn("operator new"); + + ASSERT_TRUE(PlacementId); + ASSERT_TRUE(RetId); + EXPECT_EQ(*S, (std::set{*PlacementId, *RetId})); +} + +TEST_F(OperatorNewDeletePointersExtractorTest, PlacementDelete) { + ASSERT_TRUE(setUpTest(R"cpp( + void operator delete(void *ptr, void *placement) noexcept; + void operator delete(void *ptr, void *placement) noexcept { + (void)ptr; (void)placement; + } + )cpp")); + + const auto *S = getEntitySummary("operator delete"); + + ASSERT_TRUE(S); + + auto PtrId = getEntityId("ptr"); + auto PlacementId = getEntityId("placement"); + + ASSERT_TRUE(PtrId); + ASSERT_TRUE(PlacementId); + EXPECT_EQ(*S, (std::set{*PtrId, *PlacementId})); +} + +TEST_F(OperatorNewDeletePointersExtractorTest, NoOperatorNewOrDeleteSummary) { + ASSERT_TRUE(setUpTest(R"cpp( + class T { int x; }; + )cpp")); + + auto &TUData = getData(TUSum); + auto TUSummariesIter = + TUData.find(OperatorNewDeletePointersEntitySummary::summaryName()); + + ASSERT_EQ(TUSummariesIter, TUData.end()); +} + +} // namespace diff --git a/clang/unittests/ScalableStaticAnalysis/CMakeLists.txt b/clang/unittests/ScalableStaticAnalysis/CMakeLists.txt index 12ee90f3cbebf..972287132b36d 100644 --- a/clang/unittests/ScalableStaticAnalysis/CMakeLists.txt +++ b/clang/unittests/ScalableStaticAnalysis/CMakeLists.txt @@ -2,6 +2,7 @@ add_distinct_clang_unittest(ClangScalableAnalysisTests Analyses/PointerFlow/PointerFlowTest.cpp Analyses/PointerFlow/PointerFlowWPATest.cpp Analyses/CallGraph/CallGraphExtractorTest.cpp + Analyses/OperatorNewDelete/OperatorNewDeletePointersExtractorTest.cpp Analyses/UnsafeBufferUsage/UnsafeBufferUsageTest.cpp Analyses/UnsafeBufferUsage/UnsafeBufferUsageWPATest.cpp ASTEntityMappingTest.cpp _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
