Skip to content

Commit 97d1b80

Browse files
committed
[clang] Extend the existing lifetimebound check for assignments.
Currently we only detect the builtin pointer type.
1 parent 8a43dc3 commit 97d1b80

9 files changed

+83
-23
lines changed

clang/include/clang/Basic/DiagnosticGroups.td

+3-1
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

+5
Original file line numberDiff line numberDiff line change
@@ -10120,6 +10120,11 @@ def warn_new_dangling_initializer_list : Warning<
1012010120
"the allocated initializer list}0 "
1012110121
"will be destroyed at the end of the full-expression">,
1012210122
InGroup<DanglingInitializerList>;
10123+
def warn_dangling_pointer_assignment : Warning<
10124+
"object backing the pointer %0 "
10125+
"will be destroyed at the end of the full-expression">,
10126+
InGroup<DanglingAssignment>;
10127+
1012310128
def warn_unsupported_lifetime_extension : Warning<
1012410129
"lifetime extension of "
1012510130
"%select{temporary|backing array of initializer list}0 created "

clang/lib/Sema/CheckExprLifetime.cpp

+44-16
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,11 +966,26 @@ static bool pathOnlyInitializesGslPointer(IndirectLocalPath &Path) {
964966
return false;
965967
}
966968

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

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

10291046
switch (shouldLifetimeExtendThroughPath(Path)) {
10301047
case PathLifetimeKind::Extend:
1048+
assert(InitEntity && "Expect only on initializing the entity");
10311049
// Update the storage duration of the materialized temporary.
10321050
// FIXME: Rebuild the expression instead of mutating it.
10331051
MTE->setExtendingDecl(ExtendingEntity->getDecl(),
@@ -1036,6 +1054,7 @@ void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity,
10361054
return true;
10371055

10381056
case PathLifetimeKind::ShouldExtend:
1057+
assert(InitEntity && "Expect only on initializing the entity");
10391058
// We're supposed to lifetime-extend the temporary along this path (per
10401059
// the resolution of DR1815), but we don't support that yet.
10411060
//
@@ -1053,16 +1072,23 @@ void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity,
10531072
if (pathContainsInit(Path))
10541073
return false;
10551074

1056-
SemaRef.Diag(DiagLoc, diag::warn_dangling_variable)
1057-
<< RK << !Entity.getParent()
1058-
<< ExtendingEntity->getDecl()->isImplicit()
1059-
<< ExtendingEntity->getDecl() << Init->isGLValue() << DiagRange;
1075+
if (InitEntity) {
1076+
SemaRef.Diag(DiagLoc, diag::warn_dangling_variable)
1077+
<< RK << !InitEntity->getParent()
1078+
<< ExtendingEntity->getDecl()->isImplicit()
1079+
<< ExtendingEntity->getDecl() << Init->isGLValue() << DiagRange;
1080+
} else {
1081+
SemaRef.Diag(DiagLoc, diag::warn_dangling_pointer_assignment)
1082+
<< AEntity->LHS << DiagRange;
1083+
}
10601084
break;
10611085
}
10621086
break;
10631087
}
10641088

