[clang-tidy] Add check bugprone-misleading-setter-of-reference (#132242)
This commit is contained in:
@@ -41,6 +41,7 @@
|
||||
#include "LambdaFunctionNameCheck.h"
|
||||
#include "MacroParenthesesCheck.h"
|
||||
#include "MacroRepeatedSideEffectsCheck.h"
|
||||
#include "MisleadingSetterOfReferenceCheck.h"
|
||||
#include "MisplacedOperatorInStrlenInAllocCheck.h"
|
||||
#include "MisplacedPointerArithmeticInAllocCheck.h"
|
||||
#include "MisplacedWideningCastCheck.h"
|
||||
@@ -170,6 +171,8 @@ public:
|
||||
"bugprone-macro-parentheses");
|
||||
CheckFactories.registerCheck<MacroRepeatedSideEffectsCheck>(
|
||||
"bugprone-macro-repeated-side-effects");
|
||||
CheckFactories.registerCheck<MisleadingSetterOfReferenceCheck>(
|
||||
"bugprone-misleading-setter-of-reference");
|
||||
CheckFactories.registerCheck<MisplacedOperatorInStrlenInAllocCheck>(
|
||||
"bugprone-misplaced-operator-in-strlen-in-alloc");
|
||||
CheckFactories.registerCheck<MisplacedPointerArithmeticInAllocCheck>(
|
||||
|
||||
@@ -42,6 +42,7 @@ add_clang_library(clangTidyBugproneModule STATIC
|
||||
LambdaFunctionNameCheck.cpp
|
||||
MacroParenthesesCheck.cpp
|
||||
MacroRepeatedSideEffectsCheck.cpp
|
||||
MisleadingSetterOfReferenceCheck.cpp
|
||||
MisplacedOperatorInStrlenInAllocCheck.cpp
|
||||
MisplacedPointerArithmeticInAllocCheck.cpp
|
||||
MisplacedWideningCastCheck.cpp
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
//===--- MisleadingSetterOfReferenceCheck.cpp - clang-tidy-----------------===//
|
||||
//
|
||||
// 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 "MisleadingSetterOfReferenceCheck.h"
|
||||
#include "clang/AST/ASTContext.h"
|
||||
#include "clang/ASTMatchers/ASTMatchFinder.h"
|
||||
|
||||
using namespace clang::ast_matchers;
|
||||
|
||||
namespace clang::tidy::bugprone {
|
||||
|
||||
void MisleadingSetterOfReferenceCheck::registerMatchers(MatchFinder *Finder) {
|
||||
auto RefField = fieldDecl(hasType(hasCanonicalType(referenceType(
|
||||
pointee(equalsBoundNode("type"))))))
|
||||
.bind("member");
|
||||
auto AssignLHS = memberExpr(
|
||||
hasObjectExpression(ignoringParenCasts(cxxThisExpr())), member(RefField));
|
||||
auto DerefOperand = expr(ignoringParenCasts(
|
||||
declRefExpr(to(parmVarDecl(equalsBoundNode("parm"))))));
|
||||
auto AssignRHS = expr(ignoringParenCasts(
|
||||
unaryOperator(hasOperatorName("*"), hasUnaryOperand(DerefOperand))));
|
||||
|
||||
auto BinaryOpAssign = binaryOperator(hasOperatorName("="), hasLHS(AssignLHS),
|
||||
hasRHS(AssignRHS));
|
||||
auto CXXOperatorCallAssign = cxxOperatorCallExpr(
|
||||
hasOverloadedOperatorName("="), hasLHS(AssignLHS), hasRHS(AssignRHS));
|
||||
|
||||
auto SetBody =
|
||||
compoundStmt(statementCountIs(1),
|
||||
anyOf(has(BinaryOpAssign), has(CXXOperatorCallAssign)));
|
||||
auto BadSetFunction =
|
||||
cxxMethodDecl(
|
||||
parameterCountIs(1),
|
||||
hasParameter(
|
||||
0,
|
||||
parmVarDecl(hasType(hasCanonicalType(pointerType(pointee(qualType(
|
||||
hasCanonicalType(qualType().bind("type"))))))))
|
||||
.bind("parm")),
|
||||
hasBody(SetBody))
|
||||
.bind("bad-set-function");
|
||||
Finder->addMatcher(BadSetFunction, this);
|
||||
}
|
||||
|
||||
void MisleadingSetterOfReferenceCheck::check(
|
||||
const MatchFinder::MatchResult &Result) {
|
||||
const auto *Found = Result.Nodes.getNodeAs<CXXMethodDecl>("bad-set-function");
|
||||
const auto *Member = Result.Nodes.getNodeAs<FieldDecl>("member");
|
||||
|
||||
diag(Found->getBeginLoc(),
|
||||
"function '%0' can be mistakenly used in order to change the "
|
||||
"reference '%1' instead of the value of it; consider not using a "
|
||||
"pointer as argument")
|
||||
<< Found->getName() << Member->getName();
|
||||
}
|
||||
|
||||
} // namespace clang::tidy::bugprone
|
||||
@@ -0,0 +1,37 @@
|
||||
//===--- MisleadingSetterOfReferenceCheck.h - clang-tidy---------*- C++ -*-===//
|
||||
//
|
||||
// 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_MISLEADINGSETTEROFREFERENCECHECK_H
|
||||
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_MISLEADINGSETTEROFREFERENCECHECK_H
|
||||
|
||||
#include "../ClangTidyCheck.h"
|
||||
|
||||
namespace clang::tidy::bugprone {
|
||||
|
||||
/// Emits a warning when a setter-like function that has a pointer parameter
|
||||
/// is used to set value of a field with reference type.
|
||||
///
|
||||
/// For the user-facing documentation see:
|
||||
/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone/throw-keyword-missing.html
|
||||
class MisleadingSetterOfReferenceCheck : public ClangTidyCheck {
|
||||
public:
|
||||
MisleadingSetterOfReferenceCheck(StringRef Name, ClangTidyContext *Context)
|
||||
: ClangTidyCheck(Name, Context) {}
|
||||
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
|
||||
return LangOpts.CPlusPlus;
|
||||
}
|
||||
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
|
||||
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
|
||||
std::optional<TraversalKind> getCheckTraversalKind() const override {
|
||||
return TK_IgnoreUnlessSpelledInSource;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace clang::tidy::bugprone
|
||||
|
||||
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_MISLEADINGSETTEROFREFERENCECHECK_H
|
||||
@@ -124,6 +124,12 @@ New checks
|
||||
pointer and store it as class members without handle the copy and move
|
||||
constructors and the assignments.
|
||||
|
||||
- New :doc:`bugprone-misleading-setter-of-reference
|
||||
<clang-tidy/checks/bugprone/misleading-setter-of-reference>` check.
|
||||
|
||||
Finds setter-like member functions that take a pointer parameter and set a
|
||||
reference member of the same class with the pointed value.
|
||||
|
||||
- New :doc:`bugprone-unintended-char-ostream-output
|
||||
<clang-tidy/checks/bugprone/unintended-char-ostream-output>` check.
|
||||
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
.. title:: clang-tidy - bugprone-misleading-setter-of-reference
|
||||
|
||||
bugprone-misleading-setter-of-reference
|
||||
=======================================
|
||||
|
||||
Finds setter-like member functions that take a pointer parameter and set a
|
||||
reference member of the same class with the pointed value.
|
||||
|
||||
The check detects member functions that take a single pointer parameter,
|
||||
and contain a single expression statement that dereferences the parameter and
|
||||
assigns the result to a data member with a reference type.
|
||||
|
||||
The fact that a setter function takes a pointer might cause the belief that an
|
||||
internal reference (if it would be a pointer) is changed instead of the
|
||||
pointed-to (or referenced) value.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: c++
|
||||
|
||||
class MyClass {
|
||||
int &InternalRef; // non-const reference member
|
||||
public:
|
||||
MyClass(int &Value) : InternalRef(Value) {}
|
||||
|
||||
// Warning: This setter could lead to unintended behaviour.
|
||||
void setRef(int *Value) {
|
||||
InternalRef = *Value; // This assigns to the referenced value, not changing what InternalRef references.
|
||||
}
|
||||
};
|
||||
|
||||
int main() {
|
||||
int Value1 = 42;
|
||||
int Value2 = 100;
|
||||
MyClass X(Value1);
|
||||
|
||||
// This might look like it changes what InternalRef references to,
|
||||
// but it actually modifies Value1 to be 100.
|
||||
X.setRef(&Value2);
|
||||
}
|
||||
|
||||
Possible fixes:
|
||||
- Change the parameter type of the "set" function to non-pointer type (for
|
||||
example, a const reference).
|
||||
- Change the type of the member variable to a pointer and in the "set"
|
||||
function assign a value to the pointer (without dereference).
|
||||
@@ -109,6 +109,7 @@ Clang-Tidy Checks
|
||||
:doc:`bugprone-lambda-function-name <bugprone/lambda-function-name>`,
|
||||
: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>`,
|
||||
:doc:`bugprone-misplaced-operator-in-strlen-in-alloc <bugprone/misplaced-operator-in-strlen-in-alloc>`, "Yes"
|
||||
:doc:`bugprone-misplaced-pointer-arithmetic-in-alloc <bugprone/misplaced-pointer-arithmetic-in-alloc>`, "Yes"
|
||||
:doc:`bugprone-misplaced-widening-cast <bugprone/misplaced-widening-cast>`,
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
// RUN: %check_clang_tidy %s bugprone-misleading-setter-of-reference %t
|
||||
|
||||
struct X {
|
||||
X &operator=(const X &) { return *this; }
|
||||
private:
|
||||
int &Mem;
|
||||
friend class Test1;
|
||||
};
|
||||
|
||||
class Test1 {
|
||||
X &MemX;
|
||||
int &MemI;
|
||||
protected:
|
||||
long &MemL;
|
||||
public:
|
||||
long &MemLPub;
|
||||
|
||||
Test1(X &MemX, int &MemI, long &MemL) : MemX(MemX), MemI(MemI), MemL(MemL), MemLPub(MemL) {}
|
||||
void setI(int *NewValue) {
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function 'setI' can be mistakenly used in order to change the reference 'MemI' instead of the value of it
|
||||
MemI = *NewValue;
|
||||
}
|
||||
void setL(long *NewValue) {
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function 'setL' can be mistakenly used in order to change the reference 'MemL' instead of the value of it
|
||||
MemL = *NewValue;
|
||||
}
|
||||
void setX(X *NewValue) {
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function 'setX' can be mistakenly used in order to change the reference 'MemX' instead of the value of it
|
||||
MemX = *NewValue;
|
||||
}
|
||||
void set1(int *NewValue) {
|
||||
MemX.Mem = *NewValue;
|
||||
}
|
||||
void set2(int *NewValue) {
|
||||
MemL = static_cast<long>(*NewValue);
|
||||
}
|
||||
void set3(int *NewValue) {
|
||||
MemI = *NewValue;
|
||||
MemL = static_cast<long>(*NewValue);
|
||||
}
|
||||
void set4(long *NewValue, int) {
|
||||
MemL = *NewValue;
|
||||
}
|
||||
void setLPub(long *NewValue) {
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function 'setLPub' can be mistakenly used in order to change the reference 'MemLPub' instead of the value of it
|
||||
MemLPub = *NewValue;
|
||||
}
|
||||
|
||||
private:
|
||||
void set5(long *NewValue) {
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function 'set5' can be mistakenly used in order to change the reference 'MemL' instead of the value of it
|
||||
MemL = *NewValue;
|
||||
}
|
||||
};
|
||||
|
||||
class Base {
|
||||
protected:
|
||||
int &MemI;
|
||||
};
|
||||
|
||||
class Derived : public Base {
|
||||
public:
|
||||
void setI(int *NewValue) {
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function 'setI' can be mistakenly used in order to change the reference 'MemI' instead of the value of it
|
||||
MemI = *NewValue;
|
||||
}
|
||||
};
|
||||
|
||||
using UIntRef = unsigned int &;
|
||||
using UIntPtr = unsigned int *;
|
||||
using UInt = unsigned int;
|
||||
|
||||
class AliasTest {
|
||||
UIntRef Value1;
|
||||
UInt &Value2;
|
||||
unsigned int &Value3;
|
||||
public:
|
||||
void setValue1(UIntPtr NewValue) {
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function 'setValue1' can be mistakenly used in order to change the reference 'Value1' instead of the value of it
|
||||
Value1 = *NewValue;
|
||||
}
|
||||
void setValue2(unsigned int *NewValue) {
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function 'setValue2' can be mistakenly used in order to change the reference 'Value2' instead of the value of it
|
||||
Value2 = *NewValue;
|
||||
}
|
||||
void setValue3(UInt *NewValue) {
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function 'setValue3' can be mistakenly used in order to change the reference 'Value3' instead of the value of it
|
||||
Value3 = *NewValue;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class TemplateTest {
|
||||
T &Mem;
|
||||
public:
|
||||
TemplateTest(T &V) : Mem{V} {}
|
||||
void setValue(T *NewValue) {
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function 'setValue' can be mistakenly used in order to change the reference 'Mem' instead of the value of it
|
||||
Mem = *NewValue;
|
||||
}
|
||||
};
|
||||
|
||||
void f_TemplateTest(char *Value) {
|
||||
char CharValue;
|
||||
TemplateTest<char> TTChar{CharValue};
|
||||
TTChar.setValue(Value);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
class AddMember {
|
||||
protected:
|
||||
T &Value;
|
||||
};
|
||||
|
||||
class TemplateBaseTest : public AddMember<int> {
|
||||
public:
|
||||
void setValue(int *NewValue) {
|
||||
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function 'setValue' can be mistakenly used in order to change the reference 'Value' instead of the value of it
|
||||
Value = *NewValue;
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user