[clang] [llvm] [clang][ssaf] Implement JSON format for CallGraph summary (PR #189681)

Balázs Benics via llvm-commits llvm-commits at lists.llvm.org
Wed Apr 1 07:29:26 PDT 2026


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

>From 7cd85dfe9357791fda4bdc07808562c1345c8eec Mon Sep 17 00:00:00 2001
From: Balazs Benics <benicsbalazs at gmail.com>
Date: Mon, 30 Mar 2026 15:44:10 +0100
Subject: [PATCH 1/2] [clang][ssaf] Implement JSON format for CallGraph summary

rdar://170258016
---
 .../Analyses/CallGraph/CallGraphSummary.h     |   3 +-
 .../SSAFBuiltinForceLinker.h                  |   5 +
 .../Analyses/CMakeLists.txt                   |   1 +
 .../Analyses/CallGraph/CallGraphExtractor.cpp |   3 +-
 .../CallGraph/CallGraphJSONFormat.cpp         | 142 ++++++++++++++++++
 .../TUSummaryExtractorFrontendAction.cpp      |   4 +
 clang/test/Analysis/Scalable/call-graph.cpp   |  20 +++
 .../Analysis/Scalable/ssaf-format/list.test   |   7 +-
 .../CallGraph/CallGraphExtractorTest.cpp      |   8 +-
 .../Analyses/BUILD.gn                         |   1 +
 10 files changed, 186 insertions(+), 8 deletions(-)
 create mode 100644 clang/lib/ScalableStaticAnalysisFramework/Analyses/CallGraph/CallGraphJSONFormat.cpp
 create mode 100644 clang/test/Analysis/Scalable/call-graph.cpp

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",
   ]
 }

>From d0b7ef88b0650fa47c3d7f3bb270f72f1097ac84 Mon Sep 17 00:00:00 2001
From: Balazs Benics <benicsbalazs at gmail.com>
Date: Wed, 1 Apr 2026 14:42:15 +0100
Subject: [PATCH 2/2] Use ErrorBuilder, also add tests for covering the error
 conditions

---
 .../CallGraph/CallGraphJSONFormat.cpp         | 92 ++++++++++++------
 clang/test/Analysis/Scalable/call-graph.cpp   | 44 ++++++---
 .../invalid-direct-callee-element.json        | 46 +++++++++
 .../CallGraph/invalid-direct-callee-id.json   | 48 +++++++++
 .../Inputs/CallGraph/missing-def-col.json     | 48 +++++++++
 .../Inputs/CallGraph/missing-def-file.json    | 45 +++++++++
 .../Inputs/CallGraph/missing-def-line.json    | 47 +++++++++
 .../Inputs/CallGraph/missing-def.json         | 44 +++++++++
 .../CallGraph/missing-direct-callees.json     | 42 ++++++++
 .../Inputs/CallGraph/missing-pretty-name.json | 40 ++++++++
 .../CallGraph/missing-virtual-callees.json    | 43 ++++++++
 .../call-graph-invalid-json-format.test       | 97 +++++++++++++++++++
 12 files changed, 594 insertions(+), 42 deletions(-)
 create mode 100644 clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/invalid-direct-callee-element.json
 create mode 100644 clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/invalid-direct-callee-id.json
 create mode 100644 clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/missing-def-col.json
 create mode 100644 clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/missing-def-file.json
 create mode 100644 clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/missing-def-line.json
 create mode 100644 clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/missing-def.json
 create mode 100644 clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/missing-direct-callees.json
 create mode 100644 clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/missing-pretty-name.json
 create mode 100644 clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/missing-virtual-callees.json
 create mode 100644 clang/test/Analysis/Scalable/ssaf-format/call-graph-invalid-json-format.test

diff --git a/clang/lib/ScalableStaticAnalysisFramework/Analyses/CallGraph/CallGraphJSONFormat.cpp b/clang/lib/ScalableStaticAnalysisFramework/Analyses/CallGraph/CallGraphJSONFormat.cpp
index 33da0ab0bd205..860e26417eb55 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Analyses/CallGraph/CallGraphJSONFormat.cpp
+++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/CallGraph/CallGraphJSONFormat.cpp
@@ -9,6 +9,7 @@
 #include "clang/ScalableStaticAnalysisFramework/Analyses/CallGraph/CallGraphSummary.h"
 #include "clang/ScalableStaticAnalysisFramework/Core/Model/EntityId.h"
 #include "clang/ScalableStaticAnalysisFramework/Core/Serialization/JSONFormat.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Support/ErrorBuilder.h"
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/Support/JSON.h"
 #include <memory>
