https://github.com/hjanuschka updated https://github.com/llvm/llvm-project/pull/182023
>From 2ddc82162ae926efdafef9746ff7ca07b6a40af6 Mon Sep 17 00:00:00 2001 From: Helmut Januschka <[email protected]> Date: Wed, 18 Feb 2026 14:30:08 +0100 Subject: [PATCH 1/2] [clang-tidy] Add modernize-use-size-type check Suggests changing signed integer variables to size_t when they are initialized from unsigned sources and only used in unsigned-compatible contexts. --- .../clang-tidy/modernize/CMakeLists.txt | 1 + .../modernize/ModernizeTidyModule.cpp | 2 + .../clang-tidy/modernize/UseSizeTypeCheck.cpp | 158 ++++++++++++++++++ .../clang-tidy/modernize/UseSizeTypeCheck.h | 36 ++++ clang-tools-extra/docs/ReleaseNotes.rst | 7 + .../docs/clang-tidy/checks/list.rst | 1 + .../checks/modernize/use-size-type.rst | 38 +++++ .../checkers/modernize/use-size-type.cpp | 78 +++++++++ 8 files changed, 321 insertions(+) create mode 100644 clang-tools-extra/clang-tidy/modernize/UseSizeTypeCheck.cpp create mode 100644 clang-tools-extra/clang-tidy/modernize/UseSizeTypeCheck.h create mode 100644 clang-tools-extra/docs/clang-tidy/checks/modernize/use-size-type.rst create mode 100644 clang-tools-extra/test/clang-tidy/checkers/modernize/use-size-type.cpp diff --git a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt index cc4cc7a02b594..22ebd20a66211 100644 --- a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt @@ -46,6 +46,7 @@ add_clang_library(clangTidyModernizeModule STATIC UseOverrideCheck.cpp UseRangesCheck.cpp UseScopedLockCheck.cpp + UseSizeTypeCheck.cpp UseStartsEndsWithCheck.cpp UseStdFormatCheck.cpp UseStdNumbersCheck.cpp diff --git a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp index fcb860d8c5298..38e539adaeec8 100644 --- a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp @@ -46,6 +46,7 @@ #include "UseOverrideCheck.h" #include "UseRangesCheck.h" #include "UseScopedLockCheck.h" +#include "UseSizeTypeCheck.h" #include "UseStartsEndsWithCheck.h" #include "UseStdFormatCheck.h" #include "UseStdNumbersCheck.h" @@ -94,6 +95,7 @@ class ModernizeModule : public ClangTidyModule { CheckFactories.registerCheck<UseRangesCheck>("modernize-use-ranges"); CheckFactories.registerCheck<UseScopedLockCheck>( "modernize-use-scoped-lock"); + CheckFactories.registerCheck<UseSizeTypeCheck>("modernize-use-size-type"); CheckFactories.registerCheck<UseStartsEndsWithCheck>( "modernize-use-starts-ends-with"); CheckFactories.registerCheck<UseStdFormatCheck>("modernize-use-std-format"); diff --git a/clang-tools-extra/clang-tidy/modernize/UseSizeTypeCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseSizeTypeCheck.cpp new file mode 100644 index 0000000000000..036fdfdc94e4c --- /dev/null +++ b/clang-tools-extra/clang-tidy/modernize/UseSizeTypeCheck.cpp @@ -0,0 +1,158 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "UseSizeTypeCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::modernize { + +void UseSizeTypeCheck::registerMatchers(MatchFinder *Finder) { + // Match local variables with signed integer type initialized from an + // unsigned expression (via implicit cast). + Finder->addMatcher( + varDecl(hasLocalStorage(), + hasType(qualType(hasCanonicalType(isSignedInteger()))), + hasInitializer(ignoringImplicit(expr( + hasType(qualType(hasCanonicalType(isUnsignedInteger())))))), + unless(isConstexpr()), + hasParent(declStmt(hasParent(compoundStmt().bind("scope"))))) + .bind("var"), + this); +} + +/// Return true if every use of \p VD within \p Scope is in a context that +/// expects or is compatible with an unsigned / size_t type. +static bool allUsesAreUnsignedCompatible(const VarDecl *VD, + const CompoundStmt *Scope, + ASTContext &Ctx) { + auto Refs = + match(findAll(declRefExpr(to(varDecl(equalsNode(VD)))).bind("ref")), + *Scope, Ctx); + + if (Refs.empty()) + return false; + + for (const auto &Ref : Refs) { + const auto *DRE = Ref.getNodeAs<DeclRefExpr>("ref"); + if (!DRE) + return false; + + const auto Parents = Ctx.getParents(*DRE); + if (Parents.empty()) + return false; + + // Walk up through implicit casts to find the "real" parent context. + // The DeclRefExpr is typically wrapped in LValueToRValue and + // IntegralCast implicit casts. + const Expr *Current = DRE; + DynTypedNodeList CurrentParents = Parents; + while (CurrentParents.size() == 1) { + if (const auto *ICE = CurrentParents[0].get<ImplicitCastExpr>()) { + Current = ICE; + CurrentParents = Ctx.getParents(*ICE); + continue; + } + break; + } + + bool UsageOk = false; + for (const auto &Parent : CurrentParents) { + // Used in binary comparison with unsigned operand. + if (const auto *BO = Parent.get<BinaryOperator>()) { + if (BO->isComparisonOp()) { + UsageOk = true; + break; + } + // Also accept arithmetic where the result feeds into an + // unsigned context (but not standalone). + } + + // Used as function argument (CallExpr or CXXMemberCallExpr). + if (const auto *CE = Parent.get<CallExpr>()) { + for (unsigned I = 0; I < CE->getNumArgs(); ++I) { + if (CE->getArg(I)->IgnoreParenImpCasts() == DRE) { + if (const auto *FD = CE->getDirectCallee()) { + if (I < FD->getNumParams()) { + const QualType PT = + FD->getParamDecl(I)->getType().getCanonicalType(); + if (PT->isUnsignedIntegerType()) + UsageOk = true; + } + } + break; + } + } + if (UsageOk) + break; + } + + // Used as array subscript index. + if (const auto *ASE = Parent.get<ArraySubscriptExpr>()) { + if (ASE->getIdx()->IgnoreParenImpCasts() == DRE) { + UsageOk = true; + break; + } + } + + // Used in operator[] (CXXOperatorCallExpr). + if (const auto *OCE = Parent.get<CXXOperatorCallExpr>()) { + if (OCE->getOperator() == OO_Subscript && OCE->getNumArgs() > 1 && + OCE->getArg(1)->IgnoreParenImpCasts() == DRE) { + UsageOk = true; + break; + } + if (UsageOk) + break; + } + } + + if (!UsageOk) + return false; + } + + return true; +} + +void UseSizeTypeCheck::check(const MatchFinder::MatchResult &Result) { + const auto *VD = Result.Nodes.getNodeAs<VarDecl>("var"); + const auto *Scope = Result.Nodes.getNodeAs<CompoundStmt>("scope"); + if (!VD || !Scope) + return; + + // Skip dependent types. + if (VD->getType()->isDependentType()) + return; + + // Skip macros. + if (VD->getLocation().isMacroID()) + return; + + // Check all uses are unsigned-compatible. + if (!allUsesAreUnsignedCompatible(VD, Scope, *Result.Context)) + return; + + // Get the type specifier source range to replace. + const SourceLocation TypeStart = VD->getTypeSpecStartLoc(); + const SourceLocation TypeEnd = VD->getTypeSpecEndLoc(); + if (TypeStart.isInvalid() || TypeEnd.isInvalid()) + return; + if (TypeStart.isMacroID() || TypeEnd.isMacroID()) + return; + + diag(VD->getLocation(), + "variable %0 is of signed type %1 but is initialized from and " + "used as an unsigned value; consider using 'size_t'") + << VD << VD->getType() + << FixItHint::CreateReplacement( + CharSourceRange::getTokenRange(TypeStart, TypeEnd), "size_t"); +} + +} // namespace clang::tidy::modernize diff --git a/clang-tools-extra/clang-tidy/modernize/UseSizeTypeCheck.h b/clang-tools-extra/clang-tidy/modernize/UseSizeTypeCheck.h new file mode 100644 index 0000000000000..f692e55c77b34 --- /dev/null +++ b/clang-tools-extra/clang-tidy/modernize/UseSizeTypeCheck.h @@ -0,0 +1,36 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USESIZETYPECHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USESIZETYPECHECK_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::modernize { + +/// Finds local variables declared as signed integer types that are initialized +/// from an unsigned/``size_t`` source (e.g. ``container.size()``) and only used +/// in contexts expecting unsigned types, and suggests changing the type to +/// ``size_t``. +/// +/// For the user-facing documentation see: +/// https://clang.llvm.org/extra/clang-tidy/checks/modernize/use-size-type.html +class UseSizeTypeCheck : public ClangTidyCheck { +public: + UseSizeTypeCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus11; + } +}; + +} // namespace clang::tidy::modernize + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USESIZETYPECHECK_H diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst index 68bab88146241..0dc391644e88e 100644 --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -121,6 +121,13 @@ New checks ``llvm::to_vector(llvm::make_filter_range(...))`` that can be replaced with ``llvm::map_to_vector`` and ``llvm::filter_to_vector``. +- New :doc:`modernize-use-size-type + <clang-tidy/checks/modernize/use-size-type>` check. + + Finds local variables declared as signed integer types initialized + from unsigned sources and only used in unsigned contexts, and + suggests changing the type to ``size_t``. + - New :doc:`modernize-use-string-view <clang-tidy/checks/modernize/use-string-view>` check. diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst index c475870ed7b31..a4138f3c282f3 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/list.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -326,6 +326,7 @@ Clang-Tidy Checks :doc:`modernize-use-override <modernize/use-override>`, "Yes" :doc:`modernize-use-ranges <modernize/use-ranges>`, "Yes" :doc:`modernize-use-scoped-lock <modernize/use-scoped-lock>`, "Yes" + :doc:`modernize-use-size-type <modernize/use-size-type>`, "Yes" :doc:`modernize-use-starts-ends-with <modernize/use-starts-ends-with>`, "Yes" :doc:`modernize-use-std-format <modernize/use-std-format>`, "Yes" :doc:`modernize-use-std-numbers <modernize/use-std-numbers>`, "Yes" diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-size-type.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-size-type.rst new file mode 100644 index 0000000000000..9fc7b780ae016 --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-size-type.rst @@ -0,0 +1,38 @@ +.. title:: clang-tidy - modernize-use-size-type + +modernize-use-size-type +======================= + +Finds local variables declared as signed integer types that are +initialized from an unsigned/``size_t`` source (e.g. +``container.size()``) and only used in contexts expecting unsigned +types, and suggests changing the type to ``size_t``. + +Storing a ``size_t`` value in a signed ``int`` can cause implicit +narrowing conversions and sign-comparison warnings. Using ``size_t`` +directly avoids these issues. + +For example: + +.. code-block:: c++ + + std::vector<int> v; + int n = v.size(); + v.resize(n); + + // transforms to: + + std::vector<int> v; + size_t n = v.size(); + v.resize(n); + +The check only triggers when all of the following are true: + +- The variable is a local, non-static variable. +- The variable has a signed integer type (e.g. ``int``). +- The initializer is an unsigned integer expression. +- Every use of the variable is in an unsigned-compatible context + (comparison, function argument expecting unsigned, array + subscript, or implicit cast to unsigned). +- The variable is not ``constexpr``. +- The declaration is not in a macro. diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-size-type.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-size-type.cpp new file mode 100644 index 0000000000000..da23ba1b630af --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-size-type.cpp @@ -0,0 +1,78 @@ +// RUN: %check_clang_tidy -std=c++17-or-later %s modernize-use-size-type %t + +using size_t = decltype(sizeof(0)); + +namespace std { +template <typename T> +struct vector { + size_t size() const; + T &operator[](size_t); + const T &operator[](size_t) const; + void resize(size_t); +}; + +template <typename T> +struct basic_string { + size_t size() const; + size_t length() const; + char &operator[](size_t); +}; +using string = basic_string<char>; +} // namespace std + +// Positive: int from .size(), used in comparison and subscript +void test_size_comparison(std::vector<int> &v) { + int n = v.size(); + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable 'n' is of signed type 'int' but is initialized from and used as an unsigned value; consider using 'size_t' [modernize-use-size-type] + // CHECK-FIXES: size_t n = v.size(); + for (int i = 0; i < n; ++i) { + } +} + +// Positive: int from .size(), used in resize() +void test_size_resize(std::vector<int> &v) { + int s = v.size(); + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable 's' is of signed type 'int' + // CHECK-FIXES: size_t s = v.size(); + v.resize(s); +} + +// Positive: int from .length(), used in subscript +void test_length_subscript(std::string &s) { + int len = s.length(); + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: variable 'len' is of signed type 'int' + // CHECK-FIXES: size_t len = s.length(); + for (int i = 0; i < len; ++i) { + char c = s[i]; + } +} + +// Negative: variable used in signed context (subtraction producing +// potentially negative value) +void test_signed_arithmetic(std::vector<int> &v) { + int n = v.size(); + int x = n - 10; // Used in signed arithmetic +} + +// Negative: variable not initialized from unsigned +void test_signed_init() { + int n = 42; + size_t s = n; +} + +// Negative: variable is already unsigned +void test_already_unsigned(std::vector<int> &v) { + size_t n = v.size(); +} + +// Negative: variable used in a context expecting signed +void negative_signed_param(int x); +void test_signed_param(std::vector<int> &v) { + int n = v.size(); + negative_signed_param(n); +} + +// Negative: unused variable (no uses to check) +void test_unused(std::vector<int> &v) { + int n = v.size(); +} >From 0ae18dc70465ce0299f7f8585d943ba07f768fad Mon Sep 17 00:00:00 2001 From: Helmut Januschka <[email protected]> Date: Wed, 18 Feb 2026 17:25:58 +0100 Subject: [PATCH 2/2] Update clang-tools-extra/docs/clang-tidy/checks/modernize/use-size-type.rst Co-authored-by: EugeneZelenko <[email protected]> --- .../clang-tidy/modernize/UseSizeTypeCheck.cpp | 2 -- clang-tools-extra/docs/ReleaseNotes.rst | 7 ++++--- .../docs/clang-tidy/checks/modernize/use-size-type.rst | 6 +++--- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/clang-tools-extra/clang-tidy/modernize/UseSizeTypeCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseSizeTypeCheck.cpp index 036fdfdc94e4c..67cfe59083488 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseSizeTypeCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseSizeTypeCheck.cpp @@ -52,11 +52,9 @@ static bool allUsesAreUnsignedCompatible(const VarDecl *VD, // Walk up through implicit casts to find the "real" parent context. // The DeclRefExpr is typically wrapped in LValueToRValue and // IntegralCast implicit casts. - const Expr *Current = DRE; DynTypedNodeList CurrentParents = Parents; while (CurrentParents.size() == 1) { if (const auto *ICE = CurrentParents[0].get<ImplicitCastExpr>()) { - Current = ICE; CurrentParents = Ctx.getParents(*ICE); continue; } diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst index 0dc391644e88e..dd5e95057b871 100644 --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -124,9 +124,10 @@ New checks - New :doc:`modernize-use-size-type <clang-tidy/checks/modernize/use-size-type>` check. - Finds local variables declared as signed integer types initialized - from unsigned sources and only used in unsigned contexts, and - suggests changing the type to ``size_t``. + Finds local variables declared as signed integer types that are + initialized from an unsigned/``size_t`` source (e.g. ``container.size()``) + and only used in contexts expecting unsigned types, and suggests + changing the type to ``size_t``. - New :doc:`modernize-use-string-view <clang-tidy/checks/modernize/use-string-view>` check. diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-size-type.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-size-type.rst index 9fc7b780ae016..199dcbcac3aad 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-size-type.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-size-type.rst @@ -31,8 +31,8 @@ The check only triggers when all of the following are true: - The variable is a local, non-static variable. - The variable has a signed integer type (e.g. ``int``). - The initializer is an unsigned integer expression. -- Every use of the variable is in an unsigned-compatible context - (comparison, function argument expecting unsigned, array - subscript, or implicit cast to unsigned). +- Every use of the variable is in an unsigned-compatible context (comparison, + function argument expecting unsigned, array subscript, or implicit cast to + unsigned). - The variable is not ``constexpr``. - The declaration is not in a macro. _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
