-
Notifications
You must be signed in to change notification settings - Fork 195
Description
Imagine having this class:
class MyCls implements MyMsg {
...
}
where MyMsg
is a proto message that implements a mixin:
message MyMsg {
option (dart_options.mixin) = "MyMixin";
...
}
and MyMixin
implements Map
:
class MyMixin implements Map<String, String> {
...
}
So MyCls
is both a MyMsg
(which is a GeneratedMessage
) and a Map
, and Dart expressions MyCls() is GeneratedMessage
and MyCls() is Map
both return true
.
Now, if I have another message with a MyMsg
field:
message AnotherMessage {
MyMsg my_msg = 1;
...
}
I can set myMsg
field MyCls
, which is both a GeneratedMessage
and a Map
. As a result, this code goes wrong:
protobuf.dart/protobuf/lib/src/protobuf/field_set.dart
Lines 687 to 712 in a92abc5
static int _hashField(int hash, FieldInfo fi, value) { | |
if (value is List && value.isEmpty) { | |
return hash; // It's either repeated or an empty byte array. | |
} | |
if (value is Map && value.isEmpty) { | |
return hash; | |
} | |
hash = _HashUtils._combine(hash, fi.tagNumber); | |
if (_isBytes(fi.type)) { | |
// Bytes are represented as a List<int> (Usually with byte-data). | |
// We special case that to match our equality semantics. | |
hash = _HashUtils._combine(hash, _HashUtils._hashObjects(value)); | |
} else if (!_isEnum(fi.type)) { | |
hash = _HashUtils._combine(hash, value.hashCode); | |
} else if (fi.isRepeated) { | |
hash = _HashUtils._combine( | |
hash, _HashUtils._hashObjects(value.map((enm) => enm.value))); | |
} else { | |
ProtobufEnum enm = value; | |
hash = _HashUtils._combine(hash, enm.value); | |
} | |
return hash; | |
} |
The problem is value is Map
is true
, but the field type is actually a message and using the Map
interface (in particular, isEmpty
) is not right. It's possible that the Map
interface of the message is empty, but the message has fields.
This may look like a convoluted setup, but I've just wasted a few hours debugging this exact issue in a downstream code. (thanks @sigurdm for the help with debugging this)
I think we should avoid checking field value types (e.g. value is List
and value is Map
above) and always rely on _FieldInfo
type. If _FieldInfo
says repeated, we cast to List
. If it says map, we cast to Map
, and so on.
Any thoughts @sigurdm?
To see how can a type be both a GeneratedMessage
and a Map
, here's a demos showing that a Dart type can subtype multiple types that are not in relation to each other:
class A {
String getCls() {
return 'A';
}
}
abstract class B implements A {
@override
String getCls() {
return 'B';
}
}
class C {
String getCls() {
return 'C';
}
}
class D1 extends C with B {}
class D2 with B implements C {}
void main() {
print(D1() is C); // true
print(D1() is B); // true
print(D2() is C); // true
print(D2() is B); // true
}