Skip to content

Commit 8f91099

Browse files
authored
Fix undisposed enumerators (#48322)
-Fix converters not disposing enumerators (issue #46349) -Add tests
1 parent 7ec683c commit 8f91099

15 files changed

+176
-0
lines changed

src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ConcurrentQueueOfTConverter.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ protected override bool OnWriteResume(Utf8JsonWriter writer, TCollection value,
3333
enumerator = value.GetEnumerator();
3434
if (!enumerator.MoveNext())
3535
{
36+
enumerator.Dispose();
3637
return true;
3738
}
3839
}
@@ -58,6 +59,7 @@ protected override bool OnWriteResume(Utf8JsonWriter writer, TCollection value,
5859
}
5960
} while (enumerator.MoveNext());
6061

62+
enumerator.Dispose();
6163
return true;
6264
}
6365
}

src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ConcurrentStackOfTConverter.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ protected override bool OnWriteResume(Utf8JsonWriter writer, TCollection value,
3333
enumerator = value.GetEnumerator();
3434
if (!enumerator.MoveNext())
3535
{
36+
enumerator.Dispose();
3637
return true;
3738
}
3839
}
@@ -58,6 +59,7 @@ protected override bool OnWriteResume(Utf8JsonWriter writer, TCollection value,
5859
}
5960
} while (enumerator.MoveNext());
6061

62+
enumerator.Dispose();
6163
return true;
6264
}
6365
}

src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/DictionaryOfTKeyTValueConverter.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ protected internal override bool OnWriteResume(
4141
enumerator = value.GetEnumerator();
4242
if (!enumerator.MoveNext())
4343
{
44+
enumerator.Dispose();
4445
return true;
4546
}
4647
}
@@ -93,6 +94,7 @@ protected internal override bool OnWriteResume(
9394
} while (enumerator.MoveNext());
9495
}
9596

97+
enumerator.Dispose();
9698
return true;
9799
}
98100
}

src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ICollectionOfTConverter.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ protected override bool OnWriteResume(
6565
enumerator = value.GetEnumerator();
6666
if (!enumerator.MoveNext())
6767
{
68+
enumerator.Dispose();
6869
return true;
6970
}
7071
}
@@ -90,6 +91,7 @@ protected override bool OnWriteResume(
9091
}
9192
} while (enumerator.MoveNext());
9293

94+
enumerator.Dispose();
9395
return true;
9496
}
9597

src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IDictionaryOfTKeyTValueConverter.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ protected internal override bool OnWriteResume(
6767
enumerator = value.GetEnumerator();
6868
if (!enumerator.MoveNext())
6969
{
70+
enumerator.Dispose();
7071
return true;
7172
}
7273
}
@@ -102,6 +103,7 @@ protected internal override bool OnWriteResume(
102103
state.Current.EndDictionaryElement();
103104
} while (enumerator.MoveNext());
104105

106+
enumerator.Dispose();
105107
return true;
106108
}
107109

src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IEnumerableOfTConverter.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ protected override bool OnWriteResume(Utf8JsonWriter writer, TCollection value,
3636
enumerator = value.GetEnumerator();
3737
if (!enumerator.MoveNext())
3838
{
39+
enumerator.Dispose();
3940
return true;
4041
}
4142
}
@@ -62,6 +63,7 @@ protected override bool OnWriteResume(Utf8JsonWriter writer, TCollection value,
6263
}
6364
} while (enumerator.MoveNext());
6465

66+
enumerator.Dispose();
6567
return true;
6668
}
6769

src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IListOfTConverter.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ protected override bool OnWriteResume(Utf8JsonWriter writer, TCollection value,
6161
enumerator = value.GetEnumerator();
6262
if (!enumerator.MoveNext())
6363
{
64+
enumerator.Dispose();
6465
return true;
6566
}
6667
}
@@ -86,6 +87,7 @@ protected override bool OnWriteResume(Utf8JsonWriter writer, TCollection value,
8687
}
8788
} while (enumerator.MoveNext());
8889

90+
enumerator.Dispose();
8991
return true;
9092
}
9193

src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/IReadOnlyDictionaryOfTKeyTValueConverter.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ protected internal override bool OnWriteResume(Utf8JsonWriter writer, TCollectio
3333
enumerator = value.GetEnumerator();
3434
if (!enumerator.MoveNext())
3535
{
36+
enumerator.Dispose();
3637
return true;
3738
}
3839
}
@@ -69,6 +70,7 @@ protected internal override bool OnWriteResume(Utf8JsonWriter writer, TCollectio
6970
state.Current.EndDictionaryElement();
7071
} while (enumerator.MoveNext());
7172

73+
enumerator.Dispose();
7274
return true;
7375
}
7476

