[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