Change ExtensionRegistry.reparseMessage to also reparse extensions on… (#277)
diff --git a/protobuf/CHANGELOG.md b/protobuf/CHANGELOG.md
index fb1c577..fba6f37 100644
--- a/protobuf/CHANGELOG.md
+++ b/protobuf/CHANGELOG.md
@@ -5,6 +5,9 @@
* **Breaking**: The field-adder methods on `BuilderInfo` now takes only named optional arguments.
To migrate, update `protoc_plugin` to version 18.0.0 or higher.
* The field-adder methods on `BuilderInfo` all take a new argument `protoName`.
+* **Breaking**: Changed `ExtensionRegistry.reparseMessage` to reparse extensions deeply, that is it looks at every
+nested message and tries to reparse extensions from its unknown fields.
+
## 0.13.16
diff --git a/protobuf/lib/src/protobuf/extension_registry.dart b/protobuf/lib/src/protobuf/extension_registry.dart
index 0892c2d..44ca3b7 100644
--- a/protobuf/lib/src/protobuf/extension_registry.dart
+++ b/protobuf/lib/src/protobuf/extension_registry.dart
@@ -35,12 +35,14 @@
}
/// Returns a shallow copy of [message], with all extensions in [this] parsed
- /// from the unknown fields of [message].
+ /// from the unknown fields of [message] and of every nested submessage.
///
/// Extensions already present in [message] will be preserved.
///
/// If [message] is frozen, the result will be as well.
///
+ /// Returns the original message if no new extensions are parsed.
+ ///
/// Throws an [InvalidProtocolBufferException] if the parsed extensions are
/// malformed.
///
@@ -50,7 +52,7 @@
///
/// Example:
///
- /// `foo.proto`
+ /// `sample.proto`
/// ```proto
/// syntax = "proto2";
///
@@ -90,28 +92,89 @@
T _reparseMessage<T extends GeneratedMessage>(
T message, ExtensionRegistry extensionRegistry) {
- T result = message.createEmptyInstance();
+ T result;
+ T ensureResult() {
+ if (result == null) {
+ result ??= message.createEmptyInstance();
+ result._fieldSet._shallowCopyValues(message._fieldSet);
+ }
+ return result;
+ }
- result._fieldSet._shallowCopyValues(message._fieldSet);
- UnknownFieldSet resultUnknownFields = result._fieldSet._unknownFields;
- if (resultUnknownFields != null) {
+ UnknownFieldSet resultUnknownFields;
+ UnknownFieldSet ensureUnknownFields() =>
+ resultUnknownFields ??= ensureResult()._fieldSet._unknownFields;
+
+ UnknownFieldSet messageUnknownFields = message._fieldSet._unknownFields;
+ if (messageUnknownFields != null) {
CodedBufferWriter codedBufferWriter = CodedBufferWriter();
extensionRegistry._extensions[message.info_.qualifiedMessageName]
?.forEach((tagNumber, extension) {
final UnknownFieldSetField unknownField =
- resultUnknownFields._fields[tagNumber];
+ messageUnknownFields._fields[tagNumber];
if (unknownField != null) {
unknownField.writeTo(tagNumber, codedBufferWriter);
+ ensureUnknownFields()._fields.remove(tagNumber);
}
- resultUnknownFields._fields.remove(tagNumber);
});
- result.mergeFromBuffer(codedBufferWriter.toBuffer(), extensionRegistry);
+ if (codedBufferWriter.toBuffer().length > 0) {
+ ensureResult()
+ .mergeFromBuffer(codedBufferWriter.toBuffer(), extensionRegistry);
+ }
}
- if (message._fieldSet._isReadOnly) {
+
+ message._fieldSet._meta.byIndex.forEach((FieldInfo field) {
+ PbList resultEntries;
+ PbList ensureEntries() =>
+ resultEntries ??= ensureResult()._fieldSet._values[field.index];
+
+ PbMap resultMap;
+ PbMap ensureMap() =>
+ resultMap ??= ensureResult()._fieldSet._values[field.index];
+
+ if (field.isRepeated) {
+ final messageEntries = message._fieldSet._values[field.index];
+ if (messageEntries == null) return;
+ if (field.isGroupOrMessage) {
+ for (int i = 0; i < messageEntries.length; i++) {
+ final GeneratedMessage entry = messageEntries[i];
+ final GeneratedMessage reparsedEntry =
+ _reparseMessage(entry, extensionRegistry);
+ if (!identical(entry, reparsedEntry)) {
+ ensureEntries()[i] = reparsedEntry;
+ }
+ }
+ }
+ } else if (field.isMapField) {
+ final messageMap = message._fieldSet._values[field.index];
+ if (messageMap == null) return;
+ if (field.isGroupOrMessage) {
+ for (var key in messageMap.keys) {
+ final GeneratedMessage value = messageMap[key];
+ final GeneratedMessage reparsedValue =
+ _reparseMessage(value, extensionRegistry);
+ if (!identical(value, reparsedValue)) {
+ ensureMap()[key] = reparsedValue;
+ }
+ }
+ }
+ } else if (field.isGroupOrMessage) {
+ final messageSubField = message._fieldSet._values[field.index];
+ if (messageSubField == null) return;
+ final GeneratedMessage reparsedSubField =
+ _reparseMessage(messageSubField, extensionRegistry);
+ if (!identical(messageSubField, reparsedSubField)) {
+ ensureResult()._fieldSet._values[field.index] = reparsedSubField;
+ }
+ }
+ });
+
+ if (result != null && message.isFrozen) {
result.freeze();
}
- return result;
+
+ return result ?? message;
}
class _EmptyExtensionRegistry implements ExtensionRegistry {
diff --git a/protoc_plugin/lib/src/dart_options.pb.dart b/protoc_plugin/lib/src/dart_options.pb.dart
index 74f381a..b3aca8c 100644
--- a/protoc_plugin/lib/src/dart_options.pb.dart
+++ b/protoc_plugin/lib/src/dart_options.pb.dart
@@ -5,8 +5,7 @@
// @dart = 2.3
// ignore_for_file: camel_case_types,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type
-import 'dart:core' as $core
- show bool, Deprecated, double, int, List, Map, override, pragma, String;
+import 'dart:core' as $core;
import 'package:protobuf/protobuf.dart' as $pb;
diff --git a/protoc_plugin/lib/src/descriptor.pb.dart b/protoc_plugin/lib/src/descriptor.pb.dart
index 4697c1a..a0d6b2b 100644
--- a/protoc_plugin/lib/src/descriptor.pb.dart
+++ b/protoc_plugin/lib/src/descriptor.pb.dart
@@ -5,8 +5,7 @@
// @dart = 2.3
// ignore_for_file: camel_case_types,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type
-import 'dart:core' as $core
- show bool, Deprecated, double, int, List, Map, override, pragma, String;
+import 'dart:core' as $core;
import 'package:fixnum/fixnum.dart';
import 'package:protobuf/protobuf.dart' as $pb;
diff --git a/protoc_plugin/lib/src/descriptor.pbenum.dart b/protoc_plugin/lib/src/descriptor.pbenum.dart
index 8b3e18b..b31a733 100644
--- a/protoc_plugin/lib/src/descriptor.pbenum.dart
+++ b/protoc_plugin/lib/src/descriptor.pbenum.dart
@@ -6,7 +6,7 @@
// ignore_for_file: camel_case_types,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type
// ignore_for_file: UNDEFINED_SHOWN_NAME,UNUSED_SHOWN_NAME
-import 'dart:core' as $core show int, dynamic, String, List, Map;
+import 'dart:core' as $core;
import 'package:protobuf/protobuf.dart' as $pb;
class FieldDescriptorProto_Type extends $pb.ProtobufEnum {
diff --git a/protoc_plugin/lib/src/plugin.pb.dart b/protoc_plugin/lib/src/plugin.pb.dart
index e7e57f3..2056f9d 100644
--- a/protoc_plugin/lib/src/plugin.pb.dart
+++ b/protoc_plugin/lib/src/plugin.pb.dart
@@ -5,8 +5,7 @@
// @dart = 2.3
// ignore_for_file: camel_case_types,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type
-import 'dart:core' as $core
- show bool, Deprecated, double, int, List, Map, override, pragma, String;
+import 'dart:core' as $core;
import 'package:protobuf/protobuf.dart' as $pb;
diff --git a/protoc_plugin/test/extension_test.dart b/protoc_plugin/test/extension_test.dart
index a5c14bb..8a37ea7 100644
--- a/protoc_plugin/test/extension_test.dart
+++ b/protoc_plugin/test/extension_test.dart
@@ -336,7 +336,7 @@
final reparsedWithEmpty =
ExtensionRegistry().reparseMessage(withUnknownFields);
- expect(reparsedWithEmpty, isNot(same(withUnknownFields)));
+ expect(reparsedWithEmpty, same(withUnknownFields));
final reparsed = r.reparseMessage(withUnknownFields);
@@ -386,4 +386,165 @@
.setExtension(Unittest.defaultStringExtension, 'blah'),
throwsA(TypeMatcher<UnsupportedError>()));
});
+
+ test(
+ 'ExtensionRegistry.reparseMessage returns the same object when no new extensions are parsed',
+ () {
+ final original = Outer()
+ ..inner = (Inner()
+ ..innerMost = (InnerMost()
+ ..setExtension(Extend_unittest.innerMostExtensionString, 'a')))
+ ..inners.addAll([
+ (Inner()..setExtension(Extend_unittest.innerExtensionString, "a")),
+ Inner()..value = 'value'
+ ])
+ ..innerMap[0] =
+ (Inner()..setExtension(Extend_unittest.innerExtensionString, "a"));
+
+ final reparsed = ExtensionRegistry().reparseMessage(original);
+ expect(identical(reparsed, original), isTrue);
+ });
+
+ test(
+ 'ExtensionRegistry.reparseMessage reparses extension in deeply nested subfield',
+ () {
+ final original = Outer()
+ ..inner = (Inner()
+ ..innerMost = (InnerMost()
+ ..setExtension(Extend_unittest.innerMostExtensionString, 'a')));
+
+ final withUnknownFields = Outer.fromBuffer(original.writeToBuffer());
+
+ expect(
+ withUnknownFields.inner.innerMost
+ .hasExtension(Extend_unittest.innerMostExtensionString),
+ isFalse);
+
+ final r = ExtensionRegistry()
+ ..add(Extend_unittest.innerMostExtensionString);
+ final reparsed = r.reparseMessage(withUnknownFields);
+
+ expect(identical(reparsed.inner, withUnknownFields.inner), isFalse);
+ expect(
+ identical(reparsed.inner.innerMost, withUnknownFields.inner.innerMost),
+ isFalse);
+ expect(
+ reparsed.inner.innerMost
+ .hasExtension(Extend_unittest.innerMostExtensionString),
+ isTrue);
+ });
+
+ test(
+ 'ExtensionRegistry.reparseMessage reparses extensions in all nested subfields',
+ () {
+ final original = Outer()
+ ..inner = (Inner()
+ ..setExtension(Extend_unittest.innerExtensionString, "a")
+ ..innerMost = (InnerMost()
+ ..setExtension(Extend_unittest.innerMostExtensionString, 'a')));
+
+ final withUnknownFields = Outer.fromBuffer(original.writeToBuffer());
+
+ expect(
+ withUnknownFields.inner
+ .hasExtension(Extend_unittest.innerExtensionString),
+ isFalse);
+ expect(
+ withUnknownFields.inner.innerMost
+ .hasExtension(Extend_unittest.innerMostExtensionString),
+ isFalse);
+
+ final r = ExtensionRegistry()
+ ..addAll([
+ Extend_unittest.innerExtensionString,
+ Extend_unittest.innerMostExtensionString
+ ]);
+ final reparsed = r.reparseMessage(withUnknownFields);
+
+ expect(identical(reparsed.inner, withUnknownFields.inner), isFalse);
+ expect(
+ identical(reparsed.inner.innerMost, withUnknownFields.inner.innerMost),
+ isFalse);
+ expect(reparsed.inner.hasExtension(Extend_unittest.innerExtensionString),
+ isTrue);
+ expect(
+ reparsed.inner.innerMost
+ .hasExtension(Extend_unittest.innerMostExtensionString),
+ isTrue);
+ });
+
+ test(
+ 'ExtensionRegistry.reparseMessage doesn\'t copy deepest subfield without extensions',
+ () {
+ final original = Outer()
+ ..inner = (Inner()
+ ..setExtension(Extend_unittest.innerExtensionString, "a")
+ ..innerMost = InnerMost());
+
+ final withUnknownFields = Outer.fromBuffer(original.writeToBuffer());
+
+ expect(
+ withUnknownFields.inner
+ .hasExtension(Extend_unittest.innerExtensionString),
+ false);
+
+ final r = ExtensionRegistry()..add(Extend_unittest.innerExtensionString);
+ final reparsed = r.reparseMessage(withUnknownFields);
+
+ expect(identical(reparsed.inner, withUnknownFields.inner), isFalse);
+ expect(
+ identical(reparsed.inner.innerMost, withUnknownFields.inner.innerMost),
+ isTrue);
+ expect(reparsed.inner.hasExtension(Extend_unittest.innerExtensionString),
+ isTrue);
+ });
+
+ test(
+ 'ExtensionRegistry.reparseMessage reparses extensions in repeated fields',
+ () {
+ final original = Outer()
+ ..inners.addAll([
+ (Inner()..setExtension(Extend_unittest.innerExtensionString, "a")),
+ Inner()..value = 'value'
+ ]);
+
+ final withUnknownFields = Outer.fromBuffer(original.writeToBuffer());
+
+ expect(
+ withUnknownFields.inners.first
+ .hasExtension(Extend_unittest.innerExtensionString),
+ isFalse);
+
+ final r = ExtensionRegistry()..add(Extend_unittest.innerExtensionString);
+ final reparsed = r.reparseMessage(withUnknownFields);
+
+ expect(
+ reparsed.inners[0].hasExtension(Extend_unittest.innerExtensionString),
+ isTrue);
+ expect(identical(withUnknownFields.inners[1], reparsed.inners[1]), isTrue);
+ });
+
+ test('ExtensionRegistry.reparseMessage reparses extensions in map fields',
+ () {
+ final original = Outer()
+ ..innerMap[0] =
+ (Inner()..setExtension(Extend_unittest.innerExtensionString, "a"))
+ ..innerMap[1] = (Inner());
+
+ final withUnknownFields = Outer.fromBuffer(original.writeToBuffer());
+
+ expect(
+ withUnknownFields.innerMap[0]
+ .hasExtension(Extend_unittest.innerExtensionString),
+ isFalse);
+
+ final r = ExtensionRegistry()..add(Extend_unittest.innerExtensionString);
+ final reparsed = r.reparseMessage(withUnknownFields);
+
+ expect(
+ reparsed.innerMap[0].hasExtension(Extend_unittest.innerExtensionString),
+ isTrue);
+ expect(
+ identical(withUnknownFields.innerMap[1], reparsed.innerMap[1]), isTrue);
+ });
}
diff --git a/protoc_plugin/test/protos/extend_unittest.proto b/protoc_plugin/test/protos/extend_unittest.proto
index cdbdaee..93084e8 100644
--- a/protoc_plugin/test/protos/extend_unittest.proto
+++ b/protoc_plugin/test/protos/extend_unittest.proto
@@ -8,15 +8,32 @@
message Outer {
optional Inner inner = 1;
- extensions 2 to max;
+ repeated Inner inners = 2;
+ map<int32, Inner> inner_map = 3;
+ extensions 4 to max;
}
extend Outer {
- optional Inner extension_inner = 2;
+ optional Inner extension_inner = 4;
}
message Inner {
optional string value = 1;
+ optional InnerMost inner_most = 2;
+ extensions 3 to max;
+}
+
+extend Inner {
+ optional string inner_extension_string = 3;
+}
+
+message InnerMost {
+ optional string value = 1;
+ extensions 2 to max;
+}
+
+extend InnerMost {
+ optional string inner_most_extension_string = 2;
}
extend protobuf_unittest.TestAllExtensions {