Skip to content

Commit 2ef533f

Browse files
szeigerretronym
authored andcommitted
Use proxy-based serialization for modules
This avoids serializing any state of serializable singletons which would be discarded anyway after deserializing (SI-10412). Use ClassValue to cache instances, which is significantly faster for subsequent deserialization of a given module. ClassValue entries only weakly reference the key and value so the class can be unloaded. Wrap use of reflection in AccessControler to play nicely under SecurityManager. A security manager will only walk up the stack to this point to check permissions, so if users grant scala-library permissions in the policy, the call will be allowed regardless of the code higher in the stack that has triggered deserialization.
1 parent e99f3b9 commit 2ef533f

34 files changed

+162
-204
lines changed

src/compiler/scala/tools/nsc/typechecker/SyntheticMethods.scala

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ import symtab.Flags._
3636
* def toString(): String
3737
*
3838
* Special handling:
39-
* protected def readResolve(): AnyRef
39+
* protected def writeReplace(): AnyRef
4040
*/
4141
trait SyntheticMethods extends ast.TreeDSL {
4242
self: Analyzer =>
@@ -331,16 +331,17 @@ trait SyntheticMethods extends ast.TreeDSL {
331331
// Object_equals -> (() => createMethod(Object_equals)(m => This(clazz) ANY_EQ Ident(m.firstParam)))
332332
)
333333

334-
/* If you serialize a singleton and then deserialize it twice,
335-
* you will have two instances of your singleton unless you implement
336-
* readResolve. Here it is implemented for all objects which have
337-
* no implementation and which are marked serializable (which is true
338-
* for all case objects.)
334+
/* If you serialize a singleton you will get an additional
335+
* instance of the singleton, unless you implement
336+
* special serialization logic. Here we use a serialization proxy that prevents
337+
* serialization of state and will, on deserialization by replaced by the object
338+
* via use of readResolve. This is done for all top level objects which extend
339+
* `java.io.Serializable` (such as case objects)
339340
*/
340-
def needsReadResolve = (
341+
def needsModuleSerializationProxy = (
341342
clazz.isModuleClass
342343
&& clazz.isSerializable
343-
&& !hasConcreteImpl(nme.readResolve)
344+
&& !hasConcreteImpl(nme.writeReplace)
344345
&& clazz.isStatic
345346
)
346347

@@ -374,13 +375,14 @@ trait SyntheticMethods extends ast.TreeDSL {
374375
for ((m, impl) <- methods ; if shouldGenerate(m)) yield impl()
375376
}
376377
def extras = {
377-
if (needsReadResolve) {
378+
if (needsModuleSerializationProxy) {
378379
// Aha, I finally decoded the original comment.
379380
// This method should be generated as private, but apparently if it is, then
380381
// it is name mangled afterward. (Wonder why that is.) So it's only protected.
381-
// For sure special methods like "readResolve" should not be mangled.
382-
List(createMethod(nme.readResolve, Nil, ObjectTpe)(m => {
383-
m setFlag PRIVATE; REF(clazz.sourceModule)
382+
// For sure special methods like "writeReplace" should not be mangled.
383+
List(createMethod(nme.writeReplace, Nil, ObjectTpe)(m => {
384+
m setFlag PRIVATE
385+
New(ModuleSerializationProxyClass, gen.mkClassOf(clazz.typeOfThis))
384386
}))
385387
}
386388
else Nil

src/library/scala/collection/Iterator.scala

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1190,11 +1190,6 @@ object Iterator extends IterableFactory[Iterator] {
11901190
} else Iterator.empty.next()
11911191
}
11921192
}
1193-
1194-
// scalac generates a `readReplace` method to discard the deserialized state (see https://github.com/scala/bug/issues/10412).
1195-
// This prevents it from serializing it in the first place:
1196-
private[this] def writeObject(out: ObjectOutputStream): Unit = ()
1197-
private[this] def readObject(in: ObjectInputStream): Unit = ()
11981193
}
11991194

12001195
/** Explicit instantiation of the `Iterator` trait to reduce class file size in subclasses. */

src/library/scala/collection/View.scala

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -441,11 +441,6 @@ object View extends IterableFactory[View] {
441441
}
442442
override def isEmpty: Boolean = underlying.isEmpty && len <= 0
443443
}
444-
445-
// scalac generates a `readReplace` method to discard the deserialized state (see https://github.com/scala/bug/issues/10412).
446-
// This prevents it from serializing it in the first place:
447-
private[this] def writeObject(out: ObjectOutputStream): Unit = ()
448-
private[this] def readObject(in: ObjectInputStream): Unit = ()
449444
}
450445

451446
/** Explicit instantiation of the `View` trait to reduce class file size in subclasses. */

src/library/scala/collection/concurrent/TrieMap.scala

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1019,11 +1019,6 @@ object TrieMap extends MapFactory[TrieMap] {
10191019
class MangledHashing[K] extends Hashing[K] {
10201020
def hash(k: K)= scala.util.hashing.byteswap32(k.##)
10211021
}
1022-
1023-
// scalac generates a `readReplace` method to discard the deserialized state (see https://github.com/scala/bug/issues/10412).
1024-
// This prevents it from serializing it in the first place:
1025-
private[this] def writeObject(out: ObjectOutputStream): Unit = ()
1026-
private[this] def readObject(in: ObjectInputStream): Unit = ()
10271022
}
10281023

10291024

src/library/scala/collection/immutable/ArraySeq.scala

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -423,9 +423,4 @@ object ArraySeq extends StrictOptimizedClassTagSeqFactory[ArraySeq] { self =>
423423
case _ => super.equals(that)
424424
}
425425
}
426-
427-
// scalac generates a `readReplace` method to discard the deserialized state (see https://github.com/scala/bug/issues/10412).
428-
// This prevents it from serializing it in the first place:
429-
private[this] def writeObject(out: ObjectOutputStream): Unit = ()
430-
private[this] def readObject(in: ObjectInputStream): Unit = ()
431426
}

src/library/scala/collection/immutable/BitSet.scala

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -155,9 +155,4 @@ object BitSet extends SpecificIterableFactory[Int, BitSet] {
155155
private final class SerializationProxy(coll: BitSet) extends scala.collection.BitSet.SerializationProxy(coll) {
156156
protected[this] def readResolve(): Any = BitSet.fromBitMaskNoCopy(elems)
157157
}
158-
159-
// scalac generates a `readReplace` method to discard the deserialized state (see https://github.com/scala/bug/issues/10412).
160-
// This prevents it from serializing it in the first place:
161-
private[this] def writeObject(out: ObjectOutputStream): Unit = ()
162-
private[this] def readObject(in: ObjectInputStream): Unit = ()
163158
}

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

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1328,11 +1328,6 @@ object HashMap extends MapFactory[HashMap] {
13281328
}
13291329

13301330
def newBuilder[K, V]: Builder[(K, V), HashMap[K, V]] = new HashMapBuilder[K, V]
1331-
1332-
// scalac generates a `readReplace` method to discard the deserialized state (see https://github.com/scala/bug/issues/10412).
1333-
// This prevents it from serializing it in the first place:
1334-
private[this] def writeObject(out: ObjectOutputStream): Unit = ()
1335-
private[this] def readObject(in: ObjectInputStream): Unit = ()
13361331
}
13371332

13381333

src/library/scala/collection/immutable/HashSet.scala

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -789,11 +789,6 @@ object HashSet extends IterableFactory[HashSet] {
789789
}
790790

791791
def newBuilder[A]: Builder[A, HashSet[A]] = new HashSetBuilder
792-
793-
// scalac generates a `readReplace` method to discard the deserialized state (see https://github.com/scala/bug/issues/10412).
794-
// This prevents it from serializing it in the first place:
795-
private[this] def writeObject(out: ObjectOutputStream): Unit = ()
796-
private[this] def readObject(in: ObjectInputStream): Unit = ()
797792
}
798793

799794
private[collection] final class HashSetBuilder[A] extends Builder[A, HashSet[A]] {

src/library/scala/collection/immutable/IntMap.scala

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,6 @@ object IntMap {
104104

105105
implicit def iterableFactory[V]: Factory[(Int, V), IntMap[V]] = toFactory(this)
106106
implicit def buildFromIntMap[V]: BuildFrom[IntMap[_], (Int, V), IntMap[V]] = toBuildFrom(this)
107-
108-
// scalac generates a `readReplace` method to discard the deserialized state (see https://github.com/scala/bug/issues/10412).
109-
// This prevents it from serializing it in the first place:
110-
private[this] def writeObject(out: ObjectOutputStream): Unit = ()
111-
private[this] def readObject(in: ObjectInputStream): Unit = ()
112107
}
113108

114109
// Iterator over a non-empty IntMap.

src/library/scala/collection/immutable/LazyList.scala

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -869,11 +869,6 @@ object LazyList extends SeqFactory[LazyList] {
869869

870870
def newBuilder[A]: Builder[A, LazyList[A]] = ArrayBuffer.newBuilder[A].mapResult(array => from(array))
871871

872-
// scalac generates a `readReplace` method to discard the deserialized state (see https://github.com/scala/bug/issues/10412).
873-
// This prevents it from serializing it in the first place:
874-
private[this] def writeObject(out: ObjectOutputStream): Unit = ()
875-
private[this] def readObject(in: ObjectInputStream): Unit = ()
876-
877872
/** This serialization proxy is used for LazyLists which start with a sequence of evaluated cons cells.
878873
* The forced sequence is serialized in a compact, sequential format, followed by the unevaluated tail, which uses
879874
* standard Java serialization to store the complete structure of unevaluated thunks. This allows the serialization

0 commit comments

Comments
 (0)