[clang] [Clang][Sema] Tweak tryCaptureVariable for unevaluated lambdas (PR #93206)
Younan Zhang via cfe-commits
cfe-commits at lists.llvm.org
Thu May 23 08:31:25 PDT 2024
https://github.com/zyn0217 created https://github.com/llvm/llvm-project/pull/93206
This patch picks up #78598 with the hope that we can address such crashes in `tryCaptureVariable` for unevaluated lambdas.
Fixes #88081
Fixes #69307
Fixes #91633
Fixes #90669
Fixes #89496
>From 4419914727aeb73e6bbcd3fb6e4811f1f07cd133 Mon Sep 17 00:00:00 2001
From: Younan Zhang <zyn7109 at gmail.com>
Date: Thu, 23 May 2024 22:35:11 +0800
Subject: [PATCH 1/3] [Clang][Sema] Tweak tryCaptureVariable for unevaluated
lambdas
---
clang/include/clang/AST/DeclBase.h | 4 ++++
clang/lib/Sema/SemaExpr.cpp | 9 +++++++++
clang/lib/Sema/SemaLambda.cpp | 11 +++++++++++
clang/lib/Sema/TreeTransform.h | 2 +-
4 files changed, 25 insertions(+), 1 deletion(-)
diff --git a/clang/include/clang/AST/DeclBase.h b/clang/include/clang/AST/DeclBase.h
index e43e812cd9455..3a311d4c55916 100644
--- a/clang/include/clang/AST/DeclBase.h
+++ b/clang/include/clang/AST/DeclBase.h
@@ -2148,6 +2148,10 @@ class DeclContext {
getDeclKind() <= Decl::lastRecord;
}
+ bool isRequiresExprBody() const {
+ return getDeclKind() == Decl::RequiresExprBody;
+ }
+
bool isNamespace() const { return getDeclKind() == Decl::Namespace; }
bool isStdNamespace() const;
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index e6c3fa51d54da..bde4fcec69041 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -18831,6 +18831,9 @@ bool Sema::tryCaptureVariable(
DeclContext *VarDC = Var->getDeclContext();
DeclContext *DC = CurContext;
+ while (DC->isRequiresExprBody())
+ DC = DC->getParent();
+
// tryCaptureVariable is called every time a DeclRef is formed,
// it can therefore have non-negigible impact on performances.
// For local variables and when there is no capturing scope,
@@ -18838,6 +18841,12 @@ bool Sema::tryCaptureVariable(
if (CapturingFunctionScopes == 0 && (!BuildAndDiagnose || VarDC == DC))
return true;
+ // Exception: We have not entered the function scope yet, but we're
+ // referencing the function parameters e.g. at the end of the function
+ // parameter list.
+ if (isa<ParmVarDecl>(Var) && !VarDC->isFunctionOrMethod())
+ return true;
+
const auto *VD = dyn_cast<VarDecl>(Var);
if (VD) {
if (VD->isInitCapture())
diff --git a/clang/lib/Sema/SemaLambda.cpp b/clang/lib/Sema/SemaLambda.cpp
index 1743afaf15287..b9c533eeb6e61 100644
--- a/clang/lib/Sema/SemaLambda.cpp
+++ b/clang/lib/Sema/SemaLambda.cpp
@@ -1032,10 +1032,12 @@ void Sema::ActOnLambdaExpressionAfterIntroducer(LambdaIntroducer &Intro,
assert(LSI->NumExplicitTemplateParams == 0);
+
// Determine if we're within a context where we know that the lambda will
// be dependent, because there are template parameters in scope.
CXXRecordDecl::LambdaDependencyKind LambdaDependencyKind =
CXXRecordDecl::LDK_Unknown;
+
if (LSI->NumExplicitTemplateParams > 0) {
Scope *TemplateParamScope = CurScope->getTemplateParamParent();
assert(TemplateParamScope &&
@@ -1046,6 +1048,15 @@ void Sema::ActOnLambdaExpressionAfterIntroducer(LambdaIntroducer &Intro,
LambdaDependencyKind = CXXRecordDecl::LDK_AlwaysDependent;
} else if (CurScope->getTemplateParamParent() != nullptr) {
LambdaDependencyKind = CXXRecordDecl::LDK_AlwaysDependent;
+ } else if (Scope *P = CurScope->getParent()) {
+ while (P->getEntity() && P->getEntity()->isRequiresExprBody())
+ P = P->getParent();
+ if (P->isFunctionDeclarationScope() &&
+ llvm::any_of(P->decls(), [](Decl *D) {
+ return isa<ParmVarDecl>(D) &&
+ cast<ParmVarDecl>(D)->getType()->isTemplateTypeParmType();
+ }))
+ LambdaDependencyKind = CXXRecordDecl::LDK_AlwaysDependent;
}
CXXRecordDecl *Class = createLambdaClosureType(
diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h
index c039b95293af2..bff4377e34110 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -14232,7 +14232,7 @@ TreeTransform<Derived>::TransformLambdaExpr(LambdaExpr *E) {
// will be deemed as dependent even if there are no dependent template
// arguments.
// (A ClassTemplateSpecializationDecl is always a dependent context.)
- while (DC->getDeclKind() == Decl::Kind::RequiresExprBody)
+ while (DC->isRequiresExprBody())
DC = DC->getParent();
if ((getSema().isUnevaluatedContext() ||
getSema().isConstantEvaluatedContext()) &&
>From ce7930bb4195f45a71bf0aa764d88cb3fc992d7d Mon Sep 17 00:00:00 2001
From: Younan Zhang <zyn7109 at gmail.com>
Date: Thu, 23 May 2024 23:22:28 +0800
Subject: [PATCH 2/3] Add tests
---
clang/test/SemaCXX/lambda-unevaluated.cpp | 136 ++++++++++++++++++++++
1 file changed, 136 insertions(+)
diff --git a/clang/test/SemaCXX/lambda-unevaluated.cpp b/clang/test/SemaCXX/lambda-unevaluated.cpp
index 10d4c2228ec9b..9892825067700 100644
--- a/clang/test/SemaCXX/lambda-unevaluated.cpp
+++ b/clang/test/SemaCXX/lambda-unevaluated.cpp
@@ -189,3 +189,139 @@ void recursive() {
}
}
+
+namespace GH88081 {
+
+struct S { //#S
+ S(auto value) //#S-ctor
+ requires requires { [&] -> decltype(value) { return 2; }; } {} // #S-requires
+
+ static auto foo(auto value) -> decltype([&]() -> decltype(value) {}()) { return {}; } // #S-foo
+
+ static auto bar(auto value) -> decltype([&] { return value; }()) {
+ return "a"; // #bar-body
+ }
+};
+
+S s("a"); // #use
+// expected-error@#S-requires {{cannot initialize return object of type 'decltype(value)' (aka 'const char *') with an rvalue of type 'int'}}
+// expected-error@#use {{no matching constructor}}
+// expected-note@#S-requires {{substituting into a lambda expression here}}
+// expected-note@#S-requires {{substituting template arguments into constraint expression here}}
+// expected-note@#S-requires {{in instantiation of requirement here}}
+// expected-note@#use {{checking constraint satisfaction for template 'S<const char *>' required here}}
+// expected-note@#use {{requested here}}
+// expected-note-re@#S 2{{candidate constructor {{.*}} not viable}}
+// expected-note@#S-ctor {{constraints not satisfied}}
+// expected-note-re@#S-requires {{because {{.*}} would be invalid}}
+
+void func() {
+ S::foo(42);
+ S::bar("str");
+ S::bar(0.618);
+ // expected-error-re@#bar-body {{cannot initialize return object of type {{.*}} (aka 'double') with an lvalue of type 'const char[2]'}}
+ // expected-note at -2 {{requested here}}
+}
+
+} // namespace GH88081
+
+namespace GH69307 {
+
+constexpr auto ICE() {
+ constexpr auto b = 1;
+ return [=](auto c) -> int
+ requires requires { b + c; }
+ { return 1; };
+};
+
+constexpr auto Ret = ICE()(1);
+
+constexpr auto ICE(auto a) { // template function, not lambda
+ return [=]()
+ requires requires { a; }
+ { return 1; };
+};
+
+} // namespace GH69307
+
+namespace GH91633 {
+
+struct __normal_iterator {};
+
+template <typename _Iterator>
+void operator==(_Iterator __lhs, _Iterator) // expected-note {{declared here}}
+ requires requires { __lhs; };
+
+__normal_iterator finder();
+
+template <typename >
+void findDetail() {
+ auto makeResult = [&](auto foo) -> void {
+ finder() != foo;
+ // expected-error at -1 {{function for rewritten '!=' comparison is not 'bool'}}
+ };
+ makeResult(__normal_iterator{}); // expected-note {{requested here}}
+}
+
+void find() {
+ findDetail<void>(); // expected-note {{requested here}}
+}
+} // namespace GH91633
+
+namespace GH90669 {
+
+struct __normal_iterator {};
+
+struct vector {
+ __normal_iterator begin(); // #begin
+ int end();
+};
+
+template <typename _IteratorR>
+bool operator==(_IteratorR __lhs, int)
+ requires requires { __lhs; }
+{}
+
+template <typename PrepareFunc> void queued_for_each(PrepareFunc prep) {
+ prep(vector{}); //#prep
+}
+
+void scan() {
+ queued_for_each([&](auto ino) -> int { // #queued-for-each
+ for (auto e : ino) { // #for-each
+ // expected-error@#for-each {{cannot increment value of type '__normal_iterator'}}
+ // expected-note-re@#prep {{instantiation {{.*}} requested here}}
+ // expected-note-re@#queued-for-each {{instantiation {{.*}} requested here}}
+ // expected-note@#for-each {{implicit call to 'operator++'}}
+ // expected-note@#begin {{selected 'begin' function}}
+ };
+ });
+}
+} // namespace GH90669
+
+namespace GH89496 {
+template <class Iter> struct RevIter {
+ Iter current;
+ constexpr explicit RevIter(Iter x) : current(x) {}
+ inline constexpr bool operator==(const RevIter<Iter> &other) const
+ requires requires {
+ // { current == other.current } -> std::convertible_to<bool>;
+ { other };
+ }
+ {
+ return true;
+ }
+};
+struct Foo {
+ Foo() {};
+};
+void CrashFunc() {
+ auto lambda = [&](auto from, auto to) -> Foo {
+ (void)(from == to);
+ return Foo();
+ };
+ auto from = RevIter<int *>(nullptr);
+ auto to = RevIter<int *>(nullptr);
+ lambda(from, to);
+}
+} // namespace pr89496
>From 5335704c4b1b41abac3b8a1814849bec77199545 Mon Sep 17 00:00:00 2001
From: Younan Zhang <zyn7109 at gmail.com>
Date: Thu, 23 May 2024 23:23:20 +0800
Subject: [PATCH 3/3] Format
---
clang/lib/Sema/SemaLambda.cpp | 1 -
1 file changed, 1 deletion(-)
diff --git a/clang/lib/Sema/SemaLambda.cpp b/clang/lib/Sema/SemaLambda.cpp
index b9c533eeb6e61..009c8b3bc57e5 100644
--- a/clang/lib/Sema/SemaLambda.cpp
+++ b/clang/lib/Sema/SemaLambda.cpp
@@ -1032,7 +1032,6 @@ void Sema::ActOnLambdaExpressionAfterIntroducer(LambdaIntroducer &Intro,
assert(LSI->NumExplicitTemplateParams == 0);
-
// Determine if we're within a context where we know that the lambda will
// be dependent, because there are template parameters in scope.
CXXRecordDecl::LambdaDependencyKind LambdaDependencyKind =
More information about the cfe-commits
mailing list