brendan-duncan / archive

Dart library to encode and decode various archive and compression formats, such as Zip, Tar, GZip, ZLib, and BZip2.
MIT License
395 stars 130 forks source link

Prevent modifying Archive.files directly - doing this makes "files" a… #302

Closed aemelyanovff closed 7 months ago

aemelyanovff commented 7 months ago

…nd "_fileMap" go out of sync

Problem

When you modify Archive.files directly, it does out of sync with _fileMap, and it results in errors or files disappearing mysteriously.

Example:

Future<void> main() async {
  final archive = Archive();
  archive.addFile(ArchiveFile('a.txt', 1, Uint8List.fromList([0])));
  archive.files.clear();
  archive.addFile(ArchiveFile('a.txt', 1, Uint8List.fromList([0])));
}

Output:

Unhandled exception:
RangeError (index): Invalid value: Valid value range is empty: 0
#0      List._setIndexed (dart:core-patch/growable_array.dart:273:49)
#1      List.[]= (dart:core-patch/growable_array.dart:268:5)
#2      Archive.addFile (package:archive/src/archive.dart:23:13)
#3      main (file:///Users/anton/dev/archive/example/example.dart:9:11)
#4      _delayEntrypointInvocation.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:296:19)
#5      _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:189:12)

It's even worse when it doesn't fail, but returns unexpected results:

Future<void> main() async {
  final archive = Archive();
  archive.addFile(ArchiveFile('a.txt', 1, Uint8List.fromList([0])));
  archive.files.clear();
  archive.addFile(ArchiveFile('b.txt', 1, Uint8List.fromList([0])));
  archive.addFile(ArchiveFile('a.txt', 1, Uint8List.fromList([0])));
  // Should be 2, but it prints 1
  print('Number of files in archive: ${archive.length}');
}

Solution

Since we can't keep files and _fileMap in sync when files is modified directly, Archive.files should return an unmodifiable view of the list of files. This way, attempts to modify Archive.files will fail right away with a clear message and force the user of the library to use methods like Archive.addFile() or Archive.clear().