Skip to content

Commit 31b6523

Browse files
committed
Fix crash due to out-of-band return values using return keyword
See test tests/init-global/warn/return2.scala
1 parent a8759ab commit 31b6523

File tree

1 file changed

+34
-17
lines changed

1 file changed

+34
-17
lines changed

compiler/src/dotty/tools/dotc/transform/init/Objects.scala

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -653,8 +653,8 @@ class Objects(using Context @constructorOnly):
653653
* GC may only be performed from method call contexts --- otherwise, we need
654654
* to consider values of the current local environment as well.
655655
*/
656-
def gc(returnValue: Value, heapBefore: Data, heapAfter: Data, changeSet: Set[Addr], currentObj: ObjectRef): Data =
657-
val roots: Iterable[Addr | Value] = changeSet.toSeq :+ returnValue
656+
def gc(returnValues: List[Value], heapBefore: Data, heapAfter: Data, changeSet: Set[Addr], currentObj: ObjectRef): Data =
657+
val roots: Iterable[Addr | Value] = changeSet.toSeq ++ returnValues
658658
// reachable locations from the return value and change set
659659
val reachableKeys = reachableAddresses(roots, heapAfter, currentObj)
660660

@@ -674,24 +674,25 @@ class Objects(using Context @constructorOnly):
674674
val config = Config(thisV, summon[Env.Data], Heap.getHeapData())
675675
super.get(config, expr).map(_.value)
676676

677-
def cachedEval(thisV: ThisValue, expr: Tree, cacheResult: Boolean)(fun: Tree => Value)(using Heap.MutableData, Env.Data, State.Data): Value =
677+
def cachedEval(thisV: ThisValue, expr: Tree, ctx: EvalContext)(fun: Tree => Value)(using Heap.MutableData, Env.Data, State.Data, Returns.Data): Value =
678678
val env = summon[Env.Data]
679679
val heapBefore = Heap.getHeapData()
680680
val changeSetBefore = Heap.getChangeSet()
681-
// Only perform footprint optimization when cacheResult is true
681+
// Only perform footprint optimization for method context
682682
val footprint =
683-
if cacheResult then Heap.footprint(Heap.getHeapData(), thisV, env, State.currentObjectRef)
683+
if ctx == EvalContext.Method then Heap.footprint(Heap.getHeapData(), thisV, env, State.currentObjectRef)
684684
else heapBefore
685685
val config = Config(thisV, env, footprint)
686686

687687
Heap.update(footprint, changeSet = Set.empty)
688+
val cacheResult = ctx == EvalContext.Method || ctx == EvalContext.Function || ctx == EvalContext.LazyVal
688689
val result = super.cachedEval(config, expr, cacheResult, default = Res(Bottom, footprint, Set.empty)) { expr =>
689690
val value = fun(expr)
690691
val heapAfter = Heap.getHeapData()
691692
val changeSetNew = Heap.getChangeSet()
692-
// Only perform garbage collection when cacheResult is true
693+
// Only perform garbage collection for method context
693694
val heapGC =
694-
if cacheResult then Heap.gc(value, footprint, heapAfter, changeSetNew, State.currentObjectRef)
695+
if ctx == EvalContext then Heap.gc(value :: Returns.currentReturns, footprint, heapAfter, changeSetNew, State.currentObjectRef)
695696
else heapAfter
696697
Res(value, heapGC, changeSetNew)
697698
}
@@ -740,6 +741,17 @@ class Objects(using Context @constructorOnly):
740741
case None =>
741742
report.warning("[Internal error] Unhandled return for method " + meth + " in " + meth.owner.show + ". Trace:\n" + Trace.show, Trace.position)
742743

744+
/**
745+
* Return the current return values in a method context.
746+
*
747+
* Warning: only use this method if it is certain that a method body has just
748+
* finished before any other code is executed.
749+
*
750+
* Calling this method from lazy val or function context is problematic.
751+
*/
752+
def currentReturns(using data: Data): List[Value] =
753+
data.last.values.toList
754+
743755
type Contextual[T] = (Context, State.Data, Env.Data, Cache.Data, Heap.MutableData, Regions.Data, Returns.Data, Trace) ?=> T
744756

