Skip to content

Commit

Permalink
Diagnose problematic uses of constructor/destructor attribute (#67673)
Browse files Browse the repository at this point in the history
Functions with these attributes will be automatically called before
main() or after main() exits gracefully. In glibc environments, the
constructor function is passed the same arguments as main(), so that
signature is allowed. In all other environments, we require the function
to accept no arguments and either return `void` or `int`. The functions
must use the C calling convention. In C++ language modes, the functions
cannot be a nonstatic member function, or a consteval function.

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, but we downgrade the error to be a warning
which defaults to an error to ease use for implementers like compiler-rt
or libc.

This relands a633a37 with fixes.
  • Loading branch information
AaronBallman committed Oct 11, 2023
1 parent 670034c commit 27ecb63
Show file tree
Hide file tree
Showing 15 changed files with 260 additions and 91 deletions.
5 changes: 5 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,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
10 changes: 8 additions & 2 deletions clang/include/clang/Basic/AttrDocs.td
Original file line number Diff line number Diff line change
Expand Up @@ -7255,8 +7255,14 @@ 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``.
In general, the constructor or destructor function must use the C calling
convention, cannot accept any arguments, and its return type should be
``void``, ``int``, or ``unsigned int``. The latter two types are supported for
historical reasons. On targets with a GNU environment (one which uses glibc),
the signature of the function can also be the same as that of ``main()``.

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
4 changes: 4 additions & 0 deletions clang/include/clang/Basic/DiagnosticGroups.td
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ def EnumConversion : DiagGroup<"enum-conversion",
[EnumEnumConversion,
EnumFloatConversion,
EnumCompareConditional]>;
def InvalidPriority : DiagGroup<"priority-ctor-dtor">;
// For compatibility with GCC.
def : DiagGroup<"prio-ctor-dtor", [InvalidPriority]>;

def ObjCSignedCharBoolImplicitIntConversion :
DiagGroup<"objc-signed-char-bool-implicit-int-conversion">;
def ImplicitIntConversion : DiagGroup<"implicit-int-conversion",
Expand Down
12 changes: 12 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -2870,6 +2870,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 @@ -3182,6 +3184,13 @@ 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">;
def err_ctor_dtor_member_func : Error<
"%0 attribute cannot be applied to a member function">;
def err_ctor_dtor_calling_conv : Error<
"%0 attribute must be applied to a function with the C calling convention">;
def err_attribute_sizeless_type : Error<
"%0 attribute cannot be applied to sizeless type %1">;
def err_attribute_argument_n_type : Error<
Expand All @@ -3195,6 +3204,9 @@ def err_attribute_argument_out_of_range : Error<
def err_init_priority_object_attr : Error<
"can only use 'init_priority' attribute on file-scope definitions "
"of objects of class type">;
def warn_priority_out_of_range : Warning<
err_attribute_argument_out_of_range.Summary>,
InGroup<InvalidPriority>, DefaultError;
def err_attribute_argument_out_of_bounds : Error<
"%0 attribute parameter %1 is out of bounds">;
def err_attribute_only_once_per_parameter : Error<
Expand Down
141 changes: 117 additions & 24 deletions clang/lib/Sema/SemaDeclAttr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2352,26 +2352,126 @@ 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 void 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::warn_priority_out_of_range)
<< PriorityLoc << A << ReservedPriorityLower << ReservedPriorityUpper;
}
}

static bool FunctionParamsAreMainLike(ASTContext &Context,
const FunctionDecl *FD) {
assert(FD->hasPrototype() && "expected the function to have a prototype");
const auto *FPT = FD->getType()->castAs<FunctionProtoType>();
QualType CharPP =
Context.getPointerType(Context.getPointerType(Context.CharTy));
QualType Expected[] = {Context.IntTy, CharPP, CharPP, CharPP};
for (unsigned I = 0;
I < sizeof(Expected) / sizeof(QualType) && I < FPT->getNumParams();
++I) {
QualType AT = FPT->getParamType(I);

if (!Context.hasSameUnqualifiedType(AT, Expected[I])) {
if (Expected[I] == CharPP) {
// As an extension, the following forms are okay:
// char const **
// char const * const *
// char * const *

QualifierCollector Qs;
const PointerType *PT;
if ((PT = Qs.strip(AT)->getAs<PointerType>()) &&
(PT = Qs.strip(PT->getPointeeType())->getAs<PointerType>()) &&
Context.hasSameType(QualType(Qs.strip(PT->getPointeeType()), 0),
Context.CharTy)) {
Qs.removeConst();
if (!Qs.empty())
return false;
continue; // Accepted as an extension.
}
}
return false;
}
}
return true;
}

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;

// Diagnose an invalid priority, but continue to process the attribute.
diagnoseInvalidPriority(S, Priority, AL, AL.getArgAsExpr(0)->getExprLoc());
}

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 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.
//
// However! glibc on ELF will pass the same arguments to a constructor
// function as are given to main(), so we will allow `int, char *[]` and
// `int, char *[], char *[]` (or qualified versions thereof), but only if
// the target is explicitly for glibc.
const auto *FD = cast<FunctionDecl>(D);
QualType RetTy = FD->getReturnType();
bool IsGlibC = S.Context.getTargetInfo().getTriple().isGNUEnvironment();
if (!(RetTy->isVoidType() ||
RetTy->isSpecificBuiltinType(BuiltinType::UInt) ||
RetTy->isSpecificBuiltinType(BuiltinType::Int)) ||
FD->isVariadic() ||
(FD->hasPrototype() &&
((!IsGlibC && FD->getNumParams() != 0) ||
(IsGlibC && !FunctionParamsAreMainLike(S.Context, FD))))) {
S.Diag(AL.getLoc(), diag::err_ctor_dtor_attr_on_non_void_func)
<< AL << FD->getSourceRange();
return;
}
if (FD->getType()->castAs<FunctionType>()->getCallConv() !=
CallingConv::CC_C) {
S.Diag(AL.getLoc(), diag::err_ctor_dtor_calling_conv)
<< AL << FD->getSourceRange();
return;
}
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;
}
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 +3988,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();
return;
}
// Diagnose an invalid priority, but continue to process the attribute.
diagnoseInvalidPriority(S, prioritynum, AL, E->getExprLoc());

D->addAttr(::new (S.Context) InitPriorityAttr(S.Context, AL, prioritynum));
}

Expand Down Expand Up @@ -8959,13 +9052,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
31 changes: 8 additions & 23 deletions clang/test/CodeGen/PowerPC/aix-destructor-attribute.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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

Expand Down
31 changes: 8 additions & 23 deletions clang/test/CodeGenCXX/aix-destructor-attribute.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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

Expand Down
10 changes: 10 additions & 0 deletions clang/test/Sema/constructor-attribute-diag-group.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// RUN: %clang_cc1 -fsyntax-only -verify=err %s
// RUN: %clang_cc1 -fsyntax-only -verify=warn -Wno-error=priority-ctor-dtor %s
// RUN: %clang_cc1 -fsyntax-only -verify=okay -Wno-priority-ctor-dtor %s
// RUN: %clang_cc1 -fsyntax-only -verify=okay -Wno-prio-ctor-dtor %s
// okay-no-diagnostics

void f(void) __attribute__((constructor(1))); // warn-warning {{'constructor' attribute requires integer constant between 101 and 65535 inclusive}} \
err-error {{'constructor' attribute requires integer constant between 101 and 65535 inclusive}}
void f(void) __attribute__((destructor(1))); // warn-warning {{'destructor' attribute requires integer constant between 101 and 65535 inclusive}} \
err-error {{'destructor' attribute requires integer constant between 101 and 65535 inclusive}}

0 comments on commit 27ecb63

Please sign in to comment.