google / google/protobuf.dart

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

Open
#659 4 comments 0 reactions 0 assignees View on GitHub
bug question
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.