Fix try printing when it is not the last pipeline element (#13992)

# Description

Fixes #13991. This was done by more clearly separating the case when a
pipeline is drained vs when it is being written (to a file).

I also added an `OutDest::Print` case which might not be strictly
necessary, but is a helpful addition.

# User-Facing Changes

Bug fix.

# Tests + Formatting

Added a test.

# After Submitting

There are still a few redirection bugs that I found, but they require
larger code changes, so I'll leave them until after the release.
This commit is contained in:
Ian Manske
2024-10-11 23:37:10 -07:00
committed by GitHub
parent 0e3a8c552c
commit de08b68ba8
14 changed files with 127 additions and 194 deletions

View File

@ -205,7 +205,7 @@ impl BlockBuilder {
Instruction::Span { src_dst } => allocate(&[*src_dst], &[*src_dst]),
Instruction::Drop { src } => allocate(&[*src], &[]),
Instruction::Drain { src } => allocate(&[*src], &[]),
Instruction::WriteToOutDests { src } => allocate(&[*src], &[]),
Instruction::DrainIfEnd { src } => allocate(&[*src], &[]),
Instruction::LoadVariable { dst, var_id: _ } => allocate(&[], &[*dst]),
Instruction::StoreVariable { var_id: _, src } => allocate(&[*src], &[]),
Instruction::DropVariable { var_id: _ } => Ok(()),

View File

@ -473,7 +473,7 @@ pub(crate) fn compile_try(
if let Some(mode) = redirect_modes.err {
builder.push(mode.map(|mode| Instruction::RedirectErr { mode }))?;
}
builder.push(Instruction::WriteToOutDests { src: io_reg }.into_spanned(call.head))?;
builder.push(Instruction::DrainIfEnd { src: io_reg }.into_spanned(call.head))?;
builder.push(Instruction::PopErrorHandler.into_spanned(call.head))?;
// Jump over the failure case

View File

@ -151,7 +151,8 @@ pub(crate) fn out_dest_to_redirect_mode(
OutDest::PipeSeparate => Ok(RedirectMode::PipeSeparate),
OutDest::Value => Ok(RedirectMode::Value),
OutDest::Null => Ok(RedirectMode::Null),
OutDest::Inherit => Ok(RedirectMode::Inherit),
OutDest::Print => Ok(RedirectMode::Print),
OutDest::Inherit => Err(CompileError::InvalidRedirectMode { span }),
OutDest::File(_) => Err(CompileError::InvalidRedirectMode { span }),
})
.transpose()

View File

@ -395,13 +395,13 @@ fn eval_element_with_input_inner<D: DebugContext>(
) -> Result<PipelineData, ShellError> {
let data = eval_expression_with_input::<D>(engine_state, stack, &element.expr, input)?;
if let Some(redirection) = element.redirection.as_ref() {
let is_external = if let PipelineData::ByteStream(stream, ..) = &data {
matches!(stream.source(), ByteStreamSource::Child(..))
} else {
false
};
let is_external = if let PipelineData::ByteStream(stream, ..) = &data {
matches!(stream.source(), ByteStreamSource::Child(..))
} else {
false
};
if let Some(redirection) = element.redirection.as_ref() {
if !is_external {
match redirection {
&PipelineRedirection::Single {
@ -437,30 +437,24 @@ fn eval_element_with_input_inner<D: DebugContext>(
}
}
let has_stdout_file = matches!(stack.pipe_stdout(), Some(OutDest::File(_)));
let data = match &data {
PipelineData::Value(..) | PipelineData::ListStream(..) => {
if has_stdout_file {
data.write_to_out_dests(engine_state, stack)?;
let data = if let Some(OutDest::File(file)) = stack.pipe_stdout() {
match &data {
PipelineData::Value(..) | PipelineData::ListStream(..) => {
data.write_to(file.as_ref())?;
PipelineData::Empty
} else {
data
}
}
PipelineData::ByteStream(stream, ..) => {
let write = match stream.source() {
ByteStreamSource::Read(_) | ByteStreamSource::File(_) => has_stdout_file,
ByteStreamSource::Child(_) => false,
};
if write {
data.write_to_out_dests(engine_state, stack)?;
PipelineData::Empty
} else {
data
PipelineData::ByteStream(..) => {
if !is_external {
data.write_to(file.as_ref())?;
PipelineData::Empty
} else {
data
}
}
PipelineData::Empty => PipelineData::Empty,
}
PipelineData::Empty => PipelineData::Empty,
} else {
data
};
Ok(data)

View File

@ -322,13 +322,13 @@ fn eval_instruction<D: DebugContext>(
let data = ctx.take_reg(*src);
drain(ctx, data)
}
Instruction::WriteToOutDests { src } => {
Instruction::DrainIfEnd { src } => {
let data = ctx.take_reg(*src);
let res = {
let stack = &mut ctx
.stack
.push_redirection(ctx.redirect_out.clone(), ctx.redirect_err.clone());
data.write_to_out_dests(ctx.engine_state, stack)?
data.drain_to_out_dests(ctx.engine_state, stack)?
};
ctx.put_reg(*src, res);
Ok(Continue)
@ -507,14 +507,19 @@ fn eval_instruction<D: DebugContext>(
msg: format!("Tried to write to file #{file_num}, but it is not open"),
span: Some(*span),
})?;
let result = {
let mut stack = ctx
.stack
.push_redirection(Some(Redirection::File(file)), None);
src.write_to_out_dests(ctx.engine_state, &mut stack)?
let is_external = if let PipelineData::ByteStream(stream, ..) = &src {
matches!(stream.source(), ByteStreamSource::Child(..))
} else {
false
};
// Abort execution if there's an exit code from a failed external
drain(ctx, result)
if let Err(err) = src.write_to(file.as_ref()) {
if is_external {
ctx.stack.set_last_error(&err);
}
Err(err)?
} else {
Ok(Continue)
}
}
Instruction::CloseFile { file_num } => {
if ctx.files[*file_num as usize].take().is_some() {
@ -1421,6 +1426,7 @@ fn eval_redirection(
RedirectMode::Value => Ok(Some(Redirection::Pipe(OutDest::Value))),
RedirectMode::Null => Ok(Some(Redirection::Pipe(OutDest::Null))),
RedirectMode::Inherit => Ok(Some(Redirection::Pipe(OutDest::Inherit))),
RedirectMode::Print => Ok(Some(Redirection::Pipe(OutDest::Print))),
RedirectMode::File { file_num } => {
let file = ctx
.files