[clang] [Clang] Diagnose UB and emit error when identifier has both internal and external linkage (PR #192116)

Aditya Medhane via cfe-commits cfe-commits at lists.llvm.org
Sun Apr 19 01:06:04 PDT 2026


https://github.com/flash1729 updated https://github.com/llvm/llvm-project/pull/192116

>From 61ee18fbf4a83acdcfa749c6f7049403fa293687 Mon Sep 17 00:00:00 2001
From: flash1729 <sherlockedaditya at gmail.com>
Date: Wed, 15 Apr 2026 00:42:00 +0530
Subject: [PATCH 1/4] [Clang] Diagnose UB when identifier has both internal and
 external linkage

C11 6.2.2p7 states that if the same identifier appears with both internal
and external linkage within a translation unit, the behavior is undefined.

This occurs when a block-scope extern declaration finds a file-scope static
declaration, but a no-linkage local variable shadows the static, preventing
linkage inheritance per C11 6.2.2p4.

Fixes #54215
---
 clang/docs/ReleaseNotes.rst                   |  4 +++
 .../clang/Basic/DiagnosticSemaKinds.td        |  3 ++
 clang/lib/Sema/SemaDecl.cpp                   | 18 ++++++++++
 clang/test/Sema/linkage-internal-extern.c     | 36 +++++++++++++++++++
 4 files changed, 61 insertions(+)
 create mode 100644 clang/test/Sema/linkage-internal-extern.c

diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index fd58d7847717c..f1efd5f8c3b35 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -366,6 +366,10 @@ Improvements to Clang's diagnostics
   code can automatically be made portable to other host platforms that don't
   support backslashes.
 
+- Clang now diagnoses the undefined behavior described in C11 6.2.2p7 where
+  the same identifier appears with both internal and external linkage within a
+  translation unit. (#GH54215)
+
 Improvements to Clang's time-trace
 ----------------------------------
 
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 6d2fae551566f..8981d30475dde 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -6528,6 +6528,9 @@ def err_inline_declaration_block_scope : Error<
   "inline declaration of %0 not allowed in block scope">;
 def err_static_non_static : Error<
   "static declaration of %0 follows non-static declaration">;
+def err_internal_extern_mismatch : Error<
+  "variable %0 declared with both internal and external linkage "
+  "in the same translation unit; behavior is undefined">;
 def err_different_language_linkage : Error<
   "declaration of %0 has a different language linkage">;
 def ext_retained_language_linkage : Extension<
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 9f8fc5a187b0a..19cc36446eedb 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -4800,6 +4800,24 @@ void Sema::MergeVarDecl(VarDecl *New, LookupResult &Previous) {
       return New->setInvalidDecl();
     }
   }
+
+  // C11 6.2.2p7:
+  //   If, within a translation unit, the same identifier appears with both
+  //   internal and external linkage, the behavior is undefined.
+  //
+  // This can occur when an extern declaration in block scope finds a file-scope
+  // static declaration, but a no-linkage local variable shadowed the static,
+  // preventing linkage inheritance per C11 6.2.2p4.
+  if (New->isLocalVarDecl() && New->hasExternalStorage() &&
+      Old->isFileVarDecl() && Old->hasLinkage() &&
+      Previous.isShadowed() &&
+      Old->getFormalLinkage() == Linkage::Internal) {
+    Diag(New->getLocation(), diag::err_internal_extern_mismatch)
+        << New->getDeclName();
+    Diag(OldLocation, PrevDiag);
+    return New->setInvalidDecl();
+  }
+
   // C99 6.2.2p4:
   //   For an identifier declared with the storage-class specifier
   //   extern in a scope in which a prior declaration of that
diff --git a/clang/test/Sema/linkage-internal-extern.c b/clang/test/Sema/linkage-internal-extern.c
new file mode 100644
index 0000000000000..42591c5b21f0b
--- /dev/null
+++ b/clang/test/Sema/linkage-internal-extern.c
@@ -0,0 +1,36 @@
+// RUN: %clang_cc1 -verify -fsyntax-only %s
+
+// C11 6.2.2p7: same identifier with both internal and external linkage is UB.
+
+// Conflicting linkage (UB)
+
+static int x; // expected-note {{previous}}
+void test_basic_shadow(void) {
+    int x;
+    { extern int x; } // expected-error {{declared with both internal and external linkage}}
+}
+
+static int y; // expected-note {{previous}}
+void test_deep_nesting(void) {
+    int y;
+    { int y; { { extern int y; } } } // expected-error {{declared with both internal and external linkage}}
+}
+
+static int p; // expected-note {{previous}}
+void test_param_shadow(int p) {
+    { extern int p; } // expected-error {{declared with both internal and external linkage}}
+}
+
+// Valid cases
+
+static int a;
+void test_no_shadow(void) {
+    extern int a;
+}
+
+void test_no_file_scope(void) {
+    for (static int b = 0;;) {
+        extern int b;
+        break;
+    }
+}

>From c06f72132c84a5221c18f03ea640dd7b61825967 Mon Sep 17 00:00:00 2001
From: flash1729 <sherlockedaditya at gmail.com>
Date: Wed, 15 Apr 2026 01:07:53 +0530
Subject: [PATCH 2/4] fixup! [Clang] Diagnose UB when identifier has both
 internal and external linkage

---
 clang/lib/Sema/SemaDecl.cpp | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 19cc36446eedb..bfe7d8cae888a 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -4809,8 +4809,7 @@ void Sema::MergeVarDecl(VarDecl *New, LookupResult &Previous) {
   // static declaration, but a no-linkage local variable shadowed the static,
   // preventing linkage inheritance per C11 6.2.2p4.
   if (New->isLocalVarDecl() && New->hasExternalStorage() &&
-      Old->isFileVarDecl() && Old->hasLinkage() &&
-      Previous.isShadowed() &&
+      Old->isFileVarDecl() && Old->hasLinkage() && Previous.isShadowed() &&
       Old->getFormalLinkage() == Linkage::Internal) {
     Diag(New->getLocation(), diag::err_internal_extern_mismatch)
         << New->getDeclName();

>From 6e78e961340bf9e86b3b99863911e4c21a4e7c57 Mon Sep 17 00:00:00 2001
From: flash1729 <sherlockedaditya at gmail.com>
Date: Wed, 15 Apr 2026 01:39:21 +0530
Subject: [PATCH 3/4] fixup! [Clang] Diagnose UB when identifier has both
 internal and external linkage

---
 clang/lib/Sema/SemaDecl.cpp | 6 +++---
 clang/test/C/C2y/n3410.c    | 8 +++-----
 2 files changed, 6 insertions(+), 8 deletions(-)

diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index bfe7d8cae888a..26bd18ade5348 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -4808,9 +4808,9 @@ void Sema::MergeVarDecl(VarDecl *New, LookupResult &Previous) {
   // This can occur when an extern declaration in block scope finds a file-scope
   // static declaration, but a no-linkage local variable shadowed the static,
   // preventing linkage inheritance per C11 6.2.2p4.
-  if (New->isLocalVarDecl() && New->hasExternalStorage() &&
-      Old->isFileVarDecl() && Old->hasLinkage() && Previous.isShadowed() &&
-      Old->getFormalLinkage() == Linkage::Internal) {
+  if (!getLangOpts().CPlusPlus && New->isLocalVarDecl() &&
+      New->hasExternalStorage() && Old->isFileVarDecl() && Old->hasLinkage() &&
+      Previous.isShadowed() && Old->getFormalLinkage() == Linkage::Internal) {
     Diag(New->getLocation(), diag::err_internal_extern_mismatch)
         << New->getDeclName();
     Diag(OldLocation, PrevDiag);
diff --git a/clang/test/C/C2y/n3410.c b/clang/test/C/C2y/n3410.c
index e1cb41f375b82..c7162f6d324d8 100644
--- a/clang/test/C/C2y/n3410.c
+++ b/clang/test/C/C2y/n3410.c
@@ -24,19 +24,17 @@ void func2() {
   extern int b; // Ok
 }
 
-static int c, d;
+static int c, d; // expected-note {{previous definition is here}} expected-note {{previous definition is here}}
 void func3() {
   int c; // no linkage, different object from the one declared above.
   for (int d;;) {
     // This 'c' is the same as the one declared at file scope, but because of
     // the local scope 'c', the file scope 'c' is not visible.
-    // FIXME: This should be diagnosed under N3410.
-    extern int c;
+    extern int c; // expected-error {{declared with both internal and external linkage}}
     // This 'd' is the same as the one declared at file scope as well, but
     // because of the 'd' declared within the for loop, the file scope 'd' is
     // also not visible, same as with 'c'.
-    // FIXME: This should be diagnosed under N3410.
-    extern int d;
+    extern int d; // expected-error {{declared with both internal and external linkage}}
   }
   for (static int e;;) {
     extern int e; // Ok for the same reason as 'b' above.

>From d5cf291cb982df1d1277a46dea45ad8f71678aff Mon Sep 17 00:00:00 2001
From: flash1729 <sherlockedaditya at gmail.com>
Date: Sun, 19 Apr 2026 13:30:18 +0530
Subject: [PATCH 4/4] fixup! [Clang] Diagnose UB when identifier has both
 internal and external linkage

---
 clang/docs/ReleaseNotes.rst                   |  9 ++++++---
 .../clang/Basic/DiagnosticSemaKinds.td        |  2 +-
 clang/lib/Sema/SemaDecl.cpp                   | 10 ++++++----
 clang/test/C/C2y/n3410.c                      |  2 +-
 clang/test/Sema/linkage-internal-extern.cpp   | 19 +++++++++++++++++++
 clang/www/c_status.html                       |  2 +-
 6 files changed, 34 insertions(+), 10 deletions(-)
 create mode 100644 clang/test/Sema/linkage-internal-extern.cpp

diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index f1efd5f8c3b35..715e8f00a4676 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -147,6 +147,12 @@ C Language Changes
 C2y Feature Support
 ^^^^^^^^^^^^^^^^^^^
 
+- Clang now diagnoses the use of the same identifier with both internal and
+  external linkage within a translation unit, as made ill-formed by
+  `N3410 <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3410.pdf>`_.
+  This is also diagnosed in older C language modes as the behavior was
+  undefined prior to C2y. (#GH54215)
+
 C23 Feature Support
 ^^^^^^^^^^^^^^^^^^^
 - Clang now allows C23 ``constexpr`` struct member access through the dot operator in constant expressions. (#GH178349)
@@ -366,9 +372,6 @@ Improvements to Clang's diagnostics
   code can automatically be made portable to other host platforms that don't
   support backslashes.
 
-- Clang now diagnoses the undefined behavior described in C11 6.2.2p7 where
-  the same identifier appears with both internal and external linkage within a
-  translation unit. (#GH54215)
 
 Improvements to Clang's time-trace
 ----------------------------------
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 8981d30475dde..03c43f9431df6 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -6530,7 +6530,7 @@ def err_static_non_static : Error<
   "static declaration of %0 follows non-static declaration">;
 def err_internal_extern_mismatch : Error<
   "variable %0 declared with both internal and external linkage "
-  "in the same translation unit; behavior is undefined">;
+  "in the same translation unit%select{; behavior is undefined|}1">;
 def err_different_language_linkage : Error<
   "declaration of %0 has a different language linkage">;
 def ext_retained_language_linkage : Extension<
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 26bd18ade5348..ee371466fbb75 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -4801,18 +4801,20 @@ void Sema::MergeVarDecl(VarDecl *New, LookupResult &Previous) {
     }
   }
 
-  // C11 6.2.2p7:
+  // C2y 6.2.2p7 (N3410):
   //   If, within a translation unit, the same identifier appears with both
-  //   internal and external linkage, the behavior is undefined.
+  //   internal and external linkage, the program is ill-formed.
+  //
+  // In C11 through C23, this was undefined behavior (C11 6.2.2p7).
   //
   // This can occur when an extern declaration in block scope finds a file-scope
   // static declaration, but a no-linkage local variable shadowed the static,
-  // preventing linkage inheritance per C11 6.2.2p4.
+  // preventing linkage inheritance per C2y 6.2.2p4.
   if (!getLangOpts().CPlusPlus && New->isLocalVarDecl() &&
       New->hasExternalStorage() && Old->isFileVarDecl() && Old->hasLinkage() &&
       Previous.isShadowed() && Old->getFormalLinkage() == Linkage::Internal) {
     Diag(New->getLocation(), diag::err_internal_extern_mismatch)
-        << New->getDeclName();
+        << New->getDeclName() << getLangOpts().C2y;
     Diag(OldLocation, PrevDiag);
     return New->setInvalidDecl();
   }
diff --git a/clang/test/C/C2y/n3410.c b/clang/test/C/C2y/n3410.c
index c7162f6d324d8..27baf9b44458a 100644
--- a/clang/test/C/C2y/n3410.c
+++ b/clang/test/C/C2y/n3410.c
@@ -1,6 +1,6 @@
 // RUN: %clang_cc1 -verify -std=c2y -Wall -pedantic -Wno-unused %s
 
-/* WG14 N3410: No
+/* WG14 N3410: Yes
  * Slay Some Earthly Demons XI
  *
  * It is now ill-formed for the same identifier within a TU to have both
diff --git a/clang/test/Sema/linkage-internal-extern.cpp b/clang/test/Sema/linkage-internal-extern.cpp
new file mode 100644
index 0000000000000..ab084767305fd
--- /dev/null
+++ b/clang/test/Sema/linkage-internal-extern.cpp
@@ -0,0 +1,19 @@
+// RUN: %clang_cc1 -verify -fsyntax-only -std=c++17 %s
+
+// expected-no-diagnostics
+
+// CWG 426 / [basic.link]p6: the same identifier with both internal and
+// external linkage should be ill-formed in C++, but Clang does not yet
+// diagnose this due to ABI break concerns. This test documents current
+// behavior.
+
+static int x;
+void test_shadow(void) {
+    int x;
+    {
+        // FIXME: Per CWG 426, this should be ill-formed because the
+        // file-scope 'x' has internal linkage but this 'extern' gets
+        // external linkage due to the local shadow.
+        extern int x;
+    }
+}
diff --git a/clang/www/c_status.html b/clang/www/c_status.html
index a909a1fb3013e..f584b0f30ac58 100644
--- a/clang/www/c_status.html
+++ b/clang/www/c_status.html
@@ -283,7 +283,7 @@ <h2 id="c2y">C2y implementation status</h2>
     <tr>
       <td>Slay Some Earthly Demons XI</td>
       <td><a href="https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3410.pdf">N3410</a></td>
-      <td class="none" align="center">No</td>
+      <td class="full" align="center">Clang 21</td>
 	</tr>
     <tr>
       <td>Slay Some Earthly Demons XII</td>



More information about the cfe-commits mailing list