Skip to content
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
2 changes: 2 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,8 @@ Improvements to Clang's diagnostics

- Clang now diagnoses when the result of a [[nodiscard]] function is discarded after being cast in C. Fixes #GH104391.

- Don't emit duplicated dangling diagnostics. (#GH93386).

- Improved diagnostic when trying to befriend a concept. (#GH45182).

Improvements to Clang's time-trace
Expand Down
127 changes: 55 additions & 72 deletions clang/lib/Sema/CheckExprLifetime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -326,66 +326,6 @@ static bool shouldTrackFirstArgument(const FunctionDecl *FD) {
return false;
}

static void handleGslAnnotatedTypes(IndirectLocalPath &Path, Expr *Call,
LocalVisitor Visit) {
auto VisitPointerArg = [&](const Decl *D, Expr *Arg, bool Value) {
// 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;
// Once we initialized a value with a reference, it can no longer dangle.
if (!Value) {
for (const IndirectLocalPathEntry &PE : llvm::reverse(Path)) {
if (PE.Kind == IndirectLocalPathEntry::GslReferenceInit)
continue;
if (PE.Kind == IndirectLocalPathEntry::GslPointerInit ||
PE.Kind == IndirectLocalPathEntry::GslPointerAssignment)
return;
break;
}
}
Path.push_back({Value ? IndirectLocalPathEntry::GslPointerInit
: IndirectLocalPathEntry::GslReferenceInit,
Arg, D});
if (Arg->isGLValue())
visitLocalsRetainedByReferenceBinding(Path, Arg, RK_ReferenceBinding,
Visit,
/*EnableLifetimeWarnings=*/true);
else
visitLocalsRetainedByInitializer(Path, Arg, Visit, true,
/*EnableLifetimeWarnings=*/true);
Path.pop_back();
};

if (auto *MCE = dyn_cast<CXXMemberCallExpr>(Call)) {
const auto *MD = cast_or_null<CXXMethodDecl>(MCE->getDirectCallee());
if (MD && shouldTrackImplicitObjectArg(MD))
VisitPointerArg(MD, MCE->getImplicitObjectArgument(),
!MD->getReturnType()->isReferenceType());
return;
} else if (auto *OCE = dyn_cast<CXXOperatorCallExpr>(Call)) {
FunctionDecl *Callee = OCE->getDirectCallee();
if (Callee && Callee->isCXXInstanceMember() &&
shouldTrackImplicitObjectArg(cast<CXXMethodDecl>(Callee)))
VisitPointerArg(Callee, OCE->getArg(0),
!Callee->getReturnType()->isReferenceType());
return;
} else if (auto *CE = dyn_cast<CallExpr>(Call)) {
FunctionDecl *Callee = CE->getDirectCallee();
if (Callee && shouldTrackFirstArgument(Callee))
VisitPointerArg(Callee, CE->getArg(0),
!Callee->getReturnType()->isReferenceType());
return;
}

if (auto *CCE = dyn_cast<CXXConstructExpr>(Call)) {
const auto *Ctor = CCE->getConstructor();
const CXXRecordDecl *RD = Ctor->getParent();
if (CCE->getNumArgs() > 0 && RD->hasAttr<PointerAttr>())
VisitPointerArg(Ctor->getParamDecl(0), CCE->getArgs()[0], true);
}
}

static bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD) {
const TypeSourceInfo *TSI = FD->getTypeSourceInfo();
if (!TSI)
Expand Down Expand Up @@ -423,8 +363,10 @@ static bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD) {
return false;
}

