llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-clang-tools-extra Author: Helmut Januschka (hjanuschka) <details> <summary>Changes</summary> Add new clang-tidy check that finds function parameter pairs of (pointer, size) that could be replaced with `std::span`. Passing a pointer and a separate size is a common C-style pattern that `std::span` (C++20) was designed to replace. Using `std::span` bundles the pointer and size together, preventing mismatches and improving readability. ```cpp // Before void process(int *Data, int Size); // After void process(std::span<int> Data); ``` The check uses a name-based heuristic for size parameters: `size`, `len`, `length`, `count`, `n`, `num`, or names ending in `_size`, `_len`, etc. Cases intentionally **not** flagged: - `void*` or function pointers - Non-integer size parameter types - Size parameter names not suggesting a size - Virtual methods - Unnamed size parameters Requires C++20. No fix-its are provided since changing the signature requires updating all call sites. --- Full diff: https://github.com/llvm/llvm-project/pull/182085.diff 8 Files Affected: - (modified) clang-tools-extra/clang-tidy/modernize/CMakeLists.txt (+1) - (modified) clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp (+3) - (added) clang-tools-extra/clang-tidy/modernize/PointerToSpanCheck.cpp (+154) - (added) clang-tools-extra/clang-tidy/modernize/PointerToSpanCheck.h (+34) - (modified) clang-tools-extra/docs/ReleaseNotes.rst (+6) - (modified) clang-tools-extra/docs/clang-tidy/checks/list.rst (+1) - (added) clang-tools-extra/docs/clang-tidy/checks/modernize/pointer-to-span.rst (+38) - (added) clang-tools-extra/test/clang-tidy/checkers/modernize/pointer-to-span.cpp (+41) ``````````diff diff --git a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt index cc4cc7a02b594..7679c4e6ad638 100644 --- a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt @@ -22,6 +22,7 @@ add_clang_library(clangTidyModernizeModule STATIC MinMaxUseInitializerListCheck.cpp ModernizeTidyModule.cpp PassByValueCheck.cpp + PointerToSpanCheck.cpp RawStringLiteralCheck.cpp RedundantVoidArgCheck.cpp ReplaceAutoPtrCheck.cpp diff --git a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp index fcb860d8c5298..491a9fdf59ce7 100644 --- a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp @@ -22,6 +22,7 @@ #include "MakeUniqueCheck.h" #include "MinMaxUseInitializerListCheck.h" #include "PassByValueCheck.h" +#include "PointerToSpanCheck.h" #include "RawStringLiteralCheck.h" #include "RedundantVoidArgCheck.h" #include "ReplaceAutoPtrCheck.h" @@ -100,6 +101,8 @@ class ModernizeModule : public ClangTidyModule { CheckFactories.registerCheck<UseStdNumbersCheck>( "modernize-use-std-numbers"); CheckFactories.registerCheck<UseStdPrintCheck>("modernize-use-std-print"); + CheckFactories.registerCheck<PointerToSpanCheck>( + "modernize-pointer-to-span"); CheckFactories.registerCheck<RawStringLiteralCheck>( "modernize-raw-string-literal"); CheckFactories.registerCheck<RedundantVoidArgCheck>( diff --git a/clang-tools-extra/clang-tidy/modernize/PointerToSpanCheck.cpp b/clang-tools-extra/clang-tidy/modernize/PointerToSpanCheck.cpp new file mode 100644 index 0000000000000..b38a40976d768 --- /dev/null +++ b/clang-tools-extra/clang-tidy/modernize/PointerToSpanCheck.cpp @@ -0,0 +1,154 @@ +//===----------------------------------------------------------------------===// +// +// 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 "PointerToSpanCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::modernize { + +/// Return true if \p T is an unsigned integer type commonly used for sizes +/// (size_t, unsigned, unsigned long, etc.). +static bool isSizeType(QualType T) { + T = T.getCanonicalType(); + if (const auto *BT = T->getAs<BuiltinType>()) { + switch (BT->getKind()) { + case BuiltinType::UInt: + case BuiltinType::ULong: + case BuiltinType::ULongLong: + case BuiltinType::Int: + case BuiltinType::Long: + case BuiltinType::LongLong: + return true; + default: + return false; + } + } + return false; +} + +/// Return true if the parameter name suggests it represents a size or count. +static bool isSizeName(StringRef Name) { + const StringRef Lower = Name.lower(); + return Lower == "size" || Lower == "len" || Lower == "length" || + Lower == "count" || Lower == "n" || Lower == "num" || + Lower == "nelems" || Lower == "nelem" || Lower == "num_elements" || + Lower == "num_elems" || Lower == "sz" || Lower == "cnt" || + Lower.ends_with("_size") || Lower.ends_with("_len") || + Lower.ends_with("_length") || Lower.ends_with("_count") || + Lower.ends_with("size") || Lower.ends_with("len") || + Lower.ends_with("count") || Lower.ends_with("num"); +} + +void PointerToSpanCheck::registerMatchers(MatchFinder *Finder) { + // Match function declarations (not just definitions) with at least 2 params. + Finder->addMatcher( + functionDecl( + parameterCountIs(2), + hasParameter(0, parmVarDecl(hasType(pointerType())).bind("ptr")), + hasParameter(1, parmVarDecl().bind("size")), + unless(isImplicit()), + unless(isDeleted())) + .bind("func"), + this); + + // Also match functions with more params -- look for consecutive ptr+size. + Finder->addMatcher( + functionDecl( + unless(parameterCountIs(2)), + unless(isImplicit()), + unless(isDeleted())) + .bind("funcN"), + this); +} + +void PointerToSpanCheck::check(const MatchFinder::MatchResult &Result) { + const auto &SM = *Result.SourceManager; + + // Handle the exact 2-parameter case. + if (const auto *Func = Result.Nodes.getNodeAs<FunctionDecl>("func")) { + const auto *PtrParam = Result.Nodes.getNodeAs<ParmVarDecl>("ptr"); + const auto *SizeParam = Result.Nodes.getNodeAs<ParmVarDecl>("size"); + + if (!PtrParam || !SizeParam) + return; + if (SM.isInSystemHeader(Func->getLocation())) + return; + + // Skip virtual methods. + if (const auto *MD = dyn_cast<CXXMethodDecl>(Func)) + if (MD->isVirtual()) + return; + + // Skip main. + if (Func->isMain()) + return; + + // Pointer must not be void* or function pointer. + const auto *PT = PtrParam->getType()->getAs<PointerType>(); + if (!PT || PT->getPointeeType()->isVoidType() || + PT->getPointeeType()->isFunctionType()) + return; + + // Size param must be an integer type. + if (!isSizeType(SizeParam->getType())) + return; + + // Heuristic: size param name should suggest a size. + // If unnamed, skip -- cannot verify intent. + if (!SizeParam->getIdentifier() || !isSizeName(SizeParam->getName())) + return; + + diag(PtrParam->getLocation(), + "pointer and size parameters can be replaced with 'std::span'") + << PtrParam->getSourceRange(); + diag(SizeParam->getLocation(), + "size parameter declared here", DiagnosticIDs::Note) + << SizeParam->getSourceRange(); + return; + } + + // Handle N-parameter functions: scan for consecutive (ptr, size) pairs. + const auto *Func = Result.Nodes.getNodeAs<FunctionDecl>("funcN"); + if (!Func) + return; + if (SM.isInSystemHeader(Func->getLocation())) + return; + if (const auto *MD = dyn_cast<CXXMethodDecl>(Func)) + if (MD->isVirtual()) + return; + if (Func->isMain()) + return; + + for (unsigned I = 0; I + 1 < Func->getNumParams(); ++I) { + const ParmVarDecl *PtrParam = Func->getParamDecl(I); + const ParmVarDecl *SizeParam = Func->getParamDecl(I + 1); + + const auto *PT = PtrParam->getType()->getAs<PointerType>(); + if (!PT || PT->getPointeeType()->isVoidType() || + PT->getPointeeType()->isFunctionType()) + continue; + + if (!isSizeType(SizeParam->getType())) + continue; + + if (!SizeParam->getIdentifier() || !isSizeName(SizeParam->getName())) + continue; + + diag(PtrParam->getLocation(), + "pointer and size parameters can be replaced with 'std::span'") + << PtrParam->getSourceRange(); + diag(SizeParam->getLocation(), + "size parameter declared here", DiagnosticIDs::Note) + << SizeParam->getSourceRange(); + } +} + +} // namespace clang::tidy::modernize diff --git a/clang-tools-extra/clang-tidy/modernize/PointerToSpanCheck.h b/clang-tools-extra/clang-tidy/modernize/PointerToSpanCheck.h new file mode 100644 index 0000000000000..c26cb885d79a0 --- /dev/null +++ b/clang-tools-extra/clang-tidy/modernize/PointerToSpanCheck.h @@ -0,0 +1,34 @@ +//===----------------------------------------------------------------------===// +// +// 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_POINTERTOSPANCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_POINTERTOSPANCHECK_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::modernize { + +/// Finds function parameter pairs of (pointer, size) that could be replaced +/// with std::span. +/// +/// For the user-facing documentation see: +/// https://clang.llvm.org/extra/clang-tidy/checks/modernize/pointer-to-span.html +class PointerToSpanCheck : public ClangTidyCheck { +public: + PointerToSpanCheck(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.CPlusPlus20; + } +}; + +} // namespace clang::tidy::modernize + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_POINTERTOSPANCHECK_H diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst index 68bab88146241..b0e4c356bcb6a 100644 --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -121,6 +121,12 @@ 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-pointer-to-span + <clang-tidy/checks/modernize/pointer-to-span>` check. + + Finds function parameter pairs of (pointer, size) that could be + replaced with ``std::span``. + - 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..1c1f32ed27744 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/list.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -302,6 +302,7 @@ Clang-Tidy Checks :doc:`modernize-make-unique <modernize/make-unique>`, "Yes" :doc:`modernize-min-max-use-initializer-list <modernize/min-max-use-initializer-list>`, "Yes" :doc:`modernize-pass-by-value <modernize/pass-by-value>`, "Yes" + :doc:`modernize-pointer-to-span <modernize/pointer-to-span>`, :doc:`modernize-raw-string-literal <modernize/raw-string-literal>`, "Yes" :doc:`modernize-redundant-void-arg <modernize/redundant-void-arg>`, "Yes" :doc:`modernize-replace-auto-ptr <modernize/replace-auto-ptr>`, "Yes" diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/pointer-to-span.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize/pointer-to-span.rst new file mode 100644 index 0000000000000..e16fecd4d3f62 --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/pointer-to-span.rst @@ -0,0 +1,38 @@ +.. title:: clang-tidy - modernize-pointer-to-span + +modernize-pointer-to-span +========================= + +Finds function parameter pairs of (pointer, size) that could be +replaced with ``std::span``. + +Passing a pointer and a separate size is a common C-style pattern +that ``std::span`` (C++20) was designed to replace. Using +``std::span`` bundles the pointer and size together, preventing +mismatches and improving readability. + +.. code-block:: c++ + + // Before + void process(int *Data, int Size); + + // After + void process(std::span<int> Data); + +The check uses a name-based heuristic to identify size parameters. +Names like ``size``, ``len``, ``length``, ``count``, ``n``, ``num``, +or names ending in ``_size``, ``_len``, etc. are recognized. + +The check will not flag a parameter pair if: + +- the pointer is ``void*`` or a function pointer, +- the size parameter is not an integer type, +- the size parameter name does not suggest a size, +- the function is a virtual method, or +- the size parameter is unnamed. + +.. note:: + + This check requires C++20 or later and does not provide + fix-its because changing the signature requires updating all + call sites. diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/pointer-to-span.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/pointer-to-span.cpp new file mode 100644 index 0000000000000..cc037261ce40d --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/pointer-to-span.cpp @@ -0,0 +1,41 @@ +// RUN: %check_clang_tidy -std=c++20-or-later %s modernize-pointer-to-span %t + +// Positive: basic (pointer, size) pair. +void process(int *Data, int Size); +// CHECK-MESSAGES: :[[@LINE-1]]:19: warning: pointer and size parameters can be replaced with 'std::span' + +// Positive: const pointer with unsigned long length. +void readBuf(const char *Buf, unsigned long Len); +// CHECK-MESSAGES: :[[@LINE-1]]:26: warning: pointer and size parameters can be replaced with 'std::span' + +// Positive: size parameter named "count". +void fill(float *Arr, int Count); +// CHECK-MESSAGES: :[[@LINE-1]]:18: warning: pointer and size parameters can be replaced with 'std::span' + +// Positive: size parameter named "n". +void copy(int *Dst, int N); +// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: pointer and size parameters can be replaced with 'std::span' + +// Positive: ptr+size in the middle of more params. +void multi(int Id, double *Data, unsigned Len, bool Flag); +// CHECK-MESSAGES: :[[@LINE-1]]:28: warning: pointer and size parameters can be replaced with 'std::span' + +// Negative: second param name does not suggest a size. +void noMatch(int *Data, int Flags); + +// Negative: void pointer. +void voidPtr(void *Data, int Size); + +// Negative: function pointer. +void funcPtr(void (*Fn)(int), int Size); + +// Negative: second param is not an integer. +void wrongType(int *Data, double Size); + +// Negative: virtual method. +struct Base { + virtual void vmethod(int *Data, int Size); +}; + +// Negative: unnamed size parameter (cannot verify intent). +void unnamed(int *Data, int); `````````` </details> https://github.com/llvm/llvm-project/pull/182085 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
