From 08e1bc54b81f78c0a15ff740d1817659027e7570 Mon Sep 17 00:00:00 2001 From: fox0430 Date: Fri, 29 May 2026 18:11:57 +0900 Subject: [PATCH] Reject out-of-range integer text values in getInt/getInt16 --- async_postgres/pg_types/accessors.nim | 32 +++++++++++++++++++++--- tests/test_types.nim | 36 +++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 4 deletions(-) diff --git a/async_postgres/pg_types/accessors.nim b/async_postgres/pg_types/accessors.nim index a1918b4..5954fd8 100644 --- a/async_postgres/pg_types/accessors.nim +++ b/async_postgres/pg_types/accessors.nim @@ -223,6 +223,10 @@ proc getInt*(row: Row, col: int): int32 = var v: int if parseInt(row.bufView(off, clen), v) == 0: raise newException(PgTypeError, "Column " & $col & ": invalid integer value") + if v < int(int32.low) or v > int(int32.high): + raise newException( + PgTypeError, "Column " & $col & ": integer value out of int32 range: " & $v + ) result = int32(v) proc getInt16*(row: Row, col: int): int16 = @@ -242,6 +246,10 @@ proc getInt16*(row: Row, col: int): int16 = var v: int if parseInt(row.bufView(off, clen), v) == 0: raise newException(PgTypeError, "Column " & $col & ": invalid int16 value") + if v < int(int16.low) or v > int(int16.high): + raise newException( + PgTypeError, "Column " & $col & ": integer value out of int16 range: " & $v + ) result = int16(v) proc getInt64*(row: Row, col: int): int64 = @@ -899,6 +907,22 @@ optAccessor(getPath, getPathOpt, PgPath) optAccessor(getPolygon, getPolygonOpt, PgPolygon) optAccessor(getCircle, getCircleOpt, PgCircle) +proc parseInt32Elem(s: string): int32 = + ## Parse a text array element into int32, rejecting out-of-range values. + ## Plain ``int32(parseInt)`` would silently truncate (wrap) in release builds. + let v = parseInt(s) + if v < int(int32.low) or v > int(int32.high): + raise newException(PgTypeError, "integer array element out of int32 range: " & $v) + int32(v) + +proc parseInt16Elem(s: string): int16 = + ## Parse a text array element into int16, rejecting out-of-range values. + ## Plain ``int16(parseInt)`` would silently truncate (wrap) in release builds. + let v = parseInt(s) + if v < int(int16.low) or v > int(int16.high): + raise newException(PgTypeError, "integer array element out of int16 range: " & $v) + int16(v) + proc getIntArray*(row: Row, col: int): seq[int32] = ## Get a column value as a seq of int32. Handles binary array format. if row.isBinaryCol(col): @@ -923,7 +947,7 @@ proc getIntArray*(row: Row, col: int): seq[int32] = for e in elems: if e.isNone: raise newException(PgTypeError, "NULL element in int array") - result.add(int32(parseInt(e.get))) + result.add(parseInt32Elem(e.get)) proc getInt16Array*(row: Row, col: int): seq[int16] = ## Get a column value as a seq of int16. Handles binary array format. @@ -949,7 +973,7 @@ proc getInt16Array*(row: Row, col: int): seq[int16] = for e in elems: if e.isNone: raise newException(PgTypeError, "NULL element in int16 array") - result.add(int16(parseInt(e.get))) + result.add(parseInt16Elem(e.get)) proc getInt64Array*(row: Row, col: int): seq[int64] = ## Get a column value as a seq of int64. Handles binary array format. @@ -1631,7 +1655,7 @@ proc getIntArrayElemOpt*(row: Row, col: int): seq[Option[int32]] = if e.isNone: result.add(none(int32)) else: - result.add(some(int32(parseInt(e.get)))) + result.add(some(parseInt32Elem(e.get))) proc getInt16ArrayElemOpt*(row: Row, col: int): seq[Option[int16]] = if row.isBinaryCol(col): @@ -1653,7 +1677,7 @@ proc getInt16ArrayElemOpt*(row: Row, col: int): seq[Option[int16]] = if e.isNone: result.add(none(int16)) else: - result.add(some(int16(parseInt(e.get)))) + result.add(some(parseInt16Elem(e.get))) proc getInt64ArrayElemOpt*(row: Row, col: int): seq[Option[int64]] = if row.isBinaryCol(col): diff --git a/tests/test_types.nim b/tests/test_types.nim index 3c70057..514a569 100644 --- a/tests/test_types.nim +++ b/tests/test_types.nim @@ -325,6 +325,14 @@ suite "Row accessors": expect PgTypeError: discard row.getInt16(0) + test "getInt16 out of range raises (no silent truncation)": + let row = @[some(toBytes("32768"))] + expect PgTypeError: + discard row.getInt16(0) + let rowNeg = @[some(toBytes("-32769"))] + expect PgTypeError: + discard rowNeg.getInt16(0) + test "getInt": let row = @[some(toBytes("42"))] check row.getInt(0) == 42'i32 @@ -338,6 +346,14 @@ suite "Row accessors": expect PgTypeError: discard row.getInt(0) + test "getInt out of range raises (no silent truncation)": + let row = @[some(toBytes("2147483648"))] + expect PgTypeError: + discard row.getInt(0) + let rowNeg = @[some(toBytes("-2147483649"))] + expect PgTypeError: + discard rowNeg.getInt(0) + test "getInt64": let row = @[some(toBytes("9999999999"))] check row.getInt64(0) == 9999999999'i64 @@ -1743,10 +1759,30 @@ suite "Array row accessors": let row: Row = @[some(toBytes("{}"))] check row.getIntArray(0).len == 0 + test "getIntArray out of range raises (no silent truncation)": + let row: Row = @[some(toBytes("{1,2147483648}"))] + expect PgTypeError: + discard row.getIntArray(0) + test "getInt16Array": let row: Row = @[some(toBytes("{10,-20}"))] check row.getInt16Array(0) == @[10'i16, -20'i16] + test "getInt16Array out of range raises (no silent truncation)": + let row: Row = @[some(toBytes("{10,32768}"))] + expect PgTypeError: + discard row.getInt16Array(0) + + test "getIntArrayElemOpt out of range raises (no silent truncation)": + let row: Row = @[some(toBytes("{1,NULL,-2147483649}"))] + expect PgTypeError: + discard row.getIntArrayElemOpt(0) + + test "getInt16ArrayElemOpt out of range raises (no silent truncation)": + let row: Row = @[some(toBytes("{1,NULL,-32769}"))] + expect PgTypeError: + discard row.getInt16ArrayElemOpt(0) + test "getInt64Array": let row: Row = @[some(toBytes("{9999999999,-1}"))] check row.getInt64Array(0) == @[9999999999'i64, -1'i64]