ccotter created this revision.
Herald added subscribers: PiotrZSL, carlosgalvezp, kbarton, xazax.hun, nemanjai.
Herald added a reviewer: njames93.
Herald added a project: All.
ccotter requested review of this revision.
Herald added a project: clang-tools-extra.
Herald added a subscriber: cfe-commits.

Implement the std::forward specific enforcement of ES.56.


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D146888

Files:
  clang-tools-extra/clang-tidy/cppcoreguidelines/CMakeLists.txt
  clang-tools-extra/clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp
  
clang-tools-extra/clang-tidy/cppcoreguidelines/ForwardNonForwardingParameterCheck.cpp
  
clang-tools-extra/clang-tidy/cppcoreguidelines/ForwardNonForwardingParameterCheck.h
  clang-tools-extra/docs/ReleaseNotes.rst
  
clang-tools-extra/docs/clang-tidy/checks/cppcoreguidelines/forward-non-forwarding-parameter.rst
  clang-tools-extra/docs/clang-tidy/checks/list.rst
  
clang-tools-extra/test/clang-tidy/checkers/cppcoreguidelines/forward-non-forwarding-parameter.cpp

Index: clang-tools-extra/test/clang-tidy/checkers/cppcoreguidelines/forward-non-forwarding-parameter.cpp
===================================================================
--- /dev/null
+++ clang-tools-extra/test/clang-tidy/checkers/cppcoreguidelines/forward-non-forwarding-parameter.cpp
@@ -0,0 +1,163 @@
+// RUN: %check_clang_tidy %s cppcoreguidelines-forward-non-forwarding-parameter %t
+
+// NOLINTBEGIN
+namespace std {
+
+template <typename T> struct remove_reference      { using type = T; };
+template <typename T> struct remove_reference<T&>  { using type = T; };
+template <typename T> struct remove_reference<T&&> { using type = T; };
+
+template <typename T> using remove_reference_t = typename remove_reference<T>::type;
+
+template <typename T> constexpr T &&forward(remove_reference_t<T> &t) noexcept;
+template <typename T> constexpr T &&forward(remove_reference_t<T> &&t) noexcept;
+
+} // namespace std
+// NOLINTEND
+
+struct Obj {
+  Obj();
+  Obj(const Obj&);
+  Obj(Obj&&) noexcept;
+  Obj& operator=(const Obj&);
+  Obj& operator=(Obj&&) noexcept;
+};
+
+template <class... Ts>
+void consumes_all(Ts&&...);
+
+namespace positive_cases {
+
+void forward_local_object() {
+  Obj obj;
+  Obj& obj_ref = obj;
+
+  Obj obj2 = std::forward<Obj>(obj);
+  // CHECK-MESSAGES: :[[@LINE-1]]:32: warning: calling std::forward on non-forwarding reference 'obj' [cppcoreguidelines-forward-non-forwarding-parameter]
+
+  Obj obj3 = std::forward<Obj>(obj_ref);
+  // CHECK-MESSAGES: :[[@LINE-1]]:32: warning: calling std::forward on non-forwarding reference 'obj_ref' [cppcoreguidelines-forward-non-forwarding-parameter]
+}
+
+void forward_value_param(Obj obj) {
+  Obj obj2 = std::forward<Obj>(obj);
+  // CHECK-MESSAGES: :[[@LINE-1]]:32: warning: calling std::forward on non-forwarding reference 'obj' [cppcoreguidelines-forward-non-forwarding-parameter]
+}
+
+template <class... Ts>
+void forward_pack_of_values(Ts... ts) {
+  consumes_all(std::forward<Ts>(ts)...);
+  // CHECK-MESSAGES: :[[@LINE-1]]:33: warning: calling std::forward on non-forwarding reference 'ts' [cppcoreguidelines-forward-non-forwarding-parameter]
+}
+
+template <class... Ts>
+void forward_pack_of_lvalue_refs(Ts&... ts) {
+  consumes_all(std::forward<Ts>(ts)...);
+  // CHECK-MESSAGES: :[[@LINE-1]]:33: warning: calling std::forward on non-forwarding reference 'ts' [cppcoreguidelines-forward-non-forwarding-parameter]
+}
+
+void forward_lvalue_ref(Obj& obj) {
+  Obj obj2 = std::forward<Obj>(obj);
+  // CHECK-MESSAGES: :[[@LINE-1]]:32: warning: calling std::forward on non-forwarding reference 'obj' [cppcoreguidelines-forward-non-forwarding-parameter]
+}
+
+void forward_const_lvalue_ref(const Obj& obj) {
+  Obj obj2 = std::forward<const Obj>(obj);
+  // CHECK-MESSAGES: :[[@LINE-1]]:38: warning: calling std::forward on non-forwarding reference 'obj' [cppcoreguidelines-forward-non-forwarding-parameter]
+}
+
+void forward_rvalue_ref(Obj&& obj) {
+  Obj obj2 = std::forward<Obj>(obj);
+  // CHECK-MESSAGES: :[[@LINE-1]]:32: warning: calling std::forward on rvalue reference 'obj'; use std::move instead [cppcoreguidelines-forward-non-forwarding-parameter]
+  // CHECK-FIXES: Obj obj2 = std::move(obj);
+}
+
+void forward_const_rvalue_ref(const Obj&& obj) {
+  Obj obj2 = std::forward<const Obj>(obj);
+  // CHECK-MESSAGES: :[[@LINE-1]]:38: warning: calling std::forward on non-forwarding reference 'obj' [cppcoreguidelines-forward-non-forwarding-parameter]
+}
+
+template <class T>
+void forward_value_t(T t) {
+  T other = std::forward<T>(t);
+  // CHECK-MESSAGES: :[[@LINE-1]]:29: warning: calling std::forward on non-forwarding reference 't' [cppcoreguidelines-forward-non-forwarding-parameter]
+}
+
+template <class T>
+void forward_lvalue_ref_t(T& t) {
+  T other = std::forward<T>(t);
+  // CHECK-MESSAGES: :[[@LINE-1]]:29: warning: calling std::forward on non-forwarding reference 't' [cppcoreguidelines-forward-non-forwarding-parameter]
+}
+
+template <class T>
+void forward_const_rvalue_ref_t(const T&& t) {
+  T other = std::forward<const T>(t);
+  // CHECK-MESSAGES: :[[@LINE-1]]:35: warning: calling std::forward on non-forwarding reference 't' [cppcoreguidelines-forward-non-forwarding-parameter]
+}
+
+template <class T>
+struct SomeClass
+{
+  void forwards_lvalue_ref(T& t) {
+    T other = std::forward<T>(t);
+    // CHECK-MESSAGES: :[[@LINE-1]]:31: warning: calling std::forward on non-forwarding reference 't' [cppcoreguidelines-forward-non-forwarding-parameter]
+  }
+
+  void forwards_rvalue_ref(T&& t) {
+    T other = std::forward<T>(t);
+    // CHECK-MESSAGES: :[[@LINE-1]]:31: warning: calling std::forward on rvalue reference 't'; use std::move instead [cppcoreguidelines-forward-non-forwarding-parameter]
+    // CHECK-FIXES: T other = std::move(t);
+  }
+};
+
+} // namespace positive_cases
+
+namespace negative_cases {
+
+template <class T>
+void forwards_param(T&& t) {
+  T other = std::forward<T>(t);
+}
+
+template <class... Ts>
+void forwards_param_pack(Ts&&... ts) {
+  consumes_all(std::forward<Ts>(ts)...);
+}
+
+template <class A>
+struct SomeClass {
+  template <class T>
+  void forwards_param(T&& t) {
+    T other = std::forward<T>(t);
+  }
+
+  template <class T, class U>
+  void forwards_param(T&& t) {
+    T other = std::forward<T>(t);
+  }
+
+  template <class... Ts>
+  void forwards_param_pack(Ts&&... ts) {
+    consumes_all(1, std::forward<Ts>(ts)...);
+  }
+};
+
+void instantiates_calls() {
+  Obj o;
+  forwards_param(0);
+  forwards_param(Obj{});
+  forwards_param(o);
+
+  forwards_param_pack(0);
+  forwards_param_pack(Obj{});
+  forwards_param_pack(o);
+  forwards_param_pack(0, Obj{}, o);
+
+  SomeClass<Obj> someObj;
+  someObj.forwards_param(0);
+  someObj.forwards_param(Obj{});
+  someObj.forwards_param(o);
+  someObj.forwards_param_pack(0, Obj{}, o);
+}
+
+} // namespace negative_cases
Index: clang-tools-extra/docs/clang-tidy/checks/list.rst
===================================================================
--- clang-tools-extra/docs/clang-tidy/checks/list.rst
+++ clang-tools-extra/docs/clang-tidy/checks/list.rst
@@ -186,6 +186,7 @@
    `cppcoreguidelines-avoid-goto <cppcoreguidelines/avoid-goto.html>`_,
    `cppcoreguidelines-avoid-non-const-global-variables <cppcoreguidelines/avoid-non-const-global-variables.html>`_,
    `cppcoreguidelines-avoid-reference-coroutine-parameters <cppcoreguidelines/avoid-reference-coroutine-parameters.html>`_,
