Open Source Adventures: Episode 26: Imba 2 Stack Overflow
Tags: javascript, imba
Published: 2022-03-30 18:55:34

While I was porting apps from Imba 1 to Imba 2 I encountered a bug in Imba 2, but I was too busy to deal with it. Let's take a step back, try to reproduce a bug with minimal case, and then submit it.

### The Bug

The bug was that when I'd put `debugger` statement in the code, depending on where it was, Imba would stack overflow.

### Getting to replication

I don't remember which apps had this issue, so I'll try it first with [`imba2-game-of-life`](https://github.com/taw/imba2-game-of-life). Fortunately if it works I won't even need to run it, it will be obvious on the terminal.

So I opened `client.imba` and started adding `debugger` in various places.

It took zero time to get a crash, in any function where I use it after a block, here's an example:

```
def countNeighbours(cells, x, y)
  let count = 0
  for cell of cells
    if !cell.state
      continue
    let dx = Math.abs(cell.x - x)
    let dy = Math.abs(cell.y - y)
    if Math.max(dx, dy) == 1
      count += 1
  count

def runStep(cells)
  let nextCells = []
  for cell of cells
    let n = countNeighbours(cells, cell.x, cell.y)
    let nextState = (n == 3 || (cell.state && n == 2))
    nextCells.push({x: cell.x, y: cell.y, state: nextState})
  nextCells

tag cell
  prop data

  def onclick
    data.state = !data.state
    emit("pause")

  def render
    let visualStartX = 20 * data.x + 1
    let visualStartY = 20 * data.y + 1

    <self[left:{visualStartX}px top:{visualStartY}px] .alive=(data.state) .dead=(!data.state) @click.onclick>

  css
    position: absolute
    width: 18px
    height: 18px
    &.dead
      background-color: #864
    &.alive
      background-color: #3f3

tag app
  prop cells
  prop playing = true

  def setup
    let sizex = 30
    let sizey = 30
    cells = []
    for x in [0 ... sizex]
      for y in [0 ... sizey]
        cells.push({ x: x, y: y, state: Math.random() < 0.2 })

  def step
    cells = runStep(cells)

  def mount
    imba.setInterval(&,100) do
      if playing
        step()
    debugger

  def play
    playing = true

  def pause
    playing = false

  def render
    <self>
      <header>
        "Game of Life"
      <div.board>
        for cell in cells
          <cell data=cell @pause.pause>
      <div.buttons>
        if playing
          <button @click.pause>
            "Pause"
        else
          <button @click.step>
            "Step"
          <button @click.play>
            "Play"

  css
    header
      font-size: 64px
      text-align: center
    .board
      position: relative
      height: 600px
      width: 600px
      background-color: #aaa
      margin: auto
    .buttons
      text-align: center
    button
      margin: 0.5em

imba.mount <app>
```

And error message in the terminal:

```
ERROR COMPILING IMBA RangeError: Maximum call stack size exceeded
    at Yp.J.consume (~/imba2-game-of-life/node_modules/imba/compiler.imba.js:79:11138)
    at Qe.c (~/imba2-game-of-life/node_modules/imba/compiler.imba.js:88:4768)
    at Qe.c (~/imba2-game-of-life/node_modules/imba/compiler.imba.js:88:4782)
    at Qe.c (~/imba2-game-of-life/node_modules/imba/compiler.imba.js:88:4782)
    at Qe.c (~/imba2-game-of-life/node_modules/imba/compiler.imba.js:88:4782)
    at Qe.c (~/imba2-game-of-life/node_modules/imba/compiler.imba.js:88:4782)
    at Qe.c (~/imba2-game-of-life/node_modules/imba/compiler.imba.js:88:4782)
    at Qe.c (~/imba2-game-of-life/node_modules/imba/compiler.imba.js:88:4782)
    at Qe.c (~/imba2-game-of-life/node_modules/imba/compiler.imba.js:88:4782)
    at Qe.c (~/imba2-game-of-life/node_modules/imba/compiler.imba.js:88:4782) app/client.imba
```

### Removing irrelevant parts

OK, we don't need an app, we just need a clear test case. So time to remove all extra stuff. And step by step, after each one verifying that the crash is still there, I arrived at this:

```
def foo
  debugger
```

Even that causes the same crash.

### Exploring other ways to trigger the bug

OK, so right now a `debugger` at the end of the method crashes Imba compiler. But I'm pretty sure I saw this in other contexts too.

Looks like putting `debugger` at end of a block also does it:

```
def foo
  imba.setInterval(&,1000) do
    debugger
```

I think I saw this happen in some other contexts, but I can't reproduce it right now. So maybe I'm misremembering, and possibly a fix for this would also deal with more complex cases anyway.

### Time to submit a bug

Next step is to go to [GitHub issues for Imba](https://github.com/imba/imba/issues) and do a search for `debugger`. It doesn't look like it was reported before, so time to submit it.

As usual, I deleted the pre-made template, as I find them more annoying than useful. [So here's the bug report](https://github.com/imba/imba/issues/663).

In retrospect I should have saved the failing code as I was coding, even if I didn't have time to write a bug report.

### Coming next

In the next episode I'll give some thoughts about Imba 2.