summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNobuyoshi Nakada <nobu@ruby-lang.org>2018-09-27 18:17:28 +0900
committerNobuyoshi Nakada <nobu@ruby-lang.org>2019-06-19 15:26:53 +0900
commite690df1f1ef4f791295448f9192d6e027400ee72 (patch)
treebd0ab0a070f7e57744761a74d4f8eda35fa01fc8
parent8797f48373dcfa3ff8e748667732dea8aea4347e (diff)
Marshal distant past/future
[Feature #15160]
-rw-r--r--marshal.c40
-rw-r--r--test/ruby/test_time.rb10
-rw-r--r--time.c75
3 files changed, 109 insertions, 16 deletions
diff --git a/marshal.c b/marshal.c
index 73d36e2a32..852305110e 100644
--- a/marshal.c
+++ b/marshal.c
@@ -256,6 +256,7 @@ class2path(VALUE klass)
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
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)
break;
}
}
- w_nbyte(buf, i+1, arg);
+ return i+1;
}
#ifdef DBL_MANT_DIG
@@ -1228,6 +1239,19 @@ r_long(struct load_arg *arg)
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 4c0a104976..7269bdc3ff 100644
--- a/test/ruby/test_time.rb
+++ b/test/ruby/test_time.rb
@@ -375,6 +375,16 @@ class TestTime < Test::Unit::TestCase
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 b569bf6bc1..3ed694fe23 100644
--- a/time.c
+++ b/time.c
@@ -5005,6 +5005,8 @@ time_strftime(VALUE time, VALUE format)
}
}
+int ruby_marshal_write_long(long x, char *buf);
+
/* :nodoc: */
static VALUE
time_mdump(VALUE time)
@@ -5020,19 +5022,33 @@ time_mdump(VALUE time)
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)
}
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)
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)
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)
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)
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)
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;