@@ -17,6 +18,13 @@ using namespace llvm;
 using namespace clang;
 using namespace ssaf;
 
+static const char *FailedToReadObjectAtField =
+    "failed to read {0} from field '{1}': expected JSON {2}";
+static const char *FailedToReadObjectAtIndex =
+    "failed to read {0} from index '{1}': expected JSON {2}";
+static const char *ReadingFromField = "reading {0} from field '{1}'";
+static const char *ReadingFromIndex = "reading {0} from index '{1}'";
+
 static json::Object serialize(const EntitySummary &Summary,
                               JSONFormat::EntityIdToJSONFn ToJSON) {
   const auto &S = static_cast<const CallGraphSummary &>(Summary);
@@ -49,77 +57,101 @@ deserialize(const json::Object &Obj, EntityIdTable &IdTable,
 
   auto PrettyName = Obj.getString("pretty_name");
   if (!PrettyName) {
-    return createStringError(inconvertibleErrorCode(),
-                             "missing or invalid field 'pretty_name'");
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                FailedToReadObjectAtField, "PrettyName",
+                                "pretty_name", "string")
+        .build();
   }
   Result->PrettyName = PrettyName->str();
 
   const json::Array *CalleesArray = Obj.getArray("direct_callees");
   if (!CalleesArray) {
-    return createStringError(inconvertibleErrorCode(),
-                             "missing or invalid field 'direct_callees'");
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                FailedToReadObjectAtField, "DirectCallees",
+                                "direct_callees", "array")
+        .build();
   }
   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);
+      return ErrorBuilder::create(std::errc::invalid_argument,
+                                  FailedToReadObjectAtIndex, "EntityId", Index,
+                                  "object")
+          .context(ReadingFromField, "DirectCallees", "direct_callees")
+          .build();
     }
     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());
+      return ErrorBuilder::wrap(ExpectedId.takeError())
+          .context(ReadingFromIndex, "EntityId", Index)
+          .context(ReadingFromField, "DirectCallees", "direct_callees")
+          .build();
     }
     Result->DirectCallees.insert(*ExpectedId);
   }
 
   const json::Array *VirtualCalleesArray = Obj.getArray("virtual_callees");
   if (!VirtualCalleesArray) {
-    return createStringError(inconvertibleErrorCode(),
-                             "missing or invalid field 'virtual_callees'");
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                FailedToReadObjectAtField, "VirtualCallees",
+                                "virtual_callees", "array")
+        .build();
   }
   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);
+      return ErrorBuilder::create(std::errc::invalid_argument,
+                                  FailedToReadObjectAtIndex, "EntityId", Index,
+                                  "object")
+          .context(ReadingFromField, "VirtualCallees", "virtual_callees")
+          .build();
     }
     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());
+      return ErrorBuilder::wrap(ExpectedId.takeError())
+          .context(ReadingFromIndex, "EntityId", Index)
+          .context(ReadingFromField, "VirtualCallees", "virtual_callees")
+          .build();
     }
     Result->VirtualCallees.insert(*ExpectedId);
   }
 
   const json::Object *DefObj = Obj.getObject("def");
   if (!DefObj) {
-    return createStringError(inconvertibleErrorCode(),
-                             "missing or invalid field 'def'");
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                FailedToReadObjectAtField, "SourceLocation",
+                                "def", "object")
+        .build();
   }
   auto File = DefObj->getString("file");
   if (!File) {
-    return createStringError(inconvertibleErrorCode(),
-                             "missing or invalid field 'def.file'");
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                FailedToReadObjectAtField, "File", "file",
+                                "string")
+        .context(ReadingFromField, "SourceLocation", "def")
+        .build();
   }
   auto Line = DefObj->getInteger("line");
   if (!Line) {
-    return createStringError(inconvertibleErrorCode(),
-                             "missing or invalid field 'def.line'");
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                FailedToReadObjectAtField, "Line", "line",
+                                "number")
+        .context(ReadingFromField, "SourceLocation", "def")
+        .build();
   }
   auto Col = DefObj->getInteger("col");
   if (!Col) {
-    return createStringError(inconvertibleErrorCode(),
-                             "missing or invalid field 'def.col'");
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                FailedToReadObjectAtField, "Column", "col",
+                                "number")
+        .context(ReadingFromField, "SourceLocation", "def")
+        .build();
   }
-  Result->Definition = {File->str(), static_cast<unsigned>(*Line),
-                        static_cast<unsigned>(*Col)};
+  Result->Definition = {
+      File->str(),
+      static_cast<unsigned>(*Line),
+      static_cast<unsigned>(*Col),
+  };
 
   return std::move(Result);
 }
diff --git a/clang/test/Analysis/Scalable/call-graph.cpp b/clang/test/Analysis/Scalable/call-graph.cpp
index b6e7ce91ee8c7..8ff0a7ee53c72 100644
--- a/clang/test/Analysis/Scalable/call-graph.cpp
+++ b/clang/test/Analysis/Scalable/call-graph.cpp
@@ -2,19 +2,39 @@
 // RUN: %clang_cc1 -fsyntax-only %s \
 // RUN:   --ssaf-extract-summaries=CallGraph \
 // RUN:   --ssaf-tu-summary-file=%t.summary.json
+// RUN: FileCheck %s --match-full-lines --input-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
+// caller() has a direct callee and no virtual callees.
+//   CHECK-LABEL: "entity_summary": {
+//   CHECK-DAG:     "def": {
+//   CHECK-DAG:       "col": {{[0-9]+}},
+//   CHECK-DAG:       "file": "{{.+}}",
+//   CHECK-DAG:       "line": {{[0-9]+}}
+//   CHECK-DAG:     "pretty_name": "caller()",
+//   CHECK-DAG:     "direct_callees": [
+//   CHECK-DAG:     "virtual_callees": []
 
-// 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"
+// polymorphic() has a virtual callee and no direct callees.
+//   CHECK-LABEL: "entity_summary": {
+//   CHECK-DAG:     "def": {
+//   CHECK-DAG:       "col": {{[0-9]+}},
+//   CHECK-DAG:       "file": "{{.+}}",
+//   CHECK-DAG:       "line": {{[0-9]+}}
+//   CHECK-DAG:     "pretty_name": "polymorphic(Base &)",
+//   CHECK-DAG:     "direct_callees": [],
+//   CHECK-DAG:     "virtual_callees": [
+
+struct Base {
+  virtual ~Base();
+  virtual void vmethod();
+};
+
+void callee();
+
+void caller() {
+  callee();
+}
 
