Skip to content

Commit b443510

Browse files
authored
Diagnose problematic uses of constructor/destructor attribute (#67360)
Functions with these attributes will be automatically called before `main()` or after `main()` exits gracefully, which means the functions should not accept arguments or have a returned value (nothing can provide an argument to the call in these cases, and nothing can use the returned value), nor should they be allowed on a non-static member function or consteval function in C++. We allow 'int' as a return type for the function due to finding a significant amount of historical code using `int(void)` as a signature. Additionally, these reuse the same priority logic as the init_priority attribute which explicitly reserved priorty values <= 100 or > 65535. So we now diagnose use of reserved priorities the same as we do for the init_priority attribute.
1 parent 6716d3d commit b443510

File tree

7 files changed

+155
-83
lines changed

7 files changed

+155
-83
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,11 @@ Attribute Changes in Clang
171171
automatic diagnostic to use parameters of types that the format style
172172
supports but that are never the result of default argument promotion, such as
173173
``float``. (`#59824: <https://github.com/llvm/llvm-project/issues/59824>`_)
174+
- The ``constructor`` and ``destructor`` attributes now diagnose when:
175+
- the priority is not between 101 and 65535, inclusive,
176+
- the function it is applied to accepts arguments or has a non-void return
177+
type, or
178+
- the function it is applied to is a non-static member function (C++).
174179

175180
Improvements to Clang's diagnostics
176181
-----------------------------------

clang/include/clang/Basic/AttrDocs.td

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7251,8 +7251,10 @@ after returning from ``main()`` or when the ``exit()`` function has been
72517251
called. Note, ``quick_exit()``, ``_Exit()``, and ``abort()`` prevent a function
72527252
marked ``destructor`` from being called.
72537253

7254-
The constructor or destructor function should not accept any arguments and its
7255-
return type should be ``void``.
7254+
The constructor or destructor function cannot accept any arguments and its
7255+
return type should be ``void``, ``int``, or ``unsigned int``. The latter two
7256+
types are supported for historical reasons. In C++ language modes, the function
7257+
cannot be marked ``consteval``, nor can it be a non-static member function.
72567258

72577259
The attributes accept an optional argument used to specify the priority order
72587260
in which to execute constructor and destructor functions. The priority is

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2864,6 +2864,8 @@ def warn_cxx11_compat_constexpr_body_multiple_return : Warning<
28642864
InGroup<CXXPre14Compat>, DefaultIgnore;
28652865
def note_constexpr_body_previous_return : Note<
28662866
"previous return statement is here">;
2867+
def err_ctordtor_attr_consteval : Error<
2868+
"%0 attribute cannot be applied to a 'consteval' function">;
28672869

28682870
// C++20 function try blocks in constexpr
28692871
def ext_constexpr_function_try_block_cxx20 : ExtWarn<
@@ -3176,6 +3178,11 @@ def err_alignas_underaligned : Error<
31763178
"requested alignment is less than minimum alignment of %1 for type %0">;
31773179
def warn_aligned_attr_underaligned : Warning<err_alignas_underaligned.Summary>,
31783180
InGroup<IgnoredAttributes>;
3181+
def err_ctor_dtor_attr_on_non_void_func : Error<
3182+
"%0 attribute can only be applied to a function which accepts no arguments "
3183+
"and has a 'void' or 'int' return type">;
3184+
def err_ctor_dtor_member_func : Error<
3185+
"%0 attribute cannot be applied to a member function">;
31793186
def err_attribute_sizeless_type : Error<
31803187
"%0 attribute cannot be applied to sizeless type %1">;
31813188
def err_attribute_argument_n_type : Error<

clang/lib/Sema/SemaDeclAttr.cpp

Lines changed: 68 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2352,26 +2352,78 @@ static void handleUnusedAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
23522352
D->addAttr(::new (S.Context) UnusedAttr(S.Context, AL));
23532353
}
23542354

