[clang] b117508 - [LifetimeSafety] Add support for `new`/`delete` (#192504)
via cfe-commits
cfe-commits at lists.llvm.org
Tue Apr 21 13:47:55 PDT 2026
Author: NeKon69
Date: 2026-04-22T02:17:51+05:30
New Revision: b1175088531de1a3bae9942ef50d880fab6fe144
URL: https://github.com/llvm/llvm-project/commit/b1175088531de1a3bae9942ef50d880fab6fe144
DIFF: https://github.com/llvm/llvm-project/commit/b1175088531de1a3bae9942ef50d880fab6fe144.diff
LOG: [LifetimeSafety] Add support for `new`/`delete` (#192504)
This PR extends LifetimeSafety to also support heap allocations via
`new`/`delete`.
# Contents
* Adds a new warning that is emitted on use-after-free.
* Renames `reportUseAfterFree` to `reportUseAfterScope` due to the old
name being misleading (warnings are still called `use_after_scope`).
* Adds a new `AccessPath::Kind` value `NewAllocation` that is used for
loans issued from `new` allocations.
* Adds `VisitCXXNewExpr` and `VisitCXXDeleteExpr`, which handle loan
issuance / origin propagation for `new` / `delete`.
* Comes with extensive testing of the new features (including new
use-after-free tests, and use-after-scope coverage for `new`).
Completes part of #164963
Assisted-by: GPT-5.4 for writing most of the tests (the remaining tests,
and everything else in this PR, were written by me).
Added:
Modified:
clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h
clang/include/clang/Basic/DiagnosticGroups.td
clang/include/clang/Basic/DiagnosticSemaKinds.td
clang/lib/Analysis/LifetimeSafety/Checker.cpp
clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
clang/lib/Analysis/LifetimeSafety/Loans.cpp
clang/lib/Sema/SemaLifetimeSafety.h
clang/test/Sema/Inputs/lifetime-analysis.h
clang/test/Sema/warn-lifetime-safety-suggestions.cpp
clang/test/Sema/warn-lifetime-safety.cpp
Removed:
################################################################################
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
index b70fecd5ab1d1..88b509e1b94df 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
@@ -53,7 +53,7 @@ class Fact {
TestPoint,
/// An origin that escapes the function scope (e.g., via return).
OriginEscapes,
- /// An origin is invalidated (e.g. vector resized).
+ /// An origin is invalidated (e.g. vector resized, `delete` called).
InvalidateOrigin,
/// All loans of an origin are cleared.
KillOrigin,
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
index a86f22a181a70..45b16cf1ec31d 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
@@ -53,6 +53,8 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> {
void VisitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *MTE);
void VisitLambdaExpr(const LambdaExpr *LE);
void VisitArraySubscriptExpr(const ArraySubscriptExpr *ASE);
+ void VisitCXXNewExpr(const CXXNewExpr *NE);
+ void VisitCXXDeleteExpr(const CXXDeleteExpr *DE);
private:
OriginList *getOriginsList(const ValueDecl &D);
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
index 83c3c455c4c81..d20ac87a7c8d9 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h
@@ -61,9 +61,9 @@ class LifetimeSafetySemaHelper {
LifetimeSafetySemaHelper() = default;
virtual ~LifetimeSafetySemaHelper() = default;
- virtual void reportUseAfterFree(const Expr *IssueExpr, const Expr *UseExpr,
- const Expr *MovedExpr,
- SourceLocation FreeLoc) {}
+ virtual void reportUseAfterScope(const Expr *IssueExpr, const Expr *UseExpr,
+ const Expr *MovedExpr,
+ SourceLocation FreeLoc) {}
virtual void reportUseAfterReturn(const Expr *IssueExpr,
const Expr *ReturnExpr,
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h
index aee6bf9eb69c9..d25c91333100c 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h
@@ -35,32 +35,35 @@ inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, LoanID ID) {
/// - MaterializeTemporaryExpr: a temporary object
/// - ParmVarDecl: a function parameter (placeholder)
/// - CXXMethodDecl: the implicit 'this' object (placeholder)
+/// - CXXNewExpr: a heap allocation made by `new`
///
/// Placeholder paths never expire within the function scope, as they represent
/// storage from the caller's scope.
///
/// TODO: Model access paths of other types, e.g. field, array subscript, heap
-/// and globals.
+/// allocation not through `new`, and globals.
class AccessPath {
public:
enum class Kind : uint8_t {
ValueDecl,
MaterializeTemporary,
PlaceholderParam,
- PlaceholderThis
+ PlaceholderThis,
+ NewAllocation,
};
private:
Kind K;
- const llvm::PointerUnion<const clang::ValueDecl *,
- const clang::MaterializeTemporaryExpr *,
- const ParmVarDecl *, const CXXMethodDecl *>
+ const llvm::PointerUnion<
+ const clang::ValueDecl *, const clang::MaterializeTemporaryExpr *,
+ const ParmVarDecl *, const CXXMethodDecl *, const CXXNewExpr *>
Root;
public:
AccessPath(const clang::ValueDecl *D) : K(Kind::ValueDecl), Root(D) {}
AccessPath(const clang::MaterializeTemporaryExpr *MTE)
: K(Kind::MaterializeTemporary), Root(MTE) {}
+ AccessPath(const CXXNewExpr *New) : K(Kind::NewAllocation), Root(New) {}
static AccessPath Placeholder(const ParmVarDecl *PVD) {
return AccessPath(Kind::PlaceholderParam, PVD);
}
@@ -88,6 +91,10 @@ class AccessPath {
return K == Kind::PlaceholderThis ? Root.dyn_cast<const CXXMethodDecl *>()
: nullptr;
}
+ const CXXNewExpr *getAsNewAllocation() const {
+ return K == Kind::NewAllocation ? Root.dyn_cast<const CXXNewExpr *>()
+ : nullptr;
+ }
bool operator==(const AccessPath &RHS) const {
return K == RHS.K && Root == RHS.Root;
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index 3136d109e795b..74f9fab14d82c 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -586,6 +586,12 @@ This may contain false-positives, e.g. when the borrowed storage is potentially
}];
}
+def LifetimeSafetyUseAfterFree : DiagGroup<"lifetime-safety-use-after-free"> {
+ code Documentation = [{
+Warning to detect use-after-free, introduced by freeing an object and later using it.
+ }];
+}
+
// Dangling-Field (aka Escape-To-Field)
def LifetimeSafetyDanglingField : DiagGroup<"lifetime-safety-dangling-field"> {
code Documentation = [{
@@ -622,14 +628,16 @@ def LifetimeSafetyPermissive : DiagGroup<"lifetime-safety-permissive",
[LifetimeSafetyUseAfterScope,
LifetimeSafetyReturnStackAddr,
LifetimeSafetyDanglingField,
- LifetimeSafetyDanglingGlobal]>;
+ LifetimeSafetyDanglingGlobal,
+ LifetimeSafetyUseAfterFree]>;
def LifetimeSafetyStrict : DiagGroup<"lifetime-safety-strict",
[LifetimeSafetyUseAfterScopeMoved,
LifetimeSafetyReturnStackAddrMoved,
LifetimeSafetyDanglingFieldMoved,
LifetimeSafetyDanglingGlobal,
- LifetimeSafetyInvalidation]>;
+ LifetimeSafetyInvalidation,
+ LifetimeSafetyUseAfterFree]>;
def LifetimeSafety : DiagGroup<"lifetime-safety",
[LifetimeSafetyPermissive, LifetimeSafetyStrict]> {
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index b744cd5df1664..3ab8dd412a7ad 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10965,6 +10965,10 @@ def warn_lifetime_safety_use_after_scope_moved : Warning<
"This could be false positive as the storage may have been moved later">,
InGroup<LifetimeSafetyUseAfterScopeMoved>, DefaultIgnore;
+def warn_lifetime_safety_use_after_free : Warning<
+ "%select{allocated object|parameter}0 does not live long enough">,
+ InGroup<LifetimeSafetyUseAfterFree>, DefaultIgnore;
+
def warn_lifetime_safety_return_stack_addr
: Warning<"address of stack memory is returned later">,
InGroup<LifetimeSafetyReturnStackAddr>,
@@ -11005,6 +11009,7 @@ def warn_lifetime_safety_dangling_global_moved
def note_lifetime_safety_used_here : Note<"later used here">;
def note_lifetime_safety_invalidated_here : Note<"invalidated here">;
def note_lifetime_safety_destroyed_here : Note<"destroyed here">;
+def note_lifetime_safety_freed_here : Note<"freed here">;
def note_lifetime_safety_returned_here : Note<"returned here">;
def note_lifetime_safety_moved_here : Note<"potentially moved here">;
def note_lifetime_safety_dangling_field_here: Note<"this field dangles">;
diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index fe103739dbcb0..8164eb15cb6cb 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -250,8 +250,8 @@ class LifetimeChecker {
} else
// Scope-based expiry (use-after-scope).
- SemaHelper->reportUseAfterFree(IssueExpr, UF->getUseExpr(), MovedExpr,
- ExpiryLoc);
+ SemaHelper->reportUseAfterScope(IssueExpr, UF->getUseExpr(),
+ MovedExpr, ExpiryLoc);
} else if (const auto *OEF =
CausingFact.dyn_cast<const OriginEscapesFact *>()) {
if (const auto *RetEscape = dyn_cast<ReturnEscapeFact>(OEF))
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 23990435e24dc..33da608385dcd 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -94,6 +94,14 @@ static const Loan *createLoan(FactManager &FactMgr,
return FactMgr.getLoanMgr().createLoan(Path, MTE);
}
+/// Creates a loan for an allocation through 'new'
+/// \param NE The CXXNewExpr that represents the allocation
+/// \return The new Loan on success, nullptr otherwise
+static const Loan *createLoan(FactManager &FactMgr, const CXXNewExpr *NE) {
+ AccessPath Path(NE);
+ return FactMgr.getLoanMgr().createLoan(Path, NE);
+}
+
void FactsGenerator::run() {
llvm::TimeTraceScope TimeProfile("FactGenerator");
const CFG &Cfg = *AC.getCFG();
@@ -615,6 +623,30 @@ void FactsGenerator::VisitArraySubscriptExpr(const ArraySubscriptExpr *ASE) {
Dst->getOuterOriginID(), Src->getOuterOriginID(), /*Kill=*/true));
}
+void FactsGenerator::VisitCXXNewExpr(const CXXNewExpr *NE) {
+ OriginList *NewList = getOriginsList(*NE);
+
+ const Loan *L = createLoan(FactMgr, NE);
+ CurrentBlockFacts.push_back(
+ FactMgr.createFact<IssueFact>(L->getID(), NewList->getOuterOriginID()));
+
+ NewList = NewList->peelOuterOrigin();
+
+ if (!NewList)
+ return;
+
+ // FIXME: OriginList is null for `new[]` initializers. Remove this `Init`
+ // check once array origins are supported.
+ if (OriginList *Init = getOriginsList(*NE->getInitializer()); Init)
+ flow(NewList, Init, true);
+}
+
+void FactsGenerator::VisitCXXDeleteExpr(const CXXDeleteExpr *DE) {
+ OriginList *List = getOriginsList(*DE->getArgument());
+ CurrentBlockFacts.push_back(
+ FactMgr.createFact<InvalidateOriginFact>(List->getOuterOriginID(), DE));
+}
+
bool FactsGenerator::escapesViaReturn(OriginID OID) const {
return llvm::any_of(EscapesInCurrentBlock, [OID](const Fact *F) {
if (const auto *EF = F->getAs<ReturnEscapeFact>())
diff --git a/clang/lib/Analysis/LifetimeSafety/Loans.cpp b/clang/lib/Analysis/LifetimeSafety/Loans.cpp
index 336331b8f5a27..c0c8d0b283f8b 100644
--- a/clang/lib/Analysis/LifetimeSafety/Loans.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Loans.cpp
@@ -28,6 +28,10 @@ void AccessPath::dump(llvm::raw_ostream &OS) const {
case Kind::PlaceholderThis:
OS << "$this";
break;
+ case Kind::NewAllocation:
+ if (const auto *E = getAsNewAllocation())
+ OS << "NewAllocation at " << E;
+ break;
}
}
diff --git a/clang/lib/Sema/SemaLifetimeSafety.h b/clang/lib/Sema/SemaLifetimeSafety.h
index c56a9692abe1a..92e7b5cf14ae5 100644
--- a/clang/lib/Sema/SemaLifetimeSafety.h
+++ b/clang/lib/Sema/SemaLifetimeSafety.h
@@ -43,9 +43,9 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper {
public:
LifetimeSafetySemaHelperImpl(Sema &S) : S(S) {}
- void reportUseAfterFree(const Expr *IssueExpr, const Expr *UseExpr,
- const Expr *MovedExpr,
- SourceLocation FreeLoc) override {
+ void reportUseAfterScope(const Expr *IssueExpr, const Expr *UseExpr,
+ const Expr *MovedExpr,
+ SourceLocation FreeLoc) override {
S.Diag(IssueExpr->getExprLoc(),
MovedExpr ? diag::warn_lifetime_safety_use_after_scope_moved
: diag::warn_lifetime_safety_use_after_scope)
@@ -111,21 +111,32 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper {
void reportUseAfterInvalidation(const Expr *IssueExpr, const Expr *UseExpr,
const Expr *InvalidationExpr) override {
- S.Diag(IssueExpr->getExprLoc(), diag::warn_lifetime_safety_invalidation)
+ auto WarnDiag = isa<CXXDeleteExpr>(InvalidationExpr)
+ ? diag::warn_lifetime_safety_use_after_free
+ : diag::warn_lifetime_safety_invalidation;
+ auto UseDiag = isa<CXXDeleteExpr>(InvalidationExpr)
+ ? diag::note_lifetime_safety_freed_here
+ : diag::note_lifetime_safety_invalidated_here;
+ S.Diag(IssueExpr->getExprLoc(), WarnDiag)
<< false << IssueExpr->getSourceRange();
- S.Diag(InvalidationExpr->getExprLoc(),
- diag::note_lifetime_safety_invalidated_here)
+ S.Diag(InvalidationExpr->getExprLoc(), UseDiag)
<< InvalidationExpr->getSourceRange();
S.Diag(UseExpr->getExprLoc(), diag::note_lifetime_safety_used_here)
<< UseExpr->getSourceRange();
}
void reportUseAfterInvalidation(const ParmVarDecl *PVD, const Expr *UseExpr,
const Expr *InvalidationExpr) override {
- S.Diag(PVD->getSourceRange().getBegin(),
- diag::warn_lifetime_safety_invalidation)
+
+ auto WarnDiag = isa<CXXDeleteExpr>(InvalidationExpr)
+ ? diag::warn_lifetime_safety_use_after_free
+ : diag::warn_lifetime_safety_invalidation;
+ auto UseDiag = isa<CXXDeleteExpr>(InvalidationExpr)
+ ? diag::note_lifetime_safety_freed_here
+ : diag::note_lifetime_safety_invalidated_here;
+
+ S.Diag(PVD->getSourceRange().getBegin(), WarnDiag)
<< true << PVD->getSourceRange();
- S.Diag(InvalidationExpr->getExprLoc(),
- diag::note_lifetime_safety_invalidated_here)
+ S.Diag(InvalidationExpr->getExprLoc(), UseDiag)
<< InvalidationExpr->getSourceRange();
S.Diag(UseExpr->getExprLoc(), diag::note_lifetime_safety_used_here)
<< UseExpr->getSourceRange();
diff --git a/clang/test/Sema/Inputs/lifetime-analysis.h b/clang/test/Sema/Inputs/lifetime-analysis.h
index 9806cce15d8f5..2836ec00e97cb 100644
--- a/clang/test/Sema/Inputs/lifetime-analysis.h
+++ b/clang/test/Sema/Inputs/lifetime-analysis.h
@@ -289,3 +289,6 @@ class function<R(Args...)> {
};
}
+
+void *operator new(std::size_t, void *) noexcept;
+void *operator new[](std::size_t, void *) noexcept;
diff --git a/clang/test/Sema/warn-lifetime-safety-suggestions.cpp b/clang/test/Sema/warn-lifetime-safety-suggestions.cpp
index 08405c4607f3c..bdcfe728f6776 100644
--- a/clang/test/Sema/warn-lifetime-safety-suggestions.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-suggestions.cpp
@@ -570,3 +570,53 @@ void test_inference() {
(void)ptr; // expected-note {{later used here}}
}
} // namespace make_unique_suggestion
+
+namespace new_allocation_suggestion {
+
+View* MakeView(const MyObj& in) { // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}}
+ return new View(in); // expected-note {{param returned here}} {{destroyed here}}
+}
+
+void test_new_allocation() {
+ View* v = MakeView(MyObj{}); // expected-warning {{object whose reference is captured does not live long enough}} \
+ // expected-note {{destroyed here}}
+ (void)v; // expected-note {{later used here}}
+}
+
+struct LifetimeBoundCtor {
+ View v;
+ LifetimeBoundCtor();
+ LifetimeBoundCtor(const MyObj& obj [[clang::lifetimebound]]) : v(obj) {}
+};
+
+struct HasCtorField {
+ LifetimeBoundCtor* field; // expected-note {{escapes to this field}}
+ HasCtorField(const MyObj& obj) : field(new LifetimeBoundCtor(obj)) {} // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}}
+};
+
+HasCtorField test_dangling_field_ctor() {
+ MyObj obj;
+ HasCtorField x(obj); // expected-warning {{address of stack memory is returned later}}
+ return x; // expected-note {{returned here}}
+}
+
+struct HasSetterField {
+ LifetimeBoundCtor* field; // expected-note {{this field dangles}}
+ // FIXME: Does not currently suggest `lifetime_capture_by(this)` (even without `new`)
+ void set(const MyObj& obj) {
+ field = new LifetimeBoundCtor(obj);
+ }
+ void reset() {
+ MyObj obj;
+ field = new LifetimeBoundCtor(obj); // expected-warning {{address of stack memory escapes to a field}}
+ }
+};
+
+HasSetterField test_dangling_field_member_fn() {
+ MyObj obj;
+ HasSetterField x;
+ x.set(obj);
+ return x;
+}
+
+} // namespace new_allocation_suggestion
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index fba5523931b44..bb87b13a71c49 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -2663,6 +2663,236 @@ int *noreturn_dead_nested(bool cond, bool cond2, int *num) {
} // namespace conditional_operator_control_flow
+namespace new_allocation {
+
+//===----------------------------------------------------------------------===//
+// new
+//===----------------------------------------------------------------------===//
+
+void new_view_from_dead_scope() {
+ View *p;
+ {
+ MyObj obj;
+ p = new View(obj); // expected-warning {{object whose reference is captured does not live long enough}}
+ } // expected-note {{destroyed here}}
+ p->use(); // expected-note {{later used here}}
+}
+
+void new_int_basic() {
+ int *p = new int; // expected-warning {{allocated object does not live long enough}}
+ delete p; // expected-note {{freed here}}
+ (void)*p; // expected-note {{later used here}}
+}
+
+void new_int_parens() {
+ int *p = new int(); // expected-warning {{allocated object does not live long enough}}
+ delete p; // expected-note {{freed here}}
+ (void)*p; // expected-note {{later used here}}
+}
+
+void new_int_braces() {
+ int *p = new int{}; // expected-warning {{allocated object does not live long enough}}
+ delete p; // expected-note {{freed here}}
+ (void)*p; // expected-note {{later used here}}
+}
+
+void conditional_delete(bool cond) {
+ int *p1 = new int; // expected-warning {{allocated object does not live long enough}}
+ int *p2 = new int; // expected-warning {{allocated object does not live long enough}}
+ delete (cond ? p1 : p2); // expected-note 2 {{freed here}}
+ (void)*p1; // expected-note {{later used here}}
+ (void)*p2; // expected-note {{later used here}}
+}
+
+int* foo(int* x [[clang::lifetimebound]], int* y [[clang::lifetimebound]]);
+
+void delete_returned_from_call() {
+ int* x = new int(1); // expected-warning {{allocated object does not live long enough}}
+ int* y = new int(2); // expected-warning {{allocated object does not live long enough}}
+ delete foo(x, y); // expected-note 2 {{freed here}}
+ (void)x; // expected-note {{later used here}}
+ (void)y; // expected-note {{later used here}}
+}
+
+void new_pointer_from_pointer() {
+ MyObj **p;
+ {
+ MyObj obj;
+ MyObj *q = &obj; // expected-warning {{object whose reference is captured does not live long enough}}
+ p = new MyObj *(q);
+ } // expected-note {{destroyed here}}
+ (void)**p; // expected-note {{later used here}}
+}
+
+void new_pointer_from_dead_object() {
+ MyObj **p;
+ {
+ MyObj obj;
+ p = new MyObj *(&obj); // expected-warning {{object whose reference is captured does not live long enough}}
+ } // expected-note {{destroyed here}}
+ (void)**p; // expected-note {{later used here}}
+}
+
+struct MultiView {
+ MultiView(MyObj& a [[clang::lifetimebound]], MyObj& b [[clang::lifetimebound]]);
+};
+
+void new_multiview_from_mixed_scope() {
+ MyObj obj1;
+ MultiView *p;
+ {
+ MyObj obj2;
+ p = new MultiView(obj1, obj2); // expected-warning {{object whose reference is captured does not live long enough}}
+ } // expected-note {{destroyed here}}
+ (void)p; // expected-note {{later used here}}
+}
+
+void new_array_basic() {
+ int *p = new int[2]; // expected-warning {{allocated object does not live long enough}}
+ delete[] p; // expected-note {{freed here}}
+ (void)p[0]; // expected-note {{later used here}}
+}
+
+void new_array_parens() {
+ int *p = new int[2](); // expected-warning {{allocated object does not live long enough}}
+ delete[] p; // expected-note {{freed here}}
+ (void)p[0]; // expected-note {{later used here}}
+}
+
+void new_array_braces() {
+ int *p = new int[2]{}; // expected-warning {{allocated object does not live long enough}}
+ delete[] p; // expected-note {{freed here}}
+ (void)p[0]; // expected-note {{later used here}}
+}
+
+// FIXME: https://github.com/llvm/llvm-project/issues/187471
+void new_pointer_array_from_dead_objects() {
+ MyObj **arr;
+ {
+ MyObj a, b;
+ arr = new MyObj *[2]{&a, &b};
+ }
+ (void)arr[0]->id;
+ (void)arr[1]->id;
+}
+
+struct PointerArrayFieldHolder {
+ MyObj **Ptrs;
+};
+
+// FIXME: https://github.com/llvm/llvm-project/issues/187471
+void pointer_array_field_sensitivity() {
+ PointerArrayFieldHolder h;
+ {
+ MyObj a, b;
+ h.Ptrs = new MyObj *[2]{&a, &b};
+ }
+ (void)h.Ptrs[0]->id;
+}
+
+//===----------------------------------------------------------------------===//
+// delete
+//===----------------------------------------------------------------------===//
+
+void delete_direct_use_after_free() {
+ MyObj *p = new MyObj; // expected-warning {{allocated object does not live long enough}}
+ delete p; // expected-note {{freed here}}
+ (void)p->id; // expected-note {{later used here}}
+}
+
+void delete_alias_use_after_free() {
+ MyObj *p = new MyObj; // expected-warning {{allocated object does not live long enough}}
+ MyObj *q = p;
+ delete p; // expected-note {{freed here}}
+ (void)q->id; // expected-note {{later used here}}
+}
+
+void delete_pointer_propagation_use_after_free() {
+ MyObj *p = new MyObj; // expected-warning {{allocated object does not live long enough}}
+ MyObj **pp = &p;
+ delete p; // expected-note {{freed here}}
+ (void)(*pp)->id; // expected-note {{later used here}}
+}
+
+void delete_param_pointer(int* x) { // expected-warning {{parameter does not live long enough}}
+ delete x; // expected-note {{freed here}}
+ (void)x; // expected-note {{later used here}}
+}
+
+// FIXME: false-negative
+struct S {
+ int *x;
+ void foo() {
+ delete x;
+ (void)x;
+ }
+};
+
+void use_inner_origin_after_delete(MyObj* obj) { // expected-warning {{parameter does not live long enough}}
+ int* p = &obj->id;
+ delete obj; // expected-note {{freed here}}
+ (void)*p; // expected-note {{later used here}}
+}
+
+void delete_nullptr_no_warning() {
+ int *p = nullptr;
+ delete p;
+}
+
+void delete_array_nullptr_no_warning() {
+ int *p = nullptr;
+ delete[] p;
+}
+
+struct ClassSpecificDelete {
+ int X;
+ static void operator delete(void *);
+};
+
+void class_specific_operator_delete_use_after_free() {
+ ClassSpecificDelete *p = new ClassSpecificDelete; // expected-warning {{allocated object does not live long enough}}
+ delete p; // expected-note {{freed here}}
+ (void)p->X; // expected-note {{later used here}}
+}
+
+struct PointerFieldHolder {
+ MyObj *Ptr;
+};
+
+// FIXME: https://github.com/llvm/llvm-project/issues/184344
+void placement_new_pointer_field_use_after_scope() {
+ PointerFieldHolder h;
+ PointerFieldHolder *p = &h;
+ {
+ MyObj obj;
+ p = new (&h) PointerFieldHolder{&obj};
+ }
+ (void)p->Ptr->id;
+}
+
+// FIXME: https://github.com/llvm/llvm-project/issues/184344
+void delete_through_pointer_field() {
+ PointerFieldHolder h{new MyObj};
+ delete h.Ptr;
+ (void)h.Ptr->id;
+}
+
+void delete_stack_object() {
+ MyObj obj;
+ MyObj* p = &obj; // expected-warning {{allocated object does not live long enough}}
+ delete &obj; // expected-note {{freed here}}
+ (void)p->id; // expected-note {{later used here}}
+}
+
+void delete_stack_object_int() {
+ int obj;
+ int* p = &obj; // expected-warning {{allocated object does not live long enough}}
+ delete &obj; // expected-note {{freed here}}
+ (void)*p; // expected-note {{later used here}}
+}
+
+} // namespace new_allocation
+
namespace method_call_uses_field_origins {
int GLOBAL_INT;
std::string GLOBAL_STRING{"123"};
More information about the cfe-commits
mailing list