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

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

This patch adds a check in `Sema::MergeVarDecl` that uses
`LookupResult::isShadowed()` to detect when a block-scope `extern` found
a file-scope `static` through a shadow, and emits an error.

Fixes #54215
This commit is contained in:
Aditya Medhane 2026-04-22 00:35:27 +05:30 committed by GitHub
parent facb9ab34f
commit 1e2175ec4d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 88 additions and 7 deletions

View file

@ -155,6 +155,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)

View file

@ -6534,6 +6534,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<

View file

@ -4802,6 +4802,25 @@ void Sema::MergeVarDecl(VarDecl *New, LookupResult &Previous) {
return New->setInvalidDecl();
}
}
// C2y 6.7.1p7:
// Within a translation unit, the same identifier shall not appear with
// both internal and external linkage.
//
// 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 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() << 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

View file

@ -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.

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -283,7 +283,7 @@ conformance.</p>
<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>