Skip to content

Commit df57e09

Browse files
authored
Merge pull request scala#6726 from martijnhoekstra/SI-10925
strings like ".Exxx" are not valid floats/doubles
2 parents 163b9f4 + 7108f73 commit df57e09

File tree

3 files changed

+44
-48
lines changed

3 files changed

+44
-48
lines changed

src/library/scala/collection/StringParsers.scala

Lines changed: 33 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ final private[scala] object StringParsers {
2626
def rec(i: Int, agg: Int): Option[Int] =
2727
if (agg < min) None
2828
else if (i == len) {
29-
if(!isPositive) Some(agg)
29+
if (!isPositive) Some(agg)
3030
else if (agg == min) None
3131
else Some(-agg)
3232
}
@@ -41,8 +41,8 @@ final private[scala] object StringParsers {
4141
//bool
4242
@inline
4343
final def parseBool(from: String): Option[Boolean] =
44-
if(from.equalsIgnoreCase("true")) Some(true)
45-
else if(from.equalsIgnoreCase("false")) Some(false)
44+
if (from.equalsIgnoreCase("true")) Some(true)
45+
else if (from.equalsIgnoreCase("false")) Some(false)
4646
else None
4747

4848
//integral types
@@ -90,11 +90,11 @@ final private[scala] object StringParsers {
9090
@tailrec
9191
def step(i: Int, agg: Int, isPositive: Boolean): Option[Int] = {
9292
if (i == len) {
93-
if(!isPositive) Some(agg)
93+
if (!isPositive) Some(agg)
9494
else if (agg == Int.MinValue) None
9595
else Some(-agg)
9696
}
97-
else if(agg < intOverflowBoundary) None
97+
else if (agg < intOverflowBoundary) None
9898
else {
9999
val digit = decValue(from.charAt(i))
100100
if (digit == -1 || (agg == intOverflowBoundary && digit == intOverflowDigit)) None
@@ -125,11 +125,11 @@ final private[scala] object StringParsers {
125125
@tailrec
126126
def step(i: Int, agg: Long, isPositive: Boolean): Option[Long] = {
127127
if (i == len) {
128-
if(isPositive && agg == Long.MinValue) None
128+
if (isPositive && agg == Long.MinValue) None
129129
else if (isPositive) Some(-agg)
130130
else Some(agg)
131131
}
132-
else if(agg < longOverflowBoundary) None
132+
else if (agg < longOverflowBoundary) None
133133
else {
134134
val digit = decValue(from.charAt(i))
135135
if (digit == -1 || (agg == longOverflowBoundary && digit == longOverflowDigit)) None
@@ -166,7 +166,7 @@ final private[scala] object StringParsers {
166166
def forAllBetween(start: Int, end: Int, pred: Char => Boolean): Boolean = {
167167
def rec(i: Int): Boolean = {
168168
if (i >= end) true
169-
else if(pred(format.charAt(i))) rec(i + 1)
169+
else if (pred(format.charAt(i))) rec(i + 1)
170170
else false
171171
}
172172
rec(start)
@@ -190,36 +190,32 @@ final private[scala] object StringParsers {
190190

191191
def prefixOK(startIndex: Int, endIndex: Int): Boolean = {
192192
val len = endIndex - startIndex
193-
if(len == 0) false
194-
else {
193+
(len > 0) && {
195194
//the prefix part is
196195
//hexDigits
197196
//hexDigits.
198197
//hexDigits.hexDigits
199198
//.hexDigits
200199
//but notnot .
201-
if(format.charAt(startIndex) == '.') {
200+
if (format.charAt(startIndex) == '.') {
202201
(len > 1) && forAllBetween(startIndex + 1, endIndex, isHexDigit)
203202
} else {
204203
val noLeading = skipIndexWhile(isHexDigit, startIndex, endIndex)
205-
if(noLeading >= endIndex) true
206-
else if(format.charAt(noLeading) == '.') forAllBetween(noLeading + 1, endIndex, isHexDigit)
207-
else false
204+
(noLeading >= endIndex) ||
205+
((format.charAt(noLeading) == '.') && forAllBetween(noLeading + 1, endIndex, isHexDigit))
208206
}
209207
}
210208
}
211209

212-
def postfixOK(startIndex: Int, endIndex: Int): Boolean = {
213-
if (startIndex >= endIndex) false
214-
else {
215-
if (forAllBetween(startIndex, endIndex, ch => ch >= '0' && ch <= '9')) true
216-
else {
210+
def postfixOK(startIndex: Int, endIndex: Int): Boolean =
211+
(startIndex < endIndex) && {
212+
(forAllBetween(startIndex, endIndex, ch => ch >= '0' && ch <= '9')) || {
217213
val startchar = format.charAt(startIndex)
218-
(startchar == '+' || startchar == '-') && (endIndex - startIndex > 1) && forAllBetween(startIndex + 1, endIndex, ch => ch >= '0' && ch <= '9')
214+
(startchar == '+' || startchar == '-') &&
215+
(endIndex - startIndex > 1) &&
216+
forAllBetween(startIndex + 1, endIndex, ch => ch >= '0' && ch <= '9')
219217
}
220218
}
221-
222-
}
223219
// prefix [pP] postfix
224220
val pIndex = format.indexWhere(ch => ch == 'p' || ch == 'P', startIndex)
225221
(pIndex <= endIndex) && prefixOK(startIndex, pIndex) && postfixOK(pIndex + 1, endIndex)
@@ -228,47 +224,41 @@ final private[scala] object StringParsers {
228224
def isDecFloatLiteral(startIndex: Int, endIndex: Int): Boolean = {
229225
//invariant: endIndex > startIndex
230226

231-
def expOK(startIndex: Int, endIndex: Int): Boolean = {
232-
if(startIndex >= endIndex) false
233-
else {
227+
def expOK(startIndex: Int, endIndex: Int): Boolean =
228+
(startIndex < endIndex) && {
234229
val startChar = format.charAt(startIndex)
235-
if(startChar == '+' || startChar == '-')
230+
if (startChar == '+' || startChar == '-')
236231
(endIndex > (startIndex + 1)) &&
237232
skipIndexWhile(ch => ch >= '0' && ch <= '9', startIndex + 1, endIndex) == endIndex
238233
else skipIndexWhile(ch => ch >= '0' && ch <= '9', startIndex, endIndex) == endIndex
239234
}
240-
}
241235

242236
//significant can be one of
243237
//* digits.digits
244238
//* .digits
245239
//* digits.
246240
//but not just .
247241
val startChar = format.charAt(startIndex)
248-
if(startChar == '.') {
249-
val noSignificant = skipIndexWhile(ch => ch >= '0' && ch <= '9', startIndex + 1, endIndex)
250-
if(noSignificant == endIndex) (noSignificant != startIndex + 1) //not just "."
251-
else {
252-
val e = format.charAt(noSignificant)
253-
if (e == 'e' || e == 'E') expOK(noSignificant + 1, endIndex)
254-
else false
255-
}
242+
if (startChar == '.') {
243+
val noSignificant = skipIndexWhile(ch => ch >= '0' && ch <= '9', startIndex + 1, endIndex)
244+
(noSignificant != startIndex + 1) && { //not just "." or ".Exxx"
245+
val e = format.charAt(noSignificant)
246+
(e == 'e' || e == 'E') && expOK(noSignificant + 1, endIndex)
247+
}
256248
}
257249
else if (startChar >= '0' && startChar <= '9'){
258250
//one set of digits, then optionally a period, then optionally another set of digits, then optionally an exponent
259251
val noInt = skipIndexWhile(ch => ch >= '0' && ch <= '9', startIndex, endIndex)
260-
if(noInt == endIndex) true //just the digits
261-
else {
252+
(noInt == endIndex) || { //just the digits
262253
val afterIntChar = format.charAt(noInt)
263-
if(afterIntChar == '.') {
254+
if (afterIntChar == '.') {
264255
val noSignificant = skipIndexWhile(ch => ch >= '0' && ch <= '9', noInt + 1, endIndex)
265-
if(noSignificant >= endIndex) true //no exponent
266-
else {
256+
(noSignificant >= endIndex) || { //no exponent
267257
val e = format.charAt(noSignificant)
268258
(e == 'e' || e == 'E') && expOK(noSignificant + 1, endIndex)
269259
}
270260
}
271-
else if(afterIntChar == 'e' || afterIntChar == 'E') expOK(noInt + 1, endIndex)
261+
else if (afterIntChar == 'e' || afterIntChar == 'E') expOK(noInt + 1, endIndex)
272262
else false
273263
}
274264
}
@@ -280,14 +270,14 @@ final private[scala] object StringParsers {
280270
val unspacedStart = format.indexWhere(ch => ch.toInt > 0x20)
281271
val unspacedEnd = format.lastIndexWhere(ch => ch.toInt > 0x20) + 1
282272

283-
if(unspacedStart == -1 || unspacedStart >= unspacedEnd || unspacedEnd <= 0) false
273+
if (unspacedStart == -1 || unspacedStart >= unspacedEnd || unspacedEnd <= 0) false
284274
else {
285275
//all formats can have a sign
286276
val unsigned = {
287277
val startchar = format.charAt(unspacedStart)
288278
if (startchar == '-' || startchar == '+') unspacedStart + 1 else unspacedStart
289279
}
290-
if(unsigned >= unspacedEnd) false
280+
if (unsigned >= unspacedEnd) false
291281
//that's it for NaN and Infinity
292282
else if (format.charAt(unsigned) == 'N') format.substring(unsigned, unspacedEnd) == "NaN"
293283
else if (format.charAt(unsigned) == 'I') format.substring(unsigned, unspacedEnd) == "Infinity"

test/junit/scala/collection/StringParsersTest.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,12 @@ class StringParsersTest {
242242
"-0x0.000000000000090000000000000000001p-1022",
243243
"-0x0.00000000000009fffffffffffffffffffffffffffffffffp-1022",
244244
"-0x0.0000000000000fffffffffffffffffffffffffffffffffffp-1022",
245+
".E4",
246+
"1.1E4",
247+
"1.E4",
248+
".1E4",
249+
"1E4",
250+
"E4",
245251
"0.0",
246252
"+0.0",
247253
"-0.0",

test/scalacheck/scala/collection/FloatFormatTest.scala

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -92,21 +92,21 @@ object FloatFormatTest extends Properties("FloatFormat") {
9292
case (ll, rr) => ll == rr
9393
}
9494

95-
property("plausible string are same")= forAll(genPlausibleFloatString) {
95+
property("for all strings (plausible examples) str.toDoubleOption == Try(str.toDouble).toOption") = forAll(genPlausibleFloatString) {
9696
str => deq(str.toDoubleOption, Try(str.toDouble).toOption)
9797
}
9898

99-
property("possible strings are same") = forAll(genPossibleFloatString) {
99+
property("for all strings (possible examples) str.toDoubleOption == Try(str.toDouble).toOption") = forAll(genPossibleFloatString) {
100100
str => deq(str.toDoubleOption, Try(str.toDouble).toOption)
101101
}
102102

103-
property("arbitrary strings are same") = forAll{ (str: String) =>
103+
property("for all strings (random strings) str.toDoubleOption == Try(str.toDouble).toOption") = forAll{ (str: String) =>
104104
deq(str.toDoubleOption, Try(str.toDouble).toOption)
105105
}
106106

107-
property("double.toString checks") = forAll{ (d: Double) => checkFloatFormat(d.toString)}
107+
property("double.toString is a valid float format") = forAll{ (d: Double) => checkFloatFormat(d.toString)}
108108

109-
property("nan and infinity check like toDouble") = forAll(genNanInf){ (str: String) =>
109+
property("nan and infinity toDouble == toDoubleOption.get") = forAll(genNanInf){ (str: String) =>
110110
deq(str.toDoubleOption, Try(str.toDouble).toOption)
111111
}
112112

0 commit comments

Comments
 (0)