Fix and test case for #3855. The uncurry stage now notices more

situations where a mutable var will later be lifted.  As a point of
interest, this bug reveals itself fairly clearly if you use a build
since r23112 and run the checker thusly:

  scalac -d /tmp -Ycheck-debug -Ycheck:icode -Xprint:icode test/files/run/bug3855.scala

It dies with the following explanation:

  Output changed for Block 3 [S: 2] [P: 1, 4]
  Exception in thread "main" scala.tools.nsc.backend.icode.CheckerException:
  Incompatible stacks: TypeStack() and TypeStack(2 elems) {
    REFERENCE(class IntRef)
    REFERENCE(class IntRef)
  } in Test.main at entry to block: 2

And indeed that was the source of the reported verifyerror.

Review by i. dragos.

git-svn-id: http://lampsvn.epfl.ch/svn-repos/scala/scala/trunk@23126 5e8d7ff9-d8ef-0310-90f0-a4852d11357a
This commit is contained in:
extempore 2010-09-28 15:43:32 +00:00
parent dd752c9e33
commit 3de373725e
2 changed files with 53 additions and 1 deletions

View File

@ -704,17 +704,51 @@ abstract class UnCurry extends InfoTransform with TypingTransformers {
/** Set of mutable local variables that are free in some inner method. */
private val freeMutableVars: mutable.Set[Symbol] = new mutable.HashSet
/** PP: There is apparently some degree of overlap between the CAPTURED
* flag and the role being filled here. I think this is how this was able
* to go for so long looking only at DefDef and Ident nodes, as bugs
* would only emerge under more complicated conditions such as #3855.
* I'll try to figure it all out, but if someone who already knows the
* whole story wants to fill it in, that too would be great.
*/
private val freeLocalsTraverser = new Traverser {
var currentMethod: Symbol = NoSymbol
var maybeEscaping = false
def withEscaping(body: => Unit) {
val savedEscaping = maybeEscaping
maybeEscaping = true
body
maybeEscaping = savedEscaping
}
override def traverse(tree: Tree) = tree match {
case DefDef(_, _, _, _, _, _) =>
val lastMethod = currentMethod
currentMethod = tree.symbol
super.traverse(tree)
currentMethod = lastMethod
/** A method call with a by-name parameter represents escape. */
case Apply(fn, args) if fn.symbol.paramss.nonEmpty =>
traverse(fn)
(fn.symbol.paramss.head zip args) foreach {
case (param, arg) =>
if (param.tpe != null && param.tpe.typeSymbol == ByNameParamClass)
withEscaping(traverse(arg))
else
traverse(arg)
}
/** The rhs of a closure represents escape. */
case Function(vparams, body) =>
vparams foreach traverse
withEscaping(traverse(body))
/** The appearance of an ident outside the method where it was defined or
* anytime maybeEscaping is true implies escape.
*/
case Ident(_) =>
val sym = tree.symbol
if (sym.isVariable && sym.owner.isMethod && sym.owner != currentMethod)
if (sym.isVariable && sym.owner.isMethod && (maybeEscaping || sym.owner != currentMethod))
freeMutableVars += sym
case _ =>
super.traverse(tree)

View File

@ -0,0 +1,18 @@
object Test {
def byval[A](a: => A) = a
def closure[A](f: () => A) = f()
def f1(s: String) = {
var n = try { s.toInt } catch { case _ => 1 }
byval(n)
}
def f2(s: String) = {
var n = try { s.toInt } catch { case _ => 1 }
closure(() => n)
}
def main(args: Array[String]) = {
val sum = f1("12") + f2("the witch is dead")
assert(sum == 13)
}
}