Skip to content

[webkit.UncountedLambdaCapturesChecker] Recognize nested protectedThis pattern #126443

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
Feb 15, 2025

Conversation

rniwa
Copy link
Contributor

@rniwa rniwa commented Feb 9, 2025

In WebKit, it's pretty common to capture "this" and "protectedThis" where "protectedThis" is a guardian variable of type Ref or RefPtr for "this". Furthermore, it's common for this "protectedThis" variable from being passed to an inner lambda by std::move. Recognize this pattern so that we don't emit warnings for nested inner lambdas.

To recognize this pattern, we introduce a new DenseSet, ProtectedThisDecls, which contains every "protectedThis" we've recognized to our subclass of DynamicRecursiveASTVisitor. This set is now populated in "hasProtectedThis" and "declProtectsThis" uses this DenseSet to determine a given value declaration constitutes a "protectedThis" pattern or not.

Because hasProtectedThis and declProtectsThis had to be moved from the checker class to the visitor class, it's now a responsibility of each caller of visitLambdaExpr to check whether a given lambda captures "this" without a "protectedThis" or not.

Finally, this PR improves the code to recognize "protectedThis" pattern by allowing more nested CXXBindTemporaryExpr, CXXOperatorCallExpr, and UnaryOperator expressions.

…s pattern

In WebKit, it's pretty common to capture "this" and "protectedThis" where "protectedThis" is
a guardian variable of type Ref or RefPtr for "this". Furthermore, it's common for this
"protectedThis" variable from being passed to an inner lambda by std::move. Recognize this
pattern so that we don't emit warnings for nested inner lambdas.

To recognize this pattern, we introduce a new DenseSet, ProtectedThisDecls, which contains
every "protectedThis" we've recognized to our subclass of DynamicRecursiveASTVisitor. This
set is now populated in "hasProtectedThis" and "declProtectsThis" uses this DenseSet to
determine a given value declaration constitutes a "protectedThis" pattern or not.

Because hasProtectedThis and declProtectsThis had to be moved from the checker class to
the visitor class, it's now a responsibility of each caller of visitLambdaExpr to check
whether a given lambda captures "this" without a "protectedThis" or not.

Finally, this PR improves the code to recognize "protectedThis" pattern by allowing more
nested CXXBindTemporaryExpr, CXXOperatorCallExpr, and UnaryOperator expressions.
@rniwa rniwa requested review from t-rasmud and haoNoQ February 9, 2025 22:02
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:static analyzer labels Feb 9, 2025
@llvmbot
Copy link
Member

llvmbot commented Feb 9, 2025

@llvm/pr-subscribers-clang-static-analyzer-1

Author: Ryosuke Niwa (rniwa)

Changes

In WebKit, it's pretty common to capture "this" and "protectedThis" where "protectedThis" is a guardian variable of type Ref or RefPtr for "this". Furthermore, it's common for this "protectedThis" variable from being passed to an inner lambda by std::move. Recognize this pattern so that we don't emit warnings for nested inner lambdas.

To recognize this pattern, we introduce a new DenseSet, ProtectedThisDecls, which contains every "protectedThis" we've recognized to our subclass of DynamicRecursiveASTVisitor. This set is now populated in "hasProtectedThis" and "declProtectsThis" uses this DenseSet to determine a given value declaration constitutes a "protectedThis" pattern or not.

Because hasProtectedThis and declProtectsThis had to be moved from the checker class to the visitor class, it's now a responsibility of each caller of visitLambdaExpr to check whether a given lambda captures "this" without a "protectedThis" or not.

Finally, this PR improves the code to recognize "protectedThis" pattern by allowing more nested CXXBindTemporaryExpr, CXXOperatorCallExpr, and UnaryOperator expressions.


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

2 Files Affected:

  • (modified) clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedLambdaCapturesChecker.cpp (+71-47)
  • (modified) clang/test/Analysis/Checkers/WebKit/uncounted-lambda-captures.cpp (+24)
diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedLambdaCapturesChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedLambdaCapturesChecker.cpp
index a56f48c83c660a9..c0a9e38b6aab41b 100644
--- a/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedLambdaCapturesChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedLambdaCapturesChecker.cpp
@@ -41,6 +41,7 @@ class UncountedLambdaCapturesChecker
       const UncountedLambdaCapturesChecker *Checker;
       llvm::DenseSet<const DeclRefExpr *> DeclRefExprsToIgnore;
       llvm::DenseSet<const LambdaExpr *> LambdasToIgnore;
+      llvm::DenseSet<const ValueDecl *> ProtectedThisDecls;
       QualType ClsType;
 
       explicit LocalVisitor(const UncountedLambdaCapturesChecker *Checker)
@@ -65,7 +66,7 @@ class UncountedLambdaCapturesChecker
       bool VisitLambdaExpr(LambdaExpr *L) override {
         if (LambdasToIgnore.contains(L))
           return true;
-        Checker->visitLambdaExpr(L, shouldCheckThis());
+        Checker->visitLambdaExpr(L, shouldCheckThis() && !hasProtectedThis(L));
         return true;
       }
 
@@ -93,7 +94,7 @@ class UncountedLambdaCapturesChecker
         if (!L)
           return true;
         LambdasToIgnore.insert(L);
-        Checker->visitLambdaExpr(L, shouldCheckThis());
+        Checker->visitLambdaExpr(L, shouldCheckThis() && !hasProtectedThis(L));
         return true;
       }
 
@@ -118,7 +119,8 @@ class UncountedLambdaCapturesChecker
             if (auto *L = findLambdaInArg(Arg)) {
               LambdasToIgnore.insert(L);
               if (!Param->hasAttr<NoEscapeAttr>() && !TreatAllArgsAsNoEscape)
-                Checker->visitLambdaExpr(L, shouldCheckThis());
+                Checker->visitLambdaExpr(L, shouldCheckThis() &&
+                                            !hasProtectedThis(L));
             }
             ++ArgIndex;
           }
@@ -145,6 +147,11 @@ class UncountedLambdaCapturesChecker
           return nullptr;
         if (auto *Lambda = dyn_cast<LambdaExpr>(CtorArg))
           return Lambda;
+        if (auto *TempExpr = dyn_cast<CXXBindTemporaryExpr>(CtorArg)) {
+          E = TempExpr->getSubExpr()->IgnoreParenCasts();
+          if (auto *Lambda = dyn_cast<LambdaExpr>(E))
+            return Lambda;
+        }
         auto *DRE = dyn_cast<DeclRefExpr>(CtorArg);
         if (!DRE)
           return nullptr;
@@ -189,9 +196,68 @@ class UncountedLambdaCapturesChecker
           return;
         DeclRefExprsToIgnore.insert(ArgRef);
         LambdasToIgnore.insert(L);
-        Checker->visitLambdaExpr(L, shouldCheckThis(),
+        Checker->visitLambdaExpr(L, shouldCheckThis() && !hasProtectedThis(L),
                                  /* ignoreParamVarDecl */ true);
       }
