Skip to content

deepEquals() crashes with "type 'Null' is not a subtype of type 'Object'" when comparing maps with different keys #1075

@siddharthadevops

Description

@siddharthadevops

Labels

  • bug
  • protobuf
  • null-safety

Description

The deepEquals() function in protobuf 5.0.0 crashes when comparing GeneratedMessage objects that contain map fields with different keys, throwing the error:

type 'Null' is not a subtype of type 'Object'

This is a regression from protobuf 4.x where such comparisons worked correctly.

Environment

  • Package: protobuf
  • Version: 5.0.0
  • Dart SDK: >=3.5.0 <4.0.0
  • Platform: All platforms

Steps to Reproduce

  1. Create a protobuf message with a map<K, V> field
  2. Create two instances with maps that have the same size but different keys
  3. Compare the messages using == operator

Minimal Example

import 'package:protobuf/protobuf.dart';

// Using a protobuf message with: map<uint32, bool> properties = 1;

void main() {
  final msg1 = TestMessage()
    ..properties[1] = true
    ..properties[2] = false;

  final msg2 = TestMessage()
    ..properties[1] = true
    ..properties[3] = true;  // Different key: 3 instead of 2

  // This crashes:
  print(msg1 == msg2);
}

Expected Behavior

The comparison should return false (maps are different) without crashing.

Actual Behavior

The code crashes with:

type 'Null' is not a subtype of type 'Object'
#0      areMapsEqual.<anonymous closure> (package:protobuf/src/protobuf/utils.dart:27:58)
#1      Iterable.every (dart:core/iterable.dart:446:16)
#2      areMapsEqual (package:protobuf/src/protobuf/utils.dart:27:19)
#3      deepEquals (package:protobuf/src/protobuf/utils.dart:13:44)
#4      FieldSet._equalFieldValues (package:protobuf/src/protobuf/field_set.dart:559:47)
#5      FieldSet._equals (package:protobuf/src/protobuf/field_set.dart:517:12)
#6      GeneratedMessage.== (package:protobuf/src/protobuf/generated_message.dart:133:51)

Root Cause

In protobuf 5.0.0, the deepEquals() signature changed from dynamic to non-nullable:

// protobuf 4.x
bool _deepEquals(lhs, rhs) { ... }

// protobuf 5.0.0
bool deepEquals(Object lhs, Object rhs) { ... }  // Non-nullable!

However, areMapsEqual() can pass null when accessing a key that doesn't exist:

bool areMapsEqual(Map lhs, Map rhs) {
  if (lhs.length != rhs.length) return false;
  return lhs.keys.every((key) => deepEquals(lhs[key], rhs[key]));
  //                                        ^^^^^^^^  ^^^^^^^^
  //                                        Can be null!
}

Proposed Solution

Make deepEquals accept nullable parameters:

bool deepEquals(Object? lhs, Object? rhs) {
  // Handle null cases first
  if (lhs == null || rhs == null) return lhs == rhs;
  // ... rest of the function unchanged
}

Files to Modify

  • protobuf/lib/src/protobuf/utils.dart (line 8)

Impact

This affects:

  • Any code comparing protobuf messages with map<K, V> fields
  • Production applications using protobuf 5.0.0
  • Migration from protobuf 4.x to 5.x

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions