scala / bug

Scala 2 bug reports only. Please, no questions — proper bug reports only.
https://scala-lang.org
230 stars 21 forks source link

async: switch with await in guard leads to invalid AST / classfile #12990

Closed lrytz closed 3 weeks ago

lrytz commented 1 month ago
//> using options -Xasync

import OptionAwait._

object Test {
  def main(args: Array[String]): Unit = {
    assert(Some(22) == iSw(11))
  }

  private def iSw(i: Int) = optionally {
    i match {
      case 11 if value(Some(430)) > 42 => 22
      case p => p
    }
  }
}

The ASTs are, before async:

            (x1: Int) match {
              case 11 => if (unbox(OptionAwait.value(new Some(scala.Int.box(430)))).>(42))
                scala.Int.box(22)
              else
                scala.Int.box(default3())
              case _ => default3(){
                scala.Int.box(x1)
              }

The label is added in SwitchMaker.collapseGuardedCases to translate switches with guards.

Erasure adds scala.Int.box(default3()) around the label jump, which is wrong / dead code, but wihtout async it's just skipped when emitting bytecode, so it works.

After async:

        override def apply(tr$async: Option): Unit = while$(){
          try {
            stateMachine$async.this.state() match {
              case 0 => {
                case <synthetic> val x1: Int = i;
                stateMachine$async.this.match$1 = null;
                (x1: Int) match {
                  case 11 => {
                    val awaitable$async: Some = new Some(scala.Int.box(430));
                    tr$async = stateMachine$async.this.getCompleted(awaitable$async);
                    stateMachine$async.this.state_=(1);
                    if (null.!=(tr$async))
                      while$()
                    else
                      {
                        stateMachine$async.this.onComplete(awaitable$async);
                        return ()
                      }
                  }
                  case _ => {
                    stateMachine$async.this.match$1 = default3(){
                      scala.Int.box(x1)
                    }
                  }
                };
                stateMachine$async.this.state_=(2);
                while$()
              }
              case 1 => {
                <synthetic> val await$1: Object = {
                  val tryGetResult$async: Object = stateMachine$async.this.tryGet(tr$async);
                  if (stateMachine$async.this.eq(tryGetResult$async))
                    return ()
                  else
                    tryGetResult$async.$asInstanceOf[Object]()
                };
                if (unbox(await$1).>(42))
                  stateMachine$async.this.match$1 = scala.Int.box(22)
                else
                  stateMachine$async.this.match$1 = scala.Int.box(default3());
                stateMachine$async.this.state_=(2);
                while$()
              }
              case 2 => {
                stateMachine$async.this.completeSuccess(stateMachine$async.this.match$1);
                return ()
              }
              case _ => throw new IllegalStateException(java.lang.String.valueOf(stateMachine$async.this.state()))
            }
          } catch {
            case (throwable$async @ (_: Throwable)) => {
              stateMachine$async.this.completeFailure(throwable$async);
              return ()
            }
          };
          while$()
        }

The the jump to default3() is now wrong, we don't want to jump from case 1 into case 0 of the state machine.

Also match$1 = default3(){ scala.Int.box(x1) } is strange; but that's the label def, doesn't change emitted code.

But mainly stateMachine$async.this.match$1 = scala.Int.box(default3()); is not right, default3() is a jump, not a call that returns a result that can be assigned to match$1.

Running the example gives

Exception in thread "main" java.lang.VerifyError: Bad local variable type
Exception Details:
  Location:
    Test$stateMachine$async$1.apply(Lscala/Option;)V @99: iload_2
  Reason:
    Type top (current frame, locals[2]) is not assignable to integer