expr: fix eager evaluation of parenthesized dead branches (#11395)

Co-authored-by: Sylvestre Ledru <sylvestre@debian.org>
This commit is contained in:
Can Bölük
2026-04-03 12:13:24 +02:00
committed by GitHub
co-authored by Sylvestre Ledru
parent 90c7896d23
commit 76b2f7877f
2 changed files with 27 additions and 23 deletions
+2 -21
View File
@@ -624,9 +624,6 @@ pub struct AstNode {
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub enum AstNodeInner {
Evaluated {
value: NumOrStr,
},
Leaf {
value: MaybeNonUtf8String,
},
@@ -650,15 +647,6 @@ impl AstNode {
Parser::new(input).parse()
}
pub fn evaluated(self) -> ExprResult<Self> {
Ok(Self {
id: get_next_id(),
inner: AstNodeInner::Evaluated {
value: self.eval()?,
},
})
}
pub fn eval(&self) -> ExprResult<NumOrStr> {
// This function implements a recursive tree-walking algorithm, but uses an explicit
// stack approach instead of native recursion to avoid potential stack overflow
@@ -669,9 +657,6 @@ impl AstNode {
while let Some(node) = stack.pop() {
match &node.inner {
AstNodeInner::Evaluated { value, .. } => {
result_stack.insert(node.id, Ok(value.clone()));
}
AstNodeInner::Leaf { value, .. } => {
result_stack.insert(node.id, Ok(value.to_owned().into()));
}
@@ -896,9 +881,7 @@ impl<'a, S: AsRef<MaybeNonUtf8Str>> Parser<'a, S> {
value: self.next()?.into(),
},
b"(" => {
// Evaluate the node just after parsing to we detect arithmetic
// errors before checking for the closing parenthesis.
let s = self.parse_expression()?.evaluated()?;
let s = self.parse_expression()?;
match self.next() {
Ok(b")") => {}
@@ -1070,9 +1053,7 @@ mod test {
AstNode::parse(&["(", "1", "+", "2", ")", "*", "3"]),
Ok(op(
BinOp::Numeric(NumericOp::Mul),
op(BinOp::Numeric(NumericOp::Add), "1", "2")
.evaluated()
.unwrap(),
op(BinOp::Numeric(NumericOp::Add), "1", "2"),
"3"
))
);
+25 -2
View File
@@ -217,6 +217,29 @@ fn test_and() {
new_ucmd!().args(&["", "&", ""]).fails().stdout_only("0\n");
}
#[test]
fn test_parenthesized_short_circuit_dead_branches() {
new_ucmd!()
.args(&["1", "|", "(", "1", "/", "0", ")"])
.succeeds()
.stdout_only("1\n");
new_ucmd!()
.args(&["0", "&", "(", "1", "/", "0", ")"])
.fails_with_code(1)
.stdout_only("0\n");
new_ucmd!()
.args(&["1", "|", "(", "0", "&", "(", "1", "/", "0", ")", ")"])
.succeeds()
.stdout_only("1\n");
new_ucmd!()
.args(&["0", "&", "(", "1", "|", "(", "1", "/", "0", ")", ")"])
.fails_with_code(1)
.stdout_only("0\n");
}
#[test]
fn test_length_fail() {
new_ucmd!().args(&["length", "αbcdef", "1"]).fails();
@@ -580,11 +603,11 @@ fn test_num_str_comparison() {
}
#[test]
fn test_eager_evaluation() {
fn test_missing_closing_parenthesis_reports_syntax_error() {
new_ucmd!()
.args(&["(", "1", "/", "0"])
.fails()
.stderr_contains("division by zero");
.stderr_contains("expecting ')' after '0'");
}
#[test]