-void no_body();
-void example() {
-  no_body();
+void polymorphic(Base &b) {
+  b.vmethod();
 }
diff --git a/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/invalid-direct-callee-element.json b/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/invalid-direct-callee-element.json
new file mode 100644
index 0000000000000..e1490cb50af4d
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/invalid-direct-callee-element.json
@@ -0,0 +1,46 @@
+{
+  "tu_namespace": {
+    "kind": "CompilationUnit",
+    "name": "test.cpp"
+  },
+  "id_table": [
+    {
+      "id": 0,
+      "name": {
+        "namespace": [
+          {
+            "kind": "CompilationUnit",
+            "name": "test.cpp"
+          }
+        ],
+        "suffix": "",
+        "usr": "c:@F at foo#"
+      }
+    }
+  ],
+  "linkage_table": [
+    {
+      "id": 0,
+      "linkage": {
+        "type": "External"
+      }
+    }
+  ],
+  "data": [
+    {
+      "summary_name": "CallGraph",
+      "summary_data": [
+        {
+          "entity_id": 0,
+          "entity_summary": {
+            "pretty_name": "foo()",
+            "direct_callees": [
+              42
+            ],
+            "virtual_callees": []
+          }
+        }
+      ]
+    }
+  ]
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/invalid-direct-callee-id.json b/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/invalid-direct-callee-id.json
new file mode 100644
index 0000000000000..cee8fb2d78635
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/invalid-direct-callee-id.json
@@ -0,0 +1,48 @@
+{
+  "tu_namespace": {
+    "kind": "CompilationUnit",
+    "name": "test.cpp"
+  },
+  "id_table": [
+    {
+      "id": 0,
+      "name": {
+        "namespace": [
+          {
+            "kind": "CompilationUnit",
+            "name": "test.cpp"
+          }
+        ],
+        "suffix": "",
+        "usr": "c:@F at foo#"
+      }
+    }
+  ],
+  "linkage_table": [
+    {
+      "id": 0,
+      "linkage": {
+        "type": "External"
+      }
+    }
+  ],
+  "data": [
+    {
+      "summary_name": "CallGraph",
+      "summary_data": [
+        {
+          "entity_id": 0,
+          "entity_summary": {
+            "pretty_name": "foo()",
+            "direct_callees": [
+              {
+                "@": "bad"
+              }
+            ],
+            "virtual_callees": []
+          }
+        }
+      ]
+    }
+  ]
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/missing-def-col.json b/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/missing-def-col.json
new file mode 100644
index 0000000000000..71ed3e937e24e
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/missing-def-col.json
@@ -0,0 +1,48 @@
+{
+  "tu_namespace": {
+    "kind": "CompilationUnit",
+    "name": "test.cpp"
+  },
+  "id_table": [
+    {
+      "id": 0,
+      "name": {
+        "namespace": [
+          {
+            "kind": "CompilationUnit",
+            "name": "test.cpp"
+          }
+        ],
+        "suffix": "",
+        "usr": "c:@F at foo#"
+      }
+    }
+  ],
+  "linkage_table": [
+    {
+      "id": 0,
+      "linkage": {
+        "type": "External"
+      }
+    }
+  ],
+  "data": [
+    {
+      "summary_name": "CallGraph",
+      "summary_data": [
+        {
+          "entity_id": 0,
+          "entity_summary": {
+            "pretty_name": "foo()",
+            "direct_callees": [],
+            "virtual_callees": [],
+            "def": {
+              "file": "t.cpp",
+              "line": 1
+            }
+          }
+        }
+      ]
+    }
+  ]
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/missing-def-file.json b/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/missing-def-file.json
new file mode 100644
index 0000000000000..de1e2ac704613
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/missing-def-file.json
@@ -0,0 +1,45 @@
+{
+  "tu_namespace": {
+    "kind": "CompilationUnit",
+    "name": "test.cpp"
+  },
+  "id_table": [
+    {
+      "id": 0,
+      "name": {
+        "namespace": [
+          {
+            "kind": "CompilationUnit",
+            "name": "test.cpp"
+          }
+        ],
+        "suffix": "",
+        "usr": "c:@F at foo#"
+      }
+    }
+  ],
+  "linkage_table": [
+    {
+      "id": 0,
+      "linkage": {
+        "type": "External"
+      }
+    }
+  ],
+  "data": [
+    {
+      "summary_name": "CallGraph",
+      "summary_data": [
+        {
+          "entity_id": 0,
+          "entity_summary": {
+            "pretty_name": "foo()",
+            "direct_callees": [],
+            "virtual_callees": [],
+            "def": {}
+          }
+        }
+      ]
+    }
+  ]
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/missing-def-line.json b/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/missing-def-line.json
new file mode 100644
index 0000000000000..2b23d0f55498f
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/missing-def-line.json
@@ -0,0 +1,47 @@
+{
+  "tu_namespace": {
+    "kind": "CompilationUnit",
+    "name": "test.cpp"
+  },
+  "id_table": [
+    {
+      "id": 0,
+      "name": {
+        "namespace": [
+          {
+            "kind": "CompilationUnit",
+            "name": "test.cpp"
+          }
+        ],
+        "suffix": "",
+        "usr": "c:@F at foo#"
+      }
+    }
+  ],
+  "linkage_table": [
+    {
+      "id": 0,
+      "linkage": {
+        "type": "External"
+      }
+    }
+  ],
+  "data": [
+    {
+      "summary_name": "CallGraph",
+      "summary_data": [
+        {
+          "entity_id": 0,
+          "entity_summary": {
+            "pretty_name": "foo()",
+            "direct_callees": [],
+            "virtual_callees": [],
+            "def": {
+              "file": "t.cpp"
+            }
+          }
+        }
+      ]
+    }
+  ]
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/missing-def.json b/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/missing-def.json
new file mode 100644
index 0000000000000..bf13b1cf96543
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/missing-def.json
@@ -0,0 +1,44 @@
+{
+  "tu_namespace": {
+    "kind": "CompilationUnit",
+    "name": "test.cpp"
+  },
+  "id_table": [
+    {
+      "id": 0,
+      "name": {
+        "namespace": [
+          {
+            "kind": "CompilationUnit",
+            "name": "test.cpp"
+          }
+        ],
+        "suffix": "",
+        "usr": "c:@F at foo#"
+      }
+    }
+  ],
+  "linkage_table": [
+    {
+      "id": 0,
+      "linkage": {
+        "type": "External"
+      }
+    }
+  ],
+  "data": [
+    {
+      "summary_name": "CallGraph",
+      "summary_data": [
+        {
+          "entity_id": 0,
+          "entity_summary": {
+            "pretty_name": "foo()",
+            "direct_callees": [],
+            "virtual_callees": []
+          }
+        }
+      ]
+    }
+  ]
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/missing-direct-callees.json b/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/missing-direct-callees.json
new file mode 100644
index 0000000000000..522c291191c0c
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/missing-direct-callees.json
@@ -0,0 +1,42 @@
+{
+  "tu_namespace": {
+    "kind": "CompilationUnit",
+    "name": "test.cpp"
+  },
+  "id_table": [
+    {
+      "id": 0,
+      "name": {
+        "namespace": [
+          {
+            "kind": "CompilationUnit",
+            "name": "test.cpp"
+          }
+        ],
+        "suffix": "",
+        "usr": "c:@F at foo#"
+      }
+    }
+  ],
+  "linkage_table": [
+    {
+      "id": 0,
+      "linkage": {
+        "type": "External"
+      }
+    }
+  ],
+  "data": [
+    {
+      "summary_name": "CallGraph",
+      "summary_data": [
+        {
+          "entity_id": 0,
+          "entity_summary": {
+            "pretty_name": "foo()"
+          }
+        }
+      ]
+    }
+  ]
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/missing-pretty-name.json b/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/missing-pretty-name.json
new file mode 100644
index 0000000000000..598361ee8c89d
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/missing-pretty-name.json
@@ -0,0 +1,40 @@
+{
+  "tu_namespace": {
+    "kind": "CompilationUnit",
+    "name": "test.cpp"
+  },
+  "id_table": [
+    {
+      "id": 0,
+      "name": {
+        "namespace": [
+          {
+            "kind": "CompilationUnit",
+            "name": "test.cpp"
+          }
+        ],
+        "suffix": "",
+        "usr": "c:@F at foo#"
+      }
+    }
+  ],
+  "linkage_table": [
+    {
+      "id": 0,
+      "linkage": {
+        "type": "External"
+      }
+    }
+  ],
+  "data": [
+    {
+      "summary_name": "CallGraph",
+      "summary_data": [
+        {
+          "entity_id": 0,
+          "entity_summary": {}
+        }
+      ]
+    }
+  ]
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/missing-virtual-callees.json b/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/missing-virtual-callees.json
new file mode 100644
index 0000000000000..9552ff43f840b
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/Inputs/CallGraph/missing-virtual-callees.json
@@ -0,0 +1,43 @@
+{
+  "tu_namespace": {
+    "kind": "CompilationUnit",
+    "name": "test.cpp"
+  },
+  "id_table": [
+    {
+      "id": 0,
+      "name": {
+        "namespace": [
+          {
+            "kind": "CompilationUnit",
+            "name": "test.cpp"
+          }
+        ],
+        "suffix": "",
+        "usr": "c:@F at foo#"
+      }
+    }
+  ],
+  "linkage_table": [
+    {
+      "id": 0,
+      "linkage": {
+        "type": "External"
+      }
+    }
+  ],
+  "data": [
+    {
+      "summary_name": "CallGraph",
+      "summary_data": [
+        {
+          "entity_id": 0,
+          "entity_summary": {
+            "pretty_name": "foo()",
+            "direct_callees": []
+          }
+        }
+      ]
+    }
+  ]
+}
diff --git a/clang/test/Analysis/Scalable/ssaf-format/call-graph-invalid-json-format.test b/clang/test/Analysis/Scalable/ssaf-format/call-graph-invalid-json-format.test
new file mode 100644
index 0000000000000..9cd3323603a44
--- /dev/null
+++ b/clang/test/Analysis/Scalable/ssaf-format/call-graph-invalid-json-format.test
@@ -0,0 +1,97 @@
+// Tests for CallGraph JSON deserialization error conditions.
+
+// RUN: not clang-ssaf-format --type=tu %S/Inputs/CallGraph/missing-pretty-name.json 2>&1 \
+// RUN:   | FileCheck %s --match-full-lines --check-prefix=PRETTY-NAME
+// PRETTY-NAME:      clang-ssaf-format: error: reading TUSummary from file '{{.*}}missing-pretty-name.json'
+// PRETTY-NAME-NEXT: reading SummaryData entries from field 'data'
+// PRETTY-NAME-NEXT: reading SummaryData entry from index '0'
+// PRETTY-NAME-NEXT: reading EntitySummary entries from field 'summary_data'
+// PRETTY-NAME-NEXT: reading EntitySummary entry from index '0'
+// PRETTY-NAME-NEXT: reading EntitySummary from field 'entity_summary'
+// PRETTY-NAME-NEXT: failed to read PrettyName from field 'pretty_name': expected JSON string
+
+// RUN: not clang-ssaf-format --type=tu %S/Inputs/CallGraph/missing-direct-callees.json 2>&1 \
+// RUN:   | FileCheck %s --match-full-lines --check-prefix=DIRECT-CALLEES
+// DIRECT-CALLEES:      clang-ssaf-format: error: reading TUSummary from file '{{.*}}missing-direct-callees.json'
+// DIRECT-CALLEES-NEXT: reading SummaryData entries from field 'data'
+// DIRECT-CALLEES-NEXT: reading SummaryData entry from index '0'
+// DIRECT-CALLEES-NEXT: reading EntitySummary entries from field 'summary_data'
+// DIRECT-CALLEES-NEXT: reading EntitySummary entry from index '0'
+// DIRECT-CALLEES-NEXT: reading EntitySummary from field 'entity_summary'
+// DIRECT-CALLEES-NEXT: failed to read DirectCallees from field 'direct_callees': expected JSON array
+
+// RUN: not clang-ssaf-format --type=tu %S/Inputs/CallGraph/invalid-direct-callee-element.json 2>&1 \
+// RUN:   | FileCheck %s --match-full-lines --check-prefix=CALLEE-ELEMENT
+// CALLEE-ELEMENT:      clang-ssaf-format: error: reading TUSummary from file '{{.*}}invalid-direct-callee-element.json'
+// CALLEE-ELEMENT-NEXT: reading SummaryData entries from field 'data'
+// CALLEE-ELEMENT-NEXT: reading SummaryData entry from index '0'
+// CALLEE-ELEMENT-NEXT: reading EntitySummary entries from field 'summary_data'
+// CALLEE-ELEMENT-NEXT: reading EntitySummary entry from index '0'
+// CALLEE-ELEMENT-NEXT: reading EntitySummary from field 'entity_summary'
+// CALLEE-ELEMENT-NEXT: reading DirectCallees from field 'direct_callees'
+// CALLEE-ELEMENT-NEXT: failed to read EntityId from index '0': expected JSON object
+
+// RUN: not clang-ssaf-format --type=tu %S/Inputs/CallGraph/invalid-direct-callee-id.json 2>&1 \
+// RUN:   | FileCheck %s --match-full-lines --check-prefix=CALLEE-ID
+// CALLEE-ID:      clang-ssaf-format: error: reading TUSummary from file '{{.*}}invalid-direct-callee-id.json'
+// CALLEE-ID-NEXT: reading SummaryData entries from field 'data'
+// CALLEE-ID-NEXT: reading SummaryData entry from index '0'
+// CALLEE-ID-NEXT: reading EntitySummary entries from field 'summary_data'
+// CALLEE-ID-NEXT: reading EntitySummary entry from index '0'
+// CALLEE-ID-NEXT: reading EntitySummary from field 'entity_summary'
+// CALLEE-ID-NEXT: reading DirectCallees from field 'direct_callees'
+// CALLEE-ID-NEXT: reading EntityId from index '0'
+// CALLEE-ID-NEXT: failed to read EntityId: expected JSON object with a single '@' key mapped to a number (unsigned 64-bit integer)
+
+// RUN: not clang-ssaf-format --type=tu %S/Inputs/CallGraph/missing-virtual-callees.json 2>&1 \
+// RUN:   | FileCheck %s --match-full-lines --check-prefix=VIRTUAL-CALLEES
+// VIRTUAL-CALLEES:      clang-ssaf-format: error: reading TUSummary from file '{{.*}}missing-virtual-callees.json'
+// VIRTUAL-CALLEES-NEXT: reading SummaryData entries from field 'data'
+// VIRTUAL-CALLEES-NEXT: reading SummaryData entry from index '0'
+// VIRTUAL-CALLEES-NEXT: reading EntitySummary entries from field 'summary_data'
+// VIRTUAL-CALLEES-NEXT: reading EntitySummary entry from index '0'
+// VIRTUAL-CALLEES-NEXT: reading EntitySummary from field 'entity_summary'
+// VIRTUAL-CALLEES-NEXT: failed to read VirtualCallees from field 'virtual_callees': expected JSON array
+
+// RUN: not clang-ssaf-format --type=tu %S/Inputs/CallGraph/missing-def.json 2>&1 \
+// RUN:   | FileCheck %s --match-full-lines --check-prefix=DEF
+// DEF:      clang-ssaf-format: error: reading TUSummary from file '{{.*}}missing-def.json'
+// DEF-NEXT: reading SummaryData entries from field 'data'
+// DEF-NEXT: reading SummaryData entry from index '0'
+// DEF-NEXT: reading EntitySummary entries from field 'summary_data'
+// DEF-NEXT: reading EntitySummary entry from index '0'
+// DEF-NEXT: reading EntitySummary from field 'entity_summary'
+// DEF-NEXT: failed to read SourceLocation from field 'def': expected JSON object
+
+// RUN: not clang-ssaf-format --type=tu %S/Inputs/CallGraph/missing-def-file.json 2>&1 \
+// RUN:   | FileCheck %s --match-full-lines --check-prefix=DEF-FILE
+// DEF-FILE:      clang-ssaf-format: error: reading TUSummary from file '{{.*}}missing-def-file.json'
+// DEF-FILE-NEXT: reading SummaryData entries from field 'data'
+// DEF-FILE-NEXT: reading SummaryData entry from index '0'
+// DEF-FILE-NEXT: reading EntitySummary entries from field 'summary_data'
+// DEF-FILE-NEXT: reading EntitySummary entry from index '0'
+// DEF-FILE-NEXT: reading EntitySummary from field 'entity_summary'
+// DEF-FILE-NEXT: reading SourceLocation from field 'def'
+// DEF-FILE-NEXT: failed to read File from field 'file': expected JSON string
+
+// RUN: not clang-ssaf-format --type=tu %S/Inputs/CallGraph/missing-def-line.json 2>&1 \
+// RUN:   | FileCheck %s --match-full-lines --check-prefix=DEF-LINE
+// DEF-LINE:      clang-ssaf-format: error: reading TUSummary from file '{{.*}}missing-def-line.json'
+// DEF-LINE-NEXT: reading SummaryData entries from field 'data'
+// DEF-LINE-NEXT: reading SummaryData entry from index '0'
+// DEF-LINE-NEXT: reading EntitySummary entries from field 'summary_data'
+// DEF-LINE-NEXT: reading EntitySummary entry from index '0'
+// DEF-LINE-NEXT: reading EntitySummary from field 'entity_summary'
+// DEF-LINE-NEXT: reading SourceLocation from field 'def'
+// DEF-LINE-NEXT: failed to read Line from field 'line': expected JSON number
+
+// RUN: not clang-ssaf-format --type=tu %S/Inputs/CallGraph/missing-def-col.json 2>&1 \
+// RUN:   | FileCheck %s --match-full-lines --check-prefix=DEF-COL
+// DEF-COL:      clang-ssaf-format: error: reading TUSummary from file '{{.*}}missing-def-col.json'
+// DEF-COL-NEXT: reading SummaryData entries from field 'data'
+// DEF-COL-NEXT: reading SummaryData entry from index '0'
+// DEF-COL-NEXT: reading EntitySummary entries from field 'summary_data'
+// DEF-COL-NEXT: reading EntitySummary entry from index '0'
+// DEF-COL-NEXT: reading EntitySummary from field 'entity_summary'
+// DEF-COL-NEXT: reading SourceLocation from field 'def'
+// DEF-COL-NEXT: failed to read Column from field 'col': expected JSON number



More information about the llvm-commits mailing list