+
+      bool hasProtectedThis(LambdaExpr *L) {
+        for (const LambdaCapture &OtherCapture : L->captures()) {
+          if (!OtherCapture.capturesVariable())
+            continue;
+          if (auto *ValueDecl = OtherCapture.getCapturedVar()) {
+            if (declProtectsThis(ValueDecl)) {
+              ProtectedThisDecls.insert(ValueDecl);
+              return true;
+            }
+          }
+        }
+        return false;
+      }
+
+      bool declProtectsThis(const ValueDecl *ValueDecl) const {
+        auto *VD = dyn_cast<VarDecl>(ValueDecl);
+        if (!VD)
+          return false;
+        auto *Init = VD->getInit()->IgnoreParenCasts();
+        if (!Init)
+          return false;
+        const Expr *Arg = Init;
+        do {
+          if (auto *BTE = dyn_cast<CXXBindTemporaryExpr>(Arg))
+            Arg = BTE->getSubExpr()->IgnoreParenCasts();
+          if (auto *CE = dyn_cast_or_null<CXXConstructExpr>(Arg)) {
+            auto *Ctor = CE->getConstructor();
+            if (!Ctor)
+              return false;
+            auto clsName = safeGetName(Ctor->getParent());
+            if (!isRefType(clsName) || !CE->getNumArgs())
+              return false;
+            Arg = CE->getArg(0)->IgnoreParenCasts();
+            continue;
+          }
+          if (auto *OpCE = dyn_cast<CXXOperatorCallExpr>(Arg)) {
+            auto OpCode = OpCE->getOperator();
+            if (OpCode == OO_Star || OpCode == OO_Amp) {
+              auto *Callee = OpCE->getDirectCallee();
+              auto clsName = safeGetName(Callee->getParent());
+              if (!isRefType(clsName) || !OpCE->getNumArgs())
+                return false;
+              Arg = OpCE->getArg(0)->IgnoreParenCasts();
+              continue;
+            }
+          }
+          if (auto *UO = dyn_cast<UnaryOperator>(Arg)) {
+            auto OpCode = UO->getOpcode();
+            if (OpCode == UO_Deref || OpCode == UO_AddrOf)
+              Arg = UO->getSubExpr()->IgnoreParenCasts();
+            continue;
+          }
+          break;
+        } while (Arg);
+        if (auto *DRE = dyn_cast<DeclRefExpr>(Arg))
+          return ProtectedThisDecls.contains(DRE->getDecl());
+        return isa<CXXThisExpr>(Arg);
+      }
     };
 
     LocalVisitor visitor(this);
@@ -214,53 +280,11 @@ class UncountedLambdaCapturesChecker
       } else if (C.capturesThis() && shouldCheckThis) {
         if (ignoreParamVarDecl) // this is always a parameter to this function.
           continue;
-        bool hasProtectThis = false;
-        for (const LambdaCapture &OtherCapture : L->captures()) {
-          if (!OtherCapture.capturesVariable())
-            continue;
-          if (auto *ValueDecl = OtherCapture.getCapturedVar()) {
-            if (protectThis(ValueDecl)) {
-              hasProtectThis = true;
-              break;
-            }
-          }
-        }
-        if (!hasProtectThis)
-          reportBugOnThisPtr(C);
+        reportBugOnThisPtr(C);
       }
     }
   }
 
-  bool protectThis(const ValueDecl *ValueDecl) const {
-    auto *VD = dyn_cast<VarDecl>(ValueDecl);
-    if (!VD)
-      return false;
-    auto *Init = VD->getInit()->IgnoreParenCasts();
-    if (!Init)
-      return false;
-    auto *BTE = dyn_cast<CXXBindTemporaryExpr>(Init);
-    if (!BTE)
-      return false;
-    auto *CE = dyn_cast_or_null<CXXConstructExpr>(BTE->getSubExpr());
-    if (!CE)
-      return false;
-    auto *Ctor = CE->getConstructor();
-    if (!Ctor)
-      return false;
-    auto clsName = safeGetName(Ctor->getParent());
-    if (!isRefType(clsName) || !CE->getNumArgs())
-      return false;
-    auto *Arg = CE->getArg(0)->IgnoreParenCasts();
-    while (auto *UO = dyn_cast<UnaryOperator>(Arg)) {
-      auto OpCode = UO->getOpcode();
-      if (OpCode == UO_Deref || OpCode == UO_AddrOf)
-        Arg = UO->getSubExpr();
-      else
-        break;
-    }
-    return isa<CXXThisExpr>(Arg);
-  }
-
   void reportBug(const LambdaCapture &Capture, ValueDecl *CapturedVar,
                  const QualType T) const {
     assert(CapturedVar);
diff --git a/clang/test/Analysis/Checkers/WebKit/uncounted-lambda-captures.cpp b/clang/test/Analysis/Checkers/WebKit/uncounted-lambda-captures.cpp
index 4f4a96028225303..4895879c4a385db 100644
--- a/clang/test/Analysis/Checkers/WebKit/uncounted-lambda-captures.cpp
+++ b/clang/test/Analysis/Checkers/WebKit/uncounted-lambda-captures.cpp
@@ -89,6 +89,7 @@ template <typename Callback> void call(Callback callback) {
   someFunction();
   callback();
 }
+void callAsync(const WTF::Function<void()>&);
 
 void raw_ptr() {
   RefCountable* ref_countable = make_obj();
@@ -299,6 +300,29 @@ struct RefCountableWithLambdaCapturingThis {
       return obj->next();
     });
   }
