Skip to content

Commit 665da18

Browse files
committed
[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
1 parent 3151fb5 commit 665da18

File tree

14 files changed

+461
-48
lines changed

14 files changed

+461
-48
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,9 @@ Attribute Changes in Clang
362362
- When the ``weak`` attribute is applied to a const qualified variable clang no longer
363363
tells the backend it is allowed to optimize based on initializer value.
364364

365+
- Added the ``clang::annotate_type`` attribute, which can be used to add
366+
annotations to types (see documentation for details).
367+
365368
Windows Support
366369
---------------
367370

clang/include/clang/Basic/Attr.td

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -804,6 +804,14 @@ def Annotate : InheritableParamAttr {
804804
let Documentation = [Undocumented];
805805
}
806806

807+
def AnnotateType : TypeAttr {
808+
let Spellings = [CXX11<"clang", "annotate_type">, C2x<"clang", "annotate_type">];
809+
let Args = [StringArgument<"Annotation">, VariadicExprArgument<"Args">];
810+
let HasCustomParsing = 1;
811+
let AcceptsExprPack = 1;
812+
let Documentation = [AnnotateTypeDocs];
813+
}
814+
807815
def ARMInterrupt : InheritableAttr, TargetSpecificAttr<TargetARM> {
808816
// NOTE: If you add any additional spellings, M68kInterrupt's,
809817
// MSP430Interrupt's, MipsInterrupt's and AnyX86Interrupt's spellings

clang/include/clang/Basic/AttrDocs.td

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6487,6 +6487,40 @@ The full documentation is available here: https://docs.microsoft.com/en-us/windo
64876487
}];
64886488
}
64896489

