Skip to content

Commit aee3d1a

Browse files
committed
[clang] Refine the temporay object member access filtering for GSL pointer.
1 parent 4847395 commit aee3d1a

File tree

3 files changed

+58
-14
lines changed

3 files changed

+58
-14
lines changed

clang/lib/Sema/CheckExprLifetime.cpp

Lines changed: 29 additions & 13 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,
@@ -578,19 +579,6 @@ static void visitFunctionCallArguments(IndirectLocalPath &Path, Expr *Call,
578579
Path.pop_back();
579580
};
580581
auto VisitGSLPointerArg = [&](const FunctionDecl *Callee, Expr *Arg) {
581-
// We are not interested in the temporary base objects of gsl Pointers:
582-
// Temp().ptr; // Here ptr might not dangle.
583-
if (isa<MemberExpr>(Arg->IgnoreImpCasts()))
584-
return;
585-
// Avoid false positives when the object is constructed from a conditional
586-
// operator argument. A common case is:
587-
// // 'ptr' might not be owned by the Owner object.
588-
// std::string_view s = cond() ? Owner().ptr : sv;
589-
if (const auto *Cond =
590-
dyn_cast<AbstractConditionalOperator>(Arg->IgnoreImpCasts());
591-
Cond && isPointerLikeType(Cond->getType()))
592-
return;
593-
594582
auto ReturnType = Callee->getReturnType();
595583

596584
// Once we initialized a value with a non gsl-owner reference, it can no
@@ -710,6 +698,9 @@ static void visitLocalsRetainedByReferenceBinding(IndirectLocalPath &Path,
710698
Init = ILE->getInit(0);
711699
}
712700

701+
if (MemberExpr *ME = dyn_cast<MemberExpr>(Init->IgnoreImpCasts()))
702+
Path.push_back(
703+
{IndirectLocalPathEntry::MemberExpr, ME, ME->getMemberDecl()});
713704
// Step over any subobject adjustments; we may have a materialized
714705
// temporary inside them.
715706
Init = const_cast<Expr *>(Init->skipRValueSubobjectAdjustments());
@@ -1103,6 +1094,8 @@ shouldLifetimeExtendThroughPath(const IndirectLocalPath &Path) {
11031094
for (auto Elem : Path) {
11041095
if (Elem.Kind == IndirectLocalPathEntry::DefaultInit)
11051096
return PathLifetimeKind::Extend;
1097+
if (Elem.Kind == IndirectLocalPathEntry::MemberExpr)
1098+
continue;
11061099
if (Elem.Kind != IndirectLocalPathEntry::LambdaCaptureInit)
11071100
return PathLifetimeKind::NoExtend;
11081101
}
@@ -1122,6 +1115,7 @@ static SourceRange nextPathEntryRange(const IndirectLocalPath &Path, unsigned I,
11221115
case IndirectLocalPathEntry::GslPointerInit:
11231116
case IndirectLocalPathEntry::GslPointerAssignment:
11241117
case IndirectLocalPathEntry::ParenAggInit:
1118+
case IndirectLocalPathEntry::MemberExpr:
11251119
// These exist primarily to mark the path as not permitting or
11261120
// supporting lifetime extension.
11271121
break;
@@ -1151,6 +1145,7 @@ static bool pathOnlyHandlesGslPointer(const IndirectLocalPath &Path) {
11511145
case IndirectLocalPathEntry::VarInit:
11521146
case IndirectLocalPathEntry::AddressOf:
11531147
case IndirectLocalPathEntry::LifetimeBoundCall:
1148+
case IndirectLocalPathEntry::MemberExpr:
11541149
continue;
11551150
case IndirectLocalPathEntry::GslPointerInit:
11561151
case IndirectLocalPathEntry::GslReferenceInit:
@@ -1184,6 +1179,26 @@ static AnalysisResult analyzePathForGSLPointer(const IndirectLocalPath &Path,
11841179
// At this point, Path represents a series of operations involving a
11851180
// GSLPointer, either in the process of initialization or assignment.
11861181

1182+
// Process temporary base objects for MemberExpr cases, e.g. Temp().field.
1183+
for (const auto &E : Path) {
1184+
if (E.Kind == IndirectLocalPathEntry::MemberExpr) {
1185+
// Avoid interfering with the local base object.
1186+
if (pathContainsInit(Path))
1187+
return Abandon;
1188+
1189+
// We are not interested in the temporary base objects of gsl Pointers:
1190+
// auto p1 = Temp().ptr; // Here p1 might not dangle.
1191+
// However, we want to diagnose for gsl owner fields:
1192+
// auto p2 = Temp().owner; // Here p2 is dangling.
1193+
if (const auto *FD = llvm::dyn_cast_or_null<FieldDecl>(E.D);
1194+
FD && !FD->getType()->isReferenceType() &&
1195+
isRecordWithAttr<OwnerAttr>(FD->getType())) {
1196+
return Report;
1197+
}
1198+
return Abandon;
1199+
}
1200+
}
1201+
11871202
// Note: A LifetimeBoundCall can appear interleaved in this sequence.
11881203
// For example:
11891204
// const std::string& Ref(const std::string& a [[clang::lifetimebound]]);
@@ -1510,6 +1525,7 @@ checkExprLifetimeImpl(Sema &SemaRef, const InitializedEntity *InitEntity,
15101525

15111526
case IndirectLocalPathEntry::LifetimeBoundCall:
15121527
case IndirectLocalPathEntry::TemporaryCopy:
1528+
case IndirectLocalPathEntry::MemberExpr:
15131529
case IndirectLocalPathEntry::GslPointerInit:
15141530
case IndirectLocalPathEntry::GslReferenceInit:
15151531
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: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -806,3 +806,31 @@ 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+
void test1() {
819+
std::string_view k1 = S().sv; // OK
820+
std::string_view k2 = S().s; // expected-warning {{object backing the pointer will}}
821+
822+
std::string_view k3 = Q().get()->sv; // OK
823+
std::string_view k4 = Q().get()->s; // expected-warning {{object backing the pointer will}}
824+
}
825+
826+
struct Bar {};
827+
struct Foo {
828+
std::vector<Bar> v;
829+
};
830+
Foo getFoo();
831+
void test2() {
832+
const Foo& foo = getFoo();
833+
const Bar& bar = foo.v.back(); // OK
834+
}
835+
836+
} // namespace GH120543

0 commit comments

Comments
 (0)