https://github.com/unterumarmung updated https://github.com/llvm/llvm-project/pull/189962
>From 87b7fe6a51eb5c03241e4d6151506e7ae6e89472 Mon Sep 17 00:00:00 2001 From: Daniil Dudkin <[email protected]> Date: Wed, 1 Apr 2026 16:37:47 +0300 Subject: [PATCH 01/10] [clang-tidy] Add `modernize-use-bit-cast` check --- .../clang-tidy/modernize/CMakeLists.txt | 1 + .../modernize/ModernizeTidyModule.cpp | 2 + .../clang-tidy/modernize/UseBitCastCheck.cpp | 305 ++++++++++++++++++ .../clang-tidy/modernize/UseBitCastCheck.h | 44 +++ clang-tools-extra/docs/ReleaseNotes.rst | 6 + .../docs/clang-tidy/checks/list.rst | 1 + .../checks/modernize/use-bit-cast.rst | 64 ++++ .../checkers/modernize/use-bit-cast.cpp | 291 +++++++++++++++++ 8 files changed, 714 insertions(+) create mode 100644 clang-tools-extra/clang-tidy/modernize/UseBitCastCheck.cpp create mode 100644 clang-tools-extra/clang-tidy/modernize/UseBitCastCheck.h create mode 100644 clang-tools-extra/docs/clang-tidy/checks/modernize/use-bit-cast.rst create mode 100644 clang-tools-extra/test/clang-tidy/checkers/modernize/use-bit-cast.cpp diff --git a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt index 2c5c44db587fe..728a0b21613fd 100644 --- a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt @@ -32,6 +32,7 @@ add_clang_library(clangTidyModernizeModule STATIC TypeTraitsCheck.cpp UnaryStaticAssertCheck.cpp UseAutoCheck.cpp + UseBitCastCheck.cpp UseBoolLiteralsCheck.cpp UseConstraintsCheck.cpp UseDefaultMemberInitCheck.cpp diff --git a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp index cc13da7535bcb..6e823a558c299 100644 --- a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp @@ -32,6 +32,7 @@ #include "TypeTraitsCheck.h" #include "UnaryStaticAssertCheck.h" #include "UseAutoCheck.h" +#include "UseBitCastCheck.h" #include "UseBoolLiteralsCheck.h" #include "UseConstraintsCheck.h" #include "UseDefaultMemberInitCheck.h" @@ -88,6 +89,7 @@ class ModernizeModule : public ClangTidyModule { CheckFactories.registerCheck<MinMaxUseInitializerListCheck>( "modernize-min-max-use-initializer-list"); CheckFactories.registerCheck<PassByValueCheck>("modernize-pass-by-value"); + CheckFactories.registerCheck<UseBitCastCheck>("modernize-use-bit-cast"); CheckFactories.registerCheck<UseDesignatedInitializersCheck>( "modernize-use-designated-initializers"); CheckFactories.registerCheck<UseIntegerSignComparisonCheck>( diff --git a/clang-tools-extra/clang-tidy/modernize/UseBitCastCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseBitCastCheck.cpp new file mode 100644 index 0000000000000..bc6173a658890 --- /dev/null +++ b/clang-tools-extra/clang-tidy/modernize/UseBitCastCheck.cpp @@ -0,0 +1,305 @@ +//===----------------------------------------------------------------------===// +// +// 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 "UseBitCastCheck.h" +#include "../utils/Matchers.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Expr.h" +#include "clang/AST/ExprCXX.h" +#include "clang/AST/Type.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" +#include "llvm/ADT/STLExtras.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::modernize { + +static const Expr *stripMemcpyArgument(const Expr *ExprNode) { + ExprNode = ExprNode->IgnoreParenImpCasts(); + while (const auto *Cast = dyn_cast<ExplicitCastExpr>(ExprNode)) + ExprNode = Cast->getSubExpr()->IgnoreParenImpCasts(); + return ExprNode; +} + +static bool isSupportedMemcpyObjectExpr(const Expr *ExprNode) { + ExprNode = ExprNode->IgnoreParenImpCasts(); + + if (isa<DeclRefExpr>(ExprNode)) + return true; + + const auto *Member = dyn_cast<MemberExpr>(ExprNode); + if (!Member || !isa<FieldDecl>(Member->getMemberDecl())) + if (const auto *MemberPointer = dyn_cast<BinaryOperator>(ExprNode)) + if (MemberPointer->getOpcode() == BO_PtrMemD || + MemberPointer->getOpcode() == BO_PtrMemI) + return isSupportedMemcpyObjectExpr(MemberPointer->getLHS()); + + return Member && isSupportedMemcpyObjectExpr(Member->getBase()); +} + +static const Expr *extractMemcpyObjectExpr(const Expr *ExprNode) { + ExprNode = stripMemcpyArgument(ExprNode); + const auto *AddressOf = dyn_cast<UnaryOperator>(ExprNode); + if (!AddressOf || AddressOf->getOpcode() != UO_AddrOf) + return nullptr; + + const Expr *ObjectExpr = AddressOf->getSubExpr()->IgnoreParenImpCasts(); + return isSupportedMemcpyObjectExpr(ObjectExpr) ? ObjectExpr : nullptr; +} + +static bool isSupportedMemcpyArgType(QualType Type, const ASTContext &Context, + bool RequireMutable) { + if (Type.isNull()) + return false; + + const QualType CanonicalType = Type.getCanonicalType().getNonReferenceType(); + if (CanonicalType.isNull() || CanonicalType->isDependentType() || + CanonicalType->isIncompleteType() || + CanonicalType.isVolatileQualified() || + CanonicalType->isAnyPointerType() || CanonicalType->isArrayType() || + CanonicalType->isFunctionType()) + return false; + + if (RequireMutable) { + if (CanonicalType.isConstQualified()) + return false; + + if (const auto *Record = CanonicalType->getAsCXXRecordDecl()) + if (!Record->hasSimpleCopyAssignment() && + !Record->hasSimpleMoveAssignment()) + return false; + } + + return Type.getNonReferenceType().isTriviallyCopyableType(Context); +} + +static bool isSameUnqualifiedCanonicalType(QualType LHS, QualType RHS) { + return LHS.getCanonicalType().getUnqualifiedType() == + RHS.getCanonicalType().getUnqualifiedType(); +} + +static bool isMatchingSizeOfExpression(const Expr *SizeExpr, QualType SrcType, + QualType DstType, + const ASTContext &Context) { + const auto *UnaryExpr = + dyn_cast<UnaryExprOrTypeTraitExpr>(SizeExpr->IgnoreParenImpCasts()); + if (!UnaryExpr || UnaryExpr->getKind() != UETT_SizeOf || + SizeExpr->getBeginLoc().isMacroID()) + return false; + + const QualType SizeType = UnaryExpr->getTypeOfArgument(); + if (SizeType.isNull()) + return false; + + const QualType SizeCanonical = + SizeType.getCanonicalType().getUnqualifiedType(); + const QualType SrcCanonical = SrcType.getCanonicalType().getUnqualifiedType(); + const QualType DstCanonical = DstType.getCanonicalType().getUnqualifiedType(); + if (SizeCanonical != SrcCanonical && SizeCanonical != DstCanonical) + return false; + + return Context.getTypeSizeInChars(SrcCanonical) == + Context.getTypeSizeInChars(DstCanonical); +} + +static bool isStatementBody(const Stmt *Current, const Stmt *Parent) { + if (const auto *Block = dyn_cast<CompoundStmt>(Parent)) + return llvm::is_contained(Block->body(), Current); + + if (const auto *If = dyn_cast<IfStmt>(Parent)) + return If->getThen() == Current || If->getElse() == Current; + if (const auto *While = dyn_cast<WhileStmt>(Parent)) + return While->getBody() == Current; + if (const auto *Do = dyn_cast<DoStmt>(Parent)) + return Do->getBody() == Current; + if (const auto *For = dyn_cast<ForStmt>(Parent)) + return For->getBody() == Current; + if (const auto *RangeFor = dyn_cast<CXXForRangeStmt>(Parent)) + return RangeFor->getBody() == Current; + if (const auto *Label = dyn_cast<LabelStmt>(Parent)) + return Label->getSubStmt() == Current; + if (const auto *Case = dyn_cast<SwitchCase>(Parent)) + return Case->getSubStmt() == Current; + if (const auto *Attributed = dyn_cast<AttributedStmt>(Parent)) + return Attributed->getSubStmt() == Current; + + return false; +} + +namespace { + +// These states describe how to spell the replacement when only the memcpy call +// is replaced. An existing `(void)` cast is preserved by parenthesizing the +// assignment, while comma/discarded subexpressions need an injected `(void)`. +enum class MemcpyReplacementForm { + None, + StatementBody, + PreserveOuterVoidCast, + InjectVoidCast, +}; + +} // namespace + +static MemcpyReplacementForm getMemcpyReplacementForm(const Expr *ExprNode, + ASTContext &Context) { + const Stmt *Current = ExprNode; + MemcpyReplacementForm Kind = MemcpyReplacementForm::StatementBody; + + while (true) { + auto Parents = Context.getParents(*Current); + if (Parents.size() != 1) + return MemcpyReplacementForm::None; + + if (const auto *ParentExpr = Parents[0].get<Expr>()) { + if (isa<ExprWithCleanups, ImplicitCastExpr, MaterializeTemporaryExpr, + CXXBindTemporaryExpr, ParenExpr>(ParentExpr)) { + Current = ParentExpr; + continue; + } + + if (const auto *Cast = dyn_cast<CastExpr>(ParentExpr)) + if (Cast->getCastKind() == CK_ToVoid) + return MemcpyReplacementForm::PreserveOuterVoidCast; + + if (const auto *Comma = dyn_cast<BinaryOperator>(ParentExpr)) { + if (Comma->getOpcode() != BO_Comma) + return MemcpyReplacementForm::None; + if (Comma->getLHS() == Current) + return MemcpyReplacementForm::InjectVoidCast; + if (Comma->getRHS() == Current) { + Current = Comma; + Kind = MemcpyReplacementForm::InjectVoidCast; + continue; + } + } + + return MemcpyReplacementForm::None; + } + + const auto *ParentStmt = Parents[0].get<Stmt>(); + if (!ParentStmt || !isStatementBody(Current, ParentStmt)) + return MemcpyReplacementForm::None; + return Kind; + } +} + +namespace { + +AST_MATCHER(CallExpr, isDiscardedValueContext) { + return getMemcpyReplacementForm(&Node, Finder->getASTContext()) != + MemcpyReplacementForm::None; +} + +AST_MATCHER(CallExpr, isBitCastMemcpyCandidate) { + if (Node.getNumArgs() != 3 || Node.getBeginLoc().isMacroID()) + return false; + + const auto *DstExpr = extractMemcpyObjectExpr(Node.getArg(0)); + const auto *SrcExpr = extractMemcpyObjectExpr(Node.getArg(1)); + if (!DstExpr || !SrcExpr || DstExpr->getBeginLoc().isMacroID() || + SrcExpr->getBeginLoc().isMacroID()) + return false; + + const auto &Context = Finder->getASTContext(); + const QualType DstType = DstExpr->getType().getNonReferenceType(); + const QualType SrcType = SrcExpr->getType().getNonReferenceType(); + + return isSupportedMemcpyArgType(DstType, Context, /*RequireMutable=*/true) && + isSupportedMemcpyArgType(SrcType, Context, + /*RequireMutable=*/false) && + !isSameUnqualifiedCanonicalType(SrcType, DstType) && + isMatchingSizeOfExpression(Node.getArg(2), SrcType, DstType, Context); +} + +} // namespace + +static StringRef getSourceText(const Expr *ExprNode, const SourceManager &SM, + const LangOptions &LangOpts) { + return Lexer::getSourceText( + CharSourceRange::getTokenRange(ExprNode->getSourceRange()), SM, LangOpts); +} + +UseBitCastCheck::UseBitCastCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + IncludeInserter(Options.getLocalOrGlobal("IncludeStyle", + utils::IncludeSorter::IS_LLVM), + areDiagsSelfContained()) {} + +void UseBitCastCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "IncludeStyle", IncludeInserter.getStyle()); +} + +void UseBitCastCheck::registerPPCallbacks(const SourceManager &SM, + Preprocessor *PP, + Preprocessor *ModuleExpanderPP) { + IncludeInserter.registerPreprocessor(PP); +} + +void UseBitCastCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + callExpr(callee(functionDecl(hasName("::memcpy"))), + isDiscardedValueContext(), unless(isInTemplateInstantiation()), + unless(hasAncestor(expr(matchers::hasUnevaluatedContext()))), + isBitCastMemcpyCandidate()) + .bind("memcpy"), + this); +} + +void UseBitCastCheck::check(const MatchFinder::MatchResult &Result) { + const auto *MemcpyCall = Result.Nodes.getNodeAs<CallExpr>("memcpy"); + if (!MemcpyCall) + return; + + const auto *DstExpr = extractMemcpyObjectExpr(MemcpyCall->getArg(0)); + const auto *SrcExpr = extractMemcpyObjectExpr(MemcpyCall->getArg(1)); + if (!DstExpr || !SrcExpr) + return; + + const SourceManager &SM = *Result.SourceManager; + const LangOptions &LangOpts = getLangOpts(); + StringRef DstText = getSourceText(DstExpr, SM, LangOpts); + StringRef SrcText = getSourceText(SrcExpr, SM, LangOpts); + if (DstText.empty() || SrcText.empty()) + return; + + const MemcpyReplacementForm ReplacementForm = + getMemcpyReplacementForm(MemcpyCall, *Result.Context); + if (ReplacementForm == MemcpyReplacementForm::None) + return; + + const PrintingPolicy Policy(LangOpts); + const QualType DstType = + DstExpr->getType().getNonReferenceType().getUnqualifiedType(); + const std::string Assignment = std::string(DstText) + " = std::bit_cast<" + + DstType.getAsString(Policy) + ">(" + + std::string(SrcText) + ")"; + std::string Replacement = Assignment; + switch (ReplacementForm) { + case MemcpyReplacementForm::StatementBody: + break; + case MemcpyReplacementForm::PreserveOuterVoidCast: + Replacement = "(" + Assignment + ")"; + break; + case MemcpyReplacementForm::InjectVoidCast: + Replacement = "(void)(" + Assignment + ")"; + break; + case MemcpyReplacementForm::None: + return; + } + + const DiagnosticBuilder Diag = + diag(MemcpyCall->getBeginLoc(), + "use 'std::bit_cast' instead of 'memcpy' for type punning"); + Diag << FixItHint::CreateReplacement(MemcpyCall->getSourceRange(), + Replacement); + Diag << IncludeInserter.createIncludeInsertion( + SM.getFileID(MemcpyCall->getBeginLoc()), "<bit>"); +} + +} // namespace clang::tidy::modernize diff --git a/clang-tools-extra/clang-tidy/modernize/UseBitCastCheck.h b/clang-tools-extra/clang-tidy/modernize/UseBitCastCheck.h new file mode 100644 index 0000000000000..c4672a7321d36 --- /dev/null +++ b/clang-tools-extra/clang-tidy/modernize/UseBitCastCheck.h @@ -0,0 +1,44 @@ +//===----------------------------------------------------------------------===// +// +// 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_USEBITCASTCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USEBITCASTCHECK_H + +#include "../ClangTidyCheck.h" +#include "../utils/IncludeInserter.h" + +namespace clang::tidy::modernize { + +/// Finds conservative object-to-object ``memcpy`` type punning that can be +/// expressed as ``std::bit_cast``. +/// +/// For the user-facing documentation see: +/// https://clang.llvm.org/extra/clang-tidy/checks/modernize/use-bit-cast.html +class UseBitCastCheck : public ClangTidyCheck { +public: + UseBitCastCheck(StringRef Name, ClangTidyContext *Context); + + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus20; + } + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, + Preprocessor *ModuleExpanderPP) override; + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + std::optional<TraversalKind> getCheckTraversalKind() const override { + return TK_IgnoreUnlessSpelledInSource; + } + +private: + utils::IncludeInserter IncludeInserter; +}; + +} // namespace clang::tidy::modernize + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USEBITCASTCHECK_H diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst index 36e311341f336..e1a41c4a1fd73 100644 --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -133,6 +133,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-use-bit-cast + <clang-tidy/checks/modernize/use-bit-cast>` check. + + Finds conservative object-to-object ``memcpy`` type punning that can be + rewritten as ``std::bit_cast`` in C++20 and later. + - New :doc:`modernize-use-std-bit <clang-tidy/checks/modernize/use-std-bit>` check. diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst index 2b5be931271ec..6cb962d1c2705 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/list.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -311,6 +311,7 @@ Clang-Tidy Checks :doc:`modernize-type-traits <modernize/type-traits>`, "Yes" :doc:`modernize-unary-static-assert <modernize/unary-static-assert>`, "Yes" :doc:`modernize-use-auto <modernize/use-auto>`, "Yes" + :doc:`modernize-use-bit-cast <modernize/use-bit-cast>`, "Yes" :doc:`modernize-use-bool-literals <modernize/use-bool-literals>`, "Yes" :doc:`modernize-use-constraints <modernize/use-constraints>`, "Yes" :doc:`modernize-use-default-member-init <modernize/use-default-member-init>`, "Yes" diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-bit-cast.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-bit-cast.rst new file mode 100644 index 0000000000000..e847eeb656f49 --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-bit-cast.rst @@ -0,0 +1,64 @@ +.. title:: clang-tidy - modernize-use-bit-cast + +modernize-use-bit-cast +====================== + +Finds conservative object-to-object ``memcpy`` type punning that can be +rewritten as ``std::bit_cast`` in C++20 and later. + +The check targets the common pattern of copying the full object representation +of one trivially copyable object into another trivially copyable object of a +different type: + +.. code-block:: c++ + + float src = 1.0f; + unsigned int dst; + std::memcpy(&dst, &src, sizeof(src)); + +This is rewritten to: + +.. code-block:: c++ + + float src = 1.0f; + unsigned int dst; + dst = std::bit_cast<unsigned int>(src); + +The fix intentionally replaces only the ``memcpy`` call. It does not fold a +preceding declaration into ``auto dst = ...`` because doing so can change the +construction behavior of the destination object. + +It only matches direct named source and destination objects, or direct +field subobjects accessed through ``.``, ``->``, ``.*``, or ``->*``, +and only when: + +* both object types are trivially copyable, +* neither object type is a pointer, array, function type, or + volatile-qualified, +* the source and destination types differ, +* the copy size is expressed as ``sizeof(...)`` for either copied type, and +* the ``memcpy`` call appears in a discarded-value context, such as a statement + body, the operand of an explicit ``(void)`` cast, or a comma subexpression + whose value is discarded. + +The check intentionally does not diagnose: + +* pointer punning, +* array or buffer manipulation, +* macro expansions, +* dependent template cases, +* unevaluated contexts such as ``sizeof(memcpy(...))``, +* larger expressions where the ``memcpy`` value affects the enclosing + expression, such as conditions or operands of unrelated operators, +* calls where the return value of ``memcpy`` is used, or +* unrelated overloads such as a user-defined ``memcpy``. + +If needed, the fix also inserts ``#include <bit>``. + +Options +------- + +.. option:: IncludeStyle + + A string specifying which include-style is used, ``llvm`` or ``google``. + Default is ``llvm``. diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-bit-cast.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-bit-cast.cpp new file mode 100644 index 0000000000000..9820bf6e7987c --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-bit-cast.cpp @@ -0,0 +1,291 @@ +// RUN: %check_clang_tidy -std=c++20-or-later %s modernize-use-bit-cast %t + +// CHECK-FIXES: #include <bit> + +void *memcpy(void *To, const void *From, unsigned long long Size); + +namespace std { +using ::memcpy; +} + +template <typename T> +struct identity { + using type = T; +}; + +struct NonTrivial { + NonTrivial(); + NonTrivial(const NonTrivial &); + int Value; +}; + +extern unsigned long long n; + +void basic_case() { + float src = 1.0f; + unsigned int dst; + std::memcpy(&dst, &src, sizeof(src)); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use 'std::bit_cast' instead of 'memcpy' for type punning [modernize-use-bit-cast] + // CHECK-FIXES: dst = std::bit_cast<unsigned int>(src); +} + +void unqualified_case() { + float src = 1.0f; + unsigned int dst; + memcpy(&dst, &src, sizeof(dst)); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use 'std::bit_cast' instead of 'memcpy' for type punning + // CHECK-FIXES: dst = std::bit_cast<unsigned int>(src); +} + +void global_case() { + float src = 1.0f; + unsigned int dst; + ::memcpy(&dst, &src, sizeof(unsigned int)); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use 'std::bit_cast' instead of 'memcpy' for type punning + // CHECK-FIXES: dst = std::bit_cast<unsigned int>(src); +} + +void explicit_cast_case() { + float src = 1.0f; + unsigned int dst = 0; + std::memcpy(static_cast<void *>(&dst), static_cast<const void *>(&src), + sizeof(dst)); + // CHECK-MESSAGES: :[[@LINE-2]]:3: warning: use 'std::bit_cast' instead of 'memcpy' for type punning + // CHECK-FIXES: dst = std::bit_cast<unsigned int>(src); +} + +void alias_case() { + using U = identity<unsigned int>::type; + using F = identity<float>::type; + F src = 1.0f; + U dst; + std::memcpy(&dst, &src, sizeof(U)); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use 'std::bit_cast' instead of 'memcpy' for type punning + // CHECK-FIXES: dst = std::bit_cast<U>(src); +} + +void const_source_case() { + const float src = 1.0f; + unsigned int dst; + std::memcpy(&dst, &src, sizeof(src)); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use 'std::bit_cast' instead of 'memcpy' for type punning + // CHECK-FIXES: dst = std::bit_cast<unsigned int>(src); +} + +void lambda_case() { + auto L = [] { + float src = 1.0f; + unsigned int dst; + std::memcpy(&dst, &src, sizeof(src)); + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use 'std::bit_cast' instead of 'memcpy' for type punning + // CHECK-FIXES: dst = std::bit_cast<unsigned int>(src); + }; + L(); +} + +void if_body_case(bool Cond) { + float src = 1.0f; + unsigned int dst; + if (Cond) + std::memcpy(&dst, &src, sizeof(src)); + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use 'std::bit_cast' instead of 'memcpy' for type punning + // CHECK-FIXES: if (Cond) + // CHECK-FIXES-NEXT: dst = std::bit_cast<unsigned int>(src); +} + +void comma_lhs_case() { + float src = 1.0f; + unsigned int dst; + int value = (std::memcpy(&dst, &src, sizeof(src)), 42); + (void)value; + // CHECK-MESSAGES: :[[@LINE-2]]:16: warning: use 'std::bit_cast' instead of 'memcpy' for type punning + // CHECK-FIXES: int value = ((void)(dst = std::bit_cast<unsigned int>(src)), 42); +} + +void void_cast_case() { + float src = 1.0f; + unsigned int dst; + (void)std::memcpy(&dst, &src, sizeof(src)); + // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: use 'std::bit_cast' instead of 'memcpy' for type punning + // CHECK-FIXES: (void)(dst = std::bit_cast<unsigned int>(src)); +} + +void same_type_case() { + float src = 1.0f; + float dst = 0.0f; + std::memcpy(&dst, &src, sizeof(src)); + // CHECK-MESSAGES-NOT: :[[@LINE-1]]:3: warning: use 'std::bit_cast' instead of 'memcpy' for type punning +} + +void pointer_case(int *srcp) { + int *dstp; + std::memcpy(&dstp, &srcp, sizeof(srcp)); + // CHECK-MESSAGES-NOT: :[[@LINE-1]]:3: warning: use 'std::bit_cast' instead of 'memcpy' for type punning +} + +void array_case() { + unsigned char bytes[sizeof(float)]; + float src = 1.0f; + std::memcpy(bytes, &src, sizeof(src)); + // CHECK-MESSAGES-NOT: :[[@LINE-1]]:3: warning: use 'std::bit_cast' instead of 'memcpy' for type punning +} + +void buffer_pointer_case(float *srcp, unsigned int *dstp) { + std::memcpy(dstp, srcp, sizeof(*srcp)); + // CHECK-MESSAGES-NOT: :[[@LINE-1]]:3: warning: use 'std::bit_cast' instead of 'memcpy' for type punning +} + +void partial_copy_case() { + float src = 1.0f; + unsigned int dst; + std::memcpy(&dst, &src, 2); + // CHECK-MESSAGES-NOT: :[[@LINE-1]]:3: warning: use 'std::bit_cast' instead of 'memcpy' for type punning +} + +void unknown_copy_case() { + float src = 1.0f; + unsigned int dst; + std::memcpy(&dst, &src, n); + // CHECK-MESSAGES-NOT: :[[@LINE-1]]:3: warning: use 'std::bit_cast' instead of 'memcpy' for type punning +} + +void non_trivial_case(NonTrivial src) { + NonTrivial dst; + std::memcpy(&dst, &src, sizeof(src)); + // CHECK-MESSAGES-NOT: :[[@LINE-1]]:3: warning: use 'std::bit_cast' instead of 'memcpy' for type punning +} + +void volatile_case() { + volatile float src = 1.0f; + unsigned int dst; + std::memcpy(&dst, const_cast<const float *>(&src), sizeof(src)); + // CHECK-MESSAGES-NOT: :[[@LINE-1]]:3: warning: use 'std::bit_cast' instead of 'memcpy' for type punning +} + +struct Wrap { + float src; + unsigned int dst; +}; + +struct SourceStruct { + int Value; +}; + +struct DestStruct { + const int Value; +}; + +void member_case() { + Wrap W{1.0f, 0}; + std::memcpy(&W.dst, &W.src, sizeof(W.src)); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use 'std::bit_cast' instead of 'memcpy' for type punning + // CHECK-FIXES: W.dst = std::bit_cast<unsigned int>(W.src); +} + +void pointer_member_case(Wrap *P) { + std::memcpy(&P->dst, &P->src, sizeof(P->src)); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use 'std::bit_cast' instead of 'memcpy' for type punning + // CHECK-FIXES: P->dst = std::bit_cast<unsigned int>(P->src); +} + +void member_pointer_case(Wrap W, float Wrap::*Src, unsigned int Wrap::*Dst) { + std::memcpy(&(W.*Dst), &(W.*Src), sizeof(W.*Src)); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use 'std::bit_cast' instead of 'memcpy' for type punning + // CHECK-FIXES: W.*Dst = std::bit_cast<unsigned int>(W.*Src); +} + +void pointer_member_pointer_case(Wrap *P, float Wrap::*Src, + unsigned int Wrap::*Dst) { + std::memcpy(&(P->*Dst), &(P->*Src), sizeof(P->*Src)); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use 'std::bit_cast' instead of 'memcpy' for type punning + // CHECK-FIXES: P->*Dst = std::bit_cast<unsigned int>(P->*Src); +} + +void builtin_case() { + float src = 1.0f; + unsigned int dst; + __builtin_memcpy(&dst, &src, sizeof(src)); + // CHECK-MESSAGES-NOT: :[[@LINE-1]]:3: warning: use 'std::bit_cast' instead of 'memcpy' for type punning +} + +namespace ns { +struct A { + unsigned int Value; +}; + +struct B { + unsigned int Value; +}; + +void memcpy(B *, const A *, unsigned long long); + +void overload_case() { + A src{0}; + B dst{0}; + memcpy(&dst, &src, sizeof(src)); + // CHECK-MESSAGES-NOT: :[[@LINE-1]]:3: warning: use 'std::bit_cast' instead of 'memcpy' for type punning +} +} // namespace ns + +#define DO_COPY(Dst, Src) std::memcpy(&(Dst), &(Src), sizeof(Src)) + +void macro_case() { + float src = 1.0f; + unsigned int dst; + DO_COPY(dst, src); + // CHECK-MESSAGES-NOT: :[[@LINE-1]]:3: warning: use 'std::bit_cast' instead of 'memcpy' for type punning +} + +template <typename To, typename From> +requires(sizeof(To) == sizeof(From)) +To template_case(From src) { + To dst; + std::memcpy(&dst, &src, sizeof(src)); + // CHECK-MESSAGES-NOT: :[[@LINE-1]]:3: warning: use 'std::bit_cast' instead of 'memcpy' for type punning + return dst; +} + +void unevaluated_case() { + float src = 1.0f; + unsigned int dst; + (void)sizeof(std::memcpy(&dst, &src, sizeof(src))); + // CHECK-MESSAGES-NOT: :[[@LINE-1]]:16: warning: use 'std::bit_cast' instead of 'memcpy' for type punning +} + +void used_return_value_case() { + float src = 1.0f; + unsigned int dst; + void *Ptr = std::memcpy(&dst, &src, sizeof(src)); + (void)Ptr; + // CHECK-MESSAGES-NOT: :[[@LINE-2]]:15: warning: use 'std::bit_cast' instead of 'memcpy' for type punning +} + +void comma_rhs_used_case() { + float src = 1.0f; + unsigned int dst; + void *Ptr = (0, std::memcpy(&dst, &src, sizeof(src))); + (void)Ptr; + // CHECK-MESSAGES-NOT: :[[@LINE-2]]:19: warning: use 'std::bit_cast' instead of 'memcpy' for type punning +} + +void deleted_assignment_case(SourceStruct src) { + DestStruct dst{0}; + std::memcpy(&dst, &src, sizeof(src)); + // CHECK-MESSAGES-NOT: :[[@LINE-1]]:3: warning: use 'std::bit_cast' instead of 'memcpy' for type punning +} + +void condition_use_case() { + float src = 1.0f; + unsigned int dst; + if (std::memcpy(&dst, &src, sizeof(src))) + (void)0; + // CHECK-MESSAGES-NOT: :[[@LINE-2]]:7: warning: use 'std::bit_cast' instead of 'memcpy' for type punning +} + +void conditional_operand_case(bool Cond) { + float src = 1.0f; + unsigned int dst; + void *Ptr = nullptr; + (void)(Cond ? std::memcpy(&dst, &src, sizeof(src)) : Ptr); + // CHECK-MESSAGES-NOT: :[[@LINE-1]]:17: warning: use 'std::bit_cast' instead of 'memcpy' for type punning +} >From a8d3b7ea13de50dc7d87711bbac33a16ba4dcdad Mon Sep 17 00:00:00 2001 From: Daniil Dudkin <[email protected]> Date: Wed, 1 Apr 2026 23:19:35 +0300 Subject: [PATCH 02/10] fix revidew comment --- .../clang-tidy/modernize/UseBitCastCheck.cpp | 50 +++++++------------ .../checks/modernize/use-bit-cast.rst | 12 +++-- .../checkers/modernize/use-bit-cast.cpp | 28 +++++++++++ 3 files changed, 53 insertions(+), 37 deletions(-) diff --git a/clang-tools-extra/clang-tidy/modernize/UseBitCastCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseBitCastCheck.cpp index bc6173a658890..6f681042b9a43 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseBitCastCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseBitCastCheck.cpp @@ -20,13 +20,6 @@ using namespace clang::ast_matchers; namespace clang::tidy::modernize { -static const Expr *stripMemcpyArgument(const Expr *ExprNode) { - ExprNode = ExprNode->IgnoreParenImpCasts(); - while (const auto *Cast = dyn_cast<ExplicitCastExpr>(ExprNode)) - ExprNode = Cast->getSubExpr()->IgnoreParenImpCasts(); - return ExprNode; -} - static bool isSupportedMemcpyObjectExpr(const Expr *ExprNode) { ExprNode = ExprNode->IgnoreParenImpCasts(); @@ -44,7 +37,7 @@ static bool isSupportedMemcpyObjectExpr(const Expr *ExprNode) { } static const Expr *extractMemcpyObjectExpr(const Expr *ExprNode) { - ExprNode = stripMemcpyArgument(ExprNode); + ExprNode = ExprNode->IgnoreParenCasts(); const auto *AddressOf = dyn_cast<UnaryOperator>(ExprNode); if (!AddressOf || AddressOf->getOpcode() != UO_AddrOf) return nullptr; @@ -53,30 +46,23 @@ static const Expr *extractMemcpyObjectExpr(const Expr *ExprNode) { return isSupportedMemcpyObjectExpr(ObjectExpr) ? ObjectExpr : nullptr; } -static bool isSupportedMemcpyArgType(QualType Type, const ASTContext &Context, - bool RequireMutable) { - if (Type.isNull()) - return false; +static bool isBitCastableMemcpyObjectType(QualType Type, + const ASTContext &Context) { + Type = Type.getCanonicalType().getNonReferenceType(); + return !Type.isNull() && !Type.isVolatileQualified() && + !Type->isAnyPointerType() && !Type->isFunctionType() && + Type.isTriviallyCopyableType(Context) && + Type.isBitwiseCloneableType(Context); +} - const QualType CanonicalType = Type.getCanonicalType().getNonReferenceType(); - if (CanonicalType.isNull() || CanonicalType->isDependentType() || - CanonicalType->isIncompleteType() || - CanonicalType.isVolatileQualified() || - CanonicalType->isAnyPointerType() || CanonicalType->isArrayType() || - CanonicalType->isFunctionType()) +static bool canAssignBitCastResult(QualType Type) { + Type = Type.getCanonicalType().getNonReferenceType(); + if (Type.isNull() || Type.isConstQualified() || Type->isArrayType()) return false; - if (RequireMutable) { - if (CanonicalType.isConstQualified()) - return false; - - if (const auto *Record = CanonicalType->getAsCXXRecordDecl()) - if (!Record->hasSimpleCopyAssignment() && - !Record->hasSimpleMoveAssignment()) - return false; - } - - return Type.getNonReferenceType().isTriviallyCopyableType(Context); + const auto *Record = Type->getAsCXXRecordDecl(); + return !Record || Record->hasSimpleCopyAssignment() || + Record->hasSimpleMoveAssignment(); } static bool isSameUnqualifiedCanonicalType(QualType LHS, QualType RHS) { @@ -210,9 +196,9 @@ AST_MATCHER(CallExpr, isBitCastMemcpyCandidate) { const QualType DstType = DstExpr->getType().getNonReferenceType(); const QualType SrcType = SrcExpr->getType().getNonReferenceType(); - return isSupportedMemcpyArgType(DstType, Context, /*RequireMutable=*/true) && - isSupportedMemcpyArgType(SrcType, Context, - /*RequireMutable=*/false) && + return isBitCastableMemcpyObjectType(DstType, Context) && + isBitCastableMemcpyObjectType(SrcType, Context) && + canAssignBitCastResult(DstType) && !isSameUnqualifiedCanonicalType(SrcType, DstType) && isMatchingSizeOfExpression(Node.getArg(2), SrcType, DstType, Context); } diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-bit-cast.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-bit-cast.rst index e847eeb656f49..682d21e77b9cc 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-bit-cast.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-bit-cast.rst @@ -32,9 +32,11 @@ It only matches direct named source and destination objects, or direct field subobjects accessed through ``.``, ``->``, ``.*``, or ``->*``, and only when: -* both object types are trivially copyable, -* neither object type is a pointer, array, function type, or - volatile-qualified, +* both object types are trivially copyable and bitwise-cloneable, and + neither is a pointer, function, or volatile-qualified type, +* the destination type can be assigned from the ``std::bit_cast`` result, + so raw C array destinations are excluded while types such as + ``std::array`` are allowed, * the source and destination types differ, * the copy size is expressed as ``sizeof(...)`` for either copied type, and * the ``memcpy`` call appears in a discarded-value context, such as a statement @@ -60,5 +62,5 @@ Options .. option:: IncludeStyle - A string specifying which include-style is used, ``llvm`` or ``google``. - Default is ``llvm``. + A string specifying which include-style is used, `llvm` or `google`. + Default is `llvm`. diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-bit-cast.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-bit-cast.cpp index 9820bf6e7987c..27a5114b3e22b 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-bit-cast.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-bit-cast.cpp @@ -5,6 +5,11 @@ void *memcpy(void *To, const void *From, unsigned long long Size); namespace std { +template <typename T, unsigned long long N> +struct array { + T Storage[N]; +}; + using ::memcpy; } @@ -72,6 +77,22 @@ void const_source_case() { // CHECK-FIXES: dst = std::bit_cast<unsigned int>(src); } +void std_array_case() { + std::array<float, 1> src{{1.0f}}; + std::array<unsigned int, 1> dst{}; + std::memcpy(&dst, &src, sizeof(src)); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use 'std::bit_cast' instead of 'memcpy' for type punning + // CHECK-FIXES: dst = std::bit_cast<std::array<unsigned int, 1>>(src); +} + +void raw_array_source_case() { + float src[1] = {1.0f}; + std::array<unsigned int, 1> dst{}; + std::memcpy(&dst, &src, sizeof(src)); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use 'std::bit_cast' instead of 'memcpy' for type punning + // CHECK-FIXES: dst = std::bit_cast<std::array<unsigned int, 1>>(src); +} + void lambda_case() { auto L = [] { float src = 1.0f; @@ -130,6 +151,13 @@ void array_case() { // CHECK-MESSAGES-NOT: :[[@LINE-1]]:3: warning: use 'std::bit_cast' instead of 'memcpy' for type punning } +void raw_array_destination_case() { + std::array<float, 1> src{{1.0f}}; + unsigned int dst[1]; + std::memcpy(&dst, &src, sizeof(src)); + // CHECK-MESSAGES-NOT: :[[@LINE-1]]:3: warning: use 'std::bit_cast' instead of 'memcpy' for type punning +} + void buffer_pointer_case(float *srcp, unsigned int *dstp) { std::memcpy(dstp, srcp, sizeof(*srcp)); // CHECK-MESSAGES-NOT: :[[@LINE-1]]:3: warning: use 'std::bit_cast' instead of 'memcpy' for type punning >From 929e81ab7144d03caafc98ee930c5c6c72930c80 Mon Sep 17 00:00:00 2001 From: Daniil Dudkin <[email protected]> Date: Thu, 2 Apr 2026 19:29:57 +0300 Subject: [PATCH 03/10] Fix review comments --- .../clang-tidy/modernize/UseBitCastCheck.cpp | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/clang-tools-extra/clang-tidy/modernize/UseBitCastCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseBitCastCheck.cpp index 6f681042b9a43..14e294548a0cb 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseBitCastCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseBitCastCheck.cpp @@ -15,6 +15,7 @@ #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/Lex/Lexer.h" #include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/Twine.h" using namespace clang::ast_matchers; @@ -230,17 +231,15 @@ void UseBitCastCheck::registerPPCallbacks(const SourceManager &SM, void UseBitCastCheck::registerMatchers(MatchFinder *Finder) { Finder->addMatcher( callExpr(callee(functionDecl(hasName("::memcpy"))), - isDiscardedValueContext(), unless(isInTemplateInstantiation()), - unless(hasAncestor(expr(matchers::hasUnevaluatedContext()))), - isBitCastMemcpyCandidate()) + isDiscardedValueContext(), isBitCastMemcpyCandidate(), + unless(hasAncestor(expr(matchers::hasUnevaluatedContext())))) .bind("memcpy"), this); } void UseBitCastCheck::check(const MatchFinder::MatchResult &Result) { const auto *MemcpyCall = Result.Nodes.getNodeAs<CallExpr>("memcpy"); - if (!MemcpyCall) - return; + assert(MemcpyCall); const auto *DstExpr = extractMemcpyObjectExpr(MemcpyCall->getArg(0)); const auto *SrcExpr = extractMemcpyObjectExpr(MemcpyCall->getArg(1)); @@ -262,22 +261,23 @@ void UseBitCastCheck::check(const MatchFinder::MatchResult &Result) { const PrintingPolicy Policy(LangOpts); const QualType DstType = DstExpr->getType().getNonReferenceType().getUnqualifiedType(); - const std::string Assignment = std::string(DstText) + " = std::bit_cast<" + - DstType.getAsString(Policy) + ">(" + - std::string(SrcText) + ")"; - std::string Replacement = Assignment; - switch (ReplacementForm) { - case MemcpyReplacementForm::StatementBody: - break; - case MemcpyReplacementForm::PreserveOuterVoidCast: - Replacement = "(" + Assignment + ")"; - break; - case MemcpyReplacementForm::InjectVoidCast: - Replacement = "(void)(" + Assignment + ")"; - break; - case MemcpyReplacementForm::None: - return; - } + const std::string DstTypeName = DstType.getAsString(Policy); + const std::string Replacement = + [&](const llvm::Twine &Assignment) -> std::string { + switch (ReplacementForm) { + case MemcpyReplacementForm::StatementBody: + return Assignment.str(); + case MemcpyReplacementForm::PreserveOuterVoidCast: + return ("(" + Assignment + ")").str(); + case MemcpyReplacementForm::InjectVoidCast: + return ("(void)(" + Assignment + ")").str(); + case MemcpyReplacementForm::None: + return {}; + } + + return {}; + }(llvm::Twine(DstText) + " = std::bit_cast<" + DstTypeName + ">(" + SrcText + + ")"); const DiagnosticBuilder Diag = diag(MemcpyCall->getBeginLoc(), >From 6508c9c2fe12f5cc0c851980d99b2132f793469c Mon Sep 17 00:00:00 2001 From: Daniil Dudkin <[email protected]> Date: Sat, 4 Apr 2026 20:58:00 +0300 Subject: [PATCH 04/10] Add use-bit-cast tests for sizeof(type) in templates --- .../checkers/modernize/use-bit-cast.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-bit-cast.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-bit-cast.cpp index 27a5114b3e22b..e62bff0445083 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-bit-cast.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-bit-cast.cpp @@ -77,6 +77,14 @@ void const_source_case() { // CHECK-FIXES: dst = std::bit_cast<unsigned int>(src); } +void sizeof_type_source_case() { + float src = 1.0f; + unsigned int dst; + std::memcpy(&dst, &src, sizeof(float)); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use 'std::bit_cast' instead of 'memcpy' for type punning + // CHECK-FIXES: dst = std::bit_cast<unsigned int>(src); +} + void std_array_case() { std::array<float, 1> src{{1.0f}}; std::array<unsigned int, 1> dst{}; @@ -273,6 +281,15 @@ To template_case(From src) { return dst; } +template <typename T> +void non_dependent_template_case() { + float src = 1.0f; + unsigned int dst; + memcpy(&dst, &src, sizeof(src)); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use 'std::bit_cast' instead of 'memcpy' for type punning + // CHECK-FIXES: dst = std::bit_cast<unsigned int>(src); +} + void unevaluated_case() { float src = 1.0f; unsigned int dst; >From c8b37623194dd709fd42091dd0cc7d7961f05bf6 Mon Sep 17 00:00:00 2001 From: Daniil Dudkin <[email protected]> Date: Sat, 4 Apr 2026 21:06:08 +0300 Subject: [PATCH 05/10] Bind memcpy object operands in matcher --- .../clang-tidy/modernize/UseBitCastCheck.cpp | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/clang-tools-extra/clang-tidy/modernize/UseBitCastCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseBitCastCheck.cpp index 14e294548a0cb..81035d63b6a60 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseBitCastCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseBitCastCheck.cpp @@ -197,11 +197,16 @@ AST_MATCHER(CallExpr, isBitCastMemcpyCandidate) { const QualType DstType = DstExpr->getType().getNonReferenceType(); const QualType SrcType = SrcExpr->getType().getNonReferenceType(); - return isBitCastableMemcpyObjectType(DstType, Context) && - isBitCastableMemcpyObjectType(SrcType, Context) && - canAssignBitCastResult(DstType) && - !isSameUnqualifiedCanonicalType(SrcType, DstType) && - isMatchingSizeOfExpression(Node.getArg(2), SrcType, DstType, Context); + if (!isBitCastableMemcpyObjectType(DstType, Context) || + !isBitCastableMemcpyObjectType(SrcType, Context) || + !canAssignBitCastResult(DstType) || + isSameUnqualifiedCanonicalType(SrcType, DstType) || + !isMatchingSizeOfExpression(Node.getArg(2), SrcType, DstType, Context)) + return false; + + Builder->setBinding("dstExpr", DynTypedNode::create(*DstExpr)); + Builder->setBinding("srcExpr", DynTypedNode::create(*SrcExpr)); + return true; } } // namespace @@ -239,12 +244,11 @@ void UseBitCastCheck::registerMatchers(MatchFinder *Finder) { void UseBitCastCheck::check(const MatchFinder::MatchResult &Result) { const auto *MemcpyCall = Result.Nodes.getNodeAs<CallExpr>("memcpy"); + const auto *DstExpr = Result.Nodes.getNodeAs<Expr>("dstExpr"); + const auto *SrcExpr = Result.Nodes.getNodeAs<Expr>("srcExpr"); assert(MemcpyCall); - - const auto *DstExpr = extractMemcpyObjectExpr(MemcpyCall->getArg(0)); - const auto *SrcExpr = extractMemcpyObjectExpr(MemcpyCall->getArg(1)); - if (!DstExpr || !SrcExpr) - return; + assert(DstExpr); + assert(SrcExpr); const SourceManager &SM = *Result.SourceManager; const LangOptions &LangOpts = getLangOpts(); >From e9a30f3f482c1101e2a2af2a910c46053ecf3467 Mon Sep 17 00:00:00 2001 From: Daniil Dudkin <[email protected]> Date: Sat, 4 Apr 2026 21:08:19 +0300 Subject: [PATCH 06/10] Match memcpy replacement context in the matcher --- .../clang-tidy/modernize/UseBitCastCheck.cpp | 107 ++++++++---------- .../checkers/modernize/use-bit-cast.cpp | 2 +- 2 files changed, 49 insertions(+), 60 deletions(-) diff --git a/clang-tools-extra/clang-tidy/modernize/UseBitCastCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseBitCastCheck.cpp index 81035d63b6a60..8a296f24afe0a 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseBitCastCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseBitCastCheck.cpp @@ -121,27 +121,14 @@ static bool isStatementBody(const Stmt *Current, const Stmt *Parent) { namespace { -// These states describe how to spell the replacement when only the memcpy call -// is replaced. An existing `(void)` cast is preserved by parenthesizing the -// assignment, while comma/discarded subexpressions need an injected `(void)`. -enum class MemcpyReplacementForm { - None, - StatementBody, - PreserveOuterVoidCast, - InjectVoidCast, -}; - -} // namespace - -static MemcpyReplacementForm getMemcpyReplacementForm(const Expr *ExprNode, - ASTContext &Context) { - const Stmt *Current = ExprNode; - MemcpyReplacementForm Kind = MemcpyReplacementForm::StatementBody; +AST_MATCHER(CallExpr, hasBitCastReplacementContext) { + const Stmt *Current = &Node; + const BinaryOperator *DiscardedComma = nullptr; while (true) { - auto Parents = Context.getParents(*Current); + auto Parents = Finder->getASTContext().getParents(*Current); if (Parents.size() != 1) - return MemcpyReplacementForm::None; + return false; if (const auto *ParentExpr = Parents[0].get<Expr>()) { if (isa<ExprWithCleanups, ImplicitCastExpr, MaterializeTemporaryExpr, @@ -150,37 +137,46 @@ static MemcpyReplacementForm getMemcpyReplacementForm(const Expr *ExprNode, continue; } - if (const auto *Cast = dyn_cast<CastExpr>(ParentExpr)) - if (Cast->getCastKind() == CK_ToVoid) - return MemcpyReplacementForm::PreserveOuterVoidCast; - - if (const auto *Comma = dyn_cast<BinaryOperator>(ParentExpr)) { - if (Comma->getOpcode() != BO_Comma) - return MemcpyReplacementForm::None; - if (Comma->getLHS() == Current) - return MemcpyReplacementForm::InjectVoidCast; - if (Comma->getRHS() == Current) { - Current = Comma; - Kind = MemcpyReplacementForm::InjectVoidCast; - continue; + if (const auto *Cast = dyn_cast<CastExpr>(ParentExpr)) { + if (Cast->getCastKind() != CK_ToVoid) + return false; + + if (!DiscardedComma) { + Builder->setBinding("replacementRoot", DynTypedNode::create(*Cast)); + Builder->setBinding("discardedVoidCast", DynTypedNode::create(*Cast)); + return true; } + + Current = Cast; + continue; + } + + const auto *Comma = dyn_cast<BinaryOperator>(ParentExpr); + if (!Comma || Comma->getOpcode() != BO_Comma) + return false; + if (Comma->getLHS() == Current) { + Builder->setBinding("replacementRoot", DynTypedNode::create(Node)); + Builder->setBinding("discardedComma", DynTypedNode::create(*Comma)); + return true; } + if (Comma->getRHS() != Current) + return false; - return MemcpyReplacementForm::None; + DiscardedComma = Comma; + Current = Comma; + continue; } const auto *ParentStmt = Parents[0].get<Stmt>(); if (!ParentStmt || !isStatementBody(Current, ParentStmt)) - return MemcpyReplacementForm::None; - return Kind; - } -} - -namespace { + return false; -AST_MATCHER(CallExpr, isDiscardedValueContext) { - return getMemcpyReplacementForm(&Node, Finder->getASTContext()) != - MemcpyReplacementForm::None; + Builder->setBinding("replacementRoot", DynTypedNode::create(Node)); + if (DiscardedComma) + Builder->setBinding("discardedComma", + DynTypedNode::create(*DiscardedComma)); + return true; + } } AST_MATCHER(CallExpr, isBitCastMemcpyCandidate) { @@ -236,7 +232,7 @@ void UseBitCastCheck::registerPPCallbacks(const SourceManager &SM, void UseBitCastCheck::registerMatchers(MatchFinder *Finder) { Finder->addMatcher( callExpr(callee(functionDecl(hasName("::memcpy"))), - isDiscardedValueContext(), isBitCastMemcpyCandidate(), + hasBitCastReplacementContext(), isBitCastMemcpyCandidate(), unless(hasAncestor(expr(matchers::hasUnevaluatedContext())))) .bind("memcpy"), this); @@ -246,9 +242,16 @@ void UseBitCastCheck::check(const MatchFinder::MatchResult &Result) { const auto *MemcpyCall = Result.Nodes.getNodeAs<CallExpr>("memcpy"); const auto *DstExpr = Result.Nodes.getNodeAs<Expr>("dstExpr"); const auto *SrcExpr = Result.Nodes.getNodeAs<Expr>("srcExpr"); + const auto *ReplacementRoot = Result.Nodes.getNodeAs<Expr>("replacementRoot"); + const auto *DiscardedComma = + Result.Nodes.getNodeAs<BinaryOperator>("discardedComma"); + const auto *DiscardedVoidCast = + Result.Nodes.getNodeAs<CastExpr>("discardedVoidCast"); assert(MemcpyCall); assert(DstExpr); assert(SrcExpr); + assert(ReplacementRoot); + assert(!DiscardedVoidCast || ReplacementRoot == DiscardedVoidCast); const SourceManager &SM = *Result.SourceManager; const LangOptions &LangOpts = getLangOpts(); @@ -257,36 +260,22 @@ void UseBitCastCheck::check(const MatchFinder::MatchResult &Result) { if (DstText.empty() || SrcText.empty()) return; - const MemcpyReplacementForm ReplacementForm = - getMemcpyReplacementForm(MemcpyCall, *Result.Context); - if (ReplacementForm == MemcpyReplacementForm::None) - return; - const PrintingPolicy Policy(LangOpts); const QualType DstType = DstExpr->getType().getNonReferenceType().getUnqualifiedType(); const std::string DstTypeName = DstType.getAsString(Policy); const std::string Replacement = [&](const llvm::Twine &Assignment) -> std::string { - switch (ReplacementForm) { - case MemcpyReplacementForm::StatementBody: - return Assignment.str(); - case MemcpyReplacementForm::PreserveOuterVoidCast: - return ("(" + Assignment + ")").str(); - case MemcpyReplacementForm::InjectVoidCast: + if (DiscardedComma) return ("(void)(" + Assignment + ")").str(); - case MemcpyReplacementForm::None: - return {}; - } - - return {}; + return Assignment.str(); }(llvm::Twine(DstText) + " = std::bit_cast<" + DstTypeName + ">(" + SrcText + ")"); const DiagnosticBuilder Diag = diag(MemcpyCall->getBeginLoc(), "use 'std::bit_cast' instead of 'memcpy' for type punning"); - Diag << FixItHint::CreateReplacement(MemcpyCall->getSourceRange(), + Diag << FixItHint::CreateReplacement(ReplacementRoot->getSourceRange(), Replacement); Diag << IncludeInserter.createIncludeInsertion( SM.getFileID(MemcpyCall->getBeginLoc()), "<bit>"); diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-bit-cast.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-bit-cast.cpp index e62bff0445083..599ceebe95f2b 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-bit-cast.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-bit-cast.cpp @@ -136,7 +136,7 @@ void void_cast_case() { unsigned int dst; (void)std::memcpy(&dst, &src, sizeof(src)); // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: use 'std::bit_cast' instead of 'memcpy' for type punning - // CHECK-FIXES: (void)(dst = std::bit_cast<unsigned int>(src)); + // CHECK-FIXES: dst = std::bit_cast<unsigned int>(src); } void same_type_case() { >From 770dbc8eb2054afde4e1a3200aee18463b4c14c6 Mon Sep 17 00:00:00 2001 From: Daniil Dudkin <[email protected]> Date: Sat, 4 Apr 2026 21:12:49 +0300 Subject: [PATCH 07/10] Clean up use-bit-cast matcher and printing --- .../clang-tidy/modernize/UseBitCastCheck.cpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/clang-tools-extra/clang-tidy/modernize/UseBitCastCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseBitCastCheck.cpp index 8a296f24afe0a..8418cf83eb8cf 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseBitCastCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseBitCastCheck.cpp @@ -7,7 +7,6 @@ //===----------------------------------------------------------------------===// #include "UseBitCastCheck.h" -#include "../utils/Matchers.h" #include "clang/AST/ASTContext.h" #include "clang/AST/Expr.h" #include "clang/AST/ExprCXX.h" @@ -16,6 +15,7 @@ #include "clang/Lex/Lexer.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/Twine.h" +#include <cassert> using namespace clang::ast_matchers; @@ -230,12 +230,11 @@ void UseBitCastCheck::registerPPCallbacks(const SourceManager &SM, } void UseBitCastCheck::registerMatchers(MatchFinder *Finder) { - Finder->addMatcher( - callExpr(callee(functionDecl(hasName("::memcpy"))), - hasBitCastReplacementContext(), isBitCastMemcpyCandidate(), - unless(hasAncestor(expr(matchers::hasUnevaluatedContext())))) - .bind("memcpy"), - this); + Finder->addMatcher(callExpr(callee(functionDecl(hasName("::memcpy"))), + hasBitCastReplacementContext(), + isBitCastMemcpyCandidate()) + .bind("memcpy"), + this); } void UseBitCastCheck::check(const MatchFinder::MatchResult &Result) { @@ -260,7 +259,7 @@ void UseBitCastCheck::check(const MatchFinder::MatchResult &Result) { if (DstText.empty() || SrcText.empty()) return; - const PrintingPolicy Policy(LangOpts); + const PrintingPolicy &Policy = Result.Context->getPrintingPolicy(); const QualType DstType = DstExpr->getType().getNonReferenceType().getUnqualifiedType(); const std::string DstTypeName = DstType.getAsString(Policy); >From 8ec85ddef9a7af76d1a72995f3922b435976f369 Mon Sep 17 00:00:00 2001 From: Daniil Dudkin <[email protected]> Date: Sat, 4 Apr 2026 21:24:02 +0300 Subject: [PATCH 08/10] Add sizeof destination type coverage --- .../test/clang-tidy/checkers/modernize/use-bit-cast.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-bit-cast.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-bit-cast.cpp index 599ceebe95f2b..400337a6dfa1c 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-bit-cast.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-bit-cast.cpp @@ -85,6 +85,14 @@ void sizeof_type_source_case() { // CHECK-FIXES: dst = std::bit_cast<unsigned int>(src); } +void sizeof_type_destination_case() { + float src = 1.0f; + unsigned int dst; + std::memcpy(&dst, &src, sizeof(unsigned int)); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use 'std::bit_cast' instead of 'memcpy' for type punning + // CHECK-FIXES: dst = std::bit_cast<unsigned int>(src); +} + void std_array_case() { std::array<float, 1> src{{1.0f}}; std::array<unsigned int, 1> dst{}; >From 0e3ebd106f5266baa5bc4b2b4ba2806665108dc2 Mon Sep 17 00:00:00 2001 From: Daniil Dudkin <[email protected]> Date: Sat, 4 Apr 2026 23:39:16 +0300 Subject: [PATCH 09/10] Fix warning --- clang-tools-extra/clang-tidy/modernize/UseBitCastCheck.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/clang-tools-extra/clang-tidy/modernize/UseBitCastCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseBitCastCheck.cpp index 8418cf83eb8cf..a86a2a70ae9b2 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseBitCastCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseBitCastCheck.cpp @@ -143,7 +143,6 @@ AST_MATCHER(CallExpr, hasBitCastReplacementContext) { if (!DiscardedComma) { Builder->setBinding("replacementRoot", DynTypedNode::create(*Cast)); - Builder->setBinding("discardedVoidCast", DynTypedNode::create(*Cast)); return true; } @@ -244,13 +243,10 @@ void UseBitCastCheck::check(const MatchFinder::MatchResult &Result) { const auto *ReplacementRoot = Result.Nodes.getNodeAs<Expr>("replacementRoot"); const auto *DiscardedComma = Result.Nodes.getNodeAs<BinaryOperator>("discardedComma"); - const auto *DiscardedVoidCast = - Result.Nodes.getNodeAs<CastExpr>("discardedVoidCast"); assert(MemcpyCall); assert(DstExpr); assert(SrcExpr); assert(ReplacementRoot); - assert(!DiscardedVoidCast || ReplacementRoot == DiscardedVoidCast); const SourceManager &SM = *Result.SourceManager; const LangOptions &LangOpts = getLangOpts(); >From 277de871d7e5dfe4812a5539c8fefe5ce1c00a36 Mon Sep 17 00:00:00 2001 From: Daniil Dudkin <[email protected]> Date: Sun, 5 Apr 2026 00:06:23 +0300 Subject: [PATCH 10/10] Simplify docs --- clang-tools-extra/docs/ReleaseNotes.rst | 4 +- .../checks/modernize/use-bit-cast.rst | 50 +++++++------------ 2 files changed, 20 insertions(+), 34 deletions(-) diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst index e1a41c4a1fd73..4ed6feb1fe5d2 100644 --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -136,8 +136,8 @@ New checks - New :doc:`modernize-use-bit-cast <clang-tidy/checks/modernize/use-bit-cast>` check. - Finds conservative object-to-object ``memcpy`` type punning that can be - rewritten as ``std::bit_cast`` in C++20 and later. + Finds ``memcpy``-based type punning that can be rewritten as + ``std::bit_cast`` in C++20 and later. - New :doc:`modernize-use-std-bit <clang-tidy/checks/modernize/use-std-bit>` check. diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-bit-cast.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-bit-cast.rst index 682d21e77b9cc..260576944497f 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-bit-cast.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-bit-cast.rst @@ -3,12 +3,8 @@ modernize-use-bit-cast ====================== -Finds conservative object-to-object ``memcpy`` type punning that can be -rewritten as ``std::bit_cast`` in C++20 and later. - -The check targets the common pattern of copying the full object representation -of one trivially copyable object into another trivially copyable object of a -different type: +Finds ``memcpy``-based type punning that can be rewritten as ``std::bit_cast`` +in C++20 and later. .. code-block:: c++ @@ -24,35 +20,25 @@ This is rewritten to: unsigned int dst; dst = std::bit_cast<unsigned int>(src); -The fix intentionally replaces only the ``memcpy`` call. It does not fold a -preceding declaration into ``auto dst = ...`` because doing so can change the -construction behavior of the destination object. - -It only matches direct named source and destination objects, or direct -field subobjects accessed through ``.``, ``->``, ``.*``, or ``->*``, -and only when: - -* both object types are trivially copyable and bitwise-cloneable, and - neither is a pointer, function, or volatile-qualified type, -* the destination type can be assigned from the ``std::bit_cast`` result, - so raw C array destinations are excluded while types such as - ``std::array`` are allowed, -* the source and destination types differ, -* the copy size is expressed as ``sizeof(...)`` for either copied type, and -* the ``memcpy`` call appears in a discarded-value context, such as a statement - body, the operand of an explicit ``(void)`` cast, or a comma subexpression - whose value is discarded. - -The check intentionally does not diagnose: - -* pointer punning, -* array or buffer manipulation, +The fix replaces only the ``memcpy`` call. It does not rewrite a preceding +declaration into ``auto dst = ...``. + +It matches only object-to-object copies where: + +* both object types are trivially copyable, and neither is a pointer, + function, or ``volatile``-qualified type, +* the destination can be assigned from ``std::bit_cast``, so raw C array + destinations are excluded, +* the source and destination are not the same type after removing aliases and + cv-qualifiers, +* the size argument is ``sizeof`` of either copied type, and +* the ``memcpy`` result is not used. + +It intentionally does not diagnose: + * macro expansions, * dependent template cases, * unevaluated contexts such as ``sizeof(memcpy(...))``, -* larger expressions where the ``memcpy`` value affects the enclosing - expression, such as conditions or operands of unrelated operators, -* calls where the return value of ``memcpy`` is used, or * unrelated overloads such as a user-defined ``memcpy``. If needed, the fix also inserts ``#include <bit>``. _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
