nlee updated this revision to Diff 434842.
nlee added a comment.

After some investigation, I think it is not always explicit that removing 
`const` makes any improvements when we are "constructing." For example, in this 
case <https://godbolt.org/z/5McKK538M>, the two codes which differ in the 
return type of function `f` (`S f()` vs. `const S f()`) produce the same 
assembly output. I assume this is due to various copy elisions stepping in.
However, when we are "assigning," it makes a difference. In this case 
<https://godbolt.org/z/h979nx7ov>, the existence of `const` in the return type 
of `f` decides whether the assignment invokes a move or not.

So I'm suggesting a different policy. Warn if:

- It is an assignment expression AND
- RHS is a function call expression AND
- The function returns by-const-value of a class or struct type AND
- The class or struct is move assignable

The following is a part of diagnosed warnings when applied to llvm:

  
/Users/namgoolee/Documents/llvm-project/llvm/include/llvm/Option/Option.h:103:9:
 warning: const qualifying the return value prevents move semantics 
[-Wpessimizing-return-by-const]
    const Option getGroup() const {
          ^
  /Users/namgoolee/Documents/llvm-project/llvm/lib/Option/ArgList.cpp:38:10: 
note: copy assignment is invoked here even if move assignment is supported for 
type 'llvm::opt::Option'
         O = O.getGroup()) {
           ^
  --
  
/Users/namgoolee/Documents/llvm-project/clang/include/clang/Lex/MacroInfo.h:421:9:
 warning: const qualifying the return value prevents move semantics 
[-Wpessimizing-return-by-const]
    const DefInfo findDirectiveAtLoc(SourceLocation L,
          ^
  
/Users/namgoolee/Documents/llvm-project/clang/include/clang/Lex/Preprocessor.h:1167:10:
 note: copy assignment is invoked here even if move assignment is supported for 
type 'clang::MacroDirective::DefInfo'
        DI = MD->findDirectiveAtLoc(Loc, getSourceManager());
           ^


CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D125402/new/

https://reviews.llvm.org/D125402

Files:
  clang/include/clang/Basic/DiagnosticGroups.td
  clang/include/clang/Basic/DiagnosticSemaKinds.td
  clang/include/clang/Sema/Sema.h
  clang/lib/Sema/SemaChecking.cpp
  clang/lib/Sema/SemaOverload.cpp
  clang/test/Misc/warning-wall.c
  clang/test/SemaCXX/warn-pessimizing-return-by-const.cpp

Index: clang/test/SemaCXX/warn-pessimizing-return-by-const.cpp
===================================================================
--- /dev/null
+++ clang/test/SemaCXX/warn-pessimizing-return-by-const.cpp
@@ -0,0 +1,61 @@
+// RUN: %clang_cc1 -fsyntax-only -Wpessimizing-return-by-const -std=c++11 -verify %s
+
+struct S1 {};
+
+S1 f1() {
+  return S1{};
+}
+
+const S1 f1_const() { // expected-warning{{const qualifying the return value prevents move semantics}}
+  return S1{};
+}
+
+void run1()
+{
+  S1 s1;
+  s1 = f1();
+  s1 = f1_const(); // expected-note{{copy assignment is invoked here even if move assignment is supported for type 'S1'}}
+}
+
+struct S2 {
+  S2() = default;
+  S2(const S2&) = default;
+  S2 &operator=(const S2&) = default;
+  S2 &operator=(S2&&) = default;
+};
+
+S2 f2() {
+  return S2{};
+}
+
+const S2 f2_const() { // expected-warning{{const qualifying the return value prevents move semantics}}
+  return S2{};
+}
+
+void run2()
+{
+  S2 s2;
+  s2 = f2();
+  s2 = f2_const(); // expected-note{{copy assignment is invoked here even if move assignment is supported for type 'S2'}}
+}
+
+struct S3 {
+  S3() = default;
+  S3(const S3&) = default;
+  S3 &operator=(const S3&) = default;
+};
+
+S3 f3() {
+  return S3{};
+}
+
+const S3 f3_const() { // do not warn if move assignment operator is deleted
+  return S3{};
+}
+
+void run3()
+{
+  S3 s3;
+  s3 = f3();
+  s3 = f3_const();
+}
Index: clang/test/Misc/warning-wall.c
===================================================================
--- clang/test/Misc/warning-wall.c
+++ clang/test/Misc/warning-wall.c
@@ -33,6 +33,7 @@
 CHECK-NEXT:      -Wreturn-std-move
 CHECK-NEXT:      -Wself-move
 CHECK-NEXT:    -Wmultichar
+CHECK-NEXT:    -Wpessimizing-return-by-const
 CHECK-NEXT:    -Wrange-loop-construct
 CHECK-NEXT:    -Wreorder
 CHECK-NEXT:      -Wreorder-ctor
Index: clang/lib/Sema/SemaOverload.cpp
===================================================================
--- clang/lib/Sema/SemaOverload.cpp
+++ clang/lib/Sema/SemaOverload.cpp
@@ -13810,9 +13810,10 @@
           ArgsArray = ArgsArray.slice(1);
         }
 
-        // Check for a self move.
-        if (Op == OO_Equal)
+        if (Op == OO_Equal) {
           DiagnoseSelfMove(Args[0], Args[1], OpLoc);
+          DiagnosePessimizingConstReturn(Args[1], OpLoc);
+        }
 
         if (ImplicitThis) {
           QualType ThisType = Context.getPointerType(ImplicitThis->getType());
Index: clang/lib/Sema/SemaChecking.cpp
===================================================================
--- clang/lib/Sema/SemaChecking.cpp
+++ clang/lib/Sema/SemaChecking.cpp
@@ -16800,6 +16800,47 @@
                                         << RHSExpr->getSourceRange();
 }
 
+void Sema::DiagnosePessimizingConstReturn(const Expr *RHSExpr, SourceLocation OpLoc)
+{
+  if (!getLangOpts().CPlusPlus11 ||
+      inTemplateInstantiation())
+    return;
+
+  RHSExpr = RHSExpr->IgnoreParenImpCasts();
+
+  // Is RHS a function call expression?
+  const CallExpr *CE = dyn_cast<const CallExpr>(RHSExpr);
+  if (!CE)
+    return;
+
+  const Decl *CD = CE->getCalleeDecl();
+  if (!CD)
+    return;
+
+  const FunctionDecl *FD = dyn_cast<const FunctionDecl>(CD);
+  if (!FD)
+    return;
+
+  // Is the return type by-const-value of a struct or class type?
+  const QualType RT = FD->getReturnType();
+  if (!RT->isStructureOrClassType() || !RT.isConstQualified())
+    return;
+
+  // Is the move assignment operator deleted?
+  bool MoveAssignmentIsDeleted = false;
+  for (const CXXMethodDecl *iter : RT->getAsCXXRecordDecl()->methods())
+    if (iter->isMoveAssignmentOperator() && iter->isDeleted())
+      MoveAssignmentIsDeleted = true;
+
+  if (RT->getAsCXXRecordDecl()->hasDefinition() &&
+      RT->getAsCXXRecordDecl()->hasMoveAssignment() &&
+      !MoveAssignmentIsDeleted) {
+    const SourceRange ReturnTypeLoc = FD->getReturnTypeSourceRange();
+    Diag(ReturnTypeLoc.getBegin(), diag::warn_pessimizing_return_by_const);
+    Diag(OpLoc, diag::note_pessimizing_return_by_const) << RT.getUnqualifiedType();
+  }
+}
+
 //===--- Layout compatibility ----------------------------------------------//
 
 static bool isLayoutCompatible(ASTContext &C, QualType T1, QualType T2);
Index: clang/include/clang/Sema/Sema.h
===================================================================
--- clang/include/clang/Sema/Sema.h
+++ clang/include/clang/Sema/Sema.h
@@ -5070,6 +5070,8 @@
   void DiagnoseSelfMove(const Expr *LHSExpr, const Expr *RHSExpr,
                         SourceLocation OpLoc);
 
+  void DiagnosePessimizingConstReturn(const Expr *RHSExpr, SourceLocation OpLoc);
+
   /// Warn if we're implicitly casting from a _Nullable pointer type to a
   /// _Nonnull one.
   void diagnoseNullableToNonnullConversion(QualType DstType, QualType SrcType,
Index: clang/include/clang/Basic/DiagnosticSemaKinds.td
===================================================================
--- clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -6627,6 +6627,13 @@
   InGroup<PessimizingMove>, DefaultIgnore;
 def note_remove_move : Note<"remove std::move call here">;
 
+def warn_pessimizing_return_by_const : Warning<
+  "const qualifying the return value prevents move semantics">,
+  InGroup<PessimizingReturnByConst>, DefaultIgnore;
+def note_pessimizing_return_by_const : Note<
+  "copy assignment is invoked here even if move assignment "
+  "is supported for type %0">;
+
 def warn_string_plus_int : Warning<
   "adding %0 to a string does not append to the string">,
   InGroup<StringPlusInt>;
Index: clang/include/clang/Basic/DiagnosticGroups.td
===================================================================
--- clang/include/clang/Basic/DiagnosticGroups.td
+++ clang/include/clang/Basic/DiagnosticGroups.td
@@ -555,6 +555,8 @@
 def PessimizingMove : DiagGroup<"pessimizing-move">;
 def ReturnStdMove : DiagGroup<"return-std-move">;
 
+def PessimizingReturnByConst : DiagGroup<"pessimizing-return-by-const">;
+
 def GNUPointerArith : DiagGroup<"gnu-pointer-arith">;
 def PointerArith : DiagGroup<"pointer-arith", [GNUPointerArith]>;
 
@@ -990,6 +992,7 @@
     MissingBraces,
     Move,
     MultiChar,
+    PessimizingReturnByConst,
     RangeLoopConstruct,
     Reorder,
     ReturnType,
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to