Skip to content

Commit 0b0a37e

Browse files
authored
[clang] Lifetime of locals must end before musttail call (#109255)
The lifetimes of local variables and function parameters must end before the call to a [[clang::musttail]] function, instead of before the return, because we will not have a stack frame to hold them when doing the call. This documents this limitation, and adds diagnostics to warn about some code which is invalid because of it.
1 parent 02ee96e commit 0b0a37e

File tree

7 files changed

+84
-3
lines changed

7 files changed

+84
-3
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,11 @@ Attribute Changes in Clang
268268
- Introduced a new attribute ``[[clang::coro_await_elidable_argument]]`` on function parameters
269269
to propagate safe elide context to arguments if such function is also under a safe elide context.
270270

271+
- The documentation of the ``[[clang::musttail]]`` attribute was updated to
272+
note that the lifetimes of all local variables end before the call. This does
273+
not change the behaviour of the compiler, as this was true for previous
274+
versions.
275+
271276
Improvements to Clang's diagnostics
272277
-----------------------------------
273278

@@ -324,6 +329,10 @@ Improvements to Clang's diagnostics
324329

325330
- Don't emit bogus dangling diagnostics when ``[[gsl::Owner]]`` and `[[clang::lifetimebound]]` are used together (#GH108272).
326331

332+
- The ``-Wreturn-stack-address`` warning now also warns about addresses of
333+
local variables passed to function calls using the ``[[clang::musttail]]``
334+
attribute.
335+
327336
Improvements to Clang's time-trace
328337
----------------------------------
329338

clang/include/clang/Basic/AttrDocs.td

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -637,6 +637,12 @@ return value must be trivially destructible. The calling convention of the
637637
caller and callee must match, and they must not be variadic functions or have
638638
old style K&R C function declarations.
639639

640+
The lifetimes of all local variables and function parameters end immediately
641+
before the call to the function. This means that it is undefined behaviour to
642+
pass a pointer or reference to a local variable to the called function, which
643+
is not the case without the attribute. Clang will emit a warning in common
644+
cases where this happens.
645+
640646
``clang::musttail`` provides assurances that the tail call can be optimized on
641647
all targets, not just one.
642648
}];

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10101,11 +10101,15 @@ def err_lifetimebound_ctor_dtor : Error<
1010110101
// CHECK: returning address/reference of stack memory
1010210102
def warn_ret_stack_addr_ref : Warning<
1010310103
"%select{address of|reference to}0 stack memory associated with "
10104-
"%select{local variable|parameter|compound literal}2 %1 returned">,
10104+
"%select{local variable|parameter|compound literal}2 %1 "
10105+
"%select{returned|passed to musttail function}3">,
1010510106
InGroup<ReturnStackAddress>;
1010610107
def warn_ret_local_temp_addr_ref : Warning<
1010710108
"returning %select{address of|reference to}0 local temporary object">,
1010810109
InGroup<ReturnStackAddress>;
10110+
def warn_musttail_local_temp_addr_ref : Warning<
10111+
"passing %select{address of|reference to}0 local temporary object to musttail function">,
10112+
InGroup<ReturnStackAddress>;
1010910113
def err_ret_local_temp_ref : Error<
1011010114
"returning reference to local temporary object">;
1011110115
def warn_ret_addr_label : Warning<

clang/lib/Sema/CheckExprLifetime.cpp

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ enum LifetimeKind {
3333
/// the entity is a return object.
3434
LK_Return,
3535

36+
/// The lifetime of a temporary bound to this entity ends too soon, because
37+
/// the entity passed to a musttail function call.
38+
LK_MustTail,
39+
3640
/// The lifetime of a temporary bound to this entity ends too soon, because
3741
/// the entity is the result of a statement expression.
3842
LK_StmtExprResult,
@@ -1150,6 +1154,7 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
11501154
break;
11511155

11521156
case LK_Return:
1157+
case LK_MustTail:
11531158
case LK_StmtExprResult:
11541159
if (auto *DRE = dyn_cast<DeclRefExpr>(L)) {
11551160
// We can't determine if the local variable outlives the statement
@@ -1158,7 +1163,8 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
11581163
return false;
11591164
SemaRef.Diag(DiagLoc, diag::warn_ret_stack_addr_ref)
11601165
<< InitEntity->getType()->isReferenceType() << DRE->getDecl()
1161-
<< isa<ParmVarDecl>(DRE->getDecl()) << DiagRange;
1166+
<< isa<ParmVarDecl>(DRE->getDecl()) << (LK == LK_MustTail)
1167+
<< DiagRange;
11621168
} else if (isa<BlockExpr>(L)) {
11631169
SemaRef.Diag(DiagLoc, diag::err_ret_local_block) << DiagRange;
11641170
} else if (isa<AddrLabelExpr>(L)) {
@@ -1170,7 +1176,7 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
11701176
} else if (auto *CLE = dyn_cast<CompoundLiteralExpr>(L)) {
11711177
SemaRef.Diag(DiagLoc, diag::warn_ret_stack_addr_ref)
11721178
<< InitEntity->getType()->isReferenceType() << CLE->getInitializer()
1173-
<< 2 << DiagRange;
1179+
<< 2 << (LK == LK_MustTail) << DiagRange;
11741180
} else {
11751181
// P2748R5: Disallow Binding a Returned Glvalue to a Temporary.
11761182
// [stmt.return]/p6: In a function whose return type is a reference,
@@ -1181,6 +1187,9 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
11811187
InitEntity->getType()->isReferenceType())
11821188
SemaRef.Diag(DiagLoc, diag::err_ret_local_temp_ref)
11831189
<< InitEntity->getType()->isReferenceType() << DiagRange;
1190+
else if (LK == LK_MustTail)
1191+
SemaRef.Diag(DiagLoc, diag::warn_musttail_local_temp_addr_ref)
1192+
<< InitEntity->getType()->isReferenceType() << DiagRange;
11841193
else
11851194
SemaRef.Diag(DiagLoc, diag::warn_ret_local_temp_addr_ref)
11861195
<< InitEntity->getType()->isReferenceType() << DiagRange;
@@ -1265,6 +1274,12 @@ void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity,
12651274
/*AEntity*/ nullptr, Init);
12661275
}
12671276

