Skip to content

Commit 1b1d54e

Browse files
author
Carlos Gálvez
committed
[clang-tidy] Create bugprone-bitwise-pointer-cast check
To detect unsafe usages of casting a pointer to another via copying the bytes from one into the other, either via std::bit_cast or via memcpy.use of bit_cast. This is currently not caught by any other means. Fixes #106987
1 parent 52a9ba7 commit 1b1d54e

File tree

8 files changed

+203
-1
lines changed

8 files changed

+203
-1
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
//===--- BitwisePointerCastCheck.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 "BitwisePointerCastCheck.h"
10+
#include "clang/ASTMatchers/ASTMatchFinder.h"
11+
12+
using namespace clang::ast_matchers;
13+
14+
namespace clang::tidy::bugprone {
15+
16+
void BitwisePointerCastCheck::registerMatchers(MatchFinder *Finder) {
17+
auto IsPointerType = refersToType(qualType(isAnyPointer()));
18+
Finder->addMatcher(callExpr(hasDeclaration(functionDecl(allOf(
19+
hasName("::std::bit_cast"),
20+
hasTemplateArgument(0, IsPointerType),
21+
hasTemplateArgument(1, IsPointerType)))))
22+
.bind("bit_cast"),
23+
this);
24+
25+
auto IsDoublePointerType =
26+
hasType(qualType(pointsTo(qualType(isAnyPointer()))));
27+
Finder->addMatcher(callExpr(hasArgument(0, IsDoublePointerType),
28+
hasArgument(1, IsDoublePointerType),
29+
hasDeclaration(functionDecl(hasName("::memcpy"))))
30+
.bind("memcpy"),
31+
this);
32+
}
33+
34+
void BitwisePointerCastCheck::check(const MatchFinder::MatchResult &Result) {
35+
if (const auto *Call = Result.Nodes.getNodeAs<CallExpr>("bit_cast"))
36+
diag(Call->getBeginLoc(),
37+
"do not use 'std::bit_cast' to cast between pointers")
38+
<< Call->getSourceRange();
39+
else if (const auto *Call = Result.Nodes.getNodeAs<CallExpr>("memcpy"))
40+
diag(Call->getBeginLoc(), "do not use 'memcpy' to cast between pointers")
41+
<< Call->getSourceRange();
42+
}
43+
44+
} // namespace clang::tidy::bugprone
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
//===--- BitwisePointerCastCheck.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_BITWISEPOINTERCASTCHECK_H
10+
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_BITWISEPOINTERCASTCHECK_H
11+
12+
#include "../ClangTidyCheck.h"
13+
14+
namespace clang::tidy::bugprone {
15+
16+
/// Warns about code that tries to cast between pointers by means of
17+
/// ``std::bit_cast`` or ``memcpy``.
18+
///
19+
/// For the user-facing documentation see:
20+
/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone/bitwise-pointer-cast.html
21+
class BitwisePointerCastCheck : public ClangTidyCheck {
22+
public:
23+
BitwisePointerCastCheck(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.CPlusPlus;
29+
}
30+
};
31+
32+
} // namespace clang::tidy::bugprone
33+
34+
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_BITWISEPOINTERCASTCHECK_H

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

Lines changed: 3 additions & 0 deletions
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 "BitwisePointerCastCheck.h"
1718
#include "BoolPointerImplicitConversionCheck.h"
1819
#include "BranchCloneCheck.h"
1920
#include "CastingThroughVoidCheck.h"
@@ -109,6 +110,8 @@ class BugproneModule : public ClangTidyModule {
109110
"bugprone-assignment-in-if-condition");
110111
CheckFactories.registerCheck<BadSignalToKillThreadCheck>(
111112
"bugprone-bad-signal-to-kill-thread");
113+
CheckFactories.registerCheck<BitwisePointerCastCheck>(
114+
"bugprone-bitwise-pointer-cast");
112115
CheckFactories.registerCheck<BoolPointerImplicitConversionCheck>(
113116
"bugprone-bool-pointer-implicit-conversion");
114117
CheckFactories.registerCheck<BranchCloneCheck>("bugprone-branch-clone");

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

Lines changed: 1 addition & 0 deletions
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+
BitwisePointerCastCheck.cpp
1112
BoolPointerImplicitConversionCheck.cpp
1213
BranchCloneCheck.cpp
1314
BugproneTidyModule.cpp

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,12 @@ Improvements to clang-tidy
103103
New checks
104104
^^^^^^^^^^
105105

106+
- New :doc:`bugprone-bitwise-pointer-cast
107+
<clang-tidy/checks/bugprone/bitwise-pointer-cast>` check.
108+
109+
Warns about code that tries to cast between pointers by means of
110+
``std::bit_cast`` or ``memcpy``.
111+
106112
- New :doc:`bugprone-tagged-union-member-count
107113
<clang-tidy/checks/bugprone/tagged-union-member-count>` check.
108114

@@ -168,7 +174,7 @@ Changes in existing checks
168174

169175
- Improved :doc:`modernize-avoid-c-arrays
170176
<clang-tidy/checks/modernize/avoid-c-arrays>` check to suggest using ``std::span``
171-
as a replacement for parameters of incomplete C array type in C++20 and
177+
as a replacement for parameters of incomplete C array type in C++20 and
172178
``std::array`` or ``std::vector`` before C++20.
173179

174180
- Improved :doc:`modernize-min-max-use-initializer-list
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
.. title:: clang-tidy - bugprone-bitwise-pointer-cast
2+
3+
bugprone-bitwise-pointer-cast
4+
=============================
5+
6+
Warns about code that tries to cast between pointers by means of
7+
``std::bit_cast`` or ``memcpy``.
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 may be able to optimize this copy and generate identical assembly to
38+
the original ``reinterpret_cast`` version.
39+
40+
Code before C++20 may backport ``std::bit_cast`` by means of ``memcpy``, or
41+
simply call ``memcpy`` directly, which is equally problematic. This is also
42+
detected by this check:
43+
44+
.. code-block:: c++
45+
46+
int* x{};
47+
float* y{};
48+
std::memcpy(&y, &x, sizeof(x));
49+
50+
Alternatively, if a cast between pointers is truly wanted, ``reinterpret_cast``
51+
should be used, to clearly convey the intent and enable warnings from compilers
52+
and linters, which should be addressed accordingly.

clang-tools-extra/docs/clang-tidy/checks/list.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ Clang-Tidy Checks
8181
:doc:`bugprone-assert-side-effect <bugprone/assert-side-effect>`,
8282
:doc:`bugprone-assignment-in-if-condition <bugprone/assignment-in-if-condition>`,
8383
:doc:`bugprone-bad-signal-to-kill-thread <bugprone/bad-signal-to-kill-thread>`,
84+
:doc:`bugprone-bitwise-pointer-cast <bugprone/bitwise-pointer-cast>`,
8485
:doc:`bugprone-bool-pointer-implicit-conversion <bugprone/bool-pointer-implicit-conversion>`, "Yes"
8586
:doc:`bugprone-branch-clone <bugprone/branch-clone>`,
8687
:doc:`bugprone-casting-through-void <bugprone/casting-through-void>`,
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// RUN: %check_clang_tidy %s bugprone-bitwise-pointer-cast %t
2+
3+
void memcpy(void* to, void* dst, unsigned long long size)
4+
{
5+
// Dummy implementation for the purpose of the test
6+
}
7+
8+
namespace std
9+
{
10+
template <typename To, typename From>
11+
To bit_cast(From from)
12+
{
13+
// Dummy implementation for the purpose of the test
14+
To to{};
15+
return to;
16+
}
17+
18+
using ::memcpy;
19+
}
20+
21+
void pointer2pointer()
22+
{
23+
int x{};
24+
float bad = *std::bit_cast<float*>(&x); // UB, but looks safe due to std::bit_cast
25+
// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: do not use 'std::bit_cast' to cast between pointers [bugprone-bitwise-pointer-cast]
26+
float good = std::bit_cast<float>(x); // Well-defined
27+
28+
using IntPtr = int*;
29+
using FloatPtr = float*;
30+
IntPtr x2{};
31+
float bad2 = *std::bit_cast<FloatPtr>(x2);
32+
// CHECK-MESSAGES: :[[@LINE-1]]:17: warning: do not use 'std::bit_cast' to cast between pointers [bugprone-bitwise-pointer-cast]
33+
}
34+
35+
void pointer2pointer_memcpy()
36+
{
37+
int x{};
38+
int* px{};
39+
float y{};
40+
float* py{};
41+
42+
memcpy(&py, &px, sizeof(px));
43+
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not use 'memcpy' to cast between pointers [bugprone-bitwise-pointer-cast]
44+
std::memcpy(&py, &px, sizeof(px));
45+
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not use 'memcpy' to cast between pointers [bugprone-bitwise-pointer-cast]
46+
47+
std::memcpy(&y, &x, sizeof(x));
48+
}
49+
50+
// Pointer-integer conversions are allowed by this check
51+
void int2pointer()
52+
{
53+
unsigned long long addr{};
54+
float* p = std::bit_cast<float*>(addr);
55+
}
56+
57+
void pointer2int()
58+
{
59+
float* p{};
60+
auto addr = std::bit_cast<unsigned long long>(p);
61+
}

0 commit comments

Comments
 (0)