From dce6b34c38a6d774961ca6f9fd50b11300ecddd6 Mon Sep 17 00:00:00 2001 From: Stephen Judkins Date: Sat, 21 Jan 2012 17:08:52 -0800 Subject: Fixes SI-4929, with a test to verify. Also fixes potential issue with Parsers.phrase not being reentrant; however, I was unable to actually reproduce this issue in practice. (The order in which lastNoSuccess was being set and compared seemed to guarantee that it would never actually be a problem). --- .../scala/util/parsing/combinator/Parsers.scala | 29 +++++++-------- test/files/run/t4929.check | 1 + test/files/run/t4929.scala | 42 ++++++++++++++++++++++ 3 files changed, 58 insertions(+), 14 deletions(-) create mode 100644 test/files/run/t4929.check create mode 100644 test/files/run/t4929.scala diff --git a/src/library/scala/util/parsing/combinator/Parsers.scala b/src/library/scala/util/parsing/combinator/Parsers.scala index 4004a01ad9..70c92342b4 100644 --- a/src/library/scala/util/parsing/combinator/Parsers.scala +++ b/src/library/scala/util/parsing/combinator/Parsers.scala @@ -12,6 +12,7 @@ import scala.util.parsing.input._ import scala.collection.mutable.ListBuffer import scala.annotation.tailrec import annotation.migration +import scala.util.DynamicVariable // TODO: better error handling (labelling like parsec's ) @@ -153,13 +154,14 @@ trait Parsers { val successful = true } - var lastNoSuccess: NoSuccess = null + private lazy val lastNoSuccess = new DynamicVariable[Option[NoSuccess]](None) /** A common super-class for unsuccessful parse results. */ sealed abstract class NoSuccess(val msg: String, override val next: Input) extends ParseResult[Nothing] { // when we don't care about the difference between Failure and Error val successful = false - if (!(lastNoSuccess != null && next.pos < lastNoSuccess.next.pos)) - lastNoSuccess = this + + if (lastNoSuccess.value map { v => !(next.pos < v.next.pos) } getOrElse true) + lastNoSuccess.value = Some(this) def map[U](f: Nothing => U) = this def mapPartial[U](f: PartialFunction[Nothing, U], error: Nothing => String): ParseResult[U] = this @@ -487,7 +489,7 @@ trait Parsers { } /** Changes the error message produced by a parser. - * + * * This doesn't change the behavior of a parser on neither * success nor failure, just on error. The semantics are * slightly different than those obtained by doing `| error(msg)`, @@ -877,16 +879,15 @@ trait Parsers { * if `p` consumed all the input. */ def phrase[T](p: Parser[T]) = new Parser[T] { - lastNoSuccess = null - def apply(in: Input) = p(in) match { - case s @ Success(out, in1) => - if (in1.atEnd) - s - else if (lastNoSuccess == null || lastNoSuccess.next.pos < in1.pos) - Failure("end of input expected", in1) - else - lastNoSuccess - case _ => lastNoSuccess + def apply(in: Input) = lastNoSuccess.withValue(None) { + p(in) match { + case s @ Success(out, in1) => + if (in1.atEnd) + s + else + lastNoSuccess.value filterNot { _.next.pos < in1.pos } getOrElse Failure("end of input expected", in1) + case ns => lastNoSuccess.value.getOrElse(ns) + } } } diff --git a/test/files/run/t4929.check b/test/files/run/t4929.check new file mode 100644 index 0000000000..0f0c913d55 --- /dev/null +++ b/test/files/run/t4929.check @@ -0,0 +1 @@ +success \ No newline at end of file diff --git a/test/files/run/t4929.scala b/test/files/run/t4929.scala new file mode 100644 index 0000000000..3208cd1b09 --- /dev/null +++ b/test/files/run/t4929.scala @@ -0,0 +1,42 @@ +import scala.util.parsing.json._ +import java.util.concurrent._ +import collection.JavaConversions._ + +object Test extends App { + + val LIMIT = 2000 + val THREAD_COUNT = 20 + val count = new java.util.concurrent.atomic.AtomicInteger(0) + + val begin = new CountDownLatch(THREAD_COUNT) + val finish = new CountDownLatch(THREAD_COUNT) + + val errors = new ConcurrentLinkedQueue[Throwable] + + (1 to THREAD_COUNT) foreach { i => + val thread = new Thread { + override def run() { + begin.await(1, TimeUnit.SECONDS) + try { + while (count.getAndIncrement() < LIMIT && errors.isEmpty) { + JSON.parseFull("""{"foo": [1,2,3,4]}""") + } + } catch { + case t: Throwable => errors.add(t) + } + + finish.await(10, TimeUnit.SECONDS) + } + } + + thread.setDaemon(true) + thread.start() + + } + + + errors foreach { throw(_) } + + println("success") + +} -- cgit v1.2.3