Skip to content

Commit

Permalink
start working on lifetime capture
Browse files Browse the repository at this point in the history
  • Loading branch information
usx95 committed Oct 8, 2024
1 parent 09284e7 commit 4951a7b
Show file tree
Hide file tree
Showing 14 changed files with 387 additions and 24 deletions.
40 changes: 40 additions & 0 deletions clang/include/clang/Basic/Attr.td
Original file line number Diff line number Diff line change
Expand Up @@ -1869,6 +1869,46 @@ def LifetimeBound : DeclOrTypeAttr {
let SimpleHandler = 1;
}

def LifetimeCaptureBy : DeclOrTypeAttr {
let Spellings = [Clang<"lifetime_capture_by", 0>];
let Subjects = SubjectList<[ParmVar, ImplicitObjectParameter], ErrorDiag>;
let Args = [VariadicParamOrParamIdxArgument<"Params">];
let Documentation = [LifetimeBoundDocs];
let LangOpts = [CPlusPlus];

// let SimpleHandler = 1;
// let LateParsed = LateAttrParseStandard;
// let HasCustomParsing = 1;
// let ParseArgumentsAsUnevaluated = 1;

let AdditionalMembers = [{
private:
SmallVector<IdentifierInfo*, 1> ArgIdents;
SmallVector<SourceLocation, 1> ArgLocs;

public:
static const int INVALID = -2;
static const int UNKNOWN = -1;
static const int GLOBAL = -1;
static const int THIS = 0;

void setArgs(SmallVector<IdentifierInfo*, 1> Idents,
SmallVector<SourceLocation, 1> Locs) {
assert(Idents.size() == Locs.size());
assert(Idents.size() == params_Size);
ArgIdents = std::move(Idents);
ArgLocs = std::move(Locs);
}

const SmallVector<IdentifierInfo*, 1>& getArgIdents() const { return ArgIdents; }
const SmallVector<SourceLocation, 1>& getArgLocs() const { return ArgLocs; }
void setParamIdx(size_t Idx, int Val) {
assert(Idx < params_Size);
params_[Idx] = Val;
}
}];
}

