Skip to content

Commit cb4ea5f

Browse files
author
Carlos Gálvez
committed
[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
1 parent b8b8fbe commit cb4ea5f

File tree

7 files changed

+152
-0
lines changed

7 files changed

+152
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//===--- BitCastPointersCheck.cpp - clang-tidy ----------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "BitCastPointersCheck.h"
10+
#include "clang/ASTMatchers/ASTMatchFinder.h"
11+
12+
using namespace clang::ast_matchers;
13+
14+
namespace clang::tidy::bugprone {
15+
16+
void BitCastPointersCheck::registerMatchers(MatchFinder *Finder) {
17+
auto IsPointerType = refersToType(qualType(isAnyPointer()));
18+
Finder->addMatcher(callExpr(callee(functionDecl(allOf(
19+
hasName("::std::bit_cast"),
20+
hasTemplateArgument(0, IsPointerType),
21+
hasTemplateArgument(1, IsPointerType)))))
22+
.bind("x"),
23+
this);
24+
}
25+
26+
void BitCastPointersCheck::check(const MatchFinder::MatchResult &Result) {
27+
if (const auto *MatchedDecl = Result.Nodes.getNodeAs<CallExpr>("x"))
28+
diag(MatchedDecl->getBeginLoc(),
29+
"do not use std::bit_cast on pointers; use it on values instead");
30+
}
31+
32+
} // namespace clang::tidy::bugprone
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
//===--- BitCastPointersCheck.h - clang-tidy --------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_BITCASTPOINTERSCHECK_H
10+
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_BITCASTPOINTERSCHECK_H
11+
12+
#include "../ClangTidyCheck.h"
13+
14+
namespace clang::tidy::bugprone {
15+
16+
/// Warns about usage of ``std::bit_cast`` when the input and output types are
17+
/// pointers.
18+
///
19+
/// For the user-facing documentation see:
20+
/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone/bit-cast-pointers.html
21+
class BitCastPointersCheck : public ClangTidyCheck {
22+
public:
23+
BitCastPointersCheck(StringRef Name, ClangTidyContext *Context)
24+
: ClangTidyCheck(Name, Context) {}
25+
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
26+
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
27+
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
28+
return LangOpts.CPlusPlus20;
29+
}
30+
};
31+
32+
} // namespace clang::tidy::bugprone
33+
34+
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_BITCASTPOINTERSCHECK_H

clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp

+3
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "AssertSideEffectCheck.h"
1515
#include "AssignmentInIfConditionCheck.h"
1616
#include "BadSignalToKillThreadCheck.h"
17+
#include "BitCastPointersCheck.h"
1718
#include "BoolPointerImplicitConversionCheck.h"
1819
#include "BranchCloneCheck.h"
1920
#include "CastingThroughVoidCheck.h"
@@ -108,6 +109,8 @@ class BugproneModule : public ClangTidyModule {
108109
"bugprone-assignment-in-if-condition");
109110
CheckFactories.registerCheck<BadSignalToKillThreadCheck>(
110111
"bugprone-bad-signal-to-kill-thread");
112+
CheckFactories.registerCheck<BitCastPointersCheck>(
113+
"bugprone-bit-cast-pointers");
111114
CheckFactories.registerCheck<BoolPointerImplicitConversionCheck>(
112115
"bugprone-bool-pointer-implicit-conversion");
113116
CheckFactories.registerCheck<BranchCloneCheck>("bugprone-branch-clone");

clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ add_clang_library(clangTidyBugproneModule
88
AssertSideEffectCheck.cpp
99
AssignmentInIfConditionCheck.cpp
1010
BadSignalToKillThreadCheck.cpp
11+
BitCastPointersCheck.cpp
1112
BoolPointerImplicitConversionCheck.cpp
1213
BranchCloneCheck.cpp
1314
BugproneTidyModule.cpp

clang-tools-extra/docs/ReleaseNotes.rst

+6
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,12 @@ Improvements to clang-tidy
9898
New checks
9999
^^^^^^^^^^
100100

101+
- New :doc:`bugprone-bit-cast-pointers
102+
<clang-tidy/checks/bugprone/bit-cast-pointers>` check.
103+
104+
Warns about usage of ``std::bit_cast`` when the input and output types are
105+
pointers.
106+
101107
New check aliases
102108
^^^^^^^^^^^^^^^^^
103109

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
.. title:: clang-tidy - bugprone-bit-cast-pointers
2+
3+
bugprone-bit-cast-pointers
4+
==========================
5+
6+
Warns about usage of ``std::bit_cast`` when the input and output types are
7+
pointers.
8+
9+
The motivation is that ``std::bit_cast`` is advertised as the safe alternative
10+
to type punning via ``reinterpret_cast`` in modern C++. However, one should not
11+
blindly replace ``reinterpret_cast`` with ``std::bit_cast``, as follows:
12+
13+
.. code-block:: c++
14+
15+
int x{};
16+
-float y = *reinterpret_cast<float*>(&x);
17+
+float y = *std::bit_cast<float*>(&x);
18+
19+
The drop-in replacement behaves exactly the same as ``reinterpret_cast``, and
20+
Undefined Behavior is still invoked. ``std::bit_cast`` is copying the bytes of
21+
the input pointer, not the pointee, into an output pointer of a different type,
22+
which may violate the strict aliasing rules. However, simply looking at the
23+
code, it looks "safe", because it uses ``std::bit_cast`` which is advertised as
24+
safe.
25+
26+
The solution to safe type punning is to apply ``std::bit_cast`` on value types,
27+
not on pointer types:
28+
29+
.. code-block:: c++
30+
31+
int x{};
32+
float y = std::bit_cast<float>(x);
33+
34+
This way, the bytes of the input object are copied into the output object, which
35+
is much safer. Do note that Undefined Behavior can still occur, if there is no
36+
value of type ``To`` corresponding to the value representation produced.
37+
Compilers are able to optimize this copy and generate identical assembly to the
38+
original ``reinterpret_cast`` version.
39+
40+
Alternatively, if a cast between pointers is truly wanted, ``reinterpret_cast``
41+
should be used, to clearly convey the intent and enable warnings from compilers
42+
and linters, which should be addressed accordingly.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// RUN: %check_clang_tidy -std=c++20-or-later %s bugprone-bit-cast-pointers %t
2+
3+
namespace std
4+
{
5+
template <typename To, typename From>
6+
To bit_cast(From from)
7+
{
8+
// Dummy implementation for the purpose of the test.
9+
// We don't want to include <cstring> to get std::memcpy.
10+
To to{};
11+
return to;
12+
}
13+
}
14+
15+
void pointer2pointer()
16+
{
17+
int x{};
18+
float bad = *std::bit_cast<float*>(&x); // UB, but looks safe due to std::bit_cast
19+
// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: do not use std::bit_cast on pointers; use it on values instead [bugprone-bit-cast-pointers]
20+
float good = std::bit_cast<float>(x); // Well-defined
21+
}
22+
23+
// Pointer-integer conversions are allowed by this check
24+
void int2pointer()
25+
{
26+
unsigned long long addr{};
27+
float* p = std::bit_cast<float*>(addr);
28+
}
29+
30+
void pointer2int()
31+
{
32+
float* p{};
33+
auto addr = std::bit_cast<unsigned long long>(p);
34+
}

0 commit comments

Comments
 (0)