Skip to content

Commit dd47920

Browse files
Make [[clang::lifetimebound]] work for expressions coming from default arguments (#112047)
Fixes #68596.
1 parent 3b45120 commit dd47920

File tree

3 files changed

+58
-4
lines changed

3 files changed

+58
-4
lines changed

clang/include/clang/Basic/DiagnosticSemaKinds.td

+2
Original file line numberDiff line numberDiff line change
@@ -10124,6 +10124,8 @@ def note_lambda_capture_initializer : Note<
1012410124
" via initialization of lambda capture %0}1">;
1012510125
def note_init_with_default_member_initializer : Note<
1012610126
"initializing field %0 with default member initializer">;
10127+
def note_init_with_default_argument : Note<
10128+
"initializing parameter %0 with default argument">;
1012710129

1012810130
// Check for initializing a member variable with the address or a reference to
1012910131
// a constructor parameter.

clang/lib/Sema/CheckExprLifetime.cpp

+25-4
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ struct IndirectLocalPathEntry {
198198
GslReferenceInit,
199199
GslPointerInit,
200200
GslPointerAssignment,
201+
DefaultArg,
201202
} Kind;
202203
Expr *E;
203204
union {
@@ -609,15 +610,22 @@ static void visitFunctionCallArguments(IndirectLocalPath &Path, Expr *Call,
609610
for (unsigned I = 0,
610611
N = std::min<unsigned>(Callee->getNumParams(), Args.size());
611612
I != N; ++I) {
613+
Expr *Arg = Args[I];
614+
RevertToOldSizeRAII RAII(Path);
615+
if (auto *DAE = dyn_cast<CXXDefaultArgExpr>(Arg)) {
616+
Path.push_back(
617+
{IndirectLocalPathEntry::DefaultArg, DAE, DAE->getParam()});
618+
Arg = DAE->getExpr();
619+
}
612620
if (CheckCoroCall || Callee->getParamDecl(I)->hasAttr<LifetimeBoundAttr>())
613-
VisitLifetimeBoundArg(Callee->getParamDecl(I), Args[I]);
621+
VisitLifetimeBoundArg(Callee->getParamDecl(I), Arg);
614622
else if (EnableGSLAnalysis && I == 0) {
615623
// Perform GSL analysis for the first argument
616624
if (shouldTrackFirstArgument(Callee)) {
617-
VisitGSLPointerArg(Callee, Args[0]);
625+
VisitGSLPointerArg(Callee, Arg);
618626
} else if (auto *Ctor = dyn_cast<CXXConstructExpr>(Call);
619627
Ctor && shouldTrackFirstArgumentForConstructor(Ctor)) {
620-
VisitGSLPointerArg(Ctor->getConstructor(), Args[0]);
628+
VisitGSLPointerArg(Ctor->getConstructor(), Arg);
621629
}
622630
}
623631
}
@@ -1060,6 +1068,9 @@ static SourceRange nextPathEntryRange(const IndirectLocalPath &Path, unsigned I,
10601068
if (!Path[I].Capture->capturesVariable())
10611069
continue;
10621070
return Path[I].E->getSourceRange();
1071+
1072+
case IndirectLocalPathEntry::DefaultArg:
1073+
return cast<CXXDefaultArgExpr>(Path[I].E)->getUsedLocation();
10631074
}
10641075
}
10651076
return E->getSourceRange();
@@ -1370,7 +1381,7 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
13701381
break;
13711382
}
13721383

1373-
case IndirectLocalPathEntry::LambdaCaptureInit:
1384+
case IndirectLocalPathEntry::LambdaCaptureInit: {
13741385
if (!Elem.Capture->capturesVariable())
13751386
break;
13761387
// FIXME: We can't easily tell apart an init-capture from a nested
@@ -1383,6 +1394,16 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
13831394
<< nextPathEntryRange(Path, I + 1, L);
13841395
break;
13851396
}
1397+
1398+
case IndirectLocalPathEntry::DefaultArg: {
1399+
const auto *DAE = cast<CXXDefaultArgExpr>(Elem.E);
1400+
const ParmVarDecl *Param = DAE->getParam();
1401+
SemaRef.Diag(Param->getDefaultArgRange().getBegin(),
1402+
diag::note_init_with_default_argument)
1403+
<< Param << nextPathEntryRange(Path, I + 1, L);
1404+
break;
1405+
}
1406+
}
13861407
}
13871408

