[llvm-branch-commits] [clang] [SSAF] Group virtual method slots into override families (PR #213317)
Balázs Benics via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Sat Sep 26 05:52:06 PDT 2026
https://github.com/steakhal updated https://github.com/llvm/llvm-project/pull/213317
>From 8905b8c87a6a98fd9d319cb9164dcd10a4953371 Mon Sep 17 00:00:00 2001
From: Balazs Benics <benicsbalazs at gmail.com>
Date: Fri, 31 Jul 2026 16:16:55 +0100
Subject: [PATCH 1/3] [SSAF] Group virtual method slots into override families
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
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
---
.../VirtualMethodFamily/VirtualMethodFamily.h | 46 +++
.../BuiltinAnchorSources.def | 1 +
.../Core/Model/EntityId.h | 11 +
.../Analyses/CMakeLists.txt | 1 +
.../VirtualMethodFamilyAnalysis.cpp | 201 +++++++++++
.../VirtualMethodFamilyAnalysisTest.cpp | 336 ++++++++++++++++++
.../ScalableStaticAnalysis/CMakeLists.txt | 1 +
7 files changed, 597 insertions(+)
create mode 100644 clang/lib/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamilyAnalysis.cpp
create mode 100644 clang/unittests/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamilyAnalysisTest.cpp
diff --git a/clang/include/clang/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamily.h b/clang/include/clang/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamily.h
index a39f2ffa1c9dc..b92ce61c2ea55 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,48 @@ struct VirtualMethodSummary final : public EntitySummary {
}
};
+struct VirtualMethodFamilyAnalysisResult final : AnalysisResult {
+ static AnalysisName analysisName() {
+ return AnalysisName("VirtualMethodFamilyAnalysisResult");
+ }
+
+ struct Data {
+ /// Represents the ID of the family the given parameter or return ID
+ /// corresponds to.
+ /// Right now, this ID is the "smallest" ID of the method in
+ /// the overloading set.
+ EntityId FamilyId;
+
+ /// The virtual method IDs of the param/return IDs it correspond to.
+ /// Basically, for "param" in "fun(param)" it will be "fun".
+ EntityId OwnerMethodId;
+ };
+ llvm::DenseMap<EntityId, Data> RetAndParamData;
+
+ friend bool operator==(const Data &L, const Data &R) {
+ return std::tie(L.FamilyId, L.OwnerMethodId) ==
+ std::tie(R.FamilyId, R.OwnerMethodId);
+ }
+ friend bool operator!=(const Data &L, const Data &R) { return !(L == R); }
+
+ bool operator==(const VirtualMethodFamilyAnalysisResult &Other) const {
+ return RetAndParamData == Other.RetAndParamData;
+ }
+
+ bool operator!=(const VirtualMethodFamilyAnalysisResult &Other) const {
+ return !(*this == Other);
+ }
+};
+
+/// Prints \p D as "{family=EntityId(1), owner=EntityId(2)}".
+llvm::raw_ostream &operator<<(llvm::raw_ostream &OS,
+ const VirtualMethodFamilyAnalysisResult::Data &D);
+
+/// Prints \p R as one "<param/return id> -> <data>" 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..bca12773ec552
--- /dev/null
+++ b/clang/lib/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamilyAnalysis.cpp
@@ -0,0 +1,201 @@
+//===- 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);
+
+ void seed(EntityId E, EntityId Owner) { Roots.try_emplace(E, E); }
+ void seed(EntityId Owner, const VirtualMethodSummary &S);
+
+ auto keys() const { return llvm::make_first_range(Roots); }
+
+private:
+ llvm::DenseMap<EntityId, EntityId> Roots;
+};
+
+// Keeps track of what method declared the given parameter or return value.
+struct Owners {
+ void recordOwner(EntityId Owner, const VirtualMethodSummary &S);
+ void recordOwner(EntityId E, EntityId Owner);
+
+ EntityId getOwnerOf(EntityId Id) const {
+ assert(Owners.count(Id));
+ return Owners.at(Id);
+ }
+
+private:
+ llvm::DenseMap<EntityId, EntityId> Owners;
+};
+
+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 Owners and \c Family maps.
+ void groupParamsAndReturnEntities();
+
+ /// Make the param and return IDs share a family.
+ void unionParamsAndReturnEntitiesInSummaries(const VirtualMethodSummary &LHS,
+ const VirtualMethodSummary &RHS);
+
+ Owners Owners;
+ 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 MethodFamilyUnionFind::seed(EntityId Owner,
+ const VirtualMethodSummary &S) {
+ for (EntityId P : S.ParamEntities)
+ seed(P, Owner);
+ if (S.ReturnEntity.has_value())
+ seed(S.ReturnEntity.value(), Owner);
+}
+
+void Owners::recordOwner(EntityId Owner, const VirtualMethodSummary &S) {
+ for (EntityId P : S.ParamEntities)
+ recordOwner(P, Owner);
+ if (S.ReturnEntity.has_value())
+ recordOwner(S.ReturnEntity.value(), Owner);
+}
+
+void Owners::recordOwner(EntityId E, EntityId Owner) {
+ auto [Slot, Inserted] = Owners.try_emplace(E, Owner);
+ if (!Inserted) {
+ assert(Slot->second == Owner &&
+ "Only one Owner can be associated with an Entity");
+ }
+}
+
+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 auto &[CurrId, CurrSum] : Data) {
+ Owners.recordOwner(CurrId, *CurrSum);
+
+ 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), Owners.getOwnerOf(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::Data &D) {
+ return OS << "{family=" << D.FamilyId << ", owner=" << D.OwnerMethodId << "}";
+}
+
+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, VirtualMethodFamilyAnalysisResult::Data>;
+ 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, D] : Entries)
+ OS << " " << Id << " -> " << D << "\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..f69784663df48
--- /dev/null
+++ b/clang/unittests/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamilyAnalysisTest.cpp
@@ -0,0 +1,336 @@
+//===- 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 method(llvm::StringRef NameOrSignature) {
+ return require(entityIdOf(AST.fn(NameOrSignature)), NameOrSignature);
+ }
+
+ 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:
+ VirtualMethodFamilyAnalysisResult::Data at(EntityId ParamId) const {
+ auto It = R->RetAndParamData.find(ParamId);
+ if (It != R->RetAndParamData.end())
+ return It->second;
+ ADD_FAILURE() << "no data recorded for " << ParamId;
+ // Let's just return some fallback value. The test fails anyway.
+ return {ParamId, ParamId};
+ }
+
+ // 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, std::pair<EntityId, EntityId>>>
+ Entries) {
+ VirtualMethodFamilyAnalysisResult Res;
+ Res.RetAndParamData.reserve(Entries.size());
+ for (const auto &[Id, Data] : Entries) {
+ auto [FamilyId, OwnerMethodId] = Data;
+ Res.RetAndParamData.insert({Id, {FamilyId, OwnerMethodId}});
+ }
+ 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 BaseFoo = method("Base::foo");
+ EntityId MidFoo = method("Mid::foo");
+ EntityId DerFoo = method("Der::foo");
+
+ 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, /*OwnerMethodId=*/BaseFoo}},
+ {MidFooP, {/*FamilyId=*/BaseFooP, /*OwnerMethodId=*/MidFoo}},
+ {DerFooP, {/*FamilyId=*/BaseFooP, /*OwnerMethodId=*/DerFoo}},
+ // Returns
+ {BaseFooR, {/*FamilyId=*/BaseFooR, /*OwnerMethodId=*/BaseFoo}},
+ {MidFooR, {/*FamilyId=*/BaseFooR, /*OwnerMethodId=*/MidFoo}},
+ {DerFooR, {/*FamilyId=*/BaseFooR, /*OwnerMethodId=*/DerFoo}},
+ }))
+ << 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 Base1Foo = method("Base1::foo");
+ EntityId Base2Foo = method("Base2::foo");
+ EntityId DerFoo = method("Der::foo");
+
+ 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, /*OwnerMethodId=*/Base1Foo}},
+ {Base2FooP, {/*FamilyId=*/Base1FooP, /*OwnerMethodId=*/Base2Foo}},
+ {DerFooP, {/*FamilyId=*/Base1FooP, /*OwnerMethodId=*/DerFoo}},
+ // Returns
+ {Base1FooR, {/*FamilyId=*/Base1FooR, /*OwnerMethodId=*/Base1Foo}},
+ {Base2FooR, {/*FamilyId=*/Base1FooR, /*OwnerMethodId=*/Base2Foo}},
+ {DerFooR, {/*FamilyId=*/Base1FooR, /*OwnerMethodId=*/DerFoo}},
+ }))
+ << legend();
+}
+
+// Overloads have different 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 BaseFooInt = method("Base::foo(int *)");
+ EntityId DerFoo = method("Der::foo");
+
+ 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, /*OwnerMethodId=*/BaseFooInt}},
+ {DerFooP, {/*FamilyId=*/BaseFooIntP, /*OwnerMethodId=*/DerFoo}},
+ // Returns
+ {BaseFooIntR, {/*FamilyId=*/BaseFooIntR, /*OwnerMethodId=*/BaseFooInt}},
+ {DerFooR, {/*FamilyId=*/BaseFooIntR, /*OwnerMethodId=*/DerFoo}},
+ });
+ // "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 BaseClone = method("Base::clone");
+ EntityId DerClone = method("Der::clone");
+
+ EntityId BaseCloneR = ret("Base::clone");
+ EntityId DerCloneR = ret("Der::clone");
+
+ EXPECT_EQ(
+ result(),
+ createResult({
+ {BaseCloneR, {/*FamilyId=*/BaseCloneR, /*OwnerMethodId=*/BaseClone}},
+ {DerCloneR, {/*FamilyId=*/BaseCloneR, /*OwnerMethodId=*/DerClone}},
+ }))
+ << 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 BaseFoo = method("Base::foo");
+ EntityId LeftFoo = method("Left::foo");
+ EntityId RightFoo = method("Right::foo");
+ EntityId DiaFoo = method("Dia::foo");
+
+ 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, /*OwnerMethodId=*/BaseFoo}},
+ {LeftFooP, {/*FamilyId=*/BaseFooP, /*OwnerMethodId=*/LeftFoo}},
+ {RightFooP, {/*FamilyId=*/BaseFooP, /*OwnerMethodId=*/RightFoo}},
+ {DiaFooP, {/*FamilyId=*/BaseFooP, /*OwnerMethodId=*/DiaFoo}},
+ // Returns
+ {BaseFooR, {/*FamilyId=*/BaseFooR, /*OwnerMethodId=*/BaseFoo}},
+ {LeftFooR, {/*FamilyId=*/BaseFooR, /*OwnerMethodId=*/LeftFoo}},
+ {RightFooR, {/*FamilyId=*/BaseFooR, /*OwnerMethodId=*/RightFoo}},
+ {DiaFooR, {/*FamilyId=*/BaseFooR, /*OwnerMethodId=*/DiaFoo}},
+ }))
+ << 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
>From 86c6d280f1965ec50a3770ee3b09045333f2a986 Mon Sep 17 00:00:00 2001
From: Balazs Benics <benicsbalazs at gmail.com>
Date: Sat, 26 Sep 2026 10:51:11 +0100
Subject: [PATCH 2/3] [SSAF] Drop the dead MethodFamilyUnionFind::seed
overloads
Nothing calls either overload; they are scaffolding left over from the
prototype this analysis was extracted from.
- `Roots` is populated entirely by the lazy self-rooting in `find()`,
reached via `unionSets()`.
- `seed(E, Owner)` ignored `Owner` altogether. The parameter only existed
because `seed(Owner, S)` was copy-pasted from
`Owners::recordOwner(Owner, S)`, which does need it.
- Where the prototype did call `seed`, it ran immediately above
`unionSets()` over the same entities, so it was a no-op there too.
Also document that a virtual method with no override relation contributes
no entries, so the omission reads as deliberate rather than as a gap.
Assisted-By: claude
---
.../VirtualMethodFamily/VirtualMethodFamily.h | 1 +
.../VirtualMethodFamilyAnalysis.cpp | 11 -----------
2 files changed, 1 insertion(+), 11 deletions(-)
diff --git a/clang/include/clang/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamily.h b/clang/include/clang/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamily.h
index b92ce61c2ea55..78a228ea7dfe3 100644
--- a/clang/include/clang/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamily.h
+++ b/clang/include/clang/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamily.h
@@ -66,6 +66,7 @@ struct VirtualMethodFamilyAnalysisResult final : AnalysisResult {
/// Basically, for "param" in "fun(param)" it will be "fun".
EntityId OwnerMethodId;
};
+
llvm::DenseMap<EntityId, Data> RetAndParamData;
friend bool operator==(const Data &L, const Data &R) {
diff --git a/clang/lib/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamilyAnalysis.cpp b/clang/lib/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamilyAnalysis.cpp
index bca12773ec552..c68ef2ae3cabb 100644
--- a/clang/lib/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamilyAnalysis.cpp
+++ b/clang/lib/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamilyAnalysis.cpp
@@ -28,9 +28,6 @@ struct MethodFamilyUnionFind {
EntityId find(EntityId E);
void unionSets(EntityId A, EntityId B);
- void seed(EntityId E, EntityId Owner) { Roots.try_emplace(E, E); }
- void seed(EntityId Owner, const VirtualMethodSummary &S);
-
auto keys() const { return llvm::make_first_range(Roots); }
private:
@@ -102,14 +99,6 @@ void MethodFamilyUnionFind::unionSets(EntityId A, EntityId B) {
Roots.insert_or_assign(RootB, RootA);
}
-void MethodFamilyUnionFind::seed(EntityId Owner,
- const VirtualMethodSummary &S) {
- for (EntityId P : S.ParamEntities)
- seed(P, Owner);
- if (S.ReturnEntity.has_value())
- seed(S.ReturnEntity.value(), Owner);
-}
-
void Owners::recordOwner(EntityId Owner, const VirtualMethodSummary &S) {
for (EntityId P : S.ParamEntities)
recordOwner(P, Owner);
>From 520f9600664c1f41762a03dbaedb3b44bcfb883b Mon Sep 17 00:00:00 2001
From: Balazs Benics <benicsbalazs at gmail.com>
Date: Sat, 26 Sep 2026 11:51:28 +0100
Subject: [PATCH 3/3] [SSAF] Drop the unused OwnerMethodId from method families
The only consumer, the unsafe-buffer reachability closure, needs just the family
a slot belongs to.
- Recording the owning method cost a map per analysis and a field per slot for
nothing.
- With only the family left, the per-slot `Data` collapses into a plain
slot-to-family map.
Assisted-By: claude
---
.../VirtualMethodFamily/VirtualMethodFamily.h | 30 +---
.../VirtualMethodFamilyAnalysis.cpp | 53 +-------
.../VirtualMethodFamilyAnalysisTest.cpp | 128 ++++++------------
3 files changed, 54 insertions(+), 157 deletions(-)
diff --git a/clang/include/clang/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamily.h b/clang/include/clang/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamily.h
index 78a228ea7dfe3..ce1978d82b6a7 100644
--- a/clang/include/clang/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamily.h
+++ b/clang/include/clang/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamily.h
@@ -55,25 +55,9 @@ struct VirtualMethodFamilyAnalysisResult final : AnalysisResult {
return AnalysisName("VirtualMethodFamilyAnalysisResult");
}
- struct Data {
- /// Represents the ID of the family the given parameter or return ID
- /// corresponds to.
- /// Right now, this ID is the "smallest" ID of the method in
- /// the overloading set.
- EntityId FamilyId;
-
- /// The virtual method IDs of the param/return IDs it correspond to.
- /// Basically, for "param" in "fun(param)" it will be "fun".
- EntityId OwnerMethodId;
- };
-
- llvm::DenseMap<EntityId, Data> RetAndParamData;
-
- friend bool operator==(const Data &L, const Data &R) {
- return std::tie(L.FamilyId, L.OwnerMethodId) ==
- std::tie(R.FamilyId, R.OwnerMethodId);
- }
- friend bool operator!=(const Data &L, const Data &R) { return !(L == R); }
+ /// 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;
@@ -84,12 +68,8 @@ struct VirtualMethodFamilyAnalysisResult final : AnalysisResult {
}
};
-/// Prints \p D as "{family=EntityId(1), owner=EntityId(2)}".
-llvm::raw_ostream &operator<<(llvm::raw_ostream &OS,
- const VirtualMethodFamilyAnalysisResult::Data &D);
-
-/// Prints \p R as one "<param/return id> -> <data>" line per entry, ordered by
-/// the param/return id so that the output is stable across runs.
+/// 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);
diff --git a/clang/lib/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamilyAnalysis.cpp b/clang/lib/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamilyAnalysis.cpp
index c68ef2ae3cabb..1d890da33a3bf 100644
--- a/clang/lib/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamilyAnalysis.cpp
+++ b/clang/lib/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamilyAnalysis.cpp
@@ -34,20 +34,6 @@ struct MethodFamilyUnionFind {
llvm::DenseMap<EntityId, EntityId> Roots;
};
-// Keeps track of what method declared the given parameter or return value.
-struct Owners {
- void recordOwner(EntityId Owner, const VirtualMethodSummary &S);
- void recordOwner(EntityId E, EntityId Owner);
-
- EntityId getOwnerOf(EntityId Id) const {
- assert(Owners.count(Id));
- return Owners.at(Id);
- }
-
-private:
- llvm::DenseMap<EntityId, EntityId> Owners;
-};
-
class VirtualMethodFamilyAnalysis final
: public SummaryAnalysis<VirtualMethodFamilyAnalysisResult,
VirtualMethodSummary> {
@@ -60,14 +46,13 @@ class VirtualMethodFamilyAnalysis final
llvm::Error finalize() override;
private:
- /// Fill the \c Owners and \c Family maps.
+ /// Fill the \c Family map.
void groupParamsAndReturnEntities();
/// Make the param and return IDs share a family.
void unionParamsAndReturnEntitiesInSummaries(const VirtualMethodSummary &LHS,
const VirtualMethodSummary &RHS);
- Owners Owners;
MethodFamilyUnionFind Family;
std::map<EntityId, const VirtualMethodSummary *> Data;
};
@@ -99,21 +84,6 @@ void MethodFamilyUnionFind::unionSets(EntityId A, EntityId B) {
Roots.insert_or_assign(RootB, RootA);
}
-void Owners::recordOwner(EntityId Owner, const VirtualMethodSummary &S) {
- for (EntityId P : S.ParamEntities)
- recordOwner(P, Owner);
- if (S.ReturnEntity.has_value())
- recordOwner(S.ReturnEntity.value(), Owner);
-}
-
-void Owners::recordOwner(EntityId E, EntityId Owner) {
- auto [Slot, Inserted] = Owners.try_emplace(E, Owner);
- if (!Inserted) {
- assert(Slot->second == Owner &&
- "Only one Owner can be associated with an Entity");
- }
-}
-
void VirtualMethodFamilyAnalysis::unionParamsAndReturnEntitiesInSummaries(
const VirtualMethodSummary &LHS, const VirtualMethodSummary &RHS) {
assert(LHS.ParamEntities.size() == RHS.ParamEntities.size());
@@ -128,9 +98,7 @@ void VirtualMethodFamilyAnalysis::unionParamsAndReturnEntitiesInSummaries(
}
void VirtualMethodFamilyAnalysis::groupParamsAndReturnEntities() {
- for (const auto &[CurrId, CurrSum] : Data) {
- Owners.recordOwner(CurrId, *CurrSum);
-
+ for (const VirtualMethodSummary *CurrSum : llvm::make_second_range(Data)) {
for (EntityId OverriddenMethodId : CurrSum->OverriddenMethods) {
auto BaseSumIt = Data.find(OverriddenMethodId);
assert(BaseSumIt != Data.end());
@@ -144,9 +112,8 @@ llvm::Error VirtualMethodFamilyAnalysis::finalize() {
groupParamsAndReturnEntities();
auto &R = getResult();
- for (EntityId E : Family.keys()) {
- R.RetAndParamData.insert({E, {Family.find(E), Owners.getOwnerOf(E)}});
- }
+ for (EntityId E : Family.keys())
+ R.RetAndParamData.insert({E, Family.find(E)});
return llvm::Error::success();
}
@@ -159,12 +126,6 @@ static AnalysisRegistry::Add<VirtualMethodFamilyAnalysis>
namespace clang::ssaf {
-llvm::raw_ostream &
-operator<<(llvm::raw_ostream &OS,
- const VirtualMethodFamilyAnalysisResult::Data &D) {
- return OS << "{family=" << D.FamilyId << ", owner=" << D.OwnerMethodId << "}";
-}
-
llvm::raw_ostream &operator<<(llvm::raw_ostream &OS,
const VirtualMethodFamilyAnalysisResult &R) {
OS << "VirtualMethodFamilyAnalysisResult with " << R.RetAndParamData.size()
@@ -173,15 +134,15 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &OS,
return OS << "{}";
// DenseMap iteration order depends on hashing, so sort for stable output.
- using Entry = std::pair<EntityId, VirtualMethodFamilyAnalysisResult::Data>;
+ 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, D] : Entries)
- OS << " " << Id << " -> " << D << "\n";
+ for (const auto &[Id, FamilyId] : Entries)
+ OS << " " << Id << " -> " << FamilyId << "\n";
return OS << "}";
}
diff --git a/clang/unittests/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamilyAnalysisTest.cpp b/clang/unittests/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamilyAnalysisTest.cpp
index f69784663df48..ba32cc011a201 100644
--- a/clang/unittests/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamilyAnalysisTest.cpp
+++ b/clang/unittests/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamilyAnalysisTest.cpp
@@ -69,10 +69,6 @@ class VirtualMethodFamilyAnalysisTest : public VirtualMethodFamilyTestBase {
R = &*ROrErr;
}
- EntityId method(llvm::StringRef NameOrSignature) {
- return require(entityIdOf(AST.fn(NameOrSignature)), NameOrSignature);
- }
-
EntityId param(llvm::StringRef NameOrSignature, unsigned Index = 0) {
return require(entityIdOf(AST.findParam(NameOrSignature, Index)),
NameOrSignature);
@@ -85,15 +81,6 @@ class VirtualMethodFamilyAnalysisTest : public VirtualMethodFamilyTestBase {
const VirtualMethodFamilyAnalysisResult &result() const { return *R; }
private:
- VirtualMethodFamilyAnalysisResult::Data at(EntityId ParamId) const {
- auto It = R->RetAndParamData.find(ParamId);
- if (It != R->RetAndParamData.end())
- return It->second;
- ADD_FAILURE() << "no data recorded for " << ParamId;
- // Let's just return some fallback value. The test fails anyway.
- return {ParamId, ParamId};
- }
-
// 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) {
@@ -111,14 +98,9 @@ class VirtualMethodFamilyAnalysisTest : public VirtualMethodFamilyTestBase {
};
static VirtualMethodFamilyAnalysisResult
-createResult(llvm::ArrayRef<std::pair<EntityId, std::pair<EntityId, EntityId>>>
- Entries) {
+createResult(llvm::ArrayRef<std::pair<EntityId, EntityId>> Entries) {
VirtualMethodFamilyAnalysisResult Res;
- Res.RetAndParamData.reserve(Entries.size());
- for (const auto &[Id, Data] : Entries) {
- auto [FamilyId, OwnerMethodId] = Data;
- Res.RetAndParamData.insert({Id, {FamilyId, OwnerMethodId}});
- }
+ Res.RetAndParamData.insert(Entries.begin(), Entries.end());
return Res;
}
@@ -136,10 +118,6 @@ TEST_F(VirtualMethodFamilyAnalysisTest, ChainOneFamily) {
};
)cpp");
- EntityId BaseFoo = method("Base::foo");
- EntityId MidFoo = method("Mid::foo");
- EntityId DerFoo = method("Der::foo");
-
EntityId BaseFooP = param("Base::foo");
EntityId MidFooP = param("Mid::foo");
EntityId DerFooP = param("Der::foo");
@@ -148,17 +126,16 @@ TEST_F(VirtualMethodFamilyAnalysisTest, ChainOneFamily) {
EntityId MidFooR = ret("Mid::foo");
EntityId DerFooR = ret("Der::foo");
- EXPECT_EQ(result(),
- createResult({
- // Params
- {BaseFooP, {/*FamilyId=*/BaseFooP, /*OwnerMethodId=*/BaseFoo}},
- {MidFooP, {/*FamilyId=*/BaseFooP, /*OwnerMethodId=*/MidFoo}},
- {DerFooP, {/*FamilyId=*/BaseFooP, /*OwnerMethodId=*/DerFoo}},
- // Returns
- {BaseFooR, {/*FamilyId=*/BaseFooR, /*OwnerMethodId=*/BaseFoo}},
- {MidFooR, {/*FamilyId=*/BaseFooR, /*OwnerMethodId=*/MidFoo}},
- {DerFooR, {/*FamilyId=*/BaseFooR, /*OwnerMethodId=*/DerFoo}},
- }))
+ 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();
}
@@ -181,10 +158,6 @@ TEST_F(VirtualMethodFamilyAnalysisTest, UnrelatedMultipleInheritanceMerges) {
};
)cpp");
- EntityId Base1Foo = method("Base1::foo");
- EntityId Base2Foo = method("Base2::foo");
- EntityId DerFoo = method("Der::foo");
-
EntityId Base1FooP = param("Base1::foo");
EntityId Base2FooP = param("Base2::foo");
EntityId DerFooP = param("Der::foo");
@@ -193,18 +166,16 @@ TEST_F(VirtualMethodFamilyAnalysisTest, UnrelatedMultipleInheritanceMerges) {
EntityId Base2FooR = ret("Base2::foo");
EntityId DerFooR = ret("Der::foo");
- EXPECT_EQ(
- result(),
- createResult({
- // Params
- {Base1FooP, {/*FamilyId=*/Base1FooP, /*OwnerMethodId=*/Base1Foo}},
- {Base2FooP, {/*FamilyId=*/Base1FooP, /*OwnerMethodId=*/Base2Foo}},
- {DerFooP, {/*FamilyId=*/Base1FooP, /*OwnerMethodId=*/DerFoo}},
- // Returns
- {Base1FooR, {/*FamilyId=*/Base1FooR, /*OwnerMethodId=*/Base1Foo}},
- {Base2FooR, {/*FamilyId=*/Base1FooR, /*OwnerMethodId=*/Base2Foo}},
- {DerFooR, {/*FamilyId=*/Base1FooR, /*OwnerMethodId=*/DerFoo}},
- }))
+ 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();
}
@@ -221,9 +192,6 @@ TEST_F(VirtualMethodFamilyAnalysisTest, OverloadsNotMerged) {
};
)cpp");
- EntityId BaseFooInt = method("Base::foo(int *)");
- EntityId DerFoo = method("Der::foo");
-
EntityId BaseFooIntP = param("Base::foo(int *)");
EntityId DerFooP = param("Der::foo");
@@ -232,11 +200,11 @@ TEST_F(VirtualMethodFamilyAnalysisTest, OverloadsNotMerged) {
const auto Expected = createResult({
// Params
- {BaseFooIntP, {/*FamilyId=*/BaseFooIntP, /*OwnerMethodId=*/BaseFooInt}},
- {DerFooP, {/*FamilyId=*/BaseFooIntP, /*OwnerMethodId=*/DerFoo}},
+ {BaseFooIntP, /*FamilyId=*/BaseFooIntP},
+ {DerFooP, /*FamilyId=*/BaseFooIntP},
// Returns
- {BaseFooIntR, {/*FamilyId=*/BaseFooIntR, /*OwnerMethodId=*/BaseFooInt}},
- {DerFooR, {/*FamilyId=*/BaseFooIntR, /*OwnerMethodId=*/DerFoo}},
+ {BaseFooIntR, /*FamilyId=*/BaseFooIntR},
+ {DerFooR, /*FamilyId=*/BaseFooIntR},
});
// "Base::foo(char *)" is not mentioned because that is not overridden by
// anyone.
@@ -255,18 +223,13 @@ TEST_F(VirtualMethodFamilyAnalysisTest, CovariantReturnUnifiesReturnSlots) {
};
)cpp");
- EntityId BaseClone = method("Base::clone");
- EntityId DerClone = method("Der::clone");
-
EntityId BaseCloneR = ret("Base::clone");
EntityId DerCloneR = ret("Der::clone");
- EXPECT_EQ(
- result(),
- createResult({
- {BaseCloneR, {/*FamilyId=*/BaseCloneR, /*OwnerMethodId=*/BaseClone}},
- {DerCloneR, {/*FamilyId=*/BaseCloneR, /*OwnerMethodId=*/DerClone}},
- }))
+ EXPECT_EQ(result(), createResult({
+ {BaseCloneR, /*FamilyId=*/BaseCloneR},
+ {DerCloneR, /*FamilyId=*/BaseCloneR},
+ }))
<< legend();
}
@@ -291,11 +254,6 @@ TEST_F(VirtualMethodFamilyAnalysisTest, DiamondOneFamily) {
};
)cpp");
- EntityId BaseFoo = method("Base::foo");
- EntityId LeftFoo = method("Left::foo");
- EntityId RightFoo = method("Right::foo");
- EntityId DiaFoo = method("Dia::foo");
-
EntityId BaseFooP = param("Base::foo");
EntityId LeftFooP = param("Left::foo");
EntityId RightFooP = param("Right::foo");
@@ -306,20 +264,18 @@ TEST_F(VirtualMethodFamilyAnalysisTest, DiamondOneFamily) {
EntityId RightFooR = ret("Right::foo");
EntityId DiaFooR = ret("Dia::foo");
- EXPECT_EQ(
- result(),
- createResult({
- // Params
- {BaseFooP, {/*FamilyId=*/BaseFooP, /*OwnerMethodId=*/BaseFoo}},
- {LeftFooP, {/*FamilyId=*/BaseFooP, /*OwnerMethodId=*/LeftFoo}},
- {RightFooP, {/*FamilyId=*/BaseFooP, /*OwnerMethodId=*/RightFoo}},
- {DiaFooP, {/*FamilyId=*/BaseFooP, /*OwnerMethodId=*/DiaFoo}},
- // Returns
- {BaseFooR, {/*FamilyId=*/BaseFooR, /*OwnerMethodId=*/BaseFoo}},
- {LeftFooR, {/*FamilyId=*/BaseFooR, /*OwnerMethodId=*/LeftFoo}},
- {RightFooR, {/*FamilyId=*/BaseFooR, /*OwnerMethodId=*/RightFoo}},
- {DiaFooR, {/*FamilyId=*/BaseFooR, /*OwnerMethodId=*/DiaFoo}},
- }))
+ 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();
}
More information about the llvm-branch-commits
mailing list