From 07d5b4a5da63a6e1602d04fee0eabcae3871b2f2 Mon Sep 17 00:00:00 2001 From: Matthias Gehre Date: Thu, 25 Jul 2019 17:50:51 +0000 Subject: [PATCH] Add lifetime categories attributes 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` should be annotated with `[[gsl::Owner(int)]]` and a `std::vector::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 git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@367040 91177308-0d34-0410-b5e6-96231b3b80d8 --- include/clang/Basic/Attr.td | 14 +++ include/clang/Basic/AttrDocs.td | 68 +++++++++++ include/clang/Basic/DiagnosticSemaKinds.td | 5 +- lib/Parse/ParseDecl.cpp | 27 ++++- lib/Sema/SemaDeclAttr.cpp | 68 ++++++++++- test/AST/ast-dump-attr.cpp | 17 +++ ...a-attribute-supported-attributes-list.test | 2 + test/SemaCXX/attr-gsl-owner-pointer.cpp | 107 ++++++++++++++++++ test/SemaOpenCL/invalid-kernel-attrs.cl | 6 +- utils/TableGen/ClangAttrEmitter.cpp | 4 + 10 files changed, 306 insertions(+), 12 deletions(-) create mode 100644 test/SemaCXX/attr-gsl-owner-pointer.cpp diff --git a/include/clang/Basic/Attr.td b/include/clang/Basic/Attr.td index feebd56160..9f9292d4e5 100644 --- a/include/clang/Basic/Attr.td +++ b/include/clang/Basic/Attr.td @@ -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 { diff --git a/include/clang/Basic/AttrDocs.td b/include/clang/Basic/AttrDocs.td index 5982502318..81dec0fdef 100644 --- a/include/clang/Basic/AttrDocs.td +++ b/include/clang/Basic/AttrDocs.td @@ -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. + } + +}]; +} diff --git a/include/clang/Basic/DiagnosticSemaKinds.td b/include/clang/Basic/DiagnosticSemaKinds.td index 42facca446..e58a0b16bd 100644 --- a/include/clang/Basic/DiagnosticSemaKinds.td +++ b/include/clang/Basic/DiagnosticSemaKinds.td @@ -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">; @@ -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< diff --git a/lib/Parse/ParseDecl.cpp b/lib/Parse/ParseDecl.cpp index 73b4f50fda..3cf1f82943 100644 --- a/lib/Parse/ParseDecl.cpp +++ b/lib/Parse/ParseDecl.cpp @@ -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)) @@ -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()) @@ -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); @@ -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(ArgExprs.size()); + return static_cast(ArgExprs.size() + !TheParsedType.get().isNull()); } /// Parse the arguments to a parameterized GNU attribute or diff --git a/lib/Sema/SemaDeclAttr.cpp b/lib/Sema/SemaDeclAttr.cpp index 25632e63e7..950dcac1bb 100644 --- a/lib/Sema/SemaDeclAttr.cpp +++ b/lib/Sema/SemaDeclAttr.cpp @@ -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; } @@ -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(S, D, AL)) + return; + if (const auto *OAttr = D->getAttr()) { + 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(S, D, AL)) + return; + if (const auto *PAttr = D->getAttr()) { + 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()) @@ -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(S, D, AL); break; diff --git a/test/AST/ast-dump-attr.cpp b/test/AST/ast-dump-attr.cpp index 8f67b9934e..83c4a6342d 100644 --- a/test/AST/ast-dump-attr.cpp +++ b/test/AST/ast-dump-attr.cpp @@ -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)); diff --git a/test/Misc/pragma-attribute-supported-attributes-list.test b/test/Misc/pragma-attribute-supported-attributes-list.test index fc9d86efe7..cc2d380601 100644 --- a/test/Misc/pragma-attribute-supported-attributes-list.test +++ b/test/Misc/pragma-attribute-supported-attributes-list.test @@ -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) diff --git a/test/SemaCXX/attr-gsl-owner-pointer.cpp b/test/SemaCXX/attr-gsl-owner-pointer.cpp new file mode 100644 index 0000000000..1c3deb3982 --- /dev/null +++ b/test/SemaCXX/attr-gsl-owner-pointer.cpp @@ -0,0 +1,107 @@ +// RUN: %clang_cc1 -verify -ast-dump %s | \ +// RUN: FileCheck --implicit-check-not OwnerAttr --implicit-check-not PointerAttr %s + +int [[gsl::Owner]] i; +// expected-error@-1 {{'Owner' attribute cannot be applied to types}} +void [[gsl::Owner]] f(); +// expected-error@-1 {{'Owner' attribute cannot be applied to types}} + +[[gsl::Owner]] void f(); +// expected-warning@-1 {{'Owner' attribute only applies to structs}} + +union [[gsl::Owner(int)]] Union{}; +// expected-warning@-1 {{'Owner' attribute only applies to structs}} + +struct S { +}; + +S [[gsl::Owner]] Instance; +// expected-error@-1 {{'Owner' attribute cannot be applied to types}} + +class [[gsl::Owner(7)]] OwnerDerefNoType{}; +// expected-error@-1 {{expected a type}} + +class [[gsl::Pointer("int")]] PointerDerefNoType{}; +// expected-error@-1 {{expected a type}} + +class [[gsl::Owner(int)]] [[gsl::Pointer(int)]] BothOwnerPointer{}; +// expected-error@-1 {{'Pointer' and 'Owner' attributes are not compatible}} +// expected-note@-2 {{conflicting attribute is here}} +// CHECK: CXXRecordDecl {{.*}} BothOwnerPointer +// CHECK: OwnerAttr {{.*}} int + +class [[gsl::Owner(void)]] OwnerVoidDerefType{}; +// expected-error@-1 {{'void' is an invalid argument to attribute 'Owner'}} +class [[gsl::Pointer(void)]] PointerVoidDerefType{}; +// expected-error@-1 {{'void' is an invalid argument to attribute 'Pointer'}} + +class [[gsl::Pointer(int)]] AddConflictLater{}; +// CHECK: CXXRecordDecl {{.*}} AddConflictLater +// CHECK: PointerAttr {{.*}} int +class [[gsl::Owner(int)]] AddConflictLater; +// expected-error@-1 {{'Owner' and 'Pointer' attributes are not compatible}} +// expected-note@-5 {{conflicting attribute is here}} +// CHECK: CXXRecordDecl {{.*}} AddConflictLater +// CHECK: PointerAttr {{.*}} Inherited int + +class [[gsl::Owner(int)]] AddConflictLater2{}; +// CHECK: CXXRecordDecl {{.*}} AddConflictLater2 +// CHECK: OwnerAttr {{.*}} int +class [[gsl::Owner(float)]] AddConflictLater2; +// expected-error@-1 {{'Owner' and 'Owner' attributes are not compatible}} +// expected-note@-5 {{conflicting attribute is here}} +// CHECK: CXXRecordDecl {{.*}} AddConflictLater +// CHECK: OwnerAttr {{.*}} Inherited int + +class [[gsl::Owner()]] [[gsl::Owner(int)]] WithAndWithoutParameter{}; +// expected-error@-1 {{'Owner' and 'Owner' attributes are not compatible}} +// expected-note@-2 {{conflicting attribute is here}} +// CHECK: CXXRecordDecl {{.*}} WithAndWithoutParameter +// CHECK: OwnerAttr + +class [[gsl::Owner(int &)]] ReferenceType{}; +// expected-error@-1 {{a reference type is an invalid argument to attribute 'Owner'}} + +class [[gsl::Pointer(int[])]] ArrayType{}; +// expected-error@-1 {{an array type is an invalid argument to attribute 'Pointer'}} + +class [[gsl::Owner]] OwnerMissingParameter{}; +// CHECK: CXXRecordDecl {{.*}} OwnerMissingParameter +// CHECK: OwnerAttr + +class [[gsl::Pointer]] PointerMissingParameter{}; +// CHECK: CXXRecordDecl {{.*}} PointerMissingParameter +// CHECK: PointerAttr + +class [[gsl::Owner()]] OwnerWithEmptyParameterList{}; +// CHECK: CXXRecordDecl {{.*}} OwnerWithEmptyParameterList +// CHECK: OwnerAttr {{.*}} + +class [[gsl::Pointer()]] PointerWithEmptyParameterList{}; +// CHECK: CXXRecordDecl {{.*}} PointerWithEmptyParameterList +// CHECK: PointerAttr {{.*}} + +struct [[gsl::Owner(int)]] AnOwner{}; +// CHECK: CXXRecordDecl {{.*}} AnOwner +// CHECK: OwnerAttr {{.*}} int + +struct S; +class [[gsl::Pointer(S)]] APointer{}; +// CHECK: CXXRecordDecl {{.*}} APointer +// CHECK: PointerAttr {{.*}} S + +class [[gsl::Owner(int)]] [[gsl::Owner(int)]] DuplicateOwner{}; +// CHECK: CXXRecordDecl {{.*}} DuplicateOwner +// CHECK: OwnerAttr {{.*}} int + +class [[gsl::Pointer(int)]] [[gsl::Pointer(int)]] DuplicatePointer{}; +// CHECK: CXXRecordDecl {{.*}} DuplicatePointer +// CHECK: PointerAttr {{.*}} int + +class [[gsl::Owner(int)]] AddTheSameLater{}; +// CHECK: CXXRecordDecl {{.*}} AddTheSameLater +// CHECK: OwnerAttr {{.*}} int + +class [[gsl::Owner(int)]] AddTheSameLater; +// CHECK: CXXRecordDecl {{.*}} prev {{.*}} AddTheSameLater +// CHECK: OwnerAttr {{.*}} int diff --git a/test/SemaOpenCL/invalid-kernel-attrs.cl b/test/SemaOpenCL/invalid-kernel-attrs.cl index a96e85c9ee..daa8fa07f6 100644 --- a/test/SemaOpenCL/invalid-kernel-attrs.cl +++ b/test/SemaOpenCL/invalid-kernel-attrs.cl @@ -1,12 +1,12 @@ -// RUN: %clang_cc1 -verify %s +// RUN: %clang_cc1 -verify %s kernel __attribute__((vec_type_hint)) void kernel1() {} //expected-error{{'vec_type_hint' attribute takes one argument}} kernel __attribute__((vec_type_hint(not_type))) void kernel2() {} //expected-error{{unknown type name 'not_type'}} -kernel __attribute__((vec_type_hint(void))) void kernel3() {} //expected-error{{invalid attribute argument 'void' - expecting a vector or vectorizable scalar type}} +kernel __attribute__((vec_type_hint(void))) void kernel3() {} //expected-error{{a non-vector or non-vectorizable scalar type is an invalid argument to attribute 'vec_type_hint'}} -kernel __attribute__((vec_type_hint(bool))) void kernel4() {} //expected-error{{invalid attribute argument 'bool' - expecting a vector or vectorizable scalar type}} +kernel __attribute__((vec_type_hint(bool))) void kernel4() {} //expected-error{{a non-vector or non-vectorizable scalar type is an invalid argument to attribute 'vec_type_hint'}} kernel __attribute__((vec_type_hint(int))) __attribute__((vec_type_hint(float))) void kernel5() {} //expected-warning{{attribute 'vec_type_hint' is already applied with different parameters}} diff --git a/utils/TableGen/ClangAttrEmitter.cpp b/utils/TableGen/ClangAttrEmitter.cpp index f315262ad0..0489bb073d 100644 --- a/utils/TableGen/ClangAttrEmitter.cpp +++ b/utils/TableGen/ClangAttrEmitter.cpp @@ -303,6 +303,8 @@ namespace { std::string getIsOmitted() const override { if (type == "IdentifierInfo *") return "!get" + getUpperName().str() + "()"; + if (type == "TypeSourceInfo *") + return "!get" + getUpperName().str() + "Loc()"; if (type == "ParamIdx") return "!get" + getUpperName().str() + "().isValid()"; return "false"; @@ -336,6 +338,8 @@ namespace { << " OS << \" \" << SA->get" << getUpperName() << "()->getName();\n"; } else if (type == "TypeSourceInfo *") { + if (isOptional()) + OS << " if (SA->get" << getUpperName() << "Loc())"; OS << " OS << \" \" << SA->get" << getUpperName() << "().getAsString();\n"; } else if (type == "bool") { -- 2.40.0