Skip to content

Commit c4e05bd

Browse files
committed
[clang-tidy] Add user-defined functions to bugprone-unsafe-functions check
1 parent 5f15c17 commit c4e05bd

File tree

5 files changed

+295
-35
lines changed

5 files changed

+295
-35
lines changed

clang-tools-extra/clang-tidy/bugprone/UnsafeFunctionsCheck.cpp

Lines changed: 190 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
//===----------------------------------------------------------------------===//
88

99
#include "UnsafeFunctionsCheck.h"
10+
#include "../utils/Matchers.h"
11+
#include "../utils/OptionsUtils.h"
1012
#include "clang/AST/ASTContext.h"
1113
#include "clang/ASTMatchers/ASTMatchFinder.h"
1214
#include "clang/Lex/PPCallbacks.h"
@@ -18,6 +20,12 @@ using namespace llvm;
1820

1921
namespace clang::tidy::bugprone {
2022

23+
static constexpr llvm::StringLiteral OptionNameCustomNormalFunctions =
24+
"CustomNormalFunctions";
25+
static constexpr llvm::StringLiteral OptionNameCustomAnnexKFunctions =
26+
"CustomAnnexKFunctions";
27+
static constexpr llvm::StringLiteral OptionNameReportDefaultFunctions =
28+
"ReportDefaultFunctions";
2129
static constexpr llvm::StringLiteral OptionNameReportMoreUnsafeFunctions =
2230
"ReportMoreUnsafeFunctions";
2331

@@ -26,6 +34,10 @@ static constexpr llvm::StringLiteral FunctionNamesWithAnnexKReplacementId =
2634
static constexpr llvm::StringLiteral FunctionNamesId = "FunctionsNames";
2735
static constexpr llvm::StringLiteral AdditionalFunctionNamesId =
2836
"AdditionalFunctionsNames";
37+
static constexpr llvm::StringLiteral CustomFunctionNamesId =
38+
"CustomFunctionNames";
39+
static constexpr llvm::StringLiteral CustomAnnexKFunctionNamesId =
40+
"CustomAnnexKFunctionNames";
2941
static constexpr llvm::StringLiteral DeclRefId = "DRE";
3042

3143
static std::optional<std::string>
@@ -127,57 +139,155 @@ static bool isAnnexKAvailable(std::optional<bool> &CacheVar, Preprocessor *PP,
127139
return CacheVar.value();
128140
}
129141

142+
static std::vector<UnsafeFunctionsCheck::CheckedFunction>
143+
ParseCheckedFunctions(StringRef Option, StringRef OptionName,
144+
ClangTidyContext *Context) {
145+
std::vector<StringRef> Functions = utils::options::parseStringList(Option);
146+
std::vector<UnsafeFunctionsCheck::CheckedFunction> Result;
147+
Result.reserve(Functions.size());
148+
149+
for (StringRef Function : Functions) {
150+
if (Function.empty()) {
151+
continue;
152+
}
153+
154+
auto [Name, Rest] = Function.split(',');
155+
auto [Replacement, Reason] = Rest.split(',');
156+
157+
if (Name.trim().empty()) {
158+
Context->configurationDiag("invalid configuration value for option '%0'; "
159+
"expected the name of an unsafe function")
160+
<< OptionName;
161+
}
162+
163+
if (Replacement.trim().empty()) {
164+
Context->configurationDiag(
165+
"invalid configuration value '%0' for option '%1'; "
166+
"expected a replacement function name")
167+
<< Name.trim() << OptionName;
168+
}
169+
170+
Result.push_back({Name.trim().str(), llvm::Regex(Name.trim()),
171+
Replacement.trim().str(), Reason.trim().str()});
172+
}
173+
174+
return Result;
175+
}
176+
177+
static std::string SerializeCheckedFunctions(
178+
const std::vector<UnsafeFunctionsCheck::CheckedFunction> &Functions) {
179+
std::vector<std::string> Result;
180+
Result.reserve(Functions.size());
181+
182+
for (const auto &Entry : Functions) {
183+
if (Entry.Reason.empty())
184+
Result.push_back(Entry.Name + "," + Entry.Replacement);
185+
else
186+
Result.push_back(Entry.Name + "," + Entry.Replacement + "," +
187+
Entry.Reason);
188+
}
189+
190+
return llvm::join(Result, ";");
191+
}
192+
130193
UnsafeFunctionsCheck::UnsafeFunctionsCheck(StringRef Name,
131194
ClangTidyContext *Context)
132195
: ClangTidyCheck(Name, Context),
196+
CustomNormalFunctions(ParseCheckedFunctions(
197+
Options.get(OptionNameCustomNormalFunctions, ""),
198+
OptionNameCustomNormalFunctions, Context)),
199+
CustomAnnexKFunctions(ParseCheckedFunctions(
200+
Options.get(OptionNameCustomAnnexKFunctions, ""),
201+
OptionNameCustomAnnexKFunctions, Context)),
202+
ReportDefaultFunctions(
203+
Options.get(OptionNameReportDefaultFunctions, true)),
133204
ReportMoreUnsafeFunctions(
134205
Options.get(OptionNameReportMoreUnsafeFunctions, true)) {}
135206

136207
void UnsafeFunctionsCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
208+
Options.store(Opts, OptionNameCustomNormalFunctions,
209+
SerializeCheckedFunctions(CustomNormalFunctions));
210+
Options.store(Opts, OptionNameCustomAnnexKFunctions,
211+
SerializeCheckedFunctions(CustomAnnexKFunctions));
212+
Options.store(Opts, OptionNameReportDefaultFunctions, ReportDefaultFunctions);
137213
Options.store(Opts, OptionNameReportMoreUnsafeFunctions,
138214
ReportMoreUnsafeFunctions);
139215
}
140216

141217
void UnsafeFunctionsCheck::registerMatchers(MatchFinder *Finder) {
142-
if (getLangOpts().C11) {
143-
// Matching functions with safe replacements only in Annex K.
144-
auto FunctionNamesWithAnnexKReplacementMatcher = hasAnyName(
145-
"::bsearch", "::ctime", "::fopen", "::fprintf", "::freopen", "::fscanf",
146-
"::fwprintf", "::fwscanf", "::getenv", "::gmtime", "::localtime",
147-
"::mbsrtowcs", "::mbstowcs", "::memcpy", "::memmove", "::memset",
148-
"::printf", "::qsort", "::scanf", "::snprintf", "::sprintf", "::sscanf",
149-
"::strcat", "::strcpy", "::strerror", "::strlen", "::strncat",
150-
"::strncpy", "::strtok", "::swprintf", "::swscanf", "::vfprintf",
151-
"::vfscanf", "::vfwprintf", "::vfwscanf", "::vprintf", "::vscanf",
152-
"::vsnprintf", "::vsprintf", "::vsscanf", "::vswprintf", "::vswscanf",
153-
"::vwprintf", "::vwscanf", "::wcrtomb", "::wcscat", "::wcscpy",
154-
"::wcslen", "::wcsncat", "::wcsncpy", "::wcsrtombs", "::wcstok",
155-
"::wcstombs", "::wctomb", "::wmemcpy", "::wmemmove", "::wprintf",
156-
"::wscanf");
218+
if (ReportDefaultFunctions) {
219+
if (getLangOpts().C11) {
220+
// Matching functions with safe replacements only in Annex K.
221+
auto FunctionNamesWithAnnexKReplacementMatcher = hasAnyName(
222+
"::bsearch", "::ctime", "::fopen", "::fprintf", "::freopen",
223+
"::fscanf", "::fwprintf", "::fwscanf", "::getenv", "::gmtime",
224+
"::localtime", "::mbsrtowcs", "::mbstowcs", "::memcpy", "::memmove",
225+
"::memset", "::printf", "::qsort", "::scanf", "::snprintf",
226+
"::sprintf", "::sscanf", "::strcat", "::strcpy", "::strerror",
227+
"::strlen", "::strncat", "::strncpy", "::strtok", "::swprintf",
228+
"::swscanf", "::vfprintf", "::vfscanf", "::vfwprintf", "::vfwscanf",
229+
"::vprintf", "::vscanf", "::vsnprintf", "::vsprintf", "::vsscanf",
230+
"::vswprintf", "::vswscanf", "::vwprintf", "::vwscanf", "::wcrtomb",
231+
"::wcscat", "::wcscpy", "::wcslen", "::wcsncat", "::wcsncpy",
232+
"::wcsrtombs", "::wcstok", "::wcstombs", "::wctomb", "::wmemcpy",
233+
"::wmemmove", "::wprintf", "::wscanf");
234+
Finder->addMatcher(
235+
declRefExpr(to(functionDecl(FunctionNamesWithAnnexKReplacementMatcher)
236+
.bind(FunctionNamesWithAnnexKReplacementId)))
237+
.bind(DeclRefId),
238+
this);
239+
}
240+
241+
// Matching functions with replacements without Annex K.
242+
auto FunctionNamesMatcher =
243+
hasAnyName("::asctime", "asctime_r", "::gets", "::rewind", "::setbuf");
157244
Finder->addMatcher(
158-
declRefExpr(to(functionDecl(FunctionNamesWithAnnexKReplacementMatcher)
159-
.bind(FunctionNamesWithAnnexKReplacementId)))
245+
declRefExpr(
246+
to(functionDecl(FunctionNamesMatcher).bind(FunctionNamesId)))
160247
.bind(DeclRefId),
161248
this);
249+
250+
if (ReportMoreUnsafeFunctions) {
251+
// Matching functions with replacements without Annex K, at user request.
252+
auto AdditionalFunctionNamesMatcher =
253+
hasAnyName("::bcmp", "::bcopy", "::bzero", "::getpw", "::vfork");
254+
Finder->addMatcher(
255+
declRefExpr(to(functionDecl(AdditionalFunctionNamesMatcher)
256+
.bind(AdditionalFunctionNamesId)))
257+
.bind(DeclRefId),
258+
this);
259+
}
162260
}
163261

164-
// Matching functions with replacements without Annex K.
165-
auto FunctionNamesMatcher =
166-
hasAnyName("::asctime", "asctime_r", "::gets", "::rewind", "::setbuf");
167-
Finder->addMatcher(
168-
declRefExpr(to(functionDecl(FunctionNamesMatcher).bind(FunctionNamesId)))
169-
.bind(DeclRefId),
170-
this);
171-
172-
if (ReportMoreUnsafeFunctions) {
173-
// Matching functions with replacements without Annex K, at user request.
174-
auto AdditionalFunctionNamesMatcher =
175-
hasAnyName("::bcmp", "::bcopy", "::bzero", "::getpw", "::vfork");
176-
Finder->addMatcher(
177-
declRefExpr(to(functionDecl(AdditionalFunctionNamesMatcher)
178-
.bind(AdditionalFunctionNamesId)))
179-
.bind(DeclRefId),
180-
this);
262+
if (!CustomAnnexKFunctions.empty()) {
263+
std::vector<llvm::StringRef> FunctionNames;
264+
FunctionNames.reserve(CustomAnnexKFunctions.size());
265+
266+
for (const auto &Entry : CustomAnnexKFunctions)
267+
FunctionNames.push_back(Entry.Name);
268+
269+
auto CustomAnnexKFunctionsMatcher =
270+
matchers::matchesAnyListedName(FunctionNames);
271+
272+
Finder->addMatcher(declRefExpr(to(functionDecl(CustomAnnexKFunctionsMatcher)
273+
.bind(CustomAnnexKFunctionNamesId)))
274+
.bind(DeclRefId),
275+
this);
276+
}
277+
278+
if (!CustomNormalFunctions.empty()) {
279+
std::vector<llvm::StringRef> FunctionNames;
280+
FunctionNames.reserve(CustomNormalFunctions.size());
281+
282+
for (const auto &Entry : CustomNormalFunctions)
283+
FunctionNames.push_back(Entry.Name);
284+
285+
auto CustomFunctionsMatcher = matchers::matchesAnyListedName(FunctionNames);
286+
287+
Finder->addMatcher(declRefExpr(to(functionDecl(CustomFunctionsMatcher)
288+
.bind(CustomFunctionNamesId)))
289+
.bind(DeclRefId),
290+
this);
181291
}
182292
}
183293

@@ -186,16 +296,61 @@ void UnsafeFunctionsCheck::check(const MatchFinder::MatchResult &Result) {
186296
const auto *FuncDecl = cast<FunctionDecl>(DeclRef->getDecl());
187297
assert(DeclRef && FuncDecl && "No valid matched node in check()");
188298

299+
// Only one of these are matched at a time.
189300
const auto *AnnexK = Result.Nodes.getNodeAs<FunctionDecl>(
190301
FunctionNamesWithAnnexKReplacementId);
191302
const auto *Normal = Result.Nodes.getNodeAs<FunctionDecl>(FunctionNamesId);
192303
const auto *Additional =
193304
Result.Nodes.getNodeAs<FunctionDecl>(AdditionalFunctionNamesId);
194-
assert((AnnexK || Normal || Additional) && "No valid match category.");
305+
const auto *CustomAnnexK =
306+
Result.Nodes.getNodeAs<FunctionDecl>(CustomAnnexKFunctionNamesId);
307+
const auto *CustomNormal =
308+
Result.Nodes.getNodeAs<FunctionDecl>(CustomFunctionNamesId);
309+
assert((AnnexK || Normal || Additional || CustomAnnexK || CustomNormal) &&
310+
"No valid match category.");
195311

196312
bool AnnexKIsAvailable =
197313
isAnnexKAvailable(IsAnnexKAvailable, PP, getLangOpts());
198314
StringRef FunctionName = FuncDecl->getName();
315+
316+
if (CustomAnnexK || CustomNormal) {
317+
const auto ShowCheckedFunctionWarning = [&](const CheckedFunction &Entry) {
318+
StringRef Reason =
319+
Entry.Reason.empty() ? "is marked as unsafe" : Entry.Reason.c_str();
320+
diag(DeclRef->getExprLoc(), "function %0 %1; '%2' should be used instead")
321+
<< FuncDecl << Reason << Entry.Replacement
322+
<< DeclRef->getSourceRange();
323+
};
324+
325+
if (AnnexKIsAvailable) {
326+
for (const auto &Entry : CustomAnnexKFunctions) {
327+
if (Entry.Pattern.match(FunctionName)) {
328+
// If both Annex K and Normal are matched, show Annex K warning only.
329+
if (CustomAnnexK)
330+
ShowCheckedFunctionWarning(Entry);
331+
332+
return;
333+
}
334+
}
335+
336+
assert(!CustomAnnexK && "No custom Annex K function was matched.");
337+
}
338+
339+
// Annex K was not available, or the assertion failed.
340+
if (CustomAnnexK)
341+
return;
342+
343+
for (const auto &Entry : CustomNormalFunctions) {
344+
if (Entry.Pattern.match(FunctionName)) {
345+
ShowCheckedFunctionWarning(Entry);
346+
return;
347+
}
348+
}
349+
350+
assert(false && "No custom function was matched.");
351+
return;
352+
}
353+
199354
const std::optional<std::string> ReplacementFunctionName =
200355
[&]() -> std::optional<std::string> {
201356
if (AnnexK) {

clang-tools-extra/clang-tidy/bugprone/UnsafeFunctionsCheck.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,19 @@ class UnsafeFunctionsCheck : public ClangTidyCheck {
3232
Preprocessor *ModuleExpanderPP) override;
3333
void onEndOfTranslationUnit() override;
3434

35+
struct CheckedFunction {
36+
std::string Name;
37+
llvm::Regex Pattern;
38+
std::string Replacement;
39+
std::string Reason;
40+
};
41+
3542
private:
43+
const std::vector<CheckedFunction> CustomNormalFunctions;
44+
const std::vector<CheckedFunction> CustomAnnexKFunctions;
45+
46+
// If true, the default set of functions are reported.
47+
const bool ReportDefaultFunctions;
3648
/// If true, additional functions from widely used API-s (such as POSIX) are
3749
/// added to the list of reported functions.
3850
const bool ReportMoreUnsafeFunctions;

clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-functions.rst

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ The check implements the following rules from the CERT C Coding Standard:
1919
Unsafe functions
2020
----------------
2121

22+
The following functions are reported if `ReportDefaultFunctions
23+
<unsafe-functions.html#cmdoption-arg-ReportDefaultFunctions>`_ is enabled.
24+
2225
If *Annex K.* is available, a replacement from *Annex K.* is suggested for the
2326
following functions:
2427

@@ -74,6 +77,36 @@ Both macros have to be defined to suggest replacement functions from *Annex K.*
7477
``__STDC_WANT_LIB_EXT1__`` must be defined to ``1`` by the user **before**
7578
including any system headers.
7679

80+
Custom functions
81+
----------------
82+
83+
The options `CustomNormalFunctions` and `CustomAnnexKFunctions` allow the user
84+
to define custom functions to be checked. The format is the following, without
85+
newlines:
86+
87+
.. code::
88+
89+
bugprone-unsafe-functions.CustomNormalFunctions="
90+
functionRegex1, replacement1[, reason1];
91+
functionRegex2, replacement2[, reason2];
92+
...
93+
"
94+
95+
The functions are matched using POSIX extended regular expressions.
96+
*(Note: The regular expressions do not support negative* ``(?!)`` *matches)*
97+
98+
The `reason` is optional and is used to provide additional information about the
99+
reasoning behind the replacement. The default reason is ``is marked as unsafe``.
100+
101+
As an example, the configuration ``^original$, replacement, is deprecated;``
102+
will produce the following diagnostic message.
103+
104+
.. code:: c
105+
106+
original(); // warning: function 'original' is deprecated; 'replacement' should be used instead.
107+
::std::original(); // no-warning
108+
original_function(); // no-warning
109+
77110
78111
Options
79112
-------
@@ -86,6 +119,22 @@ Options
86119
this option enables.
87120
Default is `true`.
88121

122+
.. option:: ReportDefaultFunctions
123+
124+
When `true`, the check reports the default set of functions.
125+
Default is `true`.
126+
127+
.. option:: CustomNormalFunctions
128+
129+
A comma-separated list of regular expressions, their replacements, and an
130+
optional reason. For more information, see `Custom functions
131+
<unsafe-functions.html#custom-functions>`_.
132+
133+
.. option:: CustomAnnexKFunctions
134+
135+
A comma-separated list of regular expressions, their replacements, and an
136+
optional reason. For more information, see `Custom functions
137+
<unsafe-functions.html#custom-functions>`_.
89138

90139
Examples
91140
--------

0 commit comments

Comments
 (0)