Skip to content

value is ... checks in the library are potentially wrong/buggy? #659

@osa1

Description

@osa1

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:

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
}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions