Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 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,16 @@ class TypeSourceInfo;
/// Declaration (from, to) pairs that are known not to be equivalent
/// (which we have already complained about).
NonEquivalentDeclSet NonEquivalentDecls;
// When template function return type is auto and return type is declared as
// typename from template params, there could be cycles in function
// importing when function decaration is still the need for return type
// declaration import. We have code path for nested types inside function
// (see hasReturnTypeDeclaredInside): assuming return type as VoidTy and
// calculate it later under UsedDifferentProtoType boolean. This class is
// reuse of this approach and make logic lazy - detect cycle - calculate
// return type later on.
std::unique_ptr<FunctionReturnTypeDeclCycleDetector>
FunctionReturnTypeCycleDetector;

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

using namespace clang;

class ASTImporter::FunctionReturnTypeDeclCycleDetector {
public:
class ScopedReturnTypeImport {
public:
// Do not track cycles on D == nullptr.
ScopedReturnTypeImport(FunctionReturnTypeDeclCycleDetector &owner,
const FunctionDecl *D)
: CycleDetector(owner), D(D) {
if (D)
CycleDetector.FunctionReturnTypeDeclCycles.insert(D);
}
~ScopedReturnTypeImport() {
if (D)
CycleDetector.FunctionReturnTypeDeclCycles.erase(D);
}
ScopedReturnTypeImport(const ScopedReturnTypeImport &) = delete;
ScopedReturnTypeImport &operator=(const ScopedReturnTypeImport &) = delete;

private:
FunctionReturnTypeDeclCycleDetector &CycleDetector;
const FunctionDecl *D;
};

ScopedReturnTypeImport DetectImportCycles(const FunctionDecl *D) {
if (!IsCycle(D))
return ScopedReturnTypeImport(*this, D);
return ScopedReturnTypeImport(*this, nullptr);
}

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 @@ -4035,7 +4073,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 pamams, tracked in FunctionReturnTypeCycleDetector.
if (hasReturnTypeDeclaredInside(D) ||
Importer.FunctionReturnTypeCycleDetector->IsCycle(D)) {
FromReturnTy = Importer.getFromContext().VoidTy;
UsedDifferentProtoType = true;
}
Expand All @@ -4058,6 +4099,8 @@ ExpectedDecl ASTNodeImporter::VisitFunctionDecl(FunctionDecl *D) {
}

Error Err = Error::success();
auto ScopedReturnTypeDeclCycleDetector =
Importer.FunctionReturnTypeCycleDetector->DetectImportCycles(D);
auto T = importChecked(Err, FromTy);
auto TInfo = importChecked(Err, FromTSI);
auto ToInnerLocStart = importChecked(Err, D->getInnerLocStart());
Expand Down Expand Up @@ -9294,7 +9337,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(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"(
Expand Down