Author: Balázs Benics
Date: 2026-09-29T12:01:03+01:00
New Revision: 5239371ffa13f30915fe67744be2ea261b74be2a

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

LOG: [SSAF] Group virtual method slots into override families (#213317)

A virtual call site can dispatch to any override, so the parameter and
return slots that occupy the same vtable slot across an override chain are
indistinguishable to a caller. Whole-program consumers therefore have to
treat them as one unit or they will reason about a slot that a call never
actually reaches.

Compute those units up front, keyed per slot, so consumers only need a
map lookup rather than their own traversal of the override relation.
Overloads occupy distinct vtable slots and stay in distinct families.

The family representative is the smallest EntityId in the class, which
keeps the result stable across runs.

§2 of rdar://179151603

Assisted-By: claude

Added: 
    
clang/lib/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamilyAnalysis.cpp
    
clang/unittests/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamilyAnalysisTest.cpp

Modified: 
    
clang/include/clang/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamily.h
    clang/include/clang/ScalableStaticAnalysis/BuiltinAnchorSources.def
    clang/include/clang/ScalableStaticAnalysis/Core/Model/EntityId.h
    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
index a39f2ffa1c9dc..ce1978d82b6a7 100644
--- 
a/clang/include/clang/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamily.h
+++ 
b/clang/include/clang/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamily.h
@@ -12,7 +12,11 @@
 #include "clang/ScalableStaticAnalysis/Core/Model/EntityId.h"
 #include "clang/ScalableStaticAnalysis/Core/Model/SummaryName.h"
 #include "clang/ScalableStaticAnalysis/Core/TUSummary/EntitySummary.h"
+#include 
"clang/ScalableStaticAnalysis/Core/WholeProgramAnalysis/AnalysisName.h"
+#include 
"clang/ScalableStaticAnalysis/Core/WholeProgramAnalysis/AnalysisResult.h"
+#include "llvm/ADT/DenseMap.h"
 #include "llvm/ADT/StringRef.h"
+#include "llvm/Support/raw_ostream.h"
 #include <optional>
 #include <tuple>
 #include <vector>
@@ -46,6 +50,29 @@ struct VirtualMethodSummary final : public EntitySummary {
   }
 };
 
+struct VirtualMethodFamilyAnalysisResult final : AnalysisResult {
+  static AnalysisName analysisName() {
+    return AnalysisName("VirtualMethodFamilyAnalysisResult");
+  }
+
+  /// Maps each parameter or return slot to the ID of the family it belongs to.
+  /// The family ID is the smallest slot ID in the family.
+  llvm::DenseMap<EntityId, EntityId> RetAndParamData;
+
+  bool operator==(const VirtualMethodFamilyAnalysisResult &Other) const {
+    return RetAndParamData == Other.RetAndParamData;
+  }
+
+  bool operator!=(const VirtualMethodFamilyAnalysisResult &Other) const {
+    return !(*this == Other);
+  }
+};
+
+/// Prints \p R as one "<param/return id> -> <family id>" line per entry,
+/// ordered by the param/return id so that the output is stable across runs.
+llvm::raw_ostream &operator<<(llvm::raw_ostream &OS,
+                              const VirtualMethodFamilyAnalysisResult &R);
+
 } // 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 3ccb902ceb43e..138dceb56dfc4 100644
--- a/clang/include/clang/ScalableStaticAnalysis/BuiltinAnchorSources.def
+++ b/clang/include/clang/ScalableStaticAnalysis/BuiltinAnchorSources.def
@@ -32,5 +32,6 @@ ANCHOR(UnsafeBufferUsageAnalysisAnchorSource)
 ANCHOR(UnsafeBufferUsageExtractorAnchorSource)
 ANCHOR(UnsafeBufferUsageJSONFormatAnchorSource)
 ANCHOR(VirtualMethodEntityExtractorAnchorSource)
+ANCHOR(VirtualMethodFamilyAnalysisAnchorSource)
 
 #undef ANCHOR

