Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
#include "IntegerDivisionCheck.h"
#include "InvalidEnumDefaultInitializationCheck.h"
#include "LambdaFunctionNameCheck.h"
#include "LoopVariableCopiedThenModifiedCheck.h"
#include "MacroParenthesesCheck.h"
#include "MacroRepeatedSideEffectsCheck.h"
#include "MisleadingSetterOfReferenceCheck.h"
Expand Down Expand Up @@ -161,6 +162,8 @@ class BugproneModule : public ClangTidyModule {
"bugprone-incorrect-enable-if");
CheckFactories.registerCheck<IncorrectEnableSharedFromThisCheck>(
"bugprone-incorrect-enable-shared-from-this");
CheckFactories.registerCheck<LoopVariableCopiedThenModifiedCheck>(
"bugprone-loop-variable-copied-then-modified");
CheckFactories.registerCheck<UnintendedCharOstreamOutputCheck>(
"bugprone-unintended-char-ostream-output");
CheckFactories.registerCheck<ReturnConstRefFromParameterCheck>(
Expand Down
1 change: 1 addition & 0 deletions clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ add_clang_library(clangTidyBugproneModule STATIC
IncorrectEnableIfCheck.cpp
IncorrectEnableSharedFromThisCheck.cpp
InvalidEnumDefaultInitializationCheck.cpp
LoopVariableCopiedThenModifiedCheck.cpp
UnintendedCharOstreamOutputCheck.cpp
ReturnConstRefFromParameterCheck.cpp
SuspiciousStringviewDataUsageCheck.cpp
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "LoopVariableCopiedThenModifiedCheck.h"
#include "../utils/Matchers.h"
#include "../utils/TypeTraits.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Analysis/Analyses/ExprMutationAnalyzer.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Lex/Lexer.h"

using namespace clang::ast_matchers;

namespace clang::tidy::bugprone {

namespace {
AST_MATCHER(VarDecl, isInMacro) { return Node.getBeginLoc().isMacroID(); }
} // namespace

LoopVariableCopiedThenModifiedCheck::LoopVariableCopiedThenModifiedCheck(
StringRef Name, ClangTidyContext *Context)
: ClangTidyCheck(Name, Context), IgnoreInexpensiveVariables(Options.get(
"IgnoreInexpensiveVariables", false)),
WarnOnlyOnAutoCopies(Options.get("WarnOnlyOnAutoCopies", false)) {}

void LoopVariableCopiedThenModifiedCheck::storeOptions(
ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, "IgnoreInexpensiveVariables", IgnoreInexpensiveVariables);
Options.store(Opts, "WarnOnlyOnAutoCopies", WarnOnlyOnAutoCopies);
}

void LoopVariableCopiedThenModifiedCheck::registerMatchers(
MatchFinder *Finder) {
const auto HasReferenceOrPointerType = hasType(qualType(
unless(hasCanonicalType(anyOf(referenceType(), pointerType())))));
const auto IteratorReturnsValueType = cxxOperatorCallExpr(
hasOverloadedOperatorName("*"),
callee(
cxxMethodDecl(returns(unless(hasCanonicalType(referenceType()))))));
const auto NotConstructedByCopy = cxxConstructExpr(
hasDeclaration(cxxConstructorDecl(unless(isCopyConstructor()))));
const auto ConstructedByConversion =
cxxMemberCallExpr(callee(cxxConversionDecl()));
const auto LoopVar =
varDecl(unless(isInMacro()), HasReferenceOrPointerType,
unless(hasInitializer(expr(hasDescendant(expr(
anyOf(materializeTemporaryExpr(), IteratorReturnsValueType,
NotConstructedByCopy, ConstructedByConversion)))))));
Finder->addMatcher(cxxForRangeStmt(hasLoopVariable(LoopVar.bind("loopVar")))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consider excluding system headers or implicit code like template instances, otherwise there could be conflicts on fixes

.bind("forRange"),
this);
}

void LoopVariableCopiedThenModifiedCheck::check(
const MatchFinder::MatchResult &Result) {
const auto *LoopVar = Result.Nodes.getNodeAs<VarDecl>("loopVar");
std::optional<bool> Expensive = utils::type_traits::isExpensiveToCopy(
LoopVar->getType(), *Result.Context);
if ((!Expensive || !*Expensive) && IgnoreInexpensiveVariables)
return;
Comment on lines +62 to +65
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

change logic, if IgnoreInexpensiveVariables is not set, then there is no need to call isExpensiveToCopy, also you could use value_or

if (WarnOnlyOnAutoCopies) {
if (!isa<AutoType>(LoopVar->getType())) {
return;
}
}
const auto *ForRange = Result.Nodes.getNodeAs<CXXForRangeStmt>("forRange");

if (!ExprMutationAnalyzer(*ForRange->getBody(), *Result.Context)
.isMutated(LoopVar)) {
return;
}

clang::SourceRange LoopVarSourceRange =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LoopVarTypeSourceRange

LoopVar->getTypeSourceInfo()->getTypeLoc().getSourceRange();
clang::SourceLocation EndLoc = clang::Lexer::getLocForEndOfToken(
LoopVarSourceRange.getEnd(), 0, Result.Context->getSourceManager(),
Result.Context->getLangOpts());
diag(LoopVar->getLocation(),
"loop variable '%0' is copied and then (possibly) modified; use an "
"explicit copy inside the body of the loop or make the variable a "
"reference")
<< LoopVar->getName();
diag(LoopVar->getLocation(), "consider making '%0' a reference",
DiagnosticIDs::Note)
<< LoopVar->getName()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need for getName, it's safer without it

<< FixItHint::CreateInsertion(LoopVarSourceRange.getBegin(), "const ")
<< FixItHint::CreateInsertion(EndLoc, "&");
}

} // namespace clang::tidy::bugprone
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_LOOPVARIABLECOPIEDTHENMODIFIEDCHECK_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_LOOPVARIABLECOPIEDTHENMODIFIEDCHECK_H

#include "../ClangTidyCheck.h"

namespace clang::tidy::bugprone {

/// Finds loop variables that are copied and subsequently modified.
///
/// For the user-facing documentation see:
/// https://clang.llvm.org/extra/clang-tidy/checks/bugprone/loop-variable-copied-then-modified.html
class LoopVariableCopiedThenModifiedCheck : public ClangTidyCheck {
public:
LoopVariableCopiedThenModifiedCheck(StringRef Name,
ClangTidyContext *Context);
void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
return LangOpts.CPlusPlus;
}

private:
const bool IgnoreInexpensiveVariables;
const bool WarnOnlyOnAutoCopies;
};

} // namespace clang::tidy::bugprone

