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 <[email protected]> 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 <[email protected]> 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@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@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@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@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@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@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@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@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@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 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