+
+  void callAsyncNoescape([[clang::noescape]] WTF::Function<bool(RefCountable&)>&&);
+  void method_temp_lambda(RefCountable* obj) {
+    callAsyncNoescape([this, otherObj = RefPtr { obj }](auto& obj) {
+      return otherObj == &obj;
+    });
+  }
+
+  void method_nested_lambda() {
+    callAsync([this, protectedThis = Ref { *this }] {
+      callAsync([this, protectedThis = static_cast<const Ref<RefCountableWithLambdaCapturingThis>&&>(protectedThis)] {
+        nonTrivial();
+      });
+    });
+  }
+
+  void method_nested_lambda2() {
+    callAsync([this, protectedThis = RefPtr { this }] {
+      callAsync([this, protectedThis = static_cast<const Ref<RefCountableWithLambdaCapturingThis>&&>(*protectedThis)] {
+        nonTrivial();
+      });
+    });
+  }
 };
 
 struct NonRefCountableWithLambdaCapturingThis {

@llvmbot
Copy link
Member

llvmbot commented Feb 9, 2025

@llvm/pr-subscribers-clang

Author: Ryosuke Niwa (rniwa)

Changes

In WebKit, it's pretty common to capture "this" and "protectedThis" where "protectedThis" is a guardian variable of type Ref or RefPtr for "this". Furthermore, it's common for this "protectedThis" variable from being passed to an inner lambda by std::move. Recognize this pattern so that we don't emit warnings for nested inner lambdas.

To recognize this pattern, we introduce a new DenseSet, ProtectedThisDecls, which contains every "protectedThis" we've recognized to our subclass of DynamicRecursiveASTVisitor. This set is now populated in "hasProtectedThis" and "declProtectsThis" uses this DenseSet to determine a given value declaration constitutes a "protectedThis" pattern or not.

Because hasProtectedThis and declProtectsThis had to be moved from the checker class to the visitor class, it's now a responsibility of each caller of visitLambdaExpr to check whether a given lambda captures "this" without a "protectedThis" or not.

Finally, this PR improves the code to recognize "protectedThis" pattern by allowing more nested CXXBindTemporaryExpr, CXXOperatorCallExpr, and UnaryOperator expressions.


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

2 Files Affected:

  • (modified) clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedLambdaCapturesChecker.cpp (+71-47)
  • (modified) clang/test/Analysis/Checkers/WebKit/uncounted-lambda-captures.cpp (+24)
diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedLambdaCapturesChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedLambdaCapturesChecker.cpp
index a56f48c83c660a9..c0a9e38b6aab41b 100644
--- a/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedLambdaCapturesChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedLambdaCapturesChecker.cpp
@@ -41,6 +41,7 @@ class UncountedLambdaCapturesChecker
       const UncountedLambdaCapturesChecker *Checker;
       llvm::DenseSet<const DeclRefExpr *> DeclRefExprsToIgnore;
       llvm::DenseSet<const LambdaExpr *> LambdasToIgnore;
+      llvm::DenseSet<const ValueDecl *> ProtectedThisDecls;
       QualType ClsType;
 
       explicit LocalVisitor(const UncountedLambdaCapturesChecker *Checker)
@@ -65,7 +66,7 @@ class UncountedLambdaCapturesChecker
       bool VisitLambdaExpr(LambdaExpr *L) override {
         if (LambdasToIgnore.contains(L))
           return true;
-        Checker->visitLambdaExpr(L, shouldCheckThis());
+        Checker->visitLambdaExpr(L, shouldCheckThis() && !hasProtectedThis(L));
         return true;
       }
 
@@ -93,7 +94,7 @@ class UncountedLambdaCapturesChecker
         if (!L)
           return true;
         LambdasToIgnore.insert(L);
-        Checker->visitLambdaExpr(L, shouldCheckThis());
+        Checker->visitLambdaExpr(L, shouldCheckThis() && !hasProtectedThis(L));
         return true;
       }
 