diff  --git a/clang/include/clang/ScalableStaticAnalysis/Core/Model/EntityId.h 
b/clang/include/clang/ScalableStaticAnalysis/Core/Model/EntityId.h
index e8e36119927c8..6acd2d8c86111 100644
--- a/clang/include/clang/ScalableStaticAnalysis/Core/Model/EntityId.h
+++ b/clang/include/clang/ScalableStaticAnalysis/Core/Model/EntityId.h
@@ -34,6 +34,7 @@ class EntityId {
   friend class TestFixture;
   friend llvm::raw_ostream &operator<<(llvm::raw_ostream &OS,
                                        const EntityId &Id);
+  friend struct llvm::DenseMapInfo<EntityId>;
 
   size_t Index;
 
@@ -51,4 +52,14 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const 
EntityId &Id);
 
 } // namespace clang::ssaf
 
+namespace llvm {
+template <> struct DenseMapInfo<clang::ssaf::EntityId> {
+  using EntityId = clang::ssaf::EntityId;
+  static unsigned getHashValue(EntityId Val) {
+    return densemap::detail::mix(Val.Index);
+  }
+  static bool isEqual(EntityId LHS, EntityId RHS) { return LHS == RHS; }
+};
+} // namespace llvm
+
 #endif // LLVM_CLANG_SCALABLESTATICANALYSIS_CORE_MODEL_ENTITYID_H

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

diff  --git 
a/clang/lib/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamilyAnalysis.cpp
 
