c: Do not allow thread-local tentative definitions for C2x

C2x makes it clear that thread-local declarations can never be
tentative definitions (the legacy feature of C where you can e.g. do
"int i;" more than once at file scope, possibly with one of the
declarations initialized, and it counts as exactly one definition),
but are always definitions in the absence of "extern".  The wording
about external definitions was unclear in the thread-local case in C11
/ C17 (both about what counts as a tentative definition, and what is a
"definition" at all), not having been updated to cover the addition of
thread-local storage.

Implement this C2x requirement.  Arguably this is a defect fix that
would be appropriate to apply for all standard versions, but for now
the change is conditional on flag_isoc2x (however, it doesn't handle
_Thread_local / thread_local any different from GNU __thread).  Making
the change unconditional results in various TLS tests failing to
compile (gcc.dg/c11-thread-local-1.c gcc.dg/tls/thr-init-1.c
gcc.dg/tls/thr-init-2.c gcc.dg/torture/tls/thr-init-2.c
objc.dg/torture/tls/thr-init.m), though it's not clear if those tests
reflect any real code similarly trying to make use of thread-local
tentative definitions.

Bootstrapped with no regressions for x86_64-pc-linux-gnu.

gcc/c/
	* c-decl.cc (diagnose_mismatched_decls): Do not handle
	thread-local declarations as tentative definitions for C2x.
	(finish_decl): Do not allow thread-local definition with
	incomplete type for C2x.

gcc/testsuite/
	* gcc.dg/c2x-thread-local-2.c: New test.
This commit is contained in:
Joseph Myers 2023-05-19 00:42:07 +00:00
parent b27760769c
commit c3db109674
2 changed files with 57 additions and 3 deletions

View file

@ -2442,8 +2442,20 @@ diagnose_mismatched_decls (tree newdecl, tree olddecl,
return false;
}
/* Multiple initialized definitions are not allowed (6.9p3,5). */
if (DECL_INITIAL (newdecl) && DECL_INITIAL (olddecl))
/* Multiple initialized definitions are not allowed (6.9p3,5).
For this purpose, C2x makes it clear that thread-local
declarations without extern are definitions, not tentative
definitions, whether or not they have initializers. The
wording before C2x was unclear; literally it would have made
uninitialized thread-local declarations into tentative
definitions only if they also used static, but without saying
explicitly whether or not other cases count as
definitions at all. */
if ((DECL_INITIAL (newdecl) && DECL_INITIAL (olddecl))
|| (flag_isoc2x
&& DECL_THREAD_LOCAL_P (newdecl)
&& !DECL_EXTERNAL (newdecl)
&& !DECL_EXTERNAL (olddecl)))
{
auto_diagnostic_group d;
error ("redefinition of %q+D", newdecl);
@ -5714,10 +5726,12 @@ finish_decl (tree decl, location_t init_loc, tree init,
/* A static variable with an incomplete type
is an error if it is initialized.
Also if it is not file scope.
Also if it is thread-local (in C2x).
Otherwise, let it through, but if it is not `extern'
then it may cause an error message later. */
? (DECL_INITIAL (decl) != NULL_TREE
|| !DECL_FILE_SCOPE_P (decl))
|| !DECL_FILE_SCOPE_P (decl)
|| (flag_isoc2x && DECL_THREAD_LOCAL_P (decl)))
/* An automatic variable with an incomplete type
is an error. */
: !DECL_EXTERNAL (decl)))

View file

@ -0,0 +1,40 @@
/* Test that thread-local declarations are not considered tentative definitions
in C2x. */
/* { dg-do compile } */
/* { dg-options "-std=c2x -pedantic-errors" } */
thread_local int a; /* { dg-message "previous" } */
thread_local int a; /* { dg-error "redefinition" } */
static thread_local int b; /* { dg-message "previous" } */
static thread_local int b; /* { dg-error "redefinition" } */
thread_local int c; /* { dg-message "previous" } */
thread_local int c = 1; /* { dg-error "redefinition" } */
static thread_local int d; /* { dg-message "previous" } */
static thread_local int d = 1; /* { dg-error "redefinition" } */
thread_local int e = 1; /* { dg-message "previous" } */
thread_local int e; /* { dg-error "redefinition" } */
static thread_local int f = 1; /* { dg-message "previous" } */
static thread_local int f; /* { dg-error "redefinition" } */
/* Not being a tentative definition means that incomplete arrays are an error
rather than defaulting to size 1. */
thread_local int g[]; /* { dg-error "storage size" } */
static thread_local int h[]; /* { dg-error "array size missing" } */
extern thread_local int i[];
thread_local int j[] = { 0 };
static thread_local int k[] = { 0 };
thread_local int l;
extern thread_local int l;
extern thread_local int m;
thread_local int m;
static thread_local int n;
extern thread_local int n;