Skip to content

Commit 033aa5d

Browse files
authored
CSHARP-5308: Throw when converting NaN/Infinity to integral (#1478)
1 parent 9b6b40a commit 033aa5d

File tree

5 files changed

+347
-0
lines changed

5 files changed

+347
-0
lines changed

src/MongoDB.Bson/Serialization/Options/RepresentationConverter.cs

+70
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,11 @@ public double ToDouble(ushort value)
434434
/// <returns>An Int16.</returns>
435435
public short ToInt16(Decimal128 value)
436436
{
437+
if (Decimal128.IsInfinity(value) || Decimal128.IsNaN(value))
438+
{
439+
throw new OverflowException();
440+
}
441+
437442
short shortValue;
438443
if (_allowOverflow)
439444
{
@@ -459,6 +464,11 @@ public short ToInt16(Decimal128 value)
459464
/// <returns>An Int16.</returns>
460465
public short ToInt16(double value)
461466
{
467+
if (double.IsInfinity(value) || double.IsNaN(value))
468+
{
469+
throw new OverflowException();
470+
}
471+
462472
var int16Value = (short)value;
463473
if (value < short.MinValue || value > short.MaxValue)
464474
{
@@ -534,6 +544,11 @@ public int ToInt32(decimal value)
534544
/// <returns>An Int32.</returns>
535545
public int ToInt32(Decimal128 value)
536546
{
547+
if (Decimal128.IsInfinity(value) || Decimal128.IsNaN(value))
548+
{
549+
throw new OverflowException();
550+
}
551+
537552
int intValue;
538553
if (_allowOverflow)
539554
{
@@ -557,6 +572,11 @@ public int ToInt32(Decimal128 value)
557572
/// <returns>An Int32.</returns>
558573
public int ToInt32(double value)
559574
{
575+
if (double.IsInfinity(value) || double.IsNaN(value))
576+
{
577+
throw new OverflowException();
578+
}
579+
560580
var int32Value = (int)value;
561581
if (value < int.MinValue || value > int.MaxValue)
562582
{
@@ -576,6 +596,11 @@ public int ToInt32(double value)
576596
/// <returns>An Int32.</returns>
577597
public int ToInt32(float value)
578598
{
599+
if (float.IsInfinity(value) || float.IsNaN(value))
600+
{
601+
throw new OverflowException();
602+
}
603+
579604
var int32Value = (int)value;
580605
if (value < int.MinValue || value > int.MaxValue)
581606
{
@@ -698,6 +723,11 @@ public long ToInt64(decimal value)
698723
/// <returns>An Int64.</returns>
699724
public long ToInt64(Decimal128 value)
700725
{
726+
if (Decimal128.IsInfinity(value) || Decimal128.IsNaN(value))
727+
{
728+
throw new OverflowException();
729+
}
730+
701731
long longValue;
702732
if (_allowOverflow)
703733
{
@@ -721,6 +751,11 @@ public long ToInt64(Decimal128 value)
721751
/// <returns>An Int64.</returns>
722752
public long ToInt64(double value)
723753
{
754+
if (double.IsInfinity(value) || double.IsNaN(value))
755+
{
756+
throw new OverflowException();
757+
}
758+
724759
var int64Value = (long)value;
725760
if (value < long.MinValue || value > long.MaxValue)
726761
{
@@ -740,6 +775,11 @@ public long ToInt64(double value)
740775
/// <returns>An Int64.</returns>
741776
public long ToInt64(float value)
742777
{
778+
if (float.IsInfinity(value) || float.IsNaN(value))
779+
{
780+
throw new OverflowException();
781+
}
782+
743783
var int64Value = (long)value;
744784
if (value < long.MinValue || value > long.MaxValue)
745785
{
@@ -943,6 +983,11 @@ public float ToSingle(long value)
943983
[CLSCompliant(false)]
944984
public ushort ToUInt16(Decimal128 value)
945985
{
986+
if (Decimal128.IsInfinity(value) || Decimal128.IsNaN(value))
987+
{
988+
throw new OverflowException();
989+
}
990+
946991
ushort ushortValue;
947992
if (_allowOverflow)
948993
{
@@ -969,6 +1014,11 @@ public ushort ToUInt16(Decimal128 value)
9691014
[CLSCompliant(false)]
9701015
public ushort ToUInt16(double value)
9711016
{
1017+
if (double.IsInfinity(value) || double.IsNaN(value))
1018+
{
1019+
throw new OverflowException();
1020+
}
1021+
9721022
var uint16Value = (ushort)value;
9731023
if (value < ushort.MinValue || value > ushort.MaxValue)
9741024
{
@@ -1019,6 +1069,11 @@ public ushort ToUInt16(long value)
10191069
[CLSCompliant(false)]
10201070
public uint ToUInt32(Decimal128 value)
10211071
{
1072+
if (Decimal128.IsInfinity(value) || Decimal128.IsNaN(value))
1073+
{
1074+
throw new OverflowException();
1075+
}
1076+
10221077
uint uintValue;
10231078
if (_allowOverflow)
10241079
{
@@ -1045,6 +1100,11 @@ public uint ToUInt32(Decimal128 value)
10451100
[CLSCompliant(false)]
10461101
public uint ToUInt32(double value)
10471102
{
1103+
if (double.IsInfinity(value) || double.IsNaN(value))
1104+
{
1105+
throw new OverflowException();
1106+
}
1107+
10481108
var uint32Value = (uint)value;
10491109
if (value < uint.MinValue || value > uint.MaxValue)
10501110
{
@@ -1095,6 +1155,11 @@ public uint ToUInt32(long value)
10951155
[CLSCompliant(false)]
10961156
public ulong ToUInt64(Decimal128 value)
10971157
{
1158+
if (Decimal128.IsInfinity(value) || Decimal128.IsNaN(value))
1159+
{
1160+
throw new OverflowException();
1161+
}
1162+
10981163
ulong ulongValue;
10991164
if (_allowOverflow)
11001165
{
@@ -1121,6 +1186,11 @@ public ulong ToUInt64(Decimal128 value)
11211186
[CLSCompliant(false)]
11221187
public ulong ToUInt64(double value)
11231188
{
1189+
if (double.IsInfinity(value) || double.IsNaN(value))
1190+
{
1191+
throw new OverflowException();
1192+
}
1193+
11241194
var uint64Value = (ulong)value;
11251195
if (value < ulong.MinValue || value > ulong.MaxValue)
11261196
{

tests/MongoDB.Bson.Tests/Serialization/Options/RepresentationConverterTests.cs

+100
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
using System;
1717
using FluentAssertions;
1818
using MongoDB.Bson.Serialization.Options;
19+
using MongoDB.TestHelpers.XunitExtensions;
1920
using Xunit;
2021

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

124+
[Theory]
125+
[ParameterAttributeData]
126+
public void TestConversionOfDoubleInfinityOrNanToIntegralShouldThrow(
127+
[Values(true, false)] bool allowOverflow,
128+
[Values(true, false)] bool allowTruncation)
129+
{
130+
var converter = new RepresentationConverter(allowOverflow, allowTruncation);
131+
132+
Assert.Throws<OverflowException>(() => converter.ToInt16(double.PositiveInfinity));
133+
Assert.Throws<OverflowException>(() => converter.ToInt16(double.NegativeInfinity));
134+
Assert.Throws<OverflowException>(() => converter.ToInt16(double.NaN));
135+
136+
Assert.Throws<OverflowException>(() => converter.ToInt32(double.PositiveInfinity));
137+
Assert.Throws<OverflowException>(() => converter.ToInt32(double.NegativeInfinity));
138+
Assert.Throws<OverflowException>(() => converter.ToInt32(double.NaN));
139+
140+
Assert.Throws<OverflowException>(() => converter.ToInt64(double.PositiveInfinity));
141+
Assert.Throws<OverflowException>(() => converter.ToInt64(double.NegativeInfinity));
142+
Assert.Throws<OverflowException>(() => converter.ToInt64(double.NaN));
143+
144+
Assert.Throws<OverflowException>(() => converter.ToUInt16(double.PositiveInfinity));
145+
Assert.Throws<OverflowException>(() => converter.ToUInt16(double.NegativeInfinity));
146+
Assert.Throws<OverflowException>(() => converter.ToUInt16(double.NaN));
147+
148+
Assert.Throws<OverflowException>(() => converter.ToUInt32(double.PositiveInfinity));
149+
Assert.Throws<OverflowException>(() => converter.ToUInt32(double.NegativeInfinity));
150+
Assert.Throws<OverflowException>(() => converter.ToUInt32(double.NaN));
151+
152+
Assert.Throws<OverflowException>(() => converter.ToUInt64(double.PositiveInfinity));
153+
Assert.Throws<OverflowException>(() => converter.ToUInt64(double.NegativeInfinity));
154+
Assert.Throws<OverflowException>(() => converter.ToUInt64(double.NaN));
155+
}
156+
157+
[Theory]
158+
[ParameterAttributeData]
159+
public void TestConversionOfDecimal128InfinityOrNanToIntegralShouldThrow(
160+
[Values(true, false)] bool allowOverflow,
161+
[Values(true, false)] bool allowTruncation)
162+
{
163+
var converter = new RepresentationConverter(allowOverflow, allowTruncation);
164+
165+
Assert.Throws<OverflowException>(() => converter.ToInt16(Decimal128.PositiveInfinity));
166+
Assert.Throws<OverflowException>(() => converter.ToInt16(Decimal128.NegativeInfinity));
167+
Assert.Throws<OverflowException>(() => converter.ToInt16(Decimal128.QNaN));
168+
169+
Assert.Throws<OverflowException>(() => converter.ToInt32(Decimal128.PositiveInfinity));
170+
Assert.Throws<OverflowException>(() => converter.ToInt32(Decimal128.NegativeInfinity));
171+
Assert.Throws<OverflowException>(() => converter.ToInt32(Decimal128.QNaN));
172+
173+
Assert.Throws<OverflowException>(() => converter.ToInt64(Decimal128.PositiveInfinity));
174+
Assert.Throws<OverflowException>(() => converter.ToInt64(Decimal128.NegativeInfinity));
175+
Assert.Throws<OverflowException>(() => converter.ToInt64(Decimal128.QNaN));
176+
177+
Assert.Throws<OverflowException>(() => converter.ToUInt16(Decimal128.PositiveInfinity));
178+
Assert.Throws<OverflowException>(() => converter.ToUInt16(Decimal128.NegativeInfinity));
179+
Assert.Throws<OverflowException>(() => converter.ToUInt16(Decimal128.QNaN));
180+
181+
Assert.Throws<OverflowException>(() => converter.ToUInt32(Decimal128.PositiveInfinity));
182+
Assert.Throws<OverflowException>(() => converter.ToUInt32(Decimal128.NegativeInfinity));
183+
Assert.Throws<OverflowException>(() => converter.ToUInt32(Decimal128.QNaN));
184+
185+
Assert.Throws<OverflowException>(() => converter.ToUInt64(Decimal128.PositiveInfinity));
186+
Assert.Throws<OverflowException>(() => converter.ToUInt64(Decimal128.NegativeInfinity));
187+
Assert.Throws<OverflowException>(() => converter.ToUInt64(Decimal128.QNaN));
188+
}
189+
190+
[Theory]
191+
[ParameterAttributeData]
192+
public void TestConversionOfSingleInfinityOrNanToIntegralShouldThrow(
193+
[Values(true, false)] bool allowOverflow,
194+
[Values(true, false)] bool allowTruncation)
195+
{
196+
var converter = new RepresentationConverter(allowOverflow, allowTruncation);
197+
198+
Assert.Throws<OverflowException>(() => converter.ToInt16(float.PositiveInfinity));
199+
Assert.Throws<OverflowException>(() => converter.ToInt16(float.NegativeInfinity));
200+
Assert.Throws<OverflowException>(() => converter.ToInt16(float.NaN));
201+
202+
Assert.Throws<OverflowException>(() => converter.ToInt32(float.PositiveInfinity));
203+
Assert.Throws<OverflowException>(() => converter.ToInt32(float.NegativeInfinity));
204+
Assert.Throws<OverflowException>(() => converter.ToInt32(float.NaN));
205+
206+
Assert.Throws<OverflowException>(() => converter.ToInt64(float.PositiveInfinity));
207+
Assert.Throws<OverflowException>(() => converter.ToInt64(float.NegativeInfinity));
208+
Assert.Throws<OverflowException>(() => converter.ToInt64(float.NaN));
209+
210+
Assert.Throws<OverflowException>(() => converter.ToUInt16(float.PositiveInfinity));
211+
Assert.Throws<OverflowException>(() => converter.ToUInt16(float.NegativeInfinity));
212+
Assert.Throws<OverflowException>(() => converter.ToUInt16(float.NaN));
213+
214+
Assert.Throws<OverflowException>(() => converter.ToUInt32(float.PositiveInfinity));
215+
Assert.Throws<OverflowException>(() => converter.ToUInt32(float.NegativeInfinity));
216+
Assert.Throws<OverflowException>(() => converter.ToUInt32(float.NaN));
217+
218+
Assert.Throws<OverflowException>(() => converter.ToUInt64(float.PositiveInfinity));
219+
Assert.Throws<OverflowException>(() => converter.ToUInt64(float.NegativeInfinity));
220+
Assert.Throws<OverflowException>(() => converter.ToUInt64(float.NaN));
221+
}
222+
123223
[Fact]
124224
public void TestAllowOverflowFalse()
125225
{

tests/MongoDB.Bson.Tests/Serialization/Serializers/Decimal128SerializerTests.cs

+29
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,12 @@
1414
*/
1515

1616
using System;
17+
using System.Collections.Generic;
18+
using System.IO;
19+
using System.Linq;
1720
using FluentAssertions;
21+
using MongoDB.Bson.IO;
22+
using MongoDB.Bson.Serialization;
1823
using MongoDB.Bson.Serialization.Options;
1924
using MongoDB.Bson.Serialization.Serializers;
2025
using MongoDB.TestHelpers.XunitExtensions;
@@ -124,6 +129,30 @@ public void GetHashCode_should_return_zero()
124129
result.Should().Be(0);
125130
}
126131

132+
public static IEnumerable<object[]> SerializeSpecialValuesData()
133+
{
134+
return from bsonType in new[] { BsonType.Int64, BsonType.Int32 }
135+
from val in new [] { Decimal128.PositiveInfinity, Decimal128.NegativeInfinity, Decimal128.QNaN }
136+
select new object[] { bsonType, val };
137+
}
138+
139+
[Theory]
140+
[MemberData(nameof(SerializeSpecialValuesData))]
141+
public void Serialize_NaN_or_Infinity_to_integral_should_throw(BsonType representation, Decimal128 value)
142+
{
143+
var subject = new Decimal128Serializer(representation);
144+
145+
using var textWriter = new StringWriter();
146+
using var writer = new JsonWriter(textWriter);
147+
148+
var context = BsonSerializationContext.CreateRoot(writer);
149+
writer.WriteStartDocument();
150+
writer.WriteName("x");
151+
152+
var exception = Record.Exception(() => subject.Serialize(context, value));
153+
exception.Should().BeOfType<OverflowException>();
154+
}
155+
127156
[Theory]
128157
[ParameterAttributeData]
129158
public void WithRepresentation_should_return_expected_result(

tests/MongoDB.Bson.Tests/Serialization/Serializers/DoubleSerializerTests.cs

+25
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,15 @@
1313
* limitations under the License.
1414
*/
1515

16+
using System;
17+
using System.Collections.Generic;
18+
using System.IO;
19+
using System.Linq;
1620
using FluentAssertions;
21+
using MongoDB.Bson.IO;
22+
using MongoDB.Bson.Serialization;
1723
using MongoDB.Bson.Serialization.Serializers;
24+
using MongoDB.TestHelpers.XunitExtensions;
1825
using Xunit;
1926

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

8491
result.Should().Be(0);
8592
}
93+
94+
[Theory]
95+
[ParameterAttributeData]
96+
public void Serialize_NaN_or_Infinity_to_integral_should_throw([Values(BsonType.Int64, BsonType.Int32)] BsonType representation,
97+
[Values(double.PositiveInfinity, double.NegativeInfinity, double.NaN)] double value)
98+
{
99+
var subject = new DoubleSerializer(representation);
100+
101+
using var textWriter = new StringWriter();
102+
using var writer = new JsonWriter(textWriter);
103+
104+
var context = BsonSerializationContext.CreateRoot(writer);
105+
writer.WriteStartDocument();
106+
writer.WriteName("x");
107+
108+
var exception = Record.Exception(() => subject.Serialize(context, value));
109+
exception.Should().BeOfType<OverflowException>();
110+
}
86111
}
87112
}

0 commit comments

Comments
 (0)