Skip to content
Open
Show file tree
Hide file tree
Changes from all 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