Author: Benedek Kaibas
Date: 2026-09-14T13:02:14+02:00
New Revision: e9d50d35104db2a3ac87e1280e043a0e7f4f7e4d

URL: 
https://github.com/llvm/llvm-project/commit/e9d50d35104db2a3ac87e1280e043a0e7f4f7e4d
DIFF: 
https://github.com/llvm/llvm-project/commit/e9d50d35104db2a3ac87e1280e043a0e7f4f7e4d.diff

LOG: [analyzer] Resolve lambda captures for explicit object parameters (#219726)

Currently explicit object parameters are not modeled in the
`VisitCommonDeclRefExpr` function in `ExprEngine`. Because of this when
a lambda has an explicit object parameter and a possible division by
zero the `core.DivideZero` checker does not emit warning. This PR solves
that issue by deciding if the lambda has an explicit object parameter
and then records the binding (if the parameter has the reference type)
between the parameter and the argument's expression otherwise it returns
the captured field's lvalue.

This PR fixes #218708

Added: 
    clang/test/Analysis/explicit-lambda-capture.cpp

Modified: 
    clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
    clang/lib/StaticAnalyzer/Core/ExprEngine.cpp

Removed: 
    


################################################################################
diff  --git 
a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h 
b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
index fe49e830fde76..af813714e6831 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h
@@ -654,6 +654,13 @@ class ExprEngine {
                            const CastExpr *CastE, ExplodedNodeSet &Dst,
                            ExplodedNode *Pred);
 
+private:
+  /// Resolve a lambda-captured variable's address based on whether the
+  /// enclosing method has an implicit or explicit object parameter.
+  std::optional<std::pair<SVal, QualType>>
+  resolveAsLambdaCapturedVar(const Expr *Ex, const ValueDecl *VD,
+                             const ExplodedNode *Pred) const;
+
 public:
   SVal evalBinOp(ProgramStateRef ST, BinaryOperator::Opcode Op,
                  SVal LHS, SVal RHS, QualType T) {

diff  --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp 
b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
index 4d1cd51f3a2ab..e482cb7ef7a55 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
@@ -3257,43 +3257,69 @@ 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) {
-  ProgramStateRef state = Pred->getState();
+std::optional<std::pair<SVal, QualType>>
+ExprEngine::resolveAsLambdaCapturedVar(const Expr *Ex, const ValueDecl *VD,
+                                       const ExplodedNode *Pred) const {
+  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]) {
-        Loc CXXThis = svalBuilder.getCXXThis(MD, SF);
-        SVal CXXThisVal = state->getSVal(CXXThis);
-        return std::make_pair(state->getLValue(FD, CXXThisVal), 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()) {
+    return std::nullopt;
+  }
+  // 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()) {
+        // TODO: This binding should happen at call entry instead. The same way
+        // it does for the implicit object parameter (CXXThisRegion, bound in
+        // CXXInstanceCall::getInitialStackFrameContents). The explicit object
+        // parameter's ParamVarRegion is never bound there today, so this
+        // binding is just a workaround. A follow-up PR should properly bind it
+        // at call entry, so it is no longer needed here.
+        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());
@@ -3335,7 +3361,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()) {

diff  --git a/clang/test/Analysis/explicit-lambda-capture.cpp 
b/clang/test/Analysis/explicit-lambda-capture.cpp
new file mode 100644
index 0000000000000..69b542a4af2cf
--- /dev/null
+++ b/clang/test/Analysis/explicit-lambda-capture.cpp
@@ -0,0 +1,78 @@
+// 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;
+  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; }; // no-warning
+  return lam();
+}
+
+int explicit_by_value_no_error() {
+  int d = 9;
+  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)();
+}


        
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to