diff --git a/src/Polyfill/Polyfill_StringBuilder_Append.cs b/src/Polyfill/Polyfill_StringBuilder_Append.cs index 854bec4b..535eb02f 100644 --- a/src/Polyfill/Polyfill_StringBuilder_Append.cs +++ b/src/Polyfill/Polyfill_StringBuilder_Append.cs @@ -38,7 +38,13 @@ public static StringBuilder Append(this StringBuilder target, StringBuilder? val return target; } - return target.Append(value.ToString(), startIndex, count); + if (startIndex > value.Length - count) + { + throw new ArgumentOutOfRangeException(nameof(startIndex)); + } + + // Only materialize the requested range, not the whole of value. + return target.Append(value.ToString(startIndex, count)); } #endif diff --git a/src/Split/net461/Polyfill_StringBuilder_Append.cs b/src/Split/net461/Polyfill_StringBuilder_Append.cs index d3970ce5..1f99a9c6 100644 --- a/src/Split/net461/Polyfill_StringBuilder_Append.cs +++ b/src/Split/net461/Polyfill_StringBuilder_Append.cs @@ -30,7 +30,11 @@ public static StringBuilder Append(this StringBuilder target, StringBuilder? val { return target; } - return target.Append(value.ToString(), startIndex, count); + if (startIndex > value.Length - count) + { + throw new ArgumentOutOfRangeException(nameof(startIndex)); + } + return target.Append(value.ToString(startIndex, count)); } #if FeatureMemory /// diff --git a/src/Split/net462/Polyfill_StringBuilder_Append.cs b/src/Split/net462/Polyfill_StringBuilder_Append.cs index d3970ce5..1f99a9c6 100644 --- a/src/Split/net462/Polyfill_StringBuilder_Append.cs +++ b/src/Split/net462/Polyfill_StringBuilder_Append.cs @@ -30,7 +30,11 @@ public static StringBuilder Append(this StringBuilder target, StringBuilder? val { return target; } - return target.Append(value.ToString(), startIndex, count); + if (startIndex > value.Length - count) + { + throw new ArgumentOutOfRangeException(nameof(startIndex)); + } + return target.Append(value.ToString(startIndex, count)); } #if FeatureMemory /// diff --git a/src/Split/net47/Polyfill_StringBuilder_Append.cs b/src/Split/net47/Polyfill_StringBuilder_Append.cs index d3970ce5..1f99a9c6 100644 --- a/src/Split/net47/Polyfill_StringBuilder_Append.cs +++ b/src/Split/net47/Polyfill_StringBuilder_Append.cs @@ -30,7 +30,11 @@ public static StringBuilder Append(this StringBuilder target, StringBuilder? val { return target; } - return target.Append(value.ToString(), startIndex, count); + if (startIndex > value.Length - count) + { + throw new ArgumentOutOfRangeException(nameof(startIndex)); + } + return target.Append(value.ToString(startIndex, count)); } #if FeatureMemory /// diff --git a/src/Split/net471/Polyfill_StringBuilder_Append.cs b/src/Split/net471/Polyfill_StringBuilder_Append.cs index d3970ce5..1f99a9c6 100644 --- a/src/Split/net471/Polyfill_StringBuilder_Append.cs +++ b/src/Split/net471/Polyfill_StringBuilder_Append.cs @@ -30,7 +30,11 @@ public static StringBuilder Append(this StringBuilder target, StringBuilder? val { return target; } - return target.Append(value.ToString(), startIndex, count); + if (startIndex > value.Length - count) + { + throw new ArgumentOutOfRangeException(nameof(startIndex)); + } + return target.Append(value.ToString(startIndex, count)); } #if FeatureMemory /// diff --git a/src/Split/net472/Polyfill_StringBuilder_Append.cs b/src/Split/net472/Polyfill_StringBuilder_Append.cs index d3970ce5..1f99a9c6 100644 --- a/src/Split/net472/Polyfill_StringBuilder_Append.cs +++ b/src/Split/net472/Polyfill_StringBuilder_Append.cs @@ -30,7 +30,11 @@ public static StringBuilder Append(this StringBuilder target, StringBuilder? val { return target; } - return target.Append(value.ToString(), startIndex, count); + if (startIndex > value.Length - count) + { + throw new ArgumentOutOfRangeException(nameof(startIndex)); + } + return target.Append(value.ToString(startIndex, count)); } #if FeatureMemory /// diff --git a/src/Split/net48/Polyfill_StringBuilder_Append.cs b/src/Split/net48/Polyfill_StringBuilder_Append.cs index d3970ce5..1f99a9c6 100644 --- a/src/Split/net48/Polyfill_StringBuilder_Append.cs +++ b/src/Split/net48/Polyfill_StringBuilder_Append.cs @@ -30,7 +30,11 @@ public static StringBuilder Append(this StringBuilder target, StringBuilder? val { return target; } - return target.Append(value.ToString(), startIndex, count); + if (startIndex > value.Length - count) + { + throw new ArgumentOutOfRangeException(nameof(startIndex)); + } + return target.Append(value.ToString(startIndex, count)); } #if FeatureMemory /// diff --git a/src/Split/net481/Polyfill_StringBuilder_Append.cs b/src/Split/net481/Polyfill_StringBuilder_Append.cs index d3970ce5..1f99a9c6 100644 --- a/src/Split/net481/Polyfill_StringBuilder_Append.cs +++ b/src/Split/net481/Polyfill_StringBuilder_Append.cs @@ -30,7 +30,11 @@ public static StringBuilder Append(this StringBuilder target, StringBuilder? val { return target; } - return target.Append(value.ToString(), startIndex, count); + if (startIndex > value.Length - count) + { + throw new ArgumentOutOfRangeException(nameof(startIndex)); + } + return target.Append(value.ToString(startIndex, count)); } #if FeatureMemory /// diff --git a/src/Split/netcoreapp2.0/Polyfill_StringBuilder_Append.cs b/src/Split/netcoreapp2.0/Polyfill_StringBuilder_Append.cs index d3970ce5..1f99a9c6 100644 --- a/src/Split/netcoreapp2.0/Polyfill_StringBuilder_Append.cs +++ b/src/Split/netcoreapp2.0/Polyfill_StringBuilder_Append.cs @@ -30,7 +30,11 @@ public static StringBuilder Append(this StringBuilder target, StringBuilder? val { return target; } - return target.Append(value.ToString(), startIndex, count); + if (startIndex > value.Length - count) + { + throw new ArgumentOutOfRangeException(nameof(startIndex)); + } + return target.Append(value.ToString(startIndex, count)); } #if FeatureMemory /// diff --git a/src/Split/netstandard2.0/Polyfill_StringBuilder_Append.cs b/src/Split/netstandard2.0/Polyfill_StringBuilder_Append.cs index d3970ce5..1f99a9c6 100644 --- a/src/Split/netstandard2.0/Polyfill_StringBuilder_Append.cs +++ b/src/Split/netstandard2.0/Polyfill_StringBuilder_Append.cs @@ -30,7 +30,11 @@ public static StringBuilder Append(this StringBuilder target, StringBuilder? val { return target; } - return target.Append(value.ToString(), startIndex, count); + if (startIndex > value.Length - count) + { + throw new ArgumentOutOfRangeException(nameof(startIndex)); + } + return target.Append(value.ToString(startIndex, count)); } #if FeatureMemory /// diff --git a/src/Split/uap10.0/Polyfill_StringBuilder_Append.cs b/src/Split/uap10.0/Polyfill_StringBuilder_Append.cs index d3970ce5..1f99a9c6 100644 --- a/src/Split/uap10.0/Polyfill_StringBuilder_Append.cs +++ b/src/Split/uap10.0/Polyfill_StringBuilder_Append.cs @@ -30,7 +30,11 @@ public static StringBuilder Append(this StringBuilder target, StringBuilder? val { return target; } - return target.Append(value.ToString(), startIndex, count); + if (startIndex > value.Length - count) + { + throw new ArgumentOutOfRangeException(nameof(startIndex)); + } + return target.Append(value.ToString(startIndex, count)); } #if FeatureMemory /// diff --git a/src/Tests/PolyfillTests_StringBuilder.cs b/src/Tests/PolyfillTests_StringBuilder.cs index 3c47878c..abb4206a 100644 --- a/src/Tests/PolyfillTests_StringBuilder.cs +++ b/src/Tests/PolyfillTests_StringBuilder.cs @@ -158,6 +158,87 @@ public async Task StringBuilder_Append_StringBuilder() await Assert.That(target.ToString()).IsEqualTo("ell"); } + [Test] + [Arguments(0, 5)] + [Arguments(0, 1)] + [Arguments(4, 1)] + [Arguments(2, 3)] + [Arguments(1, 3)] + [Arguments(0, 0)] + [Arguments(5, 0)] + public async Task StringBuilder_Append_StringBuilder_Ranges(int startIndex, int count) + { + var source = new StringBuilder("hello"); + var target = new StringBuilder("<"); + + var result = target.Append(source, startIndex, count); + + await Assert.That(ReferenceEquals(result, target)).IsTrue(); + await Assert.That(target.ToString()).IsEqualTo("<" + "hello".Substring(startIndex, count)); + await Assert.That(source.ToString()).IsEqualTo("hello"); + } + + [Test] + [Arguments(0, 30000)] + [Arguments(0, 1)] + [Arguments(1, 29999)] + [Arguments(7999, 2)] + [Arguments(12345, 6789)] + [Arguments(29999, 1)] + public async Task StringBuilder_Append_StringBuilder_MultiChunkSource(int startIndex, int count) + { + // many small appends leave the source holding many chunks + var source = new StringBuilder(16); + for (var index = 0; source.Length < 30000; index++) + { + source.Append((char) ('a' + index % 26)); + } + + var expected = source.ToString(); + var target = new StringBuilder("prefix"); + + target.Append(source, startIndex, count); + target.Append(source, startIndex, count); + + var range = expected.Substring(startIndex, count); + await Assert.That(target.ToString()).IsEqualTo("prefix" + range + range); + await Assert.That(source.ToString()).IsEqualTo(expected); + } + + [Test] + public async Task StringBuilder_Append_StringBuilder_Self() + { + var builder = new StringBuilder("hello"); + + builder.Append(builder, 1, 3); + + await Assert.That(builder.ToString()).IsEqualTo("helloell"); + } + + [Test] + public async Task StringBuilder_Append_StringBuilder_OutOfRange() + { + var value = new StringBuilder("world"); + + await Assert.That(() => new StringBuilder().Append(value, 0, 6)).Throws(); + await Assert.That(() => new StringBuilder().Append(value, 3, 3)).Throws(); + await Assert.That(() => new StringBuilder().Append(value, 5, 1)).Throws(); + await Assert.That(() => new StringBuilder().Append(value, 6, 1)).Throws(); + await Assert.That(() => new StringBuilder().Append(value, int.MaxValue, 1)).Throws(); + await Assert.That(() => new StringBuilder().Append(value, 1, int.MaxValue)).Throws(); + } + + [Test] + public async Task StringBuilder_Append_StringBuilder_Null() + { + var target = new StringBuilder("a"); + + target.Append((StringBuilder?) null, 0, 0); + + await Assert.That(target.ToString()).IsEqualTo("a"); + await Assert.That(() => new StringBuilder().Append((StringBuilder?) null, 0, 1)).Throws(); + } + [Test] public async Task StringBuilder_Append_StringBuilder_InvalidArgs() {