Skip to content

Commit

Permalink
Add lifetime categories attributes
Browse files Browse the repository at this point in the history
Summary:
This is the first part of work announced in
"[RFC] Adding lifetime analysis to clang" [0],
i.e. the addition of the [[gsl::Owner(T)]] and
[[gsl::Pointer(T)]] attributes, which
will enable user-defined types to participate in
the lifetime analysis (which will be part of the
next PR).
The type `T` here is called "DerefType" in the paper,
and denotes the type that an Owner owns and a Pointer
points to. E.g. `std::vector<int>` should be annotated
with `[[gsl::Owner(int)]]` and
a `std::vector<int>::iterator` with `[[gsl::Pointer(int)]]`.

[0] http://lists.llvm.org/pipermail/cfe-dev/2018-November/060355.html

Reviewers: gribozavr

Subscribers: xazax.hun, cfe-commits

Tags: #clang

Differential Revision: https://reviews.llvm.org/D63954

llvm-svn: 367040
  • Loading branch information
mgehre committed Jul 25, 2019
1 parent 3930948 commit d293cbd
Show file tree
Hide file tree
Showing 10 changed files with 306 additions and 12 deletions.
14 changes: 14 additions & 0 deletions clang/include/clang/Basic/Attr.td
Expand Up @@ -2796,6 +2796,20 @@ def TypeTagForDatatype : InheritableAttr {
let Documentation = [TypeTagForDatatypeDocs];
}

def Owner : InheritableAttr {
let Spellings = [CXX11<"gsl", "Owner">];
let Subjects = SubjectList<[Struct]>;
let Args = [TypeArgument<"DerefType", /*opt=*/1>];
let Documentation = [LifetimeOwnerDocs];
}

def Pointer : InheritableAttr {
let Spellings = [CXX11<"gsl", "Pointer">];
let Subjects = SubjectList<[Struct]>;
let Args = [TypeArgument<"DerefType", /*opt=*/1>];
let Documentation = [LifetimePointerDocs];
}

// Microsoft-related attributes

def MSNoVTable : InheritableAttr, TargetSpecificAttr<TargetMicrosoftCXXABI> {
Expand Down
68 changes: 68 additions & 0 deletions clang/include/clang/Basic/AttrDocs.td
Expand Up @@ -4230,3 +4230,71 @@ not initialized on device side. It has internal linkage and is initialized by
the initializer on host side.
}];
}

def LifetimeOwnerDocs : Documentation {
let Category = DocCatDecl;
let Content = [{
.. Note:: This attribute is experimental and its effect on analysis is subject to change in
a future version of clang.

The attribute ``[[gsl::Owner(T)]]`` applies to structs and classes that own an
object of type ``T``:

.. code-block:: c++

class [[gsl::Owner(int)]] IntOwner {
private:
int value;
public:
int *getInt() { return &value; }
};

The argument ``T`` is optional and currently ignored.
This attribute may be used by analysis tools and has no effect on code
generation.

See Pointer_ for an example.
}];
}

def LifetimePointerDocs : Documentation {
let Category = DocCatDecl;
let Content = [{
.. Note:: This attribute is experimental and its effect on analysis is subject to change in
a future version of clang.

The attribute ``[[gsl::Pointer(T)]]`` applies to structs and classes that behave
like pointers to an object of type ``T``:

.. code-block:: c++

class [[gsl::Pointer(int)]] IntPointer {
private:
int *valuePointer;
public:
int *getInt() { return &valuePointer; }
};

The argument ``T`` is optional and currently ignored.
This attribute may be used by analysis tools and has no effect on code
generation.

Example:
When constructing an instance of a class annotated like this (a Pointer) from
an instance of a class annotated with ``[[gsl::Owner]]`` (an Owner),
then the analysis will consider the Pointer to point inside the Owner.
When the Owner's lifetime ends, it will consider the Pointer to be dangling.

.. code-block:: c++

int f() {
IntPointer P;
if (true) {
IntOwner O(7);
P = IntPointer(O); // P "points into" O
} // P is dangling
return P.get(); // error: Using a dangling Pointer.
}

}];
}
5 changes: 3 additions & 2 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Expand Up @@ -2519,6 +2519,9 @@ def err_nsobject_attribute : Error<
"'NSObject' attribute is for pointer types only">;
def err_attributes_are_not_compatible : Error<
"%0 and %1 attributes are not compatible">;
def err_attribute_invalid_argument : Error<
"%select{'void'|a reference type|an array type|a non-vector or "
"non-vectorizable scalar type}0 is an invalid argument to attribute %1">;
def err_attribute_wrong_number_arguments : Error<
"%0 attribute %plural{0:takes no arguments|1:takes one argument|"
":requires exactly %1 arguments}1">;
Expand Down Expand Up @@ -2567,8 +2570,6 @@ def err_attribute_argument_out_of_range : Error<
def err_init_priority_object_attr : Error<
"can only use 'init_priority' attribute on file-scope definitions "
"of objects of class type">;
def err_attribute_argument_vec_type_hint : Error<
"invalid attribute argument %0 - expecting a vector or vectorizable scalar type">;
def err_attribute_argument_out_of_bounds : Error<
"%0 attribute parameter %1 is out of bounds">;
def err_attribute_only_once_per_parameter : Error<
Expand Down
27 changes: 22 additions & 5 deletions clang/lib/Parse/ParseDecl.cpp
Expand Up @@ -335,6 +335,7 @@ unsigned Parser::ParseAttributeArgsCommon(
ConsumeParen();

bool ChangeKWThisToIdent = attributeTreatsKeywordThisAsIdentifier(*AttrName);
bool AttributeIsTypeArgAttr = attributeIsTypeArgAttr(*AttrName);

// Interpret "kw_this" as an identifier if the attributed requests it.
if (ChangeKWThisToIdent && Tok.is(tok::kw_this))
Expand All @@ -360,6 +361,7 @@ unsigned Parser::ParseAttributeArgsCommon(
ArgExprs.push_back(ParseIdentifierLoc());
}

ParsedType TheParsedType;
if (!ArgExprs.empty() ? Tok.is(tok::comma) : Tok.isNot(tok::r_paren)) {
// Eat the comma.
if (!ArgExprs.empty())
Expand All @@ -372,8 +374,17 @@ unsigned Parser::ParseAttributeArgsCommon(
Tok.setKind(tok::identifier);

ExprResult ArgExpr;
if (Tok.is(tok::identifier) &&
attributeHasVariadicIdentifierArg(*AttrName)) {
if (AttributeIsTypeArgAttr) {
TypeResult T = ParseTypeName();
if (T.isInvalid()) {
SkipUntil(tok::r_paren, StopAtSemi);
return 0;
}
if (T.isUsable())
TheParsedType = T.get();
break; // FIXME: Multiple type arguments are not implemented.
} else if (Tok.is(tok::identifier) &&
attributeHasVariadicIdentifierArg(*AttrName)) {
ArgExprs.push_back(ParseIdentifierLoc());
} else {
bool Uneval = attributeParsedArgsUnevaluated(*AttrName);
Expand All @@ -397,14 +408,20 @@ unsigned Parser::ParseAttributeArgsCommon(
SourceLocation RParen = Tok.getLocation();
if (!ExpectAndConsume(tok::r_paren)) {
SourceLocation AttrLoc = ScopeLoc.isValid() ? ScopeLoc : AttrNameLoc;
Attrs.addNew(AttrName, SourceRange(AttrLoc, RParen), ScopeName, ScopeLoc,
ArgExprs.data(), ArgExprs.size(), Syntax);

if (AttributeIsTypeArgAttr && !TheParsedType.get().isNull()) {
Attrs.addNewTypeAttr(AttrName, SourceRange(AttrNameLoc, RParen),
ScopeName, ScopeLoc, TheParsedType, Syntax);
} else {
Attrs.addNew(AttrName, SourceRange(AttrLoc, RParen), ScopeName, ScopeLoc,
ArgExprs.data(), ArgExprs.size(), Syntax);
}
}

if (EndLoc)
*EndLoc = RParen;

return static_cast<unsigned>(ArgExprs.size());
return static_cast<unsigned>(ArgExprs.size() + !TheParsedType.get().isNull());
}

/// Parse the arguments to a parameterized GNU attribute or
Expand Down
68 changes: 66 additions & 2 deletions clang/lib/Sema/SemaDeclAttr.cpp
Expand Up @@ -2954,8 +2954,7 @@ static void handleVecTypeHint(Sema &S, Decl *D, const ParsedAttr &AL) {
if (!ParmType->isExtVectorType() && !ParmType->isFloatingType() &&
(ParmType->isBooleanType() ||
!ParmType->isIntegralType(S.getASTContext()))) {
S.Diag(AL.getLoc(), diag::err_attribute_argument_vec_type_hint)
<< ParmType;
S.Diag(AL.getLoc(), diag::err_attribute_invalid_argument) << 3 << AL;
return;
}

Expand Down Expand Up @@ -4555,6 +4554,67 @@ static void handleSuppressAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
DiagnosticIdentifiers.size(), AL.getAttributeSpellingListIndex()));
}

static void handleLifetimeCategoryAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
TypeSourceInfo *DerefTypeLoc = nullptr;
QualType ParmType;
if (AL.hasParsedType()) {
ParmType = S.GetTypeFromParser(AL.getTypeArg(), &DerefTypeLoc);

unsigned SelectIdx = ~0U;
if (ParmType->isVoidType())
SelectIdx = 0;
else if (ParmType->isReferenceType())
SelectIdx = 1;
else if (ParmType->isArrayType())
SelectIdx = 2;

if (SelectIdx != ~0U) {
S.Diag(AL.getLoc(), diag::err_attribute_invalid_argument)
<< SelectIdx << AL;
return;
}
}

// To check if earlier decl attributes do not conflict the newly parsed ones
// we always add (and check) the attribute to the cannonical decl.
D = D->getCanonicalDecl();
if (AL.getKind() == ParsedAttr::AT_Owner) {
if (checkAttrMutualExclusion<PointerAttr>(S, D, AL))
return;
if (const auto *OAttr = D->getAttr<OwnerAttr>()) {
const Type *ExistingDerefType = OAttr->getDerefTypeLoc()
? OAttr->getDerefType().getTypePtr()
: nullptr;
if (ExistingDerefType != ParmType.getTypePtrOrNull()) {
S.Diag(AL.getLoc(), diag::err_attributes_are_not_compatible)
<< AL << OAttr;
S.Diag(OAttr->getLocation(), diag::note_conflicting_attribute);
}
return;
}
D->addAttr(::new (S.Context)
OwnerAttr(AL.getRange(), S.Context, DerefTypeLoc,
AL.getAttributeSpellingListIndex()));
} else {
if (checkAttrMutualExclusion<OwnerAttr>(S, D, AL))
return;
if (const auto *PAttr = D->getAttr<PointerAttr>()) {
const Type *ExistingDerefType = PAttr->getDerefTypeLoc()
? PAttr->getDerefType().getTypePtr()
: nullptr;
if (ExistingDerefType != ParmType.getTypePtrOrNull()) {
S.Diag(AL.getLoc(), diag::err_attributes_are_not_compatible)
<< AL << PAttr;
S.Diag(PAttr->getLocation(), diag::note_conflicting_attribute);
}
return;
}
D->addAttr(::new (S.Context)
PointerAttr(AL.getRange(), S.Context, DerefTypeLoc,
AL.getAttributeSpellingListIndex()));
}
}

bool Sema::CheckCallingConvAttr(const ParsedAttr &Attrs, CallingConv &CC,
const FunctionDecl *FD) {
if (Attrs.isInvalid())
Expand Down Expand Up @@ -7158,6 +7218,10 @@ static void ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D,
case ParsedAttr::AT_Suppress:
handleSuppressAttr(S, D, AL);
break;
case ParsedAttr::AT_Owner:
case ParsedAttr::AT_Pointer:
handleLifetimeCategoryAttr(S, D, AL);
break;
case ParsedAttr::AT_OpenCLKernel:
handleSimpleAttribute<OpenCLKernelAttr>(S, D, AL);
break;
Expand Down
17 changes: 17 additions & 0 deletions clang/test/AST/ast-dump-attr.cpp
Expand Up @@ -211,6 +211,23 @@ namespace TestSuppress {
}
}

namespace TestLifetimeCategories {
class [[gsl::Owner(int)]] AOwner{};
// CHECK: CXXRecordDecl{{.*}} class AOwner
// CHECK: OwnerAttr {{.*}} int
class [[gsl::Pointer(int)]] APointer{};
// CHECK: CXXRecordDecl{{.*}} class APointer
// CHECK: PointerAttr {{.*}} int

class [[gsl::Pointer]] PointerWithoutArgument{};
// CHECK: CXXRecordDecl{{.*}} class PointerWithoutArgument
// CHECK: PointerAttr

class [[gsl::Owner]] OwnerWithoutArgument{};
// CHECK: CXXRecordDecl{{.*}} class OwnerWithoutArgument
// CHECK: OwnerAttr
} // namespace TestLifetimeCategories

// Verify the order of attributes in the Ast. It must reflect the order
// in the parsed source.
int mergeAttrTest() __attribute__((deprecated)) __attribute__((warn_unused_result));
Expand Down
Expand Up @@ -117,8 +117,10 @@
// CHECK-NEXT: OpenCLNoSVM (SubjectMatchRule_variable)
// CHECK-NEXT: OptimizeNone (SubjectMatchRule_function, SubjectMatchRule_objc_method)
// CHECK-NEXT: Overloadable (SubjectMatchRule_function)
// CHECK-NEXT: Owner (SubjectMatchRule_record_not_is_union)
// CHECK-NEXT: ParamTypestate (SubjectMatchRule_variable_is_parameter)
// CHECK-NEXT: PassObjectSize (SubjectMatchRule_variable_is_parameter)
// CHECK-NEXT: Pointer (SubjectMatchRule_record_not_is_union)
// CHECK-NEXT: RenderScriptKernel (SubjectMatchRule_function)
// CHECK-NEXT: ReqdWorkGroupSize (SubjectMatchRule_function)
// CHECK-NEXT: RequireConstantInit (SubjectMatchRule_variable_is_global)
Expand Down

0 comments on commit d293cbd

Please sign in to comment.