Skip to content
9 changes: 9 additions & 0 deletions clang/include/clang/AST/ASTImporter.h
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,8 @@ class TypeSourceInfo;
llvm::SmallDenseMap<Decl *, int, 32> Aux;
};

class FunctionReturnTypeDeclCycleDetector;

private:
std::shared_ptr<ASTImporterSharedState> SharedState = nullptr;

Expand Down Expand Up @@ -254,6 +256,13 @@ class TypeSourceInfo;
/// Declaration (from, to) pairs that are known not to be equivalent
/// (which we have already complained about).
NonEquivalentDeclSet NonEquivalentDecls;
/// A FunctionDecl can have properties that have a reference to the
/// function itself and are imported before the function is created. This
/// can come for example from auto return type or when template parameters
/// are used in the return type or parameters. This member is used to detect
/// cyclic import of FunctionDecl objects to avoid infinite recursion.
std::unique_ptr<FunctionReturnTypeDeclCycleDetector>
FunctionReturnTypeCycleDetector;

using FoundDeclsTy = SmallVector<NamedDecl *, 2>;
FoundDeclsTy findDeclsInToCtx(DeclContext *DC, DeclarationName Name);
Expand Down
35 changes: 33 additions & 2 deletions clang/lib/AST/ASTImporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1287,6 +1287,30 @@ bool ASTNodeImporter::hasSameVisibilityContextAndLinkage(TypedefNameDecl *Found,

using namespace clang;

class ASTImporter::FunctionReturnTypeDeclCycleDetector {
public:
auto makeScopedCycleDetection(const FunctionDecl *D) {
const FunctionDecl *LambdaD = nullptr;
if (!isCycle(D) && D) {
FunctionReturnTypeDeclCycles.insert(D);
LambdaD = D;
}
return llvm::make_scope_exit([this, LambdaD]() {
if (LambdaD) {
FunctionReturnTypeDeclCycles.erase(LambdaD);
}
});
}

bool isCycle(const FunctionDecl *D) const {
return FunctionReturnTypeDeclCycles.find(D) !=
FunctionReturnTypeDeclCycles.end();
}

private:
llvm::DenseSet<const FunctionDecl *> FunctionReturnTypeDeclCycles;
};

ExpectedType ASTNodeImporter::VisitType(const Type *T) {
Importer.FromDiag(SourceLocation(), diag::err_unsupported_ast_node)
<< T->getTypeClassName();
Expand Down Expand Up @@ -4034,7 +4058,10 @@ 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)) {
// Reuse this approach for auto return types declared as typenames from
// template params, tracked in FunctionReturnTypeCycleDetector.
if (hasReturnTypeDeclaredInside(D) ||
Importer.FunctionReturnTypeCycleDetector->isCycle(D)) {
FromReturnTy = Importer.getFromContext().VoidTy;
UsedDifferentProtoType = true;
}
Expand All @@ -4057,6 +4084,8 @@ ExpectedDecl ASTNodeImporter::VisitFunctionDecl(FunctionDecl *D) {
}

Error Err = Error::success();
auto ScopedReturnTypeDeclCycleDetector =
Importer.FunctionReturnTypeCycleDetector->makeScopedCycleDetection(D);
auto T = importChecked(Err, FromTy);
auto TInfo = importChecked(Err, FromTSI);
auto ToInnerLocStart = importChecked(Err, D->getInnerLocStart());
Expand Down Expand Up @@ -9293,7 +9322,9 @@ ASTImporter::ASTImporter(ASTContext &ToContext, FileManager &ToFileManager,
std::shared_ptr<ASTImporterSharedState> SharedState)
: SharedState(SharedState), ToContext(ToContext), FromContext(FromContext),
ToFileManager(ToFileManager), FromFileManager(FromFileManager),
Minimal(MinimalImport), ODRHandling(ODRHandlingType::Conservative) {
Minimal(MinimalImport), ODRHandling(ODRHandlingType::Conservative),
FunctionReturnTypeCycleDetector(
std::make_unique<FunctionReturnTypeDeclCycleDetector>()) {

// Create a default state without the lookup table: LLDB case.
if (!SharedState) {
Expand Down
51 changes: 51 additions & 0 deletions clang/unittests/AST/ASTImporterTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3204,6 +3204,57 @@ TEST_P(ImportExpr, UnresolvedMemberExpr) {
compoundStmt(has(callExpr(has(unresolvedMemberExpr())))))))));
}

TEST_P(ImportDecl, 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"(
Expand Down
Loading