Commit 8ab91a6fe9

Andrew Kelley <andrew@ziglang.org>
2025-07-29 03:59:03
error.EndOfStream disambiguation
1 parent f644f40
Changed files (1)
lib
std
compress
lib/std/compress/flate/Decompress.zig
@@ -44,14 +44,13 @@ const State = union(enum) {
 pub const Error = Container.Error || error{
     InvalidCode,
     InvalidMatch,
-    InvalidBlockType,
     WrongStoredBlockNlen,
     InvalidDynamicBlockHeader,
-    EndOfStream,
     ReadFailed,
     OversubscribedHuffmanTree,
     IncompleteHuffmanTree,
     MissingEndOfBlockCode,
+    EndOfStream,
 };
 
 pub fn init(input: *Reader, container: Container, buffer: []u8) Decompress {
@@ -153,7 +152,14 @@ fn decodeSymbol(self: *Decompress, decoder: anytype) !Symbol {
 pub fn stream(r: *Reader, w: *Writer, limit: std.Io.Limit) Reader.StreamError!usize {
     const d: *Decompress = @alignCast(@fieldParentPtr("reader", r));
     return readInner(d, w, limit) catch |err| switch (err) {
-        error.EndOfStream => return error.EndOfStream,
+        error.EndOfStream => {
+            if (d.state == .end) {
+                return error.EndOfStream;
+            } else {
+                d.read_err = error.EndOfStream;
+                return error.ReadFailed;
+            }
+        },
         error.WriteFailed => return error.WriteFailed,
         else => |e| {
             // In the event of an error, state is unmodified so that it can be
@@ -922,120 +928,109 @@ test "zlib decompress non compressed block (type 0)" {
 }
 
 test "failing end-of-stream" {
-    try testFailure(@embedFile("testdata/fuzz/end-of-stream.input"), error.EndOfStream);
+    try testFailure(.raw, @embedFile("testdata/fuzz/end-of-stream.input"), error.EndOfStream);
 }
 test "failing invalid-distance" {
-    try testFailure(@embedFile("testdata/fuzz/invalid-distance.input"), error.InvalidMatch);
+    try testFailure(.raw, @embedFile("testdata/fuzz/invalid-distance.input"), error.InvalidMatch);
 }
 test "failing invalid-tree01" {
-    try testFailure(@embedFile("testdata/fuzz/invalid-tree01.input"), error.IncompleteHuffmanTree);
+    try testFailure(.raw, @embedFile("testdata/fuzz/invalid-tree01.input"), error.IncompleteHuffmanTree);
 }
 test "failing invalid-tree02" {
-    try testFailure(@embedFile("testdata/fuzz/invalid-tree02.input"), error.IncompleteHuffmanTree);
+    try testFailure(.raw, @embedFile("testdata/fuzz/invalid-tree02.input"), error.IncompleteHuffmanTree);
 }
 test "failing invalid-tree03" {
-    try testFailure(@embedFile("testdata/fuzz/invalid-tree03.input"), error.IncompleteHuffmanTree);
+    try testFailure(.raw, @embedFile("testdata/fuzz/invalid-tree03.input"), error.IncompleteHuffmanTree);
 }
 test "failing lengths-overflow" {
-    try testFailure(@embedFile("testdata/fuzz/lengths-overflow.input"), error.InvalidDynamicBlockHeader);
+    try testFailure(.raw, @embedFile("testdata/fuzz/lengths-overflow.input"), error.InvalidDynamicBlockHeader);
 }
 test "failing out-of-codes" {
-    try testFailure(@embedFile("testdata/fuzz/out-of-codes.input"), error.InvalidCode);
+    try testFailure(.raw, @embedFile("testdata/fuzz/out-of-codes.input"), error.InvalidCode);
 }
 test "failing puff01" {
-    try testFailure(@embedFile("testdata/fuzz/puff01.input"), error.WrongStoredBlockNlen);
+    try testFailure(.raw, @embedFile("testdata/fuzz/puff01.input"), error.WrongStoredBlockNlen);
 }
 test "failing puff02" {
-    try testFailure(@embedFile("testdata/fuzz/puff02.input"), error.EndOfStream);
+    try testFailure(.raw, @embedFile("testdata/fuzz/puff02.input"), error.EndOfStream);
 }
 test "failing puff04" {
-    try testFailure(@embedFile("testdata/fuzz/puff04.input"), error.InvalidCode);
+    try testFailure(.raw, @embedFile("testdata/fuzz/puff04.input"), error.InvalidCode);
 }
 test "failing puff05" {
-    try testFailure(@embedFile("testdata/fuzz/puff05.input"), error.EndOfStream);
+    try testFailure(.raw, @embedFile("testdata/fuzz/puff05.input"), error.EndOfStream);
 }
 test "failing puff06" {
-    try testFailure(@embedFile("testdata/fuzz/puff06.input"), error.EndOfStream);
+    try testFailure(.raw, @embedFile("testdata/fuzz/puff06.input"), error.EndOfStream);
 }
 test "failing puff08" {
-    try testFailure(@embedFile("testdata/fuzz/puff08.input"), error.InvalidCode);
+    try testFailure(.raw, @embedFile("testdata/fuzz/puff08.input"), error.InvalidCode);
 }
 test "failing puff10" {
-    try testFailure(@embedFile("testdata/fuzz/puff10.input"), error.InvalidCode);
+    try testFailure(.raw, @embedFile("testdata/fuzz/puff10.input"), error.InvalidCode);
 }
 test "failing puff11" {
-    try testFailure(@embedFile("testdata/fuzz/puff11.input"), error.InvalidMatch);
+    try testFailure(.raw, @embedFile("testdata/fuzz/puff11.input"), error.InvalidMatch);
 }
 test "failing puff12" {
-    try testFailure(@embedFile("testdata/fuzz/puff12.input"), error.InvalidDynamicBlockHeader);
+    try testFailure(.raw, @embedFile("testdata/fuzz/puff12.input"), error.InvalidDynamicBlockHeader);
 }
 test "failing puff13" {
-    try testFailure(@embedFile("testdata/fuzz/puff13.input"), error.IncompleteHuffmanTree);
+    try testFailure(.raw, @embedFile("testdata/fuzz/puff13.input"), error.IncompleteHuffmanTree);
 }
 test "failing puff14" {
-    try testFailure(@embedFile("testdata/fuzz/puff14.input"), error.EndOfStream);
+    try testFailure(.raw, @embedFile("testdata/fuzz/puff14.input"), error.EndOfStream);
 }
 test "failing puff15" {
-    try testFailure(@embedFile("testdata/fuzz/puff15.input"), error.IncompleteHuffmanTree);
+    try testFailure(.raw, @embedFile("testdata/fuzz/puff15.input"), error.IncompleteHuffmanTree);
 }
 test "failing puff16" {
-    try testFailure(@embedFile("testdata/fuzz/puff16.input"), error.InvalidDynamicBlockHeader);
+    try testFailure(.raw, @embedFile("testdata/fuzz/puff16.input"), error.InvalidDynamicBlockHeader);
 }
 test "failing puff17" {
-    try testFailure(@embedFile("testdata/fuzz/puff17.input"), error.MissingEndOfBlockCode);
+    try testFailure(.raw, @embedFile("testdata/fuzz/puff17.input"), error.MissingEndOfBlockCode);
 }
 test "failing fuzz1" {
-    try testFailure(@embedFile("testdata/fuzz/fuzz1.input"), error.InvalidDynamicBlockHeader);
+    try testFailure(.raw, @embedFile("testdata/fuzz/fuzz1.input"), error.InvalidDynamicBlockHeader);
 }
 test "failing fuzz2" {
-    try testFailure(@embedFile("testdata/fuzz/fuzz2.input"), error.InvalidDynamicBlockHeader);
+    try testFailure(.raw, @embedFile("testdata/fuzz/fuzz2.input"), error.InvalidDynamicBlockHeader);
 }
 test "failing fuzz3" {
-    try testFailure(@embedFile("testdata/fuzz/fuzz3.input"), error.InvalidMatch);
+    try testFailure(.raw, @embedFile("testdata/fuzz/fuzz3.input"), error.InvalidMatch);
 }
 test "failing fuzz4" {
-    try testFailure(@embedFile("testdata/fuzz/fuzz4.input"), error.OversubscribedHuffmanTree);
+    try testFailure(.raw, @embedFile("testdata/fuzz/fuzz4.input"), error.OversubscribedHuffmanTree);
 }
 test "failing puff18" {
-    try testFailure(@embedFile("testdata/fuzz/puff18.input"), error.OversubscribedHuffmanTree);
+    try testFailure(.raw, @embedFile("testdata/fuzz/puff18.input"), error.OversubscribedHuffmanTree);
 }
 test "failing puff19" {
-    try testFailure(@embedFile("testdata/fuzz/puff19.input"), error.OversubscribedHuffmanTree);
+    try testFailure(.raw, @embedFile("testdata/fuzz/puff19.input"), error.OversubscribedHuffmanTree);
 }
 test "failing puff20" {
-    try testFailure(@embedFile("testdata/fuzz/puff20.input"), error.OversubscribedHuffmanTree);
+    try testFailure(.raw, @embedFile("testdata/fuzz/puff20.input"), error.OversubscribedHuffmanTree);
 }
 test "failing puff21" {
-    try testFailure(@embedFile("testdata/fuzz/puff21.input"), error.OversubscribedHuffmanTree);
+    try testFailure(.raw, @embedFile("testdata/fuzz/puff21.input"), error.OversubscribedHuffmanTree);
 }
 test "failing puff22" {
-    try testFailure(@embedFile("testdata/fuzz/puff22.input"), error.OversubscribedHuffmanTree);
+    try testFailure(.raw, @embedFile("testdata/fuzz/puff22.input"), error.OversubscribedHuffmanTree);
 }
 test "failing puff23" {
-    try testFailure(@embedFile("testdata/fuzz/puff23.input"), error.OversubscribedHuffmanTree);
+    try testFailure(.raw, @embedFile("testdata/fuzz/puff23.input"), error.OversubscribedHuffmanTree);
 }
 test "failing puff24" {
-    try testFailure(@embedFile("testdata/fuzz/puff24.input"), error.IncompleteHuffmanTree);
+    try testFailure(.raw, @embedFile("testdata/fuzz/puff24.input"), error.IncompleteHuffmanTree);
 }
 test "failing puff25" {
-    try testFailure(@embedFile("testdata/fuzz/puff25.input"), error.OversubscribedHuffmanTree);
+    try testFailure(.raw, @embedFile("testdata/fuzz/puff25.input"), error.OversubscribedHuffmanTree);
 }
 test "failing puff26" {
-    try testFailure(@embedFile("testdata/fuzz/puff26.input"), error.InvalidDynamicBlockHeader);
+    try testFailure(.raw, @embedFile("testdata/fuzz/puff26.input"), error.InvalidDynamicBlockHeader);
 }
 test "failing puff27" {
-    try testFailure(@embedFile("testdata/fuzz/puff27.input"), error.InvalidDynamicBlockHeader);
-}
-
-fn testFailure(in: []const u8, expected_err: anyerror) !void {
-    var reader: Reader = .fixed(in);
-    var aw: Writer.Allocating = .init(testing.allocator);
-    try aw.ensureUnusedCapacity(flate.history_len);
-    defer aw.deinit();
-
-    var decompress: Decompress = .init(&reader, .raw, &.{});
-    try testing.expectError(error.ReadFailed, decompress.reader.streamRemaining(&aw.writer));
-    try testing.expectEqual(expected_err, decompress.read_err orelse return error.TestFailed);
+    try testFailure(.raw, @embedFile("testdata/fuzz/puff27.input"), error.InvalidDynamicBlockHeader);
 }
 
 test "deflate-stream" {
@@ -1097,82 +1092,57 @@ test "don't read past deflate stream's end" {
 
 test "zlib header" {
     // Truncated header
-    try testing.expectError(
-        error.EndOfStream,
-        testDecompress(.zlib, &[_]u8{0x78}, ""),
-    );
+    try testFailure(.zlib, &[_]u8{0x78}, error.EndOfStream);
+
     // Wrong CM
-    try testing.expectError(
-        error.BadZlibHeader,
-        testDecompress(.zlib, &[_]u8{ 0x79, 0x94 }, ""),
-    );
+    try testFailure(.zlib, &[_]u8{ 0x79, 0x94 }, error.BadZlibHeader);
+
     // Wrong CINFO
-    try testing.expectError(
-        error.BadZlibHeader,
-        testDecompress(.zlib, &[_]u8{ 0x88, 0x98 }, ""),
-    );
+    try testFailure(.zlib, &[_]u8{ 0x88, 0x98 }, error.BadZlibHeader);
+
     // Wrong checksum
-    try testing.expectError(
-        error.WrongZlibChecksum,
-        testDecompress(.zlib, &[_]u8{ 0x78, 0xda, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00 }, ""),
-    );
+    try testFailure(.zlib, &[_]u8{ 0x78, 0xda, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00 }, error.WrongZlibChecksum);
+
     // Truncated checksum
-    try testing.expectError(
-        error.EndOfStream,
-        testDecompress(.zlib, &[_]u8{ 0x78, 0xda, 0x03, 0x00, 0x00 }, ""),
-    );
+    try testFailure(.zlib, &[_]u8{ 0x78, 0xda, 0x03, 0x00, 0x00 }, error.EndOfStream);
 }
 
 test "gzip header" {
     // Truncated header
-    try testing.expectError(
-        error.EndOfStream,
-        testDecompress(.gzip, &[_]u8{ 0x1f, 0x8B }, undefined),
-    );
+    try testFailure(.gzip, &[_]u8{ 0x1f, 0x8B }, error.EndOfStream);
+
     // Wrong CM
-    try testing.expectError(
-        error.BadGzipHeader,
-        testDecompress(.gzip, &[_]u8{
-            0x1f, 0x8b, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00,
-            0x00, 0x03,
-        }, undefined),
-    );
+    try testFailure(.gzip, &[_]u8{
+        0x1f, 0x8b, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x03,
+    }, error.BadGzipHeader);
 
     // Wrong checksum
-    try testing.expectError(
-        error.WrongGzipChecksum,
-        testDecompress(.gzip, &[_]u8{
-            0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
-            0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x01,
-            0x00, 0x00, 0x00, 0x00,
-        }, undefined),
-    );
+    try testFailure(.gzip, &[_]u8{
+        0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x01,
+        0x00, 0x00, 0x00, 0x00,
+    }, error.WrongGzipChecksum);
+
     // Truncated checksum
-    try testing.expectError(
-        error.EndOfStream,
-        testDecompress(.gzip, &[_]u8{
-            0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
-            0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00,
-        }, undefined),
-    );
+    try testFailure(.gzip, &[_]u8{
+        0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00,
+    }, error.EndOfStream);
+
     // Wrong initial size
-    try testing.expectError(
-        error.WrongGzipSize,
-        testDecompress(.gzip, &[_]u8{
-            0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
-            0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
-            0x00, 0x00, 0x00, 0x01,
-        }, undefined),
-    );
+    try testFailure(.gzip, &[_]u8{
+        0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x01,
+    }, error.WrongGzipSize);
+
     // Truncated initial size field
-    try testing.expectError(
-        error.EndOfStream,
-        testDecompress(.gzip, &[_]u8{
-            0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
-            0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
-            0x00, 0x00, 0x00,
-        }, undefined),
-    );
+    try testFailure(.gzip, &[_]u8{
+        0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00,
+    }, error.EndOfStream);
 
     try testDecompress(.gzip, &[_]u8{
         // GZIP header
@@ -1184,17 +1154,6 @@ test "gzip header" {
     }, "");
 }
 
-fn testDecompress(container: Container, compressed: []const u8, expected_plain: []const u8) !void {
-    var in: std.Io.Reader = .fixed(compressed);
-    var aw: std.Io.Writer.Allocating = .init(testing.allocator);
-    try aw.ensureUnusedCapacity(flate.history_len);
-    defer aw.deinit();
-
-    var decompress: Decompress = .init(&in, container, &.{});
-    _ = try decompress.reader.streamRemaining(&aw.writer);
-    try testing.expectEqualSlices(u8, expected_plain, aw.getWritten());
-}
-
 test "zlib should not overshoot" {
     // Compressed zlib data with extra 4 bytes at the end.
     const data = [_]u8{
@@ -1220,3 +1179,25 @@ test "zlib should not overshoot" {
     try std.testing.expectEqual(n, 4);
     try std.testing.expectEqualSlices(u8, data[data.len - 4 .. data.len], out[0..n]);
 }
+
+fn testFailure(container: Container, in: []const u8, expected_err: anyerror) !void {
+    var reader: Reader = .fixed(in);
+    var aw: Writer.Allocating = .init(testing.allocator);
+    try aw.ensureUnusedCapacity(flate.history_len);
+    defer aw.deinit();
+
+    var decompress: Decompress = .init(&reader, container, &.{});
+    try testing.expectError(error.ReadFailed, decompress.reader.streamRemaining(&aw.writer));
+    try testing.expectEqual(expected_err, decompress.read_err orelse return error.TestFailed);
+}
+
+fn testDecompress(container: Container, compressed: []const u8, expected_plain: []const u8) !void {
+    var in: std.Io.Reader = .fixed(compressed);
+    var aw: std.Io.Writer.Allocating = .init(testing.allocator);
+    try aw.ensureUnusedCapacity(flate.history_len);
+    defer aw.deinit();
+
+    var decompress: Decompress = .init(&in, container, &.{});
+    _ = try decompress.reader.streamRemaining(&aw.writer);
+    try testing.expectEqualSlices(u8, expected_plain, aw.getWritten());
+}