[clang] [Clang] Extend lifetime bound analysis to support assignments (PR #96475)
Haojian Wu via cfe-commits
cfe-commits at lists.llvm.org
Wed Jun 26 05:38:27 PDT 2024
https://github.com/hokein updated https://github.com/llvm/llvm-project/pull/96475
>From 3c5318893322d1f904a52b2f92d5ccd191aa5930 Mon Sep 17 00:00:00 2001
From: Haojian Wu <hokein.wu at gmail.com>
Date: Fri, 21 Jun 2024 11:48:33 +0200
Subject: [PATCH 1/2] [clang][sema] Move the initializer lifetime checking code
from SemaInit.cpp to a new place. NFC
This is a refactoring change for better code isolation and reuse, a
first step to extend it for assignments.
---
clang/lib/Sema/CMakeLists.txt | 1 +
clang/lib/Sema/CheckExprLifetime.cpp | 1258 ++++++++++++++++++++++++++
clang/lib/Sema/CheckExprLifetime.h | 29 +
clang/lib/Sema/SemaInit.cpp | 1231 +------------------------
4 files changed, 1290 insertions(+), 1229 deletions(-)
create mode 100644 clang/lib/Sema/CheckExprLifetime.cpp
create mode 100644 clang/lib/Sema/CheckExprLifetime.h
diff --git a/clang/lib/Sema/CMakeLists.txt b/clang/lib/Sema/CMakeLists.txt
index f152d243d39a5..980a83d4431aa 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 0000000000000..2b44330ef0336
--- /dev/null
+++ b/clang/lib/Sema/CheckExprLifetime.cpp
@@ -0,0 +1,1258 @@
+//===--- 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>;
+}
+
+/// 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))) {
+ // 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 0000000000000..f9cd3f55d3dbf
--- /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 f820db5233f53..389d1e26c848e 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,10 @@ 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,
>From 43ffbc27fe7d128586b54dbd33fd676532233032 Mon Sep 17 00:00:00 2001
From: Haojian Wu <hokein.wu at gmail.com>
Date: Mon, 24 Jun 2024 10:58:53 +0200
Subject: [PATCH 2/2] [clang] Extend the existing lifetimebound check for
assignments.
Currently we only detect the builtin pointer type.
---
clang/include/clang/Basic/DiagnosticGroups.td | 4 +-
.../clang/Basic/DiagnosticSemaKinds.td | 5 ++
clang/lib/Sema/CheckExprLifetime.cpp | 73 +++++++++++++------
clang/lib/Sema/CheckExprLifetime.h | 16 +++-
clang/lib/Sema/SemaExpr.cpp | 4 +
clang/lib/Sema/SemaInit.cpp | 3 +-
clang/test/Parser/compound_literal.c | 5 +-
clang/test/SemaCXX/attr-lifetimebound.cpp | 6 ++
clang/test/SemaCXX/warn-dangling-local.cpp | 2 +
9 files changed, 88 insertions(+), 30 deletions(-)
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index 9b37d4bd3205b..e828d0c459651 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -430,6 +430,7 @@ def LogicalOpParentheses: DiagGroup<"logical-op-parentheses">;
def LogicalNotParentheses: DiagGroup<"logical-not-parentheses">;
def ShiftOpParentheses: DiagGroup<"shift-op-parentheses">;
def OverloadedShiftOpParentheses: DiagGroup<"overloaded-shift-op-parentheses">;
+def DanglingAssignment: DiagGroup<"dangling-assignment">;
def DanglingElse: DiagGroup<"dangling-else">;
def DanglingField : DiagGroup<"dangling-field">;
def DanglingInitializerList : DiagGroup<"dangling-initializer-list">;
@@ -437,7 +438,8 @@ def DanglingGsl : DiagGroup<"dangling-gsl">;
def ReturnStackAddress : DiagGroup<"return-stack-address">;
// Name of this warning in GCC
def : DiagGroup<"return-local-addr", [ReturnStackAddress]>;
-def Dangling : DiagGroup<"dangling", [DanglingField,
+def Dangling : DiagGroup<"dangling", [DanglingAssignment,
+ DanglingField,
DanglingInitializerList,
DanglingGsl,
ReturnStackAddress]>;
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 25a87078a5709..207529660b37b 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10092,6 +10092,11 @@ def warn_new_dangling_initializer_list : Warning<
"the allocated initializer list}0 "
"will be destroyed at the end of the full-expression">,
InGroup<DanglingInitializerList>;
+def warn_dangling_pointer_assignment : Warning<
+ "object backing the pointer %0 "
+ "will be destroyed at the end of the full-expression">,
+ InGroup<DanglingAssignment>;
+
def warn_unsupported_lifetime_extension : Warning<
"lifetime extension of "
"%select{temporary|backing array of initializer list}0 created "
diff --git a/clang/lib/Sema/CheckExprLifetime.cpp b/clang/lib/Sema/CheckExprLifetime.cpp
index 2b44330ef0336..73b3fd2d3a138 100644
--- a/clang/lib/Sema/CheckExprLifetime.cpp
+++ b/clang/lib/Sema/CheckExprLifetime.cpp
@@ -8,6 +8,8 @@
#include "CheckExprLifetime.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"
@@ -210,7 +212,7 @@ struct RevertToOldSizeRAII {
using LocalVisitor = llvm::function_ref<bool(IndirectLocalPath &Path, Local L,
ReferenceKind RK)>;
-}
+} // namespace
static bool isVarOnPath(IndirectLocalPath &Path, VarDecl *VD) {
for (auto E : Path)
@@ -962,12 +964,27 @@ static bool pathOnlyInitializesGslPointer(IndirectLocalPath &Path) {
return false;
}
-
-void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity,
+void checkExprLifetime(Sema &SemaRef, const CheckingEntity &CEntity,
Expr *Init) {
- LifetimeResult LR = getEntityLifetime(&Entity);
- LifetimeKind LK = LR.getInt();
- const InitializedEntity *ExtendingEntity = LR.getPointer();
+ LifetimeKind LK = LK_FullExpression;
+
+ const AssignedEntity *AEntity = nullptr;
+ // Local variables for initialized entity.
+ const InitializedEntity *InitEntity = nullptr;
+ const InitializedEntity *ExtendingEntity = nullptr;
+ if (auto IEntityP = std::get_if<const InitializedEntity *>(&CEntity)) {
+ InitEntity = *IEntityP;
+ auto LTResult = getEntityLifetime(InitEntity);
+ LK = LTResult.getInt();
+ ExtendingEntity = LTResult.getPointer();
+ } else if (auto AEntityPointer =
+ std::get_if<const AssignedEntity *>(&CEntity)) {
+ AEntity = *AEntityPointer;
+ if (AEntity->LHS->getType()->isPointerType()) // builtin pointer type
+ LK = LK_Extended;
+ } else {
+ llvm_unreachable("unexpected kind");
+ }
// If this entity doesn't have an interesting lifetime, don't bother looking
// for temporaries within its initializer.
@@ -1026,6 +1043,7 @@ void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity,
switch (shouldLifetimeExtendThroughPath(Path)) {
case PathLifetimeKind::Extend:
+ assert(InitEntity && "Expect only on initializing the entity");
// Update the storage duration of the materialized temporary.
// FIXME: Rebuild the expression instead of mutating it.
MTE->setExtendingDecl(ExtendingEntity->getDecl(),
@@ -1034,6 +1052,7 @@ void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity,
return true;
case PathLifetimeKind::ShouldExtend:
+ assert(InitEntity && "Expect only on initializing the entity");
// We're supposed to lifetime-extend the temporary along this path (per
// the resolution of DR1815), but we don't support that yet.
//
@@ -1051,16 +1070,23 @@ void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity,
if (pathContainsInit(Path))
return false;
- SemaRef.Diag(DiagLoc, diag::warn_dangling_variable)
- << RK << !Entity.getParent()
- << ExtendingEntity->getDecl()->isImplicit()
- << ExtendingEntity->getDecl() << Init->isGLValue() << DiagRange;
+ if (InitEntity) {
+ SemaRef.Diag(DiagLoc, diag::warn_dangling_variable)
+ << RK << !InitEntity->getParent()
+ << ExtendingEntity->getDecl()->isImplicit()
+ << ExtendingEntity->getDecl() << Init->isGLValue() << DiagRange;
+ } else {
+ SemaRef.Diag(DiagLoc, diag::warn_dangling_pointer_assignment)
+ << AEntity->LHS << DiagRange;
+ }
break;
}
break;
}
case LK_MemInitializer: {
+ assert(InitEntity && "Expect only on initializing the entity");
+
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
@@ -1075,7 +1101,7 @@ void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity,
<< true;
return false;
}
- bool IsSubobjectMember = ExtendingEntity != &Entity;
+ bool IsSubobjectMember = ExtendingEntity != InitEntity;
SemaRef.Diag(DiagLoc, shouldLifetimeExtendThroughPath(Path) !=
PathLifetimeKind::NoExtend
? diag::err_dangling_member
@@ -1135,15 +1161,16 @@ void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity,
}
case LK_New:
+ assert(InitEntity && "Expect only on initializing the entity");
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;
+ ? diag::warn_new_dangling_reference
+ : diag::warn_new_dangling_initializer_list)
+ << !InitEntity->getParent() << DiagRange;
} else {
// We can't determine if the allocation outlives the local declaration.
return false;
@@ -1152,13 +1179,14 @@ void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity,
case LK_Return:
case LK_StmtExprResult:
+ assert(InitEntity && "Expect only on initializing the entity");
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()
+ << InitEntity->getType()->isReferenceType() << DRE->getDecl()
<< isa<ParmVarDecl>(DRE->getDecl()) << DiagRange;
} else if (isa<BlockExpr>(L)) {
SemaRef.Diag(DiagLoc, diag::err_ret_local_block) << DiagRange;
@@ -1170,8 +1198,8 @@ void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity,
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;
+ << InitEntity->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,
@@ -1179,12 +1207,12 @@ void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity,
// a return statement that binds the returned reference to a temporary
// expression ([class.temporary]) is ill-formed.
if (SemaRef.getLangOpts().CPlusPlus26 &&
- Entity.getType()->isReferenceType())
+ InitEntity->getType()->isReferenceType())
SemaRef.Diag(DiagLoc, diag::err_ret_local_temp_ref)
- << Entity.getType()->isReferenceType() << DiagRange;
+ << InitEntity->getType()->isReferenceType() << DiagRange;
else
SemaRef.Diag(DiagLoc, diag::warn_ret_local_temp_addr_ref)
- << Entity.getType()->isReferenceType() << DiagRange;
+ << InitEntity->getType()->isReferenceType() << DiagRange;
}
break;
}
@@ -1217,9 +1245,8 @@ void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity,
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);
+ << VD->getType()->isReferenceType() << VD->isImplicit()
+ << VD->getDeclName() << nextPathEntryRange(Path, I + 1, L);
break;
}
diff --git a/clang/lib/Sema/CheckExprLifetime.h b/clang/lib/Sema/CheckExprLifetime.h
index f9cd3f55d3dbf..b2944457d8e88 100644
--- a/clang/lib/Sema/CheckExprLifetime.h
+++ b/clang/lib/Sema/CheckExprLifetime.h
@@ -15,13 +15,23 @@
#include "clang/AST/Expr.h"
#include "clang/Sema/Initialization.h"
#include "clang/Sema/Sema.h"
+#include <variant>
namespace clang::sema {
+/// Describes an entity that is being assigned.
+struct AssignedEntity {
+ // The left-hand side expression of the assignment.
+ Expr *LHS = nullptr;
+};
+
+using CheckingEntity =
+ std::variant<const InitializedEntity *, const AssignedEntity *>;
+
/// 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,
+/// sufficient for initializing or assigning the entity, emit diagnostics if
+/// not.
+void checkExprLifetime(Sema &SemaRef, const CheckingEntity &CEntity,
Expr *Init);
} // namespace clang::sema
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 4a2f3a65eac96..0cf974dc56927 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -10,6 +10,7 @@
//
//===----------------------------------------------------------------------===//
+#include "CheckExprLifetime.h"
#include "TreeTransform.h"
#include "UsedDeclVisitor.h"
#include "clang/AST/ASTConsumer.h"
@@ -13777,6 +13778,9 @@ QualType Sema::CheckAssignmentOperands(Expr *LHSExpr, ExprResult &RHS,
return QualType();
CheckForNullPointerDereference(*this, LHSExpr);
+
+ AssignedEntity AE{LHSExpr};
+ checkExprLifetime(*this, &AE, RHS.get());
if (getLangOpts().CPlusPlus20 && LHSType.isVolatileQualified()) {
if (CompoundType.isNull()) {
diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp
index 389d1e26c848e..065d078eabef2 100644
--- a/clang/lib/Sema/SemaInit.cpp
+++ b/clang/lib/Sema/SemaInit.cpp
@@ -7288,7 +7288,8 @@ PerformConstructorInitialization(Sema &S,
void Sema::checkInitializerLifetime(const InitializedEntity &Entity,
Expr *Init) {
- return sema::checkExprLifetime(*this, Entity, Init);
+ sema::CheckingEntity CEntity(&Entity);
+ return sema::checkExprLifetime(*this, CEntity, Init);
}
static void DiagnoseNarrowingInInitList(Sema &S,
diff --git a/clang/test/Parser/compound_literal.c b/clang/test/Parser/compound_literal.c
index 808ca63f97d9e..281a6d7ada430 100644
--- a/clang/test/Parser/compound_literal.c
+++ b/clang/test/Parser/compound_literal.c
@@ -1,8 +1,9 @@
// RUN: %clang_cc1 -fsyntax-only -verify %s
-// RUN: %clang_cc1 -fsyntax-only -verify -x c++ %s
+// RUN: %clang_cc1 -fsyntax-only -verify -x c++ -Wno-dangling-assignment %s
// expected-no-diagnostics
int main(void) {
char *s;
- s = (char []){"whatever"};
+ // In C++ mode, the cast creates a "char [4]" array temporary here.
+ s = (char []){"whatever"}; // dangling!
s = (char(*)){s};
}
diff --git a/clang/test/SemaCXX/attr-lifetimebound.cpp b/clang/test/SemaCXX/attr-lifetimebound.cpp
index 5fcda6f23dab7..7c572ecef52ba 100644
--- a/clang/test/SemaCXX/attr-lifetimebound.cpp
+++ b/clang/test/SemaCXX/attr-lifetimebound.cpp
@@ -40,6 +40,12 @@ namespace usage_ok {
int *p = A().class_member(); // expected-warning {{temporary whose address is used as value of local variable 'p' will be destroyed at the end of the full-expression}}
int *q = A(); // expected-warning {{temporary whose address is used as value of local variable 'q' will be destroyed at the end of the full-expression}}
int *r = A(1); // expected-warning {{temporary whose address is used as value of local variable 'r' will be destroyed at the end of the full-expression}}
+
+ void test_assignment() {
+ p = A().class_member(); // expected-warning {{object backing the pointer p will be destroyed at the end of the full-expression}}
+ q = A(); // expected-warning {{object backing the pointer q will be destroyed at the end of the full-expression}}
+ r = A(1); // expected-warning {{object backing the pointer r will be destroyed at the end of the full-expression}}
+ }
}
# 1 "<std>" 1 3
diff --git a/clang/test/SemaCXX/warn-dangling-local.cpp b/clang/test/SemaCXX/warn-dangling-local.cpp
index 5c1d56972945c..a946b8a241a38 100644
--- a/clang/test/SemaCXX/warn-dangling-local.cpp
+++ b/clang/test/SemaCXX/warn-dangling-local.cpp
@@ -4,8 +4,10 @@ using T = int[];
void f() {
int *p = &(int&)(int&&)0; // expected-warning {{temporary whose address is used as value of local variable 'p' will be destroyed at the end of the full-expression}}
+ p = &(int&)(int&&)0; // expected-warning {{object backing the pointer p will be destroyed at the end of the full-expression}}
int *q = (int *const &)T{1, 2, 3}; // expected-warning {{temporary whose address is used as value of local variable 'q' will be destroyed at the end of the full-expression}}
+ q = (int *const &)T{1, 2, 3}; // expected-warning {{object backing the pointer q will be destroyed at the end of the full-expression}}
// FIXME: We don't warn here because the 'int*' temporary is not const, but
// it also can't have actually changed since it was created, so we could
More information about the cfe-commits
mailing list