diff --git a/src/env.cpp b/src/env.cpp index 1f3ef37e8..35034c80c 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -305,6 +305,15 @@ struct var_stack_t { return var_stack_t(*this); } + /// Snapshot this vars_stack. That is, return a new vars_stack that has copies of all local + /// variables. Note that this drops all shadowed nodes: only the currently executing function is + /// copied. Global variables are referenced, not copied; this is both because there are a lot of + /// global varibables so copying would be expensive, and some (electrics) are computed so cannot + /// be effectively copied. + std::unique_ptr snapshot() const { + return make_unique(snapshot_node(top), global_env); + } + /// \return true if the topomst local scope exports a variable. bool local_scope_exports(const env_node_ref_t &n) const; @@ -322,6 +331,24 @@ struct var_stack_t { void get_exported(const env_node_t *n, var_table_t &h) const; + /// Recursive helper for snapshot(). Snapshot a node and its unshadows parents, returning it. + env_node_ref_t snapshot_node(const env_node_ref_t &node) const { + assert(node && "null node in snapshot_node"); + // If we are global, re-use the global node. If we reach a new scope, jump to globals; we + // don't snapshot shadowed scopes, because the snapshot is intended to be read-only and so + // there would be no way to access them. + if (node == global_env) { + return node; + } + auto next = snapshot_node(node->new_scope ? global_env : node->next); + auto result = std::make_shared(node->new_scope, next); + // Copy over variables. + // Note assigning env is a potentially big copy. + result->exportv = node->exportv; + result->env = node->env; + return result; + } + /// \return the global variable set. /// Note that this is the only place where we talk about a single global variable set; each /// var_stack_t has its own reference to globals and could potentially have a different global @@ -458,6 +485,10 @@ maybe_t env_scoped_t::get(const wcstring &key, env_mode_flags_t mode) return none(); } +std::shared_ptr env_scoped_t::snapshot() const { + return std::shared_ptr(new env_scoped_t(vars_->snapshot())); +} + env_scoped_t::env_scoped_t() : env_scoped_t(var_stack_t::create()) {} env_scoped_t::env_scoped_t(std::unique_ptr vars) : vars_(std::move(vars)) {} env_scoped_t::env_scoped_t(env_scoped_t &&) = default; diff --git a/src/env.h b/src/env.h index fa598f629..c43cd92b4 100644 --- a/src/env.h +++ b/src/env.h @@ -216,6 +216,12 @@ class env_scoped_t : public environment_t { /// Returns all variable names. wcstring_list_t get_names(int flags) const override; + /// Snapshot this environment. This means returning a read-only copy. Local variables are copied + /// but globals are shared (i.e. changes to global will be visible to this snapshot). This + /// returns a shared_ptr for convenience, since the most common reason to snapshot is because + /// you want to read from another thread. + std::shared_ptr snapshot() const; + ~env_scoped_t() override; };