Skip to content

Commit 4cc9bf1

Browse files
Propagate lifetimebound from formal parameters to those in the canonical declaration and use that for analysis (#107627)
This partially fixes #62072 by making sure that re-declarations of a function do not have the effect of removing lifetimebound from the canonical declaration. It doesn't handle the implicit 'this' parameter, but that can be addressed in a separate fix.
1 parent 6d25345 commit 4cc9bf1

File tree

3 files changed

+79
-31
lines changed

3 files changed

+79
-31
lines changed

clang/lib/Sema/CheckExprLifetime.cpp

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -525,7 +525,20 @@ static bool isNormalAssignmentOperator(const FunctionDecl *FD) {
525525
return false;
526526
}
527527

528+
static const FunctionDecl *
529+
getDeclWithMergedLifetimeBoundAttrs(const FunctionDecl *FD) {
530+
return FD != nullptr ? FD->getMostRecentDecl() : nullptr;
531+
}
532+
533+
static const CXXMethodDecl *
534+
getDeclWithMergedLifetimeBoundAttrs(const CXXMethodDecl *CMD) {
535+
const FunctionDecl *FD = CMD;
536+
return cast_if_present<CXXMethodDecl>(
537+
getDeclWithMergedLifetimeBoundAttrs(FD));
538+
}
539+
528540
bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD) {
541+
FD = getDeclWithMergedLifetimeBoundAttrs(FD);
529542
const TypeSourceInfo *TSI = FD->getTypeSourceInfo();
530543
if (!TSI)
531544
return false;
@@ -647,21 +660,22 @@ static void visitFunctionCallArguments(IndirectLocalPath &Path, Expr *Call,
647660
}
648661
}
649662

