[clang] [clang] Avoid caching linkage before redeclaration merging (PR #221476)

Vedran Miletić via cfe-commits cfe-commits at lists.llvm.org
Sat Sep 5 11:18:42 PDT 2026


https://github.com/vedranmiletic created https://github.com/llvm/llvm-project/pull/221476

Linkage queries made while a declaration is still being formed can cache a result based only on a previous declaration. Compute linkage without caching in these paths, and defer applying #pragma redefine_extname until after redeclaration merging.

Fixes #112737
Fixes #173464

Assisted-by: Codex (OpenAI ChatGPT)

>From 8c0bc0d5867814b85f676152fb6e70ee75283950 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Vedran=20Mileti=C4=87?= <vedran.miletic at mpcdf.mpg.de>
Date: Sat, 5 Sep 2026 19:00:16 +0200
Subject: [PATCH] [clang] Avoid caching linkage before redeclaration merging

Linkage queries made while a declaration is still being formed can cache
a result based only on a previous declaration. Compute linkage without
caching in these paths, and defer applying #pragma redefine_extname
until after redeclaration merging.

Fixes #112737
Fixes #173464

Assisted-by: Codex (OpenAI ChatGPT)
---
 clang/include/clang/AST/Decl.h                |  3 ++
 clang/lib/AST/Decl.cpp                        |  6 +++
 clang/lib/Sema/SemaDecl.cpp                   | 37 ++++++++++++-------
 clang/lib/Sema/SemaDeclAttr.cpp               |  3 +-
 .../SemaCXX/redecl-linkage-before-merge.cpp   | 13 +++++++
 5 files changed, 48 insertions(+), 14 deletions(-)
 create mode 100644 clang/test/SemaCXX/redecl-linkage-before-merge.cpp

diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h
index eafeeecac7794..ccae943ed4b6e 100644
--- a/clang/include/clang/AST/Decl.h
+++ b/clang/include/clang/AST/Decl.h
@@ -422,6 +422,9 @@ class NamedDecl : public Decl {
   /// those.
   Linkage getLinkageInternal() const;
 
+  /// Compute this entity's linkage without caching it.
+  Linkage computeLinkageInternal() const;
+
   /// Get the linkage from a semantic point of view. Entities in
   /// anonymous namespaces are external (in c++98).
   Linkage getFormalLinkage() const;
diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index a620e9f211ca6..78225f877dca3 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -1188,6 +1188,12 @@ Linkage NamedDecl::getLinkageInternal() const {
       .getLinkage();
 }
 
+Linkage NamedDecl::computeLinkageInternal() const {
+  return LinkageComputer{}
+      .computeLVForDecl(this, LVComputationKind::forLinkageOnly())
+      .getLinkage();
+}
+
 static bool isExportedFromModuleInterfaceUnit(const NamedDecl *D) {
   // FIXME: Handle isModulePrivate.
   switch (D->getModuleOwnershipKind()) {
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 2451baaf94703..b12c2c8858409 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -10670,19 +10670,6 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC,
     StringLiteral *SE = cast<StringLiteral>(E);
     NewFD->addAttr(
         AsmLabelAttr::Create(Context, SE->getString(), SE->getStrTokenLoc(0)));
-  } else if (!ExtnameUndeclaredIdentifiers.empty()) {
-    llvm::MapVector<IdentifierInfo *, AsmLabelAttr *>::iterator I =
-        ExtnameUndeclaredIdentifiers.find(NewFD->getIdentifier());
-    if (I != ExtnameUndeclaredIdentifiers.end()) {
-      if (isDeclExternC(NewFD)) {
-        NewFD->addAttr(I->second);
-        ExtnameUndeclaredIdentifiers.erase(I);
-      } else if (NewFD->getDeclContext()
-                     ->getRedeclContext()
-                     ->isTranslationUnit())
-        Diag(NewFD->getLocation(), diag::warn_redefine_extname_not_applied)
-            << /*Variable*/0 << NewFD;
-    }
   }
 
   // Copy the parameter declarations from the declarator D to the function
@@ -10850,6 +10837,26 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC,
     }
   }
 
+  // This linkage query must happen after merging previous declarations.
+  auto ApplyPragmaRedefineExtname = [&] {
+    if (D.getAsmLabel() || ExtnameUndeclaredIdentifiers.empty())
+      return;
+
+    auto I = ExtnameUndeclaredIdentifiers.find(NewFD->getIdentifier());
+    if (I == ExtnameUndeclaredIdentifiers.end())
+      return;
+
+    if (isDeclExternC(NewFD)) {
+      NewFD->addAttr(I->second);
+      ExtnameUndeclaredIdentifiers.erase(I);
+    } else if (NewFD->getDeclContext()
+                   ->getRedeclContext()
+                   ->isTranslationUnit()) {
+      Diag(NewFD->getLocation(), diag::warn_redefine_extname_not_applied)
+          << /*Variable*/ 0 << NewFD;
+    }
+  };
+
   if (!getLangOpts().CPlusPlus) {
     // Perform semantic checking on the function declaration.
     if (!NewFD->isInvalidDecl() && NewFD->isMain())
@@ -10869,6 +10876,8 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC,
             Previous.getResultKind() != LookupResultKind::FoundOverloaded) &&
            "previous declaration set still overloaded");
 
+    ApplyPragmaRedefineExtname();
+
     // Diagnose no-prototype function declarations with calling conventions that
     // don't support variadic calls. Only do this in C and do it after merging
     // possibly prototyped redeclarations.
@@ -11038,6 +11047,8 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC,
             Previous.getResultKind() != LookupResultKind::FoundOverloaded) &&
            "previous declaration set still overloaded");
 
+    ApplyPragmaRedefineExtname();
+
     NamedDecl *PrincipalDecl = (FunctionTemplate
                                 ? cast<NamedDecl>(FunctionTemplate)
                                 : NewFD);
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 106605962171b..c096c1a467cb4 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -1809,7 +1809,8 @@ static void handleAliasAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
     }
   } else {
     const auto *VD = cast<VarDecl>(D);
-    if (VD->isThisDeclarationADefinition() && VD->isExternallyVisible()) {
+    if (VD->isThisDeclarationADefinition() &&
+        isExternallyVisible(VD->computeLinkageInternal())) {
       S.Diag(AL.getLoc(), diag::err_alias_is_definition) << VD << 0;
       return;
     }
diff --git a/clang/test/SemaCXX/redecl-linkage-before-merge.cpp b/clang/test/SemaCXX/redecl-linkage-before-merge.cpp
new file mode 100644
index 0000000000000..b9d8d05f124ca
--- /dev/null
+++ b/clang/test/SemaCXX/redecl-linkage-before-merge.cpp
@@ -0,0 +1,13 @@
+// RUN: %clang_cc1 -std=c++20 -fsyntax-only -verify %s
+
+// https://github.com/llvm/llvm-project/issues/173464
+// A linkage query before redeclaration merging must not cache a result that
+// depends on a previous declaration.
+typedef float v4f __attribute__((vector_size(16)));
+extern const v4f kAlias;
+const v4f kAlias __attribute__((alias("kOne")));
+
+// https://github.com/llvm/llvm-project/issues/112737
+#pragma redefine_extname foo_cpp bar_cpp
+static int foo_cpp(); // expected-warning {{#pragma redefine_extname is applicable to external C declarations only; not applied to function 'foo_cpp'}}
+extern int foo_cpp() { return 1; } // expected-warning {{#pragma redefine_extname is applicable to external C declarations only; not applied to function 'foo_cpp'}}



More information about the cfe-commits mailing list