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