[llvm-branch-commits] [clang] [SSAF] Serialize virtual method summaries and families (PR #213318)

Balázs Benics via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Fri Aug 7 04:59:09 PDT 2026


https://github.com/steakhal updated https://github.com/llvm/llvm-project/pull/213318

>From 8ad180b8d01d007a5650c6648d40b0c8a882f133 Mon Sep 17 00:00:00 2001
From: Balazs Benics <benicsbalazs at gmail.com>
Date: Fri, 31 Jul 2026 16:19:02 +0100
Subject: [PATCH] [SSAF] Serialize virtual method summaries and families
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Per-TU summaries and whole-program results cross process boundaries, and the
JSON layer refuses to write a summary kind it has no format for. Register both
sides so --ssaf-extract-summaries=VirtualMethod becomes usable and the family
result survives a round trip.

Deserialization tolerates a missing override list, since a root virtual method
legitimately has none.

§3 of rdar://179151603
---
 .../VirtualMethodFamily/VirtualMethodFamily.h |   5 +
 .../BuiltinAnchorSources.def                  |   1 +
 .../Analyses/CMakeLists.txt                   |   1 +
 .../VirtualMethodFamilyAnalysis.cpp           |  22 +
 .../VirtualMethodFamilyFormat.cpp             | 192 +++++++++
 .../ScalableStaticAnalysis/CMakeLists.txt     |   1 +
 .../VirtualMethodFamilyFormatTest.cpp         | 390 ++++++++++++++++++
 7 files changed, 612 insertions(+)
 create mode 100644 clang/lib/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamilyFormat.cpp
 create mode 100644 clang/unittests/ScalableStaticAnalysis/Serialization/JSONFormatTest/VirtualMethodFamilyFormatTest.cpp

diff --git a/clang/include/clang/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamily.h b/clang/include/clang/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamily.h
index b92ce61c2ea55..36cb4bff4bf58 100644
--- a/clang/include/clang/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamily.h
+++ b/clang/include/clang/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamily.h
@@ -50,6 +50,11 @@ struct VirtualMethodSummary final : public EntitySummary {
   }
 };
 
