https://github.com/benedekaibas updated https://github.com/llvm/llvm-project/pull/220114
>From cd800a911d402fafe1f32e4ff2709d5061732e08 Mon Sep 17 00:00:00 2001 From: benedekaibas <[email protected]> Date: Sat, 29 Aug 2026 22:41:18 +0200 Subject: [PATCH 1/9] [analyzer] Resolve lambda captures for explicit object parameters --- clang/lib/StaticAnalyzer/Core/ExprEngine.cpp | 26 +++++++++-- .../test/Analysis/explicit-lambda-capture.cpp | 43 +++++++++++++++++++ 2 files changed, 65 insertions(+), 4 deletions(-) create mode 100644 clang/test/Analysis/explicit-lambda-capture.cpp diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp index e6349eb4eba2ae..01e05924a25378 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -3040,12 +3040,30 @@ void ExprEngine::VisitCommonDeclRefExpr(const Expr *Ex, const NamedDecl *D, // Sema follows a sequence of complex rules to determine whether the // variable should be captured. if (const FieldDecl *FD = LambdaCaptureFields[VD]) { - Loc CXXThis = svalBuilder.getCXXThis(MD, SF); - SVal CXXThisVal = state->getSVal(CXXThis); - return std::make_pair(state->getLValue(FD, CXXThisVal), FD->getType()); + if (MD->isImplicitObjectMemberFunction()) { + Loc CXXThis = svalBuilder.getCXXThis(MD, SF); + SVal CXXThisVal = state->getSVal(CXXThis); + return std::make_pair(state->getLValue(FD, CXXThisVal), + FD->getType()); + } + const ParmVarDecl *PVD = MD->getParamDecl(0); + if (const Expr *CallSite = SF->getCallSite()) { + unsigned Idx = PVD->getFunctionScopeIndex(); + const ParamVarRegion *PVR = + state->getStateManager().getRegionManager().getParamVarRegion( + CallSite, Idx, SF); + const Expr *SelfArgExpr = cast<CallExpr>(CallSite)->getArg(0); + state = + state->bindLoc(loc::MemRegionVal(PVR), + state->getSVal(SelfArgExpr, SF->getParent()), SF); + SVal ParamSVal = state->getSVal(loc::MemRegionVal(PVR)); + if (!PVD->getType()->isReferenceType()) + return std::make_pair(state->getLValue(FD, loc::MemRegionVal(PVR)), + FD->getType()); + return std::make_pair(state->getLValue(FD, ParamSVal), FD->getType()); + } } } - return std::nullopt; }; diff --git a/clang/test/Analysis/explicit-lambda-capture.cpp b/clang/test/Analysis/explicit-lambda-capture.cpp new file mode 100644 index 00000000000000..2d98b80ce9637f --- /dev/null +++ b/clang/test/Analysis/explicit-lambda-capture.cpp @@ -0,0 +1,43 @@ +// RUN: %clang_cc1 -analyze -std=c++23 -analyzer-checker=core.DivideZero -verify %s + +int implicit_capture_by_value() { + int d = 0; + auto lam = [d]() { return 1 / d; }; // expected-warning {{Division by zero}} + return lam(); +} + +int explicit_rvalue_self_capture_by_reference() { + int d = 0; + auto lam = [&d](this auto &&self) { return 1 / d; }; // expected-warning {{Division by zero}} + return lam(); +} + +int gh218708_explicit_rvalue_self() { + int d = 0; + auto lam = [d](this auto &&self) { return 1 / d; }; // expected-warning {{Division by zero}} + return lam(); +} + +int gh218708_explicit_lvalue_self() { + int d = 0; + auto lam = [d](this auto &self) { return 1 / d; }; // expected-warning {{Division by zero}} + return lam(); +} + +int gh218708_explicit_by_value_self() { + int d = 0; + auto lam = [d](this auto self) { return 1 / d; }; // expected-warning {{Division by zero}} + return lam(); +} + +int explicit_rvalue_no_error() { + int d = 5; + auto lam = [d](this auto &&self) { return 1 / d; }; // 'd' is non-zero so there is no division by zero error. + return lam(); +} + +int explicit_by_value_no_error() { + int d = 9; + auto lam = [d](this auto self) { return 1 / d; }; // 'd' is non-zero so there is no division by zero error. + return lam(); +} >From 14f335aaecfcfe15de5aa0550c210b97e20a5283 Mon Sep 17 00:00:00 2001 From: benedekaibas <[email protected]> Date: Sat, 29 Aug 2026 22:58:50 +0200 Subject: [PATCH 2/9] Fix unconditional compute for the reference type construction. --- clang/lib/StaticAnalyzer/Core/ExprEngine.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp index 01e05924a25378..0ab2d22b87fe26 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -3053,14 +3053,15 @@ void ExprEngine::VisitCommonDeclRefExpr(const Expr *Ex, const NamedDecl *D, state->getStateManager().getRegionManager().getParamVarRegion( CallSite, Idx, SF); const Expr *SelfArgExpr = cast<CallExpr>(CallSite)->getArg(0); - state = - state->bindLoc(loc::MemRegionVal(PVR), - state->getSVal(SelfArgExpr, SF->getParent()), SF); - SVal ParamSVal = state->getSVal(loc::MemRegionVal(PVR)); - if (!PVD->getType()->isReferenceType()) - return std::make_pair(state->getLValue(FD, loc::MemRegionVal(PVR)), + if (PVD->getType()->isReferenceType()) { + state = state->bindLoc(loc::MemRegionVal(PVR), + state->getSVal(SelfArgExpr, SF->getParent()), + SF); + SVal ParamSVal = state->getSVal(loc::MemRegionVal(PVR)); + return std::make_pair(state->getLValue(FD, ParamSVal), FD->getType()); - return std::make_pair(state->getLValue(FD, ParamSVal), FD->getType()); + } + return std::make_pair(state->getLValue(FD, loc::MemRegionVal(PVR)), FD->getType()); } } } >From 19674e6cff8045269ef8655cd4e16816127af6f4 Mon Sep 17 00:00:00 2001 From: benedekaibas <[email protected]> Date: Sat, 29 Aug 2026 23:05:25 +0200 Subject: [PATCH 3/9] Fix formatting issue --- clang/lib/StaticAnalyzer/Core/ExprEngine.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp index 0ab2d22b87fe26..c503d686fb407d 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -3061,7 +3061,8 @@ void ExprEngine::VisitCommonDeclRefExpr(const Expr *Ex, const NamedDecl *D, return std::make_pair(state->getLValue(FD, ParamSVal), FD->getType()); } - return std::make_pair(state->getLValue(FD, loc::MemRegionVal(PVR)), FD->getType()); + return std::make_pair(state->getLValue(FD, loc::MemRegionVal(PVR)), + FD->getType()); } } } >From 57ec566d0cf17f95ae58630740d8c90be7d2877b Mon Sep 17 00:00:00 2001 From: benedekaibas <[email protected]> Date: Mon, 31 Aug 2026 11:43:52 +0200 Subject: [PATCH 4/9] Correct comments and RUN line. --- clang/test/Analysis/explicit-lambda-capture.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/clang/test/Analysis/explicit-lambda-capture.cpp b/clang/test/Analysis/explicit-lambda-capture.cpp index 2d98b80ce9637f..89d249a63da43e 100644 --- a/clang/test/Analysis/explicit-lambda-capture.cpp +++ b/clang/test/Analysis/explicit-lambda-capture.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -analyze -std=c++23 -analyzer-checker=core.DivideZero -verify %s +// RUN: %clang_analyze_cc1 -std=c++23 -analyzer-checker=core -verify %s int implicit_capture_by_value() { int d = 0; @@ -32,12 +32,12 @@ int gh218708_explicit_by_value_self() { int explicit_rvalue_no_error() { int d = 5; - auto lam = [d](this auto &&self) { return 1 / d; }; // 'd' is non-zero so there is no division by zero error. + auto lam = [d](this auto &&self) { return 1 / d; }; // no-warning return lam(); } int explicit_by_value_no_error() { int d = 9; - auto lam = [d](this auto self) { return 1 / d; }; // 'd' is non-zero so there is no division by zero error. + auto lam = [d](this auto self) { return 1 / d; }; // no-warning return lam(); } >From 9c54a9ba6ebd4f50e021855294c40465bae60de2 Mon Sep 17 00:00:00 2001 From: benedekaibas <[email protected]> Date: Mon, 31 Aug 2026 12:35:51 +0200 Subject: [PATCH 5/9] Created resolveAsLambdaCapturedVar function and included the MRMgr direct reference. --- .../Core/PathSensitive/ExprEngine.h | 4 + clang/lib/StaticAnalyzer/Core/ExprEngine.cpp | 91 +++++++++---------- 2 files changed, 49 insertions(+), 46 deletions(-) diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h index 68d4362aca9417..19ceec5cf2df60 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h @@ -677,6 +677,10 @@ class ExprEngine { static std::pair<const ProgramPointTag *, const ProgramPointTag *> getEagerlyAssumeBifurcationTags(); + std::optional<std::pair<SVal, QualType>> + resolveAsLambdaCapturedVar(const Expr *Ex, const ValueDecl *VD, + ExplodedNode *Pred); + ProgramStateRef handleLValueBitCast(ProgramStateRef state, const Expr *Ex, const StackFrame *SF, QualType T, QualType ExTy, const CastExpr *CastE, diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp index c503d686fb407d..1f047c82c32e99 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -3018,63 +3018,62 @@ void ExprEngine::processSwitch(const SwitchStmt *Switch, ExplodedNode *Pred, // Transfer functions: Loads and stores. //===----------------------------------------------------------------------===// -void ExprEngine::VisitCommonDeclRefExpr(const Expr *Ex, const NamedDecl *D, - ExplodedNode *Pred, - ExplodedNodeSet &Dst) { +std::optional<std::pair<SVal, QualType>> +ExprEngine::resolveAsLambdaCapturedVar(const Expr *Ex, const ValueDecl *VD, + ExplodedNode *Pred) { ProgramStateRef state = Pred->getState(); const StackFrame *SF = Pred->getStackFrame(); - auto resolveAsLambdaCapturedVar = - [&](const ValueDecl *VD) -> std::optional<std::pair<SVal, QualType>> { - const auto *MD = dyn_cast<CXXMethodDecl>(SF->getDecl()); - const auto *DeclRefEx = dyn_cast<DeclRefExpr>(Ex); - if (AMgr.options.ShouldInlineLambdas && DeclRefEx && - DeclRefEx->refersToEnclosingVariableOrCapture() && MD && - MD->getParent()->isLambda()) { - // Lookup the field of the lambda. - const CXXRecordDecl *CXXRec = MD->getParent(); - llvm::DenseMap<const ValueDecl *, FieldDecl *> LambdaCaptureFields; - FieldDecl *LambdaThisCaptureField; - CXXRec->getCaptureFields(LambdaCaptureFields, LambdaThisCaptureField); - - // Sema follows a sequence of complex rules to determine whether the - // variable should be captured. - if (const FieldDecl *FD = LambdaCaptureFields[VD]) { - if (MD->isImplicitObjectMemberFunction()) { - Loc CXXThis = svalBuilder.getCXXThis(MD, SF); - SVal CXXThisVal = state->getSVal(CXXThis); - return std::make_pair(state->getLValue(FD, CXXThisVal), - FD->getType()); - } - const ParmVarDecl *PVD = MD->getParamDecl(0); - if (const Expr *CallSite = SF->getCallSite()) { - unsigned Idx = PVD->getFunctionScopeIndex(); - const ParamVarRegion *PVR = - state->getStateManager().getRegionManager().getParamVarRegion( - CallSite, Idx, SF); - const Expr *SelfArgExpr = cast<CallExpr>(CallSite)->getArg(0); - if (PVD->getType()->isReferenceType()) { - state = state->bindLoc(loc::MemRegionVal(PVR), - state->getSVal(SelfArgExpr, SF->getParent()), - SF); - SVal ParamSVal = state->getSVal(loc::MemRegionVal(PVR)); - return std::make_pair(state->getLValue(FD, ParamSVal), - FD->getType()); - } - return std::make_pair(state->getLValue(FD, loc::MemRegionVal(PVR)), - FD->getType()); + const auto *MD = dyn_cast<CXXMethodDecl>(SF->getDecl()); + const auto *DeclRefEx = dyn_cast<DeclRefExpr>(Ex); + if (AMgr.options.ShouldInlineLambdas && DeclRefEx && + DeclRefEx->refersToEnclosingVariableOrCapture() && MD && + MD->getParent()->isLambda()) { + // Lookup the field of the lambda. + const CXXRecordDecl *CXXRec = MD->getParent(); + llvm::DenseMap<const ValueDecl *, FieldDecl *> LambdaCaptureFields; + FieldDecl *LambdaThisCaptureField; + CXXRec->getCaptureFields(LambdaCaptureFields, LambdaThisCaptureField); + + // Sema follows a sequence of complex rules to determine whether the + // variable should be captured. + if (const FieldDecl *FD = LambdaCaptureFields[VD]) { + if (MD->isImplicitObjectMemberFunction()) { + Loc CXXThis = svalBuilder.getCXXThis(MD, SF); + SVal CXXThisVal = state->getSVal(CXXThis); + return {{state->getLValue(FD, CXXThisVal), FD->getType()}}; + } + const ParmVarDecl *PVD = MD->getParamDecl(0); + if (const Expr *CallSite = SF->getCallSite()) { + const ParamVarRegion *PVR = + MRMgr.getParamVarRegion(CallSite, /*Index=*/0, SF); + const Expr *SelfArgExpr = cast<CallExpr>(CallSite)->getArg(0); + if (PVD->getType()->isReferenceType()) { + state = + state->bindLoc(loc::MemRegionVal(PVR), + state->getSVal(SelfArgExpr, SF->getParent()), SF); + SVal ParamSVal = state->getSVal(loc::MemRegionVal(PVR)); + return {{state->getLValue(FD, ParamSVal), FD->getType()}}; } + return {{state->getLValue(FD, loc::MemRegionVal(PVR)), FD->getType()}}; } } - return std::nullopt; - }; + } + return std::nullopt; +} + +void ExprEngine::VisitCommonDeclRefExpr(const Expr *Ex, const NamedDecl *D, + ExplodedNode *Pred, + ExplodedNodeSet &Dst) { + ProgramStateRef state = Pred->getState(); + const StackFrame *SF = Pred->getStackFrame(); if (const auto *VD = dyn_cast<VarDecl>(D)) { // C permits "extern void v", and if you cast the address to a valid type, // you can even do things with it. We simply pretend assert(Ex->isGLValue() || VD->getType()->isVoidType()); std::optional<std::pair<SVal, QualType>> VInfo = - resolveAsLambdaCapturedVar(VD); + resolveAsLambdaCapturedVar(Ex, VD, Pred); if (!VInfo) VInfo = std::make_pair(state->getLValue(VD, SF), VD->getType()); @@ -3116,7 +3115,7 @@ void ExprEngine::VisitCommonDeclRefExpr(const Expr *Ex, const NamedDecl *D, if (const auto *BD = dyn_cast<BindingDecl>(D)) { // Handle structured bindings captured by lambda. if (std::optional<std::pair<SVal, QualType>> VInfo = - resolveAsLambdaCapturedVar(BD)) { + resolveAsLambdaCapturedVar(Ex, BD, Pred)) { auto [V, T] = VInfo.value(); if (T->isReferenceType()) { >From fbb50ddfec1f359fa09da7e85fcc78e2e4c8aabc Mon Sep 17 00:00:00 2001 From: benedekaibas <[email protected]> Date: Mon, 31 Aug 2026 22:33:31 +0200 Subject: [PATCH 6/9] Add more test cases. --- .../test/Analysis/explicit-lambda-capture.cpp | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/clang/test/Analysis/explicit-lambda-capture.cpp b/clang/test/Analysis/explicit-lambda-capture.cpp index 89d249a63da43e..5d5100f8eb723b 100644 --- a/clang/test/Analysis/explicit-lambda-capture.cpp +++ b/clang/test/Analysis/explicit-lambda-capture.cpp @@ -1,4 +1,6 @@ -// RUN: %clang_analyze_cc1 -std=c++23 -analyzer-checker=core -verify %s +// RUN: %clang_analyze_cc1 -std=c++23 -analyzer-checker=core,cplusplus.Move -verify %s + +#include "Inputs/system-header-simulator-cxx.h" int implicit_capture_by_value() { int d = 0; @@ -41,3 +43,36 @@ int explicit_by_value_no_error() { auto lam = [d](this auto self) { return 1 / d; }; // no-warning return lam(); } + +auto by_val() { + std::vector<int> v; + auto lam = [v](this auto self) { + auto res = std::move(v); + return res; + }; + + lam(); + lam(); +} + +auto by_lval() { + std::vector<int> v; + auto lam = [v](this auto &self) { + auto res = std::move(v); // expected-warning {{Moved-from object '' of type 'std::vector' is moved}} + return res; + }; + + lam(); + lam(); +} + +auto by_rval() { + std::vector<int> v; + auto lam = [v](this auto &&self) { + auto res = std::move(v); // expected-warning {{Moved-from object '' of type 'std::vector' is moved}} + return res; + }; + + std::move(lam)(); + std::move(lam)(); +} >From efda1198e71a09e79bc514cac5d381209a2d217c Mon Sep 17 00:00:00 2001 From: benedekaibas <[email protected]> Date: Tue, 1 Sep 2026 00:19:30 +0200 Subject: [PATCH 7/9] [analyzer] Assert isImplicitObjectMemberFunction in SValBuilder::getCXXThis --- clang/lib/StaticAnalyzer/Core/SValBuilder.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp b/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp index 55669c1bef5d14..1c503be602b9fa 100644 --- a/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp +++ b/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp @@ -311,6 +311,7 @@ SValBuilder::getCastedMemRegionVal(const MemRegion *R, QualType Ty) { /// Return a memory region for the 'this' object reference. loc::MemRegionVal SValBuilder::getCXXThis(const CXXMethodDecl *D, const StackFrame *SF) { + assert(D->isImplicitObjectMemberFunction() && "D must be an implicit object member function"); return loc::MemRegionVal( getRegionManager().getCXXThisRegion(D->getThisType(), SF)); } >From 0ec03f49fb1d014995933a3a7e0e3e50908a3ff1 Mon Sep 17 00:00:00 2001 From: benedekaibas <[email protected]> Date: Tue, 1 Sep 2026 00:28:17 +0200 Subject: [PATCH 8/9] Run clang-format. --- clang/lib/StaticAnalyzer/Core/SValBuilder.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp b/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp index 1c503be602b9fa..1f689cca735cea 100644 --- a/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp +++ b/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp @@ -311,7 +311,8 @@ SValBuilder::getCastedMemRegionVal(const MemRegion *R, QualType Ty) { /// Return a memory region for the 'this' object reference. loc::MemRegionVal SValBuilder::getCXXThis(const CXXMethodDecl *D, const StackFrame *SF) { - assert(D->isImplicitObjectMemberFunction() && "D must be an implicit object member function"); + assert(D->isImplicitObjectMemberFunction() && + "D must be an implicit object member function"); return loc::MemRegionVal( getRegionManager().getCXXThisRegion(D->getThisType(), SF)); } >From f28c264da2c3e83c5c9edd02b39aa3b08c6e8cf0 Mon Sep 17 00:00:00 2001 From: benedekaibas <[email protected]> Date: Wed, 2 Sep 2026 15:33:39 +0200 Subject: [PATCH 9/9] Add guard for MD to check if it is an implicit object member function. --- clang/lib/StaticAnalyzer/Checkers/CXXSelfAssignmentChecker.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/clang/lib/StaticAnalyzer/Checkers/CXXSelfAssignmentChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CXXSelfAssignmentChecker.cpp index be31f5c94c4af3..84320909999d26 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CXXSelfAssignmentChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CXXSelfAssignmentChecker.cpp @@ -44,6 +44,9 @@ void CXXSelfAssignmentChecker::checkBeginFunction(CheckerContext &C) const { return; if (!MD->isCopyAssignmentOperator() && !MD->isMoveAssignmentOperator()) return; + if (!MD->isImplicitObjectMemberFunction()) + return; + auto &State = C.getState(); auto &SVB = C.getSValBuilder(); auto ThisVal = State->getSVal(SVB.getCXXThis(MD, SF)); _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