1277+
void checkExprLifetimeMustTailArg(Sema &SemaRef,
1278+
const InitializedEntity &Entity, Expr *Init) {
1279+
checkExprLifetimeImpl(SemaRef, &Entity, nullptr, LK_MustTail,
1280+
/*AEntity*/ nullptr, Init);
1281+
}
1282+
12681283
void checkExprLifetime(Sema &SemaRef, const AssignedEntity &Entity,
12691284
Expr *Init) {
12701285
bool EnableDanglingPointerAssignment = !SemaRef.getDiagnostics().isIgnored(

clang/lib/Sema/CheckExprLifetime.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity,
3535
/// sufficient for assigning to the entity.
3636
void checkExprLifetime(Sema &SemaRef, const AssignedEntity &Entity, Expr *Init);
3737

38+
/// Check that the lifetime of the given expr (and its subobjects) is
39+
/// sufficient, assuming that it is passed as an argument to a musttail
40+
/// function.
41+
void checkExprLifetimeMustTailArg(Sema &SemaRef,
42+
const InitializedEntity &Entity, Expr *Init);
43+
3844
} // namespace clang::sema
3945

4046
#endif // LLVM_CLANG_SEMA_CHECK_EXPR_LIFETIME_H

clang/lib/Sema/SemaStmt.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13+
#include "CheckExprLifetime.h"
1314
#include "clang/AST/ASTContext.h"
1415
#include "clang/AST/ASTDiagnostic.h"
1516
#include "clang/AST/ASTLambda.h"
@@ -889,6 +890,15 @@ bool Sema::checkMustTailAttr(const Stmt *St, const Attr &MTA) {
889890
return false;
890891
}
891892

893+
// The lifetimes of locals and incoming function parameters must end before
894+
// the call, because we can't have a stack frame to store them, so diagnose
895+
// any pointers or references to them passed into the musttail call.
896+
for (auto ArgExpr : CE->arguments()) {
897+
InitializedEntity Entity = InitializedEntity::InitializeParameter(
898+
Context, ArgExpr->getType(), false);
899+
checkExprLifetimeMustTailArg(*this, Entity, const_cast<Expr *>(ArgExpr));
900+
}
901+
892902
return true;
893903
}
894904

clang/test/SemaCXX/attr-musttail.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,3 +267,34 @@ namespace ns {}
267267
void TestCallNonValue() {
268268
[[clang::musttail]] return ns; // expected-error {{unexpected namespace name 'ns': expected expression}}
269269
}
270+
271+
// Test diagnostics for lifetimes of local variables, which end earlier for a
272+
// musttail call than for a nowmal one.
273+
274+
void TakesIntAndPtr(int, int *);
275+
void PassAddressOfLocal(int a, int *b) {
276+
int c;
277+
[[clang::musttail]] return TakesIntAndPtr(0, &c); // expected-warning {{address of stack memory associated with local variable 'c' passed to musttail function}}
278+
}
279+
void PassAddressOfParam(int a, int *b) {
280+
[[clang::musttail]] return TakesIntAndPtr(0, &a); // expected-warning {{address of stack memory associated with parameter 'a' passed to musttail function}}
281+
}
282+
void PassValues(int a, int *b) {
283+
[[clang::musttail]] return TakesIntAndPtr(a, b);
284+
}
285+
286+
void TakesIntAndRef(int, const int &);
287+
void PassRefOfLocal(int a, const int &b) {
288+
int c;
289+
[[clang::musttail]] return TakesIntAndRef(0, c); // expected-warning {{address of stack memory associated with local variable 'c' passed to musttail function}}
290+
}
291+
void PassRefOfParam(int a, const int &b) {
292+
[[clang::musttail]] return TakesIntAndRef(0, a); // expected-warning {{address of stack memory associated with parameter 'a' passed to musttail function}}
293+
}
294+
int ReturnInt();
295+
void PassRefOfTemporary(int a, const int &b) {
296+
[[clang::musttail]] return TakesIntAndRef(0, ReturnInt()); // expected-warning {{passing address of local temporary object to musttail function}}
297+
}
298+
void PassValuesRef(int a, const int &b) {
299+
[[clang::musttail]] return TakesIntAndRef(a, b);
300+
}

0 commit comments

Comments
 (0)