@@ -118,7 +119,8 @@ class UncountedLambdaCapturesChecker
             if (auto *L = findLambdaInArg(Arg)) {
               LambdasToIgnore.insert(L);
               if (!Param->hasAttr<NoEscapeAttr>() && !TreatAllArgsAsNoEscape)
-                Checker->visitLambdaExpr(L, shouldCheckThis());
+                Checker->visitLambdaExpr(L, shouldCheckThis() &&
+                                            !hasProtectedThis(L));
             }
             ++ArgIndex;
           }
@@ -145,6 +147,11 @@ class UncountedLambdaCapturesChecker
           return nullptr;
         if (auto *Lambda = dyn_cast<LambdaExpr>(CtorArg))
           return Lambda;
+        if (auto *TempExpr = dyn_cast<CXXBindTemporaryExpr>(CtorArg)) {
+          E = TempExpr->getSubExpr()->IgnoreParenCasts();
+          if (auto *Lambda = dyn_cast<LambdaExpr>(E))
+            return Lambda;
+        }
         auto *DRE = dyn_cast<DeclRefExpr>(CtorArg);
         if (!DRE)
           return nullptr;
@@ -189,9 +196,68 @@ class UncountedLambdaCapturesChecker
           return;
         DeclRefExprsToIgnore.insert(ArgRef);
         LambdasToIgnore.insert(L);
-        Checker->visitLambdaExpr(L, shouldCheckThis(),
+        Checker->visitLambdaExpr(L, shouldCheckThis() && !hasProtectedThis(L),
                                  /* ignoreParamVarDecl */ true);
       }
+
+      bool hasProtectedThis(LambdaExpr *L) {
+        for (const LambdaCapture &OtherCapture : L->captures()) {
+          if (!OtherCapture.capturesVariable())
+            continue;
+          if (auto *ValueDecl = OtherCapture.getCapturedVar()) {
+            if (declProtectsThis(ValueDecl)) {
+              ProtectedThisDecls.insert(ValueDecl);
+              return true;
+            }
+          }
+        }
+        return false;
+      }
+
+      bool declProtectsThis(const ValueDecl *ValueDecl) const {
+        auto *VD = dyn_cast<VarDecl>(ValueDecl);
+        if (!VD)
+          return false;
+        auto *Init = VD->getInit()->IgnoreParenCasts();
+        if (!Init)
+          return false;
+        const Expr *Arg = Init;
+        do {
+          if (auto *BTE = dyn_cast<CXXBindTemporaryExpr>(Arg))
+            Arg = BTE->getSubExpr()->IgnoreParenCasts();
+          if (auto *CE = dyn_cast_or_null<CXXConstructExpr>(Arg)) {
+            auto *Ctor = CE->getConstructor();
+            if (!Ctor)
+              return false;
+            auto clsName = safeGetName(Ctor->getParent());
+            if (!isRefType(clsName) || !CE->getNumArgs())
+              return false;
+            Arg = CE->getArg(0)->IgnoreParenCasts();
+            continue;
+          }
+          if (auto *OpCE = dyn_cast<CXXOperatorCallExpr>(Arg)) {
+            auto OpCode = OpCE->getOperator();
+            if (OpCode == OO_Star || OpCode == OO_Amp) {
+              auto *Callee = OpCE->getDirectCallee();
+              auto clsName = safeGetName(Callee->getParent());
+              if (!isRefType(clsName) || !OpCE->getNumArgs())
+                return false;
+              Arg = OpCE->getArg(0)->IgnoreParenCasts();
+              continue;
+            }
+          }
+          if (auto *UO = dyn_cast<UnaryOperator>(Arg)) {
+            auto OpCode = UO->getOpcode();
+            if (OpCode == UO_Deref || OpCode == UO_AddrOf)
+              Arg = UO->getSubExpr()->IgnoreParenCasts();
+            continue;
+          }
+          break;
+        } while (Arg);
+        if (auto *DRE = dyn_cast<DeclRefExpr>(Arg))
+          return ProtectedThisDecls.contains(DRE->getDecl());
+        return isa<CXXThisExpr>(Arg);
+      }
     };
 
     LocalVisitor visitor(this);
