From 3e07259e08a7c884438359a2634c6e5ed00989eb Mon Sep 17 00:00:00 2001 From: leftibot Date: Fri, 6 Nov 2026 15:12:10 -0700 Subject: [PATCH] Fix #6: Add prefer-reinterpret-cast check for C-style and function-style bit casts C-style and function-style casts silently fall back to reinterpret semantics when clang resolves them to a BitCast or LValueBitCast, hiding a type pun behind innocuous syntax. The new clang-query check flags these casts, including ones that also discard const (where the bit cast is an implicit child of the explicit cast) and ones that only become bit casts in template instantiations, so they can be rewritten as explicit reinterpret_cast. Co-Authored-By: Claude Opus 5.5 --- .../prefer-reinterpret-cast.query | 13 ++++++++++++ .../tests/fail/bit_cast_discarding_const.cpp | 9 ++++++++ .../tests/fail/bit_cast_in_template.cpp | 6 ++++++ .../tests/fail/c_style_pointer_bit_cast.cpp | 4 ++++ .../tests/fail/c_style_reference_bit_cast.cpp | 4 ++++ .../fail/function_style_pointer_bit_cast.cpp | 6 ++++++ .../tests/pass/reinterpret_cast.cpp | 6 ++++++ .../tests/pass/template_without_bit_cast.cpp | 6 ++++++ .../tests/pass/value_conversions.cpp | 21 +++++++++++++++++++ 9 files changed, 75 insertions(+) create mode 100644 checks/clang-query/prefer-reinterpret-cast/prefer-reinterpret-cast.query create mode 100644 checks/clang-query/prefer-reinterpret-cast/tests/fail/bit_cast_discarding_const.cpp create mode 100644 checks/clang-query/prefer-reinterpret-cast/tests/fail/bit_cast_in_template.cpp create mode 100644 checks/clang-query/prefer-reinterpret-cast/tests/fail/c_style_pointer_bit_cast.cpp create mode 100644 checks/clang-query/prefer-reinterpret-cast/tests/fail/c_style_reference_bit_cast.cpp create mode 100644 checks/clang-query/prefer-reinterpret-cast/tests/fail/function_style_pointer_bit_cast.cpp create mode 100644 checks/clang-query/prefer-reinterpret-cast/tests/pass/reinterpret_cast.cpp create mode 100644 checks/clang-query/prefer-reinterpret-cast/tests/pass/template_without_bit_cast.cpp create mode 100644 checks/clang-query/prefer-reinterpret-cast/tests/pass/value_conversions.cpp diff --git a/checks/clang-query/prefer-reinterpret-cast/prefer-reinterpret-cast.query b/checks/clang-query/prefer-reinterpret-cast/prefer-reinterpret-cast.query new file mode 100644 index 0000000..d944813 --- /dev/null +++ b/checks/clang-query/prefer-reinterpret-cast/prefer-reinterpret-cast.query @@ -0,0 +1,13 @@ +# Flags C-style and function-style casts that reinterpret an object's bits +# (clang's BitCast / LValueBitCast cast kinds); spell these as reinterpret_cast +# (plus const_cast when constness is also discarded) so the intent is explicit. +# Template instantiations are traversed so that dependent casts are reported +# once they resolve to a bit cast. +set traversal AsIs +match explicitCastExpr( + isExpansionInMainFile(), + anyOf(cStyleCastExpr(), cxxFunctionalCastExpr()), + anyOf( + hasCastKind("CK_BitCast"), + hasCastKind("CK_LValueBitCast"), + has(implicitCastExpr(anyOf(hasCastKind("CK_BitCast"), hasCastKind("CK_LValueBitCast")))))) diff --git a/checks/clang-query/prefer-reinterpret-cast/tests/fail/bit_cast_discarding_const.cpp b/checks/clang-query/prefer-reinterpret-cast/tests/fail/bit_cast_discarding_const.cpp new file mode 100644 index 0000000..556cbe1 --- /dev/null +++ b/checks/clang-query/prefer-reinterpret-cast/tests/fail/bit_cast_discarding_const.cpp @@ -0,0 +1,9 @@ +[[nodiscard]] char *mutable_bytes(const int *value) +{ + return (char *)value;// expect-finding +} + +[[nodiscard]] void *mutable_address(const int &value) +{ + return (void *)&value;// expect-finding +} diff --git a/checks/clang-query/prefer-reinterpret-cast/tests/fail/bit_cast_in_template.cpp b/checks/clang-query/prefer-reinterpret-cast/tests/fail/bit_cast_in_template.cpp new file mode 100644 index 0000000..d7e6c56 --- /dev/null +++ b/checks/clang-query/prefer-reinterpret-cast/tests/fail/bit_cast_in_template.cpp @@ -0,0 +1,6 @@ +template [[nodiscard]] To convert(const From value) +{ + return (To)value;// expect-finding +} + +[[nodiscard]] const char *as_chars(const int *value) { return convert(value); } diff --git a/checks/clang-query/prefer-reinterpret-cast/tests/fail/c_style_pointer_bit_cast.cpp b/checks/clang-query/prefer-reinterpret-cast/tests/fail/c_style_pointer_bit_cast.cpp new file mode 100644 index 0000000..fb03845 --- /dev/null +++ b/checks/clang-query/prefer-reinterpret-cast/tests/fail/c_style_pointer_bit_cast.cpp @@ -0,0 +1,4 @@ +[[nodiscard]] const unsigned char *object_bytes(const int &value) +{ + return (const unsigned char *)&value;// expect-finding +} diff --git a/checks/clang-query/prefer-reinterpret-cast/tests/fail/c_style_reference_bit_cast.cpp b/checks/clang-query/prefer-reinterpret-cast/tests/fail/c_style_reference_bit_cast.cpp new file mode 100644 index 0000000..a47a213 --- /dev/null +++ b/checks/clang-query/prefer-reinterpret-cast/tests/fail/c_style_reference_bit_cast.cpp @@ -0,0 +1,4 @@ +[[nodiscard]] float &reinterpret_bits(int &value) +{ + return (float &)value;// expect-finding +} diff --git a/checks/clang-query/prefer-reinterpret-cast/tests/fail/function_style_pointer_bit_cast.cpp b/checks/clang-query/prefer-reinterpret-cast/tests/fail/function_style_pointer_bit_cast.cpp new file mode 100644 index 0000000..cc28104 --- /dev/null +++ b/checks/clang-query/prefer-reinterpret-cast/tests/fail/function_style_pointer_bit_cast.cpp @@ -0,0 +1,6 @@ +using byte_pointer = const unsigned char *; + +[[nodiscard]] byte_pointer object_bytes(const int &value) +{ + return byte_pointer(&value);// expect-finding +} diff --git a/checks/clang-query/prefer-reinterpret-cast/tests/pass/reinterpret_cast.cpp b/checks/clang-query/prefer-reinterpret-cast/tests/pass/reinterpret_cast.cpp new file mode 100644 index 0000000..fd3e550 --- /dev/null +++ b/checks/clang-query/prefer-reinterpret-cast/tests/pass/reinterpret_cast.cpp @@ -0,0 +1,6 @@ +[[nodiscard]] const unsigned char *object_bytes(const int &value) +{ + return reinterpret_cast(&value); +} + +[[nodiscard]] float &reinterpret_bits(int &value) { return reinterpret_cast(value); } diff --git a/checks/clang-query/prefer-reinterpret-cast/tests/pass/template_without_bit_cast.cpp b/checks/clang-query/prefer-reinterpret-cast/tests/pass/template_without_bit_cast.cpp new file mode 100644 index 0000000..ad16e25 --- /dev/null +++ b/checks/clang-query/prefer-reinterpret-cast/tests/pass/template_without_bit_cast.cpp @@ -0,0 +1,6 @@ +template [[nodiscard]] constexpr To convert(const From value) +{ + return (To)value; +} + +[[nodiscard]] constexpr int truncate(const double value) { return convert(value); } diff --git a/checks/clang-query/prefer-reinterpret-cast/tests/pass/value_conversions.cpp b/checks/clang-query/prefer-reinterpret-cast/tests/pass/value_conversions.cpp new file mode 100644 index 0000000..a6dcf38 --- /dev/null +++ b/checks/clang-query/prefer-reinterpret-cast/tests/pass/value_conversions.cpp @@ -0,0 +1,21 @@ +struct Base +{ + int base_value; +}; + +struct Derived : Base +{ + int derived_value; +}; + +using integer = int; + +[[nodiscard]] constexpr int truncate(const double value) { return (int)value; } + +[[nodiscard]] constexpr int truncate_functional(const double value) { return integer(value); } + +[[nodiscard]] const Derived *as_derived(const Base *base) { return (const Derived *)base; } + +[[nodiscard]] const Base *as_base(const Derived *derived) { return (const Base *)derived; } + +[[nodiscard]] float to_float(const int &value) { return (const float &)value; }