-
Notifications
You must be signed in to change notification settings - Fork 13.6k
[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
[RFC][C++20][Modules] Fix crash when function and lambda inside loaded from different modules #104512
Changes from 11 commits
8772795
524344e
f5606eb
11b798d
1fc4693
64230ad
4a964a7
01375e3
2b588e5
db65f69
b5f6a30
bd6386f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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" | ||
|
@@ -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 " | ||
|
@@ -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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it good to only do this for the first decl? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
} | ||
|
||
|
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 |
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done