Skip to content

[clang-tidy] fix false positive in cppcoreguidelines-missing-std-forward #77056

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

Conversation

jcsxky
Copy link
Contributor

@jcsxky jcsxky commented Jan 5, 2024

Parameter variable which is forwarded in lambda capture list or in body by reference is reasonable and current version of this check produces false positive on these cases. This patch try to fix the issue

@llvmbot
Copy link
Member

llvmbot commented Jan 5, 2024

@llvm/pr-subscribers-clang-tidy

Author: Qizhi Hu (jcsxky)

Changes

Parameter variable which is forwarded in lambda capture list or in body by reference is reasonable and current version of this check produces false positive on these cases. This patch try to fix the issue


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

2 Files Affected:

  • (modified) clang-tools-extra/clang-tidy/cppcoreguidelines/MissingStdForwardCheck.cpp (+66-7)
  • (modified) clang-tools-extra/test/clang-tidy/checkers/cppcoreguidelines/missing-std-forward.cpp (+23-3)
diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/MissingStdForwardCheck.cpp b/clang-tools-extra/clang-tidy/cppcoreguidelines/MissingStdForwardCheck.cpp
index 0b85ea19735eef..f0e021039f094d 100644
--- a/clang-tools-extra/clang-tidy/cppcoreguidelines/MissingStdForwardCheck.cpp
+++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/MissingStdForwardCheck.cpp
@@ -53,18 +53,76 @@ AST_MATCHER(ParmVarDecl, isTemplateTypeParameter) {
          FuncTemplate->getTemplateParameters()->getDepth();
 }
 
