Skip to content

Commit ee2708e

Browse files
committed
Merge pull request #69 from liskin/lastnosuccess-memleak
Fix lastNoSuccessVar memory leak
2 parents 4600c5a + 01c5bac commit ee2708e

File tree

2 files changed

+66
-10
lines changed

2 files changed

+66
-10
lines changed

src/main/scala/scala/util/parsing/combinator/Parsers.scala

+15-10
Original file line numberDiff line numberDiff line change
@@ -156,14 +156,19 @@ trait Parsers {
156156
val successful = true
157157
}
158158

159-
private lazy val lastNoSuccessVar = new DynamicVariable[Option[NoSuccess]](None)
159+
/* two layers of Option:
160+
* outer Option is None if lastNoSuccess tracking is disabled (outside of
161+
* phrase) and Some if tracking is enabled
162+
* inner Option is None if NoSuccess hasn't been seen yet, Some otherwise
163+
* this is necessary to avoid leaking NoSuccesses in thread locals */
164+
private lazy val lastNoSuccessVar = new DynamicVariable[Option[Option[NoSuccess]]](None)
160165

161166
/** A common super-class for unsuccessful parse results. */
162167
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
163168
val successful = false
164169

165-
if (lastNoSuccessVar.value forall (v => !(next.pos < v.next.pos)))
166-
lastNoSuccessVar.value = Some(this)
170+
if (lastNoSuccessVar.value exists (_ forall (v => !(next.pos < v.next.pos))))
171+
lastNoSuccessVar.value = Some(Some(this))
167172

168173
def map[U](f: Nothing => U) = this
169174
def mapPartial[U](f: PartialFunction[Nothing, U], error: Nothing => String): ParseResult[U] = this
@@ -878,14 +883,14 @@ trait Parsers {
878883
* if `p` consumed all the input.
879884
*/
880885
def phrase[T](p: Parser[T]) = new Parser[T] {
881-
def apply(in: Input) = lastNoSuccessVar.withValue(None) {
886+
def apply(in: Input) = lastNoSuccessVar.withValue(Some(None)) {
882887
p(in) match {
883-
case s @ Success(out, in1) =>
884-
if (in1.atEnd)
885-
s
886-
else
887-
lastNoSuccessVar.value filterNot { _.next.pos < in1.pos } getOrElse Failure("end of input expected", in1)
888-
case ns => lastNoSuccessVar.value.getOrElse(ns)
888+
case s @ Success(out, in1) =>
889+
if (in1.atEnd)
890+
s
891+
else
892+
lastNoSuccessVar.value flatMap (_ filterNot { _.next.pos < in1.pos }) getOrElse Failure("end of input expected", in1)
893+
case ns => lastNoSuccessVar.value.flatten.getOrElse(ns)
889894
}
890895
}
891896
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import scala.util.parsing.combinator._
2+
import scala.util.DynamicVariable
3+
4+
import org.junit.Test
5+
6+
class t9010 {
7+
@Test
8+
def test: Unit = {
9+
val p = new grammar
10+
val lastNoSuccessVar = getLastNoSuccessVar(p)
11+
import p._
12+
13+
val res1 = parse(x, "x")
14+
assert(res1.successful)
15+
assert(lastNoSuccessVar.value == None)
16+
17+
val res2 = parse(x, "y")
18+
assert(!res2.successful)
19+
assert(lastNoSuccessVar.value == None)
20+
21+
val res3 = parseAll(x, "x")
22+
assert(res3.successful)
23+
assert(lastNoSuccessVar.value == None)
24+
25+
val res4 = parseAll(x, "y")
26+
assert(!res4.successful)
27+
assert(lastNoSuccessVar.value == None)
28+
}
29+
30+
private def getLastNoSuccessVar(p: Parsers): DynamicVariable[Option[_]] = {
31+
// use java reflection instead of scala (see below) because of
32+
// https://issues.scala-lang.org/browse/SI-9306
33+
val fn = "scala$util$parsing$combinator$Parsers$$lastNoSuccessVar"
34+
val f = p.getClass.getDeclaredMethod(fn)
35+
f.setAccessible(true)
36+
f.invoke(p).asInstanceOf[DynamicVariable[Option[_]]]
37+
38+
/*
39+
val ru = scala.reflect.runtime.universe
40+
val mirror = ru.runtimeMirror(getClass.getClassLoader)
41+
val lastNoSuccessVarField =
42+
ru.typeOf[Parsers].decl(ru.TermName("lastNoSuccessVar")).asTerm.accessed.asTerm
43+
mirror.reflect(p).reflectField(lastNoSuccessVarField).get.
44+
asInstanceOf[DynamicVariable[Option[_]]]
45+
*/
46+
}
47+
48+
private final class grammar extends RegexParsers {
49+
val x: Parser[String] = "x"
50+
}
51+
}

0 commit comments

Comments
 (0)