[clang] [Clang] Support the GNU copy attribute (PR #229355)
via cfe-commits
cfe-commits at lists.llvm.org
Tue Oct 6 02:49:47 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang
Author: LoveSy (yujincheng08)
<details>
<summary>Changes</summary>
Support copy attribute like GCC.
---
Patch is 42.98 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/229355.diff
17 Files Affected:
- (modified) clang/docs/ReleaseNotes.md (+4)
- (modified) clang/include/clang/Basic/Attr.td (+10)
- (modified) clang/include/clang/Basic/AttrDocs.td (+41)
- (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+9)
- (modified) clang/include/clang/Sema/Sema.h (+4)
- (modified) clang/lib/Sema/SemaDeclAttr.cpp (+260)
- (modified) clang/lib/Sema/SemaTemplateInstantiateDecl.cpp (+21)
- (modified) clang/lib/Sema/SemaType.cpp (+48)
- (added) clang/test/AST/attr-copy.c (+105)
- (added) clang/test/CodeGen/attr-copy.c (+34)
- (added) clang/test/CodeGenCXX/attr-copy-calling-conv.cpp (+8)
- (added) clang/test/PCH/attr-copy.cpp (+31)
- (added) clang/test/Sema/attr-copy-calling-conv.c (+24)
- (added) clang/test/Sema/attr-copy.c (+97)
- (added) clang/test/SemaCXX/attr-copy-calling-conv.cpp (+30)
- (added) clang/test/SemaTemplate/attr-copy.cpp (+60)
- (modified) clang/utils/TableGen/ClangAttrEmitter.cpp (+58-1)
``````````diff
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 7b878a0abbab3..1aed07bbcdfc1 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -304,6 +304,10 @@ features cannot lower the translation-unit ABI level;
### Attribute Changes in Clang
+- Clang now supports `__attribute__((copy(expression)))` and
+ `[[gnu::copy(expression)]]` for copying GNU declaration attributes from
+ another function, variable, or type.
+
- Clang now properly propagates attributes on class and variable templates to their redeclarations, which will result in redeclarations not interfering with diagnostics. (#GH209812)
- Clang now recognizes the `[[gnu::flag_enum]]` attribute and treats it equivalent to `[[clang::flag_enum]]`
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 4ef0bd2b5ca7c..a78c350d5d4e6 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -878,6 +878,16 @@ def Alias : Attr {
let Documentation = [Undocumented];
}
+def Copy : InheritableAttr {
+ let Spellings = [GCC<"copy">];
+ let Args = [ExprArgument<"Source">];
+ let Subjects = SubjectList<[Function, Var, Field, Tag, TypedefName]>;
+ let ParseArgumentsAsUnevaluated = 1;
+ let TemplateDependent = 1;
+ let MeaningfulToClassTemplateDefinition = 1;
+ let Documentation = [CopyDocs];
+}
+
def BuiltinAlias : Attr {
let Spellings = [CXX11<"clang", "builtin_alias">,
C23<"clang", "builtin_alias">,
diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index 0ab1429ff09d6..11569376cdccb 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -42,6 +42,47 @@ This page lists the attributes currently supported by Clang.
}];
}
+def CopyDocs : Documentation {
+ let Category = DocCatDecl;
+ let Content = [{
+The `copy(expression)` attribute applies the GNU declaration attributes of the
+entity or type referenced by `expression` to another declaration. The expression
+is unevaluated. For example:
+
+```c
+void *allocate(unsigned long) __attribute__((malloc, alloc_size(1), nothrow));
+extern __typeof__(allocate) allocate_alias
+ __attribute__((alias("allocate"), copy(allocate)));
+```
+
+The argument can name a function or variable, take its address, or refer to a
+type through an expression such as `(struct A *)0`. A function's attributes
+cannot be copied to a variable, or vice versa. Copied attributes are checked
+against the destination just as if they had been written there explicitly.
+
+Attributes controlling linkage, inlining, or visibility, including `alias`,
+`ifunc`, `always_inline`, `noinline`, `gnu_inline`, `visibility`, `weak`,
+`weakref`, `dllimport`, and `dllexport`, are not copied.
+Neither `deprecated` nor `target_clones` is copied. The attribute does not copy
+the source's type; `__typeof__`, as in the example, can be used when the types
+must also match.
+
+Function type attributes, such as calling conventions, can also be copied. For
+example, a captureless lambda can use `copy` to match a callback's calling
+convention:
+
+```cpp
+using Callback = int (__attribute__((ms_abi)) *)(int);
+Callback callback = [](int x) __attribute__((copy((Callback)nullptr))) {
+ return x;
+};
+```
+
+The calling convention is applied to the lambda's call operator and its
+conversion to a function pointer.
+ }];
+}
+
def SectionDocs : Documentation {
let Category = DocCatVariable;
let Content = [{
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 9208aba1445d7..2616cda0b6fda 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -4056,6 +4056,15 @@ def err_attribute_weakref_without_alias : Error<
"weakref declaration of %0 must also have an alias attribute">;
def err_alias_not_supported_on_darwin : Error <
"aliases are not supported on darwin">;
+def err_attribute_copy_invalid_argument : Error<
+ "'copy' attribute requires an expression referring to a function, variable, "
+ "or type">;
+def warn_attribute_copy_kind : Warning<
+ "'copy' attribute ignored on a declaration of a different kind than its "
+ "argument">, InGroup<IgnoredAttributes>;
+def warn_attribute_copy_self : Warning<
+ "'copy' attribute ignored on a declaration referring to itself">,
+ InGroup<IgnoredAttributes>;
def warn_attribute_non_character_array : Warning<
"%0%select{ attribute|}1 only applies to fields or variables of character "
"array type; type is %2">, InGroup<IgnoredAttributes>;
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 7fbcc4aae7d6c..e662471831630 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -5050,6 +5050,10 @@ class Sema final : public SemaBase {
/// treat a reference type as valid.
bool isValidPointerAttrType(QualType T, bool RefOkay = false);
+ void AddCopyAttr(Decl *D, const AttributeCommonInfo &CI, Expr *E,
+ bool TypeAttrsOnly = false);
+ bool ProcessCopiedTypeAttribute(Decl *D, ParsedAttr &AL);
+
/// AddAssumeAlignedAttr - Adds an assume_aligned attribute to a particular
/// declaration.
void AddAssumeAlignedAttr(Decl *D, const AttributeCommonInfo &CI, Expr *E,
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index eb4a8c2ab9ae0..8f882fd67ee66 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -7802,6 +7802,9 @@ ProcessDeclAttribute(Sema &S, Decl *D, const ParsedAttr &AL,
case ParsedAttr::AT_Constructor:
handleConstructorAttr(S, D, AL);
break;
+ case ParsedAttr::AT_Copy:
+ S.AddCopyAttr(D, AL, AL.getArgAsExpr(0));
+ break;
case ParsedAttr::AT_Deprecated:
handleDeprecatedAttr(S, D, AL);
break;
@@ -8552,6 +8555,263 @@ static void checkAMDGPUReqdWorkGroupSize(Sema &S, Decl *D) {
}
}
+static ArgsUnion createCopyAttrArgument(ASTContext &Context, SourceLocation Loc,
+ Expr *E) {
+ return E;
+}
+
+static ArgsUnion createCopyAttrArgument(ASTContext &Context, SourceLocation Loc,
+ unsigned Value) {
+ return IntegerLiteral::Create(Context, llvm::APInt(32, Value),
+ Context.UnsignedIntTy, Loc);
+}
+
+static ArgsUnion createCopyAttrArgument(ASTContext &Context, SourceLocation Loc,
+ ParamIdx Index) {
+ return createCopyAttrArgument(Context, Loc, Index.getSourceIndex());
+}
+
+static ArgsUnion createCopyAttrArgument(ASTContext &Context, SourceLocation Loc,
+ StringRef Value) {
+ return StringLiteral::Create(
+ Context, Value, StringLiteralKind::Ordinary, false,
+ Context.getStringLiteralArrayType(Context.CharTy, Value.size()), Loc);
+}
+
+static ArgsUnion createCopyAttrArgument(ASTContext &Context, SourceLocation Loc,
+ const IdentifierInfo *II) {
+ return new (Context) IdentifierLoc(Loc, const_cast<IdentifierInfo *>(II));
+}
+
+static ArgsUnion createCopyAttrArgument(ASTContext &Context, SourceLocation Loc,
+ FunctionDecl *FD) {
+ return DeclRefExpr::Create(Context, NestedNameSpecifierLoc(),
+ SourceLocation(), FD, false, Loc, FD->getType(),
+ VK_LValue);
+}
+
+#define WANT_DECL_COPY_LOGIC
+#include "clang/Sema/AttrParsedAttrImpl.inc"
+#undef WANT_DECL_COPY_LOGIC
+
+static bool isCopyableAttr(const Attr *A) {
+ // copy propagates GNU declaration attributes, not implicit attributes or
+ // attributes from other vendors. In particular, an implicit BuiltinAttr must
+ // not turn the destination into a different builtin function.
+ if (A->isImplicit() || (!A->isGNUAttribute() && !A->isGNUScope()))
+ return false;
+
+ switch (A->getKind()) {
+ case attr::Alias:
+ case attr::AlwaysInline:
+ case attr::Copy:
+ case attr::Deprecated:
+ case attr::DLLExport:
+ case attr::DLLImport:
+ case attr::GNUInline:
+ case attr::IFunc:
+ case attr::NoInline:
+ case attr::SelectAny:
+ case attr::TargetClones:
+ case attr::Visibility:
+ case attr::Weak:
+ case attr::WeakRef:
+ return false;
+ default:
+ return ParsedAttrInfo::get(*A).IsKnownToGCC;
+ }
+}
+
+void Sema::AddCopyAttr(Decl *D, const AttributeCommonInfo &CI, Expr *E,
+ bool TypeAttrsOnly) {
+ if (E->isTypeDependent() || E->isValueDependent()) {
+ auto *A = ::new (Context) CopyAttr(Context, CI, E);
+ A->setImplicit(TypeAttrsOnly);
+ D->addAttr(A);
+ return;
+ }
+
+ Expr *SourceExpr = E->IgnoreParenCasts();
+ if (const auto *UO = dyn_cast<UnaryOperator>(SourceExpr);
+ UO && (UO->getOpcode() == UO_AddrOf || UO->getOpcode() == UO_Deref))
+ SourceExpr = UO->getSubExpr()->IgnoreParenCasts();
+
+ const Decl *Source = nullptr;
+ if (const auto *DRE = dyn_cast<DeclRefExpr>(SourceExpr))
+ Source = DRE->getDecl();
+ else if (const auto *ME = dyn_cast<MemberExpr>(SourceExpr))
+ Source = ME->getMemberDecl();
+
+ // Attributes are processed before a new function or variable declaration is
+ // linked into its redeclaration chain.
+ const auto *SourceVD = dyn_cast_or_null<ValueDecl>(Source);
+ const auto *DestVD = dyn_cast<ValueDecl>(D);
+ bool IsRedeclaration =
+ SourceVD && DestVD && SourceVD->getDeclName() == DestVD->getDeclName() &&
+ SourceVD->getDeclContext()->Equals(DestVD->getDeclContext()) &&
+ Context.hasSameType(SourceVD->getType(), DestVD->getType());
+ // Nested block scopes share a DeclContext, but can declare distinct local
+ // variables with the same name and type.
+ if (const auto *VD = dyn_cast_or_null<VarDecl>(Source))
+ IsRedeclaration &= !VD->isLocalVarDecl();
+ if (Source && (Source->getCanonicalDecl() == D->getCanonicalDecl() ||
+ IsRedeclaration)) {
+ Diag(CI.getLoc(), diag::warn_attribute_copy_self);
+ return;
+ }
+
+ if (Source && !isa<FunctionDecl, VarDecl, FieldDecl>(Source)) {
+ Diag(E->getExprLoc(), diag::err_attribute_copy_invalid_argument);
+ return;
+ }
+
+ if (isa<StringLiteral>(SourceExpr->IgnoreParenCasts()) ||
+ (!Source && E->getType()->isArithmeticType() &&
+ E->isEvaluatable(Context)) ||
+ E->getType() == Context.OverloadTy) {
+ Diag(E->getExprLoc(), diag::err_attribute_copy_invalid_argument);
+ return;
+ }
+
+ QualType Ty = E->getType();
+ if (const auto *FD = dyn_cast_or_null<FunctionDecl>(Source))
+ Ty = FD->getType();
+ if (Source && isa<FunctionDecl>(D) != isa<FunctionDecl>(Source) &&
+ isa<FunctionDecl, VarDecl, FieldDecl>(D)) {
+ Diag(CI.getLoc(), diag::warn_attribute_copy_kind);
+ return;
+ }
+
+ SmallVector<const Decl *, 4> Sources;
+ SmallVector<const Attr *, 8> SourceAttrs;
+ if (Source)
+ Sources.push_back(Source->getMostRecentDecl());
+
+ // Keep typedef attributes as well as attributes on the underlying tag. Do
+ // not canonicalize Ty before walking it: that would discard typedefs.
+ bool SawPointer = false;
+ bool HasCallingConventionAttr = false;
+ while (true) {
+ if (const auto *TT = dyn_cast<TypedefType>(Ty.getTypePtr()))
+ Sources.push_back(TT->getDecl());
+ if (const auto *AT = dyn_cast<AttributedType>(Ty.getTypePtr())) {
+ if (const Attr *A = AT->getAttr()) {
+ SourceAttrs.push_back(A);
+ HasCallingConventionAttr |= AT->isCallingConv();
+ }
+ }
+ QualType Next = Ty.getSingleStepDesugaredType(Context);
+ if (Next != Ty) {
+ Ty = Next;
+ } else if (!SawPointer && Ty->isPointerType()) {
+ Ty = Ty->getPointeeType();
+ SawPointer = true;
+ } else {
+ break;
+ }
+ }
+ // Naming a pointer to a class template specialization need not instantiate
+ // it, but its attributes may depend on its template arguments.
+ if (Ty->getAsCXXRecordDecl())
+ isCompleteType(CI.getLoc(), Ty);
+ if (const auto *TT = Ty->getAs<TagType>())
+ Sources.push_back(TT->getDecl()->getMostRecentDecl());
+
+ AttributeFactory Factory;
+ ParsedAttributes Copied(Factory);
+ // Some function attributes are represented only in the function type's
+ // extended information, without an Attr or AttributedType node.
+ if (const auto *FT = Ty->getAs<FunctionType>()) {
+ auto Add = [&](StringRef Name, MutableArrayRef<ArgsUnion> Args = {}) {
+ Copied.addNew(&Context.Idents.get(Name), CI.getRange(), {}, Args.data(),
+ Args.size(), AttributeCommonInfo::Form::GNU());
+ };
+ FunctionType::ExtInfo EI = FT->getExtInfo();
+ // Substituting a template type argument can discard the AttributedType
+ // sugar while retaining the calling convention in the canonical type.
+ if (!HasCallingConventionAttr) {
+ switch (EI.getCC()) {
+ case CC_X86StdCall:
+ case CC_X86FastCall:
+ case CC_X86ThisCall:
+ case CC_Win64:
+ case CC_X86_64SysV:
+ case CC_X86RegCall:
+ Add(FunctionType::getNameForCallConv(EI.getCC()));
+ break;
+ case CC_AAPCS:
+ case CC_AAPCS_VFP: {
+ ArgsUnion Arg = createCopyAttrArgument(
+ Context, CI.getLoc(), FunctionType::getNameForCallConv(EI.getCC()));
+ Add("pcs", Arg);
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ if (EI.getNoReturn())
+ Add("noreturn");
+ if (EI.getNoCallerSavedRegs())
+ Add("no_caller_saved_registers");
+ if (EI.getNoCfCheck())
+ Add("nocf_check");
+ if (EI.getHasRegParm()) {
+ ArgsUnion Arg =
+ createCopyAttrArgument(Context, CI.getLoc(), EI.getRegParm());
+ Add("regparm", Arg);
+ }
+ if (const auto *FPT = dyn_cast<FunctionProtoType>(FT))
+ if (FPT->getExceptionSpecType() == EST_NoThrow)
+ Add("nothrow");
+ }
+ for (const Decl *From : Sources) {
+ // A type expression can refer to the very type being declared.
+ if (From->getCanonicalDecl() == D->getCanonicalDecl()) {
+ Diag(CI.getLoc(), diag::warn_attribute_copy_self);
+ continue;
+ }
+ SourceAttrs.append(From->attr_begin(), From->attr_end());
+ }
+ for (const Attr *A : SourceAttrs) {
+ if (!isCopyableAttr(A))
+ continue;
+
+ ArgsVector Args;
+ if (const auto *AA = dyn_cast<AlignedAttr>(A)) {
+ if (AA->isAlignmentExpr() && AA->getAlignmentExpr())
+ Args.push_back(AA->getAlignmentExpr());
+ } else if (!getAttributeCopyArgs(Context, A, CI.getLoc(), Args)) {
+ continue;
+ }
+
+ Copied.addNew(&Context.Idents.get(A->getSpelling()), CI.getRange(),
+ AttributeScopeInfo(A->getScopeName(), CI.getLoc()),
+ Args.data(), Args.size(), A->getForm());
+ }
+ ParsedAttributesView DeclAttrs;
+ bool CopiedType = false;
+ for (ParsedAttr &AL : Copied) {
+ if (ProcessCopiedTypeAttribute(D, AL))
+ CopiedType = true;
+ else if (!TypeAttrsOnly)
+ DeclAttrs.addAtEnd(&AL);
+ }
+ // Template instantiation rebuilds types from their written TypeSourceInfo,
+ // which does not contain attributes introduced by copy. An implicit copy
+ // attribute replays only the type attributes: declaration attributes are
+ // already expanded and will be instantiated normally.
+ if (CopiedType && D->isTemplated()) {
+ auto *A = ::new (Context) CopyAttr(Context, CI, E);
+ A->setImplicit(true);
+ D->addAttr(A);
+ }
+ // The enclosing attribute list may still contain attributes needed by a
+ // group check (for example, alias after weakref and copy).
+ for (const ParsedAttr &AL : DeclAttrs)
+ ProcessDeclAttribute(*this, D, AL, ProcessDeclAttributeOptions());
+}
+
void Sema::ProcessDeclAttributeList(
Scope *S, Decl *D, const ParsedAttributesView &AttrList,
const ProcessDeclAttributeOptions &Options) {
diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
index 5e72f05c863a0..77d7f33273369 100644
--- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -88,6 +88,17 @@ bool TemplateDeclInstantiator::SubstQualifier(const TagDecl *OldDecl,
// Include attribute instantiation code.
#include "clang/Sema/AttrTemplateInstantiate.inc"
+static void
+instantiateDependentCopyAttr(Sema &S,
+ const MultiLevelTemplateArgumentList &TemplateArgs,
+ const CopyAttr *A, Decl *New) {
+ EnterExpressionEvaluationContext Unevaluated(
+ S, Sema::ExpressionEvaluationContext::Unevaluated);
+ ExprResult E = S.SubstExpr(A->getSource(), TemplateArgs);
+ if (!E.isInvalid())
+ S.AddCopyAttr(New, *A, E.get(), A->isImplicit());
+}
+
static void instantiateDependentAlignedAttr(
Sema &S, const MultiLevelTemplateArgumentList &TemplateArgs,
const AlignedAttr *Aligned, Decl *New, bool IsPackExpansion) {
@@ -847,6 +858,11 @@ void Sema::InstantiateAttrsForDecl(
if (!isRelevantAttr(*this, New, TmplAttr))
continue;
+ if (const auto *A = dyn_cast<CopyAttr>(TmplAttr)) {
+ instantiateDependentCopyAttr(*this, TemplateArgs, A, New);
+ continue;
+ }
+
// FIXME: If any of the special case versions from InstantiateAttrs become
// applicable to template declaration, we'll need to add them here.
CXXThisScopeRAII ThisScope(
@@ -889,6 +905,11 @@ void Sema::InstantiateAttrs(const MultiLevelTemplateArgumentList &TemplateArgs,
if (!isRelevantAttr(*this, New, TmplAttr))
continue;
+ if (const auto *A = dyn_cast<CopyAttr>(TmplAttr)) {
+ instantiateDependentCopyAttr(*this, TemplateArgs, A, New);
+ continue;
+ }
+
// FIXME: This should be generalized to more than just the AlignedAttr.
const AlignedAttr *Aligned = dyn_cast<AlignedAttr>(TmplAttr);
if (Aligned && Aligned->isAlignmentDependent()) {
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index e1481b437df9e..a70505549508f 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -8468,6 +8468,54 @@ static bool handleFunctionTypeAttr(TypeProcessingState &state, ParsedAttr &attr,
return true;
}
+bool Sema::ProcessCopiedTypeAttribute(Decl *D, ParsedAttr &AL) {
+ switch (AL.getKind()) {
+ case ParsedAttr::AT_NoThrow:
+ if (!getLangOpts().CPlusPlus)
+ return false;
+ [[fallthrough]];
+ FUNCTION_TYPE_ATTRS_CASELIST:
+ break;
+ default:
+ return false;
+ }
+
+ QualType T;
+ if (auto *VD = dyn_cast<ValueDecl>(D))
+ T = VD->getType();
+ else if (auto *TD = dyn_cast<TypedefNameDecl>(D))
+ T = TD->getUnderlyingType();
+ else
+ return false;
+
+ if (checkCommonAttributeFeatures(D, AL))
+ return true;
+
+ // Reuse the function type attribute checks, including calling convention
+ // conflicts and restrictions on variadic functions. No declarator chunks
+ // need to be processed: the destination's type has already been built.
+ AttributeFactory Factory;
+ DeclSpec DS(Factory);
+ Declarator Declarator(DS, ParsedAttributesView::none(),
+ DeclaratorContext::TypeName);
+ TypeProcessingState State(*this, Declarator);
+ CUDAFunctionTarget CFT = CUDAFunctionTarget::Host;
+ if (getLangOpts().CUDA)
+ if (auto *FD = dyn_cast<FunctionDecl>(D))
+ CFT = CUDA().IdentifyTarget(FD);
+ if (!handleFunctionTypeAttr(State, AL, T, CFT))
+ diagnoseBadTypeAttribute(*this, AL, T);
+ else if (!AL.isInvalid()) {
+ if (auto *VD = dyn_cast<ValueDecl>(D))
+ VD->setType(T);
+ else {
+ auto *TD = cast<TypedefNameDecl>(D);
+ TD->setModedTypeSourceInfo(TD->getTypeSourceInfo(), T);
+ }
+ }
+ return true;
+}
+
bool Sema::hasExplicitCallingConv(QualType T) {
const AttributedType *AT;
diff --git a/clang/test/AST/attr-copy.c b/clang/test/AST/attr-copy.c
new file mode 100644
index 0000000000000..725891fc3dc22
--- /dev/null
+++ b/clang/test/AST/attr-copy.c
@@ -0,0 +1,105 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -Wno-deprecated-declarations -verify -ast-dump %s | FileCheck %s
+
+void *source(int) __attribute__((malloc, alloc_size(1), nothrow, returns_nonnull,
+ deprecated, weak, visibility("hidden")));
+
+// CHECK-LABEL: FunctionDecl {{.*}} copied 'void *(int)'
+// CHECK: Restrict...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/229355
More information about the cfe-commits
mailing list