fish-shell/src/null_terminated_array.h
ridiculousfish fb92ad946b Rework null terminated arrays
Several functions including wgetopt and execve operate on null-terminated
arrays of nul-terminated pointers: a list of pointers to C strings where
the last pointer is null. Prior to this change, each process_t stored its
argv in such an array. This had two problems:

1. It was awkward to work with this type, instead of using std::vector,
etc.
2. The process's arguments would be rearranged by builtins which is
surprising

Our null terminated arrays were built around a fancy type that would copy
input strings and also generate an array of pointers to them, in one big
allocation.

Switch to a new model where we construct an array of pointers over
existing strings. So you can supply a `vector<string>` and now
`null_terminated_array_t` will just make a list of pointers to them. Now
processes can just store their argv in a familiar wcstring_list_t.
2021-03-28 15:31:25 -07:00

82 lines
3.2 KiB
C++

// Support for null-terminated arrays like char**.
#ifndef FISH_NULL_TERMINATED_ARRAY_H
#define FISH_NULL_TERMINATED_ARRAY_H
#include "config.h" // IWYU pragma: keep
#include <memory>
#include <string>
#include <vector>
#include "common.h"
/// This supports the null-terminated array of NUL-terminated strings consumed by exec.
/// Given a list of strings, construct a vector of pointers to those strings contents.
/// This is used for building null-terminated arrays of null-terminated strings.
/// *Important*: the vector stores pointers into the interior of the input strings, which may be
/// subject to the small-string optimization. This means that pointers will be left dangling if any
/// input string is deallocated *or moved*. This class should only be used in transient calls.
template <typename T>
class null_terminated_array_t {
public:
/// \return the list of pointers, appropriate for envp or argv.
/// Note this returns a mutable array of const strings. The caller may rearrange the strings but
/// not modify their contents.
const T **get() {
assert(!pointers_.empty() && pointers_.back() == nullptr && "Should have null terminator");
return &pointers_[0];
}
// Construct from a list of strings (std::string or wcstring).
// This holds pointers into the strings.
explicit null_terminated_array_t(const std::vector<std::basic_string<T>> &strs) {
pointers_.reserve(strs.size() + 1);
for (const auto &s : strs) {
pointers_.push_back(s.c_str());
}
pointers_.push_back(nullptr);
}
// Because this class holds unowned pointers, it should not be copied or moved.
null_terminated_array_t(const null_terminated_array_t &) = delete;
null_terminated_array_t(null_terminated_array_t &&) = delete;
void operator=(const null_terminated_array_t &) = delete;
void operator=(null_terminated_array_t &&) = delete;
private:
std::vector<const T *> pointers_{};
};
/// A container which exposes a null-terminated array of pointers to strings that it owns.
/// This is useful for persisted null-terminated arrays, e.g. the exported environment variable
/// list. This assumes char, since we don't need this for wchar_t.
/// Note this class is not movable or copyable as it embeds a null_terminated_array_t.
class owning_null_terminated_array_t {
public:
// Access the null-terminated array of nul-terminated strings, appropriate for execv().
const char **get() { return pointers_.get(); }
// Construct, taking ownership of a list of strings.
explicit owning_null_terminated_array_t(std::vector<std::string> &&strings)
: strings_(std::move(strings)), pointers_(strings_) {}
private:
const std::vector<std::string> strings_;
null_terminated_array_t<char> pointers_;
};
/// Helper to convert a list of wcstring to a list of std::string.
std::vector<std::string> wide_string_list_to_narrow(const wcstring_list_t &strs);
/// \return the length of a null-terminated array of pointers to something.
template <typename T>
size_t null_terminated_array_length(const T *const *arr) {
size_t idx = 0;
while (arr[idx] != nullptr) {
idx++;
}
return idx;
}
#endif // FISH_NULL_TERMINATED_ARRAY_H