diff --git a/src/Compilers/Core/CodeAnalysisTest/CodeAnalysisTest.csproj b/src/Compilers/Core/CodeAnalysisTest/CodeAnalysisTest.csproj index 0a211286f41d25f175799f9009f62936058a319a..b2d348c1935649a15f404c900adc4c241b772126 100644 --- a/src/Compilers/Core/CodeAnalysisTest/CodeAnalysisTest.csproj +++ b/src/Compilers/Core/CodeAnalysisTest/CodeAnalysisTest.csproj @@ -30,6 +30,7 @@ + diff --git a/src/Compilers/Core/CodeAnalysisTest/Text/SourceTextStreamTests.cs b/src/Compilers/Core/CodeAnalysisTest/Text/SourceTextStreamTests.cs new file mode 100644 index 0000000000000000000000000000000000000000..26c2ef067d91d3719faaa345ee324460c4b78bd8 --- /dev/null +++ b/src/Compilers/Core/CodeAnalysisTest/Text/SourceTextStreamTests.cs @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Text; +using Microsoft.CodeAnalysis.Text; +using Xunit; + +namespace Microsoft.CodeAnalysis.UnitTests.Text +{ + public sealed class SourceTextStreamTests + { + private static readonly Encoding s_utf8NoBom = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); + + /// + /// In the case the destination buffer is of insufficient length to store the reading of a single + /// character we will throw. Returning 0 is not correct here as that indicates end of stream + /// not insufficient space in destination buffer. + /// + [Fact] + public void MinimumLength() + { + var sourceText = SourceText.From("hello world", s_utf8NoBom); + using (var stream = new SourceTextStream(sourceText)) + { + var buffer = new byte[100]; + var max = s_utf8NoBom.GetMaxByteCount(charCount: 1); + for (int i = 0; i < max; i++) + { + var local = i; + Assert.Throws(typeof(ArgumentException), () => stream.Read(buffer, 0, local)); + } + } + } + + /// + /// In the case there is insufficient number of bytes to store the next character the read should + /// complete with the already read data vs. throwing. + /// + [Fact] + public void Issue1197() + { + var baseText = "food time"; + var text = string.Format("{0}{1}", baseText, '\u2019'); + var encoding = s_utf8NoBom; + var sourceText = SourceText.From(text, encoding); + using (var stream = new SourceTextStream(sourceText, bufferSize: text.Length * 2)) + { + var buffer = new byte[baseText.Length + 1]; + Assert.Equal(baseText.Length, stream.Read(buffer, 0, buffer.Length)); + Assert.True(buffer.Take(baseText.Length).SequenceEqual(encoding.GetBytes(baseText))); + + Assert.Equal(3, stream.Read(buffer, 0, buffer.Length)); + Assert.True(buffer.Take(3).SequenceEqual(encoding.GetBytes(new[] { '\u2019' }))); + } + } + } +} diff --git a/src/Compilers/Core/Portable/Text/SourceTextStream.cs b/src/Compilers/Core/Portable/Text/SourceTextStream.cs index f715f3da86e91f3f71a8a2b922cc5bd44702db3d..6e75b22f3552c0c76964763ac3149c3933f247fe 100644 --- a/src/Compilers/Core/Portable/Text/SourceTextStream.cs +++ b/src/Compilers/Core/Portable/Text/SourceTextStream.cs @@ -14,6 +14,7 @@ internal sealed class SourceTextStream : Stream private readonly SourceText _source; private readonly Encoder _encoder; + private int _minimumTargetBufferCount; private int _position; private int _sourceOffset; private readonly char[] _charBuffer; @@ -25,6 +26,7 @@ public SourceTextStream(SourceText source, int bufferSize = 2048) { _source = source; _encoder = source.Encoding.GetEncoder(); + _minimumTargetBufferCount = source.Encoding.GetMaxByteCount(charCount: 1); _sourceOffset = 0; _position = 0; _charBuffer = new char[Math.Min(bufferSize, _source.Length)]; @@ -60,18 +62,20 @@ public override long Length public override long Position { - get - { - return _position; - } - set - { - throw new NotSupportedException(); - } + get { return _position; } + set { throw new NotSupportedException(); } } public override int Read(byte[] buffer, int offset, int count) { + if (count < _minimumTargetBufferCount) + { + // The buffer must be able to hold at least one character from the + // SourceText stream. Returning 0 for that case isn't correct because + // that indicates end of stream vs. insufficient buffer. + throw new ArgumentException(nameof(count)); + } + int originalCount = count; if (!_preambleWritten) @@ -81,7 +85,7 @@ public override int Read(byte[] buffer, int offset, int count) count -= bytesWritten; } - while (count > 0 && _position < _source.Length) + while (count >= _minimumTargetBufferCount && _position < _source.Length) { if (_bufferUnreadChars == 0) {