[clang] [llvm] [SSAF][PointerFlow] Factor out and make the pointer-flow matching reusable (PR #222780)
Ziqing Luo via cfe-commits
cfe-commits at lists.llvm.org
Fri Sep 11 17:04:23 PDT 2026
https://github.com/ziqingluo-90 updated https://github.com/llvm/llvm-project/pull/222780
>From 9e04b61962fa4f1c3c4cc4d6dff3a7b5e829c952 Mon Sep 17 00:00:00 2001
From: Ziqing Luo <ziqing_luo at apple.com>
Date: Thu, 10 Sep 2026 14:06:38 -0700
Subject: [PATCH 1/2] [SSAF][PointerFlow] Factor out and make the pointer-flow
matching reusable
The PointerFlowExtractor matches AST nodes representing pointer-flows
and converts them to entity-based data structures directly. This
commit divides this procedure into two steps: 1) match and represent
AST nodes as PointerFlowPairs; 2) convert PointerFlowPairs to
entity-based edges. Therefore, other SSAF tools may use
PointerFlowPairs.
The refactoring also improves coverage: it separates pointer-type
checking from structural matching, so structural matching alone now
discovers cases that were previously missed due to overly aggressive
type checking (e.g. a record-typed call argument or return value
initialized with a braced-init-list).
Along the way, this also fixes a bug for unnamed bit-fields.
First patch for
rdar://187125348
---
.../Analyses/PointerFlow/PointerFlowPairs.h | 122 +++++
.../Analyses/CMakeLists.txt | 1 +
.../PointerFlow/PointerFlowExtractor.cpp | 287 +++---------
.../Analyses/PointerFlow/PointerFlowPairs.cpp | 300 ++++++++++++
.../Analyses/SSAFAnalysesCommon.h | 8 +-
.../PointerFlow/PointerFlowPairsTest.cpp | 433 ++++++++++++++++++
.../ScalableStaticAnalysis/CMakeLists.txt | 1 +
.../ScalableStaticAnalysis/Analyses/BUILD.gn | 1 +
.../unittests/ScalableStaticAnalysis/BUILD.gn | 1 +
9 files changed, 927 insertions(+), 227 deletions(-)
create mode 100644 clang/include/clang/ScalableStaticAnalysis/Analyses/PointerFlow/PointerFlowPairs.h
create mode 100644 clang/lib/ScalableStaticAnalysis/Analyses/PointerFlow/PointerFlowPairs.cpp
create mode 100644 clang/unittests/ScalableStaticAnalysis/Analyses/PointerFlow/PointerFlowPairsTest.cpp
diff --git a/clang/include/clang/ScalableStaticAnalysis/Analyses/PointerFlow/PointerFlowPairs.h b/clang/include/clang/ScalableStaticAnalysis/Analyses/PointerFlow/PointerFlowPairs.h
new file mode 100644
index 0000000000000..06f892a7333cc
--- /dev/null
+++ b/clang/include/clang/ScalableStaticAnalysis/Analyses/PointerFlow/PointerFlowPairs.h
@@ -0,0 +1,122 @@
+//===- PointerFlowPairs.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 provides PointerFlowPair and PointerFlowPairMatcher.
+//
+// PointerFlowPair represents an element '(l, r)' of the pointer-flow relation
+// over declarations and expressions of pointer/array type. Each pair
+// corresponds to a value-copying (or "assignment"-flavored) language construct
+// (e.g. an assignment, argument passing, a return, or an initialization). It
+// requires that if 'l's type is refined to carry a property (e.g., buffer
+// bounds), then 'r's type must follow; otherwise the property would be
+// lost in value-copy from 'r' to 'l'.
+//
+// PointerFlowPairMatcher walks an AST node and collects the PointerFlowPairs it
+// generates. It outputs matched pairs '(l, r)' such that
+// - 'l' and 'r' have compatible types;
+// - 'l' is either a pointer or an array;
+// - 'r' may be a list-initializer, when it has an array type.
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_SCALABLESTATICANALYSIS_ANALYSES_POINTERFLOW_POINTERFLOWPAIRS_H
+#define LLVM_CLANG_SCALABLESTATICANALYSIS_ANALYSES_POINTERFLOW_POINTERFLOWPAIRS_H
+
+#include "clang/AST/ASTTypeTraits.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/Expr.h"
+#include "clang/AST/TypeBase.h"
+#include "llvm/ADT/SmallVector.h"
+#include <type_traits>
+
+namespace clang::ssaf {
+
+/// Data structure representing a pointer flow.
+/// Invariant: LHS and RHS should have compatible types.
+struct PointerFlowPair {
+ /// The left-hand side of an assignment or a variable/field
+ /// definition, a formal parameter, or the function owning a return
+ /// stmt:
+ llvm::PointerUnion<const ValueDecl *, const Expr *> LHS;
+ /// The right-hand side of an assignment or a variable/field
+ /// definition, an actual argument, or the expr being returning:
+ const Expr *RHS;
+ /// True iff the left-hand side of this PointerFlowPair represents the
+ /// return entity of a callable:
+ bool IsLHSRet;
+
+ PointerFlowPair(const ValueDecl *LHS, const Expr *RHS, bool IsLHSRet = false)
+ : LHS(LHS), RHS(RHS), IsLHSRet(IsLHSRet) {
+ assert((!IsLHSRet || isa_and_nonnull<FunctionDecl>(LHS)) &&
+ "IsLHSRet -> LHS is a FunctionDecl");
+ }
+
+ PointerFlowPair(const Expr *LHS, const Expr *RHS)
+ : LHS(LHS), RHS(RHS), IsLHSRet(false) {}
+
+ /// An alternative to access the PointerUnion LHS directly---handle it using
+ /// a function object that defines:
+ /// - T operator()(const ValueDecl *, bool IsRet, Args...);
+ /// - T operator()(const Expr *, Args...);
+ template <
+ typename F, typename... Args,
+ typename T = std::invoke_result_t<F, const ValueDecl *, bool, Args...>>
+ T visitLHS(F &&Visitor, Args... ExtraArgs) const {
+ static_assert(std::is_invocable_r_v<T, F, const ValueDecl *, bool, Args...>,
+ "Visitor(const ValueDecl *, bool, Args...) must return T");
+ static_assert(std::is_invocable_r_v<T, F, const Expr *, Args...>,
+ "Visitor(const Expr *, Args...) must return T");
+ if (const auto *VD = LHS.dyn_cast<const ValueDecl *>())
+ return Visitor(VD, IsLHSRet, ExtraArgs...);
+ return Visitor(LHS.dyn_cast<const Expr *>(), ExtraArgs...);
+ }
+};
+
+class PointerFlowPairMatcher {
+public:
+ ASTContext &Ctx;
+ PointerFlowPairMatcher(ASTContext &Ctx) : Ctx(Ctx) {}
+
+ // FIXME: Known gaps -- the following constructs are not handled:
+ // - Lambda captures (by-copy, by-reference, or init-capture) of a
+ // pointer.
+ // - Structured bindings (`auto [a, b] = pair;`) -- the per-element
+ // `BindingDecl`s are neither `VarDecl` nor `FieldDecl`.
+
+ /// Match and collect pointer flow.
+ /// The macth function 'F' can be described by the following rules:
+ ///
+ /// F(l = r) := (l, r), if 'l' has a pointer/array type;
+ /// := F(field_1, list_item_1), ..., if 'l' has a record
+ /// type and 'r' is a
+ /// list-initializer
+ /// F(foo(a, b, ...)) := F(Param_1 = a), F(Param_2 = b), ...
+ /// F(return e;) := F(FunRet = e), where 'FunRet' is the return
+ /// entity of the enclosing
+ /// function
+ /// F(ctor(a, ...) : x1(y1), ... {...})
+ /// := F(Param_1 = a), ...,
+ /// F(x1 = y1), ....
+ /// F(T var = e) := F(var = e)
+ ///
+ /// \param DynNode the node being matched.
+ /// \param Contributor the Decl that contributes \c DynNode; it is the
+ /// enclosing function decl if \c DynNode is a return stmt.
+ /// \param Result output, a set of \c PointerFlowPair matched from \c
+ /// DynNode
+ ///
+ /// Upon return, each pair '(l, r)' in \c Result is must have the following
+ /// properties:
+ /// - 'l' and 'r' have compatible types;
+ /// - 'l' is either a pointer or an array;
+ /// - 'r' may be a list-initializer when 'l' is an array
+ bool matches(const DynTypedNode &DynNode, const NamedDecl *Contributor,
+ llvm::SmallVectorImpl<PointerFlowPair> &Result) const;
+};
+
+} // namespace clang::ssaf
+
+#endif // LLVM_CLANG_SCALABLESTATICANALYSIS_ANALYSES_POINTERFLOW_POINTERFLOWPAIRS_H
diff --git a/clang/lib/ScalableStaticAnalysis/Analyses/CMakeLists.txt b/clang/lib/ScalableStaticAnalysis/Analyses/CMakeLists.txt
index 98ce8e799e0e0..1e8357e843829 100644
--- a/clang/lib/ScalableStaticAnalysis/Analyses/CMakeLists.txt
+++ b/clang/lib/ScalableStaticAnalysis/Analyses/CMakeLists.txt
@@ -12,6 +12,7 @@ add_clang_library(clangScalableStaticAnalysisAnalyses
PointerFlow/PointerFlowAnalysis.cpp
PointerFlow/PointerFlowExtractor.cpp
PointerFlow/PointerFlowFormat.cpp
+ PointerFlow/PointerFlowPairs.cpp
SharedLexicalRepresentation/EntitySourceLocationExtractor.cpp
SharedLexicalRepresentation/SharedLexicalRepresentationFormat.cpp
SSAFAnalysesCommon.cpp
diff --git a/clang/lib/ScalableStaticAnalysis/Analyses/PointerFlow/PointerFlowExtractor.cpp b/clang/lib/ScalableStaticAnalysis/Analyses/PointerFlow/PointerFlowExtractor.cpp
index 719929bd7d43a..45785a80d4603 100644
--- a/clang/lib/ScalableStaticAnalysis/Analyses/PointerFlow/PointerFlowExtractor.cpp
+++ b/clang/lib/ScalableStaticAnalysis/Analyses/PointerFlow/PointerFlowExtractor.cpp
@@ -17,12 +17,12 @@
#include "clang/AST/TypeBase.h"
#include "clang/ScalableStaticAnalysis/Analyses/EntityPointerLevel/EntityPointerLevel.h"
#include "clang/ScalableStaticAnalysis/Analyses/PointerFlow/PointerFlow.h"
-#include "clang/ScalableStaticAnalysis/Core/Model/EntityId.h"
+#include "clang/ScalableStaticAnalysis/Analyses/PointerFlow/PointerFlowPairs.h"
#include "clang/ScalableStaticAnalysis/Core/TUSummary/ExtractorRegistry.h"
-#include "clang/ScalableStaticAnalysis/Core/TUSummary/TUSummaryBuilder.h"
#include "clang/ScalableStaticAnalysis/Core/TUSummary/TUSummaryExtractor.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/STLFunctionalExtras.h"
+#include "llvm/ADT/SmallVector.h"
#include "llvm/Support/Error.h"
#include <memory>
@@ -34,62 +34,36 @@ namespace {
using namespace clang;
using namespace ssaf;
-class PointerFlowMatcher {
+class PointerFlowEdgeBuilder {
public:
EdgeSet Results;
- ASTContext &Ctx;
- TUSummaryExtractor &Extractor;
- PointerFlowMatcher(ASTContext &Ctx, TUSummaryExtractor &Extractor)
+ PointerFlowEdgeBuilder(ASTContext &Ctx, TUSummaryExtractor &Extractor)
: Ctx(Ctx), Extractor(Extractor) {}
- 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 operator()(const Expr *LHS, const Expr *RHS);
- llvm::Error matchesDecl(const Decl *D, const NamedDecl *RootDecl);
+ llvm::Error operator()(const ValueDecl *LHS, bool IsRet, const Expr *RHS);
private:
- llvm::Error addEdges(Expected<DeclPointerLevelVec> &&LHS,
- Expected<DeclPointerLevelVec> &&RHS);
-
- Expected<DeclPointerLevelVec> toDPL(const Expr *N) const {
- return translateDeclPointerLevel(N, Ctx, Extractor);
- }
-
- static DeclPointerLevel toDPL(const NamedDecl *N, bool IsRet = false) {
- return createDeclPointerLevel(N, IsRet);
- }
+ ASTContext &Ctx;
+ TUSummaryExtractor &Extractor;
- template <typename ParmsProvider, typename ArgsProvider>
- llvm::Error matchesArgsWithParams(unsigned ArgIdxStart, ParmsProvider *PP,
- ArgsProvider *AP) {
- unsigned ArgIdx = ArgIdxStart;
+ /// As \c RHS of PointerFlowPairs can still be list-initializers in case
+ /// (multi-d) pointer arrays, this function decomposes them recursively and
+ /// increases pointer level of \c LHS properly.
+ llvm::Error handleRHSAndAddEdges(const DeclPointerLevelVec &LHS,
+ const Expr *RHS,
+ unsigned ArrayElementIndirectLevel = 0);
- 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(DeclPointerLevelVec{toDPL(PD)},
- toDPL(AP->getArg(ArgIdx))))
- return Err;
- }
- }
- return llvm::Error::success();
- }
+ /// Converts DeclPointerLevelVec pairs to edges:
+ llvm::Error addEdges(const DeclPointerLevelVec &LHS,
+ Expected<DeclPointerLevelVec> &&RHS);
};
-llvm::Error PointerFlowMatcher::addEdges(Expected<DeclPointerLevelVec> &&LHS,
- Expected<DeclPointerLevelVec> &&RHS) {
- if (!LHS && !RHS)
- return llvm::joinErrors(LHS.takeError(), RHS.takeError());
- if (!LHS)
- return LHS.takeError();
+llvm::Error
+PointerFlowEdgeBuilder::addEdges(const DeclPointerLevelVec &LHS,
+ Expected<DeclPointerLevelVec> &&RHS) {
if (!RHS)
return RHS.takeError();
if (RHS->empty())
@@ -97,8 +71,8 @@ llvm::Error PointerFlowMatcher::addEdges(Expected<DeclPointerLevelVec> &&LHS,
std::vector<DeclPointerLevelVec> LVecs, RVecs;
- LVecs.reserve(LHS->size());
- for (const auto &L : *LHS)
+ LVecs.reserve(LHS.size());
+ for (const auto &L : LHS)
LVecs.push_back(elaborateHigherDeclPointerLevels(L));
RVecs.reserve(RHS->size());
for (const auto &R : *RHS)
@@ -132,189 +106,45 @@ llvm::Error PointerFlowMatcher::addEdges(Expected<DeclPointerLevelVec> &&LHS,
return llvm::Error::success();
}
-/// Match and extract pointer flow.
-/// The extraction function 'XF' can be described by the following rules:
-///
-/// XF(l = r) := addEdges(toDPL(l), toDPL(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::matchesInitializerList
-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(toDPL(BO->getLHS()), toDPL(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))
+llvm::Error
+PointerFlowEdgeBuilder::handleRHSAndAddEdges(
+ const DeclPointerLevelVec &LHS, const Expr *RHS,
+ unsigned ArrayElementIndirectLevel) {
+ const auto *ILE = dyn_cast<InitListExpr>(RHS);
+ if (!ILE) {
+ if (!hasPtrOrArrType(RHS))
return llvm::Error::success();
- return addEdges(DeclPointerLevelVec{toDPL(RootDecl, true)}, toDPL(RetExpr));
- }
- return llvm::Error::success();
-}
-llvm::Error PointerFlowMatcher::matchesDecl(const Decl *D,
- const NamedDecl *RootDecl) {
- const Expr *InitExpr = nullptr;
+ // Leaf: raise a copy of LHS by the array depth reached, then add edges.
+ DeclPointerLevelVec Copy = LHS;
- 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(DeclPointerLevelVec{toDPL(VD)}, toDPL(InitExpr));
+ for (DeclPointerLevel &DPL : Copy)
+ DPL.PointerLevel += ArrayElementIndirectLevel;
+ return addEdges(Copy, translateDeclPointerLevel(RHS, Ctx, Extractor));
}
- // 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(DeclPointerLevelVec{toDPL(E->getMember())},
- toDPL(E->getInit())))
- return Err;
- }
- }
- }
- return llvm::Error::success();
-}
+ llvm::Error Err = llvm::Error::success();
-// Helper function for matchesInitializerList 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 || ILE->inits().empty())
- return llvm::Error::success();
- 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();
+ // Descend one array dimension.
+ for (const auto *Init : ILE->inits())
+ Err = llvm::joinErrors(
+ std::move(Err),
+ handleRHSAndAddEdges(LHS, Init, ArrayElementIndirectLevel + 1));
+ return Err;
}
-// Helper function for matchesInitializerList 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();
+llvm::Error PointerFlowEdgeBuilder::operator()(const Expr *LHS,
+ const Expr *RHS) {
+ auto LVec = translateDeclPointerLevel(LHS, Ctx, Extractor);
+ if (!LVec)
+ return LVec.takeError();
+ return handleRHSAndAddEdges(*LVec, RHS);
}
-/// 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 of 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 BaseDPL = toDPL(Base);
- // Apply ArrayElementIndirectLevel to BaseDPL
- BaseDPL.PointerLevel += ArrayElementIndirectLevel;
- return addEdges(DeclPointerLevelVec{BaseDPL}, toDPL(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.
- // The initializer-list can be either singleton or empty:
- if (ILE->getNumInits() == 0)
- return llvm::Error::success();
- return matchesInitializerList(Base, ILE->getInit(0));
+llvm::Error PointerFlowEdgeBuilder::operator()(const ValueDecl *LHS, bool IsRet,
+ const Expr *RHS) {
+ DeclPointerLevelVec LVec = {createDeclPointerLevel(LHS, IsRet)};
+ return handleRHSAndAddEdges(LVec, RHS);
}
class PointerFlowTUSummaryExtractor : public TUSummaryExtractor {
@@ -325,18 +155,23 @@ class PointerFlowTUSummaryExtractor : public TUSummaryExtractor {
std::unique_ptr<PointerFlowEntitySummary>
extractEntitySummary(const std::vector<const NamedDecl *> &ContributorDecls,
ASTContext &Ctx, TUSummaryExtractor &Extractor) {
- PointerFlowMatcher Matcher(Ctx, Extractor);
+ ssaf::PointerFlowPairMatcher Matcher(Ctx);
+ PointerFlowEdgeBuilder Builder(Ctx, Extractor);
for (const auto *Contrib : ContributorDecls) {
- auto MatchAction = [&Matcher, Contrib](const DynTypedNode &Node) {
- if (auto Err = Matcher.matches(Node, Contrib))
- logWarningFromError(std::move(Err));
+ auto MatchAction = [&](const DynTypedNode &Node) {
+ llvm::SmallVector<PointerFlowPair> Pairs;
+
+ Matcher.matches(Node, Contrib, Pairs);
+ for (auto &Pair : Pairs)
+ if (auto Err = Pair.visitLHS(Builder, Pair.RHS))
+ logWarningFromError(std::move(Err));
};
findMatchesIn(Contrib, MatchAction);
}
return std::make_unique<PointerFlowEntitySummary>(
- buildPointerFlowEntitySummary(std::move(Matcher.Results)));
+ buildPointerFlowEntitySummary(std::move(Builder.Results)));
}
void HandleTranslationUnit(ASTContext &Ctx) override {
diff --git a/clang/lib/ScalableStaticAnalysis/Analyses/PointerFlow/PointerFlowPairs.cpp b/clang/lib/ScalableStaticAnalysis/Analyses/PointerFlow/PointerFlowPairs.cpp
new file mode 100644
index 0000000000000..a0b4124b10943
--- /dev/null
+++ b/clang/lib/ScalableStaticAnalysis/Analyses/PointerFlow/PointerFlowPairs.cpp
@@ -0,0 +1,300 @@
+//===- PointerFlowPairs.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/ScalableStaticAnalysis/Analyses/PointerFlow/PointerFlowPairs.h"
+#include "SSAFAnalysesCommon.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 "llvm/ADT/SmallVector.h"
+
+namespace {
+using namespace clang;
+using namespace ssaf;
+
+//===----------------------------------------------------------------------===//
+// Helper functions for `PointerFlowPairMatcher::matches`.
+//===----------------------------------------------------------------------===//
+
+bool findUntypedPairsInStmt(const Stmt *S, const NamedDecl *RootDecl,
+ llvm::SmallVectorImpl<PointerFlowPair> &Result);
+bool findUntypedPairsInDecl(const Decl *D,
+ llvm::SmallVectorImpl<PointerFlowPair> &Result);
+
+/// Dispatch \p DynNode to `findUntypedPairsInStmt`/`findUntypedPairsInDecl` and
+/// collect pairs without checking types.
+bool findUntypedPairs(const DynTypedNode &DynNode, const NamedDecl *RootDecl,
+ llvm::SmallVectorImpl<PointerFlowPair> &Result) {
+ if (const Stmt *S = DynNode.get<Stmt>())
+ return findUntypedPairsInStmt(S, RootDecl, Result);
+ if (const Decl *D = DynNode.get<Decl>())
+ return findUntypedPairsInDecl(D, Result);
+ return false;
+}
+
+template <typename ParmsProvider, typename ArgsProvider>
+bool matchesArgsWithParams(unsigned ArgIdxStart, ParmsProvider *PP,
+ ArgsProvider *AP,
+ llvm::SmallVectorImpl<PointerFlowPair> &Result) {
+ unsigned ArgIdx = ArgIdxStart;
+ bool Found = false;
+
+ for (unsigned ParmIdx = 0;
+ ParmIdx < PP->getNumParams() && ArgIdx < AP->getNumArgs();
+ ++ArgIdx, ++ParmIdx) {
+ if (const ParmVarDecl *PD = PP->getParamDecl(ParmIdx)) {
+ Result.emplace_back(PD, AP->getArg(ArgIdx));
+ Found = true;
+ }
+ }
+ return Found;
+}
+
+bool findUntypedPairsInStmt(const Stmt *S, const NamedDecl *RootDecl,
+ llvm::SmallVectorImpl<PointerFlowPair> &Result) {
+ // Match 'p = q':
+ if (const auto *BO = dyn_cast<BinaryOperator>(S);
+ BO && BO->getOpcode() == BO_Assign) {
+ Result.emplace_back(BO->getLHS(), BO->getRHS());
+ return true;
+ }
+
+ // Match arg-to-param passing (in CallExpr):
+ if (const auto *CE = dyn_cast<CallExpr>(S)) {
+ const FunctionDecl *FD = CE->getDirectCallee();
+
+ if (!FD)
+ return false;
+
+ unsigned ArgIdx = 0;
+
+ if (isa<CXXOperatorCallExpr>(CE))
+ if (const auto *MD = dyn_cast<CXXMethodDecl>(FD);
+ MD && !MD->isExplicitObjectMemberFunction())
+ ArgIdx = 1;
+ return matchesArgsWithParams(ArgIdx, FD, CE, Result);
+ }
+ // Match arg-to-param passing (in CXXConstructExpr):
+ if (const auto *CCE = dyn_cast<CXXConstructExpr>(S)) {
+ return matchesArgsWithParams(/*ArgIdxStart=*/0, CCE->getConstructor(), CCE,
+ Result);
+ }
+ if (const auto *RS = dyn_cast<ReturnStmt>(S)) {
+ const Expr *RetExpr = RS->getRetValue();
+ if (RetExpr)
+ if (const auto *FD = dyn_cast<FunctionDecl>(RootDecl)) {
+ Result.emplace_back(FD, RetExpr, /*IsLHSRet=*/true);
+ return true;
+ }
+ return false;
+ }
+ return false;
+}
+
+bool findUntypedPairsInDecl(const Decl *D,
+ llvm::SmallVectorImpl<PointerFlowPair> &Result) {
+ 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 (const auto *InitLst = dyn_cast_or_null<InitListExpr>(InitExpr)) {
+ Result.emplace_back(VD, InitLst);
+ return true;
+ }
+ if (InitExpr) {
+ // Match initializers to variables/fields of a pointer type:
+ Result.emplace_back(VD, InitExpr);
+ return true;
+ }
+ }
+
+ bool Found = false;
+ // Match C++ constructor member-initializers here. The FieldDecl a
+ // member-initializer targets is only recorded on the CXXCtorInitializer
+ // itself, which is neither a Stmt nor a Decl,
+ if (const auto *CtorD = dyn_cast<CXXConstructorDecl>(D)) {
+ for (const auto *E : CtorD->inits()) {
+ if (const FieldDecl *FD = E->getMember()) {
+ Result.emplace_back(FD, E->getInit());
+ Found = true;
+ }
+ }
+ }
+ return Found;
+}
+
+/// Pipeline the output of `findUntypedPairs`: further decompose
+/// list-initializers around record types and filter out pairs that are not
+/// pointers or arrays.
+///
+/// Upon return, each pair in \c Result has a pointer or array type. In
+/// addition, if its \c RHS is a list-initializer, the pair has an array type.
+///
+/// For example,
+/// - suppose 'LHS' has type 'struct S {int *x; int *y;};',
+/// - it finds in '(LHS, {1 , 2})' two pairs '(x, 1), (y, 2)'.
+///
+/// - suppose 'LHS' has type 'S[2]',
+/// - it finds in '(LHS, {{1 , 2}, {3, 4}})' four pairs
+/// '(x, 1), (y, 2), (x, 3), (y, 4)'.
+///
+/// - suppose 'LHS' has type 'int *[2]',
+/// - it finds in '(LHS, {nullptr, nullptr})' one pair
+/// '(LHS, {nullptr, nullptr})'.
+///
+/// - suppose 'LHS' has type 'int *',
+/// - it finds in '(LHS, nullptr)' one pair '(LHS, nullptr)'.
+bool matchPtrOrArrPairs(const PointerFlowPairMatcher &Matcher, PointerFlowPair Pair,
+ llvm::SmallVectorImpl<PointerFlowPair> &Result);
+} // namespace
+
+namespace clang::ssaf {
+
+bool PointerFlowPairMatcher::matches(
+ const DynTypedNode &DynNode, const NamedDecl *RootDecl,
+ llvm::SmallVectorImpl<PointerFlowPair> &Result) const {
+ llvm::SmallVector<PointerFlowPair, 8> UntypedPairs;
+ findUntypedPairs(DynNode, RootDecl, UntypedPairs);
+
+ bool Found = false;
+ for (const PointerFlowPair &P : UntypedPairs)
+ Found |= matchPtrOrArrPairs(*this, P, Result);
+ return Found;
+}
+} // namespace clang::ssaf
+
+namespace {
+
+struct GetType {
+ QualType operator()(const ValueDecl *D, bool IsRet) const {
+ return IsRet ? cast<FunctionDecl>(D)->getReturnType() : D->getType();
+ }
+
+ QualType operator()(const Expr *E) const { return E->getType(); }
+};
+
+//===----------------------------------------------------------------------===//
+// Helper functions for `PointerFlowPairMatcher::matchPtrOrArrPairs`.
+//===----------------------------------------------------------------------===//
+
+/// Helper function for matchPtrOrArrPairs that handles record
+/// types.
+bool matchInitializerListForRecordDeclRecursive(
+ const PointerFlowPairMatcher &Matcher, const RecordDecl *RecordTy,
+ const InitListExpr *ILE, llvm::SmallVectorImpl<PointerFlowPair> &Result) {
+ if (const auto *CXXRD = dyn_cast<CXXRecordDecl>(RecordTy))
+ if (CXXRD->getNumBases() != 0) {
+ // FIXME: support this:
+ logWarningFromError(makeErrAtNode(
+ Matcher.Ctx, ILE,
+ "attempt to create pointer assignment edges between "
+ "CXXRecordDecls with base classes and initializer-lists"));
+ return false;
+ }
+ // Handle union:
+ if (RecordTy->isUnion()) {
+ const auto *InitField = ILE->getInitializedFieldInUnion();
+
+ if (!InitField || ILE->inits().empty())
+ return false;
+ return matchPtrOrArrPairs(Matcher, {InitField, ILE->getInit(0)}, Result);
+ }
+ // Handle struct/class:
+ ILE = ILE->isSemanticForm() ? ILE : ILE->getSemanticForm();
+
+ auto FieldIter = RecordTy->field_begin();
+ bool Found = false;
+
+ for (const auto *Init : ILE->inits()) {
+ // Skip unnamed bit-fields:
+ while (FieldIter != RecordTy->field_end() && FieldIter->isUnnamedBitField())
+ ++FieldIter;
+ assert(FieldIter != RecordTy->field_end());
+ Found |= matchPtrOrArrPairs(Matcher, {*(FieldIter++), Init}, Result);
+ }
+ return Found;
+}
+
+/// Helper function of `matchPtrOrArrPairs` that specifically
+/// handles an list-initializer to a (multi-dimensional) array of a record
+/// type.
+bool matchInitializerListForRecordArrayRecursive(
+ const PointerFlowPairMatcher &Matcher, const ArrayType *ArrayType,
+ const InitListExpr *ILE, llvm::SmallVectorImpl<PointerFlowPair> &Result) {
+ assert(Matcher.Ctx.getBaseElementType(ArrayType)->isRecordType() &&
+ "expected a (multi-dimensional) array of a record type");
+ auto EltTy = ArrayType->getElementType();
+ bool Found = false;
+
+ if (const auto *RD = EltTy->getAsRecordDecl()) {
+ for (const auto *Init : ILE->inits()) {
+ if (const auto *SubILE = dyn_cast<InitListExpr>(Init))
+ Found |= matchInitializerListForRecordDeclRecursive(Matcher, RD, SubILE,
+ Result);
+ // No need to handle non-list-initialized records:
+ }
+ return Found;
+ }
+ if (auto *SubArrayType = Matcher.Ctx.getAsArrayType(EltTy)) {
+ for (const auto *Init : ILE->inits())
+ if (const auto *SubILE = dyn_cast<InitListExpr>(Init))
+ Found |= matchInitializerListForRecordArrayRecursive(
+ Matcher, SubArrayType, SubILE, Result);
+ return Found;
+ }
+ return false;
+}
+
+bool matchPtrOrArrPairs(const PointerFlowPairMatcher &Matcher, PointerFlowPair Pair,
+ llvm::SmallVectorImpl<PointerFlowPair> &Result) {
+ // - Base case: `RHS` is not a InitListExpr;
+ // - Call `matchInitializerListForRecordDeclRecursive` to handle
+ // list-initializing record;
+ // - Call `matchInitializerListForRecordArrayRecursive` to handle
+ // list-initializing (multi-d) array of records;
+ // - Recursion on list-initialization of scalar.
+ const auto *ILE = dyn_cast<InitListExpr>(Pair.RHS);
+ QualType Type = Pair.visitLHS(GetType{});
+
+ if (!ILE) {
+ // Base case:
+ if (!hasPtrOrArrType(Type))
+ return false;
+ Result.push_back(Pair);
+ return true;
+ }
+
+ if (auto *RD = Type->getAsRecordDecl())
+ return matchInitializerListForRecordDeclRecursive(Matcher, RD, ILE, Result);
+ if (auto *ArrayType = Matcher.Ctx.getAsArrayType(Type)) {
+ auto BaseTy = Matcher.Ctx.getBaseElementType(ArrayType);
+
+ if (BaseTy->isRecordType())
+ return matchInitializerListForRecordArrayRecursive(Matcher, ArrayType,
+ ILE, Result);
+ Result.push_back(Pair);
+ return true;
+ }
+
+ // Must be the case of using a initializer-list for a scalar.
+ // The initializer-list can be either singleton or empty:
+ if (ILE->getNumInits() == 0)
+ return false;
+ Pair.RHS = ILE->getInit(0);
+ return matchPtrOrArrPairs(Matcher, Pair, Result);
+}
+
+} // namespace
diff --git a/clang/lib/ScalableStaticAnalysis/Analyses/SSAFAnalysesCommon.h b/clang/lib/ScalableStaticAnalysis/Analyses/SSAFAnalysesCommon.h
index 95e3411cec386..fdce8d2b92c69 100644
--- a/clang/lib/ScalableStaticAnalysis/Analyses/SSAFAnalysesCommon.h
+++ b/clang/lib/ScalableStaticAnalysis/Analyses/SSAFAnalysesCommon.h
@@ -38,7 +38,7 @@ std::string describeJSONValue(const llvm::json::Array &A);
std::string describeJSONValue(const llvm::json::Object &O);
template <typename NodeTy, typename... Ts>
-llvm::Error makeErrAtNode(clang::ASTContext &Ctx, const NodeTy *N,
+llvm::Error makeErrAtNode(const clang::ASTContext &Ctx, const NodeTy *N,
llvm::StringRef Fmt, const Ts &...Args) {
std::string LocStr = N->getBeginLoc().printToString(Ctx.getSourceManager());
return llvm::createStringError((Fmt + " at %s").str().c_str(), Args...,
@@ -66,6 +66,12 @@ inline bool hasPtrOrArrType(const ValueDecl *D) {
D->getType().getNonReferenceType().getCanonicalType());
}
+///\return true iff QualType \c T has (reference-to) pointer or array type.
+inline bool hasPtrOrArrType(QualType T) {
+ return llvm::isa<clang::PointerType, clang::ArrayType>(
+ T.getNonReferenceType().getCanonicalType());
+}
+
llvm::Error makeEntityNameErr(clang::ASTContext &Ctx,
const clang::NamedDecl *D);
diff --git a/clang/unittests/ScalableStaticAnalysis/Analyses/PointerFlow/PointerFlowPairsTest.cpp b/clang/unittests/ScalableStaticAnalysis/Analyses/PointerFlow/PointerFlowPairsTest.cpp
new file mode 100644
index 0000000000000..a4912156ef3ea
--- /dev/null
+++ b/clang/unittests/ScalableStaticAnalysis/Analyses/PointerFlow/PointerFlowPairsTest.cpp
@@ -0,0 +1,433 @@
+//===- PointerFlowPairsTest.cpp -------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// Unit tests for `PointerFlowPair` and `PointerFlowPairMatcher`.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/ScalableStaticAnalysis/Analyses/PointerFlow/PointerFlowPairs.h"
+#include "FindDecl.h"
+#include "clang/AST/ASTTypeTraits.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/DeclCXX.h"
+#include "clang/AST/DynamicRecursiveASTVisitor.h"
+#include "clang/AST/Expr.h"
+#include "clang/Frontend/ASTUnit.h"
+#include "clang/Tooling/Tooling.h"
+#include "llvm/ADT/STLFunctionalExtras.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/raw_ostream.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include <memory>
+#include <string>
+#include <vector>
+
+using namespace clang;
+using namespace ssaf;
+
+namespace clang::ssaf {
+// Defined in the analyses library (SSAFAnalysesCommon); forward-declared here
+// so tests can drive the matcher over a contributor exactly as the extractor
+// does.
+void findMatchesIn(const NamedDecl *Contributor,
+ llvm::function_ref<void(const DynTypedNode &)> MatchAction);
+} // namespace clang::ssaf
+
+namespace {
+
+std::string exprToString(const Expr *E, const ASTContext &Ctx) {
+ std::string S;
+ llvm::raw_string_ostream OS(S);
+ E->printPretty(OS, /*Helper=*/nullptr, Ctx.getPrintingPolicy());
+ return S;
+}
+
+// Visitor for `PointerFlowPair::visitLHS` that pretty prints LHS
+struct RenderLHS {
+ const ASTContext &Ctx;
+
+ std::string operator()(const ValueDecl *D, bool IsRet) const {
+ return D->getNameAsString() + (IsRet ? "(ret)" : "");
+ }
+ std::string operator()(const Expr *E) const { return exprToString(E, Ctx); }
+};
+
+// Render a PointerFlowPair as "(<lhs>, <rhs>)", where both sides are
+// rendered by `RenderLHS`/`exprToString`.
+std::string pairToString(const PointerFlowPair &P, const ASTContext &Ctx) {
+ return "(" + P.visitLHS(RenderLHS{Ctx}) + ", " + exprToString(P.RHS, Ctx) +
+ ")";
+}
+
+// Finds a CXXConstructorDecl by name and parameter count, to disambiguate
+// between overloaded constructors of the same class (e.g. a delegating
+// constructor vs. its delegate).
+const CXXConstructorDecl *
+findCtorByNumParams(StringRef Name, unsigned NumParams, ASTContext &Ctx) {
+ class CtorFinder : public DynamicRecursiveASTVisitor {
+ public:
+ StringRef Name;
+ unsigned NumParams;
+ const CXXConstructorDecl *Found = nullptr;
+
+ CtorFinder(StringRef Name, unsigned NumParams)
+ : Name(Name), NumParams(NumParams) {}
+
+ bool VisitCXXConstructorDecl(CXXConstructorDecl *D) override {
+ if (D->getNameAsString() == Name && D->getNumParams() == NumParams) {
+ Found = D;
+ return false;
+ }
+ return true;
+ }
+ };
+
+ CtorFinder Finder(Name, NumParams);
+ Finder.TraverseDecl(Ctx.getTranslationUnitDecl());
+ return Finder.Found;
+}
+
+class PointerFlowPairsTest : public ::testing::Test {
+protected:
+ std::unique_ptr<ASTUnit> AST;
+
+ bool buildAST(StringRef Code,
+ std::vector<std::string> ExtraArgs = {"-Wno-unused-value"}) {
+ AST = tooling::buildASTFromCodeWithArgs(Code, ExtraArgs);
+ return AST != nullptr;
+ }
+
+ ASTContext &ctx() { return AST->getASTContext(); }
+
+ // Drives `PointerFlowPairMatcher` over `Contrib` and returns every matched
+ // pair rendered as "(<lhs>, <rhs>)", in traversal order.
+ std::vector<std::string> getPairsFor(const NamedDecl *Contrib) {
+ std::vector<std::string> Out;
+ if (!Contrib) {
+ ADD_FAILURE() << "null contributor";
+ return Out;
+ }
+ PointerFlowPairMatcher Matcher(ctx());
+ ssaf::findMatchesIn(Contrib, [&](const DynTypedNode &Node) {
+ llvm::SmallVector<PointerFlowPair> Pairs;
+ Matcher.matches(Node, Contrib, Pairs);
+ for (const PointerFlowPair &P : Pairs)
+ Out.push_back(pairToString(P, ctx()));
+ });
+ return Out;
+ }
+
+ template <typename ContributorDecl = NamedDecl>
+ std::vector<std::string> getPairs(StringRef Name) {
+ const auto *Contrib = findDeclByName<ContributorDecl>(Name, ctx());
+ if (!Contrib) {
+ ADD_FAILURE() << "failed to find Decl of \"" << Name.str() << "\"";
+ return {};
+ }
+ return getPairsFor(Contrib);
+ }
+};
+
+//////////////////////////////////////////////////////////////
+// Basic pair matching. //
+//////////////////////////////////////////////////////////////
+
+TEST_F(PointerFlowPairsTest, VarDeclInit) {
+ ASSERT_TRUE(buildAST(R"cpp(
+ void foo(int *p) {
+ int *q = p;
+ }
+ )cpp"));
+
+ EXPECT_THAT(getPairs("foo"), testing::ElementsAre("(q, p)"));
+}
+
+TEST_F(PointerFlowPairsTest, ReturnStmt) {
+ ASSERT_TRUE(buildAST(R"cpp(
+ int *foo(int *p) {
+ return p;
+ }
+ )cpp"));
+
+ EXPECT_THAT(getPairs("foo"), testing::ElementsAre("(foo(ret), p)"));
+}
+
+//////////////////////////////////////////////////////////////
+// No-match. //
+//////////////////////////////////////////////////////////////
+
+TEST_F(PointerFlowPairsTest, NoPairForNonPointerAssign) {
+ ASSERT_TRUE(buildAST(R"cpp(
+ void foo(int a, int b) {
+ a = b;
+ }
+ )cpp"));
+
+ EXPECT_THAT(getPairs("foo"), testing::IsEmpty());
+}
+
+TEST_F(PointerFlowPairsTest, NoPairForUninitializedVar) {
+ ASSERT_TRUE(buildAST(R"cpp(
+ void foo() {
+ int *p;
+ }
+ )cpp"));
+
+ EXPECT_THAT(getPairs("foo"), testing::IsEmpty());
+}
+
+//////////////////////////////////////////////////////////////
+// Call / Ctor argument passing. //
+//////////////////////////////////////////////////////////////
+
+TEST_F(PointerFlowPairsTest, CallArgMatching) {
+ ASSERT_TRUE(buildAST(R"cpp(
+ void bar(int *param1, int y, int *param2);
+ void foo(int *p, int x, int *q) {
+ bar(p, x, q);
+ }
+ )cpp"));
+
+ EXPECT_THAT(getPairs("foo"),
+ testing::UnorderedElementsAre("(param1, p)", "(param2, q)"));
+}
+
+TEST_F(PointerFlowPairsTest, CXXOperatorCallSkipsImplicitObjectArgument) {
+ ASSERT_TRUE(buildAST(R"cpp(
+ struct S { int *operator()(int *a, int *b); };
+ void foo(S obj, int *p, int *q) {
+ obj(p, q);
+ }
+ )cpp"));
+
+ EXPECT_THAT(getPairs("foo"),
+ testing::UnorderedElementsAre("(a, p)", "(b, q)"));
+}
+
+TEST_F(PointerFlowPairsTest, CXXConstructExprArgMatching) {
+ ASSERT_TRUE(buildAST(R"cpp(
+ struct S { S(int *a, int *b) {} };
+ void foo(int *p, int *q) {
+ S s{p, q};
+ }
+ )cpp"));
+
+ EXPECT_THAT(getPairs("foo"),
+ testing::UnorderedElementsAre("(a, p)", "(b, q)"));
+}
+
+TEST_F(PointerFlowPairsTest, MemberInitializer) {
+ ASSERT_TRUE(buildAST(R"cpp(
+ struct S {
+ int *member;
+ S(int *q) : member(q) {}
+ };
+ )cpp"));
+
+ EXPECT_THAT(getPairs<CXXConstructorDecl>("S"),
+ testing::ElementsAre("(member, q)"));
+}
+
+// The delegate target's arg-to-param pairs are found by the generic AST
+// traversal visiting its underlying CXXConstructExpr directly (not by an
+// explicit recursive call in `findUntypedPairsInDecl` -- an earlier version
+// did that too, double-counting these pairs, since the traversal already
+// visits every written constructor-initializer's init expr on its own).
+TEST_F(PointerFlowPairsTest, DelegatingCtorMatchesDelegateInitExactlyOnce) {
+ ASSERT_TRUE(buildAST(R"cpp(
+ struct S {
+ S(int *a, int *b) {}
+ S(int *p) : S(p, p) {}
+ };
+ )cpp"));
+
+ const auto *Delegator = findCtorByNumParams("S", 1, ctx());
+ ASSERT_TRUE(Delegator);
+ EXPECT_THAT(getPairsFor(Delegator),
+ testing::UnorderedElementsAre("(a, p)", "(b, p)"));
+}
+
+// Same as above, for a base-initializer's underlying CXXConstructExpr.
+TEST_F(PointerFlowPairsTest, BaseCtorInitializerMatchesBaseInitExactlyOnce) {
+ ASSERT_TRUE(buildAST(R"cpp(
+ struct Base { Base(int *a) {} };
+ struct Derived : Base { Derived(int *p) : Base(p) {} };
+ )cpp"));
+
+ EXPECT_THAT(getPairs<CXXConstructorDecl>("Derived"),
+ testing::ElementsAre("(a, p)"));
+}
+
+//////////////////////////////////////////////////////////////
+// Initializer-list decomposition. //
+//////////////////////////////////////////////////////////////
+
+TEST_F(PointerFlowPairsTest, RecordInitListDecomposesPerField) {
+ ASSERT_TRUE(buildAST(R"cpp(
+ struct S { int *a; int *b; };
+ void foo(int *p, int *q) {
+ S s = {p, q};
+ }
+ )cpp"));
+
+ EXPECT_THAT(getPairs("foo"),
+ testing::UnorderedElementsAre("(a, p)", "(b, q)"));
+}
+
+// Record decomposition applies the same way when the record-typed pair comes
+// from call-argument matching rather than a VarDecl initializer.
+TEST_F(PointerFlowPairsTest, CallArgRecordInitListDecomposesPerField) {
+ ASSERT_TRUE(buildAST(R"cpp(
+ struct S { int *a; int *b; };
+ void bar(S s);
+ void foo(int *p, int *q) {
+ bar({p, q});
+ }
+ )cpp"));
+
+ EXPECT_THAT(getPairs("foo"),
+ testing::UnorderedElementsAre("(a, p)", "(b, q)"));
+}
+
+TEST_F(PointerFlowPairsTest, RecordWithBaseClassInitListIsDropped) {
+ ASSERT_TRUE(buildAST(R"cpp(
+ struct Base { int *x; };
+ struct Derived : Base { int *y; };
+ void foo(int *p, int *q) {
+ Derived d = {p, q};
+ }
+ )cpp",
+ {"-std=c++17", "-Wno-unused-value"}));
+
+ EXPECT_THAT(getPairs("foo"), testing::IsEmpty());
+}
+
+TEST_F(PointerFlowPairsTest, UnionInitListPicksActiveField) {
+ ASSERT_TRUE(buildAST(R"cpp(
+ union U { int *x; int y; };
+ void foo(int *p) {
+ U u = {p};
+ }
+ )cpp"));
+
+ EXPECT_THAT(getPairs("foo"), testing::ElementsAre("(x, p)"));
+}
+
+TEST_F(PointerFlowPairsTest, UnionEmptyInitListProducesNoPair) {
+ ASSERT_TRUE(buildAST(R"cpp(
+ union U { int *x; int y; };
+ void foo() {
+ U u = {};
+ }
+ )cpp"));
+
+ EXPECT_THAT(getPairs("foo"), testing::IsEmpty());
+}
+
+TEST_F(PointerFlowPairsTest, ArrayOfPointersInitListIsKeptWhole) {
+ ASSERT_TRUE(buildAST(R"cpp(
+ void foo(int *p, int *q) {
+ int *arr[] = {p, q};
+ }
+ )cpp"));
+
+ EXPECT_THAT(getPairs("foo"), testing::ElementsAre("(arr, {p, q})"));
+}
+
+TEST_F(PointerFlowPairsTest, ArrayOfScalarsInitListIsAlsoKeptWhole) {
+ ASSERT_TRUE(buildAST(R"cpp(
+ void foo(int x, int y) {
+ int arr[] = {x, y};
+ }
+ )cpp"));
+
+ EXPECT_THAT(getPairs("foo"), testing::ElementsAre("(arr, {x, y})"));
+}
+
+TEST_F(PointerFlowPairsTest, CallArgArrayInitListBoundToReferenceIsKeptWhole) {
+ ASSERT_TRUE(buildAST(R"cpp(
+ void bar(int * const (¶m)[2]);
+ void foo(int *p, int *q) {
+ bar({p, q});
+ }
+ )cpp"));
+
+ EXPECT_THAT(getPairs("foo"), testing::ElementsAre("(param, {p, q})"));
+}
+
+TEST_F(PointerFlowPairsTest, RecordFieldOfArrayOfPointersKeepsInitListWhole) {
+ ASSERT_TRUE(buildAST(R"cpp(
+ struct S { int *arr[2]; };
+ S foo(int *p, int *q) {
+ return {p, q};
+ }
+ )cpp"));
+
+ EXPECT_THAT(getPairs("foo"), testing::ElementsAre("(arr, {p, q})"));
+}
+
+// Unlike an array of pointers, an array of records IS decomposed per-element,
+// since each element is itself a record init-list.
+TEST_F(PointerFlowPairsTest, ArrayOfRecordsInitListIsDecomposedPerElement) {
+ ASSERT_TRUE(buildAST(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"));
+
+ EXPECT_THAT(getPairs("foo"), testing::UnorderedElementsAre(
+ "(a, p)", "(b, q)", "(a, r)", "(b, s)"));
+}
+
+// An unnamed bit-field consumes no slot in the semantic InitListExpr, so the
+// field after it must still be paired with the right initializer.
+TEST_F(PointerFlowPairsTest, StructInitListWithUnnamedBitFieldSkipsBitField) {
+ ASSERT_TRUE(buildAST(R"cpp(
+ struct S { int a; int : 4; int *p; };
+ void foo(int a, int *q) {
+ S s = {a, q};
+ }
+ )cpp"));
+
+ EXPECT_THAT(getPairs("foo"), testing::ElementsAre("(p, q)"));
+}
+
+TEST_F(PointerFlowPairsTest, EmptyInitListForScalarProducesNoPair) {
+ ASSERT_TRUE(buildAST(R"cpp(
+ void foo() {
+ int *q = {};
+ }
+ )cpp"));
+
+ EXPECT_THAT(getPairs("foo"), testing::IsEmpty());
+}
+
+TEST_F(PointerFlowPairsTest, SingletonInitListForScalarRecursesToElement) {
+ ASSERT_TRUE(buildAST(R"cpp(
+ void foo(int *p) {
+ int *q = {p};
+ }
+ )cpp"));
+
+ EXPECT_THAT(getPairs("foo"), testing::ElementsAre("(q, p)"));
+}
+
+TEST_F(PointerFlowPairsTest, AssignRHSInitListPeelsSingletonForScalarLHS) {
+ ASSERT_TRUE(buildAST(R"cpp(
+ void foo(int *p, int *q, int *r) {
+ q = {p};
+ r = {};
+ }
+ )cpp"));
+
+ EXPECT_THAT(getPairs("foo"), testing::ElementsAre("(q, p)"));
+}
+
+} // namespace
diff --git a/clang/unittests/ScalableStaticAnalysis/CMakeLists.txt b/clang/unittests/ScalableStaticAnalysis/CMakeLists.txt
index 8c339bee60f63..556c0da1de74b 100644
--- a/clang/unittests/ScalableStaticAnalysis/CMakeLists.txt
+++ b/clang/unittests/ScalableStaticAnalysis/CMakeLists.txt
@@ -1,5 +1,6 @@
add_distinct_clang_unittest(ClangScalableAnalysisTests
Analyses/EntityPointerLevel/EntityPointerLevelTest.cpp
+ Analyses/PointerFlow/PointerFlowPairsTest.cpp
Analyses/PointerFlow/PointerFlowTest.cpp
Analyses/PointerFlow/PointerFlowWPATest.cpp
Analyses/CallGraph/CallGraphExtractorTest.cpp
diff --git a/llvm/utils/gn/secondary/clang/lib/ScalableStaticAnalysis/Analyses/BUILD.gn b/llvm/utils/gn/secondary/clang/lib/ScalableStaticAnalysis/Analyses/BUILD.gn
index 1fcd8177c3844..984dc1d7fae30 100644
--- a/llvm/utils/gn/secondary/clang/lib/ScalableStaticAnalysis/Analyses/BUILD.gn
+++ b/llvm/utils/gn/secondary/clang/lib/ScalableStaticAnalysis/Analyses/BUILD.gn
@@ -18,6 +18,7 @@ static_library("Analyses") {
"PointerFlow/PointerFlowAnalysis.cpp",
"PointerFlow/PointerFlowExtractor.cpp",
"PointerFlow/PointerFlowFormat.cpp",
+ "PointerFlow/PointerFlowPairs.cpp",
"SSAFAnalysesCommon.cpp",
"SharedLexicalRepresentation/EntitySourceLocationExtractor.cpp",
"SharedLexicalRepresentation/SharedLexicalRepresentationFormat.cpp",
diff --git a/llvm/utils/gn/secondary/clang/unittests/ScalableStaticAnalysis/BUILD.gn b/llvm/utils/gn/secondary/clang/unittests/ScalableStaticAnalysis/BUILD.gn
index 0da386d1f3f58..27932a6309d30 100644
--- a/llvm/utils/gn/secondary/clang/unittests/ScalableStaticAnalysis/BUILD.gn
+++ b/llvm/utils/gn/secondary/clang/unittests/ScalableStaticAnalysis/BUILD.gn
@@ -23,6 +23,7 @@ unittest("ClangScalableAnalysisTests") {
"ASTEntityMappingTest.cpp",
"Analyses/CallGraph/CallGraphExtractorTest.cpp",
"Analyses/EntityPointerLevel/EntityPointerLevelTest.cpp",
+ "Analyses/PointerFlow/PointerFlowPairsTest.cpp",
"Analyses/PointerFlow/PointerFlowTest.cpp",
"Analyses/PointerFlow/PointerFlowWPATest.cpp",
"Analyses/SharedLexicalRepresentation/EntitySourceLocationExtractorTest.cpp",
>From d5c1b52715d24b040f3500fd6ce408473ddb3125 Mon Sep 17 00:00:00 2001
From: Ziqing Luo <ziqing_luo at apple.com>
Date: Fri, 11 Sep 2026 16:59:12 -0700
Subject: [PATCH 2/2] fix clang-format
---
.../Analyses/PointerFlow/PointerFlowExtractor.cpp | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/clang/lib/ScalableStaticAnalysis/Analyses/PointerFlow/PointerFlowExtractor.cpp b/clang/lib/ScalableStaticAnalysis/Analyses/PointerFlow/PointerFlowExtractor.cpp
index 45785a80d4603..8b34c76965770 100644
--- a/clang/lib/ScalableStaticAnalysis/Analyses/PointerFlow/PointerFlowExtractor.cpp
+++ b/clang/lib/ScalableStaticAnalysis/Analyses/PointerFlow/PointerFlowExtractor.cpp
@@ -106,8 +106,7 @@ PointerFlowEdgeBuilder::addEdges(const DeclPointerLevelVec &LHS,
return llvm::Error::success();
}
-llvm::Error
-PointerFlowEdgeBuilder::handleRHSAndAddEdges(
+llvm::Error PointerFlowEdgeBuilder::handleRHSAndAddEdges(
const DeclPointerLevelVec &LHS, const Expr *RHS,
unsigned ArrayElementIndirectLevel) {
const auto *ILE = dyn_cast<InitListExpr>(RHS);
More information about the cfe-commits
mailing list