Skip to content

Commit 576161c

Browse files
authored
[C] Warn on uninitialized const objects (#137166)
Unlike C++, C allows the definition of an uninitialized `const` object. If the object has static or thread storage duration, it is still zero-initialized, otherwise, the object is left uninitialized. In either case, the code is not compatible with C++. This adds a new diagnostic group, `-Wdefault-const-init-unsafe`, which is on by default and diagnoses any definition of a `const` object which remains uninitialized. It also adds another new diagnostic group, `-Wdefault-const-init` (which also enabled the `unsafe` variant) that diagnoses any definition of a `const` object (including ones which are zero-initialized). This diagnostic is off by default. Finally, it adds `-Wdefault-const-init` to `-Wc++-compat`. GCC diagnoses these situations under this flag. Fixes #19297
1 parent 320ec7f commit 576161c

28 files changed

+179
-44
lines changed

clang/docs/ReleaseNotes.rst

+7
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,13 @@ C Language Changes
140140
- Clang now allows an ``inline`` specifier on a typedef declaration of a
141141
function type in Microsoft compatibility mode. #GH124869
142142
- Clang now allows ``restrict`` qualifier for array types with pointer elements (#GH92847).
143+
- Clang now diagnoses ``const``-qualified object definitions without an
144+
initializer. If the object is zero-initialized, it will be diagnosed under
145+
the new warning ``-Wdefault-const-init`` (which is grouped under
146+
``-Wc++-compat`` because this construct is not compatible with C++). If the
147+
object is left uninitialized, it will be diagnosed unsed the new warning
148+
``-Wdefault-const-init-unsafe`` (which is grouped under
149+
``-Wdefault-const-init``). #GH19297
143150
- Added ``-Wimplicit-void-ptr-cast``, grouped under ``-Wc++-compat``, which
144151
diagnoses implicit conversion from ``void *`` to another pointer type as
145152
being incompatible with C++. (#GH17792)

clang/include/clang/Basic/DiagnosticGroups.td

+4-2
Original file line numberDiff line numberDiff line change
@@ -154,9 +154,11 @@ def BuiltinRequiresHeader : DiagGroup<"builtin-requires-header">;
154154
def C99Compat : DiagGroup<"c99-compat">;
155155
def C23Compat : DiagGroup<"c23-compat">;
156156
def : DiagGroup<"c2x-compat", [C23Compat]>;
157-
157+
def DefaultConstInitUnsafe : DiagGroup<"default-const-init-unsafe">;
158+
def DefaultConstInit : DiagGroup<"default-const-init", [DefaultConstInitUnsafe]>;
158159
def ImplicitVoidPtrCast : DiagGroup<"implicit-void-ptr-cast">;
159-
def CXXCompat: DiagGroup<"c++-compat", [ImplicitVoidPtrCast]>;
160+
def CXXCompat: DiagGroup<"c++-compat", [ImplicitVoidPtrCast, DefaultConstInit]>;
161+
160162
def ExternCCompat : DiagGroup<"extern-c-compat">;
161163
def KeywordCompat : DiagGroup<"keyword-compat">;
162164
def GNUCaseRange : DiagGroup<"gnu-case-range">;

clang/include/clang/Basic/DiagnosticSemaKinds.td

+10
Original file line numberDiff line numberDiff line change
@@ -8197,6 +8197,16 @@ def err_address_space_qualified_new : Error<
81978197
def err_address_space_qualified_delete : Error<
81988198
"'delete' cannot delete objects of type %0 in address space '%1'">;
81998199

8200+
def note_default_init_const_member : Note<
8201+
"member %0 declared 'const' here">;
8202+
def warn_default_init_const : Warning<
8203+
"default initialization of an object of type %0%select{| with const member}1 "
8204+
"is incompatible with C++">,
8205+
InGroup<DefaultConstInit>, DefaultIgnore;
8206+
def warn_default_init_const_unsafe : Warning<
8207+
"default initialization of an object of type %0%select{| with const member}1 "
8208+
"leaves the object uninitialized and is incompatible with C++">,
8209+
InGroup<DefaultConstInitUnsafe>;
82008210
def err_default_init_const : Error<
82018211
"default initialization of an object of const type %0"
82028212
"%select{| without a user-provided default constructor}1">;

clang/lib/Parse/ParseStmt.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -2154,7 +2154,7 @@ StmtResult Parser::ParseForStatement(SourceLocation *TrailingElseLoc) {
21542154
FirstPart = Actions.ActOnDeclStmt(DG, DeclStart, Tok.getLocation());
21552155
} else {
21562156
// In C++0x, "for (T NS:a" might not be a typo for ::
2157-
bool MightBeForRangeStmt = getLangOpts().CPlusPlus;
2157+
bool MightBeForRangeStmt = getLangOpts().CPlusPlus || getLangOpts().ObjC;
21582158
ColonProtectionRAIIObject ColonProtection(*this, MightBeForRangeStmt);
21592159
ParsedAttributes DeclSpecAttrs(AttrFactory);
21602160
DG = ParseSimpleDeclaration(

clang/lib/Sema/Sema.cpp

+12
Original file line numberDiff line numberDiff line change
@@ -1448,6 +1448,18 @@ void Sema::ActOnEndOfTranslationUnit() {
14481448
// No initialization is performed for a tentative definition.
14491449
CheckCompleteVariableDeclaration(VD);
14501450

1451+
// In C, if the definition is const-qualified and has no initializer, it
1452+
// is left uninitialized unless it has static or thread storage duration.
1453+
QualType Type = VD->getType();
1454+
if (!VD->isInvalidDecl() && !getLangOpts().CPlusPlus &&
1455+
Type.isConstQualified() && !VD->getAnyInitializer()) {
1456+
unsigned DiagID = diag::warn_default_init_const_unsafe;
1457+
if (VD->getStorageDuration() == SD_Static ||
1458+
VD->getStorageDuration() == SD_Thread)
1459+
DiagID = diag::warn_default_init_const;
1460+
Diag(VD->getLocation(), DiagID) << Type << /*not a field*/ 0;
1461+
}
1462+
14511463
// Notify the consumer that we've completed a tentative definition.
14521464
if (!VD->isInvalidDecl())
14531465
Consumer.CompleteTentativeDefinition(VD);

clang/lib/Sema/SemaDecl.cpp

+10
Original file line numberDiff line numberDiff line change
@@ -14339,6 +14339,16 @@ void Sema::ActOnUninitializedDecl(Decl *RealDecl) {
1433914339
return;
1434014340
}
1434114341

14342+
// In C, if the definition is const-qualified and has no initializer, it
14343+
// is left uninitialized unless it has static or thread storage duration.
14344+
if (!getLangOpts().CPlusPlus && Type.isConstQualified()) {
14345+
unsigned DiagID = diag::warn_default_init_const_unsafe;
14346+
if (Var->getStorageDuration() == SD_Static ||
14347+
Var->getStorageDuration() == SD_Thread)
14348+
DiagID = diag::warn_default_init_const;
14349+
Diag(Var->getLocation(), DiagID) << Type << /*not a field*/ 0;
14350+
}
14351+
1434214352
// Check for jumps past the implicit initializer. C++0x
1434314353
// clarifies that this applies to a "variable with automatic
1434414354
// storage duration", not a "local variable".

clang/lib/Sema/SemaInit.cpp

+35-6
Original file line numberDiff line numberDiff line change
@@ -6495,6 +6495,20 @@ static bool canPerformArrayCopy(const InitializedEntity &Entity) {
64956495
return false;
64966496
}
64976497

6498+
static const FieldDecl *getConstField(const RecordDecl *RD) {
6499+
assert(!isa<CXXRecordDecl>(RD) && "Only expect to call this in C mode");
6500+
for (const FieldDecl *FD : RD->fields()) {
6501+
QualType QT = FD->getType();
6502+
if (QT.isConstQualified())
6503+
return FD;
6504+
if (const auto *RD = QT->getAsRecordDecl()) {
6505+
if (const FieldDecl *FD = getConstField(RD))
6506+
return FD;
6507+
}
6508+
}
6509+
return nullptr;
6510+
}
6511+
64986512
void InitializationSequence::InitializeFrom(Sema &S,
64996513
const InitializedEntity &Entity,
65006514
const InitializationKind &Kind,
@@ -6562,13 +6576,28 @@ void InitializationSequence::InitializeFrom(Sema &S,
65626576

65636577
if (!S.getLangOpts().CPlusPlus &&
65646578
Kind.getKind() == InitializationKind::IK_Default) {
6565-
RecordDecl *Rec = DestType->getAsRecordDecl();
6566-
if (Rec && Rec->hasUninitializedExplicitInitFields()) {
6579+
if (RecordDecl *Rec = DestType->getAsRecordDecl()) {
65676580
VarDecl *Var = dyn_cast_or_null<VarDecl>(Entity.getDecl());
6568-
if (Var && !Initializer) {
6569-
S.Diag(Var->getLocation(), diag::warn_field_requires_explicit_init)
6570-
<< /* Var-in-Record */ 1 << Rec;
6571-
emitUninitializedExplicitInitFields(S, Rec);
6581+
if (Rec->hasUninitializedExplicitInitFields()) {
6582+
if (Var && !Initializer) {
6583+
S.Diag(Var->getLocation(), diag::warn_field_requires_explicit_init)
6584+
<< /* Var-in-Record */ 1 << Rec;
6585+
emitUninitializedExplicitInitFields(S, Rec);
6586+
}
6587+
}
6588+
// If the record has any members which are const (recursively checked),
6589+
// then we want to diagnose those as being uninitialized if there is no
6590+
// initializer present.
6591+
if (!Initializer) {
6592+
if (const FieldDecl *FD = getConstField(Rec)) {
6593+
unsigned DiagID = diag::warn_default_init_const_unsafe;
6594+
if (Var->getStorageDuration() == SD_Static ||
6595+
Var->getStorageDuration() == SD_Thread)
6596+
DiagID = diag::warn_default_init_const;
6597+
6598+
S.Diag(Var->getLocation(), DiagID) << Var->getType() << /*member*/ 1;
6599+
S.Diag(FD->getLocation(), diag::note_default_init_const_member) << FD;
6600+
}
65726601
}
65736602
}
65746603
}

clang/test/C/C23/n2607.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ void test1(void) {
2424
void test2(void) {
2525
typedef int array[1];
2626
array reg_array;
27-
const array const_array;
27+
const array const_array = { 0 };
2828

2929
// An array and its elements are identically qualified. We have to test this
3030
// using pointers to the array and element, because the controlling
@@ -50,7 +50,7 @@ void test2(void) {
5050
void test3(void) {
5151
// Validate that we pick the correct composite type for a conditional
5252
// operator in the presence of qualifiers.
53-
const int const_array[1];
53+
const int const_array[1] = { 0 };
5454
int array[1];
5555

5656
// FIXME: the type here should be `const int (*)[1]`, but for some reason,

clang/test/C/drs/dr1xx.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ void dr124(void) {
289289
*/
290290
void dr126(void) {
291291
typedef int *IP;
292-
const IP object; /* expected-note {{variable 'object' declared const here}} */
292+
const IP object = 0; /* expected-note {{variable 'object' declared const here}} */
293293

294294
/* The root of the DR is whether 'object' is a pointer to a const int, or a
295295
* const pointer to int.
@@ -329,7 +329,7 @@ void dr129(void) {
329329
void dr131(void) {
330330
struct S {
331331
const int i; /* expected-note {{data member 'i' declared const here}} */
332-
} s1, s2;
332+
} s1 = { 0 }, s2 = { 0 };
333333
s1 = s2; /* expected-error {{cannot assign to variable 's1' with const-qualified data member 'i'}} */
334334
}
335335

clang/test/Parser/typeof.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ static void test(void) {
1212
short TInt eee; // expected-error{{expected ';' at end of declaration}}
1313
void ary[7] fff; // expected-error{{array has incomplete element type 'void'}} expected-error{{expected ';' at end of declaration}}
1414
typeof(void ary[7]) anIntError; // expected-error{{expected ')'}} expected-note {{to match this '('}} expected-error {{variable has incomplete type 'typeof(void)' (aka 'void')}}
15-
typeof(const int) aci;
16-
const typeof (*pi) aConstInt;
15+
typeof(const int) aci = 0;
16+
const typeof (*pi) aConstInt = 0;
1717
int xx;
1818
int *i;
1919
}

clang/test/Sema/assign.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ void test2 (const struct {int a;} *x) {
1111

1212
typedef int arr[10];
1313
void test3(void) {
14-
const arr b; // expected-note {{variable 'b' declared const here}}
15-
const int b2[10]; // expected-note {{variable 'b2' declared const here}}
14+
const arr b = {}; // expected-note {{variable 'b' declared const here}}
15+
const int b2[10] = {}; // expected-note {{variable 'b2' declared const here}}
1616
b[4] = 1; // expected-error {{cannot assign to variable 'b' with const-qualified type 'const arr' (aka 'const int[10]')}}
1717
b2[4] = 1; // expected-error {{cannot assign to variable 'b2' with const-qualified type 'const int[10]'}}
1818
}

clang/test/Sema/atomic-ops.c

+7-7
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
// RUN: %clang_cc1 %s -verify=expected,fp80,noi128 -fgnuc-version=4.2.1 -ffreestanding \
2-
// RUN: -fsyntax-only -triple=i686-linux-gnu -std=c11
2+
// RUN: -Wno-default-const-init-unsafe -fsyntax-only -triple=i686-linux-gnu -std=c11
33
// RUN: %clang_cc1 %s -verify=expected,noi128 -fgnuc-version=4.2.1 -ffreestanding \
4-
// RUN: -fsyntax-only -triple=i686-linux-android -std=c11
4+
// RUN: -Wno-default-const-init-unsafe -fsyntax-only -triple=i686-linux-android -std=c11
55
// RUN: %clang_cc1 %s -verify -fgnuc-version=4.2.1 -ffreestanding \
6-
// RUN: -fsyntax-only -triple=powerpc64-linux-gnu -std=c11
6+
// RUN: -Wno-default-const-init-unsafe -fsyntax-only -triple=powerpc64-linux-gnu -std=c11
77
// RUN: %clang_cc1 %s -verify -fgnuc-version=4.2.1 -ffreestanding \
8-
// RUN: -fsyntax-only -triple=powerpc64-linux-gnu -std=c11 \
8+
// RUN: -Wno-default-const-init-unsafe -fsyntax-only -triple=powerpc64-linux-gnu -std=c11 \
99
// RUN: -target-cpu pwr7
1010
// RUN: %clang_cc1 %s -verify -fgnuc-version=4.2.1 -ffreestanding \
11-
// RUN: -fsyntax-only -triple=powerpc64le-linux-gnu -std=c11 \
11+
// RUN: -Wno-default-const-init-unsafe -fsyntax-only -triple=powerpc64le-linux-gnu -std=c11 \
1212
// RUN: -target-cpu pwr8 -DPPC64_PWR8
1313
// RUN: %clang_cc1 %s -verify -fgnuc-version=4.2.1 -ffreestanding \
14-
// RUN: -fsyntax-only -triple=powerpc64-unknown-aix -std=c11 \
14+
// RUN: -Wno-default-const-init-unsafe -fsyntax-only -triple=powerpc64-unknown-aix -std=c11 \
1515
// RUN: -target-cpu pwr8
1616
// RUN: %clang_cc1 %s -verify -fgnuc-version=4.2.1 -ffreestanding \
17-
// RUN: -fsyntax-only -triple=powerpc64-unknown-aix -std=c11 \
17+
// RUN: -Wno-default-const-init-unsafe -fsyntax-only -triple=powerpc64-unknown-aix -std=c11 \
1818
// RUN: -mabi=quadword-atomics -target-cpu pwr8 -DPPC64_PWR8
1919

2020
// Basic parsing/Sema tests for __c11_atomic_*

clang/test/Sema/block-return.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ void foo7(void)
126126
int (^JJ) (void) = ^{ return j; }; // OK
127127
int (^KK) (void) = ^{ return j+1; }; // OK
128128

129-
__block const int k;
129+
__block const int k = 0;
130130
const int cint = 100;
131131

132132
int (^MM) (void) = ^{ return k; };

clang/test/Sema/builtins-bpf.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ unsigned invalid11(struct s *arg, int info_kind) {
6969
}
7070

7171
unsigned valid12(void) {
72-
const struct s t;
72+
const struct s t = {};
7373
return __builtin_preserve_type_info(t, 0) +
7474
__builtin_preserve_type_info(*(struct s *)0, 1);
7575
}

clang/test/Sema/builtins-elementwise-math.c

+5-5
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ void test_builtin_elementwise_add_sat(int i, short s, double d, float4 v, int3 i
8888
_BitInt(32) ext; // expected-warning {{'_BitInt' in C17 and earlier is a Clang extension}}
8989
ext = __builtin_elementwise_add_sat(ext, ext);
9090

91-
const int ci;
91+
const int ci = 0;
9292
i = __builtin_elementwise_add_sat(ci, i);
9393
i = __builtin_elementwise_add_sat(i, ci);
9494
i = __builtin_elementwise_add_sat(ci, ci);
@@ -154,7 +154,7 @@ void test_builtin_elementwise_sub_sat(int i, short s, double d, float4 v, int3 i
154154
_BitInt(32) ext; // expected-warning {{'_BitInt' in C17 and earlier is a Clang extension}}
155155
ext = __builtin_elementwise_sub_sat(ext, ext);
156156

157-
const int ci;
157+
const int ci = 0;
158158
i = __builtin_elementwise_sub_sat(ci, i);
159159
i = __builtin_elementwise_sub_sat(i, ci);
160160
i = __builtin_elementwise_sub_sat(ci, ci);
@@ -214,7 +214,7 @@ void test_builtin_elementwise_max(int i, short s, double d, float4 v, int3 iv, u
214214
_BitInt(32) ext; // expected-warning {{'_BitInt' in C17 and earlier is a Clang extension}}
215215
ext = __builtin_elementwise_max(ext, ext);
216216

217-
const int ci;
217+
const int ci = 0;
218218
i = __builtin_elementwise_max(ci, i);
219219
i = __builtin_elementwise_max(i, ci);
220220
i = __builtin_elementwise_max(ci, ci);
@@ -274,7 +274,7 @@ void test_builtin_elementwise_min(int i, short s, double d, float4 v, int3 iv, u
274274
_BitInt(32) ext; // expected-warning {{'_BitInt' in C17 and earlier is a Clang extension}}
275275
ext = __builtin_elementwise_min(ext, ext);
276276

277-
const int ci;
277+
const int ci = 0;
278278
i = __builtin_elementwise_min(ci, i);
279279
i = __builtin_elementwise_min(i, ci);
280280
i = __builtin_elementwise_min(ci, ci);
@@ -1070,7 +1070,7 @@ void test_builtin_elementwise_copysign(int i, short s, double d, float f, float4
10701070
ext = __builtin_elementwise_copysign(ext, ext);
10711071
// expected-error@-1 {{1st argument must be a scalar or vector of floating-point types (was '_BitInt(32)')}}
10721072

1073-
const float cf32;
1073+
const float cf32 = 0.0f;
10741074
f = __builtin_elementwise_copysign(cf32, f);
10751075
f = __builtin_elementwise_copysign(f, cf32);
10761076
f = __builtin_elementwise_copysign(cf32, f);

clang/test/Sema/builtins-overflow.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ void test(void) {
99
unsigned r;
1010
const char * c;
1111
float f;
12-
const unsigned q;
12+
const unsigned q = 0;
1313

1414
__builtin_add_overflow(); // expected-error {{too few arguments to function call, expected 3, have 0}}
1515
__builtin_add_overflow(1, 1, 1, 1); // expected-error {{too many arguments to function call, expected 3, have 4}}

clang/test/Sema/enable_if.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ size_t strnlen(const char *s, size_t maxlen) // expected-note {{'strnlen' has be
5252

5353
void test2(const char *s, int i) {
5454
// CHECK: define {{.*}}void @test2
55-
const char c[123];
55+
const char c[123] = { 0 };
5656
strnlen(s, i);
5757
// CHECK: call {{.*}}strnlen_real1
5858
strnlen(s, 999);

clang/test/Sema/implicit-decl.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ typedef unsigned char Boolean;
1313
extern int printf(__const char *__restrict __format, ...); // both-note{{'printf' declared here}}
1414
void func(void) {
1515
int32_t *vector[16];
16-
const char compDesc[16 + 1];
16+
const char compDesc[16 + 1] = { 0 };
1717
int32_t compCount = 0;
1818
if (_CFCalendarDecomposeAbsoluteTimeV(compDesc, vector, compCount)) { // expected-error {{call to undeclared function '_CFCalendarDecomposeAbsoluteTimeV'; ISO C99 and later do not support implicit function declarations}} \
1919
expected-note {{previous implicit declaration}} \

clang/test/Sema/overloadable.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ void incompatible_pointer_type_conversions() {
155155
}
156156

157157
void dropping_qualifiers_is_incompatible() {
158-
const char ccharbuf[1];
158+
const char ccharbuf[1] = {0};
159159
volatile char vcharbuf[1];
160160

161161
void foo(char *c) __attribute__((overloadable));

clang/test/Sema/sizeless-1.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -94,12 +94,12 @@ void func(int sel) {
9494
svint8_t bad_brace_init_int8_6 = {{local_int8, 0}}; // expected-warning {{too many braces around initializer}}
9595

9696
const svint8_t const_int8 = local_int8; // expected-note {{declared const here}}
97-
const svint8_t uninit_const_int8;
97+
const svint8_t uninit_const_int8; // expected-warning {{default initialization of an object of type 'const svint8_t' (aka 'const __SVInt8_t') leaves the object uninitialized and is incompatible with C++}};
9898

9999
volatile svint8_t volatile_int8;
100100

101101
const volatile svint8_t const_volatile_int8 = local_int8; // expected-note {{declared const here}}
102-
const volatile svint8_t uninit_const_volatile_int8;
102+
const volatile svint8_t uninit_const_volatile_int8; // expected-warning {{default initialization of an object of type 'const volatile svint8_t' (aka 'const volatile __SVInt8_t') leaves the object uninitialized and is incompatible with C++}}
103103

104104
_Atomic svint8_t atomic_int8; // expected-error {{_Atomic cannot be applied to sizeless type 'svint8_t'}}
105105
__restrict svint8_t restrict_int8; // expected-error {{requires a pointer or reference}}

clang/test/Sema/typedef-retain.c

+2-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ void test2(float4 a, int4p result, int i) {
1616
typedef int a[5];
1717
void test3(void) {
1818
typedef const a b;
19-
b r; // expected-note {{variable 'r' declared const here}}
19+
b r; // expected-note {{variable 'r' declared const here}} \
20+
expected-warning {{default initialization of an object of type 'b' (aka 'const int[5]') leaves the object uninitialized and is incompatible with C++}}
2021
r[0] = 10; // expected-error {{cannot assign to variable 'r' with const-qualified type 'b' (aka 'const int[5]')}}
2122
}
2223

clang/test/Sema/varargs-x86-64.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %clang_cc1 -fsyntax-only -verify %s -triple x86_64-apple-darwin9
1+
// RUN: %clang_cc1 -fsyntax-only -Wno-default-const-init-unsafe -verify %s -triple x86_64-apple-darwin9
22

33
void f1(void) {
44
const __builtin_va_list args2;

0 commit comments

Comments
 (0)