745757
// --------------------------- domain operations -----------------------------
@@ -875,7 +887,7 @@ class Objects(using Context @constructorOnly):
875887
val env2 = Env.of(ddef, args.map(_.value), outerEnv)
876888
extendTrace(ddef) {
877889
given Env.Data = env2
878-
cache.cachedEval(ref, ddef.rhs, cacheResult = true) { expr =>
890+
cache.cachedEval(ref, ddef.rhs, EvalContext.Method) { expr =>
879891
Returns.installHandler(meth)
880892
val res = cases(expr, thisV, cls)
881893
val returns = Returns.popHandler(meth)
@@ -904,7 +916,7 @@ class Objects(using Context @constructorOnly):
904916
case ddef: DefDef =>
905917
if meth.name == nme.apply then
906918
given Env.Data = Env.of(ddef, args.map(_.value), env)
907-
extendTrace(code) { eval(ddef.rhs, thisV, klass, cacheResult = true) }
919+
extendTrace(code) { eval(ddef.rhs, thisV, klass, EvalContext.Function) }
908920
else
909921
// The methods defined in `Any` and `AnyRef` are trivial and don't affect initialization.
910922
if meth.owner == defn.AnyClass || meth.owner == defn.ObjectClass then
@@ -919,7 +931,7 @@ class Objects(using Context @constructorOnly):
919931
case _ =>
920932
// by-name closure
921933
given Env.Data = env
922-
extendTrace(code) { eval(code, thisV, klass, cacheResult = true) }
934+
extendTrace(code) { eval(code, thisV, klass, EvalContext.Function) }
923935

924936
case ValueSet(vs) =>
925937
vs.map(v => call(v, meth, args, receiver, superType)).join
@@ -942,11 +954,11 @@ class Objects(using Context @constructorOnly):
942954
given Env.Data = Env.of(ddef, argValues, Env.NoEnv)
943955
if ctor.isPrimaryConstructor then
944956
val tpl = cls.defTree.asInstanceOf[TypeDef].rhs.asInstanceOf[Template]
945-
extendTrace(cls.defTree) { eval(tpl, ref, cls, cacheResult = true) }
957+
extendTrace(cls.defTree) { eval(tpl, ref, cls, EvalContext.Method) }
946958
else
947959
extendTrace(ddef) { // The return values for secondary constructors can be ignored
948960
Returns.installHandler(ctor)
949-
eval(ddef.rhs, ref, cls, cacheResult = true)
961+
eval(ddef.rhs, ref, cls, EvalContext.Method)
950962
Returns.popHandler(ctor)
951963
}
952964
else
@@ -977,7 +989,7 @@ class Objects(using Context @constructorOnly):
977989
given Env.Data = Env.emptyEnv(target.owner.asInstanceOf[ClassSymbol].primaryConstructor)
978990
if target.hasSource then
979991
val rhs = target.defTree.asInstanceOf[ValDef].rhs
980-
eval(rhs, ref, target.owner.asClass, cacheResult = true)
992+
eval(rhs, ref, target.owner.asClass, EvalContext.LazyVal)
981993
else
982994
Bottom
983995
else if target.exists then
@@ -1159,7 +1171,7 @@ class Objects(using Context @constructorOnly):
11591171
given Env.Data = env
11601172
if sym.is(Flags.Lazy) then
11611173
val rhs = sym.defTree.asInstanceOf[ValDef].rhs
1162-
eval(rhs, thisV, sym.enclosingClass.asClass, cacheResult = true)
1174+
eval(rhs, thisV, sym.enclosingClass.asClass, EvalContext.LazyVal)
11631175
else
11641176
// Assume forward reference check is doing a good job
11651177
val value = Env.valValue(sym)
@@ -1232,6 +1244,9 @@ class Objects(using Context @constructorOnly):
12321244
do
12331245
accessObject(classSym)
12341246

1247+
enum EvalContext:
1248+
case Method, Function, LazyVal, Other
1249+
12351250
/** Evaluate an expression with the given value for `this` in a given class `klass`
12361251
*
12371252
* Note that `klass` might be a super class of the object referred by `thisV`.
@@ -1250,10 +1265,10 @@ class Objects(using Context @constructorOnly):
12501265
* @param expr The expression to be evaluated.
12511266
* @param thisV The value for `C.this` where `C` is represented by the parameter `klass`.
12521267
* @param klass The enclosing class where the expression is located.
1253-
* @param cacheResult It is used to reduce the size of the cache.
1268+
* @param ctx The context where `eval` is called.
12541269
*/
1255-
def eval(expr: Tree, thisV: ThisValue, klass: ClassSymbol, cacheResult: Boolean = false): Contextual[Value] = log("evaluating " + expr.show + ", this = " + thisV.show + ", regions = " + Regions.show + " in " + klass.show, printer, (_: Value).show) {
1256-
cache.cachedEval(thisV, expr, cacheResult) { expr => cases(expr, thisV, klass) }
1270+
def eval(expr: Tree, thisV: ThisValue, klass: ClassSymbol, ctx: EvalContext = EvalContext.Other): Contextual[Value] = log("evaluating " + expr.show + ", this = " + thisV.show + ", regions = " + Regions.show + " in " + klass.show, printer, (_: Value).show) {
1271+
cache.cachedEval(thisV, expr, ctx) { expr => cases(expr, thisV, klass) }
12571272
}
12581273

12591274

@@ -1393,9 +1408,11 @@ class Objects(using Context @constructorOnly):
13931408
withTrace(trace2) { assign(receiver, lhs.symbol, widened, rhs.tpe) }
13941409

13951410
case closureDef(ddef) =>
1411+
// TODO: trim the environment and `thisV` to only captured locals
13961412
Fun(ddef, thisV, klass, summon[Env.Data])
13971413

13981414
case PolyFun(ddef) =>
1415+
// TODO: trim the environment and `thisV` to only captured locals
13991416
Fun(ddef, thisV, klass, summon[Env.Data])
14001417

14011418
case Block(stats, expr) =>

0 commit comments

Comments
 (0)