Skip to content

Commit fb0ef6b

Browse files
carlosgalvezpCarlos Gálvez
and
Carlos Gálvez
authored
[clang-tidy] Create bugprone-bitwise-pointer-cast check (#108083)
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. This is currently not caught by any other means. Fixes #106987 --------- Co-authored-by: Carlos Gálvez <[email protected]>
1 parent d2408c4 commit fb0ef6b

File tree

9 files changed

+247
-0
lines changed

9 files changed

+247
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
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+
if (getLangOpts().CPlusPlus20) {
18+
auto IsPointerType = refersToType(qualType(isAnyPointer()));
19+
Finder->addMatcher(callExpr(hasDeclaration(functionDecl(allOf(
20+
hasName("::std::bit_cast"),
21+
hasTemplateArgument(0, IsPointerType),
22+
hasTemplateArgument(1, IsPointerType)))))
23+
.bind("bit_cast"),
24+
this);
25+
}
26+
27+
auto IsDoublePointerType =
28+
hasType(qualType(pointsTo(qualType(isAnyPointer()))));
29+
Finder->addMatcher(callExpr(hasArgument(0, IsDoublePointerType),
30+
hasArgument(1, IsDoublePointerType),
31+
hasDeclaration(functionDecl(hasName("::memcpy"))))
32+
.bind("memcpy"),
33+
this);
34+
}
35+
36+
void BitwisePointerCastCheck::check(const MatchFinder::MatchResult &Result) {
37+
if (const auto *Call = Result.Nodes.getNodeAs<CallExpr>("bit_cast"))
38+
diag(Call->getBeginLoc(),
39+
"do not use 'std::bit_cast' to cast between pointers")
40+
<< Call->getSourceRange();
41+
else if (const auto *Call = Result.Nodes.getNodeAs<CallExpr>("memcpy"))
42+
diag(Call->getBeginLoc(), "do not use 'memcpy' to cast between pointers")
43+
<< Call->getSourceRange();
44+
}
45+
46+
} // namespace clang::tidy::bugprone
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

+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 "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

+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+
BitwisePointerCastCheck.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
@@ -109,6 +109,12 @@ Improvements to clang-tidy
109109
New checks
110110
^^^^^^^^^^
111111

112+
- New :doc:`bugprone-bitwise-pointer-cast
113+
<clang-tidy/checks/bugprone/bitwise-pointer-cast>` check.
114+
115+
Warns about code that tries to cast between pointers by means of
116+
``std::bit_cast`` or ``memcpy``.
117+
112118
- New :doc:`bugprone-tagged-union-member-count
113119
<clang-tidy/checks/bugprone/tagged-union-member-count>` check.
114120

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

+1
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>`,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// RUN: %check_clang_tidy -std=c++20 %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+
std::memcpy(&p, &addr, sizeof(addr));
56+
}
57+
58+
void pointer2int()
59+
{
60+
float* p{};
61+
auto addr = std::bit_cast<unsigned long long>(p);
62+
std::memcpy(&addr, &p, sizeof(p));
63+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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+
using ::memcpy;
11+
}
12+
13+
void pointer2pointer()
14+
{
15+
int x{};
16+
int* px{};
17+
float y{};
18+
float* py{};
19+
20+
memcpy(&py, &px, sizeof(px));
21+
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not use 'memcpy' to cast between pointers [bugprone-bitwise-pointer-cast]
22+
std::memcpy(&py, &px, sizeof(px));
23+
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not use 'memcpy' to cast between pointers [bugprone-bitwise-pointer-cast]
24+
25+
std::memcpy(&y, &x, sizeof(x));
26+
}
27+
28+
// Pointer-integer conversions are allowed by this check
29+
void int2pointer()
30+
{
31+
unsigned long long addr{};
32+
float* p{};
33+
std::memcpy(&p, &addr, sizeof(addr));
34+
}
35+
36+
void pointer2int()
37+
{
38+
unsigned long long addr{};
39+
float* p{};
40+
std::memcpy(&addr, &p, sizeof(p));
41+
}

0 commit comments

Comments
 (0)