Current section

Files

Jump to
xprof_core src xprof_core_ms.erl
Raw

src/xprof_core_ms.erl

-module(xprof_core_ms).
-export([fun2ms/1,
default_ms/0,
err/1, err/2, err/3
]).
-spec fun2ms(string()) -> {ok, xprof_core:mfa_spec()}
| {error, string()}.
fun2ms(Str) ->
ModeCb = xprof_core_lib:get_mode_cb(),
try
case ModeCb:parse_query(Str) of
{mfa, MFA} ->
{ok, {MFA, default_ms()}};
{clauses, M, F, Clauses} ->
Arity = get_arity(Clauses),
MS = ms(Clauses),
{ok, {{M, F, Arity}, fix_ms(MS)}}
end
catch throw:Error ->
Error
end.
default_ms() ->
fix_ms([{'_', [], []}]).
ms(Clauses) ->
IsEmptyArgs = (get_arity(Clauses) =:= 0),
ERR_HEAD = 3,
case ms_transform:transform_from_shell(
dbg, wrap_args(Clauses), _ImportList = []) of
{error,[{_, [{_, ms_transform, ERR_HEAD}|_]}|_], _} when IsEmptyArgs ->
%% A bug in ms_trasform that was only fixed in OTP 19.2 prevents
%% empty list as head in "dbg:fun2ms(fun([]) -> ..."
%% (see https://github.com/erlang/otp/commit/8db6c68b)
workaround_empty_args_ms(ms(workaround_empty_args_cl(Clauses)));
{error,[{_,[{Loc,Mod,Code}|_]}|_],_} ->
err(Loc, Mod, Code);
MS ->
MS
end.
get_arity([{clause, _, Args, _, _}|_]) ->
length(Args).
wrap_args(Clauses) ->
[{clause, Loc, [build_list(Args)], Guards, Body}
||{clause, Loc, Args, Guards, Body} <- Clauses].
build_list([]) ->
{nil, 0};
build_list([Arg|Args]) ->
Loc = element(2, Arg),
{cons, Loc, Arg, build_list(Args)}.
workaround_empty_args_cl(Clauses) ->
[{clause, Loc, [{var, 0, '_'}], Guards, Body}
||{clause, Loc, [], Guards, Body} <- Clauses].
workaround_empty_args_ms(Ms) ->
[{[], G, B} || {['_'], G, B} <- Ms].
%% @doc Ensure that the match-spec does not create traces that have different
%% format than what xprof_core_trace_handler anticipates (ie. {message, _}
%% directives might have to be modified)
%% - The special case {message, false} is allowed (disables sending of trace
%% messages ('call' and 'return_to') for this function call, just like if the
%% match specification had not matched).
%% - The special case {message, true} must be avoided (sets the default
%% behavior, ie. trace message is sent with no extra information). These calls
%% are replaced with our default message ({message, arity} or {message, '$_'})
%% - Other values for messages are allowed but later overriden by placing
%% {message, arity} at the end of the match-spec body in case argument capturing
%% is off
%% - For the general case when the match-spec body does not contain any message
%% directive a default message ({message, arity} or {message, '$_'}) is inserted
%% as the first action of the body as well as enabling exception_trace
fix_ms(MS) ->
{traverse_ms(MS, _CaptureOff = false),
traverse_ms(MS, _CaptureOn = true)}.
traverse_ms(MS, Capture) ->
DefaultMsg =
case Capture of
false -> arity;
true -> '$_'
end,
[{Head, Condition,
[{exception_trace},{message, DefaultMsg}|traverse_ms_c(Body, Capture)]}
|| {Head, Condition, Body} <- MS].
%% @doc traverse a match-spec clause
traverse_ms_c([{message, true}|T], false) ->
[{message, arity}|traverse_ms_c(T, false)];
traverse_ms_c([{message, true}|T], true) ->
[{message, '$_'}|traverse_ms_c(T, true)];
traverse_ms_c([{message, Other}|T], false) when Other =/= false ->
%% In case `Other' evaluates to `false', return `false' (disable sending a
%% trace message). Otherwise return `arity'.
%% message( (Other =/= false ) andalso 'arity' )
[{message, {'andalso', {'=/=', Other, false}, arity}}|traverse_ms_c(T, false)];
traverse_ms_c([H|T], C) ->
[traverse_ms_c(H, C)|traverse_ms_c(T, C)];
traverse_ms_c(Tuple, C) when is_tuple(Tuple) ->
list_to_tuple(traverse_ms_c(tuple_to_list(Tuple), C));
traverse_ms_c([], _) ->
[];
traverse_ms_c(Other, _) ->
Other.
-spec err(string()) -> no_return().
err(Fmt) ->
throw({error, fmt(Fmt, [])}).
-spec err(string(), list()) -> no_return().
err(Fmt, Args) ->
throw({error, fmt(Fmt, Args)}).
-spec err(tuple() | integer(), module(), term()) -> no_return().
err({1, StartCol, _EndCol}, Mod, Err) ->
err({1, StartCol}, Mod, Err);
err({1, Col}, Mod, Err) ->
throw({error, fmt("~s at column ~p", [Mod:format_error(Err), Col])});
err(1, Mod, Err) ->
throw({error, fmt(Mod:format_error(Err), [])}).
fmt(Fmt, Args) ->
lists:flatten(io_lib:format(Fmt, Args)).