[clang] b73ce3e - [SSAF][PointerFlow] Add PointerFlow summary and extractor (#188654)
via cfe-commits
cfe-commits at lists.llvm.org
Tue May 5 13:02:03 PDT 2026
Author: Ziqing Luo
Date: 2026-05-05T13:01:58-07:00
New Revision: b73ce3e53fcb9f72c759139e93db7e16a813c3b2
URL: https://github.com/llvm/llvm-project/commit/b73ce3e53fcb9f72c759139e93db7e16a813c3b2
DIFF: https://github.com/llvm/llvm-project/commit/b73ce3e53fcb9f72c759139e93db7e16a813c3b2.diff
LOG: [SSAF][PointerFlow] Add PointerFlow summary and extractor (#188654)
Implement a PointerFlow summary and extractor, which uses
EntityPointerLevel. An assignment is extracted as a pair of
EntityPointerLevels, representing a directed edge.
Edges extracted from assignments form a directed graph
encoding abstract pointer flow information.
rdar://172429193
---------
Co-authored-by: Balázs Benics <benicsbalazs at gmail.com>
Co-authored-by: Jan Korous <jkorous at apple.com>
Added:
clang/include/clang/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlow.h
clang/lib/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevelFormat.cpp
clang/lib/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlow.cpp
clang/lib/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowExtractor.cpp
clang/lib/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowFormat.cpp
clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageFormat.cpp
clang/test/Analysis/Scalable/PointerFlow/Inputs/tu-summary-bad-array.json
clang/test/Analysis/Scalable/PointerFlow/Inputs/tu-summary-bad-summary.json
clang/test/Analysis/Scalable/PointerFlow/Inputs/tu-summary-no-key.json
clang/test/Analysis/Scalable/PointerFlow/Inputs/tu-summary.json
clang/test/Analysis/Scalable/PointerFlow/tu-summary-serialization.test
clang/unittests/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowTest.cpp
Modified:
clang/include/clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.h
clang/include/clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevelFormat.h
clang/include/clang/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsage.h
clang/include/clang/ScalableStaticAnalysisFramework/SSAFBuiltinForceLinker.h
clang/lib/ScalableStaticAnalysisFramework/Analyses/CMakeLists.txt
clang/lib/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.cpp
clang/lib/ScalableStaticAnalysisFramework/Analyses/SSAFAnalysesCommon.cpp
clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsage.cpp
clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageExtractor.cpp
clang/lib/ScalableStaticAnalysisFramework/Tool/Utils.cpp
clang/test/Analysis/Scalable/ssaf-format/list.test
clang/unittests/ScalableStaticAnalysisFramework/CMakeLists.txt
Removed:
################################################################################
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.h b/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.h
index 429bb74fb417e..98812e0ea04e3 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.h
@@ -43,7 +43,7 @@ class EntityPointerLevel {
// For unittests:
friend EntityPointerLevel buildEntityPointerLevel(EntityId, unsigned);
- EntityPointerLevel(std::pair<EntityId, unsigned> Pair)
+ explicit EntityPointerLevel(std::pair<EntityId, unsigned> Pair)
: Entity(Pair.first), PointerLevel(Pair.second) {}
public:
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevelFormat.h b/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevelFormat.h
index 7b125edb1fb7f..568e03594005e 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevelFormat.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevelFormat.h
@@ -1,4 +1,4 @@
-//===- EntityPointerLevelFormat.h -------------------------------*- C++-*-===//
+//===- EntityPointerLevelFormat.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.
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlow.h b/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlow.h
new file mode 100644
index 0000000000000..eda7f138d9e9b
--- /dev/null
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlow.h
@@ -0,0 +1,51 @@
+//===- PointerFlow.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
+//
+//===----------------------------------------------------------------------===//
+//
+// This file defines an analysis that builds directed graphs where nodes
+// are pointers and edges are assignment operations, each of which bridges two
+// nodes.
+//
+//===----------------------------------------------------------------------===//
+#ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_ANALYSES_POINTERFLOW_POINTERFLOW_H
+#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_ANALYSES_POINTERFLOW_POINTERFLOW_H
+
+#include "clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/TUSummary/EntitySummary.h"
+
+namespace clang::ssaf {
+
+/// Maps each source node to its destination nodes:
+using EdgeSet = std::map<EntityPointerLevel, EntityPointerLevelSet>;
+
+class PointerFlowEntitySummary final : public EntitySummary {
+ EdgeSet Edges;
+
+ friend PointerFlowEntitySummary buildPointerFlowEntitySummary(EdgeSet Edges);
+ friend llvm::iterator_range<EdgeSet::const_iterator>
+ getEdges(const PointerFlowEntitySummary &);
+
+ explicit PointerFlowEntitySummary(EdgeSet Edges) : Edges(std::move(Edges)) {}
+
+public:
+ static constexpr llvm::StringLiteral Name = "PointerFlow";
+
+ SummaryName getSummaryName() const override { return summaryName(); }
+
+ bool operator==(const EdgeSet &Other) const { return Edges == Other; }
+
+ bool operator==(const PointerFlowEntitySummary &Other) const {
+ return Edges == Other.Edges;
+ }
+
+ bool empty() const { return Edges.empty(); }
+
+ static SummaryName summaryName() { return SummaryName{Name.str()}; }
+};
+} // namespace clang::ssaf
+
+#endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_ANALYSES_POINTERFLOW_POINTERFLOW_H
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsage.h b/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsage.h
index ec0c4334a802e..972ff6a21bb25 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsage.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsage.h
@@ -26,7 +26,7 @@ class UnsafeBufferUsageEntitySummary final : public EntitySummary {
getUnsafeBuffers(const UnsafeBufferUsageEntitySummary &);
explicit UnsafeBufferUsageEntitySummary(EntityPointerLevelSet UnsafeBuffers)
- : EntitySummary(), UnsafeBuffers(std::move(UnsafeBuffers)) {}
+ : UnsafeBuffers(std::move(UnsafeBuffers)) {}
public:
static constexpr llvm::StringLiteral Name = "UnsafeBufferUsage";
diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/SSAFBuiltinForceLinker.h b/clang/include/clang/ScalableStaticAnalysisFramework/SSAFBuiltinForceLinker.h
index 5616976e10f77..8419ad23619f7 100644
--- a/clang/include/clang/ScalableStaticAnalysisFramework/SSAFBuiltinForceLinker.h
+++ b/clang/include/clang/ScalableStaticAnalysisFramework/SSAFBuiltinForceLinker.h
@@ -45,6 +45,18 @@ extern volatile int UnsafeBufferUsageTUSummaryExtractorAnchorSource;
UnsafeBufferUsageTUSummaryExtractorAnchorDestination =
UnsafeBufferUsageTUSummaryExtractorAnchorSource;
+// This anchor is used to force the linker to link the PointerFlow
+// JSONFormat registration:
+extern volatile int PointerFlowSSAFJSONFormatAnchorSource;
+[[maybe_unused]] static int PointerFlowSSAFJSONFormatAnchorDestination =
+ PointerFlowSSAFJSONFormatAnchorSource;
+
+// This anchor is used to force the linker to link the PointerFlow
+// TUSummaryExtractor registration.
+extern volatile int PointerFlowTUSummaryExtractorAnchorSource;
+[[maybe_unused]] static int PointerFlowTUSummaryExtractorAnchorDestination =
+ PointerFlowTUSummaryExtractorAnchorSource;
+
// This anchor is used to force the linker to link the CallGraphExtractor.
extern volatile int CallGraphExtractorAnchorSource;
[[maybe_unused]] static int CallGraphExtractorAnchorDestination =
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Analyses/CMakeLists.txt b/clang/lib/ScalableStaticAnalysisFramework/Analyses/CMakeLists.txt
index 88dcef0c5a2fa..6353b0918fe73 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Analyses/CMakeLists.txt
+++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/CMakeLists.txt
@@ -6,9 +6,14 @@ add_clang_library(clangScalableStaticAnalysisFrameworkAnalyses
CallGraph/CallGraphExtractor.cpp
CallGraph/CallGraphJSONFormat.cpp
EntityPointerLevel/EntityPointerLevel.cpp
+ EntityPointerLevel/EntityPointerLevelFormat.cpp
+ PointerFlow/PointerFlow.cpp
+ PointerFlow/PointerFlowExtractor.cpp
+ PointerFlow/PointerFlowFormat.cpp
SSAFAnalysesCommon.cpp
UnsafeBufferUsage/UnsafeBufferUsage.cpp
UnsafeBufferUsage/UnsafeBufferUsageExtractor.cpp
+ UnsafeBufferUsage/UnsafeBufferUsageFormat.cpp
LINK_LIBS
clangAST
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.cpp b/clang/lib/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.cpp
index 52e6705ec43af..58c2195e1cd0e 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.cpp
+++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.cpp
@@ -1,4 +1,4 @@
-//===- EntityPointerLevel.cpp ----------------------------------*- C++ -*-===//
+//===- EntityPointerLevel.cpp -----------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
@@ -11,7 +11,6 @@
#include "clang/AST/ASTContext.h"
#include "clang/AST/Decl.h"
#include "clang/AST/StmtVisitor.h"
-#include "clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevelFormat.h"
#include "clang/ScalableStaticAnalysisFramework/Core/ASTEntityMapping.h"
#include "clang/ScalableStaticAnalysisFramework/Core/Model/EntityName.h"
#include <optional>
@@ -222,6 +221,12 @@ class EntityPointerLevelTranslator
return makeEntityNameErr(Ctx, E->getMemberDecl());
}
+ // Translate(`DefaultArg`) -> Translate(`DefaultArg->getExpr()`)
+ Expected<EntityPointerLevelSet>
+ VisitCXXDefaultArgExpr(const CXXDefaultArgExpr *E) {
+ return Visit(E->getExpr());
+ }
+
Expected<EntityPointerLevelSet>
VisitOpaqueValueExpr(const OpaqueValueExpr *S) {
return Visit(S->getSourceExpr());
@@ -255,44 +260,3 @@ EntityPointerLevel clang::ssaf::buildEntityPointerLevel(EntityId Id,
unsigned PtrLv) {
return EntityPointerLevel({Id, PtrLv});
}
-
-// Writes an EntityPointerLevel as
-// Array [
-// Object { "@" : [entity-id]},
-// [pointer-level-integer]
-// ]
-llvm::json::Value clang::ssaf::entityPointerLevelToJSON(
- const EntityPointerLevel &EPL, JSONFormat::EntityIdToJSONFn EntityId2JSON) {
- return llvm::json::Array{EntityId2JSON(EPL.getEntity()),
- llvm::json::Value(EPL.getPointerLevel())};
-}
-
-Expected<EntityPointerLevel> clang::ssaf::entityPointerLevelFromJSON(
- const llvm::json::Value &EPLData,
- JSONFormat::EntityIdFromJSONFn EntityIdFromJSON) {
- auto *AsArr = EPLData.getAsArray();
-
- if (!AsArr || AsArr->size() != 2)
- return makeSawButExpectedError(
- EPLData, "an array with exactly two elements representing "
- "EntityId and PointerLevel, respectively");
-
- auto *EntityIdObj = (*AsArr)[0].getAsObject();
-
- if (!EntityIdObj)
- return makeSawButExpectedError((*AsArr)[0],
- "an object representing EntityId");
-
- Expected<EntityId> Id = EntityIdFromJSON(*EntityIdObj);
-
- if (!Id)
- return Id.takeError();
-
- std::optional<uint64_t> PtrLv = (*AsArr)[1].getAsInteger();
-
- if (!PtrLv)
- return makeSawButExpectedError((*AsArr)[1],
- "an integer representing PointerLevel");
-
- return buildEntityPointerLevel(*Id, *PtrLv);
-}
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevelFormat.cpp b/clang/lib/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevelFormat.cpp
new file mode 100644
index 0000000000000..978124279d9a2
--- /dev/null
+++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevelFormat.cpp
@@ -0,0 +1,58 @@
+//===- EntityPointerLevelFormat.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/EntityPointerLevel/EntityPointerLevelFormat.h"
+#include "SSAFAnalysesCommon.h"
+#include "clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Serialization/JSONFormat.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/JSON.h"
+
+using namespace clang;
+using namespace ssaf;
+
+// Writes an EntityPointerLevel as
+// Array [
+// Object { "@" : [entity-id]},
+// [pointer-level-integer]
+// ]
+llvm::json::Value clang::ssaf::entityPointerLevelToJSON(
+ const EntityPointerLevel &EPL, JSONFormat::EntityIdToJSONFn EntityId2JSON) {
+ return llvm::json::Array{EntityId2JSON(EPL.getEntity()),
+ llvm::json::Value(EPL.getPointerLevel())};
+}
+
+llvm::Expected<EntityPointerLevel> clang::ssaf::entityPointerLevelFromJSON(
+ const llvm::json::Value &EPLData,
+ JSONFormat::EntityIdFromJSONFn EntityIdFromJSON) {
+ auto *AsArr = EPLData.getAsArray();
+
+ if (!AsArr || AsArr->size() != 2)
+ return makeSawButExpectedError(
+ EPLData, "an array with exactly two elements representing "
+ "EntityId and PointerLevel, respectively");
+
+ auto *EntityIdObj = (*AsArr)[0].getAsObject();
+
+ if (!EntityIdObj)
+ return makeSawButExpectedError((*AsArr)[0],
+ "an object representing EntityId");
+
+ llvm::Expected<EntityId> Id = EntityIdFromJSON(*EntityIdObj);
+
+ if (!Id)
+ return Id.takeError();
+
+ std::optional<uint64_t> PtrLv = (*AsArr)[1].getAsInteger();
+
+ if (!PtrLv)
+ return makeSawButExpectedError((*AsArr)[1],
+ "an integer representing PointerLevel");
+
+ return buildEntityPointerLevel(*Id, *PtrLv);
+}
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlow.cpp b/clang/lib/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlow.cpp
new file mode 100644
index 0000000000000..fe28d9ae4efce
--- /dev/null
+++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlow.cpp
@@ -0,0 +1,22 @@
+//===- PointerFlow.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/PointerFlow/PointerFlow.h"
+
+using namespace clang;
+using namespace ssaf;
+
+ssaf::PointerFlowEntitySummary
+ssaf::buildPointerFlowEntitySummary(EdgeSet Edges) {
+ return PointerFlowEntitySummary(std::move(Edges));
+}
+
+llvm::iterator_range<EdgeSet::const_iterator>
+ssaf::getEdges(const PointerFlowEntitySummary &Sum) {
+ return Sum.Edges;
+}
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowExtractor.cpp b/clang/lib/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowExtractor.cpp
new file mode 100644
index 0000000000000..f2c088af7bf1b
--- /dev/null
+++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowExtractor.cpp
@@ -0,0 +1,364 @@
+//===- PointerFlowExtractor.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 "SSAFAnalysesCommon.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/ASTTypeTraits.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/DeclCXX.h"
+#include "clang/AST/Expr.h"
+#include "clang/AST/ExprCXX.h"
+#include "clang/AST/Stmt.h"
+#include "clang/AST/TypeBase.h"
+#include "clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.h"
+#include "clang/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlow.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/ASTEntityMapping.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Model/EntityId.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Model/EntityName.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/TUSummary/ExtractorRegistry.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/TUSummary/TUSummaryBuilder.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/TUSummary/TUSummaryExtractor.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/STLFunctionalExtras.h"
+#include "llvm/ADT/Sequence.h"
+#include "llvm/Support/Error.h"
+#include <memory>
+#include <optional>
+
+namespace clang::ssaf {
+extern PointerFlowEntitySummary buildPointerFlowEntitySummary(EdgeSet Edges);
+} // namespace clang::ssaf
+
+namespace {
+using namespace clang;
+using namespace ssaf;
+
+class PointerFlowMatcher {
+public:
+ EdgeSet Results;
+ ASTContext &Ctx;
+
+ PointerFlowMatcher(ASTContext &Ctx,
+ std::function<EntityId(const EntityName &)> AddEntity)
+ : Ctx(Ctx), AddEntity(std::move(AddEntity)) {}
+
+ llvm::Error matches(const DynTypedNode &DynNode, const NamedDecl *RootDecl);
+
+ llvm::Error matchesInitializerList(const ValueDecl *Base,
+ const Expr *InitExpr,
+ unsigned ArrayElementIndirectLevel = 0);
+
+ llvm::Error matchesStmt(const Stmt *S, const NamedDecl *RootDecl);
+
+ llvm::Error matchesDecl(const Decl *D, const NamedDecl *RootDecl);
+
+private:
+ std::function<EntityId(const EntityName &)> AddEntity;
+
+ Expected<EntityPointerLevelSet> toEPL(const NamedDecl *N,
+ bool IsRet = false) const;
+
+ Expected<EntityPointerLevelSet> toEPL(const Expr *N) const;
+
+ llvm::Error addEdges(Expected<EntityPointerLevelSet> &&LHS,
+ Expected<EntityPointerLevelSet> &&RHS);
+
+ template <typename ParmsProvider, typename ArgsProvider>
+ llvm::Error matchesArgsWithParams(unsigned ArgIdxStart, ParmsProvider *PP,
+ ArgsProvider *AP) {
+ unsigned ArgIdx = ArgIdxStart;
+
+ for (unsigned ParmIdx = 0;
+ ParmIdx < PP->getNumParams() && ArgIdx < AP->getNumArgs();
+ ++ArgIdx, ++ParmIdx) {
+ if (const ParmVarDecl *PD = PP->getParamDecl(ParmIdx);
+ PD && hasPtrOrArrType(PD)) {
+ if (auto Err = addEdges(toEPL(PD), toEPL(AP->getArg(ArgIdx))))
+ return Err;
+ }
+ }
+ return llvm::Error::success();
+ }
+};
+
+Expected<EntityPointerLevelSet> PointerFlowMatcher::toEPL(const NamedDecl *N,
+ bool IsRet) const {
+ auto Ret = createEntityPointerLevel(N, AddEntity, IsRet);
+
+ if (Ret)
+ return EntityPointerLevelSet{*Ret};
+ return Ret.takeError();
+}
+
+Expected<EntityPointerLevelSet> PointerFlowMatcher::toEPL(const Expr *N) const {
+ return translateEntityPointerLevel(N, Ctx, AddEntity);
+}
+
+llvm::Error
+PointerFlowMatcher::addEdges(Expected<EntityPointerLevelSet> &&LHS,
+ Expected<EntityPointerLevelSet> &&RHS) {
+ if (!LHS && !RHS)
+ return llvm::joinErrors(LHS.takeError(), RHS.takeError());
+ if (!LHS)
+ return LHS.takeError();
+ if (!RHS)
+ return RHS.takeError();
+ for (auto L : *LHS)
+ Results[L].insert(RHS->begin(), RHS->end());
+ return llvm::Error::success();
+}
+
+/// Match and extract pointer flow.
+/// The extraction function 'XF' can be described by the following rules:
+///
+/// XF(l = r) := add edge "toEPL(l) -> toEPL(r))"
+/// XF(foo(a, b, ...)) := XF(Param_1 = a), XF(Param_2 = b), ...
+/// XF(return e;) := XF(FunRet = e), where 'FunRet' is the return
+/// entity of the enclosing
+/// function
+/// XF(ctor(a, ...) : x1(y1), ... {...})
+/// := XF(Param_1 = a), ...,
+/// XF(x1 = y1), ...,
+/// ctor's body will be visited separately.
+/// XF(T var = e) := XF(var = e)
+/// XF(T var = init-list) := see \ref
+/// PointerFlowMatcher::matchInitializerList
+llvm::Error PointerFlowMatcher::matches(const DynTypedNode &DynNode,
+ const NamedDecl *RootDecl) {
+ if (const Stmt *S = DynNode.get<Stmt>())
+ return matchesStmt(S, RootDecl);
+ if (const Decl *D = DynNode.get<Decl>())
+ return matchesDecl(D, RootDecl);
+ return llvm::Error::success();
+}
+
+llvm::Error PointerFlowMatcher::matchesStmt(const Stmt *S,
+ const NamedDecl *RootDecl) {
+ // Match 'p = q' whenever it has pointer or array type:
+ if (const auto *BO = dyn_cast<BinaryOperator>(S);
+ BO && BO->getOpcode() == BO_Assign && hasPtrOrArrType(BO)) {
+ return addEdges(toEPL(BO->getLHS()), toEPL(BO->getRHS()));
+ }
+
+ // Match arg-to-param passing (in CallExpr) for any pointer type argument:
+ if (const auto *CE = dyn_cast<CallExpr>(S)) {
+ const FunctionDecl *FD = CE->getDirectCallee();
+
+ if (!FD)
+ return llvm::Error::success();
+
+ unsigned ArgIdx = 0;
+
+ if (isa<CXXOperatorCallExpr>(CE))
+ if (auto *MD = dyn_cast<CXXMethodDecl>(FD);
+ MD && !MD->isExplicitObjectMemberFunction())
+ ArgIdx = 1;
+ return matchesArgsWithParams(ArgIdx, FD, CE);
+ }
+ // Match arg-to-param passing (in CXXConstructExpr) for any pointer type
+ // argument:
+ if (const auto *CCE = dyn_cast<CXXConstructExpr>(S)) {
+ return matchesArgsWithParams(/*ArgIdxStart=*/0, CCE->getConstructor(), CCE);
+ }
+ if (const auto *RS = dyn_cast<ReturnStmt>(S)) {
+ const Expr *RetExpr = RS->getRetValue();
+ if (!RetExpr || !hasPtrOrArrType(RetExpr))
+ return llvm::Error::success();
+ return addEdges(toEPL(RootDecl, true), toEPL(RetExpr));
+ }
+ return llvm::Error::success();
+}
+
+llvm::Error PointerFlowMatcher::matchesDecl(const Decl *D,
+ const NamedDecl *RootDecl) {
+ const Expr *InitExpr = nullptr;
+
+ if (const auto *VD = dyn_cast<ValueDecl>(D)) {
+ if (const auto *Var = dyn_cast<VarDecl>(VD))
+ InitExpr = Var->getInit();
+ if (const auto *Fd = dyn_cast<FieldDecl>(VD))
+ InitExpr = Fd->getInClassInitializer();
+
+ // Match initializer-list:
+ if (auto *InitLst = dyn_cast_or_null<InitListExpr>(InitExpr))
+ return matchesInitializerList(VD, InitLst);
+
+ // Match initializers to variables/fields of a pointer type:
+ if (InitExpr && hasPtrOrArrType(VD))
+ return addEdges(toEPL(VD), toEPL(InitExpr));
+ }
+
+ // Match C++ constructor member-initializers:
+ if (const auto *CtorD = dyn_cast<CXXConstructorDecl>(D)) {
+ for (auto *E : CtorD->inits()) {
+ if (E->isDelegatingInitializer())
+ return matches(DynTypedNode::create(*E->getInit()), RootDecl);
+ if (const FieldDecl *FD = E->getMember(); FD && hasPtrOrArrType(FD)) {
+ if (auto Err = addEdges(toEPL(E->getMember()), toEPL(E->getInit())))
+ return Err;
+ }
+ }
+ }
+ return llvm::Error::success();
+}
+
+// Helper function for matchInitializerList that handles record:
+llvm::Error matchInitializerListForRecordDecl(PointerFlowMatcher &Matcher,
+ const RecordDecl *RecordTy,
+ const InitListExpr *ILE) {
+ if (auto *CXXRD = dyn_cast<CXXRecordDecl>(RecordTy))
+ if (CXXRD->getNumBases() != 0) {
+ // FIXME: support this:
+ return makeErrAtNode(
+ Matcher.Ctx, ILE,
+ "attempt to create pointer assignment edges between "
+ "CXXRecordDecls with base classes and initializer-lists");
+ }
+ // Handle union:
+ if (RecordTy->isUnion()) {
+ auto *InitField = ILE->getInitializedFieldInUnion();
+
+ if (!InitField)
+ return llvm::Error::success();
+ assert(!ILE->inits().empty());
+ return Matcher.matchesInitializerList(InitField, ILE->getInit(0));
+ }
+ // Handle struct/class:
+ ILE = ILE->isSemanticForm() ? ILE : ILE->getSemanticForm();
+
+ auto FieldIter = RecordTy->field_begin();
+
+ assert(RecordTy->getNumFields() >= ILE->getNumInits());
+ for (auto *Init : ILE->inits())
+ if (auto Err = Matcher.matchesInitializerList(*(FieldIter++), Init))
+ return Err;
+ return llvm::Error::success();
+}
+
+// Helper function for matchInitializerList that handles array:
+llvm::Error matchInitializerListForArray(PointerFlowMatcher &Matcher,
+ const ValueDecl *Array,
+ const InitListExpr *ILE,
+ unsigned ArrayIndirectLevel = 0) {
+ for (auto *E : ILE->inits())
+ if (auto Err =
+ Matcher.matchesInitializerList(Array, E, ArrayIndirectLevel + 1))
+ return Err;
+ return llvm::Error::success();
+}
+
+/// Match initializer lists of the form 'Var = {a, b, c, ...}':
+///
+/// If 'Var' is a struct/union:
+/// XF(Var = {a, b, c, ...}) := XF(Var.field_1 = a)
+/// XF(Var.field_2 = b)
+/// ...
+/// If 'Var' is an array:
+/// XF(Var = {a, b, c, ...}) := XF(*Var = a)
+/// XF(*Var = b)
+/// ...
+///
+/// The process is recursive: 'a', 'b', 'c', ... may themselves be
+/// initializer lists. We therefore use \p ArrayElementIndirectLevel to keep
+/// track of the pointer level the left-hand side.
+llvm::Error
+PointerFlowMatcher::matchesInitializerList(const ValueDecl *Base,
+ const Expr *InitExpr,
+ unsigned ArrayElementIndirectLevel) {
+ const InitListExpr *ILE = dyn_cast<InitListExpr>(InitExpr);
+
+ if (!ILE) {
+ if (!hasPtrOrArrType(InitExpr))
+ return llvm::Error::success();
+
+ auto BaseEPL = toEPL(Base);
+
+ if (!BaseEPL)
+ return BaseEPL.takeError();
+
+ // Apply ArrayElementIndirectLevel to BaseEPL
+ auto R = llvm::map_range(*BaseEPL, [&ArrayElementIndirectLevel](
+ const EntityPointerLevel &EPL) {
+ EntityPointerLevel Result = EPL;
+ for ([[maybe_unused]] auto Ignored : llvm::seq(ArrayElementIndirectLevel))
+ Result = incrementPointerLevel(Result);
+ return Result;
+ });
+ return addEdges(EntityPointerLevelSet{R.begin(), R.end()}, toEPL(InitExpr));
+ }
+ // Note that `Base`'s type is NOT the real LHS type when
+ // ArrayElementIndirectLevel > 0:
+ QualType Type = InitExpr->getType();
+
+ if (auto *RD = Type->getAsRecordDecl())
+ return matchInitializerListForRecordDecl(*this, RD, ILE);
+ if (Type->isArrayType())
+ return matchInitializerListForArray(*this, Base, ILE,
+ ArrayElementIndirectLevel);
+ // Must be the case of using a initializer-list for a scalar:
+ return matchesInitializerList(Base, ILE->getInit(0));
+}
+
+class PointerFlowTUSummaryExtractor : public TUSummaryExtractor {
+public:
+ PointerFlowTUSummaryExtractor(TUSummaryBuilder &Builder)
+ : TUSummaryExtractor(Builder) {}
+
+ EntityId addEntity(const EntityName &EN) {
+ return SummaryBuilder.addEntity(EN);
+ }
+
+ Expected<std::unique_ptr<PointerFlowEntitySummary>>
+ extractEntitySummary(const NamedDecl *Contributor, ASTContext &Ctx) {
+ PointerFlowMatcher Matcher(
+ Ctx, [this](const EntityName &EN) { return addEntity(EN); });
+ auto MatchAction = [&Matcher, &Contributor](const DynTypedNode &Node) {
+ auto Err = Matcher.matches(Node, Contributor);
+
+ if (Err)
+ llvm::report_fatal_error(std::move(Err));
+ };
+
+ findMatchesIn(Contributor, MatchAction);
+ return std::make_unique<PointerFlowEntitySummary>(
+ buildPointerFlowEntitySummary(std::move(Matcher.Results)));
+ }
+
+ void HandleTranslationUnit(ASTContext &Ctx) override {
+ std::vector<const NamedDecl *> Contributors;
+
+ findContributors(Ctx, Contributors);
+ for (auto *CD : Contributors) {
+ auto EntitySummary = extractEntitySummary(CD, Ctx);
+
+ if (!EntitySummary)
+ llvm::reportFatalInternalError(EntitySummary.takeError());
+ assert(*EntitySummary);
+ if ((*EntitySummary)->empty())
+ continue;
+
+ auto ContributorName = getEntityName(CD);
+
+ if (!ContributorName)
+ llvm::reportFatalInternalError(makeEntityNameErr(Ctx, CD));
+
+ auto [Ignored, InsertionSucceeded] = SummaryBuilder.addSummary(
+ addEntity(*ContributorName), std::move(*EntitySummary));
+
+ assert(InsertionSucceeded && "duplicated contributor extraction");
+ }
+ }
+};
+} // namespace
+
+// NOLINTNEXTLINE(misc-use-internal-linkage)
+volatile int PointerFlowTUSummaryExtractorAnchorSource = 0;
+
+static TUSummaryExtractorRegistry::Add<PointerFlowTUSummaryExtractor>
+ RegisterExtractor(PointerFlowEntitySummary::Name,
+ "The TUSummaryExtractor for pointer flow");
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowFormat.cpp b/clang/lib/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowFormat.cpp
new file mode 100644
index 0000000000000..d35cfa626debf
--- /dev/null
+++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowFormat.cpp
@@ -0,0 +1,110 @@
+//===- PointerFlowFormat.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 "SSAFAnalysesCommon.h"
+#include "clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevelFormat.h"
+#include "clang/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlow.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Serialization/JSONFormat.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/iterator_range.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/JSON.h"
+
+using namespace clang;
+using namespace ssaf;
+using Object = llvm::json::Object;
+using Array = llvm::json::Array;
+using Value = llvm::json::Value;
+
+ssaf::PointerFlowEntitySummary
+ssaf::buildPointerFlowEntitySummary(EdgeSet Edges);
+
+llvm::iterator_range<EdgeSet::const_iterator>
+ssaf::getEdges(const PointerFlowEntitySummary &Sum);
+
+namespace {
+constexpr const char *const PointerFlowKey = "PointerFlow";
+} // namespace
+
+// Writes the 'Edges' map as an array of array of EntityPointerLevels:
+// Array [
+// Array [ [src-node], [dest-node], [dest-node], ...]
+// Array [ [src-node], [dest-node], [dest-node], ...]
+// ...
+// ]
+static llvm::json::Object
+summaryToJSON(const EntitySummary &ES,
+ JSONFormat::EntityIdToJSONFn EntityId2JSON) {
+ Array EdgesData;
+
+ for (const auto &Entry :
+ getEdges(static_cast<const PointerFlowEntitySummary &>(ES))) {
+ Array EdgesEntryData;
+ EntityPointerLevel LHS = Entry.first;
+
+ EdgesEntryData.push_back(entityPointerLevelToJSON(LHS, EntityId2JSON));
+ // Add to nodes:
+ for (const auto &RHS : Entry.second)
+ EdgesEntryData.push_back(entityPointerLevelToJSON(RHS, EntityId2JSON));
+ EdgesData.push_back(Value(std::move(EdgesEntryData)));
+ }
+ return Object{{PointerFlowKey, Value(std::move(EdgesData))}};
+}
+
+static llvm::Expected<std::unique_ptr<EntitySummary>>
+summaryFromJSON(const Object &Data, EntityIdTable &,
+ JSONFormat::EntityIdFromJSONFn EntityIdFromJSON) {
+ const Value *EdgesData = Data.get(PointerFlowKey);
+
+ if (!EdgesData)
+ return makeSawButExpectedError(
+ Object(Data), "a JSON object with the key: %s", PointerFlowKey);
+
+ EdgeSet Edges;
+ const auto *EdgesDataAsArr = EdgesData->getAsArray();
+
+ if (!EdgesDataAsArr)
+ return makeSawButExpectedError(
+ *EdgesData, "a JSON array of array of EntityPointerLevels");
+ for (const auto &EdgesEntryData : *EdgesDataAsArr) {
+ const auto *EPLArray = EdgesEntryData.getAsArray();
+
+ if (!EPLArray || EPLArray->size() <= 1)
+ return makeSawButExpectedError(
+ EdgesEntryData, "a JSON array of EntityPointerLevels with a size "
+ "greater than 1: [lhs, rhs, rhs, ...]");
+
+ auto SrcEPL = entityPointerLevelFromJSON((*EPLArray)[0], EntityIdFromJSON);
+
+ if (!SrcEPL)
+ return SrcEPL.takeError();
+ for (const auto &EPLData : llvm::drop_begin(*EPLArray)) {
+ auto EPL = entityPointerLevelFromJSON(EPLData, EntityIdFromJSON);
+ if (!EPL)
+ return EPL.takeError();
+ Edges[*SrcEPL].insert(*EPL);
+ }
+ }
+ return std::make_unique<PointerFlowEntitySummary>(
+ buildPointerFlowEntitySummary(std::move(Edges)));
+}
+
+namespace {
+struct PointerFlowJSONFormatInfo final : JSONFormat::FormatInfo {
+ PointerFlowJSONFormatInfo()
+ : JSONFormat::FormatInfo(PointerFlowEntitySummary::summaryName(),
+ summaryToJSON, summaryFromJSON) {}
+};
+} // namespace
+
+static llvm::Registry<JSONFormat::FormatInfo>::Add<PointerFlowJSONFormatInfo>
+ RegisterPointerFlowJSONFormatInfo(
+ "PointerFlow", "JSON Format info for PointerFlowEntitySummary");
+
+// NOLINTNEXTLINE(misc-use-internal-linkage)
+volatile int PointerFlowSSAFJSONFormatAnchorSource = 0;
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Analyses/SSAFAnalysesCommon.cpp b/clang/lib/ScalableStaticAnalysisFramework/Analyses/SSAFAnalysesCommon.cpp
index 98f12dec6f273..1c2f2f2a9379e 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Analyses/SSAFAnalysesCommon.cpp
+++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/SSAFAnalysesCommon.cpp
@@ -11,6 +11,7 @@
#include "clang/AST/Decl.h"
#include "clang/AST/DeclObjC.h"
#include "clang/AST/DynamicRecursiveASTVisitor.h"
+#include "clang/AST/ExprCXX.h"
#include <set>
using namespace clang;
@@ -34,10 +35,18 @@ class ContributorFinder : public DynamicRecursiveASTVisitor {
bool VisitVarDecl(VarDecl *D) override {
DeclContext *DC = D->getDeclContext();
- if (DC->isFileContext() || DC->isNamespace())
+ // Collects Decl for global variables or static data members:
+ if (DC->isFileContext() || D->isStaticDataMember())
Contributors.insert(D);
return true;
}
+
+ bool VisitLambdaExpr(LambdaExpr *L) override {
+ // TraverseLambdaExpr directly visits the body stmt, skipping the
+ // CXXMethodDecl, which is a contributor that needs to be collected.
+ VisitFunctionDecl(L->getCallOperator());
+ return true;
+ }
};
/// An AST visitor that skips the root node's strict-descendants that are
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsage.cpp b/clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsage.cpp
index ea5d2297b9836..86545762c0121 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsage.cpp
+++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsage.cpp
@@ -7,21 +7,9 @@
//===----------------------------------------------------------------------===//
#include "clang/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsage.h"
-#include "SSAFAnalysesCommon.h"
-#include "clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.h"
-#include "clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevelFormat.h"
-#include "clang/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageTest.h"
-#include "clang/ScalableStaticAnalysisFramework/Core/Serialization/JSONFormat.h"
-#include "llvm/Support/Error.h"
-#include "llvm/Support/JSON.h"
-#include <cstdint>
using namespace clang;
using namespace ssaf;
-using Array = llvm::json::Array;
-using Object = llvm::json::Object;
-
-static constexpr llvm::StringLiteral SummarySerializationKey = "UnsafeBuffers";
UnsafeBufferUsageEntitySummary
ssaf::buildUnsafeBufferUsageEntitySummary(EntityPointerLevelSet UnsafeBuffers) {
@@ -32,79 +20,3 @@ llvm::iterator_range<EntityPointerLevelSet::const_iterator>
ssaf::getUnsafeBuffers(const UnsafeBufferUsageEntitySummary &S) {
return llvm::make_range(S.UnsafeBuffers.begin(), S.UnsafeBuffers.end());
}
-
-static Object serialize(const EntitySummary &S,
- JSONFormat::EntityIdToJSONFn Fn) {
- const auto &SS = static_cast<const UnsafeBufferUsageEntitySummary &>(S);
- Array UnsafeBuffersData;
-
- for (const auto &EPL : getUnsafeBuffers(SS))
- UnsafeBuffersData.push_back(entityPointerLevelToJSON(EPL, Fn));
- return Object{{SummarySerializationKey.data(), std::move(UnsafeBuffersData)}};
-}
-
-static llvm::Expected<std::unique_ptr<EntitySummary>>
-deserializeImpl(const Object &Data, JSONFormat::EntityIdFromJSONFn Fn) {
- const Array *UnsafeBuffersData =
- Data.getArray(SummarySerializationKey.data());
-
- if (!UnsafeBuffersData)
- return makeSawButExpectedError(Object(Data), "an Object with a key %s",
- SummarySerializationKey.data());
-
- EntityPointerLevelSet EPLs;
-
- for (const auto &EltData : *UnsafeBuffersData) {
- llvm::Expected<EntityPointerLevel> EPL =
- entityPointerLevelFromJSON(EltData, Fn);
-
- if (!EPL)
- return EPL.takeError();
- EPLs.insert(*EPL);
- }
- return std::make_unique<UnsafeBufferUsageEntitySummary>(
- buildUnsafeBufferUsageEntitySummary(std::move(EPLs)));
-}
-
-static llvm::Expected<std::unique_ptr<EntitySummary>>
-deserialize(const Object &Data, EntityIdTable &,
- JSONFormat::EntityIdFromJSONFn Fn) {
- return deserializeImpl(Data, Fn);
-}
-
-struct UnsafeBufferUsageJSONFormatInfo : JSONFormat::FormatInfo {
- UnsafeBufferUsageJSONFormatInfo()
- : JSONFormat::FormatInfo(UnsafeBufferUsageEntitySummary::summaryName(),
- serialize, deserialize) {}
-};
-
-static llvm::Registry<JSONFormat::FormatInfo>::Add<
- UnsafeBufferUsageJSONFormatInfo>
- RegisterUnsafeBufferUsageJSONFormatInfo(
- UnsafeBufferUsageEntitySummary::Name,
- "JSON Format info for UnsafeBufferUsageEntitySummary");
-
-// NOLINTNEXTLINE(misc-use-internal-linkage)
-volatile int UnsafeBufferUsageSSAFJSONFormatAnchorSource = 0;
-
-// For unit test:
-llvm::Expected<std::unique_ptr<EntitySummary>>
-ssaf::serializeDeserializeRoundTrip(
- const UnsafeBufferUsageEntitySummary &S,
- std::function<uint64_t(EntityId)> IdToIntFn,
- std::function<llvm::Expected<EntityId>(uint64_t)> IdFromIntFn) {
-
- auto IdToJson = [&IdToIntFn](EntityId Id) -> Object {
- return Object({{"@", IdToIntFn(Id)}});
- };
- auto IdFromJson =
- [&IdFromIntFn](const Object &O) -> llvm::Expected<EntityId> {
- const auto *Int = O.get("@");
-
- if (Int && Int->getAsUINT64())
- return IdFromIntFn(*Int->getAsUINT64());
- return llvm::createStringError("failed to get EntityId from Object");
- };
-
- return deserializeImpl(serialize(S, IdToJson), IdFromJson);
-}
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageExtractor.cpp b/clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageExtractor.cpp
index 550416c353fb7..075e4896deed3 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageExtractor.cpp
+++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageExtractor.cpp
@@ -103,6 +103,6 @@ void clang::ssaf::UnsafeBufferUsageTUSummaryExtractor::HandleTranslationUnit(
volatile int UnsafeBufferUsageTUSummaryExtractorAnchorSource = 0;
static clang::ssaf::TUSummaryExtractorRegistry::Add<
- ssaf::UnsafeBufferUsageTUSummaryExtractor>
+ UnsafeBufferUsageTUSummaryExtractor>
RegisterExtractor(UnsafeBufferUsageEntitySummary::Name,
"The TUSummaryExtractor for unsafe buffer pointers");
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageFormat.cpp b/clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageFormat.cpp
new file mode 100644
index 0000000000000..ce1ef0b3ba691
--- /dev/null
+++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageFormat.cpp
@@ -0,0 +1,108 @@
+//===- UnsafeBufferUsageFormat.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 "SSAFAnalysesCommon.h"
+#include "clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.h"
+#include "clang/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevelFormat.h"
+#include "clang/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsage.h"
+#include "clang/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageTest.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Serialization/JSONFormat.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/JSON.h"
+#include <cstdint>
+
+using namespace clang;
+using namespace ssaf;
+using Array = llvm::json::Array;
+using Object = llvm::json::Object;
+
+static constexpr llvm::StringLiteral SummarySerializationKey = "UnsafeBuffers";
+
+extern UnsafeBufferUsageEntitySummary
+ssaf::buildUnsafeBufferUsageEntitySummary(EntityPointerLevelSet UnsafeBuffers);
+
+extern llvm::iterator_range<EntityPointerLevelSet::const_iterator>
+ssaf::getUnsafeBuffers(const UnsafeBufferUsageEntitySummary &S);
+
+static Object serialize(const EntitySummary &S,
+ JSONFormat::EntityIdToJSONFn Fn) {
+ const auto &SS = static_cast<const UnsafeBufferUsageEntitySummary &>(S);
+ Array UnsafeBuffersData;
+
+ for (const auto &EPL : getUnsafeBuffers(SS))
+ UnsafeBuffersData.push_back(entityPointerLevelToJSON(EPL, Fn));
+ return Object{{SummarySerializationKey.data(), std::move(UnsafeBuffersData)}};
+}
+
+static llvm::Expected<std::unique_ptr<EntitySummary>>
+deserializeImpl(const Object &Data, JSONFormat::EntityIdFromJSONFn Fn) {
+ const Array *UnsafeBuffersData =
+ Data.getArray(SummarySerializationKey.data());
+
+ if (!UnsafeBuffersData)
+ return makeSawButExpectedError(Object(Data), "an Object with a key %s",
+ SummarySerializationKey.data());
+
+ EntityPointerLevelSet EPLs;
+
+ for (const auto &EltData : *UnsafeBuffersData) {
+ llvm::Expected<EntityPointerLevel> EPL =
+ entityPointerLevelFromJSON(EltData, Fn);
+
+ if (!EPL)
+ return EPL.takeError();
+ EPLs.insert(*EPL);
+ }
+ return std::make_unique<UnsafeBufferUsageEntitySummary>(
+ buildUnsafeBufferUsageEntitySummary(std::move(EPLs)));
+}
+
+static llvm::Expected<std::unique_ptr<EntitySummary>>
+deserialize(const Object &Data, EntityIdTable &,
+ JSONFormat::EntityIdFromJSONFn Fn) {
+ return deserializeImpl(Data, Fn);
+}
+
+namespace {
+struct UnsafeBufferUsageJSONFormatInfo final : JSONFormat::FormatInfo {
+ UnsafeBufferUsageJSONFormatInfo()
+ : JSONFormat::FormatInfo(UnsafeBufferUsageEntitySummary::summaryName(),
+ serialize, deserialize) {}
+};
+} // namespace
+
+static llvm::Registry<JSONFormat::FormatInfo>::Add<
+ UnsafeBufferUsageJSONFormatInfo>
+ RegisterUnsafeBufferUsageJSONFormatInfo(
+ UnsafeBufferUsageEntitySummary::Name,
+ "JSON Format info for UnsafeBufferUsageEntitySummary");
+
+// NOLINTNEXTLINE(misc-use-internal-linkage)
+volatile int UnsafeBufferUsageSSAFJSONFormatAnchorSource = 0;
+
+// For unit test:
+llvm::Expected<std::unique_ptr<EntitySummary>>
+ssaf::serializeDeserializeRoundTrip(
+ const UnsafeBufferUsageEntitySummary &S,
+ std::function<uint64_t(EntityId)> IdToIntFn,
+ std::function<llvm::Expected<EntityId>(uint64_t)> IdFromIntFn) {
+
+ auto IdToJson = [&IdToIntFn](EntityId Id) -> Object {
+ return Object({{"@", IdToIntFn(Id)}});
+ };
+ auto IdFromJson =
+ [&IdFromIntFn](const Object &O) -> llvm::Expected<EntityId> {
+ const auto *Int = O.get("@");
+
+ if (Int && Int->getAsUINT64())
+ return IdFromIntFn(*Int->getAsUINT64());
+ return llvm::createStringError("failed to get EntityId from Object");
+ };
+
+ return deserializeImpl(serialize(S, IdToJson), IdFromJson);
+}
diff --git a/clang/lib/ScalableStaticAnalysisFramework/Tool/Utils.cpp b/clang/lib/ScalableStaticAnalysisFramework/Tool/Utils.cpp
index e8dde39a846e9..6a78620b757e6 100644
--- a/clang/lib/ScalableStaticAnalysisFramework/Tool/Utils.cpp
+++ b/clang/lib/ScalableStaticAnalysisFramework/Tool/Utils.cpp
@@ -1,4 +1,4 @@
-//===- Utils.cpp - Shared utilities for SSAF tools -----------------------===//
+//===- Utils.cpp - Shared utilities for SSAF tools ------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
diff --git a/clang/test/Analysis/Scalable/PointerFlow/Inputs/tu-summary-bad-array.json b/clang/test/Analysis/Scalable/PointerFlow/Inputs/tu-summary-bad-array.json
new file mode 100644
index 0000000000000..3848bd68ac0fb
--- /dev/null
+++ b/clang/test/Analysis/Scalable/PointerFlow/Inputs/tu-summary-bad-array.json
@@ -0,0 +1,88 @@
+{
+ "data": [
+ {
+ "summary_data": [
+ {
+ "entity_id": 2,
+ "entity_summary": {
+ "PointerFlow": [
+ [
+ [
+ {
+ "@": 0
+ },
+ 2
+ ]
+ ],
+ [
+ [
+ {
+ "@": 2
+ },
+ 1
+ ],
+ [
+ {
+ "@": 0
+ },
+ 1
+ ]
+ ]
+ ]
+ }
+ }
+ ],
+ "summary_name": "PointerFlow"
+ }
+ ],
+ "id_table": [
+ {
+ "id": 2,
+ "name": {
+ "namespace": [],
+ "suffix": "",
+ "usr": "c:@F at foo#***I#*S0_#I#"
+ }
+ },
+ {
+ "id": 0,
+ "name": {
+ "namespace": [],
+ "suffix": "1",
+ "usr": "c:@F at foo#***I#*S0_#I#"
+ }
+ },
+ {
+ "id": 1,
+ "name": {
+ "namespace": [],
+ "suffix": "2",
+ "usr": "c:@F at foo#***I#*S0_#I#"
+ }
+ }
+ ],
+ "linkage_table": [
+ {
+ "id": 0,
+ "linkage": {
+ "type": "External"
+ }
+ },
+ {
+ "id": 1,
+ "linkage": {
+ "type": "Internal"
+ }
+ },
+ {
+ "id": 2,
+ "linkage": {
+ "type": "Internal"
+ }
+ }
+ ],
+ "tu_namespace": {
+ "kind": "CompilationUnit",
+ "name": "Mock.cpp"
+ }
+}
diff --git a/clang/test/Analysis/Scalable/PointerFlow/Inputs/tu-summary-bad-summary.json b/clang/test/Analysis/Scalable/PointerFlow/Inputs/tu-summary-bad-summary.json
new file mode 100644
index 0000000000000..77c02313b6add
--- /dev/null
+++ b/clang/test/Analysis/Scalable/PointerFlow/Inputs/tu-summary-bad-summary.json
@@ -0,0 +1,65 @@
+{
+ "data": [
+ {
+ "summary_data": [
+ {
+ "entity_id": 2,
+ "entity_summary": {
+ "PointerFlow": {}
+ }
+ }
+ ],
+ "summary_name": "PointerFlow"
+ }
+ ],
+ "id_table": [
+ {
+ "id": 2,
+ "name": {
+ "namespace": [],
+ "suffix": "",
+ "usr": "c:@F at foo#***I#*S0_#I#"
+ }
+ },
+ {
+ "id": 0,
+ "name": {
+ "namespace": [],
+ "suffix": "1",
+ "usr": "c:@F at foo#***I#*S0_#I#"
+ }
+ },
+ {
+ "id": 1,
+ "name": {
+ "namespace": [],
+ "suffix": "2",
+ "usr": "c:@F at foo#***I#*S0_#I#"
+ }
+ }
+ ],
+ "linkage_table": [
+ {
+ "id": 0,
+ "linkage": {
+ "type": "External"
+ }
+ },
+ {
+ "id": 1,
+ "linkage": {
+ "type": "Internal"
+ }
+ },
+ {
+ "id": 2,
+ "linkage": {
+ "type": "Internal"
+ }
+ }
+ ],
+ "tu_namespace": {
+ "kind": "CompilationUnit",
+ "name": "Mock.cpp"
+ }
+}
diff --git a/clang/test/Analysis/Scalable/PointerFlow/Inputs/tu-summary-no-key.json b/clang/test/Analysis/Scalable/PointerFlow/Inputs/tu-summary-no-key.json
new file mode 100644
index 0000000000000..e950edbfa1f3c
--- /dev/null
+++ b/clang/test/Analysis/Scalable/PointerFlow/Inputs/tu-summary-no-key.json
@@ -0,0 +1,94 @@
+{
+ "data": [
+ {
+ "summary_data": [
+ {
+ "entity_id": 2,
+ "entity_summary": {
+ "NoPointerFlow": [
+ [
+ [
+ {
+ "@": 0
+ },
+ 2
+ ],
+ [
+ {
+ "@": 1
+ },
+ 1
+ ]
+ ],
+ [
+ [
+ {
+ "@": 2
+ },
+ 1
+ ],
+ [
+ {
+ "@": 0
+ },
+ 1
+ ]
+ ]
+ ]
+ }
+ }
+ ],
+ "summary_name": "PointerFlow"
+ }
+ ],
+ "id_table": [
+ {
+ "id": 2,
+ "name": {
+ "namespace": [],
+ "suffix": "",
+ "usr": "c:@F at foo#***I#*S0_#I#"
+ }
+ },
+ {
+ "id": 0,
+ "name": {
+ "namespace": [],
+ "suffix": "1",
+ "usr": "c:@F at foo#***I#*S0_#I#"
+ }
+ },
+ {
+ "id": 1,
+ "name": {
+ "namespace": [],
+ "suffix": "2",
+ "usr": "c:@F at foo#***I#*S0_#I#"
+ }
+ }
+ ],
+ "linkage_table": [
+ {
+ "id": 0,
+ "linkage": {
+ "type": "External"
+ }
+ },
+ {
+ "id": 1,
+ "linkage": {
+ "type": "Internal"
+ }
+ },
+ {
+ "id": 2,
+ "linkage": {
+ "type": "Internal"
+ }
+ }
+ ],
+ "tu_namespace": {
+ "kind": "CompilationUnit",
+ "name": "Mock.cpp"
+ }
+}
diff --git a/clang/test/Analysis/Scalable/PointerFlow/Inputs/tu-summary.json b/clang/test/Analysis/Scalable/PointerFlow/Inputs/tu-summary.json
new file mode 100644
index 0000000000000..4347b5851c8ff
--- /dev/null
+++ b/clang/test/Analysis/Scalable/PointerFlow/Inputs/tu-summary.json
@@ -0,0 +1,91 @@
+{
+ "data": [
+ {
+ "summary_data": [
+ {
+ "entity_id": 2,
+ "entity_summary": {
+ "PointerFlow": [
+ [
+ [
+ {
+ "@": 0
+ },
+ 2
+ ],
+ [
+ {
+ "@": 1
+ },
+ 1
+ ]
+ ],
+ [
+ [
+ {
+ "@": 2
+ },
+ 1
+ ],
+ [
+ {
+ "@": 0
+ },
+ 1
+ ]
+ ]
+ ]
+ }
+ }
+ ],
+ "summary_name": "PointerFlow"
+ }
+ ],
+ "id_table": [
+ {
+ "id": 2,
+ "name": {
+ "suffix": "",
+ "usr": "c:@F at foo#***I#*S0_#I#"
+ }
+ },
+ {
+ "id": 0,
+ "name": {
+ "suffix": "1",
+ "usr": "c:@F at foo#***I#*S0_#I#"
+ }
+ },
+ {
+ "id": 1,
+ "name": {
+ "suffix": "2",
+ "usr": "c:@F at foo#***I#*S0_#I#"
+ }
+ }
+ ],
+ "linkage_table": [
+ {
+ "id": 0,
+ "linkage": {
+ "type": "External"
+ }
+ },
+ {
+ "id": 1,
+ "linkage": {
+ "type": "Internal"
+ }
+ },
+ {
+ "id": 2,
+ "linkage": {
+ "type": "Internal"
+ }
+ }
+ ],
+ "tu_namespace": {
+ "kind": "CompilationUnit",
+ "name": "Mock.cpp"
+ }
+}
diff --git a/clang/test/Analysis/Scalable/PointerFlow/tu-summary-serialization.test b/clang/test/Analysis/Scalable/PointerFlow/tu-summary-serialization.test
new file mode 100644
index 0000000000000..e2cec8cb3f350
--- /dev/null
+++ b/clang/test/Analysis/Scalable/PointerFlow/tu-summary-serialization.test
@@ -0,0 +1,27 @@
+// RUN: rm -rf %t.json
+// RUN: clang-ssaf-format -type=tu %S/Inputs/tu-summary.json -o %t.json
+// RUN:
diff %S/Inputs/tu-summary.json %t.json
+
+// Negative tests:
+
+// RUN: not clang-ssaf-format -type=tu %S/Inputs/tu-summary-no-key.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=CHECK-MISSING-KEY
+// CHECK-MISSING-KEY: saw {
+// CHECK-MISSING-KEY: "NoPointerFlow": [
+// CHECK-MISSING-KEY: [
+// CHECK-MISSING-KEY: } but expected a JSON object with the key: PointerFlow
+
+// RUN: not clang-ssaf-format -type=tu %S/Inputs/tu-summary-bad-summary.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=CHECK-BAD-SUMMARY
+// CHECK-BAD-SUMMARY: saw {} but expected a JSON array of array of EntityPointerLevels
+
+// RUN: not clang-ssaf-format -type=tu %S/Inputs/tu-summary-bad-array.json 2>&1 \
+// RUN: | FileCheck %s --check-prefix=CHECK-BAD-ARRAY
+// CHECK-BAD-ARRAY: saw [
+// CHECK-BAD-ARRAY: [
+// CHECK-BAD-ARRAY: {
+// CHECK-BAD-ARRAY: "@": 0
+// CHECK-BAD-ARRAY: },
+// CHECK-BAD-ARRAY: 2
+// CHECK-BAD-ARRAY: ]
+// CHECK-BAD-ARRAY:] but expected a JSON array of EntityPointerLevels with a size greater than 1: [lhs, rhs, rhs, ...]
diff --git a/clang/test/Analysis/Scalable/ssaf-format/list.test b/clang/test/Analysis/Scalable/ssaf-format/list.test
index 82811a97c40c5..34181845ba3b5 100644
--- a/clang/test/Analysis/Scalable/ssaf-format/list.test
+++ b/clang/test/Analysis/Scalable/ssaf-format/list.test
@@ -9,3 +9,4 @@
// CHECK-DAG: Analyses:
// CHECK-DAG: [[NthFormat]].{{[0-9]+}}. CallGraph - JSON Format info for CallGraph summary
// CHECK-DAG: [[NthFormat]].{{[0-9]+}}. UnsafeBufferUsage - JSON Format info for UnsafeBufferUsageEntitySummary
+// CHECK-DAG: [[NthFormat]].{{[0-9]+}}. PointerFlow - JSON Format info for PointerFlowEntitySummary
diff --git a/clang/unittests/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowTest.cpp b/clang/unittests/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowTest.cpp
new file mode 100644
index 0000000000000..2d6a958ce4eac
--- /dev/null
+++ b/clang/unittests/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowTest.cpp
@@ -0,0 +1,1131 @@
+//===- PointerFlowTest.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/PointerFlow/PointerFlow.h"
+#include "TestFixture.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/DeclCXX.h"
+#include "clang/AST/DynamicRecursiveASTVisitor.h"
+#include "clang/AST/ExprCXX.h"
+#include "clang/Frontend/ASTUnit.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/ASTEntityMapping.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Model/EntityId.h"
+#include "clang/ScalableStaticAnalysisFramework/Core/Model/EntityName.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/Support/Casting.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include <memory>
+#include <type_traits>
+#include <variant>
+
+using namespace clang;
+using namespace ssaf;
+
+namespace {
+// Use FindEntityByName to identify entities in unit tests.
+// Unit tests are simple enough to meet the following assumptions:
+// - Named declarations should have unique names, they can be found by comparing
+// names with strings;
+// - Lambdas should initialize a variable named "X", they can be found using
+// "LambdaOfVar("X")";
+// - CXX Ctors should have unique combination of names and number of parameters,
+// they can be found using "CXXCtorOfNumParms(name, numParms)".
+struct LambdaOfVar {
+ StringRef VarName;
+};
+
+struct CXXCtorOfNumParms {
+ StringRef CXXCtorName;
+ unsigned NumParms;
+};
+
+using FindEntityByName =
+ std::variant<StringRef, CXXCtorOfNumParms, LambdaOfVar>;
+
+template <typename... Ts> struct Overloaded : Ts... {
+ using Ts::operator()...;
+};
+template <typename... Ts> Overloaded(Ts...) -> Overloaded<Ts...>;
+
+StringRef toStringRef(const FindEntityByName &N) {
+ return std::visit(
+ Overloaded{
+ [](StringRef S) -> StringRef { return S; },
+ [](const CXXCtorOfNumParms &L) -> StringRef { return L.CXXCtorName; },
+ [](const LambdaOfVar &L) -> StringRef { return L.VarName; },
+ },
+ N);
+}
+
+const NamedDecl *matchNamedDeclByFindEntityByName(const FindEntityByName &N,
+ const NamedDecl *D) {
+ return std::visit(
+ Overloaded{
+ [&D](StringRef S) -> const NamedDecl * {
+ if (D->getNameAsString() == S)
+ return D;
+ return nullptr;
+ },
+ [&D](const CXXCtorOfNumParms &L) -> const NamedDecl * {
+ if (auto *CD = dyn_cast<CXXConstructorDecl>(D)) {
+ if (CD->getNameAsString() == L.CXXCtorName &&
+ CD->getNumParams() == L.NumParms)
+ return D;
+ }
+ return nullptr;
+ },
+ [&D](const LambdaOfVar &L) -> const NamedDecl * {
+ if (const auto *VD = dyn_cast<VarDecl>(D); VD && VD->getInit()) {
+ const Expr *Init = VD->getInit()->IgnoreUnlessSpelledInSource();
+ if (isa<LambdaExpr>(Init) && VD->getNameAsString() == L.VarName)
+ return cast<LambdaExpr>(Init)->getCallOperator();
+ }
+ return nullptr;
+ },
+ },
+ N);
+}
+
+template <typename SomeDecl = NamedDecl,
+ typename = std::enable_if_t<std::is_base_of_v<NamedDecl, SomeDecl>>>
+const SomeDecl *findEntityByName(FindEntityByName Name, ASTContext &Ctx) {
+ class NamedDeclFinder : public DynamicRecursiveASTVisitor {
+ public:
+ FindEntityByName SearchingName;
+ const SomeDecl *FoundDecl = nullptr;
+
+ NamedDeclFinder(FindEntityByName SearchingName)
+ : SearchingName(SearchingName) {}
+
+ bool VisitDecl(Decl *D) override {
+ if (auto *ND = dyn_cast<NamedDecl>(D)) {
+ FoundDecl = llvm::dyn_cast_or_null<SomeDecl>(
+ matchNamedDeclByFindEntityByName(SearchingName, ND));
+ if (FoundDecl)
+ return false;
+ }
+ return true;
+ }
+ };
+
+ NamedDeclFinder Finder(Name);
+
+ Finder.TraverseDecl(Ctx.getTranslationUnitDecl());
+ return dyn_cast_or_null<SomeDecl>(Finder.FoundDecl);
+}
+
+const FunctionDecl *findFnByName(FindEntityByName Name, ASTContext &Ctx) {
+ return findEntityByName<FunctionDecl>(Name, Ctx);
+}
+
+// Same as `std::pair<StringName, unsigned>` for a pair of entity declaration
+// name and a pointer level with an extra optional flag for whether the entity
+// represents a function return value. This structure is used to explicitly
+// spell out components of an EPL such as "{"p", 1}" or "{"foo_fn", 2, true}".
+struct EPLPair {
+ EPLPair(FindEntityByName Name, unsigned Lv, bool isFunRet = false)
+ : Name(Name), Lv(Lv), isFunRet(isFunRet) {}
+
+ FindEntityByName Name;
+ unsigned Lv;
+ bool isFunRet;
+};
+
+class PointerFlowTest : public TestFixture {
+protected:
+ TUSummary TUSum;
+ TUSummaryBuilder Builder;
+ std::unique_ptr<TUSummaryExtractor> Extractor;
+ std::unique_ptr<ASTUnit> AST;
+
+ PointerFlowTest()
+ : TUSum(BuildNamespace(BuildNamespaceKind::CompilationUnit, "Mock.cpp")),
+ Builder(TUSum), Extractor(nullptr) {}
+
+ template <typename ContributorDecl = NamedDecl,
+ typename =
+ std::enable_if_t<std::is_base_of_v<NamedDecl, ContributorDecl>>>
+ bool setUpTest(StringRef Code) {
+ AST = tooling::buildASTFromCodeWithArgs(
+ Code, {"-Wno-unused-value", "-Wno-int-to-pointer-cast"});
+
+ for (auto &E : clang::ssaf::TUSummaryExtractorRegistry::entries()) {
+ if (E.getName() == PointerFlowEntitySummary::Name) {
+ Extractor = E.instantiate(Builder);
+ break;
+ }
+ }
+
+ if (!Extractor) {
+ ADD_FAILURE() << "failed to find PointerFlowTUSummaryExtractor";
+ return false;
+ }
+ Extractor->HandleTranslationUnit(AST->getASTContext());
+ return true;
+ }
+
+ template <typename ContributorDecl = NamedDecl>
+ const PointerFlowEntitySummary *getEntitySummary(FindEntityByName Name) {
+ const auto *ContributorDefn =
+ findEntityByName<ContributorDecl>(Name, AST->getASTContext());
+
+ if (!ContributorDefn) {
+ ADD_FAILURE() << "failed to find Decl of \"" << toStringRef(Name) << "\"";
+ return nullptr;
+ }
+
+ std::optional<EntityName> EN = getEntityName(ContributorDefn);
+
+ if (!EN) {
+ ADD_FAILURE() << "failed to get EntityName for contributor \""
+ << toStringRef(Name) << "\"";
+ return nullptr;
+ }
+
+ EntityId ContributorEntityId = Builder.addEntity(*EN);
+ auto &TUSumData = getData(TUSum);
+ auto EntitiesSumIter =
+ TUSumData.find(PointerFlowEntitySummary::summaryName());
+
+ // If none entity summary was collected, it may not be an entry in
+ // `TUSumData`:
+ if (EntitiesSumIter == TUSumData.end())
+ return nullptr;
+
+ auto EntitySumIter = EntitiesSumIter->second.find(ContributorEntityId);
+
+ // If entity summary is empty, it may not exist:
+ if (EntitySumIter == EntitiesSumIter->second.end())
+ return nullptr;
+ return static_cast<const PointerFlowEntitySummary *>(
+ EntitySumIter->second.get());
+ }
+
+public:
+ std::optional<EntityId> getEntityId(FindEntityByName Name) {
+ if (const auto *D = findEntityByName(Name, AST->getASTContext())) {
+ if (auto EntityName = getEntityName(D))
+ return Builder.addEntity(*EntityName);
+ }
+ return std::nullopt;
+ }
+
+ std::optional<EntityId> getEntityIdForReturn(FindEntityByName FunName) {
+ if (const auto *D = findFnByName(FunName, AST->getASTContext())) {
+ if (auto EntityName = getEntityNameForReturn(D))
+ return Builder.addEntity(*EntityName);
+ }
+ return std::nullopt;
+ }
+
+ EdgeSet makeEdges(unsigned Line, ArrayRef<std::pair<EPLPair, EPLPair>> Edges);
+};
+
+// 'ToEPL(Test, Line)' is a lambda that converts a 'EPLPair' to a
+// 'EntityPointerLevel':
+static constexpr auto ToEPL =
+ [](PointerFlowTest *Test,
+ unsigned Line) -> std::function<EntityPointerLevel(const EPLPair &)> {
+ return [Test, Line](const EPLPair &Pair) -> EntityPointerLevel {
+ std::optional<EntityId> Entity = Pair.isFunRet
+ ? Test->getEntityIdForReturn(Pair.Name)
+ : Test->getEntityId(Pair.Name);
+ if (!Entity) {
+ ADD_FAILURE_AT(__FILE__, Line)
+ << "Entity not found: " << toStringRef(Pair.Name);
+ }
+ return buildEntityPointerLevel(*Entity, Pair.Lv);
+ };
+};
+
+EdgeSet
+PointerFlowTest::makeEdges(unsigned Line,
+ ArrayRef<std::pair<EPLPair, EPLPair>> Edges) {
+ EdgeSet Result;
+ for (auto Edge : Edges)
+ Result[ToEPL(this, Line)(Edge.first)].insert(
+ ToEPL(this, Line)(Edge.second));
+ return Result;
+}
+
+TEST_F(PointerFlowTest, IsExtractorRegisteredTest) {
+ EXPECT_TRUE(isTUSummaryExtractorRegistered("PointerFlow"));
+}
+
+//////////////////////////////////////////////////////////////
+// Simple Assign Tests //
+//////////////////////////////////////////////////////////////
+TEST_F(PointerFlowTest, SimpleAssign) {
+ ASSERT_EQ(setUpTest(R"cpp(
+ void foo(int *p, int *q) {
+ q = p;
+ }
+ )cpp"),
+ true);
+
+ auto *Sum = getEntitySummary("foo");
+
+ ASSERT_NE(Sum, nullptr);
+ EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"q", 1U}, {"p", 1U}}}));
+}
+
+TEST_F(PointerFlowTest, AssignWithSubscriptLHS) {
+ ASSERT_EQ(setUpTest(R"cpp(
+ void foo(int **q, int *p, int x) {
+ q[x] = p;
+ }
+ )cpp"),
+ true);
+
+ auto *Sum = getEntitySummary("foo");
+
+ ASSERT_NE(Sum, nullptr);
+ EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"q", 2U}, {"p", 1U}}}));
+}
+
+TEST_F(PointerFlowTest, AssignWithPtrArithRHS) {
+ ASSERT_EQ(setUpTest(R"cpp(
+ void foo(int *p, int *q) {
+ q = p + 5;
+ }
+ )cpp"),
+ true);
+
+ auto *Sum = getEntitySummary("foo");
+
+ ASSERT_NE(Sum, nullptr);
+ EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"q", 1U}, {"p", 1U}}}));
+}
+
+TEST_F(PointerFlowTest, AssignInSubscript) {
+ ASSERT_EQ(setUpTest(R"cpp(
+ void foo(int *p, int *q) {
+ (q = p)[5];
+ }
+ )cpp"),
+ true);
+
+ auto *Sum = getEntitySummary("foo");
+
+ ASSERT_NE(Sum, nullptr);
+ EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"q", 1U}, {"p", 1U}}}));
+}
+
+TEST_F(PointerFlowTest, MultipleAssign) {
+ ASSERT_EQ(setUpTest(R"cpp(
+ void foo(int *p, int *q, int *r) {
+ q = p;
+ r = q;
+ }
+ )cpp"),
+ true);
+
+ auto *Sum = getEntitySummary("foo");
+
+ ASSERT_NE(Sum, nullptr);
+ EXPECT_EQ(*Sum, makeEdges(__LINE__, {
+ {{"q", 1U}, {"p", 1U}},
+ {{"r", 1U}, {"q", 1U}},
+ }));
+}
+
+TEST_F(PointerFlowTest, ChainedAssign) {
+ ASSERT_EQ(setUpTest(R"cpp(
+ void foo(int *p, int *q, int *r) {
+ r = q = p;
+ }
+ )cpp"),
+ true);
+
+ auto *Sum = getEntitySummary("foo");
+
+ ASSERT_NE(Sum, nullptr);
+ EXPECT_EQ(*Sum, makeEdges(__LINE__, {
+ {{"q", 1U}, {"p", 1U}},
+ {{"r", 1U}, {"q", 1U}},
+ }));
+}
+
+TEST_F(PointerFlowTest, CastToRValue) {
+ ASSERT_EQ(setUpTest(R"cpp(
+ void foo(int *p, int *q) {
+ q = static_cast<int *&&>(p);
+ }
+ )cpp"),
+ true);
+
+ auto *Sum = getEntitySummary("foo");
+
+ ASSERT_NE(Sum, nullptr);
+ EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"q", 1U}, {"p", 1U}}}));
+}
+
+TEST_F(PointerFlowTest, AssignToMember) {
+ ASSERT_EQ(setUpTest(R"cpp(
+ struct S { int *field; };
+ void foo(S s, int *p) {
+ s.field = p;
+ }
+ )cpp"),
+ true);
+
+ auto *Sum = getEntitySummary("foo");
+
+ ASSERT_NE(Sum, nullptr);
+ EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"field", 1U}, {"p", 1U}}}));
+}
+
+TEST_F(PointerFlowTest, AssignToMember2) {
+ ASSERT_EQ(setUpTest(R"cpp(
+ struct S { int *field; };
+ void foo(S *s, int *p) {
+ s->field = p;
+ }
+ )cpp"),
+ true);
+
+ auto *Sum = getEntitySummary("foo");
+
+ ASSERT_NE(Sum, nullptr);
+ EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"field", 1U}, {"p", 1U}}}));
+}
+
+//////////////////////////////////////////////////////////////
+// Call Expr Tests. //
+//////////////////////////////////////////////////////////////
+TEST_F(PointerFlowTest, CallArg) {
+ ASSERT_EQ(setUpTest(R"cpp(
+ void bar(int *param);
+ void foo(int *p) {
+ bar(p);
+ }
+ )cpp"),
+ true);
+
+ auto *Sum = getEntitySummary("foo");
+
+ ASSERT_NE(Sum, nullptr);
+ EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"param", 1U}, {"p", 1U}}}));
+}
+
+TEST_F(PointerFlowTest, CallMultiArgs) {
+ ASSERT_EQ(setUpTest(R"cpp(
+ void bar(int *param1, int y, int *param2);
+ void foo(int *p, int x, int *q) {
+ bar(p, x, q);
+ }
+ )cpp"),
+ true);
+
+ auto *Sum = getEntitySummary("foo");
+
+ ASSERT_NE(Sum, nullptr);
+ EXPECT_EQ(*Sum, makeEdges(__LINE__, {
+ {{"param1", 1U}, {"p", 1U}},
+ {{"param2", 1U}, {"q", 1U}},
+ }));
+}
+
+TEST_F(PointerFlowTest, CallAsCallArg) {
+ ASSERT_EQ(setUpTest(R"cpp(
+
+ int *bar(int * w);
+ void foo(int * p) {
+ foo(bar(p));
+ }
+ )cpp"),
+ true);
+
+ auto *Sum = getEntitySummary("foo");
+
+ ASSERT_NE(Sum, nullptr);
+ EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"w", 1U}, {"p", 1U}},
+ {{"p", 1U}, {"bar", 1U, true}}}));
+}
+
+TEST_F(PointerFlowTest, CXXOperatorCallMultiArgs) {
+ ASSERT_EQ(setUpTest(R"cpp(
+ struct S {
+ int* operator()(int *a, int *b);
+ };
+ void foo(S obj, int *p, int *q) {
+ foo(obj, obj(p, q), obj(p, q));
+ }
+ )cpp"),
+ true);
+
+ auto *Sum = getEntitySummary("foo");
+
+ ASSERT_NE(Sum, nullptr);
+ EXPECT_EQ(*Sum, makeEdges(__LINE__, {
+ {{"a", 1U}, {"p", 1U}},
+ {{"b", 1U}, {"q", 1U}},
+ {{"p", 1U}, {"operator()", 1U, true}},
+ {{"q", 1U}, {"operator()", 1U, true}},
+ }));
+}
+
+TEST_F(PointerFlowTest, CXXMemberCall) {
+ ASSERT_EQ(setUpTest(R"cpp(
+ struct S {
+ int* method(int *a, int *b);
+ };
+ void foo(S obj, int *p, int *q) {
+ foo(obj, obj.method(p, q), obj.method(p, q));
+ }
+ )cpp"),
+ true);
+
+ auto *Sum = getEntitySummary("foo");
+
+ ASSERT_NE(Sum, nullptr);
+ EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"a", 1U}, {"p", 1U}},
+ {{"b", 1U}, {"q", 1U}},
+ {{"p", 1U}, {"method", 1U, true}},
+ {{"q", 1U}, {"method", 1U, true}}}));
+}
+
+TEST_F(PointerFlowTest, VirtualMethodCall) {
+ ASSERT_EQ(setUpTest(R"cpp(
+ struct Base {
+ virtual void method(int *a);
+ };
+ void foo(Base &obj, int *p) {
+ obj.method(p);
+ }
+ )cpp"),
+ true);
+
+ auto *Sum = getEntitySummary("foo");
+
+ ASSERT_NE(Sum, nullptr);
+ EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"a", 1U}, {"p", 1U}}}));
+}
+
+TEST_F(PointerFlowTest, StaticMethodCall) {
+ ASSERT_EQ(setUpTest(R"cpp(
+ struct S {
+ static void method(int *a, int *b);
+ };
+ void foo(int *p, int *q) {
+ S::method(p, q);
+ }
+ )cpp"),
+ true);
+
+ auto *Sum = getEntitySummary("foo");
+
+ ASSERT_NE(Sum, nullptr);
+ EXPECT_EQ(*Sum, makeEdges(__LINE__, {
+ {{"a", 1U}, {"p", 1U}},
+ {{"b", 1U}, {"q", 1U}},
+ }));
+}
+
+TEST_F(PointerFlowTest, DefaultArg) {
+ ASSERT_EQ(setUpTest(R"cpp(
+ int *g;
+ void bar(int *a, int *b = g);
+ void foo(int *p) {
+ bar(p);
+ }
+ )cpp"),
+ true);
+
+ auto *Sum = getEntitySummary("foo");
+
+ ASSERT_NE(Sum, nullptr);
+ EXPECT_EQ(*Sum, makeEdges(__LINE__,
+ {{{"a", 1U}, {"p", 1U}}, {{"b", 1U}, {"g", 1U}}}));
+}
+
+// Counter-example for the concern that matchArgsWithParams could go OOB
+// when fewer explicit args are provided than params (due to default args).
+// In Clang's AST, CallExpr::getNumArgs() always includes CXXDefaultArgExpr
+// nodes for defaulted parameters, so getNumArgs() >= getNumParams() holds.
+TEST_F(PointerFlowTest, AllArgsDefaulted) {
+ ASSERT_EQ(setUpTest(R"cpp(
+ int *g1, *g2;
+ void bar(int *a = g1, int *b = g2);
+ void foo() {
+ bar();
+ }
+ )cpp"),
+ true);
+
+ auto *Sum = getEntitySummary("foo");
+
+ ASSERT_NE(Sum, nullptr);
+ EXPECT_EQ(*Sum, makeEdges(__LINE__, {
+ {{"a", 1U}, {"g1", 1U}},
+ {{"b", 1U}, {"g2", 1U}},
+ }));
+}
+
+TEST_F(PointerFlowTest, DefaultArg2) {
+ ASSERT_EQ(setUpTest(R"cpp(
+ int *g;
+ void bar(int *a, int *b = g);
+ void foo(int *p) {
+ bar(p, p);
+ }
+ )cpp"),
+ true);
+
+ auto *Sum = getEntitySummary("foo");
+
+ ASSERT_NE(Sum, nullptr);
+ EXPECT_EQ(*Sum, makeEdges(__LINE__, {
+ {{"a", 1U}, {"p", 1U}},
+ {{"b", 1U}, {"p", 1U}},
+ }));
+}
+
+//////////////////////////////////////////////////////////////
+// CXX Ctor Tests. //
+//////////////////////////////////////////////////////////////
+TEST_F(PointerFlowTest, CXXCtorCallMultiArgs) {
+ ASSERT_EQ(setUpTest(R"cpp(
+ struct S {
+ S(int *a, int *b) {}
+ };
+ void foo(int *p, int *q) {
+ S s{p, q};
+ }
+ )cpp"),
+ true);
+
+ auto *Sum = getEntitySummary("foo");
+
+ ASSERT_NE(Sum, nullptr);
+ EXPECT_EQ(*Sum, makeEdges(__LINE__, {
+ {{"a", 1U}, {"p", 1U}},
+ {{"b", 1U}, {"q", 1U}},
+ }));
+}
+
+TEST_F(PointerFlowTest, CXXCtorCallMultiArgs2) {
+ ASSERT_EQ(setUpTest(R"cpp(
+ struct S {
+ S(int *a, int x, int *b) {}
+ };
+ void foo(int *p, int x, int *q) {
+ S s{p, x, q};
+ }
+ )cpp"),
+ true);
+
+ auto *Sum = getEntitySummary("foo");
+
+ ASSERT_NE(Sum, nullptr);
+ EXPECT_EQ(*Sum, makeEdges(__LINE__, {
+ {{"a", 1U}, {"p", 1U}},
+ {{"b", 1U}, {"q", 1U}},
+ }));
+}
+
+TEST_F(PointerFlowTest, CXXCtorCallAsCallArg) {
+ ASSERT_EQ(setUpTest(R"cpp(
+ struct Wrapper {
+ Wrapper(int *q) {}
+ };
+ void bar(Wrapper w);
+ void foo(int *p) {
+ bar(Wrapper{p});
+ }
+ )cpp"),
+ true);
+
+ auto *Sum = getEntitySummary("foo");
+
+ ASSERT_NE(Sum, nullptr);
+ EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"q", 1U}, {"p", 1U}}}));
+}
+
+TEST_F(PointerFlowTest, DelegatingCXXCtorCall) {
+ ASSERT_EQ(setUpTest(R"cpp(
+ struct S {
+ S(int *a, int *b) {}
+ S(int *p) : S(p, p) {}
+ };
+ )cpp"),
+ true);
+
+ auto *Sum = getEntitySummary<CXXConstructorDecl>(CXXCtorOfNumParms{"S", 1});
+
+ ASSERT_NE(Sum, nullptr);
+ EXPECT_EQ(*Sum, makeEdges(__LINE__, {
+ {{"a", 1U}, {"p", 1U}},
+ {{"b", 1U}, {"p", 1U}},
+ }));
+}
+
+TEST_F(PointerFlowTest, CXXCtorBaseInit) {
+ ASSERT_EQ(setUpTest(R"cpp(
+ struct Base {
+ Base(int *a) {}
+ };
+ struct Derived : Base {
+ Derived(int *p) : Base(p) {}
+ };
+ )cpp"),
+ true);
+
+ auto *Sum = getEntitySummary<CXXConstructorDecl>("Derived");
+
+ ASSERT_NE(Sum, nullptr);
+ EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"a", 1U}, {"p", 1U}}}));
+}
+
+//////////////////////////////////////////////////////////////
+// Initializers Tests. //
+//////////////////////////////////////////////////////////////
+TEST_F(PointerFlowTest, LocalVarDeclInit) {
+ ASSERT_EQ(setUpTest(R"cpp(
+ void foo(int *p) {
+ int *q = p;
+ }
+ )cpp"),
+ true);
+
+ auto *Sum = getEntitySummary("foo");
+
+ ASSERT_NE(Sum, nullptr);
+ EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"q", 1U}, {"p", 1U}}}));
+}
+
+TEST_F(PointerFlowTest, LocalVarDeclInit2) {
+ ASSERT_EQ(setUpTest(R"cpp(
+ void foo(int (*arr)[10]) {
+ int (*p)[10] = arr;
+ }
+ )cpp"),
+ true);
+
+ auto *Sum = getEntitySummary("foo");
+
+ ASSERT_NE(Sum, nullptr);
+ EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"p", 1U}, {"arr", 1U}}}));
+}
+
+TEST_F(PointerFlowTest, FieldInit) {
+ ASSERT_EQ(setUpTest(R"cpp(
+ void foo(int *p) {
+ struct Bar {
+ int *field = p;
+ };
+ }
+ )cpp"),
+ true);
+
+ auto *Sum = getEntitySummary("Bar");
+
+ ASSERT_NE(Sum, nullptr);
+ EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"field", 1U}, {"p", 1U}}}));
+}
+
+TEST_F(PointerFlowTest, CXXCtorMemberInit) {
+ StringRef Code = R"cpp(
+ void foo(int *p) {
+ struct Bar {
+ int *member;
+ Bar(int *q) : member(q) {}
+ };
+ Bar B{p};
+ }
+ )cpp";
+
+ ASSERT_EQ(setUpTest(Code), true);
+ auto *Sum = getEntitySummary<CXXConstructorDecl>("Bar");
+
+ ASSERT_NE(Sum, nullptr);
+ EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"member", 1U}, {"q", 1U}}}));
+
+ Sum = getEntitySummary("foo");
+
+ ASSERT_NE(Sum, nullptr);
+ EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"q", 1U}, {"p", 1U}}}));
+}
+
+TEST_F(PointerFlowTest, GlobalVarInit) {
+ ASSERT_EQ(setUpTest(R"cpp(
+ int *q;
+ int *g = q;
+ )cpp"),
+ true);
+
+ auto *Sum = getEntitySummary<VarDecl>("g");
+
+ ASSERT_NE(Sum, nullptr);
+ EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"g", 1U}, {"q", 1U}}}));
+}
+
+TEST_F(PointerFlowTest, StaticLocalInit) {
+ ASSERT_EQ(setUpTest(R"cpp(
+ void foo(int *p) {
+ static int *s = p;
+ }
+ )cpp"),
+ true);
+
+ auto *Sum = getEntitySummary("foo");
+
+ ASSERT_NE(Sum, nullptr);
+ EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"s", 1U}, {"p", 1U}}}));
+}
+
+TEST_F(PointerFlowTest, StaticMemberInit) {
+ ASSERT_EQ(setUpTest(R"cpp(
+ int *g;
+ struct S { static int *member; };
+ int *S::member = g;
+ )cpp"),
+ true);
+
+ auto *Sum = getEntitySummary<VarDecl>("member");
+
+ ASSERT_NE(Sum, nullptr);
+ EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"member", 1U}, {"g", 1U}}}));
+}
+
+//////////////////////////////////////////////////////////////
+// InitList Tests. //
+//////////////////////////////////////////////////////////////
+
+TEST_F(PointerFlowTest, ArrayInitList) {
+ ASSERT_EQ(setUpTest(R"cpp(
+ void foo(int *p, int *q) {
+ int *arr[] = {p, q};
+ }
+ )cpp"),
+ true);
+
+ auto *Sum = getEntitySummary("foo");
+
+ ASSERT_NE(Sum, nullptr);
+ EXPECT_EQ(*Sum, makeEdges(__LINE__, {
+ {{"arr", 2U}, {"p", 1U}},
+ {{"arr", 2U}, {"q", 1U}},
+ }));
+}
+
+TEST_F(PointerFlowTest, StructInitList) {
+ ASSERT_EQ(setUpTest(R"cpp(
+ struct S { int *a; int *b; };
+ void foo(int *p, int *q) {
+ S s = {p, q};
+ }
+ )cpp"),
+ true);
+
+ auto *Sum = getEntitySummary("foo");
+
+ ASSERT_NE(Sum, nullptr);
+ EXPECT_EQ(*Sum, makeEdges(__LINE__, {
+ {{"a", 1U}, {"p", 1U}},
+ {{"b", 1U}, {"q", 1U}},
+ }));
+}
+
+// A union initialized with a brace-enclosed initializer:
+TEST_F(PointerFlowTest, UnionInitList) {
+ ASSERT_EQ(setUpTest(R"cpp(
+ union U { int *x; int y; };
+ void foo(int *p) {
+ U u = {p};
+ }
+ )cpp"),
+ true);
+
+ auto *Sum = getEntitySummary("foo");
+
+ ASSERT_NE(Sum, nullptr);
+ EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"x", 1U}, {"p", 1U}}}));
+}
+
+TEST_F(PointerFlowTest, NestedInitList) {
+ ASSERT_EQ(setUpTest(R"cpp(
+ struct Inner { int * a; int * b; };
+ struct S { Inner c; int * d; };
+ void foo(int *p, int *q, int *r) {
+ S s = {{p, q}, r};
+ }
+ )cpp"),
+ true);
+
+ auto *Sum = getEntitySummary("foo");
+
+ ASSERT_NE(Sum, nullptr);
+ EXPECT_EQ(*Sum, makeEdges(__LINE__, {
+ {{"a", 1U}, {"p", 1U}},
+ {{"b", 1U}, {"q", 1U}},
+ {{"d", 1U}, {"r", 1U}},
+ }));
+}
+
+TEST_F(PointerFlowTest, NestedInitList2) {
+ ASSERT_EQ(setUpTest(R"cpp(
+ union Inner { int * a; int b; };
+ struct S { Inner c; int * d; };
+ void foo(int *p, int *q) {
+ S s = {{p}, q};
+ }
+ )cpp"),
+ true);
+
+ auto *Sum = getEntitySummary("foo");
+
+ ASSERT_NE(Sum, nullptr);
+ EXPECT_EQ(*Sum, makeEdges(__LINE__, {
+ {{"a", 1U}, {"p", 1U}},
+ {{"d", 1U}, {"q", 1U}},
+ }));
+}
+
+TEST_F(PointerFlowTest, NestedInitList3) {
+ ASSERT_EQ(setUpTest(R"cpp(
+ struct Inner { int * a; int * b; };
+ union S { Inner c; int * d; };
+ void foo(int *p, int *q) {
+ S s = {{p, q}};
+ }
+ )cpp"),
+ true);
+
+ auto *Sum = getEntitySummary("foo");
+
+ ASSERT_NE(Sum, nullptr);
+ EXPECT_EQ(*Sum, makeEdges(__LINE__, {
+ {{"a", 1U}, {"p", 1U}},
+ {{"b", 1U}, {"q", 1U}},
+ }));
+}
+
+TEST_F(PointerFlowTest, NestedArrayInitList) {
+ ASSERT_EQ(setUpTest(R"cpp(
+ void foo(int *p, int *q, int *r, int *s) {
+ int *arr[][2] = {{p, q}, {r, s}};
+ }
+ )cpp"),
+ true);
+
+ auto *Sum = getEntitySummary("foo");
+
+ ASSERT_NE(Sum, nullptr);
+ EXPECT_EQ(*Sum, makeEdges(__LINE__, {
+ {{"arr", 3U}, {"p", 1U}},
+ {{"arr", 3U}, {"q", 1U}},
+ {{"arr", 3U}, {"r", 1U}},
+ {{"arr", 3U}, {"s", 1U}},
+ }));
+}
+
+TEST_F(PointerFlowTest, MixedNestedArrayStructInitList) {
+ ASSERT_EQ(setUpTest(R"cpp(
+ struct T { int *arr[2]; };
+ void foo(int *p, int *q, int *r, int *s) {
+ T t[2] = {{p, q}, {r, s}};
+ }
+ )cpp"),
+ true);
+
+ auto *Sum = getEntitySummary("foo");
+
+ ASSERT_NE(Sum, nullptr);
+ EXPECT_EQ(*Sum, makeEdges(__LINE__, {
+ {{"arr", 2U}, {"p", 1U}},
+ {{"arr", 2U}, {"q", 1U}},
+ {{"arr", 2U}, {"r", 1U}},
+ {{"arr", 2U}, {"s", 1U}},
+ }));
+}
+
+TEST_F(PointerFlowTest, ArrayOfStructInitList) {
+ ASSERT_EQ(setUpTest(R"cpp(
+ struct S { int *a; int *b; };
+ void foo(int *p, int *q, int *r, int *s) {
+ S arr[] = {{p, q}, {r, s}};
+ }
+ )cpp"),
+ true);
+
+ auto *Sum = getEntitySummary("foo");
+
+ ASSERT_NE(Sum, nullptr);
+ EXPECT_EQ(*Sum, makeEdges(__LINE__, {
+ {{"a", 1U}, {"p", 1U}},
+ {{"b", 1U}, {"q", 1U}},
+ {{"a", 1U}, {"r", 1U}},
+ {{"b", 1U}, {"s", 1U}},
+ }));
+}
+
+//////////////////////////////////////////////////////////////
+// Return Tests. //
+//////////////////////////////////////////////////////////////
+
+TEST_F(PointerFlowTest, ReturnEdge) {
+ ASSERT_EQ(setUpTest(R"cpp(
+ int *foo(int *p) {
+ return p;
+ }
+ )cpp"),
+ true);
+
+ auto *Sum = getEntitySummary("foo");
+
+ ASSERT_NE(Sum, nullptr);
+ EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"foo", 1U, true}, {"p", 1U}}}));
+}
+
+TEST_F(PointerFlowTest, MultipleReturnEdges) {
+ ASSERT_EQ(setUpTest(R"cpp(
+ int *foo(int *p, int *q, bool cond) {
+ if (cond)
+ return p;
+ return q;
+ }
+ )cpp"),
+ true);
+
+ auto *Sum = getEntitySummary("foo");
+
+ ASSERT_NE(Sum, nullptr);
+ EXPECT_EQ(*Sum, makeEdges(__LINE__, {
+ {{"foo", 1U, true}, {"p", 1U}},
+ {{"foo", 1U, true}, {"q", 1U}},
+ }));
+}
+
+TEST_F(PointerFlowTest, NoReturnEdgeForNonPointerReturnType) {
+ ASSERT_EQ(setUpTest(R"cpp(
+ int foo(int *p, int x) {
+ return x;
+ }
+ )cpp"),
+ true);
+
+ auto *Sum = getEntitySummary("foo");
+
+ EXPECT_THAT(Sum, testing::AnyOf(testing::IsNull(),
+ testing::Pointee(makeEdges(__LINE__, {}))));
+}
+
+TEST_F(PointerFlowTest, ReturnEdgeNotFromNestedFunction) {
+ StringRef Code = R"cpp(
+ int *foo(int *p) {
+ struct Inner {
+ int *bar(int *q) { return q; }
+ };
+ return p;
+ }
+ )cpp";
+
+ ASSERT_EQ(setUpTest(Code), true);
+ auto *Sum = getEntitySummary("foo");
+
+ ASSERT_NE(Sum, nullptr);
+ EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"foo", 1U, true}, {"p", 1U}}}));
+
+ Sum = getEntitySummary("bar");
+
+ ASSERT_NE(Sum, nullptr);
+ EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"bar", 1U, true}, {"q", 1U}}}));
+}
+
+TEST_F(PointerFlowTest, ReturnEdgeInClassMethod) {
+ ASSERT_EQ(setUpTest(R"cpp(
+ void foo() {
+ struct S {
+ int *method(int *p, int *q) { return p; }
+ };
+ }
+ )cpp"),
+ true);
+
+ auto *Sum = getEntitySummary("method");
+
+ ASSERT_NE(Sum, nullptr);
+ EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"method", 1U, true}, {"p", 1U}}}));
+}
+
+TEST_F(PointerFlowTest, NoEdgeFromIndirectCall) {
+ ASSERT_EQ(setUpTest(R"cpp(
+ void bar(int *param1);
+ void baz(int *param2);
+
+ void foo(int *p, void (*fp)(int *)) {
+ fp(p);
+ }
+
+ int main() {
+ int *q;
+ foo(q, bar);
+ foo(q, baz);
+ return 0;
+ }
+ )cpp"),
+ true);
+
+ /* FIXME or TBD: Currently indirect calls produce no edge: */
+ auto *Sum = getEntitySummary("foo");
+
+ EXPECT_THAT(Sum, testing::AnyOf(testing::IsNull(),
+ testing::Pointee(makeEdges(__LINE__, {}))));
+}
+
+//////////////////////////////////////////////////////////////
+// Lambda Tests. //
+//////////////////////////////////////////////////////////////
+
+TEST_F(PointerFlowTest, ReturnInLambda) {
+ StringRef Code = R"cpp(
+ int* foo(int *p) {
+ auto local = [](int *r) { return r; };
+ return local(p);
+ }
+ )cpp";
+
+ ASSERT_EQ(setUpTest(Code), true);
+ auto *Sum = getEntitySummary("foo");
+
+ ASSERT_NE(Sum, nullptr);
+ EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"r", 1U}, {"p", 1U}},
+ {{"foo", 1U, true},
+ {LambdaOfVar{"local"}, 1U, true}}}));
+
+ Sum = getEntitySummary(LambdaOfVar{"local"});
+ ASSERT_NE(Sum, nullptr);
+ EXPECT_EQ(*Sum, makeEdges(__LINE__,
+ {{{LambdaOfVar{"local"}, 1U, true}, {"r", 1U}}}));
+}
+
+TEST_F(PointerFlowTest, NestedLambdaAssign) {
+ StringRef Code = R"cpp(
+ void foo() {
+ auto outer_lambda = [](int *r, int *s) {
+ s = r;
+ auto inner_lambda = [](int *x, int *y) { y = x; };
+ };
+ }
+ )cpp";
+
+ ASSERT_EQ(setUpTest(Code), true);
+ auto *Sum = getEntitySummary(LambdaOfVar{"outer_lambda"});
+ ASSERT_NE(Sum, nullptr);
+ EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"s", 1U}, {"r", 1U}}}));
+
+ Sum = getEntitySummary(LambdaOfVar{"inner_lambda"});
+ ASSERT_NE(Sum, nullptr);
+ EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"y", 1U}, {"x", 1U}}}));
+}
+} // namespace
diff --git a/clang/unittests/ScalableStaticAnalysisFramework/CMakeLists.txt b/clang/unittests/ScalableStaticAnalysisFramework/CMakeLists.txt
index 51466070ec8d9..e5f4688a44275 100644
--- a/clang/unittests/ScalableStaticAnalysisFramework/CMakeLists.txt
+++ b/clang/unittests/ScalableStaticAnalysisFramework/CMakeLists.txt
@@ -1,4 +1,5 @@
add_distinct_clang_unittest(ClangScalableAnalysisTests
+ Analyses/PointerFlow/PointerFlowTest.cpp
Analyses/CallGraph/CallGraphExtractorTest.cpp
Analyses/UnsafeBufferUsage/UnsafeBufferUsageTest.cpp
ASTEntityMappingTest.cpp
More information about the cfe-commits
mailing list