This warning will trigger on loop variables that make copies in a range-based 
for-loop.  The three cases this warning catches are:

1) for (const Foo &x : Foos), where the range Foos does not return a copy.  
This warning will suggest using the non-reference type so the copy is obvious.
2) for (const Foo x : Foos), where the range Foos does return a reference, but 
is copied into x.  This warning will suggest using the reference type to 
prevent a copy from being made.
3) for (const Bar &x : Foos), where Bar is constructed from Foo.  In this case, 
suggest using the non-reference "const Bar" to indicate a copy is intended to 
be made, or "const Foo &" to prevent a copy from being made.

-Wrange-loop-analysis is being added as a sub-group to -Wloop-analysis.  The 
previous warnings there are moved to -Wfor-loop-analysis.  While the warnings 
are currently split along statement types, a finer grain division of warnings 
may be needed.

http://reviews.llvm.org/D4169

Files:
  include/clang/Basic/DiagnosticGroups.td
  include/clang/Basic/DiagnosticSemaKinds.td
  lib/Sema/SemaStmt.cpp
  test/SemaCXX/warn-range-loop-analysis.cpp
Index: test/SemaCXX/warn-range-loop-analysis.cpp
===================================================================
--- test/SemaCXX/warn-range-loop-analysis.cpp
+++ test/SemaCXX/warn-range-loop-analysis.cpp
@@ -0,0 +1,274 @@
+// RUN: %clang_cc1 -fsyntax-only -std=c++11 -Wloop-analysis -verify %s
+// RUN: %clang_cc1 -fsyntax-only -std=c++11 -Wrange-loop-analysis -verify %s
+
+template <typename return_type>
+class Iterator {
+public:
+  return_type operator*();
+  Iterator operator++();
+  bool operator!=(const Iterator);
+};
+
+template <typename return_type>
+class Container {
+  typedef Iterator<return_type> I;
+
+public:
+  I begin();  // expected-note 10{{}}
+  I end();
+};
+
+class Foo {};
+class Bar {
+public:
+  Bar(Foo);
+  Bar(int);
+  operator int();
+};
+
+void test0() {
+  Container<int> int_container;
+  Container<int&> int_ref_container;
+
+  for (const int &x : int_container) {}
+  // expected-warning@-1 {{loop variable 'x' is always a copy because the range of type 'Container<int>' does not return a reference}}
+  // expected-note@-2 {{use non-reference type 'const int'}}
+
+  for (const double &x : int_ref_container) {}
+  // expected-warning@-1 {{loop variable 'x' has type 'const double &' but is initialized with type 'int' resulting in a copy}}
+  // expected-note@-2 {{use non-reference type 'const double' to keep the copy or type 'const int &' to prevent copying}}
+
+  for (const int x : int_ref_container) {}
+  // expected-warning@-1 {{loop variable 'x' of type 'const int' creates a copy from type 'int'}}
+  // expected-note@-2 {{use reference type 'const int &' to prevent copying}}
+}
+
+void test1() {
+  typedef Container<int> non_reference_iterator_container;
+  non_reference_iterator_container A;
+
+  for (const int &x : A) {}
+  // expected-warning@-1 {{always a copy}}
+  // expected-note@-2 {{'const int'}}
+  for (const int x : A) {}
+  // No warning, non-reference type indicates copy is made
+  for (int &x : A) {}
+  // expected-error@-1{{cannot bind}}
+  for (int x : A) {}
+  // No warning, non-reference type indicates copy is made
+
+  for (const double &x : A) {}
+  // expected-warning@-1 {{always a copy}}
+  // expected-note@-2 {{'const double'}}
+  for (const double x : A) {}
+  // No warning, non-reference type indicates copy is made
+  for (double &x : A) {}
+  // expected-error@-1{{cannot bind}}
+  for (double x : A) {}
+  // No warning, non-reference type indicates copy is made
+
+  for (const Bar &x : A) {}
+  // expected-warning@-1 {{always a copy}}
+  // expected-note@-2 {{'const Bar'}}
+  for (const Bar x : A) {}
+  // No warning, non-reference type indicates copy is made
+  for (Bar &x : A) {}
+  // expected-error@-1{{cannot bind}}
+  for (Bar x : A) {}
+  // No warning, non-reference type indicates copy is made
+}
+
+void test2() {
+  typedef Container<int&> reference_iterator_container;
+  reference_iterator_container B;
+
+  for (const int &x : B) {}
+  // No warning, this reference is not a temporary
+  for (const int x : B) {}
+  // expected-warning@-1 {{creates a copy}}
+  // expected-note@-2 {{'const int &'}}
+  for (int &x : B) {}
+  // No warning
+  for (int x : B) {}
+  // No warning
+
+  for (const double &x : B) {}
+  // expected-warning@-1 {{resulting in a copy}}
+  // expected-note-re@-2 {{'const double'{{.*}}'const int &'}}
+  for (const double x : B) {}
+  for (double &x : B) {}
+  // expected-error@-1 {{cannot bind}}
+  for (double x : B) {}
+  // No warning
+
+  for (const Bar &x : B) {}
+  // expected-warning@-1 {{resulting in a copy}}
+  // expected-note@-2 {{'const Bar'}}
+  for (const Bar x : B) {}
+  for (Bar &x : B) {}
+  // expected-error@-1 {{cannot bind}}
+  for (Bar x : B) {}
+  // No warning
+}
+
+void test3() {
+  typedef Container<Bar> non_reference_iterator_container;
+  non_reference_iterator_container C;
+
+  for (const Bar &x : C) {}
+  // expected-warning@-1 {{always a copy}}
+  // expected-note@-2 {{'const Bar'}}
+  for (const Bar x : C) {}
+  // No warning, non-reference type indicates copy is made
+  for (Bar &x : C) {}
+  // expected-error@-1 {{cannot bind}}
+  for (Bar x : C) {}
+  // No warning, non-reference type indicates copy is made
+
+  for (const int &x : C) {}
+  // expected-warning@-1 {{always a copy}}
+  // expected-note@-2 {{'const int'}}
+  for (const int x : C) {}
+  // Now warning, copy made
+  for (int &x : C) {}
+  // expected-error@-1 {{cannot bind}}
+  for (int x : C) {}
+  // Now warning, copy made
+}
+
+void test4() {
+  typedef Container<Bar&> reference_iterator_container;
+  reference_iterator_container D;
+
+  for (const Bar &x : D) {}
+  // No warning, this reference is not a temporary
+  for (const Bar x : D) {}
+  // expected-warning@-1 {{creates a copy}}
+  // expected-note@-2 {{'const Bar &'}}
+  for (Bar &x : D) {}
+  // No warning
+  for (Bar x : D) {}
+  // No warning
+
+  for (const int &x : D) {}
+  // expected-warning@-1 {{resulting in a copy}}
+  // expected-note-re@-2 {{'const int'{{.*}}'const Bar &'}}
+  for (const int x : D) {}
+  // No warning
+  for (int &x : D) {}
+  // expected-error@-1 {{cannot bind}}
+  for (int x : D) {}
+  // No warning
+}
+
+void test5() {
+  typedef Container<Foo> non_reference_iterator_container;
+  non_reference_iterator_container E;
+
+  for (const Bar &x : E) {}
+  // expected-warning@-1 {{always a copy}}
+  // expected-note@-2 {{'const Bar'}}
+  for (const Bar x : E) {}
+  // No warning, non-reference type indicates copy is made
+  for (Bar &x : E) {}
+  // expected-error@-1 {{cannot bind}}
+  for (Bar x : E) {}
+  // No warning, non-reference type indicates copy is made
+}
+
+void test6() {
+  typedef Container<Foo&> reference_iterator_container;
+  reference_iterator_container F;
+
+  for (const Bar &x : F) {}
+  // expected-warning@-1 {{resulting in a copy}}
+  // expected-note-re@-2 {{'const Bar'{{.*}}'const Foo &'}}
+  for (const Bar x : F) {}
+  // No warning.
+  for (Bar &x : F) {}
+  // expected-error@-1 {{cannot bind}}
+  for (Bar x : F) {}
+  // No warning
+}
+
+void test7() {
+  double G[2];
+
+  for (const double &x : G) {}
+  // No warning
+  for (const double x : G) {}
+  // expected-warning@-1 {{creates a copy}}
+  // expected-note@-2 {{'const double &'}}
+  for (double &x : G) {}
+  // No warning
+  for (double x : G) {}
+  // No warning
+
+  for (const int &x : G) {}
+  // expected-warning@-1 {{resulting in a copy}}
+  // expected-note-re@-2 {{'const int'{{.*}}'const double &'}}
+  for (const int x : G) {}
+  // No warning
+  for (int &x : G) {}
+  // expected-error@-1 {{cannot bind}}
+  for (int x : G) {}
+  // No warning
+
+  for (const Bar &x : G) {}
+  // expected-warning@-1 {{resulting in a copy}}
+  // expected-note-re@-2 {{'const Bar'{{.*}}'const double &'}}
+  for (const Bar x : G) {}
+  // No warning
+  for (int &Bar : G) {}
+  // expected-error@-1 {{cannot bind}}
+  for (int Bar : G) {}
+  // No warning
+}
+
+void test8() {
+  Foo H[2];
+
+  for (const Foo &x : H) {}
+  // No warning
+  for (const Foo x : H) {}
+  // expected-warning@-1 {{creates a copy}}
+  // expected-note@-2 {{'const Foo &'}}
+  for (Foo &x : H) {}
+  // No warning
+  for (Foo x : H) {}
+  // No warning
+
+  for (const Bar &x : H) {}
+  // expected-warning@-1 {{resulting in a copy}}
+  // expected-note-re@-2 {{'const Bar'{{.*}}'const Foo &'}}
+  for (const Bar x : H) {}
+  // No warning
+  for (Bar &x: H) {}
+  // expected-error@-1 {{cannot bind}}
+  for (Bar x: H) {}
+  // No warning
+}
+
+void test9() {
+  Bar I[2] = {1,2};
+
+  for (const Bar &x : I) {}
+  // No warning
+  for (const Bar x : I) {}
+  // expected-warning@-1 {{creates a copy}}
+  // expected-note@-2 {{'const Bar &'}}
+  for (Bar &x : I) {}
+  // No warning
+  for (Bar x : I) {}
+  // No warning
+
+  for (const int &x : I) {}
+  // expected-warning@-1 {{resulting in a copy}}
+  // expected-note-re@-2 {{'const int'{{.*}}'const Bar &'}}
+  for (const int x : I) {}
+  // No warning
+  for (int &x : I) {}
+  // expected-error@-1 {{cannot bind}}
+  for (int x : I) {}
+  // No warning
+}
Index: include/clang/Basic/DiagnosticSemaKinds.td
===================================================================
--- include/clang/Basic/DiagnosticSemaKinds.td
+++ include/clang/Basic/DiagnosticSemaKinds.td
@@ -18,13 +18,30 @@
 def warn_variables_not_in_loop_body : Warning<
   "variable%select{s| %1|s %1 and %2|s %1, %2, and %3|s %1, %2, %3, and %4}0 "
   "used in loop condition not modified in loop body">,
-  InGroup<LoopAnalysis>, DefaultIgnore;
+  InGroup<ForLoopAnalysis>, DefaultIgnore;
 def warn_redundant_loop_iteration : Warning<
   "variable %0 is %select{decremented|incremented}1 both in the loop header "
   "and in the loop body">,
-  InGroup<LoopAnalysis>, DefaultIgnore;
+  InGroup<ForLoopAnalysis>, DefaultIgnore;
 def note_loop_iteration_here : Note<"%select{decremented|incremented}0 here">;
 
+def warn_for_range_const_reference_copy : Warning<
+  "loop variable %0 "
+  "%diff{has type $ but is initialized with type $"
+  "| is initialized with a value of a different type}1,2 resulting in a copy">,
+  InGroup<RangeLoopAnalysis>, DefaultIgnore;
+def note_use_type_or_non_reference : Note<
+  "use non-reference type %0 to keep the copy or type %1 to prevent copying">;
+def warn_for_range_variable_always_copy : Warning<
+  "loop variable %0 is always a copy because the range of type %1 does not "
+  "return a reference">,
+  InGroup<RangeLoopAnalysis>, DefaultIgnore;
+def note_use_non_reference_type : Note<"use non-reference type %0">;
+def warn_for_range_copy : Warning<
+  "loop variable %0 of type %1 creates a copy from type %2">,
+  InGroup<RangeLoopAnalysis>, DefaultIgnore;
+def note_use_reference_type : Note<"use reference type %0 to prevent copying">;
+
 def warn_duplicate_enum_values : Warning<
   "element %0 has been implicitly assigned %1 which another element has "
   "been assigned">, InGroup<DiagGroup<"duplicate-enum">>, DefaultIgnore;
