[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:
@@ -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(
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
|
||||
@@ -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
|
||||
-------
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user