[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