From cb91343d2b02b038f6c297ba79a052e0758caae8 Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Tue, 28 Sep 2010 15:43:32 +0000 Subject: Fix and test case for #3855. 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. --- .../scala/tools/nsc/transform/UnCurry.scala | 36 +++++++++++++++++++++- test/files/run/bug3855.scala | 18 +++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 test/files/run/bug3855.scala diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala index 85db55472d..b7a70bdf4b 100644 --- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala +++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala @@ -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) diff --git a/test/files/run/bug3855.scala b/test/files/run/bug3855.scala new file mode 100644 index 0000000000..32dfb1e23d --- /dev/null +++ b/test/files/run/bug3855.scala @@ -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) + } +} -- cgit v1.2.3