[clang] 00e69de - [LifetimeSafety] Add support for destructive function calls (#195064)
via cfe-commits
cfe-commits at lists.llvm.org
Fri May 1 03:54:52 PDT 2026
Author: NeKon69
Date: 2026-05-01T16:24:47+05:30
New Revision: 00e69deb0b14035e9dc8e24aaa03d95d6d682a6c
URL: https://github.com/llvm/llvm-project/commit/00e69deb0b14035e9dc8e24aaa03d95d6d682a6c
DIFF: https://github.com/llvm/llvm-project/commit/00e69deb0b14035e9dc8e24aaa03d95d6d682a6c.diff
LOG: [LifetimeSafety] Add support for destructive function calls (#195064)
Adds `handleDestructiveCall` function that detects invalidation caused
by explicit destructive calls, such as explicit destructor calls and
`std::destroy_at`.
Comes as part of the completion of #164963.
Assisted-by: GPT-5.4 for writing some of the tests.
Added:
Modified:
clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
clang/test/Sema/Inputs/lifetime-analysis.h
clang/test/Sema/warn-lifetime-safety-invalidations.cpp
Removed:
################################################################################
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
index 45b16cf1ec31d..766742e98101a 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
@@ -104,6 +104,10 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> {
void handleInvalidatingCall(const Expr *Call, const FunctionDecl *FD,
ArrayRef<const Expr *> Args);
+ // Detect explicit destructor calls/`std::destroy_at`
+ void handleDestructiveCall(const Expr *Call, const FunctionDecl *FD,
+ ArrayRef<const Expr *> Args);
+
template <typename Destination, typename Source>
void flowOrigin(const Destination &D, const Source &S) {
flow(getOriginsList(D), getOriginsList(S), /*Kill=*/false);
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
index 433239fb385a3..54d52fee6bea7 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
@@ -80,6 +80,10 @@ bool isUniquePtrRelease(const CXXMethodDecl &MD);
// https://en.cppreference.com/w/cpp/container#Iterator_invalidation
bool isInvalidationMethod(const CXXMethodDecl &MD);
+// Returns true if the function destroys its first argument
+// (e.g., destructors via implicit 'this', std::destroy_at).
+bool destructsFirstArg(const FunctionDecl &FD);
+
/// Returns true for standard library callable wrappers (e.g., std::function)
/// that can propagate the stored lambda's origins.
bool isStdCallableWrapperType(const CXXRecordDecl *RD);
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 7835ac8e0ff23..dc80d2783fc03 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -821,6 +821,17 @@ void FactsGenerator::handleInvalidatingCall(const Expr *Call,
ThisList->getOuterOriginID(), Call));
}
+void FactsGenerator::handleDestructiveCall(const Expr *Call,
+ const FunctionDecl *FD,
+ ArrayRef<const Expr *> Args) {
+ if (!destructsFirstArg(*FD))
+ return;
+ OriginList *ArgList = getOriginsList(*Args[0]);
+ if (ArgList)
+ CurrentBlockFacts.push_back(FactMgr.createFact<InvalidateOriginFact>(
+ ArgList->getOuterOriginID(), Call));
+}
+
void FactsGenerator::handleImplicitObjectFieldUses(const Expr *Call,
const FunctionDecl *FD) {
const auto *MemberCall = dyn_cast_or_null<CXXMemberCallExpr>(Call);
@@ -866,6 +877,7 @@ void FactsGenerator::handleFunctionCall(const Expr *Call,
for (const Expr *Arg : Args)
handleUse(Arg);
handleInvalidatingCall(Call, FD, Args);
+ handleDestructiveCall(Call, FD, Args);
handleMovedArgsInCall(FD, Args);
handleImplicitObjectFieldUses(Call, FD);
if (!CallList)
diff --git a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
index e1cc6a098694c..b33fb8edc100b 100644
--- a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
@@ -268,6 +268,12 @@ static StringRef getName(const CXXRecordDecl &RD) {
return "";
}
+static StringRef getName(const FunctionDecl &FD) {
+ if (FD.getIdentifier())
+ return FD.getName();
+ return "";
+}
+
static bool isStdUniquePtr(const CXXRecordDecl &RD) {
return RD.isInStdNamespace() && getName(RD) == "unique_ptr";
}
@@ -387,6 +393,12 @@ bool isInvalidationMethod(const CXXMethodDecl &MD) {
return InvalidatingMethods->contains(MD.getName());
}
+bool destructsFirstArg(const FunctionDecl &FD) {
+ if (isa<CXXDestructorDecl>(FD))
+ return true;
+ return isInStlNamespace(&FD) && getName(FD) == "destroy_at";
+}
+
bool isStdCallableWrapperType(const CXXRecordDecl *RD) {
if (!RD || !isInStlNamespace(RD))
return false;
diff --git a/clang/test/Sema/Inputs/lifetime-analysis.h b/clang/test/Sema/Inputs/lifetime-analysis.h
index c7473d2928adf..eaedba372ee5e 100644
--- a/clang/test/Sema/Inputs/lifetime-analysis.h
+++ b/clang/test/Sema/Inputs/lifetime-analysis.h
@@ -214,6 +214,9 @@ unique_ptr<T> make_unique(Args&&... args) {
return unique_ptr<T>(new T(args...));
}
+template <class T>
+void destroy_at(T *);
+
template<typename T>
struct shared_ptr {
shared_ptr();
diff --git a/clang/test/Sema/warn-lifetime-safety-invalidations.cpp b/clang/test/Sema/warn-lifetime-safety-invalidations.cpp
index 19e2c9299a650..f1044e2ad1cdd 100644
--- a/clang/test/Sema/warn-lifetime-safety-invalidations.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-invalidations.cpp
@@ -540,6 +540,80 @@ void function_captured_ref_invalidated() {
} // namespace callable_wrappers
+// FIXME: does not report a double free
+namespace explicit_destructor {
+
+void explicit_destructor_invalidates_pointer() {
+ std::string s = "42";
+ const char *p = s.data(); // expected-warning {{object whose reference is captured is later invalidated}}
+ s.~basic_string(); // expected-note {{invalidated here}}
+ (void)*p; // expected-note {{later used here}}
+}
+
+void pointer_destructor_invalidates_pointer() {
+ char storage[sizeof(std::string)];
+ std::string *obj = new (storage) std::string("42"); // expected-warning {{object whose reference is captured is later invalidated}}
+ const char *p = obj->data();
+ obj->~basic_string(); // expected-note {{invalidated here}}
+ (void)*p; // expected-note {{later used here}}
+}
+
+void destroy_at_invalidates_pointer() {
+ char storage[sizeof(std::string)];
+ std::string *obj = new (storage) std::string("42"); // expected-warning {{object whose reference is captured is later invalidated}}
+ const char *p = obj->data();
+ std::destroy_at(obj); // expected-note {{invalidated here}}
+ (void)*p; // expected-note {{later used here}}
+}
+
+void destroy_at_then_placement_new_rescues_pointer() {
+ char storage[sizeof(std::string)];
+ std::string *obj = new (storage) std::string("42");
+ const char *p = obj->data();
+ std::destroy_at(obj);
+ obj = new (storage) std::string("23");
+ p = obj->data();
+ (void)*p;
+}
+
+void destroy_at_invalidates_array_pointer() {
+ std::string arr[1] = {"42"};
+ std::string (&arr_ref)[1] = arr;
+ const char *p = arr[0].data(); // expected-warning {{object whose reference is captured is later invalidated}}
+ std::destroy_at(&arr_ref); // expected-note {{invalidated here}}
+ (void)*p; // expected-note {{later used here}}
+}
+
+void reference_destructor_invalidates_pointer() {
+ std::string s = "42";
+ std::string &ref = s; // expected-warning {{object whose reference is captured is later invalidated}}
+ const char *p = ref.data();
+ std::destroy_at(&ref); // expected-note {{invalidated here}}
+ (void)*p; // expected-note {{later used here}}
+}
+
+void destroy_at_ternary_operator(bool flag) {
+ std::string* str1 = new std::string; // expected-warning {{object whose reference is captured is later invalidated}}
+ std::string* str2 = new std::string;
+ const char *p = str1->data();
+ std::destroy_at(flag ? str1 : str2); // expected-note {{invalidated here}}
+ (void)*p; // expected-note {{later used here}}
+}
+
+struct StringOwner {
+ std::string s, t;
+};
+
+// FIXME: False-positive
+void member_destructor_invalidates_pointer() {
+ StringOwner owner = {"42", "43"};
+ const char *p = owner.s.data(); // expected-warning {{object whose reference is captured is later invalidated}}
+ owner.t.~basic_string(); // expected-note {{invalidated here}}
+ (void)*p; // expected-note {{later used here}}
+}
+
+} // namespace explicit_destructor
+
namespace unique_ptr_invalidation {
void invalid_after_reset() {
More information about the cfe-commits
mailing list