Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Miscompilation: When a try expression mutating a var is lifted, incorrect code is generated #7356

Closed
smarter opened this issue Oct 2, 2019 · 2 comments · Fixed by #7846
Closed

Comments

@smarter
Copy link
Member

smarter commented Oct 2, 2019

object Test {
  def main(args: Array[String]): Unit = {
    var x: String = "foo"
    identity(
      try {
        x = "bar"
      } catch {
        case ex =>
          throw ex
      }
    )
    println(x)
  }
}

This should print bar but it prints foo instead, because the generated code looks like this:

    def main(args: String[]): Unit = 
      {
        var x: String = "foo"
        identity(
          {
            this.liftedTree1(x)
          }
        )
        println(x)
      }
    private final def liftedTree1(x$1: String): 
      scala.runtime.BoxedUnit(scala.runtime.BoxedUnit.UNIT)
     = 
      try 
        {
          {
            x$1 = "bar"
            scala.runtime.BoxedUnit$#UNIT
          }
        }
       catch 
        {
          case ex @ _ => 
            throw ex
        }
  }

This is of course non-sensical (we're mutating a method parameter!) and is detected by the compiler under -Ycheck:all. Either LiftTry needs to be taught to handle vars, or it needs to be moved before CapturedVars.

@smarter smarter changed the title When a try expression mutating a var is lifted, incorrect code is generated Miscompilation: When a try expression mutating a var is lifted, incorrect code is generated Oct 2, 2019
@smarter smarter changed the title Miscompilation: When a try expression mutating a var is lifted, incorrect code is generated Miscompilation: When a try expression mutates a var is lifted, incorrect code is generated Oct 2, 2019
@smarter smarter changed the title Miscompilation: When a try expression mutates a var is lifted, incorrect code is generated Miscompilation: When a try expression mutating a var is lifted, incorrect code is generated Oct 2, 2019
@odersky
Copy link
Contributor

odersky commented Oct 4, 2019

I agree that LiftTry should be moved before CapturedVars. But then we run into this:

  // See tests/run/t2333.scala for an example where running after the group of LazyVals matters
  override def runsAfterGroupsOf: Set[String] = Set(LazyVals.name)

Since LazyVals and CapturedVars are in the same group we are out of choices. I looked at t2333.scala. The problem is that LazyVals generates this code:

        def b$lzyINIT1(): Int = 
          b$lzy1.synchronized[Int](
            if b$lzy1.initialized() then b$lzy1.value() else 
              b$lzy1.initialize(
                try 2 catch 
                  {
                    case _:Throwable => 0
                  }
              )
          )

and then LiftTry fails to lift out the try from the initialize call. The reason is that the question
whether to lift or not is determined by the context (i.e. where does the try appear?) and that
context is defined at the start of the miniphase group. At the start, it still looks like this:

lazy def b() = try ... 

which seems to indicate that no lifting is necessary. But once LazyVals did its job lifting is in fact necessary.

Possible remedies:

  1. Make LiftTry aware that a try in a lazy def has to be lifted, no matter what, or
  2. Tweak LazyVals to produce val x = try ... ; initialize(x) instead of initialize(try ... ).

Number (2) is the better fix, since it avoids unnecessary lifting.

@odersky
Copy link
Contributor

odersky commented Oct 4, 2019

Note that the algorithm for redesigned lazyvals #6979 would not have this problem. The initializing code is not passed as an argument there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants