Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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
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
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 DeclCycleMapInserter {
public:
// Do not track cycles on D == nullptr.
DeclCycleMapInserter(FunctionReturnTypeDeclCycleDetector &owner,
const FunctionDecl *D)
: CycleDetector(owner), D(D) {
if (D)
CycleDetector.FunctionReturnTypeDeclCycles.insert(D);
}
~DeclCycleMapInserter() {
if (D)
CycleDetector.FunctionReturnTypeDeclCycles.erase(D);
}
DeclCycleMapInserter(const DeclCycleMapInserter &) = delete;
DeclCycleMapInserter &operator=(const DeclCycleMapInserter &) = delete;

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

DeclCycleMapInserter detectImportCycle(const FunctionDecl *D) {
if (!isCycle(D))
return DeclCycleMapInserter(*this, D);
return DeclCycleMapInserter(*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->detectImportCycle(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(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