From b402cc71616169bab03fb856e73a7d6519330ca3 Mon Sep 17 00:00:00 2001 From: nobu Date: Tue, 10 May 2016 05:57:11 +0000 Subject: random.c: use bytes * random.c (obj_random_bytes): base on bytes method instead of rand method, not to call toplevel rand method. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@54968 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- ChangeLog | 5 +++ lib/securerandom.rb | 4 +++ random.c | 87 +++++++++++++++++++++++++++++++---------------- test/ruby/test_rand.rb | 11 ++++++ test/test_securerandom.rb | 14 ++++++++ 5 files changed, 92 insertions(+), 29 deletions(-) diff --git a/ChangeLog b/ChangeLog index 2a00110ac6..c0e2de06cb 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +Tue May 10 14:57:09 2016 Nobuyoshi Nakada + + * random.c (obj_random_bytes): base on bytes method instead of + rand method, not to call toplevel rand method. + Tue May 10 13:07:28 2016 NARUSE, Yui * configure.in (-fexcess-precision=standard): before r54895 -std=c99 diff --git a/lib/securerandom.rb b/lib/securerandom.rb index d8afc7d01b..9201c9337a 100644 --- a/lib/securerandom.rb +++ b/lib/securerandom.rb @@ -75,6 +75,10 @@ module SecureRandom ret end end + + class << self + alias bytes gen_random + end end module Random::Formatter diff --git a/random.c b/random.c index 6e6dae700a..21d1ea0840 100644 --- a/random.c +++ b/random.c @@ -898,17 +898,20 @@ rb_genrand_ulong_limited(unsigned long limit) return limited_rand(default_mt(), limit); } -static unsigned int -obj_random_int32(VALUE obj) -{ -#if SIZEOF_LONG * CHAR_BIT > 32 - VALUE lim = ULONG2NUM(0x100000000UL); -#elif defined HAVE_LONG_LONG - VALUE lim = ULL2NUM((LONG_LONG)0xffffffff+1); -#else - VALUE lim = rb_big_plus(ULONG2NUM(0xffffffff), INT2FIX(1)); -#endif - return (unsigned int)NUM2ULONG(rb_funcall2(obj, id_rand, 1, &lim)); +static VALUE +obj_random_bytes(VALUE obj, void *p, long n) +{ + VALUE len = LONG2NUM(n); + VALUE v = rb_funcallv_public(obj, id_bytes, 1, &len); + long l; + Check_Type(v, T_STRING); + l = RSTRING_LEN(v); + if (l < n) + rb_raise(rb_eRangeError, "random data too short %ld", l); + else if (l > n) + rb_raise(rb_eRangeError, "random data too long %ld", l); + if (p) memcpy(p, RSTRING_PTR(v), n); + return v; } static unsigned int @@ -922,7 +925,9 @@ rb_random_int32(VALUE obj) { rb_random_t *rnd = try_get_rnd(obj); if (!rnd) { - return obj_random_int32(obj); + uint32_t x; + obj_random_bytes(obj, &x, sizeof(x)); + return (unsigned int)x; } return random_int32(rnd); } @@ -933,8 +938,10 @@ random_real(VALUE obj, rb_random_t *rnd, int excl) uint32_t a, b; if (!rnd) { - a = obj_random_int32(obj); - b = obj_random_int32(obj); + uint32_t x[2] = {0, 0}; + obj_random_bytes(obj, x, sizeof(x)); + a = x[0]; + b = x[1]; } else { a = random_int32(rnd); @@ -982,6 +989,22 @@ ulong_to_num_plus_1(unsigned long n) static unsigned long random_ulong_limited(VALUE obj, rb_random_t *rnd, unsigned long limit) { + if (!limit) return 0; + if (!rnd) { + unsigned long val, mask = make_mask(limit); + do { + obj_random_bytes(obj, &val, sizeof(unsigned long)); + val &= mask; + } while (limit < val); + return val; + } + return limited_rand(&rnd->mt, limit); +} + +unsigned long +rb_random_ulong_limited(VALUE obj, unsigned long limit) +{ + rb_random_t *rnd = try_get_rnd(obj); if (!rnd) { VALUE lim = ulong_to_num_plus_1(limit); VALUE v = rb_to_int(rb_funcall2(obj, id_rand, 1, &lim)); @@ -1001,25 +1024,31 @@ static VALUE random_ulong_limited_big(VALUE obj, rb_random_t *rnd, VALUE vmax) { if (!rnd) { - VALUE lim = rb_big_plus(vmax, INT2FIX(1)); - VALUE v = rb_to_int(rb_funcall2(obj, id_rand, 1, &lim)); - if (rb_num_negative_p(v)) { - rb_raise(rb_eRangeError, "random number too small %"PRIsVALUE, v); - } - if (FIX2LONG(rb_big_cmp(vmax, v)) < 0) { - rb_raise(rb_eRangeError, "random number too big %"PRIsVALUE, v); + VALUE v, vtmp; + size_t i, nlz, len = rb_absint_numwords(vmax, 32, &nlz); + uint32_t *tmp = ALLOCV_N(uint32_t, vtmp, len * 2); + uint32_t mask = (uint32_t)~0 >> nlz; + uint32_t *lim_array = tmp; + uint32_t *rnd_array = tmp + len; + int flag = INTEGER_PACK_MSWORD_FIRST|INTEGER_PACK_NATIVE_BYTE_ORDER; + rb_integer_pack(vmax, lim_array, len, sizeof(uint32_t), 0, flag); + + retry: + obj_random_bytes(obj, rnd_array, len * sizeof(uint32_t)); + rnd_array[0] &= mask; + for (i = 0; i < len; ++i) { + if (lim_array[i] < rnd_array[i]) + goto retry; + if (rnd_array[i] < lim_array[i]) + break; } + v = rb_integer_unpack(rnd_array, len, sizeof(uint32_t), 0, flag); + ALLOCV_END(vtmp); return v; } return limited_big_rand(&rnd->mt, vmax); } -unsigned long -rb_random_ulong_limited(VALUE obj, unsigned long limit) -{ - return random_ulong_limited(obj, try_get_rnd(obj), limit); -} - static VALUE genrand_bytes(rb_random_t *rnd, long n); /* @@ -1068,8 +1097,7 @@ rb_random_bytes(VALUE obj, long n) { rb_random_t *rnd = try_get_rnd(obj); if (!rnd) { - VALUE len = LONG2NUM(n); - return rb_funcall2(obj, id_bytes, 1, &len); + return obj_random_bytes(obj, NULL, n); } return genrand_bytes(rnd, n); } @@ -1601,6 +1629,7 @@ InitVM_Random(void) VALUE m = rb_define_module_under(rb_cRandom, "Formatter"); rb_include_module(rb_cRandom, m); rb_define_method(m, "random_number", rand_random_number, -1); + rb_define_method(m, "rand", rand_random_number, -1); } } diff --git a/test/ruby/test_rand.rb b/test/ruby/test_rand.rb index d4258a7f1c..c24d6cd1ff 100644 --- a/test/ruby/test_rand.rb +++ b/test/ruby/test_rand.rb @@ -526,6 +526,17 @@ END assert_equal(2, gen.limit, bug7935) end + def test_random_ulong_limited_no_rand + c = Class.new do + undef rand + def bytes(n) + "\0"*n + end + end + gen = c.new.extend(Random::Formatter) + assert_equal(1, [1, 2].sample(random: gen)) + end + def test_default_seed assert_separately([], <<-End) seed = Random::DEFAULT::seed diff --git a/test/test_securerandom.rb b/test/test_securerandom.rb index 515d05e6b2..cd5cb1dee2 100644 --- a/test/test_securerandom.rb +++ b/test/test_securerandom.rb @@ -157,6 +157,20 @@ end end end + def test_s_random_number_not_default + msg = "SecureRandom#random_number should not be affected by srand" + seed = srand(0) + x = @it.random_number(1000) + 10.times do|i| + srand(0) + return unless @it.random_number(1000) == x + end + srand(0) + assert_not_equal(x, @it.random_number(1000), msg) + ensure + srand(seed) if seed + end + def test_uuid uuid = @it.uuid assert_equal(36, uuid.size) -- cgit v1.2.3