mgehre created this revision.
mgehre added reviewers: gribozavr, xazax.hun.
Herald added a subscriber: rnkovacs.
Herald added a project: clang.

Make the DerefType, i.e. the argument of gsl::Owner/gsl::Pointer optional for 
now.

The DerefType is used when infering lifetime annotations of functions, e.g.
how a return value can relate to the arguments. Our initial checks should
work without that kind of inference and instead use explicit annotations,
so we don't need the DerefType right now.
And having it optional makes the implicit/default annotations for std:: types
easier to implement. I think the DerefType is also optional in the current MSVC
implementation.

This seems to be the first attribute that has an optional Type argument,
so I needed to fix two thinks in tablegen:

- Don't crash AST dump when Owner/Pointer has no Type argument
- Don't crash when pretty-printing an Attribute with optional type argument

Add __is_gsl_owner/__is_gsl_pointer builtins. They are very useful to test
if the annotations have been attached correctly.

Hard code gsl::Owner/gsl::Pointer for std types. The paper mentions
some types explicitly. Generally, all containers and their iterators are
covered. For iterators, we cover both the case that they are defined
as an nested class or as an typedef/using. I have started to test this
implementation against some real standard library implementations, namely
libc++ 7.1.0, libc++ 8.0.1rc2, libstdc++ 4.6.4, libstdc++ 4.8.5,
libstdc++ 4.9.4, libstdc++ 5.4.0, libstdc++ 6.5.0, libstdc++ 7.3.0,
libstdc++ 8.3.0 and libstdc++ 9.1.0.

The tests are currently here

  https://github.com/mgehre/llvm-project/blob/lifetime-ci/lifetime-attr-test.sh
  https://github.com/mgehre/llvm-project/blob/lifetime-ci/lifetime-attr-test.cpp

I think due to their dependency on a standard library, they are not a good fit
for clang/test/. Where else could I put them?


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D64448

Files:
  clang/docs/LanguageExtensions.rst
  clang/include/clang/Basic/Attr.td
  clang/include/clang/Basic/AttrDocs.td
  clang/include/clang/Basic/TokenKinds.def
  clang/include/clang/Basic/TypeTraits.h
  clang/include/clang/Sema/Sema.h
  clang/lib/Sema/SemaAttr.cpp
  clang/lib/Sema/SemaDecl.cpp
  clang/lib/Sema/SemaDeclAttr.cpp
  clang/lib/Sema/SemaExprCXX.cpp
  clang/lib/Sema/SemaTemplate.cpp
  clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
  clang/test/AST/ast-dump-attr.cpp
  clang/test/SemaCXX/attr-gsl-owner-pointer.cpp
  clang/utils/TableGen/ClangAttrEmitter.cpp

Index: clang/utils/TableGen/ClangAttrEmitter.cpp
===================================================================
--- clang/utils/TableGen/ClangAttrEmitter.cpp
+++ 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") {
Index: clang/test/SemaCXX/attr-gsl-owner-pointer.cpp
===================================================================
--- clang/test/SemaCXX/attr-gsl-owner-pointer.cpp
+++ 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 <typename T>
+class vector {
+public:
+  class iterator {};
+};
+static_assert(__is_gsl_owner(vector<int>), "");
+static_assert(__is_gsl_pointer(vector<int>::iterator), "");
+
+template <typename T>
+class set_iterator {};
+
+template <typename T>
+class set {
+public:
+  using iterator = set_iterator<T>;
+};
+static_assert(__is_gsl_pointer(set<int>::iterator), "");
+
+template <typename T>
+class map_iterator {};
+
+template <typename T>
+class map {
+public:
+  typedef map_iterator<T> iterator;
+};
+static_assert(__is_gsl_pointer(map<int>::iterator), "");
+
+// list has an implicit gsl::Owner attribute,
+// but explicit attributes take precedence.
+template <typename T>
+class [[gsl::Pointer]] list{};
+static_assert(!__is_gsl_owner(list<int>), "");
+static_assert(__is_gsl_pointer(list<int>), "");
+
+// Forward declared template
+template <
+    class CharT,
+    class Traits>
+class basic_regex;
+static_assert(__is_gsl_owner(basic_regex<char, void>), "");
+
+template <class T>
+class reference_wrapper;
+static_assert(__is_gsl_pointer(reference_wrapper<char>), "");
+
+class thread;
+static_assert(!__is_gsl_pointer(thread), "");
+static_assert(!__is_gsl_owner(thread), "");
+} // namespace std
Index: clang/test/AST/ast-dump-attr.cpp
===================================================================
--- clang/test/AST/ast-dump-attr.cpp
+++ 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
Index: clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
===================================================================
--- clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -699,6 +699,7 @@
   }
 
   SemaRef.InstantiateAttrs(TemplateArgs, D, Typedef);
