Skip to content

Commit 0b52a51

Browse files
committed
SI-6863 Fix verify error in captured var inited from expr with try/catch
If a captured var was inited from a try/catch we did something reasonable. But if the var was inited from a more complicated expression (if/else, a block, match/case, etc) that ended with a try/catch then we didn't and we were generating faulty byte code. This fix patches LambdaLift to add the missing cases. For known simple expressions, the translation is just new *Ref(expr). For try/catch, if/else, match/case, and blocks this recursively walks down the internal result expressions to translate them. E.g. if(cond) trueExpr else falseExpr becomes if(cone) translate(trueExpr) else translate(falseExpr) For unknown expression types, the translation is {val temp = expr; new *Ref(expr) }
1 parent 2fa859e commit 0b52a51

File tree

3 files changed

+147
-10
lines changed

3 files changed

+147
-10
lines changed

src/compiler/scala/tools/nsc/transform/LambdaLift.scala

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -451,20 +451,45 @@ abstract class LambdaLift extends InfoTransform {
451451
}
452452
case arg => arg
453453
}
454-
/** Wrap expr argument in new *Ref(..) constructor, but make
455-
* sure that Try expressions stay at toplevel.
454+
455+
/** Wrap expr argument in new *Ref(..) constructor. But try/catch
456+
* is a problem because a throw will clear the stack and post catch
457+
* we would expect the partially-constructed object to be on the stack
458+
* for the call to init. So we recursively
459+
* search for "leaf" result expressions where we know its safe
460+
* to put the new *Ref(..) constructor or, if all else fails, transform
461+
* an expr to { val temp=expr; new *Ref(temp) }.
462+
* The reason we narrowly look for try/catch in captured var definitions
463+
* is because other try/catch expression have already been lifted
464+
* see SI-6863
456465
*/
457-
def refConstr(expr: Tree): Tree = expr match {
466+
def refConstr(expr: Tree): Tree = typer.typedPos(expr.pos) {expr match {
467+
// very simple expressions can be wrapped in a new *Ref(expr) because they can't have
468+
// a try/catch in final expression position.
469+
case Ident(_) | Apply(_, _) | Literal(_) | New(_) | Select(_, _) | Throw(_) | Assign(_, _) | ValDef(_, _, _, _) | Return(_) | EmptyTree =>
470+
New(sym.tpe, expr)
458471
case Try(block, catches, finalizer) =>
459472
Try(refConstr(block), catches map refConstrCase, finalizer)
473+
case Block(stats, expr) =>
474+
Block(stats, refConstr(expr))
475+
case If(cond, trueBranch, falseBranch) =>
476+
If(cond, refConstr(trueBranch), refConstr(falseBranch))
477+
case Match(selector, cases) =>
478+
Match(selector, cases map refConstrCase)
479+
// if we can't figure out what else to do, turn expr into {val temp1 = expr; new *Ref(temp1)} to avoid
480+
// any possibility of try/catch in the *Ref constructor. This should be a safe tranformation as a default
481+
// though it potentially wastes a variable slot. In particular this case handles LabelDefs.
460482
case _ =>
461-
New(sym.tpe, expr)
462-
}
483+
debuglog("assigning expr to temp: " + (expr.pos))
484+
val tempSym = currentOwner.newValue(unit.freshTermName("temp"), expr.pos) setInfo expr.tpe
485+
val tempDef = ValDef(tempSym, expr) setPos expr.pos
486+
val tempRef = Ident(tempSym) setPos expr.pos
487+
Block(tempDef, New(sym.tpe, tempRef))
488+
}}
463489
def refConstrCase(cdef: CaseDef): CaseDef =
464490
CaseDef(cdef.pat, cdef.guard, refConstr(cdef.body))
465-
treeCopy.ValDef(tree, mods, name, tpt1, typer.typedPos(rhs.pos) {
466-
refConstr(constructorArg)
467-
})
491+
492+
treeCopy.ValDef(tree, mods, name, tpt1, refConstr(constructorArg))
468493
} else tree
469494
case Return(Block(stats, value)) =>
470495
Block(stats, treeCopy.Return(tree, value)) setType tree.tpe setPos tree.pos

