Skip to content

[Clang] Stop changing DC when instantiating dependent friend specializations #139436

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

zyn0217
Copy link
Contributor

@zyn0217 zyn0217 commented May 11, 2025

Since 346077a, we began using the primary template's lexical DeclContext for template arguments in order to properly instantiate a friend definition.

There is a missed peculiar case, as in a friend template is specialized within a dependent context. In this scenario, the primary template is not a definition, whereas the specialization is. So the primary template's DeclContext doesn't provide any meaningful for instantiation.

Fixes #139052

Since 346077a, we began using the primary template's lexical
DeclContext for template arguments in order to properly instantiate
a friend definition.

There is a missed peculiar case, as in a friend template is specialized
within a dependent context. In this scenario, the primary template is
not a definition, whereas the specialization is. So the primary
template's DeclContext doesn't provide any meaningful for instantiation.
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels May 11, 2025
@llvmbot
Copy link
Member

llvmbot commented May 11, 2025

@llvm/pr-subscribers-clang

Author: Younan Zhang (zyn0217)

Changes

Since 346077a, we began using the primary template's lexical DeclContext for template arguments in order to properly instantiate a friend definition.

There is a missed peculiar case, as in a friend template is specialized within a dependent context. In this scenario, the primary template is not a definition, whereas the specialization is. So the primary template's DeclContext doesn't provide any meaningful for instantiation.

Fixes #139052


Full diff: https://github.com/llvm/llvm-project/pull/139436.diff

4 Files Affected:

  • (modified) clang/docs/ReleaseNotes.rst (+2)
  • (modified) clang/lib/Sema/SemaDeclCXX.cpp (+1-1)
  • (modified) clang/lib/Sema/SemaTemplateInstantiateDecl.cpp (+7-5)
  • (modified) clang/test/SemaTemplate/GH55509.cpp (+38)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 1d0896f585fb4..c75ad0be8a8e6 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -576,6 +576,8 @@ Bug Fixes in This Version
   ``#include`` directive. (#GH138094)
 - Fixed a crash during constant evaluation involving invalid lambda captures
   (#GH138832)
+- Fixed a crash when instantiating an invalid dependent friend template specialization.
+  (#GH139052)
 
 Bug Fixes to Compiler Builtins
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index cbccb567e2adf..d915448d0feb1 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -18740,7 +18740,7 @@ NamedDecl *Sema::ActOnFriendFunctionDecl(Scope *S, Declarator &D,
         // a template-id, the function name is not unqualified because these is
         // no name. While the wording requires some reading in-between the
         // lines, GCC, MSVC, and EDG all consider a friend function
-        // specialization definitions // to be de facto explicit specialization
+        // specialization definitions to be de facto explicit specialization
         // and diagnose them as such.
       } else if (isTemplateId) {
         Diag(NameInfo.getBeginLoc(), diag::err_friend_specialization_def);
diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
index 08b3a423d1526..a8c3b28f8d185 100644
--- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -5751,14 +5751,16 @@ void Sema::InstantiateFunctionDefinition(SourceLocation PointOfInstantiation,
     RebuildTypeSourceInfoForDefaultSpecialMembers();
     SetDeclDefaulted(Function, PatternDecl->getLocation());
   } else {
-    NamedDecl *ND = Function;
-    DeclContext *DC = ND->getLexicalDeclContext();
+    DeclContext *DC = Function->getLexicalDeclContext();
     std::optional<ArrayRef<TemplateArgument>> Innermost;
-    if (auto *Primary = Function->getPrimaryTemplate();
-        Primary &&
+    bool NeedDCFromPrimaryTemplate =
         !isGenericLambdaCallOperatorOrStaticInvokerSpecialization(Function) &&
         Function->getTemplateSpecializationKind() !=
-            TSK_ExplicitSpecialization) {
+            TSK_ExplicitSpecialization &&
+        !PatternDecl->getDependentSpecializationInfo();
+
+    if (auto *Primary = Function->getPrimaryTemplate();
+        Primary && NeedDCFromPrimaryTemplate) {
       auto It = llvm::find_if(Primary->redecls(),
                               [](const RedeclarableTemplateDecl *RTD) {
                                 return cast<FunctionTemplateDecl>(RTD)
diff --git a/clang/test/SemaTemplate/GH55509.cpp b/clang/test/SemaTemplate/GH55509.cpp
index 773a84305a0cd..f421e5a8fc237 100644
--- a/clang/test/SemaTemplate/GH55509.cpp
+++ b/clang/test/SemaTemplate/GH55509.cpp
@@ -110,3 +110,41 @@ namespace regression2 {
   }
   template void A<void>::f<long>();
 } // namespace regression2
+
+namespace GH139226 {
+
+struct FakeStream {};
+
+template <typename T>
+class BinaryTree;
+
+template <typename T>
+FakeStream& operator<<(FakeStream& os, BinaryTree<T>& b); // #1
+
+template <typename T>
+FakeStream& operator>>(FakeStream& os, BinaryTree<T>& b) {
+  return os;
+}
+
+template <typename T>
+struct BinaryTree {
+  T* root{};
+  // This template is described using a DependentSpecializationInfo, and its instantiations
+  // are tracked with TSK_ImplicitInstantiation kind.
+  // The primary template is declared at #1.
+  friend FakeStream& operator<< <T>(FakeStream& os, BinaryTree&) {
+    // expected-error@-1 {{friend function specialization cannot be defined}}
+    return os;
+  }
+
+  friend FakeStream& operator>> <T>(FakeStream& os, BinaryTree&);
+};
+
+void foo() {
+  FakeStream fakeout;
+  BinaryTree<int> a{};
+  fakeout << a;
+  fakeout >> a;
+}
+
+}

Copy link
Contributor

@cor3ntin cor3ntin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@zyn0217
Copy link
Contributor Author

zyn0217 commented May 13, 2025

@mizvekov It's not a big deal to turn them into explicit specializations after all... I pushed a commit

Copy link
Contributor

@mizvekov mizvekov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perfect, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Clang Crash When Defining operator<< as a Friend Template Function of a Class Template since clang 20
4 participants