def TrivialABI : InheritableAttr {
// This attribute does not have a C [[]] spelling because it requires the
// CPlusPlus language option.
Expand Down
16 changes: 16 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -3382,6 +3382,18 @@ def err_callback_callee_is_variadic : Error<
"'callback' attribute callee may not be variadic">;
def err_callback_implicit_this_not_available : Error<
"'callback' argument at position %0 references unavailable implicit 'this'">;

def err_capture_by_attribute_multiple : Error<
"multiple 'lifetime_capture' attributes specified">;
def err_capture_by_attribute_no_entity : Error<
"'lifetime_capture_by' attribute specifies no capturing entity">;
def err_capture_by_implicit_this_not_available : Error<
"'lifetime_capture_by' argument references unavailable implicit 'this'">;
def err_capture_by_attribute_argument_unknown : Error<
"'lifetime_capture_by' attribute argument %0 is not a known function parameter"
". Must be a function parameter of one of 'this', 'global' or 'unknown'">;
def err_capture_by_references_itself : Error<"'lifetime_capture_by' argument references itself">;

def err_init_method_bad_return_type : Error<
"init methods must return an object pointer type, not %0">;
def err_attribute_invalid_size : Error<
Expand Down Expand Up @@ -10185,6 +10197,10 @@ def warn_dangling_pointer_assignment : Warning<
"object backing the pointer %0 "
"will be destroyed at the end of the full-expression">,
InGroup<DanglingAssignment>;
def warn_dangling_reference_captured : Warning<
"object captured by the '%0' "
"will be destroyed at the end of the full-expression">,
InGroup<DanglingAssignment>;

// For non-floating point, expressions of the form x == x or x != x
// should result in a warning, since these always evaluate to a constant.
Expand Down
7 changes: 7 additions & 0 deletions clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
Expand Up @@ -1830,6 +1830,10 @@ class Sema final : public SemaBase {
/// Add [[gsl::Pointer]] attributes for std:: types.
void inferGslPointerAttribute(TypedefNameDecl *TD);

LifetimeCaptureByAttr *ParseLifetimeCaptureByAttr(const ParsedAttr &AL,
StringRef ParamName);
void LazyProcessLifetimeCaptureByParams(FunctionDecl *FD);

/// Add _Nullable attributes for std:: types.
void inferNullableClassAttribute(CXXRecordDecl *CRD);

Expand Down Expand Up @@ -2384,6 +2388,9 @@ class Sema final : public SemaBase {
bool BuiltinVectorMath(CallExpr *TheCall, QualType &Res);
bool BuiltinVectorToScalarMath(CallExpr *TheCall);

void checkLifetimeCaptureBy(FunctionDecl *FDecl, bool IsMemberFunction,
const Expr *ThisArg, ArrayRef<const Expr *> Args);

/// Handles the checks for format strings, non-POD arguments to vararg
/// functions, NULL arguments passed to non-NULL parameters, diagnose_if
/// attributes and AArch64 SME attributes.
Expand Down
21 changes: 21 additions & 0 deletions clang/lib/AST/TypePrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

#include "clang/AST/ASTContext.h"
#include "clang/AST/Attr.h"
#include "clang/AST/Attrs.inc"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclBase.h"
#include "clang/AST/DeclCXX.h"
Expand All @@ -25,6 +26,7 @@
#include "clang/AST/TextNodeDumper.h"
#include "clang/AST/Type.h"
#include "clang/Basic/AddressSpaces.h"
#include "clang/Basic/AttrKinds.h"
#include "clang/Basic/ExceptionSpecificationType.h"
#include "clang/Basic/IdentifierTable.h"
#include "clang/Basic/LLVM.h"
Expand Down Expand Up @@ -1907,6 +1909,24 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
OS << " [[clang::lifetimebound]]";
return;
}
if (T->getAttrKind() == attr::LifetimeCaptureBy) {
OS << " [[clang::lifetime_capture_by(...)";
// const LifetimeCaptureByAttr* A= T->getAs<LifetimeCaptureByAttr>();
// bool valid = true;
// for (int I : A->params())
// valid &= I != -2;
// if (valid) {
// OS << "invalid)";
// return;
// }
// for (size_t I = 0; I < A->params_size(); ++I) {
// OS << A->getArgIdents()[I]->getName()
// << "(idx: " << *(A->params_begin() + I) << ")";
// if (I != A->params_size() - 1)
// OS << ", ";
// }
return;
}

// The printing of the address_space attribute is handled by the qualifier
// since it is still stored in the qualifier. Return early to prevent printing
Expand Down Expand Up @@ -1966,6 +1986,7 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
case attr::SizedBy:
case attr::SizedByOrNull:
case attr::LifetimeBound:
case attr::LifetimeCaptureBy:
case attr::TypeNonNull:
case attr::TypeNullable:
case attr::TypeNullableResult:
Expand Down
54 changes: 41 additions & 13 deletions clang/lib/Sema/CheckExprLifetime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,14 @@ enum LifetimeKind {
/// a default member initializer), the program is ill-formed.
LK_MemInitializer,

/// The lifetime of a temporary bound to this entity probably ends too soon,
/// The lifetime of a temporary bound to this entity may end too soon,
/// because the entity is a pointer and we assign the address of a temporary
/// object to it.
LK_Assignment,

/// The lifetime of a temporary bound to this entity probably ends too soon,
/// because the entity may capture the reference to a temporary object.
LK_LifetimeCapture,
};
using LifetimeResult =
llvm::PointerIntPair<const InitializedEntity *, 3, LifetimeKind>;
Expand Down Expand Up @@ -189,6 +193,7 @@ struct IndirectLocalPathEntry {
VarInit,
LValToRVal,
LifetimeBoundCall,
LifetimeCapture,
TemporaryCopy,
LambdaCaptureInit,
GslReferenceInit,
Expand Down Expand Up @@ -898,6 +903,7 @@ static SourceRange nextPathEntryRange(const IndirectLocalPath &Path, unsigned I,
case IndirectLocalPathEntry::AddressOf:
case IndirectLocalPathEntry::LValToRVal:
case IndirectLocalPathEntry::LifetimeBoundCall:
case IndirectLocalPathEntry::LifetimeCapture:
case IndirectLocalPathEntry::TemporaryCopy:
case IndirectLocalPathEntry::GslReferenceInit:
case IndirectLocalPathEntry::GslPointerInit:
Expand Down Expand Up @@ -928,6 +934,7 @@ static bool pathOnlyHandlesGslPointer(IndirectLocalPath &Path) {
case IndirectLocalPathEntry::VarInit:
case IndirectLocalPathEntry::AddressOf:
case IndirectLocalPathEntry::LifetimeBoundCall:
case IndirectLocalPathEntry::LifetimeCapture:
continue;
case IndirectLocalPathEntry::GslPointerInit:
case IndirectLocalPathEntry::GslReferenceInit:
Expand All @@ -948,21 +955,22 @@ static bool isAssignmentOperatorLifetimeBound(CXXMethodDecl *CMD) {
}

static bool shouldRunGSLAssignmentAnalysis(const Sema &SemaRef,
const AssignedEntity &Entity) {
const CapturingEntity &Entity) {
bool EnableGSLAssignmentWarnings = !SemaRef.getDiagnostics().isIgnored(
diag::warn_dangling_lifetime_pointer_assignment, SourceLocation());
return (EnableGSLAssignmentWarnings &&
(isRecordWithAttr<PointerAttr>(Entity.LHS->getType()) ||
(isRecordWithAttr<PointerAttr>(Entity.Expression->getType()) ||
isAssignmentOperatorLifetimeBound(Entity.AssignmentOperator)));
}

static void checkExprLifetimeImpl(Sema &SemaRef,
const InitializedEntity *InitEntity,
const InitializedEntity *ExtendingEntity,
LifetimeKind LK,
const AssignedEntity *AEntity, Expr *Init) {
assert((AEntity && LK == LK_Assignment) ||
(InitEntity && LK != LK_Assignment));
const CapturingEntity *CEntity, Expr *Init) {
assert(InitEntity || CEntity);
assert(!CEntity || LK == LK_Assignment || LK == LK_LifetimeCapture);
assert(!InitEntity || LK != LK_Assignment);
// If this entity doesn't have an interesting lifetime, don't bother looking
// for temporaries within its initializer.
if (LK == LK_FullExpression)
Expand Down Expand Up @@ -1046,6 +1054,17 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
break;
}

case LK_LifetimeCapture: {
if (!MTE)
return false;
assert(shouldLifetimeExtendThroughPath(Path) ==
PathLifetimeKind::NoExtend &&
"No lifetime extension for in function calls");
SemaRef.Diag(DiagLoc, diag::warn_dangling_reference_captured)
<< CEntity->Expression << DiagRange;
return false;
}

case LK_Assignment: {
if (!MTE || pathContainsInit(Path))
return false;
Expand All @@ -1056,7 +1075,7 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
IsGslPtrValueFromGslTempOwner
? diag::warn_dangling_lifetime_pointer_assignment
: diag::warn_dangling_pointer_assignment)
<< AEntity->LHS << DiagRange;
<< CEntity->Expression << DiagRange;
return false;
}
case LK_MemInitializer: {
Expand Down Expand Up @@ -1199,6 +1218,7 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
break;

case IndirectLocalPathEntry::LifetimeBoundCall:
case IndirectLocalPathEntry::LifetimeCapture:
case IndirectLocalPathEntry::TemporaryCopy:
case IndirectLocalPathEntry::GslPointerInit:
case IndirectLocalPathEntry::GslReferenceInit:
Expand Down Expand Up @@ -1243,8 +1263,10 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
};

llvm::SmallVector<IndirectLocalPathEntry, 8> Path;
if (LK == LK_Assignment && shouldRunGSLAssignmentAnalysis(SemaRef, *AEntity))
if (LK == LK_Assignment && shouldRunGSLAssignmentAnalysis(SemaRef, *CEntity))
Path.push_back({IndirectLocalPathEntry::GslPointerAssignment, Init});
else if (LK == LK_LifetimeCapture)
Path.push_back({IndirectLocalPathEntry::LifetimeCapture, Init});

if (Init->isGLValue())
visitLocalsRetainedByReferenceBinding(Path, Init, RK_ReferenceBinding,
Expand All @@ -1256,7 +1278,7 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
/*RevisitSubinits=*/!InitEntity);
}

void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity,
void checkInitLifetime(Sema &SemaRef, const InitializedEntity &Entity,
Expr *Init) {
auto LTResult = getEntityLifetime(&Entity);
LifetimeKind LK = LTResult.getInt();
Expand All @@ -1265,20 +1287,26 @@ void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity,
/*AEntity*/ nullptr, Init);
}

void checkExprLifetime(Sema &SemaRef, const AssignedEntity &Entity,
Expr *Init) {
void checkAssignmentLifetime(Sema &SemaRef, const CapturingEntity &Entity,
Expr *RHS) {
bool EnableDanglingPointerAssignment = !SemaRef.getDiagnostics().isIgnored(
diag::warn_dangling_pointer_assignment, SourceLocation());
bool RunAnalysis = (EnableDanglingPointerAssignment &&
Entity.LHS->getType()->isPointerType()) ||
Entity.Expression->getType()->isPointerType()) ||
shouldRunGSLAssignmentAnalysis(SemaRef, Entity);

if (!RunAnalysis)
return;

checkExprLifetimeImpl(SemaRef, /*InitEntity=*/nullptr,
/*ExtendingEntity=*/nullptr, LK_Assignment, &Entity,
Init);
RHS);
}

void checkCaptureLifetime(Sema &SemaRef, const CapturingEntity &Entity,
Expr *Captured) {
checkExprLifetimeImpl(SemaRef, /*InitEntity=*/nullptr,
/*ExtendingEntity=*/nullptr, LK_LifetimeCapture,
&Entity, Captured);
}
} // namespace clang::sema
26 changes: 20 additions & 6 deletions clang/lib/Sema/CheckExprLifetime.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,36 @@

namespace clang::sema {

/// Describes an entity that is being assigned.
struct AssignedEntity {
// The left-hand side expression of the assignment.
Expr *LHS = nullptr;
struct CapturingEntity {
// The expression of the entity which captures another entity.
// For example:
// 1. In an assignment, this would be the left-hand side expression.
// std::string_view sv;
// sv = std::string(); // Here 'sv' is the 'Entity'.
//
// 2. In an function call involving a lifetime capture, this would be the
// argument capturing the lifetime of another argument.
// void addToSet(std::string_view s [[clang::lifetime_capture_by(sv)]],
// set<std::string_view>& setsv);
// set<std::string_view> ssv;
// addToSet(std::string(), ssv); // Here 'ssv' is the 'Entity'.
Expr *Expression = nullptr;
CXXMethodDecl *AssignmentOperator = nullptr;
};

/// Check that the lifetime of the given expr (and its subobjects) is
/// sufficient for initializing the entity, and perform lifetime extension
/// (when permitted) if not.
void checkExprLifetime(Sema &SemaRef, const InitializedEntity &Entity,
void checkInitLifetime(Sema &SemaRef, const InitializedEntity &Entity,
Expr *Init);

/// Check that the lifetime of the given expr (and its subobjects) is
/// sufficient for assigning to the entity.
void checkExprLifetime(Sema &SemaRef, const AssignedEntity &Entity, Expr *Init);
void checkAssignmentLifetime(Sema &SemaRef, const CapturingEntity &Entity,
Expr *RHS);

void checkCaptureLifetime(Sema &SemaRef, const CapturingEntity &Entity,
Expr *Captured);

} // namespace clang::sema

Expand Down
27 changes: 27 additions & 0 deletions clang/lib/Sema/SemaChecking.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@
//
//===----------------------------------------------------------------------===//

#include "CheckExprLifetime.h"
#include "clang/AST/APValue.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Attr.h"
#include "clang/AST/AttrIterator.h"
#include "clang/AST/Attrs.inc"
#include "clang/AST/CharUnits.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclBase.h"
Expand Down Expand Up @@ -3203,6 +3205,29 @@ void Sema::CheckArgAlignment(SourceLocation Loc, NamedDecl *FDecl,
<< ParamName << (FDecl != nullptr) << FDecl;
}

void Sema::checkLifetimeCaptureBy(FunctionDecl *FD, bool IsMemberFunction,
const Expr *ThisArg,
ArrayRef<const Expr *> Args) {
auto GetArgAt = [&](int Idx) {
if (IsMemberFunction && Idx == 0)
return const_cast<Expr *>(ThisArg);
return const_cast<Expr *>(Args[Idx - int(IsMemberFunction)]);
};
for (unsigned I = 0; I < FD->getNumParams(); ++I) {
auto *CapturedByAttr =
FD->getParamDecl(I)->getAttr<LifetimeCaptureByAttr>();
if (!CapturedByAttr)
continue;
for (int CapturingParamIdx : CapturedByAttr->params()) {
Expr *Capturing = GetArgAt(CapturingParamIdx);
Expr *Captured = GetArgAt(I + IsMemberFunction);
CapturingEntity CE{Capturing};
// Ensure that 'Captured' lives atleast as long as the 'Capturing' entity.
checkCaptureLifetime(*this, CE, Captured);
}
}
}

void Sema::checkCall(NamedDecl *FDecl, const FunctionProtoType *Proto,
const Expr *ThisArg, ArrayRef<const Expr *> Args,
bool IsMemberFunction, SourceLocation Loc,
Expand Down Expand Up @@ -3244,6 +3269,8 @@ void Sema::checkCall(NamedDecl *FDecl, const FunctionProtoType *Proto,
}
}

if (FD)
checkLifetimeCaptureBy(FD, IsMemberFunction, ThisArg, Args);
if (FDecl || Proto) {
CheckNonNullArguments(*this, FDecl, Proto, Args, Loc);

Expand Down
Loading

0 comments on commit 4951a7b

Please sign in to comment.