aboutsummaryrefslogtreecommitdiffstats
path: root/lib/tools/src
diff options
context:
space:
mode:
Diffstat (limited to 'lib/tools/src')
-rw-r--r--lib/tools/src/Makefile3
-rw-r--r--lib/tools/src/cover.erl293
-rw-r--r--lib/tools/src/cover_web.erl1185
-rw-r--r--lib/tools/src/cprof.erl3
-rw-r--r--lib/tools/src/eprof.erl46
-rw-r--r--lib/tools/src/fprof.erl117
-rw-r--r--lib/tools/src/instrument.erl2
-rw-r--r--lib/tools/src/lcnt.erl252
-rw-r--r--lib/tools/src/make.erl148
-rw-r--r--lib/tools/src/tags.erl6
-rw-r--r--lib/tools/src/tools.app.src10
-rw-r--r--lib/tools/src/tools.appup.src2
-rw-r--r--lib/tools/src/xref.erl2
-rw-r--r--lib/tools/src/xref_base.erl265
-rw-r--r--lib/tools/src/xref_parser.yrl6
-rw-r--r--lib/tools/src/xref_reader.erl73
-rw-r--r--lib/tools/src/xref_utils.erl42
17 files changed, 787 insertions, 1668 deletions
diff --git a/lib/tools/src/Makefile b/lib/tools/src/Makefile
index 9fcfb79628..032bd612db 100644
--- a/lib/tools/src/Makefile
+++ b/lib/tools/src/Makefile
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 1996-2013. All Rights Reserved.
+# Copyright Ericsson AB 1996-2016. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -37,7 +37,6 @@ RELSYSDIR = $(RELEASE_PATH)/lib/tools-$(VSN)
MODULES= \
cover \
- cover_web \
eprof \
fprof \
cprof \
diff --git a/lib/tools/src/cover.erl b/lib/tools/src/cover.erl
index 8d1cb96504..4e64d7aa4e 100644
--- a/lib/tools/src/cover.erl
+++ b/lib/tools/src/cover.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2001-2015. All Rights Reserved.
+%% Copyright Ericsson AB 2001-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -20,9 +20,7 @@
-module(cover).
%%
-%% This module implements the Erlang coverage tool. The module named
-%% cover_web implements a user interface for the coverage tool to run
-%% under webtool.
+%% This module implements the Erlang coverage tool.
%%
%% ARCHITECTURE
%% The coverage tool consists of one process on each node involved in
@@ -575,7 +573,7 @@ call(Request) ->
Ref = erlang:monitor(process,?SERVER),
receive {'DOWN', Ref, _Type, _Object, noproc} ->
erlang:demonitor(Ref),
- start(),
+ {ok,_} = start(),
call(Request)
after 0 ->
?SERVER ! {self(),Request},
@@ -591,7 +589,9 @@ call(Request) ->
end.
reply(From, Reply) ->
- From ! {?SERVER,Reply}.
+ From ! {?SERVER,Reply},
+ ok.
+
is_from(From) ->
is_pid(From).
@@ -617,9 +617,11 @@ remote_call(Node,Request) ->
end.
remote_reply(Proc,Reply) when is_pid(Proc) ->
- Proc ! {?SERVER,Reply};
+ Proc ! {?SERVER,Reply},
+ ok;
remote_reply(MainNode,Reply) ->
- {?SERVER,MainNode} ! {?SERVER,Reply}.
+ {?SERVER,MainNode} ! {?SERVER,Reply},
+ ok.
%%%----------------------------------------------------------------------
%%% cover_server on main node
@@ -629,14 +631,16 @@ init_main(Starter) ->
register(?SERVER,self()),
%% Having write concurrancy here gives a 40% performance boost
%% when collect/1 is called.
- ets:new(?COVER_TABLE, [set, public, named_table
- ,{write_concurrency, true}
- ]),
- ets:new(?COVER_CLAUSE_TABLE, [set, public, named_table]),
- ets:new(?BINARY_TABLE, [set, public, named_table]),
- ets:new(?COLLECTION_TABLE, [set, public, named_table]),
- ets:new(?COLLECTION_CLAUSE_TABLE, [set, public, named_table]),
- net_kernel:monitor_nodes(true),
+ ?COVER_TABLE = ets:new(?COVER_TABLE, [set, public, named_table,
+ {write_concurrency, true}]),
+ ?COVER_CLAUSE_TABLE = ets:new(?COVER_CLAUSE_TABLE, [set, public,
+ named_table]),
+ ?BINARY_TABLE = ets:new(?BINARY_TABLE, [set, public, named_table]),
+ ?COLLECTION_TABLE = ets:new(?COLLECTION_TABLE, [set, public,
+ named_table]),
+ ?COLLECTION_CLAUSE_TABLE = ets:new(?COLLECTION_CLAUSE_TABLE, [set, public,
+ named_table]),
+ ok = net_kernel:monitor_nodes(true),
Starter ! {?SERVER,started},
main_process_loop(#main_state{}).
@@ -674,7 +678,7 @@ main_process_loop(State) ->
Imported = do_import_to_table(Fd,File,
State#main_state.imported),
reply(From, ok),
- file:close(Fd),
+ ok = file:close(Fd),
main_process_loop(State#main_state{imported=Imported});
{error,Reason} ->
reply(From, {error, {cant_open_file,File,Reason}}),
@@ -872,11 +876,12 @@ main_process_loop(State) ->
init_remote(Starter,MainNode) ->
register(?SERVER,self()),
- ets:new(?COVER_TABLE, [set, public, named_table
- %% write_concurrency here makes otp_8270 break :(
- %,{write_concurrency, true}
- ]),
- ets:new(?COVER_CLAUSE_TABLE, [set, public, named_table]),
+ %% write_concurrency here makes otp_8270 break :(
+ ?COVER_TABLE = ets:new(?COVER_TABLE, [set, public, named_table
+ %,{write_concurrency, true}
+ ]),
+ ?COVER_CLAUSE_TABLE = ets:new(?COVER_CLAUSE_TABLE, [set, public,
+ named_table]),
Starter ! {self(),started},
remote_process_loop(#remote_state{main_node=MainNode}).
@@ -909,11 +914,11 @@ remote_process_loop(State) ->
'_' -> [M || {M,_} <- State#remote_state.compiled];
_ -> Modules0
end,
- spawn(fun() ->
- ?SPAWN_DBG(remote_collect,
- {Modules, CollectorPid, From}),
- do_collect(Modules, CollectorPid, From)
- end),
+ spawn(fun() ->
+ ?SPAWN_DBG(remote_collect,
+ {Modules, CollectorPid, From}),
+ do_collect(Modules, CollectorPid, From)
+ end),
remote_process_loop(State);
{remote,stop} ->
@@ -954,13 +959,13 @@ remote_process_loop(State) ->
end.
do_collect(Modules, CollectorPid, From) ->
- pmap(
- fun(Module) ->
- Pattern = {#bump{module=Module, _='_'}, '$1'},
- MatchSpec = [{Pattern,[{'=/=','$1',0}],['$_']}],
- Match = ets:select(?COVER_TABLE,MatchSpec,?CHUNK_SIZE),
- send_chunks(Match, CollectorPid, [])
- end,Modules),
+ _ = pmap(
+ fun(Module) ->
+ Pattern = {#bump{module=Module, _='_'}, '$1'},
+ MatchSpec = [{Pattern,[{'=/=','$1',0}],['$_']}],
+ Match = ets:select(?COVER_TABLE,MatchSpec,?CHUNK_SIZE),
+ send_chunks(Match, CollectorPid, [])
+ end,Modules),
CollectorPid ! done,
remote_reply(From, ok).
@@ -996,20 +1001,20 @@ get_downs(Mons) ->
end.
reload_originals(Compiled) ->
- Modules = [M || {M,_} <- Compiled],
- pmap(fun do_reload_original/1, Modules).
+ _ = pmap(fun do_reload_original/1, [M || {M,_} <- Compiled]),
+ ok.
do_reload_original(Module) ->
case code:which(Module) of
?TAG ->
- code:purge(Module), % remove code marked as 'old'
- code:delete(Module), % mark cover compiled code as 'old'
+ _ = code:purge(Module), % remove code marked as 'old'
+ _ = code:delete(Module), % mark cover compiled code as 'old'
%% Note: original beam code must be loaded before the cover
%% compiled code is purged, in order to for references to
%% 'fun M:F/A' and %% 'fun F/A' funs to be correct (they
%% refer to (M:)F/A in the *latest* version of the module)
- code:load_file(Module), % load original code
- code:purge(Module); % remove cover compiled code
+ _ = code:load_file(Module), % load original code
+ _ = code:purge(Module); % remove cover compiled code
_ ->
ignore
end.
@@ -1221,12 +1226,13 @@ remote_reset(Module,Nodes) ->
%% Collect data from remote nodes - used for analyse or stop(Node)
remote_collect(Modules,Nodes,Stop) ->
- pmap(fun(Node) ->
- ?SPAWN_DBG(remote_collect,
- {Modules, Nodes, Stop}),
- do_collection(Node, Modules, Stop)
- end,
- Nodes).
+ _ = pmap(
+ fun(Node) ->
+ ?SPAWN_DBG(remote_collect,
+ {Modules, Nodes, Stop}),
+ do_collection(Node, Modules, Stop)
+ end, Nodes),
+ ok.
do_collection(Node, Module, Stop) ->
CollectorPid = spawn(fun collector_proc/0),
@@ -1262,8 +1268,8 @@ insert_in_collection_table([]) ->
insert_in_collection_table(Key,Val) ->
case ets:member(?COLLECTION_TABLE,Key) of
true ->
- ets:update_counter(?COLLECTION_TABLE,
- Key,Val);
+ _ = ets:update_counter(?COLLECTION_TABLE, Key,Val),
+ ok;
false ->
%% Make sure that there are no race conditions from ets:member
case ets:insert_new(?COLLECTION_TABLE,{Key,Val}) of
@@ -1474,7 +1480,7 @@ do_compile_beam(Module,BeamFile0,State) ->
{ok,Module,BeamFile};
error ->
{error, BeamFile};
- {error,Reason} -> % no abstract code
+ {error,Reason} -> % no abstract code or no 'file' attribute
{error, {Reason, BeamFile}}
end;
{error,no_beam} ->
@@ -1537,32 +1543,11 @@ do_compile_beam1(Module,Beam,UserOptions) ->
{error,E};
{raw_abstract_v1,Code} ->
Forms0 = epp:interpret_file_attribute(Code),
- {Forms,Vars} = transform(Forms0, Module),
-
- %% We need to recover the source from the compilation
- %% info otherwise the newly compiled module will have
- %% source pointing to the current directory
- SourceInfo = get_source_info(Module, Beam),
-
- %% Compile and load the result
- %% It's necessary to check the result of loading since it may
- %% fail, for example if Module resides in a sticky directory
- {ok, Module, Binary} = compile:forms(Forms, SourceInfo ++ UserOptions),
- case code:load_binary(Module, ?TAG, Binary) of
- {module, Module} ->
-
- %% Store info about all function clauses in database
- InitInfo = lists:reverse(Vars#vars.init_info),
- ets:insert(?COVER_CLAUSE_TABLE, {Module, InitInfo}),
-
- %% Store binary code so it can be loaded on remote nodes
- ets:insert(?BINARY_TABLE, {Module, Binary}),
-
- {ok, Module};
-
- _Error ->
- do_clear(Module),
- error
+ case find_main_filename(Forms0) of
+ {ok,MainFile} ->
+ do_compile_beam2(Module,Beam,UserOptions,Forms0,MainFile);
+ Error ->
+ Error
end;
{_VSN,_Code} ->
%% Wrong version of abstract code. Just report that there
@@ -1579,6 +1564,35 @@ get_abstract_code(Module, Beam) ->
Error -> Error
end.
+do_compile_beam2(Module,Beam,UserOptions,Forms0,MainFile) ->
+ {Forms,Vars} = transform(Forms0, Module, MainFile),
+
+ %% We need to recover the source from the compilation
+ %% info otherwise the newly compiled module will have
+ %% source pointing to the current directory
+ SourceInfo = get_source_info(Module, Beam),
+
+ %% Compile and load the result
+ %% It's necessary to check the result of loading since it may
+ %% fail, for example if Module resides in a sticky directory
+ {ok, Module, Binary} = compile:forms(Forms, SourceInfo ++ UserOptions),
+ case code:load_binary(Module, ?TAG, Binary) of
+ {module, Module} ->
+
+ %% Store info about all function clauses in database
+ InitInfo = lists:reverse(Vars#vars.init_info),
+ ets:insert(?COVER_CLAUSE_TABLE, {Module, InitInfo}),
+
+ %% Store binary code so it can be loaded on remote nodes
+ ets:insert(?BINARY_TABLE, {Module, Binary}),
+
+ {ok, Module};
+
+ _Error ->
+ do_clear(Module),
+ error
+ end.
+
get_source_info(Module, Beam) ->
Compile = get_compile_info(Module, Beam),
case lists:keyfind(source, 1, Compile) of
@@ -1601,8 +1615,7 @@ get_compile_info(Module, Beam) ->
[]
end.
-transform(Code, Module) ->
- MainFile=find_main_filename(Code),
+transform(Code, Module, MainFile) ->
Vars0 = #vars{module=Module},
{ok,MungedForms,Vars} = transform_2(Code,[],Vars0,MainFile,on),
{MungedForms,Vars}.
@@ -1610,9 +1623,12 @@ transform(Code, Module) ->
%% Helpfunction which returns the first found file-attribute, which can
%% be interpreted as the name of the main erlang source file.
find_main_filename([{attribute,_,file,{MainFile,_}}|_]) ->
- MainFile;
+ {ok,MainFile};
find_main_filename([_|Rest]) ->
- find_main_filename(Rest).
+ find_main_filename(Rest);
+find_main_filename([]) ->
+ {error, no_file_attribute}.
+
transform_2([Form0|Forms],MungedForms,Vars,MainFile,Switch) ->
Form = expand(Form0),
@@ -1994,9 +2010,7 @@ munge_expr({lc,Line,Expr,Qs}, Vars) ->
{MungedQs, Vars3} = munge_qualifiers(Qs, Vars2),
{{lc,Line,MungedExpr,MungedQs}, Vars3};
munge_expr({bc,Line,Expr,Qs}, Vars) ->
- {bin,BLine,[{bin_element,EL,Val,Sz,TSL}|Es]} = Expr,
- Expr2 = {bin,BLine,[{bin_element,EL,?BLOCK1(Val),Sz,TSL}|Es]},
- {MungedExpr,Vars2} = munge_expr(Expr2, Vars),
+ {MungedExpr,Vars2} = munge_expr(?BLOCK1(Expr), Vars),
{MungedQs, Vars3} = munge_qualifiers(Qs, Vars2),
{{bc,Line,MungedExpr,MungedQs}, Vars3};
munge_expr({block,Line,Body}, Vars) ->
@@ -2039,7 +2053,7 @@ munge_expr({bin_element,Line,Value,Size,TypeSpecifierList}, Vars) ->
{MungedValue,Vars2} = munge_expr(Value, Vars),
{MungedSize,Vars3} = munge_expr(Size, Vars2),
{{bin_element,Line,MungedValue,MungedSize,TypeSpecifierList},Vars3};
-munge_expr(Form, Vars) -> % var|char|integer|float|string|atom|nil|eof|default
+munge_expr(Form, Vars) ->
{Form, Vars}.
munge_exprs([Expr|Exprs], Vars, MungedExprs) when Vars#vars.is_guard=:=true,
@@ -2400,8 +2414,8 @@ do_analyse_to_file1(Module, OutFile, ErlFile, HTML) ->
{ok, InFd} ->
case file:open(OutFile, [write,raw,delayed_write]) of
{ok, OutFd} ->
+ Enc = encoding(ErlFile),
if HTML ->
- Encoding = encoding(ErlFile),
Header =
["<!DOCTYPE HTML PUBLIC "
"\"-//W3C//DTD HTML 3.2 Final//EN\">\n"
@@ -2409,13 +2423,14 @@ do_analyse_to_file1(Module, OutFile, ErlFile, HTML) ->
"<head>\n"
"<meta http-equiv=\"Content-Type\""
" content=\"text/html; charset=",
- Encoding,"\"/>\n"
+ html_encoding(Enc),"\"/>\n"
"<title>",OutFile,"</title>\n"
"</head>"
"<body style='background-color: white;"
" color: black'>\n"
"<pre>\n"],
- file:write(OutFd,Header);
+ H1Bin = unicode:characters_to_binary(Header,Enc,Enc),
+ ok = file:write(OutFd,H1Bin);
true -> ok
end,
@@ -2429,26 +2444,30 @@ do_analyse_to_file1(Module, OutFile, ErlFile, HTML) ->
string:right(integer_to_list(H), 2, $0),
string:right(integer_to_list(Mi), 2, $0),
string:right(integer_to_list(S), 2, $0)]),
- file:write(OutFd,
- ["File generated from ",ErlFile," by COVER ",
+
+ H2Bin = unicode:characters_to_binary(
+ ["File generated from ",ErlFile," by COVER ",
Timestamp,"\n\n"
"**************************************"
"**************************************"
- "\n\n"]),
+ "\n\n"],
+ Enc, Enc),
+ ok = file:write(OutFd, H2Bin),
Pattern = {#bump{module=Module,line='$1',_='_'},'$2'},
- MS = [{Pattern,[],[{{'$1','$2'}}]}],
- CovLines = lists:keysort(1,ets:select(?COLLECTION_TABLE, MS)),
+ MS = [{Pattern,[{is_integer,'$1'},{'>','$1',0}],[{{'$1','$2'}}]}],
+ CovLines0 =
+ lists:keysort(1, ets:select(?COLLECTION_TABLE, MS)),
+ CovLines = merge_dup_lines(CovLines0),
print_lines(Module, CovLines, InFd, OutFd, 1, HTML),
- if
- HTML ->
- file:write(OutFd, "</pre>\n</body>\n</html>\n");
+ if HTML ->
+ ok = file:write(OutFd, "</pre>\n</body>\n</html>\n");
true -> ok
end,
- file:close(OutFd),
- file:close(InFd),
+ ok = file:close(OutFd),
+ ok = file:close(InFd),
{ok, OutFile};
@@ -2460,40 +2479,43 @@ do_analyse_to_file1(Module, OutFile, ErlFile, HTML) ->
{error, {file, ErlFile, Reason}}
end.
+merge_dup_lines(CovLines) ->
+ merge_dup_lines(CovLines, []).
+merge_dup_lines([{L, N}|T], [{L, NAcc}|TAcc]) ->
+ merge_dup_lines(T, [{L, NAcc + N}|TAcc]);
+merge_dup_lines([{L, N}|T], Acc) ->
+ merge_dup_lines(T, [{L, N}|Acc]);
+merge_dup_lines([], Acc) ->
+ lists:reverse(Acc).
print_lines(Module, CovLines, InFd, OutFd, L, HTML) ->
case file:read_line(InFd) of
eof ->
ignore;
- {ok,"%"++_=Line} -> %Comment line - not executed.
- file:write(OutFd, [tab(),escape_lt_and_gt(Line, HTML)]),
- print_lines(Module, CovLines, InFd, OutFd, L+1, HTML);
{ok,RawLine} ->
Line = escape_lt_and_gt(RawLine,HTML),
case CovLines of
[{L,N}|CovLines1] ->
- %% N = lists:foldl(fun([Ni], Nacc) -> Nacc+Ni end, 0, Ns),
- if
- N=:=0, HTML=:=true ->
- LineNoNL = Line -- "\n",
- Str = " 0",
- %%Str = string:right("0", 6, 32),
- RedLine = ["<font color=red>",Str,fill1(),
- LineNoNL,"</font>\n"],
- file:write(OutFd, RedLine);
- N<1000000 ->
- Str = string:right(integer_to_list(N), 6, 32),
- file:write(OutFd, [Str,fill1(),Line]);
- N<10000000 ->
- Str = integer_to_list(N),
- file:write(OutFd, [Str,fill2(),Line]);
- true ->
- Str = integer_to_list(N),
- file:write(OutFd, [Str,fill3(),Line])
- end,
+ if N=:=0, HTML=:=true ->
+ LineNoNL = Line -- "\n",
+ Str = " 0",
+ %%Str = string:right("0", 6, 32),
+ RedLine = ["<font color=red>",Str,fill1(),
+ LineNoNL,"</font>\n"],
+ ok = file:write(OutFd, RedLine);
+ N < 1000000 ->
+ Str = string:right(integer_to_list(N), 6, 32),
+ ok = file:write(OutFd, [Str,fill1(),Line]);
+ N < 10000000 ->
+ Str = integer_to_list(N),
+ ok = file:write(OutFd, [Str,fill2(),Line]);
+ true ->
+ Str = integer_to_list(N),
+ ok = file:write(OutFd, [Str,fill3(),Line])
+ end,
print_lines(Module, CovLines1, InFd, OutFd, L+1, HTML);
- _ ->
- file:write(OutFd, [tab(),Line]),
+ _ -> %Including comment lines
+ ok = file:write(OutFd, [tab(),Line]),
print_lines(Module, CovLines, InFd, OutFd, L+1, HTML)
end
end.
@@ -2533,7 +2555,7 @@ do_export(Module, OutFile, From, State) ->
{error,{not_cover_compiled,Module}}
end
end,
- file:close(Fd),
+ ok = file:close(Fd),
reply(From, Reply);
{error,Reason} ->
reply(From, {error, {cant_open_file,OutFile,Reason}})
@@ -2575,10 +2597,9 @@ write(Element,Fd) ->
case byte_size(Bin) of
Size when Size > 255 ->
SizeBin = term_to_binary({'$size',Size}),
- file:write(Fd,
- <<(byte_size(SizeBin)):8,SizeBin/binary,Bin/binary>>);
+ ok = file:write(Fd, <<(byte_size(SizeBin)):8,SizeBin/binary,Bin/binary>>);
Size ->
- file:write(Fd,<<Size:8,Bin/binary>>)
+ ok = file:write(Fd,<<Size:8,Bin/binary>>)
end,
ok.
@@ -2741,16 +2762,22 @@ pmap_collect(Mons,Acc) ->
end.
%%%-----------------------------------------------------------------
-%%% Read encoding from source file
+%%% Decide which encoding to use when analyzing to file.
+%%% The target file contains the file path, so if either the file name
+%%% encoding or the encoding of the source file is utf8, then we need
+%%% to use utf8.
encoding(File) ->
- Encoding =
- case epp:read_encoding(File) of
- none ->
- epp:default_encoding();
- E ->
- E
- end,
- html_encoding(Encoding).
+ case file:native_name_encoding() of
+ latin1 ->
+ case epp:read_encoding(File) of
+ none ->
+ epp:default_encoding();
+ E ->
+ E
+ end;
+ utf8 ->
+ utf8
+ end.
html_encoding(latin1) ->
"iso-8859-1";
diff --git a/lib/tools/src/cover_web.erl b/lib/tools/src/cover_web.erl
deleted file mode 100644
index ae8b3f25cf..0000000000
--- a/lib/tools/src/cover_web.erl
+++ /dev/null
@@ -1,1185 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2001-2009. All Rights Reserved.
-%%
-%% 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
-%%
-%% 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%
-%%
-
--module(cover_web).
--author('[email protected]').
--behaviour(gen_server).
-
-%%Export of configuration function
--export([configData/0]).
-%% External exports
--export([init/1, handle_call/3, handle_cast/2, handle_info/2,
- terminate/2, code_change/3]).
-
--export([start_link/0,start/0,stop/0]).
--export([menu_frame/2,nodes_frame/2,import_frame/2,
- compile_frame/2,result_frame/2]).
--export([list_dir/2,compile/2,add_node/2,remove_node/2,result/2,
- calls/2,coverage/2,import/2]).
-
--record(state,{dir}).
-
--include_lib("kernel/include/file.hrl").
-
-%% Timeouts
--define(DEFAULT_TIME,10000).
--define(MAX_COMPILE_TIME,60000).
--define(MAX_ANALYSE_TIME,30000).
-
-%% Colors
--define(INFO_BG_COLOR,"#C0C0EA").
-
-%%%----------------------------------------------------------------------
-%%% API - called from erlang shell
-%%%----------------------------------------------------------------------
-%% Start webtool and webcover from erlang shell
-start() ->
- webtool:start(),
- webtool:start_tools([],"app=webcover"),
- ok.
-
-%% Stop webtool and webcover from erlang shell
-stop() ->
- webtool:stop_tools([],"app=webcover"),
- webtool:stop().
-
-
-
-%%%----------------------------------------------------------------------
-%%% API - called from webtool
-%%%----------------------------------------------------------------------
-start_link() ->
- gen_server:start_link({local, webcover_server},cover_web, [], []).
-
-
-nodes_frame(Env,Input)->
- call({nodes_frame,Env,Input}).
-
-add_node(Env,Input)->
- call({add_node,Env,Input}).
-
-remove_node(Env,Input)->
- call({remove_node,Env,Input}).
-
-compile_frame(Env,Input)->
- call({compile_frame,Env,Input}).
-
-list_dir(Env,Input) ->
- call({list_dir,Env,Input}).
-
-compile(Env,Input)->
- call({compile,Env,Input},?MAX_COMPILE_TIME).
-
-result_frame(Env,Input)->
- call({result_frame,Env,Input}).
-
-result(Env,Input) ->
- call({result,Env,Input},?MAX_ANALYSE_TIME).
-
-calls(Env,Input) ->
- call({calls,Env,Input}).
-
-coverage(Env,Input) ->
- call({coverage,Env,Input}).
-
-import_frame(Env,Input)->
- call({import_frame,Env,Input}).
-
-import(Env,Input)->
- call({import,Env,Input}).
-
-menu_frame(Env,Input)->
- call({menu_frame,Env,Input}).
-
-call(Msg) ->
- call(Msg,?DEFAULT_TIME).
-call(Msg,Time) ->
- gen_server:call(webcover_server,Msg,Time).
-
-
-
-configData()->
- {webcover,[{web_data,{"WebCover","/webcover"}},
- {alias,{"/webcover",code:priv_dir(tools)}},
- {alias,{erl_alias,"/webcover/erl",[cover_web]}},
- {start,{child,{{local,webcover_server},
- {cover_web,start_link,[]},
- permanent,100,worker,[cover_web]}}}
- ]}.
-
-
-%%%----------------------------------------------------------------------
-%%% Callback functions from gen_server
-%%%----------------------------------------------------------------------
-
-%%----------------------------------------------------------------------
-%% Func: init/1
-%% Returns: {ok, State} |
-%% {ok, State, Timeout} |
-%% ignore |
-%% {stop, Reason}
-%%----------------------------------------------------------------------
-init([]) ->
- cover:start(),
- CS = whereis(cover_server),
- link(CS),
- GL = spawn_link(fun group_leader_proc/0),
- group_leader(GL,CS),
-
- %% Must trap exists in order to have terminate/2 executed when
- %% crashing because of a linked process crash.
- process_flag(trap_exit,true),
- {ok,Cwd} = file:get_cwd(),
- {ok, #state{dir=Cwd}}.
-
-group_leader_proc() ->
- register(cover_group_leader_proc,self()),
- group_leader_loop([]).
-group_leader_loop(Warnings) ->
- receive
- {io_request,From,ReplyAs,{put_chars,io_lib,Func,[Format,Args]}} ->
- Msg = (catch io_lib:Func(Format,Args)),
- From ! {io_reply,ReplyAs,ok},
- case lists:member(Msg,Warnings) of
- true -> group_leader_loop(Warnings);
- false -> group_leader_loop([Msg|Warnings])
- end;
- {io_request,From,ReplyAs,{put_chars,_Encoding,io_lib,Func,[Format,Args]}} ->
- Msg = (catch io_lib:Func(Format,Args)),
- From ! {io_reply,ReplyAs,ok},
- case lists:member(Msg,Warnings) of
- true -> group_leader_loop(Warnings);
- false -> group_leader_loop([Msg|Warnings])
- end;
- IoReq when element(1,IoReq)=:= io_request ->
- group_leader() ! IoReq,
- group_leader_loop(Warnings);
- {From,get_warnings} ->
- Warnings1 =
- receive
- {io_request,From,ReplyAs,
- {put_chars,io_lib,Func,[Format,Args]}} ->
- Msg = (catch io_lib:Func(Format,Args)),
- From ! {io_reply,ReplyAs,ok},
- case lists:member(Msg,Warnings) of
- true -> Warnings;
- false -> [Msg|Warnings]
- end
- after 0 ->
- Warnings
- end,
- From ! {warnings,Warnings1},
- group_leader_loop([])
- end.
-
-%%----------------------------------------------------------------------
-%% Func: handle_call/3
-%% Returns: {reply, Reply, State} |
-%% {reply, Reply, State, Timeout} |
-%% {noreply, State} |
-%% {noreply, State, Timeout} |
-%% {stop, Reason, Reply, State} | (terminate/2 is called)
-%% {stop, Reason, State} (terminate/2 is called)
-%%----------------------------------------------------------------------
-handle_call({nodes_frame,_Env,_Input},_From,State)->
- {reply,nodes_frame1(),State};
-
-handle_call({add_node,_Env,Input},_From,State)->
- {reply,do_add_node(Input),State};
-
-handle_call({remove_node,_Env,Input},_From,State)->
- {reply,do_remove_node(Input),State};
-
-handle_call({compile_frame,_Env,_Input},_From,State)->
- {reply,compile_frame1(State#state.dir),State};
-
-handle_call({list_dir,_Env,Input},_From,State)->
- Dir = get_input_data(Input,"path"),
- case filelib:is_dir(Dir) of
- true ->
- {reply,compile_frame1(Dir),State#state{dir=Dir}};
- false ->
- Err = Dir ++ " is not a directory",
- {reply,compile_frame1(State#state.dir,Err),State}
- end;
-handle_call({compile,_Env,Input},_From,State)->
- {reply,do_compile(Input,State#state.dir),State};
-
-handle_call({result_frame,_Env,_Input},_From,State)->
- {reply,result_frame1(),State};
-
-handle_call({result,_Env,Input},_From,State)->
- {reply,handle_result(Input),State};
-
-handle_call({calls,_Env,Input},_From,State)->
- {reply,call_page(Input),State};
-
-handle_call({coverage,_Env,Input},_From,State)->
- {reply,coverage_page(Input),State};
-
-handle_call({import_frame,_Env,_Input},_From,State)->
- {ok,Cwd} = file:get_cwd(),
- {reply,import_frame1(Cwd),State};
-
-handle_call({import,_Env,Input},_From,State)->
- {reply,do_import(Input),State};
-
-handle_call({menu_frame,_Env,_Input},_From,State)->
- {reply,menu_frame1(),State};
-
-handle_call(_Request, _From, State) ->
- Reply = bad_request,
- {reply, Reply, State}.
-
-
-%%----------------------------------------------------------------------
-%% Func: handle_cast/2
-%% Returns: {noreply, State} |
-%% {noreply, State, Timeout} |
-%% {stop, Reason, State} (terminate/2 is called)
-%%----------------------------------------------------------------------
-handle_cast(_Msg, State) ->
- {noreply, State}.
-
-%%----------------------------------------------------------------------
-%% Func: handle_info/2
-%% Returns: {noreply, State} |
-%% {noreply, State, Timeout} |
-%% {stop, Reason, State} (terminate/2 is called)
-%%----------------------------------------------------------------------
-handle_info({'EXIT',_Pid,Reason}, State) ->
- {stop, Reason, State}.
-
-%%----------------------------------------------------------------------
-%% Func: terminate/2
-%% Purpose: Shutdown the server
-%% Returns: any (ignored by gen_server)
-%%----------------------------------------------------------------------
-terminate(_Reason, _State) ->
- cover:stop(),
- ok.
-
-%%--------------------------------------------------------------------
-%% Func: code_change/3
-%% Purpose: Convert process state when code is changed
-%% Returns: {ok, NewState}
-%%--------------------------------------------------------------------
-code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
-
-%%%----------------------------------------------------------------------
-%%% Internal functions
-%%%----------------------------------------------------------------------
-
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% %%
-%% The functions that creates the whole pages by collecting all the %%
-%% neccessary data for each page. These functions are the public %%
-%% interface. %%
-%% %%
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%%----------------------------------------------------------------------
-%% Returns the page to the left frame
-%%----------------------------------------------------------------------
-menu_frame1()->
- [header(),html_header(""),menu_body(),html_end()].
-
-%%----------------------------------------------------------------------
-%% Creates the page where the user can add and remove nodes
-%%----------------------------------------------------------------------
-
-nodes_frame1()->
- nodes_frame1([]).
-nodes_frame1(Err)->
- [header(),html_header("Add/remove nodes"),nodes_body(Err),html_end()].
-
-%%----------------------------------------------------------------------
-%% Creates the page where the user can cover compile modules
-%%----------------------------------------------------------------------
-
-compile_frame1(Dir)->
- compile_frame1(Dir,[]).
-compile_frame1(Dir,Err) ->
- [header(),html_header("Cover compile"),compile_body(Dir,Err),html_end()].
-
-%%----------------------------------------------------------------------
-%% Creates the page where the user can handle results
-%%----------------------------------------------------------------------
-
-result_frame1()->
- result_frame1([]).
-result_frame1(Err) ->
- [header(),html_header("Show cover results"),result_body(Err),html_end()].
-
-%%----------------------------------------------------------------------
-%%The beginning of the page that clear the cover information on a cover
-%%compiled module
-%%----------------------------------------------------------------------
-call_page(Input)->
- [header(),html_header("Code coverage"),call_result(Input),html_end()].
-
-coverage_page(Input)->
- [header(),html_header("Code coverage"),coverage_result(Input),html_end()].
-
-%%----------------------------------------------------------------------
-%% Creates the page where the user an import files
-%%----------------------------------------------------------------------
-import_frame1(Dir) ->
- import_frame1(Dir,"").
-import_frame1(Dir,Err) ->
- [header(),html_header("Import coverdata"),import_body(Dir,Err),html_end()].
-
-
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% %%
-%% The functions that build the body of the menu frame %%
-%% %%
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-menu_body() ->
- Nodes = cover:which_nodes(),
- Modules = cover:modules(),
- Imported = cover:imported(),
- ["<A HREF=\"./nodes_frame\" TARGET=\"main\">Nodes</A><BR>\n",
- "<A HREF=\"./compile_frame\" TARGET=\"main\">Compile</A><BR>\n",
- "<A HREF=\"./import_frame\" TARGET=\"main\">Import</A><BR>\n",
- "<A HREF=\"./result_frame\" TARGET=\"main\">Result</A>\n",
- "<P><B>Nodes:</B>\n",
- "<UL>\n",
- lists:map(fun(N) -> "<LI>"++atom_to_list(N)++"</LI>\n" end,[node()|Nodes]),
- "</UL>\n",
- "<P><B>Compiled modules:</B>\n",
- "<UL>\n",
- lists:map(fun(M) -> "<LI>"++atom_to_list(M)++"</LI>\n" end,Modules),
- "</UL>\n",
- "<P><B>Imported files:</B>\n",
- "<UL>\n",
- "<FONT SIZE=-1>\n",
- lists:map(fun(F) ->
- Short = filename:basename(F),
- "<LI TITLE=\""++F++"\">"++Short++"</LI>\n" end,Imported),
- "</FONT>\n",
- "</UL>\n"].
-
-
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% %%
-%% The functions that build the body of the nodes frame %%
-%% %%
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-nodes_body(Err) ->
- CN = cover:which_nodes(),
- Fun = fun(N) ->
- NStr = atom_to_list(N),
- ["<OPTION VALUE=",NStr,
- " onClick=\"node.value=selected_node.value\">",NStr,
- "</OPTION>\n"]
- end,
- AllNodes = lists:append(lists:map(Fun,nodes()--CN)),
- CoverNodes = lists:append(lists:map(Fun,CN)),
-
- [reload_menu_script(Err),
- "<H1 ALIGN=center>Nodes</H1>\n",
- "<TABLE BORDER=0 WIDTH=600 ALIGN=center>\n",
- "<TR><TD BGCOLOR=",?INFO_BG_COLOR," COLSPAN=2>\n",
- "<P>You can run cover over several nodes simultaneously. Coverage data\n",
- "from all involved nodes will be merged during analysis.\n",
- "<P>Select or enter node names to add or remove here.\n",
- "</TD></TR>\n",
- "<TR><TD COLSPAN=2><BR><BR></TD></TR>\n",
- "<FORM ACTION=\"./add_node\" NAME=add_node>\n",
- "<TR><TD VALIGN=top>Add node:</TD>\n",
- "<TD><INPUT TYPE=text NAME=\"node\" SIZE=40 >",
- "<INPUT TYPE=submit\n",
- " onClick=\"if(!node.value){node.value=selected_node.value};\" VALUE=Add>"
- "<BR><SELECT NAME=selected_node TITLE=\"Select node\">\n",
- AllNodes ++
- "</SELECT>\n",
- "</TD></TR>\n"
- "</FORM>\n",
- "<TR><TD COLSPAN=2><BR><BR></TD></TR>\n",
- "<FORM ACTION=\"./remove_node\" NAME=remove_node>\n",
- "<TR><TD>Remove node:</TD>\n",
- "<TD><SELECT NAME=node TITLE=\"Select node\">\n",
- CoverNodes ++
- "</SELECT>\n",
- "<INPUT TYPE=submit VALUE=Remove>"
- "</TD></TR>\n",
- "</FORM>",
- "</TABLE>"].
-
-
-do_add_node(Input) ->
- NodeStr = get_input_data(Input, "node"),
- Node = list_to_atom(NodeStr),
- case net_adm:ping(Node) of
- pong ->
- cover:start(Node),
- nodes_frame1();
- pang ->
- nodes_frame1("Node \\\'" ++ NodeStr ++ "\\\' is not alive")
- end.
-
-do_remove_node(Input) ->
- Node = list_to_atom(get_input_data(Input, "node")),
- cover:stop(Node),
- nodes_frame1().
-
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-% %
-% The functions that is used when the user wants to compile something %
-% %
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-compile_body(Dir,Err) ->
- Erls = filelib:wildcard(filename:join(Dir,"*.erl")),
- Beams = filelib:wildcard(filename:join(Dir,"*.beam")),
-
- [reload_menu_script(Err),
- "<H1 ALIGN=center>Compile</H1>\n",
- "<TABLE WIDTH=600 ALIGN=center BORDER=0>\n",
- "<TR><TD COLSPAN=3 BGCOLOR=",?INFO_BG_COLOR,">\n",
- "Each module which shall be part of the cover analysis must be prepared\n",
- "or 'cover compiled'. On this page you can select .erl files and/or\n",
- ".beam files to include in the analysis. If you select a .erl file it\n",
- "will first be compiled with the Erlang compiler and then prepared for\n",
- "coverage analysis. If you select a .beam file it will be prepared for\n",
- "coverage analysis directly.\n",
- "</TD></TR>\n",
- "<FORM ACTION=\"./list_dir\" NAME=list_dir>\n",
- "<TR><TD WIDTH=30% BGCOLOR=",?INFO_BG_COLOR," ROWSPAN=2>\n",
- "To list a different directory, enter the directory name here.\n",
- "</TD>\n",
- "<TH COLSPAN=2><BR>List directory:<BR></TH>\n",
- "</TR>\n",
- "<TR><TD ALIGN=center COLSPAN=2>\n",
- "<INPUT TYPE=text NAME=\"path\" SIZE=40 VALUE=",Dir,">",
- "<INPUT TYPE=submit VALUE=Ok>",
- "<BR><BR></TD></TR>\n",
- "</FORM>\n",
- "<FORM ACTION=\"./compile\" NAME=compile_selection>\n",
- "<TR><TD BGCOLOR=",?INFO_BG_COLOR," ROWSPAN=2>\n",
- "<P>Select one or more .erl or .beam files to prepare for coverage\n"
- "analysis, and click the \"Compile\" button.\n",
- "<P>To reload the original file after coverage analysis is complete,\n"
- "select one or more files and click the \"Uncompile\" button, or\n",
- "simply click the \"Uncompile all\" button to reload all originals.\n"
- "</TD>\n",
- "<TH>.erl files</TH><TH>.beam files</TH></TR>\n",
- "<TR><TD ALIGN=center VALIGN=top>\n",
- "<SELECT NAME=erl TITLE=\"Select .erl files to compile\" MULTIPLE=true",
- " SIZE=15>\n",
- list_modules(Erls) ++
- "</SELECT></TD>\n",
- "<TD ALIGN=center VALIGN=top>\n",
- "<SELECT NAME=beam TITLE=\"Select .beam files to compile\"MULTIPLE=true",
- " SIZE=15>\n",
- list_modules(Beams) ++
- "</SELECT></TD></TR>\n"
- "<TR><TD BGCOLOR=",?INFO_BG_COLOR," ROWSPAN=2>\n",
- "Compile options are only needed for .erl files. The options must be\n"
- "given e.g. like this: \n"
- "<FONT SIZE=-1>[{i,\"/my/path/include\"},{i,\"/other/path/\"}]</FONT>\n"
- "</TD>\n",
- "<TH COLSPAN=2><BR>Compile options:<BR></TH>\n",
- "</TR>\n",
- "<TR><TD COLSPAN=2 ALIGN=center>\n",
- "<INPUT TYPE=text NAME=\"options\" SIZE=40>\n",
- "<INPUT TYPE=hidden NAME=\"action\"></TD></TR>\n",
- "<TR><TD></TD><TD ALIGN=center COLSPAN=2>\n",
- "<INPUT TYPE=submit onClick=\"action.value=\'compile\';\"VALUE=Compile>",
- "<INPUT TYPE=submit onClick=\"action.value=\'uncompile\';\" ",
- "VALUE=Uncompile>",
- "<INPUT TYPE=submit onClick=\"action.value=\'uncompile_all\';\" ",
- "VALUE=\"Uncompile all\">",
- "<BR><INPUT TYPE=reset VALUE=\"Reset form\"></TD></TR>\n",
- "</FORM>\n",
- "</TABLE>\n"].
-
-list_modules([File|Files]) ->
- Mod = filename:basename(File),
- ["<OPTION VALUE=",File," onDblClick=\"action.value=\'compile\';submit();\">",
- Mod,"</OPTION>\n" | list_modules(Files)];
-list_modules([]) ->
- [].
-
-do_compile(Input,Dir) ->
- {Erls,Beams,Opts,Action} = get_compile_input(parse(Input),[],[]),
- Errs =
- case Action of
- "compile" ->
- do_compile(Erls,Beams,Opts,[]);
- "uncompile" ->
- do_uncompile(Erls++Beams);
- "uncompile_all" ->
- do_uncompile(cover:modules())
- end,
- compile_frame1(Dir,Errs).
-
-get_compile_input([{"erl",File}|Input],Erl,Beam) ->
- get_compile_input(Input,[File|Erl],Beam);
-get_compile_input([{"beam",File}|Input],Erl,Beam) ->
- get_compile_input(Input,Erl,[File|Beam]);
-get_compile_input([{"options",Opts0},{"action",Action}],Erl,Beam) ->
- Opts = parse_options(Opts0),
- {Erl,Beam,Opts,Action}.
-
-do_compile([Erl|Erls],Beams,Opts,Errs) ->
- case cover:compile_module(Erl,Opts) of
- {ok,_} ->
- do_compile(Erls,Beams,Opts,Errs);
- {error,File} ->
- do_compile(Erls,Beams,Opts,["\\n"++File|Errs])
- end;
-do_compile([],[Beam|Beams],Opts,Errs) ->
- case cover:compile_beam(Beam) of
- {ok,_} ->
- do_compile([],Beams,Opts,Errs);
- {error,{no_abstract_code,File}} ->
- do_compile([],Beams,Opts,["\\n"++File++" (no_abstract_code)"|Errs])
- end;
-do_compile([],[],_,[]) ->
- [];
-do_compile([],[],_,Errs) ->
- "Compilation failed for the following files:" ++ Errs.
-
-parse_options(Options)->
- case erl_scan:string(Options ++".") of
- {ok,Tokens,_Line} ->
- case erl_parse:parse_exprs(Tokens) of
- {ok,X}->
- case lists:map(fun erl_parse:normalise/1, X) of
- [List] when is_list(List) -> List;
- List -> List
- end;
- _ ->
- []
- end;
- _ ->
- []
- end.
-
-
-do_uncompile(Files) ->
- lists:foreach(
- fun(File) ->
- Module =
- if is_atom(File) ->
- File;
- true ->
- ModStr = filename:basename(filename:rootname(File)),
- list_to_atom(ModStr)
- end,
- case code:which(Module) of
- cover_compiled ->
- code:purge(Module),
- case code:load_file(Module) of
- {module, Module} ->
- ok;
- {error, _Reason2} ->
- code:delete(Module)
- end;
- _ ->
- ok
- end
- end,
- Files),
- [].
-
-
-
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-% %
-% The functions that builds the body of the page for coverage analysis%
-% %
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-result_body(Err) ->
- [reload_menu_script(Err),
- "<H1 ALIGN=center>Result</H1>\n",
- "<TABLE BORDER=0 WIDTH=600 ALIGN=center>\n",
- "<TR><TD BGCOLOR=",?INFO_BG_COLOR,">\n",
- "<P>After executing all your tests you can view the result of the\n",
- "coverage analysis here. For each module you can\n",
- "<DL>\n",
- "<DT><B>Analyse to file</B></DT>\n",
- "<DD>The source code of the module is shown with the number of calls\n",
- "to each line stated in the left margin. Lines which are never called\n",
- "are colored red.</DD>\n",
- "<DT><B>Analyse coverage</B></DT>\n",
- "<DD>Show the number of covered and uncovered lines in the module.</DD>\n",
- "<DT><B>Analyse calls</B></DT>\n",
- "<DD>Show the number of calls in the module.</DD>\n",
- "<DT><B>Reset module</B></DT>\n",
- "<DD>Delete all coverage data for the module.</DD>\n",
- "<DT><B>Export module</B></DT>\n",
- "<DD>Write all coverage data for the module to a file. The data can\n",
- "later be imported from the \"Import\" page.</DD>\n",
- "</DL>\n",
- "<P>You can also reset or export data for all modules with the\n",
- "<B>Reset all</B> and <B>Export all</B> actions respectively. For these\n",
- "two actions there is no need to select a module.\n",
- "<P>Select module and action from the drop down menus below, and click\n",
- "the \"Execute\" button.\n",
- "</TD></TR>\n",
- "<TR><TD><BR><BR>\n",
- result_selections(),
- "</TD></TR></TABLE>"].
-
-result_selections() ->
- ModList = filter_modlist(cover:modules()++cover:imported_modules(),[]),
-
- ["<FORM ACTION=\"./result\" NAME=result_selection>\n",
- "<TABLE WIDTH=\"300\" BORDER=0 ALIGN=center>\n",
- "<TR><TD ALIGN=left>\n",
- "Module:\n",
- "<BR><SELECT NAME=module TITLE=\"Select module\">\n",
- ModList ++
- "</SELECT>\n",
- "</TD>\n",
- "<TD ALIGN=left>\n",
- "Action:\n",
- "<BR><SELECT NAME=action TITLE=\"Select action\">\n",
- "<OPTION VALUE=\"analyse_to_file\">Analyse to file</OPTION>\n"
- "<OPTION VALUE=\"coverage\">Analyse coverage</OPTION>\n"
- "<OPTION VALUE=\"calls\">Analyse calls</OPTION>\n"
- "<OPTION VALUE=\"reset\">Reset module</OPTION>\n"
- "<OPTION VALUE=\"reset_all\">Reset all</OPTION>\n"
- "<OPTION VALUE=\"export\">Export module</OPTION>\n"
- "<OPTION VALUE=\"export_all\">Export all</OPTION>\n"
- "</SELECT>\n",
- "</TD>\n",
- "<TD ALIGN=center VALIGN=bottom><INPUT TYPE=submit VALUE=Execute>\n"
- "</TD></TR>\n"
- "</TABLE>\n",
- "</FORM>\n"].
-
-filter_modlist([M|Ms],Already) ->
- case lists:member(M,Already) of
- true ->
- filter_modlist(Ms,Already);
- false ->
- MStr = atom_to_list(M),
- ["<OPTION VALUE=",MStr,">",MStr,"</OPTION>\n" |
- filter_modlist(Ms,[M|Already])]
- end;
-filter_modlist([],_Already) ->
- [].
-
-
-
-handle_result(Input) ->
- case parse(Input) of
- [{"module",M},{"action",A}] ->
- case A of
- "analyse_to_file" ->
- case cover:analyse_to_file(list_to_atom(M),[html]) of
- {ok,File} ->
- case file:read_file(File) of
- {ok,HTML}->
- file:delete(File),
- [header(),
- reload_menu_script(""),
- binary_to_list(HTML)];
- _ ->
- result_frame1("Can not read file" ++ File)
- end;
- {error,no_source_code_found} ->
- result_frame1("No source code found for \\\'" ++
- M ++ "\\\'")
- end;
- "calls" ->
- call_page(Input);
- "coverage" ->
- coverage_page(Input);
- "reset" ->
- cover:reset(list_to_atom(M)),
- result_frame1("Coverage data for \\\'" ++ M ++
- "\\\' is now reset");
- "reset_all" ->
- cover:reset(),
- result_frame1("All coverage data is now reset");
- "export" ->
- ExportFile = generate_filename(M),
- cover:export(ExportFile,list_to_atom(M)),
- result_frame1("Coverage data for \\\'" ++ M ++
- "\\\' is now exported to file \\\"" ++
- ExportFile ++ "\\\"");
- "export_all" ->
- ExportFile = generate_filename("COVER"),
- cover:export(ExportFile),
- result_frame1(
- "All coverage data is now exported to file \\\"" ++
- ExportFile ++ "\\\"")
- end;
- [{"action",_A}] ->
- result_frame1("No module is selected")
- end.
-
-generate_filename(Prefix) ->
- {ok,Cwd} = file:get_cwd(),
- filename:join(Cwd,Prefix ++ "_" ++ ts() ++ ".coverdata").
-
-ts() ->
- {{Y,M,D},{H,Min,S}} = calendar:now_to_local_time(erlang:timestamp()),
- io_lib:format("~4.4.0w~2.2.0w~2.2.0w-~2.2.0w~2.2.0w~2.2.0w",
- [Y,M,D,H,Min,S]).
-
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-% %
-% The functions that builds the body of the page that shows the calls %
-% %
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-call_result(Input)->
- Mod = list_to_atom(get_input_data(Input, "module")),
- case cover:analyse(Mod,calls) of
- {error,_}->
- error_body();
- {ok,_} ->
- call_result2(Mod,Input)
- end.
-
-call_result2(Mod,Input)->
- Result =
- case get_input_data(Input,"what") of
- "mod" ->
- call_result(mod,Mod);
- "func" ->
- call_result(func,Mod);
- "clause" ->
- call_result(clause,Mod);
- _->
- call_result(all,Mod)
- end,
- result_choice("calls",Mod) ++ Result.
-
-result_choice(Level,Mod)->
- ModStr=atom_to_list(Mod),
- [reload_menu_script(""),
- "<TABLE WIDTH=100%><TR>\n",
- "<TD><A HREF=./",Level,"?module=",ModStr,"&what=all>All Data</A></TD>\n",
- "<TD><A HREF=./",Level,"?module=",ModStr,"&what=mod>Module</A></TD>\n",
- "<TD><A HREF=./",Level,"?module=",ModStr,"&what=func>Function</A></TD>\n",
- "<TD><A HREF=./",Level,"?module=",ModStr,"&what=clause>Clause</A></TD>\n",
- "</TR></TABLE><BR>\n"].
-
-call_result(Mode,Module)->
- Content =
- case Mode of
- mod->
- format_cover_call(cover:analyse(Module,calls,module),mod);
- func->
- format_cover_call(cover:analyse(Module,calls,function),func);
- clause->
- format_cover_call(cover:analyse(Module,calls,clause),clause);
- _->
- format_cover_call(cover:analyse(Module,calls,module),mod) ++
- format_cover_call(cover:analyse(Module,calls,function),func)++
- format_cover_call(cover:analyse(Module,calls,clause),clause)
- end,
- getModDate(Module,date())++"<BR>"++
- "<TABLE WIDTH=\"100%\" BORDER=1>"
- ++ Content ++"</TABLE>".
-
-
-format_cover_call({error,_},_)->
- ["<TR><TD>\n",
- "<BR><BR><BR><BR>\n",
- "<FONT SIZE=5>The selected module is not Cover Compiled</FONT>\n",
- "<BR>\n",
- "</TD></TR>\n"];
-
-format_cover_call({ok,{Mod,Calls}},mod)->
- ["<TR BGCOLOR=\"#8899AA\"><TD COLSPAN=5><B>Module calls</B></TD></TR>\n",
- "<TR><TD COLSPAN=4><I>Module</I></TD>",
- "<TD ALIGN=\"right\"><I>Number of calls</I></TD></TR>\n",
- "<TR><TD COLSPAN=4>" ++ atom_to_list(Mod) ++"</TD>"
- "<TD ALIGN=\"right\">" ++ integer_to_list(Calls)++"</TD></TR>\n"];
-
-format_cover_call({ok,Calls},func)->
- ["<TR BGCOLOR=\"#8899AA\"><TD COLSPAN=5><B>Function calls</B></TD></TR>\n",
- "<TR><TD><I>Module</I></TD><TD><I>Function</I></TD>",
- "<TD COLSPAN=2 ALIGN=\"right\"><I>Arity</I></TD>",
- "<TD ALIGN=\"right\"><I>Number of calls </I></TD></TR>\n",
- lists:append(
- lists:map(
- fun({{Mod,Func,Arity},Nr_of_calls})->
- ["<TR><TD WIDTH=\"20%\">"++ atom_to_list(Mod)++"</TD>\n",
- "<TD WIDTH=\"20%\" >" ++ atom_to_list(Func) ++" </TD>\n",
- "<TD COLSPAN=2 WIDTH=\"40%\" ALIGN=\"right\">",
- integer_to_list(Arity),
- "</TD>\n",
- "<TD WIDTH=\"20%\" ALIGN=\"right\">",
- integer_to_list(Nr_of_calls),
- "</TD></TR>\n"]
- end,
- Calls))];
-
-format_cover_call({ok,Calls},clause)->
- ["<TR BGCOLOR=\"#8899AA\"><TD COLSPAN=5><B>Clause calls</B></TD></TR>\n",
- "<TR><TD><I>Module</I></TD><TD><I>Function</I></TD>",
- "<TD ALIGN=\"right\"><I>Arity</I></TD>",
- "<TD ALIGN=\"right\"><I>Ordinal</I></TD>",
- "<TD ALIGN=\"right\"><I>Number of calls</I></TD></TR>\n",
- lists:append(
- lists:map(
- fun({{Mod,Func,Arity,Ord},Nr_of_calls})->
- ["<TR><TD WIDTH=\"20%\" >", atom_to_list(Mod), "</TD>\n",
- "<TD WIDTH=\"20%\" >", atom_to_list(Func), "</TD>\n",
- "<TD WIDTH=\"20%\" ALIGN=\"right\">",
- integer_to_list(Arity),
- "</TD>\n",
- "<TD WIDTH=\"20%\" ALIGN=\"right\">",
- integer_to_list(Ord),
- "</TD>\n",
- "<TD WIDTH=\"20%\" ALIGN=\"right\">",
- integer_to_list(Nr_of_calls),
- "</TD></TR>\n"]
- end,
- Calls))].
-
-
-error_body()->
- ["<TABLE WIDTH=\"100%\" BORDER=1>\n",
- "<TR ALIGN=\"center\">\n",
- "<TD>\n",
- "<BR><BR><BR><BR><BR><BR>\n",
- "<FONT SIZE=5>The selected module is not Cover Compiled</FONT>\n",
- "<BR>\n",
- "</TD>\n",
- "</TR>\n",
- "</TABLE>\n"].
-
-
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-% %
-% The functions that builds the body of the page that shows coverage %
-% %
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-coverage_result(Input)->
- Mod = list_to_atom(get_input_data(Input, "module")),
- case cover:analyse(Mod,coverage) of
- {error,_}->
- error_body();
- {ok,_} ->
- coverage_result2(Mod,Input)
- end.
-
-coverage_result2(Mod,Input)->
- Result =
- case get_input_data(Input,"what") of
- "mod" ->
- coverage_result(mod,Mod);
- "func" ->
- coverage_result(func,Mod);
- "clause" ->
- coverage_result(clause,Mod);
- _->
- coverage_result(all,Mod)
- end,
- result_choice("coverage",Mod) ++ Result.
-
-coverage_result(Mode,Module)->
- Content =
- case Mode of
- mod->
- format_cover_coverage(cover:analyse(Module,coverage,module),
- mod);
- func->
- format_cover_coverage(cover:analyse(Module,coverage,function),
- func);
- clause->
- format_cover_coverage(cover:analyse(Module,coverage,clause),
- clause);
- _->
- format_cover_coverage(cover:analyse(Module,coverage,module),
- mod) ++
- format_cover_coverage(cover:analyse(Module,coverage,function),
- func)++
- format_cover_coverage(cover:analyse(Module,coverage,clause),
- clause)
- end,
- getModDate(Module,date())++"<BR>"++
- "<TABLE WIDTH=\"100%\" BORDER=1>"
- ++ Content ++"</TABLE>".
-
-getModDate(Module,{Year,Mon,Day})->
- "<TABLE>
- <TR>
- <TD>Module:</TD>
- <TD>" ++ atom_to_list(Module) ++ "</TD>
- </TR>
- <TR>
- <TD>Date:</TD>
- <TD>" ++ integer_to_list(Day) ++ "/" ++
- integer_to_list(Mon) ++"&nbsp;-&nbsp;"++
- integer_to_list(Year) ++
- "</TD>
- </TR>
- </TABLE>".
-
-
-format_cover_coverage({error,_},_)->
- "<TR><TD>
- <BR><BR><BR><BR>
- <FONT SIZE=5>The selected module is not Cover Compiled</FONT>
- <BR>
- </TD></TR>";
-
-
-format_cover_coverage({ok,{Mod,{Cov,Not_cov}}},mod)->
- ["<TR BGCOLOR=\"#8899AA\"><TD COLSPAN=6><B>Module coverage</B></TD></TR>\n",
- "<TR><TD COLSPAN=4><I>Module</I></TD>\n",
- "<TD ALIGN=\"right\"><I>Covered</I></TD>\n"
- "<TD ALIGN=\"RIGHT\" NOWRAP=\"true\"><I>Not Covered</I></TD>\n",
- "</TR>\n",
- "<TR><TD COLSPAN=4>", atom_to_list(Mod), "</TD>\n"
- "<TD ALIGN=\"right\">", integer_to_list(Cov), "</TD>\n"
- "<TD ALIGN=\"right\" >", integer_to_list(Not_cov), "</TD></TR>\n"];
-
-format_cover_coverage({ok,Cov_res},func)->
- ["<TR BGCOLOR=\"#8899AA\"><TD COLSPAN=6><B>Function coverage</B></TD>\n",
- "</TR>\n",
- "<TR><TD><I>Module</I></TD><TD><I>Function</I></TD>",
- "<TD ALIGN=\"right\"><I>Arity</I></TD>",
- "<TD COLSPAN=2 ALIGN=\"right\"><I>Covered</I></TD>",
- "<TD ALIGN=\"right\" STYLE=\"white-space:nowrap\"><I>Not Covered</I></TD>",
- "</TR>\n",
- lists:append(
- lists:map(
- fun({{Mod,Func,Arity},{Cov,Not_cov}})->
- ["<TR><TD WIDTH=\"20%\" >"++ atom_to_list(Mod) ++" </TD>\n",
- "<TD WIDTH=\"20%\" >" ++ atom_to_list(Func) ++"</TD>\n",
- "<TD WIDTH=\"40%\" ALIGN=\"right\">",
- integer_to_list(Arity),
- "</TD>\n",
- "<TD WIDTH=\"40%\" ALIGN=\"right\" COLSPAN=2>",
- integer_to_list(Cov),
- "</TD>\n"
- "<TD WIDTH=\"20%\" ALIGN=\"right\">",
- integer_to_list(Not_cov),
- "</TD></TR>\n"]
- end,
- Cov_res))];
-
-format_cover_coverage({ok,Cov_res},clause)->
- ["<TR BGCOLOR=\"#8899AA\"><TD COLSPAN=6><B>Clause coverage</B></TD></TR>\n",
- "<TR><TD><I>Module</I></TD><TD><I>Function</I></TD>\n",
- "<TD ALIGN=\"right\"><I>Arity</I></TD>\n",
- "<TD ALIGN=\"right\"><I>Ordinal<I></TD>\n",
- "<TD ALIGN=\"right\">Covered</TD>\n",
- "<TD ALIGN=\"right\" STYLE=\"white-space:nowrap\">Not Covered</TD></TR>\n",
- lists:append(
- lists:map(
- fun({{Mod,Func,Arity,Ord},{Cov,Not_cov}})->
- ["<TR><TD WIDTH=\"20%\" >"++ atom_to_list(Mod) ++"</TD>\n",
- "<TD WIDTH=\"20%\" >" ++ atom_to_list(Func) ++" </TD>\n",
- "<TD WIDTH=\"20%\" ALIGN=\"right\">",
- integer_to_list(Arity),
- "</TD>\n"
- "<TD WIDTH=\"20%\" ALIGN=\"right\">",
- integer_to_list(Ord),
- "</TD>\n"
- "<TD WIDTH=\"20%\" ALIGN=\"right\">",
- integer_to_list(Cov),
- "</TD>\n"
- "<TD WIDTH=\"20%\" ALIGN=\"right\">",
- integer_to_list(Not_cov),
- "</TD></TR>\n"]
- end,
- Cov_res))].
-
-
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-% %
-% The functions that builds the body of the import page %
-% %
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-import_body(Dir,Err) ->
- [reload_menu_script(Err),
- "<H1 ALIGN=center>Import</H1>\n",
- "<TABLE BORDER=0 WIDTH=600 ALIGN=center>\n",
- "<TR><TD BGCOLOR=",?INFO_BG_COLOR,">\n",
- "<P>You can import coverage data from a previous analysis. If you do so\n",
- "the imported data will be merged with the current coverage data.\n",
- "<P>You can export data from the current analysis from the \"Result\"\n",
- "page.\n",
- "<P>Select the file to import here.\n",
- "</TD></TR>\n",
- "<TR><TD ALIGN=center><BR><BR>\n",
- "<FORM NAME=change_import_dir METHOD=post ACTION=\"./import\">\n",
- "<B>Change directory:</B><BR>\n",
- "<INPUT TYPE=text NAME=\"file\" SIZE=30 VALUE=",Dir,">",
- "<INPUT TYPE=hidden NAME=dir VALUE=",Dir,">\n",
- "<INPUT TYPE=submit VALUE=Ok><BR>\n",
- "</FORM>\n",
- browse_import(Dir),
- "</TABLE>"].
-
-browse_import(Dir) ->
- {ok,List} = file:list_dir(Dir),
- Sorted = lists:reverse(lists:sort(List)),
- {Dirs,Files} = filter_files(Dir,Sorted,[],[]),
- ["<FORM NAME=browse_import METHOD=post ACTION=\"./import\">\n"
- "<SELECT NAME=file TITLE=\"Select import file\" SIZE=10>\n",
- "<OPTION VALUE=\"..\" onDblClick=submit()>../</OPTION>\n",
- Dirs,
- Files,
- "</SELECT>\n",
- "<INPUT TYPE=hidden NAME=dir VALUE=",Dir,">\n",
- "<BR><INPUT TYPE=submit VALUE=Ok>\n"
- "</FORM>\n"].
-
-filter_files(Dir,[File|Files],Ds,Fs) ->
- case filename:extension(File) of
- ".coverdata" ->
- Fs1 = ["<OPTION VALUE=",File," onDblClick=submit()>",
- File,"</OPTION>\n" | Fs],
- filter_files(Dir,Files,Ds,Fs1);
- _ ->
- FullName = filename:join(Dir,File),
- case filelib:is_dir(FullName) of
- true ->
- Ds1 = ["<OPTION VALUE=",File," onDblClick=submit()>",
- File,"/</OPTION>\n" | Ds],
- filter_files(Dir,Files,Ds1,Fs);
- false ->
- filter_files(Dir,Files,Ds,Fs)
- end
- end;
-filter_files(_Dir,[],Ds,Fs) ->
- {Ds,Fs}.
-
-
-
-
-do_import(Input) ->
- case parse(Input) of
- [{"file",File0},{"dir",Dir}] ->
- File = filename:join(Dir,File0),
- case filelib:is_dir(File) of
- true ->
- import_frame1(File);
- false ->
- case filelib:is_file(File) of
- true ->
- case cover:import(File) of
- ok ->
- import_frame1(Dir);
- {error,{cant_open_file,ExportFile,_Reason}} ->
- import_frame1(Dir,
- "Error importing file\\n\\\""
- ++ ExportFile ++ "\\\"")
- end;
- false ->
- import_frame1(Dir,
- "Error importing file\\n\\\"" ++
- File ++ "\\\"")
- end
- end;
- [{"dir",Dir}] ->
- import_frame1(Dir,"No file is selected")
- end.
-
-
-
-
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-% %
-% Different private helper functions %
-% %
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%%Create the Header for the page If we now the mimetype use that type %%
-%%otherwise use text %%
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-header() ->
- header("text/html").
-header(MimeType) ->
- "Pragma:no-cache\r\n" ++
- "Content-type: " ++ MimeType ++ "\r\n\r\n".
-
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%%Create the Htmlheader set the title of the page %%
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-html_header(Title) ->
- "<HTML>\n" ++
- "<HEAD>\n" ++
- "<TITLE>" ++ Title ++ "</TITLE>\n" ++
- "</HEAD>\n"
- "<BODY BGCOLOR=\"#FFFFFF\">\n".
-
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% Close the body- and Html tags %%
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-html_end()->
- "</BODY></HTML>".
-
-
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% A script which reloads the menu frame and possibly pops up an alert%%
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-reload_menu_script(Err) ->
- ["<SCRIPT>\n",
- "function reloadMenu()\n",
- " {\n",
- " parent.menu.document.location.href=\"./menu_frame\";\n",
- case Err of
- "" -> "";
- _ -> " alert(\""++Err++"\");\n"
- end,
- case get_warnings() of
- [] ->
- "";
- Warnings ->
- " alert(\""++fix_newline(lists:flatten(Warnings))++"\");\n"
- end,
- " }\n",
- "</SCRIPT>\n",
- "<BODY onLoad=reloadMenu() BGCOLOR=\"#FFFFFF\">"].
-
-fix_newline([$\n|Rest]) ->
- [$\\,$n|fix_newline(Rest)];
-fix_newline([$"|Rest]) ->
- [$\\,$"|fix_newline(Rest)];
-fix_newline([Char|Rest]) ->
- [Char|fix_newline(Rest)];
-fix_newline([]) ->
- [].
-
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-% Control the input data and return the intresting values or error %
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-get_input_data(Input,Key)->
- case lists:keysearch(Key,1,parse(Input)) of
- {value,{Key,Value}} ->
- Value;
- false ->
- undefined
- end.
-
-parse(Input) ->
- httpd:parse_query(Input).
-
-
-get_warnings() ->
- cover_group_leader_proc ! {self(), get_warnings},
- receive {warnings,Warnings} ->
- Warnings
- end.
diff --git a/lib/tools/src/cprof.erl b/lib/tools/src/cprof.erl
index 0240f876bc..f6d68f0bf8 100644
--- a/lib/tools/src/cprof.erl
+++ b/lib/tools/src/cprof.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2002-2009. All Rights Reserved.
+%% Copyright Ericsson AB 2002-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -114,6 +114,7 @@ analyse(Limit) when is_integer(Limit) ->
analyse(M) when is_atom(M) ->
analyse(M, 1).
+-dialyzer({no_improper_lists, analyse/2}).
analyse(M, Limit) when is_atom(M), is_integer(Limit) ->
L0 = [begin
MFA = {M,F,A},
diff --git a/lib/tools/src/eprof.erl b/lib/tools/src/eprof.erl
index 0357e46a50..535ddbcd04 100644
--- a/lib/tools/src/eprof.erl
+++ b/lib/tools/src/eprof.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2013. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -74,7 +74,6 @@
start() -> gen_server:start({local, ?MODULE}, ?MODULE, [], []).
stop() -> gen_server:call(?MODULE, stop, infinity).
-
analyze() ->
analyze(procs).
@@ -112,7 +111,7 @@ profile(Rootset, M, F, A, Pattern) when is_list(Rootset), is_atom(M), is_atom(F)
%% Returns when M:F/A has terminated
profile(Rootset, M, F, A, Pattern, Options) ->
- start(),
+ ok = start_internal(),
gen_server:call(?MODULE, {profile_start, Rootset, Pattern, {M,F,A}, Options}, infinity).
dump() ->
@@ -127,7 +126,7 @@ start_profiling(Rootset) ->
start_profiling(Rootset, Pattern) ->
start_profiling(Rootset, Pattern, ?default_options).
start_profiling(Rootset, Pattern, Options) ->
- start(),
+ ok = start_internal(),
gen_server:call(?MODULE, {profile_start, Rootset, Pattern, undefined, Options}, infinity).
stop_profiling() ->
@@ -247,13 +246,13 @@ handle_call(profile_stop, _From, #state{ profiling = true } = S) ->
%% logfile
handle_call({logfile, File}, _From, #state{ fd = OldFd } = S) ->
- case file:open(File, [write]) of
+ case file:open(File, [write, {encoding, utf8}]) of
{ok, Fd} ->
case OldFd of
undefined -> ok;
- OldFd -> file:close(OldFd)
+ OldFd -> ok = file:close(OldFd)
end,
- {reply, ok, S#state{ fd = Fd}};
+ {reply, ok, S#state{fd = Fd}};
Error ->
{reply, Error, S}
end;
@@ -479,11 +478,11 @@ string_bp_mfa([{Mfa, {Count, Time}}|Mfas], Tus, {MfaW, CountW, PercW, TimeW, TpC
Stpc = s("~.2f", [divide(Time,Count)]),
string_bp_mfa(Mfas, Tus, {
- erlang:max(MfaW, length(Smfa)),
- erlang:max(CountW,length(Scount)),
- erlang:max(PercW, length(Sperc)),
- erlang:max(TimeW, length(Stime)),
- erlang:max(TpCW, length(Stpc))
+ erlang:max(MfaW, string:length(Smfa)),
+ erlang:max(CountW,string:length(Scount)),
+ erlang:max(PercW, string:length(Sperc)),
+ erlang:max(TimeW, string:length(Stime)),
+ erlang:max(TpCW, string:length(Stpc))
}, [[Smfa, Scount, Sperc, Stime, Stpc] | Strings]).
print_bp_mfa(Mfas, {Tn, Tus}, Fd, Opts) ->
@@ -492,11 +491,11 @@ print_bp_mfa(Mfas, {Tn, Tus}, Fd, Opts) ->
TnStr = s(Tn),
TusStr = s(Tus),
TuspcStr = s("~.2f", [divide(Tus,Tn)]),
- Ws = {erlang:max(length("FUNCTION"), MfaW),
- lists:max([length("CALLS"), CountW, length(TnStr)]),
- erlang:max(length(" %"), PercW),
- lists:max([length("TIME"), TimeW, length(TusStr)]),
- lists:max([length("uS / CALLS"), TpCW, length(TuspcStr)])},
+ Ws = {erlang:max(string:length("FUNCTION"), MfaW),
+ lists:max([string:length("CALLS"), CountW, string:length(TnStr)]),
+ erlang:max(string:length(" %"), PercW),
+ lists:max([string:length("TIME"), TimeW, string:length(TusStr)]),
+ lists:max([string:length("uS / CALLS"), TpCW, string:length(TuspcStr)])},
format(Fd, Ws, ["FUNCTION", "CALLS", " %", "TIME", "uS / CALLS"]),
format(Fd, Ws, ["--------", "-----", "-------", "----", "----------"]),
lists:foreach(fun (String) -> format(Fd, Ws, String) end, Strs),
@@ -504,13 +503,13 @@ print_bp_mfa(Mfas, {Tn, Tus}, Fd, Opts) ->
format(Fd, Ws, ["Total:", TnStr, "100.00%", TusStr, TuspcStr]),
ok.
-s({M,F,A}) -> s("~w:~w/~w",[M,F,A]);
-s(Term) -> s("~p", [Term]).
+s({M,F,A}) -> s("~w:~tw/~w",[M,F,A]);
+s(Term) -> s("~tp", [Term]).
s(Format, Terms) -> lists:flatten(io_lib:format(Format, Terms)).
format(Fd, {MfaW, CountW, PercW, TimeW, TpCW}, Strings) ->
- format(Fd, s("~~.~ps ~~~ps ~~~ps ~~~ps [~~~ps]~~n", [MfaW, CountW, PercW, TimeW, TpCW]), Strings);
+ format(Fd, s("~~.~wts ~~~ws ~~~ws ~~~ws [~~~ws]~~n", [MfaW, CountW, PercW, TimeW, TpCW]), Strings);
format(undefined, Format, Strings) ->
io:format(Format, Strings),
ok;
@@ -521,3 +520,10 @@ format(Fd, Format, Strings) ->
divide(_,0) -> 0.0;
divide(T,N) -> T/N.
+
+start_internal() ->
+ case start() of
+ {ok, _} -> ok;
+ {error, {already_started,_}} -> ok;
+ Error -> Error
+ end.
diff --git a/lib/tools/src/fprof.erl b/lib/tools/src/fprof.erl
index 7c6fab0b75..fb657c2928 100644
--- a/lib/tools/src/fprof.erl
+++ b/lib/tools/src/fprof.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2001-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2001-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -1003,7 +1003,7 @@ handle_req(#analyse{dest = Dest,
already_open ->
ok;
ok ->
- file:close(DestPid)
+ ok = file:close(DestPid)
end,
State
end;
@@ -1136,7 +1136,7 @@ ensure_open(Pid, _Options) when is_pid(Pid) ->
ensure_open([], _Options) ->
{already_open, undefined};
ensure_open(Filename, Options) when is_atom(Filename); is_list(Filename) ->
- file:open(Filename, Options).
+ file:open(Filename, [{encoding, utf8} | Options]).
%%%---------------------------------
%%% Fairly generic utility functions
@@ -1364,7 +1364,7 @@ tracer_loop(Parent, Handler, State) ->
Trace when element(1, Trace) =:= trace_ts ->
tracer_loop(Parent, Handler, Handler(Trace, State));
{'EXIT', Parent, Reason} ->
- handler(end_of_trace, State),
+ _ = handler(end_of_trace, State),
exit(Reason);
_ ->
tracer_loop(Parent, Handler, State)
@@ -1450,12 +1450,10 @@ end_of_trace(Table, TS) ->
Procs = get(),
put(table, Table),
?dbg(2, "get() -> ~p~n", [Procs]),
- lists:map(
- fun ({Pid, _}) when is_pid(Pid) ->
- trace_exit(Table, Pid, TS)
- end,
- Procs),
- erase(),
+ _ = lists:map(fun ({Pid, _}) when is_pid(Pid) ->
+ trace_exit(Table, Pid, TS)
+ end, Procs),
+ _ = erase(),
ok.
@@ -1477,7 +1475,7 @@ info_suspect_call(GroupLeader, GroupLeader, _, _) ->
ok;
info_suspect_call(GroupLeader, _, Func, Pid) ->
io:format(GroupLeader,
- "~nWarning: ~p called in ~p - trace may become corrupt!~n",
+ "~nWarning: ~tp called in ~p - trace may become corrupt!~n",
parsify([Func, Pid])).
info(GroupLeader, GroupLeader, _, _) ->
@@ -1500,13 +1498,13 @@ dump_stack(Dump, Stack, Term) ->
{N, length(hd(Stack))}
end
end,
- io:format(Dump, "~s~p.~n", [lists:duplicate(Depth, " "), parsify(Term)]),
+ io:format(Dump, "~s~tp.~n", [lists:duplicate(Depth, " "), parsify(Term)]),
true.
dump(undefined, _) ->
false;
dump(Dump, Term) ->
- io:format(Dump, "~p.~n", [parsify(Term)]),
+ io:format(Dump, "~tp.~n", [parsify(Term)]),
true.
@@ -1567,13 +1565,20 @@ trace_handler({trace_ts, Pid, return_to, {_M, _F, Args} = MFArgs, TS} = Trace,
trace_return_to(Table, Pid, Func, TS),
TS;
%%
-%% spawn
+%% spawn, only needed (and reliable) prior to 19.0
trace_handler({trace_ts, Pid, spawn, Child, MFArgs, TS} = Trace,
Table, _, Dump) ->
dump_stack(Dump, get(Pid), Trace),
trace_spawn(Table, Child, MFArgs, TS, Pid),
TS;
%%
+%% spawned, added in 19.0
+trace_handler({trace_ts, Pid, spawned, Parent, MFArgs, TS} = Trace,
+ Table, _, Dump) ->
+ dump_stack(Dump, get(Pid), Trace),
+ trace_spawn(Table, Pid, MFArgs, TS, Parent),
+ TS;
+%%
%% exit
trace_handler({trace_ts, Pid, exit, _Reason, TS} = Trace,
Table, _, Dump) ->
@@ -1622,18 +1627,38 @@ trace_handler({trace_ts, Pid, in, {_M, _F, Args} = MFArgs, TS} = Trace,
TS;
%%
%% gc_start
-trace_handler({trace_ts, Pid, gc_start, _Func, TS} = Trace,
- Table, _, Dump) ->
+trace_handler({trace_ts, Pid, gc_minor_start, _Func, TS} = Trace, Table, _, Dump) ->
+ dump_stack(Dump, get(Pid), Trace),
+ trace_gc_start(Table, Pid, TS),
+ TS;
+
+trace_handler({trace_ts, Pid, gc_major_start, _Func, TS} = Trace, Table, _, Dump) ->
dump_stack(Dump, get(Pid), Trace),
trace_gc_start(Table, Pid, TS),
TS;
+
+trace_handler({trace_ts, Pid, gc_start, _Func, TS} = Trace, Table, _, Dump) ->
+ dump_stack(Dump, get(Pid), Trace),
+ trace_gc_start(Table, Pid, TS),
+ TS;
+
%%
%% gc_end
-trace_handler({trace_ts, Pid, gc_end, _Func, TS} = Trace,
- Table, _, Dump) ->
+trace_handler({trace_ts, Pid, gc_minor_end, _Func, TS} = Trace, Table, _, Dump) ->
+ dump_stack(Dump, get(Pid), Trace),
+ trace_gc_end(Table, Pid, TS),
+ TS;
+
+trace_handler({trace_ts, Pid, gc_major_end, _Func, TS} = Trace, Table, _, Dump) ->
dump_stack(Dump, get(Pid), Trace),
trace_gc_end(Table, Pid, TS),
TS;
+
+trace_handler({trace_ts, Pid, gc_end, _Func, TS} = Trace, Table, _, Dump) ->
+ dump_stack(Dump, get(Pid), Trace),
+ trace_gc_end(Table, Pid, TS),
+ TS;
+
%%
%% link
trace_handler({trace_ts, Pid, link, _OtherPid, TS} = Trace,
@@ -1677,6 +1702,12 @@ trace_handler({trace_ts, Pid, send, _OtherPid, _Msg, TS} = Trace,
dump_stack(Dump, get(Pid), Trace),
TS;
%%
+%% send_to_non_existing_process
+trace_handler({trace_ts, Pid, send_to_non_existing_process, _OtherPid, _Msg, TS} = Trace,
+ _Table, _, Dump) ->
+ dump_stack(Dump, get(Pid), Trace),
+ TS;
+%%
%% 'receive'
trace_handler({trace_ts, Pid, 'receive', _Msg, TS} = Trace,
_Table, _, Dump) ->
@@ -2014,8 +2045,10 @@ trace_spawn(Table, Pid, MFArgs, TS, Parent) ->
ets:insert(Table, #proc{id = Pid, parent = Parent,
spawned_as = MFArgs});
_ ->
- throw({inconsistent_trace_data, ?MODULE, ?LINE,
- [Pid, MFArgs, TS, Parent, Stack]})
+ %% In 19.0 we get both a spawn and spawned event,
+ %% however we do not know the order so we just ignore
+ %% the second event that comes
+ ok
end.
@@ -2029,7 +2062,7 @@ trace_exit(Table, Pid, TS) ->
[] ->
ok;
[_ | _] = Stack ->
- trace_return_to_int(Table, Pid, undefined, TS, Stack),
+ _ = trace_return_to_int(Table, Pid, undefined, TS, Stack),
ok
end,
ok.
@@ -2155,7 +2188,7 @@ trace_clock(_Table, _Pid, _T,
[[{suspend, _}], [{suspend, _}] | _]=_Stack, _Clock) ->
?dbg(9, "trace_clock(Table, ~w, ~w, ~w, ~w)~n",
[_Pid, _T, _Stack, _Clock]),
- void;
+ ok;
trace_clock(Table, Pid, T,
[[{garbage_collect, TS0}], [{suspend, _}]], Clock) ->
trace_clock_1(Table, Pid, T, TS0, undefined, garbage_collect, Clock);
@@ -2170,7 +2203,7 @@ trace_clock(Table, Pid, T, [[{Func0, TS0}], [{Func1, _} | _] | _], Clock) ->
trace_clock(Table, Pid, T, [[{Func0, TS0}]], Clock) ->
trace_clock_1(Table, Pid, T, TS0, undefined, Func0, Clock);
trace_clock(_, _, _, [], _) ->
- void.
+ ok.
trace_clock_1(Table, Pid, _, _, Caller, suspend, #clocks.own) ->
clock_add(Table, {Pid, Caller, suspend}, #clocks.own, 0);
@@ -2184,7 +2217,7 @@ trace_clock_1(Table, Pid, T, TS, Caller, Func, Clock) ->
clock_add(Table, Id, Clock, T) ->
?dbg(1, "clock_add(Table, ~w, ~w, ~w)~n", [Id, Clock, T]),
- try ets:update_counter(Table, Id, {Clock, T})
+ try ets:update_counter(Table, Id, {Clock, T}), ok
catch
error:badarg ->
ets:insert(Table, #clocks{id = Id}),
@@ -2193,7 +2226,7 @@ clock_add(Table, Id, Clock, T) ->
true -> ?dbg(0, "Negative counter value ~p ~p ~p ~p~n",
[X, Id, Clock, T])
end,
- X
+ ok
end.
clocks_add(Table, #clocks{id = Id} = Clocks) ->
@@ -2251,6 +2284,8 @@ do_analyse(Table, Analyse) ->
?dbg(5, "do_analyse_1(_, _) ->~p~n", [Result]),
Result.
+-dialyzer({no_improper_lists, do_analyse_1/2}).
+
do_analyse_1(Table,
#analyse{group_leader = GroupLeader,
dest = Io,
@@ -2568,17 +2603,17 @@ println({Io, [W1, W2, W3, W4]}, Head,
println({Io, _}, Head,
[],
Tail, Comment) ->
- io:format(Io, "~s~s~s~n",
+ io:format(Io, "~s~ts~ts~n",
[pad(Head, $ , 3), Tail, Comment]);
println({Io, _}, Head,
{Tag, Term},
Tail, Comment) ->
- io:format(Io, "~s~p, ~p~s~s~n",
+ io:format(Io, "~s~tp, ~tp~ts~ts~n",
[pad(Head, $ , 3), parsify(Tag), parsify(Term), Tail, Comment]);
println({Io, _}, Head,
Term,
Tail, Comment) ->
- io:format(Io, "~s~p~s~s~n",
+ io:format(Io, "~s~tp~ts~ts~n",
[pad(Head, $ , 3), parsify(Term), Tail, Comment]).
@@ -2601,22 +2636,32 @@ funcstat_pd(Pid, Func1, Func0, Clocks) ->
#funcstat{callers_sum = CallersSum,
callers = Callers} = FuncstatCallers ->
FuncstatCallers#funcstat{
- callers_sum = clocks_sum(CallersSum, Clocks, Func0),
- callers = [Clocks#clocks{id = Func1} | Callers]}
- end),
+ callers_sum = clocks_sum(CallersSum, Clocks, Func0),
+ callers = insert_call(Clocks, Func1, Callers)}
+ end),
put({Pid, Func1},
case get({Pid, Func1}) of
undefined ->
- #funcstat{callers_sum = #clocks{id = Func1},
+ #funcstat{callers_sum = #clocks{id = Func1},
called_sum = Clocks#clocks{id = Func1},
called = [Clocks#clocks{id = Func0}]};
#funcstat{called_sum = CalledSum,
called = Called} = FuncstatCalled ->
FuncstatCalled#funcstat{
called_sum = clocks_sum(CalledSum, Clocks, Func1),
- called = [Clocks#clocks{id = Func0} | Called]}
+ called = insert_call(Clocks, Func0, Called)}
end).
+insert_call(Clocks, Func, ClocksList) ->
+ insert_call(Clocks, Func, ClocksList, []).
+
+insert_call(Clocks, Func, [#clocks{id = Func} = C | T], Acc) ->
+ [clocks_sum(C, Clocks, Func) | T ++ Acc];
+insert_call(Clocks, Func, [H | T], Acc) ->
+ insert_call(Clocks, Func, T, [H | Acc]);
+insert_call(Clocks, Func, [], Acc) ->
+ [Clocks#clocks{id = Func} | Acc].
+
%% Sort a list of funcstat records,
@@ -2624,6 +2669,8 @@ funcstat_pd(Pid, Func1, Func0, Clocks) ->
funcstat_sort_r(FuncstatList, Element) ->
funcstat_sort_r_1(FuncstatList, Element, []).
+-dialyzer({no_improper_lists, funcstat_sort_r_1/3}).
+
funcstat_sort_r_1([], _, R) ->
postsort_r(lists:sort(R));
funcstat_sort_r_1([#funcstat{callers_sum = #clocks{} = Clocks,
@@ -2646,6 +2693,8 @@ funcstat_sort_r_1([#funcstat{callers_sum = #clocks{} = Clocks,
clocks_sort_r(L, E) ->
clocks_sort_r_1(L, E, []).
+-dialyzer({no_improper_lists, clocks_sort_r_1/3}).
+
clocks_sort_r_1([], _, R) ->
postsort_r(lists:sort(R));
clocks_sort_r_1([#clocks{} = C | L], E, R) ->
@@ -2671,7 +2720,7 @@ postsort_r([[_|C] | L], R) ->
flat_format(F, Trailer) when is_float(F) ->
lists:flatten([io_lib:format("~.3f", [F]), Trailer]);
flat_format(W, Trailer) ->
- lists:flatten([io_lib:format("~p", [W]), Trailer]).
+ lists:flatten([io_lib:format("~tp", [W]), Trailer]).
%% Format, flatten, and pad.
flat_format(Term, Trailer, Width) ->
diff --git a/lib/tools/src/instrument.erl b/lib/tools/src/instrument.erl
index 34c5ba04cc..055f4a7afb 100644
--- a/lib/tools/src/instrument.erl
+++ b/lib/tools/src/instrument.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1998-2009. All Rights Reserved.
+%% Copyright Ericsson AB 1998-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
diff --git a/lib/tools/src/lcnt.erl b/lib/tools/src/lcnt.erl
index e8b3d242e4..d0152a4915 100644
--- a/lib/tools/src/lcnt.erl
+++ b/lib/tools/src/lcnt.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -23,67 +23,60 @@
-author("Björn-Egil Dahlberg").
%% gen_server callbacks
--export([
- init/1,
- handle_call/3,
- handle_cast/2,
- handle_info/2,
- terminate/2,
- code_change/3
- ]).
+-export([init/1,
+ handle_call/3,
+ handle_cast/2,
+ handle_info/2,
+ terminate/2,
+ code_change/3]).
%% start/stop
--export([
- start/0,
- stop/0
- ]).
-
-%% erts_debug:lock_counters api
--export([
- rt_collect/0,
- rt_collect/1,
- rt_clear/0,
- rt_clear/1,
- rt_opt/1,
- rt_opt/2
- ]).
+-export([start/0,
+ stop/0]).
+
+%% erts_debug:lcnt_xxx api
+-export([rt_mask/0,
+ rt_mask/1,
+ rt_mask/2,
+ rt_collect/0,
+ rt_collect/1,
+ rt_clear/0,
+ rt_clear/1,
+ rt_opt/1,
+ rt_opt/2]).
%% gen_server call api
--export([
- raw/0,
- collect/0,
- collect/1,
- clear/0,
- clear/1,
- conflicts/0,
- conflicts/1,
- locations/0,
- locations/1,
- inspect/1,
- inspect/2,
- histogram/1,
- histogram/2,
- information/0,
- swap_pid_keys/0,
- % set options
- set/1,
- set/2,
-
- load/1,
- save/1
- ]).
+-export([raw/0,
+ collect/0,
+ collect/1,
+ clear/0,
+ clear/1,
+ conflicts/0,
+ conflicts/1,
+ locations/0,
+ locations/1,
+ inspect/1,
+ inspect/2,
+ histogram/1,
+ histogram/2,
+ information/0,
+ swap_pid_keys/0,
+ % set options
+ set/1,
+ set/2,
+
+ load/1,
+ save/1]).
%% convenience
--export([
- apply/3,
- apply/2,
- apply/1,
- all_conflicts/0,
- all_conflicts/1,
- pid/2, pid/3,
- port/1, port/2
- ]).
+-export([apply/3,
+ apply/2,
+ apply/1,
+ all_conflicts/0,
+ all_conflicts/1,
+ pid/2, pid/3,
+ port/1, port/2]).
-define(version, "1.0").
@@ -94,12 +87,12 @@
-record(stats, {
file :: atom(),
- line :: non_neg_integer(),
+ line :: non_neg_integer() | 'undefined',
tries :: non_neg_integer(),
colls :: non_neg_integer(),
time :: non_neg_integer(), % us
nt :: non_neg_integer(), % #timings collected
- hist :: tuple() % histogram
+ hist :: tuple() | 'undefined' % histogram
}).
-record(lock, {
@@ -135,29 +128,70 @@ start() -> gen_server:start({local, ?MODULE}, ?MODULE, [], []).
stop() -> gen_server:call(?MODULE, stop, infinity).
init([]) -> {ok, #state{ locks = [], duration = 0 } }.
+start_internal() ->
+ case start() of
+ {ok,_} -> ok;
+ {error, {already_started,_}} -> ok;
+ Error -> Error
+ end.
+
%% -------------------------------------------------------------------- %%
%%
-%% API erts_debug:lock_counters
+%% API erts_debug:lcnt_xxx
%%
%% -------------------------------------------------------------------- %%
-rt_collect() ->
- erts_debug:lock_counters(info).
+rt_mask(Node, Categories) when is_atom(Node), is_list(Categories) ->
+ rpc:call(Node, lcnt, rt_mask, [Categories]).
+
+rt_mask(Node) when is_atom(Node) ->
+ rpc:call(Node, lcnt, rt_mask, []);
+
+rt_mask(Categories) when is_list(Categories) ->
+ case erts_debug:lcnt_control(copy_save) of
+ false ->
+ erts_debug:lcnt_control(mask, Categories);
+ true ->
+ {error, copy_save_enabled}
+ end.
+
+rt_mask() ->
+ erts_debug:lcnt_control(mask).
rt_collect(Node) ->
- rpc:call(Node, erts_debug, lock_counters, [info]).
+ rpc:call(Node, lcnt, rt_collect, []).
+rt_collect() ->
+ erts_debug:lcnt_collect().
+rt_clear(Node) ->
+ rpc:call(Node, lcnt, rt_clear, []).
rt_clear() ->
- erts_debug:lock_counters(clear).
+ erts_debug:lcnt_clear().
-rt_clear(Node) ->
- rpc:call(Node, erts_debug, lock_counters, [clear]).
+rt_opt(Node, Arg) ->
+ rpc:call(Node, lcnt, rt_opt, [Arg]).
-rt_opt({Type, Opt}) ->
- erts_debug:lock_counters({Type, Opt}).
+%% Compatibility shims for the "process/port_locks" options mentioned in the
+%% manual.
+rt_opt({process_locks, Enable}) ->
+ toggle_category(process, Enable);
+rt_opt({port_locks, Enable}) ->
+ toggle_category(io, Enable);
-rt_opt(Node, {Type, Opt}) ->
- rpc:call(Node, erts_debug, lock_counters, [{Type, Opt}]).
+rt_opt({Type, NewVal}) ->
+ PreviousVal = erts_debug:lcnt_control(Type),
+ erts_debug:lcnt_control(Type, NewVal),
+ PreviousVal.
+
+toggle_category(Category, true) ->
+ PreviousMask = erts_debug:lcnt_control(mask),
+ erts_debug:lcnt_control(mask, [Category | PreviousMask]),
+ lists:member(Category, PreviousMask);
+
+toggle_category(Category, false) ->
+ PreviousMask = erts_debug:lcnt_control(mask),
+ erts_debug:lcnt_control(mask, lists:delete(Category, PreviousMask)),
+ lists:member(Category, PreviousMask).
%% -------------------------------------------------------------------- %%
%%
@@ -184,9 +218,11 @@ raw() -> call(raw).
set(Option, Value) -> call({set, Option, Value}).
set({Option, Value}) -> call({set, Option, Value}).
save(Filename) -> call({save, Filename}).
-load(Filename) -> start(), call({load, Filename}).
+load(Filename) -> call({load, Filename}).
-call(Msg) -> gen_server:call(?MODULE, Msg, infinity).
+call(Msg) ->
+ ok = start_internal(),
+ gen_server:call(?MODULE, Msg, infinity).
%% -------------------------------------------------------------------- %%
%%
@@ -195,24 +231,21 @@ call(Msg) -> gen_server:call(?MODULE, Msg, infinity).
%% -------------------------------------------------------------------- %%
apply(M,F,As) when is_atom(M), is_atom(F), is_list(As) ->
- lcnt:start(),
- Opt = lcnt:rt_opt({copy_save, true}),
- lcnt:clear(),
- Res = erlang:apply(M,F,As),
- lcnt:collect(),
- lcnt:rt_opt({copy_save, Opt}),
- Res.
+ apply(fun() ->
+ erlang:apply(M,F,As)
+ end).
apply(Fun) when is_function(Fun) ->
lcnt:apply(Fun, []).
apply(Fun, As) when is_function(Fun) ->
- lcnt:start(),
Opt = lcnt:rt_opt({copy_save, true}),
lcnt:clear(),
Res = erlang:apply(Fun, As),
lcnt:collect(),
- lcnt:rt_opt({copy_save, Opt}),
+ %% _ is bound to silence a dialyzer warning; it used to fail silently and
+ %% we don't want to change the error semantics.
+ _ = lcnt:rt_opt({copy_save, Opt}),
Res.
all_conflicts() -> all_conflicts(time).
@@ -701,19 +734,47 @@ stats2record([{{File,Line},{Tries,Colls,{S,Ns,N}}}|Stats]) ->
nt = N} | stats2record(Stats)];
stats2record([]) -> [].
+
clean_id_creation(Id) when is_pid(Id) ->
Bin = term_to_binary(Id),
- <<H:3/binary, L:16, Node:L/binary, Ids:8/binary, _Creation/binary>> = Bin,
- Bin2 = list_to_binary([H, bytes16(L), Node, Ids, 0]),
+ <<H:3/binary, Rest/binary>> = Bin,
+ <<131, PidTag, AtomTag>> = H,
+ LL = atomlen_bits(AtomTag),
+ CL = creation_bits(PidTag),
+ <<L:LL, Node:L/binary, Ids:8/binary, _Creation/binary>> = Rest,
+ Bin2 = list_to_binary([H, <<L:LL>>, Node, Ids, <<0:CL>>]),
binary_to_term(Bin2);
clean_id_creation(Id) when is_port(Id) ->
Bin = term_to_binary(Id),
- <<H:3/binary, L:16, Node:L/binary, Ids:4/binary, _Creation/binary>> = Bin,
- Bin2 = list_to_binary([H, bytes16(L), Node, Ids, 0]),
+ <<H:3/binary, Rest/binary>> = Bin,
+ <<131, PortTag, AtomTag>> = H,
+ LL = atomlen_bits(AtomTag),
+ CL = creation_bits(PortTag),
+ <<L:LL, Node:L/binary, Ids:4/binary, _Creation/binary>> = Rest,
+ Bin2 = list_to_binary([H, <<L:LL>>, Node, Ids, <<0:CL>>]),
binary_to_term(Bin2);
clean_id_creation(Id) ->
Id.
+-define(PID_EXT, $g).
+-define(NEW_PID_EXT, $X).
+-define(PORT_EXT, $f).
+-define(NEW_PORT_EXT, $Y).
+-define(ATOM_EXT, $d).
+-define(SMALL_ATOM_EXT, $s).
+-define(ATOM_UTF8_EXT, $v).
+-define(SMALL_ATOM_UTF8_EXT, $w).
+
+atomlen_bits(?ATOM_EXT) -> 16;
+atomlen_bits(?SMALL_ATOM_EXT) -> 8;
+atomlen_bits(?ATOM_UTF8_EXT) -> 16;
+atomlen_bits(?SMALL_ATOM_UTF8_EXT) -> 8.
+
+creation_bits(?PID_EXT) -> 8;
+creation_bits(?NEW_PID_EXT) -> 32;
+creation_bits(?PORT_EXT) -> 8;
+creation_bits(?NEW_PORT_EXT) -> 32.
+
%% serializer
state_default(Field) -> proplists:get_value(Field, state2list(#state{})).
@@ -757,7 +818,7 @@ list2lock([F|Fs], Ls) ->
stats2stats([]) -> [];
stats2stats([Stat|Stats]) ->
- Sz = tuple_size(#stats{}),
+ Sz = record_info(size, stats),
[stat2stat(Stat,Sz)|stats2stats(Stats)].
stat2stat(Stat,Sz) when tuple_size(Stat) =:= Sz -> Stat;
@@ -883,7 +944,7 @@ print_state_information(#state{locks = Locks} = State) ->
print(kv("#tries", s(Stats#stats.tries))),
print(kv("#colls", s(Stats#stats.colls))),
print(kv("wait time", s(Stats#stats.time) ++ " us" ++ " ( " ++ s(Stats#stats.time/1000000) ++ " s)")),
- print(kv("percent of duration", s(Stats#stats.time/State#state.duration*100) ++ " %")),
+ print(kv("percent of duration", s(percent(Stats#stats.time, State#state.duration)) ++ " %")),
ok.
@@ -933,19 +994,30 @@ strings(Strings) -> strings(Strings, []).
strings([], Out) -> Out;
strings([{space, N, S} | Ss], Out) -> strings(Ss, Out ++ term2string(term2string("~~~ws", [N]), [S]));
strings([{left, N, S} | Ss], Out) -> strings(Ss, Out ++ term2string(term2string(" ~~s~~~ws", [N]), [S,""]));
-strings([{format, Format, S} | Ss], Out) -> strings(Ss, Out ++ term2string(Format, [S]));
strings([S|Ss], Out) -> strings(Ss, Out ++ term2string("~ts", [S])).
term2string({M,F,A}) when is_atom(M), is_atom(F), is_integer(A) -> term2string("~p:~p/~p", [M,F,A]);
term2string(Term) when is_port(Term) ->
% ex #Port<6442.816>
- <<_:3/binary, L:16, Node:L/binary, Ids:32, _/binary>> = term_to_binary(Term),
- term2string("#Port<~s.~w>", [Node, Ids]);
+ case term_to_binary(Term) of
+ <<_:2/binary, ?SMALL_ATOM_UTF8_EXT, L:8, Node:L/binary, Ids:32, _/binary>> ->
+ term2string("#Port<~ts.~w>", [Node, Ids]);
+ <<_:2/binary, ?ATOM_UTF8_EXT, L:16, Node:L/binary, Ids:32, _/binary>> ->
+ term2string("#Port<~ts.~w>", [Node, Ids]);
+ <<_:2/binary, ?ATOM_EXT, L:16, Node:L/binary, Ids:32, _/binary>> ->
+ term2string("#Port<~s.~w>", [Node, Ids])
+ end;
term2string(Term) when is_pid(Term) ->
% ex <0.80.0>
- <<_:3/binary, L:16, Node:L/binary, Ids:32, Serial:32, _/binary>> = term_to_binary(Term),
- term2string("<~s.~w.~w>", [Node, Ids, Serial]);
+ case term_to_binary(Term) of
+ <<_:2/binary, ?SMALL_ATOM_UTF8_EXT, L:8, Node:L/binary, Ids:32, Serial:32, _/binary>> ->
+ term2string("<~ts.~w.~w>", [Node, Ids, Serial]);
+ <<_:2/binary, ?ATOM_UTF8_EXT, L:16, Node:L/binary, Ids:32, Serial:32, _/binary>> ->
+ term2string("<~ts.~w.~w>", [Node, Ids, Serial]);
+ <<_:2/binary, ?ATOM_EXT, L:16, Node:L/binary, Ids:32, Serial:32, _/binary>> ->
+ term2string("<~s.~w.~w>", [Node, Ids, Serial])
+ end;
term2string(Term) -> term2string("~w", [Term]).
term2string(Format, Terms) -> lists:flatten(io_lib:format(Format, Terms)).
diff --git a/lib/tools/src/make.erl b/lib/tools/src/make.erl
index 96c3e0e506..6554d338af 100644
--- a/lib/tools/src/make.erl
+++ b/lib/tools/src/make.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2013. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -25,39 +25,48 @@
%% If Emakefile is missing the current directory is used.
-module(make).
--export([all/0,all/1,files/1,files/2]).
+-export([all_or_nothing/0,all/0,all/1,files/1,files/2]).
-include_lib("kernel/include/file.hrl").
--define(MakeOpts,[noexec,load,netload,noload]).
+-define(MakeOpts,[noexec,load,netload,noload,emake]).
+
+all_or_nothing() ->
+ case all() of
+ up_to_date ->
+ up_to_date;
+ error ->
+ halt(1)
+ end.
all() ->
all([]).
all(Options) ->
- {MakeOpts,CompileOpts} = sort_options(Options,[],[]),
- case read_emakefile('Emakefile',CompileOpts) of
- Files when is_list(Files) ->
- do_make_files(Files,MakeOpts);
- error ->
- error
- end.
+ run_emake(undefined, Options).
files(Fs) ->
files(Fs, []).
files(Fs0, Options) ->
Fs = [filename:rootname(F,".erl") || F <- Fs0],
+ run_emake(Fs, Options).
+
+run_emake(Mods, Options) ->
{MakeOpts,CompileOpts} = sort_options(Options,[],[]),
- case get_opts_from_emakefile(Fs,'Emakefile',CompileOpts) of
+ Emake = get_emake(Options),
+ case normalize_emake(Emake, Mods, CompileOpts) of
Files when is_list(Files) ->
- do_make_files(Files,MakeOpts);
- error -> error
+ do_make_files(Files,MakeOpts);
+ error ->
+ error
end.
do_make_files(Fs, Opts) ->
process(Fs, lists:member(noexec, Opts), load_opt(Opts)).
+sort_options([{emake, _}=H|T],Make,Comp) ->
+ sort_options(T,[H|Make],Comp);
sort_options([H|T],Make,Comp) ->
case lists:member(H,?MakeOpts) of
@@ -81,20 +90,35 @@ sort_options([],Make,Comp) ->
%%%
%%% These elements are converted to [{ModList,OptList},...]
%%% ModList is a list of modulenames (strings)
-read_emakefile(Emakefile,Opts) ->
- case file:consult(Emakefile) of
- {ok,Emake} ->
+
+normalize_emake(EmakeRaw, Mods, Opts) ->
+ case EmakeRaw of
+ {ok, Emake} when Mods =:= undefined ->
transform(Emake,Opts,[],[]);
- {error,enoent} ->
+ {ok, Emake} when is_list(Mods) ->
+ ModsOpts = transform(Emake,Opts,[],[]),
+ ModStrings = [coerce_2_list(M) || M <- Mods],
+ get_opts_from_emakefile(ModsOpts,ModStrings,Opts,[]);
+ {error,enoent} when Mods =:= undefined ->
%% No Emakefile found - return all modules in current
%% directory and the options given at command line
- Mods = [filename:rootname(F) || F <- filelib:wildcard("*.erl")],
+ CwdMods = [filename:rootname(F) || F <- filelib:wildcard("*.erl")],
+ [{CwdMods, Opts}];
+ {error,enoent} when is_list(Mods) ->
[{Mods, Opts}];
- {error,Other} ->
- io:format("make: Trouble reading 'Emakefile':~n~tp~n",[Other]),
+ {error, Error} ->
+ io:format("make: Trouble reading 'Emakefile':~n~tp~n",[Error]),
error
end.
+get_emake(Opts) ->
+ case proplists:get_value(emake, Opts, false) of
+ false ->
+ file:consult('Emakefile');
+ OptsEmake ->
+ {ok, OptsEmake}
+ end.
+
transform([{Mod,ModOpts}|Emake],Opts,Files,Already) ->
case expand(Mod,Already) of
[] ->
@@ -135,31 +159,19 @@ expand(Mod,Already) ->
end
end.
-%%% Reads the given Emakefile to see if there are any specific compile
+%%% Reads the given Emake to see if there are any specific compile
%%% options given for the modules.
-get_opts_from_emakefile(Mods,Emakefile,Opts) ->
- case file:consult(Emakefile) of
- {ok,Emake} ->
- Modsandopts = transform(Emake,Opts,[],[]),
- ModStrings = [coerce_2_list(M) || M <- Mods],
- get_opts_from_emakefile2(Modsandopts,ModStrings,Opts,[]);
- {error,enoent} ->
- [{Mods, Opts}];
- {error,Other} ->
- io:format("make: Trouble reading 'Emakefile':~n~tp~n",[Other]),
- error
- end.
-get_opts_from_emakefile2([{MakefileMods,O}|Rest],Mods,Opts,Result) ->
+get_opts_from_emakefile([{MakefileMods,O}|Rest],Mods,Opts,Result) ->
case members(Mods,MakefileMods,[],Mods) of
{[],_} ->
- get_opts_from_emakefile2(Rest,Mods,Opts,Result);
+ get_opts_from_emakefile(Rest,Mods,Opts,Result);
{I,RestOfMods} ->
- get_opts_from_emakefile2(Rest,RestOfMods,Opts,[{I,O}|Result])
+ get_opts_from_emakefile(Rest,RestOfMods,Opts,[{I,O}|Result])
end;
-get_opts_from_emakefile2([],[],_Opts,Result) ->
+get_opts_from_emakefile([],[],_Opts,Result) ->
Result;
-get_opts_from_emakefile2([],RestOfMods,Opts,Result) ->
+get_opts_from_emakefile([],RestOfMods,Opts,Result) ->
[{RestOfMods,Opts}|Result].
members([H|T],MakefileMods,I,Rest) ->
@@ -255,15 +267,47 @@ include_opt([]) ->
recompile(File, true, _Load, _Opts) ->
io:format("Out of date: ~ts\n",[File]);
-recompile(File, false, noload, Opts) ->
- io:format("Recompile: ~ts\n",[File]),
- compile:file(File, [report_errors, report_warnings, error_summary |Opts]);
-recompile(File, false, load, Opts) ->
+recompile(File, false, Load, Opts) ->
io:format("Recompile: ~ts\n",[File]),
- c:c(File, Opts);
-recompile(File, false, netload, Opts) ->
- io:format("Recompile: ~ts\n",[File]),
- c:nc(File, Opts).
+ case compile:file(File, [report_errors, report_warnings |Opts]) of
+ Ok when is_tuple(Ok), element(1,Ok)==ok ->
+ maybe_load(element(2,Ok), Load, Opts);
+ _Error ->
+ error
+ end.
+
+maybe_load(_Mod, noload, _Opts) ->
+ ok;
+maybe_load(Mod, Load, Opts) ->
+ %% We have compiled File with options Opts. Find out where the
+ %% output file went to, and load it.
+ case compile:output_generated(Opts) of
+ true ->
+ Dir = proplists:get_value(outdir,Opts,"."),
+ do_load(Dir, Mod, Load);
+ false ->
+ io:format("** Warning: No object file created - nothing loaded **~n"),
+ ok
+ end.
+
+do_load(Dir, Mod, load) ->
+ code:purge(Mod),
+ case code:load_abs(filename:join(Dir, Mod),Mod) of
+ {module,Mod} ->
+ {ok,Mod};
+ Other ->
+ Other
+ end;
+do_load(Dir, Mod, netload) ->
+ Obj = atom_to_list(Mod) ++ code:objfile_extension(),
+ Fname = filename:join(Dir, Obj),
+ case file:read_file(Fname) of
+ {ok,Bin} ->
+ rpc:eval_everywhere(code,load_binary,[Mod,Fname,Bin]),
+ {ok,Mod};
+ Other ->
+ Other
+ end.
exists(File) ->
case file:read_file_info(File) of
@@ -278,11 +322,12 @@ coerce_2_list(X) when is_atom(X) ->
coerce_2_list(X) ->
X.
-%%% If you an include file is found with a modification
+%%% If an include file is found with a modification
%%% time larger than the modification time of the object
%%% file, return true. Otherwise return false.
check_includes(File, IncludePath, ObjMTime) ->
- Path = [filename:dirname(File)|IncludePath],
+ {ok,Cwd} = file:get_cwd(),
+ Path = [Cwd,filename:dirname(File)|IncludePath],
case epp:open(File, Path, []) of
{ok, Epp} ->
check_includes2(Epp, File, ObjMTime);
@@ -291,10 +336,11 @@ check_includes(File, IncludePath, ObjMTime) ->
end.
check_includes2(Epp, File, ObjMTime) ->
+ A1 = erl_anno:new(1),
case epp:parse_erl_form(Epp) of
- {ok, {attribute, 1, file, {File, 1}}} ->
+ {ok, {attribute, A1, file, {File, A1}}} ->
check_includes2(Epp, File, ObjMTime);
- {ok, {attribute, 1, file, {IncFile, 1}}} ->
+ {ok, {attribute, A1, file, {IncFile, A1}}} ->
case file:read_file_info(IncFile) of
{ok, #file_info{mtime=MTime}} when MTime>ObjMTime ->
epp:close(Epp),
@@ -308,5 +354,7 @@ check_includes2(Epp, File, ObjMTime) ->
epp:close(Epp),
false;
{error, _Error} ->
+ check_includes2(Epp, File, ObjMTime);
+ {warning, _Warning} ->
check_includes2(Epp, File, ObjMTime)
end.
diff --git a/lib/tools/src/tags.erl b/lib/tools/src/tags.erl
index 2bc1865503..fff67eb0fd 100644
--- a/lib/tools/src/tags.erl
+++ b/lib/tools/src/tags.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2015. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -101,7 +101,7 @@ files(Files, Options) ->
case open_out(Options) of
{ok, Os} ->
files_loop(Files, Os),
- close_out(Os),
+ ok = close_out(Os),
ok;
_ ->
error
@@ -169,7 +169,7 @@ filename(Name, Os) ->
case file:open(Name, [read]) of
{ok, Desc} ->
Acc = module(Desc, [], [], {1, 0}),
- file:close(Desc),
+ ok = file:close(Desc),
genout(Os, Name, Acc),
ok;
_ ->
diff --git a/lib/tools/src/tools.app.src b/lib/tools/src/tools.app.src
index 978b54719c..f8c6aa22cb 100644
--- a/lib/tools/src/tools.app.src
+++ b/lib/tools/src/tools.app.src
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2015. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -21,7 +21,6 @@
[{description, "DEVTOOLS CXC 138 16"},
{vsn, "%VSN%"},
{modules, [cover,
- cover_web,
eprof,
fprof,
instrument,
@@ -36,13 +35,12 @@
xref_utils
]
},
- {registered,[webcover_server]},
+ {registered, []},
{applications, [kernel, stdlib]},
{env, [{file_util_search_methods,[{"", ""}, {"ebin", "esrc"}, {"ebin", "src"}]}
]
},
- {runtime_dependencies, ["webtool-0.8.10","stdlib-2.5","runtime_tools-1.8.14",
- "kernel-3.0","inets-5.10","erts-7.0",
- "compiler-5.0"]}
+ {runtime_dependencies, ["stdlib-3.4","runtime_tools-1.8.14",
+ "kernel-5.4","erts-9.1","compiler-5.0"]}
]
}.
diff --git a/lib/tools/src/tools.appup.src b/lib/tools/src/tools.appup.src
index 9eee8df184..fa48fa4219 100644
--- a/lib/tools/src/tools.appup.src
+++ b/lib/tools/src/tools.appup.src
@@ -1,7 +1,7 @@
%% -*- erlang -*-
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2001-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2001-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
diff --git a/lib/tools/src/xref.erl b/lib/tools/src/xref.erl
index 64add7afbb..32efa36fa2 100644
--- a/lib/tools/src/xref.erl
+++ b/lib/tools/src/xref.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2000-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2000-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
diff --git a/lib/tools/src/xref_base.erl b/lib/tools/src/xref_base.erl
index 88031f6e77..a28c6ee283 100644
--- a/lib/tools/src/xref_base.erl
+++ b/lib/tools/src/xref_base.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2000-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2000-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -400,26 +400,28 @@ analysis(locals_not_used, functions) ->
%% used (indirectly) from any export: "(domain EE + range EE) * L".
%% But then we only get locals that make some calls, so we add
%% locals that are not used at all: "L * (UU + XU - LU)".
- "L * ((UU + XU - LU) + domain EE + range EE)";
+ %% We also need to exclude functions with the -on_load() attribute:
+ %% (L - OL) is used rather than just L.
+ "(L - OL) * ((UU + XU - LU) + domain EE + range EE)";
analysis(exports_not_used, _) ->
%% Local calls are not considered here. "X * UU" would do otherwise.
"X - XU";
analysis({call, F}, functions) ->
- make_query("range (E | ~w : Fun)", [F]);
+ make_query("range (E | ~tw : Fun)", [F]);
analysis({use, F}, functions) ->
- make_query("domain (E || ~w : Fun)", [F]);
+ make_query("domain (E || ~tw : Fun)", [F]);
analysis({module_call, M}, _) ->
- make_query("range (ME | ~w : Mod)", [M]);
+ make_query("range (ME | ~tw : Mod)", [M]);
analysis({module_use, M}, _) ->
- make_query("domain (ME || ~w : Mod)", [M]);
+ make_query("domain (ME || ~tw : Mod)", [M]);
analysis({application_call, A}, _) ->
- make_query("range (AE | ~w : App)", [A]);
+ make_query("range (AE | ~tw : App)", [A]);
analysis({application_use, A}, _) ->
- make_query("domain (AE || ~w : App)", [A]);
+ make_query("domain (AE || ~tw : App)", [A]);
analysis({release_call, R}, _) ->
- make_query("range (RE | ~w : Rel)", [R]);
+ make_query("range (RE | ~tw : Rel)", [R]);
analysis({release_use, R}, _) ->
- make_query("domain (RE || ~w : Rel)", [R]);
+ make_query("domain (RE || ~tw : Rel)", [R]);
analysis(deprecated_function_calls, functions) ->
"XC || DF";
analysis({deprecated_function_calls,Flag}, functions) ->
@@ -669,16 +671,45 @@ do_add_directory(Dir, AppName, Bui, Rec, Ver, War, State) ->
warnings(War, unreadable, Unreadable),
case Errors of
[] ->
- do_add_modules(FileNames, AppName, Bui, Ver, War, State, []);
+ do_add_modules(FileNames, AppName, Bui, Ver, War, State);
[Error | _] ->
throw(Error)
end.
-do_add_modules([], _AppName, _OB, _OV, _OW, State, Modules) ->
+do_add_modules(Files, AppName, OB, OV, OW, State0) ->
+ NFiles = length(Files),
+ Reader = fun(SplitName, State) ->
+ _Pid = read_module(SplitName, AppName, OB, OV, OW, State)
+ end,
+ N = parallelism(),
+ Files1 = start_readers(Files, Reader, State0, N),
+ %% Increase the number of readers towards the end to decrease the
+ %% waiting time for the collecting process:
+ Nx = N,
+ add_mods(Files1, Reader, State0, [], NFiles, Nx).
+
+add_mods(_, _ReaderFun, State, Modules, 0, _Nx) ->
{ok, sort(Modules), State};
-do_add_modules([File | Files], AppName, OB, OV, OW, State, Modules) ->
- {ok, M, NewState} = do_add_module(File, AppName, OB, OV, OW, State),
- do_add_modules(Files, AppName, OB, OV, OW, NewState, M ++ Modules).
+add_mods(Files, ReaderFun, State, Modules, N, Nx) ->
+ {I, Nx1} = case Nx > 0 of
+ false -> {1, Nx};
+ true -> {2, Nx - 1}
+ end,
+ Files1 = start_readers(Files, ReaderFun, State, I),
+ {ok, M, NewState} = process_module(State),
+ add_mods(Files1, ReaderFun, NewState, M ++ Modules, N - 1, Nx1).
+
+start_readers([SplitName|Files], ReaderFun, State, N) when N > 0 ->
+ _Pid = ReaderFun(SplitName, State),
+ start_readers(Files, ReaderFun, State, N - 1);
+start_readers(Files, _ReaderFun, _State, _) ->
+ Files.
+
+parallelism() ->
+ case erlang:system_info(multi_scheduling) of
+ enabled -> erlang:system_info(schedulers_online);
+ _ -> 1
+ end.
%% -> {ok, Module, State} | throw(Error)
do_add_a_module(File, AppName, Builtins, Verbose, Warnings, State) ->
@@ -692,50 +723,75 @@ do_add_a_module(File, AppName, Builtins, Verbose, Warnings, State) ->
%% -> {ok, Module, State} | throw(Error)
%% Options: verbose, warnings, builtins
-do_add_module({Dir, Basename}, AppName, Builtins, Verbose, Warnings, State) ->
- File = filename:join(Dir, Basename),
- {ok, M, Bad, NewState} =
- do_add_module1(Dir, File, AppName, Builtins, Verbose, Warnings, State),
- filter(fun({Tag,B}) -> warnings(Warnings, Tag, [[File,B]]) end, Bad),
- {ok, M, NewState}.
-
-do_add_module1(Dir, File, AppName, Builtins, Verbose, Warnings, State) ->
- message(Verbose, reading_beam, [File]),
- Mode = State#xref.mode,
+do_add_module(SplitName, AppName, Builtins, Verbose, Warnings, State) ->
+ _Pid = read_module(SplitName, AppName, Builtins, Verbose, Warnings, State),
+ process_module(State).
+
+read_module(SplitName, AppName, Builtins, Verbose, Warnings, State) ->
Me = self(),
- Fun = fun() -> Me ! {self(), abst(File, Builtins, Mode)} end,
- case xref_utils:subprocess(Fun, [link, {min_heap_size,100000}]) of
+ #xref{mode = Mode} = State,
+ Fun =
+ fun() ->
+ Me ! {?MODULE,
+ read_a_module(SplitName, AppName, Builtins, Verbose,
+ Warnings, Mode)}
+ end,
+ spawn_opt(Fun, [link, {min_heap_size, 1000000}, {priority, high}]).
+
+read_a_module({Dir, BaseName}, AppName, Builtins, Verbose, Warnings, Mode) ->
+ File = filename:join(Dir, BaseName),
+ case abst(File, Builtins, Mode) of
{ok, _M, no_abstract_code} when Verbose ->
- message(Verbose, skipped_beam, []),
- {ok, [], [], State};
+ message(Verbose, no_debug_info, [File]),
+ no;
{ok, _M, no_abstract_code} when not Verbose ->
message(Warnings, no_debug_info, [File]),
- {ok, [], [], State};
+ no;
{ok, M, Data, UnresCalls0} ->
- %% Remove duplicates. Identical unresolved calls on the
- %% same line are counted as _one_ unresolved call.
- UnresCalls = usort(UnresCalls0),
- message(Verbose, done, []),
- NoUnresCalls = length(UnresCalls),
- case NoUnresCalls of
- 0 -> ok;
- 1 -> warnings(Warnings, unresolved_summary1, [[M]]);
- N -> warnings(Warnings, unresolved_summary, [[M, N]])
- end,
- T = case xref_utils:file_info(File) of
- {ok, {_, _, _, Time}} -> Time;
- Error -> throw(Error)
- end,
- XMod = #xref_mod{name = M, app_name = AppName, dir = Dir,
- mtime = T, builtins = Builtins,
- no_unresolved = NoUnresCalls},
- do_add_module(State, XMod, UnresCalls, Data);
+ message(Verbose, done_file, [File]),
+ %% Remove duplicates. Identical unresolved calls on the
+ %% same line are counted as _one_ unresolved call.
+ UnresCalls = usort(UnresCalls0),
+ NoUnresCalls = length(UnresCalls),
+ case NoUnresCalls of
+ 0 -> ok;
+ 1 -> warnings(Warnings, unresolved_summary1, [[M]]);
+ N -> warnings(Warnings, unresolved_summary, [[M, N]])
+ end,
+ case xref_utils:file_info(File) of
+ {ok, {_, _, _, Time}} ->
+ XMod = #xref_mod{name = M, app_name = AppName,
+ dir = Dir, mtime = Time,
+ builtins = Builtins,
+ no_unresolved = NoUnresCalls},
+ {ok, PrepMod, Bad} =
+ prepare_module(Mode, XMod, UnresCalls, Data),
+ foreach(fun({Tag,B}) ->
+ warnings(Warnings, Tag,
+ [[File,B]])
+ end, Bad),
+ {ok, PrepMod};
+ Error -> Error
+ end;
Error ->
message(Verbose, error, []),
- throw(Error)
+ Error
end.
-abst(File, Builtins, Mode) when Mode =:= functions ->
+process_module(State) ->
+ receive
+ {?MODULE, Reply} ->
+ case Reply of
+ no ->
+ {ok, [], State};
+ {ok, PrepMod} ->
+ finish_module(PrepMod, State);
+ Error ->
+ throw(Error)
+ end
+ end.
+
+abst(File, Builtins, _Mode = functions) ->
case beam_lib:chunks(File, [abstract_code, exports, attributes]) of
{ok, {M,[{abstract_code,NoA},_X,_A]}} when NoA =:= no_abstract_code ->
{ok, M, NoA};
@@ -755,14 +811,15 @@ abst(File, Builtins, Mode) when Mode =:= functions ->
{exports,X0}, {attributes,A}]}} ->
%% R9C-
Forms0 = epp:interpret_file_attribute(Code),
- {_,_,Forms,_} = sys_pre_expand:module(Forms0, []),
+ Forms1 = erl_expand_records:module(Forms0, []),
+ Forms = erl_internal:add_predefined_functions(Forms1),
X = mfa_exports(X0, A, M),
D = deprecated(A, X, M),
xref_reader:module(M, Forms, Builtins, X, D);
Error when element(1, Error) =:= error ->
Error
end;
-abst(File, Builtins, Mode) when Mode =:= modules ->
+abst(File, Builtins, _Mode = modules) ->
case beam_lib:chunks(File, [exports, imports, attributes]) of
{ok, {Mod, [{exports,X0}, {imports,I0}, {attributes,At}]}} ->
X1 = mfa_exports(X0, At, Mod),
@@ -856,20 +913,14 @@ deprecated_flag(_) -> undefined.
%% dom CallAt = LC U XC
%% Attrs is collected from the attribute 'xref' (experimental).
do_add_module(S, XMod, Unres, Data) ->
- M = XMod#xref_mod.name,
- case dict:find(M, S#xref.modules) of
- {ok, OldXMod} ->
- BF2 = module_file(XMod),
- BF1 = module_file(OldXMod),
- throw_error({module_clash, {M, BF1, BF2}});
- error ->
- do_add_module(S, M, XMod, Unres, Data)
- end.
-
-%%do_add_module(S, M, _XMod, _Unres, Data)->
-%% {ok, M, [], S};
-do_add_module(S, M, XMod, Unres0, Data) when S#xref.mode =:= functions ->
- {DefAt0, LPreCAt0, XPreCAt0, LC0, XC0, X0, Attrs, Depr} = Data,
+ #xref{mode = Mode} = S,
+ Mode = S#xref.mode,
+ {ok, PrepMod, Bad} = prepare_module(Mode, XMod, Unres, Data),
+ {ok, Ms, NS} = finish_module(PrepMod, S),
+ {ok, Ms, Bad, NS}.
+
+prepare_module(_Mode = functions, XMod, Unres0, Data) ->
+ {DefAt0, LPreCAt0, XPreCAt0, LC0, XC0, X0, Attrs, Depr, OL0} = Data,
%% Bad is a list of bad values of 'xref' attributes.
{ALC0,AXC0,Bad0} = Attrs,
FT = [tspec(func)],
@@ -886,6 +937,7 @@ do_add_module(S, M, XMod, Unres0, Data) when S#xref.mode =:= functions ->
ALC1 = xref_utils:xset(ALC0, PCA),
UnresCalls = xref_utils:xset(Unres0, PCA),
Unres = domain(UnresCalls),
+ OL1 = xref_utils:xset(OL0, FT),
DefinedFuns = domain(DefAt),
{AXC, ALC, Bad1, LPreCAt2, XPreCAt2} =
@@ -904,29 +956,30 @@ do_add_module(S, M, XMod, Unres0, Data) when S#xref.mode =:= functions ->
LC = union(LC1, ALC),
{DF1,DF_11,DF_21,DF_31,DBad} = depr_mod(Depr, X),
+ {EE, ECallAt} = inter_graph(X, L, LC, XC, CallAt),
+ {ok, {functions, XMod, [DefAt,L,X,LCallAt,XCallAt,CallAt,LC,XC,EE,ECallAt,
+ OL1,DF1,DF_11,DF_21,DF_31], NoCalls, Unres},
+ DBad++Bad};
+prepare_module(_Mode = modules, XMod, _Unres, Data) ->
+ {X0, I0, Depr} = Data,
+ X1 = xref_utils:xset(X0, [tspec(func)]),
+ I1 = xref_utils:xset(I0, [tspec(func)]),
+ {DF1,DF_11,DF_21,DF_31,DBad} = depr_mod(Depr, X1),
+ {ok, {modules, XMod, [X1,I1,DF1,DF_11,DF_21,DF_31]}, DBad}.
- %% {EE, ECallAt} = inter_graph(X, L, LC, XC, LCallAt, XCallAt),
- Self = self(),
- Fun = fun() -> inter_graph(Self, X, L, LC, XC, CallAt) end,
- {EE, ECallAt} =
- xref_utils:subprocess(Fun, [link, {min_heap_size,100000}]),
-
+finish_module({functions, XMod, List, NoCalls, Unres}, S) ->
+ ok = check_module(XMod, S),
[DefAt2,L2,X2,LCallAt2,XCallAt2,CallAt2,LC2,XC2,EE2,ECallAt2,
- DF2,DF_12,DF_22,DF_32] =
- pack([DefAt,L,X,LCallAt,XCallAt,CallAt,LC,XC,EE,ECallAt,
- DF1,DF_11,DF_21,DF_31]),
-
- %% Foo = [DefAt2,L2,X2,LCallAt2,XCallAt2,CallAt2,LC2,XC2,EE2,ECallAt2,
- %% DF2,DF_12,DF_22,DF_32],
- %% io:format("{~p, ~p, ~p},~n", [M, pack:lsize(Foo), pack:usize(Foo)]),
+ OL2,DF2,DF_12,DF_22,DF_32] = pack(List),
LU = range(LC2),
LPredefined = predefined_funs(LU),
+ M = XMod#xref_mod.name,
MS = xref_utils:xset(M, atom),
T = from_sets({MS,DefAt2,L2,X2,LCallAt2,XCallAt2,CallAt2,
- LC2,XC2,LU,EE2,ECallAt2,Unres,LPredefined,
+ LC2,XC2,LU,EE2,ECallAt2,Unres,LPredefined,OL2,
DF2,DF_12,DF_22,DF_32}),
NoUnres = XMod#xref_mod.no_unresolved,
@@ -934,19 +987,28 @@ do_add_module(S, M, XMod, Unres0, Data) when S#xref.mode =:= functions ->
XMod1 = XMod#xref_mod{data = T, info = Info},
S1 = S#xref{modules = dict:store(M, XMod1, S#xref.modules)},
- {ok, [M], DBad++Bad, take_down(S1)};
-do_add_module(S, M, XMod, _Unres, Data) when S#xref.mode =:= modules ->
- {X0, I0, Depr} = Data,
- X1 = xref_utils:xset(X0, [tspec(func)]),
- I1 = xref_utils:xset(I0, [tspec(func)]),
- {DF1,DF_11,DF_21,DF_31,DBad} = depr_mod(Depr, X1),
- [X2,I2,DF2,DF_12,DF_22,DF_32] = pack([X1,I1,DF1,DF_11,DF_21,DF_31]),
+ {ok, [M], take_down(S1)};
+finish_module({modules, XMod, List}, S) ->
+ ok = check_module(XMod, S),
+ [X2,I2,DF2,DF_12,DF_22,DF_32] = pack(List),
+ M = XMod#xref_mod.name,
MS = xref_utils:xset(M, atom),
T = from_sets({MS, X2, I2, DF2, DF_12, DF_22, DF_32}),
Info = [],
XMod1 = XMod#xref_mod{data = T, info = Info},
S1 = S#xref{modules = dict:store(M, XMod1, S#xref.modules)},
- {ok, [M], DBad, take_down(S1)}.
+ {ok, [M], take_down(S1)}.
+
+check_module(XMod, State) ->
+ M = XMod#xref_mod.name,
+ case dict:find(M, State#xref.modules) of
+ {ok, OldXMod} ->
+ BF2 = module_file(XMod),
+ BF1 = module_file(OldXMod),
+ throw_error({module_clash, {M, BF1, BF2}});
+ error ->
+ ok
+ end.
depr_mod({Depr,Bad0}, X) ->
%% Bad0 are badly formed deprecated attributes.
@@ -992,9 +1054,6 @@ no_info(X, L, LC, XC, EE, Unres, NoCalls, NoUnresCalls) ->
%% Note: this is overwritten in do_set_up():
{no_inter_function_calls, no_elements(EE)}].
-inter_graph(Pid, X, L, LC, XC, CallAt) ->
- Pid ! {self(), inter_graph(X, L, LC, XC, CallAt)}.
-
%% Inter Call Graph.
%inter_graph(_X, _L, _LC, _XC, _CallAt) ->
% {empty_set(), empty_set()};
@@ -1164,7 +1223,7 @@ do_set_up(S, VerboseOpt) ->
%% If data has been supplied using add_module/9 (and that is the only
%% sanctioned way), then DefAt, L, X, LCallAt, XCallAt, CallAt, XC, LC,
-%% and LU are guaranteed to be functions (with all supplied
+%% LU and OL are guaranteed to be functions (with all supplied
%% modules as domain (disregarding unknown modules, that is, modules
%% not supplied but hosting unknown functions)).
%% As a consequence, V and E are also functions. V is defined for unknown
@@ -1177,8 +1236,8 @@ do_set_up(S, VerboseOpt) ->
do_set_up(S) when S#xref.mode =:= functions ->
ModDictList = dict:to_list(S#xref.modules),
[DefAt0, L, X0, LCallAt, XCallAt, CallAt, LC, XC, LU,
- EE0, ECallAt, UC, LPredefined,
- Mod_DF,Mod_DF_1,Mod_DF_2,Mod_DF_3] = make_families(ModDictList, 18),
+ EE0, ECallAt, UC, LPredefined, OL,
+ Mod_DF,Mod_DF_1,Mod_DF_2,Mod_DF_3] = make_families(ModDictList, 19),
{XC_1, XU, XPredefined} = do_set_up_1(XC),
LC_1 = user_family(union_of_family(LC)),
@@ -1258,13 +1317,14 @@ do_set_up(S) when S#xref.mode =:= functions ->
UC_1 = user_family(union_of_family(UC)),
?FORMAT("DefAt ~p~n", [DefAt]),
- ?FORMAT("U=~p~nLib=~p~nB=~p~nLU=~p~nXU=~p~nUU=~p~n", [U,Lib,B,LU,XU,UU]),
+ ?FORMAT("U=~p~nLib=~p~nB=~p~nLU=~p~nXU=~p~nUU=~p~nOL=~p~n",
+ [U,Lib,B,LU,XU,UU,OL]),
?FORMAT("E_1=~p~nLC_1=~p~nXC_1=~p~n", [E_1,LC_1,XC_1]),
?FORMAT("EE=~p~nEE_1=~p~nECallAt=~p~n", [EE, EE_1, ECallAt]),
?FORMAT("DF=~p~nDF_1=~p~nDF_2=~p~nDF_3=~p~n", [DF, DF_1, DF_2, DF_3]),
Vs = [{'L',L}, {'X',X},{'F',F},{'U',U},{'B',B},{'UU',UU},
- {'XU',XU},{'LU',LU},{'V',V},{v,V},
+ {'XU',XU},{'LU',LU},{'V',V},{v,V},{'OL',OL},
{'LC',{LC,LC_1}},{'XC',{XC,XC_1}},{'E',{E,E_1}},{e,{E,E_1}},
{'EE',{EE,EE_1}},{'UC',{UC,UC_1}},
{'M',M},{'A',A},{'R',R},
@@ -1349,6 +1409,7 @@ var_type('U') -> {function, vertex};
var_type('UU') -> {function, vertex};
var_type('V') -> {function, vertex};
var_type('X') -> {function, vertex};
+var_type('OL') -> {function, vertex};
var_type('XU') -> {function, vertex};
var_type('DF') -> {function, vertex};
var_type('DF_1') -> {function, vertex};
@@ -1727,7 +1788,7 @@ pack(T) ->
NT = pack1(T),
%% true = T =:= NT,
%% io:format("erasing ~p elements...~n", [length(erase())]),
- erase(), % wasting heap (and time)...
+ _ = erase(), % wasting heap (and time)...
foreach(fun({K,V}) -> put(K, V) end, PD),
NT.
@@ -1766,10 +1827,6 @@ tpack(T, I, L) ->
message(true, What, Arg) ->
case What of
- reading_beam ->
- io:format("~ts... ", Arg);
- skipped_beam ->
- io:format("skipped (no debug information)~n", Arg);
no_debug_info ->
io:format("Skipping ~ts (no debug information)~n", Arg);
unresolved_summary1 ->
@@ -1781,9 +1838,9 @@ message(true, What, Arg) ->
unreadable ->
io:format("Skipping ~ts (unreadable)~n", [Arg]);
xref_attr ->
- io:format("~ts: Skipping 'xref' attribute ~w~n", Arg);
+ io:format("~ts: Skipping 'xref' attribute ~tw~n", Arg);
depr_attr ->
- io:format("~ts: Skipping 'deprecated' attribute ~w~n", Arg);
+ io:format("~ts: Skipping 'deprecated' attribute ~tw~n", Arg);
lib_search ->
io:format("Scanning library path for BEAM files... ", []);
lib_check ->
@@ -1792,6 +1849,8 @@ message(true, What, Arg) ->
io:format("Setting up...", Arg);
done ->
io:format("done~n", Arg);
+ done_file ->
+ io:format("done reading ~ts~n", Arg);
error ->
io:format("error~n", Arg);
Else ->
diff --git a/lib/tools/src/xref_parser.yrl b/lib/tools/src/xref_parser.yrl
index 48602bb120..5ee6419ff5 100644
--- a/lib/tools/src/xref_parser.yrl
+++ b/lib/tools/src/xref_parser.yrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2000-2010. All Rights Reserved.
+%% Copyright Ericsson AB 2000-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -170,7 +170,7 @@ is_prefix_op('#') -> numeric;
is_prefix_op(_) -> false.
check_regexp(String) ->
- case re:compile(String) of
+ case re:compile(String, [unicode]) of
{ok, _Expr} ->
{regexpr, String};
{error, {ErrString, Position}} ->
@@ -274,7 +274,7 @@ mfa2s({M,F,A}) ->
[c2s(M),':',c2s(F),'/',A].
c2s(C) ->
- [S] = io_lib:format("~p", [C]),
+ [S] = io_lib:format("~tp", [C]),
list_to_atom(S).
re(variable) -> ['_'];
diff --git a/lib/tools/src/xref_reader.erl b/lib/tools/src/xref_reader.erl
index 41b93caaeb..d28bdb78db 100644
--- a/lib/tools/src/xref_reader.erl
+++ b/lib/tools/src/xref_reader.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2000-2015. All Rights Reserved.
+%% Copyright Ericsson AB 2000-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -42,17 +42,16 @@
%% experimental; -xref(FunEdge) is recognized.
lattrs=[], % local calls, {{mfa(),mfa()},Line}
xattrs=[], % external calls, -"-
- battrs=[] % badly formed xref attributes, term().
+ battrs=[], % badly formed xref attributes, term().
+ on_load % function name
}).
-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
+%% R7: abstract_v1
+%% R8: abstract_v2
+%% R9C: raw_abstract_v1
%% -> {ok, Module, {DefAt, CallAt, LC, XC, X, Attrs}, Unresolved}} | EXIT
%% Attrs = {ALC, AXC, Bad}
@@ -70,15 +69,26 @@ forms([F | Fs], S) ->
forms([], S) ->
#xrefr{module = M, def_at = DefAt,
l_call_at = LCallAt, x_call_at = XCallAt,
- el = LC, ex = XC, x = X, df = Depr,
+ el = LC, ex = XC, x = X, df = Depr, on_load = OnLoad,
+ lattrs = AL, xattrs = AX, battrs = B, unresolved = U} = S,
+ OL = case OnLoad of
+ undefined -> [];
+ F ->
+ [{M, F, 0}]
+ end,
+ #xrefr{def_at = DefAt,
+ l_call_at = LCallAt, x_call_at = XCallAt,
+ el = LC, ex = XC, x = X, df = Depr, on_load = OnLoad,
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}.
+ {ok, M, {DefAt, LCallAt, XCallAt, LC, XC, X, Attrs, Depr, OL}, U}.
form({attribute, Line, xref, Calls}, S) -> % experimental
#xrefr{module = M, function = Fun,
lattrs = L, xattrs = X, battrs = B} = S,
attr(Calls, erl_anno:line(Line), M, Fun, L, X, B, S);
+form({attribute, _, on_load, {F, 0}}, S) ->
+ S#xrefr{on_load = F};
form({attribute, _Line, _Attr, _Val}, S) ->
S;
form({function, _, module_info, 0, _Clauses}, S) ->
@@ -92,7 +102,12 @@ form({function, Anno, Name, Arity, Clauses}, S) ->
Line = erl_anno:line(Anno),
S2 = S1#xrefr{def_at = [{MFA,Line} | S#xrefr.def_at]},
S3 = clauses(Clauses, S2),
- S3#xrefr{function = []}.
+ S3#xrefr{function = []};
+form(_, S) ->
+ %% OTP 20. Other uninteresting forms such as {eof, _} and {warning, _}.
+ %% Exposed because sys_pre_expand is no longer run.
+ S.
+
clauses(Cls, S) ->
#xrefr{funvars = FunVars, matches = Matches} = S,
@@ -109,6 +124,8 @@ clauses([{clause, _Line, _H, G, B} | Cs], FunVars, Matches, S) ->
clauses([], _FunVars, _Matches, S) ->
S.
+attr(NotList, Ln, M, Fun, AL, AX, B, S) when not is_list(NotList) ->
+ attr([NotList], Ln, M, Fun, AL, AX, B, S);
attr([E={From, To} | As], Ln, M, Fun, AL, AX, B, S) ->
case mfa(From, M) of
{_, _, MFA} when MFA =:= Fun; [] =:= Fun ->
@@ -154,6 +171,15 @@ expr({'try',_Line,Es,Scs,Ccs,As}, S) ->
S2 = clauses(Scs, S1),
S3 = clauses(Ccs, S2),
expr(As, S3);
+expr({'fun', Line, {function,M,F,A}}, S)
+ when is_atom(M), is_atom(F), is_integer(A) ->
+ %% This is the old format for external funs, generated by a pre-R15
+ %% compiler. Exposed in OTP 20 because sys_pre_expand is no longer
+ %% run.
+ Fun = {'fun', Line, {function, {atom,Line,M},
+ {atom,Line,F},
+ {integer,Line,A}}},
+ expr(Fun, S);
expr({'fun', Line, {function, {atom,_,Mod},
{atom,_,Name},
{integer,_,Arity}}}, S) ->
@@ -168,14 +194,21 @@ expr({'fun', Line, {function, Mod, Name, _Arity}}, S) ->
%% New format in R15. M:F/A (one or more variables).
As = {var, Line, '_'},
external_call(erlang, apply, [Mod, Name, As], Line, true, S);
+%% Only abstract_v1 and abstract_v2.
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({named_fun, _Line, '_', Cs, _Extra}, S) ->
+%% End abstract_v1 and abstract_v2.
+expr({'fun', Line, {function, Name, Arity}}, S) ->
+ %% Added in OTP 20.
+ handle_call(local, S#xrefr.module, Name, Arity, Line, S);
+expr({'fun', _Line, {clauses, Cs}}, S) ->
+ clauses(Cs, S);
+expr({named_fun, _Line, '_', Cs}, S) ->
clauses(Cs, S);
-expr({named_fun, _Line, Name, Cs, _Extra}, S) ->
+expr({named_fun, _Line, Name, Cs}, S) ->
S1 = S#xrefr{funvars = [Name | S#xrefr.funvars]},
clauses(Cs, S1);
expr({call, Line, {atom, _, Name}, As}, S) ->
@@ -193,7 +226,12 @@ expr({match, _Line, {var,_,Var}, {'fun', _, {clauses, Cs}, _Extra}}, S) ->
%% 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}, {named_fun, _, _, _, _} = Fun}, S) ->
+expr({match, _Line, {var,_,Var}, {'fun', _, {clauses, Cs}}}, S) ->
+ %% OTP 20. Exposed because sys_pre_expand is no longer run.
+ S1 = S#xrefr{funvars = [Var | S#xrefr.funvars]},
+ clauses(Cs, S1);
+expr({match, _Line, {var,_,Var}, {named_fun, _, _, _} = Fun}, S) ->
+ %% OTP 20. Exposed because sys_pre_expand is no longer run.
S1 = S#xrefr{funvars = [Var | S#xrefr.funvars]},
expr(Fun, S1);
expr({match, _Line, {var,_,Var}, E}, S) ->
@@ -295,10 +333,17 @@ check_funarg(W, ArgsList, Line, S) ->
expr(ArgsList, S1).
funarg({'fun', _, _Clauses, _Extra}, _S) -> true;
+funarg({'fun', _, {clauses, _}}, _S) ->
+ %% OTP 20. sys_pre_expand not run.
+ true;
+funarg({'fun', _, {function, _, _}}, _S) ->
+ %% OTP 20. sys_pre_expand not run.
+ true;
funarg({'fun', _, {function,_,_,_}}, _S) ->
%% New abstract format for fun M:F/A in R15.
true;
-funarg({named_fun, _, _, _, _}, _S) ->
+funarg({named_fun, _, _, _}, _S) ->
+ %% OTP 20. sys_pre_expand not run.
true;
funarg({var, _, Var}, S) -> member(Var, S#xrefr.funvars);
funarg(_, _S) -> false.
diff --git a/lib/tools/src/xref_utils.erl b/lib/tools/src/xref_utils.erl
index 438ec93962..02e207d40c 100644
--- a/lib/tools/src/xref_utils.erl
+++ b/lib/tools/src/xref_utils.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2000-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2000-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -47,8 +47,6 @@
-export([options/2]).
--export([subprocess/2]).
-
-export([format_error/1]).
-import(lists, [append/1, delete/2, filter/2, foldl/3, foreach/2,
@@ -245,6 +243,8 @@ select_last_application_version(AppVs) ->
TL = to_external(partition(1, relation(AppVs))),
[last(keysort(2, L)) || L <- TL].
+-record(scan, {collected = [], errors = [], seen = [], unreadable = []}).
+
%% scan_directory(Directory, Recurse, Collect, Watch) ->
%% {Collected, Errors, Seen, Unreadable}
%%
@@ -261,8 +261,9 @@ select_last_application_version(AppVs) ->
%% Unreadable.
%%
scan_directory(File, Recurse, Collect, Watch) ->
- Init = [[] | {[],[],[]}],
- [L | {E,J,U}] = find_files_dir(File, Recurse, Collect, Watch, Init),
+ Init = #scan{},
+ S = find_files_dir(File, Recurse, Collect, Watch, Init),
+ #scan{collected = L, errors = E, seen = J, unreadable = U} = S,
{reverse(L), reverse(E), reverse(J), reverse(U)}.
%% {Dir, Basename} | false
@@ -509,12 +510,6 @@ find_beam(Culprit) ->
options(Options, Valid) ->
split_options(Options, [], [], [], Valid).
-subprocess(Fun, Opts) ->
- Pid = spawn_opt(Fun, Opts),
- receive
- {Pid, Reply} -> Reply
- end.
-
format_error({error, Module, Error}) ->
Module:format_error(Error);
format_error({file_error, FileName, Reason}) ->
@@ -576,8 +571,7 @@ find_files_dir(Dir, Recurse, Collect, Watch, L) ->
{ok, Files} ->
find_files(sort(Files), Dir, Recurse, Collect, Watch, L);
{error, Error} ->
- [B | {E,J,U}] = L,
- [B | {[file_error(Dir, Error)|E],J,U}]
+ L#scan{errors = [file_error(Dir, Error)|L#scan.errors]}
end.
find_files([F | Fs], Dir, Recurse, Collect, Watch, L) ->
@@ -588,22 +582,23 @@ find_files([F | Fs], Dir, Recurse, Collect, Watch, L) ->
{ok, {_, directory, _, _}} ->
L;
Info ->
- [B | EJU = {E,J,U}] = L,
+ #scan{collected = B, errors = E,
+ seen = J, unreadable = U} = L,
Ext = filename:extension(File),
C = member(Ext, Collect),
case C of
true ->
case Info of
{ok, {_, file, readable, _}} ->
- [[{Dir,F} | B] | EJU];
+ L#scan{collected = [{Dir,F} | B]};
{ok, {_, file, unreadable, _}} ->
- [B | {E,J,[File|U]}];
+ L#scan{unreadable = [File|U]};
Error ->
- [B | {[Error|E],J,U}]
+ L#scan{errors = [Error|E]}
end;
false ->
case member(Ext, Watch) of
- true -> [B | {E,[File|J],U}];
+ true -> L#scan{seen = [File|J]};
false -> L
end
end
@@ -643,14 +638,14 @@ neighbours([], G, Fun, VT, L, _V, Vs) ->
neighbours(Vs, G, Fun, VT, L).
match_list(L, RExpr) ->
- {ok, Expr} = re:compile(RExpr),
+ {ok, Expr} = re:compile(RExpr, [unicode]),
filter(fun(E) -> match(E, Expr) end, L).
match_one(VarL, Con, Col) ->
select_each(VarL, fun(E) -> Con =:= element(Col, E) end).
match_many(VarL, RExpr, Col) ->
- {ok, Expr} = re:compile(RExpr),
+ {ok, Expr} = re:compile(RExpr, [unicode]),
select_each(VarL, fun(E) -> match(element(Col, E), Expr) end).
match(I, Expr) when is_integer(I) ->
@@ -658,7 +653,12 @@ match(I, Expr) when is_integer(I) ->
{match, [{0,length(S)}]} =:= re:run(S, Expr, [{capture, first}]);
match(A, Expr) when is_atom(A) ->
S = atom_to_list(A),
- {match, [{0,length(S)}]} =:= re:run(S, Expr, [{capture, first}]).
+ case re:run(S, Expr, [{capture, first}]) of
+ {match, [{0,Size}]} ->
+ Size =:= byte_size(unicode:characters_to_binary(S));
+ _ ->
+ false
+ end.
select_each([{Mod,Funs} | L], Pred) ->
case filter(Pred, Funs) of