mirror of
https://github.com/mruby/mruby
synced 2026-06-08 16:11:16 +00:00
9982bc1223
rand_range_int() computed `end - begin + 1` in mrb_int before checking for a reversed or empty range. For extreme bounds the subtraction itself overflowed mrb_int (UndefinedBehaviorSanitizer signed-integer-overflow), and the wraparound could turn a reversed range into a positive span, defeating the guard. Reject reversed or empty ranges before subtracting, and compute the candidate count in unsigned arithmetic, so no signed overflow is possible. A new unsigned uniform sampler draws from the full mrb_int domain, so valid ranges whose width exceeds MRB_INT_MAX now return a uniform in-range value instead of nil, matching CRuby. Co-authored-by: Claude <noreply@anthropic.com>
182 lines
5.0 KiB
Ruby
182 lines
5.0 KiB
Ruby
##
|
|
# Random Test
|
|
|
|
assert("Random.new") do
|
|
r1 = Random.new(123)
|
|
r2 = Random.new(123)
|
|
r3 = Random.new(124)
|
|
assert_equal(r1.rand, r2.rand)
|
|
assert_not_equal(r1.rand, r3.rand)
|
|
end
|
|
|
|
assert("Kernel#srand") do
|
|
srand(234)
|
|
r1 = rand
|
|
srand(234)
|
|
r2 = rand
|
|
srand(235)
|
|
r3 = rand
|
|
assert_equal(r1, r2)
|
|
assert_not_equal(r1, r3)
|
|
end
|
|
|
|
assert("Random.srand") do
|
|
Random.srand(345)
|
|
r1 = rand
|
|
srand(345)
|
|
r2 = Random.rand
|
|
Random.srand(346)
|
|
r3 = rand
|
|
assert_equal(r1, r2)
|
|
assert_not_equal(r1, r3)
|
|
end
|
|
|
|
assert("Random#bytes") do
|
|
r = Random.new(10)
|
|
num = 11
|
|
a = r.bytes(num)
|
|
assert_kind_of String, a
|
|
assert_equal num, a.bytesize
|
|
b = r.bytes(num)
|
|
assert_kind_of String, b
|
|
assert_equal num, b.bytesize
|
|
assert_not_equal a, b
|
|
b = r.bytes(num / 2)
|
|
assert_equal num / 2, b.bytesize
|
|
end
|
|
|
|
assert("return class of Kernel#rand") do
|
|
assert_kind_of(Integer, rand(3))
|
|
assert_kind_of(Integer, rand(1.5))
|
|
skip unless Object.const_defined?(:Float)
|
|
assert_kind_of(Float, rand)
|
|
assert_kind_of(Float, rand(0.5))
|
|
end
|
|
|
|
assert("Array#shuffle") do
|
|
orig = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
|
ary = orig.dup
|
|
shuffled = ary.shuffle
|
|
assert_equal(orig, ary)
|
|
assert_not_equal(ary, shuffled)
|
|
assert_equal(orig, shuffled.sort)
|
|
end
|
|
|
|
assert('Array#shuffle!') do
|
|
orig = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
|
ary = orig.dup
|
|
assert_same(ary, ary.shuffle!)
|
|
assert_not_equal(orig, ary)
|
|
assert_equal(orig, ary.sort)
|
|
end
|
|
|
|
assert("Array#shuffle(random)") do
|
|
assert_raise(TypeError) do
|
|
# this will cause an exception due to the wrong argument
|
|
[1, 2].shuffle(random: "Not a Random instance")
|
|
end
|
|
|
|
# verify that the same seed causes the same results
|
|
ary = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
|
shuffled1 = ary.shuffle(random: Random.new(345))
|
|
shuffled2 = ary.shuffle(random: Random.new(345))
|
|
shuffled3 = ary.shuffle(random: Random.new(346))
|
|
assert_equal(shuffled1, shuffled2)
|
|
assert_not_equal(shuffled1, shuffled3)
|
|
end
|
|
|
|
assert('Array#shuffle!(random)') do
|
|
assert_raise(TypeError) do
|
|
# this will cause an exception due to the wrong argument
|
|
[1, 2].shuffle!(random: "Not a Random instance")
|
|
end
|
|
|
|
# verify that the same seed causes the same results
|
|
ary1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
|
ary1.shuffle!(random: Random.new(345))
|
|
ary2 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
|
ary2.shuffle!(random: Random.new(345))
|
|
ary3 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
|
ary3.shuffle!(random: Random.new(346))
|
|
assert_equal(ary1, ary2)
|
|
assert_not_equal(ary1, ary3)
|
|
end
|
|
|
|
assert('Array#sample') do
|
|
100.times do
|
|
assert_include([0, 1, 2], [2, 1, 0].sample)
|
|
[2, 1, 0].sample(2).each { |sample| assert_include([0, 1, 2], sample) }
|
|
h = {}
|
|
(1..10).to_a.sample(7).each do |sample|
|
|
assert_not_include(h, sample)
|
|
h[sample] = true
|
|
end
|
|
end
|
|
|
|
assert_nil([].sample)
|
|
assert_equal([], [].sample(1))
|
|
assert_equal([], [2, 1].sample(0))
|
|
assert_raise(TypeError) { [2, 1].sample(true) }
|
|
assert_raise(ArgumentError) { [2, 1].sample(-1) }
|
|
end
|
|
|
|
assert('Array#sample(random)') do
|
|
assert_raise(TypeError) do
|
|
# this will cause an exception due to the wrong argument
|
|
[1, 2].sample(2, random: "Not a Random instance")
|
|
end
|
|
|
|
# verify that the same seed causes the same results
|
|
ary = (1..10).to_a
|
|
srand(15)
|
|
samples1 = ary.sample(4)
|
|
samples2 = ary.sample(4, random: Random.new(15))
|
|
samples3 = ary.sample(4, random: Random.new(16))
|
|
assert_equal(samples1, samples2)
|
|
assert_not_equal(samples1, samples3)
|
|
end
|
|
|
|
assert("Kernel#rand()") do
|
|
100.times {
|
|
assert_include(0.0..1.0, rand)
|
|
assert_include(0...100, rand(0...100))
|
|
assert_include(0...100, rand(100))
|
|
}
|
|
|
|
assert_equal(rand(0...0), nil)
|
|
assert_equal(rand(0.0...0), nil)
|
|
assert_equal(rand(0...0.0), nil)
|
|
assert_equal(rand(0.0...0.0), nil)
|
|
assert_equal(rand(1..0), nil)
|
|
end
|
|
|
|
assert("Kernel#rand integer range overflow") do
|
|
# Width-independent guard behaviour for reversed/empty/single ranges.
|
|
assert_equal(5, rand(5..5)) # single-element inclusive range
|
|
assert_nil(rand(5...5)) # empty exclusive range
|
|
assert_nil(rand(10..3)) # reversed inclusive range
|
|
assert_nil(rand(10...3)) # reversed exclusive range
|
|
100.times { assert_include(3..7, rand(3..7)) }
|
|
|
|
# Wide fixnum ranges whose span exceeds the mrb_int range only exist on
|
|
# 64-bit mrb_int builds. Such bounds used to overflow `end - begin` in C
|
|
# (UndefinedBehaviorSanitizer signed-integer-overflow), found via a
|
|
# minimized mruby_fuzzer testcase. On 32-bit mrb_int (or when bigint
|
|
# promotes the bounds) these take a different path, so probe the
|
|
# integer-range path first and skip if absent.
|
|
hi = ((1 << 62) + (1 << 61)) rescue nil
|
|
if hi && (Integer === (rand(-hi..hi) rescue nil))
|
|
lo = -hi
|
|
# reversed wide range -> nil; old code overflowed `end - begin`.
|
|
assert_nil(rand(hi..lo))
|
|
assert_nil(rand(hi...lo))
|
|
# valid wide range -> in-range Integer; old code overflowed and wrongly
|
|
# returned nil instead of a uniform value.
|
|
100.times do
|
|
v = rand(lo..hi)
|
|
assert_kind_of(Integer, v)
|
|
assert_true(lo <= v && v <= hi)
|
|
end
|
|
end
|
|
end
|