Skip to content

Commit e7dd223

Browse files
authored
[clang-tidy] Add new check readability-use-numeric-limits (#127430)
The adds a check that replaces specific numeric literals like `32767` with the equivalent call to `std::numeric_limits` (such as `std::numeric_limits<int16_t>::max())`. Partially addresses #34434, but notably does not handle cases listed in the title post such as `~0` and `-1`.
1 parent 2ed089f commit e7dd223

File tree

8 files changed

+340
-0
lines changed

8 files changed

+340
-0
lines changed

clang-tools-extra/clang-tidy/readability/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ add_clang_library(clangTidyReadabilityModule STATIC
5858
UniqueptrDeleteReleaseCheck.cpp
5959
UppercaseLiteralSuffixCheck.cpp
6060
UseAnyOfAllOfCheck.cpp
61+
UseNumericLimitsCheck.cpp
6162
UseStdMinMaxCheck.cpp
6263

6364
LINK_LIBS

clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
#include "UniqueptrDeleteReleaseCheck.h"
6262
#include "UppercaseLiteralSuffixCheck.h"
6363
#include "UseAnyOfAllOfCheck.h"
64+
#include "UseNumericLimitsCheck.h"
6465
#include "UseStdMinMaxCheck.h"
6566

6667
namespace clang::tidy {
@@ -173,6 +174,8 @@ class ReadabilityModule : public ClangTidyModule {
173174
"readability-uppercase-literal-suffix");
174175
CheckFactories.registerCheck<UseAnyOfAllOfCheck>(
175176
"readability-use-anyofallof");
177+
CheckFactories.registerCheck<UseNumericLimitsCheck>(
178+
"readability-use-numeric-limits");
176179
CheckFactories.registerCheck<UseStdMinMaxCheck>(
177180
"readability-use-std-min-max");
178181
}
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
//===--- UseNumericLimitsCheck.cpp - clang-tidy ---------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "UseNumericLimitsCheck.h"
10+
#include "clang/AST/ASTContext.h"
11+
#include "clang/ASTMatchers/ASTMatchFinder.h"
12+
#include "clang/Lex/Preprocessor.h"
13+
#include <cmath>
14+
#include <limits>
15+
16+
using namespace clang::ast_matchers;
17+
18+
namespace clang::tidy::readability {
19+
20+
UseNumericLimitsCheck::UseNumericLimitsCheck(StringRef Name,
21+
ClangTidyContext *Context)
22+
: ClangTidyCheck(Name, Context),
23+
SignedConstants{
24+
{std::numeric_limits<int8_t>::min(),
25+
"std::numeric_limits<int8_t>::min()"},
26+
{std::numeric_limits<int8_t>::max(),
27+
"std::numeric_limits<int8_t>::max()"},
28+
{std::numeric_limits<int16_t>::min(),
29+
"std::numeric_limits<int16_t>::min()"},
30+
{std::numeric_limits<int16_t>::max(),
31+
"std::numeric_limits<int16_t>::max()"},
32+
{std::numeric_limits<int32_t>::min(),
33+
"std::numeric_limits<int32_t>::min()"},
34+
{std::numeric_limits<int32_t>::max(),
35+
"std::numeric_limits<int32_t>::max()"},
36+
{std::numeric_limits<int64_t>::min(),
37+
"std::numeric_limits<int64_t>::min()"},
38+
{std::numeric_limits<int64_t>::max(),
39+
"std::numeric_limits<int64_t>::max()"},
40+
},
41+
UnsignedConstants{
42+
{std::numeric_limits<uint8_t>::max(),
43+
"std::numeric_limits<uint8_t>::max()"},
44+
{std::numeric_limits<uint16_t>::max(),
45+
"std::numeric_limits<uint16_t>::max()"},
46+
{std::numeric_limits<uint32_t>::max(),
47+
"std::numeric_limits<uint32_t>::max()"},
48+
{std::numeric_limits<uint64_t>::max(),
49+
"std::numeric_limits<uint64_t>::max()"},
50+
},
51+
Inserter(Options.getLocalOrGlobal("IncludeStyle",
52+
utils::IncludeSorter::IS_LLVM),
53+
areDiagsSelfContained()) {}
54+
55+
void UseNumericLimitsCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
56+
Options.store(Opts, "IncludeStyle", Inserter.getStyle());
57+
}
58+
59+
void UseNumericLimitsCheck::registerMatchers(MatchFinder *Finder) {
60+
auto PositiveIntegerMatcher = [](auto Value) {
61+
return unaryOperator(hasOperatorName("+"),
62+
hasUnaryOperand(integerLiteral(equals(Value))
63+
.bind("positive-integer-literal")))
64+
.bind("unary-op");
65+
};
66+
67+
auto NegativeIntegerMatcher = [](auto Value) {
68+
return unaryOperator(hasOperatorName("-"),
69+
hasUnaryOperand(integerLiteral(equals(-Value))
70+
.bind("negative-integer-literal")))
71+
.bind("unary-op");
72+
};
73+
74+
auto BareIntegerMatcher = [](auto Value) {
75+
return integerLiteral(allOf(unless(hasParent(unaryOperator(
76+
hasAnyOperatorName("-", "+")))),
77+
equals(Value)))
78+
.bind("bare-integer-literal");
79+
};
80+
81+
for (const auto &[Value, _] : SignedConstants) {
82+
if (Value < 0) {
83+
Finder->addMatcher(NegativeIntegerMatcher(Value), this);
84+
} else {
85+
Finder->addMatcher(
86+
expr(anyOf(PositiveIntegerMatcher(Value), BareIntegerMatcher(Value))),
87+
this);
88+
}
89+
}
90+
91+
for (const auto &[Value, _] : UnsignedConstants) {
92+
Finder->addMatcher(
93+
expr(anyOf(PositiveIntegerMatcher(Value), BareIntegerMatcher(Value))),
94+
this);
95+
}
96+
}
97+
98+
void UseNumericLimitsCheck::registerPPCallbacks(
99+
const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {
100+
Inserter.registerPreprocessor(PP);
101+
}
102+
103+
void UseNumericLimitsCheck::check(const MatchFinder::MatchResult &Result) {
104+
const IntegerLiteral *MatchedDecl = nullptr;
105+
106+
const IntegerLiteral *NegativeMatchedDecl =
107+
Result.Nodes.getNodeAs<IntegerLiteral>("negative-integer-literal");
108+
const IntegerLiteral *PositiveMatchedDecl =
109+
Result.Nodes.getNodeAs<IntegerLiteral>("positive-integer-literal");
110+
const IntegerLiteral *BareMatchedDecl =
111+
Result.Nodes.getNodeAs<IntegerLiteral>("bare-integer-literal");
112+
113+
if (NegativeMatchedDecl != nullptr)
114+
MatchedDecl = NegativeMatchedDecl;
115+
else if (PositiveMatchedDecl != nullptr)
116+
MatchedDecl = PositiveMatchedDecl;
117+
else if (BareMatchedDecl != nullptr)
118+
MatchedDecl = BareMatchedDecl;
119+
120+
const llvm::APInt MatchedIntegerConstant = MatchedDecl->getValue();
121+
122+
auto Fixer = [&](auto SourceValue, auto Value,
123+
const std::string &Replacement) {
124+
static_assert(std::is_same_v<decltype(SourceValue), decltype(Value)>,
125+
"The types of SourceValue and Value must match");
126+
127+
SourceLocation Location = MatchedDecl->getExprLoc();
128+
SourceRange Range{MatchedDecl->getBeginLoc(), MatchedDecl->getEndLoc()};
129+
130+
// Only valid if unary operator is present
131+
const UnaryOperator *UnaryOpExpr =
132+
Result.Nodes.getNodeAs<UnaryOperator>("unary-op");
133+
134+
if (MatchedDecl == NegativeMatchedDecl && -SourceValue == Value) {
135+
Range = SourceRange(UnaryOpExpr->getBeginLoc(), UnaryOpExpr->getEndLoc());
136+
Location = UnaryOpExpr->getExprLoc();
137+
SourceValue = -SourceValue;
138+
} else if (MatchedDecl == PositiveMatchedDecl && SourceValue == Value) {
139+
Range = SourceRange(UnaryOpExpr->getBeginLoc(), UnaryOpExpr->getEndLoc());
140+
Location = UnaryOpExpr->getExprLoc();
141+
} else if (MatchedDecl != BareMatchedDecl || SourceValue != Value) {
142+
return;
143+
}
144+
145+
diag(Location,
146+
"the constant '%0' is being utilized; consider using '%1' instead")
147+
<< SourceValue << Replacement
148+
<< FixItHint::CreateReplacement(Range, Replacement)
149+
<< Inserter.createIncludeInsertion(
150+
Result.SourceManager->getFileID(Location), "<limits>");
151+
};
152+
153+
for (const auto &[Value, Replacement] : SignedConstants)
154+
Fixer(MatchedIntegerConstant.getSExtValue(), Value, Replacement);
155+
156+
for (const auto &[Value, Replacement] : UnsignedConstants)
157+
Fixer(MatchedIntegerConstant.getZExtValue(), Value, Replacement);
158+
}
159+
160+
} // namespace clang::tidy::readability
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//===--- UseNumericLimitsCheck.h - clang-tidy -------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_USENUMERICLIMITSCHECK_H
10+
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_USENUMERICLIMITSCHECK_H
11+
12+
#include "../ClangTidyCheck.h"
13+
#include "../utils/IncludeInserter.h"
14+
15+
namespace clang::tidy::readability {
16+
17+
/// Finds certain integer literals and suggests replacing them with equivalent
18+
/// ``std::numeric_limits`` calls.
19+
/// For the user-facing documentation see:
20+
/// http://clang.llvm.org/extra/clang-tidy/checks/readability/use-numeric-limits.html
21+
class UseNumericLimitsCheck : public ClangTidyCheck {
22+
public:
23+
UseNumericLimitsCheck(StringRef Name, ClangTidyContext *Context);
24+
void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
25+
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
26+
void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP,
27+
Preprocessor *ModuleExpanderPP) override;
28+
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
29+
30+
private:
31+
const llvm::SmallVector<std::pair<int64_t, std::string>> SignedConstants;
32+
const llvm::SmallVector<std::pair<uint64_t, std::string>> UnsignedConstants;
33+
utils::IncludeInserter Inserter;
34+
};
35+
36+
} // namespace clang::tidy::readability
37+
38+
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_USENUMERICLIMITSCHECK_H

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,12 @@ New checks
154154
Finds potentially erroneous calls to ``reset`` method on smart pointers when
155155
the pointee type also has a ``reset`` method.
156156

157+
- New :doc:`readability-use-numeric-limits
158+
<clang-tidy/checks/readability/use-numeric-limits>` check.
159+
160+
Finds certain integer literals and suggests replacing them with equivalent
161+
``std::numeric_limits`` calls.
162+
157163
New check aliases
158164
^^^^^^^^^^^^^^^^^
159165

clang-tools-extra/docs/clang-tidy/checks/list.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,7 @@ Clang-Tidy Checks
409409
:doc:`readability-uniqueptr-delete-release <readability/uniqueptr-delete-release>`, "Yes"
410410
:doc:`readability-uppercase-literal-suffix <readability/uppercase-literal-suffix>`, "Yes"
411411
:doc:`readability-use-anyofallof <readability/use-anyofallof>`,
412+
:doc:`readability-use-numeric-limits <readability/use-numeric-limits>`, "Yes"
412413
:doc:`readability-use-std-min-max <readability/use-std-min-max>`, "Yes"
413414
:doc:`zircon-temporary-objects <zircon/temporary-objects>`,
414415

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
.. title:: clang-tidy - readability-use-numeric-limits
2+
3+
readability-use-numeric-limits
4+
==============================
5+
6+
Finds certain integer literals and suggests replacing them with equivalent
7+
``std::numeric_limits`` calls.
8+
9+
Before:
10+
11+
.. code-block:: c++
12+
13+
void foo() {
14+
int32_t a = 2147483647;
15+
}
16+
17+
After:
18+
19+
.. code-block:: c++
20+
21+
void foo() {
22+
int32_t a = std::numeric_limits<int32_t>::max();
23+
}
24+
25+
Options
26+
-------
27+
28+
.. option:: IncludeStyle
29+
30+
A string specifying which include-style is used, `llvm` or `google`. Default
31+
is `llvm`.
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
// RUN: %check_clang_tidy %s readability-use-numeric-limits %t
2+
// CHECK-FIXES: #include <limits>
3+
4+
using int8_t = signed char;
5+
using int16_t = short;
6+
using int32_t = int;
7+
using int64_t = long long;
8+
using uint8_t = unsigned char;
9+
using uint16_t = unsigned short;
10+
using uint32_t = unsigned int;
11+
using uint64_t = unsigned long long;
12+
13+
14+
void Invalid() {
15+
// CHECK-MESSAGES: :[[@LINE+2]]:14: warning: the constant '-128' is being utilized; consider using 'std::numeric_limits<int8_t>::min()' instead [readability-use-numeric-limits]
16+
// CHECK-FIXES: int8_t a = std::numeric_limits<int8_t>::min();
17+
int8_t a = -128;
18+
19+
// CHECK-MESSAGES: :[[@LINE+2]]:14: warning: the constant '127' is being utilized; consider using 'std::numeric_limits<int8_t>::max()' instead [readability-use-numeric-limits]
20+
// CHECK-FIXES: int8_t b = std::numeric_limits<int8_t>::max();
21+
int8_t b = +127;
22+
23+
// CHECK-MESSAGES: :[[@LINE+2]]:14: warning: the constant '127' is being utilized; consider using 'std::numeric_limits<int8_t>::max()' instead [readability-use-numeric-limits]
24+
// CHECK-FIXES: int8_t c = std::numeric_limits<int8_t>::max();
25+
int8_t c = 127;
26+
27+
// CHECK-MESSAGES: :[[@LINE+2]]:15: warning: the constant '-32768' is being utilized; consider using 'std::numeric_limits<int16_t>::min()' instead [readability-use-numeric-limits]
28+
// CHECK-FIXES: int16_t d = std::numeric_limits<int16_t>::min();
29+
int16_t d = -32768;
30+
31+
// CHECK-MESSAGES: :[[@LINE+2]]:15: warning: the constant '32767' is being utilized; consider using 'std::numeric_limits<int16_t>::max()' instead [readability-use-numeric-limits]
32+
// CHECK-FIXES: int16_t e = std::numeric_limits<int16_t>::max();
33+
int16_t e = +32767;
34+
35+
// CHECK-MESSAGES: :[[@LINE+2]]:15: warning: the constant '32767' is being utilized; consider using 'std::numeric_limits<int16_t>::max()' instead [readability-use-numeric-limits]
36+
// CHECK-FIXES: int16_t f = std::numeric_limits<int16_t>::max();
37+
int16_t f = 32767;
38+
39+
// CHECK-MESSAGES: :[[@LINE+2]]:15: warning: the constant '-2147483648' is being utilized; consider using 'std::numeric_limits<int32_t>::min()' instead [readability-use-numeric-limits]
40+
// CHECK-FIXES: int32_t g = std::numeric_limits<int32_t>::min();
41+
int32_t g = -2147483648;
42+
43+
// CHECK-MESSAGES: :[[@LINE+2]]:15: warning: the constant '2147483647' is being utilized; consider using 'std::numeric_limits<int32_t>::max()' instead [readability-use-numeric-limits]
44+
// CHECK-FIXES: int32_t h = std::numeric_limits<int32_t>::max();
45+
int32_t h = +2147483647;
46+
47+
// CHECK-MESSAGES: :[[@LINE+2]]:15: warning: the constant '2147483647' is being utilized; consider using 'std::numeric_limits<int32_t>::max()' instead [readability-use-numeric-limits]
48+
// CHECK-FIXES: int32_t i = std::numeric_limits<int32_t>::max();
49+
int32_t i = 2147483647;
50+
51+
// CHECK-MESSAGES: :[[@LINE+2]]:15: warning: the constant '-9223372036854775808' is being utilized; consider using 'std::numeric_limits<int64_t>::min()' instead [readability-use-numeric-limits]
52+
// CHECK-FIXES: int64_t j = std::numeric_limits<int64_t>::min();
53+
int64_t j = -9223372036854775808;
54+
55+
// CHECK-MESSAGES: :[[@LINE+2]]:15: warning: the constant '9223372036854775807' is being utilized; consider using 'std::numeric_limits<int64_t>::max()' instead [readability-use-numeric-limits]
56+
// CHECK-FIXES: int64_t k = std::numeric_limits<int64_t>::max();
57+
int64_t k = +9223372036854775807;
58+
59+
// CHECK-MESSAGES: :[[@LINE+2]]:15: warning: the constant '9223372036854775807' is being utilized; consider using 'std::numeric_limits<int64_t>::max()' instead [readability-use-numeric-limits]
60+
// CHECK-FIXES: int64_t l = std::numeric_limits<int64_t>::max();
61+
int64_t l = 9223372036854775807;
62+
63+
// CHECK-MESSAGES: :[[@LINE+2]]:15: warning: the constant '255' is being utilized; consider using 'std::numeric_limits<uint8_t>::max()' instead [readability-use-numeric-limits]
64+
// CHECK-FIXES: uint8_t m = std::numeric_limits<uint8_t>::max();
65+
uint8_t m = 255;
66+
67+
// CHECK-MESSAGES: :[[@LINE+2]]:15: warning: the constant '255' is being utilized; consider using 'std::numeric_limits<uint8_t>::max()' instead [readability-use-numeric-limits]
68+
// CHECK-FIXES: uint8_t n = std::numeric_limits<uint8_t>::max();
69+
uint8_t n = +255;
70+
71+
// CHECK-MESSAGES: :[[@LINE+2]]:16: warning: the constant '65535' is being utilized; consider using 'std::numeric_limits<uint16_t>::max()' instead [readability-use-numeric-limits]
72+
// CHECK-FIXES: uint16_t o = std::numeric_limits<uint16_t>::max();
73+
uint16_t o = 65535;
74+
75+
// CHECK-MESSAGES: :[[@LINE+2]]:16: warning: the constant '65535' is being utilized; consider using 'std::numeric_limits<uint16_t>::max()' instead [readability-use-numeric-limits]
76+
// CHECK-FIXES: uint16_t p = std::numeric_limits<uint16_t>::max();
77+
uint16_t p = +65535;
78+
79+
// CHECK-MESSAGES: :[[@LINE+2]]:16: warning: the constant '4294967295' is being utilized; consider using 'std::numeric_limits<uint32_t>::max()' instead [readability-use-numeric-limits]
80+
// CHECK-FIXES: uint32_t q = std::numeric_limits<uint32_t>::max();
81+
uint32_t q = 4294967295;
82+
83+
// CHECK-MESSAGES: :[[@LINE+2]]:16: warning: the constant '4294967295' is being utilized; consider using 'std::numeric_limits<uint32_t>::max()' instead [readability-use-numeric-limits]
84+
// CHECK-FIXES: uint32_t r = std::numeric_limits<uint32_t>::max();
85+
uint32_t r = +4294967295;
86+
87+
// CHECK-MESSAGES: :[[@LINE+2]]:16: warning: the constant '18446744073709551615' is being utilized; consider using 'std::numeric_limits<uint64_t>::max()' instead [readability-use-numeric-limits]
88+
// CHECK-FIXES: uint64_t s = std::numeric_limits<uint64_t>::max();
89+
uint64_t s = 18446744073709551615;
90+
91+
// CHECK-MESSAGES: :[[@LINE+2]]:16: warning: the constant '18446744073709551615' is being utilized; consider using 'std::numeric_limits<uint64_t>::max()' instead [readability-use-numeric-limits]
92+
// CHECK-FIXES: uint64_t t = std::numeric_limits<uint64_t>::max();
93+
uint64_t t = +18446744073709551615;
94+
}
95+
96+
void Valid(){
97+
int16_t a = +128;
98+
99+
int16_t b = -127;
100+
}

0 commit comments

Comments
 (0)