1+ // ===----------------------------------------------------------------------===//
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 " NumericlimitmaxcheckCheck.h"
10+ #include " clang/AST/ASTContext.h"
11+ #include " clang/AST/Expr.h"
12+ #include " clang/ASTMatchers/ASTMatchFinder.h"
13+ #include " clang/Basic/SourceManager.h"
14+ #include " clang/Lex/Lexer.h"
15+
16+ using namespace clang ::ast_matchers;
17+
18+ namespace clang ::tidy::readability {
19+
20+ NumericlimitmaxcheckCheck::NumericlimitmaxcheckCheck (StringRef Name, ClangTidyContext *Context)
21+ : ClangTidyCheck(Name, Context),
22+ Inserter (Options.getLocalOrGlobal(" IncludeStyle" , utils::IncludeSorter::IS_LLVM),
23+ areDiagsSelfContained()) {}
24+
25+ void NumericlimitmaxcheckCheck::registerPPCallbacks (const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {
26+ Inserter.registerPreprocessor (PP);
27+ }
28+
29+ void NumericlimitmaxcheckCheck::storeOptions (ClangTidyOptions::OptionMap &Opts) {
30+ Options.store (Opts, " IncludeStyle" , Inserter.getStyle ());
31+ }
32+
33+ bool NumericlimitmaxcheckCheck::isLanguageVersionSupported (const LangOptions &LangOpts) const {
34+ return LangOpts.CPlusPlus ;
35+ }
36+
37+ void NumericlimitmaxcheckCheck::registerMatchers (MatchFinder *Finder) {
38+ if (!getLangOpts ().CPlusPlus )
39+ return ;
40+
41+ // ... inside registerMatchers() ...
42+
43+ auto NegOneLiteral = integerLiteral (equals (-1 ));
44+ auto ZeroLiteral = integerLiteral (equals (0 ));
45+
46+ auto NegOneExpr = anyOf (
47+ NegOneLiteral,
48+ unaryOperator (hasOperatorName (" -" ),
49+ hasUnaryOperand (integerLiteral (equals (1 )))));
50+
51+ auto BitNotZero = unaryOperator (hasOperatorName (" ~" ),
52+ hasUnaryOperand (ZeroLiteral));
53+
54+ // Match implicit cast of -1 to unsigned
55+ auto ImplicitNegOneToUnsigned =
56+ implicitCastExpr (
57+ hasSourceExpression (ignoringParenImpCasts (anyOf (NegOneExpr, BitNotZero))),
58+ hasType (isUnsignedInteger ()));
59+
60+ // Match explicit cast to unsigned of either -1 or ~0
61+ auto ExplicitCastOfNegOrBitnot =
62+ explicitCastExpr (
63+ hasDestinationType (isUnsignedInteger ()),
64+ hasSourceExpression (ignoringParenImpCasts (anyOf (NegOneExpr, BitNotZero))));
65+
66+ // Match ~0 with unsigned type
67+ auto UnsignedBitNotZero =
68+ unaryOperator (
69+ hasOperatorName (" ~" ),
70+ hasUnaryOperand (ZeroLiteral),
71+ hasType (isUnsignedInteger ()));
72+
73+ auto UnsignedLiteralNegOne =
74+ integerLiteral (equals (-1 ), hasType (isUnsignedInteger ()));
75+
76+ // *** ADD THIS NEW MATCHER ***
77+ // Matches -1 or ~0 when they are a branch of a ternary operator
78+ // that is itself being implicitly cast to unsigned.
79+ auto TernaryBranch =
80+ expr (anyOf (NegOneExpr, BitNotZero),
81+ hasAncestor ( // <-- Use hasAncestor to look up the tree
82+ conditionalOperator (
83+ // Check that the conditional operator itself has an ancestor
84+ // which is the implicit cast to unsigned
85+ hasAncestor (implicitCastExpr (hasType (isUnsignedInteger ()))
86+ .bind (" outerCast" )))))
87+ .bind (" unsignedMaxExpr" );
88+
89+ // *** MODIFY THIS PART ***
90+ auto OldCombined =
91+ expr (anyOf (
92+ ExplicitCastOfNegOrBitnot,
93+ ImplicitNegOneToUnsigned,
94+ UnsignedBitNotZero,
95+ UnsignedLiteralNegOne
96+ )).bind (" unsignedMaxExpr" );
97+
98+ Finder->addMatcher (OldCombined, this );
99+ Finder->addMatcher (TernaryBranch, this ); // Add the new matcher
100+ }
101+
102+
103+ void NumericlimitmaxcheckCheck::check (const MatchFinder::MatchResult &Result) {
104+ const auto *E = Result.Nodes .getNodeAs <Expr>(" unsignedMaxExpr" );
105+ const auto *OuterCast = Result.Nodes .getNodeAs <CastExpr>(" outerCast" ); // Get the cast
106+
107+ if (!E)
108+ return ;
109+
110+ ASTContext &Ctx = *Result.Context ; // Get context *before* first use
111+
112+ QualType QT;
113+ if (OuterCast) {
114+ // This is our new ternary matcher. Get type from the *cast*.
115+ QT = OuterCast->getType ();
116+ } else {
117+ // This is the old logic. Get type from the bound expression.
118+ QT = E->getType ();
119+ }
120+
121+ if (E->getBeginLoc ().isInvalid () || E->getBeginLoc ().isMacroID ())
122+ return ;
123+
124+ const SourceManager &SM = Ctx.getSourceManager ();
125+
126+ // *** ADD THIS LOGIC BLOCK ***
127+ // This logic prevents double-reporting for the *old* matchers.
128+ // We skip it for the new ternary matcher (when OuterCast is not null)
129+ // because the ternary matcher binds the *inner* expression, and we
130+ // *do* want to report it.
131+ if (!OuterCast) {
132+ auto Parents = Ctx.getParents (*E); // This fixes the [-Wunused] warning
133+ if (!Parents.empty ()) {
134+ for (const auto &Parent : Parents) {
135+ // Check if parent is an explicit cast to unsigned
136+ if (const auto *ParentCast = Parent.get <ExplicitCastExpr>()) {
137+ if (ParentCast->getType ()->isUnsignedIntegerType ()) {
138+ // Skip this match - the cast itself will be (or was) reported
139+ return ;
140+ }
141+ }
142+ // Also check if parent is an implicit cast that's part of an explicit cast chain
143+ if (const auto *ImplicitCast = Parent.get <ImplicitCastExpr>()) {
144+ auto GrandParents = Ctx.getParents (*ImplicitCast);
145+ for (const auto &GP : GrandParents) {
146+ if (const auto *GPCast = GP.get <ExplicitCastExpr>()) {
147+ if (GPCast->getType ()->isUnsignedIntegerType ()) {
148+ return ;
149+ }
150+ }
151+ }
152+ }
153+ }
154+ }
155+ }
156+ // ... (rest of parent checking logic) ...
157+ // ...
158+ // Determine the unsigned destination type
159+ // QualType QT = E->getType(); // This line is moved up and modified
160+ if (QT.isNull () || !QT->isUnsignedIntegerType ())
161+ return ;
162+
163+ // Get a printable type string
164+ std::string TypeStr = QT.getUnqualifiedType ().getAsString ();
165+ if (const auto *Typedef = QT->getAs <TypedefType>()) {
166+ TypeStr = Typedef->getDecl ()->getName ().str ();
167+ }
168+
169+ // Get original source text for diagnostic message
170+ StringRef OriginalText =
171+ Lexer::getSourceText (CharSourceRange::getTokenRange (E->getSourceRange ()),
172+ SM, getLangOpts ());
173+
174+ // Build replacement text
175+ std::string Replacement = " std::numeric_limits<" + TypeStr + " >::max()" ;
176+
177+ // Create diagnostic
178+ auto Diag = diag (E->getBeginLoc (),
179+ " use 'std::numeric_limits<%0>::max()' instead of '%1'" )
180+ << TypeStr << OriginalText;
181+
182+ // Add fix-it hints
183+ Diag << FixItHint::CreateReplacement (E->getSourceRange (), Replacement);
184+
185+ // Add include for <limits>
186+ FileID FID = SM.getFileID (E->getBeginLoc ());
187+ if (auto IncludeHint = Inserter.createIncludeInsertion (FID, " <limits>" )) {
188+ Diag << *IncludeHint;
189+ }
190+ }
191+
192+ } // namespace clang::tidy::readability
0 commit comments