-
Notifications
You must be signed in to change notification settings - Fork 15.3k
[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
Changes from 5 commits
e3064b6
2a7b326
8f2c2d0
9babb88
9c2306e
ce9dfb1
d0b0776
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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), isPublic(), | ||
| 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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| .. 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 | ||
| (non-const) reference member of the same class with the pointed value. | ||
|
|
||
| The checker detects public member functions that have a single parameter (which | ||
| is a pointer) and contain a single (maybe overloaded) assignment operator call. | ||
| The assignment should set a member variable with the dereference of the | ||
| parameter pointer. The member variable can have any visibility. | ||
NagyDonat marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| 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 ref_ references. | ||
NagyDonat marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
| }; | ||
| 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 or const reference | ||
| type. | ||
NagyDonat marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| - Change the type of the member variable to a pointer and in the "set" | ||
| function assign a value to the pointer (without dereference). | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,117 @@ | ||
| // RUN: %check_clang_tidy %s bugprone-misleading-setter-of-reference %t | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider adding tests for inheritance. E.g.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, a test with templated class and templated method could be useful.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
| } | ||
|
|
||
| protected: | ||
| void set5(long *NewValue) { | ||
| 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; | ||
| } | ||
| }; | ||
|
|
||
| char CharValue; | ||
| TemplateTest<char> TTChar{CharValue}; | ||
|
|
||
| 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; | ||
| } | ||
| }; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clang-Tidy uses
check.