Skip to content

Commit

Permalink
Consider condition context in apply_errexit
Browse files Browse the repository at this point in the history
  • Loading branch information
magicant committed Nov 17, 2022
1 parent 6958b74 commit 01918e8
Show file tree
Hide file tree
Showing 4 changed files with 38 additions and 24 deletions.
9 changes: 5 additions & 4 deletions yash-env/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -435,17 +435,18 @@ impl Env {
self.variables.assign(scope, name, value)
}

pub(crate) fn errexit_is_applicable(&self) -> bool {
self.options.get(ErrExit) == On && !self.stack.contains(&Frame::Condition)
}

/// Returns a `Divert` if the shell should exit because of the `ErrExit`
/// [shell option](self::option::Option).
///
/// The function returns `Break(Divert::Exit)` if the `ErrExit` option is
/// on, the current `self.exit_status` is non-zero, and the current stack
/// has no `Condition` [frame](Frame); otherwise, `Continue(())`.
pub fn apply_errexit(&self) -> ControlFlow<Divert> {
if self.options.get(ErrExit) == On
&& self.exit_status != ExitStatus::SUCCESS
&& !self.stack.contains(&Frame::Condition)
{
if self.exit_status != ExitStatus::SUCCESS && self.errexit_is_applicable() {
Break(Divert::Exit(None))
} else {
Continue(())
Expand Down
43 changes: 28 additions & 15 deletions yash-env/src/semantics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@

//! Type definitions for command execution.

use crate::option::Option::ErrExit;
use crate::option::OptionSet;
use crate::option::State::On;
use crate::Env;
use nix::sys::signal::Signal;
use nix::sys::wait::WaitStatus;
use std::ffi::c_int;
Expand Down Expand Up @@ -227,44 +225,59 @@ pub type Result<T = ()> = ControlFlow<Divert, T>;

/// Applies the `ErrExit` shell option to the result.
///
/// If the `ErrExit` option is on in `options` and the `result` is
/// `Divert::Interrupt`, then the result is converted to `Divert::Exit`.
pub fn apply_errexit<T>(result: Result<T>, options: &OptionSet) -> Result<T> {
/// If the `ErrExit` option is on in `env.options`, `env.stack` contains no
/// `Frame::Condition`, and the `result` is `Divert::Interrupt`, then the result
/// is converted to `Divert::Exit`.
pub fn apply_errexit<T>(result: Result<T>, env: &Env) -> Result<T> {
match result {
Break(Divert::Interrupt(exit_status)) if options.get(ErrExit) == On => {
Break(Divert::Interrupt(exit_status)) if env.errexit_is_applicable() => {
Break(Divert::Exit(exit_status))
}

other => other,
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::option::Option::ErrExit;
use crate::option::State::On;
use crate::stack::Frame;

#[test]
fn apply_errexit_applicable() {
let mut options = OptionSet::empty();
options.set(ErrExit, On);
let mut env = Env::new_virtual();
env.options.set(ErrExit, On);
let subject: Result = Break(Divert::Interrupt(Some(ExitStatus(42))));
let result = apply_errexit(subject, &options);
let result = apply_errexit(subject, &env);
assert_eq!(result, Break(Divert::Exit(Some(ExitStatus(42)))));
}

#[test]
fn apply_errexit_to_non_interrupt() {
let mut options = OptionSet::empty();
options.set(ErrExit, On);
let mut env = Env::new_virtual();
env.options.set(ErrExit, On);
let subject: Result = Break(Divert::Return);
let result = apply_errexit(subject, &options);
let result = apply_errexit(subject, &env);
assert_eq!(result, subject);
}

#[test]
fn apply_errexit_in_condition_context() {
let mut env = Env::new_virtual();
env.options.set(ErrExit, On);
let env = env.push_frame(Frame::Condition);
let subject: Result = Break(Divert::Interrupt(Some(ExitStatus(42))));
let result = apply_errexit(subject, &env);
assert_eq!(result, subject);
}

#[test]
fn apply_errexit_with_disabled_option() {
let options = OptionSet::empty();
let env = Env::new_virtual();
let subject: Result = Break(Divert::Interrupt(Some(ExitStatus(42))));
let result = apply_errexit(subject, &options);
let result = apply_errexit(subject, &env);
assert_eq!(result, subject);
}

Expand Down
4 changes: 2 additions & 2 deletions yash-semantics/src/command/compound_command/case.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,14 @@ fn config() -> Config {
pub async fn execute(env: &mut Env, subject: &Word, items: &[CaseItem]) -> Result {
let subject = match expand_word(env, subject).await {
Ok((expansion, _exit_status)) => expansion,
Err(error) => return apply_errexit(error.handle(env).await, &env.options),
Err(error) => return apply_errexit(error.handle(env).await, &env),
};

'outer: for item in items {
for pattern in &item.patterns {
let mut pattern = match expand_word_attr(env, pattern).await {
Ok((expansion, _exit_status)) => expansion,
Err(error) => return apply_errexit(error.handle(env).await, &env.options),
Err(error) => return apply_errexit(error.handle(env).await, &env),
};

// Unquoted backslashes should act as quoting, as required by POSIX XCU 2.13.1
Expand Down
6 changes: 3 additions & 3 deletions yash-semantics/src/command/compound_command/for_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,13 @@ pub async fn execute(
) -> Result {
let (name, _) = match expand_word(env, name).await {
Ok(word) => word,
Err(error) => return apply_errexit(error.handle(env).await, &env.options),
Err(error) => return apply_errexit(error.handle(env).await, &env),
};

let values = if let Some(words) = values {
match expand_words(env, words).await {
Ok((fields, _)) => fields,
Err(error) => return apply_errexit(error.handle(env).await, &env.options),
Err(error) => return apply_errexit(error.handle(env).await, &env),
}
} else {
match env.variables.positional_params().value {
Expand Down Expand Up @@ -86,7 +86,7 @@ pub async fn execute(
let cause = ErrorCause::AssignReadOnly(error);
let location = name.origin;
let error = Error { cause, location };
return apply_errexit(error.handle(env).await, &env.options);
return apply_errexit(error.handle(env).await, &env);
}
};
}
Expand Down

0 comments on commit 01918e8

Please sign in to comment.