Skip to content

Local optimisations inline too eagerly: System.out is final but can still be mutated by System.setOut #2772

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

Closed
smarter opened this issue Jun 15, 2017 · 1 comment

Comments

@smarter
Copy link
Member

smarter commented Jun 15, 2017

import java.io.OutputStream

object Test {
  def main(args: Array[String]): Unit = {
    val oldOut = System.out
    System.setOut(null)
    val a = () => foo(oldOut)
    a()
  }

  def foo(out: OutputStream): Unit = {
    out.write(0)
  }
}

Without -optimise this code works, with -optimise we get a NullPointerException.
Without -optimise:

result of try/inout.scala after genBCode:

package <empty> {
  @scala.annotation.internal.SourceFile("try/inout.scala") final module class 
    Test$
   extends Object { 
    def <init>(): Unit = 
      {
        super()
        ()
      }
    def main(args: String[]): Unit = 
      {
        val oldOut: java.io.PrintStream = System.out
        System.setOut(null)
        val a: Function0 = 
          closure(oldOut | Test.$anonfun$1:scala.compat.java8.JFunction0$mcV$sp)
        {
          a.apply()
          ()
        }
      }
    def foo(out: java.io.OutputStream): Unit = out.write(0)
    private def $anonfun$1(oldOut$1: java.io.PrintStream): Unit = 
      Test.foo(oldOut$1)
  }
  final lazy module val Test: Test$ = new Test$()
}

With -optimise:

result of try/inout.scala after genBCode:

package <empty> {
  @scala.annotation.internal.SourceFile("try/inout.scala") final module class 
    Test$
   extends Object { 
    def <init>(): Unit = 
      {
        super()
        ()
      }
    def main(args: String[]): Unit = 
      {
        System.setOut(null)
        val a: Function0 = 
          closure(Test.$anonfun$1:scala.compat.java8.JFunction0$mcV$sp)
        {
          a.apply()
          ()
        }
      }
    def foo(out: java.io.OutputStream): Unit = out.write(0)
    private def $anonfun$1(): Unit = Test.foo(System.out)
  }
  final lazy module val Test: Test$ = new Test$()
}

Notice that the anonymous function now calls System.out instead of out.

This happened in the Dotty Language Server (https://github.com/lampepfl/dotty/blob/master/language-server/src/dotty/tools/languageserver/Main.scala#L32-L35), the result is that outside projects cannot use the IDE with scalaVersion := 0.2.0-bin-20170614-8ddfcaf-NIGHTLY.

@smarter
Copy link
Member Author

smarter commented Jun 15, 2017

Apparently System.out/in/err are the only Java fields for which final does not mean final: https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.5.4

@smarter smarter changed the title Local optimisations inline too eagerly: System.out is final but still can be mutated by System.setOut Local optimisations inline too eagerly: System.out is final but can still be mutated by System.setOut Jun 18, 2017
@OlivierBlanvillain OlivierBlanvillain self-assigned this Jun 19, 2017
OlivierBlanvillain added a commit to dotty-staging/dotty that referenced this issue Jun 19, 2017
Also update DropNoEffects to keep Select on static java field, to make sure a
Java class doesn't end up in value position. That's another bug, unrelated to
smarter added a commit that referenced this issue Jun 25, 2017
Fix #2772: Special case Devalify for java.lang.System.*
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants