[clang] [Clang] Add the `annotate_decl` attribute. (PR #122431)
via cfe-commits
cfe-commits at lists.llvm.org
Fri Jan 10 00:27:02 PST 2025
https://github.com/martinboehme created https://github.com/llvm/llvm-project/pull/122431
This attribute is intended as a general-purpose mechanism to add annotations for
use by Clang-based tools. People have been using the existing `annotate`
attribute for this purpose, but this can interfere with optimizations -- see
https://github.com/llvm/llvm-project/issues/55190.
For details, see this RFC:
(TODO: Insert link)
>From e9caf1f8f7b92bb75ca9f04f6604cbd9f827fcf1 Mon Sep 17 00:00:00 2001
From: Martin Braenne <mboehme at google.com>
Date: Fri, 10 Jan 2025 08:21:20 +0000
Subject: [PATCH] [Clang] Add the `annotate_decl` attribute.
This attribute is intended as a general-purpose mechanism to add annotations for
use by Clang-based tools. People have been using the existing `annotate`
attribute for this purpose, but this can interfere with optimizations -- see
https://github.com/llvm/llvm-project/issues/55190.
For details, see this RFC:
(TODO: Insert link)
---
clang/include/clang/Basic/Attr.td | 8 ++++
clang/include/clang/Basic/AttrDocs.td | 35 +++++++++++++++-
clang/lib/Sema/SemaDeclAttr.cpp | 29 +++++++++++++
clang/test/SemaCXX/annotate-decl.cpp | 48 ++++++++++++++++++++++
clang/unittests/AST/AttrTest.cpp | 59 +++++++++++++++++++++++++--
5 files changed, 174 insertions(+), 5 deletions(-)
create mode 100644 clang/test/SemaCXX/annotate-decl.cpp
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 12faf06597008e..1c5a13bf8db65b 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -962,6 +962,14 @@ def Annotate : InheritableParamOrStmtAttr {
let Documentation = [Undocumented];
}
+def AnnotateDecl : Attr {
+ let Spellings = [CXX11<"clang", "annotate_decl">, C23<"clang", "annotate_decl">];
+ let Args = [StringArgument<"Annotation">, VariadicExprArgument<"Args">];
+ let HasCustomParsing = 1;
+ let AcceptsExprPack = 1;
+ let Documentation = [AnnotateDeclDocs];
+}
+
def AnnotateType : TypeAttr {
let Spellings = [CXX11<"clang", "annotate_type">, C23<"clang", "annotate_type">];
let Args = [StringArgument<"Annotation">, VariadicExprArgument<"Args">];
diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index b8d702e41aa0bb..dcbbb06c7d922e 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -8146,14 +8146,45 @@ and copied back to the argument after the callee returns.
}];
}
+def AnnotateDeclDocs : Documentation {
+ let Category = DocCatType;
+ let Heading = "annotate_decl";
+ let Content = [{
+This attribute is used to add annotations to declarations, 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). See also the
+`annotate_type` attribute, which serves the same purpose, but for types.
+
+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++
+
+ [[clang::annotate_decl("category", "foo", 1)]] int i;
+
+The attribute does not have any effect on the semantics of the declaration or
+the code that should be produced for it.
+
+There is no requirement that different declarations of an entity (i.e.
+redeclarations) use the same `annotate_decl` annotations, so that the annotation
+can be used as flexibly as possible. If a Clang-based tool wants to impose
+additional rules for specific annotations, it needs to check for and enforce
+these itself.
+ }];
+}
+
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.
+Clang-Tidy checks or out-of-tree Clang-based tools). See also the
+`annotate_decl` 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
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index bb4d33560b93b8..748b03f4a24c1c 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -4113,6 +4113,32 @@ static void handleAnnotateAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
}
}
+static void handleAnnotateDeclAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
+ if (AL.getNumArgs() < 1) {
+ S.Diag(AL.getLoc(), diag::err_attribute_too_few_arguments) << AL << 1;
+ return;
+ }
+
+ // Make sure that there is a string literal as the annotation's first
+ // argument.
+ StringRef Str;
+ if (!S.checkStringLiteralArgumentAttr(AL, 0, Str))
+ return;
+
+ llvm::SmallVector<Expr *, 4> Args;
+ Args.reserve(AL.getNumArgs() - 1);
+ for (unsigned Idx = 1; Idx < AL.getNumArgs(); Idx++) {
+ assert(!AL.isArgIdent(Idx));
+ Args.push_back(AL.getArgAsExpr(Idx));
+ }
+ if (!S.ConstantFoldAttrArgs(AL, Args))
+ return;
+ if (auto *AnnotateDeclAttr = AnnotateDeclAttr::Create(
+ S.Context, Str, Args.data(), Args.size(), AL)) {
+ D->addAttr(AnnotateDeclAttr);
+ }
+}
+
static void handleAlignValueAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
S.AddAlignValueAttr(D, AL, AL.getArgAsExpr(0));
}
@@ -6724,6 +6750,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
case ParsedAttr::AT_Annotate:
handleAnnotateAttr(S, D, AL);
break;
+ case ParsedAttr::AT_AnnotateDecl:
+ handleAnnotateDeclAttr(S, D, AL);
+ break;
case ParsedAttr::AT_Availability:
handleAvailabilityAttr(S, D, AL);
break;
diff --git a/clang/test/SemaCXX/annotate-decl.cpp b/clang/test/SemaCXX/annotate-decl.cpp
new file mode 100644
index 00000000000000..d37e671a6422fa
--- /dev/null
+++ b/clang/test/SemaCXX/annotate-decl.cpp
@@ -0,0 +1,48 @@
+// RUN: %clang_cc1 %s -std=c++17 -fsyntax-only -fcxx-exceptions -verify
+
+[[clang::annotate_decl("foo")]] int global1;
+int [[clang::annotate_decl("foo")]] global2; // expected-error {{'annotate_decl' attribute cannot be applied to types}}
+
+// Redeclarations.
+
+// A declaration that originally didn't have an `annotate_decl` attribute
+// can be redeclared with one.
+extern int global3;
+[[clang::annotate_decl("foo")]] extern int global3;
+
+// A declaration that originally had an `annotate_decl` attribute can be
+// redeclared without one.
+[[clang::annotate_decl("foo")]] extern int global4;
+extern int global4;
+
+// A declaration that originally had an `annotate_decl` attribute with one
+// set of arguments can be redeclared with another set of arguments.
+[[clang::annotate_decl("foo", "arg1", 1)]] extern int global5;
+[[clang::annotate_decl("foo", "arg2", 2)]] extern int global5;
+
+// Different types of declarations.
+[[clang::annotate_decl("foo")]] void f();
+namespace [[clang::annotate_decl("foo")]] my_namespace {}
+struct [[clang::annotate_decl("foo")]] S;
+struct [[clang::annotate_decl("foo")]] S{
+ [[clang::annotate_decl("foo")]] int member;
+};
+template <class T>
+[[clang::annotate_decl("foo")]] T var_template;
+extern "C" [[clang::annotate_decl("foo")]] int extern_c_func();
+[[clang::annotate_decl("foo")]] extern "C" int extern_c_func(); // expected-error {{an attribute list cannot appear here}}
+
+// Declarations within functions.
+void f2() {
+ [[clang::annotate_decl("foo")]] int i;
+ [[clang::annotate_decl("foo")]] i = 1; // expected-error {{'annotate_decl' attribute cannot be applied to a statement}}
+
+ // Test various cases where a declaration can appear inside a statement.
+ for ([[clang::annotate_decl("foo")]] int i = 0; i < 42; ++i) {}
+ for (; [[clang::annotate_decl("foo")]] bool b = false;) {}
+ while ([[clang::annotate_decl("foo")]] bool b = false) {}
+ if ([[clang::annotate_decl("foo")]] bool b = false) {}
+ try {
+ } catch ([[clang::annotate_decl("foo")]] int i) {
+ }
+}
diff --git a/clang/unittests/AST/AttrTest.cpp b/clang/unittests/AST/AttrTest.cpp
index 46c3f5729021ec..e7bcce51f6c2c2 100644
--- a/clang/unittests/AST/AttrTest.cpp
+++ b/clang/unittests/AST/AttrTest.cpp
@@ -45,10 +45,11 @@ const FunctionDecl *getFunctionNode(ASTUnit *AST, const std::string &Name) {
return Result[0].getNodeAs<FunctionDecl>("fn");
}
-const VarDecl *getVariableNode(ASTUnit *AST, const std::string &Name) {
+const VarDecl *getVariableNode(ASTUnit *AST, const std::string &Name,
+ int NumRedeclsExpected = 1) {
auto Result = match(varDecl(hasName(Name)).bind("var"), AST->getASTContext());
- EXPECT_EQ(Result.size(), 1u);
- return Result[0].getNodeAs<VarDecl>("var");
+ EXPECT_EQ(Result.size(), NumRedeclsExpected);
+ return Result[0].getNodeAs<VarDecl>("var")->getFirstDecl();
}
template <class ModifiedTypeLoc>
@@ -69,6 +70,20 @@ void AssertAnnotatedAs(TypeLoc TL, llvm::StringRef annotation,
}
}
+void AssertAnnotatedAs(const Decl *D, llvm::StringRef annotation,
+ const AnnotateDeclAttr **AnnotateOut = nullptr) {
+ for (const Attr *A : D->attrs()) {
+ if (const auto *Annotate = dyn_cast<AnnotateDeclAttr>(A)) {
+ EXPECT_EQ(Annotate->getAnnotation(), annotation);
+ if (AnnotateOut) {
+ *AnnotateOut = Annotate;
+ }
+ return;
+ }
+ }
+ FAIL() << "No AnnotateDeclAttr found";
+}
+
TEST(Attr, AnnotateType) {
// Test that the AnnotateType attribute shows up in the right places and that
@@ -168,6 +183,44 @@ TEST(Attr, AnnotateType) {
}
}
+TEST(Attr, AnnotateDecl) {
+
+ // Test that the AnnotateDecl attribute shows up in the right places and that
+ // it stores its arguments correctly.
+
+ auto AST = buildASTFromCode(R"cpp(
+ [[clang::annotate_decl("foo", "arg1", 2)]] int global;
+
+ [[clang::annotate_decl("first_decl")]] extern int global2;
+ [[clang::annotate_decl("second_decl")]] extern int global2;
+ )cpp");
+
+ {
+ const VarDecl *Global = getVariableNode(AST.get(), "global");
+
+ const AnnotateDeclAttr *Annotate;
+ AssertAnnotatedAs(Global, "foo", &Annotate);
+
+ EXPECT_EQ(Annotate->args_size(), 2u);
+ 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(2u)).bind("int"))),
+ *Annotate->args_begin()[1], AST->getASTContext())
+ .size(),
+ 1u);
+ }
+
+ {
+ const VarDecl *Global2 = getVariableNode(AST.get(), "global2", 2);
+
+ AssertAnnotatedAs(Global2, "first_decl");
+ AssertAnnotatedAs(Global2->getMostRecentDecl(), "second_decl");
+ }
+}
+
TEST(Attr, RegularKeywordAttribute) {
auto AST = clang::tooling::buildASTFromCode("");
auto &Ctx = AST->getASTContext();
More information about the cfe-commits
mailing list