+  SemaRef.addDefaultGslPointerAttribute(Typedef);
 
   Typedef->setAccess(D->getAccess());
 
Index: clang/lib/Sema/SemaTemplate.cpp
===================================================================
--- clang/lib/Sema/SemaTemplate.cpp
+++ 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.
Index: clang/lib/Sema/SemaExprCXX.cpp
===================================================================
--- clang/lib/Sema/SemaExprCXX.cpp
+++ 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<OwnerAttr>();
+    return false;
+  case UTT_IsGslPointer:
+    if (T->hasPointerRepresentation())
+      return true;
+    if (const CXXRecordDecl *RD = T->getAsCXXRecordDecl())
+      return RD->getCanonicalDecl()->hasAttr<PointerAttr>();
+    return false;
   }
 }
 
Index: clang/lib/Sema/SemaDeclAttr.cpp
===================================================================
--- clang/lib/Sema/SemaDeclAttr.cpp
+++ 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<PointerAttr>(S, D, AL))
       return;
     if (const auto *OAttr = D->getAttr<OwnerAttr>()) {
-      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<OwnerAttr>(S, D, AL))
       return;
     if (const auto *PAttr = D->getAttr<PointerAttr>()) {
-      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);
Index: clang/lib/Sema/SemaDecl.cpp
===================================================================
--- clang/lib/Sema/SemaDecl.cpp
+++ clang/lib/Sema/SemaDecl.cpp
@@ -15075,6 +15075,9 @@
   if (PrevDecl)
     mergeDeclAttributes(New, PrevDecl);
 
+  if (auto *CXXRD = dyn_cast<CXXRecordDecl>(New))
+    addDefaultGslOwnerPointerAttribute(CXXRD);
+
   // If there's a #pragma GCC visibility in scope, set the visibility of this
   // record.
   AddPushedVisibilityAttribute(New);
Index: clang/lib/Sema/SemaAttr.cpp
===================================================================
--- clang/lib/Sema/SemaAttr.cpp
+++ clang/lib/Sema/SemaAttr.cpp
@@ -85,6 +85,114 @@
         MSVtorDispAttr::CreateImplicit(Context, VtorDispStack.CurrentValue));
 }
 
+template <typename Attribute>
+static void addGslOwnerPointerAttributeIfNotExisting(ASTContext &Context,
+                                                     CXXRecordDecl *Record) {
+  CXXRecordDecl *Canonical = Record->getCanonicalDecl();
+  if (Canonical->hasAttr<OwnerAttr>() || Canonical->hasAttr<PointerAttr>())
+    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<CXXRecordDecl>(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<PointerAttr>(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<OwnerAttr>() || Canonical->hasAttr<PointerAttr>())
+      return;
+
+    if (StdOwners.count(Record->getName()))
+      addGslOwnerPointerAttributeIfNotExisting<OwnerAttr>(Context, Record);
+    else if (StdPointers.count(Record->getName()))
+      addGslOwnerPointerAttributeIfNotExisting<PointerAttr>(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;
Index: clang/include/clang/Sema/Sema.h
===================================================================
--- clang/include/clang/Sema/Sema.h
+++ 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
Index: clang/include/clang/Basic/TypeTraits.h
===================================================================
--- clang/include/clang/Basic/TypeTraits.h
+++ 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,
Index: clang/include/clang/Basic/TokenKinds.def
===================================================================
--- clang/include/clang/Basic/TokenKinds.def
+++ 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
Index: clang/include/clang/Basic/AttrDocs.td
===================================================================
--- clang/include/clang/Basic/AttrDocs.td
+++ 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.
 
Index: clang/include/clang/Basic/Attr.td
===================================================================
--- clang/include/clang/Basic/Attr.td
+++ 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];
 }
 
Index: clang/docs/LanguageExtensions.rst
===================================================================
--- clang/docs/LanguageExtensions.rst
+++ 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
 <https://clang-analyzer.llvm.org/annotations.html>`_.
 
+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
 ===============================
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to