From fe9a3e9c5ef2d2de5fa62a14fa4a613a573e5e5f Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Thu, 22 Aug 2013 23:51:15 +0200 Subject: SI-7269 Rework MapLike#retains to account for desugaring change `MapLike#retains` contains a for-comprehension that relied on the strict `filter` by its generator. You can't, in general, iterate a mutable map and remove items in the same pass. Here's the history of the desugaring of: def retain[A, B](thiz: mutable.Map[A, B])(p: (A, B) => Boolean): thiz.type = { thiz.foreach { case (k, v) => if (p(k, v)) thiz -= k } Before regression (c82ecabad6~1): thiz.filter(((check$ifrefutable$1) => check$ifrefutable$1: @scala.unchecked match { case scala.Tuple2((k @ _), (v @ _)) => true case _ => false })).withFilter(((x$1) => x$1: @scala.unchecked match { case scala.Tuple2((k @ _), (v @ _)) => p(k, v).unary_$bang })).foreach(((x$2) => x$2: @scala.unchecked match { case scala.Tuple2((k @ _), (v @ _)) => thiz.$minus$eq(k) })); After regression (c82ecabad6, which incorrectly assumed in the parser that no filter is required for isInstanceOf[Tuple2]) thiz.withFilter(((x$1) => x$1: @scala.unchecked match { case scala.Tuple2((k @ _), (v @ _)) => p(k, v).unary_$bang })).foreach(((x$2) => x$2: @scala.unchecked match { case scala.Tuple2((k @ _), (v @ _)) => thiz.$minus$eq(k) })); After the reversion of c82ecabad6, v2.10.2 This is also after 365bb2b4e, which uses `withFilter` rather than `filter`. thiz.withFilter(((check$q$1) => check$ifrefutable$1: @scala.unchecked match { case scala.Tuple2((k @ _), (v @ _)) => true case _ => false })).withFilter(((x$1) => x$1: @scala.unchecked match { case scala.Tuple2((k @ _), (v @ _)) => p(k, v).unary_$bang })).foreach(((x$2) => x$2: @scala.unchecked match { case scala.Tuple2((k @ _), (v @ _)) => thiz.$minus$eq(k) })); This commit does the same as `SetLike#retains`, and converts the map to an immutable list before the rest of the operation. --- src/library/scala/collection/mutable/MapLike.scala | 4 +-- src/library/scala/collection/mutable/SetLike.scala | 4 ++- test/files/run/t7269.scala | 32 ++++++++++++++++++++++ 3 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 test/files/run/t7269.scala diff --git a/src/library/scala/collection/mutable/MapLike.scala b/src/library/scala/collection/mutable/MapLike.scala index a53aa3b76a..42e5a0a4c4 100644 --- a/src/library/scala/collection/mutable/MapLike.scala +++ b/src/library/scala/collection/mutable/MapLike.scala @@ -209,8 +209,8 @@ trait MapLike[A, B, +This <: MapLike[A, B, This] with Map[A, B]] * @param p The test predicate */ def retain(p: (A, B) => Boolean): this.type = { - for ((k, v) <- this.seq ; if !p(k, v)) - this -= k + for ((k, v) <- this.toList) // SI-7269 toList avoids ConcurrentModificationException + if (!p(k, v)) this -= k this } diff --git a/src/library/scala/collection/mutable/SetLike.scala b/src/library/scala/collection/mutable/SetLike.scala index 01f87447ae..71da4c89dc 100644 --- a/src/library/scala/collection/mutable/SetLike.scala +++ b/src/library/scala/collection/mutable/SetLike.scala @@ -120,7 +120,9 @@ trait SetLike[A, +This <: SetLike[A, This] with Set[A]] * which `p` returns `true` are retained in the set; all others * are removed. */ - def retain(p: A => Boolean): Unit = for (elem <- this.toList) if (!p(elem)) this -= elem + def retain(p: A => Boolean): Unit = + for (elem <- this.toList) // SI-7269 toList avoids ConcurrentModificationException + if (!p(elem)) this -= elem /** Removes all elements from the set. After this operation is completed, * the set will be empty. diff --git a/test/files/run/t7269.scala b/test/files/run/t7269.scala new file mode 100644 index 0000000000..d22e57dfee --- /dev/null +++ b/test/files/run/t7269.scala @@ -0,0 +1,32 @@ +import scala.collection.JavaConversions._ +import scala.collection.mutable + +object Test extends App { + + def testMap(): Unit = { + val mapJ = new java.util.HashMap[Int, String] + val mapS: mutable.Map[Int, String] = mapJ + + (10 to 20).foreach(i => mapS += ((i, i.toString))) + assert(11 == mapS.size) + + // ConcurrentModificationException thrown in the following line + mapS.retain((i, str) => i % 2 == 0) + assert(6 == mapS.size) + } + + def testSet(): Unit = { + val mapJ = new java.util.HashSet[Int] + val mapS: mutable.Set[Int] = mapJ + + (10 to 20).foreach(i => mapS += i) + assert(11 == mapS.size) + + // ConcurrentModificationException thrown in the following line + mapS.retain((i) => i % 2 == 0) + assert(6 == mapS.size) + } + + testSet() + testMap() +} -- cgit v1.2.3 From 8b10daf2cb4367cd2565bd67efd2b6ead94ca18e Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Thu, 29 Aug 2013 12:33:37 -0700 Subject: [nomaster] SI-7652 Bad tools fails loudly A brief message is all that's required to alleviate the look of consternation on the face of your user. --- src/compiler/scala/tools/util/Javap.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/scala/tools/util/Javap.scala b/src/compiler/scala/tools/util/Javap.scala index c3264d0787..21137aca56 100644 --- a/src/compiler/scala/tools/util/Javap.scala +++ b/src/compiler/scala/tools/util/Javap.scala @@ -50,7 +50,7 @@ class JavapClass( tryFile(path) getOrElse tryClass(path) def apply(args: Seq[String]): List[JpResult] = { - if (failed) Nil + if (failed) List(new JpError("Could not load javap tool. Check that JAVA_HOME is correct.")) else args.toList filterNot (_ startsWith "-") map { path => val bytes = findBytes(path) if (bytes.isEmpty) new JpError("Could not find class bytes for '%s'".format(path)) -- cgit v1.2.3 From 7804ceca432c1d7d6ce3a669828ac0d3f6ffeac4 Mon Sep 17 00:00:00 2001 From: Som Snytt Date: Thu, 29 Aug 2013 12:47:46 -0700 Subject: [nomaster] SI-7652 REPL extended quest for tools Javap tries harder and fails louder when looking for tools.jar This is a backport of the method as it was first coded for partest. If JAVA_HOME is wrong, it will either fail with a message or succeed after rooting about. ``` apm@mara:~/tmp/q$ JAVA_HOME=$JAVA7_HOME JAVACMD='/usr/lib/jvm/java-6-openjdk-amd64/bin/java' /home/apm/projects/snytt/build/pack/bin/scala Welcome to Scala version 2.10.3-20130829-123337-59d6568daa (OpenJDK 64-Bit Server VM, Java 1.6.0_27). Type in expressions to have them evaluated. Type :help for more information. scala> :javap :javap [-lcsvp] [path1 path2 ...] scala> :javap java.lang.Object Failed: Could not load javap tool. Check that JAVA_HOME is correct. apm@mara:~/tmp/q$ JAVA_HOME=/usr/lib/jvm/java-6-openjdk-amd64/jre JAVACMD='/usr/lib/jvm/java-6-openjdk-amd64/bin/java' /home/apm/projects/snytt/build/pack/bin/scala Welcome to Scala version 2.10.3-20130829-123337-59d6568daa (OpenJDK 64-Bit Server VM, Java 1.6.0_27). Type in expressions to have them evaluated. Type :help for more information. scala> scala> :javap :javap [-lcsvp] [path1 path2 ...] scala> :javap java.lang.Object Compiled from "Object.java" ``` --- .../scala/tools/nsc/interpreter/ILoop.scala | 35 ++++++++++++++-------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/src/compiler/scala/tools/nsc/interpreter/ILoop.scala b/src/compiler/scala/tools/nsc/interpreter/ILoop.scala index b7e07ecdd6..6aef72a3b8 100644 --- a/src/compiler/scala/tools/nsc/interpreter/ILoop.scala +++ b/src/compiler/scala/tools/nsc/interpreter/ILoop.scala @@ -11,14 +11,14 @@ import java.io.{ BufferedReader, FileReader } import java.util.concurrent.locks.ReentrantLock import scala.sys.process.Process import session._ -import scala.util.Properties.{ jdkHome, javaVersion } +import scala.util.Properties.{ envOrNone, javaHome, jdkHome, javaVersion } import scala.tools.util.{ Javap } import scala.annotation.tailrec import scala.collection.mutable.ListBuffer import scala.concurrent.ops import util.{ ClassPath, Exceptional, stringFromWriter, stringFromStream } import interpreter._ -import io.{ File, Directory } +import io.{ File, Directory, Path } import scala.reflect.NameTransformer._ import util.ScalaClassLoader import ScalaClassLoader._ @@ -373,18 +373,29 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) } } - private def findToolsJar() = { - val jdkPath = Directory(jdkHome) - val jar = jdkPath / "lib" / "tools.jar" toFile; + private[this] lazy val platformTools: Option[File] = { + val jarName = "tools.jar" + def jarPath(path: Path) = (path / "lib" / jarName).toFile + def jarAt(path: Path) = { + val f = jarPath(path) + if (f.isFile) Some(f) else None + } + val jdkDir = { + val d = Directory(jdkHome) + if (d.isDirectory) Some(d) else None + } + def deeply(dir: Directory) = dir.deepFiles find (_.name == jarName) - if (jar isFile) - Some(jar) - else if (jdkPath.isDirectory) - jdkPath.deepFiles find (_.name == "tools.jar") - else None - } + val home = envOrNone("JDK_HOME") orElse envOrNone("JAVA_HOME") map (p => Path(p)) + val install = Some(Path(javaHome)) + + (home flatMap jarAt) orElse + (install flatMap jarAt) orElse + (install map (_.parent) flatMap jarAt) orElse + (jdkDir flatMap deeply) + } private def addToolsJarToLoader() = { - val cl = findToolsJar match { + val cl = platformTools match { case Some(tools) => ScalaClassLoader.fromURLs(Seq(tools.toURL), intp.classLoader) case _ => intp.classLoader } -- cgit v1.2.3 From fb43ec8dc28625c929beaf28767a955388700d0d Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Thu, 5 Sep 2013 15:27:15 +0200 Subject: SI-7814 Avoid init cycle between Predef, `package`, ScalaRuntime Not every application will force these in a single thread; we have to do our best to avoid cycles between them. The enclosed test was failing every time before the change. This commit breaks the cycle by avoiding computing `tupleNames` in the constructor of `ScalaRuntime`. The new version has the added benefit of including specialized tuple subclasses, which is verified with a unit test for `isTuple`. Are there more of these lurking? It seems likely. I'm more than a little concerned about the way the `ControlThrowable` fires up `scala.SystemProperties` to check whether or not to suppress stack traces; there is already an ugly hack in place: object NoStackTrace { final def noSuppression = _noSuppression // two-stage init to make checkinit happy, // since sys.SystemProperties.noTraceSupression.value // calls back into NoStackTrace.noSuppression final private var _noSuppression = false _noSuppression = sys.SystemProperties.noTraceSupression.value } --- src/library/scala/runtime/ScalaRunTime.scala | 14 ++--- test/files/run/predef-cycle.scala | 71 +++++++++++++++++++++++++ test/junit/scala/runtime/ScalaRunTimeTest.scala | 70 ++++++++++++++++++++++++ 3 files changed, 144 insertions(+), 11 deletions(-) create mode 100644 test/files/run/predef-cycle.scala create mode 100644 test/junit/scala/runtime/ScalaRunTimeTest.scala diff --git a/src/library/scala/runtime/ScalaRunTime.scala b/src/library/scala/runtime/ScalaRunTime.scala index 1d8fe5e9ad..4fee6d75a3 100644 --- a/src/library/scala/runtime/ScalaRunTime.scala +++ b/src/library/scala/runtime/ScalaRunTime.scala @@ -34,21 +34,13 @@ object ScalaRunTime { clazz.isArray && (atLevel == 1 || isArrayClass(clazz.getComponentType, atLevel - 1)) def isValueClass(clazz: jClass[_]) = clazz.isPrimitive() - def isTuple(x: Any) = x != null && tupleNames(x.getClass.getName) + + // includes specialized subclasses and future proofed against hypothetical TupleN (for N > 22) + def isTuple(x: Any) = x != null && x.getClass.getName.startsWith("scala.Tuple") def isAnyVal(x: Any) = x match { case _: Byte | _: Short | _: Char | _: Int | _: Long | _: Float | _: Double | _: Boolean | _: Unit => true case _ => false } - // Avoiding boxing which messes up the specialized tests. Don't ask. - private val tupleNames = { - var i = 22 - var names: List[String] = Nil - while (i >= 1) { - names ::= ("scala.Tuple" + String.valueOf(i)) - i -= 1 - } - names.toSet - } /** Return the class object representing an array with element class `clazz`. */ diff --git a/test/files/run/predef-cycle.scala b/test/files/run/predef-cycle.scala new file mode 100644 index 0000000000..ab147688bc --- /dev/null +++ b/test/files/run/predef-cycle.scala @@ -0,0 +1,71 @@ +class Force { + val t1 = new Thread { + override def run() { + scala.`package` + } + } + val t2 = new Thread { + override def run() { + scala.Predef + } + } + t1.start() + t2.start() + t1.join() + t2.join() +} + +object Test { + def main(args: Array[String]) { + new Force() + } +} + +/* Was deadlocking: +"Thread-2" prio=5 tid=7f9637268000 nid=0x119601000 in Object.wait() [119600000] + java.lang.Thread.State: RUNNABLE + at scala.Predef$.(Predef.scala:90) + at scala.Predef$.(Predef.scala) + at Force$$anon$2.run(predef-cycle.scala:10) + +"Thread-1" prio=5 tid=7f9637267800 nid=0x1194fe000 in Object.wait() [1194fb000] + java.lang.Thread.State: RUNNABLE + at scala.collection.immutable.Set$Set4.$plus(Set.scala:127) + at scala.collection.immutable.Set$Set4.$plus(Set.scala:121) + at scala.collection.mutable.SetBuilder.$plus$eq(SetBuilder.scala:24) + at scala.collection.mutable.SetBuilder.$plus$eq(SetBuilder.scala:22) + at scala.collection.generic.Growable$$anonfun$$plus$plus$eq$1.apply(Growable.scala:48) + at scala.collection.generic.Growable$$anonfun$$plus$plus$eq$1.apply(Growable.scala:48) + at scala.collection.immutable.List.foreach(List.scala:318) + at scala.collection.generic.Growable$class.$plus$plus$eq(Growable.scala:48) + at scala.collection.mutable.SetBuilder.$plus$plus$eq(SetBuilder.scala:22) + at scala.collection.TraversableLike$class.to(TraversableLike.scala:629) + at scala.collection.AbstractTraversable.to(Traversable.scala:105) + at scala.collection.TraversableOnce$class.toSet(TraversableOnce.scala:267) + at scala.collection.AbstractTraversable.toSet(Traversable.scala:105) + at scala.runtime.ScalaRunTime$.(ScalaRunTime.scala:50) + at scala.runtime.ScalaRunTime$.(ScalaRunTime.scala) + at scala.collection.mutable.HashTable$HashUtils$class.elemHashCode(HashTable.scala) + at scala.collection.mutable.HashMap.elemHashCode(HashMap.scala:39) + at scala.collection.mutable.HashTable$class.findOrAddEntry(HashTable.scala:161) + at scala.collection.mutable.HashMap.findOrAddEntry(HashMap.scala:39) + at scala.collection.mutable.HashMap.put(HashMap.scala:75) + at scala.collection.mutable.HashMap.update(HashMap.scala:80) + at scala.sys.SystemProperties$.addHelp(SystemProperties.scala:64) + at scala.sys.SystemProperties$.bool(SystemProperties.scala:68) + at scala.sys.SystemProperties$.noTraceSupression$lzycompute(SystemProperties.scala:80) + - locked <7b8b0e228> (a scala.sys.SystemProperties$) + at scala.sys.SystemProperties$.noTraceSupression(SystemProperties.scala:80) + at scala.util.control.NoStackTrace$.(NoStackTrace.scala:31) + at scala.util.control.NoStackTrace$.(NoStackTrace.scala) + at scala.util.control.NoStackTrace$class.fillInStackTrace(NoStackTrace.scala:22) + at scala.util.control.BreakControl.fillInStackTrace(Breaks.scala:93) + at java.lang.Throwable.(Throwable.java:181) + at scala.util.control.BreakControl.(Breaks.scala:93) + at scala.util.control.Breaks.(Breaks.scala:28) + at scala.collection.Traversable$.(Traversable.scala:96) + at scala.collection.Traversable$.(Traversable.scala) + at scala.package$.(package.scala:46) + at scala.package$.(package.scala) + at Force$$anon$1.run(predef-cycle.scala:4) + */ \ No newline at end of file diff --git a/test/junit/scala/runtime/ScalaRunTimeTest.scala b/test/junit/scala/runtime/ScalaRunTimeTest.scala new file mode 100644 index 0000000000..9da197c71a --- /dev/null +++ b/test/junit/scala/runtime/ScalaRunTimeTest.scala @@ -0,0 +1,70 @@ +package scala.runtime + +import org.junit.Assert._ +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +/** Tests for the private class DefaultPromise */ +@RunWith(classOf[JUnit4]) +class ScalaRunTimeTest { + @Test + def testIsTuple() { + import ScalaRunTime.isTuple + def check(v: Any) = { + assertTrue(v.toString, isTuple(v)) + } + + val s = "" + check(Tuple1(s)) + check((s, s)) + check((s, s, s)) + check((s, s, s, s)) + check((s, s, s, s, s)) + check((s, s, s, s, s, s)) + check((s, s, s, s, s, s, s)) + check((s, s, s, s, s, s, s, s)) + check((s, s, s, s, s, s, s, s, s)) + check((s, s, s, s, s, s, s, s, s, s)) + check((s, s, s, s, s, s, s, s, s, s, s)) + check((s, s, s, s, s, s, s, s, s, s, s, s)) + check((s, s, s, s, s, s, s, s, s, s, s, s, s)) + check((s, s, s, s, s, s, s, s, s, s, s, s, s, s)) + check((s, s, s, s, s, s, s, s, s, s, s, s, s, s, s)) + check((s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s)) + check((s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s)) + check((s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s)) + check((s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s)) + check((s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s)) + check((s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s)) + check((s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s, s)) + + // some specialized variants will have mangled classnames + check(Tuple1(0)) + check((0, 0)) + check((0, 0, 0)) + check((0, 0, 0, 0)) + check((0, 0, 0, 0, 0)) + check((0, 0, 0, 0, 0, 0)) + check((0, 0, 0, 0, 0, 0, 0)) + check((0, 0, 0, 0, 0, 0, 0, 0)) + check((0, 0, 0, 0, 0, 0, 0, 0, 0)) + check((0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) + check((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) + check((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) + check((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) + check((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) + check((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) + check((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) + check((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) + check((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) + check((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) + check((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) + check((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) + check((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) + + case class C() + val c = new C() + assertFalse(c.toString, isTuple(c)) + } +} -- cgit v1.2.3 From a19babc952c1e30d9b92452505e85752ff2d5460 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Thu, 5 Sep 2013 16:00:40 +0200 Subject: SI-7814 Updates the instrumented version of ScalaRuntime. Some tests for specialization use a modified version of the standard library that count boxing, array lookups etc. These sources are updated manually with the script: % test/instrumented/mkinstrumented.sh build Looks that that wasn't done for a while, though. This commit brings it up to date, and adjusts a few braces in ScalaRuntime.scala so the patch srt.scala (used by that script) is shorter. We should really avoid checking in the products of that script and run it as part of the build, or, better, use the bytecode instrumentation framework instead of a modified standard library. But I have to leave that for another day. --- src/library/scala/runtime/ScalaRunTime.scala | 52 ++++++----- test/instrumented/boxes.patch | 2 +- .../library/scala/runtime/BoxesRunTime.java | 7 +- .../library/scala/runtime/ScalaRunTime.scala | 100 +++++++-------------- test/instrumented/srt.patch | 65 +------------- 5 files changed, 70 insertions(+), 156 deletions(-) diff --git a/src/library/scala/runtime/ScalaRunTime.scala b/src/library/scala/runtime/ScalaRunTime.scala index 4fee6d75a3..dcd323961e 100644 --- a/src/library/scala/runtime/ScalaRunTime.scala +++ b/src/library/scala/runtime/ScalaRunTime.scala @@ -67,33 +67,37 @@ object ScalaRunTime { classTag[T].runtimeClass.asInstanceOf[jClass[T]] /** Retrieve generic array element */ - def array_apply(xs: AnyRef, idx: Int): Any = xs match { - case x: Array[AnyRef] => x(idx).asInstanceOf[Any] - case x: Array[Int] => x(idx).asInstanceOf[Any] - case x: Array[Double] => x(idx).asInstanceOf[Any] - case x: Array[Long] => x(idx).asInstanceOf[Any] - case x: Array[Float] => x(idx).asInstanceOf[Any] - case x: Array[Char] => x(idx).asInstanceOf[Any] - case x: Array[Byte] => x(idx).asInstanceOf[Any] - case x: Array[Short] => x(idx).asInstanceOf[Any] - case x: Array[Boolean] => x(idx).asInstanceOf[Any] - case x: Array[Unit] => x(idx).asInstanceOf[Any] - case null => throw new NullPointerException + def array_apply(xs: AnyRef, idx: Int): Any = { + xs match { + case x: Array[AnyRef] => x(idx).asInstanceOf[Any] + case x: Array[Int] => x(idx).asInstanceOf[Any] + case x: Array[Double] => x(idx).asInstanceOf[Any] + case x: Array[Long] => x(idx).asInstanceOf[Any] + case x: Array[Float] => x(idx).asInstanceOf[Any] + case x: Array[Char] => x(idx).asInstanceOf[Any] + case x: Array[Byte] => x(idx).asInstanceOf[Any] + case x: Array[Short] => x(idx).asInstanceOf[Any] + case x: Array[Boolean] => x(idx).asInstanceOf[Any] + case x: Array[Unit] => x(idx).asInstanceOf[Any] + case null => throw new NullPointerException + } } /** update generic array element */ - def array_update(xs: AnyRef, idx: Int, value: Any): Unit = xs match { - case x: Array[AnyRef] => x(idx) = value.asInstanceOf[AnyRef] - case x: Array[Int] => x(idx) = value.asInstanceOf[Int] - case x: Array[Double] => x(idx) = value.asInstanceOf[Double] - case x: Array[Long] => x(idx) = value.asInstanceOf[Long] - case x: Array[Float] => x(idx) = value.asInstanceOf[Float] - case x: Array[Char] => x(idx) = value.asInstanceOf[Char] - case x: Array[Byte] => x(idx) = value.asInstanceOf[Byte] - case x: Array[Short] => x(idx) = value.asInstanceOf[Short] - case x: Array[Boolean] => x(idx) = value.asInstanceOf[Boolean] - case x: Array[Unit] => x(idx) = value.asInstanceOf[Unit] - case null => throw new NullPointerException + def array_update(xs: AnyRef, idx: Int, value: Any): Unit = { + xs match { + case x: Array[AnyRef] => x(idx) = value.asInstanceOf[AnyRef] + case x: Array[Int] => x(idx) = value.asInstanceOf[Int] + case x: Array[Double] => x(idx) = value.asInstanceOf[Double] + case x: Array[Long] => x(idx) = value.asInstanceOf[Long] + case x: Array[Float] => x(idx) = value.asInstanceOf[Float] + case x: Array[Char] => x(idx) = value.asInstanceOf[Char] + case x: Array[Byte] => x(idx) = value.asInstanceOf[Byte] + case x: Array[Short] => x(idx) = value.asInstanceOf[Short] + case x: Array[Boolean] => x(idx) = value.asInstanceOf[Boolean] + case x: Array[Unit] => x(idx) = value.asInstanceOf[Unit] + case null => throw new NullPointerException + } } /** Get generic array length */ diff --git a/test/instrumented/boxes.patch b/test/instrumented/boxes.patch index 6c5ff23f9f..2bb3243221 100644 --- a/test/instrumented/boxes.patch +++ b/test/instrumented/boxes.patch @@ -1,5 +1,5 @@ 9c9 -< +< --- > /* INSTRUMENTED VERSION */ 51a52,59 diff --git a/test/instrumented/library/scala/runtime/BoxesRunTime.java b/test/instrumented/library/scala/runtime/BoxesRunTime.java index 172ed8ee14..57799bd9b1 100644 --- a/test/instrumented/library/scala/runtime/BoxesRunTime.java +++ b/test/instrumented/library/scala/runtime/BoxesRunTime.java @@ -1,6 +1,6 @@ /* __ *\ ** ________ ___ / / ___ Scala API ** -** / __/ __// _ | / / / _ | (c) 2006-2011, LAMP/EPFL ** +** / __/ __// _ | / / / _ | (c) 2006-2013, LAMP/EPFL ** ** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** ** /____/\___/_/ |_/____/_/ | | ** ** |/ ** @@ -244,7 +244,7 @@ public final class BoxesRunTime * as yet have not. * * Note: Among primitives, Float.NaN != Float.NaN, but the boxed - * verisons are equal. This still needs reconciliation. + * versions are equal. This still needs reconciliation. */ public static int hashFromLong(java.lang.Long n) { int iv = n.intValue(); @@ -258,6 +258,9 @@ public final class BoxesRunTime long lv = n.longValue(); if (lv == dv) return java.lang.Long.valueOf(lv).hashCode(); + + float fv = n.floatValue(); + if (fv == dv) return java.lang.Float.valueOf(fv).hashCode(); else return n.hashCode(); } public static int hashFromFloat(java.lang.Float n) { diff --git a/test/instrumented/library/scala/runtime/ScalaRunTime.scala b/test/instrumented/library/scala/runtime/ScalaRunTime.scala index 5a3f83015f..e474ae737c 100644 --- a/test/instrumented/library/scala/runtime/ScalaRunTime.scala +++ b/test/instrumented/library/scala/runtime/ScalaRunTime.scala @@ -1,6 +1,6 @@ /* __ *\ ** ________ ___ / / ___ Scala API ** -** / __/ __// _ | / / / _ | (c) 2002-2011, LAMP/EPFL ** +** / __/ __// _ | / / / _ | (c) 2002-2013, LAMP/EPFL ** ** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** ** /____/\___/_/ |_/____/_/ | | ** ** |/ ** @@ -8,7 +8,8 @@ /* INSTRUMENTED VERSION */ -package scala.runtime +package scala +package runtime import scala.collection.{ Seq, IndexedSeq, TraversableView, AbstractIterator } import scala.collection.mutable.WrappedArray @@ -17,6 +18,7 @@ import scala.collection.generic.{ Sorted } import scala.reflect.{ ClassTag, classTag } import scala.util.control.ControlThrowable import scala.xml.{ Node, MetaData } +import java.lang.{ Class => jClass } import java.lang.Double.doubleToLongBits import java.lang.reflect.{ Modifier, Method => JMethod } @@ -30,29 +32,21 @@ object ScalaRunTime { def isArray(x: Any, atLevel: Int): Boolean = x != null && isArrayClass(x.getClass, atLevel) - private def isArrayClass(clazz: Class[_], atLevel: Int): Boolean = + private def isArrayClass(clazz: jClass[_], atLevel: Int): Boolean = clazz.isArray && (atLevel == 1 || isArrayClass(clazz.getComponentType, atLevel - 1)) - def isValueClass(clazz: Class[_]) = clazz.isPrimitive() - def isTuple(x: Any) = x != null && tupleNames(x.getClass.getName) + def isValueClass(clazz: jClass[_]) = clazz.isPrimitive() + + // includes specialized subclasses and future proofed against hypothetical TupleN (for N > 22) + def isTuple(x: Any) = x != null && x.getClass.getName.startsWith("scala.Tuple") def isAnyVal(x: Any) = x match { case _: Byte | _: Short | _: Char | _: Int | _: Long | _: Float | _: Double | _: Boolean | _: Unit => true case _ => false } - // Avoiding boxing which messes up the specialized tests. Don't ask. - private val tupleNames = { - var i = 22 - var names: List[String] = Nil - while (i >= 1) { - names ::= ("scala.Tuple" + String.valueOf(i)) - i -= 1 - } - names.toSet - } /** Return the class object representing an array with element class `clazz`. */ - def arrayClass(clazz: Class[_]): Class[_] = { + def arrayClass(clazz: jClass[_]): jClass[_] = { // newInstance throws an exception if the erasure is Void.TYPE. see SI-5680 if (clazz == java.lang.Void.TYPE) classOf[Array[Unit]] else java.lang.reflect.Array.newInstance(clazz, 0).getClass @@ -60,18 +54,19 @@ object ScalaRunTime { /** Return the class object representing elements in arrays described by a given schematic. */ - def arrayElementClass(schematic: Any): Class[_] = schematic match { - case cls: Class[_] => cls.getComponentType + def arrayElementClass(schematic: Any): jClass[_] = schematic match { + case cls: jClass[_] => cls.getComponentType case tag: ClassTag[_] => tag.runtimeClass - case _ => throw new UnsupportedOperationException("unsupported schematic %s (%s)".format(schematic, if (schematic == null) "null" else schematic.getClass)) + case _ => + throw new UnsupportedOperationException(s"unsupported schematic $schematic (${schematic.getClass})") } /** Return the class object representing an unboxed value type, * e.g. classOf[int], not classOf[java.lang.Integer]. The compiler * rewrites expressions like 5.getClass to come here. */ - def anyValClass[T <: AnyVal : ClassTag](value: T): Class[T] = - classTag[T].runtimeClass.asInstanceOf[Class[T]] + def anyValClass[T <: AnyVal : ClassTag](value: T): jClass[T] = + classTag[T].runtimeClass.asInstanceOf[jClass[T]] var arrayApplyCount = 0 @@ -93,11 +88,9 @@ object ScalaRunTime { } } - var arrayUpdateCount = 0 - /** update generic array element */ def array_update(xs: AnyRef, idx: Int, value: Any): Unit = { - arrayUpdateCount += 1 + arrayApplyCount += 1 xs match { case x: Array[AnyRef] => x(idx) = value.asInstanceOf[AnyRef] case x: Array[Int] => x(idx) = value.asInstanceOf[Int] @@ -156,7 +149,7 @@ object ScalaRunTime { dest } - def toArray[T](xs: collection.Seq[T]) = { + def toArray[T](xs: scala.collection.Seq[T]) = { val arr = new Array[AnyRef](xs.length) var i = 0 for (x <- xs) { @@ -179,39 +172,10 @@ object ScalaRunTime { def checkInitialized[T <: AnyRef](x: T): T = if (x == null) throw new UninitializedError else x - abstract class Try[+A] { - def Catch[B >: A](handler: PartialFunction[Throwable, B]): B - def Finally(fin: => Unit): A - } - - def Try[A](block: => A): Try[A] = new Try[A] with Runnable { - private var result: A = _ - private var exception: Throwable = - try { run() ; null } - catch { - case e: ControlThrowable => throw e // don't catch non-local returns etc - case e: Throwable => e - } - - def run() { result = block } - - def Catch[B >: A](handler: PartialFunction[Throwable, B]): B = - if (exception == null) result - else if (handler isDefinedAt exception) handler(exception) - else throw exception - - def Finally(fin: => Unit): A = { - fin - - if (exception == null) result - else throw exception - } - } - def _toString(x: Product): String = x.productIterator.mkString(x.productPrefix + "(", ",", ")") - def _hashCode(x: Product): Int = scala.util.MurmurHash3.productHash(x) + def _hashCode(x: Product): Int = scala.util.hashing.MurmurHash3.productHash(x) /** A helper for case classes. */ def typedProductIterator[T](x: Product): Iterator[T] = { @@ -246,12 +210,12 @@ object ScalaRunTime { // Note that these are the implementations called by ##, so they // must not call ## themselves. - @inline def hash(x: Any): Int = + def hash(x: Any): Int = if (x == null) 0 else if (x.isInstanceOf[java.lang.Number]) BoxesRunTime.hashFromNumber(x.asInstanceOf[java.lang.Number]) else x.hashCode - @inline def hash(dv: Double): Int = { + def hash(dv: Double): Int = { val iv = dv.toInt if (iv == dv) return iv @@ -261,7 +225,7 @@ object ScalaRunTime { val fv = dv.toFloat if (fv == dv) fv.hashCode else dv.hashCode } - @inline def hash(fv: Float): Int = { + def hash(fv: Float): Int = { val iv = fv.toInt if (iv == fv) return iv @@ -269,29 +233,29 @@ object ScalaRunTime { if (lv == fv) return hash(lv) else fv.hashCode } - @inline def hash(lv: Long): Int = { + def hash(lv: Long): Int = { val low = lv.toInt val lowSign = low >>> 31 val high = (lv >>> 32).toInt low ^ (high + lowSign) } - @inline def hash(x: Number): Int = runtime.BoxesRunTime.hashFromNumber(x) + def hash(x: Number): Int = runtime.BoxesRunTime.hashFromNumber(x) // The remaining overloads are here for completeness, but the compiler // inlines these definitions directly so they're not generally used. - @inline def hash(x: Int): Int = x - @inline def hash(x: Short): Int = x.toInt - @inline def hash(x: Byte): Int = x.toInt - @inline def hash(x: Char): Int = x.toInt - @inline def hash(x: Boolean): Int = if (x) true.hashCode else false.hashCode - @inline def hash(x: Unit): Int = 0 + def hash(x: Int): Int = x + def hash(x: Short): Int = x.toInt + def hash(x: Byte): Int = x.toInt + def hash(x: Char): Int = x.toInt + def hash(x: Boolean): Int = if (x) true.hashCode else false.hashCode + def hash(x: Unit): Int = 0 /** A helper method for constructing case class equality methods, * because existential types get in the way of a clean outcome and * it's performing a series of Any/Any equals comparisons anyway. * See ticket #2867 for specifics. */ - def sameElements(xs1: collection.Seq[Any], xs2: collection.Seq[Any]) = xs1 sameElements xs2 + def sameElements(xs1: scala.collection.Seq[Any], xs2: scala.collection.Seq[Any]) = xs1 sameElements xs2 /** Given any Scala value, convert it to a String. * @@ -358,7 +322,7 @@ object ScalaRunTime { case x: String => if (x.head.isWhitespace || x.last.isWhitespace) "\"" + x + "\"" else x case x if useOwnToString(x) => x.toString case x: AnyRef if isArray(x) => arrayToString(x) - case x: collection.Map[_, _] => x.iterator take maxElements map mapInner mkString (x.stringPrefix + "(", ", ", ")") + case x: scala.collection.Map[_, _] => x.iterator take maxElements map mapInner mkString (x.stringPrefix + "(", ", ", ")") case x: Iterable[_] => x.iterator take maxElements map inner mkString (x.stringPrefix + "(", ", ", ")") case x: Traversable[_] => x take maxElements map inner mkString (x.stringPrefix + "(", ", ", ")") case x: Product1[_] if isTuple(x) => "(" + inner(x._1) + ",)" // that special trailing comma diff --git a/test/instrumented/srt.patch b/test/instrumented/srt.patch index 47dcfa2197..ee619b2ecb 100644 --- a/test/instrumented/srt.patch +++ b/test/instrumented/srt.patch @@ -1,67 +1,10 @@ 8a9,10 > /* INSTRUMENTED VERSION */ > -73a76,77 +68a71,72 > var arrayApplyCount = 0 > -75,86c79,93 -< def array_apply(xs: AnyRef, idx: Int): Any = xs match { -< case x: Array[AnyRef] => x(idx).asInstanceOf[Any] -< case x: Array[Int] => x(idx).asInstanceOf[Any] -< case x: Array[Double] => x(idx).asInstanceOf[Any] -< case x: Array[Long] => x(idx).asInstanceOf[Any] -< case x: Array[Float] => x(idx).asInstanceOf[Any] -< case x: Array[Char] => x(idx).asInstanceOf[Any] -< case x: Array[Byte] => x(idx).asInstanceOf[Any] -< case x: Array[Short] => x(idx).asInstanceOf[Any] -< case x: Array[Boolean] => x(idx).asInstanceOf[Any] -< case x: Array[Unit] => x(idx).asInstanceOf[Any] -< case null => throw new NullPointerException ---- -> def array_apply(xs: AnyRef, idx: Int): Any = { +70a75 +> arrayApplyCount += 1 +87a93 > arrayApplyCount += 1 -> xs match { -> case x: Array[AnyRef] => x(idx).asInstanceOf[Any] -> case x: Array[Int] => x(idx).asInstanceOf[Any] -> case x: Array[Double] => x(idx).asInstanceOf[Any] -> case x: Array[Long] => x(idx).asInstanceOf[Any] -> case x: Array[Float] => x(idx).asInstanceOf[Any] -> case x: Array[Char] => x(idx).asInstanceOf[Any] -> case x: Array[Byte] => x(idx).asInstanceOf[Any] -> case x: Array[Short] => x(idx).asInstanceOf[Any] -> case x: Array[Boolean] => x(idx).asInstanceOf[Any] -> case x: Array[Unit] => x(idx).asInstanceOf[Any] -> case null => throw new NullPointerException -> } -88a96,97 -> var arrayUpdateCount = 0 -> -90,101c99,113 -< def array_update(xs: AnyRef, idx: Int, value: Any): Unit = xs match { -< case x: Array[AnyRef] => x(idx) = value.asInstanceOf[AnyRef] -< case x: Array[Int] => x(idx) = value.asInstanceOf[Int] -< case x: Array[Double] => x(idx) = value.asInstanceOf[Double] -< case x: Array[Long] => x(idx) = value.asInstanceOf[Long] -< case x: Array[Float] => x(idx) = value.asInstanceOf[Float] -< case x: Array[Char] => x(idx) = value.asInstanceOf[Char] -< case x: Array[Byte] => x(idx) = value.asInstanceOf[Byte] -< case x: Array[Short] => x(idx) = value.asInstanceOf[Short] -< case x: Array[Boolean] => x(idx) = value.asInstanceOf[Boolean] -< case x: Array[Unit] => x(idx) = value.asInstanceOf[Unit] -< case null => throw new NullPointerException ---- -> def array_update(xs: AnyRef, idx: Int, value: Any): Unit = { -> arrayUpdateCount += 1 -> xs match { -> case x: Array[AnyRef] => x(idx) = value.asInstanceOf[AnyRef] -> case x: Array[Int] => x(idx) = value.asInstanceOf[Int] -> case x: Array[Double] => x(idx) = value.asInstanceOf[Double] -> case x: Array[Long] => x(idx) = value.asInstanceOf[Long] -> case x: Array[Float] => x(idx) = value.asInstanceOf[Float] -> case x: Array[Char] => x(idx) = value.asInstanceOf[Char] -> case x: Array[Byte] => x(idx) = value.asInstanceOf[Byte] -> case x: Array[Short] => x(idx) = value.asInstanceOf[Short] -> case x: Array[Boolean] => x(idx) = value.asInstanceOf[Boolean] -> case x: Array[Unit] => x(idx) = value.asInstanceOf[Unit] -> case null => throw new NullPointerException -> } -- cgit v1.2.3 From cb028ba477f37778bccfc23e597acc0284259681 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Fri, 6 Sep 2013 17:04:53 +0200 Subject: SI-7818 Cast our way out of extended existential angst `substituteSymbols` is not sophisticated enough to operate on `TypeSkolem`-s which are based on one of the "from" symbols. The pertinant usage of `substituteSymbols` for this bug in in `Extender`. Recapping on that transform: // orig class C[T](...) extends AnyVal { def foo[U] = } // transform class C[T] extends AnyVal { ... } object C { def foo$extension[T', U'] = } Where `` has been subtituted with, among other things, `[T, U] ~> [T', U']`. In this case our expected type contains a new type parameter (of the extension method), whereas the type of the RHS contains an existential skolem still pinned to the corresponding class type parameter. tree.tpe = Observable1#7037[_$1#12344] <_$1#12344>.info = <: T#7040 pt = Observable1#7037[T#15644] The limitation of substution is lamented in the comments of `adaptMismatchedSkolems`, which faces the harder version of the issue where the skolems are in the expected type. But, we're in the "easy" case with the skolems in the tree's type; we can cast our way out of the problem. See also f335e447 / ed915c54. --- src/compiler/scala/tools/nsc/transform/ExtensionMethods.scala | 7 ++++++- test/files/pos/t7818.scala | 10 ++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 test/files/pos/t7818.scala diff --git a/src/compiler/scala/tools/nsc/transform/ExtensionMethods.scala b/src/compiler/scala/tools/nsc/transform/ExtensionMethods.scala index bc54054028..e0c0cd0fdb 100644 --- a/src/compiler/scala/tools/nsc/transform/ExtensionMethods.scala +++ b/src/compiler/scala/tools/nsc/transform/ExtensionMethods.scala @@ -231,9 +231,14 @@ abstract class ExtensionMethods extends Transform with TypingTransformers { .substituteThis(origThis, extensionThis) .changeOwner(origMeth -> extensionMeth) ) + val castBody = + if (extensionBody.tpe <:< extensionMono.finalResultType) + extensionBody + else + gen.mkCastPreservingAnnotations(extensionBody, extensionMono.finalResultType) // SI-7818 e.g. mismatched existential skolems // Record the extension method ( FIXME: because... ? ) - extensionDefs(companion) += atPos(tree.pos)(DefDef(extensionMeth, extensionBody)) + extensionDefs(companion) += atPos(tree.pos)(DefDef(extensionMeth, castBody)) // These three lines are assembling Foo.bar$extension[T1, T2, ...]($this) // which leaves the actual argument application for extensionCall. diff --git a/test/files/pos/t7818.scala b/test/files/pos/t7818.scala new file mode 100644 index 0000000000..77b99e7d5d --- /dev/null +++ b/test/files/pos/t7818.scala @@ -0,0 +1,10 @@ +class Observable1[+T](val asJava: JObservable[_ <: T]) extends AnyVal { + private def foo[X](a: JObservable[X]): JObservable[X] = ??? + // was generating a type error as the type of the RHS included an existential + // skolem based on the class type parameter `T`, which did not conform + // to the typer parameter of the extension method into which the RHS is + // transplanted. + def synchronize: Observable1[T] = new Observable1(foo(asJava)) +} + +class JObservable[T] -- cgit v1.2.3 From 48283ca80c17b2814a27464c30bca2445ffcc7a5 Mon Sep 17 00:00:00 2001 From: François Garillot Date: Thu, 5 Sep 2013 18:18:43 +0200 Subject: SI-7767 avoid rejecting Scaladoc comments in early initializers review by @retronym --- src/compiler/scala/tools/nsc/ast/parser/Parsers.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala index 5476afa75e..996287dea8 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala @@ -2772,6 +2772,8 @@ self => List(copyValDef(vdef)(mods = mods | Flags.PRESUPER)) case tdef @ TypeDef(mods, name, tparams, rhs) => List(treeCopy.TypeDef(tdef, mods | Flags.PRESUPER, name, tparams, rhs)) + case docdef @ DocDef(comm, rhs) => + List(treeCopy.DocDef(docdef, comm, rhs)) case stat if !stat.isEmpty => syntaxError(stat.pos, "only type definitions and concrete field definitions allowed in early object initialization section", false) List() -- cgit v1.2.3 From 23918873cbe055f22d40fa4c96651fd93186e687 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Mon, 9 Sep 2013 10:33:42 +0200 Subject: SI-7767 Test case for Scaladoc on early initializers --- test/scaladoc/run/t7767.check | 1 + test/scaladoc/run/t7767.scala | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 test/scaladoc/run/t7767.check create mode 100644 test/scaladoc/run/t7767.scala diff --git a/test/scaladoc/run/t7767.check b/test/scaladoc/run/t7767.check new file mode 100644 index 0000000000..619c56180b --- /dev/null +++ b/test/scaladoc/run/t7767.check @@ -0,0 +1 @@ +Done. diff --git a/test/scaladoc/run/t7767.scala b/test/scaladoc/run/t7767.scala new file mode 100644 index 0000000000..be5e9fe080 --- /dev/null +++ b/test/scaladoc/run/t7767.scala @@ -0,0 +1,20 @@ +import scala.tools.nsc.doc.model._ +import scala.tools.partest.ScaladocModelTest + +object Test extends ScaladocModelTest { + + // This caused an infinite recursion in method inline() in CommentFactory.scala + override def code = """ + class Docable extends { /**Doc*/ val foo = 0 } with AnyRef + """ + + // no need for special settings + def scaladocSettings = "" + + def testModel(rootPackage: Package) = { + import access._ + // if it doesn't hang, the test is passed + val comment = rootPackage._class("Docable")._value("foo").comment.map(_.body.toString.trim).getOrElse("") + assert(comment.contains("Doc"), comment) + } +} -- cgit v1.2.3 From 13c716eb45a09faf8853ea13207a48dbe8b59a19 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Tue, 10 Sep 2013 11:18:28 +0200 Subject: Build partest-extras under `pack.done` ... rather than just in `test.suite.init`. Now: % ant pack.done | egrep -i 'compiling|jar' desired.jars.uptodate: [quick.partest-extras] Compiling 1 file to /Users/jason/code/scala/build/quick/classes/partest-extras [jar] Building jar: /Users/jason/code/scala/build/pack/lib/scala-partest-extras.jar Note: Because of the recent changes to the way that the classpath or partest is build up (it is done via `ant test.suite.init`), partest no longer works with quick/classes, the classpath is always taken as `pack`. So `ant quick.bin && ./test/partest` is insufficient; you need to run `ant pack.done`, or just `ant` if you prefer brevity. --- build.xml | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/build.xml b/build.xml index f8dbf42242..c8256a4a24 100755 --- a/build.xml +++ b/build.xml @@ -1281,6 +1281,14 @@ TODO: + + + + + + + @@ -1308,7 +1316,7 @@ TODO: - + @@ -1325,6 +1333,11 @@ TODO: + + + + @@ -1357,7 +1370,7 @@ TODO: - + @@ -1606,18 +1619,12 @@ TODO: - + - - - - - - -- cgit v1.2.3