Index: include/clang/Basic/DiagnosticGroups.td
===================================================================
--- include/clang/Basic/DiagnosticGroups.td
+++ include/clang/Basic/DiagnosticGroups.td
@@ -200,7 +200,10 @@
 def LiteralRange : DiagGroup<"literal-range">;
 def LocalTypeTemplateArgs : DiagGroup<"local-type-template-args",
                                       [CXX98CompatLocalTypeTemplateArgs]>;
-def LoopAnalysis : DiagGroup<"loop-analysis">;
+def RangeLoopAnalysis : DiagGroup<"range-loop-analysis">;
+def ForLoopAnalysis : DiagGroup<"for-loop-analysis">;
+def LoopAnalysis : DiagGroup<"loop-analysis", [ForLoopAnalysis,
+                                               RangeLoopAnalysis]>;
 def MalformedWarningCheck : DiagGroup<"malformed-warning-check">;
 def Main : DiagGroup<"main">;
 def MainReturnType : DiagGroup<"main-return-type">;
Index: lib/Sema/SemaStmt.cpp
===================================================================
--- lib/Sema/SemaStmt.cpp
+++ lib/Sema/SemaStmt.cpp
@@ -2348,6 +2348,126 @@
   return S;
 }
 
+/// DiagnoseForRangeVariableCopies - Diagnose three cases and fixes for them.
+/// 1) for (const foo &x : foos) where foos only returns a copy.  Suggest
+///    using "const foo x" to show that a copy is made
+/// 2) for (const bar &x : foos) where bar is a temporary intialized by bar.
+///    Suggest either "const bar x" to keep the copying or "const foo& x" to
+///    prevent the copy.
+/// 3) for (const foo x : foos) where x is constructed from a reference foo.
+///    Suggest "const foo &x" to prevent the copy.
+static void DiagnoseForRangeVariableCopies(Sema &SemaRef,
+                                           const CXXForRangeStmt *ForStmt) {
+  if (SemaRef.Diags.getDiagnosticLevel(
+          diag::warn_for_range_const_reference_copy, ForStmt->getLocStart()) ==
+          DiagnosticsEngine::Ignored &&
+      SemaRef.Diags.getDiagnosticLevel(
+          diag::warn_for_range_variable_always_copy, ForStmt->getLocStart()) ==
+          DiagnosticsEngine::Ignored &&
+      SemaRef.Diags.getDiagnosticLevel(diag::warn_for_range_copy,
+                                       ForStmt->getLocStart()) ==
+          DiagnosticsEngine::Ignored) {
+    return;
+  }
+
+  const VarDecl *VD = ForStmt->getLoopVariable();
+  if (!VD)
+    return;
+
+  QualType VariableType = VD->getType();
+
+  if (VariableType->isIncompleteType())
+    return;
+
+  const Expr *InitExpr = VD->getInit();
+  if (!InitExpr)
+    return;
+
+  if (VariableType->isReferenceType()) {
+    // Foo foos[5];
+    // for (const Bar &bar : foos);
+    if (const MaterializeTemporaryExpr *MTE =
+            dyn_cast<MaterializeTemporaryExpr>(InitExpr)) {
+      const Expr *E = MTE->GetTemporaryExpr()->IgnoreImpCasts();
+
+      // Searching for either UnaryOperator for dereference of a pointer or
+      // CXXOperatorCallExpr for handling iterators.
+      while (!isa<CXXOperatorCallExpr>(E) && !isa<UnaryOperator>(E)) {
+        if (const CXXConstructExpr *CCE =
+                                      dyn_cast<CXXConstructExpr>(E)) {
+          E = CCE->getArg(0);
+        } else if (const CXXMemberCallExpr *Call =
+                       dyn_cast<CXXMemberCallExpr>(E)) {
+          const MemberExpr *ME = cast<MemberExpr>(Call->getCallee());
+          E = ME->getBase();
+        } else if (const MaterializeTemporaryExpr *MTE =
+                       dyn_cast<MaterializeTemporaryExpr>(E)) {
+          E = MTE->GetTemporaryExpr();
+        } else {
+          llvm_unreachable("Unexpected expression encountered.");
+        }
+        E = E->IgnoreImpCasts();
+      }
+
+      bool ReturnsReference = false;
+      if (isa<UnaryOperator>(E)) {
+        ReturnsReference = true;
+      } else if (const CXXOperatorCallExpr *Call =
+                   dyn_cast<CXXOperatorCallExpr>(E)) {
+        const FunctionDecl *FD = Call->getDirectCallee();
+        QualType ReturnType = FD->getReturnType();
+        ReturnsReference = ReturnType->isReferenceType();
+      } else {
+        llvm_unreachable("Unexpected expression encountered.");
+      }
+
+      if (ReturnsReference) {
+        // Loop variable creates a temporary.  Suggest either to go with
+        // non-reference loop variable to indiciate a copy is made, or
+        // the correct time to bind a const reference.
+        SemaRef.Diag(VD->getLocation(),
+                     diag::warn_for_range_const_reference_copy)
+            << VD << VariableType << E->getType();
+        QualType NewType = SemaRef.Context.getLValueReferenceType(
+                                               E->getType().withConst());
+        SemaRef.Diag(VD->getLocStart(), diag::note_use_type_or_non_reference)
+            << VariableType.getNonReferenceType() << NewType
+            << VD->getSourceRange();
+      } else {
+        // The range always returns a copy, so a temporary is always created.
+        // Suggest removing the reference from the loop variable.
+        SemaRef.Diag(VD->getLocation(),
+                     diag::warn_for_range_variable_always_copy)
+            << VD << ForStmt->getRangeInit()->getType();
+        SemaRef.Diag(VD->getLocStart(), diag::note_use_non_reference_type)
+            << VariableType.getNonReferenceType() << VD->getSourceRange();
+        return;
+      }
+    }
+  } else if (VariableType.isConstQualified()) {
+    // Foo foos[5];
+    // for (const Bar bar : foos);
+    bool MakeCopy = false;
+    if (const CXXConstructExpr *CE = dyn_cast<CXXConstructExpr>(InitExpr)) {
+      if (CE->getConstructor()->isCopyConstructor())
+        MakeCopy = true;
+    } else if (const CastExpr *CE = dyn_cast<CastExpr>(InitExpr)) {
+      if (CE->getCastKind() == CK_LValueToRValue)
+        MakeCopy = true;
+    }
+    if (MakeCopy) {
+      // Suggest changing from a const variable to a const reference variable
+      // if doing so will prevent a copy.
+      SemaRef.Diag(VD->getLocation(), diag::warn_for_range_copy)
+          << VD << VariableType << InitExpr->getType();
+      SemaRef.Diag(VD->getLocStart(), diag::note_use_reference_type)
+          << SemaRef.Context.getLValueReferenceType(VariableType)
+          << VD->getSourceRange();
+    }
+    return;
+  }
+}
+
 /// FinishCXXForRangeStmt - Attach the body to a C++0x for-range statement.
 /// This is a separate step from ActOnCXXForRangeStmt because analysis of the
 /// body cannot be performed until after the type of the range variable is
@@ -2365,6 +2485,8 @@
   DiagnoseEmptyStmtBody(ForStmt->getRParenLoc(), B,
                         diag::warn_empty_range_based_for_body);
 
+  DiagnoseForRangeVariableCopies(*this, ForStmt);
+
   return S;
 }
 
_______________________________________________
cfe-commits mailing list
[email protected]
http://lists.cs.uiuc.edu/mailman/listinfo/cfe-commits

Reply via email to