Author: Aditya Medhane
Date: 2026-08-12T02:25:08+05:30
New Revision: a5fa8c34d9a07076a6cda4d2bcb85923787ce7f8

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

LOG: [Clang] Reland "Diagnose UB and emit error when identifier has both 
internal and external linkage" (#193567)

C11 6.2.2p7 makes it undefined behavior for the same identifier to
appear with both internal and external linkage in a translation unit;
C2y N3410 makes this ill-formed. Clang now diagnoses it as an error in
all C language modes, noting the undefined behavior in pre-C2y modes.

Sema::MergeVarDecl uses LookupResult::isShadowed() to detect when a
block-scope extern declaration conflicts with an internal-linkage
declaration it reached through a shadowed lookup. C++ is unaffected:
a block-scope extern declaration targets the enclosing namespace scope
([dcl.meaning.general]/3.5, P1787R6) and inherits internal linkage, so
no conflict arises.

Fixes #54215

Added: 
    clang/test/Sema/linkage-internal-extern.cpp

Modified: 
    clang/docs/ReleaseNotes.md
    clang/include/clang/Basic/DiagnosticSemaKinds.td
    clang/lib/Sema/SemaDecl.cpp
    clang/test/C/C2y/n3410.c
    clang/www/c_status.html

Removed: 
    


