[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
This commit is contained in:
parent
3151fb5ef7
commit
665da187cc
@ -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
|
||||
---------------
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 differ 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 = [{
|
||||
|
||||
@ -10389,6 +10389,13 @@ public:
|
||||
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,
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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);
|
||||
if (ConstantFoldAttrArgs(
|
||||
CI, MutableArrayRef<Expr *>(Attr->args_begin(), Attr->args_end()))) {
|
||||
D->addAttr(Attr);
|
||||
}
|
||||
D->addAttr(Attr);
|
||||
}
|
||||
|
||||
static void handleAnnotateAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
|
||||
|
||||
@ -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
|
||||
|
||||
12
clang/test/AST/attr-annotate-type.c
Normal file
12
clang/test/AST/attr-annotate-type.c
Normal file
@ -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;
|
||||
17
clang/test/CodeGenCXX/annotate-type.cpp
Normal file
17
clang/test/CodeGenCXX/annotate-type.cpp
Normal file
@ -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
|
||||
48
clang/test/Sema/annotate-type.c
Normal file
48
clang/test/Sema/annotate-type.c
Normal file
@ -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 different 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;
|
||||
};
|
||||
};
|
||||
70
clang/test/SemaCXX/annotate-type.cpp
Normal file
70
clang/test/SemaCXX/annotate-type.cpp
Normal file
@ -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 differ 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 differ 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 different 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();
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user