Skip to content

[clang] Refine the temporay object member access filtering for GSL pointer #122088

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jan 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 37 additions & 19 deletions clang/lib/Sema/CheckExprLifetime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ struct IndirectLocalPathEntry {
LifetimeBoundCall,
TemporaryCopy,
LambdaCaptureInit,
MemberExpr,
GslReferenceInit,
GslPointerInit,
GslPointerAssignment,
Expand Down Expand Up @@ -593,19 +594,6 @@ static void visitFunctionCallArguments(IndirectLocalPath &Path, Expr *Call,
Path.pop_back();
};
auto VisitGSLPointerArg = [&](const FunctionDecl *Callee, Expr *Arg) {
// We are not interested in the temporary base objects of gsl Pointers:
// Temp().ptr; // Here ptr might not dangle.
if (isa<MemberExpr>(Arg->IgnoreImpCasts()))
return;
// Avoid false positives when the object is constructed from a conditional
// operator argument. A common case is:
// // 'ptr' might not be owned by the Owner object.
// std::string_view s = cond() ? Owner().ptr : sv;
if (const auto *Cond =
dyn_cast<AbstractConditionalOperator>(Arg->IgnoreImpCasts());
Cond && isPointerLikeType(Cond->getType()))
return;

auto ReturnType = Callee->getReturnType();

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

if (MemberExpr *ME = dyn_cast<MemberExpr>(Init->IgnoreImpCasts()))
Path.push_back(
{IndirectLocalPathEntry::MemberExpr, ME, ME->getMemberDecl()});
// Step over any subobject adjustments; we may have a materialized
// temporary inside them.
Init = const_cast<Expr *>(Init->skipRValueSubobjectAdjustments());
Expand Down Expand Up @@ -1117,10 +1108,12 @@ enum PathLifetimeKind {
static PathLifetimeKind
shouldLifetimeExtendThroughPath(const IndirectLocalPath &Path) {
for (auto Elem : Path) {
if (Elem.Kind == IndirectLocalPathEntry::DefaultInit)
return PathLifetimeKind::Extend;
if (Elem.Kind != IndirectLocalPathEntry::LambdaCaptureInit)
return PathLifetimeKind::NoExtend;
if (Elem.Kind == IndirectLocalPathEntry::MemberExpr ||
Elem.Kind == IndirectLocalPathEntry::LambdaCaptureInit)
continue;
return Elem.Kind == IndirectLocalPathEntry::DefaultInit
? PathLifetimeKind::Extend
: PathLifetimeKind::NoExtend;
}
return PathLifetimeKind::Extend;
}
Expand All @@ -1138,6 +1131,7 @@ static SourceRange nextPathEntryRange(const IndirectLocalPath &Path, unsigned I,
case IndirectLocalPathEntry::GslPointerInit:
case IndirectLocalPathEntry::GslPointerAssignment:
case IndirectLocalPathEntry::ParenAggInit:
case IndirectLocalPathEntry::MemberExpr:
// These exist primarily to mark the path as not permitting or
// supporting lifetime extension.
break;
Expand Down Expand Up @@ -1167,6 +1161,7 @@ static bool pathOnlyHandlesGslPointer(const IndirectLocalPath &Path) {
case IndirectLocalPathEntry::VarInit:
case IndirectLocalPathEntry::AddressOf:
case IndirectLocalPathEntry::LifetimeBoundCall:
case IndirectLocalPathEntry::MemberExpr:
continue;
case IndirectLocalPathEntry::GslPointerInit:
case IndirectLocalPathEntry::GslReferenceInit:
Expand All @@ -1193,13 +1188,34 @@ enum AnalysisResult {
// Analyze cases where a GSLPointer is initialized or assigned from a
// temporary owner object.
static AnalysisResult analyzePathForGSLPointer(const IndirectLocalPath &Path,
Local L) {
Local L, LifetimeKind LK) {
if (!pathOnlyHandlesGslPointer(Path))
return NotGSLPointer;

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

// Process temporary base objects for MemberExpr cases, e.g. Temp().field.
for (const auto &E : Path) {
if (E.Kind == IndirectLocalPathEntry::MemberExpr) {
// Avoid interfering with the local base object.
if (pathContainsInit(Path))
return Abandon;

// We are not interested in the temporary base objects of gsl Pointers:
// auto p1 = Temp().ptr; // Here p1 might not dangle.
// However, we want to diagnose for gsl owner fields:
// auto p2 = Temp().owner; // Here p2 is dangling.
if (const auto *FD = llvm::dyn_cast_or_null<FieldDecl>(E.D);
FD && !FD->getType()->isReferenceType() &&
isRecordWithAttr<OwnerAttr>(FD->getType()) &&
LK != LK_MemInitializer) {
return Report;
}
return Abandon;
}
}

// Note: A LifetimeBoundCall can appear interleaved in this sequence.
// For example:
// const std::string& Ref(const std::string& a [[clang::lifetimebound]]);
Expand Down Expand Up @@ -1297,7 +1313,7 @@ checkExprLifetimeImpl(Sema &SemaRef, const InitializedEntity *InitEntity,
auto *MTE = dyn_cast<MaterializeTemporaryExpr>(L);

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

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

case IndirectLocalPathEntry::LifetimeBoundCall:
case IndirectLocalPathEntry::TemporaryCopy:
case IndirectLocalPathEntry::MemberExpr:
case IndirectLocalPathEntry::GslPointerInit:
case IndirectLocalPathEntry::GslReferenceInit:
case IndirectLocalPathEntry::GslPointerAssignment:
Expand Down
2 changes: 1 addition & 1 deletion clang/test/Sema/Inputs/lifetime-analysis.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ struct vector {

void push_back(const T&);
void push_back(T&&);

const T& back() const;
void insert(iterator, T&&);
};

Expand Down
46 changes: 46 additions & 0 deletions clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -806,3 +806,49 @@ std::string_view test2(int c, std::string_view sv) {
}

} // namespace GH120206

namespace GH120543 {
struct S {
std::string_view sv;
std::string s;
};
struct Q {
const S* get() const [[clang::lifetimebound]];
};

std::string_view foo(std::string_view sv [[clang::lifetimebound]]);

void test1() {
std::string_view k1 = S().sv; // OK
std::string_view k2 = S().s; // expected-warning {{object backing the pointer will}}

std::string_view k3 = Q().get()->sv; // OK
std::string_view k4 = Q().get()->s; // expected-warning {{object backing the pointer will}}

std::string_view lb1 = foo(S().s); // expected-warning {{object backing the pointer will}}
std::string_view lb2 = foo(Q().get()->s); // expected-warning {{object backing the pointer will}}
}

struct Bar {};
struct Foo {
std::vector<Bar> v;
};
Foo getFoo();
void test2() {
const Foo& foo = getFoo();
const Bar& bar = foo.v.back(); // OK
}

struct Foo2 {
std::unique_ptr<Bar> bar;
};

struct Test {
Test(Foo2 foo) : bar(foo.bar.get()), // OK
storage(std::move(foo.bar)) {};

Bar* bar;
std::unique_ptr<Bar> storage;
};

} // namespace GH120543
Loading