[clang] [llvm] [clang][ssaf] Implement JSON format for CallGraph summary (PR #189681)
via cfe-commits
cfe-commits at lists.llvm.org
Tue Mar 31 07:34:50 PDT 2026
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang-ssaf
Author: Balázs Benics (steakhal)
<details>
<summary>Changes</summary>
rdar://170258016
---
Full diff: https://github.com/llvm/llvm-project/pull/189681.diff
10 Files Affected:
- (modified) clang/include/clang/ScalableStaticAnalysisFramework/Analyses/CallGraph/CallGraphSummary.h (+2-1)
- (modified) clang/include/clang/ScalableStaticAnalysisFramework/SSAFBuiltinForceLinker.h (+5)
- (modified) clang/lib/ScalableStaticAnalysisFramework/Analyses/CMakeLists.txt (+1)
- (modified) clang/lib/ScalableStaticAnalysisFramework/Analyses/CallGraph/CallGraphExtractor.cpp (+2-1)
- (added) clang/lib/ScalableStaticAnalysisFramework/Analyses/CallGraph/CallGraphJSONFormat.cpp (+142)
- (modified) clang/lib/ScalableStaticAnalysisFramework/Frontend/TUSummaryExtractorFrontendAction.cpp (+4)
- (added) clang/test/Analysis/Scalable/call-graph.cpp (+20)
- (modified) clang/test/Analysis/Scalable/ssaf-format/list.test (+4-3)
- (modified) clang/unittests/ScalableStaticAnalysisFramework/Analyses/CallGraph/CallGraphExtractorTest.cpp (+5-3)
- (modified) llvm/utils/gn/secondary/clang/lib/ScalableStaticAnalysisFramework/Analyses/BUILD.gn (+1)
``````````diff
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/CallGraph/CallGraphSummary.h b/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/CallGraph/CallGraphSummary.h
index ad70218d01614..8056b1001a216 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/CallGraph/CallGraphSummary.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/CallGraph/CallGraphSummary.h
@@ -30,8 +30,9 @@ struct CallGraphSummary final : public EntitySummary {
unsigned Column;
};
+ static constexpr llvm::StringLiteral Name = "CallGraph";
SummaryName getSummaryName() const override {
- return SummaryName("CallGraph");
+ return SummaryName(Name.str());
}
/// Represents the location of the function.
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/SSAFBuiltinForceLinker.h b/clang/include/clang/ScalableStaticAnalysisFramework/SSAFBuiltinForceLinker.h
index 707573ce34e46..1dc50c9c58dd8 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/SSAFBuiltinForceLinker.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/SSAFBuiltinForceLinker.h
@@ -37,4 +37,9 @@ extern volatile int CallGraphExtractorAnchorSource;
[[maybe_unused]] static int CallGraphExtractorAnchorDestination =
CallGraphExtractorAnchorSource;
+// This anchor is used to force the linker to link the CallGraph JSON format.
+extern volatile int CallGraphJSONFormatAnchorSource;
+[[maybe_unused]] static int CallGraphJSONFormatAnchorDestination =
+ CallGraphJSONFormatAnchorSource;
+
#endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_SSAFBUILTINFORCELINKER_H
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Analyses/CMakeLists.txt b/clang/lib/ScalableStaticAnalysisFramework/Analyses/CMakeLists.txt
index 2dcce40f886dd..df8079a7d375d 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Analyses/CMakeLists.txt
+++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/CMakeLists.txt
@@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS
add_clang_library(clangScalableStaticAnalysisFrameworkAnalyses
CallGraph/CallGraphExtractor.cpp
+ CallGraph/CallGraphJSONFormat.cpp
UnsafeBufferUsage/UnsafeBufferUsageExtractor.cpp
LINK_LIBS
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Analyses/CallGraph/CallGraphExtractor.cpp b/clang/lib/ScalableStaticAnalysisFramework/Analyses/CallGraph/CallGraphExtractor.cpp
index b2cd2e40f33b5..1dbed7e0b0d8a 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Analyses/CallGraph/CallGraphExtractor.cpp
+++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/CallGraph/CallGraphExtractor.cpp
@@ -98,7 +98,8 @@ void CallGraphExtractor::handleCallGraphNode(const ASTContext &Ctx,
}
static TUSummaryExtractorRegistry::Add<CallGraphExtractor>
- RegisterExtractor("CallGraph", "Extracts static call-graph information");
+ RegisterExtractor(CallGraphSummary::Name,
+ "Extracts static call-graph information");
// This anchor is used to force the linker to link in the generated object file
// and thus register the CallGraphExtractor.
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Analyses/CallGraph/CallGraphJSONFormat.cpp b/clang/lib/ScalableStaticAnalysisFramework/Analyses/CallGraph/CallGraphJSONFormat.cpp
new file mode 100644
index 0000000000000..33da0ab0bd205
--- /dev/null
+++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/CallGraph/CallGraphJSONFormat.cpp
@@ -0,0 +1,142 @@
+//===- CallGraphJSONFormat.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/ScalableStaticAnalysisFramework/Analyses/CallGraph/CallGraphSummary.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Model/EntityId.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Serialization/JSONFormat.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/Support/JSON.h"
+#include <memory>
+
+using namespace llvm;
+using namespace clang;
+using namespace ssaf;
+
+static json::Object serialize(const EntitySummary &Summary,
+ JSONFormat::EntityIdToJSONFn ToJSON) {
+ const auto &S = static_cast<const CallGraphSummary &>(Summary);
+
+ json::Array DirectCalleesArray;
+ DirectCalleesArray.reserve(S.DirectCallees.size());
+ append_range(DirectCalleesArray, map_range(S.DirectCallees, ToJSON));
+
+ json::Array VirtualCalleesArray;
+ VirtualCalleesArray.reserve(S.VirtualCallees.size());
+ append_range(VirtualCalleesArray, map_range(S.VirtualCallees, ToJSON));
+
+ return json::Object{
+ {"pretty_name", json::Value(S.PrettyName)},
+ {"direct_callees", std::move(DirectCalleesArray)},
+ {"virtual_callees", std::move(VirtualCalleesArray)},
+ {"def",
+ json::Object{
+ {"file", json::Value(S.Definition.File)},
+ {"line", json::Value(S.Definition.Line)},
+ {"col", json::Value(S.Definition.Column)},
+ }},
+ };
+}
+
+static Expected<std::unique_ptr<EntitySummary>>
+deserialize(const json::Object &Obj, EntityIdTable &IdTable,
+ JSONFormat::EntityIdFromJSONFn FromJSON) {
+ auto Result = std::make_unique<CallGraphSummary>();
+
+ auto PrettyName = Obj.getString("pretty_name");
+ if (!PrettyName) {
+ return createStringError(inconvertibleErrorCode(),
+ "missing or invalid field 'pretty_name'");
+ }
+ Result->PrettyName = PrettyName->str();
+
+ const json::Array *CalleesArray = Obj.getArray("direct_callees");
+ if (!CalleesArray) {
+ return createStringError(inconvertibleErrorCode(),
+ "missing or invalid field 'direct_callees'");
+ }
+ for (const auto &[Index, Value] : llvm::enumerate(*CalleesArray)) {
+ const json::Object *CalleeObj = Value.getAsObject();
+ if (!CalleeObj) {
+ return createStringError(
+ inconvertibleErrorCode(),
+ "direct_callees element at index %zu is not a JSON object", Index);
+ }
+ auto ExpectedId = FromJSON(*CalleeObj);
+ if (!ExpectedId) {
+ return createStringError(
+ inconvertibleErrorCode(),
+ "invalid entity id in direct_callees at index %zu: %s", Index,
+ toString(ExpectedId.takeError()).c_str());
+ }
+ Result->DirectCallees.insert(*ExpectedId);
+ }
+
+ const json::Array *VirtualCalleesArray = Obj.getArray("virtual_callees");
+ if (!VirtualCalleesArray) {
+ return createStringError(inconvertibleErrorCode(),
+ "missing or invalid field 'virtual_callees'");
+ }
+ for (const auto &[Index, Value] : llvm::enumerate(*VirtualCalleesArray)) {
+ const json::Object *CalleeObj = Value.getAsObject();
+ if (!CalleeObj) {
+ return createStringError(
+ inconvertibleErrorCode(),
+ "virtual_callees element at index %zu is not a JSON object", Index);
+ }
+ auto ExpectedId = FromJSON(*CalleeObj);
+ if (!ExpectedId) {
+ return createStringError(
+ inconvertibleErrorCode(),
+ "invalid entity id in virtual_callees at index %zu: %s", Index,
+ toString(ExpectedId.takeError()).c_str());
+ }
+ Result->VirtualCallees.insert(*ExpectedId);
+ }
+
+ const json::Object *DefObj = Obj.getObject("def");
+ if (!DefObj) {
+ return createStringError(inconvertibleErrorCode(),
+ "missing or invalid field 'def'");
+ }
+ auto File = DefObj->getString("file");
+ if (!File) {
+ return createStringError(inconvertibleErrorCode(),
+ "missing or invalid field 'def.file'");
+ }
+ auto Line = DefObj->getInteger("line");
+ if (!Line) {
+ return createStringError(inconvertibleErrorCode(),
+ "missing or invalid field 'def.line'");
+ }
+ auto Col = DefObj->getInteger("col");
+ if (!Col) {
+ return createStringError(inconvertibleErrorCode(),
+ "missing or invalid field 'def.col'");
+ }
+ Result->Definition = {File->str(), static_cast<unsigned>(*Line),
+ static_cast<unsigned>(*Col)};
+
+ return std::move(Result);
+}
+
+namespace {
+struct CallGraphJSONFormatInfo final : JSONFormat::FormatInfo {
+ CallGraphJSONFormatInfo()
+ : JSONFormat::FormatInfo(SummaryName(CallGraphSummary::Name.str()),
+ serialize, deserialize) {}
+};
+} // namespace
+
+static llvm::Registry<JSONFormat::FormatInfo>::Add<CallGraphJSONFormatInfo>
+ RegisterFormatInfo(CallGraphSummary::Name,
+ "JSON Format info for CallGraph summary");
+
+// This anchor is used to force the linker to link in the generated object file
+// and thus register the JSON format for CallGraphSummary.
+// NOLINTNEXTLINE(misc-use-internal-linkage)
+volatile int CallGraphJSONFormatAnchorSource = 0;
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Frontend/TUSummaryExtractorFrontendAction.cpp b/clang/lib/ScalableStaticAnalysisFramework/Frontend/TUSummaryExtractorFrontendAction.cpp
index 9a75b20fa548b..d4bf8d4a63fa4 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Frontend/TUSummaryExtractorFrontendAction.cpp
+++ b/clang/lib/ScalableStaticAnalysisFramework/Frontend/TUSummaryExtractorFrontendAction.cpp
@@ -17,6 +17,7 @@
#include "clang/ScalableStaticAnalysisFramework/Core/TUSummary/TUSummaryExtractor.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/IOSandbox.h"
#include "llvm/Support/Path.h"
#include <memory>
#include <string>
@@ -150,6 +151,9 @@ void TUSummaryRunner::HandleTranslationUnit(ASTContext &Ctx) {
// First, invoke the Summary Extractors.
MultiplexConsumer::HandleTranslationUnit(Ctx);
+ // FIXME(sandboxing): Remove this by adopting `llvm::vfs::OutputBackend`.
+ llvm::sys::sandbox::ScopedSetting Guard = llvm::sys::sandbox::scopedDisable();
+
// Then serialize the result.
if (auto Err = Format->writeTUSummary(Summary, Opts.SSAFTUSummaryFile)) {
Ctx.getDiagnostics().Report(diag::warn_ssaf_write_tu_summary_failed)
diff --git a/clang/test/Analysis/Scalable/call-graph.cpp b/clang/test/Analysis/Scalable/call-graph.cpp
new file mode 100644
index 0000000000000..b6e7ce91ee8c7
--- /dev/null
+++ b/clang/test/Analysis/Scalable/call-graph.cpp
@@ -0,0 +1,20 @@
+// RUN: rm -rf %t.summary.json
+// RUN: %clang_cc1 -fsyntax-only %s \
+// RUN: --ssaf-extract-summaries=CallGraph \
+// RUN: --ssaf-tu-summary-file=%t.summary.json
+
+// Check that the JSON validation passes.
+// TODO: Enable the next line once the LinkageTable is populated.
+// R U N: clang-ssaf-format --type=tu %t.summary.json
+
+// Check that the JSON has plausible content irrespective of the order of the fields.
+// RUN: FileCheck %s --match-full-lines --input-file=%t.summary.json
+// CHECK-DAG: "direct_callees": [
+// CHECK-DAG: "pretty_name": "example()",
+// CHECK-DAG: "virtual_callees": []
+// CHECK-DAG: "summary_name": "CallGraph"
+
+void no_body();
+void example() {
+ no_body();
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/list.test b/clang/test/Analysis/Scalable/ssaf-format/list.test
index a21d1543915f7..ffc1624c5872a 100644
--- a/clang/test/Analysis/Scalable/ssaf-format/list.test
+++ b/clang/test/Analysis/Scalable/ssaf-format/list.test
@@ -1,9 +1,10 @@
// Test clang-ssaf-format --list output without any loaded plugins.
// RUN: clang-ssaf-format --list \
-// RUN: | FileCheck %s
+// RUN: | FileCheck %s --match-full-lines
// CHECK: Registered serialization formats:
// CHECK-EMPTY:
-// CHECK-NEXT: 1. json - JSON serialization format
-// CHECK-NEXT: Analyses: (none)
+// CHECK-DAG: [[NthFormat:[0-9]+]]. json - JSON serialization format
+// CHECK-DAG: Analyses:
+// CHECK-DAG: [[NthFormat]].[[MthSummary:[0-9]+]]. CallGraph - JSON Format info for CallGraph summary
diff --git a/clang/unittests/ScalableStaticAnalysisFramework/Analyses/CallGraph/CallGraphExtractorTest.cpp b/clang/unittests/ScalableStaticAnalysisFramework/Analyses/CallGraph/CallGraphExtractorTest.cpp
index 9e0b9e6e256a4..2557c7a62479e 100644
--- a/clang/unittests/ScalableStaticAnalysisFramework/Analyses/CallGraph/CallGraphExtractorTest.cpp
+++ b/clang/unittests/ScalableStaticAnalysisFramework/Analyses/CallGraph/CallGraphExtractorTest.cpp
@@ -120,6 +120,8 @@ template <typename... Matchers> auto hasSummaryThat(const Matchers &...Ms) {
// Test fixture
// ============================================================================
+static const SummaryName CallGraphName{CallGraphSummary::Name.str()};
+
struct CallGraphExtractorTest : ssaf::TestFixture {
TUSummary Summary =
BuildNamespace(BuildNamespaceKind::CompilationUnit, "Mock.cpp");
@@ -129,7 +131,7 @@ struct CallGraphExtractorTest : ssaf::TestFixture {
/// This will update the \c AST \c Builder and \c Summary data members.
void runExtractor(StringRef Code, ArrayRef<std::string> Args = {}) {
AST = tooling::buildASTFromCodeWithArgs(Code, Args);
- auto Consumer = makeTUSummaryExtractor("CallGraph", Builder);
+ auto Consumer = makeTUSummaryExtractor(CallGraphName.str(), Builder);
Consumer->HandleTranslationUnit(AST->getASTContext());
}
@@ -207,7 +209,7 @@ CallGraphExtractorTest::findSummary(llvm::StringRef FnName) const {
}
EntityId ID = It->second;
auto &Data = getData(Summary);
- auto SummaryIt = Data.find(SummaryName("CallGraph"));
+ auto SummaryIt = Data.find(CallGraphName);
if (SummaryIt == Data.end())
return llvm::createStringError("There is no 'CallGraph' summary");
auto EntityIt = SummaryIt->second.find(ID);
@@ -343,7 +345,7 @@ TEST_F(CallGraphExtractorTest, DeclarationsOnlyNoSummary) {
)cpp");
// No summary for functions without definitions.
- EXPECT_FALSE(llvm::is_contained(getData(Summary), SummaryName("CallGraph")));
+ EXPECT_FALSE(llvm::is_contained(getData(Summary), CallGraphName));
}
TEST_F(CallGraphExtractorTest, DuplicateCallees) {
diff --git a/llvm/utils/gn/secondary/clang/lib/ScalableStaticAnalysisFramework/Analyses/BUILD.gn b/llvm/utils/gn/secondary/clang/lib/ScalableStaticAnalysisFramework/Analyses/BUILD.gn
index ac62574ad8534..1450d8866b71f 100644
--- a/llvm/utils/gn/secondary/clang/lib/ScalableStaticAnalysisFramework/Analyses/BUILD.gn
+++ b/llvm/utils/gn/secondary/clang/lib/ScalableStaticAnalysisFramework/Analyses/BUILD.gn
@@ -10,6 +10,7 @@ static_library("Analyses") {
]
sources = [
"CallGraph/CallGraphExtractor.cpp",
+ "CallGraph/CallGraphJSONFormat.cpp",
"UnsafeBufferUsage/UnsafeBufferUsageExtractor.cpp",
]
}
``````````
</details>
https://github.com/llvm/llvm-project/pull/189681
More information about the cfe-commits
mailing list