231 changes: 157 additions & 74 deletions clang/lib/Sema/SemaInit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6322,12 +6322,14 @@ struct IndirectLocalPathEntry {
AddressOf,
VarInit,
LValToRVal,
LifetimeBoundCall,
} Kind;
Expr *E;
Decl *D = nullptr;
const Decl *D = nullptr;
IndirectLocalPathEntry() {}
IndirectLocalPathEntry(EntryKind K, Expr *E) : Kind(K), E(E) {}
IndirectLocalPathEntry(EntryKind K, Expr *E, Decl *D) : Kind(K), E(E), D(D) {}
IndirectLocalPathEntry(EntryKind K, Expr *E, const Decl *D)
: Kind(K), E(E), D(D) {}
};

using IndirectLocalPath = llvm::SmallVectorImpl<IndirectLocalPathEntry>;
Expand Down Expand Up @@ -6361,6 +6363,68 @@ static void visitLocalsRetainedByInitializer(IndirectLocalPath &Path,
Expr *Init, LocalVisitor Visit,
bool RevisitSubinits);

static void visitLocalsRetainedByReferenceBinding(IndirectLocalPath &Path,
Expr *Init, ReferenceKind RK,
LocalVisitor Visit);

static bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD) {
const TypeSourceInfo *TSI = FD->getTypeSourceInfo();
if (!TSI)
return false;
for (TypeLoc TL = TSI->getTypeLoc();
auto ATL = TL.getAsAdjusted<AttributedTypeLoc>();
TL = ATL.getModifiedLoc()) {
if (ATL.getAttrKind() == AttributedType::attr_lifetimebound)
return true;
}
return false;
}

static void visitLifetimeBoundArguments(IndirectLocalPath &Path, Expr *Call,
LocalVisitor Visit) {
const FunctionDecl *Callee;
ArrayRef<Expr*> Args;

if (auto *CE = dyn_cast<CallExpr>(Call)) {
Callee = CE->getDirectCallee();
Args = llvm::makeArrayRef(CE->getArgs(), CE->getNumArgs());
} else {
auto *CCE = cast<CXXConstructExpr>(Call);
Callee = CCE->getConstructor();
Args = llvm::makeArrayRef(CCE->getArgs(), CCE->getNumArgs());
}
if (!Callee)
return;

Expr *ObjectArg = nullptr;
if (isa<CXXOperatorCallExpr>(Call) && Callee->isCXXInstanceMember()) {
ObjectArg = Args[0];
Args = Args.slice(1);
} else if (auto *MCE = dyn_cast<CXXMemberCallExpr>(Call)) {
ObjectArg = MCE->getImplicitObjectArgument();
}

auto VisitLifetimeBoundArg = [&](const Decl *D, Expr *Arg) {
Path.push_back({IndirectLocalPathEntry::LifetimeBoundCall, Arg, D});
if (Arg->isGLValue())
visitLocalsRetainedByReferenceBinding(Path, Arg, RK_ReferenceBinding,
Visit);
else
visitLocalsRetainedByInitializer(Path, Arg, Visit, true);
Path.pop_back();
};

if (ObjectArg && implicitObjectParamIsLifetimeBound(Callee))
VisitLifetimeBoundArg(Callee, ObjectArg);

for (unsigned I = 0,
N = std::min<unsigned>(Callee->getNumParams(), Args.size());
I != N; ++I) {
if (Callee->getParamDecl(I)->hasAttr<LifetimeBoundAttr>())
VisitLifetimeBoundArg(Callee->getParamDecl(I), Args[I]);
}
}

/// Visit the locals that would be reachable through a reference bound to the
/// glvalue expression \c Init.
static void visitLocalsRetainedByReferenceBinding(IndirectLocalPath &Path,
Expand Down Expand Up @@ -6420,6 +6484,9 @@ static void visitLocalsRetainedByReferenceBinding(IndirectLocalPath &Path,
true);
}

if (isa<CallExpr>(Init))
return visitLifetimeBoundArguments(Path, Init, Visit);

switch (Init->getStmtClass()) {
case Stmt::DeclRefExprClass: {
// If we find the name of a local non-reference parameter, we could have a
Expand Down Expand Up @@ -6483,21 +6550,90 @@ static void visitLocalsRetainedByInitializer(IndirectLocalPath &Path,
bool RevisitSubinits) {
RevertToOldSizeRAII RAII(Path);

// 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();
}
Expr *Old;
do {
Old = Init;

if (auto *EWC = dyn_cast<ExprWithCleanups>(Init))
Init = EWC->getSubExpr();
// 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();
}

// Dig out the expression which constructs the extended temporary.
Init = const_cast<Expr *>(Init->skipRValueSubobjectAdjustments());
if (auto *EWC = dyn_cast<ExprWithCleanups>(Init))
Init = EWC->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->GetTemporaryExpr(),
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;
}

if (CXXBindTemporaryExpr *BTE = dyn_cast<CXXBindTemporaryExpr>(Init))
Init = BTE->getSubExpr();
Init = CE->getSubExpr();
}
} while (Old != Init);

