From a318c96851579b2a9812034c3a42f0e3fef05d9a Mon Sep 17 00:00:00 2001 From: Jade Lovelace Date: Sun, 4 Aug 2024 20:11:50 -0700 Subject: [PATCH] util: implement charptr_cast I don't like having so many reinterpret_cast statements that have to actually be looked at to determine if they are UB. A huge number of the reinterpret_cast instances in Lix are actually casting to some pointer of some character type, which is always valid no matter the source type. However, it is also worth looking at if it is not casting both *from* a character type and also *to* a character type, since IMO splatting a struct into a character array should be a very deliberate action instead of just being about dealing with bad APIs. So let's write a template that encapsulates this invariant so we can not worry about the trivially safe reinterpret_cast invocations. Change-Id: Ia4e2f1fa0c567123a96604ddadb3bdd7449660a4 --- src/libutil/charptr-cast.hh | 140 ++++++++++++++++++++++++++++++++++++ src/libutil/meson.build | 1 + 2 files changed, 141 insertions(+) create mode 100644 src/libutil/charptr-cast.hh diff --git a/src/libutil/charptr-cast.hh b/src/libutil/charptr-cast.hh new file mode 100644 index 000000000..990f2ec55 --- /dev/null +++ b/src/libutil/charptr-cast.hh @@ -0,0 +1,140 @@ +#pragma once +/** @file Safe casts between character pointer types. */ + +#include // IWYU pragma: keep +#include + +namespace nix { + +namespace charptr_cast_detail { + +/** Custom version of std::decay that does not eat CV qualifiers on \c {char * const}. */ +template +struct DecayArrayInternal +{ + using type = T; +}; + +template +struct DecayArrayInternal +{ + using type = T *; +}; + +template +struct DecayArrayInternal +{ + using type = T *; +}; + +template +using DecayArray = DecayArrayInternal::type; + +/** Is a character type for the purposes of safe reinterpret_cast. */ +template +concept IsChar = std::same_as || std::same_as; + +template +concept IsConvertibleToChar = std::same_as || std::same_as || IsChar; + +template +concept IsDecayOrPointer = std::is_pointer_v || std::is_pointer_v>; + +template +concept ValidQualifiers = requires { + // Does not discard const + requires !std::is_const_v || std::is_const_v; + // Don't deal with volatile + requires !std::is_volatile_v && !std::is_volatile_v; +}; + +template +concept BaseCase = requires { + // Cannot cast away const + requires ValidQualifiers; + // At base case, neither should be pointers + requires !std::is_pointer_v && !std::is_pointer_v; + // Finally are the types compatible? + requires IsConvertibleToChar>; + requires IsChar>; +}; + +static_assert(BaseCase); +static_assert(BaseCase); +static_assert(BaseCase); +static_assert(!BaseCase); +static_assert(!BaseCase); +static_assert(BaseCase); +// Not legal to cast to char8_t +static_assert(!BaseCase); +// No pointers +static_assert(!BaseCase); +static_assert(!BaseCase); + +// Required to be written in the old style because recursion in concepts is not +// allowed. Personally I think the committee hates fun. +template +struct RecursionHelper : std::false_type +{}; + +template +struct RecursionHelper>> : std::true_type +{}; + +template +struct RecursionHelper< + From, + To, + std::enable_if_t && std::is_pointer_v && ValidQualifiers>> + : RecursionHelper, std::remove_pointer_t> +{}; + +template +concept IsCharCastable = requires { + // We only decay arrays in From for safety reasons. There is almost no reason + // to cast *into* an array and such code probably needs closer inspection + // anyway. + requires RecursionHelper, To>::value; + requires IsDecayOrPointer && std::is_pointer_v; +}; + +static_assert(!IsCharCastable); +static_assert(IsCharCastable); +static_assert(!IsCharCastable); +static_assert(!IsCharCastable); +static_assert(!IsCharCastable); +static_assert(IsCharCastable); +static_assert(IsCharCastable); +static_assert(IsCharCastable); +static_assert(!IsCharCastable); +static_assert(!IsCharCastable); +static_assert(IsCharCastable); +static_assert(!IsCharCastable); +static_assert(IsCharCastable); +} + +/** Casts between character pointers with guaranteed safety. If this compiles, + * it is at least a sound conversion per C++23 ยง7.2.1 line 11. + * + * This will not let you: + * - Turn things into void * + * - Turn things that are not char into char + * - Turn things into things that are not char + * - Cast away const + * + * At every level in the pointer indirections, \c To must as const or more + * const than \c From. + * + * \c From may be any character pointer or void pointer or an array of characters. + * + * N.B. Be careful, the template args are in the possibly-surprising + * order To, From due to deduction. + */ +template + requires charptr_cast_detail::IsCharCastable +inline To charptr_cast(From p) +{ + return reinterpret_cast(p); +} + +} diff --git a/src/libutil/meson.build b/src/libutil/meson.build index 01fe65207..4740ea64d 100644 --- a/src/libutil/meson.build +++ b/src/libutil/meson.build @@ -52,6 +52,7 @@ libutil_headers = files( 'box_ptr.hh', 'canon-path.hh', 'cgroup.hh', + 'charptr-cast.hh', 'checked-arithmetic.hh', 'chunked-vector.hh', 'closure.hh',