[Clang] Instantiate the correct lambda call operator (#110446)
This is a fix for the following issue: when a lambda’s class type is merged across modules (e.g. because it is defined in a template in the GMF of some module `A`, and some other module `B` both imports `A` and has the same template in its GMF), then `getLambdaCallOperator()` might return the wrong operator (e.g. while compiling `B`, the lambda’s class type would be the one attached to `B`’s GMF, but the call operator ends up being the one attached to `A`’s GMF). This causes issues in situations where the call operator is in a template and accesses declarations in the surrounding context: when those declarations are instantated, a mapping is introduced from the original node in the template to that of the instantiation. If such an instantiation happens in `B`, and we then try to instantiate `A`’s call operator, any nodes in that call operator refer to declarations in the template in `A`, but the `LocalInstantiationScope` only contains mappings for declarations in `B`! This causes the following assertion (for godbolt links and more, see the issue below): ``` Assertion `isa<LabelDecl>(D) && "declaration not instantiated in this scope"' failed. ``` We now walk the redecl chain of the call operator to find the one that is in the same module as the record decl. This fixes #110401.
This commit is contained in:
parent
235067b222
commit
f01364ebc8
@ -479,6 +479,8 @@ Bug Fixes to C++ Support
|
||||
conformance of explicit instantiation behaviour with MSVC. (#GH111266)
|
||||
- Fixed a bug in constraint expression comparison where the ``sizeof...`` expression was not handled properly
|
||||
in certain friend declarations. (#GH93099)
|
||||
- Clang now instantiates the correct lambda call operator when a lambda's class type is
|
||||
merged across modules. (#GH110401)
|
||||
|
||||
Bug Fixes to AST Handling
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@ -1631,13 +1631,42 @@ static bool allLookupResultsAreTheSame(const DeclContext::lookup_result &R) {
|
||||
static NamedDecl* getLambdaCallOperatorHelper(const CXXRecordDecl &RD) {
|
||||
if (!RD.isLambda()) return nullptr;
|
||||
DeclarationName Name =
|
||||
RD.getASTContext().DeclarationNames.getCXXOperatorName(OO_Call);
|
||||
DeclContext::lookup_result Calls = RD.lookup(Name);
|
||||
RD.getASTContext().DeclarationNames.getCXXOperatorName(OO_Call);
|
||||
|
||||
DeclContext::lookup_result Calls = RD.lookup(Name);
|
||||
assert(!Calls.empty() && "Missing lambda call operator!");
|
||||
assert(allLookupResultsAreTheSame(Calls) &&
|
||||
"More than one lambda call operator!");
|
||||
return Calls.front();
|
||||
|
||||
// FIXME: If we have multiple call operators, we might be in a situation
|
||||
// where we merged this lambda with one from another module; in that
|
||||
// case, return our method (instead of that of the other lambda).
|
||||
//
|
||||
// This avoids situations where, given two modules A and B, if we
|
||||
// try to instantiate A's call operator in a function in B, anything
|
||||
// in the call operator that relies on local decls in the surrounding
|
||||
// function will crash because it tries to find A's decls, but we only
|
||||
// instantiated B's:
|
||||
//
|
||||
// template <typename>
|
||||
// void f() {
|
||||
// using T = int; // We only instantiate B's version of this.
|
||||
// auto L = [](T) { }; // But A's call operator would want A's here.
|
||||
// }
|
||||
//
|
||||
// Walk the call operator’s redecl chain to find the one that belongs
|
||||
// to this module.
|
||||
//
|
||||
// TODO: We need to fix this properly (see
|
||||
// https://github.com/llvm/llvm-project/issues/90154).
|
||||
Module *M = RD.getOwningModule();
|
||||
for (Decl *D : Calls.front()->redecls()) {
|
||||
auto *MD = cast<NamedDecl>(D);
|
||||
if (MD->getOwningModule() == M)
|
||||
return MD;
|
||||
}
|
||||
|
||||
llvm_unreachable("Couldn't find our call operator!");
|
||||
}
|
||||
|
||||
FunctionTemplateDecl* CXXRecordDecl::getDependentLambdaCallOperator() const {
|
||||
|
||||
44
clang/test/Modules/gh110401.cppm
Normal file
44
clang/test/Modules/gh110401.cppm
Normal file
@ -0,0 +1,44 @@
|
||||
// RUN: rm -rf %t
|
||||
// RUN: mkdir %t
|
||||
// RUN: split-file %s %t
|
||||
//
|
||||
// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux -emit-module-interface %t/a.cppm -o %t/A.pcm
|
||||
// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux -emit-module-interface -fprebuilt-module-path=%t %t/b.cppm -o %t/B.pcm
|
||||
|
||||
// Just check that this doesn't crash.
|
||||
|
||||
//--- a.cppm
|
||||
module;
|
||||
|
||||
template <typename _Visitor>
|
||||
void __do_visit(_Visitor &&__visitor) {
|
||||
using _V0 = int;
|
||||
[](_V0 __v) -> _V0 { return __v; } (1);
|
||||
}
|
||||
|
||||
export module A;
|
||||
|
||||
void g() {
|
||||
struct Visitor { };
|
||||
__do_visit(Visitor());
|
||||
}
|
||||
|
||||
//--- b.cppm
|
||||
module;
|
||||
|
||||
template <typename _Visitor>
|
||||
void __do_visit(_Visitor &&__visitor) {
|
||||
using _V0 = int;
|
||||
|
||||
// Check that we instantiate this lambda's call operator in 'f' below
|
||||
// instead of the one in 'a.cppm' here; otherwise, we won't find a
|
||||
// corresponding instantiation of the using declaration above.
|
||||
[](_V0 __v) -> _V0 { return __v; } (1);
|
||||
}
|
||||
|
||||
export module B;
|
||||
import A;
|
||||
|
||||
void f() {
|
||||
__do_visit(1);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user