@@ -214,53 +280,11 @@ class UncountedLambdaCapturesChecker
       } else if (C.capturesThis() && shouldCheckThis) {
         if (ignoreParamVarDecl) // this is always a parameter to this function.
           continue;
-        bool hasProtectThis = false;
-        for (const LambdaCapture &OtherCapture : L->captures()) {
-          if (!OtherCapture.capturesVariable())
-            continue;
-          if (auto *ValueDecl = OtherCapture.getCapturedVar()) {
-            if (protectThis(ValueDecl)) {
-              hasProtectThis = true;
-              break;
-            }
-          }
-        }
-        if (!hasProtectThis)
-          reportBugOnThisPtr(C);
+        reportBugOnThisPtr(C);
       }
     }
   }
 
-  bool protectThis(const ValueDecl *ValueDecl) const {
-    auto *VD = dyn_cast<VarDecl>(ValueDecl);
-    if (!VD)
-      return false;
-    auto *Init = VD->getInit()->IgnoreParenCasts();
-    if (!Init)
-      return false;
-    auto *BTE = dyn_cast<CXXBindTemporaryExpr>(Init);
-    if (!BTE)
-      return false;
-    auto *CE = dyn_cast_or_null<CXXConstructExpr>(BTE->getSubExpr());
-    if (!CE)
-      return false;
-    auto *Ctor = CE->getConstructor();
-    if (!Ctor)
-      return false;
-    auto clsName = safeGetName(Ctor->getParent());
-    if (!isRefType(clsName) || !CE->getNumArgs())
-      return false;
-    auto *Arg = CE->getArg(0)->IgnoreParenCasts();
-    while (auto *UO = dyn_cast<UnaryOperator>(Arg)) {
-      auto OpCode = UO->getOpcode();
-      if (OpCode == UO_Deref || OpCode == UO_AddrOf)
-        Arg = UO->getSubExpr();
-      else
-        break;
-    }
-    return isa<CXXThisExpr>(Arg);
-  }
-
   void reportBug(const LambdaCapture &Capture, ValueDecl *CapturedVar,
                  const QualType T) const {
     assert(CapturedVar);
diff --git a/clang/test/Analysis/Checkers/WebKit/uncounted-lambda-captures.cpp b/clang/test/Analysis/Checkers/WebKit/uncounted-lambda-captures.cpp
index 4f4a96028225303..4895879c4a385db 100644
--- a/clang/test/Analysis/Checkers/WebKit/uncounted-lambda-captures.cpp
+++ b/clang/test/Analysis/Checkers/WebKit/uncounted-lambda-captures.cpp
@@ -89,6 +89,7 @@ template <typename Callback> void call(Callback callback) {
   someFunction();
   callback();
 }
+void callAsync(const WTF::Function<void()>&);
 
 void raw_ptr() {
   RefCountable* ref_countable = make_obj();
@@ -299,6 +300,29 @@ struct RefCountableWithLambdaCapturingThis {
       return obj->next();
     });
   }
+
+  void callAsyncNoescape([[clang::noescape]] WTF::Function<bool(RefCountable&)>&&);
+  void method_temp_lambda(RefCountable* obj) {
+    callAsyncNoescape([this, otherObj = RefPtr { obj }](auto& obj) {
+      return otherObj == &obj;
+    });
+  }
+
+  void method_nested_lambda() {
+    callAsync([this, protectedThis = Ref { *this }] {
+      callAsync([this, protectedThis = static_cast<const Ref<RefCountableWithLambdaCapturingThis>&&>(protectedThis)] {
+        nonTrivial();
+      });
+    });
+  }
+
+  void method_nested_lambda2() {
+    callAsync([this, protectedThis = RefPtr { this }] {
+      callAsync([this, protectedThis = static_cast<const Ref<RefCountableWithLambdaCapturingThis>&&>(*protectedThis)] {
+        nonTrivial();
+      });
+    });
+  }
 };
 
 struct NonRefCountableWithLambdaCapturingThis {

Copy link

github-actions bot commented Feb 9, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

nonTrivial();
});
});
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need a test for a lambda capture with a this and an unprotected this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, we have such a test case. See method_captures_this_unsafe around line 217 :)

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 guess we can add a test case for nested lambdas though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a test case.

