[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 Sep 5 14:37:12 PDT 2026


https://github.com/unterumarmung updated https://github.com/llvm/llvm-project/pull/189962

>From b50ada2f36fd109666bfc0e3a922d9c4b4043706 Mon Sep 17 00:00:00 2001
From: Daniil Dudkin <unterumarmung at yandex.ru>
Date: Sat, 5 Sep 2026 13:28:11 +0300
Subject: [PATCH] Add modernize-use-bit-cast check

---
 .../clang-tidy/modernize/CMakeLists.txt       |   1 +
 .../modernize/ModernizeTidyModule.cpp         |   2 +
 .../clang-tidy/modernize/UseBitCastCheck.cpp  | 362 ++++++++++++
 .../clang-tidy/modernize/UseBitCastCheck.h    |  44 ++
 clang-tools-extra/docs/ReleaseNotes.md        |   6 +
 .../docs/clang-tidy/checks/list.md            |   1 +
 .../checks/modernize/use-bit-cast.md          |  47 ++
 .../modernize/Inputs/use-bit-cast/header.h    |  14 +
 .../modernize/use-bit-cast-header.cpp         |  13 +
 .../modernize/use-bit-cast-overload.cpp       |  22 +
 .../checkers/modernize/use-bit-cast.cpp       | 538 ++++++++++++++++++
 11 files changed, 1050 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.md
 create mode 100644 clang-tools-extra/test/clang-tidy/checkers/modernize/Inputs/use-bit-cast/header.h
 create mode 100644 clang-tools-extra/test/clang-tidy/checkers/modernize/use-bit-cast-header.cpp
 create mode 100644 clang-tools-extra/test/clang-tidy/checkers/modernize/use-bit-cast-overload.cpp
 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 23aeb393ad016..d6b0440a7cd7f 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 62676469aefe7..5cc3963b5a296 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"
@@ -120,6 +121,7 @@ class ModernizeModule : public ClangTidyModule {
     CheckFactories.registerCheck<UnaryStaticAssertCheck>(
         "modernize-unary-static-assert");
     CheckFactories.registerCheck<UseAutoCheck>("modernize-use-auto");
+    CheckFactories.registerCheck<UseBitCastCheck>("modernize-use-bit-cast");
     CheckFactories.registerCheck<UseBoolLiteralsCheck>(
         "modernize-use-bool-literals");
     CheckFactories.registerCheck<UseConstraintsCheck>(
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..1c2e9e2e1e248
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/modernize/UseBitCastCheck.cpp
@@ -0,0 +1,362 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 "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/Tooling/FixIt.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/TypeSwitch.h"
+#include "llvm/Support/FormatVariadic.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::modernize {
+
+static bool isSupportedMemcpyObjectExpr(const Expr *ExprNode) {
+  ExprNode = ExprNode->IgnoreParenImpCasts();
+
+  if (isa<DeclRefExpr>(ExprNode))
+    return true;
+
+  if (const auto *MemberPointer = dyn_cast<BinaryOperator>(ExprNode))
+    return MemberPointer->isPtrMemOp() &&
+           isSupportedMemcpyObjectExpr(MemberPointer->getLHS());
+
+  if (const auto *Member = dyn_cast<MemberExpr>(ExprNode))
+    return isa<FieldDecl>(Member->getMemberDecl()) &&
+           isSupportedMemcpyObjectExpr(Member->getBase());
+
+  return false;
+}
+
+static const Expr *extractMemcpyObjectExpr(const Expr *ExprNode) {
+  ExprNode = ExprNode->IgnoreParenCasts();
+  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 isBitCastableMemcpyObjectType(QualType Type,
+                                          const ASTContext &Context) {
+  Type = Type.getCanonicalType().getNonReferenceType();
+  return !Type.isNull() && Type.isTriviallyCopyableType(Context);
+}
+
+static bool canAssignBitCastResult(QualType Type) {
+  Type = Type.getCanonicalType().getNonReferenceType();
+  if (Type.isConstQualified() || Type->isArrayType() ||
+      (Type.isVolatileQualified() && Type->isRecordType()))
+    return false;
+
+  const auto *Record = Type->getAsCXXRecordDecl();
+  return !Record || Record->hasSimpleCopyAssignment() ||
+         Record->hasSimpleMoveAssignment();
+}
+
+static bool isSameUnqualifiedCanonicalType(QualType LHS, QualType RHS) {
+  return LHS.getCanonicalType().getUnqualifiedType() ==
+         RHS.getCanonicalType().getUnqualifiedType();
+}
+
+static bool isUnnameableType(QualType Type) {
+  const TagDecl *Tag = Type->getAsTagDecl();
+  return Tag && !Tag->getIdentifier() && !Tag->getTypedefNameForAnonDecl();
+}
+
+static bool canUseDecltypeAsBitCastType(const Expr *DstExpr) {
+  if (const auto *Ref = dyn_cast<DeclRefExpr>(DstExpr))
+    if (const auto *Var = dyn_cast<VarDecl>(Ref->getDecl()))
+      return !Var->getType()->isReferenceType();
+
+  return isa<MemberExpr>(DstExpr);
+}
+
+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) {
+  const auto IsCurrentBody = [Current](const Stmt *Body) {
+    if (Body == Current)
+      return true;
+
+    // IgnoreUnlessSpelledInSource can make `Current` skip over a parenthesized
+    // body expression even though the enclosing statement still stores it.
+    const auto *BodyExpr = dyn_cast_or_null<Expr>(Body);
+    return BodyExpr && BodyExpr->IgnoreParenImpCasts() == Current;
+  };
+
+  return llvm::TypeSwitch<const Stmt *, bool>(Parent)
+      .Case([&](const CompoundStmt *Block) {
+        return llvm::any_of(Block->body(), IsCurrentBody);
+      })
+      .Case([&](const IfStmt *If) {
+        return IsCurrentBody(If->getThen()) || IsCurrentBody(If->getElse());
+      })
+      .Case<WhileStmt, DoStmt, ForStmt, CXXForRangeStmt>(
+          [&](const auto *Loop) { return IsCurrentBody(Loop->getBody()); })
+      .Case<LabelStmt, SwitchCase, AttributedStmt>([&](const auto *Wrapper) {
+        return IsCurrentBody(Wrapper->getSubStmt());
+      })
+      .Default(false);
+}
+
+namespace {
+
+// Accept only discarded-value uses of the memcpy call:
+//   memcpy(...);
+//   (void)memcpy(...);
+//   (memcpy(...), rhs);
+//   (lhs, memcpy(...));    if the enclosing comma expression is discarded
+//   (void)(lhs, memcpy(...));
+// Skip transparent wrappers on the way up and reject any other parent shape.
+AST_MATCHER(CallExpr, hasBitCastReplacementContext) {
+  const Stmt *Current = &Node;
+  bool SawDiscardedCommaRHS = false;
+  const CastExpr *DirectVoidCast = nullptr;
+  const BinaryOperator *CommaContext = nullptr;
+  const BinaryOperator *OverloadableCommaContext = nullptr;
+  const auto IsTransparentReplacementParent = [](const Expr *ExprNode) {
+    return isa<ExprWithCleanups, ImplicitCastExpr, MaterializeTemporaryExpr,
+               CXXBindTemporaryExpr, ParenExpr>(ExprNode);
+  };
+  const auto BindReplacementContext = [&](const Expr &ReplacementRoot) {
+    Builder->setBinding("replacementRoot",
+                        DynTypedNode::create(ReplacementRoot));
+    if (CommaContext)
+      Builder->setBinding("commaContext", DynTypedNode::create(*CommaContext));
+    if (OverloadableCommaContext)
+      Builder->setBinding("overloadableCommaContext",
+                          DynTypedNode::create(*OverloadableCommaContext));
+    return true;
+  };
+  const auto RecordCommaContext = [&](const BinaryOperator *Comma,
+                                      const Expr *Sibling) {
+    CommaContext = Comma;
+    const QualType SiblingType = Sibling->getType();
+    if (Sibling->isTypeDependent() || SiblingType.isNull() ||
+        SiblingType->isOverloadableType())
+      OverloadableCommaContext = Comma;
+  };
+  const auto IsCurrentOperand = [&](const Expr *Operand) {
+    // The traversal can skip parentheses that the BinaryOperator still owns.
+    return Operand == Current || Operand->IgnoreParenImpCasts() == Current;
+  };
+
+  while (true) {
+    const auto Parents = Finder->getASTContext().getParents(*Current);
+    if (Parents.size() != 1)
+      return false;
+
+    if (DirectVoidCast) {
+      if (const auto *ParentExpr = Parents[0].get<Expr>()) {
+        if (IsTransparentReplacementParent(ParentExpr)) {
+          Current = ParentExpr;
+          continue;
+        }
+      } else if (const auto *ParentStmt = Parents[0].get<Stmt>();
+                 ParentStmt && isStatementBody(Current, ParentStmt)) {
+        return BindReplacementContext(*DirectVoidCast);
+      }
+
+      Builder->setBinding("preservedVoidCast",
+                          DynTypedNode::create(*DirectVoidCast));
+      return BindReplacementContext(Node);
+    }
+
+    if (const auto *ParentExpr = Parents[0].get<Expr>()) {
+      if (IsTransparentReplacementParent(ParentExpr)) {
+        Current = ParentExpr;
+        continue;
+      }
+
+      if (const auto *Cast = dyn_cast<CastExpr>(ParentExpr)) {
+        if (Cast->getCastKind() != CK_ToVoid)
+          return false;
+        if (!SawDiscardedCommaRHS)
+          DirectVoidCast = Cast;
+
+        Current = Cast;
+        continue;
+      }
+
+      const auto *Comma = dyn_cast<BinaryOperator>(ParentExpr);
+      if (!Comma || Comma->getOpcode() != BO_Comma)
+        return false;
+      if (IsCurrentOperand(Comma->getLHS())) {
+        RecordCommaContext(Comma, Comma->getRHS());
+        return BindReplacementContext(Node);
+      }
+      if (!IsCurrentOperand(Comma->getRHS()))
+        return false;
+
+      // A memcpy on the right-hand side of `,` is safe only if the enclosing
+      // comma expression is itself discarded, so keep walking from the comma
+      // node. Remember every sibling because changing the memcpy result type
+      // can make an overloaded comma viable at any level.
+      RecordCommaContext(Comma, Comma->getLHS());
+      SawDiscardedCommaRHS = true;
+      Current = Comma;
+      continue;
+    }
+
+    const auto *ParentStmt = Parents[0].get<Stmt>();
+    if (!ParentStmt || !isStatementBody(Current, ParentStmt))
+      return false;
+
+    return BindReplacementContext(Node);
+  }
+}
+
+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();
+
+  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;
+}
+
+AST_MATCHER(FunctionDecl, hasSizeTypeThirdParameter) {
+  const auto *Type = Node.getType()->getAs<FunctionProtoType>();
+  return Type && Type->getNumParams() == 3 &&
+         ASTContext::hasSameType(Type->getParamType(2),
+                                 Finder->getASTContext().getSizeType());
+}
+
+} // namespace
+
+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) {
+  const auto MemcpyDecl = functionDecl(
+      hasAnyName("::memcpy", "::std::memcpy"), parameterCountIs(3),
+      returns(pointerType(pointee(voidType()))),
+      hasParameter(0, hasType(pointerType(pointee(voidType())))),
+      hasParameter(1, hasType(pointerType(
+                          pointee(qualType(isConstQualified(), voidType()))))),
+      hasSizeTypeThirdParameter());
+  Finder->addMatcher(callExpr(callee(MemcpyDecl), isBitCastMemcpyCandidate(),
+                              hasBitCastReplacementContext())
+                         .bind("memcpy"),
+                     this);
+}
+
+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 *CommaContext =
+      Result.Nodes.getNodeAs<BinaryOperator>("commaContext");
+  const auto *OverloadableCommaContext =
+      Result.Nodes.getNodeAs<BinaryOperator>("overloadableCommaContext");
+  const auto *PreservedVoidCast =
+      Result.Nodes.getNodeAs<CastExpr>("preservedVoidCast");
+  assert(MemcpyCall && "memcpy call must be bound");
+  assert(DstExpr && "destination expression must be bound");
+  assert(SrcExpr && "source expression must be bound");
+  assert(ReplacementRoot && "replacement root must be bound");
+
+  const SourceManager &SM = *Result.SourceManager;
+  StringRef DstText = tooling::fixit::getText(*DstExpr, *Result.Context);
+  StringRef SrcText = tooling::fixit::getText(*SrcExpr, *Result.Context);
+  if (DstText.empty() || SrcText.empty())
+    return;
+
+  const PrintingPolicy &Policy = Result.Context->getPrintingPolicy();
+  const QualType DstType =
+      DstExpr->getType().getNonReferenceType().getUnqualifiedType();
+  const bool UseDecltype = isUnnameableType(DstType);
+  if (UseDecltype && !canUseDecltypeAsBitCastType(DstExpr)) {
+    diag(MemcpyCall->getBeginLoc(),
+         "use 'std::bit_cast' instead of 'memcpy' for type punning");
+    return;
+  }
+  const std::string DstTypeName =
+      UseDecltype ? llvm::formatv("decltype({0})", DstText).str()
+                  : DstType.getAsString(Policy);
+  const std::string Replacement = [&]() -> std::string {
+    std::string Assignment = llvm::formatv("{0} = std::bit_cast<{1}>({2})",
+                                           DstText, DstTypeName, SrcText)
+                                 .str();
+    if (PreservedVoidCast)
+      return llvm::formatv("({0})", Assignment).str();
+    if (CommaContext &&
+        (OverloadableCommaContext || DstType->isOverloadableType()))
+      return llvm::formatv("(void)({0})", Assignment).str();
+    return Assignment;
+  }();
+
+  const DiagnosticBuilder Diag =
+      diag(MemcpyCall->getBeginLoc(),
+           "use 'std::bit_cast' instead of 'memcpy' for type punning");
+  Diag << tooling::fixit::createReplacement(*ReplacementRoot, 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..f724dbb9be5b9
--- /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 ``memcpy``-based type punning that can be rewritten as
+/// ``std::bit_cast`` in C++20 and later.
+///
+/// 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.md b/clang-tools-extra/docs/ReleaseNotes.md
index 70dd45eb3297c..79b64fdd198b4 100644
--- a/clang-tools-extra/docs/ReleaseNotes.md
+++ b/clang-tools-extra/docs/ReleaseNotes.md
@@ -107,6 +107,12 @@ infrastructure are described first, followed by tool-specific sections.
 
   Detects malformed regex patterns defined in a single string literal.
 
+- New {doc}`modernize-use-bit-cast
+  <clang-tidy/checks/modernize/use-bit-cast>` check.
+
+  Finds `memcpy`-based type punning that can be rewritten as `std::bit_cast`
+  in C++20 and later.
+
 - New {doc}`modernize-use-to-underlying
   <clang-tidy/checks/modernize/use-to-underlying>` check.
 
diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.md b/clang-tools-extra/docs/clang-tidy/checks/list.md
index ea6f13365e206..5d029c61214a2 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/list.md
+++ b/clang-tools-extra/docs/clang-tidy/checks/list.md
@@ -313,6 +313,7 @@ readability/*
 | {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.md b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-bit-cast.md
new file mode 100644
index 0000000000000..526dff539f412
--- /dev/null
+++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-bit-cast.md
@@ -0,0 +1,47 @@
+```{title} clang-tidy - modernize-use-bit-cast
+```
+
+# modernize-use-bit-cast
+
+Finds `memcpy`-based type punning that can be rewritten as `std::bit_cast` in
+C++20 and later.
+
+```cpp
+float src = 1.0f;
+unsigned int dst;
+std::memcpy(&dst, &src, sizeof(src));
+```
+
+This is rewritten to:
+
+```cpp
+float src = 1.0f;
+unsigned int dst;
+dst = std::bit_cast<unsigned int>(src);
+```
+
+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,
+- 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, or unrelated overloads such as a user-defined
+`memcpy`.
+
+If needed, the fix also inserts `#include <bit>`.
+
+## Options
+
+### IncludeStyle
+
+A string specifying which include style is used, `llvm` or `google`. The
+default is `llvm`.
diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/Inputs/use-bit-cast/header.h b/clang-tools-extra/test/clang-tidy/checkers/modernize/Inputs/use-bit-cast/header.h
new file mode 100644
index 0000000000000..b1e4156649dc1
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/Inputs/use-bit-cast/header.h
@@ -0,0 +1,14 @@
+#ifndef LLVM_CLANG_TOOLS_EXTRA_TEST_CLANG_TIDY_CHECKERS_MODERNIZE_INPUTS_USE_BIT_CAST_HEADER_H
+#define LLVM_CLANG_TOOLS_EXTRA_TEST_CLANG_TIDY_CHECKERS_MODERNIZE_INPUTS_USE_BIT_CAST_HEADER_H
+
+// CHECK-FIXES: #include <bit>
+
+inline void header_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);
+}
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_TEST_CLANG_TIDY_CHECKERS_MODERNIZE_INPUTS_USE_BIT_CAST_HEADER_H
diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-bit-cast-header.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-bit-cast-header.cpp
new file mode 100644
index 0000000000000..6c251f3a76581
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-bit-cast-header.cpp
@@ -0,0 +1,13 @@
+// RUN: %check_clang_tidy -std=c++20-or-later \
+// RUN:   -check-header %S/Inputs/use-bit-cast/header.h \
+// RUN:   %s modernize-use-bit-cast %t -- \
+// RUN:   -- -I%S/Inputs/use-bit-cast
+
+void *memcpy(void *To, const void *From, unsigned long long Size);
+
+namespace std {
+using ::memcpy;
+}
+
+#include "header.h"
+#include "header.h"
diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-bit-cast-overload.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-bit-cast-overload.cpp
new file mode 100644
index 0000000000000..07c196cc6927f
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-bit-cast-overload.cpp
@@ -0,0 +1,22 @@
+// RUN: %check_clang_tidy -std=c++20-or-later %s modernize-use-bit-cast %t
+
+void *memcpy(void *To, const void *From, int Size);
+
+namespace std {
+void *memcpy(void *To, const void *From, __SIZE_TYPE__ Size);
+}
+
+void nonstandard_size_parameter_case() {
+  float src = 1.0f;
+  unsigned int dst;
+  ::memcpy(&dst, &src, sizeof(src));
+  // CHECK-MESSAGES-NOT: :[[@LINE-1]]:3: warning: use 'std::bit_cast' instead of 'memcpy' for type punning
+}
+
+void standard_size_parameter_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
+  // CHECK-FIXES: dst = std::bit_cast<unsigned int>(src);
+}
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..60f180e44a158
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-bit-cast.cpp
@@ -0,0 +1,538 @@
+// 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, __SIZE_TYPE__ Size);
+
+namespace std {
+template <typename T, unsigned long long N>
+struct array {
+  T Storage[N];
+};
+
+using ::memcpy;
+}
+
+template <typename T>
+struct identity {
+  using type = T;
+};
+
+struct NonTrivial {
+  NonTrivial();
+  NonTrivial(const NonTrivial &);
+  int Value;
+};
+
+struct CommaSource {
+  unsigned int Value;
+};
+
+struct CommaDestination {
+  unsigned int Value;
+};
+
+void *memcpy(CommaDestination *, const CommaSource *, __SIZE_TYPE__);
+
+enum class CommaSourceEnum : unsigned int {};
+enum class CommaDestinationEnum : unsigned int {};
+
+namespace rhs_adl {
+struct Token {};
+int operator,(unsigned int, Token);
+} // namespace rhs_adl
+
+namespace lhs_adl {
+struct Token {};
+int operator,(Token, unsigned int);
+} // namespace lhs_adl
+
+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 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 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 sizeof_dereferenced_source_pointer_case() {
+  float src = 1.0f;
+  float *srcp = &src;
+  unsigned int dst;
+  std::memcpy(&dst, &src, sizeof(*srcp));
+  // 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);
+  std::memcpy(&dst, srcp, sizeof(*srcp));
+  // CHECK-MESSAGES-NOT: :[[@LINE-1]]:3: warning: use 'std::bit_cast' instead of 'memcpy' for type punning
+}
+
+void sizeof_dereferenced_destination_pointer_case() {
+  float src = 1.0f;
+  unsigned int dst;
+  unsigned int *dstp = &dst;
+  std::memcpy(&dst, &src, sizeof(*dstp));
+  // 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);
+  std::memcpy(dstp, &src, sizeof(*dstp));
+  // CHECK-MESSAGES-NOT: :[[@LINE-1]]:3: warning: use 'std::bit_cast' instead of 'memcpy' for type punning
+}
+
+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;
+    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();
+}
+
+struct OneByte {
+  unsigned char Value;
+};
+
+void anonymous_destination_case() {
+  OneByte src{0};
+  struct {
+    unsigned char Value;
+  } 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<decltype(dst)>(src);
+}
+
+struct AnonymousMemberHolder {
+  struct {
+    unsigned char Value;
+  } dst;
+};
+
+void anonymous_member_destination_case() {
+  OneByte src{0};
+  AnonymousMemberHolder holder{};
+  std::memcpy(&holder.dst, &src, sizeof(src));
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use 'std::bit_cast' instead of 'memcpy' for type punning
+  // CHECK-FIXES: holder.dst = std::bit_cast<decltype(holder.dst)>(src);
+}
+
+void lambda_destination_case() {
+  OneByte src{0};
+  auto 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<decltype(dst)>(src);
+}
+
+void lambda_reference_destination_case() {
+  OneByte src{0};
+  auto storage = [] {};
+  auto &dst = storage;
+  std::memcpy(&dst, &src, sizeof(src));
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use 'std::bit_cast' instead of 'memcpy' for type punning
+  // CHECK-FIXES: std::memcpy(&dst, &src, sizeof(src));
+}
+
+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 = (dst = std::bit_cast<unsigned int>(src), 42);
+}
+
+void comma_rhs_case() {
+  float src = 1.0f;
+  unsigned int dst;
+  (0, std::memcpy(&dst, &src, sizeof(src)));
+  // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: use 'std::bit_cast' instead of 'memcpy' for type punning
+  // CHECK-FIXES: (0, dst = std::bit_cast<unsigned int>(src));
+}
+
+void comma_record_destination_case() {
+  CommaSource src{0};
+  CommaDestination dst{0};
+  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<CommaDestination>(src)), 42);
+}
+
+void comma_enum_destination_case() {
+  CommaSourceEnum src{};
+  CommaDestinationEnum 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<CommaDestinationEnum>(src)), 42);
+}
+
+void comma_rhs_adl_case() {
+  float src = 1.0f;
+  unsigned int dst;
+  auto value = (std::memcpy(&dst, &src, sizeof(src)), rhs_adl::Token{});
+  (void)value;
+  // CHECK-MESSAGES: :[[@LINE-2]]:17: warning: use 'std::bit_cast' instead of 'memcpy' for type punning
+  // CHECK-FIXES: auto value = ((void)(dst = std::bit_cast<unsigned int>(src)), rhs_adl::Token{});
+}
+
+void comma_lhs_adl_case() {
+  float src = 1.0f;
+  unsigned int dst;
+  (lhs_adl::Token{}, std::memcpy(&dst, &src, sizeof(src)));
+  // CHECK-MESSAGES: :[[@LINE-1]]:22: warning: use 'std::bit_cast' instead of 'memcpy' for type punning
+  // CHECK-FIXES: (lhs_adl::Token{}, (void)(dst = std::bit_cast<unsigned int>(src)));
+}
+
+void nested_comma_adl_case() {
+  float src = 1.0f;
+  unsigned int dst;
+  (0, (lhs_adl::Token{}, std::memcpy(&dst, &src, sizeof(src))));
+  // CHECK-MESSAGES: :[[@LINE-1]]:26: warning: use 'std::bit_cast' instead of 'memcpy' for type punning
+  // CHECK-FIXES: (0, (lhs_adl::Token{}, (void)(dst = std::bit_cast<unsigned int>(src))));
+}
+
+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: dst = std::bit_cast<unsigned int>(src);
+}
+
+void void_cast_conditional_case(bool Cond) {
+  float src = 1.0f;
+  unsigned int dst;
+  Cond ? (void)std::memcpy(&dst, &src, sizeof(src)) : (void)0;
+  // CHECK-MESSAGES: :[[@LINE-1]]:16: warning: use 'std::bit_cast' instead of 'memcpy' for type punning
+  // CHECK-FIXES: Cond ? (void)(dst = std::bit_cast<unsigned int>(src)) : (void)0;
+}
+
+void void_cast_comma_case() {
+  float src = 1.0f;
+  unsigned int dst;
+  ((void)std::memcpy(&dst, &src, sizeof(src)), rhs_adl::Token{});
+  // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: use 'std::bit_cast' instead of 'memcpy' for type punning
+  // CHECK-FIXES: ((void)(dst = std::bit_cast<unsigned int>(src)), rhs_adl::Token{});
+}
+
+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 pointer_object_case(float *srcp) {
+  unsigned int *dstp;
+  std::memcpy(&dstp, &srcp, sizeof(srcp));
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use 'std::bit_cast' instead of 'memcpy' for type punning
+  // CHECK-FIXES: dstp = std::bit_cast<unsigned int *>(srcp);
+}
+
+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 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
+}
+
+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: :[[@LINE-1]]:3: warning: use 'std::bit_cast' instead of 'memcpy' for type punning
+  // CHECK-FIXES: dst = std::bit_cast<unsigned int>(src);
+}
+
+void volatile_destination_case() {
+  float src = 1.0f;
+  volatile unsigned int dst;
+  std::memcpy(const_cast<unsigned int *>(&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 volatile_record_destination_case(CommaSource src) {
+  volatile CommaDestination dst{0};
+  std::memcpy(const_cast<CommaDestination *>(&dst), &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
+
+void global_overload_case() {
+  CommaSource src{0};
+  CommaDestination dst{0};
+  memcpy(&dst, &src, sizeof(src));
+  // CHECK-MESSAGES-NOT: :[[@LINE-1]]:3: warning: use 'std::bit_cast' instead of 'memcpy' for type punning
+}
+
+#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;
+}
+
+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);
+}
+
+template void non_dependent_template_case<int>();
+
+template <typename T>
+concept MemcpyInRequires = requires(float &src, unsigned int &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 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
+}



More information about the cfe-commits mailing list