aboutsummaryrefslogtreecommitdiffstats
path: root/lib/dialyzer/src/dialyzer_dep.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/dialyzer/src/dialyzer_dep.erl')
-rw-r--r--lib/dialyzer/src/dialyzer_dep.erl139
1 files changed, 94 insertions, 45 deletions
diff --git a/lib/dialyzer/src/dialyzer_dep.erl b/lib/dialyzer/src/dialyzer_dep.erl
index febb65b766..273c05c54c 100644
--- a/lib/dialyzer/src/dialyzer_dep.erl
+++ b/lib/dialyzer/src/dialyzer_dep.erl
@@ -2,18 +2,19 @@
%%-----------------------------------------------------------------------
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2010. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2016. 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/.
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
%%
-%% 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.
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
%%
%% %CopyrightEnd%
%%
@@ -39,7 +40,7 @@
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
-%% analyze(CoreTree) -> {Deps, Esc, Calls}.
+%% analyze(CoreTree) -> {Deps, Esc, Calls, Letrecs}.
%%
%% Deps = a dict mapping labels of functions to an ordset of functions
%% it calls.
@@ -53,18 +54,30 @@
%% which the operation can refer to. If 'external' is part of
%% the set the operation can be externally defined.
%%
+%% Letrecs = a dict mapping var labels to their recursive definition.
+%% top-level letrecs are not included as they are handled
+%% separately.
+%%
+
+-type dep_ordset() :: ordsets:ordset(label() | 'external').
+
+-type deps() :: dict:dict(label() | 'external' | 'top', dep_ordset()).
+-type esc() :: dep_ordset().
+-type calls() :: dict:dict(label(), ordsets:ordset(label())).
+-type letrecs() :: dict:dict(label(), label()).
--spec analyze(cerl:c_module()) -> {dict(), ordset('external' | label()), dict()}.
+-spec analyze(cerl:c_module()) -> {deps(), esc(), calls(), letrecs()}.
analyze(Tree) ->
%% io:format("Handling ~w\n", [cerl:atom_val(cerl:module_name(Tree))]),
{_, State} = traverse(Tree, map__new(), state__new(Tree), top),
Esc = state__esc(State),
- %% Add dependency from 'external' to all escaping function
+ %% Add dependency from 'external' to all escaping functions
State1 = state__add_deps(external, output(Esc), State),
Deps = state__deps(State1),
Calls = state__calls(State1),
- {map__finalize(Deps), set__to_ordsets(Esc), map__finalize(Calls)}.
+ Letrecs = state__letrecs(State1),
+ {map__finalize(Deps), set__to_ordsets(Esc), map__finalize(Calls), Letrecs}.
traverse(Tree, Out, State, CurrentFun) ->
%% io:format("Type: ~w\n", [cerl:type(Tree)]),
@@ -72,22 +85,26 @@ traverse(Tree, Out, State, CurrentFun) ->
apply ->
Op = cerl:apply_op(Tree),
Args = cerl:apply_args(Tree),
- %% Op is always a variable and should not be marked as escaping
- %% based on its use.
case var =:= cerl:type(Op) of
- false -> erlang:error({apply_op_not_a_variable, cerl:type(Op)});
- true -> ok
- end,
- OpFuns = case map__lookup(cerl_trees:get_label(Op), Out) of
- none -> output(none);
- {value, OF} -> OF
- end,
- {ArgFuns, State2} = traverse_list(Args, Out, State, CurrentFun),
- State3 = state__add_esc(merge_outs(ArgFuns), State2),
- State4 = state__add_deps(CurrentFun, OpFuns, State3),
- State5 = state__store_callsite(cerl_trees:get_label(Tree),
- OpFuns, length(Args), State4),
- {output(set__singleton(external)), State5};
+ false ->
+ %% We have discovered an error here, but we ignore it and let
+ %% later passes handle it; we do not modify the dependencies.
+ %% erlang:error({apply_op_not_a_variable, cerl:type(Op)});
+ {output(none), State};
+ true ->
+ %% Op is a variable and should not be marked as escaping
+ %% based on its use.
+ OpFuns = case map__lookup(cerl_trees:get_label(Op), Out) of
+ none -> output(none);
+ {value, OF} -> OF
+ end,
+ {ArgFuns, State2} = traverse_list(Args, Out, State, CurrentFun),
+ State3 = state__add_esc(merge_outs(ArgFuns), State2),
+ State4 = state__add_deps(CurrentFun, OpFuns, State3),
+ State5 = state__store_callsite(cerl_trees:get_label(Tree),
+ OpFuns, length(Args), State4),
+ {output(set__singleton(external)), State5}
+ end;
binary ->
{output(none), State};
'case' ->
@@ -118,8 +135,10 @@ traverse(Tree, Out, State, CurrentFun) ->
TmpState = state__add_deps(Label, O1, State),
state__add_deps(CurrentFun, O2,TmpState)
end,
- {BodyFuns, State2} = traverse(Body, Out, State1,
- cerl_trees:get_label(Tree)),
+ Vars = cerl:fun_vars(Tree),
+ Out1 = bind_single(Vars, output(set__singleton(external)), Out),
+ {BodyFuns, State2} =
+ traverse(Body, Out1, State1, cerl_trees:get_label(Tree)),
{output(set__singleton(Label)), state__add_esc(BodyFuns, State2)};
'let' ->
Vars = cerl:let_vars(Tree),
@@ -131,9 +150,12 @@ traverse(Tree, Out, State, CurrentFun) ->
letrec ->
Defs = cerl:letrec_defs(Tree),
Body = cerl:letrec_body(Tree),
+ State1 = lists:foldl(fun({ Var, Fun }, Acc) ->
+ state__add_letrecs(cerl_trees:get_label(Var), cerl_trees:get_label(Fun), Acc)
+ end, State, Defs),
Out1 = bind_defs(Defs, Out),
- State1 = traverse_defs(Defs, Out1, State, CurrentFun),
- traverse(Body, Out1, State1, CurrentFun);
+ State2 = traverse_defs(Defs, Out1, State1, CurrentFun),
+ traverse(Body, Out1, State2, CurrentFun);
literal ->
{output(none), State};
module ->
@@ -173,6 +195,15 @@ traverse(Tree, Out, State, CurrentFun) ->
Args = cerl:tuple_es(Tree),
{List, State1} = traverse_list(Args, Out, State, CurrentFun),
{merge_outs(List), State1};
+ map ->
+ Args = cerl:map_es(Tree),
+ {List, State1} = traverse_list(Args, Out, State, CurrentFun),
+ {merge_outs(List), State1};
+ map_pair ->
+ Key = cerl:map_pair_key(Tree),
+ Val = cerl:map_pair_val(Tree),
+ {List, State1} = traverse_list([Key,Val], Out, State, CurrentFun),
+ {merge_outs(List), State1};
values ->
traverse_list(cerl:values_es(Tree), Out, State, CurrentFun);
var ->
@@ -291,7 +322,7 @@ primop(Tree, ArgFuns, State) ->
%% Set
%%
--record(set, {set :: set()}).
+-record(set, {set :: sets:set()}).
set__singleton(Val) ->
#set{set = sets:add_element(Val, sets:new())}.
@@ -460,18 +491,30 @@ all_vars(Tree, AccIn) ->
-type local_set() :: 'none' | #set{}.
--record(state, {deps :: dict(),
+-record(state, {deps :: deps(),
esc :: local_set(),
- call :: dict(),
- arities :: dict()}).
+ calls :: calls(),
+ arities :: dict:dict(label() | 'top', arity()),
+ letrecs :: letrecs()}).
state__new(Tree) ->
Exports = set__from_list([X || X <- cerl:module_exports(Tree)]),
- InitEsc = set__from_list([cerl_trees:get_label(Fun)
- || {Var, Fun} <- cerl:module_defs(Tree),
- set__is_element(Var, Exports)]),
+ %% get the labels of all exported functions
+ ExpLs = [cerl_trees:get_label(Fun) || {Var, Fun} <- cerl:module_defs(Tree),
+ set__is_element(Var, Exports)],
+ %% make sure to also initiate an analysis from all functions called
+ %% from on_load attributes; in Core these exist as a list of {F,A} pairs
+ OnLoadFAs = lists:flatten([cerl:atom_val(Args)
+ || {Attr, Args} <- cerl:module_attrs(Tree),
+ cerl:atom_val(Attr) =:= on_load]),
+ OnLoadLs = [cerl_trees:get_label(Fun)
+ || {Var, Fun} <- cerl:module_defs(Tree),
+ lists:member(cerl:var_name(Var), OnLoadFAs)],
+ %% init the escaping function labels to exported + called from on_load
+ InitEsc = set__from_list(OnLoadLs ++ ExpLs),
Arities = cerl_trees:fold(fun find_arities/2, dict:new(), Tree),
- #state{deps = map__new(), esc = InitEsc, call = map__new(), arities = Arities}.
+ #state{deps = map__new(), esc = InitEsc, calls = map__new(),
+ arities = Arities, letrecs = map__new()}.
find_arities(Tree, AccMap) ->
case cerl:is_c_fun(Tree) of
@@ -485,14 +528,20 @@ find_arities(Tree, AccMap) ->
state__add_deps(_From, #output{content = none}, State) ->
State;
-state__add_deps(From, #output{type = single, content=To},
+state__add_deps(From, #output{type = single, content = To},
#state{deps = Map} = State) ->
%% io:format("Adding deps from ~w to ~w\n", [From, set__to_ordsets(To)]),
State#state{deps = map__add(From, To, Map)}.
+state__add_letrecs(Var, Fun, #state{letrecs = Map} = State) ->
+ State#state{letrecs = map__store(Var, Fun, Map)}.
+
state__deps(#state{deps = Deps}) ->
Deps.
+state__letrecs(#state{letrecs = Letrecs}) ->
+ Letrecs.
+
state__add_esc(#output{content = none}, State) ->
State;
state__add_esc(#output{type = single, content = Set},
@@ -505,16 +554,16 @@ state__esc(#state{esc = Esc}) ->
state__store_callsite(_From, #output{content = none}, _CallArity, State) ->
State;
state__store_callsite(From, To, CallArity,
- #state{call = Calls, arities = Arities} = State) ->
+ #state{calls = Calls, arities = Arities} = State) ->
Filter = fun(external) -> true;
(Fun) -> CallArity =:= dict:fetch(Fun, Arities)
end,
case filter_outs(To, Filter) of
#output{content = none} -> State;
- To1 -> State#state{call = map__store(From, To1, Calls)}
+ To1 -> State#state{calls = map__store(From, To1, Calls)}
end.
-state__calls(#state{call = Calls}) ->
+state__calls(#state{calls = Calls}) ->
Calls.
%%------------------------------------------------------------