[llvm-branch-commits] [clang] [SSAF][SourceEdit] The last patch of expression rewrite rules for the initial version (PR #223553)

via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Mon Sep 14 16:02:36 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-clang-ssaf

Author: Ziqing Luo (ziqingluo-90)

<details>
<summary>Changes</summary>

Add a list of rewrite rules:
- '&var'/'&member' is rewritten to 'addr_of(var)', if it needs to have bounded type;
- 'this' is rewritten to 'make_single(this)', if it needs to have bounded type;
- A non-empty list-initializer flowing into a bounded_array LHS gets an extra '{...}' layer;
- A union with a transformed variant member gets a '= {}' default initializer added to one member, if necessary;
- 'delete e' gets '.data()' appended to 'e' when its base is transformed.

Final step of
rdar://187125348

---

Patch is 20.23 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/223553.diff


2 Files Affected:

- (modified) clang/lib/ScalableStaticAnalysis/SourceTransformation/Transformations/CppBoundedBuffers.cpp (+199-15) 
- (modified) clang/unittests/ScalableStaticAnalysis/SourceTransformation/CppBoundedBuffersTest.cpp (+202-3) 


``````````diff
diff --git a/clang/lib/ScalableStaticAnalysis/SourceTransformation/Transformations/CppBoundedBuffers.cpp b/clang/lib/ScalableStaticAnalysis/SourceTransformation/Transformations/CppBoundedBuffers.cpp
index e4f9c511ad070..236c6bb3dcbf8 100644
--- a/clang/lib/ScalableStaticAnalysis/SourceTransformation/Transformations/CppBoundedBuffers.cpp
+++ b/clang/lib/ScalableStaticAnalysis/SourceTransformation/Transformations/CppBoundedBuffers.cpp
@@ -9,6 +9,7 @@
 #include "clang/ScalableStaticAnalysis/SourceTransformation/Transformations/CppBoundedBuffers.h"
 #include "../../Analyses/SSAFAnalysesCommon.h"
 #include "clang/AST/ASTContext.h"
+#include "clang/AST/ASTTypeTraits.h"
 #include "clang/AST/Decl.h"
 #include "clang/AST/DeclBase.h"
 #include "clang/AST/DeclCXX.h"
@@ -35,12 +36,14 @@
 #include "clang/Tooling/Refactoring/AtomicChange.h"
 #include "llvm/ADT/DenseMap.h"
 #include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallPtrSet.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/Twine.h"
 #include "llvm/Support/Casting.h"
 #include "llvm/Support/Error.h"
 #include "llvm/Support/ErrorHandling.h"
 #include <cassert>
+#include <functional>
 #include <map>
 #include <optional>
 #include <string>
@@ -593,6 +596,13 @@ class ExpressionRewriter {
   bool appendAsBoundedCallToArg(const PointerFlowPair &Pair,
                                 tooling::AtomicChange &AC) const;
 
+  /// Provided that \c Pair LHS is transformed, wrap RHS in `{...}`, if it is a
+  /// list-initializer of array type and LHS is transformed to bounded_array.
+  /// \return true iff RHS needs edit and \c addEditToAtomicChange is called on
+  /// AC.
+  bool wrapBoundedArrayInitWithBraces(const PointerFlowPair &Pair,
+                                      tooling::AtomicChange &AC) const;
+
   /// If \c E has the form `new T[n]` and it needs to have a bounded type, edit
   /// it to `bounded_ptr<T>::_new(n)`
   /// \return true iff E needs edit and \c addEditToAtomicChange is called on
@@ -607,6 +617,19 @@ class ExpressionRewriter {
   bool replaceMallocCallWithBounded(const Expr *E,
                                     tooling::AtomicChange &AC) const;
 
+  /// If \c E has the form '&var', where 'var' is a DRE or MemberExpr, edit it
+  /// to 'addr_of(var)'.
+  /// \return true iff E needs edit and \c addEditToAtomicChange is called on
+  /// AC.
+  bool wrapAddrOfVariableOrMember(const Expr *E,
+                                  tooling::AtomicChange &AC) const;
+
+  /// If \c E is a \c CXXThisExpr and it needs to have a bounded type, edit it
+  /// to 'make_single(this)'.
+  /// \return true iff E needs edit and \c addEditToAtomicChange is called on
+  /// AC.
+  bool wrapThisWithMakeSingle(const Expr *E, 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
@@ -621,6 +644,25 @@ class ExpressionRewriter {
   /// AC.
   bool rewritePointerCast(const Expr *E, tooling::AtomicChange &AC) const;
 
+  /// For each union decl who has members in \c TransformedDecls, if it has
+  /// neither a default initializer for any of its member nor an user-provided
+  /// constructor, add a default initializer `{}` to one of its transformed
+  /// members.
+  ///
+  /// Why this kind of edits are needed:
+  /// A union with a non-trivially-default-constructible member has its
+  /// implicitly-defined default constructor deleted — unless exactly one
+  /// variant member carries a default member initializer (= {}).
+  ///
+  /// Minimal edit conflict:
+  /// A field decl with an initializer will not be edited by this function.
+  /// The initializer shall have been edited, if needed, by \c adaptPointerFlow.
+  void addDefaultInitForTransformedUnionField() const;
+
+  /// Provided \c E has the form 'delete e', if 'e' \c isExprBaseTransformed,
+  /// append '.data()' to it.
+  void appendDataCallToDeleteStmt(const CXXDeleteExpr *E) const;
+
   ASTContext &Ctx;
   const SSAFOptions &Opts;
   SourceEditEmitter &Edits;
@@ -800,7 +842,10 @@ ExpressionRewriter::adaptPointerFlow(const PointerFlowPair &Pair) const {
   else if (IsLHSTransformed) {
     // Handle RHSes that are non-entity based and need to have bounded types:
     IsRHSTransformed = replaceNewExprWithBounded(Pair.RHS, AC) ||
-                       replaceMallocCallWithBounded(Pair.RHS, AC);
+                       replaceMallocCallWithBounded(Pair.RHS, AC) ||
+                       wrapAddrOfVariableOrMember(Pair.RHS, AC) ||
+                       wrapThisWithMakeSingle(Pair.RHS, AC) ||
+                       wrapBoundedArrayInitWithBraces(Pair, AC);
   }
   if (!IsLHSTransformed && IsRHSTransformed)
     appendDataCallToArg(Pair, AC);
@@ -878,6 +923,29 @@ bool ExpressionRewriter::appendAsBoundedCallToArg(
   return true;
 }
 
+bool ExpressionRewriter::wrapBoundedArrayInitWithBraces(
+    const PointerFlowPair &Pair, tooling::AtomicChange &AC) const {
+  auto LHSClassifyResults = Pair.visitLHS(GetLHSClassifyResults{*this});
+
+  if (LHSClassifyResults.empty() ||
+      LHSClassifyResults.front()->NewType != BoundedType::Array)
+    return false;
+
+  const auto *ILE = dyn_cast<InitListExpr>(Pair.RHS->IgnoreParenImpCasts());
+
+  // An empty list ('{}') binds directly to bounded_array's default
+  // constructor and needs no extra braces.
+  if (!ILE || ILE->getNumInits() == 0)
+    return false;
+
+  CharSourceRange ILERange = Lexer::getAsCharRange(
+      ILE->getSourceRange(), Ctx.getSourceManager(), Ctx.getLangOpts());
+
+  addEditToAtomicChange(ILERange, "{", EditKind::InsertAtBegin, AC);
+  addEditToAtomicChange(ILERange, "}", EditKind::InsertAtEnd, AC);
+  return true;
+}
+
 bool ExpressionRewriter::rewriteAddrofElementAccess(
     const Expr *E, tooling::AtomicChange &AC) const {
   const auto *UO = dyn_cast<UnaryOperator>(E->IgnoreParenImpCasts());
@@ -1048,6 +1116,111 @@ bool ExpressionRewriter::replaceMallocCallWithBounded(
   return addEditToAtomicChange(CR, "_", EK, AC);
 }
 
+bool ExpressionRewriter::wrapAddrOfVariableOrMember(
+    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();
+
+  // FIXME: is there a unified approach for all AddrOf expressions?
+  if (!isa<DeclRefExpr, MemberExpr>(SubExpr))
+    return false;
+
+  const SourceManager &SM = Ctx.getSourceManager();
+  const LangOptions &LO = Ctx.getLangOpts();
+  CharSourceRange FullExprCR =
+      Lexer::getAsCharRange(UO->getSourceRange(), SM, LO);
+  CharSourceRange UOSubExprCR =
+      Lexer::getAsCharRange(UO->getSubExpr()->getSourceRange(), SM, LO);
+  CharSourceRange AmpCR = CharSourceRange::getCharRange(FullExprCR.getBegin(),
+                                                        UOSubExprCR.getBegin());
+
+  // For '&var',
+  // - replace '&' with 'addr_of(', and
+  // - append ')' to 'var'
+  addEditToAtomicChange(AmpCR, "addr_of(", EditKind::Replace, AC);
+  addEditToAtomicChange(UOSubExprCR, ")", EditKind::InsertAtEnd, AC);
+  return true;
+}
+
+bool ExpressionRewriter::wrapThisWithMakeSingle(
+    const Expr *E, tooling::AtomicChange &AC) const {
+  const auto *CTE = dyn_cast<CXXThisExpr>(E->IgnoreParenImpCasts());
+  if (!CTE)
+    return false;
+
+  CharSourceRange CR = Lexer::getAsCharRange(
+      CTE->getSourceRange(), Ctx.getSourceManager(), Ctx.getLangOpts());
+
+  addEditToAtomicChange(CR, "make_single(", EditKind::InsertAtBegin, AC);
+  addEditToAtomicChange(CR, ")", EditKind::InsertAtEnd, AC);
+  return true;
+}
+
+void ExpressionRewriter::addDefaultInitForTransformedUnionField() const {
+  llvm::SmallPtrSet<const RecordDecl *, 8> Seen;
+
+  for (const auto &Entry : TransformedDecls) {
+    const auto *FD = dyn_cast<FieldDecl>(Entry.first);
+    if (!FD)
+      continue;
+
+    const auto *RD = dyn_cast<CXXRecordDecl>(FD->getParent());
+    if (!RD || !RD->isUnion() || !Seen.insert(RD).second)
+      continue;
+
+    // Either already prevents the union's implicitly-defined default
+    // constructor from being deleted, so no edit is needed.
+    if (RD->hasInClassInitializer() || RD->hasUserDeclaredConstructor())
+      continue;
+
+    // Add '= {}' to the first transformed member.
+    auto FieldToEdit = llvm::find_if(RD->fields(), [this](const FieldDecl *FD) {
+      return TransformedDecls.count(FD) > 0;
+    });
+
+    const SourceManager &SM = Ctx.getSourceManager();
+    CharSourceRange FieldCR = Lexer::getAsCharRange(
+        FieldToEdit->getSourceRange(), SM, Ctx.getLangOpts());
+    tooling::AtomicChange AC("", "");
+
+    addEditToAtomicChange(FieldCR, " = {}", EditKind::InsertAtEnd, AC);
+
+    if (llvm::all_of(AC.getReplacements(),
+                     std::mem_fn(&tooling::Replacement::isApplicable)))
+      for (const tooling::Replacement &R : AC.getReplacements())
+        Edits.addReplacement(R);
+  }
+}
+
+void ExpressionRewriter::appendDataCallToDeleteStmt(
+    const CXXDeleteExpr *E) const {
+  const Expr *Operand = E->getArgument();
+
+  if (!isExprBaseTransformed(Operand))
+    return;
+
+  tooling::AtomicChange AC("", "");
+
+  rewriteExpression(Operand, AC);
+  // Operand may be edited, its source range should stay intact.
+
+  const SourceManager &SM = Ctx.getSourceManager();
+  CharSourceRange OperandCR =
+      Lexer::getAsCharRange(Operand->getSourceRange(), SM, Ctx.getLangOpts());
+
+  addEditToAtomicChange(OperandCR, "(", EditKind::InsertAtBegin, AC);
+  addEditToAtomicChange(OperandCR, ").data()", EditKind::InsertAtEnd, AC);
+
+  if (llvm::all_of(AC.getReplacements(),
+                   std::mem_fn(&tooling::Replacement::isApplicable))) {
+    for (auto R : AC.getReplacements())
+      Edits.addReplacement(R);
+  }
+}
+
 void ExpressionRewriter::rewriteExprInTU(const TranslationUnitDecl *TU) {
   llvm::DenseMap<const NamedDecl *, std::vector<const NamedDecl *>>
       ContributorGroups;
@@ -1055,25 +1228,36 @@ void ExpressionRewriter::rewriteExprInTU(const TranslationUnitDecl *TU) {
   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) {
+      auto PairsRewriter = [this, &ContriDecl](const DynTypedNode &Node) {
+        llvm::SmallVector<PointerFlowPair, 4> Pairs;
+        PointerFlowPairMatcher Matcher{Ctx};
+
         Matcher.matches(Node, ContriDecl, Pairs);
+        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);
+        }
       };
-      findMatchesIn(ContriDecl, PairsCollector);
+
+      findMatchesIn(ContriDecl, PairsRewriter);
+      findMatchesIn(ContriDecl, [this](const DynTypedNode &Node) {
+        const auto *DeleteStmt = Node.get<CXXDeleteExpr>();
+
+        if (DeleteStmt)
+          appendDataCallToDeleteStmt(DeleteStmt);
+      });
     }
 
-  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);
-  }
+  // A union with a non-trivially-default-constructible member has its
+  // implicitly-defined default constructor deleted — unless exactly one variant
+  // member carries a default member initializer (= {}).
+  addDefaultInitForTransformedUnionField();
 }
 
 bool ExpressionRewriter::isExprBaseTransformed(const Expr *E) const {
@@ -1137,7 +1321,7 @@ bool ExpressionRewriter::addEditToAtomicChange(
       return AC.replace(SM, FileRange, NewText);
     case InsertAtBegin:
       return AC.insert(SM, FileRange.getBegin(), NewText,
-                        /*InsertAfter=*/false);
+                       /*InsertAfter=*/false);
     case InsertAtEnd:
       return AC.insert(SM, FileRange.getEnd(), NewText, /*InsertAfter=*/true);
     }
diff --git a/clang/unittests/ScalableStaticAnalysis/SourceTransformation/CppBoundedBuffersTest.cpp b/clang/unittests/ScalableStaticAnalysis/SourceTransformation/CppBoundedBuffersTest.cpp
index 74487af55481f..2f0c1e37244d3 100644
--- a/clang/unittests/ScalableStaticAnalysis/SourceTransformation/CppBoundedBuffersTest.cpp
+++ b/clang/unittests/ScalableStaticAnalysis/SourceTransformation/CppBoundedBuffersTest.cpp
@@ -964,12 +964,12 @@ TEST_F(CppBoundedBuffersTest, RHSNewArrayMacroSizeExprRewritten) {
   // rewrite would land only the closing ')' without ever inserting the
   // 'bounded_ptr<int>::_new(' prefix.
   StringRef Code = "#define HALF 9\n"
-                    "void g() { int *p = new int[HALF + 1]; }\n";
+                   "void g() { int *p = new int[HALF + 1]; }\n";
   Captured C =
       run(Code, [](ASTContext &Ctx) { return varEntity("p", Ctx); }, {1});
   EXPECT_EQ(C.Rewritten, "#define HALF 9\n"
-                          "void g() { bounded_ptr<int> p = "
-                          "bounded_ptr<int>::_new(HALF + 1); }\n");
+                         "void g() { bounded_ptr<int> p = "
+                         "bounded_ptr<int>::_new(HALF + 1); }\n");
   EXPECT_TRUE(C.Reports.empty());
 }
 
@@ -1081,4 +1081,203 @@ TEST_F(CppBoundedBuffersTest, RHSQualifiedMallocCallRewritten) {
   EXPECT_TRUE(C.Reports.empty());
 }
 
+TEST_F(CppBoundedBuffersTest, RHSAddrOfPlainVarRewritten) {
+  // '&a' has no decl-linked entity of its own (unlike '&arr[i]'/'&*p', which
+  // resolve back to arr/p's own entity), so it is rewritten based on the LHS
+  // ('p') being transformed rather than the RHS.
+  StringRef Code = R"cpp(
+    void g(int *p) { int a; p = &a; }
+  )cpp";
+  Captured C =
+      run(Code, [](ASTContext &Ctx) { return paramEntity("g", 0, Ctx); }, {1});
+  EXPECT_EQ(C.Rewritten, R"cpp(
+    void g(bounded_ptr<int> p) { int a; p = addr_of(a); }
+  )cpp");
+  EXPECT_TRUE(C.Reports.empty());
+}
+
+TEST_F(CppBoundedBuffersTest, RHSAddrOfMemberExprRewritten) {
+  // '&s.a' has no decl-linked entity of its own (the field 'S::a' is never
+  // itself transformed here; only the destination 'p' is), so it is
+  // rewritten based on the LHS being transformed, same as the plain-DRE
+  // case above.
+  StringRef Code = R"cpp(
+    struct S { int a; };
+    void g(int *p) { S s; p = &s.a; }
+  )cpp";
+  Captured C =
+      run(Code, [](ASTContext &Ctx) { return paramEntity("g", 0, Ctx); }, {1});
+  EXPECT_EQ(C.Rewritten, R"cpp(
+    struct S { int a; };
+    void g(bounded_ptr<int> p) { S s; p = addr_of(s.a); }
+  )cpp");
+  EXPECT_TRUE(C.Reports.empty());
+}
+
+TEST_F(CppBoundedBuffersTest, RHSThisRewrittenToMakeSingle) {
+  // 'this' has no decl-linked entity of its own, so it is rewritten based on
+  // the LHS ('m') being transformed rather than the RHS.
+  StringRef Code = R"cpp(
+    struct S {
+      S *m;
+      void f() { m = this; }
+    };
+  )cpp";
+  Captured C =
+      run(Code, [](ASTContext &Ctx) { return fieldEntity("m", Ctx); }, {1});
+  EXPECT_EQ(C.Rewritten, R"cpp(
+    struct S {
+      bounded_ptr<S> m;
+      void f() { m = make_single(this); }
+    };
+  )cpp");
+  EXPECT_TRUE(C.Reports.empty());
+}
+
+TEST_F(CppBoundedBuffersTest, RHSArrayInitEmptyBraceNotWrapped) {
+  // '{}' binds directly to bounded_array's default constructor, so no extra
+  // braces are inserted.
+  StringRef Code = R"cpp(
+    void g() { int a[2] = {}; }
+  )cpp";
+  Captured C =
+      run(Code, [](ASTContext &Ctx) { return varEntity("a", Ctx); }, {1});
+  EXPECT_EQ(C.Rewritten, R"cpp(
+    void g() { bounded_array<int, 2> a = {}; }
+  )cpp");
+  EXPECT_TRUE(C.Reports.empty());
+}
+
+TEST_F(CppBoundedBuffersTest, RHSArrayInitNonEmptyBraceWrapped) {
+  // bounded_array is not an aggregate, so a non-empty list-init needs an
+  // extra level of braces to bind to its single std::array<T,N>&& ctor
+  // parameter.
+  StringRef Code = R"cpp(
+    void g() { int a[2] = {1, 2}; }
+  )cpp";
+  Captured C =
+      run(Code, [](ASTContext &Ctx) { return varEntity("a", Ctx); }, {1});
+  EXPECT_EQ(C.Rewritten, R"cpp(
+    void g() { bounded_array<int, 2> a = {{1, 2}}; }
+  )cpp");
+  EXPECT_TRUE(C.Reports.empty());
+}
+
+TEST_F(CppBoundedBuffersTest, RHSArrayFieldInitBraceElided) {
+  // 'a's sub-initializer has no explicit '{...}' of its own (brace elision),
+  // so Sema creates an implicit InitListExpr with no source braces. Since
+  // 'a' becomes a bounded_array, its sub-initializer still needs its own
+  // explicit braces inserted, same as the non-empty top-level case.
+  StringRef Code = R"cpp(
+    struct S { int a[3]; int x; };
+    S s = {1, 2, 3, 4};
+  )cpp";
+  Captured C =
+      run(Code, [](ASTContext &Ctx) { return fieldEntity("a", Ctx); }, {1});
+  EXPECT_EQ(C.Rewritten, R"cpp(
+    struct S { bounded_array<int, 3> a; int x; };
+    S s = {{1, 2, 3}, 4};
+  )cpp");
+  EXPECT_TRUE(C.Reports.empty());
+}
+
+TEST_F(CppBoundedBuffersTest, UnionFieldGetsDefaultInit) {
+  StringRef Code = R"cpp(
+    union U { int *p; int x; };
+  )cpp";
+  Captured C =
+      run(Code, [](ASTContext &Ctx) { return fieldEntity("p", Ctx); }, {1});
+  EXPECT_EQ(C.Rewritten, R"cpp(
+    union U { bounded_ptr<int> p = {}; int x; };
+  )cpp");
+  EXPECT_TRUE(C.Reports.empty());
+}
+
+TEST_F(CppBoundedBuffersTest,
+       UnionFieldNoDefaultInitWhenSiblingHasInitializer) {
+  StringRef Code = R"cpp(
+    union U { int *p; int x = 0; };
+  )cpp";
+  Captured C =
+      run(Code, [](ASTContext &Ctx) { return fieldEntity("p", Ctx); }, {1});
+  EXPECT_EQ(C.Rewritten, R"cpp(
+    union U { bounded_ptr<int> p; int x = 0; };
+  )cpp");
+  EXPECT_TRUE(C.Reports.empty());
+}
+
+TEST_F(CppBoundedBuffersTest,
+       UnionFieldNoDefaultInitWhenUserDeclaredCtorExists) {
+  StringRef Code = R"cpp(
+    union U { int *p; int x; U() : x(0) {} };
+  )cpp";
+  Captured C =
+      run(Code, [](ASTContext &Ctx) { return fieldEntity("p", Ctx); }, {1});
+  EXPECT_EQ(C.Rewritten, R"cpp(
+    union U { bounded_ptr<int> p; int x; U() : x(0) {} };
+  )cpp");
+  EXPECT_TRUE(C.Reports.empty());
+}
+
+TEST_F(CppBoundedBuffersTest, DeleteAddDataCall) {
+  // 'p' is transformed, so 'delete p;' needs the raw pointer via '.data()'.
+  StringRef Code = R"cpp(
+    void f() { int *p; delete p; }
+  )cpp";
+  Captured C =
+      run(Code, [](ASTContext &Ctx) { return varEntity("p", Ctx); }, {1});
+  EXPECT_EQ(C.Rewritten, R"cpp(
+    void f() { bounded_ptr<int> p; delete (p).data(); }
+  )cpp");
+  EXPECT_TRUE(C.Reports.empty());
+}
+
+TEST_F(CppBoundedBuffersTest, DeleteArrayAddDataCall) {
+  // Same as DeleteAddDataCall, but for the array form 'delete[] p;'.
+  StringRef Code = R"cpp(
+    void f() { int *p; delete[] p; }
+  )cpp";
+  Captured C =
+      run(Code, [](ASTContext &Ctx) { return varEntity("p", Ctx); }, {1});
+  EXPECT_EQ(C.Rewritten, R"cpp(
+    void f() { bounded_ptr<int> p; delete[] (p).data(); }
+  )cpp");
+  EXPECT_TRUE(C.Reports.empty());
+}
+
+TEST_F(CppBoundedBuffersTest, DeleteNoChangeWhenNotTransformed) {
+  // 'p' is not transformed, so the delete-expression is left untouched.
+  StringRef Code = R"cpp(
+    void f() { int *p; delete p; }
+  )cpp";
+  Captured C =
+      run(Code, [](ASTContext &Ctx) { return varEntity("p", Ctx); }, {});
+  EXPECT_EQ(C.Rewritten, Code);
+  EXPECT_TRUE(C.Reports.empty());
+}
+
+TEST_F(CppBoundedBuffersTest, DeleteAddrOfDerefAddDataCall) {
+  StringRef Code = R"cpp(
+    void f() { int *p; delete &*p; }
+  )cpp";
+  Captured C =
+      run(Code, [](ASTContext &Ctx) { return varEntity("p", Ctx); }, {1});
+  EXPECT_EQ(C.Rewritten, R"cpp(
+    void f() { bounded_ptr<int> p; delete (p).data(); }
+  )cpp");
+  EXPECT_TRUE(C.Reports.empty());
+}
+
+TEST_F(CppBoundedBuffersTest, DeleteAddrOfSubscriptAddDataCall) {
+  StringRef Code = R"cpp(
+    void f(int i) { int *p; delete &p[0]; }
+  )cpp";
+  Captured C =
+      run(C...
[truncated]

``````````

</details>


https://github.com/llvm/llvm-project/pull/223553


More information about the llvm-branch-commits mailing list