Skip to content

Commit 5c287ef

Browse files
authored
[Clang] Extend lifetime bound analysis to support assignments for the built-in pointer type (#96475)
The lifetime bound warning in Clang currently only considers initializations. This patch extends the warning to include assignments. - **Support for assignments of built-in pointer types**: this is done is by reusing the existing statement-local implementation. Clang now warns if the pointer is assigned to a temporary object that being destoryed at the end of the full assignment expression. With this patch, we will detect more cases under the on-by-default diagnostic `-Wdangling`. I have added a new category for this specific diagnostic so that people can temporarily disable it if their codebase is not yet clean. This is the first step to address #63310, focusing only on pointer types. Support for C++ assignment operators will come in a follow-up patch. Fixes #54492
1 parent 2e81f7d commit 5c287ef

File tree

9 files changed

+103
-24
lines changed

9 files changed

+103
-24
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -641,6 +641,10 @@ Improvements to Clang's diagnostics
641641
used rather than when they are needed for constant evaluation or when code is generated for them.
642642
The check is now stricter to prevent crashes for some unsupported declarations (Fixes #GH95495).
643643

644+
- Clang now diagnoses dangling cases where a pointer is assigned to a temporary
645+
that will be destroyed at the end of the full expression.
646+
Fixes #GH54492.
647+
644648
Improvements to Clang's time-trace
645649
----------------------------------
646650

clang/include/clang/Basic/DiagnosticGroups.td

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -430,14 +430,16 @@ def LogicalOpParentheses: DiagGroup<"logical-op-parentheses">;
430430
def LogicalNotParentheses: DiagGroup<"logical-not-parentheses">;
431431
def ShiftOpParentheses: DiagGroup<"shift-op-parentheses">;
432432
def OverloadedShiftOpParentheses: DiagGroup<"overloaded-shift-op-parentheses">;
433+
def DanglingAssignment: DiagGroup<"dangling-assignment">;
433434
def DanglingElse: DiagGroup<"dangling-else">;
434435
def DanglingField : DiagGroup<"dangling-field">;
435436
def DanglingInitializerList : DiagGroup<"dangling-initializer-list">;
436437
def DanglingGsl : DiagGroup<"dangling-gsl">;
437438
def ReturnStackAddress : DiagGroup<"return-stack-address">;
438439
// Name of this warning in GCC
439440
def : DiagGroup<"return-local-addr", [ReturnStackAddress]>;
440-
def Dangling : DiagGroup<"dangling", [DanglingField,
441+
def Dangling : DiagGroup<"dangling", [DanglingAssignment,
442+
DanglingField,
441443
DanglingInitializerList,
442444
DanglingGsl,
443445
ReturnStackAddress]>;

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10109,6 +10109,11 @@ def warn_new_dangling_initializer_list : Warning<
1010910109
"the allocated initializer list}0 "
1011010110
"will be destroyed at the end of the full-expression">,
1011110111
InGroup<DanglingInitializerList>;
10112+
def warn_dangling_pointer_assignment : Warning<
10113+
"object backing the pointer %0 "
10114+
"will be destroyed at the end of the full-expression">,
10115+
InGroup<DanglingAssignment>;
10116+
1011210117
def warn_unsupported_lifetime_extension : Warning<
1011310118
"lifetime extension of "
1011410119
"%select{temporary|backing array of initializer list}0 created "

clang/lib/Sema/CheckExprLifetime.cpp

Lines changed: 65 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
#include "CheckExprLifetime.h"
1010
#include "clang/AST/Expr.h"
11+
#include "clang/Basic/DiagnosticSema.h"
12+
#include "clang/Sema/Initialization.h"
1113
#include "clang/Sema/Sema.h"
1214
#include "llvm/ADT/PointerIntPair.h"
1315

@@ -964,17 +966,34 @@ static bool pathOnlyInitializesGslPointer(IndirectLocalPath &Path) {
964966
return false;
965967
}
966968

967-
void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity,
968-
Expr *Init) {
969-
LifetimeResult LR = getEntityLifetime(&Entity);
970-
LifetimeKind LK = LR.getInt();
971-
const InitializedEntity *ExtendingEntity = LR.getPointer();
972-
969+
static void checkExprLifetimeImpl(
970+
Sema &SemaRef,
971+
llvm::PointerUnion<const InitializedEntity *, const AssignedEntity *>
972+
CEntity,
973+
Expr *Init) {
974+
LifetimeKind LK = LK_FullExpression;
975+
976+
const AssignedEntity *AEntity = nullptr;
977+
// Local variables for initialized entity.
978+
const InitializedEntity *InitEntity = nullptr;
979+
const InitializedEntity *ExtendingEntity = nullptr;
980+
if (CEntity.is<const InitializedEntity *>()) {
981+
InitEntity = CEntity.get<const InitializedEntity *>();
982+
auto LTResult = getEntityLifetime(InitEntity);
983+
LK = LTResult.getInt();
984+
ExtendingEntity = LTResult.getPointer();
985+
} else {
986+
AEntity = CEntity.get<const AssignedEntity *>();
987+
if (AEntity->LHS->getType()->isPointerType()) // builtin pointer type
988+
LK = LK_Extended;
989+
}
973990
// If this entity doesn't have an interesting lifetime, don't bother looking
974991
// for temporaries within its initializer.
975992
if (LK == LK_FullExpression)
976993
return;
977994

995+
// FIXME: consider moving the TemporaryVisitor and visitLocalsRetained*
996+
// functions to a dedicated class.
978997
auto TemporaryVisitor = [&](IndirectLocalPath &Path, Local L,
979998
ReferenceKind RK) -> bool {
980999
SourceRange DiagRange = nextPathEntryRange(Path, 0, L);
@@ -1028,6 +1047,8 @@ void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity,
10281047

10291048
switch (shouldLifetimeExtendThroughPath(Path)) {
10301049
case PathLifetimeKind::Extend:
1050+
assert(InitEntity && "Lifetime extension should happen only for "
1051+
"initialization and not assignment");
10311052
// Update the storage duration of the materialized temporary.
10321053
// FIXME: Rebuild the expression instead of mutating it.
10331054
MTE->setExtendingDecl(ExtendingEntity->getDecl(),
@@ -1036,6 +1057,8 @@ void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity,
10361057
return true;
10371058

10381059
case PathLifetimeKind::ShouldExtend:
1060+
assert(InitEntity && "Lifetime extension should happen only for "
1061+
"initialization and not assignment");
10391062
// We're supposed to lifetime-extend the temporary along this path (per
10401063
// the resolution of DR1815), but we don't support that yet.
10411064
//
@@ -1053,17 +1076,24 @@ void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity,
10531076
if (pathContainsInit(Path))
10541077
return false;
10551078

1056-
SemaRef.Diag(DiagLoc, diag::warn_dangling_variable)
1057-
<< RK << !Entity.getParent()
1058-
<< ExtendingEntity->getDecl()->isImplicit()
1059-
<< ExtendingEntity->getDecl() << Init->isGLValue() << DiagRange;
1079+
if (InitEntity) {
1080+
SemaRef.Diag(DiagLoc, diag::warn_dangling_variable)
1081+
<< RK << !InitEntity->getParent()
1082+
<< ExtendingEntity->getDecl()->isImplicit()
1083+
<< ExtendingEntity->getDecl() << Init->isGLValue() << DiagRange;
1084+
} else {
1085+
SemaRef.Diag(DiagLoc, diag::warn_dangling_pointer_assignment)
1086+
<< AEntity->LHS << DiagRange;
1087+
}
10601088
break;
10611089
}
10621090
break;
10631091
}
10641092

10651093
case LK_MemInitializer: {
1066-
if (isa<MaterializeTemporaryExpr>(L)) {
1094+
assert(InitEntity && "Expect only on initializing the entity");
1095+
1096+
if (MTE) {
10671097
// Under C++ DR1696, if a mem-initializer (or a default member
10681098
// initializer used by the absence of one) would lifetime-extend a
10691099
// temporary, the program is ill-formed.
@@ -1077,7 +1107,7 @@ void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity,
10771107
<< true;
10781108
return false;
10791109
}
1080-
bool IsSubobjectMember = ExtendingEntity != &Entity;
1110+
bool IsSubobjectMember = ExtendingEntity != InitEntity;
10811111
SemaRef.Diag(DiagLoc, shouldLifetimeExtendThroughPath(Path) !=
10821112
PathLifetimeKind::NoExtend
10831113
? diag::err_dangling_member
@@ -1137,6 +1167,7 @@ void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity,
11371167
}
11381168

11391169
case LK_New:
1170+
assert(InitEntity && "Expect only on initializing the entity");
11401171
if (isa<MaterializeTemporaryExpr>(L)) {
11411172
if (IsGslPtrInitWithGslTempOwner)
11421173
SemaRef.Diag(DiagLoc, diag::warn_dangling_lifetime_pointer)
@@ -1145,7 +1176,7 @@ void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity,
11451176
SemaRef.Diag(DiagLoc, RK == RK_ReferenceBinding
11461177
? diag::warn_new_dangling_reference
11471178
: diag::warn_new_dangling_initializer_list)
1148-
<< !Entity.getParent() << DiagRange;
1179+
<< !InitEntity->getParent() << DiagRange;
11491180
} else {
11501181
// We can't determine if the allocation outlives the local declaration.
11511182
return false;
@@ -1154,13 +1185,14 @@ void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity,
11541185

11551186
case LK_Return:
11561187
case LK_StmtExprResult:
1188+
assert(InitEntity && "Expect only on initializing the entity");
11571189
if (auto *DRE = dyn_cast<DeclRefExpr>(L)) {
11581190
// We can't determine if the local variable outlives the statement
11591191
// expression.
11601192
if (LK == LK_StmtExprResult)
11611193
return false;
11621194
SemaRef.Diag(DiagLoc, diag::warn_ret_stack_addr_ref)
1163-
<< Entity.getType()->isReferenceType() << DRE->getDecl()
1195+
<< InitEntity->getType()->isReferenceType() << DRE->getDecl()
11641196
<< isa<ParmVarDecl>(DRE->getDecl()) << DiagRange;
11651197
} else if (isa<BlockExpr>(L)) {
11661198
SemaRef.Diag(DiagLoc, diag::err_ret_local_block) << DiagRange;
@@ -1172,21 +1204,21 @@ void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity,
11721204
SemaRef.Diag(DiagLoc, diag::warn_ret_addr_label) << DiagRange;
11731205
} else if (auto *CLE = dyn_cast<CompoundLiteralExpr>(L)) {
11741206
SemaRef.Diag(DiagLoc, diag::warn_ret_stack_addr_ref)
1175-
<< Entity.getType()->isReferenceType() << CLE->getInitializer() << 2
1176-
<< DiagRange;
1207+
<< InitEntity->getType()->isReferenceType() << CLE->getInitializer()
1208+
<< 2 << DiagRange;
11771209
} else {
11781210
// P2748R5: Disallow Binding a Returned Glvalue to a Temporary.
11791211
// [stmt.return]/p6: In a function whose return type is a reference,
11801212
// other than an invented function for std::is_convertible ([meta.rel]),
11811213
// a return statement that binds the returned reference to a temporary
11821214
// expression ([class.temporary]) is ill-formed.
11831215
if (SemaRef.getLangOpts().CPlusPlus26 &&
1184-
Entity.getType()->isReferenceType())
1216+
InitEntity->getType()->isReferenceType())
11851217
SemaRef.Diag(DiagLoc, diag::err_ret_local_temp_ref)
1186-
<< Entity.getType()->isReferenceType() << DiagRange;
1218+
<< InitEntity->getType()->isReferenceType() << DiagRange;
11871219
else
11881220
SemaRef.Diag(DiagLoc, diag::warn_ret_local_temp_addr_ref)
1189-
<< Entity.getType()->isReferenceType() << DiagRange;
1221+
<< InitEntity->getType()->isReferenceType() << DiagRange;
11901222
}
11911223
break;
11921224
}
@@ -1252,8 +1284,20 @@ void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity,
12521284
TemporaryVisitor,
12531285
EnableLifetimeWarnings);
12541286
else
1255-
visitLocalsRetainedByInitializer(Path, Init, TemporaryVisitor, false,
1256-
EnableLifetimeWarnings);
1287+
visitLocalsRetainedByInitializer(
1288+
Path, Init, TemporaryVisitor,
1289+
// Don't revisit the sub inits for the intialization case.
1290+
/*RevisitSubinits=*/!InitEntity, EnableLifetimeWarnings);
1291+
}
1292+
1293+
void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity,
1294+
Expr *Init) {
1295+
checkExprLifetimeImpl(SemaRef, &Entity, Init);
1296+
}
1297+
1298+
void checkExprLifetime(Sema &SemaRef, const AssignedEntity &Entity,
1299+
Expr *Init) {
1300+
checkExprLifetimeImpl(SemaRef, &Entity, Init);
12571301
}
12581302

12591303
} // namespace clang::sema

clang/lib/Sema/CheckExprLifetime.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,22 @@
1818

1919
namespace clang::sema {
2020

21+
/// Describes an entity that is being assigned.
22+
struct AssignedEntity {
23+
// The left-hand side expression of the assignment.
24+
Expr *LHS = nullptr;
25+
};
26+
2127
/// Check that the lifetime of the given expr (and its subobjects) is
2228
/// sufficient for initializing the entity, and perform lifetime extension
2329
/// (when permitted) if not.
2430
void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity,
2531
Expr *Init);
2632

33+
/// Check that the lifetime of the given expr (and its subobjects) is
34+
/// sufficient for assigning to the entity.
35+
void checkExprLifetime(Sema &SemaRef, const AssignedEntity &Entity, Expr *Init);
36+
2737
} // namespace clang::sema
2838

2939
#endif // LLVM_CLANG_SEMA_CHECK_EXPR_LIFETIME_H

clang/lib/Sema/SemaExpr.cpp

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

13+
#include "CheckExprLifetime.h"
1314
#include "TreeTransform.h"
1415
#include "UsedDeclVisitor.h"
1516
#include "clang/AST/ASTConsumer.h"
@@ -13778,6 +13779,9 @@ QualType Sema::CheckAssignmentOperands(Expr *LHSExpr, ExprResult &RHS,
1377813779

1377913780
CheckForNullPointerDereference(*this, LHSExpr);
1378013781

13782+
AssignedEntity AE{LHSExpr};
13783+
checkExprLifetime(*this, AE, RHS.get());
13784+
1378113785
if (getLangOpts().CPlusPlus20 && LHSType.isVolatileQualified()) {
1378213786
if (CompoundType.isNull()) {
1378313787
// C++2a [expr.ass]p5:

clang/test/Parser/compound_literal.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
// RUN: %clang_cc1 -fsyntax-only -verify %s
2-
// RUN: %clang_cc1 -fsyntax-only -verify -x c++ %s
2+
// RUN: %clang_cc1 -fsyntax-only -verify -x c++ -Wno-dangling-assignment %s
33
// expected-no-diagnostics
44
int main(void) {
55
char *s;
6-
s = (char []){"whatever"};
6+
// In C++ mode, the cast creates a "char [4]" array temporary here.
7+
s = (char []){"whatever"}; // dangling!
78
s = (char(*)){s};
89
}

clang/test/SemaCXX/attr-lifetimebound.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,13 @@ namespace usage_ok {
4040
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}}
4141
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}}
4242
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}}
43+
44+
void test_assignment() {
45+
p = A().class_member(); // expected-warning {{object backing the pointer p will be destroyed at the end of the full-expression}}
46+
p = {A().class_member()}; // expected-warning {{object backing the pointer p will be destroyed at the end of the full-expression}}
47+
q = A(); // expected-warning {{object backing the pointer q will be destroyed at the end of the full-expression}}
48+
r = A(1); // expected-warning {{object backing the pointer r will be destroyed at the end of the full-expression}}
49+
}
4350
}
4451

4552
# 1 "<std>" 1 3

clang/test/SemaCXX/warn-dangling-local.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ using T = int[];
44

55
void f() {
66
int *p = &(int&)(int&&)0; // expected-warning {{temporary whose address is used as value of local variable 'p' will be destroyed at the end of the full-expression}}
7+
p = &(int&)(int&&)0; // expected-warning {{object backing the pointer p will be destroyed at the end of the full-expression}}
78

89
int *q = (int *const &)T{1, 2, 3}; // expected-warning {{temporary whose address is used as value of local variable 'q' will be destroyed at the end of the full-expression}}
10+
q = (int *const &)T{1, 2, 3}; // expected-warning {{object backing the pointer q will be destroyed at the end of the full-expression}}
911

1012
// FIXME: We don't warn here because the 'int*' temporary is not const, but
1113
// it also can't have actually changed since it was created, so we could

0 commit comments

Comments
 (0)