[clang-tidy] Add C++ member function support to custom bugprone-unsafe-functions matches (#117165)

Before, C++ member functions in the format of ``Class instance;
instance.memberfn();`` were unable to be matched.
This PR adds support for this type of call, and it is matched in exactly
the same format as other functions (eg. ``::Class::memberfn`` qualified
name).
This commit is contained in:
Discookie
2025-01-30 10:49:01 +00:00
committed by GitHub
parent 7939ce6295
commit 3262863805
5 changed files with 77 additions and 12 deletions

View File

@@ -256,13 +256,32 @@ void UnsafeFunctionsCheck::registerMatchers(MatchFinder *Finder) {
.bind(CustomFunctionNamesId)))
.bind(DeclRefId),
this);
// C++ member calls do not contain a DeclRefExpr to the function decl.
// Instead, they contain a MemberExpr that refers to the decl.
Finder->addMatcher(memberExpr(member(functionDecl(CustomFunctionsMatcher)
.bind(CustomFunctionNamesId)))
.bind(DeclRefId),
this);
}
}
void UnsafeFunctionsCheck::check(const MatchFinder::MatchResult &Result) {
const auto *DeclRef = Result.Nodes.getNodeAs<DeclRefExpr>(DeclRefId);
const auto *FuncDecl = cast<FunctionDecl>(DeclRef->getDecl());
assert(DeclRef && FuncDecl && "No valid matched node in check()");
const Expr *SourceExpr;
const FunctionDecl *FuncDecl;
if (const auto *DeclRef = Result.Nodes.getNodeAs<DeclRefExpr>(DeclRefId)) {
SourceExpr = DeclRef;
FuncDecl = cast<FunctionDecl>(DeclRef->getDecl());
} else if (const auto *Member =
Result.Nodes.getNodeAs<MemberExpr>(DeclRefId)) {
SourceExpr = Member;
FuncDecl = cast<FunctionDecl>(Member->getMemberDecl());
} else {
llvm_unreachable("No valid matched node in check()");
return;
}
assert(SourceExpr && FuncDecl && "No valid matched node in check()");
// Only one of these are matched at a time.
const auto *AnnexK = Result.Nodes.getNodeAs<FunctionDecl>(
@@ -286,14 +305,15 @@ void UnsafeFunctionsCheck::check(const MatchFinder::MatchResult &Result) {
Entry.Reason.empty() ? "is marked as unsafe" : Entry.Reason.c_str();
if (Entry.Replacement.empty()) {
diag(DeclRef->getExprLoc(), "function %0 %1; it should not be used")
diag(SourceExpr->getExprLoc(),
"function %0 %1; it should not be used")
<< FuncDecl << Reason << Entry.Replacement
<< DeclRef->getSourceRange();
<< SourceExpr->getSourceRange();
} else {
diag(DeclRef->getExprLoc(),
diag(SourceExpr->getExprLoc(),
"function %0 %1; '%2' should be used instead")
<< FuncDecl << Reason << Entry.Replacement
<< DeclRef->getSourceRange();
<< SourceExpr->getSourceRange();
}
return;
@@ -323,9 +343,9 @@ void UnsafeFunctionsCheck::check(const MatchFinder::MatchResult &Result) {
if (!ReplacementFunctionName)
return;
diag(DeclRef->getExprLoc(), "function %0 %1; '%2' should be used instead")
diag(SourceExpr->getExprLoc(), "function %0 %1; '%2' should be used instead")
<< FuncDecl << getRationaleFor(FunctionName)
<< ReplacementFunctionName.value() << DeclRef->getSourceRange();
<< ReplacementFunctionName.value() << SourceExpr->getSourceRange();
}
void UnsafeFunctionsCheck::registerPPCallbacks(

View File

@@ -43,7 +43,7 @@ public:
private:
const std::vector<CheckedFunction> CustomFunctions;
// If true, the default set of functions are reported.
/// If true, the default set of functions are reported.
const bool ReportDefaultFunctions;
/// If true, additional functions from widely used API-s (such as POSIX) are
/// added to the list of reported functions.

View File

@@ -97,6 +97,10 @@ New check aliases
Changes in existing checks
^^^^^^^^^^^^^^^^^^^^^^^^^^
- Improved :doc:`bugprone-unsafe-functions
<clang-tidy/checks/bugprone/unsafe-functions>` check to allow specifying
additional C++ member functions to match.
Removed checks
^^^^^^^^^^^^^^

View File

@@ -114,6 +114,17 @@ qualified name (i.e. ``std::original``), otherwise the regex is matched against
If the regular expression starts with `::` (or `^::`), it is matched against the
fully qualified name (``::std::original``).
.. note::
Fully qualified names can contain template parameters on certain C++ classes, but not on C++ functions.
Type aliases are resolved before matching.
As an example, the member function ``open`` in the class ``std::ifstream``
has a fully qualified name of ``::std::basic_ifstream<char>::open``.
The example could also be matched with the regex ``::std::basic_ifstream<[^>]*>::open``, which matches all potential
template parameters, but does not match nested template classes.
Options
-------

View File

@@ -1,11 +1,19 @@
// RUN: %check_clang_tidy -check-suffix=NON-STRICT-REGEX %s bugprone-unsafe-functions %t --\
// RUN: -config="{CheckOptions: {bugprone-unsafe-functions.CustomFunctions: '::name_match,replacement,is a qualname match;^::prefix_match,,is matched on qualname prefix'}}"
// RUN: -config="{CheckOptions: {bugprone-unsafe-functions.CustomFunctions: '::name_match,replacement,is a qualname match;^::prefix_match,,is matched on qualname prefix;^::S::member_match_,,is matched on a C++ class member'}}"
// RUN: %check_clang_tidy -check-suffix=STRICT-REGEX %s bugprone-unsafe-functions %t --\
// RUN: -config="{CheckOptions: {bugprone-unsafe-functions.CustomFunctions: '^name_match$,replacement,is matched on function name only;^::prefix_match$,,is a full qualname match'}}"
// RUN: -config="{CheckOptions: {bugprone-unsafe-functions.CustomFunctions: '^name_match$,replacement,is matched on function name only;^::prefix_match$,,is a full qualname match;^::S::member_match_1$,,is matched on a C++ class member'}}"
void name_match();
void prefix_match();
struct S {
static void member_match_1() {}
void member_match_2() {}
};
void member_match_1() {}
void member_match_unmatched() {}
namespace regex_test {
void name_match();
void prefix_match();
@@ -42,3 +50,25 @@ void f1() {
// CHECK-MESSAGES-NON-STRICT-REGEX: :[[@LINE-1]]:3: warning: function 'prefix_match_regex' is matched on qualname prefix; it should not be used
// no-warning STRICT-REGEX
}
void f2() {
S s;
S::member_match_1();
// CHECK-MESSAGES-NON-STRICT-REGEX: :[[@LINE-1]]:3: warning: function 'member_match_1' is matched on a C++ class member; it should not be used
// CHECK-MESSAGES-STRICT-REGEX: :[[@LINE-2]]:3: warning: function 'member_match_1' is matched on a C++ class member; it should not be used
s.member_match_1();
// CHECK-MESSAGES-NON-STRICT-REGEX: :[[@LINE-1]]:5: warning: function 'member_match_1' is matched on a C++ class member; it should not be used
// CHECK-MESSAGES-STRICT-REGEX: :[[@LINE-2]]:5: warning: function 'member_match_1' is matched on a C++ class member; it should not be used
s.member_match_2();
// CHECK-MESSAGES-NON-STRICT-REGEX: :[[@LINE-1]]:5: warning: function 'member_match_2' is matched on a C++ class member; it should not be used
// no-warning STRICT-REGEX
member_match_1();
// no-warning
member_match_unmatched();
// no-warning
}