This warning will catch code such as:

class X {};
X foo() {
  X x;
  return std::move(x);
}

where NRVO could have been applied if a call to std::move was not used.  This 
will emit one warning per function, with one note per return statement that 
needs updated.

class.cc:15:3: warning: return value optimization for function 'foo' prevented
      by call to std::move on return statement [-Wpessimizing-move]
X foo() {
  ^
class.cc:17:7: note: remove call to std::move here to allow return value
      optimization
      return std::move(x);
      ^      ~~~~~~~~~~~~
             x
1 warning generated.

http://reviews.llvm.org/D7633

Files:
  include/clang/AST/Stmt.h
  include/clang/Basic/DiagnosticGroups.td
  include/clang/Basic/DiagnosticSemaKinds.td
  include/clang/Sema/ScopeInfo.h
  include/clang/Sema/Sema.h
  lib/Sema/ScopeInfo.cpp
  lib/Sema/SemaDecl.cpp
  lib/Sema/SemaStmt.cpp

EMAIL PREFERENCES
  http://reviews.llvm.org/settings/panel/emailpreferences/
Index: include/clang/AST/Stmt.h
===================================================================
--- include/clang/AST/Stmt.h
+++ include/clang/AST/Stmt.h
@@ -1344,15 +1344,16 @@
   Stmt *RetExpr;
   SourceLocation RetLoc;
   const VarDecl *NRVOCandidate;
+  bool PessimizingMoveCandidate;
 
 public:
   ReturnStmt(SourceLocation RL)
     : Stmt(ReturnStmtClass), RetExpr(nullptr), RetLoc(RL),
-      NRVOCandidate(nullptr) {}
+      NRVOCandidate(nullptr), PessimizingMoveCandidate(false) {}
 
   ReturnStmt(SourceLocation RL, Expr *E, const VarDecl *NRVOCandidate)
     : Stmt(ReturnStmtClass), RetExpr((Stmt*) E), RetLoc(RL),
-      NRVOCandidate(NRVOCandidate) {}
+      NRVOCandidate(NRVOCandidate), PessimizingMoveCandidate(false) {}
 
   /// \brief Build an empty return expression.
   explicit ReturnStmt(EmptyShell Empty) : Stmt(ReturnStmtClass, Empty) { }
@@ -1372,6 +1373,9 @@
   const VarDecl *getNRVOCandidate() const { return NRVOCandidate; }
   void setNRVOCandidate(const VarDecl *Var) { NRVOCandidate = Var; }
 
+  bool isPessimizingMoveCandidate() { return PessimizingMoveCandidate; }
+  void setPessimizingMoveCandidate() { PessimizingMoveCandidate = true; }
+
   SourceLocation getLocStart() const LLVM_READONLY { return RetLoc; }
   SourceLocation getLocEnd() const LLVM_READONLY {
     return RetExpr ? RetExpr->getLocEnd() : RetLoc;
Index: include/clang/Basic/DiagnosticGroups.td
===================================================================
--- include/clang/Basic/DiagnosticGroups.td
+++ include/clang/Basic/DiagnosticGroups.td
@@ -279,6 +279,7 @@
 def ExplicitInitializeCall : DiagGroup<"explicit-initialize-call">;
 def Packed : DiagGroup<"packed">;
 def Padded : DiagGroup<"padded">;
+def PessimizingMove : DiagGroup<"pessimizing-move">;
 def PointerArith : DiagGroup<"pointer-arith">;
 def PoundWarning : DiagGroup<"#warnings">;
 def PoundPragmaMessage : DiagGroup<"#pragma-messages">,
Index: include/clang/Basic/DiagnosticSemaKinds.td
===================================================================
--- include/clang/Basic/DiagnosticSemaKinds.td
+++ include/clang/Basic/DiagnosticSemaKinds.td
@@ -4741,6 +4741,13 @@
   "explicitly moving variable of type %0 to itself">,
   InGroup<SelfMove>, DefaultIgnore;
 
+def warn_pessimizing_move : Warning<
+  "return value optimization for function %0 prevented by call to "
+  "std::move on return statement">,
+  InGroup<PessimizingMove>, DefaultIgnore;
+def note_remove_move : Note<
+  "remove call to std::move here to allow return value optimization">;
+
 def warn_string_plus_int : Warning<
   "adding %0 to a string does not append to the string">,
   InGroup<StringPlusInt>;
Index: include/clang/Sema/ScopeInfo.h
===================================================================
--- include/clang/Sema/ScopeInfo.h
+++ include/clang/Sema/ScopeInfo.h
@@ -124,6 +124,9 @@
   /// false if there is an invocation of an initializer on 'self'.
   bool ObjCWarnForNoInitDelegation;
 
+  /// True when function may contain pessimizing moves in return statements.
+  bool HasPossiblePessimizingMove;
+
   /// First C++ 'try' statement in the current function.
   SourceLocation FirstCXXTryLoc;
 
@@ -150,7 +153,7 @@
   /// current function scope.  These diagnostics are vetted for reachability
   /// prior to being emitted.
   SmallVector<PossiblyUnreachableDiag, 4> PossiblyUnreachableDiags;
-  
+
   /// \brief A list of parameters which have the nonnull attribute and are
   /// modified in the function.
   llvm::SmallPtrSet<const ParmVarDecl*, 8>  ModifiedNonNullParams;
@@ -342,7 +345,7 @@
         (HasIndirectGoto ||
           (HasBranchProtectedScope && HasBranchIntoScope));
   }
-  
+
   FunctionScopeInfo(DiagnosticsEngine &Diag)
     : Kind(SK_Function),
       HasBranchProtectedScope(false),
@@ -354,6 +357,7 @@
       ObjCWarnForNoDesignatedInitChain(false),
       ObjCIsSecondaryInit(false),
       ObjCWarnForNoInitDelegation(false),
+      HasPossiblePessimizingMove(true),
       ErrorTrap(Diag) { }
 
   virtual ~FunctionScopeInfo();
Index: include/clang/Sema/Sema.h
===================================================================
--- include/clang/Sema/Sema.h
+++ include/clang/Sema/Sema.h
@@ -1706,7 +1706,8 @@
   /// \c constexpr in C++11 or has an 'auto' return type in C++14).
   bool canSkipFunctionBody(Decl *D);
 
-  void computeNRVO(Stmt *Body, sema::FunctionScopeInfo *Scope);
+  void computeNRVO(Stmt *Body, sema::FunctionScopeInfo *Scope,
+                   FunctionDecl *FD = nullptr);
   Decl *ActOnFinishFunctionBody(Decl *Decl, Stmt *Body);
   Decl *ActOnFinishFunctionBody(Decl *Decl, Stmt *Body, bool IsInstantiation);
   Decl *ActOnSkippedFunctionBody(Decl *Decl);
Index: lib/Sema/ScopeInfo.cpp
===================================================================
--- lib/Sema/ScopeInfo.cpp
+++ lib/Sema/ScopeInfo.cpp
@@ -33,6 +33,7 @@
   ObjCWarnForNoDesignatedInitChain = false;
   ObjCIsSecondaryInit = false;
   ObjCWarnForNoInitDelegation = false;
+  HasPossiblePessimizingMove = true;
   FirstCXXTryLoc = SourceLocation();
   FirstSEHTryLoc = SourceLocation();
 
Index: lib/Sema/SemaDecl.cpp
===================================================================
--- lib/Sema/SemaDecl.cpp
+++ lib/Sema/SemaDecl.cpp
@@ -10367,24 +10367,58 @@
 }
 
 /// \brief Given the set of return statements within a function body,
