[clang] [clang][ssaf] Implement JSONFormat (PR #180021)
Balázs Benics via cfe-commits
cfe-commits at lists.llvm.org
Thu Feb 12 08:47:13 PST 2026
================
@@ -0,0 +1,966 @@
+#include "clang/Analysis/Scalable/Serialization/JSONFormat.h"
+#include "clang/Analysis/Scalable/TUSummary/TUSummary.h"
+
+#include "llvm/ADT/StringExtras.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/FormatVariadic.h"
+#include "llvm/Support/JSON.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/Path.h"
+#include "llvm/Support/Registry.h"
+
+using namespace clang::ssaf;
+
+using Array = llvm::json::Array;
+using Object = llvm::json::Object;
+using Value = llvm::json::Value;
+
+//----------------------------------------------------------------------------
+// ErrorBuilder - Fluent API for constructing contextual errors
+//----------------------------------------------------------------------------
+
+namespace {
+
+class ErrorBuilder {
+private:
+ std::error_code Code;
+ std::vector<std::string> ContextStack;
+
+ // Private constructor - only accessible via static factories
+ explicit ErrorBuilder(std::error_code EC) : Code(EC) {}
+
+ // Helper: Format message and add to context stack
+ template <typename... Args>
+ void addFormattedContext(const char *Fmt, Args &&...ArgVals) {
+ std::string Message =
+ llvm::formatv(Fmt, std::forward<Args>(ArgVals)...).str();
+ ContextStack.push_back(std::move(Message));
+ }
+
+public:
+ // Static factory: Create new error from error code and formatted message
+ template <typename... Args>
+ static ErrorBuilder create(std::error_code EC, const char *Fmt,
+ Args &&...ArgVals) {
+ ErrorBuilder Builder(EC);
+ Builder.addFormattedContext(Fmt, std::forward<Args>(ArgVals)...);
+ return Builder;
+ }
+
+ // Convenience overload for std::errc
+ template <typename... Args>
+ static ErrorBuilder create(std::errc EC, const char *Fmt, Args &&...ArgVals) {
+ return create(std::make_error_code(EC), Fmt,
+ std::forward<Args>(ArgVals)...);
+ }
+
+ // Static factory: Wrap existing error and optionally add context
+ static ErrorBuilder wrap(llvm::Error E) {
+ if (!E) {
+ llvm::consumeError(std::move(E));
+ // Return builder with generic error code for success case
+ return ErrorBuilder(std::make_error_code(std::errc::invalid_argument));
+ }
+
+ std::error_code EC;
+ std::string ErrorMsg;
+
+ llvm::handleAllErrors(std::move(E), [&](const llvm::ErrorInfoBase &EI) {
+ EC = EI.convertToErrorCode();
+ ErrorMsg = EI.message();
+ });
+
+ ErrorBuilder Builder(EC);
+ if (!ErrorMsg.empty()) {
+ Builder.ContextStack.push_back(std::move(ErrorMsg));
+ }
+ return Builder;
+ }
+
+ // Add context (plain string)
+ ErrorBuilder &context(const char *Msg) {
+ ContextStack.push_back(Msg);
+ return *this;
+ }
+
+ // Add context (formatted string)
+ template <typename... Args>
+ ErrorBuilder &context(const char *Fmt, Args &&...ArgVals) {
+ addFormattedContext(Fmt, std::forward<Args>(ArgVals)...);
+ return *this;
+ }
+
+ // Build the final error
+ llvm::Error build() {
+ if (ContextStack.empty())
+ return llvm::Error::success();
+
+ // Reverse the context stack so that the most recent context appears first
+ // and the wrapped error (if any) appears last
+ std::vector<std::string> ReversedContext(ContextStack.rbegin(),
+ ContextStack.rend());
+ std::string FinalMessage = llvm::join(ReversedContext, "\n");
+ return llvm::createStringError(Code, "%s", FinalMessage.c_str());
+ }
+};
+
+} // namespace
+
+//----------------------------------------------------------------------------
+// Error Message Constants
+//----------------------------------------------------------------------------
+
+namespace {
+
+namespace ErrorMessages {
+
+constexpr const char *FailedToReadFile = "failed to read file '{0}': {1}";
+constexpr const char *FailedToWriteFile = "failed to write file '{0}': {1}";
+constexpr const char *FileNotFound = "file does not exist";
+constexpr const char *FileIsDirectory = "path is a directory, not a file";
+constexpr const char *FileIsNotJSON =
+ "file does not end with '.json' extension";
+constexpr const char *FileExists = "file already exists";
+constexpr const char *ParentDirectoryNotFound =
+ "parent directory does not exist";
+
+constexpr const char *ReadingFromField = "reading {0} from field '{1}'";
+constexpr const char *WritingToField = "writing {0} to field '{1}'";
+constexpr const char *ReadingFromIndex = "reading {0} from index '{1}'";
+constexpr const char *WritingToIndex = "writing {0} to index '{1}'";
+constexpr const char *ReadingFromFile = "reading {0} from file '{1}'";
+constexpr const char *WritingToFile = "writing {0} to file '{1}'";
+
+constexpr const char *FailedInsertionOnDuplication =
+ "failed to insert {0} at index '{1}': encountered duplicate {2} '{3}'";
+
+constexpr const char *FailedToReadObject =
+ "failed to read {0}: expected JSON {1}";
+constexpr const char *FailedToReadObjectAtField =
+ "failed to read {0} from field '{1}': expected JSON {2}";
+constexpr const char *FailedToReadObjectAtIndex =
+ "failed to read {0} from index '{1}': expected JSON {2}";
+
+constexpr const char *FailedToDeserializeEntitySummary =
+ "failed to deserialize EntitySummary: no FormatInfo registered for summary "
+ "'{0}'";
+constexpr const char *FailedToSerializeEntitySummary =
+ "failed to serialize EntitySummary: no FormatInfo registered for summary "
+ "'{0}'";
+
+constexpr const char *InvalidBuildNamespaceKind =
+ "invalid 'kind' BuildNamespaceKind value '{0}'";
+
+} // namespace ErrorMessages
+
+} // namespace
+
+//----------------------------------------------------------------------------
+// JSON Reader and Writer
+//----------------------------------------------------------------------------
+
+namespace {
+
+llvm::Expected<Value> readJSON(llvm::StringRef Path) {
+ if (!llvm::sys::fs::exists(Path)) {
+ return ErrorBuilder::create(std::errc::no_such_file_or_directory,
+ ErrorMessages::FailedToReadFile, Path,
+ ErrorMessages::FileNotFound)
+ .build();
+ }
+
+ if (llvm::sys::fs::is_directory(Path)) {
+ return ErrorBuilder::create(std::errc::is_a_directory,
+ ErrorMessages::FailedToReadFile, Path,
+ ErrorMessages::FileIsDirectory)
+ .build();
+ }
+
+ if (!Path.ends_with_insensitive(".json")) {
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedToReadFile, Path,
+ ErrorMessages::FileIsNotJSON)
+ .build();
+ }
+
+ auto BufferOrError = llvm::MemoryBuffer::getFile(Path);
+ if (!BufferOrError) {
+ const std::error_code EC = BufferOrError.getError();
+ return ErrorBuilder::create(EC, ErrorMessages::FailedToReadFile, Path,
+ EC.message())
+ .build();
+ }
+
+ return llvm::json::parse(BufferOrError.get()->getBuffer());
+}
+
+llvm::Error writeJSON(Value &&Value, llvm::StringRef Path) {
+ if (llvm::sys::fs::exists(Path)) {
+ return ErrorBuilder::create(std::errc::file_exists,
+ ErrorMessages::FailedToWriteFile, Path,
+ ErrorMessages::FileExists)
+ .build();
+ }
+
+ llvm::StringRef Dir = llvm::sys::path::parent_path(Path);
+ if (!Dir.empty() && !llvm::sys::fs::is_directory(Dir)) {
+ return ErrorBuilder::create(std::errc::no_such_file_or_directory,
+ ErrorMessages::FailedToWriteFile, Path,
+ ErrorMessages::ParentDirectoryNotFound)
+ .build();
+ }
+
+ if (!Path.ends_with_insensitive(".json")) {
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedToWriteFile, Path,
+ ErrorMessages::FileIsNotJSON)
+ .build();
+ }
+
+ std::error_code EC;
+ llvm::raw_fd_ostream OutStream(Path, EC, llvm::sys::fs::OF_Text);
+
+ if (EC) {
+ return ErrorBuilder::create(EC, ErrorMessages::FailedToWriteFile, Path,
+ EC.message())
+ .build();
+ }
+
+ OutStream << llvm::formatv("{0:2}\n", Value);
+ OutStream.flush();
+
+ if (OutStream.has_error()) {
+ return ErrorBuilder::create(OutStream.error(),
+ ErrorMessages::FailedToWriteFile, Path,
+ OutStream.error().message())
+ .build();
+ }
+
+ return llvm::Error::success();
+}
+
+} // namespace
+
+//----------------------------------------------------------------------------
+// JSONFormat Constructor
+//----------------------------------------------------------------------------
+
+JSONFormat::JSONFormat(llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS)
+ : SerializationFormat(FS) {
+ for (const auto &FormatInfoEntry : llvm::Registry<FormatInfo>::entries()) {
+ std::unique_ptr<FormatInfo> Info = FormatInfoEntry.instantiate();
+ bool Inserted = FormatInfos.try_emplace(Info->ForSummary, *Info).second;
+ if (!Inserted) {
+ llvm::report_fatal_error(
+ "FormatInfo is already registered for summary: " +
+ Info->ForSummary.str());
+ }
+ }
+}
+
+//----------------------------------------------------------------------------
+// SummaryName
+//----------------------------------------------------------------------------
+
+namespace {
+
+SummaryName summaryNameFromJSON(llvm::StringRef SummaryNameStr) {
+ return SummaryName(SummaryNameStr.str());
+}
+
+llvm::StringRef summaryNameToJSON(const SummaryName &SN) { return SN.str(); }
+
+} // namespace
+
+//----------------------------------------------------------------------------
+// EntityId
+//----------------------------------------------------------------------------
+
+EntityId JSONFormat::entityIdFromJSON(const uint64_t EntityIdIndex) const {
+ return SerializationFormat::makeEntityId(static_cast<size_t>(EntityIdIndex));
+}
+
+uint64_t JSONFormat::entityIdToJSON(EntityId EI) const {
+ return static_cast<uint64_t>(SerializationFormat::getEntityIdIndex(EI));
+}
+
+//----------------------------------------------------------------------------
+// BuildNamespaceKind
+//----------------------------------------------------------------------------
+
+llvm::Expected<BuildNamespaceKind> JSONFormat::buildNamespaceKindFromJSON(
+ llvm::StringRef BuildNamespaceKindStr) const {
+ auto OptBuildNamespaceKind = parseBuildNamespaceKind(BuildNamespaceKindStr);
+ if (!OptBuildNamespaceKind) {
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::InvalidBuildNamespaceKind,
+ BuildNamespaceKindStr)
+ .build();
+ }
+
+ return *OptBuildNamespaceKind;
+}
+
+namespace {
+
+llvm::StringRef buildNamespaceKindToJSON(BuildNamespaceKind BNK) {
+ return toString(BNK);
+}
+
+} // namespace
+
+//----------------------------------------------------------------------------
+// BuildNamespace
+//----------------------------------------------------------------------------
+
+llvm::Expected<BuildNamespace>
+JSONFormat::buildNamespaceFromJSON(const Object &BuildNamespaceObject) const {
+ auto OptBuildNamespaceKindStr = BuildNamespaceObject.getString("kind");
+ if (!OptBuildNamespaceKindStr) {
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedToReadObjectAtField,
+ "BuildNamespaceKind", "kind", "string")
+ .build();
+ }
+
+ auto ExpectedKind = buildNamespaceKindFromJSON(*OptBuildNamespaceKindStr);
+ if (!ExpectedKind)
+ return ErrorBuilder::wrap(ExpectedKind.takeError())
+ .context(ErrorMessages::ReadingFromField, "BuildNamespaceKind", "kind")
+ .build();
+
+ auto OptNameStr = BuildNamespaceObject.getString("name");
+ if (!OptNameStr) {
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedToReadObjectAtField,
+ "BuildNamespaceName", "name", "string")
+ .build();
+ }
+
+ return {BuildNamespace(*ExpectedKind, *OptNameStr)};
+}
+
+Object JSONFormat::buildNamespaceToJSON(const BuildNamespace &BN) const {
+ Object Result;
+ Result["kind"] = buildNamespaceKindToJSON(getBuildNamespaceKind(BN));
+ Result["name"] = getBuildNamespaceName(BN);
+ return Result;
+}
+
+//----------------------------------------------------------------------------
+// NestedBuildNamespace
+//----------------------------------------------------------------------------
+
+llvm::Expected<NestedBuildNamespace> JSONFormat::nestedBuildNamespaceFromJSON(
+ const Array &NestedBuildNamespaceArray) const {
+ std::vector<BuildNamespace> Namespaces;
+
+ size_t NamespaceCount = NestedBuildNamespaceArray.size();
+ Namespaces.reserve(NamespaceCount);
+
+ for (const auto &[Index, BuildNamespaceValue] :
+ llvm::enumerate(NestedBuildNamespaceArray)) {
+
+ const Object *BuildNamespaceObject = BuildNamespaceValue.getAsObject();
+ if (!BuildNamespaceObject) {
+ return ErrorBuilder::create(std::errc::invalid_argument,
+ ErrorMessages::FailedToReadObjectAtIndex,
+ "BuildNamespace", Index, "object")
+ .build();
+ }
+
+ auto ExpectedBuildNamespace = buildNamespaceFromJSON(*BuildNamespaceObject);
+ if (!ExpectedBuildNamespace) {
+ return ErrorBuilder::wrap(ExpectedBuildNamespace.takeError())
+ .context(ErrorMessages::ReadingFromIndex, "BuildNamespace", Index)
+ .build();
----------------
steakhal wrote:
This is uncovered by unit tests, and I think we should.
https://github.com/llvm/llvm-project/pull/180021
More information about the cfe-commits
mailing list