Current section
Files
Jump to
Current section
Files
src/concuerror_inspect.erl
%%% @private
%%% @doc
%%% The instrumenter replaces interesting operations with calls
%%% to inspect/3
-module(concuerror_inspect).
%% Interface to instrumented code:
-export([start_inspection/1, stop_inspection/0, inspect/3, explain_error/1]).
-export_type([instrumented_tag/0]).
%%------------------------------------------------------------------------------
-include("concuerror.hrl").
%%------------------------------------------------------------------------------
-type instrumented_tag() :: 'apply' | 'call' | 'receive'.
%%------------------------------------------------------------------------------
-spec start_inspection(term()) -> 'ok'.
start_inspection(Info) ->
NewDict = erase(),
put(concuerror_info, {under_concuerror, Info, NewDict}),
ok.
-spec stop_inspection() -> 'false' | {'true', term()}.
stop_inspection() ->
case get(concuerror_info) of
{under_concuerror, Info, Dict} ->
erase(concuerror_info),
_ = [put(K, V) || {K, V} <- Dict],
{true, Info};
_ -> false
end.
%% Function inspect/3 should:
%% - return the result of a call, if it is called from a non-Concuerror process
%% - grab concuerror_info and continue to concuerror_callback, otherwise
-spec inspect(Tag :: instrumented_tag(),
Args :: [term()],
Location :: term()) -> Return :: term().
inspect(Tag, Args, Location) ->
Ret =
case stop_inspection() of
false -> doit;
{true, Info} ->
{R, NewInfo} =
concuerror_callback:instrumented(Tag, Args, Location, Info),
start_inspection(NewInfo),
R
end,
case Ret of
doit ->
case {Tag, Args} of
{apply, [Fun, ApplyArgs]} ->
erlang:apply(Fun, ApplyArgs);
{call, [Module, Name, CallArgs]} ->
erlang:apply(Module, Name, CallArgs);
{'receive', [_, Timeout]} ->
Timeout
end;
{didit, Res} -> Res;
{error, Reason} ->
StackTop =
case {Tag, Args} of
{apply, Args} ->
{erlang, apply, Args, Location};
{call, [Module, Name, CallArgs]} ->
{Module, Name, CallArgs, Location}
end,
erlang:raise(error, Reason, [StackTop|get_stacktrace()]);
{skip_timeout, CreateMessage} ->
assert_no_messages(),
case CreateMessage of
false -> ok;
{true, D} -> self() ! D
end,
0
end.
assert_no_messages() ->
receive
Msg -> exit(self(), {?MODULE, {pending_message, self(), Msg}})
after
0 -> ok
end.
get_stacktrace() ->
{_, Trace} = erlang:process_info(self(), current_stacktrace),
[T || T <- Trace, not_concuerror_module(element(1, T))].
not_concuerror_module(Atom) ->
case atom_to_list(Atom) of
"concuerror" ++ _ -> false;
_ -> true
end.
-spec explain_error(term()) -> string().
explain_error({pending_message, Proc, Msg}) ->
io_lib:format(
"A process (~w) had a message (~w) in it's mailbox when it"
" shouldn't." ++ ?notify_us_msg, [Proc, Msg]).