2024-06-27 10:56:06 +02:00
|
|
|
//===--- 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"
|
2024-07-24 15:58:52 +02:00
|
|
|
#include "clang/AST/Decl.h"
|
2024-06-27 10:56:06 +02:00
|
|
|
#include "clang/AST/Expr.h"
|
2024-07-01 17:43:07 +02:00
|
|
|
#include "clang/Basic/DiagnosticSema.h"
|
|
|
|
#include "clang/Sema/Initialization.h"
|
2024-06-27 10:56:06 +02:00
|
|
|
#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,
|
|
|
|
|
2024-09-23 11:04:08 +02:00
|
|
|
/// The lifetime of a temporary bound to this entity ends too soon, because
|
|
|
|
/// the entity passed to a musttail function call.
|
|
|
|
LK_MustTail,
|
|
|
|
|
2024-06-27 10:56:06 +02:00
|
|
|
/// 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,
|
2024-07-16 11:23:39 +02:00
|
|
|
|
|
|
|
/// The lifetime of a temporary bound to this entity probably ends too soon,
|
|
|
|
/// because the entity is a pointer and we assign the address of a temporary
|
|
|
|
/// object to it.
|
|
|
|
LK_Assignment,
|
2024-06-27 10:56:06 +02:00
|
|
|
};
|
|
|
|
using LifetimeResult =
|
|
|
|
llvm::PointerIntPair<const InitializedEntity *, 3, LifetimeKind>;
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
/// Determine the declaration which an initialized entity ultimately refers to,
|
|
|
|
/// for the purpose of lifetime-extending a temporary bound to a reference in
|
|
|
|
/// the initialization of \p Entity.
|
|
|
|
static LifetimeResult
|
|
|
|
getEntityLifetime(const InitializedEntity *Entity,
|
|
|
|
const InitializedEntity *InitField = nullptr) {
|
|
|
|
// C++11 [class.temporary]p5:
|
|
|
|
switch (Entity->getKind()) {
|
|
|
|
case InitializedEntity::EK_Variable:
|
|
|
|
// The temporary [...] persists for the lifetime of the reference
|
|
|
|
return {Entity, LK_Extended};
|
|
|
|
|
|
|
|
case InitializedEntity::EK_Member:
|
|
|
|
// For subobjects, we look at the complete object.
|
|
|
|
if (Entity->getParent())
|
|
|
|
return getEntityLifetime(Entity->getParent(), Entity);
|
|
|
|
|
|
|
|
// except:
|
|
|
|
// C++17 [class.base.init]p8:
|
|
|
|
// A temporary expression bound to a reference member in a
|
|
|
|
// mem-initializer is ill-formed.
|
|
|
|
// C++17 [class.base.init]p11:
|
|
|
|
// A temporary expression bound to a reference member from a
|
|
|
|
// default member initializer is ill-formed.
|
|
|
|
//
|
|
|
|
// The context of p11 and its example suggest that it's only the use of a
|
|
|
|
// default member initializer from a constructor that makes the program
|
|
|
|
// ill-formed, not its mere existence, and that it can even be used by
|
|
|
|
// aggregate initialization.
|
|
|
|
return {Entity, Entity->isDefaultMemberInitializer() ? LK_Extended
|
|
|
|
: LK_MemInitializer};
|
|
|
|
|
|
|
|
case InitializedEntity::EK_Binding:
|
|
|
|
// Per [dcl.decomp]p3, the binding is treated as a variable of reference
|
|
|
|
// type.
|
|
|
|
return {Entity, LK_Extended};
|
|
|
|
|
|
|
|
case InitializedEntity::EK_Parameter:
|
|
|
|
case InitializedEntity::EK_Parameter_CF_Audited:
|
|
|
|
// -- A temporary bound to a reference parameter in a function call
|
|
|
|
// persists until the completion of the full-expression containing
|
|
|
|
// the call.
|
|
|
|
return {nullptr, LK_FullExpression};
|
|
|
|
|
|
|
|
case InitializedEntity::EK_TemplateParameter:
|
|
|
|
// FIXME: This will always be ill-formed; should we eagerly diagnose it
|
|
|
|
// here?
|
|
|
|
return {nullptr, LK_FullExpression};
|
|
|
|
|
|
|
|
case InitializedEntity::EK_Result:
|
|
|
|
// -- The lifetime of a temporary bound to the returned value in a
|
|
|
|
// function return statement is not extended; the temporary is
|
|
|
|
// destroyed at the end of the full-expression in the return statement.
|
|
|
|
return {nullptr, LK_Return};
|
|
|
|
|
|
|
|
case InitializedEntity::EK_StmtExprResult:
|
|
|
|
// FIXME: Should we lifetime-extend through the result of a statement
|
|
|
|
// expression?
|
|
|
|
return {nullptr, LK_StmtExprResult};
|
|
|
|
|
|
|
|
case InitializedEntity::EK_New:
|
|
|
|
// -- A temporary bound to a reference in a new-initializer persists
|
|
|
|
// until the completion of the full-expression containing the
|
|
|
|
// new-initializer.
|
|
|
|
return {nullptr, LK_New};
|
|
|
|
|
|
|
|
case InitializedEntity::EK_Temporary:
|
|
|
|
case InitializedEntity::EK_CompoundLiteralInit:
|
|
|
|
case InitializedEntity::EK_RelatedResult:
|
|
|
|
// We don't yet know the storage duration of the surrounding temporary.
|
|
|
|
// Assume it's got full-expression duration for now, it will patch up our
|
|
|
|
// storage duration if that's not correct.
|
|
|
|
return {nullptr, LK_FullExpression};
|
|
|
|
|
|
|
|
case InitializedEntity::EK_ArrayElement:
|
|
|
|
// For subobjects, we look at the complete object.
|
|
|
|
return getEntityLifetime(Entity->getParent(), InitField);
|
|
|
|
|
|
|
|
case InitializedEntity::EK_Base:
|
|
|
|
// For subobjects, we look at the complete object.
|
|
|
|
if (Entity->getParent())
|
|
|
|
return getEntityLifetime(Entity->getParent(), InitField);
|
|
|
|
return {InitField, LK_MemInitializer};
|
|
|
|
|
|
|
|
case InitializedEntity::EK_Delegating:
|
|
|
|
// We can reach this case for aggregate initialization in a constructor:
|
|
|
|
// struct A { int &&r; };
|
|
|
|
// struct B : A { B() : A{0} {} };
|
|
|
|
// In this case, use the outermost field decl as the context.
|
|
|
|
return {InitField, LK_MemInitializer};
|
|
|
|
|
|
|
|
case InitializedEntity::EK_BlockElement:
|
|
|
|
case InitializedEntity::EK_LambdaToBlockConversionBlockElement:
|
|
|
|
case InitializedEntity::EK_LambdaCapture:
|
|
|
|
case InitializedEntity::EK_VectorElement:
|
|
|
|
case InitializedEntity::EK_ComplexElement:
|
|
|
|
return {nullptr, LK_FullExpression};
|
|
|
|
|
|
|
|
case InitializedEntity::EK_Exception:
|
|
|
|
// FIXME: Can we diagnose lifetime problems with exceptions?
|
|
|
|
return {nullptr, LK_FullExpression};
|
|
|
|
|
|
|
|
case InitializedEntity::EK_ParenAggInitMember:
|
|
|
|
// -- A temporary object bound to a reference element of an aggregate of
|
|
|
|
// class type initialized from a parenthesized expression-list
|
|
|
|
// [dcl.init, 9.3] persists until the completion of the full-expression
|
|
|
|
// containing the expression-list.
|
|
|
|
return {nullptr, LK_FullExpression};
|
|
|
|
}
|
|
|
|
|
|
|
|
llvm_unreachable("unknown entity kind");
|
|
|
|
}
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
enum ReferenceKind {
|
|
|
|
/// Lifetime would be extended by a reference binding to a temporary.
|
|
|
|
RK_ReferenceBinding,
|
|
|
|
/// Lifetime would be extended by a std::initializer_list object binding to
|
|
|
|
/// its backing array.
|
|
|
|
RK_StdInitializerList,
|
|
|
|
};
|
|
|
|
|
|
|
|
/// A temporary or local variable. This will be one of:
|
|
|
|
/// * A MaterializeTemporaryExpr.
|
|
|
|
/// * A DeclRefExpr whose declaration is a local.
|
|
|
|
/// * An AddrLabelExpr.
|
|
|
|
/// * A BlockExpr for a block with captures.
|
|
|
|
using Local = Expr *;
|
|
|
|
|
|
|
|
/// Expressions we stepped over when looking for the local state. Any steps
|
|
|
|
/// that would inhibit lifetime extension or take us out of subexpressions of
|
|
|
|
/// the initializer are included.
|
|
|
|
struct IndirectLocalPathEntry {
|
|
|
|
enum EntryKind {
|
|
|
|
DefaultInit,
|
|
|
|
AddressOf,
|
|
|
|
VarInit,
|
|
|
|
LValToRVal,
|
|
|
|
LifetimeBoundCall,
|
|
|
|
TemporaryCopy,
|
|
|
|
LambdaCaptureInit,
|
|
|
|
GslReferenceInit,
|
2024-07-18 10:02:35 +02:00
|
|
|
GslPointerInit,
|
|
|
|
GslPointerAssignment,
|
2024-10-15 16:41:52 -04:00
|
|
|
DefaultArg,
|
2024-06-27 10:56:06 +02:00
|
|
|
} Kind;
|
|
|
|
Expr *E;
|
|
|
|
union {
|
|
|
|
const Decl *D = nullptr;
|
|
|
|
const LambdaCapture *Capture;
|
|
|
|
};
|
|
|
|
IndirectLocalPathEntry() {}
|
|
|
|
IndirectLocalPathEntry(EntryKind K, Expr *E) : Kind(K), E(E) {}
|
|
|
|
IndirectLocalPathEntry(EntryKind K, Expr *E, const Decl *D)
|
|
|
|
: Kind(K), E(E), D(D) {}
|
|
|
|
IndirectLocalPathEntry(EntryKind K, Expr *E, const LambdaCapture *Capture)
|
|
|
|
: Kind(K), E(E), Capture(Capture) {}
|
|
|
|
};
|
|
|
|
|
|
|
|
using IndirectLocalPath = llvm::SmallVectorImpl<IndirectLocalPathEntry>;
|
|
|
|
|
|
|
|
struct RevertToOldSizeRAII {
|
|
|
|
IndirectLocalPath &Path;
|
|
|
|
unsigned OldSize = Path.size();
|
|
|
|
RevertToOldSizeRAII(IndirectLocalPath &Path) : Path(Path) {}
|
|
|
|
~RevertToOldSizeRAII() { Path.resize(OldSize); }
|
|
|
|
};
|
|
|
|
|
|
|
|
using LocalVisitor = llvm::function_ref<bool(IndirectLocalPath &Path, Local L,
|
|
|
|
ReferenceKind RK)>;
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
static bool isVarOnPath(IndirectLocalPath &Path, VarDecl *VD) {
|
|
|
|
for (auto E : Path)
|
|
|
|
if (E.Kind == IndirectLocalPathEntry::VarInit && E.D == VD)
|
|
|
|
return true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool pathContainsInit(IndirectLocalPath &Path) {
|
|
|
|
return llvm::any_of(Path, [=](IndirectLocalPathEntry E) {
|
|
|
|
return E.Kind == IndirectLocalPathEntry::DefaultInit ||
|
|
|
|
E.Kind == IndirectLocalPathEntry::VarInit;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
static void visitLocalsRetainedByInitializer(IndirectLocalPath &Path,
|
|
|
|
Expr *Init, LocalVisitor Visit,
|
2024-09-04 13:49:48 +02:00
|
|
|
bool RevisitSubinits);
|
2024-06-27 10:56:06 +02:00
|
|
|
|
|
|
|
static void visitLocalsRetainedByReferenceBinding(IndirectLocalPath &Path,
|
|
|
|
Expr *Init, ReferenceKind RK,
|
2024-09-04 13:49:48 +02:00
|
|
|
LocalVisitor Visit);
|
2024-06-27 10:56:06 +02:00
|
|
|
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
2024-09-25 14:12:49 +02:00
|
|
|
static bool isPointerLikeType(QualType Type) {
|
|
|
|
return isRecordWithAttr<PointerAttr>(Type) || Type->isPointerType() ||
|
|
|
|
Type->isNullPtrType();
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
2024-06-27 10:56:06 +02:00
|
|
|
static bool shouldTrackImplicitObjectArg(const CXXMethodDecl *Callee) {
|
|
|
|
if (auto *Conv = dyn_cast_or_null<CXXConversionDecl>(Callee))
|
2024-09-16 15:21:33 +02:00
|
|
|
if (isRecordWithAttr<PointerAttr>(Conv->getConversionType()) &&
|
|
|
|
Callee->getParent()->hasAttr<OwnerAttr>())
|
2024-06-27 10:56:06 +02:00
|
|
|
return true;
|
|
|
|
if (!isInStlNamespace(Callee->getParent()))
|
|
|
|
return false;
|
|
|
|
if (!isRecordWithAttr<PointerAttr>(
|
|
|
|
Callee->getFunctionObjectParameterType()) &&
|
2024-09-12 09:23:52 +02:00
|
|
|
!isRecordWithAttr<OwnerAttr>(Callee->getFunctionObjectParameterType()))
|
2024-06-27 10:56:06 +02:00
|
|
|
return false;
|
2024-09-25 14:12:49 +02:00
|
|
|
if (isPointerLikeType(Callee->getReturnType())) {
|
2024-06-27 10:56:06 +02:00
|
|
|
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);
|
2024-09-05 13:24:38 +02:00
|
|
|
}
|
|
|
|
if (Callee->getReturnType()->isReferenceType()) {
|
2024-06-27 10:56:06 +02:00
|
|
|
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;
|
2024-07-19 16:46:31 +02:00
|
|
|
if (!RD->hasAttr<PointerAttr>() && !RD->hasAttr<OwnerAttr>())
|
2024-06-27 10:56:06 +02:00
|
|
|
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);
|
2024-09-05 13:24:38 +02:00
|
|
|
}
|
|
|
|
if (FD->getReturnType()->isReferenceType()) {
|
2024-06-27 10:56:06 +02:00
|
|
|
return llvm::StringSwitch<bool>(FD->getName())
|
|
|
|
.Cases("get", "any_cast", true)
|
|
|
|
.Default(false);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2024-09-25 14:12:49 +02:00
|
|
|
// 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;
|
|
|
|
|
2024-10-14 21:09:00 +02:00
|
|
|
if (Ctor->getConstructor()->param_empty() ||
|
2024-09-25 14:12:49 +02:00
|
|
|
!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;
|
|
|
|
}
|
|
|
|
|
2024-09-04 10:09:04 +02:00
|
|
|
// Return true if this is an "normal" assignment operator.
|
|
|
|
// We assuments that a normal assingment operator always returns *this, that is,
|
|
|
|
// an lvalue reference that is the same type as the implicit object parameter
|
|
|
|
// (or the LHS for a non-member operator$=).
|
2024-09-05 13:17:17 +02:00
|
|
|
static bool isNormalAssignmentOperator(const FunctionDecl *FD) {
|
2024-06-27 10:56:06 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2024-09-04 10:09:04 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2024-09-05 13:17:17 +02:00
|
|
|
return isNormalAssignmentOperator(FD);
|
2024-09-04 10:09:04 +02:00
|
|
|
}
|
|
|
|
|
2024-08-23 17:50:27 +02:00
|
|
|
// Visit lifetimebound or gsl-pointer arguments.
|
|
|
|
static void visitFunctionCallArguments(IndirectLocalPath &Path, Expr *Call,
|
2024-09-04 13:49:48 +02:00
|
|
|
LocalVisitor Visit) {
|
2024-06-27 10:56:06 +02:00
|
|
|
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;
|
|
|
|
|
2024-09-04 13:49:48 +02:00
|
|
|
bool EnableGSLAnalysis = !Callee->getASTContext().getDiagnostics().isIgnored(
|
|
|
|
diag::warn_dangling_lifetime_pointer, SourceLocation());
|
2024-06-27 10:56:06 +02:00
|
|
|
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,
|
2024-09-04 13:49:48 +02:00
|
|
|
Visit);
|
2024-06-27 10:56:06 +02:00
|
|
|
else
|
2024-09-04 13:49:48 +02:00
|
|
|
visitLocalsRetainedByInitializer(Path, Arg, Visit, true);
|
2024-06-27 10:56:06 +02:00
|
|
|
Path.pop_back();
|
|
|
|
};
|
2024-09-06 12:37:21 +02:00
|
|
|
auto VisitGSLPointerArg = [&](const FunctionDecl *Callee, Expr *Arg) {
|
2024-08-23 17:50:27 +02:00
|
|
|
// 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;
|
2024-09-06 12:37:21 +02:00
|
|
|
auto ReturnType = Callee->getReturnType();
|
|
|
|
|
|
|
|
// Once we initialized a value with a non gsl-owner reference, it can no
|
|
|
|
// longer dangle.
|
|
|
|
if (ReturnType->isReferenceType() &&
|
2024-09-12 09:23:52 +02:00
|
|
|
!isRecordWithAttr<OwnerAttr>(ReturnType->getPointeeType())) {
|
2024-08-23 17:50:27 +02:00
|
|
|
for (const IndirectLocalPathEntry &PE : llvm::reverse(Path)) {
|
2024-09-04 13:49:48 +02:00
|
|
|
if (PE.Kind == IndirectLocalPathEntry::GslReferenceInit ||
|
|
|
|
PE.Kind == IndirectLocalPathEntry::LifetimeBoundCall)
|
2024-08-23 17:50:27 +02:00
|
|
|
continue;
|
|
|
|
if (PE.Kind == IndirectLocalPathEntry::GslPointerInit ||
|
|
|
|
PE.Kind == IndirectLocalPathEntry::GslPointerAssignment)
|
|
|
|
return;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2024-09-06 12:37:21 +02:00
|
|
|
Path.push_back({ReturnType->isReferenceType()
|
|
|
|
? IndirectLocalPathEntry::GslReferenceInit
|
|
|
|
: IndirectLocalPathEntry::GslPointerInit,
|
|
|
|
Arg, Callee});
|
2024-08-23 17:50:27 +02:00
|
|
|
if (Arg->isGLValue())
|
|
|
|
visitLocalsRetainedByReferenceBinding(Path, Arg, RK_ReferenceBinding,
|
2024-09-04 13:49:48 +02:00
|
|
|
Visit);
|
2024-08-23 17:50:27 +02:00
|
|
|
else
|
2024-09-04 13:49:48 +02:00
|
|
|
visitLocalsRetainedByInitializer(Path, Arg, Visit, true);
|
2024-08-23 17:50:27 +02:00
|
|
|
Path.pop_back();
|
|
|
|
};
|
2024-06-27 10:56:06 +02:00
|
|
|
|
|
|
|
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);
|
2024-09-04 13:49:48 +02:00
|
|
|
else if (EnableGSLAnalysis) {
|
2024-08-23 17:50:27 +02:00
|
|
|
if (auto *CME = dyn_cast<CXXMethodDecl>(Callee);
|
|
|
|
CME && shouldTrackImplicitObjectArg(CME))
|
2024-09-06 12:37:21 +02:00
|
|
|
VisitGSLPointerArg(Callee, ObjectArg);
|
2024-08-23 17:50:27 +02:00
|
|
|
}
|
2024-06-27 10:56:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
for (unsigned I = 0,
|
|
|
|
N = std::min<unsigned>(Callee->getNumParams(), Args.size());
|
|
|
|
I != N; ++I) {
|
2024-10-15 16:41:52 -04:00
|
|
|
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();
|
|
|
|
}
|
2024-06-27 10:56:06 +02:00
|
|
|
if (CheckCoroCall || Callee->getParamDecl(I)->hasAttr<LifetimeBoundAttr>())
|
2024-10-15 16:41:52 -04:00
|
|
|
VisitLifetimeBoundArg(Callee->getParamDecl(I), Arg);
|
2024-09-04 13:49:48 +02:00
|
|
|
else if (EnableGSLAnalysis && I == 0) {
|
2024-09-25 14:12:49 +02:00
|
|
|
// Perform GSL analysis for the first argument
|
2024-08-23 17:50:27 +02:00
|
|
|
if (shouldTrackFirstArgument(Callee)) {
|
2024-10-15 16:41:52 -04:00
|
|
|
VisitGSLPointerArg(Callee, Arg);
|
2024-09-25 14:12:49 +02:00
|
|
|
} else if (auto *Ctor = dyn_cast<CXXConstructExpr>(Call);
|
|
|
|
Ctor && shouldTrackFirstArgumentForConstructor(Ctor)) {
|
2024-10-15 16:41:52 -04:00
|
|
|
VisitGSLPointerArg(Ctor->getConstructor(), Arg);
|
2024-08-23 17:50:27 +02:00
|
|
|
}
|
|
|
|
}
|
2024-06-27 10:56:06 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// 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,
|
2024-09-04 13:49:48 +02:00
|
|
|
LocalVisitor Visit) {
|
2024-06-27 10:56:06 +02:00
|
|
|
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.
|
2024-09-04 13:49:48 +02:00
|
|
|
return visitLocalsRetainedByInitializer(Path, Init, Visit, true);
|
2024-06-27 10:56:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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))
|
2024-09-04 13:49:48 +02:00
|
|
|
visitLocalsRetainedByInitializer(Path, MTE->getSubExpr(), Visit, true);
|
2024-06-27 10:56:06 +02:00
|
|
|
}
|
|
|
|
|
2024-07-24 15:58:52 +02:00
|
|
|
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())
|
2024-09-04 13:49:48 +02:00
|
|
|
visitLocalsRetainedByInitializer(Path, M->getBase(), Visit, true);
|
2024-07-24 15:58:52 +02:00
|
|
|
}
|
|
|
|
|
2024-08-23 17:50:27 +02:00
|
|
|
if (isa<CallExpr>(Init))
|
2024-09-04 13:49:48 +02:00
|
|
|
return visitFunctionCallArguments(Path, Init, Visit);
|
2024-06-27 10:56:06 +02:00
|
|
|
|
|
|
|
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(),
|
2024-09-04 13:49:48 +02:00
|
|
|
RK_ReferenceBinding, Visit);
|
2024-06-27 10:56:06 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
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)
|
2024-09-04 13:49:48 +02:00
|
|
|
visitLocalsRetainedByInitializer(Path, U->getSubExpr(), Visit, true);
|
2024-06-27 10:56:06 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case Stmt::ArraySectionExprClass: {
|
2024-09-04 13:49:48 +02:00
|
|
|
visitLocalsRetainedByInitializer(
|
|
|
|
Path, cast<ArraySectionExpr>(Init)->getBase(), Visit, true);
|
2024-06-27 10:56:06 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case Stmt::ConditionalOperatorClass:
|
|
|
|
case Stmt::BinaryConditionalOperatorClass: {
|
|
|
|
auto *C = cast<AbstractConditionalOperator>(Init);
|
|
|
|
if (!C->getTrueExpr()->getType()->isVoidType())
|
2024-09-04 13:49:48 +02:00
|
|
|
visitLocalsRetainedByReferenceBinding(Path, C->getTrueExpr(), RK, Visit);
|
2024-06-27 10:56:06 +02:00
|
|
|
if (!C->getFalseExpr()->getType()->isVoidType())
|
2024-09-04 13:49:48 +02:00
|
|
|
visitLocalsRetainedByReferenceBinding(Path, C->getFalseExpr(), RK, Visit);
|
2024-06-27 10:56:06 +02:00
|
|
|
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,
|
2024-09-04 13:49:48 +02:00
|
|
|
bool RevisitSubinits) {
|
2024-06-27 10:56:06 +02:00
|
|
|
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});
|
2024-09-04 13:49:48 +02:00
|
|
|
visitLocalsRetainedByInitializer(Path, VD->getInit(), Visit,
|
|
|
|
true);
|
2024-06-27 10:56:06 +02:00
|
|
|
}
|
|
|
|
} else if (auto *MTE = dyn_cast<MaterializeTemporaryExpr>(L)) {
|
|
|
|
if (MTE->getType().isConstQualified())
|
|
|
|
visitLocalsRetainedByInitializer(Path, MTE->getSubExpr(),
|
2024-09-04 13:49:48 +02:00
|
|
|
Visit, true);
|
2024-06-27 10:56:06 +02:00
|
|
|
}
|
|
|
|
return false;
|
2024-09-04 13:49:48 +02:00
|
|
|
});
|
2024-06-27 10:56:06 +02:00
|
|
|
|
|
|
|
// 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});
|
2024-09-04 13:49:48 +02:00
|
|
|
return visitLocalsRetainedByReferenceBinding(
|
|
|
|
Path, CE->getSubExpr(), RK_ReferenceBinding, Visit);
|
2024-06-27 10:56:06 +02:00
|
|
|
|
|
|
|
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(),
|
2024-09-04 13:49:48 +02:00
|
|
|
RK_StdInitializerList, Visit);
|
2024-06-27 10:56:06 +02:00
|
|
|
|
|
|
|
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,
|
2024-09-04 13:49:48 +02:00
|
|
|
RevisitSubinits);
|
2024-06-27 10:56:06 +02:00
|
|
|
|
|
|
|
if (ILE->getType()->isArrayType()) {
|
|
|
|
for (unsigned I = 0, N = ILE->getNumInits(); I != N; ++I)
|
|
|
|
visitLocalsRetainedByInitializer(Path, ILE->getInit(I), Visit,
|
2024-09-04 13:49:48 +02:00
|
|
|
RevisitSubinits);
|
2024-06-27 10:56:06 +02:00
|
|
|
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),
|
2024-09-04 13:49:48 +02:00
|
|
|
RK_ReferenceBinding, Visit);
|
2024-06-27 10:56:06 +02:00
|
|
|
else {
|
|
|
|
unsigned Index = 0;
|
|
|
|
for (; Index < RD->getNumBases() && Index < ILE->getNumInits(); ++Index)
|
|
|
|
visitLocalsRetainedByInitializer(Path, ILE->getInit(Index), Visit,
|
2024-09-04 13:49:48 +02:00
|
|
|
RevisitSubinits);
|
2024-06-27 10:56:06 +02:00
|
|
|
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,
|
2024-09-04 13:49:48 +02:00
|
|
|
RK_ReferenceBinding, Visit);
|
2024-06-27 10:56:06 +02:00
|
|
|
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.
|
2024-09-04 13:49:48 +02:00
|
|
|
visitLocalsRetainedByInitializer(Path, SubInit, Visit,
|
|
|
|
RevisitSubinits);
|
2024-06-27 10:56:06 +02:00
|
|
|
++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,
|
2024-09-04 13:49:48 +02:00
|
|
|
Visit);
|
2024-06-27 10:56:06 +02:00
|
|
|
else
|
2024-09-04 13:49:48 +02:00
|
|
|
visitLocalsRetainedByInitializer(Path, E, Visit, true);
|
2024-06-27 10:56:06 +02:00
|
|
|
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()});
|
2024-09-04 13:49:48 +02:00
|
|
|
visitLocalsRetainedByInitializer(Path, Arg, Visit, true);
|
2024-06-27 10:56:06 +02:00
|
|
|
Path.pop_back();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-23 17:50:27 +02:00
|
|
|
if (isa<CallExpr>(Init) || isa<CXXConstructExpr>(Init))
|
2024-09-04 13:49:48 +02:00
|
|
|
return visitFunctionCallArguments(Path, Init, Visit);
|
2024-06-27 10:56:06 +02:00
|
|
|
|
|
|
|
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(),
|
2024-09-04 13:49:48 +02:00
|
|
|
RK_ReferenceBinding, Visit);
|
2024-06-27 10:56:06 +02:00
|
|
|
}
|
|
|
|
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())
|
2024-09-04 13:49:48 +02:00
|
|
|
visitLocalsRetainedByInitializer(Path, BO->getLHS(), Visit, true);
|
2024-06-27 10:56:06 +02:00
|
|
|
else if (BO->getRHS()->getType()->isPointerType())
|
2024-09-04 13:49:48 +02:00
|
|
|
visitLocalsRetainedByInitializer(Path, BO->getRHS(), Visit, true);
|
2024-06-27 10:56:06 +02:00
|
|
|
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())
|
2024-09-04 13:49:48 +02:00
|
|
|
visitLocalsRetainedByInitializer(Path, C->getTrueExpr(), Visit, true);
|
2024-06-27 10:56:06 +02:00
|
|
|
if (!C->getFalseExpr()->getType()->isVoidType())
|
2024-09-04 13:49:48 +02:00
|
|
|
visitLocalsRetainedByInitializer(Path, C->getFalseExpr(), Visit, true);
|
2024-06-27 10:56:06 +02:00
|
|
|
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) {
|
|
|
|
PathLifetimeKind Kind = PathLifetimeKind::Extend;
|
|
|
|
for (auto Elem : Path) {
|
|
|
|
if (Elem.Kind == IndirectLocalPathEntry::DefaultInit)
|
2024-09-12 06:29:48 +08:00
|
|
|
return PathLifetimeKind::Extend;
|
2024-06-27 10:56:06 +02:00
|
|
|
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:
|
2024-07-18 10:02:35 +02:00
|
|
|
case IndirectLocalPathEntry::GslPointerAssignment:
|
2024-06-27 10:56:06 +02:00
|
|
|
// 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();
|
2024-10-15 16:41:52 -04:00
|
|
|
|
|
|
|
case IndirectLocalPathEntry::DefaultArg:
|
|
|
|
return cast<CXXDefaultArgExpr>(Path[I].E)->getUsedLocation();
|
2024-06-27 10:56:06 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return E->getSourceRange();
|
|
|
|
}
|
|
|
|
|
2024-07-18 10:02:35 +02:00
|
|
|
static bool pathOnlyHandlesGslPointer(IndirectLocalPath &Path) {
|
2024-06-27 10:56:06 +02:00
|
|
|
for (const auto &It : llvm::reverse(Path)) {
|
2024-07-18 10:02:35 +02:00
|
|
|
switch (It.Kind) {
|
|
|
|
case IndirectLocalPathEntry::VarInit:
|
|
|
|
case IndirectLocalPathEntry::AddressOf:
|
|
|
|
case IndirectLocalPathEntry::LifetimeBoundCall:
|
2024-06-27 10:56:06 +02:00
|
|
|
continue;
|
2024-07-18 10:02:35 +02:00
|
|
|
case IndirectLocalPathEntry::GslPointerInit:
|
|
|
|
case IndirectLocalPathEntry::GslReferenceInit:
|
|
|
|
case IndirectLocalPathEntry::GslPointerAssignment:
|
|
|
|
return true;
|
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
2024-06-27 10:56:06 +02:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2024-09-05 13:17:17 +02:00
|
|
|
static bool isAssignmentOperatorLifetimeBound(CXXMethodDecl *CMD) {
|
2024-09-04 10:09:04 +02:00
|
|
|
if (!CMD)
|
|
|
|
return false;
|
2024-09-05 13:17:17 +02:00
|
|
|
return isNormalAssignmentOperator(CMD) && CMD->param_size() == 1 &&
|
2024-09-04 10:09:04 +02:00
|
|
|
CMD->getParamDecl(0)->hasAttr<LifetimeBoundAttr>();
|
|
|
|
}
|
|
|
|
|
|
|
|
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()) ||
|
2024-09-05 13:17:17 +02:00
|
|
|
isAssignmentOperatorLifetimeBound(Entity.AssignmentOperator)));
|
2024-09-04 10:09:04 +02:00
|
|
|
}
|
|
|
|
|
2024-07-01 23:56:59 +01:00
|
|
|
static void checkExprLifetimeImpl(Sema &SemaRef,
|
|
|
|
const InitializedEntity *InitEntity,
|
|
|
|
const InitializedEntity *ExtendingEntity,
|
|
|
|
LifetimeKind LK,
|
2024-09-04 13:49:48 +02:00
|
|
|
const AssignedEntity *AEntity, Expr *Init) {
|
2024-07-16 11:23:39 +02:00
|
|
|
assert((AEntity && LK == LK_Assignment) ||
|
|
|
|
(InitEntity && LK != LK_Assignment));
|
2024-06-27 10:56:06 +02:00
|
|
|
// If this entity doesn't have an interesting lifetime, don't bother looking
|
|
|
|
// for temporaries within its initializer.
|
|
|
|
if (LK == LK_FullExpression)
|
|
|
|
return;
|
|
|
|
|
2024-07-01 17:43:07 +02:00
|
|
|
// FIXME: consider moving the TemporaryVisitor and visitLocalsRetained*
|
|
|
|
// functions to a dedicated class.
|
2024-06-27 10:56:06 +02:00
|
|
|
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);
|
|
|
|
|
2024-07-18 10:02:35 +02:00
|
|
|
bool IsGslPtrValueFromGslTempOwner = false;
|
2024-06-27 10:56:06 +02:00
|
|
|
bool IsLocalGslOwner = false;
|
2024-07-18 10:02:35 +02:00
|
|
|
if (pathOnlyHandlesGslPointer(Path)) {
|
2024-06-27 10:56:06 +02:00
|
|
|
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;
|
2024-09-12 09:23:52 +02:00
|
|
|
IsLocalGslOwner = isRecordWithAttr<OwnerAttr>(L->getType());
|
2024-06-27 10:56:06 +02:00
|
|
|
if (pathContainsInit(Path) || !IsLocalGslOwner)
|
|
|
|
return false;
|
|
|
|
} else {
|
2024-07-18 10:02:35 +02:00
|
|
|
IsGslPtrValueFromGslTempOwner =
|
2024-09-12 09:23:52 +02:00
|
|
|
MTE && !MTE->getExtendingDecl() &&
|
|
|
|
isRecordWithAttr<OwnerAttr>(MTE->getType());
|
2024-06-27 10:56:06 +02:00
|
|
|
// 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.
|
2024-07-18 10:02:35 +02:00
|
|
|
if (!IsGslPtrValueFromGslTempOwner)
|
2024-06-27 10:56:06 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2024-07-18 10:02:35 +02:00
|
|
|
if (IsGslPtrValueFromGslTempOwner && DiagLoc.isValid()) {
|
2024-06-27 10:56:06 +02:00
|
|
|
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::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;
|
|
|
|
|
2024-07-02 15:21:06 +02:00
|
|
|
SemaRef.Diag(DiagLoc, diag::warn_dangling_variable)
|
|
|
|
<< RK << !InitEntity->getParent()
|
|
|
|
<< ExtendingEntity->getDecl()->isImplicit()
|
|
|
|
<< ExtendingEntity->getDecl() << Init->isGLValue() << DiagRange;
|
2024-06-27 10:56:06 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2024-07-16 11:23:39 +02:00
|
|
|
case LK_Assignment: {
|
2024-07-18 10:02:35 +02:00
|
|
|
if (!MTE || pathContainsInit(Path))
|
2024-07-16 11:23:39 +02:00
|
|
|
return false;
|
|
|
|
assert(shouldLifetimeExtendThroughPath(Path) ==
|
|
|
|
PathLifetimeKind::NoExtend &&
|
|
|
|
"No lifetime extension for assignments");
|
2024-07-18 10:02:35 +02:00
|
|
|
SemaRef.Diag(DiagLoc,
|
|
|
|
IsGslPtrValueFromGslTempOwner
|
|
|
|
? diag::warn_dangling_lifetime_pointer_assignment
|
|
|
|
: diag::warn_dangling_pointer_assignment)
|
|
|
|
<< AEntity->LHS << DiagRange;
|
2024-07-16 11:23:39 +02:00
|
|
|
return false;
|
|
|
|
}
|
2024-06-27 10:56:06 +02:00
|
|
|
case LK_MemInitializer: {
|
2024-07-01 17:43:07 +02:00
|
|
|
if (MTE) {
|
2024-06-27 10:56:06 +02:00
|
|
|
// 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) {
|
2024-07-18 10:02:35 +02:00
|
|
|
if (IsGslPtrValueFromGslTempOwner) {
|
2024-06-27 10:56:06 +02:00
|
|
|
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;
|
|
|
|
}
|
2024-07-01 17:43:07 +02:00
|
|
|
bool IsSubobjectMember = ExtendingEntity != InitEntity;
|
2024-06-27 10:56:06 +02:00
|
|
|
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)) {}
|
2024-07-18 10:02:35 +02:00
|
|
|
if (IsLocalGslOwner && pathOnlyHandlesGslPointer(Path))
|
2024-06-27 10:56:06 +02:00
|
|
|
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)) {
|
2024-07-18 10:02:35 +02:00
|
|
|
if (IsGslPtrValueFromGslTempOwner)
|
2024-06-27 10:56:06 +02:00
|
|
|
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)
|
2024-07-01 17:43:07 +02:00
|
|
|
<< !InitEntity->getParent() << DiagRange;
|
2024-06-27 10:56:06 +02:00
|
|
|
} else {
|
|
|
|
// We can't determine if the allocation outlives the local declaration.
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case LK_Return:
|
2024-09-23 11:04:08 +02:00
|
|
|
case LK_MustTail:
|
2024-06-27 10:56:06 +02:00
|
|
|
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)
|
2024-07-01 17:43:07 +02:00
|
|
|
<< InitEntity->getType()->isReferenceType() << DRE->getDecl()
|
2024-09-23 11:04:08 +02:00
|
|
|
<< isa<ParmVarDecl>(DRE->getDecl()) << (LK == LK_MustTail)
|
|
|
|
<< DiagRange;
|
2024-06-27 10:56:06 +02:00
|
|
|
} 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)
|
2024-07-01 17:43:07 +02:00
|
|
|
<< InitEntity->getType()->isReferenceType() << CLE->getInitializer()
|
2024-09-23 11:04:08 +02:00
|
|
|
<< 2 << (LK == LK_MustTail) << DiagRange;
|
2024-06-27 10:56:06 +02:00
|
|
|
} 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 &&
|
2024-07-01 17:43:07 +02:00
|
|
|
InitEntity->getType()->isReferenceType())
|
2024-06-27 10:56:06 +02:00
|
|
|
SemaRef.Diag(DiagLoc, diag::err_ret_local_temp_ref)
|
2024-07-01 17:43:07 +02:00
|
|
|
<< InitEntity->getType()->isReferenceType() << DiagRange;
|
2024-09-23 11:04:08 +02:00
|
|
|
else if (LK == LK_MustTail)
|
|
|
|
SemaRef.Diag(DiagLoc, diag::warn_musttail_local_temp_addr_ref)
|
|
|
|
<< InitEntity->getType()->isReferenceType() << DiagRange;
|
2024-06-27 10:56:06 +02:00
|
|
|
else
|
|
|
|
SemaRef.Diag(DiagLoc, diag::warn_ret_local_temp_addr_ref)
|
2024-07-01 17:43:07 +02:00
|
|
|
<< InitEntity->getType()->isReferenceType() << DiagRange;
|
2024-06-27 10:56:06 +02:00
|
|
|
}
|
|
|
|
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:
|
2024-07-18 10:02:35 +02:00
|
|
|
case IndirectLocalPathEntry::GslPointerAssignment:
|
2024-06-27 10:56:06 +02:00
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
2024-10-15 16:41:52 -04:00
|
|
|
case IndirectLocalPathEntry::LambdaCaptureInit: {
|
2024-06-27 10:56:06 +02:00
|
|
|
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;
|
|
|
|
}
|
2024-10-15 16:41:52 -04:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2024-06-27 10:56:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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;
|
2024-09-04 10:09:04 +02:00
|
|
|
if (LK == LK_Assignment && shouldRunGSLAssignmentAnalysis(SemaRef, *AEntity))
|
2024-07-18 10:02:35 +02:00
|
|
|
Path.push_back({IndirectLocalPathEntry::GslPointerAssignment, Init});
|
|
|
|
|
2024-06-27 10:56:06 +02:00
|
|
|
if (Init->isGLValue())
|
|
|
|
visitLocalsRetainedByReferenceBinding(Path, Init, RK_ReferenceBinding,
|
2024-09-04 13:49:48 +02:00
|
|
|
TemporaryVisitor);
|
2024-06-27 10:56:06 +02:00
|
|
|
else
|
2024-07-01 17:43:07 +02:00
|
|
|
visitLocalsRetainedByInitializer(
|
|
|
|
Path, Init, TemporaryVisitor,
|
|
|
|
// Don't revisit the sub inits for the intialization case.
|
2024-09-04 13:49:48 +02:00
|
|
|
/*RevisitSubinits=*/!InitEntity);
|
2024-07-01 17:43:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity,
|
|
|
|
Expr *Init) {
|
2024-07-01 23:56:59 +01:00
|
|
|
auto LTResult = getEntityLifetime(&Entity);
|
|
|
|
LifetimeKind LK = LTResult.getInt();
|
|
|
|
const InitializedEntity *ExtendingEntity = LTResult.getPointer();
|
2024-07-18 10:02:35 +02:00
|
|
|
checkExprLifetimeImpl(SemaRef, &Entity, ExtendingEntity, LK,
|
2024-09-04 13:49:48 +02:00
|
|
|
/*AEntity*/ nullptr, Init);
|
2024-07-01 17:43:07 +02:00
|
|
|
}
|
|
|
|
|
2024-09-23 11:04:08 +02:00
|
|
|
void checkExprLifetimeMustTailArg(Sema &SemaRef,
|
|
|
|
const InitializedEntity &Entity, Expr *Init) {
|
|
|
|
checkExprLifetimeImpl(SemaRef, &Entity, nullptr, LK_MustTail,
|
|
|
|
/*AEntity*/ nullptr, Init);
|
|
|
|
}
|
|
|
|
|
2024-07-01 17:43:07 +02:00
|
|
|
void checkExprLifetime(Sema &SemaRef, const AssignedEntity &Entity,
|
|
|
|
Expr *Init) {
|
2024-09-01 16:18:13 +02:00
|
|
|
bool EnableDanglingPointerAssignment = !SemaRef.getDiagnostics().isIgnored(
|
|
|
|
diag::warn_dangling_pointer_assignment, SourceLocation());
|
|
|
|
bool RunAnalysis = (EnableDanglingPointerAssignment &&
|
|
|
|
Entity.LHS->getType()->isPointerType()) ||
|
2024-09-04 10:09:04 +02:00
|
|
|
shouldRunGSLAssignmentAnalysis(SemaRef, Entity);
|
2024-07-18 10:02:35 +02:00
|
|
|
|
|
|
|
if (!RunAnalysis)
|
2024-07-16 11:23:39 +02:00
|
|
|
return;
|
2024-07-18 10:02:35 +02:00
|
|
|
|
2024-07-16 11:23:39 +02:00
|
|
|
checkExprLifetimeImpl(SemaRef, /*InitEntity=*/nullptr,
|
|
|
|
/*ExtendingEntity=*/nullptr, LK_Assignment, &Entity,
|
2024-09-04 13:49:48 +02:00
|
|
|
Init);
|
2024-06-27 10:56:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace clang::sema
|