Handle int overflows better; Support Nim 1.2
This commit is contained in:
parent
6350b72b5e
commit
6e9d69dafb
|
@ -12,6 +12,7 @@ type
|
||||||
tkEof,
|
tkEof,
|
||||||
tkString,
|
tkString,
|
||||||
tkInt,
|
tkInt,
|
||||||
|
tkNegativeInt,
|
||||||
tkFloat,
|
tkFloat,
|
||||||
tkTrue,
|
tkTrue,
|
||||||
tkFalse,
|
tkFalse,
|
||||||
|
@ -50,7 +51,7 @@ type
|
||||||
tok*: TokKind
|
tok*: TokKind
|
||||||
err*: JsonErrorKind
|
err*: JsonErrorKind
|
||||||
|
|
||||||
intVal*: int64
|
absIntVal*: uint64 # BEWARE: negative integers will have tok == tkNegativeInt
|
||||||
floatVal*: float
|
floatVal*: float
|
||||||
strVal*: string
|
strVal*: string
|
||||||
|
|
||||||
|
@ -84,7 +85,7 @@ proc init*(T: type JsonLexer, stream: ref AsciiStream, mode = defaultJsonMode):
|
||||||
tokenStart: -1,
|
tokenStart: -1,
|
||||||
tok: tkError,
|
tok: tkError,
|
||||||
err: errNone,
|
err: errNone,
|
||||||
intVal: int64 0,
|
absIntVal: uint64 0,
|
||||||
floatVal: 0'f,
|
floatVal: 0'f,
|
||||||
strVal: "")
|
strVal: "")
|
||||||
|
|
||||||
|
@ -271,15 +272,16 @@ proc scanNumber(lexer: var JsonLexer) =
|
||||||
lexer.tok = tkFloat
|
lexer.tok = tkFloat
|
||||||
c = lexer.stream[].peek()
|
c = lexer.stream[].peek()
|
||||||
elif c.isDigit:
|
elif c.isDigit:
|
||||||
lexer.tok = tkInt
|
lexer.tok = if sign > 0: tkInt
|
||||||
|
else: tkNegativeInt
|
||||||
let scannedValue = lexer.scanInt()
|
let scannedValue = lexer.scanInt()
|
||||||
checkForNonPortableInt scannedValue
|
checkForNonPortableInt scannedValue
|
||||||
lexer.intVal = int64(scannedValue) * sign
|
lexer.absIntVal = scannedValue
|
||||||
if lexer.stream[].eof: return
|
if lexer.stream[].eof: return
|
||||||
c = lexer.stream[].peek()
|
c = lexer.stream[].peek()
|
||||||
if c == '.':
|
if c == '.':
|
||||||
lexer.tok = tkFloat
|
lexer.tok = tkFloat
|
||||||
lexer.floatVal = float(lexer.intVal)
|
lexer.floatVal = float(lexer.absIntVal) * float(sign)
|
||||||
c = eatDigitAndPeek()
|
c = eatDigitAndPeek()
|
||||||
else:
|
else:
|
||||||
error errNumberExpected
|
error errNumberExpected
|
||||||
|
|
|
@ -37,6 +37,15 @@ type
|
||||||
encountedToken*: TokKind
|
encountedToken*: TokKind
|
||||||
expectedToken*: ExpectedTokenCategory
|
expectedToken*: ExpectedTokenCategory
|
||||||
|
|
||||||
|
IntOverflowError* = object of JsonReaderError
|
||||||
|
isNegative: bool
|
||||||
|
absIntVal: uint64
|
||||||
|
|
||||||
|
func valueStr(err: ref IntOverflowError): string =
|
||||||
|
if err.isNegative:
|
||||||
|
result.add '-'
|
||||||
|
result.add($err.absIntVal)
|
||||||
|
|
||||||
method formatMsg*(err: ref JsonReaderError, filename: string): string =
|
method formatMsg*(err: ref JsonReaderError, filename: string): string =
|
||||||
fmt"{filename}({err.line}, {err.col}) Error while reading json file"
|
fmt"{filename}({err.line}, {err.col}) Error while reading json file"
|
||||||
|
|
||||||
|
@ -49,6 +58,9 @@ method formatMsg*(err: ref UnexpectedToken, filename: string): string =
|
||||||
method formatMsg*(err: ref GenericJsonReaderError, filename: string): string =
|
method formatMsg*(err: ref GenericJsonReaderError, filename: string): string =
|
||||||
fmt"{filename}({err.line}, {err.col}) Exception encountered while deserializing '{err.deserializedField}': [{err.innerException.name}] {err.innerException.msg}"
|
fmt"{filename}({err.line}, {err.col}) Exception encountered while deserializing '{err.deserializedField}': [{err.innerException.name}] {err.innerException.msg}"
|
||||||
|
|
||||||
|
method formatMsg*(err: ref IntOverflowError, filename: string): string =
|
||||||
|
fmt"{filename}({err.line}, {err.col}) The value '{err.valueStr}' is outside of the allowed range"
|
||||||
|
|
||||||
template init*(T: type JsonReader, stream: ByteStreamVar, mode = defaultJsonMode): auto =
|
template init*(T: type JsonReader, stream: ByteStreamVar, mode = defaultJsonMode): auto =
|
||||||
init JsonReader, AsciiStreamVar(stream), mode
|
init JsonReader, AsciiStreamVar(stream), mode
|
||||||
|
|
||||||
|
@ -56,14 +68,21 @@ proc assignLineNumber*(ex: ref JsonReaderError, r: JsonReader) =
|
||||||
ex.line = r.lexer.line
|
ex.line = r.lexer.line
|
||||||
ex.col = r.lexer.tokenStartCol
|
ex.col = r.lexer.tokenStartCol
|
||||||
|
|
||||||
proc raiseUnexpectedToken*(r: JsonReader, expected: ExpectedTokenCategory) =
|
proc raiseUnexpectedToken*(r: JsonReader, expected: ExpectedTokenCategory) {.noreturn.} =
|
||||||
var ex = new UnexpectedToken
|
var ex = new UnexpectedToken
|
||||||
ex.assignLineNumber(r)
|
ex.assignLineNumber(r)
|
||||||
ex.encountedToken = r.lexer.tok
|
ex.encountedToken = r.lexer.tok
|
||||||
ex.expectedToken = expected
|
ex.expectedToken = expected
|
||||||
raise ex
|
raise ex
|
||||||
|
|
||||||
proc raiseUnexpectedField*(r: JsonReader, fieldName, deserializedType: cstring) =
|
proc raiseIntOverflow*(r: JsonReader, absIntVal: uint64, isNegative: bool) {.noreturn.} =
|
||||||
|
var ex = new IntOverflowError
|
||||||
|
ex.assignLineNumber(r)
|
||||||
|
ex.absIntVal = absIntVal
|
||||||
|
ex.isNegative = isNegative
|
||||||
|
raise ex
|
||||||
|
|
||||||
|
proc raiseUnexpectedField*(r: JsonReader, fieldName, deserializedType: cstring) {.noreturn.} =
|
||||||
var ex = new UnexpectedField
|
var ex = new UnexpectedField
|
||||||
ex.assignLineNumber(r)
|
ex.assignLineNumber(r)
|
||||||
ex.encounteredField = fieldName
|
ex.encounteredField = fieldName
|
||||||
|
@ -92,7 +111,7 @@ proc requireToken*(r: JsonReader, tk: TokKind) =
|
||||||
if r.lexer.tok != tk:
|
if r.lexer.tok != tk:
|
||||||
r.raiseUnexpectedToken case tk
|
r.raiseUnexpectedToken case tk
|
||||||
of tkString: etString
|
of tkString: etString
|
||||||
of tkInt: etInt
|
of tkInt, tkNegativeInt: etInt
|
||||||
of tkComma: etComma
|
of tkComma: etComma
|
||||||
of tkBracketRi: etBracketRi
|
of tkBracketRi: etBracketRi
|
||||||
of tkBracketLe: etBracketLe
|
of tkBracketLe: etBracketLe
|
||||||
|
@ -141,6 +160,13 @@ iterator readObject*(r: var JsonReader, KeyType: typedesc, ValueType: typedesc):
|
||||||
r.lexer.next()
|
r.lexer.next()
|
||||||
r.skipToken tkCurlyRi
|
r.skipToken tkCurlyRi
|
||||||
|
|
||||||
|
func maxAbsValue(T: type[SomeInteger]): uint64 {.compileTime.} =
|
||||||
|
when T is int8 : 128'u64
|
||||||
|
elif T is int16: 32768'u64
|
||||||
|
elif T is int32: 2147483648'u64
|
||||||
|
elif T is int64: 9223372036854775808'u64
|
||||||
|
else: uint64(high(T))
|
||||||
|
|
||||||
proc readValue*(r: var JsonReader, value: var auto) =
|
proc readValue*(r: var JsonReader, value: var auto) =
|
||||||
mixin readValue
|
mixin readValue
|
||||||
|
|
||||||
|
@ -173,20 +199,39 @@ proc readValue*(r: var JsonReader, value: var auto) =
|
||||||
value.setParsed(r.lexer.strVal)
|
value.setParsed(r.lexer.strVal)
|
||||||
of tkInt:
|
of tkInt:
|
||||||
# TODO: validate that the value is in range
|
# TODO: validate that the value is in range
|
||||||
value = type(value)(r.lexer.intVal)
|
value = type(value)(r.lexer.absIntVal)
|
||||||
else:
|
else:
|
||||||
r.raiseUnexpectedToken etEnum
|
r.raiseUnexpectedToken etEnum
|
||||||
r.lexer.next()
|
r.lexer.next()
|
||||||
|
|
||||||
elif value is SomeInteger:
|
elif value is SomeInteger:
|
||||||
type TargetType = type(value)
|
type TargetType = type(value)
|
||||||
r.requireToken tkInt
|
const maxValidValue = maxAbsValue(TargetType)
|
||||||
value = TargetType(r.lexer.intVal)
|
|
||||||
|
if r.lexer.absIntVal > maxValidValue:
|
||||||
|
r.raiseIntOverflow r.lexer.absIntVal, tok == tkNegativeInt
|
||||||
|
|
||||||
|
case tok
|
||||||
|
of tkInt:
|
||||||
|
value = TargetType(r.lexer.absIntVal)
|
||||||
|
of tkNegativeInt:
|
||||||
|
when value is SomeSignedInt:
|
||||||
|
if r.lexer.absIntVal == maxValidValue:
|
||||||
|
# We must handle this as a special case because it would be illegal
|
||||||
|
# to convert a value like 128 to int8 before negating it. The max
|
||||||
|
# int8 value is 127 (while the minimum is -128).
|
||||||
|
value = low(TargetType)
|
||||||
|
else:
|
||||||
|
value = -TargetType(r.lexer.absIntVal)
|
||||||
|
else:
|
||||||
|
r.raiseIntOverflow r.lexer.absIntVal, true
|
||||||
|
else:
|
||||||
|
r.raiseUnexpectedToken etInt
|
||||||
r.lexer.next()
|
r.lexer.next()
|
||||||
|
|
||||||
elif value is SomeFloat:
|
elif value is SomeFloat:
|
||||||
case tok
|
case tok
|
||||||
of tkInt: value = float(r.lexer.intVal)
|
of tkInt: value = float(r.lexer.absIntVal)
|
||||||
of tkFloat: value = r.lexer.floatVal
|
of tkFloat: value = r.lexer.floatVal
|
||||||
else:
|
else:
|
||||||
r.raiseUnexpectedToken etNumber
|
r.raiseUnexpectedToken etNumber
|
||||||
|
|
Loading…
Reference in New Issue