Browse Source

make everything build

Jan Tattermusch 5 years ago
parent
commit
ea605381e6

+ 1 - 1
csharp/src/Google.Protobuf.Test.TestProtos/Google.Protobuf.Test.TestProtos.csproj

@@ -6,7 +6,7 @@
     and without the internal visibility from the test project (all of which have caused issues in the past).
   -->
   <PropertyGroup>
-    <TargetFrameworks>net45;netstandard1.0;netstandard2.0</TargetFrameworks>
+    <TargetFrameworks>net45;netstandard1.1;netstandard2.0</TargetFrameworks>
     <LangVersion>3.0</LangVersion>
     <AssemblyOriginatorKeyFile>../../keys/Google.Protobuf.snk</AssemblyOriginatorKeyFile>
     <SignAssembly>true</SignAssembly>

+ 62 - 62
csharp/src/Google.Protobuf.Test/CodedInputStreamTest.cs

@@ -202,29 +202,29 @@ namespace Google.Protobuf
         [Test]
         public void DecodeZigZag32()
         {
-            Assert.AreEqual(0, CodedInputStream.DecodeZigZag32(0));
-            Assert.AreEqual(-1, CodedInputStream.DecodeZigZag32(1));
-            Assert.AreEqual(1, CodedInputStream.DecodeZigZag32(2));
-            Assert.AreEqual(-2, CodedInputStream.DecodeZigZag32(3));
-            Assert.AreEqual(0x3FFFFFFF, CodedInputStream.DecodeZigZag32(0x7FFFFFFE));
-            Assert.AreEqual(unchecked((int) 0xC0000000), CodedInputStream.DecodeZigZag32(0x7FFFFFFF));
-            Assert.AreEqual(0x7FFFFFFF, CodedInputStream.DecodeZigZag32(0xFFFFFFFE));
-            Assert.AreEqual(unchecked((int) 0x80000000), CodedInputStream.DecodeZigZag32(0xFFFFFFFF));
+            Assert.AreEqual(0, ParsingPrimitives.DecodeZigZag32(0));
+            Assert.AreEqual(-1, ParsingPrimitives.DecodeZigZag32(1));
+            Assert.AreEqual(1, ParsingPrimitives.DecodeZigZag32(2));
+            Assert.AreEqual(-2, ParsingPrimitives.DecodeZigZag32(3));
+            Assert.AreEqual(0x3FFFFFFF, ParsingPrimitives.DecodeZigZag32(0x7FFFFFFE));
+            Assert.AreEqual(unchecked((int) 0xC0000000), ParsingPrimitives.DecodeZigZag32(0x7FFFFFFF));
+            Assert.AreEqual(0x7FFFFFFF, ParsingPrimitives.DecodeZigZag32(0xFFFFFFFE));
+            Assert.AreEqual(unchecked((int) 0x80000000), ParsingPrimitives.DecodeZigZag32(0xFFFFFFFF));
         }
 
         [Test]
         public void DecodeZigZag64()
         {
-            Assert.AreEqual(0, CodedInputStream.DecodeZigZag64(0));
-            Assert.AreEqual(-1, CodedInputStream.DecodeZigZag64(1));
-            Assert.AreEqual(1, CodedInputStream.DecodeZigZag64(2));
-            Assert.AreEqual(-2, CodedInputStream.DecodeZigZag64(3));
-            Assert.AreEqual(0x000000003FFFFFFFL, CodedInputStream.DecodeZigZag64(0x000000007FFFFFFEL));
-            Assert.AreEqual(unchecked((long) 0xFFFFFFFFC0000000L), CodedInputStream.DecodeZigZag64(0x000000007FFFFFFFL));
-            Assert.AreEqual(0x000000007FFFFFFFL, CodedInputStream.DecodeZigZag64(0x00000000FFFFFFFEL));
-            Assert.AreEqual(unchecked((long) 0xFFFFFFFF80000000L), CodedInputStream.DecodeZigZag64(0x00000000FFFFFFFFL));
-            Assert.AreEqual(0x7FFFFFFFFFFFFFFFL, CodedInputStream.DecodeZigZag64(0xFFFFFFFFFFFFFFFEL));
-            Assert.AreEqual(unchecked((long) 0x8000000000000000L), CodedInputStream.DecodeZigZag64(0xFFFFFFFFFFFFFFFFL));
+            Assert.AreEqual(0, ParsingPrimitives.DecodeZigZag64(0));
+            Assert.AreEqual(-1, ParsingPrimitives.DecodeZigZag64(1));
+            Assert.AreEqual(1, ParsingPrimitives.DecodeZigZag64(2));
+            Assert.AreEqual(-2, ParsingPrimitives.DecodeZigZag64(3));
+            Assert.AreEqual(0x000000003FFFFFFFL, ParsingPrimitives.DecodeZigZag64(0x000000007FFFFFFEL));
+            Assert.AreEqual(unchecked((long) 0xFFFFFFFFC0000000L), ParsingPrimitives.DecodeZigZag64(0x000000007FFFFFFFL));
+            Assert.AreEqual(0x000000007FFFFFFFL, ParsingPrimitives.DecodeZigZag64(0x00000000FFFFFFFEL));
+            Assert.AreEqual(unchecked((long) 0xFFFFFFFF80000000L), ParsingPrimitives.DecodeZigZag64(0x00000000FFFFFFFFL));
+            Assert.AreEqual(0x7FFFFFFFFFFFFFFFL, ParsingPrimitives.DecodeZigZag64(0xFFFFFFFFFFFFFFFEL));
+            Assert.AreEqual(unchecked((long) 0x8000000000000000L), ParsingPrimitives.DecodeZigZag64(0xFFFFFFFFFFFFFFFFL));
         }
         
         [Test]
@@ -339,64 +339,64 @@ namespace Google.Protobuf
             Assert.Throws<InvalidProtocolBufferException>(() => TestRecursiveMessage.Parser.ParseFrom(input));
         }
         
-        private static byte[] MakeMaliciousRecursionUnknownFieldsPayload(int recursionDepth)
-        {
-            // generate recursively nested groups that will be parsed as unknown fields
-            int unknownFieldNumber = 14;  // an unused field number
-            MemoryStream ms = new MemoryStream();
-            CodedOutputStream output = new CodedOutputStream(ms);
-            for (int i = 0; i < recursionDepth; i++)
-            {
-                output.WriteTag(WireFormat.MakeTag(unknownFieldNumber, WireFormat.WireType.StartGroup));
-            }
-            for (int i = 0; i < recursionDepth; i++)
-            {
-                output.WriteTag(WireFormat.MakeTag(unknownFieldNumber, WireFormat.WireType.EndGroup));
-            }
-            output.Flush();
-            return ms.ToArray();
+        private static byte[] MakeMaliciousRecursionUnknownFieldsPayload(int recursionDepth)
+        {
+            // generate recursively nested groups that will be parsed as unknown fields
+            int unknownFieldNumber = 14;  // an unused field number
+            MemoryStream ms = new MemoryStream();
+            CodedOutputStream output = new CodedOutputStream(ms);
+            for (int i = 0; i < recursionDepth; i++)
+            {
+                output.WriteTag(WireFormat.MakeTag(unknownFieldNumber, WireFormat.WireType.StartGroup));
+            }
+            for (int i = 0; i < recursionDepth; i++)
+            {
+                output.WriteTag(WireFormat.MakeTag(unknownFieldNumber, WireFormat.WireType.EndGroup));
+            }
+            output.Flush();
+            return ms.ToArray();
         }
 
         [Test]
         public void MaliciousRecursion_UnknownFields()
         {
-            byte[] payloadAtRecursiveLimit = MakeMaliciousRecursionUnknownFieldsPayload(CodedInputStream.DefaultRecursionLimit);
-            byte[] payloadBeyondRecursiveLimit = MakeMaliciousRecursionUnknownFieldsPayload(CodedInputStream.DefaultRecursionLimit + 1);
-            
-            Assert.DoesNotThrow(() => TestRecursiveMessage.Parser.ParseFrom(payloadAtRecursiveLimit));
+            byte[] payloadAtRecursiveLimit = MakeMaliciousRecursionUnknownFieldsPayload(CodedInputStream.DefaultRecursionLimit);
+            byte[] payloadBeyondRecursiveLimit = MakeMaliciousRecursionUnknownFieldsPayload(CodedInputStream.DefaultRecursionLimit + 1);
+            
+            Assert.DoesNotThrow(() => TestRecursiveMessage.Parser.ParseFrom(payloadAtRecursiveLimit));
             Assert.Throws<InvalidProtocolBufferException>(() => TestRecursiveMessage.Parser.ParseFrom(payloadBeyondRecursiveLimit));
         }
 
         [Test]
         public void ReadGroup_WrongEndGroupTag()
-        {
-            int groupFieldNumber = Proto2.TestAllTypes.OptionalGroupFieldNumber;
-
-            // write Proto2.TestAllTypes with "optional_group" set, but use wrong EndGroup closing tag
-            MemoryStream ms = new MemoryStream();
-            CodedOutputStream output = new CodedOutputStream(ms);
-            output.WriteTag(WireFormat.MakeTag(groupFieldNumber, WireFormat.WireType.StartGroup));
-            output.WriteGroup(new Proto2.TestAllTypes.Types.OptionalGroup { A = 12345 });
-            // end group with different field number
-            output.WriteTag(WireFormat.MakeTag(groupFieldNumber + 1, WireFormat.WireType.EndGroup));
-            output.Flush();
-            var payload = ms.ToArray();
-
-            Assert.Throws<InvalidProtocolBufferException>(() => Proto2.TestAllTypes.Parser.ParseFrom(payload));
+        {
+            int groupFieldNumber = Proto2.TestAllTypes.OptionalGroupFieldNumber;
+
+            // write Proto2.TestAllTypes with "optional_group" set, but use wrong EndGroup closing tag
+            MemoryStream ms = new MemoryStream();
+            CodedOutputStream output = new CodedOutputStream(ms);
+            output.WriteTag(WireFormat.MakeTag(groupFieldNumber, WireFormat.WireType.StartGroup));
+            output.WriteGroup(new Proto2.TestAllTypes.Types.OptionalGroup { A = 12345 });
+            // end group with different field number
+            output.WriteTag(WireFormat.MakeTag(groupFieldNumber + 1, WireFormat.WireType.EndGroup));
+            output.Flush();
+            var payload = ms.ToArray();
+
+            Assert.Throws<InvalidProtocolBufferException>(() => Proto2.TestAllTypes.Parser.ParseFrom(payload));
         }
 
         [Test]
         public void ReadGroup_UnknownFields_WrongEndGroupTag()
-        {
-            MemoryStream ms = new MemoryStream();
-            CodedOutputStream output = new CodedOutputStream(ms);
-            output.WriteTag(WireFormat.MakeTag(14, WireFormat.WireType.StartGroup));
-            // end group with different field number
-            output.WriteTag(WireFormat.MakeTag(15, WireFormat.WireType.EndGroup));
-            output.Flush();
-            var payload = ms.ToArray();
-
-            Assert.Throws<InvalidProtocolBufferException>(() => TestRecursiveMessage.Parser.ParseFrom(payload));
+        {
+            MemoryStream ms = new MemoryStream();
+            CodedOutputStream output = new CodedOutputStream(ms);
+            output.WriteTag(WireFormat.MakeTag(14, WireFormat.WireType.StartGroup));
+            // end group with different field number
+            output.WriteTag(WireFormat.MakeTag(15, WireFormat.WireType.EndGroup));
+            output.Flush();
+            var payload = ms.ToArray();
+
+            Assert.Throws<InvalidProtocolBufferException>(() => TestRecursiveMessage.Parser.ParseFrom(payload));
         }
 
         [Test]

+ 12 - 12
csharp/src/Google.Protobuf.Test/CodedOutputStreamTest.cs

@@ -247,26 +247,26 @@ namespace Google.Protobuf
         {
             // Some easier-to-verify round-trip tests.  The inputs (other than 0, 1, -1)
             // were chosen semi-randomly via keyboard bashing.
-            Assert.AreEqual(0, CodedInputStream.DecodeZigZag32(CodedOutputStream.EncodeZigZag32(0)));
-            Assert.AreEqual(1, CodedInputStream.DecodeZigZag32(CodedOutputStream.EncodeZigZag32(1)));
-            Assert.AreEqual(-1, CodedInputStream.DecodeZigZag32(CodedOutputStream.EncodeZigZag32(-1)));
-            Assert.AreEqual(14927, CodedInputStream.DecodeZigZag32(CodedOutputStream.EncodeZigZag32(14927)));
-            Assert.AreEqual(-3612, CodedInputStream.DecodeZigZag32(CodedOutputStream.EncodeZigZag32(-3612)));
+            Assert.AreEqual(0, ParsingPrimitives.DecodeZigZag32(CodedOutputStream.EncodeZigZag32(0)));
+            Assert.AreEqual(1, ParsingPrimitives.DecodeZigZag32(CodedOutputStream.EncodeZigZag32(1)));
+            Assert.AreEqual(-1, ParsingPrimitives.DecodeZigZag32(CodedOutputStream.EncodeZigZag32(-1)));
+            Assert.AreEqual(14927, ParsingPrimitives.DecodeZigZag32(CodedOutputStream.EncodeZigZag32(14927)));
+            Assert.AreEqual(-3612, ParsingPrimitives.DecodeZigZag32(CodedOutputStream.EncodeZigZag32(-3612)));
         }
 
         [Test]
         public void RoundTripZigZag64()
         {
-            Assert.AreEqual(0, CodedInputStream.DecodeZigZag64(CodedOutputStream.EncodeZigZag64(0)));
-            Assert.AreEqual(1, CodedInputStream.DecodeZigZag64(CodedOutputStream.EncodeZigZag64(1)));
-            Assert.AreEqual(-1, CodedInputStream.DecodeZigZag64(CodedOutputStream.EncodeZigZag64(-1)));
-            Assert.AreEqual(14927, CodedInputStream.DecodeZigZag64(CodedOutputStream.EncodeZigZag64(14927)));
-            Assert.AreEqual(-3612, CodedInputStream.DecodeZigZag64(CodedOutputStream.EncodeZigZag64(-3612)));
+            Assert.AreEqual(0, ParsingPrimitives.DecodeZigZag64(CodedOutputStream.EncodeZigZag64(0)));
+            Assert.AreEqual(1, ParsingPrimitives.DecodeZigZag64(CodedOutputStream.EncodeZigZag64(1)));
+            Assert.AreEqual(-1, ParsingPrimitives.DecodeZigZag64(CodedOutputStream.EncodeZigZag64(-1)));
+            Assert.AreEqual(14927, ParsingPrimitives.DecodeZigZag64(CodedOutputStream.EncodeZigZag64(14927)));
+            Assert.AreEqual(-3612, ParsingPrimitives.DecodeZigZag64(CodedOutputStream.EncodeZigZag64(-3612)));
 
             Assert.AreEqual(856912304801416L,
-                            CodedInputStream.DecodeZigZag64(CodedOutputStream.EncodeZigZag64(856912304801416L)));
+                            ParsingPrimitives.DecodeZigZag64(CodedOutputStream.EncodeZigZag64(856912304801416L)));
             Assert.AreEqual(-75123905439571256L,
-                            CodedInputStream.DecodeZigZag64(CodedOutputStream.EncodeZigZag64(-75123905439571256L)));
+                            ParsingPrimitives.DecodeZigZag64(CodedOutputStream.EncodeZigZag64(-75123905439571256L)));
         }
 
         [Test]

+ 3 - 3
csharp/src/Google.Protobuf.Test/FieldCodecTest.cs

@@ -128,7 +128,7 @@ namespace Google.Protobuf
                 codedOutput.Flush();
                 stream.Position = 0;
                 var codedInput = new CodedInputStream(stream);
-                Assert.AreEqual(sampleValue, codec.ValueReader(codedInput));
+                Assert.AreEqual(sampleValue, codec.Read(codedInput));
                 Assert.IsTrue(codedInput.IsAtEnd);
             }
 
