diff --git a/src/builtin_argparse.cpp b/src/builtin_argparse.cpp index 4fe148032..df85cdd32 100644 --- a/src/builtin_argparse.cpp +++ b/src/builtin_argparse.cpp @@ -441,22 +441,24 @@ static int validate_arg(parser_t &parser, const argparse_cmd_opts_t &opts, optio wcstring_list_t cmd_output; - parser.vars().push(true); - env_set_one(L"_argparse_cmd", ENV_LOCAL, opts.name); + auto &vars = parser.vars(); + + vars.push(true); + vars.set_one(L"_argparse_cmd", ENV_LOCAL, opts.name); if (is_long_flag) { - env_set_one(var_name_prefix + L"name", ENV_LOCAL, opt_spec->long_flag); + vars.set_one(var_name_prefix + L"name", ENV_LOCAL, opt_spec->long_flag); } else { - env_set_one(var_name_prefix + L"name", ENV_LOCAL, - wcstring(1, opt_spec->short_flag).c_str()); + vars.set_one(var_name_prefix + L"name", ENV_LOCAL, + wcstring(1, opt_spec->short_flag).c_str()); } - env_set_one(var_name_prefix + L"value", ENV_LOCAL, woptarg); + vars.set_one(var_name_prefix + L"value", ENV_LOCAL, woptarg); int retval = exec_subshell(opt_spec->validation_command, cmd_output, false); for (const auto &output : cmd_output) { streams.err.append(output); streams.err.push_back(L'\n'); } - parser.vars().pop(); + vars.pop(); return retval; } @@ -623,13 +625,13 @@ static int check_min_max_args_constraints(const argparse_cmd_opts_t &opts, parse } /// Put the result of parsing the supplied args into the caller environment as local vars. -static void set_argparse_result_vars(const argparse_cmd_opts_t &opts) { +static void set_argparse_result_vars(env_stack_t &vars, const argparse_cmd_opts_t &opts) { for (const auto &kv : opts.options) { const auto &opt_spec = kv.second; if (!opt_spec->num_seen) continue; if (opt_spec->short_flag_valid) { - env_set(var_name_prefix + opt_spec->short_flag, ENV_LOCAL, opt_spec->vals); + vars.set(var_name_prefix + opt_spec->short_flag, ENV_LOCAL, opt_spec->vals); } if (!opt_spec->long_flag.empty()) { // We do a simple replacement of all non alphanum chars rather than calling @@ -638,11 +640,11 @@ static void set_argparse_result_vars(const argparse_cmd_opts_t &opts) { for (size_t pos = 0; pos < long_flag.size(); pos++) { if (!iswalnum(long_flag[pos])) long_flag[pos] = L'_'; } - env_set(var_name_prefix + long_flag, ENV_LOCAL, opt_spec->vals); + vars.set(var_name_prefix + long_flag, ENV_LOCAL, opt_spec->vals); } } - env_set(L"argv", ENV_LOCAL, opts.argv); + vars.set(L"argv", ENV_LOCAL, opts.argv); } /// The argparse builtin. This is explicitly not compatible with the BSD or GNU version of this @@ -679,6 +681,6 @@ int builtin_argparse(parser_t &parser, io_streams_t &streams, wchar_t **argv) { retval = check_min_max_args_constraints(opts, parser, streams); if (retval != STATUS_CMD_OK) return retval; - set_argparse_result_vars(opts); + set_argparse_result_vars(parser.vars(), opts); return retval; } diff --git a/src/builtin_fg.cpp b/src/builtin_fg.cpp index ac4af798a..605a1301a 100644 --- a/src/builtin_fg.cpp +++ b/src/builtin_fg.cpp @@ -12,6 +12,7 @@ #include "env.h" #include "fallback.h" // IWYU pragma: keep #include "io.h" +#include "parser.h" #include "proc.h" #include "reader.h" #include "tokenizer.h" @@ -103,7 +104,7 @@ int builtin_fg(parser_t &parser, io_streams_t &streams, wchar_t **argv) { const wcstring ft = tok_first(j->command()); //For compatibility with fish 2.0's $_, now replaced with `status current-command` - if (!ft.empty()) env_set_one(L"_", ENV_EXPORT, ft); + if (!ft.empty()) parser.vars().set_one(L"_", ENV_EXPORT, ft); reader_write_title(j->command()); j->promote(); diff --git a/src/builtin_read.cpp b/src/builtin_read.cpp index 4494ae886..bfe2e0089 100644 --- a/src/builtin_read.cpp +++ b/src/builtin_read.cpp @@ -415,6 +415,7 @@ static int validate_read_args(const wchar_t *cmd, read_cmd_opts_t &opts, int arg /// The read builtin. Reads from stdin and stores the values in environment variables. int builtin_read(parser_t &parser, io_streams_t &streams, wchar_t **argv) { + auto &vars = parser.vars(); wchar_t *cmd = argv[0]; int argc = builtin_count_args(argv); wcstring buff; @@ -513,13 +514,13 @@ int builtin_read(parser_t &parser, io_streams_t &streams, wchar_t **argv) { if (opts.array) { // Array mode: assign each char as a separate element of the sole var. - env_set(*var_ptr++, opts.place, chars); + vars.set(*var_ptr++, opts.place, chars); } else { // Not array mode: assign each char to a separate var with the remainder being assigned // to the last var. auto c = chars.begin(); for (; c != chars.end() && vars_left(); ++c) { - env_set_one(*var_ptr++, opts.place, *c); + vars.set_one(*var_ptr++, opts.place, *c); } } } else if (opts.array) { @@ -535,14 +536,14 @@ int builtin_read(parser_t &parser, io_streams_t &streams, wchar_t **argv) { loc.first != wcstring::npos; loc = wcstring_tok(buff, opts.delimiter, loc)) { tokens.emplace_back(wcstring(buff, loc.first, loc.second)); } - env_set(*var_ptr++, opts.place, tokens); + vars.set(*var_ptr++, opts.place, tokens); } else { // We're using a delimiter provided by the user so use the `string split` behavior. wcstring_list_t splits; split_about(buff.begin(), buff.end(), opts.delimiter.begin(), opts.delimiter.end(), &splits); - env_set(*var_ptr++, opts.place, splits); + vars.set(*var_ptr++, opts.place, splits); } } else { // Not array mode. Split the input into tokens and assign each to the vars in sequence. @@ -556,7 +557,7 @@ int builtin_read(parser_t &parser, io_streams_t &streams, wchar_t **argv) { if (loc.first != wcstring::npos) { substr = wcstring(buff, loc.first, loc.second); } - env_set_one(*var_ptr++, opts.place, substr); + vars.set_one(*var_ptr++, opts.place, substr); } } else { // We're using a delimiter provided by the user so use the `string split` behavior. @@ -567,7 +568,7 @@ int builtin_read(parser_t &parser, io_streams_t &streams, wchar_t **argv) { &splits, argc - 1); assert(splits.size() <= (size_t) vars_left()); for (const auto &split : splits) { - env_set_one(*var_ptr++, opts.place, split); + vars.set_one(*var_ptr++, opts.place, split); } } } diff --git a/src/builtin_set.cpp b/src/builtin_set.cpp index 4e5f253e8..fdd38b33b 100644 --- a/src/builtin_set.cpp +++ b/src/builtin_set.cpp @@ -344,12 +344,13 @@ static void handle_env_return(int retval, const wchar_t *cmd, const wchar_t *key /// Call env_set. If this is a path variable, e.g. PATH, validate the elements. On error, print a /// description of the problem to stderr. static int env_set_reporting_errors(const wchar_t *cmd, const wchar_t *key, int scope, - wcstring_list_t &list, io_streams_t &streams) { + const wcstring_list_t &list, io_streams_t &streams, + env_stack_t &vars) { if (is_path_variable(key) && !validate_path_warning_on_colons(cmd, key, list, streams)) { return STATUS_CMD_ERROR; } - int retval = env_set(key, scope | ENV_USER, list); + int retval = vars.set(key, scope | ENV_USER, list); handle_env_return(retval, cmd, key, streams); return retval; @@ -664,7 +665,7 @@ static int builtin_set_erase(const wchar_t *cmd, set_cmd_opts_t &opts, int argc, wcstring_list_t result; dest_var->to_list(result); erase_values(result, indexes); - retval = env_set_reporting_errors(cmd, dest, scope, result, streams); + retval = env_set_reporting_errors(cmd, dest, scope, result, streams, parser.vars()); } if (retval != STATUS_CMD_OK) return retval; @@ -773,7 +774,7 @@ static int builtin_set_set(const wchar_t *cmd, set_cmd_opts_t &opts, int argc, w } if (retval != STATUS_CMD_OK) return retval; - retval = env_set_reporting_errors(cmd, varname, scope, new_values, streams); + retval = env_set_reporting_errors(cmd, varname, scope, new_values, streams, parser.vars()); if (retval != STATUS_CMD_OK) return retval; return check_global_scope_exists(cmd, opts, varname, streams); } diff --git a/src/common.cpp b/src/common.cpp index e13f68738..193a6320d 100644 --- a/src/common.cpp +++ b/src/common.cpp @@ -1758,15 +1758,17 @@ static void validate_new_termsize(struct winsize *new_termsize) { /// Export the new terminal size as env vars and to the kernel if possible. static void export_new_termsize(struct winsize *new_termsize) { + auto &vars = env_stack_t::globals(); wchar_t buf[64]; auto cols = env_get(L"COLUMNS", ENV_EXPORT); swprintf(buf, 64, L"%d", (int)new_termsize->ws_col); - env_set_one(L"COLUMNS", ENV_GLOBAL | (cols.missing_or_empty() ? ENV_DEFAULT : ENV_EXPORT), buf); + vars.set_one(L"COLUMNS", ENV_GLOBAL | (cols.missing_or_empty() ? ENV_DEFAULT : ENV_EXPORT), + buf); auto lines = env_get(L"LINES", ENV_EXPORT); swprintf(buf, 64, L"%d", (int)new_termsize->ws_row); - env_set_one(L"LINES", ENV_GLOBAL | (lines.missing_or_empty() ? ENV_DEFAULT : ENV_EXPORT), buf); + vars.set_one(L"LINES", ENV_GLOBAL | (lines.missing_or_empty() ? ENV_DEFAULT : ENV_EXPORT), buf); #ifdef HAVE_WINSIZE // Only write the new terminal size if we are in the foreground (#4477) diff --git a/src/common.h b/src/common.h index dac5d9a6d..d52204929 100644 --- a/src/common.h +++ b/src/common.h @@ -624,8 +624,8 @@ class null_terminated_array_t { CharType_t **array{NULL}; // No assignment or copying. - void operator=(null_terminated_array_t rhs); - null_terminated_array_t(const null_terminated_array_t &); + void operator=(null_terminated_array_t rhs) = delete; + null_terminated_array_t(const null_terminated_array_t &) = delete; typedef std::vector> string_list_t; diff --git a/src/env.cpp b/src/env.cpp index 82ca99df5..f3fa69416 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -95,7 +95,7 @@ bool term_has_xn = false; /// found in `TERMINFO_DIRS` we don't to call `handle_curses()` before we've imported the latter. static bool env_initialized = false; -typedef std::unordered_map +typedef std::unordered_map var_dispatch_table_t; static var_dispatch_table_t var_dispatch_table; @@ -147,6 +147,8 @@ static std::mutex env_lock; // but we can imagine having separate (linked) stacks // if we introduce multiple threads of execution struct var_stack_t { + var_stack_t(var_stack_t &&) = default; + // Top node on the function stack. env_node_ref_t top; @@ -154,11 +156,13 @@ struct var_stack_t { env_node_ref_t global_env; // Exported variable array used by execv. - null_terminated_array_t export_array; + maybe_t> export_array; /// Flag for checking if we need to regenerate the exported variable array. - bool has_changed_exported = true; - void mark_changed_exported() { has_changed_exported = true; } + void mark_changed_exported() { export_array.reset(); } + + bool has_changed_exported() const { return !export_array; } + void update_export_array_if_necessary(); var_stack_t() : top(globals()), global_env(globals()) { @@ -183,7 +187,15 @@ struct var_stack_t { // shadowing scope, or the global scope if none. This implements the default behavior of `set`. env_node_ref_t resolve_unspecified_scope(); + /// Copy this vars_stack. + var_stack_t clone() const { + return var_stack_t(*this); + } + private: + /// Copy constructor. This does not copy the export array; it just allows it to be regenerated. + var_stack_t(const var_stack_t &rhs) : top(rhs.top), global_env(rhs.global_env) {} + bool local_scope_exports(const env_node_ref_t &n) const; void get_exported(const env_node_t *n, var_table_t &h) const; @@ -273,6 +285,7 @@ env_node_ref_t var_stack_t::resolve_unspecified_scope() { } env_stack_t::env_stack_t() : vars_(make_unique()) {} +env_stack_t::env_stack_t(std::unique_ptr vars) : vars_(std::move(vars)) {} // Get the variable stack var_stack_t &env_stack_t::vars_stack() { return *vars_; } @@ -351,8 +364,8 @@ static void handle_timezone(const wchar_t *env_var_name) { /// Some env vars contain a list of paths where an empty path element is equivalent to ".". /// Unfortunately that convention causes problems for fish scripts. So this function replaces the /// empty path element with an explicit ".". See issue #3914. -static void fix_colon_delimited_var(const wcstring &var_name) { - const auto paths = env_get(var_name); +static void fix_colon_delimited_var(const wcstring &var_name, env_stack_t &vars) { + const auto paths = vars.get(var_name); if (paths.missing_or_empty()) return; // See if there's any empties. @@ -362,7 +375,7 @@ static void fix_colon_delimited_var(const wcstring &var_name) { // Copy the list and replace empties with L"." wcstring_list_t newstrs = strs; std::replace(newstrs.begin(), newstrs.end(), empty, wcstring(L".")); - int retval = env_set(var_name, ENV_DEFAULT | ENV_USER, std::move(newstrs)); + int retval = vars.set(var_name, ENV_DEFAULT | ENV_USER, std::move(newstrs)); if (retval != ENV_OK) { debug(0, L"fix_colon_delimited_var failed unexpectedly with retval %d", retval); } @@ -527,8 +540,8 @@ static bool initialize_curses_using_fallback(const char *term) { /// elements are converted to explicit "." to make the vars easier to use in fish scripts. static void init_path_vars() { // Do not replace empties in MATHPATH - see #4158. - fix_colon_delimited_var(L"PATH"); - fix_colon_delimited_var(L"CDPATH"); + fix_colon_delimited_var(L"PATH", env_stack_t::globals()); + fix_colon_delimited_var(L"CDPATH", env_stack_t::globals()); } /// Update the value of g_guessed_fish_emoji_width @@ -597,7 +610,7 @@ static void init_curses() { } /// React to modifying the given variable. -static void react_to_variable_change(const wchar_t *op, const wcstring &key) { +static void react_to_variable_change(const wchar_t *op, const wcstring &key, env_stack_t &vars) { // Don't do any of this until `env_init()` has run. We only want to do this in response to // variables set by the user; e.g., in a script like *config.fish* or interactively or as part // of loading the universal variables for the first time. Variables we import from the @@ -607,7 +620,7 @@ static void react_to_variable_change(const wchar_t *op, const wcstring &key) { auto dispatch = var_dispatch_table.find(key); if (dispatch != var_dispatch_table.end()) { - (*dispatch->second)(op, key); + (*dispatch->second)(op, key, vars); } else if (string_prefixes_string(L"_fish_abbr_", key)) { update_abbr_cache(op, key); } else if (string_prefixes_string(L"fish_color_", key)) { @@ -620,7 +633,7 @@ static void react_to_variable_change(const wchar_t *op, const wcstring &key) { static void universal_callback(env_stack_t *stack, const callback_data_t &cb) { const wchar_t *op = cb.is_erase() ? L"ERASE" : L"SET"; - react_to_variable_change(op, cb.key); + react_to_variable_change(op, cb.key, *stack); stack->mark_changed_exported(); event_t ev = event_t::variable_event(cb.key); @@ -632,10 +645,11 @@ static void universal_callback(env_stack_t *stack, const callback_data_t &cb) { /// Make sure the PATH variable contains something. static void setup_path() { - const auto path = env_get(L"PATH"); + auto &vars = env_stack_t::globals(); + const auto path = vars.get(L"PATH"); if (path.missing_or_empty()) { wcstring_list_t value({L"/usr/bin", L"/bin"}); - env_set(L"PATH", ENV_GLOBAL | ENV_EXPORT, value); + vars.set(L"PATH", ENV_GLOBAL | ENV_EXPORT, value); } } @@ -643,11 +657,12 @@ static void setup_path() { /// defaults. They will be updated later by the `get_current_winsize()` function if they need to be /// adjusted. void env_stack_t::set_termsize() { + auto &vars = env_stack_t::globals(); auto cols = get(L"COLUMNS"); - if (cols.missing_or_empty()) env_set_one(L"COLUMNS", ENV_GLOBAL, DFLT_TERM_COL_STR); + if (cols.missing_or_empty()) vars.set_one(L"COLUMNS", ENV_GLOBAL, DFLT_TERM_COL_STR); auto rows = get(L"LINES"); - if (rows.missing_or_empty()) env_set_one(L"LINES", ENV_GLOBAL, DFLT_TERM_ROW_STR); + if (rows.missing_or_empty()) vars.set_one(L"LINES", ENV_GLOBAL, DFLT_TERM_ROW_STR); } /// Update the PWD variable directory from the result of getcwd(). @@ -658,7 +673,7 @@ void env_stack_t::set_pwd_from_getcwd() { _(L"Could not determine current working directory. Is your locale set correctly?")); return; } - env_set_one(L"PWD", ENV_EXPORT | ENV_GLOBAL, std::move(cwd)); + set_one(L"PWD", ENV_EXPORT | ENV_GLOBAL, cwd); } /// Allow the user to override the limit on how much data the `read` command will process. @@ -700,7 +715,7 @@ static void setup_user(bool force) { int retval = getpwuid_r(getuid(), &userinfo, buf, sizeof(buf), &result); if (!retval && result) { const wcstring uname = str2wcstring(userinfo.pw_name); - env_set_one(L"USER", ENV_GLOBAL | ENV_EXPORT, uname); + env_stack_t::globals().set_one(L"USER", ENV_GLOBAL | ENV_EXPORT, uname); } } } @@ -736,20 +751,23 @@ void env_stack_t::universal_barrier() { env_universal_callbacks(this, callbacks); } -static void handle_fish_term_change(const wcstring &op, const wcstring &var_name) { +static void handle_fish_term_change(const wcstring &op, const wcstring &var_name, + env_stack_t &vars) { UNUSED(op); UNUSED(var_name); update_fish_color_support(); reader_react_to_color_change(); } -static void handle_escape_delay_change(const wcstring &op, const wcstring &var_name) { +static void handle_escape_delay_change(const wcstring &op, const wcstring &var_name, + env_stack_t &vars) { UNUSED(op); UNUSED(var_name); update_wait_on_escape_ms(); } -static void handle_change_emoji_width(const wcstring &op, const wcstring &var_name) { +static void handle_change_emoji_width(const wcstring &op, const wcstring &var_name, + env_stack_t &vars) { (void)op; (void)var_name; int new_width = 0; @@ -759,7 +777,8 @@ static void handle_change_emoji_width(const wcstring &op, const wcstring &var_na g_fish_emoji_width = std::max(0, new_width); } -static void handle_change_ambiguous_width(const wcstring &op, const wcstring &var_name) { +static void handle_change_ambiguous_width(const wcstring &op, const wcstring &var_name, + env_stack_t &vars) { (void)op; (void)var_name; int new_width = 1; @@ -769,53 +788,59 @@ static void handle_change_ambiguous_width(const wcstring &op, const wcstring &va g_fish_ambiguous_width = std::max(0, new_width); } -static void handle_term_size_change(const wcstring &op, const wcstring &var_name) { +static void handle_term_size_change(const wcstring &op, const wcstring &var_name, + env_stack_t &vars) { UNUSED(op); UNUSED(var_name); invalidate_termsize(true); // force fish to update its idea of the terminal size plus vars } -static void handle_read_limit_change(const wcstring &op, const wcstring &var_name) { +static void handle_read_limit_change(const wcstring &op, const wcstring &var_name, + env_stack_t &vars) { UNUSED(op); UNUSED(var_name); env_set_read_limit(); } -static void handle_fish_history_change(const wcstring &op, const wcstring &var_name) { +static void handle_fish_history_change(const wcstring &op, const wcstring &var_name, + env_stack_t &vars) { UNUSED(op); UNUSED(var_name); reader_change_history(history_session_id().c_str()); } -static void handle_function_path_change(const wcstring &op, const wcstring &var_name) { +static void handle_function_path_change(const wcstring &op, const wcstring &var_name, + env_stack_t &vars) { UNUSED(op); UNUSED(var_name); function_invalidate_path(); } -static void handle_complete_path_change(const wcstring &op, const wcstring &var_name) { +static void handle_complete_path_change(const wcstring &op, const wcstring &var_name, + env_stack_t &vars) { UNUSED(op); UNUSED(var_name); complete_invalidate_path(); } -static void handle_tz_change(const wcstring &op, const wcstring &var_name) { +static void handle_tz_change(const wcstring &op, const wcstring &var_name, env_stack_t &vars) { UNUSED(op); handle_timezone(var_name.c_str()); } -static void handle_magic_colon_var_change(const wcstring &op, const wcstring &var_name) { +static void handle_magic_colon_var_change(const wcstring &op, const wcstring &var_name, + env_stack_t &vars) { UNUSED(op); - fix_colon_delimited_var(var_name); + fix_colon_delimited_var(var_name, vars); } -static void handle_locale_change(const wcstring &op, const wcstring &var_name) { +static void handle_locale_change(const wcstring &op, const wcstring &var_name, env_stack_t &vars) { UNUSED(op); UNUSED(var_name); init_locale(); } -static void handle_curses_change(const wcstring &op, const wcstring &var_name) { +static void handle_curses_change(const wcstring &op, const wcstring &var_name, env_stack_t &vars) { UNUSED(op); UNUSED(var_name); guess_emoji_width(); @@ -852,9 +877,7 @@ static void setup_var_dispatch_table() { void env_init(const struct config_paths_t *paths /* or NULL */) { setup_var_dispatch_table(); - env_stack_t &vars = env_stack_t::principal(); - // Now the environment variable handling is set up, the next step is to insert valid data. - + env_stack_t &vars = env_stack_t::globals(); // Import environment variables. Walk backwards so that the first one out of any duplicates wins // (See issue #2784). wcstring key, val; @@ -872,25 +895,25 @@ void env_init(const struct config_paths_t *paths /* or NULL */) { key.assign(key_and_val, 0, eql); val.assign(key_and_val, eql+1, wcstring::npos); if (is_read_only(key) || is_electric(key)) continue; - env_set(key, ENV_EXPORT | ENV_GLOBAL, {val}); + vars.set(key, ENV_EXPORT | ENV_GLOBAL, {val}); } } // Set the given paths in the environment, if we have any. if (paths != NULL) { - env_set_one(FISH_DATADIR_VAR, ENV_GLOBAL, paths->data); - env_set_one(FISH_SYSCONFDIR_VAR, ENV_GLOBAL, paths->sysconf); - env_set_one(FISH_HELPDIR_VAR, ENV_GLOBAL, paths->doc); - env_set_one(FISH_BIN_DIR, ENV_GLOBAL, paths->bin); + vars.set_one(FISH_DATADIR_VAR, ENV_GLOBAL, paths->data); + vars.set_one(FISH_SYSCONFDIR_VAR, ENV_GLOBAL, paths->sysconf); + vars.set_one(FISH_HELPDIR_VAR, ENV_GLOBAL, paths->doc); + vars.set_one(FISH_BIN_DIR, ENV_GLOBAL, paths->bin); } wcstring user_config_dir; path_get_config(user_config_dir); - env_set_one(FISH_CONFIG_DIR, ENV_GLOBAL, user_config_dir); + vars.set_one(FISH_CONFIG_DIR, ENV_GLOBAL, user_config_dir); wcstring user_data_dir; path_get_data(user_data_dir); - env_set_one(FISH_USER_DATA_DIR, ENV_GLOBAL, user_data_dir); + vars.set_one(FISH_USER_DATA_DIR, ENV_GLOBAL, user_data_dir); init_locale(); init_curses(); @@ -910,20 +933,20 @@ void env_init(const struct config_paths_t *paths /* or NULL */) { setup_user(uid == 0); // Set up $IFS - this used to be in share/config.fish, but really breaks if it isn't done. - env_set_one(L"IFS", ENV_GLOBAL, L"\n \t"); + vars.set_one(L"IFS", ENV_GLOBAL, L"\n \t"); // Set up the version variable. wcstring version = str2wcstring(get_fish_version()); - env_set_one(L"version", ENV_GLOBAL, version); - env_set_one(L"FISH_VERSION", ENV_GLOBAL, version); + vars.set_one(L"version", ENV_GLOBAL, version); + vars.set_one(L"FISH_VERSION", ENV_GLOBAL, version); // Set the $fish_pid variable. - env_set_one(L"fish_pid", ENV_GLOBAL, to_string(getpid())); + vars.set_one(L"fish_pid", ENV_GLOBAL, to_string(getpid())); // Set the $hostname variable wcstring hostname = L"fish"; get_hostname_identifier(hostname); - env_set_one(L"hostname", ENV_GLOBAL, hostname); + vars.set_one(L"hostname", ENV_GLOBAL, hostname); // Set up SHLVL variable. Not we can't use env_get because SHLVL is read-only, and therefore was // not inherited from the environment. @@ -936,7 +959,7 @@ void env_init(const struct config_paths_t *paths /* or NULL */) { nshlvl_str = to_string(shlvl_i + 1); } } - env_set_one(L"SHLVL", ENV_GLOBAL | ENV_EXPORT, nshlvl_str); + vars.set_one(L"SHLVL", ENV_GLOBAL | ENV_EXPORT, nshlvl_str); // Set up the HOME variable. // Unlike $USER, it doesn't seem that `su`s pass this along @@ -962,7 +985,7 @@ void env_init(const struct config_paths_t *paths /* or NULL */) { } if (!retval && result && userinfo.pw_dir) { const wcstring dir = str2wcstring(userinfo.pw_dir); - env_set_one(L"HOME", ENV_GLOBAL | ENV_EXPORT, dir); + vars.set_one(L"HOME", ENV_GLOBAL | ENV_EXPORT, dir); } else { // We cannot get $HOME. This triggers warnings for history and config.fish already, // so it isn't necessary to warn here as well. @@ -1088,7 +1111,7 @@ static void env_set_internal_universal(const wcstring &key, wcstring_list_t val, int env_stack_t::set_internal(const wcstring &key, env_mode_flags_t input_var_mode, wcstring_list_t val) { ASSERT_IS_MAIN_THREAD(); env_mode_flags_t var_mode = input_var_mode; - bool has_changed_old = vars_stack().has_changed_exported; + bool has_changed_old = vars_stack().has_changed_exported(); int done = 0; if (val.size() == 1 && (key == L"PWD" || key == L"HOME")) { @@ -1220,7 +1243,7 @@ int env_stack_t::set_internal(const wcstring &key, env_mode_flags_t input_var_mo event_fire(&ev); // debug(1, L"env_set: return from event firing"); - react_to_variable_change(L"SET", key); + react_to_variable_change(L"SET", key, *this); return ENV_OK; } @@ -1315,7 +1338,7 @@ int env_stack_t::remove(const wcstring &key, int var_mode) { if (is_exported) vars_stack().mark_changed_exported(); } - react_to_variable_change(L"ERASE", key); + react_to_variable_change(L"ERASE", key, *this); return erased ? ENV_OK : ENV_NOT_FOUND; } @@ -1423,14 +1446,6 @@ maybe_t env_get(const wcstring &key, env_mode_flags_t mode) { return env_stack_t::principal().get(key, mode); } -int env_set(const wcstring &key, env_mode_flags_t mode, wcstring_list_t vals) { - return env_stack_t::principal().set(key, mode, std::move(vals)); -} - -int env_set_one(const wcstring &key, env_mode_flags_t mode, wcstring val) { - return env_stack_t::principal().set_one(key, mode, std::move(val)); -} - void env_universal_barrier() { env_stack_t::principal().universal_barrier(); } void env_set_read_limit() { return env_stack_t::principal().set_read_limit(); } @@ -1557,7 +1572,7 @@ static std::vector get_export_list(const var_table_t &envs) { } void var_stack_t::update_export_array_if_necessary() { - if (!this->has_changed_exported) { + if (!this->has_changed_exported()) { return; } @@ -1578,15 +1593,15 @@ void var_stack_t::update_export_array_if_necessary() { } } - export_array.set(get_export_list(vals)); - has_changed_exported = false; + export_array.emplace(get_export_list(vals)); } const char *const *env_stack_t::export_arr() { ASSERT_IS_MAIN_THREAD(); ASSERT_IS_NOT_FORKED_CHILD(); vars_stack().update_export_array_if_necessary(); - return vars_stack().export_array.get(); + assert(vars_stack().export_array && "Should have export array"); + return vars_stack().export_array->get(); } void env_stack_t::set_argv(const wchar_t *const *argv) { @@ -1603,6 +1618,7 @@ void env_stack_t::set_argv(const wchar_t *const *argv) { environment_t::~environment_t() = default; env_stack_t::~env_stack_t() = default; +env_stack_t::env_stack_t(env_stack_t &&) = default; null_environment_t::null_environment_t() = default; null_environment_t::~null_environment_t() = default; @@ -1611,11 +1627,22 @@ maybe_t null_environment_t::get(const wcstring &key, env_mode_flags_t } wcstring_list_t null_environment_t::get_names(int flags) const { return {}; } +env_stack_t env_stack_t::make_principal() { + const env_stack_t &gl = env_stack_t::globals(); + std::unique_ptr dup_stack = make_unique(gl.vars_stack().clone()); + return env_stack_t{std::move(dup_stack)}; +} + env_stack_t &env_stack_t::principal() { - static env_stack_t s_principal; + static env_stack_t s_principal = make_principal(); return s_principal; } +env_stack_t &env_stack_t::globals() { + static env_stack_t s_global; + return s_global; +} + env_vars_snapshot_t::env_vars_snapshot_t(const environment_t &source, const wchar_t *const *keys) { ASSERT_IS_MAIN_THREAD(); wcstring key; diff --git a/src/env.h b/src/env.h index e7e6855e8..32a425f5e 100644 --- a/src/env.h +++ b/src/env.h @@ -159,12 +159,6 @@ class null_environment_t : public environment_t { /// Gets the variable with the specified name, or none() if it does not exist. maybe_t env_get(const wcstring &key, env_mode_flags_t mode = ENV_DEFAULT); -/// Sets the variable with the specified name to the given values. -int env_set(const wcstring &key, env_mode_flags_t mode, wcstring_list_t vals); - -/// Sets the variable with the specified name to a single value. -int env_set_one(const wcstring &key, env_mode_flags_t mode, wcstring val); - /// Synchronizes all universal variable changes: writes everything out, reads stuff in. void env_universal_barrier(); @@ -183,12 +177,17 @@ class env_stack_t : public environment_t { bool try_remove(std::shared_ptr n, const wchar_t *key, int var_mode); std::shared_ptr get_node(const wcstring &key); + static env_stack_t make_principal(); + var_stack_t &vars_stack(); const var_stack_t &vars_stack() const; + explicit env_stack_t(std::unique_ptr vars_); env_stack_t(); ~env_stack_t() override; + env_stack_t(env_stack_t &&); + public: /// Gets the variable with the specified name, or none() if it does not exist. maybe_t get(const wcstring &key, env_mode_flags_t mode = ENV_DEFAULT) const override; @@ -247,6 +246,10 @@ class env_stack_t : public environment_t { // Compatibility hack; access the "environment stack" from back when there was just one. static env_stack_t &principal(); + + // Access a variable stack that only represents globals. + // Do not push or pop from this. + static env_stack_t &globals(); }; class env_vars_snapshot_t : public environment_t { diff --git a/src/exec.cpp b/src/exec.cpp index 3e7af698a..25143e863 100644 --- a/src/exec.cpp +++ b/src/exec.cpp @@ -377,7 +377,7 @@ void internal_exec(env_stack_t &vars, job_t *j, const io_chain_t &all_ios) { shlvl_str = to_string(shlvl - 1); } } - env_set_one(L"SHLVL", ENV_GLOBAL | ENV_EXPORT, shlvl_str); + vars.set_one(L"SHLVL", ENV_GLOBAL | ENV_EXPORT, std::move(shlvl_str)); // launch_process _never_ returns. launch_process_nofork(vars, j->processes.front().get()); @@ -1074,7 +1074,7 @@ bool exec_job(parser_t &parser, shared_ptr j) { j->set_flag(job_flag_t::CONSTRUCTED, true); if (!j->is_foreground()) { - env_set_one(L"last_pid", ENV_GLOBAL, to_string(j->pgid)); + parser.vars().set_one(L"last_pid", ENV_GLOBAL, to_string(j->pgid)); } if (exec_error) { diff --git a/src/fish.cpp b/src/fish.cpp index b9857e8af..4cf5e268c 100644 --- a/src/fish.cpp +++ b/src/fish.cpp @@ -417,7 +417,7 @@ int main(int argc, char **argv) { for (char **ptr = argv + my_optind; *ptr; ptr++) { list.push_back(str2wcstring(*ptr)); } - env_set(L"argv", ENV_DEFAULT, list); + parser.vars().set(L"argv", ENV_DEFAULT, list); const wcstring rel_filename = str2wcstring(file); diff --git a/src/fish_tests.cpp b/src/fish_tests.cpp index 9ec724d91..ba6bd38a6 100644 --- a/src/fish_tests.cpp +++ b/src/fish_tests.cpp @@ -1777,7 +1777,7 @@ static void test_abbreviations() { {L"gx", L"git checkout"}, }; for (const auto &kv : abbreviations) { - int ret = env_set_one(L"_fish_abbr_" + kv.first, ENV_LOCAL, kv.second); + int ret = vars.set_one(L"_fish_abbr_" + kv.first, ENV_LOCAL, kv.second); if (ret != 0) err(L"Unable to set abbreviation variable"); } @@ -2539,7 +2539,7 @@ static void test_complete() { function_data_t fd; fd.name = L"testabbrsonetwothreefour"; function_add(fd, parser_t::principal_parser()); - int ret = env_set_one(L"_fish_abbr_testabbrsonetwothreezero", ENV_LOCAL, L"expansion"); + int ret = pvars.set_one(L"_fish_abbr_testabbrsonetwothreezero", ENV_LOCAL, L"expansion"); complete(L"testabbrsonetwothree", &completions, COMPLETION_REQUEST_DEFAULT, pvars); do_test(ret == 0); do_test(completions.size() == 2); @@ -2692,6 +2692,7 @@ static void perform_one_completion_cd_test(const wcstring &command, const wcstri // Testing test_autosuggest_suggest_special, in particular for properly handling quotes and // backslashes. static void test_autosuggest_suggest_special() { + auto &vars = parser_t::principal_parser().vars(); if (system("mkdir -p 'test/autosuggest_test/0foobar'")) err(L"mkdir failed"); if (system("mkdir -p 'test/autosuggest_test/1foo bar'")) err(L"mkdir failed"); if (system("mkdir -p 'test/autosuggest_test/2foo bar'")) err(L"mkdir failed"); @@ -2705,7 +2706,7 @@ static void test_autosuggest_suggest_special() { // This is to ensure tilde expansion is handled. See the `cd ~/test_autosuggest_suggest_specia` // test below. // Fake out the home directory - env_set_one(L"HOME", ENV_LOCAL | ENV_EXPORT, L"test/test-home"); + parser_t::principal_parser().vars().set_one(L"HOME", ENV_LOCAL | ENV_EXPORT, L"test/test-home"); if (system("mkdir -p test/test-home/test_autosuggest_suggest_special/")) { err(L"mkdir failed"); } @@ -2740,7 +2741,7 @@ static void test_autosuggest_suggest_special() { perform_one_autosuggestion_cd_test(L"cd \"test/autosuggest_test/5", L"foo\"bar/", __LINE__); perform_one_autosuggestion_cd_test(L"cd 'test/autosuggest_test/5", L"foo\"bar/", __LINE__); - env_set_one(L"AUTOSUGGEST_TEST_LOC", ENV_LOCAL, wd); + vars.set_one(L"AUTOSUGGEST_TEST_LOC", ENV_LOCAL, wd); perform_one_autosuggestion_cd_test(L"cd $AUTOSUGGEST_TEST_LOC/0", L"foobar/", __LINE__); perform_one_autosuggestion_cd_test(L"cd ~/test_autosuggest_suggest_specia", L"l/", __LINE__); @@ -4755,14 +4756,15 @@ static void test_string() { /// Helper for test_timezone_env_vars(). long return_timezone_hour(time_t tstamp, const wchar_t *timezone) { + auto &vars = parser_t::principal_parser().vars(); struct tm ltime; char ltime_str[3]; char *str_ptr; size_t n; - env_set_one(L"TZ", ENV_EXPORT, timezone); + vars.set_one(L"TZ", ENV_EXPORT, timezone); - const auto var = env_get(L"TZ", ENV_DEFAULT); + const auto var = vars.get(L"TZ", ENV_DEFAULT); (void)var; localtime_r(&tstamp, <ime); diff --git a/src/function.cpp b/src/function.cpp index e8a424414..2a8e21710 100644 --- a/src/function.cpp +++ b/src/function.cpp @@ -353,7 +353,7 @@ void function_prepare_environment(env_stack_t &vars, const wcstring &name, const wchar_t *const *arg = argv; for (const wcstring &named_arg : props->named_arguments) { if (*arg) { - env_set_one(named_arg, ENV_LOCAL | ENV_USER, *arg); + vars.set_one(named_arg, ENV_LOCAL | ENV_USER, *arg); arg++; } else { vars.set_empty(named_arg, ENV_LOCAL | ENV_USER); @@ -362,6 +362,6 @@ void function_prepare_environment(env_stack_t &vars, const wcstring &name, } for (const auto &kv : inherited_vars) { - env_set(kv.first, ENV_LOCAL | ENV_USER, kv.second.as_list()); + vars.set(kv.first, ENV_LOCAL | ENV_USER, kv.second.as_list()); } } diff --git a/src/history.cpp b/src/history.cpp index f978914ed..685e61916 100644 --- a/src/history.cpp +++ b/src/history.cpp @@ -36,6 +36,7 @@ #include "io.h" #include "iothread.h" #include "lru.h" +#include "parser.h" #include "parse_constants.h" #include "parse_util.h" #include "path.h" @@ -1992,13 +1993,15 @@ void history_t::resolve_pending() { } -static bool private_mode = false; +static std::atomic private_mode{false}; + void start_private_mode() { - private_mode = true; - env_set_one(L"fish_history", ENV_GLOBAL, L""); - env_set_one(L"fish_private_mode", ENV_GLOBAL, L"1"); + private_mode.store(true); + auto &vars = parser_t::principal_parser().vars(); + vars.set_one(L"fish_history", ENV_GLOBAL, L""); + vars.set_one(L"fish_private_mode", ENV_GLOBAL, L"1"); } bool in_private_mode() { - return private_mode; + return private_mode.load(); } diff --git a/src/input.cpp b/src/input.cpp index 9374fb253..2fe26049d 100644 --- a/src/input.cpp +++ b/src/input.cpp @@ -164,8 +164,8 @@ static bool input_function_status; static int input_function_args_index = 0; /// Return the current bind mode. -wcstring input_get_bind_mode() { - auto mode = env_get(FISH_BIND_MODE_VAR); +wcstring input_get_bind_mode(const environment_t &vars) { + auto mode = vars.get(FISH_BIND_MODE_VAR); return mode ? mode->as_string() : DEFAULT_BIND_MODE; } @@ -173,9 +173,11 @@ wcstring input_get_bind_mode() { void input_set_bind_mode(const wcstring &bm) { // Only set this if it differs to not execute variable handlers all the time. // modes may not be empty - empty is a sentinel value meaning to not change the mode + ASSERT_IS_MAIN_THREAD(); + auto &vars = parser_t::principal_parser().vars(); assert(!bm.empty()); - if (input_get_bind_mode() != bm.c_str()) { - env_set_one(FISH_BIND_MODE_VAR, ENV_GLOBAL, bm); + if (input_get_bind_mode(vars) != bm.c_str()) { + vars.set_one(FISH_BIND_MODE_VAR, ENV_GLOBAL, bm); } } @@ -417,7 +419,8 @@ void input_queue_ch(wint_t ch) { input_common_queue_ch(ch); } static void input_mapping_execute_matching_or_generic(bool allow_commands) { const input_mapping_t *generic = NULL; - const wcstring bind_mode = input_get_bind_mode(); + const auto &vars = parser_t::principal_parser().vars(); + const wcstring bind_mode = input_get_bind_mode(vars); for (auto& m : mapping_list) { if (m.mode != bind_mode) { diff --git a/src/input.h b/src/input.h index 553a68b19..757a243ac 100644 --- a/src/input.h +++ b/src/input.h @@ -12,6 +12,8 @@ #define FISH_BIND_MODE_VAR L"fish_bind_mode" +class environment_t; + wcstring describe_char(wint_t c); /// Set to true when the input subsytem has been initialized. @@ -74,7 +76,7 @@ bool input_mapping_get(const wcstring &sequence, const wcstring &mode, wcstring_ wcstring *out_new_mode); /// Return the current bind mode. -wcstring input_get_bind_mode(); +wcstring input_get_bind_mode(const environment_t &vars); /// Set the current bind mode. void input_set_bind_mode(const wcstring &bind_mode); diff --git a/src/maybe.h b/src/maybe.h index 9e8301ed5..3281dca02 100644 --- a/src/maybe.h +++ b/src/maybe.h @@ -53,6 +53,14 @@ class maybe_t { } } + // Construct a value in-place. + template + void emplace(Args &&... args) { + reset(); + filled = true; + new (storage) T(std::forward(args)...); + } + // Access the value. T &value() { assert(filled && "maybe_t does not have a value"); diff --git a/src/parse_execution.cpp b/src/parse_execution.cpp index c0a98e5cb..a960b8fa3 100644 --- a/src/parse_execution.cpp +++ b/src/parse_execution.cpp @@ -405,7 +405,7 @@ parse_execution_result_t parse_execution_context_t::run_for_statement( break; } - int retval = env_set_one(for_var_name, ENV_DEFAULT | ENV_USER, val); + int retval = parser->vars().set_one(for_var_name, ENV_DEFAULT | ENV_USER, val); assert(retval == ENV_OK && "for loop variable should have been successfully set"); (void)retval; diff --git a/src/path.cpp b/src/path.cpp index a4a8e1c3f..55b32dee6 100644 --- a/src/path.cpp +++ b/src/path.cpp @@ -266,10 +266,11 @@ wcstring path_apply_working_directory(const wcstring &path, const wcstring &work static void maybe_issue_path_warning(const wcstring &which_dir, const wcstring &custom_error_msg, bool using_xdg, const wcstring &xdg_var, const wcstring &path, int saved_errno) { + auto &vars = env_stack_t::globals(); wcstring warning_var_name = L"_FISH_WARNED_" + which_dir; - auto var = env_get(warning_var_name, ENV_GLOBAL | ENV_EXPORT); + auto var = vars.get(warning_var_name, ENV_GLOBAL | ENV_EXPORT); if (!var) return; - env_set_one(warning_var_name, ENV_GLOBAL | ENV_EXPORT, L"1"); + vars.set_one(warning_var_name, ENV_GLOBAL | ENV_EXPORT, L"1"); debug(0, custom_error_msg.c_str()); if (path.empty()) { diff --git a/src/reader.cpp b/src/reader.cpp index a994d9dfe..6784c4138 100644 --- a/src/reader.cpp +++ b/src/reader.cpp @@ -397,6 +397,11 @@ class reader_data_t { /// Expand abbreviations at the current cursor position, minus backtrack_amt. bool expand_abbreviation_as_necessary(size_t cursor_backtrack) const; + /// Return the variable set used for e.g. command duration. + env_stack_t &vars() { return parser_t::principal_parser().vars(); } + + const env_stack_t &vars() const { return parser_t::principal_parser().vars(); } + /// Constructor reader_data_t(history_t *hist) : history(hist) {} }; @@ -903,10 +908,12 @@ static void exec_prompt() { void reader_init() { DIE_ON_FAILURE(pthread_key_create(&generation_count_key, NULL)); + auto &vars = parser_t::principal_parser().vars(); + // Ensure this var is present even before an interactive command is run so that if it is used // in a function like `fish_prompt` or `fish_right_prompt` it is defined at the time the first // prompt is written. - env_set_one(ENV_CMD_DURATION, ENV_UNEXPORT, L"0"); + vars.set_one(ENV_CMD_DURATION, ENV_UNEXPORT, L"0"); // Save the initial terminal mode. tcgetattr(STDIN_FILENO, &terminal_mode_on_startup); @@ -1825,7 +1832,7 @@ static void reader_interactive_init() { invalidate_termsize(); // For compatibility with fish 2.0's $_, now replaced with `status current-command` - env_set_one(L"_", ENV_GLOBAL, L"fish"); + parser_t::principal_parser().vars().set_one(L"_", ENV_GLOBAL, L"fish"); } /// Destroy data for interactive use. @@ -1998,7 +2005,7 @@ bool reader_get_selection(size_t *start, size_t *len) { return result; } -void set_env_cmd_duration(struct timeval *after, struct timeval *before) { +void set_env_cmd_duration(struct timeval *after, struct timeval *before, env_stack_t &vars) { time_t secs = after->tv_sec - before->tv_sec; suseconds_t usecs = after->tv_usec - before->tv_usec; wchar_t buf[16]; @@ -2009,7 +2016,7 @@ void set_env_cmd_duration(struct timeval *after, struct timeval *before) { } swprintf(buf, 16, L"%d", (secs * 1000) + (usecs / 1000)); - env_set_one(ENV_CMD_DURATION, ENV_UNEXPORT, buf); + vars.set_one(ENV_CMD_DURATION, ENV_UNEXPORT, buf); } void reader_run_command(parser_t &parser, const wcstring &cmd) { @@ -2018,7 +2025,7 @@ void reader_run_command(parser_t &parser, const wcstring &cmd) { wcstring ft = tok_first(cmd); // For compatibility with fish 2.0's $_, now replaced with `status current-command` - if (!ft.empty()) env_set_one(L"_", ENV_GLOBAL, ft); + if (!ft.empty()) parser.vars().set_one(L"_", ENV_GLOBAL, ft); reader_write_title(cmd); @@ -2033,12 +2040,12 @@ void reader_run_command(parser_t &parser, const wcstring &cmd) { // update the execution duration iff a command is requested for execution // issue - #4926 - if (!ft.empty()) set_env_cmd_duration(&time_after, &time_before); + if (!ft.empty()) set_env_cmd_duration(&time_after, &time_before, parser.vars()); term_steal(); // For compatibility with fish 2.0's $_, now replaced with `status current-command` - env_set_one(L"_", ENV_GLOBAL, program_name); + parser.vars().set_one(L"_", ENV_GLOBAL, program_name); #ifdef HAVE__PROC_SELF_STAT proc_update_jiffies();