diff --git a/src/fish_tests.cpp b/src/fish_tests.cpp index ebf6591e3..61f84b61e 100644 --- a/src/fish_tests.cpp +++ b/src/fish_tests.cpp @@ -1032,12 +1032,16 @@ static void test_1_cancellation(const wchar_t *src) { usleep(delay * 1E6); pthread_kill(thread, SIGINT); }); - parser_t::principal_parser().eval(src, io_chain_t{filler}, TOP); + eval_result_t ret = parser_t::principal_parser().eval(src, io_chain_t{filler}, TOP); + fprintf(stderr, "eval: %d\n", (int)ret); auto buffer = io_bufferfill_t::finish(std::move(filler)); if (buffer->buffer().size() != 0) { err(L"Expected 0 bytes in out_buff, but instead found %lu bytes, for command %ls\n", buffer->buffer().size(), src); } + // TODO: cancelling out of command substitutions is currently reported as an error, not a + // cancellation. + // do_test(ret == eval_result_t::cancelled); iothread_drain_all(); } diff --git a/src/parse_execution.cpp b/src/parse_execution.cpp index de5007b17..09be1e00a 100644 --- a/src/parse_execution.cpp +++ b/src/parse_execution.cpp @@ -183,27 +183,21 @@ process_type_t parse_execution_context_t::process_type_for_command( return process_type; } -bool parse_execution_context_t::should_cancel_execution(const block_t *block) const { - return cancellation_reason(block) != execution_cancellation_none; -} - -parse_execution_context_t::execution_cancellation_reason_t -parse_execution_context_t::cancellation_reason(const block_t *block) const { - UNUSED(block); +maybe_t parse_execution_context_t::check_end_execution() const { if (shell_is_exiting()) { - return execution_cancellation_exit; + return eval_result_t::cancelled; } if (parser && parser->cancellation_requested) { - return execution_cancellation_skip; + return eval_result_t::cancelled; } const auto &ld = parser->libdata(); if (ld.returning) { - return execution_cancellation_skip; + return eval_result_t::control_flow; } if (ld.loop_status != loop_status_t::normals) { - return execution_cancellation_loop_control; + return eval_result_t::control_flow; } - return execution_cancellation_none; + return none(); } /// Return whether the job contains a single statement, of block type, with no redirections. @@ -240,9 +234,9 @@ bool parse_execution_context_t::job_is_simple_block(tnode_t job_node) co } } -parse_execution_result_t parse_execution_context_t::run_if_statement( - tnode_t statement, const block_t *associated_block) { - parse_execution_result_t result = parse_execution_success; +eval_result_t parse_execution_context_t::run_if_statement(tnode_t statement, + const block_t *associated_block) { + eval_result_t result = eval_result_t::ok; // We have a sequence of if clauses, with a final else, resulting in a single job list that we // execute. @@ -254,8 +248,8 @@ parse_execution_result_t parse_execution_context_t::run_if_statement( trace_if_enabled(*parser, L"if"); for (;;) { - if (should_cancel_execution(associated_block)) { - result = parse_execution_cancelled; + if (auto ret = check_end_execution()) { + result = *ret; break; } @@ -263,14 +257,14 @@ parse_execution_result_t parse_execution_context_t::run_if_statement( tnode_t condition_head = if_clause.child<1>(); tnode_t condition_boolean_tail = if_clause.child<3>(); - // Check the condition and the tail. We treat parse_execution_errored here as failure, in + // Check the condition and the tail. We treat eval_result_t::error here as failure, in // accordance with historic behavior. - parse_execution_result_t cond_ret = run_job_conjunction(condition_head, associated_block); - if (cond_ret == parse_execution_success) { + eval_result_t cond_ret = run_job_conjunction(condition_head, associated_block); + if (cond_ret == eval_result_t::ok) { cond_ret = run_job_list(condition_boolean_tail, associated_block); } const bool take_branch = - (cond_ret == parse_execution_success) && parser->get_last_status() == EXIT_SUCCESS; + (cond_ret == eval_result_t::ok) && parser->get_last_status() == EXIT_SUCCESS; if (take_branch) { // Condition succeeded. @@ -304,8 +298,8 @@ parse_execution_result_t parse_execution_context_t::run_if_statement( if (job_list_to_execute) { block_t *ib = parser->push_block(block_t::if_block()); run_job_list(job_list_to_execute, ib); - if (should_cancel_execution(ib)) { - result = parse_execution_cancelled; + if (auto ret = check_end_execution()) { + result = *ret; } parser->pop_block(ib); } else { @@ -316,35 +310,33 @@ parse_execution_result_t parse_execution_context_t::run_if_statement( trace_if_enabled(*parser, L"end if"); // It's possible there's a last-minute cancellation (issue #1297). - if (should_cancel_execution(associated_block)) { - result = parse_execution_cancelled; + if (auto ret = check_end_execution()) { + result = *ret; } // Otherwise, take the exit status of the job list. Reversal of issue #1061. return result; } -parse_execution_result_t parse_execution_context_t::run_begin_statement( - tnode_t contents) { +eval_result_t parse_execution_context_t::run_begin_statement(tnode_t contents) { // Basic begin/end block. Push a scope block, run jobs, pop it trace_if_enabled(*parser, L"begin"); block_t *sb = parser->push_block(block_t::scope_block(BEGIN)); - parse_execution_result_t ret = run_job_list(contents, sb); + eval_result_t ret = run_job_list(contents, sb); parser->pop_block(sb); trace_if_enabled(*parser, L"end begin"); return ret; } // Define a function. -parse_execution_result_t parse_execution_context_t::run_function_statement( - tnode_t header, tnode_t body) { +eval_result_t parse_execution_context_t::run_function_statement(tnode_t header, + tnode_t body) { // Get arguments. wcstring_list_t arguments; argument_node_list_t arg_nodes = header.descendants(); - parse_execution_result_t result = - this->expand_arguments_from_nodes(arg_nodes, &arguments, failglob); + eval_result_t result = this->expand_arguments_from_nodes(arg_nodes, &arguments, failglob); - if (result != parse_execution_success) { + if (result != eval_result_t::ok) { return result; } trace_if_enabled(*parser, L"function", arguments); @@ -355,18 +347,18 @@ parse_execution_result_t parse_execution_context_t::run_function_statement( wcstring errtext = streams.err.contents(); if (!errtext.empty()) { this->report_error(header, L"%ls", errtext.c_str()); - result = parse_execution_errored; + result = eval_result_t::error; } return result; } -parse_execution_result_t parse_execution_context_t::run_block_statement( - tnode_t statement, const block_t *associated_block) { +eval_result_t parse_execution_context_t::run_block_statement(tnode_t statement, + const block_t *associated_block) { tnode_t bheader = statement.child<0>(); tnode_t contents = statement.child<1>(); - parse_execution_result_t ret = parse_execution_success; + eval_result_t ret = eval_result_t::ok; if (auto header = bheader.try_get_child()) { ret = run_for_statement(header, contents); } else if (auto header = bheader.try_get_child()) { @@ -391,7 +383,7 @@ bool parse_execution_context_t::is_function_context() const { return is_within_function_call; } -parse_execution_result_t parse_execution_context_t::run_for_statement( +eval_result_t parse_execution_context_t::run_for_statement( tnode_t header, tnode_t block_contents) { // Get the variable name: `for var_name in ...`. We expand the variable name. It better result // in just one. @@ -399,14 +391,14 @@ parse_execution_result_t parse_execution_context_t::run_for_statement( wcstring for_var_name = get_source(var_name_node); if (!expand_one(for_var_name, expand_flags_t{}, parser->vars(), parser->shared())) { report_error(var_name_node, FAILED_EXPANSION_VARIABLE_NAME_ERR_MSG, for_var_name.c_str()); - return parse_execution_errored; + return eval_result_t::error; } // Get the contents to iterate over. wcstring_list_t arguments; - parse_execution_result_t ret = this->expand_arguments_from_nodes( - get_argument_nodes(header.child<3>()), &arguments, nullglob); - if (ret != parse_execution_success) { + eval_result_t ret = this->expand_arguments_from_nodes(get_argument_nodes(header.child<3>()), + &arguments, nullglob); + if (ret != eval_result_t::ok) { return ret; } @@ -418,13 +410,13 @@ parse_execution_result_t parse_execution_context_t::run_for_statement( if (retval != ENV_OK) { report_error(var_name_node, L"You cannot use read-only variable '%ls' in a for loop", for_var_name.c_str()); - return parse_execution_errored; + return eval_result_t::error; } } if (!valid_var_name(for_var_name)) { report_error(var_name_node, BUILTIN_ERR_VARNAME, L"for", for_var_name.c_str()); - return parse_execution_errored; + return eval_result_t::error; } trace_if_enabled(*parser, L"for", arguments); @@ -432,8 +424,8 @@ parse_execution_result_t parse_execution_context_t::run_for_statement( // Now drive the for loop. for (const wcstring &val : arguments) { - if (should_cancel_execution(fb)) { - ret = parse_execution_cancelled; + if (auto reason = check_end_execution()) { + ret = *reason; break; } @@ -445,7 +437,7 @@ parse_execution_result_t parse_execution_context_t::run_for_statement( ld.loop_status = loop_status_t::normals; this->run_job_list(block_contents, fb); - if (this->cancellation_reason(fb) == execution_cancellation_loop_control) { + if (check_end_execution() == eval_result_t::control_flow) { // Handle break or continue. bool do_break = (ld.loop_status == loop_status_t::breaks); ld.loop_status = loop_status_t::normals; @@ -460,9 +452,9 @@ parse_execution_result_t parse_execution_context_t::run_for_statement( return ret; } -parse_execution_result_t parse_execution_context_t::run_switch_statement( +eval_result_t parse_execution_context_t::run_switch_statement( tnode_t statement) { - parse_execution_result_t result = parse_execution_success; + eval_result_t result = eval_result_t::ok; // Get the switch variable. tnode_t switch_value_n = statement.child<1>(); @@ -495,13 +487,13 @@ parse_execution_result_t parse_execution_context_t::run_switch_statement( } } - if (result == parse_execution_success && switch_values_expanded.size() > 1) { + if (result == eval_result_t::ok && switch_values_expanded.size() > 1) { result = report_error(switch_value_n, _(L"switch: Expected at most one argument, got %lu\n"), switch_values_expanded.size()); } - if (result != parse_execution_success) { + if (result != eval_result_t::ok) { return result; } @@ -514,8 +506,8 @@ parse_execution_result_t parse_execution_context_t::run_switch_statement( tnode_t case_item_list = statement.child<3>(); tnode_t matching_case_item{}; while (auto case_item = case_item_list.next_in_list()) { - if (should_cancel_execution(sb)) { - result = parse_execution_cancelled; + if (auto ret = check_end_execution()) { + result = *ret; break; } @@ -524,9 +516,9 @@ parse_execution_result_t parse_execution_context_t::run_switch_statement( // contains an unexpandable process will report and then fail to match. auto arg_nodes = get_argument_nodes(case_item.child<1>()); wcstring_list_t case_args; - parse_execution_result_t case_result = + eval_result_t case_result = this->expand_arguments_from_nodes(arg_nodes, &case_args, failglob); - if (case_result == parse_execution_success) { + if (case_result == eval_result_t::ok) { for (const wcstring &arg : case_args) { // Unescape wildcards so they can be expanded again. wcstring unescaped_arg = parse_util_unescape_wildcards(arg); @@ -544,7 +536,7 @@ parse_execution_result_t parse_execution_context_t::run_switch_statement( if (matching_case_item) { // Success, evaluate the job list. - assert(result == parse_execution_success && "Expected success"); + assert(result == eval_result_t::ok && "Expected success"); auto job_list = matching_case_item.child<3>(); result = this->run_job_list(job_list, sb); } @@ -553,10 +545,10 @@ parse_execution_result_t parse_execution_context_t::run_switch_statement( return result; } -parse_execution_result_t parse_execution_context_t::run_while_statement( - tnode_t header, tnode_t contents, - const block_t *associated_block) { - parse_execution_result_t ret = parse_execution_success; +eval_result_t parse_execution_context_t::run_while_statement(tnode_t header, + tnode_t contents, + const block_t *associated_block) { + eval_result_t ret = eval_result_t::ok; // "The exit status of the while loop shall be the exit status of the last compound-list-2 // executed, or zero if none was executed." @@ -584,16 +576,15 @@ parse_execution_result_t parse_execution_context_t::run_while_statement( first_cond_check = false; // Check the condition. - parse_execution_result_t cond_ret = - this->run_job_conjunction(condition_head, associated_block); - if (cond_ret == parse_execution_success) { + eval_result_t cond_ret = this->run_job_conjunction(condition_head, associated_block); + if (cond_ret == eval_result_t::ok) { cond_ret = run_job_list(condition_boolean_tail, associated_block); } // If the loop condition failed to execute, then exit the loop without modifying the exit // status. If the loop condition executed with a failure status, restore the status and then // exit the loop. - if (cond_ret != parse_execution_success) { + if (cond_ret != eval_result_t::ok) { break; } else if (parser->get_last_status() != EXIT_SUCCESS) { parser->set_last_statuses(cond_saved_status); @@ -601,8 +592,8 @@ parse_execution_result_t parse_execution_context_t::run_while_statement( } // Check cancellation. - if (this->should_cancel_execution(associated_block)) { - ret = parse_execution_cancelled; + if (auto reason = check_end_execution()) { + ret = *reason; break; } @@ -612,10 +603,10 @@ parse_execution_result_t parse_execution_context_t::run_while_statement( block_t *wb = parser->push_block(block_t::while_block()); this->run_job_list(contents, wb); - auto cancel_reason = this->cancellation_reason(wb); + auto cancel_reason = this->check_end_execution(); parser->pop_block(wb); - if (cancel_reason == execution_cancellation_loop_control) { + if (cancel_reason == eval_result_t::control_flow) { // Handle break or continue. bool do_break = (ld.loop_status == loop_status_t::breaks); ld.loop_status = loop_status_t::normals; @@ -636,10 +627,10 @@ parse_execution_result_t parse_execution_context_t::run_while_statement( return ret; } -// Reports an error. Always returns parse_execution_errored, so you can assign the result to an +// Reports an error. Always returns eval_result_t::error, so you can assign the result to an // 'errored' variable. -parse_execution_result_t parse_execution_context_t::report_error(const parse_node_t &node, - const wchar_t *fmt, ...) const { +eval_result_t parse_execution_context_t::report_error(const parse_node_t &node, const wchar_t *fmt, + ...) const { // Create an error. parse_error_list_t error_list = parse_error_list_t(1); parse_error_t *error = &error_list.at(0); @@ -653,11 +644,10 @@ parse_execution_result_t parse_execution_context_t::report_error(const parse_nod va_end(va); this->report_errors(error_list); - return parse_execution_errored; + return eval_result_t::error; } -parse_execution_result_t parse_execution_context_t::report_errors( - const parse_error_list_t &error_list) const { +eval_result_t parse_execution_context_t::report_errors(const parse_error_list_t &error_list) const { if (!parser->cancellation_requested) { if (error_list.empty()) { FLOG(error, L"Error reported but no error text found."); @@ -672,19 +662,19 @@ parse_execution_result_t parse_execution_context_t::report_errors( std::fwprintf(stderr, L"%ls", backtrace_and_desc.c_str()); } } - return parse_execution_errored; + return eval_result_t::error; } -/// Reports an unmatched wildcard error and returns parse_execution_errored. -parse_execution_result_t parse_execution_context_t::report_unmatched_wildcard_error( +/// Reports an unmatched wildcard error and returns eval_result_t::error. +eval_result_t parse_execution_context_t::report_unmatched_wildcard_error( 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 parse_execution_errored; + return eval_result_t::error; } /// Handle the case of command not found. -parse_execution_result_t parse_execution_context_t::handle_command_not_found( +eval_result_t parse_execution_context_t::handle_command_not_found( const wcstring &cmd_str, tnode_t statement, int err_code) { // We couldn't find the specified command. This is a non-fatal error. We want to set the exit // status to 127, which is the standard number used by other shells like bash and zsh. @@ -698,10 +688,10 @@ parse_execution_result_t parse_execution_context_t::handle_command_not_found( wcstring_list_t event_args; { auto args = get_argument_nodes(statement.child<1>()); - parse_execution_result_t arg_result = + eval_result_t arg_result = this->expand_arguments_from_nodes(args, &event_args, failglob); - if (arg_result != parse_execution_success) { + if (arg_result != eval_result_t::ok) { return arg_result; } @@ -718,12 +708,12 @@ parse_execution_result_t parse_execution_context_t::handle_command_not_found( int status = err_code == ENOENT ? STATUS_CMD_UNKNOWN : STATUS_NOT_EXECUTABLE; parser->set_last_statuses(statuses_t::just(status)); - return parse_execution_errored; + return eval_result_t::error; } -parse_execution_result_t parse_execution_context_t::expand_command( - tnode_t statement, wcstring *out_cmd, - wcstring_list_t *out_args) const { +eval_result_t parse_execution_context_t::expand_command(tnode_t statement, + wcstring *out_cmd, + wcstring_list_t *out_args) const { // Here we're expanding a command, for example $HOME/bin/stuff or $randomthing. The first // completion becomes the command itself, everything after becomes arguments. Command // substitutions are not supported. @@ -753,11 +743,11 @@ parse_execution_result_t parse_execution_context_t::expand_command( if (out_cmd->empty()) { return this->report_error(statement, _(L"The expanded command was empty.")); } - return parse_execution_success; + return eval_result_t::ok; } /// Creates a 'normal' (non-block) process. -parse_execution_result_t parse_execution_context_t::populate_plain_process( +eval_result_t parse_execution_context_t::populate_plain_process( job_t *job, process_t *proc, tnode_t statement) { assert(job != nullptr); assert(proc != nullptr); @@ -769,7 +759,7 @@ parse_execution_result_t parse_execution_context_t::populate_plain_process( wcstring cmd; wcstring_list_t args_from_cmd_expansion; auto ret = expand_command(statement, &cmd, &args_from_cmd_expansion); - if (ret != parse_execution_success) { + if (ret != eval_result_t::ok) { return ret; } assert(!cmd.empty() && "expand_command should not produce an empty command"); @@ -796,7 +786,7 @@ parse_execution_result_t parse_execution_context_t::populate_plain_process( if (isatty(STDIN_FILENO) && current_run_count - 1 != last_exec_run_count) { reader_bg_job_warning(*parser); last_exec_run_count = current_run_count; - return parse_execution_errored; + return eval_result_t::error; } else { hup_background_jobs(*parser); } @@ -852,15 +842,15 @@ parse_execution_result_t parse_execution_context_t::populate_plain_process( cmd_args.insert(cmd_args.end(), args_from_cmd_expansion.begin(), args_from_cmd_expansion.end()); argument_node_list_t arg_nodes = statement.descendants(); - parse_execution_result_t arg_result = + eval_result_t arg_result = this->expand_arguments_from_nodes(arg_nodes, &cmd_args, glob_behavior); - if (arg_result != parse_execution_success) { + if (arg_result != eval_result_t::ok) { return arg_result; } // The set of IO redirections that we construct for the process. if (!this->determine_redirections(statement.child<1>(), &redirections)) { - return parse_execution_errored; + return eval_result_t::error; } // Determine the process type. @@ -872,12 +862,12 @@ parse_execution_result_t parse_execution_context_t::populate_plain_process( proc->set_argv(cmd_args); proc->set_redirection_specs(std::move(redirections)); proc->actual_cmd = std::move(path_to_external_command); - return parse_execution_success; + return eval_result_t::ok; } // Determine the list of arguments, expanding stuff. Reports any errors caused by expansion. If we // have a wildcard that could not be expanded, report the error and continue. -parse_execution_result_t parse_execution_context_t::expand_arguments_from_nodes( +eval_result_t parse_execution_context_t::expand_arguments_from_nodes( const argument_node_list_t &argument_nodes, wcstring_list_t *out_arguments, globspec_t glob_behavior) { // Get all argument nodes underneath the statement. We guess we'll have that many arguments (but @@ -897,14 +887,13 @@ parse_execution_result_t parse_execution_context_t::expand_arguments_from_nodes( parse_error_offset_source_start(&errors, arg_node.source_range()->start); switch (expand_ret) { case expand_result_t::error: { - this->report_errors(errors); - return parse_execution_errored; + return this->report_errors(errors); } case expand_result_t::wildcard_no_match: { if (glob_behavior == failglob) { // Report the unmatched wildcard error and stop processing. report_unmatched_wildcard_error(arg_node); - return parse_execution_errored; + return eval_result_t::error; } break; } @@ -927,11 +916,11 @@ parse_execution_result_t parse_execution_context_t::expand_arguments_from_nodes( } // We may have received a cancellation during this expansion. - if (parser->cancellation_requested) { - return parse_execution_cancelled; + if (auto ret = check_end_execution()) { + return *ret; } - return parse_execution_success; + return eval_result_t::ok; } bool parse_execution_context_t::determine_redirections( @@ -981,7 +970,7 @@ bool parse_execution_context_t::determine_redirections( return true; } -parse_execution_result_t parse_execution_context_t::populate_not_process( +eval_result_t parse_execution_context_t::populate_not_process( job_t *job, process_t *proc, tnode_t not_statement) { auto &flags = job->mut_flags(); flags.negate = !flags.negate; @@ -991,9 +980,9 @@ parse_execution_result_t parse_execution_context_t::populate_not_process( } template -parse_execution_result_t parse_execution_context_t::populate_block_process( - job_t *job, process_t *proc, tnode_t statement, - tnode_t specific_statement) { +eval_result_t parse_execution_context_t::populate_block_process(job_t *job, process_t *proc, + tnode_t statement, + tnode_t specific_statement) { // We handle block statements by creating process_type_t::block_node, that will bounce back to // us when it's time to execute them. UNUSED(job); @@ -1008,22 +997,22 @@ parse_execution_result_t parse_execution_context_t::populate_block_process( auto arguments = specific_statement.template find_child(); redirection_spec_list_t redirections; if (!this->determine_redirections(arguments, &redirections)) { - return parse_execution_errored; + return eval_result_t::error; } 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 parse_execution_success; + return eval_result_t::ok; } -parse_execution_result_t parse_execution_context_t::apply_variable_assignments( +eval_result_t parse_execution_context_t::apply_variable_assignments( process_t *proc, tnode_t variable_assignments, const block_t **block) { variable_assignment_node_list_t assignment_list = get_variable_assignment_nodes(variable_assignments); - if (assignment_list.empty()) return parse_execution_success; + if (assignment_list.empty()) return eval_result_t::ok; *block = parser->push_block(block_t::variable_assignment_block()); for (const auto &variable_assignment : assignment_list) { const wcstring &source = variable_assignment.get_source(pstree->src); @@ -1042,7 +1031,7 @@ parse_execution_result_t parse_execution_context_t::apply_variable_assignments( switch (expand_ret) { case expand_result_t::error: { this->report_errors(errors); - return parse_execution_errored; + return eval_result_t::error; } case expand_result_t::wildcard_no_match: // nullglob (equivalent to set) case expand_result_t::wildcard_match: @@ -1061,22 +1050,21 @@ parse_execution_result_t parse_execution_context_t::apply_variable_assignments( if (proc) proc->variable_assignments.push_back({variable_name, vals}); parser->vars().set(std::move(variable_name), ENV_LOCAL | ENV_EXPORT, std::move(vals)); } - return parse_execution_success; + return eval_result_t::ok; } -parse_execution_result_t parse_execution_context_t::populate_job_process( +eval_result_t parse_execution_context_t::populate_job_process( job_t *job, process_t *proc, tnode_t statement, tnode_t variable_assignments) { // Get the "specific statement" which is boolean / block / if / switch / decorated. const parse_node_t &specific_statement = statement.get_child_node<0>(); const block_t *block = nullptr; - parse_execution_result_t result = - this->apply_variable_assignments(proc, variable_assignments, &block); + eval_result_t result = this->apply_variable_assignments(proc, variable_assignments, &block); cleanup_t scope([&]() { if (block) parser->pop_block(block); }); - if (result != parse_execution_success) return parse_execution_errored; + if (result != eval_result_t::ok) return eval_result_t::error; switch (specific_statement.type) { case symbol_not_statement: { @@ -1113,7 +1101,7 @@ parse_execution_result_t parse_execution_context_t::populate_job_process( return result; } -parse_execution_result_t parse_execution_context_t::populate_job_from_job_node( +eval_result_t parse_execution_context_t::populate_job_from_job_node( job_t *j, tnode_t job_node, const block_t *associated_block) { UNUSED(associated_block); @@ -1128,7 +1116,7 @@ parse_execution_result_t parse_execution_context_t::populate_job_from_job_node( // Create processes. Each one may fail. process_list_t processes; processes.emplace_back(new process_t()); - parse_execution_result_t result = + eval_result_t result = this->populate_job_process(j, processes.back().get(), statement, variable_assignments); // Construct process_ts for job continuations (pipelines), by walking the list until we hit the @@ -1136,7 +1124,7 @@ parse_execution_result_t parse_execution_context_t::populate_job_from_job_node( tnode_t job_cont = job_node.child<2>(); assert(job_cont); while (auto pipe = job_cont.try_get_child()) { - if (result != parse_execution_success) { + if (result != eval_result_t::ok) { break; } auto variable_assignments = job_cont.require_get_child(); @@ -1173,7 +1161,7 @@ parse_execution_result_t parse_execution_context_t::populate_job_from_job_node( processes.back()->is_last_in_job = true; // Return what happened. - if (result == parse_execution_success) { + if (result == eval_result_t::ok) { // Link up the processes. assert(!processes.empty()); //!OCLINT(multiple unary operator) j->processes = std::move(processes); @@ -1191,10 +1179,10 @@ static bool remove_job(parser_t &parser, job_t *job) { return false; } -parse_execution_result_t parse_execution_context_t::run_1_job(tnode_t job_node, - const block_t *associated_block) { - if (should_cancel_execution(associated_block)) { - return parse_execution_cancelled; +eval_result_t parse_execution_context_t::run_1_job(tnode_t job_node, + const block_t *associated_block) { + if (auto ret = check_end_execution()) { + return *ret; } // Get terminal modes. @@ -1202,7 +1190,7 @@ parse_execution_result_t parse_execution_context_t::run_1_job(tnode_t jo if (parser->is_interactive() && tcgetattr(STDIN_FILENO, &tmodes)) { // Need real error handling here. wperror(L"tcgetattr"); - return parse_execution_errored; + return eval_result_t::error; } // Increment the eval_level for the duration of this command. @@ -1225,7 +1213,7 @@ parse_execution_result_t parse_execution_context_t::run_1_job(tnode_t jo if (job_is_simple_block(job_node)) { tnode_t variable_assignments = job_node.child<0>(); const block_t *block = nullptr; - parse_execution_result_t result = + eval_result_t result = this->apply_variable_assignments(nullptr, variable_assignments, &block); cleanup_t scope([&]() { if (block) parser->pop_block(block); @@ -1234,7 +1222,7 @@ parse_execution_result_t parse_execution_context_t::run_1_job(tnode_t jo tnode_t statement = job_node.child<1>(); const parse_node_t &specific_statement = statement.get_child_node<0>(); assert(specific_statement_type_is_redirectable_block(specific_statement)); - if (result == parse_execution_success) { + if (result == eval_result_t::ok) { switch (specific_statement.type) { case symbol_block_statement: { result = @@ -1302,7 +1290,7 @@ parse_execution_result_t parse_execution_context_t::run_1_job(tnode_t jo // Populate the job. This may fail for reasons like command_not_found. If this fails, an error // will have been printed. - parse_execution_result_t pop_result = + eval_result_t pop_result = this->populate_job_from_job_node(job.get(), job_node, associated_block); assert(libdata.caller_job_id == job->job_id && "Caller job ID unexpectedly changed"); @@ -1314,8 +1302,7 @@ parse_execution_result_t parse_execution_context_t::run_1_job(tnode_t jo } // Clean up the job on failure or cancellation. - bool populated_job = (pop_result == parse_execution_success); - if (populated_job) { + if (pop_result == eval_result_t::ok) { // Success. Give the job to the parser - it will clean it up. parser->job_add(job); @@ -1346,21 +1333,24 @@ parse_execution_result_t parse_execution_context_t::run_1_job(tnode_t jo profile_item->parse = static_cast(parse_time - start_time); profile_item->exec = static_cast(exec_time - parse_time); profile_item->cmd = job ? job->command() : wcstring(); - profile_item->skipped = !populated_job; + profile_item->skipped = (pop_result != eval_result_t::ok); } job_reap(*parser, false); // clean up jobs - return populated_job ? parse_execution_success : parse_execution_errored; + return pop_result; } -parse_execution_result_t parse_execution_context_t::run_job_conjunction( +eval_result_t parse_execution_context_t::run_job_conjunction( tnode_t job_expr, const block_t *associated_block) { - parse_execution_result_t result = parse_execution_success; + eval_result_t result = eval_result_t::ok; tnode_t cursor = job_expr; // continuation is the parent of the cursor tnode_t continuation; while (cursor) { - if (should_cancel_execution(associated_block)) break; + if (auto reason = check_end_execution()) { + result = *reason; + break; + } bool skip = false; if (continuation) { // Check the conjunction type. @@ -1391,20 +1381,23 @@ bool parse_execution_context_t::should_skip(parse_bool_statement_type_t type) co } template -parse_execution_result_t parse_execution_context_t::run_job_list(tnode_t job_list, - const block_t *associated_block) { +eval_result_t parse_execution_context_t::run_job_list(tnode_t job_list, + const block_t *associated_block) { // We handle both job_list and andor_job_list uniformly. static_assert(Type::token == symbol_job_list || Type::token == symbol_andor_job_list, "Not a job list"); - parse_execution_result_t result = parse_execution_success; + eval_result_t result = eval_result_t::ok; while (auto job_conj = job_list.template next_in_list()) { - if (should_cancel_execution(associated_block)) break; + if (auto reason = check_end_execution()) { + result = *reason; + break; + } // Maybe skip the job if it has a leading and/or. // Skipping is treated as success. if (should_skip(get_decorator(job_conj))) { - result = parse_execution_success; + result = eval_result_t::ok; } else { result = this->run_job_conjunction(job_conj, associated_block); } @@ -1414,11 +1407,11 @@ parse_execution_result_t parse_execution_context_t::run_job_list(tnode_t j return result; } -parse_execution_result_t parse_execution_context_t::eval_node(tnode_t statement, - const block_t *associated_block) { +eval_result_t parse_execution_context_t::eval_node(tnode_t statement, + const block_t *associated_block) { assert(statement && "Empty node in eval_node"); assert(statement.matches_node_tree(tree()) && "statement has unexpected tree"); - enum parse_execution_result_t status = parse_execution_success; + enum eval_result_t status = eval_result_t::ok; if (auto block = statement.try_get_child()) { status = this->run_block_statement(block, associated_block); } else if (auto ifstat = statement.try_get_child()) { @@ -1433,8 +1426,8 @@ parse_execution_result_t parse_execution_context_t::eval_node(tnode_t job_list, - const block_t *associated_block) { +eval_result_t parse_execution_context_t::eval_node(tnode_t job_list, + const block_t *associated_block) { // Apply this block IO for the duration of this function. assert(job_list && "Empty node in eval_node"); assert(job_list.matches_node_tree(tree()) && "job_list has unexpected tree"); diff --git a/src/parse_execution.h b/src/parse_execution.h index b9018f8ff..493d65f4d 100644 --- a/src/parse_execution.h +++ b/src/parse_execution.h @@ -13,14 +13,21 @@ class block_t; class parser_t; -enum parse_execution_result_t { - /// The job was successfully executed (though it have failed on its own). - parse_execution_success, - /// The job did not execute due to some error (e.g. failed to wildcard expand). An error will - /// have been printed and proc_last_status will have been set. - parse_execution_errored, - /// The job was cancelled (e.g. Ctrl-C). - parse_execution_cancelled, +/// An eval_result represents evaluation errors including wildcards which failed to match, syntax +/// errors, or other expansion errors. It also tracks when evaluation was skipped due to signal +/// cancellation. Note it does not track the exit status of commands. +enum class eval_result_t { + /// Evaluation was successfull. + ok, + + /// Evaluation was skipped due to control flow (break or return). + control_flow, + + /// Evaluation was cancelled, e.g. because of a signal or exit. + cancelled, + + /// A parse error or failed expansion (but not an error exit status from a command). + error, }; class parse_execution_context_t { @@ -38,31 +45,22 @@ class parse_execution_context_t { parse_execution_context_t(const parse_execution_context_t &) = delete; parse_execution_context_t &operator=(const parse_execution_context_t &) = delete; - // Should I cancel? - bool should_cancel_execution(const block_t *block) const; + // Check to see if we should end execution. + // \return the eval result to end with, or none() to continue on. + // This will never return eval_result_t::ok. + maybe_t check_end_execution() const; - // Ways that we can stop executing a block. These are in a sort of ascending order of - // importance, e.g. `exit` should trump `break`. - enum execution_cancellation_reason_t { - execution_cancellation_none, - execution_cancellation_loop_control, - execution_cancellation_skip, - execution_cancellation_exit - }; - execution_cancellation_reason_t cancellation_reason(const block_t *block) const; - - // Report an error. Always returns true. - parse_execution_result_t report_error(const parse_node_t &node, const wchar_t *fmt, ...) const; - parse_execution_result_t report_errors(const parse_error_list_t &error_list) const; + // Report an error. Always returns 'eval_result_t::error'. + eval_result_t report_error(const parse_node_t &node, const wchar_t *fmt, ...) const; + eval_result_t report_errors(const parse_error_list_t &error_list) const; // Wildcard error helper. - parse_execution_result_t report_unmatched_wildcard_error( - const parse_node_t &unmatched_wildcard) const; + eval_result_t report_unmatched_wildcard_error(const parse_node_t &unmatched_wildcard) const; /// Command not found support. - parse_execution_result_t handle_command_not_found(const wcstring &cmd, - tnode_t statement, - int err_code); + eval_result_t handle_command_not_found(const wcstring &cmd, + tnode_t statement, + int err_code); // Utilities wcstring get_source(const parse_node_t &node) const; @@ -72,8 +70,8 @@ class parse_execution_context_t { // Expand a command which may contain variables, producing an expand command and possibly // arguments. Prints an error message on error. - parse_execution_result_t expand_command(tnode_t statement, - wcstring *out_cmd, wcstring_list_t *out_args) const; + eval_result_t expand_command(tnode_t statement, wcstring *out_cmd, + wcstring_list_t *out_args) const; /// Return whether we should skip a job with the given bool statement type. bool should_skip(parse_bool_statement_type_t type) const; @@ -83,58 +81,57 @@ class parse_execution_context_t { enum process_type_t process_type_for_command(tnode_t statement, const wcstring &cmd) const; - parse_execution_result_t apply_variable_assignments( + eval_result_t apply_variable_assignments( process_t *proc, tnode_t variable_assignments, const block_t **block); // These create process_t structures from statements. - parse_execution_result_t populate_job_process( - job_t *job, process_t *proc, tnode_t statement, - tnode_t variable_assignments); - parse_execution_result_t populate_not_process(job_t *job, process_t *proc, - tnode_t not_statement); - parse_execution_result_t populate_plain_process(job_t *job, process_t *proc, - tnode_t statement); + eval_result_t populate_job_process(job_t *job, process_t *proc, + tnode_t statement, + tnode_t variable_assignments); + eval_result_t populate_not_process(job_t *job, process_t *proc, + tnode_t not_statement); + eval_result_t populate_plain_process(job_t *job, process_t *proc, + tnode_t statement); template - parse_execution_result_t populate_block_process(job_t *job, process_t *proc, - tnode_t statement, - tnode_t specific_statement); + eval_result_t populate_block_process(job_t *job, process_t *proc, + tnode_t statement, + tnode_t specific_statement); // These encapsulate the actual logic of various (block) statements. - parse_execution_result_t run_block_statement(tnode_t statement, - const block_t *associated_block); - parse_execution_result_t run_for_statement(tnode_t header, - tnode_t contents); - parse_execution_result_t run_if_statement(tnode_t statement, - const block_t *associated_block); - parse_execution_result_t run_switch_statement(tnode_t statement); - parse_execution_result_t run_while_statement(tnode_t header, - tnode_t contents, - const block_t *associated_block); - parse_execution_result_t run_function_statement(tnode_t header, - tnode_t body); - parse_execution_result_t run_begin_statement(tnode_t contents); + eval_result_t run_block_statement(tnode_t statement, + const block_t *associated_block); + eval_result_t run_for_statement(tnode_t header, + tnode_t contents); + eval_result_t run_if_statement(tnode_t statement, + const block_t *associated_block); + eval_result_t run_switch_statement(tnode_t statement); + eval_result_t run_while_statement(tnode_t header, + tnode_t contents, + const block_t *associated_block); + eval_result_t run_function_statement(tnode_t header, + tnode_t body); + eval_result_t run_begin_statement(tnode_t contents); enum globspec_t { failglob, nullglob }; using argument_node_list_t = std::vector>; - parse_execution_result_t expand_arguments_from_nodes(const argument_node_list_t &argument_nodes, - wcstring_list_t *out_arguments, - globspec_t glob_behavior); + eval_result_t expand_arguments_from_nodes(const argument_node_list_t &argument_nodes, + wcstring_list_t *out_arguments, + globspec_t glob_behavior); // Determines the list of redirections for a node. Returns none() on failure, for example, an // invalid fd. bool determine_redirections(tnode_t node, redirection_spec_list_t *out_redirs); - parse_execution_result_t run_1_job(tnode_t job, const block_t *associated_block); - parse_execution_result_t run_job_conjunction(tnode_t job_expr, - const block_t *associated_block); + eval_result_t run_1_job(tnode_t job, const block_t *associated_block); + eval_result_t run_job_conjunction(tnode_t job_expr, + const block_t *associated_block); template - parse_execution_result_t run_job_list(tnode_t job_list_node, - const block_t *associated_block); - parse_execution_result_t populate_job_from_job_node(job_t *j, tnode_t job_node, - const block_t *associated_block); + eval_result_t run_job_list(tnode_t job_list_node, const block_t *associated_block); + eval_result_t populate_job_from_job_node(job_t *j, tnode_t job_node, + const block_t *associated_block); // Returns the line number of the node. Not const since it touches cached_lineno_offset. int line_offset_of_node(tnode_t node); @@ -158,10 +155,8 @@ class parse_execution_context_t { /// Start executing at the given node. Returns 0 if there was no error, 1 if there was an /// error. - parse_execution_result_t eval_node(tnode_t statement, - const block_t *associated_block); - parse_execution_result_t eval_node(tnode_t job_list, - const block_t *associated_block); + eval_result_t eval_node(tnode_t statement, const block_t *associated_block); + eval_result_t eval_node(tnode_t job_list, const block_t *associated_block); }; #endif diff --git a/src/parser.cpp b/src/parser.cpp index 1146fe1a3..20fa4e01e 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -674,19 +674,18 @@ eval_result_t parser_t::eval_node(parsed_source_ref_t ps, tnode_t node, block using exc_ctx_ref_t = std::unique_ptr; scoped_push exc( &execution_context, make_unique(ps, this, std::move(lineage))); - parse_execution_result_t res = execution_context->eval_node(node, scope_block); + eval_result_t res = execution_context->eval_node(node, scope_block); exc.restore(); this->pop_block(scope_block); job_reap(*this, false); // reap again - switch (res) { - case parse_execution_success: - return eval_result_t::ok; - case parse_execution_errored: - return eval_result_t::error; - case parse_execution_cancelled: - return eval_result_t::cancelled; + + // control_flow is used internally to react to break and return. + // Here we treat that as success. + if (res == eval_result_t::control_flow) { + res = eval_result_t::ok; } + return res; } // Explicit instantiations. TODO: use overloads instead? diff --git a/src/parser.h b/src/parser.h index bbe1c1eff..41aff1176 100644 --- a/src/parser.h +++ b/src/parser.h @@ -15,6 +15,7 @@ #include "event.h" #include "expand.h" #include "parse_constants.h" +#include "parse_execution.h" #include "parse_tree.h" #include "proc.h" @@ -53,18 +54,6 @@ enum class loop_status_t { continues, /// current loop block should be skipped }; -/// Result of the source code form of eval. -enum class eval_result_t { - /// eval was able to evaluate the source or tree. - ok, - - /// Evaluation was cancelled, e.g. because of a signal. - cancelled, - - /// Parse or execution error (but not simply a failed command). - error, -}; - /// block_t represents a block of commands. class block_t { /// Construct from a block type.