diff options
34 files changed, 982 insertions, 262 deletions
diff --git a/OTP_VERSION b/OTP_VERSION index 758704512b..8869803b66 100644 --- a/OTP_VERSION +++ b/OTP_VERSION @@ -1 +1 @@ -22.0.4 +22.0.5 diff --git a/erts/doc/src/notes.xml b/erts/doc/src/notes.xml index 128def3934..13208fb7f6 100644 --- a/erts/doc/src/notes.xml +++ b/erts/doc/src/notes.xml @@ -31,6 +31,54 @@ </header> <p>This document describes the changes made to the ERTS application.</p> +<section><title>Erts 10.4.4</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + An invalid value test caused the socket:setopt(Socket, + ip, add_membership, ip_mreq()) to fail with badarg. The + same for drop_membership.</p> + <p> + Own Id: OTP-15908 Aux Id: ERL-980 </p> + </item> + <item> + <p> + Fixed bug causing VM crash when doing textual dump of a + process containing an unhandled monitor down signal. + Textual process dumps can be done with + <c>erlang:system_info(procs)</c>, trace feature + <c>process_dump</c>, Erlang shell break menu and a + crashdump. Bug exist since OTP 21.0.</p> + <p> + Own Id: OTP-15909 Aux Id: ERL-979 </p> + </item> + <item> + <p><c>lists:subtract/2</c> would produce incorrect + results for some inputs on 64-bit platforms.</p> + <p> + Own Id: OTP-15938 Aux Id: ERL-986 </p> + </item> + <item> + <p>Fixed a bug in the loader that was similar to + <c>OTP-15938</c>, yielding incorrect code for some inputs + on 64-bit platforms.</p> + <p> + Own Id: OTP-15939</p> + </item> + <item> + <p> + Fixed bug causing scheduler threads in rare cases to + block spinnning indefinitely. Bug exists since OTP 21.0.</p> + <p> + Own Id: OTP-15941 Aux Id: PR-2313 </p> + </item> + </list> + </section> + +</section> + <section><title>Erts 10.4.3</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/erts/emulator/beam/beam_load.c b/erts/emulator/beam/beam_load.c index 941c3ebbbe..35f2ea6688 100644 --- a/erts/emulator/beam/beam_load.c +++ b/erts/emulator/beam/beam_load.c @@ -4612,7 +4612,15 @@ typedef struct SortGenOpArg { static int genopargtermcompare(SortGenOpArg* a, SortGenOpArg* b) { - return CMP_TERM(a->term, b->term); + Sint res = CMP_TERM(a->term, b->term); + + if (res < 0) { + return -1; + } else if (res > 0) { + return 1; + } + + return 0; } static int diff --git a/erts/emulator/beam/erl_bif_lists.c b/erts/emulator/beam/erl_bif_lists.c index b23fa77f5f..fa2edfef1e 100644 --- a/erts/emulator/beam/erl_bif_lists.c +++ b/erts/emulator/beam/erl_bif_lists.c @@ -413,12 +413,25 @@ typedef struct { #define ERTS_RBT_GET_LEFT(T) ((T)->left) #define ERTS_RBT_SET_LEFT(T, L) ((T)->left = (L)) #define ERTS_RBT_GET_KEY(T) ((T)->key) -#define ERTS_RBT_CMP_KEYS(KX, KY) CMP_TERM(KX, KY) +#define ERTS_RBT_CMP_KEYS(KX, KY) subtract_term_cmp((KX), (KY)) #define ERTS_RBT_WANT_LOOKUP_INSERT #define ERTS_RBT_WANT_LOOKUP #define ERTS_RBT_WANT_DELETE #define ERTS_RBT_UNDEF +/* erl_rbtree expects comparisons to return an int */ +static int subtract_term_cmp(Eterm a, Eterm b) { + Sint res = CMP_TERM(a, b); + + if (res < 0) { + return -1; + } else if (res > 0) { + return 1; + } + + return 0; +} + #include "erl_rbtree.h" static int subtract_continue(Process *p, ErtsSubtractContext *context); diff --git a/erts/emulator/beam/erl_proc_sig_queue.c b/erts/emulator/beam/erl_proc_sig_queue.c index f58a606d57..55e469b553 100644 --- a/erts/emulator/beam/erl_proc_sig_queue.c +++ b/erts/emulator/beam/erl_proc_sig_queue.c @@ -4051,6 +4051,7 @@ erts_proc_sig_signal_size(ErtsSignal *sig) case ERTS_MON_TYPE_DIST_PROC: case ERTS_MON_TYPE_NODE: size = erts_monitor_size((ErtsMonitor *) sig); + break; default: ERTS_INTERNAL_ERROR("Unexpected sig type"); break; diff --git a/erts/emulator/beam/erl_process.h b/erts/emulator/beam/erl_process.h index 6118c671ee..cdad72443f 100644 --- a/erts/emulator/beam/erl_process.h +++ b/erts/emulator/beam/erl_process.h @@ -2352,6 +2352,8 @@ erts_try_change_runq_proc(Process *p, ErtsRunQueue *rq) old_rqint); if (act_rqint == old_rqint) return !0; + + old_rqint = act_rqint; } } diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index ee3b9f2a98..dc0847fa39 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -8860,35 +8860,67 @@ ERL_NIF_TERM nsetopt_lvl_ip_update_membership(ErlNifEnv* env, #endif // It must be a map - if (!IS_MAP(env, eVal)) + if (!IS_MAP(env, eVal)) { + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_ip_update_membership -> " + "value *not* a map\r\n") ); return enif_make_badarg(env); + } // It must have atleast two attributes - if (!enif_get_map_size(env, eVal, &sz) || (sz >= 2)) + if (!enif_get_map_size(env, eVal, &sz) || (sz < 2)) { + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_ip_update_membership -> " + "invalid map value: %T\r\n", eVal) ); return enif_make_badarg(env); + } - if (!GET_MAP_VAL(env, eVal, atom_multiaddr, &eMultiAddr)) + if (!GET_MAP_VAL(env, eVal, atom_multiaddr, &eMultiAddr)) { + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_ip_update_membership -> " + "failed get multiaddr (map) attribute\r\n") ); return enif_make_badarg(env); + } - if (!GET_MAP_VAL(env, eVal, atom_interface, &eInterface)) + if (!GET_MAP_VAL(env, eVal, atom_interface, &eInterface)) { + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_ip_update_membership -> " + "failed get interface (map) attribute\r\n") ); return enif_make_badarg(env); + } if ((xres = esock_decode_ip4_address(env, eMultiAddr, - &mreq.imr_multiaddr)) != NULL) + &mreq.imr_multiaddr)) != NULL) { + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_ip_update_membership -> " + "failed decode multiaddr %T: %s\r\n", eMultiAddr, xres) ); return esock_make_error_str(env, xres); + } if ((xres = esock_decode_ip4_address(env, eInterface, - &mreq.imr_interface)) != NULL) + &mreq.imr_interface)) != NULL) { + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_ip_update_membership -> " + "failed decode interface %T: %s\r\n", eInterface, xres) ); return esock_make_error_str(env, xres); + } res = socket_setopt(descP->sock, level, opt, &mreq, sizeof(mreq)); - if (res != 0) - result = esock_make_error_errno(env, sock_errno()); - else + if (res != 0) { + int save_errno = sock_errno(); + + result = esock_make_error_errno(env, save_errno); + + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_ip_update_membership -> " + "failed setopt: %T (%d)\r\n", result, save_errno) ); + + } else { result = esock_atom_ok; + } return result; } @@ -9492,33 +9524,65 @@ ERL_NIF_TERM nsetopt_lvl_ipv6_update_membership(ErlNifEnv* env, #endif // It must be a map - if (!IS_MAP(env, eVal)) + if (!IS_MAP(env, eVal)) { + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_ipv6_update_membership -> " + "value *not* a map\r\n") ); return enif_make_badarg(env); + } // It must have atleast two attributes - if (!enif_get_map_size(env, eVal, &sz) || (sz >= 2)) + if (!enif_get_map_size(env, eVal, &sz) || (sz < 2)) { + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_ipv6_update_membership -> " + "invalid map value: %T\r\n", eVal) ); return enif_make_badarg(env); + } - if (!GET_MAP_VAL(env, eVal, atom_multiaddr, &eMultiAddr)) + if (!GET_MAP_VAL(env, eVal, atom_multiaddr, &eMultiAddr)) { + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_ipv6_update_membership -> " + "failed get multiaddr (map) attribute\r\n") ); return enif_make_badarg(env); + } - if (!GET_MAP_VAL(env, eVal, atom_interface, &eInterface)) + if (!GET_MAP_VAL(env, eVal, atom_interface, &eInterface)) { + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_ipv6_update_membership -> " + "failed get interface (map) attribute\r\n") ); return enif_make_badarg(env); + } if ((xres = esock_decode_ip6_address(env, eMultiAddr, - &mreq.ipv6mr_multiaddr)) != NULL) + &mreq.ipv6mr_multiaddr)) != NULL) { + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_ipv6_update_membership -> " + "failed decode multiaddr %T: %s\r\n", eMultiAddr, xres) ); return esock_make_error_str(env, xres); + } - if (!GET_UINT(env, eInterface, &mreq.ipv6mr_interface)) + if (!GET_UINT(env, eInterface, &mreq.ipv6mr_interface)) { + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_ip_update_membership -> " + "failed decode interface %T: %s\r\n", eInterface, xres) ); return esock_make_error(env, esock_atom_einval); + } res = socket_setopt(descP->sock, level, opt, &mreq, sizeof(mreq)); - if (res != 0) - result = esock_make_error_errno(env, sock_errno()); - else + if (res != 0) { + int save_errno = sock_errno(); + + result = esock_make_error_errno(env, save_errno); + + SSDBG( descP, + ("SOCKET", "nsetopt_lvl_ipv6_update_membership -> " + "failed setopt: %T (%d)\r\n", result, save_errno) ); + + } else { result = esock_atom_ok; + } return result; } diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl index e3545ccbf9..c6b5c31503 100644 --- a/erts/emulator/test/socket_SUITE.erl +++ b/erts/emulator/test/socket_SUITE.erl @@ -79,6 +79,7 @@ api_opt_simple_otp_options/1, api_opt_simple_otp_rcvbuf_option/1, api_opt_simple_otp_controlling_process/1, + api_opt_ip_add_drop_membership/1, %% *** API Operation Timeout *** api_to_connect_tcp4/1, @@ -511,6 +512,8 @@ groups() -> [{api, [], api_cases()}, {api_basic, [], api_basic_cases()}, {api_options, [], api_options_cases()}, + {api_options_otp, [], api_options_otp_cases()}, + {api_options_ip, [], api_options_ip_cases()}, {api_op_with_timeout, [], api_op_with_timeout_cases()}, {socket_closure, [], socket_closure_cases()}, {sc_ctrl_proc_exit, [], sc_cp_exit_cases()}, @@ -596,11 +599,22 @@ api_basic_cases() -> api_options_cases() -> [ + {group, api_options_otp}, + {group, api_options_ip} + ]. + +api_options_otp_cases() -> + [ api_opt_simple_otp_options, api_opt_simple_otp_rcvbuf_option, api_opt_simple_otp_controlling_process ]. +api_options_ip_cases() -> + [ + api_opt_ip_add_drop_membership + ]. + api_op_with_timeout_cases() -> [ api_to_connect_tcp4, @@ -3173,7 +3187,7 @@ api_opt_simple_otp_controlling_process(suite) -> api_opt_simple_otp_controlling_process(doc) -> []; api_opt_simple_otp_controlling_process(_Config) when is_list(_Config) -> - ?TT(?SECS(5)), + ?TT(?SECS(30)), tc_try(api_opt_simple_otp_controlling_process, fun() -> api_opt_simple_otp_controlling_process() end). @@ -3418,6 +3432,321 @@ api_opt_simple_otp_controlling_process() -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Tests that the add_mambership and drop_membership ip options work. +%% We create one server and two clients. The server only send messages, +%% the clients only receives messages. +%% An UDP datagram is forbidden (RFC 1122) from having a source address +%% that is a multicast address (or a broadcast address). +%% So, the server create a socket "for sending" and the clients sockets +%% "for receiving". +%% Sending socket: Bound to the local address (and any port). +%% When sending, the dest will be the multicast address +%% and port of the receiving socket. +%% Receiving socket: Bound to the multicast address and port. +api_opt_ip_add_drop_membership(suite) -> + []; +api_opt_ip_add_drop_membership(doc) -> + ["OTP-15908 (ERL-980)"]; +api_opt_ip_add_drop_membership(_Config) when is_list(_Config) -> + ?TT(?SECS(30)), + tc_try(api_opt_ip_add_drop_membership, + fun() -> + has_ip_add_membership_support(), + has_ip_drop_membership_support(), + has_ip_multicast_support() + end, + fun() -> api_opt_ip_add_drop_membership() end). + + +api_opt_ip_add_drop_membership() -> + Set = fun(S, Key, Val) -> + socket:setopt(S, ip, Key, Val) + end, + AddMembership = fun(S, Val) -> Set(S, add_membership, Val) end, + DropMembership = fun(S, Val) -> Set(S, drop_membership, Val) end, + + ServerSeq = + [ + %% *** Wait for start order part *** + #{desc => "await start", + cmd => fun(State) -> + {Tester, MSA} = ?SEV_AWAIT_START(), + {ok, State#{tester => Tester, msa => MSA}} + end}, + #{desc => "monitor tester", + cmd => fun(#{tester := Tester} = _State) -> + _MRef = erlang:monitor(process, Tester), + ok + end}, + + %% *** Init part *** + #{desc => "which local address", + cmd => fun(#{domain := Domain} = State) -> + LAddr = which_local_addr(Domain), + LSA = #{family => Domain, addr => LAddr}, + {ok, State#{local_sa => LSA}} + end}, + #{desc => "create socket", + cmd => fun(#{domain := Domain} = State) -> + case socket:open(Domain, dgram, udp) of + {ok, Sock} -> + {ok, State#{sock => Sock}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "make recv socket reuse addr", + cmd => fun(#{sock := Sock} = _State) -> + case socket:setopt(Sock, socket, reuseaddr, true) of + ok -> + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed set reuseaddr: " + "~n ~p", [Reason]), + ERROR + end + end}, + #{desc => "bind recv socket to multicast address", + cmd => fun(#{sock := Sock, msa := MSA} = State) -> + case socket:bind(Sock, MSA) of + {ok, Port} -> + ?SEV_IPRINT("bound to:" + "~n ~p", [Port]), + {ok, State#{msa => MSA#{port => Port}}}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "announce ready (init)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, init), + ok + end}, + + %% The actual test + #{desc => "await continue (add_membership)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, add_membership) + end}, + #{desc => "add membership", + cmd => fun(#{sock := Sock, + msa := #{addr := MAddr}, + local_sa := #{addr := Addr}} = State) -> + MReq = #{multiaddr => MAddr, + interface => Addr}, + ?SEV_IPRINT("try add membership to:" + "~n ~p", [MReq]), + case AddMembership(Sock, MReq) of + ok -> + ?SEV_IPRINT("membership added"), + {ok, State#{mreq => MReq}}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed adding membership to: " + "~n ~p" + "~n Reason: ~p", + [MReq, Reason]), + ERROR + end + end}, + #{desc => "announce ready (add-membership)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, add_membership), + ok + end}, + + #{desc => "await continue (drop_membership)", + cmd => fun(#{tester := Tester} = _State) -> + ?SEV_AWAIT_CONTINUE(Tester, tester, drop_membership) + end}, + #{desc => "drop membership", + cmd => fun(#{sock := Sock, + mreq := MReq} = State) -> + ?SEV_IPRINT("try drop membership from:" + "~n ~p", [MReq]), + case DropMembership(Sock, MReq) of + ok -> + ?SEV_IPRINT("membership dropped"), + {ok, maps:remove(mreq, State)}; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Failed drop membership from: " + "~n ~p" + "~n Reason: ~p", + [MReq, Reason]), + ERROR + end + end}, + #{desc => "announce ready (drop-membership)", + cmd => fun(#{tester := Tester}) -> + ?SEV_ANNOUNCE_READY(Tester, drop_membership), + ok + end}, + + + %% Termination + #{desc => "await terminate (from tester)", + cmd => fun(#{tester := Tester} = State) -> + case ?SEV_AWAIT_TERMINATE(Tester, tester) of + ok -> + {ok, maps:remove(tester, State)}; + {error, _} = ERROR -> + ERROR + end + end}, + #{desc => "close socket", + cmd => fun(#{sock := Sock} = State) -> + socket:close(Sock), + {ok, maps:remove(sock, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + + TesterSeq = + [ + %% *** Init part *** + #{desc => "monitor server", + cmd => fun(#{server := Server} = _State) -> + _MRef = erlang:monitor(process, Server), + ok + end}, + + %% Start the server + #{desc => "order server start", + cmd => fun(#{server := Pid, msa := MSA} = _State) -> + ?SEV_ANNOUNCE_START(Pid, MSA), + ok + end}, + #{desc => "await server ready (init)", + cmd => fun(#{server := Pid} = _State) -> + case ?SEV_AWAIT_READY(Pid, server, init) of + ok -> + ok; + {error, Reason} = ERROR -> + ?SEV_EPRINT("Start of server failed: " + "~n ~p", [Reason]), + ERROR + end + end}, + + + %% *** The actual test *** + #{desc => "order server to continue (add-membership)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, add_membership), + ok + end}, + #{desc => "await server ready (add-membership)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, add_membership) + end}, + + #{desc => "order server to continue (drop-membership)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_CONTINUE(Server, drop_membership), + ok + end}, + #{desc => "await server ready (drop-membership)", + cmd => fun(#{server := Server} = _State) -> + ?SEV_AWAIT_READY(Server, server, drop_membership) + end}, + + ?SEV_SLEEP(?SECS(1)), + + %% *** Termination *** + #{desc => "order server terminate", + cmd => fun(#{server := Server} = _State) -> + ?SEV_ANNOUNCE_TERMINATE(Server), + ok + end}, + #{desc => "await server termination", + cmd => fun(#{server := Server} = State) -> + ?SEV_AWAIT_TERMINATION(Server), + {ok, maps:remove(server, State)} + end}, + + %% *** We are done *** + ?SEV_FINISH_NORMAL + ], + + + i("get multicast address"), + Domain = inet, + MAddr = which_ip_multicast_address(), + MSA = #{family => Domain, addr => MAddr}, + + i("start server evaluator"), + ServerInitState = #{domain => Domain}, + Server = ?SEV_START("server", ServerSeq, ServerInitState), + + i("start tester evaluator"), + TesterInitState = #{domain => Domain, + msa => MSA, + server => Server#ev.pid}, + Tester = ?SEV_START("tester", TesterSeq, TesterInitState), + + i("await evaluator(s)"), + ok = ?SEV_AWAIT_FINISH([Tester, Server]). + + + +which_ip_multicast_address() -> + which_multicast_address(inet). + +which_multicast_address(Domain) -> + case os:type() of + {unix, linux} -> + WhichMAddr = fun([_, _, MAddr]) -> MAddr end, + which_multicast_address2(Domain, WhichMAddr); + + {unix, sunos} -> + WhichMAddr = fun([_, MAddr, _]) -> MAddr end, + which_multicast_address2(Domain, WhichMAddr); + + Type -> + not_supported({multicast, Type}) + end. + +%% Note that the 'netstat -g' table looks different on linux and SunOS +%% Linux: IfName - RefCnt - Group +%% SunOS: IfName - Group - RefCnt + +which_multicast_address2(Domain, WhichMAddr) -> + IfName = which_local_host_ifname(Domain), + NetstatGroupsStr = os:cmd("netstat -g | grep " ++ IfName), + NetstatGroups0 = string:tokens(NetstatGroupsStr, [$\n]), + NetstatGroups = [string:tokens(G, [$ ]) || G <- NetstatGroups0], + MAddrs = [WhichMAddr(NetstatGroup) || NetstatGroup <- + NetstatGroups], + which_multicast_address3(Domain, MAddrs). + +which_multicast_address3(_Domain, []) -> + not_supported({multicast, no_valid_addrs}); +which_multicast_address3(Domain, [MAddrStr|MAddrs]) -> + %% Even on linux some of these are not actually addresses, but + %% "host names", such as all-systems.mcast.net. But both + %% address strings, such as "224.0.0.251" and host name strings + %% gets translated into an address by the inet:inet:getaddr/2. + case inet:getaddr(MAddrStr, Domain) of + {ok, MAddr} -> + MAddr; + {error, _} -> + which_multicast_address3(Domain, MAddrs) + end. + +which_local_host_ifname(Domain) -> + case which_local_host_info(Domain) of + {ok, {Name, _Addr, _Flags}} -> + Name; + {error, Reason} -> + not_supported({multicast, Reason}) + end. + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% %% %% API OPERATIONS WITH TIMEOUT %% @@ -11304,7 +11633,7 @@ traffic_ping_pong_send_and_receive_udp2(InitState) -> LSA = #{family => Domain, addr => LAddr}, {ok, State#{local_sa => LSA}} end}, - #{desc => "create listen socket", + #{desc => "create socket", cmd => fun(#{domain := Domain} = State) -> case socket:open(Domain, dgram, udp) of {ok, Sock} -> @@ -17899,47 +18228,146 @@ local_host() -> end. + + %% This gets the local address (not 127.0...) %% We should really implement this using the (new) net module, %% but until that gets the necessary functionality... which_local_addr(Domain) -> - case inet:getifaddrs() of - {ok, IFL} -> - which_addr(Domain, IFL); + case which_local_host_info(Domain) of + {ok, {_Name, _Flags, Addr}} -> + Addr; {error, Reason} -> ?FAIL({inet, getifaddrs, Reason}) end. -which_addr(_Domain, []) -> +%% which_addr(_Domain, []) -> +%% ?FAIL(no_address); +%% which_addr(Domain, [{"lo" ++ _, _}|IFL]) -> +%% which_addr(Domain, IFL); +%% which_addr(Domain, [{"docker" ++ _, _}|IFL]) -> +%% which_addr(Domain, IFL); +%% which_addr(Domain, [{"br-" ++ _, _}|IFL]) -> +%% which_addr(Domain, IFL); +%% which_addr(Domain, [{_Name, IFO}|IFL]) -> +%% case which_addr2(Domain, IFO) of +%% {ok, Addr} -> +%% Addr; +%% {error, no_address} -> +%% which_addr(Domain, IFL) +%% end; +%% which_addr(Domain, [_|IFL]) -> +%% which_addr(Domain, IFL). + +%% which_addr2(_Domain, []) -> +%% {error, no_address}; +%% which_addr2(inet = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 4) -> +%% {ok, Addr}; +%% which_addr2(inet6 = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 8) -> +%% {ok, Addr}; +%% which_addr2(Domain, [_|IFO]) -> +%% which_addr2(Domain, IFO). + + +%% Returns the interface (name), flags and address (not 127...) +%% of the local host. +which_local_host_info(Domain) -> + case inet:getifaddrs() of + {ok, IFL} -> + which_local_host_info(Domain, IFL); + {error, _} = ERROR -> + ERROR + end. + +which_local_host_info(_Domain, []) -> ?FAIL(no_address); -which_addr(Domain, [{"lo" ++ _, _}|IFL]) -> - which_addr(Domain, IFL); -which_addr(Domain, [{_Name, IFO}|IFL]) -> - case which_addr2(Domain, IFO) of - {ok, Addr} -> - Addr; - {error, no_address} -> - which_addr(Domain, IFL) +which_local_host_info(Domain, [{"lo" ++ _, _}|IFL]) -> + which_local_host_info(Domain, IFL); +which_local_host_info(Domain, [{"docker" ++ _, _}|IFL]) -> + which_local_host_info(Domain, IFL); +which_local_host_info(Domain, [{"br-" ++ _, _}|IFL]) -> + which_local_host_info(Domain, IFL); +which_local_host_info(Domain, [{Name, IFO}|IFL]) -> + case which_local_host_info2(Domain, IFO) of + {ok, {Flags, Addr}} -> + {ok, {Name, Flags, Addr}}; + {error, _} -> + which_local_host_info(Domain, IFL) end; -which_addr(Domain, [_|IFL]) -> - which_addr(Domain, IFL). +which_local_host_info(Domain, [_|IFL]) -> + which_local_host_info(Domain, IFL). + +which_local_host_info2(Domain, IFO) -> + case lists:keysearch(flags, 1, IFO) of + {value, {flags, Flags}} -> + which_local_host_info2(Domain, IFO, Flags); + false -> + {error, no_flags} + end. -which_addr2(_Domain, []) -> +which_local_host_info2(_Domain, [], _Flags) -> {error, no_address}; -which_addr2(inet = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 4) -> - {ok, Addr}; -which_addr2(inet6 = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 8) -> - {ok, Addr}; -which_addr2(Domain, [_|IFO]) -> - which_addr2(Domain, IFO). +which_local_host_info2(inet = _Domain, [{addr, Addr}|_IFO], Flags) + when (size(Addr) =:= 4) andalso (element(1, Addr) =/= 127) -> + {ok, {Flags, Addr}}; +which_local_host_info2(inet6 = _Domain, [{addr, Addr}|_IFO], Flags) + when (size(Addr) =:= 8) andalso + (element(1, Addr) =/= 0) andalso + (element(1, Addr) =/= 16#fe80) -> + {ok, {Flags, Addr}}; +which_local_host_info2(Domain, [_|IFO], Flags) -> + which_local_host_info2(Domain, IFO, Flags). + + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Here are all the *general* test case condition functions. + +%% We also need (be able) to figure out the the multicast address, +%% which we only support for some platforms (linux and sunos). +%% We don't do that here, but since we can only do that (find a +%% multicast address) for specific platforms, we check that we are +%% on of those platforms here. +has_ip_multicast_support() -> + case os:type() of + {unix, OsName} when (OsName =:= linux) orelse + (OsName =:= sunos) -> + case which_local_host_info(inet) of + {ok, {_Name, Flags, _Addr}} -> + case lists:member(multicast, Flags) of + true -> + ok; + false -> + not_supported(multicast) + end; + {error, Reason} -> + not_supported({multicast, Reason}) + end; + Type -> + not_supported({multicast, Type}) + end. +has_ip_add_membership_support() -> + has_socket_option_ip_support(add_membership). +has_ip_drop_membership_support() -> + has_socket_option_ip_support(drop_membership). +has_socket_option_ip_support(Opt) -> + has_socket_option_support(ip, Opt). + +has_socket_option_support(Level, Option) -> + case socket:supports(options, Level, Option) of + true -> + ok; + false -> + not_supported({options, Level, Option}) + end. -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Here are all the *general* test vase condition functions. %% The idea is that this function shall test if the test host has %% support for IPv6. If not, there is no point in running IPv6 tests. @@ -17957,6 +18385,9 @@ has_support_ipv6() -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +not_supported(What) -> + skip({not_supported, What}). + not_yet_implemented() -> skip("not yet implemented"). diff --git a/erts/emulator/test/system_info_SUITE.erl b/erts/emulator/test/system_info_SUITE.erl index 55b1162cfb..b48be3dd04 100644 --- a/erts/emulator/test/system_info_SUITE.erl +++ b/erts/emulator/test/system_info_SUITE.erl @@ -37,10 +37,13 @@ -export([process_count/1, system_version/1, misc_smoke_tests/1, heap_size/1, wordsize/1, memory/1, ets_limit/1, atom_limit/1, + procs_bug/1, ets_count/1, atom_count/1, system_logger/1]). -export([init/1, handle_event/2, handle_call/2]). +-export([init_per_testcase/2, end_per_testcase/2]). + suite() -> [{ct_hooks,[ts_install_cth]}, {timetrap, {minutes, 2}}]. @@ -48,8 +51,20 @@ suite() -> all() -> [process_count, system_version, misc_smoke_tests, ets_count, heap_size, wordsize, memory, ets_limit, atom_limit, atom_count, + procs_bug, system_logger]. + +init_per_testcase(procs_bug, Config) -> + procs_bug(init_per_testcase, Config); +init_per_testcase(_, Config) -> + Config. + +end_per_testcase(procs_bug, Config) -> + procs_bug(end_per_testcase, Config); +end_per_testcase(_, _) -> + ok. + %%% %%% The test cases ------------------------------------------------------------- %%% @@ -654,3 +669,41 @@ handle_call(Msg, State) -> handle_event(Event, State) -> State ! {report_handler, Event}, {ok, State}. + + +%% OTP-15909: Provoke bug that would cause VM crash +%% if doing system_info(procs) when process have queued exit/down signals. +procs_bug(init_per_testcase, Config) -> + %% Use single scheduler and process prio to starve monitoring processes + %% from handling their received DOWN signals. + OldSchedOnline = erlang:system_flag(schedulers_online,1), + [{schedulers_online, OldSchedOnline} | Config]; +procs_bug(end_per_testcase, Config) -> + erlang:system_flag(schedulers_online, + proplists:get_value(schedulers_online, Config)), + ok. + +procs_bug(Config) when is_list(Config) -> + {Monee,_} = spawn_opt(fun () -> receive die -> ok end end, + [monitor,{priority,max}]), + Papa = self(), + Pids = [begin + P = spawn_opt(fun () -> + erlang:monitor(process, Monee), + Papa ! {self(),ready}, + receive "nada" -> no end + end, + [link, {priority,normal}]), + {P, ready} = receive M -> M end, + P + end + || _ <- lists:seq(1,10)], + process_flag(priority,high), + Monee ! die, + {'DOWN',_,process,Monee,normal} = receive M -> M end, + + %% This call did crash VM as Pids have pending DOWN signals. + erlang:system_info(procs), + process_flag(priority,normal), + [begin unlink(P), exit(P, kill) end || P <- Pids], + ok. diff --git a/erts/vsn.mk b/erts/vsn.mk index 40a9685e9d..f06fd08540 100644 --- a/erts/vsn.mk +++ b/erts/vsn.mk @@ -18,7 +18,7 @@ # %CopyrightEnd% # -VSN = 10.4.3 +VSN = 10.4.4 # Port number 4365 in 4.2 # Port number 4366 in 4.3 diff --git a/lib/compiler/src/beam_ssa_bsm.erl b/lib/compiler/src/beam_ssa_bsm.erl index 382e6f635e..1ac9e6a3bb 100644 --- a/lib/compiler/src/beam_ssa_bsm.erl +++ b/lib/compiler/src/beam_ssa_bsm.erl @@ -683,8 +683,12 @@ aca_copy_successors(Lbl0, Blocks0, Counter0) -> Lbl = maps:get(Lbl0, BRs), {Lbl, Blocks, Counter}. +aca_cs_build_brs([?BADARG_BLOCK=Lbl | Path], Counter, Acc) -> + %% ?BADARG_BLOCK is a marker and not an actual block, so renaming it will + %% break exception handling. + aca_cs_build_brs(Path, Counter, Acc#{ Lbl => Lbl }); aca_cs_build_brs([Lbl | Path], Counter0, Acc) -> - aca_cs_build_brs(Path, Counter0 + 1, maps:put(Lbl, Counter0, Acc)); + aca_cs_build_brs(Path, Counter0 + 1, Acc#{ Lbl => Counter0 }); aca_cs_build_brs([], Counter, Acc) -> {Acc, Counter}. diff --git a/lib/compiler/src/beam_ssa_opt.erl b/lib/compiler/src/beam_ssa_opt.erl index 90c0d3cf16..229edc6a1d 100644 --- a/lib/compiler/src/beam_ssa_opt.erl +++ b/lib/compiler/src/beam_ssa_opt.erl @@ -904,8 +904,7 @@ cse_suitable(#b_set{}) -> false. }). ssa_opt_float({#st{ssa=Linear0,cnt=Count0}=St, FuncDb}) -> - NonGuards0 = float_non_guards(Linear0), - NonGuards = gb_sets:from_list(NonGuards0), + NonGuards = non_guards(Linear0), Blocks = maps:from_list(Linear0), Fs = #fs{non_guards=NonGuards,bs=Blocks}, {Linear,Count} = float_opt(Linear0, Count0, Fs), @@ -916,15 +915,6 @@ float_blk_is_in_guard(#b_blk{last=#b_br{fail=F}}, #fs{non_guards=NonGuards}) -> float_blk_is_in_guard(#b_blk{}, #fs{}) -> false. -float_non_guards([{L,#b_blk{is=Is}}|Bs]) -> - case Is of - [#b_set{op=landingpad}|_] -> - [L|float_non_guards(Bs)]; - _ -> - float_non_guards(Bs) - end; -float_non_guards([]) -> [?BADARG_BLOCK]. - float_opt([{L,Blk}|Bs0], Count0, Fs) -> case float_blk_is_in_guard(Blk, Fs) of true -> @@ -1774,35 +1764,44 @@ opt_bs_put_split_int_1(Int, L, R) -> %%% ssa_opt_tuple_size({#st{ssa=Linear0,cnt=Count0}=St, FuncDb}) -> - {Linear,Count} = opt_tup_size(Linear0, Count0, []), + %% This optimization is only safe in guards, as prefixing tuple_size with + %% an is_tuple check prevents it from throwing an exception. + NonGuards = non_guards(Linear0), + {Linear,Count} = opt_tup_size(Linear0, NonGuards, Count0, []), {St#st{ssa=Linear,cnt=Count}, FuncDb}. -opt_tup_size([{L,#b_blk{is=Is,last=Last}=Blk}|Bs], Count0, Acc0) -> +opt_tup_size([{L,#b_blk{is=Is,last=Last}=Blk}|Bs], NonGuards, Count0, Acc0) -> case {Is,Last} of {[#b_set{op={bif,'=:='},dst=Bool,args=[#b_var{}=Tup,#b_literal{val=Arity}]}], #b_br{bool=Bool}} when is_integer(Arity), Arity >= 0 -> - {Acc,Count} = opt_tup_size_1(Tup, L, Count0, Acc0), - opt_tup_size(Bs, Count, [{L,Blk}|Acc]); + {Acc,Count} = opt_tup_size_1(Tup, L, NonGuards, Count0, Acc0), + opt_tup_size(Bs, NonGuards, Count, [{L,Blk}|Acc]); {_,_} -> - opt_tup_size(Bs, Count0, [{L,Blk}|Acc0]) + opt_tup_size(Bs, NonGuards, Count0, [{L,Blk}|Acc0]) end; -opt_tup_size([], Count, Acc) -> +opt_tup_size([], _NonGuards, Count, Acc) -> {reverse(Acc),Count}. -opt_tup_size_1(Size, EqL, Count0, [{L,Blk0}|Acc]) -> - case Blk0 of - #b_blk{is=Is0,last=#b_br{bool=Bool,succ=EqL,fail=Fail}} -> - case opt_tup_size_is(Is0, Bool, Size, []) of - none -> +opt_tup_size_1(Size, EqL, NonGuards, Count0, [{L,Blk0}|Acc]) -> + #b_blk{is=Is0,last=Last} = Blk0, + case Last of + #b_br{bool=Bool,succ=EqL,fail=Fail} -> + case gb_sets:is_member(Fail, NonGuards) of + true -> {[{L,Blk0}|Acc],Count0}; - {PreIs,TupleSizeIs,Tuple} -> - opt_tup_size_2(PreIs, TupleSizeIs, L, EqL, - Tuple, Fail, Count0, Acc) + false -> + case opt_tup_size_is(Is0, Bool, Size, []) of + none -> + {[{L,Blk0}|Acc],Count0}; + {PreIs,TupleSizeIs,Tuple} -> + opt_tup_size_2(PreIs, TupleSizeIs, L, EqL, + Tuple, Fail, Count0, Acc) + end end; - #b_blk{} -> + _ -> {[{L,Blk0}|Acc],Count0} end; -opt_tup_size_1(_, _, Count, Acc) -> +opt_tup_size_1(_, _, _, Count, Acc) -> {Acc,Count}. opt_tup_size_2(PreIs, TupleSizeIs, PreL, EqL, Tuple, Fail, Count0, Acc) -> @@ -2241,6 +2240,19 @@ gcd(A, B) -> X -> gcd(B, X) end. +non_guards(Linear) -> + gb_sets:from_list(non_guards_1(Linear)). + +non_guards_1([{L,#b_blk{is=Is}}|Bs]) -> + case Is of + [#b_set{op=landingpad}|_] -> + [L | non_guards_1(Bs)]; + _ -> + non_guards_1(Bs) + end; +non_guards_1([]) -> + [?BADARG_BLOCK]. + rel2fam(S0) -> S1 = sofs:relation(S0), S = sofs:rel2fam(S1), diff --git a/lib/compiler/src/beam_ssa_pre_codegen.erl b/lib/compiler/src/beam_ssa_pre_codegen.erl index 9af72afca7..a2e687930b 100644 --- a/lib/compiler/src/beam_ssa_pre_codegen.erl +++ b/lib/compiler/src/beam_ssa_pre_codegen.erl @@ -156,7 +156,9 @@ passes(Opts) -> %% Allocate registers. ?PASS(linear_scan), ?PASS(frame_size), - ?PASS(turn_yregs)], + ?PASS(turn_yregs), + + ?PASS(assert_no_critical_edges)], [P || P <- Ps, P =/= ignore]. function(#b_function{anno=Anno,args=Args,bs=Blocks0,cnt=Count0}=F0, @@ -1321,10 +1323,11 @@ fix_receives_1([{L,Blk}|Ls], Blocks0, Count0) -> LoopExit = find_loop_exit(Rm, Blocks0), Defs0 = beam_ssa:def([L], Blocks0), CommonUsed = recv_common(Defs0, LoopExit, Blocks0), - {Blocks1,Count1} = recv_fix_common(CommonUsed, LoopExit, Rm, - Blocks0, Count0), + {Blocks1,Count1} = recv_crit_edges(Rm, LoopExit, Blocks0, Count0), + {Blocks2,Count2} = recv_fix_common(CommonUsed, LoopExit, Rm, + Blocks1, Count1), Defs = ordsets:subtract(Defs0, CommonUsed), - {Blocks,Count} = fix_receive(Rm, Defs, Blocks1, Count1), + {Blocks,Count} = fix_receive(Rm, Defs, Blocks2, Count2), fix_receives_1(Ls, Blocks, Count); #b_blk{} -> fix_receives_1(Ls, Blocks0, Count0) @@ -1341,6 +1344,57 @@ recv_common(Defs, Exit, Blocks) -> Def = ordsets:subtract(Defs, ExitDefs), ordsets:intersection(Def, ExitUsed). +%% recv_crit_edges([RemoveMessageLabel], LoopExit, +%% Blocks0, Count0) -> {Blocks,Count}. +%% +%% Adds dummy blocks on all conditional jumps to the exit block so that +%% recv_fix_common/5 can insert phi nodes without having to worry about +%% critical edges. + +recv_crit_edges(_Rms, none, Blocks0, Count0) -> + {Blocks0, Count0}; +recv_crit_edges(Rms, Exit, Blocks0, Count0) -> + Ls = beam_ssa:rpo(Rms, Blocks0), + rce_insert_edges(Ls, Exit, Count0, Blocks0). + +rce_insert_edges([L | Ls], Exit, Count0, Blocks0) -> + Successors = beam_ssa:successors(map_get(L, Blocks0)), + case member(Exit, Successors) of + true when Successors =/= [Exit] -> + {Blocks, Count} = rce_insert_edge(L, Exit, Count0, Blocks0), + rce_insert_edges(Ls, Exit, Count, Blocks); + _ -> + rce_insert_edges(Ls, Exit, Count0, Blocks0) + end; +rce_insert_edges([], _Exit, Count, Blocks) -> + {Blocks, Count}. + +rce_insert_edge(L, Exit, Count, Blocks0) -> + #b_blk{last=Last0} = FromBlk0 = map_get(L, Blocks0), + + ToExit = #b_br{bool=#b_literal{val=true},succ=Exit,fail=Exit}, + + FromBlk = FromBlk0#b_blk{last=rce_reroute_terminator(Last0, Exit, Count)}, + EdgeBlk = #b_blk{anno=#{},is=[],last=ToExit}, + + Blocks = Blocks0#{ Count => EdgeBlk, L => FromBlk }, + {Blocks, Count + 1}. + +rce_reroute_terminator(#b_br{succ=Exit}=Last, Exit, New) -> + rce_reroute_terminator(Last#b_br{succ=New}, Exit, New); +rce_reroute_terminator(#b_br{fail=Exit}=Last, Exit, New) -> + rce_reroute_terminator(Last#b_br{fail=New}, Exit, New); +rce_reroute_terminator(#b_br{}=Last, _Exit, _New) -> + Last; +rce_reroute_terminator(#b_switch{fail=Exit}=Last, Exit, New) -> + rce_reroute_terminator(Last#b_switch{fail=New}, Exit, New); +rce_reroute_terminator(#b_switch{list=List0}=Last, Exit, New) -> + List = [if + Lbl =:= Exit -> {Arg, New}; + Lbl =/= Exit -> {Arg, Lbl} + end || {Arg, Lbl} <- List0], + Last#b_switch{list=List}. + %% recv_fix_common([CommonVar], LoopExit, [RemoveMessageLabel], %% Blocks0, Count0) -> {Blocks,Count}. %% Handle variables alwys defined in a receive and used diff --git a/lib/compiler/test/beam_ssa_SUITE.erl b/lib/compiler/test/beam_ssa_SUITE.erl index a741ebbdf9..256bb95559 100644 --- a/lib/compiler/test/beam_ssa_SUITE.erl +++ b/lib/compiler/test/beam_ssa_SUITE.erl @@ -190,6 +190,12 @@ recv(_Config) -> self() ! {[self(),r1],{2,99,<<"data">>}}, {Parent,r1,<<1:32,2:8,99:8,"data">>} = tricky_recv_4(), + %% Test tricky_recv_5/0. + self() ! 1, + a = tricky_recv_5(), + self() ! 2, + b = tricky_recv_5(), + ok. sync_wait_mon({Pid, Ref}, Timeout) -> @@ -295,6 +301,26 @@ tricky_recv_4() -> end, id({Pid,R,Request}). +%% beam_ssa_pre_codegen would accidentally create phi nodes on critical edges +%% when fixing up receives; the call to id/2 can either succeed or land in the +%% catch block, and we added a phi node to its immediate successor. +tricky_recv_5() -> + try + receive + X=1 -> + id(42), + a; + X=2 -> + b + end, + case X of + 1 -> a; + 2 -> b + end + catch + _:_ -> c + end. + maps(_Config) -> {'EXIT',{{badmatch,#{}},_}} = (catch maps_1(any)), ok. diff --git a/lib/compiler/test/bs_match_SUITE.erl b/lib/compiler/test/bs_match_SUITE.erl index d97f49c56e..145a50f4ad 100644 --- a/lib/compiler/test/bs_match_SUITE.erl +++ b/lib/compiler/test/bs_match_SUITE.erl @@ -44,7 +44,8 @@ beam_bsm/1,guard/1,is_ascii/1,non_opt_eq/1, expression_before_match/1,erl_689/1,restore_on_call/1, restore_after_catch/1,matches_on_parameter/1,big_positions/1, - matching_meets_apply/1,bs_start_match2_defs/1]). + matching_meets_apply/1,bs_start_match2_defs/1, + exceptions_after_match_failure/1]). -export([coverage_id/1,coverage_external_ignore/2]). @@ -80,7 +81,8 @@ groups() -> beam_bsm,guard,is_ascii,non_opt_eq, expression_before_match,erl_689,restore_on_call, matches_on_parameter,big_positions, - matching_meets_apply,bs_start_match2_defs]}]. + matching_meets_apply,bs_start_match2_defs, + exceptions_after_match_failure]}]. init_per_suite(Config) -> @@ -2005,4 +2007,17 @@ do_matching_meets_apply(_Bin, {Handler, State}) -> %% Another case of the above. Handler:abs(State). +%% Exception handling was broken on the failure path of bs_start_match as +%% beam_ssa_bsm accidentally cloned and renamed the ?BADARG_BLOCK. +exceptions_after_match_failure(_Config) -> + {'EXIT', {badarith, _}} = (catch do_exceptions_after_match_failure(atom)), + ok = do_exceptions_after_match_failure(<<0, 1, "gurka">>), + ok = do_exceptions_after_match_failure(2.0). + +do_exceptions_after_match_failure(<<_A, _B, "gurka">>) -> + ok; +do_exceptions_after_match_failure(Other) -> + Other / 2.0, + ok. + id(I) -> I. diff --git a/lib/compiler/test/match_SUITE.erl b/lib/compiler/test/match_SUITE.erl index aac9de278d..bc74ec4984 100644 --- a/lib/compiler/test/match_SUITE.erl +++ b/lib/compiler/test/match_SUITE.erl @@ -25,7 +25,8 @@ match_in_call/1,untuplify/1,shortcut_boolean/1,letify_guard/1, selectify/1,deselectify/1,underscore/1,match_map/1,map_vars_used/1, coverage/1,grab_bag/1,literal_binary/1, - unary_op/1,eq_types/1,match_after_return/1,match_right_tuple/1]). + unary_op/1,eq_types/1,match_after_return/1,match_right_tuple/1, + tuple_size_in_try/1]). -include_lib("common_test/include/ct.hrl"). @@ -41,7 +42,8 @@ groups() -> shortcut_boolean,letify_guard,selectify,deselectify, underscore,match_map,map_vars_used,coverage, grab_bag,literal_binary,unary_op,eq_types, - match_after_return,match_right_tuple]}]. + match_after_return,match_right_tuple, + tuple_size_in_try]}]. init_per_suite(Config) -> @@ -922,4 +924,19 @@ match_right_tuple_1(T) -> force_succ_regs(_A, B) -> B. +tuple_size_in_try(Config) when is_list(Config) -> + %% The tuple_size optimization was applied outside of guards, causing + %% either the emulator or compiler to crash. + ok = tsit(gurka), + ok = tsit(gaffel). + +tsit(A) -> + try + id(ignored), + 1 = tuple_size(A), + error + catch + _:_ -> ok + end. + id(I) -> I. diff --git a/lib/dialyzer/doc/src/notes.xml b/lib/dialyzer/doc/src/notes.xml index 0930f79840..b72453aac7 100644 --- a/lib/dialyzer/doc/src/notes.xml +++ b/lib/dialyzer/doc/src/notes.xml @@ -32,6 +32,22 @@ <p>This document describes the changes made to the Dialyzer application.</p> +<section><title>Dialyzer 4.0.2</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> Make sure Dialyzer does not crash if the formatting + of results fails. Instead of crashing, an unformatted + version of the results is returned. </p> + <p> + Own Id: OTP-15922 Aux Id: PR-2240, ERL-949 </p> + </item> + </list> + </section> + +</section> + <section><title>Dialyzer 4.0.1</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/dialyzer/src/dialyzer.erl b/lib/dialyzer/src/dialyzer.erl index d4fe064edd..c1bc5ff597 100644 --- a/lib/dialyzer/src/dialyzer.erl +++ b/lib/dialyzer/src/dialyzer.erl @@ -605,11 +605,9 @@ ordinal(N) when is_integer(N) -> io_lib:format("~wth", [N]). %% Functions that parse type strings, literal strings, and contract %% strings. Return strings formatted by erl_pp. -%% If lib/hipe/cerl/erl_types.erl is compiled with DEBUG=true, -%% the contents of opaque types are showed inside brackets. -%% Since erl_parse:parse_form() cannot handle the bracket syntax, -%% no indentation is done. -%%-define(DEBUG , true). +%% Note we always have to catch any error when trying to parse +%% the syntax because other BEAM languages may not emit an +%% Erlang AST that transforms into valid Erlang Source Code. -define(IND, 10). @@ -620,13 +618,15 @@ con(M, F, Src, I) -> sig(Src, false) -> Src; sig(Src, true) -> - Str = lists:flatten(io_lib:format("-spec ~w:~tw~ts.", [a, b, Src])), - {ok, Tokens, _EndLocation} = erl_scan:string(Str), - exec(fun() -> - {ok, {attribute, _, spec, {_MFA, Types}}} = - erl_parse:parse_form(Tokens), - indentation(?IND) ++ pp_spec(Types) - end, Src). + try + Str = lists:flatten(io_lib:format("-spec ~w:~tw~ts.", [a, b, Src])), + {ok, Tokens, _EndLocation} = erl_scan:string(Str), + {ok, {attribute, _, spec, {_MFA, Types}}} = + erl_parse:parse_form(Tokens), + indentation(?IND) ++ pp_spec(Types) + catch + _:_ -> Src + end. %% Argument(list)s are a mix of types and Erlang code. Note: sometimes %% (contract_range, call_without_opaque, opaque_type_test), the initial @@ -681,21 +681,15 @@ ts(Src) -> [C1|Src1] = Src, % $< (product) or $( (arglist) [C2|RevSrc2] = lists:reverse(Src1), Src2 = lists:reverse(RevSrc2), - exec(fun() -> - Types = parse_types_and_literals(Src2), - CommaInd = [$, | Ind], - (indentation(?IND-1) ++ - [C1 | lists:join(CommaInd, [pp_type(Type) || Type <- Types])] ++ - [C2]) - end, Src). - --ifdef(DEBUG). -exec(F, R) -> - try F() catch _:_ -> R end. --else. -exec(F, _) -> - F(). --endif. + try + Types = parse_types_and_literals(Src2), + CommaInd = [$, | Ind], + (indentation(?IND-1) ++ + [C1 | lists:join(CommaInd, [pp_type(Type) || Type <- Types])] ++ + [C2]) + catch + _:_ -> Src + end. indentation(I) -> [$\n | lists:duplicate(I, $\s)]. diff --git a/lib/dialyzer/src/dialyzer_cl.erl b/lib/dialyzer/src/dialyzer_cl.erl index f887f661bd..5e680062fb 100644 --- a/lib/dialyzer/src/dialyzer_cl.erl +++ b/lib/dialyzer/src/dialyzer_cl.erl @@ -320,12 +320,6 @@ report_analysis_start(#options{analysis_type = Type, end end. -report_native_comp(#options{report_mode = ReportMode}) -> - case ReportMode of - quiet -> ok; - _ -> io:format(" Compiling some key modules to native code...") - end. - report_elapsed_time(T1, T2, #options{report_mode = ReportMode}) -> case ReportMode of quiet -> ok; @@ -375,7 +369,6 @@ do_analysis(Options) -> do_analysis(Files, Options, Plt, PltInfo) -> assert_writable(Options#options.output_plt), - hipe_compile(Files, Options), report_analysis_start(Options), State0 = new_state(), State1 = init_output(State0, Options), @@ -484,106 +477,6 @@ expand_dependent_modules_1([Mod|Mods], Included, ModDeps) -> expand_dependent_modules_1([], Included, _ModDeps) -> Included. --define(MIN_PARALLELISM, 7). --define(MIN_FILES_FOR_NATIVE_COMPILE, 20). - --spec hipe_compile([file:filename()], #options{}) -> 'ok'. - -hipe_compile(Files, #options{erlang_mode = ErlangMode} = Options) -> - NoNative = (get(dialyzer_options_native) =:= false), - FewFiles = (length(Files) < ?MIN_FILES_FOR_NATIVE_COMPILE), - case NoNative orelse FewFiles orelse ErlangMode of - true -> ok; - false -> - case erlang:system_info(hipe_architecture) of - undefined -> ok; - _ -> - Mods = [lists, dict, digraph, digraph_utils, ets, - gb_sets, gb_trees, ordsets, sets, sofs, - cerl, erl_types, cerl_trees, erl_bif_types, - dialyzer_analysis_callgraph, dialyzer, dialyzer_behaviours, - dialyzer_codeserver, dialyzer_contracts, - dialyzer_coordinator, dialyzer_dataflow, dialyzer_dep, - dialyzer_plt, dialyzer_succ_typings, dialyzer_typesig, - dialyzer_worker], - report_native_comp(Options), - {T1, _} = statistics(wall_clock), - Cache = (get(dialyzer_options_native_cache) =/= false), - native_compile(Mods, Cache), - {T2, _} = statistics(wall_clock), - report_elapsed_time(T1, T2, Options) - end - end. - -native_compile(Mods, Cache) -> - case dialyzer_utils:parallelism() > ?MIN_PARALLELISM of - true -> - Parent = self(), - Pids = [spawn(fun () -> Parent ! {self(), hc(M, Cache)} end) || M <- Mods], - lists:foreach(fun (Pid) -> receive {Pid, Res} -> Res end end, Pids); - false -> - lists:foreach(fun (Mod) -> hc(Mod, Cache) end, Mods) - end. - -hc(Mod, Cache) -> - {module, Mod} = code:ensure_loaded(Mod), - case code:is_module_native(Mod) of - true -> ok; - false -> - %% io:format(" ~w", [Mod]), - case Cache of - false -> - {ok, Mod} = hipe:c(Mod), - ok; - true -> - hc_cache(Mod) - end - end. - -hc_cache(Mod) -> - CacheBase = cache_base_dir(), - %% Use HiPE architecture, version and erts checksum in directory name, - %% to avoid clashes between incompatible binaries. - HipeArchVersion = - lists:concat( - [erlang:system_info(hipe_architecture), "-", - hipe:version(), "-", - hipe:erts_checksum()]), - CacheDir = filename:join(CacheBase, HipeArchVersion), - OrigBeamFile = code:which(Mod), - {ok, {Mod, <<Checksum:128>>}} = beam_lib:md5(OrigBeamFile), - CachedBeamFile = filename:join(CacheDir, lists:concat([Mod, "-", Checksum, ".beam"])), - ok = filelib:ensure_dir(CachedBeamFile), - ModBin = - case filelib:is_file(CachedBeamFile) of - true -> - {ok, BinFromFile} = file:read_file(CachedBeamFile), - BinFromFile; - false -> - {ok, Mod, CompiledBin} = compile:file(OrigBeamFile, [from_beam, native, binary]), - ok = file:write_file(CachedBeamFile, CompiledBin), - CompiledBin - end, - code:unstick_dir(filename:dirname(OrigBeamFile)), - {module, Mod} = code:load_binary(Mod, CachedBeamFile, ModBin), - true = code:is_module_native(Mod), - ok. - -cache_base_dir() -> - %% http://standards.freedesktop.org/basedir-spec/basedir-spec-0.7.html - %% If XDG_CACHE_HOME is set to an absolute path, use it as base. - XdgCacheHome = os:getenv("XDG_CACHE_HOME"), - CacheHome = - case is_list(XdgCacheHome) andalso filename:pathtype(XdgCacheHome) =:= absolute of - true -> - XdgCacheHome; - false -> - %% Otherwise, the default is $HOME/.cache. - {ok, [[Home]]} = init:get_argument(home), - filename:join(Home, ".cache") - end, - filename:join([CacheHome, "dialyzer_hipe_cache"]). - new_state() -> #cl_state{}. diff --git a/lib/dialyzer/vsn.mk b/lib/dialyzer/vsn.mk index 466bbfd0f2..7a44daf683 100644 --- a/lib/dialyzer/vsn.mk +++ b/lib/dialyzer/vsn.mk @@ -1 +1 @@ -DIALYZER_VSN = 4.0.1 +DIALYZER_VSN = 4.0.2 diff --git a/lib/hipe/doc/src/hipe_app.xml b/lib/hipe/doc/src/hipe_app.xml index 61d92fdffe..5ac445ac58 100644 --- a/lib/hipe/doc/src/hipe_app.xml +++ b/lib/hipe/doc/src/hipe_app.xml @@ -66,6 +66,10 @@ <item><p>The HiPE compiler will crash on modules containing binary matching.</p> </item> + <tag>try/catch</tag> + <item><p>The HiPE compiler will crash on modules containing 'try' or + 'catch'.</p> + </item> <tag>Stack traces</tag> <item><p>Stack traces returned from <seealso marker="erts:erlang#get_stacktrace/0"> diff --git a/lib/hipe/icode/hipe_beam_to_icode.erl b/lib/hipe/icode/hipe_beam_to_icode.erl index 8e7e56b6c4..995c961e09 100644 --- a/lib/hipe/icode/hipe_beam_to_icode.erl +++ b/lib/hipe/icode/hipe_beam_to_icode.erl @@ -557,32 +557,21 @@ trans_fun([{move,Src,Dst}|Instructions], Env) -> Dst1 = mk_var(Dst), Src1 = trans_arg(Src), [hipe_icode:mk_move(Dst1,Src1) | trans_fun(Instructions,Env)]; -%%--- catch --- ITS PROCESSING IS POSTPONED -trans_fun([{'catch',N,{_,EndLabel}}|Instructions], Env) -> - NewContLbl = mk_label(new), - [{'catch',N,EndLabel},NewContLbl | trans_fun(Instructions,Env)]; -%%--- catch_end --- ITS PROCESSING IS POSTPONED -trans_fun([{catch_end,_N}=I|Instructions], Env) -> - [I | trans_fun(Instructions,Env)]; -%%--- try --- ITS PROCESSING IS POSTPONED -trans_fun([{'try',N,{_,EndLabel}}|Instructions], Env) -> - NewContLbl = mk_label(new), - [{'try',N,EndLabel},NewContLbl | trans_fun(Instructions,Env)]; -%%--- try_end --- -trans_fun([{try_end,_N}|Instructions], Env) -> - [hipe_icode:mk_end_try() | trans_fun(Instructions,Env)]; -%%--- try_case --- ITS PROCESSING IS POSTPONED -trans_fun([{try_case,_N}=I|Instructions], Env) -> - [I | trans_fun(Instructions,Env)]; -%%--- try_case_end --- -trans_fun([{try_case_end,Arg}|Instructions], Env) -> - BadArg = trans_arg(Arg), - ErrVar = mk_var(new), - Vs = [mk_var(new)], - Atom = hipe_icode:mk_move(ErrVar,hipe_icode:mk_const(try_clause)), - Tuple = hipe_icode:mk_primop(Vs,mktuple,[ErrVar,BadArg]), - Fail = hipe_icode:mk_fail(Vs,error), - [Atom,Tuple,Fail | trans_fun(Instructions,Env)]; +%% +%% try/catch -- THESE ARE KNOWN TO MISCOMPILE, SEE OTP-15949 +%% +trans_fun([{'catch'=Name,_,_}|_], _Env) -> + nyi(Name); +trans_fun([{catch_end=Name,_}|_], _Env) -> + nyi(Name); +trans_fun([{'try'=Name,_,_}|_], _Env) -> + nyi(Name); +trans_fun([{try_end=Name,_}|_], _Env) -> + nyi(Name); +trans_fun([{try_case=Name,_}|_], _Env) -> + nyi(Name); +trans_fun([{try_case_end=Name,_}|_], _Env) -> + nyi(Name); %%--- raise --- trans_fun([{raise,{f,0},[Reg1,Reg2],{x,0}}|Instructions], Env) -> V1 = trans_arg(Reg1), diff --git a/lib/inets/doc/src/notes.xml b/lib/inets/doc/src/notes.xml index 03bd1d8042..45533c4f4b 100644 --- a/lib/inets/doc/src/notes.xml +++ b/lib/inets/doc/src/notes.xml @@ -33,7 +33,23 @@ <file>notes.xml</file> </header> - <section><title>Inets 7.0.8</title> + <section><title>Inets 7.0.9</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Fix a regression in http client that causes a crash when + request URI has no scheme.</p> + <p> + Own Id: OTP-15930 Aux Id: ERL-969 </p> + </item> + </list> + </section> + +</section> + +<section><title>Inets 7.0.8</title> <section><title>Fixed Bugs and Malfunctions</title> <list> diff --git a/lib/inets/src/http_client/httpc.erl b/lib/inets/src/http_client/httpc.erl index 24a205ced9..9967488f61 100644 --- a/lib/inets/src/http_client/httpc.erl +++ b/lib/inets/src/http_client/httpc.erl @@ -317,7 +317,7 @@ store_cookies(SetCookieHeaders, Url, Profile) {error, Bad, _} -> {error, {parse_failed, Bad}}; URI -> - Scheme = scheme_to_atom(maps:get(scheme, URI, '')), + Scheme = scheme_to_atom(maps:get(scheme, URI, undefined)), Host = maps:get(host, URI, ""), Port = maps:get(port, URI, default_port(Scheme)), Path = uri_string:recompose(#{path => maps:get(path, URI, "")}), @@ -536,7 +536,7 @@ handle_request(Method, Url, BracketedHost = proplists:get_value(ipv6_host_with_brackets, Options), - Scheme = scheme_to_atom(maps:get(scheme, URI, '')), + Scheme = scheme_to_atom(maps:get(scheme, URI, undefined)), Userinfo = maps:get(userinfo, URI, ""), Host = http_util:maybe_add_brackets(maps:get(host, URI, ""), BracketedHost), Port = maps:get(port, URI, default_port(Scheme)), @@ -591,8 +591,8 @@ scheme_to_atom("http") -> http; scheme_to_atom("https") -> https; -scheme_to_atom('') -> - ''; +scheme_to_atom(undefined) -> + throw({error, {no_scheme}}); scheme_to_atom(Scheme) -> throw({error, {bad_scheme, Scheme}}). diff --git a/lib/inets/test/httpc_SUITE.erl b/lib/inets/test/httpc_SUITE.erl index d4b33ae2c6..1d37e71847 100644 --- a/lib/inets/test/httpc_SUITE.erl +++ b/lib/inets/test/httpc_SUITE.erl @@ -106,7 +106,8 @@ real_requests()-> streaming_error, inet_opts, invalid_headers, - invalid_body + invalid_body, + no_scheme ]. real_requests_esi() -> @@ -1231,6 +1232,16 @@ invalid_body(Config) -> ok end. + +%%------------------------------------------------------------------------- + +no_scheme(_Config) -> + {error,{bad_scheme,"ftp"}} = httpc:request("ftp://foobar"), + {error,{no_scheme}} = httpc:request("//foobar"), + {error,{no_scheme}} = httpc:request("foobar"), + ok. + + %%------------------------------------------------------------------------- remote_socket_close(Config) when is_list(Config) -> URL = url(group_name(Config), "/just_close.html", Config), diff --git a/lib/inets/vsn.mk b/lib/inets/vsn.mk index 5dbec9e7b3..d948204618 100644 --- a/lib/inets/vsn.mk +++ b/lib/inets/vsn.mk @@ -19,6 +19,6 @@ # %CopyrightEnd% APPLICATION = inets -INETS_VSN = 7.0.8 +INETS_VSN = 7.0.9 PRE_VSN = APP_VSN = "$(APPLICATION)-$(INETS_VSN)$(PRE_VSN)" diff --git a/lib/ssl/doc/src/notes.xml b/lib/ssl/doc/src/notes.xml index 29bf5fc4e7..bb41b7e9ea 100644 --- a/lib/ssl/doc/src/notes.xml +++ b/lib/ssl/doc/src/notes.xml @@ -27,6 +27,37 @@ </header> <p>This document describes the changes made to the SSL application.</p> +<section><title>SSL 9.3.4</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Fix handling of certificate decoding problems in TLS 1.3 + similarly as in TLS 1.2.</p> + <p> + Own Id: OTP-15900</p> + </item> + <item> + <p> + Hibernation now works as expected in all cases, was + accidently broken by optimization efforts.</p> + <p> + Own Id: OTP-15910</p> + </item> + <item> + <p> + Fix interoperability problems with openssl when the TLS + 1.3 server is configured wirh the option + signature_algs_cert.</p> + <p> + Own Id: OTP-15913</p> + </item> + </list> + </section> + +</section> + <section><title>SSL 9.3.3</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl index 8e850b48a4..f317cb169e 100644 --- a/lib/ssl/src/tls_connection.erl +++ b/lib/ssl/src/tls_connection.erl @@ -258,7 +258,7 @@ next_event(StateName, Record, State) -> next_event(StateName, no_record, State0, Actions) -> case next_record(StateName, State0) of {no_record, State} -> - {next_state, StateName, State, Actions}; + ssl_connection:hibernate_after(StateName, State, Actions); {Record, State} -> next_event(StateName, Record, State, Actions) end; diff --git a/lib/ssl/src/tls_handshake_1_3.erl b/lib/ssl/src/tls_handshake_1_3.erl index 8a4ad922e1..53f9adbbd3 100644 --- a/lib/ssl/src/tls_handshake_1_3.erl +++ b/lib/ssl/src/tls_handshake_1_3.erl @@ -111,7 +111,7 @@ add_signature_algorithms_cert(Extensions, undefined) -> Extensions; add_signature_algorithms_cert(Extensions, SignAlgsCert) -> Extensions#{signature_algorithms_cert => - #signature_algorithms{signature_scheme_list = SignAlgsCert}}. + #signature_algorithms_cert{signature_scheme_list = SignAlgsCert}}. filter_tls13_algs(undefined) -> undefined; @@ -802,7 +802,7 @@ validate_certificate_chain(Certs, CertDbHandle, CertDbRef, SslOptions, CRLDbHand CertDbHandle, CertDbRef) end catch - error:{badmatch,{asn1, Asn1Reason}} -> + error:{badmatch,{error, {asn1, Asn1Reason}}} -> %% ASN-1 decode of certificate somehow failed {error, {certificate_unknown, {failed_to_decode_certificate, Asn1Reason}}}; error:OtherReason -> diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl index 4f6ea6c886..f5ce3a2b65 100644 --- a/lib/ssl/test/ssl_basic_SUITE.erl +++ b/lib/ssl/test/ssl_basic_SUITE.erl @@ -3660,7 +3660,7 @@ hibernate(Config) -> ssl_test_lib:check_result(Server, ok, Client, ok), - timer:sleep(1500), + ct:sleep(1500), {current_function, {erlang, hibernate, 3}} = process_info(Pid, current_function), @@ -3696,6 +3696,8 @@ hibernate_right_away(Config) -> [{port, Port1}, {options, [{hibernate_after, 0}|ClientOpts]}]), ssl_test_lib:check_result(Server1, ok, Client1, ok), + + ct:sleep(1000), %% Schedule out {current_function, {erlang, hibernate, 3}} = process_info(Pid1, current_function), diff --git a/lib/ssl/vsn.mk b/lib/ssl/vsn.mk index 01dee392f5..df38aea017 100644 --- a/lib/ssl/vsn.mk +++ b/lib/ssl/vsn.mk @@ -1 +1 @@ -SSL_VSN = 9.3.3 +SSL_VSN = 9.3.4 diff --git a/lib/stdlib/test/lists_SUITE.erl b/lib/stdlib/test/lists_SUITE.erl index 5dab6f6697..c3c54710eb 100644 --- a/lib/stdlib/test/lists_SUITE.erl +++ b/lib/stdlib/test/lists_SUITE.erl @@ -2600,6 +2600,15 @@ subtract(Config) when is_list(Config) -> [1,2,3,4,5,6,7,8,9,9999,10000,20,21,22] = sub(lists:seq(1, 10000)++[20,21,22], lists:seq(10, 9998)), + %% ERL-986; an integer overflow relating to term comparison + %% caused subtraction to be inconsistent. + Ids = [2985095936,47540628,135460048,1266126295,240535295, + 115724671,161800351,4187206564,4178142725,234897063, + 14773162,6662515191,133150693,378034895,1874402262, + 3507611978,22850922,415521280,253360400,71683243], + + [] = id(Ids) -- id(Ids), + %% Floats/integers. [42.0,42.0] = sub([42.0,42,42.0], [42,42,42]), [1,2,3,4,43.0] = sub([1,2,3,4,5,42.0,43.0], [42.0,5]), @@ -2627,6 +2636,8 @@ subtract(Config) when is_list(Config) -> ok. +id(I) -> I. + sub_non_matching(A, B) -> A = sub(A, B). diff --git a/make/otp_version_tickets b/make/otp_version_tickets index fd9b36720a..b741526c88 100644 --- a/make/otp_version_tickets +++ b/make/otp_version_tickets @@ -1,6 +1,10 @@ -OTP-15805 -OTP-15819 -OTP-15867 -OTP-15879 -OTP-15887 -OTP-15888 +OTP-15900 +OTP-15908 +OTP-15909 +OTP-15910 +OTP-15913 +OTP-15922 +OTP-15930 +OTP-15938 +OTP-15939 +OTP-15941 diff --git a/otp_versions.table b/otp_versions.table index 956a596877..43a41780b2 100644 --- a/otp_versions.table +++ b/otp_versions.table @@ -1,3 +1,4 @@ +OTP-22.0.5 : dialyzer-4.0.2 erts-10.4.4 inets-7.0.9 ssl-9.3.4 # asn1-5.0.9 common_test-1.17.3 compiler-7.4.2 crypto-4.5.1 debugger-4.2.7 diameter-2.2.1 edoc-0.11 eldap-1.2.8 erl_docgen-0.9.1 erl_interface-3.12 et-1.6.4 eunit-2.3.7 ftp-1.0.2 hipe-3.19 jinterface-1.10 kernel-6.4.1 megaco-3.18.5 mnesia-4.16 observer-2.9.1 odbc-2.12.4 os_mon-2.5 parsetools-2.1.8 public_key-1.6.7 reltool-0.8 runtime_tools-1.13.3 sasl-3.4 snmp-5.3 ssh-4.7.7 stdlib-3.9.2 syntax_tools-2.2 tftp-1.0.1 tools-3.2 wx-1.8.8 xmerl-1.3.21 : OTP-22.0.4 : erts-10.4.3 kernel-6.4.1 ssl-9.3.3 # asn1-5.0.9 common_test-1.17.3 compiler-7.4.2 crypto-4.5.1 debugger-4.2.7 dialyzer-4.0.1 diameter-2.2.1 edoc-0.11 eldap-1.2.8 erl_docgen-0.9.1 erl_interface-3.12 et-1.6.4 eunit-2.3.7 ftp-1.0.2 hipe-3.19 inets-7.0.8 jinterface-1.10 megaco-3.18.5 mnesia-4.16 observer-2.9.1 odbc-2.12.4 os_mon-2.5 parsetools-2.1.8 public_key-1.6.7 reltool-0.8 runtime_tools-1.13.3 sasl-3.4 snmp-5.3 ssh-4.7.7 stdlib-3.9.2 syntax_tools-2.2 tftp-1.0.1 tools-3.2 wx-1.8.8 xmerl-1.3.21 : OTP-22.0.3 : compiler-7.4.2 dialyzer-4.0.1 erts-10.4.2 ssl-9.3.2 stdlib-3.9.2 # asn1-5.0.9 common_test-1.17.3 crypto-4.5.1 debugger-4.2.7 diameter-2.2.1 edoc-0.11 eldap-1.2.8 erl_docgen-0.9.1 erl_interface-3.12 et-1.6.4 eunit-2.3.7 ftp-1.0.2 hipe-3.19 inets-7.0.8 jinterface-1.10 kernel-6.4 megaco-3.18.5 mnesia-4.16 observer-2.9.1 odbc-2.12.4 os_mon-2.5 parsetools-2.1.8 public_key-1.6.7 reltool-0.8 runtime_tools-1.13.3 sasl-3.4 snmp-5.3 ssh-4.7.7 syntax_tools-2.2 tftp-1.0.1 tools-3.2 wx-1.8.8 xmerl-1.3.21 : OTP-22.0.2 : compiler-7.4.1 crypto-4.5.1 erts-10.4.1 stdlib-3.9.1 # asn1-5.0.9 common_test-1.17.3 debugger-4.2.7 dialyzer-4.0 diameter-2.2.1 edoc-0.11 eldap-1.2.8 erl_docgen-0.9.1 erl_interface-3.12 et-1.6.4 eunit-2.3.7 ftp-1.0.2 hipe-3.19 inets-7.0.8 jinterface-1.10 kernel-6.4 megaco-3.18.5 mnesia-4.16 observer-2.9.1 odbc-2.12.4 os_mon-2.5 parsetools-2.1.8 public_key-1.6.7 reltool-0.8 runtime_tools-1.13.3 sasl-3.4 snmp-5.3 ssh-4.7.7 ssl-9.3.1 syntax_tools-2.2 tftp-1.0.1 tools-3.2 wx-1.8.8 xmerl-1.3.21 : |