diff options
Diffstat (limited to 'lib')
63 files changed, 1646 insertions, 1147 deletions
diff --git a/lib/common_test/doc/src/ct.xml b/lib/common_test/doc/src/ct.xml index 5231ef24a4..264bcff251 100644 --- a/lib/common_test/doc/src/ct.xml +++ b/lib/common_test/doc/src/ct.xml @@ -935,7 +935,7 @@ </func> <func> - <name>reload_config(Required) -> ValueOrElement</name> + <name>reload_config(Required) -> ValueOrElement | {error, Reason}</name> <fsummary>Reloads configuration file containing specified configuration key.</fsummary> <type> diff --git a/lib/common_test/src/ct_logs.erl b/lib/common_test/src/ct_logs.erl index 455864efb6..53245c596a 100644 --- a/lib/common_test/src/ct_logs.erl +++ b/lib/common_test/src/ct_logs.erl @@ -137,7 +137,8 @@ close(Info, StartDir) -> %% so we need to use a local copy of the log cache data LogCacheBin = case make_last_run_index() of - {error,_} -> % log server not responding + {error, Reason} -> % log server not responding + io:format("Warning! ct_logs not responding: ~p~n", [Reason]), undefined; LCB -> LCB @@ -240,7 +241,7 @@ call(Msg) -> Pid -> MRef = erlang:monitor(process,Pid), Ref = make_ref(), - ?MODULE ! {Msg,{self(),Ref}}, + Pid ! {Msg,{self(),Ref}}, receive {Ref, Result} -> erlang:demonitor(MRef, [flush]), diff --git a/lib/common_test/test/ct_gen_conn_SUITE_data/conn_SUITE.erl b/lib/common_test/test/ct_gen_conn_SUITE_data/conn_SUITE.erl index 1404df6410..19eb1211fa 100644 --- a/lib/common_test/test/ct_gen_conn_SUITE_data/conn_SUITE.erl +++ b/lib/common_test/test/ct_gen_conn_SUITE_data/conn_SUITE.erl @@ -73,24 +73,28 @@ handles_to_multi_conn_pids(_Config) -> ConnPid3 = ct_gen_conn:get_conn_pid(Handle3), {true,true} = {is_process_alive(Handle3),is_process_alive(ConnPid3)}, + monitor_procs([Handle1,ConnPid1,Handle2,ConnPid2,Handle3,ConnPid3]), + ok = proto:close(Handle1), - ct:sleep(100), + ok = wait_procs_down([Handle1,ConnPid1]), {false,false} = {is_process_alive(Handle1),is_process_alive(ConnPid1)}, {true,true} = {is_process_alive(Handle2),is_process_alive(ConnPid2)}, ok = proto:kill_conn_proc(Handle2), - ct:sleep(100), + ok = wait_procs_down([ConnPid2]), {true,false} = {is_process_alive(Handle2),is_process_alive(ConnPid2)}, ConnPid2x = ct_gen_conn:get_conn_pid(Handle2), true = is_process_alive(ConnPid2x), + monitor_procs([ConnPid2x]), + ok = proto:close(Handle2), - ct:sleep(100), + ok = wait_procs_down([Handle2,ConnPid2x]), {false,false} = {is_process_alive(Handle2),is_process_alive(ConnPid2x)}, application:set_env(ct_test, reconnect, false), ok = proto:kill_conn_proc(Handle3), - ct:sleep(100), + ok = wait_procs_down([Handle3,ConnPid3]), {false,false} = {is_process_alive(Handle3),is_process_alive(ConnPid3)}, ok. @@ -116,24 +120,28 @@ handles_to_single_conn_pids(_Config) -> {undefined,Handle3,_,_}] = lists:sort(ct_util:get_connections(ConnPid)), ct:pal("CONNS = ~n~p", [Conns]), + monitor_procs([Handle1,Handle2,Handle3,ConnPid]), ok = proto:close(Handle1), - ct:sleep(100), + ok = wait_procs_down([Handle1]), {false,true} = {is_process_alive(Handle1),is_process_alive(ConnPid)}, ok = proto:kill_conn_proc(Handle2), - ct:sleep(100), + ok = wait_procs_down([ConnPid]), NewConnPid = ct_gen_conn:get_conn_pid(Handle2), NewConnPid = ct_gen_conn:get_conn_pid(Handle3), true = is_process_alive(Handle2), true = is_process_alive(Handle3), + false = is_process_alive(ConnPid), + + monitor_procs([NewConnPid]), ok = proto:close(Handle2), - ct:sleep(100), + ok = wait_procs_down([Handle2]), {false,true} = {is_process_alive(Handle2),is_process_alive(NewConnPid)}, application:set_env(ct_test, reconnect, false), ok = proto:kill_conn_proc(Handle3), - ct:sleep(100), + ok = wait_procs_down([Handle3,NewConnPid]), {false,false} = {is_process_alive(Handle3),is_process_alive(NewConnPid)}, ok. @@ -158,30 +166,37 @@ names_to_multi_conn_pids(_Config) -> Handle1 = proto:open(mconn1), + monitor_procs([Handle1,ConnPid1,Handle2,ConnPid2,Handle3,ConnPid3]), + ok = proto:close(mconn1), - ct:sleep(100), + ok = wait_procs_down([Handle1,ConnPid1]), {false,false} = {is_process_alive(Handle1),is_process_alive(ConnPid1)}, ok = proto:kill_conn_proc(Handle2), - ct:sleep(100), + ok = wait_procs_down([ConnPid2]), Handle2 = proto:open(mconn2), % should've been reconnected already {true,false} = {is_process_alive(Handle2),is_process_alive(ConnPid2)}, ConnPid2x = ct_gen_conn:get_conn_pid(Handle2), true = is_process_alive(ConnPid2x), + monitor_procs([ConnPid2x]), + ok = proto:close(mconn2), - ct:sleep(100), + ok = wait_procs_down([Handle2,ConnPid2x]), {false,false} = {is_process_alive(Handle2),is_process_alive(ConnPid2x)}, Handle2y = proto:open(mconn2), ConnPid2y = ct_gen_conn:get_conn_pid(Handle2y), {true,true} = {is_process_alive(Handle2y),is_process_alive(ConnPid2y)}, + + monitor_procs([Handle2y,ConnPid2y]), + ok = proto:close(mconn2), - ct:sleep(100), + ok = wait_procs_down([Handle2y,ConnPid2y]), {false,false} = {is_process_alive(Handle2y),is_process_alive(ConnPid2y)}, application:set_env(ct_test, reconnect, false), ok = proto:kill_conn_proc(Handle3), - ct:sleep(100), + ok = wait_procs_down([Handle3,ConnPid3]), {false,false} = {is_process_alive(Handle3),is_process_alive(ConnPid3)}, ok. @@ -211,16 +226,20 @@ names_to_single_conn_pids(_Config) -> {sconn3,Handle3,_,_}] = lists:sort(ct_util:get_connections(ConnPid)), ct:pal("CONNS on ~p = ~n~p", [ConnPid,Conns]), + monitor_procs([Handle1,Handle2,Handle3,ConnPid]), + ok = proto:close(sconn1), - ct:sleep(100), + ok = wait_procs_down([Handle1]), {false,true} = {is_process_alive(Handle1),is_process_alive(ConnPid)}, ok = proto:kill_conn_proc(Handle2), - ct:sleep(100), + ok = wait_procs_down([ConnPid]), {true,false} = {is_process_alive(Handle2),is_process_alive(ConnPid)}, Handle2 = proto:open(sconn2), % should've been reconnected already NewConnPid = ct_gen_conn:get_conn_pid(Handle2), true = is_process_alive(NewConnPid), + + monitor_procs([NewConnPid]), Conns1 = [{sconn2,Handle2,_,_}, {sconn3,Handle3,_,_}] = @@ -228,14 +247,29 @@ names_to_single_conn_pids(_Config) -> ct:pal("CONNS on ~p = ~n~p", [NewConnPid,Conns1]), ok = proto:close(sconn2), - ct:sleep(100), + ok = wait_procs_down([Handle2]), {false,true} = {is_process_alive(Handle2),is_process_alive(NewConnPid)}, application:set_env(ct_test, reconnect, false), ok = proto:kill_conn_proc(Handle3), - ct:sleep(100), + ok = wait_procs_down([Handle3,NewConnPid]), {false,false} = {is_process_alive(Handle3),is_process_alive(NewConnPid)}, ok. +%%%----------------------------------------------------------------- +monitor_procs(Pids) -> + [erlang:monitor(process,Pid) || Pid <- Pids], + ok. + +wait_procs_down([]) -> + ok; +wait_procs_down(Pids) -> + receive + {'DOWN',_,process,Pid,_} -> + wait_procs_down(lists:delete(Pid,Pids)) + after 2000 -> + timeout + end. + diff --git a/lib/common_test/test/ct_netconfc_SUITE.erl b/lib/common_test/test/ct_netconfc_SUITE.erl index 03fbc17bd2..2919f01605 100644 --- a/lib/common_test/test/ct_netconfc_SUITE.erl +++ b/lib/common_test/test/ct_netconfc_SUITE.erl @@ -44,16 +44,28 @@ %% there will be clashes with logging processes etc). %%-------------------------------------------------------------------- init_per_suite(Config) -> - case application:load(crypto) of - {error,Reason} when Reason=/={already_loaded,crypto} -> - {skip, Reason}; - _ -> - case application:load(ssh) of - {error,Reason} when Reason=/={already_loaded,ssh} -> - {skip, Reason}; - _ -> - ct_test_support:init_per_suite(Config) - end + case check_crypto_and_ssh() of + ok -> + ct_test_support:init_per_suite(Config); + Skip -> + Skip + end. + +check_crypto_and_ssh() -> + (catch code:load_file(crypto)), + case code:is_loaded(crypto) of + {file,_} -> + case ssh:start() of + Ok when Ok==ok; Ok=={error,{already_started,ssh}} -> + ct:log("ssh started",[]), + ok; + Other -> + ct:log("could not start ssh: ~p",[Other]), + {skip, "SSH could not be started!"} + end; + Other -> + ct:log("could not load crypto: ~p",[Other]), + {skip, "crypto could not be loaded!"} end. end_per_suite(Config) -> diff --git a/lib/common_test/test/ct_netconfc_SUITE_data/netconfc1_SUITE.erl b/lib/common_test/test/ct_netconfc_SUITE_data/netconfc1_SUITE.erl index f34969683c..2aa6c4d354 100644 --- a/lib/common_test/test/ct_netconfc_SUITE_data/netconfc1_SUITE.erl +++ b/lib/common_test/test/ct_netconfc_SUITE_data/netconfc1_SUITE.erl @@ -124,8 +124,9 @@ end_per_testcase(_Case, _Config) -> ok. init_per_suite(Config) -> - case catch ssh:start() of - Ok when Ok==ok; Ok=={error,{already_started,ssh}} -> + (catch code:load_file(crypto)), + case {ssh:start(),code:is_loaded(crypto)} of + {Ok,{file,_}} when Ok==ok; Ok=={error,{already_started,ssh}} -> ct:log("ssh started",[]), SshDir = filename:join(filename:dirname(code:which(?MODULE)), "ssh_dir"), @@ -133,7 +134,7 @@ init_per_suite(Config) -> ct:log("netconf server started",[]), [{netconf_server,Server},{ssh_dir,SshDir}|Config]; Other -> - ct:log("could not start ssh: ~p",[Other]), + ct:log("could not start ssh or load crypto: ~p",[Other]), {skip, "SSH could not be started!"} end. @@ -360,7 +361,7 @@ get(Config) -> get_a_lot(Config) -> SshDir = ?config(ssh_dir,Config), {ok,Client} = open_success(SshDir), - Descr = lists:append(lists:duplicate(1000,"Description of myserver! ")), + Descr = lists:append(lists:duplicate(100,"Description of myserver! ")), Server = {server,[{xmlns,"myns"}],[{name,[],["myserver"]}, {description,[],[Descr]}]}, Data = lists:duplicate(100,Server), diff --git a/lib/common_test/test/ct_netconfc_SUITE_data/netconfc_remote_SUITE.erl b/lib/common_test/test/ct_netconfc_SUITE_data/netconfc_remote_SUITE.erl index 0a49cdabbb..a65275da43 100644 --- a/lib/common_test/test/ct_netconfc_SUITE_data/netconfc_remote_SUITE.erl +++ b/lib/common_test/test/ct_netconfc_SUITE_data/netconfc_remote_SUITE.erl @@ -62,14 +62,15 @@ stop_node(Case) -> init_per_suite(Config) -> - case ssh:start() of - Ok when Ok==ok; Ok=={error,{already_started,ssh}} -> + (catch code:load_file(crypto)), + case {ssh:start(),code:is_loaded(crypto)} of + {Ok,{file,_}} when Ok==ok; Ok=={error,{already_started,ssh}} -> ct:log("SSH started locally",[]), SshDir = filename:join(filename:dirname(code:which(?MODULE)), "ssh_dir"), [{ssh_dir,SshDir}|Config]; Other -> - ct:log("could not start ssh locally: ~p",[Other]), + ct:log("could not start ssh or load crypto locally: ~p",[Other]), {skip, "SSH could not be started locally!"} end. @@ -85,15 +86,15 @@ remote_crash(Config) -> {ok,Node} = ct_slave:start(nc_remote_crash), Pa = filename:dirname(code:which(?NS)), true = rpc:call(Node,code,add_patha,[Pa]), - - case rpc:call(Node,ssh,start,[]) of - Ok when Ok==ok; Ok=={error,{already_started,ssh}} -> + rpc:call(Node,code,load_file,[crypto]), + case {rpc:call(Node,ssh,start,[]),rpc:call(Node,code,is_loaded,[crypto])} of + {Ok,{file,_}} when Ok==ok; Ok=={error,{already_started,ssh}} -> ct:log("SSH started remote",[]), ns(Node,start,[?config(ssh_dir,Config)]), ct:log("netconf server started remote",[]), remote_crash(Node,Config); Other -> - ct:log("could not start ssh remote: ~p",[Other]), + ct:log("could not start ssh or load crypto remote: ~p",[Other]), {skip, "SSH could not be started remote!"} end. diff --git a/lib/common_test/test/ct_netconfc_SUITE_data/ns.erl b/lib/common_test/test/ct_netconfc_SUITE_data/ns.erl index 9fb1fb6547..e62bc617fa 100644 --- a/lib/common_test/test/ct_netconfc_SUITE_data/ns.erl +++ b/lib/common_test/test/ct_netconfc_SUITE_data/ns.erl @@ -279,7 +279,7 @@ send({CM,Ch},Data) -> %%% Split into many small parts and send to client send_frag({CM,Ch},Data) -> - Sz = rand:uniform(2000), + Sz = rand:uniform(1000), case Data of <<Chunk:Sz/binary,Rest/binary>> -> ssh_connection:send(CM, Ch, Chunk), diff --git a/lib/common_test/test/ct_test_support.erl b/lib/common_test/test/ct_test_support.erl index 477fcb8a26..e926abd885 100644 --- a/lib/common_test/test/ct_test_support.erl +++ b/lib/common_test/test/ct_test_support.erl @@ -484,7 +484,8 @@ get_events(_, Config) -> {event_receiver,CTNode} ! {self(),get_events}, Events = receive {event_receiver,Evs} -> Evs end, test_server:format(Level, "Stopping event receiver!~n", []), - {event_receiver,CTNode} ! stop, + {event_receiver,CTNode} ! {self(),stop}, + receive {event_receiver,stopped} -> ok end, Events. er() -> @@ -499,8 +500,9 @@ er_loop(Evs) -> {From,get_events} -> From ! {event_receiver,lists:reverse(Evs)}, er_loop(Evs); - stop -> + {From,stop} -> unregister(event_receiver), + From ! {event_receiver,stopped}, ok end. diff --git a/lib/common_test/test/telnet_server.erl b/lib/common_test/test/telnet_server.erl index 2c33cb268a..65300b0bdf 100644 --- a/lib/common_test/test/telnet_server.erl +++ b/lib/common_test/test/telnet_server.erl @@ -117,38 +117,64 @@ init_client(#state{client=Sock}=State) -> dbg("Server sending: ~p~n",["login: "]), R = case gen_tcp:send(Sock,"login: ") of ok -> - loop(State, 1); + loop(State); Error -> Error end, _ = gen_tcp:close(Sock), R. -loop(State, N) -> +loop(State=#state{client=Sock}) -> receive - {tcp,_,Data} -> + {tcp,Sock,Data} -> try handle_data(Data,State) of {ok,State1} -> - loop(State1, N); + loop(State1); closed -> + _ = flush(State), closed catch throw:Error -> + _ = flush(State), Error end; - {tcp_closed, _} -> + {tcp_closed,Sock} -> closed; - {tcp_error,_,Error} -> + {tcp_error,Sock,Error} -> {error,tcp,Error}; disconnect -> - Sock = State#state.client, dbg("Server closing connection on socket ~p~n", [Sock]), + timer:sleep(1000), ok = gen_tcp:close(Sock), - closed; + _ = flush(State); stop -> + _ = flush(State), stopped end. +flush(State=#state{client=Sock}) -> + receive + {tcp,Sock,Data} = M-> + dbg("Message flushed after close or error: ~p~n", [M]), + try handle_data(Data,State) of + {ok,State1} -> + flush(State1); + closed -> + flush(State) + catch + throw:Error -> + Error + end; + {tcp_closed,Sock} = M -> + dbg("Message flushed after close or error: ~p~n", [M]), + ok; + {tcp_error,Sock,Error} = M -> + dbg("Message flushed after close or error: ~p~n", [M]), + {error,tcp,Error} + after 100 -> + ok + end. + handle_data(Cmd,#state{break=true}=State) -> dbg("Server got data when in break mode: ~p~n",[Cmd]), handle_break_cmd(Cmd,State); diff --git a/lib/dialyzer/src/dialyzer_contracts.erl b/lib/dialyzer/src/dialyzer_contracts.erl index 272ad10e90..976a2b8955 100644 --- a/lib/dialyzer/src/dialyzer_contracts.erl +++ b/lib/dialyzer/src/dialyzer_contracts.erl @@ -53,7 +53,9 @@ %% to expand records and/or remote types that they might contain. %%----------------------------------------------------------------------- --type tmp_contract_fun() :: fun((sets:set(mfa()), types()) -> contract_pair()). +-type cache() :: ets:tid(). +-type tmp_contract_fun() :: + fun((sets:set(mfa()), types(), cache()) -> contract_pair()). -record(tmp_contract, {contract_funs = [] :: [tmp_contract_fun()], forms = [] :: [{_, _}]}). @@ -153,19 +155,30 @@ process_contract_remote_types(CodeServer) -> ExpTypes = dialyzer_codeserver:get_exported_types(CodeServer), RecordDict = dialyzer_codeserver:get_records(CodeServer), ContractFun = - fun({_M, _F, _A}, {File, #tmp_contract{contract_funs = CFuns, forms = Forms}, Xtra}) -> - NewCs = [CFun(ExpTypes, RecordDict) || CFun <- CFuns], - Args = general_domain(NewCs), - {File, #contract{contracts = NewCs, args = Args, forms = Forms}, Xtra} + fun({{_M, _F, _A}=MFA, {File, TmpContract, Xtra}}, C0) -> + #tmp_contract{contract_funs = CFuns, forms = Forms} = TmpContract, + {NewCs, C2} = lists:mapfoldl(fun(CFun, C1) -> + CFun(ExpTypes, RecordDict, C1) + end, C0, CFuns), + Args = general_domain(NewCs), + Contract = #contract{contracts = NewCs, args = Args, forms = Forms}, + {{MFA, {File, Contract, Xtra}}, C2} end, ModuleFun = - fun(_ModuleName, ContractDict) -> - dict:map(ContractFun, ContractDict) + fun({ModuleName, ContractDict}, C3) -> + {NewContractList, C4} = + lists:mapfoldl(ContractFun, C3, dict:to_list(ContractDict)), + {{ModuleName, dict:from_list(NewContractList)}, C4} end, - NewContractDict = dict:map(ModuleFun, TmpContractDict), - NewCallbackDict = dict:map(ModuleFun, TmpCallbackDict), + Cache = erl_types:cache__new(), + {NewContractList, C5} = + lists:mapfoldl(ModuleFun, Cache, dict:to_list(TmpContractDict)), + {NewCallbackList, _C6} = + lists:mapfoldl(ModuleFun, C5, dict:to_list(TmpCallbackDict)), + NewContractDict = dict:from_list(NewContractList), + NewCallbackDict = dict:from_list(NewCallbackList), dialyzer_codeserver:finalize_contracts(NewContractDict, NewCallbackDict, - CodeServer). + CodeServer). -type opaques_fun() :: fun((module()) -> [erl_types:erl_type()]). @@ -431,19 +444,19 @@ contract_from_form(Forms, MFA, RecDict, FileLine) -> contract_from_form([{type, _, 'fun', [_, _]} = Form | Left], MFA, RecDict, FileLine, TypeAcc, FormAcc) -> TypeFun = - fun(ExpTypes, AllRecords) -> - NewType = + fun(ExpTypes, AllRecords, Cache) -> + {NewType, NewCache} = try - from_form_with_check(Form, ExpTypes, MFA, AllRecords) + from_form_with_check(Form, ExpTypes, MFA, AllRecords, Cache) catch throw:{error, Msg} -> {File, Line} = FileLine, NewMsg = io_lib:format("~s:~p: ~s", [filename:basename(File), - Line, Msg]), + Line, Msg]), throw({error, NewMsg}) end, NewTypeNoVars = erl_types:subst_all_vars_to_any(NewType), - {NewTypeNoVars, []} + {{NewTypeNoVars, []}, NewCache} end, NewTypeAcc = [TypeFun | TypeAcc], NewFormAcc = [{Form, []} | FormAcc], @@ -452,13 +465,15 @@ contract_from_form([{type, _L1, bounded_fun, [{type, _L2, 'fun', [_, _]} = Form, Constr]}| Left], MFA, RecDict, FileLine, TypeAcc, FormAcc) -> TypeFun = - fun(ExpTypes, AllRecords) -> - {Constr1, VarDict} = - process_constraints(Constr, MFA, RecDict, ExpTypes, AllRecords), - NewType = from_form_with_check(Form, ExpTypes, MFA, AllRecords, - VarDict), + fun(ExpTypes, AllRecords, Cache) -> + {Constr1, VarTable, Cache1} = + process_constraints(Constr, MFA, RecDict, ExpTypes, AllRecords, + Cache), + {NewType, NewCache} = + from_form_with_check(Form, ExpTypes, MFA, AllRecords, + VarTable, Cache1), NewTypeNoVars = erl_types:subst_all_vars_to_any(NewType), - {NewTypeNoVars, Constr1} + {{NewTypeNoVars, Constr1}, NewCache} end, NewTypeAcc = [TypeFun | TypeAcc], NewFormAcc = [{Form, Constr} | FormAcc], @@ -466,74 +481,91 @@ contract_from_form([{type, _L1, bounded_fun, contract_from_form([], _MFA, _RecDict, _FileLine, TypeAcc, FormAcc) -> {lists:reverse(TypeAcc), lists:reverse(FormAcc)}. -process_constraints(Constrs, MFA, RecDict, ExpTypes, AllRecords) -> - Init0 = initialize_constraints(Constrs, MFA, RecDict, ExpTypes, AllRecords), +process_constraints(Constrs, MFA, RecDict, ExpTypes, AllRecords, Cache) -> + {Init0, NewCache} = initialize_constraints(Constrs, MFA, RecDict, ExpTypes, + AllRecords, Cache), Init = remove_cycles(Init0), - constraints_fixpoint(Init, MFA, RecDict, ExpTypes, AllRecords). + constraints_fixpoint(Init, MFA, RecDict, ExpTypes, AllRecords, NewCache). -initialize_constraints(Constrs, MFA, RecDict, ExpTypes, AllRecords) -> - initialize_constraints(Constrs, MFA, RecDict, ExpTypes, AllRecords, []). +initialize_constraints(Constrs, MFA, RecDict, ExpTypes, AllRecords, Cache) -> + initialize_constraints(Constrs, MFA, RecDict, ExpTypes, AllRecords, + Cache, []). -initialize_constraints([], _MFA, _RecDict, _ExpTypes, _AllRecords, Acc) -> - Acc; -initialize_constraints([Constr|Rest], MFA, RecDict, ExpTypes, AllRecords, Acc) -> +initialize_constraints([], _MFA, _RecDict, _ExpTypes, _AllRecords, + Cache, Acc) -> + {Acc, Cache}; +initialize_constraints([Constr|Rest], MFA, RecDict, ExpTypes, AllRecords, + Cache, Acc) -> case Constr of {type, _, constraint, [{atom, _, is_subtype}, [Type1, Type2]]} -> VarTable = erl_types:var_table__new(), - T1 = final_form(Type1, ExpTypes, MFA, AllRecords, VarTable), + {T1, NewCache} = + final_form(Type1, ExpTypes, MFA, AllRecords, VarTable, Cache), Entry = {T1, Type2}, - initialize_constraints(Rest, MFA, RecDict, ExpTypes, AllRecords, [Entry|Acc]); + initialize_constraints(Rest, MFA, RecDict, ExpTypes, AllRecords, + NewCache, [Entry|Acc]); {type, _, constraint, [{atom,_,Name}, List]} -> N = length(List), throw({error, io_lib:format("Unsupported type guard ~w/~w\n", [Name, N])}) end. -constraints_fixpoint(Constrs, MFA, RecDict, ExpTypes, AllRecords) -> +constraints_fixpoint(Constrs, MFA, RecDict, ExpTypes, AllRecords, Cache) -> VarTable = erl_types:var_table__new(), - VarDict = - constraints_to_dict(Constrs, MFA, RecDict, ExpTypes, AllRecords, VarTable), - constraints_fixpoint(VarDict, MFA, Constrs, RecDict, ExpTypes, AllRecords). - -constraints_fixpoint(OldVarDict, MFA, Constrs, RecDict, ExpTypes, AllRecords) -> - NewVarDict = - constraints_to_dict(Constrs, MFA, RecDict, ExpTypes, AllRecords, OldVarDict), - case NewVarDict of - OldVarDict -> + {VarTab, NewCache} = + constraints_to_dict(Constrs, MFA, RecDict, ExpTypes, AllRecords, + VarTable, Cache), + constraints_fixpoint(VarTab, MFA, Constrs, RecDict, ExpTypes, + AllRecords, NewCache). + +constraints_fixpoint(OldVarTab, MFA, Constrs, RecDict, ExpTypes, + AllRecords, Cache) -> + {NewVarTab, NewCache} = + constraints_to_dict(Constrs, MFA, RecDict, ExpTypes, AllRecords, + OldVarTab, Cache), + case NewVarTab of + OldVarTab -> Fun = fun(Key, Value, Acc) -> [{subtype, erl_types:t_var(Key), Value}|Acc] end, - FinalConstrs = maps:fold(Fun, [], NewVarDict), - {FinalConstrs, NewVarDict}; + FinalConstrs = maps:fold(Fun, [], NewVarTab), + {FinalConstrs, NewVarTab, NewCache}; _Other -> - constraints_fixpoint(NewVarDict, MFA, Constrs, RecDict, ExpTypes, AllRecords) + constraints_fixpoint(NewVarTab, MFA, Constrs, RecDict, ExpTypes, + AllRecords, NewCache) end. -final_form(Form, ExpTypes, MFA, AllRecords, VarDict) -> - from_form_with_check(Form, ExpTypes, MFA, AllRecords, VarDict). +final_form(Form, ExpTypes, MFA, AllRecords, VarTable, Cache) -> + from_form_with_check(Form, ExpTypes, MFA, AllRecords, VarTable, Cache). -from_form_with_check(Form, ExpTypes, MFA, AllRecords) -> +from_form_with_check(Form, ExpTypes, MFA, AllRecords, Cache) -> VarTable = erl_types:var_table__new(), - from_form_with_check(Form, ExpTypes, MFA, AllRecords, VarTable). + from_form_with_check(Form, ExpTypes, MFA, AllRecords, VarTable, Cache). -from_form_with_check(Form, ExpTypes, MFA, AllRecords, VarDict) -> +from_form_with_check(Form, ExpTypes, MFA, AllRecords, VarTable, Cache) -> Site = {spec, MFA}, - erl_types:t_check_record_fields(Form, ExpTypes, Site, AllRecords, VarDict), - erl_types:t_from_form(Form, ExpTypes, Site, AllRecords, VarDict). - -constraints_to_dict(Constrs, MFA, RecDict, ExpTypes, AllRecords, VarDict) -> - Subtypes = - constraints_to_subs(Constrs, MFA, RecDict, ExpTypes, AllRecords, VarDict, []), - insert_constraints(Subtypes). - -constraints_to_subs([], _MFA, _RecDict, _ExpTypes, _AllRecords, _VarDict, Acc) -> - Acc; -constraints_to_subs([C|Rest], MFA, RecDict, ExpTypes, AllRecords, VarDict, Acc) -> - {T1, Form2} = C, - T2 = final_form(Form2, ExpTypes, MFA, AllRecords, VarDict), + C1 = erl_types:t_check_record_fields(Form, ExpTypes, Site, AllRecords, + VarTable, Cache), + erl_types:t_from_form(Form, ExpTypes, Site, AllRecords, VarTable, C1). + +constraints_to_dict(Constrs, MFA, RecDict, ExpTypes, AllRecords, + VarTab, Cache) -> + {Subtypes, NewCache} = + constraints_to_subs(Constrs, MFA, RecDict, ExpTypes, AllRecords, + VarTab, Cache, []), + {insert_constraints(Subtypes), NewCache}. + +constraints_to_subs([], _MFA, _RecDict, _ExpTypes, _AllRecords, + _VarTab, Cache, Acc) -> + {Acc, Cache}; +constraints_to_subs([{T1, Form2}|Rest], MFA, RecDict, ExpTypes, AllRecords, + VarTab, Cache, Acc) -> + {T2, NewCache} = + final_form(Form2, ExpTypes, MFA, AllRecords, VarTab, Cache), NewAcc = [{subtype, T1, T2}|Acc], - constraints_to_subs(Rest, MFA, RecDict, ExpTypes, AllRecords, VarDict, NewAcc). + constraints_to_subs(Rest, MFA, RecDict, ExpTypes, AllRecords, + VarTab, NewCache, NewAcc). %% Replaces variables with '_' when necessary to break up cycles among %% the constraints. @@ -647,6 +679,7 @@ get_invalid_contract_warnings_funs([{MFA, {FileLine, Contract, _Xtra}}|Left], {value, {Ret, Args}} -> Sig = erl_types:t_fun(Args, Ret), {M, _F, _A} = MFA, + %% io:format("MFA ~p~n", [MFA]), Opaques = FindOpaques(M), {File, Line} = FileLine, WarningInfo = {File, Line, MFA}, @@ -795,7 +828,7 @@ is_remote_types_related(Contract, CSig, Sig, MFA, RecDict) -> t_from_forms_without_remote([{FType, []}], MFA, RecDict) -> Site = {spec, MFA}, - Type1 = erl_types:t_from_form_without_remote(FType, Site, RecDict), + {Type1, _} = erl_types:t_from_form_without_remote(FType, Site, RecDict), {ok, erl_types:subst_all_vars_to_any(Type1)}; t_from_forms_without_remote([{_FType, _Constrs}], _MFA, _RecDict) -> %% 'When' constraints diff --git a/lib/dialyzer/src/dialyzer_dataflow.erl b/lib/dialyzer/src/dialyzer_dataflow.erl index 3349b12932..9399789464 100644 --- a/lib/dialyzer/src/dialyzer_dataflow.erl +++ b/lib/dialyzer/src/dialyzer_dataflow.erl @@ -522,7 +522,7 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left], ?debug("RetWithoutLocal: ~s\n", [erl_types:t_to_string(RetWithoutLocal)]), ?debug("BifRet: ~s\n", [erl_types:t_to_string(BifRange(NewArgTypes))]), ?debug("SigRange: ~s\n", [erl_types:t_to_string(SigRange)]), - ?debug("ContrRet: ~s\n", [erl_types:t_to_string(CRange(NewArgTypes))]), + ?debug("ContrRet: ~s\n", [erl_types:t_to_string(ContrRet)]), ?debug("LocalRet: ~s\n", [erl_types:t_to_string(LocalRet)]), State1 = @@ -2954,11 +2954,15 @@ is_call_to_send(Tree) -> Arity = cerl:call_arity(Tree), cerl:is_c_atom(Mod) andalso cerl:is_c_atom(Name) - andalso (cerl:atom_val(Name) =:= '!') + andalso is_send(cerl:atom_val(Name)) andalso (cerl:atom_val(Mod) =:= erlang) andalso (Arity =:= 2) end. +is_send('!') -> true; +is_send(send) -> true; +is_send(_) -> false. + is_lc_simple_list(Tree, TreeType, State) -> Opaques = State#state.opaques, Ann = cerl:get_ann(Tree), @@ -3067,7 +3071,10 @@ state__add_warning(#state{warnings = Warnings, warning_mode = true} = State, false -> WarningInfo = {get_file(Ann), get_line(Ann), State#state.curr_fun}, Warn = {Tag, WarningInfo, Msg}, - ?debug("MSG ~s\n", [dialyzer:format_warning(Warn)]), + case Tag of + ?WARN_CONTRACT_RANGE -> ok; + _ -> ?debug("MSG ~s\n", [dialyzer:format_warning(Warn)]) + end, State#state{warnings = [Warn|Warnings]} end end. diff --git a/lib/dialyzer/src/dialyzer_utils.erl b/lib/dialyzer/src/dialyzer_utils.erl index d37701f03b..76a5cf3d0b 100644 --- a/lib/dialyzer/src/dialyzer_utils.erl +++ b/lib/dialyzer/src/dialyzer_utils.erl @@ -194,15 +194,18 @@ get_core_from_abstract_code(AbstrCode, Opts) -> %% %% ============================================================================ +-type type_table() :: erl_types:type_table(). +-type mod_records() :: dict:dict(module(), type_table()). + -spec get_record_and_type_info(abstract_code()) -> - {'ok', dict:dict()} | {'error', string()}. + {'ok', type_table()} | {'error', string()}. get_record_and_type_info(AbstractCode) -> Module = get_module(AbstractCode), get_record_and_type_info(AbstractCode, Module, dict:new()). --spec get_record_and_type_info(abstract_code(), module(), dict:dict()) -> - {'ok', dict:dict()} | {'error', string()}. +-spec get_record_and_type_info(abstract_code(), module(), type_table()) -> + {'ok', type_table()} | {'error', string()}. get_record_and_type_info(AbstractCode, Module, RecDict) -> get_record_and_type_info(AbstractCode, Module, RecDict, "nofile"). @@ -299,92 +302,117 @@ get_record_fields([], _RecDict, Acc) -> process_record_remote_types(CServer) -> TempRecords = dialyzer_codeserver:get_temp_records(CServer), ExpTypes = dialyzer_codeserver:get_exported_types(CServer), - TempRecords1 = process_opaque_types0(TempRecords, ExpTypes), + Cache = erl_types:cache__new(), + {TempRecords1, Cache1} = + process_opaque_types0(TempRecords, ExpTypes, Cache), + %% A cache (not the field type cache) is used for speeding things up a bit. + VarTable = erl_types:var_table__new(), ModuleFun = - fun(Module, Record) -> + fun({Module, Record}, C0) -> RecordFun = - fun(Key, Value) -> + fun({Key, Value}, C2) -> case Key of {record, Name} -> FieldFun = - fun(Arity, Fields) -> + fun({Arity, Fields}, C4) -> Site = {record, {Module, Name, Arity}}, - [{FieldName, Field, - erl_types:t_from_form(Field, - ExpTypes, - Site, - TempRecords1)} - || {FieldName, Field, _} <- Fields] + {Fields1, C7} = + lists:mapfoldl(fun({FieldName, Field, _}, C5) -> + {FieldT, C6} = + erl_types:t_from_form + (Field, ExpTypes, Site, + TempRecords1, VarTable, + C5), + {{FieldName, Field, FieldT}, C6} + end, C4, Fields), + {{Arity, Fields1}, C7} end, {FileLine, Fields} = Value, - {FileLine, orddict:map(FieldFun, Fields)}; - _Other -> Value + {FieldsList, C3} = + lists:mapfoldl(FieldFun, C2, orddict:to_list(Fields)), + {{Key, {FileLine, orddict:from_list(FieldsList)}}, C3}; + _Other -> {{Key, Value}, C2} end end, - dict:map(RecordFun, Record) + {RecordList, C1} = + lists:mapfoldl(RecordFun, C0, dict:to_list(Record)), + {{Module, dict:from_list(RecordList)}, C1} end, - NewRecords = dict:map(ModuleFun, TempRecords1), - ok = check_record_fields(NewRecords, ExpTypes), + {NewRecordsList, C1} = + lists:mapfoldl(ModuleFun, Cache1, dict:to_list(TempRecords1)), + NewRecords = dict:from_list(NewRecordsList), + _C8 = check_record_fields(NewRecords, ExpTypes, C1), dialyzer_codeserver:finalize_records(NewRecords, CServer). %% erl_types:t_from_form() substitutes the declaration of opaque types %% for the expanded type in some cases. To make sure the initial type, %% any(), is not used, the expansion is done twice. %% XXX: Recursive opaque types are not handled well. -process_opaque_types0(TempRecords0, TempExpTypes) -> - TempRecords1 = process_opaque_types(TempRecords0, TempExpTypes), - process_opaque_types(TempRecords1, TempExpTypes). +process_opaque_types0(TempRecords0, TempExpTypes, Cache) -> + {TempRecords1, NewCache} = + process_opaque_types(TempRecords0, TempExpTypes, Cache), + process_opaque_types(TempRecords1, TempExpTypes, NewCache). -process_opaque_types(TempRecords, TempExpTypes) -> +process_opaque_types(TempRecords, TempExpTypes, Cache) -> + VarTable = erl_types:var_table__new(), ModuleFun = - fun(Module, Record) -> + fun({Module, Record}, C0) -> RecordFun = - fun(Key, Value) -> + fun({Key, Value}, C2) -> case Key of {opaque, Name, NArgs} -> {{_Module, _FileLine, Form, _ArgNames}=F, _Type} = Value, Site = {type, {Module, Name, NArgs}}, - Type = erl_types:t_from_form(Form, TempExpTypes, Site, - TempRecords), - {F, Type}; - _Other -> Value + {Type, C3} = + erl_types:t_from_form(Form, TempExpTypes, Site, + TempRecords, VarTable, C2), + {{Key, {F, Type}}, C3}; + _Other -> {{Key, Value}, C2} end end, - dict:map(RecordFun, Record) + {RecordList, C1} = + lists:mapfoldl(RecordFun, C0, dict:to_list(Record)), + {{Module, dict:from_list(RecordList)}, C1} + %% dict:map(RecordFun, Record) end, - dict:map(ModuleFun, TempRecords). + {TempRecordList, NewCache} = + lists:mapfoldl(ModuleFun, Cache, dict:to_list(TempRecords)), + {dict:from_list(TempRecordList), NewCache}. + %% dict:map(ModuleFun, TempRecords). -check_record_fields(Records, TempExpTypes) -> +check_record_fields(Records, TempExpTypes, Cache) -> + VarTable = erl_types:var_table__new(), CheckFun = - fun({Module, Element}) -> - CheckForm = fun(Form, Site) -> - erl_types:t_check_record_fields(Form, TempExpTypes, - Site, Records) + fun({Module, Element}, C0) -> + CheckForm = fun(Form, Site, C1) -> + erl_types:t_check_record_fields(Form, TempExpTypes, + Site, Records, + VarTable, C1) end, ElemFun = - fun({Key, Value}) -> + fun({Key, Value}, C2) -> case Key of {record, Name} -> FieldFun = - fun({Arity, Fields}) -> + fun({Arity, Fields}, C3) -> Site = {record, {Module, Name, Arity}}, - _ = [ok = CheckForm(Field, Site) || - {_, Field, _} <- Fields], - ok + lists:foldl(fun({_, Field, _}, C4) -> + CheckForm(Field, Site, C4) + end, C3, Fields) end, {FileLine, Fields} = Value, - Fun = fun() -> lists:foreach(FieldFun, Fields) end, + Fun = fun() -> lists:foldl(FieldFun, C2, Fields) end, msg_with_position(Fun, FileLine); {_OpaqueOrType, Name, NArgs} -> Site = {type, {Module, Name, NArgs}}, {{_Module, FileLine, Form, _ArgNames}, _Type} = Value, - Fun = fun() -> ok = CheckForm(Form, Site) end, + Fun = fun() -> CheckForm(Form, Site, C2) end, msg_with_position(Fun, FileLine) end end, - lists:foreach(ElemFun, dict:to_list(Element)) + lists:foldl(ElemFun, C0, dict:to_list(Element)) end, - lists:foreach(CheckFun, dict:to_list(Records)). + lists:foldl(CheckFun, Cache, dict:to_list(Records)). msg_with_position(Fun, FileLine) -> try Fun() @@ -396,7 +424,7 @@ msg_with_position(Fun, FileLine) -> throw({error, NewMsg}) end. --spec merge_records(dict:dict(), dict:dict()) -> dict:dict(). +-spec merge_records(mod_records(), mod_records()) -> mod_records(). merge_records(NewRecords, OldRecords) -> dict:merge(fun(_Key, NewVal, _OldVal) -> NewVal end, NewRecords, OldRecords). @@ -410,7 +438,7 @@ merge_records(NewRecords, OldRecords) -> -type spec_dict() :: dict:dict(). -type callback_dict() :: dict:dict(). --spec get_spec_info(module(), abstract_code(), dict:dict()) -> +-spec get_spec_info(module(), abstract_code(), type_table()) -> {'ok', spec_dict(), callback_dict()} | {'error', string()}. get_spec_info(ModName, AbstractCode, RecordsDict) -> @@ -676,7 +704,7 @@ format_errors([]) -> format_sig(Type) -> format_sig(Type, dict:new()). --spec format_sig(erl_types:erl_type(), dict:dict()) -> string(). +-spec format_sig(erl_types:erl_type(), type_table()) -> string(). format_sig(Type, RecDict) -> "fun(" ++ Sig = lists:flatten(erl_types:t_to_string(Type, RecDict)), diff --git a/lib/dialyzer/test/map_SUITE_data/results/exact b/lib/dialyzer/test/map_SUITE_data/results/exact index 374ada8869..ea00e61330 100644 --- a/lib/dialyzer/test/map_SUITE_data/results/exact +++ b/lib/dialyzer/test/map_SUITE_data/results/exact @@ -1,3 +1,3 @@ exact.erl:15: Function t2/1 has no local return -exact.erl:19: The variable _ can never match since previous clauses completely covered the type #{'a':=_, ...} +exact.erl:19: The variable _ can never match since previous clauses completely covered the type #{'a':=_, _=>_} diff --git a/lib/dialyzer/test/map_SUITE_data/results/guard_update b/lib/dialyzer/test/map_SUITE_data/results/guard_update index e4bc892195..98df23907f 100644 --- a/lib/dialyzer/test/map_SUITE_data/results/guard_update +++ b/lib/dialyzer/test/map_SUITE_data/results/guard_update @@ -1,5 +1,5 @@ guard_update.erl:5: Function t/0 has no local return -guard_update.erl:6: The call guard_update:f(#{'a':=2}) will never return since it differs in the 1st argument from the success typing arguments: (#{'b':=_, ...}) +guard_update.erl:6: The call guard_update:f(#{'a':=2}) will never return since it differs in the 1st argument from the success typing arguments: (#{'b':=_, _=>_}) guard_update.erl:8: Clause guard cannot succeed. The variable M was matched against the type #{'a':=2} guard_update.erl:8: Function f/1 has no local return diff --git a/lib/dialyzer/test/map_SUITE_data/results/map_in_guard2 b/lib/dialyzer/test/map_SUITE_data/results/map_in_guard2 index 6bc0c010d7..f6fb98a863 100644 --- a/lib/dialyzer/test/map_SUITE_data/results/map_in_guard2 +++ b/lib/dialyzer/test/map_SUITE_data/results/map_in_guard2 @@ -1,7 +1,7 @@ map_in_guard2.erl:10: The call map_in_guard2:assoc_guard_clause('not_a_map') will never return since it differs in the 1st argument from the success typing arguments: (map()) map_in_guard2.erl:12: The pattern 'true' can never match the type 'false' -map_in_guard2.erl:14: The call map_in_guard2:exact_guard_clause(#{}) will never return since it differs in the 1st argument from the success typing arguments: (#{'a':=_, ...}) +map_in_guard2.erl:14: The call map_in_guard2:exact_guard_clause(#{}) will never return since it differs in the 1st argument from the success typing arguments: (#{'a':=_, _=>_}) map_in_guard2.erl:17: Clause guard cannot succeed. The variable M was matched against the type 'not_a_map' map_in_guard2.erl:20: Function assoc_update/1 has no local return map_in_guard2.erl:20: Guard test is_map(M::'not_a_map') can never succeed diff --git a/lib/dialyzer/test/map_SUITE_data/results/typeflow b/lib/dialyzer/test/map_SUITE_data/results/typeflow index e3378a24bb..acfb7f551e 100644 --- a/lib/dialyzer/test/map_SUITE_data/results/typeflow +++ b/lib/dialyzer/test/map_SUITE_data/results/typeflow @@ -1,4 +1,4 @@ typeflow.erl:14: Function t2/1 has no local return typeflow.erl:16: The call lists:sort(integer()) will never return since it differs in the 1st argument from the success typing arguments: ([any()]) -typeflow.erl:9: The variable _ can never match since previous clauses completely covered the type #{'a':=integer(), ...} +typeflow.erl:9: The variable _ can never match since previous clauses completely covered the type #{'a':=integer(), _=>_} diff --git a/lib/dialyzer/test/map_SUITE_data/results/typesig b/lib/dialyzer/test/map_SUITE_data/results/typesig index 3049402860..fb2f851a7d 100644 --- a/lib/dialyzer/test/map_SUITE_data/results/typesig +++ b/lib/dialyzer/test/map_SUITE_data/results/typesig @@ -1,5 +1,5 @@ typesig.erl:5: Function t1/0 has no local return -typesig.erl:5: The call typesig:test(#{'a':=1}) will never return since it differs in the 1st argument from the success typing arguments: (#{'a':={number()}, ...}) +typesig.erl:5: The call typesig:test(#{'a':=1}) will never return since it differs in the 1st argument from the success typing arguments: (#{'a':={number()}, _=>_}) typesig.erl:6: Function t2/0 has no local return -typesig.erl:6: The call typesig:test(#{'a':={'b'}}) will never return since it differs in the 1st argument from the success typing arguments: (#{'a':={number()}, ...}) +typesig.erl:6: The call typesig:test(#{'a':={'b'}}) will never return since it differs in the 1st argument from the success typing arguments: (#{'a':={number()}, _=>_}) diff --git a/lib/dialyzer/test/small_SUITE_data/results/maps1 b/lib/dialyzer/test/small_SUITE_data/results/maps1 index a178e96b20..f36f7f4926 100644 --- a/lib/dialyzer/test/small_SUITE_data/results/maps1 +++ b/lib/dialyzer/test/small_SUITE_data/results/maps1 @@ -1,4 +1,4 @@ maps1.erl:43: Function t3/0 has no local return -maps1.erl:44: The call maps1:foo(#{'greger'=>3, #{'arne'=>'anka'}=>45},1) will never return since it differs in the 1st and 2nd argument from the success typing arguments: (#{'beta':=_, ...},'b') +maps1.erl:44: The call maps1:foo(#{'greger'=>3, #{'arne'=>'anka'}=>45},1) will never return since it differs in the 1st and 2nd argument from the success typing arguments: (#{'beta':=_, _=>_},'b') maps1.erl:52: The variable Mod can never match since previous clauses completely covered the type #{} diff --git a/lib/dialyzer/test/unmatched_returns_SUITE_data/src/send.erl b/lib/dialyzer/test/unmatched_returns_SUITE_data/src/send.erl new file mode 100644 index 0000000000..4d681b5cc7 --- /dev/null +++ b/lib/dialyzer/test/unmatched_returns_SUITE_data/src/send.erl @@ -0,0 +1,11 @@ +-module(send). + +-export([s/0]). + +s() -> + self() ! n(), % no warning + erlang:send(self(), n()), % no warning + ok. + +n() -> + {1, 1}. diff --git a/lib/diameter/src/base/diameter_config.erl b/lib/diameter/src/base/diameter_config.erl index 702f11593a..fdbbd412a1 100644 --- a/lib/diameter/src/base/diameter_config.erl +++ b/lib/diameter/src/base/diameter_config.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2015. All Rights Reserved. +%% Copyright Ericsson AB 2010-2016. 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,17 +38,17 @@ -module(diameter_config). -behaviour(gen_server). --compile({no_auto_import, [monitor/2]}). - -export([start_service/2, stop_service/1, add_transport/2, remove_transport/2, have_transport/2, - lookup/1]). + lookup/1, + subscribe/2]). -%% child server start --export([start_link/0]). +%% server start +-export([start_link/0, + start_link/1]). %% gen_server callbacks -export([init/1, @@ -58,8 +58,8 @@ handle_info/2, code_change/3]). -%% diameter_sync requests. --export([sync/1]). +%% callbacks +-export([sync/1]). %% diameter_sync requests %% debug -export([state/0, @@ -69,7 +69,8 @@ -include("diameter_internal.hrl"). %% Server state. --record(state, {id = diameter_lib:now()}). +-record(state, {id = diameter_lib:now(), + role :: server | transport}). %% Registered name of the server. -define(SERVER, ?MODULE). @@ -77,6 +78,9 @@ %% Table config is written to. -define(TABLE, ?MODULE). +%% Key on which a transport-specific child registers itself. +-define(TRANSPORT_KEY(Ref), {?MODULE, transport, Ref}). + %% Workaround for dialyzer's lack of understanding of match specs. -type match(T) :: T | '_' | '$1' | '$2' | '$3' | '$4'. @@ -225,6 +229,13 @@ pred(_) -> ?THROW(pred). %% -------------------------------------------------------------------------- +%% # subscribe/2 +%% -------------------------------------------------------------------------- + +subscribe(Ref, T) -> + diameter_reg:subscribe(?TRANSPORT_KEY(Ref), T). + +%% -------------------------------------------------------------------------- %% # have_transport/2 %% %% Output: true | false @@ -264,6 +275,9 @@ start_link() -> Options = [{spawn_opt, diameter_lib:spawn_opts(server, [])}], gen_server:start_link(ServerName, Module, Args, Options). +start_link(T) -> + proc_lib:start_link(?MODULE, init, [T], infinity, []). + state() -> call(state). @@ -274,8 +288,27 @@ uptime() -> %%% # init/1 %%% ---------------------------------------------------------- +%% ?SERVER start. init([]) -> - {ok, #state{}}. + {ok, #state{role = server}}; + +%% Child start as a consequence of add_transport. +init({SvcName, Type, Opts}) -> + Res = try + add(SvcName, Type, Opts) + catch + ?FAILURE(Reason) -> {error, Reason} + end, + proc_lib:init_ack({ok, self(), Res}), + loop(Res). + +%% loop/1 + +loop({ok, _}) -> + gen_server:enter_loop(?MODULE, [], #state{role = transport}); + +loop({error, _}) -> + ok. %% die %%% ---------------------------------------------------------- %%% # handle_call/2 @@ -284,8 +317,8 @@ init([]) -> handle_call(state, _, State) -> {reply, State, State}; -handle_call(uptime, _, #state{id = Time} = State) -> - {reply, diameter_lib:now_diff(Time), State}; +handle_call(uptime, _, #state{id = Time} = S) -> + {reply, diameter_lib:now_diff(Time), S}; handle_call(Req, From, State) -> ?UNEXPECTED([Req, From]), @@ -304,30 +337,34 @@ handle_cast(Msg, State) -> %%% # handle_info/2 %%% ---------------------------------------------------------- +%% remove_transport is telling published child to die. +handle_info(stop, #state{role = transport} = S) -> + {stop, normal, S}; + %% A service process has died. This is most likely a consequence of %% stop_service, in which case the restart will find no config for the %% service and do nothing. The entry keyed on the monitor ref is only %% removed as a result of the 'DOWN' notification however. -handle_info({'DOWN', MRef, process, _, Reason}, State) -> +handle_info({'DOWN', MRef, process, _, Reason}, #state{role = server} = S) -> [#monitor{service = SvcName} = T] = select([{#monitor{mref = MRef, _ = '_'}, [], ['$_']}]), queue_restart(Reason, SvcName), delete_object(T), - {noreply, State}; + {noreply, S}; -handle_info({monitor, SvcName, Pid}, State) -> - monitor(Pid, SvcName), - {noreply, State}; +handle_info({monitor, SvcName, Pid}, #state{role = server} = S) -> + insert_monitor(Pid, SvcName), + {noreply, S}; -handle_info({restart, SvcName}, State) -> +handle_info({restart, SvcName}, #state{role = server} = S) -> restart(SvcName), - {noreply, State}; + {noreply, S}; -handle_info(restart, State) -> +handle_info(restart, #state{role = server} = S) -> restart(), - {noreply, State}; + {noreply, S}; handle_info(Info, State) -> ?UNEXPECTED([Info]), @@ -404,19 +441,22 @@ sync({start_service, SvcName, Opts}) -> sync({stop_service, SvcName}) -> stop(SvcName); +%% Start a child whose only purpose is to be alive for the lifetime of +%% the transport configuration and publish itself in diameter_reg. +%% This is to provide a way for processes to to be notified when the +%% configuration is removed (diameter_reg:subscribe/2). sync({add, SvcName, Type, Opts}) -> - try - add(SvcName, Type, Opts) - catch - ?FAILURE(Reason) -> {error, Reason} - end; + {ok, _Pid, Res} = diameter_config_sup:start_child({SvcName, Type, Opts}), + Res; sync({remove, SvcName, Pred}) -> - remove(select([{#transport{service = '$1', _ = '_'}, + Recs = select([{#transport{service = '$1', _ = '_'}, [{'=:=', '$1', {const, SvcName}}], ['$_']}]), - SvcName, - Pred). + F = fun(#transport{ref = R, type = T, options = O}) -> + Pred(R,T,O) + end, + remove(SvcName, lists:filter(F, Recs)). %% start/3 @@ -438,8 +478,8 @@ startmon(SvcName, {ok, Pid}) -> startmon(_, {error, _}) -> ok. -monitor(Pid, SvcName) -> - MRef = erlang:monitor(process, Pid), +insert_monitor(Pid, SvcName) -> + MRef = monitor(process, Pid), insert(#monitor{mref = MRef, service = SvcName}). %% queue_restart/2 @@ -503,6 +543,7 @@ add(SvcName, Type, Opts) -> ok = transport_opts(Opts), Ref = make_ref(), + true = diameter_reg:add_new(?TRANSPORT_KEY(Ref)), T = {Ref, Type, Opts}, %% The call to the service returns error if the service isn't %% started yet, which is harmless. The transport will be started @@ -594,26 +635,30 @@ start_transport(SvcName, T) -> No end. -%% remove/3 +%% remove/2 -remove(L, SvcName, Pred) -> - rm(SvcName, lists:filter(fun(#transport{ref = R, type = T, options = O}) -> - Pred(R,T,O) - end, - L)). - -rm(_, []) -> +remove(_, []) -> ok; -rm(SvcName, L) -> + +remove(SvcName, L) -> Refs = lists:map(fun(#transport{ref = R}) -> R end, L), case stop_transport(SvcName, Refs) of ok -> + lists:foreach(fun stop_child/1, Refs), diameter_stats:flush(Refs), lists:foreach(fun delete_object/1, L); {error, _} = No -> No end. +stop_child(Ref) -> + case diameter_reg:match(?TRANSPORT_KEY(Ref)) of + [{_, Pid}] -> %% tell the transport-specific child to die + Pid ! stop; + [] -> %% already removed/dead + ok + end. + stop_transport(SvcName, Refs) -> case diameter_service:stop_transport(SvcName, Refs) of ok -> diff --git a/lib/diameter/src/base/diameter_config_sup.erl b/lib/diameter/src/base/diameter_config_sup.erl new file mode 100644 index 0000000000..9524573378 --- /dev/null +++ b/lib/diameter/src/base/diameter_config_sup.erl @@ -0,0 +1,58 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2016. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +%% Supervisor for config processes. +%% + +-module(diameter_config_sup). + +-behaviour(supervisor). + +%% interface +-export([start_link/0, %% supervisor start + start_child/1]). %% config start + +-export([init/1]). + +-define(NAME, ?MODULE). %% supervisor name + +%% start_link/0 + +start_link() -> + SupName = {local, ?NAME}, + supervisor:start_link(SupName, ?MODULE, []). + +%% start_child/1 + +start_child(T) -> + supervisor:start_child(?NAME, [T]). + +%% init/1 + +init([]) -> + Mod = diameter_config, + Flags = {simple_one_for_one, 0, 1}, + ChildSpec = {Mod, + {Mod, start_link, []}, + temporary, + 1000, + worker, + [Mod]}, + {ok, {Flags, [ChildSpec]}}. diff --git a/lib/diameter/src/base/diameter_lib.erl b/lib/diameter/src/base/diameter_lib.erl index 43b0ca24ab..b835e87967 100644 --- a/lib/diameter/src/base/diameter_lib.erl +++ b/lib/diameter/src/base/diameter_lib.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2015. All Rights Reserved. +%% Copyright Ericsson AB 2010-2016. 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. @@ -20,18 +20,17 @@ -module(diameter_lib). -compile({no_auto_import, [now/0]}). --compile({nowarn_deprecated_function, [{erlang, now, 0}]}). -export([info_report/2, error_report/2, warning_report/2, now/0, + timestamp/0, timestamp/1, now_diff/1, micro_diff/1, micro_diff/2, time/1, - seed/0, eval/1, eval_name/1, get_stacktrace/0, @@ -110,6 +109,16 @@ now() -> erlang:monotonic_time(). %% --------------------------------------------------------------------------- +%% # timestamp/0 +%% --------------------------------------------------------------------------- + +-spec timestamp() + -> erlang:timestamp(). + +timestamp() -> + timestamp(now()). + +%% --------------------------------------------------------------------------- %% # timestamp/1 %% --------------------------------------------------------------------------- @@ -184,24 +193,6 @@ time(Micro) -> %% elapsed time {H, M, S, Micro rem 1000000}. %% --------------------------------------------------------------------------- -%% # seed/0 -%% --------------------------------------------------------------------------- - --spec seed() - -> {erlang:timestamp(), {integer(), integer(), integer()}}. - -%% Return an argument for random:seed/1. - -seed() -> - T = now(), - {timestamp(T), seed(T)}. - -%% seed/1 - -seed(T) -> %% monotonic time - {erlang:phash2(node()), T, erlang:unique_integer()}. - -%% --------------------------------------------------------------------------- %% # eval/1 %% %% Evaluate a function in various forms. diff --git a/lib/diameter/src/base/diameter_reg.erl b/lib/diameter/src/base/diameter_reg.erl index 7f198080ba..9027130063 100644 --- a/lib/diameter/src/base/diameter_reg.erl +++ b/lib/diameter/src/base/diameter_reg.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2015. All Rights Reserved. +%% Copyright Ericsson AB 2010-2016. 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. @@ -25,14 +25,12 @@ -module(diameter_reg). -behaviour(gen_server). --compile({no_auto_import, [monitor/2]}). - -export([add/1, add_new/1, - del/1, - repl/2, + remove/1, match/1, - wait/1]). + wait/1, + subscribe/2]). -export([start_link/0]). @@ -46,29 +44,32 @@ %% test -export([pids/0, - terms/0]). + terms/0, + subs/0, + waits/0]). %% debug -export([state/0, uptime/0]). --include("diameter_internal.hrl"). - -define(SERVER, ?MODULE). -define(TABLE, ?MODULE). -%% Table entry used to keep from starting more than one monitor on the -%% same process. This isn't a problem but there's no point in starting -%% multiple monitors if we can avoid it. Note that we can't have a 2-tuple -%% keyed on Pid since a registered term can be anything. Want the entry -%% keyed on Pid so that lookup is fast. --define(MONITOR(Pid, MRef), {Pid, monitor, MRef}). - -%% Table entry containing the Term -> Pid mapping. --define(MAPPING(Term, Pid), {Term, Pid}). +-type key() :: term(). +-type from() :: {pid(), term()}. +-type pattern() :: term(). -record(state, {id = diameter_lib:now(), - q = []}). %% [{From, Pat}] + receivers = dict:new() + :: dict:dict(pattern(), [[pid() | term()]%% subscribe + | from()]), %% wait + monitors = sets:new() :: sets:set(pid())}). + +%% The ?TABLE bag contains the Key -> Pid mapping, as {Key, Pid} +%% tuples. Each pid is stored in the monitors set to ensure only one +%% monitor for each pid: more are harmless, but unnecessary. A pattern +%% is added to receivers a result of calls to wait/1 or subscribe/2: +%% changes to ?TABLE causes processes to be notified as required. %% =========================================================================== %% # add(T) @@ -77,18 +78,18 @@ %% this or other assocations can be retrieved using match/1. %% %% An association is removed when the calling process dies or as a -%% result of calling del/1. Adding the same term more than once is -%% equivalent to adding it exactly once. +%% result of calling remove/1. Adding the same term more than once is +%% equivalent to adding it once. %% %% Note that since match/1 takes a pattern as argument, specifying a %% term that contains match variables is probably not a good idea %% =========================================================================== --spec add(any()) +-spec add(key()) -> true. add(T) -> - call({add, fun ets:insert/2, T, self()}). + call({add, false, T}). %% =========================================================================== %% # add_new(T) @@ -97,36 +98,23 @@ add(T) -> %% association, false being returned if an association already exists. %% =========================================================================== --spec add_new(any()) +-spec add_new(key()) -> boolean(). add_new(T) -> - call({add, fun insert_new/2, T, self()}). + call({add, true, T}). %% =========================================================================== -%% # repl(T, NewT) -%% -%% Like add/1 but only replace an existing association on T, false -%% being returned if it doesn't exist. -%% =========================================================================== - --spec repl(any(), any()) - -> boolean(). - -repl(T, U) -> - call({repl, T, U, self()}). - -%% =========================================================================== -%% # del(Term) +%% # remove(Term) %% %% Remove any existing association of Term with self(). %% =========================================================================== --spec del(any()) +-spec remove(key()) -> true. -del(T) -> - call({del, T, self()}). +remove(T) -> + call({remove, T}). %% =========================================================================== %% # match(Pat) @@ -139,12 +127,17 @@ del(T) -> %% associations removed.) %% =========================================================================== --spec match(any()) - -> [{term(), pid()}]. +-spec match(pattern()) + -> [{key(), pid()}]. match(Pat) -> - ets:match_object(?TABLE, ?MAPPING(Pat, '_')). + match(Pat, '_'). + +%% match/2 +match(Pat, Pid) -> + ets:match_object(?TABLE, {Pat, Pid}). + %% =========================================================================== %% # wait(Pat) %% @@ -152,10 +145,29 @@ match(Pat) -> %% It's up to the caller to ensure that the wait won't be forever. %% =========================================================================== +-spec wait(pattern()) + -> [{key(), pid()}]. + wait(Pat) -> + _ = match(Pat), %% ensure match can succeed call({wait, Pat}). %% =========================================================================== +%% # subscribe(Pat, T) +%% +%% Like match/1, but additionally receive messages of the form +%% {T, add|remove, {term(), pid()} when associations are added +%% or removed. +%% =========================================================================== + +-spec subscribe(Pat :: any(), T :: term()) + -> [{term(), pid()}]. + +subscribe(Pat, T) -> + _ = match(Pat), %% ensure match can succeed + call({subscribe, Pat, T}). + +%% =========================================================================== start_link() -> ServerName = {local, ?SERVER}, @@ -169,19 +181,15 @@ uptime() -> call(uptime). %% pids/0 -%% -%% Return: list of {Pid, [Term, ...]} + +-spec pids() + -> [{pid(), [key()]}]. pids() -> to_list(fun swap/1). to_list(Fun) -> - ets:foldl(fun(T,A) -> acc(Fun, T, A) end, orddict:new(), ?TABLE). - -acc(Fun, ?MAPPING(Term, Pid), Dict) -> - append(Fun({Term, Pid}), Dict); -acc(_, _, Dict) -> - Dict. + ets:foldl(fun(T,D) -> append(Fun(T), D) end, orddict:new(), ?TABLE). append({K,V}, Dict) -> orddict:append(K, V, Dict). @@ -189,14 +197,47 @@ append({K,V}, Dict) -> id(T) -> T. %% terms/0 -%% -%% Return: list of {Term, [Pid, ...]} + +-spec terms() + -> [{key(), [pid()]}]. terms() -> to_list(fun id/1). swap({X,Y}) -> {Y,X}. +%% subs/0 + +-spec subs() + -> [{pattern(), [{pid(), term()}]}]. + +subs() -> + #state{receivers = RD} = state(), + dict:fold(fun sub/3, orddict:new(), RD). + +sub(Pat, Ps, Dict) -> + lists:foldl(fun([P|T], D) -> orddict:append(Pat, {P,T}, D); + (_, D) -> D + end, + Dict, + Ps). + +%% waits/0 + +-spec waits() + -> [{pattern(), [{from(), term()}]}]. + +waits() -> + #state{receivers = RD} = state(), + dict:fold(fun wait/3, orddict:new(), RD). + +wait(Pat, Ps, Dict) -> + lists:foldl(fun({_,_} = F, D) -> orddict:append(Pat, F, D); + (_, D) -> D + end, + Dict, + Ps). + %% ---------------------------------------------------------- %% # init/1 %% ---------------------------------------------------------- @@ -209,57 +250,58 @@ init(_) -> %% # handle_call/3 %% ---------------------------------------------------------- -handle_call({add, Fun, Key, Pid}, _, S) -> - B = Fun(?TABLE, {Key, Pid}), - monitor(B andalso no_monitor(Pid), Pid), - {reply, B, pending(B, S)}; - -handle_call({del, Key, Pid}, _, S) -> - {reply, ets:delete_object(?TABLE, ?MAPPING(Key, Pid)), S}; - -handle_call({repl, T, U, Pid}, _, S) -> - MatchSpec = [{?MAPPING('$1', Pid), - [{'=:=', '$1', {const, T}}], - ['$_']}], - {reply, repl(ets:select(?TABLE, MatchSpec), U, Pid), S}; - -handle_call({wait, Pat}, From, #state{q = Q} = S) -> - case find(Pat) of - {ok, L} -> - {reply, L, S}; - false -> - {noreply, S#state{q = [{From, Pat} | Q]}} +handle_call({add, Uniq, Key}, {Pid, _}, S0) -> + Rec = {Key, Pid}, + S1 = flush(Uniq, Rec, S0), + {Res, New} = insert(Uniq, Rec), + {Recvs, S} = add(New, Rec, S1), + notify(Recvs, Rec), + {reply, Res, S}; + +handle_call({remove, Key}, {Pid, _}, S) -> + Rec = {Key, Pid}, + Recvs = delete([Rec], S), + ets:delete_object(?TABLE, Rec), + notify(Recvs, remove), + {reply, true, S}; + +handle_call({wait, Pat}, {Pid, _} = From, #state{receivers = RD} = S) -> + NS = add_monitor(Pid, S), + case match(Pat) of + [_|_] = L -> + {reply, L, NS}; + [] -> + {noreply, NS#state{receivers = dict:append(Pat, From, RD)}} end; +handle_call({subscribe, Pat, T}, {Pid, _}, #state{receivers = RD} = S) -> + NS = add_monitor(Pid, S), + {reply, match(Pat), NS#state{receivers = dict:append(Pat, [Pid | T], RD)}}; + handle_call(state, _, S) -> {reply, S, S}; handle_call(uptime, _, #state{id = Time} = S) -> {reply, diameter_lib:now_diff(Time), S}; -handle_call(Req, From, S) -> - ?UNEXPECTED([Req, From]), +handle_call(_Req, _From, S) -> {reply, nok, S}. %% ---------------------------------------------------------- %% # handle_cast/2 %% ---------------------------------------------------------- -handle_cast(Msg, S)-> - ?UNEXPECTED([Msg]), +handle_cast(_Msg, S)-> {noreply, S}. %% ---------------------------------------------------------- %% # handle_info/2 %% ---------------------------------------------------------- -handle_info({'DOWN', MRef, process, Pid, _}, S) -> - ets:delete_object(?TABLE, ?MONITOR(Pid, MRef)), - ets:match_delete(?TABLE, ?MAPPING('_', Pid)), - {noreply, S}; +handle_info({'DOWN', _MRef, process, Pid, _}, S) -> + {noreply, down(Pid, S)}; -handle_info(Info, S) -> - ?UNEXPECTED([Info]), +handle_info(_Info, S) -> {noreply, S}. %% ---------------------------------------------------------- @@ -278,71 +320,166 @@ code_change(_OldVsn, State, _Extra) -> %% =========================================================================== -monitor(true, Pid) -> - ets:insert(?TABLE, ?MONITOR(Pid, erlang:monitor(process, Pid))); -monitor(false, _) -> - ok. +%% insert/2 + +insert(false, Rec) -> + Spec = [{'$1', [{'==', '$1', {const, Rec}}], ['$_']}], + X = '$end_of_table' /= ets:select(?TABLE, Spec, 1), %% entry exists? + X orelse ets:insert(?TABLE, Rec), + {true, not X}; -%% Do we need a monitor for the specified Pid? -no_monitor(Pid) -> - [] == ets:match_object(?TABLE, ?MONITOR(Pid, '_')). +insert(true, Rec) -> + B = ets:insert_new(?TABLE, Rec), %% entry inserted? + {B, B}. -%% insert_new/2 +%% add/3 -insert_new(?TABLE, {Key, _} = T) -> - flush(ets:lookup(?TABLE, Key)), - ets:insert_new(?TABLE, T). +%% Only add a single monitor for any given process, since there's no +%% use to more. +add(true, {_Key, Pid} = Rec, S) -> + NS = add_monitor(Pid, S), + {Recvs, RD} = add(Rec, NS), + {Recvs, S#state{receivers = RD}}; + +add(false = No, _, S) -> + {No, S}. + +%% add/2 + +%% Notify processes whose patterns match the inserted key. +add({_Key, Pid} = Rec, #state{receivers = RD}) -> + dict:fold(fun(Pt, Ps, A) -> + add(lists:member(Rec, match(Pt, Pid)), Pt, Ps, Rec, A) + end, + {sets:new(), RD}, + RD). + +%% add/5 + +add(true, Pat, Recvs, {_,_} = Rec, {Set, Dict}) -> + {lists:foldl(fun sets:add_element/2, Set, Recvs), + remove(fun erlang:is_list/1, Pat, Recvs, Dict)}; + +add(false, _, _, _, Acc) -> + Acc. + +%% add_monitor/2 + +add_monitor(Pid, #state{monitors = MS} = S) -> + add_monitor(sets:is_element(Pid, MS), Pid, S). + +%% add_monitor/3 + +add_monitor(false, Pid, #state{monitors = MS} = S) -> + monitor(process, Pid), + S#state{monitors = sets:add_element(Pid, MS)}; + +add_monitor(true, _, S) -> + S. + +%% delete/2 + +delete(Recs, #state{receivers = RD}) -> + lists:foldl(fun(R,S) -> delete(R, RD, S) end, sets:new(), Recs). + +%% delete/3 + +delete({_Key, Pid} = Rec, RD, Set) -> + dict:fold(fun(Pt, Ps, S) -> + delete(lists:member(Rec, match(Pt, Pid)), Rec, Ps, S) + end, + Set, + RD). + +%% delete/4 + +%% Entry matches a pattern ... +delete(true, Rec, Recvs, Set) -> + lists:foldl(fun(R,S) -> sets:add_element({R, Rec}, S) end, + Set, + Recvs); + +%% ... or not. +delete(false, _, _, Set) -> + Set. + +%% notify/2 + +notify(false = No, _) -> + No; + +notify(Recvs, remove = Op) -> + sets:fold(fun({P,R}, N) -> send(P, R, Op), N+1 end, 0, Recvs); + +notify(Recvs, {_,_} = Rec) -> + sets:fold(fun(P,N) -> send(P, Rec, add), N+1 end, 0, Recvs). + +%% send/3 + +%% No processes waiting on remove, by construction: they've either +%% received notification at add or aren't waiting. +send([Pid | T], Rec, Op) -> + Pid ! {T, Op, Rec}; + +send({_,_} = From, Rec, add) -> + gen_server:reply(From, [Rec]). + +%% down/2 + +down(Pid, #state{monitors = MS} = S) -> + NS = flush(Pid, S), + Recvs = delete(match('_', Pid), NS), + ets:match_delete(?TABLE, {'_', Pid}), + notify(Recvs, remove), + NS#state{monitors = sets:del_element(Pid, MS)}. + +%% flush/3 %% Remove any processes that are dead but for which we may not have -%% received 'DOWN' yet. This is to ensure that add_new can be used -%% to register a unique name each time a process restarts. -flush(List) -> - lists:foreach(fun({_,P} = T) -> - del(erlang:is_process_alive(P), T) - end, - List). - -del(Alive, T) -> - Alive orelse ets:delete_object(?TABLE, T). - -%% repl/3 - -repl([?MAPPING(_, Pid) = M], Key, Pid) -> - ets:delete_object(?TABLE, M), - true = ets:insert(?TABLE, ?MAPPING(Key, Pid)); -repl([], _, _) -> - false. - -%% pending/1 - -pending(true, #state{q = [_|_] = Q} = S) -> - S#state{q = q(lists:reverse(Q), [])}; %% retain reply order -pending(_, S) -> +%% received 'DOWN' yet, to ensure that add_new can be used to register +%% a unique name each time a registering process restarts. +flush(true, {Key, Pid}, S) -> + Spec = [{{'$1', '$2'}, + [{'andalso', {'==', '$1', {const, Key}}, + {'/=', '$2', Pid}}], + ['$2']}], + lists:foldl(fun down/2, S, [P || P <- ets:select(?TABLE, Spec), + not is_process_alive(P)]); + +flush(false, _, S) -> S. -q([], Q) -> - Q; -q([{From, Pat} = T | Rest], Q) -> - case find(Pat) of - {ok, L} -> - gen_server:reply(From, L), - q(Rest, Q); - false -> - q(Rest, [T|Q]) - end. - -%% find/1 - -find(Pat) -> - try match(Pat) of - [] -> - false; - L -> - {ok, L} - catch - _:_ -> - {ok, []} - end. +%% flush/2 + +%% Process has died and should no longer receive messages/replies. +flush(Pid, #state{receivers = RD} = S) + when is_pid(Pid) -> + S#state{receivers = dict:fold(fun(Pt,Ps,D) -> flush(Pid, Pt, Ps, D) end, + RD, + RD)}. + +%% flush/4 + +flush(Pid, Pat, Recvs, Dict) -> + remove(fun(T) -> Pid /= head(T) end, Pat, Recvs, Dict). + +%% head/1 + +head([P|_]) -> + P; + +head({P,_}) -> + P. + +%% remove/4 + +remove(Pred, Key, Values, Dict) -> + case lists:filter(Pred, Values) of + [] -> + dict:erase(Key, Dict); + Rest -> + dict:store(Key, Rest, Dict) + end. %% call/1 diff --git a/lib/diameter/src/base/diameter_service.erl b/lib/diameter/src/base/diameter_service.erl index cfb5cb5b82..ccf68f4d93 100644 --- a/lib/diameter/src/base/diameter_service.erl +++ b/lib/diameter/src/base/diameter_service.erl @@ -136,7 +136,7 @@ state = ?WD_INITIAL :: match(wd_state()), started = diameter_lib:now(),%% at process start peer = false :: match(boolean() | pid())}). - %% true at accepted, pid() at okay/reopen + %% true at accepted/remove, pid() at okay/reopen %% Record representing a Peer State Machine processes implemented by %% diameter_peer_fsm. @@ -250,7 +250,7 @@ subscribe(SvcName) -> diameter_reg:add({?MODULE, subscriber, SvcName}). unsubscribe(SvcName) -> - diameter_reg:del({?MODULE, subscriber, SvcName}). + diameter_reg:remove({?MODULE, subscriber, SvcName}). subscriptions(Pat) -> pmap(diameter_reg:match({?MODULE, subscriber, Pat})). @@ -676,25 +676,34 @@ mod_state(Alias, ModS) -> %% remove_transport shutdown(Refs, #state{watchdogT = WatchdogT}) when is_list(Refs) -> - ets:foldl(fun(P,ok) -> st(P, Refs), ok end, ok, WatchdogT); + ets:insert(WatchdogT, ets:foldl(fun(R,A) -> st(R, Refs, A) end, + [], + WatchdogT)); %% application/service shutdown shutdown(Reason, #state{watchdogT = WatchdogT}) when Reason == application; Reason == service -> - diameter_lib:wait(ets:foldl(fun(P,A) -> st(P, Reason, A) end, + diameter_lib:wait(ets:foldl(fun(P,A) -> ss(P, Reason, A) end, [], WatchdogT)). -%% st/2 +%% st/3 -st(#watchdog{ref = Ref, pid = Pid}, Refs) -> - lists:member(Ref, Refs) - andalso (Pid ! {shutdown, self(), transport}). %% 'DOWN' cleans up +%% Mark replacement as started so that a subsequent accept doesn't +%% result in a new process that isn't terminated. +st(#watchdog{ref = Ref, pid = Pid, peer = P} = Rec, Refs, Acc) -> + case lists:member(Ref, Refs) of + true -> + Pid ! {shutdown, self(), transport}, %% 'DOWN' cleans up + [Rec#watchdog{peer = true} || P == false] ++ Acc; + false -> + Acc + end. -%% st/3 +%% ss/3 -st(#watchdog{pid = Pid}, Reason, Acc) -> +ss(#watchdog{pid = Pid}, Reason, Acc) -> MRef = monitor(process, Pid), Pid ! {shutdown, self(), Reason}, [MRef | Acc]. @@ -974,11 +983,22 @@ ms(_, Svc) -> %% --------------------------------------------------------------------------- accepted(Pid, _TPid, #state{watchdogT = WatchdogT} = S) -> - #watchdog{ref = Ref, type = accept = T, peer = false, options = Opts} + #watchdog{type = accept = T, peer = P} = Wd = fetch(WatchdogT, Pid), - ets:insert(WatchdogT, Wd#watchdog{peer = true}),%% mark replacement started - start(Ref, T, Opts, S). %% start new watchdog + if not P -> + #watchdog{ref = Ref, options = Opts} = Wd, + %% Mark replacement started, and start new watchdog. + ets:insert(WatchdogT, Wd#watchdog{peer = true}), + start(Ref, T, Opts, S); + P -> + %% Transport removal in progress: true has been set in + %% shutdown/2, and the transport will die as a + %% consequence. + ok + end. + +%% fetch/2 fetch(Tid, Key) -> [T] = ets:lookup(Tid, Key), @@ -1317,8 +1337,7 @@ start_tc(Tc, T, _) -> tc_timeout({Ref, _Type, _Opts} = T, #state{service_name = SvcName} = S) -> tc(diameter_config:have_transport(SvcName, Ref), T, S). -tc(true, {Ref, Type, Opts}, #state{service_name = SvcName} - = S) -> +tc(true, {Ref, Type, Opts}, #state{service_name = SvcName} = S) -> send_event(SvcName, {reconnect, Ref, Opts}), start(Ref, Type, Opts, S); tc(false = No, _, _) -> %% removed diff --git a/lib/diameter/src/base/diameter_session.erl b/lib/diameter/src/base/diameter_session.erl index 53973649fd..d854bc36a5 100644 --- a/lib/diameter/src/base/diameter_session.erl +++ b/lib/diameter/src/base/diameter_session.erl @@ -158,10 +158,9 @@ session_id(Host) -> %% --------------------------------------------------------------------------- init() -> - {Now, Seed} = diameter_lib:seed(), - random:seed(Seed), + Now = diameter_lib:timestamp(), Time = time32(Now), - Seq = (?INT32 band (Time bsl 20)) bor (random:uniform(1 bsl 20) - 1), + Seq = (?INT32 band (Time bsl 20)) bor (rand:uniform(1 bsl 20) - 1), ets:insert(diameter_sequence, [{origin_state_id, Time}, {session_base, Time bsl 32}, {sequence, Seq}]), diff --git a/lib/diameter/src/base/diameter_sup.erl b/lib/diameter/src/base/diameter_sup.erl index e89ede9843..482289cb9a 100644 --- a/lib/diameter/src/base/diameter_sup.erl +++ b/lib/diameter/src/base/diameter_sup.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2015. All Rights Reserved. +%% Copyright Ericsson AB 2010-2016. 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. @@ -34,6 +34,7 @@ -export([init/1]). -define(CHILDREN, [diameter_misc_sup, + diameter_config_sup, diameter_watchdog_sup, diameter_peer_fsm_sup, diameter_transport_sup, diff --git a/lib/diameter/src/base/diameter_watchdog.erl b/lib/diameter/src/base/diameter_watchdog.erl index 3fd87b223e..2ba60a65fb 100644 --- a/lib/diameter/src/base/diameter_watchdog.erl +++ b/lib/diameter/src/base/diameter_watchdog.erl @@ -125,8 +125,6 @@ i({Ack, T, Pid, {RecvData, = Svc}}) -> monitor(process, Pid), wait(Ack, Pid), - {_, Seed} = diameter_lib:seed(), - random:seed(Seed), putr(restart, {T, Opts, Svc, SvcOpts}), %% save seeing it in trace putr(dwr, dwr(Caps)), %% {_,_} = Mask = proplists:get_value(sequence, SvcOpts), @@ -565,7 +563,7 @@ tw(TwInit, Ms) -> tw(T) when is_integer(T), T >= 6000 -> - T - 2000 + (random:uniform(4001) - 1); %% RFC3539 jitter of +/- 2 sec. + T - 2000 + (rand:uniform(4001) - 1); %% RFC3539 jitter of +/- 2 sec. tw({M,F,A}) -> apply(M,F,A). diff --git a/lib/diameter/src/modules.mk b/lib/diameter/src/modules.mk index 3b223ea391..4e4ce60ddf 100644 --- a/lib/diameter/src/modules.mk +++ b/lib/diameter/src/modules.mk @@ -1,7 +1,7 @@ # %CopyrightBegin% # -# Copyright Ericsson AB 2010-2015. All Rights Reserved. +# Copyright Ericsson AB 2010-2016. 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. @@ -37,6 +37,7 @@ RT_MODULES = \ base/diameter_callback \ base/diameter_capx \ base/diameter_config \ + base/diameter_config_sup \ base/diameter_codec \ base/diameter_dict \ base/diameter_lib \ diff --git a/lib/diameter/src/transport/diameter_sctp.erl b/lib/diameter/src/transport/diameter_sctp.erl index 8a80ce630a..4a005b853d 100644 --- a/lib/diameter/src/transport/diameter_sctp.erl +++ b/lib/diameter/src/transport/diameter_sctp.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2015. All Rights Reserved. +%% Copyright Ericsson AB 2010-2016. 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. @@ -61,10 +61,6 @@ %% Remote addresses to accept connections from. -define(DEFAULT_ACCEPT, []). %% any -%% How long a listener with no associations lives before offing -%% itself. --define(LISTENER_TIMEOUT, 30000). - %% How long to wait for a transport process to attach after %% association establishment. -define(ACCEPT_TIMEOUT, 5000). @@ -104,7 +100,6 @@ socket :: gen_sctp:sctp_socket(), count = 0 :: uint(), %% attached transport processes pending = {0, queue:new()}, - tref :: reference() | undefined, accept :: [match()]}). %% Field pending implements two queues: the first of transport-to-be %% processes to which an association has been assigned but for which @@ -216,14 +211,15 @@ init(T) -> %% A process owning a listening socket. i({listen, Ref, {Opts, Addrs}}) -> + [_] = diameter_config:subscribe(Ref, transport), %% assert existence {[Matches], Rest} = proplists:split(Opts, [accept]), {LAs, Sock} = AS = open(Addrs, Rest, ?DEFAULT_PORT), ok = gen_sctp:listen(Sock, true), true = diameter_reg:add_new({?MODULE, listener, {Ref, AS}}), proc_lib:init_ack({ok, self(), LAs}), - start_timer(#listener{ref = Ref, - socket = Sock, - accept = [[M] || {accept, M} <- Matches]}); + #listener{ref = Ref, + socket = Sock, + accept = [[M] || {accept, M} <- Matches]}; %% A connecting transport. i({connect, Pid, Opts, Addrs, Ref}) -> @@ -431,13 +427,6 @@ putr(Key, Val) -> getr(Key) -> get({?MODULE, Key}). -%% start_timer/1 - -start_timer(#listener{count = 0} = S) -> - S#listener{tref = erlang:start_timer(?LISTENER_TIMEOUT, self(), close)}; -start_timer(S) -> - S. - %% l/2 %% %% Transition listener state. @@ -455,12 +444,10 @@ l({sctp, Sock, _RA, _RP, Data} = T, #listener{socket = Sock, l({'DOWN', _MRef, process, TPid, _}, #listener{pending = {_,Q}} = S) -> down(queue:member(TPid, Q), TPid, S); -%% Timeout after the last accepting process has died. -l({timeout, TRef, close = T}, #listener{tref = TRef, - count = 0}) -> - x(T); -l({timeout, _, close}, #listener{} = S) -> - S. +%% Transport has been removed. +l({transport, remove, _} = T, #listener{socket = Sock}) -> + gen_sctp:close(Sock), + x(T). %% down/3 %% @@ -472,15 +459,15 @@ down(true, TPid, #listener{pending = {N,Q}, = S) -> NQ = queue:filter(fun(P) -> P /= TPid end, Q), if N < 0 -> %% awaiting an association ... - start_timer(S#listener{count = K-1, - pending = {N+1, NQ}}); + S#listener{count = K-1, + pending = {N+1, NQ}}; true -> %% ... or one has been assigned S#listener{pending = {N-1, NQ}} end; %% ... or one that's already attached. down(false, _TPid, #listener{count = K} = S) -> - start_timer(S#listener{count = K-1}). + S#listener{count = K-1}. %% t/2 %% diff --git a/lib/diameter/src/transport/diameter_tcp.erl b/lib/diameter/src/transport/diameter_tcp.erl index 6a5e5fe89d..546c2cfa5e 100644 --- a/lib/diameter/src/transport/diameter_tcp.erl +++ b/lib/diameter/src/transport/diameter_tcp.erl @@ -57,7 +57,6 @@ -define(ERROR(T), erlang:error({T, ?MODULE, ?LINE})). -define(DEFAULT_PORT, 3868). %% RFC 3588, ch 2.1 --define(LISTENER_TIMEOUT, 30000). -define(DEFAULT_FRAGMENT_TIMEOUT, 1000). -define(IS_UINT32(N), (is_integer(N) andalso 0 =< N andalso 0 == N bsr 32)). @@ -73,8 +72,10 @@ %% Listener process state. -record(listener, {socket :: inet:socket(), - count = 1 :: non_neg_integer(), - tref :: reference() | undefined}). + count = 1 :: non_neg_integer()}). %% accepting processes +%% The count of accepting processes was previously used to terminate +%% the listening process, but diameter_reg:subscribe/2 is now used for +%% this. Leave the the count for trace purposes. %% Monitor process state. -record(monitor, @@ -240,6 +241,7 @@ i(#monitor{parent = Pid, transport = TPid} = S) -> %% gen_tcp seems to so. Links should be left to supervisors. i({listen, LRef, APid, {Mod, Opts, Addrs}}) -> + [_] = diameter_config:subscribe(LRef, transport), %% assert existence {[LA, LP], Rest} = proplists:split(Opts, [ip, port]), LAddrOpt = get_addr(LA, Addrs), LPort = get_port(LP), @@ -248,7 +250,7 @@ i({listen, LRef, APid, {Mod, Opts, Addrs}}) -> true = diameter_reg:add_new({?MODULE, listener, {LRef, {LAddr, LSock}}}), proc_lib:init_ack({ok, self(), {LAddr, LSock}}), monitor(process, APid), - start_timer(#listener{socket = LSock}). + #listener{socket = LSock}. laddr([], Mod, Sock) -> {ok, {Addr, _Port}} = sockname(Mod, Sock), @@ -484,13 +486,6 @@ putr(Key, Val) -> getr(Key) -> get({?MODULE, Key}). -%% start_timer/1 - -start_timer(#listener{count = 0} = S) -> - S#listener{tref = erlang:start_timer(?LISTENER_TIMEOUT, self(), close)}; -start_timer(S) -> - S. - %% m/2 %% %% Transition monitor state. @@ -512,21 +507,19 @@ m({'DOWN', _, process, Pid, _}, #monitor{parent = Pid, %% %% Transition listener state. -%% Another accept transport is attaching. +%% An accepting transport is attaching. l({accept, TPid}, #listener{count = N} = S) -> monitor(process, TPid), S#listener{count = N+1}; %% Accepting process has died. l({'DOWN', _, process, _, _}, #listener{count = N} = S) -> - start_timer(S#listener{count = N-1}); + S#listener{count = N-1}; -%% Timeout after the last accepting process has died. -l({timeout, TRef, close = T}, #listener{tref = TRef, - count = 0}) -> - x(T); -l({timeout, _, close}, #listener{} = S) -> - S. +%% Transport has been removed. +l({transport, remove, _} = T, #listener{socket = Sock}) -> + gen_tcp:close(Sock), + x(T). %% t/2 %% diff --git a/lib/diameter/test/diameter_codec_test.erl b/lib/diameter/test/diameter_codec_test.erl index 78308856ac..37c41a1761 100644 --- a/lib/diameter/test/diameter_codec_test.erl +++ b/lib/diameter/test/diameter_codec_test.erl @@ -230,13 +230,12 @@ v(Max, Ord, E) when Ord =< Max -> diameter_enum:to_list(E); v(Max, Ord, E) -> - random:seed(diameter_util:seed()), v(Max, Ord, E, []). v(0, _, _, Acc) -> Acc; v(N, Ord, E, Acc) -> - v(N-1, Ord, E, [E(random:uniform(Ord)) | Acc]). + v(N-1, Ord, E, [E(rand:uniform(Ord)) | Acc]). %% arity/3 @@ -518,15 +517,7 @@ random(M) -> random(0,M). random(Mn,Mx) -> - seed(get({?MODULE, seed})), - Mn + random:uniform(Mx - Mn + 1) - 1. - -seed(undefined) -> - put({?MODULE, seed}, true), - random:seed(diameter_util:seed()); - -seed(true) -> - ok. + Mn + rand:uniform(Mx - Mn + 1) - 1. %% run/1 %% diff --git a/lib/diameter/test/diameter_gen_sctp_SUITE.erl b/lib/diameter/test/diameter_gen_sctp_SUITE.erl index cbd7fc8ec5..79db39ca45 100644 --- a/lib/diameter/test/diameter_gen_sctp_SUITE.erl +++ b/lib/diameter/test/diameter_gen_sctp_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2015. All Rights Reserved. +%% Copyright Ericsson AB 2010-2016. 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. @@ -365,8 +365,8 @@ open(Opts) -> assoc(Sock) -> receive - ?SCTP(Sock, {[], #sctp_assoc_change{state = S, - assoc_id = Id}}) -> + ?SCTP(Sock, {_, #sctp_assoc_change{state = S, + assoc_id = Id}}) -> comm_up = S, %% assert Id end. diff --git a/lib/diameter/test/diameter_reg_SUITE.erl b/lib/diameter/test/diameter_reg_SUITE.erl index 3d9ad8bfa8..e2a1ca00c3 100644 --- a/lib/diameter/test/diameter_reg_SUITE.erl +++ b/lib/diameter/test/diameter_reg_SUITE.erl @@ -33,8 +33,7 @@ %% testcases -export([add/1, add_new/1, - del/1, - repl/1, + remove/1, terms/1, pids/1]). @@ -56,8 +55,7 @@ groups() -> tc() -> [add, add_new, - del, - repl, + remove, terms, pids]. @@ -82,20 +80,11 @@ add_new(_) -> true = ?reg:add_new(Ref), false = ?reg:add_new(Ref). -del(_) -> +remove(_) -> Ref = make_ref(), true = ?reg:add_new(Ref), true = ?reg:add_new({Ref}), - true = ?reg:del({Ref}), - [{Ref, Pid}] = ?reg:match(Ref), - Pid = self(). - -repl(_) -> - Ref = make_ref(), - true = ?reg:add_new({Ref}), - true = ?reg:repl({Ref}, Ref), - false = ?reg:add_new(Ref), - false = ?reg:repl({Ref}, Ref), + true = ?reg:remove({Ref}), [{Ref, Pid}] = ?reg:match(Ref), Pid = self(). diff --git a/lib/diameter/test/diameter_relay_SUITE.erl b/lib/diameter/test/diameter_relay_SUITE.erl index f766f54a80..b5e520e642 100644 --- a/lib/diameter/test/diameter_relay_SUITE.erl +++ b/lib/diameter/test/diameter_relay_SUITE.erl @@ -171,8 +171,9 @@ connect(Config) -> Conns)). disconnect(Config) -> - lists:foreach(fun({{CN,CR},{SN,SR}}) -> ?util:disconnect(CN,CR,SN,SR) end, - ?util:read_priv(Config, "cfg")). + [] = [{T,C} || C <- ?util:read_priv(Config, "cfg"), + T <- [break(C)], + T /= ok]. stop_services(_Config) -> [] = [{H,T} || H <- ?SERVICES, @@ -184,6 +185,13 @@ stop(_Config) -> %% ---------------------------------------- +break({{CN,CR},{SN,SR}}) -> + try + ?util:disconnect(CN,CR,SN,SR) + after + diameter:remove_transport(SN, SR) + end. + server(Name, Dict) -> ok = diameter:start_service(Name, ?SERVICE(Name, Dict)), {Name, ?util:listen(Name, tcp)}. diff --git a/lib/diameter/test/diameter_traffic_SUITE.erl b/lib/diameter/test/diameter_traffic_SUITE.erl index 976abf9138..6f3a4801ee 100644 --- a/lib/diameter/test/diameter_traffic_SUITE.erl +++ b/lib/diameter/test/diameter_traffic_SUITE.erl @@ -428,7 +428,11 @@ remove_transports(Config) -> server_service = SN} = group(Config), [LRef | Cs] = ?util:read_priv(Config, "transport"), - [?util:disconnect(CN, C, SN, LRef) || C <- Cs]. + try + [] = [T || C <- Cs, T <- [?util:disconnect(CN, C, SN, LRef)], T /= ok] + after + ok = diameter:remove_transport(SN, LRef) + end. stop_services(Config) -> #group{client_service = CN, diff --git a/lib/diameter/test/diameter_transport_SUITE.erl b/lib/diameter/test/diameter_transport_SUITE.erl index 53d2d6660e..c94f46b7a5 100644 --- a/lib/diameter/test/diameter_transport_SUITE.erl +++ b/lib/diameter/test/diameter_transport_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2015. All Rights Reserved. +%% Copyright Ericsson AB 2010-2016. 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. @@ -140,7 +140,9 @@ sctp_accept(Config) -> -define(PEER_COUNT, 8). accept(Prot) -> - T = {Prot, make_ref()}, + Ref = make_ref(), + true = diameter_reg:add_new({diameter_config, transport, Ref}), %% fake it + T = {Prot, Ref}, [] = ?util:run(?util:scramble(acc(2*?PEER_COUNT, T, []))). acc(0, _, Acc) -> @@ -336,13 +338,12 @@ make_msg() -> %% crypto:rand_bytes/1 isn't available on all platforms (since openssl %% isn't) so roll our own. rand_bytes(N) -> - random:seed(diameter_util:seed()), rand_bytes(N, <<>>). rand_bytes(0, Bin) -> Bin; rand_bytes(N, Bin) -> - Oct = random:uniform(256) - 1, + Oct = rand:uniform(256) - 1, rand_bytes(N-1, <<Oct, Bin/binary>>). %% =========================================================================== diff --git a/lib/diameter/test/diameter_util.erl b/lib/diameter/test/diameter_util.erl index 52b747e99c..37fcbbc267 100644 --- a/lib/diameter/test/diameter_util.erl +++ b/lib/diameter/test/diameter_util.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2015. All Rights Reserved. +%% Copyright Ericsson AB 2010-2016. 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. @@ -31,7 +31,6 @@ fold/3, foldl/3, scramble/1, - seed/0, unique_string/0, have_sctp/0]). @@ -178,23 +177,15 @@ scramble(L) -> [[fun s/1, L]]). s(L) -> - random:seed(seed()), s([], L). s(Acc, []) -> Acc; s(Acc, L) -> - {H, [T|Rest]} = lists:split(random:uniform(length(L)) - 1, L), + {H, [T|Rest]} = lists:split(rand:uniform(length(L)) - 1, L), s([T|Acc], H ++ Rest). %% --------------------------------------------------------------------------- -%% seed/0 - -seed() -> - {_,T} = diameter_lib:seed(), - T. - -%% --------------------------------------------------------------------------- %% unique_string/0 unique_string() -> @@ -345,11 +336,12 @@ transport(SvcName, Ref) -> disconnect(Client, Ref, Server, LRef) -> true = diameter:subscribe(Server), ok = diameter:remove_transport(Client, Ref), - ok = receive - {diameter_event, Server, {down, LRef, _, _}} -> ok - after 10000 -> - {Client, Ref, Server, LRef, process_info(self(), messages)} - end. + receive + {diameter_event, Server, {down, LRef, _, _}} -> + ok + after 10000 -> + {Client, Ref, Server, LRef, process_info(self(), messages)} + end. %% --------------------------------------------------------------------------- diff --git a/lib/edoc/src/edoc_layout.erl b/lib/edoc/src/edoc_layout.erl index e86d090b13..ef57b7b084 100644 --- a/lib/edoc/src/edoc_layout.erl +++ b/lib/edoc/src/edoc_layout.erl @@ -901,17 +901,11 @@ t_map(Es) -> t_map_field(#xmlElement{content = [K,V]}=E) -> KElem = t_utype_elem(K), VElem = t_utype_elem(V), - AT = get_attrval(assoc_type, E), - IsAny = fun(["any","()"]) -> true; (_) -> false end, - case AT =:= "assoc" andalso IsAny(KElem) andalso IsAny(VElem) of - true -> "..."; - false -> - AS = case AT of - "assoc" -> " => "; - "exact" -> " := " - end, - KElem ++ [AS] ++ VElem - end. + AS = case get_attrval(assoc_type, E) of + "assoc" -> " => "; + "exact" -> " := " + end, + KElem ++ [AS] ++ VElem. t_record(E, Es) -> Name = ["#"] ++ t_type(get_elem(atom, Es)), diff --git a/lib/edoc/src/edoc_parser.yrl b/lib/edoc/src/edoc_parser.yrl index 983e2f8496..68a3439f10 100644 --- a/lib/edoc/src/edoc_parser.yrl +++ b/lib/edoc/src/edoc_parser.yrl @@ -82,9 +82,6 @@ utype_map_field -> utype '=>' utype : #t_map_field{assoc_type = assoc, utype_map_field -> utype ':=' utype : #t_map_field{assoc_type = exact, k_type = '$1', v_type = '$3'}. -utype_map_field -> '...' : #t_map_field{assoc_type = assoc, - k_type = any(), - v_type = any()}. utype_tuple -> '{' utypes '}' : lists:reverse('$2'). @@ -354,9 +351,6 @@ all_vars([#t_var{} | As]) -> all_vars(As) -> As =:= []. -any() -> - #t_type{name = #t_name{name = any}, args = []}. - %% --------------------------------------------------------------------- %% @doc EDoc type specification parsing. Parses the content of diff --git a/lib/erl_docgen/src/docgen_otp_specs.erl b/lib/erl_docgen/src/docgen_otp_specs.erl index 5bc3be7a8d..6c41147e27 100644 --- a/lib/erl_docgen/src/docgen_otp_specs.erl +++ b/lib/erl_docgen/src/docgen_otp_specs.erl @@ -446,17 +446,11 @@ t_map(Es) -> t_map_field(#xmlElement{content = [K,V]}=E) -> KElem = t_utype_elem(K), VElem = t_utype_elem(V), - AT = get_attrval(assoc_type, E), - IsAny = fun(["any","()"]) -> true; (_) -> false end, - case AT =:= "assoc" andalso IsAny(KElem) andalso IsAny(VElem) of - true -> "..."; - false -> - AS = case AT of - "assoc" -> " => "; - "exact" -> " := " - end, - KElem ++ [AS] ++ VElem - end. + AS = case get_attrval(assoc_type, E) of + "assoc" -> " => "; + "exact" -> " := " + end, + KElem ++ [AS] ++ VElem. t_record(E, Es) -> Name = ["#"] ++ t_type(get_elem(atom, Es)), diff --git a/lib/hipe/cerl/erl_types.erl b/lib/hipe/cerl/erl_types.erl index c383541020..7826dada9d 100644 --- a/lib/hipe/cerl/erl_types.erl +++ b/lib/hipe/cerl/erl_types.erl @@ -80,11 +80,9 @@ t_float/0, t_var_names/1, t_form_to_string/1, - t_from_form/4, - t_from_form/5, + t_from_form/6, t_from_form_without_remote/3, - t_check_record_fields/4, - t_check_record_fields/5, + t_check_record_fields/6, t_from_range/2, t_from_range_unsafe/2, t_from_term/1, @@ -221,6 +219,7 @@ is_erl_type/1, atom_to_string/1, var_table__new/0, + cache__new/0, map_pairwise_merge/3 ]). @@ -237,7 +236,7 @@ -export([t_is_identifier/1]). -endif. --export_type([erl_type/0, opaques/0, type_table/0, var_table/0]). +-export_type([erl_type/0, opaques/0, type_table/0, var_table/0, cache/0]). %%-define(DEBUG, true). @@ -328,7 +327,7 @@ %% Auxiliary types and convenient macros %% --type parse_form() :: erl_parse:abstract_expr(). +-type parse_form() :: erl_parse:abstract_type(). -type rng_elem() :: 'pos_inf' | 'neg_inf' | integer(). -record(int_set, {set :: [integer()]}). @@ -375,9 +374,11 @@ -type opaques() :: [erl_type()] | 'universe'. -type record_key() :: {'record', atom()}. --type type_key() :: {'type' | 'opaque', atom(), arity()}. +-type type_key() :: {'type' | 'opaque', mfa()}. -type record_value() :: [{atom(), erl_parse:abstract_expr(), erl_type()}]. --type type_value() :: {module(), erl_type(), atom()}. +-type type_value() :: {{module(), {file:name(), erl_anno:line()}, + erl_parse:abstract_type(), ArgNames :: [atom()]}, + erl_type()}. -type type_table() :: dict:dict(record_key() | type_key(), record_value() | type_value()). @@ -757,8 +758,8 @@ t_opaque_from_records(RecDict) -> {{Module, _FileLine, _Form, ArgNames}, _Type}) -> %% Args = args_to_types(ArgNames), %% List = lists:zip(ArgNames, Args), - %% TmpVarDict = dict:from_list(List), - %% Rep = t_from_form(Type, RecDict, TmpVarDict), + %% TmpVarTab = maps:to_list(List), + %% Rep = t_from_form(Type, RecDict, TmpVarTab), Rep = t_any(), % not used for anything right now Args = [t_any() || _ <- ArgNames], t_opaque(Module, Name, Args, Rep) @@ -1769,7 +1770,8 @@ mapdict_insert(E={_,_,_}, T) -> [E|T]. t_map_mandatoriness(), erl_type()) -> t_map_pair() | false), erl_type(), erl_type()) -> t_map_dict(). -map_pairwise_merge(F, ?map(APairs, ADefK, ADefV), ?map(BPairs, BDefK, BDefV)) -> +map_pairwise_merge(F, ?map(APairs, ADefK, ADefV), + ?map(BPairs, BDefK, BDefV)) -> map_pairwise_merge(F, APairs, ADefK, ADefV, BPairs, BDefK, BDefV). map_pairwise_merge(_, [], _, _, [], _, _) -> []; @@ -4302,7 +4304,6 @@ t_to_string(?map(Pairs0,DefK,DefV), RecDict) -> {Pairs, ExtraEl} = case {DefK, DefV} of {?none, ?none} -> {Pairs0, []}; - {?any, ?any} -> {Pairs0, ["..."]}; _ -> {Pairs0 ++ [{DefK,?opt,DefV}], []} end, Tos = fun(T) -> case T of @@ -4414,33 +4415,30 @@ mod_name(Mod, Name) -> -type type_names() :: [type_key() | record_key()]. --type mta() :: {module(), atom(), arity()}. --type mra() :: {module(), atom(), arity()}. --type site() :: {'type', mta()} | {'spec', mfa()} | {'record', mra()}. +-type mta() :: {module(), atom(), arity()}. +-type mra() :: {module(), atom(), arity()}. +-type site() :: {'type', mta()} | {'spec', mfa()} | {'record', mra()}. +-type cache_key() :: {module(), atom(), expand_depth(), + [erl_type()], type_names()}. +-opaque cache() :: #{cache_key() => {erl_type(), expand_limit()}}. --spec t_from_form(parse_form(), sets:set(mfa()), - site(), mod_records()) -> erl_type(). +-spec t_from_form(parse_form(), sets:set(mfa()), site(), mod_records(), + var_table(), cache()) -> {erl_type(), cache()}. -t_from_form(Form, ExpTypes, Site, RecDict) -> - t_from_form(Form, ExpTypes, Site, RecDict, maps:new()). - --spec t_from_form(parse_form(), sets:set(mfa()), - site(), mod_records(), var_table()) -> erl_type(). - -t_from_form(Form, ExpTypes, Site, RecDict, VarDict) -> - {T, _} = t_from_form1(Form, ExpTypes, Site, RecDict, VarDict), - T. +t_from_form(Form, ExpTypes, Site, RecDict, VarTab, Cache) -> + t_from_form1(Form, ExpTypes, Site, RecDict, VarTab, Cache). %% Replace external types with with none(). -spec t_from_form_without_remote(parse_form(), site(), type_table()) -> - erl_type(). + {erl_type(), cache()}. t_from_form_without_remote(Form, Site, TypeTable) -> Module = site_module(Site), RecDict = dict:from_list([{Module, TypeTable}]), ExpTypes = replace_by_none, - {T, _} = t_from_form1(Form, ExpTypes, Site, RecDict, maps:new()), - T. + VarTab = var_table__new(), + Cache = cache__new(), + t_from_form1(Form, ExpTypes, Site, RecDict, VarTab, Cache). %% REC_TYPE_LIMIT is used for limiting the depth of recursive types. %% EXPAND_LIMIT is used for limiting the size of types by @@ -4453,34 +4451,53 @@ t_from_form_without_remote(Form, Site, TypeTable) -> -type expand_depth() :: integer(). +-record(from_form, {site :: site(), + xtypes :: sets:set(mfa()) | 'replace_by_none', + mrecs :: mod_records(), + vtab :: var_table(), + tnames :: type_names()}). + -spec t_from_form1(parse_form(), sets:set(mfa()) | 'replace_by_none', - site(), mod_records(), var_table()) -> - {erl_type(), expand_limit()}. + site(), mod_records(), var_table(), cache()) -> + {erl_type(), cache()}. -t_from_form1(Form, ET, Site, MR, V) -> +t_from_form1(Form, ET, Site, MR, V, C) -> TypeNames = initial_typenames(Site), - t_from_form1(Form, TypeNames, ET, Site, MR, V, ?EXPAND_DEPTH). + State = #from_form{site = Site, + xtypes = ET, + mrecs = MR, + vtab = V, + tnames = TypeNames}, + L = ?EXPAND_LIMIT, + {T1, L1, C1} = from_form(Form, State, ?EXPAND_DEPTH, L, C), + if + L1 =< 0 -> + from_form_loop(Form, State, 1, L, C1); + true -> + {T1, C1} + end. initial_typenames({type, _MTA}=Site) -> [Site]; initial_typenames({spec, _MFA}) -> []; initial_typenames({record, _MRA}) -> []. -t_from_form1(Form, TypeNames, ET, Site, MR, V, D) -> - L = ?EXPAND_LIMIT, - {T, L1} = t_from_form(Form, TypeNames, ET, Site, MR, V, D, L), +from_form_loop(Form, State, D, Limit, C) -> + {T1, L1, C1} = from_form(Form, State, D, Limit, C), + Delta = Limit - L1, if - L1 =< 0, D > 1 -> - D1 = D div 2, - t_from_form1(Form, TypeNames, ET, Site, MR, V, D1); + %% Save some time by assuming next depth will exceed the limit. + Delta * 8 > Limit -> + {T1, C1}; true -> - {T, L1} + D1 = D + 1, + from_form_loop(Form, State, D1, Limit, C1) end. --spec t_from_form(parse_form(), type_names(), - sets:set(mfa()) | 'replace_by_none', - site(), mod_records(), var_table(), - expand_depth(), expand_limit()) - -> {erl_type(), expand_limit()}. +-spec from_form(parse_form(), + #from_form{}, + expand_depth(), + expand_limit(), + cache()) -> {erl_type(), expand_limit(), cache()}. %% If there is something wrong with parse_form() %% throw({error, io_lib:chars()} is called; @@ -4490,330 +4507,336 @@ t_from_form1(Form, TypeNames, ET, Site, MR, V, D) -> %% %% It is assumed that site_module(S) can be found in MR. -t_from_form(_, _TypeNames, _ET, _S, _MR, _V, D, L) when D =< 0 ; L =< 0 -> - {t_any(), L}; -t_from_form({var, _L, '_'}, _TypeNames, _ET, _S, _MR, _V, _D, L) -> - {t_any(), L}; -t_from_form({var, _L, Name}, _TypeNames, _ET, _S, _MR, V, _D, L) -> +from_form(_, _S, D, L, C) when D =< 0 ; L =< 0 -> + {t_any(), L, C}; +from_form({var, _L, '_'}, _S, _D, L, C) -> + {t_any(), L, C}; +from_form({var, _L, Name}, S, _D, L, C) -> + V = S#from_form.vtab, case maps:find(Name, V) of - error -> {t_var(Name), L}; - {ok, Val} -> {Val, L} + error -> {t_var(Name), L, C}; + {ok, Val} -> {Val, L, C} end; -t_from_form({ann_type, _L, [_Var, Type]}, TypeNames, ET, S, MR, V, D, L) -> - t_from_form(Type, TypeNames, ET, S, MR, V, D, L); -t_from_form({paren_type, _L, [Type]}, TypeNames, ET, S, MR, V, D, L) -> - t_from_form(Type, TypeNames, ET, S, MR, V, D, L); -t_from_form({remote_type, _L, [{atom, _, Module}, {atom, _, Type}, Args]}, - TypeNames, ET, S, MR, V, D, L) -> - remote_from_form(Module, Type, Args, TypeNames, ET, S, MR, V, D, L); -t_from_form({atom, _L, Atom}, _TypeNames, _ET, _S, _MR, _V, _D, L) -> - {t_atom(Atom), L}; -t_from_form({integer, _L, Int}, _TypeNames, _ET, _S, _MR, _V, _D, L) -> - {t_integer(Int), L}; -t_from_form({op, _L, _Op, _Arg} = Op, _TypeNames, _ET, _S, _MR, _V, _D, L) -> +from_form({ann_type, _L, [_Var, Type]}, S, D, L, C) -> + from_form(Type, S, D, L, C); +from_form({paren_type, _L, [Type]}, S, D, L, C) -> + from_form(Type, S, D, L, C); +from_form({remote_type, _L, [{atom, _, Module}, {atom, _, Type}, Args]}, + S, D, L, C) -> + remote_from_form(Module, Type, Args, S, D, L, C); +from_form({atom, _L, Atom}, _S, _D, L, C) -> + {t_atom(Atom), L, C}; +from_form({integer, _L, Int}, _S, _D, L, C) -> + {t_integer(Int), L, C}; +from_form({op, _L, _Op, _Arg} = Op, _S, _D, L, C) -> case erl_eval:partial_eval(Op) of {integer, _, Val} -> - {t_integer(Val), L}; + {t_integer(Val), L, C}; _ -> throw({error, io_lib:format("Unable to evaluate type ~w\n", [Op])}) end; -t_from_form({op, _L, _Op, _Arg1, _Arg2} = Op, _TypeNames, - _ET, _S, _MR, _V, _D, L) -> +from_form({op, _L, _Op, _Arg1, _Arg2} = Op, _S, _D, L, C) -> case erl_eval:partial_eval(Op) of {integer, _, Val} -> - {t_integer(Val), L}; + {t_integer(Val), L, C}; _ -> throw({error, io_lib:format("Unable to evaluate type ~w\n", [Op])}) end; -t_from_form({type, _L, any, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) -> - {t_any(), L}; -t_from_form({type, _L, arity, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) -> - {t_arity(), L}; -t_from_form({type, _L, atom, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) -> - {t_atom(), L}; -t_from_form({type, _L, binary, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) -> - {t_binary(), L}; -t_from_form({type, _L, binary, [Base, Unit]} = Type, - _TypeNames, _ET, _S, _MR, _V, _D, L) -> +from_form({type, _L, any, []}, _S, _D, L, C) -> + {t_any(), L, C}; +from_form({type, _L, arity, []}, _S, _D, L, C) -> + {t_arity(), L, C}; +from_form({type, _L, atom, []}, _S, _D, L, C) -> + {t_atom(), L, C}; +from_form({type, _L, binary, []}, _S, _D, L, C) -> + {t_binary(), L, C}; +from_form({type, _L, binary, [Base, Unit]} = Type, _S, _D, L, C) -> case {erl_eval:partial_eval(Base), erl_eval:partial_eval(Unit)} of {{integer, _, B}, {integer, _, U}} when B >= 0, U >= 0 -> - {t_bitstr(U, B), L}; + {t_bitstr(U, B), L, C}; _ -> throw({error, io_lib:format("Unable to evaluate type ~w\n", [Type])}) end; -t_from_form({type, _L, bitstring, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) -> - {t_bitstr(), L}; -t_from_form({type, _L, bool, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) -> - {t_boolean(), L}; % XXX: Temporarily -t_from_form({type, _L, boolean, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) -> - {t_boolean(), L}; -t_from_form({type, _L, byte, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) -> - {t_byte(), L}; -t_from_form({type, _L, char, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) -> - {t_char(), L}; -t_from_form({type, _L, float, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) -> - {t_float(), L}; -t_from_form({type, _L, function, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) -> - {t_fun(), L}; -t_from_form({type, _L, 'fun', []}, _TypeNames, _ET, _S, _MR, _V, _D, L) -> - {t_fun(), L}; -t_from_form({type, _L, 'fun', [{type, _, any}, Range]}, TypeNames, - ET, S, MR, V, D, L) -> - {T, L1} = t_from_form(Range, TypeNames, ET, S, MR, V, D - 1, L - 1), - {t_fun(T), L1}; -t_from_form({type, _L, 'fun', [{type, _, product, Domain}, Range]}, - TypeNames, ET, S, MR, V, D, L) -> - {Dom1, L1} = list_from_form(Domain, TypeNames, ET, S, MR, V, D, L), - {Ran1, L2} = t_from_form(Range, TypeNames, ET, S, MR, V, D, L1), - {t_fun(Dom1, Ran1), L2}; -t_from_form({type, _L, identifier, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) -> - {t_identifier(), L}; -t_from_form({type, _L, integer, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) -> - {t_integer(), L}; -t_from_form({type, _L, iodata, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) -> - {t_iodata(), L}; -t_from_form({type, _L, iolist, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) -> - {t_iolist(), L}; -t_from_form({type, _L, list, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) -> - {t_list(), L}; -t_from_form({type, _L, list, [Type]}, TypeNames, ET, S, MR, V, D, L) -> - {T, L1} = t_from_form(Type, TypeNames, ET, S, MR, V, D - 1, L - 1), - {t_list(T), L1}; -t_from_form({type, _L, map, any}, TypeNames, ET, S, MR, V, D, L) -> - builtin_type(map, t_map(), TypeNames, ET, S, MR, V, D, L); -t_from_form({type, _L, map, List}, TypeNames, ET, S, MR, V, D, L) -> - {Pairs1, L5} = - fun PairsFromForm(_, L1) when L1 =< 0 -> {[{?any,?opt,?any}], L1}; - PairsFromForm([], L1) -> {[], L1}; - PairsFromForm([{type, _, Oper, [KF, VF]}|T], L1) -> - {Key, L2} = t_from_form(KF, TypeNames, ET, S, MR, V, D - 1, L1), - {Val, L3} = t_from_form(VF, TypeNames, ET, S, MR, V, D - 1, L2), - {Pairs0, L4} = PairsFromForm(T, L3 - 1), +from_form({type, _L, bitstring, []}, _S, _D, L, C) -> + {t_bitstr(), L, C}; +from_form({type, _L, bool, []}, _S, _D, L, C) -> + {t_boolean(), L, C}; % XXX: Temporarily +from_form({type, _L, boolean, []}, _S, _D, L, C) -> + {t_boolean(), L, C}; +from_form({type, _L, byte, []}, _S, _D, L, C) -> + {t_byte(), L, C}; +from_form({type, _L, char, []}, _S, _D, L, C) -> + {t_char(), L, C}; +from_form({type, _L, float, []}, _S, _D, L, C) -> + {t_float(), L, C}; +from_form({type, _L, function, []}, _S, _D, L, C) -> + {t_fun(), L, C}; +from_form({type, _L, 'fun', []}, _S, _D, L, C) -> + {t_fun(), L, C}; +from_form({type, _L, 'fun', [{type, _, any}, Range]}, S, D, L, C) -> + {T, L1, C1} = from_form(Range, S, D - 1, L - 1, C), + {t_fun(T), L1, C1}; +from_form({type, _L, 'fun', [{type, _, product, Domain}, Range]}, + S, D, L, C) -> + {Dom1, L1, C1} = list_from_form(Domain, S, D, L, C), + {Ran1, L2, C2} = from_form(Range, S, D, L1, C1), + {t_fun(Dom1, Ran1), L2, C2}; +from_form({type, _L, identifier, []}, _S, _D, L, C) -> + {t_identifier(), L, C}; +from_form({type, _L, integer, []}, _S, _D, L, C) -> + {t_integer(), L, C}; +from_form({type, _L, iodata, []}, _S, _D, L, C) -> + {t_iodata(), L, C}; +from_form({type, _L, iolist, []}, _S, _D, L, C) -> + {t_iolist(), L, C}; +from_form({type, _L, list, []}, _S, _D, L, C) -> + {t_list(), L, C}; +from_form({type, _L, list, [Type]}, S, D, L, C) -> + {T, L1, C1} = from_form(Type, S, D - 1, L - 1, C), + {t_list(T), L1, C1}; +from_form({type, _L, map, any}, S, D, L, C) -> + builtin_type(map, t_map(), S, D, L, C); +from_form({type, _L, map, List}, S, D0, L, C) -> + {Pairs1, L5, C5} = + fun PairsFromForm(_, L1, C1) when L1 =< 0 -> {[{?any,?opt,?any}], L1, C1}; + PairsFromForm([], L1, C1) -> {[], L1, C1}; + PairsFromForm([{type, _, Oper, [KF, VF]}|T], L1, C1) -> + D = D0 - 1, + {Key, L2, C2} = from_form(KF, S, D, L1, C1), + {Val, L3, C3} = from_form(VF, S, D, L2, C2), + {Pairs0, L4, C4} = PairsFromForm(T, L3 - 1, C3), case Oper of - map_field_assoc -> {[{Key,?opt, Val}|Pairs0], L4}; - map_field_exact -> {[{Key,?mand,Val}|Pairs0], L4} + map_field_assoc -> {[{Key,?opt, Val}|Pairs0], L4, C4}; + map_field_exact -> {[{Key,?mand,Val}|Pairs0], L4, C4} end - end(List, L), + end(List, L, C), try {Pairs, DefK, DefV} = map_from_form(Pairs1, [], [], [], ?none, ?none), - {t_map(Pairs, DefK, DefV), L5} - catch none -> {t_none(), L5} + {t_map(Pairs, DefK, DefV), L5, C5} + catch none -> {t_none(), L5, C5} end; -t_from_form({type, _L, mfa, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) -> - {t_mfa(), L}; -t_from_form({type, _L, module, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) -> - {t_module(), L}; -t_from_form({type, _L, nil, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) -> - {t_nil(), L}; -t_from_form({type, _L, neg_integer, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) -> - {t_neg_integer(), L}; -t_from_form({type, _L, non_neg_integer, []}, _TypeNames, _ET, _S, _MR, - _V, _D, L) -> - {t_non_neg_integer(), L}; -t_from_form({type, _L, no_return, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) -> - {t_unit(), L}; -t_from_form({type, _L, node, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) -> - {t_node(), L}; -t_from_form({type, _L, none, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) -> - {t_none(), L}; -t_from_form({type, _L, nonempty_list, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) -> - {t_nonempty_list(), L}; -t_from_form({type, _L, nonempty_list, [Type]}, TypeNames, ET, S, MR, V, D, L) -> - {T, L1} = t_from_form(Type, TypeNames, ET, S, MR, V, D, L - 1), - {t_nonempty_list(T), L1}; -t_from_form({type, _L, nonempty_improper_list, [Cont, Term]}, TypeNames, - ET, S, MR, V, D, L) -> - {T1, L1} = t_from_form(Cont, TypeNames, ET, S, MR, V, D, L - 1), - {T2, L2} = t_from_form(Term, TypeNames, ET, S, MR, V, D, L1), - {t_cons(T1, T2), L2}; -t_from_form({type, _L, nonempty_maybe_improper_list, []}, _TypeNames, - _ET, _S, _MR, _V, _D, L) -> - {t_cons(?any, ?any), L}; -t_from_form({type, _L, nonempty_maybe_improper_list, [Cont, Term]}, - TypeNames, ET, S, MR, V, D, L) -> - {T1, L1} = t_from_form(Cont, TypeNames, ET, S, MR, V, D, L - 1), - {T2, L2} = t_from_form(Term, TypeNames, ET, S, MR, V, D, L1), - {t_cons(T1, T2), L2}; -t_from_form({type, _L, nonempty_string, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) -> - {t_nonempty_string(), L}; -t_from_form({type, _L, number, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) -> - {t_number(), L}; -t_from_form({type, _L, pid, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) -> - {t_pid(), L}; -t_from_form({type, _L, port, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) -> - {t_port(), L}; -t_from_form({type, _L, pos_integer, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) -> - {t_pos_integer(), L}; -t_from_form({type, _L, maybe_improper_list, []}, _TypeNames, - _ET, _S, _MR, _V, _D, L) -> - {t_maybe_improper_list(), L}; -t_from_form({type, _L, maybe_improper_list, [Content, Termination]}, - TypeNames, ET, S, MR, V, D, L) -> - {T1, L1} = t_from_form(Content, TypeNames, ET, S, MR, V, D, L - 1), - {T2, L2} = t_from_form(Termination, TypeNames, ET, S, MR, V, D, L1), - {t_maybe_improper_list(T1, T2), L2}; -t_from_form({type, _L, product, Elements}, TypeNames, ET, S, MR, V, D, L) -> - {Lst, L1} = list_from_form(Elements, TypeNames, ET, S, MR, V, D - 1, L), - {t_product(Lst), L1}; -t_from_form({type, _L, range, [From, To]} = Type, - _TypeNames, _ET, _S, _MR, _V, _D, L) -> +from_form({type, _L, mfa, []}, _S, _D, L, C) -> + {t_mfa(), L, C}; +from_form({type, _L, module, []}, _S, _D, L, C) -> + {t_module(), L, C}; +from_form({type, _L, nil, []}, _S, _D, L, C) -> + {t_nil(), L, C}; +from_form({type, _L, neg_integer, []}, _S, _D, L, C) -> + {t_neg_integer(), L, C}; +from_form({type, _L, non_neg_integer, []}, _S, _D, L, C) -> + {t_non_neg_integer(), L, C}; +from_form({type, _L, no_return, []}, _S, _D, L, C) -> + {t_unit(), L, C}; +from_form({type, _L, node, []}, _S, _D, L, C) -> + {t_node(), L, C}; +from_form({type, _L, none, []}, _S, _D, L, C) -> + {t_none(), L, C}; +from_form({type, _L, nonempty_list, []}, _S, _D, L, C) -> + {t_nonempty_list(), L, C}; +from_form({type, _L, nonempty_list, [Type]}, S, D, L, C) -> + {T, L1, C1} = from_form(Type, S, D, L - 1, C), + {t_nonempty_list(T), L1, C1}; +from_form({type, _L, nonempty_improper_list, [Cont, Term]}, S, D, L, C) -> + {T1, L1, C1} = from_form(Cont, S, D, L - 1, C), + {T2, L2, C2} = from_form(Term, S, D, L1, C1), + {t_cons(T1, T2), L2, C2}; +from_form({type, _L, nonempty_maybe_improper_list, []}, _S, _D, L, C) -> + {t_cons(?any, ?any), L, C}; +from_form({type, _L, nonempty_maybe_improper_list, [Cont, Term]}, + S, D, L, C) -> + {T1, L1, C1} = from_form(Cont, S, D, L - 1, C), + {T2, L2, C2} = from_form(Term, S, D, L1, C1), + {t_cons(T1, T2), L2, C2}; +from_form({type, _L, nonempty_string, []}, _S, _D, L, C) -> + {t_nonempty_string(), L, C}; +from_form({type, _L, number, []}, _S, _D, L, C) -> + {t_number(), L, C}; +from_form({type, _L, pid, []}, _S, _D, L, C) -> + {t_pid(), L, C}; +from_form({type, _L, port, []}, _S, _D, L, C) -> + {t_port(), L, C}; +from_form({type, _L, pos_integer, []}, _S, _D, L, C) -> + {t_pos_integer(), L, C}; +from_form({type, _L, maybe_improper_list, []}, _S, _D, L, C) -> + {t_maybe_improper_list(), L, C}; +from_form({type, _L, maybe_improper_list, [Content, Termination]}, + S, D, L, C) -> + {T1, L1, C1} = from_form(Content, S, D, L - 1, C), + {T2, L2, C2} = from_form(Termination, S, D, L1, C1), + {t_maybe_improper_list(T1, T2), L2, C2}; +from_form({type, _L, product, Elements}, S, D, L, C) -> + {Lst, L1, C1} = list_from_form(Elements, S, D - 1, L, C), + {t_product(Lst), L1, C1}; +from_form({type, _L, range, [From, To]} = Type, _S, _D, L, C) -> case {erl_eval:partial_eval(From), erl_eval:partial_eval(To)} of {{integer, _, FromVal}, {integer, _, ToVal}} -> - {t_from_range(FromVal, ToVal), L}; + {t_from_range(FromVal, ToVal), L, C}; _ -> throw({error, io_lib:format("Unable to evaluate type ~w\n", [Type])}) end; -t_from_form({type, _L, record, [Name|Fields]}, TypeNames, ET, S, MR, V, D, L) -> - record_from_form(Name, Fields, TypeNames, ET, S, MR, V, D, L); -t_from_form({type, _L, reference, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) -> - {t_reference(), L}; -t_from_form({type, _L, string, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) -> - {t_string(), L}; -t_from_form({type, _L, term, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) -> - {t_any(), L}; -t_from_form({type, _L, timeout, []}, _TypeNames, _ET, _S, _MR, _V, _D, L) -> - {t_timeout(), L}; -t_from_form({type, _L, tuple, any}, _TypeNames, _ET, _S, _MR, _V, _D, L) -> - {t_tuple(), L}; -t_from_form({type, _L, tuple, Args}, TypeNames, ET, S, MR, V, D, L) -> - {Lst, L1} = list_from_form(Args, TypeNames, ET, S, MR, V, D - 1, L), - {t_tuple(Lst), L1}; -t_from_form({type, _L, union, Args}, TypeNames, ET, S, MR, V, D, L) -> - {Lst, L1} = list_from_form(Args, TypeNames, ET, S, MR, V, D, L), - {t_sup(Lst), L1}; -t_from_form({user_type, _L, Name, Args}, TypeNames, ET, S, MR, V, D, L) -> - type_from_form(Name, Args, TypeNames, ET, S, MR, V, D, L); -t_from_form({type, _L, Name, Args}, TypeNames, ET, S, MR, V, D, L) -> +from_form({type, _L, record, [Name|Fields]}, S, D, L, C) -> + record_from_form(Name, Fields, S, D, L, C); +from_form({type, _L, reference, []}, _S, _D, L, C) -> + {t_reference(), L, C}; +from_form({type, _L, string, []}, _S, _D, L, C) -> + {t_string(), L, C}; +from_form({type, _L, term, []}, _S, _D, L, C) -> + {t_any(), L, C}; +from_form({type, _L, timeout, []}, _S, _D, L, C) -> + {t_timeout(), L, C}; +from_form({type, _L, tuple, any}, _S, _D, L, C) -> + {t_tuple(), L, C}; +from_form({type, _L, tuple, Args}, S, D, L, C) -> + {Lst, L1, C1} = list_from_form(Args, S, D - 1, L, C), + {t_tuple(Lst), L1, C1}; +from_form({type, _L, union, Args}, S, D, L, C) -> + {Lst, L1, C1} = list_from_form(Args, S, D, L, C), + {t_sup(Lst), L1, C1}; +from_form({user_type, _L, Name, Args}, S, D, L, C) -> + type_from_form(Name, Args, S, D, L, C); +from_form({type, _L, Name, Args}, S, D, L, C) -> %% Compatibility: modules compiled before Erlang/OTP 18.0. - type_from_form(Name, Args, TypeNames, ET, S, MR, V, D, L); -t_from_form({opaque, _L, Name, {Mod, Args, Rep}}, _TypeNames, - _ET, _S, _MR, _V, _D, L) -> + type_from_form(Name, Args, S, D, L, C); +from_form({opaque, _L, Name, {Mod, Args, Rep}}, _S, _D, L, C) -> %% XXX. To be removed. - {t_opaque(Mod, Name, Args, Rep), L}. + {t_opaque(Mod, Name, Args, Rep), L, C}. -builtin_type(Name, Type, TypeNames, ET, Site, MR, V, D, L) -> +builtin_type(Name, Type, S, D, L, C) -> + #from_form{site = Site, mrecs = MR} = S, M = site_module(Site), case dict:find(M, MR) of {ok, R} -> case lookup_type(Name, 0, R) of {_, {{_M, _FL, _F, _A}, _T}} -> - type_from_form(Name, [], TypeNames, ET, Site, MR, V, D, L); + type_from_form(Name, [], S, D, L, C); error -> - {Type, L} + {Type, L, C} end; error -> - {Type, L} + {Type, L, C} end. -type_from_form(Name, Args, TypeNames, ET, Site0, MR, V, D, L) -> +type_from_form(Name, Args, S, D, L, C) -> + #from_form{site = Site, mrecs = MR, tnames = TypeNames} = S, ArgsLen = length(Args), - Module = site_module(Site0), - {ok, R} = dict:find(Module, MR), + Module = site_module(Site), TypeName = {type, {Module, Name, ArgsLen}}, + case can_unfold_more(TypeName, TypeNames) of + true -> + {ok, R} = dict:find(Module, MR), + type_from_form1(Name, Args, ArgsLen, R, TypeName, TypeNames, + S, D, L, C); + false -> + {t_any(), L, C} + end. + +type_from_form1(Name, Args, ArgsLen, R, TypeName, TypeNames, S, D, L, C) -> case lookup_type(Name, ArgsLen, R) of - {type, {{Module, _FileName, Form, ArgNames}, _Type}} -> - case can_unfold_more(TypeName, TypeNames) of - true -> - NewTypeNames = [TypeName|TypeNames], - {ArgTypes, L1} = - list_from_form(Args, TypeNames, ET, Site0, MR, V, D, L), - List = lists:zip(ArgNames, ArgTypes), - TmpV = maps:from_list(List), - Site = TypeName, - t_from_form(Form, NewTypeNames, ET, Site, MR, TmpV, D, L1); - false -> - {t_any(), L} - end; - {opaque, {{Module, _FileName, Form, ArgNames}, Type}} -> - case can_unfold_more(TypeName, TypeNames) of - true -> - NewTypeNames = [TypeName|TypeNames], - {ArgTypes, L1} = - list_from_form(Args, NewTypeNames, ET, Site0, MR, V, D, L), + {Tag, {{Module, _FileName, Form, ArgNames}, Type}} -> + NewTypeNames = [TypeName|TypeNames], + S1 = S#from_form{tnames = NewTypeNames}, + {ArgTypes, L1, C1} = list_from_form(Args, S1, D, L, C), + CKey = cache_key(Module, Name, ArgTypes, TypeNames, D), + case cache_find(CKey, C) of + {CachedType, DeltaL} -> + {CachedType, L1 - DeltaL, C}; + error -> List = lists:zip(ArgNames, ArgTypes), TmpV = maps:from_list(List), - Site = TypeName, - {Rep, L2} = - t_from_form(Form, NewTypeNames, ET, Site, MR, TmpV, D, L1), - Rep1 = choose_opaque_type(Rep, Type), - Rep2 = case cannot_have_opaque(Rep1, TypeName, TypeNames) of - true -> Rep1; - false -> - ArgTypes2 = subst_all_vars_to_any_list(ArgTypes), - t_opaque(Module, Name, ArgTypes2, Rep1) - end, - {Rep2, L2}; - false -> {t_any(), L} + S2 = S1#from_form{site = TypeName, vtab = TmpV}, + Fun = fun(DD, LL) -> from_form(Form, S2, DD, LL, C1) end, + {NewType, L3, C3} = + case Tag of + type -> + recur_limit(Fun, D, L1, TypeName, TypeNames); + opaque -> + {Rep, L2, C2} = recur_limit(Fun, D, L1, TypeName, TypeNames), + Rep1 = choose_opaque_type(Rep, Type), + Rep2 = case cannot_have_opaque(Rep1, TypeName, TypeNames) of + true -> Rep1; + false -> + ArgTypes2 = subst_all_vars_to_any_list(ArgTypes), + t_opaque(Module, Name, ArgTypes2, Rep1) + end, + {Rep2, L2, C2} + end, + C4 = cache_put(CKey, NewType, L1 - L3, C3), + {NewType, L3, C4} end; error -> - Msg = io_lib:format("Unable to find type ~w/~w\n", [Name, ArgsLen]), + Msg = io_lib:format("Unable to find type ~w/~w\n", + [Name, ArgsLen]), throw({error, Msg}) end. -remote_from_form(RemMod, Name, Args, TypeNames, ET, S, MR, V, D, L) -> +remote_from_form(RemMod, Name, Args, S, D, L, C) -> + #from_form{xtypes = ET, mrecs = MR, tnames = TypeNames} = S, if ET =:= replace_by_none -> - {t_none(), L}; + {t_none(), L, C}; true -> ArgsLen = length(Args), MFA = {RemMod, Name, ArgsLen}, case dict:find(RemMod, MR) of error -> self() ! {self(), ext_types, MFA}, - {t_any(), L}; + {t_any(), L, C}; {ok, RemDict} -> - RemType = {type, MFA}, case sets:is_element(MFA, ET) of true -> - case lookup_type(Name, ArgsLen, RemDict) of - {type, {{_Mod, _FileLine, Form, ArgNames}, _Type}} -> - case can_unfold_more(RemType, TypeNames) of - true -> - NewTypeNames = [RemType|TypeNames], - {ArgTypes, L1} = list_from_form(Args, TypeNames, - ET, S, MR, V, D, L), - List = lists:zip(ArgNames, ArgTypes), - TmpVarDict = maps:from_list(List), - Site = RemType, - t_from_form(Form, NewTypeNames, ET, - Site, MR, TmpVarDict, D, L1); - false -> - {t_any(), L} - end; - {opaque, {{Mod, _FileLine, Form, ArgNames}, Type}} -> - case can_unfold_more(RemType, TypeNames) of - true -> - NewTypeNames = [RemType|TypeNames], - {ArgTypes, L1} = list_from_form(Args, NewTypeNames, - ET, S, MR, V, D, L), - List = lists:zip(ArgNames, ArgTypes), - TmpVarDict = maps:from_list(List), - Site = RemType, - {NewRep, L2} = - t_from_form(Form, NewTypeNames, ET, Site, MR, - TmpVarDict, D, L1), - NewRep1 = choose_opaque_type(NewRep, Type), - NewRep2 = - case - cannot_have_opaque(NewRep1, RemType, TypeNames) - of - true -> NewRep1; - false -> - ArgTypes2 = subst_all_vars_to_any_list(ArgTypes), - t_opaque(Mod, Name, ArgTypes2, NewRep1) - end, - {NewRep2, L2}; - false -> - {t_any(), L} - end; - error -> - Msg = io_lib:format("Unable to find remote type ~w:~w()\n", - [RemMod, Name]), - throw({error, Msg}) + RemType = {type, MFA}, + case can_unfold_more(RemType, TypeNames) of + true -> + remote_from_form1(RemMod, Name, Args, ArgsLen, RemDict, + RemType, TypeNames, S, D, L, C); + false -> + {t_any(), L, C} end; false -> self() ! {self(), ext_types, {RemMod, Name, ArgsLen}}, - {t_any(), L} + {t_any(), L, C} end end end. +remote_from_form1(RemMod, Name, Args, ArgsLen, RemDict, RemType, TypeNames, + S, D, L, C) -> + case lookup_type(Name, ArgsLen, RemDict) of + {Tag, {{Mod, _FileLine, Form, ArgNames}, Type}} -> + NewTypeNames = [RemType|TypeNames], + S1 = S#from_form{tnames = NewTypeNames}, + {ArgTypes, L1, C1} = list_from_form(Args, S1, D, L, C), + CKey = cache_key(RemMod, Name, ArgTypes, TypeNames, D), + %% case error of + case cache_find(CKey, C) of + {CachedType, DeltaL} -> + {CachedType, L - DeltaL, C}; + error -> + List = lists:zip(ArgNames, ArgTypes), + TmpVarTab = maps:from_list(List), + S2 = S1#from_form{site = RemType, vtab = TmpVarTab}, + Fun = fun(DD, LL) -> from_form(Form, S2, DD, LL, C1) end, + {NewType, L3, C3} = + case Tag of + type -> + recur_limit(Fun, D, L1, RemType, TypeNames); + opaque -> + {NewRep, L2, C2} = recur_limit(Fun, D, L1, RemType, TypeNames), + NewRep1 = choose_opaque_type(NewRep, Type), + NewRep2 = + case cannot_have_opaque(NewRep1, RemType, TypeNames) of + true -> NewRep1; + false -> + ArgTypes2 = subst_all_vars_to_any_list(ArgTypes), + t_opaque(Mod, Name, ArgTypes2, NewRep1) + end, + {NewRep2, L2, C2} + end, + C4 = cache_put(CKey, NewType, L1 - L3, C3), + {NewType, L3, C4} + end; + error -> + Msg = io_lib:format("Unable to find remote type ~w:~w()\n", + [RemMod, Name]), + throw({error, Msg}) + end. + subst_all_vars_to_any_list(Types) -> [subst_all_vars_to_any(Type) || Type <- Types]. @@ -4836,63 +4859,67 @@ choose_opaque_type(Type, DeclType) -> false -> DeclType end. -record_from_form({atom, _, Name}, ModFields, TypeNames, ET, S, MR, V, D, L) -> - case can_unfold_more({record, Name}, TypeNames) of +record_from_form({atom, _, Name}, ModFields, S, D0, L0, C) -> + #from_form{site = Site, mrecs = MR, tnames = TypeNames} = S, + RecordType = {record, Name}, + case can_unfold_more(RecordType, TypeNames) of true -> - M = site_module(S), + M = site_module(Site), {ok, R} = dict:find(M, MR), case lookup_record(Name, R) of {ok, DeclFields} -> - NewTypeNames = [{record, Name}|TypeNames], - S1 = {record, {M, Name, length(DeclFields)}}, - {GetModRec, L1} = get_mod_record(ModFields, DeclFields, - NewTypeNames, ET, S1, MR, V, D, L), - case GetModRec of - {error, FieldName} -> - throw({error, io_lib:format("Illegal declaration of #~w{~w}\n", - [Name, FieldName])}); - {ok, NewFields} -> - {NewFields1, L2} = - fields_from_form(NewFields, NewTypeNames, ET, S1, MR, - maps:new(), D, L1), - Rec = t_tuple( - [t_atom(Name)|[Type - || {_FieldName, Type} <- NewFields1]]), - {Rec, L2} - end; + NewTypeNames = [RecordType|TypeNames], + Site1 = {record, {M, Name, length(DeclFields)}}, + S1 = S#from_form{site = Site1, tnames = NewTypeNames}, + Fun = fun(D, L) -> + {GetModRec, L1, C1} = + get_mod_record(ModFields, DeclFields, S1, D, L, C), + case GetModRec of + {error, FieldName} -> + throw({error, + io_lib:format("Illegal declaration of #~w{~w}\n", + [Name, FieldName])}); + {ok, NewFields} -> + S2 = S1#from_form{vtab = var_table__new()}, + {NewFields1, L2, C2} = + fields_from_form(NewFields, S2, D, L1, C1), + Rec = t_tuple( + [t_atom(Name)|[Type + || {_FieldName, Type} <- NewFields1]]), + {Rec, L2, C2} + end + end, + recur_limit(Fun, D0, L0, RecordType, TypeNames); error -> throw({error, io_lib:format("Unknown record #~w{}\n", [Name])}) end; false -> - {t_any(), L} + {t_any(), L0, C} end. -get_mod_record([], DeclFields, _TypeNames, _ET, _S, _MR, _V, _D, L) -> - {{ok, DeclFields}, L}; -get_mod_record(ModFields, DeclFields, TypeNames, ET, S, MR, V, D, L) -> +get_mod_record([], DeclFields, _S, _D, L, C) -> + {{ok, DeclFields}, L, C}; +get_mod_record(ModFields, DeclFields, S, D, L, C) -> DeclFieldsDict = lists:keysort(1, DeclFields), - {ModFieldsDict, L1} = - build_field_dict(ModFields, TypeNames, ET, S, MR, V, D, L), + {ModFieldsDict, L1, C1} = build_field_dict(ModFields, S, D, L, C), case get_mod_record_types(DeclFieldsDict, ModFieldsDict, []) of - {error, _FieldName} = Error -> {Error, L1}; + {error, _FieldName} = Error -> {Error, L1, C1}; {ok, FinalKeyDict} -> Fields = [lists:keyfind(FieldName, 1, FinalKeyDict) || {FieldName, _, _} <- DeclFields], - {{ok, Fields}, L1} + {{ok, Fields}, L1, C1} end. -build_field_dict(FieldTypes, TypeNames, ET, S, MR, V, D, L) -> - build_field_dict(FieldTypes, TypeNames, ET, S, MR, V, D, L, []). +build_field_dict(FieldTypes, S, D, L, C) -> + build_field_dict(FieldTypes, S, D, L, C, []). build_field_dict([{type, _, field_type, [{atom, _, Name}, Type]}|Left], - TypeNames, ET, S, MR, V, D, L, Acc) -> - {T, L1} = t_from_form(Type, TypeNames, ET, S, MR, V, D, L - 1), + S, D, L, C, Acc) -> + {T, L1, C1} = from_form(Type, S, D, L - 1, C), NewAcc = [{Name, Type, T}|Acc], - {Dict, L2} = - build_field_dict(Left, TypeNames, ET, S, MR, V, D, L1, NewAcc), - {Dict, L2}; -build_field_dict([], _TypeNames, _ET, _S, _MR, _V, _D, L, Acc) -> - {lists:keysort(1, Acc), L}. + build_field_dict(Left, S, D, L1, C1, NewAcc); +build_field_dict([], _S, _D, L, C, Acc) -> + {lists:keysort(1, Acc), L, C}. get_mod_record_types([{FieldName, _Abstr, _DeclType}|Left1], [{FieldName, TypeForm, ModType}|Left2], @@ -4910,20 +4937,19 @@ get_mod_record_types(_, [{FieldName2, _FormType, _ModType}|_], _Acc) -> %% It is important to create a limited version of the record type %% since nested record types can otherwise easily result in huge %% terms. -fields_from_form([], _TypeNames, _ET, _S, _MR, _V, _D, L) -> - {[], L}; -fields_from_form([{Name, Abstr, _Type}|Tail], TypeNames, ET, S, MR, - V, D, L) -> - {T, L1} = t_from_form(Abstr, TypeNames, ET, S, MR, V, D, L), - {F, L2} = fields_from_form(Tail, TypeNames, ET, S, MR, V, D, L1), - {[{Name, T}|F], L2}. - -list_from_form([], _TypeNames, _ET, _S, _MR, _V, _D, L) -> - {[], L}; -list_from_form([H|Tail], TypeNames, ET, S, MR, V, D, L) -> - {H1, L1} = t_from_form(H, TypeNames, ET, S, MR, V, D, L - 1), - {T1, L2} = list_from_form(Tail, TypeNames, ET, S, MR, V, D, L1), - {[H1|T1], L2}. +fields_from_form([], _S, _D, L, C) -> + {[], L, C}; +fields_from_form([{Name, Abstr, _Type}|Tail], S, D, L, C) -> + {T, L1, C1} = from_form(Abstr, S, D, L, C), + {F, L2, C2} = fields_from_form(Tail, S, D, L1, C1), + {[{Name, T}|F], L2, C2}. + +list_from_form([], _S, _D, L, C) -> + {[], L, C}; +list_from_form([H|Tail], S, D, L, C) -> + {H1, L1, C1} = from_form(H, S, D, L - 1, C), + {T1, L2, C2} = list_from_form(Tail, S, D, L1, C1), + {[H1|T1], L2, C2}. %% Sorts, combines non-singleton pairs, and applies precendence and %% mandatoriness rules. @@ -4969,80 +4995,140 @@ promote_to_mand(MKs, [E={K,_,V}|T]) -> false -> E end|promote_to_mand(MKs, T)]. --spec t_check_record_fields(parse_form(), sets:set(mfa()), site(), - mod_records()) -> ok. +-define(RECUR_EXPAND_LIMIT, 10). +-define(RECUR_EXPAND_DEPTH, 2). -t_check_record_fields(Form, ExpTypes, Site, RecDict) -> - t_check_record_fields(Form, ExpTypes, Site, RecDict, maps:new()). +%% If more of the limited resources is spent on the non-recursive +%% forms, more warnings are found. And the analysis is also a bit +%% faster. +%% +%% Setting REC_TYPE_LIMIT to 1 would work also work well. + +recur_limit(Fun, D, L, _, _) when L =< ?RECUR_EXPAND_DEPTH, + D =< ?RECUR_EXPAND_LIMIT -> + Fun(D, L); +recur_limit(Fun, D, L, TypeName, TypeNames) -> + case is_recursive(TypeName, TypeNames) of + true -> + {T, L1, C1} = Fun(?RECUR_EXPAND_DEPTH, ?RECUR_EXPAND_LIMIT), + {T, L - L1, C1}; + false -> + Fun(D, L) + end. -spec t_check_record_fields(parse_form(), sets:set(mfa()), site(), - mod_records(), var_table()) -> ok. + mod_records(), var_table(), cache()) -> cache(). + +t_check_record_fields(Form, ExpTypes, Site, RecDict, VarTable, Cache) -> + State = #from_form{site = Site, + xtypes = ExpTypes, + mrecs = RecDict, + vtab = VarTable, + tnames = []}, + check_record_fields(Form, State, Cache). + +-spec check_record_fields(parse_form(), #from_form{}, cache()) -> cache(). %% If there is something wrong with parse_form() %% throw({error, io_lib:chars()} is called. -t_check_record_fields({var, _L, _}, _ET, _S, _MR, _V) -> ok; -t_check_record_fields({ann_type, _L, [_Var, Type]}, ET, S, MR, V) -> - t_check_record_fields(Type, ET, S, MR, V); -t_check_record_fields({paren_type, _L, [Type]}, ET, S, MR, V) -> - t_check_record_fields(Type, ET, S, MR, V); -t_check_record_fields({remote_type, _L, [{atom, _, _}, {atom, _, _}, Args]}, - ET, S, MR, V) -> - list_check_record_fields(Args, ET, S, MR, V); -t_check_record_fields({atom, _L, _}, _ET, _S, _MR, _V) -> ok; -t_check_record_fields({integer, _L, _}, _ET, _S, _MR, _V) -> ok; -t_check_record_fields({op, _L, _Op, _Arg}, _ET, _S, _MR, _V) -> ok; -t_check_record_fields({op, _L, _Op, _Arg1, _Arg2}, _ET, _S, _MR, _V) -> ok; -t_check_record_fields({type, _L, tuple, any}, _ET, _S, _MR, _V) -> ok; -t_check_record_fields({type, _L, map, any}, _ET, _S, _MR, _V) -> ok; -t_check_record_fields({type, _L, binary, [_Base, _Unit]}, _ET, _S, _MR, _V) -> - ok; -t_check_record_fields({type, _L, 'fun', [{type, _, any}, Range]}, - ET, S, MR, V) -> - t_check_record_fields(Range, ET, S, MR, V); -t_check_record_fields({type, _L, range, [_From, _To]}, _ET, _S, _MR, _V) -> - ok; -t_check_record_fields({type, _L, record, [Name|Fields]}, ET, S, MR, V) -> - check_record(Name, Fields, ET, S, MR, V); -t_check_record_fields({type, _L, _, Args}, ET, S, MR, V) -> - list_check_record_fields(Args, ET, S, MR, V); -t_check_record_fields({user_type, _L, _Name, Args}, ET, S, MR, V) -> - list_check_record_fields(Args, ET, S, MR, V). - -check_record({atom, _, Name}, ModFields, ET, Site, MR, V) -> +check_record_fields({var, _L, _}, _S, C) -> C; +check_record_fields({ann_type, _L, [_Var, Type]}, S, C) -> + check_record_fields(Type, S, C); +check_record_fields({paren_type, _L, [Type]}, S, C) -> + check_record_fields(Type, S, C); +check_record_fields({remote_type, _L, [{atom, _, _}, {atom, _, _}, Args]}, + S, C) -> + list_check_record_fields(Args, S, C); +check_record_fields({atom, _L, _}, _S, C) -> C; +check_record_fields({integer, _L, _}, _S, C) -> C; +check_record_fields({op, _L, _Op, _Arg}, _S, C) -> C; +check_record_fields({op, _L, _Op, _Arg1, _Arg2}, _S, C) -> C; +check_record_fields({type, _L, tuple, any}, _S, C) -> C; +check_record_fields({type, _L, map, any}, _S, C) -> C; +check_record_fields({type, _L, binary, [_Base, _Unit]}, _S, C) -> C; +check_record_fields({type, _L, 'fun', [{type, _, any}, Range]}, S, C) -> + check_record_fields(Range, S, C); +check_record_fields({type, _L, range, [_From, _To]}, _S, C) -> C; +check_record_fields({type, _L, record, [Name|Fields]}, S, C) -> + check_record(Name, Fields, S, C); +check_record_fields({type, _L, _, Args}, S, C) -> + list_check_record_fields(Args, S, C); +check_record_fields({user_type, _L, _Name, Args}, S, C) -> + list_check_record_fields(Args, S, C). + +check_record({atom, _, Name}, ModFields, S, C) -> + #from_form{site = Site, mrecs = MR} = S, M = site_module(Site), {ok, R} = dict:find(M, MR), {ok, DeclFields} = lookup_record(Name, R), - case check_fields(Name, ModFields, DeclFields, ET, Site, MR, V) of + case check_fields(Name, ModFields, DeclFields, S, C) of {error, FieldName} -> throw({error, io_lib:format("Illegal declaration of #~w{~w}\n", [Name, FieldName])}); - ok -> ok + C1 -> C1 end. check_fields(RecName, [{type, _, field_type, [{atom, _, Name}, Abstr]}|Left], - DeclFields, ET, Site0, MR, V) -> + DeclFields, S, C) -> + #from_form{site = Site0, xtypes = ET, mrecs = MR, vtab = V} = S, M = site_module(Site0), Site = {record, {M, RecName, length(DeclFields)}}, - Type = t_from_form(Abstr, ET, Site, MR, V), + {Type, C1} = t_from_form(Abstr, ET, Site, MR, V, C), {Name, _, DeclType} = lists:keyfind(Name, 1, DeclFields), TypeNoVars = subst_all_vars_to_any(Type), case t_is_subtype(TypeNoVars, DeclType) of false -> {error, Name}; - true -> check_fields(RecName, Left, DeclFields, ET, Site0, MR, V) + true -> check_fields(RecName, Left, DeclFields, S, C1) end; -check_fields(_RecName, [], _Decl, _ET, _Site, _MR, _V) -> - ok. +check_fields(_RecName, [], _Decl, _S, C) -> + C. -list_check_record_fields([], _ET, _S, _MR, _V) -> - ok; -list_check_record_fields([H|Tail], ET, S, MR, V) -> - ok = t_check_record_fields(H, ET, S, MR, V), - list_check_record_fields(Tail, ET, S, MR, V). +list_check_record_fields([], _S, C) -> + C; +list_check_record_fields([H|Tail], S, C) -> + C1 = check_record_fields(H, S, C), + list_check_record_fields(Tail, S, C1). site_module({_, {Module, _, _}}) -> Module. +-spec cache__new() -> cache(). + +cache__new() -> + maps:new(). + +-spec cache_key(module(), atom(), [erl_type()], + type_names(), expand_depth()) -> cache_key(). + +%% If TypeNames is left out from the key, the cache is smaller, and +%% the form-to-type translation is faster. But it would be a shame if, +%% for example, any() is used, where a more complex type should be +%% used. There is also a slight risk of creating unnecessarily big +%% types. + +cache_key(Module, Name, ArgTypes, TypeNames, D) -> + {Module, Name, D, ArgTypes, TypeNames}. + +-spec cache_find(cache_key(), cache()) -> + {erl_type(), expand_limit()} | 'error'. + +cache_find(Key, Cache) -> + case maps:find(Key, Cache) of + {ok, Value} -> + Value; + error -> + error + end. + +-spec cache_put(cache_key(), erl_type(), expand_limit(), cache()) -> cache(). + +cache_put(_Key, _Type, DeltaL, Cache) when DeltaL < 0 -> + %% The type is truncated; do not reuse it. + Cache; +cache_put(Key, Type, DeltaL, Cache) -> + maps:put(Key, {Type, DeltaL}, Cache). + -spec t_var_names([erl_type()]) -> [atom()]. t_var_names([{var, _, Name}|L]) when L =/= '_' -> @@ -5141,10 +5227,15 @@ t_form_to_string({type, _L, Name, []} = T) -> M = mod, D0 = dict:new(), MR = dict:from_list([{M, D0}]), - S = {type, {M,Name,0}}, - V = #{}, - {T1, _} = - t_from_form(T, [], sets:new(), S, MR, V, _Deep=1000, _ALot=100000), + Site = {type, {M,Name,0}}, + V = var_table__new(), + C = cache__new(), + State = #from_form{site = Site, + xtypes = sets:new(), + mrecs = MR, + vtab = V, + tnames = []}, + {T1, _, _} = from_form(T, State, _Deep=1000, _ALot=1000000, C), t_to_string(T1) catch throw:{error, _} -> atom_to_string(Name) ++ "()" end; @@ -5220,6 +5311,7 @@ lookup_record(Tag, Arity, RecDict) when is_atom(Tag) -> error -> error end. +-spec lookup_type(_, _, _) -> {'type' | 'opaque', type_value()} | 'error'. lookup_type(Name, Arity, RecDict) -> case dict:find({type, Name, Arity}, RecDict) of error -> diff --git a/lib/inets/test/ftp_SUITE.erl b/lib/inets/test/ftp_SUITE.erl index a8d39e3fe7..e2dec0c42a 100644 --- a/lib/inets/test/ftp_SUITE.erl +++ b/lib/inets/test/ftp_SUITE.erl @@ -114,6 +114,7 @@ ftp_tests()-> %%% ftpservers list of servers to check if they are available %%% The element is: %%% {Name, % string(). The os command name +%%% Path, % string(). The os PATH syntax, e.g "/bin:/usr/bin" %%% StartCommand, % fun()->{ok,start_result()} | {error,string()}. %%% % The command to start the daemon with. %%% ChkUp, % fun(start_result()) -> string(). Os command to check @@ -129,12 +130,13 @@ ftp_tests()-> -define(default_ftp_servers, [{"vsftpd", - fun(__CONF__) -> + "/sbin:/usr/sbin:/usr/local/sbin", + fun(__CONF__, AbsName) -> DataDir = proplists:get_value(data_dir,__CONF__), ConfFile = filename:join(DataDir, "vsftpd.conf"), PrivDir = proplists:get_value(priv_dir,__CONF__), AnonRoot = PrivDir, - Cmd = ["vsftpd "++filename:join(DataDir,"vsftpd.conf"), + Cmd = [AbsName ++" "++filename:join(DataDir,"vsftpd.conf"), " -oftpd_banner=erlang_otp_testing", " -oanon_root=\"",AnonRoot,"\"", " -orsa_cert_file=\"",filename:join(DataDir,"server-cert.pem"),"\"", @@ -856,28 +858,51 @@ chk_no_dir(PathList, Config) -> %%-------------------------------------------------------------------- find_executable(Config) -> - FTPservers = case proplists:get_value(ftpservers,Config) of - undefined -> ?default_ftp_servers; - L -> L - end, - case lists:dropwhile(fun not_available/1, FTPservers) of - [] -> false; - [FTPD_data|_] -> {ok, FTPD_data} - end. + search_executable(proplists:get_value(ftpservers, Config, ?default_ftp_servers)). + + +search_executable([{Name,Paths,_StartCmd,_ChkUp,_StopCommand,_ConfigUpd,_Host,_Port}|Srvrs]) -> + case os_find(Name,Paths) of + false -> + ct:log("~p not found",[Name]), + search_executable(Srvrs); + AbsName -> + ct:comment("Found ~p",[AbsName]), + {ok, {AbsName,_StartCmd,_ChkUp,_StopCommand,_ConfigUpd,_Host,_Port}} + end; +search_executable([]) -> + false. -not_available({Name,_StartCmd,_ChkUp,_StopCommand,_ConfigUpd,_Host,_Port}) -> - os:find_executable(Name) == false. +os_find(Name, Paths) -> + case os:find_executable(Name, Paths) of + false -> os:find_executable(Name); + AbsName -> AbsName + end. -start_ftpd(Config) -> - {Name,StartCmd,_ChkUp,_StopCommand,ConfigRewrite,Host,Port} = proplists:get_value(ftpd_data, Config), - case StartCmd(Config) of +%%%---------------------------------------------------------------- +start_ftpd(Config0) -> + {AbsName,StartCmd,_ChkUp,_StopCommand,ConfigRewrite,Host,Port} = + proplists:get_value(ftpd_data, Config0), + case StartCmd(Config0, AbsName) of {ok,StartResult} -> - [{ftpd_host,Host}, - {ftpd_port,Port}, - {ftpd_start_result,StartResult} | ConfigRewrite(Config)]; + Config = [{ftpd_host,Host}, + {ftpd_port,Port}, + {ftpd_start_result,StartResult} | ConfigRewrite(Config0)], + try + ftp__close(ftp__open(Config,[verbose])) + of + Config1 when is_list(Config1) -> + ct:log("Usuable ftp server ~p started on ~p:~p",[AbsName,Host,Port]), + Config + catch + Class:Exception -> + ct:log("Ftp server ~p started on ~p:~p but is unusable:~n~p:~p", + [AbsName,Host,Port,Class,Exception]), + {skip, [AbsName," started but unusuable"]} + end; {error,Msg} -> - {skip, [Name," not started: ",Msg]} + {skip, [AbsName," not started: ",Msg]} end. stop_ftpd(Config) -> diff --git a/lib/mnesia/doc/src/Mnesia_chap2.xmlsrc b/lib/mnesia/doc/src/Mnesia_chap2.xmlsrc index ba0746e736..37389ce5ae 100644 --- a/lib/mnesia/doc/src/Mnesia_chap2.xmlsrc +++ b/lib/mnesia/doc/src/Mnesia_chap2.xmlsrc @@ -317,7 +317,7 @@ sex = male, phone = 98108, room_no = {221, 015}}, - insert_emp(Me, 'B/SFR', [Erlang, mnesia, otp]).</code> + insert_emp(Emp, 'B/SFR', [Erlang, mnesia, otp]).</code> <note><p>For information about Funs, see "Fun Expressions" in section <c>Erlang Reference Manual</c> in System Documentation..</p> diff --git a/lib/stdlib/doc/src/erl_id_trans.xml b/lib/stdlib/doc/src/erl_id_trans.xml index 153bd4148e..649490f8b3 100644 --- a/lib/stdlib/doc/src/erl_id_trans.xml +++ b/lib/stdlib/doc/src/erl_id_trans.xml @@ -49,7 +49,8 @@ <name>parse_transform(Forms, Options) -> Forms</name> <fsummary>Transform Erlang forms</fsummary> <type> - <v>Forms = [<seealso marker="erl_parse#type-abstract_form">erl_parse:abstract_form()</seealso>]</v> + <v>Forms = [<seealso marker="erl_parse#type-abstract_form">erl_parse:abstract_form()</seealso> + | <seealso marker="erl_parse#type-form_info">erl_parse:form_info()</seealso>]</v> <v>Options = [<seealso marker="compile#type-option">compile:option()</seealso>]</v> </type> <desc> diff --git a/lib/stdlib/doc/src/erl_parse.xml b/lib/stdlib/doc/src/erl_parse.xml index 32fce16d68..771ccc2dc6 100644 --- a/lib/stdlib/doc/src/erl_parse.xml +++ b/lib/stdlib/doc/src/erl_parse.xml @@ -73,6 +73,14 @@ <name name="error_info"></name> </datatype> <datatype> + <name name="form_info"></name> + <desc><p>Tuples <c>{error, error_info()}</c> and <c>{warning, + error_info()}</c>, denoting syntactically incorrect forms and + warnings, and <c>{eof, line()}</c>, denoting an end-of-stream + encountered before a complete form had been parsed.</p> + </desc> + </datatype> + <datatype> <name name="token"></name> </datatype> </datatypes> diff --git a/lib/stdlib/src/erl_expand_records.erl b/lib/stdlib/src/erl_expand_records.erl index 9c0a7fb7d5..ebcbc54ab1 100644 --- a/lib/stdlib/src/erl_expand_records.erl +++ b/lib/stdlib/src/erl_expand_records.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2005-2015. All Rights Reserved. +%% Copyright Ericsson AB 2005-2016. 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. @@ -37,8 +37,9 @@ checked_ra=[] % successfully accessed records }). --spec(module(AbsForms, CompileOptions) -> AbsForms when +-spec(module(AbsForms, CompileOptions) -> AbsForms2 when AbsForms :: [erl_parse:abstract_form()], + AbsForms2 :: [erl_parse:abstract_form()], CompileOptions :: [compile:option()]). %% Is is assumed that Fs is a valid list of forms. It should pass diff --git a/lib/stdlib/src/erl_lint.erl b/lib/stdlib/src/erl_lint.erl index 2508f96b91..e9332ce069 100644 --- a/lib/stdlib/src/erl_lint.erl +++ b/lib/stdlib/src/erl_lint.erl @@ -99,7 +99,7 @@ value_option(Flag, Default, On, OnVal, Off, OffVal, Opts) -> module='', %Module behaviour=[], %Behaviour exports=gb_sets:empty() :: gb_sets:set(fa()),%Exports - imports=[] :: [fa()], %Imports, an orddict() + imports=[] :: orddict:orddict(fa(), module()),%Imports compile=[], %Compile flags records=dict:new() %Record definitions :: dict:dict(atom(), {line(),Fields :: term()}), @@ -467,7 +467,7 @@ used_vars(Exprs, BindingsList) -> %% really all ordsets! -spec(module(AbsForms) -> {ok, Warnings} | {error, Errors, Warnings} when - AbsForms :: [erl_parse:abstract_form()], + AbsForms :: [erl_parse:abstract_form() | erl_parse:form_info()], Warnings :: [{file:filename(),[ErrorInfo]}], Errors :: [{FileName2 :: file:filename(),[ErrorInfo]}], ErrorInfo :: error_info()). @@ -479,7 +479,7 @@ module(Forms) -> -spec(module(AbsForms, FileName) -> {ok, Warnings} | {error, Errors, Warnings} when - AbsForms :: [erl_parse:abstract_form()], + AbsForms :: [erl_parse:abstract_form() | erl_parse:form_info()], FileName :: atom() | string(), Warnings :: [{file:filename(),[ErrorInfo]}], Errors :: [{FileName2 :: file:filename(),[ErrorInfo]}], @@ -492,7 +492,7 @@ module(Forms, FileName) -> -spec(module(AbsForms, FileName, CompileOptions) -> {ok, Warnings} | {error, Errors, Warnings} when - AbsForms :: [erl_parse:abstract_form()], + AbsForms :: [erl_parse:abstract_form() | erl_parse:form_info()], FileName :: atom() | string(), CompileOptions :: [compile:option()], Warnings :: [{file:filename(),[ErrorInfo]}], diff --git a/lib/stdlib/src/erl_parse.yrl b/lib/stdlib/src/erl_parse.yrl index a896de4f1c..85b2816451 100644 --- a/lib/stdlib/src/erl_parse.yrl +++ b/lib/stdlib/src/erl_parse.yrl @@ -170,9 +170,6 @@ fun_type -> '(' top_types ')' '->' top_type : {type, ?anno('$1'), 'fun', [{type, ?anno('$1'), product, '$2'},'$5']}. -map_pair_types -> '...' : [{type, ?anno('$1'), map_field_assoc, - [{type, ?anno('$1'), any, []}, - {type, ?anno('$1'), any, []}]}]. map_pair_types -> map_pair_type : ['$1']. map_pair_types -> map_pair_type ',' map_pair_types : ['$1'|'$3']. @@ -534,7 +531,7 @@ Erlang code. -compile([{hipe,[{regalloc,linear_scan}]}]). -export_type([abstract_clause/0, abstract_expr/0, abstract_form/0, - abstract_type/0, error_info/0]). + abstract_type/0, form_info/0, error_info/0]). %% Start of Abstract Format @@ -546,7 +543,6 @@ Erlang code. | af_export() | af_import() | af_export_type() - | af_optional_callbacks() | af_compile() | af_file() | af_record_decl() @@ -573,9 +569,6 @@ Erlang code. -type af_ta_list() :: [{type_name(), arity()}]. --type af_optional_callbacks() :: - {'attribute', anno(), 'optional_callbacks', af_fa_list()}. - -type af_compile() :: {'attribute', anno(), 'compile', any()}. -type af_file() :: {'attribute', anno(), 'file', {string(), anno()}}. @@ -867,16 +860,22 @@ Erlang code. | af_unary_op(af_singleton_integer_type()) | af_binary_op(af_singleton_integer_type()). --type af_literal() :: af_atom() | af_integer() | af_float() | af_string(). +-type af_literal() :: af_atom() + | af_character() + | af_float() + | af_integer() + | af_string(). -type af_atom() :: af_lit_atom(atom()). -type af_lit_atom(A) :: {'atom', anno(), A}. --type af_integer() :: {'integer', anno(), non_neg_integer()}. +-type af_character() :: {'char', anno(), char()}. -type af_float() :: {'float', anno(), float()}. +-type af_integer() :: {'integer', anno(), non_neg_integer()}. + -type af_string() :: {'string', anno(), string()}. -type af_match(T) :: {'match', anno(), af_pattern(), T}. @@ -944,6 +943,10 @@ Erlang code. -type type_name() :: atom(). +-type form_info() :: {'eof', erl_anno:line()} + | {'error', erl_scan:error_info() | error_info()} + | {'warning', erl_scan:error_info() | error_info()}. + %% End of Abstract Format %% XXX. To be refined. @@ -1503,8 +1506,9 @@ type_preop_prec('#') -> {700,800}. | abstract_type(). -spec map_anno(Fun, Abstr) -> NewAbstr when - Fun :: fun((Anno) -> Anno), + Fun :: fun((Anno) -> NewAnno), Anno :: erl_anno:anno(), + NewAnno :: erl_anno:anno(), Abstr :: erl_parse_tree(), NewAbstr :: erl_parse_tree(). @@ -1513,14 +1517,14 @@ map_anno(F0, Abstr) -> {NewAbstr, []} = modify_anno1(Abstr, [], F), NewAbstr. --spec fold_anno(Fun, Acc0, Abstr) -> NewAbstr when +-spec fold_anno(Fun, Acc0, Abstr) -> Acc1 when Fun :: fun((Anno, AccIn) -> AccOut), Anno :: erl_anno:anno(), Acc0 :: term(), + Acc1 :: term(), AccIn :: term(), AccOut :: term(), - Abstr :: erl_parse_tree(), - NewAbstr :: erl_parse_tree(). + Abstr :: erl_parse_tree(). fold_anno(F0, Acc0, Abstr) -> F = fun(A, Acc) -> {A, F0(A, Acc)} end, @@ -1528,8 +1532,9 @@ fold_anno(F0, Acc0, Abstr) -> NewAcc. -spec mapfold_anno(Fun, Acc0, Abstr) -> {NewAbstr, Acc1} when - Fun :: fun((Anno, AccIn) -> {Anno, AccOut}), + Fun :: fun((Anno, AccIn) -> {NewAnno, AccOut}), Anno :: erl_anno:anno(), + NewAnno :: erl_anno:anno(), Acc0 :: term(), Acc1 :: term(), AccIn :: term(), @@ -1545,7 +1550,9 @@ mapfold_anno(F, Acc0, Abstr) -> Abstr :: erl_parse_tree(). new_anno(Term) -> - map_anno(fun erl_anno:new/1, Term). + F = fun(L, Acc) -> {erl_anno:new(L), Acc} end, + {NewAbstr, []} = modify_anno1(Term, [], F), + NewAbstr. -spec anno_to_term(Abstr) -> term() when Abstr :: erl_parse_tree(). diff --git a/lib/stdlib/src/erl_pp.erl b/lib/stdlib/src/erl_pp.erl index ca764675fc..016962f538 100644 --- a/lib/stdlib/src/erl_pp.erl +++ b/lib/stdlib/src/erl_pp.erl @@ -70,13 +70,13 @@ %%% -spec(form(Form) -> io_lib:chars() when - Form :: erl_parse:abstract_form()). + Form :: erl_parse:abstract_form() | erl_parse:form_info()). form(Thing) -> form(Thing, none). -spec(form(Form, Options) -> io_lib:chars() when - Form :: erl_parse:abstract_form(), + Form :: erl_parse:abstract_form() | erl_parse:form_info(), Options :: options()). form(Thing, Options) -> @@ -344,27 +344,9 @@ binary_type(I1, I2) -> map_type(Fs) -> {first,[$#],map_pair_types(Fs)}. -map_pair_types(Fs0) -> - Fs = replace_any_map(Fs0), +map_pair_types(Fs) -> tuple_type(Fs, fun map_pair_type/2). -replace_any_map([{type,Line,map_field_assoc,[KType,VType]}]=Fs) -> - IsAny = fun({type,_,any,[]}) -> true; - %% ({var,_,'_'}) -> true; - (_) -> false - end, - case IsAny(KType) andalso IsAny(VType) of - true -> - [{type,Line,map_field_assoc,any}]; - false -> - Fs - end; -replace_any_map([F|Fs]) -> - [F|replace_any_map(Fs)]; -replace_any_map([]) -> []. - -map_pair_type({type,_Line,map_field_assoc,any}, _Prec) -> - leaf("..."); map_pair_type({type,_Line,map_field_assoc,[KType,VType]}, Prec) -> {list,[{cstep,[ltype(KType, Prec),leaf(" =>")],ltype(VType, Prec)}]}; map_pair_type({type,_Line,map_field_exact,[KType,VType]}, Prec) -> diff --git a/lib/stdlib/src/lib.erl b/lib/stdlib/src/lib.erl index 6fba63a895..56654097d9 100644 --- a/lib/stdlib/src/lib.erl +++ b/lib/stdlib/src/lib.erl @@ -73,7 +73,7 @@ nonl([H|T]) -> [H|nonl(T)]. send(To, Msg) -> To ! Msg. --spec sendw(To, Msg) -> Msg when +-spec sendw(To, Msg) -> term() when To :: pid() | atom() | {atom(), node()}, Msg :: term(). diff --git a/lib/stdlib/src/ms_transform.erl b/lib/stdlib/src/ms_transform.erl index 24b5fde1db..c0eea652e7 100644 --- a/lib/stdlib/src/ms_transform.erl +++ b/lib/stdlib/src/ms_transform.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2002-2015. All Rights Reserved. +%% Copyright Ericsson AB 2002-2016. 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. @@ -224,8 +224,9 @@ transform_from_shell(Dialect, Clauses, BoundEnvironment) -> %% Called when translating during compiling %% --spec parse_transform(Forms, Options) -> Forms when - Forms :: [erl_parse:abstract_form()], +-spec parse_transform(Forms, Options) -> Forms2 when + Forms :: [erl_parse:abstract_form() | erl_parse:form_info()], + Forms2 :: [erl_parse:abstract_form() | erl_parse:form_info()], Options :: term(). parse_transform(Forms, _Options) -> diff --git a/lib/stdlib/src/proplists.erl b/lib/stdlib/src/proplists.erl index 8e99ec0ed9..5356467b19 100644 --- a/lib/stdlib/src/proplists.erl +++ b/lib/stdlib/src/proplists.erl @@ -438,8 +438,9 @@ substitute_aliases_1([], P) -> %% @see normalize/2 -spec substitute_negations(Negations, ListIn) -> ListOut when - Negations :: [{Key, Key}], - Key :: term(), + Negations :: [{Key1, Key2}], + Key1 :: term(), + Key2 :: term(), ListIn :: [term()], ListOut :: [term()]. diff --git a/lib/stdlib/src/qlc.erl b/lib/stdlib/src/qlc.erl index b396ba7057..f3665824f2 100644 --- a/lib/stdlib/src/qlc.erl +++ b/lib/stdlib/src/qlc.erl @@ -734,10 +734,11 @@ table(TraverseFun, Options) when is_function(TraverseFun) -> table(T1, T2) -> erlang:error(badarg, [T1, T2]). --spec(transform_from_evaluator(LC, Bs) -> Expr when +-spec(transform_from_evaluator(LC, Bs) -> Return when LC :: abstract_expr(), - Expr :: abstract_expr(), - Bs :: erl_eval:binding_struct()). + Bs :: erl_eval:binding_struct(), + Return :: {ok, abstract_expr()} + | {not_ok, {error, module(), Reason :: term()}}). transform_from_evaluator(LC, Bs0) -> qlc_pt:transform_from_evaluator(LC, Bs0). diff --git a/lib/stdlib/src/qlc_pt.erl b/lib/stdlib/src/qlc_pt.erl index e4b9768b12..0db63b81f4 100644 --- a/lib/stdlib/src/qlc_pt.erl +++ b/lib/stdlib/src/qlc_pt.erl @@ -67,8 +67,8 @@ %%% -spec(parse_transform(Forms, Options) -> Forms2 when - Forms :: [erl_parse:abstract_form()], - Forms2 :: [erl_parse:abstract_form()], + Forms :: [erl_parse:abstract_form() | erl_parse:form_info()], + Forms2 :: [erl_parse:abstract_form() | erl_parse:form_info()], Options :: [Option], Option :: type_checker | compile:option()). @@ -117,19 +117,21 @@ parse_transform(Forms0, Options) -> true = ets:delete(NodeInfo) end. --spec(transform_from_evaluator(LC, Bs) -> Expr when +-spec(transform_from_evaluator(LC, Bs) -> Return when LC :: erl_parse:abstract_expr(), - Expr :: erl_parse:abstract_expr(), - Bs :: erl_eval:binding_struct()). + Bs :: erl_eval:binding_struct(), + Return :: {ok, erl_parse:abstract_expr()} + | {not_ok, {error, module(), Reason :: term()}}). transform_from_evaluator(LC, Bindings) -> ?DEBUG("qlc Parse Transform (Evaluator Version)~n", []), transform_expression(LC, Bindings, false). --spec(transform_expression(LC, Bs) -> Expr when +-spec(transform_expression(LC, Bs) -> Return when LC :: erl_parse:abstract_expr(), - Expr :: erl_parse:abstract_expr(), - Bs :: erl_eval:binding_struct()). + Bs :: erl_eval:binding_struct(), + Return :: {ok, erl_parse:abstract_expr()} + | {not_ok, [{error, Reason :: term()}]}). transform_expression(LC, Bindings) -> transform_expression(LC, Bindings, true). diff --git a/lib/stdlib/src/sofs.erl b/lib/stdlib/src/sofs.erl index b18df2ad09..c244e06ca4 100644 --- a/lib/stdlib/src/sofs.erl +++ b/lib/stdlib/src/sofs.erl @@ -621,6 +621,9 @@ canonical_relation(Sets) when ?IS_SET(Sets) -> %%% Functions on binary relations only. %%% +-spec(rel2fam(BinRel) -> Family when + Family :: family(), + BinRel :: binary_relation()). rel2fam(R) -> relation_to_family(R). diff --git a/lib/stdlib/test/erl_pp_SUITE.erl b/lib/stdlib/test/erl_pp_SUITE.erl index a48ba7b5b7..951a17d3eb 100644 --- a/lib/stdlib/test/erl_pp_SUITE.erl +++ b/lib/stdlib/test/erl_pp_SUITE.erl @@ -1112,15 +1112,14 @@ pr_1014(Config) -> ok = pp_forms(<<"-type t() :: #{any() => _}. ">>), ok = pp_forms(<<"-type t() :: #{_ => any()}. ">>), ok = pp_forms(<<"-type t() :: #{any() => any()}. ">>), - ok = pp_forms(<<"-type t() :: #{...}. ">>), - ok = pp_forms(<<"-type t() :: #{atom() := integer(), ...}. ">>), + ok = pp_forms(<<"-type t() :: #{atom() := integer(), any() => any()}. ">>), FileName = filename('pr_1014.erl', Config), C = <<"-module pr_1014.\n" "-compile export_all.\n" "-type m() :: #{..., a := integer()}.\n">>, ok = file:write_file(FileName, C), - {error,[{_,[{3,erl_parse,["syntax error before: ","','"]}]}],_} = + {error,[{_,[{3,erl_parse,["syntax error before: ","'...'"]}]}],_} = compile:file(FileName, [return]), ok. diff --git a/lib/syntax_tools/src/Makefile b/lib/syntax_tools/src/Makefile index 2e91adf8af..8325db45a8 100644 --- a/lib/syntax_tools/src/Makefile +++ b/lib/syntax_tools/src/Makefile @@ -29,7 +29,7 @@ ERL_COMPILE_FLAGS += -pa $(EBIN) -pa ./ -I$(INCLUDE) ifeq ($(NATIVE_LIBS_ENABLED),yes) ERL_COMPILE_FLAGS += +native endif -ERL_COMPILE_FLAGS += +nowarn_shadow_vars +warn_unused_import -Werror # +warn_missing_spec +warn_untyped_record +ERL_COMPILE_FLAGS += +nowarn_shadow_vars +warn_unused_import #-Werror # +warn_missing_spec +warn_untyped_record SOURCES=erl_syntax.erl erl_prettypr.erl erl_syntax_lib.erl \ erl_comment_scan.erl erl_recomment.erl erl_tidy.erl \ diff --git a/lib/syntax_tools/src/erl_prettypr.erl b/lib/syntax_tools/src/erl_prettypr.erl index 119d375746..f1615b2610 100644 --- a/lib/syntax_tools/src/erl_prettypr.erl +++ b/lib/syntax_tools/src/erl_prettypr.erl @@ -1130,13 +1130,14 @@ lay_2(Node, Ctxt) -> any_size -> text("map()"); Fs -> - {Prec, _PrecR} = type_preop_prec('#'), - Es = lay_map_fields(Fs, - floating(text(",")), - reset_prec(Ctxt)), + Ctxt1 = reset_prec(Ctxt), + Es = seq(Fs, + floating(text(",")), Ctxt1, + fun lay/2), D = beside(floating(text("#{")), beside(par(Es), floating(text("}")))), + {Prec, _PrecR} = type_preop_prec('#'), maybe_parentheses(D, Prec, Ctxt) end; @@ -1400,36 +1401,6 @@ lay_error_info(T, Ctxt) -> lay_concrete(T, Ctxt) -> lay(erl_syntax:abstract(T), Ctxt). -lay_map_fields([H | T], Separator, Ctxt) -> - case T of - [] -> - [case erl_syntax:type(H) of - map_type_assoc -> - lay_last_type_assoc(H, Ctxt); - _ -> - lay(H, Ctxt) - end]; - _ -> - [maybe_append(Separator, lay(H, Ctxt)) - | lay_map_fields(T, Separator, Ctxt)] - end; -lay_map_fields([], _, _) -> - [empty()]. - -lay_last_type_assoc(Node, Ctxt) -> - Name = erl_syntax:map_type_assoc_name(Node), - Value = erl_syntax:map_type_assoc_value(Node), - IsAny = fun({type,_,any,[]}) -> true; - %% ({var,_,'_'}) -> true; - (_) -> false - end, - case IsAny(Name) andalso IsAny(Value) of - true -> - text("..."); - false -> - lay_type_assoc(Name, Value, Ctxt) - end. - lay_type_assoc(Name, Value, Ctxt) -> Ctxt1 = reset_prec(Ctxt), D1 = lay(Name, Ctxt1), diff --git a/lib/syntax_tools/src/erl_recomment.erl b/lib/syntax_tools/src/erl_recomment.erl index 5ce533285d..c1141b2bc6 100644 --- a/lib/syntax_tools/src/erl_recomment.erl +++ b/lib/syntax_tools/src/erl_recomment.erl @@ -601,16 +601,16 @@ expand_comment(C) -> -record(leaf, {min = 0 :: integer(), max = 0 :: integer(), - precomments = [] :: [erl_syntax:syntaxTree()], - postcomments = [] :: [erl_syntax:syntaxTree()], + precomments = [] :: [erl_comment_scan:comment()], + postcomments = [] :: [erl_comment_scan:comment()], value :: erl_syntax:syntaxTree()}). -record(tree, {min = 0 :: integer(), max = 0 :: integer(), type :: atom(), attrs :: erl_syntax:syntaxTreeAttributes(), - precomments = [] :: [erl_syntax:syntaxTree()], - postcomments = [] :: [erl_syntax:syntaxTree()], + precomments = [] :: [erl_comment_scan:comment()], + postcomments = [] :: [erl_comment_scan:comment()], subtrees = [] :: [extendedSyntaxTree()]}). diff --git a/lib/syntax_tools/src/erl_syntax.erl b/lib/syntax_tools/src/erl_syntax.erl index f4cda814fc..ee42e56172 100644 --- a/lib/syntax_tools/src/erl_syntax.erl +++ b/lib/syntax_tools/src/erl_syntax.erl @@ -443,7 +443,14 @@ -type syntaxTree() :: #tree{} | #wrapper{} | erl_parse(). --type erl_parse() :: erl_parse:abstract_form() | erl_parse:abstract_expr(). +-type erl_parse() :: erl_parse:abstract_clause() + | erl_parse:abstract_expr() + | erl_parse:abstract_form() + | erl_parse:abstract_type() + | erl_parse:form_info() + %% To shut up Dialyzer: + | {bin_element, _, _, _, _}. + %% The representation built by the Erlang standard library parser %% `erl_parse'. This is a subset of the {@link syntaxTree()} type. diff --git a/lib/syntax_tools/test/syntax_tools_SUITE_data/type_specs.erl b/lib/syntax_tools/test/syntax_tools_SUITE_data/type_specs.erl index 5621d3a293..e4f8a1c3de 100644 --- a/lib/syntax_tools/test/syntax_tools_SUITE_data/type_specs.erl +++ b/lib/syntax_tools/test/syntax_tools_SUITE_data/type_specs.erl @@ -49,9 +49,12 @@ -type m1() :: #{} | map(). -type m2() :: #{a := m1(), b => #{} | fy:m2()}. --type m3() :: #{...}. --type m4() :: #{_ => _, ...}. --type m5() :: #{any() => any(), ...}. % Currently printed as `#{..., ...}'. +%-type m3() :: #{...}. +%-type m4() :: #{_ => _, ...}. +%-type m5() :: #{any() => any(), ...}. +-type m3() :: #{any() => any()}. +-type m4() :: #{_ => _, any() => any()}. +-type m5() :: #{any() => any(), any() => any()}. -type b1() :: B1 :: binary() | (BitString :: bitstring()). -define(PAIR(A, B), {(A), (B)}). diff --git a/lib/tools/src/xref_base.erl b/lib/tools/src/xref_base.erl index 4322943c59..bb9815f9b0 100644 --- a/lib/tools/src/xref_base.erl +++ b/lib/tools/src/xref_base.erl @@ -669,16 +669,45 @@ do_add_directory(Dir, AppName, Bui, Rec, Ver, War, State) -> warnings(War, unreadable, Unreadable), case Errors of [] -> - do_add_modules(FileNames, AppName, Bui, Ver, War, State, []); + do_add_modules(FileNames, AppName, Bui, Ver, War, State); [Error | _] -> throw(Error) end. -do_add_modules([], _AppName, _OB, _OV, _OW, State, Modules) -> +do_add_modules(Files, AppName, OB, OV, OW, State0) -> + NFiles = length(Files), + Reader = fun(SplitName, State) -> + _Pid = read_module(SplitName, AppName, OB, OV, OW, State) + end, + N = parallelism(), + Files1 = start_readers(Files, Reader, State0, N), + %% Increase the number of readers towards the end to decrease the + %% waiting time for the collecting process: + Nx = N, + add_mods(Files1, Reader, State0, [], NFiles, Nx). + +add_mods(_, _ReaderFun, State, Modules, 0, _Nx) -> {ok, sort(Modules), State}; -do_add_modules([File | Files], AppName, OB, OV, OW, State, Modules) -> - {ok, M, NewState} = do_add_module(File, AppName, OB, OV, OW, State), - do_add_modules(Files, AppName, OB, OV, OW, NewState, M ++ Modules). +add_mods(Files, ReaderFun, State, Modules, N, Nx) -> + {I, Nx1} = case Nx > 0 of + false -> {1, Nx}; + true -> {2, Nx - 1} + end, + Files1 = start_readers(Files, ReaderFun, State, I), + {ok, M, NewState} = process_module(State), + add_mods(Files1, ReaderFun, NewState, M ++ Modules, N - 1, Nx1). + +start_readers([SplitName|Files], ReaderFun, State, N) when N > 0 -> + _Pid = ReaderFun(SplitName, State), + start_readers(Files, ReaderFun, State, N - 1); +start_readers(Files, _ReaderFun, _State, _) -> + Files. + +parallelism() -> + case erlang:system_info(multi_scheduling) of + enabled -> erlang:system_info(schedulers_online); + _ -> 1 + end. %% -> {ok, Module, State} | throw(Error) do_add_a_module(File, AppName, Builtins, Verbose, Warnings, State) -> @@ -692,50 +721,75 @@ do_add_a_module(File, AppName, Builtins, Verbose, Warnings, State) -> %% -> {ok, Module, State} | throw(Error) %% Options: verbose, warnings, builtins -do_add_module({Dir, Basename}, AppName, Builtins, Verbose, Warnings, State) -> - File = filename:join(Dir, Basename), - {ok, M, Bad, NewState} = - do_add_module1(Dir, File, AppName, Builtins, Verbose, Warnings, State), - _ = filter(fun({Tag,B}) -> warnings(Warnings, Tag, [[File,B]]) end, Bad), - {ok, M, NewState}. - -do_add_module1(Dir, File, AppName, Builtins, Verbose, Warnings, State) -> - message(Verbose, reading_beam, [File]), - Mode = State#xref.mode, +do_add_module(SplitName, AppName, Builtins, Verbose, Warnings, State) -> + _Pid = read_module(SplitName, AppName, Builtins, Verbose, Warnings, State), + process_module(State). + +read_module(SplitName, AppName, Builtins, Verbose, Warnings, State) -> Me = self(), - Fun = fun() -> Me ! {self(), abst(File, Builtins, Mode)} end, - case xref_utils:subprocess(Fun, [link, {min_heap_size,100000}]) of + #xref{mode = Mode} = State, + Fun = + fun() -> + Me ! {?MODULE, + read_a_module(SplitName, AppName, Builtins, Verbose, + Warnings, Mode)} + end, + spawn_opt(Fun, [link, {min_heap_size, 1000000}, {priority, high}]). + +read_a_module({Dir, BaseName}, AppName, Builtins, Verbose, Warnings, Mode) -> + File = filename:join(Dir, BaseName), + case abst(File, Builtins, Mode) of {ok, _M, no_abstract_code} when Verbose -> - message(Verbose, skipped_beam, []), - {ok, [], [], State}; + message(Verbose, no_debug_info, [File]), + no; {ok, _M, no_abstract_code} when not Verbose -> message(Warnings, no_debug_info, [File]), - {ok, [], [], State}; + no; {ok, M, Data, UnresCalls0} -> - %% Remove duplicates. Identical unresolved calls on the - %% same line are counted as _one_ unresolved call. - UnresCalls = usort(UnresCalls0), - message(Verbose, done, []), - NoUnresCalls = length(UnresCalls), - case NoUnresCalls of - 0 -> ok; - 1 -> warnings(Warnings, unresolved_summary1, [[M]]); - N -> warnings(Warnings, unresolved_summary, [[M, N]]) - end, - T = case xref_utils:file_info(File) of - {ok, {_, _, _, Time}} -> Time; - Error -> throw(Error) - end, - XMod = #xref_mod{name = M, app_name = AppName, dir = Dir, - mtime = T, builtins = Builtins, - no_unresolved = NoUnresCalls}, - do_add_module(State, XMod, UnresCalls, Data); + message(Verbose, done, [File]), + %% Remove duplicates. Identical unresolved calls on the + %% same line are counted as _one_ unresolved call. + UnresCalls = usort(UnresCalls0), + NoUnresCalls = length(UnresCalls), + case NoUnresCalls of + 0 -> ok; + 1 -> warnings(Warnings, unresolved_summary1, [[M]]); + N -> warnings(Warnings, unresolved_summary, [[M, N]]) + end, + case xref_utils:file_info(File) of + {ok, {_, _, _, Time}} -> + XMod = #xref_mod{name = M, app_name = AppName, + dir = Dir, mtime = Time, + builtins = Builtins, + no_unresolved = NoUnresCalls}, + {ok, PrepMod, Bad} = + prepare_module(Mode, XMod, UnresCalls, Data), + foreach(fun({Tag,B}) -> + warnings(Warnings, Tag, + [[File,B]]) + end, Bad), + {ok, PrepMod}; + Error -> Error + end; Error -> message(Verbose, error, []), - throw(Error) + Error end. -abst(File, Builtins, Mode) when Mode =:= functions -> +process_module(State) -> + receive + {?MODULE, Reply} -> + case Reply of + no -> + {ok, [], State}; + {ok, PrepMod} -> + finish_module(PrepMod, State); + Error -> + throw(Error) + end + end. + +abst(File, Builtins, _Mode = functions) -> case beam_lib:chunks(File, [abstract_code, exports, attributes]) of {ok, {M,[{abstract_code,NoA},_X,_A]}} when NoA =:= no_abstract_code -> {ok, M, NoA}; @@ -762,7 +816,7 @@ abst(File, Builtins, Mode) when Mode =:= functions -> Error when element(1, Error) =:= error -> Error end; -abst(File, Builtins, Mode) when Mode =:= modules -> +abst(File, Builtins, _Mode = modules) -> case beam_lib:chunks(File, [exports, imports, attributes]) of {ok, {Mod, [{exports,X0}, {imports,I0}, {attributes,At}]}} -> X1 = mfa_exports(X0, At, Mod), @@ -856,19 +910,13 @@ deprecated_flag(_) -> undefined. %% dom CallAt = LC U XC %% Attrs is collected from the attribute 'xref' (experimental). do_add_module(S, XMod, Unres, Data) -> - M = XMod#xref_mod.name, - case dict:find(M, S#xref.modules) of - {ok, OldXMod} -> - BF2 = module_file(XMod), - BF1 = module_file(OldXMod), - throw_error({module_clash, {M, BF1, BF2}}); - error -> - do_add_module(S, M, XMod, Unres, Data) - end. + #xref{mode = Mode} = S, + Mode = S#xref.mode, + {ok, PrepMod, Bad} = prepare_module(Mode, XMod, Unres, Data), + {ok, Ms, NS} = finish_module(PrepMod, S), + {ok, Ms, Bad, NS}. -%%do_add_module(S, M, _XMod, _Unres, Data)-> -%% {ok, M, [], S}; -do_add_module(S, M, XMod, Unres0, Data) when S#xref.mode =:= functions -> +prepare_module(_Mode = functions, XMod, Unres0, Data) -> {DefAt0, LPreCAt0, XPreCAt0, LC0, XC0, X0, Attrs, Depr} = Data, %% Bad is a list of bad values of 'xref' attributes. {ALC0,AXC0,Bad0} = Attrs, @@ -904,26 +952,27 @@ do_add_module(S, M, XMod, Unres0, Data) when S#xref.mode =:= functions -> LC = union(LC1, ALC), {DF1,DF_11,DF_21,DF_31,DBad} = depr_mod(Depr, X), + {EE, ECallAt} = inter_graph(X, L, LC, XC, CallAt), + {ok, {functions, XMod, [DefAt,L,X,LCallAt,XCallAt,CallAt,LC,XC,EE,ECallAt, + DF1,DF_11,DF_21,DF_31], NoCalls, Unres}, + DBad++Bad}; +prepare_module(_Mode = modules, XMod, _Unres, Data) -> + {X0, I0, Depr} = Data, + X1 = xref_utils:xset(X0, [tspec(func)]), + I1 = xref_utils:xset(I0, [tspec(func)]), + {DF1,DF_11,DF_21,DF_31,DBad} = depr_mod(Depr, X1), + {ok, {modules, XMod, [X1,I1,DF1,DF_11,DF_21,DF_31]}, DBad}. - %% {EE, ECallAt} = inter_graph(X, L, LC, XC, LCallAt, XCallAt), - Self = self(), - Fun = fun() -> inter_graph(Self, X, L, LC, XC, CallAt) end, - {EE, ECallAt} = - xref_utils:subprocess(Fun, [link, {min_heap_size,100000}]), - +finish_module({functions, XMod, List, NoCalls, Unres}, S) -> + ok = check_module(XMod, S), [DefAt2,L2,X2,LCallAt2,XCallAt2,CallAt2,LC2,XC2,EE2,ECallAt2, - DF2,DF_12,DF_22,DF_32] = - pack([DefAt,L,X,LCallAt,XCallAt,CallAt,LC,XC,EE,ECallAt, - DF1,DF_11,DF_21,DF_31]), - - %% Foo = [DefAt2,L2,X2,LCallAt2,XCallAt2,CallAt2,LC2,XC2,EE2,ECallAt2, - %% DF2,DF_12,DF_22,DF_32], - %% io:format("{~p, ~p, ~p},~n", [M, pack:lsize(Foo), pack:usize(Foo)]), + DF2,DF_12,DF_22,DF_32] = pack(List), LU = range(LC2), LPredefined = predefined_funs(LU), + M = XMod#xref_mod.name, MS = xref_utils:xset(M, atom), T = from_sets({MS,DefAt2,L2,X2,LCallAt2,XCallAt2,CallAt2, LC2,XC2,LU,EE2,ECallAt2,Unres,LPredefined, @@ -934,19 +983,28 @@ do_add_module(S, M, XMod, Unres0, Data) when S#xref.mode =:= functions -> XMod1 = XMod#xref_mod{data = T, info = Info}, S1 = S#xref{modules = dict:store(M, XMod1, S#xref.modules)}, - {ok, [M], DBad++Bad, take_down(S1)}; -do_add_module(S, M, XMod, _Unres, Data) when S#xref.mode =:= modules -> - {X0, I0, Depr} = Data, - X1 = xref_utils:xset(X0, [tspec(func)]), - I1 = xref_utils:xset(I0, [tspec(func)]), - {DF1,DF_11,DF_21,DF_31,DBad} = depr_mod(Depr, X1), - [X2,I2,DF2,DF_12,DF_22,DF_32] = pack([X1,I1,DF1,DF_11,DF_21,DF_31]), + {ok, [M], take_down(S1)}; +finish_module({modules, XMod, List}, S) -> + ok = check_module(XMod, S), + [X2,I2,DF2,DF_12,DF_22,DF_32] = pack(List), + M = XMod#xref_mod.name, MS = xref_utils:xset(M, atom), T = from_sets({MS, X2, I2, DF2, DF_12, DF_22, DF_32}), Info = [], XMod1 = XMod#xref_mod{data = T, info = Info}, S1 = S#xref{modules = dict:store(M, XMod1, S#xref.modules)}, - {ok, [M], DBad, take_down(S1)}. + {ok, [M], take_down(S1)}. + +check_module(XMod, State) -> + M = XMod#xref_mod.name, + case dict:find(M, State#xref.modules) of + {ok, OldXMod} -> + BF2 = module_file(XMod), + BF1 = module_file(OldXMod), + throw_error({module_clash, {M, BF1, BF2}}); + error -> + ok + end. depr_mod({Depr,Bad0}, X) -> %% Bad0 are badly formed deprecated attributes. @@ -992,9 +1050,6 @@ no_info(X, L, LC, XC, EE, Unres, NoCalls, NoUnresCalls) -> %% Note: this is overwritten in do_set_up(): {no_inter_function_calls, no_elements(EE)}]. -inter_graph(Pid, X, L, LC, XC, CallAt) -> - Pid ! {self(), inter_graph(X, L, LC, XC, CallAt)}. - %% Inter Call Graph. %inter_graph(_X, _L, _LC, _XC, _CallAt) -> % {empty_set(), empty_set()}; @@ -1766,10 +1821,6 @@ tpack(T, I, L) -> message(true, What, Arg) -> case What of - reading_beam -> - io:format("~ts... ", Arg); - skipped_beam -> - io:format("skipped (no debug information)~n", Arg); no_debug_info -> io:format("Skipping ~ts (no debug information)~n", Arg); unresolved_summary1 -> @@ -1791,7 +1842,7 @@ message(true, What, Arg) -> set_up -> io:format("Setting up...", Arg); done -> - io:format("done~n", Arg); + io:format("done reading ~ts~n", Arg); error -> io:format("error~n", Arg); Else -> diff --git a/lib/tools/src/xref_utils.erl b/lib/tools/src/xref_utils.erl index f69aa70244..b0c168e018 100644 --- a/lib/tools/src/xref_utils.erl +++ b/lib/tools/src/xref_utils.erl @@ -47,8 +47,6 @@ -export([options/2]). --export([subprocess/2]). - -export([format_error/1]). -import(lists, [append/1, delete/2, filter/2, foldl/3, foreach/2, @@ -512,12 +510,6 @@ find_beam(Culprit) -> options(Options, Valid) -> split_options(Options, [], [], [], Valid). -subprocess(Fun, Opts) -> - Pid = spawn_opt(Fun, Opts), - receive - {Pid, Reply} -> Reply - end. - format_error({error, Module, Error}) -> Module:format_error(Error); format_error({file_error, FileName, Reason}) -> |