Skip to content

Commit bdd90b2

Browse files
authored
Fix map decoding when key or value fields are missing (#745)
Map fields are encoded as repeated message fields where the message field 1 is the key and 2 is the value. In proto syntax, a map field like map<int32, MyMessage> map_field = 1; is encoded as if it was repeated MapFieldEntry map_field = 1; where message MapFieldEntry { optional int32 key = 1; optional MyMessage value = 2; } Since map entries are ordinary messages, it's possible for a map entry to have no key or value fields. This PR updates map decoding to handle missing key and value fields. Three tests added for (1) missing key field (2) missing value field (3) missing key and value fields (entry message has 0 length prefix). Fixes #719
1 parent 53a1448 commit bdd90b2

File tree

5 files changed

+98
-5
lines changed

5 files changed

+98
-5
lines changed

protobuf/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
* Avoid copying when reading map fields of read-only messages. ([#741])
1717
* Fix `PbMap._isReadonly` field initialization in `PbMap.unmodifiable`.
1818
([#741])
19+
* Fix decoding map fields when key or value (or both) fields of a map entry is
20+
missing. ([#719], [#745])
1921

2022
[#183]: https://github.com/google/protobuf.dart/issues/183
2123
[#644]: https://github.com/google/protobuf.dart/pull/644
@@ -29,6 +31,8 @@
2931
[#721]: https://github.com/google/protobuf.dart/pull/721
3032
[#681]: https://github.com/google/protobuf.dart/pull/681
3133
[#741]: https://github.com/google/protobuf.dart/pull/741
34+
[#719]: https://github.com/google/protobuf.dart/issues/719
35+
[#745]: https://github.com/google/protobuf.dart/pull/745
3236

3337
## 2.1.0
3438

protobuf/lib/src/protobuf/builder_info.dart

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -241,11 +241,12 @@ class BuilderInfo {
241241
List<ProtobufEnum>? enumValues,
242242
ProtobufEnum? defaultEnumValue,
243243
PackageName packageName = const PackageName(''),
244-
String? protoName}) {
244+
String? protoName,
245+
dynamic valueDefaultOrMaker}) {
245246
var mapEntryBuilderInfo = BuilderInfo(entryClassName, package: packageName)
246247
..add(PbMap._keyFieldNumber, 'key', keyFieldType, null, null, null, null)
247-
..add(PbMap._valueFieldNumber, 'value', valueFieldType, null,
248-
valueCreator, valueOf, enumValues);
248+
..add(PbMap._valueFieldNumber, 'value', valueFieldType,
249+
valueDefaultOrMaker, valueCreator, valueOf, enumValues);
249250

250251
addMapField<K, V>(tagNumber, name, keyFieldType, valueFieldType,
251252
mapEntryBuilderInfo, valueCreator,

protoc_plugin/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,14 @@
2222
```
2323
* Export public dependencies (`import public`s in proto files) in
2424
`.pbenum.dart` files, same as `.pb.dart` files. ([9aad6aa])
25+
* Fix decoding map fields when key or value (or both) fields of a map entry is
26+
missing. ([#719], [#745])
2527

2628
[#679]: https://github.com/google/protobuf.dart/pull/679
2729
[#703]: https://github.com/google/protobuf.dart/pull/703
2830
[9aad6aa]: https://github.com/google/protobuf.dart/commits/9aad6aa
31+
[#719]: https://github.com/google/protobuf.dart/issues/719
32+
[#745]: https://github.com/google/protobuf.dart/pull/745
2933

3034
## 20.0.1
3135

protoc_plugin/lib/src/protobuf_field.dart

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -204,8 +204,13 @@ class ProtobufField {
204204
final generator = baseType.generator as MessageGenerator;
205205
var key = generator._fieldList[0];
206206
var value = generator._fieldList[1];
207-
var keyType = key.baseType.getDartType(parent.fileGen!);
208-
var valueType = value.baseType.getDartType(parent.fileGen!);
207+
208+
// Key type is an integer type or string. No need to specify the default
209+
// value as the library knows the defaults for integer and string fields.
210+
final keyType = key.baseType.getDartType(parent.fileGen!);
211+
212+
// Value type can be anything other than another map.
213+
final valueType = value.baseType.getDartType(parent.fileGen!);
209214

210215
invocation = 'm<$keyType, $valueType>';
211216

@@ -214,10 +219,12 @@ class ProtobufField {
214219
named['valueFieldType'] = value.typeConstant;
215220
if (value.baseType.isMessage || value.baseType.isGroup) {
216221
named['valueCreator'] = '$valueType.create';
222+
named['valueDefaultOrMaker'] = value.generateDefaultFunction();
217223
}
218224
if (value.baseType.isEnum) {
219225
named['valueOf'] = '$valueType.valueOf';
220226
named['enumValues'] = '$valueType.values';
227+
named['valueDefaultOrMaker'] = value.generateDefaultFunction();
221228
named['defaultEnumValue'] = value.generateDefaultFunction();
222229
}
223230
if (package != '') {

protoc_plugin/test/map_field_test.dart

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,4 +370,81 @@ void main() {
370370
map2[1] = 2;
371371
expect(msg2.int32ToInt32Field[1], 2);
372372
});
373+
374+
test('Parses empty map fields', () {
375+
// Map fields are encoded as messages (as length-delimited fields). Check
376+
// that we handle 0 length fields. (#719)
377+
{
378+
final messageBytes = <int>[
379+
(5 << 3) | 2, // tag = 5, wire type = 2 (length delimited)
380+
0, // length = 0
381+
];
382+
final message = TestMap.fromBuffer(messageBytes);
383+
expect(
384+
message, TestMap()..int32ToMessageField[0] = TestMap_MessageValue());
385+
}
386+
387+
{
388+
final messageBytes = <int>[
389+
(4 << 3) | 2, // tag = 4, wire type = 2 (length delimited)
390+
0, // length = 0
391+
];
392+
final message = TestMap.fromBuffer(messageBytes);
393+
expect(
394+
message, TestMap()..int32ToEnumField[0] = TestMap_EnumValue.DEFAULT);
395+
}
396+
});
397+
398+
test('Parses map field with just key', () {
399+
// Similar to the case above, but the field just has key (no value)
400+
{
401+
final messageBytes = <int>[
402+
(5 << 3) | 2, // tag = 5, wire type = 2 (length delimited)
403+
2, // length = 2
404+
(1 << 3) | 0, // tag = 1 (map key), wire type = 0 (varint)
405+
1, // key = 1
406+
];
407+
final message = TestMap.fromBuffer(messageBytes);
408+
expect(
409+
message, TestMap()..int32ToMessageField[1] = TestMap_MessageValue());
410+
}
411+
412+
{
413+
final messageBytes = <int>[
414+
(4 << 3) | 2, // tag = 4, wire type = 2 (length delimited)
415+
2, // length = 2
416+
(1 << 3) | 0, // tag = 1 (map key), wire type = 0 (varint)
417+
1, // key = 1
418+
];
419+
final message = TestMap.fromBuffer(messageBytes);
420+
expect(
421+
message, TestMap()..int32ToEnumField[1] = TestMap_EnumValue.DEFAULT);
422+
}
423+
});
424+
425+
test('Parses map field with just key', () {
426+
// Similar to the case above, but the field just has value (no key)
427+
{
428+
final messageBytes = <int>[
429+
(5 << 3) | 2, // tag = 5, wire type = 2 (length delimited)
430+
2, // length = 2
431+
(2 << 3) | 2, // tag = 2 (map value), wire type = 2 (length delimited)
432+
0, // length = 0 (empty message)
433+
];
434+
final message = TestMap.fromBuffer(messageBytes);
435+
expect(
436+
message, TestMap()..int32ToMessageField[0] = TestMap_MessageValue());
437+
}
438+
439+
{
440+
final messageBytes = <int>[
441+
(4 << 3) | 2, // tag = 4, wire type = 2 (length delimited)
442+
2, // length = 2
443+
(2 << 3) | 2, // tag = 2 (map value), wire type = 2 (length delimited)
444+
1, // enum value = 1
445+
];
446+
final message = TestMap.fromBuffer(messageBytes);
447+
expect(message, TestMap()..int32ToEnumField[0] = TestMap_EnumValue.BAR);
448+
}
449+
});
373450
}

0 commit comments

Comments
 (0)