diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 8a136aae5489a..bdf2f47e37144 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -171,6 +171,11 @@ Attribute Changes in Clang automatic diagnostic to use parameters of types that the format style supports but that are never the result of default argument promotion, such as ``float``. (`#59824: `_) +- The ``constructor`` and ``destructor`` attributes now diagnose when: + - the priority is not between 101 and 65535, inclusive, + - the function it is applied to accepts arguments or has a non-void return + type, or + - the function it is applied to is a non-static member function (C++). Improvements to Clang's diagnostics ----------------------------------- diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td index 2f9d4d1b7907b..7720580b2c349 100644 --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -7251,8 +7251,10 @@ after returning from ``main()`` or when the ``exit()`` function has been called. Note, ``quick_exit()``, ``_Exit()``, and ``abort()`` prevent a function marked ``destructor`` from being called. -The constructor or destructor function should not accept any arguments and its -return type should be ``void``. +The constructor or destructor function cannot accept any arguments and its +return type should be ``void``, ``int``, or ``unsigned int``. The latter two +types are supported for historical reasons. In C++ language modes, the function +cannot be marked ``consteval``, nor can it be a non-static member function. The attributes accept an optional argument used to specify the priority order in which to execute constructor and destructor functions. The priority is diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index f4eb02fd9570c..3f4624d1d09db 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -2864,6 +2864,8 @@ def warn_cxx11_compat_constexpr_body_multiple_return : Warning< InGroup, DefaultIgnore; def note_constexpr_body_previous_return : Note< "previous return statement is here">; +def err_ctordtor_attr_consteval : Error< + "%0 attribute cannot be applied to a 'consteval' function">; // C++20 function try blocks in constexpr def ext_constexpr_function_try_block_cxx20 : ExtWarn< @@ -3176,6 +3178,11 @@ def err_alignas_underaligned : Error< "requested alignment is less than minimum alignment of %1 for type %0">; def warn_aligned_attr_underaligned : Warning, InGroup; +def err_ctor_dtor_attr_on_non_void_func : Error< + "%0 attribute can only be applied to a function which accepts no arguments " + "and has a 'void' or 'int' return type">; +def err_ctor_dtor_member_func : Error< + "%0 attribute cannot be applied to a member function">; def err_attribute_sizeless_type : Error< "%0 attribute cannot be applied to sizeless type %1">; def err_attribute_argument_n_type : Error< diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index 090a54eedaa07..a0000a2118e81 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -2352,26 +2352,78 @@ static void handleUnusedAttr(Sema &S, Decl *D, const ParsedAttr &AL) { D->addAttr(::new (S.Context) UnusedAttr(S.Context, AL)); } -static void handleConstructorAttr(Sema &S, Decl *D, const ParsedAttr &AL) { - uint32_t priority = ConstructorAttr::DefaultPriority; +static bool diagnoseInvalidPriority(Sema &S, uint32_t Priority, + const ParsedAttr &A, + SourceLocation PriorityLoc) { + constexpr uint32_t ReservedPriorityLower = 101, ReservedPriorityUpper = 65535; + + // Only perform the priority check if the attribute is outside of a system + // header. Values <= 100 are reserved for the implementation, and libc++ + // benefits from being able to specify values in that range. Values > 65535 + // are reserved for historical reasons. + if ((Priority < ReservedPriorityLower || Priority > ReservedPriorityUpper) && + !S.getSourceManager().isInSystemHeader(A.getLoc())) { + S.Diag(A.getLoc(), diag::err_attribute_argument_out_of_range) + << PriorityLoc << A << ReservedPriorityLower << ReservedPriorityUpper; + A.setInvalid(); + return true; + } + return false; +} + +template +static void handleCtorDtorAttr(Sema &S, Decl *D, const ParsedAttr &AL) { + uint32_t Priority = CtorDtorAttr::DefaultPriority; if (S.getLangOpts().HLSL && AL.getNumArgs()) { S.Diag(AL.getLoc(), diag::err_hlsl_init_priority_unsupported); return; } - if (AL.getNumArgs() && - !checkUInt32Argument(S, AL, AL.getArgAsExpr(0), priority)) - return; - D->addAttr(::new (S.Context) ConstructorAttr(S.Context, AL, priority)); -} + // If we're given an argument for the priority, check that it's valid. + if (AL.getNumArgs()) { + if (!checkUInt32Argument(S, AL, AL.getArgAsExpr(0), Priority)) + return; -static void handleDestructorAttr(Sema &S, Decl *D, const ParsedAttr &AL) { - uint32_t priority = DestructorAttr::DefaultPriority; - if (AL.getNumArgs() && - !checkUInt32Argument(S, AL, AL.getArgAsExpr(0), priority)) + // Ensure the priority is in a reasonable range. + if (diagnoseInvalidPriority(S, Priority, AL, + AL.getArgAsExpr(0)->getExprLoc())) + return; + } + + // Ensure the function we're attaching to is something that is sensible to + // automatically call before or after main(); it should accept no arguments. + // In theory, a void return type is the only truly safe return type (consider + // that calling conventions may place returned values in a hidden pointer + // argument passed to the function that will not be present when called + // automatically). However, there is a significant amount of existing code + // which uses an int return type. So we will accept void, int, and + // unsigned int return types. Any other return type, or a non-void parameter + // list is treated as an error because it's a form of type system + // incompatibility. The function also cannot be a member function. We allow + // K&R C functions because that's a difficult edge case where it depends on + // how the function is defined as to whether it does or does not expect + // arguments. + const auto *FD = cast(D); + QualType RetTy = FD->getReturnType(); + if (!(RetTy->isVoidType() || + RetTy->isSpecificBuiltinType(BuiltinType::UInt) || + RetTy->isSpecificBuiltinType(BuiltinType::Int)) || + (FD->hasPrototype() && FD->getNumParams() != 0)) { + S.Diag(AL.getLoc(), diag::err_ctor_dtor_attr_on_non_void_func) + << AL << FD->getSourceRange(); return; + } else if (const auto *MD = dyn_cast(FD); + MD && MD->isInstance()) { + S.Diag(AL.getLoc(), diag::err_ctor_dtor_member_func) + << AL << FD->getSourceRange(); + return; + } else if (FD->isConsteval()) { + S.Diag(AL.getLoc(), diag::err_ctordtor_attr_consteval) + << AL << FD->getSourceRange(); + return; + } - D->addAttr(::new (S.Context) DestructorAttr(S.Context, AL, priority)); + D->addAttr(CtorDtorAttr::Create(S.Context, Priority, AL)); } template @@ -3888,16 +3940,9 @@ static void handleInitPriorityAttr(Sema &S, Decl *D, const ParsedAttr &AL) { return; } - // Only perform the priority check if the attribute is outside of a system - // header. Values <= 100 are reserved for the implementation, and libc++ - // benefits from being able to specify values in that range. - if ((prioritynum < 101 || prioritynum > 65535) && - !S.getSourceManager().isInSystemHeader(AL.getLoc())) { - S.Diag(AL.getLoc(), diag::err_attribute_argument_out_of_range) - << E->getSourceRange() << AL << 101 << 65535; - AL.setInvalid(); + if (diagnoseInvalidPriority(S, prioritynum, AL, E->getExprLoc())) return; - } + D->addAttr(::new (S.Context) InitPriorityAttr(S.Context, AL, prioritynum)); } @@ -8930,13 +8975,13 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL, handlePassObjectSizeAttr(S, D, AL); break; case ParsedAttr::AT_Constructor: - handleConstructorAttr(S, D, AL); + handleCtorDtorAttr(S, D, AL); break; case ParsedAttr::AT_Deprecated: handleDeprecatedAttr(S, D, AL); break; case ParsedAttr::AT_Destructor: - handleDestructorAttr(S, D, AL); + handleCtorDtorAttr(S, D, AL); break; case ParsedAttr::AT_EnableIf: handleEnableIfAttr(S, D, AL); diff --git a/clang/test/CodeGen/PowerPC/aix-destructor-attribute.c b/clang/test/CodeGen/PowerPC/aix-destructor-attribute.c index cfb1fd7b0171a..f0d83d161be50 100644 --- a/clang/test/CodeGen/PowerPC/aix-destructor-attribute.c +++ b/clang/test/CodeGen/PowerPC/aix-destructor-attribute.c @@ -12,9 +12,8 @@ // RUN: -fno-use-cxa-atexit -fregister-global-dtors-with-atexit < %s | \ // RUN: FileCheck --check-prefix=REGISTER %s -int bar(void) __attribute__((destructor(100))); +int bar(void) __attribute__((destructor(101))); int bar2(void) __attribute__((destructor(65535))); -int bar3(int) __attribute__((destructor(65535))); int bar(void) { return 1; @@ -24,16 +23,12 @@ int bar2(void) { return 2; } -int bar3(int a) { - return a; -} - -// NO-REGISTER: @llvm.global_dtors = appending global [3 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 100, ptr @bar, ptr null }, { i32, ptr, ptr } { i32 65535, ptr @bar2, ptr null }, { i32, ptr, ptr } { i32 65535, ptr @bar3, ptr null }] +// NO-REGISTER: @llvm.global_dtors = appending global [2 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 101, ptr @bar, ptr null }, { i32, ptr, ptr } { i32 65535, ptr @bar2, ptr null }] -// REGISTER: @llvm.global_ctors = appending global [2 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 100, ptr @__GLOBAL_init_100, ptr null }, { i32, ptr, ptr } { i32 65535, ptr @__GLOBAL_init_65535, ptr null }] -// REGISTER: @llvm.global_dtors = appending global [2 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 100, ptr @__GLOBAL_cleanup_100, ptr null }, { i32, ptr, ptr } { i32 65535, ptr @__GLOBAL_cleanup_65535, ptr null }] +// REGISTER: @llvm.global_ctors = appending global [2 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 101, ptr @__GLOBAL_init_101, ptr null }, { i32, ptr, ptr } { i32 65535, ptr @__GLOBAL_init_65535, ptr null }] +// REGISTER: @llvm.global_dtors = appending global [2 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 101, ptr @__GLOBAL_cleanup_101, ptr null }, { i32, ptr, ptr } { i32 65535, ptr @__GLOBAL_cleanup_65535, ptr null }] -// REGISTER: define internal void @__GLOBAL_init_100() [[ATTR:#[0-9]+]] { +// REGISTER: define internal void @__GLOBAL_init_101() [[ATTR:#[0-9]+]] { // REGISTER: entry: // REGISTER: %0 = call i32 @atexit(ptr @bar) // REGISTER: ret void @@ -42,11 +37,10 @@ int bar3(int a) { // REGISTER: define internal void @__GLOBAL_init_65535() [[ATTR:#[0-9]+]] { // REGISTER: entry: // REGISTER: %0 = call i32 @atexit(ptr @bar2) -// REGISTER: %1 = call i32 @atexit(ptr @bar3) // REGISTER: ret void // REGISTER: } -// REGISTER: define internal void @__GLOBAL_cleanup_100() [[ATTR:#[0-9]+]] { +// REGISTER: define internal void @__GLOBAL_cleanup_101() [[ATTR:#[0-9]+]] { // REGISTER: entry: // REGISTER: %0 = call i32 @unatexit(ptr @bar) // REGISTER: %needs_destruct = icmp eq i32 %0, 0 @@ -62,20 +56,11 @@ int bar3(int a) { // REGISTER: define internal void @__GLOBAL_cleanup_65535() [[ATTR:#[0-9]+]] { // REGISTER: entry: -// REGISTER: %0 = call i32 @unatexit(ptr @bar3) +// REGISTER: %0 = call i32 @unatexit(ptr @bar2) // REGISTER: %needs_destruct = icmp eq i32 %0, 0 -// REGISTER: br i1 %needs_destruct, label %destruct.call, label %unatexit.call +// REGISTER: br i1 %needs_destruct, label %destruct.call, label %destruct.end // REGISTER: destruct.call: -// REGISTER: call void @bar3() -// REGISTER: br label %unatexit.call - -// REGISTER: unatexit.call: -// REGISTER: %1 = call i32 @unatexit(ptr @bar2) -// REGISTER: %needs_destruct1 = icmp eq i32 %1, 0 -// REGISTER: br i1 %needs_destruct1, label %destruct.call2, label %destruct.end - -// REGISTER: destruct.call2: // REGISTER: call void @bar2() // REGISTER: br label %destruct.end diff --git a/clang/test/CodeGenCXX/aix-destructor-attribute.cpp b/clang/test/CodeGenCXX/aix-destructor-attribute.cpp index 5ebea7a997c9d..2cdf147af8d07 100644 --- a/clang/test/CodeGenCXX/aix-destructor-attribute.cpp +++ b/clang/test/CodeGenCXX/aix-destructor-attribute.cpp @@ -17,9 +17,8 @@ struct test { ~test(); } t; -int bar() __attribute__((destructor(100))); +int bar() __attribute__((destructor(101))); int bar2() __attribute__((destructor(65535))); -int bar3(int) __attribute__((destructor(65535))); int bar() { return 1; @@ -29,17 +28,13 @@ int bar2() { return 2; } -int bar3(int a) { - return a; -} - // NO-REGISTER: @llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @_GLOBAL__sub_I__, ptr null }] -// NO-REGISTER: @llvm.global_dtors = appending global [4 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 100, ptr @_Z3barv, ptr null }, { i32, ptr, ptr } { i32 65535, ptr @_Z4bar2v, ptr null }, { i32, ptr, ptr } { i32 65535, ptr @_Z4bar3i, ptr null }, { i32, ptr, ptr } { i32 65535, ptr @_GLOBAL__D_a, ptr null }] +// NO-REGISTER: @llvm.global_dtors = appending global [3 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 101, ptr @_Z3barv, ptr null }, { i32, ptr, ptr } { i32 65535, ptr @_Z4bar2v, ptr null }, { i32, ptr, ptr } { i32 65535, ptr @_GLOBAL__D_a, ptr null }] -// REGISTER: @llvm.global_ctors = appending global [3 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @_GLOBAL__sub_I__, ptr null }, { i32, ptr, ptr } { i32 100, ptr @__GLOBAL_init_100, ptr null }, { i32, ptr, ptr } { i32 65535, ptr @__GLOBAL_init_65535, ptr null }] -// REGISTER: @llvm.global_dtors = appending global [3 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @_GLOBAL__D_a, ptr null }, { i32, ptr, ptr } { i32 100, ptr @__GLOBAL_cleanup_100, ptr null }, { i32, ptr, ptr } { i32 65535, ptr @__GLOBAL_cleanup_65535, ptr null }] +// REGISTER: @llvm.global_ctors = appending global [3 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @_GLOBAL__sub_I__, ptr null }, { i32, ptr, ptr } { i32 101, ptr @__GLOBAL_init_101, ptr null }, { i32, ptr, ptr } { i32 65535, ptr @__GLOBAL_init_65535, ptr null }] +// REGISTER: @llvm.global_dtors = appending global [3 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @_GLOBAL__D_a, ptr null }, { i32, ptr, ptr } { i32 101, ptr @__GLOBAL_cleanup_101, ptr null }, { i32, ptr, ptr } { i32 65535, ptr @__GLOBAL_cleanup_65535, ptr null }] -// REGISTER: define internal void @__GLOBAL_init_100() [[ATTR:#[0-9]+]] { +// REGISTER: define internal void @__GLOBAL_init_101() [[ATTR:#[0-9]+]] { // REGISTER: entry: // REGISTER: %0 = call i32 @atexit(ptr @_Z3barv) // REGISTER: ret void @@ -48,11 +43,10 @@ int bar3(int a) { // REGISTER: define internal void @__GLOBAL_init_65535() [[ATTR:#[0-9]+]] { // REGISTER: entry: // REGISTER: %0 = call i32 @atexit(ptr @_Z4bar2v) -// REGISTER: %1 = call i32 @atexit(ptr @_Z4bar3i) // REGISTER: ret void // REGISTER: } -// REGISTER: define internal void @__GLOBAL_cleanup_100() [[ATTR:#[0-9]+]] { +// REGISTER: define internal void @__GLOBAL_cleanup_101() [[ATTR:#[0-9]+]] { // REGISTER: entry: // REGISTER: %0 = call i32 @unatexit(ptr @_Z3barv) // REGISTER: %needs_destruct = icmp eq i32 %0, 0 @@ -68,20 +62,11 @@ int bar3(int a) { // REGISTER: define internal void @__GLOBAL_cleanup_65535() [[ATTR:#[0-9]+]] { // REGISTER: entry: -// REGISTER: %0 = call i32 @unatexit(ptr @_Z4bar3i) +// REGISTER: %0 = call i32 @unatexit(ptr @_Z4bar2v) // REGISTER: %needs_destruct = icmp eq i32 %0, 0 -// REGISTER: br i1 %needs_destruct, label %destruct.call, label %unatexit.call +// REGISTER: br i1 %needs_destruct, label %destruct.call, label %destruct.end // REGISTER: destruct.call: -// REGISTER: call void @_Z4bar3i() -// REGISTER: br label %unatexit.call - -// REGISTER: unatexit.call: -// REGISTER: %1 = call i32 @unatexit(ptr @_Z4bar2v) -// REGISTER: %needs_destruct1 = icmp eq i32 %1, 0 -// REGISTER: br i1 %needs_destruct1, label %destruct.call2, label %destruct.end - -// REGISTER: destruct.call2: // REGISTER: call void @_Z4bar2v() // REGISTER: br label %destruct.end diff --git a/clang/test/Sema/constructor-attribute.c b/clang/test/Sema/constructor-attribute.c index 2317c7735bda5..4d91f84af103a 100644 --- a/clang/test/Sema/constructor-attribute.c +++ b/clang/test/Sema/constructor-attribute.c @@ -1,16 +1,59 @@ // RUN: %clang_cc1 -fsyntax-only -verify -Wno-strict-prototypes %s +// RUN: %clang_cc1 -fsyntax-only -verify -x c++ -std=c++20 %s -int x __attribute__((constructor)); // expected-warning {{'constructor' attribute only applies to functions}} -int f(void) __attribute__((constructor)); -int f(void) __attribute__((constructor(1))); -int f(void) __attribute__((constructor(1,2))); // expected-error {{'constructor' attribute takes no more than 1 argument}} -int f(void) __attribute__((constructor(1.0))); // expected-error {{'constructor' attribute requires an integer constant}} -int f(void) __attribute__((constructor(0x100000000))); // expected-error {{integer constant expression evaluates to value 4294967296 that cannot be represented in a 32-bit unsigned integer type}} +int x1 __attribute__((constructor)); // expected-warning {{'constructor' attribute only applies to functions}} +void f(void) __attribute__((constructor)); +void f(void) __attribute__((constructor(1))); // expected-error {{'constructor' attribute requires integer constant between 101 and 65535 inclusive}} +void f(void) __attribute__((constructor(1,2))); // expected-error {{'constructor' attribute takes no more than 1 argument}} +void f(void) __attribute__((constructor(1.0))); // expected-error {{'constructor' attribute requires an integer constant}} +void f(void) __attribute__((constructor(0x100000000))); // expected-error {{integer constant expression evaluates to value 4294967296 that cannot be represented in a 32-bit unsigned integer type}} +void f(void) __attribute__((constructor(101))); -int x __attribute__((destructor)); // expected-warning {{'destructor' attribute only applies to functions}} -int f(void) __attribute__((destructor)); -int f(void) __attribute__((destructor(1))); -int f(void) __attribute__((destructor(1,2))); // expected-error {{'destructor' attribute takes no more than 1 argument}} -int f(void) __attribute__((destructor(1.0))); // expected-error {{'destructor' attribute requires an integer constant}} +int x2 __attribute__((destructor)); // expected-warning {{'destructor' attribute only applies to functions}} +void f(void) __attribute__((destructor)); +void f(void) __attribute__((destructor(1))); // expected-error {{'destructor' attribute requires integer constant between 101 and 65535 inclusive}} +void f(void) __attribute__((destructor(1,2))); // expected-error {{'destructor' attribute takes no more than 1 argument}} +void f(void) __attribute__((destructor(1.0))); // expected-error {{'destructor' attribute requires an integer constant}} +void f(void) __attribute__((destructor(101))); -void knr() __attribute__((constructor)); +void knr1() __attribute__((constructor)); +void knr2() __attribute__((destructor)); + +// Require a void or (unsigned) int return type +int g0(void) __attribute__((constructor)); +signed int g1(void) __attribute__((constructor)); +float g2(void) __attribute__((constructor)); // expected-error {{'constructor' attribute can only be applied to a function which accepts no arguments and has a 'void' or 'int' return type}} +int h0(void) __attribute__((destructor)); +unsigned int h1(void) __attribute__((destructor)); +float h2(void) __attribute__((destructor)); // expected-error {{'destructor' attribute can only be applied to a function which accepts no arguments and has a 'void' or 'int' return type}} + +// Require no parameters +void i(int v) __attribute__((constructor)); // expected-error {{'constructor' attribute can only be applied to a function which accepts no arguments and has a 'void' or 'int' return type}} +void j(int v) __attribute__((destructor)); // expected-error {{'destructor' attribute can only be applied to a function which accepts no arguments and has a 'void' or 'int' return type}} + +#ifdef __cplusplus +struct S { + // Not allowed on a nonstatic member function, but is allowed on a static + // member function so long as it has no args/void return type. + void mem1() __attribute__((constructor)); // expected-error {{'constructor' attribute cannot be applied to a member function}} + void mem2() __attribute__((destructor)); // expected-error {{'destructor' attribute cannot be applied to a member function}} + + static signed nonmem1() __attribute__((constructor)); + static unsigned nonmem2() __attribute__((destructor)); + + static _BitInt(32) nonmem3() __attribute__((constructor)); // expected-error {{'constructor' attribute can only be applied to a function which accepts no arguments and has a 'void' or 'int' return type}} + static char nonmem4() __attribute__((destructor)); // expected-error {{'destructor' attribute can only be applied to a function which accepts no arguments and has a 'void' or 'int' return type}} + + static void nonmem5(int) __attribute__((constructor)); // expected-error {{'constructor' attribute can only be applied to a function which accepts no arguments and has a 'void' or 'int' return type}} + static void nonmem6(int) __attribute__((destructor)); // expected-error {{'destructor' attribute can only be applied to a function which accepts no arguments and has a 'void' or 'int' return type}} +}; + +consteval void consteval_func1() __attribute__((constructor)); // expected-error {{'constructor' attribute cannot be applied to a 'consteval' function}} +consteval void consteval_func2() __attribute__((destructor)); // expected-error {{'destructor' attribute cannot be applied to a 'consteval' function}} +#endif // __cplusplus + +# 1 "source.c" 1 3 +// Can use reserved priorities within a system header +void f(void) __attribute__((constructor(1))); +void f(void) __attribute__((destructor(1))); +# 1 "source.c" 2