Skip to content

Commit 709bb01

Browse files
committed
Merge pull request scala#1393 from scalamacros/topic/leaks-in-toolboxes
SI-6412 alleviates leaks in toolboxes, attempt #2
2 parents 1d072fc + 8c7d94e commit 709bb01

File tree

5 files changed

+166
-90
lines changed

5 files changed

+166
-90
lines changed

src/compiler/scala/tools/nsc/Global.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1079,12 +1079,12 @@ class Global(var currentSettings: Settings, var reporter: Reporter)
10791079
* of what file was being compiled when it broke. Since I really
10801080
* really want to know, this hack.
10811081
*/
1082-
private var lastSeenSourceFile: SourceFile = NoSourceFile
1082+
protected var lastSeenSourceFile: SourceFile = NoSourceFile
10831083

10841084
/** Let's share a lot more about why we crash all over the place.
10851085
* People will be very grateful.
10861086
*/
1087-
private var lastSeenContext: analyzer.Context = null
1087+
protected var lastSeenContext: analyzer.Context = null
10881088

10891089
/** The currently active run
10901090
*/

src/compiler/scala/tools/reflect/ToolBoxFactory.scala

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,20 @@ abstract class ToolBoxFactory[U <: JavaUniverse](val u: U) { factorySelf =>
4646
newTermName("__wrapper$" + wrapCount + "$" + java.util.UUID.randomUUID.toString.replace("-", ""))
4747
}
4848

49+
// should be called after every use of ToolBoxGlobal in order to prevent leaks
50+
// there's the `withCleanupCaches` method defined below, which provides a convenient interface for that
51+
def cleanupCaches(): Unit = {
52+
perRunCaches.clearAll()
53+
undoLog.clear()
54+
analyzer.lastTreeToTyper = EmptyTree
55+
lastSeenSourceFile = NoSourceFile
56+
lastSeenContext = null
57+
}
58+
59+
def withCleanupCaches[T](body: => T): T =
60+
try body
61+
finally cleanupCaches()
62+
4963
def verify(expr: Tree): Unit = {
5064
// Previously toolboxes used to typecheck their inputs before compiling.
5165
// Actually, the initial demo by Martin first typechecked the reified tree,
@@ -337,7 +351,7 @@ abstract class ToolBoxFactory[U <: JavaUniverse](val u: U) { factorySelf =>
337351
lazy val importer = compiler.mkImporter(u)
338352
lazy val exporter = importer.reverse
339353

340-
def typeCheck(tree: u.Tree, expectedType: u.Type, silent: Boolean = false, withImplicitViewsDisabled: Boolean = false, withMacrosDisabled: Boolean = false): u.Tree = {
354+
def typeCheck(tree: u.Tree, expectedType: u.Type, silent: Boolean = false, withImplicitViewsDisabled: Boolean = false, withMacrosDisabled: Boolean = false): u.Tree = compiler.withCleanupCaches {
341355
if (compiler.settings.verbose.value) println("importing "+tree+", expectedType = "+expectedType)
342356
var ctree: compiler.Tree = importer.importTree(tree)
343357
var cexpectedType: compiler.Type = importer.importType(expectedType)
@@ -357,7 +371,7 @@ abstract class ToolBoxFactory[U <: JavaUniverse](val u: U) { factorySelf =>
357371
inferImplicit(tree, viewTpe, isView = true, silent = silent, withMacrosDisabled = withMacrosDisabled, pos = pos)
358372
}
359373

360-
private def inferImplicit(tree: u.Tree, pt: u.Type, isView: Boolean, silent: Boolean, withMacrosDisabled: Boolean, pos: u.Position): u.Tree = {
374+
private def inferImplicit(tree: u.Tree, pt: u.Type, isView: Boolean, silent: Boolean, withMacrosDisabled: Boolean, pos: u.Position): u.Tree = compiler.withCleanupCaches {
361375
if (compiler.settings.verbose.value) println("importing "+pt, ", tree = "+tree+", pos = "+pos)
362376
var ctree: compiler.Tree = importer.importTree(tree)
363377
var cpt: compiler.Type = importer.importType(pt)

src/reflect/scala/reflect/internal/Importers.scala

Lines changed: 96 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package scala.reflect
22
package internal
3+
34
import scala.collection.mutable.WeakHashMap
5+
import scala.ref.WeakReference
46

57
// SI-6241: move importers to a mirror
68
trait Importers extends api.Importers { self: SymbolTable =>
@@ -26,8 +28,12 @@ trait Importers extends api.Importers { self: SymbolTable =>
2628

2729
val from: SymbolTable
2830

29-
lazy val symMap: WeakHashMap[from.Symbol, Symbol] = new WeakHashMap
30-
lazy val tpeMap: WeakHashMap[from.Type, Type] = new WeakHashMap
31+
protected lazy val symMap = new Cache[from.Symbol, Symbol]()
32+
protected lazy val tpeMap = new Cache[from.Type, Type]()
33+
protected class Cache[K <: AnyRef, V <: AnyRef] extends WeakHashMap[K, WeakReference[V]] {
34+
def weakGet(key: K): Option[V] = this get key flatMap WeakReference.unapply
35+
def weakUpdate(key: K, value: V) = this.update(key, WeakReference(value))
36+
}
3137

3238
// fixups and maps prevent stackoverflows in importer
3339
var pendingSyms = 0
@@ -44,79 +50,81 @@ trait Importers extends api.Importers { self: SymbolTable =>
4450

4551
object reverse extends from.StandardImporter {
4652
val from: self.type = self
47-
for ((fromsym, mysym) <- StandardImporter.this.symMap) symMap += ((mysym, fromsym))
48-
for ((fromtpe, mytpe) <- StandardImporter.this.tpeMap) tpeMap += ((mytpe, fromtpe))
53+
// FIXME this and reverse should be constantly kept in sync
54+
// not just synced once upon the first usage of reverse
55+
for ((fromsym, WeakReference(mysym)) <- StandardImporter.this.symMap) symMap += ((mysym, WeakReference(fromsym)))
56+
for ((fromtpe, WeakReference(mytpe)) <- StandardImporter.this.tpeMap) tpeMap += ((mytpe, WeakReference(fromtpe)))
4957
}
5058

5159
// todo. careful import of positions
5260
def importPosition(pos: from.Position): Position =
5361
pos.asInstanceOf[Position]
5462

5563
def importSymbol(sym0: from.Symbol): Symbol = {
56-
def doImport(sym: from.Symbol): Symbol = {
57-
if (symMap.contains(sym))
58-
return symMap(sym)
59-
60-
val myowner = importSymbol(sym.owner)
61-
val mypos = importPosition(sym.pos)
62-
val myname = importName(sym.name).toTermName
63-
val myflags = sym.flags
64-
def linkReferenced(mysym: TermSymbol, x: from.TermSymbol, op: from.Symbol => Symbol): Symbol = {
65-
symMap(x) = mysym
66-
mysym.referenced = op(x.referenced)
67-
mysym
68-
}
69-
val mysym = sym match {
70-
case x: from.MethodSymbol =>
71-
linkReferenced(myowner.newMethod(myname, mypos, myflags), x, importSymbol)
72-
case x: from.ModuleSymbol =>
73-
linkReferenced(myowner.newModuleSymbol(myname, mypos, myflags), x, importSymbol)
74-
case x: from.FreeTermSymbol =>
75-
newFreeTermSymbol(importName(x.name).toTermName, x.value, x.flags, x.origin) setInfo importType(x.info)
76-
case x: from.FreeTypeSymbol =>
77-
newFreeTypeSymbol(importName(x.name).toTypeName, x.flags, x.origin)
78-
case x: from.TermSymbol =>
79-
linkReferenced(myowner.newValue(myname, mypos, myflags), x, importSymbol)
80-
case x: from.TypeSkolem =>
81-
val origin = x.unpackLocation match {
82-
case null => null
83-
case y: from.Tree => importTree(y)
84-
case y: from.Symbol => importSymbol(y)
64+
def doImport(sym: from.Symbol): Symbol =
65+
symMap weakGet sym match {
66+
case Some(result) => result
67+
case _ =>
68+
val myowner = importSymbol(sym.owner)
69+
val mypos = importPosition(sym.pos)
70+
val myname = importName(sym.name).toTermName
71+
val myflags = sym.flags
72+
def linkReferenced(mysym: TermSymbol, x: from.TermSymbol, op: from.Symbol => Symbol): Symbol = {
73+
symMap.weakUpdate(x, mysym)
74+
mysym.referenced = op(x.referenced)
75+
mysym
8576
}
86-
myowner.newTypeSkolemSymbol(myname.toTypeName, origin, mypos, myflags)
87-
case x: from.ModuleClassSymbol =>
88-
val mysym = myowner.newModuleClass(myname.toTypeName, mypos, myflags)
89-
symMap(x) = mysym
90-
mysym.sourceModule = importSymbol(x.sourceModule)
91-
mysym
92-
case x: from.ClassSymbol =>
93-
val mysym = myowner.newClassSymbol(myname.toTypeName, mypos, myflags)
94-
symMap(x) = mysym
95-
if (sym.thisSym != sym) {
96-
mysym.typeOfThis = importType(sym.typeOfThis)
97-
mysym.thisSym setName importName(sym.thisSym.name)
77+
val mysym = sym match {
78+
case x: from.MethodSymbol =>
79+
linkReferenced(myowner.newMethod(myname, mypos, myflags), x, importSymbol)
80+
case x: from.ModuleSymbol =>
81+
linkReferenced(myowner.newModuleSymbol(myname, mypos, myflags), x, importSymbol)
82+
case x: from.FreeTermSymbol =>
83+
newFreeTermSymbol(importName(x.name).toTermName, x.value, x.flags, x.origin) setInfo importType(x.info)
84+
case x: from.FreeTypeSymbol =>
85+
newFreeTypeSymbol(importName(x.name).toTypeName, x.flags, x.origin)
86+
case x: from.TermSymbol =>
87+
linkReferenced(myowner.newValue(myname, mypos, myflags), x, importSymbol)
88+
case x: from.TypeSkolem =>
89+
val origin = x.unpackLocation match {
90+
case null => null
91+
case y: from.Tree => importTree(y)
92+
case y: from.Symbol => importSymbol(y)
93+
}
94+
myowner.newTypeSkolemSymbol(myname.toTypeName, origin, mypos, myflags)
95+
case x: from.ModuleClassSymbol =>
96+
val mysym = myowner.newModuleClass(myname.toTypeName, mypos, myflags)
97+
symMap.weakUpdate(x, mysym)
98+
mysym.sourceModule = importSymbol(x.sourceModule)
99+
mysym
100+
case x: from.ClassSymbol =>
101+
val mysym = myowner.newClassSymbol(myname.toTypeName, mypos, myflags)
102+
symMap.weakUpdate(x, mysym)
103+
if (sym.thisSym != sym) {
104+
mysym.typeOfThis = importType(sym.typeOfThis)
105+
mysym.thisSym setName importName(sym.thisSym.name)
106+
}
107+
mysym
108+
case x: from.TypeSymbol =>
109+
myowner.newTypeSymbol(myname.toTypeName, mypos, myflags)
98110
}
99-
mysym
100-
case x: from.TypeSymbol =>
101-
myowner.newTypeSymbol(myname.toTypeName, mypos, myflags)
102-
}
103-
symMap(sym) = mysym
104-
mysym setFlag Flags.LOCKED
105-
mysym setInfo {
106-
val mytypeParams = sym.typeParams map importSymbol
107-
new LazyPolyType(mytypeParams) with FlagAgnosticCompleter {
108-
override def complete(s: Symbol) {
109-
val result = sym.info match {
110-
case from.PolyType(_, res) => res
111-
case result => result
111+
symMap.weakUpdate(sym, mysym)
112+
mysym setFlag Flags.LOCKED
113+
mysym setInfo {
114+
val mytypeParams = sym.typeParams map importSymbol
115+
new LazyPolyType(mytypeParams) with FlagAgnosticCompleter {
116+
override def complete(s: Symbol) {
117+
val result = sym.info match {
118+
case from.PolyType(_, res) => res
119+
case result => result
120+
}
121+
s setInfo GenPolyType(mytypeParams, importType(result))
122+
s setAnnotations (sym.annotations map importAnnotationInfo)
123+
}
112124
}
113-
s setInfo GenPolyType(mytypeParams, importType(result))
114-
s setAnnotations (sym.annotations map importAnnotationInfo)
115125
}
116-
}
117-
}
118-
mysym resetFlag Flags.LOCKED
119-
} // end doImport
126+
mysym resetFlag Flags.LOCKED
127+
} // end doImport
120128

121129
def importOrRelink: Symbol = {
122130
val sym = sym0 // makes sym visible in the debugger
@@ -186,17 +194,18 @@ trait Importers extends api.Importers { self: SymbolTable =>
186194
} // end importOrRelink
187195

188196
val sym = sym0
189-
if (symMap contains sym) {
190-
symMap(sym)
191-
} else {
192-
pendingSyms += 1
193-
194-
try {
195-
symMap getOrElseUpdate (sym, importOrRelink)
196-
} finally {
197-
pendingSyms -= 1
198-
tryFixup()
199-
}
197+
symMap.weakGet(sym) match {
198+
case Some(result) => result
199+
case None =>
200+
pendingSyms += 1
201+
try {
202+
val result = importOrRelink
203+
symMap.weakUpdate(sym, result)
204+
result
205+
} finally {
206+
pendingSyms -= 1
207+
tryFixup()
208+
}
200209
}
201210
}
202211

@@ -258,17 +267,18 @@ trait Importers extends api.Importers { self: SymbolTable =>
258267
def importOrRelink: Type =
259268
doImport(tpe)
260269

261-
if (tpeMap contains tpe) {
262-
tpeMap(tpe)
263-
} else {
264-
pendingTpes += 1
265-
266-
try {
267-
tpeMap getOrElseUpdate (tpe, importOrRelink)
268-
} finally {
269-
pendingTpes -= 1
270-
tryFixup()
271-
}
270+
tpeMap.weakGet(tpe) match {
271+
case Some(result) => result
272+
case None =>
273+
pendingTpes += 1
274+
try {
275+
val result = importOrRelink
276+
tpeMap.weakUpdate(tpe, result)
277+
result
278+
} finally {
279+
pendingTpes -= 1
280+
tryFixup()
281+
}
272282
}
273283
}
274284

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import scala.tools.partest.MemoryTest
2+
3+
trait A { type T <: A }
4+
trait B { type T <: B }
5+
6+
object Test extends MemoryTest {
7+
lazy val tb = {
8+
import scala.reflect.runtime.universe._
9+
import scala.reflect.runtime.{currentMirror => cm}
10+
import scala.tools.reflect.ToolBox
11+
cm.mkToolBox()
12+
}
13+
14+
override def maxDelta = 10
15+
override def calcsPerIter = 8
16+
override def calc() {
17+
var snippet = """
18+
trait A { type T <: A }
19+
trait B { type T <: B }
20+
def foo[T](x: List[T]) = x
21+
foo(List(new A {}, new B {}))
22+
""".trim
23+
snippet = snippet + "\n" + (List.fill(50)(snippet.split("\n").last) mkString "\n")
24+
tb.typeCheck(tb.parse(snippet))
25+
}
26+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import scala.tools.partest.MemoryTest
2+
3+
trait A { type T <: A }
4+
trait B { type T <: B }
5+
6+
object Test extends MemoryTest {
7+
lazy val tb = {
8+
import scala.reflect.runtime.universe._
9+
import scala.reflect.runtime.{currentMirror => cm}
10+
import scala.tools.reflect.ToolBox
11+
cm.mkToolBox()
12+
}
13+
14+
override def maxDelta = 10
15+
override def calcsPerIter = 3
16+
override def calc() {
17+
var snippet = """
18+
trait A { type T <: A }
19+
trait B { type T <: B }
20+
def foo[T](x: List[T]) = x
21+
foo(List(new A {}, new B {}))
22+
""".trim
23+
snippet = snippet + "\n" + (List.fill(50)(snippet.split("\n").last) mkString "\n")
24+
tb.eval(tb.parse(snippet))
25+
}
26+
}

0 commit comments

Comments
 (0)