Skip to content

[clang] Extend lifetime analysis to support assignments for pointer-like objects. #99032

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 2 commits into from
Jul 18, 2024
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
3 changes: 3 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -713,6 +713,9 @@ Improvements to Clang's diagnostics

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

- Clang now diagnoses dangling assignments for pointer-like objects (annotated with `[[gsl::Pointer]]`) under `-Wdangling-assignment-gsl` (off by default)
Fixes #GH63310.

Improvements to Clang's time-trace
----------------------------------

Expand Down
2 changes: 2 additions & 0 deletions clang/include/clang/Basic/DiagnosticGroups.td
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,7 @@ def LogicalNotParentheses: DiagGroup<"logical-not-parentheses">;
def ShiftOpParentheses: DiagGroup<"shift-op-parentheses">;
def OverloadedShiftOpParentheses: DiagGroup<"overloaded-shift-op-parentheses">;
def DanglingAssignment: DiagGroup<"dangling-assignment">;
def DanglingAssignmentGsl : DiagGroup<"dangling-assignment-gsl">;
def DanglingElse: DiagGroup<"dangling-else">;
def DanglingField : DiagGroup<"dangling-field">;
def DanglingInitializerList : DiagGroup<"dangling-initializer-list">;
Expand All @@ -457,6 +458,7 @@ def ReturnStackAddress : DiagGroup<"return-stack-address">;
// Name of this warning in GCC
def : DiagGroup<"return-local-addr", [ReturnStackAddress]>;
def Dangling : DiagGroup<"dangling", [DanglingAssignment,
DanglingAssignmentGsl,
DanglingField,
DanglingInitializerList,
DanglingGsl,
Expand Down
3 changes: 3 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -10121,6 +10121,9 @@ def warn_dangling_lifetime_pointer : Warning<
"object backing the pointer "
"will be destroyed at the end of the full-expression">,
InGroup<DanglingGsl>;
def warn_dangling_lifetime_pointer_assignment : Warning<"object backing the "
"pointer %0 will be destroyed at the end of the full-expression">,
InGroup<DanglingAssignmentGsl>, DefaultIgnore;
def warn_new_dangling_initializer_list : Warning<
"array backing "
"%select{initializer list subobject of the allocated object|"
Expand Down
79 changes: 51 additions & 28 deletions clang/lib/Sema/CheckExprLifetime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,8 @@ struct IndirectLocalPathEntry {
TemporaryCopy,
LambdaCaptureInit,
GslReferenceInit,
GslPointerInit
GslPointerInit,
GslPointerAssignment,
} Kind;
Expr *E;
union {
Expand Down Expand Up @@ -337,7 +338,8 @@ static void handleGslAnnotatedTypes(IndirectLocalPath &Path, Expr *Call,
for (const IndirectLocalPathEntry &PE : llvm::reverse(Path)) {
if (PE.Kind == IndirectLocalPathEntry::GslReferenceInit)
continue;
if (PE.Kind == IndirectLocalPathEntry::GslPointerInit)
if (PE.Kind == IndirectLocalPathEntry::GslPointerInit ||
PE.Kind == IndirectLocalPathEntry::GslPointerAssignment)
return;
break;
}
Expand Down Expand Up @@ -937,6 +939,7 @@ static SourceRange nextPathEntryRange(const IndirectLocalPath &Path, unsigned I,
case IndirectLocalPathEntry::TemporaryCopy:
case IndirectLocalPathEntry::GslReferenceInit:
case IndirectLocalPathEntry::GslPointerInit:
case IndirectLocalPathEntry::GslPointerAssignment:
// These exist primarily to mark the path as not permitting or
// supporting lifetime extension.
break;
Expand All @@ -957,16 +960,20 @@ static SourceRange nextPathEntryRange(const IndirectLocalPath &Path, unsigned I,
return E->getSourceRange();
}

static bool pathOnlyInitializesGslPointer(IndirectLocalPath &Path) {
static bool pathOnlyHandlesGslPointer(IndirectLocalPath &Path) {
for (const auto &It : llvm::reverse(Path)) {
if (It.Kind == IndirectLocalPathEntry::VarInit)
continue;
if (It.Kind == IndirectLocalPathEntry::AddressOf)
continue;
if (It.Kind == IndirectLocalPathEntry::LifetimeBoundCall)
switch (It.Kind) {
case IndirectLocalPathEntry::VarInit:
case IndirectLocalPathEntry::AddressOf:
case IndirectLocalPathEntry::LifetimeBoundCall:
continue;
return It.Kind == IndirectLocalPathEntry::GslPointerInit ||
It.Kind == IndirectLocalPathEntry::GslReferenceInit;
case IndirectLocalPathEntry::GslPointerInit:
case IndirectLocalPathEntry::GslReferenceInit:
case IndirectLocalPathEntry::GslPointerAssignment:
return true;
default:
return false;
}
}
return false;
}
Expand All @@ -975,7 +982,8 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
const InitializedEntity *InitEntity,
const InitializedEntity *ExtendingEntity,
LifetimeKind LK,
const AssignedEntity *AEntity, Expr *Init) {
const AssignedEntity *AEntity, Expr *Init,
bool EnableLifetimeWarnings) {
assert((AEntity && LK == LK_Assignment) ||
(InitEntity && LK != LK_Assignment));
// If this entity doesn't have an interesting lifetime, don't bother looking
Expand All @@ -992,9 +1000,9 @@ static void checkExprLifetimeImpl(Sema &SemaRef,

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

bool IsGslPtrInitWithGslTempOwner = false;
bool IsGslPtrValueFromGslTempOwner = false;
bool IsLocalGslOwner = false;
if (pathOnlyInitializesGslPointer(Path)) {
if (pathOnlyHandlesGslPointer(Path)) {
if (isa<DeclRefExpr>(L)) {
// We do not want to follow the references when returning a pointer
// originating from a local owner to avoid the following false positive:
Expand All @@ -1005,13 +1013,13 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
if (pathContainsInit(Path) || !IsLocalGslOwner)
return false;
} else {
IsGslPtrInitWithGslTempOwner =
IsGslPtrValueFromGslTempOwner =
MTE && !MTE->getExtendingDecl() &&
isRecordWithAttr<OwnerAttr>(MTE->getType());
// Skipping a chain of initializing gsl::Pointer annotated objects.
// We are looking only for the final source to find out if it was
// a local or temporary owner or the address of a local variable/param.
if (!IsGslPtrInitWithGslTempOwner)
if (!IsGslPtrValueFromGslTempOwner)
return true;
}
}
Expand All @@ -1030,7 +1038,7 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
return false;
}

if (IsGslPtrInitWithGslTempOwner && DiagLoc.isValid()) {
if (IsGslPtrValueFromGslTempOwner && DiagLoc.isValid()) {
SemaRef.Diag(DiagLoc, diag::warn_dangling_lifetime_pointer)
<< DiagRange;
return false;
Expand Down Expand Up @@ -1073,14 +1081,16 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
}

case LK_Assignment: {
if (!MTE)
if (!MTE || pathContainsInit(Path))
return false;
assert(shouldLifetimeExtendThroughPath(Path) ==
PathLifetimeKind::NoExtend &&
"No lifetime extension for assignments");
if (!pathContainsInit(Path))
SemaRef.Diag(DiagLoc, diag::warn_dangling_pointer_assignment)
<< AEntity->LHS << DiagRange;
SemaRef.Diag(DiagLoc,
IsGslPtrValueFromGslTempOwner
? diag::warn_dangling_lifetime_pointer_assignment
: diag::warn_dangling_pointer_assignment)
<< AEntity->LHS << DiagRange;
return false;
}
case LK_MemInitializer: {
Expand All @@ -1090,7 +1100,7 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
// temporary, the program is ill-formed.
if (auto *ExtendingDecl =
ExtendingEntity ? ExtendingEntity->getDecl() : nullptr) {
if (IsGslPtrInitWithGslTempOwner) {
if (IsGslPtrValueFromGslTempOwner) {
SemaRef.Diag(DiagLoc, diag::warn_dangling_lifetime_pointer_member)
<< ExtendingDecl << DiagRange;
SemaRef.Diag(ExtendingDecl->getLocation(),
Expand Down Expand Up @@ -1131,7 +1141,7 @@ static void checkExprLifetimeImpl(Sema &SemaRef,

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

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

case LK_New:
if (isa<MaterializeTemporaryExpr>(L)) {
if (IsGslPtrInitWithGslTempOwner)
if (IsGslPtrValueFromGslTempOwner)
SemaRef.Diag(DiagLoc, diag::warn_dangling_lifetime_pointer)
<< DiagRange;
else
Expand Down Expand Up @@ -1226,6 +1236,7 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
case IndirectLocalPathEntry::TemporaryCopy:
case IndirectLocalPathEntry::GslPointerInit:
case IndirectLocalPathEntry::GslReferenceInit:
case IndirectLocalPathEntry::GslPointerAssignment:
// FIXME: Consider adding a note for these.
break;

Expand Down Expand Up @@ -1265,9 +1276,11 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
return false;
};

bool EnableLifetimeWarnings = !SemaRef.getDiagnostics().isIgnored(
diag::warn_dangling_lifetime_pointer, SourceLocation());
llvm::SmallVector<IndirectLocalPathEntry, 8> Path;
if (EnableLifetimeWarnings && LK == LK_Assignment &&
isRecordWithAttr<PointerAttr>(AEntity->LHS->getType()))
Path.push_back({IndirectLocalPathEntry::GslPointerAssignment, Init});

if (Init->isGLValue())
visitLocalsRetainedByReferenceBinding(Path, Init, RK_ReferenceBinding,
TemporaryVisitor,
Expand All @@ -1284,16 +1297,26 @@ void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity,
auto LTResult = getEntityLifetime(&Entity);
LifetimeKind LK = LTResult.getInt();
const InitializedEntity *ExtendingEntity = LTResult.getPointer();
checkExprLifetimeImpl(SemaRef, &Entity, ExtendingEntity, LK, nullptr, Init);
bool EnableLifetimeWarnings = !SemaRef.getDiagnostics().isIgnored(
diag::warn_dangling_lifetime_pointer, SourceLocation());
checkExprLifetimeImpl(SemaRef, &Entity, ExtendingEntity, LK,
/*AEntity*/ nullptr, Init, EnableLifetimeWarnings);
}

void checkExprLifetime(Sema &SemaRef, const AssignedEntity &Entity,
Expr *Init) {
if (!Entity.LHS->getType()->isPointerType()) // builtin pointer type
bool EnableLifetimeWarnings = !SemaRef.getDiagnostics().isIgnored(
diag::warn_dangling_lifetime_pointer, SourceLocation());
bool RunAnalysis = Entity.LHS->getType()->isPointerType() ||
(EnableLifetimeWarnings &&
isRecordWithAttr<PointerAttr>(Entity.LHS->getType()));

if (!RunAnalysis)
return;

checkExprLifetimeImpl(SemaRef, /*InitEntity=*/nullptr,
/*ExtendingEntity=*/nullptr, LK_Assignment, &Entity,
Init);
Init, EnableLifetimeWarnings);
}

} // namespace clang::sema
9 changes: 6 additions & 3 deletions clang/lib/Sema/SemaOverload.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
//
//===----------------------------------------------------------------------===//

#include "CheckExprLifetime.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/ASTLambda.h"
#include "clang/AST/CXXInheritance.h"
Expand Down Expand Up @@ -14714,10 +14715,12 @@ ExprResult Sema::CreateOverloadedBinOp(SourceLocation OpLoc,
FnDecl))
return ExprError();

// Check for a self move.
if (Op == OO_Equal)
if (Op == OO_Equal) {
// Check for a self move.
DiagnoseSelfMove(Args[0], Args[1], OpLoc);

// lifetime check.
checkExprLifetime(*this, AssignedEntity{Args[0]}, Args[1]);
}
if (ImplicitThis) {
QualType ThisType = Context.getPointerType(ImplicitThis->getType());
QualType ThisTypeFromDecl = Context.getPointerType(
Expand Down
4 changes: 4 additions & 0 deletions clang/test/Sema/warn-lifetime-analysis-nocfg-disabled.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,7 @@ MyIntPointer g() {
MyIntOwner o;
return o; // No warning, it is disabled.
}

void h(MyIntPointer p) {
p = MyIntOwner(); // No warning, it is disabled.
}
19 changes: 14 additions & 5 deletions clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,13 @@ MyLongPointerFromConversion global2;

void initLocalGslPtrWithTempOwner() {
MyIntPointer p = MyIntOwner{}; // expected-warning {{object backing the pointer will be destroyed at the end of the full-expression}}
p = MyIntOwner{}; // TODO ?
global = MyIntOwner{}; // TODO ?
MyIntPointer pp = p = MyIntOwner{}; // expected-warning {{object backing the pointer p will be}}
p = MyIntOwner{}; // expected-warning {{object backing the pointer p }}
pp = p; // no warning
global = MyIntOwner{}; // expected-warning {{object backing the pointer global }}
MyLongPointerFromConversion p2 = MyLongOwnerWithConversion{}; // expected-warning {{object backing the pointer will be destroyed at the end of the full-expression}}
p2 = MyLongOwnerWithConversion{}; // TODO ?
global2 = MyLongOwnerWithConversion{}; // TODO ?
p2 = MyLongOwnerWithConversion{}; // expected-warning {{object backing the pointer p2 }}
global2 = MyLongOwnerWithConversion{}; // expected-warning {{object backing the pointer global2 }}
}

namespace __gnu_cxx {
Expand Down Expand Up @@ -170,6 +172,7 @@ struct basic_string_view {
basic_string_view(const T *);
const T *begin() const;
};
using string_view = basic_string_view<char>;

template<class _Mystr> struct iter {
iter& operator-=(int);
Expand All @@ -188,7 +191,7 @@ struct basic_string {
operator basic_string_view<T> () const;
using const_iterator = iter<T>;
};

using string = basic_string<char>;

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

std::string operator+(std::string_view s1, std::string_view s2);
void danglingStringviewAssignment(std::string_view a1, std::string_view a2) {
a1 = std::string(); // expected-warning {{object backing}}
a2 = a1 + a1; // expected-warning {{object backing}}
}

std::reference_wrapper<int> danglingPtrFromNonOwnerLocal() {
int i = 5;
return i; // TODO
Expand Down
4 changes: 2 additions & 2 deletions clang/test/SemaCXX/warn-dangling-local.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// RUN: %clang_cc1 -verify -std=c++11 %s
// RUN: %clang_cc1 -verify -std=c++11 -Wdangling-assignment-gsl %s

using T = int[];

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a little confused here, since a is of type const char*, why do we mention the -gsl version of the warning here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The warning is triggered by the implicit gsl::Owner attribute on std::basic_string. Without this attribute, the warning would not be issued.

Loading