Skip to content

Commit 471155f

Browse files
hokeinyuxuanchen1997
authored andcommitted
[clang] Extend lifetime analysis to support assignments for pointer-like objects. (#99032)
Summary: This is a follow-up patch to #96475 to detect dangling assignments for C++ pointer-like objects (classes annotated with the `[[gsl::Pointer]]`). Fixes #63310. Similar to the behavior for built-in pointer types, if a temporary owner (`[[gsl::Owner]]`) object is assigned to a pointer-like class object, and this temporary object is destroyed at the end of the full assignment expression, the assignee pointer is considered dangling. In such cases, clang will emit a warning: ``` /tmp/t.cpp:7:20: warning: object backing the pointer my_string_view will be destroyed at the end of the full-expression [-Wdangling-assignment-gsl] 7 | my_string_view = CreateString(); | ^~~~~~~~~~~~~~ 1 warning generated. ``` This new warning is `-Wdangling-assignment-gsl`. It is initially disabled, but I intend to enable it by default in clang 20. I have initially tested this patch on our internal codebase, and it has identified many use-after-free bugs, primarily related to `string_view`. Test Plan: Reviewers: Subscribers: Tasks: Tags: Differential Revision: https://phabricator.intern.facebook.com/D60251757
1 parent cfc6908 commit 471155f

8 files changed

+85
-38
lines changed

clang/docs/ReleaseNotes.rst

+3
Original file line numberDiff line numberDiff line change
@@ -727,6 +727,9 @@ Improvements to Clang's diagnostics
727727

728728
- Clang now diagnoses integer constant expressions that are folded to a constant value as an extension in more circumstances. Fixes #GH59863
729729

730+
- Clang now diagnoses dangling assignments for pointer-like objects (annotated with `[[gsl::Pointer]]`) under `-Wdangling-assignment-gsl` (off by default)
731+
Fixes #GH63310.
732+
730733
Improvements to Clang's time-trace
731734
----------------------------------
732735

clang/include/clang/Basic/DiagnosticGroups.td

+2
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,7 @@ def LogicalNotParentheses: DiagGroup<"logical-not-parentheses">;
451451
def ShiftOpParentheses: DiagGroup<"shift-op-parentheses">;
452452
def OverloadedShiftOpParentheses: DiagGroup<"overloaded-shift-op-parentheses">;
453453
def DanglingAssignment: DiagGroup<"dangling-assignment">;
454+
def DanglingAssignmentGsl : DiagGroup<"dangling-assignment-gsl">;
454455
def DanglingElse: DiagGroup<"dangling-else">;
455456
def DanglingField : DiagGroup<"dangling-field">;
456457
def DanglingInitializerList : DiagGroup<"dangling-initializer-list">;
@@ -459,6 +460,7 @@ def ReturnStackAddress : DiagGroup<"return-stack-address">;
459460
// Name of this warning in GCC
460461
def : DiagGroup<"return-local-addr", [ReturnStackAddress]>;
461462
def Dangling : DiagGroup<"dangling", [DanglingAssignment,
463+
DanglingAssignmentGsl,
462464
DanglingField,
463465
DanglingInitializerList,
464466
DanglingGsl,

clang/include/clang/Basic/DiagnosticSemaKinds.td

+3
Original file line numberDiff line numberDiff line change
@@ -10124,6 +10124,9 @@ def warn_dangling_lifetime_pointer : Warning<
1012410124
"object backing the pointer "
1012510125
"will be destroyed at the end of the full-expression">,
1012610126
InGroup<DanglingGsl>;
10127+
def warn_dangling_lifetime_pointer_assignment : Warning<"object backing the "
10128+
"pointer %0 will be destroyed at the end of the full-expression">,
10129+
InGroup<DanglingAssignmentGsl>, DefaultIgnore;
1012710130
def warn_new_dangling_initializer_list : Warning<
1012810131
"array backing "
1012910132
"%select{initializer list subobject of the allocated object|"

clang/lib/Sema/CheckExprLifetime.cpp

+51-28
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,8 @@ struct IndirectLocalPathEntry {
191191
TemporaryCopy,
192192
LambdaCaptureInit,
193193
GslReferenceInit,
194-
GslPointerInit
194+
GslPointerInit,
195+
GslPointerAssignment,
195196
} Kind;
196197
Expr *E;
197198
union {
@@ -337,7 +338,8 @@ static void handleGslAnnotatedTypes(IndirectLocalPath &Path, Expr *Call,
337338
for (const IndirectLocalPathEntry &PE : llvm::reverse(Path)) {
338339
if (PE.Kind == IndirectLocalPathEntry::GslReferenceInit)
339340
continue;
340-
if (PE.Kind == IndirectLocalPathEntry::GslPointerInit)
341+
if (PE.Kind == IndirectLocalPathEntry::GslPointerInit ||
342+
PE.Kind == IndirectLocalPathEntry::GslPointerAssignment)
341343
return;
342344
break;
343345
}
@@ -937,6 +939,7 @@ static SourceRange nextPathEntryRange(const IndirectLocalPath &Path, unsigned I,
937939
case IndirectLocalPathEntry::TemporaryCopy:
938940
case IndirectLocalPathEntry::GslReferenceInit:
939941
case IndirectLocalPathEntry::GslPointerInit:
942+
case IndirectLocalPathEntry::GslPointerAssignment:
940943
// These exist primarily to mark the path as not permitting or
941944
// supporting lifetime extension.
942945
break;
@@ -957,16 +960,20 @@ static SourceRange nextPathEntryRange(const IndirectLocalPath &Path, unsigned I,
957960
return E->getSourceRange();
958961
}
959962

960-
static bool pathOnlyInitializesGslPointer(IndirectLocalPath &Path) {
963+
static bool pathOnlyHandlesGslPointer(IndirectLocalPath &Path) {
961964
for (const auto &It : llvm::reverse(Path)) {
962-
if (It.Kind == IndirectLocalPathEntry::VarInit)
963-
continue;
964-
if (It.Kind == IndirectLocalPathEntry::AddressOf)
965-
continue;
966-
if (It.Kind == IndirectLocalPathEntry::LifetimeBoundCall)
965+
switch (It.Kind) {
966+
case IndirectLocalPathEntry::VarInit:
967+
case IndirectLocalPathEntry::AddressOf:
968+
case IndirectLocalPathEntry::LifetimeBoundCall:
967969
continue;
968-
return It.Kind == IndirectLocalPathEntry::GslPointerInit ||
969-
It.Kind == IndirectLocalPathEntry::GslReferenceInit;
970+
case IndirectLocalPathEntry::GslPointerInit:
971+
case IndirectLocalPathEntry::GslReferenceInit:
972+
case IndirectLocalPathEntry::GslPointerAssignment:
973+
return true;
974+
default:
975+
return false;
976+
}
970977
}
971978
return false;
972979
}
@@ -975,7 +982,8 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
975982
const InitializedEntity *InitEntity,
976983
const InitializedEntity *ExtendingEntity,
977984
LifetimeKind LK,
978-
const AssignedEntity *AEntity, Expr *Init) {
985+
const AssignedEntity *AEntity, Expr *Init,
986+
bool EnableLifetimeWarnings) {
979987
assert((AEntity && LK == LK_Assignment) ||
980988
(InitEntity && LK != LK_Assignment));
981989
// If this entity doesn't have an interesting lifetime, don't bother looking
@@ -992,9 +1000,9 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
9921000

9931001
auto *MTE = dyn_cast<MaterializeTemporaryExpr>(L);
9941002

995-
bool IsGslPtrInitWithGslTempOwner = false;
1003+
bool IsGslPtrValueFromGslTempOwner = false;
9961004
bool IsLocalGslOwner = false;
997-
if (pathOnlyInitializesGslPointer(Path)) {
1005+
if (pathOnlyHandlesGslPointer(Path)) {
9981006
if (isa<DeclRefExpr>(L)) {
9991007
// We do not want to follow the references when returning a pointer
10001008
// originating from a local owner to avoid the following false positive:
@@ -1005,13 +1013,13 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
10051013
if (pathContainsInit(Path) || !IsLocalGslOwner)
10061014
return false;
10071015
} else {
1008-
IsGslPtrInitWithGslTempOwner =
1016+
IsGslPtrValueFromGslTempOwner =
10091017
MTE && !MTE->getExtendingDecl() &&
10101018
isRecordWithAttr<OwnerAttr>(MTE->getType());
10111019
// Skipping a chain of initializing gsl::Pointer annotated objects.
10121020
// We are looking only for the final source to find out if it was
10131021
// a local or temporary owner or the address of a local variable/param.
1014-
if (!IsGslPtrInitWithGslTempOwner)
1022+
if (!IsGslPtrValueFromGslTempOwner)
10151023
return true;
10161024
}
10171025
}
@@ -1030,7 +1038,7 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
10301038
return false;
10311039
}
10321040

1033-
if (IsGslPtrInitWithGslTempOwner && DiagLoc.isValid()) {
1041+
if (IsGslPtrValueFromGslTempOwner && DiagLoc.isValid()) {
10341042
SemaRef.Diag(DiagLoc, diag::warn_dangling_lifetime_pointer)
10351043
<< DiagRange;
10361044
return false;
@@ -1073,14 +1081,16 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
10731081
}
10741082

10751083
case LK_Assignment: {
1076-
if (!MTE)
1084+
if (!MTE || pathContainsInit(Path))
10771085
return false;
10781086
assert(shouldLifetimeExtendThroughPath(Path) ==
10791087
PathLifetimeKind::NoExtend &&
10801088
"No lifetime extension for assignments");
1081-
if (!pathContainsInit(Path))
1082-
SemaRef.Diag(DiagLoc, diag::warn_dangling_pointer_assignment)
1083-
<< AEntity->LHS << DiagRange;
1089+
SemaRef.Diag(DiagLoc,
1090+
IsGslPtrValueFromGslTempOwner
1091+
? diag::warn_dangling_lifetime_pointer_assignment
1092+
: diag::warn_dangling_pointer_assignment)
1093+
<< AEntity->LHS << DiagRange;
10841094
return false;
10851095
}
10861096
case LK_MemInitializer: {
@@ -1090,7 +1100,7 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
10901100
// temporary, the program is ill-formed.
10911101
if (auto *ExtendingDecl =
10921102
ExtendingEntity ? ExtendingEntity->getDecl() : nullptr) {
1093-
if (IsGslPtrInitWithGslTempOwner) {
1103+
if (IsGslPtrValueFromGslTempOwner) {
10941104
SemaRef.Diag(DiagLoc, diag::warn_dangling_lifetime_pointer_member)
10951105
<< ExtendingDecl << DiagRange;
10961106
SemaRef.Diag(ExtendingDecl->getLocation(),
@@ -1131,7 +1141,7 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
11311141

11321142
// Suppress false positives for code like the one below:
11331143
// Ctor(unique_ptr<T> up) : member(*up), member2(move(up)) {}
1134-
if (IsLocalGslOwner && pathOnlyInitializesGslPointer(Path))
1144+
if (IsLocalGslOwner && pathOnlyHandlesGslPointer(Path))
11351145
return false;
11361146

11371147
auto *DRE = dyn_cast<DeclRefExpr>(L);
@@ -1159,7 +1169,7 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
11591169

11601170
case LK_New:
11611171
if (isa<MaterializeTemporaryExpr>(L)) {
1162-
if (IsGslPtrInitWithGslTempOwner)
1172+
if (IsGslPtrValueFromGslTempOwner)
11631173
SemaRef.Diag(DiagLoc, diag::warn_dangling_lifetime_pointer)
11641174
<< DiagRange;
11651175
else
@@ -1226,6 +1236,7 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
12261236
case IndirectLocalPathEntry::TemporaryCopy:
12271237
case IndirectLocalPathEntry::GslPointerInit:
12281238
case IndirectLocalPathEntry::GslReferenceInit:
1239+
case IndirectLocalPathEntry::GslPointerAssignment:
12291240
// FIXME: Consider adding a note for these.
12301241
break;
12311242

@@ -1265,9 +1276,11 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
12651276
return false;
12661277
};
12671278

1268-
bool EnableLifetimeWarnings = !SemaRef.getDiagnostics().isIgnored(
1269-
diag::warn_dangling_lifetime_pointer, SourceLocation());
12701279
llvm::SmallVector<IndirectLocalPathEntry, 8> Path;
1280+
if (EnableLifetimeWarnings && LK == LK_Assignment &&
1281+
isRecordWithAttr<PointerAttr>(AEntity->LHS->getType()))
1282+
Path.push_back({IndirectLocalPathEntry::GslPointerAssignment, Init});
1283+
12711284
if (Init->isGLValue())
12721285
visitLocalsRetainedByReferenceBinding(Path, Init, RK_ReferenceBinding,
12731286
TemporaryVisitor,
@@ -1284,16 +1297,26 @@ void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity,
12841297
auto LTResult = getEntityLifetime(&Entity);
12851298
LifetimeKind LK = LTResult.getInt();
12861299
const InitializedEntity *ExtendingEntity = LTResult.getPointer();
1287-
checkExprLifetimeImpl(SemaRef, &Entity, ExtendingEntity, LK, nullptr, Init);
1300+
bool EnableLifetimeWarnings = !SemaRef.getDiagnostics().isIgnored(
1301+
diag::warn_dangling_lifetime_pointer, SourceLocation());
1302+
checkExprLifetimeImpl(SemaRef, &Entity, ExtendingEntity, LK,
1303+
/*AEntity*/ nullptr, Init, EnableLifetimeWarnings);
12881304
}
12891305

12901306
void checkExprLifetime(Sema &SemaRef, const AssignedEntity &Entity,
12911307
Expr *Init) {
1292-
if (!Entity.LHS->getType()->isPointerType()) // builtin pointer type
1308+
bool EnableLifetimeWarnings = !SemaRef.getDiagnostics().isIgnored(
1309+
diag::warn_dangling_lifetime_pointer, SourceLocation());
1310+
bool RunAnalysis = Entity.LHS->getType()->isPointerType() ||
1311+
(EnableLifetimeWarnings &&
1312+
isRecordWithAttr<PointerAttr>(Entity.LHS->getType()));
1313+
1314+
if (!RunAnalysis)
12931315
return;
1316+
12941317
checkExprLifetimeImpl(SemaRef, /*InitEntity=*/nullptr,
12951318
/*ExtendingEntity=*/nullptr, LK_Assignment, &Entity,
1296-
Init);
1319+
Init, EnableLifetimeWarnings);
12971320
}
12981321

12991322
} // namespace clang::sema

clang/lib/Sema/SemaOverload.cpp

+6-3
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/ASTLambda.h"
1516
#include "clang/AST/CXXInheritance.h"
@@ -14714,10 +14715,12 @@ ExprResult Sema::CreateOverloadedBinOp(SourceLocation OpLoc,
1471414715
FnDecl))
1471514716
return ExprError();
1471614717

14717-
// Check for a self move.
14718-
if (Op == OO_Equal)
14718+
if (Op == OO_Equal) {
14719+
// Check for a self move.
1471914720
DiagnoseSelfMove(Args[0], Args[1], OpLoc);
14720-
14721+
// lifetime check.
14722+
checkExprLifetime(*this, AssignedEntity{Args[0]}, Args[1]);
14723+
}
1472114724
if (ImplicitThis) {
1472214725
QualType ThisType = Context.getPointerType(ImplicitThis->getType());
1472314726
QualType ThisTypeFromDecl = Context.getPointerType(

clang/test/Sema/warn-lifetime-analysis-nocfg-disabled.cpp

+4
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,7 @@ MyIntPointer g() {
2121
MyIntOwner o;
2222
return o; // No warning, it is disabled.
2323
}
24+
25+
void h(MyIntPointer p) {
26+
p = MyIntOwner(); // No warning, it is disabled.
27+
}

clang/test/Sema/warn-lifetime-analysis-nocfg.cpp

+14-5
Original file line numberDiff line numberDiff line change
@@ -120,11 +120,13 @@ MyLongPointerFromConversion global2;
120120

121121
void initLocalGslPtrWithTempOwner() {
122122
MyIntPointer p = MyIntOwner{}; // expected-warning {{object backing the pointer will be destroyed at the end of the full-expression}}
123-
p = MyIntOwner{}; // TODO ?
124-
global = MyIntOwner{}; // TODO ?
123+
MyIntPointer pp = p = MyIntOwner{}; // expected-warning {{object backing the pointer p will be}}
124+
p = MyIntOwner{}; // expected-warning {{object backing the pointer p }}
125+
pp = p; // no warning
126+
global = MyIntOwner{}; // expected-warning {{object backing the pointer global }}
125127
MyLongPointerFromConversion p2 = MyLongOwnerWithConversion{}; // expected-warning {{object backing the pointer will be destroyed at the end of the full-expression}}
126-
p2 = MyLongOwnerWithConversion{}; // TODO ?
127-
global2 = MyLongOwnerWithConversion{}; // TODO ?
128+
p2 = MyLongOwnerWithConversion{}; // expected-warning {{object backing the pointer p2 }}
129+
global2 = MyLongOwnerWithConversion{}; // expected-warning {{object backing the pointer global2 }}
128130
}
129131

130132
namespace __gnu_cxx {
@@ -170,6 +172,7 @@ struct basic_string_view {
170172
basic_string_view(const T *);
171173
const T *begin() const;
172174
};
175+
using string_view = basic_string_view<char>;
173176

174177
template<class _Mystr> struct iter {
175178
iter& operator-=(int);
@@ -188,7 +191,7 @@ struct basic_string {
188191
operator basic_string_view<T> () const;
189192
using const_iterator = iter<T>;
190193
};
191-
194+
using string = basic_string<char>;
192195

193196
template<typename T>
194197
struct unique_ptr {
@@ -346,6 +349,12 @@ void handleTernaryOperator(bool cond) {
346349
std::basic_string_view<char> v = cond ? def : ""; // expected-warning {{object backing the pointer will be destroyed at the end of the full-expression}}
347350
}
348351

352+
std::string operator+(std::string_view s1, std::string_view s2);
353+
void danglingStringviewAssignment(std::string_view a1, std::string_view a2) {
354+
a1 = std::string(); // expected-warning {{object backing}}
355+
a2 = a1 + a1; // expected-warning {{object backing}}
356+
}
357+
349358
std::reference_wrapper<int> danglingPtrFromNonOwnerLocal() {
350359
int i = 5;
351360
return i; // TODO

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %clang_cc1 -verify -std=c++11 %s
1+
// RUN: %clang_cc1 -verify -std=c++11 -Wdangling-assignment-gsl %s
22

33
using T = int[];
44

@@ -34,6 +34,6 @@ struct basic_string {
3434
};
3535
} // namespace std
3636
void test(const char* a) {
37-
// verify we're emitting the `-Wdangling-assignment` warning.
37+
// verify we're emitting the `-Wdangling-assignment-gsl` warning.
3838
a = std::basic_string().c_str(); // expected-warning {{object backing the pointer a will be destroyed at the end of the full-expression}}
3939
}

0 commit comments

Comments
 (0)