Skip to content

Commit 6508018

Browse files
authored
Fix assertion at System.Numerics.BigIntegerCalculator.Multiply (#96744)
* Fix BigNumberTools Use typeof and nameof directly. * Fix BigIntTools Use typeof and nameof as in BigNumberTools. * RunWithFakeThreshold hack * Add regression test * Fix assertion
1 parent 3f84acb commit 6508018

9 files changed

Lines changed: 70 additions & 101 deletions

File tree

src/libraries/System.Runtime.Numerics/src/System/Number.BigInteger.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -659,9 +659,9 @@ internal static ParsingStatus TryParseBigIntegerBinaryNumberStyle(ReadOnlySpan<c
659659
//
660660
#if DEBUG
661661
// Mutable for unit testing...
662-
private static
662+
internal static
663663
#else
664-
private const
664+
internal const
665665
#endif
666666
int s_naiveThreshold = 20000;
667667
private static ParsingStatus NumberToBigInteger(ref NumberBuffer number, out BigInteger result)

src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.PowMod.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -299,9 +299,9 @@ stackalloc uint[StackAllocThreshold]
299299

300300
#if DEBUG
301301
// Mutable for unit testing...
302-
private static
302+
internal static
303303
#else
304-
private const
304+
internal const
305305
#endif
306306
int ReducerThreshold = 32;
307307

src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.SquMul.cs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ internal static partial class BigIntegerCalculator
1212
{
1313
#if DEBUG
1414
// Mutable for unit testing...
15-
private static
15+
internal static
1616
#else
17-
private const
17+
internal const
1818
#endif
1919
int SquareThreshold = 32;
2020

@@ -160,7 +160,8 @@ internal const
160160
public static void Multiply(ReadOnlySpan<uint> left, ReadOnlySpan<uint> right, Span<uint> bits)
161161
{
162162
Debug.Assert(left.Length >= right.Length);
163-
Debug.Assert(bits.Length == left.Length + right.Length);
163+
Debug.Assert(bits.Length >= left.Length + right.Length);
164+
Debug.Assert(!bits.ContainsAnyExcept(0u));
164165

165166
if (left.Length - right.Length < 3)
166167
{
@@ -175,7 +176,8 @@ public static void Multiply(ReadOnlySpan<uint> left, ReadOnlySpan<uint> right, S
175176
private static void MultiplyFarLength(ReadOnlySpan<uint> left, ReadOnlySpan<uint> right, Span<uint> bits)
176177
{
177178
Debug.Assert(left.Length - right.Length >= 3);
178-
Debug.Assert(bits.Length == left.Length + right.Length);
179+
Debug.Assert(bits.Length >= left.Length + right.Length);
180+
Debug.Assert(!bits.ContainsAnyExcept(0u));
179181

180182
// Executes different algorithms for computing z = a * b
181183
// based on the actual length of b. If b is "small" enough
@@ -370,7 +372,8 @@ stackalloc uint[StackAllocThreshold]
370372
private static void MultiplyNearLength(ReadOnlySpan<uint> left, ReadOnlySpan<uint> right, Span<uint> bits)
371373
{
372374
Debug.Assert(left.Length - right.Length < 3);
373-
Debug.Assert(bits.Length == left.Length + right.Length);
375+
Debug.Assert(bits.Length >= left.Length + right.Length);
376+
Debug.Assert(!bits.ContainsAnyExcept(0u));
374377

375378
// Executes different algorithms for computing z = a * b
376379
// based on the actual length of b. If b is "small" enough

src/libraries/System.Runtime.Numerics/tests/BigInteger/BigIntTools.cs

Lines changed: 15 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System;
5-
using System.Numerics;
6-
using System.Reflection;
5+
using System.Runtime.CompilerServices;
76
using System.Text;
87

98
namespace BigIntTools
@@ -34,43 +33,29 @@ public static string BuildRandomNumber(int maxdigits, int seed)
3433
}
3534
}
3635

37-
private static TypeInfo InternalCalculator
36+
public static void RunWithFakeThreshold(in int field, int value, Action action)
3837
{
39-
get
40-
{
41-
if (s_lazyInternalCalculator == null)
42-
{
43-
Type t = typeof(BigInteger).Assembly.GetType("System.Numerics.BigIntegerCalculator");
44-
if (t != null)
45-
{
46-
s_lazyInternalCalculator = t.GetTypeInfo();
47-
}
48-
}
49-
return s_lazyInternalCalculator;
50-
}
51-
}
52-
53-
private static volatile TypeInfo s_lazyInternalCalculator;
54-
55-
public static void RunWithFakeThreshold(string name, int value, Action action)
56-
{
57-
TypeInfo internalCalculator = InternalCalculator;
58-
if (internalCalculator == null)
59-
return; // Internal frame types are not reflectable on AoT platforms. Skip the test.
38+
int lastValue = field;
6039

61-
FieldInfo field = internalCalculator.GetDeclaredField(name);
62-
if (field is null || field.IsLiteral)
63-
return; // in Release config the field may be const
40+
// This is tricky hack. If only DEBUG build is targeted,
41+
// `RunWithFakeThreshold(ref int field, int value, Action action action)` would be more appropriate.
42+
// However, in RELEASE build, the code should be like this.
43+
// This is because const fields cannot be passed as ref arguments.
44+
// When a const field is passed to the in argument, a local
45+
// variable reference is implicitly passed to the in argument
46+
// so that the original const field value is never rewritten.
47+
ref int reference = ref Unsafe.AsRef(in field);
6448

65-
int lastValue = (int)field.GetValue(null);
66-
field.SetValue(null, value);
6749
try
6850
{
51+
reference = value;
52+
if (field != value)
53+
return; // In release build, the test will be skipped.
6954
action();
7055
}
7156
finally
7257
{
73-
field.SetValue(null, lastValue);
58+
reference = lastValue;
7459
}
7560
}
7661
}

src/libraries/System.Runtime.Numerics/tests/BigInteger/BigNumberTools.cs

Lines changed: 0 additions & 49 deletions
This file was deleted.

src/libraries/System.Runtime.Numerics/tests/BigInteger/modpow.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ public static void ModPow1Large2SmallInt()
142142
public static void ModPow1Large2SmallInt_Threshold()
143143
{
144144
// Again, with lower threshold
145-
BigIntTools.Utils.RunWithFakeThreshold("ReducerThreshold", 8, ModPow1Large2SmallInt);
145+
BigIntTools.Utils.RunWithFakeThreshold(BigIntegerCalculator.ReducerThreshold, 8, ModPow1Large2SmallInt);
146146
}
147147

148148
[Fact]
@@ -166,7 +166,7 @@ public static void ModPow2Large1SmallInt()
166166
public static void ModPow2Large1SmallInt_Threshold()
167167
{
168168
// Again, with lower threshold
169-
BigIntTools.Utils.RunWithFakeThreshold("ReducerThreshold", 8, ModPow2Large1SmallInt);
169+
BigIntTools.Utils.RunWithFakeThreshold(BigIntegerCalculator.ReducerThreshold, 8, ModPow2Large1SmallInt);
170170
}
171171

172172
// InlineData randomly generated using a new Random(0) and the same logic as is used in MyBigIntImp
@@ -194,7 +194,7 @@ public static void ModPow3LargeInt(string value, string exponent, string modulus
194194
Assert.Equal(resultInt, BigInteger.ModPow(valueInt, exponentInt, modulusInt));
195195

196196
// Once with reduced threshold
197-
BigIntTools.Utils.RunWithFakeThreshold("ReducerThreshold", 8, () =>
197+
BigIntTools.Utils.RunWithFakeThreshold(BigIntegerCalculator.ReducerThreshold, 8, () =>
198198
{
199199
Assert.Equal(resultInt, BigInteger.ModPow(valueInt, exponentInt, modulusInt));
200200
});

src/libraries/System.Runtime.Numerics/tests/BigInteger/multiply.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,14 @@ public static void RunMultiply_TwoLargeBigIntegers()
3737
public static void RunMultiply_TwoLargeBigIntegers_Threshold()
3838
{
3939
// Again, with lower threshold
40-
BigIntTools.Utils.RunWithFakeThreshold("SquareThreshold", 8, () =>
41-
BigIntTools.Utils.RunWithFakeThreshold("MultiplyThreshold", 8, RunMultiply_TwoLargeBigIntegers)
40+
BigIntTools.Utils.RunWithFakeThreshold(BigIntegerCalculator.SquareThreshold, 8, () =>
41+
BigIntTools.Utils.RunWithFakeThreshold(BigIntegerCalculator.MultiplyThreshold, 8, RunMultiply_TwoLargeBigIntegers)
4242
);
4343

4444
// Again, with lower threshold
45-
BigIntTools.Utils.RunWithFakeThreshold("SquareThreshold", 8, () =>
46-
BigIntTools.Utils.RunWithFakeThreshold("MultiplyThreshold", 8, () =>
47-
BigIntTools.Utils.RunWithFakeThreshold("StackAllocThreshold", 8, RunMultiply_TwoLargeBigIntegers)
45+
BigIntTools.Utils.RunWithFakeThreshold(BigIntegerCalculator.SquareThreshold, 8, () =>
46+
BigIntTools.Utils.RunWithFakeThreshold(BigIntegerCalculator.MultiplyThreshold, 8, () =>
47+
BigIntTools.Utils.RunWithFakeThreshold(BigIntegerCalculator.StackAllocThreshold, 8, RunMultiply_TwoLargeBigIntegers)
4848
)
4949
);
5050
}

src/libraries/System.Runtime.Numerics/tests/BigInteger/parse.cs

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ public static IEnumerable<object[]> Cultures
3737
public static void RunParseToStringTests(CultureInfo culture)
3838
{
3939
Test();
40-
BigNumberTools.Utils.RunWithFakeThreshold("s_naiveThreshold", 0, Test);
40+
BigIntTools.Utils.RunWithFakeThreshold(Number.s_naiveThreshold, 0, Test);
41+
4142
void Test()
4243
{
4344
byte[] tempByteArray1 = new byte[0];
@@ -101,7 +102,9 @@ void Test()
101102
public void Parse_Subspan_Success(string input, int offset, int length, string expected)
102103
{
103104
Test();
104-
BigNumberTools.Utils.RunWithFakeThreshold("s_naiveThreshold", 0, Test);
105+
106+
BigIntTools.Utils.RunWithFakeThreshold(Number.s_naiveThreshold, 0, Test);
107+
105108
void Test()
106109
{
107110
Eval(BigInteger.Parse(input.AsSpan(offset, length)), expected);
@@ -114,7 +117,8 @@ void Test()
114117
public void Parse_EmptySubspan_Fails()
115118
{
116119
Test();
117-
BigNumberTools.Utils.RunWithFakeThreshold("s_naiveThreshold", 0, Test);
120+
BigIntTools.Utils.RunWithFakeThreshold(Number.s_naiveThreshold, 0, Test);
121+
118122
void Test()
119123
{
120124
Assert.False(BigInteger.TryParse("12345".AsSpan(0, 0), out BigInteger result));
@@ -150,6 +154,33 @@ public void Parse_Hex32Bits()
150154
});
151155
}
152156

157+
public static IEnumerable<object[]> RegressionIssueRuntime94610_TestData()
158+
{
159+
yield return new object[]
160+
{
161+
new string('9', 865),
162+
};
163+
164+
yield return new object[]
165+
{
166+
new string('9', 20161),
167+
};
168+
}
169+
170+
[Theory]
171+
[MemberData(nameof(RegressionIssueRuntime94610_TestData))]
172+
public void RegressionIssueRuntime94610(string text)
173+
{
174+
// Regression test for: https://github.com/dotnet/runtime/issues/94610
175+
Test();
176+
BigIntTools.Utils.RunWithFakeThreshold(Number.s_naiveThreshold, 0, Test);
177+
178+
void Test()
179+
{
180+
VerifyParseToString(text, NumberStyles.Integer, true);
181+
}
182+
}
183+
153184
private static void RunFormatProviderParseStrings()
154185
{
155186
NumberFormatInfo nfi = new NumberFormatInfo();
@@ -782,7 +813,7 @@ private static string GetBinaryDigitSequence(int min, int max, Random random)
782813
{
783814
string result = string.Empty;
784815
int size = random.Next(min, max);
785-
816+
786817
for (int i = 0; i < size; i++)
787818
{
788819
result += random.Next(0, 2);

src/libraries/System.Runtime.Numerics/tests/System.Runtime.Numerics.Tests.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
<Compile Include="BigInteger\BigIntegerToStringTests.cs" />
1111
<Compile Include="BigInteger\BigInteger.AddTests.cs" />
1212
<Compile Include="BigInteger\BigInteger.SubtractTests.cs" />
13-
<Compile Include="BigInteger\BigNumberTools.cs" />
1413
<Compile Include="BigInteger\BigIntTools.cs" />
1514
<Compile Include="BigInteger\cast_from.cs" />
1615
<Compile Include="BigInteger\cast_to.cs" />

0 commit comments

Comments
 (0)