https://github.com/usefahmed07 updated https://github.com/llvm/llvm-project/pull/235057
>From 560f49f4cff54d2c491317ce2226a3e1634f2b7c Mon Sep 17 00:00:00 2001 From: usefahmed07 <[email protected]> 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 <[email protected]> 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'}} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
