[clang] b8ff1bc - [clang] Fix thread_local incorrectly treated as tentative definition (#219780)
via cfe-commits
cfe-commits at lists.llvm.org
Tue Oct 6 10:52:00 PDT 2026
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
More information about the cfe-commits
mailing list