Skip to content

Commit 658e9d4

Browse files
committed
[Clang][Sema] Tweak tryCaptureVariable for unevaluated lambdas
This patch picks up #78598 with the hope that we can address such crashes in tryCaptureVariable for unevaluated lambdas. In addition to tryCaptureVariable, this also contains several other fixes on e.g. lambda parsing/dependencies.
1 parent 8070b2d commit 658e9d4

File tree

7 files changed

+206
-2
lines changed

7 files changed

+206
-2
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -710,6 +710,9 @@ Bug Fixes to C++ Support
710710
- Correctly treat the compound statement of an ``if consteval`` as an immediate context. Fixes (#GH91509).
711711
- When partial ordering alias templates against template template parameters,
712712
allow pack expansions when the alias has a fixed-size parameter list. Fixes (#GH62529).
713+
- Fixes several bugs in capturing variables within unevaluated contexts. Fixes (#GH63845), (#GH67260), (#GH69307), (#GH88081),
714+
(#GH89496), (#GH90669) and (#GH91633).
715+
713716

714717
Bug Fixes to AST Handling
715718
^^^^^^^^^^^^^^^^^^^^^^^^^

clang/include/clang/AST/DeclBase.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2148,6 +2148,10 @@ class DeclContext {
21482148
getDeclKind() <= Decl::lastRecord;
21492149
}
21502150

2151+
bool isRequiresExprBody() const {
2152+
return getDeclKind() == Decl::RequiresExprBody;
2153+
}
2154+
21512155
bool isNamespace() const { return getDeclKind() == Decl::Namespace; }
21522156

21532157
bool isStdNamespace() const;

clang/lib/Parse/ParseExprCXX.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1576,7 +1576,10 @@ ExprResult Parser::ParseLambdaExpressionAfterIntroducer(
15761576
TrailingReturnTypeLoc, &DS),
15771577
std::move(Attributes), DeclEndLoc);
15781578

1579-
Actions.ActOnLambdaClosureQualifiers(Intro, MutableLoc);
1579+
// We have called ActOnLambdaClosureQualifiers for parentheses-less cases
1580+
// above.
1581+
if (HasParentheses)
1582+
Actions.ActOnLambdaClosureQualifiers(Intro, MutableLoc);
15801583

15811584
if (HasParentheses && Tok.is(tok::kw_requires))
15821585
ParseTrailingRequiresClause(D);

clang/lib/Sema/SemaExpr.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18831,13 +18831,23 @@ bool Sema::tryCaptureVariable(
1883118831
DeclContext *VarDC = Var->getDeclContext();
1883218832
DeclContext *DC = CurContext;
1883318833

18834+
// Skip past RequiresExprBodys because they don't constitute function scopes.
18835+
while (DC->isRequiresExprBody())
18836+
DC = DC->getParent();
18837+
1883418838
// tryCaptureVariable is called every time a DeclRef is formed,
1883518839
// it can therefore have non-negigible impact on performances.
1883618840
// For local variables and when there is no capturing scope,
1883718841
// we can bailout early.
1883818842
if (CapturingFunctionScopes == 0 && (!BuildAndDiagnose || VarDC == DC))
1883918843
return true;
1884018844

18845+
// Exception: Function parameters are not tied to the function's DeclContext
18846+
// until we enter the function definition. Capturing them anyway would result
18847+
// in an out-of-bounds error while traversing DC and its parents.
18848+
if (isa<ParmVarDecl>(Var) && !VarDC->isFunctionOrMethod())
18849+
return true;
18850+
1884118851
const auto *VD = dyn_cast<VarDecl>(Var);
1884218852
if (VD) {
1884318853
if (VD->isInitCapture())

clang/lib/Sema/SemaLambda.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1036,6 +1036,7 @@ void Sema::ActOnLambdaExpressionAfterIntroducer(LambdaIntroducer &Intro,
10361036
// be dependent, because there are template parameters in scope.
10371037
CXXRecordDecl::LambdaDependencyKind LambdaDependencyKind =
10381038
CXXRecordDecl::LDK_Unknown;
1039+
10391040
if (LSI->NumExplicitTemplateParams > 0) {
10401041
Scope *TemplateParamScope = CurScope->getTemplateParamParent();
10411042
assert(TemplateParamScope &&
@@ -1046,6 +1047,25 @@ void Sema::ActOnLambdaExpressionAfterIntroducer(LambdaIntroducer &Intro,
10461047
LambdaDependencyKind = CXXRecordDecl::LDK_AlwaysDependent;
10471048
} else if (CurScope->getTemplateParamParent() != nullptr) {
10481049
LambdaDependencyKind = CXXRecordDecl::LDK_AlwaysDependent;
1050+
} else if (Scope *P = CurScope->getParent()) {
1051+
// Given a lambda defined inside a requires expression,
1052+
//
1053+
// struct S {
1054+
// S(auto var) requires requires { [&] -> decltype(var) { }; }
1055+
// {}
1056+
// };
1057+
//
1058+
// The parameter var is not injected into the function Decl at the point of
1059+
// parsing lambda. In such scenarios, perceiving it as dependent could
1060+
// result in the constraint being evaluated, which matches what GCC does.
1061+
while (P->getEntity() && P->getEntity()->isRequiresExprBody())
1062+
P = P->getParent();
1063+
if (P->isFunctionDeclarationScope() &&
1064+
llvm::any_of(P->decls(), [](Decl *D) {
1065+
return isa<ParmVarDecl>(D) &&
1066+
cast<ParmVarDecl>(D)->getType()->isTemplateTypeParmType();
1067+
}))
1068+
LambdaDependencyKind = CXXRecordDecl::LDK_AlwaysDependent;
10491069
}
10501070

10511071
CXXRecordDecl *Class = createLambdaClosureType(

clang/lib/Sema/TreeTransform.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14232,7 +14232,7 @@ TreeTransform<Derived>::TransformLambdaExpr(LambdaExpr *E) {
1423214232
// will be deemed as dependent even if there are no dependent template
1423314233
// arguments.
1423414234
// (A ClassTemplateSpecializationDecl is always a dependent context.)
14235-
while (DC->getDeclKind() == Decl::Kind::RequiresExprBody)
14235+
while (DC->isRequiresExprBody())
1423614236
DC = DC->getParent();
1423714237
if ((getSema().isUnevaluatedContext() ||
1423814238
getSema().isConstantEvaluatedContext()) &&

clang/test/SemaCXX/lambda-unevaluated.cpp

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,3 +189,167 @@ void recursive() {
189189

190190
}
191191
}
192+
193+
namespace GH63845 {
194+
195+
template <bool> struct A {};
196+
197+
struct true_type {
198+
constexpr operator bool() noexcept { return true; }
199+
};
200+
201+
constexpr bool foo() {
202+
true_type x{};
203+
return requires { typename A<x>; }; // fails in Clang
204+
}
205+
206+
constexpr bool foo(auto b) {
207+
return requires { typename A<b>; };
208+
}
209+
210+
static_assert(foo(true_type{}));
211+
static_assert(foo());
212+
213+
}
214+
215+
namespace GH88081 {
216+
217+
// Test that ActOnLambdaClosureQualifiers is called only once.
218+
void foo(auto value)
219+
requires requires { [&] -> decltype(value) {}; }
220+
// expected-error@-1 {{non-local lambda expression cannot have a capture-default}}
221+
{}
222+
223+
struct S { //#S
224+
S(auto value) //#S-ctor
225+
requires requires { [&] -> decltype(value) { return 2; }; } {} // #S-requires
226+
227+
static auto foo(auto value) -> decltype([&]() -> decltype(value) {}()) { return {}; } // #S-foo
228+
229+
static auto bar(auto value) -> decltype([&] { return value; }()) {
230+
return "a"; // #bar-body
231+
}
232+
};
233+
234+
S s("a"); // #use
235+
// expected-error@#S-requires {{cannot initialize return object of type 'decltype(value)' (aka 'const char *') with an rvalue of type 'int'}}
236+
// expected-error@#use {{no matching constructor}}
237+
// expected-note@#S-requires {{substituting into a lambda expression here}}
238+
// expected-note@#S-requires {{substituting template arguments into constraint expression here}}
239+
// expected-note@#S-requires {{in instantiation of requirement here}}
240+
// expected-note@#use {{checking constraint satisfaction for template 'S<const char *>' required here}}
241+
// expected-note@#use {{requested here}}
242+
// expected-note-re@#S 2{{candidate constructor {{.*}} not viable}}
243+
// expected-note@#S-ctor {{constraints not satisfied}}
244+
// expected-note-re@#S-requires {{because {{.*}} would be invalid}}
245+
246+
void func() {
247+
S::foo(42);
248+
S::bar("str");
249+
S::bar(0.618);
250+
// expected-error-re@#bar-body {{cannot initialize return object of type {{.*}} (aka 'double') with an lvalue of type 'const char[2]'}}
251+
// expected-note@-2 {{requested here}}
252+
}
253+
254+
} // namespace GH88081
255+
256+
namespace GH69307 {
257+
258+
constexpr auto ICE() {
259+
constexpr auto b = 1;
260+
return [=](auto c) -> int
261+
requires requires { b + c; }
262+
{ return 1; };
263+
};
264+
265+
constexpr auto Ret = ICE()(1);
266+
267+
constexpr auto ICE(auto a) { // template function, not lambda
268+
return [=]()
269+
requires requires { a; }
270+
{ return 1; };
271+
};
272+
273+
} // namespace GH69307
274+
275+
namespace GH91633 {
276+
277+
struct __normal_iterator {};
278+
279+
template <typename _Iterator>
280+
void operator==(_Iterator __lhs, _Iterator) // expected-note {{declared here}}
281+
requires requires { __lhs; };
282+
283+
__normal_iterator finder();
284+
285+
template <typename >
286+
void findDetail() {
287+
auto makeResult = [&](auto foo) -> void {
288+
finder() != foo;
289+
// expected-error@-1 {{function for rewritten '!=' comparison is not 'bool'}}
290+
};
291+
makeResult(__normal_iterator{}); // expected-note {{requested here}}
292+
}
293+
294+
void find() {
295+
findDetail<void>(); // expected-note {{requested here}}
296+
}
297+
} // namespace GH91633
298+
299+
namespace GH90669 {
300+
301+
struct __normal_iterator {};
302+
303+
struct vector {
304+
__normal_iterator begin(); // #begin
305+
int end();
306+
};
307+
308+
template <typename _IteratorR>
309+
bool operator==(_IteratorR __lhs, int)
310+
requires requires { __lhs; }
311+
{}
312+
313+
template <typename PrepareFunc> void queued_for_each(PrepareFunc prep) {
314+
prep(vector{}); //#prep
315+
}
316+
317+
void scan() {
318+
queued_for_each([&](auto ino) -> int { // #queued-for-each
319+
for (auto e : ino) { // #for-each
320+
// expected-error@#for-each {{cannot increment value of type '__normal_iterator'}}
321+
// expected-note-re@#prep {{instantiation {{.*}} requested here}}
322+
// expected-note-re@#queued-for-each {{instantiation {{.*}} requested here}}
323+
// expected-note@#for-each {{implicit call to 'operator++'}}
324+
// expected-note@#begin {{selected 'begin' function}}
325+
};
326+
});
327+
}
328+
} // namespace GH90669
329+
330+
namespace GH89496 {
331+
template <class Iter> struct RevIter {
332+
Iter current;
333+
constexpr explicit RevIter(Iter x) : current(x) {}
334+
inline constexpr bool operator==(const RevIter<Iter> &other) const
335+
requires requires {
336+
// { current == other.current } -> std::convertible_to<bool>;
337+
{ other };
338+
}
339+
{
340+
return true;
341+
}
342+
};
343+
struct Foo {
344+
Foo() {};
345+
};
346+
void CrashFunc() {
347+
auto lambda = [&](auto from, auto to) -> Foo {
348+
(void)(from == to);
349+
return Foo();
350+
};
351+
auto from = RevIter<int *>(nullptr);
352+
auto to = RevIter<int *>(nullptr);
353+
lambda(from, to);
354+
}
355+
} // namespace pr89496

0 commit comments

Comments
 (0)