Skip to content

[clang-tidy] Add check bugprone-misleading-setter-of-reference #132242

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
May 17, 2025
Merged
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 @@ -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"
Expand Down Expand Up @@ -170,6 +171,8 @@ class BugproneModule : public ClangTidyModule {
"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>(
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 @@ -42,6 +42,7 @@ add_clang_library(clangTidyBugproneModule STATIC
LambdaFunctionNameCheck.cpp
MacroParenthesesCheck.cpp
MacroRepeatedSideEffectsCheck.cpp
MisleadingSetterOfReferenceCheck.cpp
MisplacedOperatorInStrlenInAllocCheck.cpp
MisplacedPointerArithmeticInAllocCheck.cpp
MisplacedWideningCastCheck.cpp
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
6 changes: 6 additions & 0 deletions clang-tools-extra/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,12 @@ New checks
Finds lambda captures that capture the ``this`` 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.

Expand Down
Original file line number Diff line number Diff line change
@@ -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).
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 @@ -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>`,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// RUN: %check_clang_tidy %s bugprone-misleading-setter-of-reference %t
Copy link
Contributor

Choose a reason for hiding this comment

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

Consider adding tests for inheritance. E.g. Base class has method SetX and Derived calls it.

Copy link
Contributor

Choose a reason for hiding this comment

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

Also, a test with templated class and templated method could be useful.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Added tests for inheritance and templates.


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;
}
};
Loading