src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ISetOfTConverter.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ protected override bool OnWriteResume(Utf8JsonWriter writer, TCollection value,
5858
enumerator = value.GetEnumerator();
5959
if (!enumerator.MoveNext())
6060
{
61+
enumerator.Dispose();
6162
return true;
6263
}
6364
}
@@ -83,6 +84,7 @@ protected override bool OnWriteResume(Utf8JsonWriter writer, TCollection value,
8384
}
8485
} while (enumerator.MoveNext());
8586

87+
enumerator.Dispose();
8688
return true;
8789
}
8890

src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ImmutableDictionaryOfTKeyTValueConverter.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ protected internal override bool OnWriteResume(Utf8JsonWriter writer, TCollectio
4444
enumerator = value.GetEnumerator();
4545
if (!enumerator.MoveNext())
4646
{
47+
enumerator.Dispose();
4748
return true;
4849
}
4950
}
@@ -80,6 +81,7 @@ protected internal override bool OnWriteResume(Utf8JsonWriter writer, TCollectio
8081
state.Current.EndDictionaryElement();
8182
} while (enumerator.MoveNext());
8283

84+
enumerator.Dispose();
8385
return true;
8486
}
8587
}

src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ImmutableEnumerableOfTConverter.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ protected override bool OnWriteResume(Utf8JsonWriter writer, TCollection value,
4343
enumerator = value.GetEnumerator();
4444
if (!enumerator.MoveNext())
4545
{
46+
enumerator.Dispose();
4647
return true;
4748
}
4849
}
@@ -68,6 +69,7 @@ protected override bool OnWriteResume(Utf8JsonWriter writer, TCollection value,
6869
}
6970
} while (enumerator.MoveNext());
7071

72+
enumerator.Dispose();
7173
return true;
7274
}
7375
}

src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/QueueOfTConverter.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ protected override bool OnWriteResume(Utf8JsonWriter writer, TCollection value,
3232
enumerator = value.GetEnumerator();
3333
if (!enumerator.MoveNext())
3434
{
35+
enumerator.Dispose();
3536
return true;
3637
}
3738
}
@@ -57,6 +58,7 @@ protected override bool OnWriteResume(Utf8JsonWriter writer, TCollection value,
5758
}
5859
} while (enumerator.MoveNext());
5960

61+
enumerator.Dispose();
6062
return true;
6163
}
6264
}

src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/StackOfTConverter.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ protected override bool OnWriteResume(Utf8JsonWriter writer, TCollection value,
3232
enumerator = value.GetEnumerator();
3333
if (!enumerator.MoveNext())
3434
{
35+
enumerator.Dispose();
3536
return true;
3637
}
3738
}
@@ -57,6 +58,7 @@ protected override bool OnWriteResume(Utf8JsonWriter writer, TCollection value,
5758
}
5859
} while (enumerator.MoveNext());
5960

61+
enumerator.Dispose();
6062
return true;
6163
}
6264
}

src/libraries/System.Text.Json/tests/Serialization/CollectionTests/CollectionTests.Generic.Write.cs

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -882,6 +882,78 @@ public static void ConvertIEnumerableValueTypesThenSerialize()
882882
Assert.Equal(expectedJson, JsonSerializer.Serialize<IEnumerable<ValueB>>(valueBs));
883883
}
884884

