Skip to content

Commit edadbdc

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 c014db4 commit edadbdc

File tree

3 files changed

+35
-19
lines changed

3 files changed

+35
-19
lines changed

clang/lib/Sema/CheckExprLifetime.cpp

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -462,14 +462,16 @@ static void visitFunctionCallArguments(IndirectLocalPath &Path, Expr *Call,
462462
}
463463
}
464464

465-
for (unsigned I = 0,
466-
N = std::min<unsigned>(Callee->getNumParams(), Args.size());
467-
I != N; ++I) {
468-
if (CheckCoroCall || Callee->getParamDecl(I)->hasAttr<LifetimeBoundAttr>())
469-
VisitLifetimeBoundArg(Callee->getParamDecl(I), Args[I]);
465+
const FunctionDecl *CanonCallee = Callee->getCanonicalDecl();
466+
const unsigned int NP =
467+
std::min(Callee->getNumParams(), CanonCallee->getNumParams());
468+
for (unsigned I = 0, N = std::min<unsigned>(NP, Args.size()); I != N; ++I) {
469+
if (CheckCoroCall ||
470+
CanonCallee->getParamDecl(I)->hasAttr<LifetimeBoundAttr>())
471+
VisitLifetimeBoundArg(CanonCallee->getParamDecl(I), Args[I]);
470472
else if (EnableGSLAnalysis && I == 0) {
471-
if (shouldTrackFirstArgument(Callee)) {
472-
VisitGSLPointerArg(Callee, Args[0]);
473+
if (shouldTrackFirstArgument(CanonCallee)) {
474+
VisitGSLPointerArg(CanonCallee, Args[0]);
473475
} else if (auto *CCE = dyn_cast<CXXConstructExpr>(Call);
474476
CCE &&
475477
CCE->getConstructor()->getParent()->hasAttr<PointerAttr>()) {

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+
const unsigned int 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+
const unsigned int NP = std::min(NumParams, CanonDecl->getNumParams());
271+
for (unsigned int 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: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ 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 [[clang::lifetimebound]]); // Add LB
24+
const int &crefparam(const int &param) { return param; } // Omit in impl
2225
int &refparam(int &param [[clang::lifetimebound]]);
2326
int &classparam(IntRef param [[clang::lifetimebound]]);
2427

@@ -41,6 +44,7 @@ namespace usage_ok {
4144
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}}
4245
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}}
4346
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}}
47+
const int& s = crefparam(2); // expected-warning {{temporary bound to local reference 's' will be destroyed at the end of the full-expression}}
4448

4549
void test_assignment() {
4650
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)