Skip to content

Commit

Permalink
Add new warning -Wrange-loop-analysis to warn on copies during loops.
Browse files Browse the repository at this point in the history
-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
  • Loading branch information
Weverything committed Apr 13, 2015
1 parent 6534578 commit 3e1d483
Show file tree
Hide file tree
Showing 4 changed files with 474 additions and 3 deletions.
5 changes: 4 additions & 1 deletion clang/include/clang/Basic/DiagnosticGroups.td
Expand Up @@ -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">;
Expand Down
21 changes: 19 additions & 2 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Expand Up @@ -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;
Expand Down
152 changes: 152 additions & 0 deletions clang/lib/Sema/SemaStmt.cpp
Expand Up @@ -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
Expand All @@ -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;
}

Expand Down

0 comments on commit 3e1d483

Please sign in to comment.