Skip to content

Commit

Permalink
[Clang][Sema] Tweak tryCaptureVariable for unevaluated lambdas (#93206)
Browse files Browse the repository at this point in the history
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.

Fixes #63845
Fixes #67260
Fixes #69307
Fixes #88081
Fixes #89496
Fixes #90669
Fixes #91633
  • Loading branch information
zyn0217 committed Jun 4, 2024
1 parent 8ea59ec commit 3d361b2
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 11 deletions.
2 changes: 2 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -824,6 +824,8 @@ Bug Fixes to C++ Support
- Fix a crash when a variable is captured by a block nested inside a lambda. (Fixes #GH93625).
- Fixed a type constraint substitution issue involving a generic lambda expression. (#GH93821)
- Fix a crash caused by improper use of ``__array_extent``. (#GH80474)
- Fixed several bugs in capturing variables within unevaluated contexts. (#GH63845), (#GH67260), (#GH69307),
(#GH88081), (#GH89496), (#GH90669) and (#GH91633).

Bug Fixes to AST Handling
^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
4 changes: 4 additions & 0 deletions clang/include/clang/AST/DeclBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
5 changes: 4 additions & 1 deletion clang/lib/Parse/ParseExprCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1581,7 +1581,10 @@ ExprResult Parser::ParseLambdaExpressionAfterIntroducer(
TrailingReturnTypeLoc, &DS),
std::move(Attributes), DeclEndLoc);

Actions.ActOnLambdaClosureQualifiers(Intro, MutableLoc);
// We have called ActOnLambdaClosureQualifiers for parentheses-less cases
// above.
if (HasParentheses)
Actions.ActOnLambdaClosureQualifiers(Intro, MutableLoc);

if (HasParentheses && Tok.is(tok::kw_requires))
ParseTrailingRequiresClause(D);
Expand Down
10 changes: 10 additions & 0 deletions clang/lib/Sema/SemaExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18768,13 +18768,23 @@ bool Sema::tryCaptureVariable(
DeclContext *VarDC = Var->getDeclContext();
DeclContext *DC = CurContext;

// Skip past RequiresExprBodys because they don't constitute function scopes.
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,
// we can bailout early.
if (CapturingFunctionScopes == 0 && (!BuildAndDiagnose || VarDC == DC))
return true;

// Exception: Function parameters are not tied to the function's DeclContext
// until we enter the function definition. Capturing them anyway would result
// in an out-of-bounds error while traversing DC and its parents.
if (isa<ParmVarDecl>(Var) && !VarDC->isFunctionOrMethod())
return true;

const auto *VD = dyn_cast<VarDecl>(Var);
if (VD) {
if (VD->isInitCapture())
Expand Down
29 changes: 20 additions & 9 deletions clang/lib/Sema/SemaLambda.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1076,16 +1076,27 @@ void Sema::ActOnLambdaExpressionAfterIntroducer(LambdaIntroducer &Intro,
// 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 &&
"Lambda with explicit template param list should establish a "
"template param scope");
assert(TemplateParamScope->getParent());
if (TemplateParamScope->getParent()->getTemplateParamParent() != nullptr)
LambdaDependencyKind = CXXRecordDecl::LDK_AlwaysDependent;
} else if (CurScope->getTemplateParamParent() != nullptr) {
if (CurScope->getTemplateParamParent() != nullptr) {
LambdaDependencyKind = CXXRecordDecl::LDK_AlwaysDependent;
} else if (Scope *P = CurScope->getParent()) {
// Given a lambda defined inside a requires expression,
//
// struct S {
// S(auto var) requires requires { [&] -> decltype(var) { }; }
// {}
// };
//
// The parameter var is not injected into the function Decl at the point of
// parsing lambda. In such scenarios, perceiving it as dependent could
// result in the constraint being evaluated, which matches what GCC does.
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(
Expand Down
2 changes: 1 addition & 1 deletion clang/lib/Sema/TreeTransform.h
Original file line number Diff line number Diff line change
Expand Up @@ -14247,7 +14247,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()) &&
Expand Down
75 changes: 75 additions & 0 deletions clang/test/SemaCXX/lambda-unevaluated.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -189,3 +189,78 @@ void recursive() {

}
}

// GH63845: Test if we have skipped past RequiresExprBodyDecls in tryCaptureVariable().
namespace GH63845 {

template <bool> struct A {};

struct true_type {
constexpr operator bool() noexcept { return true; }
};

constexpr bool foo() {
true_type x{};
return requires { typename A<x>; };
}

static_assert(foo());

} // namespace GH63845

// GH69307: Test if we can correctly handle param decls that have yet to get into the function scope.
namespace GH69307 {

constexpr auto ICE() {
constexpr auto b = 1;
return [=](auto c) -> int
requires requires { b + c; }
{ return 1; };
};

constexpr auto Ret = ICE()(1);

} // namespace GH69307

// GH88081: Test if we evaluate the requires expression with lambda captures properly.
namespace GH88081 {

// Test that ActOnLambdaClosureQualifiers() is called only once.
void foo(auto value)
requires requires { [&] -> decltype(value) {}; }
// expected-error@-1 {{non-local lambda expression cannot have a capture-default}}
{}

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

// FIXME: 'value' does not constitute an ODR use here. Add a diagnostic for it.
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@-2 {{requested here}}
}

} // namespace GH88081

0 comments on commit 3d361b2

Please sign in to comment.