您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

288 行
10 KiB

  1. %% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*-
  2. %% ex: ts=4 sw=4 et
  3. -module(rebar_prv_xref).
  4. -behaviour(provider).
  5. -export([init/1,
  6. do/1,
  7. format_error/1]).
  8. -include("rebar.hrl").
  9. -include_lib("providers/include/providers.hrl").
  10. -define(PROVIDER, xref).
  11. -define(DEPS, [compile]).
  12. -define(SUPPORTED_XREFS, [undefined_function_calls, undefined_functions,
  13. locals_not_used, exports_not_used,
  14. deprecated_function_calls, deprecated_functions]).
  15. %% ===================================================================
  16. %% Public API
  17. %% ===================================================================
  18. -spec init(rebar_state:t()) -> {ok, rebar_state:t()}.
  19. init(State) ->
  20. Provider = providers:create([{name, ?PROVIDER},
  21. {module, ?MODULE},
  22. {deps, ?DEPS},
  23. {bare, true},
  24. {example, "rebar3 xref"},
  25. {short_desc, short_desc()},
  26. {desc, desc()}]),
  27. State1 = rebar_state:add_provider(State, Provider),
  28. {ok, State1}.
  29. -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}.
  30. do(State) ->
  31. code:add_pathsa(rebar_state:code_paths(State, all_deps)),
  32. XrefChecks = prepare(State),
  33. XrefIgnores = rebar_state:get(State, xref_ignores, []),
  34. %% Run xref checks
  35. ?INFO("Running cross reference analysis...", []),
  36. XrefResults = xref_checks(XrefChecks, XrefIgnores),
  37. %% Run custom queries
  38. QueryChecks = rebar_state:get(State, xref_queries, []),
  39. QueryResults = lists:foldl(fun check_query/2, [], QueryChecks),
  40. stopped = xref:stop(xref),
  41. rebar_utils:cleanup_code_path(rebar_state:code_paths(State, default)),
  42. case XrefResults =:= [] andalso QueryResults =:= [] of
  43. true ->
  44. {ok, State};
  45. false ->
  46. ?PRV_ERROR({xref_issues, XrefResults, QueryResults})
  47. end.
  48. -spec format_error(any()) -> iolist().
  49. format_error({xref_issues, XrefResults, QueryResults}) ->
  50. lists:flatten(display_results(XrefResults, QueryResults));
  51. format_error(Reason) ->
  52. io_lib:format("~p", [Reason]).
  53. %% ===================================================================
  54. %% Internal functions
  55. %% ===================================================================
  56. short_desc() ->
  57. "Run cross reference analysis.".
  58. desc() ->
  59. io_lib:format(
  60. "~s~n"
  61. "~n"
  62. "Valid rebar.config options:~n"
  63. " ~p~n"
  64. " ~p~n"
  65. " ~p~n"
  66. " ~p~n",
  67. [short_desc(),
  68. {xref_warnings, false},
  69. {xref_extra_paths,[]},
  70. {xref_checks, [undefined_function_calls, undefined_functions,
  71. locals_not_used, exports_not_used,
  72. deprecated_function_calls, deprecated_functions]},
  73. {xref_queries,
  74. [{"(xc - uc) || (xu - x - b"
  75. " - (\"mod\":\".*foo\"/\"4\"))",[]}]}
  76. ]).
  77. -spec prepare(rebar_state:t()) -> [atom()].
  78. prepare(State) ->
  79. {ok, _} = xref:start(xref),
  80. ok = xref:set_library_path(xref, code_path(State)),
  81. xref:set_default(xref, [{warnings,
  82. rebar_state:get(State, xref_warnings, false)},
  83. {verbose, rebar_log:is_verbose(State)}]),
  84. [{ok, _} = xref:add_directory(xref, rebar_app_info:ebin_dir(App))
  85. || App <- rebar_state:project_apps(State)],
  86. %% Get list of xref checks we want to run
  87. ConfXrefChecks = rebar_state:get(State, xref_checks,
  88. [exports_not_used,
  89. undefined_function_calls]),
  90. XrefChecks = sets:to_list(sets:intersection(
  91. sets:from_list(?SUPPORTED_XREFS),
  92. sets:from_list(ConfXrefChecks))),
  93. XrefChecks.
  94. xref_checks(XrefChecks, XrefIgnores) ->
  95. run_xref_checks(XrefChecks, XrefIgnores, []).
  96. run_xref_checks([], _XrefIgnores, Acc) ->
  97. Acc;
  98. run_xref_checks([XrefCheck | T], XrefIgnores, Acc) ->
  99. {ok, Results} = xref:analyze(xref, XrefCheck),
  100. case filter_xref_results(XrefCheck, XrefIgnores, Results) of
  101. [] ->
  102. run_xref_checks(T, XrefIgnores, Acc);
  103. FilterResult ->
  104. run_xref_checks(T, XrefIgnores, [{XrefCheck, FilterResult} | Acc])
  105. end.
  106. check_query({Query, Value}, Acc) ->
  107. {ok, Answer} = xref:q(xref, Query),
  108. case Answer =:= Value of
  109. false ->
  110. [{Query, Value, Answer} | Acc];
  111. _ ->
  112. Acc
  113. end.
  114. code_path(State) ->
  115. [P || P <- code:get_path() ++
  116. rebar_state:get(State, xref_extra_paths, []),
  117. filelib:is_dir(P)].
  118. %% Ignore behaviour functions, and explicitly marked functions
  119. %%
  120. %% Functions can be ignored by using
  121. %% -ignore_xref([{F, A}, {M, F, A}...]).
  122. get_xref_ignorelist(Mod, XrefCheck) ->
  123. %% Get ignore_xref attribute and combine them in one list
  124. Attributes =
  125. try
  126. Mod:module_info(attributes)
  127. catch
  128. _Class:_Error -> []
  129. end,
  130. IgnoreXref = keyall(ignore_xref, Attributes),
  131. BehaviourCallbacks = get_behaviour_callbacks(XrefCheck, Attributes),
  132. %% And create a flat {M,F,A} list
  133. lists:foldl(
  134. fun({F, A}, Acc) -> [{Mod,F,A} | Acc];
  135. ({M, F, A}, Acc) -> [{M,F,A} | Acc]
  136. end, [], lists:flatten([IgnoreXref, BehaviourCallbacks])).
  137. keyall(Key, List) ->
  138. lists:flatmap(fun({K, L}) when Key =:= K -> L; (_) -> [] end, List).
  139. get_behaviour_callbacks(exports_not_used, Attributes) ->
  140. [B:behaviour_info(callbacks) || B <- keyall(behaviour, Attributes) ++
  141. keyall(behavior, Attributes)];
  142. get_behaviour_callbacks(_XrefCheck, _Attributes) ->
  143. [].
  144. parse_xref_result({_, MFAt}) -> MFAt;
  145. parse_xref_result(MFAt) -> MFAt.
  146. filter_xref_results(XrefCheck, XrefIgnores, XrefResults) ->
  147. SearchModules = lists:usort(
  148. lists:map(
  149. fun({Mt,_Ft,_At}) -> Mt;
  150. ({{Ms,_Fs,_As},{_Mt,_Ft,_At}}) -> Ms;
  151. (_) -> undefined
  152. end, XrefResults)),
  153. Ignores = XrefIgnores ++ lists:flatmap(fun(Module) ->
  154. get_xref_ignorelist(Module, XrefCheck)
  155. end, SearchModules),
  156. [Result || Result <- XrefResults,
  157. not lists:member(parse_xref_result(Result), Ignores)].
  158. display_results(XrefResults, QueryResults) ->
  159. [lists:map(fun display_xref_results_for_type/1, XrefResults),
  160. lists:map(fun display_query_result/1, QueryResults)].
  161. display_query_result({Query, Answer, Value}) ->
  162. io_lib:format("Query ~s~n answer ~p~n did not match ~p~n",
  163. [Query, Answer, Value]).
  164. display_xref_results_for_type({Type, XrefResults}) ->
  165. lists:map(display_xref_result_fun(Type), XrefResults).
  166. display_xref_result_fun(Type) ->
  167. fun(XrefResult) ->
  168. {Source, SMFA, TMFA} =
  169. case XrefResult of
  170. {MFASource, MFATarget} ->
  171. {format_mfa_source(MFASource),
  172. format_mfa(MFASource),
  173. format_mfa(MFATarget)};
  174. MFATarget ->
  175. {format_mfa_source(MFATarget),
  176. format_mfa(MFATarget),
  177. undefined}
  178. end,
  179. case Type of
  180. undefined_function_calls ->
  181. io_lib:format("~sWarning: ~s calls undefined function ~s (Xref)\n",
  182. [Source, SMFA, TMFA]);
  183. undefined_functions ->
  184. io_lib:format("~sWarning: ~s is undefined function (Xref)\n",
  185. [Source, SMFA]);
  186. locals_not_used ->
  187. io_lib:format("~sWarning: ~s is unused local function (Xref)\n",
  188. [Source, SMFA]);
  189. exports_not_used ->
  190. io_lib:format("~sWarning: ~s is unused export (Xref)\n",
  191. [Source, SMFA]);
  192. deprecated_function_calls ->
  193. io_lib:format("~sWarning: ~s calls deprecated function ~s (Xref)\n",
  194. [Source, SMFA, TMFA]);
  195. deprecated_functions ->
  196. io_lib:format("~sWarning: ~s is deprecated function (Xref)\n",
  197. [Source, SMFA]);
  198. Other ->
  199. io_lib:format("~sWarning: ~s - ~s xref check: ~s (Xref)\n",
  200. [Source, SMFA, TMFA, Other])
  201. end
  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. try
  216. element(N, Tuple)
  217. catch
  218. error:badarg ->
  219. undefined
  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, _, 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.