Skip to content

Commit b4fe248

Browse files
committed
[GR-18163] Fix IO#read_nonblock and preserve buffer's encoding
PullRequest: truffleruby/4234
2 parents 6ddaef5 + df33fc1 commit b4fe248

24 files changed

+166
-31
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Bug fixes:
1010
* Fix `rb_global_variable()` for `Float` and bignum values during the `Init_` function (#3478, @eregon).
1111
* Fix `rb_gc_register_mark_object()` for `Float` and bignum values (#3502, @eregon, @andrykonchin).
1212
* Fix parsing literal floats when the locale does not use `.` for the decimal separator (e.g. `LANG=fr_FR.UTF-8`) (#3512, @eregon).
13+
* Fix `IO#{read_nonblock,readpartial,sysread}`, `BasicSocket#{recv,recv_nonblock}`, `{Socket,UDPSocket}#recvfrom_nonblock`, `UnixSocket#recvfrom` and preserve a provided buffer's encoding (#3506, @andrykonchyn).
1314

1415
Compatibility:
1516

lib/truffle/socket/basic_socket.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,11 @@ def send(message, flags, dest_sockaddr = nil)
163163
end
164164

165165
str = buf.read_string(n_bytes)
166-
buffer ? buffer.replace(str) : str
166+
if buffer
167+
buffer.replace str.force_encoding(buffer.encoding)
168+
else
169+
str
170+
end
167171
end
168172
end
169173

lib/truffle/socket/ip_socket.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,9 @@ def peeraddr(reverse_lookup = nil)
6161
end
6262
end
6363

64-
message = buffer.replace(message) if buffer
64+
if buffer
65+
message = buffer.replace message.force_encoding(buffer.encoding)
66+
end
6567

6668
[message, [aname, addr.ip_port, hostname, addr.ip_address]]
6769
end

lib/truffle/socket/socket.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,9 @@ def recvfrom(bytes, flags = 0)
384384
if message == :wait_readable
385385
message
386386
else
387-
message = buffer.replace(message) if buffer
387+
if buffer
388+
message = buffer.replace message.force_encoding(buffer.encoding)
389+
end
388390
[message, addr]
389391
end
390392
end

lib/truffle/socket/unix_socket.rb

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,18 @@ def initialize(path)
5858
Errno.handle('connect(2)') if status < 0
5959
end
6060

61-
def recvfrom(bytes_read, flags = 0)
61+
def recvfrom(bytes_read, flags = 0, buffer = nil)
6262
Truffle::Socket::Foreign.memory_pointer(bytes_read) do |buf|
6363
n_bytes = Truffle::Socket::Foreign.recvfrom(Primitive.io_fd(self), buf, bytes_read, flags, nil, nil)
6464
Errno.handle('recvfrom(2)') if n_bytes == -1
65-
return [buf.read_string(n_bytes), ['AF_UNIX', '']]
65+
66+
message = buf.read_string(n_bytes)
67+
68+
if buffer
69+
message = buffer.replace message.force_encoding(buffer.encoding)
70+
end
71+
72+
return [message, ['AF_UNIX', '']]
6673
end
6774
end
6875

lib/truffle/stringio.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,8 @@ def read(length = nil, buffer = nil)
450450
pos = d.pos
451451
string = d.string
452452

453+
# intentionally don't preserve buffer's encoding
454+
# see https://bugs.ruby-lang.org/issues/20418
453455
if length
454456
length = Truffle::Type.coerce_to length, Integer, :to_int
455457
raise ArgumentError if length < 0

spec/ruby/core/io/pread_spec.rb

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222

2323
it "accepts a length, an offset, and an output buffer" do
2424
buffer = +"foo"
25-
@file.pread(3, 4, buffer)
25+
@file.pread(3, 4, buffer).should.equal?(buffer)
2626
buffer.should == "567"
2727
end
2828

@@ -38,6 +38,13 @@
3838
buffer.should == "12345"
3939
end
4040

41+
it "preserves the encoding of the given buffer" do
42+
buffer = ''.encode(Encoding::ISO_8859_1)
43+
@file.pread(10, 0, buffer)
44+
45+
buffer.encoding.should == Encoding::ISO_8859_1
46+
end
47+
4148
it "does not advance the file pointer" do
4249
@file.pread(4, 0).should == "1234"
4350
@file.read.should == "1234567890"

spec/ruby/core/io/read_spec.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,21 @@
376376
buf.should == @contents[0..4]
377377
end
378378

379+
it "preserves the encoding of the given buffer" do
380+
buffer = ''.encode(Encoding::ISO_8859_1)
381+
@io.read(10, buffer)
382+
383+
buffer.encoding.should == Encoding::ISO_8859_1
384+
end
385+
386+
# https://bugs.ruby-lang.org/issues/20416
387+
it "does not preserve the encoding of the given buffer when max length is not provided" do
388+
buffer = ''.encode(Encoding::ISO_8859_1)
389+
@io.read(nil, buffer)
390+
391+
buffer.encoding.should_not == Encoding::ISO_8859_1
392+
end
393+
379394
it "returns the given buffer" do
380395
buf = +""
381396

spec/ruby/core/io/readpartial_spec.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
buffer = +"existing content"
6363
@wr.write("hello world")
6464
@wr.close
65-
@rd.readpartial(11, buffer)
65+
@rd.readpartial(11, buffer).should.equal?(buffer)
6666
buffer.should == "hello world"
6767
end
6868

@@ -106,6 +106,7 @@
106106
@wr.write("abc")
107107
@wr.close
108108
@rd.readpartial(10, buffer)
109+
109110
buffer.encoding.should == Encoding::ISO_8859_1
110111
end
111112
end

spec/ruby/core/io/sysread_spec.rb

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@
9797

9898
it "discards the existing buffer content upon successful read" do
9999
buffer = +"existing content"
100-
@file.sysread(11, buffer)
100+
@file.sysread(11, buffer).should.equal?(buffer)
101101
buffer.should == "01234567890"
102102
end
103103

@@ -107,6 +107,13 @@
107107
-> { @file.sysread(1, buffer) }.should raise_error(EOFError)
108108
buffer.should be_empty
109109
end
110+
111+
it "preserves the encoding of the given buffer" do
112+
buffer = ''.encode(Encoding::ISO_8859_1)
113+
string = @file.sysread(10, buffer)
114+
115+
buffer.encoding.should == Encoding::ISO_8859_1
116+
end
110117
end
111118

112119
describe "IO#sysread" do

spec/ruby/library/socket/basicsocket/recv_nonblock_spec.rb

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,19 @@
5252
@s2.send("data", 0, @s1.getsockname)
5353
IO.select([@s1], nil, nil, 2)
5454

55-
buf = +"foo"
56-
@s1.recv_nonblock(5, 0, buf)
57-
buf.should == "data"
55+
buffer = +"foo"
56+
@s1.recv_nonblock(5, 0, buffer).should.equal?(buffer)
57+
buffer.should == "data"
58+
end
59+
60+
it "preserves the encoding of the given buffer" do
61+
@s1.bind(Socket.pack_sockaddr_in(0, ip_address))
62+
@s2.send("data", 0, @s1.getsockname)
63+
IO.select([@s1], nil, nil, 2)
64+
65+
buffer = ''.encode(Encoding::ISO_8859_1)
66+
@s1.recv_nonblock(5, 0, buffer)
67+
buffer.encoding.should == Encoding::ISO_8859_1
5868
end
5969

6070
it "does not block if there's no data available" do

spec/ruby/library/socket/basicsocket/recv_spec.rb

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,13 +100,29 @@
100100
socket.write("data")
101101

102102
client = @server.accept
103-
buf = +"foo"
103+
buffer = +"foo"
104104
begin
105-
client.recv(4, 0, buf)
105+
client.recv(4, 0, buffer).should.equal?(buffer)
106106
ensure
107107
client.close
108108
end
109-
buf.should == "data"
109+
buffer.should == "data"
110+
111+
socket.close
112+
end
113+
114+
it "preserves the encoding of the given buffer" do
115+
socket = TCPSocket.new('127.0.0.1', @port)
116+
socket.write("data")
117+
118+
client = @server.accept
119+
buffer = ''.encode(Encoding::ISO_8859_1)
120+
begin
121+
client.recv(4, 0, buffer)
122+
ensure
123+
client.close
124+
end
125+
buffer.encoding.should == Encoding::ISO_8859_1
110126

111127
socket.close
112128
end

spec/ruby/library/socket/socket/recvfrom_nonblock_spec.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,27 @@
5252
end
5353
end
5454

55+
it "allows an output buffer as third argument" do
56+
@client.write('hello')
57+
58+
IO.select([@server])
59+
buffer = +''
60+
message, = @server.recvfrom_nonblock(5, 0, buffer)
61+
62+
message.should.equal?(buffer)
63+
buffer.should == 'hello'
64+
end
65+
66+
it "preserves the encoding of the given buffer" do
67+
@client.write('hello')
68+
69+
IO.select([@server])
70+
buffer = ''.encode(Encoding::ISO_8859_1)
71+
@server.recvfrom_nonblock(5, 0, buffer)
72+
73+
buffer.encoding.should == Encoding::ISO_8859_1
74+
end
75+
5576
describe 'the returned data' do
5677
it 'is the same as the sent data' do
5778
5.times do

spec/ruby/library/socket/udpsocket/recvfrom_nonblock_spec.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,15 @@
5858
buffer.should == 'h'
5959
end
6060

61+
it "preserves the encoding of the given buffer" do
62+
buffer = ''.encode(Encoding::ISO_8859_1)
63+
IO.select([@server])
64+
message, = @server.recvfrom_nonblock(1, 0, buffer)
65+
66+
message.should.equal?(buffer)
67+
buffer.encoding.should == Encoding::ISO_8859_1
68+
end
69+
6170
describe 'the returned Array' do
6271
before do
6372
IO.select([@server])

spec/ruby/library/socket/unixsocket/recvfrom_spec.rb

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,29 @@
3131
sock.close
3232
end
3333

34+
it "allows an output buffer as third argument" do
35+
buffer = +''
36+
37+
@client.send("foobar", 0)
38+
sock = @server.accept
39+
message, = sock.recvfrom(6, 0, buffer)
40+
sock.close
41+
42+
message.should.equal?(buffer)
43+
buffer.should == "foobar"
44+
end
45+
46+
it "preserves the encoding of the given buffer" do
47+
buffer = ''.encode(Encoding::ISO_8859_1)
48+
49+
@client.send("foobar", 0)
50+
sock = @server.accept
51+
sock.recvfrom(6, 0, buffer)
52+
sock.close
53+
54+
buffer.encoding.should == Encoding::ISO_8859_1
55+
end
56+
3457
it "uses different message options" do
3558
@client.send("foobar", Socket::MSG_PEEK)
3659
sock = @server.accept

spec/ruby/library/stringio/shared/read.rb

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,17 @@
1111
end
1212

1313
it "reads length bytes and writes them to the buffer String" do
14-
@io.send(@method, 7, buffer = +"")
14+
@io.send(@method, 7, buffer = +"").should.equal?(buffer)
1515
buffer.should == "example"
1616
end
1717

18+
it "does not preserve the encoding of the given buffer" do
19+
buffer = ''.encode(Encoding::ISO_8859_1)
20+
@io.send(@method, 7, buffer)
21+
22+
buffer.encoding.should_not == Encoding::ISO_8859_1
23+
end
24+
1825
it "tries to convert the passed buffer Object to a String using #to_str" do
1926
obj = mock("to_str")
2027
obj.should_receive(:to_str).and_return(buffer = +"")

spec/tags/core/io/pread_tags.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,4 @@ fails:IO#pread raises TypeError if offset is not an Integer and cannot be coerce
1818
fails:IO#pread raises ArgumentError for negative values of maxlen
1919
fails:IO#pread raised Errno::EINVAL for negative values of offset
2020
fails:IO#pread raises TypeError if the buffer is not a String and cannot be coerced into String
21+
fails:IO#pread preserves the encoding of the given buffer
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
11
fails:IO#read_nonblock raises an exception after ungetc with data in the buffer and character conversion enabled
22
fails:IO#read_nonblock discards the existing buffer content upon error
3-
fails:IO#read_nonblock preserves the encoding of the given buffer

spec/tags/core/io/readpartial_tags.txt

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,2 @@
11
fails:BasicSocket#read_nonblock using IPv4 does not set the IO in nonblock mode
22
fails:BasicSocket#read_nonblock using IPv6 does not set the IO in nonblock mode
3-
fails:BasicSocket#read_nonblock using IPv4 replaces the content of the provided buffer without changing its encoding
4-
fails:BasicSocket#read_nonblock using IPv6 replaces the content of the provided buffer without changing its encoding

spec/tags/library/socket/basicsocket/recv_nonblock_tags.txt

Lines changed: 0 additions & 2 deletions
This file was deleted.

spec/tags/library/socket/basicsocket/recv_tags.txt

Lines changed: 0 additions & 1 deletion
This file was deleted.

spec/tags/library/socket/socket/recvfrom_nonblock_tags.txt

Lines changed: 0 additions & 1 deletion
This file was deleted.

src/main/ruby/truffleruby/core/io.rb

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1673,6 +1673,8 @@ def read(length = nil, buffer = nil)
16731673
str = IO.read_encode self, read_all
16741674
return str unless buffer
16751675

1676+
# intentionally don't preserve buffer encoding
1677+
# see https://bugs.ruby-lang.org/issues/20416
16761678
return buffer.replace(str)
16771679
end
16781680

@@ -1753,10 +1755,14 @@ def read_nonblock(size, buffer = nil, exception: true)
17531755
str = Truffle::POSIX.read_string_nonblock(self, size, exception)
17541756

17551757
case str
1756-
when Symbol
1758+
when Symbol # :wait_readable in case of EAGAIN_ERRNO
17571759
str
17581760
when String
1759-
buffer ? buffer.replace(str) : str
1761+
if buffer
1762+
buffer.replace str.force_encoding(buffer.encoding)
1763+
else
1764+
str
1765+
end
17601766
else # EOF
17611767
if exception
17621768
raise EOFError, 'end of file reached'
@@ -1871,8 +1877,7 @@ def readpartial(size, buffer = nil)
18711877
raise EOFError if Primitive.nil? data
18721878
end
18731879

1874-
buffer.replace(data)
1875-
buffer
1880+
buffer.replace data.force_encoding(buffer.encoding)
18761881
else
18771882
return +'' if size == 0
18781883

@@ -2196,20 +2201,23 @@ def sync=(v)
21962201
#
21972202
# @todo Improve reading into provided buffer.
21982203
#
2199-
def sysread(number_of_bytes, buffer = undefined)
2204+
def sysread(number_of_bytes, buffer = nil)
22002205
ensure_open_and_readable
22012206
flush
22022207
raise IOError unless @ibuffer.empty?
22032208

2209+
buffer = StringValue(buffer) if buffer
2210+
22042211
str, errno = Truffle::POSIX.read_string(self, number_of_bytes)
22052212
Errno.handle_errno(errno) unless errno == 0
22062213

22072214
raise EOFError if Primitive.nil? str
22082215

2209-
unless Primitive.undefined? buffer
2210-
StringValue(buffer).replace str
2216+
if buffer
2217+
buffer.replace str.force_encoding(buffer.encoding)
2218+
else
2219+
str
22112220
end
2212-
str
22132221
end
22142222

22152223
##

0 commit comments

Comments
 (0)