+   `cppcoreguidelines-forward-non-forwarding-parameter <cppcoreguidelines/forward-non-forwarding-parameter.html>`_, "Yes"
    `cppcoreguidelines-init-variables <cppcoreguidelines/init-variables.html>`_, "Yes"
    `cppcoreguidelines-interfaces-global-init <cppcoreguidelines/interfaces-global-init.html>`_,
    `cppcoreguidelines-macro-usage <cppcoreguidelines/macro-usage.html>`_,
Index: clang-tools-extra/docs/clang-tidy/checks/cppcoreguidelines/forward-non-forwarding-parameter.rst
===================================================================
--- /dev/null
+++ clang-tools-extra/docs/clang-tidy/checks/cppcoreguidelines/forward-non-forwarding-parameter.rst
@@ -0,0 +1,35 @@
+.. title:: clang-tidy - cppcoreguidelines-forward-non-forwarding-parameter
+
+cppcoreguidelines-forward-non-forwarding-parameter
+==================================================
+
+Warns when ``std::forward`` is used on a non-forwarding reference.
+``std::forward`` is a named cast that preserves the value category of
+the input parameter. It is used in templated functions when the
+parameter should be "forwarded" to another function or constructor
+within the function template body. Using ``std::forward`` on
+non-forwarding references can lead to unexpected or buggy behavior.
+
+Example:
+
+.. code-block:: c++
+
+  void by_lvalue_ref(Object& Input) {
+    // This will actually move Input, always. This is confusing
+    // since the caller of by_lvalue_ref did not std::move, and
+    // could lead to use after move bugs.
+
+    // If a copy was intended, remove std::forward.
+    // If a move is intended, change Input to 'Object&&' or 'Object'
+    // and update the caller.
+    If that is what is intended, use std::move.
+    Object Copy(std::forward<Object>(Input));
+  }
+
+  void move_param(Object&& Input) {
+    Object Copy(std::move(Input)); // Ok
+    Object Copy2(std::forward<Object>(Input)); // Use std::move instead
+  }
+
+This check implements the ``std::forward`` specific enforcements of
+`CppCoreGuideline ES.56 <http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#es56-write-stdmove-only-when-you-need-to-explicitly-move-an-object-to-another-scope>
Index: clang-tools-extra/docs/ReleaseNotes.rst
===================================================================
--- clang-tools-extra/docs/ReleaseNotes.rst
+++ clang-tools-extra/docs/ReleaseNotes.rst
@@ -133,6 +133,11 @@
   Checks that all implicit and explicit inline functions in header files are
   tagged with the ``LIBC_INLINE`` macro.
 
+- New :doc:`cppcoreguidelines-forward-non-forwarding-parameter
+  <clang-tidy/checks/cppcoreguidelines/forward-non-forwarding-parameter>` check.
+
+  Warns when ``std::forward`` is used on a non-forwarding reference.
+
 New check aliases
 ^^^^^^^^^^^^^^^^^
 
Index: clang-tools-extra/clang-tidy/cppcoreguidelines/ForwardNonForwardingParameterCheck.h
===================================================================
--- /dev/null
+++ clang-tools-extra/clang-tidy/cppcoreguidelines/ForwardNonForwardingParameterCheck.h
@@ -0,0 +1,32 @@
+//===--- ForwardNonForwardingParameterCheck.h - clang-tidy ------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_FORWARDNONFORWARDINGPARAMETERCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_FORWARDNONFORWARDINGPARAMETERCHECK_H
+
+#include "../ClangTidyCheck.h"
+
+namespace clang::tidy::cppcoreguidelines {
+
+/// Warns when std::forward is called on a non-forwarding reference. This
+/// check implements the std::forward specific enforcement section of
+/// CppCoreGuideline ES.56.
+///
+/// For the user-facing documentation see:
+/// http://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines/forward-non-forwarding-parameter.html
+class ForwardNonForwardingParameterCheck : public ClangTidyCheck {
+public:
+  ForwardNonForwardingParameterCheck(StringRef Name, ClangTidyContext *Context)
+      : ClangTidyCheck(Name, Context) {}
+  void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+  void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+};
+
+} // namespace clang::tidy::cppcoreguidelines
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_FORWARDNONFORWARDINGPARAMETERCHECK_H
Index: clang-tools-extra/clang-tidy/cppcoreguidelines/ForwardNonForwardingParameterCheck.cpp
===================================================================
--- /dev/null
+++ clang-tools-extra/clang-tidy/cppcoreguidelines/ForwardNonForwardingParameterCheck.cpp
@@ -0,0 +1,115 @@
+//===--- ForwardNonForwardingParameterCheck.cpp - clang-tidy --------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "ForwardNonForwardingParameterCheck.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/Lex/Lexer.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::cppcoreguidelines {
+
+namespace {
+
+AST_MATCHER_P(QualType, possiblyPackExpansionOf,
+              ast_matchers::internal::Matcher<QualType>, InnerMatcher) {
+  return InnerMatcher.matches(Node.getNonPackExpansionType(), Finder, Builder);
+}
+
+AST_MATCHER(ParmVarDecl, isForwardingRefTypeOfFunction) {
+  ast_matchers::internal::Matcher<QualType> Inner = possiblyPackExpansionOf(
+      qualType(rValueReferenceType(),
+               references(templateTypeParmType(
+                   hasDeclaration(templateTypeParmDecl()))),
+               unless(references(qualType(isConstQualified())))));
+  if (!Inner.matches(Node.getType(), Finder, Builder))
+    return false;
+
+  const auto *Function = dyn_cast<FunctionDecl>(Node.getDeclContext());
+  if (!Function)
+    return false;
+
+  const FunctionTemplateDecl *FuncTemplate =
+      Function->getDescribedFunctionTemplate();
+  if (!FuncTemplate)
+    return false;
+
+  QualType ParamType =
+      Node.getType().getNonPackExpansionType()->getPointeeType();
+  const auto *TemplateType = ParamType->getAs<TemplateTypeParmType>();
+  if (!TemplateType)
+    return false;
+
+  return TemplateType->getDepth() ==
+         FuncTemplate->getTemplateParameters()->getDepth();
+}
+
+AST_MATCHER(ParmVarDecl, isInInstantiation) {
+  const auto *Function = dyn_cast<FunctionDecl>(Node.getDeclContext());
+  if (!Function)
+    return false;
+  return Function->isTemplateInstantiation();
+}
+
+} // namespace
+
+void ForwardNonForwardingParameterCheck::registerMatchers(MatchFinder *Finder) {
+  auto RvalueRefDecl =
+      varDecl(hasType(type(rValueReferenceType())),
+              unless(hasType(references(qualType(isConstQualified())))));
+  auto VarMatcher =
+      varDecl(unless(parmVarDecl(isForwardingRefTypeOfFunction())),
+              unless(parmVarDecl(isInInstantiation())),
+              optionally(RvalueRefDecl.bind("rvalue-var")))
+          .bind("var");
+
+  Finder->addMatcher(
+      callExpr(anyOf(callee(functionDecl(hasName("::std::forward"))),
+                     callee(unresolvedLookupExpr(hasAnyDeclaration(namedDecl(
+                         hasUnderlyingDecl(hasName("::std::forward"))))))),
+               argumentCountIs(1),
+               hasArgument(0, declRefExpr(to(VarMatcher)).bind("ref")))
+          .bind("call"),
+      this);
+}
+
+void ForwardNonForwardingParameterCheck::check(
+    const MatchFinder::MatchResult &Result) {
+  const auto *Call = Result.Nodes.getNodeAs<CallExpr>("call");
+  const auto *Ref = Result.Nodes.getNodeAs<DeclRefExpr>("ref");
+  const auto *Var = Result.Nodes.getNodeAs<VarDecl>("var");
+  const auto *RvalueRefVar = Result.Nodes.getNodeAs<VarDecl>("rvalue-var");
+
+  if (!Call || !Var || !Ref)
+    return;
+
+  if (RvalueRefVar) {
+    ASTContext &Context = *Result.Context;
+
+    std::string MoveText = "std::move(";
+    MoveText += Lexer::getSourceText(
+        CharSourceRange::getTokenRange(Ref->getBeginLoc(), Ref->getEndLoc()),
+        Context.getSourceManager(), Context.getLangOpts());
+    MoveText += ")";
+
+    diag(Ref->getLocation(),
+         "calling std::forward on rvalue reference %0; use std::move instead")
+        << Var
+        << FixItHint::CreateReplacement(
+               CharSourceRange::getTokenRange(Call->getBeginLoc(),
+                                              Call->getEndLoc()),
+               MoveText);
+  } else {
+    diag(Ref->getLocation(),
+         "calling std::forward on non-forwarding reference %0")
+        << Var;
+  }
+}
+
+} // namespace clang::tidy::cppcoreguidelines
Index: clang-tools-extra/clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp
===================================================================
--- clang-tools-extra/clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp
+++ clang-tools-extra/clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp
@@ -21,6 +21,7 @@
 #include "AvoidGotoCheck.h"
 #include "AvoidNonConstGlobalVariablesCheck.h"
 #include "AvoidReferenceCoroutineParametersCheck.h"
+#include "ForwardNonForwardingParameterCheck.h"
 #include "InitVariablesCheck.h"
 #include "InterfacesGlobalInitCheck.h"
 #include "MacroUsageCheck.h"
@@ -70,6 +71,8 @@
         "cppcoreguidelines-avoid-reference-coroutine-parameters");
     CheckFactories.registerCheck<modernize::UseOverrideCheck>(
         "cppcoreguidelines-explicit-virtual-functions");
+    CheckFactories.registerCheck<ForwardNonForwardingParameterCheck>(
+        "cppcoreguidelines-forward-non-forwarding-parameter");
     CheckFactories.registerCheck<InitVariablesCheck>(
         "cppcoreguidelines-init-variables");
     CheckFactories.registerCheck<InterfacesGlobalInitCheck>(
Index: clang-tools-extra/clang-tidy/cppcoreguidelines/CMakeLists.txt
===================================================================
--- clang-tools-extra/clang-tidy/cppcoreguidelines/CMakeLists.txt
+++ clang-tools-extra/clang-tidy/cppcoreguidelines/CMakeLists.txt
@@ -12,6 +12,7 @@
   AvoidNonConstGlobalVariablesCheck.cpp
   AvoidReferenceCoroutineParametersCheck.cpp
   CppCoreGuidelinesTidyModule.cpp
+  ForwardNonForwardingParameterCheck.cpp
   InitVariablesCheck.cpp
   InterfacesGlobalInitCheck.cpp
   MacroUsageCheck.cpp
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to