aboutsummaryrefslogtreecommitdiffstats
path: root/lib/tools/src/xref_reader.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/tools/src/xref_reader.erl')
-rw-r--r--lib/tools/src/xref_reader.erl352
1 files changed, 352 insertions, 0 deletions
diff --git a/lib/tools/src/xref_reader.erl b/lib/tools/src/xref_reader.erl
new file mode 100644
index 0000000000..db755c31d8
--- /dev/null
+++ b/lib/tools/src/xref_reader.erl
@@ -0,0 +1,352 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2000-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(xref_reader).
+
+-export([module/5]).
+
+-import(lists, [keysearch/3, member/2, reverse/1]).
+
+-record(xrefr,
+ {module=[],
+ function=[],
+ def_at=[],
+ l_call_at=[],
+ x_call_at=[],
+ el=[],
+ ex=[],
+ x=[],
+ df,
+ builtins_too=false,
+ is_abstr, % abstract module?
+ funvars=[], % records variables bound to funs
+ % (for coping with list comprehension)
+ matches=[], % records other bound variables
+ unresolved=[], % unresolved calls, {{mfa(),mfa()},Line}
+ %% experimental; -xref(FunEdge) is recognized.
+ lattrs=[], % local calls, {{mfa(),mfa()},Line}
+ xattrs=[], % external calls, -"-
+ battrs=[] % badly formed xref attributes, term().
+ }).
+
+-include("xref.hrl").
+
+%% sys_pre_expand has modified the forms slightly compared to what
+%% erl_id_trans recognizes.
+
+%% The versions of the abstract code are as follows:
+%% R7: abstract_v1
+%% R8: abstract_v2
+
+%% -> {ok, Module, {DefAt, CallAt, LC, XC, X, Attrs}, Unresolved}} | EXIT
+%% Attrs = {ALC, AXC, Bad}
+%% ALC, AXC and Bad are extracted from the attribute 'xref'. An experiment.
+module(Module, Forms, CollectBuiltins, X, DF) ->
+ Attrs = [{Attr,V} || {attribute,_Line,Attr,V} <- Forms],
+ IsAbstract = xref_utils:is_abstract_module(Attrs),
+ S = #xrefr{module = Module, builtins_too = CollectBuiltins,
+ is_abstr = IsAbstract, x = X, df = DF},
+ forms(Forms, S).
+
+forms([F | Fs], S) ->
+ S1 = form(F, S),
+ forms(Fs, S1);
+forms([], S) ->
+ #xrefr{module = M, def_at = DefAt,
+ l_call_at = LCallAt, x_call_at = XCallAt,
+ el = LC, ex = XC, x = X, df = Depr,
+ lattrs = AL, xattrs = AX, battrs = B, unresolved = U} = S,
+ Attrs = {lists:reverse(AL), lists:reverse(AX), lists:reverse(B)},
+ {ok, M, {DefAt, LCallAt, XCallAt, LC, XC, X, Attrs, Depr}, U}.
+
+form({attribute, Line, xref, Calls}, S) -> % experimental
+ #xrefr{module = M, function = Fun,
+ lattrs = L, xattrs = X, battrs = B} = S,
+ attr(Calls, Line, M, Fun, L, X, B, S);
+form({attribute, _Line, _Attr, _Val}, S) ->
+ S;
+form({function, 0, 'MNEMOSYNE RULE', 1, _Clauses}, S) ->
+ S;
+form({function, 0, 'MNEMOSYNE QUERY', 2, _Clauses}, S) ->
+ S;
+form({function, 0, 'MNEMOSYNE RECFUNDEF', 1, _Clauses}, S) ->
+ S;
+form({function, 0, module_info, 0, _Clauses}, S) ->
+ S;
+form({function, 0, module_info, 1, _Clauses}, S) ->
+ S;
+form({function, Line, Name, Arity, Clauses}, S) ->
+ MFA0 = {S#xrefr.module, Name, Arity},
+ MFA = adjust_arity(S, MFA0),
+ S1 = S#xrefr{function = MFA},
+ S2 = S1#xrefr{def_at = [{MFA,Line} | S#xrefr.def_at]},
+ S3 = clauses(Clauses, S2),
+ S3#xrefr{function = []}.
+
+clauses(Cls, S) ->
+ #xrefr{funvars = FunVars, matches = Matches} = S,
+ clauses(Cls, FunVars, Matches, S).
+
+clauses([{clause, _Line, _H, G, B} | Cs], FunVars, Matches, S) ->
+ S1 = case S#xrefr.builtins_too of
+ true -> expr(G, S);
+ false -> S
+ end,
+ S2 = expr(B, S1),
+ S3 = S2#xrefr{funvars = FunVars, matches = Matches},
+ clauses(Cs, S3);
+clauses([], _FunVars, _Matches, S) ->
+ S.
+
+attr([E={From, To} | As], Ln, M, Fun, AL, AX, B, S) ->
+ case mfa(From, M) of
+ {_, _, MFA} when MFA =:= Fun; [] =:= Fun ->
+ attr(From, To, Ln, M, Fun, AL, AX, B, S, As, E);
+ {_, _, _} ->
+ attr(As, Ln, M, Fun, AL, AX, [E | B], S);
+ _ ->
+ attr(Fun, E, Ln, M, Fun, AL, AX, B, S, As, E)
+ end;
+attr([To | As], Ln, M, Fun, AL, AX, B, S) ->
+ attr(Fun, To, Ln, M, Fun, AL, AX, B, S, As, To);
+attr([], _Ln, _M, _Fun, AL, AX, B, S) ->
+ S#xrefr{lattrs = AL, xattrs = AX, battrs = B}.
+
+attr(From, To, Ln, M, Fun, AL, AX, B, S, As, E) ->
+ case {mfa(From, M), mfa(To, M)} of
+ {{true,_,F}, {_,external,T}} ->
+ attr(As, Ln, M, Fun, AL, [{{F,T},Ln} | AX], B, S);
+ {{true,_,F}, {_,local,T}} ->
+ attr(As, Ln, M, Fun, [{{F,T},Ln} | AL], AX, B, S);
+ _ -> attr(As, Ln, M, Fun, AL, AX, [E | B], S)
+ end.
+
+mfa({F,A}, M) when is_atom(F), is_integer(A) ->
+ {true, local, {M,F,A}};
+mfa(MFA={M,F,A}, M1) when is_atom(M), is_atom(F), is_integer(A) ->
+ {M=:=M1, external, MFA};
+mfa(_, _M) -> false.
+
+expr({'if', _Line, Cs}, S) ->
+ clauses(Cs, S);
+expr({'case', _Line, E, Cs}, S) ->
+ S1 = expr(E, S),
+ clauses(Cs, S1);
+expr({'receive', _Line, Cs}, S) ->
+ clauses(Cs, S);
+expr({'receive', _Line, Cs, To, ToEs}, S) ->
+ S1 = expr(To, S),
+ S2 = expr(ToEs, S1),
+ clauses(Cs, S2);
+expr({'try',_Line,Es,Scs,Ccs,As}, S) ->
+ S1 = expr(Es, S),
+ S2 = clauses(Scs, S1),
+ S3 = clauses(Ccs, S2),
+ expr(As, S3);
+expr({call, Line,
+ {remote, _, {atom,_,erlang}, {atom,_,make_fun}},
+ [{atom,_,Mod}, {atom,_,Fun}, {integer,_,Arity}]}, S) ->
+ %% Added in R10B-6. M:F/A.
+ expr({'fun', Line, {function, Mod, Fun, Arity}}, S);
+expr({'fun', Line, {function, Mod, Name, Arity}}, S) ->
+ %% Added in R10B-6. M:F/A.
+ As = lists:duplicate(Arity, {atom, Line, foo}),
+ external_call(Mod, Name, As, Line, false, S);
+expr({'fun', Line, {function, Name, Arity}, _Extra}, S) ->
+ %% Added in R8.
+ handle_call(local, S#xrefr.module, Name, Arity, Line, S);
+expr({'fun', _Line, {clauses, Cs}, _Extra}, S) ->
+ clauses(Cs, S);
+expr({call, Line, {atom, _, Name}, As}, S) ->
+ S1 = handle_call(local, S#xrefr.module, Name, length(As), Line, S),
+ expr(As, S1);
+expr({call, Line, {remote, _Line, {atom,_,Mod}, {atom,_,Name}}, As}, S) ->
+ external_call(Mod, Name, As, Line, false, S);
+expr({call, Line, {remote, _Line, Mod, Name}, As}, S) ->
+ %% Added in R8.
+ external_call(erlang, apply, [Mod, Name, list2term(As)], Line, true, S);
+expr({call, Line, F, As}, S) ->
+ external_call(erlang, apply, [F, list2term(As)], Line, true, S);
+expr({match, _Line, {var,_,Var}, {'fun', _, {clauses, Cs}, _Extra}}, S) ->
+ %% This is what is needed in R7 to avoid warnings for the functions
+ %% that are passed around by the "expansion" of list comprehension.
+ S1 = S#xrefr{funvars = [Var | S#xrefr.funvars]},
+ clauses(Cs, S1);
+expr({match, _Line, {var,_,Var}, E}, S) ->
+ %% Used for resolving code like
+ %% Args = [A,B], apply(m, f, Args)
+ S1 = S#xrefr{matches = [{Var, E} | S#xrefr.matches]},
+ expr(E, S1);
+expr(T, S) when is_tuple(T) ->
+ expr(tuple_to_list(T), S);
+expr([E | Es], S) ->
+ expr(Es, expr(E, S));
+expr(_E, S) ->
+ S.
+
+%% Mod and Fun may not correspond to something in the abstract code,
+%% which is signalled by X =:= true.
+external_call(Mod, Fun, ArgsList, Line, X, S) ->
+ Arity = length(ArgsList),
+ W = case xref_utils:is_funfun(Mod, Fun, Arity) of
+ true when erlang =:= Mod, apply =:= Fun, 2 =:= Arity -> apply2;
+ true when erts_debug =:= Mod, apply =:= Fun,4 =:= Arity -> debug4;
+ true when erlang =:= Mod, spawn_opt =:= Fun -> Arity - 1;
+ true -> Arity;
+ false when Mod =:= erlang ->
+ case erl_internal:type_test(Fun, Arity) of
+ true -> type;
+ false -> false
+ end;
+ false -> false
+ end,
+ S1 = if
+ W =:= type; X ->
+ S;
+ true ->
+ handle_call(external, Mod, Fun, Arity, Line, S)
+ end,
+ case {W, ArgsList} of
+ {false, _} ->
+ expr(ArgsList, S1);
+ {type, _} ->
+ expr(ArgsList, S1);
+ {apply2, [{tuple, _, [M,F]}, ArgsTerm]} ->
+ eval_args(M, F, ArgsTerm, Line, S1, ArgsList, []);
+ {1, [{tuple, _, [M,F]} | R]} -> % R = [] unless spawn_opt
+ eval_args(M, F, list2term([]), Line, S1, ArgsList, R);
+ {2, [Node, {tuple, _, [M,F]} | R]} -> % R = [] unless spawn_opt
+ eval_args(M, F, list2term([]), Line, S1, ArgsList, [Node | R]);
+ {3, [M, F, ArgsTerm | R]} -> % R = [] unless spawn_opt
+ eval_args(M, F, ArgsTerm, Line, S1, ArgsList, R);
+ {4, [Node, M, F, ArgsTerm | R]} -> % R = [] unless spawn_opt
+ eval_args(M, F, ArgsTerm, Line, S1, ArgsList, [Node | R]);
+ {debug4, [M, F, ArgsTerm, _]} ->
+ eval_args(M, F, ArgsTerm, Line, S1, ArgsList, []);
+ _Else -> % apply2, 1 or 2
+ check_funarg(W, ArgsList, Line, S1)
+ end.
+
+eval_args(Mod, Fun, ArgsTerm, Line, S, ArgsList, Extra) ->
+ {IsSimpleCall, M, F} = mod_fun(Mod, Fun),
+ case term2list(ArgsTerm, [], S) of
+ undefined ->
+ S1 = unresolved(M, F, -1, Line, S),
+ expr(ArgsList, S1);
+ ArgsList2 when not IsSimpleCall ->
+ S1 = unresolved(M, F, length(ArgsList2), Line, S),
+ expr(ArgsList, S1);
+ ArgsList2 when IsSimpleCall ->
+ S1 = expr(Extra, S),
+ external_call(M, F, ArgsList2, Line, false, S1)
+ end.
+
+mod_fun({atom,_,M1}, {atom,_,F1}) -> {true, M1, F1};
+mod_fun({atom,_,M1}, _) -> {false, M1, ?VAR_EXPR};
+mod_fun(_, {atom,_,F1}) -> {false, ?MOD_EXPR, F1};
+mod_fun(_, _) -> {false, ?MOD_EXPR, ?VAR_EXPR}.
+
+check_funarg(W, ArgsList, Line, S) ->
+ {FunArg, Args} = fun_args(W, ArgsList),
+ S1 = case funarg(FunArg, S) of
+ true ->
+ S;
+ false when is_integer(W) -> % 1 or 2
+ unresolved(?MOD_EXPR, ?VAR_EXPR, 0, Line, S);
+ false -> % apply2
+ N = case term2list(Args, [], S) of
+ undefined -> -1;
+ As -> length(As)
+ end,
+ unresolved(?MOD_EXPR, ?VAR_EXPR, N, Line, S)
+ end,
+ expr(ArgsList, S1).
+
+funarg({'fun', _, _Clauses, _Extra}, _S) -> true;
+funarg({var, _, Var}, S) -> member(Var, S#xrefr.funvars);
+funarg({call,_,{remote,_,{atom,_,erlang},{atom,_,make_fun}},_MFA}, _S) ->
+ %% R10B-6. M:F/A.
+ true;
+funarg(_, _S) -> false.
+
+fun_args(apply2, [FunArg, Args]) -> {FunArg, Args};
+fun_args(1, [FunArg | Args]) -> {FunArg, Args};
+fun_args(2, [_Node, FunArg | Args]) -> {FunArg, Args}.
+
+list2term([A | As]) ->
+ {cons, 0, A, list2term(As)};
+list2term([]) ->
+ {nil, 0}.
+
+term2list({cons, _Line, H, T}, L, S) ->
+ term2list(T, [H | L], S);
+term2list({nil, _Line}, L, _S) ->
+ reverse(L);
+term2list({var, _, Var}, L, S) ->
+ case keysearch(Var, 1, S#xrefr.matches) of
+ {value, {Var, E}} ->
+ term2list(E, L, S);
+ false ->
+ undefined
+ end;
+term2list(_Else, _L, _S) ->
+ undefined.
+
+unresolved(M, F, A, Line, S) ->
+ handle_call(external, {M,F,A}, Line, S, true).
+
+handle_call(Locality, Module, Name, Arity, Line, S) ->
+ case xref_utils:is_builtin(Module, Name, Arity) of
+ true when not S#xrefr.builtins_too -> S;
+ _Else ->
+ To = {Module, Name, Arity},
+ handle_call(Locality, To, Line, S, false)
+ end.
+
+handle_call(_Locality, {_, 'MNEMOSYNE RULE',1}, _Line, S, _) -> S;
+handle_call(_Locality, {_, 'MNEMOSYNE QUERY', 2}, _Line, S, _) -> S;
+handle_call(_Locality, {_, 'MNEMOSYNE RECFUNDEF',1}, _Line, S, _) -> S;
+handle_call(Locality, To0, Line, S, IsUnres) ->
+ From = S#xrefr.function,
+ To = adjust_arity(S, To0),
+ Call = {From, To},
+ CallAt = {Call, Line},
+ S1 = if
+ IsUnres ->
+ S#xrefr{unresolved = [CallAt | S#xrefr.unresolved]};
+ true ->
+ S
+ end,
+ case Locality of
+ local ->
+ S1#xrefr{el = [Call | S1#xrefr.el],
+ l_call_at = [CallAt | S1#xrefr.l_call_at]};
+ external ->
+ S1#xrefr{ex = [Call | S1#xrefr.ex],
+ x_call_at = [CallAt | S1#xrefr.x_call_at]}
+ end.
+
+adjust_arity(#xrefr{is_abstr = true, module = M}, {M, F, A} = MFA) ->
+ case xref_utils:is_static_function(F, A) of
+ true ->
+ MFA;
+ false ->
+ {M,F,A-1}
+ end;
+adjust_arity(_S, MFA) ->
+ MFA.