From 5dbc37dfbe0e9a039da6744e45012abc3034cdf5 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Fri, 23 Aug 2013 14:23:48 +0200 Subject: SI-7779 Account for class name compactification in reflection We have to assume that the classes we are reflecting on were compiled with the default value for -Xmax-classfile-name (255). With this assumption, we can apply the same name compactification as done in the regular compiler. The REPL is particularly prone to generating long class names with the '$iw' prefixes, so this is an important fix for runtime reflection. Also adds support for getting the runtime class of `O.type` if `O` is a module. --- test/files/run/t7779.scala | 67 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 test/files/run/t7779.scala (limited to 'test/files/run') diff --git a/test/files/run/t7779.scala b/test/files/run/t7779.scala new file mode 100644 index 0000000000..db32cb751f --- /dev/null +++ b/test/files/run/t7779.scala @@ -0,0 +1,67 @@ +// -Xmax-classfile-length doesn't compress top-level classes. +// class ::::::::::::::::::::::::::::::::::::::::::::::::: + +trait Marker + +class Short extends Marker + +// We just test with member classes +object O { + object ::::::::::::::::::::::::::::::::::::::::::::::::: extends Marker +} +class C { + class D { + class ::::::::::::::::::::::::::::::::::::::::::::::::: extends Marker + } +} + +package pack { + // abbreviates to: $colon$colon$colon$colon$colon$colon$colon$colon$colon$colon$colon$colon$colon$colon$colon$colon$colon$colon$colon$colon$colon$colon$colon$colon$colon$colon$colon$colon$colon$colon$colon$colon$colon$colon$colon$colon$colon$colon$colon$colon$colon$colon$colon$colon$colon$colon$colon$colon$colon to $read$$iw$$iw$$colon$colon$colon$colon$colon$colon$colon$colon$$$$c39b3f245029fbed9732fc888d44231b$$$$on$colon$colon$colon$colon$colon$colon$colon$colon$colon$colon + // class ::::::::::::::::::::::::::::::::::::::::::::::::: + + class Short extends Marker + + // We just test with member classes + object O { + object ::::::::::::::::::::::::::::::::::::::::::::::::: extends Marker + } + class C { + class D { + class ::::::::::::::::::::::::::::::::::::::::::::::::: extends Marker + } + } + package p2 { + class Short extends Marker + + object O { + object ::::::::::::::::::::::::::::::::::::::::::::::::: extends Marker + } + class C { + class D { + class ::::::::::::::::::::::::::::::::::::::::::::::::: extends Marker + } + } + } +} + + +object Test extends App { + import reflect.runtime.universe._ + def test[T: TypeTag] = { + val tt = typeTag[T] + val clz = tt.mirror.runtimeClass(tt.tpe) + assert(classOf[Marker].isAssignableFrom(clz), clz.toString) + } + + test[Short] + test[O.:::::::::::::::::::::::::::::::::::::::::::::::::.type] + test[C#D#`:::::::::::::::::::::::::::::::::::::::::::::::::`] + + test[pack.Short] + test[pack.O.:::::::::::::::::::::::::::::::::::::::::::::::::.type] + test[pack.C#D#`:::::::::::::::::::::::::::::::::::::::::::::::::`] + + test[pack.p2.Short] + test[pack.p2.O.:::::::::::::::::::::::::::::::::::::::::::::::::.type] + test[pack.p2.C#D#`:::::::::::::::::::::::::::::::::::::::::::::::::`] +} -- cgit v1.2.3 From 9d5ed33ef9b503f20506dbe3e410960069a99d0a Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Fri, 23 Aug 2013 09:29:56 +0200 Subject: SI-7775 Harden against the shifting sands of System.getProperties If another thread writes a new system property (which can happen in pretty innocuous code such as `new Date`!), the compiler startup could fail with a `ConcurrentModificationException` as it iterated all bindings in the properties map in search of a boot classpath property for esoteric JVMs. This commit uses `Properties#getStringProperties` to get a snapshot of the keys that isn't backed by the live map, and iterates these instead. That method will also limit us to bindings with String values, which is all that we expect. --- .../scala/tools/reflect/WrappedProperties.scala | 8 ++++++-- test/files/run/t7775.scala | 17 +++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 test/files/run/t7775.scala (limited to 'test/files/run') diff --git a/src/compiler/scala/tools/reflect/WrappedProperties.scala b/src/compiler/scala/tools/reflect/WrappedProperties.scala index c34edb8ba0..7ce0171c0b 100644 --- a/src/compiler/scala/tools/reflect/WrappedProperties.scala +++ b/src/compiler/scala/tools/reflect/WrappedProperties.scala @@ -26,9 +26,13 @@ trait WrappedProperties extends PropertiesTrait { override def envOrElse(name: String, alt: String) = wrap(super.envOrElse(name, alt)) getOrElse alt override def envOrNone(name: String) = wrap(super.envOrNone(name)).flatten - def systemProperties: Iterator[(String, String)] = { + def systemProperties: List[(String, String)] = { import scala.collection.JavaConverters._ - wrap(System.getProperties.asScala.iterator) getOrElse Iterator.empty + wrap { + val props = System.getProperties + // SI-7269 Be careful to avoid `ConcurrentModificationException` if another thread modifies the properties map + props.stringPropertyNames().asScala.toList.map(k => (k, props.get(k).asInstanceOf[String])) + } getOrElse Nil } } diff --git a/test/files/run/t7775.scala b/test/files/run/t7775.scala new file mode 100644 index 0000000000..5fb0327611 --- /dev/null +++ b/test/files/run/t7775.scala @@ -0,0 +1,17 @@ +import scala.concurrent.{duration, future, Await, ExecutionContext} +import scala.tools.nsc.Settings +import ExecutionContext.Implicits.global + +// Was failing pretty regularly with a ConcurrentModificationException as +// WrappedProperties#systemProperties iterated directly over the mutable +// global system properties map. +object Test { + def main(args: Array[String]) { + val tries = 1000 // YMMV + val compiler = future { + for(_ <- 1 to tries) new Settings(_ => {}) + } + for(i <- 1 to tries * 10) System.setProperty(s"foo$i", i.toString) + Await.result(compiler, duration.Duration.Inf) + } +} -- cgit v1.2.3 From 9772ec8b2f15fd8c2971413d90006beb8bf0d2b7 Mon Sep 17 00:00:00 2001 From: Eugene Burmako Date: Thu, 29 Aug 2013 08:29:26 +0200 Subject: typedAnnotated no longer emits nulls Adds a null-check in original synthesis for the result of typedAnnotated. Previously it was possible for the aforementioned result to look like TypeTree() setOriginal Annotated(..., null). Not anymore. --- src/compiler/scala/tools/nsc/typechecker/Typers.scala | 2 +- test/files/run/typed-annotated.check | 1 + test/files/run/typed-annotated/Macros_1.scala | 17 +++++++++++++++++ test/files/run/typed-annotated/Test_2.scala | 3 +++ 4 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 test/files/run/typed-annotated.check create mode 100644 test/files/run/typed-annotated/Macros_1.scala create mode 100644 test/files/run/typed-annotated/Test_2.scala (limited to 'test/files/run') diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index bf2170310f..e27f540a7d 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -4107,7 +4107,7 @@ trait Typers extends Modes with Adaptations with Tags { // we need symbol-ful originals for reification // hence we go the extra mile to hand-craft tis guy val original = arg1 match { - case tt @ TypeTree() => Annotated(ann, tt.original) + case tt @ TypeTree() if tt.original != null => Annotated(ann, tt.original) // this clause is needed to correctly compile stuff like "new C @D" or "@(inline @getter)" case _ => Annotated(ann, arg1) } diff --git a/test/files/run/typed-annotated.check b/test/files/run/typed-annotated.check new file mode 100644 index 0000000000..d81cc0710e --- /dev/null +++ b/test/files/run/typed-annotated.check @@ -0,0 +1 @@ +42 diff --git a/test/files/run/typed-annotated/Macros_1.scala b/test/files/run/typed-annotated/Macros_1.scala new file mode 100644 index 0000000000..dd18c63d90 --- /dev/null +++ b/test/files/run/typed-annotated/Macros_1.scala @@ -0,0 +1,17 @@ +import scala.reflect.macros.Context +import language.experimental.macros + +class ann extends scala.annotation.StaticAnnotation + +object Macros { + def impl(c: Context) = { + import c.universe._ + // val tpt = Annotated(Apply(Select(New(Ident(newTypeName("ann"))), nme.CONSTRUCTOR), List()), Ident(newTypeName("Int"))) + val tpt = Annotated(Apply(Select(New(Ident(newTypeName("ann"))), nme.CONSTRUCTOR), List()), TypeTree(weakTypeOf[Int])) + c.Expr[Unit](Block( + List(ValDef(Modifiers(), newTermName("x"), tpt, Literal(Constant(42)))), + Apply(Ident(newTermName("println")), List(Ident(newTermName("x")))))) + } + + def foo = macro impl +} \ No newline at end of file diff --git a/test/files/run/typed-annotated/Test_2.scala b/test/files/run/typed-annotated/Test_2.scala new file mode 100644 index 0000000000..acfddae942 --- /dev/null +++ b/test/files/run/typed-annotated/Test_2.scala @@ -0,0 +1,3 @@ +object Test extends App { + Macros.foo +} \ No newline at end of file -- cgit v1.2.3 From 3ada7038877928711176da6ebde68528ab5fc39c Mon Sep 17 00:00:00 2001 From: Grzegorz Kossakowski Date: Tue, 3 Sep 2013 17:47:29 -0700 Subject: SI-7150 Replace scala.reflect.internal.WeakHashSet Replaces scala.reflect.internal.WeakHashSet with a version that * extends the mutable.Set trait * doesn't leak WeakReferences * is unit tested --- bincompat-backward.whitelist.conf | 8 + bincompat-forward.whitelist.conf | 800 +++++++++++++++++++++ .../scala/reflect/internal/util/WeakHashSet.scala | 453 ++++++++++-- test/files/run/WeakHashSetTest.scala | 174 +++++ 4 files changed, 1393 insertions(+), 42 deletions(-) create mode 100644 test/files/run/WeakHashSetTest.scala (limited to 'test/files/run') diff --git a/bincompat-backward.whitelist.conf b/bincompat-backward.whitelist.conf index 35b67a13ee..c016b52241 100644 --- a/bincompat-backward.whitelist.conf +++ b/bincompat-backward.whitelist.conf @@ -271,6 +271,14 @@ filter { { matchName="scala.reflect.internal.StdNames.compactifyName" problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet" + problemName=FinalClassProblem + }, + { + matchName="scala.reflect.internal.util.WeakReferenceWithEquals" + problemName=MissingClassProblem } ] } diff --git a/bincompat-forward.whitelist.conf b/bincompat-forward.whitelist.conf index 20e5e30b68..d1a19534db 100644 --- a/bincompat-forward.whitelist.conf +++ b/bincompat-forward.whitelist.conf @@ -583,6 +583,806 @@ filter { { matchName="scala.reflect.internal.StdNames.compactifyName" problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet$Diagnostics" + problemName=MissingClassProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet$Entry" + problemName=MissingClassProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet$" + problemName=MissingClassProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet" + problemName=MissingTypesProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.newBuilder" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.min" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.initialCapacity" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.foldLeft" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.toIterable" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.toIterable" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.union" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.union" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.groupBy" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.groupBy" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.toSet" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.toSet" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.toSeq" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.toSeq" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.toIndexedSeq" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.unzip3" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.nonEmpty" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.slice" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.max" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.addString" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.addString" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.addString" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.subsetOf" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.fold" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.toIterator" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.foreach" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.flatten" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.headOption" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.mapResult" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.scala$reflect$internal$util$WeakHashSet$$bucketFor" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.toTraversable" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.toTraversable" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.filter" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.tails" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.last" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.collect" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.takeRight" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.lastOption" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.reduceRightOption" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.+" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.+" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.+" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.+" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.+" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.+" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.take" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.zipWithIndex" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.foldRight" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.hasDefiniteSize" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.<<" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.sliceWithKnownBound" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.to" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.result" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.result" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.isTraversableAgain" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.add" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.partition" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.toBuffer" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.update" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.view" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.view" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.view" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.view" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.tail" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.zipAll" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.retain" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.find" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.withFilter" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.init" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.findEntryOrUpdate" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.diagnostics" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.zip" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.drop" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.:\\" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.companion" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.toMap" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.toMap" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.genericBuilder" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.unzip" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.seq" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.seq" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.seq" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.seq" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.seq" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.seq" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.seq" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.&~" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.toStream" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.-=" + problemName=IncompatibleResultTypeProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.-=" + problemName=IncompatibleResultTypeProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.-=" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.splitAt" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.addEntry" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.aggregate" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.parCombiner" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.maxBy" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.sliding" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.sliding" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.repr" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.repr" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.scan" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.span" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.toArray" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.findEntry" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.toVector" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.scala$collection$SetLike$$super$map" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.dropWhile" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.forall" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.reduce" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.intersect" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.this" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.--=" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.loadFactor" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.copyToArray" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.copyToArray" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.copyToArray" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.canEqual" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.inits" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.sliceWithKnownDelta" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.grouped" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.minBy" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet./:" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.--" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.--" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.sameElements" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.par" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.equals" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.sizeHint" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.sizeHint" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.sizeHint" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet./:\\" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.threshhold_=" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.++" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.++" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.++" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.map" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.clone" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.clone" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.diff" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.diff" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.isEmpty" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.&" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.head" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.toCollection" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.toCollection" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.++:" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.++:" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.mkString" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.mkString" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.mkString" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.threshhold" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.iterator" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.toList" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.-" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.-" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.-" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.-" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.-" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.-" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.-" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.++=" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.takeWhile" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.exists" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.scanRight" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.transpose" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.sizeHintBounded" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.hashCode" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.scala$collection$mutable$Cloneable$$super$clone" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.remove" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.|" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.reduceLeft" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.count" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.scanLeft" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.reduceLeftOption" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.dropRight" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.collectFirst" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.flatMap" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.+=" + problemName=IncompatibleResultTypeProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.+=" + problemName=IncompatibleResultTypeProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.+=" + problemName=IncompatibleResultTypeProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.+=" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.reversed" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.sum" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.filterNot" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.product" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.thisCollection" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.thisCollection" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.copyToBuffer" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.subsets" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.subsets" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.reduceRight" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.empty" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.empty" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.empty" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.stringPrefix" + problemName=MissingMethodProblem + }, + { + matchName="scala.reflect.internal.util.WeakHashSet.reduceOption" + problemName=MissingMethodProblem } ] } diff --git a/src/reflect/scala/reflect/internal/util/WeakHashSet.scala b/src/reflect/scala/reflect/internal/util/WeakHashSet.scala index 9882aad5e5..fc12e31360 100644 --- a/src/reflect/scala/reflect/internal/util/WeakHashSet.scala +++ b/src/reflect/scala/reflect/internal/util/WeakHashSet.scala @@ -1,61 +1,430 @@ -package scala.reflect.internal.util +package scala +package reflect.internal.util -import scala.collection.mutable -import scala.collection.mutable.ArrayBuffer -import scala.collection.mutable.Builder -import scala.collection.mutable.SetBuilder +import java.lang.ref.{WeakReference, ReferenceQueue} +import scala.annotation.tailrec import scala.collection.generic.Clearable -import scala.runtime.AbstractFunction1 +import scala.collection.mutable.{Set => mSet} -/** A bare-bones implementation of a mutable `Set` that uses weak references - * to hold the elements. +/** + * A HashSet where the elements are stored weakly. Elements in this set are elligible for GC if no other + * hard references are associated with them. Its primary use case is as a canonical reference + * identity holder (aka "hash-consing") via findEntryOrUpdate * - * This implementation offers only add/remove/test operations, - * therefore it does not fulfill the contract of Scala collection sets. + * This Set implementation cannot hold null. Any attempt to put a null in it will result in a NullPointerException + * + * This set implmeentation is not in general thread safe without external concurrency control. However it behaves + * properly when GC concurrently collects elements in this set. */ -class WeakHashSet[T <: AnyRef] extends AbstractFunction1[T, Boolean] with Clearable { - private val underlying = mutable.HashSet[WeakReferenceWithEquals[T]]() +final class WeakHashSet[A <: AnyRef](val initialCapacity: Int, val loadFactor: Double) extends Set[A] with Function1[A, Boolean] with mSet[A] { + + import WeakHashSet._ + + def this() = this(initialCapacity = WeakHashSet.defaultInitialCapacity, loadFactor = WeakHashSet.defaultLoadFactor) + + type This = WeakHashSet[A] + + /** + * queue of Entries that hold elements scheduled for GC + * the removeStaleEntries() method works through the queue to remeove + * stale entries from the table + */ + private[this] val queue = new ReferenceQueue[A] + + /** + * the number of elements in this set + */ + private[this] var count = 0 + + /** + * from a specified initial capacity compute the capacity we'll use as being the next + * power of two equal to or greater than the specified initial capacity + */ + private def computeCapacity = { + if (initialCapacity < 0) throw new IllegalArgumentException("initial capacity cannot be less than 0"); + var candidate = 1 + while (candidate < initialCapacity) { + candidate *= 2 + } + candidate + } + + /** + * the underlying table of entries which is an array of Entry linked lists + */ + private[this] var table = new Array[Entry[A]](computeCapacity) + + /** + * the limit at which we'll increase the size of the hash table + */ + var threshhold = computeThreshHold + + private[this] def computeThreshHold: Int = (table.size * loadFactor).ceil.toInt - /** Add the given element to this set. */ - def +=(elem: T): this.type = { - underlying += new WeakReferenceWithEquals(elem) - this + /** + * find the bucket associated with an elements's hash code + */ + private[this] def bucketFor(hash: Int): Int = { + // spread the bits around to try to avoid accidental collisions using the + // same algorithm as java.util.HashMap + var h = hash + h ^= h >>> 20 ^ h >>> 12 + h ^= h >>> 7 ^ h >>> 4 + + // this is finding h % table.length, but takes advantage of the + // fact that table length is a power of 2, + // if you don't do bit flipping in your head, if table.length + // is binary 100000.. (with n 0s) then table.length - 1 + // is 1111.. with n 1's. + // In other words this masks on the last n bits in the hash + h & (table.length - 1) } - /** Remove the given element from this set. */ - def -=(elem: T): this.type = { - underlying -= new WeakReferenceWithEquals(elem) - this + /** + * remove a single entry from a linked list in a given bucket + */ + private[this] def remove(bucket: Int, prevEntry: Entry[A], entry: Entry[A]) { + prevEntry match { + case null => table(bucket) = entry.tail + case _ => prevEntry.tail = entry.tail + } + count -= 1 } - /** Does the given element belong to this set? */ - def contains(elem: T): Boolean = - underlying.contains(new WeakReferenceWithEquals(elem)) + /** + * remove entries associated with elements that have been gc'ed + */ + private[this] def removeStaleEntries() { + def poll(): Entry[A] = queue.poll().asInstanceOf[Entry[A]] - /** Does the given element belong to this set? */ - def apply(elem: T): Boolean = contains(elem) + @tailrec + def queueLoop { + val stale = poll() + if (stale != null) { + val bucket = bucketFor(stale.hash) - /** Return the number of elements in this set, including reclaimed elements. */ - def size = underlying.size + @tailrec + def linkedListLoop(prevEntry: Entry[A], entry: Entry[A]): Unit = if (stale eq entry) remove(bucket, prevEntry, entry) + else if (entry != null) linkedListLoop(entry, entry.tail) - /** Remove all elements in this set. */ - def clear() = underlying.clear() -} + linkedListLoop(null, table(bucket)) -/** A WeakReference implementation that implements equals and hashCode by - * delegating to the referent. - */ -class WeakReferenceWithEquals[T <: AnyRef](ref: T) { - def get(): T = underlying.get() + queueLoop + } + } + + queueLoop + } + + /** + * Double the size of the internal table + */ + private[this] def resize() { + val oldTable = table + table = new Array[Entry[A]](oldTable.size * 2) + threshhold = computeThreshHold + + @tailrec + def tableLoop(oldBucket: Int): Unit = if (oldBucket < oldTable.size) { + @tailrec + def linkedListLoop(entry: Entry[A]): Unit = entry match { + case null => () + case _ => { + val bucket = bucketFor(entry.hash) + val oldNext = entry.tail + entry.tail = table(bucket) + table(bucket) = entry + linkedListLoop(oldNext) + } + } + linkedListLoop(oldTable(oldBucket)) + + tableLoop(oldBucket + 1) + } + tableLoop(0) + } + + // from scala.reflect.internal.Set, find an element or null if it isn't contained + override def findEntry(elem: A): A = elem match { + case null => throw new NullPointerException("WeakHashSet cannot hold nulls") + case _ => { + removeStaleEntries() + val hash = elem.hashCode + val bucket = bucketFor(hash) + + @tailrec + def linkedListLoop(entry: Entry[A]): A = entry match { + case null => null.asInstanceOf[A] + case _ => { + val entryElem = entry.get + if (elem == entryElem) entryElem + else linkedListLoop(entry.tail) + } + } + + linkedListLoop(table(bucket)) + } + } + // add an element to this set unless it's already in there and return the element + def findEntryOrUpdate(elem: A): A = elem match { + case null => throw new NullPointerException("WeakHashSet cannot hold nulls") + case _ => { + removeStaleEntries() + val hash = elem.hashCode + val bucket = bucketFor(hash) + val oldHead = table(bucket) + + def add() = { + table(bucket) = new Entry(elem, hash, oldHead, queue) + count += 1 + if (count > threshhold) resize() + elem + } + + @tailrec + def linkedListLoop(entry: Entry[A]): A = entry match { + case null => add() + case _ => { + val entryElem = entry.get + if (elem == entryElem) entryElem + else linkedListLoop(entry.tail) + } + } + + linkedListLoop(oldHead) + } + } + + // add an element to this set unless it's already in there and return this set + override def +(elem: A): this.type = elem match { + case null => throw new NullPointerException("WeakHashSet cannot hold nulls") + case _ => { + removeStaleEntries() + val hash = elem.hashCode + val bucket = bucketFor(hash) + val oldHead = table(bucket) + + def add() { + table(bucket) = new Entry(elem, hash, oldHead, queue) + count += 1 + if (count > threshhold) resize() + } + + @tailrec + def linkedListLoop(entry: Entry[A]): Unit = entry match { + case null => add() + case _ if (elem == entry.get) => () + case _ => linkedListLoop(entry.tail) + } + + linkedListLoop(oldHead) + this + } + } + + def +=(elem: A) = this + elem + + // from scala.reflect.interanl.Set + override def addEntry(x: A) { this += x } + + // remove an element from this set and return this set + override def -(elem: A): this.type = elem match { + case null => this + case _ => { + removeStaleEntries() + val bucket = bucketFor(elem.hashCode) - override val hashCode = ref.hashCode - override def equals(other: Any): Boolean = other match { - case wf: WeakReferenceWithEquals[_] => - underlying.get() == wf.get() - case _ => - false + + @tailrec + def linkedListLoop(prevEntry: Entry[A], entry: Entry[A]): Unit = entry match { + case null => () + case _ if (elem == entry.get) => remove(bucket, prevEntry, entry) + case _ => linkedListLoop(entry, entry.tail) + } + + linkedListLoop(null, table(bucket)) + this + } } - private val underlying = new java.lang.ref.WeakReference(ref) + def -=(elem: A) = this - elem + + // empty this set + override def clear(): Unit = { + table = new Array[Entry[A]](table.size) + threshhold = computeThreshHold + count = 0 + + // drain the queue - doesn't do anything because we're throwing away all the values anyway + @tailrec def queueLoop(): Unit = if (queue.poll() != null) queueLoop() + queueLoop() + } + + // true if this set is empty + override def empty: This = new WeakHashSet[A](initialCapacity, loadFactor) + + // the number of elements in this set + override def size: Int = { + removeStaleEntries() + count + } + + override def apply(x: A): Boolean = this contains x + + override def foreach[U](f: A => U): Unit = iterator foreach f + + override def toList(): List[A] = iterator.toList + + // Iterator over all the elements in this set in no particular order + override def iterator: Iterator[A] = { + removeStaleEntries() + + new Iterator[A] { + + /** + * the bucket currently being examined. Initially it's set past the last bucket and will be decremented + */ + private[this] var currentBucket: Int = table.size + + /** + * the entry that was last examined + */ + private[this] var entry: Entry[A] = null + + /** + * the element that will be the result of the next call to next() + */ + private[this] var lookaheadelement: A = null.asInstanceOf[A] + + @tailrec + def hasNext: Boolean = { + while (entry == null && currentBucket > 0) { + currentBucket -= 1 + entry = table(currentBucket) + } + + if (entry == null) false + else { + lookaheadelement = entry.get + if (lookaheadelement == null) { + // element null means the weakref has been cleared since we last did a removeStaleEntries(), move to the next entry + entry = entry.tail + hasNext + } else { + true + } + } + } + + def next(): A = if (lookaheadelement == null) + throw new IndexOutOfBoundsException("next on an empty iterator") + else { + val result = lookaheadelement + lookaheadelement = null.asInstanceOf[A] + entry = entry.tail + result + } + } + } + + /** + * Diagnostic information about the internals of this set. Not normally + * needed by ordinary code, but may be useful for diagnosing performance problems + */ + private[util] class Diagnostics { + /** + * Verify that the internal structure of this hash set is fully consistent. + * Throws an assertion error on any problem. In order for it to be reliable + * the entries must be stable. If any are garbage collected during validation + * then an assertion may inappropriately fire. + */ + def fullyValidate { + var computedCount = 0 + var bucket = 0 + while (bucket < table.size) { + var entry = table(bucket) + while (entry != null) { + assert(entry.get != null, s"$entry had a null value indicated that gc activity was happening during diagnostic validation or that a null value was inserted") + computedCount += 1 + val cachedHash = entry.hash + val realHash = entry.get.hashCode + assert(cachedHash == realHash, s"for $entry cached hash was $cachedHash but should have been $realHash") + val computedBucket = bucketFor(realHash) + assert(computedBucket == bucket, s"for $entry the computed bucket was $computedBucket but should have been $bucket") + + entry = entry.tail + } + + bucket += 1 + } + + assert(computedCount == count, s"The computed count was $computedCount but should have been $count") + } + + /** + * Produces a diagnostic dump of the table that underlies this hash set. + */ + def dump = table.deep + + /** + * Number of buckets that hold collisions. Useful for diagnosing performance issues. + */ + def collisionBucketsCount: Int = + (table filter (entry => entry != null && entry.tail != null)).size + + /** + * Number of buckets that are occupied in this hash table. + */ + def fullBucketsCount: Int = + (table filter (entry => entry != null)).size + + /** + * Number of buckets in the table + */ + def bucketsCount: Int = table.size + + /** + * Number of buckets that don't hold anything + */ + def emptyBucketsCount = bucketsCount - fullBucketsCount + + /** + * Number of elements that are in collision. Useful for diagnosing performance issues. + */ + def collisionsCount = size - (fullBucketsCount - collisionBucketsCount) + + /** + * A map from a count of elements to the number of buckets with that count + */ + def elementCountDistribution = table map linkedListSize groupBy identity map {case (size, list) => (size, list.size)} + + private def linkedListSize(entry: Entry[A]) = { + var e = entry + var count = 0 + while (e != null) { + count += 1 + e = e.tail + } + count + } + } + + private[util] def diagnostics = new Diagnostics +} + +/** + * Companion object for WeakHashSet + */ +object WeakHashSet { + /** + * A single entry in a WeakHashSet. It's a WeakReference plus a cached hash code and + * a link to the next Entry in the same bucket + */ + private class Entry[A](element: A, val hash:Int, var tail: Entry[A], queue: ReferenceQueue[A]) extends WeakReference[A](element, queue) + + val defaultInitialCapacity = 16 + val defaultLoadFactor = .75 + + def apply[A <: AnyRef](initialCapacity: Int = WeakHashSet.defaultInitialCapacity, loadFactor: Double = WeakHashSet.defaultLoadFactor) = new WeakHashSet[A](initialCapacity, defaultLoadFactor) } diff --git a/test/files/run/WeakHashSetTest.scala b/test/files/run/WeakHashSetTest.scala new file mode 100644 index 0000000000..3c8f380150 --- /dev/null +++ b/test/files/run/WeakHashSetTest.scala @@ -0,0 +1,174 @@ +object Test { + def main(args: Array[String]) { + val test = scala.reflect.internal.util.WeakHashSetTest + test.checkEmpty + test.checkPlusEquals + test.checkPlusEqualsCollisions + test.checkRehashing + test.checkRehashCollisions + test.checkFindOrUpdate + test.checkMinusEquals + test.checkMinusEqualsCollisions + test.checkClear + test.checkIterator + test.checkIteratorCollisions + + // This test is commented out because it relies on gc behavior which isn't reliable enough in an automated environment + // test.checkRemoveUnreferencedObjects + } +} + +// put the main test object in the same package as WeakHashSet because +// it uses the package private "diagnostics" method +package scala.reflect.internal.util { + + object WeakHashSetTest { + // a class guaranteed to provide hash collisions + case class Collider(x : String) extends Comparable[Collider] with Serializable { + override def hashCode = 0 + def compareTo(y : Collider) = this.x compareTo y.x + } + + // basic emptiness check + def checkEmpty { + val hs = new WeakHashSet[String]() + assert(hs.size == 0) + hs.diagnostics.fullyValidate + } + + // make sure += works + def checkPlusEquals { + val hs = new WeakHashSet[String]() + val elements = List("hello", "goodbye") + elements foreach (hs += _) + assert(hs.size == 2) + assert(hs contains "hello") + assert(hs contains "goodbye") + hs.diagnostics.fullyValidate + } + + // make sure += works when there are collisions + def checkPlusEqualsCollisions { + val hs = new WeakHashSet[Collider]() + val elements = List("hello", "goodbye") map Collider + elements foreach (hs += _) + assert(hs.size == 2) + assert(hs contains Collider("hello")) + assert(hs contains Collider("goodbye")) + hs.diagnostics.fullyValidate + } + + // add a large number of elements to force rehashing and then validate + def checkRehashing { + val size = 200 + val hs = new WeakHashSet[String]() + val elements = (0 until size).toList map ("a" + _) + elements foreach (hs += _) + elements foreach {i => assert(hs contains i)} + hs.diagnostics.fullyValidate + } + + // make sure rehashing works properly when the set is rehashed + def checkRehashCollisions { + val size = 200 + val hs = new WeakHashSet[Collider]() + val elements = (0 until size).toList map {x => Collider("a" + x)} + elements foreach (hs += _) + elements foreach {i => assert(hs contains i)} + hs.diagnostics.fullyValidate + } + + // test that unreferenced objects are removed + // not run in an automated environment because gc behavior can't be relied on + def checkRemoveUnreferencedObjects { + val size = 200 + val hs = new WeakHashSet[Collider]() + val elements = (0 until size).toList map {x => Collider("a" + x)} + elements foreach (hs += _) + // don't throw the following into a retained collection so gc + // can remove them + for (i <- 0 until size) { + hs += Collider("b" + i) + } + System.gc() + Thread.sleep(1000) + assert(hs.size == 200) + elements foreach {i => assert(hs contains i)} + for (i <- 0 until size) { + assert(!(hs contains Collider("b" + i))) + } + hs.diagnostics.fullyValidate + } + + // make sure findOrUpdate returns the originally entered element + def checkFindOrUpdate { + val size = 200 + val hs = new WeakHashSet[Collider]() + val elements = (0 until size).toList map {x => Collider("a" + x)} + elements foreach {x => assert(hs findEntryOrUpdate x eq x)} + for (i <- 0 until size) { + // when we do a lookup the result should be the same reference we + // original put in + assert(hs findEntryOrUpdate(Collider("a" + i)) eq elements(i)) + } + hs.diagnostics.fullyValidate + } + + // check -= functionality + def checkMinusEquals { + val hs = new WeakHashSet[String]() + val elements = List("hello", "goodbye") + elements foreach (hs += _) + hs -= "goodbye" + assert(hs.size == 1) + assert(hs contains "hello") + assert(!(hs contains "goodbye")) + hs.diagnostics.fullyValidate + } + + // check -= when there are collisions + def checkMinusEqualsCollisions { + val hs = new WeakHashSet[Collider] + val elements = List(Collider("hello"), Collider("goodbye")) + elements foreach (hs += _) + hs -= Collider("goodbye") + assert(hs.size == 1) + assert(hs contains Collider("hello")) + assert(!(hs contains Collider("goodbye"))) + hs -= Collider("hello") + assert(hs.size == 0) + assert(!(hs contains Collider("hello"))) + hs.diagnostics.fullyValidate + } + + // check that the clear method actually cleans everything + def checkClear { + val size = 200 + val hs = new WeakHashSet[String]() + val elements = (0 until size).toList map ("a" + _) + elements foreach (hs += _) + hs.clear() + assert(hs.size == 0) + elements foreach {i => assert(!(hs contains i))} + hs.diagnostics.fullyValidate + } + + // check that the iterator covers all the contents + def checkIterator { + val hs = new WeakHashSet[String]() + val elements = (0 until 20).toList map ("a" + _) + elements foreach (hs += _) + assert(elements.iterator.toList.sorted == elements.sorted) + hs.diagnostics.fullyValidate + } + + // check that the iterator covers all the contents even when there is a collision + def checkIteratorCollisions { + val hs = new WeakHashSet[Collider] + val elements = (0 until 20).toList map {x => Collider("a" + x)} + elements foreach (hs += _) + assert(elements.iterator.toList.sorted == elements.sorted) + hs.diagnostics.fullyValidate + } + } +} -- cgit v1.2.3