diff options
25 files changed, 438 insertions, 159 deletions
diff --git a/erts/emulator/beam/io.c b/erts/emulator/beam/io.c index dfdbe475b3..2ef5bc4d01 100644 --- a/erts/emulator/beam/io.c +++ b/erts/emulator/beam/io.c @@ -4659,6 +4659,7 @@ erts_port_call(Process* c_p, unsigned ret_flags = 0U; Eterm term; Eterm* hp; + ErtsPortOpResult result; res = call_driver_call(c_p->common.id, prt, @@ -4672,25 +4673,36 @@ erts_port_call(Process* c_p, finalize_imm_drv_call(&try_call_state); if (bufp != &input_buf[0]) erts_free(ERTS_ALC_T_TMP, bufp); - if (res == ERTS_PORT_OP_BADARG) - return ERTS_PORT_OP_BADARG; - hsz = erts_decode_ext_size((byte *) resp_bufp, resp_size); - if (hsz < 0) - return ERTS_PORT_OP_BADARG; - hsz += 3; - erts_factory_proc_prealloc_init(&factory, c_p, hsz); - endp = (byte *) resp_bufp; - term = erts_decode_ext(&factory, (const byte**)&endp, 0); - if (term == THE_NON_VALUE) - return ERTS_PORT_OP_BADARG; - hp = erts_produce_heap(&factory,3,0); - *retvalp = TUPLE2(hp, am_ok, term); - erts_factory_close(&factory); + if (res == ERTS_PORT_OP_BADARG) { + result = ERTS_PORT_OP_BADARG; + } + else { + hsz = erts_decode_ext_size((byte *) resp_bufp, resp_size); + if (hsz < 0) { + result = ERTS_PORT_OP_BADARG; + } + else { + hsz += 3; + erts_factory_proc_prealloc_init(&factory, c_p, hsz); + endp = (byte *) resp_bufp; + term = erts_decode_ext(&factory, (const byte**)&endp, 0); + if (term == THE_NON_VALUE) { + result = ERTS_PORT_OP_BADARG; + } + else { + hp = erts_produce_heap(&factory,3,0); + *retvalp = TUPLE2(hp, am_ok, term); + result = ERTS_PORT_OP_DONE; + } + erts_factory_close(&factory); + } + } if (resp_bufp != &resp_buf[0] - && !(ret_flags & DRIVER_CALL_KEEP_BUFFER)) + && !(ret_flags & DRIVER_CALL_KEEP_BUFFER)) { driver_free(resp_bufp); + } BUMP_REDS(c_p, ERTS_PORT_REDS_CALL); - return ERTS_PORT_OP_DONE; + return result; } case ERTS_TRY_IMM_DRV_CALL_INVALID_PORT: if (bufp != &input_buf[0]) diff --git a/erts/emulator/beam/jit/arm/ops.tab b/erts/emulator/beam/jit/arm/ops.tab index cc0bfeccf0..9cfce18e58 100644 --- a/erts/emulator/beam/jit/arm/ops.tab +++ b/erts/emulator/beam/jit/arm/ops.tab @@ -666,18 +666,14 @@ i_perf_counter bif0 u$bif:erlang:self/0 Dst=d => self Dst bif0 u$bif:erlang:node/0 Dst=d => node Dst -bif1 Fail=f Bif=u$bif:erlang:hd/1 Src=n Dst => - jump Fail -bif1 Fail=f Bif=u$bif:erlang:hd/1 Src Dst => +bif1 Fail=f Bif=u$bif:erlang:hd/1 Src=xy Dst => is_nonempty_list Fail Src | get_hd Src Dst bif1 Fail=p Bif=u$bif:erlang:hd/1 Src Dst => bif_hd Src Dst bif_hd s d -bif1 Fail=f Bif=u$bif:erlang:tl/1 Src=n Dst => - jump Fail -bif1 Fail=f Bif=u$bif:erlang:tl/1 Src Dst => +bif1 Fail=f Bif=u$bif:erlang:tl/1 Src=xy Dst => is_nonempty_list Fail Src | get_tl Src Dst bif1 Fail=p Bif=u$bif:erlang:tl/1 Src Dst => bif_tl Src Dst diff --git a/erts/emulator/beam/jit/x86/instr_guard_bifs.cpp b/erts/emulator/beam/jit/x86/instr_guard_bifs.cpp index 4dfe39cb98..26133d5ac0 100644 --- a/erts/emulator/beam/jit/x86/instr_guard_bifs.cpp +++ b/erts/emulator/beam/jit/x86/instr_guard_bifs.cpp @@ -60,24 +60,22 @@ void BeamGlobalAssembler::emit_handle_hd_error() { * The code size for this specialization of hd/1 is 21 bytes, * while the code size for the bif1 instruction is 24 bytes. */ -void BeamModuleAssembler::emit_bif_hd(const ArgLabel &Fail, - const ArgSource &Src, +void BeamModuleAssembler::emit_bif_hd(const ArgSource &Src, const ArgRegister &Hd) { + Label good_cons = a.newLabel(); + mov_arg(RET, Src); a.test(RETb, imm(_TAG_PRIMARY_MASK - TAG_PRIMARY_LIST)); - if (Fail.get() != 0) { - a.jne(resolve_beam_label(Fail)); - } else { - Label next = a.newLabel(); - a.short_().je(next); - safe_fragment_call(ga->get_handle_hd_error()); - a.bind(next); - } + a.short_().je(good_cons); + safe_fragment_call(ga->get_handle_hd_error()); - x86::Gp boxed_ptr = emit_ptr_val(RET, RET); - a.mov(ARG2, getCARRef(boxed_ptr)); - mov_arg(Hd, ARG2); + a.bind(good_cons); + { + x86::Gp boxed_ptr = emit_ptr_val(RET, RET); + a.mov(ARG2, getCARRef(boxed_ptr)); + mov_arg(Hd, ARG2); + } } void BeamGlobalAssembler::emit_handle_element_error() { diff --git a/erts/emulator/beam/jit/x86/ops.tab b/erts/emulator/beam/jit/x86/ops.tab index 1ce0d6f9c2..5481fff2d8 100644 --- a/erts/emulator/beam/jit/x86/ops.tab +++ b/erts/emulator/beam/jit/x86/ops.tab @@ -660,18 +660,14 @@ i_perf_counter bif0 u$bif:erlang:self/0 Dst=d => self Dst bif0 u$bif:erlang:node/0 Dst=d => node Dst -bif1 Fail=f Bif=u$bif:erlang:hd/1 Src=n Dst => - jump Fail -bif1 Fail=f Bif=u$bif:erlang:hd/1 Src Dst => +bif1 Fail=f Bif=u$bif:erlang:hd/1 Src=xy Dst => is_nonempty_list Fail Src | get_hd Src Dst -bif1 Fail Bif=u$bif:erlang:hd/1 Src Dst => - bif_hd Fail Src Dst +bif1 Fail=p Bif=u$bif:erlang:hd/1 Src Dst => + bif_hd Src Dst -bif_hd j s d +bif_hd s d -bif1 Fail=f Bif=u$bif:erlang:tl/1 Src=n Dst => - jump Fail -bif1 Fail=f Bif=u$bif:erlang:tl/1 Src Dst => +bif1 Fail=f Bif=u$bif:erlang:tl/1 Src=xy Dst => is_nonempty_list Fail Src | get_tl Src Dst bif1 Fail Bif=u$bif:erlang:get/1 Src=s Dst=d => get(Src, Dst) diff --git a/lib/compiler/src/beam_validator.erl b/lib/compiler/src/beam_validator.erl index 98eacce0fc..b38d288f99 100644 --- a/lib/compiler/src/beam_validator.erl +++ b/lib/compiler/src/beam_validator.erl @@ -2299,6 +2299,8 @@ update_type(Merge, With, #value_ref{}=Ref, Vst0) -> case Merge(Current, With) of none -> throw({type_conflict, Current, With}); + Current -> + Vst0; Type -> Vst = update_container_type(Type, Ref, Vst0), set_type(Type, Ref, Vst) diff --git a/lib/compiler/src/sys_core_fold.erl b/lib/compiler/src/sys_core_fold.erl index d79753068f..d96f3a6ae0 100644 --- a/lib/compiler/src/sys_core_fold.erl +++ b/lib/compiler/src/sys_core_fold.erl @@ -215,15 +215,15 @@ expr(#c_tuple{anno=Anno,es=Es0}=Tuple, Ctxt, Sub) -> ann_c_tuple(Anno, Es) end; expr(#c_map{anno=Anno,arg=V0,es=Es0}=Map, Ctxt, Sub) -> - Es = pair_list(Es0, Ctxt, descend(Map, Sub)), + %% Warn for useless building, but always build the map + %% anyway to preserve a possible exception. case Ctxt of - effect -> - warn_useless_building(Map, Sub), - make_effect_seq(Es, Sub); - value -> - V = expr(V0, Ctxt, Sub), - ann_c_map(Anno,V,Es) - end; + effect -> warn_useless_building(Map, Sub); + value -> ok + end, + Es = pair_list(Es0, descend(Map, Sub)), + V = expr(V0, value, Sub), + ann_c_map(Anno, V, Es); expr(#c_binary{segments=Ss}=Bin0, Ctxt, Sub) -> %% Warn for useless building, but always build the binary %% anyway to preserve a possible exception. @@ -490,14 +490,12 @@ ifes_list(_FVar, [], _Safe) -> expr_list(Es, Ctxt, Sub) -> [expr(E, Ctxt, Sub) || E <- Es]. -pair_list(Es, Ctxt, Sub) -> - [pair(E, Ctxt, Sub) || E <- Es]. +pair_list(Es, Sub) -> + [pair(E, Sub) || E <- Es]. -pair(#c_map_pair{key=K,val=V}, effect, Sub) -> - make_effect_seq([K,V], Sub); -pair(#c_map_pair{key=K0,val=V0}=Pair, value=Ctxt, Sub) -> - K = expr(K0, Ctxt, Sub), - V = expr(V0, Ctxt, Sub), +pair(#c_map_pair{key=K0,val=V0}=Pair, Sub) -> + K = expr(K0, value, Sub), + V = expr(V0, value, Sub), Pair#c_map_pair{key=K,val=V}. bitstr_list(Es, Sub) -> diff --git a/lib/compiler/test/beam_validator_SUITE.erl b/lib/compiler/test/beam_validator_SUITE.erl index 64a356c7e4..aba3b8dfe2 100644 --- a/lib/compiler/test/beam_validator_SUITE.erl +++ b/lib/compiler/test/beam_validator_SUITE.erl @@ -41,7 +41,8 @@ missing_return_type/1,will_succeed/1, bs_saved_position_units/1,parent_container/1, container_performance/1, - not_equal_inference/1]). + not_equal_inference/1, + inert_update_type/1]). -include_lib("common_test/include/ct.hrl"). @@ -77,7 +78,8 @@ groups() -> missing_return_type,will_succeed, bs_saved_position_units,parent_container, container_performance, - not_equal_inference]}]. + not_equal_inference, + inert_update_type]}]. init_per_suite(Config) -> test_lib:recompile(?MODULE), @@ -1045,5 +1047,18 @@ not_equal_inference(_Config) -> not_equal_inference_1(X) when (X /= []) /= is_port(0 div 0) -> [X || _ <- []]. +%% GH-6969: A type was made concrete even though that added no additional +%% information. +inert_update_type(_Config) -> + hello(<<"string">>, id(42)). + +hello(A, B) -> + mike([{sys_period, {A, B}}, {some_atom, B}]). + +mike([Head | _Rest]) -> joe(Head). + +joe({Name, 42}) -> Name; +joe({sys_period, {A, _B}}) -> {41, 42, A}. + id(I) -> I. diff --git a/lib/compiler/test/bif_SUITE.erl b/lib/compiler/test/bif_SUITE.erl index 8480569507..34bf54a871 100644 --- a/lib/compiler/test/bif_SUITE.erl +++ b/lib/compiler/test/bif_SUITE.erl @@ -24,7 +24,8 @@ -export([all/0,suite/0,groups/0,init_per_suite/1,end_per_suite/1, init_per_group/2,end_per_group/2, beam_validator/1,trunc_and_friends/1,cover_safe_and_pure_bifs/1, - cover_trim/1]). + cover_trim/1, + head_tail/1]). suite() -> [{ct_hooks,[ts_install_cth]}]. @@ -37,7 +38,8 @@ groups() -> [beam_validator, trunc_and_friends, cover_safe_and_pure_bifs, - cover_trim + cover_trim, + head_tail ]}]. init_per_suite(Config) -> @@ -167,6 +169,28 @@ cover_trim_3(Header, N)-> false end. +%% GH-7024: The loader transformations for hd/1 and tl/1 were incorrect and +%% failed when certain optimizations were turned off. +head_tail(_Config) -> + {1, ok} = head_case(), + {1, ok} = tail_case(), + + 1 = hd(id([1])), + [] = tl(id([1])), + + ok. + +head_case() -> + case 1 of + X when hd(X) -> blurf; + X -> {X, ok} + end. + +tail_case() -> + case 1 of + X when tl(X) -> blurf; + X -> {X, ok} + end. id(I) -> I. diff --git a/lib/compiler/test/core_fold_SUITE.erl b/lib/compiler/test/core_fold_SUITE.erl index 4da55bed2e..0bdcfab5b7 100644 --- a/lib/compiler/test/core_fold_SUITE.erl +++ b/lib/compiler/test/core_fold_SUITE.erl @@ -29,7 +29,7 @@ no_no_file/1,configuration/1,supplies/1, redundant_stack_frame/1,export_from_case/1, empty_values/1,cover_letrec_effect/1, - receive_effect/1]). + receive_effect/1,map_effect/1]). -export([foo/0,foo/1,foo/2,foo/3]). @@ -50,7 +50,7 @@ groups() -> no_no_file,configuration,supplies, redundant_stack_frame,export_from_case, empty_values,cover_letrec_effect, - receive_effect]}]. + receive_effect,map_effect]}]. init_per_suite(Config) -> @@ -700,4 +700,20 @@ receive_effect(_Config) -> do_receive_effect() -> {} = receive _ -> {} = {} end. +map_effect(_Config) -> + {'EXIT',{{badkey,key},_}} = catch map_effect_1(), + + {'EXIT',{{badkey,key},_}} = catch map_effect_2(#{}), + {'EXIT',{{badmap,no_map},_}} = catch map_effect_2(no_map), + + ok. + +map_effect_1() -> + #{}#{key := value}, + ok. + +map_effect_2(Map) -> + Map#{key := value}, + ok. + id(I) -> I. diff --git a/lib/eldap/doc/src/eldap.xml b/lib/eldap/doc/src/eldap.xml index 4d9ec96a70..b3b8dc0a9b 100644 --- a/lib/eldap/doc/src/eldap.xml +++ b/lib/eldap/doc/src/eldap.xml @@ -548,6 +548,18 @@ Control2 = eldap:paged_result_control(PageSize, Cookie1), the series.</p> </desc> </func> + <func> + <name since="OTP @OTP-18480@">info(Handle) -> connection_info()</name> + <fsummary>Returns information about the LDAP connection. + </fsummary> + <type> + <v>connection_info() = #{socket := Socket, socket_type := tcp | ssl}</v> + <v>Socket = ssl:sslsocket() | gen_tcp:socket()</v> + </type> + <desc><p>Currently available information reveals the socket and the transport + protocol, TCP or TLS (SSL), used by the LDAP connection.</p> + </desc> + </func> </funcs> diff --git a/lib/eldap/src/eldap.erl b/lib/eldap/src/eldap.erl index 22d816c8c8..7edd012263 100644 --- a/lib/eldap/src/eldap.erl +++ b/lib/eldap/src/eldap.erl @@ -30,7 +30,8 @@ parse_ldap_url/1, paged_result_control/1, paged_result_control/2, - paged_result_cookie/1]). + paged_result_cookie/1, + info/1]). -export([neverDerefAliases/0, derefInSearching/0, derefFindingBaseObj/0, derefAlways/0]). @@ -155,6 +156,13 @@ controlling_process(Handle, Pid) when is_pid(Handle), is_pid(Pid) -> recv(Handle). %%% -------------------------------------------------------------------- +%%% Return LDAP socket information +%%% -------------------------------------------------------------------- +info(Handle) when is_pid(Handle) -> + send(Handle, info), + recv(Handle). + +%%% -------------------------------------------------------------------- %%% Authenticate ourselves to the Directory %%% using simple authentication. %%% @@ -608,6 +616,18 @@ loop(Cpid, Data) -> send(From, Result), ?MODULE:loop(Cpid, Data); + {From, info} -> + SocketType = + case Data#eldap.ldaps of + true -> + ssl; + false -> + tcp + end, + Res = #{socket => Data#eldap.fd, socket_type => SocketType}, + send(From, Res), + ?MODULE:loop(Cpid, Data); + {Cpid, 'EXIT', Reason} -> ?PRINT("Got EXIT from Cpid, reason=~p~n",[Reason]), exit(Reason); @@ -618,7 +638,6 @@ loop(Cpid, Data) -> end. - %%% -------------------------------------------------------------------- %%% startTLS Request %%% -------------------------------------------------------------------- diff --git a/lib/eldap/test/eldap_basic_SUITE.erl b/lib/eldap/test/eldap_basic_SUITE.erl index 5fa6d4ca69..2b26ed52bd 100644 --- a/lib/eldap/test/eldap_basic_SUITE.erl +++ b/lib/eldap/test/eldap_basic_SUITE.erl @@ -46,6 +46,7 @@ more_add/1, open_ret_val_error/1, open_ret_val_success/1, + plain_ldap_socket_info/1, search_filter_and/1, search_filter_and_not/1, search_filter_equalityMatch/1, @@ -63,6 +64,7 @@ search_extensible_match_without_dn/1, search_paged_results/1, ssl_connection/1, + ssl_conn_socket_info/1, start_tls_on_ssl_should_fail/1, start_tls_twice_should_fail/1, tcp_connection/1, @@ -81,8 +83,8 @@ suite/0 ]). -%%-include_lib("common_test/include/ct.hrl"). -include_lib("common_test/include/ct.hrl"). +-include_lib("stdlib/include/assert.hrl"). -include_lib("eldap/include/eldap.hrl"). -include_lib("eldap/ebin/ELDAPv3.hrl"). @@ -159,7 +161,9 @@ connection_tests() -> client_side_bind_timeout, client_side_add_timeout, client_side_search_timeout, - close_after_tcp_error + close_after_tcp_error, + ssl_conn_socket_info, + plain_ldap_socket_info ]. @@ -259,7 +263,7 @@ end_per_group(start_tls_api, Config) -> clear_db(Config); end_per_group(_Group, Config) -> Config. -init_per_testcase(ssl_connection, Config) -> +init_per_testcase(TC, Config) when TC == ssl_connection; TC == ssl_conn_socket_info -> case proplists:get_value(ssl_available,Config) of true -> SSL_Port = 9999, @@ -423,6 +427,35 @@ ssl_connection(Config) -> end. %%%---------------------------------------------------------------- +ssl_conn_socket_info(Config) -> + Host = proplists:get_value(listen_host, Config), + Port = proplists:get_value(ssl_listen_port, Config), + Opts = proplists:get_value(tcp_connect_opts, Config), + SSLOpts = proplists:get_value(ssl_connect_opts, Config), + case eldap:open([Host], [{port,Port}, + {ssl,true}, + {timeout,5000}, + {sslopts,SSLOpts}|Opts]) of + {ok,H} -> + #{socket := Socket, socket_type := ssl} = eldap:info(H), + ?assertMatch({ok, _Data}, ssl:connection_information(Socket)); + Other -> ct:fail("eldap:open failed: ~p",[Other]) + end. + +%%%---------------------------------------------------------------- +plain_ldap_socket_info(Config) -> + Host = proplists:get_value(listen_host, Config), + Port = proplists:get_value(listen_port, Config), + Opts = proplists:get_value(tcp_connect_opts, Config), + T = 1000, + case eldap:open([Host], [{timeout,T},{port,Port}|Opts]) of + {ok,H} -> + ?assertMatch(#{socket := _, socket_type := tcp}, + eldap:info(H)); + Other -> ct:fail("eldap:open failed: ~p",[Other]) + end. + +%%%---------------------------------------------------------------- client_side_add_timeout(Config) -> client_timeout( fun(H) -> diff --git a/lib/inets/src/http_client/httpc_handler.erl b/lib/inets/src/http_client/httpc_handler.erl index 63e26f5266..b7b8dd7055 100644 --- a/lib/inets/src/http_client/httpc_handler.erl +++ b/lib/inets/src/http_client/httpc_handler.erl @@ -322,7 +322,7 @@ terminate(normal, cancel_timers(Timers), %% Maybe deliver answers to requests - deliver_answer(Request), + maybe_deliver_answer(Request, State), %% And, just in case, close our side (**really** overkill) http_transport:close(SocketType, Socket); @@ -706,24 +706,26 @@ call(Msg, Pid) -> cast(Msg, Pid) -> gen_server:cast(Pid, Msg). -maybe_retry_queue(Q, State) -> - case queue:is_empty(Q) of - false -> +maybe_retry_queue(Q, #state{status = new} = State) -> + retry_pipeline(queue:to_list(Q), State); +maybe_retry_queue(Q, #state{request = Request} = State) -> + case Request of + undefined -> retry_pipeline(queue:to_list(Q), State); - true -> - ok + _ -> + retry_pipeline(queue:to_list(queue:cons(Request, Q)), State) end. - + maybe_send_answer(#request{from = answer_sent}, _Reason, State) -> State; maybe_send_answer(Request, Answer, State) -> answer_request(Request, Answer, State). -deliver_answer(#request{from = From} = Request) +maybe_deliver_answer(#request{from = From} = Request, #state{status = new}) when From =/= answer_sent -> Response = httpc_response:error(Request, socket_closed_remotely), httpc_response:send(From, Response); -deliver_answer(_Request) -> +maybe_deliver_answer(_,_) -> ok. %%%-------------------------------------------------------------------- diff --git a/lib/snmp/doc/src/snmp_app.xml b/lib/snmp/doc/src/snmp_app.xml index 0613de3158..1400e7bf32 100644 --- a/lib/snmp/doc/src/snmp_app.xml +++ b/lib/snmp/doc/src/snmp_app.xml @@ -4,7 +4,7 @@ <appref> <header> <copyright> - <year>1997</year><year>2021</year> + <year>1997</year><year>2023</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -295,14 +295,15 @@ in the snmp_config file! <c><![CDATA[agent_net_if_options() = [agent_net_if_option()] <optional>]]></c></tag> <item> <p><c>agent_net_if_option() = - {bind_to, bind_to()} | - {sndbuf, sndbuf()} | - {recbuf, recbuf()} | - {no_reuse, no_reuse()} | - {req_limit, req_limit()} | - {filter, agent_net_if_filter_options()} | - {extra_sock_opts, extra_socket_options()} | - {inet_backend, inet | socket}</c></p> + {bind_to, bind_to()} | + {sndbuf, sndbuf()} | + {recbuf, recbuf()} | + {no_reuse, no_reuse()} | + {req_limit, req_limit()} | + {filter, agent_net_if_filter_options()} | + {open_err_filters, agent_net_if_open_err_filters()} | + {extra_sock_opts, extra_socket_options()} | + {inet_backend, inet | socket}</c></p> <p>These options are actually specific to the used module. The ones shown here are applicable to the default <c>agent_net_if_module()</c>.</p> @@ -343,6 +344,25 @@ in the snmp_config file! <p>Default is <c>snmpa_net_if_filter</c>.</p> </item> + <tag><marker id="agent_ni_open_err_filters"></marker> + <c><![CDATA[agent_net_if_open_err_filters() = [agent_net_if_open_err_filter()] <optional>]]></c></tag> + <item> + <p><c>agent_net_if_open_err_filter() = atom()</c></p> + <p>During agent initiation, the transports UDP sockets are opened. + If this operation fails, the net-if (and the agent) fails to start + (crash). This (filter) list contains error (reasons) that will + make net-if fail "nicely". + This (filter) list, is supposed to contain errors that can be + returned by + <seemfa marker="kernel:gen_udp#open/1">gen_udp:open/1,2</seemfa>. + The effect is that any error returned by + <seemfa marker="kernel:gen_udp#open/1">gen_udp:open</seemfa> + which *are* in this list, will be considered + "non-fatal" and will only result in an info message, rather than + an error message. Net If, and the agent, will still crash, + but will produce a less obnoxious message. </p> + </item> + <tag><marker id="agent_mibs"></marker> <c><![CDATA[agent_mibs() = [string()] <optional>]]></c></tag> <item> diff --git a/lib/snmp/doc/src/snmp_config.xml b/lib/snmp/doc/src/snmp_config.xml index 0b8d786a2d..99324ed400 100644 --- a/lib/snmp/doc/src/snmp_config.xml +++ b/lib/snmp/doc/src/snmp_config.xml @@ -4,7 +4,7 @@ <chapter> <header> <copyright> - <year>1997</year><year>2021</year> + <year>1997</year><year>2023</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -281,14 +281,15 @@ <c><![CDATA[agent_net_if_options() = [agent_net_if_option()] <optional>]]></c></tag> <item> <p><c>agent_net_if_option() = - {bind_to, bind_to()} | - {sndbuf, sndbuf()} | - {recbuf, recbuf()} | - {no_reuse, no_reuse()} | - {req_limit, req_limit()} | - {filter, agent_net_if_filter_options()} | - {extra_sock_opts, extra_socket_options()} | - {inet_backend, inet | socket}</c></p> + {bind_to, bind_to()} | + {sndbuf, sndbuf()} | + {recbuf, recbuf()} | + {no_reuse, no_reuse()} | + {req_limit, req_limit()} | + {filter, agent_net_if_filter_options()} | + {extra_sock_opts, extra_socket_options()} | + {open_err_filters, agent_net_if_open_err_filters()} | + {inet_backend, inet | socket}</c></p> <p>These options are actually specific to the used module. The ones shown here are applicable to the default <c>agent_net_if_module()</c>.</p> @@ -329,6 +330,25 @@ <p>Default is <c>snmpa_net_if_filter</c>.</p> </item> + <tag><marker id="agent_ni_open_err_filters"></marker> + <c><![CDATA[agent_net_if_open_err_filters() = [agent_net_if_open_err_filter()] <optional>]]></c></tag> + <item> + <p><c>agent_net_if_open_err_filter() = atom()</c></p> + <p>During agent initiation, the transports UDP sockets are opened. + If this operation fails, the net-if (and the agent) fails to start + (crash). This (filter) list contains error (reasons) that will + make net-if fail "nicely". + This (filter) list, is supposed to contain errors that can be + returned by + <seemfa marker="kernel:gen_udp#open/1">gen_udp:open/1,2</seemfa>. + The effect is that any error returned by + <seemfa marker="kernel:gen_udp#open/1">gen_udp:open</seemfa> + which *are* in this list, will be considered + "non-fatal" and will only result in an info message, rather than + an error message. Net If, and the agent, will still crash, + but will produce a less obnoxious message. </p> + </item> + <tag><marker id="agent_mibs"></marker> <c><![CDATA[agent_mibs() = [string()] <optional>]]></c></tag> <item> diff --git a/lib/snmp/src/agent/snmpa_agent.erl b/lib/snmp/src/agent/snmpa_agent.erl index 736b2907fa..8c24440efc 100644 --- a/lib/snmp/src/agent/snmpa_agent.erl +++ b/lib/snmp/src/agent/snmpa_agent.erl @@ -340,10 +340,16 @@ init([Prio, Parent, Ref, Options]) -> "~n Options: ~p", [Prio, Parent, Ref, Options]), case (catch do_init(Prio, Parent, Ref, Options)) of {ok, State} -> - ?vdebug("started",[]), + ?vdebug("started"), {ok, State}; + {error, {net_if, info, Reason}} -> + info_msg("Failed starting agent: " + "~n Net If error: ~p", [Reason]), + %% {shutdown, Reason}; + exit(Reason); {error, Reason} -> - config_err("failed starting agent: ~n~p", [Reason]), + config_err("Failed starting agent: " + "~n ~p", [Reason]), {stop, Reason} end. @@ -410,15 +416,18 @@ start_note_store(Prio, Ref, Options) -> {ok, Pid} -> ?vdebug("start_note_store -> Pid: ~p", [Pid]), Pid; - {error, Reason} -> - ?vinfo("error starting note store: ~n~p",[Reason]), - throw({error, {note_store_error, Reason}}); - {'EXIT', Reason} -> - ?vinfo("exit starting note store: ~n~p",[Reason]), - throw({error, {note_store_exit, Reason}}); + {error, {Reason, _ChildSpec}} -> + ?vinfo("error starting note store: " + "~n ~p", [Reason]), + throw({error, {note_store, error, Reason}}); + {'EXIT', {Reason, _ChildSpec}} -> + ?vinfo("exit starting note store: " + "~n ~p", [Reason]), + throw({error, {note_store, exit, Reason}}); Error -> - ?vinfo("failed starting note store: ~n~p",[Error]), - throw({error, {note_store_failed, Error}}) + ?vinfo("failed starting note store: " + "~n ~p", [Error]), + throw({error, {note_store, failed, Error}}) end. @@ -437,18 +446,25 @@ start_net_if(none, Prio, Ref, Vsns, NoteStore, Options) -> case (catch snmpa_misc_sup:start_net_if(Prio, NoteStore, Ref, self(), Mod, NiOpts)) of - {ok, Pid} -> + {ok, Pid} -> ?vdebug("start_net_if -> Pid: ~p", [Pid]), {master_agent, Pid, Mod}; - {error, Reason} -> - ?vinfo("error starting net if: ~n~p",[Reason]), - throw({error, {net_if_error, Reason}}); + {error, {{Class, udp_open, PortNo, Reason}, _ChildSpec}} -> + ?vinfo("error starting net if: " + "~n ~p", [Reason]), + throw({error, {net_if, Class, {udp_open, PortNo, Reason}}}); + {error, {Reason, _ChildSpec}} -> + ?vinfo("error starting net if: " + "~n ~p", [Reason]), + throw({error, {net_if, error, Reason}}); {'EXIT', Reason} -> - ?vinfo("exit starting net if: ~n~p",[Reason]), - throw({error, {net_if_exit, Reason}}); + ?vinfo("exit starting net if: " + "~n ~p", [Reason]), + throw({error, {net_if, exit, Reason}}); Error -> - ?vinfo("failed starting net if: ~n~p",[Error]), - throw({error, {net_if_failed, Error}}) + ?vinfo("failed starting net if: " + "~n ~p", [Error]), + throw({error, {net_if, failed, Error}}) end; start_net_if(Parent, _Prio, _Ref, _Vsns, _NoteStore, _Options) when is_pid(Parent) -> @@ -470,15 +486,18 @@ start_mib_server(Prio, Ref, Mibs, Options) -> {ok, Pid} -> ?vdebug("start_mib_server -> Pid: ~p", [Pid]), Pid; - {error, Reason} -> - ?vinfo("error starting mib server: ~n~p",[Reason]), - throw({error, {mib_server_error, Reason}}); + {error, {Reason, _ChildSpec}} -> + ?vinfo("error starting mib server: " + "~n ~p", [Reason]), + throw({error, {mib_server, error, Reason}}); {'EXIT', Reason} -> - ?vinfo("exit starting mib server: ~n~p",[Reason]), - throw({error, {mib_server_exit, Reason}}); + ?vinfo("exit starting mib server: " + "~n ~p", [Reason]), + throw({error, {mib_server, exit, Reason}}); Error -> - ?vinfo("failed starting mib server: ~n~p",[Error]), - throw({error, {mib_server_failed, Error}}) + ?vinfo("failed starting mib server: " + "~n ~p", [Error]), + throw({error, {mib_server, failed, Error}}) end. @@ -3204,8 +3223,8 @@ get_stats_counters([Counter|Counters], Acc) -> %% --------------------------------------------------------------------- -%% info_msg(F, A) -> -%% ?snmpa_info(F, A). +info_msg(F, A) -> + ?snmpa_info(F, A). warning_msg(F, A) -> ?snmpa_warning(F, A). diff --git a/lib/snmp/src/agent/snmpa_agent_sup.erl b/lib/snmp/src/agent/snmpa_agent_sup.erl index 0a5116b2d0..b1eeaba988 100644 --- a/lib/snmp/src/agent/snmpa_agent_sup.erl +++ b/lib/snmp/src/agent/snmpa_agent_sup.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1997-2016. All Rights Reserved. +%% Copyright Ericsson AB 1997-2023. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -24,7 +24,9 @@ -behaviour(supervisor). %% External exports --export([start_link/0, start_link/1, start_subagent/3, stop_subagent/1]). +-export([start_link/0, start_link/1, + start_master_agent/1, + start_subagent/3, stop_subagent/1]). %% Internal exports -export([init/1]). @@ -40,8 +42,7 @@ %%%----------------------------------------------------------------- -%%% This is a supervisor for the mib processes. Each agent has one -%%% mib process. +%%% This is a supervisor for the agent processes (master and sub). %%%----------------------------------------------------------------- start_link() -> ?d("start_link -> entry", []), @@ -52,6 +53,9 @@ start_link(AgentSpec) -> "~n AgentSpec: ~p", [AgentSpec]), supervisor:start_link({local, ?SERVER}, ?MODULE, [[AgentSpec]]). +start_master_agent(MasterAgentSpec) -> + supervisor:start_child(snmpa_agent_sup, MasterAgentSpec). + start_subagent(ParentAgent, Subtree, Mibs) -> ?d("start_subagent -> entry with" "~n ParentAgent: ~p" diff --git a/lib/snmp/src/agent/snmpa_net_if.erl b/lib/snmp/src/agent/snmpa_net_if.erl index d37aa6c28a..72e3ac3e41 100644 --- a/lib/snmp/src/agent/snmpa_net_if.erl +++ b/lib/snmp/src/agent/snmpa_net_if.erl @@ -235,11 +235,21 @@ init(Prio, NoteStore, MasterAgent, Parent, Opts) -> end, erlang:raise(C, E, S) end; - {error, Reason} -> - config_err("failed starting net-if: ~n~p", [Reason]), - proc_lib:init_ack({error, Reason}); + {error, {udp_open, {open, PortNo, Reason}}} -> + OEFilters = get_open_err_filters(Opts), + Class = + case lists:member(Reason, OEFilters) of + false -> + error; + true -> + info + end, + proc_lib:init_ack({error, {Class, udp_open, PortNo, Reason}}); + {error, Reason} -> + %% config_err("failed starting net-if: ~n~p", [Reason]), + proc_lib:init_ack({error, Reason}); Error -> - config_err("failed starting net-if: ~n~p", [Error]), + %% config_err("failed starting net-if: ~n~p", [Error]), proc_lib:init_ack({error, Error}) end. @@ -256,7 +266,7 @@ do_init(Prio, NoteStore, MasterAgent, Parent, Opts) -> Vsns = get_vsns(Opts), ?vdebug("vsns: ~w",[Vsns]), - %% Flow control -- + %% -- Flow control -- Limit = get_req_limit(Opts), ?vdebug("Limit: ~w", [Limit]), FilterOpts = get_filter_opts(Opts), @@ -475,7 +485,7 @@ gen_udp_open(system, Opts) -> throw({udp_open, {port, PReason}}) end; {error, OReason} -> - throw({udp_open, {open, OReason}}) + throw({udp_open, {open, 0, OReason}}) end; %% This is for "future compat" since we cannot actually config '0'... gen_udp_open(IpPort, Opts) when (IpPort =:= 0) -> @@ -533,7 +543,7 @@ gen_udp_range_open(Min, Max, Opts) -> gen_udp_range_open(Min+1, Max, Opts); {error, Reason} -> ?vdebug("gen_udp_range_open(~w,~w) -> ~w", [Reason]), - throw({udp_open, {open, Reason}}) + throw({udp_open, {open, Min, Reason}}) catch C:E:S -> ?vinfo("gen_udp_range_open(~w,~w) -> failed open socket: " @@ -2101,6 +2111,16 @@ get_filter_opts(O) -> get_filter_module(O) -> snmp_misc:get_option(module, O, ?DEFAULT_FILTER_MODULE). +get_open_err_filters(O) -> + case snmp_misc:get_option(open_err_filters, O, []) of + Filters when is_list(Filters) -> + Filters; + Filter when is_atom(Filter) -> + [Filter]; + _ -> + [] + end. + get_recbuf(Opts, DefaultOpts) -> get_socket_opt(recbuf, Opts, DefaultOpts, use_default). @@ -2153,8 +2173,8 @@ info_msg(F,A) -> user_err(F, A) -> snmpa_error:user_err(F, A). -config_err(F, A) -> - snmpa_error:config_err(F, A). +%% config_err(F, A) -> +%% snmpa_error:config_err(F, A). %% ---------------------------------------------------------------- diff --git a/lib/snmp/src/agent/snmpa_supervisor.erl b/lib/snmp/src/agent/snmpa_supervisor.erl index 215851f23c..26c61579d5 100644 --- a/lib/snmp/src/agent/snmpa_supervisor.erl +++ b/lib/snmp/src/agent/snmpa_supervisor.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1996-2022. All Rights Reserved. +%% Copyright Ericsson AB 1996-2023. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -29,6 +29,7 @@ %% Internal exports -export([init/1, config/2]). +-compile({no_auto_import,[erase/1]}). -define(SERVER, ?MODULE). @@ -41,6 +42,10 @@ %% Process structure %% ================= %% +%% "application" +%% | +%% app-sup +%% | %% ___________________ supervisor __________________ %% / | | \ \ %% ___misc_sup___ target_cache symbolic_store local_db agent_sup @@ -176,7 +181,23 @@ start_master_sup(Opts) -> do_start_master_sup(Opts) -> verify_mandatory([db_dir], Opts), - supervisor:start_link({local, ?SERVER}, ?MODULE, [master, Opts]). + case supervisor:start_link({local, ?SERVER}, ?MODULE, [master, Opts]) of + {ok, Pid} = OK -> + %% <HACKETI-HACK-HACK> + Key = master_agent_child_spec, + MasterAgentSpec = lookup(Key), + case snmpa_agent_sup:start_master_agent(MasterAgentSpec) of + {ok, MPid} when is_pid(MPid) -> + erase(Key), + OK; + {error, {Reason, _ChildSpec}} -> + stop(Pid, 0), + {error, Reason} + end; + %% </HACKETI-HACK-HACK> + Else -> + Else + end. verify_mandatory([], _) -> ok; @@ -503,9 +524,18 @@ init([AgentType, Opts]) -> worker_spec(snmpa_agent, [Prio, snmp_master_agent, none, Ref, AgentOpts], Restart, 15000), + %% <HACKETI-HACK-HACK> + %% The point is to make start failure more quiet + %% Often the failure happens in the master agent, + %% so we move the start of that out of this function + %% and into the 'do_start_master_sup' function. + %% At some point we should rewrite this. Maybe start all + %% children the same way (explicitly). + store(master_agent_child_spec, AgentSpec), AgentSupSpec = - sup_spec(snmpa_agent_sup, [AgentSpec], + sup_spec(snmpa_agent_sup, [], Restart, infinity), + %% </HACKETI-HACK-HACK> [ConfigSpec, AgentSupSpec]; _ -> ?vdebug("[sub agent] spec for the agent supervisor",[]), @@ -521,6 +551,13 @@ init([AgentType, Opts]) -> store(Key, Value) -> ets:insert(snmp_agent_table, {Key, Value}). +lookup(Key) -> + [{Key, Value}] = ets:lookup(snmp_agent_table, Key), + Value. + +erase(Key) -> + ets:delete(snmp_agent_table, Key). + get_mibs(Mibs, Vsns) -> MibDir = filename:join(code:priv_dir(snmp), "mibs"), StdMib = diff --git a/lib/snmp/src/app/snmp_app.erl b/lib/snmp/src/app/snmp_app.erl index 486b276383..7ba3c659f0 100644 --- a/lib/snmp/src/app/snmp_app.erl +++ b/lib/snmp/src/app/snmp_app.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2003-2021. All Rights Reserved. +%% Copyright Ericsson AB 2003-2023. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -38,8 +38,13 @@ start(Type, []) -> %% First start the (new) central supervisor, {ok, Pid} = snmp_app_sup:start_link(), Entities = entities(), - ok = start_entities(Type, Entities), - {ok, Pid}. + case start_entities(Type, Entities) of + ok -> + {ok, Pid}; + Error -> + snmp_app_sup:stop(), + Error + end. entities() -> entities([agent, manager], []). diff --git a/lib/snmp/src/app/snmp_app_sup.erl b/lib/snmp/src/app/snmp_app_sup.erl index eb89cc5b6d..bb6faa18cb 100644 --- a/lib/snmp/src/app/snmp_app_sup.erl +++ b/lib/snmp/src/app/snmp_app_sup.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2003-2016. All Rights Reserved. +%% Copyright Ericsson AB 2003-2023. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -64,7 +64,12 @@ start_agent(Type, Opts) -> "~n Type: ~p" "~n Opts: ~p", [Type, Opts]), Restart = get_restart(Opts, permanent), - start_sup_child(snmpa_supervisor, Restart, [Type, Opts]). + case start_sup_child(snmpa_supervisor, Restart, [Type, Opts]) of + {ok, Pid} = OK when is_pid(Pid) -> + OK; + {error, {Reason, _ChildSpec}} -> + {error, Reason} + end. start_manager(Type, Opts) -> @@ -72,7 +77,12 @@ start_manager(Type, Opts) -> "~n Type: ~p" "~n Opts: ~p", [Type, Opts]), Restart = get_restart(Opts, transient), - start_sup_child(snmpm_supervisor, Restart, [Type, Opts]). + case start_sup_child(snmpm_supervisor, Restart, [Type, Opts]) of + {ok, Pid} = OK when is_pid(Pid) -> + OK; + {error, {Reason, _ChildSpec}} -> + {error, Reason} + end. %%%------------------------------------------------------------------- diff --git a/lib/ssl/src/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl index 87dd624306..6e952a4584 100644 --- a/lib/ssl/src/ssl_cipher.erl +++ b/lib/ssl/src/ssl_cipher.erl @@ -647,7 +647,7 @@ signature_scheme(SignAlgo) when is_integer(SignAlgo) -> signature_scheme(_) -> unassigned. signature_schemes_1_2(SigAlgs) -> - lists:foldl(fun(Alg, Acc) when is_atom(Alg) -> + lists:reverse(lists:foldl(fun(Alg, Acc) when is_atom(Alg) -> case scheme_to_components(Alg) of {Hash, Sign = rsa_pss_pss,_} -> [{Hash, Sign} | Acc]; @@ -658,7 +658,7 @@ signature_schemes_1_2(SigAlgs) -> end; (Alg, Acc) -> [Alg| Acc] - end, [], SigAlgs). + end, [], SigAlgs)). %% TODO: reserved code points? diff --git a/lib/ssl/test/ssl_cipher_SUITE.erl b/lib/ssl/test/ssl_cipher_SUITE.erl index def13d0860..70c0b50d4a 100644 --- a/lib/ssl/test/ssl_cipher_SUITE.erl +++ b/lib/ssl/test/ssl_cipher_SUITE.erl @@ -41,7 +41,8 @@ aes_decipher_good/1, aes_decipher_fail/0, aes_decipher_fail/1, - padding_test/1 + padding_test/1, + sign_algorithms/1 ]). @@ -49,7 +50,7 @@ %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- all() -> - [aes_decipher_good, aes_decipher_fail, padding_test]. + [aes_decipher_good, aes_decipher_fail, padding_test, sign_algorithms]. groups() -> []. @@ -122,6 +123,10 @@ padding_test(Config) when is_list(Config) -> pad_test(HashSz, CipherState, {3,2}), pad_test(HashSz, CipherState, {3,3}). +%%-------------------------------------------------------------------- +sign_algorithms(Config) when is_list(Config) -> + [{sha256,rsa_pss_pss},{rsa,sha256}] = ssl_cipher:signature_schemes_1_2([rsa_pss_pss_sha256, {rsa, sha256}]). + %%-------------------------------------------------------------------- % Internal functions -------------------------------------------------------- %%-------------------------------------------------------------------- diff --git a/lib/ssl/test/ssl_session_SUITE.erl b/lib/ssl/test/ssl_session_SUITE.erl index 40fff3bbbd..f597c78bb5 100644 --- a/lib/ssl/test/ssl_session_SUITE.erl +++ b/lib/ssl/test/ssl_session_SUITE.erl @@ -464,8 +464,11 @@ no_reuses_session_server_restart_new_cert() -> no_reuses_session_server_restart_new_cert(Config) when is_list(Config) -> ClientOpts = ssl_test_lib:ssl_options(client_rsa_der_opts, Config), ServerOpts = ssl_test_lib:ssl_options(server_rsa_der_verify_opts, Config), - RSA1024ServerOpts = ssl_test_lib:ssl_options(server_rsa_1024_der_opts, Config), - RSA1024ClientOpts = ssl_test_lib:ssl_options(client_rsa_1024_der_opts, Config), + #{client_config := NewCOpts, + server_config := NewSOpts} = ssl_test_lib:make_cert_chains_der(rsa, + [[{key, ssl_test_lib:hardcode_rsa_key(4)}], + [{key, ssl_test_lib:hardcode_rsa_key(5)}], + [{key, ssl_test_lib:hardcode_rsa_key(6)}]]), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), @@ -490,12 +493,12 @@ no_reuses_session_server_restart_new_cert(Config) when is_list(Config) -> Server1 = ssl_test_lib:start_server([{node, ServerNode}, {port, Port}, {from, self()}, {mfa, {ssl_test_lib, session_info_result, []}}, - {options, [{reuseaddr, true} | RSA1024ServerOpts]}]), + {options, [{reuseaddr, true} | NewSOpts]}]), Client1 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, {mfa, {ssl_test_lib, session_info_result, []}}, - {from, self()}, {options, RSA1024ClientOpts}]), + {from, self()}, {options, NewCOpts}]), Info1 = receive {Server1, Info10} -> Info10 end, receive @@ -517,7 +520,12 @@ no_reuses_session_server_restart_new_cert_file() -> no_reuses_session_server_restart_new_cert_file(Config) when is_list(Config) -> ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), - RSA1024ServerOpts = ssl_test_lib:ssl_options(server_rsa_1024_verify_opts, Config), + #{client_config := NewCOpts, + server_config := NewSOpts} = ssl_test_lib:make_cert_chains_pem(rsa, + [[{key, ssl_test_lib:hardcode_rsa_key(4)}], + [{key, ssl_test_lib:hardcode_rsa_key(5)}], + [{key, ssl_test_lib:hardcode_rsa_key(6)}]], + Config, "ssl_session_new_rsa"), PrivDir = proplists:get_value(priv_dir, Config), NewServerOpts0 = ssl_test_lib:new_config(PrivDir, ServerOpts), @@ -548,7 +556,7 @@ no_reuses_session_server_restart_new_cert_file(Config) when is_list(Config) -> ssl:clear_pem_cache(), - NewServerOpts1 = ssl_test_lib:new_config(PrivDir, RSA1024ServerOpts), + NewServerOpts1 = ssl_test_lib:new_config(PrivDir, NewSOpts), Server1 = ssl_test_lib:start_server([{node, ServerNode}, {port, Port}, @@ -559,7 +567,7 @@ no_reuses_session_server_restart_new_cert_file(Config) when is_list(Config) -> ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, {mfa, {ssl_test_lib, session_info_result, []}}, - {from, self()}, {options, ClientOpts}]), + {from, self()}, {options, NewCOpts}]), receive {Client1, SessionInfo} -> ct:fail(session_reused_when_server_has_new_cert); diff --git a/lib/stdlib/src/erl_parse.yrl b/lib/stdlib/src/erl_parse.yrl index f97ea99e60..6228c5857a 100644 --- a/lib/stdlib/src/erl_parse.yrl +++ b/lib/stdlib/src/erl_parse.yrl @@ -661,6 +661,7 @@ Erlang code. -type abstract_expr() :: af_literal() | af_match(abstract_expr()) + | af_maybe_match() | af_variable() | af_tuple(abstract_expr()) | af_nil() @@ -687,7 +688,9 @@ Erlang code. | af_local_fun() | af_remote_fun() | af_fun() - | af_named_fun(). + | af_named_fun() + | af_maybe() + | af_maybe_else(). -type af_record_update(T) :: {'record', anno(), @@ -830,6 +833,9 @@ Erlang code. -type af_map_pattern() :: {'map', anno(), [af_assoc_exact(af_pattern())]}. +-type af_maybe() :: {'maybe', anno(), af_body()}. +-type af_maybe_else() :: {'maybe', anno(), af_body(), {'else', anno(), af_clause_seq()}}. + -type abstract_type() :: af_annotated_type() | af_atom() | af_bitstring_type() @@ -940,6 +946,8 @@ Erlang code. -type af_match(T) :: {'match', anno(), af_pattern(), T}. +-type af_maybe_match() :: {'maybe_match', anno(), af_pattern(), abstract_expr()}. + -type af_variable() :: {'var', anno(), atom()}. % | af_anon_variable() %-type af_anon_variable() :: {'var', anno(), '_'}. @@ -965,7 +973,7 @@ Erlang code. -type binary_op() :: '/' | '*' | 'div' | 'rem' | 'band' | 'and' | '+' | '-' | 'bor' | 'bxor' | 'bsl' | 'bsr' | 'or' | 'xor' | '++' | '--' | '==' | '/=' | '=<' | '<' | '>=' | '>' | '=:=' - | '=/='. + | '=/=' | '!'. -type af_unary_op(T) :: {'op', anno(), unary_op(), T}. |