diff --git a/src/halmos/sevm.py b/src/halmos/sevm.py index 4265934d..ac5ce97d 100644 --- a/src/halmos/sevm.py +++ b/src/halmos/sevm.py @@ -452,9 +452,15 @@ def peek(self, n: int = 1) -> Word: return self.stack[-n] def dup(self, n: int) -> None: + if len(self.stack) < n: + raise StackUnderflowError() + self.stack.append(self.stack[-n]) def swap(self, n: int) -> None: + if len(self.stack) < n + 1: + raise StackUnderflowError() + self.stack[-(n + 1)], self.stack[-1] = self.stack[-1], self.stack[-(n + 1)] def mloc(self, subst: dict = None) -> int: diff --git a/tests/test_sevm.py b/tests/test_sevm.py index 7c39ef77..eabfd211 100644 --- a/tests/test_sevm.py +++ b/tests/test_sevm.py @@ -336,13 +336,18 @@ def test_opcode_stack(hexcode, stack_in, stack_out, sevm: SEVM, solver, storage) assert output_ex.st.stack == list(reversed(stack_out)) -def test_stack_underflow_pop(sevm: SEVM, solver, storage): - # check that we get an exception when popping from an empty stack - ex = mk_ex(o(EVM.POP), sevm, solver, storage, caller, this) - - exs: list[Exec] = list(sevm.run(ex)) - - [output_ex] = exs +@pytest.mark.parametrize( + "opcode", + [ + EVM.POP, + *range(EVM.DUP1, EVM.DUP16 + 1), + *range(EVM.SWAP1, EVM.SWAP16 + 1), + ], +) +def test_stack_underflow(sevm: SEVM, solver, storage, opcode): + """Test that operations on empty stack raise StackUnderflowError""" + ex = mk_ex(o(opcode), sevm, solver, storage, caller, this) + [output_ex] = list(sevm.run(ex)) assert isinstance(output_ex.context.output.error, StackUnderflowError)