diff options
Diffstat (limited to 'src/libutil')
-rw-r--r-- | src/libutil/args.hh | 2 | ||||
-rw-r--r-- | src/libutil/charptr-cast.hh | 140 | ||||
-rw-r--r-- | src/libutil/compression.cc | 12 | ||||
-rw-r--r-- | src/libutil/file-descriptor.cc | 2 | ||||
-rw-r--r-- | src/libutil/file-system.cc | 3 | ||||
-rw-r--r-- | src/libutil/finally.hh | 2 | ||||
-rw-r--r-- | src/libutil/hash.cc | 69 | ||||
-rw-r--r-- | src/libutil/hash.hh | 8 | ||||
-rw-r--r-- | src/libutil/meson.build | 1 | ||||
-rw-r--r-- | src/libutil/processes.cc | 8 | ||||
-rw-r--r-- | src/libutil/strings.cc | 3 | ||||
-rw-r--r-- | src/libutil/strings.hh | 2 | ||||
-rw-r--r-- | src/libutil/tarfile.cc | 4 |
13 files changed, 198 insertions, 58 deletions
diff --git a/src/libutil/args.hh b/src/libutil/args.hh index 71f8f88fa..5fdbaba7e 100644 --- a/src/libutil/args.hh +++ b/src/libutil/args.hh @@ -15,7 +15,7 @@ namespace nix { -enum HashType : char; +enum class HashType : char; class MultiCommand; 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 <concepts> // IWYU pragma: keep +#include <type_traits> + +namespace nix { + +namespace charptr_cast_detail { + +/** Custom version of std::decay that does not eat CV qualifiers on \c {char * const}. */ +template<typename T> +struct DecayArrayInternal +{ + using type = T; +}; + +template <typename T> +struct DecayArrayInternal<T[]> +{ + using type = T *; +}; + +template <typename T, std::size_t N> +struct DecayArrayInternal<T[N]> +{ + using type = T *; +}; + +template <typename T> +using DecayArray = DecayArrayInternal<T>::type; + +/** Is a character type for the purposes of safe reinterpret_cast. */ +template<typename T> +concept IsChar = std::same_as<T, char> || std::same_as<T, unsigned char>; + +template<typename T> +concept IsConvertibleToChar = std::same_as<T, char8_t> || std::same_as<T, void> || IsChar<T>; + +template<typename T> +concept IsDecayOrPointer = std::is_pointer_v<T> || std::is_pointer_v<DecayArray<T>>; + +template<typename From, typename To> +concept ValidQualifiers = requires { + // Does not discard const + requires !std::is_const_v<From> || std::is_const_v<To>; + // Don't deal with volatile + requires !std::is_volatile_v<From> && !std::is_volatile_v<To>; +}; + +template<typename From, typename To> +concept BaseCase = requires { + // Cannot cast away const + requires ValidQualifiers<From, To>; + // At base case, neither should be pointers + requires !std::is_pointer_v<From> && !std::is_pointer_v<To>; + // Finally are the types compatible? + requires IsConvertibleToChar<std::remove_cv_t<From>>; + requires IsChar<std::remove_cv_t<To>>; +}; + +static_assert(BaseCase<char, char>); +static_assert(BaseCase<unsigned char, char>); +static_assert(BaseCase<char8_t, char>); +static_assert(!BaseCase<const char8_t, char>); +static_assert(!BaseCase<const char8_t, unsigned char>); +static_assert(BaseCase<void, unsigned char>); +// Not legal to cast to char8_t +static_assert(!BaseCase<void, char8_t>); +// No pointers +static_assert(!BaseCase<void *, char8_t>); +static_assert(!BaseCase<char *, char *>); + +// Required to be written in the old style because recursion in concepts is not +// allowed. Personally I think the committee hates fun. +template<typename From, typename To, typename = void> +struct RecursionHelper : std::false_type +{}; + +template<typename From, typename To> +struct RecursionHelper<From, To, std::enable_if_t<BaseCase<From, To>>> : std::true_type +{}; + +template<typename From, typename To> +struct RecursionHelper< + From, + To, + std::enable_if_t<std::is_pointer_v<From> && std::is_pointer_v<To> && ValidQualifiers<From, To>>> + : RecursionHelper<std::remove_pointer_t<From>, std::remove_pointer_t<To>> +{}; + +template<typename From, typename To> +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<DecayArray<From>, To>::value; + requires IsDecayOrPointer<From> && std::is_pointer_v<To>; +}; + +static_assert(!IsCharCastable<char **, char *>); +static_assert(IsCharCastable<char *, char *>); +static_assert(!IsCharCastable<const char *, char *>); +static_assert(!IsCharCastable<volatile char *, char *>); +static_assert(!IsCharCastable<char *, volatile char *>); +static_assert(IsCharCastable<char *, const char *>); +static_assert(IsCharCastable<char **, const char **>); +static_assert(IsCharCastable<char **, const char * const *>); +static_assert(!IsCharCastable<char * const *, const char **>); +static_assert(!IsCharCastable<char, char>); +static_assert(IsCharCastable<const char *, const unsigned char *>); +static_assert(!IsCharCastable<char [64][64], char **>); +static_assert(IsCharCastable<char [64], char *>); +} + +/** 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<typename To, typename From> + requires charptr_cast_detail::IsCharCastable<From, To> +inline To charptr_cast(From p) +{ + return reinterpret_cast<To>(p); +} + +} diff --git a/src/libutil/compression.cc b/src/libutil/compression.cc index 773617031..6b0fa9d15 100644 --- a/src/libutil/compression.cc +++ b/src/libutil/compression.cc @@ -119,8 +119,8 @@ private: static ssize_t callback_write(struct archive * archive, void * _self, const void * buffer, size_t length) { - auto self = (ArchiveCompressionSink *) _self; - self->nextSink({(const char *) buffer, length}); + auto self = static_cast<ArchiveCompressionSink *>(_self); + self->nextSink({static_cast<const char *>(buffer), length}); return length; } }; @@ -160,7 +160,7 @@ struct BrotliDecompressionSource : Source size_t read(char * data, size_t len) override { - uint8_t * out = (uint8_t *) data; + uint8_t * out = reinterpret_cast<uint8_t *>(data); const auto * begin = out; while (len && !BrotliDecoderIsFinished(state.get())) { @@ -172,7 +172,7 @@ struct BrotliDecompressionSource : Source } catch (EndOfFile &) { break; } - next_in = (const uint8_t *) buf.get(); + next_in = reinterpret_cast<const uint8_t *>(buf.get()); } if (!BrotliDecoderDecompressStream( @@ -238,7 +238,7 @@ struct BrotliCompressionSink : ChunkedCompressionSink void writeInternal(std::string_view data) override { - auto next_in = (const uint8_t *) data.data(); + auto next_in = reinterpret_cast<const uint8_t *>(data.data()); size_t avail_in = data.size(); uint8_t * next_out = outbuf; size_t avail_out = sizeof(outbuf); @@ -254,7 +254,7 @@ struct BrotliCompressionSink : ChunkedCompressionSink throw CompressionError("error while compressing brotli compression"); if (avail_out < sizeof(outbuf) || avail_in == 0) { - nextSink({(const char *) outbuf, sizeof(outbuf) - avail_out}); + nextSink({reinterpret_cast<const char *>(outbuf), sizeof(outbuf) - avail_out}); next_out = outbuf; avail_out = sizeof(outbuf); } diff --git a/src/libutil/file-descriptor.cc b/src/libutil/file-descriptor.cc index 037cd5297..7c82988b3 100644 --- a/src/libutil/file-descriptor.cc +++ b/src/libutil/file-descriptor.cc @@ -115,7 +115,7 @@ Generator<Bytes> drainFDSource(int fd, bool block) throw SysError("reading from file"); } else if (rd == 0) break; - else co_yield std::span{(char *) buf.data(), (size_t) rd}; + else co_yield std::span{reinterpret_cast<char *>(buf.data()), (size_t) rd}; } } diff --git a/src/libutil/file-system.cc b/src/libutil/file-system.cc index e2319ec59..631cf076b 100644 --- a/src/libutil/file-system.cc +++ b/src/libutil/file-system.cc @@ -567,9 +567,8 @@ Path createTempDir(const Path & tmpRoot, const Path & prefix, std::pair<AutoCloseFD, Path> createTempFile(const Path & prefix) { Path tmpl(defaultTempDir() + "/" + prefix + ".XXXXXX"); - // Strictly speaking, this is UB, but who cares... // FIXME: use O_TMPFILE. - AutoCloseFD fd(mkstemp((char *) tmpl.c_str())); + AutoCloseFD fd(mkstemp(tmpl.data())); if (!fd) throw SysError("creating temporary file '%s'", tmpl); closeOnExec(fd.get()); diff --git a/src/libutil/finally.hh b/src/libutil/finally.hh index cbfd6195b..dc51d7b1e 100644 --- a/src/libutil/finally.hh +++ b/src/libutil/finally.hh @@ -22,7 +22,7 @@ public: Finally(Finally &&other) : fun(std::move(other.fun)) { other.movedFrom = true; } - ~Finally() noexcept(false) + ~Finally() noexcept(noexcept(fun())) { try { if (!movedFrom) diff --git a/src/libutil/hash.cc b/src/libutil/hash.cc index 0ce82f273..925f71f80 100644 --- a/src/libutil/hash.cc +++ b/src/libutil/hash.cc @@ -18,10 +18,10 @@ namespace nix { static size_t regularHashSize(HashType type) { switch (type) { - case htMD5: return md5HashSize; - case htSHA1: return sha1HashSize; - case htSHA256: return sha256HashSize; - case htSHA512: return sha512HashSize; + case HashType::MD5: return md5HashSize; + case HashType::SHA1: return sha1HashSize; + case HashType::SHA256: return sha256HashSize; + case HashType::SHA512: return sha512HashSize; } abort(); } @@ -109,34 +109,33 @@ static std::string printHash32(const Hash & hash) std::string printHash16or32(const Hash & hash) { - assert(hash.type); - return hash.to_string(hash.type == htMD5 ? Base16 : Base32, false); + return hash.to_string(hash.type == HashType::MD5 ? Base::Base16 : Base::Base32, false); } std::string Hash::to_string(Base base, bool includeType) const { std::string s; - if (base == SRI || includeType) { + if (base == Base::SRI || includeType) { s += printHashType(type); - s += base == SRI ? '-' : ':'; + s += base == Base::SRI ? '-' : ':'; } switch (base) { - case Base16: + case Base::Base16: s += printHash16(*this); break; - case Base32: + case Base::Base32: s += printHash32(*this); break; - case Base64: - case SRI: - s += base64Encode(std::string_view((const char *) hash, hashSize)); + case Base::Base64: + case Base::SRI: + s += base64Encode(std::string_view(reinterpret_cast<const char *>(hash), hashSize)); break; } return s; } -Hash Hash::dummy(htSHA256); +Hash Hash::dummy(HashType::SHA256); Hash Hash::parseSRI(std::string_view original) { auto rest = original; @@ -266,7 +265,7 @@ Hash newHashAllowEmpty(std::string_view hashStr, std::optional<HashType> ht) if (!ht) throw BadHash("empty hash requires explicit hash type"); Hash h(*ht); - warn("found empty hash, assuming '%s'", h.to_string(SRI, true)); + warn("found empty hash, assuming '%s'", h.to_string(Base::SRI, true)); return h; } else return Hash::parseAny(hashStr, ht); @@ -284,29 +283,29 @@ union Ctx static void start(HashType ht, Ctx & ctx) { - if (ht == htMD5) MD5_Init(&ctx.md5); - else if (ht == htSHA1) SHA1_Init(&ctx.sha1); - else if (ht == htSHA256) SHA256_Init(&ctx.sha256); - else if (ht == htSHA512) SHA512_Init(&ctx.sha512); + if (ht == HashType::MD5) MD5_Init(&ctx.md5); + else if (ht == HashType::SHA1) SHA1_Init(&ctx.sha1); + else if (ht == HashType::SHA256) SHA256_Init(&ctx.sha256); + else if (ht == HashType::SHA512) SHA512_Init(&ctx.sha512); } static void update(HashType ht, Ctx & ctx, std::string_view data) { - if (ht == htMD5) MD5_Update(&ctx.md5, data.data(), data.size()); - else if (ht == htSHA1) SHA1_Update(&ctx.sha1, data.data(), data.size()); - else if (ht == htSHA256) SHA256_Update(&ctx.sha256, data.data(), data.size()); - else if (ht == htSHA512) SHA512_Update(&ctx.sha512, data.data(), data.size()); + if (ht == HashType::MD5) MD5_Update(&ctx.md5, data.data(), data.size()); + else if (ht == HashType::SHA1) SHA1_Update(&ctx.sha1, data.data(), data.size()); + else if (ht == HashType::SHA256) SHA256_Update(&ctx.sha256, data.data(), data.size()); + else if (ht == HashType::SHA512) SHA512_Update(&ctx.sha512, data.data(), data.size()); } static void finish(HashType ht, Ctx & ctx, unsigned char * hash) { - if (ht == htMD5) MD5_Final(hash, &ctx.md5); - else if (ht == htSHA1) SHA1_Final(hash, &ctx.sha1); - else if (ht == htSHA256) SHA256_Final(hash, &ctx.sha256); - else if (ht == htSHA512) SHA512_Final(hash, &ctx.sha512); + if (ht == HashType::MD5) MD5_Final(hash, &ctx.md5); + else if (ht == HashType::SHA1) SHA1_Final(hash, &ctx.sha1); + else if (ht == HashType::SHA256) SHA256_Final(hash, &ctx.sha256); + else if (ht == HashType::SHA512) SHA512_Final(hash, &ctx.sha512); } @@ -387,10 +386,10 @@ Hash compressHash(const Hash & hash, unsigned int newSize) std::optional<HashType> parseHashTypeOpt(std::string_view s) { - if (s == "md5") return htMD5; - else if (s == "sha1") return htSHA1; - else if (s == "sha256") return htSHA256; - else if (s == "sha512") return htSHA512; + if (s == "md5") return HashType::MD5; + else if (s == "sha1") return HashType::SHA1; + else if (s == "sha256") return HashType::SHA256; + else if (s == "sha512") return HashType::SHA512; else return std::optional<HashType> {}; } @@ -406,10 +405,10 @@ HashType parseHashType(std::string_view s) std::string_view printHashType(HashType ht) { switch (ht) { - case htMD5: return "md5"; - case htSHA1: return "sha1"; - case htSHA256: return "sha256"; - case htSHA512: return "sha512"; + case HashType::MD5: return "md5"; + case HashType::SHA1: return "sha1"; + case HashType::SHA256: return "sha256"; + case HashType::SHA512: return "sha512"; default: // illegal hash type enum value internally, as opposed to external input // which should be validated with nice error message. diff --git a/src/libutil/hash.hh b/src/libutil/hash.hh index 1c2f8493b..47e970e17 100644 --- a/src/libutil/hash.hh +++ b/src/libutil/hash.hh @@ -12,7 +12,7 @@ namespace nix { MakeError(BadHash, Error); -enum HashType : char { htMD5 = 42, htSHA1, htSHA256, htSHA512 }; +enum class HashType : char { MD5 = 42, SHA1, SHA256, SHA512 }; const int md5HashSize = 16; @@ -24,7 +24,7 @@ extern std::set<std::string> hashTypes; extern const std::string base32Chars; -enum Base : int { Base64, Base32, Base16, SRI }; +enum class Base : int { Base64, Base32, Base16, SRI }; struct Hash @@ -119,12 +119,12 @@ public: std::string gitRev() const { - return to_string(Base16, false); + return to_string(Base::Base16, false); } std::string gitShortRev() const { - return std::string(to_string(Base16, false), 0, 7); + return std::string(to_string(Base::Base16, false), 0, 7); } static Hash dummy; 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', diff --git a/src/libutil/processes.cc b/src/libutil/processes.cc index 866ba9647..61e1ad556 100644 --- a/src/libutil/processes.cc +++ b/src/libutil/processes.cc @@ -9,9 +9,7 @@ #include <cerrno> #include <cstdlib> #include <cstring> -#include <future> #include <iostream> -#include <thread> #include <grp.h> #include <sys/types.h> @@ -176,7 +174,7 @@ static pid_t doFork(std::function<void()> fun) #if __linux__ static int childEntry(void * arg) { - auto main = (std::function<void()> *) arg; + auto main = static_cast<std::function<void()> *>(arg); (*main)(); return 1; } @@ -212,8 +210,8 @@ Pid startProcess(std::function<void()> fun, const ProcessOptions & options) assert(!(options.cloneFlags & CLONE_VM)); size_t stackSize = 1 * 1024 * 1024; - auto stack = (char *) mmap(0, stackSize, - PROT_WRITE | PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0); + auto stack = static_cast<char *>(mmap(0, stackSize, + PROT_WRITE | PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0)); if (stack == MAP_FAILED) throw SysError("allocating stack"); Finally freeStack([&]() { munmap(stack, stackSize); }); diff --git a/src/libutil/strings.cc b/src/libutil/strings.cc index df48e9203..de9a0eb9f 100644 --- a/src/libutil/strings.cc +++ b/src/libutil/strings.cc @@ -8,7 +8,8 @@ namespace nix { std::vector<char *> stringsToCharPtrs(const Strings & ss) { std::vector<char *> res; - for (auto & s : ss) res.push_back((char *) s.c_str()); + // This is const cast since this exists for OS APIs that want char * + for (auto & s : ss) res.push_back(const_cast<char *>(s.data())); res.push_back(0); return res; } diff --git a/src/libutil/strings.hh b/src/libutil/strings.hh index 03dff8160..7330e2063 100644 --- a/src/libutil/strings.hh +++ b/src/libutil/strings.hh @@ -20,6 +20,8 @@ constexpr char treeNull[] = " "; * Convert a list of strings to a null-terminated vector of `char * *`s. The result must not be accessed beyond the lifetime of the * list of strings. + * + * Modifying the resulting array elements violates the constness of ss. */ std::vector<char *> stringsToCharPtrs(const Strings & ss); diff --git a/src/libutil/tarfile.cc b/src/libutil/tarfile.cc index c7f9499fd..f024149ec 100644 --- a/src/libutil/tarfile.cc +++ b/src/libutil/tarfile.cc @@ -15,11 +15,11 @@ static int callback_open(struct archive *, void * self) static ssize_t callback_read(struct archive * archive, void * _self, const void * * buffer) { - auto self = (TarArchive *) _self; + auto self = static_cast<TarArchive *>(_self); *buffer = self->buffer.data(); try { - return self->source->read((char *) self->buffer.data(), self->buffer.size()); + return self->source->read(reinterpret_cast<char *>(self->buffer.data()), self->buffer.size()); } catch (EndOfFile &) { return 0; } catch (std::exception & err) { |