summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon Ochsenreither <simon@ochsenreither.de>2013-01-25 22:44:50 +0100
committerSimon Ochsenreither <simon@ochsenreither.de>2013-01-25 22:48:12 +0100
commit4c627b715e196a90de1030e641cf4e3e015e772d (patch)
tree42ab98b32e4f25df03d407f1e2399567d7a73079
parent53d4ec0187416c72cd1f25d24c5811c7fd6574db (diff)
downloadscala-4c627b715e196a90de1030e641cf4e3e015e772d.tar.gz
scala-4c627b715e196a90de1030e641cf4e3e015e772d.tar.bz2
scala-4c627b715e196a90de1030e641cf4e3e015e772d.zip
[backport] SI-6969, mishandling of SoftReferences in method cache.
More interesting to test than it was to fix. The soft reference is now dereferenced once, the locally stored underlying value ascertained to be non-null, and the remainder of the references to the value use the local var. The enclosed test reliably NPEs without this patch.
-rw-r--r--src/compiler/scala/tools/nsc/transform/CleanUp.scala25
-rw-r--r--test/files/run/t6969.check1
-rw-r--r--test/files/run/t6969.scala28
3 files changed, 47 insertions, 7 deletions
diff --git a/src/compiler/scala/tools/nsc/transform/CleanUp.scala b/src/compiler/scala/tools/nsc/transform/CleanUp.scala
index aa829d61ab..64aecada83 100644
--- a/src/compiler/scala/tools/nsc/transform/CleanUp.scala
+++ b/src/compiler/scala/tools/nsc/transform/CleanUp.scala
@@ -227,12 +227,17 @@ abstract class CleanUp extends Transform with ast.TreeDSL {
var reflPoly$Cache: SoftReference[scala.runtime.MethodCache] = new SoftReference(new EmptyMethodCache())
def reflMethod$Method(forReceiver: JClass[_]): JMethod = {
- var method: JMethod = reflPoly$Cache.find(forReceiver)
- if (method != null)
+ var methodCache: MethodCache = reflPoly$Cache.find(forReceiver)
+ if (methodCache eq null) {
+ methodCache = new EmptyMethodCache
+ reflPoly$Cache = new SoftReference(methodCache)
+ }
+ var method: JMethod = methodCache.find(forReceiver)
+ if (method ne null)
return method
else {
method = ScalaRunTime.ensureAccessible(forReceiver.getMethod("xyz", reflParams$Cache))
- reflPoly$Cache = new SoftReference(reflPoly$Cache.get.add(forReceiver, method))
+ reflPoly$Cache = new SoftReference(methodCache.add(forReceiver, method))
return method
}
}
@@ -248,16 +253,22 @@ abstract class CleanUp extends Transform with ast.TreeDSL {
addStaticMethodToClass("reflMethod$Method", List(ClassClass.tpe), MethodClass.tpe)
{ case Pair(reflMethodSym, List(forReceiverSym)) =>
+ val methodCache = reflMethodSym.newVariable(ad.pos, mkTerm("methodCache")) setInfo MethodCacheClass.tpe
val methodSym = reflMethodSym.newVariable(ad.pos, mkTerm("method")) setInfo MethodClass.tpe
BLOCK(
- IF (getPolyCache OBJ_EQ NULL) THEN (safeREF(reflPolyCacheSym) === mkNewPolyCache) ENDIF,
- VAL(methodSym) === ((getPolyCache DOT methodCache_find)(REF(forReceiverSym))) ,
- IF (REF(methodSym) OBJ_!= NULL) .
+ VAR(methodCache) === getPolyCache,
+ IF (REF(methodCache) OBJ_EQ NULL) THEN BLOCK(
+ REF(methodCache) === NEW(TypeTree(EmptyMethodCacheClass.tpe)),
+ REF(reflPolyCacheSym) === gen.mkSoftRef(REF(methodCache))
+ ) ENDIF,
+
+ VAR(methodSym) === (REF(methodCache) DOT methodCache_find)(REF(forReceiverSym)),
+ IF (REF(methodSym) OBJ_NE NULL) .
THEN (Return(REF(methodSym)))
ELSE {
def methodSymRHS = ((REF(forReceiverSym) DOT Class_getMethod)(LIT(method), safeREF(reflParamsCacheSym)))
- def cacheRHS = ((getPolyCache DOT methodCache_add)(REF(forReceiverSym), REF(methodSym)))
+ def cacheRHS = ((REF(methodCache) DOT methodCache_add)(REF(forReceiverSym), REF(methodSym)))
BLOCK(
REF(methodSym) === (REF(ensureAccessibleMethod) APPLY (methodSymRHS)),
safeREF(reflPolyCacheSym) === gen.mkSoftRef(cacheRHS),
diff --git a/test/files/run/t6969.check b/test/files/run/t6969.check
new file mode 100644
index 0000000000..78297812c9
--- /dev/null
+++ b/test/files/run/t6969.check
@@ -0,0 +1 @@
+All threads completed.
diff --git a/test/files/run/t6969.scala b/test/files/run/t6969.scala
new file mode 100644
index 0000000000..50b252c8c8
--- /dev/null
+++ b/test/files/run/t6969.scala
@@ -0,0 +1,28 @@
+object Test {
+ private type Clearable = { def clear(): Unit }
+ private def choke() = {
+ try new Array[Object]((Runtime.getRuntime().maxMemory min Int.MaxValue).toInt)
+ catch {
+ case _: OutOfMemoryError => // what do you mean, out of memory?
+ case t: Throwable => println(t)
+ }
+ }
+ private def f(x: Clearable) = x.clear()
+ class Choker(id: Int) extends Thread {
+ private def g(iteration: Int) = {
+ val map = scala.collection.mutable.Map[Int, Int](1 -> 2)
+ try f(map) catch { case t: NullPointerException => println("Failed at "+id+"/"+iteration) ; throw t }
+ choke()
+ }
+ override def run() {
+ 1 to 50 foreach g
+ }
+ }
+
+ def main(args: Array[String]): Unit = {
+ val threads = 1 to 3 map (id => new Choker(id))
+ threads foreach (_.start())
+ threads foreach (_.join())
+ println("All threads completed.")
+ }
+}