+/// Prints \p S as
+/// "VirtualMethodSummary { params=[...], return=..., overridden=[...] }".
+llvm::raw_ostream &operator<<(llvm::raw_ostream &OS,
+                              const VirtualMethodSummary &S);
+
 struct VirtualMethodFamilyAnalysisResult final : AnalysisResult {
   static AnalysisName analysisName() {
     return AnalysisName("VirtualMethodFamilyAnalysisResult");
diff --git a/clang/include/clang/ScalableStaticAnalysis/BuiltinAnchorSources.def b/clang/include/clang/ScalableStaticAnalysis/BuiltinAnchorSources.def
index 9fc9d7b3513ae..fca0f9c902ab9 100644
--- a/clang/include/clang/ScalableStaticAnalysis/BuiltinAnchorSources.def
+++ b/clang/include/clang/ScalableStaticAnalysis/BuiltinAnchorSources.def
@@ -32,5 +32,6 @@ ANCHOR(UnsafeBufferUsageExtractorAnchorSource)
 ANCHOR(UnsafeBufferUsageJSONFormatAnchorSource)
 ANCHOR(VirtualMethodEntityExtractorAnchorSource)
 ANCHOR(VirtualMethodFamilyAnalysisAnchorSource)
+ANCHOR(VirtualMethodFamilyJSONFormatAnchorSource)
 
 #undef ANCHOR
diff --git a/clang/lib/ScalableStaticAnalysis/Analyses/CMakeLists.txt b/clang/lib/ScalableStaticAnalysis/Analyses/CMakeLists.txt
index e83a84739d6a9..82ea646249fc0 100644
--- a/clang/lib/ScalableStaticAnalysis/Analyses/CMakeLists.txt
+++ b/clang/lib/ScalableStaticAnalysis/Analyses/CMakeLists.txt
@@ -22,6 +22,7 @@ add_clang_library(clangScalableStaticAnalysisAnalyses
   UnsafeBufferUsage/UnsafeBufferUsageFormat.cpp
   VirtualMethodFamily/VirtualMethodEntityExtractor.cpp
   VirtualMethodFamily/VirtualMethodFamilyAnalysis.cpp
+  VirtualMethodFamily/VirtualMethodFamilyFormat.cpp
 
   LINK_LIBS
   clangAST
diff --git a/clang/lib/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamilyAnalysis.cpp b/clang/lib/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamilyAnalysis.cpp
index bca12773ec552..18339ca2d3d47 100644
--- a/clang/lib/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamilyAnalysis.cpp
+++ b/clang/lib/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamilyAnalysis.cpp
@@ -10,6 +10,7 @@
 #include "clang/ScalableStaticAnalysis/Core/Model/EntityId.h"
 #include "clang/ScalableStaticAnalysis/Core/WholeProgramAnalysis/AnalysisRegistry.h"
 #include "clang/ScalableStaticAnalysis/Core/WholeProgramAnalysis/SummaryAnalysis.h"
+#include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/DenseMap.h"
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/SmallVector.h"
@@ -168,8 +169,29 @@ static AnalysisRegistry::Add<VirtualMethodFamilyAnalysis>
 // Printing
 //===----------------------------------------------------------------------===//
 
+static void printEntityIds(llvm::raw_ostream &OS,
+                           llvm::ArrayRef<EntityId> Ids) {
+  OS << "[";
+  llvm::interleaveComma(Ids, OS, [&](EntityId Id) { OS << Id; });
+  OS << "]";
+}
+
 namespace clang::ssaf {
 
+llvm::raw_ostream &operator<<(llvm::raw_ostream &OS,
+                              const VirtualMethodSummary &S) {
+  OS << "VirtualMethodSummary { params=";
+  printEntityIds(OS, S.ParamEntities);
+  OS << ", return=";
+  if (S.ReturnEntity)
+    OS << *S.ReturnEntity;
+  else
+    OS << "<none>";
+  OS << ", overridden=";
+  printEntityIds(OS, S.OverriddenMethods);
+  return OS << " }";
+}
+
 llvm::raw_ostream &
 operator<<(llvm::raw_ostream &OS,
            const VirtualMethodFamilyAnalysisResult::Data &D) {
diff --git a/clang/lib/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamilyFormat.cpp b/clang/lib/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamilyFormat.cpp
new file mode 100644
index 0000000000000..0b9f3bd66af83
--- /dev/null
+++ b/clang/lib/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamilyFormat.cpp
@@ -0,0 +1,192 @@
+//===- VirtualMethodFamilyFormat.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 "SSAFAnalysesCommon.h"
+#include "clang/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamily.h"
+#include "clang/ScalableStaticAnalysis/Core/Serialization/JSONFormat.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/JSON.h"
+#include "llvm/Support/Registry.h"
+#include <memory>
+#include <utility>
+#include <vector>
+
+using namespace clang;
+using namespace ssaf;
+
+using llvm::Expected;
+using Object = llvm::json::Object;
+using Array = llvm::json::Array;
+
+namespace {
+constexpr llvm::StringLiteral KeyParamEntities = "param_entities";
+constexpr llvm::StringLiteral KeyReturnEntity = "return_entity";
+constexpr llvm::StringLiteral KeyOverriddenMethods = "overridden_methods";
+
+constexpr llvm::StringLiteral KeyRetAndParamFamilyIds = "families";
+constexpr llvm::StringLiteral KeyParamId = "pid";
+constexpr llvm::StringLiteral KeyFamilyId = "fid";
+constexpr llvm::StringLiteral KeyOwnerMethodId = "oid";
+} // namespace
+
+static Array entityIdVectorToJSON(const std::vector<EntityId> &Ids,
+                                  JSONFormat::EntityIdToJSONFn IdToJSON) {
+  Array Result;
+  Result.reserve(Ids.size());
+  for (EntityId Id : Ids)
+    Result.push_back(IdToJSON(Id));
+  return Result;
+}
+
+static Expected<std::vector<EntityId>>
+entityIdVectorFromJSON(const Array &Arr,
+                       JSONFormat::EntityIdFromJSONFn IdFromJSON) {
+  std::vector<EntityId> Result;
+  Result.reserve(Arr.size());
+  for (const auto &V : Arr) {
+    const Object *Obj = V.getAsObject();
+    if (!Obj)
+      return makeSawButExpectedError(V, "an object representing EntityId");
+    auto Id = IdFromJSON(*Obj);
+    if (!Id)
+      return Id.takeError();
+    Result.push_back(*Id);
+  }
+  return Result;
+}
+
+//===----------------------------------------------------------------------===//
+// VirtualMethodSummary <-> JSON
+//===----------------------------------------------------------------------===//
+
+static Object
+serializeVirtualMethodSummary(const EntitySummary &ES,
+                              JSONFormat::EntityIdToJSONFn IdToJSON) {
+  const auto &S = static_cast<const VirtualMethodSummary &>(ES);
+  Object Out;
+  Out[KeyParamEntities] = entityIdVectorToJSON(S.ParamEntities, IdToJSON);
+  if (S.ReturnEntity.has_value())
+    Out[KeyReturnEntity] = IdToJSON(*S.ReturnEntity);
+  Out[KeyOverriddenMethods] =
+      entityIdVectorToJSON(S.OverriddenMethods, IdToJSON);
+  return Out;
+}
+
+static Expected<std::unique_ptr<EntitySummary>>
+deserializeVirtualMethodSummary(const Object &Obj, EntityIdTable &,
+                                JSONFormat::EntityIdFromJSONFn IdFromJSON) {
+  auto Result = std::make_unique<VirtualMethodSummary>();
+
+  const Array *ParamArr = Obj.getArray(KeyParamEntities);
+  if (!ParamArr)
+    return makeSawButExpectedError(Obj, "an object with an array field '%s'",
+                                   KeyParamEntities.data());
+  auto Params = entityIdVectorFromJSON(*ParamArr, IdFromJSON);
+  if (!Params)
+    return Params.takeError();
+  Result->ParamEntities = std::move(*Params);
+
+  if (const Object *RE = Obj.getObject(KeyReturnEntity)) {
+    auto Id = IdFromJSON(*RE);
+    if (!Id)
+      return Id.takeError();
+    Result->ReturnEntity = *Id;
+  }
+
+  // Tolerant: absent key means no override edges (a root virtual method).
+  if (const Array *OMArr = Obj.getArray(KeyOverriddenMethods)) {
+    auto OM = entityIdVectorFromJSON(*OMArr, IdFromJSON);
+    if (!OM)
+      return OM.takeError();
+    Result->OverriddenMethods = std::move(*OM);
+  }
+
+  return std::move(Result);
+}
+
+//===----------------------------------------------------------------------===//
+// VirtualMethodFamilyAnalysisResult <-> JSON
+//===----------------------------------------------------------------------===//
+
+static Object serializeVirtualMethodFamilyAnalysisResult(
+    const VirtualMethodFamilyAnalysisResult &R,
+    JSONFormat::EntityIdToJSONFn IdToJSON) {
+  Array FamilyDataArr;
+  for (const auto &[ParamId, Data] : R.RetAndParamData) {
+    Object Item;
+    Item[KeyParamId] = IdToJSON(ParamId);
+    Item[KeyFamilyId] = IdToJSON(Data.FamilyId);
+    Item[KeyOwnerMethodId] = IdToJSON(Data.OwnerMethodId);
+    FamilyDataArr.push_back(std::move(Item));
+  }
+
+  Object Out;
+  Out[KeyRetAndParamFamilyIds] = std::move(FamilyDataArr);
+  return Out;
+}
+
+static Expected<std::unique_ptr<AnalysisResult>>
+deserializeVirtualMethodFamilyAnalysisResult(
+    const Object &Obj, JSONFormat::EntityIdFromJSONFn IdFromJSON) {
+  const Array *FamilyDataArr = Obj.getArray(KeyRetAndParamFamilyIds);
+  if (!FamilyDataArr)
+    return makeSawButExpectedError(Obj, "an object with an array field '%s'",
+                                   KeyRetAndParamFamilyIds.data());
+
+  auto Result = std::make_unique<VirtualMethodFamilyAnalysisResult>();
+
+  for (const auto &V : *FamilyDataArr) {
+    const Object *Item = V.getAsObject();
+    if (!Item)
+      return makeSawButExpectedError(V, "an object {pid, fid, oid}");
+    const Object *ParamObj = Item->getObject(KeyParamId);
+    const Object *FamilyObj = Item->getObject(KeyFamilyId);
+    const Object *OwnerMethodObj = Item->getObject(KeyOwnerMethodId);
+    if (!ParamObj || !FamilyObj || !OwnerMethodObj)
+      return makeSawButExpectedError(
+          *Item, "an object with fields {'%s', '%s', '%s'}", KeyParamId.data(),
+          KeyFamilyId.data(), KeyOwnerMethodId.data());
+    auto ParamId = IdFromJSON(*ParamObj);
+    if (!ParamId)
+      return ParamId.takeError();
+    auto FamilyId = IdFromJSON(*FamilyObj);
+    if (!FamilyId)
+      return FamilyId.takeError();
+    auto OwnerMethodId = IdFromJSON(*OwnerMethodObj);
+    if (!OwnerMethodId)
+      return OwnerMethodId.takeError();
+    Result->RetAndParamData.insert({*ParamId, {*FamilyId, *OwnerMethodId}});
+  }
+  return std::move(Result);
+}
+
+namespace {
+
+struct VirtualMethodSummaryJSONFormatInfo final : JSONFormat::FormatInfo {
+  VirtualMethodSummaryJSONFormatInfo()
+      : JSONFormat::FormatInfo(VirtualMethodSummary::summaryName(),
+                               serializeVirtualMethodSummary,
+                               deserializeVirtualMethodSummary) {}
+};
+} // namespace
+
+static llvm::Registry<JSONFormat::FormatInfo>::Add<
+    VirtualMethodSummaryJSONFormatInfo>
+    RegisterJSONFormat(VirtualMethodSummary::Name,
+                       "JSON Format info for VirtualMethodSummary");
+
+static JSONFormat::AnalysisResultRegistry::Add<
+    VirtualMethodFamilyAnalysisResult>
+    RegisterResultJSONFormat(serializeVirtualMethodFamilyAnalysisResult,
+                             deserializeVirtualMethodFamilyAnalysisResult);
+
+namespace clang::ssaf {
+// NOLINTNEXTLINE(misc-use-internal-linkage)
+volatile int VirtualMethodFamilyJSONFormatAnchorSource = 0;
+} // namespace clang::ssaf
diff --git a/clang/unittests/ScalableStaticAnalysis/CMakeLists.txt b/clang/unittests/ScalableStaticAnalysis/CMakeLists.txt
index bdc1cf63ac025..49ea60cfc503f 100644
--- a/clang/unittests/ScalableStaticAnalysis/CMakeLists.txt
+++ b/clang/unittests/ScalableStaticAnalysis/CMakeLists.txt
@@ -29,6 +29,7 @@ add_distinct_clang_unittest(ClangScalableAnalysisTests
   Serialization/JSONFormatTest/LUSummaryTest.cpp
   Serialization/JSONFormatTest/SharedLexicalRepresentationFormatTest.cpp
   Serialization/JSONFormatTest/TUSummaryTest.cpp
+  Serialization/JSONFormatTest/VirtualMethodFamilyFormatTest.cpp
   SourceTransformation/EmitterTest.cpp
   SourceTransformation/RegistryTest.cpp
   SourceTransformation/SARIFFormatTest.cpp
diff --git a/clang/unittests/ScalableStaticAnalysis/Serialization/JSONFormatTest/VirtualMethodFamilyFormatTest.cpp b/clang/unittests/ScalableStaticAnalysis/Serialization/JSONFormatTest/VirtualMethodFamilyFormatTest.cpp
new file mode 100644
index 0000000000000..127bdd310a342
--- /dev/null
+++ b/clang/unittests/ScalableStaticAnalysis/Serialization/JSONFormatTest/VirtualMethodFamilyFormatTest.cpp
@@ -0,0 +1,390 @@
+//===- VirtualMethodFamilyFormatTest.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 "JSONFormatTest.h"
+#include "ParsedAST.h"
+#include "clang/AST/Decl.h"
+#include "clang/ScalableStaticAnalysis/Analyses/VirtualMethodFamily/VirtualMethodFamily.h"
+#include "clang/ScalableStaticAnalysis/Core/ASTEntityMapping.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/EntityIdTable.h"
+#include "clang/ScalableStaticAnalysis/Core/Model/EntityLinkage.h"
+#include "clang/ScalableStaticAnalysis/Core/Model/EntityName.h"
+#include "clang/ScalableStaticAnalysis/Core/Serialization/JSONFormat.h"
+#include "clang/ScalableStaticAnalysis/Core/WholeProgramAnalysis/WPASuite.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>
+#include <vector>
+
+using namespace clang;
+using namespace ssaf;
+
+namespace clang::ssaf {
+// NOLINTNEXTLINE(misc-use-internal-linkage)
+void PrintTo(const VirtualMethodSummary &S, std::ostream *OS) {
+  std::string Str;
+  llvm::raw_string_ostream(Str) << S;
+  *OS << Str;
+}
+} // namespace clang::ssaf
+
+namespace {
+
+class VirtualMethodFamilyFormatTest : public JSONFormatTest {
+protected:
+  static constexpr EntityLinkage ExternalLinkage{EntityLinkageType::External};
+
+  ParsedAST AST;
+
+  std::unique_ptr<LUSummary> makeLUSummary() {
+    return std::make_unique<LUSummary>(llvm::Triple("arm64-apple-macosx"),
+                                       linkUnitNamespace());
+  }
+
+  /// Create an LU-scope EntityId for \p ND and mark it externally visible.
+  EntityId addEntity(LUSummary &LU, const NamedDecl *ND) {
+    return declare(LU, addEntity(getIdTable(LU), ND));
+  }
+
+  /// Create an LU-scope EntityId for the return slot of \p FD.
+  EntityId addReturnEntity(LUSummary &LU, const FunctionDecl *FD) {
+    return declare(LU, addReturnEntity(getIdTable(LU), FD));
+  }
+
+  /// Create an EntityId for \p ND directly in \p Ids. Used for the WPASuite
+  /// tests, which carry a bare EntityIdTable rather than an LUSummary.
+  EntityId addEntity(EntityIdTable &Ids, const NamedDecl *ND) {
+    return getOrCreateId(Ids, ND ? getEntityName(ND) : std::nullopt, ND);
+  }
+
+  /// Create an EntityId for the return slot of \p FD directly in \p Ids.
+  EntityId addReturnEntity(EntityIdTable &Ids, const FunctionDecl *FD) {
+    return getOrCreateId(Ids, FD ? getEntityNameForReturn(FD) : std::nullopt,
+                         FD);
+  }
+
+  /// Insert a single entity summary (by SummaryName, EntityId).
+  template <typename SummaryT>
+  void insertSummary(LUSummary &LU, EntityId Id, SummaryT Sum) {
+    getData(LU)[SummaryT::summaryName()][Id] =
+        std::make_unique<SummaryT>(std::move(Sum));
+  }
+
+  /// Looks up the \c SummaryT stored for \p Id, or nullptr (with a failure
+  /// recorded) if there is none.
+  template <typename SummaryT>
+  const SummaryT *getSummary(const LUSummary &LU, EntityId Id) {
+    const auto &Data = getData(LU);
+    auto SumIt = Data.find(SummaryT::summaryName());
+    if (SumIt == Data.end()) {
+      ADD_FAILURE() << "no summaries of kind " << SummaryT::summaryName();
+      return nullptr;
+    }
+    auto EIt = SumIt->second.find(Id);
+    if (EIt == SumIt->second.end()) {
+      ADD_FAILURE() << "no " << SummaryT::summaryName() << " for " << Id;
+      return nullptr;
+    }
+    return static_cast<const SummaryT *>(EIt->second.get());
+  }
+
+  /// Number of \c SummaryT entries in \p LU.
+  template <typename SummaryT> size_t summaryCount(const LUSummary &LU) {
+    const auto &Data = getData(LU);
+    auto It = Data.find(SummaryT::summaryName());
+    return It == Data.end() ? 0 : It->second.size();
+  }
+
+  /// Round-trips an LUSummary through JSON write -> read and returns the
+  /// resulting LUSummary on success.
+  llvm::Expected<LUSummary> roundTripLU(const LUSummary &LU) {
+    PathString InPath = makePath("vmf-lu.json");
+    if (auto Err = JSONFormat().writeLUSummary(LU, InPath))
+      return std::move(Err);
+    return JSONFormat().readLUSummary(InPath);
+  }
+
+  /// Build a WPASuite with one entry of every given AnalysisResult type. The
+  /// test fixture is a friend of WPASuite so it can construct one and reach
+  /// into private members.
+  WPASuite makeSuite(
+      EntityIdTable IdTable,
+      std::vector<std::pair<AnalysisName, std::unique_ptr<AnalysisResult>>>
+          Entries) {
+    WPASuite Suite = makeWPASuite();
+    getIdTable(Suite) = std::move(IdTable);
+    for (auto &[Name, R] : Entries)
+      getData(Suite).emplace(Name, std::move(R));
+    return Suite;
+  }
+
+  /// Round-trips a WPASuite through JSON write -> read.
+  llvm::Expected<WPASuite> roundTripSuite(const WPASuite &Suite) {
+    PathString Path = makePath("vmf-suite.json");
+    if (auto Err = JSONFormat().writeWPASuite(Suite, Path))
+      return std::move(Err);
+    return JSONFormat().readWPASuite(Path);
+  }
+
+private:
+  static NestedBuildNamespace linkUnitNamespace() {
+    constexpr auto LinkUnitKind = BuildNamespaceKind::LinkUnit;
+    return NestedBuildNamespace{BuildNamespace(LinkUnitKind, "TestLU")};
+  }
+
+  /// Entity names coming out of ASTEntityMapping have no namespace; qualify
+  /// them with the link unit the way EntityLinker does, so the ids in these
+  /// tests look like the ones a real LUSummary carries.
+  EntityId getOrCreateId(EntityIdTable &Ids, std::optional<EntityName> Name,
+                         const NamedDecl *ND) {
+    if (Name)
+      return Ids.getId(Name->makeQualified(linkUnitNamespace()));
+    ADD_FAILURE() << "no entity name for "
+                  << (ND ? ND->getQualifiedNameAsString() : "<null decl>");
+    // EntityId has no default constructor; the failure above already fails
+    // the test, so any id will do.
+    return Ids.getId(EntityName("<missing>", "", linkUnitNamespace()));
+  }
+
+  static EntityId declare(LUSummary &LU, EntityId Id) {
+    getLinkageTable(LU).insert({Id, ExternalLinkage});
+    return Id;
+  }
+};
+
+//===----------------------------------------------------------------------===//
+// VirtualMethodSummary round-trips
+//===----------------------------------------------------------------------===//
+
+static VirtualMethodSummary
+createSummary(llvm::ArrayRef<EntityId> Params, std::optional<EntityId> Return,
+              llvm::ArrayRef<EntityId> Overridden = {}) {
+  VirtualMethodSummary S;
+  S.ParamEntities.assign(Params.begin(), Params.end());
+  S.ReturnEntity = Return;
+  S.OverriddenMethods.assign(Overridden.begin(), Overridden.end());
+  return S;
+}
+
+TEST_F(VirtualMethodFamilyFormatTest, VirtualMethodSummaryEmpty) {
+  ASSERT_TRUE(AST.parse(R"cpp(
+    struct Foo {
+      virtual void m();
+    };
+  )cpp"));
+
+  auto LU = makeLUSummary();
+  EntityId M = addEntity(*LU, AST.fn("Foo::m"));
+
+  // ParamEntities and OverriddenMethods intentionally empty.
+  const VirtualMethodSummary Expected =
+      createSummary({}, addReturnEntity(*LU, AST.fn("Foo::m")));
+  insertSummary(*LU, M, Expected);
+
+  auto Round = roundTripLU(*LU);
+  ASSERT_THAT_EXPECTED(Round, llvm::Succeeded());
+
+  EXPECT_EQ(summaryCount<VirtualMethodSummary>(*Round), 1u);
+  const auto *Out = getSummary<VirtualMethodSummary>(*Round, M);
+  ASSERT_TRUE(Out);
+  EXPECT_EQ(*Out, Expected);
+}
+
+TEST_F(VirtualMethodFamilyFormatTest, VirtualMethodSummarySingleParam) {
+  ASSERT_TRUE(AST.parse(R"cpp(
+    struct BarBase {
+      virtual int *foo(int *p);
+    };
+    struct Bar : BarBase {
+      int *foo(int *p) override;
+    };
+  )cpp"));
+
+  auto LU = makeLUSummary();
+  EntityId M = addEntity(*LU, AST.fn("Bar::foo"));
+  EntityId Overridden = addEntity(*LU, AST.fn("BarBase::foo"));
+  EntityId P = addEntity(*LU, AST.findParam("Bar::foo", 0));
+  EntityId R = addReturnEntity(*LU, AST.fn("Bar::foo"));
+
+  const VirtualMethodSummary Expected =
+      createSummary({P}, R, /*Overridden=*/{Overridden});
+  insertSummary(*LU, M, Expected);
+
+  auto Round = roundTripLU(*LU);
+  ASSERT_THAT_EXPECTED(Round, llvm::Succeeded());
+
+  EXPECT_EQ(summaryCount<VirtualMethodSummary>(*Round), 1u);
+  const auto *Out = getSummary<VirtualMethodSummary>(*Round, M);
+  ASSERT_TRUE(Out);
+  EXPECT_EQ(*Out, Expected);
+}
+
+TEST_F(VirtualMethodFamilyFormatTest, VirtualMethodSummaryMultiParam) {
+  ASSERT_TRUE(AST.parse(R"cpp(
+    struct Base {
+      virtual char *foo(int *p1, char *p2);
+    };
+    struct Derived : Base {
+      char *foo(int *p1, char *p2) override;
+    };
+  )cpp"));
+
+  auto LU = makeLUSummary();
+  EntityId M1 = addEntity(*LU, AST.fn("Base::foo"));
+  EntityId M2 = addEntity(*LU, AST.fn("Derived::foo"));
+  EntityId P1a = addEntity(*LU, AST.findParam("Base::foo", 0));
+  EntityId P1b = addEntity(*LU, AST.findParam("Base::foo", 1));
+  EntityId P2a = addEntity(*LU, AST.findParam("Derived::foo", 0));
+  EntityId P2b = addEntity(*LU, AST.findParam("Derived::foo", 1));
+  EntityId R1 = addReturnEntity(*LU, AST.fn("Base::foo"));
+  EntityId R2 = addReturnEntity(*LU, AST.fn("Derived::foo"));
+
+  const VirtualMethodSummary E1 = createSummary({P1a, P1b}, R1);
+  // Derived::foo overrides Base::foo.
+  const VirtualMethodSummary E2 =
+      createSummary({P2a, P2b}, R2, /*Overridden=*/{M1});
+  insertSummary(*LU, M1, E1);
+  insertSummary(*LU, M2, E2);
+
+  auto Round = roundTripLU(*LU);
+  ASSERT_THAT_EXPECTED(Round, llvm::Succeeded());
+
+  EXPECT_EQ(summaryCount<VirtualMethodSummary>(*Round), 2u);
+  const auto *Out1 = getSummary<VirtualMethodSummary>(*Round, M1);
+  ASSERT_TRUE(Out1);
+  EXPECT_EQ(*Out1, E1);
+  const auto *Out2 = getSummary<VirtualMethodSummary>(*Round, M2);
+  ASSERT_TRUE(Out2);
+  EXPECT_EQ(*Out2, E2);
+}
+
+//===----------------------------------------------------------------------===//
+// VirtualMethodFamilyAnalysisResult round-trips (via WPASuite).
+//===----------------------------------------------------------------------===//
+
+class VirtualMethodFamilyAnalysisRoundTrip
+    : public VirtualMethodFamilyFormatTest {
+protected:
+  EntityIdTable IdTable;
+};
+
+TEST_F(VirtualMethodFamilyAnalysisRoundTrip, EmptyResultRoundTrips) {
+  auto R = std::make_unique<VirtualMethodFamilyAnalysisResult>();
+  std::vector<std::pair<AnalysisName, std::unique_ptr<AnalysisResult>>> Entries;
+  Entries.emplace_back(VirtualMethodFamilyAnalysisResult::analysisName(),
+                       std::move(R));
+  WPASuite Suite = makeSuite(std::move(IdTable), std::move(Entries));
+
+  auto Round = roundTripSuite(Suite);
+  ASSERT_THAT_EXPECTED(Round, llvm::Succeeded());
+
+  auto Got = Round->get<VirtualMethodFamilyAnalysisResult>();
+  ASSERT_THAT_EXPECTED(Got, llvm::Succeeded());
+  EXPECT_TRUE(Got->RetAndParamData.empty());
+}
+
+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(VirtualMethodFamilyAnalysisRoundTrip, SingleFamilyRoundTrips) {
+  ASSERT_TRUE(AST.parse(R"cpp(
+    struct Base {
+      virtual void foo(int *p);
+    };
+    struct Derived : Base {
+      void foo(int *p) override;
+    };
+  )cpp"));
+
+  EntityId M1 = addEntity(IdTable, AST.fn("Base::foo"));
+  EntityId M2 = addEntity(IdTable, AST.fn("Derived::foo"));
+  EntityId P1 = addEntity(IdTable, AST.findParam("Base::foo", 0));
+  EntityId P2 = addEntity(IdTable, AST.findParam("Derived::foo", 0));
+
+  const VirtualMethodFamilyAnalysisResult Expected = createResult({
+      {P1, {/*FamilyId=*/P1, /*OwnerMethodId=*/M1}},
+      {P2, {/*FamilyId=*/P1, /*OwnerMethodId=*/M2}},
+  });
+
+  std::vector<std::pair<AnalysisName, std::unique_ptr<AnalysisResult>>> Entries;
+  Entries.emplace_back(
+      VirtualMethodFamilyAnalysisResult::analysisName(),
+      std::make_unique<VirtualMethodFamilyAnalysisResult>(Expected));
+  WPASuite Suite = makeSuite(std::move(IdTable), std::move(Entries));
+
+  auto Round = roundTripSuite(Suite);
+  ASSERT_THAT_EXPECTED(Round, llvm::Succeeded());
+
+  auto Got = Round->get<VirtualMethodFamilyAnalysisResult>();
+  ASSERT_THAT_EXPECTED(Got, llvm::Succeeded());
+  EXPECT_EQ(*Got, Expected);
+}
+
+TEST_F(VirtualMethodFamilyAnalysisRoundTrip,
+       MultiFamilyAndReturnSlotRoundTrips) {
+  ASSERT_TRUE(AST.parse(R"cpp(
+    struct A {
+      virtual void m1(int *p);
+      virtual int *m2();
+    };
+    struct B : A {
+      void m1(int *p) override;
+      int *m2() override;
+    };
+  )cpp"));
+
+  EntityId M1 = addEntity(IdTable, AST.fn("A::m1"));
+  EntityId M2 = addEntity(IdTable, AST.fn("B::m1"));
+  EntityId M3 = addEntity(IdTable, AST.fn("A::m2"));
+  EntityId M4 = addEntity(IdTable, AST.fn("B::m2"));
+  EntityId P1 = addEntity(IdTable, AST.findParam("A::m1", 0));
+  EntityId P2 = addEntity(IdTable, AST.findParam("B::m1", 0));
+  EntityId R1 = addReturnEntity(IdTable, AST.fn("A::m2"));
+  EntityId R2 = addReturnEntity(IdTable, AST.fn("B::m2"));
+
+  const VirtualMethodFamilyAnalysisResult Expected = createResult({
+      // Family #1: parameter slot
+      {P1, {/*FamilyId=*/P1, /*OwnerMethodId=*/M1}},
+      {P2, {/*FamilyId=*/P1, /*OwnerMethodId=*/M2}},
+      // Family #2: return slot
+      {R1, {/*FamilyId=*/R1, /*OwnerMethodId=*/M3}},
+      {R2, {/*FamilyId=*/R1, /*OwnerMethodId=*/M4}},
+  });
+
+  std::vector<std::pair<AnalysisName, std::unique_ptr<AnalysisResult>>> Entries;
+  Entries.emplace_back(
+      VirtualMethodFamilyAnalysisResult::analysisName(),
+      std::make_unique<VirtualMethodFamilyAnalysisResult>(Expected));
+  WPASuite Suite = makeSuite(std::move(IdTable), std::move(Entries));
+
+  auto Round = roundTripSuite(Suite);
+  ASSERT_THAT_EXPECTED(Round, llvm::Succeeded());
+
+  auto Got = Round->get<VirtualMethodFamilyAnalysisResult>();
+  ASSERT_THAT_EXPECTED(Got, llvm::Succeeded());
+  EXPECT_EQ(*Got, Expected);
+}
+
+} // namespace



More information about the llvm-branch-commits mailing list