fixes #159434 In HLSL matrices are matrix_type in all respects except that they support a constructor style syntax for initializing matrices. This change adds a translation of vector constructor arguments into initializer lists. This supports the following HLSL syntax: (1) HLSL matrices support constructor syntax (2) HLSL matrices are expanded to constituate components in constructor using the same initalizer list behavior defined in transformInitList allows us to support struct element initalization via HLSLElementwiseCast
1629 lines
63 KiB
C++
1629 lines
63 KiB
C++
//===--- 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/Decl.h"
|
|
#include "clang/AST/Expr.h"
|
|
#include "clang/AST/Type.h"
|
|
#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
|
|
#include "clang/Basic/DiagnosticSema.h"
|
|
#include "clang/Sema/Initialization.h"
|
|
#include "clang/Sema/Sema.h"
|
|
#include "llvm/ADT/PointerIntPair.h"
|
|
|
|
namespace clang::sema {
|
|
namespace {
|
|
enum LifetimeKind {
|
|
/// 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 passed to a musttail function call.
|
|
LK_MustTail,
|
|
|
|
/// 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,
|
|
|
|
/// The lifetime of a temporary bound to this entity may end too soon,
|
|
/// because the entity is a pointer and we assign the address of a temporary
|
|
/// object to it.
|
|
LK_Assignment,
|
|
|
|
/// The lifetime of a temporary bound to this entity may end too soon,
|
|
/// because the entity may capture the reference to a temporary object.
|
|
LK_LifetimeCapture,
|
|
};
|
|
using LifetimeResult =
|
|
llvm::PointerIntPair<const InitializedEntity *, 3, LifetimeKind>;
|
|
} // namespace
|
|
|
|
/// Determine the declaration which an initialized entity ultimately refers to,
|
|
/// for the purpose of lifetime-extending a temporary bound to a reference in
|
|
/// the initialization of \p Entity.
|
|
static LifetimeResult
|
|
getEntityLifetime(const InitializedEntity *Entity,
|
|
const InitializedEntity *InitField = nullptr) {
|
|
// C++11 [class.temporary]p5:
|
|
switch (Entity->getKind()) {
|
|
case InitializedEntity::EK_Variable:
|
|
// The temporary [...] persists for the lifetime of the reference
|
|
return {Entity, LK_Extended};
|
|
|
|
case InitializedEntity::EK_Member:
|
|
// For subobjects, we look at the complete object.
|
|
if (Entity->getParent())
|
|
return getEntityLifetime(Entity->getParent(), Entity);
|
|
|
|
// except:
|
|
// C++17 [class.base.init]p8:
|
|
// A temporary expression bound to a reference member in a
|
|
// mem-initializer is ill-formed.
|
|
// C++17 [class.base.init]p11:
|
|
// A temporary expression bound to a reference member from a
|
|
// default member initializer is ill-formed.
|
|
//
|
|
// The context of p11 and its example suggest that it's only the use of a
|
|
// default member initializer from a constructor that makes the program
|
|
// ill-formed, not its mere existence, and that it can even be used by
|
|
// aggregate initialization.
|
|
return {Entity, Entity->isDefaultMemberInitializer() ? LK_Extended
|
|
: LK_MemInitializer};
|
|
|
|
case InitializedEntity::EK_Binding:
|
|
// Per [dcl.decomp]p3, the binding is treated as a variable of reference
|
|
// type.
|
|
return {Entity, LK_Extended};
|
|
|
|
case InitializedEntity::EK_Parameter:
|
|
case InitializedEntity::EK_Parameter_CF_Audited:
|
|
// -- A temporary bound to a reference parameter in a function call
|
|
// persists until the completion of the full-expression containing
|
|
// the call.
|
|
return {nullptr, LK_FullExpression};
|
|
|
|
case InitializedEntity::EK_TemplateParameter:
|
|
// FIXME: This will always be ill-formed; should we eagerly diagnose it
|
|
// here?
|
|
return {nullptr, LK_FullExpression};
|
|
|
|
case InitializedEntity::EK_Result:
|
|
// -- The lifetime of a temporary bound to the returned value in a
|
|
// function return statement is not extended; the temporary is
|
|
// destroyed at the end of the full-expression in the return statement.
|
|
return {nullptr, LK_Return};
|
|
|
|
case InitializedEntity::EK_StmtExprResult:
|
|
// FIXME: Should we lifetime-extend through the result of a statement
|
|
// expression?
|
|
return {nullptr, LK_StmtExprResult};
|
|
|
|
case InitializedEntity::EK_New:
|
|
// -- A temporary bound to a reference in a new-initializer persists
|
|
// until the completion of the full-expression containing the
|
|
// new-initializer.
|
|
return {nullptr, LK_New};
|
|
|
|
case InitializedEntity::EK_Temporary:
|
|
case InitializedEntity::EK_CompoundLiteralInit:
|
|
case InitializedEntity::EK_RelatedResult:
|
|
// We don't yet know the storage duration of the surrounding temporary.
|
|
// Assume it's got full-expression duration for now, it will patch up our
|
|
// storage duration if that's not correct.
|
|
return {nullptr, LK_FullExpression};
|
|
|
|
case InitializedEntity::EK_ArrayElement:
|
|
// For subobjects, we look at the complete object.
|
|
return getEntityLifetime(Entity->getParent(), InitField);
|
|
|
|
case InitializedEntity::EK_Base:
|
|
// For subobjects, we look at the complete object.
|
|
if (Entity->getParent())
|
|
return getEntityLifetime(Entity->getParent(), InitField);
|
|
return {InitField, LK_MemInitializer};
|
|
|
|
case InitializedEntity::EK_Delegating:
|
|
// We can reach this case for aggregate initialization in a constructor:
|
|
// struct A { int &&r; };
|
|
// struct B : A { B() : A{0} {} };
|
|
// In this case, use the outermost field decl as the context.
|
|
return {InitField, LK_MemInitializer};
|
|
|
|
case InitializedEntity::EK_BlockElement:
|
|
case InitializedEntity::EK_LambdaToBlockConversionBlockElement:
|
|
case InitializedEntity::EK_LambdaCapture:
|
|
case InitializedEntity::EK_VectorElement:
|
|
case InitializedEntity::EK_MatrixElement:
|
|
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,
|
|
MemberExpr,
|
|
GslReferenceInit,
|
|
GslPointerInit,
|
|
GslPointerAssignment,
|
|
DefaultArg,
|
|
ParenAggInit,
|
|
} Kind;
|
|
Expr *E;
|
|
union {
|
|
const Decl *D = nullptr;
|
|
const LambdaCapture *Capture;
|
|
};
|
|
IndirectLocalPathEntry() {}
|
|
IndirectLocalPathEntry(EntryKind K, Expr *E) : Kind(K), E(E) {}
|
|
IndirectLocalPathEntry(EntryKind K, Expr *E, const Decl *D)
|
|
: Kind(K), E(E), D(D) {}
|
|
IndirectLocalPathEntry(EntryKind K, Expr *E, const LambdaCapture *Capture)
|
|
: Kind(K), E(E), Capture(Capture) {}
|
|
};
|
|
|
|
using IndirectLocalPath = llvm::SmallVectorImpl<IndirectLocalPathEntry>;
|
|
|
|
struct RevertToOldSizeRAII {
|
|
IndirectLocalPath &Path;
|
|
unsigned OldSize = Path.size();
|
|
RevertToOldSizeRAII(IndirectLocalPath &Path) : Path(Path) {}
|
|
~RevertToOldSizeRAII() { Path.resize(OldSize); }
|
|
};
|
|
|
|
using LocalVisitor = llvm::function_ref<bool(IndirectLocalPath &Path, Local L,
|
|
ReferenceKind RK)>;
|
|
} // namespace
|
|
|
|
static bool isVarOnPath(const IndirectLocalPath &Path, VarDecl *VD) {
|
|
for (auto E : Path)
|
|
if (E.Kind == IndirectLocalPathEntry::VarInit && E.D == VD)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
static bool pathContainsInit(const 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);
|
|
|
|
static void visitLocalsRetainedByReferenceBinding(IndirectLocalPath &Path,
|
|
Expr *Init, ReferenceKind RK,
|
|
LocalVisitor Visit);
|
|
|
|
template <typename T> static bool isRecordWithAttr(QualType Type) {
|
|
auto *RD = Type->getAsCXXRecordDecl();
|
|
if (!RD)
|
|
return false;
|
|
// Generally, if a primary template class declaration is annotated with an
|
|
// attribute, all its specializations generated from template instantiations
|
|
// should inherit the attribute.
|
|
//
|
|
// However, since lifetime analysis occurs during parsing, we may encounter
|
|
// cases where a full definition of the specialization is not required. In
|
|
// such cases, the specialization declaration remains incomplete and lacks the
|
|
// attribute. Therefore, we fall back to checking the primary template class.
|
|
//
|
|
// Note: it is possible for a specialization declaration to have an attribute
|
|
// even if the primary template does not.
|
|
//
|
|
// FIXME: What if the primary template and explicit specialization
|
|
// declarations have conflicting attributes? We should consider diagnosing
|
|
// this scenario.
|
|
bool Result = RD->hasAttr<T>();
|
|
|
|
if (auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(RD))
|
|
Result |= CTSD->getSpecializedTemplate()->getTemplatedDecl()->hasAttr<T>();
|
|
|
|
return Result;
|
|
}
|
|
|
|
// Tells whether the type is annotated with [[gsl::Pointer]].
|
|
bool isGLSPointerType(QualType QT) { return isRecordWithAttr<PointerAttr>(QT); }
|
|
|
|
static bool isPointerLikeType(QualType QT) {
|
|
return isGLSPointerType(QT) || QT->isPointerType() || QT->isNullPtrType();
|
|
}
|
|
|
|
// 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();
|
|
}
|
|
|
|
// Returns true if the given Record decl is a form of `GSLOwner<Pointer>`
|
|
// type, e.g. std::vector<string_view>, std::optional<string_view>.
|
|
static bool isContainerOfPointer(const RecordDecl *Container) {
|
|
if (const auto *CTSD =
|
|
dyn_cast_if_present<ClassTemplateSpecializationDecl>(Container)) {
|
|
if (!CTSD->hasAttr<OwnerAttr>()) // Container must be a GSL owner type.
|
|
return false;
|
|
const auto &TAs = CTSD->getTemplateArgs();
|
|
return TAs.size() > 0 && TAs[0].getKind() == TemplateArgument::Type &&
|
|
isPointerLikeType(TAs[0].getAsType());
|
|
}
|
|
return false;
|
|
}
|
|
static bool isContainerOfOwner(const RecordDecl *Container) {
|
|
const auto *CTSD =
|
|
dyn_cast_if_present<ClassTemplateSpecializationDecl>(Container);
|
|
if (!CTSD)
|
|
return false;
|
|
if (!CTSD->hasAttr<OwnerAttr>()) // Container must be a GSL owner type.
|
|
return false;
|
|
const auto &TAs = CTSD->getTemplateArgs();
|
|
return TAs.size() > 0 && TAs[0].getKind() == TemplateArgument::Type &&
|
|
isRecordWithAttr<OwnerAttr>(TAs[0].getAsType());
|
|
}
|
|
|
|
// Returns true if the given Record is `std::initializer_list<pointer>`.
|
|
static bool isStdInitializerListOfPointer(const RecordDecl *RD) {
|
|
if (const auto *CTSD =
|
|
dyn_cast_if_present<ClassTemplateSpecializationDecl>(RD)) {
|
|
const auto &TAs = CTSD->getTemplateArgs();
|
|
return isInStlNamespace(RD) && RD->getIdentifier() &&
|
|
RD->getName() == "initializer_list" && TAs.size() > 0 &&
|
|
TAs[0].getKind() == TemplateArgument::Type &&
|
|
isPointerLikeType(TAs[0].getAsType());
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool shouldTrackImplicitObjectArg(const CXXMethodDecl *Callee) {
|
|
if (auto *Conv = dyn_cast_or_null<CXXConversionDecl>(Callee))
|
|
if (isRecordWithAttr<PointerAttr>(Conv->getConversionType()) &&
|
|
Callee->getParent()->hasAttr<OwnerAttr>())
|
|
return true;
|
|
if (!isInStlNamespace(Callee->getParent()))
|
|
return false;
|
|
if (!isRecordWithAttr<PointerAttr>(
|
|
Callee->getFunctionObjectParameterType()) &&
|
|
!isRecordWithAttr<OwnerAttr>(Callee->getFunctionObjectParameterType()))
|
|
return false;
|
|
if (isPointerLikeType(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);
|
|
}
|
|
if (Callee->getReturnType()->isReferenceType()) {
|
|
if (!Callee->getIdentifier()) {
|
|
auto OO = Callee->getOverloadedOperator();
|
|
if (!Callee->getParent()->hasAttr<OwnerAttr>())
|
|
return false;
|
|
return OO == OverloadedOperatorKind::OO_Subscript ||
|
|
OO == OverloadedOperatorKind::OO_Star;
|
|
}
|
|
return llvm::StringSwitch<bool>(Callee->getName())
|
|
.Cases({"front", "back", "at", "top", "value"}, true)
|
|
.Default(false);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool shouldTrackFirstArgument(const FunctionDecl *FD) {
|
|
if (!FD->getIdentifier() || FD->getNumParams() != 1)
|
|
return false;
|
|
const auto *RD = FD->getParamDecl(0)->getType()->getPointeeCXXRecordDecl();
|
|
if (!FD->isInStdNamespace() || !RD || !RD->isInStdNamespace())
|
|
return false;
|
|
if (!RD->hasAttr<PointerAttr>() && !RD->hasAttr<OwnerAttr>())
|
|
return false;
|
|
if (FD->getReturnType()->isPointerType() ||
|
|
isRecordWithAttr<PointerAttr>(FD->getReturnType())) {
|
|
return llvm::StringSwitch<bool>(FD->getName())
|
|
.Cases({"begin", "rbegin", "cbegin", "crbegin"}, true)
|
|
.Cases({"end", "rend", "cend", "crend"}, true)
|
|
.Case("data", true)
|
|
.Default(false);
|
|
}
|
|
if (FD->getReturnType()->isReferenceType()) {
|
|
return llvm::StringSwitch<bool>(FD->getName())
|
|
.Cases({"get", "any_cast"}, true)
|
|
.Default(false);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Returns true if the given constructor is a copy-like constructor, such as
|
|
// `Ctor(Owner<U>&&)` or `Ctor(const Owner<U>&)`.
|
|
static bool isCopyLikeConstructor(const CXXConstructorDecl *Ctor) {
|
|
if (!Ctor || Ctor->param_size() != 1)
|
|
return false;
|
|
const auto *ParamRefType =
|
|
Ctor->getParamDecl(0)->getType()->getAs<ReferenceType>();
|
|
if (!ParamRefType)
|
|
return false;
|
|
|
|
// Check if the first parameter type is "Owner<U>".
|
|
if (const auto *TST =
|
|
ParamRefType->getPointeeType()->getAs<TemplateSpecializationType>())
|
|
return TST->getTemplateName()
|
|
.getAsTemplateDecl()
|
|
->getTemplatedDecl()
|
|
->hasAttr<OwnerAttr>();
|
|
return false;
|
|
}
|
|
|
|
// Returns true if we should perform the GSL analysis on the first argument for
|
|
// the given constructor.
|
|
static bool
|
|
shouldTrackFirstArgumentForConstructor(const CXXConstructExpr *Ctor) {
|
|
const auto *LHSRecordDecl = Ctor->getConstructor()->getParent();
|
|
|
|
// Case 1, construct a GSL pointer, e.g. std::string_view
|
|
// Always inspect when LHS is a pointer.
|
|
if (LHSRecordDecl->hasAttr<PointerAttr>())
|
|
return true;
|
|
|
|
if (Ctor->getConstructor()->param_empty() ||
|
|
!isContainerOfPointer(LHSRecordDecl))
|
|
return false;
|
|
|
|
// Now, the LHS is an Owner<Pointer> type, e.g., std::vector<string_view>.
|
|
//
|
|
// At a high level, we cannot precisely determine what the nested pointer
|
|
// owns. However, by analyzing the RHS owner type, we can use heuristics to
|
|
// infer ownership information. These heuristics are designed to be
|
|
// conservative, minimizing false positives while still providing meaningful
|
|
// diagnostics.
|
|
//
|
|
// While this inference isn't perfect, it helps catch common use-after-free
|
|
// patterns.
|
|
auto RHSArgType = Ctor->getArg(0)->getType();
|
|
const auto *RHSRD = RHSArgType->getAsRecordDecl();
|
|
// LHS is constructed from an intializer_list.
|
|
//
|
|
// std::initializer_list is a proxy object that provides access to the backing
|
|
// array. We perform analysis on it to determine if there are any dangling
|
|
// temporaries in the backing array.
|
|
// E.g. std::vector<string_view> abc = {string()};
|
|
if (isStdInitializerListOfPointer(RHSRD))
|
|
return true;
|
|
|
|
// RHS must be an owner.
|
|
if (!isRecordWithAttr<OwnerAttr>(RHSArgType))
|
|
return false;
|
|
|
|
// Bail out if the RHS is Owner<Pointer>.
|
|
//
|
|
// We cannot reliably determine what the LHS nested pointer owns -- it could
|
|
// be the entire RHS or the nested pointer in RHS. To avoid false positives,
|
|
// we skip this case, such as:
|
|
// std::stack<std::string_view> s(std::deque<std::string_view>{});
|
|
//
|
|
// TODO: this also has a false negative, it doesn't catch the case like:
|
|
// std::optional<span<int*>> os = std::vector<int*>{}
|
|
if (isContainerOfPointer(RHSRD))
|
|
return false;
|
|
|
|
// Assume that the nested Pointer is constructed from the nested Owner.
|
|
// E.g. std::optional<string_view> sv = std::optional<string>(s);
|
|
if (isContainerOfOwner(RHSRD))
|
|
return true;
|
|
|
|
// Now, the LHS is an Owner<Pointer> and the RHS is an Owner<X>, where X is
|
|
// neither an `Owner` nor a `Pointer`.
|
|
//
|
|
// Use the constructor's signature as a hint. If it is a copy-like constructor
|
|
// `Owner1<Pointer>(Owner2<X>&&)`, we assume that the nested pointer is
|
|
// constructed from X. In such cases, we do not diagnose, as `X` is not an
|
|
// owner, e.g.
|
|
// std::optional<string_view> sv = std::optional<Foo>();
|
|
if (const auto *PrimaryCtorTemplate =
|
|
Ctor->getConstructor()->getPrimaryTemplate();
|
|
PrimaryCtorTemplate &&
|
|
isCopyLikeConstructor(dyn_cast_if_present<CXXConstructorDecl>(
|
|
PrimaryCtorTemplate->getTemplatedDecl()))) {
|
|
return false;
|
|
}
|
|
// Assume that the nested pointer is constructed from the whole RHS.
|
|
// E.g. optional<string_view> s = std::string();
|
|
return true;
|
|
}
|
|
|
|
// Visit lifetimebound or gsl-pointer arguments.
|
|
static void visitFunctionCallArguments(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;
|
|
|
|
bool EnableGSLAnalysis = !Callee->getASTContext().getDiagnostics().isIgnored(
|
|
diag::warn_dangling_lifetime_pointer, SourceLocation());
|
|
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);
|
|
else
|
|
visitLocalsRetainedByInitializer(Path, Arg, Visit, true);
|
|
Path.pop_back();
|
|
};
|
|
auto VisitGSLPointerArg = [&](const FunctionDecl *Callee, Expr *Arg) {
|
|
auto ReturnType = Callee->getReturnType();
|
|
|
|
// Once we initialized a value with a non gsl-owner reference, it can no
|
|
// longer dangle.
|
|
if (ReturnType->isReferenceType() &&
|
|
!isRecordWithAttr<OwnerAttr>(ReturnType->getPointeeType())) {
|
|
for (const IndirectLocalPathEntry &PE : llvm::reverse(Path)) {
|
|
if (PE.Kind == IndirectLocalPathEntry::GslReferenceInit ||
|
|
PE.Kind == IndirectLocalPathEntry::LifetimeBoundCall)
|
|
continue;
|
|
if (PE.Kind == IndirectLocalPathEntry::GslPointerInit ||
|
|
PE.Kind == IndirectLocalPathEntry::GslPointerAssignment)
|
|
return;
|
|
break;
|
|
}
|
|
}
|
|
Path.push_back({ReturnType->isReferenceType()
|
|
? IndirectLocalPathEntry::GslReferenceInit
|
|
: IndirectLocalPathEntry::GslPointerInit,
|
|
Arg, Callee});
|
|
if (Arg->isGLValue())
|
|
visitLocalsRetainedByReferenceBinding(Path, Arg, RK_ReferenceBinding,
|
|
Visit);
|
|
else
|
|
visitLocalsRetainedByInitializer(Path, Arg, Visit, true);
|
|
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 (lifetimes::implicitObjectParamIsLifetimeBound(Callee) ||
|
|
CheckCoroObjArg)
|
|
VisitLifetimeBoundArg(Callee, ObjectArg);
|
|
else if (EnableGSLAnalysis) {
|
|
if (auto *CME = dyn_cast<CXXMethodDecl>(Callee);
|
|
CME && shouldTrackImplicitObjectArg(CME))
|
|
VisitGSLPointerArg(Callee, ObjectArg);
|
|
}
|
|
}
|
|
|
|
const FunctionDecl *CanonCallee =
|
|
lifetimes::getDeclWithMergedLifetimeBoundAttrs(Callee);
|
|
unsigned NP = std::min(Callee->getNumParams(), CanonCallee->getNumParams());
|
|
for (unsigned I = 0, N = std::min<unsigned>(NP, Args.size()); I != N; ++I) {
|
|
Expr *Arg = Args[I];
|
|
RevertToOldSizeRAII RAII(Path);
|
|
if (auto *DAE = dyn_cast<CXXDefaultArgExpr>(Arg)) {
|
|
Path.push_back(
|
|
{IndirectLocalPathEntry::DefaultArg, DAE, DAE->getParam()});
|
|
Arg = DAE->getExpr();
|
|
}
|
|
if (CheckCoroCall ||
|
|
CanonCallee->getParamDecl(I)->hasAttr<LifetimeBoundAttr>())
|
|
VisitLifetimeBoundArg(CanonCallee->getParamDecl(I), Arg);
|
|
else if (const auto *CaptureAttr =
|
|
CanonCallee->getParamDecl(I)->getAttr<LifetimeCaptureByAttr>();
|
|
CaptureAttr && isa<CXXConstructorDecl>(CanonCallee) &&
|
|
llvm::any_of(CaptureAttr->params(), [](int ArgIdx) {
|
|
return ArgIdx == LifetimeCaptureByAttr::This;
|
|
}))
|
|
// `lifetime_capture_by(this)` in a class constructor has the same
|
|
// semantics as `lifetimebound`:
|
|
//
|
|
// struct Foo {
|
|
// const int& a;
|
|
// // Equivalent to Foo(const int& t [[clang::lifetimebound]])
|
|
// Foo(const int& t [[clang::lifetime_capture_by(this)]]) : a(t) {}
|
|
// };
|
|
//
|
|
// In the implementation, `lifetime_capture_by` is treated as an alias for
|
|
// `lifetimebound` and shares the same code path. This implies the emitted
|
|
// diagnostics will be emitted under `-Wdangling`, not
|
|
// `-Wdangling-capture`.
|
|
VisitLifetimeBoundArg(CanonCallee->getParamDecl(I), Arg);
|
|
else if (EnableGSLAnalysis && I == 0) {
|
|
// Perform GSL analysis for the first argument
|
|
if (shouldTrackFirstArgument(CanonCallee)) {
|
|
VisitGSLPointerArg(CanonCallee, Arg);
|
|
} else if (auto *Ctor = dyn_cast<CXXConstructExpr>(Call);
|
|
Ctor && shouldTrackFirstArgumentForConstructor(Ctor)) {
|
|
VisitGSLPointerArg(Ctor->getConstructor(), Arg);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 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) {
|
|
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);
|
|
}
|
|
|
|
if (MemberExpr *ME = dyn_cast<MemberExpr>(Init->IgnoreImpCasts()))
|
|
Path.push_back(
|
|
{IndirectLocalPathEntry::MemberExpr, ME, ME->getMemberDecl()});
|
|
// 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);
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
|
|
if (auto *M = dyn_cast<MemberExpr>(Init)) {
|
|
// Lifetime of a non-reference type field is same as base object.
|
|
if (auto *F = dyn_cast<FieldDecl>(M->getMemberDecl());
|
|
F && !F->getType()->isReferenceType())
|
|
visitLocalsRetainedByInitializer(Path, M->getBase(), Visit, true);
|
|
}
|
|
|
|
if (isa<CallExpr>(Init))
|
|
return visitFunctionCallArguments(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);
|
|
}
|
|
}
|
|
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);
|
|
break;
|
|
}
|
|
|
|
case Stmt::ArraySectionExprClass: {
|
|
visitLocalsRetainedByInitializer(
|
|
Path, cast<ArraySectionExpr>(Init)->getBase(), Visit, true);
|
|
break;
|
|
}
|
|
|
|
case Stmt::ConditionalOperatorClass:
|
|
case Stmt::BinaryConditionalOperatorClass: {
|
|
auto *C = cast<AbstractConditionalOperator>(Init);
|
|
if (!C->getTrueExpr()->getType()->isVoidType())
|
|
visitLocalsRetainedByReferenceBinding(Path, C->getTrueExpr(), RK, Visit);
|
|
if (!C->getFalseExpr()->getType()->isVoidType())
|
|
visitLocalsRetainedByReferenceBinding(Path, C->getFalseExpr(), RK, Visit);
|
|
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) {
|
|
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);
|
|
}
|
|
} else if (auto *MTE = dyn_cast<MaterializeTemporaryExpr>(L)) {
|
|
if (MTE->getType().isConstQualified())
|
|
visitLocalsRetainedByInitializer(Path, MTE->getSubExpr(),
|
|
Visit, true);
|
|
}
|
|
return false;
|
|
});
|
|
|
|
// 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);
|
|
|
|
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);
|
|
|
|
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);
|
|
|
|
if (ILE->getType()->isArrayType()) {
|
|
for (unsigned I = 0, N = ILE->getNumInits(); I != N; ++I)
|
|
visitLocalsRetainedByInitializer(Path, ILE->getInit(I), Visit,
|
|
RevisitSubinits);
|
|
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);
|
|
else {
|
|
unsigned Index = 0;
|
|
for (; Index < RD->getNumBases() && Index < ILE->getNumInits(); ++Index)
|
|
visitLocalsRetainedByInitializer(Path, ILE->getInit(Index), Visit,
|
|
RevisitSubinits);
|
|
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);
|
|
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);
|
|
++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);
|
|
else
|
|
visitLocalsRetainedByInitializer(Path, E, Visit, true);
|
|
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);
|
|
Path.pop_back();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isa<CallExpr>(Init) || isa<CXXConstructExpr>(Init))
|
|
return visitFunctionCallArguments(Path, Init, Visit);
|
|
|
|
if (auto *CPE = dyn_cast<CXXParenListInitExpr>(Init)) {
|
|
RevertToOldSizeRAII RAII(Path);
|
|
Path.push_back({IndirectLocalPathEntry::ParenAggInit, CPE});
|
|
for (auto *I : CPE->getInitExprs()) {
|
|
if (I->isGLValue())
|
|
visitLocalsRetainedByReferenceBinding(Path, I, RK_ReferenceBinding,
|
|
Visit);
|
|
else
|
|
visitLocalsRetainedByInitializer(Path, I, Visit, true);
|
|
}
|
|
}
|
|
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);
|
|
}
|
|
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);
|
|
else if (BO->getRHS()->getType()->isPointerType())
|
|
visitLocalsRetainedByInitializer(Path, BO->getRHS(), Visit, true);
|
|
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);
|
|
if (!C->getFalseExpr()->getType()->isVoidType())
|
|
visitLocalsRetainedByInitializer(Path, C->getFalseExpr(), Visit, true);
|
|
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,
|
|
/// 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) {
|
|
for (auto Elem : Path) {
|
|
if (Elem.Kind == IndirectLocalPathEntry::MemberExpr ||
|
|
Elem.Kind == IndirectLocalPathEntry::LambdaCaptureInit)
|
|
continue;
|
|
return Elem.Kind == IndirectLocalPathEntry::DefaultInit
|
|
? PathLifetimeKind::Extend
|
|
: PathLifetimeKind::NoExtend;
|
|
}
|
|
return PathLifetimeKind::Extend;
|
|
}
|
|
|
|
/// 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:
|
|
case IndirectLocalPathEntry::GslPointerAssignment:
|
|
case IndirectLocalPathEntry::ParenAggInit:
|
|
case IndirectLocalPathEntry::MemberExpr:
|
|
// 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();
|
|
|
|
case IndirectLocalPathEntry::DefaultArg:
|
|
return cast<CXXDefaultArgExpr>(Path[I].E)->getUsedLocation();
|
|
}
|
|
}
|
|
return E->getSourceRange();
|
|
}
|
|
|
|
static bool pathOnlyHandlesGslPointer(const IndirectLocalPath &Path) {
|
|
for (const auto &It : llvm::reverse(Path)) {
|
|
switch (It.Kind) {
|
|
case IndirectLocalPathEntry::VarInit:
|
|
case IndirectLocalPathEntry::AddressOf:
|
|
case IndirectLocalPathEntry::LifetimeBoundCall:
|
|
case IndirectLocalPathEntry::MemberExpr:
|
|
continue;
|
|
case IndirectLocalPathEntry::GslPointerInit:
|
|
case IndirectLocalPathEntry::GslReferenceInit:
|
|
case IndirectLocalPathEntry::GslPointerAssignment:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
// Result of analyzing the Path for GSLPointer.
|
|
enum AnalysisResult {
|
|
// Path does not correspond to a GSLPointer.
|
|
NotGSLPointer,
|
|
|
|
// A relevant case was identified.
|
|
Report,
|
|
// Stop the entire traversal.
|
|
Abandon,
|
|
// Skip this step and continue traversing inner AST nodes.
|
|
Skip,
|
|
};
|
|
// Analyze cases where a GSLPointer is initialized or assigned from a
|
|
// temporary owner object.
|
|
static AnalysisResult analyzePathForGSLPointer(const IndirectLocalPath &Path,
|
|
Local L, LifetimeKind LK) {
|
|
if (!pathOnlyHandlesGslPointer(Path))
|
|
return NotGSLPointer;
|
|
|
|
// At this point, Path represents a series of operations involving a
|
|
// GSLPointer, either in the process of initialization or assignment.
|
|
|
|
// Process temporary base objects for MemberExpr cases, e.g. Temp().field.
|
|
for (const auto &E : Path) {
|
|
if (E.Kind == IndirectLocalPathEntry::MemberExpr) {
|
|
// Avoid interfering with the local base object.
|
|
if (pathContainsInit(Path))
|
|
return Abandon;
|
|
|
|
// We are not interested in the temporary base objects of gsl Pointers:
|
|
// auto p1 = Temp().ptr; // Here p1 might not dangle.
|
|
// However, we want to diagnose for gsl owner fields:
|
|
// auto p2 = Temp().owner; // Here p2 is dangling.
|
|
if (const auto *FD = llvm::dyn_cast_or_null<FieldDecl>(E.D);
|
|
FD && !FD->getType()->isReferenceType() &&
|
|
isRecordWithAttr<OwnerAttr>(FD->getType()) &&
|
|
LK != LK_MemInitializer) {
|
|
return Report;
|
|
}
|
|
return Abandon;
|
|
}
|
|
}
|
|
|
|
// Note: A LifetimeBoundCall can appear interleaved in this sequence.
|
|
// For example:
|
|
// const std::string& Ref(const std::string& a [[clang::lifetimebound]]);
|
|
// string_view abc = Ref(std::string());
|
|
// The "Path" is [GSLPointerInit, LifetimeboundCall], where "L" is the
|
|
// temporary "std::string()" object. We need to check the return type of the
|
|
// function with the lifetimebound attribute.
|
|
if (Path.back().Kind == IndirectLocalPathEntry::LifetimeBoundCall) {
|
|
// The lifetimebound applies to the implicit object parameter of a method.
|
|
const FunctionDecl *FD =
|
|
llvm::dyn_cast_or_null<FunctionDecl>(Path.back().D);
|
|
// The lifetimebound applies to a function parameter.
|
|
if (const auto *PD = llvm::dyn_cast<ParmVarDecl>(Path.back().D))
|
|
FD = llvm::dyn_cast<FunctionDecl>(PD->getDeclContext());
|
|
|
|
if (isa_and_present<CXXConstructorDecl>(FD)) {
|
|
// Constructor case: the parameter is annotated with lifetimebound
|
|
// e.g., GSLPointer(const S& s [[clang::lifetimebound]])
|
|
// We still respect this case even the type S is not an owner.
|
|
return Report;
|
|
}
|
|
// Check the return type, e.g.
|
|
// const GSLOwner& func(const Foo& foo [[clang::lifetimebound]])
|
|
// GSLOwner* func(cosnt Foo& foo [[clang::lifetimebound]])
|
|
// GSLPointer func(const Foo& foo [[clang::lifetimebound]])
|
|
if (FD &&
|
|
((FD->getReturnType()->isPointerOrReferenceType() &&
|
|
isRecordWithAttr<OwnerAttr>(FD->getReturnType()->getPointeeType())) ||
|
|
isGLSPointerType(FD->getReturnType())))
|
|
return Report;
|
|
|
|
return Abandon;
|
|
}
|
|
|
|
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;
|
|
if (!pathContainsInit(Path) && isRecordWithAttr<OwnerAttr>(L->getType()))
|
|
return Report;
|
|
return Abandon;
|
|
}
|
|
|
|
// The GSLPointer is from a temporary object.
|
|
auto *MTE = dyn_cast<MaterializeTemporaryExpr>(L);
|
|
|
|
bool IsGslPtrValueFromGslTempOwner =
|
|
MTE && !MTE->getExtendingDecl() &&
|
|
isRecordWithAttr<OwnerAttr>(MTE->getType());
|
|
// Skipping a chain of initializing gsl::Pointer annotated objects.
|
|
// We are looking only for the final source to find out if it was
|
|
// a local or temporary owner or the address of a local
|
|
// variable/param.
|
|
if (!IsGslPtrValueFromGslTempOwner)
|
|
return Skip;
|
|
return Report;
|
|
}
|
|
|
|
static bool shouldRunGSLAssignmentAnalysis(const Sema &SemaRef,
|
|
const AssignedEntity &Entity) {
|
|
bool EnableGSLAssignmentWarnings = !SemaRef.getDiagnostics().isIgnored(
|
|
diag::warn_dangling_lifetime_pointer_assignment, SourceLocation());
|
|
return (EnableGSLAssignmentWarnings &&
|
|
(isRecordWithAttr<PointerAttr>(Entity.LHS->getType()) ||
|
|
lifetimes::isAssignmentOperatorLifetimeBound(
|
|
Entity.AssignmentOperator)));
|
|
}
|
|
|
|
static void
|
|
checkExprLifetimeImpl(Sema &SemaRef, const InitializedEntity *InitEntity,
|
|
const InitializedEntity *ExtendingEntity, LifetimeKind LK,
|
|
const AssignedEntity *AEntity,
|
|
const CapturingEntity *CapEntity, Expr *Init) {
|
|
assert(!AEntity || LK == LK_Assignment);
|
|
assert(!CapEntity || LK == LK_LifetimeCapture);
|
|
assert(!InitEntity || (LK != LK_Assignment && LK != LK_LifetimeCapture));
|
|
// If this entity doesn't have an interesting lifetime, don't bother looking
|
|
// for temporaries within its initializer.
|
|
if (LK == LK_FullExpression)
|
|
return;
|
|
|
|
// FIXME: consider moving the TemporaryVisitor and visitLocalsRetained*
|
|
// functions to a dedicated class.
|
|
auto TemporaryVisitor = [&](const IndirectLocalPath &Path, Local L,
|
|
ReferenceKind RK) -> bool {
|
|
SourceRange DiagRange = nextPathEntryRange(Path, 0, L);
|
|
SourceLocation DiagLoc = DiagRange.getBegin();
|
|
|
|
auto *MTE = dyn_cast<MaterializeTemporaryExpr>(L);
|
|
|
|
bool IsGslPtrValueFromGslTempOwner = true;
|
|
switch (analyzePathForGSLPointer(Path, L, LK)) {
|
|
case Abandon:
|
|
return false;
|
|
case Skip:
|
|
return true;
|
|
case NotGSLPointer:
|
|
IsGslPtrValueFromGslTempOwner = false;
|
|
[[fallthrough]];
|
|
case Report:
|
|
break;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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::NoExtend:
|
|
if (SemaRef.getLangOpts().CPlusPlus23 && InitEntity) {
|
|
if (const VarDecl *VD =
|
|
dyn_cast_if_present<VarDecl>(InitEntity->getDecl());
|
|
VD && VD->isCXXForRangeImplicitVar()) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (IsGslPtrValueFromGslTempOwner && DiagLoc.isValid()) {
|
|
SemaRef.Diag(DiagLoc, diag::warn_dangling_lifetime_pointer)
|
|
<< DiagRange;
|
|
return false;
|
|
}
|
|
|
|
// 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 << !InitEntity->getParent()
|
|
<< ExtendingEntity->getDecl()->isImplicit()
|
|
<< ExtendingEntity->getDecl() << Init->isGLValue() << DiagRange;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case LK_LifetimeCapture: {
|
|
// The captured entity has lifetime beyond the full-expression,
|
|
// and the capturing entity does too, so don't warn.
|
|
if (!MTE)
|
|
return false;
|
|
if (CapEntity->Entity)
|
|
SemaRef.Diag(DiagLoc, diag::warn_dangling_reference_captured)
|
|
<< CapEntity->Entity << DiagRange;
|
|
else
|
|
SemaRef.Diag(DiagLoc, diag::warn_dangling_reference_captured_by_unknown)
|
|
<< DiagRange;
|
|
return false;
|
|
}
|
|
|
|
case LK_Assignment: {
|
|
if (!MTE || pathContainsInit(Path))
|
|
return false;
|
|
if (IsGslPtrValueFromGslTempOwner)
|
|
SemaRef.Diag(DiagLoc, diag::warn_dangling_lifetime_pointer_assignment)
|
|
<< AEntity->LHS << DiagRange;
|
|
else
|
|
SemaRef.Diag(DiagLoc, diag::warn_dangling_pointer_assignment)
|
|
<< AEntity->LHS->getType()->isPointerType() << AEntity->LHS
|
|
<< DiagRange;
|
|
return false;
|
|
}
|
|
case LK_MemInitializer: {
|
|
if (MTE) {
|
|
// 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 (IsGslPtrValueFromGslTempOwner) {
|
|
SemaRef.Diag(DiagLoc, diag::warn_dangling_lifetime_pointer_member)
|
|
<< ExtendingDecl << DiagRange;
|
|
SemaRef.Diag(ExtendingDecl->getLocation(),
|
|
diag::note_ref_or_ptr_member_declared_here)
|
|
<< true;
|
|
return false;
|
|
}
|
|
bool IsSubobjectMember = ExtendingEntity != InitEntity;
|
|
SemaRef.Diag(DiagLoc, shouldLifetimeExtendThroughPath(Path) !=
|
|
PathLifetimeKind::NoExtend
|
|
? diag::err_dangling_member
|
|
: diag::warn_dangling_member)
|
|
<< ExtendingDecl << IsSubobjectMember << RK << DiagRange;
|
|
// 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;
|
|
|
|
auto *DRE = dyn_cast<DeclRefExpr>(L);
|
|
// Suppress false positives for code like the one below:
|
|
// Ctor(unique_ptr<T> up) : pointer(up.get()), owner(move(up)) {}
|
|
// FIXME: move this logic to analyzePathForGSLPointer.
|
|
if (DRE && isRecordWithAttr<OwnerAttr>(DRE->getType()))
|
|
return false;
|
|
|
|
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 (IsGslPtrValueFromGslTempOwner)
|
|
SemaRef.Diag(DiagLoc, diag::warn_dangling_lifetime_pointer)
|
|
<< DiagRange;
|
|
else
|
|
SemaRef.Diag(DiagLoc, RK == RK_ReferenceBinding
|
|
? diag::warn_new_dangling_reference
|
|
: diag::warn_new_dangling_initializer_list)
|
|
<< !InitEntity->getParent() << DiagRange;
|
|
} else {
|
|
// We can't determine if the allocation outlives the local declaration.
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case LK_Return:
|
|
case LK_MustTail:
|
|
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)
|
|
<< InitEntity->getType()->isReferenceType() << DRE->getDecl()
|
|
<< isa<ParmVarDecl>(DRE->getDecl()) << (LK == LK_MustTail)
|
|
<< 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)
|
|
<< InitEntity->getType()->isReferenceType() << CLE->getInitializer()
|
|
<< 2 << (LK == LK_MustTail) << 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 &&
|
|
InitEntity->getType()->isReferenceType())
|
|
SemaRef.Diag(DiagLoc, diag::err_ret_local_temp_ref)
|
|
<< InitEntity->getType()->isReferenceType() << DiagRange;
|
|
else if (LK == LK_MustTail)
|
|
SemaRef.Diag(DiagLoc, diag::warn_musttail_local_temp_addr_ref)
|
|
<< InitEntity->getType()->isReferenceType() << DiagRange;
|
|
else
|
|
SemaRef.Diag(DiagLoc, diag::warn_ret_local_temp_addr_ref)
|
|
<< InitEntity->getType()->isReferenceType() << DiagRange;
|
|
}
|
|
break;
|
|
}
|
|
|
|
for (unsigned I = 0; I != Path.size(); ++I) {
|
|
auto Elem = Path[I];
|
|
|
|
switch (Elem.Kind) {
|
|
case IndirectLocalPathEntry::AddressOf:
|
|
case IndirectLocalPathEntry::LValToRVal:
|
|
case IndirectLocalPathEntry::ParenAggInit:
|
|
// These exist primarily to mark the path as not permitting or
|
|
// supporting lifetime extension.
|
|
break;
|
|
|
|
case IndirectLocalPathEntry::LifetimeBoundCall:
|
|
case IndirectLocalPathEntry::TemporaryCopy:
|
|
case IndirectLocalPathEntry::MemberExpr:
|
|
case IndirectLocalPathEntry::GslPointerInit:
|
|
case IndirectLocalPathEntry::GslReferenceInit:
|
|
case IndirectLocalPathEntry::GslPointerAssignment:
|
|
// 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;
|
|
}
|
|
|
|
case IndirectLocalPathEntry::DefaultArg: {
|
|
const auto *DAE = cast<CXXDefaultArgExpr>(Elem.E);
|
|
const ParmVarDecl *Param = DAE->getParam();
|
|
SemaRef.Diag(Param->getDefaultArgRange().getBegin(),
|
|
diag::note_init_with_default_argument)
|
|
<< Param << 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;
|
|
};
|
|
|
|
llvm::SmallVector<IndirectLocalPathEntry, 8> Path;
|
|
switch (LK) {
|
|
case LK_Assignment: {
|
|
if (shouldRunGSLAssignmentAnalysis(SemaRef, *AEntity))
|
|
Path.push_back({lifetimes::isAssignmentOperatorLifetimeBound(
|
|
AEntity->AssignmentOperator)
|
|
? IndirectLocalPathEntry::LifetimeBoundCall
|
|
: IndirectLocalPathEntry::GslPointerAssignment,
|
|
Init});
|
|
break;
|
|
}
|
|
case LK_LifetimeCapture: {
|
|
if (isPointerLikeType(Init->getType()))
|
|
Path.push_back({IndirectLocalPathEntry::GslPointerInit, Init});
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (Init->isGLValue())
|
|
visitLocalsRetainedByReferenceBinding(Path, Init, RK_ReferenceBinding,
|
|
TemporaryVisitor);
|
|
else
|
|
visitLocalsRetainedByInitializer(
|
|
Path, Init, TemporaryVisitor,
|
|
// Don't revisit the sub inits for the intialization case.
|
|
/*RevisitSubinits=*/!InitEntity);
|
|
}
|
|
|
|
void checkInitLifetime(Sema &SemaRef, const InitializedEntity &Entity,
|
|
Expr *Init) {
|
|
auto LTResult = getEntityLifetime(&Entity);
|
|
LifetimeKind LK = LTResult.getInt();
|
|
const InitializedEntity *ExtendingEntity = LTResult.getPointer();
|
|
checkExprLifetimeImpl(SemaRef, &Entity, ExtendingEntity, LK,
|
|
/*AEntity=*/nullptr, /*CapEntity=*/nullptr, Init);
|
|
}
|
|
|
|
void checkExprLifetimeMustTailArg(Sema &SemaRef,
|
|
const InitializedEntity &Entity, Expr *Init) {
|
|
checkExprLifetimeImpl(SemaRef, &Entity, nullptr, LK_MustTail,
|
|
/*AEntity=*/nullptr, /*CapEntity=*/nullptr, Init);
|
|
}
|
|
|
|
void checkAssignmentLifetime(Sema &SemaRef, const AssignedEntity &Entity,
|
|
Expr *Init) {
|
|
bool EnableDanglingPointerAssignment = !SemaRef.getDiagnostics().isIgnored(
|
|
diag::warn_dangling_pointer_assignment, SourceLocation());
|
|
bool RunAnalysis = (EnableDanglingPointerAssignment &&
|
|
Entity.LHS->getType()->isPointerType()) ||
|
|
shouldRunGSLAssignmentAnalysis(SemaRef, Entity);
|
|
|
|
if (!RunAnalysis)
|
|
return;
|
|
|
|
checkExprLifetimeImpl(SemaRef, /*InitEntity=*/nullptr,
|
|
/*ExtendingEntity=*/nullptr, LK_Assignment, &Entity,
|
|
/*CapEntity=*/nullptr, Init);
|
|
}
|
|
|
|
void checkCaptureByLifetime(Sema &SemaRef, const CapturingEntity &Entity,
|
|
Expr *Init) {
|
|
if (SemaRef.getDiagnostics().isIgnored(diag::warn_dangling_reference_captured,
|
|
SourceLocation()) &&
|
|
SemaRef.getDiagnostics().isIgnored(
|
|
diag::warn_dangling_reference_captured_by_unknown, SourceLocation()))
|
|
return;
|
|
return checkExprLifetimeImpl(SemaRef, /*InitEntity=*/nullptr,
|
|
/*ExtendingEntity=*/nullptr, LK_LifetimeCapture,
|
|
/*AEntity=*/nullptr,
|
|
/*CapEntity=*/&Entity, Init);
|
|
}
|
|
|
|
} // namespace clang::sema
|