diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -794,6 +794,12 @@ let Documentation = [Undocumented]; } +def AnnotateType : TypeAttr { + let Spellings = [CXX11<"clang", "annotate_type">, C2x<"clang", "annotate_type">]; + let Args = [StringArgument<"Annotation">, VariadicExprArgument<"Args">]; + let Documentation = [AnnotateTypeDocs]; +} + def ARMInterrupt : InheritableAttr, TargetSpecificAttr { // NOTE: If you add any additional spellings, M68kInterrupt's, // MSP430Interrupt's, MipsInterrupt's and AnyX86Interrupt's spellings diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -6371,3 +6371,26 @@ .. _Return-Oriented Programming: https://en.wikipedia.org/wiki/Return-oriented_programming }]; } + +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"]] *); + + }]; +} diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -10309,6 +10309,13 @@ void AddAnnotationAttr(Decl *D, const AttributeCommonInfo &CI, StringRef Annot, MutableArrayRef 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 Args); + /// AddLaunchBoundsAttr - Adds a launch_bounds attribute to a particular /// declaration. void AddLaunchBoundsAttr(Decl *D, const AttributeCommonInfo &CI, diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp --- a/clang/lib/AST/TypePrinter.cpp +++ b/clang/lib/AST/TypePrinter.cpp @@ -1681,6 +1681,11 @@ if (T->getAttrKind() == attr::AddressSpace) return; + // We have no way to meaningfully print annotate_type attributes because that + // would require retrieving the attribute arguments, which we don't have here. + if (T->getAttrKind() == attr::AnnotateType) + return; + OS << " __attribute__(("; switch (T->getAttrKind()) { #define TYPE_ATTR(NAME) @@ -1715,6 +1720,7 @@ case attr::UPtr: case attr::AddressSpace: case attr::CmseNSCall: + case attr::AnnotateType: llvm_unreachable("This attribute should have been handled already"); case attr::NSReturnsRetained: diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp --- a/clang/lib/Parse/ParseDecl.cpp +++ b/clang/lib/Parse/ParseDecl.cpp @@ -3177,10 +3177,24 @@ 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 C++11 / C2x attributes that aren't type attributes. + for (const ParsedAttr &PA : attrs) { + if (!PA.isCXX11Attribute() && !PA.isC2xAttribute()) + continue; + // We reject AT_LifetimeBound and AT_AnyX86NoCfCheck, even though they + // are type attributes, because we historically haven't allowed these + // to be used as type attributes in C++11 / C2x syntax. + if (PA.isTypeAttr() && PA.getKind() != ParsedAttr::AT_LifetimeBound && + PA.getKind() != ParsedAttr::AT_AnyX86NoCfCheck) + continue; + if (PA.getKind() == ParsedAttr::UnknownAttribute) + Diag(PA.getLoc(), diag::warn_unknown_attribute_ignored) + << PA << PA.getRange(); + else { + Diag(PA.getLoc(), diag::err_attribute_not_type_attr) << PA; + PA.setInvalid(); + } + } DS.takeAttributesFrom(attrs); } diff --git a/clang/lib/Sema/SemaAttr.cpp b/clang/lib/Sema/SemaAttr.cpp --- a/clang/lib/Sema/SemaAttr.cpp +++ b/clang/lib/Sema/SemaAttr.cpp @@ -384,6 +384,52 @@ AlignPackStack.Act(PragmaLoc, Action, SlotLabel, Info); } +bool Sema::ConstantFoldAttrArgs(const AttributeCommonInfo &CI, + MutableArrayRef Args) { + llvm::SmallVector 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; + + 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) { diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -4146,48 +4146,10 @@ void Sema::AddAnnotationAttr(Decl *D, const AttributeCommonInfo &CI, StringRef Str, MutableArrayRef Args) { auto *Attr = AnnotateAttr::Create(Context, Str, Args.data(), Args.size(), CI); - llvm::SmallVector 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); - } - D->addAttr(Attr); + if (ConstantFoldAttrArgs( + CI, MutableArrayRef(Attr->args_begin(), Attr->args_end()))) { + D->addAttr(Attr); + } } static void handleAnnotateAttr(Sema &S, Decl *D, const ParsedAttr &AL) { diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp --- a/clang/lib/Sema/SemaType.cpp +++ b/clang/lib/Sema/SemaType.cpp @@ -8098,6 +8098,30 @@ CurType = T; } +static void HandleAnnotateTypeAttr(TypeProcessingState &State, + QualType &CurType, const ParsedAttr &Attr) { + Sema &S = State.getSema(); + + // Make sure that there is a string literal as the annotation's first + // argument. + StringRef Str; + if (!S.checkStringLiteralArgumentAttr(Attr, 0, Str)) + return; + + llvm::SmallVector Args; + Args.reserve(Attr.getNumArgs() - 1); + for (unsigned Idx = 1; Idx < Attr.getNumArgs(); Idx++) { + assert(!Attr.isArgIdent(Idx)); + Args.push_back(Attr.getArgAsExpr(Idx)); + } + if (!S.ConstantFoldAttrArgs(Attr, Args)) { + return; + } + auto *AnnotateTypeAttr = + AnnotateTypeAttr::Create(S.Context, Str, Args.data(), Args.size(), Attr); + CurType = State.getAttributedType(AnnotateTypeAttr, CurType, CurType); +} + static void HandleLifetimeBoundAttr(TypeProcessingState &State, QualType &CurType, ParsedAttr &Attr) { @@ -8158,10 +8182,11 @@ 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; } @@ -8359,6 +8384,11 @@ 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 diff --git a/clang/test/CodeGenCXX/annotate-type.cpp b/clang/test/CodeGenCXX/annotate-type.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/annotate-type.cpp @@ -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 struct S {}; + +S +g(S) { + return {}; +} +// CHECK: @_Z1g1SIPiE() diff --git a/clang/test/Sema/annotate-type.c b/clang/test/Sema/annotate-type.c new file mode 100644 --- /dev/null +++ b/clang/test/Sema/annotate-type.c @@ -0,0 +1,12 @@ +// RUN: %clang_cc1 %s -fsyntax-only -fdouble-square-bracket-attributes -verify + +void foo(float * [[clang::annotate_type("foo")]] a) { + int [[clang::annotate_type("bar")]] x1; + int * [[clang::annotate_type("bar")]] x2; + int * [[clang::annotate_type(1)]] x3; // expected-error {{'annotate_type' attribute requires a string}} + int * [[clang::annotate_type("bar", 1)]] x4; + + // 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}} +} diff --git a/clang/test/SemaCXX/annotate-type.cpp b/clang/test/SemaCXX/annotate-type.cpp new file mode 100644 --- /dev/null +++ b/clang/test/SemaCXX/annotate-type.cpp @@ -0,0 +1,30 @@ +// RUN: %clang_cc1 %s -std=c++17 -fsyntax-only -verify + +struct S1 { + void f() [[clang::annotate_type("foo")]]; +}; + +template struct is_same { + static constexpr bool value = false; +}; + +template struct is_same { + static constexpr bool value = true; +}; + +static_assert(is_same::value); +static_assert(is_same::value); +static_assert(is_same::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 struct S2 {}; + +template <> struct S2 {}; // expected-note {{previous definition is here}} + +template <> +struct S2 {}; // expected-error {{redefinition of 'S2'}} diff --git a/clang/unittests/AST/AttrTest.cpp b/clang/unittests/AST/AttrTest.cpp --- a/clang/unittests/AST/AttrTest.cpp +++ b/clang/unittests/AST/AttrTest.cpp @@ -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,81 @@ 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::tooling::buildASTFromCode; + 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(clang::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("fn"); +} + +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")]]); +)cpp"); + + const FunctionDecl *Func = getFunctionNode(AST.get(), "f"); + + { + TypeLoc Parm0TL = Func->getParamDecl(0)->getTypeSourceInfo()->getTypeLoc(); + const auto PointerTL = Parm0TL.getAs(); + ASSERT_FALSE(PointerTL.isNull()); + const auto AttributedTL = + PointerTL.getPointeeLoc().getAs(); + ASSERT_FALSE(AttributedTL.isNull()); + ASSERT_NE(AttributedTL.getAttr(), nullptr); + ASSERT_TRUE(AttributedTL.getModifiedLoc().getAs()); + + const auto *Annotate = dyn_cast(AttributedTL.getAttr()); + ASSERT_NE(Annotate, nullptr); + EXPECT_EQ(Annotate->getAnnotation(), "foo"); + + EXPECT_EQ(Annotate->args_size(), 2); + const auto *StringLit = selectFirst( + "str", match(constantExpr(hasDescendant(stringLiteral().bind("str"))), + *Annotate->args_begin()[0], Func->getASTContext())); + ASSERT_NE(StringLit, nullptr); + EXPECT_EQ(StringLit->getString(), "arg1"); + EXPECT_EQ(match(constantExpr(has(integerLiteral(equals(2)).bind("int"))), + *Annotate->args_begin()[1], Func->getASTContext()) + .size(), + 1); + } + + { + TypeLoc Parm1TL = Func->getParamDecl(1)->getTypeSourceInfo()->getTypeLoc(); + const auto AttributedTL = Parm1TL.getAs(); + ASSERT_FALSE(AttributedTL.isNull()); + ASSERT_TRUE(AttributedTL.getModifiedLoc().getType() == + Func->getASTContext().IntTy); + + const auto *Annotate = dyn_cast(AttributedTL.getAttr()); + ASSERT_NE(Annotate, nullptr); + EXPECT_EQ(Annotate->getAnnotation(), "bar"); + } +} + } // namespace