Skip to content

Commit

Permalink
implement box avoidance (#2639)
Browse files Browse the repository at this point in the history
Co-authored-by: Marc Gravell <marcgravell@microsoft.com>
Co-authored-by: James Newton-King <james@newtonking.com>
  • Loading branch information
3 people committed May 15, 2022
1 parent 197c6db commit 13717cf
Show file tree
Hide file tree
Showing 10 changed files with 302 additions and 57 deletions.
2 changes: 1 addition & 1 deletion Src/Newtonsoft.Json.Tests/Issues/Issue2484.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
// OTHER DEALINGS IN THE SOFTWARE.
#endregion

#if (NET45 || NET50)
#if (NET45 || NET5_0_OR_GREATER)
#if DNXCORE50
using Xunit;
using Test = Xunit.FactAttribute;
Expand Down
2 changes: 1 addition & 1 deletion Src/Newtonsoft.Json.Tests/Issues/Issue2492.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
// OTHER DEALINGS IN THE SOFTWARE.
#endregion

#if (NET45 || NET50)
#if (NET45 || NET5_0_OR_GREATER)
#if DNXCORE50
using Xunit;
using Test = Xunit.FactAttribute;
Expand Down
2 changes: 1 addition & 1 deletion Src/Newtonsoft.Json.Tests/Issues/Issue2504.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
// OTHER DEALINGS IN THE SOFTWARE.
#endregion

#if (NET45 || NET50)
#if (NET45 || NET5_0_OR_GREATER)
#if DNXCORE50
using Xunit;
using Test = Xunit.FactAttribute;
Expand Down
2 changes: 1 addition & 1 deletion Src/Newtonsoft.Json.Tests/Issues/Issue2529.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
// OTHER DEALINGS IN THE SOFTWARE.
#endregion

#if (NET45 || NET50)
#if (NET45 || NET5_0_OR_GREATER)
#if DNXCORE50
using Xunit;
using Test = Xunit.FactAttribute;
Expand Down
129 changes: 129 additions & 0 deletions Src/Newtonsoft.Json.Tests/Issues/Issue2638.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
#region License
// Copyright (c) 2022 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

using Newtonsoft.Json.Linq;
using System.Globalization;
using Newtonsoft.Json.Tests.Documentation.Samples.Linq;

#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 Issue2638
{
[Test]
public void DeserilizeUsesSharedBooleans()
{
Test(true);
Test(false);

static void Test(bool value)
{
var obj = (JObject)JToken.Parse(@"{""x"": XXX, ""y"": XXX}".Replace("XXX", value ? "true" : "false"));
var x = ((JValue)obj["x"]).Value;
var y = ((JValue)obj["y"]).Value;

Assert.AreEqual(value, (bool)x);
Assert.AreEqual(value, (bool)y);
Assert.AreSame(x, y);
}
}

[Test]
public void DeserilizeUsesSharedDoubleZeros()
{
Test(0, true);
Test(double.NaN, true);
Test(double.NegativeInfinity, true);
Test(double.PositiveInfinity, true);
Test(1, false);
Test(42.42, false);

static void Test(double value, bool expectSame)
{
var obj = (JObject)JToken.Parse(@"{""x"": XXX, ""y"": XXX}".Replace("XXX", value.ToString("0.0###", CultureInfo.InvariantCulture)));
var x = ((JValue)obj["x"]).Value;
var y = ((JValue)obj["y"]).Value;

Assert.AreEqual(value, (double)x);
Assert.AreEqual(value, (double)y);
if (expectSame)
{
Assert.AreSame(x, y);
}
else
{
Assert.AreNotSame(x, y);
}
var unboxed = (double)x;
Assert.AreEqual(double.IsNaN(value), double.IsNaN(unboxed));
Assert.AreEqual(double.IsPositiveInfinity(value), double.IsPositiveInfinity(unboxed));
Assert.AreEqual(double.IsNegativeInfinity(value), double.IsNegativeInfinity(unboxed));
}
}

[Test]
public void DeserilizeUsesSharedSmallInt64()
{
Test(-2, false);
Test(-1, true);
Test(0, true);
Test(1, true);
Test(2, true);
Test(3, true);
Test(4, true);
Test(5, true);
Test(6, true);
Test(7, true);
Test(8, true);
Test(9, false);

static void Test(long value, bool expectSame)
{
var obj = (JObject)JToken.Parse(@"{""x"": XXX, ""y"": XXX}".Replace("XXX", value.ToString(CultureInfo.InvariantCulture)));
var x = ((JValue)obj["x"]).Value;
var y = ((JValue)obj["y"]).Value;

Assert.AreEqual(value, (long)x);
Assert.AreEqual(value, (long)y);
if (expectSame)
{
Assert.AreSame(x, y);
}
else
{
Assert.AreNotSame(x, y);
}
}
}
}
}
2 changes: 1 addition & 1 deletion Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<RootNamespace>Newtonsoft.Json.Tests</RootNamespace>
<IsPackable>false</IsPackable>
<!-- Workaround for https://github.com/nunit/nunit3-vs-adapter/issues/296 -->
<DebugType Condition="'$(TargetFramework)' != '' AND '$(TargetFramework)' != 'netcoreapp2.1' AND '$(TargetFramework)' != 'netcoreapp3.1' AND '$(TargetFramework)' != 'net5.0'">Full</DebugType>
<DebugType Condition="'$(TargetFramework)' != '' AND '$(TargetFramework)' != 'netcoreapp2.1' AND '$(TargetFramework)' != 'netcoreapp3.1' AND '$(TargetFramework)' != 'net6.0'">Full</DebugType>
<!-- Disabled because SourceLink isn't referenced to calculate paths -->
<DeterministicSourcePaths>false</DeterministicSourcePaths>
<!-- It's ok if a test target has exited support. Disable NETSSDK1138 warning -->
Expand Down
40 changes: 20 additions & 20 deletions Src/Newtonsoft.Json/JsonTextReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -841,7 +841,7 @@ private JsonReaderException CreateUnexpectedCharacterException(char c)
{
throw CreateUnexpectedCharacterException(_chars[_charPos]);
}
SetToken(JsonToken.Boolean, isTrue);
SetToken(JsonToken.Boolean, BoxedPrimitives.Get(isTrue));
return isTrue;
case '/':
ParseComment(false);
Expand Down Expand Up @@ -2030,7 +2030,7 @@ private void ParseReadNumber(ReadType readType, char firstChar, int initialPosit
if (singleDigit)
{
// digit char values start at 48
numberValue = firstChar - 48;
numberValue = BoxedPrimitives.Get(firstChar - 48);
}
else if (nonBase10)
{
Expand All @@ -2040,7 +2040,7 @@ private void ParseReadNumber(ReadType readType, char firstChar, int initialPosit
{
int integer = number.StartsWith("0x", StringComparison.OrdinalIgnoreCase) ? Convert.ToInt32(number, 16) : Convert.ToInt32(number, 8);

numberValue = integer;
numberValue = BoxedPrimitives.Get(integer);
}
catch (Exception ex)
{
Expand All @@ -2052,7 +2052,7 @@ private void ParseReadNumber(ReadType readType, char firstChar, int initialPosit
ParseResult parseResult = ConvertUtils.Int32TryParse(_stringReference.Chars, _stringReference.StartIndex, _stringReference.Length, out int value);
if (parseResult == ParseResult.Success)
{
numberValue = value;
numberValue = BoxedPrimitives.Get(value);
}
else if (parseResult == ParseResult.Overflow)
{
Expand All @@ -2072,7 +2072,7 @@ private void ParseReadNumber(ReadType readType, char firstChar, int initialPosit
if (singleDigit)
{
// digit char values start at 48
numberValue = (decimal)firstChar - 48;
numberValue = BoxedPrimitives.Get((decimal)firstChar - 48);
}
else if (nonBase10)
{
Expand All @@ -2083,7 +2083,7 @@ private void ParseReadNumber(ReadType readType, char firstChar, int initialPosit
// 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);
numberValue = BoxedPrimitives.Get(Convert.ToDecimal(integer));
}
catch (Exception ex)
{
Expand All @@ -2095,7 +2095,7 @@ private void ParseReadNumber(ReadType readType, char firstChar, int initialPosit
ParseResult parseResult = ConvertUtils.DecimalTryParse(_stringReference.Chars, _stringReference.StartIndex, _stringReference.Length, out decimal value);
if (parseResult == ParseResult.Success)
{
numberValue = value;
numberValue = BoxedPrimitives.Get(value);
}
else
{
Expand All @@ -2111,7 +2111,7 @@ private void ParseReadNumber(ReadType readType, char firstChar, int initialPosit
if (singleDigit)
{
// digit char values start at 48
numberValue = (double)firstChar - 48;
numberValue = BoxedPrimitives.Get((double)firstChar - 48);
}
else if (nonBase10)
{
Expand All @@ -2122,7 +2122,7 @@ private void ParseReadNumber(ReadType readType, char firstChar, int initialPosit
// 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);
numberValue = BoxedPrimitives.Get(Convert.ToDouble(integer));
}
catch (Exception ex)
{
Expand All @@ -2135,7 +2135,7 @@ private void ParseReadNumber(ReadType readType, char firstChar, int initialPosit

if (double.TryParse(number, NumberStyles.Float, CultureInfo.InvariantCulture, out double value))
{
numberValue = value;
numberValue = BoxedPrimitives.Get(value);
}
else
{
Expand All @@ -2152,7 +2152,7 @@ private void ParseReadNumber(ReadType readType, char firstChar, int initialPosit
if (singleDigit)
{
// digit char values start at 48
numberValue = (long)firstChar - 48;
numberValue = BoxedPrimitives.Get((long)firstChar - 48);
numberType = JsonToken.Integer;
}
else if (nonBase10)
Expand All @@ -2161,7 +2161,7 @@ private void ParseReadNumber(ReadType readType, char firstChar, int initialPosit

try
{
numberValue = number.StartsWith("0x", StringComparison.OrdinalIgnoreCase) ? Convert.ToInt64(number, 16) : Convert.ToInt64(number, 8);
numberValue = BoxedPrimitives.Get(number.StartsWith("0x", StringComparison.OrdinalIgnoreCase) ? Convert.ToInt64(number, 16) : Convert.ToInt64(number, 8));
}
catch (Exception ex)
{
Expand All @@ -2175,7 +2175,7 @@ private void ParseReadNumber(ReadType readType, char firstChar, int initialPosit
ParseResult parseResult = ConvertUtils.Int64TryParse(_stringReference.Chars, _stringReference.StartIndex, _stringReference.Length, out long value);
if (parseResult == ParseResult.Success)
{
numberValue = value;
numberValue = BoxedPrimitives.Get(value);
numberType = JsonToken.Integer;
}
else if (parseResult == ParseResult.Overflow)
Expand All @@ -2201,7 +2201,7 @@ private void ParseReadNumber(ReadType readType, char firstChar, int initialPosit
parseResult = ConvertUtils.DecimalTryParse(_stringReference.Chars, _stringReference.StartIndex, _stringReference.Length, out decimal d);
if (parseResult == ParseResult.Success)
{
numberValue = d;
numberValue = BoxedPrimitives.Get(d);
}
else
{
Expand All @@ -2214,7 +2214,7 @@ private void ParseReadNumber(ReadType readType, char firstChar, int initialPosit

if (double.TryParse(number, NumberStyles.Float, CultureInfo.InvariantCulture, out double d))
{
numberValue = d;
numberValue = BoxedPrimitives.Get(d);
}
else
{
Expand Down Expand Up @@ -2455,7 +2455,7 @@ private void ParseTrue()
// or the text ends
if (MatchValueWithTrailingSeparator(JsonConvert.True))
{
SetToken(JsonToken.Boolean, true);
SetToken(JsonToken.Boolean, BoxedPrimitives.BooleanTrue);
}
else
{
Expand Down Expand Up @@ -2491,7 +2491,7 @@ private void ParseFalse()
{
if (MatchValueWithTrailingSeparator(JsonConvert.False))
{
SetToken(JsonToken.Boolean, false);
SetToken(JsonToken.Boolean, BoxedPrimitives.BooleanFalse);
}
else
{
Expand All @@ -2514,7 +2514,7 @@ private object ParseNumberNegativeInfinity(ReadType readType, bool matched)
case ReadType.ReadAsDouble:
if (_floatParseHandling == FloatParseHandling.Double)
{
SetToken(JsonToken.Float, double.NegativeInfinity);
SetToken(JsonToken.Float, BoxedPrimitives.DoubleNegativeInfinity);
return double.NegativeInfinity;
}
break;
Expand Down Expand Up @@ -2543,7 +2543,7 @@ private object ParseNumberPositiveInfinity(ReadType readType, bool matched)
case ReadType.ReadAsDouble:
if (_floatParseHandling == FloatParseHandling.Double)
{
SetToken(JsonToken.Float, double.PositiveInfinity);
SetToken(JsonToken.Float, BoxedPrimitives.DoublePositiveInfinity);
return double.PositiveInfinity;
}
break;
Expand Down Expand Up @@ -2573,7 +2573,7 @@ private object ParseNumberNaN(ReadType readType, bool matched)
case ReadType.ReadAsDouble:
if (_floatParseHandling == FloatParseHandling.Double)
{
SetToken(JsonToken.Float, double.NaN);
SetToken(JsonToken.Float, BoxedPrimitives.DoubleNaN);
return double.NaN;
}
break;
Expand Down
Loading

0 comments on commit 13717cf

Please sign in to comment.