[clang] [clang][ssaf] Implement JSONFormat (PR #180021)
Aviral Goel via cfe-commits
cfe-commits at lists.llvm.org
Mon Feb 16 08:38:48 PST 2026
================
@@ -0,0 +1,1817 @@
+//===- 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/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/raw_ostream.h"
+#include "llvm/Testing/Support/Error.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include <algorithm>
+#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 PairsEntitySummaryForJSONFormatTest final : EntitySummary {
+
+ SummaryName getSummaryName() const override {
+ return SummaryName("PairsEntitySummaryForJSONFormatTest");
+ }
+
+ std::vector<std::pair<EntityId, EntityId>> Pairs;
+};
+
+static json::Object serializePairsEntitySummaryForJSONFormatTest(
+ const EntitySummary &Summary,
+ const JSONFormat::EntityIdConverter &Converter) {
+ const auto &TA =
+ static_cast<const PairsEntitySummaryForJSONFormatTest &>(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>>
+deserializePairsEntitySummaryForJSONFormatTest(
+ const json::Object &Obj, EntityIdTable &IdTable,
+ const JSONFormat::EntityIdConverter &Converter) {
+ auto Result = std::make_unique<PairsEntitySummaryForJSONFormatTest>();
+ 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 PairsEntitySummaryForJSONFormatTestFormatInfo : JSONFormat::FormatInfo {
+ PairsEntitySummaryForJSONFormatTestFormatInfo()
+ : JSONFormat::FormatInfo(
+ SummaryName("PairsEntitySummaryForJSONFormatTest"),
+ serializePairsEntitySummaryForJSONFormatTest,
+ deserializePairsEntitySummaryForJSONFormatTest) {}
+};
+
+static llvm::Registry<JSONFormat::FormatInfo>::Add<
+ PairsEntitySummaryForJSONFormatTestFormatInfo>
+ RegisterPairsEntitySummaryForJSONFormatTest(
+ "PairsEntitySummaryForJSONFormatTest",
+ "Format info for PairsArrayEntitySummary");
+
+// ============================================================================
+// Test Fixture
+// ============================================================================
+
+class JSONFormatTest : public ::testing::Test {
+public:
+ using PathString = SmallString<128>;
+
+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); }
+
+ PathString makePath(StringRef FileOrDirectoryName) const {
+ PathString FullPath = TestDir;
+ sys::path::append(FullPath, FileOrDirectoryName);
+
+ return FullPath;
+ }
+
+ PathString makePath(StringRef Dir, StringRef FileName) const {
+ PathString FullPath = TestDir;
+ sys::path::append(FullPath, Dir, FileName);
+
+ return FullPath;
+ }
+
+ Expected<PathString> makeDirectory(StringRef DirectoryName) const {
+ PathString DirPath = makePath(DirectoryName);
+
+ std::error_code EC = sys::fs::create_directory(DirPath);
+ if (EC) {
+ return createStringError(EC, "Failed to create directory '%s': %s",
+ DirPath.c_str(), EC.message().c_str());
+ }
+
+ return DirPath;
+ }
+
+ Expected<PathString> makeSymlink(StringRef TargetFileName,
+ StringRef SymlinkFileName) const {
+ PathString TargetPath = makePath(TargetFileName);
+ PathString SymlinkPath = makePath(SymlinkFileName);
+
+ std::error_code EC = sys::fs::create_link(TargetPath, SymlinkPath);
+ if (EC) {
+ return createStringError(EC, "Failed to create symlink '%s' -> '%s': %s",
+ SymlinkPath.c_str(), TargetPath.c_str(),
+ EC.message().c_str());
+ }
+
+ return SymlinkPath;
+ }
+
+ llvm::Error setPermission(StringRef FileName,
+ const sys::fs::perms Perms) const {
+ PathString Path = makePath(FileName);
+
+ std::error_code EC = sys::fs::setPermissions(Path, Perms);
+ if (EC) {
+ return createStringError(EC, "Failed to set permissions on '%s': %s",
+ Path.c_str(), EC.message().c_str());
+ }
+
+ return llvm::Error::success();
+ }
+
+ Expected<json::Value> readJSONFromFile(StringRef FileName) const {
+ PathString FilePath = makePath(FileName);
+
+ auto BufferOrError = MemoryBuffer::getFile(FilePath);
+ if (!BufferOrError) {
+ return createStringError(BufferOrError.getError(),
+ "Failed to read file: %s", FilePath.c_str());
+ }
+
+ Expected<json::Value> ExpectedValue =
+ json::parse(BufferOrError.get()->getBuffer());
+ if (!ExpectedValue)
+ return ExpectedValue.takeError();
+
+ return *ExpectedValue;
+ }
+
+ Expected<PathString> writeJSON(StringRef JSON, StringRef FileName) const {
+ PathString FilePath = makePath(FileName);
+
+ std::error_code EC;
+ raw_fd_ostream OS(FilePath, EC);
+ if (EC) {
+ return createStringError(EC, "Failed to create file '%s': %s",
+ FilePath.c_str(), EC.message().c_str());
+ }
+
+ OS << JSON;
+ OS.close();
+
+ if (OS.has_error()) {
+ return createStringError(OS.error(), "Failed to write to file '%s': %s",
+ FilePath.c_str(), OS.error().message().c_str());
+ }
+
+ return FilePath;
+ }
+
+ llvm::Expected<TUSummary> readTUSummaryFromFile(StringRef FileName) const {
+ PathString FilePath = makePath(FileName);
+
+ return JSONFormat().readTUSummary(FilePath);
+ }
+
+ llvm::Expected<TUSummary>
+ readTUSummaryFromString(StringRef JSON,
+ StringRef FileName = "test.json") const {
+ auto ExpectedFilePath = writeJSON(JSON, FileName);
+ if (!ExpectedFilePath)
+ return ExpectedFilePath.takeError();
+
+ return readTUSummaryFromFile(FileName);
+ }
+
+ llvm::Error writeTUSummary(const TUSummary &Summary,
+ StringRef FileName) const {
+ PathString FilePath = makePath(FileName);
+
+ return JSONFormat().writeTUSummary(Summary, FilePath);
+ }
+
+ // Normalize TUSummary JSON by sorting id_table by id field
+ static Expected<json::Value> normalizeTUSummaryJSON(json::Value Val) {
+ auto *Obj = Val.getAsObject();
+ if (!Obj) {
+ return createStringError(
+ inconvertibleErrorCode(),
+ "Cannot normalize TUSummary JSON: expected an object");
+ }
+
+ auto *IDTable = Obj->getArray("id_table");
+ if (!IDTable) {
+ return createStringError(inconvertibleErrorCode(),
+ "Cannot normalize TUSummary JSON: 'id_table' "
+ "field is either missing or has the wrong type");
+ }
+
+ // Sort id_table entries by the "id" field to ensure deterministic ordering
+ // for comparison
+ std::sort(IDTable->begin(), IDTable->end(),
+ [](const json::Value &A, const json::Value &B) {
+ const auto *AObj = A.getAsObject();
+ const auto *BObj = B.getAsObject();
+ if (!AObj || !BObj)
+ return false;
+
+ auto AID = AObj->getInteger("id");
+ auto BID = BObj->getInteger("id");
+ if (!AID || !BID)
+ return false;
+
+ return *AID < *BID;
+ });
----------------
aviralg wrote:
Fixed in https://github.com/llvm/llvm-project/pull/180021/commits/2a8be113345f614387679fbffbd0c63c9a167e08
https://github.com/llvm/llvm-project/pull/180021
More information about the cfe-commits
mailing list