Copy link
Contributor

@t-rasmud t-rasmud left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left a comment about a potentially missing test. Otherwise LGTM.

@rniwa
Copy link
Contributor Author

rniwa commented Feb 15, 2025

Thanks for the review!

@rniwa rniwa merged commit 77041da into llvm:main Feb 15, 2025
8 checks passed
@rniwa rniwa deleted the recognize-nested-protected-this branch February 15, 2025 00:33
@llvm-ci
Copy link
Collaborator

llvm-ci commented Feb 15, 2025

LLVM Buildbot has detected a new failure on builder clangd-ubuntu-tsan running on clangd-ubuntu-clang while building clang at step 6 "test-build-clangd-clangd-index-server-clangd-in...".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/134/builds/13474

Here is the relevant piece of the build log for the reference
Step 6 (test-build-clangd-clangd-index-server-clangd-in...) failure: test (failure)
******************** TEST 'Clangd :: target_info.test' FAILED ********************
Exit Code: 66

Command Output (stderr):
--
RUN: at line 5: rm -rf /vol/worker/clangd-ubuntu-clang/clangd-ubuntu-tsan/build/tools/clang/tools/extra/clangd/test/Output/target_info.test.tmp.dir && mkdir -p /vol/worker/clangd-ubuntu-clang/clangd-ubuntu-tsan/build/tools/clang/tools/extra/clangd/test/Output/target_info.test.tmp.dir
+ rm -rf /vol/worker/clangd-ubuntu-clang/clangd-ubuntu-tsan/build/tools/clang/tools/extra/clangd/test/Output/target_info.test.tmp.dir
+ mkdir -p /vol/worker/clangd-ubuntu-clang/clangd-ubuntu-tsan/build/tools/clang/tools/extra/clangd/test/Output/target_info.test.tmp.dir
RUN: at line 7: echo '[{"directory": "/vol/worker/clangd-ubuntu-clang/clangd-ubuntu-tsan/build/tools/clang/tools/extra/clangd/test/Output/target_info.test.tmp.dir", "command": "/vol/worker/clangd-ubuntu-clang/clangd-ubuntu-tsan/build/tools/clang/tools/extra/clangd/test/Output/target_info.test.tmp.dir/armv7-clang -x c++ the-file.cpp -v", "file": "the-file.cpp"}]' > /vol/worker/clangd-ubuntu-clang/clangd-ubuntu-tsan/build/tools/clang/tools/extra/clangd/test/Output/target_info.test.tmp.dir/compile_commands.json
+ echo '[{"directory": "/vol/worker/clangd-ubuntu-clang/clangd-ubuntu-tsan/build/tools/clang/tools/extra/clangd/test/Output/target_info.test.tmp.dir", "command": "/vol/worker/clangd-ubuntu-clang/clangd-ubuntu-tsan/build/tools/clang/tools/extra/clangd/test/Output/target_info.test.tmp.dir/armv7-clang -x c++ the-file.cpp -v", "file": "the-file.cpp"}]'
RUN: at line 9: sed -e "s|INPUT_DIR|/vol/worker/clangd-ubuntu-clang/clangd-ubuntu-tsan/build/tools/clang/tools/extra/clangd/test/Output/target_info.test.tmp.dir|g" /vol/worker/clangd-ubuntu-clang/clangd-ubuntu-tsan/llvm-project/clang-tools-extra/clangd/test/target_info.test > /vol/worker/clangd-ubuntu-clang/clangd-ubuntu-tsan/build/tools/clang/tools/extra/clangd/test/Output/target_info.test.tmp.test.1
+ sed -e 's|INPUT_DIR|/vol/worker/clangd-ubuntu-clang/clangd-ubuntu-tsan/build/tools/clang/tools/extra/clangd/test/Output/target_info.test.tmp.dir|g' /vol/worker/clangd-ubuntu-clang/clangd-ubuntu-tsan/llvm-project/clang-tools-extra/clangd/test/target_info.test
RUN: at line 12: sed -E -e 's|"file://([A-Z]):/|"file:///\1:/|g' /vol/worker/clangd-ubuntu-clang/clangd-ubuntu-tsan/build/tools/clang/tools/extra/clangd/test/Output/target_info.test.tmp.test.1 > /vol/worker/clangd-ubuntu-clang/clangd-ubuntu-tsan/build/tools/clang/tools/extra/clangd/test/Output/target_info.test.tmp.test
+ sed -E -e 's|"file://([A-Z]):/|"file:///\1:/|g' /vol/worker/clangd-ubuntu-clang/clangd-ubuntu-tsan/build/tools/clang/tools/extra/clangd/test/Output/target_info.test.tmp.test.1
RUN: at line 14: clangd -lit-test < /vol/worker/clangd-ubuntu-clang/clangd-ubuntu-tsan/build/tools/clang/tools/extra/clangd/test/Output/target_info.test.tmp.test 2>&1 | /vol/worker/clangd-ubuntu-clang/clangd-ubuntu-tsan/build/bin/FileCheck -strict-whitespace /vol/worker/clangd-ubuntu-clang/clangd-ubuntu-tsan/build/tools/clang/tools/extra/clangd/test/Output/target_info.test.tmp.test
+ clangd -lit-test
+ /vol/worker/clangd-ubuntu-clang/clangd-ubuntu-tsan/build/bin/FileCheck -strict-whitespace /vol/worker/clangd-ubuntu-clang/clangd-ubuntu-tsan/build/tools/clang/tools/extra/clangd/test/Output/target_info.test.tmp.test

