forked from OSchip/llvm-project
[P0936R0] add [[clang::lifetimebound]] attribute
This patch adds support for a new attribute, [[clang::lifetimebound]], that indicates that the lifetime of a function result is related to one of the function arguments. When walking an initializer to make sure that the lifetime of the initial value is at least as long as the lifetime of the initialized object, we step through parameters (including the implicit object parameter of a non-static member function) that are marked with this attribute. There's nowhere to write an attribute on the implicit object parameter, so in lieu of that, it may be applied to a function type (where it appears immediately after the cv-qualifiers and ref-qualifier, which is as close to a declaration of the implicit object parameter as we have). I'm currently modeling this in the AST as the attribute appertaining to the function type. Differential Revision: https://reviews.llvm.org/D49922 llvm-svn: 338464
This commit is contained in:
parent
eb159e56a6
commit
f4e248c23e
|
@ -4237,6 +4237,7 @@ public:
|
|||
attr_null_unspecified,
|
||||
attr_objc_kindof,
|
||||
attr_objc_inert_unsafe_unretained,
|
||||
attr_lifetimebound,
|
||||
};
|
||||
|
||||
private:
|
||||
|
|
|
@ -141,6 +141,13 @@ def HasFunctionProto : SubsetSubject<DeclBase,
|
|||
isa<BlockDecl>(S)}],
|
||||
"non-K&R-style functions">;
|
||||
|
||||
// A subject that matches the implicit object parameter of a non-static member
|
||||
// function. Accepted as a function type attribute on the type of such a
|
||||
// member function.
|
||||
// FIXME: This does not actually ever match currently.
|
||||
def ImplicitObjectParameter : SubsetSubject<Function, [{false}],
|
||||
"implicit object parameters">;
|
||||
|
||||
// A single argument to an attribute
|
||||
class Argument<string name, bit optional, bit fake = 0> {
|
||||
string Name = name;
|
||||
|
@ -1211,6 +1218,13 @@ def LayoutVersion : InheritableAttr, TargetSpecificAttr<TargetMicrosoftCXXABI> {
|
|||
let Documentation = [LayoutVersionDocs];
|
||||
}
|
||||
|
||||
def LifetimeBound : InheritableAttr {
|
||||
let Spellings = [Clang<"lifetimebound", 0>];
|
||||
let Subjects = SubjectList<[ParmVar, ImplicitObjectParameter], ErrorDiag>;
|
||||
let Documentation = [LifetimeBoundDocs];
|
||||
let LangOpts = [CPlusPlus];
|
||||
}
|
||||
|
||||
def TrivialABI : InheritableAttr {
|
||||
// This attribute does not have a C [[]] spelling because it requires the
|
||||
// CPlusPlus language option.
|
||||
|
|
|
@ -2362,6 +2362,22 @@ It is only supported when using the Microsoft C++ ABI.
|
|||
}];
|
||||
}
|
||||
|
||||
def LifetimeBoundDocs : Documentation {
|
||||
let Category = DocCatFunction;
|
||||
let Content = [{
|
||||
The ``lifetimebound`` attribute indicates that a resource owned by
|
||||
a function parameter or implicit object parameter
|
||||
is retained by the return value of the annotated function
|
||||
(or, for a parameter of a constructor, in the value of the constructed object).
|
||||
It is only supported in C++.
|
||||
|
||||
This attribute provides an experimental implementation of the facility
|
||||
described in the C++ committee paper [http://wg21.link/p0936r0](P0936R0),
|
||||
and is subject to change as the design of the corresponding functionality
|
||||
changes.
|
||||
}];
|
||||
}
|
||||
|
||||
def TrivialABIDocs : Documentation {
|
||||
let Category = DocCatVariable;
|
||||
let Content = [{
|
||||
|
|
|
@ -7854,6 +7854,13 @@ def warn_null_ret : Warning<
|
|||
"null returned from %select{function|method}0 that requires a non-null return value">,
|
||||
InGroup<NonNull>;
|
||||
|
||||
def err_lifetimebound_no_object_param : Error<
|
||||
"'lifetimebound' attribute cannot be applied; %select{static |non-}0member "
|
||||
"function has no implicit object parameter">;
|
||||
def err_lifetimebound_ctor_dtor : Error<
|
||||
"'lifetimebound' attribute cannot be applied to a "
|
||||
"%select{constructor|destructor}0">;
|
||||
|
||||
// CHECK: returning address/reference of stack memory
|
||||
def warn_ret_stack_addr_ref : Warning<
|
||||
"%select{address of|reference to}0 stack memory associated with "
|
||||
|
|
|
@ -3178,6 +3178,7 @@ bool AttributedType::isQualifier() const {
|
|||
case AttributedType::attr_nonnull:
|
||||
case AttributedType::attr_nullable:
|
||||
case AttributedType::attr_null_unspecified:
|
||||
case AttributedType::attr_lifetimebound:
|
||||
return true;
|
||||
|
||||
// These aren't qualifiers; they rewrite the modified type to be a
|
||||
|
@ -3247,6 +3248,7 @@ bool AttributedType::isCallingConv() const {
|
|||
case attr_null_unspecified:
|
||||
case attr_objc_kindof:
|
||||
case attr_nocf_check:
|
||||
case attr_lifetimebound:
|
||||
return false;
|
||||
|
||||
case attr_pcs:
|
||||
|
|
|
@ -1443,9 +1443,27 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
|
|||
return;
|
||||
}
|
||||
|
||||
if (T->getAttrKind() == AttributedType::attr_lifetimebound) {
|
||||
OS << " [[clang::lifetimebound]]";
|
||||
return;
|
||||
}
|
||||
|
||||
OS << " __attribute__((";
|
||||
switch (T->getAttrKind()) {
|
||||
default: llvm_unreachable("This attribute should have been handled already");
|
||||
case AttributedType::attr_lifetimebound:
|
||||
case AttributedType::attr_nonnull:
|
||||
case AttributedType::attr_nullable:
|
||||
case AttributedType::attr_null_unspecified:
|
||||
case AttributedType::attr_objc_gc:
|
||||
case AttributedType::attr_objc_inert_unsafe_unretained:
|
||||
case AttributedType::attr_objc_kindof:
|
||||
case AttributedType::attr_objc_ownership:
|
||||
case AttributedType::attr_ptr32:
|
||||
case AttributedType::attr_ptr64:
|
||||
case AttributedType::attr_sptr:
|
||||
case AttributedType::attr_uptr:
|
||||
llvm_unreachable("This attribute should have been handled already");
|
||||
|
||||
case AttributedType::attr_address_space:
|
||||
OS << "address_space(";
|
||||
// FIXME: printing the raw LangAS value is wrong. This should probably
|
||||
|
|
|
@ -6008,6 +6008,27 @@ static void checkAttributesAfterMerging(Sema &S, NamedDecl &ND) {
|
|||
<< Attr;
|
||||
ND.dropAttr<NotTailCalledAttr>();
|
||||
}
|
||||
|
||||
// Check the attributes on the function type, if any.
|
||||
if (const auto *FD = dyn_cast<FunctionDecl>(&ND)) {
|
||||
for (TypeLoc TL = FD->getTypeSourceInfo()->getTypeLoc();
|
||||
auto ATL = TL.getAsAdjusted<AttributedTypeLoc>();
|
||||
TL = ATL.getModifiedLoc()) {
|
||||
// The [[lifetimebound]] attribute can be applied to the implicit object
|
||||
// parameter of a non-static member function (other than a ctor or dtor)
|
||||
// by applying it to the function type.
|
||||
if (ATL.getAttrKind() == AttributedType::attr_lifetimebound) {
|
||||
const auto *MD = dyn_cast<CXXMethodDecl>(FD);
|
||||
if (!MD || MD->isStatic()) {
|
||||
S.Diag(ATL.getAttrNameLoc(), diag::err_lifetimebound_no_object_param)
|
||||
<< !MD << ATL.getLocalSourceRange();
|
||||
} else if (isa<CXXConstructorDecl>(MD) || isa<CXXDestructorDecl>(MD)) {
|
||||
S.Diag(ATL.getAttrNameLoc(), diag::err_lifetimebound_ctor_dtor)
|
||||
<< isa<CXXDestructorDecl>(MD) << ATL.getLocalSourceRange();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void checkDLLAttributeRedeclaration(Sema &S, NamedDecl *OldDecl,
|
||||
|
|
|
@ -6150,6 +6150,9 @@ static void ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D,
|
|||
case ParsedAttr::AT_Restrict:
|
||||
handleRestrictAttr(S, D, AL);
|
||||
break;
|
||||
case ParsedAttr::AT_LifetimeBound:
|
||||
handleSimpleAttribute<LifetimeBoundAttr>(S, D, AL);
|
||||
break;
|
||||
case ParsedAttr::AT_MayAlias:
|
||||
handleSimpleAttribute<MayAliasAttr>(S, D, AL);
|
||||
break;
|
||||
|
|
|
@ -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>;
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
||||
if (CXXBindTemporaryExpr *BTE = dyn_cast<CXXBindTemporaryExpr>(Init))
|
||||
Init = BTE->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;
|
||||
}
|
||||
|
||||
Init = CE->getSubExpr();
|
||||
}
|
||||
} while (Old != Init);
|
||||
|
||||
// C++17 [dcl.init.list]p6:
|
||||
// initializing an initializer_list object from the array extends the
|
||||
|
@ -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;
|
||||
});
|
||||
if (isa<CallExpr>(Init) || isa<CXXConstructExpr>(Init))
|
||||
return visitLifetimeBoundArguments(Path, Init, Visit);
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
Init = Init->IgnoreParens();
|
||||
switch (Init->getStmtClass()) {
|
||||
case Stmt::UnaryOperatorClass: {
|
||||
auto *UO = cast<UnaryOperator>(Init);
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
|
|
@ -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!");
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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))
|
||||
|
|
|
@ -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 ¶m [[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 ¶m [[clang::lifetimebound(42)]]); // expected-error {{takes no arguments}}
|
||||
}
|
||||
|
||||
namespace usage_ok {
|
||||
struct IntRef { int *target; };
|
||||
|
||||
int &refparam(int ¶m [[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 ¶m [[clang::lifetimebound]]) {
|
||||
return (long long)¶m;
|
||||
}
|
||||
// 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'}}
|
||||
}
|
|
@ -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
|
||||
|
@ -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");
|
||||
|
|
Loading…
Reference in New Issue