DEV Community

Tomasz Wegrzanowski
Tomasz Wegrzanowski

Posted on

Open Source Adventures: Episode 26: Imba 2 Stack Overflow

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. 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>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 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.

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.

Top comments (0)