Skip to content

Commit 6e720df

Browse files
authored
[clang] Improve the lifetime_capture_by diagnostic on the constructor. (llvm#117792)
With this change, the lifetime_capture_by code path will not handle the constructor decl to avoid bogus diagnostics (see the testcase). Instead, we reuse the lifetimebound code as the lifetime_capture_by(this) has the same semantic as lifetimebound in constructor. The downside is that the lifetimebound diagnostic is reused for the capture case (I think it is not a big issue). Fixes llvm#117680
1 parent 32ef417 commit 6e720df

File tree

4 files changed

+54
-0
lines changed

4 files changed

+54
-0
lines changed

clang/include/clang/Basic/AttrDocs.td

+2
Original file line numberDiff line numberDiff line change
@@ -3985,6 +3985,8 @@ The capturing entity ``X`` can be one of the following:
39853985
std::set<std::string_view> s;
39863986
};
39873987

3988+
Note: When applied to a constructor parameter, `[[clang::lifetime_capture_by(this)]]` is just an alias of `[[clang::lifetimebound]]`.
3989+
39883990
- `global`, `unknown`.
39893991

39903992
.. code-block:: c++

clang/lib/Sema/CheckExprLifetime.cpp

+20
Original file line numberDiff line numberDiff line change
@@ -623,6 +623,26 @@ static void visitFunctionCallArguments(IndirectLocalPath &Path, Expr *Call,
623623
}
624624
if (CheckCoroCall || Callee->getParamDecl(I)->hasAttr<LifetimeBoundAttr>())
625625
VisitLifetimeBoundArg(Callee->getParamDecl(I), Arg);
626+
else if (const auto *CaptureAttr =
627+
Callee->getParamDecl(I)->getAttr<LifetimeCaptureByAttr>();
628+
CaptureAttr && isa<CXXConstructorDecl>(Callee) &&
629+
llvm::any_of(CaptureAttr->params(), [](int ArgIdx) {
630+
return ArgIdx == LifetimeCaptureByAttr::THIS;
631+
}))
632+
// `lifetime_capture_by(this)` in a class constructor has the same
633+
// semantics as `lifetimebound`:
634+
//
635+
// struct Foo {
636+
// const int& a;
637+
// // Equivalent to Foo(const int& t [[clang::lifetimebound]])
638+
// Foo(const int& t [[clang::lifetime_capture_by(this)]]) : a(t) {}
639+
// };
640+
//
641+
// In the implementation, `lifetime_capture_by` is treated as an alias for
642+
// `lifetimebound` and shares the same code path. This implies the emitted
643+
// diagnostics will be emitted under `-Wdangling`, not
644+
// `-Wdangling-capture`.
645+
VisitLifetimeBoundArg(Callee->getParamDecl(I), Arg);
626646
else if (EnableGSLAnalysis && I == 0) {
627647
// Perform GSL analysis for the first argument
628648
if (shouldTrackFirstArgument(Callee)) {

clang/lib/Sema/SemaChecking.cpp

+6
Original file line numberDiff line numberDiff line change
@@ -3240,8 +3240,14 @@ void Sema::checkLifetimeCaptureBy(FunctionDecl *FD, bool IsMemberFunction,
32403240
unsigned ArgIdx) {
32413241
if (!Attr)
32423242
return;
3243+
32433244
Expr *Captured = const_cast<Expr *>(GetArgAt(ArgIdx));
32443245
for (int CapturingParamIdx : Attr->params()) {
3246+
// lifetime_capture_by(this) case is handled in the lifetimebound expr
3247+
// initialization codepath.
3248+
if (CapturingParamIdx == LifetimeCaptureByAttr::THIS &&
3249+
isa<CXXConstructorDecl>(FD))
3250+
continue;
32453251
Expr *Capturing = const_cast<Expr *>(GetArgAt(CapturingParamIdx));
32463252
CapturingEntity CE{Capturing};
32473253
// Ensure that 'Captured' outlives the 'Capturing' entity.

clang/test/Sema/warn-lifetime-analysis-capture-by.cpp

+26
Original file line numberDiff line numberDiff line change
@@ -425,3 +425,29 @@ void use() {
425425
}
426426
} // namespace with_span
427427
} // namespace inferred_capture_by
428+
429+
namespace on_constructor {
430+
struct T {
431+
T(const int& t [[clang::lifetime_capture_by(this)]]);
432+
};
433+
struct T2 {
434+
T2(const int& t [[clang::lifetime_capture_by(x)]], int& x);
435+
};
436+
struct T3 {
437+
T3(const T& t [[clang::lifetime_capture_by(this)]]);
438+
};
439+
440+
int foo(const T& t);
441+
int bar(const T& t[[clang::lifetimebound]]);
442+
443+
void test() {
444+
auto x = foo(T(1)); // OK. no diagnosic
445+
T(1); // OK. no diagnostic
446+
T t(1); // expected-warning {{temporary whose address is used}}
447+
auto y = bar(T(1)); // expected-warning {{temporary whose address is used}}
448+
T3 t3(T(1)); // expected-warning {{temporary whose address is used}}
449+
450+
int a;
451+
T2(1, a); // expected-warning {{object whose reference is captured by}}
452+
}
453+
} // namespace on_constructor

0 commit comments

Comments
 (0)