diff --git a/ext/java/org/jruby/ext/stringio/StringIO.java b/ext/java/org/jruby/ext/stringio/StringIO.java index 85bb955..18f0f5a 100644 --- a/ext/java/org/jruby/ext/stringio/StringIO.java +++ b/ext/java/org/jruby/ext/stringio/StringIO.java @@ -896,7 +896,8 @@ private IRubyObject getline(ThreadContext context, final IRubyObject rs, int lim int w = 0; if (limit > 0 && s + limit < e) { - e = getEncoding().rightAdjustCharHead(stringBytes, s, s + limit, e); + // a fixed-width encoding may step past a truncated last character + e = Math.min(e, getEncoding().rightAdjustCharHead(stringBytes, s, s + limit, e)); } if (rs == context.nil) { if (chomp) { diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index 4f953f6..1255096 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -1555,7 +1555,10 @@ strio_getline(struct getline_arg *arg, struct StringIO *ptr) e = s + RSTRING_LEN(ptr->string); s += ptr->pos; if (limit > 0 && (size_t)limit < (size_t)(e - s)) { + const char *const end = e; e = rb_enc_right_char_head(s, s + limit, e, get_enc(ptr)); + /* a fixed-width encoding may step past a truncated last character */ + if (e > end) e = end; } if (NIL_P(str)) { if (arg->chomp) { diff --git a/test/stringio/test_stringio.rb b/test/stringio/test_stringio.rb index fd88de5..cfada6f 100644 --- a/test/stringio/test_stringio.rb +++ b/test/stringio/test_stringio.rb @@ -151,6 +151,17 @@ def test_gets_utf_16 assert_nil(stringio.gets) end + def test_gets_limit_in_truncated_fixed_width_char + # 6 bytes: one UTF-32 code unit and a truncated one. The limit + # falls inside the truncated unit, and the line must stop at the + # end of the string, not beyond it. + stringio = StringIO.new("abcdef".b.freeze) + stringio.set_encoding(Encoding::UTF_32LE) + assert_equal("abcdef".b, stringio.gets(5).b) + assert_equal(6, stringio.pos) + assert_nil(stringio.gets(5)) + end + def test_gets_chomp assert_equal(nil, StringIO.new("").gets(chomp: true)) assert_equal("", StringIO.new("\n").gets(chomp: true))