Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

List(Of T) saved by unknown hero?

Tags:

.net

vb.net

What prevents a List(Of T) from throwing an arithmetic overflow exception when I set the internal _version field to Integer.MaxValue and add a new item?


TL;DR:

As you can see when looking at the source code there's no protection against the potential arithmetic overflow. But some-one/thing is setting the value to Integer.MinValue if the value is maximum.

<__DynamicallyInvokable()> _
Public Sub Add(ByVal item As T)
    If (Me._size = Me._items.Length) Then
        Me.EnsureCapacity((Me._size + 1))
    End If
    Me._items(Me._size++) = item
    Me._version += 1
End Sub

Detailed

As the internal item-array of List(Of T) is private, and I wouldn't use reflection to access it, I decided to create a custom list using the original List(Of T) source code.

However, I noticed that there were no protection against a potential arithmetic overflow if the version ever reached Integer.MaxValue.

Me._version += 1

So I modified the code:

Me._version = If((Me._version = Integer.MaxValue), Integer.MinValue, (Me._version + 1I))

Now begins the fun part; Out of curiosity, I set up a test where I used reflection to set the internal _version field to max.

Dim flags As BindingFlags = (BindingFlags.Instance Or BindingFlags.NonPublic)
Dim list As New List(Of String)
Dim _version As FieldInfo = GetType(List(Of String)).GetField("_version", flags)

_version.SetValue(list, Integer.MaxValue)
Debug.WriteLine("Count: {0}, Version: {1}", list.Count, _version.GetValue(list))
list.Add("str")
Debug.WriteLine("Count: {0}, Version: {1}", list.Count, _version.GetValue(list))

I was shocked by the results:

Count: 0, Version: 2147483647
Count: 1, Version: -2147483648

There's nothing in the source code that sets the field to minimum if maximum is reached. I even looked at the DynamicallyInvokable attribute, but as far as I can tell it's not relevant.

<__DynamicallyInvokable()> _
Public Sub Add(ByVal item As T)
    If (Me._size = Me._items.Length) Then
        Me.EnsureCapacity((Me._size + 1))
    End If
    Me._items(Me._size++) = item
    Me._version += 1
End Sub

Private Sub EnsureCapacity(ByVal min As Integer)
    If (Me._items.Length < min) Then
        Dim num As Integer = IIf((Me._items.Length = 0), 4, (Me._items.Length * 2))
        If (num > &H7FEFFFFF) Then
            num = &H7FEFFFFF
        End If
        If (num < min) Then
            num = min
        End If
        Me.Capacity = num
    End If
End Sub

<__DynamicallyInvokable()> _
Public Property Capacity As Integer
    <TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries"), __DynamicallyInvokable()> _
    Get
        Return Me._items.Length
    End Get
    <__DynamicallyInvokable()> _
    Set(ByVal value As Integer)
        If (value < Me._size) Then
            ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.value, ExceptionResource.ArgumentOutOfRange_SmallCapacity)
        End If
        If (value <> Me._items.Length) Then
            If (value > 0) Then
                Dim destinationArray As T() = New T(value - 1) {}
                If (Me._size > 0) Then
                    Array.Copy(Me._items, 0, destinationArray, 0, Me._size)
                End If
                Me._items = destinationArray
            Else
                Me._items = List(Of T)._emptyArray
            End If
        End If
    End Set
End Property
like image 421
Bjørn-Roger Kringsjå Avatar asked Sep 09 '14 08:09

Bjørn-Roger Kringsjå


1 Answers

The base class library is written in C#, not in VB.NET.

By default, arithmetic operations in C# do not throw overflow exceptions. Instead, they silently wrap the value around, as you have noticed.

This decision was probably made for reasons of efficiency; not everyone agrees with it. More information can be found in the following questions:

  • Why doesn't C# use arithmetic overflow checking by default?
  • Why don't languages raise errors on integer overflow by default?
like image 55
Heinzi Avatar answered Nov 13 '22 05:11

Heinzi