-
Notifications
You must be signed in to change notification settings - Fork 13.6k
[clang][Sema] Fix a bug when instantiating a lambda with requires clause #65193
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Just to note that this has some overlap with https://reviews.llvm.org/D159126 |
Could you rebase on top of main? There have been a few changes to lambda that i think address part of the issues here, but this patch is still very much needed. We add a bug when referring to a capture in a decltype or a require would cause a crash, and that is fixed. I suspect the salient part of your patch is the addition of the parameters of the enclosing function. |
@llvm/pr-subscribers-clang ChangesInstantiating a lambda at a scope different from where it is defined will paralyze clang if the trailing require clause refers to local variables. This patch fixes this by re-adding the local variables to Fixes #64462Full diff: https://github.com/llvm/llvm-project/pull/65193.diff 2 Files Affected:
diff --git a/clang/lib/Sema/SemaConcept.cpp b/clang/lib/Sema/SemaConcept.cpp index d1fa8e7831225b7..45a25cdcb88c8d3 100644 --- a/clang/lib/Sema/SemaConcept.cpp +++ b/clang/lib/Sema/SemaConcept.cpp @@ -567,6 +567,59 @@ bool Sema::addInstantiatedCapturesToScope( return false; } +static void addDeclsFromParentScope(Sema &S, FunctionDecl *FD, + LocalInstantiationScope &Scope) { + assert(isLambdaCallOperator(FD) && "FD must be a lambda call operator"); + + LambdaScopeInfo *LSI = cast(S.getFunctionScopes().back()); + + auto captureVar = [&](VarDecl *VD) { + LSI->addCapture(VD, /*isBlock=*/false, /*isByref=*/false, + /*isNested=*/false, VD->getBeginLoc(), SourceLocation(), + VD->getType(), /*Invalid=*/false); + }; + + FD = dyn_cast(FD->getParent()->getParent()); + FunctionDecl *Pattern = nullptr; + + if (!FD || !FD->isTemplateInstantiation()) + return; + + Pattern = FD->getPrimaryTemplate()->getTemplatedDecl(); + + for (unsigned I = 0; I < Pattern->getNumParams(); ++I) { + ParmVarDecl *PVD = Pattern->getParamDecl(I); + if (!PVD->isParameterPack()) { + Scope.InstantiatedLocal(PVD, FD->getParamDecl(I)); + captureVar(FD->getParamDecl(I)); + continue; + } + + Scope.MakeInstantiatedLocalArgPack(PVD); + + for (ParmVarDecl *Inst : FD->parameters().drop_front(I)) { + Scope.InstantiatedLocalPackArg(PVD, Inst); + captureVar(Inst); + } + } + + for (auto *decl : Pattern->decls()) { + if (!isa(decl) || isa(decl)) + continue; + + IdentifierInfo *II = cast(decl)->getIdentifier(); + auto it = llvm::find_if(FD->decls(), [&](Decl *inst) { + VarDecl *VD = dyn_cast(inst); + return VD && VD->isLocalVarDecl() && VD->getIdentifier() == II; + }); + + assert(it != FD->decls().end() && "Cannot find the instantiated variable."); + + Scope.InstantiatedLocal(decl, *it); + captureVar(cast(*it)); + } +} + bool Sema::SetupConstraintScope( FunctionDecl *FD, std::optional> TemplateArgs, MultiLevelTemplateArgumentList MLTAL, LocalInstantiationScope &Scope) { @@ -684,9 +737,11 @@ bool Sema::CheckFunctionConstraints(const FunctionDecl *FD, CtxToSave = CtxToSave->getNonTransparentContext(); } + const bool shouldAddDeclsFromParentScope = !CtxToSave->Encloses(CurContext); ContextRAII SavedContext{*this, CtxToSave}; LocalInstantiationScope Scope(*this, !ForOverloadResolution || isLambdaCallOperator(FD)); + std::optional MLTAL = SetupConstraintCheckingTemplateArgumentsAndScope( const_cast(FD), {}, Scope); @@ -705,6 +760,9 @@ bool Sema::CheckFunctionConstraints(const FunctionDecl *FD, LambdaScopeForCallOperatorInstantiationRAII LambdaScope( *this, const_cast(FD), *MLTAL, Scope); + if (isLambdaCallOperator(FD) && shouldAddDeclsFromParentScope) + addDeclsFromParentScope(*this, const_cast(FD), Scope); + return CheckConstraintSatisfaction( FD, {FD->getTrailingRequiresClause()}, *MLTAL, SourceRange(UsageLoc.isValid() ? UsageLoc : FD->getLocation()), @@ -896,6 +954,9 @@ bool Sema::CheckInstantiatedFunctionTemplateConstraints( LambdaScopeForCallOperatorInstantiationRAII LambdaScope( *this, const_cast(Decl), *MLTAL, Scope); + if (isLambdaCallOperator(Decl)) + addDeclsFromParentScope(*this, Decl, Scope); + llvm::SmallVector Converted; return CheckConstraintSatisfaction(Template, TemplateAC, Converted, *MLTAL, PointOfInstantiation, Satisfaction); diff --git a/clang/test/SemaCXX/pr64462.cpp b/clang/test/SemaCXX/pr64462.cpp new file mode 100644 index 000000000000000..cc8b5510d1a823a --- /dev/null +++ b/clang/test/SemaCXX/pr64462.cpp @@ -0,0 +1,20 @@ +// RUN: %clang_cc1 -verify -std=c++20 -fsyntax-only %s + +auto c1(auto f, auto ...fs) { + constexpr bool a = true; + // expected-note@+2{{because substituted constraint expression is ill-formed: no matching function for call to 'c1'}} + // expected-note@+1{{candidate template ignored: constraints not satisfied [with auto:1 = bool}} + return [](auto) requires a && (c1(fs...)) {}; +} + +auto c2(auto f, auto ...fs) { + constexpr bool a = true; + // expected-note@+2{{because substituted constraint expression is ill-formed: no matching function for call to 'c2'}} + // expected-note@+1{{candidate function not viable: constraints not satisfied}} + return []() requires a && (c2(fs...)) {}; +} + +void foo() { + c1(true)(true); // expected-error {{no matching function for call to object of type}} + c2(true)(); // expected-error {{no matching function for call to object of type}} +} |
clang/lib/Sema/SemaConcept.cpp
Outdated
assert(isLambdaCallOperator(FD) && "FD must be a lambda call operator"); | ||
|
||
LambdaScopeInfo *LSI = cast<LambdaScopeInfo>(S.getFunctionScopes().back()); | ||
|
||
auto captureVar = [&](VarDecl *VD) { | ||
LSI->addCapture(VD, /*isBlock=*/false, /*isByref=*/false, | ||
/*isNested=*/false, VD->getBeginLoc(), SourceLocation(), | ||
VD->getType(), /*Invalid=*/false); | ||
}; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That logic is already in LambdaScopeForCallOperatorInstantiationRAII
, you need to add it again
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I couldn't find where this addCapture
logic is in LambdaScopeForCallOperatorInstantiationRAII
. Would you mind giving more hints ? Thanks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have addInstantiatedParametersToScope
- Which we use in SetupConstraints
for example
We could reuse that to add the parameter of the enclosing function.
We could similarly add an addInstantiatedLocalVarsToScope
method doing the same thing.
And the logic could be moved to LambdaScopeForCallOperatorInstantiationRAII
@erichkeane for additional feedback
clang/lib/Sema/SemaConcept.cpp
Outdated
|
||
assert(it != FD->decls().end() && "Cannot find the instantiated variable."); | ||
|
||
Scope.InstantiatedLocal(decl, *it); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A I understand, this is the important bit, we don't care they are captures, all captures are added unconditionally
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm having difficulty understanding the problem. Is there anything incorrect here ? It would be great if more context could be provided. Thanks.
That sounds like a really good idea to me. We seem to be messing with this sort of thing all over the place, so having it all in 1 place seems beneficial. |
There is also the question of what happen in the deeply nested case int foo(auto...);
auto f(auto... a) {
return [] {
return [] () requires requires { foo(a...) ;} {};
}();
}
auto g = f()(); we may have to visit all functions recursively from the bottom up to add their locals / parameters / captures |
Support for nested lambda has been added. I will address your kindly feedbacks later. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I found a couple of typos, and this needs a release note to indicate #64462 was fixed.
I'd like @erichkeane to take a last look but otherwise I think this looks reasonable
(Sorry for the delay)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a pair of nits based on the style guide, else LGTM.
The typo and the style issues have been fixed. Thank you. Now I'm going to rebase to main and add release note. |
Instantiating a lambda at a scope different from its definition scope will paralyze clang if the trailing require clause refers to local variables of that definition scope. This patch fixes this by re-adding the local variables to `LocalInstantiationScope`. Fixes llvm#64462
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, thanks! (Feel free to squash-merge yourself)
Thank you for your review. I appreciate your feedback ;) |
Local branch amd-gfx b60b014 Merged main:9370271ec5de into amd-gfx:a9743de9eb8f Remote branch main 548d67a [clang][Sema] Fix a bug when instantiating a lambda with requires clause (llvm#65193)
|
||
SemasRef.RebuildLambdaScopeInfo(cast<CXXMethodDecl>(FD)); | ||
if (!FD || !Pattern) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@0x59616e Should the !FD
be !ParentFD
instead? Just noticed this in a static analysis report
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm looking into it.
…y out logic We should be checking for a failed dyn_cast on the ParentFD result - not the loop invariant FD root value. Seems to have been introduced in llvm#65193 Noticed by static analyser (I have no specific test case).
…y out logic We should be checking for a failed dyn_cast on the ParentFD result - not the loop invariant FD root value. Seems to have been introduced in llvm#65193 Noticed by static analyser (I have no specific test case).
…y out logic We should be checking for a failed dyn_cast on the ParentFD result - not the loop invariant FD root value. Seems to have been introduced in llvm#65193 Noticed by static analyser (I have no specific test case).
…y out logic (llvm#96888) We should be checking for a failed dyn_cast on the ParentFD result - not the loop invariant FD root value. Seems to have been introduced in llvm#65193 Noticed by static analyser (I have no specific test case).
Instantiating a lambda at a scope different from where it is defined will paralyze clang if the trailing require clause refers to local variables. This patch fixes this by re-adding the local variables to
LocalInstantiationScope
.Fixes #64462