diff options
author | Haojian Wu <hokein.wu@gmail.com> | 2024-06-27 10:56:06 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-06-27 10:56:06 +0200 |
commit | 8a43dc3efdd9bfba0bea32061ef2f3397a968eb9 (patch) | |
tree | 363ff3e8b932cc25e19f252a6276c5ba3c5fe450 /clang/lib/Sema | |
parent | 9a9ec228cdcf75d01be82be5be13e1542f0fc75d (diff) | |
download | llvm-8a43dc3efdd9bfba0bea32061ef2f3397a968eb9.zip llvm-8a43dc3efdd9bfba0bea32061ef2f3397a968eb9.tar.gz llvm-8a43dc3efdd9bfba0bea32061ef2f3397a968eb9.tar.bz2 |
[clang][Sema] Move the initializer lifetime checking code from SemaInit.cpp to a new place, NFC (#96758)
This is a refactoring change for better code isolation and reuse, the
first step to extend it for assignments.
Diffstat (limited to 'clang/lib/Sema')
-rw-r--r-- | clang/lib/Sema/CMakeLists.txt | 1 | ||||
-rw-r--r-- | clang/lib/Sema/CheckExprLifetime.cpp | 1259 | ||||
-rw-r--r-- | clang/lib/Sema/CheckExprLifetime.h | 29 | ||||
-rw-r--r-- | clang/lib/Sema/SemaInit.cpp | 1232 |
4 files changed, 1291 insertions, 1230 deletions
diff --git a/clang/lib/Sema/CMakeLists.txt b/clang/lib/Sema/CMakeLists.txt index f152d24..980a83d 100644 --- a/clang/lib/Sema/CMakeLists.txt +++ b/clang/lib/Sema/CMakeLists.txt @@ -15,6 +15,7 @@ clang_tablegen(OpenCLBuiltins.inc -gen-clang-opencl-builtins add_clang_library(clangSema AnalysisBasedWarnings.cpp + CheckExprLifetime.cpp CodeCompleteConsumer.cpp DeclSpec.cpp DelayedDiagnostic.cpp diff --git a/clang/lib/Sema/CheckExprLifetime.cpp b/clang/lib/Sema/CheckExprLifetime.cpp new file mode 100644 index 0000000..54e2f1c --- /dev/null +++ b/clang/lib/Sema/CheckExprLifetime.cpp @@ -0,0 +1,1259 @@ +//===--- CheckExprLifetime.cpp --------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "CheckExprLifetime.h" +#include "clang/AST/Expr.h" +#include "clang/Sema/Sema.h" +#include "llvm/ADT/PointerIntPair.h" + +namespace clang::sema { +namespace { +enum LifetimeKind { + /// The lifetime of a temporary bound to this entity ends at the end of the + /// full-expression, and that's (probably) fine. + LK_FullExpression, + + /// The lifetime of a temporary bound to this entity is extended to the + /// lifeitme of the entity itself. + LK_Extended, + + /// The lifetime of a temporary bound to this entity probably ends too soon, + /// because the entity is allocated in a new-expression. + LK_New, + + /// The lifetime of a temporary bound to this entity ends too soon, because + /// the entity is a return object. + LK_Return, + + /// The lifetime of a temporary bound to this entity ends too soon, because + /// the entity is the result of a statement expression. + LK_StmtExprResult, + + /// This is a mem-initializer: if it would extend a temporary (other than via + /// a default member initializer), the program is ill-formed. + LK_MemInitializer, +}; +using LifetimeResult = + llvm::PointerIntPair<const InitializedEntity *, 3, LifetimeKind>; +} // namespace + +/// Determine the declaration which an initialized entity ultimately refers to, +/// for the purpose of lifetime-extending a temporary bound to a reference in +/// the initialization of \p Entity. +static LifetimeResult +getEntityLifetime(const InitializedEntity *Entity, + const InitializedEntity *InitField = nullptr) { + // C++11 [class.temporary]p5: + switch (Entity->getKind()) { + case InitializedEntity::EK_Variable: + // The temporary [...] persists for the lifetime of the reference + return {Entity, LK_Extended}; + + case InitializedEntity::EK_Member: + // For subobjects, we look at the complete object. + if (Entity->getParent()) + return getEntityLifetime(Entity->getParent(), Entity); + + // except: + // C++17 [class.base.init]p8: + // A temporary expression bound to a reference member in a + // mem-initializer is ill-formed. + // C++17 [class.base.init]p11: + // A temporary expression bound to a reference member from a + // default member initializer is ill-formed. + // + // The context of p11 and its example suggest that it's only the use of a + // default member initializer from a constructor that makes the program + // ill-formed, not its mere existence, and that it can even be used by + // aggregate initialization. + return {Entity, Entity->isDefaultMemberInitializer() ? LK_Extended + : LK_MemInitializer}; + + case InitializedEntity::EK_Binding: + // Per [dcl.decomp]p3, the binding is treated as a variable of reference + // type. + return {Entity, LK_Extended}; + + case InitializedEntity::EK_Parameter: + case InitializedEntity::EK_Parameter_CF_Audited: + // -- A temporary bound to a reference parameter in a function call + // persists until the completion of the full-expression containing + // the call. + return {nullptr, LK_FullExpression}; + + case InitializedEntity::EK_TemplateParameter: + // FIXME: This will always be ill-formed; should we eagerly diagnose it + // here? + return {nullptr, LK_FullExpression}; + + case InitializedEntity::EK_Result: + // -- The lifetime of a temporary bound to the returned value in a + // function return statement is not extended; the temporary is + // destroyed at the end of the full-expression in the return statement. + return {nullptr, LK_Return}; + + case InitializedEntity::EK_StmtExprResult: + // FIXME: Should we lifetime-extend through the result of a statement + // expression? + return {nullptr, LK_StmtExprResult}; + + case InitializedEntity::EK_New: + // -- A temporary bound to a reference in a new-initializer persists + // until the completion of the full-expression containing the + // new-initializer. + return {nullptr, LK_New}; + + case InitializedEntity::EK_Temporary: + case InitializedEntity::EK_CompoundLiteralInit: + case InitializedEntity::EK_RelatedResult: + // We don't yet know the storage duration of the surrounding temporary. + // Assume it's got full-expression duration for now, it will patch up our + // storage duration if that's not correct. + return {nullptr, LK_FullExpression}; + + case InitializedEntity::EK_ArrayElement: + // For subobjects, we look at the complete object. + return getEntityLifetime(Entity->getParent(), InitField); + + case InitializedEntity::EK_Base: + // For subobjects, we look at the complete object. + if (Entity->getParent()) + return getEntityLifetime(Entity->getParent(), InitField); + return {InitField, LK_MemInitializer}; + + case InitializedEntity::EK_Delegating: + // We can reach this case for aggregate initialization in a constructor: + // struct A { int &&r; }; + // struct B : A { B() : A{0} {} }; + // In this case, use the outermost field decl as the context. + 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: + // FIXME: Can we diagnose lifetime problems with exceptions? + return {nullptr, LK_FullExpression}; + + case InitializedEntity::EK_ParenAggInitMember: + // -- A temporary object bound to a reference element of an aggregate of + // class type initialized from a parenthesized expression-list + // [dcl.init, 9.3] persists until the completion of the full-expression + // containing the expression-list. + return {nullptr, LK_FullExpression}; + } + + llvm_unreachable("unknown entity kind"); +} + +namespace { +enum ReferenceKind { + /// Lifetime would be extended by a reference binding to a temporary. + RK_ReferenceBinding, + /// Lifetime would be extended by a std::initializer_list object binding to + /// its backing array. + RK_StdInitializerList, +}; + +/// A temporary or local variable. This will be one of: +/// * A MaterializeTemporaryExpr. +/// * A DeclRefExpr whose declaration is a local. +/// * An AddrLabelExpr. +/// * A BlockExpr for a block with captures. +using Local = Expr *; + +/// Expressions we stepped over when looking for the local state. Any steps +/// that would inhibit lifetime extension or take us out of subexpressions of +/// the initializer are included. +struct IndirectLocalPathEntry { + enum EntryKind { + DefaultInit, + AddressOf, + VarInit, + LValToRVal, + LifetimeBoundCall, + TemporaryCopy, + LambdaCaptureInit, + GslReferenceInit, + GslPointerInit + } 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)>; +} // namespace + +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; +} + +// Decl::isInStdNamespace will return false for iterators in some STL +// implementations due to them being defined in a namespace outside of the std +// namespace. +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) + // Map and set types. + .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 (!isRecordWithAttr<PointerAttr>(QualType(RD->getTypeForDecl(), 0)) && + !isRecordWithAttr<OwnerAttr>(QualType(RD->getTypeForDecl(), 0))) + 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) { + // 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) + 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) + return false; + // Don't declare this variable in the second operand of the for-statement; + // GCC miscompiles that by ending its lifetime before evaluating the + // third operand. See gcc.gnu.org/PR86769. + AttributedTypeLoc ATL; + for (TypeLoc TL = TSI->getTypeLoc(); + (ATL = TL.getAsAdjusted<AttributedTypeLoc>()); + TL = ATL.getModifiedLoc()) { + if (ATL.getAttrAs<LifetimeBoundAttr>()) + return true; + } + + // Assume that all assignment operators with a "normal" return type return + // *this, that is, an lvalue reference that is the same type as the implicit + // object parameter (or the LHS for a non-member operator$=). + 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, + /*EnableLifetimeWarnings=*/false); + else + visitLocalsRetainedByInitializer(Path, Arg, Visit, true, + /*EnableLifetimeWarnings=*/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; + // Coroutine lambda objects with empty capture list are not lifetimebound. + if (auto *LE = dyn_cast<LambdaExpr>(ObjectArg->IgnoreImplicit()); + LE && LE->captures().empty()) + CheckCoroObjArg = false; + // Allow `get_return_object()` as the object param (__promise) is not + // lifetimebound. + 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]); + } +} + +/// Visit the locals that would be reachable through a reference bound to the +/// glvalue expression \c Init. +static void visitLocalsRetainedByReferenceBinding(IndirectLocalPath &Path, + Expr *Init, ReferenceKind RK, + LocalVisitor Visit, + bool EnableLifetimeWarnings) { + RevertToOldSizeRAII RAII(Path); + + // Walk past any constructs which we can lifetime-extend across. + Expr *Old; + do { + Old = Init; + + if (auto *FE = dyn_cast<FullExpr>(Init)) + Init = FE->getSubExpr(); + + if (InitListExpr *ILE = dyn_cast<InitListExpr>(Init)) { + // If this is just redundant braces around an initializer, step over it. + if (ILE->isTransparent()) + Init = ILE->getInit(0); + } + + // Step over any subobject adjustments; we may have a materialized + // temporary inside them. + Init = const_cast<Expr *>(Init->skipRValueSubobjectAdjustments()); + + // Per current approach for DR1376, look through casts to reference type + // when performing lifetime extension. + if (CastExpr *CE = dyn_cast<CastExpr>(Init)) + if (CE->getSubExpr()->isGLValue()) + Init = CE->getSubExpr(); + + // Per the current approach for DR1299, look through array element access + // on array glvalues when performing lifetime extension. + 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 + // We can't lifetime extend through this but we might still find some + // retained temporaries. + return visitLocalsRetainedByInitializer(Path, Init, Visit, true, + EnableLifetimeWarnings); + } + + // Step into CXXDefaultInitExprs so we can diagnose cases where a + // constructor inherits one as an implicit mem-initializer. + 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 (isa<CallExpr>(Init)) { + if (EnableLifetimeWarnings) + handleGslAnnotatedTypes(Path, Init, Visit); + return visitLifetimeBoundArguments(Path, Init, Visit); + } + + switch (Init->getStmtClass()) { + case Stmt::DeclRefExprClass: { + // If we find the name of a local non-reference parameter, we could have a + // lifetime problem. + 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())) { + // The lifetime of a reference parameter is unknown; assume it's OK + // for now. + 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: { + // The only unary operator that make sense to handle here + // is Deref. All others don't resolve to a "name." This includes + // handling all sorts of rvalues passed to a unary operator. + 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; + } + + // FIXME: Visit the left-hand side of an -> or ->*. + + default: + break; + } +} + +/// Visit the locals that would be reachable through an object initialized by +/// the prvalue expression \c Init. +static void visitLocalsRetainedByInitializer(IndirectLocalPath &Path, + Expr *Init, LocalVisitor Visit, + bool RevisitSubinits, + bool EnableLifetimeWarnings) { + RevertToOldSizeRAII RAII(Path); + + Expr *Old; + do { + Old = Init; + + // Step into CXXDefaultInitExprs so we can diagnose cases where a + // constructor inherits one as an implicit mem-initializer. + 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(); + + // Dig out the expression which constructs the extended temporary. + Init = const_cast<Expr *>(Init->skipRValueSubobjectAdjustments()); + + if (CXXBindTemporaryExpr *BTE = dyn_cast<CXXBindTemporaryExpr>(Init)) + Init = BTE->getSubExpr(); + + Init = Init->IgnoreParens(); + + // Step over value-preserving rvalue casts. + if (auto *CE = dyn_cast<CastExpr>(Init)) { + switch (CE->getCastKind()) { + case CK_LValueToRValue: + // If we can match the lvalue to a const object, we can look at its + // initializer. + 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); + + // We assume that objects can be retained by pointers cast to integers, + // but not if the integer is cast to floating-point type or to _Complex. + // We assume that casts to 'bool' do not preserve enough information to + // retain a local object. + 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: + // Model array-to-pointer decay as taking the address of the array + // lvalue. + Path.push_back({IndirectLocalPathEntry::AddressOf, CE}); + return visitLocalsRetainedByReferenceBinding(Path, CE->getSubExpr(), + RK_ReferenceBinding, Visit, + EnableLifetimeWarnings); + + default: + return; + } + + Init = CE->getSubExpr(); + } + } while (Old != Init); + + // C++17 [dcl.init.list]p6: + // initializing an initializer_list object from the array extends the + // lifetime of the array exactly like binding a reference to a temporary. + if (auto *ILE = dyn_cast<CXXStdInitializerListExpr>(Init)) + return visitLocalsRetainedByReferenceBinding(Path, ILE->getSubExpr(), + RK_StdInitializerList, Visit, + EnableLifetimeWarnings); + + if (InitListExpr *ILE = dyn_cast<InitListExpr>(Init)) { + // We already visited the elements of this initializer list while + // performing the initialization. Don't visit them again unless we've + // changed the lifetime of the initialized entity. + 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 we lifetime-extend a braced initializer which is initializing an + // aggregate, and that aggregate contains reference members which are + // bound to temporaries, those temporaries are also lifetime-extended. + 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 + // This might be either aggregate-initialization of a member or + // initialization of a std::initializer_list object. Regardless, + // we should recursively lifetime-extend that initializer. + visitLocalsRetainedByInitializer( + Path, SubInit, Visit, RevisitSubinits, EnableLifetimeWarnings); + ++Index; + } + } + } + return; + } + + // The lifetime of an init-capture is that of the closure object constructed + // by a lambda-expression. + 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(); + } + } + + // Assume that a copy or move from a temporary references the same objects + // that the temporary does. + if (auto *CCE = dyn_cast<CXXConstructExpr>(Init)) { + if (CCE->getConstructor()->isCopyOrMoveConstructor()) { + if (auto *MTE = dyn_cast<MaterializeTemporaryExpr>(CCE->getArg(0))) { + // assert(false && "hit temporary copy path"); + Expr *Arg = MTE->getSubExpr(); + Path.push_back({IndirectLocalPathEntry::TemporaryCopy, Arg, + CCE->getConstructor()}); + visitLocalsRetainedByInitializer(Path, Arg, Visit, true, + /*EnableLifetimeWarnings*/ 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 the initializer is the address of a local, we could have a lifetime + // problem. + if (UO->getOpcode() == UO_AddrOf) { + // If this is &rvalue, then it's ill-formed and we have already diagnosed + // it. Don't produce a redundant warning about the lifetime of the + // temporary. + if (isa<MaterializeTemporaryExpr>(UO->getSubExpr())) + return; + + Path.push_back({IndirectLocalPathEntry::AddressOf, UO}); + visitLocalsRetainedByReferenceBinding(Path, UO->getSubExpr(), + RK_ReferenceBinding, Visit, + EnableLifetimeWarnings); + } + break; + } + + case Stmt::BinaryOperatorClass: { + // Handle pointer arithmetic. + 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); + // In C++, we can have a throw-expression operand, which has 'void' type + // and isn't interesting from a lifetime perspective. + 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()) { + // This is a local block, whose lifetime is that of the function. + Visit(Path, Local(cast<BlockExpr>(Init)), RK_ReferenceBinding); + } + break; + + case Stmt::AddrLabelExprClass: + // We want to warn if the address of a label would escape the function. + Visit(Path, Local(cast<AddrLabelExpr>(Init)), RK_ReferenceBinding); + break; + + default: + break; + } +} + +/// Whether a path to an object supports lifetime extension. +enum PathLifetimeKind { + /// Lifetime-extend along this path. + Extend, + /// We should lifetime-extend, but we don't because (due to technical + /// limitations) we can't. This happens for default member initializers, + /// which we don't clone for every use, so we don't have a unique + /// MaterializeTemporaryExpr to update. + ShouldExtend, + /// Do not lifetime extend along this path. + NoExtend +}; + +/// Determine whether this is an indirect path to a temporary that we are +/// supposed to lifetime-extend along. +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; +} + +/// Find the range for the first interesting entry in the path at or after I. +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: + // These exist primarily to mark the path as not permitting or + // supporting lifetime extension. + 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 pathOnlyInitializesGslPointer(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) + continue; + return It.Kind == IndirectLocalPathEntry::GslPointerInit || + It.Kind == IndirectLocalPathEntry::GslReferenceInit; + } + return false; +} + +void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity, + Expr *Init) { + LifetimeResult LR = getEntityLifetime(&Entity); + LifetimeKind LK = LR.getInt(); + const InitializedEntity *ExtendingEntity = LR.getPointer(); + + // If this entity doesn't have an interesting lifetime, don't bother looking + // for temporaries within its initializer. + 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 IsGslPtrInitWithGslTempOwner = false; + bool IsLocalGslOwner = false; + if (pathOnlyInitializesGslPointer(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: + // int &p = *localUniquePtr; + // someContainer.add(std::move(localUniquePtr)); + // return p; + IsLocalGslOwner = isRecordWithAttr<OwnerAttr>(L->getType()); + if (pathContainsInit(Path) || !IsLocalGslOwner) + return false; + } else { + IsGslPtrInitWithGslTempOwner = + 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) + return true; + } + } + + switch (LK) { + case LK_FullExpression: + llvm_unreachable("already handled this"); + + case LK_Extended: { + if (!MTE) { + // The initialized entity has lifetime beyond the full-expression, + // and the local entity does too, so don't warn. + // + // FIXME: We should consider warning if a static / thread storage + // duration variable retains an automatic storage duration local. + return false; + } + + if (IsGslPtrInitWithGslTempOwner && DiagLoc.isValid()) { + SemaRef.Diag(DiagLoc, diag::warn_dangling_lifetime_pointer) + << DiagRange; + return false; + } + + switch (shouldLifetimeExtendThroughPath(Path)) { + case PathLifetimeKind::Extend: + // Update the storage duration of the materialized temporary. + // FIXME: Rebuild the expression instead of mutating it. + MTE->setExtendingDecl(ExtendingEntity->getDecl(), + ExtendingEntity->allocateManglingNumber()); + // Also visit the temporaries lifetime-extended by this initializer. + return true; + + case PathLifetimeKind::ShouldExtend: + // We're supposed to lifetime-extend the temporary along this path (per + // the resolution of DR1815), but we don't support that yet. + // + // FIXME: Properly handle this situation. Perhaps the easiest approach + // would be to clone the initializer expression on each use that would + // lifetime extend its temporaries. + SemaRef.Diag(DiagLoc, diag::warn_unsupported_lifetime_extension) + << RK << DiagRange; + break; + + case PathLifetimeKind::NoExtend: + // If the path goes through the initialization of a variable or field, + // it can't possibly reach a temporary created in this full-expression. + // We will have already diagnosed any problems with the initializer. + if (pathContainsInit(Path)) + return false; + + SemaRef.Diag(DiagLoc, diag::warn_dangling_variable) + << RK << !Entity.getParent() + << ExtendingEntity->getDecl()->isImplicit() + << ExtendingEntity->getDecl() << Init->isGLValue() << DiagRange; + break; + } + break; + } + + case LK_MemInitializer: { + if (isa<MaterializeTemporaryExpr>(L)) { + // Under C++ DR1696, if a mem-initializer (or a default member + // initializer used by the absence of one) would lifetime-extend a + // temporary, the program is ill-formed. + if (auto *ExtendingDecl = + ExtendingEntity ? ExtendingEntity->getDecl() : nullptr) { + if (IsGslPtrInitWithGslTempOwner) { + 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 != &Entity; + SemaRef.Diag(DiagLoc, shouldLifetimeExtendThroughPath(Path) != + PathLifetimeKind::NoExtend + ? diag::err_dangling_member + : diag::warn_dangling_member) + << ExtendingDecl << IsSubobjectMember << RK << DiagRange; + // Don't bother adding a note pointing to the field if we're inside + // its default member initializer; our primary diagnostic points to + // the same place in that case. + if (Path.empty() || + Path.back().Kind != IndirectLocalPathEntry::DefaultInit) { + SemaRef.Diag(ExtendingDecl->getLocation(), + diag::note_lifetime_extending_member_declared_here) + << RK << IsSubobjectMember; + } + } else { + // We have a mem-initializer but no particular field within it; this + // is either a base class or a delegating initializer directly + // initializing the base-class from something that doesn't live long + // enough. + // + // FIXME: Warn on this. + return false; + } + } else { + // Paths via a default initializer can only occur during error recovery + // (there's no other way that a default initializer can refer to a + // local). Don't produce a bogus warning on those cases. + if (pathContainsInit(Path)) + return false; + + // Suppress false positives for code like the one below: + // Ctor(unique_ptr<T> up) : member(*up), member2(move(up)) {} + if (IsLocalGslOwner && pathOnlyInitializesGslPointer(Path)) + return false; + + auto *DRE = dyn_cast<DeclRefExpr>(L); + auto *VD = DRE ? dyn_cast<VarDecl>(DRE->getDecl()) : nullptr; + if (!VD) { + // A member was initialized to a local block. + // FIXME: Warn on this. + 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 (IsGslPtrInitWithGslTempOwner) + 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) + << !Entity.getParent() << DiagRange; + } else { + // We can't determine if the allocation outlives the local declaration. + return false; + } + break; + + case LK_Return: + case LK_StmtExprResult: + if (auto *DRE = dyn_cast<DeclRefExpr>(L)) { + // We can't determine if the local variable outlives the statement + // expression. + if (LK == LK_StmtExprResult) + return false; + SemaRef.Diag(DiagLoc, diag::warn_ret_stack_addr_ref) + << Entity.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)) { + // Don't warn when returning a label from a statement expression. + // Leaving the scope doesn't end its lifetime. + 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) + << Entity.getType()->isReferenceType() << CLE->getInitializer() << 2 + << DiagRange; + } else { + // P2748R5: Disallow Binding a Returned Glvalue to a Temporary. + // [stmt.return]/p6: In a function whose return type is a reference, + // other than an invented function for std::is_convertible ([meta.rel]), + // a return statement that binds the returned reference to a temporary + // expression ([class.temporary]) is ill-formed. + if (SemaRef.getLangOpts().CPlusPlus26 && + Entity.getType()->isReferenceType()) + SemaRef.Diag(DiagLoc, diag::err_ret_local_temp_ref) + << Entity.getType()->isReferenceType() << DiagRange; + else + SemaRef.Diag(DiagLoc, diag::warn_ret_local_temp_addr_ref) + << Entity.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: + // These exist primarily to mark the path as not permitting or + // supporting lifetime extension. + break; + + case IndirectLocalPathEntry::LifetimeBoundCall: + case IndirectLocalPathEntry::TemporaryCopy: + case IndirectLocalPathEntry::GslPointerInit: + case IndirectLocalPathEntry::GslReferenceInit: + // FIXME: Consider adding a note for these. + 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; + // FIXME: We can't easily tell apart an init-capture from a nested + // capture of an init-capture. + 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; + } + } + + // We didn't lifetime-extend, so don't go any further; we don't need more + // warnings or errors on inner temporaries within this one's initializer. + return false; + }; + + bool EnableLifetimeWarnings = !SemaRef.getDiagnostics().isIgnored( + diag::warn_dangling_lifetime_pointer, SourceLocation()); + llvm::SmallVector<IndirectLocalPathEntry, 8> Path; + if (Init->isGLValue()) + visitLocalsRetainedByReferenceBinding(Path, Init, RK_ReferenceBinding, + TemporaryVisitor, + EnableLifetimeWarnings); + else + visitLocalsRetainedByInitializer(Path, Init, TemporaryVisitor, false, + EnableLifetimeWarnings); +} + +} // namespace clang::sema diff --git a/clang/lib/Sema/CheckExprLifetime.h b/clang/lib/Sema/CheckExprLifetime.h new file mode 100644 index 0000000..f9cd3f5 --- /dev/null +++ b/clang/lib/Sema/CheckExprLifetime.h @@ -0,0 +1,29 @@ +//===- CheckExprLifetime.h ----------------------------------- -*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +//===----------------------------------------------------------------------===// +// +// This files implements a statement-local lifetime analysis. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_SEMA_CHECK_EXPR_LIFETIME_H +#define LLVM_CLANG_SEMA_CHECK_EXPR_LIFETIME_H + +#include "clang/AST/Expr.h" +#include "clang/Sema/Initialization.h" +#include "clang/Sema/Sema.h" + +namespace clang::sema { + +/// Check that the lifetime of the given expr (and its subobjects) is +/// sufficient for initializing the entity, and perform lifetime extension +/// (when permitted) if not. +void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity, + Expr *Init); + +} // namespace clang::sema + +#endif // LLVM_CLANG_SEMA_CHECK_EXPR_LIFETIME_H diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp index f820db5..26c65b3 100644 --- a/clang/lib/Sema/SemaInit.cpp +++ b/clang/lib/Sema/SemaInit.cpp @@ -10,6 +10,7 @@ // //===----------------------------------------------------------------------===// +#include "CheckExprLifetime.h" #include "clang/AST/ASTContext.h" #include "clang/AST/DeclObjC.h" #include "clang/AST/Expr.h" @@ -7284,1238 +7285,9 @@ PerformConstructorInitialization(Sema &S, return CurInit; } -namespace { -enum LifetimeKind { - /// The lifetime of a temporary bound to this entity ends at the end of the - /// full-expression, and that's (probably) fine. - LK_FullExpression, - - /// The lifetime of a temporary bound to this entity is extended to the - /// lifeitme of the entity itself. - LK_Extended, - - /// The lifetime of a temporary bound to this entity probably ends too soon, - /// because the entity is allocated in a new-expression. - LK_New, - - /// The lifetime of a temporary bound to this entity ends too soon, because - /// the entity is a return object. - LK_Return, - - /// The lifetime of a temporary bound to this entity ends too soon, because - /// the entity is the result of a statement expression. - LK_StmtExprResult, - - /// This is a mem-initializer: if it would extend a temporary (other than via - /// a default member initializer), the program is ill-formed. - LK_MemInitializer, -}; -using LifetimeResult = - llvm::PointerIntPair<const InitializedEntity *, 3, LifetimeKind>; -} - -/// Determine the declaration which an initialized entity ultimately refers to, -/// for the purpose of lifetime-extending a temporary bound to a reference in -/// the initialization of \p Entity. -static LifetimeResult getEntityLifetime( - const InitializedEntity *Entity, - const InitializedEntity *InitField = nullptr) { - // C++11 [class.temporary]p5: - switch (Entity->getKind()) { - case InitializedEntity::EK_Variable: - // The temporary [...] persists for the lifetime of the reference - return {Entity, LK_Extended}; - - case InitializedEntity::EK_Member: - // For subobjects, we look at the complete object. - if (Entity->getParent()) - return getEntityLifetime(Entity->getParent(), Entity); - - // except: - // C++17 [class.base.init]p8: - // A temporary expression bound to a reference member in a - // mem-initializer is ill-formed. - // C++17 [class.base.init]p11: - // A temporary expression bound to a reference member from a - // default member initializer is ill-formed. - // - // The context of p11 and its example suggest that it's only the use of a - // default member initializer from a constructor that makes the program - // ill-formed, not its mere existence, and that it can even be used by - // aggregate initialization. - return {Entity, Entity->isDefaultMemberInitializer() ? LK_Extended - : LK_MemInitializer}; - - case InitializedEntity::EK_Binding: - // Per [dcl.decomp]p3, the binding is treated as a variable of reference - // type. - return {Entity, LK_Extended}; - - case InitializedEntity::EK_Parameter: - case InitializedEntity::EK_Parameter_CF_Audited: - // -- A temporary bound to a reference parameter in a function call - // persists until the completion of the full-expression containing - // the call. - return {nullptr, LK_FullExpression}; - - case InitializedEntity::EK_TemplateParameter: - // FIXME: This will always be ill-formed; should we eagerly diagnose it here? - return {nullptr, LK_FullExpression}; - - case InitializedEntity::EK_Result: - // -- The lifetime of a temporary bound to the returned value in a - // function return statement is not extended; the temporary is - // destroyed at the end of the full-expression in the return statement. - return {nullptr, LK_Return}; - - case InitializedEntity::EK_StmtExprResult: - // FIXME: Should we lifetime-extend through the result of a statement - // expression? - return {nullptr, LK_StmtExprResult}; - - case InitializedEntity::EK_New: - // -- A temporary bound to a reference in a new-initializer persists - // until the completion of the full-expression containing the - // new-initializer. - return {nullptr, LK_New}; - - case InitializedEntity::EK_Temporary: - case InitializedEntity::EK_CompoundLiteralInit: - case InitializedEntity::EK_RelatedResult: - // We don't yet know the storage duration of the surrounding temporary. - // Assume it's got full-expression duration for now, it will patch up our - // storage duration if that's not correct. - return {nullptr, LK_FullExpression}; - - case InitializedEntity::EK_ArrayElement: - // For subobjects, we look at the complete object. - return getEntityLifetime(Entity->getParent(), InitField); - - case InitializedEntity::EK_Base: - // For subobjects, we look at the complete object. - if (Entity->getParent()) - return getEntityLifetime(Entity->getParent(), InitField); - return {InitField, LK_MemInitializer}; - - case InitializedEntity::EK_Delegating: - // We can reach this case for aggregate initialization in a constructor: - // struct A { int &&r; }; - // struct B : A { B() : A{0} {} }; - // In this case, use the outermost field decl as the context. - 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: - // FIXME: Can we diagnose lifetime problems with exceptions? - return {nullptr, LK_FullExpression}; - - case InitializedEntity::EK_ParenAggInitMember: - // -- A temporary object bound to a reference element of an aggregate of - // class type initialized from a parenthesized expression-list - // [dcl.init, 9.3] persists until the completion of the full-expression - // containing the expression-list. - return {nullptr, LK_FullExpression}; - } - - llvm_unreachable("unknown entity kind"); -} - -namespace { -enum ReferenceKind { - /// Lifetime would be extended by a reference binding to a temporary. - RK_ReferenceBinding, - /// Lifetime would be extended by a std::initializer_list object binding to - /// its backing array. - RK_StdInitializerList, -}; - -/// A temporary or local variable. This will be one of: -/// * A MaterializeTemporaryExpr. -/// * A DeclRefExpr whose declaration is a local. -/// * An AddrLabelExpr. -/// * A BlockExpr for a block with captures. -using Local = Expr*; - -/// Expressions we stepped over when looking for the local state. Any steps -/// that would inhibit lifetime extension or take us out of subexpressions of -/// the initializer are included. -struct IndirectLocalPathEntry { - enum EntryKind { - DefaultInit, - AddressOf, - VarInit, - LValToRVal, - LifetimeBoundCall, - TemporaryCopy, - LambdaCaptureInit, - GslReferenceInit, - GslPointerInit - } 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; -} - -// Decl::isInStdNamespace will return false for iterators in some STL -// implementations due to them being defined in a namespace outside of the std -// namespace. -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) - // Map and set types. - .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 (!isRecordWithAttr<PointerAttr>(QualType(RD->getTypeForDecl(), 0)) && - !isRecordWithAttr<OwnerAttr>(QualType(RD->getTypeForDecl(), 0))) - 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) { - // 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) - 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) - return false; - // Don't declare this variable in the second operand of the for-statement; - // GCC miscompiles that by ending its lifetime before evaluating the - // third operand. See gcc.gnu.org/PR86769. - AttributedTypeLoc ATL; - for (TypeLoc TL = TSI->getTypeLoc(); - (ATL = TL.getAsAdjusted<AttributedTypeLoc>()); - TL = ATL.getModifiedLoc()) { - if (ATL.getAttrAs<LifetimeBoundAttr>()) - return true; - } - - // Assume that all assignment operators with a "normal" return type return - // *this, that is, an lvalue reference that is the same type as the implicit - // object parameter (or the LHS for a non-member operator$=). - 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, - /*EnableLifetimeWarnings=*/false); - else - visitLocalsRetainedByInitializer(Path, Arg, Visit, true, - /*EnableLifetimeWarnings=*/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; - // Coroutine lambda objects with empty capture list are not lifetimebound. - if (auto *LE = dyn_cast<LambdaExpr>(ObjectArg->IgnoreImplicit()); - LE && LE->captures().empty()) - CheckCoroObjArg = false; - // Allow `get_return_object()` as the object param (__promise) is not - // lifetimebound. - 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]); - } -} - -/// Visit the locals that would be reachable through a reference bound to the -/// glvalue expression \c Init. -static void visitLocalsRetainedByReferenceBinding(IndirectLocalPath &Path, - Expr *Init, ReferenceKind RK, - LocalVisitor Visit, - bool EnableLifetimeWarnings) { - RevertToOldSizeRAII RAII(Path); - - // Walk past any constructs which we can lifetime-extend across. - Expr *Old; - do { - Old = Init; - - if (auto *FE = dyn_cast<FullExpr>(Init)) - Init = FE->getSubExpr(); - - if (InitListExpr *ILE = dyn_cast<InitListExpr>(Init)) { - // If this is just redundant braces around an initializer, step over it. - if (ILE->isTransparent()) - Init = ILE->getInit(0); - } - - // Step over any subobject adjustments; we may have a materialized - // temporary inside them. - Init = const_cast<Expr *>(Init->skipRValueSubobjectAdjustments()); - - // Per current approach for DR1376, look through casts to reference type - // when performing lifetime extension. - if (CastExpr *CE = dyn_cast<CastExpr>(Init)) - if (CE->getSubExpr()->isGLValue()) - Init = CE->getSubExpr(); - - // Per the current approach for DR1299, look through array element access - // on array glvalues when performing lifetime extension. - 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 - // We can't lifetime extend through this but we might still find some - // retained temporaries. - return visitLocalsRetainedByInitializer(Path, Init, Visit, true, - EnableLifetimeWarnings); - } - - // Step into CXXDefaultInitExprs so we can diagnose cases where a - // constructor inherits one as an implicit mem-initializer. - 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 (isa<CallExpr>(Init)) { - if (EnableLifetimeWarnings) - handleGslAnnotatedTypes(Path, Init, Visit); - return visitLifetimeBoundArguments(Path, Init, Visit); - } - - switch (Init->getStmtClass()) { - case Stmt::DeclRefExprClass: { - // If we find the name of a local non-reference parameter, we could have a - // lifetime problem. - 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())) { - // The lifetime of a reference parameter is unknown; assume it's OK - // for now. - 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: { - // The only unary operator that make sense to handle here - // is Deref. All others don't resolve to a "name." This includes - // handling all sorts of rvalues passed to a unary operator. - 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; - } - - // FIXME: Visit the left-hand side of an -> or ->*. - - default: - break; - } -} - -/// Visit the locals that would be reachable through an object initialized by -/// the prvalue expression \c Init. -static void visitLocalsRetainedByInitializer(IndirectLocalPath &Path, - Expr *Init, LocalVisitor Visit, - bool RevisitSubinits, - bool EnableLifetimeWarnings) { - RevertToOldSizeRAII RAII(Path); - - Expr *Old; - do { - Old = Init; - - // Step into CXXDefaultInitExprs so we can diagnose cases where a - // constructor inherits one as an implicit mem-initializer. - 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(); - - // Dig out the expression which constructs the extended temporary. - Init = const_cast<Expr *>(Init->skipRValueSubobjectAdjustments()); - - if (CXXBindTemporaryExpr *BTE = dyn_cast<CXXBindTemporaryExpr>(Init)) - Init = BTE->getSubExpr(); - - Init = Init->IgnoreParens(); - - // Step over value-preserving rvalue casts. - if (auto *CE = dyn_cast<CastExpr>(Init)) { - switch (CE->getCastKind()) { - case CK_LValueToRValue: - // If we can match the lvalue to a const object, we can look at its - // initializer. - 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); - - // We assume that objects can be retained by pointers cast to integers, - // but not if the integer is cast to floating-point type or to _Complex. - // We assume that casts to 'bool' do not preserve enough information to - // retain a local object. - 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: - // Model array-to-pointer decay as taking the address of the array - // lvalue. - Path.push_back({IndirectLocalPathEntry::AddressOf, CE}); - return visitLocalsRetainedByReferenceBinding(Path, CE->getSubExpr(), - RK_ReferenceBinding, Visit, - EnableLifetimeWarnings); - - default: - return; - } - - Init = CE->getSubExpr(); - } - } while (Old != Init); - - // C++17 [dcl.init.list]p6: - // initializing an initializer_list object from the array extends the - // lifetime of the array exactly like binding a reference to a temporary. - if (auto *ILE = dyn_cast<CXXStdInitializerListExpr>(Init)) - return visitLocalsRetainedByReferenceBinding(Path, ILE->getSubExpr(), - RK_StdInitializerList, Visit, - EnableLifetimeWarnings); - - if (InitListExpr *ILE = dyn_cast<InitListExpr>(Init)) { - // We already visited the elements of this initializer list while - // performing the initialization. Don't visit them again unless we've - // changed the lifetime of the initialized entity. - 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 we lifetime-extend a braced initializer which is initializing an - // aggregate, and that aggregate contains reference members which are - // bound to temporaries, those temporaries are also lifetime-extended. - 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 - // This might be either aggregate-initialization of a member or - // initialization of a std::initializer_list object. Regardless, - // we should recursively lifetime-extend that initializer. - visitLocalsRetainedByInitializer(Path, SubInit, Visit, - RevisitSubinits, - EnableLifetimeWarnings); - ++Index; - } - } - } - return; - } - - // The lifetime of an init-capture is that of the closure object constructed - // by a lambda-expression. - 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(); - } - } - - // Assume that a copy or move from a temporary references the same objects - // that the temporary does. - 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, - /*EnableLifetimeWarnings*/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 the initializer is the address of a local, we could have a lifetime - // problem. - if (UO->getOpcode() == UO_AddrOf) { - // If this is &rvalue, then it's ill-formed and we have already diagnosed - // it. Don't produce a redundant warning about the lifetime of the - // temporary. - if (isa<MaterializeTemporaryExpr>(UO->getSubExpr())) - return; - - Path.push_back({IndirectLocalPathEntry::AddressOf, UO}); - visitLocalsRetainedByReferenceBinding(Path, UO->getSubExpr(), - RK_ReferenceBinding, Visit, - EnableLifetimeWarnings); - } - break; - } - - case Stmt::BinaryOperatorClass: { - // Handle pointer arithmetic. - 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); - // In C++, we can have a throw-expression operand, which has 'void' type - // and isn't interesting from a lifetime perspective. - 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()) { - // This is a local block, whose lifetime is that of the function. - Visit(Path, Local(cast<BlockExpr>(Init)), RK_ReferenceBinding); - } - break; - - case Stmt::AddrLabelExprClass: - // We want to warn if the address of a label would escape the function. - Visit(Path, Local(cast<AddrLabelExpr>(Init)), RK_ReferenceBinding); - break; - - default: - break; - } -} - -/// Whether a path to an object supports lifetime extension. -enum PathLifetimeKind { - /// Lifetime-extend along this path. - Extend, - /// We should lifetime-extend, but we don't because (due to technical - /// limitations) we can't. This happens for default member initializers, - /// which we don't clone for every use, so we don't have a unique - /// MaterializeTemporaryExpr to update. - ShouldExtend, - /// Do not lifetime extend along this path. - NoExtend -}; - -/// Determine whether this is an indirect path to a temporary that we are -/// supposed to lifetime-extend along. -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; -} - -/// Find the range for the first interesting entry in the path at or after I. -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: - // These exist primarily to mark the path as not permitting or - // supporting lifetime extension. - 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 pathOnlyInitializesGslPointer(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) - continue; - return It.Kind == IndirectLocalPathEntry::GslPointerInit || - It.Kind == IndirectLocalPathEntry::GslReferenceInit; - } - return false; -} - void Sema::checkInitializerLifetime(const InitializedEntity &Entity, Expr *Init) { - LifetimeResult LR = getEntityLifetime(&Entity); - LifetimeKind LK = LR.getInt(); - const InitializedEntity *ExtendingEntity = LR.getPointer(); - - // If this entity doesn't have an interesting lifetime, don't bother looking - // for temporaries within its initializer. - 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 IsGslPtrInitWithGslTempOwner = false; - bool IsLocalGslOwner = false; - if (pathOnlyInitializesGslPointer(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: - // int &p = *localUniquePtr; - // someContainer.add(std::move(localUniquePtr)); - // return p; - IsLocalGslOwner = isRecordWithAttr<OwnerAttr>(L->getType()); - if (pathContainsInit(Path) || !IsLocalGslOwner) - return false; - } else { - IsGslPtrInitWithGslTempOwner = 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) - return true; - } - } - - switch (LK) { - case LK_FullExpression: - llvm_unreachable("already handled this"); - - case LK_Extended: { - if (!MTE) { - // The initialized entity has lifetime beyond the full-expression, - // and the local entity does too, so don't warn. - // - // FIXME: We should consider warning if a static / thread storage - // duration variable retains an automatic storage duration local. - return false; - } - - if (IsGslPtrInitWithGslTempOwner && DiagLoc.isValid()) { - Diag(DiagLoc, diag::warn_dangling_lifetime_pointer) << DiagRange; - return false; - } - - switch (shouldLifetimeExtendThroughPath(Path)) { - case PathLifetimeKind::Extend: - // Update the storage duration of the materialized temporary. - // FIXME: Rebuild the expression instead of mutating it. - MTE->setExtendingDecl(ExtendingEntity->getDecl(), - ExtendingEntity->allocateManglingNumber()); - // Also visit the temporaries lifetime-extended by this initializer. - return true; - - case PathLifetimeKind::ShouldExtend: - // We're supposed to lifetime-extend the temporary along this path (per - // the resolution of DR1815), but we don't support that yet. - // - // FIXME: Properly handle this situation. Perhaps the easiest approach - // would be to clone the initializer expression on each use that would - // lifetime extend its temporaries. - Diag(DiagLoc, diag::warn_unsupported_lifetime_extension) - << RK << DiagRange; - break; - - case PathLifetimeKind::NoExtend: - // If the path goes through the initialization of a variable or field, - // it can't possibly reach a temporary created in this full-expression. - // We will have already diagnosed any problems with the initializer. - if (pathContainsInit(Path)) - return false; - - Diag(DiagLoc, diag::warn_dangling_variable) - << RK << !Entity.getParent() - << ExtendingEntity->getDecl()->isImplicit() - << ExtendingEntity->getDecl() << Init->isGLValue() << DiagRange; - break; - } - break; - } - - case LK_MemInitializer: { - if (isa<MaterializeTemporaryExpr>(L)) { - // Under C++ DR1696, if a mem-initializer (or a default member - // initializer used by the absence of one) would lifetime-extend a - // temporary, the program is ill-formed. - if (auto *ExtendingDecl = - ExtendingEntity ? ExtendingEntity->getDecl() : nullptr) { - if (IsGslPtrInitWithGslTempOwner) { - Diag(DiagLoc, diag::warn_dangling_lifetime_pointer_member) - << ExtendingDecl << DiagRange; - Diag(ExtendingDecl->getLocation(), - diag::note_ref_or_ptr_member_declared_here) - << true; - return false; - } - bool IsSubobjectMember = ExtendingEntity != &Entity; - Diag(DiagLoc, shouldLifetimeExtendThroughPath(Path) != - PathLifetimeKind::NoExtend - ? diag::err_dangling_member - : diag::warn_dangling_member) - << ExtendingDecl << IsSubobjectMember << RK << DiagRange; - // Don't bother adding a note pointing to the field if we're inside - // its default member initializer; our primary diagnostic points to - // the same place in that case. - if (Path.empty() || - Path.back().Kind != IndirectLocalPathEntry::DefaultInit) { - Diag(ExtendingDecl->getLocation(), - diag::note_lifetime_extending_member_declared_here) - << RK << IsSubobjectMember; - } - } else { - // We have a mem-initializer but no particular field within it; this - // is either a base class or a delegating initializer directly - // initializing the base-class from something that doesn't live long - // enough. - // - // FIXME: Warn on this. - return false; - } - } else { - // Paths via a default initializer can only occur during error recovery - // (there's no other way that a default initializer can refer to a - // local). Don't produce a bogus warning on those cases. - if (pathContainsInit(Path)) - return false; - - // Suppress false positives for code like the one below: - // Ctor(unique_ptr<T> up) : member(*up), member2(move(up)) {} - if (IsLocalGslOwner && pathOnlyInitializesGslPointer(Path)) - return false; - - auto *DRE = dyn_cast<DeclRefExpr>(L); - auto *VD = DRE ? dyn_cast<VarDecl>(DRE->getDecl()) : nullptr; - if (!VD) { - // A member was initialized to a local block. - // FIXME: Warn on this. - return false; - } - - if (auto *Member = - ExtendingEntity ? ExtendingEntity->getDecl() : nullptr) { - bool IsPointer = !Member->getType()->isReferenceType(); - Diag(DiagLoc, IsPointer ? diag::warn_init_ptr_member_to_parameter_addr - : diag::warn_bind_ref_member_to_parameter) - << Member << VD << isa<ParmVarDecl>(VD) << DiagRange; - Diag(Member->getLocation(), - diag::note_ref_or_ptr_member_declared_here) - << (unsigned)IsPointer; - } - } - break; - } - - case LK_New: - if (isa<MaterializeTemporaryExpr>(L)) { - if (IsGslPtrInitWithGslTempOwner) - Diag(DiagLoc, diag::warn_dangling_lifetime_pointer) << DiagRange; - else - Diag(DiagLoc, RK == RK_ReferenceBinding - ? diag::warn_new_dangling_reference - : diag::warn_new_dangling_initializer_list) - << !Entity.getParent() << DiagRange; - } else { - // We can't determine if the allocation outlives the local declaration. - return false; - } - break; - - case LK_Return: - case LK_StmtExprResult: - if (auto *DRE = dyn_cast<DeclRefExpr>(L)) { - // We can't determine if the local variable outlives the statement - // expression. - if (LK == LK_StmtExprResult) - return false; - Diag(DiagLoc, diag::warn_ret_stack_addr_ref) - << Entity.getType()->isReferenceType() << DRE->getDecl() - << isa<ParmVarDecl>(DRE->getDecl()) << DiagRange; - } else if (isa<BlockExpr>(L)) { - Diag(DiagLoc, diag::err_ret_local_block) << DiagRange; - } else if (isa<AddrLabelExpr>(L)) { - // Don't warn when returning a label from a statement expression. - // Leaving the scope doesn't end its lifetime. - if (LK == LK_StmtExprResult) - return false; - Diag(DiagLoc, diag::warn_ret_addr_label) << DiagRange; - } else if (auto *CLE = dyn_cast<CompoundLiteralExpr>(L)) { - Diag(DiagLoc, diag::warn_ret_stack_addr_ref) - << Entity.getType()->isReferenceType() << CLE->getInitializer() << 2 - << DiagRange; - } else { - // P2748R5: Disallow Binding a Returned Glvalue to a Temporary. - // [stmt.return]/p6: In a function whose return type is a reference, - // other than an invented function for std::is_convertible ([meta.rel]), - // a return statement that binds the returned reference to a temporary - // expression ([class.temporary]) is ill-formed. - if (getLangOpts().CPlusPlus26 && Entity.getType()->isReferenceType()) - Diag(DiagLoc, diag::err_ret_local_temp_ref) - << Entity.getType()->isReferenceType() << DiagRange; - else - Diag(DiagLoc, diag::warn_ret_local_temp_addr_ref) - << Entity.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: - // These exist primarily to mark the path as not permitting or - // supporting lifetime extension. - break; - - case IndirectLocalPathEntry::LifetimeBoundCall: - case IndirectLocalPathEntry::TemporaryCopy: - case IndirectLocalPathEntry::GslPointerInit: - case IndirectLocalPathEntry::GslReferenceInit: - // FIXME: Consider adding a note for these. - break; - - case IndirectLocalPathEntry::DefaultInit: { - auto *FD = cast<FieldDecl>(Elem.D); - 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); - 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; - // FIXME: We can't easily tell apart an init-capture from a nested - // capture of an init-capture. - const ValueDecl *VD = Elem.Capture->getCapturedVar(); - 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; - } - } - - // We didn't lifetime-extend, so don't go any further; we don't need more - // warnings or errors on inner temporaries within this one's initializer. - return false; - }; - - bool EnableLifetimeWarnings = !getDiagnostics().isIgnored( - diag::warn_dangling_lifetime_pointer, SourceLocation()); - llvm::SmallVector<IndirectLocalPathEntry, 8> Path; - if (Init->isGLValue()) - visitLocalsRetainedByReferenceBinding(Path, Init, RK_ReferenceBinding, - TemporaryVisitor, - EnableLifetimeWarnings); - else - visitLocalsRetainedByInitializer(Path, Init, TemporaryVisitor, false, - EnableLifetimeWarnings); + return sema::checkExprLifetime(*this, Entity, Init); } static void DiagnoseNarrowingInInitList(Sema &S, |