Skip to content

Conversation

@ganenkokb-yandex
Copy link
Contributor

@ganenkokb-yandex ganenkokb-yandex commented Oct 8, 2025

ASTImporter on importing template specialization with auto return type faces cycle when return type is not nested one, but typename from template arguments and other template.
There is code, that prevents cycle to auto return types when nested type declared. Solved case differs somehow from nested types, but have same solution with UsedDifferentProtoType - with delayed return type determining.

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Oct 8, 2025
@llvmbot
Copy link
Member

llvmbot commented Oct 8, 2025

@llvm/pr-subscribers-clang

Author: None (ganenkokb-yandex)

Changes

On importing template specialization with auto return type cycle occurs when return type is not nested one, but typename from template arguments and other template.
There is code, that prevents cycle to auto return types when nested type declared. Solved case differs somehow from nested types, but have same solution with UsedDifferentProtoType - with delayed return type determining.


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

3 Files Affected:

  • (modified) clang/include/clang/AST/ASTImporter.h (+1)
  • (modified) clang/lib/AST/ASTImporter.cpp (+8-1)
  • (modified) clang/unittests/AST/ASTImporterTest.cpp (+50)
diff --git a/clang/include/clang/AST/ASTImporter.h b/clang/include/clang/AST/ASTImporter.h
index 4a0ca45b785a9..eea4ccccb1600 100644
--- a/clang/include/clang/AST/ASTImporter.h
+++ b/clang/include/clang/AST/ASTImporter.h
@@ -254,6 +254,7 @@ class TypeSourceInfo;
     /// Declaration (from, to) pairs that are known not to be equivalent
     /// (which we have already complained about).
     NonEquivalentDeclSet NonEquivalentDecls;
+    llvm::DenseSet<const Decl *> DeclTypeCycles;
 
     using FoundDeclsTy = SmallVector<NamedDecl *, 2>;
     FoundDeclsTy findDeclsInToCtx(DeclContext *DC, DeclarationName Name);
diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp
index f43fa8c90ad3b..0dc2e1c3b4f8b 100644
--- a/clang/lib/AST/ASTImporter.cpp
+++ b/clang/lib/AST/ASTImporter.cpp
@@ -4035,7 +4035,8 @@ ExpectedDecl ASTNodeImporter::VisitFunctionDecl(FunctionDecl *D) {
     // E.g.: auto foo() { struct X{}; return X(); }
     // To avoid an infinite recursion when importing, create the FunctionDecl
     // with a simplified return type.
-    if (hasReturnTypeDeclaredInside(D)) {
+    if (hasReturnTypeDeclaredInside(D) ||
+      Importer.DeclTypeCycles.find(D) != Importer.DeclTypeCycles.end()) {
       FromReturnTy = Importer.getFromContext().VoidTy;
       UsedDifferentProtoType = true;
     }
@@ -4058,7 +4059,13 @@ ExpectedDecl ASTNodeImporter::VisitFunctionDecl(FunctionDecl *D) {
   }
 
   Error Err = Error::success();
+  if (!UsedDifferentProtoType) {
+    Importer.DeclTypeCycles.insert(D);
+  }
   auto T = importChecked(Err, FromTy);
+  if (!UsedDifferentProtoType) {
+    Importer.DeclTypeCycles.erase(D);
+  }
   auto TInfo = importChecked(Err, FromTSI);
   auto ToInnerLocStart = importChecked(Err, D->getInnerLocStart());
   auto ToEndLoc = importChecked(Err, D->getEndLoc());
diff --git a/clang/unittests/AST/ASTImporterTest.cpp b/clang/unittests/AST/ASTImporterTest.cpp
index e7160bcf2e0c2..5f7fcdf817ea0 100644
--- a/clang/unittests/AST/ASTImporterTest.cpp
+++ b/clang/unittests/AST/ASTImporterTest.cpp
@@ -3204,6 +3204,56 @@ TEST_P(ImportExpr, UnresolvedMemberExpr) {
                  compoundStmt(has(callExpr(has(unresolvedMemberExpr())))))))));
 }
 
+TEST_P(ImportExpr, CycleInAutoTemplateSpec) {
+  MatchVerifier<Decl> Verifier;
+  const char *Code = R"(
+  template <class _CharT>
+  struct basic_string {
+    using value_type = _CharT;
+  };
+
+  template<typename T>
+  struct basic_string_view {
+    using value_type = T;
+  };
+
+  using string_view = basic_string_view<char>;
+  using string = basic_string<char>;
+
+  template<typename T>
+  struct span {
+  };
+
+  template <typename StringT>
+  auto StrCatT(span<const StringT> pieces) {
+    basic_string<typename StringT::value_type> result;
+    return result;
+  }
+
+  string StrCat(span<const string_view> pieces) {
+      return StrCatT(pieces);
+  }
+
+  string StrCat(span<const string> pieces) {
+      return StrCatT(pieces);
+  }
+
+  template <typename T>
+  auto declToImport(T pieces) {
+    return StrCat(pieces);
+  }
+
+  void test() {
+    span<const string> pieces;
+    auto result = declToImport(pieces);
+  }
+)";
+  // This test reproduces the StrCatT recursion pattern with concepts and span
+  // that may cause infinite recursion during AST import due to circular dependencies
+  testImport(Code, Lang_CXX20, "", Lang_CXX20, Verifier,
+             functionTemplateDecl(hasName("declToImport")));
+}
+
 TEST_P(ImportExpr, ConceptNoRequirement) {
   MatchVerifier<Decl> Verifier;
   const char *Code = R"(

@github-actions
Copy link

github-actions bot commented Oct 8, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

@shafik
Copy link
Collaborator

shafik commented Oct 11, 2025

Your summary should mention ASTImporter and your subject should have [Clang][ASTImporter]

@shafik shafik requested a review from balazske October 11, 2025 02:08
@ganenkokb-yandex ganenkokb-yandex force-pushed the ctu_clangsa_type_cycle_fix branch from b76ab82 to 416fbca Compare October 13, 2025 13:25
@ganenkokb-yandex ganenkokb-yandex changed the title Fix cycle in importing template specialization on auto type with typename [Clang][ASTImporter] Fix cycle in importing template specialization on auto type with typename Oct 13, 2025
@balazske
Copy link
Collaborator

This approach can be used to detect any import cycle when a FunctionDecl is imported recursively. It does not matter if the recursion comes from the return type or something other that is imported before the new function is created. The call to function hasReturnTypeDeclaredInside can be removed and it should still work (I tried it out and got no test failures). The recursive import of return type is (and was) often cause of new crashes (because new "unknown" code constructs) so I would like if the function hasReturnTypeDeclaredInside (and related things) is removed. This would make the code more future-proof. But this change can be added in a later commit.

Copy link
Collaborator

@balazske balazske left a comment

Choose a reason for hiding this comment

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

Looks mostly OK, I have only naming changes.

@shafik
Copy link
Collaborator

shafik commented Oct 13, 2025

@balazske please give your approval if your happy, I was just doing a drive by review. If there a better person to tag on ASTImporter PRs let me know (hopefully I won't forget).

@balazske
Copy link
Collaborator

balazske commented Oct 14, 2025

I wanted to have a different pull request for removal of hasReturnTypeDeclaredInside. This can make debugging possible problems later more easy. Additionally the function has dependent code (IsTypeDeclaredInsideVisitor) that should also be removed.

@ganenkokb-yandex
Copy link
Contributor Author

@shafik @ojhunt Could I have your review please?

@ganenkokb-yandex
Copy link
Contributor Author

@ojhunt should I implement your suggested approach, or should I wait for @shafik opinion?

@ojhunt
Copy link
Contributor

ojhunt commented Nov 10, 2025

@ojhunt should I implement your suggested approach, or should I wait for @shafik opinion?

I think it's reasonable to use the make_scope_exit rather than adding another new RAII object

Add comments for new member.
Make RAII approach for cycles monitoring.
Use FunctionDecl straight forward.
Fix naming and comment
Now FunctionReturnTypeCycleDetector works out with nested
 typenames as well.
@ganenkokb-yandex ganenkokb-yandex force-pushed the ctu_clangsa_type_cycle_fix branch from 786def1 to e12db08 Compare November 11, 2025 07:50
@ganenkokb-yandex ganenkokb-yandex force-pushed the ctu_clangsa_type_cycle_fix branch from e12db08 to 32e28ff Compare November 11, 2025 07:56
Copy link
Collaborator

@balazske balazske left a comment

Choose a reason for hiding this comment

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

To me the code looks good after the name changes.

@ganenkokb-yandex
Copy link
Contributor Author

@ojhunt can you have a look please?

@github-actions
Copy link

🐧 Linux x64 Test Results

  • 111355 tests passed
  • 4426 tests skipped

@irishrover irishrover merged commit c15a6cc into llvm:main Nov 24, 2025
10 checks passed
aadeshps-mcw pushed a commit to aadeshps-mcw/llvm-project that referenced this pull request Nov 26, 2025
…n auto type with typename (llvm#162514)

ASTImporter on importing template specialization with auto return type
faces cycle when return type is not nested one, but typename from
template arguments and other template.
There is code, that prevents cycle to auto return types when nested type
declared. Solved case differs somehow from nested types, but have same
solution with UsedDifferentProtoType - with delayed return type
determining.
Priyanshu3820 pushed a commit to Priyanshu3820/llvm-project that referenced this pull request Nov 26, 2025
…n auto type with typename (llvm#162514)

ASTImporter on importing template specialization with auto return type
faces cycle when return type is not nested one, but typename from
template arguments and other template.
There is code, that prevents cycle to auto return types when nested type
declared. Solved case differs somehow from nested types, but have same
solution with UsedDifferentProtoType - with delayed return type
determining.
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.

6 participants