Add method for retrieving extensions from unknown fields (#256)

diff --git a/protobuf/CHANGELOG.md b/protobuf/CHANGELOG.md
index a151f52..bff643b 100644
--- a/protobuf/CHANGELOG.md
+++ b/protobuf/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 0.13.13
+
+* `Added `ExtensionRegistry.reparseMessage()` for decoding extensions from unknown fields after the initial
+   decoding.
+
 ## 0.13.12
 
 * `BuilderInfo.add` now ignores fields with tag number 0.
diff --git a/protobuf/lib/src/protobuf/extension_registry.dart b/protobuf/lib/src/protobuf/extension_registry.dart
index 243d609..0892c2d 100644
--- a/protobuf/lib/src/protobuf/extension_registry.dart
+++ b/protobuf/lib/src/protobuf/extension_registry.dart
@@ -33,13 +33,91 @@
     }
     return null;
   }
+
+  /// Returns a shallow copy of [message], with all extensions in [this] parsed
+  /// from the unknown fields of [message].
+  ///
+  /// Extensions already present in [message] will be preserved.
+  ///
+  /// If [message] is frozen, the result will be as well.
+  ///
+  /// Throws an [InvalidProtocolBufferException] if the parsed extensions are
+  /// malformed.
+  ///
+  /// Using this method to retrieve extensions is more expensive overall than
+  /// using an [ExtensionRegistry] with all the needed extensions when doing
+  /// [GeneratedMessage.fromBuffer].
+  ///
+  /// Example:
+  ///
+  /// `foo.proto`
+  /// ```proto
+  /// syntax = "proto2";
+  ///
+  /// message Foo {
+  ///   extensions 1 to max;
+  /// }
+  ///
+  /// extend Foo {
+  ///   optional string val1 = 1;
+  ///   optional string val2 = 2;
+  /// }
+  /// ```
+  /// `main.dart`
+  /// ```
+  /// import 'package:protobuf/protobuf.dart';
+  /// import 'package:test/test.dart';
+  /// import 'src/generated/sample.pb.dart';
+  ///
+  /// void main() {
+  ///   ExtensionRegistry r1 = ExtensionRegistry()..add(Sample.val1);
+  ///   ExtensionRegistry r2 = ExtensionRegistry()..add(Sample.val2);
+  ///   Foo original = Foo()..setExtension(Sample.val1, 'a')..setExtension(Sample.val2, 'b');
+  ///   Foo withUnknownFields = Foo.fromBuffer(original.writeToBuffer());
+  ///   Foo reparsed1 = r1.reparseMessage(withUnknownFields);
+  ///   Foo reparsed2 = r2.reparseMessage(reparsed1);
+  ///   expect(withUnknownFields.hasExtension(Sample.val1), isFalse);
+  ///   expect(withUnknownFields.hasExtension(Sample.val2), isFalse);
+  ///   expect(reparsed1.hasExtension(Sample.val1), isTrue);
+  ///   expect(reparsed1.hasExtension(Sample.val2), isFalse);
+  ///   expect(reparsed2.hasExtension(Sample.val1), isTrue);
+  ///   expect(reparsed2.hasExtension(Sample.val2), isTrue);
+  /// }
+  /// ```
+  T reparseMessage<T extends GeneratedMessage>(T message) =>
+      _reparseMessage(message, this);
+}
+
+T _reparseMessage<T extends GeneratedMessage>(
+    T message, ExtensionRegistry extensionRegistry) {
+  T result = message.createEmptyInstance();
+
+  result._fieldSet._shallowCopyValues(message._fieldSet);
+  UnknownFieldSet resultUnknownFields = result._fieldSet._unknownFields;
+  if (resultUnknownFields != null) {
+    CodedBufferWriter codedBufferWriter = CodedBufferWriter();
+    extensionRegistry._extensions[message.info_.qualifiedMessageName]
+        ?.forEach((tagNumber, extension) {
+      final UnknownFieldSetField unknownField =
+          resultUnknownFields._fields[tagNumber];
+      if (unknownField != null) {
+        unknownField.writeTo(tagNumber, codedBufferWriter);
+      }
+      resultUnknownFields._fields.remove(tagNumber);
+    });
+
+    result.mergeFromBuffer(codedBufferWriter.toBuffer(), extensionRegistry);
+  }
+  if (message._fieldSet._isReadOnly) {
+    result.freeze();
+  }
+  return result;
 }
 
 class _EmptyExtensionRegistry implements ExtensionRegistry {
   const _EmptyExtensionRegistry();
 
-  // Needed to quiet missing member warning.
-  get _extensions => null;
+  get _extensions => const <String, Map<int, Extension>>{};
 
   void add(Extension extension) {
     throw UnsupportedError('Immutable ExtensionRegistry');
@@ -50,4 +128,7 @@
   }
 
   Extension getExtension(String messageName, int tagNumber) => null;
+
+  T reparseMessage<T extends GeneratedMessage>(T message) =>
+      _reparseMessage(message, this);
 }
