[llvm-branch-commits] [clang] [SSAF][SourceTransformation] Add transformation for new[] and malloc (PR #223274)
Ziqing Luo via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Sun Sep 13 14:09:52 PDT 2026
https://github.com/ziqingluo-90 created https://github.com/llvm/llvm-project/pull/223274
For a PointerFlowPair, if RHS is an array-new or malloc call and LHS has bounded type, transform RHS to the bounded-producing alternative to new[]/malloc.
4th step of
rdar://187125348
>From 4ef24aceb646d8900261d7ada6bf5b8a628d621a Mon Sep 17 00:00:00 2001
From: Ziqing Luo <ziqing_luo at apple.com>
Date: Sun, 13 Sep 2026 12:02:14 -0700
Subject: [PATCH] [SSAF][SourceTransformation] Add transformation for new[] and
malloc
For a PointerFlowPair, if RHS is an array-new or malloc call and LHS
has bounded type, transform RHS to the bounded-producing alternative
to new[]/malloc.
4th step of
rdar://187125348
---
.../Transformations/CppBoundedBuffers.cpp | 110 ++++++++++++-
.../CppBoundedBuffersTest.cpp | 149 ++++++++++++++++++
2 files changed, 254 insertions(+), 5 deletions(-)
diff --git a/clang/lib/ScalableStaticAnalysis/SourceTransformation/Transformations/CppBoundedBuffers.cpp b/clang/lib/ScalableStaticAnalysis/SourceTransformation/Transformations/CppBoundedBuffers.cpp
index 5d600c6ec74157..e4f9c511ad070c 100644
--- a/clang/lib/ScalableStaticAnalysis/SourceTransformation/Transformations/CppBoundedBuffers.cpp
+++ b/clang/lib/ScalableStaticAnalysis/SourceTransformation/Transformations/CppBoundedBuffers.cpp
@@ -17,6 +17,7 @@
#include "clang/AST/ExprCXX.h"
#include "clang/AST/Type.h"
#include "clang/AST/TypeLoc.h"
+#include "clang/Basic/Builtins.h"
#include "clang/Basic/LangOptions.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Basic/SourceManager.h"
@@ -36,6 +37,7 @@
#include "llvm/ADT/STLExtras.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>
@@ -591,6 +593,20 @@ class ExpressionRewriter {
bool appendAsBoundedCallToArg(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
+ /// AC.
+ bool replaceNewExprWithBounded(const Expr *E,
+ tooling::AtomicChange &AC) const;
+
+ /// If \c E is a call expression of the form `malloc(n)` and it needs to have
+ /// a bounded type, edit it to `_malloc(n)`
+ /// \return true iff E needs edit and \c addEditToAtomicChange is called on
+ /// AC.
+ bool replaceMallocCallWithBounded(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
@@ -781,6 +797,11 @@ ExpressionRewriter::adaptPointerFlow(const PointerFlowPair &Pair) const {
if (IsRHSTransformed)
rewriteExpression(Pair.RHS, AC);
+ 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);
+ }
if (!IsLHSTransformed && IsRHSTransformed)
appendDataCallToArg(Pair, AC);
if (IsLHSTransformed && IsRHSTransformed)
@@ -955,6 +976,78 @@ bool ExpressionRewriter::rewritePointerCast(const Expr *E,
return true;
}
+bool ExpressionRewriter::replaceNewExprWithBounded(
+ const Expr *E, tooling::AtomicChange &AC) const {
+ const auto *NE = dyn_cast<CXXNewExpr>(E->IgnoreParenImpCasts());
+
+ if (!NE || !NE->isArray() || NE->hasInitializer() ||
+ NE->getNumPlacementArgs() != 0)
+ return false;
+
+ std::optional<const Expr *> ArraySize = NE->getArraySize();
+
+ if (!ArraySize)
+ return false;
+
+ const SourceManager &SM = Ctx.getSourceManager();
+ const LangOptions &LO = Ctx.getLangOpts();
+ CharSourceRange SizeCR =
+ Lexer::getAsCharRange((*ArraySize)->getSourceRange(), SM, LO);
+ CharSourceRange FullNewExprCR =
+ Lexer::getAsCharRange(NE->getSourceRange(), SM, LO);
+ CharSourceRange PreSizeCR = CharSourceRange::getCharRange(
+ FullNewExprCR.getBegin(), SizeCR.getBegin());
+ CharSourceRange PostSizeCR =
+ CharSourceRange::getCharRange(SizeCR.getEnd(), FullNewExprCR.getEnd());
+ std::string T = spell(NE->getAllocatedType(), Ctx);
+
+ // For `new T[n]`,
+ // 1. replace contents in PreSizeCR with "bounded_ptr<T>::_new(", and
+ // 2. replace contents in PostSizeCR with ")",
+ // results in 'bounded_ptr<T>::_new(n)'.
+ addEditToAtomicChange(PreSizeCR, "bounded_ptr<" + T + ">::_new(",
+ EditKind::Replace, AC);
+ addEditToAtomicChange(PostSizeCR, ")", EditKind::Replace, AC);
+ return true;
+}
+
+bool ExpressionRewriter::replaceMallocCallWithBounded(
+ const Expr *E, tooling::AtomicChange &AC) const {
+ // strip cast:
+ if (const auto *CastE = dyn_cast<CastExpr>(E->IgnoreParenImpCasts()))
+ E = CastE->getSubExpr();
+
+ const auto *CallE = dyn_cast<CallExpr>(E->IgnoreParenImpCasts());
+
+ if (!CallE)
+ return false;
+
+ const auto *CalleeDRE =
+ dyn_cast<DeclRefExpr>(CallE->getCallee()->IgnoreParenImpCasts());
+
+ if (!CalleeDRE)
+ return false;
+
+ const FunctionDecl *Callee = dyn_cast<FunctionDecl>(CalleeDRE->getDecl());
+
+ if (!Callee || !Callee->getIdentifier() ||
+ Callee->getBuiltinID() != Builtin::BImalloc)
+ return false;
+
+ bool HasQualifier = CalleeDRE->hasQualifier();
+ // If there are name qualifiers, the insertion is at the end loc of callee
+ // DRE's qualifier range. E.g., for `std::malloc`, we need to insert at the
+ // end of the `std::` qualifier.
+ SourceRange SR = HasQualifier
+ ? CalleeDRE->getQualifierLoc().getLocalSourceRange()
+ : CalleeDRE->getSourceRange();
+ EditKind EK = HasQualifier ? EditKind::InsertAtEnd : EditKind::InsertAtBegin;
+ CharSourceRange CR =
+ Lexer::getAsCharRange(SR, Ctx.getSourceManager(), Ctx.getLangOpts());
+
+ return addEditToAtomicChange(CR, "_", EK, AC);
+}
+
void ExpressionRewriter::rewriteExprInTU(const TranslationUnitDecl *TU) {
llvm::DenseMap<const NamedDecl *, std::vector<const NamedDecl *>>
ContributorGroups;
@@ -1027,19 +1120,26 @@ bool ExpressionRewriter::addEditToAtomicChange(
tooling::AtomicChange &AC) const {
assert(Range.isCharRange());
- if (Range.getBegin().isMacroID() || Range.getEnd().isMacroID())
+ const SourceManager &SM = Ctx.getSourceManager();
+ CharSourceRange FileRange =
+ Lexer::makeFileCharRange(Range, SM, Ctx.getLangOpts());
+
+ // An invalid FileRange means part of Range resides inside a macro
+ // expansion (or spans two different files) that couldn't be safely
+ // resolved to a real file location.
+ if (!FileRange.isValid())
// FIXME: report...
return false;
- const SourceManager &SM = Ctx.getSourceManager();
llvm::Error Err = [&]() -> llvm::Error {
switch (EditKind) {
case Replace:
- return AC.replace(SM, Range, NewText);
+ return AC.replace(SM, FileRange, NewText);
case InsertAtBegin:
- return AC.insert(SM, Range.getBegin(), NewText, /*InsertAfter=*/false);
+ return AC.insert(SM, FileRange.getBegin(), NewText,
+ /*InsertAfter=*/false);
case InsertAtEnd:
- return AC.insert(SM, Range.getEnd(), NewText, /*InsertAfter=*/true);
+ return AC.insert(SM, FileRange.getEnd(), NewText, /*InsertAfter=*/true);
}
llvm_unreachable("unhandled EditKind");
}();
diff --git a/clang/unittests/ScalableStaticAnalysis/SourceTransformation/CppBoundedBuffersTest.cpp b/clang/unittests/ScalableStaticAnalysis/SourceTransformation/CppBoundedBuffersTest.cpp
index 4d0ba31cc36edc..74487af55481f2 100644
--- a/clang/unittests/ScalableStaticAnalysis/SourceTransformation/CppBoundedBuffersTest.cpp
+++ b/clang/unittests/ScalableStaticAnalysis/SourceTransformation/CppBoundedBuffersTest.cpp
@@ -932,4 +932,153 @@ TEST_F(CppBoundedBuffersTest, RHSNoChangeRHSNotHardened) {
EXPECT_TRUE(C.Reports.empty());
}
+TEST_F(CppBoundedBuffersTest, RHSNewArrayRewritten) {
+ StringRef Code = R"cpp(
+ void g(int n) { int *p = new int[n]; }
+ )cpp";
+ Captured C =
+ run(Code, [](ASTContext &Ctx) { return varEntity("p", Ctx); }, {1});
+ EXPECT_EQ(C.Rewritten, R"cpp(
+ void g(int n) { bounded_ptr<int> p = bounded_ptr<int>::_new(n); }
+ )cpp");
+ EXPECT_TRUE(C.Reports.empty());
+}
+
+TEST_F(CppBoundedBuffersTest, RHSNewArrayPreservesSizeExprVerbatim) {
+ StringRef Code = R"cpp(
+ void g(int n) { int *p = new int[n + /* pad */ 1]; }
+ )cpp";
+ Captured C =
+ run(Code, [](ASTContext &Ctx) { return varEntity("p", Ctx); }, {1});
+ EXPECT_EQ(C.Rewritten, R"cpp(
+ void g(int n) { bounded_ptr<int> p = bounded_ptr<int>::_new(n + /* pad */ 1); }
+ )cpp");
+ EXPECT_TRUE(C.Reports.empty());
+}
+
+TEST_F(CppBoundedBuffersTest, RHSNewArrayMacroSizeExprRewritten) {
+ // The size expression begins with a macro-expanded token, so the edit
+ // boundary immediately before it (SizeCR.getBegin()) is a macro-ID
+ // location. addEditToAtomicChange must resolve this via
+ // Lexer::makeFileCharRange rather than dropping the edit outright, or the
+ // 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";
+ 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");
+ EXPECT_TRUE(C.Reports.empty());
+}
+
+TEST_F(CppBoundedBuffersTest, RHSNewArrayRewrittenAsArg) {
+ StringRef Code = R"cpp(
+ void f(int *p);
+ void g(int n) { f(new int[n]); }
+ )cpp";
+ Captured C =
+ run(Code, [](ASTContext &Ctx) { return paramEntity("f", 0, Ctx); }, {1});
+ EXPECT_EQ(C.Rewritten, R"cpp(
+ void f(bounded_ptr<int> p);
+ void g(int n) { f(bounded_ptr<int>::_new(n)); }
+ )cpp");
+ EXPECT_TRUE(C.Reports.empty());
+}
+
+TEST_F(CppBoundedBuffersTest, RHSNewArrayRewrittenAsArgAsBounded) {
+ StringRef Code = R"cpp(
+ void f(const int *p);
+ void g(int n) { f(new int[n]); }
+ )cpp";
+ Captured C =
+ run(Code, [](ASTContext &Ctx) { return paramEntity("f", 0, Ctx); }, {1});
+ EXPECT_EQ(C.Rewritten, R"cpp(
+ void f(bounded_ptr<const int> p);
+ void g(int n) { f((bounded_ptr<int>::_new(n)).as_bounded<const int>()); }
+ )cpp");
+ EXPECT_TRUE(C.Reports.empty());
+}
+
+TEST_F(CppBoundedBuffersTest, RHSNewArrayWithInitializerNotRewritten) {
+ // A new-array with an initializer is not yet handled, so the RHS is left
+ // as-is even though the LHS is transformed.
+ StringRef Code = R"cpp(
+ void g() { int *p = new int[3]{1, 2, 3}; }
+ )cpp";
+ Captured C =
+ run(Code, [](ASTContext &Ctx) { return varEntity("p", Ctx); }, {1});
+ EXPECT_EQ(C.Rewritten, R"cpp(
+ void g() { bounded_ptr<int> p = new int[3]{1, 2, 3}; }
+ )cpp");
+ EXPECT_TRUE(C.Reports.empty());
+}
+
+TEST_F(CppBoundedBuffersTest, RHSNewArrayNoChangeWhenLHSNotTransformed) {
+ StringRef Code = R"cpp(
+ void g(int n) { int *p = new int[n]; }
+ )cpp";
+ Captured C =
+ run(Code, [](ASTContext &Ctx) { return varEntity("p", Ctx); }, {});
+ EXPECT_EQ(C.Rewritten, Code);
+ EXPECT_TRUE(C.Reports.empty());
+}
+
+TEST_F(CppBoundedBuffersTest, RHSNewArrayDoNotFollowLHSInnerType) {
+ StringRef Code = R"cpp(
+ void g(int n) { void *p = new int[n]; }
+ )cpp";
+ Captured C =
+ run(Code, [](ASTContext &Ctx) { return varEntity("p", Ctx); }, {1});
+ EXPECT_EQ(C.Rewritten, R"cpp(
+ void g(int n) { bounded_ptr<char> p = bounded_ptr<int>::_new(n); }
+ )cpp");
+ EXPECT_TRUE(C.Reports.empty());
+}
+
+TEST_F(CppBoundedBuffersTest, RHSMallocCallRewritten) {
+ // 'malloc(n)' is likewise rewritten based on the LHS being transformed.
+ StringRef Code = R"cpp(
+ extern "C" void *malloc(decltype(sizeof(0)) n);
+ void g(int n) { void *p = malloc(n); }
+ )cpp";
+ Captured C =
+ run(Code, [](ASTContext &Ctx) { return varEntity("p", Ctx); }, {1});
+ EXPECT_EQ(C.Rewritten, R"cpp(
+ extern "C" void *malloc(decltype(sizeof(0)) n);
+ void g(int n) { bounded_ptr<char> p = _malloc(n); }
+ )cpp");
+ EXPECT_TRUE(C.Reports.empty());
+}
+
+TEST_F(CppBoundedBuffersTest, RHSCastMallocCallRewritten) {
+ // 'malloc(n)' is likewise rewritten based on the LHS being transformed.
+ StringRef Code = R"cpp(
+ extern "C" void *malloc(decltype(sizeof(0)) n);
+ void g(int n) { int *p = (int *)malloc(n); }
+ )cpp";
+ Captured C =
+ run(Code, [](ASTContext &Ctx) { return varEntity("p", Ctx); }, {1});
+ EXPECT_EQ(C.Rewritten, R"cpp(
+ extern "C" void *malloc(decltype(sizeof(0)) n);
+ void g(int n) { bounded_ptr<int> p = (int *)_malloc(n); }
+ )cpp");
+ EXPECT_TRUE(C.Reports.empty());
+}
+
+TEST_F(CppBoundedBuffersTest, RHSQualifiedMallocCallRewritten) {
+ StringRef Code = R"cpp(
+ extern "C" void *malloc(decltype(sizeof(0)) n);
+ void g(int n) { void *p = ::malloc(n); }
+ )cpp";
+ Captured C =
+ run(Code, [](ASTContext &Ctx) { return varEntity("p", Ctx); }, {1});
+ EXPECT_EQ(C.Rewritten, R"cpp(
+ extern "C" void *malloc(decltype(sizeof(0)) n);
+ void g(int n) { bounded_ptr<char> p = ::_malloc(n); }
+ )cpp");
+ EXPECT_TRUE(C.Reports.empty());
+}
+
} // namespace
More information about the llvm-branch-commits
mailing list