################################################################################
diff  --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 55fca8c9a6658..da0c20487b78e 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -164,6 +164,12 @@ features cannot lower the translation-unit ABI level;
   }
 ```
 
+- 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
 
 ### Objective-C Language Changes

diff  --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td 
b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index fc30db9f16887..b314c17ad27bd 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -6590,7 +6590,11 @@ def err_inline_decl_follows_def : Error<
 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">;
+  "static declaration of %0 follows non-static declaration"
+  "%select{|; behavior is undefined}1">;
+def err_internal_extern_mismatch : Error<
+  "variable %0%select{| cannot be}1 declared with external linkage following "
+  "a declaration with internal linkage%select{; behavior is undefined|}1">;
 def err_
diff erent_language_linkage : Error<
   "declaration of %0 has a 
diff erent language linkage">;
 def ext_retained_language_linkage : Extension<

diff  --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index c5dcdee7dc5dd..032737c7a191d 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -3800,7 +3800,8 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, NamedDecl 
*&OldD, Scope *S,
       Diag(New->getLocation(), diag::ext_static_non_static) << New;
       Diag(OldLocation, PrevDiag) << Old << Old->getType();
     } else {
-      Diag(New->getLocation(), diag::err_static_non_static) << New;
+      Diag(New->getLocation(), diag::err_static_non_static)
+          << New << /*MixedLinkageUB=*/false;
       Diag(OldLocation, PrevDiag) << Old << Old->getType();
       return true;
     }
@@ -4821,12 +4822,37 @@ void Sema::MergeVarDecl(VarDecl *New, LookupResult 
&Previous) {
           << New->getDeclName();
       Diag(OldLocation, PrevDiag);
     } else {
+      // This is the same internal/external linkage conflict as C2y 6.7.1p7;
+      // before C2y it was undefined behavior (C11 6.2.2p7), so note that in
+      // the older C language modes.
       Diag(New->getLocation(), diag::err_static_non_static)
-          << New->getDeclName();
+          << New->getDeclName()
+          << (!getLangOpts().CPlusPlus && !getLangOpts().C2y);
       Diag(OldLocation, PrevDiag);
       return New->setInvalidDecl();
     }
   }
+
+  // C2y 6.7.1p7: an identifier shall not appear with both internal and
+  // external linkage within a translation unit. Before C2y this was UB
+  // (C11 6.2.2p7).
+  //
+  // In C, a local shadow prevents a block-scope extern from inheriting the
+  // file-scope static's internal linkage (C2y 6.2.2p6), so it defaults to
+  // external linkage, creating the conflict.
+  //
+  // In C++, block-scope extern declarations target the enclosing namespace
+  // scope ([dcl.meaning.general]/3.5), bypassing local shadows entirely, so
+  // the extern always inherits internal linkage. No conflict arises.
+  if (!getLangOpts().CPlusPlus && New->isLocalVarDecl() &&
+      New->hasExternalStorage() && Previous.isShadowed() &&
+      Old->getFormalLinkage() == Linkage::Internal) {
+    Diag(New->getLocation(), diag::err_internal_extern_mismatch)
+        << New->getDeclName() << getLangOpts().C2y;
+    Diag(OldLocation, diag::note_previous_declaration);
+    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/C/C2y/n3410.c b/clang/test/C/C2y/n3410.c
index e1cb41f375b82..60317bf1d3833 100644
--- a/clang/test/C/C2y/n3410.c
+++ b/clang/test/C/C2y/n3410.c
@@ -1,45 +1,128 @@
-// RUN: %clang_cc1 -verify -std=c2y -Wall -pedantic -Wno-unused %s
+// RUN: %clang_cc1 -verify-directives -verify=expected,c2y -std=c2y -Wall 
-pedantic -Wno-unused %s
+// RUN: %clang_cc1 -verify-directives -verify=expected,c89-23 -std=c23 -Wall 
-pedantic -Wno-unused %s
+// RUN: %clang_cc1 -verify-directives -verify=expected,c89-23 -std=c17 -Wall 
-pedantic -Wno-unused %s
+// RUN: %clang_cc1 -verify-directives -verify=expected,c89-23 -std=c11 -Wall 
-pedantic -Wno-unused %s
+// RUN: %clang_cc1 -verify-directives -verify=expected,c89-23 -std=c99 -Wall 
-pedantic -Wno-unused %s
+// RUN: %clang_cc1 -verify-directives -verify=expected,c89-23 -std=c89 -Wall 
-pedantic -Wno-unused -Wno-comment %s
 
-/* WG14 N3410: No
+/* WG14 N3410: Clang 24
  * Slay Some Earthly Demons XI
  *
  * It is now ill-formed for the same identifier within a TU to have both
  * internal and external linkage.
  */
 
-void func1() {
-  extern int a; // #a
+void func1(void) {
+  extern int a; /* #a */
 }
 
-// This 'a' is the same as the one declared extern above.
-static int a; /* expected-error {{static declaration of 'a' follows non-static 
declaration}}
+/* This 'a' is the same as the one declared extern above. */
+static int a; /* c2y-error {{static declaration of 'a' follows non-static 
declaration}}
+                 c89-23-error {{static declaration of 'a' follows non-static 
declaration; behavior is undefined}}
                  expected-note@#a {{previous declaration is here}}
                */
 
 static int b;
-void func2() {
-  // This 'b' is the same as the one declaraed static above, but this is not
-  // ill-formed because of C2y 6.2.2p4, which gives this variable internal
-  // linkage because the previous declaration had internal linkage.
-  extern int b; // Ok
+void func2(void) {
+  /* This 'b' is well-formed, because C2y 6.2.2p6 makes it "inherit" the
+     static linkage of `static int b` above, because the latter is visible.
+   */
+  extern int b; /* Ok */
 }
 
-static int c, d;
-void func3() {
-  int c; // no linkage, 
diff erent 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;
-    // 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;
+static int c, d; /* #c_d */
+void func3(void) {
+  int c; /* no linkage, 
diff erent object from the one declared above. */
+  {
+    int d; /* no linkage, 
diff erent object from the file-scope '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. */
+      extern int c; /* c2y-error {{variable 'c' cannot be declared with 
external linkage following a declaration with internal linkage}}
+                       c89-23-error {{variable 'c' declared with external 
linkage following a declaration with internal linkage; behavior is undefined}}
+                       expected-note@#c_d {{previous declaration is here}}
+                     */
+      /* This 'd' is the same as the one declared at file scope as well, but
+         because of the enclosing block-scope 'd', the file scope 'd' is also
+         not visible, same as with 'c'. */
+      extern int d; /* c2y-error {{variable 'd' cannot be declared with 
external linkage following a declaration with internal linkage}}
+                       c89-23-error {{variable 'd' declared with external 
linkage following a declaration with internal linkage; behavior is undefined}}
+                       expected-note@#c_d {{previous declaration is here}}
+                     */
+    }
   }
-  for (static int e;;) {
-    extern int e; // Ok for the same reason as 'b' above.
+  {
+    static int e;
+    {
+      extern int e; /* Ok for the same reason as 'b' above. */
+    }
   }
 }
 
+/* A function parameter shadows the file-scope 'p' the same way a local
+   variable does, so the block-scope 'extern' does not inherit internal
+   linkage and conflicts. */
+static int p; /* #p */
+void func4(int p) {
+  {
+    extern int p; /* c2y-error {{variable 'p' cannot be declared with external 
linkage following a declaration with internal linkage}}
+                     c89-23-error {{variable 'p' declared with external 
linkage following a declaration with internal linkage; behavior is undefined}}
+                     expected-note@#p {{previous declaration is here}}
+                   */
+  }
+}
+
+static int q;
+void func5(void) {
+  /* No shadow intervenes here, so this 'q' inherits the internal linkage of
+     the file-scope 'q', which is fine. */
+  extern int q; /* #q */
+  {
+    int q; /* no linkage; shadows the declarations above. */
+    {
+      /* The file-scope 'q' is now hidden, so this 'extern' has external
+         linkage and conflicts with the internal-linkage declaration above. */
+      extern int q; /* c2y-error {{variable 'q' cannot be declared with 
external linkage following a declaration with internal linkage}}
+                       c89-23-error {{variable 'q' declared with external 
linkage following a declaration with internal linkage; behavior is undefined}}
+                       expected-note@#q {{previous declaration is here}}
+                     */
+    }
+  }
+}
+
+void func6(void) {
+  /* No file-scope declaration of 'r' exists, so the block-scope 'extern' just
+     has external linkage and there is no conflict. */
+  {
+    int r; /* no linkage. */
+    {
+      extern int r; /* Ok */
+    }
+  }
+}
+
+static int s; /* #s */
+void func7(void) {
+  {
+    /* The file-scope 's' is visible here, so this 'extern' inherits its
+       internal linkage, which may be surprising. */
+    extern int s; /* Ok */
+  }
+  {
+    int s; /* no linkage; shadows the file-scope 's'. */
+    {
+      /* The file-scope 's' is hidden by the local 's' above, so this 'extern'
+         has external linkage and conflicts.
+
+         This tests that we do not accidentally note the internal linkage
+         declaration using the 'extern' specifier in the function scope; we
+         want the note to point to the declaration using the 'static'
+         specifier at global scope because the function scope identifier is
+         hidden at this point. */
+      extern int s; /* c2y-error {{variable 's' cannot be declared with 
external linkage following a declaration with internal linkage}}
+                       c89-23-error {{variable 's' declared with external 
linkage following a declaration with internal linkage; behavior is undefined}}
+                       expected-note@#s {{previous declaration is here}}
+                     */
+    }
+  }
+}

diff  --git a/clang/test/Sema/linkage-internal-extern.cpp 
b/clang/test/Sema/linkage-internal-extern.cpp
new file mode 100644
index 0000000000000..deace77566db0
--- /dev/null
+++ b/clang/test/Sema/linkage-internal-extern.cpp
@@ -0,0 +1,41 @@
+// RUN: %clang_cc1 -ast-dump %s | FileCheck %s
+
+// In C++, block-scope extern declarations target the enclosing namespace
+// scope ([dcl.meaning.general]/3.5), so they match against the namespace-scope
+// static despite local shadows and inherit internal linkage. No conflict 
arises.
+//
+// This 
diff ers from C, where a local shadow breaks linkage inheritance,
+// causing the conflict diagnosed by err_internal_extern_mismatch.
+
+// Example adapted from [basic.link]/6.
+static void f();
+// CHECK: FunctionDecl {{.*}} f 'void ()' static internal-linkage
+static int i = 0;
+// CHECK: VarDecl {{.*}} i 'int' static cinit internal-linkage
+void g() {
+// CHECK: FunctionDecl {{.*}} g 'void ()' external-linkage
+    extern void f();
+    // CHECK: FunctionDecl {{.*}} prev {{.*}} f 'void ()' extern 
internal-linkage
+    int i;
+    // CHECK: VarDecl {{.*}} i 'int'{{$}}
+    {
+        extern void f();
+        // CHECK: FunctionDecl {{.*}} prev {{.*}} f 'void ()' extern 
internal-linkage
+        extern int i;
+        // CHECK: VarDecl {{.*}} prev {{.*}} i 'int' extern internal-linkage
+    }
+}
+
+// Block-scope function declarations behave identically without extern
+// (C11 6.2.2p5, C++ [dcl.meaning.general]/3.5).
+static void h();
+// CHECK: FunctionDecl {{.*}} h 'void ()' static internal-linkage
+void g2() {
+// CHECK: FunctionDecl {{.*}} g2 'void ()' external-linkage
+    int h;
+    // CHECK: VarDecl {{.*}} h 'int'{{$}}
+    {
+        void h();
+        // CHECK: FunctionDecl {{.*}} prev {{.*}} h 'void ()' internal-linkage
+    }
+}

diff  --git a/clang/www/c_status.html b/clang/www/c_status.html
index f478a56857fa3..6984de2bd1480 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="unreleased" align="center">Clang 24</td>
        </tr>
     <tr>
       <td>Slay Some Earthly Demons XII</td>


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

Reply via email to