diff --git a/protobuf/pubspec.yaml b/protobuf/pubspec.yaml
index 20e8044..9e8b5b0 100644
--- a/protobuf/pubspec.yaml
+++ b/protobuf/pubspec.yaml
@@ -1,5 +1,5 @@
 name: protobuf
-version: 0.13.12
+version: 0.13.13
 author: Dart Team <misc@dartlang.org>
 description: >
   Runtime library for protocol buffers support.
diff --git a/protoc_plugin/Makefile b/protoc_plugin/Makefile
index 1b5ee10..a12c57f 100644
--- a/protoc_plugin/Makefile
+++ b/protoc_plugin/Makefile
@@ -18,6 +18,7 @@
 	google/protobuf/unittest \
 	dart_name \
 	enum_extension \
+	extend_unittest \
 	ExtensionEnumNameConflict \
 	ExtensionNameConflict \
 	foo \
diff --git a/protoc_plugin/test/extension_test.dart b/protoc_plugin/test/extension_test.dart
index 8423ebf..a5c14bb 100644
--- a/protoc_plugin/test/extension_test.dart
+++ b/protoc_plugin/test/extension_test.dart
@@ -10,6 +10,7 @@
 
 import '../out/protos/google/protobuf/unittest.pb.dart';
 import '../out/protos/enum_extension.pb.dart';
+import '../out/protos/extend_unittest.pb.dart';
 import '../out/protos/nested_extension.pb.dart';
 import '../out/protos/non_nested_extension.pb.dart';
 import '../out/protos/ExtensionNameConflict.pb.dart';
@@ -23,6 +24,17 @@
       return true;
     }));
 
