aboutsummaryrefslogtreecommitdiff
path: root/src/libutil
diff options
context:
space:
mode:
Diffstat (limited to 'src/libutil')
-rw-r--r--src/libutil/args.hh2
-rw-r--r--src/libutil/charptr-cast.hh140
-rw-r--r--src/libutil/compression.cc12
-rw-r--r--src/libutil/file-descriptor.cc2
-rw-r--r--src/libutil/file-system.cc3
-rw-r--r--src/libutil/finally.hh2
-rw-r--r--src/libutil/hash.cc69
-rw-r--r--src/libutil/hash.hh8
-rw-r--r--src/libutil/meson.build1
-rw-r--r--src/libutil/processes.cc8
-rw-r--r--src/libutil/strings.cc3
-rw-r--r--src/libutil/strings.hh2
-rw-r--r--src/libutil/tarfile.cc4
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) {