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 <mboe...@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(); _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits