r234804 - Add new warning -Wrange-loop-analysis to warn on copies during loops.

Richard Trieu rtrieu at google.com
Mon Apr 13 15:08:56 PDT 2015


Author: rtrieu
Date: Mon Apr 13 17:08:55 2015
New Revision: 234804

URL: http://llvm.org/viewvc/llvm-project?rev=234804&view=rev
Log:
Add new warning -Wrange-loop-analysis to warn on copies during loops.

-Wrange-loop-analysis is a subgroup of -Wloop-analysis and will warn when
a range-based for-loop makes copies of the elements in the range.  If possible,
suggest the proper type to prevent copies, or the non-reference to help
distinguish copy versus non-copy forms.  Existing warnings in -Wloop-analysis
are moved to -Wfor-loop-analysis, also a subgroup of -Wloop-analysis.

Differential Revision: http://reviews.llvm.org/D4169


Added:
    cfe/trunk/test/SemaCXX/warn-range-loop-analysis.cpp
Modified:
    cfe/trunk/include/clang/Basic/DiagnosticGroups.td
    cfe/trunk/include/clang/Basic/DiagnosticSemaKinds.td
    cfe/trunk/lib/Sema/SemaStmt.cpp

Modified: cfe/trunk/include/clang/Basic/DiagnosticGroups.td
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/Basic/DiagnosticGroups.td?rev=234804&r1=234803&r2=234804&view=diff
==============================================================================
--- cfe/trunk/include/clang/Basic/DiagnosticGroups.td (original)
+++ cfe/trunk/include/clang/Basic/DiagnosticGroups.td Mon Apr 13 17:08:55 2015
@@ -222,7 +222,10 @@ def GNULabelsAsValue : DiagGroup<"gnu-la
 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">;

Modified: cfe/trunk/include/clang/Basic/DiagnosticSemaKinds.td
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/Basic/DiagnosticSemaKinds.td?rev=234804&r1=234803&r2=234804&view=diff
==============================================================================
--- cfe/trunk/include/clang/Basic/DiagnosticSemaKinds.td (original)
+++ cfe/trunk/include/clang/Basic/DiagnosticSemaKinds.td Mon Apr 13 17:08:55 2015
@@ -24,13 +24,30 @@ def note_defined_here : Note<"%0 defined
 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;

Modified: cfe/trunk/lib/Sema/SemaStmt.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Sema/SemaStmt.cpp?rev=234804&r1=234803&r2=234804&view=diff
==============================================================================
--- cfe/trunk/lib/Sema/SemaStmt.cpp (original)
+++ cfe/trunk/lib/Sema/SemaStmt.cpp Mon Apr 13 17:08:55 2015
@@ -2364,6 +2364,156 @@ StmtResult Sema::FinishObjCForCollection
   return S;
 }
 
