[llvm-branch-commits] [clang] [llvm] [SSAF][clang-reforge] Retrofit expressions after CppBoundedBuffers decl rewrites (PR #223105)
Ziqing Luo via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Sat Sep 12 15:15:04 PDT 2026
https://github.com/ziqingluo-90 updated https://github.com/llvm/llvm-project/pull/223105
>From 81e4717aeca65241db7f18c24a35f12b192a24eb 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/3] [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 dc7fb830f80daa8c9254b9fab821310a64118966 Mon Sep 17 00:00:00 2001
From: Ziqing Luo <ziqing_luo at apple.com>
Date: Thu, 10 Sep 2026 14:33:19 -0700
Subject: [PATCH 2/3] [SSAF][PointerFlow] Drop unused TUSummaryExtractor param
from translateDeclPointerLevel
TUSummaryExtractor is only needed to mint EntityIds when building an
EntityPointerLevel; translateDeclPointerLevel just walks the Expr and
returns raw DeclPointerLevels, so it never touched Extractor.
This is the second patch the radar below depends on:
rdar://187125348
---
.../EntityPointerLevel/EntityPointerLevel.h | 5 +--
.../Analyses/PointerFlow/PointerFlowPairs.h | 2 +
.../EntityPointerLevel/EntityPointerLevel.cpp | 44 ++++++++++---------
.../PointerFlow/PointerFlowExtractor.cpp | 4 +-
4 files changed, 30 insertions(+), 25 deletions(-)
diff --git a/clang/include/clang/ScalableStaticAnalysis/Analyses/EntityPointerLevel/EntityPointerLevel.h b/clang/include/clang/ScalableStaticAnalysis/Analyses/EntityPointerLevel/EntityPointerLevel.h
index 6a4e4c2879ae1..1a6bf072cb0e1 100644
--- a/clang/include/clang/ScalableStaticAnalysis/Analyses/EntityPointerLevel/EntityPointerLevel.h
+++ b/clang/include/clang/ScalableStaticAnalysis/Analyses/EntityPointerLevel/EntityPointerLevel.h
@@ -110,9 +110,8 @@ translateEntityPointerLevel(const Expr *E, ASTContext &Ctx,
/// Same as \c translateEntityPointerLevel, except it returns raw
/// `(NamedDecl *, pointer level, is-return)` tuples (a.k.a. DeclPointerLevels)
/// instead of assembling an `EntityPointerLevelSet` directly.
-llvm::Expected<DeclPointerLevelVec>
-translateDeclPointerLevel(const Expr *E, ASTContext &Ctx,
- TUSummaryExtractor &Extractor);
+llvm::Expected<DeclPointerLevelVec> translateDeclPointerLevel(const Expr *E,
+ ASTContext &Ctx);
/// Assemble `DeclPointerLevels` into an `EntityPointerLevelSet`.
Expected<EntityPointerLevelSet>
diff --git a/clang/include/clang/ScalableStaticAnalysis/Analyses/PointerFlow/PointerFlowPairs.h b/clang/include/clang/ScalableStaticAnalysis/Analyses/PointerFlow/PointerFlowPairs.h
index 06f892a7333cc..5bebbfa5a93c2 100644
--- a/clang/include/clang/ScalableStaticAnalysis/Analyses/PointerFlow/PointerFlowPairs.h
+++ b/clang/include/clang/ScalableStaticAnalysis/Analyses/PointerFlow/PointerFlowPairs.h
@@ -85,6 +85,8 @@ class PointerFlowPairMatcher {
// pointer.
// - Structured bindings (`auto [a, b] = pair;`) -- the per-element
// `BindingDecl`s are neither `VarDecl` nor `FieldDecl`.
+ // - Compound literals (`(T){...}`) -- not handled, and not covered by
+ // tests.
/// Match and collect pointer flow.
/// The macth function 'F' can be described by the following rules:
diff --git a/clang/lib/ScalableStaticAnalysis/Analyses/EntityPointerLevel/EntityPointerLevel.cpp b/clang/lib/ScalableStaticAnalysis/Analyses/EntityPointerLevel/EntityPointerLevel.cpp
index 299f1be30dcef..6646cf7364836 100644
--- a/clang/lib/ScalableStaticAnalysis/Analyses/EntityPointerLevel/EntityPointerLevel.cpp
+++ b/clang/lib/ScalableStaticAnalysis/Analyses/EntityPointerLevel/EntityPointerLevel.cpp
@@ -60,7 +60,8 @@ class EntityPointerLevelTranslator
}
Expected<EntityPointerLevel>
- createEntityPointerLevelFor(const NamedDecl *ND) {
+ createEntityPointerLevelFor(const NamedDecl *ND,
+ TUSummaryExtractor &Extractor) {
std::optional<EntityId> Id = Extractor.addEntity(ND);
if (!Id)
return makeErrAtNode(Ctx, ND, "failed to create EntityId for %s",
@@ -69,7 +70,8 @@ class EntityPointerLevelTranslator
}
Expected<EntityPointerLevel>
- createEntityPointerLevelForReturn(const FunctionDecl *FD) {
+ createEntityPointerLevelForReturn(const FunctionDecl *FD,
+ TUSummaryExtractor &Extractor) {
std::optional<EntityId> Id = Extractor.addEntityForReturn(FD);
if (!Id) {
return makeErrAtNode(Ctx, FD, "failed to create EntityId for function %s",
@@ -91,28 +93,30 @@ class EntityPointerLevelTranslator
return SubResult;
}
- TUSummaryExtractor &Extractor;
ASTContext &Ctx;
public:
- EntityPointerLevelTranslator(TUSummaryExtractor &Extractor, ASTContext &Ctx)
- : Extractor(Extractor), Ctx(Ctx) {}
+ EntityPointerLevelTranslator(ASTContext &Ctx) : Ctx(Ctx) {}
Expected<DeclPointerLevelVec> translate(const Expr *E) { return Visit(E); }
- Expected<EntityPointerLevel> translate(const NamedDecl *D, bool IsRet) {
+ Expected<EntityPointerLevel> translate(const NamedDecl *D, bool IsRet,
+ TUSummaryExtractor &Extractor) {
if (!IsRet)
- return createEntityPointerLevelFor(D);
+ return createEntityPointerLevelFor(D, Extractor);
if (const auto *FD = dyn_cast<FunctionDecl>(D))
- return createEntityPointerLevelForReturn(FD);
+ return createEntityPointerLevelForReturn(FD, Extractor);
return makeErrAtNode(Ctx, D, "attempt to get entity for return of %s",
D->getDeclKindName());
}
// Converts a `DeclPointerLevel` to an `EntityPointerLevel`
- Expected<EntityPointerLevel> toEntityPointerLevel(const DeclPointerLevel &D) {
- Expected<EntityPointerLevel> Base = translate(D.Decl, D.IsReturn);
+ Expected<EntityPointerLevel>
+ toEntityPointerLevel(const DeclPointerLevel &D,
+ TUSummaryExtractor &Extractor) {
+ Expected<EntityPointerLevel> Base =
+ translate(D.Decl, D.IsReturn, Extractor);
if (!Base)
return Base.takeError();
return buildEntityPointerLevel(Base->getEntity(), D.PointerLevel);
@@ -334,9 +338,8 @@ class EntityPointerLevelTranslator
} // namespace clang::ssaf
Expected<DeclPointerLevelVec>
-clang::ssaf::translateDeclPointerLevel(const Expr *E, ASTContext &Ctx,
- TUSummaryExtractor &Extractor) {
- EntityPointerLevelTranslator Translator(Extractor, Ctx);
+clang::ssaf::translateDeclPointerLevel(const Expr *E, ASTContext &Ctx) {
+ EntityPointerLevelTranslator Translator(Ctx);
return Translator.translate(E);
}
@@ -344,7 +347,7 @@ clang::ssaf::translateDeclPointerLevel(const Expr *E, ASTContext &Ctx,
Expected<EntityPointerLevelSet>
clang::ssaf::translateEntityPointerLevel(const Expr *E, ASTContext &Ctx,
TUSummaryExtractor &Extractor) {
- EntityPointerLevelTranslator Translator(Extractor, Ctx);
+ EntityPointerLevelTranslator Translator(Ctx);
auto DPLs = Translator.translate(E);
if (!DPLs)
return DPLs.takeError();
@@ -359,9 +362,9 @@ DeclPointerLevel clang::ssaf::createDeclPointerLevel(const NamedDecl *ND,
/// Create an EntityPointerLevel from a ValueDecl of a pointer type.
Expected<EntityPointerLevel> clang::ssaf::createEntityPointerLevel(
const NamedDecl *ND, TUSummaryExtractor &Extractor, bool IsFunRet) {
- EntityPointerLevelTranslator Translator(Extractor, ND->getASTContext());
+ EntityPointerLevelTranslator Translator(ND->getASTContext());
- return Translator.translate(ND, IsFunRet);
+ return Translator.translate(ND, IsFunRet, Extractor);
}
DeclPointerLevelVec
@@ -400,11 +403,12 @@ Expected<EntityPointerLevelSet>
clang::ssaf::toEntityPointerLevels(const DeclPointerLevelVec &DPLs,
ASTContext &Ctx,
TUSummaryExtractor &Extractor) {
- EntityPointerLevelTranslator Translator(Extractor, Ctx);
+ EntityPointerLevelTranslator Translator(Ctx);
EntityPointerLevelSet Result;
for (const auto &DPL : DPLs) {
- Expected<EntityPointerLevel> EPL = Translator.toEntityPointerLevel(DPL);
+ Expected<EntityPointerLevel> EPL =
+ Translator.toEntityPointerLevel(DPL, Extractor);
if (!EPL)
return EPL.takeError();
Result.insert(*EPL);
@@ -415,8 +419,8 @@ clang::ssaf::toEntityPointerLevels(const DeclPointerLevelVec &DPLs,
Expected<EntityPointerLevel>
clang::ssaf::toEntityPointerLevel(const DeclPointerLevel &DPL, ASTContext &Ctx,
TUSummaryExtractor &Extractor) {
- EntityPointerLevelTranslator Translator(Extractor, Ctx);
- return Translator.toEntityPointerLevel(DPL);
+ EntityPointerLevelTranslator Translator(Ctx);
+ return Translator.toEntityPointerLevel(DPL, Extractor);
}
EntityPointerLevel clang::ssaf::buildEntityPointerLevel(EntityId Id,
diff --git a/clang/lib/ScalableStaticAnalysis/Analyses/PointerFlow/PointerFlowExtractor.cpp b/clang/lib/ScalableStaticAnalysis/Analyses/PointerFlow/PointerFlowExtractor.cpp
index 45785a80d4603..24e6423e07ff7 100644
--- a/clang/lib/ScalableStaticAnalysis/Analyses/PointerFlow/PointerFlowExtractor.cpp
+++ b/clang/lib/ScalableStaticAnalysis/Analyses/PointerFlow/PointerFlowExtractor.cpp
@@ -120,7 +120,7 @@ PointerFlowEdgeBuilder::handleRHSAndAddEdges(
for (DeclPointerLevel &DPL : Copy)
DPL.PointerLevel += ArrayElementIndirectLevel;
- return addEdges(Copy, translateDeclPointerLevel(RHS, Ctx, Extractor));
+ return addEdges(Copy, translateDeclPointerLevel(RHS, Ctx));
}
llvm::Error Err = llvm::Error::success();
@@ -135,7 +135,7 @@ PointerFlowEdgeBuilder::handleRHSAndAddEdges(
llvm::Error PointerFlowEdgeBuilder::operator()(const Expr *LHS,
const Expr *RHS) {
- auto LVec = translateDeclPointerLevel(LHS, Ctx, Extractor);
+ auto LVec = translateDeclPointerLevel(LHS, Ctx);
if (!LVec)
return LVec.takeError();
return handleRHSAndAddEdges(*LVec, RHS);
>From 0b53ccb2fa2b684bfa6d7f1171e2bf8b220a6da8 Mon Sep 17 00:00:00 2001
From: Ziqing Luo <ziqing_luo at apple.com>
Date: Fri, 11 Sep 2026 16:11:28 -0700
Subject: [PATCH 3/3] [SSAF][clang-reforge] Retrofit expressions after
CppBoundedBuffers decl rewrites
PR #210457 introduces declaration rewriting. When CppBoundedBuffers
rewrites a declaration or return type to a bounded_ptr/bounded_array,
some of the existing uses of that entity needs retrofit.
This commit creates expression rewrites for the following patterns:
- Append '.data()' to call arguments when necessary parameter is not
transformed but argument is transformed.
- Append '.as_bounded<T>()' to call arguments when both parameter and
argument are transformed but element types are not identical.
- Rewrite '&e[i]' to '(e + i)' and '&*e'/'&(*e)' to 'e', if 'e' is
transformed.
- Rewrite '(T*)e', 'static_cast<T*>(e)', and 'reinterpret_cast<T*>(e)'
to 'e.as_bounded<T>()', if 'e' is transformed.
The 3rd step of
rdar://187125348
---
.../SourceTransformation/CMakeLists.txt | 9 +
.../Transformations/CppBoundedBuffers.cpp | 451 +++++++++++++++++-
.../CppBoundedBuffersTest.cpp | 247 +++++++++-
.../SourceTransformation/BUILD.gn | 7 +
4 files changed, 708 insertions(+), 6 deletions(-)
diff --git a/clang/lib/ScalableStaticAnalysis/SourceTransformation/CMakeLists.txt b/clang/lib/ScalableStaticAnalysis/SourceTransformation/CMakeLists.txt
index 96e85ec3a50a8..951a40e874398 100644
--- a/clang/lib/ScalableStaticAnalysis/SourceTransformation/CMakeLists.txt
+++ b/clang/lib/ScalableStaticAnalysis/SourceTransformation/CMakeLists.txt
@@ -2,6 +2,13 @@ set(LLVM_LINK_COMPONENTS
Support
)
+# TODO: move SSAFAnalysesCommon.h to a higher-up folder shared by both
+# libraries instead of reaching into a sibling library's private header.
+#
+# Needed to reach clang/lib/ScalableStaticAnalysis/Analyses/SSAFAnalysesCommon.h,
+# a private header shared by the two libraries.
+include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../Analyses)
+
add_clang_library(clangScalableStaticAnalysisSourceTransformation
SARIFTransformationReportFormat.cpp
TransformationRegistry.cpp
@@ -12,6 +19,8 @@ add_clang_library(clangScalableStaticAnalysisSourceTransformation
clangAST
clangBasic
clangLex
+ clangScalableStaticAnalysisAnalyses
clangScalableStaticAnalysisCore
clangToolingCore
+ clangToolingRefactoring
)
diff --git a/clang/lib/ScalableStaticAnalysis/SourceTransformation/Transformations/CppBoundedBuffers.cpp b/clang/lib/ScalableStaticAnalysis/SourceTransformation/Transformations/CppBoundedBuffers.cpp
index c119ee2f01c5e..01c909d5c7597 100644
--- a/clang/lib/ScalableStaticAnalysis/SourceTransformation/Transformations/CppBoundedBuffers.cpp
+++ b/clang/lib/ScalableStaticAnalysis/SourceTransformation/Transformations/CppBoundedBuffers.cpp
@@ -7,11 +7,14 @@
//===----------------------------------------------------------------------===//
#include "clang/ScalableStaticAnalysis/SourceTransformation/Transformations/CppBoundedBuffers.h"
+#include "../../Analyses/SSAFAnalysesCommon.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclBase.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/DynamicRecursiveASTVisitor.h"
+#include "clang/AST/Expr.h"
+#include "clang/AST/ExprCXX.h"
#include "clang/AST/Type.h"
#include "clang/AST/TypeLoc.h"
#include "clang/Basic/LangOptions.h"
@@ -20,6 +23,7 @@
#include "clang/Frontend/SSAFOptions.h"
#include "clang/Lex/Lexer.h"
#include "clang/ScalableStaticAnalysis/Analyses/EntityPointerLevel/EntityPointerLevel.h"
+#include "clang/ScalableStaticAnalysis/Analyses/PointerFlow/PointerFlowPairs.h"
#include "clang/ScalableStaticAnalysis/Analyses/UnsafeBufferUsage/UnsafeBufferUsageAnalysis.h"
#include "clang/ScalableStaticAnalysis/Core/ASTEntityMapping.h"
#include "clang/ScalableStaticAnalysis/Core/Model/EntityId.h"
@@ -27,12 +31,18 @@
#include "clang/ScalableStaticAnalysis/Core/Model/EntityName.h"
#include "clang/ScalableStaticAnalysis/SourceTransformation/TransformationRegistry.h"
#include "clang/Tooling/Core/Replacement.h"
+#include "clang/Tooling/Refactoring/AtomicChange.h"
+#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/Twine.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/ErrorHandling.h"
#include <cassert>
#include <map>
#include <optional>
#include <string>
+#include <vector>
using namespace clang;
using namespace clang::ssaf;
@@ -290,6 +300,11 @@ class CollectVisitor : public DynamicRecursiveASTVisitor {
/// Rewrites or reports every collected declarator and function return.
class RewriteVisitor : public DynamicRecursiveASTVisitor {
public:
+ // Decls and their ClassifyResults for all that are successfully
+ // transformed by `emit`:
+ llvm::DenseMap<const Decl *, ClassifyResult> TransformedDecls;
+ llvm::DenseMap<const FunctionDecl *, ClassifyResult> TransformedReturns;
+
RewriteVisitor(ASTContext &Ctx, DeclLevels &Decls, ReturnLevels &Returns,
SourceEditEmitter &Edits, TransformationReportEmitter &Report)
: Ctx(Ctx), Decls(Decls), Returns(Returns), Edits(Edits), Report(Report) {
@@ -324,8 +339,12 @@ class RewriteVisitor : public DynamicRecursiveASTVisitor {
if (!FunTypeLoc)
return report(FD, ReportReason::EmissionFailed);
- return report(FD, emit(FD->getBeginLoc(), NameLoc,
- FunTypeLoc.getReturnLoc(), FD->getReturnType(), R));
+
+ auto Reason = emit(FD->getBeginLoc(), NameLoc, FunTypeLoc.getReturnLoc(),
+ FD->getReturnType(), R);
+ if (!Reason)
+ TransformedReturns[FD] = R;
+ return report(FD, Reason);
}
private:
@@ -347,7 +366,12 @@ class RewriteVisitor : public DynamicRecursiveASTVisitor {
if (R.Skip)
return (void)report(D, *R.Skip);
- report(D, emit(D->getBeginLoc(), NameLoc, TSI->getTypeLoc(), T, R));
+
+ auto Reason = emit(D->getBeginLoc(), NameLoc, TSI->getTypeLoc(), T, R);
+
+ if (!Reason)
+ TransformedDecls[D] = R;
+ report(D, Reason);
}
/// Compute the precise source range for rewriting. The produced range is
@@ -482,6 +506,113 @@ class RewriteVisitor : public DynamicRecursiveASTVisitor {
TransformationReportEmitter &Report;
};
+// FIXME: adding report for any unsuccessful edits
+// FIXME: we need clusters to group edits atomically
+
+/// Traverses the whole TU and create edits for expressions in order to adapt to
+/// transformed Decls.
+class ExpressionRewriter {
+public:
+ ExpressionRewriter(
+ ASTContext &Ctx, const SSAFOptions &Opts, SourceEditEmitter &Edits,
+ const llvm::DenseMap<const Decl *, ClassifyResult> &TransformedDecls,
+ const llvm::DenseMap<const FunctionDecl *, ClassifyResult>
+ &TransformedReturns)
+ : Ctx(Ctx), Opts(Opts), Edits(Edits), TransformedDecls(TransformedDecls),
+ TransformedReturns(TransformedReturns) {}
+
+ /// Traverses the whole \c TU and create edits expressions in order to adapt
+ /// to transformed Decls.
+ void rewriteExprInTU(const TranslationUnitDecl *TU);
+
+ /// Provided pointer/array expression \c E that \c isExprBaseTransformed(E),
+ /// rewrite the expression if necessary to produce correct bounded type
+ /// expression.
+ ///
+ /// \return true iff a rewrite edit was created.
+ bool rewriteExpression(const Expr *E, tooling::AtomicChange &AC) const;
+
+ /// Adapt expressions of a pointer flow to Decl transformations.
+ /// \return \c tooling::AtomicChange if any edit was created.
+ std::optional<tooling::AtomicChange>
+ adaptPointerFlow(const PointerFlowPair &Pair) const;
+
+private:
+ /// Associate edit operations to \c CharSourceRange always, since they carry
+ /// token/char range info while single SourceLocation doesn't. This way, the
+ /// edit kind (replacement or insertion) cannot be inferred from the input
+ /// (i.e., SourceRange vs. SourceLocation). Use this enum to explicitly
+ /// express the kinds.
+ enum EditKind { Replace, InsertAtBegin, InsertAtEnd };
+
+ /// \param Range The source locations associated with the edit. For any \c
+ /// EditKind, source locations are given by a \c CharSourceRange, which
+ /// carries the information of whether it is a token range or a char range.
+ /// \param NewText The text of the edit that will replace a source range or
+ /// be inserted at a location.
+ /// \param EditKind The kind of edit: replacement or insertion.
+ /// \param AC IN/OUT parameter. The new edit will be added to \c AC
+ /// \return true if edit is successfully added to \c AC
+ bool addEditToAtomicChange(CharSourceRange Range, StringRef NewText,
+ EditKind EditKind,
+ tooling::AtomicChange &AC) const;
+
+ /// \return true iff the base(s) of the pointer/array expression \c E are all
+ /// transformed to have bounded types.
+ bool isExprBaseTransformed(const Expr *E) const;
+
+ /// \return a non-null pointer to a \c ClassifyResult, if `D` is transformed
+ /// to have bounded types.
+ const ClassifyResult *getDeclClassifyResultsIfTransformed(const Decl *D,
+ bool IsRet) const;
+
+ /// \return a non-empty vector of \c ClassifyResult, the base(s) of the
+ /// pointer/array expression \c E are all transformed to have bounded
+ /// types.
+ std::vector<const ClassifyResult *>
+ getPtrExprClassifyResultsIfTransformed(const Expr *E) const;
+
+ friend struct GetLHSClassifyResults;
+
+ //==---------------- Expression rewrite rules -------------------==//
+
+ /// Provided that \c Pair LHS is NOT transformed but RHS is transformed,
+ /// wrap RHS in '(RHS).data()' if appropriate.
+ /// \return true iff RHS needs edit and \c addEditToAtomicChange is called on
+ /// AC.
+ bool appendDataCallToArg(const PointerFlowPair &Pair,
+ tooling::AtomicChange &AC) const;
+
+ /// Provided that \c Pair LHS and RHS are both transformed,
+ /// wrap RHS in '(RHS).as_bounded<T>()', if their element types are not
+ /// identical.
+ /// \return true iff RHS needs edit and \c addEditToAtomicChange is called on
+ /// AC.
+ bool appendAsBoundedCallToArg(const PointerFlowPair &Pair,
+ tooling::AtomicChange &AC) const;
+
+ /// Provided \c E is transformed, if it has the form '&e[i]' or '&*e',
+ /// edit it to '(e + i)' or 'e', resp.
+ /// \return true iff E needs edit and \c addEditToAtomicChange is called on
+ /// AC.
+ bool rewriteAddrofElementAccess(const Expr *E,
+ tooling::AtomicChange &AC) const;
+
+ /// Provided \c E is transformed, if it has the form
+ /// '(T*)e/static_cast<T>(e)/reinterpret_cast<T>(e)', edit it to
+ /// '(e).as_bounded<T>()'.
+ /// \return true iff E needs edit and \c addEditToAtomicChange is called on
+ /// AC.
+ bool rewritePointerCast(const Expr *E, tooling::AtomicChange &AC) const;
+
+ ASTContext &Ctx;
+ const SSAFOptions &Opts;
+ SourceEditEmitter &Edits;
+ const llvm::DenseMap<const Decl *, ClassifyResult> &TransformedDecls;
+ const llvm::DenseMap<const FunctionDecl *, ClassifyResult>
+ &TransformedReturns;
+};
+
} // namespace
namespace clang::ssaf {
@@ -607,14 +738,324 @@ void CppBoundedBuffers::HandleTranslationUnit(ASTContext &Ctx) {
DeclLevels Decls;
ReturnLevels Returns;
- Decl *TU = Ctx.getTranslationUnitDecl();
+ auto *TU = Ctx.getTranslationUnitDecl();
CollectVisitor(Reach, TUNamespace, LUNamespace, Decls, Returns)
.TraverseDecl(TU);
- RewriteVisitor(Ctx, Decls, Returns, Edits, Report).TraverseDecl(TU);
+ auto RV = RewriteVisitor(Ctx, Decls, Returns, Edits, Report);
+
+ RV.TraverseDecl(TU);
+
+ ExpressionRewriter ExprRewriter(Ctx, Opts, Edits, RV.TransformedDecls,
+ RV.TransformedReturns);
+
+ ExprRewriter.rewriteExprInTU(TU);
}
} // namespace clang::ssaf
+namespace {
+
+//===------------ ExpressionRewriter implementation --------------===//
+struct GetLHSClassifyResults {
+ const ExpressionRewriter &ER;
+
+ std::vector<const ClassifyResult *> operator()(const ValueDecl *D,
+ bool IsRet) const {
+ if (const ClassifyResult *R =
+ ER.getDeclClassifyResultsIfTransformed(D, IsRet))
+ return {R};
+ return {};
+ }
+
+ std::vector<const ClassifyResult *> operator()(const Expr *E) const {
+ return ER.getPtrExprClassifyResultsIfTransformed(E);
+ }
+};
+
+std::optional<tooling::AtomicChange>
+ExpressionRewriter::adaptPointerFlow(const PointerFlowPair &Pair) const {
+ auto LHSClassifyResults = Pair.visitLHS(GetLHSClassifyResults{*this});
+ bool IsLHSTransformed = !LHSClassifyResults.empty();
+ bool IsRHSTransformed = isExprBaseTransformed(Pair.RHS);
+ tooling::AtomicChange AC("", "");
+
+ if (IsRHSTransformed)
+ rewriteExpression(Pair.RHS, AC);
+ if (!IsLHSTransformed && IsRHSTransformed)
+ appendDataCallToArg(Pair, AC);
+ if (IsLHSTransformed && IsRHSTransformed)
+ appendAsBoundedCallToArg(Pair, AC);
+ return AC;
+}
+
+bool ExpressionRewriter::rewriteExpression(const Expr *E,
+ tooling::AtomicChange &AC) const {
+ return rewriteAddrofElementAccess(E, AC) || rewritePointerCast(E, AC);
+}
+
+bool ExpressionRewriter::appendDataCallToArg(const PointerFlowPair &Pair,
+ tooling::AtomicChange &AC) const {
+ const auto *PVD =
+ dyn_cast_or_null<ParmVarDecl>(Pair.LHS.dyn_cast<const ValueDecl *>());
+
+ if (!PVD)
+ return false;
+
+ // '(RHS).data()' is a prvalue that can't bind to a reference. So bail.
+ if (PVD->getType()->isReferenceType())
+ return false;
+
+ QualType RTypeBeforeImpCast = Pair.RHS->IgnoreImpCasts()->getType();
+ QualType LType = PVD->getType().getNonReferenceType();
+
+ // If RHS has `void*` type, it will have `char*` after transformation and
+ // being appened '.data()'. This type change may cause the callee to be
+ // silently swapped to a different overload. So bail.
+ if (RTypeBeforeImpCast->isVoidPointerType() && LType->isVoidPointerType())
+ return false;
+
+ CharSourceRange RHSRange = Lexer::getAsCharRange(
+ Pair.RHS->getSourceRange(), Ctx.getSourceManager(), Ctx.getLangOpts());
+
+ addEditToAtomicChange(RHSRange, "(", EditKind::InsertAtBegin, AC);
+ addEditToAtomicChange(RHSRange, ").data()", EditKind::InsertAtEnd, AC);
+ return true;
+}
+
+bool ExpressionRewriter::appendAsBoundedCallToArg(
+ const PointerFlowPair &Pair, tooling::AtomicChange &AC) const {
+ const auto *PVD =
+ dyn_cast_or_null<ParmVarDecl>(Pair.LHS.dyn_cast<const ValueDecl *>());
+
+ if (!PVD)
+ return false;
+
+ CharSourceRange RHSRange = Lexer::getAsCharRange(
+ Pair.RHS->getSourceRange(), Ctx.getSourceManager(), Ctx.getLangOpts());
+ QualType RTypeBeforeImpCast = Pair.RHS->IgnoreImpCasts()->getType();
+ QualType LType = PVD->getType().getNonReferenceType();
+ QualType RPteTy = RTypeBeforeImpCast->getPointeeType();
+ QualType LPteTy = LType->getPointeeType();
+
+ if (LPteTy.isNull() || RPteTy.isNull())
+ return false;
+
+ if (Ctx.hasSameType(LPteTy, RPteTy))
+ return false;
+
+ auto *LHSClassifyResult = getDeclClassifyResultsIfTransformed(PVD, false);
+
+ if (!LHSClassifyResult)
+ return false;
+
+ StringRef LHSInnerSpelling = LHSClassifyResult->InnerSpelling;
+
+ addEditToAtomicChange(RHSRange, "(", EditKind::InsertAtBegin, AC);
+ addEditToAtomicChange(
+ RHSRange,
+ (").as_bounded<" + LHSInnerSpelling + ">()").getSingleStringRef(),
+ EditKind::InsertAtEnd, AC);
+ return true;
+}
+
+bool ExpressionRewriter::rewriteAddrofElementAccess(
+ const Expr *E, tooling::AtomicChange &AC) const {
+ const auto *UO = dyn_cast<UnaryOperator>(E->IgnoreParenImpCasts());
+ if (!UO || UO->getOpcode() != UO_AddrOf)
+ return false;
+
+ const Expr *SubExpr = UO->getSubExpr()->IgnoreParenImpCasts();
+ const Expr *Ptr, *Offset = nullptr;
+
+ if (const auto *ASE = dyn_cast<ArraySubscriptExpr>(SubExpr)) {
+ Ptr = ASE->getBase();
+ Offset = ASE->getIdx();
+ } else if (const auto *Deref = dyn_cast<UnaryOperator>(SubExpr);
+ Deref && Deref->getOpcode() == UO_Deref) {
+ Ptr = Deref->getSubExpr();
+ } else
+ return false;
+
+ rewriteExpression(Ptr, AC);
+ // Ptr may have been recursively edited, so its source range should stay
+ // intact.
+
+ const SourceManager &SM = Ctx.getSourceManager();
+ const LangOptions &LO = Ctx.getLangOpts();
+ auto PtrCR = Lexer::getAsCharRange(Ptr->getSourceRange(), SM, LO);
+ auto FullExprCR = Lexer::getAsCharRange(UO->getSourceRange(), SM, LO);
+ // Source range before `Ptr`:
+ auto PrePtrCR =
+ CharSourceRange::getCharRange(FullExprCR.getBegin(), PtrCR.getBegin());
+
+ if (!Offset) {
+ // Source range after `Ptr`:
+ auto PostPtrCR =
+ CharSourceRange::getCharRange(PtrCR.getEnd(), FullExprCR.getEnd());
+ // For '&*ptr' or '&(*ptr)', drop contents in PrePtrCR and PostPtrCR:
+ addEditToAtomicChange(PrePtrCR, "", EditKind::Replace, AC);
+ addEditToAtomicChange(PostPtrCR, "", EditKind::Replace, AC);
+ return true;
+ }
+
+ auto OffsetCR = Lexer::getAsCharRange(Offset->getSourceRange(), SM, LO);
+ auto PostPtrPreOffsetCR =
+ CharSourceRange::getCharRange(PtrCR.getEnd(), OffsetCR.getBegin());
+ auto PostOffsetCR =
+ CharSourceRange::getCharRange(OffsetCR.getEnd(), FullExprCR.getEnd());
+
+ // For '&ptr[offset]' or '&(ptr[offset])',
+ // 1. replace contents in PrePtrCR with "(", and
+ // 2. replace contents in PostPtrPreOffsetCR with " + ", and
+ // 3. replace contents in postOffsetCR with ") ",
+ // results in '(ptr + offset)':
+ addEditToAtomicChange(PrePtrCR, "(", EditKind::Replace, AC);
+ addEditToAtomicChange(PostPtrPreOffsetCR, " + ", EditKind::Replace, AC);
+ addEditToAtomicChange(PostOffsetCR, ")", EditKind::Replace, AC);
+ return true;
+}
+
+bool ExpressionRewriter::rewritePointerCast(const Expr *E,
+ tooling::AtomicChange &AC) const {
+ const auto *CE = dyn_cast<ExplicitCastExpr>(E->IgnoreParenImpCasts());
+
+ if (!CE ||
+ !isa<CStyleCastExpr, CXXStaticCastExpr, CXXReinterpretCastExpr>(CE))
+ return false;
+
+ QualType DestTy = CE->getTypeAsWritten();
+ QualType DestPteTy = DestTy->getPointeeType();
+
+ if (!DestTy->isPointerType())
+ return false;
+
+ const Expr *Ptr = CE->getSubExpr();
+ rewriteExpression(Ptr, AC);
+ // Ptr may have been recursively edited, so its source range should stay
+ // intact.
+
+ const SourceManager &SM = Ctx.getSourceManager();
+ const LangOptions &LO = Ctx.getLangOpts();
+ CharSourceRange PtrCR =
+ Lexer::getAsCharRange(CE->getSubExpr()->getSourceRange(), SM, LO);
+ CharSourceRange FullCastExprCR =
+ Lexer::getAsCharRange(CE->getSourceRange(), SM, LO);
+ CharSourceRange PrePtrCR = CharSourceRange::getCharRange(
+ FullCastExprCR.getBegin(), PtrCR.getBegin());
+ CharSourceRange PostPtrCR =
+ CharSourceRange::getCharRange(PtrCR.getEnd(), FullCastExprCR.getEnd());
+ std::string T = spell(DestPteTy, Ctx);
+
+ // For `(T*)ptr` or *_cast<T>(ptr),
+ // 1. replace contents in PrePtrCR with "(", and
+ // 2. replace contents in PostPtrCR with ").as_bounded<T>()",
+ // results in '(ptr).as_bounded<T>()'.
+ addEditToAtomicChange(PrePtrCR, "(", EditKind::Replace, AC);
+ addEditToAtomicChange(PostPtrCR, ").as_bounded<" + T + ">()",
+ EditKind::Replace, AC);
+ return true;
+}
+
+void ExpressionRewriter::rewriteExprInTU(const TranslationUnitDecl *TU) {
+ llvm::DenseMap<const NamedDecl *, std::vector<const NamedDecl *>>
+ ContributorGroups;
+
+ findContributors(Ctx, Opts, ContributorGroups,
+ /*ExtractFromSystemHeaders=*/false);
+
+ llvm::SmallVector<PointerFlowPair> Pairs;
+ PointerFlowPairMatcher Matcher{Ctx};
+
+ for (auto &[GrpCano, ContriGrp] : ContributorGroups)
+ for (auto *ContriDecl : ContriGrp) {
+ auto PairsCollector = [&Pairs, &Matcher,
+ &ContriDecl](const DynTypedNode &Node) {
+ Matcher.matches(Node, ContriDecl, Pairs);
+ };
+ findMatchesIn(ContriDecl, PairsCollector);
+ }
+
+ for (const PointerFlowPair &Pair : Pairs) {
+ if (auto AC = adaptPointerFlow(Pair);
+ AC && llvm::all_of(AC->getReplacements(),
+ std::mem_fn(&tooling::Replacement::isApplicable)))
+ for (const tooling::Replacement &R : AC->getReplacements())
+ Edits.addReplacement(R);
+ }
+}
+
+bool ExpressionRewriter::isExprBaseTransformed(const Expr *E) const {
+ return !getPtrExprClassifyResultsIfTransformed(E).empty();
+}
+
+const ClassifyResult *
+ExpressionRewriter::getDeclClassifyResultsIfTransformed(const Decl *D,
+ bool IsRet) const {
+ auto Lookup = [](const auto &Map, const auto *Key) {
+ auto It = Map.find(Key);
+ return It == Map.end() ? nullptr : &It->second;
+ };
+ return IsRet ? Lookup(TransformedReturns, cast<FunctionDecl>(D))
+ : Lookup(TransformedDecls, D);
+}
+
+std::vector<const ClassifyResult *>
+ExpressionRewriter::getPtrExprClassifyResultsIfTransformed(
+ const Expr *E) const {
+ auto DPLs = translateDeclPointerLevel(E, Ctx);
+
+ if (!DPLs) {
+ // Errors indicate no transformation for E. No further action.
+ llvm::consumeError(DPLs.takeError());
+ return {};
+ }
+
+ std::vector<const ClassifyResult *> Result;
+
+ for (auto &DPL : *DPLs) {
+ const auto *ClassifyResult =
+ getDeclClassifyResultsIfTransformed(DPL.Decl, DPL.IsReturn);
+
+ if (!ClassifyResult)
+ return {};
+ Result.push_back(ClassifyResult);
+ }
+ return Result;
+}
+
+bool ExpressionRewriter::addEditToAtomicChange(
+ CharSourceRange Range, StringRef NewText, EditKind EditKind,
+ tooling::AtomicChange &AC) const {
+ assert(Range.isCharRange());
+
+ if (Range.getBegin().isMacroID() || Range.getEnd().isMacroID())
+ // FIXME: report...
+ return false;
+
+ const SourceManager &SM = Ctx.getSourceManager();
+ llvm::Error Err = [&]() -> llvm::Error {
+ switch (EditKind) {
+ case Replace:
+ return AC.replace(SM, Range, NewText);
+ case InsertAtBegin:
+ return AC.insert(SM, Range.getBegin(), NewText, /*InsertAfter=*/false);
+ case InsertAtEnd:
+ return AC.insert(SM, Range.getEnd(), NewText, /*InsertAfter=*/true);
+ }
+ llvm_unreachable("unhandled EditKind");
+ }();
+
+ if (Err) {
+ llvm::consumeError(std::move(Err));
+ // FIXME: generate a Report
+ // If AtomicChange has an error,the whole should be discard
+ return false;
+ }
+ return true;
+}
+
+} // namespace
+
namespace clang::ssaf {
// NOLINTNEXTLINE(misc-use-internal-linkage)
volatile int CppBoundedBuffersAnchorSource = 0;
diff --git a/clang/unittests/ScalableStaticAnalysis/SourceTransformation/CppBoundedBuffersTest.cpp b/clang/unittests/ScalableStaticAnalysis/SourceTransformation/CppBoundedBuffersTest.cpp
index 54fc08b6e8ab7..4d0ba31cc36ed 100644
--- a/clang/unittests/ScalableStaticAnalysis/SourceTransformation/CppBoundedBuffersTest.cpp
+++ b/clang/unittests/ScalableStaticAnalysis/SourceTransformation/CppBoundedBuffersTest.cpp
@@ -24,7 +24,6 @@
#include "clang/ScalableStaticAnalysis/SourceTransformation/TransformationReportEmitter.h"
#include "clang/Tooling/Core/Replacement.h"
#include "clang/Tooling/Tooling.h"
-#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/SmallSet.h"
#include "llvm/ADT/StringSet.h"
#include "llvm/Support/Error.h"
@@ -687,4 +686,250 @@ TEST_F(CppBoundedBuffersTest, MessageForIsUnique) {
}
}
+//===----------------------------------------------------------------------===//
+// Expression rewriting tests
+//===----------------------------------------------------------------------===//
+
+TEST_F(CppBoundedBuffersTest, CallArgAddDataDRE) {
+ StringRef Code = R"cpp(
+ void f(int *p);
+ void g() { int *q; f(q); }
+ )cpp";
+ Captured C = runMarked(Code, [](ASTContext &Ctx, WPASuite &Suite,
+ UnsafeBufferReachableAnalysisResult &Result) {
+ markReachable(Suite, Result, varEntity("q", Ctx), {1});
+ });
+ EXPECT_EQ(C.Rewritten, R"cpp(
+ void f(int *p);
+ void g() { bounded_ptr<int> q; f((q).data()); }
+ )cpp");
+ EXPECT_TRUE(C.Reports.empty());
+}
+
+TEST_F(CppBoundedBuffersTest, CallArgAddDataAddrOfDeref) {
+ StringRef Code = R"cpp(
+ void f(int *p);
+ void g() { int *q; f(&*q); }
+ )cpp";
+ Captured C = runMarked(Code, [](ASTContext &Ctx, WPASuite &Suite,
+ UnsafeBufferReachableAnalysisResult &Result) {
+ markReachable(Suite, Result, varEntity("q", Ctx), {1});
+ });
+ EXPECT_EQ(C.Rewritten, R"cpp(
+ void f(int *p);
+ void g() { bounded_ptr<int> q; f((q).data()); }
+ )cpp");
+ EXPECT_TRUE(C.Reports.empty());
+}
+
+TEST_F(CppBoundedBuffersTest, CallArgAddDataAddrOfDerefParen) {
+ StringRef Code = R"cpp(
+ void f(int *p);
+ void g() { int *q; f(&(*q)); }
+ )cpp";
+ Captured C = runMarked(Code, [](ASTContext &Ctx, WPASuite &Suite,
+ UnsafeBufferReachableAnalysisResult &Result) {
+ markReachable(Suite, Result, varEntity("q", Ctx), {1});
+ });
+ EXPECT_EQ(C.Rewritten, R"cpp(
+ void f(int *p);
+ void g() { bounded_ptr<int> q; f((q).data()); }
+ )cpp");
+ EXPECT_TRUE(C.Reports.empty());
+}
+
+TEST_F(CppBoundedBuffersTest, CallArgAddDataAddrOfSubscript) {
+ StringRef Code = R"cpp(
+ void f(int *p);
+ void g(int i) { int arr[4]; f(&arr[i]); }
+ )cpp";
+ Captured C =
+ run(Code, [](ASTContext &Ctx) { return varEntity("arr", Ctx); }, {1});
+ EXPECT_EQ(C.Rewritten, R"cpp(
+ void f(int *p);
+ void g(int i) { bounded_array<int, 4> arr; f(((arr + i)).data()); }
+ )cpp");
+ EXPECT_TRUE(C.Reports.empty());
+}
+
+TEST_F(CppBoundedBuffersTest, CallArgAddDataAddrOfSubscript2) {
+ StringRef Code = R"cpp(
+ void f(int *p);
+ void g(int i) { int *q; f(&q[i]); }
+ )cpp";
+ Captured C =
+ run(Code, [](ASTContext &Ctx) { return varEntity("q", Ctx); }, {1});
+ EXPECT_EQ(C.Rewritten, R"cpp(
+ void f(int *p);
+ void g(int i) { bounded_ptr<int> q; f(((q + i)).data()); }
+ )cpp");
+ EXPECT_TRUE(C.Reports.empty());
+}
+
+TEST_F(CppBoundedBuffersTest, CallArgAddDataPtrCast) {
+ StringRef Code = R"cpp(
+ void f(char *p);
+ void g() { int *q; f((char*)q); }
+ )cpp";
+ Captured C = runMarked(Code, [](ASTContext &Ctx, WPASuite &Suite,
+ UnsafeBufferReachableAnalysisResult &Result) {
+ markReachable(Suite, Result, varEntity("q", Ctx), {1});
+ });
+ EXPECT_EQ(C.Rewritten, R"cpp(
+ void f(char *p);
+ void g() { bounded_ptr<int> q; f(((q).as_bounded<char>()).data()); }
+ )cpp");
+ EXPECT_TRUE(C.Reports.empty());
+}
+
+TEST_F(CppBoundedBuffersTest, CallArgAddDataPtrCastStaticCast) {
+ StringRef Code = R"cpp(
+ void f(char *p);
+ void g() { int *q; f(reinterpret_cast<char*>(q)); }
+ )cpp";
+ Captured C = runMarked(Code, [](ASTContext &Ctx, WPASuite &Suite,
+ UnsafeBufferReachableAnalysisResult &Result) {
+ markReachable(Suite, Result, varEntity("q", Ctx), {1});
+ });
+ EXPECT_EQ(C.Rewritten, R"cpp(
+ void f(char *p);
+ void g() { bounded_ptr<int> q; f(((q).as_bounded<char>()).data()); }
+ )cpp");
+ EXPECT_TRUE(C.Reports.empty());
+}
+
+TEST_F(CppBoundedBuffersTest, CallArgAddDataPtrCastAddrOfSubscript) {
+ StringRef Code = R"cpp(
+ void f(char *p);
+ void g(int i) {
+ int *q;
+ f((char*)&q[i]);
+ }
+ )cpp";
+ Captured C = runMarked(Code, [](ASTContext &Ctx, WPASuite &Suite,
+ UnsafeBufferReachableAnalysisResult &Result) {
+ markReachable(Suite, Result, varEntity("q", Ctx), {1});
+ });
+ EXPECT_EQ(C.Rewritten, R"cpp(
+ void f(char *p);
+ void g(int i) {
+ bounded_ptr<int> q;
+ f((((q + i)).as_bounded<char>()).data());
+ }
+ )cpp");
+ EXPECT_TRUE(C.Reports.empty());
+}
+
+TEST_F(CppBoundedBuffersTest, AssignPtrCastAddrOfSubscript) {
+ // The argument is a C-style cast of an address-of-subscript, '(char*)&q[i]'.
+ // 'q' is transformed but f's parameter is not.
+ StringRef Code = R"cpp(
+ void g(int i) {
+ int *q;
+ char *p = (char*)&q[i];
+ }
+ )cpp";
+ Captured C = runMarked(Code, [](ASTContext &Ctx, WPASuite &Suite,
+ UnsafeBufferReachableAnalysisResult &Result) {
+ markReachable(Suite, Result, varEntity("q", Ctx), {1});
+ });
+ EXPECT_EQ(C.Rewritten, R"cpp(
+ void g(int i) {
+ bounded_ptr<int> q;
+ char *p = ((q + i)).as_bounded<char>();
+ }
+ )cpp");
+ EXPECT_TRUE(C.Reports.empty());
+}
+
+TEST_F(CppBoundedBuffersTest, CallArgNoChangeImplicit) {
+ StringRef Code = R"cpp(
+ void f(int *p);
+ void g() { int *q; f(q); }
+ )cpp";
+ Captured C = runMarked(Code, [](ASTContext &Ctx, WPASuite &Suite,
+ UnsafeBufferReachableAnalysisResult &Result) {
+ markReachable(Suite, Result, paramEntity("f", 0, Ctx), {1});
+ });
+ EXPECT_EQ(C.Rewritten, R"cpp(
+ void f(bounded_ptr<int> p);
+ void g() { int *q; f(q); }
+ )cpp");
+ EXPECT_TRUE(C.Reports.empty());
+}
+
+TEST_F(CppBoundedBuffersTest, CallArgNoChangeBothHardened) {
+ // Both the parameter and the argument are rewritten to the same bounded
+ // type, so the call site needs no adjustment.
+ StringRef Code = R"cpp(
+ void f(int *p);
+ void g() { int *q; f(q); }
+ )cpp";
+ Captured C = runMarked(Code, [](ASTContext &Ctx, WPASuite &Suite,
+ UnsafeBufferReachableAnalysisResult &Result) {
+ markReachable(Suite, Result, paramEntity("f", 0, Ctx), {1});
+ markReachable(Suite, Result, varEntity("q", Ctx), {1});
+ });
+ EXPECT_EQ(C.Rewritten, R"cpp(
+ void f(bounded_ptr<int> p);
+ void g() { bounded_ptr<int> q; f(q); }
+ )cpp");
+ EXPECT_TRUE(C.Reports.empty());
+}
+
+TEST_F(CppBoundedBuffersTest, CallArgNoChangeVoidParamVoidArg) {
+ // 'q' is void*, so it gets rewritten to 'bounded_ptr<char>', not
+ // 'bounded_ptr<void>'. If '.data()' were appended it would return
+ // 'char *', which could silently steal a different overload of 'f' than
+ // the original 'void *' argument would have. So bail rather than adding
+ // '.data()' when both the parameter and the argument's original type are
+ // 'void*'.
+ StringRef Code = R"cpp(
+ void f(void *p);
+ void g() { void *q; f(q); }
+ )cpp";
+ Captured C = runMarked(Code, [](ASTContext &Ctx, WPASuite &Suite,
+ UnsafeBufferReachableAnalysisResult &Result) {
+ markReachable(Suite, Result, varEntity("q", Ctx), {1});
+ });
+ EXPECT_EQ(C.Rewritten, R"cpp(
+ void f(void *p);
+ void g() { bounded_ptr<char> q; f(q); }
+ )cpp");
+ EXPECT_TRUE(C.Reports.empty());
+}
+
+TEST_F(CppBoundedBuffersTest, CallArgNoChangeArrayDecayMatchingType) {
+ // 'arr' is passed directly (no cast), so its element type must already
+ // match 'f's parameter pointee type pre-transformation. The implicit
+ // 'bounded_array<T, N>' -> 'bounded_ptr<T>' conversion bridges this case,
+ // so no '.as_bounded<T>()' is needed at the call site.
+ StringRef Code = R"cpp(
+ void f(int *p);
+ void g() { int arr[4]; f(arr); }
+ )cpp";
+ Captured C = runMarked(Code, [](ASTContext &Ctx, WPASuite &Suite,
+ UnsafeBufferReachableAnalysisResult &Result) {
+ markReachable(Suite, Result, paramEntity("f", 0, Ctx), {1});
+ markReachable(Suite, Result, varEntity("arr", Ctx), {1});
+ });
+ EXPECT_EQ(C.Rewritten, R"cpp(
+ void f(bounded_ptr<int> p);
+ void g() { bounded_array<int, 4> arr; f(arr); }
+ )cpp");
+ EXPECT_TRUE(C.Reports.empty());
+}
+
+TEST_F(CppBoundedBuffersTest, RHSNoChangeRHSNotHardened) {
+ StringRef Code = R"cpp(
+ void g() { int arr[4]; int *p = &arr[0]; }
+ )cpp";
+ Captured C =
+ run(Code, [](ASTContext &Ctx) { return varEntity("p", Ctx); }, {1});
+ EXPECT_EQ(C.Rewritten, R"cpp(
+ void g() { int arr[4]; bounded_ptr<int> p = &arr[0]; }
+ )cpp");
+ EXPECT_TRUE(C.Reports.empty());
+}
+
} // namespace
diff --git a/llvm/utils/gn/secondary/clang/lib/ScalableStaticAnalysis/SourceTransformation/BUILD.gn b/llvm/utils/gn/secondary/clang/lib/ScalableStaticAnalysis/SourceTransformation/BUILD.gn
index e6e1af033db8a..0ffac2699ffae 100644
--- a/llvm/utils/gn/secondary/clang/lib/ScalableStaticAnalysis/SourceTransformation/BUILD.gn
+++ b/llvm/utils/gn/secondary/clang/lib/ScalableStaticAnalysis/SourceTransformation/BUILD.gn
@@ -1,11 +1,18 @@
static_library("SourceTransformation") {
output_name = "clangScalableStaticAnalysisSourceTransformation"
configs += [ "//llvm/utils/gn/build:clang_code" ]
+
+ # Needed to reach
+ # clang/lib/ScalableStaticAnalysis/Analyses/SSAFAnalysesCommon.h, a private
+ # header shared by the two libraries.
+ include_dirs = [ "//clang/lib/ScalableStaticAnalysis/Analyses" ]
deps = [
"//clang/lib/AST",
"//clang/lib/Basic",
+ "//clang/lib/ScalableStaticAnalysis/Analyses",
"//clang/lib/ScalableStaticAnalysis/Core",
"//clang/lib/Tooling/Core",
+ "//clang/lib/Tooling/Refactoring",
"//llvm/lib/Support",
]
sources = [
More information about the llvm-branch-commits
mailing list