// C++17 [dcl.init.list]p6:
// initializing an initializer_list object from the array extends the
Expand Down Expand Up @@ -6558,67 +6694,9 @@ static void visitLocalsRetainedByInitializer(IndirectLocalPath &Path,
return;
}

// Step over value-preserving rvalue casts.
while (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->GetTemporaryExpr(),
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_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();
}
if (isa<CallExpr>(Init) || isa<CXXConstructExpr>(Init))
return visitLifetimeBoundArguments(Path, Init, Visit);

Init = Init->IgnoreParens();
switch (Init->getStmtClass()) {
case Stmt::UnaryOperatorClass: {
auto *UO = cast<UnaryOperator>(Init);
Expand Down Expand Up @@ -6698,6 +6776,7 @@ static SourceRange nextPathEntryRange(const IndirectLocalPath &Path, unsigned I,
switch (Path[I].Kind) {
case IndirectLocalPathEntry::AddressOf:
case IndirectLocalPathEntry::LValToRVal:
case IndirectLocalPathEntry::LifetimeBoundCall:
// These exist primarily to mark the path as not permitting or
// supporting lifetime extension.
break;
Expand Down Expand Up @@ -6876,6 +6955,10 @@ void Sema::checkInitializerLifetime(const InitializedEntity &Entity,
// supporting lifetime extension.
break;

case IndirectLocalPathEntry::LifetimeBoundCall:
// FIXME: Consider adding a note for this.
break;

case IndirectLocalPathEntry::DefaultInit: {
auto *FD = cast<FieldDecl>(Elem.D);
Diag(FD->getLocation(), diag::note_init_with_default_member_initalizer)
Expand Down
21 changes: 21 additions & 0 deletions clang/lib/Sema/SemaType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5233,6 +5233,8 @@ static ParsedAttr::Kind getAttrListKind(AttributedType::Kind kind) {
return ParsedAttr::AT_ObjCKindOf;
case AttributedType::attr_ns_returns_retained:
return ParsedAttr::AT_NSReturnsRetained;
case AttributedType::attr_lifetimebound:
return ParsedAttr::AT_LifetimeBound;
}
llvm_unreachable("unexpected attribute kind!");
}
Expand Down Expand Up @@ -7194,6 +7196,18 @@ static void deduceOpenCLImplicitAddrSpace(TypeProcessingState &State,
T = State.getSema().Context.getAddrSpaceQualType(T, ImpAddr);
}

static void HandleLifetimeBoundAttr(QualType &CurType,
const ParsedAttr &Attr,
Sema &S, Declarator &D) {
if (D.isDeclarationOfFunction()) {
CurType = S.Context.getAttributedType(AttributedType::attr_lifetimebound,
CurType, CurType);
} else {
Attr.diagnoseAppertainsTo(S, nullptr);
}
}


static void processTypeAttrs(TypeProcessingState &state, QualType &type,
TypeAttrLocation TAL,
ParsedAttributesView &attrs) {
Expand Down Expand Up @@ -7298,6 +7312,13 @@ static void processTypeAttrs(TypeProcessingState &state, QualType &type,
HandleOpenCLAccessAttr(type, attr, state.getSema());
attr.setUsedAsTypeAttr();
break;
case ParsedAttr::AT_LifetimeBound:
if (TAL == TAL_DeclChunk) {
HandleLifetimeBoundAttr(type, attr, state.getSema(),
state.getDeclarator());
attr.setUsedAsTypeAttr();
}
break;

MS_TYPE_ATTRS_CASELIST:
if (!handleMSPointerTypeQualifierAttr(state, attr, type))
Expand Down
115 changes: 115 additions & 0 deletions clang/test/SemaCXX/attr-lifetimebound.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// RUN: %clang_cc1 -std=c++2a -verify %s

namespace usage_invalid {
// FIXME: Should we diagnose a void return type?
void voidreturn(int &param [[clang::lifetimebound]]);

int *not_class_member() [[clang::lifetimebound]]; // expected-error {{non-member function has no implicit object parameter}}
struct A {
A() [[clang::lifetimebound]]; // expected-error {{cannot be applied to a constructor}}
~A() [[clang::lifetimebound]]; // expected-error {{cannot be applied to a destructor}}
static int *static_class_member() [[clang::lifetimebound]]; // expected-error {{static member function has no implicit object parameter}}
int not_function [[clang::lifetimebound]]; // expected-error {{only applies to parameters and implicit object parameters}}
int [[clang::lifetimebound]] also_not_function; // expected-error {{cannot be applied to types}}
};
int *attr_with_param(int &param [[clang::lifetimebound(42)]]); // expected-error {{takes no arguments}}
}

namespace usage_ok {
struct IntRef { int *target; };

int &refparam(int &param [[clang::lifetimebound]]);
int &classparam(IntRef param [[clang::lifetimebound]]);

// Do not diagnose non-void return types; they can still be lifetime-bound.
long long ptrintcast(int &param [[clang::lifetimebound]]) {
return (long long)&param;
}
// Likewise.
int &intptrcast(long long param [[clang::lifetimebound]]) {
return *(int*)param;
}

struct A {
A();
A(int);
int *class_member() [[clang::lifetimebound]];
operator int*() [[clang::lifetimebound]];
};

int *p = A().class_member(); // expected-warning {{temporary whose address is used as value of local variable 'p' will be destroyed at the end of the full-expression}}
int *q = A(); // expected-warning {{temporary whose address is used as value of local variable 'q' will be destroyed at the end of the full-expression}}
int *r = A(1); // expected-warning {{temporary whose address is used as value of local variable 'r' will be destroyed at the end of the full-expression}}
}

# 1 "<std>" 1 3
namespace std {
using size_t = __SIZE_TYPE__;
struct string {
string();
string(const char*);

char &operator[](size_t) const [[clang::lifetimebound]];
};
string operator""s(const char *, size_t);

struct string_view {
string_view();
string_view(const char *p [[clang::lifetimebound]]);
string_view(const string &s [[clang::lifetimebound]]);
};
string_view operator""sv(const char *, size_t);

struct vector {
int *data();
size_t size();
};

template<typename K, typename V> struct map {};
}
# 68 "attr-lifetimebound.cpp" 2

using std::operator""s;
using std::operator""sv;

namespace p0936r0_examples {
std::string_view s = "foo"s; // expected-warning {{temporary}}

std::string operator+(std::string_view s1, std::string_view s2);
void f() {
std::string_view sv = "hi";
std::string_view sv2 = sv + sv; // expected-warning {{temporary}}
sv2 = sv + sv; // FIXME: can we infer that we should warn here too?
}

struct X { int a, b; };
const int &f(const X &x [[clang::lifetimebound]]) { return x.a; }
const int &r = f(X()); // expected-warning {{temporary}}

char &c = std::string("hello my pretty long strong")[0]; // expected-warning {{temporary}}

struct reversed_range {
int *begin();
int *end();
int *p;
std::size_t n;
};
template <typename R> reversed_range reversed(R &&r [[clang::lifetimebound]]) {
return reversed_range{r.data(), r.size()};
}

std::vector make_vector();
void use_reversed_range() {
// FIXME: Don't expose the name of the internal range variable.
for (auto x : reversed(make_vector())) {} // expected-warning {{temporary bound to local reference '__range1'}}
}

template <typename K, typename V>
const V &findOrDefault(const std::map<K, V> &m [[clang::lifetimebound]],
const K &key,
const V &defvalue [[clang::lifetimebound]]);

// FIXME: Maybe weaken the wording here: "local reference 'v' could bind to temporary that will be destroyed at end of full-expression"?
std::map<std::string, std::string> m;
const std::string &v = findOrDefault(m, "foo"s, "bar"s); // expected-warning {{temporary bound to local reference 'v'}}
}
9 changes: 7 additions & 2 deletions clang/utils/TableGen/ClangAttrEmitter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3305,11 +3305,16 @@ static std::string GenerateAppertainsTo(const Record &Attr, raw_ostream &OS) {
// Otherwise, generate an appertainsTo check specific to this attribute which
// checks all of the given subjects against the Decl passed in. Return the
// name of that check to the caller.
//
// If D is null, that means the attribute was not applied to a declaration
// at all (for instance because it was applied to a type), or that the caller
// has determined that the check should fail (perhaps prior to the creation
// of the declaration).
std::string FnName = "check" + Attr.getName().str() + "AppertainsTo";
std::stringstream SS;
SS << "static bool " << FnName << "(Sema &S, const ParsedAttr &Attr, ";
SS << "const Decl *D) {\n";
SS << " if (";
SS << " if (!D || (";
for (auto I = Subjects.begin(), E = Subjects.end(); I != E; ++I) {
// If the subject has custom code associated with it, generate a function
// for it. The function cannot be inlined into this check (yet) because it
Expand All @@ -3325,7 +3330,7 @@ static std::string GenerateAppertainsTo(const Record &Attr, raw_ostream &OS) {
if (I + 1 != E)
SS << " && ";
}
SS << ") {\n";
SS << ")) {\n";
SS << " S.Diag(Attr.getLoc(), diag::";
SS << (Warn ? "warn_attribute_wrong_decl_type_str" :
"err_attribute_wrong_decl_type_str");
Expand Down