From 27c4eb88f60dab989a1ac5e7ace8618796d98da3 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Sun, 29 Jul 2018 16:19:01 +1200 Subject: [PATCH] Improve exception when number cannot be read as type (#1782) --- Src/Newtonsoft.Json.Tests/Issues/Issue1778.cs | 81 ++++ .../Newtonsoft.Json.Tests.csproj | 3 + Src/Newtonsoft.Json/JsonTextReader.cs | 407 +++++++++--------- 3 files changed, 293 insertions(+), 198 deletions(-) create mode 100644 Src/Newtonsoft.Json.Tests/Issues/Issue1778.cs diff --git a/Src/Newtonsoft.Json.Tests/Issues/Issue1778.cs b/Src/Newtonsoft.Json.Tests/Issues/Issue1778.cs new file mode 100644 index 00000000..d344b955 --- /dev/null +++ b/Src/Newtonsoft.Json.Tests/Issues/Issue1778.cs @@ -0,0 +1,81 @@ +#region License +// Copyright (c) 2007 James Newton-King +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +#endregion + +#if !NET20 +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Reflection.Emit; +using System.Runtime.Serialization; +#if !(NET20 || NET35 || NET40 || PORTABLE40) +using System.Threading.Tasks; +#endif +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Serialization; +using Newtonsoft.Json.Utilities; +#if DNXCORE50 +using Xunit; +using Test = Xunit.FactAttribute; +using Assert = Newtonsoft.Json.Tests.XUnitAssert; +#else +using NUnit.Framework; +#endif + +namespace Newtonsoft.Json.Tests.Issues +{ + [TestFixture] + public class Issue1778 : TestFixtureBase + { + [Test] + public void Test() + { + JsonTextReader reader = new JsonTextReader(new StringReader(@"{""enddate"":-1}")); + reader.Read(); + reader.Read(); + + ExceptionAssert.Throws( + () => reader.ReadAsDateTime(), + "Cannot read number value as type. Path 'enddate', line 1, position 13."); + } + +#if !(NET20 || NET35 || NET40 || PORTABLE40) + [Test] + public async Task Test_Async() + { + JsonTextReader reader = new JsonTextReader(new StringReader(@"{""enddate"":-1}")); + reader.Read(); + reader.Read(); + + await ExceptionAssert.ThrowsAsync( + () => reader.ReadAsDateTimeAsync(), + "Cannot read number value as type. Path 'enddate', line 1, position 13."); + } +#endif + } +} +#endif \ No newline at end of file diff --git a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj index 960e8bc7..d6f88865 100644 --- a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj +++ b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj @@ -12,10 +12,13 @@ Newtonsoft.Json.Tests Newtonsoft.Json.Tests false + + Full false + PreserveNewest diff --git a/Src/Newtonsoft.Json/JsonTextReader.cs b/Src/Newtonsoft.Json/JsonTextReader.cs index 67512da2..948ce346 100644 --- a/Src/Newtonsoft.Json/JsonTextReader.cs +++ b/Src/Newtonsoft.Json/JsonTextReader.cs @@ -1940,236 +1940,247 @@ namespace Newtonsoft.Json bool singleDigit = (char.IsDigit(firstChar) && _stringReference.Length == 1); bool nonBase10 = (firstChar == '0' && _stringReference.Length > 1 && _stringReference.Chars[_stringReference.StartIndex + 1] != '.' && _stringReference.Chars[_stringReference.StartIndex + 1] != 'e' && _stringReference.Chars[_stringReference.StartIndex + 1] != 'E'); - if (readType == ReadType.ReadAsString) + switch (readType) { - string number = _stringReference.ToString(); - - // validate that the string is a valid number - if (nonBase10) - { - try + case ReadType.ReadAsString: { - if (number.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) + string number = _stringReference.ToString(); + + // validate that the string is a valid number + if (nonBase10) { - Convert.ToInt64(number, 16); + try + { + if (number.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) + { + Convert.ToInt64(number, 16); + } + else + { + Convert.ToInt64(number, 8); + } + } + catch (Exception ex) + { + throw ThrowReaderError("Input string '{0}' is not a valid number.".FormatWith(CultureInfo.InvariantCulture, number), ex); + } } else { - Convert.ToInt64(number, 8); - } - } - catch (Exception ex) - { - throw ThrowReaderError("Input string '{0}' is not a valid number.".FormatWith(CultureInfo.InvariantCulture, number), ex); - } - } - else - { - if (!double.TryParse(number, NumberStyles.Float, CultureInfo.InvariantCulture, out _)) - { - throw ThrowReaderError("Input string '{0}' is not a valid number.".FormatWith(CultureInfo.InvariantCulture, _stringReference.ToString())); - } - } - - numberType = JsonToken.String; - numberValue = number; - } - else if (readType == ReadType.ReadAsInt32) - { - if (singleDigit) - { - // digit char values start at 48 - numberValue = firstChar - 48; - } - else if (nonBase10) - { - string number = _stringReference.ToString(); - - try - { - int integer = number.StartsWith("0x", StringComparison.OrdinalIgnoreCase) ? Convert.ToInt32(number, 16) : Convert.ToInt32(number, 8); - - numberValue = integer; - } - catch (Exception ex) - { - throw ThrowReaderError("Input string '{0}' is not a valid integer.".FormatWith(CultureInfo.InvariantCulture, number), ex); - } - } - else - { - ParseResult parseResult = ConvertUtils.Int32TryParse(_stringReference.Chars, _stringReference.StartIndex, _stringReference.Length, out int value); - if (parseResult == ParseResult.Success) - { - numberValue = value; - } - else if (parseResult == ParseResult.Overflow) - { - throw ThrowReaderError("JSON integer {0} is too large or small for an Int32.".FormatWith(CultureInfo.InvariantCulture, _stringReference.ToString())); - } - else - { - throw ThrowReaderError("Input string '{0}' is not a valid integer.".FormatWith(CultureInfo.InvariantCulture, _stringReference.ToString())); - } - } - - numberType = JsonToken.Integer; - } - else if (readType == ReadType.ReadAsDecimal) - { - if (singleDigit) - { - // digit char values start at 48 - numberValue = (decimal)firstChar - 48; - } - else if (nonBase10) - { - string number = _stringReference.ToString(); - - try - { - // decimal.Parse doesn't support parsing hexadecimal values - long integer = number.StartsWith("0x", StringComparison.OrdinalIgnoreCase) ? Convert.ToInt64(number, 16) : Convert.ToInt64(number, 8); - - numberValue = Convert.ToDecimal(integer); - } - catch (Exception ex) - { - throw ThrowReaderError("Input string '{0}' is not a valid decimal.".FormatWith(CultureInfo.InvariantCulture, number), ex); - } - } - else - { - ParseResult parseResult = ConvertUtils.DecimalTryParse(_stringReference.Chars, _stringReference.StartIndex, _stringReference.Length, out decimal value); - if (parseResult == ParseResult.Success) - { - numberValue = value; - } - else - { - throw ThrowReaderError("Input string '{0}' is not a valid decimal.".FormatWith(CultureInfo.InvariantCulture, _stringReference.ToString())); - } - } - - numberType = JsonToken.Float; - } - else if (readType == ReadType.ReadAsDouble) - { - if (singleDigit) - { - // digit char values start at 48 - numberValue = (double)firstChar - 48; - } - else if (nonBase10) - { - string number = _stringReference.ToString(); - - try - { - // double.Parse doesn't support parsing hexadecimal values - long integer = number.StartsWith("0x", StringComparison.OrdinalIgnoreCase) ? Convert.ToInt64(number, 16) : Convert.ToInt64(number, 8); - - numberValue = Convert.ToDouble(integer); - } - catch (Exception ex) - { - throw ThrowReaderError("Input string '{0}' is not a valid double.".FormatWith(CultureInfo.InvariantCulture, number), ex); - } - } - else - { - string number = _stringReference.ToString(); - - if (double.TryParse(number, NumberStyles.Float, CultureInfo.InvariantCulture, out double value)) - { - numberValue = value; - } - else - { - throw ThrowReaderError("Input string '{0}' is not a valid double.".FormatWith(CultureInfo.InvariantCulture, _stringReference.ToString())); - } - } - - numberType = JsonToken.Float; - } - else - { - if (singleDigit) - { - // digit char values start at 48 - numberValue = (long)firstChar - 48; - numberType = JsonToken.Integer; - } - else if (nonBase10) - { - string number = _stringReference.ToString(); - - try - { - numberValue = number.StartsWith("0x", StringComparison.OrdinalIgnoreCase) ? Convert.ToInt64(number, 16) : Convert.ToInt64(number, 8); - } - catch (Exception ex) - { - throw ThrowReaderError("Input string '{0}' is not a valid number.".FormatWith(CultureInfo.InvariantCulture, number), ex); - } - - numberType = JsonToken.Integer; - } - else - { - ParseResult parseResult = ConvertUtils.Int64TryParse(_stringReference.Chars, _stringReference.StartIndex, _stringReference.Length, out long value); - if (parseResult == ParseResult.Success) - { - numberValue = value; - numberType = JsonToken.Integer; - } - else if (parseResult == ParseResult.Overflow) - { -#if HAVE_BIG_INTEGER - string number = _stringReference.ToString(); - - if (number.Length > MaximumJavascriptIntegerCharacterLength) - { - throw ThrowReaderError("JSON integer {0} is too large to parse.".FormatWith(CultureInfo.InvariantCulture, _stringReference.ToString())); + if (!double.TryParse(number, NumberStyles.Float, CultureInfo.InvariantCulture, out _)) + { + throw ThrowReaderError("Input string '{0}' is not a valid number.".FormatWith(CultureInfo.InvariantCulture, _stringReference.ToString())); + } } - numberValue = BigIntegerParse(number, CultureInfo.InvariantCulture); - numberType = JsonToken.Integer; -#else - throw ThrowReaderError("JSON integer {0} is too large or small for an Int64.".FormatWith(CultureInfo.InvariantCulture, _stringReference.ToString())); -#endif + numberType = JsonToken.String; + numberValue = number; } - else + break; + case ReadType.ReadAsInt32: { - if (_floatParseHandling == FloatParseHandling.Decimal) + if (singleDigit) { - parseResult = ConvertUtils.DecimalTryParse(_stringReference.Chars, _stringReference.StartIndex, _stringReference.Length, out decimal d); + // digit char values start at 48 + numberValue = firstChar - 48; + } + else if (nonBase10) + { + string number = _stringReference.ToString(); + + try + { + int integer = number.StartsWith("0x", StringComparison.OrdinalIgnoreCase) ? Convert.ToInt32(number, 16) : Convert.ToInt32(number, 8); + + numberValue = integer; + } + catch (Exception ex) + { + throw ThrowReaderError("Input string '{0}' is not a valid integer.".FormatWith(CultureInfo.InvariantCulture, number), ex); + } + } + else + { + ParseResult parseResult = ConvertUtils.Int32TryParse(_stringReference.Chars, _stringReference.StartIndex, _stringReference.Length, out int value); if (parseResult == ParseResult.Success) { - numberValue = d; + numberValue = value; + } + else if (parseResult == ParseResult.Overflow) + { + throw ThrowReaderError("JSON integer {0} is too large or small for an Int32.".FormatWith(CultureInfo.InvariantCulture, _stringReference.ToString())); + } + else + { + throw ThrowReaderError("Input string '{0}' is not a valid integer.".FormatWith(CultureInfo.InvariantCulture, _stringReference.ToString())); + } + } + + numberType = JsonToken.Integer; + } + break; + case ReadType.ReadAsDecimal: + { + if (singleDigit) + { + // digit char values start at 48 + numberValue = (decimal)firstChar - 48; + } + else if (nonBase10) + { + string number = _stringReference.ToString(); + + try + { + // decimal.Parse doesn't support parsing hexadecimal values + long integer = number.StartsWith("0x", StringComparison.OrdinalIgnoreCase) ? Convert.ToInt64(number, 16) : Convert.ToInt64(number, 8); + + numberValue = Convert.ToDecimal(integer); + } + catch (Exception ex) + { + throw ThrowReaderError("Input string '{0}' is not a valid decimal.".FormatWith(CultureInfo.InvariantCulture, number), ex); + } + } + else + { + ParseResult parseResult = ConvertUtils.DecimalTryParse(_stringReference.Chars, _stringReference.StartIndex, _stringReference.Length, out decimal value); + if (parseResult == ParseResult.Success) + { + numberValue = value; } else { throw ThrowReaderError("Input string '{0}' is not a valid decimal.".FormatWith(CultureInfo.InvariantCulture, _stringReference.ToString())); } } + + numberType = JsonToken.Float; + } + break; + case ReadType.ReadAsDouble: + { + if (singleDigit) + { + // digit char values start at 48 + numberValue = (double)firstChar - 48; + } + else if (nonBase10) + { + string number = _stringReference.ToString(); + + try + { + // double.Parse doesn't support parsing hexadecimal values + long integer = number.StartsWith("0x", StringComparison.OrdinalIgnoreCase) ? Convert.ToInt64(number, 16) : Convert.ToInt64(number, 8); + + numberValue = Convert.ToDouble(integer); + } + catch (Exception ex) + { + throw ThrowReaderError("Input string '{0}' is not a valid double.".FormatWith(CultureInfo.InvariantCulture, number), ex); + } + } else { string number = _stringReference.ToString(); - if (double.TryParse(number, NumberStyles.Float, CultureInfo.InvariantCulture, out double d)) + if (double.TryParse(number, NumberStyles.Float, CultureInfo.InvariantCulture, out double value)) { - numberValue = d; + numberValue = value; } else { - throw ThrowReaderError("Input string '{0}' is not a valid number.".FormatWith(CultureInfo.InvariantCulture, _stringReference.ToString())); + throw ThrowReaderError("Input string '{0}' is not a valid double.".FormatWith(CultureInfo.InvariantCulture, _stringReference.ToString())); } } numberType = JsonToken.Float; } - } + break; + case ReadType.Read: + case ReadType.ReadAsInt64: + { + if (singleDigit) + { + // digit char values start at 48 + numberValue = (long)firstChar - 48; + numberType = JsonToken.Integer; + } + else if (nonBase10) + { + string number = _stringReference.ToString(); + + try + { + numberValue = number.StartsWith("0x", StringComparison.OrdinalIgnoreCase) ? Convert.ToInt64(number, 16) : Convert.ToInt64(number, 8); + } + catch (Exception ex) + { + throw ThrowReaderError("Input string '{0}' is not a valid number.".FormatWith(CultureInfo.InvariantCulture, number), ex); + } + + numberType = JsonToken.Integer; + } + else + { + ParseResult parseResult = ConvertUtils.Int64TryParse(_stringReference.Chars, _stringReference.StartIndex, _stringReference.Length, out long value); + if (parseResult == ParseResult.Success) + { + numberValue = value; + numberType = JsonToken.Integer; + } + else if (parseResult == ParseResult.Overflow) + { +#if HAVE_BIG_INTEGER + string number = _stringReference.ToString(); + + if (number.Length > MaximumJavascriptIntegerCharacterLength) + { + throw ThrowReaderError("JSON integer {0} is too large to parse.".FormatWith(CultureInfo.InvariantCulture, _stringReference.ToString())); + } + + numberValue = BigIntegerParse(number, CultureInfo.InvariantCulture); + numberType = JsonToken.Integer; +#else + throw ThrowReaderError("JSON integer {0} is too large or small for an Int64.".FormatWith(CultureInfo.InvariantCulture, _stringReference.ToString())); +#endif + } + else + { + if (_floatParseHandling == FloatParseHandling.Decimal) + { + parseResult = ConvertUtils.DecimalTryParse(_stringReference.Chars, _stringReference.StartIndex, _stringReference.Length, out decimal d); + if (parseResult == ParseResult.Success) + { + numberValue = d; + } + else + { + throw ThrowReaderError("Input string '{0}' is not a valid decimal.".FormatWith(CultureInfo.InvariantCulture, _stringReference.ToString())); + } + } + else + { + string number = _stringReference.ToString(); + + if (double.TryParse(number, NumberStyles.Float, CultureInfo.InvariantCulture, out double d)) + { + numberValue = d; + } + else + { + throw ThrowReaderError("Input string '{0}' is not a valid number.".FormatWith(CultureInfo.InvariantCulture, _stringReference.ToString())); + } + } + + numberType = JsonToken.Float; + } + } + } + break; + default: + throw JsonReaderException.Create(this, "Cannot read number value as type."); } ClearRecentString();