diff options
61 files changed, 2363 insertions, 1238 deletions
diff --git a/bootstrap/lib/compiler/ebin/compile.beam b/bootstrap/lib/compiler/ebin/compile.beam Binary files differindex b309544c39..8003d590c9 100644 --- a/bootstrap/lib/compiler/ebin/compile.beam +++ b/bootstrap/lib/compiler/ebin/compile.beam diff --git a/bootstrap/lib/kernel/ebin/inet.beam b/bootstrap/lib/kernel/ebin/inet.beam Binary files differindex 8eaf2fbe74..b771df5608 100644 --- a/bootstrap/lib/kernel/ebin/inet.beam +++ b/bootstrap/lib/kernel/ebin/inet.beam diff --git a/bootstrap/lib/kernel/ebin/local_tcp.beam b/bootstrap/lib/kernel/ebin/local_tcp.beam Binary files differindex 153aad2936..9ff39fd7fa 100644 --- a/bootstrap/lib/kernel/ebin/local_tcp.beam +++ b/bootstrap/lib/kernel/ebin/local_tcp.beam diff --git a/bootstrap/lib/kernel/ebin/local_udp.beam b/bootstrap/lib/kernel/ebin/local_udp.beam Binary files differindex 29c9b01faa..44a4bee902 100644 --- a/bootstrap/lib/kernel/ebin/local_udp.beam +++ b/bootstrap/lib/kernel/ebin/local_udp.beam diff --git a/bootstrap/lib/stdlib/ebin/erl_parse.beam b/bootstrap/lib/stdlib/ebin/erl_parse.beam Binary files differindex 9b04739d58..33a0753965 100644 --- a/bootstrap/lib/stdlib/ebin/erl_parse.beam +++ b/bootstrap/lib/stdlib/ebin/erl_parse.beam diff --git a/bootstrap/lib/stdlib/ebin/erl_pp.beam b/bootstrap/lib/stdlib/ebin/erl_pp.beam Binary files differindex b3343c9ccb..012c28ac81 100644 --- a/bootstrap/lib/stdlib/ebin/erl_pp.beam +++ b/bootstrap/lib/stdlib/ebin/erl_pp.beam diff --git a/bootstrap/lib/stdlib/ebin/supervisor.beam b/bootstrap/lib/stdlib/ebin/supervisor.beam Binary files differindex ba6a64d594..3ae911ab29 100644 --- a/bootstrap/lib/stdlib/ebin/supervisor.beam +++ b/bootstrap/lib/stdlib/ebin/supervisor.beam diff --git a/erts/doc/src/absform.xml b/erts/doc/src/absform.xml index bfabb7f042..0b04f8f70e 100644 --- a/erts/doc/src/absform.xml +++ b/erts/doc/src/absform.xml @@ -152,9 +152,11 @@ <list type="bulleted"> <item>If L is an atom literal, then Rep(L) = <c>{atom,LINE,L}</c>.</item> + <item>If L is a character literal, then + Rep(L) = <c>{char,LINE,L}</c>.</item> <item>If L is a float literal, then Rep(L) = <c>{float,LINE,L}</c>.</item> - <item>If L is an integer or character literal, then + <item>If L is an integer literal, then Rep(L) = <c>{integer,LINE,L}</c>.</item> <item>If L is a string literal consisting of the characters <c>C_1</c>, ..., <c>C_k</c>, then diff --git a/erts/doc/src/erlang.xml b/erts/doc/src/erlang.xml index e0c3fed0c2..fa13e4c142 100644 --- a/erts/doc/src/erlang.xml +++ b/erts/doc/src/erlang.xml @@ -2916,107 +2916,105 @@ os_prompt% </pre> <func> <name name="monitor" arity="2" clause_i="1"/> <name name="monitor" arity="2" clause_i="2"/> + <name name="monitor" arity="2" clause_i="3"/> <fsummary>Starts monitoring.</fsummary> <type name="registered_name"/> <type name="registered_process_identifier"/> <type name="monitor_process_identifier"/> + <type name="monitor_port_identifier"/> <desc> - <p>Send a monitor request of type <c><anno>Type</anno></c> to the - entity identified by <c><anno>Item</anno></c>. The caller of - <c>monitor/2</c> will later be notified by a monitor message on the - following format if the monitored state is changed:</p> + <p>Sends a monitor request of type <c><anno>Type</anno></c> to the + entity identified by <c><anno>Item</anno></c>. If the monitored entity + does not exist or when it dies, the caller of <c>monitor/2</c> will + be notified by a message on the following format:</p> <code type="none">{Tag, <anno>MonitorRef</anno>, <anno>Type</anno>, Object, Info}</code> <note><p>The monitor request is an asynchronous signal. That is, it takes time before the signal reaches its destination.</p></note> - <p>Valid <c><anno>Type</anno></c>s:</p> - <taglist> - <tag><marker id="monitor_process"/><c>process</c></tag> - <item> - <p>Monitor the existence of the process identified by - <c><anno>Item</anno></c>. Valid - <c><anno>Item</anno></c>s in combination with the - <c>process <anno>Type</anno></c> can be any of the following:</p> - <taglist> - <tag><c>pid()</c></tag> - <item> - <p>The process identifier of the process to monitor.</p> - </item> - <tag><c>{RegisteredName, Node}</c></tag> - <item> - <p>A tuple consisting of a registered name of a process and - a node name. The process residing on the node <c>Node</c> - with the registered name <c>{RegisteredName, Node}</c> will - be monitored.</p> - </item> - <tag><c>RegisteredName</c></tag> - <item> - <p>The process locally registered as <c>RegisteredName</c> - will become monitored.</p> - </item> - </taglist> - <note><p>When a registered name is used, the - process that has the registered name when the - monitor request reach its destination will be monitored. - The monitor is not effected if the registered name is - unregistered, or unregistered and later registered on another - process.</p></note> - <p>The monitor is triggered either when the monitored process - terminates, is non existing, or if the connection to it is - lost. In the case the connection to it is lost, we do not know - if it still exist or not. After this type of monitor has been - triggered, the monitor is automatically removed.</p> - <p>When the monitor is triggered a <c>'DOWN'</c> message is - sent to the monitoring process. A <c>'DOWN'</c> message has - the following pattern:</p> - <code type="none">{'DOWN', MonitorRef, Type, Object, Info}</code> - <p>Here <c>MonitorRef</c> and <c>Type</c> are the same as - described earlier, and:</p> - <taglist> - <tag><c>Object</c></tag> - <item> - <p>equals:</p> - <taglist> - <tag><c><anno>Item</anno></c></tag> - <item>If <c><anno>Item</anno></c> is specified by a - process identifier.</item> - <tag><c>{RegisteredName, Node}</c></tag> - <item>If <c><anno>Item</anno></c> is specified as - <c>RegisteredName</c>, or <c>{RegisteredName, Node}</c> - where <c>Node</c> corresponds to the node that the - monitored process resides on.</item> - </taglist> - </item> - <tag><c>Info</c></tag> - <item> - <p>Either the exit reason of the process, <c>noproc</c> - (non-existing process), or <c>noconnection</c> (no - connection to the node where the monitored process - resides).</p></item> - </taglist> - <p>The monitoring is turned off when the <c>'DOWN'</c> - message is sent or when - <seealso marker="#demonitor/1">demonitor/1</seealso> - is called.</p> - <p>If an attempt is made to monitor a process on an older node - (where remote process monitoring is not implemented or - where remote process monitoring by registered name is not - implemented), the call fails with <c>badarg</c>.</p> - <note> - <p>The format of the <c>'DOWN'</c> message changed in ERTS - version 5.2 (OTP R9B) for monitoring - <em>by registered name</em>. Element <c>Object</c> of - the <c>'DOWN'</c> message could in earlier versions - sometimes be the process identifier of the monitored process and sometimes - be the registered name. Now element <c>Object</c> is - always a tuple consisting of the registered name and - the node name. Processes on new nodes (ERTS version 5.2 - or higher) always get <c>'DOWN'</c> messages on - the new format even if they are monitoring processes on old - nodes. Processes on old nodes always get <c>'DOWN'</c> - messages on the old format.</p> - </note> - </item> - <tag><marker id="monitor_time_offset"/><c>time_offset</c></tag> + + <p><c><anno>Type</anno></c> can be one of the following atoms: + <c>process</c>, <c>port</c> or <c>time_offset</c>.</p> + + <p>A monitor is triggered only once, after that it is removed from + both monitoring process and the monitored entity. + Monitors are fired when the monitored process or port terminates, + does not exist at the moment of creation, or if the connection to + it is lost. In the case with connection, we lose knowledge about + the fact if it still exists or not. The monitoring is also turned off + when <seealso marker="#demonitor/1">demonitor/1</seealso> + is called.</p> + + <p>When monitoring by name please note, that the <c>RegisteredName</c> + is resolved to <c>pid()</c> or <c>port()</c> only once + at the moment of monitor instantiation, later changes to the name + registration will not affect the existing monitor.</p> + + <p>When a monitor is triggered, a <c>'DOWN'</c> message that has the + following pattern <c>{'DOWN', MonitorRef, Type, Object, Info}</c> + is sent to the monitoring process.</p> + + <p>In monitor message <c>MonitorRef</c> and <c>Type</c> are the same as + described earlier, and:</p> + <taglist> + <tag><c>Object</c></tag> + <item> + <p>The monitored entity, which triggered the event. When monitoring + a local process or port, <c>Object</c> will be equal to the + <c>pid()</c> or <c>port()</c> that was being monitored. When + monitoring process or port by name, <c>Object</c> will have format + <c>{RegisteredName, Node}</c> where <c>RegisteredName</c> is the + name which has been used with <c>monitor/2</c> call and + <c>Node</c> is local or remote node name (for ports monitored by + name, <c>Node</c> is always local node name).</p> + </item> + <tag><c>Info</c></tag> + <item> + <p>Either the exit reason of the process, <c>noproc</c> + (process or port did not exist at the time of monitor creation), + or <c>noconnection</c> (no connection to the node where the + monitored process resides). </p></item> + </taglist> + + <p>If an attempt is made to monitor a process on an older node + (where remote process monitoring is not implemented or + where remote process monitoring by registered name is not + implemented), the call fails with <c>badarg</c>.</p> + <note> + <p>The format of the <c>'DOWN'</c> message changed in ERTS + version 5.2 (OTP R9B) for monitoring + <em>by registered name</em>. Element <c>Object</c> of + the <c>'DOWN'</c> message could in earlier versions + sometimes be the process identifier of the monitored process and sometimes + be the registered name. Now element <c>Object</c> is + always a tuple consisting of the registered name and + the node name. Processes on new nodes (ERTS version 5.2 + or higher) always get <c>'DOWN'</c> messages on + the new format even if they are monitoring processes on old + nodes. Processes on old nodes always get <c>'DOWN'</c> + messages on the old format.</p> + </note> + + <taglist> + <tag>Monitoring a <marker id="monitor_process"/><c>process</c></tag> + <item> + <p>Creates monitor between the current process and another + process identified by <c><anno>Item</anno></c>, which can be a + <c>pid()</c> (local or remote), an atom <c>RegisteredName</c> or + a tuple <c>{RegisteredName, Node}</c> for a registered process, + located elsewhere.</p> + </item> + + <tag>Monitoring a <marker id="monitor_port"/><c>port</c></tag> + <item> + <p>Creates monitor between the current process and a port + identified by <c><anno>Item</anno></c>, which can be a + <c>port()</c> (only local), an atom <c>RegisteredName</c> or + a tuple <c>{RegisteredName, Node}</c> for a registered port, + located on this node. Note, that attempt to monitor a remote port + will result in <c>badarg</c>.</p> + </item> + + <tag>Monitoring a + <marker id="monitor_time_offset"/><c>time_offset</c></tag> <item> <p>Monitor changes in <seealso marker="#time_offset/0">time offset</seealso> @@ -3072,15 +3070,17 @@ os_prompt% </pre> Note that you can observe the change of the time offset when calling <c>erlang:time_offset()</c> before you get the <c>'CHANGE'</c> message.</p> - </item> </taglist> + <p>Making several calls to <c>monitor/2</c> for the same - <c><anno>Item</anno></c> and/or <c><anno>Type</anno></c> is not - an error; it results in as many independent monitoring instances.</p> + <c><anno>Item</anno></c> and/or <c><anno>Type</anno></c> is not + an error; it results in as many independent monitoring instances.</p> + <p>The monitor functionality is expected to be extended. That is, - other <c><anno>Type</anno></c>s and <c><anno>Item</anno></c>s - are expected to be supported in a future release.</p> + other <c><anno>Type</anno></c>s and <c><anno>Item</anno></c>s + are expected to be supported in a future release.</p> + <note> <p>If or when <c>monitor/2</c> is extended, other possible values for <c>Tag</c>, <c>Object</c> and @@ -4150,6 +4150,22 @@ os_prompt% </pre> <func> <name name="port_info" arity="2" clause_i="8"/> + <fsummary>Which processes are monitoring this port.</fsummary> + <desc> + <p>Returns list of pids that are monitoring given port at the + moment.</p> + <p>If the port identified by <c><anno>Port</anno></c> is not open, + <c>undefined</c> is returned. If the port is closed and the + calling process was previously linked to the port, the exit + signal from the port is guaranteed to be delivered before + <c>port_info/2</c> returns <c>undefined</c>.</p> + <p>Failure: <c>badarg</c> if <c><anno>Port</anno></c> is not a local + port identifier, or an atom.</p> + </desc> + </func> + + <func> + <name name="port_info" arity="2" clause_i="9"/> <fsummary>Information about the name of a port.</fsummary> <desc> <p><c><anno>Name</anno></c> is the command name set by @@ -4165,7 +4181,7 @@ os_prompt% </pre> </func> <func> - <name name="port_info" arity="2" clause_i="9"/> + <name name="port_info" arity="2" clause_i="10"/> <fsummary>Information about the OS pid of a port.</fsummary> <desc> <p><c><anno>OsPid</anno></c> is the process identifier (or equivalent) @@ -4184,7 +4200,7 @@ os_prompt% </pre> </func> <func> - <name name="port_info" arity="2" clause_i="10"/> + <name name="port_info" arity="2" clause_i="11"/> <fsummary>Information about the output of a port.</fsummary> <desc> <p><c><anno>Bytes</anno></c> is the total number of bytes written @@ -4203,7 +4219,7 @@ os_prompt% </pre> </func> <func> - <name name="port_info" arity="2" clause_i="11"/> + <name name="port_info" arity="2" clause_i="12"/> <fsummary>Information about the parallelism hint of a port.</fsummary> <desc> <p><c><anno>Boolean</anno></c> corresponds to the port parallelism @@ -4214,7 +4230,7 @@ os_prompt% </pre> </func> <func> - <name name="port_info" arity="2" clause_i="12"/> + <name name="port_info" arity="2" clause_i="13"/> <fsummary>Information about the queue size of a port.</fsummary> <desc> <p><c><anno>Bytes</anno></c> is the total number @@ -4231,7 +4247,7 @@ os_prompt% </pre> </func> <func> - <name name="port_info" arity="2" clause_i="13"/> + <name name="port_info" arity="2" clause_i="14"/> <fsummary>Information about the registered name of a port.</fsummary> <desc> <p><c><anno>RegisteredName</anno></c> is the registered name of @@ -4865,10 +4881,19 @@ os_prompt% </pre> <p>A list of monitors (started by <c>monitor/2</c>) that are active for the process. For a local process monitor or a remote process monitor by a process - identifier, the list item is <c>{process, <anno>Pid</anno>}</c>. - For a remote process - monitor by name, the list item is - <c>{process, {<anno>RegName</anno>, <anno>Node</anno>}}</c>.</p> + identifier, the list consists of:</p> + <taglist> + <tag><c>{process, <anno>Pid</anno>}</c></tag> + <item>Process is monitored by pid.</item> + <tag><c>{process, {<anno>RegName</anno>, <anno>Node</anno>}}</c></tag> + <item>Local or remote process is monitored by name.</item> + <tag><c>{port, PortId}</c></tag> + <item>Local port is monitored by port id.</item> + <tag><c>{port, {<anno>RegName</anno>, <anno>Node</anno>}}</c></tag> + <item>Local port is monitored by name. Please note, that + remote port monitors are not supported, so <c>Node</c> will + always be the local node name.</item> + </taglist> </item> <tag><c>{message_queue_data, <anno>MQD</anno>}</c></tag> <item> diff --git a/erts/emulator/beam/bif.c b/erts/emulator/beam/bif.c index b18910e2c7..fc14061a44 100644 --- a/erts/emulator/beam/bif.c +++ b/erts/emulator/beam/bif.c @@ -282,20 +282,17 @@ res_no_proc: { } } -#define ERTS_DEMONITOR_FALSE 2 -#define ERTS_DEMONITOR_TRUE 1 -#define ERTS_DEMONITOR_BADARG 0 -#define ERTS_DEMONITOR_YIELD_TRUE -1 -#define ERTS_DEMONITOR_INTERNAL_ERROR -2 - -static int +/* This function is allowed to return range of values handled by demonitor/1-2 + * Namely: atoms true, false, yield, internal_error, badarg or THE_NON_VALUE + */ +static Eterm remote_demonitor(Process *c_p, DistEntry *dep, Eterm ref, Eterm to) { ErtsDSigData dsd; ErtsMonitor *dmon; ErtsMonitor *mon; int code; - int res; + Eterm res = am_false; #ifndef ERTS_SMP int stale_mon = 0; #endif @@ -328,7 +325,7 @@ remote_demonitor(Process *c_p, DistEntry *dep, Eterm ref, Eterm to) mon = erts_remove_monitor(&ERTS_P_MONITORS(c_p), ref); erts_smp_proc_unlock(c_p, ERTS_PROC_LOCK_LINK); - res = ERTS_DEMONITOR_TRUE; + res = am_true; break; case ERTS_DSIG_PREP_CONNECTED: @@ -352,7 +349,7 @@ remote_demonitor(Process *c_p, DistEntry *dep, Eterm ref, Eterm to) * This is possible when smp support is enabled. * 'DOWN' message just arrived. */ - res = ERTS_DEMONITOR_TRUE; + res = am_true; } else { /* @@ -367,16 +364,13 @@ remote_demonitor(Process *c_p, DistEntry *dep, Eterm ref, Eterm to) : mon->pid), ref, 0); - res = (code == ERTS_DSIG_SEND_YIELD - ? ERTS_DEMONITOR_YIELD_TRUE - : ERTS_DEMONITOR_TRUE); + res = (code == ERTS_DSIG_SEND_YIELD ? am_yield : am_true); erts_destroy_monitor(dmon); - } break; default: ASSERT(! "Invalid dsig prepare result"); - return ERTS_DEMONITOR_INTERNAL_ERROR; + return am_internal_error; } #ifndef ERTS_SMP @@ -404,27 +398,96 @@ remote_demonitor(Process *c_p, DistEntry *dep, Eterm ref, Eterm to) return res; } -static int demonitor(Process *c_p, Eterm ref, Eterm *multip) +static ERTS_INLINE void +demonitor_local_process(Process *c_p, Eterm ref, Eterm to, Eterm *res) +{ + Process *rp = erts_pid2proc_opt(c_p, + ERTS_PROC_LOCK_MAIN|ERTS_PROC_LOCK_LINK, + to, + ERTS_PROC_LOCK_LINK, + ERTS_P2P_FLG_ALLOW_OTHER_X); + ErtsMonitor *mon = erts_remove_monitor(&ERTS_P_MONITORS(c_p), ref); + +#ifndef ERTS_SMP + ASSERT(mon); +#else + if (!mon) + *res = am_false; + else +#endif + { + *res = am_true; + erts_destroy_monitor(mon); + } + if (rp) { + ErtsMonitor *rmon; + rmon = erts_remove_monitor(&ERTS_P_MONITORS(rp), ref); + if (rp != c_p) + erts_smp_proc_unlock(rp, ERTS_PROC_LOCK_LINK); + if (rmon != NULL) + erts_destroy_monitor(rmon); + } + else { + ERTS_SMP_ASSERT_IS_NOT_EXITING(c_p); + } +} + +static ERTS_INLINE BIF_RETTYPE +demonitor_local_port(Process *origin, Eterm ref, Eterm target) { - ErtsMonitor *mon = NULL; /* The monitor entry to delete */ - Process *rp; /* Local target process */ - Eterm to = NIL; /* Monitor link traget */ - DistEntry *dep = NULL; /* Target's distribution entry */ - int deref_de = 0; - int res; - int unlock_link = 1; + BIF_RETTYPE res = am_false; + Port *port = erts_port_lookup_raw(target); + + if (!port) { + BIF_ERROR(origin, BADARG); + } + erts_smp_proc_unlock(origin, ERTS_PROC_LOCK_LINK); + + if (port) { + Eterm trap_ref; + switch (erts_port_demonitor(origin, ERTS_PORT_DEMONITOR_NORMAL, + port, ref, &trap_ref)) { + case ERTS_PORT_OP_DROPPED: + case ERTS_PORT_OP_BADARG: + break; + case ERTS_PORT_OP_SCHEDULED: + BIF_TRAP3(await_port_send_result_trap, origin, trap_ref, + am_busy_port, am_true); + /* the busy_port atom will never be returned, because it cannot be + * returned from erts_port_(de)monitor, but just in case if in future + * internal API changes - you may see this atom */ + default: + break; + } + } + else { + ERTS_SMP_ASSERT_IS_NOT_EXITING(origin); + } + BIF_RET(res); +} +/* Can return atom true, false, yield, internal_error, badarg or + * THE_NON_VALUE if error occured or trap has been set up + */ +static +BIF_RETTYPE demonitor(Process *c_p, Eterm ref, Eterm *multip) +{ + ErtsMonitor *mon = NULL; /* The monitor entry to delete */ + Eterm to = NIL; /* Monitor link traget */ + DistEntry *dep = NULL; /* Target's distribution entry */ + int deref_de = 0; + BIF_RETTYPE res = am_false; + int unlock_link = 1; erts_smp_proc_lock(c_p, ERTS_PROC_LOCK_LINK); if (is_not_internal_ref(ref)) { - res = ERTS_DEMONITOR_BADARG; + res = am_badarg; goto done; /* Cannot be this monitor's ref */ } mon = erts_lookup_monitor(ERTS_P_MONITORS(c_p), ref); if (!mon) { - res = ERTS_DEMONITOR_FALSE; goto done; } @@ -432,70 +495,50 @@ static int demonitor(Process *c_p, Eterm ref, Eterm *multip) case MON_TIME_OFFSET: *multip = am_true; erts_demonitor_time_offset(ref); - res = ERTS_DEMONITOR_TRUE; + res = am_true; break; case MON_ORIGIN: to = mon->pid; *multip = am_false; if (is_atom(to)) { - /* Monitoring a name at node to */ - ASSERT(is_node_name_atom(to)); - dep = erts_sysname_to_connected_dist_entry(to); - ASSERT(dep != erts_this_dist_entry); - if (dep) - deref_de = 1; + /* Monitoring a name at node to */ + ASSERT(is_node_name_atom(to)); + dep = erts_sysname_to_connected_dist_entry(to); + ASSERT(dep != erts_this_dist_entry); + if (dep) + deref_de = 1; + } else if (is_port(to)) { + if (port_dist_entry(to) != erts_this_dist_entry) { + goto badarg; + } + res = demonitor_local_port(c_p, ref, to); + unlock_link = 0; + goto done; } else { - ASSERT(is_pid(to)); - dep = pid_dist_entry(to); + ASSERT(is_pid(to)); + dep = pid_dist_entry(to); } if (dep != erts_this_dist_entry) { - res = remote_demonitor(c_p, dep, ref, to); - /* remote_demonitor() unlocks link lock on c_p */ - unlock_link = 0; + res = remote_demonitor(c_p, dep, ref, to); + /* remote_demonitor() unlocks link lock on c_p */ + unlock_link = 0; } else { /* Local monitor */ - if (deref_de) { - deref_de = 0; - erts_deref_dist_entry(dep); - } - dep = NULL; - rp = erts_pid2proc_opt(c_p, - ERTS_PROC_LOCK_MAIN|ERTS_PROC_LOCK_LINK, - to, - ERTS_PROC_LOCK_LINK, - ERTS_P2P_FLG_ALLOW_OTHER_X); - mon = erts_remove_monitor(&ERTS_P_MONITORS(c_p), ref); -#ifndef ERTS_SMP - ASSERT(mon); -#else - if (!mon) - res = ERTS_DEMONITOR_FALSE; - else -#endif - { - res = ERTS_DEMONITOR_TRUE; - erts_destroy_monitor(mon); - } - if (rp) { - ErtsMonitor *rmon; - rmon = erts_remove_monitor(&ERTS_P_MONITORS(rp), ref); - if (rp != c_p) - erts_smp_proc_unlock(rp, ERTS_PROC_LOCK_LINK); - if (rmon != NULL) - erts_destroy_monitor(rmon); - } - else { - ERTS_SMP_ASSERT_IS_NOT_EXITING(c_p); - } - + if (deref_de) { + deref_de = 0; + erts_deref_dist_entry(dep); + } + dep = NULL; + demonitor_local_process(c_p, ref, to, &res); } break; - default: - res = ERTS_DEMONITOR_BADARG; + default /* case */ : +badarg: + res = am_badarg; /* will be converted to error by caller */ *multip = am_false; break; } - done: +done: if (unlock_link) erts_smp_proc_unlock(c_p, ERTS_PROC_LOCK_LINK); @@ -506,21 +549,20 @@ static int demonitor(Process *c_p, Eterm ref, Eterm *multip) } ERTS_SMP_LC_ASSERT(ERTS_PROC_LOCK_MAIN == erts_proc_lc_my_proc_locks(c_p)); - return res; + BIF_RET(res); } BIF_RETTYPE demonitor_1(BIF_ALIST_1) { Eterm multi; switch (demonitor(BIF_P, BIF_ARG_1, &multi)) { - case ERTS_DEMONITOR_FALSE: - case ERTS_DEMONITOR_TRUE: - BIF_RET(am_true); - case ERTS_DEMONITOR_YIELD_TRUE: - ERTS_BIF_YIELD_RETURN(BIF_P, am_true); - case ERTS_DEMONITOR_BADARG: - BIF_ERROR(BIF_P, BADARG); - case ERTS_DEMONITOR_INTERNAL_ERROR: + case am_false: + case am_true: BIF_RET(am_true); + case THE_NON_VALUE: BIF_RET(THE_NON_VALUE); + case am_yield: ERTS_BIF_YIELD_RETURN(BIF_P, am_true); + case am_badarg: BIF_ERROR(BIF_P, BADARG); + + case am_internal_error: default: ASSERT(! "demonitor(): internal error"); BIF_ERROR(BIF_P, EXC_INTERNAL_ERROR); @@ -529,11 +571,11 @@ BIF_RETTYPE demonitor_1(BIF_ALIST_1) BIF_RETTYPE demonitor_2(BIF_ALIST_2) { - Eterm res = am_true; - Eterm multi = am_false; - int info = 0; - int flush = 0; - Eterm list = BIF_ARG_2; + BIF_RETTYPE res = am_true; + Eterm multi = am_false; + int info = 0; + int flush = 0; + Eterm list = BIF_ARG_2; while (is_list(list)) { Eterm* consp = list_val(list); @@ -554,24 +596,27 @@ BIF_RETTYPE demonitor_2(BIF_ALIST_2) goto badarg; switch (demonitor(BIF_P, BIF_ARG_1, &multi)) { - case ERTS_DEMONITOR_FALSE: + case THE_NON_VALUE: + /* If other error occured or trap has been set up - pass through */ + BIF_RET(THE_NON_VALUE); + case am_false: if (info) res = am_false; if (flush) { - flush_messages: +flush_messages: BIF_TRAP3(flush_monitor_messages_trap, BIF_P, BIF_ARG_1, multi, res); } - case ERTS_DEMONITOR_TRUE: + case am_true: if (multi == am_true && flush) goto flush_messages; BIF_RET(res); - case ERTS_DEMONITOR_YIELD_TRUE: + case am_yield: ERTS_BIF_YIELD_RETURN(BIF_P, am_true); - case ERTS_DEMONITOR_BADARG: - badarg: + case am_badarg: +badarg: BIF_ERROR(BIF_P, BADARG); - case ERTS_DEMONITOR_INTERNAL_ERROR: + case am_internal_error: default: ASSERT(! "demonitor(): internal error"); BIF_ERROR(BIF_P, EXC_INTERNAL_ERROR); @@ -615,14 +660,13 @@ erts_queue_monitor_message(Process *p, erts_queue_message(p, *p_locksp, msgp, tup, am_system); } -static BIF_RETTYPE +static Eterm local_pid_monitor(Process *p, Eterm target, Eterm mon_ref, int boolean) { - BIF_RETTYPE ret; - Process *rp; + Eterm ret = mon_ref; + Process *rp; ErtsProcLocks p_locks = ERTS_PROC_LOCK_MAIN|ERTS_PROC_LOCK_LINK; - ERTS_BIF_PREP_RET(ret, mon_ref); if (target == p->common.id) { return ret; } @@ -658,40 +702,112 @@ local_pid_monitor(Process *p, Eterm target, Eterm mon_ref, int boolean) } static BIF_RETTYPE -local_name_monitor(Process *p, Eterm target_name) +local_port_monitor(Process *origin, Eterm target) { - BIF_RETTYPE ret; - Eterm mon_ref; - ErtsProcLocks p_locks = ERTS_PROC_LOCK_MAIN|ERTS_PROC_LOCK_LINK; - Process *rp; + BIF_RETTYPE ref = erts_make_ref(origin); + Port *port = erts_sig_lookup_port(origin, target); + ErtsProcLocks p_locks = ERTS_PROC_LOCK_MAIN; - mon_ref = erts_make_ref(p); - ERTS_BIF_PREP_RET(ret, mon_ref); - erts_smp_proc_lock(p, ERTS_PROC_LOCK_LINK); - rp = erts_whereis_process(p, p_locks, target_name, ERTS_PROC_LOCK_LINK, - ERTS_P2P_FLG_ALLOW_OTHER_X); - if (!rp) { - DeclareTmpHeap(lhp,3,p); + if (!port) { +res_no_proc: + /* Send the DOWN message immediately. Ref is made on the fly because + * caller has never seen it yet. */ + erts_queue_monitor_message(origin, &p_locks, ref, + am_port, target, am_noproc); + } + else { + switch (erts_port_monitor(origin, port, target, &ref)) { + case ERTS_PORT_OP_DROPPED: + case ERTS_PORT_OP_BADARG: + goto res_no_proc; + case ERTS_PORT_OP_SCHEDULED: + BIF_TRAP3(await_port_send_result_trap, origin, ref, + am_busy_port, ref); + /* the busy_port atom will never be returned, because it cannot be + * returned from erts_port_monitor, but just in case if in future + * internal API changes - you may see this atom */ + default: + break; + } + } + erts_smp_proc_unlock(origin, p_locks & ~ERTS_PROC_LOCK_MAIN); + BIF_RET(ref); +} + +/* Type = process | port :: atom(), 1st argument passed to erlang:monitor/2 + */ +static BIF_RETTYPE +local_name_monitor(Process *self, Eterm type, Eterm target_name) +{ + BIF_RETTYPE ret = erts_make_ref(self); + + ErtsProcLocks p_locks = ERTS_PROC_LOCK_MAIN | ERTS_PROC_LOCK_LINK; + Process *proc = NULL; + Port *port = NULL; + + erts_smp_proc_lock(self, ERTS_PROC_LOCK_LINK); + + erts_whereis_name(self, p_locks, target_name, + &proc, ERTS_PROC_LOCK_LINK, + ERTS_P2P_FLG_ALLOW_OTHER_X, + &port, 0); + + /* If the name is not registered, + * or if we asked for proc and got a port, + * or if we asked for port and got a proc, + * we just send the 'DOWN' message. + */ + if ((!proc && !port) || + (type == am_process && port) || + (type == am_port && proc)) { + DeclareTmpHeap(lhp,3,self); Eterm item; - UseTmpHeap(3,p); - erts_smp_proc_unlock(p, ERTS_PROC_LOCK_LINK); + UseTmpHeap(3,self); + + erts_smp_proc_unlock(self, ERTS_PROC_LOCK_LINK); p_locks &= ~ERTS_PROC_LOCK_LINK; + item = TUPLE2(lhp, target_name, erts_this_dist_entry->sysname); - erts_queue_monitor_message(p, &p_locks, - mon_ref, am_process, item, am_noproc); - UnUseTmpHeap(3,p); - } - else if (rp != p) { - erts_add_monitor(&ERTS_P_MONITORS(p), MON_ORIGIN, mon_ref, rp->common.id, - target_name); - erts_add_monitor(&ERTS_P_MONITORS(rp), MON_TARGET, mon_ref, p->common.id, - target_name); - erts_smp_proc_unlock(rp, ERTS_PROC_LOCK_LINK); + erts_queue_monitor_message(self, &p_locks, + ret, + type, /* = process|port :: atom() */ + item, am_noproc); + UnUseTmpHeap(3,self); + } + else if (port) { + erts_smp_proc_unlock(self, p_locks & ~ERTS_PROC_LOCK_MAIN); + p_locks &= ~ERTS_PROC_LOCK_MAIN; + + switch (erts_port_monitor(self, port, target_name, &ret)) { + case ERTS_PORT_OP_DONE: + return ret; + case ERTS_PORT_OP_SCHEDULED: { /* Scheduled a signal */ + ASSERT(is_internal_ref(ret)); + BIF_TRAP3(await_port_send_result_trap, self, + ret, am_true, ret); + /* bif_trap returns */ + } break; + default: + goto badarg; + } + } + else if (proc != self) { + erts_add_monitor(&ERTS_P_MONITORS(self), MON_ORIGIN, ret, + proc->common.id, target_name); + erts_add_monitor(&ERTS_P_MONITORS(proc), MON_TARGET, ret, + self->common.id, target_name); + erts_smp_proc_unlock(proc, ERTS_PROC_LOCK_LINK); } - erts_smp_proc_unlock(p, p_locks & ~ERTS_PROC_LOCK_MAIN); - - return ret; + if (p_locks) { + erts_smp_proc_unlock(self, p_locks & ~ERTS_PROC_LOCK_MAIN); + } + BIF_RET(ret); +badarg: + if (p_locks) { + erts_smp_proc_unlock(self, p_locks & ~ERTS_PROC_LOCK_MAIN); + } + BIF_ERROR(self, BADARG); } static BIF_RETTYPE @@ -758,7 +874,7 @@ remote_monitor(Process *p, Eterm bifarg1, Eterm bifarg2, break; } - return ret; + BIF_RET(ret); } BIF_RETTYPE monitor_2(BIF_ALIST_2) @@ -772,8 +888,9 @@ BIF_RETTYPE monitor_2(BIF_ALIST_2) switch (BIF_ARG_1) { case am_time_offset: { Eterm ref; - if (BIF_ARG_2 != am_clock_service) - goto error; + if (BIF_ARG_2 != am_clock_service) { + goto badarg; + } ref = erts_make_ref(BIF_P); erts_smp_proc_lock(BIF_P, ERTS_PROC_LOCK_LINK); erts_add_monitor(&ERTS_P_MONITORS(BIF_P), MON_TIME_OFFSET, @@ -783,46 +900,57 @@ BIF_RETTYPE monitor_2(BIF_ALIST_2) BIF_RET(ref); } case am_process: + case am_port: break; default: - goto error; + goto badarg; } - if (is_internal_pid(target)) { - local_pid: - ret = local_pid_monitor(BIF_P, target, erts_make_ref(BIF_P), 0); - } else if (is_external_pid(target)) { + if (is_internal_pid(target) && BIF_ARG_1 == am_process) { +local_pid: + ret = local_pid_monitor(BIF_P, target, erts_make_ref(BIF_P), 0); + } else if (is_external_pid(target) && BIF_ARG_1 == am_process) { dep = external_pid_dist_entry(target); if (dep == erts_this_dist_entry) goto local_pid; ret = remote_monitor(BIF_P, BIF_ARG_1, BIF_ARG_2, dep, target, 0); + } else if (is_internal_port(target) && BIF_ARG_1 == am_port) { +local_port: + ret = local_port_monitor(BIF_P, target); + } else if (is_external_port(target) && BIF_ARG_1 == am_port) { + dep = external_port_dist_entry(target); + if (dep == erts_this_dist_entry) { + goto local_port; + } + goto badarg; /* No want remote port */ } else if (is_atom(target)) { - ret = local_name_monitor(BIF_P, target); + ret = local_name_monitor(BIF_P, BIF_ARG_1, target); } else if (is_tuple(target)) { Eterm *tp = tuple_val(target); Eterm remote_node; Eterm name; - if (arityval(*tp) != 2) - goto error; + if (arityval(*tp) != 2) { + goto badarg; + } remote_node = tp[2]; name = tp[1]; if (!is_atom(remote_node) || !is_atom(name)) { - goto error; + goto badarg; } if (!erts_is_alive && remote_node != am_Noname) { - goto error; /* Remote monitor from (this) undistributed node */ + goto badarg; /* Remote monitor from (this) undistributed node */ } dep = erts_sysname_to_connected_dist_entry(remote_node); if (dep == erts_this_dist_entry) { deref_de = 1; - ret = local_name_monitor(BIF_P, name); + ret = local_name_monitor(BIF_P, BIF_ARG_1, name); } else { if (dep) deref_de = 1; ret = remote_monitor(BIF_P, BIF_ARG_1, BIF_ARG_2, dep, name, 1); } } else { - error: +badarg: ERTS_BIF_PREP_ERROR(ret, BIF_P, BADARG); } if (deref_de) { diff --git a/erts/emulator/beam/erl_bif_info.c b/erts/emulator/beam/erl_bif_info.c index b410578d37..3fb866733c 100644 --- a/erts/emulator/beam/erl_bif_info.c +++ b/erts/emulator/beam/erl_bif_info.c @@ -361,8 +361,13 @@ erts_print_system_version(int to, void *arg, Process *c_p) } typedef struct { + /* {Entity,Node} = {monitor.Name,monitor.Pid} for external by name + * {Entity,Node} = {monitor.Pid,NIL} for external/external by pid + * {Entity,Node} = {monitor.Name,erlang:node()} for internal by name */ Eterm entity; Eterm node; + /* pid is actual target being monitored, no matter pid/port or name */ + Eterm pid; } MonitorInfo; typedef struct { @@ -420,21 +425,27 @@ static void collect_one_origin_monitor(ErtsMonitor *mon, void *vmicp) EXTEND_MONITOR_INFOS(micp); if (is_atom(mon->pid)) { /* external by name */ micp->mi[micp->mi_i].entity = mon->name; - micp->mi[micp->mi_i].node = mon->pid; - micp->sz += 3; /* need one 2-tuple */ + micp->mi[micp->mi_i].node = mon->pid; + micp->sz += 3; /* need one 2-tuple */ } else if (is_external_pid(mon->pid)) { /* external by pid */ micp->mi[micp->mi_i].entity = mon->pid; - micp->mi[micp->mi_i].node = NIL; - micp->sz += NC_HEAP_SIZE(mon->pid); + micp->mi[micp->mi_i].node = NIL; + micp->sz += NC_HEAP_SIZE(mon->pid); } else if (!is_nil(mon->name)) { /* internal by name */ micp->mi[micp->mi_i].entity = mon->name; - micp->mi[micp->mi_i].node = erts_this_dist_entry->sysname; - micp->sz += 3; /* need one 2-tuple */ + micp->mi[micp->mi_i].node = erts_this_dist_entry->sysname; + micp->sz += 3; /* need one 2-tuple */ } else { /* internal by pid */ micp->mi[micp->mi_i].entity = mon->pid; - micp->mi[micp->mi_i].node = NIL; + micp->mi[micp->mi_i].node = NIL; /* no additional heap space needed */ } + + /* have always pid at hand, to assist with figuring out if its a port or + * a process, when we monitored by name and process_info is requested. + * See: erl_bif_info.c:process_info_aux section for am_monitors */ + micp->mi[micp->mi_i].pid = mon->pid; + micp->mi_i++; micp->sz += 2 + 3; /* For a cons cell and a 2-tuple */ } @@ -1190,37 +1201,49 @@ process_info_aux(Process *BIF_P, case am_monitors: { MonitorInfoCollection mic; - int i; + int i; INIT_MONITOR_INFOS(mic); - erts_doforall_monitors(ERTS_P_MONITORS(rp),&collect_one_origin_monitor,&mic); - hp = HAlloc(BIF_P, 3 + mic.sz); + erts_doforall_monitors(ERTS_P_MONITORS(rp), + &collect_one_origin_monitor, &mic); + hp = HAlloc(BIF_P, 3 + mic.sz); res = NIL; for (i = 0; i < mic.mi_i; i++) { if (is_atom(mic.mi[i].entity)) { /* Monitor by name. - * Build {process, {Name, Node}} and cons it. + * Build {process|port, {Name, Node}} and cons it. */ Eterm t1, t2; + /* If pid is an atom, then it is a remote named monitor, which + has to be a process */ + Eterm m_type = is_port(mic.mi[i].pid) ? am_port : am_process; + ASSERT(is_pid(mic.mi[i].pid) + || is_port(mic.mi[i].pid) + || is_atom(mic.mi[i].pid)); t1 = TUPLE2(hp, mic.mi[i].entity, mic.mi[i].node); hp += 3; - t2 = TUPLE2(hp, am_process, t1); + t2 = TUPLE2(hp, m_type, t1); hp += 3; res = CONS(hp, t2, res); - hp += 2; + hp += 2; } else { - /* Monitor by pid. Build {process, Pid} and cons it. */ + /* Monitor by pid. Build {process|port, Pid} and cons it. */ Eterm t; Eterm pid = STORE_NC(&hp, &MSO(BIF_P), mic.mi[i].entity); - t = TUPLE2(hp, am_process, pid); + + Eterm m_type = is_port(mic.mi[i].pid) ? am_port : am_process; + ASSERT(is_pid(mic.mi[i].pid) + || is_port(mic.mi[i].pid)); + + t = TUPLE2(hp, m_type, pid); hp += 3; res = CONS(hp, t, res); - hp += 2; + hp += 2; } } - DESTROY_MONITOR_INFOS(mic); + DESTROY_MONITOR_INFOS(mic); break; } @@ -2880,7 +2903,8 @@ BIF_RETTYPE system_info_1(BIF_ALIST_1) */ Eterm -erts_bld_port_info(Eterm **hpp, ErlOffHeap *ohp, Uint *szp, Port *prt, Eterm item) +erts_bld_port_info(Eterm **hpp, ErlOffHeap *ohp, Uint *szp, Port *prt, + Eterm item) { Eterm res = THE_NON_VALUE; @@ -2928,8 +2952,8 @@ erts_bld_port_info(Eterm **hpp, ErlOffHeap *ohp, Uint *szp, Port *prt, Eterm ite Eterm item; INIT_MONITOR_INFOS(mic); - - erts_doforall_monitors(ERTS_P_MONITORS(prt), &collect_one_origin_monitor, &mic); + erts_doforall_monitors(ERTS_P_MONITORS(prt), + &collect_one_origin_monitor, &mic); if (szp) *szp += mic.sz; @@ -2938,14 +2962,16 @@ erts_bld_port_info(Eterm **hpp, ErlOffHeap *ohp, Uint *szp, Port *prt, Eterm ite res = NIL; for (i = 0; i < mic.mi_i; i++) { Eterm t; - item = STORE_NC(hpp, ohp, mic.mi[i].entity); - t = TUPLE2(*hpp, am_process, item); + Eterm m_type; + + item = STORE_NC(hpp, ohp, mic.mi[i].entity); + m_type = is_port(item) ? am_port : am_process; + t = TUPLE2(*hpp, m_type, item); *hpp += 3; res = CONS(*hpp, t, res); *hpp += 2; } - } - + } // hpp DESTROY_MONITOR_INFOS(mic); if (szp) { @@ -2953,6 +2979,32 @@ erts_bld_port_info(Eterm **hpp, ErlOffHeap *ohp, Uint *szp, Port *prt, Eterm ite goto done; } } + else if (item == am_monitored_by) { + MonitorInfoCollection mic; + int i; + Eterm item; + + INIT_MONITOR_INFOS(mic); + erts_doforall_monitors(ERTS_P_MONITORS(prt), + &collect_one_target_monitor, &mic); + if (szp) + *szp += mic.sz; + + if (hpp) { + res = NIL; + for (i = 0; i < mic.mi_i; ++i) { + item = STORE_NC(hpp, ohp, mic.mi[i].entity); + res = CONS(*hpp, item, res); + *hpp += 2; + } + } // hpp + DESTROY_MONITOR_INFOS(mic); + + if (szp) { + res = am_true; + goto done; + } + } else if (item == am_name) { int count = sys_strlen(prt->name); diff --git a/erts/emulator/beam/erl_bif_port.c b/erts/emulator/beam/erl_bif_port.c index fefa9d8391..90e78a9b0b 100644 --- a/erts/emulator/beam/erl_bif_port.c +++ b/erts/emulator/beam/erl_bif_port.c @@ -139,6 +139,12 @@ sig_lookup_port(Process *c_p, Eterm id_or_name) return lookup_port(c_p, id_or_name, ERTS_PORT_SFLGS_INVALID_DRIVER_LOOKUP); } +/* Non-inline copy of sig_lookup_port to be exported */ +Port *erts_sig_lookup_port(Process *c_p, Eterm id_or_name) +{ + return lookup_port(c_p, id_or_name, ERTS_PORT_SFLGS_INVALID_DRIVER_LOOKUP); +} + static ERTS_INLINE Port * data_lookup_port(Process *c_p, Eterm id_or_name) { diff --git a/erts/emulator/beam/erl_port.h b/erts/emulator/beam/erl_port.h index f0075ca2b9..f90844ccc8 100644 --- a/erts/emulator/beam/erl_port.h +++ b/erts/emulator/beam/erl_port.h @@ -361,6 +361,8 @@ Eterm erts_request_io_bytes(Process *c_p); #define ERTS_PORT_REDS_CONNECT (CONTEXT_REDS/200) #define ERTS_PORT_REDS_UNLINK (CONTEXT_REDS/200) #define ERTS_PORT_REDS_LINK (CONTEXT_REDS/200) +#define ERTS_PORT_REDS_MONITOR (CONTEXT_REDS/200) +#define ERTS_PORT_REDS_DEMONITOR (CONTEXT_REDS/200) #define ERTS_PORT_REDS_BADSIG (CONTEXT_REDS/200) #define ERTS_PORT_REDS_CONTROL (CONTEXT_REDS/100) #define ERTS_PORT_REDS_CALL (CONTEXT_REDS/50) @@ -850,16 +852,20 @@ void erts_port_resume_procs(Port *); struct binary; -#define ERTS_P2P_SIG_TYPE_BAD 0 -#define ERTS_P2P_SIG_TYPE_OUTPUT 1 -#define ERTS_P2P_SIG_TYPE_OUTPUTV 2 -#define ERTS_P2P_SIG_TYPE_CONNECT 3 -#define ERTS_P2P_SIG_TYPE_EXIT 4 -#define ERTS_P2P_SIG_TYPE_CONTROL 5 -#define ERTS_P2P_SIG_TYPE_CALL 6 -#define ERTS_P2P_SIG_TYPE_INFO 7 -#define ERTS_P2P_SIG_TYPE_LINK 8 -#define ERTS_P2P_SIG_TYPE_UNLINK 9 +enum { + ERTS_P2P_SIG_TYPE_BAD = 0, + ERTS_P2P_SIG_TYPE_OUTPUT = 1, + ERTS_P2P_SIG_TYPE_OUTPUTV = 2, + ERTS_P2P_SIG_TYPE_CONNECT = 3, + ERTS_P2P_SIG_TYPE_EXIT = 4, + ERTS_P2P_SIG_TYPE_CONTROL = 5, + ERTS_P2P_SIG_TYPE_CALL = 6, + ERTS_P2P_SIG_TYPE_INFO = 7, + ERTS_P2P_SIG_TYPE_LINK = 8, + ERTS_P2P_SIG_TYPE_UNLINK = 9, + ERTS_P2P_SIG_TYPE_MONITOR = 10, + ERTS_P2P_SIG_TYPE_DEMONITOR = 11 +}; #define ERTS_P2P_SIG_TYPE_BITS 4 #define ERTS_P2P_SIG_TYPE_MASK \ @@ -921,6 +927,15 @@ struct ErtsProc2PortSigData_ { struct { Eterm from; } unlink; + struct { + Eterm origin; /* who receives monitor event, pid */ + Eterm name; /* either name for named monitor, or port id */ + } monitor; + struct { + Eterm origin; /* who is at the other end of the monitor, pid */ + Eterm name; /* port id */ + Uint32 ref[ERTS_MAX_REF_NUMBERS]; /* box contents of a ref */ + } demonitor; } u; } ; @@ -1017,6 +1032,29 @@ ErtsPortOpResult erts_port_control(Process *, Port *, unsigned int, Eterm, Eterm ErtsPortOpResult erts_port_call(Process *, Port *, unsigned int, Eterm, Eterm *); ErtsPortOpResult erts_port_info(Process *, Port *, Eterm, Eterm *); +/* Creates monitor between Origin and Target. Ref must be initialized to + * a reference (ref may be rewritten to be used to serve additionally as a + * signal id). Name is atom if user monitors port by name or NIL */ +ErtsPortOpResult erts_port_monitor(Process *origin, Port *target, Eterm name, + Eterm *ref); + +typedef enum { + /* Normal demonitor rules apply with locking and reductions bump */ + ERTS_PORT_DEMONITOR_NORMAL = 1, + /* Relaxed demonitor rules when process is about to die, which means that + * pid lookup won't work, locks won't work, no reductions bump. */ + ERTS_PORT_DEMONITOR_ORIGIN_ON_DEATHBED = 2, +} ErtsDemonitorMode; + +/* Removes monitor between origin and target, identified by ref. + * origin_is_dying can be 0 (false, normal locking rules and reductions bump + * apply) or 1 (true, in case when we avoid origin locking) */ +ErtsPortOpResult erts_port_demonitor(Process *origin, ErtsDemonitorMode mode, + Port *target, Eterm ref, + Eterm *trap_ref); +/* defined in erl_bif_port.c */ +Port *erts_sig_lookup_port(Process *c_p, Eterm id_or_name); + int erts_port_output_async(Port *, Eterm, Eterm); /* diff --git a/erts/emulator/beam/erl_process.c b/erts/emulator/beam/erl_process.c index 5193be85b4..48f89d2bd7 100644 --- a/erts/emulator/beam/erl_process.c +++ b/erts/emulator/beam/erl_process.c @@ -12239,7 +12239,6 @@ static void doit_exit_monitor(ErtsMonitor *mon, void *vpcontext) ExitMonitorContext *pcontext = vpcontext; DistEntry *dep; ErtsMonitor *rmon; - Process *rp; switch (mon->type) { case MON_ORIGIN: @@ -12268,9 +12267,10 @@ static void doit_exit_monitor(ErtsMonitor *mon, void *vpcontext) erts_deref_dist_entry(dep); } } else { - ASSERT(is_pid(mon->pid)); - if (is_internal_pid(mon->pid)) { /* local by pid or name */ - rp = erts_pid2proc(NULL, 0, mon->pid, ERTS_PROC_LOCK_LINK); + ASSERT(is_pid(mon->pid) || is_port(mon->pid)); + /* if is local by pid or name */ + if (is_internal_pid(mon->pid)) { + Process *rp = erts_pid2proc(NULL, 0, mon->pid, ERTS_PROC_LOCK_LINK); if (!rp) { goto done; } @@ -12280,7 +12280,17 @@ static void doit_exit_monitor(ErtsMonitor *mon, void *vpcontext) goto done; } erts_destroy_monitor(rmon); - } else { /* remote by pid */ + } else if (is_internal_port(mon->pid)) { + /* Is a local port */ + Port *prt = erts_port_lookup_raw(mon->pid); + if (!prt) { + goto done; + } + erts_port_demonitor(pcontext->p, + ERTS_PORT_DEMONITOR_ORIGIN_ON_DEATHBED, + prt, mon->ref, NULL); + return; /* let erts_port_demonitor do the deletion */ + } else { /* remote by pid */ ASSERT(is_external_pid(mon->pid)); dep = external_pid_dist_entry(mon->pid); ASSERT(dep != NULL); @@ -12318,6 +12328,7 @@ static void doit_exit_monitor(ErtsMonitor *mon, void *vpcontext) erts_port_release(prt); } else if (is_internal_pid(mon->pid)) {/* local by name or pid */ Eterm watched; + Process *rp; DeclareTmpHeapNoproc(lhp,3); ErtsProcLocks rp_locks = (ERTS_PROC_LOCK_LINK | ERTS_PROC_LOCKS_MSG_SEND); diff --git a/erts/emulator/beam/io.c b/erts/emulator/beam/io.c index 01df5476db..cb8792dffa 100644 --- a/erts/emulator/beam/io.c +++ b/erts/emulator/beam/io.c @@ -1260,7 +1260,7 @@ typedef struct { /* * Try doing an immediate driver callback call from a process. If * this fail, the operation should be scheduled in the normal case... - * + * Returns: ok to do the call, or error (lock busy, does not exist, etc) */ static ERTS_INLINE ErtsTryImmDrvCallResult try_imm_drv_call(ErtsTryImmDrvCallState *sp) @@ -3074,6 +3074,250 @@ erts_port_link(Process *c_p, Port *prt, Eterm to, Eterm *refp) } static void +port_monitor_failure(Eterm port_id, Eterm origin, Eterm ref_DOWN) +{ + Process *origin_p; + ErtsProcLocks p_locks = ERTS_PROC_LOCK_LINK; + ASSERT(is_internal_pid(origin)); + + origin_p = erts_pid2proc(NULL, 0, origin, p_locks); + if (! origin_p) { return; } + + /* Send the DOWN message immediately. Ref is made on the fly because + * caller has never seen it yet. */ + erts_queue_monitor_message(origin_p, &p_locks, ref_DOWN, + am_port, port_id, am_noproc); + erts_smp_proc_unlock(origin_p, p_locks); +} + +/* Origin wants to monitor port Prt. State contains possible error, which has + * happened just before. Name is either NIL or an atom, if user monitors + * a port by name. Ref is premade reference that will be returned to user */ +static void +port_monitor(Port *prt, erts_aint32_t state, Eterm origin, + Eterm name, Eterm ref) +{ + Eterm name_or_nil = is_atom(name) ? name : NIL; + + ASSERT(is_pid(origin)); + ASSERT(is_atom(name) || is_port(name) || name == NIL); + ASSERT(is_internal_ref(ref)); + + if (!(state & ERTS_PORT_SFLGS_INVALID_LOOKUP)) { + ErtsProcLocks p_locks = ERTS_PROC_LOCK_LINK; + + Process *origin_p = erts_pid2proc(NULL, 0, origin, p_locks); + if (! origin_p) { + goto failure; + } + erts_add_monitor(&ERTS_P_MONITORS(origin_p), MON_ORIGIN, ref, + prt->common.id, name_or_nil); + erts_add_monitor(&ERTS_P_MONITORS(prt), MON_TARGET, ref, + origin, name_or_nil); + + erts_smp_proc_unlock(origin_p, p_locks); + } else { +failure: + port_monitor_failure(prt->common.id, origin, ref); + } +} + +static int +port_sig_monitor(Port *prt, erts_aint32_t state, int op, + ErtsProc2PortSigData *sigdp) +{ + Eterm hp[REF_THING_SIZE]; + Eterm ref = make_internal_ref(&hp); + write_ref_thing(hp, sigdp->ref[0], sigdp->ref[1], sigdp->ref[2]); + + if (op == ERTS_PROC2PORT_SIG_EXEC) { + /* erts_add_monitor call inside port_monitor will copy ref from hp */ + port_monitor(prt, state, + sigdp->u.monitor.origin, + sigdp->u.monitor.name, + ref); + } else { + port_monitor_failure(sigdp->u.monitor.name, + sigdp->u.monitor.origin, + ref); + } + if (sigdp->flags & ERTS_P2P_SIG_DATA_FLG_REPLY) { + port_sched_op_reply(sigdp->caller, sigdp->ref, am_true, prt); + } + return ERTS_PORT_REDS_MONITOR; +} + +/* Creates monitor between Origin and Target. Ref must be initialized to + * a reference (ref may be rewritten to be used to serve additionally as a + * signal id). Name is atom if user monitors port by name or NIL */ +ErtsPortOpResult +erts_port_monitor(Process *origin, Port *port, Eterm name, Eterm *refp) +{ + ErtsProc2PortSigData *sigdp; + ErtsTryImmDrvCallState try_call_state + = ERTS_INIT_TRY_IMM_DRV_CALL_STATE( + origin, port, ERTS_PORT_SFLGS_INVALID_LOOKUP, + 0, + 0, /* trap_ref is always set so !trap_ref always is false */ + am_monitor); + + ASSERT(origin); + ASSERT(port); + ASSERT(is_atom(name) || is_port(name)); + ASSERT(refp); + + switch (try_imm_drv_call(&try_call_state)) { + case ERTS_TRY_IMM_DRV_CALL_OK: + port_monitor(port, try_call_state.state, origin->common.id, name, *refp); + finalize_imm_drv_call(&try_call_state); + BUMP_REDS(origin, ERTS_PORT_REDS_MONITOR); + return ERTS_PORT_OP_DONE; + case ERTS_TRY_IMM_DRV_CALL_INVALID_PORT: + return ERTS_PORT_OP_BADARG; + default: + break; /* Schedule call instead... */ + } + + sigdp = erts_port_task_alloc_p2p_sig_data(); + sigdp->flags = ERTS_P2P_SIG_TYPE_MONITOR; + sigdp->u.monitor.origin = origin->common.id; + sigdp->u.monitor.name = name; /* either named monitor, or port id */ + + /* Ref contents will be initialized here */ + return erts_schedule_proc2port_signal(origin, port, origin->common.id, + refp, sigdp, 0, NULL, + port_sig_monitor); +} + +static void +port_demonitor_failure(Eterm port_id, Eterm origin, Eterm ref) +{ + Process *origin_p; + ErtsProcLocks rp_locks = ERTS_PROC_LOCK_LINK; + ErtsMonitor *mon1; + ASSERT(is_internal_pid(origin)); + + origin_p = erts_pid2proc(NULL, 0, origin, rp_locks); + if (! origin_p) { return; } + + /* do not send any DOWN messages, drop monitors on process */ + mon1 = erts_remove_monitor(&ERTS_P_MONITORS(origin_p), ref); + if (mon1 != NULL) { + erts_destroy_monitor(mon1); + } + + erts_smp_proc_unlock(origin_p, rp_locks); +} + +/* Origin wants to demonitor port Prt. State contains possible error, which has + * happened just before. Ref is reference to monitor */ +static void +port_demonitor(Port *port, erts_aint32_t state, Eterm origin, Eterm ref) +{ + ASSERT(port); + ASSERT(is_pid(origin)); + ASSERT(is_internal_ref(ref)); + + if (!(state & ERTS_PORT_SFLGS_INVALID_LOOKUP)) { + ErtsProcLocks p_locks = ERTS_PROC_LOCK_LINK; + Process *origin_p = erts_pid2proc(NULL, 0, origin, p_locks); + if (origin_p) { + ErtsMonitor *mon1 = erts_remove_monitor(&ERTS_P_MONITORS(origin_p), + ref); + if (mon1 != NULL) { + erts_destroy_monitor(mon1); + } + } + if (1) { + ErtsMonitor *mon2 = erts_remove_monitor(&ERTS_P_MONITORS(port), + ref); + if (mon2 != NULL) { + erts_destroy_monitor(mon2); + } + } + if (origin_p) { /* when origin is dying, it won't be found */ + erts_smp_proc_unlock(origin_p, p_locks); + } + } else { + port_demonitor_failure(port->common.id, origin, ref); + } +} + +static int +port_sig_demonitor(Port *prt, erts_aint32_t state, int op, + ErtsProc2PortSigData *sigdp) +{ + Eterm hp[REF_THING_SIZE]; + Eterm ref = make_internal_ref(&hp); + write_ref_thing(hp, sigdp->u.demonitor.ref[0], + sigdp->u.demonitor.ref[1], + sigdp->u.demonitor.ref[2]); + if (op == ERTS_PROC2PORT_SIG_EXEC) { + port_demonitor(prt, state, sigdp->u.demonitor.origin, ref); + } else { + port_demonitor_failure(sigdp->u.demonitor.name, + sigdp->u.demonitor.origin, + ref); + } + if (sigdp->flags & ERTS_P2P_SIG_DATA_FLG_REPLY) { + port_sched_op_reply(sigdp->caller, sigdp->ref, am_true, prt); + } + return ERTS_PORT_REDS_DEMONITOR; +} + +/* Removes monitor between origin and target, identified by ref. + * Mode defines normal or relaxed demonitor rules (process is at death) */ +ErtsPortOpResult erts_port_demonitor(Process *origin, ErtsDemonitorMode mode, + Port *target, Eterm ref, + Eterm *trap_ref) +{ + Process *c_p = mode == ERTS_PORT_DEMONITOR_NORMAL ? origin : NULL; + ErtsProc2PortSigData *sigdp; + ErtsTryImmDrvCallState try_call_state + = ERTS_INIT_TRY_IMM_DRV_CALL_STATE( + c_p, + target, ERTS_PORT_SFLGS_INVALID_LOOKUP, + 0, + !trap_ref, + am_demonitor); + + ASSERT(origin); + ASSERT(target); + ASSERT(is_internal_ref(ref)); + + switch (try_imm_drv_call(&try_call_state)) { + case ERTS_TRY_IMM_DRV_CALL_OK: + port_demonitor(target, try_call_state.state, origin->common.id, ref); + finalize_imm_drv_call(&try_call_state); + if (mode == ERTS_PORT_DEMONITOR_NORMAL) { + BUMP_REDS(origin, ERTS_PORT_REDS_DEMONITOR); + } + return ERTS_PORT_OP_DONE; + case ERTS_TRY_IMM_DRV_CALL_INVALID_PORT: + return ERTS_PORT_OP_BADARG; + default: + break; /* Schedule call instead... */ + } + + sigdp = erts_port_task_alloc_p2p_sig_data(); + sigdp->flags = ERTS_P2P_SIG_TYPE_DEMONITOR; + sigdp->u.demonitor.origin = origin->common.id; + sigdp->u.demonitor.name = target->common.id; + { + RefThing *reft = ref_thing_ptr(ref); + /* Start from 1 skip ref arity */ + sys_memcpy(sigdp->u.demonitor.ref, + internal_thing_ref_numbers(reft), + sizeof(sigdp->u.demonitor.ref)); + } + + /* Ref contents will be initialized here */ + return erts_schedule_proc2port_signal(c_p, target, origin->common.id, + trap_ref, sigdp, 0, NULL, + port_sig_demonitor); +} + +static void init_ack_send_reply(Port *port, Eterm resp) { @@ -3942,23 +4186,30 @@ erts_terminate_port(Port *pp) terminate_port(pp); } +static void port_fire_one_monitor(ErtsMonitor *mon, void *ctx0); static void sweep_one_monitor(ErtsMonitor *mon, void *vpsc) { - ErtsMonitor *rmon; - Process *rp; + switch (mon->type) { + case MON_ORIGIN: { + ErtsMonitor *rmon; + Process *rp; - ASSERT(mon->type == MON_ORIGIN); - ASSERT(is_internal_pid(mon->pid)); - rp = erts_pid2proc(NULL, 0, mon->pid, ERTS_PROC_LOCK_LINK); - if (!rp) { - goto done; - } - rmon = erts_remove_monitor(&ERTS_P_MONITORS(rp), mon->ref); - erts_smp_proc_unlock(rp, ERTS_PROC_LOCK_LINK); - if (rmon == NULL) { - goto done; + ASSERT(is_internal_pid(mon->pid)); + rp = erts_pid2proc(NULL, 0, mon->pid, ERTS_PROC_LOCK_LINK); + if (!rp) { + goto done; + } + rmon = erts_remove_monitor(&ERTS_P_MONITORS(rp), mon->ref); + erts_smp_proc_unlock(rp, ERTS_PROC_LOCK_LINK); + if (rmon == NULL) { + goto done; + } + erts_destroy_monitor(rmon); + } break; + case MON_TARGET: { + port_fire_one_monitor(mon, vpsc); /* forward call */ + } break; } - erts_destroy_monitor(rmon); done: erts_destroy_monitor(mon); } @@ -4039,6 +4290,43 @@ static void sweep_one_link(ErtsLink *lnk, void *vpsc) erts_destroy_link(lnk); } +static void +port_fire_one_monitor(ErtsMonitor *mon, void *ctx0) +{ + Process *origin; + ErtsProcLocks origin_locks; + + if (mon->type != MON_TARGET || ! is_pid(mon->pid)) { + return; + } + /* + * Proceed here if someone monitors us, we (port) are the target and + * origin is some process + */ + origin_locks = ERTS_PROC_LOCKS_MSG_SEND | ERTS_PROC_LOCK_LINK; + + origin = erts_pid2proc(NULL, 0, mon->pid, origin_locks); + if (origin) { + DeclareTmpHeapNoproc(lhp,3); + SweepContext *ctx = (SweepContext *)ctx0; + ErtsMonitor *rmon; + Eterm watched = (is_atom(mon->name) + ? TUPLE2(lhp, mon->name, erts_this_dist_entry->sysname) + : ctx->port->common.id); + + erts_queue_monitor_message(origin, &origin_locks, mon->ref, am_port, + watched, ctx->reason); + UnUseTmpHeapNoproc(3); + + rmon = erts_remove_monitor(&ERTS_P_MONITORS(origin), mon->ref); + erts_smp_proc_unlock(origin, origin_locks); + + if (rmon) { + erts_destroy_monitor(rmon); + } + } +} + /* 'from' is sending 'this_port' an exit signal, (this_port must be internal). * If reason is normal we don't do anything, *unless* from is our connected * process in which case we close the port. Any other reason kills the port. @@ -4050,39 +4338,40 @@ static void sweep_one_link(ErtsLink *lnk, void *vpsc) */ int -erts_deliver_port_exit(Port *p, Eterm from, Eterm reason, int send_closed, +erts_deliver_port_exit(Port *prt, Eterm from, Eterm reason, int send_closed, int drop_normal) { ErtsLink *lnk; - Eterm rreason; + Eterm modified_reason; erts_aint32_t state, set_state_flags; ERTS_SMP_CHK_NO_PROC_LOCKS; - ERTS_SMP_LC_ASSERT(erts_lc_is_port_locked(p)); + ERTS_SMP_LC_ASSERT(erts_lc_is_port_locked(prt)); - rreason = (reason == am_kill) ? am_killed : reason; + modified_reason = (reason == am_kill) ? am_killed : reason; #ifdef USE_VM_PROBES if (DTRACE_ENABLED(port_exit)) { DTRACE_CHARBUF(from_str, DTRACE_TERM_BUF_SIZE); DTRACE_CHARBUF(port_str, DTRACE_TERM_BUF_SIZE); - DTRACE_CHARBUF(rreason_str, 64); + DTRACE_CHARBUF(reason_str, 64); erts_snprintf(from_str, sizeof(DTRACE_CHARBUF_NAME(from_str)), "%T", from); - dtrace_port_str(p, port_str); - erts_snprintf(rreason_str, sizeof(DTRACE_CHARBUF_NAME(rreason_str)), "%T", rreason); - DTRACE4(port_exit, from_str, port_str, p->name, rreason_str); + dtrace_port_str(prt, port_str); + erts_snprintf(reason_str, sizeof(DTRACE_CHARBUF_NAME(reason_str)), "%T", + modified_reason); + DTRACE4(port_exit, from_str, port_str, prt->name, reason_str); } #endif - state = erts_atomic32_read_nob(&p->state); + state = erts_atomic32_read_nob(&prt->state); if (state & (ERTS_PORT_SFLGS_DEAD | ERTS_PORT_SFLG_EXITING | ERTS_PORT_SFLG_CLOSING)) return 0; - if (reason == am_normal && from != ERTS_PORT_GET_CONNECTED(p) - && from != p->common.id && drop_normal) { + if (reason == am_normal && from != ERTS_PORT_GET_CONNECTED(prt) + && from != prt->common.id && drop_normal) { return 0; } @@ -4090,53 +4379,54 @@ erts_deliver_port_exit(Port *p, Eterm from, Eterm reason, int send_closed, if (send_closed) set_state_flags |= ERTS_PORT_SFLG_SEND_CLOSED; - erts_port_task_sched_enter_exiting_state(&p->sched); + erts_port_task_sched_enter_exiting_state(&prt->sched); - state = erts_atomic32_read_bor_mb(&p->state, set_state_flags); + state = erts_atomic32_read_bor_mb(&prt->state, set_state_flags); state |= set_state_flags; - if (IS_TRACED_FL(p, F_TRACE_PORTS)) - trace_port(p, am_closed, reason); + if (IS_TRACED_FL(prt, F_TRACE_PORTS)) + trace_port(prt, am_closed, reason); - erts_trace_check_exiting(p->common.id); + erts_trace_check_exiting(prt->common.id); - set_busy_port(ERTS_Port2ErlDrvPort(p), 0); + set_busy_port(ERTS_Port2ErlDrvPort(prt), 0); - if (p->common.u.alive.reg != NULL) - (void) erts_unregister_name(NULL, 0, p, p->common.u.alive.reg->name); + if (prt->common.u.alive.reg != NULL) + (void) erts_unregister_name(NULL, 0, prt, prt->common.u.alive.reg->name); { - SweepContext sc = {p, rreason}; - lnk = ERTS_P_LINKS(p); - ERTS_P_LINKS(p) = NULL; + SweepContext sc = {prt, modified_reason}; + lnk = ERTS_P_LINKS(prt); + ERTS_P_LINKS(prt) = NULL; erts_sweep_links(lnk, &sweep_one_link, &sc); } - DRV_MONITOR_LOCK_PDL(p); + DRV_MONITOR_LOCK_PDL(prt); { - ErtsMonitor *moni = ERTS_P_MONITORS(p); - ERTS_P_MONITORS(p) = NULL; - erts_sweep_monitors(moni, &sweep_one_monitor, NULL); + SweepContext ctx = {prt, modified_reason}; + ErtsMonitor *moni = ERTS_P_MONITORS(prt); + ERTS_P_MONITORS(prt) = NULL; + erts_sweep_monitors(moni, &sweep_one_monitor, &ctx); } - DRV_MONITOR_UNLOCK_PDL(p); + DRV_MONITOR_UNLOCK_PDL(prt); - if ((state & ERTS_PORT_SFLG_DISTRIBUTION) && p->dist_entry) { - erts_do_net_exits(p->dist_entry, rreason); - erts_deref_dist_entry(p->dist_entry); - p->dist_entry = NULL; - erts_atomic32_read_band_relb(&p->state, + if ((state & ERTS_PORT_SFLG_DISTRIBUTION) && prt->dist_entry) { + erts_do_net_exits(prt->dist_entry, modified_reason); + erts_deref_dist_entry(prt->dist_entry); + prt->dist_entry = NULL; + erts_atomic32_read_band_relb(&prt->state, ~ERTS_PORT_SFLG_DISTRIBUTION); } - if ((reason != am_kill) && !is_port_ioq_empty(p)) { + if ((reason != am_kill) && !is_port_ioq_empty(prt)) { /* must turn exiting flag off */ - erts_atomic32_read_bset_relb(&p->state, + erts_atomic32_read_bset_relb(&prt->state, (ERTS_PORT_SFLG_EXITING | ERTS_PORT_SFLG_CLOSING), ERTS_PORT_SFLG_CLOSING); - flush_port(p); + flush_port(prt); } else { - terminate_port(p); + terminate_port(prt); } return 1; diff --git a/erts/emulator/beam/register.c b/erts/emulator/beam/register.c index 77f79fcea4..ac7096745e 100644 --- a/erts/emulator/beam/register.c +++ b/erts/emulator/beam/register.c @@ -323,7 +323,8 @@ erts_whereis_name(Process *c_p, Process** proc, ErtsProcLocks need_locks, int flags, - Port** port) + Port** port, + int lock_port) { RegProc* rp = NULL; HashValue hval; @@ -406,31 +407,33 @@ erts_whereis_name(Process *c_p, *port = NULL; else { #ifdef ERTS_SMP - if (pending_port == rp->pt) - pending_port = NULL; - else { - if (pending_port) { - /* Ahh! Registered port changed while reg lock - was unlocked... */ - erts_port_release(pending_port); - pending_port = NULL; - } + if (lock_port) { + if (pending_port == rp->pt) + pending_port = NULL; + else { + if (pending_port) { + /* Ahh! Registered port changed while reg lock + was unlocked... */ + erts_port_release(pending_port); + pending_port = NULL; + } - if (erts_smp_port_trylock(rp->pt) == EBUSY) { - Eterm id = rp->pt->common.id; /* id read only... */ - /* Unlock all locks, acquire port lock, and restart... */ - if (current_c_p_locks) { - erts_smp_proc_unlock(c_p, current_c_p_locks); - current_c_p_locks = 0; - } - reg_read_unlock(); - pending_port = erts_id2port(id); - goto restart; - } - } + if (erts_smp_port_trylock(rp->pt) == EBUSY) { + Eterm id = rp->pt->common.id; /* id read only... */ + /* Unlock all locks, acquire port lock, and restart... */ + if (current_c_p_locks) { + erts_smp_proc_unlock(c_p, current_c_p_locks); + current_c_p_locks = 0; + } + reg_read_unlock(); + pending_port = erts_id2port(id); + goto restart; + } + } + ERTS_SMP_LC_ASSERT(erts_lc_is_port_locked(rp->pt)); + } #endif *port = rp->pt; - ERTS_SMP_LC_ASSERT(erts_lc_is_port_locked(*port)); } } @@ -452,7 +455,7 @@ erts_whereis_process(Process *c_p, int flags) { Process *proc; - erts_whereis_name(c_p, c_p_locks, name, &proc, need_locks, flags, NULL); + erts_whereis_name(c_p, c_p_locks, name, &proc, need_locks, flags, NULL, 0); return proc; } diff --git a/erts/emulator/beam/register.h b/erts/emulator/beam/register.h index 88ab7b7bf1..d839f55d6b 100644 --- a/erts/emulator/beam/register.h +++ b/erts/emulator/beam/register.h @@ -49,7 +49,7 @@ int erts_register_name(Process *, Eterm, Eterm); Eterm erts_whereis_name_to_id(Process *, Eterm); void erts_whereis_name(Process *, ErtsProcLocks, Eterm, Process**, ErtsProcLocks, int, - Port**); + Port**, int); Process *erts_whereis_process(Process *, ErtsProcLocks, Eterm, diff --git a/erts/emulator/test/monitor_SUITE.erl b/erts/emulator/test/monitor_SUITE.erl index 8955e62df5..90d2bd8c5d 100644 --- a/erts/emulator/test/monitor_SUITE.erl +++ b/erts/emulator/test/monitor_SUITE.erl @@ -21,6 +21,7 @@ -module(monitor_SUITE). -include_lib("common_test/include/ct.hrl"). +-include_lib("eunit/include/eunit.hrl"). -export([all/0, suite/0, groups/0, case_1/1, case_1a/1, case_2/1, case_2a/1, mon_e_1/1, demon_e_1/1, demon_1/1, @@ -706,7 +707,7 @@ named_down(Config) when is_list(Config) -> spawn_opt(fun () -> WFun = fun (F, hej) -> F(F, hopp); -(F, hopp) -> F(F, hej) + (F, hopp) -> F(F, hej) end, NoSchedulers = erlang:system_info(schedulers_online), lists:foreach(fun (_) -> @@ -726,13 +727,14 @@ named_down(Config) when is_list(Config) -> NamedProc = spawn_link(fun () -> receive after infinity -> ok end end), - true = register(Name, NamedProc), + ?assertEqual(true, register(Name, NamedProc)), unlink(NamedProc), exit(NamedProc, bang), Mon = erlang:monitor(process, Name), - receive {'DOWN',Mon, _, _, _} -> ok end, - true = register(Name, self()), - true = unregister(Name), + receive {'DOWN',Mon, _, _, bang} -> ok + after 3000 -> ?assert(false) end, + ?assertEqual(true, register(Name, self())), + ?assertEqual(true, unregister(Name)), process_flag(priority,Prio), ok. diff --git a/erts/emulator/test/port_SUITE.erl b/erts/emulator/test/port_SUITE.erl index 79abcbde5f..ee07699884 100644 --- a/erts/emulator/test/port_SUITE.erl +++ b/erts/emulator/test/port_SUITE.erl @@ -74,27 +74,68 @@ %% --export([all/0, suite/0, groups/0, - init_per_testcase/2, end_per_testcase/2, - init_per_suite/1, end_per_suite/1, - stream_small/1, stream_big/1, - basic_ping/1, slow_writes/1, bad_packet/1, bad_port_messages/1, - mul_basic/1, mul_slow_writes/1, - dying_port/1, port_program_with_path/1, - open_input_file_port/1, open_output_file_port/1, - count_fds/1, - iter_max_ports/1, eof/1, input_only/1, output_only/1, - name1/1, - t_binary/1, parallell/1, t_exit/1, - env/1, huge_env/1, bad_env/1, cd/1, exit_status/1, - bad_args/1, - tps_16_bytes/1, tps_1K/1, line/1, stderr_to_stdout/1, - otp_3906/1, otp_4389/1, win_massive/1, win_massive_client/1, - mix_up_ports/1, otp_5112/1, otp_5119/1, otp_6224/1, - exit_status_multi_scheduling_block/1, ports/1, - spawn_driver/1, spawn_executable/1, close_deaf_port/1, - port_setget_data/1, - unregister_name/1, parallelism_option/1]). +-export([all/0, suite/0, groups/0, init_per_testcase/2, end_per_testcase/2, + init_per_suite/1, end_per_suite/1]). +-export([ + bad_args/1, + bad_env/1, + bad_packet/1, + bad_port_messages/1, + basic_ping/1, + cd/1, + close_deaf_port/1, + count_fds/1, + dying_port/1, + env/1, + eof/1, + exit_status/1, + exit_status_multi_scheduling_block/1, + huge_env/1, + input_only/1, + iter_max_ports/1, + line/1, + mix_up_ports/1, + mon_port_invalid_type/1, + mon_port_bad_named/1, + mon_port_bad_remote_on_local/1, + mon_port_local/1, + mon_port_name_demonitor/1, + mon_port_named/1, + mon_port_origin_dies/1, + mon_port_pid_demonitor/1, + mon_port_remote_on_remote/1, + mon_port_driver_die/1, + mon_port_driver_die_demonitor/1, + mul_basic/1, + mul_slow_writes/1, + name1/1, + open_input_file_port/1, + open_output_file_port/1, + otp_3906/1, + otp_4389/1, + otp_5112/1, + otp_5119/1, + otp_6224/1, + output_only/1, + parallelism_option/1, + parallell/1, + port_program_with_path/1, + port_setget_data/1, + ports/1, + slow_writes/1, + spawn_driver/1, + spawn_executable/1, + stderr_to_stdout/1, + stream_big/1, + stream_small/1, + t_binary/1, + t_exit/1, + tps_16_bytes/1, + tps_1K/1, + unregister_name/1, + win_massive/1, + win_massive_client/1 +]). -export([do_iter_max_ports/2]). @@ -105,12 +146,13 @@ -include_lib("common_test/include/ct.hrl"). -include_lib("kernel/include/file.hrl"). +-include_lib("eunit/include/eunit.hrl"). suite() -> [{ct_hooks,[ts_install_cth]}, {timetrap, {seconds, 10}}]. -all() -> +all() -> [otp_6224, {group, stream}, basic_ping, slow_writes, bad_packet, bad_port_messages, {group, options}, {group, multiple_packets}, parallell, dying_port, @@ -123,14 +165,32 @@ all() -> exit_status_multi_scheduling_block, ports, spawn_driver, spawn_executable, close_deaf_port, unregister_name, port_setget_data, - parallelism_option]. - -groups() -> + parallelism_option, + mon_port_invalid_type, + mon_port_local, + mon_port_remote_on_remote, + mon_port_bad_remote_on_local, + mon_port_origin_dies, + mon_port_named, + mon_port_bad_named, + mon_port_pid_demonitor, + mon_port_name_demonitor, + mon_port_driver_die, + mon_port_driver_die_demonitor + ]. + +groups() -> [{stream, [], [stream_small, stream_big]}, {options, [], [t_binary, eof, input_only, output_only]}, {multiple_packets, [], [mul_basic, mul_slow_writes]}, {tps, [], [tps_16_bytes, tps_1K]}]. +init_per_testcase(Case, Config) when Case =:= mon_port_driver_die; + Case =:= mon_port_driver_die_demonitor -> + case erlang:system_info(schedulers_online) of + 1 -> {skip, "Need 2 schedulers to run testcase"}; + _ -> Config + end; init_per_testcase(Case, Config) -> [{testcase, Case} |Config]. @@ -160,7 +220,7 @@ do_win_massive() -> ct:timetrap({minutes, 6}), SuiteDir = filename:dirname(code:which(?MODULE)), Ports = " +Q 8192", - {ok, Node} = + {ok, Node} = test_server:start_node(win_massive, slave, [{args, " -pa " ++ SuiteDir ++ Ports}]), @@ -169,7 +229,7 @@ do_win_massive() -> ok. win_massive_client(N) -> - {ok,P}=gen_tcp:listen(?WIN_MASSIVE_PORT,[{reuseaddr,true}]), + {ok,P}=gen_tcp:listen(?WIN_MASSIVE_PORT,[{reuseaddr,true}]), L = win_massive_loop(P,N), Len = length(L), lists:foreach(fun(E) -> @@ -278,7 +338,7 @@ bad_port_messages(Config) when is_list(Config) -> bad_message(PortTest, {self(),{connect,no_pid}}), ok. -bad_message(PortTest, Message) -> +bad_message(PortTest, Message) -> P = open_port({spawn,PortTest}, []), P ! Message, receive @@ -773,7 +833,7 @@ line(Config) when is_list(Config) -> S1 = lists:flatten(io_lib:format("-l~w", [length(L1)])), io:format("S1 = ~w, L1 = ~w~n", [S1,L1]), port_expect(Config,[{L1, - [{eol, Packet1}, {noeol, Packet2}, eof]}], 0, + [{eol, Packet1}, {noeol, Packet2}, eof]}], 0, S1, [{line,Siz},eof]), %% Test that lonely <CR> Don't get treated as newlines port_expect(Config,[{lists:append([Packet1, [13], Packet2, @@ -844,9 +904,9 @@ env(Config) when is_list(Config) -> {"glurf","a glorfy string"}]), %% A lot of non existing variables (mingled with existing) - NotExistingList = [{lists:flatten(io_lib:format("V~p_not_existing",[X])),false} + NotExistingList = [{lists:flatten(io_lib:format("V~p_not_existing",[X])),false} || X <- lists:seq(1,150)], - ExistingList = [{lists:flatten(io_lib:format("V~p_existing",[X])),"a_value"} + ExistingList = [{lists:flatten(io_lib:format("V~p_existing",[X])),"a_value"} || X <- lists:seq(1,150)], env_slave(Temp, lists:sort(ExistingList ++ NotExistingList)), ok. @@ -1320,22 +1380,22 @@ spawn_driver(Config) when is_list(Config) -> ok = load_driver(Path, "echo_drv"), Port = erlang:open_port({spawn_driver, "echo_drv"}, []), Port ! {self(), {command, "Hello port!"}}, - receive - {Port, {data, "Hello port!"}} = Msg1 -> + receive + {Port, {data, "Hello port!"}} = Msg1 -> io:format("~p~n", [Msg1]), - ok; + ok; Other -> ct:fail({unexpected, Other}) end, Port ! {self(), close}, receive {Port, closed} -> ok end, - Port2 = erlang:open_port({spawn_driver, "echo_drv -Hello port?"}, + Port2 = erlang:open_port({spawn_driver, "echo_drv -Hello port?"}, []), - receive - {Port2, {data, "Hello port?"}} = Msg2 -> + receive + {Port2, {data, "Hello port?"}} = Msg2 -> io:format("~p~n", [Msg2]), - ok; + ok; Other2 -> ct:fail({unexpected2, Other2}) end, @@ -1354,23 +1414,23 @@ parallelism_option(Config) when is_list(Config) -> [{parallelism, true}]), {parallelism, true} = erlang:port_info(Port, parallelism), Port ! {self(), {command, "Hello port!"}}, - receive - {Port, {data, "Hello port!"}} = Msg1 -> + receive + {Port, {data, "Hello port!"}} = Msg1 -> io:format("~p~n", [Msg1]), - ok; + ok; Other -> ct:fail({unexpected, Other}) end, Port ! {self(), close}, receive {Port, closed} -> ok end, - Port2 = erlang:open_port({spawn_driver, "echo_drv -Hello port?"}, + Port2 = erlang:open_port({spawn_driver, "echo_drv -Hello port?"}, [{parallelism, false}]), {parallelism, false} = erlang:port_info(Port2, parallelism), - receive - {Port2, {data, "Hello port?"}} = Msg2 -> + receive + {Port2, {data, "Hello port?"}} = Msg2 -> io:format("~p~n", [Msg2]), - ok; + ok; Other2 -> ct:fail({unexpected2, Other2}) end, @@ -1389,20 +1449,20 @@ spawn_executable(Config) when is_list(Config) -> ["echo_args"] = run_echo_args(DataDir,[binary, "echo_args"]), ["echo_arguments"] = run_echo_args(DataDir,["echo_arguments"]), ["echo_arguments"] = run_echo_args(DataDir,[binary, "echo_arguments"]), - [ExactFile1,"hello world","dlrow olleh"] = + [ExactFile1,"hello world","dlrow olleh"] = run_echo_args(DataDir,[ExactFile1,"hello world","dlrow olleh"]), [ExactFile1] = run_echo_args(DataDir,[default]), [ExactFile1] = run_echo_args(DataDir,[binary, default]), - [ExactFile1,"hello world","dlrow olleh"] = + [ExactFile1,"hello world","dlrow olleh"] = run_echo_args(DataDir,[switch_order,ExactFile1,"hello world", "dlrow olleh"]), - [ExactFile1,"hello world","dlrow olleh"] = + [ExactFile1,"hello world","dlrow olleh"] = run_echo_args(DataDir,[binary,switch_order,ExactFile1,"hello world", "dlrow olleh"]), [ExactFile1,"hello world","dlrow olleh"] = run_echo_args(DataDir,[default,"hello world","dlrow olleh"]), - [ExactFile1,"hello world","dlrow olleh"] = + [ExactFile1,"hello world","dlrow olleh"] = run_echo_args_2("\""++ExactFile1++"\" "++"\"hello world\" \"dlrow olleh\""), [ExactFile1,"hello world","dlrow olleh"] = run_echo_args_2(unicode:characters_to_binary("\""++ExactFile1++"\" "++"\"hello world\" \"dlrow olleh\"")), @@ -1418,7 +1478,7 @@ spawn_executable(Config) when is_list(Config) -> [ExactFile2] = run_echo_args(SpaceDir,[]), ["echo_args"] = run_echo_args(SpaceDir,["echo_args"]), ["echo_arguments"] = run_echo_args(SpaceDir,["echo_arguments"]), - [ExactFile2,"hello world","dlrow olleh"] = + [ExactFile2,"hello world","dlrow olleh"] = run_echo_args(SpaceDir,[ExactFile2,"hello world","dlrow olleh"]), [ExactFile2,"hello world","dlrow olleh"] = run_echo_args(SpaceDir,[binary, ExactFile2,"hello world","dlrow olleh"]), @@ -1429,16 +1489,16 @@ spawn_executable(Config) when is_list(Config) -> run_echo_args(SpaceDir,[binary, ExactFile2,"hello \"world\"","\"dlrow\" olleh"]), [ExactFile2] = run_echo_args(SpaceDir,[default]), - [ExactFile2,"hello world","dlrow olleh"] = + [ExactFile2,"hello world","dlrow olleh"] = run_echo_args(SpaceDir,[switch_order,ExactFile2,"hello world", "dlrow olleh"]), - [ExactFile2,"hello world","dlrow olleh"] = + [ExactFile2,"hello world","dlrow olleh"] = run_echo_args(SpaceDir,[default,"hello world","dlrow olleh"]), - [ExactFile2,"hello world","dlrow olleh"] = + [ExactFile2,"hello world","dlrow olleh"] = run_echo_args_2("\""++ExactFile2++"\" "++"\"hello world\" \"dlrow olleh\""), [ExactFile2,"hello world","dlrow olleh"] = run_echo_args_2(unicode:characters_to_binary("\""++ExactFile2++"\" "++"\"hello world\" \"dlrow olleh\"")), - ExeExt = + ExeExt = case string:to_lower(lists:last(string:tokens(ExactFile2,"."))) of "exe" -> ".exe"; @@ -1452,17 +1512,17 @@ spawn_executable(Config) when is_list(Config) -> [ExactFile3] = run_echo_args(SpaceDir,Executable2,[]), ["echo_args"] = run_echo_args(SpaceDir,Executable2,["echo_args"]), ["echo_arguments"] = run_echo_args(SpaceDir,Executable2,["echo_arguments"]), - [ExactFile3,"hello world","dlrow olleh"] = + [ExactFile3,"hello world","dlrow olleh"] = run_echo_args(SpaceDir,Executable2,[ExactFile3,"hello world","dlrow olleh"]), [ExactFile3] = run_echo_args(SpaceDir,Executable2,[default]), - [ExactFile3,"hello world","dlrow olleh"] = + [ExactFile3,"hello world","dlrow olleh"] = run_echo_args(SpaceDir,Executable2, [switch_order,ExactFile3,"hello world", "dlrow olleh"]), - [ExactFile3,"hello world","dlrow olleh"] = + [ExactFile3,"hello world","dlrow olleh"] = run_echo_args(SpaceDir,Executable2, [default,"hello world","dlrow olleh"]), - [ExactFile3,"hello world","dlrow olleh"] = + [ExactFile3,"hello world","dlrow olleh"] = run_echo_args_2("\""++ExactFile3++"\" "++"\"hello world\" \"dlrow olleh\""), [ExactFile3,"hello world","dlrow olleh"] = run_echo_args_2(unicode:characters_to_binary("\""++ExactFile3++"\" "++"\"hello world\" \"dlrow olleh\"")), @@ -1510,11 +1570,11 @@ test_bat_file(Dir) -> <<"\r\n">>], file:write_file(Full,list_to_binary(D)), EF = filename:basename(FN), - [DN,"hello","world"] = + [DN,"hello","world"] = run_echo_args(Dir,FN, [default,"hello","world"]), %% The arg0 argumant should be ignored when running batch files - [DN,"hello","world"] = + [DN,"hello","world"] = run_echo_args(Dir,FN, ["knaskurt","hello","world"]), EF = filename:basename(DN), @@ -1533,10 +1593,10 @@ test_sh_file(Dir) -> <<"done\n">>], file:write_file(Full,list_to_binary(D)), chmodplusx(Full), - [Full,"hello","world"] = + [Full,"hello","world"] = run_echo_args(Dir,FN, [default,"hello","world"]), - [Full,"hello","world of spaces"] = + [Full,"hello","world of spaces"] = run_echo_args(Dir,FN, [default,"hello","world of spaces"]), file:write_file(filename:join([Dir,"testfile1"]),<<"testdata1">>), @@ -1544,7 +1604,7 @@ test_sh_file(Dir) -> Pattern = filename:join([Dir,"testfile*"]), L = filelib:wildcard(Pattern), 2 = length(L), - [Full,"hello",Pattern] = + [Full,"hello",Pattern] = run_echo_args(Dir,FN, [default,"hello",Pattern]), ok. @@ -1620,10 +1680,10 @@ mix_up_ports(Config) when is_list(Config) -> ok = load_driver(Path, "echo_drv"), Port = erlang:open_port({spawn, "echo_drv"}, []), Port ! {self(), {command, "Hello port!"}}, - receive - {Port, {data, "Hello port!"}} = Msg1 -> + receive + {Port, {data, "Hello port!"}} = Msg1 -> io:format("~p~n", [Msg1]), - ok; + ok; Other -> ct:fail({unexpected, Other}) end, @@ -1631,7 +1691,7 @@ mix_up_ports(Config) when is_list(Config) -> receive {Port, closed} -> ok end, loop(start, done, fun(P) -> - Q = + Q = (catch erlang:open_port({spawn, "echo_drv"}, [])), %% io:format("~p ", [Q]), if is_port(Q) -> @@ -1642,7 +1702,7 @@ mix_up_ports(Config) when is_list(Config) -> end end), Port ! {self(), {command, "Hello again port!"}}, - receive + receive Msg2 -> ct:fail({unexpected, Msg2}) after 1000 -> @@ -1802,7 +1862,7 @@ exit_status_msb_test(Config, SleepSecs) when is_list(Config) -> %% We want to start port programs from as many schedulers as possible %% and we want these port programs to terminate while multi-scheduling %% is blocked. - %% + %% NoSchedsOnln = erlang:system_info(schedulers_online), Parent = self(), io:format("SleepSecs = ~p~n", [SleepSecs]), @@ -2214,7 +2274,7 @@ ports_snapshots(0, _, _) -> ok; ports_snapshots(Iter, TrafficPid, OtherPorts) -> - TrafficPid ! start, + TrafficPid ! start, receive after 1 -> ok end, Snapshot = erlang:ports(), @@ -2243,7 +2303,7 @@ ports_traffic_stopped(MaxPorts, {PortList, PortCnt}) -> end. ports_traffic_started(MaxPorts, {PortList, PortCnt}, EventList) -> - receive + receive {Pid, stop} -> %%io:format("Traffic stopped in ~p\n",[self()]), Pid ! {self(), EventList, PortList}, @@ -2256,7 +2316,7 @@ ports_traffic_started(MaxPorts, {PortList, PortCnt}, EventList) -> ports_traffic_do(MaxPorts, {PortList, PortCnt}, EventList) -> N = uniform(MaxPorts), case N > PortCnt of - true -> % Open port + true -> % Open port P = open_port({spawn, "exit_drv"}, []), %%io:format("Created port ~p\n",[P]), ports_traffic_started(MaxPorts, {[P|PortList], PortCnt+1}, @@ -2270,7 +2330,7 @@ ports_traffic_do(MaxPorts, {PortList, PortCnt}, EventList) -> [{close,P}|EventList]) end. -ports_verify(Ports, PortsAfter, EventList) -> +ports_verify(Ports, PortsAfter, EventList) -> %%io:format("Candidate=~p\nEvents=~p\n", [PortsAfter, EventList]), case lists:sort(Ports) =:= lists:sort(PortsAfter) of true -> @@ -2280,10 +2340,10 @@ ports_verify(Ports, PortsAfter, EventList) -> %% Note that we track the event list "backwards", undoing open/close: case EventList of [{open,P} | Tail] -> - ports_verify(Ports, lists:delete(P,PortsAfter), Tail); + ports_verify(Ports, lists:delete(P,PortsAfter), Tail); [{close,P} | Tail] -> - ports_verify(Ports, [P | PortsAfter], Tail); + ports_verify(Ports, [P | PortsAfter], Tail); [] -> ct:fail("Inconsistent snapshot from erlang:ports()") @@ -2391,3 +2451,227 @@ wait_until(Fun) -> receive after 100 -> ok end, wait_until(Fun) end. + +%% Attempt to monitor pid as port, and port as pid +mon_port_invalid_type(_Config) -> + Port = hd(erlang:ports()), + ?assertError(badarg, erlang:monitor(port, self())), + ?assertError(badarg, erlang:monitor(process, Port)), + ok. + +%% With local port +mon_port_local(Config) -> + Port1 = create_port(Config, ["-h1", "-q"]), % will close after we send 1 byte + Ref1 = erlang:monitor(port, Port1), + ?assertMatch({proc_monitors, true, port_monitored_by, true}, + port_is_monitored(self(), Port1)), + Port1 ! {self(), {command, <<"1">>}}, % port test will close self immediately + receive ExitP1 -> ?assertMatch({'DOWN', Ref1, port, Port1, _}, ExitP1) + after 1000 -> ?assert(false) end, + ?assertMatch({proc_monitors, false, port_monitored_by, false}, + port_is_monitored(self(), Port1)), + + %% Trying to re-monitor a port which exists but is not healthy will + %% succeed but then will immediately send DOWN + Ref2 = erlang:monitor(port, Port1), + receive ExitP2 -> ?assertMatch({'DOWN', Ref2, port, Port1, _}, ExitP2) + after 1000 -> ?assert(false) end, + ok. + +%% With remote port on remote node (should fail) +mon_port_remote_on_remote(_Config) -> + Port3 = binary_to_term(<<131, 102, % Ext term format: PORT_EXT + 100, 0, 13, "fgsfds@fgsfds", % Node :: ATOM_EXT + 1:32/big, % Id + 0>>), % Creation + ?assertError(badarg, erlang:monitor(port, Port3)), + ok. + +%% Remote port belongs to this node and does not exist +%% Port4 produces #Port<0.167772160> which should not exist in a test run +mon_port_bad_remote_on_local(_Config) -> + Port4 = binary_to_term(<<131, 102, % Ext term format: PORT_EXT + 100, 0, 13, "nonode@nohost", % Node + 167772160:32/big, % Id + 0>>), % Creation + ?assertError(badarg, erlang:monitor(port, Port4)), + ok. + +%% Monitor owner (origin) dies before port is closed +mon_port_origin_dies(Config) -> + Port5 = create_port(Config, ["-h1", "-q"]), % will close after we send 1 byte + Self5 = self(), + Proc5 = spawn(fun() -> + Self5 ! test5_started, + erlang:monitor(port, Port5), + receive stop -> ok end + end), + erlang:monitor(process, Proc5), % we want to sync with its death + receive test5_started -> ok + after 1000 -> ?assert(false) end, + ?assertMatch({proc_monitors, true, port_monitored_by, true}, + port_is_monitored(Proc5, Port5)), + Proc5 ! stop, + % receive from monitor (removing race condition) + receive ExitP5 -> ?assertMatch({'DOWN', _, process, Proc5, _}, ExitP5) + after 1000 -> ?assert(false) end, + ?assertMatch({proc_monitors, false, port_monitored_by, false}, + port_is_monitored(Proc5, Port5)), + Port5 ! {self(), {command, <<"1">>}}, % make port quit + ok. + +%% Monitor a named port +mon_port_named(Config) -> + Name6 = test_port6, + Port6 = create_port(Config, ["-h1", "-q"]), % will close after we send 1 byte + erlang:register(Name6, Port6), + erlang:monitor(port, Name6), + ?assertMatch({proc_monitors, true, port_monitored_by, true}, + port_is_monitored(self(), Name6)), + Port6 ! {self(), {command, <<"1">>}}, % port test will close self immediately + receive ExitP6 -> ?assertMatch({'DOWN', _, port, {Name6, _}, _}, ExitP6) + after 1000 -> ?assert(false) end, + ?assertMatch({proc_monitors, false, port_monitored_by, false}, + port_is_monitored(self(), Name6)), + ok. + +%% Named does not exist: Should succeed but immediately send 'DOWN' +mon_port_bad_named(_Config) -> + Name7 = test_port7, + erlang:monitor(port, Name7), + receive {'DOWN', _, port, {Name7, _}, noproc} -> ok + after 1000 -> ?assert(false) end, + ok. + +%% Monitor a pid and demonitor by ref +mon_port_pid_demonitor(Config) -> + Port8 = create_port(Config, ["-h1", "-q"]), % will close after we send 1 byte + Ref8 = erlang:monitor(port, Port8), + ?assertMatch({proc_monitors, true, port_monitored_by, true}, + port_is_monitored(self(), Port8)), + erlang:demonitor(Ref8), + ?assertMatch({proc_monitors, false, port_monitored_by, false}, + port_is_monitored(self(), Port8)), + Port8 ! {self(), {command, <<"1">>}}, % port test will close self immediately + ok. + +%% Monitor by name and demonitor by ref +mon_port_name_demonitor(Config) -> + Name9 = test_port9, + Port9 = create_port(Config, ["-h1", "-q"]), % will close after we send 1 byte + erlang:register(Name9, Port9), + Ref9 = erlang:monitor(port, Name9), + ?assertMatch({proc_monitors, true, port_monitored_by, true}, + port_is_monitored(self(), Name9)), + erlang:demonitor(Ref9), + ?assertMatch({proc_monitors, false, port_monitored_by, false}, + port_is_monitored(self(), Name9)), + Port9 ! {self(), {command, <<"1">>}}, % port test will close self immediately + ok. + +%% 1. Spawn a port which will sleep 3 seconds +%% 2. Port driver and dies horribly (via C driver_failure call). This should +%% mark port as exiting or something. +%% 3. While the command happens, a monitor is requested on the port +mon_port_driver_die(Config) -> + erlang:process_flag(scheduler, 1), + + Path = proplists:get_value(data_dir, Config), + ok = load_driver(Path, "sleep_failure_drv"), + Port = open_port({spawn, "sleep_failure_drv"}, []), + + Self = self(), + erlang:spawn_opt(fun() -> + timer:sleep(250), + Ref = erlang:monitor(port, Port), + %% Now check that msg actually arrives + receive + {'DOWN', Ref, _Port2, _, _} = M -> Self ! M + after 3000 -> Self ! no_down_message + end + end,[{scheduler, 2}]), + Port ! {self(), {command, "Fail, please!"}}, + receive + A when is_atom(A) -> ?assertEqual(A, 'A_should_be_printed'); + {'DOWN', _R, port, Port, noproc} -> ok; + {'DOWN', _R, _P, _, _} = M -> ct:fail({got_wrong_down,M}) + after 5000 -> ?assert(false) + end, + ok. + + +%% 1. Spawn a port which will sleep 3 seconds +%% 2. Monitor port +%% 3. Port driver and dies horribly (via C driver_failure call). This should +%% mark port as exiting or something. +%% 4. While the command happens, a demonitor is requested on the port +mon_port_driver_die_demonitor(Config) -> + erlang:process_flag(scheduler, 1), + + Path = proplists:get_value(data_dir, Config), + ok = load_driver(Path, "sleep_failure_drv"), + Port = open_port({spawn, "sleep_failure_drv"}, []), + + Self = self(), + erlang:spawn_opt( + fun() -> + Ref = erlang:monitor(port, Port), + Self ! Ref, + timer:sleep(250), + erlang:demonitor(Ref), + %% Now check that msg still arrives, + %% the demon should have arrived after + %% the port exited + receive + {'DOWN', Ref, _Port2, _, _} = M -> Self ! M + after 3000 -> Self ! no_down_message + end + end,[{scheduler, 2}]), + Ref = receive R -> R end, + Port ! {self(), {command, "Fail, please!"}}, + receive + {'DOWN', Ref, port, Port, normal} -> ok; + {'DOWN', _R, _P, _, _} = M -> ct:fail({got_wrong_down,M}) + after 5000 -> ?assert(false) + end, + ok. + +%% @doc Makes a controllable port for testing. Underlying mechanism of this +%% port is not important, only important is our ability to close/kill it or +%% have it monitored. +create_port(Config, Args) -> + DataDir = ?config(data_dir, Config), + %% Borrow port test utility from port SUITE + Program = filename:join([DataDir, "port_test"]), + erlang:open_port({spawn_executable, Program}, [{args, Args}]). + +%% @doc Checks if process Pid exists, and if so, if its monitoring (or not) +%% the Port (or if port doesn't exist, we assume answer is no). +port_is_monitored(Pid, Port) when is_pid(Pid), is_port(Port) -> + %% Variant for when port is a port id (port()) + A = case erlang:process_info(Pid, monitors) of + undefined -> false; + {monitors, ProcMTargets} -> lists:member({port, Port}, ProcMTargets) + end, + B = case erlang:port_info(Port, monitored_by) of + undefined -> false; + {monitored_by, PortMonitors} -> lists:member(Pid, PortMonitors) + end, + {proc_monitors, A, port_monitored_by, B}; +port_is_monitored(Pid, PortName) when is_pid(Pid), is_atom(PortName) -> + %% Variant for when port is an atom + A = case erlang:process_info(Pid, monitors) of + undefined -> false; + {monitors, ProcMTargets} -> + lists:member({port, {PortName, node()}}, ProcMTargets) + end, + B = case erlang:whereis(PortName) of + undefined -> false; % name is not registered or is dead + PortId -> + case erlang:port_info(PortId, monitored_by) of + undefined -> false; % is dead + {monitored_by, PortMonitors} -> + lists:member(Pid, PortMonitors) + end + end, + {proc_monitors, A, port_monitored_by, B}. diff --git a/erts/emulator/test/port_SUITE_data/Makefile.src b/erts/emulator/test/port_SUITE_data/Makefile.src index ff822ae720..fb7685c4b6 100644 --- a/erts/emulator/test/port_SUITE_data/Makefile.src +++ b/erts/emulator/test/port_SUITE_data/Makefile.src @@ -4,7 +4,7 @@ CFLAGS = @CFLAGS@ -I@erl_include@ @DEFS@ CROSSLDFLAGS = @CROSSLDFLAGS@ PROGS = port_test@exe@ echo_args@exe@ dead_port@exe@ -DRIVERS = echo_drv@dll@ exit_drv@dll@ failure_drv@dll@ +DRIVERS = echo_drv@dll@ exit_drv@dll@ failure_drv@dll@ sleep_failure_drv@dll@ all: $(PROGS) $(DRIVERS) port_test.@EMULATOR@ diff --git a/erts/emulator/test/port_SUITE_data/sleep_failure_drv.c b/erts/emulator/test/port_SUITE_data/sleep_failure_drv.c new file mode 100644 index 0000000000..1f52646572 --- /dev/null +++ b/erts/emulator/test/port_SUITE_data/sleep_failure_drv.c @@ -0,0 +1,76 @@ +#include <stdio.h> +#include "erl_driver.h" +#ifdef __WIN32__ +# include <windows.h> +#else +# include <unistd.h> +#endif + +typedef struct _erl_drv_data FailureDrvData; + +static FailureDrvData *failure_drv_start(ErlDrvPort, char *); +static void failure_drv_stop(FailureDrvData *); +static void failure_drv_output(ErlDrvData, char *, ErlDrvSizeT); +static void failure_drv_finish(void); + +static ErlDrvEntry failure_drv_entry = { + NULL, /* init */ + failure_drv_start, + failure_drv_stop, + failure_drv_output, + NULL, /* ready_input */ + NULL, /* ready_output */ + "sleep_failure_drv", + NULL, /* finish */ + NULL, /* handle */ + NULL, /* control */ + NULL, /* timeout */ + NULL, /* outputv */ + NULL, /* ready_async */ + NULL, + NULL, + NULL, + ERL_DRV_EXTENDED_MARKER, + ERL_DRV_EXTENDED_MAJOR_VERSION, + ERL_DRV_EXTENDED_MINOR_VERSION, + 0, + NULL, + NULL, + NULL, +}; + + + +/* ------------------------------------------------------------------------- +** Entry functions +**/ + +DRIVER_INIT(failure_drv) +{ + return &failure_drv_entry; +} + +static FailureDrvData *failure_drv_start(ErlDrvPort port, char *command) { + void *void_ptr; + + return void_ptr = port; +} + +static void failure_drv_stop(FailureDrvData *data_p) { +} + +static void failure_drv_output(ErlDrvData drv_data, char *buf, ErlDrvSizeT len) { + FailureDrvData *data_p = (FailureDrvData *) drv_data; + void *void_ptr; + ErlDrvPort port = void_ptr = data_p; + +#ifdef __WIN32__ + Sleep(3000); +#else + sleep(3); +#endif + driver_failure(port, 0); +} + +static void failure_drv_finish() { +} diff --git a/erts/preloaded/src/erlang.erl b/erts/preloaded/src/erlang.erl index 94f3078173..edf79b8f75 100644 --- a/erts/preloaded/src/erlang.erl +++ b/erts/preloaded/src/erlang.erl @@ -1206,16 +1206,18 @@ module_loaded(_Module) -> erlang:nif_error(undefined). -type registered_name() :: atom(). - -type registered_process_identifier() :: registered_name() | {registered_name(), node()}. - -type monitor_process_identifier() :: pid() | registered_process_identifier(). +-type monitor_port_identifier() :: port() | registered_name(). %% monitor/2 --spec monitor(process, monitor_process_identifier()) -> MonitorRef when - MonitorRef :: reference(); - (time_offset, clock_service) -> MonitorRef when - MonitorRef :: reference(). +-spec monitor + (process, monitor_process_identifier()) -> MonitorRef + when MonitorRef :: reference(); + (port, monitor_port_identifier()) -> MonitorRef + when MonitorRef :: reference(); + (time_offset, clock_service) -> MonitorRef + when MonitorRef :: reference(). monitor(_Type, _Item) -> erlang:nif_error(undefined). @@ -2160,7 +2162,7 @@ process_flag(_Flag, _Value) -> {max_heap_size, MaxHeapSize :: max_heap_size()} | {monitored_by, Pids :: [pid()]} | {monitors, - Monitors :: [{process, Pid :: pid() | + Monitors :: [{process | port, Pid :: pid() | port() | {RegName :: atom(), Node :: node()}}]} | {message_queue_data, MQD :: message_queue_data()} | {priority, Level :: priority_level()} | @@ -3087,6 +3089,9 @@ port_info(Port) -> (Port, monitors) -> {monitors, Monitors} | 'undefined' when Port :: port() | atom(), Monitors :: [{process, pid()}]; + (Port, monitored_by) -> {monitored_by, MonitoredBy} | 'undefined' when + Port :: port() | atom(), + MonitoredBy :: [pid()]; (Port, name) -> {name, Name} | 'undefined' when Port :: port() | atom(), Name :: string(); 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/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/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}) -> diff --git a/system/doc/reference_manual/typespec.xml b/system/doc/reference_manual/typespec.xml index 9e26e9058d..f17e5df277 100644 --- a/system/doc/reference_manual/typespec.xml +++ b/system/doc/reference_manual/typespec.xml @@ -142,7 +142,7 @@ PairList :: Pair | Pair, PairList - Pair :: Type := Type %% notes a pair that must be present + Pair :: Type := Type %% denotes a pair that must be present | Type => Type TList :: Type @@ -174,19 +174,13 @@ </p> <p> The general form of maps is <c>#{PairList}</c>. The key types in - <c>PairList</c> are allowed to overlap, and if they do, the leftmost pair - takes precedence. A map value does not belong to this type if contains a key - that is not in <c>PairList</c>. + <c>PairList</c> are allowed to overlap, and if they do, the + leftmost pair takes precedence. A map pair has a key in + <c>PairList</c> if it belongs to this type. </p> <p> - Because it is common to end a map type with <c>any() => any()</c> to denote - that keys that do not belong to any other pair in <c>PairList</c> are - allowed, and may map to any value, the shorthand notation <c>...</c> is - allowed as the last pair of a map type. - </p> - <p> - Notice that the syntactic representation of <c>map()</c> is <c>#{...}</c> - (or <c>#{_ => _}</c>, or <c>#{any() => any()}</c>), not <c>#{}</c>. + Notice that the syntactic representation of <c>map()</c> is + <c>#{any() => any()}</c> (or <c>#{_ => _}</c>), not <c>#{}</c>. The notation <c>#{}</c> specifies the singleton type for the empty map. </p> <p> |