@@ -158,7 +158,7 @@ namespace Google.Protobuf
             {
                 // WriteTagAndValue ignores default values
                 var stream = new MemoryStream();
-                CodedOutputStream codedOutput;
+                CodedOutputStream codedOutput;
 #if !NET35
                 codedOutput = new CodedOutputStream(stream);
                 codec.WriteTagAndValue(codedOutput, codec.DefaultValue);
@@ -181,7 +181,7 @@ namespace Google.Protobuf
                     Assert.AreEqual(stream.Position, codec.ValueSizeCalculator(codec.DefaultValue));
                     stream.Position = 0;
                     var codedInput = new CodedInputStream(stream);
-                    Assert.AreEqual(codec.DefaultValue, codec.ValueReader(codedInput));
+                    Assert.AreEqual(codec.DefaultValue, codec.Read(codedInput));
                 }
             }
 

+ 2 - 2
csharp/src/Google.Protobuf/ByteString.cs

@@ -160,7 +160,7 @@ namespace Google.Protobuf
             int capacity = stream.CanSeek ? checked((int) (stream.Length - stream.Position)) : 0;
             var memoryStream = new MemoryStream(capacity);
             stream.CopyTo(memoryStream);
-#if NETSTANDARD1_0 || NETSTANDARD2_0
+#if NETSTANDARD1_1 || NETSTANDARD2_0
             byte[] bytes = memoryStream.ToArray();
 #else
             // Avoid an extra copy if we can.
@@ -186,7 +186,7 @@ namespace Google.Protobuf
             // We have to specify the buffer size here, as there's no overload accepting the cancellation token
             // alone. But it's documented to use 81920 by default if not specified.
             await stream.CopyToAsync(memoryStream, 81920, cancellationToken);
-#if NETSTANDARD1_0 || NETSTANDARD2_0
+#if NETSTANDARD1_1 || NETSTANDARD2_0
             byte[] bytes = memoryStream.ToArray();
 #else
             // Avoid an extra copy if we can.

+ 2 - 3
csharp/src/Google.Protobuf/CodedInputStream.cs

@@ -431,15 +431,14 @@ namespace Google.Protobuf
         public void ReadMessage(IMessage builder)
         {
             var span = new ReadOnlySpan<byte>(buffer);
-            var ctx = new CodedInputReader(ref span, ref state);
+            var ctx = new ParseContext(ref span, ref state);
             try
             {
                 ParsingPrimitivesMessages.ReadMessage(ref ctx, builder);
             }
             finally
             {
-                // store the state
-                state = ctx.state;
+                ctx.CopyStateTo(this);
             }
         }
 

+ 26 - 8
csharp/src/Google.Protobuf/Collections/RepeatedField.cs

@@ -94,23 +94,41 @@ namespace Google.Protobuf.Collections
         /// <param name="input">The input stream to read from.</param>
         /// <param name="codec">The codec to use in order to read each entry.</param>
         public void AddEntriesFrom(CodedInputStream input, FieldCodec<T> codec)
+        {
+            var ctx = new ParseContext(input);
+            try
+            {
+                AddEntriesFrom(ref ctx, codec);
+            }
+            finally
+            {
+                ctx.CopyStateTo(input);
+            }
+        }
+
+        /// <summary>
+        /// Adds the entries from the given parse context, decoding them with the specified codec.
+        /// </summary>
+        /// <param name="ctx">The input to read from.</param>
+        /// <param name="codec">The codec to use in order to read each entry.</param>
+        public void AddEntriesFrom(ref ParseContext ctx, FieldCodec<T> codec)
         {
             // TODO: Inline some of the Add code, so we can avoid checking the size on every
             // iteration.
-            uint tag = input.LastTag;
+            uint tag = ctx.state.lastTag;
             var reader = codec.ValueReader;
             // Non-nullable value types can be packed or not.
             if (FieldCodec<T>.IsPackedRepeatedField(tag))
             {
-                int length = input.ReadLength();
+                int length = ctx.ReadLength();
                 if (length > 0)
                 {
-                    int oldLimit = input.PushLimit(length);
-                    while (!input.ReachedLimit)
+                    int oldLimit = SegmentedBufferHelper.PushLimit(ref ctx.state, length);
+                    while (!SegmentedBufferHelper.IsReachedLimit(ref ctx.state))
                     {
-                        Add(reader(input));
+                        Add(reader(ref ctx));
                     }
-                    input.PopLimit(oldLimit);
+                    SegmentedBufferHelper.PopLimit(ref ctx.state, oldLimit);
                 }
                 // Empty packed field. Odd, but valid - just ignore.
             }
@@ -119,8 +137,8 @@ namespace Google.Protobuf.Collections
                 // Not packed... (possibly not packable)
                 do
                 {
-                    Add(reader(input));
-                } while (input.MaybeConsumeTag(tag));
+                    Add(reader(ref ctx));
+                } while (ParsingPrimitives.MaybeConsumeTag(ref ctx.buffer, ref ctx.state, tag));
             }
         }
 

+ 35 - 22
csharp/src/Google.Protobuf/ExtensionValue.cs

@@ -93,14 +93,27 @@ namespace Google.Protobuf
 
         public void MergeFrom(CodedInputStream input)
         {
-            codec.ValueMerger(input, ref field);
+            var ctx = new ParseContext(input);
+            try
+            {
+                codec.ValueMerger(ref ctx, ref field);
+            }
+            finally
+            {
+                ctx.CopyStateTo(input);
+            }
+        }
+
+        public void MergeFrom(ref ParseContext ctx)
+        {
+            codec.ValueMerger(ref ctx, ref field);
         }
 
         public void MergeFrom(IExtensionValue value)
         {
             if (value is ExtensionValue<T>)
             {
-                var extensionValue = value as ExtensionValue<T>;
+                var extensionValue = value as ExtensionValue<T>;
                 codec.FieldMerger(ref field, extensionValue.field);
             }
         }