2355-
static void handleConstructorAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
2356-
uint32_t priority = ConstructorAttr::DefaultPriority;
2355+
static bool diagnoseInvalidPriority(Sema &S, uint32_t Priority,
2356+
const ParsedAttr &A,
2357+
SourceLocation PriorityLoc) {
2358+
constexpr uint32_t ReservedPriorityLower = 101, ReservedPriorityUpper = 65535;
2359+
2360+
// Only perform the priority check if the attribute is outside of a system
2361+
// header. Values <= 100 are reserved for the implementation, and libc++
2362+
// benefits from being able to specify values in that range. Values > 65535
2363+
// are reserved for historical reasons.
2364+
if ((Priority < ReservedPriorityLower || Priority > ReservedPriorityUpper) &&
2365+
!S.getSourceManager().isInSystemHeader(A.getLoc())) {
2366+
S.Diag(A.getLoc(), diag::err_attribute_argument_out_of_range)
2367+
<< PriorityLoc << A << ReservedPriorityLower << ReservedPriorityUpper;
2368+
A.setInvalid();
2369+
return true;
2370+
}
2371+
return false;
2372+
}
2373+
2374+
template <typename CtorDtorAttr>
2375+
static void handleCtorDtorAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
2376+
uint32_t Priority = CtorDtorAttr::DefaultPriority;
23572377
if (S.getLangOpts().HLSL && AL.getNumArgs()) {
23582378
S.Diag(AL.getLoc(), diag::err_hlsl_init_priority_unsupported);
23592379
return;
23602380
}
2361-
if (AL.getNumArgs() &&
2362-
!checkUInt32Argument(S, AL, AL.getArgAsExpr(0), priority))
2363-
return;
23642381

2365-
D->addAttr(::new (S.Context) ConstructorAttr(S.Context, AL, priority));
2366-
}
2382+
// If we're given an argument for the priority, check that it's valid.
2383+
if (AL.getNumArgs()) {
2384+
if (!checkUInt32Argument(S, AL, AL.getArgAsExpr(0), Priority))
2385+
return;
23672386

2368-
static void handleDestructorAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
2369-
uint32_t priority = DestructorAttr::DefaultPriority;
2370-
if (AL.getNumArgs() &&
2371-
!checkUInt32Argument(S, AL, AL.getArgAsExpr(0), priority))
2387+
// Ensure the priority is in a reasonable range.
2388+
if (diagnoseInvalidPriority(S, Priority, AL,
2389+
AL.getArgAsExpr(0)->getExprLoc()))
2390+
return;
2391+
}
2392+
2393+
// Ensure the function we're attaching to is something that is sensible to
2394+
// automatically call before or after main(); it should accept no arguments.
2395+
// In theory, a void return type is the only truly safe return type (consider
2396+
// that calling conventions may place returned values in a hidden pointer
2397+
// argument passed to the function that will not be present when called
2398+
// automatically). However, there is a significant amount of existing code
2399+
// which uses an int return type. So we will accept void, int, and
2400+
// unsigned int return types. Any other return type, or a non-void parameter
2401+
// list is treated as an error because it's a form of type system
2402+
// incompatibility. The function also cannot be a member function. We allow
2403+
// K&R C functions because that's a difficult edge case where it depends on
2404+
// how the function is defined as to whether it does or does not expect
2405+
// arguments.
2406+
const auto *FD = cast<FunctionDecl>(D);
2407+
QualType RetTy = FD->getReturnType();
2408+
if (!(RetTy->isVoidType() ||
2409+
RetTy->isSpecificBuiltinType(BuiltinType::UInt) ||
2410+
RetTy->isSpecificBuiltinType(BuiltinType::Int)) ||
2411+
(FD->hasPrototype() && FD->getNumParams() != 0)) {
2412+
S.Diag(AL.getLoc(), diag::err_ctor_dtor_attr_on_non_void_func)
2413+
<< AL << FD->getSourceRange();
23722414
return;
2415+
} else if (const auto *MD = dyn_cast<CXXMethodDecl>(FD);
2416+
MD && MD->isInstance()) {
2417+
S.Diag(AL.getLoc(), diag::err_ctor_dtor_member_func)
2418+
<< AL << FD->getSourceRange();
2419+
return;
2420+
} else if (FD->isConsteval()) {
2421+
S.Diag(AL.getLoc(), diag::err_ctordtor_attr_consteval)
2422+
<< AL << FD->getSourceRange();
2423+
return;
2424+
}
23732425

2374-
D->addAttr(::new (S.Context) DestructorAttr(S.Context, AL, priority));
2426+
D->addAttr(CtorDtorAttr::Create(S.Context, Priority, AL));
23752427
}
23762428

23772429
template <typename AttrTy>
@@ -3888,16 +3940,9 @@ static void handleInitPriorityAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
38883940
return;
38893941
}
38903942

3891-
// Only perform the priority check if the attribute is outside of a system
3892-
// header. Values <= 100 are reserved for the implementation, and libc++
3893-
// benefits from being able to specify values in that range.
3894-
if ((prioritynum < 101 || prioritynum > 65535) &&
3895-
!S.getSourceManager().isInSystemHeader(AL.getLoc())) {
3896-
S.Diag(AL.getLoc(), diag::err_attribute_argument_out_of_range)
3897-
<< E->getSourceRange() << AL << 101 << 65535;
3898-
AL.setInvalid();
3943+
if (diagnoseInvalidPriority(S, prioritynum, AL, E->getExprLoc()))
38993944
return;
3900-
}
3945+
39013946
D->addAttr(::new (S.Context) InitPriorityAttr(S.Context, AL, prioritynum));
39023947
}
39033948

@@ -8930,13 +8975,13 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
89308975
handlePassObjectSizeAttr(S, D, AL);
89318976
break;
89328977
case ParsedAttr::AT_Constructor:
8933-
handleConstructorAttr(S, D, AL);
8978+
handleCtorDtorAttr<ConstructorAttr>(S, D, AL);
89348979
break;
89358980
case ParsedAttr::AT_Deprecated:
89368981
handleDeprecatedAttr(S, D, AL);
89378982
break;
89388983
case ParsedAttr::AT_Destructor:
8939-
handleDestructorAttr(S, D, AL);
8984+
handleCtorDtorAttr<DestructorAttr>(S, D, AL);
89408985
break;
89418986
case ParsedAttr::AT_EnableIf:
89428987
handleEnableIfAttr(S, D, AL);

clang/test/CodeGen/PowerPC/aix-destructor-attribute.c

Lines changed: 8 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,8 @@
1212
// RUN: -fno-use-cxa-atexit -fregister-global-dtors-with-atexit < %s | \
1313
// RUN: FileCheck --check-prefix=REGISTER %s
1414

15-
int bar(void) __attribute__((destructor(100)));
15+
int bar(void) __attribute__((destructor(101)));
1616
int bar2(void) __attribute__((destructor(65535)));
17-
int bar3(int) __attribute__((destructor(65535)));
1817