13881409
// We didn't lifetime-extend, so don't go any further; we don't need more

clang/test/SemaCXX/attr-lifetimebound.cpp

+31
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,37 @@ namespace std {
107107
using std::operator""s;
108108
using std::operator""sv;
109109

110+
namespace default_args {
111+
using IntArray = int[];
112+
const int *defaultparam1(const int &def1 [[clang::lifetimebound]] = 0); // #def1
113+
const int &defaultparam_array([[clang::lifetimebound]] const int *p = IntArray{1, 2, 3}); // #def2
114+
struct A {
115+
A(const char *, const int &def3 [[clang::lifetimebound]] = 0); // #def3
116+
};
117+
const int &defaultparam2(const int &def4 [[clang::lifetimebound]] = 0); // #def4
118+
const int &defaultparam3(const int &def5 [[clang::lifetimebound]] = defaultparam2(), const int &def6 [[clang::lifetimebound]] = 0); // #def5 #def6
119+
std::string_view defaultparam4(std::string_view s [[clang::lifetimebound]] = std::string()); // #def7
120+
121+
const int *test_default_args() {
122+
const int *c = defaultparam1(); // expected-warning {{temporary whose address is used as value of local variable 'c' will be destroyed at the end of the full-expression}} expected-note@#def1 {{initializing parameter 'def1' with default argument}}
123+
A a = A(""); // expected-warning {{temporary whose address is used as value of local variable 'a' will be destroyed at the end of the full-expression}} expected-note@#def3 {{initializing parameter 'def3' with default argument}}
124+
const int &s = defaultparam2(); // expected-warning {{temporary bound to local reference 's' will be destroyed at the end of the full-expression}} expected-note@#def4 {{initializing parameter 'def4' with default argument}}
125+
const int &t = defaultparam3(); // expected-warning {{temporary bound to local reference 't' will be destroyed at the end of the full-expression}} expected-note@#def4 {{initializing parameter 'def4' with default argument}} expected-note@#def5 {{initializing parameter 'def5' with default argument}} expected-warning {{temporary bound to local reference 't' will be destroyed at the end of the full-expression}} expected-note@#def6 {{initializing parameter 'def6' with default argument}}
126+
const int &u = defaultparam_array(); // expected-warning {{temporary bound to local reference 'u' will be destroyed at the end of the full-expression}} expected-note@#def2 {{initializing parameter 'p' with default argument}}
127+
int local;
128+
const int &v = defaultparam2(local); // no warning
129+
const int &w = defaultparam2(1); // expected-warning {{temporary bound to local reference 'w' will be destroyed at the end of the full-expression}}
130+
if (false) {
131+
return &defaultparam2(); // expected-warning {{returning address of local temporary object}}
132+
}
133+
if (false) {
134+
return &defaultparam2(0); // expected-warning {{returning address of local temporary object}} expected-note@#def4 {{initializing parameter 'def4' with default argument}}
135+
}
136+
std::string_view sv = defaultparam4(); // expected-warning {{temporary whose address is used as value of local variable 'sv' will be destroyed at the end of the full-expression}} expected-note@#def7 {{initializing parameter 's' with default argument}}
137+
return nullptr;
138+
}
139+
} // namespace default_args
140+
110141
namespace p0936r0_examples {
111142
std::string_view s = "foo"s; // expected-warning {{temporary}}
112143

0 commit comments

Comments
 (0)