--

********************


rniwa added a commit to rniwa/llvm-project that referenced this pull request Feb 15, 2025
…s pattern (llvm#126443)

In WebKit, it's pretty common to capture "this" and "protectedThis"
where "protectedThis" is a guardian variable of type Ref or RefPtr for
"this". Furthermore, it's common for this "protectedThis" variable from
being passed to an inner lambda by std::move. Recognize this pattern so
that we don't emit warnings for nested inner lambdas.

To recognize this pattern, we introduce a new DenseSet,
ProtectedThisDecls, which contains every "protectedThis" we've
recognized to our subclass of DynamicRecursiveASTVisitor. This set is
now populated in "hasProtectedThis" and "declProtectsThis" uses this
DenseSet to determine a given value declaration constitutes a
"protectedThis" pattern or not.

Because hasProtectedThis and declProtectsThis had to be moved from the
checker class to the visitor class, it's now a responsibility of each
caller of visitLambdaExpr to check whether a given lambda captures
"this" without a "protectedThis" or not.

Finally, this PR improves the code to recognize "protectedThis" pattern
by allowing more nested CXXBindTemporaryExpr, CXXOperatorCallExpr, and
UnaryOperator expressions.
sivan-shani pushed a commit to sivan-shani/llvm-project that referenced this pull request Feb 24, 2025
…s pattern (llvm#126443)

In WebKit, it's pretty common to capture "this" and "protectedThis"
where "protectedThis" is a guardian variable of type Ref or RefPtr for
"this". Furthermore, it's common for this "protectedThis" variable from
being passed to an inner lambda by std::move. Recognize this pattern so
that we don't emit warnings for nested inner lambdas.

To recognize this pattern, we introduce a new DenseSet,
ProtectedThisDecls, which contains every "protectedThis" we've
recognized to our subclass of DynamicRecursiveASTVisitor. This set is
now populated in "hasProtectedThis" and "declProtectsThis" uses this
DenseSet to determine a given value declaration constitutes a
"protectedThis" pattern or not.

Because hasProtectedThis and declProtectsThis had to be moved from the
checker class to the visitor class, it's now a responsibility of each
caller of visitLambdaExpr to check whether a given lambda captures
"this" without a "protectedThis" or not.

Finally, this PR improves the code to recognize "protectedThis" pattern
by allowing more nested CXXBindTemporaryExpr, CXXOperatorCallExpr, and
UnaryOperator expressions.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:static analyzer clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants