Skip to content

[RFC][C++20][Modules] Fix crash when function and lambda inside loaded from different modules #104512

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from 11 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
5 changes: 5 additions & 0 deletions clang/include/clang/Serialization/ASTReader.h
Original file line number Diff line number Diff line change
Expand Up @@ -1188,6 +1188,11 @@ class ASTReader
/// once recursing loading has been completed.
llvm::SmallVector<NamedDecl *, 16> PendingOdrMergeChecks;

/// Lambdas that need to be loaded right after the function they belong to.
/// It is required to have the right canonical declaration for lambda class
/// from the same module as the function.
Copy link
Member

Choose a reason for hiding this comment

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

nit: maybe it is better to expand this statement as we did in this patch. So that other readers can have a better understanding for it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

SmallVector<GlobalDeclID, 4> PendingLambdas;

using DataPointers =
std::pair<CXXRecordDecl *, struct CXXRecordDecl::DefinitionData *>;
using ObjCInterfaceDataPointers =
Expand Down
8 changes: 7 additions & 1 deletion clang/lib/Serialization/ASTReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9782,7 +9782,8 @@ void ASTReader::finishPendingActions() {
!PendingDeducedVarTypes.empty() || !PendingIncompleteDeclChains.empty() ||
!PendingDeclChains.empty() || !PendingMacroIDs.empty() ||
!PendingDeclContextInfos.empty() || !PendingUpdateRecords.empty() ||
!PendingObjCExtensionIvarRedeclarations.empty()) {
!PendingObjCExtensionIvarRedeclarations.empty() ||
!PendingLambdas.empty()) {
// If any identifiers with corresponding top-level declarations have
// been loaded, load those declarations now.
using TopLevelDeclsMap =
Expand Down Expand Up @@ -9927,6 +9928,11 @@ void ASTReader::finishPendingActions() {
}
PendingObjCExtensionIvarRedeclarations.pop_back();
}

// Load any pendiong lambdas.
for (auto ID : PendingLambdas)
GetDecl(ID);
PendingLambdas.clear();
}

// At this point, all update records for loaded decls are in place, so any
Expand Down
10 changes: 10 additions & 0 deletions clang/lib/Serialization/ASTReaderDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1155,6 +1155,16 @@ void ASTDeclReader::VisitFunctionDecl(FunctionDecl *FD) {
for (unsigned I = 0; I != NumParams; ++I)
Params.push_back(readDeclAs<ParmVarDecl>());
FD->setParams(Reader.getContext(), Params);

// For the first decl add all lambdas inside for loading them later,
// otherwise skip them.
unsigned NumLambdas = Record.readInt();
if (FD->isFirstDecl()) {
for (unsigned I = 0; I != NumLambdas; ++I)
Reader.PendingLambdas.push_back(Record.readDeclID());
} else {
(void)Record.readIntArray(NumLambdas);
Copy link
Member

Choose a reason for hiding this comment

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

nit: skipInts might be better API here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done, thank for the suggestion, I missed this function.

}
}

void ASTDeclReader::VisitObjCMethodDecl(ObjCMethodDecl *MD) {
Expand Down
37 changes: 37 additions & 0 deletions clang/lib/Serialization/ASTWriterDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "clang/AST/Expr.h"
#include "clang/AST/OpenMPClause.h"
#include "clang/AST/PrettyDeclStackTrace.h"
#include "clang/AST/StmtVisitor.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Serialization/ASTReader.h"
#include "clang/Serialization/ASTRecordWriter.h"
Expand Down Expand Up @@ -625,6 +626,33 @@ void ASTDeclWriter::VisitDeclaratorDecl(DeclaratorDecl *D) {
: QualType());
}

static llvm::SmallVector<const Decl *, 2> collectLambdas(FunctionDecl *D) {
struct LambdaCollector : public ConstStmtVisitor<LambdaCollector> {
llvm::SmallVectorImpl<const Decl *> &Lambdas;

LambdaCollector(llvm::SmallVectorImpl<const Decl *> &Lambdas)
: Lambdas(Lambdas) {}

void VisitLambdaExpr(const LambdaExpr *E) {
VisitStmt(E);
Lambdas.push_back(E->getLambdaClass());
}

void VisitStmt(const Stmt *S) {
if (!S)
return;
for (const Stmt *Child : S->children())
if (Child)
Visit(Child);
}
};

llvm::SmallVector<const Decl *, 2> Lambdas;
if (D->hasBody())
LambdaCollector(Lambdas).VisitStmt(D->getBody());
return Lambdas;
}