static void visitLifetimeBoundArguments(IndirectLocalPath &Path, Expr *Call,
LocalVisitor Visit) {
// Visit lifetimebound or gsl-pointer arguments.
static void visitFunctionCallArguments(IndirectLocalPath &Path, Expr *Call,
LocalVisitor Visit,
bool EnableLifetimeWarnings) {
const FunctionDecl *Callee;
ArrayRef<Expr *> Args;

Expand Down Expand Up @@ -458,6 +400,34 @@ static void visitLifetimeBoundArguments(IndirectLocalPath &Path, Expr *Call,
/*EnableLifetimeWarnings=*/false);
Path.pop_back();
};
auto VisitGSLPointerArg = [&](const Decl *D, Expr *Arg, bool Value) {
// 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;
// Once we initialized a value with a reference, it can no longer dangle.
if (!Value) {
for (const IndirectLocalPathEntry &PE : llvm::reverse(Path)) {
if (PE.Kind == IndirectLocalPathEntry::GslReferenceInit)
continue;
if (PE.Kind == IndirectLocalPathEntry::GslPointerInit ||
PE.Kind == IndirectLocalPathEntry::GslPointerAssignment)
return;
break;
}
}
Path.push_back({Value ? IndirectLocalPathEntry::GslPointerInit
: IndirectLocalPathEntry::GslReferenceInit,
Arg, D});
if (Arg->isGLValue())
visitLocalsRetainedByReferenceBinding(Path, Arg, RK_ReferenceBinding,
Visit,
/*EnableLifetimeWarnings=*/true);
else
visitLocalsRetainedByInitializer(Path, Arg, Visit, true,
/*EnableLifetimeWarnings=*/true);
Path.pop_back();
};

bool CheckCoroCall = false;
if (const auto *RD = Callee->getReturnType()->getAsRecordDecl()) {
Expand All @@ -478,13 +448,30 @@ static void visitLifetimeBoundArguments(IndirectLocalPath &Path, Expr *Call,
CheckCoroObjArg = false;
if (implicitObjectParamIsLifetimeBound(Callee) || CheckCoroObjArg)
VisitLifetimeBoundArg(Callee, ObjectArg);
else if (EnableLifetimeWarnings) {
if (auto *CME = dyn_cast<CXXMethodDecl>(Callee);
CME && shouldTrackImplicitObjectArg(CME))
VisitGSLPointerArg(Callee, ObjectArg,
!Callee->getReturnType()->isReferenceType());
}
}

for (unsigned I = 0,
N = std::min<unsigned>(Callee->getNumParams(), Args.size());
I != N; ++I) {
if (CheckCoroCall || Callee->getParamDecl(I)->hasAttr<LifetimeBoundAttr>())
VisitLifetimeBoundArg(Callee->getParamDecl(I), Args[I]);
else if (EnableLifetimeWarnings && I == 0) {
if (shouldTrackFirstArgument(Callee)) {
VisitGSLPointerArg(Callee, Args[0],
!Callee->getReturnType()->isReferenceType());
} else {
if (auto *CCE = dyn_cast<CXXConstructExpr>(Call);
CCE && CCE->getConstructor()->getParent()->hasAttr<PointerAttr>())
VisitGSLPointerArg(CCE->getConstructor()->getParamDecl(0), Args[0],
true);
}
}
}
}

Expand Down Expand Up @@ -557,11 +544,9 @@ static void visitLocalsRetainedByReferenceBinding(IndirectLocalPath &Path,
EnableLifetimeWarnings);
}

if (isa<CallExpr>(Init)) {
if (EnableLifetimeWarnings)
handleGslAnnotatedTypes(Path, Init, Visit);
return visitLifetimeBoundArguments(Path, Init, Visit);
}
if (isa<CallExpr>(Init))
return visitFunctionCallArguments(Path, Init, Visit,
EnableLifetimeWarnings);

switch (Init->getStmtClass()) {
case Stmt::DeclRefExprClass: {
Expand Down Expand Up @@ -835,11 +820,9 @@ static void visitLocalsRetainedByInitializer(IndirectLocalPath &Path,
}
}

if (isa<CallExpr>(Init) || isa<CXXConstructExpr>(Init)) {
if (EnableLifetimeWarnings)
handleGslAnnotatedTypes(Path, Init, Visit);
return visitLifetimeBoundArguments(Path, Init, Visit);
}
if (isa<CallExpr>(Init) || isa<CXXConstructExpr>(Init))
return visitFunctionCallArguments(Path, Init, Visit,
EnableLifetimeWarnings);

switch (Init->getStmtClass()) {
case Stmt::UnaryOperatorClass: {
Expand Down
20 changes: 20 additions & 0 deletions clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -479,3 +479,23 @@ void testForBug49342()
{
auto it = std::iter<char>{} - 2; // Used to be false positive.
}

namespace GH93386 {
// verify no duplicated diagnostics are emitted.
struct [[gsl::Pointer]] S {
S(const std::vector<int>& abc [[clang::lifetimebound]]);
};

S test(std::vector<int> a) {
return S(a); // expected-warning {{address of stack memory associated with}}
}

auto s = S(std::vector<int>()); // expected-warning {{temporary whose address is used as value of local variable}}

// Verify no regression on the follow case.
std::string_view test2(int i, std::optional<std::string_view> a) {
if (i)
return std::move(*a);
return std::move(a.value());
}
}