From ff7cbed6df0fa3c6fe1b1350415b2b08b03b746d Mon Sep 17 00:00:00 2001 From: Brian Bennewitz Date: Thu, 20 Mar 2025 11:18:05 -0500 Subject: [PATCH] SNOW-944787 Fix overflow when parsing values outside range of `Int64` SNOW-944787 Fix overflow when parsing values outside range of `Int64` - created a `FastParser.TryFastParseInt64` that will return false instead `throw` an `OverflowException` - added check in `SFDataConverter.ConvertToCSharpVal` to return `string` if an overflow of `Int64` would occur - added test cases to existing test `TestConvertToInt64` (https://github.com/snowflakedb/snowflake-connector-net/issues/797) --- .../UnitTests/SFDataConverterTest.cs | 33 ++++++++++++++----- Snowflake.Data/Core/FastParser.cs | 26 +++++++++++---- Snowflake.Data/Core/SFDataConverter.cs | 12 ++++++- 3 files changed, 55 insertions(+), 16 deletions(-) diff --git a/Snowflake.Data.Tests/UnitTests/SFDataConverterTest.cs b/Snowflake.Data.Tests/UnitTests/SFDataConverterTest.cs index 435175494..1a944d46b 100755 --- a/Snowflake.Data.Tests/UnitTests/SFDataConverterTest.cs +++ b/Snowflake.Data.Tests/UnitTests/SFDataConverterTest.cs @@ -154,15 +154,32 @@ private void internalTestConvertDate(DateTime dtExpected, DateTime testValue) } [Test] - [TestCase("9223372036854775807")] - [TestCase("-9223372036854775808")] - [TestCase("-1")] - [TestCase("999999999999999999")] - public void TestConvertToInt64(string s) + [TestCase("0", false)] + [TestCase("-1", false)] + [TestCase("1", false)] + [TestCase("999999999999999999", false)] + [TestCase("-9223372036854775808", false)] //C# `Int64.MinValue` + [TestCase("9223372036854775807", false)] //C# `Int64.MaxValue` + [TestCase("-9223372036854775809", true)] //C# Just below `Int64.MinValue` + [TestCase("9223372036854775809", true)] //C# Just above `Int64.MaxValue` + [TestCase("100000000000000000000", true)] //Significantly larger than C# `Int64.MinValue` + [TestCase("-100000000000000000000", true)] //Significantly smaller than C# `Int64.MinValue` + [TestCase("-4611686018427387904", false)] //OCaml 63-bit Integer min + [TestCase("4611686018427387903", false)] //OCaml 63-bit Integer max + public void TestConvertToInt64(string s, bool willOverflow) { - Int64 actual = (Int64)SFDataConverter.ConvertToCSharpVal(ConvertToUTF8Buffer(s), SFDataType.FIXED, typeof(Int64)); - Int64 expected = Convert.ToInt64(s); - Assert.AreEqual(expected, actual); + if (willOverflow) + { + string actual = (string)SFDataConverter.ConvertToCSharpVal(ConvertToUTF8Buffer(s), SFDataType.FIXED, typeof(Int64)); + string expected = s; + Assert.AreEqual(expected, actual); + } + else + { + Int64 actual = (Int64)SFDataConverter.ConvertToCSharpVal(ConvertToUTF8Buffer(s), SFDataType.FIXED, typeof(Int64)); + Int64 expected = Convert.ToInt64(s); + Assert.AreEqual(expected, actual); + } } [Test] diff --git a/Snowflake.Data/Core/FastParser.cs b/Snowflake.Data/Core/FastParser.cs index bb06f1eaf..7229b061d 100644 --- a/Snowflake.Data/Core/FastParser.cs +++ b/Snowflake.Data/Core/FastParser.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.CompilerServices; using Snowflake.Data.Client; using Snowflake.Data.Log; @@ -8,7 +9,8 @@ public class FastParser { private static readonly SFLogger Logger = SFLoggerFactory.GetLogger(); - public static Int64 FastParseInt64(byte[] s, int offset, int len) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryFastParseInt64(byte[] s, int offset, int len, out Int64 result) { if (s == null) { @@ -17,7 +19,7 @@ public static Int64 FastParseInt64(byte[] s, int offset, int len) throw ex; } - Int64 result = 0; + result = 0; int i = offset; bool isMinus = false; if (len > 0 && s[i] == '-') @@ -29,7 +31,7 @@ public static Int64 FastParseInt64(byte[] s, int offset, int len) for (; i < end; i++) { if ((UInt64)result > (0x7fffffffffffffff / 10)) - throw new OverflowException(); + return false; int c = s[i] - '0'; if (c < 0 || c > 9) throw new FormatException(); @@ -39,14 +41,24 @@ public static Int64 FastParseInt64(byte[] s, int offset, int len) { result = -result; if (result > 0) - throw new OverflowException(); + return false; } else { if (result < 0) - throw new OverflowException(); + return false; } - return result; + return true; + } + + public static Int64 FastParseInt64(byte[] s, int offset, int len) + { + if(TryFastParseInt64(s, offset, len, out Int64 result)) + { + return result; + } + + throw new OverflowException(); } public static Int32 FastParseInt32(byte[] s, int offset, int len) @@ -108,7 +120,7 @@ public static decimal FastParseDecimal(byte[] s, int offset, int len) if (decimalPos < 0) { // If len > 19 (the number of digits in int64.MaxValue), the value is likely bigger - // than max int64. Potentially, if it is a negative number it could be ok, but it + // than max int64. Potentially, if it is a negative number it could be ok, but it // is better to not to find out during the call to FastParseInt64. // Fallback to regular decimal constructor from string instead. if (len > 19) diff --git a/Snowflake.Data/Core/SFDataConverter.cs b/Snowflake.Data/Core/SFDataConverter.cs index 28651588e..24d334e7b 100755 --- a/Snowflake.Data/Core/SFDataConverter.cs +++ b/Snowflake.Data/Core/SFDataConverter.cs @@ -51,7 +51,17 @@ internal static object ConvertToCSharpVal(UTF8Buffer srcVal, SFDataType srcType, // The most common conversions are checked first for maximum performance if (destType == typeof(Int64)) { - return FastParser.FastParseInt64(srcVal.Buffer, srcVal.offset, srcVal.length); + if (srcVal.length > 8) //may overflow `Int64` (SNOW-944787, GitHub Issue#797) + { + if(FastParser.TryFastParseInt64(srcVal.Buffer, srcVal.offset, srcVal.length, out long result)) + { + return result; + } + //in the future this method should probably support `System.Numerics.BigInteger` instead + return UTF8Buffer.UTF8.GetString(srcVal.Buffer, srcVal.offset, srcVal.length); + } + else + return FastParser.FastParseInt64(srcVal.Buffer, srcVal.offset, srcVal.length); } else if (destType == typeof(Int32)) {