From b0fd5df5c284fb1b3df961e3173344559914f1b6 Mon Sep 17 00:00:00 2001 From: Peter Andersson Date: Wed, 22 Oct 2014 13:06:33 +0200 Subject: Make sure code links are generated even if undefined macros exist OTP-11766 --- lib/test_server/src/erl2html2.erl | 65 +++++++++++++++++++++++++++++++-------- 1 file changed, 52 insertions(+), 13 deletions(-) (limited to 'lib') diff --git a/lib/test_server/src/erl2html2.erl b/lib/test_server/src/erl2html2.erl index 952036502a..b9b45cda25 100644 --- a/lib/test_server/src/erl2html2.erl +++ b/lib/test_server/src/erl2html2.erl @@ -88,34 +88,73 @@ convert(File, Dest, Header) -> %%% %%% All function clauses are also marked in order to allow %%% possibly_enhance/2 to write these in bold. +%%% +%%% Use expanded preprocessor directives if possible (epp). Only if +%%% this fails, fall back on using non-expanded code (epp_dodger). + parse_file(File) -> case epp:open(File, [], []) of {ok,Epp} -> - Forms = parse_file(Epp,File,false), - epp:close(Epp), - {ok,Forms}; - {error,E} -> - {error,E} + try parse_preprocessed_file(Epp,File,false) of + Forms -> + epp:close(Epp), + {ok,Forms} + catch + _:{error,_Reason,true} -> + parse_non_preprocessed_file(File); + _:{error,_Reason,false} -> + {ok,[]} + end; + Error = {error,_} -> + Error end. - -parse_file(Epp,File,InCorrectFile) -> +parse_preprocessed_file(Epp,File,InCorrectFile) -> case epp:parse_erl_form(Epp) of {ok,Form} -> case Form of {attribute,_,file,{File,_}} -> - parse_file(Epp,File,true); + parse_preprocessed_file(Epp,File,true); {attribute,_,file,{_OtherFile,_}} -> - parse_file(Epp,File,false); + parse_preprocessed_file(Epp,File,false); {function,L,F,A,[_|C]} when InCorrectFile -> Clauses = [{clause,CL} || {clause,CL,_,_,_} <- C], [{atom_to_list(F),A,L} | Clauses] ++ - parse_file(Epp,File,true); + parse_preprocessed_file(Epp,File,true); + _ -> + parse_preprocessed_file(Epp,File,InCorrectFile) + end; + {error,Reason={_L,epp,{undefined,_Macro,none}}} -> + throw({error,Reason,InCorrectFile}); + {error,_Reason} -> + parse_preprocessed_file(Epp,File,InCorrectFile); + {eof,_Location} -> + [] + end. + +parse_non_preprocessed_file(File) -> + case file:open(File, []) of + {ok,Epp} -> + Forms = parse_non_preprocessed_file(Epp, File, 1), + file:close(Epp), + {ok,Forms}; + Error = {error,_E} -> + Error + end. + +parse_non_preprocessed_file(Epp, File, Location) -> + case epp_dodger:parse_form(Epp, Location) of + {ok,Tree,Location1} -> + case erl_syntax:revert(Tree) of + {function,L,F,A,[_|C]} -> + Clauses = [{clause,CL} || {clause,CL,_,_,_} <- C], + [{atom_to_list(F),A,L} | Clauses] ++ + parse_non_preprocessed_file(Epp, File, Location1); _ -> - parse_file(Epp,File,InCorrectFile) + parse_non_preprocessed_file(Epp, File, Location1) end; - {error,_E} -> - parse_file(Epp,File,InCorrectFile); + {error,_E,Location1} -> + parse_non_preprocessed_file(Epp, File, Location1); {eof,_Location} -> [] end. -- cgit v1.2.3 From 3133492c5902fd22817ee3f1fc65eb129c9e0574 Mon Sep 17 00:00:00 2001 From: Peter Andersson Date: Wed, 5 Nov 2014 14:20:56 +0100 Subject: Update test_server app file --- lib/test_server/src/test_server.app.src | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/test_server/src/test_server.app.src b/lib/test_server/src/test_server.app.src index 5672baa6ef..173f7075db 100644 --- a/lib/test_server/src/test_server.app.src +++ b/lib/test_server/src/test_server.app.src @@ -34,5 +34,5 @@ {env, []}, {runtime_dependencies, ["tools-2.6.14","stdlib-2.0","runtime_tools-1.8.14", "observer-2.0","kernel-3.0","inets-5.10", - "erts-6.0"]}]}. + "syntax_tools-1.6.16","erts-6.0"]}]}. -- cgit v1.2.3 From 9a5b4e9b2a2a622e67fb55ab4262eb858dd46e54 Mon Sep 17 00:00:00 2001 From: Siri Hansen Date: Wed, 5 Nov 2014 11:58:35 +0100 Subject: [ct] Add 'newline' option to send functions in ct_telnet ct_telnet by default adds a newline to all command strings before sending to the telnet server. In some situations this is not desired, for example when sending telnet command sequences (prefixed with the Interprete As Command, IAC, character). In such cases, the new option can be used. Example - send an Are Your There (AYT) sequence: ct_telnet:send(Connection, [255,246], [{newline,false}]). --- lib/common_test/src/ct_telnet.erl | 136 ++++++++++++++++----- lib/common_test/src/ct_telnet_client.erl | 10 +- .../ct_telnet_SUITE_data/ct_telnet_basic_SUITE.erl | 12 +- .../ct_telnet_own_server_SUITE.erl | 41 ++++++- lib/common_test/test/telnet_server.erl | 36 ++++-- 5 files changed, 189 insertions(+), 46 deletions(-) (limited to 'lib') diff --git a/lib/common_test/src/ct_telnet.erl b/lib/common_test/src/ct_telnet.erl index 3b2652d06c..babe73e575 100644 --- a/lib/common_test/src/ct_telnet.erl +++ b/lib/common_test/src/ct_telnet.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2003-2013. All Rights Reserved. +%% Copyright Ericsson AB 2003-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -141,7 +141,8 @@ -export([open/1, open/2, open/3, open/4, close/1]). -export([cmd/2, cmd/3, cmdf/3, cmdf/4, get_data/1, - send/2, sendf/3, expect/2, expect/3]). + send/2, send/3, sendf/3, sendf/4, + expect/2, expect/3]). %% Callbacks -export([init/3,handle_msg/2,reconnect/2,terminate/2]). @@ -304,42 +305,74 @@ close(Connection) -> %%% Test suite interface %%%----------------------------------------------------------------- %%% @spec cmd(Connection,Cmd) -> {ok,Data} | {error,Reason} -%%% @equiv cmd(Connection,Cmd,DefaultTimeout) +%%% @equiv cmd(Connection,Cmd,[]) cmd(Connection,Cmd) -> - cmd(Connection,Cmd,default). + cmd(Connection,Cmd,[]). %%%----------------------------------------------------------------- -%%% @spec cmd(Connection,Cmd,Timeout) -> {ok,Data} | {error,Reason} +%%% @spec cmd(Connection,Cmd,Opts) -> {ok,Data} | {error,Reason} %%% Connection = ct_telnet:connection() %%% Cmd = string() -%%% Timeout = integer() +%%% Opts = [Opt] +%%% Opt = {timeout,timeout()} | {newline,boolean()} %%% Data = [string()] %%% Reason = term() %%% @doc Send a command via telnet and wait for prompt. -cmd(Connection,Cmd,Timeout) -> - case get_handle(Connection) of - {ok,Pid} -> - call(Pid,{cmd,Cmd,Timeout}); +%%% +%%% This function will by default add a newline to the end of the +%%% given command. If this is not desired, the option +%%% `{newline,false}' can be used. This is necessary, for example, +%%% when sending telnet command sequences (prefixed with the +%%% Interprete As Command, IAC, character). +%%% +%%% The option `timeout' specifies how long the client shall wait for +%%% prompt. If the time expires, the function returns +%%% `{error,timeout}'. See the module description for information +%%% about the default value for the command timeout. +cmd(Connection,Cmd,Opts) when is_list(Opts) -> + case check_cmd_opts(Opts) of + ok -> + case get_handle(Connection) of + {ok,Pid} -> + call(Pid,{cmd,Cmd,Opts}); + Error -> + Error + end; Error -> Error - end. + end; +cmd(Connection,Cmd,Timeout) when is_integer(Timeout); Timeout==default -> + %% This clause is kept for backwards compatibility only + cmd(Connection,Cmd,[{timeout,Timeout}]). + +check_cmd_opts([{timeout,Timeout}|Opts]) when is_integer(Timeout); + Timeout==default -> + check_cmd_opts(Opts); +check_cmd_opts([]) -> + ok; +check_cmd_opts(Opts) -> + check_send_opts(Opts). + %%%----------------------------------------------------------------- %%% @spec cmdf(Connection,CmdFormat,Args) -> {ok,Data} | {error,Reason} -%%% @equiv cmdf(Connection,CmdFormat,Args,DefaultTimeout) +%%% @equiv cmdf(Connection,CmdFormat,Args,[]) cmdf(Connection,CmdFormat,Args) -> - cmdf(Connection,CmdFormat,Args,default). + cmdf(Connection,CmdFormat,Args,[]). %%%----------------------------------------------------------------- -%%% @spec cmdf(Connection,CmdFormat,Args,Timeout) -> {ok,Data} | {error,Reason} +%%% @spec cmdf(Connection,CmdFormat,Args,Opts) -> {ok,Data} | {error,Reason} %%% Connection = ct_telnet:connection() %%% CmdFormat = string() %%% Args = list() -%%% Timeout = integer() +%%% Opts = [Opt] +%%% Opt = {timeout,timeout()} | {newline,boolean()} %%% Data = [string()] %%% Reason = term() %%% @doc Send a telnet command and wait for prompt %%% (uses a format string and list of arguments to build the command). -cmdf(Connection,CmdFormat,Args,Timeout) when is_list(Args) -> +%%% +%%% See {@link cmd/3} further description. +cmdf(Connection,CmdFormat,Args,Opts) when is_list(Args) -> Cmd = lists:flatten(io_lib:format(CmdFormat,Args)), - cmd(Connection,Cmd,Timeout). + cmd(Connection,Cmd,Opts). %%%----------------------------------------------------------------- %%% @spec get_data(Connection) -> {ok,Data} | {error,Reason} @@ -358,32 +391,67 @@ get_data(Connection) -> %%%----------------------------------------------------------------- %%% @spec send(Connection,Cmd) -> ok | {error,Reason} +%%% @equiv send(Connection,Cmd,[]) +send(Connection,Cmd) -> + send(Connection,Cmd,[]). + +%%%----------------------------------------------------------------- +%%% @spec send(Connection,Cmd,Opts) -> ok | {error,Reason} %%% Connection = ct_telnet:connection() %%% Cmd = string() +%%% Opts = [Opt] +%%% Opt = {newline,boolean()} %%% Reason = term() %%% @doc Send a telnet command and return immediately. %%% +%%% This function will by default add a newline to the end of the +%%% given command. If this is not desired, the option +%%% `{newline,false}' can be used. This is necessary, for example, +%%% when sending telnet command sequences (prefixed with the +%%% Interprete As Command, IAC, character). +%%% %%%

The resulting output from the command can be read with %%% get_data/1 or expect/2/3.

-send(Connection,Cmd) -> - case get_handle(Connection) of - {ok,Pid} -> - call(Pid,{send,Cmd}); +send(Connection,Cmd,Opts) -> + case check_send_opts(Opts) of + ok -> + case get_handle(Connection) of + {ok,Pid} -> + call(Pid,{send,Cmd,Opts}); + Error -> + Error + end; Error -> Error end. +check_send_opts([{newline,Bool}|Opts]) when is_boolean(Bool) -> + check_send_opts(Opts); +check_send_opts([Invalid|_]) -> + {error,{invalid_option,Invalid}}; +check_send_opts([]) -> + ok. + + %%%----------------------------------------------------------------- %%% @spec sendf(Connection,CmdFormat,Args) -> ok | {error,Reason} +%%% @equiv sendf(Connection,CmdFormat,Args,[]) +sendf(Connection,CmdFormat,Args) when is_list(Args) -> + sendf(Connection,CmdFormat,Args,[]). + +%%%----------------------------------------------------------------- +%%% @spec sendf(Connection,CmdFormat,Args,Opts) -> ok | {error,Reason} %%% Connection = ct_telnet:connection() %%% CmdFormat = string() %%% Args = list() +%%% Opts = [Opt] +%%% Opt = {newline,boolean()} %%% Reason = term() %%% @doc Send a telnet command and return immediately (uses a format %%% string and a list of arguments to build the command). -sendf(Connection,CmdFormat,Args) when is_list(Args) -> +sendf(Connection,CmdFormat,Args,Opts) when is_list(Args) -> Cmd = lists:flatten(io_lib:format(CmdFormat,Args)), - send(Connection,Cmd). + send(Connection,Cmd,Opts). %%%----------------------------------------------------------------- %%% @spec expect(Connection,Patterns) -> term() @@ -559,7 +627,7 @@ set_telnet_defaults([],S) -> S. %% @hidden -handle_msg({cmd,Cmd,Timeout},State) -> +handle_msg({cmd,Cmd,Opts},State) -> start_gen_log(heading(cmd,State#state.name)), log(State,cmd,"Cmd: ~p",[Cmd]), @@ -584,11 +652,14 @@ handle_msg({cmd,Cmd,Timeout},State) -> {ip,true} -> ok end, - TO = if Timeout == default -> State#state.com_to; - true -> Timeout + TO = case proplists:get_value(timeout,Opts,default) of + default -> State#state.com_to; + Timeout -> Timeout end, + Newline = proplists:get_value(newline,Opts,true), {Return,NewBuffer,Prompt} = - case teln_cmd(State#state.teln_pid, Cmd, State#state.prx, TO) of + case teln_cmd(State#state.teln_pid, Cmd, State#state.prx, + Newline, TO) of {ok,Data,_PromptType,Rest} -> log(State,recv,"Return: ~p",[{ok,Data}]), {{ok,Data},Rest,true}; @@ -597,13 +668,13 @@ handle_msg({cmd,Cmd,Timeout},State) -> {State#state.name, State#state.type}, State#state.teln_pid, - {cmd,Cmd,TO}}}, + {cmd,Cmd,Opts}}}, log(State,recv,"Return: ~p",[Error]), {Retry,[],false} end, end_gen_log(), {Return,State#state{buffer=NewBuffer,prompt=Prompt}}; -handle_msg({send,Cmd},State) -> +handle_msg({send,Cmd,Opts},State) -> start_gen_log(heading(send,State#state.name)), log(State,send,"Sending: ~p",[Cmd]), @@ -628,7 +699,8 @@ handle_msg({send,Cmd},State) -> {ip,true} -> ok end, - ct_telnet_client:send_data(State#state.teln_pid,Cmd), + Newline = proplists:get_value(newline,Opts,true), + ct_telnet_client:send_data(State#state.teln_pid,Cmd,Newline), end_gen_log(), {ok,State#state{buffer=[],prompt=false}}; handle_msg(get_data,State) -> @@ -868,8 +940,8 @@ format_data(_How,{String,Args}) -> %%%================================================================= %%% Abstraction layer on top of ct_telnet_client.erl -teln_cmd(Pid,Cmd,Prx,Timeout) -> - ct_telnet_client:send_data(Pid,Cmd), +teln_cmd(Pid,Cmd,Prx,Newline,Timeout) -> + ct_telnet_client:send_data(Pid,Cmd,Newline), teln_receive_until_prompt(Pid,Prx,Timeout). teln_get_all_data(Pid,Prx,Data,Acc,LastLine) -> diff --git a/lib/common_test/src/ct_telnet_client.erl b/lib/common_test/src/ct_telnet_client.erl index ce30dcb74b..3ae373e433 100644 --- a/lib/common_test/src/ct_telnet_client.erl +++ b/lib/common_test/src/ct_telnet_client.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2003-2013. All Rights Reserved. +%% Copyright Ericsson AB 2003-2014. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -35,7 +35,7 @@ %% -define(debug, true). -export([open/2, open/3, open/4, open/5, close/1]). --export([send_data/2, get_data/1]). +-export([send_data/2, send_data/3, get_data/1]). -define(TELNET_PORT, 23). -define(OPEN_TIMEOUT,10000). @@ -97,7 +97,11 @@ close(Pid) -> end. send_data(Pid, Data) -> - Pid ! {send_data, Data++"\n"}, + send_data(Pid, Data, true). +send_data(Pid, Data, true) -> + send_data(Pid, Data++"\n", false); +send_data(Pid, Data, false) -> + Pid ! {send_data, Data}, ok. get_data(Pid) -> diff --git a/lib/common_test/test/ct_telnet_SUITE_data/ct_telnet_basic_SUITE.erl b/lib/common_test/test/ct_telnet_SUITE_data/ct_telnet_basic_SUITE.erl index 80616af064..3885c1991d 100644 --- a/lib/common_test/test/ct_telnet_SUITE_data/ct_telnet_basic_SUITE.erl +++ b/lib/common_test/test/ct_telnet_SUITE_data/ct_telnet_basic_SUITE.erl @@ -20,7 +20,7 @@ suite() -> [ operations() -> [start_stop, send_and_get, expect, already_closed, - cmd, sendf, close_wrong_type]. + cmd, sendf, no_newline, close_wrong_type]. mult_case(_Case, 0) -> []; @@ -129,6 +129,16 @@ sendf(Config) -> ok = ct_telnet:close(Handle), ok. +no_newline(Config) -> + {ok, Handle} = ct_telnet:open(?conn_name(?get_n(Config))), + IAC = 255, % interprete as command + AYT = 246, % are you there + ok = ct_telnet:send(Handle, [IAC,AYT], [{newline,false}]), + {ok,_} = ct_telnet:expect(Handle,"yes",[no_prompt_check]), + {ok,_} = ct_telnet:cmd(Handle, ""), % send newline only to get back prompt + ok = ct_telnet:close(Handle), + ok. + close_wrong_type(_) -> {error, _} = ct_telnet:close(whatever), ok. diff --git a/lib/common_test/test/ct_telnet_SUITE_data/ct_telnet_own_server_SUITE.erl b/lib/common_test/test/ct_telnet_SUITE_data/ct_telnet_own_server_SUITE.erl index c0f79d0f10..9afe545b26 100644 --- a/lib/common_test/test/ct_telnet_SUITE_data/ct_telnet_own_server_SUITE.erl +++ b/lib/common_test/test/ct_telnet_SUITE_data/ct_telnet_own_server_SUITE.erl @@ -4,6 +4,24 @@ -include_lib("common_test/include/ct.hrl"). +%% telnet control characters +-define(SE, 240). +-define(NOP, 241). +-define(DM, 242). +-define(BRK, 243). +-define(IP, 244). +-define(AO, 245). +-define(AYT, 246). +-define(EC, 247). +-define(EL, 248). +-define(GA, 249). +-define(SB, 250). +-define(WILL, 251). +-define(WONT, 252). +-define(DO, 253). +-define(DONT, 254). +-define(IAC, 255). + %%-------------------------------------------------------------------- %% TEST SERVER CALLBACK FUNCTIONS %%-------------------------------------------------------------------- @@ -34,7 +52,9 @@ all() -> ignore_prompt_timeout, large_string, server_speaks, - server_disconnects + server_disconnects, + newline_ayt, + newline_break ]. groups() -> @@ -285,3 +305,22 @@ server_disconnects(_) -> timer:sleep(3000), _ = ct_telnet:close(Handle), ok. + +%% Test option {newline,false} to send telnet command sequence. +newline_ayt(_) -> + {ok, Handle} = ct_telnet:open(telnet_server_conn1), + ok = ct_telnet:send(Handle, [?IAC,?AYT], [{newline,false}]), + {ok,["yes"]} = ct_telnet:expect(Handle, ["yes"]), + ok = ct_telnet:close(Handle), + ok. + +%% Test option {newline,false} to send telnet command sequence. +newline_break(_) -> + {ok, Handle} = ct_telnet:open(telnet_server_conn1), + ok = ct_telnet:send(Handle, [?IAC,?BRK], [{newline,false}]), + %% '#' is the prompt in break mode + {ok,["# "]} = ct_telnet:expect(Handle, ["# "], [no_prompt_check]), + {ok,R} = ct_telnet:cmd(Handle, "q", [{newline,false}]), + "> " = lists:flatten(R), + ok = ct_telnet:close(Handle), + ok. diff --git a/lib/common_test/test/telnet_server.erl b/lib/common_test/test/telnet_server.erl index 1d341d6106..0a23a66324 100644 --- a/lib/common_test/test/telnet_server.erl +++ b/lib/common_test/test/telnet_server.erl @@ -31,7 +31,8 @@ users, authorized=false, suppress_go_ahead=false, - buffer=[]}). + buffer=[], + break=false}). -type options() :: [{port,pos_integer()} | {users,users()}]. -type users() :: [{user(),password()}]. @@ -148,6 +149,9 @@ loop(State, N) -> stopped end. +handle_data(Cmd,#state{break=true}=State) -> + dbg("Server got data when in break mode: ~p~n",[Cmd]), + handle_break_cmd(Cmd,State); handle_data([?IAC|Cmd],State) -> dbg("Server got cmd: ~p~n",[Cmd]), handle_cmd(Cmd,State); @@ -171,24 +175,38 @@ handle_data(Data,State) -> {ok,State#state{buffer=[Data|State#state.buffer]}} end. -%% Add function clause below to handle new telnet commands (sent with -%% ?IAC from client - this is not possible to do from ct_telnet API, -%% but ct_telnet sends DONT SUPPRESS_GO_AHEAD) +%% Add function clause below to handle new telnet commands sent with +%% ?IAC from client. This can be done from ct_telnet:send or +%% ct_telnet:cmd if using the option {newline,false}. Also, ct_telnet +%% sends DONT SUPPRESS_GO_AHEAD. handle_cmd([?DO,?SUPPRESS_GO_AHEAD|T],State) -> send([?IAC,?WILL,?SUPPRESS_GO_AHEAD],State), - handle_cmd(T,State#state{suppress_go_ahead=true}); + handle_data(T,State#state{suppress_go_ahead=true}); handle_cmd([?DONT,?SUPPRESS_GO_AHEAD|T],State) -> send([?IAC,?WONT,?SUPPRESS_GO_AHEAD],State), - handle_cmd(T,State#state{suppress_go_ahead=false}); -handle_cmd([?IAC|T],State) -> - %% Multiple commands in one packet - handle_cmd(T,State); + handle_data(T,State#state{suppress_go_ahead=false}); +handle_cmd([?BRK|T],State) -> + %% Used when testing 'newline' option in ct_telnet:send and ct_telnet:cmd. + send("# ",State), + handle_data(T,State#state{break=true}); +handle_cmd([?AYT|T],State) -> + %% Used when testing 'newline' option in ct_telnet:send and ct_telnet:cmd. + send("yes\r\n> ",State), + handle_data(T,State); handle_cmd([_H|T],State) -> %% Not responding to this command handle_cmd(T,State); handle_cmd([],State) -> {ok,State}. +handle_break_cmd([$q|T],State) -> + %% Dummy cmd allowed in break mode - quit break mode + send("\r\n> ",State), + handle_data(T,State#state{break=false}); +handle_break_cmd([],State) -> + {ok,State}. + + %% Add function clause below to handle new text command (text entered %% from the telnet prompt) do_handle_data(Data,#state{authorized=false}=State) -> -- cgit v1.2.3 From 8276831a98ebe3d3b821ffcc1093acbebf0c6022 Mon Sep 17 00:00:00 2001 From: Siri Hansen Date: Tue, 28 Oct 2014 14:11:20 +0100 Subject: Add stack trace for gen_server exit in ERROR REPORT If a callback function was terminated with exit/1, there would be no stack trace in the ERROR REPORT produced by gen_server. This has been corrected. The actual exit reason for the process is not changed. --- lib/stdlib/src/gen_server.erl | 144 +++++++++++++++++++++++++---------- lib/stdlib/test/gen_server_SUITE.erl | 12 ++- 2 files changed, 113 insertions(+), 43 deletions(-) (limited to 'lib') diff --git a/lib/stdlib/src/gen_server.erl b/lib/stdlib/src/gen_server.erl index dadfe56b3d..528dd23e1c 100644 --- a/lib/stdlib/src/gen_server.erl +++ b/lib/stdlib/src/gen_server.erl @@ -567,28 +567,88 @@ start_monitor(Node, Name) when is_atom(Node), is_atom(Name) -> end end. +%% --------------------------------------------------- +%% Helper functions for try-catch of callbacks. +%% Returns the return value of the callback, or +%% {'EXIT', ExitReason, ReportReason} (if an exception occurs) +%% +%% ExitReason is the reason that shall be used when the process +%% terminates. +%% +%% ReportReason is the reason that shall be printed in the error +%% report. +%% +%% These functions are introduced in order to add the stack trace in +%% the error report produced when a callback is terminated with +%% erlang:exit/1 (OTP-12263). +%% --------------------------------------------------- + +try_dispatch({'$gen_cast', Msg}, Mod, State) -> + try_dispatch(Mod, handle_cast, Msg, State); +try_dispatch(Info, Mod, State) -> + try_dispatch(Mod, handle_info, Info, State). + +try_dispatch(Mod, Func, Msg, State) -> + try + {ok, Mod:Func(Msg, State)} + catch + throw:R -> + {ok, R}; + error:R -> + Stacktrace = erlang:get_stacktrace(), + {'EXIT', {R, Stacktrace}, {R, Stacktrace}}; + exit:R -> + Stacktrace = erlang:get_stacktrace(), + {'EXIT', R, {R, Stacktrace}} + end. + +try_handle_call(Mod, Msg, From, State) -> + try + {ok, Mod:handle_call(Msg, From, State)} + catch + throw:R -> + {ok, R}; + error:R -> + Stacktrace = erlang:get_stacktrace(), + {'EXIT', {R, Stacktrace}, {R, Stacktrace}}; + exit:R -> + Stacktrace = erlang:get_stacktrace(), + {'EXIT', R, {R, Stacktrace}} + end. + +try_terminate(Mod, Reason, State) -> + try + {ok, Mod:terminate(Reason, State)} + catch + throw:R -> + {ok, R}; + error:R -> + Stacktrace = erlang:get_stacktrace(), + {'EXIT', {R, Stacktrace}, {R, Stacktrace}}; + exit:R -> + Stacktrace = erlang:get_stacktrace(), + {'EXIT', R, {R, Stacktrace}} + end. + + %%% --------------------------------------------------- %%% Message handling functions %%% --------------------------------------------------- -dispatch({'$gen_cast', Msg}, Mod, State) -> - Mod:handle_cast(Msg, State); -dispatch(Info, Mod, State) -> - Mod:handle_info(Info, State). - handle_msg({'$gen_call', From, Msg}, Parent, Name, State, Mod) -> - case catch Mod:handle_call(Msg, From, State) of - {reply, Reply, NState} -> + Result = try_handle_call(Mod, Msg, From, State), + case Result of + {ok, {reply, Reply, NState}} -> reply(From, Reply), loop(Parent, Name, NState, Mod, infinity, []); - {reply, Reply, NState, Time1} -> + {ok, {reply, Reply, NState, Time1}} -> reply(From, Reply), loop(Parent, Name, NState, Mod, Time1, []); - {noreply, NState} -> + {ok, {noreply, NState}} -> loop(Parent, Name, NState, Mod, infinity, []); - {noreply, NState, Time1} -> + {ok, {noreply, NState, Time1}} -> loop(Parent, Name, NState, Mod, Time1, []); - {stop, Reason, Reply, NState} -> + {ok, {stop, Reason, Reply, NState}} -> {'EXIT', R} = (catch terminate(Reason, Name, Msg, Mod, NState, [])), reply(From, Reply), @@ -596,26 +656,27 @@ handle_msg({'$gen_call', From, Msg}, Parent, Name, State, Mod) -> Other -> handle_common_reply(Other, Parent, Name, Msg, Mod, State) end; handle_msg(Msg, Parent, Name, State, Mod) -> - Reply = (catch dispatch(Msg, Mod, State)), + Reply = try_dispatch(Msg, Mod, State), handle_common_reply(Reply, Parent, Name, Msg, Mod, State). handle_msg({'$gen_call', From, Msg}, Parent, Name, State, Mod, Debug) -> - case catch Mod:handle_call(Msg, From, State) of - {reply, Reply, NState} -> + Result = try_handle_call(Mod, Msg, From, State), + case Result of + {ok, {reply, Reply, NState}} -> Debug1 = reply(Name, From, Reply, NState, Debug), loop(Parent, Name, NState, Mod, infinity, Debug1); - {reply, Reply, NState, Time1} -> + {ok, {reply, Reply, NState, Time1}} -> Debug1 = reply(Name, From, Reply, NState, Debug), loop(Parent, Name, NState, Mod, Time1, Debug1); - {noreply, NState} -> + {ok, {noreply, NState}} -> Debug1 = sys:handle_debug(Debug, fun print_event/3, Name, {noreply, NState}), loop(Parent, Name, NState, Mod, infinity, Debug1); - {noreply, NState, Time1} -> + {ok, {noreply, NState, Time1}} -> Debug1 = sys:handle_debug(Debug, fun print_event/3, Name, {noreply, NState}), loop(Parent, Name, NState, Mod, Time1, Debug1); - {stop, Reason, Reply, NState} -> + {ok, {stop, Reason, Reply, NState}} -> {'EXIT', R} = (catch terminate(Reason, Name, Msg, Mod, NState, Debug)), _ = reply(Name, From, Reply, NState, Debug), @@ -624,39 +685,39 @@ handle_msg({'$gen_call', From, Msg}, Parent, Name, State, Mod, Debug) -> handle_common_reply(Other, Parent, Name, Msg, Mod, State, Debug) end; handle_msg(Msg, Parent, Name, State, Mod, Debug) -> - Reply = (catch dispatch(Msg, Mod, State)), + Reply = try_dispatch(Msg, Mod, State), handle_common_reply(Reply, Parent, Name, Msg, Mod, State, Debug). handle_common_reply(Reply, Parent, Name, Msg, Mod, State) -> case Reply of - {noreply, NState} -> + {ok, {noreply, NState}} -> loop(Parent, Name, NState, Mod, infinity, []); - {noreply, NState, Time1} -> + {ok, {noreply, NState, Time1}} -> loop(Parent, Name, NState, Mod, Time1, []); - {stop, Reason, NState} -> + {ok, {stop, Reason, NState}} -> terminate(Reason, Name, Msg, Mod, NState, []); - {'EXIT', What} -> - terminate(What, Name, Msg, Mod, State, []); - _ -> - terminate({bad_return_value, Reply}, Name, Msg, Mod, State, []) + {'EXIT', ExitReason, ReportReason} -> + terminate(ExitReason, ReportReason, Name, Msg, Mod, State, []); + {ok, BadReply} -> + terminate({bad_return_value, BadReply}, Name, Msg, Mod, State, []) end. handle_common_reply(Reply, Parent, Name, Msg, Mod, State, Debug) -> case Reply of - {noreply, NState} -> + {ok, {noreply, NState}} -> Debug1 = sys:handle_debug(Debug, fun print_event/3, Name, {noreply, NState}), loop(Parent, Name, NState, Mod, infinity, Debug1); - {noreply, NState, Time1} -> + {ok, {noreply, NState, Time1}} -> Debug1 = sys:handle_debug(Debug, fun print_event/3, Name, {noreply, NState}), loop(Parent, Name, NState, Mod, Time1, Debug1); - {stop, Reason, NState} -> + {ok, {stop, Reason, NState}} -> terminate(Reason, Name, Msg, Mod, NState, Debug); - {'EXIT', What} -> - terminate(What, Name, Msg, Mod, State, Debug); - _ -> - terminate({bad_return_value, Reply}, Name, Msg, Mod, State, Debug) + {'EXIT', ExitReason, ReportReason} -> + terminate(ExitReason, ReportReason, Name, Msg, Mod, State, Debug); + {ok, BadReply} -> + terminate({bad_return_value, BadReply}, Name, Msg, Mod, State, Debug) end. reply(Name, {To, Tag}, Reply, State, Debug) -> @@ -718,13 +779,16 @@ print_event(Dev, Event, Name) -> %%% --------------------------------------------------- terminate(Reason, Name, Msg, Mod, State, Debug) -> - case catch Mod:terminate(Reason, State) of - {'EXIT', R} -> + terminate(Reason, Reason, Name, Msg, Mod, State, Debug). +terminate(ExitReason, ReportReason, Name, Msg, Mod, State, Debug) -> + Reply = try_terminate(Mod, ExitReason, State), + case Reply of + {'EXIT', ExitReason1, ReportReason1} -> FmtState = format_status(terminate, Mod, get(), State), - error_info(R, Name, Msg, FmtState, Debug), - exit(R); + error_info(ReportReason1, Name, Msg, FmtState, Debug), + exit(ExitReason1); _ -> - case Reason of + case ExitReason of normal -> exit(normal); shutdown -> @@ -733,8 +797,8 @@ terminate(Reason, Name, Msg, Mod, State, Debug) -> exit(Shutdown); _ -> FmtState = format_status(terminate, Mod, get(), State), - error_info(Reason, Name, Msg, FmtState, Debug), - exit(Reason) + error_info(ReportReason, Name, Msg, FmtState, Debug), + exit(ExitReason) end end. diff --git a/lib/stdlib/test/gen_server_SUITE.erl b/lib/stdlib/test/gen_server_SUITE.erl index 42694d8b5d..0f03fda30a 100644 --- a/lib/stdlib/test/gen_server_SUITE.erl +++ b/lib/stdlib/test/gen_server_SUITE.erl @@ -275,7 +275,9 @@ crash(Config) when is_list(Config) -> receive {error,_GroupLeader4,{Pid4, "** Generic server"++_, - [Pid4,crash,{formatted, state4},crashed]}} -> + [Pid4,crash,{formatted, state4}, + {crashed,[{?MODULE,handle_call,3,_} + |_Stacktrace]}]}} -> ok; Other4a -> ?line io:format("Unexpected: ~p", [Other4a]), @@ -1026,7 +1028,9 @@ error_format_status(Config) when is_list(Config) -> receive {error,_GroupLeader,{Pid, "** Generic server"++_, - [Pid,crash,{formatted, State},crashed]}} -> + [Pid,crash,{formatted, State}, + {crashed,[{?MODULE,handle_call,3,_} + |_Stacktrace]}]}} -> ok; Other -> ?line io:format("Unexpected: ~p", [Other]), @@ -1048,7 +1052,9 @@ terminate_crash_format(Config) when is_list(Config) -> receive {error,_GroupLeader,{Pid, "** Generic server"++_, - [Pid,stop, {formatted, State},{crash, terminate}]}} -> + [Pid,stop, {formatted, State}, + {{crash, terminate},[{?MODULE,terminate,2,_} + |_Stacktrace]}]}} -> ok; Other -> io:format("Unexpected: ~p", [Other]), -- cgit v1.2.3