10651089
case LK_MemInitializer: {
1090+
assert(InitEntity && "Expect only on initializing the entity");
1091+
10661092
if (isa<MaterializeTemporaryExpr>(L)) {
10671093
// Under C++ DR1696, if a mem-initializer (or a default member
10681094
// initializer used by the absence of one) would lifetime-extend a
@@ -1077,7 +1103,7 @@ void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity,
10771103
<< true;
10781104
return false;
10791105
}
1080-
bool IsSubobjectMember = ExtendingEntity != &Entity;
1106+
bool IsSubobjectMember = ExtendingEntity != InitEntity;
10811107
SemaRef.Diag(DiagLoc, shouldLifetimeExtendThroughPath(Path) !=
10821108
PathLifetimeKind::NoExtend
10831109
? diag::err_dangling_member
@@ -1137,6 +1163,7 @@ void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity,
11371163
}
11381164

11391165
case LK_New:
1166+
assert(InitEntity && "Expect only on initializing the entity");
11401167
if (isa<MaterializeTemporaryExpr>(L)) {
11411168
if (IsGslPtrInitWithGslTempOwner)
11421169
SemaRef.Diag(DiagLoc, diag::warn_dangling_lifetime_pointer)
@@ -1145,7 +1172,7 @@ void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity,
11451172
SemaRef.Diag(DiagLoc, RK == RK_ReferenceBinding
11461173
? diag::warn_new_dangling_reference
11471174
: diag::warn_new_dangling_initializer_list)
1148-
<< !Entity.getParent() << DiagRange;
1175+
<< !InitEntity->getParent() << DiagRange;
11491176
} else {
11501177
// We can't determine if the allocation outlives the local declaration.
11511178
return false;
@@ -1154,13 +1181,14 @@ void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity,
11541181

11551182
case LK_Return:
11561183
case LK_StmtExprResult:
1184+
assert(InitEntity && "Expect only on initializing the entity");
11571185
if (auto *DRE = dyn_cast<DeclRefExpr>(L)) {
11581186
// We can't determine if the local variable outlives the statement
11591187
// expression.
11601188
if (LK == LK_StmtExprResult)
11611189
return false;
11621190
SemaRef.Diag(DiagLoc, diag::warn_ret_stack_addr_ref)
1163-
<< Entity.getType()->isReferenceType() << DRE->getDecl()
1191+
<< InitEntity->getType()->isReferenceType() << DRE->getDecl()
11641192
<< isa<ParmVarDecl>(DRE->getDecl()) << DiagRange;
11651193
} else if (isa<BlockExpr>(L)) {
11661194
SemaRef.Diag(DiagLoc, diag::err_ret_local_block) << DiagRange;
@@ -1172,21 +1200,21 @@ void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity,
11721200
SemaRef.Diag(DiagLoc, diag::warn_ret_addr_label) << DiagRange;
11731201
} else if (auto *CLE = dyn_cast<CompoundLiteralExpr>(L)) {
11741202
SemaRef.Diag(DiagLoc, diag::warn_ret_stack_addr_ref)
1175-
<< Entity.getType()->isReferenceType() << CLE->getInitializer() << 2
1176-
<< DiagRange;
1203+
<< InitEntity->getType()->isReferenceType() << CLE->getInitializer()
1204+
<< 2 << DiagRange;
11771205
} else {
11781206
// P2748R5: Disallow Binding a Returned Glvalue to a Temporary.
11791207
// [stmt.return]/p6: In a function whose return type is a reference,
11801208
// other than an invented function for std::is_convertible ([meta.rel]),
11811209
// a return statement that binds the returned reference to a temporary
11821210
// expression ([class.temporary]) is ill-formed.
11831211
if (SemaRef.getLangOpts().CPlusPlus26 &&
1184-
Entity.getType()->isReferenceType())
1212+
InitEntity->getType()->isReferenceType())
11851213
SemaRef.Diag(DiagLoc, diag::err_ret_local_temp_ref)
1186-
<< Entity.getType()->isReferenceType() << DiagRange;
1214+
<< InitEntity->getType()->isReferenceType() << DiagRange;
11871215
else
11881216
SemaRef.Diag(DiagLoc, diag::warn_ret_local_temp_addr_ref)
1189-
<< Entity.getType()->isReferenceType() << DiagRange;
1217+
<< InitEntity->getType()->isReferenceType() << DiagRange;
11901218
}
11911219
break;
11921220
}

clang/lib/Sema/CheckExprLifetime.h

+15-3
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,25 @@
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 to the entity.
33+
///
34+
/// If the entity is being initialized and its lifetime is insufficient, perform
35+
/// lifetime extension (when permitted).
36+
void checkExprLifetime(Sema &SemaRef, const CheckingEntity &CEntity,
2537
Expr *Init);
2638

2739
} // namespace clang::sema

clang/lib/Sema/SemaExpr.cpp

+4
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/lib/Sema/SemaInit.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -7287,7 +7287,7 @@ PerformConstructorInitialization(Sema &S,
72877287

72887288
void Sema::checkInitializerLifetime(const InitializedEntity &Entity,
72897289
Expr *Init) {
7290-
return sema::checkExprLifetime(*this, Entity, Init);
7290+
return sema::checkExprLifetime(*this, &Entity, Init);
72917291
}
72927292

72937293
static void DiagnoseNarrowingInInitList(Sema &S,

clang/test/Parser/compound_literal.c

+3-2
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

+6
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

+2
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)