Skip to content

Diagnose problematic uses of constructor/destructor attribute #67360

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Sep 26, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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: <https://github.com/llvm/llvm-project/issues/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
-----------------------------------
Expand Down
6 changes: 4 additions & 2 deletions clang/include/clang/Basic/AttrDocs.td
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -2864,6 +2864,8 @@ def warn_cxx11_compat_constexpr_body_multiple_return : Warning<
InGroup<CXXPre14Compat>, 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<
Expand Down Expand Up @@ -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<err_alignas_underaligned.Summary>,
InGroup<IgnoredAttributes>;
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">;
Comment on lines +3181 to +3183
Copy link
Member

@nickdesaulniers nickdesaulniers Sep 26, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC, there's a similar restriction for __attribute__((cleanup())) functions. Is there an existing Diag we can reuse here from that?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not that I could find -- the function marked cleanup needs to accept one argument and it has to be a pointer type:

def err_attribute_cleanup_func_must_take_one_arg : Error<
  "'cleanup' function %0 must take 1 parameter">;
def err_attribute_cleanup_func_arg_incompatible_type : Error<
  "'cleanup' function %0 parameter has "
  "%diff{type $ which is incompatible with type $|incompatible type}1,2">;

I looked around for an existing diagnostic to augment but didn't spot any that worked well.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, perhaps what I'm thinking of is that we aught to emit a similar diagnostic for the return type of those functions.

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<
Expand Down
91 changes: 68 additions & 23 deletions clang/lib/Sema/SemaDeclAttr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <typename CtorDtorAttr>
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<FunctionDecl>(D);
QualType RetTy = FD->getReturnType();
if (!(RetTy->isVoidType() ||
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it also make sense to check that the function uses the C calling convention and isn't varargs?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think those restrictions make sense, good call!

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<CXXMethodDecl>(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 <typename AttrTy>
Expand Down Expand Up @@ -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));
}

Expand Down Expand Up @@ -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<ConstructorAttr>(S, D, AL);
break;
case ParsedAttr::AT_Deprecated:
handleDeprecatedAttr(S, D, AL);
break;
case ParsedAttr::AT_Destructor:
handleDestructorAttr(S, D, AL);
handleCtorDtorAttr<DestructorAttr>(S, D, AL);
break;
case ParsedAttr::AT_EnableIf:
handleEnableIfAttr(S, D, AL);
Expand Down
4 changes: 1 addition & 3 deletions clang/test/CodeGen/2008-03-03-CtorAttrType.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
// RUN: %clang_cc1 %s -emit-llvm -o - | grep llvm.global_ctors
int __attribute__((constructor)) foo(void) {
return 0;
}
void __attribute__((constructor)) foo(void) {}
void __attribute__((constructor)) bar(void) {}

20 changes: 6 additions & 14 deletions clang/test/CodeGen/PowerPC/aix-constructor-attribute.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,10 @@

// CHECK: @llvm.global_ctors = appending global [3 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @foo3, ptr null }, { i32, ptr, ptr } { i32 180, ptr @foo2, ptr null }, { i32, ptr, ptr } { i32 180, ptr @foo, ptr null }]

int foo(void) __attribute__((constructor(180)));
int foo2(void) __attribute__((constructor(180)));
int foo3(void) __attribute__((constructor(65535)));
void foo(void) __attribute__((constructor(180)));
void foo2(void) __attribute__((constructor(180)));
void foo3(void) __attribute__((constructor(65535)));

int foo3(void) {
return 3;
}

int foo2(void) {
return 2;
}

int foo(void) {
return 1;
}
void foo3(void) {}
void foo2(void) {}
void foo(void) {}
30 changes: 11 additions & 19 deletions clang/test/CodeGen/PowerPC/aix-destructor-attribute.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,20 @@
// RUN: -fno-use-cxa-atexit -fregister-global-dtors-with-atexit < %s | \
// RUN: FileCheck --check-prefix=REGISTER %s

int bar(void) __attribute__((destructor(100)));
int bar2(void) __attribute__((destructor(65535)));
int bar3(int) __attribute__((destructor(65535)));
void bar(void) __attribute__((destructor(101)));
void bar2(void) __attribute__((destructor(65535)));
void bar3(void) __attribute__((destructor(65535)));

int bar(void) {
return 1;
}
void bar(void) {}
void bar2(void) {}
void bar3(void) {}

int bar2(void) {
return 2;
}
// NO-REGISTER: @llvm.global_dtors = appending global [3 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 101, ptr @bar, ptr null }, { i32, ptr, ptr } { i32 65535, ptr @bar2, ptr null }, { i32, ptr, ptr } { i32 65535, ptr @bar3, ptr null }]

int bar3(int a) {
return a;
}
// 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 }]

// 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 }]

// 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: 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
Expand All @@ -46,7 +38,7 @@ int bar3(int a) {
// 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
Expand Down
20 changes: 6 additions & 14 deletions clang/test/CodeGenCXX/aix-constructor-attribute.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,16 @@
// CHECK: @llvm.global_ctors = appending global [4 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @_Z4foo3v, ptr null }, { i32, ptr, ptr } { i32 180, ptr @_Z4foo2v, ptr null }, { i32, ptr, ptr } { i32 180, ptr @_Z3foov, ptr null }, { i32, ptr, ptr } { i32 65535, ptr @_GLOBAL__sub_I__, ptr null }]
// CHECK: @llvm.global_dtors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @_GLOBAL__D_a, ptr null }]

int foo() __attribute__((constructor(180)));
int foo2() __attribute__((constructor(180)));
int foo3() __attribute__((constructor(65535)));
void foo() __attribute__((constructor(180)));
void foo2() __attribute__((constructor(180)));
void foo3() __attribute__((constructor(65535)));

struct Test {
public:
Test() {}
~Test() {}
} t;

int foo3() {
return 3;
}

int foo2() {
return 2;
}

int foo() {
return 1;
}
void foo3() {}
void foo2() {}
void foo() {}
36 changes: 14 additions & 22 deletions clang/test/CodeGenCXX/aix-destructor-attribute.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,29 +17,21 @@ struct test {
~test();
} t;

int bar() __attribute__((destructor(100)));
int bar2() __attribute__((destructor(65535)));
int bar3(int) __attribute__((destructor(65535)));
void bar() __attribute__((destructor(101)));
void bar2() __attribute__((destructor(65535)));
void bar3() __attribute__((destructor(65535)));

int bar() {
return 1;
}

int bar2() {
return 2;
}

int bar3(int a) {
return a;
}
void bar() {}
void bar2() {}
void bar3() {}

// 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 [4 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 @_Z4bar3v, 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
Expand All @@ -48,11 +40,11 @@ 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: %1 = call i32 @atexit(ptr @_Z4bar3v)
// 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
Expand All @@ -68,12 +60,12 @@ 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 @_Z4bar3v)
// REGISTER: %needs_destruct = icmp eq i32 %0, 0
// REGISTER: br i1 %needs_destruct, label %destruct.call, label %unatexit.call

// REGISTER: destruct.call:
// REGISTER: call void @_Z4bar3i()
// REGISTER: call void @_Z4bar3v()
// REGISTER: br label %unatexit.call

// REGISTER: unatexit.call:
Expand Down
Loading