src/compiler/scala/tools/nsc/transform/UnCurry.scala

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -603,8 +603,6 @@ abstract class UnCurry extends InfoTransform
603603
}
604604
case ValDef(_, _, _, rhs) =>
605605
if (sym eq NoSymbol) throw new IllegalStateException("Encountered Valdef without symbol: "+ tree + " in "+ unit)
606-
// a local variable that is mutable and free somewhere later should be lifted
607-
// as lambda lifting (coming later) will wrap 'rhs' in an Ref object.
608606
if (!sym.owner.isSourceMethod)
609607
withNeedLift(true) { super.transform(tree) }
610608
else

test/files/run/t6863.scala

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/** Make sure that when a variable is captured its initialization expression is handled properly */
2+
object Test {
3+
def lazyVal() = {
4+
// internally lazy vals become vars which are initialized with "_", so they need to be tested just like vars do
5+
lazy val x = "42"
6+
assert({ () => x }.apply == "42")
7+
}
8+
def ident() = {
9+
val y = "42"
10+
var x = y
11+
assert({ () => x }.apply == "42")
12+
}
13+
def apply() = {
14+
def y(x : Int) = x.toString
15+
var x = y(42)
16+
assert({ () => x }.apply == "42")
17+
}
18+
def literal() = {
19+
var x = "42"
20+
assert({ () => x }.apply == "42")
21+
}
22+
def `new`() = {
23+
var x = new String("42")
24+
assert({ () => x }.apply == "42")
25+
}
26+
def select() = {
27+
object Foo{val bar = "42"}
28+
var x = Foo.bar
29+
assert({ () => x }.apply == "42")
30+
}
31+
def `throw`() = {
32+
var x = if (true) "42" else throw new Exception("42")
33+
assert({ () => x }.apply == "42")
34+
}
35+
def assign() = {
36+
var y = 1
37+
var x = y = 42
38+
assert({ () => x}.apply == ())
39+
}
40+
def valDef() = {
41+
var x = {val y = 42}
42+
assert({ () => x}.apply == ())
43+
}
44+
def `return`(): String = {
45+
var x = if (true) return "42" else ()
46+
assert({ () => x}.apply == ())
47+
"42"
48+
}
49+
def tryFinally() = {
50+
var x = try { "42" } finally ()
51+
assert({ () => x }.apply == "42")
52+
}
53+
def tryCatch() = {
54+
var x = try { "42" } catch { case _ => "43" }
55+
assert({ () => x }.apply == "42")
56+
}
57+
def `if`() = {
58+
var x = if (true) ()
59+
assert({ () => x }.apply == ())
60+
}
61+
def ifElse() = {
62+
var x = if(true) "42" else "43"
63+
assert({ () => x }.apply == "42")
64+
}
65+
def matchCase() = {
66+
var x = 100 match {
67+
case 100 => "42"
68+
case _ => "43"
69+
}
70+
assert({ () => x }.apply == "42")
71+
}
72+
def block() = {
73+
var x = {
74+
val y = 42
75+
"42"
76+
}
77+
assert({ () => x }.apply == "42")
78+
}
79+
def labelDef() = {
80+
var x = 100 match {
81+
case 100 => try "42" finally ()
82+
}
83+
assert({ () => x }.apply == "42")
84+
}
85+
def nested() = {
86+
var x = {
87+
val y = 42
88+
if(true) try "42" catch {case _ => "43"}
89+
else "44"
90+
}
91+
assert({ () => x }.apply == "42")
92+
}
93+
def main(args: Array[String]) {
94+
lazyVal()
95+
ident()
96+
apply()
97+
literal()
98+
`new`()
99+
select()
100+
`throw`()
101+
assign()
102+
valDef()
103+
`return`()
104+
tryFinally()
105+
tryCatch()
106+
ifElse()
107+
`if`()
108+
matchCase()
109+
block()
110+
labelDef()
111+
nested()
112+
}
113+
}
114+

0 commit comments

Comments
 (0)