25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

287 lines
10 KiB

14 년 전
14 년 전
14 년 전
  1. %% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*-
  2. %% ex: ts=4 sw=4 et
  3. %% -------------------------------------------------------------------
  4. %%
  5. %% rebar: Erlang Build Tools
  6. %%
  7. %% Copyright (c) 2009 Dave Smith (dizzyd@dizzyd.com)
  8. %%
  9. %% Permission is hereby granted, free of charge, to any person obtaining a copy
  10. %% of this software and associated documentation files (the "Software"), to deal
  11. %% in the Software without restriction, including without limitation the rights
  12. %% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  13. %% copies of the Software, and to permit persons to whom the Software is
  14. %% furnished to do so, subject to the following conditions:
  15. %%
  16. %% The above copyright notice and this permission notice shall be included in
  17. %% all copies or substantial portions of the Software.
  18. %%
  19. %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  20. %% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  21. %% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  22. %% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  23. %% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  24. %% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  25. %% THE SOFTWARE.
  26. %%
  27. %% -------------------------------------------------------------------
  28. %% -------------------------------------------------------------------
  29. %% This module borrows heavily from http://github.com/etnt/exrefcheck project as
  30. %% written by Torbjorn Tornkvist <tobbe@kreditor.se>, Daniel Luna
  31. %% <daniel@lunas.se> and others.
  32. %% -------------------------------------------------------------------
  33. -module(rebar_xref).
  34. -include("rebar.hrl").
  35. -export([xref/2]).
  36. %% for internal use only
  37. -export([info/2]).
  38. %% ===================================================================
  39. %% Public API
  40. %% ===================================================================
  41. xref(Config, _) ->
  42. %% Spin up xref
  43. {ok, _} = xref:start(xref),
  44. ok = xref:set_library_path(xref, code_path(Config)),
  45. xref:set_default(xref, [{warnings,
  46. rebar_config:get(Config, xref_warnings, false)},
  47. {verbose, rebar_log:is_verbose(Config)}]),
  48. {ok, _} = xref:add_directory(xref, "ebin"),
  49. %% Save the code path prior to doing anything
  50. OrigPath = code:get_path(),
  51. true = code:add_path(rebar_utils:ebin_dir()),
  52. %% Get list of xref checks we want to run
  53. ConfXrefChecks = rebar_config:get(Config, xref_checks,
  54. [exports_not_used,
  55. undefined_function_calls]),
  56. SupportedXrefs = [undefined_function_calls, undefined_functions,
  57. locals_not_used, exports_not_used,
  58. deprecated_function_calls, deprecated_functions],
  59. XrefChecks = sets:to_list(sets:intersection(
  60. sets:from_list(SupportedXrefs),
  61. sets:from_list(ConfXrefChecks))),
  62. %% Run xref checks
  63. XrefNoWarn = xref_checks(XrefChecks),
  64. %% Run custom queries
  65. QueryChecks = rebar_config:get(Config, xref_queries, []),
  66. QueryNoWarn = lists:all(fun check_query/1, QueryChecks),
  67. %% Restore the original code path
  68. true = code:set_path(OrigPath),
  69. %% Stop xref
  70. stopped = xref:stop(xref),
  71. case lists:member(false, [XrefNoWarn, QueryNoWarn]) of
  72. true ->
  73. ?FAIL;
  74. false ->
  75. ok
  76. end.
  77. %% ===================================================================
  78. %% Internal functions
  79. %% ===================================================================
  80. info(help, xref) ->
  81. ?CONSOLE(
  82. "Run cross reference analysis.~n"
  83. "~n"
  84. "Valid rebar.config options:~n"
  85. " ~p~n"
  86. " ~p~n"
  87. " ~p~n",
  88. [
  89. {xref_warnings, false},
  90. {xref_checks, [undefined_function_calls, undefined_functions,
  91. locals_not_used, exports_not_used,
  92. deprecated_function_calls, deprecated_functions]},
  93. {xref_queries,
  94. [{"(xc - uc) || (xu - x - b"
  95. " - (\"mod\":\".*foo\"/\"4\"))",[]}]}
  96. ]).
  97. xref_checks(XrefChecks) ->
  98. XrefWarnCount = lists:foldl(fun run_xref_check/2, 0, XrefChecks),
  99. XrefWarnCount =:= 0.
  100. run_xref_check(XrefCheck, Acc) ->
  101. {ok, Results} = xref:analyze(xref, XrefCheck),
  102. FilteredResults =filter_xref_results(XrefCheck, Results),
  103. lists:foreach(fun(Res) ->
  104. display_xref_result(XrefCheck, Res)
  105. end,
  106. FilteredResults),
  107. Acc + length(FilteredResults).
  108. check_query({Query, Value}) ->
  109. {ok, Answer} = xref:q(xref, Query),
  110. case Answer =:= Value of
  111. false ->
  112. ?CONSOLE("Query ~s~n answer ~p~n did not match ~p~n",
  113. [Query, Answer, Value]),
  114. false;
  115. _ ->
  116. true
  117. end.
  118. code_path(Config) ->
  119. %% Slight hack to ensure that sub_dirs get properly included
  120. %% in code path for xref -- otherwise one gets a lot of undefined
  121. %% functions, even though those functions are present as part
  122. %% of compilation. H/t to @dluna. Long term we should tie more
  123. %% properly into the overall compile code path if possible.
  124. BaseDir = rebar_config:get_xconf(Config, base_dir),
  125. [P || P <- code:get_path() ++
  126. [filename:join(BaseDir, filename:join(SubDir, "ebin"))
  127. || SubDir <- rebar_config:get(Config, sub_dirs, [])],
  128. filelib:is_dir(P)].
  129. %%
  130. %% Ignore behaviour functions, and explicitly marked functions
  131. %%
  132. %% Functions can be ignored by using
  133. %% -ignore_xref([{F, A}, {M, F, A}...]).
  134. get_xref_ignorelist(Mod, XrefCheck) ->
  135. %% Get ignore_xref attribute and combine them in one list
  136. Attributes =
  137. try
  138. Mod:module_info(attributes)
  139. catch
  140. _Class:_Error -> []
  141. end,
  142. IgnoreXref = keyall(ignore_xref, Attributes),
  143. BehaviourCallbacks = get_behaviour_callbacks(XrefCheck, Attributes),
  144. %% And create a flat {M,F,A} list
  145. lists:foldl(
  146. fun({F, A}, Acc) -> [{Mod,F,A} | Acc];
  147. ({M, F, A}, Acc) -> [{M,F,A} | Acc]
  148. end, [], lists:flatten([IgnoreXref, BehaviourCallbacks])).
  149. keyall(Key, List) ->
  150. lists:flatmap(fun({K, L}) when Key =:= K -> L; (_) -> [] end, List).
  151. get_behaviour_callbacks(exports_not_used, Attributes) ->
  152. [B:behaviour_info(callbacks) || B <- keyall(behaviour, Attributes)];
  153. get_behaviour_callbacks(_XrefCheck, _Attributes) ->
  154. [].
  155. parse_xref_result({_, MFAt}) -> MFAt;
  156. parse_xref_result(MFAt) -> MFAt.
  157. filter_xref_results(XrefCheck, XrefResults) ->
  158. SearchModules = lists:usort(
  159. lists:map(
  160. fun({Mt,_Ft,_At}) -> Mt;
  161. ({{Ms,_Fs,_As},{_Mt,_Ft,_At}}) -> Ms;
  162. (_) -> undefined
  163. end, XrefResults)),
  164. Ignores = lists:flatmap(fun(Module) ->
  165. get_xref_ignorelist(Module, XrefCheck)
  166. end, SearchModules),
  167. [Result || Result <- XrefResults,
  168. not lists:member(parse_xref_result(Result), Ignores)].
  169. display_xref_result(Type, XrefResult) ->
  170. { Source, SMFA, TMFA } = case XrefResult of
  171. {MFASource, MFATarget} ->
  172. {format_mfa_source(MFASource),
  173. format_mfa(MFASource),
  174. format_mfa(MFATarget)};
  175. MFATarget ->
  176. {format_mfa_source(MFATarget),
  177. format_mfa(MFATarget),
  178. undefined}
  179. end,
  180. case Type of
  181. undefined_function_calls ->
  182. ?CONSOLE("~sWarning: ~s calls undefined function ~s (Xref)\n",
  183. [Source, SMFA, TMFA]);
  184. undefined_functions ->
  185. ?CONSOLE("~sWarning: ~s is undefined function (Xref)\n",
  186. [Source, SMFA]);
  187. locals_not_used ->
  188. ?CONSOLE("~sWarning: ~s is unused local function (Xref)\n",
  189. [Source, SMFA]);
  190. exports_not_used ->
  191. ?CONSOLE("~sWarning: ~s is unused export (Xref)\n",
  192. [Source, SMFA]);
  193. deprecated_function_calls ->
  194. ?CONSOLE("~sWarning: ~s calls deprecated function ~s (Xref)\n",
  195. [Source, SMFA, TMFA]);
  196. deprecated_functions ->
  197. ?CONSOLE("~sWarning: ~s is deprecated function (Xref)\n",
  198. [Source, SMFA]);
  199. Other ->
  200. ?CONSOLE("~sWarning: ~s - ~s xref check: ~s (Xref)\n",
  201. [Source, SMFA, TMFA, Other])
  202. end.
  203. format_mfa({M, F, A}) ->
  204. ?FMT("~s:~s/~w", [M, F, A]).
  205. format_mfa_source(MFA) ->
  206. case find_mfa_source(MFA) of
  207. {module_not_found, function_not_found} -> "";
  208. {Source, function_not_found} -> ?FMT("~s: ", [Source]);
  209. {Source, Line} -> ?FMT("~s:~w: ", [Source, Line])
  210. end.
  211. %%
  212. %% Extract an element from a tuple, or undefined if N > tuple size
  213. %%
  214. safe_element(N, Tuple) ->
  215. case catch(element(N, Tuple)) of
  216. {'EXIT', {badarg, _}} ->
  217. undefined;
  218. Value ->
  219. Value
  220. end.
  221. %%
  222. %% Given a MFA, find the file and LOC where it's defined. Note that
  223. %% xref doesn't work if there is no abstract_code, so we can avoid
  224. %% being too paranoid here.
  225. %%
  226. find_mfa_source({M, F, A}) ->
  227. case code:get_object_code(M) of
  228. error -> {module_not_found, function_not_found};
  229. {M, Bin, _} -> find_function_source(M,F,A,Bin)
  230. end.
  231. find_function_source(M, F, A, Bin) ->
  232. AbstractCode = beam_lib:chunks(Bin, [abstract_code]),
  233. {ok, {M, [{abstract_code, {raw_abstract_v1, Code}}]}} = AbstractCode,
  234. %% Extract the original source filename from the abstract code
  235. [{attribute, 1, file, {Source, _}} | _] = Code,
  236. %% Extract the line number for a given function def
  237. Fn = [E || E <- Code,
  238. safe_element(1, E) == function,
  239. safe_element(3, E) == F,
  240. safe_element(4, E) == A],
  241. case Fn of
  242. [{function, Line, F, _, _}] -> {Source, Line};
  243. %% do not crash if functions are exported, even though they
  244. %% are not in the source.
  245. %% parameterized modules add new/1 and instance/1 for example.
  246. [] -> {Source, function_not_found}
  247. end.