[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:07 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