Skip to content

[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

Merged
merged 6 commits into from
Oct 4, 2023

Conversation

0x59616e
Copy link
Contributor

@0x59616e 0x59616e commented Sep 2, 2023

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

@0x59616e 0x59616e requested a review from a team as a code owner September 2, 2023 01:52
@0x59616e 0x59616e marked this pull request as draft September 2, 2023 01:59
@0x59616e 0x59616e marked this pull request as ready for review September 2, 2023 03:05
@cor3ntin
Copy link
Contributor

cor3ntin commented Sep 2, 2023

Just to note that this has some overlap with https://reviews.llvm.org/D159126

@cor3ntin cor3ntin added clang:frontend Language frontend issues, e.g. anything involving "Sema" lambda C++11 lambda expressions labels Sep 2, 2023
@cor3ntin
Copy link
Contributor

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.

@0x59616e 0x59616e marked this pull request as draft September 12, 2023 10:29
@llvmbot llvmbot added the clang Clang issues not falling into any other category label Sep 12, 2023
@llvmbot
Copy link
Member

llvmbot commented Sep 12, 2023

@llvm/pr-subscribers-clang

Changes

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

Full diff: https://github.com/llvm/llvm-project/pull/65193.diff

2 Files Affected:

  • (modified) clang/lib/Sema/SemaConcept.cpp (+61)
  • (added) clang/test/SemaCXX/pr64462.cpp (+20)
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}}
+}

@0x59616e 0x59616e marked this pull request as ready for review September 12, 2023 12:09
Comment on lines 572 to 581
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);
};

Copy link
Contributor

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

Copy link
Contributor Author

@0x59616e 0x59616e Sep 15, 2023

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.

Copy link
Contributor

@cor3ntin cor3ntin left a 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


assert(it != FD->decls().end() && "Cannot find the instantiated variable.");

Scope.InstantiatedLocal(decl, *it);
Copy link
Contributor

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

Copy link
Contributor Author

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.

@erichkeane
Copy link
Collaborator

We have addInstantiatedParametersToScope - Which we use in SetupConstraintsfor example We could reuse that to add the parameter of the enclosing function. We could similarly add anaddInstantiatedLocalVarsToScope` method doing the same thing.

And the logic could be moved to LambdaScopeForCallOperatorInstantiationRAII

@erichkeane for additional feedback

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.

@cor3ntin
Copy link
Contributor

cor3ntin commented Sep 12, 2023

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

@0x59616e
Copy link
Contributor Author

Support for nested lambda has been added. I will address your kindly feedbacks later.

Copy link
Contributor

@cor3ntin cor3ntin left a 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)

Copy link
Collaborator

@erichkeane erichkeane left a 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.

@0x59616e
Copy link
Contributor Author

0x59616e commented Oct 3, 2023

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
Copy link
Contributor

@cor3ntin cor3ntin left a 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)

@0x59616e
Copy link
Contributor Author

0x59616e commented Oct 4, 2023

Thank you for your review. I appreciate your feedback ;)

@0x59616e 0x59616e merged commit 548d67a into llvm:main Oct 4, 2023
@0x59616e 0x59616e deleted the pr64462-2 branch October 4, 2023 02:19
Guzhu-AMD pushed a commit to GPUOpen-Drivers/llvm-project that referenced this pull request Oct 12, 2023
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)
Copy link
Collaborator

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

Copy link
Contributor Author

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.

RKSimon added a commit to RKSimon/llvm-project that referenced this pull request Jun 27, 2024
…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).
RKSimon added a commit to RKSimon/llvm-project that referenced this pull request Jun 27, 2024
…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).
RKSimon added a commit to RKSimon/llvm-project that referenced this pull request Jun 28, 2024
…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).
RKSimon added a commit that referenced this pull request Jun 28, 2024
…y out logic (#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 #65193

Noticed by static analyser (I have no specific test case).
lravenclaw pushed a commit to lravenclaw/llvm-project that referenced this pull request Jul 3, 2024
…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).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category lambda C++11 lambda expressions
Projects
None yet
Development

Successfully merging this pull request may close these issues.

ICE on 'requires' for recursive function template
5 participants