Skip to content

Commit 43ffbc2

Browse files
committed
[clang] Extend the existing lifetimebound check for assignments.
Currently we only detect the builtin pointer type.
1 parent 3c53188 commit 43ffbc2

File tree

9 files changed

+88
-30
lines changed

9 files changed

+88
-30
lines changed

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
@@ -10092,6 +10092,11 @@ def warn_new_dangling_initializer_list : Warning<
1009210092
"the allocated initializer list}0 "
1009310093
"will be destroyed at the end of the full-expression">,
1009410094
InGroup<DanglingInitializerList>;
10095+
def warn_dangling_pointer_assignment : Warning<
10096+
"object backing the pointer %0 "
10097+
"will be destroyed at the end of the full-expression">,
10098+
InGroup<DanglingAssignment>;
10099+
1009510100
def warn_unsupported_lifetime_extension : Warning<
1009610101
"lifetime extension of "
1009710102
"%select{temporary|backing array of initializer list}0 created "

clang/lib/Sema/CheckExprLifetime.cpp

Lines changed: 50 additions & 23 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

@@ -210,7 +212,7 @@ struct RevertToOldSizeRAII {
210212

211213
using LocalVisitor = llvm::function_ref<bool(IndirectLocalPath &Path, Local L,
212214
ReferenceKind RK)>;
213-
}
215+
} // namespace
214216

