Skip to content

Commit 4b01577

Browse files
committed
Lucene.Net.Support.VIntUtils: Fixed Debug.Assert to check for invalid VInt lengths, not requiring maximum lengths, which are in fact allowed for sources that don't change position on read.
1 parent 12ed55f commit 4b01577

2 files changed

Lines changed: 126 additions & 2 deletions

File tree

src/Lucene.Net.Tests/Support/TestVIntUtils.cs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,5 +171,93 @@ public void TestMaxLengths()
171171
assertEquals(VIntUtils.MaxVInt32Length, EncodeVInt32(-1).Length);
172172
assertEquals(VIntUtils.MaxVInt64Length, EncodeVInt64(long.MaxValue).Length);
173173
}
174+
175+
#if DEBUG
176+
177+
[TestCase(0)]
178+
[TestCase(1)]
179+
[TestCase(127)]
180+
[TestCase(128)]
181+
[TestCase(16383)]
182+
[TestCase(16384)]
183+
[TestCase(2097151)]
184+
[TestCase(2097152)]
185+
[TestCase(268435455)]
186+
[TestCase(int.MaxValue)]
187+
[TestCase(-1)]
188+
[LuceneNetSpecific]
189+
public void TestReadVInt32_DebugAssertRequiresCompleteSpan(int value)
190+
{
191+
byte[] encoded = EncodeVInt32(value);
192+
193+
// Every prefix shorter than the encoded value should assert.
194+
for (int length = 0; length < encoded.Length; length++)
195+
{
196+
Assert.Throws(Lucene.ExceptionExtensions.DebugAssertExceptionType, () =>
197+
{
198+
VIntUtils.TryReadVInt32(encoded.AsSpan(0, length), out _, out _);
199+
}, $"Expected {Lucene.ExceptionExtensions.DebugAssertExceptionType.AssemblyQualifiedName} for value {value} with span length {length}.");
200+
}
201+
202+
// The exact encoded length should succeed.
203+
{
204+
Assert.DoesNotThrow(() =>
205+
{
206+
bool ok = VIntUtils.TryReadVInt32(encoded, out int result, out int count);
207+
208+
assertTrue(ok);
209+
assertEquals(value, result);
210+
assertEquals(encoded.Length, count);
211+
});
212+
}
213+
}
214+
215+
[TestCase(0L)]
216+
[TestCase(1L)]
217+
[TestCase(127L)]
218+
[TestCase(128L)]
219+
[TestCase(16383L)]
220+
[TestCase(16384L)]
221+
[TestCase(2097151L)]
222+
[TestCase(2097152L)]
223+
[TestCase(268435455L)]
224+
[TestCase(268435456L)]
225+
[TestCase(34359738367L)]
226+
[TestCase(34359738368L)]
227+
[TestCase(4398046511103L)]
228+
[TestCase(4398046511104L)]
229+
[TestCase(562949953421311L)]
230+
[TestCase(562949953421312L)]
231+
[TestCase(72057594037927935L)]
232+
[TestCase(72057594037927936L)]
233+
[TestCase(long.MaxValue)]
234+
[LuceneNetSpecific]
235+
public void TestReadVInt64_DebugAssertRequiresCompleteSpan(long value)
236+
{
237+
byte[] encoded = EncodeVInt64(value);
238+
239+
// Every prefix shorter than the encoded value should assert.
240+
for (int length = 0; length < encoded.Length; length++)
241+
{
242+
Assert.Throws(Lucene.ExceptionExtensions.DebugAssertExceptionType, () =>
243+
{
244+
VIntUtils.TryReadVInt64(encoded.AsSpan(0, length), out _, out _);
245+
}, $"Expected {Lucene.ExceptionExtensions.DebugAssertExceptionType.AssemblyQualifiedName} for value {value} with span length {length}.");
246+
}
247+
248+
// The exact encoded length should succeed.
249+
{
250+
Assert.DoesNotThrow(() =>
251+
{
252+
bool ok = VIntUtils.TryReadVInt64(encoded, out long result, out int count);
253+
254+
assertTrue(ok);
255+
assertEquals(value, result);
256+
assertEquals(encoded.Length, count);
257+
});
258+
}
259+
}
260+
261+
#endif
174262
}
175263
}

src/Lucene.Net/Support/VIntUtils.cs

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ internal static class VIntUtils
5555
/// extra high bits set.</returns>
5656
public static bool TryReadVInt32(ReadOnlySpan<byte> source, out int value, out int count)
5757
{
58-
Debug.Assert(source.Length >= 1);
58+
AssertCanContainVInt32(source);
5959
byte b = source[0];
6060
if (b <= sbyte.MaxValue) // LUCENENET: Optimized equivalent of "if ((sbyte)b >= 0)"
6161
{
@@ -109,7 +109,7 @@ public static bool TryReadVInt32(ReadOnlySpan<byte> source, out int value, out i
109109
/// the continuation bit set (which would indicate a negative value, disallowed).</returns>
110110
public static bool TryReadVInt64(ReadOnlySpan<byte> source, out long value, out int count)
111111
{
112-
Debug.Assert(source.Length >= 1);
112+
AssertCanContainVInt64(source);
113113
byte b = source[0];
114114
if (b <= sbyte.MaxValue)
115115
{
@@ -181,6 +181,42 @@ public static bool TryReadVInt64(ReadOnlySpan<byte> source, out long value, out
181181
return b <= sbyte.MaxValue;
182182
}
183183

184+
[Conditional("DEBUG")]
185+
private static void AssertCanContainVInt32(ReadOnlySpan<byte> source)
186+
{
187+
int length = source.Length;
188+
if (length >= MaxVInt32Length) return;
189+
190+
int limit = Math.Min(length, MaxVInt32Length);
191+
192+
for (int i = 0; i < limit; i++)
193+
{
194+
if ((source[i] & 0x80) == 0)
195+
return;
196+
}
197+
198+
Debug.Assert(length >= MaxVInt32Length,
199+
"The span is too short to contain a complete VInt32.");
200+
}
201+
202+
[Conditional("DEBUG")]
203+
private static void AssertCanContainVInt64(ReadOnlySpan<byte> source)
204+
{
205+
int length = source.Length;
206+
if (length >= MaxVInt64Length) return;
207+
208+
int limit = Math.Min(length, MaxVInt64Length);
209+
210+
for (int i = 0; i < limit; i++)
211+
{
212+
if ((source[i] & 0x80) == 0)
213+
return;
214+
}
215+
216+
Debug.Assert(length >= MaxVInt64Length,
217+
"The span is too short to contain a complete VInt64.");
218+
}
219+
184220
// LUCENENET: The throw sites below are factored into dedicated, non-inlined helpers so the
185221
// hot Read*VInt* overrides that call them stay small enough for the JIT to inline. Keeping
186222
// the `throw` + message string out of the caller body avoids bloating its IL (the inliner

0 commit comments

Comments
 (0)