[clang] 665da18 - [Clang] Add the `annotate_type` attribute

Martin Boehme via cfe-commits cfe-commits at lists.llvm.org
Wed Jun 15 00:47:44 PDT 2022


Author: Martin Boehme
Date: 2022-06-15T09:47:28+02:00
New Revision: 665da187ccf338bad1560e8a960e8feaebb5c9d9

URL: https://github.com/llvm/llvm-project/commit/665da187ccf338bad1560e8a960e8feaebb5c9d9
DIFF: https://github.com/llvm/llvm-project/commit/665da187ccf338bad1560e8a960e8feaebb5c9d9.diff

LOG: [Clang] Add the `annotate_type` attribute

This is an analog to the `annotate` attribute but for types. The intent is to allow adding arbitrary annotations to types for use in static analysis tools.

For details, see this RFC:

https://discourse.llvm.org/t/rfc-new-attribute-annotate-type-iteration-2/61378

Reviewed By: aaron.ballman

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

Added: 
    clang/test/AST/attr-annotate-type.c
    clang/test/CodeGenCXX/annotate-type.cpp
    clang/test/Sema/annotate-type.c
    clang/test/SemaCXX/annotate-type.cpp

Modified: 
    clang/docs/ReleaseNotes.rst
    clang/include/clang/Basic/Attr.td
    clang/include/clang/Basic/AttrDocs.td
    clang/include/clang/Sema/Sema.h
    clang/lib/AST/TypePrinter.cpp
    clang/lib/Parse/ParseDecl.cpp
    clang/lib/Sema/SemaAttr.cpp
    clang/lib/Sema/SemaDeclAttr.cpp
    clang/lib/Sema/SemaType.cpp
    clang/unittests/AST/AttrTest.cpp

Removed: 
    


################################################################################
diff  --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 7d84933b43bca..19ce6cb4d0d45 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -362,6 +362,9 @@ Attribute Changes in Clang
 - When the ``weak`` attribute is applied to a const qualified variable clang no longer
   tells the backend it is allowed to optimize based on initializer value.
 
+- Added the ``clang::annotate_type`` attribute, which can be used to add
+  annotations to types (see documentation for details).
+
 Windows Support
 ---------------
 

diff  --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 16384969f68e4..5f77073413fbf 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -804,6 +804,14 @@ def Annotate : InheritableParamAttr {
   let Documentation = [Undocumented];
 }
 
+def AnnotateType : TypeAttr {
+  let Spellings = [CXX11<"clang", "annotate_type">, C2x<"clang", "annotate_type">];
+  let Args = [StringArgument<"Annotation">, VariadicExprArgument<"Args">];
+  let HasCustomParsing = 1;
+  let AcceptsExprPack = 1;
+  let Documentation = [AnnotateTypeDocs];
+}
+
 def ARMInterrupt : InheritableAttr, TargetSpecificAttr<TargetARM> {
   // NOTE: If you add any additional spellings, M68kInterrupt's,
   // MSP430Interrupt's, MipsInterrupt's and AnyX86Interrupt's spellings

diff  --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index 59bbc80708ed5..6562e363a1ca7 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -6487,6 +6487,40 @@ The full documentation is available here: https://docs.microsoft.com/en-us/windo
   }];
 }
 
