diff --git a/csharp/src/Google.Protobuf.Test/CodedInputStreamTest.cs b/csharp/src/Google.Protobuf.Test/CodedInputStreamTest.cs index 4335b88548..0ad286f378 100644 --- a/csharp/src/Google.Protobuf.Test/CodedInputStreamTest.cs +++ b/csharp/src/Google.Protobuf.Test/CodedInputStreamTest.cs @@ -926,39 +926,6 @@ namespace Google.Protobuf } } - [Test] - public void TestParseFromSpan() - { - var testMessage = new TestAllTypes - { - RepeatedBool = { true, false }, - SingleDouble = 42.0d, - SingleString = "testing" - }; - - ReadOnlySpan messagesBytes = testMessage.ToByteArray().AsSpan(); - TestAllTypes parsedMessage = TestAllTypes.Parser.ParseFrom(messagesBytes); - - Assert.AreEqual(testMessage, parsedMessage); - } - - [Test] - public void TestMergeFromSpan() - { - var testMessage = new TestAllTypes - { - RepeatedBool = { true, false }, - SingleDouble = 42.0d, - SingleString = "testing" - }; - - ReadOnlySpan messagesBytes = testMessage.ToByteArray().AsSpan(); - var mergedMessage = new TestAllTypes(); - mergedMessage.MergeFrom(messagesBytes); - - Assert.AreEqual(testMessage, mergedMessage); - } - /// A serialized big message private static byte[] GenerateBigSerializedMessage() { diff --git a/csharp/src/Google.Protobuf.Test/MessageParsingHelpers.cs b/csharp/src/Google.Protobuf.Test/MessageParsingHelpers.cs index 65d2fe0395..0e33cc025e 100644 --- a/csharp/src/Google.Protobuf.Test/MessageParsingHelpers.cs +++ b/csharp/src/Google.Protobuf.Test/MessageParsingHelpers.cs @@ -41,32 +41,40 @@ namespace Google.Protobuf { public static void AssertReadingMessage(MessageParser parser, byte[] bytes, Action assert) where T : IMessage { - var parsedStream = parser.ParseFrom(bytes); + var parsedBuffer = parser.ParseFrom(bytes); + assert(parsedBuffer); // Load content as single segment - var parsedBuffer = parser.ParseFrom(new ReadOnlySequence(bytes)); + parsedBuffer = parser.ParseFrom(new ReadOnlySequence(bytes)); assert(parsedBuffer); // Load content as multiple segments parsedBuffer = parser.ParseFrom(ReadOnlySequenceFactory.CreateWithContent(bytes)); assert(parsedBuffer); - assert(parsedStream); + // Load content as ReadOnlySpan + ReadOnlySpan bytesSpan = bytes.AsSpan(); + parsedBuffer = parser.ParseFrom(ref bytesSpan); + assert(parsedBuffer); } public static void AssertReadingMessage(MessageParser parser, byte[] bytes, Action assert) { - var parsedStream = parser.ParseFrom(bytes); + var parsedBuffer = parser.ParseFrom(bytes); + assert(parsedBuffer); // Load content as single segment - var parsedBuffer = parser.ParseFrom(new ReadOnlySequence(bytes)); + parsedBuffer = parser.ParseFrom(new ReadOnlySequence(bytes)); assert(parsedBuffer); // Load content as multiple segments parsedBuffer = parser.ParseFrom(ReadOnlySequenceFactory.CreateWithContent(bytes)); assert(parsedBuffer); - assert(parsedStream); + // Load content as ReadOnlySpan + ReadOnlySpan bytesSpan = bytes.AsSpan(); + parsedBuffer = parser.ParseFrom(ref bytesSpan); + assert(parsedBuffer); } public static void AssertReadingMessageThrows(MessageParser parser, byte[] bytes) @@ -76,6 +84,12 @@ namespace Google.Protobuf Assert.Throws(() => parser.ParseFrom(bytes)); Assert.Throws(() => parser.ParseFrom(new ReadOnlySequence(bytes))); + + Assert.Throws(() => + { + ReadOnlySpan bytesSpan = bytes.AsSpan(); + parser.ParseFrom(ref bytesSpan); + }); } public static void AssertRoundtrip(MessageParser parser, T message, Action additionalAssert = null) where T : IMessage @@ -87,8 +101,12 @@ namespace Google.Protobuf message.WriteTo(bufferWriter); Assert.AreEqual(bytes, bufferWriter.WrittenSpan.ToArray(), "Both serialization approaches need to result in the same data."); + var parsedBuffer = parser.ParseFrom(bytes); + Assert.AreEqual(message, parsedBuffer); + additionalAssert?.Invoke(parsedBuffer); + // Load content as single segment - var parsedBuffer = parser.ParseFrom(new ReadOnlySequence(bytes)); + parsedBuffer = parser.ParseFrom(new ReadOnlySequence(bytes)); Assert.AreEqual(message, parsedBuffer); additionalAssert?.Invoke(parsedBuffer); @@ -97,10 +115,11 @@ namespace Google.Protobuf Assert.AreEqual(message, parsedBuffer); additionalAssert?.Invoke(parsedBuffer); - var parsedStream = parser.ParseFrom(bytes); - - Assert.AreEqual(message, parsedStream); - additionalAssert?.Invoke(parsedStream); + // Load content as ReadOnlySpan + ReadOnlySpan bytesSpan = bytes.AsSpan(); + parsedBuffer = parser.ParseFrom(ref bytesSpan); + Assert.AreEqual(message, parsedBuffer); + additionalAssert?.Invoke(parsedBuffer); } public static void AssertWritingMessage(IMessage message) diff --git a/csharp/src/Google.Protobuf/MessageExtensions.cs b/csharp/src/Google.Protobuf/MessageExtensions.cs index c4b3f82343..1c0c437c58 100644 --- a/csharp/src/Google.Protobuf/MessageExtensions.cs +++ b/csharp/src/Google.Protobuf/MessageExtensions.cs @@ -85,8 +85,8 @@ namespace Google.Protobuf /// The message to merge the data into. /// Span containing the data to merge, which must be protobuf-encoded binary data. [SecuritySafeCritical] - public static void MergeFrom(this IMessage message, ReadOnlySpan span) => - MergeFrom(message, span, false, null); + public static void MergeFrom(this IMessage message, ref ReadOnlySpan span) => + MergeFrom(message, ref span, false, null); /// /// Merges length-delimited data from the given stream into an existing message. @@ -304,9 +304,9 @@ namespace Google.Protobuf } [SecuritySafeCritical] - internal static void MergeFrom(this IMessage message, ReadOnlySpan data, bool discardUnknownFields, ExtensionRegistry registry) + internal static void MergeFrom(this IMessage message, ref ReadOnlySpan data, bool discardUnknownFields, ExtensionRegistry registry) { - ParseContext.Initialize(data, out ParseContext ctx); + ParseContext.Initialize(ref data, out ParseContext ctx); ctx.DiscardUnknownFields = discardUnknownFields; ctx.ExtensionRegistry = registry; ParsingPrimitivesMessages.ReadRawMessage(ref ctx, message); diff --git a/csharp/src/Google.Protobuf/MessageParser.cs b/csharp/src/Google.Protobuf/MessageParser.cs index 30a25a8698..824d1afca6 100644 --- a/csharp/src/Google.Protobuf/MessageParser.cs +++ b/csharp/src/Google.Protobuf/MessageParser.cs @@ -134,10 +134,10 @@ namespace Google.Protobuf /// The data to parse. /// The parsed message. [SecuritySafeCritical] - public IMessage ParseFrom(ReadOnlySpan data) + public IMessage ParseFrom(ref ReadOnlySpan data) { IMessage message = factory(); - message.MergeFrom(data, DiscardUnknownFields, Extensions); + message.MergeFrom(ref data, DiscardUnknownFields, Extensions); return message; } @@ -334,10 +334,10 @@ namespace Google.Protobuf /// The data to parse. /// The parsed message. [SecuritySafeCritical] - public new T ParseFrom(ReadOnlySpan data) + public new T ParseFrom(ref ReadOnlySpan data) { T message = factory(); - message.MergeFrom(data, DiscardUnknownFields, Extensions); + message.MergeFrom(ref data, DiscardUnknownFields, Extensions); return message; } diff --git a/csharp/src/Google.Protobuf/ParseContext.cs b/csharp/src/Google.Protobuf/ParseContext.cs index 4d185d9fd6..5bee01b339 100644 --- a/csharp/src/Google.Protobuf/ParseContext.cs +++ b/csharp/src/Google.Protobuf/ParseContext.cs @@ -65,6 +65,29 @@ namespace Google.Protobuf ctx.state = state; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void Initialize(ref ReadOnlySpan input, out ParseContext ctx) + { + Initialize(ref input, DefaultRecursionLimit, out ctx); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void Initialize(ref ReadOnlySpan input, int recursionLimit, out ParseContext ctx) + { + ctx.buffer = input; + ctx.state = default; + ctx.state.lastTag = 0; + ctx.state.recursionDepth = 0; + ctx.state.sizeLimit = DefaultSizeLimit; + ctx.state.recursionLimit = recursionLimit; + ctx.state.currentLimit = int.MaxValue; + ctx.state.bufferPos = 0; + ctx.state.bufferSize = input.Length; + + ctx.state.DiscardUnknownFields = false; + ctx.state.ExtensionRegistry = null; + } + /// /// Creates a ParseContext instance from CodedInputStream. /// WARNING: internally this copies the CodedInputStream's state, so after done with the ParseContext, @@ -104,29 +127,6 @@ namespace Google.Protobuf ctx.state.ExtensionRegistry = null; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void Initialize(ReadOnlySpan input, out ParseContext ctx) - { - Initialize(input, DefaultRecursionLimit, out ctx); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void Initialize(ReadOnlySpan input, int recursionLimit, out ParseContext ctx) - { - ctx.buffer = input; - ctx.state = default; - ctx.state.lastTag = 0; - ctx.state.recursionDepth = 0; - ctx.state.sizeLimit = DefaultSizeLimit; - ctx.state.recursionLimit = recursionLimit; - ctx.state.currentLimit = int.MaxValue; - ctx.state.bufferPos = 0; - ctx.state.bufferSize = input.Length; - - ctx.state.DiscardUnknownFields = false; - ctx.state.ExtensionRegistry = null; - } - /// /// Returns the last tag read, or 0 if no tags have been read or we've read beyond /// the end of the input.