From 13717cfc452ee9c1ea0b4fc368b8e9922d562028 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Sun, 15 May 2022 15:08:18 +0100 Subject: [PATCH] implement box avoidance (#2639) Co-authored-by: Marc Gravell Co-authored-by: James Newton-King --- Src/Newtonsoft.Json.Tests/Issues/Issue2484.cs | 2 +- Src/Newtonsoft.Json.Tests/Issues/Issue2492.cs | 2 +- Src/Newtonsoft.Json.Tests/Issues/Issue2504.cs | 2 +- Src/Newtonsoft.Json.Tests/Issues/Issue2529.cs | 2 +- Src/Newtonsoft.Json.Tests/Issues/Issue2638.cs | 129 ++++++++++++++++++ .../Newtonsoft.Json.Tests.csproj | 2 +- Src/Newtonsoft.Json/JsonTextReader.cs | 40 +++--- Src/Newtonsoft.Json/Linq/JTokenWriter.cs | 56 ++++---- Src/Newtonsoft.Json/Linq/JValue.cs | 8 +- .../Utilities/BoxedPrimitives.cs | 116 ++++++++++++++++ 10 files changed, 302 insertions(+), 57 deletions(-) create mode 100644 Src/Newtonsoft.Json.Tests/Issues/Issue2638.cs create mode 100644 Src/Newtonsoft.Json/Utilities/BoxedPrimitives.cs diff --git a/Src/Newtonsoft.Json.Tests/Issues/Issue2484.cs b/Src/Newtonsoft.Json.Tests/Issues/Issue2484.cs index baa70f32..d779bb18 100644 --- a/Src/Newtonsoft.Json.Tests/Issues/Issue2484.cs +++ b/Src/Newtonsoft.Json.Tests/Issues/Issue2484.cs @@ -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; diff --git a/Src/Newtonsoft.Json.Tests/Issues/Issue2492.cs b/Src/Newtonsoft.Json.Tests/Issues/Issue2492.cs index 70268abf..34146a66 100644 --- a/Src/Newtonsoft.Json.Tests/Issues/Issue2492.cs +++ b/Src/Newtonsoft.Json.Tests/Issues/Issue2492.cs @@ -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; diff --git a/Src/Newtonsoft.Json.Tests/Issues/Issue2504.cs b/Src/Newtonsoft.Json.Tests/Issues/Issue2504.cs index 4363a911..9ff3be6e 100644 --- a/Src/Newtonsoft.Json.Tests/Issues/Issue2504.cs +++ b/Src/Newtonsoft.Json.Tests/Issues/Issue2504.cs @@ -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; diff --git a/Src/Newtonsoft.Json.Tests/Issues/Issue2529.cs b/Src/Newtonsoft.Json.Tests/Issues/Issue2529.cs index 5d7cb7b9..8406bfc0 100644 --- a/Src/Newtonsoft.Json.Tests/Issues/Issue2529.cs +++ b/Src/Newtonsoft.Json.Tests/Issues/Issue2529.cs @@ -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; diff --git a/Src/Newtonsoft.Json.Tests/Issues/Issue2638.cs b/Src/Newtonsoft.Json.Tests/Issues/Issue2638.cs new file mode 100644 index 00000000..c32fe4c6 --- /dev/null +++ b/Src/Newtonsoft.Json.Tests/Issues/Issue2638.cs @@ -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); + } + } + } + } +} diff --git a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj index 250b9b66..f1166b1a 100644 --- a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj +++ b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj @@ -13,7 +13,7 @@ Newtonsoft.Json.Tests false - Full + Full false diff --git a/Src/Newtonsoft.Json/JsonTextReader.cs b/Src/Newtonsoft.Json/JsonTextReader.cs index 0ef5b0e5..6b107b0e 100644 --- a/Src/Newtonsoft.Json/JsonTextReader.cs +++ b/Src/Newtonsoft.Json/JsonTextReader.cs @@ -841,7 +841,7 @@ namespace Newtonsoft.Json { throw CreateUnexpectedCharacterException(_chars[_charPos]); } - SetToken(JsonToken.Boolean, isTrue); + SetToken(JsonToken.Boolean, BoxedPrimitives.Get(isTrue)); return isTrue; case '/': ParseComment(false); @@ -2030,7 +2030,7 @@ namespace Newtonsoft.Json if (singleDigit) { // digit char values start at 48 - numberValue = firstChar - 48; + numberValue = BoxedPrimitives.Get(firstChar - 48); } else if (nonBase10) { @@ -2040,7 +2040,7 @@ namespace Newtonsoft.Json { int integer = number.StartsWith("0x", StringComparison.OrdinalIgnoreCase) ? Convert.ToInt32(number, 16) : Convert.ToInt32(number, 8); - numberValue = integer; + numberValue = BoxedPrimitives.Get(integer); } catch (Exception ex) { @@ -2052,7 +2052,7 @@ namespace Newtonsoft.Json 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) { @@ -2072,7 +2072,7 @@ namespace Newtonsoft.Json if (singleDigit) { // digit char values start at 48 - numberValue = (decimal)firstChar - 48; + numberValue = BoxedPrimitives.Get((decimal)firstChar - 48); } else if (nonBase10) { @@ -2083,7 +2083,7 @@ namespace Newtonsoft.Json // 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) { @@ -2095,7 +2095,7 @@ namespace Newtonsoft.Json ParseResult parseResult = ConvertUtils.DecimalTryParse(_stringReference.Chars, _stringReference.StartIndex, _stringReference.Length, out decimal value); if (parseResult == ParseResult.Success) { - numberValue = value; + numberValue = BoxedPrimitives.Get(value); } else { @@ -2111,7 +2111,7 @@ namespace Newtonsoft.Json if (singleDigit) { // digit char values start at 48 - numberValue = (double)firstChar - 48; + numberValue = BoxedPrimitives.Get((double)firstChar - 48); } else if (nonBase10) { @@ -2122,7 +2122,7 @@ namespace Newtonsoft.Json // 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) { @@ -2135,7 +2135,7 @@ namespace Newtonsoft.Json if (double.TryParse(number, NumberStyles.Float, CultureInfo.InvariantCulture, out double value)) { - numberValue = value; + numberValue = BoxedPrimitives.Get(value); } else { @@ -2152,7 +2152,7 @@ namespace Newtonsoft.Json if (singleDigit) { // digit char values start at 48 - numberValue = (long)firstChar - 48; + numberValue = BoxedPrimitives.Get((long)firstChar - 48); numberType = JsonToken.Integer; } else if (nonBase10) @@ -2161,7 +2161,7 @@ namespace Newtonsoft.Json 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) { @@ -2175,7 +2175,7 @@ namespace Newtonsoft.Json 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) @@ -2201,7 +2201,7 @@ namespace Newtonsoft.Json parseResult = ConvertUtils.DecimalTryParse(_stringReference.Chars, _stringReference.StartIndex, _stringReference.Length, out decimal d); if (parseResult == ParseResult.Success) { - numberValue = d; + numberValue = BoxedPrimitives.Get(d); } else { @@ -2214,7 +2214,7 @@ namespace Newtonsoft.Json if (double.TryParse(number, NumberStyles.Float, CultureInfo.InvariantCulture, out double d)) { - numberValue = d; + numberValue = BoxedPrimitives.Get(d); } else { @@ -2455,7 +2455,7 @@ namespace Newtonsoft.Json // or the text ends if (MatchValueWithTrailingSeparator(JsonConvert.True)) { - SetToken(JsonToken.Boolean, true); + SetToken(JsonToken.Boolean, BoxedPrimitives.BooleanTrue); } else { @@ -2491,7 +2491,7 @@ namespace Newtonsoft.Json { if (MatchValueWithTrailingSeparator(JsonConvert.False)) { - SetToken(JsonToken.Boolean, false); + SetToken(JsonToken.Boolean, BoxedPrimitives.BooleanFalse); } else { @@ -2514,7 +2514,7 @@ namespace Newtonsoft.Json case ReadType.ReadAsDouble: if (_floatParseHandling == FloatParseHandling.Double) { - SetToken(JsonToken.Float, double.NegativeInfinity); + SetToken(JsonToken.Float, BoxedPrimitives.DoubleNegativeInfinity); return double.NegativeInfinity; } break; @@ -2543,7 +2543,7 @@ namespace Newtonsoft.Json case ReadType.ReadAsDouble: if (_floatParseHandling == FloatParseHandling.Double) { - SetToken(JsonToken.Float, double.PositiveInfinity); + SetToken(JsonToken.Float, BoxedPrimitives.DoublePositiveInfinity); return double.PositiveInfinity; } break; @@ -2573,7 +2573,7 @@ namespace Newtonsoft.Json case ReadType.ReadAsDouble: if (_floatParseHandling == FloatParseHandling.Double) { - SetToken(JsonToken.Float, double.NaN); + SetToken(JsonToken.Float, BoxedPrimitives.DoubleNaN); return double.NaN; } break; diff --git a/Src/Newtonsoft.Json/Linq/JTokenWriter.cs b/Src/Newtonsoft.Json/Linq/JTokenWriter.cs index 9e281c60..344ae834 100644 --- a/Src/Newtonsoft.Json/Linq/JTokenWriter.cs +++ b/Src/Newtonsoft.Json/Linq/JTokenWriter.cs @@ -187,12 +187,12 @@ namespace Newtonsoft.Json.Linq base.WritePropertyName(name); } - private void AddValue(object? value, JsonToken token) + private void AddRawValue(object? value, JTokenType type, JsonToken token) { - AddValue(new JValue(value), token); + AddJValue(new JValue(value, type), token); } - internal void AddValue(JValue? value, JsonToken token) + internal void AddJValue(JValue? value, JsonToken token) { if (_parent != null) { @@ -228,7 +228,7 @@ namespace Newtonsoft.Json.Linq if (value is BigInteger) { InternalWriteValue(JsonToken.Integer); - AddValue(value, JsonToken.Integer); + AddRawValue(value, JTokenType.Integer, JsonToken.Integer); } else #endif @@ -243,7 +243,7 @@ namespace Newtonsoft.Json.Linq public override void WriteNull() { base.WriteNull(); - AddValue(null, JsonToken.Null); + AddJValue(JValue.CreateNull(), JsonToken.Null); } /// @@ -252,7 +252,7 @@ namespace Newtonsoft.Json.Linq public override void WriteUndefined() { base.WriteUndefined(); - AddValue(null, JsonToken.Undefined); + AddJValue(JValue.CreateUndefined(), JsonToken.Undefined); } /// @@ -262,7 +262,7 @@ namespace Newtonsoft.Json.Linq public override void WriteRaw(string? json) { base.WriteRaw(json); - AddValue(new JRaw(json), JsonToken.Raw); + AddJValue(new JRaw(json), JsonToken.Raw); } /// @@ -272,7 +272,7 @@ namespace Newtonsoft.Json.Linq public override void WriteComment(string? text) { base.WriteComment(text); - AddValue(JValue.CreateComment(text), JsonToken.Comment); + AddJValue(JValue.CreateComment(text), JsonToken.Comment); } /// @@ -282,7 +282,7 @@ namespace Newtonsoft.Json.Linq public override void WriteValue(string? value) { base.WriteValue(value); - AddValue(value, JsonToken.String); + AddJValue(new JValue(value), JsonToken.String); } /// @@ -292,7 +292,7 @@ namespace Newtonsoft.Json.Linq public override void WriteValue(int value) { base.WriteValue(value); - AddValue(value, JsonToken.Integer); + AddRawValue(value, JTokenType.Integer, JsonToken.Integer); } /// @@ -303,7 +303,7 @@ namespace Newtonsoft.Json.Linq public override void WriteValue(uint value) { base.WriteValue(value); - AddValue(value, JsonToken.Integer); + AddRawValue(value, JTokenType.Integer, JsonToken.Integer); } /// @@ -313,7 +313,7 @@ namespace Newtonsoft.Json.Linq public override void WriteValue(long value) { base.WriteValue(value); - AddValue(value, JsonToken.Integer); + AddJValue(new JValue(value), JsonToken.Integer); } /// @@ -324,7 +324,7 @@ namespace Newtonsoft.Json.Linq public override void WriteValue(ulong value) { base.WriteValue(value); - AddValue(value, JsonToken.Integer); + AddJValue(new JValue(value), JsonToken.Integer); } /// @@ -334,7 +334,7 @@ namespace Newtonsoft.Json.Linq public override void WriteValue(float value) { base.WriteValue(value); - AddValue(value, JsonToken.Float); + AddJValue(new JValue(value), JsonToken.Float); } /// @@ -344,7 +344,7 @@ namespace Newtonsoft.Json.Linq public override void WriteValue(double value) { base.WriteValue(value); - AddValue(value, JsonToken.Float); + AddJValue(new JValue(value), JsonToken.Float); } /// @@ -354,7 +354,7 @@ namespace Newtonsoft.Json.Linq public override void WriteValue(bool value) { base.WriteValue(value); - AddValue(value, JsonToken.Boolean); + AddJValue(new JValue(value), JsonToken.Boolean); } /// @@ -364,7 +364,7 @@ namespace Newtonsoft.Json.Linq public override void WriteValue(short value) { base.WriteValue(value); - AddValue(value, JsonToken.Integer); + AddRawValue(value, JTokenType.Integer, JsonToken.Integer); } /// @@ -375,7 +375,7 @@ namespace Newtonsoft.Json.Linq public override void WriteValue(ushort value) { base.WriteValue(value); - AddValue(value, JsonToken.Integer); + AddRawValue(value, JTokenType.Integer, JsonToken.Integer); } /// @@ -391,7 +391,7 @@ namespace Newtonsoft.Json.Linq #else s = value.ToString(); #endif - AddValue(s, JsonToken.String); + AddJValue(new JValue(s), JsonToken.String); } /// @@ -401,7 +401,7 @@ namespace Newtonsoft.Json.Linq public override void WriteValue(byte value) { base.WriteValue(value); - AddValue(value, JsonToken.Integer); + AddRawValue(value, JTokenType.Integer, JsonToken.Integer); } /// @@ -412,7 +412,7 @@ namespace Newtonsoft.Json.Linq public override void WriteValue(sbyte value) { base.WriteValue(value); - AddValue(value, JsonToken.Integer); + AddRawValue(value, JTokenType.Integer, JsonToken.Integer); } /// @@ -422,7 +422,7 @@ namespace Newtonsoft.Json.Linq public override void WriteValue(decimal value) { base.WriteValue(value); - AddValue(value, JsonToken.Float); + AddJValue(new JValue(value), JsonToken.Float); } /// @@ -433,7 +433,7 @@ namespace Newtonsoft.Json.Linq { base.WriteValue(value); value = DateTimeUtils.EnsureDateTime(value, DateTimeZoneHandling); - AddValue(value, JsonToken.Date); + AddJValue(new JValue(value), JsonToken.Date); } #if HAVE_DATE_TIME_OFFSET @@ -444,7 +444,7 @@ namespace Newtonsoft.Json.Linq public override void WriteValue(DateTimeOffset value) { base.WriteValue(value); - AddValue(value, JsonToken.Date); + AddJValue(new JValue(value), JsonToken.Date); } #endif @@ -455,7 +455,7 @@ namespace Newtonsoft.Json.Linq public override void WriteValue(byte[]? value) { base.WriteValue(value); - AddValue(value, JsonToken.Bytes); + AddJValue(new JValue(value, JTokenType.Bytes), JsonToken.Bytes); } /// @@ -465,7 +465,7 @@ namespace Newtonsoft.Json.Linq public override void WriteValue(TimeSpan value) { base.WriteValue(value); - AddValue(value, JsonToken.String); + AddJValue(new JValue(value), JsonToken.String); } /// @@ -475,7 +475,7 @@ namespace Newtonsoft.Json.Linq public override void WriteValue(Guid value) { base.WriteValue(value); - AddValue(value, JsonToken.String); + AddJValue(new JValue(value), JsonToken.String); } /// @@ -485,7 +485,7 @@ namespace Newtonsoft.Json.Linq public override void WriteValue(Uri? value) { base.WriteValue(value); - AddValue(value, JsonToken.String); + AddJValue(new JValue(value), JsonToken.String); } #endregion diff --git a/Src/Newtonsoft.Json/Linq/JValue.cs b/Src/Newtonsoft.Json/Linq/JValue.cs index 27d5de88..86ea4dfe 100644 --- a/Src/Newtonsoft.Json/Linq/JValue.cs +++ b/Src/Newtonsoft.Json/Linq/JValue.cs @@ -72,7 +72,7 @@ namespace Newtonsoft.Json.Linq /// /// The value. public JValue(long value) - : this(value, JTokenType.Integer) + : this(BoxedPrimitives.Get(value), JTokenType.Integer) { } @@ -81,7 +81,7 @@ namespace Newtonsoft.Json.Linq /// /// The value. public JValue(decimal value) - : this(value, JTokenType.Float) + : this(BoxedPrimitives.Get(value), JTokenType.Float) { } @@ -109,7 +109,7 @@ namespace Newtonsoft.Json.Linq /// /// The value. public JValue(double value) - : this(value, JTokenType.Float) + : this(BoxedPrimitives.Get(value), JTokenType.Float) { } @@ -147,7 +147,7 @@ namespace Newtonsoft.Json.Linq /// /// The value. public JValue(bool value) - : this(value, JTokenType.Boolean) + : this(BoxedPrimitives.Get(value), JTokenType.Boolean) { } diff --git a/Src/Newtonsoft.Json/Utilities/BoxedPrimitives.cs b/Src/Newtonsoft.Json/Utilities/BoxedPrimitives.cs new file mode 100644 index 00000000..e6fb4f2d --- /dev/null +++ b/Src/Newtonsoft.Json/Utilities/BoxedPrimitives.cs @@ -0,0 +1,116 @@ +#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 + +namespace Newtonsoft.Json.Utilities +{ + internal static class BoxedPrimitives + { + internal static object Get(bool value) => value ? BooleanTrue : BooleanFalse; + + internal static readonly object BooleanTrue = true; + internal static readonly object BooleanFalse = false; + + internal static object Get(int value) => value switch + { + -1 => Int32_M1, + 0 => Int32_0, + 1 => Int32_1, + 2 => Int32_2, + 3 => Int32_3, + 4 => Int32_4, + 5 => Int32_5, + 6 => Int32_6, + 7 => Int32_7, + 8 => Int32_8, + _ => value, + }; + + // integers tend to be weighted towards a handful of low numbers; we could argue + // for days over the "correct" range to have special handling, but I'm arbitrarily + // mirroring the same decision as the IL opcodes, which has M1 thru 8 + internal static readonly object Int32_M1 = -1; + internal static readonly object Int32_0 = 0; + internal static readonly object Int32_1 = 1; + internal static readonly object Int32_2 = 2; + internal static readonly object Int32_3 = 3; + internal static readonly object Int32_4 = 4; + internal static readonly object Int32_5 = 5; + internal static readonly object Int32_6 = 6; + internal static readonly object Int32_7 = 7; + internal static readonly object Int32_8 = 8; + + internal static object Get(long value) => value switch + { + -1 => Int64_M1, + 0 => Int64_0, + 1 => Int64_1, + 2 => Int64_2, + 3 => Int64_3, + 4 => Int64_4, + 5 => Int64_5, + 6 => Int64_6, + 7 => Int64_7, + 8 => Int64_8, + _ => value, + }; + + internal static readonly object Int64_M1 = -1L; + internal static readonly object Int64_0 = 0L; + internal static readonly object Int64_1 = 1L; + internal static readonly object Int64_2 = 2L; + internal static readonly object Int64_3 = 3L; + internal static readonly object Int64_4 = 4L; + internal static readonly object Int64_5 = 5L; + internal static readonly object Int64_6 = 6L; + internal static readonly object Int64_7 = 7L; + internal static readonly object Int64_8 = 8L; + + internal static object Get(decimal value) => value == decimal.Zero ? DecimalZero : value; + + private static readonly object DecimalZero = decimal.Zero; + + internal static object Get(double value) + { + if (value == 0.0d) + { + return DoubleZero; + } + if (double.IsInfinity(value)) + { + return double.IsPositiveInfinity(value) ? DoublePositiveInfinity : DoubleNegativeInfinity; + } + if (double.IsNaN(value)) + { + return DoubleNaN; + } + return value; + } + + internal static readonly object DoubleNaN = double.NaN; + internal static readonly object DoublePositiveInfinity = double.PositiveInfinity; + internal static readonly object DoubleNegativeInfinity = double.NegativeInfinity; + internal static readonly object DoubleZero = (double)0; + } +}