Author: ahmed mohamed kamel
Date: 2026-10-06T17:51:51Z
New Revision: b8ff1bc057f3631286194a22a77155fc4a4b467f

URL: 
https://github.com/llvm/llvm-project/commit/b8ff1bc057f3631286194a22a77155fc4a4b467f
DIFF: 
https://github.com/llvm/llvm-project/commit/b8ff1bc057f3631286194a22a77155fc4a4b467f.diff

LOG: [clang] Fix thread_local incorrectly treated as tentative definition 
(#219780)

Fixes #217636.

In C23 (6.9.3p1-2), a file-scope declaration with `thread_local` is a
definition and is excluded from tentative definitions. Clang classified
such
declarations as tentative, so `thread_local int x; thread_local int x;`
was
accepted.

In C23 and later, `VarDecl::isThisDeclarationADefinition` no longer
returns
`TentativeDefinition` for file-scope thread-local variables, and the C
path of
`MergeVarDecl` now diagnoses a redefinition when a thread-local
definition
follows a previous definition. Earlier language modes keep the old
behavior,
since older standards have no such rule.

Tests: `Sema/c23-thread-local-definition.c` covers both C23 and pre-C23.

Added: 
    clang/test/Sema/c23-thread-local-definition.c

Modified: 
    clang/docs/ReleaseNotes.md
    clang/lib/AST/Decl.cpp
    clang/lib/Sema/SemaDecl.cpp

Removed: 
    


################################################################################
diff  --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index aca8d5bf630b2..c679846ee1e86 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -609,6 +609,8 @@ features cannot lower the translation-unit ABI level;
 - No longer crashing due to follow-on diagnostics when there is an invalid 
operand in a logical operator involving a vector operand. (#GH227588)
 - Fixed assertion failures caused by stale linkage information when an extern 
variable or function declaration is merged with a preceding static declaration. 
(#GH204759, #GH204754)
 - Fixed a crash due to typo correction mishandling custom keywords 
`_virtual_inheritance` and `_multiple_inheritance` in `-fms-compatibility` 
mode. (#GH228003)
+- Clang no longer treats a file-scope `thread_local` declaration without an 
initializer as a tentative definition in C23 mode. As specified by C23 6.9.3, 
such a declaration is a definition, so declaring the same variable more that 
once is now diagnosed as a redefinition. (#GH217636)
+
 #### Bug Fixes to Compiler Builtins
 
 - Fixed a crash when classifying a call to a builtin with dependent arguments,

diff  --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index f7a635e35336a..0c79fe07f8d0c 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -2309,16 +2309,21 @@ VarDecl::isThisDeclarationADefinition(ASTContext &C) 
const {
   if (isSingleLineLanguageLinkage(*this))
     return DeclarationOnly;
 
-  // C99 6.9.2p2:
-  //   A declaration of an object that has file scope without an initializer,
-  //   and without a storage class specifier or the scs 'static', constitutes
-  //   a tentative definition.
-  // No such thing in C++.
-  if (!C.getLangOpts().CPlusPlus && isFileVarDecl())
+  // C23 6.9.3p2:
+  //   A declaration of an identifier for an object that has file scope
+  //   without an initializer, and without the storage-class specifier extern
+  //   or thread_local, constitutes a tentative definition.
+  // In C23 and later, file-scope thread_local / _Thread_local / __thread
+  // declarations without initializers are full external definitions
+  // (C23 6.9.3p1). Pre-C23 standards allowed tentative TLS definitions, so we
+  // preserve that behavior in earlier language modes.
+  if (!C.getLangOpts().CPlusPlus && isFileVarDecl() &&
+      (getTLSKind() == TLS_None || !C.getLangOpts().C23))
     return TentativeDefinition;
 
-  // What's left is (in C, block-scope) declarations without initializers or
-  // external storage. These are definitions.
+  // What's left is (in C) block-scope declarations and, in C23, file-scope
+  // thread_local declarations without initializers or external storage. These
+  // are definitions.
   return Definition;
 }
 

diff  --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 59c0faa67364a..fdbe152116512 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -4976,7 +4976,13 @@ void Sema::MergeVarDecl(VarDecl *New, LookupResult 
&Previous) {
     // [basic.def]p2 for details, but the basic idea is: if the old declaration
     // contains the extern specifier and doesn't have an initializer, it's fine
     // in C++.
-    if (Old->getStorageClass() != SC_Extern || Old->hasInit()) {
+    if (New->getTLSKind() != VarDecl::TLS_None &&
+        New->isThisDeclarationADefinition() == VarDecl::Definition) {
+      VarDecl *Def = Old->getDefinition();
+      if (Def && checkVarDeclRedefinition(Def, New)) {
+        return;
+      }
+    } else if (Old->getStorageClass() != SC_Extern || Old->hasInit()) {
       Diag(New->getLocation(), diag::warn_cxx_compat_tentative_definition)
           << New;
       Diag(Old->getLocation(), diag::note_previous_declaration);

diff  --git a/clang/test/Sema/c23-thread-local-definition.c 
b/clang/test/Sema/c23-thread-local-definition.c
new file mode 100644
index 0000000000000..cb5aeea2feb40
--- /dev/null
+++ b/clang/test/Sema/c23-thread-local-definition.c
@@ -0,0 +1,24 @@
+// RUN: %clang_cc1 -std=c23 -fsyntax-only -verify %s
+// RUN: %clang_cc1 -std=c17 -fsyntax-only -verify=pre23 %s
+// pre23-no-diagnostics
+
+_Thread_local int a;     // expected-note {{previous definition is here}}
+_Thread_local int a;     // expected-error {{redefinition of 'a'}}
+
+static _Thread_local int b; // expected-note {{previous definition is here}}
+static _Thread_local int b; // expected-error {{redefinition of 'b'}}
+
+__thread int c;          // expected-note {{previous definition is here}}
+__thread int c;          // expected-error {{redefinition of 'c'}}
+
+// 'extern' makes these declarations, not definitions.
+extern _Thread_local int e;
+_Thread_local int e;
+
+_Thread_local int f;
+extern _Thread_local int f;
+
+#if __STDC_VERSION__ >= 202311L
+thread_local int x, x;   // expected-error {{redefinition of 'x'}} \
+                         // expected-note {{previous definition is here}}
+#endif


        
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to