diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td index 6fb2eb3eb3e66..6a31c9a3f31c4 100644 --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -3976,6 +3976,8 @@ The capturing entity ``X`` can be one of the following: std::set s; }; + Note: When applied to a constructor parameter, `[[clang::lifetime_capture_by(this)]]` is just an alias of `[[clang::lifetimebound]]`. + - `global`, `unknown`. .. code-block:: c++ diff --git a/clang/lib/Sema/CheckExprLifetime.cpp b/clang/lib/Sema/CheckExprLifetime.cpp index 6cdd4dc629e50..607b7daf878e1 100644 --- a/clang/lib/Sema/CheckExprLifetime.cpp +++ b/clang/lib/Sema/CheckExprLifetime.cpp @@ -623,6 +623,26 @@ static void visitFunctionCallArguments(IndirectLocalPath &Path, Expr *Call, } if (CheckCoroCall || Callee->getParamDecl(I)->hasAttr()) VisitLifetimeBoundArg(Callee->getParamDecl(I), Arg); + else if (const auto *CaptureAttr = + Callee->getParamDecl(I)->getAttr(); + CaptureAttr && isa(Callee) && + llvm::any_of(CaptureAttr->params(), [](int ArgIdx) { + return ArgIdx == LifetimeCaptureByAttr::THIS; + })) + // `lifetime_capture_by(this)` in a class constructor has the same + // semantics as `lifetimebound`: + // + // struct Foo { + // const int& a; + // // Equivalent to Foo(const int& t [[clang::lifetimebound]]) + // Foo(const int& t [[clang::lifetime_capture_by(this)]]) : a(t) {} + // }; + // + // In the implementation, `lifetime_capture_by` is treated as an alias for + // `lifetimebound` and shares the same code path. This implies the emitted + // diagnostics will be emitted under `-Wdangling`, not + // `-Wdangling-capture`. + VisitLifetimeBoundArg(Callee->getParamDecl(I), Arg); else if (EnableGSLAnalysis && I == 0) { // Perform GSL analysis for the first argument if (shouldTrackFirstArgument(Callee)) { diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index a49605e486765..1605523097a6b 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -3240,8 +3240,14 @@ void Sema::checkLifetimeCaptureBy(FunctionDecl *FD, bool IsMemberFunction, unsigned ArgIdx) { if (!Attr) return; + Expr *Captured = const_cast(GetArgAt(ArgIdx)); for (int CapturingParamIdx : Attr->params()) { + // lifetime_capture_by(this) case is handled in the lifetimebound expr + // initialization codepath. + if (CapturingParamIdx == LifetimeCaptureByAttr::THIS && + isa(FD)) + continue; Expr *Capturing = const_cast(GetArgAt(CapturingParamIdx)); CapturingEntity CE{Capturing}; // Ensure that 'Captured' outlives the 'Capturing' entity. diff --git a/clang/test/Sema/warn-lifetime-analysis-capture-by.cpp b/clang/test/Sema/warn-lifetime-analysis-capture-by.cpp index 4d562bac1e305..3d3e33596a126 100644 --- a/clang/test/Sema/warn-lifetime-analysis-capture-by.cpp +++ b/clang/test/Sema/warn-lifetime-analysis-capture-by.cpp @@ -411,3 +411,29 @@ void use() { } } // namespace with_span } // namespace inferred_capture_by + +namespace on_constructor { +struct T { + T(const int& t [[clang::lifetime_capture_by(this)]]); +}; +struct T2 { + T2(const int& t [[clang::lifetime_capture_by(x)]], int& x); +}; +struct T3 { + T3(const T& t [[clang::lifetime_capture_by(this)]]); +}; + +int foo(const T& t); +int bar(const T& t[[clang::lifetimebound]]); + +void test() { + auto x = foo(T(1)); // OK. no diagnosic + T(1); // OK. no diagnostic + T t(1); // expected-warning {{temporary whose address is used}} + auto y = bar(T(1)); // expected-warning {{temporary whose address is used}} + T3 t3(T(1)); // expected-warning {{temporary whose address is used}} + + int a; + T2(1, a); // expected-warning {{object whose reference is captured by}} +} +} // namespace on_constructor