`value is ...` checks in the library are potentially wrong/buggy?
- Dominant language
- Dart
- Stars
- 572
- Forks
- 196
- Avg merge
- 1h 59m
- Merged PRs (30d)
- 2
Description
Imagine having this class:
```dart
class MyCls implements MyMsg {
...
}
```
where `MyMsg` is a proto message that implements a mixin:
```proto
message MyMsg {
option (dart_options.mixin) = "MyMixin";
...
}
```
and `MyMixin` implements `Map`:
```dart
class MyMixin implements Map {
...
}
```
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:
```proto
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: https://github.com/google/protobuf.dart/blob/a92abc50ef54d2b02b56e5a580c317f5ef170aed/protobuf/lib/src/protobuf/field_set.dart#L687-L712
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:
```dart
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
}
```
Contributor guide
Research direction
Start in protobuf/lib/src/protobuf/field_set.dart at the linked lines 687-712 and inspect how field values are classified. Reproduce the case where a generated message also implements Map, then compare the checks with the _FieldInfo type. Done means message-valued fields no longer use Map behavior while repeated and map fields still do.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- dart
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100