mirror of
https://github.com/fish-shell/fish-shell.git
synced 2026-06-01 21:21:15 -03:00
Rationalize $status and errors
Prior to this fix, fish was rather inconsistent in when $status gets set in response to an error. For example, a failed expansion like "$foo[" would not modify $status. This makes the following inter-related changes: 1. String expansion now directly returns the value to set for $status on error. The value is always used. 2. parser_t::eval() now directly returns the proc_status_t, which cleans up a lot of call sites. 3. We expose a new function exec_subshell_for_expand() which ignores $status but returns errors specifically related to subshell expansion. 4. We reify the notion of "expansion breaking" errors. These include command-not-found, expand syntax errors, and others. The upshot is we are more consistent about always setting $status on errors.
This commit is contained in:
@@ -24,19 +24,16 @@ int builtin_eval(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
|
|||||||
new_cmd += argv[i];
|
new_cmd += argv[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto cached_exec_count = parser.libdata().exec_count;
|
|
||||||
int status = STATUS_CMD_OK;
|
int status = STATUS_CMD_OK;
|
||||||
if (argc > 1) {
|
if (argc > 1) {
|
||||||
if (parser.eval(new_cmd, *streams.io_chain) != end_execution_reason_t::ok) {
|
auto res = parser.eval(new_cmd, *streams.io_chain);
|
||||||
status = STATUS_CMD_ERROR;
|
if (res.was_empty) {
|
||||||
} else if (cached_exec_count == parser.libdata().exec_count) {
|
|
||||||
// Issue #5692, in particular, to catch `eval ""`, `eval "begin; end;"`, etc.
|
// Issue #5692, in particular, to catch `eval ""`, `eval "begin; end;"`, etc.
|
||||||
// where we have an argument but nothing is executed.
|
// where we have an argument but nothing is executed.
|
||||||
status = STATUS_CMD_OK;
|
status = STATUS_CMD_OK;
|
||||||
} else {
|
} else {
|
||||||
status = parser.get_last_status();
|
status = res.status.status_value();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -817,6 +817,8 @@ enum {
|
|||||||
STATUS_ILLEGAL_CMD = 123,
|
STATUS_ILLEGAL_CMD = 123,
|
||||||
/// The status code used when `read` is asked to consume too much data.
|
/// The status code used when `read` is asked to consume too much data.
|
||||||
STATUS_READ_TOO_MUCH = 122,
|
STATUS_READ_TOO_MUCH = 122,
|
||||||
|
/// The status code when an expansion fails, for example, "$foo["
|
||||||
|
STATUS_EXPAND_ERROR = 121,
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Normally casting an expression to void discards its value, but GCC
|
/* Normally casting an expression to void discards its value, but GCC
|
||||||
|
|||||||
@@ -615,41 +615,40 @@ void completer_t::complete_cmd_desc(const wcstring &str) {
|
|||||||
// systems with a large set of manuals, but it should be ok since apropos is only called once.
|
// systems with a large set of manuals, but it should be ok since apropos is only called once.
|
||||||
lookup.clear();
|
lookup.clear();
|
||||||
list.clear();
|
list.clear();
|
||||||
if (exec_subshell(lookup_cmd, *ctx.parser, list, false /* don't apply exit status */) != -1) {
|
(void)exec_subshell(lookup_cmd, *ctx.parser, list, false /* don't apply exit status */);
|
||||||
// Then discard anything that is not a possible completion and put the result into a
|
// Then discard anything that is not a possible completion and put the result into a
|
||||||
// hashtable with the completion as key and the description as value.
|
// hashtable with the completion as key and the description as value.
|
||||||
//
|
//
|
||||||
// Should be reasonably fast, since no memory allocations are needed.
|
// Should be reasonably fast, since no memory allocations are needed.
|
||||||
// mqudsi: I don't know if the above were ever true, but it's certainly not any more.
|
// mqudsi: I don't know if the above were ever true, but it's certainly not any more.
|
||||||
// Plenty of allocations below.
|
// Plenty of allocations below.
|
||||||
for (const wcstring &elstr : list) {
|
for (const wcstring &elstr : list) {
|
||||||
if (elstr.length() < cmd.length()) continue;
|
if (elstr.length() < cmd.length()) continue;
|
||||||
const wcstring fullkey(elstr, cmd.length());
|
const wcstring fullkey(elstr, cmd.length());
|
||||||
|
|
||||||
size_t tab_idx = fullkey.find(L'\t');
|
size_t tab_idx = fullkey.find(L'\t');
|
||||||
if (tab_idx == wcstring::npos) continue;
|
if (tab_idx == wcstring::npos) continue;
|
||||||
|
|
||||||
const wcstring key(fullkey, 0, tab_idx);
|
const wcstring key(fullkey, 0, tab_idx);
|
||||||
wcstring val(fullkey, tab_idx + 1);
|
wcstring val(fullkey, tab_idx + 1);
|
||||||
|
|
||||||
// And once again I make sure the first character is uppercased because I like it that
|
// And once again I make sure the first character is uppercased because I like it that
|
||||||
// way, and I get to decide these things.
|
// way, and I get to decide these things.
|
||||||
if (!val.empty()) val[0] = towupper(val[0]);
|
if (!val.empty()) val[0] = towupper(val[0]);
|
||||||
lookup[key] = val;
|
lookup[key] = val;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Then do a lookup on every completion and if a match is found, change to the new
|
// Then do a lookup on every completion and if a match is found, change to the new
|
||||||
// description.
|
// description.
|
||||||
//
|
//
|
||||||
// This needs to do a reallocation for every description added, but there shouldn't be that
|
// This needs to do a reallocation for every description added, but there shouldn't be that
|
||||||
// many completions, so it should be ok.
|
// many completions, so it should be ok.
|
||||||
for (auto &completion : completions) {
|
for (auto &completion : completions) {
|
||||||
const wcstring &el = completion.completion;
|
const wcstring &el = completion.completion;
|
||||||
if (el.empty()) continue;
|
if (el.empty()) continue;
|
||||||
|
|
||||||
auto new_desc_iter = lookup.find(el);
|
auto new_desc_iter = lookup.find(el);
|
||||||
if (new_desc_iter != lookup.end()) completion.description = new_desc_iter->second;
|
if (new_desc_iter != lookup.end()) completion.description = new_desc_iter->second;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -683,7 +682,7 @@ void completer_t::complete_cmd(const wcstring &str_cmd) {
|
|||||||
if (result == expand_result_t::cancel) {
|
if (result == expand_result_t::cancel) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (result != expand_result_t::error && this->wants_descriptions()) {
|
if (result == expand_result_t::ok && this->wants_descriptions()) {
|
||||||
this->complete_cmd_desc(str_cmd);
|
this->complete_cmd_desc(str_cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1592,7 +1591,7 @@ void completer_t::perform() {
|
|||||||
auto expand_ret = expand_string(expression, &expression_expanded,
|
auto expand_ret = expand_string(expression, &expression_expanded,
|
||||||
expand_flag::no_descriptions, ctx);
|
expand_flag::no_descriptions, ctx);
|
||||||
wcstring_list_t vals;
|
wcstring_list_t vals;
|
||||||
if (expand_ret != expand_result_t::error) {
|
if (expand_ret == expand_result_t::ok) {
|
||||||
for (auto &completion : expression_expanded)
|
for (auto &completion : expression_expanded)
|
||||||
vals.emplace_back(std::move(completion.completion));
|
vals.emplace_back(std::move(completion.completion));
|
||||||
ctx.parser->vars().set(variable_name, ENV_LOCAL | ENV_EXPORT,
|
ctx.parser->vars().set(variable_name, ENV_LOCAL | ENV_EXPORT,
|
||||||
|
|||||||
165
src/exec.cpp
165
src/exec.cpp
@@ -706,18 +706,7 @@ static proc_performer_t get_performer_for_process(process_t *p, job_t *job,
|
|||||||
const parsed_source_ref_t &source = p->block_node_source;
|
const parsed_source_ref_t &source = p->block_node_source;
|
||||||
tnode_t<grammar::statement> node = p->internal_block_node;
|
tnode_t<grammar::statement> node = p->internal_block_node;
|
||||||
assert(source && node && "Process is missing node info");
|
assert(source && node && "Process is missing node info");
|
||||||
return [=](parser_t &parser) {
|
return [=](parser_t &parser) { return parser.eval_node(source, node, lineage).status; };
|
||||||
end_execution_reason_t res = parser.eval_node(source, node, lineage);
|
|
||||||
switch (res) {
|
|
||||||
case end_execution_reason_t::ok:
|
|
||||||
case end_execution_reason_t::error:
|
|
||||||
case end_execution_reason_t::cancelled:
|
|
||||||
return proc_status_t::from_exit_code(parser.get_last_status());
|
|
||||||
case end_execution_reason_t::control_flow:
|
|
||||||
default:
|
|
||||||
DIE("end_execution_reason_t::control_flow should not be returned from eval_node");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} else {
|
} else {
|
||||||
assert(p->type == process_type_t::function);
|
assert(p->type == process_type_t::function);
|
||||||
auto props = function_get_properties(p->argv0());
|
auto props = function_get_properties(p->argv0());
|
||||||
@@ -729,25 +718,15 @@ static proc_performer_t get_performer_for_process(process_t *p, job_t *job,
|
|||||||
return [=](parser_t &parser) {
|
return [=](parser_t &parser) {
|
||||||
// Pull out the job list from the function.
|
// Pull out the job list from the function.
|
||||||
tnode_t<grammar::job_list> body = props->func_node.child<1>();
|
tnode_t<grammar::job_list> body = props->func_node.child<1>();
|
||||||
const auto &ld = parser.libdata();
|
|
||||||
auto saved_exec_count = ld.exec_count;
|
|
||||||
const block_t *fb = function_prepare_environment(parser, *argv, *props);
|
const block_t *fb = function_prepare_environment(parser, *argv, *props);
|
||||||
auto res = parser.eval_node(props->parsed_source, body, lineage);
|
auto res = parser.eval_node(props->parsed_source, body, lineage);
|
||||||
function_restore_environment(parser, fb);
|
function_restore_environment(parser, fb);
|
||||||
|
|
||||||
switch (res) {
|
// If the function did not execute anything, treat it as success.
|
||||||
case end_execution_reason_t::ok:
|
if (res.was_empty) {
|
||||||
// If the function did not execute anything, treat it as success.
|
res = proc_status_t::from_exit_code(EXIT_SUCCESS);
|
||||||
return proc_status_t::from_exit_code(saved_exec_count == ld.exec_count
|
|
||||||
? EXIT_SUCCESS
|
|
||||||
: parser.get_last_status());
|
|
||||||
case end_execution_reason_t::error:
|
|
||||||
case end_execution_reason_t::cancelled:
|
|
||||||
return proc_status_t::from_exit_code(parser.get_last_status());
|
|
||||||
default:
|
|
||||||
case end_execution_reason_t::control_flow:
|
|
||||||
DIE("end_execution_reason_t::control_flow should not be returned from eval_node");
|
|
||||||
}
|
}
|
||||||
|
return res.status;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -879,7 +858,7 @@ static bool exec_process_in_job(parser_t &parser, process_t *p, const std::share
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (p->type != process_type_t::block_node) {
|
if (p->type != process_type_t::block_node) {
|
||||||
// An simple `begin ... end` should not be considered an execution of a command.
|
// A simple `begin ... end` should not be considered an execution of a command.
|
||||||
parser.libdata().exec_count++;
|
parser.libdata().exec_count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1121,60 +1100,10 @@ bool exec_job(parser_t &parser, const shared_ptr<job_t> &j, const job_lineage_t
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int exec_subshell_internal(const wcstring &cmd, parser_t &parser, wcstring_list_t *lst,
|
/// Populate \p lst with the output of \p buffer, perhaps splitting lines according to \p split.
|
||||||
bool apply_exit_status, bool is_subcmd) {
|
static void populate_subshell_output(wcstring_list_t *lst, const io_buffer_t &buffer, bool split) {
|
||||||
ASSERT_IS_MAIN_THREAD();
|
|
||||||
auto &ld = parser.libdata();
|
|
||||||
bool prev_subshell = ld.is_subshell;
|
|
||||||
auto prev_statuses = parser.get_last_statuses();
|
|
||||||
bool split_output = false;
|
|
||||||
|
|
||||||
auto prev_read_limit = ld.read_limit;
|
|
||||||
ld.read_limit = is_subcmd ? read_byte_limit : 0;
|
|
||||||
|
|
||||||
const auto ifs = parser.vars().get(L"IFS");
|
|
||||||
if (!ifs.missing_or_empty()) {
|
|
||||||
split_output = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
ld.is_subshell = true;
|
|
||||||
auto subcommand_statuses = statuses_t::just(-1); // assume the worst
|
|
||||||
|
|
||||||
// IO buffer creation may fail (e.g. if we have too many open files to make a pipe), so this may
|
|
||||||
// be null.
|
|
||||||
std::shared_ptr<io_buffer_t> buffer;
|
|
||||||
if (auto bufferfill = io_bufferfill_t::create(fd_set_t{}, ld.read_limit)) {
|
|
||||||
if (parser.eval(cmd, io_chain_t{bufferfill}, block_type_t::subst) == end_execution_reason_t::ok) {
|
|
||||||
subcommand_statuses = parser.get_last_statuses();
|
|
||||||
}
|
|
||||||
buffer = io_bufferfill_t::finish(std::move(bufferfill));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (buffer && buffer->buffer().discarded()) {
|
|
||||||
subcommand_statuses = statuses_t::just(STATUS_READ_TOO_MUCH);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the caller asked us to preserve the exit status, restore the old status. Otherwise set the
|
|
||||||
// status of the subcommand.
|
|
||||||
if (apply_exit_status) {
|
|
||||||
// Hack: If the evaluation failed, avoid returning -1 to the user.
|
|
||||||
if (subcommand_statuses.status == -1) {
|
|
||||||
parser.set_last_statuses(statuses_t::just(255));
|
|
||||||
} else {
|
|
||||||
parser.set_last_statuses(subcommand_statuses);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
parser.set_last_statuses(std::move(prev_statuses));
|
|
||||||
}
|
|
||||||
|
|
||||||
ld.is_subshell = prev_subshell;
|
|
||||||
ld.read_limit = prev_read_limit;
|
|
||||||
|
|
||||||
if (lst == nullptr || !buffer) {
|
|
||||||
return subcommand_statuses.status;
|
|
||||||
}
|
|
||||||
// Walk over all the elements.
|
// Walk over all the elements.
|
||||||
for (const auto &elem : buffer->buffer().elements()) {
|
for (const auto &elem : buffer.buffer().elements()) {
|
||||||
if (elem.is_explicitly_separated()) {
|
if (elem.is_explicitly_separated()) {
|
||||||
// Just append this one.
|
// Just append this one.
|
||||||
lst->push_back(str2wcstring(elem.contents));
|
lst->push_back(str2wcstring(elem.contents));
|
||||||
@@ -1185,7 +1114,7 @@ static int exec_subshell_internal(const wcstring &cmd, parser_t &parser, wcstrin
|
|||||||
assert(!elem.is_explicitly_separated() && "should not be explicitly separated");
|
assert(!elem.is_explicitly_separated() && "should not be explicitly separated");
|
||||||
const char *begin = elem.contents.data();
|
const char *begin = elem.contents.data();
|
||||||
const char *end = begin + elem.contents.size();
|
const char *end = begin + elem.contents.size();
|
||||||
if (split_output) {
|
if (split) {
|
||||||
const char *cursor = begin;
|
const char *cursor = begin;
|
||||||
while (cursor < end) {
|
while (cursor < end) {
|
||||||
// Look for the next separator.
|
// Look for the next separator.
|
||||||
@@ -1210,17 +1139,73 @@ static int exec_subshell_internal(const wcstring &cmd, parser_t &parser, wcstrin
|
|||||||
lst->push_back(str2wcstring(begin, end - begin));
|
lst->push_back(str2wcstring(begin, end - begin));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return subcommand_statuses.status;
|
/// Execute \p cmd in a subshell in \p parser. If \p lst is not null, populate it with the output.
|
||||||
|
/// Return $status in \p out_status.
|
||||||
|
/// If \p apply_exit_status is false, then reset $status back to its original value.
|
||||||
|
/// \p is_subcmd controls whether we apply a read limit.
|
||||||
|
/// \p break_expand is used to propagate whether the result should be "expansion breaking" in the
|
||||||
|
/// sense that subshells used during string expansion should halt that expansion. \return the value
|
||||||
|
/// of $status.
|
||||||
|
static int exec_subshell_internal(const wcstring &cmd, parser_t &parser, wcstring_list_t *lst,
|
||||||
|
bool *break_expand, bool apply_exit_status, bool is_subcmd) {
|
||||||
|
ASSERT_IS_MAIN_THREAD();
|
||||||
|
auto &ld = parser.libdata();
|
||||||
|
|
||||||
|
scoped_push<bool> is_subshell(&ld.is_subshell, true);
|
||||||
|
scoped_push<size_t> read_limit(&ld.read_limit, is_subcmd ? read_byte_limit : 0);
|
||||||
|
|
||||||
|
auto prev_statuses = parser.get_last_statuses();
|
||||||
|
const cleanup_t put_back([&] {
|
||||||
|
if (!apply_exit_status) {
|
||||||
|
parser.set_last_statuses(prev_statuses);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const bool split_output = !parser.vars().get(L"IFS").missing_or_empty();
|
||||||
|
|
||||||
|
// IO buffer creation may fail (e.g. if we have too many open files to make a pipe), so this may
|
||||||
|
// be null.
|
||||||
|
auto bufferfill = io_bufferfill_t::create(fd_set_t{}, ld.read_limit);
|
||||||
|
if (!bufferfill) {
|
||||||
|
*break_expand = true;
|
||||||
|
return STATUS_CMD_ERROR;
|
||||||
|
}
|
||||||
|
eval_res_t eval_res = parser.eval(cmd, io_chain_t{bufferfill}, block_type_t::subst);
|
||||||
|
std::shared_ptr<io_buffer_t> buffer = io_bufferfill_t::finish(std::move(bufferfill));
|
||||||
|
if (buffer->buffer().discarded()) {
|
||||||
|
*break_expand = true;
|
||||||
|
return STATUS_READ_TOO_MUCH;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eval_res.break_expand) {
|
||||||
|
*break_expand = true;
|
||||||
|
return eval_res.status.status_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lst) {
|
||||||
|
populate_subshell_output(lst, *buffer, split_output);
|
||||||
|
}
|
||||||
|
*break_expand = false;
|
||||||
|
return eval_res.status.status_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
int exec_subshell_for_expand(const wcstring &cmd, parser_t &parser, wcstring_list_t &outputs) {
|
||||||
|
ASSERT_IS_MAIN_THREAD();
|
||||||
|
bool break_expand = false;
|
||||||
|
int ret = exec_subshell_internal(cmd, parser, &outputs, &break_expand, true, true);
|
||||||
|
// Only return an error code if we should break expansion.
|
||||||
|
return break_expand ? ret : STATUS_CMD_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int exec_subshell(const wcstring &cmd, parser_t &parser, bool apply_exit_status) {
|
||||||
|
bool break_expand = false;
|
||||||
|
return exec_subshell_internal(cmd, parser, nullptr, &break_expand, apply_exit_status, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
int exec_subshell(const wcstring &cmd, parser_t &parser, wcstring_list_t &outputs,
|
int exec_subshell(const wcstring &cmd, parser_t &parser, wcstring_list_t &outputs,
|
||||||
bool apply_exit_status, bool is_subcmd) {
|
bool apply_exit_status) {
|
||||||
ASSERT_IS_MAIN_THREAD();
|
bool break_expand = false;
|
||||||
return exec_subshell_internal(cmd, parser, &outputs, apply_exit_status, is_subcmd);
|
return exec_subshell_internal(cmd, parser, &outputs, &break_expand, apply_exit_status, false);
|
||||||
}
|
|
||||||
|
|
||||||
int exec_subshell(const wcstring &cmd, parser_t &parser, bool apply_exit_status, bool is_subcmd) {
|
|
||||||
ASSERT_IS_MAIN_THREAD();
|
|
||||||
return exec_subshell_internal(cmd, parser, nullptr, apply_exit_status, is_subcmd);
|
|
||||||
}
|
}
|
||||||
|
|||||||
19
src/exec.h
19
src/exec.h
@@ -17,17 +17,22 @@ struct job_lineage_t;
|
|||||||
class parser_t;
|
class parser_t;
|
||||||
bool exec_job(parser_t &parser, const std::shared_ptr<job_t> &j, const job_lineage_t &lineage);
|
bool exec_job(parser_t &parser, const std::shared_ptr<job_t> &j, const job_lineage_t &lineage);
|
||||||
|
|
||||||
/// Evaluate the expression cmd in a subshell, add the outputs into the list l. On return, the
|
/// Evaluate a command.
|
||||||
/// status flag as returned bu \c proc_gfet_last_status will not be changed.
|
|
||||||
///
|
///
|
||||||
/// \param cmd the command to execute
|
/// \param cmd the command to execute
|
||||||
/// \param outputs The list to insert output into.
|
/// \param parser the parser with which to execute code
|
||||||
|
/// \param outputs the list to insert output into.
|
||||||
|
/// \param apply_exit_status if set, update $status within the parser, otherwise do not.
|
||||||
///
|
///
|
||||||
/// \return the status of the last job to exit, or -1 if en error was encountered.
|
/// \return a value appropriate for populating $status.
|
||||||
|
int exec_subshell(const wcstring &cmd, parser_t &parser, bool apply_exit_status);
|
||||||
int exec_subshell(const wcstring &cmd, parser_t &parser, wcstring_list_t &outputs,
|
int exec_subshell(const wcstring &cmd, parser_t &parser, wcstring_list_t &outputs,
|
||||||
bool apply_exit_status, bool is_subcmd = false);
|
bool apply_exit_status);
|
||||||
int exec_subshell(const wcstring &cmd, parser_t &parser, bool apply_exit_status,
|
|
||||||
bool is_subcmd = false);
|
/// Like exec_subshell, but only returns expansion-breaking errors. That is, a zero return means
|
||||||
|
/// "success" (even though the command may have failed), a non-zero return means that we should
|
||||||
|
/// halt expansion.
|
||||||
|
int exec_subshell_for_expand(const wcstring &cmd, parser_t &parser, wcstring_list_t &outputs);
|
||||||
|
|
||||||
/// Loops over close until the syscall was run without being interrupted.
|
/// Loops over close until the syscall was run without being interrupted.
|
||||||
void exec_close(int fd);
|
void exec_close(int fd);
|
||||||
|
|||||||
@@ -528,7 +528,7 @@ static expand_result_t expand_braces(const wcstring &instr, expand_flags_t flags
|
|||||||
|
|
||||||
if (syntax_error) {
|
if (syntax_error) {
|
||||||
append_syntax_error(errors, SOURCE_LOCATION_UNKNOWN, _(L"Mismatched braces"));
|
append_syntax_error(errors, SOURCE_LOCATION_UNKNOWN, _(L"Mismatched braces"));
|
||||||
return expand_result_t::error;
|
return expand_result_t::make_error(STATUS_EXPAND_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (brace_begin == nullptr) {
|
if (brace_begin == nullptr) {
|
||||||
@@ -574,9 +574,10 @@ static expand_result_t expand_braces(const wcstring &instr, expand_flags_t flags
|
|||||||
return expand_result_t::ok;
|
return expand_result_t::ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Perform cmdsubst expansion.
|
/// Expand a command substitution \p input, executing on \p parser, and inserting the results into
|
||||||
static bool expand_cmdsubst(wcstring input, parser_t &parser, completion_list_t *out_list,
|
/// \p out_list, or any errors into \p errors. \return an expand result.
|
||||||
parse_error_list_t *errors) {
|
static expand_result_t expand_cmdsubst(wcstring input, parser_t &parser,
|
||||||
|
completion_list_t *out_list, parse_error_list_t *errors) {
|
||||||
wchar_t *paren_begin = nullptr, *paren_end = nullptr;
|
wchar_t *paren_begin = nullptr, *paren_end = nullptr;
|
||||||
wchar_t *tail_begin = nullptr;
|
wchar_t *tail_begin = nullptr;
|
||||||
size_t i, j;
|
size_t i, j;
|
||||||
@@ -586,11 +587,11 @@ static bool expand_cmdsubst(wcstring input, parser_t &parser, completion_list_t
|
|||||||
switch (parse_util_locate_cmdsubst(in, &paren_begin, &paren_end, false)) {
|
switch (parse_util_locate_cmdsubst(in, &paren_begin, &paren_end, false)) {
|
||||||
case -1: {
|
case -1: {
|
||||||
append_syntax_error(errors, SOURCE_LOCATION_UNKNOWN, L"Mismatched parenthesis");
|
append_syntax_error(errors, SOURCE_LOCATION_UNKNOWN, L"Mismatched parenthesis");
|
||||||
return false;
|
return expand_result_t::make_error(STATUS_EXPAND_ERROR);
|
||||||
}
|
}
|
||||||
case 0: {
|
case 0: {
|
||||||
append_completion(out_list, std::move(input));
|
append_completion(out_list, std::move(input));
|
||||||
return true;
|
return expand_result_t::ok;
|
||||||
}
|
}
|
||||||
case 1: {
|
case 1: {
|
||||||
break;
|
break;
|
||||||
@@ -603,18 +604,23 @@ static bool expand_cmdsubst(wcstring input, parser_t &parser, completion_list_t
|
|||||||
|
|
||||||
wcstring_list_t sub_res;
|
wcstring_list_t sub_res;
|
||||||
const wcstring subcmd(paren_begin + 1, paren_end - paren_begin - 1);
|
const wcstring subcmd(paren_begin + 1, paren_end - paren_begin - 1);
|
||||||
if (exec_subshell(subcmd, parser, sub_res, true /* apply_exit_status */,
|
int subshell_status = exec_subshell_for_expand(subcmd, parser, sub_res);
|
||||||
true /* is_subcmd */) == -1) {
|
if (subshell_status != 0) {
|
||||||
append_cmdsub_error(errors, SOURCE_LOCATION_UNKNOWN,
|
// TODO: Ad-hoc switch, how can we enumerate the possible errors more safely?
|
||||||
L"Unknown error while evaluating command substitution");
|
const wchar_t *err;
|
||||||
return false;
|
switch (subshell_status) {
|
||||||
}
|
case STATUS_READ_TOO_MUCH:
|
||||||
|
err = L"Too much data emitted by command substitution so it was discarded";
|
||||||
if (parser.get_last_status() == STATUS_READ_TOO_MUCH) {
|
break;
|
||||||
append_cmdsub_error(
|
case STATUS_CMD_ERROR:
|
||||||
errors, in - paren_begin,
|
err = L"Too many active file descriptors";
|
||||||
_(L"Too much data emitted by command substitution so it was discarded\n"));
|
break;
|
||||||
return false;
|
default:
|
||||||
|
err = L"Unknown error while evaluating command substitution";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
append_cmdsub_error(errors, in - paren_begin, _(err));
|
||||||
|
return expand_result_t::make_error(subshell_status);
|
||||||
}
|
}
|
||||||
|
|
||||||
tail_begin = paren_end + 1;
|
tail_begin = paren_end + 1;
|
||||||
@@ -627,7 +633,7 @@ static bool expand_cmdsubst(wcstring input, parser_t &parser, completion_list_t
|
|||||||
bad_pos = parse_slice(slice_begin, &slice_end, slice_idx, sub_res.size());
|
bad_pos = parse_slice(slice_begin, &slice_end, slice_idx, sub_res.size());
|
||||||
if (bad_pos != 0) {
|
if (bad_pos != 0) {
|
||||||
append_syntax_error(errors, slice_begin - in + bad_pos, L"Invalid index value");
|
append_syntax_error(errors, slice_begin - in + bad_pos, L"Invalid index value");
|
||||||
return false;
|
return expand_result_t::make_error(STATUS_EXPAND_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
wcstring_list_t sub_res2;
|
wcstring_list_t sub_res2;
|
||||||
@@ -684,7 +690,7 @@ static bool expand_cmdsubst(wcstring input, parser_t &parser, completion_list_t
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return parser.get_last_status() != STATUS_READ_TOO_MUCH;
|
return expand_result_t::ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Given that input[0] is HOME_DIRECTORY or tilde (ugh), return the user's name. Return the empty
|
// Given that input[0] is HOME_DIRECTORY or tilde (ugh), return the user's name. Return the empty
|
||||||
@@ -885,21 +891,18 @@ expand_result_t expander_t::stage_cmdsubst(wcstring input, completion_list_t *ou
|
|||||||
switch (parse_util_locate_cmdsubst_range(input, &cur, nullptr, &start, &end, true)) {
|
switch (parse_util_locate_cmdsubst_range(input, &cur, nullptr, &start, &end, true)) {
|
||||||
case 0:
|
case 0:
|
||||||
append_completion(out, std::move(input));
|
append_completion(out, std::move(input));
|
||||||
break;
|
return expand_result_t::ok;
|
||||||
case 1:
|
case 1:
|
||||||
append_cmdsub_error(errors, start, L"Command substitutions not allowed");
|
append_cmdsub_error(errors, start, L"Command substitutions not allowed");
|
||||||
/* intentionally falls through */
|
/* intentionally falls through */
|
||||||
case -1:
|
case -1:
|
||||||
default:
|
default:
|
||||||
return expand_result_t::error;
|
return expand_result_t::make_error(STATUS_EXPAND_ERROR);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
assert(ctx.parser && "Must have a parser to expand command substitutions");
|
assert(ctx.parser && "Must have a parser to expand command substitutions");
|
||||||
bool cmdsubst_ok = expand_cmdsubst(std::move(input), *ctx.parser, out, errors);
|
return expand_cmdsubst(std::move(input), *ctx.parser, out, errors);
|
||||||
if (!cmdsubst_ok) return expand_result_t::error;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return expand_result_t::ok;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
expand_result_t expander_t::stage_variables(wcstring input, completion_list_t *out) {
|
expand_result_t expander_t::stage_variables(wcstring input, completion_list_t *out) {
|
||||||
@@ -918,7 +921,7 @@ expand_result_t expander_t::stage_variables(wcstring input, completion_list_t *o
|
|||||||
} else {
|
} else {
|
||||||
size_t size = next.size();
|
size_t size = next.size();
|
||||||
if (!expand_variables(std::move(next), out, size, ctx.vars, errors)) {
|
if (!expand_variables(std::move(next), out, size, ctx.vars, errors)) {
|
||||||
return expand_result_t::error;
|
return expand_result_t::make_error(STATUS_EXPAND_ERROR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return expand_result_t::ok;
|
return expand_result_t::ok;
|
||||||
@@ -1085,7 +1088,7 @@ expand_result_t expander_t::expand_string(wcstring input, completion_list_t *out
|
|||||||
total_result = expand_result_t::ok;
|
total_result = expand_result_t::ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (total_result != expand_result_t::error) {
|
if (total_result == expand_result_t::ok) {
|
||||||
// Hack to un-expand tildes (see #647).
|
// Hack to un-expand tildes (see #647).
|
||||||
if (!(flags & expand_flag::skip_home_directories)) {
|
if (!(flags & expand_flag::skip_home_directories)) {
|
||||||
unexpand_tildes(input, ctx.vars, &completions);
|
unexpand_tildes(input, ctx.vars, &completions);
|
||||||
@@ -1113,7 +1116,7 @@ bool expand_one(wcstring &string, expand_flags_t flags, const operation_context_
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (expand_string(std::move(string), &completions, flags | expand_flag::no_descriptions, ctx,
|
if (expand_string(std::move(string), &completions, flags | expand_flag::no_descriptions, ctx,
|
||||||
errors) != expand_result_t::error &&
|
errors) == expand_result_t::ok &&
|
||||||
completions.size() == 1) {
|
completions.size() == 1) {
|
||||||
string = std::move(completions.at(0).completion);
|
string = std::move(completions.at(0).completion);
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
43
src/expand.h
43
src/expand.h
@@ -100,16 +100,39 @@ enum : wchar_t {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// These are the possible return values for expand_string.
|
/// These are the possible return values for expand_string.
|
||||||
enum class expand_result_t {
|
struct expand_result_t {
|
||||||
/// There was a syntax error, for example, unmatched braces.
|
enum result_t {
|
||||||
error,
|
/// There was an error, for example, unmatched braces.
|
||||||
/// Expansion succeeded.
|
error,
|
||||||
ok,
|
/// Expansion succeeded.
|
||||||
/// Expansion was cancelled (e.g. control-C).
|
ok,
|
||||||
cancel,
|
/// Expansion was cancelled (e.g. control-C).
|
||||||
/// Expansion succeeded, but a wildcard in the string matched no files,
|
cancel,
|
||||||
/// so the output is empty.
|
/// Expansion succeeded, but a wildcard in the string matched no files,
|
||||||
wildcard_no_match,
|
/// so the output is empty.
|
||||||
|
wildcard_no_match,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The result of expansion.
|
||||||
|
result_t result;
|
||||||
|
|
||||||
|
/// If expansion resulted in an error, this is an appropriate value with which to populate
|
||||||
|
/// $status.
|
||||||
|
int status{0};
|
||||||
|
|
||||||
|
/* implicit */ expand_result_t(result_t result) : result(result) {}
|
||||||
|
|
||||||
|
/// operator== allows for comparison against result_t values.
|
||||||
|
bool operator==(result_t rhs) const { return result == rhs; }
|
||||||
|
bool operator!=(result_t rhs) const { return !(*this == rhs); }
|
||||||
|
|
||||||
|
/// Make an error value with the given status.
|
||||||
|
static expand_result_t make_error(int status) {
|
||||||
|
assert(status != 0 && "status cannot be 0 for an error result");
|
||||||
|
expand_result_t result(error);
|
||||||
|
result.status = status;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The string represented by PROCESS_EXPAND_SELF
|
/// The string represented by PROCESS_EXPAND_SELF
|
||||||
|
|||||||
@@ -1033,16 +1033,13 @@ static void test_1_cancellation(const wchar_t *src) {
|
|||||||
usleep(delay * 1E6);
|
usleep(delay * 1E6);
|
||||||
pthread_kill(thread, SIGINT);
|
pthread_kill(thread, SIGINT);
|
||||||
});
|
});
|
||||||
end_execution_reason_t ret = parser_t::principal_parser().eval(src, io_chain_t{filler});
|
eval_res_t res = parser_t::principal_parser().eval(src, io_chain_t{filler});
|
||||||
auto buffer = io_bufferfill_t::finish(std::move(filler));
|
auto buffer = io_bufferfill_t::finish(std::move(filler));
|
||||||
if (buffer->buffer().size() != 0) {
|
if (buffer->buffer().size() != 0) {
|
||||||
err(L"Expected 0 bytes in out_buff, but instead found %lu bytes, for command %ls\n",
|
err(L"Expected 0 bytes in out_buff, but instead found %lu bytes, for command %ls\n",
|
||||||
buffer->buffer().size(), src);
|
buffer->buffer().size(), src);
|
||||||
}
|
}
|
||||||
// TODO: cancelling out of command substitutions is currently reported as an error, not a
|
do_test(res.status.signal_exited() && res.status.signal_code() == SIGINT);
|
||||||
// cancellation.
|
|
||||||
// do_test(ret == end_execution_reason_t::cancelled);
|
|
||||||
(void)ret;
|
|
||||||
iothread_drain_all();
|
iothread_drain_all();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -259,8 +259,8 @@ end_execution_reason_t parse_execution_context_t::run_if_statement(
|
|||||||
tnode_t<g::job_conjunction> condition_head = if_clause.child<1>();
|
tnode_t<g::job_conjunction> condition_head = if_clause.child<1>();
|
||||||
tnode_t<g::andor_job_list> condition_boolean_tail = if_clause.child<3>();
|
tnode_t<g::andor_job_list> condition_boolean_tail = if_clause.child<3>();
|
||||||
|
|
||||||
// Check the condition and the tail. We treat end_execution_reason_t::error here as failure, in
|
// Check the condition and the tail. We treat end_execution_reason_t::error here as failure,
|
||||||
// accordance with historic behavior.
|
// in accordance with historic behavior.
|
||||||
end_execution_reason_t cond_ret = run_job_conjunction(condition_head, associated_block);
|
end_execution_reason_t cond_ret = run_job_conjunction(condition_head, associated_block);
|
||||||
if (cond_ret == end_execution_reason_t::ok) {
|
if (cond_ret == end_execution_reason_t::ok) {
|
||||||
cond_ret = run_job_list(condition_boolean_tail, associated_block);
|
cond_ret = run_job_list(condition_boolean_tail, associated_block);
|
||||||
@@ -350,10 +350,8 @@ end_execution_reason_t parse_execution_context_t::run_function_statement(
|
|||||||
|
|
||||||
wcstring errtext = streams.err.contents();
|
wcstring errtext = streams.err.contents();
|
||||||
if (!errtext.empty()) {
|
if (!errtext.empty()) {
|
||||||
this->report_error(header, L"%ls", errtext.c_str());
|
return this->report_error(err, header, L"%ls", errtext.c_str());
|
||||||
result = end_execution_reason_t::error;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -385,8 +383,8 @@ end_execution_reason_t parse_execution_context_t::run_for_statement(
|
|||||||
tnode_t<g::tok_string> var_name_node = header.child<1>();
|
tnode_t<g::tok_string> var_name_node = header.child<1>();
|
||||||
wcstring for_var_name = get_source(var_name_node);
|
wcstring for_var_name = get_source(var_name_node);
|
||||||
if (!expand_one(for_var_name, expand_flags_t{}, ctx)) {
|
if (!expand_one(for_var_name, expand_flags_t{}, ctx)) {
|
||||||
report_error(var_name_node, FAILED_EXPANSION_VARIABLE_NAME_ERR_MSG, for_var_name.c_str());
|
return report_error(STATUS_EXPAND_ERROR, var_name_node,
|
||||||
return end_execution_reason_t::error;
|
FAILED_EXPANSION_VARIABLE_NAME_ERR_MSG, for_var_name.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the contents to iterate over.
|
// Get the contents to iterate over.
|
||||||
@@ -399,9 +397,9 @@ end_execution_reason_t parse_execution_context_t::run_for_statement(
|
|||||||
|
|
||||||
auto var = parser->vars().get(for_var_name, ENV_DEFAULT);
|
auto var = parser->vars().get(for_var_name, ENV_DEFAULT);
|
||||||
if (var && var->read_only()) {
|
if (var && var->read_only()) {
|
||||||
report_error(var_name_node, L"You cannot use read-only variable '%ls' in a for loop",
|
return report_error(STATUS_INVALID_ARGS, var_name_node,
|
||||||
for_var_name.c_str());
|
L"You cannot use read-only variable '%ls' in a for loop",
|
||||||
return end_execution_reason_t::error;
|
for_var_name.c_str());
|
||||||
}
|
}
|
||||||
int retval;
|
int retval;
|
||||||
if (var) {
|
if (var) {
|
||||||
@@ -412,8 +410,8 @@ end_execution_reason_t parse_execution_context_t::run_for_statement(
|
|||||||
assert(retval == ENV_OK);
|
assert(retval == ENV_OK);
|
||||||
|
|
||||||
if (!valid_var_name(for_var_name)) {
|
if (!valid_var_name(for_var_name)) {
|
||||||
report_error(var_name_node, BUILTIN_ERR_VARNAME, L"for", for_var_name.c_str());
|
return report_error(STATUS_INVALID_ARGS, var_name_node, BUILTIN_ERR_VARNAME, L"for",
|
||||||
return end_execution_reason_t::error;
|
for_var_name.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
trace_if_enabled(*parser, L"for", arguments);
|
trace_if_enabled(*parser, L"for", arguments);
|
||||||
@@ -462,19 +460,20 @@ end_execution_reason_t parse_execution_context_t::run_switch_statement(
|
|||||||
expand_flag::no_descriptions, ctx, &errors);
|
expand_flag::no_descriptions, ctx, &errors);
|
||||||
parse_error_offset_source_start(&errors, switch_value_n.source_range()->start);
|
parse_error_offset_source_start(&errors, switch_value_n.source_range()->start);
|
||||||
|
|
||||||
switch (expand_ret) {
|
switch (expand_ret.result) {
|
||||||
case expand_result_t::error:
|
case expand_result_t::error:
|
||||||
return report_errors(errors);
|
return report_errors(expand_ret.status, errors);
|
||||||
|
|
||||||
case expand_result_t::cancel:
|
case expand_result_t::cancel:
|
||||||
return end_execution_reason_t::cancelled;
|
return end_execution_reason_t::cancelled;
|
||||||
|
|
||||||
case expand_result_t::wildcard_no_match:
|
case expand_result_t::wildcard_no_match:
|
||||||
return report_unmatched_wildcard_error(switch_value_n);
|
return report_error(STATUS_UNMATCHED_WILDCARD, switch_value_n, WILDCARD_ERR_MSG,
|
||||||
|
get_source(switch_value_n).c_str());
|
||||||
|
|
||||||
case expand_result_t::ok:
|
case expand_result_t::ok:
|
||||||
if (switch_values_expanded.size() > 1) {
|
if (switch_values_expanded.size() > 1) {
|
||||||
return report_error(switch_value_n,
|
return report_error(STATUS_INVALID_ARGS, switch_value_n,
|
||||||
_(L"switch: Expected at most one argument, got %lu\n"),
|
_(L"switch: Expected at most one argument, got %lu\n"),
|
||||||
switch_values_expanded.size());
|
switch_values_expanded.size());
|
||||||
}
|
}
|
||||||
@@ -617,9 +616,8 @@ end_execution_reason_t parse_execution_context_t::run_while_statement(
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reports an error. Always returns end_execution_reason_t::error, so you can assign the result to an
|
// Reports an error. Always returns end_execution_reason_t::error.
|
||||||
// 'errored' variable.
|
end_execution_reason_t parse_execution_context_t::report_error(int status, const parse_node_t &node,
|
||||||
end_execution_reason_t parse_execution_context_t::report_error(const parse_node_t &node,
|
|
||||||
const wchar_t *fmt, ...) const {
|
const wchar_t *fmt, ...) const {
|
||||||
// Create an error.
|
// Create an error.
|
||||||
parse_error_list_t error_list = parse_error_list_t(1);
|
parse_error_list_t error_list = parse_error_list_t(1);
|
||||||
@@ -633,12 +631,11 @@ end_execution_reason_t parse_execution_context_t::report_error(const parse_node_
|
|||||||
error->text = vformat_string(fmt, va);
|
error->text = vformat_string(fmt, va);
|
||||||
va_end(va);
|
va_end(va);
|
||||||
|
|
||||||
this->report_errors(error_list);
|
return this->report_errors(status, error_list);
|
||||||
return end_execution_reason_t::error;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
end_execution_reason_t parse_execution_context_t::report_errors(
|
end_execution_reason_t parse_execution_context_t::report_errors(
|
||||||
const parse_error_list_t &error_list) const {
|
int status, const parse_error_list_t &error_list) const {
|
||||||
if (!parser->cancellation_signal) {
|
if (!parser->cancellation_signal) {
|
||||||
if (error_list.empty()) {
|
if (error_list.empty()) {
|
||||||
FLOG(error, L"Error reported but no error text found.");
|
FLOG(error, L"Error reported but no error text found.");
|
||||||
@@ -652,15 +649,10 @@ end_execution_reason_t parse_execution_context_t::report_errors(
|
|||||||
if (!should_suppress_stderr_for_tests()) {
|
if (!should_suppress_stderr_for_tests()) {
|
||||||
std::fwprintf(stderr, L"%ls", backtrace_and_desc.c_str());
|
std::fwprintf(stderr, L"%ls", backtrace_and_desc.c_str());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return end_execution_reason_t::error;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reports an unmatched wildcard error and returns end_execution_reason_t::error.
|
// Mark status.
|
||||||
end_execution_reason_t parse_execution_context_t::report_unmatched_wildcard_error(
|
parser->set_last_statuses(statuses_t::just(status));
|
||||||
const parse_node_t &unmatched_wildcard) const {
|
}
|
||||||
parser->set_last_statuses(statuses_t::just(STATUS_UNMATCHED_WILDCARD));
|
|
||||||
report_error(unmatched_wildcard, WILDCARD_ERR_MSG, get_source(unmatched_wildcard).c_str());
|
|
||||||
return end_execution_reason_t::error;
|
return end_execution_reason_t::error;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -672,7 +664,8 @@ end_execution_reason_t parse_execution_context_t::handle_command_not_found(
|
|||||||
|
|
||||||
const wchar_t *const cmd = cmd_str.c_str();
|
const wchar_t *const cmd = cmd_str.c_str();
|
||||||
if (err_code != ENOENT) {
|
if (err_code != ENOENT) {
|
||||||
this->report_error(statement, _(L"The file '%ls' is not executable by this user"), cmd);
|
return this->report_error(STATUS_NOT_EXECUTABLE, statement,
|
||||||
|
_(L"The file '%ls' is not executable by this user"), cmd);
|
||||||
} else {
|
} else {
|
||||||
// Handle unrecognized commands with standard command not found handler that can make better
|
// Handle unrecognized commands with standard command not found handler that can make better
|
||||||
// error messages.
|
// error messages.
|
||||||
@@ -692,14 +685,8 @@ end_execution_reason_t parse_execution_context_t::handle_command_not_found(
|
|||||||
event_fire_generic(*parser, L"fish_command_not_found", &event_args);
|
event_fire_generic(*parser, L"fish_command_not_found", &event_args);
|
||||||
|
|
||||||
// Here we want to report an error (so it shows a backtrace), but with no text.
|
// Here we want to report an error (so it shows a backtrace), but with no text.
|
||||||
this->report_error(statement, L"");
|
return this->report_error(STATUS_CMD_UNKNOWN, statement, L"");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the last proc status appropriately.
|
|
||||||
int status = err_code == ENOENT ? STATUS_CMD_UNKNOWN : STATUS_NOT_EXECUTABLE;
|
|
||||||
parser->set_last_statuses(statuses_t::just(status));
|
|
||||||
|
|
||||||
return end_execution_reason_t::error;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
end_execution_reason_t parse_execution_context_t::expand_command(
|
end_execution_reason_t parse_execution_context_t::expand_command(
|
||||||
@@ -718,21 +705,22 @@ end_execution_reason_t parse_execution_context_t::expand_command(
|
|||||||
expand_result_t expand_err =
|
expand_result_t expand_err =
|
||||||
expand_to_command_and_args(unexp_cmd, ctx, out_cmd, out_args, &errors);
|
expand_to_command_and_args(unexp_cmd, ctx, out_cmd, out_args, &errors);
|
||||||
if (expand_err == expand_result_t::error) {
|
if (expand_err == expand_result_t::error) {
|
||||||
parser->set_last_statuses(statuses_t::just(STATUS_ILLEGAL_CMD));
|
|
||||||
// Issue #5812 - the expansions were done on the command token,
|
// Issue #5812 - the expansions were done on the command token,
|
||||||
// excluding prefixes such as " " or "if ".
|
// excluding prefixes such as " " or "if ".
|
||||||
// This means that the error positions are relative to the beginning
|
// This means that the error positions are relative to the beginning
|
||||||
// of the token; we need to make them relative to the original source.
|
// of the token; we need to make them relative to the original source.
|
||||||
for (auto &error : errors) error.source_start += pos_of_command_token;
|
for (auto &error : errors) error.source_start += pos_of_command_token;
|
||||||
return report_errors(errors);
|
return report_errors(STATUS_ILLEGAL_CMD, errors);
|
||||||
} else if (expand_err == expand_result_t::wildcard_no_match) {
|
} else if (expand_err == expand_result_t::wildcard_no_match) {
|
||||||
return report_unmatched_wildcard_error(statement);
|
return report_error(STATUS_UNMATCHED_WILDCARD, statement, WILDCARD_ERR_MSG,
|
||||||
|
get_source(statement).c_str());
|
||||||
}
|
}
|
||||||
assert(expand_err == expand_result_t::ok);
|
assert(expand_err == expand_result_t::ok);
|
||||||
|
|
||||||
// Complain if the resulting expansion was empty, or expanded to an empty string.
|
// Complain if the resulting expansion was empty, or expanded to an empty string.
|
||||||
if (out_cmd->empty()) {
|
if (out_cmd->empty()) {
|
||||||
return this->report_error(statement, _(L"The expanded command was empty."));
|
return this->report_error(STATUS_ILLEGAL_CMD, statement,
|
||||||
|
_(L"The expanded command was empty."));
|
||||||
}
|
}
|
||||||
return end_execution_reason_t::ok;
|
return end_execution_reason_t::ok;
|
||||||
}
|
}
|
||||||
@@ -840,8 +828,9 @@ end_execution_reason_t parse_execution_context_t::populate_plain_process(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The set of IO redirections that we construct for the process.
|
// The set of IO redirections that we construct for the process.
|
||||||
if (!this->determine_redirections(statement.child<1>(), &redirections)) {
|
auto reason = this->determine_redirections(statement.child<1>(), &redirections);
|
||||||
return end_execution_reason_t::error;
|
if (reason != end_execution_reason_t::ok) {
|
||||||
|
return reason;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine the process type.
|
// Determine the process type.
|
||||||
@@ -876,18 +865,19 @@ end_execution_reason_t parse_execution_context_t::expand_arguments_from_nodes(
|
|||||||
auto expand_ret =
|
auto expand_ret =
|
||||||
expand_string(arg_str, &arg_expanded, expand_flag::no_descriptions, ctx, &errors);
|
expand_string(arg_str, &arg_expanded, expand_flag::no_descriptions, ctx, &errors);
|
||||||
parse_error_offset_source_start(&errors, arg_node.source_range()->start);
|
parse_error_offset_source_start(&errors, arg_node.source_range()->start);
|
||||||
switch (expand_ret) {
|
switch (expand_ret.result) {
|
||||||
case expand_result_t::error: {
|
case expand_result_t::error: {
|
||||||
return this->report_errors(errors);
|
return this->report_errors(expand_ret.status, errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
case expand_result_t::cancel: {
|
case expand_result_t::cancel: {
|
||||||
return end_execution_reason_t::cancelled;
|
return end_execution_reason_t::cancelled;
|
||||||
}
|
}
|
||||||
case expand_result_t::wildcard_no_match: {
|
case expand_result_t::wildcard_no_match: {
|
||||||
if (glob_behavior == failglob) {
|
if (glob_behavior == failglob) {
|
||||||
// Report the unmatched wildcard error and stop processing.
|
// Report the unmatched wildcard error and stop processing.
|
||||||
report_unmatched_wildcard_error(arg_node);
|
return report_error(STATUS_UNMATCHED_WILDCARD, arg_node, WILDCARD_ERR_MSG,
|
||||||
return end_execution_reason_t::error;
|
get_source(arg_node).c_str());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -916,7 +906,7 @@ end_execution_reason_t parse_execution_context_t::expand_arguments_from_nodes(
|
|||||||
return end_execution_reason_t::ok;
|
return end_execution_reason_t::ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool parse_execution_context_t::determine_redirections(
|
end_execution_reason_t parse_execution_context_t::determine_redirections(
|
||||||
tnode_t<g::arguments_or_redirections_list> node, redirection_spec_list_t *out_redirections) {
|
tnode_t<g::arguments_or_redirections_list> node, redirection_spec_list_t *out_redirections) {
|
||||||
// Get all redirection nodes underneath the statement.
|
// Get all redirection nodes underneath the statement.
|
||||||
while (auto redirect_node = node.next_in_list<g::redirection>()) {
|
while (auto redirect_node = node.next_in_list<g::redirection>()) {
|
||||||
@@ -925,9 +915,8 @@ bool parse_execution_context_t::determine_redirections(
|
|||||||
|
|
||||||
if (!redirect || !redirect->is_valid()) {
|
if (!redirect || !redirect->is_valid()) {
|
||||||
// TODO: figure out if this can ever happen. If so, improve this error message.
|
// TODO: figure out if this can ever happen. If so, improve this error message.
|
||||||
report_error(redirect_node, _(L"Invalid redirection: %ls"),
|
return report_error(STATUS_INVALID_ARGS, redirect_node, _(L"Invalid redirection: %ls"),
|
||||||
redirect_node.get_source(pstree->src).c_str());
|
redirect_node.get_source(pstree->src).c_str());
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PCA: I can't justify this skip_variables flag. It was like this when I got here.
|
// PCA: I can't justify this skip_variables flag. It was like this when I got here.
|
||||||
@@ -935,8 +924,8 @@ bool parse_execution_context_t::determine_redirections(
|
|||||||
expand_one(target, no_exec() ? expand_flag::skip_variables : expand_flags_t{}, ctx);
|
expand_one(target, no_exec() ? expand_flag::skip_variables : expand_flags_t{}, ctx);
|
||||||
if (!target_expanded || target.empty()) {
|
if (!target_expanded || target.empty()) {
|
||||||
// TODO: Improve this error message.
|
// TODO: Improve this error message.
|
||||||
report_error(redirect_node, _(L"Invalid redirection target: %ls"), target.c_str());
|
return report_error(STATUS_INVALID_ARGS, redirect_node,
|
||||||
return false;
|
_(L"Invalid redirection target: %ls"), target.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make a redirection spec from the redirect token.
|
// Make a redirection spec from the redirect token.
|
||||||
@@ -948,8 +937,7 @@ bool parse_execution_context_t::determine_redirections(
|
|||||||
if (spec.mode == redirection_mode_t::fd && !spec.is_close() && !spec.get_target_as_fd()) {
|
if (spec.mode == redirection_mode_t::fd && !spec.is_close() && !spec.get_target_as_fd()) {
|
||||||
const wchar_t *fmt =
|
const wchar_t *fmt =
|
||||||
_(L"Requested redirection to '%ls', which is not a valid file descriptor");
|
_(L"Requested redirection to '%ls', which is not a valid file descriptor");
|
||||||
report_error(redirect_node, fmt, spec.target.c_str());
|
return report_error(STATUS_INVALID_ARGS, redirect_node, fmt, spec.target.c_str());
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
out_redirections->push_back(std::move(spec));
|
out_redirections->push_back(std::move(spec));
|
||||||
|
|
||||||
@@ -959,7 +947,7 @@ bool parse_execution_context_t::determine_redirections(
|
|||||||
out_redirections->push_back(get_stderr_merge());
|
out_redirections->push_back(get_stderr_merge());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return end_execution_reason_t::ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
end_execution_reason_t parse_execution_context_t::populate_not_process(
|
end_execution_reason_t parse_execution_context_t::populate_not_process(
|
||||||
@@ -970,8 +958,7 @@ end_execution_reason_t parse_execution_context_t::populate_not_process(
|
|||||||
if (optional_time.tag() == parse_optional_time_time) {
|
if (optional_time.tag() == parse_optional_time_time) {
|
||||||
flags.has_time_prefix = true;
|
flags.has_time_prefix = true;
|
||||||
if (!job->mut_flags().foreground) {
|
if (!job->mut_flags().foreground) {
|
||||||
this->report_error(not_statement, ERROR_TIME_BACKGROUND);
|
return this->report_error(STATUS_INVALID_ARGS, not_statement, ERROR_TIME_BACKGROUND);
|
||||||
return end_execution_reason_t::error;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this->populate_job_process(
|
return this->populate_job_process(
|
||||||
@@ -996,15 +983,14 @@ end_execution_reason_t parse_execution_context_t::populate_block_process(
|
|||||||
// TODO: fix this ugly find_child.
|
// TODO: fix this ugly find_child.
|
||||||
auto arguments = specific_statement.template find_child<g::arguments_or_redirections_list>();
|
auto arguments = specific_statement.template find_child<g::arguments_or_redirections_list>();
|
||||||
redirection_spec_list_t redirections;
|
redirection_spec_list_t redirections;
|
||||||
if (!this->determine_redirections(arguments, &redirections)) {
|
auto reason = this->determine_redirections(arguments, &redirections);
|
||||||
return end_execution_reason_t::error;
|
if (reason == end_execution_reason_t::ok) {
|
||||||
|
proc->type = process_type_t::block_node;
|
||||||
|
proc->block_node_source = pstree;
|
||||||
|
proc->internal_block_node = statement;
|
||||||
|
proc->set_redirection_specs(std::move(redirections));
|
||||||
}
|
}
|
||||||
|
return reason;
|
||||||
proc->type = process_type_t::block_node;
|
|
||||||
proc->block_node_source = pstree;
|
|
||||||
proc->internal_block_node = statement;
|
|
||||||
proc->set_redirection_specs(std::move(redirections));
|
|
||||||
return end_execution_reason_t::ok;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
end_execution_reason_t parse_execution_context_t::apply_variable_assignments(
|
end_execution_reason_t parse_execution_context_t::apply_variable_assignments(
|
||||||
@@ -1027,18 +1013,17 @@ end_execution_reason_t parse_execution_context_t::apply_variable_assignments(
|
|||||||
expand_flag::no_descriptions, ctx, &errors);
|
expand_flag::no_descriptions, ctx, &errors);
|
||||||
parse_error_offset_source_start(
|
parse_error_offset_source_start(
|
||||||
&errors, variable_assignment.source_range()->start + *equals_pos + 1);
|
&errors, variable_assignment.source_range()->start + *equals_pos + 1);
|
||||||
switch (expand_ret) {
|
switch (expand_ret.result) {
|
||||||
case expand_result_t::error: {
|
case expand_result_t::error:
|
||||||
this->report_errors(errors);
|
return this->report_errors(expand_ret.status, errors);
|
||||||
return end_execution_reason_t::error;
|
|
||||||
}
|
case expand_result_t::cancel:
|
||||||
case expand_result_t::cancel: {
|
|
||||||
return end_execution_reason_t::cancelled;
|
return end_execution_reason_t::cancelled;
|
||||||
}
|
|
||||||
case expand_result_t::wildcard_no_match: // nullglob (equivalent to set)
|
case expand_result_t::wildcard_no_match: // nullglob (equivalent to set)
|
||||||
case expand_result_t::ok: {
|
case expand_result_t::ok:
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
default: {
|
default: {
|
||||||
DIE("unexpected expand_string() return value");
|
DIE("unexpected expand_string() return value");
|
||||||
break;
|
break;
|
||||||
@@ -1066,7 +1051,7 @@ end_execution_reason_t parse_execution_context_t::populate_job_process(
|
|||||||
cleanup_t scope([&]() {
|
cleanup_t scope([&]() {
|
||||||
if (block) parser->pop_block(block);
|
if (block) parser->pop_block(block);
|
||||||
});
|
});
|
||||||
if (result != end_execution_reason_t::ok) return end_execution_reason_t::error;
|
if (result != end_execution_reason_t::ok) return result;
|
||||||
|
|
||||||
switch (specific_statement.type) {
|
switch (specific_statement.type) {
|
||||||
case symbol_not_statement: {
|
case symbol_not_statement: {
|
||||||
@@ -1122,8 +1107,7 @@ end_execution_reason_t parse_execution_context_t::populate_job_from_job_node(
|
|||||||
if (optional_time.tag() == parse_optional_time_time) {
|
if (optional_time.tag() == parse_optional_time_time) {
|
||||||
j->mut_flags().has_time_prefix = true;
|
j->mut_flags().has_time_prefix = true;
|
||||||
if (job_node_is_background(job_node)) {
|
if (job_node_is_background(job_node)) {
|
||||||
this->report_error(job_node, ERROR_TIME_BACKGROUND);
|
return this->report_error(STATUS_INVALID_ARGS, job_node, ERROR_TIME_BACKGROUND);
|
||||||
return end_execution_reason_t::error;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
end_execution_reason_t result =
|
end_execution_reason_t result =
|
||||||
@@ -1144,7 +1128,8 @@ end_execution_reason_t parse_execution_context_t::populate_job_from_job_node(
|
|||||||
auto parsed_pipe = pipe_or_redir_t::from_string(get_source(pipe));
|
auto parsed_pipe = pipe_or_redir_t::from_string(get_source(pipe));
|
||||||
assert(parsed_pipe.has_value() && parsed_pipe->is_pipe && "Failed to parse valid pipe");
|
assert(parsed_pipe.has_value() && parsed_pipe->is_pipe && "Failed to parse valid pipe");
|
||||||
if (!parsed_pipe->is_valid()) {
|
if (!parsed_pipe->is_valid()) {
|
||||||
result = report_error(pipe, ILLEGAL_FD_ERR_MSG, get_source(pipe).c_str());
|
result = report_error(STATUS_INVALID_ARGS, pipe, ILLEGAL_FD_ERR_MSG,
|
||||||
|
get_source(pipe).c_str());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
processes.back()->pipe_write_fd = parsed_pipe->fd;
|
processes.back()->pipe_write_fd = parsed_pipe->fd;
|
||||||
@@ -1200,6 +1185,7 @@ end_execution_reason_t parse_execution_context_t::run_1_job(tnode_t<g::job> job_
|
|||||||
if (parser->is_interactive() && tcgetattr(STDIN_FILENO, &tmodes)) {
|
if (parser->is_interactive() && tcgetattr(STDIN_FILENO, &tmodes)) {
|
||||||
// Need real error handling here.
|
// Need real error handling here.
|
||||||
wperror(L"tcgetattr");
|
wperror(L"tcgetattr");
|
||||||
|
parser->set_last_statuses(statuses_t::just(STATUS_CMD_ERROR));
|
||||||
return end_execution_reason_t::error;
|
return end_execution_reason_t::error;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1452,13 +1438,13 @@ end_execution_reason_t parse_execution_context_t::eval_node(tnode_t<g::job_list>
|
|||||||
this->infinite_recursive_statement_in_job_list(job_list, &func_name);
|
this->infinite_recursive_statement_in_job_list(job_list, &func_name);
|
||||||
if (infinite_recursive_node) {
|
if (infinite_recursive_node) {
|
||||||
// We have an infinite recursion.
|
// We have an infinite recursion.
|
||||||
return this->report_error(infinite_recursive_node, INFINITE_FUNC_RECURSION_ERR_MSG,
|
return this->report_error(STATUS_CMD_ERROR, infinite_recursive_node,
|
||||||
func_name.c_str());
|
INFINITE_FUNC_RECURSION_ERR_MSG, func_name.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for stack overflow. The TOP check ensures we only do this for function calls.
|
// Check for stack overflow. The TOP check ensures we only do this for function calls.
|
||||||
if (associated_block->type() == block_type_t::top && parser->function_stack_is_overflowing()) {
|
if (associated_block->type() == block_type_t::top && parser->function_stack_is_overflowing()) {
|
||||||
return this->report_error(job_list, CALL_STACK_LIMIT_EXCEEDED_ERR_MSG);
|
return this->report_error(STATUS_CMD_ERROR, job_list, CALL_STACK_LIMIT_EXCEEDED_ERR_MSG);
|
||||||
}
|
}
|
||||||
return this->run_job_list(job_list, associated_block);
|
return this->run_job_list(job_list, associated_block);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,13 +52,11 @@ class parse_execution_context_t {
|
|||||||
// This will never return end_execution_reason_t::ok.
|
// This will never return end_execution_reason_t::ok.
|
||||||
maybe_t<end_execution_reason_t> check_end_execution() const;
|
maybe_t<end_execution_reason_t> check_end_execution() const;
|
||||||
|
|
||||||
// Report an error. Always returns 'end_execution_reason_t::error'.
|
// Report an error, setting $status to \p status. Always returns
|
||||||
end_execution_reason_t report_error(const parse_node_t &node, const wchar_t *fmt, ...) const;
|
// 'end_execution_reason_t::error'.
|
||||||
end_execution_reason_t report_errors(const parse_error_list_t &error_list) const;
|
end_execution_reason_t report_error(int status, const parse_node_t &node, const wchar_t *fmt,
|
||||||
|
...) const;
|
||||||
// Wildcard error helper.
|
end_execution_reason_t report_errors(int status, const parse_error_list_t &error_list) const;
|
||||||
end_execution_reason_t report_unmatched_wildcard_error(
|
|
||||||
const parse_node_t &unmatched_wildcard) const;
|
|
||||||
|
|
||||||
/// Command not found support.
|
/// Command not found support.
|
||||||
end_execution_reason_t handle_command_not_found(const wcstring &cmd,
|
end_execution_reason_t handle_command_not_found(const wcstring &cmd,
|
||||||
@@ -122,10 +120,10 @@ class parse_execution_context_t {
|
|||||||
wcstring_list_t *out_arguments,
|
wcstring_list_t *out_arguments,
|
||||||
globspec_t glob_behavior);
|
globspec_t glob_behavior);
|
||||||
|
|
||||||
// Determines the list of redirections for a node. Returns none() on failure, for example, an
|
// Determines the list of redirections for a node.
|
||||||
// invalid fd.
|
end_execution_reason_t determine_redirections(
|
||||||
bool determine_redirections(tnode_t<grammar::arguments_or_redirections_list> node,
|
tnode_t<grammar::arguments_or_redirections_list> node,
|
||||||
redirection_spec_list_t *out_redirections);
|
redirection_spec_list_t *out_redirections);
|
||||||
|
|
||||||
end_execution_reason_t run_1_job(tnode_t<grammar::job> job, const block_t *associated_block);
|
end_execution_reason_t run_1_job(tnode_t<grammar::job> job, const block_t *associated_block);
|
||||||
end_execution_reason_t run_job_conjunction(tnode_t<grammar::job_conjunction> job_expr,
|
end_execution_reason_t run_job_conjunction(tnode_t<grammar::job_conjunction> job_expr,
|
||||||
|
|||||||
@@ -614,8 +614,7 @@ profile_item_t *parser_t::create_profile_item() {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
end_execution_reason_t parser_t::eval(const wcstring &cmd, const io_chain_t &io,
|
eval_res_t parser_t::eval(const wcstring &cmd, const io_chain_t &io, enum block_type_t block_type) {
|
||||||
enum block_type_t block_type) {
|
|
||||||
// Parse the source into a tree, if we can.
|
// Parse the source into a tree, if we can.
|
||||||
parse_error_list_t error_list;
|
parse_error_list_t error_list;
|
||||||
if (parsed_source_ref_t ps = parse_source(cmd, parse_flag_none, &error_list)) {
|
if (parsed_source_ref_t ps = parse_source(cmd, parse_flag_none, &error_list)) {
|
||||||
@@ -627,12 +626,16 @@ end_execution_reason_t parser_t::eval(const wcstring &cmd, const io_chain_t &io,
|
|||||||
|
|
||||||
// Print it.
|
// Print it.
|
||||||
std::fwprintf(stderr, L"%ls\n", backtrace_and_desc.c_str());
|
std::fwprintf(stderr, L"%ls\n", backtrace_and_desc.c_str());
|
||||||
return end_execution_reason_t::error;
|
|
||||||
|
// Set a valid status.
|
||||||
|
this->set_last_statuses(statuses_t::just(STATUS_ILLEGAL_CMD));
|
||||||
|
bool break_expand = true;
|
||||||
|
return eval_res_t{proc_status_t::from_exit_code(STATUS_ILLEGAL_CMD), break_expand};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
end_execution_reason_t parser_t::eval(const parsed_source_ref_t &ps, const io_chain_t &io,
|
eval_res_t parser_t::eval(const parsed_source_ref_t &ps, const io_chain_t &io,
|
||||||
enum block_type_t block_type) {
|
enum block_type_t block_type) {
|
||||||
assert(block_type == block_type_t::top || block_type == block_type_t::subst);
|
assert(block_type == block_type_t::top || block_type == block_type_t::subst);
|
||||||
if (!ps->tree.empty()) {
|
if (!ps->tree.empty()) {
|
||||||
job_lineage_t lineage;
|
job_lineage_t lineage;
|
||||||
@@ -640,13 +643,17 @@ end_execution_reason_t parser_t::eval(const parsed_source_ref_t &ps, const io_ch
|
|||||||
// Execute the first node.
|
// Execute the first node.
|
||||||
tnode_t<grammar::job_list> start{&ps->tree, &ps->tree.front()};
|
tnode_t<grammar::job_list> start{&ps->tree, &ps->tree.front()};
|
||||||
return this->eval_node(ps, start, std::move(lineage), block_type);
|
return this->eval_node(ps, start, std::move(lineage), block_type);
|
||||||
|
} else {
|
||||||
|
auto status = proc_status_t::from_exit_code(get_last_status());
|
||||||
|
bool break_expand = false;
|
||||||
|
bool was_empty = true;
|
||||||
|
return eval_res_t{status, break_expand, was_empty};
|
||||||
}
|
}
|
||||||
return end_execution_reason_t::ok;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
end_execution_reason_t parser_t::eval_node(const parsed_source_ref_t &ps, tnode_t<T> node,
|
eval_res_t parser_t::eval_node(const parsed_source_ref_t &ps, tnode_t<T> node,
|
||||||
job_lineage_t lineage, block_type_t block_type) {
|
job_lineage_t lineage, block_type_t block_type) {
|
||||||
static_assert(
|
static_assert(
|
||||||
std::is_same<T, grammar::statement>::value || std::is_same<T, grammar::job_list>::value,
|
std::is_same<T, grammar::statement>::value || std::is_same<T, grammar::job_list>::value,
|
||||||
"Unexpected node type");
|
"Unexpected node type");
|
||||||
@@ -655,7 +662,7 @@ end_execution_reason_t parser_t::eval_node(const parsed_source_ref_t &ps, tnode_
|
|||||||
// not empty, we are still in the process of cancelling; refuse to evaluate anything.
|
// not empty, we are still in the process of cancelling; refuse to evaluate anything.
|
||||||
if (this->cancellation_signal) {
|
if (this->cancellation_signal) {
|
||||||
if (!block_list.empty()) {
|
if (!block_list.empty()) {
|
||||||
return end_execution_reason_t::cancelled;
|
return proc_status_t::from_signal(this->cancellation_signal);
|
||||||
}
|
}
|
||||||
this->cancellation_signal = 0;
|
this->cancellation_signal = 0;
|
||||||
}
|
}
|
||||||
@@ -674,25 +681,33 @@ end_execution_reason_t parser_t::eval_node(const parsed_source_ref_t &ps, tnode_
|
|||||||
using exc_ctx_ref_t = std::unique_ptr<parse_execution_context_t>;
|
using exc_ctx_ref_t = std::unique_ptr<parse_execution_context_t>;
|
||||||
scoped_push<exc_ctx_ref_t> exc(&execution_context, make_unique<parse_execution_context_t>(
|
scoped_push<exc_ctx_ref_t> exc(&execution_context, make_unique<parse_execution_context_t>(
|
||||||
ps, this, op_ctx, std::move(lineage)));
|
ps, this, op_ctx, std::move(lineage)));
|
||||||
end_execution_reason_t res = execution_context->eval_node(node, scope_block);
|
|
||||||
|
// Check the exec count so we know if anything got executed.
|
||||||
|
const size_t prev_exec_count = libdata().exec_count;
|
||||||
|
end_execution_reason_t reason = execution_context->eval_node(node, scope_block);
|
||||||
|
const size_t new_exec_count = libdata().exec_count;
|
||||||
|
|
||||||
exc.restore();
|
exc.restore();
|
||||||
this->pop_block(scope_block);
|
this->pop_block(scope_block);
|
||||||
|
|
||||||
job_reap(*this, false); // reap again
|
job_reap(*this, false); // reap again
|
||||||
|
|
||||||
// control_flow is used internally to react to break and return.
|
if (this->cancellation_signal) {
|
||||||
// Here we treat that as success.
|
// We were signalled.
|
||||||
if (res == end_execution_reason_t::control_flow) {
|
return proc_status_t::from_signal(this->cancellation_signal);
|
||||||
res = end_execution_reason_t::ok;
|
} else {
|
||||||
|
auto status = proc_status_t::from_exit_code(this->get_last_status());
|
||||||
|
bool break_expand = (reason == end_execution_reason_t::error);
|
||||||
|
bool was_empty = !break_expand && prev_exec_count == new_exec_count;
|
||||||
|
return eval_res_t{status, break_expand, was_empty};
|
||||||
}
|
}
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Explicit instantiations. TODO: use overloads instead?
|
// Explicit instantiations. TODO: use overloads instead?
|
||||||
template end_execution_reason_t parser_t::eval_node(const parsed_source_ref_t &, tnode_t<grammar::statement>,
|
template eval_res_t parser_t::eval_node(const parsed_source_ref_t &, tnode_t<grammar::statement>,
|
||||||
job_lineage_t, block_type_t);
|
job_lineage_t, block_type_t);
|
||||||
template end_execution_reason_t parser_t::eval_node(const parsed_source_ref_t &, tnode_t<grammar::job_list>,
|
template eval_res_t parser_t::eval_node(const parsed_source_ref_t &, tnode_t<grammar::job_list>,
|
||||||
job_lineage_t, block_type_t);
|
job_lineage_t, block_type_t);
|
||||||
|
|
||||||
void parser_t::get_backtrace(const wcstring &src, const parse_error_list_t &errors,
|
void parser_t::get_backtrace(const wcstring &src, const parse_error_list_t &errors,
|
||||||
wcstring &output) const {
|
wcstring &output) const {
|
||||||
|
|||||||
35
src/parser.h
35
src/parser.h
@@ -200,6 +200,24 @@ struct library_data_t {
|
|||||||
|
|
||||||
class operation_context_t;
|
class operation_context_t;
|
||||||
|
|
||||||
|
/// The result of parser_t::eval family.
|
||||||
|
struct eval_res_t {
|
||||||
|
/// The value for $status.
|
||||||
|
proc_status_t status;
|
||||||
|
|
||||||
|
/// If set, there was an error that should be considered a failed expansion, such as
|
||||||
|
/// command-not-found. For example, `touch (not-a-command)` will not invoke 'touch' because
|
||||||
|
/// command-not-found will mark break_expand.
|
||||||
|
bool break_expand;
|
||||||
|
|
||||||
|
/// If set, no commands were executed and there we no errors.
|
||||||
|
bool was_empty{false};
|
||||||
|
|
||||||
|
/* implicit */ eval_res_t(proc_status_t status, bool break_expand = false,
|
||||||
|
bool was_empty = false)
|
||||||
|
: status(status), break_expand(break_expand), was_empty(was_empty) {}
|
||||||
|
};
|
||||||
|
|
||||||
class parser_t : public std::enable_shared_from_this<parser_t> {
|
class parser_t : public std::enable_shared_from_this<parser_t> {
|
||||||
friend class parse_execution_context_t;
|
friend class parse_execution_context_t;
|
||||||
|
|
||||||
@@ -266,22 +284,23 @@ class parser_t : public std::enable_shared_from_this<parser_t> {
|
|||||||
/// \param io io redirections to perform on all started jobs
|
/// \param io io redirections to perform on all started jobs
|
||||||
/// \param block_type The type of block to push on the block stack, which must be either 'top'
|
/// \param block_type The type of block to push on the block stack, which must be either 'top'
|
||||||
/// or 'subst'.
|
/// or 'subst'.
|
||||||
|
/// \param break_expand If not null, return by reference whether the error ought to be an expand
|
||||||
|
/// error. This includes nested expand errors, and command-not-found.
|
||||||
///
|
///
|
||||||
/// \return the eval result,
|
/// \return the result of evaluation.
|
||||||
end_execution_reason_t eval(const wcstring &cmd, const io_chain_t &io,
|
eval_res_t eval(const wcstring &cmd, const io_chain_t &io,
|
||||||
block_type_t block_type = block_type_t::top);
|
block_type_t block_type = block_type_t::top);
|
||||||
|
|
||||||
/// Evaluate the parsed source ps.
|
/// Evaluate the parsed source ps.
|
||||||
/// Because the source has been parsed, a syntax error is impossible.
|
/// Because the source has been parsed, a syntax error is impossible.
|
||||||
end_execution_reason_t eval(const parsed_source_ref_t &ps, const io_chain_t &io,
|
eval_res_t eval(const parsed_source_ref_t &ps, const io_chain_t &io,
|
||||||
block_type_t block_type = block_type_t::top);
|
block_type_t block_type = block_type_t::top);
|
||||||
|
|
||||||
/// Evaluates a node.
|
/// Evaluates a node.
|
||||||
/// The node type must be grammar::statement or grammar::job_list.
|
/// The node type must be grammar::statement or grammar::job_list.
|
||||||
template <typename T>
|
template <typename T>
|
||||||
end_execution_reason_t eval_node(const parsed_source_ref_t &ps, tnode_t<T> node,
|
eval_res_t eval_node(const parsed_source_ref_t &ps, tnode_t<T> node, job_lineage_t lineage,
|
||||||
job_lineage_t lineage,
|
block_type_t block_type = block_type_t::top);
|
||||||
block_type_t block_type = block_type_t::top);
|
|
||||||
|
|
||||||
/// Evaluate line as a list of parameters, i.e. tokenize it and perform parameter expansion and
|
/// Evaluate line as a list of parameters, i.e. tokenize it and perform parameter expansion and
|
||||||
/// cmdsubst execution on the tokens. Errors are ignored. If a parser is provided, it is used
|
/// cmdsubst execution on the tokens. Errors are ignored. If a parser is provided, it is used
|
||||||
|
|||||||
@@ -874,8 +874,8 @@ void reader_write_title(const wcstring &cmd, parser_t &parser, bool reset_cursor
|
|||||||
}
|
}
|
||||||
|
|
||||||
wcstring_list_t lst;
|
wcstring_list_t lst;
|
||||||
if (exec_subshell(fish_title_command, parser, lst, false /* ignore exit status */) != -1 &&
|
(void)exec_subshell(fish_title_command, parser, lst, false /* ignore exit status */);
|
||||||
!lst.empty()) {
|
if (!lst.empty()) {
|
||||||
std::fputws(L"\x1B]0;", stdout);
|
std::fputws(L"\x1B]0;", stdout);
|
||||||
for (const auto &i : lst) {
|
for (const auto &i : lst) {
|
||||||
std::fputws(i.c_str(), stdout);
|
std::fputws(i.c_str(), stdout);
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ set --show b
|
|||||||
#CHECK: $b: not set in global scope
|
#CHECK: $b: not set in global scope
|
||||||
#CHECK: $b: not set in universal scope
|
#CHECK: $b: not set in universal scope
|
||||||
#CHECKERR: {{.*}}: Too much data emitted by command substitution so it was discarded
|
#CHECKERR: {{.*}}: Too much data emitted by command substitution so it was discarded
|
||||||
#CHECKERR:
|
|
||||||
#CHECKERR: set b (string repeat -n 512 x)
|
#CHECKERR: set b (string repeat -n 512 x)
|
||||||
#CHECKERR: ^
|
#CHECKERR: ^
|
||||||
|
|
||||||
@@ -43,7 +42,6 @@ set --show c
|
|||||||
#CHECK: $c[1]: length=0 value=||
|
#CHECK: $c[1]: length=0 value=||
|
||||||
#CHECK: $c: not set in universal scope
|
#CHECK: $c: not set in universal scope
|
||||||
#CHECKERR: {{.*}}: Too much data emitted by command substitution so it was discarded
|
#CHECKERR: {{.*}}: Too much data emitted by command substitution so it was discarded
|
||||||
#CHECKERR:
|
|
||||||
#CHECKERR: set -l x (string repeat -n $argv x)
|
#CHECKERR: set -l x (string repeat -n $argv x)
|
||||||
#CHECKERR: ^
|
#CHECKERR: ^
|
||||||
#CHECKERR: in function 'subme' with arguments '513'
|
#CHECKERR: in function 'subme' with arguments '513'
|
||||||
@@ -62,6 +60,5 @@ test $saved_status -eq 122
|
|||||||
or echo expected status 122, saw $saved_status >&2
|
or echo expected status 122, saw $saved_status >&2
|
||||||
|
|
||||||
#CHECKERR: {{.*}}: Too much data emitted by command substitution so it was discarded
|
#CHECKERR: {{.*}}: Too much data emitted by command substitution so it was discarded
|
||||||
#CHECKERR:
|
|
||||||
#CHECKERR: echo this will fail (string repeat --max 513 b) to output anything
|
#CHECKERR: echo this will fail (string repeat --max 513 b) to output anything
|
||||||
#CHECKERR: ^
|
#CHECKERR: ^
|
||||||
|
|||||||
@@ -21,14 +21,14 @@ echo $status
|
|||||||
false
|
false
|
||||||
eval "("
|
eval "("
|
||||||
echo $status
|
echo $status
|
||||||
# CHECK: 1
|
# CHECK: 123
|
||||||
# CHECKERR: {{.*}}checks/eval.fish (line {{\d+}}): Unexpected end of string, expecting ')'
|
# CHECKERR: {{.*}}checks/eval.fish (line {{\d+}}): Unexpected end of string, expecting ')'
|
||||||
# CHECKERR: (
|
# CHECKERR: (
|
||||||
# CHECKERR: ^
|
# CHECKERR: ^
|
||||||
false
|
false
|
||||||
eval '""'
|
eval '""'
|
||||||
echo $status
|
echo $status
|
||||||
# CHECK: 1
|
# CHECK: 123
|
||||||
# CHECKERR: {{.*}}checks/eval.fish (line {{\d+}}): The expanded command was empty.
|
# CHECKERR: {{.*}}checks/eval.fish (line {{\d+}}): The expanded command was empty.
|
||||||
# CHECKERR: ""
|
# CHECKERR: ""
|
||||||
# CHECKERR: ^
|
# CHECKERR: ^
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
$fish -c "echo 1.2.3.4."
|
$fish -c "echo 1.2.3.4."
|
||||||
# CHECK: 1.2.3.4.
|
# CHECK: 1.2.3.4.
|
||||||
|
|
||||||
|
PATH= $fish -c "command a" 2>/dev/null
|
||||||
|
echo $status
|
||||||
|
# CHECK: 127
|
||||||
|
|
||||||
PATH= $fish -c "echo (command a)" 2>/dev/null
|
PATH= $fish -c "echo (command a)" 2>/dev/null
|
||||||
echo $status
|
echo $status
|
||||||
# CHECK: 255
|
# CHECK: 127
|
||||||
|
|||||||
26
tests/checks/status-value.fish
Normal file
26
tests/checks/status-value.fish
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# RUN: %fish %s
|
||||||
|
|
||||||
|
# Empty commands should be 123
|
||||||
|
set empty_var
|
||||||
|
$empty_var
|
||||||
|
echo $status
|
||||||
|
# CHECK: 123
|
||||||
|
# CHECKERR: {{.*}} The expanded command was empty.
|
||||||
|
# CHECKERR: $empty_var
|
||||||
|
# CHECKERR: ^
|
||||||
|
|
||||||
|
# Failed expansions
|
||||||
|
echo "$abc["
|
||||||
|
echo $status
|
||||||
|
# CHECK: 121
|
||||||
|
# CHECKERR: {{.*}} Invalid index value
|
||||||
|
# CHECKERR: echo "$abc["
|
||||||
|
# CHECKERR: ^
|
||||||
|
|
||||||
|
# Failed wildcards
|
||||||
|
echo *gibberishgibberishgibberish*
|
||||||
|
echo $status
|
||||||
|
# CHECK: 124
|
||||||
|
# CHECKERR: {{.*}} No matches for wildcard '*gibberishgibberishgibberish*'. {{.*}}
|
||||||
|
# CHECKERR: echo *gibberishgibberishgibberish*
|
||||||
|
# CHECKERR: ^
|
||||||
Reference in New Issue
Block a user