@@ -124,13 +137,13 @@ namespace Google.Protobuf
 
         public bool IsInitialized()
         {
-            if (field is IMessage)
-            {
-                return (field as IMessage).IsInitialized();
+            if (field is IMessage)
+            {
+                return (field as IMessage).IsInitialized();
             }
-            else
-            {
-                return true;
+            else
+            {
+                return true;
             }
         }
     }
@@ -202,20 +215,20 @@ namespace Google.Protobuf
 
         public bool IsInitialized()
         {
-            for (int i = 0; i < field.Count; i++)
-            {
-                var element = field[i];
-                if (element is IMessage)
-                {
-                    if (!(element as IMessage).IsInitialized())
-                    {
-                        return false;
-                    }
-                }
-                else
-                {
-                    break;
-                }
+            for (int i = 0; i < field.Count; i++)
+            {
+                var element = field[i];
+                if (element is IMessage)
+                {
+                    if (!(element as IMessage).IsInitialized())
+                    {
+                        return false;
+                    }
+                }
+                else
+                {
+                    break;
+                }
             }
 
             return true;

+ 96 - 48
csharp/src/Google.Protobuf/FieldCodec.cs

@@ -218,7 +218,7 @@ namespace Google.Protobuf
         /// <returns>A codec for the given tag.</returns>
         public static FieldCodec<string> ForString(uint tag, string defaultValue)
         {
-            return new FieldCodec<string>(input => input.ReadString(), (output, value) => output.WriteString(value), CodedOutputStream.ComputeStringSize, tag, defaultValue);
+            return new FieldCodec<string>((ref ParseContext ctx) => ctx.ReadString(), (output, value) => output.WriteString(value), CodedOutputStream.ComputeStringSize, tag, defaultValue);
         }
 
         /// <summary>
@@ -229,7 +229,7 @@ namespace Google.Protobuf
         /// <returns>A codec for the given tag.</returns>
         public static FieldCodec<ByteString> ForBytes(uint tag, ByteString defaultValue)
         {
-            return new FieldCodec<ByteString>(input => input.ReadBytes(), (output, value) => output.WriteBytes(value), CodedOutputStream.ComputeBytesSize, tag, defaultValue);
+            return new FieldCodec<ByteString>((ref ParseContext ctx) => ctx.ReadBytes(), (output, value) => output.WriteBytes(value), CodedOutputStream.ComputeBytesSize, tag, defaultValue);
         }
 
         /// <summary>
@@ -240,7 +240,7 @@ namespace Google.Protobuf
         /// <returns>A codec for the given tag.</returns>
         public static FieldCodec<bool> ForBool(uint tag, bool defaultValue)
         {
-            return new FieldCodec<bool>(input => input.ReadBool(), (output, value) => output.WriteBool(value), CodedOutputStream.BoolSize, tag, defaultValue);
+            return new FieldCodec<bool>((ref ParseContext ctx) => ctx.ReadBool(), (output, value) => output.WriteBool(value), CodedOutputStream.BoolSize, tag, defaultValue);
         }
 
         /// <summary>
@@ -251,7 +251,7 @@ namespace Google.Protobuf
         /// <returns>A codec for the given tag.</returns>
         public static FieldCodec<int> ForInt32(uint tag, int defaultValue)
         {
-            return new FieldCodec<int>(input => input.ReadInt32(), (output, value) => output.WriteInt32(value), CodedOutputStream.ComputeInt32Size, tag, defaultValue);
+            return new FieldCodec<int>((ref ParseContext ctx) => ctx.ReadInt32(), (output, value) => output.WriteInt32(value), CodedOutputStream.ComputeInt32Size, tag, defaultValue);
         }
 
         /// <summary>
@@ -262,7 +262,7 @@ namespace Google.Protobuf
         /// <returns>A codec for the given tag.</returns>
         public static FieldCodec<int> ForSInt32(uint tag, int defaultValue)
         {
-            return new FieldCodec<int>(input => input.ReadSInt32(), (output, value) => output.WriteSInt32(value), CodedOutputStream.ComputeSInt32Size, tag, defaultValue);
+            return new FieldCodec<int>((ref ParseContext ctx) => ctx.ReadSInt32(), (output, value) => output.WriteSInt32(value), CodedOutputStream.ComputeSInt32Size, tag, defaultValue);
         }
 
         /// <summary>
@@ -273,7 +273,7 @@ namespace Google.Protobuf
         /// <returns>A codec for the given tag.</returns>
         public static FieldCodec<uint> ForFixed32(uint tag, uint defaultValue)
         {
-            return new FieldCodec<uint>(input => input.ReadFixed32(), (output, value) => output.WriteFixed32(value), 4, tag, defaultValue);
+            return new FieldCodec<uint>((ref ParseContext ctx) => ctx.ReadFixed32(), (output, value) => output.WriteFixed32(value), 4, tag, defaultValue);
         }
 
         /// <summary>
@@ -284,7 +284,7 @@ namespace Google.Protobuf
         /// <returns>A codec for the given tag.</returns>
         public static FieldCodec<int> ForSFixed32(uint tag, int defaultValue)
         {
-            return new FieldCodec<int>(input => input.ReadSFixed32(), (output, value) => output.WriteSFixed32(value), 4, tag, defaultValue);
+            return new FieldCodec<int>((ref ParseContext ctx) => ctx.ReadSFixed32(), (output, value) => output.WriteSFixed32(value), 4, tag, defaultValue);
         }
 
         /// <summary>
@@ -295,7 +295,7 @@ namespace Google.Protobuf
         /// <returns>A codec for the given tag.</returns>
         public static FieldCodec<uint> ForUInt32(uint tag, uint defaultValue)
         {
-            return new FieldCodec<uint>(input => input.ReadUInt32(), (output, value) => output.WriteUInt32(value), CodedOutputStream.ComputeUInt32Size, tag, defaultValue);
+            return new FieldCodec<uint>((ref ParseContext ctx) => ctx.ReadUInt32(), (output, value) => output.WriteUInt32(value), CodedOutputStream.ComputeUInt32Size, tag, defaultValue);
         }
 
         /// <summary>
@@ -306,7 +306,7 @@ namespace Google.Protobuf
         /// <returns>A codec for the given tag.</returns>
         public static FieldCodec<long> ForInt64(uint tag, long defaultValue)
         {
-            return new FieldCodec<long>(input => input.ReadInt64(), (output, value) => output.WriteInt64(value), CodedOutputStream.ComputeInt64Size, tag, defaultValue);
+            return new FieldCodec<long>((ref ParseContext ctx) => ctx.ReadInt64(), (output, value) => output.WriteInt64(value), CodedOutputStream.ComputeInt64Size, tag, defaultValue);
         }
 
         /// <summary>
@@ -317,7 +317,7 @@ namespace Google.Protobuf
         /// <returns>A codec for the given tag.</returns>
         public static FieldCodec<long> ForSInt64(uint tag, long defaultValue)
         {
-            return new FieldCodec<long>(input => input.ReadSInt64(), (output, value) => output.WriteSInt64(value), CodedOutputStream.ComputeSInt64Size, tag, defaultValue);
+            return new FieldCodec<long>((ref ParseContext ctx) => ctx.ReadSInt64(), (output, value) => output.WriteSInt64(value), CodedOutputStream.ComputeSInt64Size, tag, defaultValue);
         }
 
         /// <summary>
@@ -328,7 +328,7 @@ namespace Google.Protobuf
         /// <returns>A codec for the given tag.</returns>
         public static FieldCodec<ulong> ForFixed64(uint tag, ulong defaultValue)
         {
-            return new FieldCodec<ulong>(input => input.ReadFixed64(), (output, value) => output.WriteFixed64(value), 8, tag, defaultValue);
+            return new FieldCodec<ulong>((ref ParseContext ctx) => ctx.ReadFixed64(), (output, value) => output.WriteFixed64(value), 8, tag, defaultValue);
         }
 
         /// <summary>
@@ -339,7 +339,7 @@ namespace Google.Protobuf
         /// <returns>A codec for the given tag.</returns>
         public static FieldCodec<long> ForSFixed64(uint tag, long defaultValue)
         {
-            return new FieldCodec<long>(input => input.ReadSFixed64(), (output, value) => output.WriteSFixed64(value), 8, tag, defaultValue);
+            return new FieldCodec<long>((ref ParseContext ctx) => ctx.ReadSFixed64(), (output, value) => output.WriteSFixed64(value), 8, tag, defaultValue);
         }
 
         /// <summary>
@@ -350,7 +350,7 @@ namespace Google.Protobuf
         /// <returns>A codec for the given tag.</returns>
         public static FieldCodec<ulong> ForUInt64(uint tag, ulong defaultValue)
         {
-            return new FieldCodec<ulong>(input => input.ReadUInt64(), (output, value) => output.WriteUInt64(value), CodedOutputStream.ComputeUInt64Size, tag, defaultValue);
+            return new FieldCodec<ulong>((ref ParseContext ctx) => ctx.ReadUInt64(), (output, value) => output.WriteUInt64(value), CodedOutputStream.ComputeUInt64Size, tag, defaultValue);
         }
 
         /// <summary>
@@ -361,7 +361,7 @@ namespace Google.Protobuf
         /// <returns>A codec for the given tag.</returns>
         public static FieldCodec<float> ForFloat(uint tag, float defaultValue)
         {
-            return new FieldCodec<float>(input => input.ReadFloat(), (output, value) => output.WriteFloat(value), CodedOutputStream.FloatSize, tag, defaultValue);
+            return new FieldCodec<float>((ref ParseContext ctx) => ctx.ReadFloat(), (output, value) => output.WriteFloat(value), CodedOutputStream.FloatSize, tag, defaultValue);
         }
 
         /// <summary>
@@ -372,7 +372,7 @@ namespace Google.Protobuf
         /// <returns>A codec for the given tag.</returns>
         public static FieldCodec<double> ForDouble(uint tag, double defaultValue)
         {
-            return new FieldCodec<double>(input => input.ReadDouble(), (output, value) => output.WriteDouble(value), CodedOutputStream.DoubleSize, tag, defaultValue);
+            return new FieldCodec<double>((ref ParseContext ctx) => ctx.ReadDouble(), (output, value) => output.WriteDouble(value), CodedOutputStream.DoubleSize, tag, defaultValue);
         }
 
         // Enums are tricky. We can probably use expression trees to build these delegates automatically,
@@ -388,8 +388,8 @@ namespace Google.Protobuf
         /// <returns>A codec for the given tag.</returns>
         public static FieldCodec<T> ForEnum<T>(uint tag, Func<T, int> toInt32, Func<int, T> fromInt32, T defaultValue)
         {
-            return new FieldCodec<T>(input => fromInt32(
-                input.ReadEnum()),
+            return new FieldCodec<T>((ref ParseContext ctx) => fromInt32(
+                ctx.ReadEnum()),
                 (output, value) => output.WriteEnum(toInt32(value)),
                 value => CodedOutputStream.ComputeEnumSize(toInt32(value)), tag, defaultValue);
         }
@@ -403,21 +403,21 @@ namespace Google.Protobuf
         public static FieldCodec<T> ForMessage<T>(uint tag, MessageParser<T> parser) where T : class, IMessage<T>
         {
             return new FieldCodec<T>(
-                input => 
+                (ref ParseContext ctx) => 
                 { 
                     T message = parser.CreateTemplate(); 
-                    input.ReadMessage(message); 
+                    ctx.ReadMessage(message); 
                     return message; 
                 },
                 (output, value) => output.WriteMessage(value),
-                (CodedInputStream i, ref T v) => 
+                (ref ParseContext ctx, ref T v) => 
                 {
                     if (v == null)
                     {
                         v = parser.CreateTemplate();
                     }
 
-                    i.ReadMessage(v);
+                    ctx.ReadMessage(v);
                 },
                 (ref T v, T v2) =>
                 {
@@ -448,21 +448,21 @@ namespace Google.Protobuf
         public static FieldCodec<T> ForGroup<T>(uint startTag, uint endTag, MessageParser<T> parser) where T : class, IMessage<T>
         {
             return new FieldCodec<T>(
-                input => 
+                (ref ParseContext ctx) => 
                 { 
                     T message = parser.CreateTemplate();
-                    input.ReadGroup(message);
+                    ctx.ReadGroup(message);
                     return message;
                 },
                 (output, value) => output.WriteGroup(value), 
-                (CodedInputStream i, ref T v) => 
+                (ref ParseContext ctx, ref T v) => 
                 {
                     if (v == null)
                     {
                         v = parser.CreateTemplate();
                     }
 
-                    i.ReadGroup(v);
+                    ctx.ReadGroup(v);
                 },
                 (ref T v, T v2) =>
                 {
@@ -490,9 +490,9 @@ namespace Google.Protobuf
         {
             var nestedCodec = WrapperCodecs.GetCodec<T>();
             return new FieldCodec<T>(
-                input => WrapperCodecs.Read<T>(input, nestedCodec),
+                (ref ParseContext ctx) => WrapperCodecs.Read<T>(ref ctx, nestedCodec),
                 (output, value) => WrapperCodecs.Write<T>(output, value, nestedCodec),
-                (CodedInputStream i, ref T v) => v = WrapperCodecs.Read<T>(i, nestedCodec),
+                (ref ParseContext ctx, ref T v) => v = WrapperCodecs.Read<T>(ref ctx, nestedCodec),
                 (ref T v, T v2) => { v = v2; return v == null; },
                 value => WrapperCodecs.CalculateSize<T>(value, nestedCodec),
                 tag, 0,
@@ -509,7 +509,7 @@ namespace Google.Protobuf
             return new FieldCodec<T?>(
                 WrapperCodecs.GetReader<T>(),
                 (output, value) => WrapperCodecs.Write<T>(output, value.Value, nestedCodec),
-                (CodedInputStream i, ref T? v) => v = WrapperCodecs.Read<T>(i, nestedCodec),
+                (ref ParseContext ctx, ref T? v) => v = WrapperCodecs.Read<T>(ref ctx, nestedCodec),
                 (ref T? v, T? v2) => { if (v2.HasValue) { v = v2; } return v.HasValue; },
                 value => value == null ? 0 : WrapperCodecs.CalculateSize<T>(value.Value, nestedCodec),
                 tag, 0,
@@ -542,17 +542,17 @@ namespace Google.Protobuf
             private static readonly Dictionary<System.Type, object> Readers = new Dictionary<System.Type, object>
             {
                 // TODO: Provide more optimized readers.
-                { typeof(bool), (Func<CodedInputStream, bool?>)CodedInputStream.ReadBoolWrapper },
-                { typeof(int), (Func<CodedInputStream, int?>)CodedInputStream.ReadInt32Wrapper },
-                { typeof(long), (Func<CodedInputStream, long?>)CodedInputStream.ReadInt64Wrapper },
-                { typeof(uint), (Func<CodedInputStream, uint?>)CodedInputStream.ReadUInt32Wrapper },
-                { typeof(ulong), (Func<CodedInputStream, ulong?>)CodedInputStream.ReadUInt64Wrapper },
+                { typeof(bool), (ValueReader<bool?>)ParsingPrimitivesWrappers.ReadBoolWrapper },
+                { typeof(int), (ValueReader<int?>)ParsingPrimitivesWrappers.ReadInt32Wrapper },
+                { typeof(long), (ValueReader<long?>)ParsingPrimitivesWrappers.ReadInt64Wrapper },
+                { typeof(uint), (ValueReader<uint?>)ParsingPrimitivesWrappers.ReadUInt32Wrapper },
+                { typeof(ulong), (ValueReader<ulong?>)ParsingPrimitivesWrappers.ReadUInt64Wrapper },
                 { typeof(float), BitConverter.IsLittleEndian ?
-                    (Func<CodedInputStream, float?>)CodedInputStream.ReadFloatWrapperLittleEndian :
-                    (Func<CodedInputStream, float?>)CodedInputStream.ReadFloatWrapperSlow },
+                    (ValueReader<float?>)ParsingPrimitivesWrappers.ReadFloatWrapperLittleEndian :
+                    (ValueReader<float?>)ParsingPrimitivesWrappers.ReadFloatWrapperSlow },
                 { typeof(double), BitConverter.IsLittleEndian ?
-                    (Func<CodedInputStream, double?>)CodedInputStream.ReadDoubleWrapperLittleEndian :
-                    (Func<CodedInputStream, double?>)CodedInputStream.ReadDoubleWrapperSlow },
+                    (ValueReader<double?>)ParsingPrimitivesWrappers.ReadDoubleWrapperLittleEndian :
+                    (ValueReader<double?>)ParsingPrimitivesWrappers.ReadDoubleWrapperSlow },
                 // `string` and `ByteString` less performance-sensitive. Do not implement for now.
                 { typeof(string), null },
                 { typeof(ByteString), null },
@@ -572,7 +572,7 @@ namespace Google.Protobuf
                 return (FieldCodec<T>) value;
             }
 
-            internal static Func<CodedInputStream, T?> GetReader<T>() where T : struct
+            internal static ValueReader<T?> GetReader<T>() where T : struct
             {
                 object value;
                 if (!Readers.TryGetValue(typeof(T), out value))
@@ -583,10 +583,10 @@ namespace Google.Protobuf
                 {
                     // Return default unoptimized reader for the wrapper type.
                     var nestedCoded = GetCodec<T>();
-                    return input => Read<T>(input, nestedCoded);
+                    return (ref ParseContext ctx) => Read<T>(ref ctx, nestedCoded);
                 }
                 // Return optimized read for the wrapper type.
-                return (Func<CodedInputStream, T?>)value;
+                return (ValueReader<T?>)value;
             }
 
             internal static T Read<T>(CodedInputStream input, FieldCodec<T> codec)
@@ -614,6 +614,31 @@ namespace Google.Protobuf
                 return value;
             }
 
+            internal static T Read<T>(ref ParseContext ctx, FieldCodec<T> codec)
+            {
+                int length = ctx.ReadLength();
+                int oldLimit = SegmentedBufferHelper.PushLimit(ref ctx.state, length);
+
+                uint tag;
+                T value = codec.DefaultValue;
+                while ((tag = ctx.ReadTag()) != 0)
+                {
+                    if (tag == codec.Tag)
+                    {
+                        value = codec.Read(ref ctx);
+                    }
+                    else
+                    {
+                        ParsingPrimitivesMessages.SkipLastField(ref ctx.buffer, ref ctx.state);
+                    }
+
+                }
+                ParsingPrimitivesMessages.CheckReadEndOfStreamTag(ref ctx.state);
+                SegmentedBufferHelper.PopLimit(ref ctx.state, oldLimit);
+
+                return value;
+            }
+
             internal static void Write<T>(CodedOutputStream output, T value, FieldCodec<T> codec)
             {
                 output.WriteLength(codec.CalculateSizeWithTag(value));
@@ -628,6 +653,8 @@ namespace Google.Protobuf
         }
     }
 
+    internal delegate TValue ValueReader<out TValue>(ref ParseContext ctx);
+
     /// <summary>
     /// <para>
     /// An encode/decode pair for a single field. This effectively encapsulates
@@ -653,7 +680,7 @@ namespace Google.Protobuf
         /// <summary>
         /// Merges an input stream into a value
         /// </summary>
-        internal delegate void InputMerger(CodedInputStream input, ref T value);
+        internal delegate void InputMerger(ref ParseContext ctx, ref T value);
 
         /// <summary>
         /// Merges a value into a reference to another value, returning a boolean if the value was set
@@ -692,7 +719,7 @@ namespace Google.Protobuf
         /// Returns a delegate to read a value from a coded input stream. It is assumed that
         /// the stream is already positioned on the appropriate tag.
         /// </summary>
-        internal Func<CodedInputStream, T> ValueReader { get; }
+        internal ValueReader<T> ValueReader { get; }
 
         /// <summary>
         /// Returns a delegate to merge a value from a coded input stream.
@@ -739,7 +766,7 @@ namespace Google.Protobuf
         private readonly int tagSize;
 
         internal FieldCodec(
-                Func<CodedInputStream, T> reader,
+                ValueReader<T> reader,
                 Action<CodedOutputStream, T> writer,
                 int fixedSize,
                 uint tag,
@@ -749,16 +776,16 @@ namespace Google.Protobuf
         }
 
         internal FieldCodec(
-            Func<CodedInputStream, T> reader,
+            ValueReader<T> reader,
             Action<CodedOutputStream, T> writer,
             Func<T, int> sizeCalculator,
             uint tag,
-            T defaultValue) : this(reader, writer, (CodedInputStream i, ref T v) => v = reader(i), (ref T v, T v2) => { v = v2; return true; }, sizeCalculator, tag, 0, defaultValue)
+            T defaultValue) : this(reader, writer, (ref ParseContext ctx, ref T v) => v = reader(ref ctx), (ref T v, T v2) => { v = v2; return true; }, sizeCalculator, tag, 0, defaultValue)
         {
         }
 
         internal FieldCodec(
-            Func<CodedInputStream, T> reader,
+            ValueReader<T> reader,
             Action<CodedOutputStream, T> writer,
             InputMerger inputMerger,
             ValuesMerger valuesMerger,
@@ -769,7 +796,7 @@ namespace Google.Protobuf
         }
 
         internal FieldCodec(
-            Func<CodedInputStream, T> reader,
+            ValueReader<T> reader,
             Action<CodedOutputStream, T> writer,
             InputMerger inputMerger,
             ValuesMerger valuesMerger,
@@ -815,7 +842,28 @@ namespace Google.Protobuf
         /// </summary>
         /// <param name="input">The input stream to read from.</param>
         /// <returns>The value read from the stream.</returns>
-        public T Read(CodedInputStream input) => ValueReader(input);
+        public T Read(CodedInputStream input)
+        {
+            var ctx = new ParseContext(input);
+            try
+            {
+                return ValueReader(ref ctx);
+            }
+            finally
+            {
+                ctx.CopyStateTo(input);
+            }
+        }
+
+        /// <summary>
+        /// Reads a value of the codec type from the given <see cref="ParseContext"/>.
+        /// </summary>
+        /// <param name="ctx">The parse context to read from.</param>
+        /// <returns>The value read.</returns>
+        public T Read(ref ParseContext ctx)
+        {
+            return ValueReader(ref ctx);
+        }
 
         /// <summary>
         /// Calculates the size required to write the given value, with a tag,

+ 14 - 12
csharp/src/Google.Protobuf/Google.Protobuf.csproj

@@ -1,13 +1,14 @@
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
     <Description>C# runtime library for Protocol Buffers - Google's data interchange format.</Description>
     <Copyright>Copyright 2015, Google Inc.</Copyright>
     <AssemblyTitle>Google Protocol Buffers</AssemblyTitle>
     <VersionPrefix>3.11.4</VersionPrefix>
-    <LangVersion>6</LangVersion>
+    <!-- C# 7.2 is required for Span/BufferWriter/ReadOnlySequence -->
+    <LangVersion>7.2</LangVersion>
     <Authors>Google Inc.</Authors>
-    <TargetFrameworks>netstandard1.0;netstandard2.0;net45</TargetFrameworks>
+    <TargetFrameworks>netstandard1.1;netstandard2.0;net45</TargetFrameworks>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <AssemblyOriginatorKeyFile>../../keys/Google.Protobuf.snk</AssemblyOriginatorKeyFile>
     <SignAssembly>true</SignAssembly>
@@ -18,25 +19,26 @@
     <PackageLicenseUrl>https://github.com/protocolbuffers/protobuf/blob/master/LICENSE</PackageLicenseUrl>
     <RepositoryType>git</RepositoryType>
     <RepositoryUrl>https://github.com/protocolbuffers/protobuf.git</RepositoryUrl>
+    <DefineConstants>$(DefineConstants);GOOGLE_PROTOBUF_SUPPORT_SYSTEM_MEMORY</DefineConstants>
+    <AllowUnsafeBlocks>True</AllowUnsafeBlocks>
     <!-- Include PDB in the built .nupkg -->
     <AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
   </PropertyGroup>
 
-  <PropertyGroup Condition=" '$(TargetFramework)' == 'net45' or '$(TargetFramework)' == 'netstandard2.0' ">
-    <DefineConstants>$(DefineConstants);GOOGLE_PROTOBUF_SUPPORT_SYSTEM_MEMORY</DefineConstants>
+  <PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
+    <DefineConstants>$(DefineConstants);GOOGLE_PROTOBUF_SUPPORT_FAST_STRING</DefineConstants>
   </PropertyGroup>
 
-  <!-- Needed for the net45 build to work on Unix. See https://github.com/dotnet/designs/pull/33 -->
   <ItemGroup>
-    <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" PrivateAssets="All" Version="1.0.0-preview.2"/>
-  </ItemGroup>
-
-  <ItemGroup Condition=" '$(TargetFramework)' == 'net45' or '$(TargetFramework)' == 'netstandard2.0' ">
     <PackageReference Include="System.Memory" Version="4.5.2"/>
+    <PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" Version="1.0.0-beta2-18618-05"/>
+    <!-- Needed for the net45 build to work on Unix. See https://github.com/dotnet/designs/pull/33 -->
+    <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" PrivateAssets="All" Version="1.0.0"/>
   </ItemGroup>
 
-  <ItemGroup>
-    <PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" Version="1.0.0-beta2-18618-05"/>
+  <!-- Needed for netcoreapp2.1 to work correctly. .NET is not able to load the assembly without this -->
+  <ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
+    <PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.5.2"/>
   </ItemGroup>
 
 </Project>

+ 1 - 15
csharp/src/Google.Protobuf/IBufferMessage.cs

@@ -35,29 +35,15 @@ namespace Google.Protobuf
 #if GOOGLE_PROTOBUF_SUPPORT_SYSTEM_MEMORY
     /// <summary>
     /// Interface for a Protocol Buffers message, supporting
-    /// <see cref="CodedInputReader"/> and <see cref="CodedOutputWriter"/>
-    /// serialization operations.
+    /// parsing from <see cref="ParseContext"/>.
     /// </summary>
     public interface IBufferMessage : IMessage
     {
-        /// <summary>
-        /// Merges the data from the specified <see cref="CodedInputReader"/> with the current message.
-        /// </summary>
-        /// <remarks>See the user guide for precise merge semantics.</remarks>
-        /// <param name="input"><see cref="CodedInputReader"/> to read data from. Must not be null.</param>
-        void MergeFrom(ref CodedInputReader input);
-
         /// <summary>
         /// Internal implementation of merging data from given parse context into this message.
         /// Users should never invoke this method directly.
         /// </summary>        
         void MergeFrom_Internal(ref ParseContext ctx);
-
-        /// <summary>
-        /// Writes the data to the given <see cref="CodedOutputWriter"/>.
-        /// </summary>
-        /// <param name="output"><see cref="CodedOutputWriter"/> to write the data to. Must not be null.</param>
-        void WriteTo(ref CodedOutputWriter output);
     }
 #endif
 }

+ 5 - 0
csharp/src/Google.Protobuf/ParseContext.cs

@@ -318,5 +318,10 @@ namespace Google.Protobuf
         {
             input.InternalState = state;
         }
+
+        internal void LoadStateFrom(CodedInputStream input)
+        {
+            state = input.InternalState;
+        }
     }
 }

+ 41 - 45
csharp/src/Google.Protobuf/ParsingPrimitivesMessages.cs

@@ -110,28 +110,6 @@ namespace Google.Protobuf
             state.recursionDepth--;
         }
 
-        public static void ReadMessage(ref CodedInputReader ctx, IMessage message)
-        {
-            int length = ParsingPrimitives.ParseLength(ref ctx.buffer, ref ctx.state);
-            if (ctx.state.recursionDepth >= ctx.state.recursionLimit)
-            {
-                throw InvalidProtocolBufferException.RecursionLimitExceeded();
-            }
-            int oldLimit = SegmentedBufferHelper.PushLimit(ref ctx.state, length);
-            ++ctx.state.recursionDepth;
-
-            ReadRawMessage(ref ctx, message);
-
-            CheckReadEndOfStreamTag(ref ctx.state);
-            // Check that we've read exactly as much data as expected.
-            if (!SegmentedBufferHelper.IsReachedLimit(ref ctx.state))
-            {
-                throw InvalidProtocolBufferException.TruncatedMessage();
-            }
-            --ctx.state.recursionDepth;
-            SegmentedBufferHelper.PopLimit(ref ctx.state, oldLimit);
-        }
-
         public static void ReadMessage(ref ParseContext ctx, IMessage message)
         {
             int length = ParsingPrimitives.ParseLength(ref ctx.buffer, ref ctx.state);
@@ -154,7 +132,7 @@ namespace Google.Protobuf
             SegmentedBufferHelper.PopLimit(ref ctx.state, oldLimit);
         }
 
-        public static void ReadGroup(ref CodedInputReader ctx, IMessage message)
+        public static void ReadGroup(ref ParseContext ctx, IMessage message)
         {
             if (ctx.state.recursionDepth >= ctx.state.recursionLimit)
             {
@@ -162,55 +140,66 @@ namespace Google.Protobuf
             }
             ++ctx.state.recursionDepth;
             
+            uint tag = ctx.state.lastTag;
+            int fieldNumber = WireFormat.GetTagFieldNumber(tag);
             ReadRawMessage(ref ctx, message);
+            CheckLastTagWas(ref ctx.state, WireFormat.MakeTag(fieldNumber, WireFormat.WireType.EndGroup));
 
             --ctx.state.recursionDepth;
         }
 
-        public static void ReadGroup(ref ParseContext ctx, IMessage message)
+        public static void ReadGroup(ref ParseContext ctx, int fieldNumber, UnknownFieldSet set)
         {
             if (ctx.state.recursionDepth >= ctx.state.recursionLimit)
             {
                 throw InvalidProtocolBufferException.RecursionLimitExceeded();
             }
             ++ctx.state.recursionDepth;
-            
-            ReadRawMessage(ref ctx, message);
+
+            set.MergeGroupFrom(ref ctx);
+            CheckLastTagWas(ref ctx.state, WireFormat.MakeTag(fieldNumber, WireFormat.WireType.EndGroup));
 
             --ctx.state.recursionDepth;
         }
 
-        public static void ReadRawMessage(ref CodedInputReader ctx, IMessage message)
+        public static void ReadRawMessage(ref ParseContext ctx, IMessage message)
         {
             if (message is IBufferMessage bufferMessage)
             {
-                bufferMessage.MergeFrom(ref ctx);
+                bufferMessage.MergeFrom_Internal(ref ctx);   
             }
             else
             {
+                // If we reached here, it means we've ran into a nested message with older generated code
+                // which doesn't provide the MergeFrom_Internal method that takes a ParseContext.
+                // With a slight performance overhead, we can still parse this message just fine,
+                // but we need to find the original CodedInputStream instance that initiated this
+                // parsing process and make sure its internal state is up to date.
+                // Note that this performance overhead is not very high (basically copying contents of a struct)
+                // and it will only be incurred in case the application mixes older and newer generated code.
+                // Regenerating the code from .proto files will remove this overhead because it will
+                // generate the MergeFrom_Internal method we need.
+
                 if (ctx.state.codedInputStream == null)
                 {
-                    // TODO: improve the msg
-                    throw new InvalidProtocolBufferException("Cannot parse message with current parse context. Do you need to regenerate the code?");
+                    // This can only happen when the parsing started without providing a CodedInputStream instance
+                    // (e.g. ParseContext was created directly from a ReadOnlySequence).
+                    // That also means that one of the new parsing APIs was used at the top level
+                    // and in such case it is reasonable to require that all the nested message provide
+                    // up-to-date generated code with ParseContext support (and fail otherwise).
+                    throw new InvalidProtocolBufferException($"Message ${message.GetType()} doesn't provide the generated method that enables ParseContext-based parsing. You might need to regenerate the generated protobuf code.");
                 }
-                message.MergeFrom(ctx.state.codedInputStream);
-            }
-        }
 
-        public static void ReadRawMessage(ref ParseContext ctx, IMessage message)
-        {
-            if (message is IBufferMessage bufferMessage)
-            {
-                bufferMessage.MergeFrom_Internal(ref ctx);   
-            }
-            else
-            {
-                if (ctx.state.codedInputStream == null)
+                ctx.CopyStateTo(ctx.state.codedInputStream);
+                try
                 {
-                    // TODO: improve the msg
-                    throw new InvalidProtocolBufferException("Cannot parse message with current parse context. Do you need to regenerate the code?");
+                    // fallback parse using the CodedInputStream that started current parsing tree
+                    message.MergeFrom(ctx.state.codedInputStream);
+                }
+                finally
+                {
+                    ctx.LoadStateFrom(ctx.state.codedInputStream);
                 }
-                message.MergeFrom(ctx.state.codedInputStream);
             }
         }
 
@@ -227,5 +216,12 @@ namespace Google.Protobuf
                 throw InvalidProtocolBufferException.MoreDataAvailable();
             }
         }
+
+        private static void CheckLastTagWas(ref ParserInternalState state, uint expectedTag)
+        {
+            if (state.lastTag != expectedTag) {
+               throw InvalidProtocolBufferException.InvalidEndTag();
+            }
+        }
     }
 }

+ 43 - 19
csharp/src/Google.Protobuf/UnknownFieldSet.cs

@@ -176,47 +176,48 @@ namespace Google.Protobuf
             fields[number] = field;
             return this;
         }
