diff --git a/src/env.cpp b/src/env.cpp index 3cfe58715..4d338622d 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -1239,17 +1239,16 @@ int env_stack_t::set_internal(const wcstring &key, env_mode_flags_t input_var_mo has_changed_new = true; } - var.set_vals(std::move(val)); - var.set_pathvar(var_mode & ENV_PATHVAR); - var.set_read_only(is_read_only(key)); + var = var.setting_vals(std::move(val)) + .setting_exports(var_mode & ENV_EXPORT) + .setting_pathvar(var_mode & ENV_PATHVAR) + .setting_read_only(is_read_only(key)); if (var_mode & ENV_EXPORT) { // The new variable is exported. - var.set_exports(true); node->exportv = true; has_changed_new = true; } else { - var.set_exports(false); // Set the node's exported when it changes something about exports // (also when it redefines a variable to not be exported). node->exportv = has_changed_old != has_changed_new; @@ -1351,20 +1350,16 @@ int env_stack_t::remove(const wcstring &key, int var_mode) { return erased ? ENV_OK : ENV_NOT_FOUND; } -const wcstring_list_t &env_var_t::as_list() const { return vals; } +const wcstring_list_t &env_var_t::as_list() const { return *vals_; } wchar_t env_var_t::get_delimiter() const { return is_pathvar() ? PATH_ARRAY_SEP : NONPATH_ARRAY_SEP; } /// Return a string representation of the var. -wcstring env_var_t::as_string() const { - return join_strings(vals, get_delimiter()); -} +wcstring env_var_t::as_string() const { return join_strings(*vals_, get_delimiter()); } -void env_var_t::to_list(wcstring_list_t &out) const { - out = vals; -} +void env_var_t::to_list(wcstring_list_t &out) const { out = *vals_; } env_var_t::env_var_flags_t env_var_t::flags_for(const wchar_t *name) { env_var_flags_t result = 0; @@ -1372,6 +1367,12 @@ env_var_t::env_var_flags_t env_var_t::flags_for(const wchar_t *name) { return result; } +/// \return a singleton empty list, to avoid unnecessary allocations in env_var_t. +std::shared_ptr env_var_t::empty_list() { + static const auto result = std::make_shared(); + return result; +} + maybe_t env_stack_t::get(const wcstring &key, env_mode_flags_t mode) const { const bool has_scope = mode & (ENV_LOCAL | ENV_GLOBAL | ENV_UNIVERSAL); const bool search_local = !has_scope || (mode & ENV_LOCAL); diff --git a/src/env.h b/src/env.h index f5d240b84..be26aefa2 100644 --- a/src/env.h +++ b/src/env.h @@ -66,13 +66,22 @@ void env_init(const struct config_paths_t *paths = NULL); /// routines. void misc_init(); +/// env_var_t is an immutable value-type data structure representing the value of an environment +/// variable. class env_var_t { public: using env_var_flags_t = uint8_t; private: - wcstring_list_t vals; // list of values assigned to the var - env_var_flags_t flags; + env_var_t(std::shared_ptr vals, env_var_flags_t flags) + : vals_(std::move(vals)), flags_(flags) {} + + /// The list of values in this variable. + /// shared_ptr allows for cheap copying. + std::shared_ptr vals_{empty_list()}; + + /// Flag in this variable. + env_var_flags_t flags_{}; public: enum { @@ -82,24 +91,27 @@ class env_var_t { }; // Constructors. + env_var_t() = default; env_var_t(const env_var_t &) = default; env_var_t(env_var_t &&) = default; - env_var_t(wcstring_list_t vals, env_var_flags_t flags) : vals(std::move(vals)), flags(flags) {} + + env_var_t(wcstring_list_t vals, env_var_flags_t flags) + : env_var_t(std::make_shared(std::move(vals)), flags) {} + env_var_t(wcstring val, env_var_flags_t flags) : env_var_t(wcstring_list_t{std::move(val)}, flags) {} // Constructors that infer the flags from a name. env_var_t(const wchar_t *name, wcstring_list_t vals) : env_var_t(std::move(vals), flags_for(name)) {} + env_var_t(const wchar_t *name, wcstring val) : env_var_t(std::move(val), flags_for(name)) {} - env_var_t() = default; - - bool empty() const { return vals.empty() || (vals.size() == 1 && vals[0].empty()); } - bool read_only() const { return flags & flag_read_only; } - bool exports() const { return flags & flag_export; } - bool is_pathvar() const { return flags & flag_pathvar; } - env_var_flags_t get_flags() const { return flags; } + bool empty() const { return vals_->empty() || (vals_->size() == 1 && vals_->front().empty()); } + bool read_only() const { return flags_ & flag_read_only; } + bool exports() const { return flags_ & flag_export; } + bool is_pathvar() const { return flags_ & flag_pathvar; } + env_var_flags_t get_flags() const { return flags_; } wcstring as_string() const; void to_list(wcstring_list_t &out) const; @@ -108,38 +120,50 @@ class env_var_t { /// \return the character used when delimiting quoted expansion. wchar_t get_delimiter() const; - void set_vals(wcstring_list_t v) { vals = std::move(v); } + /// \return a copy of this variable with new values. + env_var_t setting_vals(wcstring_list_t vals) const { + return env_var_t{std::move(vals), flags_}; + } - void set_exports(bool exportv) { + env_var_t setting_exports(bool exportv) const { + env_var_flags_t flags = flags_; if (exportv) { flags |= flag_export; } else { flags &= ~flag_export; } + return env_var_t{vals_, flags}; } - void set_pathvar(bool pathvar) { + env_var_t setting_pathvar(bool pathvar) const { + env_var_flags_t flags = flags_; if (pathvar) { flags |= flag_pathvar; } else { flags &= ~flag_pathvar; } + return env_var_t{vals_, flags}; } - void set_read_only(bool read_only) { + env_var_t setting_read_only(bool read_only) const { + env_var_flags_t flags = flags_; if (read_only) { flags |= flag_read_only; } else { flags &= ~flag_read_only; } + return env_var_t{vals_, flags}; } static env_var_flags_t flags_for(const wchar_t *name); + static std::shared_ptr empty_list(); env_var_t &operator=(const env_var_t &var) = default; env_var_t &operator=(env_var_t &&) = default; - bool operator==(const env_var_t &rhs) const { return vals == rhs.vals && flags == rhs.flags; } + bool operator==(const env_var_t &rhs) const { + return *vals_ == *rhs.vals_ && flags_ == rhs.flags_; + } bool operator!=(const env_var_t &rhs) const { return ! (*this == rhs); } };