885+
[Fact]
886+
public static void WriteIEnumerableT_DisposesEnumerators()
887+
{
888+
for (int count = 0; count < 5; count++)
889+
{
890+
var items = new RefCountedList<int>(Enumerable.Range(1, count));
891+
_ = JsonSerializer.Serialize(items.AsEnumerable());
892+
893+
Assert.Equal(0, items.RefCount);
894+
}
895+
}
896+
897+
[Fact]
898+
public static void WriteICollectionT_DisposesEnumerators()
899+
{
900+
for (int count = 0; count < 5; count++)
901+
{
902+
var items = new RefCountedList<int>(Enumerable.Range(1, count));
903+
_ = JsonSerializer.Serialize((ICollection<int>)items);
904+
905+
Assert.Equal(0, items.RefCount);
906+
}
907+
}
908+
909+
[Fact]
910+
public static void WriteIListT_DisposesEnumerators()
911+
{
912+
for (int count = 0; count < 5; count++)
913+
{
914+
var items = new RefCountedList<int>(Enumerable.Range(1, count));
915+
_ = JsonSerializer.Serialize((IList<int>)items);
916+
917+
Assert.Equal(0, items.RefCount);
918+
}
919+
}
920+
921+
[Fact]
922+
public static void WriteIDictionaryT_DisposesEnumerators()
923+
{
924+
for (int count = 0; count < 5; count++)
925+
{
926+
var pairs = new RefCountedDictionary<int, int>(Enumerable.Range(1, count).Select(x => new KeyValuePair<int, int>(x, x)));
927+
_ = JsonSerializer.Serialize((IDictionary<int, int>)pairs);
928+
929+
Assert.Equal(0, pairs.RefCount);
930+
}
931+
}
932+
933+
[Fact]
934+
public static void WriteIReadOnlyDictionaryT_DisposesEnumerators()
935+
{
936+
for (int count = 0; count < 5; count++)
937+
{
938+
var pairs = new RefCountedDictionary<int, int>(Enumerable.Range(1, count).Select(x => new KeyValuePair<int, int>(x, x)));
939+
_ = JsonSerializer.Serialize((IReadOnlyDictionary<int, int>)pairs);
940+
941+
Assert.Equal(0, pairs.RefCount);
942+
}
943+
}
944+
945+
[Fact]
946+
public static void WriteISetT_DisposesEnumerators()
947+
{
948+
for (int count = 0; count < 5; count++)
949+
{
950+
var items = new RefCountedSet<int>(Enumerable.Range(1, count));
951+
_ = JsonSerializer.Serialize((ISet<int>)items);
952+
953+
Assert.Equal(0, items.RefCount);
954+
}
955+
}
956+
885957
public class SimpleClassWithKeyValuePairs
886958
{
887959
public KeyValuePair<string, string> KvpWStrVal { get; set; }
@@ -901,5 +973,57 @@ public class ValueB
901973
{
902974
public int Value { get; set; }
903975
}
976+
977+
private class RefCountedList<T> : List<T>, IEnumerable<T> // Reimplement interface.
978+
{
979+
public RefCountedList() : base() { }
980+
public RefCountedList(IEnumerable<T> collection) : base(collection) { }
981+
982+
public int RefCount { get; private set; }
983+
984+
IEnumerator<T> IEnumerable<T>.GetEnumerator()
985+
{
986+
RefCount++;
987+
return new DisposableEnumerator<T>(GetEnumerator(), () => RefCount--);
988+
}
989+
990+
Collections.IEnumerator Collections.IEnumerable.GetEnumerator() => this.AsEnumerable().GetEnumerator();
991+
}
992+
993+
private class RefCountedDictionary<TKey, TValue> : Dictionary<TKey, TValue>, IEnumerable<KeyValuePair<TKey, TValue>> // Reimplement interface.
994+
{
995+
public RefCountedDictionary() : base() { }
996+
public RefCountedDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection)
997+
{
998+
foreach (var kvp in collection)
999+
Add(kvp.Key, kvp.Value);
1000+
}
1001+
1002+
public int RefCount { get; private set; }
1003+
1004+
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator()
1005+
{
1006+
RefCount++;
1007+
return new DisposableEnumerator<KeyValuePair<TKey, TValue>>(GetEnumerator(), () => RefCount--);
1008+
}
1009+
1010+
Collections.IEnumerator Collections.IEnumerable.GetEnumerator() => this.AsEnumerable().GetEnumerator();
1011+
}
1012+
1013+
private class RefCountedSet<T> : HashSet<T>, IEnumerable<T> // Reimplement interface.
1014+
{
1015+
public RefCountedSet() : base() { }
1016+
public RefCountedSet(IEnumerable<T> collection) : base(collection) { }
1017+
1018+
public int RefCount { get; private set; }
1019+
1020+
IEnumerator<T> IEnumerable<T>.GetEnumerator()
1021+
{
1022+
RefCount++;
1023+
return new DisposableEnumerator<T>(GetEnumerator(), () => RefCount--);
1024+
}
1025+
1026+
Collections.IEnumerator Collections.IEnumerable.GetEnumerator() => this.AsEnumerable().GetEnumerator();
1027+
}
9041028
}
9051029
}

src/libraries/System.Text.Json/tests/Serialization/TestClasses/TestClasses.GenericCollections.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1641,4 +1641,30 @@ public void Verify()
16411641
Assert.Equal("value1", Dictionary.Value["key1"]);
16421642
}
16431643
}
1644+
1645+
public class DisposableEnumerator<T> : IEnumerator<T>
1646+
{
1647+
private readonly IEnumerator<T> _inner;
1648+
private Action _onDispose;
1649+
1650+
public DisposableEnumerator(IEnumerator<T> inner, Action onDispose)
1651+
{
1652+
_inner = inner;
1653+
_onDispose = onDispose;
1654+
}
1655+
1656+
public T Current => _inner.Current;
1657+
1658+
object IEnumerator.Current => ((IEnumerator)_inner).Current;
1659+
1660+
public bool MoveNext() => _inner.MoveNext();
1661+
1662+
public void Dispose()
1663+
{
1664+
_onDispose?.Invoke();
1665+
_onDispose = null;
1666+
}
1667+
1668+
public void Reset() => _inner.Reset();
1669+
}
16441670
}

0 commit comments

Comments
 (0)