1 // Functions for storing and retrieving function information. These functions also take care of
2 // autoloading functions in the $fish_function_path. Actual function evaluation is taken care of by
3 // the parser and to some degree the builtin handling library.
4 //
5 #include "config.h" // IWYU pragma: keep
6
7 // IWYU pragma: no_include <type_traits>
8 #include <dirent.h>
9 #include <pthread.h>
10 #include <stddef.h>
11
12 #include <algorithm>
13 #include <cwchar>
14 #include <map>
15 #include <memory>
16 #include <string>
17 #include <unordered_map>
18 #include <unordered_set>
19 #include <utility>
20
21 #include "autoload.h"
22 #include "common.h"
23 #include "env.h"
24 #include "event.h"
25 #include "exec.h"
26 #include "fallback.h" // IWYU pragma: keep
27 #include "function.h"
28 #include "intern.h"
29 #include "parser.h"
30 #include "parser_keywords.h"
31 #include "reader.h"
32 #include "signal.h"
33 #include "wcstringutil.h"
34 #include "wutil.h" // IWYU pragma: keep
35
36 class function_info_t {
37 public:
38 /// Immutable properties of the function.
39 function_properties_ref_t props;
40 /// Function description. This may be changed after the function is created.
41 wcstring description;
42 /// File where this function was defined (intern'd string).
43 const wchar_t *const definition_file;
44 /// Flag for specifying that this function was automatically loaded.
45 const bool is_autoload;
46
47 function_info_t(function_properties_ref_t props, wcstring desc, const wchar_t *def_file,
48 bool autoload);
49 };
50
51 /// Type wrapping up the set of all functions.
52 /// There's only one of these; it's managed by a lock.
53 struct function_set_t {
54 /// The map of all functions by name.
55 std::unordered_map<wcstring, function_info_t> funcs;
56
57 /// Tombstones for functions that should no longer be autoloaded.
58 std::unordered_set<wcstring> autoload_tombstones;
59
60 /// The autoloader for our functions.
61 autoload_t autoloader{L"fish_function_path"};
62
63 /// Remove a function.
64 /// \return true if successful, false if it doesn't exist.
65 bool remove(const wcstring &name);
66
67 /// Get the info for a function, or nullptr if none.
get_infofunction_set_t68 const function_info_t *get_info(const wcstring &name) const {
69 auto iter = funcs.find(name);
70 return iter == funcs.end() ? nullptr : &iter->second;
71 }
72
73 /// \return true if we should allow autoloading a given function.
74 bool allow_autoload(const wcstring &name) const;
75
76 function_set_t() = default;
77 };
78
79 /// The big set of all functions.
80 static owning_lock<function_set_t> function_set;
81
allow_autoload(const wcstring & name) const82 bool function_set_t::allow_autoload(const wcstring &name) const {
83 // Prohibit autoloading if we have a non-autoload (explicit) function, or if the function is
84 // tombstoned.
85 auto info = get_info(name);
86 bool has_explicit_func = info && !info->is_autoload;
87 bool is_tombstoned = autoload_tombstones.count(name) > 0;
88 return !has_explicit_func && !is_tombstoned;
89 }
90
91 /// Make sure that if the specified function is a dynamically loaded function, it has been fully
92 /// loaded.
93 /// Note this executes fish script code.
try_autoload(const wcstring & name,parser_t & parser)94 static void try_autoload(const wcstring &name, parser_t &parser) {
95 ASSERT_IS_MAIN_THREAD();
96 maybe_t<wcstring> path_to_autoload;
97 // Note we can't autoload while holding the funcset lock.
98 // Lock around a local region.
99 {
100 auto funcset = function_set.acquire();
101 if (funcset->allow_autoload(name)) {
102 path_to_autoload = funcset->autoloader.resolve_command(name, env_stack_t::globals());
103 }
104 }
105
106 // Release the lock and perform any autoload, then reacquire the lock and clean up.
107 if (path_to_autoload) {
108 // Crucially, the lock is acquired *after* do_autoload_file_at_path().
109 autoload_t::perform_autoload(*path_to_autoload, parser);
110 function_set.acquire()->autoloader.mark_autoload_finished(name);
111 }
112 }
113
114 /// Insert a list of all dynamically loaded functions into the specified list.
autoload_names(std::unordered_set<wcstring> & names,int get_hidden)115 static void autoload_names(std::unordered_set<wcstring> &names, int get_hidden) {
116 size_t i;
117
118 // TODO: justfy this.
119 auto &vars = env_stack_t::principal();
120 const auto path_var = vars.get(L"fish_function_path");
121 if (path_var.missing_or_empty()) return;
122
123 const wcstring_list_t &path_list = path_var->as_list();
124
125 for (i = 0; i < path_list.size(); i++) {
126 const wcstring &ndir_str = path_list.at(i);
127 dir_t dir(ndir_str);
128 if (!dir.valid()) continue;
129
130 wcstring name;
131 while (dir.read(name)) {
132 const wchar_t *fn = name.c_str();
133 const wchar_t *suffix;
134 if (!get_hidden && fn[0] == L'_') continue;
135
136 suffix = std::wcsrchr(fn, L'.');
137 if (suffix && (std::wcscmp(suffix, L".fish") == 0)) {
138 wcstring name(fn, suffix - fn);
139 names.insert(name);
140 }
141 }
142 }
143 }
144
function_info_t(function_properties_ref_t props,wcstring desc,const wchar_t * def_file,bool autoload)145 function_info_t::function_info_t(function_properties_ref_t props, wcstring desc,
146 const wchar_t *def_file, bool autoload)
147 : props(std::move(props)),
148 description(std::move(desc)),
149 definition_file(intern(def_file)),
150 is_autoload(autoload) {}
151
function_add(wcstring name,wcstring description,function_properties_ref_t props,const wchar_t * filename)152 void function_add(wcstring name, wcstring description, function_properties_ref_t props,
153 const wchar_t *filename) {
154 ASSERT_IS_MAIN_THREAD();
155 auto funcset = function_set.acquire();
156
157 // Historical check. TODO: rationalize this.
158 if (name.empty()) {
159 return;
160 }
161
162 // Remove the old function.
163 funcset->remove(name);
164
165 // Check if this is a function that we are autoloading.
166 bool is_autoload = funcset->autoloader.autoload_in_progress(name);
167
168 // Create and store a new function.
169 auto ins = funcset->funcs.emplace(
170 std::move(name),
171 function_info_t(std::move(props), std::move(description), filename, is_autoload));
172 assert(ins.second && "Function should not already be present in the table");
173 (void)ins;
174 }
175
function_get_properties(const wcstring & name)176 std::shared_ptr<const function_properties_t> function_get_properties(const wcstring &name) {
177 if (parser_keywords_is_reserved(name)) return nullptr;
178 auto funcset = function_set.acquire();
179 if (auto info = funcset->get_info(name)) {
180 return info->props;
181 }
182 return nullptr;
183 }
184
function_exists(const wcstring & cmd,parser_t & parser)185 int function_exists(const wcstring &cmd, parser_t &parser) {
186 ASSERT_IS_MAIN_THREAD();
187 if (!valid_func_name(cmd)) return false;
188 if (parser_keywords_is_reserved(cmd)) return 0;
189 try_autoload(cmd, parser);
190 auto funcset = function_set.acquire();
191 return funcset->funcs.find(cmd) != funcset->funcs.end();
192 }
193
function_load(const wcstring & cmd,parser_t & parser)194 void function_load(const wcstring &cmd, parser_t &parser) {
195 ASSERT_IS_MAIN_THREAD();
196 if (!parser_keywords_is_reserved(cmd)) {
197 try_autoload(cmd, parser);
198 }
199 }
200
function_exists_no_autoload(const wcstring & cmd)201 bool function_exists_no_autoload(const wcstring &cmd) {
202 if (!valid_func_name(cmd)) return false;
203 if (parser_keywords_is_reserved(cmd)) return false;
204 auto funcset = function_set.acquire();
205
206 // Check if we either have the function, or it could be autoloaded.
207 return funcset->get_info(cmd) || funcset->autoloader.can_autoload(cmd);
208 }
209
remove(const wcstring & name)210 bool function_set_t::remove(const wcstring &name) {
211 size_t amt = funcs.erase(name);
212 if (amt > 0) {
213 event_remove_function_handlers(name);
214 }
215 return amt > 0;
216 }
217
function_remove(const wcstring & name)218 void function_remove(const wcstring &name) {
219 auto funcset = function_set.acquire();
220 funcset->remove(name);
221 // Prevent (re-)autoloading this function.
222 funcset->autoload_tombstones.insert(name);
223 }
224
function_get_definition(const wcstring & name,wcstring & out_definition)225 bool function_get_definition(const wcstring &name, wcstring &out_definition) {
226 const auto funcset = function_set.acquire();
227 const function_info_t *func = funcset->get_info(name);
228 if (!func || !func->props) return false;
229 // We want to preserve comments that the AST attaches to the header (#5285).
230 // Take everything from the end of the header to the 'end' keyword.
231 const auto &props = func->props;
232 auto header_src = props->func_node->header->try_source_range();
233 auto end_kw_src = props->func_node->end.try_source_range();
234 if (header_src && end_kw_src) {
235 uint32_t body_start = header_src->start + header_src->length;
236 uint32_t body_end = end_kw_src->start;
237 assert(body_start <= body_end && "end keyword should come after header");
238 out_definition = wcstring(props->parsed_source->src, body_start, body_end - body_start);
239 }
240 return true;
241 }
242
function_get_desc(const wcstring & name,wcstring & out_desc)243 bool function_get_desc(const wcstring &name, wcstring &out_desc) {
244 const auto funcset = function_set.acquire();
245 const function_info_t *func = funcset->get_info(name);
246 if (func && !func->description.empty()) {
247 out_desc = _(func->description.c_str());
248 return true;
249 }
250 return false;
251 }
252
function_set_desc(const wcstring & name,const wcstring & desc,parser_t & parser)253 void function_set_desc(const wcstring &name, const wcstring &desc, parser_t &parser) {
254 ASSERT_IS_MAIN_THREAD();
255 try_autoload(name, parser);
256 auto funcset = function_set.acquire();
257 auto iter = funcset->funcs.find(name);
258 if (iter != funcset->funcs.end()) {
259 iter->second.description = desc;
260 }
261 }
262
function_copy(const wcstring & name,const wcstring & new_name)263 bool function_copy(const wcstring &name, const wcstring &new_name) {
264 auto funcset = function_set.acquire();
265 auto iter = funcset->funcs.find(name);
266 if (iter == funcset->funcs.end()) {
267 // No such function.
268 return false;
269 }
270 const function_info_t &src_func = iter->second;
271
272 // This new instance of the function shouldn't be tied to the definition file of the
273 // original, so pass NULL filename, etc.
274 // Note this will NOT overwrite an existing function with the new name.
275 // TODO: rationalize if this behavior is desired.
276 funcset->funcs.emplace(new_name,
277 function_info_t(src_func.props, src_func.description, nullptr, false));
278 return true;
279 }
280
function_get_names(int get_hidden)281 wcstring_list_t function_get_names(int get_hidden) {
282 std::unordered_set<wcstring> names;
283 auto funcset = function_set.acquire();
284 autoload_names(names, get_hidden);
285 for (const auto &func : funcset->funcs) {
286 const wcstring &name = func.first;
287
288 // Maybe skip hidden.
289 if (!get_hidden && (name.empty() || name.at(0) == L'_')) {
290 continue;
291 }
292 names.insert(name);
293 }
294 return wcstring_list_t(names.begin(), names.end());
295 }
296
function_get_definition_file(const wcstring & name)297 const wchar_t *function_get_definition_file(const wcstring &name) {
298 const auto funcset = function_set.acquire();
299 const function_info_t *func = funcset->get_info(name);
300 return func ? func->definition_file : nullptr;
301 }
302
function_is_autoloaded(const wcstring & name)303 bool function_is_autoloaded(const wcstring &name) {
304 const auto funcset = function_set.acquire();
305 const function_info_t *func = funcset->get_info(name);
306 return func ? func->is_autoload : false;
307 }
308
function_get_definition_lineno(const wcstring & name)309 int function_get_definition_lineno(const wcstring &name) {
310 const auto funcset = function_set.acquire();
311 const function_info_t *func = funcset->get_info(name);
312 if (!func) return -1;
313 // return one plus the number of newlines at offsets less than the start of our function's
314 // statement (which includes the header).
315 // TODO: merge with line_offset_of_character_at_offset?
316 auto source_range = func->props->func_node->try_source_range();
317 assert(source_range && "Function has no source range");
318 uint32_t func_start = source_range->start;
319 const wcstring &source = func->props->parsed_source->src;
320 assert(func_start <= source.size() && "function start out of bounds");
321 return 1 + std::count(source.begin(), source.begin() + func_start, L'\n');
322 }
323
function_invalidate_path()324 void function_invalidate_path() {
325 // Remove all autoloaded functions and update the autoload path.
326 // Note we don't want to risk removal during iteration; we expect this to be called
327 // infrequently.
328 auto funcset = function_set.acquire();
329 wcstring_list_t autoloadees;
330 for (const auto &kv : funcset->funcs) {
331 if (kv.second.is_autoload) {
332 autoloadees.push_back(kv.first);
333 }
334 }
335 for (const wcstring &name : autoloadees) {
336 funcset->remove(name);
337 }
338 funcset->autoloader.clear();
339 }
340
341 /// Return a definition of the specified function. Used by the functions builtin.
functions_def(const wcstring & name)342 wcstring functions_def(const wcstring &name) {
343 assert(!name.empty() && "Empty name");
344 wcstring out;
345 wcstring desc, def;
346 function_get_desc(name, desc);
347 function_get_definition(name, def);
348 std::vector<std::shared_ptr<event_handler_t>> ev = event_get_function_handlers(name);
349
350 out.append(L"function ");
351
352 // Typically we prefer to specify the function name first, e.g. "function foo --description bar"
353 // But if the function name starts with a -, we'll need to output it after all the options.
354 bool defer_function_name = (name.at(0) == L'-');
355 if (!defer_function_name) {
356 out.append(escape_string(name, ESCAPE_ALL));
357 }
358
359 // Output wrap targets.
360 for (const wcstring &wrap : complete_get_wrap_targets(name)) {
361 out.append(L" --wraps=");
362 out.append(escape_string(wrap, ESCAPE_ALL));
363 }
364
365 if (!desc.empty()) {
366 wcstring esc_desc = escape_string(desc, ESCAPE_ALL);
367 out.append(L" --description ");
368 out.append(esc_desc);
369 }
370
371 auto props = function_get_properties(name);
372 assert(props && "Should have function properties");
373 if (!props->shadow_scope) {
374 out.append(L" --no-scope-shadowing");
375 }
376
377 for (const auto &next : ev) {
378 const event_description_t &d = next->desc;
379 switch (d.type) {
380 case event_type_t::signal: {
381 append_format(out, L" --on-signal %ls", sig2wcs(d.param1.signal));
382 break;
383 }
384 case event_type_t::variable: {
385 append_format(out, L" --on-variable %ls", d.str_param1.c_str());
386 break;
387 }
388 case event_type_t::process_exit: {
389 append_format(out, L" --on-process-exit %d", d.param1.pid);
390 break;
391 }
392 case event_type_t::job_exit: {
393 append_format(out, L" --on-job-exit %d", d.param1.jobspec.pid);
394 break;
395 }
396 case event_type_t::caller_exit: {
397 append_format(out, L" --on-job-exit caller");
398 break;
399 }
400 case event_type_t::generic: {
401 append_format(out, L" --on-event %ls", d.str_param1.c_str());
402 break;
403 }
404 case event_type_t::any:
405 default: {
406 DIE("unexpected next->type");
407 }
408 }
409 }
410
411 const wcstring_list_t &named = props->named_arguments;
412 if (!named.empty()) {
413 append_format(out, L" --argument");
414 for (const auto &name : named) {
415 append_format(out, L" %ls", name.c_str());
416 }
417 }
418
419 // Output the function name if we deferred it.
420 if (defer_function_name) {
421 out.append(L" -- ");
422 out.append(escape_string(name, ESCAPE_ALL));
423 }
424
425 // Output any inherited variables as `set -l` lines.
426 for (const auto &kv : props->inherit_vars) {
427 // We don't know what indentation style the function uses,
428 // so we do what fish_indent would.
429 append_format(out, L"\n set -l %ls", kv.first.c_str());
430 for (const auto &arg : kv.second) {
431 wcstring earg = escape_string(arg, ESCAPE_ALL);
432 out.push_back(L' ');
433 out.append(earg);
434 }
435 }
436 out.push_back('\n');
437 out.append(def);
438
439 // Append a newline before the 'end', unless there already is one there.
440 if (!string_suffixes_string(L"\n", def)) {
441 out.push_back(L'\n');
442 }
443 out.append(L"end\n");
444 return out;
445 }
446