[clang-tools-extra] [clang-tidy] Add `modernize-use-bit-cast` check (PR #189962)
Daniil Dudkin via cfe-commits
cfe-commits at lists.llvm.org
Sat Apr 4 14:06:41 PDT 2026
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 <unterumarmung at yandex.ru>
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 <unterumarmung at yandex.ru>
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 <unterumarmung at yandex.ru>
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 <unterumarmung at yandex.ru>
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 <unterumarmung at yandex.ru>
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 <unterumarmung at yandex.ru>
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 <unterumarmung at yandex.ru>
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 <unterumarmung at yandex.ru>
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 <unterumarmung at yandex.ru>
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 <unterumarmung at yandex.ru>
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>``.
More information about the cfe-commits
mailing list