Skip to content

Commit

Permalink
[GR-44710] Fix Range#bsearch validation
Browse files Browse the repository at this point in the history
PullRequest: truffleruby/3962
  • Loading branch information
andrykonchin committed Jan 15, 2024
2 parents 294a695 + 3d43e58 commit c68549d
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 13 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Bug fixes:
* Run context cleanup such as showing the output of tools when `SignalException` and `Interrupt` escape (@eregon).
* Handle a new variable inside the `case` target expression correctly (#3377, @eregon).
* The arguments of `Thread.new(*args, &block)` need to be marked as shared between multiple threads (#3179, @eregon).
* Fix `Range#bsearch` and raise `TypeError` when range boundaries are non-numeric and block not passed (@andrykonchin).

Compatibility:

Expand Down
44 changes: 37 additions & 7 deletions spec/ruby/core/range/bsearch_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,30 @@
it_behaves_like :enumeratorized_with_unknown_size, :bsearch, (1..3)

it "raises a TypeError if the block returns an Object" do
-> { (0..1).bsearch { Object.new } }.should raise_error(TypeError)
-> { (0..1).bsearch { Object.new } }.should raise_error(TypeError, "wrong argument type Object (must be numeric, true, false or nil)")
end

it "raises a TypeError if the block returns a String" do
-> { (0..1).bsearch { "1" } }.should raise_error(TypeError)
it "raises a TypeError if the block returns a String and boundaries are Integer values" do
-> { (0..1).bsearch { "1" } }.should raise_error(TypeError, "wrong argument type String (must be numeric, true, false or nil)")
end

it "raises a TypeError if the block returns a String and boundaries are Float values" do
-> { (0.0..1.0).bsearch { "1" } }.should raise_error(TypeError, "wrong argument type String (must be numeric, true, false or nil)")
end

it "raises a TypeError if the Range has Object values" do
value = mock("range bsearch")
r = Range.new value, value

-> { r.bsearch { true } }.should raise_error(TypeError)
-> { r.bsearch { true } }.should raise_error(TypeError, "can't do binary search for MockObject")
end

it "raises a TypeError if the Range has String values" do
-> { ("a".."e").bsearch { true } }.should raise_error(TypeError)
-> { ("a".."e").bsearch { true } }.should raise_error(TypeError, "can't do binary search for String")
end

it "raises TypeError when non-Numeric begin/end and block not passed" do
-> { ("a".."e").bsearch }.should raise_error(TypeError, "can't do binary search for String")
end

context "with Integer values" do
Expand Down Expand Up @@ -94,6 +102,10 @@
(4..2).bsearch { 0 }.should == nil
(4..2).bsearch { -1 }.should == nil
end

it "returns enumerator when block not passed" do
(0...3).bsearch.kind_of?(Enumerator).should == true
end
end

context "with Float values" do
Expand Down Expand Up @@ -156,7 +168,6 @@

it "returns nil if the block returns greater than zero for every element" do
(0.3..3.0).bsearch { |x| x <=> -1 }.should be_nil

end

it "returns nil if the block never returns zero" do
Expand Down Expand Up @@ -213,6 +224,10 @@
(0...inf).bsearch { |x| x >= Float::MAX ? 0 : 1 }.should == Float::MAX
end
end

it "returns enumerator when block not passed" do
(0.1...2.3).bsearch.kind_of?(Enumerator).should == true
end
end

context "with endless ranges and Integer values" do
Expand Down Expand Up @@ -250,6 +265,10 @@
[1, 2, 3].should include(result)
end
end

it "returns enumerator when block not passed" do
eval("(-2..)").bsearch.kind_of?(Enumerator).should == true
end
end

context "with endless ranges and Float values" do
Expand Down Expand Up @@ -327,8 +346,11 @@
eval("(0.0...)").bsearch { 0 }.should != inf
end
end
end

it "returns enumerator when block not passed" do
eval("(0.1..)").bsearch.kind_of?(Enumerator).should == true
end
end

context "with beginless ranges and Integer values" do
context "with a block returning true or false" do
Expand Down Expand Up @@ -361,6 +383,10 @@
[1, 2, 3].should include(result)
end
end

it "returns enumerator when block not passed" do
(..10).bsearch.kind_of?(Enumerator).should == true
end
end

context "with beginless ranges and Float values" do
Expand Down Expand Up @@ -432,5 +458,9 @@
(...inf).bsearch { |x| 3 - x }.should == 3
end
end

it "returns enumerator when block not passed" do
(..-0.1).bsearch.kind_of?(Enumerator).should == true
end
end
end
18 changes: 12 additions & 6 deletions src/main/ruby/truffleruby/core/range.rb
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,6 @@ def eql?(other)
end

def bsearch(&block)
return to_enum :bsearch unless block_given?

start = self.begin
stop = self.end

Expand All @@ -79,12 +77,12 @@ def bsearch(&block)
elsif Primitive.is_a?(stop, Integer)
bsearch_integer(&block)
else
raise TypeError, "bsearch is not available for #{Primitive.class(stop)}"
raise TypeError, "can't do binary search for #{Primitive.class(stop)}"
end
elsif Primitive.nil?(start) && Primitive.is_a?(stop, Integer)
bsearch_beginless(&block)
else
raise TypeError, "bsearch is not available for #{Primitive.class(start)}"
raise TypeError, "can't do binary search for #{Primitive.class(start)}"
end
end

Expand All @@ -99,6 +97,8 @@ def bsearch(&block)
end

private def bsearch_float(&block)
return to_enum :bsearch_float unless block_given?

normalized_begin = Primitive.nil?(self.begin) ? -Float::INFINITY : self.begin.to_f
normalized_end = Primitive.nil?(self.end) ? Float::INFINITY : self.end.to_f
normalized_end = normalized_end.prev_float if self.exclude_end?
Expand Down Expand Up @@ -165,7 +165,7 @@ def bsearch(&block)
when false, nil
min = mid
else
raise TypeError, 'Range#bsearch block must return Numeric or boolean'
raise TypeError, "wrong argument type #{Primitive.class(result)} (must be numeric, true, false or nil)"
end
end

Expand All @@ -187,6 +187,8 @@ def bsearch(&block)
end

private def bsearch_endless(&block)
return to_enum :bsearch_endless unless block_given?

min = self.begin
cur = min
diff = 1
Expand All @@ -207,6 +209,8 @@ def bsearch(&block)
end

private def bsearch_beginless(&block)
return to_enum :bsearch_beginless unless block_given?

max = self.end
cur = max
diff = 1
Expand All @@ -227,6 +231,8 @@ def bsearch(&block)
end

private def bsearch_integer(&block)
return to_enum :bsearch_integer unless block_given?

min = self.begin
max = self.end
max -= 1 if Primitive.is_a?(max, Integer) and exclude_end?
Expand Down Expand Up @@ -255,7 +261,7 @@ def bsearch(&block)
when false, nil
min = mid + 1
else
raise TypeError, 'Range#bsearch block must return Numeric or boolean'
raise TypeError, "wrong argument type #{Primitive.class(result)} (must be numeric, true, false or nil)"
end
end

Expand Down

0 comments on commit c68549d

Please sign in to comment.