[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