[clang] d08ebbe - [clang][ssaf] Add CallGraph summary and extractor (#188753)
via cfe-commits
cfe-commits at lists.llvm.org
Tue Mar 31 06:03:09 PDT 2026
Author: Balázs Benics
Date: 2026-03-31T14:03:02+01:00
New Revision: d08ebbe8eba1dd2453a00ae0a0a6bd04b0d88d51
URL: https://github.com/llvm/llvm-project/commit/d08ebbe8eba1dd2453a00ae0a0a6bd04b0d88d51
DIFF: https://github.com/llvm/llvm-project/commit/d08ebbe8eba1dd2453a00ae0a0a6bd04b0d88d51.diff
LOG: [clang][ssaf] Add CallGraph summary and extractor (#188753)
rdar://170258016
Added:
clang/include/clang/ScalableStaticAnalysisFramework/Analyses/CallGraph/CallGraphSummary.h
clang/lib/ScalableStaticAnalysisFramework/Analyses/CallGraph/CallGraphExtractor.cpp
clang/unittests/ScalableStaticAnalysisFramework/Analyses/CallGraph/CallGraphExtractorTest.cpp
Modified:
clang/include/clang/ScalableStaticAnalysisFramework/SSAFBuiltinForceLinker.h
clang/lib/Driver/CMakeLists.txt
clang/lib/FrontendTool/CMakeLists.txt
clang/lib/ScalableStaticAnalysisFramework/Analyses/CMakeLists.txt
clang/tools/clang-ssaf-format/CMakeLists.txt
clang/tools/clang-ssaf-linker/CMakeLists.txt
clang/unittests/ScalableStaticAnalysisFramework/CMakeLists.txt
clang/unittests/ScalableStaticAnalysisFramework/Registries/SummaryExtractorRegistryTest.cpp
llvm/utils/gn/secondary/clang/lib/Driver/BUILD.gn
llvm/utils/gn/secondary/clang/lib/FrontendTool/BUILD.gn
llvm/utils/gn/secondary/clang/lib/ScalableStaticAnalysisFramework/Analyses/BUILD.gn
llvm/utils/gn/secondary/clang/unittests/ScalableStaticAnalysisFramework/BUILD.gn
Removed:
################################################################################
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/CallGraph/CallGraphSummary.h b/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/CallGraph/CallGraphSummary.h
new file mode 100644
index 0000000000000..ad70218d01614
--- /dev/null
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/CallGraph/CallGraphSummary.h
@@ -0,0 +1,53 @@
+//===- CallGraphSummary.h ---------------------------------------*- C++ -*-===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_ANALYSES_CALLGRAPH_CALLGRAPHSUMMARY_H
+#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_ANALYSES_CALLGRAPH_CALLGRAPHSUMMARY_H
+
+#include "clang/ScalableStaticAnalysisFramework/Core/Model/EntityId.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Model/SummaryName.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/TUSummary/EntitySummary.h"
+#include <set>
+
+namespace clang::ssaf {
+
+/// Summary of direct call-graph edges for a single function entity.
+///
+/// Represents a function definition, and information about its callees.
+///
+/// \bug Indirect calls (e.g. function pointers) are not represented.
+/// \bug ObjCMessageExprs are not represented.
+/// \bug Primary template functions are not represented.
+struct CallGraphSummary final : public EntitySummary {
+ struct Location {
+ std::string File;
+ unsigned Line;
+ unsigned Column;
+ };
+
+ SummaryName getSummaryName() const override {
+ return SummaryName("CallGraph");
+ }
+
+ /// Represents the location of the function.
+ Location Definition = {};
+
+ /// The set of direct callees of this function.
+ std::set<EntityId> DirectCallees;
+
+ /// The set of virtual callees of this function.
+ std::set<EntityId> VirtualCallees;
+
+ /// A human-readable name of the function.
+ /// This is not guaranteed to be accurate or unique.
+ std::string PrettyName;
+};
+
+} // namespace clang::ssaf
+
+#endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_ANALYSES_CALLGRAPH_CALLGRAPHSUMMARY_H
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/SSAFBuiltinForceLinker.h b/clang/include/clang/ScalableStaticAnalysisFramework/SSAFBuiltinForceLinker.h
index 2f144b92a1a94..707573ce34e46 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/SSAFBuiltinForceLinker.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/SSAFBuiltinForceLinker.h
@@ -20,6 +20,8 @@
#ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_SSAFBUILTINFORCELINKER_H
#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_SSAFBUILTINFORCELINKER_H
+// TODO: Move these to the `clang::ssaf` namespace.
+
// This anchor is used to force the linker to link the JSONFormat registration.
extern volatile int SSAFJSONFormatAnchorSource;
[[maybe_unused]] static int SSAFJSONFormatAnchorDestination =
@@ -30,4 +32,9 @@ extern volatile int SSAFAnalysisRegistryAnchorSource;
[[maybe_unused]] static int SSAFAnalysisRegistryAnchorDestination =
SSAFAnalysisRegistryAnchorSource;
+// This anchor is used to force the linker to link the CallGraphExtractor.
+extern volatile int CallGraphExtractorAnchorSource;
+[[maybe_unused]] static int CallGraphExtractorAnchorDestination =
+ CallGraphExtractorAnchorSource;
+
#endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_SSAFBUILTINFORCELINKER_H
diff --git a/clang/lib/Driver/CMakeLists.txt b/clang/lib/Driver/CMakeLists.txt
index 8a2ffb303b4cb..92554712fffd8 100644
--- a/clang/lib/Driver/CMakeLists.txt
+++ b/clang/lib/Driver/CMakeLists.txt
@@ -110,6 +110,7 @@ add_clang_library(clangDriver
clangBasic
clangDependencyScanning
clangFrontend
+ clangScalableStaticAnalysisFrameworkAnalyses
clangScalableStaticAnalysisFrameworkCore
clangScalableStaticAnalysisFrameworkFrontend
clangSerialization
diff --git a/clang/lib/FrontendTool/CMakeLists.txt b/clang/lib/FrontendTool/CMakeLists.txt
index a451eb967e904..24623303e6bdb 100644
--- a/clang/lib/FrontendTool/CMakeLists.txt
+++ b/clang/lib/FrontendTool/CMakeLists.txt
@@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS
)
set(link_libs
+ clangScalableStaticAnalysisFrameworkAnalyses
clangScalableStaticAnalysisFrameworkCore
clangScalableStaticAnalysisFrameworkFrontend
clangBasic
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Analyses/CMakeLists.txt b/clang/lib/ScalableStaticAnalysisFramework/Analyses/CMakeLists.txt
index 34c6dd9b61203..2dcce40f886dd 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Analyses/CMakeLists.txt
+++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/CMakeLists.txt
@@ -3,6 +3,7 @@ set(LLVM_LINK_COMPONENTS
)
add_clang_library(clangScalableStaticAnalysisFrameworkAnalyses
+ CallGraph/CallGraphExtractor.cpp
UnsafeBufferUsage/UnsafeBufferUsageExtractor.cpp
LINK_LIBS
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Analyses/CallGraph/CallGraphExtractor.cpp b/clang/lib/ScalableStaticAnalysisFramework/Analyses/CallGraph/CallGraphExtractor.cpp
new file mode 100644
index 0000000000000..b2cd2e40f33b5
--- /dev/null
+++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/CallGraph/CallGraphExtractor.cpp
@@ -0,0 +1,106 @@
+//===- CallGraphExtractor.cpp - Call Graph Summary Extractor --------------===//
+//
+// 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/AST/ASTContext.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/DeclCXX.h"
+#include "clang/AST/DeclObjC.h"
+#include "clang/Analysis/AnalysisDeclContext.h"
+#include "clang/Analysis/CallGraph.h"
+#include "clang/Basic/SourceManager.h"
+#include "clang/ScalableStaticAnalysisFramework/Analyses/CallGraph/CallGraphSummary.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/ASTEntityMapping.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/TUSummary/ExtractorRegistry.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/TUSummary/TUSummaryBuilder.h"
+#include "llvm/ADT/STLExtras.h"
+#include <memory>
+
+using namespace clang;
+using namespace ssaf;
+
+namespace {
+class CallGraphExtractor final : public TUSummaryExtractor {
+public:
+ using TUSummaryExtractor::TUSummaryExtractor;
+
+private:
+ void HandleTranslationUnit(ASTContext &Ctx) override;
+
+ void handleCallGraphNode(const ASTContext &Ctx, const CallGraphNode *N);
+};
+} // namespace
+
+void CallGraphExtractor::HandleTranslationUnit(ASTContext &Ctx) {
+ CallGraph CG;
+ CG.addToCallGraph(
+ const_cast<TranslationUnitDecl *>(Ctx.getTranslationUnitDecl()));
+
+ for (const auto &N : llvm::make_second_range(CG)) {
+ if (N && N->getDecl() && N->getDefinition())
+ handleCallGraphNode(Ctx, N.get());
+ }
+}
+
+void CallGraphExtractor::handleCallGraphNode(const ASTContext &Ctx,
+ const CallGraphNode *N) {
+ const FunctionDecl *Definition = N->getDefinition();
+
+ // FIXME: `clang::CallGraph` does not create entries for primary templates.
+ assert(!Definition->isTemplated());
+
+ auto CallerName = getEntityName(Definition);
+ if (!CallerName)
+ return;
+
+ auto FnSummary = std::make_unique<CallGraphSummary>();
+
+ PresumedLoc Loc =
+ Ctx.getSourceManager().getPresumedLoc(Definition->getLocation());
+ FnSummary->Definition.File = Loc.getFilename();
+ FnSummary->Definition.Line = Loc.getLine();
+ FnSummary->Definition.Column = Loc.getColumn();
+ FnSummary->PrettyName = AnalysisDeclContext::getFunctionName(Definition);
+
+ for (const auto &Record : N->callees()) {
+ const Decl *CalleeDecl = Record.Callee->getDecl();
+
+ // FIXME: `clang::CallGraph` does not consider indirect calls, thus this is
+ // never null.
+ assert(CalleeDecl);
+
+ // FIXME: `clang::CallGraph` does not consider ObjCMessageExprs as calls.
+ // Consequently, they don't appear as a Callee.
+ assert(!isa<ObjCMethodDecl>(CalleeDecl));
+
+ // FIXME: `clang::CallGraph` does not create entries for primary templates.
+ assert(!CalleeDecl->isTemplated());
+
+ auto CalleeName = getEntityName(CalleeDecl);
+ if (!CalleeName)
+ continue;
+
+ EntityId CalleeId = SummaryBuilder.addEntity(*CalleeName);
+ if (const auto *MD = dyn_cast_or_null<CXXMethodDecl>(CalleeDecl);
+ MD && MD->isVirtual()) {
+ FnSummary->VirtualCallees.insert(CalleeId);
+ continue;
+ }
+ FnSummary->DirectCallees.insert(CalleeId);
+ }
+
+ EntityId CallerId = SummaryBuilder.addEntity(*CallerName);
+ SummaryBuilder.addSummary(CallerId, std::move(FnSummary));
+}
+
+static TUSummaryExtractorRegistry::Add<CallGraphExtractor>
+ RegisterExtractor("CallGraph", "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.
+// NOLINTNEXTLINE(misc-use-internal-linkage)
+volatile int CallGraphExtractorAnchorSource = 0;
diff --git a/clang/tools/clang-ssaf-format/CMakeLists.txt b/clang/tools/clang-ssaf-format/CMakeLists.txt
index 864fe5bc27aaa..c76756ad4770c 100644
--- a/clang/tools/clang-ssaf-format/CMakeLists.txt
+++ b/clang/tools/clang-ssaf-format/CMakeLists.txt
@@ -10,6 +10,7 @@ add_clang_tool(clang-ssaf-format
clang_target_link_libraries(clang-ssaf-format
PRIVATE
clangBasic
+ clangScalableStaticAnalysisFrameworkAnalyses
clangScalableStaticAnalysisFrameworkCore
clangScalableStaticAnalysisFrameworkTool
)
diff --git a/clang/tools/clang-ssaf-linker/CMakeLists.txt b/clang/tools/clang-ssaf-linker/CMakeLists.txt
index 95db5ad2d3d08..af65aaa3b1aeb 100644
--- a/clang/tools/clang-ssaf-linker/CMakeLists.txt
+++ b/clang/tools/clang-ssaf-linker/CMakeLists.txt
@@ -10,6 +10,7 @@ add_clang_tool(clang-ssaf-linker
clang_target_link_libraries(clang-ssaf-linker
PRIVATE
clangBasic
+ clangScalableStaticAnalysisFrameworkAnalyses
clangScalableStaticAnalysisFrameworkCore
clangScalableStaticAnalysisFrameworkTool
)
diff --git a/clang/unittests/ScalableStaticAnalysisFramework/Analyses/CallGraph/CallGraphExtractorTest.cpp b/clang/unittests/ScalableStaticAnalysisFramework/Analyses/CallGraph/CallGraphExtractorTest.cpp
new file mode 100644
index 0000000000000..9e0b9e6e256a4
--- /dev/null
+++ b/clang/unittests/ScalableStaticAnalysisFramework/Analyses/CallGraph/CallGraphExtractorTest.cpp
@@ -0,0 +1,470 @@
+//===- CallGraphExtractorTest.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 "TestFixture.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/Decl.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/ScalableStaticAnalysisFramework/Analyses/CallGraph/CallGraphSummary.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/ASTEntityMapping.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/TUSummary/ExtractorRegistry.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/TUSummary/TUSummary.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/TUSummary/TUSummaryBuilder.h"
+#include "clang/Tooling/Tooling.h"
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Testing/Support/Error.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include <cassert>
+
+using namespace clang;
+using namespace ssaf;
+
+namespace {
+AST_MATCHER(FunctionDecl, isPrimaryTemplate) {
+ return Node.getDescribedFunctionTemplate() != nullptr;
+}
+} // namespace
+
+static llvm::Expected<const NamedDecl *> findDecl(ASTContext &Ctx,
+ StringRef FnName) {
+ using namespace ast_matchers;
+ auto Matcher =
+ functionDecl(hasName(FnName), unless(isPrimaryTemplate())).bind("decl");
+ auto Matches = match(Matcher, Ctx);
+ if (Matches.empty())
+ return llvm::createStringError("No definition was found with name '" +
+ FnName + "'");
+ auto *ND = Matches[0].template getNodeAs<NamedDecl>("decl");
+ assert(ND);
+ return cast<NamedDecl>(ND->getCanonicalDecl());
+}
+
+// ============================================================================
+// PrintTo overload for readable failure messages.
+// Must live in the same namespace as Location (clang::ssaf) for ADL.
+// ============================================================================
+
+namespace clang::ssaf {
+void PrintTo(const CallGraphSummary::Location &Loc, std::ostream *OS) {
+ *OS << Loc.File << ":" << Loc.Line << ":" << Loc.Column;
+}
+void PrintTo(const CallGraphSummary &S, std::ostream *OS) {
+ *OS << "CallGraphSummary { PrettyName: '" << S.PrettyName << "'"
+ << ", Definition: ";
+ PrintTo(S.Definition, OS);
+ *OS << ", DirectCallees: " << S.DirectCallees.size()
+ << ", VirtualCallees: " << S.VirtualCallees.size() << " }";
+}
+} // namespace clang::ssaf
+
+namespace {
+
+MATCHER_P3(DefinedAt, File, Line, Column,
+ std::string(negation ? "is not" : "is") + " defined at " +
+ std::string(File) + ":" + testing::PrintToString(Line) + ":" +
+ testing::PrintToString(Column)) {
+ const auto &D = arg.Definition;
+ if (D.File != File || D.Line != Line || D.Column != Column) {
+ *result_listener << "defined at " << D.File << ":" << D.Line << ":"
+ << D.Column;
+ return false;
+ }
+ return true;
+}
+
+MATCHER_P(HasPrettyName, Name,
+ std::string(negation ? "doesn't have" : "has") + " pretty name '" +
+ testing::PrintToString(Name) + "'") {
+ if (arg.PrettyName != std::string(Name)) {
+ *result_listener << "has pretty name '" << arg.PrettyName << "'";
+ return false;
+ }
+ return true;
+}
+
+MATCHER(HasNoDirectCallees,
+ std::string(negation ? "has" : "has no") + " direct callees") {
+ if (!arg.DirectCallees.empty()) {
+ *result_listener << "has " << arg.DirectCallees.size()
+ << " direct callee(s)";
+ return false;
+ }
+ return true;
+}
+
+MATCHER(HasNoVirtualCallees,
+ std::string(negation ? "has" : "has no") + " virtual callees") {
+ if (!arg.VirtualCallees.empty()) {
+ *result_listener << "has " << arg.VirtualCallees.size()
+ << " virtual callee(s)";
+ return false;
+ }
+ return true;
+}
+
+template <typename... Matchers> auto hasSummaryThat(const Matchers &...Ms) {
+ using namespace testing;
+ return llvm::HasValue(Pointee(AllOf(std::move(Ms)...)));
+}
+
+// ============================================================================
+// Test fixture
+// ============================================================================
+
+struct CallGraphExtractorTest : ssaf::TestFixture {
+ TUSummary Summary =
+ BuildNamespace(BuildNamespaceKind::CompilationUnit, "Mock.cpp");
+ TUSummaryBuilder Builder = TUSummaryBuilder(Summary);
+
+ /// Creates the AST and extractor, then extracts the summaries from the AST.
+ /// 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);
+ Consumer->HandleTranslationUnit(AST->getASTContext());
+ }
+
+ /// Tries to find the \c CallGraphSummary for the \p FnName function.
+ llvm::Expected<const CallGraphSummary *>
+ findSummary(llvm::StringRef FnName) const;
+
+ /// Matcher factory: matches a summary whose direct callees are exactly the
+ /// given set of function names (resolved to USRs via the entity table).
+ /// Uses \c testing::ResultOf to transform the summary's EntityId set into
+ /// USR strings before comparing with \c testing::ContainerEq.
+ auto hasDirectCallees(llvm::ArrayRef<StringRef> Names)
+ -> testing::Matcher<const CallGraphSummary &> {
+ auto MaybeUSRs = asUSRs(Names);
+ if (!MaybeUSRs) {
+ ADD_FAILURE() << "Failed to resolve callee names to USRs: "
+ << llvm::toString(MaybeUSRs.takeError());
+ return testing::An<const CallGraphSummary &>();
+ }
+ std::set<std::string> ExpectedUSRs = std::move(*MaybeUSRs);
+ return testing::ResultOf(
+ "direct callees",
+ [this](const CallGraphSummary &S) {
+ return getUSRsForCallees(S.DirectCallees);
+ },
+ testing::ContainerEq(ExpectedUSRs));
+ }
+
+ /// Matcher factory: same as \c hasDirectCallees but for virtual callees.
+ auto hasVirtualCallees(llvm::ArrayRef<StringRef> Names)
+ -> testing::Matcher<const CallGraphSummary &> {
+ auto MaybeUSRs = asUSRs(Names);
+ if (!MaybeUSRs) {
+ ADD_FAILURE() << "Failed to resolve callee names to USRs: "
+ << llvm::toString(MaybeUSRs.takeError());
+ return testing::A<const CallGraphSummary &>();
+ }
+ std::set<std::string> ExpectedUSRs = std::move(*MaybeUSRs);
+ return testing::ResultOf(
+ "virtual callees",
+ [this](const CallGraphSummary &S) {
+ return getUSRsForCallees(S.VirtualCallees);
+ },
+ testing::ContainerEq(ExpectedUSRs));
+ }
+
+private:
+ std::unique_ptr<ASTUnit> AST;
+
+ std::set<std::string>
+ getUSRsForCallees(const std::set<EntityId> &Callees) const;
+
+ /// Looks up the Decls for \p FnNames, and then transforms those into USRs.
+ llvm::Expected<std::set<std::string>>
+ asUSRs(llvm::ArrayRef<StringRef> FnNames);
+};
+
+llvm::Expected<const CallGraphSummary *>
+CallGraphExtractorTest::findSummary(llvm::StringRef FnName) const {
+ auto MaybeDecl = findDecl(AST->getASTContext(), FnName);
+ if (!MaybeDecl)
+ return MaybeDecl.takeError();
+
+ std::optional<EntityName> EntName = getEntityName(*MaybeDecl);
+ if (!EntName.has_value()) {
+ return llvm::createStringError("Failed to create an entity name for '" +
+ FnName + "'");
+ }
+
+ const auto &EntitiesTable = getEntities(getIdTable(Summary));
+ auto It = EntitiesTable.find(EntName.value());
+ if (It == EntitiesTable.end()) {
+ return llvm::createStringError(
+ "No entity ID was present in the entity table for '" + FnName + "'");
+ }
+ EntityId ID = It->second;
+ auto &Data = getData(Summary);
+ auto SummaryIt = Data.find(SummaryName("CallGraph"));
+ if (SummaryIt == Data.end())
+ return llvm::createStringError("There is no 'CallGraph' summary");
+ auto EntityIt = SummaryIt->second.find(ID);
+ if (EntityIt == SummaryIt->second.end()) {
+ return llvm::createStringError(
+ "There is no 'CallGraph' summary for entity ID " +
+ std::to_string(getIndex(ID)) + " aka. '" + FnName + "'");
+ }
+ return static_cast<const CallGraphSummary *>(EntityIt->second.get());
+}
+
+std::set<std::string> CallGraphExtractorTest::getUSRsForCallees(
+ const std::set<EntityId> &Callees) const {
+ std::set<std::string> USRs;
+
+ auto GatherCalleeUSRs = [&](const EntityName &Name, EntityId Id) {
+ if (llvm::is_contained(Callees, Id))
+ USRs.insert(TestFixture::getUSR(Name));
+ };
+ TestFixture::getIdTable(Summary).forEach(GatherCalleeUSRs);
+ assert(Callees.size() == USRs.size());
+ return USRs;
+}
+
+llvm::Expected<std::set<std::string>>
+CallGraphExtractorTest::asUSRs(llvm::ArrayRef<StringRef> FnNames) {
+ std::set<std::string> USRs;
+ ASTContext &Ctx = AST->getASTContext();
+ for (StringRef FnName : FnNames) {
+ auto MaybeDecl = findDecl(Ctx, FnName);
+ if (!MaybeDecl)
+ return MaybeDecl.takeError();
+ std::optional<EntityName> Name = getEntityName(MaybeDecl.get());
+ if (!Name.has_value()) {
+ return llvm::createStringError("Failed to get the USR of '" + FnName +
+ "'");
+ }
+ USRs.insert(getUSR(Name.value()));
+ }
+ assert(USRs.size() == FnNames.size());
+ return USRs;
+}
+
+// ============================================================================
+// Tests
+// ============================================================================
+
+TEST_F(CallGraphExtractorTest, SimpleFunctionCalls) {
+ runExtractor(R"cpp(
+ void a();
+ void b();
+ void calls_a_and_b(bool coin) {
+ if (coin)
+ a();
+ else
+ b();
+ }
+ )cpp");
+
+ ASSERT_THAT_EXPECTED(
+ findSummary("calls_a_and_b"),
+ hasSummaryThat(hasDirectCallees({"a", "b"}), HasNoVirtualCallees()));
+}
+
+TEST_F(CallGraphExtractorTest, NoCallees) {
+ runExtractor(R"cpp(
+ void leaf() {}
+ )cpp");
+
+ ASSERT_THAT_EXPECTED(
+ findSummary("leaf"),
+ hasSummaryThat(HasNoDirectCallees(), HasNoVirtualCallees()));
+}
+
+TEST_F(CallGraphExtractorTest, TransitiveCalls) {
+ runExtractor(R"cpp(
+ void c() { /*empty*/ }
+ void b() { c(); }
+ void a() { b(); }
+ )cpp");
+
+ // a calls b (not c — we only record direct callees).
+ ASSERT_THAT_EXPECTED(findSummary("a"), hasSummaryThat(hasDirectCallees({"b"}),
+ HasNoVirtualCallees()));
+
+ // b calls c.
+ ASSERT_THAT_EXPECTED(findSummary("b"), hasSummaryThat(hasDirectCallees({"c"}),
+ HasNoVirtualCallees()));
+
+ // c calls nothing.
+ ASSERT_THAT_EXPECTED(findSummary("c"), hasSummaryThat(HasNoDirectCallees(),
+ HasNoVirtualCallees()));
+}
+
+TEST_F(CallGraphExtractorTest, VirtualCallsAreImprecise) {
+ runExtractor(R"cpp(
+ struct Base {
+ virtual void virt();
+ };
+ struct Derived : Base {
+ void virt() override;
+ };
+ void caller(Base &Obj) {
+ Obj.virt();
+ }
+ )cpp");
+
+ ASSERT_THAT_EXPECTED(
+ findSummary("caller"),
+ hasSummaryThat(HasNoDirectCallees(), hasVirtualCallees({"Base::virt"})));
+}
+
+TEST_F(CallGraphExtractorTest, MixedDirectAndVirtualCalls) {
+ runExtractor(R"cpp(
+ void direct_target();
+ struct Base {
+ virtual void virt();
+ };
+ void caller(Base &Obj) {
+ direct_target();
+ Obj.virt();
+ }
+ )cpp");
+
+ ASSERT_THAT_EXPECTED(findSummary("caller"),
+ hasSummaryThat(hasDirectCallees({"direct_target"}),
+ hasVirtualCallees({"Base::virt"})));
+}
+
+TEST_F(CallGraphExtractorTest, DeclarationsOnlyNoSummary) {
+ runExtractor(R"cpp(
+ void declared_only();
+ )cpp");
+
+ // No summary for functions without definitions.
+ EXPECT_FALSE(llvm::is_contained(getData(Summary), SummaryName("CallGraph")));
+}
+
+TEST_F(CallGraphExtractorTest, DuplicateCallees) {
+ runExtractor(R"cpp(
+ void target();
+ void caller() {
+ target();
+ target();
+ target();
+ }
+ )cpp");
+
+ // Despite three calls, there's only one unique callee.
+ ASSERT_THAT_EXPECTED(
+ findSummary("caller"),
+ hasSummaryThat(hasDirectCallees({"target"}), HasNoVirtualCallees()));
+}
+
+TEST_F(CallGraphExtractorTest, NonVirtualMethodCalls) {
+ runExtractor(R"cpp(
+ struct S {
+ void method();
+ };
+ void caller() {
+ S s;
+ s.method();
+ }
+ )cpp");
+
+ ASSERT_THAT_EXPECTED(
+ findSummary("caller"),
+ hasSummaryThat(hasDirectCallees({"method"}), HasNoVirtualCallees()));
+}
+
+TEST_F(CallGraphExtractorTest, StaticMethodCalls) {
+ runExtractor(R"cpp(
+ struct S {
+ static void staticMethod();
+ };
+ void caller() {
+ S::staticMethod();
+ }
+ )cpp");
+
+ ASSERT_THAT_EXPECTED(findSummary("caller"),
+ hasSummaryThat(hasDirectCallees({"staticMethod"}),
+ HasNoVirtualCallees()));
+}
+
+TEST_F(CallGraphExtractorTest, FunctionPtrCall) {
+ runExtractor(R"cpp(
+ void caller(int (&fptr)()) {
+ fptr();
+ }
+ )cpp");
+
+ ASSERT_THAT_EXPECTED(
+ findSummary("caller"),
+ hasSummaryThat(HasNoDirectCallees(), HasNoVirtualCallees()));
+}
+
+TEST_F(CallGraphExtractorTest, ObjCMessageExprs) {
+ runExtractor(R"cpp(
+ @interface NSString
+ - (id)stringByAppendingString:(id)str;
+ @end
+
+ void caller(void) {
+ id msg = [@"Hello" stringByAppendingString:@", World!"];
+ }
+ )cpp",
+ {"-x", "objective-c"});
+
+ ASSERT_THAT_EXPECTED(
+ findSummary("caller"),
+ hasSummaryThat(HasNoDirectCallees(), HasNoVirtualCallees()));
+}
+
+TEST_F(CallGraphExtractorTest, DefinitionLocation) {
+ runExtractor(R"cpp(
+ void callee_with_def() {}
+ void callee_without_def();
+ void caller(int n) {
+ if (n == 0) return;
+ callee_with_def();
+ callee_without_def();
+ caller(n - 1);
+ }
+ )cpp");
+
+ ASSERT_THAT_EXPECTED(
+ findSummary("caller"),
+ hasSummaryThat(
+ hasDirectCallees({"caller", "callee_with_def", "callee_without_def"}),
+ HasNoVirtualCallees(), DefinedAt("input.cc", 4U, 10U)));
+
+ ASSERT_THAT_EXPECTED(findSummary("callee_with_def"),
+ hasSummaryThat(HasNoDirectCallees(),
+ HasNoVirtualCallees(),
+ DefinedAt("input.cc", 2U, 10U)));
+}
+
+TEST_F(CallGraphExtractorTest, PrettyName) {
+ runExtractor(R"cpp(
+ template <class T, int N>
+ void templated_function(int *) {}
+ void caller(int n) {
+ templated_function<struct TypeTag, 404>(&n);
+ }
+ )cpp");
+
+ ASSERT_THAT_EXPECTED(findSummary("caller"),
+ hasSummaryThat(hasDirectCallees({"templated_function"}),
+ HasNoVirtualCallees(),
+ HasPrettyName("caller(int)")));
+
+ // FIXME: The template arguments are not spelled here.
+ ASSERT_THAT_EXPECTED(
+ findSummary("templated_function"),
+ hasSummaryThat(HasNoDirectCallees(), HasNoVirtualCallees(),
+ HasPrettyName("templated_function(int *)")));
+}
+
+} // namespace
diff --git a/clang/unittests/ScalableStaticAnalysisFramework/CMakeLists.txt b/clang/unittests/ScalableStaticAnalysisFramework/CMakeLists.txt
index 8eeaa982daaf6..5ae0a5de35e21 100644
--- a/clang/unittests/ScalableStaticAnalysisFramework/CMakeLists.txt
+++ b/clang/unittests/ScalableStaticAnalysisFramework/CMakeLists.txt
@@ -1,4 +1,5 @@
add_distinct_clang_unittest(ClangScalableAnalysisTests
+ Analyses/CallGraph/CallGraphExtractorTest.cpp
Analyses/UnsafeBufferUsage/UnsafeBufferUsageTest.cpp
ASTEntityMappingTest.cpp
BuildNamespaceTest.cpp
diff --git a/clang/unittests/ScalableStaticAnalysisFramework/Registries/SummaryExtractorRegistryTest.cpp b/clang/unittests/ScalableStaticAnalysisFramework/Registries/SummaryExtractorRegistryTest.cpp
index 2018beebd53da..2ffccf1cf0694 100644
--- a/clang/unittests/ScalableStaticAnalysisFramework/Registries/SummaryExtractorRegistryTest.cpp
+++ b/clang/unittests/ScalableStaticAnalysisFramework/Registries/SummaryExtractorRegistryTest.cpp
@@ -12,6 +12,7 @@
#include "clang/ScalableStaticAnalysisFramework/Core/TUSummary/TUSummary.h"
#include "clang/Tooling/Tooling.h"
#include "llvm/ADT/StringRef.h"
+#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include <memory>
@@ -38,11 +39,8 @@ TEST(SummaryExtractorRegistryTest, EnumeratingRegistryEntries) {
EXPECT_TRUE(Inserted);
}
- EXPECT_EQ(ActualNames, (std::set<llvm::StringRef>{
- "MockSummaryExtractor1",
- "MockSummaryExtractor2",
- "NoOpExtractor",
- }));
+ EXPECT_THAT(ActualNames, testing::IsSupersetOf({"MockSummaryExtractor1",
+ "MockSummaryExtractor2"}));
}
TEST(SummaryExtractorRegistryTest, InstantiatingExtractor1) {
diff --git a/llvm/utils/gn/secondary/clang/lib/Driver/BUILD.gn b/llvm/utils/gn/secondary/clang/lib/Driver/BUILD.gn
index f8073d9bf1ea9..8148f656fe258 100644
--- a/llvm/utils/gn/secondary/clang/lib/Driver/BUILD.gn
+++ b/llvm/utils/gn/secondary/clang/lib/Driver/BUILD.gn
@@ -17,6 +17,7 @@ static_library("Driver") {
"//clang/lib/DependencyScanning",
"//clang/lib/Frontend",
"//clang/lib/Options",
+ "//clang/lib/ScalableStaticAnalysisFramework/Analyses",
"//clang/lib/ScalableStaticAnalysisFramework/Core",
"//clang/lib/ScalableStaticAnalysisFramework/Frontend",
"//llvm/include/llvm/Config:llvm-config",
diff --git a/llvm/utils/gn/secondary/clang/lib/FrontendTool/BUILD.gn b/llvm/utils/gn/secondary/clang/lib/FrontendTool/BUILD.gn
index 60157daa66d40..4d515b7a25ce0 100644
--- a/llvm/utils/gn/secondary/clang/lib/FrontendTool/BUILD.gn
+++ b/llvm/utils/gn/secondary/clang/lib/FrontendTool/BUILD.gn
@@ -12,6 +12,7 @@ static_library("FrontendTool") {
"//clang/lib/Frontend",
"//clang/lib/Frontend/Rewrite",
"//clang/lib/Options",
+ "//clang/lib/ScalableStaticAnalysisFramework/Analyses",
"//clang/lib/ScalableStaticAnalysisFramework/Core",
"//clang/lib/ScalableStaticAnalysisFramework/Frontend",
"//llvm/lib/Option",
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 e002c3256eac7..ac62574ad8534 100644
--- a/llvm/utils/gn/secondary/clang/lib/ScalableStaticAnalysisFramework/Analyses/BUILD.gn
+++ b/llvm/utils/gn/secondary/clang/lib/ScalableStaticAnalysisFramework/Analyses/BUILD.gn
@@ -8,5 +8,8 @@ static_library("Analyses") {
"//clang/lib/ScalableStaticAnalysisFramework/Core",
"//llvm/lib/Support",
]
- sources = [ "UnsafeBufferUsage/UnsafeBufferUsageExtractor.cpp" ]
+ sources = [
+ "CallGraph/CallGraphExtractor.cpp",
+ "UnsafeBufferUsage/UnsafeBufferUsageExtractor.cpp",
+ ]
}
diff --git a/llvm/utils/gn/secondary/clang/unittests/ScalableStaticAnalysisFramework/BUILD.gn b/llvm/utils/gn/secondary/clang/unittests/ScalableStaticAnalysisFramework/BUILD.gn
index edc43e6b04c98..b6eebea5e843b 100644
--- a/llvm/utils/gn/secondary/clang/unittests/ScalableStaticAnalysisFramework/BUILD.gn
+++ b/llvm/utils/gn/secondary/clang/unittests/ScalableStaticAnalysisFramework/BUILD.gn
@@ -18,8 +18,9 @@ unittest("ClangScalableAnalysisTests") {
]
include_dirs = [ "." ]
sources = [
- "ASTEntityMappingTest.cpp",
+ "Analyses/CallGraph/CallGraphExtractorTest.cpp",
"Analyses/UnsafeBufferUsage/UnsafeBufferUsageTest.cpp",
+ "ASTEntityMappingTest.cpp",
"BuildNamespaceTest.cpp",
"EntityIdTableTest.cpp",
"EntityIdTest.cpp",
More information about the cfe-commits
mailing list