-/// compute the variables that are subject to the named return value 
+/// compute the variables that are subject to the named return value
 /// optimization.
 ///
-/// Each of the variables that is subject to the named return value 
+/// Each of the variables that is subject to the named return value
 /// optimization will be marked as NRVO variables in the AST, and any
 /// return statement that has a marked NRVO variable as its NRVO candidate can
 /// use the named return value optimization.
 ///
 /// This function applies a very simplistic algorithm for NRVO: if every return
 /// statement in the scope of a variable has the same NRVO candidate, that
 /// candidate is an NRVO variable.
-void Sema::computeNRVO(Stmt *Body, FunctionScopeInfo *Scope) {
+void Sema::computeNRVO(Stmt *Body, FunctionScopeInfo *Scope, FunctionDecl *FD) {
   ReturnStmt **Returns = Scope->Returns.data();
 
+  bool FoundPessimizingMove = false;
+  const VarDecl *VD = nullptr;
+  bool HasVD = false;
   for (unsigned I = 0, E = Scope->Returns.size(); I != E; ++I) {
     if (const VarDecl *NRVOCandidate = Returns[I]->getNRVOCandidate()) {
-      if (!NRVOCandidate->isNRVOVariable())
+      if (!HasVD) {
+        VD = NRVOCandidate;
+        HasVD = true;
+      }
+      if (VD != NRVOCandidate)
+        VD = nullptr;
+      if (!NRVOCandidate->isNRVOVariable()) {
         Returns[I]->setNRVOCandidate(nullptr);
+        if (Returns[I]->isPessimizingMoveCandidate()) {
+          FoundPessimizingMove = true;
+        }
+      }
+    }
+  }
+
+  // Only diagnose in functions
+  if (!FD) return;
+  // No pessimizing moves found in return statements
+  if (!FoundPessimizingMove) return;
+  // No single VarDecl across all return statements
+  if (!VD) return;
+  // Check the scope status of pessimizing move
+  if (!Scope->HasPossiblePessimizingMove) return;
+  // Don't warn in template instantiations
+  if (!ActiveTemplateInstantiations.empty()) return;
+
+  Diag(FD->getLocation(), diag::warn_pessimizing_move) << FD;
+
+  for (unsigned I = 0, E = Scope->Returns.size(); I != E; ++I) {
+    if (Returns[I]->isPessimizingMoveCandidate()) {
+      Diag(Returns[I]->getLocStart(), diag::note_remove_move)
+          << FixItHint::CreateReplacement(
+              Returns[I]->getRetValue()->getSourceRange(), VD->getName());
     }
   }
 }
@@ -10498,15 +10532,15 @@
         MarkVTableUsed(FD->getLocation(), Constructor->getParent());
       else if (CXXDestructorDecl *Destructor = dyn_cast<CXXDestructorDecl>(FD))
         MarkVTableUsed(FD->getLocation(), Destructor->getParent());
-      
+
       // Try to apply the named return value optimization. We have to check
       // if we can do this here because lambdas keep return statements around
       // to deduce an implicit return type.
       if (getLangOpts().CPlusPlus && FD->getReturnType()->isRecordType() &&
           !FD->isDependentContext())
-        computeNRVO(Body, getCurFunction());
+        computeNRVO(Body, getCurFunction(), FD);
     }
-    
+
     assert((FD == getCurFunctionDecl() || getCurLambda()->CallOperator == FD) &&
            "Function parsing confused");
   } else if (ObjCMethodDecl *MD = dyn_cast_or_null<ObjCMethodDecl>(dcl)) {
Index: lib/Sema/SemaStmt.cpp
===================================================================
--- lib/Sema/SemaStmt.cpp
+++ lib/Sema/SemaStmt.cpp
@@ -2905,16 +2905,45 @@
     return R;
   }
 
-  if (VarDecl *VD =
-      const_cast<VarDecl*>(cast<ReturnStmt>(R.get())->getNRVOCandidate())) {
+  ReturnStmt *RetStmt = cast<ReturnStmt>(R.get());
+  VarDecl *VD = const_cast<VarDecl *>(RetStmt->getNRVOCandidate());
+  // Only set NRVO on true NRVO candidates.  Ignore pessimizing move candidates.
+  if (VD && !RetStmt->isPessimizingMoveCandidate()) {
     CurScope->addNRVOCandidate(VD);
   } else {
     CurScope->setNoNRVO();
   }
 
   return R;
 }
 
+// If the RetExpr is a function call in the form of std::move(x), return
+// the VarDecl for x if "return x;" could possibly be an NRVO.
+static VarDecl *GetPessimizingMoveDecl(Sema &SemaRef, QualType RetType,
+                                       Expr *RetExpr) {
+  if (!RetExpr || RetType->isReferenceType()) return nullptr;
+
+  CallExpr *CE = dyn_cast<CallExpr>(RetExpr);
+  if (!CE || CE->getNumArgs() != 1) return nullptr;
+
+  FunctionDecl *FD = CE->getDirectCallee();
+  if (!FD || !FD->isInStdNamespace() || !FD->getIdentifier() ||
+      !FD->getIdentifier()->isStr("move"))
+    return nullptr;
+
+  DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(CE->getArg(0));
+  if (!DRE || !DRE->getDecl()) return nullptr;
+
+  VarDecl *VD = dyn_cast<VarDecl>(DRE->getDecl());
+  if (!VD) return nullptr;
+
+  if (!SemaRef.isCopyElisionCandidate(RetType, VD, false)) return nullptr;
+
+  if (!SemaRef.Context.hasSameType(RetType, VD->getType())) return nullptr;
+
+  return VD;
+}
+
 StmtResult Sema::BuildReturnStmt(SourceLocation ReturnLoc, Expr *RetValExp) {
   // Check for unexpanded parameter packs.
   if (RetValExp && DiagnoseUnexpandedParameterPack(RetValExp))
@@ -2928,13 +2957,17 @@
   const AttrVec *Attrs = nullptr;
   bool isObjCMethod = false;
 
+  // Hold the VarDecl being moved in the return statement.
+  VarDecl* MovedDecl = nullptr;
+
   if (const FunctionDecl *FD = getCurFunctionDecl()) {
     FnRetType = FD->getReturnType();
     if (FD->hasAttrs())
       Attrs = &FD->getAttrs();
     if (FD->isNoReturn())
       Diag(ReturnLoc, diag::warn_noreturn_function_has_return_expr)
         << FD->getDeclName();
+    MovedDecl = GetPessimizingMoveDecl(*this, FnRetType, RetValExp);
   } else if (ObjCMethodDecl *MD = getCurMethodDecl()) {
     FnRetType = MD->getReturnType();
     isObjCMethod = true;
@@ -3120,10 +3153,20 @@
     Result = new (Context) ReturnStmt(ReturnLoc, RetValExp, NRVOCandidate);
   }
 
-  // If we need to check for the named return value optimization, save the
-  // return statement in our scope for later processing.
-  if (Result->getNRVOCandidate())
+  if (Result->getNRVOCandidate()) {
+    // If we need to check for the named return value optimization, save the
+    // return statement in our scope for later processing.
     FunctionScopes.back()->Returns.push_back(Result);
+  } else if (MovedDecl) {
+    // If the return expression is "return std::move(x);" and "return x;" could
+    // be a NRVO, mark it as a pessimizing move for later diagnostics.
+    Result->setNRVOCandidate(MovedDecl);
+    Result->setPessimizingMoveCandidate();
+    FunctionScopes.back()->Returns.push_back(Result);
+  } else {
+    // Return statement is neither an NRVO candidate or a pessimizing move.
+    FunctionScopes.back()->HasPossiblePessimizingMove = false;
+  }
 
   return Result;
 }
_______________________________________________
cfe-commits mailing list
[email protected]
http://lists.cs.uiuc.edu/mailman/listinfo/cfe-commits

Reply via email to