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
