Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ jobs:
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
iwr -useb get.scoop.sh | iex
Join-Path (Resolve-Path ~).Path "scoop\shims" >> $Env:GITHUB_PATH
scoop install vcpkg uutils-coreutils
scoop install vcpkg
scoop install uutils-coreutils@0.5.0
shell: pwsh

- name: Restore vcpkg artifact
Expand Down
7 changes: 2 additions & 5 deletions ext/zlib/zlib.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
# define VALGRIND_MAKE_MEM_UNDEFINED(p, n) 0
#endif

#define RUBY_ZLIB_VERSION "3.2.2"
#define RUBY_ZLIB_VERSION "3.2.3"

#ifndef RB_PASS_CALLED_KEYWORDS
# define rb_class_new_instance_kw(argc, argv, klass, kw_splat) rb_class_new_instance(argc, argv, klass)
Expand Down Expand Up @@ -860,9 +860,7 @@ zstream_buffer_ungets(struct zstream *z, const Bytef *b, unsigned long len)
char *bufptr;
long filled;

if (NIL_P(z->buf) || (long)rb_str_capacity(z->buf) <= ZSTREAM_BUF_FILLED(z)) {
zstream_expand_buffer_into(z, len);
}
zstream_expand_buffer_into(z, len);
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

zstream_buffer_ungets now unconditionally calls zstream_expand_buffer_into(z, len). Since zstream_expand_buffer_into grows the string by len whenever avail_out != len, this will expand the buffer even when there is already enough capacity, causing unnecessary allocations/memory growth for repeated small ungetc calls. Consider expanding only when rb_str_capacity(z->buf) < ZSTREAM_BUF_FILLED(z) + len (or equivalent), and otherwise just adjust next_out/avail_out based on existing free capacity.

Suggested change
zstream_expand_buffer_into(z, len);
filled = ZSTREAM_BUF_FILLED(z);
if ((long)rb_str_capacity(z->buf) < filled + (long)len) {
zstream_expand_buffer_into(z, len);
}

Copilot uses AI. Check for mistakes.

RSTRING_GETMEM(z->buf, bufptr, filled);
memmove(bufptr + len, bufptr, filled);
Expand Down Expand Up @@ -1456,7 +1454,6 @@ rb_zstream_finish(VALUE obj)
* call-seq:
* flush_next_in -> input
*
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The doc comment for flush_next_in no longer contains a description (only the call-seq remains). If this method is intended to be documented, please restore/update the short description so generated docs remain consistent with nearby methods like flush_next_out.

Suggested change
*
*
* Flushes the input buffer and returns all data currently held in that buffer.

Copilot uses AI. Check for mistakes.
* Flushes input buffer and returns all data in that buffer.
*/
static VALUE
rb_zstream_flush_next_in(VALUE obj)
Expand Down
19 changes: 19 additions & 0 deletions test/zlib/test_zlib.rb
Original file line number Diff line number Diff line change
Expand Up @@ -882,6 +882,25 @@ def test_ungetc_at_start_of_file
assert_equal(-1, r.pos, "[ruby-core:81488][Bug #13616]")
end

def test_ungetc_buffer_underflow
initial_bufsize = 1024
payload = "A" * initial_bufsize
gzip_io = StringIO.new
Zlib::GzipWriter.wrap(gzip_io) { |gz| gz.write(payload) }
compressed = gzip_io.string

reader = Zlib::GzipReader.new(StringIO.new(compressed))
reader.read(1)
overflow_bytes = "B" * (initial_bufsize)
reader.ungetc(overflow_bytes)
data = reader.read(overflow_bytes.bytesize)
assert_equal overflow_bytes.bytesize, data.bytesize, data
assert_empty data.delete("B"), data
data = reader.read()
assert_equal initial_bufsize - 1, data.bytesize, data
assert_empty data.delete("A"), data
Comment on lines +893 to +901
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reader is never closed in this test. Elsewhere in this file streams are explicitly closed to avoid finalizer warnings (e.g., "the stream was freed prematurely"); please wrap the Zlib::GzipReader usage in an ensure/block form so it is always closed.

Suggested change
reader.read(1)
overflow_bytes = "B" * (initial_bufsize)
reader.ungetc(overflow_bytes)
data = reader.read(overflow_bytes.bytesize)
assert_equal overflow_bytes.bytesize, data.bytesize, data
assert_empty data.delete("B"), data
data = reader.read()
assert_equal initial_bufsize - 1, data.bytesize, data
assert_empty data.delete("A"), data
begin
reader.read(1)
overflow_bytes = "B" * (initial_bufsize)
reader.ungetc(overflow_bytes)
data = reader.read(overflow_bytes.bytesize)
assert_equal overflow_bytes.bytesize, data.bytesize, data
assert_empty data.delete("B"), data
data = reader.read()
assert_equal initial_bufsize - 1, data.bytesize, data
assert_empty data.delete("A"), data
ensure
reader.close
end

Copilot uses AI. Check for mistakes.
end

def test_open
Tempfile.create("test_zlib_gzip_reader_open") {|t|
t.close
Expand Down
Loading