6490+
def AnnotateTypeDocs : Documentation {
6491+
let Category = DocCatType;
6492+
let Heading = "annotate_type";
6493+
let Content = [{
6494+
This attribute is used to add annotations to types, typically for use by static
6495+
analysis tools that are not integrated into the core Clang compiler (e.g.,
6496+
Clang-Tidy checks or out-of-tree Clang-based tools). It is a counterpart to the
6497+
`annotate` attribute, which serves the same purpose, but for declarations.
6498+
6499+
The attribute takes a mandatory string literal argument specifying the
6500+
annotation category and an arbitrary number of optional arguments that provide
6501+
additional information specific to the annotation category. The optional
6502+
arguments must be constant expressions of arbitrary type.
6503+
6504+
For example:
6505+
6506+
.. code-block:: c++
6507+
6508+
int* [[clang::annotate("category1", "foo", 1)]] f(int[[clang::annotate("category2")]] *);
6509+
6510+
The attribute does not have any effect on the semantics of the type system,
6511+
neither type checking rules, nor runtime semantics. In particular:
6512+
6513+
- ``std::is_same<T, T [[clang::annotate_type("foo")]]`` is true for all types
6514+
``T``.
6515+
6516+
- It is not permissible for overloaded functions or template specializations
6517+
to differ merely by an ``annotate_type`` attribute.
6518+
6519+
- The presence of an ``annotate_type`` attribute will not affect name
6520+
mangling.
6521+
}];
6522+
}
6523+
64906524
def WeakDocs : Documentation {
64916525
let Category = DocCatDecl;
64926526
let Content = [{

clang/include/clang/Sema/Sema.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10389,6 +10389,13 @@ class Sema final {
1038910389
void AddAnnotationAttr(Decl *D, const AttributeCommonInfo &CI,
1039010390
StringRef Annot, MutableArrayRef<Expr *> Args);
1039110391

10392+
/// ConstantFoldAttrArgs - Folds attribute arguments into ConstantExprs
10393+
/// (unless they are value dependent or type dependent). Returns false
10394+
/// and emits a diagnostic if one or more of the arguments could not be
10395+
/// folded into a constant.
10396+
bool ConstantFoldAttrArgs(const AttributeCommonInfo &CI,
10397+
MutableArrayRef<Expr *> Args);
10398+
1039210399
/// AddLaunchBoundsAttr - Adds a launch_bounds attribute to a particular
1039310400
/// declaration.
1039410401
void AddLaunchBoundsAttr(Decl *D, const AttributeCommonInfo &CI,

clang/lib/AST/TypePrinter.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1706,6 +1706,15 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
17061706
if (T->getAttrKind() == attr::AddressSpace)
17071707
return;
17081708

1709+
if (T->getAttrKind() == attr::AnnotateType) {
1710+
// FIXME: Print the attribute arguments once we have a way to retrieve these
1711+
// here. For the meantime, we just print `[[clang::annotate_type(...)]]`
1712+
// without the arguments so that we know at least that we had _some_
1713+
// annotation on the type.
1714+
OS << " [[clang::annotate_type(...)]]";
1715+
return;
1716+
}
1717+
17091718
OS << " __attribute__((";
17101719
switch (T->getAttrKind()) {
17111720
#define TYPE_ATTR(NAME)
@@ -1743,6 +1752,7 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
17431752
case attr::UPtr:
17441753
case attr::AddressSpace:
17451754
case attr::CmseNSCall:
1755+
case attr::AnnotateType:
17461756
llvm_unreachable("This attribute should have been handled already");
17471757

17481758
case attr::NSReturnsRetained:

clang/lib/Parse/ParseDecl.cpp

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3176,10 +3176,23 @@ void Parser::ParseDeclarationSpecifiers(DeclSpec &DS,
31763176
if (!AttrsLastTime)
31773177
ProhibitAttributes(attrs);
31783178
else {
3179-
// Reject C++11 attributes that appertain to decl specifiers as
3180-
// we don't support any C++11 attributes that appertain to decl
3181-
// specifiers. This also conforms to what g++ 4.8 is doing.
3182-
ProhibitCXX11Attributes(attrs, diag::err_attribute_not_type_attr);
3179+
// Reject most C++11 / C2x attributes on the decl-specifier-seq, but
3180+
// allow `annotate_type` as a special case.
3181+
// FIXME: We should more generally allow type attributes to be placed
3182+
// on the decl-specifier-seq; https://reviews.llvm.org/D126061 will
3183+
// make this change.
3184+
for (const ParsedAttr &PA : attrs) {
3185+
if (!PA.isCXX11Attribute() && !PA.isC2xAttribute())
3186+
continue;
3187+
if (PA.getKind() == ParsedAttr::UnknownAttribute)
3188+
// We will warn about the unknown attribute elsewhere (in
3189+
// SemaDeclAttr.cpp)
3190+
continue;
3191+
if (PA.getKind() == ParsedAttr::AT_AnnotateType)
3192+
continue;
3193+
Diag(PA.getLoc(), diag::err_attribute_not_type_attr) << PA;
3194+
PA.setInvalid();
3195+
}
31833196

31843197
DS.takeAttributesFrom(attrs);
31853198
}

clang/lib/Sema/SemaAttr.cpp

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,54 @@ void Sema::ActOnPragmaPack(SourceLocation PragmaLoc, PragmaMsStackAction Action,
384384
AlignPackStack.Act(PragmaLoc, Action, SlotLabel, Info);
385385
}
386386

387+
bool Sema::ConstantFoldAttrArgs(const AttributeCommonInfo &CI,
388+
MutableArrayRef<Expr *> Args) {
389+
llvm::SmallVector<PartialDiagnosticAt, 8> Notes;
390+
for (unsigned Idx = 0; Idx < Args.size(); Idx++) {
391+
Expr *&E = Args.begin()[Idx];
392+
assert(E && "error are handled before");
393+
if (E->isValueDependent() || E->isTypeDependent())
394+
continue;
395+
396+
// FIXME: Use DefaultFunctionArrayLValueConversion() in place of the logic
397+
// that adds implicit casts here.
398+
if (E->getType()->isArrayType())
399+
E = ImpCastExprToType(E, Context.getPointerType(E->getType()),
400+
clang::CK_ArrayToPointerDecay)
401+
.get();
402+
if (E->getType()->isFunctionType())
403+
E = ImplicitCastExpr::Create(Context,
404+
Context.getPointerType(E->getType()),
405+
clang::CK_FunctionToPointerDecay, E, nullptr,
406+
VK_PRValue, FPOptionsOverride());
407+
if (E->isLValue())
408+
E = ImplicitCastExpr::Create(Context, E->getType().getNonReferenceType(),
409+
clang::CK_LValueToRValue, E, nullptr,
410+
VK_PRValue, FPOptionsOverride());
411+
412+
Expr::EvalResult Eval;
413+
Notes.clear();
414+
Eval.Diag = &Notes;
415+
416+
bool Result = E->EvaluateAsConstantExpr(Eval, Context);
417+
418+
/// Result means the expression can be folded to a constant.
419+
/// Note.empty() means the expression is a valid constant expression in the
420+
/// current language mode.
421+
if (!Result || !Notes.empty()) {
422+
Diag(E->getBeginLoc(), diag::err_attribute_argument_n_type)
423+
<< CI << (Idx + 1) << AANT_ArgumentConstantExpr;
424+
for (auto &Note : Notes)
425+
Diag(Note.first, Note.second);
426+
return false;
427+
}
428+
assert(Eval.Val.hasValue());
429+
E = ConstantExpr::Create(Context, E, Eval.Val);
430+
}
431+
432+
return true;
433+
}
434+
387435
void Sema::DiagnoseNonDefaultPragmaAlignPack(PragmaAlignPackDiagnoseKind Kind,
388436
SourceLocation IncludeLoc) {
389437
if (Kind == PragmaAlignPackDiagnoseKind::NonDefaultStateAtInclude) {

clang/lib/Sema/SemaDeclAttr.cpp

Lines changed: 4 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -4147,48 +4147,10 @@ static void handleTransparentUnionAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
41474147
void Sema::AddAnnotationAttr(Decl *D, const AttributeCommonInfo &CI,
41484148
StringRef Str, MutableArrayRef<Expr *> Args) {
41494149
auto *Attr = AnnotateAttr::Create(Context, Str, Args.data(), Args.size(), CI);
4150-
llvm::SmallVector<PartialDiagnosticAt, 8> Notes;
4151-
for (unsigned Idx = 0; Idx < Attr->args_size(); Idx++) {
4152-
Expr *&E = Attr->args_begin()[Idx];
4153-
assert(E && "error are handled before");
4154-
if (E->isValueDependent() || E->isTypeDependent())
4155-
continue;
4156-
4157-
if (E->getType()->isArrayType())
4158-
E = ImpCastExprToType(E, Context.getPointerType(E->getType()),
4159-
clang::CK_ArrayToPointerDecay)
4160-
.get();
4161-
if (E->getType()->isFunctionType())
4162-
E = ImplicitCastExpr::Create(Context,
4163-
Context.getPointerType(E->getType()),
4164-
clang::CK_FunctionToPointerDecay, E, nullptr,
4165-
VK_PRValue, FPOptionsOverride());
4166-
if (E->isLValue())
4167-
E = ImplicitCastExpr::Create(Context, E->getType().getNonReferenceType(),
4168-
clang::CK_LValueToRValue, E, nullptr,
4169-
VK_PRValue, FPOptionsOverride());
4170-
4171-
Expr::EvalResult Eval;
4172-
Notes.clear();
4173-
Eval.Diag = &Notes;
4174-
4175-
bool Result =
4176-
E->EvaluateAsConstantExpr(Eval, Context);
4177-
4178-
/// Result means the expression can be folded to a constant.
4179-
/// Note.empty() means the expression is a valid constant expression in the
4180-
/// current language mode.
4181-
if (!Result || !Notes.empty()) {
4182-
Diag(E->getBeginLoc(), diag::err_attribute_argument_n_type)
4183-
<< CI << (Idx + 1) << AANT_ArgumentConstantExpr;
4184-
for (auto &Note : Notes)
4185-
Diag(Note.first, Note.second);
4186-
return;
4187-
}
4188-
assert(Eval.Val.hasValue());
4189-
E = ConstantExpr::Create(Context, E, Eval.Val);
4190-
}
4191-
D->addAttr(Attr);
4150+
if (ConstantFoldAttrArgs(
4151+
CI, MutableArrayRef<Expr *>(Attr->args_begin(), Attr->args_end()))) {
4152+
D->addAttr(Attr);
4153+
}
41924154
}
41934155

41944156
static void handleAnnotateAttr(Sema &S, Decl *D, const ParsedAttr &AL) {

clang/lib/Sema/SemaType.cpp

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8144,6 +8144,34 @@ static void HandleMatrixTypeAttr(QualType &CurType, const ParsedAttr &Attr,
81448144
CurType = T;
81458145
}
81468146

8147+
static void HandleAnnotateTypeAttr(TypeProcessingState &State,
8148+
QualType &CurType, const ParsedAttr &PA) {
8149+
Sema &S = State.getSema();
8150+
8151+
if (PA.getNumArgs() < 1) {
8152+
S.Diag(PA.getLoc(), diag::err_attribute_too_few_arguments) << PA << 1;
8153+
return;
8154+
}
8155+
8156+
// Make sure that there is a string literal as the annotation's first
8157+
// argument.
8158+
StringRef Str;
8159+
if (!S.checkStringLiteralArgumentAttr(PA, 0, Str))
8160+
return;
8161+
8162+
llvm::SmallVector<Expr *, 4> Args;
8163+
Args.reserve(PA.getNumArgs() - 1);
8164+
for (unsigned Idx = 1; Idx < PA.getNumArgs(); Idx++) {
8165+
assert(!PA.isArgIdent(Idx));
8166+
Args.push_back(PA.getArgAsExpr(Idx));
8167+
}
8168+
if (!S.ConstantFoldAttrArgs(PA, Args))
8169+
return;
8170+
auto *AnnotateTypeAttr =
8171+
AnnotateTypeAttr::Create(S.Context, Str, Args.data(), Args.size(), PA);
8172+
CurType = State.getAttributedType(AnnotateTypeAttr, CurType, CurType);
8173+
}
8174+
81478175
static void HandleLifetimeBoundAttr(TypeProcessingState &State,
81488176
QualType &CurType,
81498177
ParsedAttr &Attr) {
@@ -8206,10 +8234,11 @@ static void processTypeAttrs(TypeProcessingState &state, QualType &type,
82068234
if (!IsTypeAttr)
82078235
continue;
82088236
}
8209-
} else if (TAL != TAL_DeclChunk && !isAddressSpaceKind(attr)) {
8237+
} else if (TAL != TAL_DeclChunk && !isAddressSpaceKind(attr) &&
8238+
attr.getKind() != ParsedAttr::AT_AnnotateType) {
82108239
// Otherwise, only consider type processing for a C++11 attribute if
82118240
// it's actually been applied to a type.
8212-
// We also allow C++11 address_space and
8241+
// We also allow C++11 address_space and annotate_type and
82138242
// OpenCL language address space attributes to pass through.
82148243
continue;
82158244
}
@@ -8407,6 +8436,11 @@ static void processTypeAttrs(TypeProcessingState &state, QualType &type,
84078436
attr.setUsedAsTypeAttr();
84088437
break;
84098438
}
8439+
case ParsedAttr::AT_AnnotateType: {
8440+
HandleAnnotateTypeAttr(state, type, attr);
8441+
attr.setUsedAsTypeAttr();
8442+
break;
8443+
}
84108444
}
84118445

84128446
// Handle attributes that are defined in a macro. We do not want this to be

clang/test/AST/attr-annotate-type.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// RUN: %clang_cc1 %s -ast-dump -fdouble-square-bracket-attributes | FileCheck %s
2+
3+
// Verify that we print the [[clang::annotate_type]] attribute.
4+
// FIXME: The arguments are currently not printed -- see also comments in
5+
// TypePrinter.cpp.
6+
7+
// Need to escape the `[[` as a regex to avoid it being interpreted as a
8+
// substitution block.
9+
// CHECK: VarDecl {{.*}} x1 'int {{\[\[}}clang::annotate_type(...){{]]}}':'int'
10+
int [[clang::annotate_type("bar")]] x1;
11+
// CHECK: VarDecl {{.*}} x2 'int * {{\[\[}}clang::annotate_type(...){{]]}}':'int *'
12+
int *[[clang::annotate_type("bar")]] x2;

0 commit comments

Comments
 (0)