Skip to content

[C] Warn on uninitialized const objects #137166

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 9 commits into from
Apr 25, 2025
Merged
Show file tree
Hide file tree
Changes from 8 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
7 changes: 7 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,13 @@ C Language Changes
- Clang now allows an ``inline`` specifier on a typedef declaration of a
function type in Microsoft compatibility mode. #GH124869
- Clang now allows ``restrict`` qualifier for array types with pointer elements (#GH92847).
- Clang now diagnoses ``const``-qualified object definitions without an
initializer. If the object is zero-initialized, it will be diagnosed under
the new warning ``-Wdefault-const-init`` (which is grouped under
``-Wc++-compat`` because this construct is not compatible with C++). If the
object is left uninitialized, it will be diagnosed unsed the new warning
``-Wdefault-const-init-unsafe`` (which is grouped under
``-Wdefault-const-init``). #GH19297
- Added ``-Wimplicit-void-ptr-cast``, grouped under ``-Wc++-compat``, which
diagnoses implicit conversion from ``void *`` to another pointer type as
being incompatible with C++. (#GH17792)
Expand Down
6 changes: 4 additions & 2 deletions clang/include/clang/Basic/DiagnosticGroups.td
Original file line number Diff line number Diff line change
Expand Up @@ -154,9 +154,11 @@ def BuiltinRequiresHeader : DiagGroup<"builtin-requires-header">;
def C99Compat : DiagGroup<"c99-compat">;
def C23Compat : DiagGroup<"c23-compat">;
def : DiagGroup<"c2x-compat", [C23Compat]>;

def DefaultConstInitUnsafe : DiagGroup<"default-const-init-unsafe">;
def DefaultConstInit : DiagGroup<"default-const-init", [DefaultConstInitUnsafe]>;
def ImplicitVoidPtrCast : DiagGroup<"implicit-void-ptr-cast">;
def CXXCompat: DiagGroup<"c++-compat", [ImplicitVoidPtrCast]>;
def CXXCompat: DiagGroup<"c++-compat", [ImplicitVoidPtrCast, DefaultConstInit]>;

def ExternCCompat : DiagGroup<"extern-c-compat">;
def KeywordCompat : DiagGroup<"keyword-compat">;
def GNUCaseRange : DiagGroup<"gnu-case-range">;
Expand Down
10 changes: 10 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -8197,6 +8197,16 @@ def err_address_space_qualified_new : Error<
def err_address_space_qualified_delete : Error<
"'delete' cannot delete objects of type %0 in address space '%1'">;

def note_default_init_const_member : Note<
"member %0 declared 'const' here">;
def warn_default_init_const : Warning<
"default initialization of an object of type %0%select{| with const member}1 "
"is incompatible with C++">,
InGroup<DefaultConstInit>, DefaultIgnore;
def warn_default_init_const_unsafe : Warning<
"default initialization of an object of type %0%select{| with const member}1 "
"leaves the object uninitialized and is incompatible with C++">,
InGroup<DefaultConstInitUnsafe>;
def err_default_init_const : Error<
"default initialization of an object of const type %0"
"%select{| without a user-provided default constructor}1">;
Expand Down
2 changes: 1 addition & 1 deletion clang/lib/Parse/ParseStmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2154,7 +2154,7 @@ StmtResult Parser::ParseForStatement(SourceLocation *TrailingElseLoc) {
FirstPart = Actions.ActOnDeclStmt(DG, DeclStart, Tok.getLocation());
} else {
// In C++0x, "for (T NS:a" might not be a typo for ::
bool MightBeForRangeStmt = getLangOpts().CPlusPlus;
bool MightBeForRangeStmt = getLangOpts().CPlusPlus || getLangOpts().ObjC;
ColonProtectionRAIIObject ColonProtection(*this, MightBeForRangeStmt);
ParsedAttributes DeclSpecAttrs(AttrFactory);
DG = ParseSimpleDeclaration(
Expand Down
12 changes: 12 additions & 0 deletions clang/lib/Sema/Sema.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1448,6 +1448,18 @@ void Sema::ActOnEndOfTranslationUnit() {
// No initialization is performed for a tentative definition.
CheckCompleteVariableDeclaration(VD);

// In C, if the definition is const-qualified and has no initializer, it
// is left uninitialized unless it has static or thread storage duration.
QualType Type = VD->getType();
if (!VD->isInvalidDecl() && !getLangOpts().CPlusPlus &&
Type.isConstQualified() && !VD->getAnyInitializer()) {
unsigned DiagID = diag::warn_default_init_const_unsafe;
if (VD->getStorageDuration() == SD_Static ||
VD->getStorageDuration() == SD_Thread)
DiagID = diag::warn_default_init_const;
Diag(VD->getLocation(), DiagID) << Type << /*not a field*/ 0;
}

// Notify the consumer that we've completed a tentative definition.
if (!VD->isInvalidDecl())
Consumer.CompleteTentativeDefinition(VD);
Expand Down
10 changes: 10 additions & 0 deletions clang/lib/Sema/SemaDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14333,6 +14333,16 @@ void Sema::ActOnUninitializedDecl(Decl *RealDecl) {
return;
}

// In C, if the definition is const-qualified and has no initializer, it
// is left uninitialized unless it has static or thread storage duration.
if (!getLangOpts().CPlusPlus && Type.isConstQualified()) {
unsigned DiagID = diag::warn_default_init_const_unsafe;
if (Var->getStorageDuration() == SD_Static ||
Var->getStorageDuration() == SD_Thread)
DiagID = diag::warn_default_init_const;
Diag(Var->getLocation(), DiagID) << Type << /*not a field*/ 0;
}

// Check for jumps past the implicit initializer. C++0x
// clarifies that this applies to a "variable with automatic
// storage duration", not a "local variable".
Expand Down
38 changes: 32 additions & 6 deletions clang/lib/Sema/SemaInit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6496,6 +6496,17 @@ static bool canPerformArrayCopy(const InitializedEntity &Entity) {
return false;
}

static const FieldDecl *getConstField(const RecordDecl *RD) {
for (const FieldDecl *FD : RD->fields()) {
QualType QT = FD->getType();
if (QT.isConstQualified())
return FD;
if (const auto *RD = QT->getAsRecordDecl())
return getConstField(RD);
}
return nullptr;
}

void InitializationSequence::InitializeFrom(Sema &S,
const InitializedEntity &Entity,
const InitializationKind &Kind,
Expand Down Expand Up @@ -6563,13 +6574,28 @@ void InitializationSequence::InitializeFrom(Sema &S,

if (!S.getLangOpts().CPlusPlus &&
Kind.getKind() == InitializationKind::IK_Default) {
RecordDecl *Rec = DestType->getAsRecordDecl();
if (Rec && Rec->hasUninitializedExplicitInitFields()) {
if (RecordDecl *Rec = DestType->getAsRecordDecl()) {
VarDecl *Var = dyn_cast_or_null<VarDecl>(Entity.getDecl());
if (Var && !Initializer) {
S.Diag(Var->getLocation(), diag::warn_field_requires_explicit_init)
<< /* Var-in-Record */ 1 << Rec;
emitUninitializedExplicitInitFields(S, Rec);
if (Rec->hasUninitializedExplicitInitFields()) {
if (Var && !Initializer) {
S.Diag(Var->getLocation(), diag::warn_field_requires_explicit_init)
<< /* Var-in-Record */ 1 << Rec;
emitUninitializedExplicitInitFields(S, Rec);
}
}
// If the record has any members which are const (recursively checked),
// then we want to diagnose those as being uninitialized if there is no
// initializer present.
if (!Initializer) {
if (const FieldDecl *FD = getConstField(Rec)) {
unsigned DiagID = diag::warn_default_init_const_unsafe;
if (Var->getStorageDuration() == SD_Static ||
Var->getStorageDuration() == SD_Thread)
DiagID = diag::warn_default_init_const;

S.Diag(Var->getLocation(), DiagID) << Var->getType() << /*member*/ 1;
S.Diag(FD->getLocation(), diag::note_default_init_const_member) << FD;
}
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions clang/test/C/C23/n2607.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ void test1(void) {
void test2(void) {
typedef int array[1];
array reg_array;
const array const_array;
const array const_array = { 0 };

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

// FIXME: the type here should be `const int (*)[1]`, but for some reason,
Expand Down
4 changes: 2 additions & 2 deletions clang/test/C/drs/dr1xx.c
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ void dr124(void) {
*/
void dr126(void) {
typedef int *IP;
const IP object; /* expected-note {{variable 'object' declared const here}} */
const IP object = 0; /* expected-note {{variable 'object' declared const here}} */

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

Expand Down
4 changes: 2 additions & 2 deletions clang/test/Parser/typeof.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ static void test(void) {
short TInt eee; // expected-error{{expected ';' at end of declaration}}
void ary[7] fff; // expected-error{{array has incomplete element type 'void'}} expected-error{{expected ';' at end of declaration}}
typeof(void ary[7]) anIntError; // expected-error{{expected ')'}} expected-note {{to match this '('}} expected-error {{variable has incomplete type 'typeof(void)' (aka 'void')}}
typeof(const int) aci;
const typeof (*pi) aConstInt;
typeof(const int) aci = 0;
const typeof (*pi) aConstInt = 0;
int xx;
int *i;
}
Expand Down
4 changes: 2 additions & 2 deletions clang/test/Sema/assign.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ void test2 (const struct {int a;} *x) {

typedef int arr[10];
void test3(void) {
const arr b; // expected-note {{variable 'b' declared const here}}
const int b2[10]; // expected-note {{variable 'b2' declared const here}}
const arr b = {}; // expected-note {{variable 'b' declared const here}}
const int b2[10] = {}; // expected-note {{variable 'b2' declared const here}}
b[4] = 1; // expected-error {{cannot assign to variable 'b' with const-qualified type 'const arr' (aka 'const int[10]')}}
b2[4] = 1; // expected-error {{cannot assign to variable 'b2' with const-qualified type 'const int[10]'}}
}
Expand Down
14 changes: 7 additions & 7 deletions clang/test/Sema/atomic-ops.c
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
// RUN: %clang_cc1 %s -verify=expected,fp80,noi128 -fgnuc-version=4.2.1 -ffreestanding \
// RUN: -fsyntax-only -triple=i686-linux-gnu -std=c11
// RUN: -Wno-default-const-init-unsafe -fsyntax-only -triple=i686-linux-gnu -std=c11
// RUN: %clang_cc1 %s -verify=expected,noi128 -fgnuc-version=4.2.1 -ffreestanding \
// RUN: -fsyntax-only -triple=i686-linux-android -std=c11
// RUN: -Wno-default-const-init-unsafe -fsyntax-only -triple=i686-linux-android -std=c11
// RUN: %clang_cc1 %s -verify -fgnuc-version=4.2.1 -ffreestanding \
// RUN: -fsyntax-only -triple=powerpc64-linux-gnu -std=c11
// RUN: -Wno-default-const-init-unsafe -fsyntax-only -triple=powerpc64-linux-gnu -std=c11
// RUN: %clang_cc1 %s -verify -fgnuc-version=4.2.1 -ffreestanding \
// RUN: -fsyntax-only -triple=powerpc64-linux-gnu -std=c11 \
// RUN: -Wno-default-const-init-unsafe -fsyntax-only -triple=powerpc64-linux-gnu -std=c11 \
// RUN: -target-cpu pwr7
// RUN: %clang_cc1 %s -verify -fgnuc-version=4.2.1 -ffreestanding \
// RUN: -fsyntax-only -triple=powerpc64le-linux-gnu -std=c11 \
// RUN: -Wno-default-const-init-unsafe -fsyntax-only -triple=powerpc64le-linux-gnu -std=c11 \
// RUN: -target-cpu pwr8 -DPPC64_PWR8
// RUN: %clang_cc1 %s -verify -fgnuc-version=4.2.1 -ffreestanding \
// RUN: -fsyntax-only -triple=powerpc64-unknown-aix -std=c11 \
// RUN: -Wno-default-const-init-unsafe -fsyntax-only -triple=powerpc64-unknown-aix -std=c11 \
// RUN: -target-cpu pwr8
// RUN: %clang_cc1 %s -verify -fgnuc-version=4.2.1 -ffreestanding \
// RUN: -fsyntax-only -triple=powerpc64-unknown-aix -std=c11 \
// RUN: -Wno-default-const-init-unsafe -fsyntax-only -triple=powerpc64-unknown-aix -std=c11 \
// RUN: -mabi=quadword-atomics -target-cpu pwr8 -DPPC64_PWR8

// Basic parsing/Sema tests for __c11_atomic_*
Expand Down
2 changes: 1 addition & 1 deletion clang/test/Sema/block-return.c
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ void foo7(void)
int (^JJ) (void) = ^{ return j; }; // OK
int (^KK) (void) = ^{ return j+1; }; // OK

__block const int k;
__block const int k = 0;
const int cint = 100;

int (^MM) (void) = ^{ return k; };
Expand Down
2 changes: 1 addition & 1 deletion clang/test/Sema/builtins-bpf.c
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ unsigned invalid11(struct s *arg, int info_kind) {
}

unsigned valid12(void) {
const struct s t;
const struct s t = {};
return __builtin_preserve_type_info(t, 0) +
__builtin_preserve_type_info(*(struct s *)0, 1);
}
Expand Down
10 changes: 5 additions & 5 deletions clang/test/Sema/builtins-elementwise-math.c
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ void test_builtin_elementwise_add_sat(int i, short s, double d, float4 v, int3 i
_BitInt(32) ext; // expected-warning {{'_BitInt' in C17 and earlier is a Clang extension}}
ext = __builtin_elementwise_add_sat(ext, ext);

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

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

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

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

const float cf32;
const float cf32 = 0.0f;
f = __builtin_elementwise_copysign(cf32, f);
f = __builtin_elementwise_copysign(f, cf32);
f = __builtin_elementwise_copysign(cf32, f);
Expand Down
2 changes: 1 addition & 1 deletion clang/test/Sema/builtins-overflow.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ void test(void) {
unsigned r;
const char * c;
float f;
const unsigned q;
const unsigned q = 0;

__builtin_add_overflow(); // expected-error {{too few arguments to function call, expected 3, have 0}}
__builtin_add_overflow(1, 1, 1, 1); // expected-error {{too many arguments to function call, expected 3, have 4}}
Expand Down
2 changes: 1 addition & 1 deletion clang/test/Sema/enable_if.c
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ size_t strnlen(const char *s, size_t maxlen) // expected-note {{'strnlen' has be

void test2(const char *s, int i) {
// CHECK: define {{.*}}void @test2
const char c[123];
const char c[123] = { 0 };
strnlen(s, i);
// CHECK: call {{.*}}strnlen_real1
strnlen(s, 999);
Expand Down
2 changes: 1 addition & 1 deletion clang/test/Sema/implicit-decl.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ typedef unsigned char Boolean;
extern int printf(__const char *__restrict __format, ...); // both-note{{'printf' declared here}}
void func(void) {
int32_t *vector[16];
const char compDesc[16 + 1];
const char compDesc[16 + 1] = { 0 };
int32_t compCount = 0;
if (_CFCalendarDecomposeAbsoluteTimeV(compDesc, vector, compCount)) { // expected-error {{call to undeclared function '_CFCalendarDecomposeAbsoluteTimeV'; ISO C99 and later do not support implicit function declarations}} \
expected-note {{previous implicit declaration}} \
Expand Down
2 changes: 1 addition & 1 deletion clang/test/Sema/overloadable.c
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ void incompatible_pointer_type_conversions() {
}

void dropping_qualifiers_is_incompatible() {
const char ccharbuf[1];
const char ccharbuf[1] = {0};
volatile char vcharbuf[1];

void foo(char *c) __attribute__((overloadable));
Expand Down
4 changes: 2 additions & 2 deletions clang/test/Sema/sizeless-1.c
Original file line number Diff line number Diff line change
Expand Up @@ -94,12 +94,12 @@ void func(int sel) {
svint8_t bad_brace_init_int8_6 = {{local_int8, 0}}; // expected-warning {{too many braces around initializer}}

const svint8_t const_int8 = local_int8; // expected-note {{declared const here}}
const svint8_t uninit_const_int8;
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++}};

volatile svint8_t volatile_int8;

const volatile svint8_t const_volatile_int8 = local_int8; // expected-note {{declared const here}}
const volatile svint8_t uninit_const_volatile_int8;
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++}}

_Atomic svint8_t atomic_int8; // expected-error {{_Atomic cannot be applied to sizeless type 'svint8_t'}}
__restrict svint8_t restrict_int8; // expected-error {{requires a pointer or reference}}
Expand Down
3 changes: 2 additions & 1 deletion clang/test/Sema/typedef-retain.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ void test2(float4 a, int4p result, int i) {
typedef int a[5];
void test3(void) {
typedef const a b;
b r; // expected-note {{variable 'r' declared const here}}
b r; // expected-note {{variable 'r' declared const here}} \
expected-warning {{default initialization of an object of type 'b' (aka 'const int[5]') leaves the object uninitialized and is incompatible with C++}}
r[0] = 10; // expected-error {{cannot assign to variable 'r' with const-qualified type 'b' (aka 'const int[5]')}}
}

Expand Down
2 changes: 1 addition & 1 deletion clang/test/Sema/varargs-x86-64.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// RUN: %clang_cc1 -fsyntax-only -verify %s -triple x86_64-apple-darwin9
// RUN: %clang_cc1 -fsyntax-only -Wno-default-const-init-unsafe -verify %s -triple x86_64-apple-darwin9

void f1(void) {
const __builtin_va_list args2;
Expand Down
Loading
Loading