[clang] [clang][ssaf] Implement JSONFormat (PR #180021)
Jan Korous via cfe-commits
cfe-commits at lists.llvm.org
Wed Feb 11 18:02:44 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
----------------
jkorous-apple wrote:
I want to make sure that our happy path tests will fail in case of a catastrophic bug, such as `readTUSummary` or `writeTUSummary` always returning empty data structure.
Do we have coverage for such scenario? If not, can we add some assertion about non-emptiness of `Summary1` or `Summary2` and `Buffer1` or `Buffer2`?
https://github.com/llvm/llvm-project/pull/180021
More information about the cfe-commits
mailing list