215217
static bool isVarOnPath(IndirectLocalPath &Path, VarDecl *VD) {
216218
for (auto E : Path)
@@ -962,12 +964,27 @@ static bool pathOnlyInitializesGslPointer(IndirectLocalPath &Path) {
962964
return false;
963965
}
964966

965-
966-
void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity,
967+
void checkExprLifetime(Sema &SemaRef, const CheckingEntity &CEntity,
967968
Expr *Init) {
968-
LifetimeResult LR = getEntityLifetime(&Entity);
969-
LifetimeKind LK = LR.getInt();
970-
const InitializedEntity *ExtendingEntity = LR.getPointer();
969+
LifetimeKind LK = LK_FullExpression;
970+
971+
const AssignedEntity *AEntity = nullptr;
972+
// Local variables for initialized entity.
973+
const InitializedEntity *InitEntity = nullptr;
974+
const InitializedEntity *ExtendingEntity = nullptr;
975+
if (auto IEntityP = std::get_if<const InitializedEntity *>(&CEntity)) {
976+
InitEntity = *IEntityP;
977+
auto LTResult = getEntityLifetime(InitEntity);
978+
LK = LTResult.getInt();
979+
ExtendingEntity = LTResult.getPointer();
980+
} else if (auto AEntityPointer =
981+
std::get_if<const AssignedEntity *>(&CEntity)) {
982+
AEntity = *AEntityPointer;
983+
if (AEntity->LHS->getType()->isPointerType()) // builtin pointer type
984+
LK = LK_Extended;
985+
} else {
986+
llvm_unreachable("unexpected kind");
987+
}
971988

972989
// If this entity doesn't have an interesting lifetime, don't bother looking
973990
// for temporaries within its initializer.
@@ -1026,6 +1043,7 @@ void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity,
10261043

10271044
switch (shouldLifetimeExtendThroughPath(Path)) {
10281045
case PathLifetimeKind::Extend:
1046+
assert(InitEntity && "Expect only on initializing the entity");
10291047
// Update the storage duration of the materialized temporary.
10301048
// FIXME: Rebuild the expression instead of mutating it.
10311049
MTE->setExtendingDecl(ExtendingEntity->getDecl(),
@@ -1034,6 +1052,7 @@ void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity,
10341052
return true;
10351053

10361054
case PathLifetimeKind::ShouldExtend:
1055+
assert(InitEntity && "Expect only on initializing the entity");
10371056
// We're supposed to lifetime-extend the temporary along this path (per
10381057
// the resolution of DR1815), but we don't support that yet.
10391058
//
@@ -1051,16 +1070,23 @@ void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity,
10511070
if (pathContainsInit(Path))
10521071
return false;
10531072

1054-
SemaRef.Diag(DiagLoc, diag::warn_dangling_variable)
1055-
<< RK << !Entity.getParent()
1056-
<< ExtendingEntity->getDecl()->isImplicit()
1057-
<< ExtendingEntity->getDecl() << Init->isGLValue() << DiagRange;
1073+
if (InitEntity) {
1074+
SemaRef.Diag(DiagLoc, diag::warn_dangling_variable)
1075+
<< RK << !InitEntity->getParent()
1076+
<< ExtendingEntity->getDecl()->isImplicit()
1077+
<< ExtendingEntity->getDecl() << Init->isGLValue() << DiagRange;
1078+
} else {
1079+
SemaRef.Diag(DiagLoc, diag::warn_dangling_pointer_assignment)
1080+
<< AEntity->LHS << DiagRange;
1081+
}
10581082
break;
10591083
}
10601084
break;
10611085
}
10621086

10631087
case LK_MemInitializer: {
1088+
assert(InitEntity && "Expect only on initializing the entity");
1089+
10641090
if (isa<MaterializeTemporaryExpr>(L)) {
10651091
// Under C++ DR1696, if a mem-initializer (or a default member
10661092
// initializer used by the absence of one) would lifetime-extend a
@@ -1075,7 +1101,7 @@ void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity,
10751101
<< true;
10761102
return false;
10771103
}
1078-
bool IsSubobjectMember = ExtendingEntity != &Entity;
1104+
bool IsSubobjectMember = ExtendingEntity != InitEntity;
10791105
SemaRef.Diag(DiagLoc, shouldLifetimeExtendThroughPath(Path) !=
10801106
PathLifetimeKind::NoExtend
10811107
? diag::err_dangling_member
@@ -1135,15 +1161,16 @@ void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity,
11351161
}
11361162

11371163
case LK_New:
1164+
assert(InitEntity && "Expect only on initializing the entity");
11381165
if (isa<MaterializeTemporaryExpr>(L)) {
11391166
if (IsGslPtrInitWithGslTempOwner)
11401167
SemaRef.Diag(DiagLoc, diag::warn_dangling_lifetime_pointer)
11411168
<< DiagRange;
11421169
else
11431170
SemaRef.Diag(DiagLoc, RK == RK_ReferenceBinding
1144-
? diag::warn_new_dangling_reference
1145-
: diag::warn_new_dangling_initializer_list)
1146-
<< !Entity.getParent() << DiagRange;
1171+
? diag::warn_new_dangling_reference
1172+
: diag::warn_new_dangling_initializer_list)
1173+
<< !InitEntity->getParent() << DiagRange;
11471174
} else {
11481175
// We can't determine if the allocation outlives the local declaration.
11491176
return false;
@@ -1152,13 +1179,14 @@ void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity,
11521179

11531180
case LK_Return:
11541181
case LK_StmtExprResult:
1182+
assert(InitEntity && "Expect only on initializing the entity");
11551183
if (auto *DRE = dyn_cast<DeclRefExpr>(L)) {
11561184
// We can't determine if the local variable outlives the statement
11571185
// expression.
11581186
if (LK == LK_StmtExprResult)
11591187
return false;
11601188
SemaRef.Diag(DiagLoc, diag::warn_ret_stack_addr_ref)
1161-
<< Entity.getType()->isReferenceType() << DRE->getDecl()
1189+
<< InitEntity->getType()->isReferenceType() << DRE->getDecl()
11621190
<< isa<ParmVarDecl>(DRE->getDecl()) << DiagRange;
11631191
} else if (isa<BlockExpr>(L)) {
11641192
SemaRef.Diag(DiagLoc, diag::err_ret_local_block) << DiagRange;
@@ -1170,21 +1198,21 @@ void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity,
11701198
SemaRef.Diag(DiagLoc, diag::warn_ret_addr_label) << DiagRange;
11711199
} else if (auto *CLE = dyn_cast<CompoundLiteralExpr>(L)) {
11721200
SemaRef.Diag(DiagLoc, diag::warn_ret_stack_addr_ref)
1173-
<< Entity.getType()->isReferenceType() << CLE->getInitializer() << 2
1174-
<< DiagRange;
1201+
<< InitEntity->getType()->isReferenceType() << CLE->getInitializer()
1202+
<< 2 << DiagRange;
11751203
} else {
11761204
// P2748R5: Disallow Binding a Returned Glvalue to a Temporary.
11771205
// [stmt.return]/p6: In a function whose return type is a reference,
11781206
// other than an invented function for std::is_convertible ([meta.rel]),
11791207
// a return statement that binds the returned reference to a temporary
11801208
// expression ([class.temporary]) is ill-formed.
11811209
if (SemaRef.getLangOpts().CPlusPlus26 &&
1182-
Entity.getType()->isReferenceType())
1210+
InitEntity->getType()->isReferenceType())
11831211
SemaRef.Diag(DiagLoc, diag::err_ret_local_temp_ref)
1184-
<< Entity.getType()->isReferenceType() << DiagRange;
1212+
<< InitEntity->getType()->isReferenceType() << DiagRange;
11851213
else
11861214
SemaRef.Diag(DiagLoc, diag::warn_ret_local_temp_addr_ref)
1187-
<< Entity.getType()->isReferenceType() << DiagRange;
1215+
<< InitEntity->getType()->isReferenceType() << DiagRange;
11881216
}
11891217
break;
11901218
}
@@ -1217,9 +1245,8 @@ void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity,
12171245
case IndirectLocalPathEntry::VarInit: {
12181246
const VarDecl *VD = cast<VarDecl>(Elem.D);
12191247
SemaRef.Diag(VD->getLocation(), diag::note_local_var_initializer)
1220-
<< VD->getType()->isReferenceType()
1221-
<< VD->isImplicit() << VD->getDeclName()
1222-
<< nextPathEntryRange(Path, I + 1, L);
1248+
<< VD->getType()->isReferenceType() << VD->isImplicit()
1249+
<< VD->getDeclName() << nextPathEntryRange(Path, I + 1, L);
12231250
break;
12241251
}
12251252

clang/lib/Sema/CheckExprLifetime.h

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,23 @@
1515
#include "clang/AST/Expr.h"
1616
#include "clang/Sema/Initialization.h"
1717
#include "clang/Sema/Sema.h"
18+
#include <variant>
1819

1920
namespace clang::sema {
2021

22+
/// Describes an entity that is being assigned.
23+
struct AssignedEntity {
24+
// The left-hand side expression of the assignment.
25+
Expr *LHS = nullptr;
26+
};
27+
28+
using CheckingEntity =
29+
std::variant<const InitializedEntity *, const AssignedEntity *>;
30+
2131
/// Check that the lifetime of the given expr (and its subobjects) is
22-
/// sufficient for initializing the entity, and perform lifetime extension
23-
/// (when permitted) if not.
24-
void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity,
32+
/// sufficient for initializing or assigning the entity, emit diagnostics if
33+
/// not.
34+
void checkExprLifetime(Sema &SemaRef, const CheckingEntity &CEntity,
2535
Expr *Init);
2636

2737
} // namespace clang::sema

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"
@@ -13777,6 +13778,9 @@ QualType Sema::CheckAssignmentOperands(Expr *LHSExpr, ExprResult &RHS,
1377713778
return QualType();
1377813779

1377913780
CheckForNullPointerDereference(*this, LHSExpr);
13781+
13782+
AssignedEntity AE{LHSExpr};
13783+
checkExprLifetime(*this, &AE, RHS.get());
1378013784

1378113785
if (getLangOpts().CPlusPlus20 && LHSType.isVolatileQualified()) {
1378213786
if (CompoundType.isNull()) {

clang/lib/Sema/SemaInit.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7288,7 +7288,8 @@ PerformConstructorInitialization(Sema &S,
72887288

72897289
void Sema::checkInitializerLifetime(const InitializedEntity &Entity,
72907290
Expr *Init) {
7291-
return sema::checkExprLifetime(*this, Entity, Init);
7291+
sema::CheckingEntity CEntity(&Entity);
7292+
return sema::checkExprLifetime(*this, CEntity, Init);
72927293
}
72937294

72947295
static void DiagnoseNarrowingInInitList(Sema &S,

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: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ 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+
q = A(); // expected-warning {{object backing the pointer q will be destroyed at the end of the full-expression}}
47+
r = A(1); // expected-warning {{object backing the pointer r will be destroyed at the end of the full-expression}}
48+
}
4349
}
4450

4551
# 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)