Last active
December 27, 2020 10:03
-
-
Save DavidKlempfner/d3eb11b9e947cef46bff3c1e2bfd23d6 to your computer and use it in GitHub Desktop.
HackingForeach.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Reflection; | |
namespace ConsoleApp9 | |
{ | |
class Program | |
{ | |
static void Main(string[] args) | |
{ | |
var list = new List<int> { 2, 3 }; | |
int version = GetVersion(list); //2 | |
bool isFirstIteration = true; | |
foreach (var item in list) | |
{ | |
//The first time this foreach loop is | |
//executed, list.GetEnumerator() will be called. | |
//It will return an instance of Enumerator | |
//with its version field set to list._version | |
//which is 2, and which doesn't change. | |
if (isFirstIteration) | |
{ | |
UpdateListUIntMaxValueTimes(list); | |
version = GetVersion(list); //1 | |
isFirstIteration = false; | |
} | |
//Usually the following line would cause a run time error at the start of the next iteration: | |
list[1] = 5; | |
//But because of the call to UpdateListUIntMaxValueTimes(list), | |
//the above line causes list._version to be 2, | |
//which is the same as Enumerator.version. | |
//So the foreach loop thinks the list has not been updated. | |
//On the second iteration, list._version = 3 | |
//which != what Enumerator.version has which is 2. | |
//On the 3rd iteration when MoveNext() is called, | |
//it will see that: | |
//list._version != Enumerator.version | |
//and an InvalidOperationException will be thrown. | |
version = GetVersion(list); | |
} | |
} | |
/// <summary> | |
/// This will modify the list 4294967295 times | |
/// which will overflow list._version | |
/// and make it one less than what it was | |
/// </summary> | |
/// <param name="list"></param> | |
static void UpdateListUIntMaxValueTimes(List<int> list) | |
{ | |
for (uint i = 0; i < uint.MaxValue; i++) | |
{ | |
list[0] = list[0]; | |
} | |
} | |
private static int GetVersion(List<int> list) | |
{ | |
int version = Convert.ToInt32(GetInstanceField(typeof(List<int>), list, "_version")); | |
return version; | |
} | |
private static object GetInstanceField(Type type, object instance, string fieldName) | |
{ | |
BindingFlags bindFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static; | |
FieldInfo field = type.GetField(fieldName, bindFlags); | |
return field.GetValue(instance); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment