[clang] [Clang] Diagnose function parameters used in lambda bodies in default arguments (PR #235057)
Usef Ahmed via cfe-commits
cfe-commits at lists.llvm.org
Fri Oct 23 22:52:09 PDT 2026
https://github.com/usefahmed07 updated https://github.com/llvm/llvm-project/pull/235057
>From 560f49f4cff54d2c491317ce2226a3e1634f2b7c Mon Sep 17 00:00:00 2001
From: usefahmed07 <youssef.ahmed.hamaza at gmail.com>
Date: Fri, 23 Oct 2026 21:39:24 +0300
Subject: [PATCH 1/2] [Clang] Diagnose function parameters used in lambda
bodies in default arguments
A lambda in a default argument could name a function parameter without
any diagnostic, and codegen then emitted a bogus global for it. Visit the
lambda body in CheckDefaultArgumentVisitor, skipping entities declared
inside the lambda and lambdas in unevaluated operands.
Fixes #233811
---
clang/docs/ReleaseNotes.md | 2 +
clang/lib/Sema/SemaDeclCXX.cpp | 56 +++++++++++++++++++
.../default-arg-lambda-param-unevaluated.cpp | 7 +++
.../test/SemaCXX/default-arg-lambda-param.cpp | 8 +++
4 files changed, 73 insertions(+)
create mode 100644 clang/test/SemaCXX/default-arg-lambda-param-unevaluated.cpp
create mode 100644 clang/test/SemaCXX/default-arg-lambda-param.cpp
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 1ae4ec0d6b6d5a..035dbcc99e3fd7 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -1109,6 +1109,8 @@ documentation](CIR/index.md) for more information regarding ClangIR's status and
instantiation, explicit specialization, or call against a function template
whose parameter type uses pack indexing, and makes such valid matches
succeed instead of being rejected. (#GH232220)
+- Clang now diagnoses uses of function parameters inside the body of a lambda
+ appearing in a default argument. (#GH233811)
#### Bug Fixes to AST Handling
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 0fe0daa71c1bcf..66fa46c753a41e 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -86,6 +86,15 @@ class CheckDefaultArgumentVisitor
bool VisitPseudoObjectExpr(const PseudoObjectExpr *POE);
bool VisitCoawaitExpr(const CoawaitExpr *E);
bool VisitCoyieldExpr(const CoyieldExpr *E);
+ bool VisitUnaryExprOrTypeTraitExpr(const UnaryExprOrTypeTraitExpr *E);
+ bool VisitCXXNoexceptExpr(const CXXNoexceptExpr *E);
+ bool VisitCXXTypeidExpr(const CXXTypeidExpr *E);
+
+private:
+ bool CheckLambdaBody(const Stmt *S);
+ bool VisitOperand(const Expr *Node, bool Unevaluated);
+ SmallVector<const CXXMethodDecl *, 2> LambdaCallOperators;
+ unsigned UnevaluatedDepth = 0;
};
/// VisitExpr - Visit all of the children of this expression.
@@ -106,6 +115,10 @@ bool CheckDefaultArgumentVisitor::VisitDeclRefExpr(const DeclRefExpr *DRE) {
if (!isa<VarDecl, BindingDecl>(Decl))
return false;
+ for (const CXXMethodDecl *CallOp : LambdaCallOperators)
+ if (CallOp->Encloses(Decl->getDeclContext()))
+ return false;
+
if (const auto *Param = dyn_cast<ParmVarDecl>(Decl)) {
// C++ [dcl.fct.default]p9:
// [...] parameters of a function shall not be used in default
@@ -179,9 +192,52 @@ bool CheckDefaultArgumentVisitor::VisitLambdaExpr(const LambdaExpr *Lambda) {
auto *D = cast<VarDecl>(LC.getCapturedVar());
Invalid |= Visit(D->getInit());
}
+ // The body of a lambda inside an unevaluated operand is not potentially
+ // evaluated, so parameters named there are fine.
+ if (UnevaluatedDepth)
+ return Invalid;
+ LambdaCallOperators.push_back(Lambda->getCallOperator());
+ Invalid |= CheckLambdaBody(Lambda->getBody());
+ LambdaCallOperators.pop_back();
+ return Invalid;
+}
+
+bool CheckDefaultArgumentVisitor::CheckLambdaBody(const Stmt *S) {
+ if (!S)
+ return false;
+ if (isa<Expr>(S))
+ return Visit(S);
+ bool Invalid = false;
+ for (const Stmt *Child : S->children())
+ Invalid |= CheckLambdaBody(Child);
return Invalid;
}
+bool CheckDefaultArgumentVisitor::VisitOperand(const Expr *Node,
+ bool Unevaluated) {
+ if (Unevaluated)
+ ++UnevaluatedDepth;
+ bool Invalid = VisitExpr(Node);
+ if (Unevaluated)
+ --UnevaluatedDepth;
+ return Invalid;
+}
+
+bool CheckDefaultArgumentVisitor::VisitUnaryExprOrTypeTraitExpr(
+ const UnaryExprOrTypeTraitExpr *E) {
+ // The operand of sizeof/alignof is unevaluated, except for VLAs.
+ return VisitOperand(E, !E->getTypeOfArgument()->isVariablyModifiedType());
+}
+
+bool CheckDefaultArgumentVisitor::VisitCXXNoexceptExpr(
+ const CXXNoexceptExpr *E) {
+ return VisitOperand(E, /*Unevaluated=*/true);
+}
+
+bool CheckDefaultArgumentVisitor::VisitCXXTypeidExpr(const CXXTypeidExpr *E) {
+ return VisitOperand(E, !E->isPotentiallyEvaluated());
+}
+
bool CheckDefaultArgumentVisitor::VisitCoawaitExpr(const CoawaitExpr *E) {
// [expr.await] An await-expression shall not appear in a default argument.
// Note that this is generally diagnosed by isValidCoroutineContext,
diff --git a/clang/test/SemaCXX/default-arg-lambda-param-unevaluated.cpp b/clang/test/SemaCXX/default-arg-lambda-param-unevaluated.cpp
new file mode 100644
index 00000000000000..729552b2a431c9
--- /dev/null
+++ b/clang/test/SemaCXX/default-arg-lambda-param-unevaluated.cpp
@@ -0,0 +1,7 @@
+// RUN: %clang_cc1 -std=c++20 -fsyntax-only -verify %s
+// expected-no-diagnostics
+
+int p(int x, int = sizeof(x));
+int q(int x, int = sizeof([] { return x; }()));
+int r(int x, int = decltype([] { return x; }())());
+int s(int x, bool = noexcept([] { return x; }()));
diff --git a/clang/test/SemaCXX/default-arg-lambda-param.cpp b/clang/test/SemaCXX/default-arg-lambda-param.cpp
new file mode 100644
index 00000000000000..ab71b900f02170
--- /dev/null
+++ b/clang/test/SemaCXX/default-arg-lambda-param.cpp
@@ -0,0 +1,8 @@
+// RUN: %clang_cc1 -std=c++11 -fsyntax-only -verify %s
+// RUN: %clang_cc1 -std=c++20 -fsyntax-only -verify %s
+
+int a(int g, int h = [] { return g; }()); // expected-error {{default argument references parameter 'g'}}
+int b(int g, int h = [] { return sizeof(g); }());
+int c(int h = [](int x) { int y = x; return y; }(1));
+int d(int g, int h = [] { return [] { return g; }(); }()); // expected-error {{default argument references parameter 'g'}}
+int e(int g, int h = [] { int g = 1; return g; }());
>From ca836921b5f4c7c2ce087948d6afcb4eb122d38d Mon Sep 17 00:00:00 2001
From: usefahmed07 <youssef.ahmed.hamaza at gmail.com>
Date: Sat, 24 Oct 2026 08:51:56 +0300
Subject: [PATCH 2/2] [Clang] Diagnose parameter use in lambdas in default
arguments in tryCaptureVariable
Address review feedback: instead of checking lambda bodies in the
default-argument visitor, fix the root cause. tryCaptureVariable returned
early for a parameter that is not yet tied to its function (the case while
its default argument is parsed), so no capture diagnostic was ever issued.
Diagnose the use there when the lambda is in that parameter's default
argument. This also rejects such lambdas in unevaluated operands, and
removes the previous visitor change.
---
clang/lib/Sema/SemaDeclCXX.cpp | 56 -------------------
clang/lib/Sema/SemaExpr.cpp | 19 ++++++-
.../cxx1y-generic-lambdas-variadics.cpp | 2 +-
.../default-arg-lambda-param-unevaluated.cpp | 10 ++--
.../SemaCXX/lambda-as-default-parameter.cpp | 3 +-
5 files changed, 27 insertions(+), 63 deletions(-)
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 66fa46c753a41e..0fe0daa71c1bcf 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -86,15 +86,6 @@ class CheckDefaultArgumentVisitor
bool VisitPseudoObjectExpr(const PseudoObjectExpr *POE);
bool VisitCoawaitExpr(const CoawaitExpr *E);
bool VisitCoyieldExpr(const CoyieldExpr *E);
- bool VisitUnaryExprOrTypeTraitExpr(const UnaryExprOrTypeTraitExpr *E);
- bool VisitCXXNoexceptExpr(const CXXNoexceptExpr *E);
- bool VisitCXXTypeidExpr(const CXXTypeidExpr *E);
-
-private:
- bool CheckLambdaBody(const Stmt *S);
- bool VisitOperand(const Expr *Node, bool Unevaluated);
- SmallVector<const CXXMethodDecl *, 2> LambdaCallOperators;
- unsigned UnevaluatedDepth = 0;
};
/// VisitExpr - Visit all of the children of this expression.
@@ -115,10 +106,6 @@ bool CheckDefaultArgumentVisitor::VisitDeclRefExpr(const DeclRefExpr *DRE) {
if (!isa<VarDecl, BindingDecl>(Decl))
return false;
- for (const CXXMethodDecl *CallOp : LambdaCallOperators)
- if (CallOp->Encloses(Decl->getDeclContext()))
- return false;
-
if (const auto *Param = dyn_cast<ParmVarDecl>(Decl)) {
// C++ [dcl.fct.default]p9:
// [...] parameters of a function shall not be used in default
@@ -192,52 +179,9 @@ bool CheckDefaultArgumentVisitor::VisitLambdaExpr(const LambdaExpr *Lambda) {
auto *D = cast<VarDecl>(LC.getCapturedVar());
Invalid |= Visit(D->getInit());
}
- // The body of a lambda inside an unevaluated operand is not potentially
- // evaluated, so parameters named there are fine.
- if (UnevaluatedDepth)
- return Invalid;
- LambdaCallOperators.push_back(Lambda->getCallOperator());
- Invalid |= CheckLambdaBody(Lambda->getBody());
- LambdaCallOperators.pop_back();
- return Invalid;
-}
-
-bool CheckDefaultArgumentVisitor::CheckLambdaBody(const Stmt *S) {
- if (!S)
- return false;
- if (isa<Expr>(S))
- return Visit(S);
- bool Invalid = false;
- for (const Stmt *Child : S->children())
- Invalid |= CheckLambdaBody(Child);
return Invalid;
}
-bool CheckDefaultArgumentVisitor::VisitOperand(const Expr *Node,
- bool Unevaluated) {
- if (Unevaluated)
- ++UnevaluatedDepth;
- bool Invalid = VisitExpr(Node);
- if (Unevaluated)
- --UnevaluatedDepth;
- return Invalid;
-}
-
-bool CheckDefaultArgumentVisitor::VisitUnaryExprOrTypeTraitExpr(
- const UnaryExprOrTypeTraitExpr *E) {
- // The operand of sizeof/alignof is unevaluated, except for VLAs.
- return VisitOperand(E, !E->getTypeOfArgument()->isVariablyModifiedType());
-}
-
-bool CheckDefaultArgumentVisitor::VisitCXXNoexceptExpr(
- const CXXNoexceptExpr *E) {
- return VisitOperand(E, /*Unevaluated=*/true);
-}
-
-bool CheckDefaultArgumentVisitor::VisitCXXTypeidExpr(const CXXTypeidExpr *E) {
- return VisitOperand(E, !E->isPotentiallyEvaluated());
-}
-
bool CheckDefaultArgumentVisitor::VisitCoawaitExpr(const CoawaitExpr *E) {
// [expr.await] An await-expression shall not appear in a default argument.
// Note that this is generally diagnosed by isValidCoroutineContext,
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 563b9297a6f288..54cdbc39a33d5e 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -20179,8 +20179,25 @@ bool Sema::tryCaptureVariable(
// 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())
+ if (isa<ParmVarDecl>(Var) && !VarDC->isFunctionOrMethod()) {
+ // The parameters of a function are not tied to it while its default
+ // arguments are being parsed. A lambda there cannot capture a parameter,
+ // so naming one in the lambda's body is ill-formed.
+ if (BuildAndDiagnose && !Var->isInvalidDecl() && getCurLambda()) {
+ for (const auto &Rec : llvm::reverse(ExprEvalContexts)) {
+ auto *DefaultArgParam =
+ dyn_cast_or_null<ParmVarDecl>(Rec.ManglingContextDecl);
+ if (!DefaultArgParam)
+ continue;
+ if (DefaultArgParam->getFunctionScopeDepth() ==
+ cast<ParmVarDecl>(Var)->getFunctionScopeDepth())
+ Diag(ExprLoc, diag::err_param_default_argument_references_param)
+ << Var->getDeclName();
+ break;
+ }
+ }
return true;
+ }
const auto *VD = dyn_cast<VarDecl>(Var);
if (VD) {
diff --git a/clang/test/SemaCXX/cxx1y-generic-lambdas-variadics.cpp b/clang/test/SemaCXX/cxx1y-generic-lambdas-variadics.cpp
index a3464d66681784..c8a14b8ccedadb 100644
--- a/clang/test/SemaCXX/cxx1y-generic-lambdas-variadics.cpp
+++ b/clang/test/SemaCXX/cxx1y-generic-lambdas-variadics.cpp
@@ -134,7 +134,7 @@ void pr42587() {
(void)[](auto... args, int = args) {}; // expected-error {{default argument contains unexpanded parameter pack}}
(void)[](auto... args, decltype(args)) {}; // expected-error {{type contains unexpanded parameter pack}}
(void)[](auto... args, decltype(args)...) {}; // (ok)
- (void)[](auto... args, int = [=] { return args; }()) {}; // expected-error {{default argument contains unexpanded parameter pack}}
+ (void)[](auto... args, int = [=] { return args; }()) {}; // expected-error {{default argument contains unexpanded parameter pack}} expected-error {{default argument references parameter 'args'}}
(void)([]<typename ...T> (T t) {} + ...); // expected-error {{contains unexpanded parameter pack 'T'}} expected-error {{does not contain any unexpanded}} expected-warning 0-2{{extension}}
(void)([]<int ...N> (int k = N) {} + ...); // expected-error {{contains unexpanded parameter pack 'N'}} expected-error {{does not contain any unexpanded}} expected-warning 0-2{{extension}}
(void)([]<template<typename> typename ...T> (T<int>) {} + ...); // expected-error {{contains unexpanded parameter pack 'T'}} expected-error {{does not contain any unexpanded}} expected-warning 0-3{{extension}}
diff --git a/clang/test/SemaCXX/default-arg-lambda-param-unevaluated.cpp b/clang/test/SemaCXX/default-arg-lambda-param-unevaluated.cpp
index 729552b2a431c9..e5a1cdc635d054 100644
--- a/clang/test/SemaCXX/default-arg-lambda-param-unevaluated.cpp
+++ b/clang/test/SemaCXX/default-arg-lambda-param-unevaluated.cpp
@@ -1,7 +1,9 @@
// RUN: %clang_cc1 -std=c++20 -fsyntax-only -verify %s
-// expected-no-diagnostics
+
+// Using a function parameter in the body of a lambda requires capturing it,
+// even if the lambda appears in an unevaluated operand.
int p(int x, int = sizeof(x));
-int q(int x, int = sizeof([] { return x; }()));
-int r(int x, int = decltype([] { return x; }())());
-int s(int x, bool = noexcept([] { return x; }()));
+int q(int x, int = sizeof([] { return x; }())); // expected-error {{default argument references parameter 'x'}}
+int r(int x, int = decltype([] { return x; }())()); // expected-error {{default argument references parameter 'x'}}
+int s(int x, bool = noexcept([] { return x; }())); // expected-error {{default argument references parameter 'x'}}
diff --git a/clang/test/SemaCXX/lambda-as-default-parameter.cpp b/clang/test/SemaCXX/lambda-as-default-parameter.cpp
index 1f07a7f5644b7c..c57545794d33e2 100644
--- a/clang/test/SemaCXX/lambda-as-default-parameter.cpp
+++ b/clang/test/SemaCXX/lambda-as-default-parameter.cpp
@@ -3,4 +3,5 @@
struct a; // expected-note {{forward declaration of 'a'}} \
expected-note {{forward declaration of 'a'}}
void b(a c = [] { return c; }); // expected-error {{initialization of incomplete type 'a'}} \
- expected-error {{variable has incomplete type 'a'}}
+ expected-error {{variable has incomplete type 'a'}} \
+ expected-error {{default argument references parameter 'c'}}
More information about the cfe-commits
mailing list