https://github.com/ziqingluo-90 updated https://github.com/llvm/llvm-project/pull/197568
>From c096a1004ca0f78c10895c3fd0e26959c9d2ea68 Mon Sep 17 00:00:00 2001 From: Ziqing Luo <[email protected]> Date: Wed, 13 May 2026 14:36:46 -0700 Subject: [PATCH 1/2] [SSAF] Increase Expr kind coverage in EntityPointerLevelTranslator Add support for more kinds of Expr that can be translated to EntityPointerLevel(s). Additionally, fix bugs in PointerFlowExtractor discovered by tests added for the new Expr kinds. --- .../EntityPointerLevel/EntityPointerLevel.cpp | 109 +++++++++++++++-- .../PointerFlow/PointerFlowExtractor.cpp | 15 ++- .../Analyses/PointerFlow/PointerFlowTest.cpp | 67 ++++++++++ .../UnsafeBufferUsageTest.cpp | 114 ++++++++++++++++++ 4 files changed, 291 insertions(+), 14 deletions(-) diff --git a/clang/lib/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.cpp b/clang/lib/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.cpp index da6084f4d41a8..d1aa41bd03d47 100644 --- a/clang/lib/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.cpp +++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.cpp @@ -10,6 +10,8 @@ #include "SSAFAnalysesCommon.h" #include "clang/AST/ASTContext.h" #include "clang/AST/Decl.h" +#include "clang/AST/Expr.h" +#include "clang/AST/ExprCXX.h" #include "clang/AST/StmtVisitor.h" #include "clang/ScalableStaticAnalysisFramework/Core/TUSummary/TUSummaryExtractor.h" #include <optional> @@ -43,10 +45,17 @@ class EntityPointerLevelTranslator friend class StmtVisitorBase; // Fallback method for all unsupported expression kind: - llvm::Error fallback(const Stmt *E) { - return makeErrAtNode(Ctx, E, - "attempt to translate %s to EntityPointerLevels", - E->getStmtClassName()); + Expected<EntityPointerLevelSet> fallback(const Stmt *S) { + // Report an error/warning (at least in debug mode) for any unsupported kind + // of pointer/array typed expression, because we want to understand every + // pointer/array expression. But for non-pointer/array typed expressions, we + // could silently ignore unsupported kinds. This translator visits + // non-pointer/array typed expressions because of address-of expressions. + if (const Expr *E = dyn_cast<Expr>(S); E && hasPtrOrArrType(E)) + return makeErrAtNode(Ctx, E, + "attempt to translate %s to EntityPointerLevels", + E->getStmtClassName()); + return EntityPointerLevelSet{}; } Expected<EntityPointerLevel> @@ -142,6 +151,7 @@ class EntityPointerLevelTranslator // Translate(*base) -> Translate(base) with .pointerLevel += 1 // Translate(&base) -> {}, if Translate(base) is {} // -> Translate(base) with .pointerLevel -= 1 + // Translate(+base) -> Translate(base) Expected<EntityPointerLevelSet> VisitUnaryOperator(const UnaryOperator *E) { switch (E->getOpcode()) { case clang::UO_PostInc: @@ -159,6 +169,8 @@ class EntityPointerLevelTranslator } case clang::UO_Deref: return translateDereferencePointer(E->getSubExpr()); + case clang::UO_Plus: + return Visit(E->getSubExpr()); default: return fallback(E); } @@ -209,13 +221,21 @@ class EntityPointerLevelTranslator return Visit(E->getSubExpr()); } - // Translate("string-literal") -> {} - // Buffer accesses on string literals are unsafe, but string literals are not - // entities so there is no EntityPointerLevel associated with it. + // Translate("string-literal") -> {} // no entity involved Expected<EntityPointerLevelSet> VisitStringLiteral(const StringLiteral *E) { return EntityPointerLevelSet{}; } + // Translate(predefined-expr) -> {} // treated the same as string literals + Expected<EntityPointerLevelSet> VisitPredefinedExpr(const PredefinedExpr *E) { + return EntityPointerLevelSet{}; + } + + // Translate(integer-literal) -> {} // no entity involved + Expected<EntityPointerLevelSet> VisitIntegerLiteral(const IntegerLiteral *E) { + return EntityPointerLevelSet{}; + } + // Translate(DRE) -> {(Decl, 1)} Expected<EntityPointerLevelSet> VisitDeclRefExpr(const DeclRefExpr *E) { auto Res = createEntityPointerLevelFor(E->getDecl()); @@ -232,16 +252,89 @@ class EntityPointerLevelTranslator return EntityPointerLevelSet{*Res}; } - // Translate(`DefaultArg`) -> Translate(`DefaultArg->getExpr()`) + // Unwrap CXXDefaultArgExpr Expected<EntityPointerLevelSet> VisitCXXDefaultArgExpr(const CXXDefaultArgExpr *E) { return Visit(E->getExpr()); } + // Unwrap OpaqueValueExpr Expected<EntityPointerLevelSet> VisitOpaqueValueExpr(const OpaqueValueExpr *S) { return Visit(S->getSourceExpr()); } + + // Unwrap ExprWithCleanups + Expected<EntityPointerLevelSet> + VisitExprWithCleanups(const ExprWithCleanups *S) { + return Visit(S->getSubExpr()); + } + + // Unwrap MaterializeTemporaryExpr + Expected<EntityPointerLevelSet> + VisitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *S) { + return Visit(S->getSubExpr()); + } + + // Unwrap CXXDefaultInitExpr + Expected<EntityPointerLevelSet> + VisitCXXDefaultInitExpr(const CXXDefaultInitExpr *E) { + return Visit(E->getExpr()); + } + + // Translate(`nullptr`) -> {} + Expected<EntityPointerLevelSet> + VisitCXXNullPtrLiteralExpr(const CXXNullPtrLiteralExpr *S) { + return EntityPointerLevelSet{}; + } + + // Translate(`this`) -> {} + Expected<EntityPointerLevelSet> VisitCXXThisExpr(const CXXThisExpr *S) { + return EntityPointerLevelSet{}; + } + + // Translate(`new`/`new [*]`) -> {} + Expected<EntityPointerLevelSet> VisitCXXNewExpr(const CXXNewExpr *S) { + return EntityPointerLevelSet{}; + } + + // ImplicitValueInitExpr, for raw pointer type, + // evaluates to a compile-time constant zero (or null). So no EPL in the + // result. + Expected<EntityPointerLevelSet> + VisitImplicitValueInitExpr(const ImplicitValueInitExpr *S) { + return EntityPointerLevelSet{}; + } + + // Fall back if the InitListExpr is not an empty or singleton list that + // initializes a pointer scalar. EntityPointerLevelTranslator does not + // expect to visit an InitListExpr in any other case. + Expected<EntityPointerLevelSet> VisitInitListExpr(const InitListExpr *E) { + if (E->getNumInits() < 1) + return EntityPointerLevelSet{}; + if (E->getType()->isPointerType()) + return Visit(E->getInit(0)); + return fallback(E); + } + + // Clang may default initializes an array with a CXXConstructExpr. Fallback on + // other cases, if they exist. + // When a CXXConstructExpr has an array type, clang is initializing an array + // of class-type objects with default values. In this case, no entity is + // associated with the initializer. + Expected<EntityPointerLevelSet> + VisitCXXConstructExpr(const CXXConstructExpr *E) { + if (E->getType()->isArrayType()) { + return EntityPointerLevelSet{}; + } + return fallback(E); + } + + // No entity is associated with a CXXScalarValueInitExpr: + Expected<EntityPointerLevelSet> + VisitCXXScalarValueInitExpr(const CXXScalarValueInitExpr *E) { + return EntityPointerLevelSet{}; + } }; } // namespace clang::ssaf diff --git a/clang/lib/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowExtractor.cpp b/clang/lib/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowExtractor.cpp index e1130a2c52e4c..3bd088284249c 100644 --- a/clang/lib/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowExtractor.cpp +++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowExtractor.cpp @@ -107,8 +107,9 @@ PointerFlowMatcher::addEdges(Expected<EntityPointerLevelSet> &&LHS, return LHS.takeError(); if (!RHS) return RHS.takeError(); - for (auto L : *LHS) - Results[L].insert(RHS->begin(), RHS->end()); + if (!RHS->empty()) + for (auto L : *LHS) + Results[L].insert(RHS->begin(), RHS->end()); return llvm::Error::success(); } @@ -222,9 +223,8 @@ llvm::Error matchInitializerListForRecordDecl(PointerFlowMatcher &Matcher, if (RecordTy->isUnion()) { auto *InitField = ILE->getInitializedFieldInUnion(); - if (!InitField) + if (!InitField || ILE->getNumInits() == 0) return llvm::Error::success(); - assert(!ILE->inits().empty()); return Matcher.matchesInitializerList(InitField, ILE->getInit(0)); } // Handle struct/class: @@ -299,8 +299,11 @@ PointerFlowMatcher::matchesInitializerList(const ValueDecl *Base, if (Type->isArrayType()) return matchInitializerListForArray(*this, Base, ILE, ArrayElementIndirectLevel); - // Must be the case of using a initializer-list for a scalar: - return matchesInitializerList(Base, ILE->getInit(0)); + + // Must be the case of using an initializer-list for a scalar: + if (ILE->getNumInits() > 0) + return matchesInitializerList(Base, ILE->getInit(0)); + return llvm::Error::success(); } class PointerFlowTUSummaryExtractor : public TUSummaryExtractor { diff --git a/clang/unittests/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowTest.cpp b/clang/unittests/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowTest.cpp index 9f7a508ea35fd..bff9dc10bfc1f 100644 --- a/clang/unittests/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowTest.cpp +++ b/clang/unittests/ScalableStaticAnalysisFramework/Analyses/PointerFlow/PointerFlowTest.cpp @@ -1126,4 +1126,71 @@ TEST_F(PointerFlowTest, NestedLambdaAssign) { ASSERT_NE(Sum, nullptr); EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"y", 1U}, {"x", 1U}}})); } + +TEST_F(PointerFlowTest, ImplicitValueInit) { + ASSERT_EQ(setUpTest(R"cpp( + struct S { int *a; int *b; }; + void foo(int *p) { + S s = {p}; // ImplicitValueInit inits 'b' + } + )cpp"), + true); + + auto *Sum = getEntitySummary("foo"); + + ASSERT_NE(Sum, nullptr); + EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"a", 1U}, {"p", 1U}}})); +} + +TEST_F(PointerFlowTest, InitListExpr) { + ASSERT_EQ(setUpTest(R"cpp( + void foo(int *p) { + int *q = {p}; + int *r = {}; + } + )cpp"), + true); + + auto *Sum = getEntitySummary("foo"); + + ASSERT_NE(Sum, nullptr); + EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"q", 1U}, {"p", 1U}}})); +} + +TEST_F(PointerFlowTest, CXXDefaultInitExpr) { + ASSERT_EQ(setUpTest(R"cpp( + int *g; + struct S { + int *field = g; + }; + void foo(int *p) { + S s; + s.field = p; + } + )cpp"), + true); + + auto *Sum = getEntitySummary<RecordDecl>("S"); + + ASSERT_NE(Sum, nullptr); + EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"field", 1U}, {"g", 1U}}})); +} + +TEST_F(PointerFlowTest, CXXConstructExprArrayInit) { + ASSERT_EQ(setUpTest( + R"cpp( + struct S {}; + void foo(S *q) { + S arr[3]; // -VarDecl <...> arr 'S[3]' callinit + // `-CXXConstructExpr <...> 'S[3]' 'void () noexcept' + q = arr; + } + )cpp"), + true); + + auto *Sum = getEntitySummary("foo"); + + ASSERT_NE(Sum, nullptr); + EXPECT_EQ(*Sum, makeEdges(__LINE__, {{{"q", 1U}, {"arr", 1U}}})); +} } // namespace diff --git a/clang/unittests/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageTest.cpp b/clang/unittests/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageTest.cpp index 6b75ed3f1a1fe..1ef2c5e7337bf 100644 --- a/clang/unittests/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageTest.cpp +++ b/clang/unittests/ScalableStaticAnalysisFramework/Analyses/UnsafeBufferUsage/UnsafeBufferUsageTest.cpp @@ -570,4 +570,118 @@ TEST_F(UnsafeBufferUsageTest, NestedDefinitions2) { EXPECT_EQ(Sum, nullptr); } +TEST_F(UnsafeBufferUsageTest, UnaryPlusSubscript) { + ASSERT_TRUE(setUpTest(R"cpp( + void foo(int *p) { + (+p)[5]; + } + )cpp")); + const auto *Sum = getEntitySummary("foo"); + + EXPECT_NE(Sum, nullptr); + EXPECT_EQ(*Sum, makeSet(__LINE__, {{"p", 1U}})); +} + +TEST_F(UnsafeBufferUsageTest, PredefinedExprSubscript) { + ASSERT_TRUE(setUpTest(R"cpp( + void foo() { + __func__[100]; + } + )cpp")); + const auto *Sum = getEntitySummary("foo"); + + EXPECT_EQ(Sum, nullptr); +} + +TEST_F(UnsafeBufferUsageTest, IntegerLiteralCastSubscript) { + ASSERT_TRUE(setUpTest(R"cpp( + void foo() { + ((int *)0)[5]; + } + )cpp")); + const auto *Sum = getEntitySummary("foo"); + + EXPECT_EQ(Sum, nullptr); +} + +TEST_F(UnsafeBufferUsageTest, CXXNewArraySubscript) { + ASSERT_TRUE(setUpTest(R"cpp( + void foo() { + (new int[10])[5]; + } + )cpp")); + const auto *Sum = getEntitySummary("foo"); + + EXPECT_EQ(Sum, nullptr); +} + +TEST_F(UnsafeBufferUsageTest, CXXNullPtrSubscript) { + ASSERT_TRUE(setUpTest(R"cpp( + void foo() { + ((int*)nullptr)[5]; + } + )cpp")); + const auto *Sum = getEntitySummary("foo"); + + EXPECT_EQ(Sum, nullptr); +} + +TEST_F(UnsafeBufferUsageTest, CXXThisSubscript) { + ASSERT_TRUE(setUpTest(R"cpp( + struct S { + void foo() { + this[5]; + } + }; + )cpp")); + const auto *Sum = getEntitySummary("foo"); + + EXPECT_EQ(Sum, nullptr); +} + +TEST_F(UnsafeBufferUsageTest, ExprWithCleanupsSubscript) { + ASSERT_TRUE(setUpTest(R"cpp( + struct Guard { ~Guard(); }; + int *getPtr(Guard); + void foo() { + getPtr(Guard{})[5]; + } + )cpp")); + const auto *Sum = getEntitySummary("foo"); + + EXPECT_NE(Sum, nullptr); + EXPECT_EQ(*Sum, makeSet(__LINE__, {{"getPtr", 1U, true}})); +} + +TEST_F(UnsafeBufferUsageTest, MaterializeTemporaryExpr) { + ASSERT_EQ(setUpTest(R"cpp( + struct S { int *get(); }; + void foo(int *p) { + S{}.get()[5]; + } + )cpp"), + true); + + auto *Sum = getEntitySummary("foo"); + + ASSERT_NE(Sum, nullptr); + EXPECT_EQ(*Sum, makeSet(__LINE__, {{"get", 1U, true}})); +} + +TEST_F(UnsafeBufferUsageTest, CXXScalarValueInitExpr) { + ASSERT_EQ(setUpTest(R"cpp( + using IntPtr = int*; + + void foo(int *q) { + int p = IntPtr()[5]; // no EPL created for CXXScalarValueInitExpr + q[5]; + } + )cpp"), + true); + + auto *Sum = getEntitySummary("foo"); + + ASSERT_NE(Sum, nullptr); + EXPECT_EQ(*Sum, makeSet(__LINE__, {{"q", 1U}})); +} } // namespace >From 5549c1bee7e8ae121c07281eebe216a0cb5de7d2 Mon Sep 17 00:00:00 2001 From: Ziqing Luo <[email protected]> Date: Wed, 13 May 2026 18:03:27 -0700 Subject: [PATCH 2/2] address comments --- .../Analyses/EntityPointerLevel/EntityPointerLevel.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/clang/lib/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.cpp b/clang/lib/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.cpp index d1aa41bd03d47..758009be1f49b 100644 --- a/clang/lib/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.cpp +++ b/clang/lib/ScalableStaticAnalysisFramework/Analyses/EntityPointerLevel/EntityPointerLevel.cpp @@ -306,15 +306,16 @@ class EntityPointerLevelTranslator return EntityPointerLevelSet{}; } - // Fall back if the InitListExpr is not an empty or singleton list that - // initializes a pointer scalar. EntityPointerLevelTranslator does not - // expect to visit an InitListExpr in any other case. + // The InitListExpr must be an empty or singleton list that + // initializes a pointer scalar. Other cases are unexpected thus an error. Expected<EntityPointerLevelSet> VisitInitListExpr(const InitListExpr *E) { if (E->getNumInits() < 1) return EntityPointerLevelSet{}; if (E->getType()->isPointerType()) return Visit(E->getInit(0)); - return fallback(E); + return llvm::createStringError( + "Cannot translate an InitListExpr to EntityPointerLevels if it is not " + "an empty or singleton list that initializes a pointer scalar"); } // Clang may default initializes an array with a CXXConstructExpr. Fallback on _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
