From d154bec0d5f0dd32e0d30559ab8a0a7ed8be98d2 Mon Sep 17 00:00:00 2001 From: shyouhei Date: Tue, 15 Jan 2019 06:41:58 +0000 Subject: [PATCH] setbyte / ungetbyte allow out-of-range integers * string.c: String#setbyte to accept arbitrary integers [Bug #15460] * io.c: ditto for IO#ungetbyte * ext/strringio/stringio.c: ditto for StringIO#ungetbyte git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@66824 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- ext/stringio/stringio.c | 45 ++++++++++++----------------- io.c | 28 +++++++----------- spec/ruby/core/io/ungetbyte_spec.rb | 10 ++++++- string.c | 9 +++--- test/ruby/test_m17n.rb | 14 ++++----- test/stringio/test_stringio.rb | 6 ++-- 6 files changed, 53 insertions(+), 59 deletions(-) diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index 161ce7fd83..47c2c50b95 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -805,33 +805,26 @@ strio_ungetbyte(VALUE self, VALUE c) struct StringIO *ptr = readable(self); check_modifiable(ptr); - if (NIL_P(c)) return Qnil; - if (FIXNUM_P(c)) { - int i = FIX2INT(c); - if (0 <= i && i <= UCHAR_MAX) { - char buf[1]; - buf[0] = (char)i; - return strio_unget_bytes(ptr, buf, 1); - } - else { - rb_raise(rb_eRangeError, - "integer %d too big to convert into `unsigned char'", i); - } - } - else if (RB_TYPE_P(c, T_BIGNUM)) { - rb_raise(rb_eRangeError, "bignum too big to convert into `unsigned char'"); - } - else { - char *cp; - long cl; - SafeStringValue(c); - cp = RSTRING_PTR(c); - cl = RSTRING_LEN(c); - if (cl == 0) return Qnil; - strio_unget_bytes(ptr, cp, cl); - RB_GC_GUARD(c); - return Qnil; + switch (TYPE(c)) { + case T_NIL: + return Qnil; + case T_FIXNUM: + case T_BIGNUM: ; + /* rb_int_modulo() not visible from exts */ + VALUE v = rb_funcall(c, rb_intern("modulo"), 1, INT2FIX(256)); + unsigned char cc = NUM2INT(v) & 0xFF; + c = rb_str_new((const char *)&cc, 1); + break; + default: + SafeStringValue(c); } + + const char *cp = RSTRING_PTR(c); + long cl = RSTRING_LEN(c); + if (cl == 0) return Qnil; + strio_unget_bytes(ptr, cp, cl); + RB_GC_GUARD(c); + return Qnil; } static VALUE diff --git a/io.c b/io.c index 24dacc5dd8..7de405de32 100644 --- a/io.c +++ b/io.c @@ -4258,23 +4258,17 @@ rb_io_ungetbyte(VALUE io, VALUE b) GetOpenFile(io, fptr); rb_io_check_byte_readable(fptr); - if (NIL_P(b)) return Qnil; - if (FIXNUM_P(b)) { - int i = FIX2INT(b); - if (0 <= i && i <= UCHAR_MAX) { - unsigned char cc = i & 0xFF; - b = rb_str_new((const char *)&cc, 1); - } - else { - rb_raise(rb_eRangeError, - "integer %d too big to convert into `unsigned char'", i); - } - } - else if (RB_TYPE_P(b, T_BIGNUM)) { - rb_raise(rb_eRangeError, "bignum too big to convert into `unsigned char'"); - } - else { - SafeStringValue(b); + switch (TYPE(b)) { + case T_NIL: + return Qnil; + case T_FIXNUM: + case T_BIGNUM: ; + VALUE v = rb_int_modulo(b, INT2FIX(256)); + unsigned char c = NUM2INT(v) & 0xFF; + b = rb_str_new((const char *)&c, 1); + break; + default: + SafeStringValue(b); } io_ungetbyte(b, fptr); return Qnil; diff --git a/spec/ruby/core/io/ungetbyte_spec.rb b/spec/ruby/core/io/ungetbyte_spec.rb index 2639d24c45..2e51fb0298 100644 --- a/spec/ruby/core/io/ungetbyte_spec.rb +++ b/spec/ruby/core/io/ungetbyte_spec.rb @@ -49,7 +49,7 @@ describe "IO#ungetbyte" do end end - ruby_version_is '2.6' do + ruby_version_is '2.6'...'2.7' do it "is an RangeError if the integer is not in 8bit" do for i in [4095, 0x4f7574206f6620636861722072616e6765] do lambda { @io.ungetbyte(i) }.should raise_error(RangeError) @@ -57,6 +57,14 @@ describe "IO#ungetbyte" do end end + ruby_version_is '2.7' do + it "never raises RangeError" do + for i in [4095, 0x4f7574206f6620636861722072616e6765] do + lambda { @io.ungetbyte(i) }.should_not raise_error + end + end + end + it "raises an IOError if the IO is closed" do @io.close lambda { @io.ungetbyte(42) }.should raise_error(IOError) diff --git a/string.c b/string.c index fefcd05e64..2fbe36b1b8 100644 --- a/string.c +++ b/string.c @@ -5409,7 +5409,6 @@ static VALUE rb_str_setbyte(VALUE str, VALUE index, VALUE value) { long pos = NUM2LONG(index); - int byte = NUM2INT(value); long len = RSTRING_LEN(str); char *head, *left = 0; unsigned char *ptr; @@ -5420,10 +5419,10 @@ rb_str_setbyte(VALUE str, VALUE index, VALUE value) rb_raise(rb_eIndexError, "index %ld out of string", pos); if (pos < 0) pos += len; - if (byte < 0) - rb_raise(rb_eRangeError, "integer %d too small to convert into `unsigned char'", byte); - if (UCHAR_MAX < byte) - rb_raise(rb_eRangeError, "integer %d too big to convert into `unsigned char'", byte); + + VALUE v = rb_to_int(value); + VALUE w = rb_int_modulo(v, INT2FIX(256)); + unsigned char byte = NUM2INT(w) & 0xFF; if (!str_independent(str)) str_make_independent(str); diff --git a/test/ruby/test_m17n.rb b/test/ruby/test_m17n.rb index 0251315fcf..c1184c551f 100644 --- a/test/ruby/test_m17n.rb +++ b/test/ruby/test_m17n.rb @@ -1526,13 +1526,13 @@ class TestM17N < Test::Unit::TestCase def test_setbyte_range s = u("\xE3\x81\x82\xE3\x81\x84") - assert_raise(RangeError) { s.setbyte(0, -1) } - assert_nothing_raised { s.setbyte(0, 0x00) } - assert_nothing_raised { s.setbyte(0, 0x7F) } - assert_nothing_raised { s.setbyte(0, 0x80) } - assert_nothing_raised { s.setbyte(0, 0xff) } - assert_raise(RangeError) { s.setbyte(0, 0x100) } - assert_raise(RangeError) { s.setbyte(0, 0x4f7574206f6620636861722072616e6765) } + assert_nothing_raised { s.setbyte(0, -1) } + assert_nothing_raised { s.setbyte(0, 0x00) } + assert_nothing_raised { s.setbyte(0, 0x7F) } + assert_nothing_raised { s.setbyte(0, 0x80) } + assert_nothing_raised { s.setbyte(0, 0xff) } + assert_nothing_raised { s.setbyte(0, 0x100) } + assert_nothing_raised { s.setbyte(0, 0x4f7574206f6620636861722072616e6765) } end def test_compatible diff --git a/test/stringio/test_stringio.rb b/test/stringio/test_stringio.rb index b508d56310..6d1a6ea8d5 100644 --- a/test/stringio/test_stringio.rb +++ b/test/stringio/test_stringio.rb @@ -453,9 +453,9 @@ class TestStringIO < Test::Unit::TestCase assert_equal(0, t.pos) assert_equal("\u{30eb 30d3 30fc}\u7d05\u7389bar\n", s) - assert_raise(RangeError) {t.ungetbyte(-1)} - assert_raise(RangeError) {t.ungetbyte(256)} - assert_raise(RangeError) {t.ungetbyte(1<<64)} + assert_nothing_raised {t.ungetbyte(-1)} + assert_nothing_raised {t.ungetbyte(256)} + assert_nothing_raised {t.ungetbyte(1<<64)} end def test_ungetc