+def AnnotateTypeDocs : Documentation {
+  let Category = DocCatType;
+  let Heading = "annotate_type";
+  let Content = [{
+This attribute is used to add annotations to types, typically for use by static
+analysis tools that are not integrated into the core Clang compiler (e.g.,
+Clang-Tidy checks or out-of-tree Clang-based tools). It is a counterpart to the
+`annotate` attribute, which serves the same purpose, but for declarations.
+
+The attribute takes a mandatory string literal argument specifying the
+annotation category and an arbitrary number of optional arguments that provide
+additional information specific to the annotation category. The optional
+arguments must be constant expressions of arbitrary type.
+
+For example:
+
+.. code-block:: c++
+
+  int* [[clang::annotate("category1", "foo", 1)]] f(int[[clang::annotate("category2")]] *);
+
+The attribute does not have any effect on the semantics of the type system,
+neither type checking rules, nor runtime semantics. In particular:
+
+- ``std::is_same<T, T [[clang::annotate_type("foo")]]`` is true for all types
+  ``T``.
+
+- It is not permissible for overloaded functions or template specializations
+  to 
diff er merely by an ``annotate_type`` attribute.
+
+- The presence of an ``annotate_type`` attribute will not affect name
+  mangling.
+  }];
+}
+
 def WeakDocs : Documentation {
   let Category = DocCatDecl;
   let Content = [{

diff  --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 8df7e185cc55a..aeb0a43e872e4 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -10389,6 +10389,13 @@ class Sema final {
   void AddAnnotationAttr(Decl *D, const AttributeCommonInfo &CI,
                          StringRef Annot, MutableArrayRef<Expr *> Args);
 
+  /// ConstantFoldAttrArgs - Folds attribute arguments into ConstantExprs
+  /// (unless they are value dependent or type dependent). Returns false
+  /// and emits a diagnostic if one or more of the arguments could not be
+  /// folded into a constant.
+  bool ConstantFoldAttrArgs(const AttributeCommonInfo &CI,
+                            MutableArrayRef<Expr *> Args);
+
   /// AddLaunchBoundsAttr - Adds a launch_bounds attribute to a particular
   /// declaration.
   void AddLaunchBoundsAttr(Decl *D, const AttributeCommonInfo &CI,

diff  --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp
index 38b5a5ab222f5..6b13d38060370 100644
--- a/clang/lib/AST/TypePrinter.cpp
+++ b/clang/lib/AST/TypePrinter.cpp
@@ -1706,6 +1706,15 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
   if (T->getAttrKind() == attr::AddressSpace)
     return;
 
+  if (T->getAttrKind() == attr::AnnotateType) {
+    // FIXME: Print the attribute arguments once we have a way to retrieve these
+    // here. For the meantime, we just print `[[clang::annotate_type(...)]]`
+    // without the arguments so that we know at least that we had _some_
+    // annotation on the type.
+    OS << " [[clang::annotate_type(...)]]";
+    return;
+  }
+
   OS << " __attribute__((";
   switch (T->getAttrKind()) {
 #define TYPE_ATTR(NAME)
@@ -1743,6 +1752,7 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
   case attr::UPtr:
   case attr::AddressSpace:
   case attr::CmseNSCall:
+  case attr::AnnotateType:
     llvm_unreachable("This attribute should have been handled already");
 
   case attr::NSReturnsRetained:

diff  --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index a1a768bb2c2e4..12af519c0919f 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -3176,10 +3176,23 @@ void Parser::ParseDeclarationSpecifiers(DeclSpec &DS,
       if (!AttrsLastTime)
         ProhibitAttributes(attrs);
       else {
-        // Reject C++11 attributes that appertain to decl specifiers as
-        // we don't support any C++11 attributes that appertain to decl
-        // specifiers. This also conforms to what g++ 4.8 is doing.
-        ProhibitCXX11Attributes(attrs, diag::err_attribute_not_type_attr);
+        // Reject most C++11 / C2x attributes on the decl-specifier-seq, but
+        // allow `annotate_type` as a special case.
+        // FIXME: We should more generally allow type attributes to be placed
+        // on the decl-specifier-seq; https://reviews.llvm.org/D126061 will
+        // make this change.
+        for (const ParsedAttr &PA : attrs) {
+          if (!PA.isCXX11Attribute() && !PA.isC2xAttribute())
+            continue;
+          if (PA.getKind() == ParsedAttr::UnknownAttribute)
+            // We will warn about the unknown attribute elsewhere (in
+            // SemaDeclAttr.cpp)
+            continue;
+          if (PA.getKind() == ParsedAttr::AT_AnnotateType)
+            continue;
+          Diag(PA.getLoc(), diag::err_attribute_not_type_attr) << PA;
+          PA.setInvalid();
+        }
 
         DS.takeAttributesFrom(attrs);
       }

diff  --git a/clang/lib/Sema/SemaAttr.cpp b/clang/lib/Sema/SemaAttr.cpp
index 4560bb060a4aa..840aeb2a440eb 100644
--- a/clang/lib/Sema/SemaAttr.cpp
+++ b/clang/lib/Sema/SemaAttr.cpp
@@ -384,6 +384,54 @@ void Sema::ActOnPragmaPack(SourceLocation PragmaLoc, PragmaMsStackAction Action,
   AlignPackStack.Act(PragmaLoc, Action, SlotLabel, Info);
 }
 
+bool Sema::ConstantFoldAttrArgs(const AttributeCommonInfo &CI,
+                                MutableArrayRef<Expr *> Args) {
+  llvm::SmallVector<PartialDiagnosticAt, 8> Notes;
+  for (unsigned Idx = 0; Idx < Args.size(); Idx++) {
+    Expr *&E = Args.begin()[Idx];
+    assert(E && "error are handled before");
+    if (E->isValueDependent() || E->isTypeDependent())
+      continue;
+
+    // FIXME: Use DefaultFunctionArrayLValueConversion() in place of the logic
+    // that adds implicit casts here.
+    if (E->getType()->isArrayType())
+      E = ImpCastExprToType(E, Context.getPointerType(E->getType()),
+                            clang::CK_ArrayToPointerDecay)
+              .get();
+    if (E->getType()->isFunctionType())
+      E = ImplicitCastExpr::Create(Context,
+                                   Context.getPointerType(E->getType()),
+                                   clang::CK_FunctionToPointerDecay, E, nullptr,
+                                   VK_PRValue, FPOptionsOverride());
+    if (E->isLValue())
+      E = ImplicitCastExpr::Create(Context, E->getType().getNonReferenceType(),
+                                   clang::CK_LValueToRValue, E, nullptr,
+                                   VK_PRValue, FPOptionsOverride());
+
+    Expr::EvalResult Eval;
+    Notes.clear();
+    Eval.Diag = &Notes;
+
+    bool Result = E->EvaluateAsConstantExpr(Eval, Context);
+
+    /// Result means the expression can be folded to a constant.
+    /// Note.empty() means the expression is a valid constant expression in the
+    /// current language mode.
+    if (!Result || !Notes.empty()) {
+      Diag(E->getBeginLoc(), diag::err_attribute_argument_n_type)
+          << CI << (Idx + 1) << AANT_ArgumentConstantExpr;
+      for (auto &Note : Notes)
+        Diag(Note.first, Note.second);
+      return false;
+    }
+    assert(Eval.Val.hasValue());
+    E = ConstantExpr::Create(Context, E, Eval.Val);
+  }
+
+  return true;
+}
+
 void Sema::DiagnoseNonDefaultPragmaAlignPack(PragmaAlignPackDiagnoseKind Kind,
                                              SourceLocation IncludeLoc) {
   if (Kind == PragmaAlignPackDiagnoseKind::NonDefaultStateAtInclude) {

diff  --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index ef01bef2a9dff..2e62cc0c242ae 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -4147,48 +4147,10 @@ static void handleTransparentUnionAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
 void Sema::AddAnnotationAttr(Decl *D, const AttributeCommonInfo &CI,
                              StringRef Str, MutableArrayRef<Expr *> Args) {
   auto *Attr = AnnotateAttr::Create(Context, Str, Args.data(), Args.size(), CI);
-  llvm::SmallVector<PartialDiagnosticAt, 8> Notes;
-  for (unsigned Idx = 0; Idx < Attr->args_size(); Idx++) {
-    Expr *&E = Attr->args_begin()[Idx];
-    assert(E && "error are handled before");
-    if (E->isValueDependent() || E->isTypeDependent())
-      continue;
-
-    if (E->getType()->isArrayType())
-      E = ImpCastExprToType(E, Context.getPointerType(E->getType()),
-                            clang::CK_ArrayToPointerDecay)
-              .get();
-    if (E->getType()->isFunctionType())
-      E = ImplicitCastExpr::Create(Context,
-                                   Context.getPointerType(E->getType()),
-                                   clang::CK_FunctionToPointerDecay, E, nullptr,
-                                   VK_PRValue, FPOptionsOverride());
-    if (E->isLValue())
-      E = ImplicitCastExpr::Create(Context, E->getType().getNonReferenceType(),
-                                   clang::CK_LValueToRValue, E, nullptr,
-                                   VK_PRValue, FPOptionsOverride());
-
-    Expr::EvalResult Eval;
-    Notes.clear();
-    Eval.Diag = &Notes;
-
-    bool Result =
-        E->EvaluateAsConstantExpr(Eval, Context);
-
-    /// Result means the expression can be folded to a constant.
-    /// Note.empty() means the expression is a valid constant expression in the
-    /// current language mode.
-    if (!Result || !Notes.empty()) {
-      Diag(E->getBeginLoc(), diag::err_attribute_argument_n_type)
-          << CI << (Idx + 1) << AANT_ArgumentConstantExpr;
-      for (auto &Note : Notes)
-        Diag(Note.first, Note.second);
-      return;
-    }
-    assert(Eval.Val.hasValue());
-    E = ConstantExpr::Create(Context, E, Eval.Val);
-  }
-  D->addAttr(Attr);
+  if (ConstantFoldAttrArgs(
+          CI, MutableArrayRef<Expr *>(Attr->args_begin(), Attr->args_end()))) {
+    D->addAttr(Attr);
+  }
 }
 
 static void handleAnnotateAttr(Sema &S, Decl *D, const ParsedAttr &AL) {

diff  --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index c4b86ee2c5ee8..e487c892e569d 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -8144,6 +8144,34 @@ static void HandleMatrixTypeAttr(QualType &CurType, const ParsedAttr &Attr,
     CurType = T;
 }
 
+static void HandleAnnotateTypeAttr(TypeProcessingState &State,
+                                   QualType &CurType, const ParsedAttr &PA) {
+  Sema &S = State.getSema();
+
+  if (PA.getNumArgs() < 1) {
+    S.Diag(PA.getLoc(), diag::err_attribute_too_few_arguments) << PA << 1;
+    return;
+  }
+
+  // Make sure that there is a string literal as the annotation's first
+  // argument.
+  StringRef Str;
+  if (!S.checkStringLiteralArgumentAttr(PA, 0, Str))
+    return;
+
+  llvm::SmallVector<Expr *, 4> Args;
+  Args.reserve(PA.getNumArgs() - 1);
+  for (unsigned Idx = 1; Idx < PA.getNumArgs(); Idx++) {
+    assert(!PA.isArgIdent(Idx));
+    Args.push_back(PA.getArgAsExpr(Idx));
+  }
+  if (!S.ConstantFoldAttrArgs(PA, Args))
+    return;
+  auto *AnnotateTypeAttr =
+      AnnotateTypeAttr::Create(S.Context, Str, Args.data(), Args.size(), PA);
+  CurType = State.getAttributedType(AnnotateTypeAttr, CurType, CurType);
+}
+
 static void HandleLifetimeBoundAttr(TypeProcessingState &State,
                                     QualType &CurType,
                                     ParsedAttr &Attr) {
@@ -8206,10 +8234,11 @@ static void processTypeAttrs(TypeProcessingState &state, QualType &type,
           if (!IsTypeAttr)
             continue;
         }
-      } else if (TAL != TAL_DeclChunk && !isAddressSpaceKind(attr)) {
+      } else if (TAL != TAL_DeclChunk && !isAddressSpaceKind(attr) &&
+                 attr.getKind() != ParsedAttr::AT_AnnotateType) {
         // Otherwise, only consider type processing for a C++11 attribute if
         // it's actually been applied to a type.
-        // We also allow C++11 address_space and
+        // We also allow C++11 address_space and annotate_type and
         // OpenCL language address space attributes to pass through.
         continue;
       }
@@ -8407,6 +8436,11 @@ static void processTypeAttrs(TypeProcessingState &state, QualType &type,
       attr.setUsedAsTypeAttr();
       break;
     }
+    case ParsedAttr::AT_AnnotateType: {
+      HandleAnnotateTypeAttr(state, type, attr);
+      attr.setUsedAsTypeAttr();
+      break;
+    }
     }
 
     // Handle attributes that are defined in a macro. We do not want this to be

diff  --git a/clang/test/AST/attr-annotate-type.c b/clang/test/AST/attr-annotate-type.c
new file mode 100644
index 0000000000000..115fd5912d75b
--- /dev/null
+++ b/clang/test/AST/attr-annotate-type.c
@@ -0,0 +1,12 @@
+// RUN: %clang_cc1 %s -ast-dump -fdouble-square-bracket-attributes | FileCheck %s
+
+// Verify that we print the [[clang::annotate_type]] attribute.
+// FIXME: The arguments are currently not printed -- see also comments in
+// TypePrinter.cpp.
+
+// Need to escape the `[[` as a regex to avoid it being interpreted as a
+// substitution block.
+// CHECK: VarDecl {{.*}} x1 'int {{\[\[}}clang::annotate_type(...){{]]}}':'int'
+int [[clang::annotate_type("bar")]] x1;
+// CHECK: VarDecl {{.*}} x2 'int * {{\[\[}}clang::annotate_type(...){{]]}}':'int *'
+int *[[clang::annotate_type("bar")]] x2;

diff  --git a/clang/test/CodeGenCXX/annotate-type.cpp b/clang/test/CodeGenCXX/annotate-type.cpp
new file mode 100644
index 0000000000000..456888b1ecd97
--- /dev/null
+++ b/clang/test/CodeGenCXX/annotate-type.cpp
@@ -0,0 +1,17 @@
+// RUN: %clang_cc1 -triple %itanium_abi_triple -emit-llvm-only %s -emit-llvm -o - | FileCheck %s
+
+// Test that `annotate_type` does not affect mangled names.
+
+int *[[clang::annotate_type("foo")]] f(int *[[clang::annotate_type("foo")]],
+                                       int [[clang::annotate_type("foo")]]) {
+  return nullptr;
+}
+// CHECK: @_Z1fPii
+
+template <class T> struct S {};
+
+S<int *[[clang::annotate_type("foo")]]>
+g(S<int *[[clang::annotate_type("foo")]]>) {
+  return {};
+}
+// CHECK: @_Z1g1SIPiE

diff  --git a/clang/test/Sema/annotate-type.c b/clang/test/Sema/annotate-type.c
new file mode 100644
index 0000000000000..bbf26dd1793bb
--- /dev/null
+++ b/clang/test/Sema/annotate-type.c
@@ -0,0 +1,48 @@
+// RUN: %clang_cc1 %s -fsyntax-only -fdouble-square-bracket-attributes -verify
+
+const char *some_function();
+
+void foo(float *[[clang::annotate_type("foo")]] a) {
+  int [[clang::annotate_type("bar")]] x1;
+  int *[[clang::annotate_type("bar")]] x2;
+  int *[[clang::annotate_type("bar", 1)]] x3;
+  int *[[clang::annotate_type("bar", 1 + 2)]] x4;
+  struct {} [[clang::annotate_type("foo")]] x5;
+  int [[clang::annotate_type("int")]] *[[clang::annotate_type("ptr")]] array[10] [[clang::annotate_type("arr")]];
+
+  typedef int [[clang::annotate_type("bar")]] my_typedef;
+
+  // GNU spelling is not supported
+  int __attribute__((annotate_type("bar"))) y1;  // expected-warning {{unknown attribute 'annotate_type' ignored}}
+  int *__attribute__((annotate_type("bar"))) y2; // expected-warning {{unknown attribute 'annotate_type' ignored}}
+
+  // Various error cases
+  // FIXME: We would want to prohibit the attribute on the following two lines.
+  // However, Clang currently generally doesn't prohibit type-only C++11
+  // attributes on declarations. This should be fixed more generally.
+  [[clang::annotate_type("bar")]] int *z1;
+  int *z2 [[clang::annotate_type("bar")]];
+  [[clang::annotate_type("bar")]]; // expected-error {{'annotate_type' attribute cannot be applied to a statement}}
+  int *[[clang::annotate_type(1)]] z3; // expected-error {{'annotate_type' attribute requires a string}}
+  int *[[clang::annotate_type()]] z4; // expected-error {{'annotate_type' attribute takes at least 1 argument}}
+  int *[[clang::annotate_type]] z5; // expected-error {{'annotate_type' attribute takes at least 1 argument}}
+  int *[[clang::annotate_type(some_function())]] z6; // expected-error {{'annotate_type' attribute requires a string}}
+  int *[[clang::annotate_type("bar", some_function())]] z7; // expected-error {{'annotate_type' attribute requires parameter 1 to be a constant expression}} expected-note{{subexpression not valid in a constant expression}}
+  int *[[clang::annotate_type("bar", z7)]] z8; // expected-error {{'annotate_type' attribute requires parameter 1 to be a constant expression}} expected-note{{subexpression not valid in a constant expression}}
+  int *[[clang::annotate_type("bar", int)]] z9; // expected-error {{expected expression}}
+}
+// More error cases: Prohibit adding the attribute to declarations.
+// Different declarations hit 
diff erent code paths, so they need separate tests.
+// FIXME: Clang currently generally doesn't prohibit type-only C++11
+// attributes on declarations.
+[[clang::annotate_type("bar")]] int *global;
+void annotated_function([[clang::annotate_type("bar")]] int);
+void g([[clang::annotate_type("bar")]] int);
+struct [[clang::annotate_type("foo")]] S;
+struct [[clang::annotate_type("foo")]] S{
+  [[clang::annotate_type("foo")]] int member;
+  [[clang::annotate_type("foo")]] union {
+    int i;
+    float f;
+  };
+};

diff  --git a/clang/test/SemaCXX/annotate-type.cpp b/clang/test/SemaCXX/annotate-type.cpp
new file mode 100644
index 0000000000000..c76b1767e7d3c
--- /dev/null
+++ b/clang/test/SemaCXX/annotate-type.cpp
@@ -0,0 +1,70 @@
+// RUN: %clang_cc1 %s -std=c++17 -fsyntax-only -fcxx-exceptions -verify
+
+struct S1 {
+  void f() [[clang::annotate_type("foo")]];
+  // FIXME: We would want to prohibit the attribute in the following location.
+  // However, Clang currently generally doesn't prohibit type-only C++11
+  // attributes on declarations. This should be fixed more generally.
+  [[clang::annotate_type("foo")]] void g();
+};
+
+template <typename T1, typename T2> struct is_same {
+  static constexpr bool value = false;
+};
+
+template <typename T1> struct is_same<T1, T1> {
+  static constexpr bool value = true;
+};
+
+static_assert(is_same<int, int [[clang::annotate_type("foo")]]>::value);
+static_assert(is_same<int [[clang::annotate_type("foo")]],
+                      int [[clang::annotate_type("bar")]]>::value);
+static_assert(is_same<int *, int *[[clang::annotate_type("foo")]]>::value);
+
+// Cannot overload on types that only 
diff er by `annotate_type` attribute.
+void f(int) {} // expected-note {{previous definition is here}}
+void f(int [[clang::annotate_type("foo")]]) {} // expected-error {{redefinition of 'f'}}
+
+// Cannot specialize on types that only 
diff er by `annotate_type` attribute.
+template <class T> struct S2 {};
+
+template <> struct S2<int> {}; // expected-note {{previous definition is here}}
+
+template <>
+struct S2<int [[clang::annotate_type("foo")]]> {}; // expected-error {{redefinition of 'S2<int>'}}
+
+// Test that the attribute supports parameter pack expansion.
+template <int... Is> void variadic_func_template() {
+  int [[clang::annotate_type("foo", Is...)]] val;
+}
+int f2() { variadic_func_template<1, 2, 3>(); }
+
+// Make sure we correctly diagnose wrong number of arguments for
+// [[clang::annotate_type]] inside a template argument.
+template <typename Ty> void func_template();
+void f3() {
+  func_template<int [[clang::annotate_type()]]>(); // expected-error {{'annotate_type' attribute takes at least 1 argument}}
+}
+
+// More error cases: Prohibit adding the attribute to declarations.
+// Different declarations hit 
diff erent code paths, so they need separate tests.
+// FIXME: Clang currently generally doesn't prohibit type-only C++11
+// attributes on declarations.
+namespace [[clang::annotate_type("foo")]] my_namespace {}
+struct [[clang::annotate_type("foo")]] S3;
+struct [[clang::annotate_type("foo")]] S3{
+  [[clang::annotate_type("foo")]] int member;
+};
+void f4() {
+  for ([[clang::annotate_type("foo")]] int i = 0; i < 42; ++i) {}
+  for (; [[clang::annotate_type("foo")]] bool b = false;) {}
+  while ([[clang::annotate_type("foo")]] bool b = false) {}
+  if ([[clang::annotate_type("foo")]] bool b = false) {}
+  try {
+  } catch ([[clang::annotate_type("foo")]] int i) {
+  }
+}
+template <class T>
+[[clang::annotate_type("foo")]] T var_template;
+[[clang::annotate_type("foo")]] extern "C" int extern_c_func();
+extern "C" [[clang::annotate_type("foo")]] int extern_c_func();

diff  --git a/clang/unittests/AST/AttrTest.cpp b/clang/unittests/AST/AttrTest.cpp
index 38e723f3ee090..d9ea1ae061f0e 100644
--- a/clang/unittests/AST/AttrTest.cpp
+++ b/clang/unittests/AST/AttrTest.cpp
@@ -7,7 +7,10 @@
 //===----------------------------------------------------------------------===//
 
 #include "clang/AST/Attr.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
 #include "clang/Basic/AttrKinds.h"
+#include "clang/Tooling/Tooling.h"
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 
@@ -15,10 +18,154 @@ using namespace clang;
 
 namespace {
 
+using clang::ast_matchers::constantExpr;
+using clang::ast_matchers::equals;
+using clang::ast_matchers::functionDecl;
+using clang::ast_matchers::has;
+using clang::ast_matchers::hasDescendant;
+using clang::ast_matchers::hasName;
+using clang::ast_matchers::integerLiteral;
+using clang::ast_matchers::match;
+using clang::ast_matchers::selectFirst;
+using clang::ast_matchers::stringLiteral;
+using clang::ast_matchers::varDecl;
+using clang::tooling::buildASTFromCode;
+using clang::tooling::buildASTFromCodeWithArgs;
+
 TEST(Attr, Doc) {
   EXPECT_THAT(Attr::getDocumentation(attr::Used).str(),
               testing::HasSubstr("The compiler must emit the definition even "
                                  "if it appears to be unused"));
 }
 
+const FunctionDecl *getFunctionNode(ASTUnit *AST, const std::string &Name) {
+  auto Result =
+      match(functionDecl(hasName(Name)).bind("fn"), AST->getASTContext());
+  EXPECT_EQ(Result.size(), 1u);
+  return Result[0].getNodeAs<FunctionDecl>("fn");
+}
+
+const VarDecl *getVariableNode(ASTUnit *AST, const std::string &Name) {
+  auto Result = match(varDecl(hasName(Name)).bind("var"), AST->getASTContext());
+  EXPECT_EQ(Result.size(), 1u);
+  return Result[0].getNodeAs<VarDecl>("var");
+}
+
+template <class ModifiedTypeLoc>
+void AssertAnnotatedAs(TypeLoc TL, llvm::StringRef annotation,
+                       ModifiedTypeLoc &ModifiedTL,
+                       const AnnotateTypeAttr **AnnotateOut = nullptr) {
+  const auto AttributedTL = TL.getAs<AttributedTypeLoc>();
+  ASSERT_FALSE(AttributedTL.isNull());
+  ModifiedTL = AttributedTL.getModifiedLoc().getAs<ModifiedTypeLoc>();
+  ASSERT_TRUE(ModifiedTL);
+
+  ASSERT_NE(AttributedTL.getAttr(), nullptr);
+  const auto *Annotate = dyn_cast<AnnotateTypeAttr>(AttributedTL.getAttr());
+  ASSERT_NE(Annotate, nullptr);
+  EXPECT_EQ(Annotate->getAnnotation(), annotation);
+  if (AnnotateOut) {
+    *AnnotateOut = Annotate;
+  }
+}
+
+TEST(Attr, AnnotateType) {
+
+  // Test that the AnnotateType attribute shows up in the right places and that
+  // it stores its arguments correctly.
+
+  auto AST = buildASTFromCode(R"cpp(
+    void f(int* [[clang::annotate_type("foo", "arg1", 2)]] *,
+           int [[clang::annotate_type("bar")]]);
+
+    int [[clang::annotate_type("int")]] * [[clang::annotate_type("ptr")]]
+      array[10] [[clang::annotate_type("arr")]];
+
+    void (* [[clang::annotate_type("funcptr")]] fp)(void);
+
+    struct S { int mem; };
+    int [[clang::annotate_type("int")]]
+    S::* [[clang::annotate_type("ptr_to_mem")]] ptr_to_member = &S::mem;
+  )cpp");
+
+  {
+    const FunctionDecl *Func = getFunctionNode(AST.get(), "f");
+
+    // First parameter.
+    const auto PointerTL = Func->getParamDecl(0)
+                               ->getTypeSourceInfo()
+                               ->getTypeLoc()
+                               .getAs<PointerTypeLoc>();
+    ASSERT_FALSE(PointerTL.isNull());
+    PointerTypeLoc PointerPointerTL;
+    const AnnotateTypeAttr *Annotate;
+    AssertAnnotatedAs(PointerTL.getPointeeLoc(), "foo", PointerPointerTL,
+                      &Annotate);
+
+    EXPECT_EQ(Annotate->args_size(), 2);
+    const auto *StringLit = selectFirst<StringLiteral>(
+        "str", match(constantExpr(hasDescendant(stringLiteral().bind("str"))),
+                     *Annotate->args_begin()[0], AST->getASTContext()));
+    ASSERT_NE(StringLit, nullptr);
+    EXPECT_EQ(StringLit->getString(), "arg1");
+    EXPECT_EQ(match(constantExpr(has(integerLiteral(equals(2)).bind("int"))),
+                    *Annotate->args_begin()[1], AST->getASTContext())
+                  .size(),
+              1);
+
+    // Second parameter.
+    BuiltinTypeLoc IntTL;
+    AssertAnnotatedAs(Func->getParamDecl(1)->getTypeSourceInfo()->getTypeLoc(),
+                      "bar", IntTL);
+    EXPECT_EQ(IntTL.getType(), AST->getASTContext().IntTy);
+  }
+
+  {
+    const VarDecl *Var = getVariableNode(AST.get(), "array");
+
+    ArrayTypeLoc ArrayTL;
+    AssertAnnotatedAs(Var->getTypeSourceInfo()->getTypeLoc(), "arr", ArrayTL);
+    PointerTypeLoc PointerTL;
+    AssertAnnotatedAs(ArrayTL.getElementLoc(), "ptr", PointerTL);
+    BuiltinTypeLoc IntTL;
+    AssertAnnotatedAs(PointerTL.getPointeeLoc(), "int", IntTL);
+    EXPECT_EQ(IntTL.getType(), AST->getASTContext().IntTy);
+  }
+
+  {
+    const VarDecl *Var = getVariableNode(AST.get(), "fp");
+
+    PointerTypeLoc PointerTL;
+    AssertAnnotatedAs(Var->getTypeSourceInfo()->getTypeLoc(), "funcptr",
+                      PointerTL);
+    ASSERT_TRUE(
+        PointerTL.getPointeeLoc().IgnoreParens().getAs<FunctionTypeLoc>());
+  }
+
+  {
+    const VarDecl *Var = getVariableNode(AST.get(), "ptr_to_member");
+
+    MemberPointerTypeLoc MemberPointerTL;
+    AssertAnnotatedAs(Var->getTypeSourceInfo()->getTypeLoc(), "ptr_to_mem",
+                      MemberPointerTL);
+    BuiltinTypeLoc IntTL;
+    AssertAnnotatedAs(MemberPointerTL.getPointeeLoc(), "int", IntTL);
+    EXPECT_EQ(IntTL.getType(), AST->getASTContext().IntTy);
+  }
+
+  // Test type annotation on an `__auto_type` type in C mode.
+  AST = buildASTFromCodeWithArgs(R"c(
+    __auto_type [[clang::annotate_type("auto")]] auto_var = 1;
+  )c",
+                                 {"-fdouble-square-bracket-attributes"},
+                                 "input.c");
+
+  {
+    const VarDecl *Var = getVariableNode(AST.get(), "auto_var");
+
+    AutoTypeLoc AutoTL;
+    AssertAnnotatedAs(Var->getTypeSourceInfo()->getTypeLoc(), "auto", AutoTL);
+  }
+}
+
 } // namespace


        


More information about the cfe-commits mailing list