https://github.com/flash1729 updated https://github.com/llvm/llvm-project/pull/193567
>From 210026569ec49c47d38ee0e3e540b1e131c71a52 Mon Sep 17 00:00:00 2001 From: flash1729 <[email protected]> Date: Wed, 15 Apr 2026 00:42:00 +0530 Subject: [PATCH 1/3] [Clang] Diagnose mixed internal/external linkage in C (reland) 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.2p4), so it defaults to external linkage, creating the conflict. This relands the original patch with fixes: - Corrected C++ test: CWG 426 was superseded by P1787R6; Clang applies P1787R6 semantics so no linkage conflict exists in C++ - Added [dcl.meaning.general]/3.5 citation for C++ exclusion - Restored C2y 6.2.2p4 citation Fixes #54215 --- clang/docs/ReleaseNotes.rst | 6 ++++ .../clang/Basic/DiagnosticSemaKinds.td | 3 ++ clang/lib/Sema/SemaDecl.cpp | 21 +++++++++++ clang/test/C/C2y/n3410.c | 10 +++--- clang/test/Sema/linkage-internal-extern.c | 36 +++++++++++++++++++ clang/test/Sema/linkage-internal-extern.cpp | 22 ++++++++++++ clang/www/c_status.html | 2 +- 7 files changed, 93 insertions(+), 7 deletions(-) create mode 100644 clang/test/Sema/linkage-internal-extern.c create mode 100644 clang/test/Sema/linkage-internal-extern.cpp diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 843a01f57c39f..060d68ef7f4d0 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -187,6 +187,12 @@ C2y Feature Support ``stdc_rotate_left_{uc,us,ui,ul,ull}`` and ``stdc_rotate_right_{uc,us,ui,ul,ull}``. +- 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) diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 9cd3a5b2fc049..2a942ae204dd8 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -6535,6 +6535,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%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 62cb9360d1322..1c29ec1a2bbd7 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -4822,6 +4822,27 @@ void Sema::MergeVarDecl(VarDecl *New, LookupResult &Previous) { 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.2p4), 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() && Old->isFileVarDecl() && Old->hasLinkage() && + Previous.isShadowed() && Old->getFormalLinkage() == Linkage::Internal) { + Diag(New->getLocation(), diag::err_internal_extern_mismatch) + << New->getDeclName() << getLangOpts().C2y; + 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/C/C2y/n3410.c b/clang/test/C/C2y/n3410.c index e1cb41f375b82..7ce3c2916d300 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: Clang 23 * Slay Some Earthly Demons XI * * It is now ill-formed for the same identifier within a TU to have both @@ -24,19 +24,17 @@ void func2() { extern int b; // Ok } -static int c, d; +static int c, d; // expected-note 2 {{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. 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; + } +} diff --git a/clang/test/Sema/linkage-internal-extern.cpp b/clang/test/Sema/linkage-internal-extern.cpp new file mode 100644 index 0000000000000..242f38978b4c5 --- /dev/null +++ b/clang/test/Sema/linkage-internal-extern.cpp @@ -0,0 +1,22 @@ +// RUN: %clang_cc1 -verify -fsyntax-only -std=c++17 %s + +// expected-no-diagnostics + +// In C++, block-scope extern declarations target the enclosing namespace +// scope ([dcl.meaning.general]/3.5), so they find the file-scope static +// despite local shadows and inherit internal linkage. No conflict arises. +// +// This differs 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(); +static int i = 0; +void g() { + extern void f(); // internal linkage + int i; // no linkage + { + extern void f(); // internal linkage + extern int i; // internal linkage + } +} diff --git a/clang/www/c_status.html b/clang/www/c_status.html index 5270033471167..4b3695831de58 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 23</td> </tr> <tr> <td>Slay Some Earthly Demons XII</td> >From 5b852b777b3030ae30a02ad68a14fd8d058b0fae Mon Sep 17 00:00:00 2001 From: flash1729 <[email protected]> Date: Sat, 25 Apr 2026 23:16:13 +0530 Subject: [PATCH 2/3] fixup! [Clang] Diagnose mixed internal/external linkage in C (reland) --- clang/lib/Sema/SemaDecl.cpp | 4 ++-- clang/test/Sema/linkage-internal-extern.c | 12 +++++++++++- clang/test/Sema/linkage-internal-extern.cpp | 4 ++-- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index 1c29ec1a2bbd7..c80faf7c5b902 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -4835,8 +4835,8 @@ void Sema::MergeVarDecl(VarDecl *New, LookupResult &Previous) { // 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() && Old->isFileVarDecl() && Old->hasLinkage() && - Previous.isShadowed() && Old->getFormalLinkage() == Linkage::Internal) { + New->hasExternalStorage() && Old->hasLinkage() && Previous.isShadowed() && + Old->getFormalLinkage() == Linkage::Internal) { Diag(New->getLocation(), diag::err_internal_extern_mismatch) << New->getDeclName() << getLangOpts().C2y; Diag(OldLocation, PrevDiag); diff --git a/clang/test/Sema/linkage-internal-extern.c b/clang/test/Sema/linkage-internal-extern.c index 42591c5b21f0b..06f30e60261e2 100644 --- a/clang/test/Sema/linkage-internal-extern.c +++ b/clang/test/Sema/linkage-internal-extern.c @@ -21,6 +21,16 @@ void test_param_shadow(int p) { { extern int p; } // expected-error {{declared with both internal and external linkage}} } +static int z; +void test_chained_shadow(void) { + extern int z; // expected-note {{previous}} #2: internal linkage + { + int z; // shadow: no linkage + { extern int z; } // expected-error {{declared with both internal and external linkage}} + } +} + + // Valid cases static int a; @@ -29,7 +39,7 @@ void test_no_shadow(void) { } void test_no_file_scope(void) { - for (static int b = 0;;) { + for (int b = 0;;) { extern int b; break; } diff --git a/clang/test/Sema/linkage-internal-extern.cpp b/clang/test/Sema/linkage-internal-extern.cpp index 242f38978b4c5..f20822a5635ae 100644 --- a/clang/test/Sema/linkage-internal-extern.cpp +++ b/clang/test/Sema/linkage-internal-extern.cpp @@ -3,8 +3,8 @@ // expected-no-diagnostics // In C++, block-scope extern declarations target the enclosing namespace -// scope ([dcl.meaning.general]/3.5), so they find the file-scope static -// despite local shadows and inherit internal linkage. No conflict arises. +// 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 differs from C, where a local shadow breaks linkage inheritance, // causing the conflict diagnosed by err_internal_extern_mismatch. >From e4ae3215da4ad5b23a9f83528ee6710b577268c6 Mon Sep 17 00:00:00 2001 From: flash1729 <[email protected]> Date: Tue, 26 May 2026 16:58:29 +0530 Subject: [PATCH 3/3] fixup! [Clang] Diagnose mixed internal/external linkage in C (reland) --- clang/lib/Sema/SemaDecl.cpp | 2 +- clang/test/Sema/linkage-internal-extern.cpp | 33 ++++++++++++++++----- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index c80faf7c5b902..87f410927829e 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -4835,7 +4835,7 @@ void Sema::MergeVarDecl(VarDecl *New, LookupResult &Previous) { // 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() && Old->hasLinkage() && Previous.isShadowed() && + New->hasExternalStorage() && Previous.isShadowed() && Old->getFormalLinkage() == Linkage::Internal) { Diag(New->getLocation(), diag::err_internal_extern_mismatch) << New->getDeclName() << getLangOpts().C2y; diff --git a/clang/test/Sema/linkage-internal-extern.cpp b/clang/test/Sema/linkage-internal-extern.cpp index f20822a5635ae..27b9566620289 100644 --- a/clang/test/Sema/linkage-internal-extern.cpp +++ b/clang/test/Sema/linkage-internal-extern.cpp @@ -1,6 +1,4 @@ -// RUN: %clang_cc1 -verify -fsyntax-only -std=c++17 %s - -// expected-no-diagnostics +// RUN: %clang_cc1 -ast-dump -std=c++17 %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 @@ -11,12 +9,33 @@ // 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() { - extern void f(); // internal linkage - int i; // no linkage +// 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.stc]p5). +static void h(); +// CHECK: FunctionDecl {{.*}} h 'void ()' static internal-linkage +void g2() { +// CHECK: FunctionDecl {{.*}} g2 'void ()' external-linkage + int h; + // CHECK: VarDecl {{.*}} h 'int'{{$}} { - extern void f(); // internal linkage - extern int i; // internal linkage + void h(); + // CHECK: FunctionDecl {{.*}} prev {{.*}} h 'void ()' internal-linkage } } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