+// Warn when the loop variable is a const reference that creates a copy.
+// Suggest using the non-reference type for copies.  If a copy can be prevented
+// suggest the const reference type that would do so.
+// For instance, given "for (const &Foo : Range)", suggest
+// "for (const Foo : Range)" to denote a copy is made for the loop.  If
+// possible, also suggest "for (const &Bar : Range)" if this type prevents
+// the copy altogether.
+static void DiagnoseForRangeReferenceVariableCopies(Sema &SemaRef,
+                                                    const VarDecl *VD,
+                                                    QualType RangeInitType) {
+  const Expr *InitExpr = VD->getInit();
+  if (!InitExpr)
+    return;
+
+  QualType VariableType = VD->getType();
+
+  const MaterializeTemporaryExpr *MTE =
+      dyn_cast<MaterializeTemporaryExpr>(InitExpr);
+
+  // No copy made.
+  if (!MTE)
+    return;
+
+  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 {
+      const MaterializeTemporaryExpr *MTE = cast<MaterializeTemporaryExpr>(E);
+      E = MTE->GetTemporaryExpr();
+    }
+    E = E->IgnoreImpCasts();
+  }
+
+  bool ReturnsReference = false;
+  if (isa<UnaryOperator>(E)) {
+    ReturnsReference = true;
+  } else {
+    const CXXOperatorCallExpr *Call = cast<CXXOperatorCallExpr>(E);
+    const FunctionDecl *FD = Call->getDirectCallee();
+    QualType ReturnType = FD->getReturnType();
+    ReturnsReference = ReturnType->isReferenceType();
+  }
+
+  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 NonReferenceType = VariableType.getNonReferenceType();
+    NonReferenceType.removeLocalConst();
+    QualType NewReferenceType =
+        SemaRef.Context.getLValueReferenceType(E->getType().withConst());
+    SemaRef.Diag(VD->getLocStart(), diag::note_use_type_or_non_reference)
+        << NonReferenceType << NewReferenceType << 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 << RangeInitType;
+    QualType NonReferenceType = VariableType.getNonReferenceType();
+    NonReferenceType.removeLocalConst();
+    SemaRef.Diag(VD->getLocStart(), diag::note_use_non_reference_type)
+        << NonReferenceType << VD->getSourceRange();
+  }
+}
+
+// Warns when the loop variable can be changed to a reference type to
+// prevent a copy.  For instance, if given "for (const Foo x : Range)" suggest
+// "for (const Foo &x : Range)" if this form does not make a copy.
+static void DiagnoseForRangeConstVariableCopies(Sema &SemaRef,
+                                                const VarDecl *VD) {
+  const Expr *InitExpr = VD->getInit();
+  if (!InitExpr)
+    return;
+
+  QualType VariableType = VD->getType();
+
+  if (const CXXConstructExpr *CE = dyn_cast<CXXConstructExpr>(InitExpr)) {
+    if (!CE->getConstructor()->isCopyConstructor())
+      return;
+  } else if (const CastExpr *CE = dyn_cast<CastExpr>(InitExpr)) {
+    if (CE->getCastKind() != CK_LValueToRValue)
+      return;
+  } else {
+    return;
+  }
+
+  // TODO: Determine a maximum size that a POD type can be before a diagnostic
+  // should be emitted.  Also, only ignore POD types with trivial copy
+  // constructors.
+  if (VariableType.isPODType(SemaRef.Context))
+    return;
+
+  // 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();
+}
+
+/// 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.isIgnored(diag::warn_for_range_const_reference_copy,
+                              ForStmt->getLocStart()) &&
+      SemaRef.Diags.isIgnored(diag::warn_for_range_variable_always_copy,
+                              ForStmt->getLocStart()) &&
+      SemaRef.Diags.isIgnored(diag::warn_for_range_copy,
+                              ForStmt->getLocStart())) {
+    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()) {
+    DiagnoseForRangeReferenceVariableCopies(SemaRef, VD,
+                                            ForStmt->getRangeInit()->getType());
+  } else if (VariableType.isConstQualified()) {
+    DiagnoseForRangeConstVariableCopies(SemaRef, VD);
+  }
+}
+
 /// 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
@@ -2381,6 +2531,8 @@ StmtResult Sema::FinishCXXForRangeStmt(S
   DiagnoseEmptyStmtBody(ForStmt->getRParenLoc(), B,
                         diag::warn_empty_range_based_for_body);
 
+  DiagnoseForRangeVariableCopies(*this, ForStmt);
+
   return S;
 }
 