1918
int bar(void) {
2019
return 1;
@@ -24,16 +23,12 @@ int bar2(void) {
2423
return 2;
2524
}
2625

27-
int bar3(int a) {
28-
return a;
29-
}
30-
31-
// 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 }]
26+
// 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 }]
3227

33-
// 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 }]
34-
// 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 }]
28+
// 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 }]
29+
// 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 }]
3530

36-
// REGISTER: define internal void @__GLOBAL_init_100() [[ATTR:#[0-9]+]] {
31+
// REGISTER: define internal void @__GLOBAL_init_101() [[ATTR:#[0-9]+]] {
3732
// REGISTER: entry:
3833
// REGISTER: %0 = call i32 @atexit(ptr @bar)
3934
// REGISTER: ret void
@@ -42,11 +37,10 @@ int bar3(int a) {
4237
// REGISTER: define internal void @__GLOBAL_init_65535() [[ATTR:#[0-9]+]] {
4338
// REGISTER: entry:
4439
// REGISTER: %0 = call i32 @atexit(ptr @bar2)
45-
// REGISTER: %1 = call i32 @atexit(ptr @bar3)
4640
// REGISTER: ret void
4741
// REGISTER: }
4842

49-
// REGISTER: define internal void @__GLOBAL_cleanup_100() [[ATTR:#[0-9]+]] {
43+
// REGISTER: define internal void @__GLOBAL_cleanup_101() [[ATTR:#[0-9]+]] {
5044
// REGISTER: entry:
5145
// REGISTER: %0 = call i32 @unatexit(ptr @bar)
5246
// REGISTER: %needs_destruct = icmp eq i32 %0, 0
@@ -62,20 +56,11 @@ int bar3(int a) {
6256

6357
// REGISTER: define internal void @__GLOBAL_cleanup_65535() [[ATTR:#[0-9]+]] {
6458
// REGISTER: entry:
65-
// REGISTER: %0 = call i32 @unatexit(ptr @bar3)
59+
// REGISTER: %0 = call i32 @unatexit(ptr @bar2)
6660
// REGISTER: %needs_destruct = icmp eq i32 %0, 0
67-
// REGISTER: br i1 %needs_destruct, label %destruct.call, label %unatexit.call
61+
// REGISTER: br i1 %needs_destruct, label %destruct.call, label %destruct.end
6862

6963
// REGISTER: destruct.call:
70-
// REGISTER: call void @bar3()
71-
// REGISTER: br label %unatexit.call
72-
73-
// REGISTER: unatexit.call:
74-
// REGISTER: %1 = call i32 @unatexit(ptr @bar2)
75-
// REGISTER: %needs_destruct1 = icmp eq i32 %1, 0
76-
// REGISTER: br i1 %needs_destruct1, label %destruct.call2, label %destruct.end
77-
78-
// REGISTER: destruct.call2:
7964
// REGISTER: call void @bar2()
8065
// REGISTER: br label %destruct.end
8166

clang/test/CodeGenCXX/aix-destructor-attribute.cpp

Lines changed: 8 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,8 @@ struct test {
1717
~test();
1818
} t;
1919

20-
int bar() __attribute__((destructor(100)));
20+
int bar() __attribute__((destructor(101)));
2121
int bar2() __attribute__((destructor(65535)));
22-
int bar3(int) __attribute__((destructor(65535)));
2322

2423
int bar() {
2524
return 1;
@@ -29,17 +28,13 @@ int bar2() {
2928
return 2;
3029
}
3130

32-
int bar3(int a) {
33-
return a;
34-
}
35-
3631
// NO-REGISTER: @llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @_GLOBAL__sub_I__, ptr null }]
37-
// 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 }]
32+
// 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 }]
3833

39-
// 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 }]
40-
// 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 }]
34+
// 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 }]
35+
// 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 }]
4136

42-
// REGISTER: define internal void @__GLOBAL_init_100() [[ATTR:#[0-9]+]] {
37+
// REGISTER: define internal void @__GLOBAL_init_101() [[ATTR:#[0-9]+]] {
4338
// REGISTER: entry:
4439
// REGISTER: %0 = call i32 @atexit(ptr @_Z3barv)
4540
// REGISTER: ret void
@@ -48,11 +43,10 @@ int bar3(int a) {
4843
// REGISTER: define internal void @__GLOBAL_init_65535() [[ATTR:#[0-9]+]] {
4944
// REGISTER: entry:
5045
// REGISTER: %0 = call i32 @atexit(ptr @_Z4bar2v)
51-
// REGISTER: %1 = call i32 @atexit(ptr @_Z4bar3i)
5246
// REGISTER: ret void
5347
// REGISTER: }
5448

55-
// REGISTER: define internal void @__GLOBAL_cleanup_100() [[ATTR:#[0-9]+]] {
49+
// REGISTER: define internal void @__GLOBAL_cleanup_101() [[ATTR:#[0-9]+]] {
5650
// REGISTER: entry:
5751
// REGISTER: %0 = call i32 @unatexit(ptr @_Z3barv)
5852
// REGISTER: %needs_destruct = icmp eq i32 %0, 0
@@ -68,20 +62,11 @@ int bar3(int a) {
6862

6963
// REGISTER: define internal void @__GLOBAL_cleanup_65535() [[ATTR:#[0-9]+]] {
7064
// REGISTER: entry:
71-
// REGISTER: %0 = call i32 @unatexit(ptr @_Z4bar3i)
65+
// REGISTER: %0 = call i32 @unatexit(ptr @_Z4bar2v)
7266
// REGISTER: %needs_destruct = icmp eq i32 %0, 0
73-
// REGISTER: br i1 %needs_destruct, label %destruct.call, label %unatexit.call
67+
// REGISTER: br i1 %needs_destruct, label %destruct.call, label %destruct.end
7468

7569
// REGISTER: destruct.call:
76-
// REGISTER: call void @_Z4bar3i()
77-
// REGISTER: br label %unatexit.call
78-
79-
// REGISTER: unatexit.call:
80-
// REGISTER: %1 = call i32 @unatexit(ptr @_Z4bar2v)
81-
// REGISTER: %needs_destruct1 = icmp eq i32 %1, 0
82-
// REGISTER: br i1 %needs_destruct1, label %destruct.call2, label %destruct.end
83-
84-
// REGISTER: destruct.call2:
8570
// REGISTER: call void @_Z4bar2v()
8671
// REGISTER: br label %destruct.end
8772

0 commit comments

Comments
 (0)