Fix handling of dead processes in recon:info/2

Closes https://github.com/ferd/recon/issues/95
diff --git a/README.md b/README.md
index 0cefdfc..71eb450 100644
--- a/README.md
+++ b/README.md
@@ -27,6 +27,8 @@
 
 *2.x*
 
+- 2.5.3 (unpublished)
+  - [Handle dead processes in `recon:info/2` types and edge cases](https://github.com/ferd/recon/pull/97)
 - 2.5.2
   - [Increase Dialyzer strictness](https://github.com/ferd/recon/pull/88)
   - [Accumulate all block entries in `format_blocks`](https://github.com/ferd/recon/pull/83)
diff --git a/src/recon.app.src b/src/recon.app.src
index 0fc4df5..9fab2f5 100644
--- a/src/recon.app.src
+++ b/src/recon.app.src
@@ -1,6 +1,6 @@
 {application, recon,
  [{description, "Diagnostic tools for production use"},
-  {vsn, "2.5.2"},
+  {vsn, "2.5.3"},
   {modules, [recon, recon_alloc, recon_lib, recon_trace, recon_rec]},
   {registered, []},
   {applications, [kernel, stdlib]},
diff --git a/src/recon.erl b/src/recon.erl
index 707f55d..9fc62af 100644
--- a/src/recon.erl
+++ b/src/recon.erl
@@ -174,6 +174,7 @@
 %% another registry supported in the `{via, Module, Name}' syntax (must have a
 %% `Module:whereis_name/1' function). Pids can also be passed in as a string
 %% (`"<0.39.0>"') or a triple (`{0,39,0}') and will be converted to be used.
+%% Returns `undefined' as a value when a process died.
 -spec info(pid_term()) -> [{info_type(), [{info_key(), Value}]},...] when
       Value :: term().
 info(PidTerm) ->
@@ -198,9 +199,9 @@
 %% A fake attribute `binary_memory' is also available to return the
 %% amount of memory used by refc binaries for a process.
 -dialyzer({no_contracts, info/2}). % ... Overloaded contract for recon:info/2 has overlapping domains
--spec info(pid_term(), info_type()) -> {info_type(), [{info_key(), term()}]}
-    ;     (pid_term(), [atom()]) -> [{atom(), term()}]
-    ;     (pid_term(), atom()) -> {atom(), term()}.
+-spec info(pid_term(), info_type()) -> {info_type(), [{info_key(), term()}] | undefined}
+    ;     (pid_term(), [atom()]) -> [{atom(), term()}] | undefined
+    ;     (pid_term(), atom()) -> {atom(), term()} | undefined.
 info(PidTerm, meta) ->
     info_type(PidTerm, meta, [registered_name, dictionary, group_leader,
                               status]);
@@ -226,8 +227,12 @@
 %% @private wrapper around `erlang:process_info/2' that allows special
 %% attribute handling for items like `binary_memory'.
 proc_info(Pid, binary_memory) ->
-    {binary, Bins} = erlang:process_info(Pid, binary),
-    {binary_memory, recon_lib:binary_memory(Bins)};
+    case erlang:process_info(Pid, binary) of
+        undefined ->
+            undefined;
+        {binary, Bins} ->
+            {binary_memory, recon_lib:binary_memory(Bins)}
+    end;
 proc_info(Pid, Term) when is_atom(Term) ->
     erlang:process_info(Pid, Term);
 proc_info(Pid, List) when is_list(List) ->
diff --git a/test/recon_SUITE.erl b/test/recon_SUITE.erl
index dbb4d86..af1730d 100644
--- a/test/recon_SUITE.erl
+++ b/test/recon_SUITE.erl
@@ -21,7 +21,7 @@
           node_stats_list, get_state, source, tcp, udp, files, port_types,
           inet_count, inet_window, binary_memory, scheduler_usage].
 
-groups() -> [{info, [], [info3, info4, info1, info2,
+groups() -> [{info, [], [info3, info4, info1, info2, info_dead,
                          port_info1, port_info2]}].
 
 init_per_group(info, Config) ->
@@ -144,6 +144,31 @@
                         K1 == K2]),
     unregister(info2).
 
+info_dead(_Config) ->
+    Pid = spawn(fun() -> ok end),
+    timer:sleep(10),
+    Categories = [{meta, [registered_name, dictionary, group_leader, status]},
+                  {signals, [links, monitors, monitored_by, trap_exit]},
+                  {location, [initial_call, current_stacktrace]},
+                  {memory_used, [memory, message_queue_len, heap_size,
+                                 total_heap_size, garbage_collection]},
+                  {work, [reductions]}],
+    undefined = recon:info(Pid, registered_name),
+    Keys = lists:flatten([K || {_,L} <- Categories, K <- L]),
+    %% check that individual category calls return undefined values
+    [] = lists:flatten(
+        [GetCat || {Cat, _List} <- Categories,
+                   {GetCat,Info} <- [recon:info(Pid, Cat)],
+                   Cat =:= GetCat,
+                   undefined =/= Info]
+    ),
+    true = lists:all(fun(X) -> X == undefined end,
+                     [recon:info(Pid, K) || K <- Keys]),
+    undefined = recon:info(Pid, Keys),
+    %% Special virtual record.
+    undefined = recon:info(Pid, binary_memory),
+    ok.
+
 proc_count(_Config) ->
     Res = recon:proc_count(memory, 10),
     true = proc_attrs(Res),