-
+        
         /// <summary>
-        /// Parse a single field from <paramref name="input"/> and merge it
+        /// Parse a single field from <paramref name="ctx"/> and merge it
         /// into this set.
         /// </summary>
-        /// <param name="input">The coded input stream containing the field</param>
+        /// <param name="ctx">The parse context from which to read the field</param>
         /// <returns>false if the tag is an "end group" tag, true otherwise</returns>
-        private bool MergeFieldFrom(CodedInputStream input)
+        private bool MergeFieldFrom(ref ParseContext ctx)
         {
-            uint tag = input.LastTag;
+            // TODO: deduplicate MergeFieldFrom implementations
+            uint tag = ctx.LastTag;
             int number = WireFormat.GetTagFieldNumber(tag);
             switch (WireFormat.GetTagWireType(tag))
             {
                 case WireFormat.WireType.Varint:
                     {
-                        ulong uint64 = input.ReadUInt64();
+                        ulong uint64 = ctx.ReadUInt64();
                         GetOrAddField(number).AddVarint(uint64);
                         return true;
                     }
                 case WireFormat.WireType.Fixed32:
                     {
-                        uint uint32 = input.ReadFixed32();
+                        uint uint32 = ctx.ReadFixed32();
                         GetOrAddField(number).AddFixed32(uint32);
                         return true;
                     }
                 case WireFormat.WireType.Fixed64:
                     {
-                        ulong uint64 = input.ReadFixed64();
+                        ulong uint64 = ctx.ReadFixed64();
                         GetOrAddField(number).AddFixed64(uint64);
                         return true;
                     }
                 case WireFormat.WireType.LengthDelimited:
                     {
-                        ByteString bytes = input.ReadBytes();
+                        ByteString bytes = ctx.ReadBytes();
                         GetOrAddField(number).AddLengthDelimited(bytes);
                         return true;
                     }
                 case WireFormat.WireType.StartGroup:
                     {
                         UnknownFieldSet set = new UnknownFieldSet();
-                        input.ReadGroup(number, set);
+                        ParsingPrimitivesMessages.ReadGroup(ref ctx, number, set);
                         GetOrAddField(number).AddGroup(set);
                         return true;
                     }
@@ -229,16 +230,16 @@ namespace Google.Protobuf
             }
         }
 
