1
%% ``The contents of this file are subject to the Erlang Public License,
2
%% Version 1.1, (the "License"); you may not use this file except in
3
%% compliance with the License. You should have received a copy of the
4
%% Erlang Public License along with this software. If not, it can be
5
%% retrieved via the world wide web at http://www.erlang.org/.
7
%% Software distributed under the License is distributed on an "AS IS"
8
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
9
%% the License for the specific language governing rights and limitations
12
%% Copyright 2006, Tobias Lindahl and Kostis Sagonas
17
%%%-------------------------------------------------------------------
18
%%% File : dialyzer_dep.erl
19
%%% Author : Tobias Lindahl <tobiasl@it.uu.se>
21
%%% Description: A pretty limited but efficient escape/dependency
22
%%% analysis of Core Erlang.
24
%%% Created : 28 Oct 2005 by Tobias Lindahl <tobiasl@it.uu.se>
25
%%%-------------------------------------------------------------------
26
-module(dialyzer_dep).
28
-define(NO_UNUSED, true).
35
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
37
%% analyze(CoreTree) -> {Deps, Esc, Calls}.
39
%% Deps = a dict mapping labels of functions to a ordset of functions it calls.
41
%% Esc = a set (ordsets) of the labels of escaping functions. A function
42
%% is considered to escape if the control escapes a function,
43
%% i.e., this analysis is not module-local but rather
46
%% Calls = a dict mapping apply:s to a ordset of function labels to which
47
%% the operation can refer to. If 'external' is part of the
48
%% set the operation can be externally defined.
52
%%io:format("Handling ~w\n", [cerl:atom_val(cerl:module_name(Tree))]),
53
{_, State} = traverse(Tree, map__new(), state__new(Tree), top),
54
Esc = state__esc(State),
55
%% Add dependency from 'external' to all escaping function
56
State1 = state__add_deps(external, output(Esc), State),
57
Deps = state__deps(State1),
58
Calls = state__calls(State1),
59
{map__finalize(Deps), set__to_ordsets(Esc), map__finalize(Calls)}.
61
traverse(Tree, Out, State, CurrentFun) ->
62
%%io:format("Type: ~w\n", [cerl:type(Tree)]),
63
case cerl:type(Tree) of
65
Op = cerl:apply_op(Tree),
66
Args = cerl:apply_args(Tree),
67
%% Op is always a variable and should not be marked as escaping
69
case var =:= cerl:type(Op) of
70
false -> erlang:fault({apply_op_not_a_variable, cerl:type(Op)});
73
OpFuns = case map__lookup(cerl_trees:get_label(Op), Out) of
77
{ArgFuns, State2} = traverse_list(Args, Out, State, CurrentFun),
78
State3 = state__add_esc(merge_outs(ArgFuns), State2),
79
State4 = state__add_deps(CurrentFun, OpFuns, State3),
80
State5 = state__store_callsite(cerl_trees:get_label(Tree), OpFuns,State4),
81
{output(set__singleton(external)), State5};
83
{output(none), State};
85
Arg = cerl:case_arg(Tree),
86
{Funs, NewState} = traverse(Arg, Out, State, CurrentFun),
87
Clauses = cerl:case_clauses(Tree),
88
traverse_clauses(Clauses, Funs, Out, NewState, CurrentFun);
90
Args = cerl:call_args(Tree),
91
{ArgFuns, State1} = traverse_list(Args, Out, State, CurrentFun),
92
remote_call(Tree, merge_outs(ArgFuns), State1);
94
traverse(cerl:catch_body(Tree), Out, State, CurrentFun);
96
{HdFuns, State1} = traverse(cerl:cons_hd(Tree), Out, State, CurrentFun),
97
{TlFuns, State2} = traverse(cerl:cons_tl(Tree), Out, State1, CurrentFun),
98
{merge_outs([HdFuns, TlFuns]), State2};
100
%%io:format("Entering fun: ~w\n", [cerl_trees:get_label(Tree)]),
101
Body = cerl:fun_body(Tree),
102
Label = cerl_trees:get_label(Tree),
103
if CurrentFun =:= top ->
104
State1 = state__add_deps(top, output(set__singleton(Label)), State);
106
O1 = output(set__singleton(CurrentFun)),
107
O2 = output(set__singleton(Label)),
108
TmpState = state__add_deps(Label, O1, State),
109
State1 = state__add_deps(CurrentFun, O2,TmpState)
111
{BodyFuns, State2} = traverse(Body, Out, State1,
112
cerl_trees:get_label(Tree)),
113
{output(set__singleton(Label)), state__add_esc(BodyFuns, State2)};
115
Vars = cerl:let_vars(Tree),
116
Arg = cerl:let_arg(Tree),
117
Body = cerl:let_body(Tree),
118
{ArgFuns, State1} = traverse(Arg, Out, State, CurrentFun),
119
Out1 = bind_list(Vars, ArgFuns, Out),
120
traverse(Body, Out1, State1, CurrentFun);
122
Defs = cerl:letrec_defs(Tree),
123
Body = cerl:letrec_body(Tree),
124
Out1 = bind_defs(Defs, Out),
125
State1 = traverse_defs(Defs, Out1, State, CurrentFun),
126
traverse(Body, Out1, State1, CurrentFun);
128
{output(none), State};
130
Defs = cerl:module_defs(Tree),
131
Out1 = bind_defs(Defs, Out),
132
State1 = traverse_defs(Defs, Out1, State, CurrentFun),
133
{output(none), State1};
135
Args = cerl:primop_args(Tree),
136
{ArgFuns, State1} = traverse_list(Args, Out, State, CurrentFun),
137
primop(Tree, merge_outs(ArgFuns), State1);
139
Clauses = cerl:receive_clauses(Tree),
140
TimeOut = cerl:receive_timeout(Tree),
141
Action = cerl:receive_action(Tree),
142
{ClauseFuns, State1} =
143
traverse_clauses(Clauses, output(none), Out, State, CurrentFun),
144
{_, State2} = traverse(TimeOut, Out, State1, CurrentFun),
145
{ActionFuns, State3} = traverse(Action, Out, State2, CurrentFun),
146
{merge_outs([ClauseFuns, ActionFuns]), State3};
148
{_, State1} = traverse(cerl:seq_arg(Tree), Out, State, CurrentFun),
149
traverse(cerl:seq_body(Tree), Out, State1, CurrentFun);
151
Arg = cerl:try_arg(Tree),
152
Body = cerl:try_body(Tree),
153
Vars = cerl:try_vars(Tree),
154
EVars = cerl:try_evars(Tree),
155
Handler = cerl:try_handler(Tree),
156
{ArgFuns, State1} = traverse(Arg, Out, State, CurrentFun),
157
Out1 = bind_list(Vars, ArgFuns, Out),
158
{BodyFuns, State2} = traverse(Body, Out1, State1, CurrentFun),
159
Out2 = bind_single(EVars, output(set__singleton(external)), Out),
160
{HandlerFuns, State3} = traverse(Handler, Out2, State2, CurrentFun),
161
{merge_outs([BodyFuns, HandlerFuns]), State3};
163
Args = cerl:tuple_es(Tree),
164
{List, State1} = traverse_list(Args, Out, State, CurrentFun),
165
{merge_outs(List), State1};
167
traverse_list(cerl:values_es(Tree), Out, State, CurrentFun);
169
case map__lookup(cerl_trees:get_label(Tree), Out) of
170
none -> {output(none), State};
172
case is_only_external(Val) of
177
%% If this is used in a function this means a dependency.
178
{Val, state__add_deps(CurrentFun, Val, State)}
183
traverse_list(Trees, Out, State, CurrentFun) ->
184
traverse_list(Trees, Out, State, CurrentFun, []).
186
traverse_list([Tree|Left], Out, State, CurrentFun, Acc) ->
187
{X, State1} = traverse(Tree, Out, State, CurrentFun),
188
traverse_list(Left, Out, State1, CurrentFun, [X|Acc]);
189
traverse_list([], _Out, State, _CurrentFun, Acc) ->
190
{output(lists:reverse(Acc)), State}.
192
traverse_defs([{_, Fun}|Left], Out, State, CurrentFun) ->
193
{_, State1} = traverse(Fun, Out, State, CurrentFun),
194
traverse_defs(Left, Out, State1, CurrentFun);
195
traverse_defs([], _Out, State, _CurrentFun) ->
198
traverse_clauses(Clauses, ArgFuns, Out, State, CurrentFun) ->
199
case filter_match_fail(Clauses) of
201
%% Can happen for example with receives used as timouts.
202
{output(none), State};
204
traverse_clauses(Clauses1, ArgFuns, Out, State, CurrentFun, [])
207
traverse_clauses([Clause|Left], ArgFuns, Out, State, CurrentFun, Acc) ->
208
Pats = cerl:clause_pats(Clause),
209
Guard = cerl:clause_guard(Clause),
210
Body = cerl:clause_body(Clause),
211
Out1 = bind_pats_list(Pats, ArgFuns, Out),
212
{_, State2} = traverse(Guard, Out1, State, CurrentFun),
213
{BodyFuns, State3} = traverse(Body, Out1, State2, CurrentFun),
214
traverse_clauses(Left, ArgFuns, Out, State3, CurrentFun, [BodyFuns|Acc]);
215
traverse_clauses([], _ArgFuns, _Out, State, _CurrentFun, Acc) ->
216
{merge_outs(Acc), State}.
218
filter_match_fail([Clause]) ->
219
Body = cerl:clause_body(Clause),
220
case cerl:type(Body) of
222
case cerl:atom_val(cerl:primop_name(Body)) of
229
filter_match_fail([H|T]) ->
230
[H|filter_match_fail(T)];
231
filter_match_fail([]) ->
232
%% This can actually happen, for example in
233
%% receive after 1 -> ok end
236
remote_call(Tree, ArgFuns, State) ->
237
M = cerl:call_module(Tree),
238
F = cerl:call_name(Tree),
239
A = length(cerl:call_args(Tree)),
240
case cerl:is_c_atom(M) andalso cerl:is_c_atom(F) of
243
{output(set__singleton(external)), state__add_esc(ArgFuns, State)};
245
M1 = cerl:atom_val(M),
246
F1 = cerl:atom_val(F),
247
Literal = cerl_closurean:is_literal_op(M1, F1, A),
248
case erl_bifs:is_pure(M1, F1, A) of
252
{output(none), State};
254
{output(set__singleton(external)), state__add_esc(ArgFuns, State)}
257
State1 = case cerl_closurean:is_escape_op(M1, F1, A) of
258
true -> state__add_esc(ArgFuns, State);
262
true -> {output(none), State1};
263
false -> {add_external(ArgFuns), State1}
268
primop(Tree, ArgFuns, State) ->
269
F = cerl:atom_val(cerl:primop_name(Tree)),
270
A = length(cerl:primop_args(Tree)),
271
State1 = case cerl_closurean:is_escape_op(F, A) of
272
true -> state__add_esc(ArgFuns, State);
275
case cerl_closurean:is_literal_op(F, A) of
276
true -> {output(none), State1};
277
false -> {ArgFuns, State1}
280
%%____________________________________________________________
287
set__singleton(Val) ->
288
#set{set=sets:add_element(Val, sets:new())}.
290
set__from_list(List) ->
291
#set{set=sets:from_list(List)}.
293
set__is_element(_El, none) ->
295
set__is_element(El, #set{set=Set}) ->
296
sets:is_element(El, Set).
298
set__union(none, X) -> X;
299
set__union(X, none) -> X;
300
set__union(#set{set=X}, #set{set=Y}) -> #set{set=sets:union(X, Y)}.
302
set__to_ordsets(none) -> [];
303
set__to_ordsets(#set{set=Set}) -> ordsets:from_list(sets:to_list(Set)).
305
set__size(none) -> 0;
306
set__size(#set{set=X}) -> sets:size(X).
308
%%____________________________________________________________
313
%% #output{type = single|list,
314
%% content = #set{} | [#output{}] | none}
316
-record(output, {type, content}).
318
output(none) -> #output{type=single, content=none};
319
output(S = #set{}) -> #output{type=single, content=S};
320
output(List) when is_list(List) -> #output{type=list, content=List}.
324
merge_outs(#output{type=list, content=[H|T]}) ->
326
merge_outs(#output{type=list, content=[]}) ->
329
merge_outs([#output{content=none}|Left], O) ->
331
merge_outs([O|Left], #output{content=none}) ->
333
merge_outs([#output{type=single, content=S1}|Left],
334
#output{type=single, content=S2}) ->
335
merge_outs(Left, output(set__union(S1, S2)));
336
merge_outs([#output{type=list, content=L1}|Left],
337
#output{type=list, content=L2}) ->
338
NewList = [merge_outs([X, Y]) || {X, Y} <- lists:zip(L1, L2)],
339
merge_outs(Left, output(NewList));
340
merge_outs([], Res) ->
343
add_external(#output{type=single, content=Set}) ->
344
output(set__union(Set, set__singleton(external)));
345
add_external(#output{type=list, content=List}) ->
346
output([add_external(O) || O <- List]).
348
is_only_external(#output{type=single, content=Set}) ->
349
set__is_element(external, Set) andalso (set__size(Set) =:= 1).
351
%%____________________________________________________________
359
map__add(_Label, none, Map) ->
361
map__add(Label, Set, Map) ->
362
case map__lookup(Label, Map) of
364
NewSet = set__union(OldSet, Set),
365
map__store(Label, NewSet, Map);
367
map__store(Label, Set, Map)
370
map__store(Label, Val, Map) ->
371
dict:store(Label, Val, Map).
373
map__lookup(Label, Map) ->
374
case dict:find(Label, Map) of
375
{ok, Val} -> {value, Val};
379
map__finalize(Map) ->
380
dict:map(fun(_Key, Set = #set{}) -> set__to_ordsets(Set);
381
(_Key, #output{type=single, content=Set}) -> set__to_ordsets(Set)
384
%%____________________________________________________________
386
%% Binding outs in the map
389
bind_pats_list(_Pats, #output{content=none}, Map) ->
391
bind_pats_list([Pat], O = #output{type=single}, Map) ->
392
bind_single(all_vars(Pat), O, Map);
393
bind_pats_list(Pats, #output{type=list, content=List}, Map) ->
394
bind_pats_list(Pats, List, Map);
395
bind_pats_list([Pat|PatLeft],
396
[O = #output{type=single}|SetLeft], Map)->
397
Map1 = bind_single(all_vars(Pat), O, Map),
398
bind_pats_list(PatLeft, SetLeft, Map1);
399
bind_pats_list([Pat|PatLeft],
400
[#output{type=list, content=List}|SetLeft], Map)->
401
case cerl:is_c_values(Pat) of
402
true -> Map1 = bind_pats_list(cerl:values_es(Pat), List, Map);
403
false -> Map1 = bind_single(all_vars(Pat), merge_outs(List), Map)
405
bind_pats_list(PatLeft, SetLeft, Map1);
406
bind_pats_list([], [], Map) ->
409
bind_single([Var|Left], O, Map) ->
410
bind_single(Left, O, map__store(cerl_trees:get_label(Var), O, Map));
411
bind_single([], _O, Map) ->
414
bind_list(List, O = #output{type=single}, Map) ->
415
bind_single(List, O, Map);
416
bind_list(List1, #output{type=list, content=List2}, Map) ->
417
bind_list1(List1, List2, Map).
419
bind_list1([Var|VarLeft], [O|OLeft], Map) ->
420
bind_list1(VarLeft, OLeft, map__store(cerl_trees:get_label(Var), O, Map));
421
bind_list1([], [], Map) ->
424
bind_defs([{Var, Fun}|Left], Map) ->
425
O = output(set__singleton(cerl_trees:get_label(Fun))),
426
Map1 = map__store(cerl_trees:get_label(Var), O, Map),
427
bind_defs(Left, Map1);
428
bind_defs([], Map) ->
434
all_vars(Tree, AccIn) ->
435
cerl_trees:fold(fun(SubTree, Acc) ->
436
case cerl:is_c_var(SubTree) of
437
true -> [SubTree|Acc];
442
%%____________________________________________________________
446
-record(state, {deps, esc, call}).
449
Exports = set__from_list([X || X <- cerl:module_exports(Tree)]),
450
InitEsc = set__from_list([cerl_trees:get_label(Fun)
451
|| {Var, Fun} <- cerl:module_defs(Tree),
452
set__is_element(Var, Exports)]),
453
#state{deps=map__new(), esc=InitEsc, call=map__new()}.
455
state__add_deps(_From, #output{content=none}, State) ->
457
state__add_deps(From, #output{type=single, content=To},
458
State = #state{deps=Map}) ->
459
%%io:format("Adding deps from ~w to ~w\n", [From, set__to_ordsets(To)]),
460
State#state{deps=map__add(From, To, Map)}.
462
state__deps(#state{deps=Deps}) ->
465
state__add_esc(#output{content=none}, State) ->
467
state__add_esc(#output{type=single, content=Set}, State = #state{esc=Esc}) ->
468
State#state{esc=set__union(Set, Esc)}.
470
state__esc(#state{esc=Esc}) ->
473
state__store_callsite(_From, #output{content=none}, State) ->
475
state__store_callsite(From, To, State = #state{call=Calls}) ->
476
State#state{call=map__store(From, To, Calls)}.
478
state__calls(#state{call=Calls}) ->
481
%%____________________________________________________________
483
%% A test function. Not part of the intended interface.
489
{ok, _, Code} = compile:file(Mod, [to_core,binary]),
490
Tree = cerl:from_records(Code),
491
{LabeledTree, _} = cerl_trees:label(Tree),
493
%%io:put_chars(cerl_prettypr:format(LabeledTree)),
496
{Deps, Esc, Calls} = analyze(LabeledTree),
497
Edges0 = dict:fold(fun(Caller, Set, Acc) ->
498
[[{Caller, Callee} || Callee <- Set]|Acc]
500
Edges1 = lists:flatten(Edges0),
501
Edges = [{X,Y} || {X,Y} <- Edges1, X =/= top],
502
Fun = fun(SubTree, Acc) ->
503
case cerl:type(SubTree) of
505
case lists:keysearch(id, 1, cerl:get_ann(SubTree)) of
508
dict:store(cerl_trees:get_label(SubTree), ID, Acc)
511
Defs = cerl:module_defs(SubTree),
512
lists:foldl(fun({Var, Fun}, Acc1) ->
513
dict:store(cerl_trees:get_label(Fun),
515
cerl:fname_arity(Var)},
519
Defs = cerl:letrec_defs(SubTree),
520
lists:foldl(fun({Var, Fun}, Acc1) ->
521
dict:store(cerl_trees:get_label(Fun),
523
cerl:fname_arity(Var)},
529
NameMap1 = cerl_trees:fold(Fun, dict:new(), LabeledTree),
530
NameMap = dict:store(external, external, NameMap1),
531
NamedEdges = lists:map(fun({X, Y}) ->
532
{dict:fetch(X, NameMap), dict:fetch(Y, NameMap)}
534
NamedEsc = lists:map(fun(X) -> dict:fetch(X, NameMap) end, Esc),
536
ColorEsc = [{X, {color, red}} || X <- NamedEsc],
538
CallEdges0 = dict:fold(fun(Caller, Set, Acc) ->
539
[[{Caller, Callee} || Callee <- Set]|Acc]
541
CallEdges = lists:flatten(CallEdges0),
542
NamedCallEdges = lists:map(fun({X, Y}) ->
543
{X, dict:fetch(Y, NameMap)}
546
hipe_dot:translate_list(NamedEdges ++ NamedCallEdges, "/tmp/cg.dot", "CG",
548
os:cmd("dot -T ps -o /tmp/cg.ps /tmp/cg.dot"),