Skip to content

Commit 67e4d95

Browse files
committed
Fix thread safety issue in runtime reflection
Disable the cache of the `SubstMap` used in `Type.subst` when in the runtime reflection universe.
1 parent 65122fa commit 67e4d95

File tree

2 files changed

+105
-3
lines changed

2 files changed

+105
-3
lines changed

src/reflect/scala/reflect/internal/Types.scala

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,12 +111,11 @@ trait Types
111111
private object substTypeMapCache {
112112
private[this] var cached: SubstTypeMap = new SubstTypeMap(Nil, Nil)
113113

114-
def apply(from: List[Symbol], to: List[Type]): SubstTypeMap = {
114+
def apply(from: List[Symbol], to: List[Type]): SubstTypeMap = if (isCompilerUniverse) {
115115
if ((cached.from ne from) || (cached.to ne to))
116116
cached = new SubstTypeMap(from, to)
117-
118117
cached
119-
}
118+
} else new SubstTypeMap(from, to)
120119
}
121120

122121
/** The current skolemization level, needed for the algorithms
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/*
2+
* Scala (https://www.scala-lang.org)
3+
*
4+
* Copyright EPFL and Lightbend, Inc.
5+
*
6+
* Licensed under Apache License 2.0
7+
* (http://www.apache.org/licenses/LICENSE-2.0).
8+
*
9+
* See the NOTICE file distributed with this work for
10+
* additional information regarding copyright ownership.
11+
*/
12+
13+
package scala.reflect.runtime
14+
15+
import java.util.concurrent.{Callable, Executors}
16+
17+
import org.junit.Test
18+
19+
class ThreadSafetyTest {
20+
import scala.reflect.runtime.universe._
21+
sealed abstract class Instance(tp: Type)
22+
final case class ListInstance(tp: Type, elemInstance: Instance) extends Instance(appliedType(symbolOf[List[_]], tp :: Nil))
23+
24+
final case class DoubleInstance() extends Instance(typeOf[Double])
25+
26+
final case class StringInstance() extends Instance(typeOf[String])
27+
28+
final case class LowPriorityInstance(tpe: Type) extends Instance(tpe)
29+
30+
def classKeyOf[T: TypeTag]: String = {
31+
classKeyOf(typeOf[T])
32+
}
33+
def classKeyOf(tpe: Type): String = {
34+
tpe.typeSymbol.fullName
35+
}
36+
37+
class Lazy[T](thunk: () => T) {
38+
lazy val force: T = thunk()
39+
}
40+
41+
class Registry {
42+
private val cache = new java.util.concurrent.ConcurrentHashMap[Type, Lazy[Instance]]()
43+
def instance[T: TypeTag]: Lazy[Instance] = {
44+
instance(typeOf[T])
45+
}
46+
def instance(tpe: Type): Lazy[Instance] = {
47+
val value = keyOf(tpe)
48+
assert(value =:= tpe, (value, tpe))
49+
cache.computeIfAbsent(value, create(_))
50+
}
51+
52+
private def create(tpe: Type): Lazy[Instance] = {
53+
val key = classKeyOf(tpe)
54+
if (key == "scala.collection.immutable.List") {
55+
new Lazy(() => {val elemTpe = tpe.dealias.typeArgs.head; ListInstance(tpe.dealias, instance(elemTpe).force)})
56+
} else if (key == "scala.Double") {
57+
new Lazy(() => DoubleInstance())
58+
} else if (key == "java.lang.String") {
59+
new Lazy(() => StringInstance())
60+
} else {
61+
new Lazy(() => LowPriorityInstance(tpe))
62+
}
63+
}
64+
private def keyOf(tp: Type): universe.Type = {
65+
tp.map(_.dealias)
66+
}
67+
}
68+
69+
@Test
70+
def test(): Unit = {
71+
val executor = Executors.newFixedThreadPool(16)
72+
for (i <- (0 to 128)) {
73+
val registry = new Registry
74+
val is = List(
75+
(() => typeOf[List[Double]], "ListInstance(List[Double],DoubleInstance())"),
76+
(() => typeOf[List[String]], "ListInstance(List[String],StringInstance())"),
77+
(() => typeOf[List[List[Double]]], "ListInstance(List[List[Double]],ListInstance(List[Double],DoubleInstance()))"),
78+
(() => typeOf[List[List[List[Double]]]], "ListInstance(List[List[List[Double]]],ListInstance(List[List[Double]],ListInstance(List[Double],DoubleInstance())))"),
79+
(() => typeOf[List[List[List[Object]]]], "ListInstance(List[List[List[Object]]],ListInstance(List[List[Object]],ListInstance(List[Object],LowPriorityInstance(Object))))")
80+
)
81+
sealed abstract class Result
82+
case object Okay extends Result
83+
case class Failed(i: Int, tp: Type, expected: String, instance: String) extends Result
84+
def check(i: Int): Result = {
85+
val (f, expected) = is(i % is.size)
86+
val tp = f()
87+
val instance = registry.instance(tp).force
88+
if (instance.toString == expected) Okay else Failed(i, tp, expected, "[" + instance + "]")
89+
}
90+
val par = true
91+
val fs: List[Result] = if (par) Array.tabulate(32)(i =>
92+
executor.submit(new Callable[Result] {
93+
override def call(): Result = {
94+
check(i % is.size)
95+
}
96+
})).map(_.get).toList
97+
else is.indices.map(check).toList
98+
val fails = fs.filter(_ != Okay)
99+
assert(fails.isEmpty, "iteration " + i + ": " + fails.mkString("\n"))
100+
}
101+
executor.shutdownNow()
102+
}
103+
}

0 commit comments

Comments
 (0)