Skip to content

Commit

Permalink
Fix parseBiggestUInt to detect overflow (#24649)
Browse files Browse the repository at this point in the history
With some inputs larger than `BiggestUInt.high`, `parseBiggestUInt` proc
in `parseutils.nim` fails to detect overflow and returns random value.
This is because `rawParseUInt` try to detects overflow with `if prev >
res:` but it doesn't detects the overflow from multiplication.
It is possible that `x *= 10` causes overflow and resulting value is
larger than original value.
Here is example values larger than `BiggestUInt.high` but
`parseBiggestUInt` returns without detecting overflow:
```
22751622367522324480000000
41404969074137497600000000
20701551093035827200000000000000000
22546225502460313600000000000000000
204963831854661632000000000000000000
```

Following code search for values larger than `BiggestUInt.high` and
`parseBiggestUInt` cannot detect overflow:
```nim
import std/[strutils]

const
  # Increase this to extend search range
  NBits = 34'u
  NBitsMax1 = 1'u shl NBits
  NBitsMax = NBitsMax1 - 1'u

  # Increase this when there are too many results and want to see only larger result.
  MinMultiply10 = 14

var nfound = 0
for i in (NBitsMax div 10'u + 1'u) .. NBitsMax:
  var
    x = i
    n10 = 0
  for j in 0 ..< NBits:
    let px = x
    x = (x * 10'u) and NBitsMax
    if x < px:
      break
    inc n10
  if n10 >= MinMultiply10:
    echo "i =   ", i
    echo "uint: ", (i shl (64'u - NBits)), '0'.repeat n10
    inc nfound
    if nfound > 15:
      break

echo "found: ", nfound
```

(cherry picked from commit 95b1dda)
  • Loading branch information
demotomohiro authored and narimiran committed Jan 27, 2025
1 parent c2b8257 commit 2193c3f
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 3 deletions.
8 changes: 5 additions & 3 deletions lib/pure/parseutils.nim
Original file line number Diff line number Diff line change
Expand Up @@ -526,16 +526,18 @@ proc parseSaturatedNatural*(s: openArray[char], b: var int): int {.
proc rawParseUInt(s: openArray[char], b: var BiggestUInt): int =
var
res = 0.BiggestUInt
prev = 0.BiggestUInt
i = 0
if i < s.len - 1 and s[i] == '-' and s[i + 1] in {'0'..'9'}:
integerOutOfRangeError()
if i < s.len and s[i] == '+': inc(i) # Allow
if i < s.len and s[i] in {'0'..'9'}:
b = 0
while i < s.len and s[i] in {'0'..'9'}:
prev = res
res = res * 10 + (ord(s[i]) - ord('0')).BiggestUInt
if res > BiggestUInt.high div 10: # Highest value that you can multiply 10 without overflow
integerOutOfRangeError()
res = res * 10
let prev = res
res += (ord(s[i]) - ord('0')).BiggestUInt
if prev > res:
integerOutOfRangeError()
inc(i)
Expand Down
48 changes: 48 additions & 0 deletions tests/stdlib/tparseuints.nim
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,54 @@ import unittest, strutils

block: # parseutils
check: parseBiggestUInt("0") == 0'u64
check: parseBiggestUInt("1") == 1'u64
check: parseBiggestUInt("2") == 2'u64
check: parseBiggestUInt("10") == 10'u64
check: parseBiggestUInt("11") == 11'u64
check: parseBiggestUInt("99") == 99'u64
check: parseBiggestUInt("123") == 123'u64
check: parseBiggestUInt("9876") == 9876'u64
check: parseBiggestUInt("1_234") == 1234'u64
check: parseBiggestUInt("123__4") == 1234'u64
for i in 1.BiggestUInt .. 9.BiggestUInt:
var x = i
for j in 1 .. 19:
check parseBiggestUInt((i + '0'.uint).char.repeat j) == x
x *= 10
x += i
check: parseBiggestUInt("18446744073709551609") == 0xFFFF_FFFF_FFFF_FFF9'u64
check: parseBiggestUInt("18446744073709551610") == 0xFFFF_FFFF_FFFF_FFFA'u64
check: parseBiggestUInt("18446744073709551611") == 0xFFFF_FFFF_FFFF_FFFB'u64
check: parseBiggestUInt("18446744073709551612") == 0xFFFF_FFFF_FFFF_FFFC'u64
check: parseBiggestUInt("18446744073709551613") == 0xFFFF_FFFF_FFFF_FFFD'u64
check: parseBiggestUInt("18446744073709551614") == 0xFFFF_FFFF_FFFF_FFFE'u64
check: parseBiggestUInt("18446744073709551615") == 0xFFFF_FFFF_FFFF_FFFF'u64
expect(ValueError):
discard parseBiggestUInt("18446744073709551616")
expect(ValueError):
discard parseBiggestUInt("18446744073709551617")
expect(ValueError):
discard parseBiggestUInt("18446744073709551618")
expect(ValueError):
discard parseBiggestUInt("18446744073709551619")
expect(ValueError):
discard parseBiggestUInt("18446744073709551620")
expect(ValueError):
discard parseBiggestUInt("18446744073709551621")
expect(ValueError):
discard parseBiggestUInt("18446744073709551622")
expect(ValueError):
discard parseBiggestUInt("18446744073709551623")
expect(ValueError):
for i in 0 .. 999:
discard parseBiggestUInt("18446744073709552" & intToStr(i, 3))
expect(ValueError):
discard parseBiggestUInt("22751622367522324480000000")
expect(ValueError):
discard parseBiggestUInt("41404969074137497600000000")
expect(ValueError):
discard parseBiggestUInt("20701551093035827200000000000000000")
expect(ValueError):
discard parseBiggestUInt("225462255024603136000000000000000000")
expect(ValueError):
discard parseBiggestUInt("204963831854661632000000000000000000")

0 comments on commit 2193c3f

Please sign in to comment.