+AST_MATCHER_P(NamedDecl, hasSameNameAsBoundNode, std::string, BindingID) {
+  const auto &Name = Node.getNameAsString();
+
+  return Builder->removeBindings(
+      [this, Name](const ast_matchers::internal::BoundNodesMap &Nodes) {
+        const auto &BN = Nodes.getNode(this->BindingID);
+        if (const auto *ND = BN.get<NamedDecl>()) {
+          if (!isa<FieldDecl, CXXMethodDecl, VarDecl>(ND))
+            return true;
+          return ND->getName() != Name;
+        }
+        return true;
+      });
+}
+
+AST_MATCHER_P(LambdaCapture, hasCaptureKind, LambdaCaptureKind, Kind) {
+  return Node.getCaptureKind() == Kind;
+}
+
+AST_MATCHER_P(LambdaExpr, hasCaptureDefaultKind, LambdaCaptureDefault, Kind) {
+  return Node.getCaptureDefault() == Kind;
+}
+
+AST_MATCHER(LambdaExpr, hasCaptureToParm) {
+  auto RefToParm = capturesVar(varDecl(hasSameNameAsBoundNode("param")));
+  auto HasRefToParm = hasAnyCapture(RefToParm);
+
+  auto CaptureInRef =
+      allOf(hasCaptureDefaultKind(LambdaCaptureDefault::LCD_ByRef),
+            unless(HasRefToParm));
+  auto CaptureInCopy = allOf(
+      hasCaptureDefaultKind(LambdaCaptureDefault::LCD_ByCopy), HasRefToParm);
+  auto CaptureByRefExplicit = hasAnyCapture(
+      allOf(hasCaptureKind(LambdaCaptureKind::LCK_ByRef), RefToParm));
+
+  auto Captured =
+      lambdaExpr(anyOf(CaptureInRef, CaptureInCopy, CaptureByRefExplicit));
+  if (Captured.matches(Node, Finder, Builder))
+    return true;
+
+  return false;
+}
+
+AST_MATCHER(CallExpr, forCallableNode) {
+  auto InvokeInCaptureList = hasAnyCapture(capturesVar(
+      varDecl(hasInitializer(ignoringParenImpCasts(equalsNode(&Node))))));
+  auto InvokeInLambda = hasDeclContext(cxxRecordDecl(
+      isLambda(),
+      hasParent(lambdaExpr(forCallable(equalsBoundNode("func")),
+                           anyOf(InvokeInCaptureList, hasCaptureToParm())))));
+
+  if (forCallable(anyOf(equalsBoundNode("func"), InvokeInLambda))
+          .matches(Node, Finder, Builder))
+    return true;
+
+  return false;
+}
+
 } // namespace
 
 void MissingStdForwardCheck::registerMatchers(MatchFinder *Finder) {
   auto ToParam = hasAnyParameter(parmVarDecl(equalsBoundNode("param")));
 
-  auto ForwardCallMatcher = callExpr(
-      forCallable(equalsBoundNode("func")), argumentCountIs(1),
-      callee(unresolvedLookupExpr(hasAnyDeclaration(
-          namedDecl(hasUnderlyingDecl(hasName("::std::forward")))))),
-      hasArgument(0, declRefExpr(to(equalsBoundNode("param"))).bind("ref")),
-      unless(anyOf(hasAncestor(typeLoc()),
-                   hasAncestor(expr(hasUnevaluatedContext())))));
+  auto ForwardCallMatcher =
+      callExpr(forCallableNode(), argumentCountIs(1),
+               callee(unresolvedLookupExpr(hasAnyDeclaration(
+                   namedDecl(hasUnderlyingDecl(hasName("::std::forward")))))),
+               hasArgument(0, declRefExpr(to(equalsBoundNode("param")))),
+               unless(anyOf(hasAncestor(typeLoc()),
+                            hasAncestor(expr(hasUnevaluatedContext())))));
 
   Finder->addMatcher(
       parmVarDecl(parmVarDecl().bind("param"), isTemplateTypeParameter(),
@@ -76,6 +134,7 @@ void MissingStdForwardCheck::registerMatchers(MatchFinder *Finder) {
 }
 
 void MissingStdForwardCheck::check(const MatchFinder::MatchResult &Result) {
+
   const auto *Param = Result.Nodes.getNodeAs<ParmVarDecl>("param");
 
   if (!Param)
diff --git a/clang-tools-extra/test/clang-tidy/checkers/cppcoreguidelines/missing-std-forward.cpp b/clang-tools-extra/test/clang-tidy/checkers/cppcoreguidelines/missing-std-forward.cpp
index b9720db272e406..55d6be743c22ab 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/cppcoreguidelines/missing-std-forward.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/cppcoreguidelines/missing-std-forward.cpp
@@ -90,9 +90,9 @@ void lambda_value_capture(T&& t) {
 }
 
 template <class T>
-void lambda_value_reference(T&& t) {
-  // CHECK-MESSAGES: :[[@LINE-1]]:33: warning: forwarding reference parameter 't' is never forwarded inside the function body [cppcoreguidelines-missing-std-forward]
-  [&]() { T other = std::forward<T>(t); };
+void lambda_value_capture_copy(T&& t) {
+  // CHECK-MESSAGES: :[[@LINE-1]]:36: warning: forwarding reference parameter 't' is never forwarded inside the function body [cppcoreguidelines-missing-std-forward]
+  [&,t]() { T other = std::forward<T>(t); };
 }
 
 } // namespace positive_cases
@@ -147,4 +147,24 @@ class AClass {
   T data;
 };
 
+template <class T>
+void lambda_value_reference(T&& t) {
+  [&]() { T other = std::forward<T>(t); };
+}
+
+template<typename T>
+void lambda_value_reference_capture_list_ref_1(T&& t) {
+    [=, &t] { T other = std::forward<T>(t); };
+}
+
+template<typename T>
+void lambda_value_reference_capture_list_ref_2(T&& t) {
+    [&t] { T other = std::forward<T>(t); };
+}
+
+template<typename T>
+void lambda_value_reference_capture_list(T&& t) {
+    [t = std::forward<T>(t)] { t(); };
+}
+
 } // namespace negative_cases

@jcsxky jcsxky force-pushed the fix_false_positive_cppcoreguidelines-missing-std-forward branch from 799efba to 043fc62 Compare January 5, 2024 08:23
Copy link
Member

@PiotrZSL PiotrZSL left a comment

Choose a reason for hiding this comment

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

Missing release notes & some mention about this in documentation.

@jcsxky jcsxky force-pushed the fix_false_positive_cppcoreguidelines-missing-std-forward branch 2 times, most recently from 4b8438f to 0f1e72b Compare January 6, 2024 03:10
Copy link
Member

@PiotrZSL PiotrZSL left a comment

Choose a reason for hiding this comment

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

LGTM.

@jcsxky jcsxky force-pushed the fix_false_positive_cppcoreguidelines-missing-std-forward branch from 0f1e72b to 880554d Compare January 6, 2024 09:55
@jcsxky jcsxky merged commit d084829 into llvm:main Jan 6, 2024
justinfargnoli pushed a commit to justinfargnoli/llvm-project that referenced this pull request Jan 28, 2024
…ard (llvm#77056)

Parameter variable which is forwarded in lambda capture list or in body
by reference is reasonable and current version of this check produces
false positive on these cases. This patch try to fix the
[issue](llvm#68105)

Co-authored-by: huqizhi <836744285@qq.com>
@HerrCai0907
Copy link
Contributor

I agree to fix false positive reported in #68105. But for capturing in lambda be reference and do forward in lambda, It is just like create a record by ref instead of using forward and do forward in some member function. I don't think it is an expected false positive.

template<typename T>
void lambda_value_reference_capture_list_ref_2(T&& t) {
    [&t] { T other = std::forward<T>(t); };
}

@jcsxky @PiotrZSL Could you think again about this PR?

HerrCai0907 added a commit to HerrCai0907/llvm-project that referenced this pull request Mar 4, 2024
…in lambda body

Fixes: llvm#83845
Capturing variable in lambda by reference and doing forward in lambda body are like constructing a struct by reference and using std::forward in some member function. It is dangerous if the lifetime of lambda expression is longer then current function. And it goes against what this check is intended to check.
This PR wants to revert this behavior introduced in llvm#77056 partially.
@jcsxky
Copy link
Contributor Author

jcsxky commented Mar 5, 2024

I agree to fix false positive reported in #68105. But for capturing in lambda be reference and do forward in lambda, It is just like create a record by ref instead of using forward and do forward in some member function. I don't think it is an expected false positive.

template<typename T>
void lambda_value_reference_capture_list_ref_2(T&& t) {
    [&t] { T other = std::forward<T>(t); };
}

@jcsxky @PiotrZSL Could you think again about this PR?

Not really. Capture by reference makes t in parameter same with it in lambda body. Maybe you can check #68105 (comment) .

@HerrCai0907
Copy link
Contributor

HerrCai0907 commented Mar 5, 2024

Capture by reference makes t in parameter same with it in lambda body

That is definitely wrong.
It will get stack-use-after-scope error if address sanitize is enabled.

#include <iostream>
#include <utility>

template <class T>
auto f(T &&t) {
  return [&]() {
    return std::forward<T>(t);
  };
}

struct S {
  int v;
};

int main() {
  auto fn = f(S{.v = 1000});

  std::cout << fn().v << "\n";
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants