Skip to content

Commit

Permalink
CSHARP-5308: Throw when converting NaN/Infinity to integral (mongodb#…
Browse files Browse the repository at this point in the history
  • Loading branch information
papafe authored Oct 7, 2024
1 parent 9b6b40a commit 033aa5d
Show file tree
Hide file tree
Showing 5 changed files with 347 additions and 0 deletions.
70 changes: 70 additions & 0 deletions src/MongoDB.Bson/Serialization/Options/RepresentationConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,11 @@ public double ToDouble(ushort value)
/// <returns>An Int16.</returns>
public short ToInt16(Decimal128 value)
{
if (Decimal128.IsInfinity(value) || Decimal128.IsNaN(value))
{
throw new OverflowException();
}

short shortValue;
if (_allowOverflow)
{
Expand All @@ -459,6 +464,11 @@ public short ToInt16(Decimal128 value)
/// <returns>An Int16.</returns>
public short ToInt16(double value)
{
if (double.IsInfinity(value) || double.IsNaN(value))
{
throw new OverflowException();
}

var int16Value = (short)value;
if (value < short.MinValue || value > short.MaxValue)
{
Expand Down Expand Up @@ -534,6 +544,11 @@ public int ToInt32(decimal value)
/// <returns>An Int32.</returns>
public int ToInt32(Decimal128 value)
{
if (Decimal128.IsInfinity(value) || Decimal128.IsNaN(value))
{
throw new OverflowException();
}

int intValue;
if (_allowOverflow)
{
Expand All @@ -557,6 +572,11 @@ public int ToInt32(Decimal128 value)
/// <returns>An Int32.</returns>
public int ToInt32(double value)
{
if (double.IsInfinity(value) || double.IsNaN(value))
{
throw new OverflowException();
}

var int32Value = (int)value;
if (value < int.MinValue || value > int.MaxValue)
{
Expand All @@ -576,6 +596,11 @@ public int ToInt32(double value)
/// <returns>An Int32.</returns>
public int ToInt32(float value)
{
if (float.IsInfinity(value) || float.IsNaN(value))
{
throw new OverflowException();
}

var int32Value = (int)value;
if (value < int.MinValue || value > int.MaxValue)
{
Expand Down Expand Up @@ -698,6 +723,11 @@ public long ToInt64(decimal value)
/// <returns>An Int64.</returns>
public long ToInt64(Decimal128 value)
{
if (Decimal128.IsInfinity(value) || Decimal128.IsNaN(value))
{
throw new OverflowException();
}

long longValue;
if (_allowOverflow)
{
Expand All @@ -721,6 +751,11 @@ public long ToInt64(Decimal128 value)
/// <returns>An Int64.</returns>
public long ToInt64(double value)
{
if (double.IsInfinity(value) || double.IsNaN(value))
{
throw new OverflowException();
}

var int64Value = (long)value;
if (value < long.MinValue || value > long.MaxValue)
{
Expand All @@ -740,6 +775,11 @@ public long ToInt64(double value)
/// <returns>An Int64.</returns>
public long ToInt64(float value)
{
if (float.IsInfinity(value) || float.IsNaN(value))
{
throw new OverflowException();
}

var int64Value = (long)value;
if (value < long.MinValue || value > long.MaxValue)
{
Expand Down Expand Up @@ -943,6 +983,11 @@ public float ToSingle(long value)
[CLSCompliant(false)]
public ushort ToUInt16(Decimal128 value)
{
if (Decimal128.IsInfinity(value) || Decimal128.IsNaN(value))
{
throw new OverflowException();
}

ushort ushortValue;
if (_allowOverflow)
{
Expand All @@ -969,6 +1014,11 @@ public ushort ToUInt16(Decimal128 value)
[CLSCompliant(false)]
public ushort ToUInt16(double value)
{
if (double.IsInfinity(value) || double.IsNaN(value))
{
throw new OverflowException();
}

var uint16Value = (ushort)value;
if (value < ushort.MinValue || value > ushort.MaxValue)
{
Expand Down Expand Up @@ -1019,6 +1069,11 @@ public ushort ToUInt16(long value)
[CLSCompliant(false)]
public uint ToUInt32(Decimal128 value)
{
if (Decimal128.IsInfinity(value) || Decimal128.IsNaN(value))
{
throw new OverflowException();
}

uint uintValue;
if (_allowOverflow)
{
Expand All @@ -1045,6 +1100,11 @@ public uint ToUInt32(Decimal128 value)
[CLSCompliant(false)]
public uint ToUInt32(double value)
{
if (double.IsInfinity(value) || double.IsNaN(value))
{
throw new OverflowException();
}

var uint32Value = (uint)value;
if (value < uint.MinValue || value > uint.MaxValue)
{
Expand Down Expand Up @@ -1095,6 +1155,11 @@ public uint ToUInt32(long value)
[CLSCompliant(false)]
public ulong ToUInt64(Decimal128 value)
{
if (Decimal128.IsInfinity(value) || Decimal128.IsNaN(value))
{
throw new OverflowException();
}

ulong ulongValue;
if (_allowOverflow)
{
Expand All @@ -1121,6 +1186,11 @@ public ulong ToUInt64(Decimal128 value)
[CLSCompliant(false)]
public ulong ToUInt64(double value)
{
if (double.IsInfinity(value) || double.IsNaN(value))
{
throw new OverflowException();
}

var uint64Value = (ulong)value;
if (value < ulong.MinValue || value > ulong.MaxValue)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
using System;
using FluentAssertions;
using MongoDB.Bson.Serialization.Options;
using MongoDB.TestHelpers.XunitExtensions;
using Xunit;

namespace MongoDB.Bson.Tests.Serialization
Expand Down Expand Up @@ -120,6 +121,105 @@ public void TestConversions()
Assert.Equal((long)(ulong)long.MaxValue, converter.ToInt64(long.MaxValue));
}

[Theory]
[ParameterAttributeData]
public void TestConversionOfDoubleInfinityOrNanToIntegralShouldThrow(
[Values(true, false)] bool allowOverflow,
[Values(true, false)] bool allowTruncation)
{
var converter = new RepresentationConverter(allowOverflow, allowTruncation);

Assert.Throws<OverflowException>(() => converter.ToInt16(double.PositiveInfinity));
Assert.Throws<OverflowException>(() => converter.ToInt16(double.NegativeInfinity));
Assert.Throws<OverflowException>(() => converter.ToInt16(double.NaN));

Assert.Throws<OverflowException>(() => converter.ToInt32(double.PositiveInfinity));
Assert.Throws<OverflowException>(() => converter.ToInt32(double.NegativeInfinity));
Assert.Throws<OverflowException>(() => converter.ToInt32(double.NaN));

Assert.Throws<OverflowException>(() => converter.ToInt64(double.PositiveInfinity));
Assert.Throws<OverflowException>(() => converter.ToInt64(double.NegativeInfinity));
Assert.Throws<OverflowException>(() => converter.ToInt64(double.NaN));

Assert.Throws<OverflowException>(() => converter.ToUInt16(double.PositiveInfinity));
Assert.Throws<OverflowException>(() => converter.ToUInt16(double.NegativeInfinity));
Assert.Throws<OverflowException>(() => converter.ToUInt16(double.NaN));

Assert.Throws<OverflowException>(() => converter.ToUInt32(double.PositiveInfinity));
Assert.Throws<OverflowException>(() => converter.ToUInt32(double.NegativeInfinity));
Assert.Throws<OverflowException>(() => converter.ToUInt32(double.NaN));

Assert.Throws<OverflowException>(() => converter.ToUInt64(double.PositiveInfinity));
Assert.Throws<OverflowException>(() => converter.ToUInt64(double.NegativeInfinity));
Assert.Throws<OverflowException>(() => converter.ToUInt64(double.NaN));
}

[Theory]
[ParameterAttributeData]
public void TestConversionOfDecimal128InfinityOrNanToIntegralShouldThrow(
[Values(true, false)] bool allowOverflow,
[Values(true, false)] bool allowTruncation)
{
var converter = new RepresentationConverter(allowOverflow, allowTruncation);

Assert.Throws<OverflowException>(() => converter.ToInt16(Decimal128.PositiveInfinity));
Assert.Throws<OverflowException>(() => converter.ToInt16(Decimal128.NegativeInfinity));
Assert.Throws<OverflowException>(() => converter.ToInt16(Decimal128.QNaN));

Assert.Throws<OverflowException>(() => converter.ToInt32(Decimal128.PositiveInfinity));
Assert.Throws<OverflowException>(() => converter.ToInt32(Decimal128.NegativeInfinity));
Assert.Throws<OverflowException>(() => converter.ToInt32(Decimal128.QNaN));

Assert.Throws<OverflowException>(() => converter.ToInt64(Decimal128.PositiveInfinity));
Assert.Throws<OverflowException>(() => converter.ToInt64(Decimal128.NegativeInfinity));
Assert.Throws<OverflowException>(() => converter.ToInt64(Decimal128.QNaN));

Assert.Throws<OverflowException>(() => converter.ToUInt16(Decimal128.PositiveInfinity));
Assert.Throws<OverflowException>(() => converter.ToUInt16(Decimal128.NegativeInfinity));
Assert.Throws<OverflowException>(() => converter.ToUInt16(Decimal128.QNaN));

Assert.Throws<OverflowException>(() => converter.ToUInt32(Decimal128.PositiveInfinity));
Assert.Throws<OverflowException>(() => converter.ToUInt32(Decimal128.NegativeInfinity));
Assert.Throws<OverflowException>(() => converter.ToUInt32(Decimal128.QNaN));

Assert.Throws<OverflowException>(() => converter.ToUInt64(Decimal128.PositiveInfinity));
Assert.Throws<OverflowException>(() => converter.ToUInt64(Decimal128.NegativeInfinity));
Assert.Throws<OverflowException>(() => converter.ToUInt64(Decimal128.QNaN));
}

[Theory]
[ParameterAttributeData]
public void TestConversionOfSingleInfinityOrNanToIntegralShouldThrow(
[Values(true, false)] bool allowOverflow,
[Values(true, false)] bool allowTruncation)
{
var converter = new RepresentationConverter(allowOverflow, allowTruncation);

Assert.Throws<OverflowException>(() => converter.ToInt16(float.PositiveInfinity));
Assert.Throws<OverflowException>(() => converter.ToInt16(float.NegativeInfinity));
Assert.Throws<OverflowException>(() => converter.ToInt16(float.NaN));

Assert.Throws<OverflowException>(() => converter.ToInt32(float.PositiveInfinity));
Assert.Throws<OverflowException>(() => converter.ToInt32(float.NegativeInfinity));
Assert.Throws<OverflowException>(() => converter.ToInt32(float.NaN));

Assert.Throws<OverflowException>(() => converter.ToInt64(float.PositiveInfinity));
Assert.Throws<OverflowException>(() => converter.ToInt64(float.NegativeInfinity));
Assert.Throws<OverflowException>(() => converter.ToInt64(float.NaN));

Assert.Throws<OverflowException>(() => converter.ToUInt16(float.PositiveInfinity));
Assert.Throws<OverflowException>(() => converter.ToUInt16(float.NegativeInfinity));
Assert.Throws<OverflowException>(() => converter.ToUInt16(float.NaN));

Assert.Throws<OverflowException>(() => converter.ToUInt32(float.PositiveInfinity));
Assert.Throws<OverflowException>(() => converter.ToUInt32(float.NegativeInfinity));
Assert.Throws<OverflowException>(() => converter.ToUInt32(float.NaN));

Assert.Throws<OverflowException>(() => converter.ToUInt64(float.PositiveInfinity));
Assert.Throws<OverflowException>(() => converter.ToUInt64(float.NegativeInfinity));
Assert.Throws<OverflowException>(() => converter.ToUInt64(float.NaN));
}

[Fact]
public void TestAllowOverflowFalse()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@
*/

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using FluentAssertions;
using MongoDB.Bson.IO;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Options;
using MongoDB.Bson.Serialization.Serializers;
using MongoDB.TestHelpers.XunitExtensions;
Expand Down Expand Up @@ -124,6 +129,30 @@ public void GetHashCode_should_return_zero()
result.Should().Be(0);
}

public static IEnumerable<object[]> SerializeSpecialValuesData()
{
return from bsonType in new[] { BsonType.Int64, BsonType.Int32 }
from val in new [] { Decimal128.PositiveInfinity, Decimal128.NegativeInfinity, Decimal128.QNaN }
select new object[] { bsonType, val };
}

[Theory]
[MemberData(nameof(SerializeSpecialValuesData))]
public void Serialize_NaN_or_Infinity_to_integral_should_throw(BsonType representation, Decimal128 value)
{
var subject = new Decimal128Serializer(representation);

using var textWriter = new StringWriter();
using var writer = new JsonWriter(textWriter);

var context = BsonSerializationContext.CreateRoot(writer);
writer.WriteStartDocument();
writer.WriteName("x");

var exception = Record.Exception(() => subject.Serialize(context, value));
exception.Should().BeOfType<OverflowException>();
}

[Theory]
[ParameterAttributeData]
public void WithRepresentation_should_return_expected_result(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,15 @@
* limitations under the License.
*/

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using FluentAssertions;
using MongoDB.Bson.IO;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Serializers;
using MongoDB.TestHelpers.XunitExtensions;
using Xunit;

namespace MongoDB.Bson.Tests.Serialization.Serializers
Expand Down Expand Up @@ -83,5 +90,23 @@ public void GetHashCode_should_return_zero()

result.Should().Be(0);
}

[Theory]
[ParameterAttributeData]
public void Serialize_NaN_or_Infinity_to_integral_should_throw([Values(BsonType.Int64, BsonType.Int32)] BsonType representation,
[Values(double.PositiveInfinity, double.NegativeInfinity, double.NaN)] double value)
{
var subject = new DoubleSerializer(representation);

using var textWriter = new StringWriter();
using var writer = new JsonWriter(textWriter);

var context = BsonSerializationContext.CreateRoot(writer);
writer.WriteStartDocument();
writer.WriteName("x");

var exception = Record.Exception(() => subject.Serialize(context, value));
exception.Should().BeOfType<OverflowException>();
}
}
}
Loading

0 comments on commit 033aa5d

Please sign in to comment.