Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[GR-44710] Fix Range#bsearch validation #3386

Merged
merged 2 commits into from
Jan 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading