#include "CheckExprLifetime.h"
#include "clang/AST/Decl.h"
#include "clang/AST/Expr.h"
#include "clang/Basic/DiagnosticSema.h"
#include "clang/Sema/Initialization.h"
#include "clang/Sema/Sema.h"
#include "llvm/ADT/PointerIntPair.h"
namespace clang::sema {
namespace {
enum LifetimeKind {
LK_FullExpression,
LK_Extended,
LK_New,
LK_Return,
LK_StmtExprResult,
LK_MemInitializer,
LK_Assignment,
};
using LifetimeResult =
llvm::PointerIntPair<const InitializedEntity *, 3, LifetimeKind>;
}
static LifetimeResult
getEntityLifetime(const InitializedEntity *Entity,
const InitializedEntity *InitField = nullptr) {
switch (Entity->getKind()) {
case InitializedEntity::EK_Variable:
return {Entity, LK_Extended};
case InitializedEntity::EK_Member:
if (Entity->getParent())
return getEntityLifetime(Entity->getParent(), Entity);
return {Entity, Entity->isDefaultMemberInitializer() ? LK_Extended
: LK_MemInitializer};
case InitializedEntity::EK_Binding:
return {Entity, LK_Extended};
case InitializedEntity::EK_Parameter:
case InitializedEntity::EK_Parameter_CF_Audited:
return {nullptr, LK_FullExpression};
case InitializedEntity::EK_TemplateParameter:
return {nullptr, LK_FullExpression};
case InitializedEntity::EK_Result:
return {nullptr, LK_Return};
case InitializedEntity::EK_StmtExprResult:
return {nullptr, LK_StmtExprResult};
case InitializedEntity::EK_New:
return {nullptr, LK_New};
case InitializedEntity::EK_Temporary:
case InitializedEntity::EK_CompoundLiteralInit:
case InitializedEntity::EK_RelatedResult:
return {nullptr, LK_FullExpression};
case InitializedEntity::EK_ArrayElement:
return getEntityLifetime(Entity->getParent(), InitField);
case InitializedEntity::EK_Base:
if (Entity->getParent())
return getEntityLifetime(Entity->getParent(), InitField);
return {InitField, LK_MemInitializer};
case InitializedEntity::EK_Delegating:
return {InitField, LK_MemInitializer};
case InitializedEntity::EK_BlockElement:
case InitializedEntity::EK_LambdaToBlockConversionBlockElement:
case InitializedEntity::EK_LambdaCapture:
case InitializedEntity::EK_VectorElement:
case InitializedEntity::EK_ComplexElement:
return {nullptr, LK_FullExpression};
case InitializedEntity::EK_Exception:
return {nullptr, LK_FullExpression};
case InitializedEntity::EK_ParenAggInitMember:
return {nullptr, LK_FullExpression};
}
llvm_unreachable("unknown entity kind");
}
namespace {
enum ReferenceKind {
RK_ReferenceBinding,
RK_StdInitializerList,
};
using Local = Expr *;
struct IndirectLocalPathEntry {
enum EntryKind {
DefaultInit,
AddressOf,
VarInit,
LValToRVal,
LifetimeBoundCall,
TemporaryCopy,
LambdaCaptureInit,
GslReferenceInit,
GslPointerInit,
GslPointerAssignment,
} Kind;
Expr *E;
union {
const Decl *D = nullptr;
const LambdaCapture *Capture;
};
IndirectLocalPathEntry() {}
IndirectLocalPathEntry(EntryKind K, Expr *E) : Kind(K), E(E) {}
IndirectLocalPathEntry(EntryKind K, Expr *E, const Decl *D)
: Kind(K), E(E), D(D) {}
IndirectLocalPathEntry(EntryKind K, Expr *E, const LambdaCapture *Capture)
: Kind(K), E(E), Capture(Capture) {}
};
using IndirectLocalPath = llvm::SmallVectorImpl<IndirectLocalPathEntry>;
struct RevertToOldSizeRAII {
IndirectLocalPath &Path;
unsigned OldSize = Path.size();
RevertToOldSizeRAII(IndirectLocalPath &Path) : Path(Path) {}
~RevertToOldSizeRAII() { Path.resize(OldSize); }
};
using LocalVisitor = llvm::function_ref<bool(IndirectLocalPath &Path, Local L,
ReferenceKind RK)>;
}
static bool isVarOnPath(IndirectLocalPath &Path, VarDecl *VD) {
for (auto E : Path)
if (E.Kind == IndirectLocalPathEntry::VarInit && E.D == VD)
return true;
return false;
}
static bool pathContainsInit(IndirectLocalPath &Path) {
return llvm::any_of(Path, [=](IndirectLocalPathEntry E) {
return E.Kind == IndirectLocalPathEntry::DefaultInit ||
E.Kind == IndirectLocalPathEntry::VarInit;
});
}
static void visitLocalsRetainedByInitializer(IndirectLocalPath &Path,
Expr *Init, LocalVisitor Visit,
bool RevisitSubinits,
bool EnableLifetimeWarnings);
static void visitLocalsRetainedByReferenceBinding(IndirectLocalPath &Path,
Expr *Init, ReferenceKind RK,
LocalVisitor Visit,
bool EnableLifetimeWarnings);
template <typename T> static bool isRecordWithAttr(QualType Type) {
if (auto *RD = Type->getAsCXXRecordDecl())
return RD->hasAttr<T>();
return false;
}
static bool isInStlNamespace(const Decl *D) {
const DeclContext *DC = D->getDeclContext();
if (!DC)
return false;
if (const auto *ND = dyn_cast<NamespaceDecl>(DC))
if (const IdentifierInfo *II = ND->getIdentifier()) {
StringRef Name = II->getName();
if (Name.size() >= 2 && Name.front() == '_' &&
(Name[1] == '_' || isUppercase(Name[1])))
return true;
}
return DC->isStdNamespace();
}
static bool shouldTrackImplicitObjectArg(const CXXMethodDecl *Callee) {
if (auto *Conv = dyn_cast_or_null<CXXConversionDecl>(Callee))
if (isRecordWithAttr<PointerAttr>(Conv->getConversionType()))
return true;
if (!isInStlNamespace(Callee->getParent()))
return false;
if (!isRecordWithAttr<PointerAttr>(
Callee->getFunctionObjectParameterType()) &&
!isRecordWithAttr<OwnerAttr>(Callee->getFunctionObjectParameterType()))
return false;
if (Callee->getReturnType()->isPointerType() ||
isRecordWithAttr<PointerAttr>(Callee->getReturnType())) {
if (!Callee->getIdentifier())
return false;
return llvm::StringSwitch<bool>(Callee->getName())
.Cases("begin", "rbegin", "cbegin", "crbegin", true)
.Cases("end", "rend", "cend", "crend", true)
.Cases("c_str", "data", "get", true)
.Cases("find", "equal_range", "lower_bound", "upper_bound", true)
.Default(false);
} else if (Callee->getReturnType()->isReferenceType()) {
if (!Callee->getIdentifier()) {
auto OO = Callee->getOverloadedOperator();
return OO == OverloadedOperatorKind::OO_Subscript ||
OO == OverloadedOperatorKind::OO_Star;
}
return llvm::StringSwitch<bool>(Callee->getName())
.Cases("front", "back", "at", "top", "value", true)
.Default(false);
}
return false;
}
static bool shouldTrackFirstArgument(const FunctionDecl *FD) {
if (!FD->getIdentifier() || FD->getNumParams() != 1)
return false;
const auto *RD = FD->getParamDecl(0)->getType()->getPointeeCXXRecordDecl();
if (!FD->isInStdNamespace() || !RD || !RD->isInStdNamespace())
return false;
if (!RD->hasAttr<PointerAttr>() && !RD->hasAttr<OwnerAttr>())
return false;
if (FD->getReturnType()->isPointerType() ||
isRecordWithAttr<PointerAttr>(FD->getReturnType())) {
return llvm::StringSwitch<bool>(FD->getName())
.Cases("begin", "rbegin", "cbegin", "crbegin", true)
.Cases("end", "rend", "cend", "crend", true)
.Case("data", true)
.Default(false);
} else if (FD->getReturnType()->isReferenceType()) {
return llvm::StringSwitch<bool>(FD->getName())
.Cases("get", "any_cast", true)
.Default(false);
}
return false;
}
static void handleGslAnnotatedTypes(IndirectLocalPath &Path, Expr *Call,
LocalVisitor Visit) {
auto VisitPointerArg = [&](const Decl *D, Expr *Arg, bool Value) {
if (isa<MemberExpr>(Arg->IgnoreImpCasts()))
return;
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,
true);
else
visitLocalsRetainedByInitializer(Path, Arg, Visit, true,
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)
return false;
AttributedTypeLoc ATL;
for (TypeLoc TL = TSI->getTypeLoc();
(ATL = TL.getAsAdjusted<AttributedTypeLoc>());
TL = ATL.getModifiedLoc()) {
if (ATL.getAttrAs<LifetimeBoundAttr>())
return true;
}
OverloadedOperatorKind OO = FD->getDeclName().getCXXOverloadedOperator();
if (OO == OO_Equal || isCompoundAssignmentOperator(OO)) {
QualType RetT = FD->getReturnType();
if (RetT->isLValueReferenceType()) {
ASTContext &Ctx = FD->getASTContext();
QualType LHST;
auto *MD = dyn_cast<CXXMethodDecl>(FD);
if (MD && MD->isCXXInstanceMember())
LHST = Ctx.getLValueReferenceType(MD->getFunctionObjectParameterType());
else
LHST = MD->getParamDecl(0)->getType();
if (Ctx.hasSameType(RetT, LHST))
return true;
}
}
return false;
}
static void visitLifetimeBoundArguments(IndirectLocalPath &Path, Expr *Call,
LocalVisitor Visit) {
const FunctionDecl *Callee;
ArrayRef<Expr *> Args;
if (auto *CE = dyn_cast<CallExpr>(Call)) {
Callee = CE->getDirectCallee();
Args = llvm::ArrayRef(CE->getArgs(), CE->getNumArgs());
} else {
auto *CCE = cast<CXXConstructExpr>(Call);
Callee = CCE->getConstructor();
Args = llvm::ArrayRef(CCE->getArgs(), CCE->getNumArgs());
}
if (!Callee)
return;
Expr *ObjectArg = nullptr;
if (isa<CXXOperatorCallExpr>(Call) && Callee->isCXXInstanceMember()) {
ObjectArg = Args[0];
Args = Args.slice(1);
} else if (auto *MCE = dyn_cast<CXXMemberCallExpr>(Call)) {
ObjectArg = MCE->getImplicitObjectArgument();
}
auto VisitLifetimeBoundArg = [&](const Decl *D, Expr *Arg) {
Path.push_back({IndirectLocalPathEntry::LifetimeBoundCall, Arg, D});
if (Arg->isGLValue())
visitLocalsRetainedByReferenceBinding(Path, Arg, RK_ReferenceBinding,
Visit,
false);
else
visitLocalsRetainedByInitializer(Path, Arg, Visit, true,
false);
Path.pop_back();
};
bool CheckCoroCall = false;
if (const auto *RD = Callee->getReturnType()->getAsRecordDecl()) {
CheckCoroCall = RD->hasAttr<CoroLifetimeBoundAttr>() &&
RD->hasAttr<CoroReturnTypeAttr>() &&
!Callee->hasAttr<CoroDisableLifetimeBoundAttr>();
}
if (ObjectArg) {
bool CheckCoroObjArg = CheckCoroCall;
if (auto *LE = dyn_cast<LambdaExpr>(ObjectArg->IgnoreImplicit());
LE && LE->captures().empty())
CheckCoroObjArg = false;
if (Sema::CanBeGetReturnObject(Callee))
CheckCoroObjArg = false;
if (implicitObjectParamIsLifetimeBound(Callee) || CheckCoroObjArg)
VisitLifetimeBoundArg(Callee, ObjectArg);
}
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]);
}
}
static void visitLocalsRetainedByReferenceBinding(IndirectLocalPath &Path,
Expr *Init, ReferenceKind RK,
LocalVisitor Visit,
bool EnableLifetimeWarnings) {
RevertToOldSizeRAII RAII(Path);
Expr *Old;
do {
Old = Init;
if (auto *FE = dyn_cast<FullExpr>(Init))
Init = FE->getSubExpr();
if (InitListExpr *ILE = dyn_cast<InitListExpr>(Init)) {
if (ILE->isTransparent())
Init = ILE->getInit(0);
}
Init = const_cast<Expr *>(Init->skipRValueSubobjectAdjustments());
if (CastExpr *CE = dyn_cast<CastExpr>(Init))
if (CE->getSubExpr()->isGLValue())
Init = CE->getSubExpr();
if (auto *ASE = dyn_cast<ArraySubscriptExpr>(Init)) {
Init = ASE->getBase();
auto *ICE = dyn_cast<ImplicitCastExpr>(Init);
if (ICE && ICE->getCastKind() == CK_ArrayToPointerDecay)
Init = ICE->getSubExpr();
else
return visitLocalsRetainedByInitializer(Path, Init, Visit, true,
EnableLifetimeWarnings);
}
if (auto *DIE = dyn_cast<CXXDefaultInitExpr>(Init)) {
Path.push_back(
{IndirectLocalPathEntry::DefaultInit, DIE, DIE->getField()});
Init = DIE->getExpr();
}
} while (Init != Old);
if (auto *MTE = dyn_cast<MaterializeTemporaryExpr>(Init)) {
if (Visit(Path, Local(MTE), RK))
visitLocalsRetainedByInitializer(Path, MTE->getSubExpr(), Visit, true,
EnableLifetimeWarnings);
}
if (auto *M = dyn_cast<MemberExpr>(Init)) {
if (auto *F = dyn_cast<FieldDecl>(M->getMemberDecl());
F && !F->getType()->isReferenceType())
visitLocalsRetainedByInitializer(Path, M->getBase(), Visit, true,
EnableLifetimeWarnings);
}
if (isa<CallExpr>(Init)) {
if (EnableLifetimeWarnings)
handleGslAnnotatedTypes(Path, Init, Visit);
return visitLifetimeBoundArguments(Path, Init, Visit);
}
switch (Init->getStmtClass()) {
case Stmt::DeclRefExprClass: {
auto *DRE = cast<DeclRefExpr>(Init);
auto *VD = dyn_cast<VarDecl>(DRE->getDecl());
if (VD && VD->hasLocalStorage() &&
!DRE->refersToEnclosingVariableOrCapture()) {
if (!VD->getType()->isReferenceType()) {
Visit(Path, Local(DRE), RK);
} else if (isa<ParmVarDecl>(DRE->getDecl())) {
break;
} else if (VD->getInit() && !isVarOnPath(Path, VD)) {
Path.push_back({IndirectLocalPathEntry::VarInit, DRE, VD});
visitLocalsRetainedByReferenceBinding(Path, VD->getInit(),
RK_ReferenceBinding, Visit,
EnableLifetimeWarnings);
}
}
break;
}
case Stmt::UnaryOperatorClass: {
const UnaryOperator *U = cast<UnaryOperator>(Init);
if (U->getOpcode() == UO_Deref)
visitLocalsRetainedByInitializer(Path, U->getSubExpr(), Visit, true,
EnableLifetimeWarnings);
break;
}
case Stmt::ArraySectionExprClass: {
visitLocalsRetainedByInitializer(Path,
cast<ArraySectionExpr>(Init)->getBase(),
Visit, true, EnableLifetimeWarnings);
break;
}
case Stmt::ConditionalOperatorClass:
case Stmt::BinaryConditionalOperatorClass: {
auto *C = cast<AbstractConditionalOperator>(Init);
if (!C->getTrueExpr()->getType()->isVoidType())
visitLocalsRetainedByReferenceBinding(Path, C->getTrueExpr(), RK, Visit,
EnableLifetimeWarnings);
if (!C->getFalseExpr()->getType()->isVoidType())
visitLocalsRetainedByReferenceBinding(Path, C->getFalseExpr(), RK, Visit,
EnableLifetimeWarnings);
break;
}
case Stmt::CompoundLiteralExprClass: {
if (auto *CLE = dyn_cast<CompoundLiteralExpr>(Init)) {
if (!CLE->isFileScope())
Visit(Path, Local(CLE), RK);
}
break;
}
default:
break;
}
}
static void visitLocalsRetainedByInitializer(IndirectLocalPath &Path,
Expr *Init, LocalVisitor Visit,
bool RevisitSubinits,
bool EnableLifetimeWarnings) {
RevertToOldSizeRAII RAII(Path);
Expr *Old;
do {
Old = Init;
if (auto *DIE = dyn_cast<CXXDefaultInitExpr>(Init)) {
Path.push_back(
{IndirectLocalPathEntry::DefaultInit, DIE, DIE->getField()});
Init = DIE->getExpr();
}
if (auto *FE = dyn_cast<FullExpr>(Init))
Init = FE->getSubExpr();
Init = const_cast<Expr *>(Init->skipRValueSubobjectAdjustments());
if (CXXBindTemporaryExpr *BTE = dyn_cast<CXXBindTemporaryExpr>(Init))
Init = BTE->getSubExpr();
Init = Init->IgnoreParens();
if (auto *CE = dyn_cast<CastExpr>(Init)) {
switch (CE->getCastKind()) {
case CK_LValueToRValue:
Path.push_back({IndirectLocalPathEntry::LValToRVal, CE});
return visitLocalsRetainedByReferenceBinding(
Path, Init, RK_ReferenceBinding,
[&](IndirectLocalPath &Path, Local L, ReferenceKind RK) -> bool {
if (auto *DRE = dyn_cast<DeclRefExpr>(L)) {
auto *VD = dyn_cast<VarDecl>(DRE->getDecl());
if (VD && VD->getType().isConstQualified() && VD->getInit() &&
!isVarOnPath(Path, VD)) {
Path.push_back({IndirectLocalPathEntry::VarInit, DRE, VD});
visitLocalsRetainedByInitializer(
Path, VD->getInit(), Visit, true, EnableLifetimeWarnings);
}
} else if (auto *MTE = dyn_cast<MaterializeTemporaryExpr>(L)) {
if (MTE->getType().isConstQualified())
visitLocalsRetainedByInitializer(Path, MTE->getSubExpr(),
Visit, true,
EnableLifetimeWarnings);
}
return false;
},
EnableLifetimeWarnings);
case CK_NoOp:
case CK_BitCast:
case CK_BaseToDerived:
case CK_DerivedToBase:
case CK_UncheckedDerivedToBase:
case CK_Dynamic:
case CK_ToUnion:
case CK_UserDefinedConversion:
case CK_ConstructorConversion:
case CK_IntegralToPointer:
case CK_PointerToIntegral:
case CK_VectorSplat:
case CK_IntegralCast:
case CK_CPointerToObjCPointerCast:
case CK_BlockPointerToObjCPointerCast:
case CK_AnyPointerToBlockPointerCast:
case CK_AddressSpaceConversion:
break;
case CK_ArrayToPointerDecay:
Path.push_back({IndirectLocalPathEntry::AddressOf, CE});
return visitLocalsRetainedByReferenceBinding(Path, CE->getSubExpr(),
RK_ReferenceBinding, Visit,
EnableLifetimeWarnings);
default:
return;
}
Init = CE->getSubExpr();
}
} while (Old != Init);
if (auto *ILE = dyn_cast<CXXStdInitializerListExpr>(Init))
return visitLocalsRetainedByReferenceBinding(Path, ILE->getSubExpr(),
RK_StdInitializerList, Visit,
EnableLifetimeWarnings);
if (InitListExpr *ILE = dyn_cast<InitListExpr>(Init)) {
if (!RevisitSubinits)
return;
if (ILE->isTransparent())
return visitLocalsRetainedByInitializer(Path, ILE->getInit(0), Visit,
RevisitSubinits,
EnableLifetimeWarnings);
if (ILE->getType()->isArrayType()) {
for (unsigned I = 0, N = ILE->getNumInits(); I != N; ++I)
visitLocalsRetainedByInitializer(Path, ILE->getInit(I), Visit,
RevisitSubinits,
EnableLifetimeWarnings);
return;
}
if (CXXRecordDecl *RD = ILE->getType()->getAsCXXRecordDecl()) {
assert(RD->isAggregate() && "aggregate init on non-aggregate");
if (RD->isUnion() && ILE->getInitializedFieldInUnion() &&
ILE->getInitializedFieldInUnion()->getType()->isReferenceType())
visitLocalsRetainedByReferenceBinding(Path, ILE->getInit(0),
RK_ReferenceBinding, Visit,
EnableLifetimeWarnings);
else {
unsigned Index = 0;
for (; Index < RD->getNumBases() && Index < ILE->getNumInits(); ++Index)
visitLocalsRetainedByInitializer(Path, ILE->getInit(Index), Visit,
RevisitSubinits,
EnableLifetimeWarnings);
for (const auto *I : RD->fields()) {
if (Index >= ILE->getNumInits())
break;
if (I->isUnnamedBitField())
continue;
Expr *SubInit = ILE->getInit(Index);
if (I->getType()->isReferenceType())
visitLocalsRetainedByReferenceBinding(Path, SubInit,
RK_ReferenceBinding, Visit,
EnableLifetimeWarnings);
else
visitLocalsRetainedByInitializer(
Path, SubInit, Visit, RevisitSubinits, EnableLifetimeWarnings);
++Index;
}
}
}
return;
}
if (auto *LE = dyn_cast<LambdaExpr>(Init)) {
LambdaExpr::capture_iterator CapI = LE->capture_begin();
for (Expr *E : LE->capture_inits()) {
assert(CapI != LE->capture_end());
const LambdaCapture &Cap = *CapI++;
if (!E)
continue;
if (Cap.capturesVariable())
Path.push_back({IndirectLocalPathEntry::LambdaCaptureInit, E, &Cap});
if (E->isGLValue())
visitLocalsRetainedByReferenceBinding(Path, E, RK_ReferenceBinding,
Visit, EnableLifetimeWarnings);
else
visitLocalsRetainedByInitializer(Path, E, Visit, true,
EnableLifetimeWarnings);
if (Cap.capturesVariable())
Path.pop_back();
}
}
if (auto *CCE = dyn_cast<CXXConstructExpr>(Init)) {
if (CCE->getConstructor()->isCopyOrMoveConstructor()) {
if (auto *MTE = dyn_cast<MaterializeTemporaryExpr>(CCE->getArg(0))) {
Expr *Arg = MTE->getSubExpr();
Path.push_back({IndirectLocalPathEntry::TemporaryCopy, Arg,
CCE->getConstructor()});
visitLocalsRetainedByInitializer(Path, Arg, Visit, true,
false);
Path.pop_back();
}
}
}
if (isa<CallExpr>(Init) || isa<CXXConstructExpr>(Init)) {
if (EnableLifetimeWarnings)
handleGslAnnotatedTypes(Path, Init, Visit);
return visitLifetimeBoundArguments(Path, Init, Visit);
}
switch (Init->getStmtClass()) {
case Stmt::UnaryOperatorClass: {
auto *UO = cast<UnaryOperator>(Init);
if (UO->getOpcode() == UO_AddrOf) {
if (isa<MaterializeTemporaryExpr>(UO->getSubExpr()))
return;
Path.push_back({IndirectLocalPathEntry::AddressOf, UO});
visitLocalsRetainedByReferenceBinding(Path, UO->getSubExpr(),
RK_ReferenceBinding, Visit,
EnableLifetimeWarnings);
}
break;
}
case Stmt::BinaryOperatorClass: {
auto *BO = cast<BinaryOperator>(Init);
BinaryOperatorKind BOK = BO->getOpcode();
if (!BO->getType()->isPointerType() || (BOK != BO_Add && BOK != BO_Sub))
break;
if (BO->getLHS()->getType()->isPointerType())
visitLocalsRetainedByInitializer(Path, BO->getLHS(), Visit, true,
EnableLifetimeWarnings);
else if (BO->getRHS()->getType()->isPointerType())
visitLocalsRetainedByInitializer(Path, BO->getRHS(), Visit, true,
EnableLifetimeWarnings);
break;
}
case Stmt::ConditionalOperatorClass:
case Stmt::BinaryConditionalOperatorClass: {
auto *C = cast<AbstractConditionalOperator>(Init);
if (!C->getTrueExpr()->getType()->isVoidType())
visitLocalsRetainedByInitializer(Path, C->getTrueExpr(), Visit, true,
EnableLifetimeWarnings);
if (!C->getFalseExpr()->getType()->isVoidType())
visitLocalsRetainedByInitializer(Path, C->getFalseExpr(), Visit, true,
EnableLifetimeWarnings);
break;
}
case Stmt::BlockExprClass:
if (cast<BlockExpr>(Init)->getBlockDecl()->hasCaptures()) {
Visit(Path, Local(cast<BlockExpr>(Init)), RK_ReferenceBinding);
}
break;
case Stmt::AddrLabelExprClass:
Visit(Path, Local(cast<AddrLabelExpr>(Init)), RK_ReferenceBinding);
break;
default:
break;
}
}
enum PathLifetimeKind {
Extend,
ShouldExtend,
NoExtend
};
static PathLifetimeKind
shouldLifetimeExtendThroughPath(const IndirectLocalPath &Path) {
PathLifetimeKind Kind = PathLifetimeKind::Extend;
for (auto Elem : Path) {
if (Elem.Kind == IndirectLocalPathEntry::DefaultInit)
Kind = PathLifetimeKind::ShouldExtend;
else if (Elem.Kind != IndirectLocalPathEntry::LambdaCaptureInit)
return PathLifetimeKind::NoExtend;
}
return Kind;
}
static SourceRange nextPathEntryRange(const IndirectLocalPath &Path, unsigned I,
Expr *E) {
for (unsigned N = Path.size(); I != N; ++I) {
switch (Path[I].Kind) {
case IndirectLocalPathEntry::AddressOf:
case IndirectLocalPathEntry::LValToRVal:
case IndirectLocalPathEntry::LifetimeBoundCall:
case IndirectLocalPathEntry::TemporaryCopy:
case IndirectLocalPathEntry::GslReferenceInit:
case IndirectLocalPathEntry::GslPointerInit:
case IndirectLocalPathEntry::GslPointerAssignment:
break;
case IndirectLocalPathEntry::VarInit:
if (cast<VarDecl>(Path[I].D)->isImplicit())
return SourceRange();
[[fallthrough]];
case IndirectLocalPathEntry::DefaultInit:
return Path[I].E->getSourceRange();
case IndirectLocalPathEntry::LambdaCaptureInit:
if (!Path[I].Capture->capturesVariable())
continue;
return Path[I].E->getSourceRange();
}
}
return E->getSourceRange();
}
static bool pathOnlyHandlesGslPointer(IndirectLocalPath &Path) {
for (const auto &It : llvm::reverse(Path)) {
switch (It.Kind) {
case IndirectLocalPathEntry::VarInit:
case IndirectLocalPathEntry::AddressOf:
case IndirectLocalPathEntry::LifetimeBoundCall:
continue;
case IndirectLocalPathEntry::GslPointerInit:
case IndirectLocalPathEntry::GslReferenceInit:
case IndirectLocalPathEntry::GslPointerAssignment:
return true;
default:
return false;
}
}
return false;
}
static void checkExprLifetimeImpl(Sema &SemaRef,
const InitializedEntity *InitEntity,
const InitializedEntity *ExtendingEntity,
LifetimeKind LK,
const AssignedEntity *AEntity, Expr *Init,
bool EnableLifetimeWarnings) {
assert((AEntity && LK == LK_Assignment) ||
(InitEntity && LK != LK_Assignment));
if (LK == LK_FullExpression)
return;
auto TemporaryVisitor = [&](IndirectLocalPath &Path, Local L,
ReferenceKind RK) -> bool {
SourceRange DiagRange = nextPathEntryRange(Path, 0, L);
SourceLocation DiagLoc = DiagRange.getBegin();
auto *MTE = dyn_cast<MaterializeTemporaryExpr>(L);
bool IsGslPtrValueFromGslTempOwner = false;
bool IsLocalGslOwner = false;
if (pathOnlyHandlesGslPointer(Path)) {
if (isa<DeclRefExpr>(L)) {
IsLocalGslOwner = isRecordWithAttr<OwnerAttr>(L->getType());
if (pathContainsInit(Path) || !IsLocalGslOwner)
return false;
} else {
IsGslPtrValueFromGslTempOwner =
MTE && !MTE->getExtendingDecl() &&
isRecordWithAttr<OwnerAttr>(MTE->getType());
if (!IsGslPtrValueFromGslTempOwner)
return true;
}
}
switch (LK) {
case LK_FullExpression:
llvm_unreachable("already handled this");
case LK_Extended: {
if (!MTE) {
return false;
}
if (IsGslPtrValueFromGslTempOwner && DiagLoc.isValid()) {
SemaRef.Diag(DiagLoc, diag::warn_dangling_lifetime_pointer)
<< DiagRange;
return false;
}
switch (shouldLifetimeExtendThroughPath(Path)) {
case PathLifetimeKind::Extend:
MTE->setExtendingDecl(ExtendingEntity->getDecl(),
ExtendingEntity->allocateManglingNumber());
return true;
case PathLifetimeKind::ShouldExtend:
SemaRef.Diag(DiagLoc, diag::warn_unsupported_lifetime_extension)
<< RK << DiagRange;
break;
case PathLifetimeKind::NoExtend:
if (pathContainsInit(Path))
return false;
SemaRef.Diag(DiagLoc, diag::warn_dangling_variable)
<< RK << !InitEntity->getParent()
<< ExtendingEntity->getDecl()->isImplicit()
<< ExtendingEntity->getDecl() << Init->isGLValue() << DiagRange;
break;
}
break;
}
case LK_Assignment: {
if (!MTE || pathContainsInit(Path))
return false;
assert(shouldLifetimeExtendThroughPath(Path) ==
PathLifetimeKind::NoExtend &&
"No lifetime extension for assignments");
SemaRef.Diag(DiagLoc,
IsGslPtrValueFromGslTempOwner
? diag::warn_dangling_lifetime_pointer_assignment
: diag::warn_dangling_pointer_assignment)
<< AEntity->LHS << DiagRange;
return false;
}
case LK_MemInitializer: {
if (MTE) {
if (auto *ExtendingDecl =
ExtendingEntity ? ExtendingEntity->getDecl() : nullptr) {
if (IsGslPtrValueFromGslTempOwner) {
SemaRef.Diag(DiagLoc, diag::warn_dangling_lifetime_pointer_member)
<< ExtendingDecl << DiagRange;
SemaRef.Diag(ExtendingDecl->getLocation(),
diag::note_ref_or_ptr_member_declared_here)
<< true;
return false;
}
bool IsSubobjectMember = ExtendingEntity != InitEntity;
SemaRef.Diag(DiagLoc, shouldLifetimeExtendThroughPath(Path) !=
PathLifetimeKind::NoExtend
? diag::err_dangling_member
: diag::warn_dangling_member)
<< ExtendingDecl << IsSubobjectMember << RK << DiagRange;
if (Path.empty() ||
Path.back().Kind != IndirectLocalPathEntry::DefaultInit) {
SemaRef.Diag(ExtendingDecl->getLocation(),
diag::note_lifetime_extending_member_declared_here)
<< RK << IsSubobjectMember;
}
} else {
return false;
}
} else {
if (pathContainsInit(Path))
return false;
if (IsLocalGslOwner && pathOnlyHandlesGslPointer(Path))
return false;
auto *DRE = dyn_cast<DeclRefExpr>(L);
auto *VD = DRE ? dyn_cast<VarDecl>(DRE->getDecl()) : nullptr;
if (!VD) {
return false;
}
if (auto *Member =
ExtendingEntity ? ExtendingEntity->getDecl() : nullptr) {
bool IsPointer = !Member->getType()->isReferenceType();
SemaRef.Diag(DiagLoc,
IsPointer ? diag::warn_init_ptr_member_to_parameter_addr
: diag::warn_bind_ref_member_to_parameter)
<< Member << VD << isa<ParmVarDecl>(VD) << DiagRange;
SemaRef.Diag(Member->getLocation(),
diag::note_ref_or_ptr_member_declared_here)
<< (unsigned)IsPointer;
}
}
break;
}
case LK_New:
if (isa<MaterializeTemporaryExpr>(L)) {
if (IsGslPtrValueFromGslTempOwner)
SemaRef.Diag(DiagLoc, diag::warn_dangling_lifetime_pointer)
<< DiagRange;
else
SemaRef.Diag(DiagLoc, RK == RK_ReferenceBinding
? diag::warn_new_dangling_reference
: diag::warn_new_dangling_initializer_list)
<< !InitEntity->getParent() << DiagRange;
} else {
return false;
}
break;
case LK_Return:
case LK_StmtExprResult:
if (auto *DRE = dyn_cast<DeclRefExpr>(L)) {
if (LK == LK_StmtExprResult)
return false;
SemaRef.Diag(DiagLoc, diag::warn_ret_stack_addr_ref)
<< InitEntity->getType()->isReferenceType() << DRE->getDecl()
<< isa<ParmVarDecl>(DRE->getDecl()) << DiagRange;
} else if (isa<BlockExpr>(L)) {
SemaRef.Diag(DiagLoc, diag::err_ret_local_block) << DiagRange;
} else if (isa<AddrLabelExpr>(L)) {
if (LK == LK_StmtExprResult)
return false;
SemaRef.Diag(DiagLoc, diag::warn_ret_addr_label) << DiagRange;
} else if (auto *CLE = dyn_cast<CompoundLiteralExpr>(L)) {
SemaRef.Diag(DiagLoc, diag::warn_ret_stack_addr_ref)
<< InitEntity->getType()->isReferenceType() << CLE->getInitializer()
<< 2 << DiagRange;
} else {
if (SemaRef.getLangOpts().CPlusPlus26 &&
InitEntity->getType()->isReferenceType())
SemaRef.Diag(DiagLoc, diag::err_ret_local_temp_ref)
<< InitEntity->getType()->isReferenceType() << DiagRange;
else
SemaRef.Diag(DiagLoc, diag::warn_ret_local_temp_addr_ref)
<< InitEntity->getType()->isReferenceType() << DiagRange;
}
break;
}
for (unsigned I = 0; I != Path.size(); ++I) {
auto Elem = Path[I];
switch (Elem.Kind) {
case IndirectLocalPathEntry::AddressOf:
case IndirectLocalPathEntry::LValToRVal:
break;
case IndirectLocalPathEntry::LifetimeBoundCall:
case IndirectLocalPathEntry::TemporaryCopy:
case IndirectLocalPathEntry::GslPointerInit:
case IndirectLocalPathEntry::GslReferenceInit:
case IndirectLocalPathEntry::GslPointerAssignment:
break;
case IndirectLocalPathEntry::DefaultInit: {
auto *FD = cast<FieldDecl>(Elem.D);
SemaRef.Diag(FD->getLocation(),
diag::note_init_with_default_member_initializer)
<< FD << nextPathEntryRange(Path, I + 1, L);
break;
}
case IndirectLocalPathEntry::VarInit: {
const VarDecl *VD = cast<VarDecl>(Elem.D);
SemaRef.Diag(VD->getLocation(), diag::note_local_var_initializer)
<< VD->getType()->isReferenceType() << VD->isImplicit()
<< VD->getDeclName() << nextPathEntryRange(Path, I + 1, L);
break;
}
case IndirectLocalPathEntry::LambdaCaptureInit:
if (!Elem.Capture->capturesVariable())
break;
const ValueDecl *VD = Elem.Capture->getCapturedVar();
SemaRef.Diag(Elem.Capture->getLocation(),
diag::note_lambda_capture_initializer)
<< VD << VD->isInitCapture() << Elem.Capture->isExplicit()
<< (Elem.Capture->getCaptureKind() == LCK_ByRef) << VD
<< nextPathEntryRange(Path, I + 1, L);
break;
}
}
return false;
};
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,
EnableLifetimeWarnings);
else
visitLocalsRetainedByInitializer(
Path, Init, TemporaryVisitor,
!InitEntity, EnableLifetimeWarnings);
}
void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity,
Expr *Init) {
auto LTResult = getEntityLifetime(&Entity);
LifetimeKind LK = LTResult.getInt();
const InitializedEntity *ExtendingEntity = LTResult.getPointer();
bool EnableLifetimeWarnings = !SemaRef.getDiagnostics().isIgnored(
diag::warn_dangling_lifetime_pointer, SourceLocation());
checkExprLifetimeImpl(SemaRef, &Entity, ExtendingEntity, LK,
nullptr, Init, EnableLifetimeWarnings);
}
void checkExprLifetime(Sema &SemaRef, const AssignedEntity &Entity,
Expr *Init) {
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, nullptr,
nullptr, LK_Assignment, &Entity,
Init, EnableLifetimeWarnings);
}
}