https://github.com/arhwx updated https://github.com/llvm/llvm-project/pull/212154
>From ef4a173b0d6a9c13a7fe99a490e34b09e4e2aa83 Mon Sep 17 00:00:00 2001 From: arhwx <[email protected]> Date: Sun, 26 Jul 2026 18:08:56 -0400 Subject: [PATCH 1/4] [LifetimeSafety] Fix crash on explicit object member functions handleMovedArgsInCall assumed the object argument is absent from FunctionDecl::parameters() and applied a one parameter offset. An explicit object parameter is a regular ParmVarDecl, so the offset misaligned arguments with parameters and the assert dereferenced a null OriginList. Fixes #204210 --- .../Analysis/LifetimeSafety/FactsGenerator.cpp | 3 ++- .../explicit-object-param-no-crash.cpp | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 clang/test/Sema/LifetimeSafety/explicit-object-param-no-crash.cpp diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp index 466bb2185c6d8..e858e0b54688b 100644 --- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp +++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp @@ -911,7 +911,8 @@ void FactsGenerator::handleMovedArgsInCall(const FunctionDecl *FD, ArrayRef<const Expr *> Args) { unsigned IsInstance = 0; if (const auto *MD = dyn_cast<CXXMethodDecl>(FD); - MD && MD->isInstance() && !isa<CXXConstructorDecl>(FD)) { + MD && !isa<CXXConstructorDecl>(FD) && + MD->isImplicitObjectMemberFunction()) { IsInstance = 1; // std::unique_ptr::release() transfers ownership. // Treat it as a move to prevent false-positive warnings when the unique_ptr diff --git a/clang/test/Sema/LifetimeSafety/explicit-object-param-no-crash.cpp b/clang/test/Sema/LifetimeSafety/explicit-object-param-no-crash.cpp new file mode 100644 index 0000000000000..56615437ab1ae --- /dev/null +++ b/clang/test/Sema/LifetimeSafety/explicit-object-param-no-crash.cpp @@ -0,0 +1,18 @@ +// RUN: %clang_cc1 %s -std=c++23 -verify -fsyntax-only -Wlifetime-safety + +// expected-no-diagnostics + +// Explicit object member functions must not be treated as having an implicit +// object argument. +struct Foo { + template <typename T> + int get(this Foo &&self, T) { + return self.field; + } + + int field; +}; + +void call() { + Foo().get(0); +} >From 549b8ce565201f3839f43de668de0a3982251c87 Mon Sep 17 00:00:00 2001 From: arhwx <[email protected]> Date: Mon, 27 Jul 2026 01:15:03 -0400 Subject: [PATCH 2/4] [LifetimeSafety] Add explicit object parameter test coverage --- .../explicit-object-param-no-crash.cpp | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/clang/test/Sema/LifetimeSafety/explicit-object-param-no-crash.cpp b/clang/test/Sema/LifetimeSafety/explicit-object-param-no-crash.cpp index 56615437ab1ae..159c342f15527 100644 --- a/clang/test/Sema/LifetimeSafety/explicit-object-param-no-crash.cpp +++ b/clang/test/Sema/LifetimeSafety/explicit-object-param-no-crash.cpp @@ -13,6 +13,38 @@ struct Foo { int field; }; +struct Base {}; +struct Derived : Base { + int base(this Base &&, int); +}; + +template <typename T> +struct Crtp { + template <typename Self> + int deduced(this Self &&self, T) { + return self.field; + } +}; + +struct Widget : Crtp<int> { + int field; +}; + +struct NoMove { + NoMove() = default; + NoMove(NoMove &&) = delete; + NoMove(const NoMove &) = delete; + + int get(this NoMove &&self, int) { + return self.field; + } + + int field; +}; + void call() { Foo().get(0); + Derived().base(0); + Widget().deduced(0); + NoMove().get(0); } >From 3cb27ed309f13003df61507d29d8095bb1f2012c Mon Sep 17 00:00:00 2001 From: arhwx <[email protected]> Date: Mon, 27 Jul 2026 03:50:43 -0400 Subject: [PATCH 3/4] [LifetimeSafety] Avoid treating explicit object parameter as moved --- .../LifetimeSafety/FactsGenerator.cpp | 2 ++ .../explicit-object-param-no-crash.cpp | 27 ++++++++++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp index e858e0b54688b..4b045731e92a8 100644 --- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp +++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp @@ -930,6 +930,8 @@ void FactsGenerator::handleMovedArgsInCall(const FunctionDecl *FD, for (unsigned I = IsInstance; I < Args.size() && I < FD->getNumParams() + IsInstance; ++I) { const ParmVarDecl *PVD = FD->getParamDecl(I - IsInstance); + if (PVD->isExplicitObjectParameter()) + continue; if (!PVD->getType()->isRValueReferenceType()) continue; // Skip lifetime annotated r-value reference parameters. Lifetime annotation diff --git a/clang/test/Sema/LifetimeSafety/explicit-object-param-no-crash.cpp b/clang/test/Sema/LifetimeSafety/explicit-object-param-no-crash.cpp index 159c342f15527..56c170e188af7 100644 --- a/clang/test/Sema/LifetimeSafety/explicit-object-param-no-crash.cpp +++ b/clang/test/Sema/LifetimeSafety/explicit-object-param-no-crash.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 %s -std=c++23 -verify -fsyntax-only -Wlifetime-safety +// RUN: %clang_cc1 %s -std=c++23 -verify -fsyntax-only -Wlifetime-safety -Wlifetime-safety-lifetimebound-violation // expected-no-diagnostics @@ -42,9 +42,34 @@ struct NoMove { int field; }; +template <typename T> +struct SharedPtr { + SharedPtr() = default; + SharedPtr(SharedPtr &&o) : p(o.p) { o.p = nullptr; } + SharedPtr(const SharedPtr &) = default; + + int get(this SharedPtr &&self, int) { + return self.p->field; + } + + T *p = nullptr; +}; + void call() { Foo().get(0); Derived().base(0); Widget().deduced(0); NoMove().get(0); + SharedPtr<NoMove>().get(0); +} + +struct Holder { + int field; + const int *borrow() [[clang::lifetimebound]] { return &field; } + void consume(this Holder &&self) {} +}; + +const int *object_arg_is_not_moved(Holder &&h [[clang::lifetimebound]]) { + static_cast<Holder &&>(h).consume(); + return h.borrow(); } >From 600ea9d7d6136533258201b952a1cd32b456eda7 Mon Sep 17 00:00:00 2001 From: arhwx <[email protected]> Date: Mon, 27 Jul 2026 05:58:56 -0400 Subject: [PATCH 4/4] [LifetimeSafety] Address review comments --- .../Analysis/LifetimeSafety/FactsGenerator.cpp | 15 +++++++++------ .../explicit-object-param-no-crash.cpp | 14 +++++++++++++- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp index 4b045731e92a8..d32596388553e 100644 --- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp +++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp @@ -909,11 +909,11 @@ void FactsGenerator::handleGSLPointerConstruction(const CXXConstructExpr *CCE) { void FactsGenerator::handleMovedArgsInCall(const FunctionDecl *FD, ArrayRef<const Expr *> Args) { - unsigned IsInstance = 0; + unsigned ImplicitObjectArgOffset = 0; if (const auto *MD = dyn_cast<CXXMethodDecl>(FD); MD && !isa<CXXConstructorDecl>(FD) && MD->isImplicitObjectMemberFunction()) { - IsInstance = 1; + ImplicitObjectArgOffset = 1; // std::unique_ptr::release() transfers ownership. // Treat it as a move to prevent false-positive warnings when the unique_ptr // destructor runs after ownership has been transferred. @@ -926,10 +926,13 @@ void FactsGenerator::handleMovedArgsInCall(const FunctionDecl *FD, } } - // Skip 'this' arg as it cannot be moved. - for (unsigned I = IsInstance; - I < Args.size() && I < FD->getNumParams() + IsInstance; ++I) { - const ParmVarDecl *PVD = FD->getParamDecl(I - IsInstance); + // Skip implicit 'this' arg as it cannot be moved. + for (unsigned I = ImplicitObjectArgOffset; + I < Args.size() && I < FD->getNumParams() + ImplicitObjectArgOffset; + ++I) { + const ParmVarDecl *PVD = FD->getParamDecl(I - ImplicitObjectArgOffset); + // In principle, explicit object parameters can be moved, but skip marking + // them as moved for consistency with implicit 'this'. if (PVD->isExplicitObjectParameter()) continue; if (!PVD->getType()->isRValueReferenceType()) diff --git a/clang/test/Sema/LifetimeSafety/explicit-object-param-no-crash.cpp b/clang/test/Sema/LifetimeSafety/explicit-object-param-no-crash.cpp index 56c170e188af7..71c86b9f793fb 100644 --- a/clang/test/Sema/LifetimeSafety/explicit-object-param-no-crash.cpp +++ b/clang/test/Sema/LifetimeSafety/explicit-object-param-no-crash.cpp @@ -1,6 +1,6 @@ // RUN: %clang_cc1 %s -std=c++23 -verify -fsyntax-only -Wlifetime-safety -Wlifetime-safety-lifetimebound-violation -// expected-no-diagnostics +#include "Inputs/lifetime-analysis.h" // Explicit object member functions must not be treated as having an implicit // object argument. @@ -73,3 +73,15 @@ const int *object_arg_is_not_moved(Holder &&h [[clang::lifetimebound]]) { static_cast<Holder &&>(h).consume(); return h.borrow(); } + +const int *t1(Holder h) { + const int *ptr = h.borrow(); // expected-warning {{stack memory associated with parameter 'h' is returned}} + static_cast<Holder &&>(h).consume(); + return ptr; // expected-note {{returned here}} +} + +const int *t2(Holder h) { + const int *ptr = h.borrow(); // expected-warning {{stack memory associated with parameter 'h' is returned}} + std::move(h).consume(); + return ptr; // expected-note {{returned here}} +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