650-
for (unsigned I = 0,
651-
N = std::min<unsigned>(Callee->getNumParams(), Args.size());
652-
I != N; ++I) {
663+
const FunctionDecl *CanonCallee = getDeclWithMergedLifetimeBoundAttrs(Callee);
664+
unsigned NP = std::min(Callee->getNumParams(), CanonCallee->getNumParams());
665+
for (unsigned I = 0, N = std::min<unsigned>(NP, Args.size()); I != N; ++I) {
653666
Expr *Arg = Args[I];
654667
RevertToOldSizeRAII RAII(Path);
655668
if (auto *DAE = dyn_cast<CXXDefaultArgExpr>(Arg)) {
656669
Path.push_back(
657670
{IndirectLocalPathEntry::DefaultArg, DAE, DAE->getParam()});
658671
Arg = DAE->getExpr();
659672
}
660-
if (CheckCoroCall || Callee->getParamDecl(I)->hasAttr<LifetimeBoundAttr>())
661-
VisitLifetimeBoundArg(Callee->getParamDecl(I), Arg);
673+
if (CheckCoroCall ||
674+
CanonCallee->getParamDecl(I)->hasAttr<LifetimeBoundAttr>())
675+
VisitLifetimeBoundArg(CanonCallee->getParamDecl(I), Arg);
662676
else if (const auto *CaptureAttr =
663-
Callee->getParamDecl(I)->getAttr<LifetimeCaptureByAttr>();
664-
CaptureAttr && isa<CXXConstructorDecl>(Callee) &&
677+
CanonCallee->getParamDecl(I)->getAttr<LifetimeCaptureByAttr>();
678+
CaptureAttr && isa<CXXConstructorDecl>(CanonCallee) &&
665679
llvm::any_of(CaptureAttr->params(), [](int ArgIdx) {
666680
return ArgIdx == LifetimeCaptureByAttr::THIS;
667681
}))
@@ -678,11 +692,11 @@ static void visitFunctionCallArguments(IndirectLocalPath &Path, Expr *Call,
678692
// `lifetimebound` and shares the same code path. This implies the emitted
679693
// diagnostics will be emitted under `-Wdangling`, not
680694
// `-Wdangling-capture`.
681-
VisitLifetimeBoundArg(Callee->getParamDecl(I), Arg);
695+
VisitLifetimeBoundArg(CanonCallee->getParamDecl(I), Arg);
682696
else if (EnableGSLAnalysis && I == 0) {
683697
// Perform GSL analysis for the first argument
684-
if (shouldTrackFirstArgument(Callee)) {
685-
VisitGSLPointerArg(Callee, Arg);
698+
if (shouldTrackFirstArgument(CanonCallee)) {
699+
VisitGSLPointerArg(CanonCallee, Arg);
686700
} else if (auto *Ctor = dyn_cast<CXXConstructExpr>(Call);
687701
Ctor && shouldTrackFirstArgumentForConstructor(Ctor)) {
688702
VisitGSLPointerArg(Ctor->getConstructor(), Arg);
@@ -1245,7 +1259,8 @@ static AnalysisResult analyzePathForGSLPointer(const IndirectLocalPath &Path,
12451259
return Report;
12461260
}
12471261

1248-
static bool isAssignmentOperatorLifetimeBound(CXXMethodDecl *CMD) {
1262+
static bool isAssignmentOperatorLifetimeBound(const CXXMethodDecl *CMD) {
1263+
CMD = getDeclWithMergedLifetimeBoundAttrs(CMD);
12491264
return CMD && isNormalAssignmentOperator(CMD) && CMD->param_size() == 1 &&
12501265
CMD->getParamDecl(0)->hasAttr<LifetimeBoundAttr>();
12511266
}

clang/lib/Sema/SemaDecl.cpp

Lines changed: 48 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3239,6 +3239,42 @@ void Sema::mergeDeclAttributes(NamedDecl *New, Decl *Old,
32393239
if (!foundAny) New->dropAttrs();
32403240
}
32413241

3242+
// Returns the number of added attributes.
3243+
template <class T>
3244+
static unsigned propagateAttribute(ParmVarDecl *To, const ParmVarDecl *From,
3245+
Sema &S) {
3246+
unsigned found = 0;
3247+
for (const auto *I : From->specific_attrs<T>()) {
3248+
if (!DeclHasAttr(To, I)) {
3249+
T *newAttr = cast<T>(I->clone(S.Context));
3250+
newAttr->setInherited(true);
3251+
To->addAttr(newAttr);
3252+
++found;
3253+
}
3254+
}
3255+
return found;
3256+
}
3257+
3258+
template <class F>
3259+
static void propagateAttributes(ParmVarDecl *To, const ParmVarDecl *From,
3260+
F &&propagator) {
3261+
if (!From->hasAttrs()) {
3262+
return;
3263+
}
3264+
3265+
bool foundAny = To->hasAttrs();
3266+
3267+
// Ensure that any moving of objects within the allocated map is
3268+
// done before we process them.
3269+
if (!foundAny)
3270+
To->setAttrs(AttrVec());
3271+
3272+
foundAny |= std::forward<F>(propagator)(To, From) != 0;
3273+
3274+
if (!foundAny)
3275+
To->dropAttrs();
3276+
}
3277+
32423278
/// mergeParamDeclAttributes - Copy attributes from the old parameter
32433279
/// to the new one.
32443280
static void mergeParamDeclAttributes(ParmVarDecl *newDecl,
@@ -3262,26 +3298,17 @@ static void mergeParamDeclAttributes(ParmVarDecl *newDecl,
32623298
diag::note_carries_dependency_missing_first_decl) << 1/*Param*/;
32633299
}
32643300

3265-
if (!oldDecl->hasAttrs())
3266-
return;
3267-
3268-
bool foundAny = newDecl->hasAttrs();
3269-
3270-
// Ensure that any moving of objects within the allocated map is
3271-
// done before we process them.
3272-
if (!foundAny) newDecl->setAttrs(AttrVec());
3273-
3274-
for (const auto *I : oldDecl->specific_attrs<InheritableParamAttr>()) {
3275-
if (!DeclHasAttr(newDecl, I)) {
3276-
InheritableAttr *newAttr =
3277-
cast<InheritableParamAttr>(I->clone(S.Context));
3278-
newAttr->setInherited(true);
3279-
newDecl->addAttr(newAttr);
3280-
foundAny = true;
3281-
}
3282-
}
3283-
3284-
if (!foundAny) newDecl->dropAttrs();
3301+
propagateAttributes(
3302+
newDecl, oldDecl, [&S](ParmVarDecl *To, const ParmVarDecl *From) {
3303+
unsigned found = 0;
3304+
found += propagateAttribute<InheritableParamAttr>(To, From, S);
3305+
// Propagate the lifetimebound attribute from parameters to the
3306+
// most recent declaration. Note that this doesn't include the implicit
3307+
// 'this' parameter, as the attribute is applied to the function type in
3308+
// that case.
3309+
found += propagateAttribute<LifetimeBoundAttr>(To, From, S);
3310+
return found;
3311+
});
32853312
}
32863313

32873314
static bool EquivalentArrayTypes(QualType Old, QualType New,
@@ -6960,6 +6987,7 @@ static void checkInheritableAttr(Sema &S, NamedDecl &ND) {
69606987
static void checkLifetimeBoundAttr(Sema &S, NamedDecl &ND) {
69616988
// Check the attributes on the function type and function params, if any.
69626989
if (const auto *FD = dyn_cast<FunctionDecl>(&ND)) {
6990+
FD = FD->getMostRecentDecl();
69636991
// Don't declare this variable in the second operand of the for-statement;
69646992
// GCC miscompiles that by ending its lifetime before evaluating the
69656993
// third operand. See gcc.gnu.org/PR86769.

clang/test/SemaCXX/attr-lifetimebound.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ namespace usage_invalid {
3333
namespace usage_ok {
3434
struct IntRef { int *target; };
3535

36+
const int &crefparam(const int &param); // Omitted in first decl
37+
const int &crefparam(const int &param); // Omitted in second decl
38+
const int &crefparam(const int &param [[clang::lifetimebound]]); // Add LB
39+
const int &crefparam(const int &param) { return param; } // Omit in impl
3640
int &refparam(int &param [[clang::lifetimebound]]);
3741
int &classparam(IntRef param [[clang::lifetimebound]]);
3842

@@ -62,6 +66,7 @@ namespace usage_ok {
6266
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}}
6367
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}}
6468
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}}
69+
const int& s = crefparam(2); // expected-warning {{temporary bound to local reference 's' will be destroyed at the end of the full-expression}}
6570

6671
void test_assignment() {
6772
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)