Skip to content

stdlib: Fix io_lib:get_until to return binaries at eof #7993

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

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
12 changes: 8 additions & 4 deletions lib/kernel/src/file_io_server.erl
Original file line number Diff line number Diff line change
Expand Up @@ -679,18 +679,22 @@ get_chars_apply(Mod, Func, XtraArg, S0, OutEnc,
error:ErrReason ->
{stop,ErrReason,{error,err_func(Mod, Func, XtraArg)},State}
end.

%% A hack that tries to inform the caller about the position where the
%% error occurred.
invalid_unicode_error(Mod, Func, XtraArg, S) ->
invalid_unicode_error(Mod = io_lib, Func = get_until,
XtraArg = {erl_scan,tokens,_Args},
{GetUntilState, S})
when is_boolean(GetUntilState) ->
try
{erl_scan,tokens,_Args} = XtraArg,
Location = erl_scan:continuation_location(S),
{error,{Location, ?MODULE, invalid_unicode},Location}
catch
_:_ ->
{error,err_func(Mod, Func, XtraArg)}
end.
end;
invalid_unicode_error(Mod, Func, XtraArg, _S) ->
{error,err_func(Mod, Func, XtraArg)}.

%% Convert error code to make it look as before
err_func(io_lib, get_until, {_,F,_}) ->
Expand Down
16 changes: 10 additions & 6 deletions lib/stdlib/src/io_lib.erl
Original file line number Diff line number Diff line change
Expand Up @@ -960,8 +960,12 @@ get_until(Any,Data,Arg) ->

%% Now we are aware of encoding...
get_until(start, Data, Encoding, XtraArg) ->
get_until([], Data, Encoding, XtraArg);
get_until(Cont, Data, Encoding, {Mod, Func, XtraArgs}) ->
%% We use the type of the initial data as an indicator of what
%% the final result should be cast to. We cannot use the final
%% data as that might be eof and then we have no idea what to
%% convert to.
get_until({is_binary(Data), []}, Data, Encoding, XtraArg);
get_until({IsDataBinary, Cont}, Data, Encoding, {Mod, Func, XtraArgs}) ->
Chars = if is_binary(Data), Encoding =:= unicode ->
unicode:characters_to_list(Data,utf8);
is_binary(Data) ->
Expand All @@ -971,14 +975,14 @@ get_until(Cont, Data, Encoding, {Mod, Func, XtraArgs}) ->
end,
case apply(Mod, Func, [Cont,Chars|XtraArgs]) of
{done,Result,Buf} ->
{stop,if is_binary(Data),
{stop,if IsDataBinary,
is_list(Result),
Encoding =:= unicode ->
unicode:characters_to_binary(Result,unicode,unicode);
is_binary(Data),
IsDataBinary,
is_list(Result) ->
erlang:iolist_to_binary(Result);
%% is_list(Data),
%% IsDataBinary,
%% is_list(Result),
%% Encoding =:= latin1 ->
%% % Should check for only latin1, but skip that for
Expand All @@ -990,7 +994,7 @@ get_until(Cont, Data, Encoding, {Mod, Func, XtraArgs}) ->
end,
Buf};
{more,NewCont} ->
NewCont
{IsDataBinary, NewCont}
end.

binrev(L) ->
Expand Down
77 changes: 75 additions & 2 deletions lib/stdlib/test/io_proto_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
file_read_line_stdin_unicode_translation_error_binary_mode/1,
file_read_line_stdin_unicode_translation_error_list_mode/1,
io_get_chars_stdin_binary_mode/1, io_get_chars_stdin_list_mode/1,
io_get_until_stdin_binary_mode/1, io_get_until_stdin_list_mode/1,
io_get_chars_file_read_stdin_binary_mode/1,
file_read_stdin_latin1_binary_mode/1,
file_read_stdin_latin1_list_mode/1,
Expand All @@ -48,6 +49,8 @@

-export([write_raw_to_stdout/0, read_raw_from_stdin/1]).

-export([get_until_eof/2]).

%%-define(debug, true).

-ifdef(debug).
Expand All @@ -73,6 +76,8 @@ all() ->
file_read_line_stdin_unicode_translation_error_list_mode,
io_get_chars_stdin_binary_mode,
io_get_chars_stdin_list_mode,
io_get_until_stdin_binary_mode,
io_get_until_stdin_list_mode,
io_get_chars_file_read_stdin_binary_mode,
file_read_stdin_latin1_binary_mode,
file_read_stdin_latin1_list_mode,
Expand Down Expand Up @@ -367,7 +372,7 @@ file_read_line_stdin_unicode_translation_error_list_mode(_Config) ->

ok.

%% Test that reading from stdin using file:read works when io is in binary mode
%% Test that reading from stdin using io:get_chars works when io is in binary mode
io_get_chars_stdin_binary_mode(_Config) ->
{ok, P, ErlPort} = start_stdin_node(
fun() ->
Expand All @@ -385,7 +390,75 @@ io_get_chars_stdin_binary_mode(_Config) ->

ok.

%% Test that reading from stdin using file:read works when io is in binary mode
%% Test that reading from stdin using custom io_request works when io is in binary mode
io_get_until_stdin_binary_mode(_Config) ->

GetUntilEof =
fun() ->
IoServer = group_leader(),
IoServer !
{io_request,
self(),
IoServer,
{get_until, unicode, '', ?MODULE, get_until_eof, []}},
receive
{io_reply, IoServer, Data} ->
{ok, Data}
end
end,

{ok, P, ErlPort} = start_stdin_node(GetUntilEof, [binary]),

erlang:port_command(ErlPort, "x\n"),
{error, timeout} = gen_tcp:recv(P, 0, 250),
ErlPort ! {self(), close},
{ok, "got: <<\"x\\n\">>\n"} = gen_tcp:recv(P, 0),

{ok, P2, ErlPort2} = start_stdin_node(GetUntilEof, [binary]),

{error, timeout} = gen_tcp:recv(P2, 0, 250),
ErlPort2 ! {self(), close},
{ok, "got: eof\n"} = gen_tcp:recv(P2, 0),

ok.

%% Test that reading from stdin using custom io_request works when io is in list mode
io_get_until_stdin_list_mode(_Config) ->

GetUntilEof =
fun() ->
IoServer = group_leader(),
IoServer !
{io_request,
self(),
IoServer,
{get_until, unicode, '', ?MODULE, get_until_eof, []}},
receive
{io_reply, IoServer, Data} ->
{ok, Data}
end
end,

{ok, P, ErlPort} = start_stdin_node(GetUntilEof, [list]),

erlang:port_command(ErlPort, "x\n"),
{error, timeout} = gen_tcp:recv(P, 0, 250),
ErlPort ! {self(), close},
{ok, "got: \"x\\n\"\n"} = gen_tcp:recv(P, 0),

{ok, P2, ErlPort2} = start_stdin_node(GetUntilEof, [list]),

{error, timeout} = gen_tcp:recv(P2, 0, 250),
ErlPort2 ! {self(), close},
{ok, "got: eof\n"} = gen_tcp:recv(P2, 0),

ok.

get_until_eof([],eof) -> {done,eof,[]};
get_until_eof(ThisFar,eof) -> {done,ThisFar,eof};
get_until_eof(ThisFar,CharList) -> {more,ThisFar++CharList}.

%% Test that reading from stdin using io:get_chars works when io is in list mode
io_get_chars_stdin_list_mode(_Config) ->
{ok, P, ErlPort} = start_stdin_node(
fun() -> case io:get_chars(standard_io, "", 1) of
Expand Down