diff options
Diffstat (limited to 'lib/tools/src')
-rw-r--r-- | lib/tools/src/cover.erl | 62 | ||||
-rw-r--r-- | lib/tools/src/eprof.erl | 30 | ||||
-rw-r--r-- | lib/tools/src/fprof.erl | 38 | ||||
-rw-r--r-- | lib/tools/src/lcnt.erl | 144 | ||||
-rw-r--r-- | lib/tools/src/make.erl | 131 | ||||
-rw-r--r-- | lib/tools/src/tools.app.src | 7 | ||||
-rw-r--r-- | lib/tools/src/xref_base.erl | 50 | ||||
-rw-r--r-- | lib/tools/src/xref_parser.yrl | 6 | ||||
-rw-r--r-- | lib/tools/src/xref_reader.erl | 73 | ||||
-rw-r--r-- | lib/tools/src/xref_utils.erl | 13 |
10 files changed, 374 insertions, 180 deletions
diff --git a/lib/tools/src/cover.erl b/lib/tools/src/cover.erl index 92c10cc306..4e64d7aa4e 100644 --- a/lib/tools/src/cover.erl +++ b/lib/tools/src/cover.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2001-2016. 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. @@ -2053,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, @@ -2414,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" @@ -2423,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"], - ok = file:write(OutFd,Header); + H1Bin = unicode:characters_to_binary(Header,Enc,Enc), + ok = file:write(OutFd,H1Bin); true -> ok end, @@ -2443,16 +2444,21 @@ 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)]), - ok = 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,[{is_integer,'$1'},{'>','$1',0}],[{{'$1','$2'}}]}], - CovLines = lists:keysort(1,ets:select(?COLLECTION_TABLE, MS)), + CovLines0 = + lists:keysort(1, ets:select(?COLLECTION_TABLE, MS)), + CovLines = merge_dup_lines(CovLines0), print_lines(Module, CovLines, InFd, OutFd, 1, HTML), if HTML -> @@ -2473,19 +2479,23 @@ 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. - ok = 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", @@ -2504,7 +2514,7 @@ print_lines(Module, CovLines, InFd, OutFd, L, HTML) -> ok = file:write(OutFd, [Str,fill3(),Line]) end, print_lines(Module, CovLines1, InFd, OutFd, L+1, HTML); - _ -> + _ -> %Including comment lines ok = file:write(OutFd, [tab(),Line]), print_lines(Module, CovLines, InFd, OutFd, L+1, HTML) end @@ -2752,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/eprof.erl b/lib/tools/src/eprof.erl index 3ae899a078..535ddbcd04 100644 --- a/lib/tools/src/eprof.erl +++ b/lib/tools/src/eprof.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1996-2016. 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. @@ -246,7 +246,7 @@ 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; @@ -478,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) -> @@ -491,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), @@ -503,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; diff --git a/lib/tools/src/fprof.erl b/lib/tools/src/fprof.erl index d1a4624419..fb657c2928 100644 --- a/lib/tools/src/fprof.erl +++ b/lib/tools/src/fprof.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2001-2016. 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. @@ -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 @@ -1475,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, _, _) -> @@ -1498,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. @@ -2603,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]). @@ -2636,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, @@ -2710,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/lcnt.erl b/lib/tools/src/lcnt.erl index 23d66b084e..d0152a4915 100644 --- a/lib/tools/src/lcnt.erl +++ b/lib/tools/src/lcnt.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2016. 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. @@ -34,8 +34,11 @@ -export([start/0, stop/0]). -%% erts_debug:lock_counters api --export([rt_collect/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, @@ -134,27 +137,61 @@ start_internal() -> %% -------------------------------------------------------------------- %% %% -%% 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]). + +%% 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({Type, Opt}) -> - erts_debug:lock_counters({Type, Opt}). +rt_opt({Type, NewVal}) -> + PreviousVal = erts_debug:lcnt_control(Type), + erts_debug:lcnt_control(Type, NewVal), + PreviousVal. -rt_opt(Node, {Type, Opt}) -> - rpc:call(Node, erts_debug, lock_counters, [{Type, Opt}]). +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). %% -------------------------------------------------------------------- %% %% @@ -181,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) -> ok = start_internal(), 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). %% -------------------------------------------------------------------- %% %% @@ -192,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) -> - ok = start_internal(), - 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) -> - ok = start_internal(), 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). @@ -698,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{})). @@ -880,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. @@ -936,12 +1000,24 @@ 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 37e67cbe34..6554d338af 100644 --- a/lib/tools/src/make.erl +++ b/lib/tools/src/make.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1996-2016. 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. @@ -29,7 +29,7 @@ -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 @@ -43,29 +43,30 @@ 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 @@ -89,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 [] -> @@ -143,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) -> @@ -263,15 +267,47 @@ include_opt([]) -> recompile(File, true, _Load, _Opts) -> io:format("Out of date: ~ts\n",[File]); -recompile(File, false, noload, Opts) -> +recompile(File, false, Load, Opts) -> io:format("Recompile: ~ts\n",[File]), - compile:file(File, [report_errors, report_warnings, error_summary |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 @@ -286,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); diff --git a/lib/tools/src/tools.app.src b/lib/tools/src/tools.app.src index 4c7dd24006..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-2016. 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. @@ -40,8 +40,7 @@ {env, [{file_util_search_methods,[{"", ""}, {"ebin", "esrc"}, {"ebin", "src"}]} ] }, - {runtime_dependencies, ["stdlib-3.1","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/xref_base.erl b/lib/tools/src/xref_base.erl index f298a1ce81..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-2016. 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) -> @@ -809,7 +811,8 @@ abst(File, Builtins, _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); @@ -917,7 +920,7 @@ do_add_module(S, XMod, Unres, Data) -> {ok, Ms, Bad, NS}. prepare_module(_Mode = functions, XMod, Unres0, Data) -> - {DefAt0, LPreCAt0, XPreCAt0, LC0, XC0, X0, Attrs, Depr} = 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)], @@ -934,6 +937,7 @@ prepare_module(_Mode = functions, XMod, Unres0, Data) -> 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} = @@ -954,7 +958,7 @@ prepare_module(_Mode = functions, XMod, Unres0, Data) -> {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, - DF1,DF_11,DF_21,DF_31], NoCalls, Unres}, + OL1,DF1,DF_11,DF_21,DF_31], NoCalls, Unres}, DBad++Bad}; prepare_module(_Mode = modules, XMod, _Unres, Data) -> {X0, I0, Depr} = Data, @@ -966,7 +970,7 @@ prepare_module(_Mode = modules, XMod, _Unres, Data) -> 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(List), + OL2,DF2,DF_12,DF_22,DF_32] = pack(List), LU = range(LC2), @@ -975,7 +979,7 @@ finish_module({functions, XMod, List, NoCalls, Unres}, S) -> 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, @@ -1219,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 @@ -1232,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)), @@ -1313,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}, @@ -1404,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}; @@ -1832,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 -> diff --git a/lib/tools/src/xref_parser.yrl b/lib/tools/src/xref_parser.yrl index 0711da79e2..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-2016. 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 b0c168e018..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-2016. 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. @@ -638,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) -> @@ -653,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 |