[clang] [clang][ssaf] Implement JSONFormat (PR #180021)

Aviral Goel via cfe-commits cfe-commits at lists.llvm.org
Sat Feb 14 09:00:54 PST 2026


================
@@ -0,0 +1,1374 @@
+//===- unittests/Analysis/Scalable/JSONFormatTest.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
+//
+//===----------------------------------------------------------------------===//
+//
+// Unit tests for SSAF JSON serialization format reading and writing.
+//
+//===----------------------------------------------------------------------===//
+
+
+#include "clang/Analysis/Scalable/Serialization/JSONFormat.h"
+#include "clang/Analysis/Scalable/TUSummary/TUSummary.h"
+#include "llvm/ADT/IntrusiveRefCntPtr.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/Path.h"
+#include "llvm/Support/Registry.h"
+#include "llvm/Support/VirtualFileSystem.h"
+#include "llvm/Support/raw_ostream.h"
+#include "llvm/Testing/Support/Error.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include <memory>
+#include <string>
+#include <vector>
+
+using namespace clang::ssaf;
+using namespace llvm;
+using ::testing::AllOf;
+using ::testing::HasSubstr;
+
+namespace {
+
+// ============================================================================
+// Test Analysis - Simple analysis for testing JSON serialization
+// ============================================================================
+
+struct TestAnalysis : EntitySummary {
+  TestAnalysis() : EntitySummary(SummaryName("test_summary")) {}
+  std::vector<std::pair<EntityId, EntityId>> Pairs;
+};
+
+static json::Object
+serializeTestAnalysis(const EntitySummary &Summary,
+                      const JSONFormat::EntityIdConverter &Converter) {
+  const auto &TA = static_cast<const TestAnalysis &>(Summary);
+  json::Array PairsArray;
+  for (const auto &[First, Second] : TA.Pairs) {
+    PairsArray.push_back(json::Object{
+        {"first", Converter.toJSON(First)},
+        {"second", Converter.toJSON(Second)},
+    });
+  }
+  return json::Object{{"pairs", std::move(PairsArray)}};
+}
+
+static Expected<std::unique_ptr<EntitySummary>>
+deserializeTestAnalysis(const json::Object &Obj, EntityIdTable &IdTable,
+                        const JSONFormat::EntityIdConverter &Converter) {
+  auto Result = std::make_unique<TestAnalysis>();
+  const json::Array *PairsArray = Obj.getArray("pairs");
+  if (!PairsArray)
+    return createStringError(inconvertibleErrorCode(),
+                             "missing or invalid field 'pairs'");
+  for (const auto &[Index, Value] : llvm::enumerate(*PairsArray)) {
+    const json::Object *Pair = Value.getAsObject();
+    if (!Pair)
+      return createStringError(
+          inconvertibleErrorCode(),
+          "pairs element at index %zu is not a JSON object", Index);
+    auto FirstOpt = Pair->getInteger("first");
+    if (!FirstOpt)
+      return createStringError(
+          inconvertibleErrorCode(),
+          "missing or invalid 'first' field at index '%zu'", Index);
+    auto SecondOpt = Pair->getInteger("second");
+    if (!SecondOpt)
+      return createStringError(
+          inconvertibleErrorCode(),
+          "missing or invalid 'second' field at index '%zu'", Index);
+    Result->Pairs.emplace_back(Converter.fromJSON(*FirstOpt),
+                               Converter.fromJSON(*SecondOpt));
+  }
+  return std::move(Result);
+}
+
+struct TestAnalysisFormatInfo : JSONFormat::FormatInfo {
+  TestAnalysisFormatInfo()
+      : JSONFormat::FormatInfo(SummaryName("test_summary"),
+                               serializeTestAnalysis, deserializeTestAnalysis) {
+  }
+};
+
+static llvm::Registry<JSONFormat::FormatInfo>::Add<TestAnalysisFormatInfo>
+    RegisterTestAnalysis("TestAnalysis", "Format info for test analysis data");
+
+// ============================================================================
+// Test Fixture
+// ============================================================================
+
+class JSONFormatTest : public ::testing::Test {
+protected:
+  SmallString<128> TestDir;
+
+  void SetUp() override {
+    std::error_code EC =
+        sys::fs::createUniqueDirectory("json-format-test", TestDir);
+    ASSERT_FALSE(EC) << "Failed to create temp directory: " << EC.message();
+  }
+
+  void TearDown() override { sys::fs::remove_directories(TestDir); }
+
+  llvm::Expected<TUSummary> readJSON(StringRef JSON,
+                                     StringRef Filename = "test.json") {
+    SmallString<128> FilePath = TestDir;
+    sys::path::append(FilePath, Filename);
+
+    std::error_code EC;
+    raw_fd_ostream OS(FilePath, EC);
+    EXPECT_FALSE(EC) << "Failed to create file: " << EC.message();
+    OS << JSON;
+    OS.close();
+
+    return JSONFormat(vfs::getRealFileSystem()).readTUSummary(FilePath);
+  }
+
+  void readWriteJSON(StringRef InputJSON) {
+    // Read the input JSON
+    auto Summary1 = readJSON(InputJSON, "input.json");
+    ASSERT_THAT_EXPECTED(Summary1, Succeeded());
+
+    // Write to first output file
+    SmallString<128> Output1Path = TestDir;
+    sys::path::append(Output1Path, "output1.json");
+
+    JSONFormat Format(vfs::getRealFileSystem());
+    auto WriteErr1 = Format.writeTUSummary(*Summary1, Output1Path);
+    ASSERT_THAT_ERROR(std::move(WriteErr1), Succeeded());
+
+    // Read back from first output
+    auto Summary2 = Format.readTUSummary(Output1Path);
+    ASSERT_THAT_EXPECTED(Summary2, Succeeded());
+
+    // Write to second output file
+    SmallString<128> Output2Path = TestDir;
+    sys::path::append(Output2Path, "output2.json");
+
+    auto WriteErr2 = Format.writeTUSummary(*Summary2, Output2Path);
+    ASSERT_THAT_ERROR(std::move(WriteErr2), Succeeded());
+
+    // Compare the two output files byte-by-byte
----------------
aviralg wrote:

This will not happen now. The comparison method, `readWriteCompareTUSummary`, has been redesigned to compare the supplied TUSummary JSON against the version serialized by JSONFormat. If the TUSummary JSON supplied in the input is not empty, then any catastrophic bug causing readTUSummary/writeTUSummary to return an empty data structure will lead to a test failure.

https://github.com/llvm/llvm-project/pull/180021


More information about the cfe-commits mailing list