+final withExtensions = TestAllExtensions()
+  ..setExtension(
+      Unittest.optionalForeignMessageExtension, ForeignMessage()..c = 3)
+  ..setExtension(Unittest.defaultStringExtension, 'bar')
+  ..getExtension(Unittest.repeatedBytesExtension).add('pop'.codeUnits)
+  ..setExtension(
+      Extend_unittest.outer,
+      Outer()
+        ..inner = (Inner()..value = 'abc')
+        ..setExtension(Extend_unittest.extensionInner, Inner()..value = 'def'));
+
 void main() {
   test('can set all extension types', () {
     TestAllExtensions message = TestAllExtensions();
@@ -232,4 +244,146 @@
     expect(decodedWithExtension == withExtension, false);
     expect(decodedWithExtension == withExtension, false);
   });
+
+  test(
+      'ExtensionRegistry.reparseMessage will preserve already registered extensions',
+      () {
+    ExtensionRegistry r1 = ExtensionRegistry();
+    Unittest.registerAllExtensions(r1);
+
+    ExtensionRegistry r2 = ExtensionRegistry();
+    Extend_unittest.registerAllExtensions(r2);
+    final withUnknownFields =
+        TestAllExtensions.fromBuffer(withExtensions.writeToBuffer());
+    final reparsedR1 = r1.reparseMessage(withUnknownFields);
+
+    expect(
+        reparsedR1.getExtension(Unittest.optionalForeignMessageExtension).c, 3);
+
+    expect(
+        r2
+            .reparseMessage(reparsedR1)
+            .getExtension(Unittest.optionalForeignMessageExtension)
+            .c,
+        3);
+  });
+
+  test(
+      'ExtensionRegistry.reparseMessage reparses extensions that were not in the original registry',
+      () {
+    ExtensionRegistry r = ExtensionRegistry();
+    Unittest.registerAllExtensions(r);
+    Extend_unittest.registerAllExtensions(r);
+
+    final withUnknownFields =
+        TestAllExtensions.fromBuffer(withExtensions.writeToBuffer());
+    final reparsed = r.reparseMessage(withUnknownFields);
+
+    expect(withUnknownFields.getExtension(Unittest.defaultStringExtension),
+        'hello');
+
+    expect(reparsed.getExtension(Unittest.defaultStringExtension), 'bar');
+
+    expect(
+        reparsed.unknownFields
+            .getField(Unittest.defaultStringExtension.tagNumber),
+        null,
+        reason:
+            'ExtensionRegistry.reparseMessage does not leave reparsed fields in unknownFields');
+    expect(reparsed.getExtension(Unittest.repeatedBytesExtension),
+        ['pop'.codeUnits]);
+
+    expect(
+        reparsed.getExtension(Unittest.optionalForeignMessageExtension).c, 3);
+
+    expect(reparsed.getExtension(Extend_unittest.outer).inner.value, 'abc');
+
+    final onlyOuter = ExtensionRegistry()..add(Extend_unittest.outer);
+    final onlyOuterReparsed = onlyOuter.reparseMessage(withUnknownFields);
+    expect(
+        onlyOuterReparsed
+            .getExtension(Extend_unittest.outer)
+            .hasExtension(Extend_unittest.extensionInner),
+        isFalse);
+    expect(
+        onlyOuter
+            .reparseMessage(withUnknownFields)
+            .getExtension(Extend_unittest.outer)
+            .getExtension(Extend_unittest.extensionInner)
+            .value,
+        '');
+
+    expect(
+        reparsed
+            .getExtension(Extend_unittest.outer)
+            .hasExtension(Extend_unittest.extensionInner),
+        isTrue);
+    expect(
+        reparsed
+            .getExtension(Extend_unittest.outer)
+            .getExtension(Extend_unittest.extensionInner)
+            .value,
+        'def');
+  });
+
+  test('ExtensionRegistry.reparseMessage does not update the original', () {
+    ExtensionRegistry r = ExtensionRegistry();
+    Unittest.registerAllExtensions(r);
+    Extend_unittest.registerAllExtensions(r);
+
+    final withUnknownFields =
+        TestAllExtensions.fromBuffer(withExtensions.writeToBuffer());
+
+    final reparsedWithEmpty =
+        ExtensionRegistry().reparseMessage(withUnknownFields);
+    expect(reparsedWithEmpty, isNot(same(withUnknownFields)));
+
+    final reparsed = r.reparseMessage(withUnknownFields);
+
+    List<String> strings =
+        withUnknownFields.getExtension(Unittest.repeatedStringExtension);
+    expect(strings, []);
+    strings.add('pop2');
+    expect(reparsed.getExtension(Unittest.repeatedStringExtension), []);
+  });
+
+  test('ExtensionRegistry.reparseMessage will throw on malformed buffers', () {
+    final ExtensionRegistry r = ExtensionRegistry();
+    Unittest.registerAllExtensions(r);
+    final ExtensionRegistry r2 = ExtensionRegistry();
+
+    Extend_unittest.registerAllExtensions(r2);
+
+    // The message encoded in this buffer has an encoding error in the
+    // Extend_unittest.outer extension field.
+    final withMalformedExtensionEncoding = TestAllExtensions.fromBuffer([
+      154, 1, 2, 8, 3, 210, //
+      4, 3, 98, 97, 114, 194, 6, 14, 10, 5, 10, 4,
+      97, 98, 99, 18, 5, 10, 3, 100, 101, 102
+    ]);
+    expect(
+        r
+            .reparseMessage(withMalformedExtensionEncoding)
+            .getExtension(Unittest.defaultStringExtension),
+        'bar',
+        reason:
+            'this succeeds because it does not decode Extend_unittest.outer');
+    expect(() => r2.reparseMessage(withMalformedExtensionEncoding),
+        throwsA(const TypeMatcher<InvalidProtocolBufferException>()));
+  });
+
+  test('ExtensionRegistry.reparseMessage preserves frozenness', () {
+    final ExtensionRegistry r = ExtensionRegistry();
+    Unittest.registerAllExtensions(r);
+
+    final withUnknownFields =
+        TestAllExtensions.fromBuffer(withExtensions.writeToBuffer());
+    withUnknownFields.freeze();
+
+    expect(
+        () => r
+            .reparseMessage(withUnknownFields)
+            .setExtension(Unittest.defaultStringExtension, 'blah'),
+        throwsA(TypeMatcher<UnsupportedError>()));
+  });
 }
diff --git a/protoc_plugin/test/protos/extend_unittest.proto b/protoc_plugin/test/protos/extend_unittest.proto
new file mode 100644
index 0000000..cdbdaee
--- /dev/null
+++ b/protoc_plugin/test/protos/extend_unittest.proto
@@ -0,0 +1,24 @@
+// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+syntax = "proto2";
+
+import "google/protobuf/unittest.proto";
+
+message Outer {
+  optional Inner inner = 1;
+  extensions 2 to max;
+}
+
+extend Outer {
+  optional Inner extension_inner = 2;
+}
+
+message Inner {
+  optional string value = 1;
+}
+
+extend protobuf_unittest.TestAllExtensions {
+  optional Outer outer = 104;
+}