-        internal void MergeGroupFrom(CodedInputStream input)
+        internal void MergeGroupFrom(ref ParseContext ctx)
         {
             while (true)
             {
-                uint tag = input.ReadTag();
+                uint tag = ctx.ReadTag();
                 if (tag == 0)
                 {
                     break;
                 }
-                if (!MergeFieldFrom(input))
+                if (!MergeFieldFrom(ref ctx))
                 {
                     break;
                 }
@@ -257,21 +258,44 @@ namespace Google.Protobuf
         public static UnknownFieldSet MergeFieldFrom(UnknownFieldSet unknownFields,
                                                      CodedInputStream input)
         {
-            if (input.DiscardUnknownFields)
+            var ctx = new ParseContext(input);
+            try
+            {
+                return MergeFieldFrom(unknownFields, ref ctx);
+            }
+            finally
             {
-                input.SkipLastField();
+                ctx.CopyStateTo(input);
+            }
+        }
+
+        /// <summary>
+        /// Create a new UnknownFieldSet if unknownFields is null.
+        /// Parse a single field from <paramref name="ctx"/> and merge it
+        /// into unknownFields. If <paramref name="ctx"/> is configured to discard unknown fields,
+        /// <paramref name="unknownFields"/> will be returned as-is and the field will be skipped.
+        /// </summary>
+        /// <param name="unknownFields">The UnknownFieldSet which need to be merged</param>
+        /// <param name="ctx">The parse context from which to read the field</param>
+        /// <returns>The merged UnknownFieldSet</returns>
+        public static UnknownFieldSet MergeFieldFrom(UnknownFieldSet unknownFields,
+                                                     ref ParseContext ctx)
+        {
+            if (ctx.DiscardUnknownFields)
+            {
+                ParsingPrimitivesMessages.SkipLastField(ref ctx.buffer, ref ctx.state);
                 return unknownFields;
             }
             if (unknownFields == null)
             {
                 unknownFields = new UnknownFieldSet();
             }
-            if (!unknownFields.MergeFieldFrom(input))
-            {
-                throw new InvalidProtocolBufferException("Merge an unknown field of end-group tag, indicating that the corresponding start-group was missing."); // match the old code-gen
+            if (!unknownFields.MergeFieldFrom(ref ctx))
+            {
+                throw new InvalidProtocolBufferException("Merge an unknown field of end-group tag, indicating that the corresponding start-group was missing."); // match the old code-gen
             }
             return unknownFields;
-        }
+        }
 
         /// <summary>
         /// Merges the fields from <paramref name="other"/> into this set.