Skip to content

Commit 4dfad6c

Browse files
Propagate lifetimebound from formal parameters to those in the canonical declaration, then use the canonical declaration for analysis
Note that this doesn't handle the implicit 'this' parameter; that can be addressed in a separate commit.
1 parent 998bdae commit 4dfad6c

File tree

3 files changed

+37
-22
lines changed

3 files changed

+37
-22
lines changed

clang/lib/Sema/CheckExprLifetime.cpp

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -645,21 +645,21 @@ static void visitFunctionCallArguments(IndirectLocalPath &Path, Expr *Call,
645645
}
646646
}
647647

648-
for (unsigned I = 0,
649-
N = std::min<unsigned>(Callee->getNumParams(), Args.size());
650-
I != N; ++I) {
648+
const FunctionDecl *CanonCallee = Callee->getCanonicalDecl();
649+
unsigned NP = std::min(Callee->getNumParams(), CanonCallee->getNumParams());
650+
for (unsigned I = 0, N = std::min<unsigned>(NP, Args.size()); I != N; ++I) {
651651
Expr *Arg = Args[I];
652652
RevertToOldSizeRAII RAII(Path);
653653
if (auto *DAE = dyn_cast<CXXDefaultArgExpr>(Arg)) {
654654
Path.push_back(
655655
{IndirectLocalPathEntry::DefaultArg, DAE, DAE->getParam()});
656656
Arg = DAE->getExpr();
657657
}
658-
if (CheckCoroCall || Callee->getParamDecl(I)->hasAttr<LifetimeBoundAttr>())
659-
VisitLifetimeBoundArg(Callee->getParamDecl(I), Arg);
658+
if (CheckCoroCall || CanonCallee->getParamDecl(I)->hasAttr<LifetimeBoundAttr>())
659+
VisitLifetimeBoundArg(CanonCallee->getParamDecl(I), Arg);
660660
else if (const auto *CaptureAttr =
661-
Callee->getParamDecl(I)->getAttr<LifetimeCaptureByAttr>();
662-
CaptureAttr && isa<CXXConstructorDecl>(Callee) &&
661+
CanonCallee->getParamDecl(I)->getAttr<LifetimeCaptureByAttr>();
662+
CaptureAttr && isa<CXXConstructorDecl>(CanonCallee) &&
663663
llvm::any_of(CaptureAttr->params(), [](int ArgIdx) {
664664
return ArgIdx == LifetimeCaptureByAttr::THIS;
665665
}))
@@ -676,11 +676,11 @@ static void visitFunctionCallArguments(IndirectLocalPath &Path, Expr *Call,
676676
// `lifetimebound` and shares the same code path. This implies the emitted
677677
// diagnostics will be emitted under `-Wdangling`, not
678678
// `-Wdangling-capture`.
679-
VisitLifetimeBoundArg(Callee->getParamDecl(I), Arg);
679+
VisitLifetimeBoundArg(CanonCallee->getParamDecl(I), Arg);
680680
else if (EnableGSLAnalysis && I == 0) {
681681
// Perform GSL analysis for the first argument
682-
if (shouldTrackFirstArgument(Callee)) {
683-
VisitGSLPointerArg(Callee, Arg);
682+
if (shouldTrackFirstArgument(CanonCallee)) {
683+
VisitGSLPointerArg(CanonCallee, Arg);
684684
} else if (auto *Ctor = dyn_cast<CXXConstructExpr>(Call);
685685
Ctor && shouldTrackFirstArgumentForConstructor(Ctor)) {
686686
VisitGSLPointerArg(Ctor->getConstructor(), Arg);

clang/lib/Sema/SemaAttr.cpp

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,8 @@ void Sema::inferGslOwnerPointerAttribute(CXXRecordDecl *Record) {
217217
}
218218

219219
void Sema::inferLifetimeBoundAttribute(FunctionDecl *FD) {
220-
if (FD->getNumParams() == 0)
220+
unsigned NumParams = FD->getNumParams();
221+
if (NumParams == 0)
221222
return;
222223

223224
if (unsigned BuiltinID = FD->getBuiltinID()) {
@@ -239,18 +240,13 @@ void Sema::inferLifetimeBoundAttribute(FunctionDecl *FD) {
239240
default:
240241
break;
241242
}
242-
return;
243-
}
244-
if (auto *CMD = dyn_cast<CXXMethodDecl>(FD)) {
245-
const auto *CRD = CMD->getParent();
246-
if (!CRD->isInStdNamespace() || !CRD->getIdentifier())
247-
return;
248-
249-
if (isa<CXXConstructorDecl>(CMD)) {
243+
} else if (auto *CMD = dyn_cast<CXXMethodDecl>(FD)) {
244+
const CXXRecordDecl *CRD = CMD->getParent();
245+
if (CRD->isInStdNamespace() && CRD->getIdentifier() &&
246+
isa<CXXConstructorDecl>(CMD)) {
250247
auto *Param = CMD->getParamDecl(0);
251-
if (Param->hasAttr<LifetimeBoundAttr>())
252-
return;
253-
if (CRD->getName() == "basic_string_view" &&
248+
if (!Param->hasAttr<LifetimeBoundAttr>() &&
249+
CRD->getName() == "basic_string_view" &&
254250
Param->getType()->isPointerType()) {
255251
// construct from a char array pointed by a pointer.
256252
// basic_string_view(const CharT* s);
@@ -266,6 +262,20 @@ void Sema::inferLifetimeBoundAttribute(FunctionDecl *FD) {
266262
LifetimeBoundAttr::CreateImplicit(Context, FD->getLocation()));
267263
}
268264
}
265+
} else if (auto *CanonDecl = FD->getCanonicalDecl(); FD != CanonDecl) {
266+
// Propagate the lifetimebound attribute from parameters to the canonical
267+
// declaration.
268+
// Note that this doesn't include the implicit 'this' parameter, as the
269+
// attribute is applied to the function type in that case.
270+
unsigned NP = std::min(NumParams, CanonDecl->getNumParams());
271+
for (unsigned I = 0; I < NP; ++I) {
272+
auto *CanonParam = CanonDecl->getParamDecl(I);
273+
if (!CanonParam->hasAttr<LifetimeBoundAttr>() &&
274+
FD->getParamDecl(I)->hasAttr<LifetimeBoundAttr>()) {
275+
CanonParam->addAttr(LifetimeBoundAttr::CreateImplicit(
276+
Context, CanonParam->getLocation()));
277+
}
278+
}
269279
}
270280
}
271281

clang/test/SemaCXX/attr-lifetimebound.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ namespace usage_invalid {
1919
namespace usage_ok {
2020
struct IntRef { int *target; };
2121

22+
const int &crefparam(const int &param); // Omitted in first decl
23+
const int &crefparam(const int &param); // Omitted in second decl
24+
const int &crefparam(const int &param [[clang::lifetimebound]]); // Add LB
25+
const int &crefparam(const int &param) { return param; } // Omit in impl
2226
int &refparam(int &param [[clang::lifetimebound]]);
2327
int &classparam(IntRef param [[clang::lifetimebound]]);
2428

@@ -48,6 +52,7 @@ namespace usage_ok {
4852
int *p = A().class_member(); // expected-warning {{temporary whose address is used as value of local variable 'p' will be destroyed at the end of the full-expression}}
4953
int *q = A(); // expected-warning {{temporary whose address is used as value of local variable 'q' will be destroyed at the end of the full-expression}}
5054
int *r = A(1); // expected-warning {{temporary whose address is used as value of local variable 'r' will be destroyed at the end of the full-expression}}
55+
const int& s = crefparam(2); // expected-warning {{temporary bound to local reference 's' will be destroyed at the end of the full-expression}}
5156

5257
void test_assignment() {
5358
p = A().class_member(); // expected-warning {{object backing the pointer p will be destroyed at the end of the full-expression}}

0 commit comments

Comments
 (0)