Skip to content

Commit 59b64bc

Browse files
hokeinrorth
authored andcommitted
[clang][modules] Fix lambda and its enclosing function are not loaded from same module. (llvm#142467)
This is a follow-up fix to llvm#109167. Previously, we stored a mapping between the enclosing function and the lambda class declaration. When loading the enclosing function, we also loaded the corresponding lambda class declaration. However, loading the lambda class declaration does not guarantee that its call operator (a `CXXMethodDecl`) is loaded as well. As a result, if the lambda call operator is later loaded from a different module, we can end up with a `DeclRefExpr` that refers to a `VarDecl` from a different module — leading to inconsistencies. To fix this, we should ensure the lambda call operator itself is loaded. Fixes llvm#141582
1 parent 8b4a655 commit 59b64bc

File tree

3 files changed

+151
-2
lines changed

3 files changed

+151
-2
lines changed

clang/include/clang/Serialization/ASTReader.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -565,7 +565,7 @@ class ASTReader
565565
/// modules. It is used for the following cases:
566566
/// - Lambda inside a template function definition: The main declaration is
567567
/// the enclosing function, and the related declarations are the lambda
568-
/// declarations.
568+
/// call operators.
569569
/// - Friend function defined inside a template CXXRecord declaration: The
570570
/// main declaration is the enclosing record, and the related declarations
571571
/// are the friend functions.

clang/lib/Serialization/ASTWriterDecl.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1655,7 +1655,7 @@ void ASTDeclWriter::VisitCXXRecordDecl(CXXRecordDecl *D) {
16551655
if (auto *FD = llvm::dyn_cast_or_null<FunctionDecl>(D->getDeclContext());
16561656
FD && isDefinitionInDependentContext(FD)) {
16571657
Writer.RelatedDeclsMap[Writer.GetDeclRef(FD)].push_back(
1658-
Writer.GetDeclRef(D));
1658+
Writer.GetDeclRef(D->getLambdaCallOperator()));
16591659
}
16601660
} else {
16611661
Record.push_back(CXXRecNotTemplate);
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
// RUN: rm -fR %t
2+
// RUN: split-file %s %t
3+
// RUN: cd %t
4+
// RUN: %clang_cc1 -std=c++20 -xc++ -emit-module -fmodules foo.cppmap -fmodule-name=foo -fmodule-name=foo -o foo.pcm
5+
// RUN: %clang_cc1 -std=c++20 -xc++ -emit-module -fmodules bar.cppmap -fmodule-name=bar -o bar.pcm
6+
// RUN: %clang_cc1 -std=c++20 -xc++ -emit-module -fmodules experiment_context.cppmap -fmodule-name=experiment_context -fmodule-file=foo.pcm -fmodule-file=bar.pcm -o experiment_context.pcm
7+
// RUN: %clang_cc1 -verify -std=c++20 -xc++ -fmodule-file=experiment_context.pcm experiment_context_test.cc -o experiment_context_test.o
8+
9+
// https://github.com/llvm/llvm-project/issues/141582
10+
//--- bar.cppmap
11+
module "bar" {
12+
export *
13+
header "co.h"
14+
}
15+
16+
//--- foo.cppmap
17+
module "foo" {
18+
export *
19+
header "co.h"
20+
}
21+
22+
//--- experiment_context.cppmap
23+
module "experiment_context" {
24+
export *
25+
header "lazy.h"
26+
27+
use "foo"
28+
use "bar"
29+
}
30+
31+
//--- experiment_context_test.cc
32+
// expected-no-diagnostics
33+
#include "lazy.h"
34+
35+
namespace c9 {
36+
37+
template <typename T>
38+
void WaitForCoroutine() {
39+
MakeCo<T>([]() -> Co<void> {
40+
co_return;
41+
});
42+
}
43+
44+
void test() {
45+
c9::WaitForCoroutine<void>();
46+
}
47+
}
48+
49+
//--- lazy.h
50+
#pragma once
51+
52+
#include "co.h"
53+
54+
namespace c9 {
55+
template <typename T, typename F>
56+
Co<T> MakeCo(F f)
57+
{
58+
co_return co_await f();
59+
}
60+
}
61+
62+
inline c9::Co<void> DoNothing() { co_return; }
63+
64+
65+
//--- co.h
66+
#pragma once
67+
namespace std {
68+
69+
template <class _Ret, class... _Args>
70+
struct coroutine_traits {};
71+
72+
template <typename Ret, typename... Args>
73+
requires requires { typename Ret::promise_type; }
74+
struct coroutine_traits<Ret, Args...> {
75+
using promise_type = typename Ret::promise_type;
76+
};
77+
78+
template <typename Promise = void>
79+
struct coroutine_handle;
80+
81+
template <>
82+
struct coroutine_handle<void> {};
83+
84+
template <typename Promise = void>
85+
struct coroutine_handle : coroutine_handle<> {
86+
static coroutine_handle from_address(void *addr);
87+
};
88+
89+
struct suspend_always {
90+
bool await_ready() noexcept;
91+
void await_suspend(coroutine_handle<>) noexcept;
92+
void await_resume() noexcept;
93+
};
94+
95+
} // namespace std
96+
97+
namespace c9 {
98+
99+
template <typename T>
100+
class Co;
101+
102+
namespace internal {
103+
104+
template <typename T>
105+
class CoroutinePromise {
106+
public:
107+
template <typename... Args>
108+
explicit CoroutinePromise(Args&&... args) {
109+
// Ensure that the 'dummy_color' VarDecl referenced by the inner DeclRefExpr
110+
// is the same declaration as the one outside the lambda.
111+
// This is guaranteed because both CoroutinePromise and the lambda's call operator
112+
// (CXXMethodDecl) are loaded from the same module.
113+
const int dummy_color = 1;
114+
[&]{ (void)dummy_color; }();
115+
}
116+
117+
~CoroutinePromise();
118+
void return_void();
119+
auto get_return_object() {
120+
return Co<T>();
121+
}
122+
void unhandled_exception();
123+
std::suspend_always initial_suspend();
124+
125+
struct result_t {
126+
~result_t();
127+
bool await_ready() const noexcept;
128+
void await_suspend(std::coroutine_handle<void>) noexcept;
129+
void await_resume() const noexcept;
130+
};
131+
132+
template <typename U>
133+
result_t await_transform(Co<U> co);
134+
135+
std::suspend_always final_suspend() noexcept;
136+
};
137+
} // namespace internal
138+
139+
template <typename T>
140+
class Co {
141+
public:
142+
using promise_type = internal::CoroutinePromise<void>;
143+
};
144+
145+
class CoIncomingModuleBase {
146+
public:
147+
Co<void> CoAfterFinish() { co_return; }
148+
};
149+
} // namespace c9

0 commit comments

Comments
 (0)