-
Notifications
You must be signed in to change notification settings - Fork 186
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Decoder throws exception when decoding an empty map #719
Comments
FWIW: This happens on JS runtime When the map contains a Page that is not empty, everything works as expected. |
No difference on master:
|
Thanks for reporting this. I simplified the repro a little bit: syntax = "proto3";
message ProtoPara {
string id = 1;
}
message ProtoPage {
uint32 page_id = 1;
repeated ProtoPara paras = 2;
}
message ProtoBoard {
string id = 1;
string team_id = 2;
map<uint32, ProtoPage> pages = 3;
} void main() {
List<int> protoBoardBytes = [
10, // tag = 1, wire type = 2 (length delimited), type = string
12, // length = 12
51, 72, 86, 66, 71, 84, 106, 73, 56, 48, 75, 102, // <-- the string for "team_id" field
26, // tag = 3, wire type = 2 (length delimited), type = map<uint32, ProtoPage>
0 // length = 0
];
print(ProtoBoard.fromBuffer(protoBoardBytes));
} I annotated the wire message parts. There may be multiple issues involved here, but one problem seems to be that we don't handle 0 as length prefix in I don't understand why the code fails to create a default value for the map value type here ( It's also a bit strange that the protobuf implementation that generates this encoding generates an empty field for the |
@osa1 speaking of your comments in the code: I'll look into the Rust implementation as to why they emit the empty field for the pages. But maybe it's because the field is not technically empty, but contains one empty (default) element? |
@osa1 Ugh, the dump looks different for me:
It doesn't end with zero. |
@kika the part I show in my repro is not your original message, it's the part of it that triggers the bug. I'm using this Python 3 expression to generate Dart
The part starting with 12, 51, ... and ending with 26, 0 is a |
In proto3, without Unfortunately protobuf spec is not super clear about this stuff and you need to read design documents from the protobuf GitHub repo to figure it out. We have some relevant discussions in #691. Since you don't have That said, we also don't follow this rule in the Dart implementation. proto3 spec says default values should be omitted by default in JSON encoding (#585, #592). There should be some mention of the same thing in binary format as well, but I can't find it now, so maybe I'm misremembering? |
So it's a bug to produce this empty map and a bug to explode when seeing this empty map on the wire, right? |
I can't find where in the spec it says default values should be omitted (with no presence checking, i.e. no However on the receiving end we should still be able to handle it, because it could be an So in short:
|
I found where this is mentioned: https://github.com/protocolbuffers/protobuf/blob/main/docs/field_presence.md#presence-in-tag-value-stream-wire-format-serialization
So without As mentioned in my previous comment, we also don't follow this in protobuf.dart. |
This PR fixes two bugs when decoding map fields. 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; } This means we should handle these three cases: 1. A map field with length 0. In the example above, it's possible to see tag 1 (for `map_field`) with length prefix 0. Currently this case causes a null check error. (This is reported in google#719) A message encoding with length prefix 0 is the empty message. For a map field entry, this means default key mapped to default value. So this case now generates an entry `0 => MyMessage()`. 2. A map field with only key specified 3. A map field with only value specified. These two cases worked when the value is not a message or enum, becuase the library knows how to generate default values for fields other than messages and enums. (Reminder: map keys can only be integers or strings) To fix, `BuilderInfo.m` now takes a "default or maker" argument for the default value of the map values. Code generator passes the value when the map value is a message or enum. Test cases added for all three cases. Fixes google#719
I'm sending a message from the Rust backend which has almost all fields set to default (either 0 or empty string or empty
repeated
). I understand that in such case protobuf just "optimizes away" such values. Upon receiving this message the Dart code throws anullcheck
exception from runtime.Here's the
.proto
code:The message only has
ProtoBoard.id
set to something and also theProtoMessage.request_id
. Thepages
map contains a default a single value ofProtoPage
(page_id
is 0 andparas
array is empty). A binary dump of the message which is read byprotoc --decode
without a problem:ChAKDDNIVkJHVGpJODBLZhoAogYRb3BlbmJvYXJkTmpkOTc3ZjA=
protobuf package is at 2.1.0
Stack trace:
(skipped all below zone)
Changelog refers to some similar bug fixed in 2.0.1 but I can' seem to be able to find it.
The text was updated successfully, but these errors were encountered: