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

llvm-svn: 234804
This commit is contained in:
Richard Trieu 2015-04-13 22:08:55 +00:00
parent 653457831b
commit 3e1d483e0e
4 changed files with 474 additions and 3 deletions

View File

@ -222,7 +222,10 @@ def GNULabelsAsValue : DiagGroup<"gnu-label-as-value">;
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">;

View File

@ -24,13 +24,30 @@ def note_defined_here : Note<"%0 defined here">;
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;

View File

@ -2364,6 +2364,156 @@ StmtResult Sema::FinishObjCForCollectionStmt(Stmt *S, Stmt *B) {
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(Stmt *S, Stmt *B) {
DiagnoseEmptyStmtBody(ForStmt->getRParenLoc(), B,
diag::warn_empty_range_based_for_body);
DiagnoseForRangeVariableCopies(*this, ForStmt);
return S;
}

View File

@ -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@-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 'int'}}
for (const double &x : int_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 'double' to keep the copy or type 'const int &' to prevent copying}}
for (const Bar x : bar_container) {}
// expected-warning@-1 {{loop variable 'x' of type 'const Bar' creates a copy from type 'const Bar'}}
// expected-note@-2 {{use reference type 'const Bar &' to prevent copying}}
}
void test1() {
Container<int> A;
for (const int &x : A) {}
// expected-warning@-1 {{always a copy}}
// expected-note@-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@-1 {{always a copy}}
// expected-note@-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@-1 {{always a copy}}
// expected-note@-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@-1 {{resulting in a copy}}
// expected-note-re@-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@-1 {{resulting in a copy}}
// expected-note@-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@-1 {{always a copy}}
// expected-note@-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@-1 {{always a copy}}
// expected-note@-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@-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 {{'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@-1 {{always a copy}}
// expected-note@-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@-1 {{resulting in a copy}}
// expected-note-re@-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@-1 {{resulting in a copy}}
// expected-note-re@-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@-1 {{resulting in a copy}}
// expected-note-re@-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@-1 {{resulting in a copy}}
// expected-note-re@-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@-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 {{'int'{{.*}}'const Bar &'}}
for (const int x : I) {}
// No warning
//for (int &x : I) {}
// Binding error
for (int x : I) {}
// No warning
}