diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst --- a/clang/docs/LanguageExtensions.rst +++ b/clang/docs/LanguageExtensions.rst @@ -330,11 +330,11 @@ ``__BASE_FILE__`` Defined to a string that contains the name of the main input file passed to Clang. - + ``__FILE_NAME__`` Clang-specific extension that functions similar to ``__FILE__`` but only renders the last path component (the filename) instead of an invocation - dependent full path to that file. + dependent full path to that file. ``__COUNTER__`` Defined to an integer value that starts at zero and is incremented each time @@ -2542,6 +2542,13 @@ in the analyzer's `list of source-level annotations `_. +Use ``__is_gsl_pointer(T)`` to check if the type ``T`` is a pointer, reference +or a class with an implicit or explicit ``[[gsl::Pointer]]`` attribute. Clang +implicitly adds that attribute to suitable classes from namespace ``std``. + +Use ``__is_gsl_owner(T)`` to check if the type ``T`` a class with an implicit +or explicit ``[[gsl::Owner]]`` attribute. Clang implicitly adds that attribute +to suitable classes from namespace ``std``. Extensions for Dynamic Analysis =============================== diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -2769,14 +2769,16 @@ def Owner : InheritableAttr { let Spellings = [CXX11<"gsl", "Owner">]; let Subjects = SubjectList<[CXXRecord]>; - let Args = [TypeArgument<"DerefType">]; + let Args = [TypeArgument<"DerefType", /*opt=*/1>]; + let MeaningfulToClassTemplateDefinition = 1; let Documentation = [LifetimeOwnerDocs]; } def Pointer : InheritableAttr { let Spellings = [CXX11<"gsl", "Pointer">]; let Subjects = SubjectList<[CXXRecord]>; - let Args = [TypeArgument<"DerefType">]; + let Args = [TypeArgument<"DerefType", /*opt=*/1>]; + let MeaningfulToClassTemplateDefinition = 1; let Documentation = [LifetimePointerDocs]; } diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -4164,7 +4164,8 @@ When annotating a class ``O`` with ``[[gsl::Owner(T)]]``, then each function that returns cv-qualified ``T&`` or ``T*`` is assumed to return a pointer/reference to the data owned by ``O``. The owned data is assumed to end -its lifetime once the owning object's lifetime ends. +its lifetime once the owning object's lifetime ends. The argument ``T`` is +optional. This attribute may be used by analysis tools but will not have effect on code generation. @@ -4174,8 +4175,10 @@ def LifetimePointerDocs : Documentation { let Category = DocCatFunction; let Content = [{ -When annotating a class ``P`` with ``[[gsl::Pointer(T)]]``, it assumed to be a +When annotating a class with ``[[gsl::Pointer(T)]]``, it assumed to be a non-owning type whose objects can point to ``T`` type objects or dangle. +The argument ``T`` is optional. + This attribute may be used by analysis tools but will not have effect on code generation. diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def --- a/clang/include/clang/Basic/TokenKinds.def +++ b/clang/include/clang/Basic/TokenKinds.def @@ -482,6 +482,8 @@ TYPE_TRAIT_1(__is_trivially_copyable, IsTriviallyCopyable, KEYCXX) TYPE_TRAIT_2(__is_trivially_assignable, IsTriviallyAssignable, KEYCXX) TYPE_TRAIT_2(__reference_binds_to_temporary, ReferenceBindsToTemporary, KEYCXX) +TYPE_TRAIT_1(__is_gsl_owner, IsGslOwner, KEYCXX) +TYPE_TRAIT_1(__is_gsl_pointer, IsGslPointer, KEYCXX) KEYWORD(__underlying_type , KEYCXX) // Embarcadero Expression Traits diff --git a/clang/include/clang/Basic/TypeTraits.h b/clang/include/clang/Basic/TypeTraits.h --- a/clang/include/clang/Basic/TypeTraits.h +++ b/clang/include/clang/Basic/TypeTraits.h @@ -44,6 +44,8 @@ UTT_IsFloatingPoint, UTT_IsFunction, UTT_IsFundamental, + UTT_IsGslOwner, + UTT_IsGslPointer, UTT_IsIntegral, UTT_IsInterfaceClass, UTT_IsLiteral, diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -6085,6 +6085,18 @@ ClassTemplateSpecializationDecl *BaseTemplateSpec, SourceLocation BaseLoc); + /// Add gsl::Pointer attribute to std::container::iterator + /// \param ND The declaration that introduces the name + /// std::container::iterator. \param UnderlyingRecord The record named by ND. + void addDefaultGslPointerAttribute(NamedDecl *ND, + CXXRecordDecl *UnderlyingRecord); + + /// Add [[gsl::Owner]] and [[gsl::Pointer]] attributes for std:: types. + void addDefaultGslOwnerPointerAttribute(CXXRecordDecl *Record); + + /// Add [[gsl::Owner]] and [[gsl::Pointer]] attributes for std:: types. + void addDefaultGslPointerAttribute(TypedefNameDecl *TD); + void CheckCompletedCXXClass(CXXRecordDecl *Record); /// Check that the C++ class annoated with "trivial_abi" satisfies all the diff --git a/clang/lib/Sema/SemaAttr.cpp b/clang/lib/Sema/SemaAttr.cpp --- a/clang/lib/Sema/SemaAttr.cpp +++ b/clang/lib/Sema/SemaAttr.cpp @@ -85,6 +85,114 @@ MSVtorDispAttr::CreateImplicit(Context, VtorDispStack.CurrentValue)); } +template +static void addGslOwnerPointerAttributeIfNotExisting(ASTContext &Context, + CXXRecordDecl *Record) { + CXXRecordDecl *Canonical = Record->getCanonicalDecl(); + if (Canonical->hasAttr() || Canonical->hasAttr()) + return; + + Canonical->addAttr(::new (Context) Attribute(SourceRange{}, Context, + /*DerefType*/ nullptr, + /*Spelling=*/0)); +} + +void Sema::addDefaultGslPointerAttribute(NamedDecl *ND, + CXXRecordDecl *UnderlyingRecord) { + if (!UnderlyingRecord) + return; + + const auto *Parent = dyn_cast(ND->getDeclContext()); + if (!Parent) + return; + + static llvm::StringSet<> Containers{ + "array", + "basic_string", + "deque", + "forward_list", + "vector", + "list", + "map", + "multiset", + "multimap", + "priority_queue", + "queue", + "set", + "stack", + "unordered_set", + "unordered_map", + "unordered_multiset", + "unordered_multimap", + }; + + static llvm::StringSet<> Iterators{"iterator", "const_iterator", + "reverse_iterator", + "const_reverse_iterator"}; + + if (Parent->isInStdNamespace() && Iterators.count(ND->getName()) && + Containers.count(Parent->getName())) + addGslOwnerPointerAttributeIfNotExisting(Context, + UnderlyingRecord); +} + +void Sema::addDefaultGslPointerAttribute(TypedefNameDecl *TD) { + addDefaultGslPointerAttribute( + TD, TD->getUnderlyingType().getCanonicalType()->getAsCXXRecordDecl()); +} + +void Sema::addDefaultGslOwnerPointerAttribute(CXXRecordDecl *Record) { + static llvm::StringSet<> StdOwners{ + "any", + "array", + "basic_regex", + "basic_string", + "deque", + "forward_list", + "vector", + "list", + "map", + "multiset", + "multimap", + "optional", + "priority_queue", + "queue", + "set", + "stack", + "unique_ptr", + "unordered_set", + "unordered_map", + "unordered_multiset", + "unordered_multimap", + "variant", + }; + static llvm::StringSet<> StdPointers{ + "basic_string_view", + "reference_wrapper", + "regex_iterator", + }; + + if (!Record->getIdentifier()) + return; + + // Handle classes that directly appear in std namespace. + if (Record->isInStdNamespace()) { + CXXRecordDecl *Canonical = Record->getCanonicalDecl(); + if (Canonical->hasAttr() || Canonical->hasAttr()) + return; + + if (StdOwners.count(Record->getName())) + addGslOwnerPointerAttributeIfNotExisting(Context, Record); + else if (StdPointers.count(Record->getName())) + addGslOwnerPointerAttributeIfNotExisting(Context, Record); + + return; + } + + // Handle nested classes that could be a gsl::Pointer. + addDefaultGslPointerAttribute(Record, Record); +} + void Sema::ActOnPragmaOptionsAlign(PragmaOptionsAlignKind Kind, SourceLocation PragmaLoc) { PragmaMsStackAction Action = Sema::PSK_Reset; diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -15075,6 +15075,9 @@ if (PrevDecl) mergeDeclAttributes(New, PrevDecl); + if (auto *CXXRD = dyn_cast(New)) + addDefaultGslOwnerPointerAttribute(CXXRD); + // If there's a #pragma GCC visibility in scope, set the visibility of this // record. AddPushedVisibilityAttribute(New); diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -4535,12 +4535,15 @@ static void handleLifetimeCategoryAttr(Sema &S, Decl *D, const ParsedAttr &AL) { TypeSourceInfo *DerefTypeLoc = nullptr; - QualType ParmType = S.GetTypeFromParser(AL.getTypeArg(), &DerefTypeLoc); - assert(DerefTypeLoc && "no type source info for attribute argument"); + QualType ParmType; + if (AL.hasParsedType()) { + ParmType = S.GetTypeFromParser(AL.getTypeArg(), &DerefTypeLoc); - if (ParmType->isVoidType()) { - S.Diag(AL.getLoc(), diag::err_attribute_invalid_argument) << "'void'" << AL; - return; + if (ParmType->isVoidType()) { + S.Diag(AL.getLoc(), diag::err_attribute_invalid_argument) + << "'void'" << AL; + return; + } } // To check if earlier decl attributes do not conflict the newly parsed ones @@ -4550,7 +4553,10 @@ if (checkAttrMutualExclusion(S, D, AL)) return; if (const auto *OAttr = D->getAttr()) { - if (OAttr->getDerefType().getTypePtr() != ParmType.getTypePtr()) { + 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); @@ -4563,7 +4569,10 @@ if (checkAttrMutualExclusion(S, D, AL)) return; if (const auto *PAttr = D->getAttr()) { - if (PAttr->getDerefType().getTypePtr() != ParmType.getTypePtr()) { + 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); diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp --- a/clang/lib/Sema/SemaExprCXX.cpp +++ b/clang/lib/Sema/SemaExprCXX.cpp @@ -4381,6 +4381,9 @@ // This type trait always returns false, checking the type is moot. case UTT_IsInterfaceClass: + + case UTT_IsGslOwner: + case UTT_IsGslPointer: return true; // C++14 [meta.unary.prop]: @@ -4875,6 +4878,16 @@ return !T->isIncompleteType(); case UTT_HasUniqueObjectRepresentations: return C.hasUniqueObjectRepresentations(T); + case UTT_IsGslOwner: + if (const CXXRecordDecl *RD = T->getAsCXXRecordDecl()) + return RD->getCanonicalDecl()->hasAttr(); + return false; + case UTT_IsGslPointer: + if (T->hasPointerRepresentation()) + return true; + if (const CXXRecordDecl *RD = T->getAsCXXRecordDecl()) + return RD->getCanonicalDecl()->hasAttr(); + return false; } } diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp --- a/clang/lib/Sema/SemaTemplate.cpp +++ b/clang/lib/Sema/SemaTemplate.cpp @@ -1686,6 +1686,7 @@ mergeDeclAttributes(NewClass, PrevClassTemplate->getTemplatedDecl()); AddPushedVisibilityAttribute(NewClass); + addDefaultGslOwnerPointerAttribute(NewClass); if (TUK != TUK_Friend) { // Per C++ [basic.scope.temp]p2, skip the template parameter scopes. diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp --- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp @@ -699,6 +699,7 @@ } SemaRef.InstantiateAttrs(TemplateArgs, D, Typedef); + SemaRef.addDefaultGslPointerAttribute(Typedef); Typedef->setAccess(D->getAccess()); diff --git a/clang/test/AST/ast-dump-attr.cpp b/clang/test/AST/ast-dump-attr.cpp --- a/clang/test/AST/ast-dump-attr.cpp +++ b/clang/test/AST/ast-dump-attr.cpp @@ -218,6 +218,14 @@ 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 diff --git a/clang/test/SemaCXX/attr-gsl-owner-pointer.cpp b/clang/test/SemaCXX/attr-gsl-owner-pointer.cpp --- a/clang/test/SemaCXX/attr-gsl-owner-pointer.cpp +++ b/clang/test/SemaCXX/attr-gsl-owner-pointer.cpp @@ -10,14 +10,15 @@ struct S { }; +static_assert(!__is_gsl_owner(S), ""); +static_assert(!__is_gsl_pointer(S), ""); S [[gsl::Owner]] Instance; // expected-error@-1 {{'Owner' attribute cannot be applied to types}} class [[gsl::Owner]] OwnerMissingParameter{}; -// expected-error@-1 {{'Owner' attribute takes one argument}} + class [[gsl::Pointer]] PointerMissingParameter{}; -// expected-error@-1 {{'Pointer' attribute takes one argument}} class [[gsl::Owner(7)]] OwnerDerefNoType{}; // expected-error@-1 {{expected a type}} expected-error@-1 {{expected ')'}} @@ -32,8 +33,12 @@ // expected-note@-2 {{conflicting attribute is here}} class [[gsl::Owner(int)]] [[gsl::Owner(int)]] DuplicateOwner{}; +static_assert(__is_gsl_owner(DuplicateOwner), ""); +static_assert(!__is_gsl_pointer(DuplicateOwner), ""); class [[gsl::Pointer(int)]] [[gsl::Pointer(int)]] DuplicatePointer{}; +static_assert(!__is_gsl_owner(DuplicatePointer), ""); +static_assert(__is_gsl_pointer(DuplicatePointer), ""); class [[gsl::Owner(void)]] OwnerVoidDerefType{}; // expected-error@-1 {{'void' is an invalid argument to attribute 'Owner'}} @@ -41,7 +46,12 @@ // expected-error@-1 {{'void' is an invalid argument to attribute 'Pointer'}} class [[gsl::Owner(int)]] AnOwner{}; +static_assert(__is_gsl_owner(AnOwner), ""); +static_assert(!__is_gsl_pointer(AnOwner), ""); + class [[gsl::Pointer(S)]] APointer{}; +static_assert(!__is_gsl_owner(APointer), ""); +static_assert(__is_gsl_pointer(APointer), ""); class AddOwnerLater {}; class [[gsl::Owner(int)]] AddOwnerLater; @@ -58,3 +68,76 @@ class [[gsl::Owner(int)]] AddTheSameLater{}; class [[gsl::Owner(int)]] AddTheSameLater; + +class [[gsl::Owner()]] OwnerWithEmptyParameterList{}; +static_assert(__is_gsl_owner(OwnerWithEmptyParameterList), ""); + +class [[gsl::Pointer()]] PointerWithEmptyParameterList{}; +static_assert(__is_gsl_pointer(PointerWithEmptyParameterList), ""); + +class [[gsl::Owner()]] [[gsl::Owner(int)]] WithAndWithoutParameter{}; +// expected-error@-1 {{'Owner' and 'Owner' attributes are not compatible}} +// expected-note@-2 {{conflicting attribute is here}} + +static_assert(!__is_gsl_pointer(int), ""); +static_assert(__is_gsl_pointer(int &), ""); +static_assert(__is_gsl_pointer(int *), ""); + +// Test builtin annotation for std types. +namespace std { +// Complete class +class any { +}; +static_assert(__is_gsl_owner(any), ""); + +// Complete template +template +class vector { +public: + class iterator {}; +}; +static_assert(__is_gsl_owner(vector), ""); +static_assert(__is_gsl_pointer(vector::iterator), ""); + +template +class set_iterator {}; + +template +class set { +public: + using iterator = set_iterator; +}; +static_assert(__is_gsl_pointer(set::iterator), ""); + +template +class map_iterator {}; + +template +class map { +public: + typedef map_iterator iterator; +}; +static_assert(__is_gsl_pointer(map::iterator), ""); + +// list has an implicit gsl::Owner attribute, +// but explicit attributes take precedence. +template +class [[gsl::Pointer]] list{}; +static_assert(!__is_gsl_owner(list), ""); +static_assert(__is_gsl_pointer(list), ""); + +// Forward declared template +template < + class CharT, + class Traits> +class basic_regex; +static_assert(__is_gsl_owner(basic_regex), ""); + +template +class reference_wrapper; +static_assert(__is_gsl_pointer(reference_wrapper), ""); + +class thread; +static_assert(!__is_gsl_pointer(thread), ""); +static_assert(!__is_gsl_owner(thread), ""); +} // namespace std diff --git a/clang/utils/TableGen/ClangAttrEmitter.cpp b/clang/utils/TableGen/ClangAttrEmitter.cpp --- a/clang/utils/TableGen/ClangAttrEmitter.cpp +++ b/clang/utils/TableGen/ClangAttrEmitter.cpp @@ -303,6 +303,8 @@ 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 @@ << " 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") {