[clang-tools-extra] [clang-tidy] Create bugprone-bit-cast-pointers check (PR #108083)
Carlos Galvez via cfe-commits
cfe-commits at lists.llvm.org
Tue Sep 17 02:22:00 PDT 2024
https://github.com/carlosgalvezp updated https://github.com/llvm/llvm-project/pull/108083
>From d7eacb7d6614b374a959c33a7394f2652df32982 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Carlos=20G=C3=A1lvez?= <carlos.galvez at zenseact.com>
Date: Tue, 10 Sep 2024 13:46:51 +0000
Subject: [PATCH] [clang-tidy] Create bugprone-bit-cast-pointers check
To detect unsafe use of bit_cast. Otherwise, bit_cast bypasses all
checks done by compilers and linters.
Fixes #106987
---
.../bugprone/BitCastPointersCheck.cpp | 45 ++++++++++++++
.../bugprone/BitCastPointersCheck.h | 34 +++++++++++
.../bugprone/BugproneTidyModule.cpp | 3 +
.../clang-tidy/bugprone/CMakeLists.txt | 1 +
clang-tools-extra/docs/ReleaseNotes.rst | 6 ++
.../checks/bugprone/bit-cast-pointers.rst | 52 ++++++++++++++++
.../checkers/bugprone/bit-cast-pointers.cpp | 61 +++++++++++++++++++
7 files changed, 202 insertions(+)
create mode 100644 clang-tools-extra/clang-tidy/bugprone/BitCastPointersCheck.cpp
create mode 100644 clang-tools-extra/clang-tidy/bugprone/BitCastPointersCheck.h
create mode 100644 clang-tools-extra/docs/clang-tidy/checks/bugprone/bit-cast-pointers.rst
create mode 100644 clang-tools-extra/test/clang-tidy/checkers/bugprone/bit-cast-pointers.cpp
diff --git a/clang-tools-extra/clang-tidy/bugprone/BitCastPointersCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/BitCastPointersCheck.cpp
new file mode 100644
index 00000000000000..30a700fc8c0062
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/bugprone/BitCastPointersCheck.cpp
@@ -0,0 +1,45 @@
+//===--- BitCastPointersCheck.cpp - clang-tidy ----------------------------===//
+//
+// 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 "BitCastPointersCheck.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::bugprone {
+
+void BitCastPointersCheck::registerMatchers(MatchFinder *Finder) {
+ auto IsPointerType = refersToType(qualType(isAnyPointer()));
+ Finder->addMatcher(callExpr(hasDeclaration(functionDecl(allOf(
+ hasName("::std::bit_cast"),
+ hasTemplateArgument(0, IsPointerType),
+ hasTemplateArgument(1, IsPointerType)))))
+ .bind("bit_cast"),
+ this);
+
+ auto IsDoublePointerType =
+ hasType(qualType(pointsTo(qualType(isAnyPointer()))));
+ Finder->addMatcher(callExpr(hasArgument(0, IsDoublePointerType),
+ hasArgument(1, IsDoublePointerType),
+ hasDeclaration(functionDecl(hasName("::memcpy"))))
+ .bind("memcpy"),
+ this);
+}
+
+void BitCastPointersCheck::check(const MatchFinder::MatchResult &Result) {
+ if (const auto *MatchedDecl = Result.Nodes.getNodeAs<CallExpr>("bit_cast"))
+ diag(MatchedDecl->getBeginLoc(),
+ "do not use std::bit_cast to cast between pointers")
+ << MatchedDecl->getSourceRange();
+ if (const auto *MatchedDecl = Result.Nodes.getNodeAs<CallExpr>("memcpy"))
+ diag(MatchedDecl->getBeginLoc(),
+ "do not use memcpy to cast between pointers")
+ << MatchedDecl->getSourceRange();
+}
+
+} // namespace clang::tidy::bugprone
diff --git a/clang-tools-extra/clang-tidy/bugprone/BitCastPointersCheck.h b/clang-tools-extra/clang-tidy/bugprone/BitCastPointersCheck.h
new file mode 100644
index 00000000000000..857a35be13c72f
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/bugprone/BitCastPointersCheck.h
@@ -0,0 +1,34 @@
+//===--- BitCastPointersCheck.h - clang-tidy --------------------*- C++ -*-===//
+//
+// 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_BUGPRONE_BITCASTPOINTERSCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_BITCASTPOINTERSCHECK_H
+
+#include "../ClangTidyCheck.h"
+
+namespace clang::tidy::bugprone {
+
+/// Warns about usage of ``std::bit_cast`` when the input and output types are
+/// pointers.
+///
+/// For the user-facing documentation see:
+/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone/bit-cast-pointers.html
+class BitCastPointersCheck : public ClangTidyCheck {
+public:
+ BitCastPointersCheck(StringRef Name, ClangTidyContext *Context)
+ : ClangTidyCheck(Name, Context) {}
+ void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+ void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+ bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
+ return LangOpts.CPlusPlus;
+ }
+};
+
+} // namespace clang::tidy::bugprone
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_BITCASTPOINTERSCHECK_H
diff --git a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
index 689eb92a3d8d17..931624224d784b 100644
--- a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
@@ -14,6 +14,7 @@
#include "AssertSideEffectCheck.h"
#include "AssignmentInIfConditionCheck.h"
#include "BadSignalToKillThreadCheck.h"
+#include "BitCastPointersCheck.h"
#include "BoolPointerImplicitConversionCheck.h"
#include "BranchCloneCheck.h"
#include "CastingThroughVoidCheck.h"
@@ -108,6 +109,8 @@ class BugproneModule : public ClangTidyModule {
"bugprone-assignment-in-if-condition");
CheckFactories.registerCheck<BadSignalToKillThreadCheck>(
"bugprone-bad-signal-to-kill-thread");
+ CheckFactories.registerCheck<BitCastPointersCheck>(
+ "bugprone-bit-cast-pointers");
CheckFactories.registerCheck<BoolPointerImplicitConversionCheck>(
"bugprone-bool-pointer-implicit-conversion");
CheckFactories.registerCheck<BranchCloneCheck>("bugprone-branch-clone");
diff --git a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
index cb0d8ae98bac58..55766b3d9bf91c 100644
--- a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
@@ -8,6 +8,7 @@ add_clang_library(clangTidyBugproneModule
AssertSideEffectCheck.cpp
AssignmentInIfConditionCheck.cpp
BadSignalToKillThreadCheck.cpp
+ BitCastPointersCheck.cpp
BoolPointerImplicitConversionCheck.cpp
BranchCloneCheck.cpp
BugproneTidyModule.cpp
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 8d028f8863cb7a..555929ffd5afe8 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -98,6 +98,12 @@ Improvements to clang-tidy
New checks
^^^^^^^^^^
+- New :doc:`bugprone-bit-cast-pointers
+ <clang-tidy/checks/bugprone/bit-cast-pointers>` check.
+
+ Warns about usage of ``std::bit_cast`` when the input and output types are
+ pointers.
+
New check aliases
^^^^^^^^^^^^^^^^^
diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone/bit-cast-pointers.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone/bit-cast-pointers.rst
new file mode 100644
index 00000000000000..9089b370374662
--- /dev/null
+++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone/bit-cast-pointers.rst
@@ -0,0 +1,52 @@
+.. title:: clang-tidy - bugprone-bit-cast-pointers
+
+bugprone-bit-cast-pointers
+==========================
+
+Warns about code that tries to cast between pointers by means of
+``std::bit_cast`` or ``memcpy``.
+
+The motivation is that ``std::bit_cast`` is advertised as the safe alternative
+to type punning via ``reinterpret_cast`` in modern C++. However, one should not
+blindly replace ``reinterpret_cast`` with ``std::bit_cast``, as follows:
+
+.. code-block:: c++
+
+ int x{};
+ -float y = *reinterpret_cast<float*>(&x);
+ +float y = *std::bit_cast<float*>(&x);
+
+The drop-in replacement behaves exactly the same as ``reinterpret_cast``, and
+Undefined Behavior is still invoked. ``std::bit_cast`` is copying the bytes of
+the input pointer, not the pointee, into an output pointer of a different type,
+which may violate the strict aliasing rules. However, simply looking at the
+code, it looks "safe", because it uses ``std::bit_cast`` which is advertised as
+safe.
+
+The solution to safe type punning is to apply ``std::bit_cast`` on value types,
+not on pointer types:
+
+.. code-block:: c++
+
+ int x{};
+ float y = std::bit_cast<float>(x);
+
+This way, the bytes of the input object are copied into the output object, which
+is much safer. Do note that Undefined Behavior can still occur, if there is no
+value of type ``To`` corresponding to the value representation produced.
+Compilers may be able to optimize this copy and generate identical assembly to
+the original ``reinterpret_cast`` version.
+
+Code before C++20 may backport ``std::bit_cast`` by means of ``memcpy``, or
+simply call ``memcpy`` directly, which is equally problematic. This is also
+detected by this check:
+
+.. code-block:: c++
+
+ int* x{};
+ float* y{};
+ std::memcpy(&y, &x, sizeof(x));
+
+Alternatively, if a cast between pointers is truly wanted, ``reinterpret_cast``
+should be used, to clearly convey the intent and enable warnings from compilers
+and linters, which should be addressed accordingly.
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/bit-cast-pointers.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/bit-cast-pointers.cpp
new file mode 100644
index 00000000000000..f302453b9a90cf
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/bit-cast-pointers.cpp
@@ -0,0 +1,61 @@
+// RUN: %check_clang_tidy %s bugprone-bit-cast-pointers %t
+
+void memcpy(void* to, void* dst, unsigned long long size)
+{
+ // Dummy implementation for the purpose of the test
+}
+
+namespace std
+{
+template <typename To, typename From>
+To bit_cast(From from)
+{
+ // Dummy implementation for the purpose of the test
+ To to{};
+ return to;
+}
+
+using ::memcpy;
+}
+
+void pointer2pointer()
+{
+ int x{};
+ float bad = *std::bit_cast<float*>(&x); // UB, but looks safe due to std::bit_cast
+ // CHECK-MESSAGES: :[[@LINE-1]]:16: warning: do not use std::bit_cast to cast between pointers [bugprone-bit-cast-pointers]
+ float good = std::bit_cast<float>(x); // Well-defined
+
+ using IntPtr = int*;
+ using FloatPtr = float*;
+ IntPtr x2{};
+ float bad2 = *std::bit_cast<FloatPtr>(x2);
+ // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: do not use std::bit_cast to cast between pointers [bugprone-bit-cast-pointers]
+}
+
+void pointer2pointer_memcpy()
+{
+ int x{};
+ int* px{};
+ float y{};
+ float* py{};
+
+ memcpy(&py, &px, sizeof(px));
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not use memcpy to cast between pointers [bugprone-bit-cast-pointers]
+ std::memcpy(&py, &px, sizeof(px));
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not use memcpy to cast between pointers [bugprone-bit-cast-pointers]
+
+ std::memcpy(&y, &x, sizeof(x));
+}
+
+// Pointer-integer conversions are allowed by this check
+void int2pointer()
+{
+ unsigned long long addr{};
+ float* p = std::bit_cast<float*>(addr);
+}
+
+void pointer2int()
+{
+ float* p{};
+ auto addr = std::bit_cast<unsigned long long>(p);
+}
More information about the cfe-commits
mailing list