void ASTDeclWriter::VisitFunctionDecl(FunctionDecl *D) {
static_assert(DeclContext::NumFunctionDeclBits == 44,
"You need to update the serializer after you change the "
Expand Down Expand Up @@ -764,6 +792,15 @@ void ASTDeclWriter::VisitFunctionDecl(FunctionDecl *D) {
Record.push_back(D->param_size());
for (auto *P : D->parameters())
Record.AddDeclRef(P);

// Store references to all lambda decls inside function to load them
// immediately after loading the function to make sure that canonical
// decls for lambdas will be from the same module.
llvm::SmallVector<const Decl *, 2> Lambdas = collectLambdas(D);
Record.push_back(Lambdas.size());
for (const auto *L : Lambdas)
Record.AddDeclRef(L);
Copy link
Member

Choose a reason for hiding this comment

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

Is it good to only do this for the first decl?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Seems that doing it only for canonical is good enough, at least I don't see other examples.

Copy link
Member

Choose a reason for hiding this comment

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

Also probably we need to update the abbreviation. You need to look at DeclCXXMethodAbbrev and getFunctionDeclAbbrev.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it seems that it is not required but added comment there to clarify it.


Code = serialization::DECL_FUNCTION;
}

Expand Down
76 changes: 76 additions & 0 deletions clang/test/Headers/crash-instantiated-in-scope-cxx-modules.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// RUN: rm -fR %t
// RUN: split-file %s %t
// RUN: cd %t
// RUN: %clang_cc1 -std=c++20 -emit-header-unit -xc++-user-header -Werror=uninitialized folly-conv.h
// RUN: %clang_cc1 -std=c++20 -emit-header-unit -xc++-user-header -Werror=uninitialized thrift_cpp2_base.h
// RUN: %clang_cc1 -std=c++20 -emit-header-unit -xc++-user-header -Werror=uninitialized -fmodule-file=folly-conv.pcm -fmodule-file=thrift_cpp2_base.pcm logger_base.h

//--- Conv.h
#pragma once

template <typename _Tp, typename _Up = _Tp&&>
_Up __declval(int);

template <typename _Tp>
auto declval() noexcept -> decltype(__declval<_Tp>(0));

namespace folly {

template <class Value, class Error>
struct Expected {
template <class Yes>
auto thenOrThrow() -> decltype(declval<Value&>()) {
return 1;
}
};

struct ExpectedHelper {
template <class Error, class T>
static constexpr Expected<T, Error> return_(T) {
return Expected<T, Error>();
}

template <class This, class Fn, class E = int, class T = ExpectedHelper>
static auto then_(This&&, Fn&&)
-> decltype(T::template return_<E>((declval<Fn>()(true), 0))) {
return Expected<int, int>();
}
};

template <class Tgt>
inline Expected<Tgt, const char*> tryTo() {
Tgt result = 0;
// In build with asserts:
// clang/lib/Sema/SemaTemplateInstantiate.cpp: llvm::PointerUnion<Decl *, LocalInstantiationScope::DeclArgumentPack *> *clang::LocalInstantiationScope::findInstantiationOf(const Decl *): Assertion `isa<LabelDecl>(D) && "declaration not instantiated in this scope"' failed.
// In release build compilation error on the line below inside lambda:
// error: variable 'result' is uninitialized when used here [-Werror,-Wuninitialized]
ExpectedHelper::then_(Expected<bool, int>(), [&](bool) { return result; });
return {};
}

} // namespace folly

inline void bar() {
folly::tryTo<int>();
}
// expected-no-diagnostics

//--- folly-conv.h
#pragma once
#include "Conv.h"
// expected-no-diagnostics

//--- thrift_cpp2_base.h
#pragma once
#include "Conv.h"
// expected-no-diagnostics

//--- logger_base.h
#pragma once
import "folly-conv.h";
import "thrift_cpp2_base.h";

inline void foo() {
folly::tryTo<unsigned>();
}
// expected-no-diagnostics
30 changes: 30 additions & 0 deletions clang/test/Headers/crash-instantiated-in-scope-cxx-modules2.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// RUN: rm -fR %t
// RUN: split-file %s %t
// RUN: cd %t
// RUN: %clang_cc1 -std=c++20 -emit-header-unit -xc++-user-header header.h
// RUN: %clang_cc1 -std=c++20 -fmodule-file=header.pcm main.cpp

//--- header.h
template <typename T>
void f(T) {}

class A {
virtual ~A();
};

inline A::~A() {
f([](){});
}

struct B {
void g() {
f([](){
[](){};
});
}
};
// expected-no-diagnostics

//--- main.cpp
import "header.h";
// expected-no-diagnostics