File 4061-xref-Add-native-support-for-ignore_xref.patch of Package erlang
From 6f398fb896fc26817a0d96d4b3dc186dcaf9d300 Mon Sep 17 00:00:00 2001
From: Richard Carlsson <carlsson.richard@gmail.com>
Date: Sun, 25 Jan 2026 17:15:16 +0100
Subject: [PATCH] xref: Add native support for ignore_xref
Moves the result filtering from rebar3 into xref itself.
---
lib/tools/src/xref.erl | 102 ++++++++++++++++--
lib/tools/src/xref.hrl | 2 +
lib/tools/src/xref_base.erl | 38 +++++--
lib/tools/src/xref_reader.erl | 6 +-
lib/tools/test/xref_SUITE.erl | 5 +-
lib/tools/test/xref_SUITE_data/lib_test/t.erl | 19 +++-
6 files changed, 145 insertions(+), 27 deletions(-)
diff --git a/lib/tools/src/xref.erl b/lib/tools/src/xref.erl
index 21de672a40..e67d9a3c6d 100644
--- a/lib/tools/src/xref.erl
+++ b/lib/tools/src/xref.erl
@@ -103,6 +103,24 @@ module. All unknown functions are also undefined functions; there is a
[figure](xref_chapter.md#venn2) in the User's Guide that illustrates
this relationship.
+The module attribute `ignore_xref` can be used to suppress warnings for
+certain modules or functions in your modules. The following forms are
+understood by Xref:
+
+```erlang
+-ignore_xref(module).
+-ignore_xref([module1, module2, module3]).
+
+-ignore_xref(function/0).
+-ignore_xref([function1/0, function2/1, function3/2]).
+-ignore_xref({function,0}).
+-ignore_xref([{function1,0}, {function2,1}, {function3,2}]).
+
+%% ignoring calls/references to other modules from the current
+-ignore_xref({other_mod1, func1, 0}).
+-ignore_xref([{other_mod1, func1, 0}, {other_mod2, func2, 1}]).
+```
+
The module attribute tag `deprecated` can be used to inform
Xref about _deprecated functions_{: #deprecated_function } and optionally when
functions are planned to be removed. A few examples show the idea:
@@ -595,6 +613,8 @@ called, all user variables are forgotten.
-import(sofs, [to_external/1, is_sofs_set/1]).
+-include("xref.hrl").
+
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
@@ -1965,16 +1985,16 @@ handle_call({variables, Options}, _From, State) ->
{reply, Reply, NewState};
handle_call({analyze, What}, _From, State) ->
{Reply, NewState} = xref_base:analyze(State, What),
- {reply, unsetify(Reply), NewState};
+ {reply, finalize(Reply, NewState), NewState};
handle_call({analyze, What, Options}, _From, State) ->
{Reply, NewState} = xref_base:analyze(State, What, Options),
- {reply, unsetify(Reply), NewState};
+ {reply, finalize(Reply, NewState), NewState};
handle_call({qry, Q}, _From, State) ->
{Reply, NewState} = xref_base:q(State, Q),
- {reply, unsetify(Reply), NewState};
+ {reply, finalize(Reply, NewState), NewState};
handle_call({qry, Q, Options}, _From, State) ->
{Reply, NewState} = xref_base:q(State, Q, Options),
- {reply, unsetify(Reply), NewState};
+ {reply, finalize(Reply, NewState), NewState};
handle_call(get_default, _From, State) ->
Reply = xref_base:get_default(State),
{reply, Reply, State};
@@ -2075,10 +2095,72 @@ do_analysis(State, Analysis) ->
throw(Error)
end.
-unsetify(Reply={ok, X}) ->
+finalize({ok, X}, #xref{ignores = Ignores}) ->
+ {ok, filter_xref_results(Ignores, unsetify(X))};
+finalize(Error, _State) ->
+ Error.
+
+unsetify(X) ->
case is_sofs_set(X) of
- true -> {ok, to_external(X)};
- false -> Reply
- end;
-unsetify(Reply) ->
- Reply.
+ true -> to_external(X);
+ false -> X
+ end.
+
+%% Filters out behaviour functions and explicitly marked functions
+%% For example: `-ignore_xref([{F, A}, {M, F, A}, M, ...]).`
+filter_xref_results(_Ignores, Results) when not is_list(Results) ->
+ Results;
+filter_xref_results(Ignores, Results) ->
+ SearchModules = lists:usort(
+ lists:map(
+ fun({Mt,_Ft,_At}) -> Mt;
+ ({{Ms,_Fs,_As},{_Mt,_Ft,_At}}) -> Ms;
+ (_) -> undefined
+ end, Results)),
+
+ Ignores1 = Ignores ++ lists:flatmap(fun(Module) ->
+ get_xref_ignorelist(Module)
+ end, SearchModules),
+
+ lists:filter( fun(Result) -> pred_xref_result(Result, Ignores1) end, Results).
+
+pred_xref_result({Src, Dest}, Ignores) ->
+ pred_xref_result1(Src, Ignores)
+ andalso pred_xref_result1(Dest, Ignores);
+pred_xref_result(Vertex, Ignores) ->
+ pred_xref_result1(Vertex, Ignores).
+
+pred_xref_result1(Vertex, Ignores) ->
+ Mod = case Vertex of
+ {Module, _Func, _Arity} -> Module;
+ _ -> Vertex
+ end,
+ not lists:member(Vertex, Ignores) andalso not lists:member(Mod, Ignores).
+
+get_xref_ignorelist(Mod) ->
+ %% Get ignore_xref attribute and combine them in one list
+ Attributes = get_module_attrs(Mod),
+ IgnoreXref = keyall(ignore_xref, Attributes),
+ lists:foldl(
+ fun({F, A}, Acc) -> [{Mod,F,A} | Acc];
+ ({M, F, A}, Acc) -> [{M,F,A} | Acc];
+ (M, Acc) when is_atom(M) -> [M | Acc]
+ end, [], lists:flatten([IgnoreXref])).
+
+keyall(Key, List) ->
+ lists:flatmap(fun({K, L}) when Key =:= K -> L; (_) -> [] end, List).
+
+get_module_attrs(Mod) ->
+ case erlang:module_loaded(Mod) of
+ true ->
+ erlang:get_module_info(Mod, attributes);
+ false ->
+ case code:which(Mod) of
+ non_existing -> [];
+ Path ->
+ case beam_lib:chunks(Path, [attributes]) of
+ {ok, {Mod, [{attributes,As}]}} -> As;
+ _ -> []
+ end
+ end
+ end.
diff --git a/lib/tools/src/xref.hrl b/lib/tools/src/xref.hrl
index f8c223237e..ddd7cb0b8f 100644
--- a/lib/tools/src/xref.hrl
+++ b/lib/tools/src/xref.hrl
@@ -40,6 +40,7 @@
modules = dict:new(), % dict-of(xref_mod())
applications = dict:new(), % dict-of(xref_app())
releases = dict:new(), % dict-of(xref_rel())
+ ignores = [],
library_path = [], % [string()] | code_path
libraries = dict:new(), % dict-of(xref_lib())
@@ -58,6 +59,7 @@
builtins, % whether calls to built-in functions are included
info, % number of exports, locals etc.
no_unresolved = 0, % number of unresolved calls
+ attributes = [], % module attributes
data
%% Data has been read from the BEAM file, and is represented here
%% as a list of sets.
diff --git a/lib/tools/src/xref_base.erl b/lib/tools/src/xref_base.erl
index 044349446b..99859aa90a 100644
--- a/lib/tools/src/xref_base.erl
+++ b/lib/tools/src/xref_base.erl
@@ -743,13 +743,13 @@ read_module(SplitName, AppName, Builtins, Verbose, Warnings, State) ->
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 ->
+ {ok, _M, _Attrs, no_abstract_code} when Verbose ->
message(Verbose, no_debug_info, [File]),
no;
- {ok, _M, no_abstract_code} when not Verbose ->
+ {ok, _M, _Attrs, no_abstract_code} when not Verbose ->
message(Warnings, no_debug_info, [File]),
no;
- {ok, M, Data, UnresCalls0} ->
+ {ok, M, Attrs, Data, UnresCalls0} ->
message(Verbose, done_file, [File]),
%% Remove duplicates. Identical unresolved calls on the
%% same line are counted as _one_ unresolved call.
@@ -764,7 +764,7 @@ read_a_module({Dir, BaseName}, AppName, Builtins, Verbose, Warnings, Mode) ->
{ok, {_, _, _, Time}} ->
XMod = #xref_mod{name = M, app_name = AppName,
dir = Dir, mtime = Time,
- builtins = Builtins,
+ builtins = Builtins, attributes = Attrs,
no_unresolved = NoUnresCalls},
{ok, PrepMod, Bad} =
prepare_module(Mode, XMod, UnresCalls, Data),
@@ -796,7 +796,7 @@ process_module(State) ->
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};
+ {ok, M, [], NoA};
{ok, {M, [{abstract_code, {raw_abstract_v1, Code}},
{exports,X0}, {attributes,A}]}} ->
%% R9C-
@@ -805,7 +805,8 @@ abst(File, Builtins, _Mode = functions) ->
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);
+ {Data, U} = xref_reader:module(M, Forms, Builtins, X, D),
+ {ok, M, A, Data, U};
Error when element(1, Error) =:= error ->
Error
end;
@@ -824,7 +825,7 @@ abst(File, Builtins, _Mode = modules) ->
end,
filter(Fun, I0)
end,
- {ok, Mod, {X, I, D}, []};
+ {ok, Mod, At, {X, I, D}, []};
Error when element(1, Error) =:= error ->
Error
end.
@@ -914,7 +915,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, _, Depr, OL0} = Data,
+ {DefAt0, LPreCAt0, XPreCAt0, LC0, XC0, X0, Depr, OL0} = Data,
FT = [tspec(func)],
FET = [tspec(fun_edge)],
PCA = [tspec(pre_call_at)],
@@ -969,7 +970,8 @@ finish_module({functions, XMod, List, NoCalls, Unres}, S) ->
Info = no_info(X2, L2, LC2, XC2, EE2, Unres, NoCalls, NoUnres),
XMod1 = XMod#xref_mod{data = T, info = Info},
- S1 = S#xref{modules = dict:store(M, XMod1, S#xref.modules)},
+ S1 = S#xref{modules = dict:store(M, XMod1, S#xref.modules),
+ ignores = ignores(M, XMod#xref_mod.attributes) ++ S#xref.ignores},
{ok, [M], take_down(S1)};
finish_module({modules, XMod, List}, S) ->
ok = check_module(XMod, S),
@@ -982,6 +984,24 @@ finish_module({modules, XMod, List}, S) ->
S1 = S#xref{modules = dict:store(M, XMod1, S#xref.modules)},
{ok, [M], take_down(S1)}.
+ignores(M, Attrs) ->
+ case lists:keyfind(ignore_xref, 1, Attrs) of
+ {ignore_xref, Xs} when is_list(Xs) -> normalize_ignores(M, Xs);
+ {ignore_xref, X} -> normalize_ignores(M, [X]);
+ _ -> []
+ end.
+
+normalize_ignores(M, [{F, A} | Xs]) ->
+ [{M, F, A} | normalize_ignores(M, Xs)];
+normalize_ignores(M, [{_M, _F, _A}=X | Xs]) ->
+ [X | normalize_ignores(M, Xs)];
+normalize_ignores(M, [X | Xs]) when is_atom(X) ->
+ [X | normalize_ignores(M, Xs)];
+normalize_ignores(M, [_ | Xs]) ->
+ normalize_ignores(M, Xs);
+normalize_ignores(_M, []) ->
+ [].
+
check_module(XMod, State) ->
M = XMod#xref_mod.name,
case dict:find(M, State#xref.modules) of
diff --git a/lib/tools/src/xref_reader.erl b/lib/tools/src/xref_reader.erl
index 82dcda0ce8..8dc8b8416f 100644
--- a/lib/tools/src/xref_reader.erl
+++ b/lib/tools/src/xref_reader.erl
@@ -47,9 +47,6 @@
-include("xref.hrl").
-%% -> {ok, Module, {DefAt, LCallAt, XCallAt, LC, XC, X, Attrs, Depr, OL},
-%% Unresolved}} | EXIT
-%% Attrs = {[], [], []} (no longer used)
module(Module, Forms, CollectBuiltins, X, DF) ->
Attrs = [{Attr,V} || {attribute,_Anno,Attr,V} <- Forms],
IsAbstract = xref_utils:is_abstract_module(Attrs),
@@ -70,8 +67,7 @@ forms([], S) ->
F ->
[{M, F, 0}]
end,
- Attrs = {[], [], []},
- {ok, M, {DefAt, LCallAt, XCallAt, LC, XC, X, Attrs, Depr, OL}, U}.
+ {{DefAt, LCallAt, XCallAt, LC, XC, X, Depr, OL}, U}.
form({attribute, _, on_load, {F, 0}}, S) ->
S#xrefr{on_load = F};
diff --git a/lib/tools/test/xref_SUITE.erl b/lib/tools/test/xref_SUITE.erl
index 0e7ee1a811..a569dcb7fd 100644
--- a/lib/tools/test/xref_SUITE.erl
+++ b/lib/tools/test/xref_SUITE.erl
@@ -904,6 +904,8 @@ lib(Conf) when is_list(Conf) ->
{{lib2,unknown,0},0}, {{lib3,f,0},0}]} = xref:q(s,"(Lin)LM"),
{ok,[lib1,lib2,lib3,t,unknown]} = xref:q(s,"M"),
{ok,[{lib2,f,0},{lib3,f,0},{t,t,0}]} = xref:q(s,"X * M"),
+ {ok, ExportsNotUsed} = xref:analyze(s, exports_not_used),
+ [{t,t,0}] = ExportsNotUsed,
check_state(s),
copy_file(fname(Dir, "lib1.erl"), fname(Dir,"lib1.beam")),
@@ -2508,12 +2510,11 @@ eval(Query, S) ->
unsetify(Answer).
add_module(S, XMod, DefAt, X, LCallAt, XCallAt, XC, LC) ->
- Attr = {[], [], []},
Depr0 = {[], [], [], []},
DBad = [],
Depr = {Depr0,DBad},
OL = [],
- Data = {DefAt, LCallAt, XCallAt, LC, XC, X, Attr, Depr, OL},
+ Data = {DefAt, LCallAt, XCallAt, LC, XC, X, Depr, OL},
Unres = [],
{ok, _Module, _Bad, State} =
xref_base:do_add_module(S, XMod, Unres, Data),
diff --git a/lib/tools/test/xref_SUITE_data/lib_test/t.erl b/lib/tools/test/xref_SUITE_data/lib_test/t.erl
index d2bd81110e..117392d5e8 100644
--- a/lib/tools/test/xref_SUITE_data/lib_test/t.erl
+++ b/lib/tools/test/xref_SUITE_data/lib_test/t.erl
@@ -1,14 +1,31 @@
+%% %CopyrightBegin%
+%%
+%% SPDX-License-Identifier: Apache-2.0
+%%
+%% Copyright Ericsson AB 2025. All Rights Reserved.
+%%
+%% %CopyrightEnd%
-module(t).
--export([t/0]).
+-export([t/0, u/0]).
+
+-ignore_xref(lib0).
+-ignore_xref(u/0).
+-ignore_xref([{lib2,unknown_ignored,0}]).
t() ->
+ %% lib0: nonexisting, ignored
%% lib1: only unknown functions used
%% lib2: one known used, one unknown function used, one local used
%% lib3: one known function used
+ lib0:unknown(),
lib1:unknown(),
lib2:f(), %% known, g/0 not used
+ lib2:unknown_ignored(),
lib2:unknown(),
lib2:local(),
lib3:f(),
unknown:unknown().
+
+u() ->
+ true.
--
2.51.0