#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_LOOPVARIABLECOPIEDTHENMODIFIEDCHECK_H
12 changes: 9 additions & 3 deletions clang-tools-extra/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -161,16 +161,22 @@ Improvements to clang-tidy
New checks
^^^^^^^^^^

- New :doc:`bugprone-derived-method-shadowing-base-method
<clang-tidy/checks/bugprone/derived-method-shadowing-base-method>` check.

Finds derived class methods that shadow a (non-virtual) base class method.

- New :doc:`bugprone-invalid-enum-default-initialization
<clang-tidy/checks/bugprone/invalid-enum-default-initialization>` check.

Detects default initialization (to 0) of variables with ``enum`` type where
the enum has no enumerator with value of 0.

- New :doc:`bugprone-derived-method-shadowing-base-method
<clang-tidy/checks/bugprone/derived-method-shadowing-base-method>` check.
- New :doc:`bugprone-loop-variable-copied-then-modified
<clang-tidy/checks/bugprone/loop-variable-copied-then-modified>` check.

Finds derived class methods that shadow a (non-virtual) base class method.
Detects when a loop variable is copied and then subsequently (possibly) modified
and suggests replacing with a reference or an explicit copy.

- New :doc:`cppcoreguidelines-pro-bounds-avoid-unchecked-container-access
<clang-tidy/checks/cppcoreguidelines/pro-bounds-avoid-unchecked-container-access>`
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
.. title:: clang-tidy - bugprone-loop-variable-copied-then-modified

bugprone-loop-variable-copied-then-modified
===========================================

Detects when a loop variable is copied and then subsequently (possibly) modified
and suggests replacing with a reference or an explicit copy.

This pattern is considered bugprone because, frequently, programmers do not
realize that they are modifying a *copy* rather than an underlying value,
resulting in subtly erroneous code.

For instance, the following code attempts to null out a value in a map, but only
succeeds in nulling out a value in a *copy* of the map:

.. code-block:: c++

for (auto target : target_map) {
target.value = nullptr;
}

The programmer is likely to have intended this code instead:

.. code-block:: c++

for (auto& target : target_map) {
target.value = nullptr;
}

This code can be fixed in one of two ways:
- In cases where the programmer did not intend to create a copy, they can
convert the loop variable to a reference or a ``const`` reference. A
fix-note message will provide a naive suggestion of how to achieve this,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do not refer to those fixes as "fix-note", mention somewhere that check provide fixes that can be enabled with --fix-notes. Also mention that those fixes can break compilation.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should common functionality like --fix-notes be spelled explicitly? May be just fix-its? Otherwise such clarification needs to be repeated in each check with fix-its.

which works in most cases.
- In cases where the intent is in fact to modify a copy, they may perform the
copy explicitly, inside the body of the loop, and perform whatever
operations they like on that copy.

This is a conservative check: in cases where it cannot be determined at compile
time whether or not a particular function modifies the variable, it assumes a
modification has ocurred and warns accordingly. However, in such cases, the
warning can still be suppressed by doing one of the actions described above.

Options
-------

.. option:: IgnoreInexpensiveVariables

When `true`, this check will only alert on types that are expensive to copy.
This will lead to fewer false positives, but will also overlook some
instances where there may be an actual bug. Default is `false`.

.. option:: WarnOnlyOnAutoCopies

When `true`, this check will only alert on `auto` types. Default is `false`.
1 change: 1 addition & 0 deletions clang-tools-extra/docs/clang-tidy/checks/list.rst
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ Clang-Tidy Checks
:doc:`bugprone-integer-division <bugprone/integer-division>`,
:doc:`bugprone-invalid-enum-default-initialization <bugprone/invalid-enum-default-initialization>`,
:doc:`bugprone-lambda-function-name <bugprone/lambda-function-name>`,
:doc:`bugprone-loop-variable-copied-then-modified <bugprone/loop-variable-copied-then-modified>`, "Yes"
:doc:`bugprone-macro-parentheses <bugprone/macro-parentheses>`, "Yes"
:doc:`bugprone-macro-repeated-side-effects <bugprone/macro-repeated-side-effects>`,
:doc:`bugprone-misleading-setter-of-reference <bugprone/misleading-setter-of-reference>`,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// RUN: %check_clang_tidy -std=c++17-or-later %s bugprone-loop-variable-copied-then-modified %t --fix-notes

template <typename T>
struct Iterator {
void operator++() {}
const T& operator*() {
static T* TT = new T();
return *TT;
}
bool operator!=(const Iterator &) { return false; }
};
template <typename T>
struct View {
T begin() { return T(); }
T begin() const { return T(); }
T end() { return T(); }
T end() const { return T(); }
};

struct ConstructorConvertible {
};

struct S {
int value;

S() : value(0) {};
S(const S &);
S(const ConstructorConvertible&) {}
~S();
S &operator=(const S &);
void modify() {
value++;
}
};

struct Convertible {
operator S() const {
return S();
}
};

struct PairLike {
int id;
S data;
};

template <typename V>
struct Generic {
V value;

Generic() : value{} {};
Generic(const Generic &);
~Generic();
Generic &operator=(const Generic &);
void modify() {
value++;
}
};

void PositiveLoopVariableCopiedAndThenModfiedGeneric() {
for (Generic G : View<Iterator<Generic<double>>>()) {
// CHECK-MESSAGES: [[@LINE-1]]:16: warning: loop variable 'G' is copied and then (possibly) modified; use an explicit copy inside the body of the loop or make the variable a reference
// CHECK-MESSAGES: [[@LINE-2]]:16: note: consider making 'G' a reference
// CHECK-FIXES: for (const Generic& G : View<Iterator<Generic<double>>>()) {
G.modify();
}
}

void NegativeLoopVariableIsReferenceAndModifiedGeneric() {
for (const Generic<double>& G : View<Iterator<Generic<double>>>()) {
// It's fine to copy-by-value G into some other G.
Generic<double> G2 = G;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// RUN: %check_clang_tidy %s bugprone-loop-variable-copied-then-modified %t --fix-notes \
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing tests with "auto&&" and "const auto"

// RUN: -config="{CheckOptions: \
// RUN: {bugprone-loop-variable-copied-then-modified.IgnoreInexpensiveVariables: true}}" \
// RUN: -- -I%S
#include "Inputs/system-header-simulator/sim_initializer_list"
#include "Inputs/system-header-simulator/sim_vector"

template <typename T>
struct Iterator {
void operator++() {}
const T& operator*() {
static T* TT = new T();
return *TT;
}
bool operator!=(const Iterator &) { return false; }
};
template <typename T>
struct View {
T begin() { return T(); }
T begin() const { return T(); }
T end() { return T(); }
T end() const { return T(); }
};

struct S {
int value;

S() : value(0) {};
S(const S &);
~S();
S &operator=(const S &);
void modify() {
value++;
}
};

void NegativeOnlyCopyingInts() {
std::vector<int> foo;
foo.push_back(1);
foo.push_back(2);
foo.push_back(3);
for (int v : foo) {
v += 1;
}
}

void PositiveLoopVariableCopiedAndThenModfied() {
for (S S1 : View<Iterator<S>>()) {
// CHECK-MESSAGES: [[@LINE-1]]:10: warning: loop variable 'S1' is copied and then (possibly) modified; use an explicit copy inside the body of the loop or make the variable a reference
// CHECK-MESSAGES: [[@LINE-2]]:10: note: consider making 'S1' a reference
// CHECK-FIXES: for (const S& S1 : View<Iterator<S>>()) {
S1.modify();
}
}
Loading