Skip to content

Commit 7895317

Browse files
authored
Merge pull request scala#11128 from som-snytt/test/8946-restore-memory-leak-test
Restore memory leak test for reflection
2 parents f041d2c + 2e6ee26 commit 7895317

File tree

4 files changed

+56
-6
lines changed

4 files changed

+56
-6
lines changed

src/library/scala/collection/immutable/HashMap.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1345,6 +1345,8 @@ private final class BitmapIndexedMapNode[K, +V](
13451345
override def hashCode(): Int =
13461346
throw new UnsupportedOperationException("Trie nodes do not support hashing.")
13471347

1348+
override def toString = s"${getClass.getName}@${Integer.toHexString(System.identityHashCode(this))}"
1349+
13481350
override def concat[V1 >: V](that: MapNode[K, V1], shift: Int): BitmapIndexedMapNode[K, V1] = that match {
13491351
case bm: BitmapIndexedMapNode[K, V] @unchecked =>
13501352
if (size == 0) return bm
@@ -2090,6 +2092,8 @@ private final class HashCollisionMapNode[K, +V ](
20902092
override def hashCode(): Int =
20912093
throw new UnsupportedOperationException("Trie nodes do not support hashing.")
20922094

2095+
override def toString = s"${getClass.getName}@${Integer.toHexString(System.identityHashCode(this))}"
2096+
20932097
override def cachedJavaKeySetHashCode: Int = size * hash
20942098

20952099
}

src/reflect/scala/reflect/runtime/ThreadLocalStorage.scala

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,23 @@
1313
package scala.reflect
1414
package runtime
1515

16-
import java.lang.Thread._
16+
import java.lang.Thread.currentThread
17+
import java.util.Collections.synchronizedMap
18+
import java.util.{WeakHashMap => jWeakHashMap}
1719

1820
private[reflect] trait ThreadLocalStorage {
1921
self: SymbolTable =>
2022

2123
// see a discussion at scala-internals for more information:
2224
// https://groups.google.com/group/scala-internals/browse_thread/thread/337ce68aa5e51f79
23-
trait ThreadLocalStorage[T] { def get: T; def set(newValue: T): Unit }
25+
trait ThreadLocalStorage[T] {
26+
def get: T
27+
def set(newValue: T): Unit
28+
}
2429
private class MyThreadLocalStorage[T](initialValue: => T) extends ThreadLocalStorage[T] {
25-
// TODO: how do we use org.cliffc.high_scale_lib.NonBlockingHashMap here?
26-
// (we would need a version that uses weak keys)
27-
private[this] val values = java.util.Collections.synchronizedMap(new java.util.WeakHashMap[Thread, T]())
30+
// consider a nonblocking map such as org.cliffc.high_scale_lib.NonBlockingHashMap
31+
// but we would need a version that uses weak keys
32+
private[this] val values = synchronizedMap(new jWeakHashMap[Thread, T])
2833
def get: T = {
2934
if (values containsKey currentThread) values.get(currentThread)
3035
else {

src/testkit/scala/tools/testkit/AssertUtil.scala

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,11 @@ object AssertUtil {
203203
def assertSameElements[A, B >: A](expected: Array[A], actual: Array[B], message: String): Unit =
204204
assertSameElements(expected.toIndexedSeq, actual.toIndexedSeq, message)
205205

206+
@nowarn("msg=This catches all Throwables")
207+
private def safeString(x: AnyRef): String =
208+
try String.valueOf(x)
209+
catch (t: Throwable) => t.getMessage
210+
206211
/** Value is not strongly reachable from roots after body is evaluated.
207212
*/
208213
def assertNotReachable[A <: AnyRef](a: => A, roots: AnyRef*)(body: => Unit): Unit = {
@@ -216,7 +221,8 @@ object AssertUtil {
216221
val o: AnyRef = stack.pop()
217222
if (o != null && !seen.containsKey(o)) {
218223
seen.put(o, ())
219-
assertFalse(s"Root $root held reference $o", ref.hasReferent(o))
224+
if (ref.hasReferent(o))
225+
fail(s"Root ${safeString(root)} held reference ${safeString(o)}")
220226
o match {
221227
case a: Array[AnyRef] =>
222228
a.foreach(e => if (e != null && !e.isInstanceOf[Reference[_]]) stack.push(e))

test/files/run/t8946.scala

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//> using jvm 17+
2+
//> using javaOpt --add-opens java.base/java.lang=ALL-UNNAMED
3+
//> using options -Xsource:3
4+
5+
import scala.jdk.CollectionConverters.*
6+
import scala.jdk.OptionConverters.*
7+
import scala.reflect.runtime.*, universe.*
8+
import scala.tools.testkit.AssertUtil.assertNotReachable
9+
import scala.tools.testkit.ReflectUtil.*
10+
11+
// `t8946 reflection subtype does not hold onto Thread`
12+
object Test extends App {
13+
CanOpener.deworm()
14+
val reflector = new Thread("scala.reflector") {
15+
override def run() = typeOf[List[String]] <:< typeOf[Seq[_]]
16+
}
17+
val joiner: Thread => Unit = { t =>
18+
t.start()
19+
t.join()
20+
}
21+
assertNotReachable(reflector, universe)(joiner(reflector))
22+
// without the fix to use WeakHashMap in ThreadLocalStorage:
23+
//AssertionError: Root scala.reflect.runtime.JavaUniverse@6ce86ce1 held reference Thread[#23,scala.reflector,5,]
24+
}
25+
26+
// Modules are a can of worms for tools which must tread through arbitrary packages.
27+
object CanOpener {
28+
def deworm(): Unit = {
29+
val True = java.lang.Boolean.TRUE
30+
val unnamed = getClass.getClassLoader.getUnnamedModule
31+
val method = getMethodAccessible[Module]("implAddExportsOrOpens")
32+
for (base <- ModuleLayer.boot.findModule("java.base").toScala; pkg <- base.getPackages.asScala)
33+
method.invokeAs[Unit](base, pkg, unnamed, True, True)
34+
}
35+
}

0 commit comments

Comments
 (0)