Author: Balázs Benics
Date: 2026-09-26T14:47:17+01:00
New Revision: ea0493b90c036776652105da246ce3fa56e33901

URL: 
https://github.com/llvm/llvm-project/commit/ea0493b90c036776652105da246ce3fa56e33901
DIFF: 
https://github.com/llvm/llvm-project/commit/ea0493b90c036776652105da246ce3fa56e33901.diff

LOG: [SSAF] Extract the virtual method override relation per TU (#213316)

A virtual call may dispatch to any override of its callee, so a whole-program
analysis cannot reason about a method's parameters and return value in
isolation. It needs to know which method overrides which, and which slots
that relates. Collect this per TU, so a later pass can join the related
slots into families.

JSON serialization lands separately, so the summary is not writable via
`--ssaf-extract-summaries` yet.

§1 of rdar://179151603

Assisted-By: claude

Added: 
    
clang/include/clang/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamily.h
    
clang/lib/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodEntityExtractor.cpp
    
clang/unittests/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamilyExtractorTest.cpp
    
clang/unittests/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamilyTestSupport.h
    clang/unittests/ScalableStaticAnalysis/ParsedAST.h

Modified: 
    clang/include/clang/ScalableStaticAnalysis/BuiltinAnchorSources.def
    clang/lib/ScalableStaticAnalysis/Analyses/CMakeLists.txt
    clang/unittests/ScalableStaticAnalysis/CMakeLists.txt

Removed: 
    


################################################################################
diff  --git 
a/clang/include/clang/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamily.h
 
b/clang/include/clang/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamily.h
new file mode 100644
index 0000000000000..a39f2ffa1c9dc
--- /dev/null
+++ 
b/clang/include/clang/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamily.h
@@ -0,0 +1,51 @@
+//===- VirtualMethodFamily.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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef 
LLVM_CLANG_SCALABLESTATICANALYSIS_ANALYSES_VIRTUALMETHODFAMILY_VIRTUALMETHODFAMILY_H
+#define 
LLVM_CLANG_SCALABLESTATICANALYSIS_ANALYSES_VIRTUALMETHODFAMILY_VIRTUALMETHODFAMILY_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 <optional>
+#include <tuple>
+#include <vector>
+
+namespace clang::ssaf {
+
+struct VirtualMethodSummary final : public EntitySummary {
+  static constexpr llvm::StringLiteral Name = "VirtualMethod";
+
+  static SummaryName summaryName() { return SummaryName(Name.str()); }
+
+  SummaryName getSummaryName() const override { return summaryName(); }
+
+  /// EntityIds of each ParmVarDecl, in source order.
+  std::vector<EntityId> ParamEntities;
+
+  /// EntityId of the synthetic return-slot entity for this method.
+  std::optional<EntityId> ReturnEntity;
+
+  /// The result of \c CXXMethodDecl::overridden_methods().
+  std::vector<EntityId> OverriddenMethods;
+
+  bool operator==(const VirtualMethodSummary &Other) const {
+    return std::tie(ParamEntities, ReturnEntity, OverriddenMethods) ==
+           std::tie(Other.ParamEntities, Other.ReturnEntity,
+                    Other.OverriddenMethods);
+  }
+
+  bool operator!=(const VirtualMethodSummary &Other) const {
+    return !(*this == Other);
+  }
+};
+
+} // namespace clang::ssaf
+
+#endif // 
LLVM_CLANG_SCALABLESTATICANALYSIS_ANALYSES_VIRTUALMETHODFAMILY_VIRTUALMETHODFAMILY_H

diff  --git 
a/clang/include/clang/ScalableStaticAnalysis/BuiltinAnchorSources.def 
b/clang/include/clang/ScalableStaticAnalysis/BuiltinAnchorSources.def
index 4af2184b9d8a6..3ccb902ceb43e 100644
--- a/clang/include/clang/ScalableStaticAnalysis/BuiltinAnchorSources.def
+++ b/clang/include/clang/ScalableStaticAnalysis/BuiltinAnchorSources.def
@@ -31,5 +31,6 @@ ANCHOR(SharedLexicalRepresentationJSONFormatAnchorSource)
 ANCHOR(UnsafeBufferUsageAnalysisAnchorSource)
 ANCHOR(UnsafeBufferUsageExtractorAnchorSource)
 ANCHOR(UnsafeBufferUsageJSONFormatAnchorSource)
+ANCHOR(VirtualMethodEntityExtractorAnchorSource)
 
 #undef ANCHOR

diff  --git a/clang/lib/ScalableStaticAnalysis/Analyses/CMakeLists.txt 
b/clang/lib/ScalableStaticAnalysis/Analyses/CMakeLists.txt
index 98ce8e799e0e0..cc9191922309b 100644
--- a/clang/lib/ScalableStaticAnalysis/Analyses/CMakeLists.txt
+++ b/clang/lib/ScalableStaticAnalysis/Analyses/CMakeLists.txt
@@ -20,6 +20,7 @@ add_clang_library(clangScalableStaticAnalysisAnalyses
   UnsafeBufferUsage/UnsafeBufferUsageAnalysis.cpp
   UnsafeBufferUsage/UnsafeBufferUsageExtractor.cpp
   UnsafeBufferUsage/UnsafeBufferUsageFormat.cpp
+  VirtualMethodFamily/VirtualMethodEntityExtractor.cpp
 
   LINK_LIBS
   clangAST

diff  --git 
a/clang/lib/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodEntityExtractor.cpp
 
b/clang/lib/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodEntityExtractor.cpp
new file mode 100644
index 0000000000000..e3a55487d7505
--- /dev/null
+++ 
b/clang/lib/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodEntityExtractor.cpp
@@ -0,0 +1,92 @@
+//===- VirtualMethodEntityExtractor.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
+//
+//===----------------------------------------------------------------------===//
+//
+// Extract what virtual methods override what other methods.
+// The parameters might be also important for consumers so collect those as
+// well - alongside with the ID of the return value.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/DeclCXX.h"
+#include "clang/AST/DynamicRecursiveASTVisitor.h"
+#include 
"clang/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamily.h"
+#include "clang/ScalableStaticAnalysis/Core/Model/EntityId.h"
+#include "clang/ScalableStaticAnalysis/Core/TUSummary/ExtractorRegistry.h"
+#include "clang/ScalableStaticAnalysis/Core/TUSummary/TUSummaryBuilder.h"
+#include "clang/ScalableStaticAnalysis/Core/TUSummary/TUSummaryExtractor.h"
+#include <memory>
+#include <optional>
+
+using namespace clang;
+using namespace ssaf;
+
+namespace {
+
+class VirtualMethodEntityExtractor final : public TUSummaryExtractor,
+                                           ConstDynamicRecursiveASTVisitor {
+public:
+  explicit VirtualMethodEntityExtractor(TUSummaryBuilder &Builder)
+      : TUSummaryExtractor(Builder) {
+    ShouldVisitTemplateInstantiations = true;
+    ShouldWalkTypesOfTypeLocs = false;
+    ShouldVisitImplicitCode = false;
+    ShouldVisitLambdaBody = true;
+  }
+
+private:
+  void HandleTranslationUnit(ASTContext &Ctx) override { TraverseAST(Ctx); }
+
+  bool VisitCXXMethodDecl(const CXXMethodDecl *MD) override;
+};
+} // namespace
+
+bool VirtualMethodEntityExtractor::VisitCXXMethodDecl(const CXXMethodDecl *MD) 
{
+  if (!MD->isVirtual())
+    return true;
+
+  std::optional<EntityId> MethodId = addEntity(MD);
+  if (!MethodId)
+    return true;
+
+  auto Summary = std::make_unique<VirtualMethodSummary>();
+  Summary->ParamEntities.reserve(MD->getNumParams());
+
+  for (const ParmVarDecl *P : MD->parameters()) {
+    auto ParamId = addEntity(P);
+    if (!ParamId) {
+      // If we can't get an EntityId for a parameter, drop the entire summary
+      // rather than leaving a half-populated record.
+      return true;
+    }
+    Summary->ParamEntities.push_back(ParamId.value());
+  }
+
+  if (auto ReturnId = addEntityForReturn(MD))
+    Summary->ReturnEntity = ReturnId.value();
+
+  for (const CXXMethodDecl *Overridden : MD->overridden_methods()) {
+    // We may not be able to convert methods that are coming from system
+    // headers, so skip them gracefully.
+    if (auto OverriddenId = addEntity(Overridden))
+      Summary->OverriddenMethods.push_back(*OverriddenId);
+  }
+
+  SummaryBuilder.addSummary(MethodId.value(), std::move(Summary));
+  return true;
+}
+
+static TUSummaryExtractorRegistry::Add<VirtualMethodEntityExtractor>
+    RegisterExtractor(VirtualMethodSummary::Name,
+                      "Extract information about virtual methods");
+
+namespace clang::ssaf {
+// NOLINTNEXTLINE(misc-use-internal-linkage)
+volatile int VirtualMethodEntityExtractorAnchorSource = 0;
+} // namespace clang::ssaf

diff  --git 
a/clang/unittests/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamilyExtractorTest.cpp
 
b/clang/unittests/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamilyExtractorTest.cpp
new file mode 100644
index 0000000000000..8342ee3e56de7
--- /dev/null
+++ 
b/clang/unittests/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamilyExtractorTest.cpp
@@ -0,0 +1,264 @@
+//===- VirtualMethodFamilyExtractorTest.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 "VirtualMethodFamilyTestSupport.h"
+#include 
"clang/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamily.h"
+#include "clang/ScalableStaticAnalysis/Core/Model/EntityId.h"
+#include "clang/ScalableStaticAnalysis/Core/TUSummary/ExtractorRegistry.h"
+#include "gtest/gtest.h"
+
+#include <set>
+
+using namespace clang;
+using namespace ssaf;
+
+namespace {
+
+using VirtualMethodFamilyExtractorTest = VirtualMethodFamilyTestBase;
+
+TEST_F(VirtualMethodFamilyExtractorTest, Registers) {
+  EXPECT_TRUE(isTUSummaryExtractorRegistered(VirtualMethodSummary::Name));
+}
+
+using VirtualMethodFamilyExtractorBasicFieldPopulationTest =
+    VirtualMethodFamilyExtractorTest;
+
+TEST_F(VirtualMethodFamilyExtractorBasicFieldPopulationTest, BaseVirtual) {
+  ASSERT_TRUE(runVirtualMethodExtractor(R"cpp(
+    class Base {
+    public:
+      virtual void foo(int *p);
+    };
+  )cpp"));
+
+  const auto *S = getMethodSummary(AST.fn("Base::foo"));
+  ASSERT_TRUE(S);
+  EXPECT_TRUE(S->ReturnEntity.has_value());
+  EXPECT_EQ(S->ParamEntities.size(), 1u);
+  // A root virtual method overrides nothing.
+  EXPECT_TRUE(S->OverriddenMethods.empty());
+}
+
+TEST_F(VirtualMethodFamilyExtractorBasicFieldPopulationTest, PureVirtual) {
+  ASSERT_TRUE(runVirtualMethodExtractor(R"cpp(
+    class Interface {
+    public:
+      virtual void foo(int *p) = 0;
+    };
+  )cpp"));
+
+  // A pure-virtual method is still virtual, so a summary is produced for it.
+  ASSERT_TRUE(getMethodSummary(AST.fn("Interface::foo")));
+}
+
+TEST_F(VirtualMethodFamilyExtractorBasicFieldPopulationTest,
+       NonVirtualMethodSkipped) {
+  ASSERT_TRUE(runVirtualMethodExtractor(R"cpp(
+    class C {
+    public:
+      virtual void v();
+      void nv();
+    };
+  )cpp"));
+
+  // Only the virtual method has a summary; non-virtual is skipped.
+  EXPECT_EQ(methodSummaryCount(), 1u);
+  EXPECT_TRUE(getMethodSummary(AST.fn("C::v")));
+}
+
+TEST_F(VirtualMethodFamilyExtractorBasicFieldPopulationTest,
+       OverrideWithoutVirtualKeywordExtracted) {
+  ASSERT_TRUE(runVirtualMethodExtractor(R"cpp(
+    class Base {
+    public:
+      virtual void foo(int *p);
+    };
+    class Derived : public Base {
+    public:
+      void foo(int *p) override;
+    };
+  )cpp"));
+
+  EXPECT_EQ(methodSummaryCount(), 2u);
+  EXPECT_TRUE(getMethodSummary(AST.fn("Base::foo")));
+  EXPECT_TRUE(getMethodSummary(AST.fn("Derived::foo")));
+}
+
+using VirtualMethodFamilyExtractorOverriddenMethodTest =
+    VirtualMethodFamilyExtractorTest;
+
+TEST_F(VirtualMethodFamilyExtractorOverriddenMethodTest,
+       EdgeBaseDerivedOverride) {
+  ASSERT_TRUE(runVirtualMethodExtractor(R"cpp(
+    struct Base {
+      virtual void f(int *p);
+    };
+    struct Derived : Base {
+      void f(int *p) override;
+    };
+  )cpp"));
+  const auto *D = getMethodSummary(AST.fn("Derived::f"));
+  const auto *B = getMethodSummary(AST.fn("Base::f"));
+  ASSERT_TRUE(D);
+  ASSERT_TRUE(B);
+  auto BId = entityIdOf(AST.fn("Base::f"));
+  ASSERT_TRUE(BId.has_value());
+  ASSERT_EQ(D->OverriddenMethods.size(), 1u);
+  EXPECT_EQ(D->OverriddenMethods[0], *BId);
+  EXPECT_TRUE(B->OverriddenMethods.empty());
+}
+
+TEST_F(VirtualMethodFamilyExtractorOverriddenMethodTest,
+       EdgePureVirtualReabstractsOverride) {
+  // Tricky: a pure-virtual method that OVERRIDES a concrete virtual. Its edge
+  // set must be non-empty despite being pure.
+  ASSERT_TRUE(runVirtualMethodExtractor(R"cpp(
+    struct A {
+      virtual void f(int *p);
+    };
+    struct B : A {
+      void f(int *p) = 0;
+    };
+  )cpp"));
+  const auto *Bf = getMethodSummary(AST.fn("B::f"));
+  ASSERT_TRUE(Bf);
+  auto Af = entityIdOf(AST.fn("A::f"));
+  ASSERT_TRUE(Af.has_value());
+  ASSERT_EQ(Bf->OverriddenMethods.size(), 1u);
+  EXPECT_EQ(Bf->OverriddenMethods[0], *Af);
+}
+
+TEST_F(VirtualMethodFamilyExtractorOverriddenMethodTest,
+       EdgeMultipleInheritanceTwoEdges) {
+  // Tricky: one override occupies two independent base slots.
+  ASSERT_TRUE(runVirtualMethodExtractor(R"cpp(
+    struct A {
+      virtual void f(int *p);
+    };
+    struct B {
+      virtual void f(int *p);
+    };
+    struct D : A, B {
+      void f(int *p) override;
+    };
+  )cpp"));
+  const auto *Df = getMethodSummary(AST.fn("D::f"));
+  ASSERT_TRUE(Df);
+  auto Af = entityIdOf(AST.fn("A::f"));
+  auto Bf = entityIdOf(AST.fn("B::f"));
+  ASSERT_TRUE(Af.has_value() && Bf.has_value());
+  ASSERT_EQ(Df->OverriddenMethods.size(), 2u);
+  std::set<EntityId> Edges(Df->OverriddenMethods.begin(),
+                           Df->OverriddenMethods.end());
+  EXPECT_EQ(Edges.count(*Af), 1u);
+  EXPECT_EQ(Edges.count(*Bf), 1u);
+}
+
+TEST_F(VirtualMethodFamilyExtractorOverriddenMethodTest,
+       EdgeTransitiveChainRecordsOnlyDirectOverride) {
+  ASSERT_TRUE(runVirtualMethodExtractor(R"cpp(
+    struct C {
+      virtual void f(int *p);
+    };
+    struct B : C {
+      void f(int *p) override;
+    };
+    struct A : B {
+      void f(int *p) override;
+    };
+  )cpp"));
+  const auto *Af = getMethodSummary(AST.fn("A::f"));
+  const auto *Bf = getMethodSummary(AST.fn("B::f"));
+  const auto *Cf = getMethodSummary(AST.fn("C::f"));
+  auto BfId = entityIdOf(AST.fn("B::f"));
+  auto CfId = entityIdOf(AST.fn("C::f"));
+  ASSERT_TRUE(Af);
+  ASSERT_TRUE(Bf);
+  ASSERT_TRUE(Cf);
+  ASSERT_TRUE(BfId.has_value());
+  ASSERT_TRUE(CfId.has_value());
+
+  // A::f overrides only its immediate base B::f, not the transitive C::f.
+  ASSERT_EQ(Af->OverriddenMethods.size(), 1u);
+  EXPECT_EQ(Af->OverriddenMethods[0], *BfId);
+
+  // B::f overrides C::f.
+  ASSERT_EQ(Bf->OverriddenMethods.size(), 1u);
+  EXPECT_EQ(Bf->OverriddenMethods[0], *CfId);
+
+  // C::f is a root and overrides nothing.
+  EXPECT_TRUE(Cf->OverriddenMethods.empty());
+}
+
+TEST_F(VirtualMethodFamilyExtractorOverriddenMethodTest,
+       EdgeOverrideLinksMatchingOverloadOnly) {
+  // Tricky: overloads must not be conflated. B::f(int*) overrides only the
+  // f(int*) base overload, never f(char*).
+  ASSERT_TRUE(runVirtualMethodExtractor(R"cpp(
+    struct A {
+      virtual void f(int *p);
+      virtual void f(char *p);
+    };
+    struct B : A {
+      void f(int *p) override;
+    };
+  )cpp"));
+  const auto *Bf = getMethodSummary(AST.fn("B::f"));
+  ASSERT_TRUE(Bf);
+  auto AfInt = entityIdOf(AST.fn("A::f(int *)"));
+  auto AfChar = entityIdOf(AST.fn("A::f(char *)"));
+  ASSERT_TRUE(AfInt.has_value() && AfChar.has_value());
+  ASSERT_EQ(Bf->OverriddenMethods.size(), 1u);
+  EXPECT_EQ(Bf->OverriddenMethods[0], *AfInt);
+  EXPECT_NE(Bf->OverriddenMethods[0], *AfChar);
+}
+
+TEST_F(VirtualMethodFamilyExtractorOverriddenMethodTest,
+       EdgeCovariantReturnOverride) {
+  ASSERT_TRUE(runVirtualMethodExtractor(R"cpp(
+    struct Base {
+      virtual Base *clone();
+    };
+    struct Deriv : Base {
+      Deriv *clone() override;
+    };
+  )cpp"));
+  const auto *Dc = getMethodSummary(AST.fn("Deriv::clone"));
+  ASSERT_TRUE(Dc);
+  auto Bc = entityIdOf(AST.fn("Base::clone"));
+  ASSERT_TRUE(Bc.has_value());
+  ASSERT_EQ(Dc->OverriddenMethods.size(), 1u);
+  EXPECT_EQ(Dc->OverriddenMethods[0], *Bc);
+  EXPECT_TRUE(Dc->ReturnEntity.has_value());
+}
+
+TEST_F(VirtualMethodFamilyExtractorOverriddenMethodTest,
+       EdgeDependentBaseTemplatePatternNoCrash) {
+  // Tricky: the primary template pattern has a dependent base; overridden_
+  // methods is unresolved there. Must not crash; the instantiation carries the
+  // edge.
+  ASSERT_TRUE(runVirtualMethodExtractor(R"cpp(
+    template <class T>
+    struct Wrapper {
+      virtual void f(int *p);
+    };
+    template <class T> struct DTypeParam : T {
+      virtual void f(int *p);
+    };
+    template <class T> struct DSpec : Wrapper<T> {
+      void f(int *p) override;
+    };
+    struct Concrete {
+      virtual void f(int *p);
+    };
+    template struct DSpec<Concrete>;
+  )cpp"));
+  EXPECT_GT(methodSummaryCount(), 0u);
+}
+
+} // namespace

diff  --git 
a/clang/unittests/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamilyTestSupport.h
 
b/clang/unittests/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamilyTestSupport.h
new file mode 100644
index 0000000000000..122f827bdda5f
--- /dev/null
+++ 
b/clang/unittests/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamilyTestSupport.h
@@ -0,0 +1,155 @@
+//===- VirtualMethodFamilyTestSupport.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
+//
+//===----------------------------------------------------------------------===//
+//
+// Shared fixture for the VirtualMethodFamily tests: parses a snippet, runs the
+// VirtualMethod extractor over it, and resolves declarations to the EntityIds
+// the extractor minted.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef 
LLVM_CLANG_UNITTESTS_SCALABLESTATICANALYSIS_ANALYSES_VIRTUALMETHODFAMILY_VIRTUALMETHODFAMILYTESTSUPPORT_H
+#define 
LLVM_CLANG_UNITTESTS_SCALABLESTATICANALYSIS_ANALYSES_VIRTUALMETHODFAMILY_VIRTUALMETHODFAMILYTESTSUPPORT_H
+
+#include "ParsedAST.h"
+#include "TestFixture.h"
+#include "clang/Frontend/SSAFOptions.h"
+#include 
"clang/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamily.h"
+#include "clang/ScalableStaticAnalysis/Core/ASTEntityMapping.h"
+#include "clang/ScalableStaticAnalysis/Core/Model/BuildNamespace.h"
+#include "clang/ScalableStaticAnalysis/Core/Model/EntityId.h"
+#include "clang/ScalableStaticAnalysis/Core/Model/EntityName.h"
+#include "clang/ScalableStaticAnalysis/Core/TUSummary/ExtractorRegistry.h"
+#include "clang/ScalableStaticAnalysis/Core/TUSummary/TUSummary.h"
+#include "clang/ScalableStaticAnalysis/Core/TUSummary/TUSummaryBuilder.h"
+#include "llvm/TargetParser/Triple.h"
+
+#include <map>
+#include <optional>
+#include <string>
+
+namespace clang::ssaf {
+
+/// Base fixture for tests that need a TUSummary populated by the
+/// VirtualMethod extractor.
+class VirtualMethodFamilyTestBase : public TestFixture {
+protected:
+  ParsedAST AST;
+
+  /// Parses \p Code and runs the VirtualMethod extractor over it. Returns
+  /// false if the AST could not be built or the extractor is not registered.
+  /// Call once per test, before any of the lookups below.
+  [[nodiscard]] bool runVirtualMethodExtractor(llvm::StringRef Code) {
+    if (!AST.parse(Code))
+      return false;
+    auto Extractor =
+        makeTUSummaryExtractor(VirtualMethodSummary::Name, Builder);
+    if (!Extractor)
+      return false;
+    Extractor->HandleTranslationUnit(AST.getASTContext());
+    return true;
+  }
+
+  /// Resolves \p ND to the EntityId the extractor minted for it, or
+  /// std::nullopt if the extractor produced no entity for it.
+  std::optional<EntityId> entityIdOf(const NamedDecl *ND) const {
+    return ND ? lookup(getEntityName(ND)) : std::nullopt;
+  }
+
+  /// Resolves the return slot of \p FD to its EntityId.
+  std::optional<EntityId> returnEntityIdOf(const FunctionDecl *FD) const {
+    return FD ? lookup(getEntityNameForReturn(FD)) : std::nullopt;
+  }
+
+  /// Resolves an EntityName against the table the extractor populated.
+  std::optional<EntityId> lookup(std::optional<EntityName> Name) const {
+    if (!Name)
+      return std::nullopt;
+    const auto &Entities = getEntities(getIdTable(TUSum));
+    auto It = Entities.find(*Name);
+    if (It == Entities.end())
+      return std::nullopt;
+    return It->second;
+  }
+
+  /// Looks up the extractor's summary for \p FD, or nullptr if it produced
+  /// none for this function.
+  const VirtualMethodSummary *getMethodSummary(const FunctionDecl *FD) const {
+    auto Id = entityIdOf(FD);
+    if (!Id)
+      return nullptr;
+    const auto &Data = getData(TUSum);
+    auto SumIt = Data.find(VirtualMethodSummary::summaryName());
+    if (SumIt == Data.end())
+      return nullptr;
+    auto EIt = SumIt->second.find(*Id);
+    if (EIt == SumIt->second.end())
+      return nullptr;
+    return static_cast<const VirtualMethodSummary *>(EIt->second.get());
+  }
+
+  /// Count of method-summary entries in the TUSummary.
+  std::size_t methodSummaryCount() const {
+    const auto &Data = getData(TUSum);
+    auto It = Data.find(VirtualMethodSummary::summaryName());
+    if (It == Data.end())
+      return 0;
+    return It->second.size();
+  }
+
+  /// Maps every EntityId the extractor minted for the parsed snippet to a
+  /// readable label: "Base::foo(int *)", "Base::foo(int *)#return" or
+  /// "Base::foo(int *)#param0 'p'". Entities the extractor skipped are absent.
+  std::map<EntityId, std::string> entityLabels() const {
+    std::map<EntityId, std::string> Labels;
+    auto Add = [&](std::optional<EntityId> Id, std::string Label) {
+      if (Id)
+        Labels.insert({*Id, std::move(Label)});
+    };
+
+    for (const FunctionDecl *FD : AST.functions()) {
+      std::string Sig = ParsedAST::signatureOf(FD);
+      Add(entityIdOf(FD), Sig);
+      Add(returnEntityIdOf(FD), Sig + "#return");
+      for (unsigned I = 0, E = FD->getNumParams(); I != E; ++I) {
+        const ParmVarDecl *P = FD->getParamDecl(I);
+        std::string Label = Sig + "#param" + std::to_string(I);
+        if (!P->getName().empty())
+          Label += " '" + P->getName().str() + "'";
+        Add(entityIdOf(P), std::move(Label));
+      }
+    }
+    return Labels;
+  }
+
+  /// The entityLabels() mapping as text, to be streamed into a failing
+  /// assertion so that the EntityIds in its message can be decoded.
+  std::string legend() const {
+    std::map<EntityId, std::string> Labels = entityLabels();
+    if (Labels.empty())
+      return "\nid legend: <no entities extracted>";
+
+    std::string Result;
+    llvm::raw_string_ostream OS(Result);
+    OS << "\nid legend:";
+    for (const auto &[Id, Label] : Labels)
+      OS << "\n  " << Id << " = " << Label;
+    return Result;
+  }
+
+  TUSummary &tuSummary() { return TUSum; }
+
+private:
+  SSAFOptions Opts;
+  BuildNamespace NS{BuildNamespaceKind::CompilationUnit, "Mock.cpp"};
+  TUSummary TUSum{llvm::Triple("arm64-apple-macosx"), NS};
+  TUSummaryBuilder Builder{TUSum, Opts};
+};
+
+} // namespace clang::ssaf
+
+#endif // 
LLVM_CLANG_UNITTESTS_SCALABLESTATICANALYSIS_ANALYSES_VIRTUALMETHODFAMILY_VIRTUALMETHODFAMILYTESTSUPPORT_H

diff  --git a/clang/unittests/ScalableStaticAnalysis/CMakeLists.txt 
b/clang/unittests/ScalableStaticAnalysis/CMakeLists.txt
index 8c339bee60f63..aaee5b76a6e73 100644
--- a/clang/unittests/ScalableStaticAnalysis/CMakeLists.txt
+++ b/clang/unittests/ScalableStaticAnalysis/CMakeLists.txt
@@ -7,6 +7,7 @@ add_distinct_clang_unittest(ClangScalableAnalysisTests
   Analyses/SharedLexicalRepresentation/EntitySourceLocationExtractorTest.cpp
   Analyses/UnsafeBufferUsage/UnsafeBufferUsageTest.cpp
   Analyses/UnsafeBufferUsage/UnsafeBufferUsageWPATest.cpp
+  Analyses/VirtualMethodFamily/VirtualMethodFamilyExtractorTest.cpp
   ASTEntityMappingTest.cpp
   BuildNamespaceTest.cpp
   EntityIdTableTest.cpp

diff  --git a/clang/unittests/ScalableStaticAnalysis/ParsedAST.h 
b/clang/unittests/ScalableStaticAnalysis/ParsedAST.h
new file mode 100644
index 0000000000000..71f9decd6ec58
--- /dev/null
+++ b/clang/unittests/ScalableStaticAnalysis/ParsedAST.h
@@ -0,0 +1,145 @@
+//===- ParsedAST.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
+//
+//===----------------------------------------------------------------------===//
+//
+// Owns an AST parsed from a code snippet and resolves declarations in it by
+// qualified name. Held by value in a test fixture, so that fixtures which
+// already have a base class of their own can still get AST lookups.
+//
+// Unlike findDeclByName in FindDecl.h, lookups here take a *qualified* name
+// ("Base::foo") and can pick among overloads.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_UNITTESTS_SCALABLESTATICANALYSIS_PARSEDAST_H
+#define LLVM_CLANG_UNITTESTS_SCALABLESTATICANALYSIS_PARSEDAST_H
+
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/DeclCXX.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/Frontend/ASTUnit.h"
+#include "clang/Tooling/Tooling.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/raw_ostream.h"
+#include "gtest/gtest.h"
+
+#include <memory>
+#include <string>
+
+namespace clang::ssaf {
+
+class ParsedAST {
+  std::unique_ptr<ASTUnit> AST;
+
+public:
+  /// Parses \p Code as C++17. Returns false if the AST could not be built, in
+  /// which case the lookups below all return nullptr.
+  [[nodiscard]] bool parse(llvm::StringRef Code) {
+    AST = tooling::buildASTFromCodeWithArgs(Code, {"-std=c++17"});
+    return AST != nullptr;
+  }
+
+  explicit operator bool() const { return AST != nullptr; }
+
+  ASTContext &getASTContext() const { return AST->getASTContext(); }
+
+  /// Finds a function by qualified name, e.g. "foo" or "Base::foo". Methods
+  /// are functions too, so this finds those as well.
+  ///
+  /// To pick among overloads, append the parameter list as clang prints it:
+  /// "A::f(int *)" and "A::f(char *)" name the two overloads of A::f. A bare
+  /// name that matches more than one function is an error, not a silent pick
+  /// of the first: it reports a gtest failure and returns nullptr, as does a
+  /// name that matches nothing.
+  const FunctionDecl *fn(llvm::StringRef NameOrSignature) const {
+    using namespace ast_matchers;
+    if (!AST)
+      return nullptr;
+
+    auto [Name, Params] = NameOrSignature.split('(');
+    auto Matches =
+        match(functionDecl(hasName(Name)).bind("f"), AST->getASTContext());
+
+    llvm::SmallVector<const FunctionDecl *> Candidates;
+    for (const auto &M : Matches) {
+      const auto *FD = M.getNodeAs<FunctionDecl>("f");
+      if (Params.empty() || paramsOf(FD) == Params.rtrim(')'))
+        Candidates.push_back(FD);
+    }
+
+    if (Candidates.size() == 1)
+      return Candidates.front();
+    if (Candidates.empty()) {
+      ADD_FAILURE() << "no function named '" << NameOrSignature << "'";
+    } else {
+      ADD_FAILURE() << "'" << NameOrSignature << "' is ambiguous; it matches "
+                    << Candidates.size()
+                    << " overloads. Append the parameter list to select one, "
+                       "e.g. '"
+                    << Name << "(" << paramsOf(Candidates.front()) << ")'";
+    }
+    return nullptr;
+  }
+
+  /// Finds parameter \p Index of the function named \p NameOrSignature.
+  /// Returns nullptr if there is no such function, or if it has too few
+  /// parameters.
+  const ParmVarDecl *findParam(llvm::StringRef NameOrSignature,
+                               unsigned Index) const {
+    const FunctionDecl *FD = fn(NameOrSignature);
+    if (!FD)
+      return nullptr;
+    if (Index >= FD->getNumParams()) {
+      ADD_FAILURE() << "'" << NameOrSignature << "' has no parameter " << 
Index;
+      return nullptr;
+    }
+    return FD->getParamDecl(Index);
+  }
+
+  /// Every non-implicit function (methods included) in the parsed snippet, in
+  /// the order the matcher walks the AST. Unlike fn(), a miss is not a test
+  /// failure: this is a plain enumeration, meant for building diagnostics.
+  llvm::SmallVector<const FunctionDecl *> functions() const {
+    using namespace ast_matchers;
+    llvm::SmallVector<const FunctionDecl *> Result;
+    if (!AST)
+      return Result;
+    for (const auto &M :
+         match(functionDecl().bind("f"), AST->getASTContext())) {
+      const auto *FD = M.getNodeAs<FunctionDecl>("f");
+      if (FD && !FD->isImplicit())
+        Result.push_back(FD);
+    }
+    return Result;
+  }
+
+  /// \p FD spelled the way fn() takes it: "Base::foo(int *)".
+  static std::string signatureOf(const FunctionDecl *FD) {
+    if (!FD)
+      return "<null>";
+    return FD->getQualifiedNameAsString() + "(" + paramsOf(FD) + ")";
+  }
+
+private:
+  /// The parameter list of \p FD as clang prints it, without the parentheses:
+  /// "int *", or "unsigned long, A &".
+  static std::string paramsOf(const FunctionDecl *FD) {
+    std::string Result;
+    llvm::raw_string_ostream OS(Result);
+    llvm::interleave(
+        FD->parameters(), OS,
+        [&](const ParmVarDecl *P) { OS << P->getType().getAsString(); }, ", ");
+    return Result;
+  }
+};
+
+} // namespace clang::ssaf
+
+#endif // LLVM_CLANG_UNITTESTS_SCALABLESTATICANALYSIS_PARSEDAST_H


        
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to