Skip to content

Overly-Eager Evaluation of By-Name Parameter in Inferred Partial Application #5610

Closed
@scabug

Description

@scabug
object Bug extends App {
  class Result(_str: => String) {
    lazy val str = _str
  }
  
  def foo(str: => String)(i: Int) = new Result(str)
  
  def bar(f: Int => Result) = f(42)
  
  var test: String = null
  val result = bar(foo(test))
  test = "bar"
  
  if (result.str == null) {
    println("Destroy ALL THE THINGS!!!")
  } else {
    println("Stroke a kitten")
  }
}

We would expect the output of this code would be "Stroke a kitten", since the value of test is given a non-null value before the str lazy val is realized. However, the actual printout is "Destroy ALL THE THINGS!!!", which isn't a polite thing to do.

The problem lies in the bytecode generated for bar(foo(test)). More specifically, the issue is in the lifting of the test actual for the foo invocation. Bytecode (from Bug$delayedInit$body.apply:()Ljava/lang/Object;):

      18: aload_0       
      19: getfield      #12                 // Field $outer:LBug$;
      22: invokevirtual #22                 // Method Bug$.test:()Ljava/lang/String;
      25: astore_1      
      26: new           #24                 // class Bug$$anonfun$1
      29: dup           
      30: aload_1       
      31: invokespecial #27                 // Method Bug$$anonfun$1."<init>":(Ljava/lang/String;)V

Notice instruction 22. We're getting a raw (un-thunked) value for test and then lifting it into the requisite closure for the foo(test) invocation.

Using an explicit thunk (of type () => String) rather than a by-name parameter for str avoids this issue.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions