2016-05-01 11:31:02 +08:00
|
|
|
// Functions for storing and retrieving function information. These functions also take care of
|
|
|
|
// autoloading functions in the $fish_function_path. Actual function evaluation is taken care of by
|
|
|
|
// the parser and to some degree the builtin handling library.
|
|
|
|
//
|
2016-05-19 06:30:21 +08:00
|
|
|
#include "config.h" // IWYU pragma: keep
|
|
|
|
|
2016-04-21 14:00:54 +08:00
|
|
|
// IWYU pragma: no_include <type_traits>
|
2016-05-01 11:31:02 +08:00
|
|
|
#include <dirent.h>
|
2011-12-27 11:18:46 +08:00
|
|
|
#include <pthread.h>
|
2016-05-01 11:31:02 +08:00
|
|
|
#include <stddef.h>
|
2017-02-14 12:37:27 +08:00
|
|
|
|
2018-02-10 13:53:06 +08:00
|
|
|
#include <algorithm>
|
2019-10-14 06:50:48 +08:00
|
|
|
#include <cwchar>
|
2012-01-14 15:44:18 +08:00
|
|
|
#include <map>
|
2016-05-01 11:31:02 +08:00
|
|
|
#include <memory>
|
2015-07-25 23:14:25 +08:00
|
|
|
#include <string>
|
2017-08-20 00:55:06 +08:00
|
|
|
#include <unordered_map>
|
2018-02-10 13:53:06 +08:00
|
|
|
#include <unordered_set>
|
2015-07-25 23:14:25 +08:00
|
|
|
#include <utility>
|
2005-09-20 21:26:39 +08:00
|
|
|
|
2015-07-25 23:14:25 +08:00
|
|
|
#include "autoload.h"
|
2005-09-20 21:26:39 +08:00
|
|
|
#include "common.h"
|
2016-05-01 11:31:02 +08:00
|
|
|
#include "env.h"
|
2005-10-06 06:37:08 +08:00
|
|
|
#include "event.h"
|
2019-04-27 06:03:41 +08:00
|
|
|
#include "exec.h"
|
2016-05-01 11:31:02 +08:00
|
|
|
#include "fallback.h" // IWYU pragma: keep
|
|
|
|
#include "function.h"
|
|
|
|
#include "intern.h"
|
2018-09-25 10:26:46 +08:00
|
|
|
#include "parser.h"
|
2007-04-22 17:50:26 +08:00
|
|
|
#include "parser_keywords.h"
|
2016-05-01 11:31:02 +08:00
|
|
|
#include "reader.h"
|
|
|
|
#include "wutil.h" // IWYU pragma: keep
|
2005-09-20 21:26:39 +08:00
|
|
|
|
2018-02-11 09:38:57 +08:00
|
|
|
class function_info_t {
|
2019-04-27 06:03:41 +08:00
|
|
|
public:
|
|
|
|
/// Immutable properties of the function.
|
2019-11-13 01:53:10 +08:00
|
|
|
function_properties_ref_t props;
|
2019-04-27 06:03:41 +08:00
|
|
|
/// Function description. This may be changed after the function is created.
|
|
|
|
wcstring description;
|
|
|
|
/// File where this function was defined (intern'd string).
|
|
|
|
const wchar_t *const definition_file;
|
|
|
|
/// Flag for specifying that this function was automatically loaded.
|
|
|
|
const bool is_autoload;
|
|
|
|
|
2019-11-13 01:53:10 +08:00
|
|
|
function_info_t(function_properties_ref_t props, wcstring desc, const wchar_t *def_file,
|
2019-04-27 06:03:41 +08:00
|
|
|
bool autoload);
|
2018-02-11 09:38:57 +08:00
|
|
|
};
|
|
|
|
|
2019-04-27 06:03:41 +08:00
|
|
|
/// Type wrapping up the set of all functions.
|
|
|
|
/// There's only one of these; it's managed by a lock.
|
|
|
|
struct function_set_t {
|
|
|
|
/// The map of all functions by name.
|
|
|
|
std::unordered_map<wcstring, function_info_t> funcs;
|
2011-12-27 11:18:46 +08:00
|
|
|
|
2019-04-27 06:03:41 +08:00
|
|
|
/// Tombstones for functions that should no longer be autoloaded.
|
|
|
|
std::unordered_set<wcstring> autoload_tombstones;
|
2015-03-11 21:14:56 +08:00
|
|
|
|
2019-04-27 06:03:41 +08:00
|
|
|
/// The autoloader for our functions.
|
2019-04-28 06:42:34 +08:00
|
|
|
autoload_t autoloader{L"fish_function_path"};
|
2011-12-27 11:18:46 +08:00
|
|
|
|
2019-04-27 06:03:41 +08:00
|
|
|
/// Remove a function.
|
|
|
|
/// \return true if successful, false if it doesn't exist.
|
|
|
|
bool remove(const wcstring &name);
|
2017-01-30 10:56:55 +08:00
|
|
|
|
2019-04-27 06:03:41 +08:00
|
|
|
/// Get the info for a function, or nullptr if none.
|
|
|
|
const function_info_t *get_info(const wcstring &name) const {
|
|
|
|
auto iter = funcs.find(name);
|
|
|
|
return iter == funcs.end() ? nullptr : &iter->second;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// \return true if we should allow autoloading a given function.
|
|
|
|
bool allow_autoload(const wcstring &name) const;
|
|
|
|
|
|
|
|
function_set_t() = default;
|
|
|
|
};
|
|
|
|
|
|
|
|
/// The big set of all functions.
|
|
|
|
static owning_lock<function_set_t> function_set;
|
|
|
|
|
|
|
|
bool function_set_t::allow_autoload(const wcstring &name) const {
|
|
|
|
// Prohibit autoloading if we have a non-autoload (explicit) function, or if the function is
|
|
|
|
// tombstoned.
|
|
|
|
const auto *info = get_info(name);
|
|
|
|
bool has_explicit_func = info && !info->is_autoload;
|
|
|
|
bool is_tombstoned = autoload_tombstones.count(name) > 0;
|
|
|
|
return !has_explicit_func && !is_tombstoned;
|
|
|
|
}
|
2006-02-08 17:20:05 +08:00
|
|
|
|
2016-05-01 11:31:02 +08:00
|
|
|
/// Make sure that if the specified function is a dynamically loaded function, it has been fully
|
|
|
|
/// loaded.
|
2019-04-27 06:03:41 +08:00
|
|
|
/// Note this executes fish script code.
|
2019-05-05 11:20:52 +08:00
|
|
|
static void try_autoload(const wcstring &name, parser_t &parser) {
|
2011-12-27 13:56:23 +08:00
|
|
|
ASSERT_IS_MAIN_THREAD();
|
2019-04-27 06:03:41 +08:00
|
|
|
maybe_t<wcstring> path_to_autoload;
|
|
|
|
// Note we can't autoload while holding the funcset lock.
|
|
|
|
// Lock around a local region.
|
|
|
|
{
|
|
|
|
auto funcset = function_set.acquire();
|
|
|
|
if (funcset->allow_autoload(name)) {
|
2019-07-13 03:19:00 +08:00
|
|
|
path_to_autoload = funcset->autoloader.resolve_command(name, env_stack_t::globals());
|
2019-04-27 06:03:41 +08:00
|
|
|
}
|
2011-12-27 11:18:46 +08:00
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2019-04-27 06:03:41 +08:00
|
|
|
// Release the lock and perform any autoload, then reacquire the lock and clean up.
|
|
|
|
if (path_to_autoload) {
|
|
|
|
// Crucially, the lock is acquired *after* do_autoload_file_at_path().
|
2019-05-05 11:20:52 +08:00
|
|
|
autoload_t::perform_autoload(*path_to_autoload, parser);
|
2019-04-27 06:03:41 +08:00
|
|
|
function_set.acquire()->autoloader.mark_autoload_finished(name);
|
|
|
|
}
|
2006-02-08 17:20:05 +08:00
|
|
|
}
|
|
|
|
|
2016-05-01 11:31:02 +08:00
|
|
|
/// Insert a list of all dynamically loaded functions into the specified list.
|
2017-08-20 04:29:52 +08:00
|
|
|
static void autoload_names(std::unordered_set<wcstring> &names, int get_hidden) {
|
2012-11-19 08:30:30 +08:00
|
|
|
size_t i;
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2018-09-25 10:26:46 +08:00
|
|
|
// TODO: justfy this.
|
|
|
|
auto &vars = env_stack_t::principal();
|
|
|
|
const auto path_var = vars.get(L"fish_function_path");
|
2017-08-06 12:40:27 +08:00
|
|
|
if (path_var.missing_or_empty()) return;
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2012-01-11 04:51:09 +08:00
|
|
|
wcstring_list_t path_list;
|
2017-08-28 15:25:41 +08:00
|
|
|
path_var->to_list(path_list);
|
2006-02-09 01:37:18 +08:00
|
|
|
|
2016-05-01 11:31:02 +08:00
|
|
|
for (i = 0; i < path_list.size(); i++) {
|
2012-11-19 08:30:30 +08:00
|
|
|
const wcstring &ndir_str = path_list.at(i);
|
2018-09-22 19:28:19 +08:00
|
|
|
dir_t dir(ndir_str);
|
|
|
|
if (!dir.valid()) continue;
|
2012-11-19 08:30:30 +08:00
|
|
|
|
|
|
|
wcstring name;
|
2018-09-22 19:28:19 +08:00
|
|
|
while (dir.read(name)) {
|
2012-11-19 08:30:30 +08:00
|
|
|
const wchar_t *fn = name.c_str();
|
|
|
|
const wchar_t *suffix;
|
2016-05-01 11:31:02 +08:00
|
|
|
if (!get_hidden && fn[0] == L'_') continue;
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2019-03-13 05:06:01 +08:00
|
|
|
suffix = std::wcsrchr(fn, L'.');
|
|
|
|
if (suffix && (std::wcscmp(suffix, L".fish") == 0)) {
|
2012-01-14 15:44:18 +08:00
|
|
|
wcstring name(fn, suffix - fn);
|
|
|
|
names.insert(name);
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
}
|
2006-02-08 17:20:05 +08:00
|
|
|
}
|
|
|
|
|
2019-11-13 01:53:10 +08:00
|
|
|
function_info_t::function_info_t(function_properties_ref_t props, wcstring desc,
|
|
|
|
const wchar_t *def_file, bool autoload)
|
|
|
|
: props(std::move(props)),
|
|
|
|
description(std::move(desc)),
|
|
|
|
definition_file(intern(def_file)),
|
2018-02-12 08:37:38 +08:00
|
|
|
is_autoload(autoload) {}
|
2016-05-01 11:31:02 +08:00
|
|
|
|
2019-11-13 03:25:41 +08:00
|
|
|
void function_add(wcstring name, wcstring description, function_properties_ref_t props,
|
|
|
|
const wchar_t *filename) {
|
2012-03-04 07:20:30 +08:00
|
|
|
ASSERT_IS_MAIN_THREAD();
|
2019-04-27 06:03:41 +08:00
|
|
|
auto funcset = function_set.acquire();
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2019-04-27 06:03:41 +08:00
|
|
|
// Historical check. TODO: rationalize this.
|
2019-11-13 03:25:41 +08:00
|
|
|
if (name.empty()) {
|
2019-04-27 06:03:41 +08:00
|
|
|
return;
|
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-01 11:31:02 +08:00
|
|
|
// Remove the old function.
|
2019-11-13 03:25:41 +08:00
|
|
|
funcset->remove(name);
|
2019-04-27 06:03:41 +08:00
|
|
|
|
|
|
|
// Check if this is a function that we are autoloading.
|
2019-11-13 03:25:41 +08:00
|
|
|
bool is_autoload = funcset->autoloader.autoload_in_progress(name);
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-01 11:31:02 +08:00
|
|
|
// Create and store a new function.
|
2019-11-13 01:53:10 +08:00
|
|
|
auto ins = funcset->funcs.emplace(
|
2019-12-27 13:54:21 +08:00
|
|
|
std::move(name),
|
|
|
|
function_info_t(std::move(props), std::move(description), filename, is_autoload));
|
2019-04-27 06:03:41 +08:00
|
|
|
assert(ins.second && "Function should not already be present in the table");
|
|
|
|
(void)ins;
|
2005-09-20 21:26:39 +08:00
|
|
|
}
|
|
|
|
|
2018-02-12 09:21:24 +08:00
|
|
|
std::shared_ptr<const function_properties_t> function_get_properties(const wcstring &name) {
|
|
|
|
if (parser_keywords_is_reserved(name)) return nullptr;
|
2019-04-27 06:03:41 +08:00
|
|
|
auto funcset = function_set.acquire();
|
|
|
|
if (const auto *info = funcset->get_info(name)) {
|
|
|
|
return info->props;
|
2018-02-12 09:21:24 +08:00
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2019-05-05 11:20:52 +08:00
|
|
|
int function_exists(const wcstring &cmd, parser_t &parser) {
|
2019-04-27 06:03:41 +08:00
|
|
|
ASSERT_IS_MAIN_THREAD();
|
2016-05-01 11:31:02 +08:00
|
|
|
if (parser_keywords_is_reserved(cmd)) return 0;
|
2019-05-05 11:20:52 +08:00
|
|
|
try_autoload(cmd, parser);
|
2019-04-27 06:03:41 +08:00
|
|
|
auto funcset = function_set.acquire();
|
|
|
|
return funcset->funcs.find(cmd) != funcset->funcs.end();
|
2011-12-27 11:18:46 +08:00
|
|
|
}
|
2010-09-18 09:51:16 +08:00
|
|
|
|
2019-05-05 11:20:52 +08:00
|
|
|
void function_load(const wcstring &cmd, parser_t &parser) {
|
2019-04-27 06:03:41 +08:00
|
|
|
ASSERT_IS_MAIN_THREAD();
|
2016-05-01 11:31:02 +08:00
|
|
|
if (!parser_keywords_is_reserved(cmd)) {
|
2019-05-05 11:20:52 +08:00
|
|
|
try_autoload(cmd, parser);
|
2015-10-08 09:59:41 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-11 05:01:47 +08:00
|
|
|
int function_exists_no_autoload(const wcstring &cmd) {
|
2016-05-01 11:31:02 +08:00
|
|
|
if (parser_keywords_is_reserved(cmd)) return 0;
|
2019-04-27 06:03:41 +08:00
|
|
|
auto funcset = function_set.acquire();
|
2015-03-11 21:14:56 +08:00
|
|
|
|
2019-04-27 06:03:41 +08:00
|
|
|
// Check if we either have the function, or it could be autoloaded.
|
|
|
|
return funcset->get_info(cmd) || funcset->autoloader.can_autoload(cmd);
|
2012-01-26 10:40:08 +08:00
|
|
|
}
|
2006-02-05 21:10:35 +08:00
|
|
|
|
2019-04-27 06:03:41 +08:00
|
|
|
bool function_set_t::remove(const wcstring &name) {
|
|
|
|
size_t amt = funcs.erase(name);
|
|
|
|
if (amt > 0) {
|
|
|
|
event_remove_function_handlers(name);
|
|
|
|
}
|
|
|
|
return amt > 0;
|
2012-01-24 12:32:36 +08:00
|
|
|
}
|
|
|
|
|
2019-04-27 06:03:41 +08:00
|
|
|
void function_remove(const wcstring &name) {
|
|
|
|
auto funcset = function_set.acquire();
|
2019-06-22 17:08:36 +08:00
|
|
|
funcset->remove(name);
|
|
|
|
// Prevent (re-)autoloading this function.
|
|
|
|
funcset->autoload_tombstones.insert(name);
|
2005-09-20 21:26:39 +08:00
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2018-09-27 09:35:30 +08:00
|
|
|
bool function_get_definition(const wcstring &name, wcstring &out_definition) {
|
2019-04-27 06:03:41 +08:00
|
|
|
const auto funcset = function_set.acquire();
|
2020-01-04 06:40:28 +08:00
|
|
|
const function_info_t *func = funcset->get_info(name);
|
|
|
|
if (!func || !func->props) return false;
|
|
|
|
// We want to preserve comments that the AST attaches to the header (#5285).
|
|
|
|
// Take everything from the end of the header to the end of the body.
|
|
|
|
const auto &props = func->props;
|
|
|
|
namespace g = grammar;
|
|
|
|
tnode_t<g::block_header> header = props->func_node.child<0>();
|
|
|
|
tnode_t<g::job_list> jobs = props->func_node.child<1>();
|
|
|
|
auto header_src = header.source_range();
|
|
|
|
auto jobs_src = jobs.source_range();
|
|
|
|
if (header_src && jobs_src) {
|
|
|
|
uint32_t body_start = header_src->start + header_src->length;
|
|
|
|
uint32_t body_end = jobs_src->start + jobs_src->length;
|
|
|
|
assert(body_start <= jobs_src->start && "job list must come after header");
|
|
|
|
out_definition = wcstring(props->parsed_source->src, body_start, body_end - body_start);
|
2012-05-18 10:37:46 +08:00
|
|
|
}
|
2020-01-04 06:40:28 +08:00
|
|
|
return true;
|
2005-09-20 21:26:39 +08:00
|
|
|
}
|
2007-04-17 04:06:11 +08:00
|
|
|
|
2018-09-27 09:35:30 +08:00
|
|
|
bool function_get_desc(const wcstring &name, wcstring &out_desc) {
|
2019-04-27 06:03:41 +08:00
|
|
|
const auto funcset = function_set.acquire();
|
|
|
|
const function_info_t *func = funcset->get_info(name);
|
2018-09-27 09:35:30 +08:00
|
|
|
if (func && !func->description.empty()) {
|
|
|
|
out_desc = _(func->description.c_str());
|
2012-05-18 10:46:08 +08:00
|
|
|
return true;
|
2012-01-24 12:43:39 +08:00
|
|
|
}
|
2016-10-29 08:42:50 +08:00
|
|
|
return false;
|
2005-09-20 21:26:39 +08:00
|
|
|
}
|
|
|
|
|
2019-05-05 11:20:52 +08:00
|
|
|
void function_set_desc(const wcstring &name, const wcstring &desc, parser_t &parser) {
|
2019-04-27 06:03:41 +08:00
|
|
|
ASSERT_IS_MAIN_THREAD();
|
2019-05-05 11:20:52 +08:00
|
|
|
try_autoload(name, parser);
|
2019-04-27 06:03:41 +08:00
|
|
|
auto funcset = function_set.acquire();
|
|
|
|
auto iter = funcset->funcs.find(name);
|
|
|
|
if (iter != funcset->funcs.end()) {
|
2012-05-19 05:00:36 +08:00
|
|
|
iter->second.description = desc;
|
|
|
|
}
|
2005-09-20 21:26:39 +08:00
|
|
|
}
|
|
|
|
|
2016-05-01 11:31:02 +08:00
|
|
|
bool function_copy(const wcstring &name, const wcstring &new_name) {
|
2019-04-27 06:03:41 +08:00
|
|
|
auto funcset = function_set.acquire();
|
|
|
|
auto iter = funcset->funcs.find(name);
|
|
|
|
if (iter == funcset->funcs.end()) {
|
|
|
|
// No such function.
|
|
|
|
return false;
|
2012-01-14 15:44:18 +08:00
|
|
|
}
|
2019-11-13 01:53:10 +08:00
|
|
|
const function_info_t &src_func = iter->second;
|
2019-04-27 06:03:41 +08:00
|
|
|
|
|
|
|
// This new instance of the function shouldn't be tied to the definition file of the
|
|
|
|
// original, so pass NULL filename, etc.
|
|
|
|
// Note this will NOT overwrite an existing function with the new name.
|
|
|
|
// TODO: rationalize if this behavior is desired.
|
2019-11-13 01:53:10 +08:00
|
|
|
funcset->funcs.emplace(new_name,
|
|
|
|
function_info_t(src_func.props, src_func.description, nullptr, false));
|
2019-04-27 06:03:41 +08:00
|
|
|
return true;
|
2005-09-20 21:26:39 +08:00
|
|
|
}
|
|
|
|
|
2016-05-01 11:31:02 +08:00
|
|
|
wcstring_list_t function_get_names(int get_hidden) {
|
2017-08-20 04:29:52 +08:00
|
|
|
std::unordered_set<wcstring> names;
|
2019-04-27 06:03:41 +08:00
|
|
|
auto funcset = function_set.acquire();
|
2012-11-19 08:30:30 +08:00
|
|
|
autoload_names(names, get_hidden);
|
2019-04-27 06:03:41 +08:00
|
|
|
for (const auto &func : funcset->funcs) {
|
2017-08-20 04:29:52 +08:00
|
|
|
const wcstring &name = func.first;
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-01 11:31:02 +08:00
|
|
|
// Maybe skip hidden.
|
2016-10-23 02:21:13 +08:00
|
|
|
if (!get_hidden && (name.empty() || name.at(0) == L'_')) {
|
|
|
|
continue;
|
2012-01-14 15:44:18 +08:00
|
|
|
}
|
|
|
|
names.insert(name);
|
|
|
|
}
|
|
|
|
return wcstring_list_t(names.begin(), names.end());
|
2005-09-20 21:26:39 +08:00
|
|
|
}
|
2005-10-06 06:37:08 +08:00
|
|
|
|
2016-05-01 11:31:02 +08:00
|
|
|
const wchar_t *function_get_definition_file(const wcstring &name) {
|
2019-04-27 06:03:41 +08:00
|
|
|
const auto funcset = function_set.acquire();
|
|
|
|
const function_info_t *func = funcset->get_info(name);
|
2019-11-19 10:34:50 +08:00
|
|
|
return func ? func->definition_file : nullptr;
|
2006-01-26 22:48:10 +08:00
|
|
|
}
|
|
|
|
|
2017-01-10 14:49:33 +08:00
|
|
|
bool function_is_autoloaded(const wcstring &name) {
|
2019-04-27 06:03:41 +08:00
|
|
|
const auto funcset = function_set.acquire();
|
|
|
|
const function_info_t *func = funcset->get_info(name);
|
|
|
|
return func ? func->is_autoload : false;
|
2017-01-10 14:49:33 +08:00
|
|
|
}
|
|
|
|
|
2018-02-10 13:53:06 +08:00
|
|
|
int function_get_definition_lineno(const wcstring &name) {
|
2019-04-27 06:03:41 +08:00
|
|
|
const auto funcset = function_set.acquire();
|
|
|
|
const function_info_t *func = funcset->get_info(name);
|
2018-02-10 13:53:06 +08:00
|
|
|
if (!func) return -1;
|
2018-09-22 19:28:19 +08:00
|
|
|
// return one plus the number of newlines at offsets less than the start of our function's
|
|
|
|
// statement (which includes the header).
|
2018-02-10 13:53:06 +08:00
|
|
|
// TODO: merge with line_offset_of_character_at_offset?
|
2020-01-04 06:40:28 +08:00
|
|
|
auto source_range = func->props->func_node.source_range();
|
2018-02-10 13:53:06 +08:00
|
|
|
assert(source_range && "Function has no source range");
|
|
|
|
uint32_t func_start = source_range->start;
|
2018-02-12 08:37:38 +08:00
|
|
|
const wcstring &source = func->props->parsed_source->src;
|
2018-02-10 13:53:06 +08:00
|
|
|
assert(func_start <= source.size() && "function start out of bounds");
|
|
|
|
return 1 + std::count(source.begin(), source.begin() + func_start, L'\n');
|
2006-01-26 22:48:10 +08:00
|
|
|
}
|
2015-08-16 04:37:17 +08:00
|
|
|
|
2019-04-27 06:03:41 +08:00
|
|
|
void function_invalidate_path() {
|
|
|
|
// Remove all autoloaded functions and update the autoload path.
|
|
|
|
// Note we don't want to risk removal during iteration; we expect this to be called
|
|
|
|
// infrequently.
|
|
|
|
auto funcset = function_set.acquire();
|
|
|
|
wcstring_list_t autoloadees;
|
|
|
|
for (const auto &kv : funcset->funcs) {
|
|
|
|
if (kv.second.is_autoload) {
|
|
|
|
autoloadees.push_back(kv.first);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (const wcstring &name : autoloadees) {
|
|
|
|
funcset->remove(name);
|
|
|
|
}
|
|
|
|
funcset->autoloader.clear();
|
|
|
|
}
|