Skip to content

Commit b2f5101

Browse files
committed
Merge pull request scala#4307 from som-snytt/issue/4339-2.10-b
SI-4339 Backpatch event errors and attr fix
2 parents 47be353 + b4e3bec commit b2f5101

File tree

8 files changed

+80
-42
lines changed

8 files changed

+80
-42
lines changed

bincompat-forward.whitelist.conf

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,14 @@ filter {
187187
{
188188
matchName="scala.xml.dtd.ElementValidator.scala$xml$dtd$ElementValidator$$find$2"
189189
problemName=IncompatibleMethTypeProblem
190+
},
191+
{
192+
matchName="scala.xml.pull.ExceptionEvent"
193+
problemName=MissingClassProblem
194+
},
195+
{
196+
matchName="scala.xml.pull.ExceptionEvent$"
197+
problemName=MissingClassProblem
190198
}
191199
]
192200
}

src/compiler/scala/tools/nsc/ast/parser/Scanners.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,9 @@ trait Scanners extends ScannersCommon {
367367
val last = if (charOffset >= 2) buf(charOffset - 2) else ' '
368368
nextChar()
369369
last match {
370-
case ' ' | '\t' | '\n' | '{' | '(' | '>' if isNameStart(ch) || ch == '!' || ch == '?' =>
370+
// exclude valid xml names that happen to be Scala operator chars
371+
case ' ' | '\t' | '\n' | '{' | '(' | '>' if (isNameStart(ch) && ch != ':' && !isSpecial(ch))
372+
|| ch == '!' || ch == '?' =>
371373
token = XMLSTART
372374
case _ =>
373375
// Console.println("found '<', but last is '"+in.last+"'"); // DEBUG

src/library/scala/xml/parsing/MarkupParserCommon.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,9 @@ private[scala] trait MarkupParserCommon extends TokenTests {
5858
@param endCh either `'` or `"`
5959
*/
6060
def xAttributeValue(endCh: Char): String = {
61+
require(endCh == '\'' || endCh == '"', s"Expected single or double quote, found $endCh")
6162
val buf = new StringBuilder
62-
while (ch != endCh) {
63+
while (ch != endCh && !eof) {
6364
// well-formedness constraint
6465
if (ch == '<') return errorAndResult("'<' not allowed in attrib value", "")
6566
else if (ch == SU) truncatedError("")

src/library/scala/xml/parsing/TokenTests.scala

Lines changed: 19 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -30,43 +30,25 @@ trait TokenTests {
3030
def isAlpha(c: Char) = (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
3131
def isAlphaDigit(c: Char) = isAlpha(c) || (c >= '0' && c <= '9')
3232

33-
/** {{{
34-
* NameChar ::= Letter | Digit | '.' | '-' | '_' | ':'
35-
* | CombiningChar | Extender
36-
* }}}
37-
* See [4] and Appendix B of XML 1.0 specification.
38-
*/
39-
def isNameChar(ch: Char) = {
40-
import java.lang.Character._
41-
// The constants represent groups Mc, Me, Mn, Lm, and Nd.
42-
43-
isNameStart(ch) || (getType(ch).toByte match {
44-
case COMBINING_SPACING_MARK |
45-
ENCLOSING_MARK | NON_SPACING_MARK |
46-
MODIFIER_LETTER | DECIMAL_DIGIT_NUMBER => true
47-
case _ => ".-:" contains ch
48-
})
49-
}
50-
51-
/** {{{
52-
* NameStart ::= ( Letter | '_' )
53-
* }}}
54-
* where Letter means in one of the Unicode general
55-
* categories `{ Ll, Lu, Lo, Lt, Nl }`.
56-
*
57-
* We do not allow a name to start with `:`.
58-
* See [3] and Appendix B of XML 1.0 specification
59-
*/
60-
def isNameStart(ch: Char) = {
61-
import java.lang.Character._
62-
63-
getType(ch).toByte match {
64-
case LOWERCASE_LETTER |
65-
UPPERCASE_LETTER | OTHER_LETTER |
66-
TITLECASE_LETTER | LETTER_NUMBER => true
67-
case _ => ch == '_'
68-
}
69-
}
33+
def isNameChar(c: Char): Boolean = (
34+
isNameStart(c) ||
35+
(c >= '0' && c <= '9') ||
36+
c == '-' ||
37+
c == '.' ||
38+
c == 0xB7 ||
39+
(c >= 0x300 && c <= 0x36F) ||
40+
(c >= 0x203F && c <= 0x2040)
41+
)
42+
def isNameStart(c: Char): Boolean = (
43+
if (c < 0x00C0) isAlpha(c) || c == ':' || c == '_'
44+
else if (c < 0x0300) c != 0xD7 && c != 0xF7
45+
else if (c < 0x2000) c >= 0x370 && c != 0x37E
46+
else if (c < 0x3001) c == 0x200C || c == 0x200D || (0x2070 to 0x218F contains c) ||
47+
(0x2C00 to 0x2FEF contains c)
48+
else if (c < 0xD800) true
49+
else if (c < 0x10000) (0xF900 to 0xFDCF contains c) || (0xFDF0 to 0xFFFD contains c)
50+
else false // codepoint < 0xF0000
51+
)
7052

7153
/** {{{
7254
* Name ::= ( Letter | '_' ) (NameChar)*

src/library/scala/xml/pull/XMLEventReader.scala

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,12 +91,16 @@ extends scala.collection.AbstractIterator[XMLEvent]
9191

9292
override def run() {
9393
curInput = input
94-
interruptibly { this.initialize.document() }
95-
setEvent(POISON)
94+
try interruptibly { this.initialize.document() }
95+
catch { case e:Exception => setEvent(ExceptionEvent(e)) }
96+
finally setEvent(POISON)
9697
}
9798
}
9899
}
99100

101+
// An internal class used to propagate exception from helper threads to API end users.
102+
private case class ExceptionEvent(exception:Exception) extends XMLEvent
103+
100104
// An iterator designed for one or more producers to generate
101105
// elements, and a single consumer to iterate. Iteration will continue
102106
// until closeIterator() is called, after which point producers
@@ -141,6 +145,7 @@ trait ProducerConsumerIterator[T >: Null] extends Iterator[T] {
141145
def next() = {
142146
if (eos) throw new NoSuchElementException("ProducerConsumerIterator")
143147
if (buffer == null) fillBuffer
148+
if (buffer.isInstanceOf[ExceptionEvent]) throw buffer.asInstanceOf[ExceptionEvent].exception
144149

145150
drainBuffer
146151
}

test/files/jvm/unittest_xml.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,9 @@ object Test {
6262
object UtilityTest {
6363
def run() {
6464
assert(Utility.isNameStart('b'))
65-
assert(!Utility.isNameStart(':'))
65+
66+
// no longer: this was a convenience for the implementation, not to spec.
67+
//assert(!Utility.isNameStart(':'))
6668

6769
val x = <foo>
6870
<toomuchws/>

test/files/run/t4339.check

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Saw failure: scala.xml.parsing.FatalError: expected closing tag of foo
2+
EvElemStart(null,foo, bar="baz/&gt;",)
3+
Saw failure: scala.xml.parsing.FatalError: expected closing tag of foo

test/files/run/t4339.scala

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
2+
import scala.util.{ Try, Success, Failure }
3+
import xml._
4+
import scala.io.Source.fromString
5+
import java.io.PrintStream
6+
7+
object Test extends App {
8+
9+
def quietSource(text: String) = new io.Source {
10+
override protected val iter = io.Source fromString text
11+
override def report(pos: Int, msg: String, out: PrintStream) = ()
12+
}
13+
def reading(text: String)(f: pull.XMLEventReader => Unit): Unit = {
14+
val r = new pull.XMLEventReader(quietSource(text))
15+
try f(r)
16+
finally r.stop()
17+
}
18+
def trying(body: => Unit): Unit =
19+
Try (body) match {
20+
case Success(_) => Console println "Expected failure"
21+
case Failure(e) => Console println s"Saw failure: $e"
22+
}
23+
24+
val problematic = """<foo bar="baz/>"""
25+
26+
trying (
27+
parsing.ConstructingParser.fromSource(quietSource(problematic), false).document.docElem
28+
)
29+
30+
trying (
31+
reading(problematic) { r =>
32+
while (r.hasNext) println(r.next())
33+
}
34+
)
35+
}

0 commit comments

Comments
 (0)