Skip to content

PbList.addAll traverses the iterable twice, causes side effects to run twice #730

@osa1

Description

@osa1

Reported by an internal user.

@override
void addAll(Iterable<E> iterable) {
_checkModifiable('addAll');
iterable.forEach(_check);
_wrappedList.addAll(iterable);
}

The line iterable.forEach(_check) traverses the iterable once. If the iterable has side effects, those will be run for the first time here.

Then the next line _wrappedList.addAll(iterable) will again traverse the iterable, causing the side effects to run again.

I don't know if we should call this a bug, but I think it's fair to say that it's a bit unexpected.

Two possible fixes:

  1. Copy iterable elements to a list, then traverse the list many times:

    void addAll(Iterable<E> iterable) {
      _checkModifiable('addAll');
      List<E> list = iterable is List ? iterable : iterable.toList();
      list.forEach(_check);
      _wrappedList.addAll(list);
    }

    The downside is the function will now require allocating an intermediate list, and the list may be large in some uses.

  2. Check as we add elements to the wrapped list:

    void addAll(Iterable<E> iterable) {
      _checkModifiable('addAll');
      for (final e in iterable) {
        _check(e);
        _wrappedList.add(e);
      }
    }

    The downside is if a check fails the list will be updated. To avoid that, we could do:

    void addAll(Iterable<E> iterable) {
      _checkModifiable('addAll');
      var added = 0;
      for (final e in iterable) {
        try {
          _check(e);
        } catch (_) {
          _wrappedList.removeRange(_wrappedList.length - added, _wrappedList.length);
          rethrow;
        }
        _wrappedList.add(e);
        added += 1;
      }
    }

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