b/clang/lib/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamilyAnalysis.cpp
new file mode 100644
index 0000000000000..1d890da33a3bf
--- /dev/null
+++ 
b/clang/lib/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamilyAnalysis.cpp
@@ -0,0 +1,151 @@
+//===- VirtualMethodFamilyAnalysis.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 
"clang/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamily.h"
+#include "clang/ScalableStaticAnalysis/Core/Model/EntityId.h"
+#include 
"clang/ScalableStaticAnalysis/Core/WholeProgramAnalysis/AnalysisRegistry.h"
+#include 
"clang/ScalableStaticAnalysis/Core/WholeProgramAnalysis/SummaryAnalysis.h"
+#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/raw_ostream.h"
+#include <cassert>
+#include <map>
+#include <optional>
+#include <utility>
+
+using namespace clang::ssaf;
+
+namespace {
+
+struct MethodFamilyUnionFind {
+  EntityId find(EntityId E);
+  void unionSets(EntityId A, EntityId B);
+
+  auto keys() const { return llvm::make_first_range(Roots); }
+
+private:
+  llvm::DenseMap<EntityId, EntityId> Roots;
+};
+
+class VirtualMethodFamilyAnalysis final
+    : public SummaryAnalysis<VirtualMethodFamilyAnalysisResult,
+                             VirtualMethodSummary> {
+public:
+  llvm::Error add(EntityId Id, const VirtualMethodSummary &Summary) override {
+    Data[Id] = &Summary;
+    return llvm::Error::success();
+  }
+
+  llvm::Error finalize() override;
+
+private:
+  /// Fill the \c Family map.
+  void groupParamsAndReturnEntities();
+
+  /// Make the param and return IDs share a family.
+  void unionParamsAndReturnEntitiesInSummaries(const VirtualMethodSummary &LHS,
+                                               const VirtualMethodSummary 
&RHS);
+
+  MethodFamilyUnionFind Family;
+  std::map<EntityId, const VirtualMethodSummary *> Data;
+};
+} // namespace
+
+EntityId MethodFamilyUnionFind::find(EntityId E) {
+  auto It = Roots.find(E);
+  if (It == Roots.end()) {
+    Roots.try_emplace(E, E); // Self-rooted singleton.
+    return E;
+  }
+  if (It->second == E)
+    return E;
+  EntityId Root = find(It->second);
+  Roots.insert_or_assign(E, Root); // Path compression.
+  return Root;
+}
+
+void MethodFamilyUnionFind::unionSets(EntityId A, EntityId B) {
+  EntityId RootA = find(A);
+  EntityId RootB = find(B);
+  if (RootA == RootB)
+    return;
+
+  // Prefer the lexicographically-smaller rep for stable output across runs.
+  if (RootB < RootA)
+    std::swap(RootA, RootB);
+
+  Roots.insert_or_assign(RootB, RootA);
+}
+
+void VirtualMethodFamilyAnalysis::unionParamsAndReturnEntitiesInSummaries(
+    const VirtualMethodSummary &LHS, const VirtualMethodSummary &RHS) {
+  assert(LHS.ParamEntities.size() == RHS.ParamEntities.size());
+  assert(LHS.ReturnEntity.has_value() == RHS.ReturnEntity.has_value());
+
+  using llvm::zip_equal;
+  for (auto [LParam, RParam] : zip_equal(LHS.ParamEntities, RHS.ParamEntities))
+    Family.unionSets(LParam, RParam);
+
+  if (LHS.ReturnEntity.has_value())
+    Family.unionSets(*LHS.ReturnEntity, *RHS.ReturnEntity);
+}
+
+void VirtualMethodFamilyAnalysis::groupParamsAndReturnEntities() {
+  for (const VirtualMethodSummary *CurrSum : llvm::make_second_range(Data)) {
+    for (EntityId OverriddenMethodId : CurrSum->OverriddenMethods) {
+      auto BaseSumIt = Data.find(OverriddenMethodId);
+      assert(BaseSumIt != Data.end());
+      const VirtualMethodSummary &BaseSum = *BaseSumIt->second;
+      unionParamsAndReturnEntitiesInSummaries(*CurrSum, BaseSum);
+    }
+  }
+}
+
+llvm::Error VirtualMethodFamilyAnalysis::finalize() {
+  groupParamsAndReturnEntities();
+
+  auto &R = getResult();
+  for (EntityId E : Family.keys())
+    R.RetAndParamData.insert({E, Family.find(E)});
+  return llvm::Error::success();
+}
+
+static AnalysisRegistry::Add<VirtualMethodFamilyAnalysis>
+    RegisterAnalysis("Override-family equivalence classes for virtual 
methods");
+
+//===----------------------------------------------------------------------===//
+// Printing
+//===----------------------------------------------------------------------===//
+
+namespace clang::ssaf {
+
+llvm::raw_ostream &operator<<(llvm::raw_ostream &OS,
+                              const VirtualMethodFamilyAnalysisResult &R) {
+  OS << "VirtualMethodFamilyAnalysisResult with " << R.RetAndParamData.size()
+     << " entries ";
+  if (R.RetAndParamData.empty())
+    return OS << "{}";
+
+  // DenseMap iteration order depends on hashing, so sort for stable output.
+  using Entry = std::pair<EntityId, EntityId>;
+  llvm::SmallVector<Entry> Entries(R.RetAndParamData.begin(),
+                                   R.RetAndParamData.end());
+  llvm::sort(Entries,
+             [](const Entry &L, const Entry &R) { return L.first < R.first; });
+
+  OS << "{\n";
+  for (const auto &[Id, FamilyId] : Entries)
+    OS << "  " << Id << " -> " << FamilyId << "\n";
+  return OS << "}";
+}
+
+// NOLINTNEXTLINE(misc-use-internal-linkage)
+volatile int VirtualMethodFamilyAnalysisAnchorSource = 0;
+} // namespace clang::ssaf

diff  --git 
a/clang/unittests/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamilyAnalysisTest.cpp
 
b/clang/unittests/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamilyAnalysisTest.cpp
new file mode 100644
index 0000000000000..ba32cc011a201
--- /dev/null
+++ 
b/clang/unittests/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamilyAnalysisTest.cpp
@@ -0,0 +1,292 @@
+//===- VirtualMethodFamilyAnalysisTest.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/EntityLinker/LUSummary.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/WholeProgramAnalysis/AnalysisDriver.h"
+#include "clang/ScalableStaticAnalysis/Core/WholeProgramAnalysis/WPASuite.h"
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/Support/raw_ostream.h"
+#include "llvm/TargetParser/Triple.h"
+#include "llvm/Testing/Support/Error.h"
+#include "gtest/gtest.h"
+
+#include <memory>
+#include <optional>
+#include <ostream>
+#include <string>
+#include <utility>
+
+using namespace clang;
+using namespace ssaf;
+
+namespace clang::ssaf {
+// NOLINTNEXTLINE(misc-use-internal-linkage)
+void PrintTo(const VirtualMethodFamilyAnalysisResult &R, std::ostream *OS) {
+  std::string Str;
+  llvm::raw_string_ostream(Str) << R;
+  *OS << Str;
+}
+} // namespace clang::ssaf
+
+namespace {
+
+class VirtualMethodFamilyAnalysisTest : public VirtualMethodFamilyTestBase {
+protected:
+  // Parses \p Code, runs the VirtualMethod extractor over it, and drives the
+  // family analysis on the extracted summaries. Must be called exactly once
+  // per test, before any of the lookup helpers below.
+  void analyze(llvm::StringRef Code) {
+    ASSERT_TRUE(runVirtualMethodExtractor(Code))
+        << "failed to build the AST or instantiate the extractor";
+
+    // Hand the extracted summaries to the analysis. The EntityIdTable is
+    // copied rather than moved: the LUSummary is consumed by the driver, but
+    // the ids in the result still have to be resolvable by name afterwards.
+    constexpr auto LinkUnitKind = BuildNamespaceKind::LinkUnit;
+    NestedBuildNamespace NS{BuildNamespace(LinkUnitKind, "TestLU")};
+    llvm::Triple Target{"arm64-apple-macosx"};
+    auto LU = std::make_unique<LUSummary>(Target, std::move(NS));
+    getIdTable(*LU) = getIdTable(tuSummary());
+    getLinkageTable(*LU) = getLinkageTable(tuSummary());
+    getData(*LU) = std::move(getData(tuSummary()));
+
+    AnalysisDriver Driver(std::move(LU));
+    auto WPAOrErr = Driver.run<VirtualMethodFamilyAnalysisResult>();
+    ASSERT_THAT_EXPECTED(WPAOrErr, llvm::Succeeded());
+    WPA = std::move(*WPAOrErr);
+    auto ROrErr = WPA.get<VirtualMethodFamilyAnalysisResult>();
+    ASSERT_THAT_EXPECTED(ROrErr, llvm::Succeeded());
+    R = &*ROrErr;
+  }
+
+  EntityId param(llvm::StringRef NameOrSignature, unsigned Index = 0) {
+    return require(entityIdOf(AST.findParam(NameOrSignature, Index)),
+                   NameOrSignature);
+  }
+
+  EntityId ret(llvm::StringRef NameOrSignature) {
+    return require(returnEntityIdOf(AST.fn(NameOrSignature)), NameOrSignature);
+  }
+
+  const VirtualMethodFamilyAnalysisResult &result() const { return *R; }
+
+private:
+  // EntityId has no default constructor, so report the miss and fall back to
+  // an arbitrary id; the ADD_FAILURE() above makes the test fail regardless.
+  EntityId require(std::optional<EntityId> Id, llvm::StringRef QualifiedName) {
+    if (Id)
+      return *Id;
+    ADD_FAILURE() << "no entity extracted for '" << QualifiedName << "'";
+    const auto &Entities = getEntities(getIdTable(tuSummary()));
+    if (!Entities.empty())
+      return Entities.begin()->second;
+    return getIdTable(tuSummary()).getId(EntityName("<missing>", "", {}));
+  }
+
+  WPASuite WPA = makeWPASuite();
+  const VirtualMethodFamilyAnalysisResult *R = nullptr;
+};
+
+static VirtualMethodFamilyAnalysisResult
+createResult(llvm::ArrayRef<std::pair<EntityId, EntityId>> Entries) {
+  VirtualMethodFamilyAnalysisResult Res;
+  Res.RetAndParamData.insert(Entries.begin(), Entries.end());
+  return Res;
+}
+
+TEST_F(VirtualMethodFamilyAnalysisTest, ChainOneFamily) {
+  // Base <- Mid <- Der
+  analyze(R"cpp(
+    struct Base {
+      virtual void foo(int *p);
+    };
+    struct Mid : Base {
+      void foo(int *p) override;
+    };
+    struct Der : Mid {
+      void foo(int *p) override;
+    };
+  )cpp");
+
+  EntityId BaseFooP = param("Base::foo");
+  EntityId MidFooP = param("Mid::foo");
+  EntityId DerFooP = param("Der::foo");
+
+  EntityId BaseFooR = ret("Base::foo");
+  EntityId MidFooR = ret("Mid::foo");
+  EntityId DerFooR = ret("Der::foo");
+
+  EXPECT_EQ(result(), createResult({
+                          // Params
+                          {BaseFooP, /*FamilyId=*/BaseFooP},
+                          {MidFooP, /*FamilyId=*/BaseFooP},
+                          {DerFooP, /*FamilyId=*/BaseFooP},
+                          // Returns
+                          {BaseFooR, /*FamilyId=*/BaseFooR},
+                          {MidFooR, /*FamilyId=*/BaseFooR},
+                          {DerFooR, /*FamilyId=*/BaseFooR},
+                      }))
+      << legend();
+}
+
+// Unrelated multiple inheritance: D::f overrides both {A::f, B::f}.
+// The joining overrider bridges the two roots into a single family.
+
+TEST_F(VirtualMethodFamilyAnalysisTest, UnrelatedMultipleInheritanceMerges) {
+  // Base1 <--
+  //          |-- Der
+  // Base2 <--
+  analyze(R"cpp(
+    struct Base1 {
+      virtual void foo(int *p);
+    };
+    struct Base2 {
+      virtual void foo(int *p);
+    };
+    struct Der : Base1, Base2 {
+      void foo(int *p) override;
+    };
+  )cpp");
+
+  EntityId Base1FooP = param("Base1::foo");
+  EntityId Base2FooP = param("Base2::foo");
+  EntityId DerFooP = param("Der::foo");
+
+  EntityId Base1FooR = ret("Base1::foo");
+  EntityId Base2FooR = ret("Base2::foo");
+  EntityId DerFooR = ret("Der::foo");
+
+  EXPECT_EQ(result(), createResult({
+                          // Params
+                          {Base1FooP, /*FamilyId=*/Base1FooP},
+                          {Base2FooP, /*FamilyId=*/Base1FooP},
+                          {DerFooP, /*FamilyId=*/Base1FooP},
+                          // Returns
+                          {Base1FooR, /*FamilyId=*/Base1FooR},
+                          {Base2FooR, /*FamilyId=*/Base1FooR},
+                          {DerFooR, /*FamilyId=*/Base1FooR},
+                      }))
+      << legend();
+}
+
+// Overloads have 
diff erent vtable slots, thus they need to be treated separate.
+TEST_F(VirtualMethodFamilyAnalysisTest, OverloadsNotMerged) {
+  // Base <- Der
+  analyze(R"cpp(
+    struct Base {
+      virtual void foo(int *p);  // <-- later gets overridden
+      virtual void foo(char *p); // <-- unrelated overload
+    };
+    struct Der : Base {
+      void foo(int *p) override;
+    };
+  )cpp");
+
+  EntityId BaseFooIntP = param("Base::foo(int *)");
+  EntityId DerFooP = param("Der::foo");
+
+  EntityId BaseFooIntR = ret("Base::foo(int *)");
+  EntityId DerFooR = ret("Der::foo");
+
+  const auto Expected = createResult({
+      // Params
+      {BaseFooIntP, /*FamilyId=*/BaseFooIntP},
+      {DerFooP, /*FamilyId=*/BaseFooIntP},
+      // Returns
+      {BaseFooIntR, /*FamilyId=*/BaseFooIntR},
+      {DerFooR, /*FamilyId=*/BaseFooIntR},
+  });
+  // "Base::foo(char *)" is not mentioned because that is not overridden by
+  // anyone.
+  EXPECT_EQ(result(), Expected) << legend();
+}
+
+// Covariant returns have the same family.
+TEST_F(VirtualMethodFamilyAnalysisTest, CovariantReturnUnifiesReturnSlots) {
+  // Base <- Der
+  analyze(R"cpp(
+    struct Base {
+      virtual Base *clone();
+    };
+    struct Der : Base {
+      Der *clone() override; // <-- has covariant return type
+    };
+  )cpp");
+
+  EntityId BaseCloneR = ret("Base::clone");
+  EntityId DerCloneR = ret("Der::clone");
+
+  EXPECT_EQ(result(), createResult({
+                          {BaseCloneR, /*FamilyId=*/BaseCloneR},
+                          {DerCloneR, /*FamilyId=*/BaseCloneR},
+                      }))
+      << legend();
+}
+
+TEST_F(VirtualMethodFamilyAnalysisTest, DiamondOneFamily) {
+  //    Base      //
+  //   /    \     //
+  // Left  Right  //
+  //   \    /     //
+  //    Dia       //
+  analyze(R"cpp(
+    struct Base {
+      virtual void foo(int *p);
+    };
+    struct Left : Base {
+      void foo(int *p) override;
+    };
+    struct Right : Base {
+      void foo(int *p) override;
+    };
+    struct Dia : Left, Right {
+      void foo(int *p) override;
+    };
+  )cpp");
+
+  EntityId BaseFooP = param("Base::foo");
+  EntityId LeftFooP = param("Left::foo");
+  EntityId RightFooP = param("Right::foo");
+  EntityId DiaFooP = param("Dia::foo");
+
+  EntityId BaseFooR = ret("Base::foo");
+  EntityId LeftFooR = ret("Left::foo");
+  EntityId RightFooR = ret("Right::foo");
+  EntityId DiaFooR = ret("Dia::foo");
+
+  EXPECT_EQ(result(), createResult({
+                          // Params
+                          {BaseFooP, /*FamilyId=*/BaseFooP},
+                          {LeftFooP, /*FamilyId=*/BaseFooP},
+                          {RightFooP, /*FamilyId=*/BaseFooP},
+                          {DiaFooP, /*FamilyId=*/BaseFooP},
+                          // Returns
+                          {BaseFooR, /*FamilyId=*/BaseFooR},
+                          {LeftFooR, /*FamilyId=*/BaseFooR},
+                          {RightFooR, /*FamilyId=*/BaseFooR},
+                          {DiaFooR, /*FamilyId=*/BaseFooR},
+                      }))
+      << legend();
+}
+
+TEST_F(VirtualMethodFamilyAnalysisTest, NoFamilies) {
+  analyze(R"cpp(
+    struct A {
+      virtual void f(int *p);
+    };
+  )cpp");
+  // No methods are overridden => empty map.
+  EXPECT_EQ(result(), createResult({})) << legend();
+}
+
+} // namespace

diff  --git a/clang/unittests/ScalableStaticAnalysis/CMakeLists.txt 
b/clang/unittests/ScalableStaticAnalysis/CMakeLists.txt
index aaee5b76a6e73..6223528d29776 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/VirtualMethodFamilyAnalysisTest.cpp
   Analyses/VirtualMethodFamily/VirtualMethodFamilyExtractorTest.cpp
   ASTEntityMappingTest.cpp
   BuildNamespaceTest.cpp


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

Reply via email to