stalwartlabs/stalwart · error

Invalid jump position

Error message

Invalid jump position

What it means

In the expression parser, update_jmp_pos patches the jump offset of a previously emitted JmpIf instruction once its target position is known. If the item at the recorded position is not a JmpIf, the recorded jump position is corrupted — a parser invariant violation. The panic is compiled in only under `cfg(test)`, so in release builds the mismatch is silently ignored and the failure surfaces as a test-time "Invalid jump position" panic.

Source

Thrown at crates/common/src/expr/parser.rs:259

                _ => {}
            }
        }
    }

    fn dec_arg_count(&mut self) {
        if let Some(x) = self.arg_count.last_mut() {
            *x = x.saturating_sub(1);
        }
    }

    fn update_jmp_pos(&mut self, jmp_pos: Option<usize>) {
        if let Some(jmp_pos) = jmp_pos {
            let cur_pos = self.output.len();
            if let ExpressionItem::JmpIf { pos, .. } = &mut self.output[jmp_pos] {
                *pos = (cur_pos - jmp_pos) as u32;
            } else {
                #[cfg(test)]
                panic!("Invalid jump position");
            }
        }
    }
}

impl BinaryOperator {
    fn precedence(&self) -> i32 {
        match self {
            BinaryOperator::Multiply | BinaryOperator::Divide => 7,
            BinaryOperator::Add | BinaryOperator::Subtract => 6,
            BinaryOperator::Gt | BinaryOperator::Ge | BinaryOperator::Lt | BinaryOperator::Le => 5,
            BinaryOperator::Eq | BinaryOperator::Ne => 4,
            BinaryOperator::Xor => 3,
            BinaryOperator::And => 2,
            BinaryOperator::Or => 1,
        }
    }
}

View on GitHub (pinned to e962003857)

Solutions

  1. Audit the parse path that pushes jmp_pos: ensure a JmpIf is emitted at exactly the recorded index before update_jmp_pos runs.
  2. Check that no code path replaces or reorders self.output entries between emitting JmpIf and patching it.
  3. Run the parser tests with debug output of self.output to find where the recorded slot no longer holds JmpIf.

Example fix

// before: emitting a non-jump item into a slot that had a pending JmpIf
self.output[jmp_pos] = ExpressionItem::Const(...);
// after: push new items; never overwrite pending JmpIf slots
self.output.push(ExpressionItem::Const(...));
// then update_jmp_pos(Some(jmp_pos)) patches the untouched JmpIf
Defensive patterns

Strategy: type-guard

Type guard

fn is_jmp_if(item: &ExpressionItem) -> bool {
    matches!(item, ExpressionItem::JmpIf { .. })
}

Try / catch

// test-only panic; in library code prefer a no-op or assert with context:
if let Some(jmp_pos) = jmp_pos {
    debug_assert!(matches!(self.output[jmp_pos], ExpressionItem::JmpIf { .. }),
        "jmp slot {jmp_pos} does not hold JmpIf");
}

Prevention

When it happens

Trigger: Parsing an expression whose control-flow bookkeeping gets out of sync — e.g. a jump was recorded (jmp_pos pushed) but the output slot was later overwritten with a non-JmpIf item, or positions were recorded/emitted in the wrong order while parsing conditional operators. Running parser tests with such input triggers the panic inside update_jmp_pos, called from parse.

Common situations: Developers modifying the parser's output emission or jump bookkeeping (adding new operators or short-circuit handling) and running the test suite; malformed expressions exercising edge cases in jump patching during test runs.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of stalwartlabs/stalwart@e962003857 (2026-09-06). Data as JSON: /api/errors/0e4795c59d2ba52f. Report an issue: GitHub.