https://github.com/ziqingluo-90 updated https://github.com/llvm/llvm-project/pull/222780
>From a73dac50f2d8c5c88c8fe4f84b12aa445e0ada9a Mon Sep 17 00:00:00 2001 From: Ziqing Luo <[email protected]> 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 58b7d983bf4e76f51298f2db2eae66952d622b21 Mon Sep 17 00:00:00 2001 From: Ziqing Luo <[email protected]> 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); _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
