ruby-changes:56140
From: Nobuyoshi <ko1@a...>
Date: Wed, 19 Jun 2019 16:04:27 +0900 (JST)
Subject: [ruby-changes:56140] Nobuyoshi Nakada: e690df1f1e (trunk): Marshal distant past/future
https://git.ruby-lang.org/ruby.git/commit/?id=e690df1f1e From e690df1f1ef4f791295448f9192d6e027400ee72 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada <nobu@r...> Date: Thu, 27 Sep 2018 18:17:28 +0900 Subject: Marshal distant past/future [Feature #15160] diff --git a/marshal.c b/marshal.c index 73d36e2..8523051 100644 --- a/marshal.c +++ b/marshal.c @@ -256,6 +256,7 @@ class2path(VALUE klass) https://github.com/ruby/ruby/blob/trunk/marshal.c#L256 return path; } +int ruby_marshal_write_long(long x, char *buf); static void w_long(long, struct dump_arg*); static void w_encoding(VALUE encname, struct dump_call_arg *arg); static VALUE encoding_name(VALUE obj, struct dump_arg *arg); @@ -298,26 +299,36 @@ static void https://github.com/ruby/ruby/blob/trunk/marshal.c#L299 w_long(long x, struct dump_arg *arg) { char buf[sizeof(long)+1]; + int i = ruby_marshal_write_long(x, buf); + if (i < 0) { + rb_raise(rb_eTypeError, "long too big to dump"); + } + w_nbyte(buf, i, arg); +} + +int +ruby_marshal_write_long(long x, char *buf) +{ int i; #if SIZEOF_LONG > 4 if (!(RSHIFT(x, 31) == 0 || RSHIFT(x, 31) == -1)) { /* big long does not fit in 4 bytes */ - rb_raise(rb_eTypeError, "long too big to dump"); + return -1; } #endif if (x == 0) { - w_byte(0, arg); - return; + buf[0] = 0; + return 1; } if (0 < x && x < 123) { - w_byte((char)(x + 5), arg); - return; + buf[0] = (char)(x + 5); + return 1; } if (-124 < x && x < 0) { - w_byte((char)((x - 5)&0xff), arg); - return; + buf[0] = (char)((x - 5)&0xff); + return 1; } for (i=1;i<(int)sizeof(long)+1;i++) { buf[i] = (char)(x & 0xff); @@ -331,7 +342,7 @@ w_long(long x, struct dump_arg *arg) https://github.com/ruby/ruby/blob/trunk/marshal.c#L342 break; } } - w_nbyte(buf, i+1, arg); + return i+1; } #ifdef DBL_MANT_DIG @@ -1228,6 +1239,19 @@ r_long(struct load_arg *arg) https://github.com/ruby/ruby/blob/trunk/marshal.c#L1239 return x; } +long +ruby_marshal_read_long(const char **buf, long len) +{ + long x; + struct RString src; + struct load_arg arg; + memset(&arg, 0, sizeof(arg)); + arg.src = rb_setup_fake_str(&src, *buf, len, 0); + x = r_long(&arg); + *buf += arg.offset; + return x; +} + static VALUE r_bytes1(long len, struct load_arg *arg) { diff --git a/test/ruby/test_time.rb b/test/ruby/test_time.rb index 4c0a104..7269bdc 100644 --- a/test/ruby/test_time.rb +++ b/test/ruby/test_time.rb @@ -375,6 +375,16 @@ class TestTime < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_time.rb#L375 end end + def test_marshal_distant_past + assert_marshal_roundtrip(Time.utc(1890, 1, 1)) + assert_marshal_roundtrip(Time.utc(-4.5e9, 1, 1)) + end + + def test_marshal_distant_future + assert_marshal_roundtrip(Time.utc(30000, 1, 1)) + assert_marshal_roundtrip(Time.utc(5.67e9, 4, 8)) + end + def test_at3 t2000 = get_t2000 assert_equal(t2000, Time.at(t2000)) diff --git a/time.c b/time.c index b569bf6..3ed694f 100644 --- a/time.c +++ b/time.c @@ -5005,6 +5005,8 @@ time_strftime(VALUE time, VALUE format) https://github.com/ruby/ruby/blob/trunk/time.c#L5005 } } +int ruby_marshal_write_long(long x, char *buf); + /* :nodoc: */ static VALUE time_mdump(VALUE time) @@ -5020,19 +5022,33 @@ time_mdump(VALUE time) https://github.com/ruby/ruby/blob/trunk/time.c#L5022 long usec, nsec; VALUE subsecx, nano, subnano, v, zone; + VALUE year_extend = Qnil; + const int max_year = 1900+0xffff; + GetTimeval(time, tobj); gmtimew(tobj->timew, &vtm); if (FIXNUM_P(vtm.year)) { year = FIX2LONG(vtm.year); - if (year < 1900 || 1900+0xffff < year) - rb_raise(rb_eArgError, "year too %s to marshal: %ld UTC", - (year < 1900 ? "small" : "big"), year); + if (year > max_year) { + year_extend = INT2FIX(year - max_year); + year = max_year; + } + else if (year < 1900) { + year_extend = LONG2NUM(1900 - year); + year = 1900; + } } else { - rb_raise(rb_eArgError, "year too %s to marshal: %"PRIsVALUE" UTC", - (le(vtm.year, INT2FIX(1900)) ? "small" : "big"), vtm.year); + if (rb_int_positive_p(vtm.year)) { + year_extend = rb_int_minus(vtm.year, INT2FIX(max_year)); + year = max_year; + } + else { + year_extend = rb_int_minus(INT2FIX(1900), vtm.year); + year = 1900; + } } subsecx = vtm.subsecx; @@ -5065,6 +5081,26 @@ time_mdump(VALUE time) https://github.com/ruby/ruby/blob/trunk/time.c#L5081 } str = rb_str_new(buf, 8); + if (!NIL_P(year_extend)) { + /* + * Append extended year distance from 1900..(1900+0xffff). In + * each cases, there is no sign as the value is positive. The + * format is length (marshaled long) + little endian packed + * binary (like as Fixnum and Bignum). + */ + size_t ysize = rb_absint_size(year_extend, NULL); + char *p; + if (ysize > LONG_MAX || + (i = ruby_marshal_write_long((long)ysize, buf)) < 0) { + rb_raise(rb_eArgError, "year too %s to marshal: %"PRIsVALUE" UTC", + (year == 1900 ? "small" : "big"), vtm.year); + } + rb_str_resize(str, sizeof(buf) + i + ysize); + p = RSTRING_PTR(str) + sizeof(buf); + memcpy(p, buf, i); + p += i; + rb_integer_pack(year_extend, p, ysize, 1, 0, INTEGER_PACK_LITTLE_ENDIAN); + } rb_copy_generic_ivar(str, time); if (!rb_equal(nano, INT2FIX(0))) { if (RB_TYPE_P(nano, T_RATIONAL)) { @@ -5142,6 +5178,8 @@ mload_zone(VALUE time, VALUE zone) https://github.com/ruby/ruby/blob/trunk/time.c#L5178 return z; } +long ruby_marshal_read_long(const char **buf, long len); + /* :nodoc: */ static VALUE time_mload(VALUE time, VALUE str) @@ -5154,7 +5192,7 @@ time_mload(VALUE time, VALUE str) https://github.com/ruby/ruby/blob/trunk/time.c#L5192 struct vtm vtm; int i, gmt; long nsec; - VALUE submicro, nano_num, nano_den, offset, zone; + VALUE submicro, nano_num, nano_den, offset, zone, year; wideval_t timew; time_modify(time); @@ -5170,6 +5208,7 @@ time_mload(VALUE time, VALUE str) https://github.com/ruby/ruby/blob/trunk/time.c#L5208 get_attr(submicro, {}); get_attr(offset, (offset = rb_rescue(validate_utc_offset, offset, NULL, Qnil))); get_attr(zone, (zone = rb_rescue(validate_zone_name, zone, NULL, Qnil))); + get_attr(year, {}); #undef get_attr @@ -5177,7 +5216,8 @@ time_mload(VALUE time, VALUE str) https://github.com/ruby/ruby/blob/trunk/time.c#L5216 StringValue(str); buf = (unsigned char *)RSTRING_PTR(str); - if (RSTRING_LEN(str) != 8) { + if (RSTRING_LEN(str) < 8) { + invalid_format: rb_raise(rb_eTypeError, "marshaled time format differ"); } @@ -5201,7 +5241,26 @@ time_mload(VALUE time, VALUE str) https://github.com/ruby/ruby/blob/trunk/time.c#L5241 p &= ~(1UL<<31); gmt = (int)((p >> 30) & 0x1); - vtm.year = INT2FIX(((int)(p >> 14) & 0xffff) + 1900); + if (NIL_P(year)) { + year = INT2FIX(((int)(p >> 14) & 0xffff) + 1900); + } + if (RSTRING_LEN(str) > 8) { + long len = RSTRING_LEN(str) - 8; + long ysize = 0; + VALUE year_extend; + const char *ybuf = (const char *)(buf += 8); + ysize = ruby_marshal_read_long(&ybuf, len); + len -= ybuf - (const char *)buf; + if (ysize < 0 || ysize > len) goto invalid_format; + year_extend = rb_integer_unpack(ybuf, ysize, 1, 0, INTEGER_PACK_LITTLE_ENDIAN); + if (year == INT2FIX(1900)) { + year = rb_int_minus(year, year_extend); + } + else { + year = rb_int_plus(year, year_extend); + } + } + vtm.year = year; vtm.mon = ((int)(p >> 10) & 0xf) + 1; vtm.mday = (int)(p >> 5) & 0x1f; vtm.hour = (int) p & 0x1f; -- cgit v0.10.2 -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/