Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SI-9015 Reject 0x and minor parser cleanup #4182

Merged
merged 1 commit into from Dec 9, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
192 changes: 73 additions & 119 deletions src/compiler/scala/tools/nsc/ast/parser/Scanners.scala
Expand Up @@ -453,18 +453,15 @@ trait Scanners extends ScannersCommon {
getOperatorRest()
}
case '0' =>
def fetchZero() = {
putChar(ch)
def fetchLeadingZero(): Unit = {
nextChar()
if (ch == 'x' || ch == 'X') {
nextChar()
base = 16
} else {
base = 8
ch match {
case 'x' | 'X' => base = 16 ; nextChar()
case _ => base = 8 // single decimal zero, perhaps
}
getNumber()
}
fetchZero()
fetchLeadingZero()
getNumber()
case '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' =>
base = 10
getNumber()
Expand Down Expand Up @@ -902,62 +899,61 @@ trait Scanners extends ScannersCommon {
*/
def charVal: Char = if (strVal.length > 0) strVal.charAt(0) else 0

/** Convert current strVal, base to long value
/** Convert current strVal, base to long value.
* This is tricky because of max negative value.
*
* Conversions in base 10 and 16 are supported. As a permanent migration
* path, attempts to write base 8 literals except `0` emit a verbose error.
*/
def intVal(negated: Boolean): Long = {
if (token == CHARLIT && !negated) {
charVal.toLong
} else {
var value: Long = 0
val divider = if (base == 10) 1 else 2
val limit: Long =
if (token == LONGLIT) Long.MaxValue else Int.MaxValue
var i = 0
def malformed: Long = {
if (base == 8) syntaxError("Decimal integer literals may not have a leading zero. (Octal syntax is obsolete.)")
else syntaxError("malformed integer number")
0
}
def tooBig: Long = {
syntaxError("integer number too large")
0
}
def intConvert: Long = {
val len = strVal.length
while (i < len) {
val d = digit2int(strVal charAt i, base)
if (d < 0) {
syntaxError("malformed integer number")
return 0
}
if (value < 0 ||
limit / (base / divider) < value ||
limit - (d / divider) < value * (base / divider) &&
!(negated && limit == value * base - 1 + d)) {
syntaxError("integer number too large")
return 0
}
value = value * base + d
i += 1
if (len == 0) {
if (base != 8) syntaxError("missing integer number") // e.g., 0x;
0
} else {
val divider = if (base == 10) 1 else 2
val limit: Long = if (token == LONGLIT) Long.MaxValue else Int.MaxValue
@tailrec def convert(value: Long, i: Int): Long =
if (i >= len) value
else {
val d = digit2int(strVal charAt i, base)
if (d < 0)
malformed
else if (value < 0 ||
limit / (base / divider) < value ||
limit - (d / divider) < value * (base / divider) &&
!(negated && limit == value * base - 1 + d))
tooBig
else
convert(value * base + d, i + 1)
}
val result = convert(0, 0)
if (base == 8) malformed else if (negated) -result else result
}
if (negated) -value else value
}
if (token == CHARLIT && !negated) charVal.toLong else intConvert
}

def intVal: Long = intVal(negated = false)

/** Convert current strVal, base to double value
*/
*/
def floatVal(negated: Boolean): Double = {

val limit: Double =
if (token == DOUBLELIT) Double.MaxValue else Float.MaxValue
val limit: Double = if (token == DOUBLELIT) Double.MaxValue else Float.MaxValue
try {
val value: Double = java.lang.Double.valueOf(strVal).doubleValue()
def isDeprecatedForm = {
val idx = strVal indexOf '.'
(idx == strVal.length - 1) || (
(idx >= 0)
&& (idx + 1 < strVal.length)
&& (!Character.isDigit(strVal charAt (idx + 1)))
)
}
if (value > limit)
syntaxError("floating point number too large")
if (isDeprecatedForm)
syntaxError("floating point number is missing digit after dot")

if (negated) -value else value
} catch {
case _: NumberFormatException =>
Expand All @@ -968,86 +964,44 @@ trait Scanners extends ScannersCommon {

def floatVal: Double = floatVal(negated = false)

def checkNoLetter(): Unit = {
def checkNoLetter(): Unit = {
if (isIdentifierPart(ch) && ch >= ' ')
syntaxError("Invalid literal number")
}

/** Read a number into strVal and set base */
protected def getNumber(): Unit = {
val base1 = if (base < 10) 10 else base
// Read 8,9's even if format is octal, produce a malformed number error afterwards.
// At this point, we have already read the first digit, so to tell an innocent 0 apart
// from an octal literal 0123... (which we want to disallow), we check whether there
// are any additional digits coming after the first one we have already read.
var notSingleZero = false
while (digit2int(ch, base1) >= 0) {
putChar(ch)
nextChar()
notSingleZero = true
}
token = INTLIT

/* When we know for certain it's a number after using a touch of lookahead */
def restOfNumber() = {
putChar(ch)
nextChar()
/** Read a number into strVal.
*
* The `base` can be 8, 10 or 16, where base 8 flags a leading zero.
* For ints, base 8 is legal only for the case of exactly one zero.
*/
protected def getNumber(): Unit = {
// consume digits of a radix
def consumeDigits(radix: Int): Unit =
while (digit2int(ch, radix) >= 0) {
putChar(ch)
nextChar()
}
// adding decimal point is always OK because `Double valueOf "0."` is OK
def restOfNonIntegralNumber(): Unit = {
putChar('.')
if (ch == '.') nextChar()
getFraction()
}
def restOfUncertainToken() = {
def isEfd = ch match { case 'e' | 'E' | 'f' | 'F' | 'd' | 'D' => true ; case _ => false }
def isL = ch match { case 'l' | 'L' => true ; case _ => false }

if (base <= 10 && isEfd)
getFraction()
else {
// Checking for base == 8 is not enough, because base = 8 is set
// as soon as a 0 is read in `case '0'` of method fetchToken.
if (base == 8 && notSingleZero) syntaxError("Non-zero integral values may not have a leading zero.")
setStrVal()
if (isL) {
nextChar()
token = LONGLIT
}
else checkNoLetter()
// after int: 5e7f, 42L, 42.toDouble but not 42b. Repair 0d.
def restOfNumber(): Unit = {
ch match {
case 'e' | 'E' | 'f' | 'F' |
'd' | 'D' => if (cbuf.isEmpty) putChar('0'); restOfNonIntegralNumber()
case 'l' | 'L' => token = LONGLIT ; setStrVal() ; nextChar()
case _ => token = INTLIT ; setStrVal() ; checkNoLetter()
}
}

if (base > 10 || ch != '.')
restOfUncertainToken()
else {
val lookahead = lookaheadReader
val c = lookahead.getc()

/* Prohibit 1. */
if (!isDigit(c))
return setStrVal()

val isDefinitelyNumber = (c: @switch) match {
/** Another digit is a giveaway. */
case '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' =>
true
// consume leading digits, provisionally an Int
consumeDigits(if (base == 16) 16 else 10)

/* Backquoted idents like 22.`foo`. */
case '`' =>
return setStrVal() /** Note the early return */

/* These letters may be part of a literal, or a method invocation on an Int.
*/
case 'd' | 'D' | 'f' | 'F' =>
!isIdentifierPart(lookahead.getc())

/* A little more special handling for e.g. 5e7 */
case 'e' | 'E' =>
val ch = lookahead.getc()
!isIdentifierPart(ch) || (isDigit(ch) || ch == '+' || ch == '-')

case x =>
!isIdentifierStart(x)
}
if (isDefinitelyNumber) restOfNumber()
else restOfUncertainToken()
}
val detectedFloat: Boolean = base != 16 && ch == '.' && isDigit(lookaheadReader.getc)
if (detectedFloat) restOfNonIntegralNumber() else restOfNumber()
}

/** Parse character literal if current character is followed by \',
Expand Down
40 changes: 40 additions & 0 deletions test/files/neg/literals.check
@@ -0,0 +1,40 @@
literals.scala:6: error: missing integer number
def missingHex: Int = { 0x } // line 4: was: not reported, taken as zero
^
literals.scala:8: error: Decimal integer literals may not have a leading zero. (Octal syntax is obsolete.)
def leadingZeros: Int = { 01 } // line 6: no leading zero
^
literals.scala:10: error: Decimal integer literals may not have a leading zero. (Octal syntax is obsolete.)
def tooManyZeros: Int = { 00 } // line 8: no leading zero
^
literals.scala:12: error: Decimal integer literals may not have a leading zero. (Octal syntax is obsolete.)
def zeroOfNine: Int = { 09 } // line 10: no leading zero
^
literals.scala:16: error: Decimal integer literals may not have a leading zero. (Octal syntax is obsolete.)
def zeroOfNineDot: Int = { 09. } // line 14: malformed integer, ident expected
^
literals.scala:23: error: missing integer number
def missingHex: Int = 0x // line 22: was: not reported, taken as zero
^
literals.scala:27: error: Decimal integer literals may not have a leading zero. (Octal syntax is obsolete.)
def tooManyZeros: Int = 00 // line 26: no leading zero
^
literals.scala:14: error: identifier expected but '}' found.
def orphanDot: Int = { 9. } // line 12: ident expected
^
literals.scala:16: error: identifier expected but '}' found.
def zeroOfNineDot: Int = { 09. } // line 14: malformed integer, ident expected
^
literals.scala:18: error: ';' expected but double literal found.
def noHexFloat: Double = { 0x1.2 } // line 16: ';' expected but double literal found.
^
literals.scala:25: error: ';' expected but 'def' found.
def leadingZeros: Int = 01 // line 24: no leading zero
^
literals.scala:29: error: ';' expected but 'def' found.
def zeroOfNine: Int = 09 // line 28: no leading zero
^
literals.scala:33: error: identifier expected but 'def' found.
def zeroOfNineDot: Int = 09. // line 32: malformed integer, ident expected
^
13 errors found
36 changes: 36 additions & 0 deletions test/files/neg/literals.scala
@@ -0,0 +1,36 @@

/* This took me literally all day.
*/
trait RejectedLiterals {

def missingHex: Int = { 0x } // line 4: was: not reported, taken as zero

def leadingZeros: Int = { 01 } // line 6: no leading zero

def tooManyZeros: Int = { 00 } // line 8: no leading zero

def zeroOfNine: Int = { 09 } // line 10: no leading zero

def orphanDot: Int = { 9. } // line 12: ident expected

def zeroOfNineDot: Int = { 09. } // line 14: malformed integer, ident expected

def noHexFloat: Double = { 0x1.2 } // line 16: ';' expected but double literal found.
}

trait Braceless {

def missingHex: Int = 0x // line 22: was: not reported, taken as zero

def leadingZeros: Int = 01 // line 24: no leading zero

def tooManyZeros: Int = 00 // line 26: no leading zero

def zeroOfNine: Int = 09 // line 28: no leading zero

def orphanDot: Int = 9. // line 30: ident expected

def zeroOfNineDot: Int = 09. // line 32: malformed integer, ident expected

def noHexFloat: Double = 0x1.2 // line 34: ';' expected but double literal found.
}
69 changes: 12 additions & 57 deletions test/files/run/literals.check
@@ -1,57 +1,12 @@
warning: there were 5 deprecation warnings; re-run with -deprecation for details
test '\u0024' == '$' was successful
test '\u005f' == '_' was successful
test 65.asInstanceOf[Char] == 'A' was successful
test "\141\142" == "ab" was successful
test "\0x61\0x62".trim() == "x61\0x62" was successful

test (65 : Byte) == 'A' was successful

test 0X01 == 1 was successful
test 0x01 == 1 was successful
test 0x10 == 16 was successful
test 0xa == 10 was successful
test 0x0a == 10 was successful
test +0x01 == 1 was successful
test +0x10 == 16 was successful
test +0xa == 10 was successful
test +0x0a == 10 was successful
test -0x01 == -1 was successful
test -0x10 == -16 was successful
test -0xa == -10 was successful
test -0x0a == -10 was successful
test 0x7fffffff == 2147483647 was successful
test 0x80000000 == -2147483648 was successful
test 0xffffffff == -1 was successful

test 1l == 1L was successful
test 1L == 1l was successful
test 1.asInstanceOf[Long] == 1l was successful
test 0x7fffffffffffffffL == 9223372036854775807L was successful
test 0x8000000000000000L == -9223372036854775808L was successful
test 0xffffffffffffffffL == -1L was successful

test 1e1f == 10.0f was successful
test .3f == 0.3f was successful
test 0f == 0.0f was successful
test 01.23f == 1.23f was successful
test 3.14f == 3.14f was successful
test 6.022e23f == 6.022e23f was successful
test 09f == 9.0f was successful
test 1.asInstanceOf[Float] == 1.0 was successful
test 1l.asInstanceOf[Float] == 1.0 was successful

test 1e1 == 10.0 was successful
test .3 == 0.3 was successful
test 0.0 == 0.0 was successful
test 0d == 0.0 was successful
test 01.23 == 1.23 was successful
test 01.23d == 1.23d was successful
test 3.14 == 3.14 was successful
test 1e-9d == 1.0e-9 was successful
test 1e137 == 1.0e137 was successful
test 1.asInstanceOf[Double] == 1.0 was successful
test 1l.asInstanceOf[Double] == 1.0 was successful

test "".length() was successful
test ggg == 3 was successful
literals.scala:34: warning: Octal escape literals are deprecated, use \u0061 instead.
check_success("\"\\141\\142\" == \"ab\"", "\141\142", "ab")
^
literals.scala:34: warning: Octal escape literals are deprecated, use \u0062 instead.
check_success("\"\\141\\142\" == \"ab\"", "\141\142", "ab")
^
literals.scala:37: warning: Octal escape literals are deprecated, use \u0000 instead.
"\0x61\0x62".getBytes(io.Codec.UTF8.charSet) sameElements Array[Byte](0, 120, 54, 49, 0, 120, 54, 50),
^
literals.scala:37: warning: Octal escape literals are deprecated, use \u0000 instead.
"\0x61\0x62".getBytes(io.Codec.UTF8.charSet) sameElements Array[Byte](0, 120, 54, 49, 0, 120, 54, 50),
^
1 change: 1 addition & 0 deletions test/files/run/literals.flags
@@ -0,0 +1 @@
-deprecation