Skip to content

Commit 955c560

Browse files
lrytzsmarter
authored andcommitted
Fix returns from within finalizers
When a return in a finalizer was reached through a return within the try block, the backend ignored the return in the finalizer: try { try { return 1 } finally { return 2 } } finally { println() } This expression should evaluate to 2 (it does in 2.11.8), but in 2.12.0 it the result is 1. The Scala spec is currently incomplete, it does not say that a finalizer should be exectuted if a return occurs within a try block, and it does not specify what happens if also the finally block has a return. So we follow the Java spec, which basically says: if the finally blocks completes abruptly for reason S, then the entire try statement completes abruptly with reason S. An abrupt termination of the try block for a different reason R is discarded. Abrupt completion is basically returning or throwing.
1 parent 289e48e commit 955c560

File tree

5 files changed

+101
-26
lines changed

5 files changed

+101
-26
lines changed

compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -563,16 +563,11 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
563563
bc emitRETURN returnType
564564
case nextCleanup :: rest =>
565565
if (saveReturnValue) {
566-
if (insideCleanupBlock) {
567-
int.warning(r.pos, "Return statement found in finally-clause, discarding its return-value in favor of that of a more deeply nested return.")
568-
bc drop returnType
569-
} else {
570-
// regarding return value, the protocol is: in place of a `return-stmt`, a sequence of `adapt, store, jump` are inserted.
571-
if (earlyReturnVar == null) {
572-
earlyReturnVar = locals.makeLocal(returnType, "earlyReturnVar", expr.tpe, expr.pos)
573-
}
574-
locals.store(earlyReturnVar)
566+
// regarding return value, the protocol is: in place of a `return-stmt`, a sequence of `adapt, store, jump` are inserted.
567+
if (earlyReturnVar == null) {
568+
earlyReturnVar = locals.makeLocal(returnType, "earlyReturnVar", expr.tpe, expr.pos)
575569
}
570+
locals.store(earlyReturnVar)
576571
}
577572
bc goTo nextCleanup
578573
shouldEmitCleanup = true

compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,6 @@ trait BCodeSkelBuilder extends BCodeHelpers {
255255
// used by genLoadTry() and genSynchronized()
256256
var earlyReturnVar: Symbol = null
257257
var shouldEmitCleanup = false
258-
var insideCleanupBlock = false
259258
// line numbers
260259
var lastEmittedLineNr = -1
261260

compiler/src/dotty/tools/backend/jvm/BCodeSyncAndTry.scala

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -341,13 +341,10 @@ trait BCodeSyncAndTry extends BCodeBodyBuilder {
341341
// `shouldEmitCleanup` can be set, and at the same time this try expression may lack a finally-clause.
342342
// In other words, all combinations of (hasFinally, shouldEmitCleanup) are valid.
343343
if (hasFinally && currentFinallyBlockNeedsCleanup) {
344-
val savedInsideCleanup = insideCleanupBlock
345-
insideCleanupBlock = true
346344
markProgramPoint(finCleanup)
347345
// regarding return value, the protocol is: in place of a `return-stmt`, a sequence of `adapt, store, jump` are inserted.
348346
emitFinalizer(finalizer, null, isDuplicate = true)
349347
pendingCleanups()
350-
insideCleanupBlock = savedInsideCleanup
351348
}
352349

353350
/* ------ (4) finally-clause-for-normal-nonEarlyReturn-exit

tests/run/t10032.check

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
t10032.scala:72: warning: Return statement found in finally-clause, discarding its return-value in favor of that of a more deeply nested return.
2-
finally { return i2 }
3-
^
41
t1
52
i1
63
a1
@@ -22,6 +19,9 @@ t3
2219
t4
2320
i1
2421
i2
22+
t4
23+
e1
24+
i2
2525
t5
2626
i1
2727
a1
@@ -35,6 +35,10 @@ t6
3535
i1
3636
i2
3737
i3
38+
t6
39+
e1
40+
i2
41+
i3
3842
t7
3943
i1
4044
a1
@@ -47,3 +51,32 @@ t8
4751
i2
4852
a1
4953
a2
54+
t8
55+
e1
56+
i2
57+
a1
58+
a2
59+
t9
60+
i1
61+
i2
62+
a1
63+
t9
64+
e1
65+
i2
66+
a1
67+
t10
68+
i1
69+
i2
70+
i3
71+
t10
72+
e1
73+
i2
74+
i3
75+
t11
76+
i1
77+
i2
78+
a1
79+
t11
80+
e1
81+
i2
82+
a1

tests/run/t10032.scala

Lines changed: 61 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ object Test extends App {
2424
try { return i1 }
2525
finally { a1() }
2626
} finally {
27-
try { a2() } finally { a3() }
27+
try { a2() }
28+
finally { a3() }
2829
}
2930
}
3031

@@ -42,10 +43,10 @@ object Test extends App {
4243
}
4344
}
4445

45-
def t4: Int = {
46+
def t4(i: => Int): Int = {
4647
println("t4")
4748
try {
48-
return i1
49+
return i
4950
} finally {
5051
return i2
5152
}
@@ -65,10 +66,10 @@ object Test extends App {
6566
}
6667
}
6768

68-
def t6: Int = {
69+
def t6(i: => Int): Int = {
6970
println("t6")
7071
try {
71-
try { return i1 }
72+
try { return i }
7273
finally { return i2 }
7374
} finally {
7475
return i3
@@ -86,10 +87,10 @@ object Test extends App {
8687
}
8788
}
8889

89-
def t8(): Int = {
90+
def t8(i: => Int): Int = {
9091
println("t8")
9192
try {
92-
try { i1 }
93+
try { i }
9394
finally { // no cleanup version
9495
try { return i2 }
9596
finally { a1() } // cleanup version required
@@ -99,15 +100,65 @@ object Test extends App {
99100
}
100101
}
101102

103+
def t9(i: => Int): Int = {
104+
println("t9")
105+
try {
106+
return i
107+
} finally {
108+
try { return i2 }
109+
finally { a1() }
110+
}
111+
}
112+
113+
def t10(i: => Int): Int = {
114+
println("t10")
115+
try {
116+
return i
117+
} finally {
118+
try { return i2 }
119+
finally { return i3 }
120+
}
121+
}
122+
123+
// this changed semantics between 2.12.0 and 2.12.1, see https://github.com/scala/scala/pull/5509#issuecomment-259291609
124+
def t11(i: => Int): Int = {
125+
println("t11")
126+
try {
127+
try { return i }
128+
finally { return i2 }
129+
} finally {
130+
a1()
131+
}
132+
}
133+
102134
assert(t1 == 1)
135+
103136
assert(t2 == 1)
137+
104138
assert(t3(i1) == 1)
105139
assert(t3(e1) == 2)
106-
assert(t4 == 2)
140+
141+
assert(t4(i1) == 2)
142+
assert(t4(e1) == 2)
143+
107144
assert(t5(i1) == 1)
108145
assert(t5(e1) == 2)
109-
assert(t6 == 3)
146+
147+
assert(t6(i1) == 3)
148+
assert(t6(e1) == 3)
149+
110150
assert(t7(i1) == 1)
111151
assert(t7(e1) == 2)
112-
assert(t8 == 2)
152+
153+
assert(t8(i1) == 2)
154+
assert(t8(e1) == 2)
155+
156+
assert(t9(i1) == 2)
157+
assert(t9(e1) == 2)
158+
159+
assert(t10(i1) == 3)
160+
assert(t10(e1) == 3)
161+
162+
assert(t11(i1) == 2)
163+
assert(t11(e1) == 2)
113164
}

0 commit comments

Comments
 (0)