From 8ab91a6fe9d37ffc51645718a077b781be5d8873 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 28 Jul 2025 18:59:03 -0700 Subject: [PATCH] error.EndOfStream disambiguation --- lib/std/compress/flate/Decompress.zig | 225 ++++++++++++-------------- 1 file changed, 103 insertions(+), 122 deletions(-) diff --git a/lib/std/compress/flate/Decompress.zig b/lib/std/compress/flate/Decompress.zig index 1fde977f3e..f3cd45fe7e 100644 --- a/lib/std/compress/flate/Decompress.zig +++ b/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()); +}