Skip to content

Commit bd56950

Browse files
authored
[clang] Refine the temporay object member access filtering for GSL pointer (#122088)
We currently have ad-hoc filtering logic for temporary object member access in `VisitGSLPointerArg`. This logic filters out more cases than it should, leading to false negatives. Furthermore, this location lacks sufficient context to implement a more accurate solution. This patch refines the filtering logic by moving it to the central filtering location, `analyzePathForGSLPointer`, consolidating the logic and avoiding scattered filtering across multiple places. As a result, the special handling for conditional operators (#120233) is no longer necessary. This change also resolves #120543.
1 parent 5136c6d commit bd56950

File tree

3 files changed

+84
-20
lines changed

3 files changed

+84
-20
lines changed

clang/lib/Sema/CheckExprLifetime.cpp

Lines changed: 37 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@ struct IndirectLocalPathEntry {
200200
LifetimeBoundCall,
201201
TemporaryCopy,
202202
LambdaCaptureInit,
203+
MemberExpr,
203204
GslReferenceInit,
204205
GslPointerInit,
205206
GslPointerAssignment,
@@ -593,19 +594,6 @@ static void visitFunctionCallArguments(IndirectLocalPath &Path, Expr *Call,
593594
Path.pop_back();
594595
};
595596
auto VisitGSLPointerArg = [&](const FunctionDecl *Callee, Expr *Arg) {
596-
// We are not interested in the temporary base objects of gsl Pointers:
597-
// Temp().ptr; // Here ptr might not dangle.
598-
if (isa<MemberExpr>(Arg->IgnoreImpCasts()))
599-
return;
600-
// Avoid false positives when the object is constructed from a conditional
601-
// operator argument. A common case is:
602-
// // 'ptr' might not be owned by the Owner object.
603-
// std::string_view s = cond() ? Owner().ptr : sv;
604-
if (const auto *Cond =
605-
dyn_cast<AbstractConditionalOperator>(Arg->IgnoreImpCasts());
606-
Cond && isPointerLikeType(Cond->getType()))
607-
return;
608-
609597
auto ReturnType = Callee->getReturnType();
610598

611599
// Once we initialized a value with a non gsl-owner reference, it can no
@@ -726,6 +714,9 @@ static void visitLocalsRetainedByReferenceBinding(IndirectLocalPath &Path,
726714
Init = ILE->getInit(0);
727715
}
728716

717+
if (MemberExpr *ME = dyn_cast<MemberExpr>(Init->IgnoreImpCasts()))
718+
Path.push_back(
719+
{IndirectLocalPathEntry::MemberExpr, ME, ME->getMemberDecl()});
729720
// Step over any subobject adjustments; we may have a materialized
730721
// temporary inside them.
731722
Init = const_cast<Expr *>(Init->skipRValueSubobjectAdjustments());
@@ -1117,10 +1108,12 @@ enum PathLifetimeKind {
11171108
static PathLifetimeKind
11181109
shouldLifetimeExtendThroughPath(const IndirectLocalPath &Path) {
11191110
for (auto Elem : Path) {
1120-
if (Elem.Kind == IndirectLocalPathEntry::DefaultInit)
1121-
return PathLifetimeKind::Extend;
1122-
if (Elem.Kind != IndirectLocalPathEntry::LambdaCaptureInit)
1123-
return PathLifetimeKind::NoExtend;
1111+
if (Elem.Kind == IndirectLocalPathEntry::MemberExpr ||
1112+
Elem.Kind == IndirectLocalPathEntry::LambdaCaptureInit)
1113+
continue;
1114+
return Elem.Kind == IndirectLocalPathEntry::DefaultInit
1115+
? PathLifetimeKind::Extend
1116+
: PathLifetimeKind::NoExtend;
11241117
}
11251118
return PathLifetimeKind::Extend;
11261119
}
@@ -1138,6 +1131,7 @@ static SourceRange nextPathEntryRange(const IndirectLocalPath &Path, unsigned I,
11381131
case IndirectLocalPathEntry::GslPointerInit:
11391132
case IndirectLocalPathEntry::GslPointerAssignment:
11401133
case IndirectLocalPathEntry::ParenAggInit:
1134+
case IndirectLocalPathEntry::MemberExpr:
11411135
// These exist primarily to mark the path as not permitting or
11421136
// supporting lifetime extension.
11431137
break;
@@ -1167,6 +1161,7 @@ static bool pathOnlyHandlesGslPointer(const IndirectLocalPath &Path) {
11671161
case IndirectLocalPathEntry::VarInit:
11681162
case IndirectLocalPathEntry::AddressOf:
11691163
case IndirectLocalPathEntry::LifetimeBoundCall:
1164+
case IndirectLocalPathEntry::MemberExpr:
11701165
continue;
11711166
case IndirectLocalPathEntry::GslPointerInit:
11721167
case IndirectLocalPathEntry::GslReferenceInit:
@@ -1193,13 +1188,34 @@ enum AnalysisResult {
11931188
// Analyze cases where a GSLPointer is initialized or assigned from a
11941189
// temporary owner object.
11951190
static AnalysisResult analyzePathForGSLPointer(const IndirectLocalPath &Path,
1196-
Local L) {
1191+
Local L, LifetimeKind LK) {
11971192
if (!pathOnlyHandlesGslPointer(Path))
11981193
return NotGSLPointer;
11991194

12001195
// At this point, Path represents a series of operations involving a
12011196
// GSLPointer, either in the process of initialization or assignment.
12021197

1198+
// Process temporary base objects for MemberExpr cases, e.g. Temp().field.
1199+
for (const auto &E : Path) {
1200+
if (E.Kind == IndirectLocalPathEntry::MemberExpr) {
1201+
// Avoid interfering with the local base object.
1202+
if (pathContainsInit(Path))
1203+
return Abandon;
1204+
1205+
// We are not interested in the temporary base objects of gsl Pointers:
1206+
// auto p1 = Temp().ptr; // Here p1 might not dangle.
1207+
// However, we want to diagnose for gsl owner fields:
1208+
// auto p2 = Temp().owner; // Here p2 is dangling.
1209+
if (const auto *FD = llvm::dyn_cast_or_null<FieldDecl>(E.D);
1210+
FD && !FD->getType()->isReferenceType() &&
1211+
isRecordWithAttr<OwnerAttr>(FD->getType()) &&
1212+
LK != LK_MemInitializer) {
1213+
return Report;
1214+
}
1215+
return Abandon;
1216+
}
1217+
}
1218+
12031219
// Note: A LifetimeBoundCall can appear interleaved in this sequence.
12041220
// For example:
12051221
// const std::string& Ref(const std::string& a [[clang::lifetimebound]]);
@@ -1297,7 +1313,7 @@ checkExprLifetimeImpl(Sema &SemaRef, const InitializedEntity *InitEntity,
12971313
auto *MTE = dyn_cast<MaterializeTemporaryExpr>(L);
12981314

12991315
bool IsGslPtrValueFromGslTempOwner = true;
1300-
switch (analyzePathForGSLPointer(Path, L)) {
1316+
switch (analyzePathForGSLPointer(Path, L, LK)) {
13011317
case Abandon:
13021318
return false;
13031319
case Skip:
@@ -1429,6 +1445,7 @@ checkExprLifetimeImpl(Sema &SemaRef, const InitializedEntity *InitEntity,
14291445
auto *DRE = dyn_cast<DeclRefExpr>(L);
14301446
// Suppress false positives for code like the one below:
14311447
// Ctor(unique_ptr<T> up) : pointer(up.get()), owner(move(up)) {}
1448+
// FIXME: move this logic to analyzePathForGSLPointer.
14321449
if (DRE && isRecordWithAttr<OwnerAttr>(DRE->getType()))
14331450
return false;
14341451

@@ -1527,6 +1544,7 @@ checkExprLifetimeImpl(Sema &SemaRef, const InitializedEntity *InitEntity,
15271544

15281545
case IndirectLocalPathEntry::LifetimeBoundCall:
15291546
case IndirectLocalPathEntry::TemporaryCopy:
1547+
case IndirectLocalPathEntry::MemberExpr:
15301548
case IndirectLocalPathEntry::GslPointerInit:
15311549
case IndirectLocalPathEntry::GslReferenceInit:
15321550
case IndirectLocalPathEntry::GslPointerAssignment:

clang/test/Sema/Inputs/lifetime-analysis.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ struct vector {
5252

5353
void push_back(const T&);
5454
void push_back(T&&);
55-
55+
const T& back() const;
5656
void insert(iterator, T&&);
5757
};
5858

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

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -806,3 +806,49 @@ std::string_view test2(int c, std::string_view sv) {
806806
}
807807

808808
} // namespace GH120206
809+
810+
namespace GH120543 {
811+
struct S {
812+
std::string_view sv;
813+
std::string s;
814+
};
815+
struct Q {
816+
const S* get() const [[clang::lifetimebound]];
817+
};
818+
819+
std::string_view foo(std::string_view sv [[clang::lifetimebound]]);
820+
821+
void test1() {
822+
std::string_view k1 = S().sv; // OK
823+
std::string_view k2 = S().s; // expected-warning {{object backing the pointer will}}
824+
825+
std::string_view k3 = Q().get()->sv; // OK
826+
std::string_view k4 = Q().get()->s; // expected-warning {{object backing the pointer will}}
827+
828+
std::string_view lb1 = foo(S().s); // expected-warning {{object backing the pointer will}}
829+
std::string_view lb2 = foo(Q().get()->s); // expected-warning {{object backing the pointer will}}
830+
}
831+
832+
struct Bar {};
833+
struct Foo {
834+
std::vector<Bar> v;
835+
};
836+
Foo getFoo();
837+
void test2() {
838+
const Foo& foo = getFoo();
839+
const Bar& bar = foo.v.back(); // OK
840+
}
841+
842+
struct Foo2 {
843+
std::unique_ptr<Bar> bar;
844+
};
845+
846+
struct Test {
847+
Test(Foo2 foo) : bar(foo.bar.get()), // OK
848+
storage(std::move(foo.bar)) {};
849+
850+
Bar* bar;
851+
std::unique_ptr<Bar> storage;
852+
};
853+
854+
} // namespace GH120543

0 commit comments

Comments
 (0)