From 1727f7fe35a32302d9bdd2653f1c556263153406 Mon Sep 17 00:00:00 2001 From: Shaisolaris <66793913+Shaisolaris@users.noreply.github.com> Date: Sun, 11 Oct 2026 22:51:51 -0700 Subject: [PATCH] Skip leading newlines before applying the limit in paragraph mode gets --- ext/java/org/jruby/ext/stringio/StringIO.java | 17 ++++++++++------ ext/stringio/stringio.c | 19 +++++++++++------- test/stringio/test_stringio.rb | 20 +++++++++++++++++++ 3 files changed, 43 insertions(+), 13 deletions(-) diff --git a/ext/java/org/jruby/ext/stringio/StringIO.java b/ext/java/org/jruby/ext/stringio/StringIO.java index 85bb955..72599e5 100644 --- a/ext/java/org/jruby/ext/stringio/StringIO.java +++ b/ext/java/org/jruby/ext/stringio/StringIO.java @@ -895,6 +895,17 @@ private IRubyObject getline(ThreadContext context, final IRubyObject rs, int lim int p; int w = 0; + if (rs != context.nil && ((RubyString) rs).size() == 0) { + // paragraph mode: skip leading newlines before applying the limit + p = s; + while (stringBytes[p] == '\n') { + if (++p == e) { + ptr.pos = e - begin; + return context.nil; + } + } + s = p; + } if (limit > 0 && s + limit < e) { e = getEncoding().rightAdjustCharHead(stringBytes, s, s + limit, e); } @@ -906,12 +917,6 @@ private IRubyObject getline(ThreadContext context, final IRubyObject rs, int lim } else if ((n = ((RubyString) rs).size()) == 0) { int paragraph_end = 0; p = s; - while (stringBytes[p] == '\n') { - if (++p == e) { - return context.nil; - } - } - s = p; while ((p = Helpers.memchr(stringBytes, p, '\n', e - p)) != -1 && (p != e)) { p++; if (!((p < e && stringBytes[p] == '\n') || diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index a689e6e..da4d1f1 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -1411,6 +1411,18 @@ strio_getline(struct getline_arg *arg, struct StringIO *ptr) s = RSTRING_PTR(ptr->string); e = s + RSTRING_LEN(ptr->string); s += ptr->pos; + if (!NIL_P(str) && RSTRING_LEN(str) == 0) { + /* paragraph mode: skip leading newlines before applying the limit */ + p = s; + while (p[(p + 1 < e) && (*p == '\r') && 0] == '\n') { + p += *p == '\r'; + if (++p == e) { + ptr->pos = e - RSTRING_PTR(ptr->string); + return Qnil; + } + } + s = p; + } if (limit > 0 && (size_t)limit < (size_t)(e - s)) { e = rb_enc_right_char_head(s, s + limit, e, get_enc(ptr)); } @@ -1423,13 +1435,6 @@ strio_getline(struct getline_arg *arg, struct StringIO *ptr) else if ((n = RSTRING_LEN(str)) == 0) { const char *paragraph_end = NULL; p = s; - while (p[(p + 1 < e) && (*p == '\r') && 0] == '\n') { - p += *p == '\r'; - if (++p == e) { - return Qnil; - } - } - s = p; while ((p = memchr(p, '\n', e - p)) && (p != e)) { p++; if (!((p < e && *p == '\n') || diff --git a/test/stringio/test_stringio.rb b/test/stringio/test_stringio.rb index fd88de5..7c947e8 100644 --- a/test/stringio/test_stringio.rb +++ b/test/stringio/test_stringio.rb @@ -143,6 +143,26 @@ def test_gets assert_string("", Encoding::UTF_8, StringIO.new("foo").gets(0)) end + def test_gets_paragraph_with_limit + stringio = StringIO.new("a\n\n\n\nb\n\n\nc") + assert_equal("a", stringio.gets("", 1)) + assert_equal("b", stringio.gets("", 1)) + assert_equal("c", stringio.gets("", 1)) + assert_nil(stringio.gets("", 1)) + + stringio = StringIO.new("a\n\n\n\nb") + assert_equal("a\n\n", stringio.gets("", 3)) + assert_equal("b", stringio.gets("", 3)) + assert_nil(stringio.gets("", 3)) + end + + def test_gets_paragraph_only_newlines + stringio = StringIO.new("\n\n\n") + assert_nil(stringio.gets("")) + assert_equal(3, stringio.pos) + assert_predicate(stringio, :eof?) + end + def test_gets_utf_16 stringio = StringIO.new("line1\nline2\nline3\n".encode("utf-16le")) assert_equal("line1\n".encode("utf-16le"), stringio.gets)