Added: cfe/trunk/test/SemaCXX/warn-range-loop-analysis.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/SemaCXX/warn-range-loop-analysis.cpp?rev=234804&view=auto
==============================================================================
--- cfe/trunk/test/SemaCXX/warn-range-loop-analysis.cpp (added)
+++ cfe/trunk/test/SemaCXX/warn-range-loop-analysis.cpp Mon Apr 13 17:08:55 2015
@@ -0,0 +1,299 @@
+// 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>
+struct Iterator {
+  return_type operator*();
+  Iterator operator++();
+  bool operator!=(const Iterator);
+};
+
+template <typename T>
+struct Container {
+  typedef Iterator<T> I;
+
+  I begin();
+  I end();
+};
+
+struct Foo {};
+struct Bar {
+  Bar(Foo);
+  Bar(int);
+  operator int();
+};
+
+// Testing notes:
+// test0 checks that the full text of the warnings and notes is correct.  The
+//   rest of the tests checks a smaller portion of the text.
+// test1-6 are set in pairs, the odd numbers are the non-reference returning
+//   versions of the even numbers.
+// test7-9 use an array instead of a range object
+// tests use all four versions of the loop varaible, const &T, const T, T&, and
+//   T.  Versions producing errors and are commented out.
+//
+// Conversion chart:
+//   double <=> int
+//   int    <=> Bar
+//   double  => Bar
+//   Foo     => Bar
+//
+// Conversions during tests:
+// test1-2
+//   int => int
+//   int => double
+//   int => Bar
+// test3-4
+//   Bar => Bar
+//   Bar => int
+// test5-6
+//   Foo => Bar
+// test7
+//   double => double
+//   double => int
+//   double => Bar
+// test8
+//   Foo => Foo
+//   Foo => Bar
+// test9
+//   Bar => Bar
+//   Bar => int
+
+void test0() {
+  Container<int> int_non_ref_container;
+  Container<int&> int_container;
+  Container<Bar&> bar_container;
+
+  for (const int &x : int_non_ref_container) {}
+  // expected-warning at -1 {{loop variable 'x' is always a copy because the range of type 'Container<int>' does not return a reference}}
+  // expected-note at -2 {{use non-reference type 'int'}}
+
+  for (const double &x : int_container) {}
+  // expected-warning at -1 {{loop variable 'x' has type 'const double &' but is initialized with type 'int' resulting in a copy}}
+  // expected-note at -2 {{use non-reference type 'double' to keep the copy or type 'const int &' to prevent copying}}
+
+  for (const Bar x : bar_container) {}
+  // expected-warning at -1 {{loop variable 'x' of type 'const Bar' creates a copy from type 'const Bar'}}
+  // expected-note at -2 {{use reference type 'const Bar &' to prevent copying}}
+}
+
+void test1() {
+  Container<int> A;
+
+  for (const int &x : A) {}
+  // expected-warning at -1 {{always a copy}}
+  // expected-note at -2 {{'int'}}
+  for (const int x : A) {}
+  // No warning, non-reference type indicates copy is made
+  //for (int &x : A) {}
+  // Binding error
+  for (int x : A) {}
+  // No warning, non-reference type indicates copy is made
+
+  for (const double &x : A) {}
+  // expected-warning at -1 {{always a copy}}
+  // expected-note at -2 {{'double'}}
+  for (const double x : A) {}
+  // No warning, non-reference type indicates copy is made
+  //for (double &x : A) {}
+  // Binding error
+  for (double x : A) {}
+  // No warning, non-reference type indicates copy is made
+
+  for (const Bar &x : A) {}
+  // expected-warning at -1 {{always a copy}}
+  // expected-note at -2 {{'Bar'}}
+  for (const Bar x : A) {}
+  // No warning, non-reference type indicates copy is made
+  //for (Bar &x : A) {}
+  // Binding error
+  for (Bar x : A) {}
+  // No warning, non-reference type indicates copy is made
+}
+
+void test2() {
+  Container<int&> B;
+
+  for (const int &x : B) {}
+  // No warning, this reference is not a temporary
+  for (const int x : B) {}
+  // No warning on POD copy
+  for (int &x : B) {}
+  // No warning
+  for (int x : B) {}
+  // No warning
+
+  for (const double &x : B) {}
+  // expected-warning at -1 {{resulting in a copy}}
+  // expected-note-re at -2 {{'double'{{.*}}'const int &'}}
+  for (const double x : B) {}
+  //for (double &x : B) {}
+  // Binding error
+  for (double x : B) {}
+  // No warning
+
+  for (const Bar &x : B) {}
+  // expected-warning at -1 {{resulting in a copy}}
+  // expected-note at -2 {{'Bar'}}
+  for (const Bar x : B) {}
+  //for (Bar &x : B) {}
+  // Binding error
+  for (Bar x : B) {}
+  // No warning
+}
+
+void test3() {
+  Container<Bar> C;
+
+  for (const Bar &x : C) {}
+  // expected-warning at -1 {{always a copy}}
+  // expected-note at -2 {{'Bar'}}
+  for (const Bar x : C) {}
+  // No warning, non-reference type indicates copy is made
+  //for (Bar &x : C) {}
+  // Binding error
+  for (Bar x : C) {}
+  // No warning, non-reference type indicates copy is made
+
+  for (const int &x : C) {}
+  // expected-warning at -1 {{always a copy}}
+  // expected-note at -2 {{'int'}}
+  for (const int x : C) {}
+  // No warning, copy made
+  //for (int &x : C) {}
+  // Binding error
+  for (int x : C) {}
+  // No warning, copy made
+}
+
+void test4() {
+  Container<Bar&> D;
+
+  for (const Bar &x : D) {}
+  // No warning, this reference is not a temporary
+  for (const Bar x : D) {}
+  // expected-warning at -1 {{creates a copy}}
+  // expected-note at -2 {{'const Bar &'}}
+  for (Bar &x : D) {}
+  // No warning
+  for (Bar x : D) {}
+  // No warning
+
+  for (const int &x : D) {}
+  // expected-warning at -1 {{resulting in a copy}}
+  // expected-note-re at -2 {{'int'{{.*}}'const Bar &'}}
+  for (const int x : D) {}
+  // No warning
+  //for (int &x : D) {}
+  // Binding error
+  for (int x : D) {}
+  // No warning
+}
+
+void test5() {
+  Container<Foo> E;
+
+  for (const Bar &x : E) {}
+  // expected-warning at -1 {{always a copy}}
+  // expected-note at -2 {{'Bar'}}
+  for (const Bar x : E) {}
+  // No warning, non-reference type indicates copy is made
+  //for (Bar &x : E) {}
+  // Binding error
+  for (Bar x : E) {}
+  // No warning, non-reference type indicates copy is made
+}
+
+void test6() {
+  Container<Foo&> F;
+
+  for (const Bar &x : F) {}
+  // expected-warning at -1 {{resulting in a copy}}
+  // expected-note-re at -2 {{'Bar'{{.*}}'const Foo &'}}
+  for (const Bar x : F) {}
+  // No warning.
+  //for (Bar &x : F) {}
+  // Binding error
+  for (Bar x : F) {}
+  // No warning
+}
+
+void test7() {
+  double G[2];
+
+  for (const double &x : G) {}
+  // No warning
+  for (const double x : G) {}
+  // No warning on POD copy
+  for (double &x : G) {}
+  // No warning
+  for (double x : G) {}
+  // No warning
+
+  for (const int &x : G) {}
+  // expected-warning at -1 {{resulting in a copy}}
+  // expected-note-re at -2 {{'int'{{.*}}'const double &'}}
+  for (const int x : G) {}
+  // No warning
+  //for (int &x : G) {}
+  // Binding error
+  for (int x : G) {}
+  // No warning
+
+  for (const Bar &x : G) {}
+  // expected-warning at -1 {{resulting in a copy}}
+  // expected-note-re at -2 {{'Bar'{{.*}}'const double &'}}
+  for (const Bar x : G) {}
+  // No warning
+  //for (int &Bar : G) {}
+  // Binding error
+  for (int Bar : G) {}
+  // No warning
+}
+
+void test8() {
+  Foo H[2];
+
+  for (const Foo &x : H) {}
+  // No warning
+  for (const Foo x : H) {}
+  // No warning on POD copy
+  for (Foo &x : H) {}
+  // No warning
+  for (Foo x : H) {}
+  // No warning
+
+  for (const Bar &x : H) {}
+  // expected-warning at -1 {{resulting in a copy}}
+  // expected-note-re at -2 {{'Bar'{{.*}}'const Foo &'}}
+  for (const Bar x : H) {}
+  // No warning
+  //for (Bar &x: H) {}
+  // Binding error
+  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 at -1 {{creates a copy}}
+  // expected-note at -2 {{'const Bar &'}}
+  for (Bar &x : I) {}
+  // No warning
+  for (Bar x : I) {}
+  // No warning
+
+  for (const int &x : I) {}
+  // expected-warning at -1 {{resulting in a copy}}
+  // expected-note-re at -2 {{'int'{{.*}}'const Bar &'}}
+  for (const int x : I) {}
+  // No warning
+  //for (int &x : I) {}
+  // Binding error
+  for (int x : I) {}
+  // No warning
+}





More information about the cfe-commits mailing list