diff options
author | Erlang/OTP <[email protected]> | 2010-06-03 13:45:29 +0200 |
---|---|---|
committer | Erlang/OTP <[email protected]> | 2010-06-03 13:45:29 +0200 |
commit | 09f146a9dcfa4734f91c72bbb286ecca739fe439 (patch) | |
tree | 6a8d09d274a57fadde3539bdf103529521fc2e62 /lib/stdlib | |
parent | a1d19fe9606ec3500d3d44431c11003017f46594 (diff) | |
parent | ad8eef21ca9b8354fa4e32f799b6870dfbd35afb (diff) | |
download | otp-09f146a9dcfa4734f91c72bbb286ecca739fe439.tar.gz otp-09f146a9dcfa4734f91c72bbb286ecca739fe439.tar.bz2 otp-09f146a9dcfa4734f91c72bbb286ecca739fe439.zip |
Merge branch 'pan/otp_8579_autoimport_override' into dev
* origin/pan/otp_8579_autoimport_override:
Update preloaded modules
Update primary bootstrap
Remove outcommented code from erl_lint
Make port_command/3 auto-imported
Remove (harmless) warnings about min/max in core applications
Autoimport min/2 and max/2
Improve coverage of erl_int in testcases
Change warning to error for nowarn_bif_clash compiler directive
Add -compile({no_auto_import,[F/A]}) doc to compiler.xml
Add some testcases to compiler to verify that overriding really happens
Return nowarn_bif_clash functionality but with warning
Teach erl_lint to better override BIFs with local functions and imports
Teach compiler to override autoimport with import
First prototype for local functions overriding autoimported
OTP-8579 Local functions should override auto-imported
Local and imported functions now override the autoimported
BIFs when the names clash. The pre R14 behaviour was that
autoimported BIFs would override local functions. To avoid
that old programs change behaviour, the following will
generate an error:
Doing a call without explicit module name to a local function
having a name clashing with the name of an autoimported BIF
that was present (and autoimported) before OTP R14A
Explicitly importing a function having a name clashing with
the name of an autoimported BIF that was present (and
autoimported) before OTP R14A Using any form of the old
compiler directive nowarn_bif_clash
If the BIF was added or autoimported in OTP R14A or later,
overriding it with an import or a local function will only
result in a warning,
To resolve clashes, you can either use the explicit module
name erlang to call the BIF, or you can remove the autoimport
of that specific BIF by using the new compiler directive
-compile({no_auto_import,[F/A]})., which makes all calls to
the local or imported function without explicit module name
pass without warnings or errors.
The change makes it possible to add autoimported BIFs without
breaking or silently changing old code in the future. However
some current code ingeniously utilizing the old behaviour or
the nowarn_bif_clash compiler directive, might need changing
to be accepted by the compiler.
Diffstat (limited to 'lib/stdlib')
-rw-r--r-- | lib/stdlib/src/erl_internal.erl | 136 | ||||
-rw-r--r-- | lib/stdlib/src/erl_lint.erl | 231 | ||||
-rw-r--r-- | lib/stdlib/src/file_sorter.erl | 9 | ||||
-rw-r--r-- | lib/stdlib/src/lists.erl | 3 | ||||
-rw-r--r-- | lib/stdlib/test/erl_lint_SUITE.erl | 224 |
5 files changed, 526 insertions, 77 deletions
diff --git a/lib/stdlib/src/erl_internal.erl b/lib/stdlib/src/erl_internal.erl index f78d8dc609..bf6e5bc5ca 100644 --- a/lib/stdlib/src/erl_internal.erl +++ b/lib/stdlib/src/erl_internal.erl @@ -48,7 +48,7 @@ %% -export([bif/2,bif/3,guard_bif/2, - type_test/2,new_type_test/2,old_type_test/2]). + type_test/2,new_type_test/2,old_type_test/2,old_bif/2]). -export([arith_op/2,bool_op/2,comp_op/2,list_op/2,send_op/2,op_type/2]). %%--------------------------------------------------------------------------- @@ -299,6 +299,8 @@ bif(list_to_pid, 1) -> true; bif(list_to_tuple, 1) -> true; bif(load_module, 2) -> true; bif(make_ref, 0) -> true; +bif(max,2) -> true; +bif(min,2) -> true; bif(module_loaded, 1) -> true; bif(monitor_node, 2) -> true; bif(node, 0) -> true; @@ -310,6 +312,7 @@ bif(open_port, 2) -> true; bif(pid_to_list, 1) -> true; bif(port_close, 1) -> true; bif(port_command, 2) -> true; +bif(port_command, 3) -> true; bif(port_connect, 2) -> true; bif(port_control, 3) -> true; bif(pre_loaded, 0) -> true; @@ -354,3 +357,134 @@ bif(unlink, 1) -> true; bif(unregister, 1) -> true; bif(whereis, 1) -> true; bif(Name, A) when is_atom(Name), is_integer(A) -> false. + +-spec old_bif(Name::atom(), Arity::arity()) -> boolean(). +%% Returns true if erlang:Name/Arity is an old (pre R14) auto-imported BIF, false otherwise. +%% Use erlang:is_bultin(Mod, Name, Arity) to find whether a function is a BIF +%% (meaning implemented in C) or not. + +old_bif(abs, 1) -> true; +old_bif(apply, 2) -> true; +old_bif(apply, 3) -> true; +old_bif(atom_to_binary, 2) -> true; +old_bif(atom_to_list, 1) -> true; +old_bif(binary_to_atom, 2) -> true; +old_bif(binary_to_existing_atom, 2) -> true; +old_bif(binary_to_list, 1) -> true; +old_bif(binary_to_list, 3) -> true; +old_bif(binary_to_term, 1) -> true; +old_bif(bitsize, 1) -> true; +old_bif(bit_size, 1) -> true; +old_bif(bitstring_to_list, 1) -> true; +old_bif(byte_size, 1) -> true; +old_bif(check_process_code, 2) -> true; +old_bif(concat_binary, 1) -> true; +old_bif(date, 0) -> true; +old_bif(delete_module, 1) -> true; +old_bif(disconnect_node, 1) -> true; +old_bif(element, 2) -> true; +old_bif(erase, 0) -> true; +old_bif(erase, 1) -> true; +old_bif(exit, 1) -> true; +old_bif(exit, 2) -> true; +old_bif(float, 1) -> true; +old_bif(float_to_list, 1) -> true; +old_bif(garbage_collect, 0) -> true; +old_bif(garbage_collect, 1) -> true; +old_bif(get, 0) -> true; +old_bif(get, 1) -> true; +old_bif(get_keys, 1) -> true; +old_bif(group_leader, 0) -> true; +old_bif(group_leader, 2) -> true; +old_bif(halt, 0) -> true; +old_bif(halt, 1) -> true; +old_bif(hd, 1) -> true; +old_bif(integer_to_list, 1) -> true; +old_bif(iolist_size, 1) -> true; +old_bif(iolist_to_binary, 1) -> true; +old_bif(is_alive, 0) -> true; +old_bif(is_process_alive, 1) -> true; +old_bif(is_atom, 1) -> true; +old_bif(is_boolean, 1) -> true; +old_bif(is_binary, 1) -> true; +old_bif(is_bitstr, 1) -> true; +old_bif(is_bitstring, 1) -> true; +old_bif(is_float, 1) -> true; +old_bif(is_function, 1) -> true; +old_bif(is_function, 2) -> true; +old_bif(is_integer, 1) -> true; +old_bif(is_list, 1) -> true; +old_bif(is_number, 1) -> true; +old_bif(is_pid, 1) -> true; +old_bif(is_port, 1) -> true; +old_bif(is_reference, 1) -> true; +old_bif(is_tuple, 1) -> true; +old_bif(is_record, 2) -> true; +old_bif(is_record, 3) -> true; +old_bif(length, 1) -> true; +old_bif(link, 1) -> true; +old_bif(list_to_atom, 1) -> true; +old_bif(list_to_binary, 1) -> true; +old_bif(list_to_bitstring, 1) -> true; +old_bif(list_to_existing_atom, 1) -> true; +old_bif(list_to_float, 1) -> true; +old_bif(list_to_integer, 1) -> true; +old_bif(list_to_pid, 1) -> true; +old_bif(list_to_tuple, 1) -> true; +old_bif(load_module, 2) -> true; +old_bif(make_ref, 0) -> true; +old_bif(module_loaded, 1) -> true; +old_bif(monitor_node, 2) -> true; +old_bif(node, 0) -> true; +old_bif(node, 1) -> true; +old_bif(nodes, 0) -> true; +old_bif(nodes, 1) -> true; +old_bif(now, 0) -> true; +old_bif(open_port, 2) -> true; +old_bif(pid_to_list, 1) -> true; +old_bif(port_close, 1) -> true; +old_bif(port_command, 2) -> true; +old_bif(port_connect, 2) -> true; +old_bif(port_control, 3) -> true; +old_bif(pre_loaded, 0) -> true; +old_bif(process_flag, 2) -> true; +old_bif(process_flag, 3) -> true; +old_bif(process_info, 1) -> true; +old_bif(process_info, 2) -> true; +old_bif(processes, 0) -> true; +old_bif(purge_module, 1) -> true; +old_bif(put, 2) -> true; +old_bif(register, 2) -> true; +old_bif(registered, 0) -> true; +old_bif(round, 1) -> true; +old_bif(self, 0) -> true; +old_bif(setelement, 3) -> true; +old_bif(size, 1) -> true; +old_bif(spawn, 1) -> true; +old_bif(spawn, 2) -> true; +old_bif(spawn, 3) -> true; +old_bif(spawn, 4) -> true; +old_bif(spawn_link, 1) -> true; +old_bif(spawn_link, 2) -> true; +old_bif(spawn_link, 3) -> true; +old_bif(spawn_link, 4) -> true; +old_bif(spawn_monitor, 1) -> true; +old_bif(spawn_monitor, 3) -> true; +old_bif(spawn_opt, 2) -> true; +old_bif(spawn_opt, 3) -> true; +old_bif(spawn_opt, 4) -> true; +old_bif(spawn_opt, 5) -> true; +old_bif(split_binary, 2) -> true; +old_bif(statistics, 1) -> true; +old_bif(term_to_binary, 1) -> true; +old_bif(term_to_binary, 2) -> true; +old_bif(throw, 1) -> true; +old_bif(time, 0) -> true; +old_bif(tl, 1) -> true; +old_bif(trunc, 1) -> true; +old_bif(tuple_size, 1) -> true; +old_bif(tuple_to_list, 1) -> true; +old_bif(unlink, 1) -> true; +old_bif(unregister, 1) -> true; +old_bif(whereis, 1) -> true; +old_bif(Name, A) when is_atom(Name), is_integer(A) -> false. diff --git a/lib/stdlib/src/erl_lint.erl b/lib/stdlib/src/erl_lint.erl index 2cc5c6a5ac..229d455e06 100644 --- a/lib/stdlib/src/erl_lint.erl +++ b/lib/stdlib/src/erl_lint.erl @@ -94,6 +94,8 @@ value_option(Flag, Default, On, OnVal, Off, OffVal, Opts) -> mod_imports=dict:new() :: dict(), %Module Imports compile=[], %Compile flags records=dict:new() :: dict(), %Record definitions + locals=gb_sets:empty() :: gb_set(), %All defined functions (prescanned) + no_auto=gb_sets:empty() :: gb_set(), %Functions explicitly not autoimported defined=gb_sets:empty() :: gb_set(), %Defined fuctions on_load=[] :: [{atom(),integer()}], %On-load function on_load_line=0 :: integer(), %Line for on_load @@ -116,7 +118,7 @@ value_option(Flag, Default, On, OnVal, Off, OffVal, Opts) -> types = dict:new() :: dict() %Type definitions }). --type lint_state() :: #lint{}. +%% -type lint_state() :: #lint{}. %% format_error(Error) %% Return a string describing the error. @@ -161,6 +163,9 @@ format_error({bad_nowarn_unused_function,{F,A}}) -> io_lib:format("function ~w/~w undefined", [F,A]); format_error({bad_nowarn_bif_clash,{F,A}}) -> io_lib:format("function ~w/~w undefined", [F,A]); +format_error(disallowed_nowarn_bif_clash) -> + io_lib:format("compile directive nowarn_bif_clash is no longer allowed,~n" + " - use explicit module names or -compile({no_auto_import, [F/A]})", []); format_error({bad_nowarn_deprecated_function,{M,F,A}}) -> io_lib:format("~w:~w/~w is not a deprecated function", [M,F,A]); format_error({bad_on_load,Term}) -> @@ -186,13 +191,21 @@ format_error({define_import,{F,A}}) -> io_lib:format("defining imported function ~w/~w", [F,A]); format_error({unused_function,{F,A}}) -> io_lib:format("function ~w/~w is unused", [F,A]); -format_error({redefine_bif,{F,A}}) -> - io_lib:format("defining BIF ~w/~w", [F,A]); format_error({call_to_redefined_bif,{F,A}}) -> - io_lib:format("call to ~w/~w will call erlang:~w/~w; " - "not ~w/~w in this module \n" - " (add an explicit module name to the call to avoid this error)", - [F,A,F,A,F,A]); + io_lib:format("ambiguous call of redefined auto-imported BIF ~w/~w~n" + " - use erlang:~w/~w or \"-compile({no_auto_import,[~w/~w]}).\" " + "to resolve name clash", [F,A,F,A,F,A]); +format_error({call_to_redefined_old_bif,{F,A}}) -> + io_lib:format("ambiguous call of redefined pre R14 auto-imported BIF ~w/~w~n" + " - use erlang:~w/~w or \"-compile({no_auto_import,[~w/~w]}).\" " + "to resolve name clash", [F,A,F,A,F,A]); +format_error({redefine_old_bif_import,{F,A}}) -> + io_lib:format("import directive redefines pre R14 auto-imported BIF ~w/~w~n" + " - use \"-compile({no_auto_import,[~w/~w]}).\" " + "to resolve name clash", [F,A,F,A]); +format_error({redefine_bif_import,{F,A}}) -> + io_lib:format("import directive redefines auto-imported BIF ~w/~w~n" + " - use \"-compile({no_auto_import,[~w/~w]}).\" to resolve name clash", [F,A,F,A]); format_error({deprecated, MFA, ReplacementMFA, Rel}) -> io_lib:format("~s is deprecated and will be removed in ~s; use ~s", @@ -538,8 +551,12 @@ loc(L) -> forms(Forms0, St0) -> Forms = eval_file_attribute(Forms0, St0), + Locals = local_functions(Forms), + AutoImportSuppressed = auto_import_suppressed(St0#lint.compile), + StDeprecated = disallowed_compile_flags(Forms,St0), %% Line numbers are from now on pairs {File,Line}. - St1 = includes_qlc_hrl(Forms, St0), + St1 = includes_qlc_hrl(Forms, StDeprecated#lint{locals = Locals, + no_auto = AutoImportSuppressed}), St2 = bif_clashes(Forms, St1), St3 = not_deprecated(Forms, St2), St4 = foldl(fun form/2, pre_scan(Forms, St3), Forms), @@ -724,13 +741,6 @@ bif_clashes(Forms, St) -> Clashes = ordsets:subtract(ordsets:from_list(Clashes0), Nowarn), St#lint{clashes=Clashes}. --spec is_bif_clash(atom(), byte(), lint_state()) -> boolean(). - -is_bif_clash(_Name, _Arity, #lint{clashes=[]}) -> - false; -is_bif_clash(Name, Arity, #lint{clashes=Clashes}) -> - ordsets:is_element({Name,Arity}, Clashes). - %% not_deprecated(Forms, State0) -> State not_deprecated(Forms, St0) -> @@ -745,6 +755,24 @@ not_deprecated(Forms, St0) -> St1 = func_line_warning(bad_nowarn_deprecated_function, Bad, St0), St1#lint{not_deprecated = ordsets:from_list(Nowarn)}. +%% The nowarn_bif_clash directive is not only deprecated, it's actually an error from R14A +disallowed_compile_flags(Forms, St0) -> + %% There are (still) no line numbers in St0#lint.compile. + Errors0 = [ {St0#lint.file,{L,erl_lint,disallowed_nowarn_bif_clash}} || + {attribute,[{line,{_,L}}],compile,nowarn_bif_clash} <- Forms ], + Errors1 = [ {St0#lint.file,{L,erl_lint,disallowed_nowarn_bif_clash}} || + {attribute,[{line,{_,L}}],compile,{nowarn_bif_clash, {_,_}}} <- Forms ], + Disabled = (not is_warn_enabled(bif_clash, St0)), + Errors = if + Disabled andalso Errors0 =:= [] -> + [{St0#lint.file,{erl_lint,disallowed_nowarn_bif_clash}} | St0#lint.errors]; + Disabled -> + Errors0 ++ Errors1 ++ St0#lint.errors; + true -> + Errors1 ++ St0#lint.errors + end, + St0#lint{errors=Errors}. + %% post_traversal_check(Forms, State0) -> State. %% Do some further checking after the forms have been traversed and %% data about calls etc. have been collected. @@ -1003,7 +1031,8 @@ check_option_functions(Forms, Tag0, Type, St0) -> {Tag, FAs0} <- lists:flatten([Args]), Tag0 =:= Tag, FA <- lists:flatten([FAs0])], - DefFunctions = gb_sets:to_list(St0#lint.defined) -- pseudolocals(), + DefFunctions = (gb_sets:to_list(St0#lint.defined) -- pseudolocals()) ++ + [{F,A} || {{F,A},_} <- orddict:to_list(St0#lint.imports)], Bad = [{FA,L} || {FA,L} <- FAsL, not member(FA, DefFunctions)], func_line_error(Type, Bad, St0). @@ -1094,11 +1123,41 @@ import(Line, {Mod,Fs}, St) -> St#lint{imports=add_imports(list_to_atom(Mod1), Mfs, St#lint.imports)}; Efs -> - foldl(fun (Ef, St0) -> - add_error(Line, {redefine_import,Ef}, - St0) + {Err, St1} = + foldl(fun ({bif,{F,A},_}, {Err,St0}) -> + %% BifClash - import directive + Warn = is_warn_enabled(bif_clash, St0) + and (not bif_clash_specifically_disabled(St0,{F,A})), + AutoImpSup = is_autoimport_suppressed(St0#lint.no_auto,{F,A}), + OldBif = erl_internal:old_bif(F,A), + {Err,if + Warn and (not AutoImpSup) and OldBif -> + add_error + (Line, + {redefine_old_bif_import, {F,A}}, + St0); + Warn and (not AutoImpSup) -> + add_warning + (Line, + {redefine_bif_import, {F,A}}, + St0); + true -> + St0 + end}; + (Ef, {_Err,St0}) -> + {true,add_error(Line, + {redefine_import,Ef}, + St0)} end, - St, Efs) + {false,St}, Efs), + if + not Err -> + St1#lint{imports= + add_imports(list_to_atom(Mod1), Mfs, + St#lint.imports)}; + true -> + St1 + end end; false -> add_error(Line, {bad_module_name, Mod1}, St) @@ -1191,12 +1250,6 @@ call_function(Line, F, A, #lint{usage=Usage0,called=Cd,func=Func}=St) -> end, St#lint{called=[{NA,Line}|Cd], usage=Usage}. -%% is_function_exported(Name, Arity, State) -> false|true. - -is_function_exported(Name, Arity, #lint{exports=Exports,compile=Compile}) -> - gb_sets:is_element({Name,Arity}, Exports) orelse - member(export_all, Compile). - %% function(Line, Name, Arity, Clauses, State) -> State. function(Line, instance, _Arity, _Cs, St) when St#lint.global_vt =/= [] -> @@ -1215,14 +1268,9 @@ define_function(Line, Name, Arity, St0) -> add_error(Line, {redefine_function,NA}, St1); false -> St2 = St1#lint{defined=gb_sets:add_element(NA, St1#lint.defined)}, - St = case erl_internal:bif(Name, Arity) andalso - not is_function_exported(Name, Arity, St2) of - true -> add_warning(Line, {redefine_bif,NA}, St2); - false -> St2 - end, - case imported(Name, Arity, St) of - {yes,_M} -> add_error(Line, {define_import,NA}, St); - no -> St + case imported(Name, Arity, St2) of + {yes,_M} -> add_error(Line, {define_import,NA}, St2); + no -> St2 end end. @@ -1678,8 +1726,6 @@ gexpr({cons,_Line,H,T}, Vt, St) -> gexpr_list([H,T], Vt, St); gexpr({tuple,_Line,Es}, Vt, St) -> gexpr_list(Es, Vt, St); -%%gexpr({struct,_Line,_Tag,Es}, Vt, St) -> -%% gexpr_list(Es, Vt, St); gexpr({record_index,Line,Name,Field}, _Vt, St) -> check_record(Line, Name, St, fun (Dfs, St1) -> record_field(Field, Name, Dfs, St1) end ); @@ -1725,14 +1771,16 @@ gexpr({call,Line,{remote,_,{atom,_,erlang},{atom,_,is_record}=Isr},[_,_,_]=Args} gexpr({call,Line,{atom,_La,F},As}, Vt, St0) -> {Asvt,St1} = gexpr_list(As, Vt, St0), A = length(As), - case erl_internal:guard_bif(F, A) of + %% BifClash - Function called in guard + case erl_internal:guard_bif(F, A) andalso no_guard_bif_clash(St1,{F,A}) of true -> %% Also check that it is auto-imported. case erl_internal:bif(F, A) of true -> {Asvt,St1}; false -> {Asvt,add_error(Line, {explicit_export,F,A}, St1)} end; - false -> {Asvt,add_error(Line, illegal_guard_expr, St1)} + false -> + {Asvt,add_error(Line, illegal_guard_expr, St1)} end; gexpr({call,Line,{remote,_Lr,{atom,_Lm,erlang},{atom,_Lf,F}},As}, Vt, St0) -> {Asvt,St1} = gexpr_list(As, Vt, St0), @@ -1895,8 +1943,6 @@ expr({bc,_Line,E,Qs}, Vt0, St0) -> {vtold(Vt,Vt0),St}; %Don't export local variables expr({tuple,_Line,Es}, Vt, St) -> expr_list(Es, Vt, St); -%%expr({struct,Line,Tag,Es}, Vt, St) -> -%% expr_list(Es, Vt, St); expr({record_index,Line,Name,Field}, _Vt, St) -> check_record(Line, Name, St, fun (Dfs, St1) -> record_field(Field, Name, Dfs, St1) end); @@ -1958,8 +2004,11 @@ expr({'fun',Line,Body}, Vt, St) -> {Bvt, St1} = fun_clauses(Cs, Vt, St), {vtupdate(Bvt, Vt), St1}; {function,F,A} -> + %% BifClash - Fun expression %% N.B. Only allows BIFs here as well, NO IMPORTS!! - case erl_internal:bif(F, A) of + case ((not is_local_function(St#lint.locals,{F,A})) andalso + (erl_internal:bif(F, A) andalso + (not is_autoimport_suppressed(St#lint.no_auto,{F,A})))) of true -> {[],St}; false -> {[],call_function(Line, F, A, St)} end; @@ -1992,16 +2041,14 @@ expr({call,Line,{atom,La,F},As}, Vt, St0) -> St1 = keyword_warning(La, F, St0), {Asvt,St2} = expr_list(As, Vt, St1), A = length(As), - case erl_internal:bif(F, A) of + IsLocal = is_local_function(St2#lint.locals,{F,A}), + IsAutoBif = erl_internal:bif(F, A), + AutoSuppressed = is_autoimport_suppressed(St2#lint.no_auto,{F,A}), + Warn = is_warn_enabled(bif_clash, St2) and (not bif_clash_specifically_disabled(St2,{F,A})), + case ((not IsLocal) andalso IsAutoBif andalso (not AutoSuppressed)) of true -> St3 = deprecated_function(Line, erlang, F, As, St2), - {Asvt,case is_warn_enabled(bif_clash, St3) andalso - is_bif_clash(F, A, St3) of - false -> - St3; - true -> - add_error(Line, {call_to_redefined_bif,{F,A}}, St3) - end}; + {Asvt,St3}; false -> {Asvt,case imported(F, A, St2) of {yes,M} -> @@ -2010,11 +2057,36 @@ expr({call,Line,{atom,La,F},As}, Vt, St0) -> Imp = ordsets:add_element({{F,A},M},U0#usage.imported), St3#lint{usage=U0#usage{imported = Imp}}; no -> - case {F,A} of - {record_info,2} -> + case {F,A} of + {record_info,2} -> check_record_info_call(Line,La,As,St2); - N when N =:= St2#lint.func -> St2; - _ -> call_function(Line, F, A, St2) + N -> + %% BifClash - function call + %% Issue these warnings/errors even if it's a recursive call + St3 = if + (not AutoSuppressed) andalso IsAutoBif andalso Warn -> + case erl_internal:old_bif(F,A) of + true -> + add_error + (Line, + {call_to_redefined_old_bif, {F,A}}, + St2); + false -> + add_warning + (Line, + {call_to_redefined_bif, {F,A}}, + St2) + end; + true -> + St2 + end, + %% ...but don't lint recursive calls + if + N =:= St3#lint.func -> + St3; + true -> + call_function(Line, F, A, St3) + end end end} end; @@ -3443,3 +3515,56 @@ expand_package(M, St0) -> {error, St1} end end. + + +%% Prebuild set of local functions (to override auto-import) +local_functions(Forms) -> + gb_sets:from_list([ {Func,Arity} || {function,_,Func,Arity,_} <- Forms ]). +%% Predicate to find out if the function is locally defined +is_local_function(LocalSet,{Func,Arity}) -> + gb_sets:is_element({Func,Arity},LocalSet). +%% Predicate to see if a function is explicitly imported +is_imported_function(ImportSet,{Func,Arity}) -> + case orddict:find({Func,Arity}, ImportSet) of + {ok,_Mod} -> true; + error -> false + end. +%% Predicate to see if a function is explicitly imported from the erlang module +is_imported_from_erlang(ImportSet,{Func,Arity}) -> + case orddict:find({Func,Arity}, ImportSet) of + {ok,erlang} -> true; + _ -> false + end. +%% Build set of functions where auto-import is explicitly supressed +auto_import_suppressed(CompileFlags) -> + L0 = [ X || {no_auto_import,X} <- CompileFlags ], + L1 = [ {Y,Z} || {Y,Z} <- lists:flatten(L0), is_atom(Y), is_integer(Z) ], + gb_sets:from_list(L1). +%% Predicate to find out if autoimport is explicitly supressed for a function +is_autoimport_suppressed(NoAutoSet,{Func,Arity}) -> + gb_sets:is_element({Func,Arity},NoAutoSet). +%% Predicate to find out if a function specific bif-clash supression (old deprecated) is present +bif_clash_specifically_disabled(St,{F,A}) -> + Nowarn = nowarn_function(nowarn_bif_clash, St#lint.compile), + lists:member({F,A},Nowarn). + +%% Predicate to find out if an autoimported guard_bif is not overriden in some way +%% Guard Bif without module name is disallowed if +%% * It is overridden by local function +%% * It is overridden by -import and that import is not of itself (i.e. from module erlang) +%% * The autoimport is suppressed or it's not reimported by -import directive +%% Otherwise it's OK (given that it's actually a guard bif and actually is autoimported) +no_guard_bif_clash(St,{F,A}) -> + ( + (not is_local_function(St#lint.locals,{F,A})) + andalso + ( + (not is_imported_function(St#lint.imports,{F,A})) orelse + is_imported_from_erlang(St#lint.imports,{F,A}) + ) + andalso + ( + (not is_autoimport_suppressed(St#lint.no_auto, {F,A})) orelse + is_imported_from_erlang(St#lint.imports,{F,A}) + ) + ). diff --git a/lib/stdlib/src/file_sorter.erl b/lib/stdlib/src/file_sorter.erl index e21a0c88f3..3875eca39d 100644 --- a/lib/stdlib/src/file_sorter.erl +++ b/lib/stdlib/src/file_sorter.erl @@ -191,7 +191,7 @@ options([{format, Format} | L], Opts) when Format =:= binary; options([{format, binary_term} | L], Opts) -> options(L, Opts#opts{format = binary_term_fun()}); options([{size, Size} | L], Opts) when is_integer(Size), Size >= 0 -> - options(L, Opts#opts{size = max(Size, 1)}); + options(L, Opts#opts{size = erlang:max(Size, 1)}); options([{no_files, NoFiles} | L], Opts) when is_integer(NoFiles), NoFiles > 1 -> options(L, Opts#opts{no_files = NoFiles}); @@ -997,10 +997,10 @@ close_read_fun(Fd, FileName, fsort) -> file:delete(FileName). read_objs(Fd, FileName, I, L, Bin0, Size0, LSz, W) -> - Max = max(Size0, ?CHUNKSIZE), + Max = erlang:max(Size0, ?CHUNKSIZE), BSz0 = byte_size(Bin0), Min = Size0 - BSz0 + W#w.hdlen, % Min > 0 - NoBytes = max(Min, Max), + NoBytes = erlang:max(Min, Max), case read(Fd, FileName, NoBytes, W) of {ok, Bin} -> BSz = byte_size(Bin), @@ -1180,9 +1180,6 @@ make_key2([Kp], T) -> make_key2([Kp | Kps], T) -> [element(Kp, T) | make_key2(Kps, T)]. -max(A, B) when A < B -> B; -max(A, _) -> A. - infun(W) -> W1 = W#w{in = undefined}, try (W#w.in)(read) of diff --git a/lib/stdlib/src/lists.erl b/lib/stdlib/src/lists.erl index 857eda8161..08ee595f4d 100644 --- a/lib/stdlib/src/lists.erl +++ b/lib/stdlib/src/lists.erl @@ -18,6 +18,9 @@ %% -module(lists). +-compile({no_auto_import,[max/2]}). +-compile({no_auto_import,[min/2]}). + -export([append/2, append/1, subtract/2, reverse/1, nth/2, nthtail/2, prefix/2, suffix/2, last/1, seq/2, seq/3, sum/1, duplicate/2, min/1, max/1, sublist/2, sublist/3, diff --git a/lib/stdlib/test/erl_lint_SUITE.erl b/lib/stdlib/test/erl_lint_SUITE.erl index 8581b496aa..01f494ee38 100644 --- a/lib/stdlib/test/erl_lint_SUITE.erl +++ b/lib/stdlib/test/erl_lint_SUITE.erl @@ -1784,6 +1784,9 @@ otp_5362(Config) when is_list(Config) -> {15,erl_lint,{undefined_field,ok,nix}}, {16,erl_lint,{field_name_is_variable,ok,'Var'}}]}}, + %% Nowarn_bif_clash has changed behaviour as local functions + %% nowdays supersede auto-imported BIFs, why nowarn_bif_clash in itself generates an error + %% (OTP-8579) /PaN {otp_5362_4, <<"-compile(nowarn_deprecated_function). -compile(nowarn_bif_clash). @@ -1795,9 +1798,8 @@ otp_5362(Config) when is_list(Config) -> warn_deprecated_function, warn_bif_clash]}, {error, - [{5,erl_lint,{call_to_redefined_bif,{spawn,1}}}], - [{3,erl_lint,{redefine_bif,{spawn,1}}}, - {4,erl_lint,{deprecated,{erlang,hash,2},{erlang,phash2,2}, + [{5,erl_lint,{call_to_redefined_old_bif,{spawn,1}}}], + [{4,erl_lint,{deprecated,{erlang,hash,2},{erlang,phash2,2}, "in a future release"}}]}}, {otp_5362_5, @@ -1808,8 +1810,8 @@ otp_5362(Config) when is_list(Config) -> spawn(A). ">>, {[nowarn_unused_function]}, - {warnings, - [{3,erl_lint,{redefine_bif,{spawn,1}}}]}}, + {errors, + [{2,erl_lint,disallowed_nowarn_bif_clash}],[]}}, %% The special nowarn_X are not affected by general warn_X. {otp_5362_6, @@ -1822,8 +1824,8 @@ otp_5362(Config) when is_list(Config) -> {[nowarn_unused_function, warn_deprecated_function, warn_bif_clash]}, - {warnings, - [{3,erl_lint,{redefine_bif,{spawn,1}}}]}}, + {errors, + [{2,erl_lint,disallowed_nowarn_bif_clash}],[]}}, {otp_5362_7, <<"-export([spawn/1]). @@ -1838,7 +1840,9 @@ otp_5362(Config) when is_list(Config) -> spawn(A). ">>, {[nowarn_unused_function]}, - {error,[{4,erl_lint,{bad_nowarn_bif_clash,{spawn,2}}}], + {error,[{3,erl_lint,disallowed_nowarn_bif_clash}, + {4,erl_lint,disallowed_nowarn_bif_clash}, + {4,erl_lint,{bad_nowarn_bif_clash,{spawn,2}}}], [{5,erl_lint,{bad_nowarn_deprecated_function,{3,hash,-1}}}, {5,erl_lint,{bad_nowarn_deprecated_function,{erlang,hash,-1}}}, {5,erl_lint,{bad_nowarn_deprecated_function,{{a,b,c},hash,-1}}}]} @@ -1865,7 +1869,21 @@ otp_5362(Config) when is_list(Config) -> t() -> #a{}. ">>, {[]}, - []} + []}, + + {otp_5362_10, + <<"-compile({nowarn_deprecated_function,{erlang,hash,2}}). + -compile({nowarn_bif_clash,{spawn,1}}). + -import(x,[spawn/1]). + spin(A) -> + erlang:hash(A, 3000), + spawn(A). + ">>, + {[nowarn_unused_function, + warn_deprecated_function, + warn_bif_clash]}, + {errors, + [{2,erl_lint,disallowed_nowarn_bif_clash}],[]}} ], @@ -2389,9 +2407,9 @@ bif_clash(Config) when is_list(Config) -> N. ">>, [], - {errors,[{2,erl_lint,{call_to_redefined_bif,{size,1}}}],[]}}, + {errors,[{2,erl_lint,{call_to_redefined_old_bif,{size,1}}}],[]}}, - %% Verify that (some) warnings can be turned off. + %% Verify that warnings can not be turned off in the old way. {clash2, <<"-export([t/1,size/1]). t(X) -> @@ -2400,17 +2418,189 @@ bif_clash(Config) when is_list(Config) -> size({N,_}) -> N. - %% My own abs/1 function works on lists too. - %% Unfortunately, it is not exported, so there will - %% be a warning that can't be turned off. + %% My own abs/1 function works on lists too. From R14 this really works. abs([H|T]) when $a =< H, H =< $z -> [H-($a-$A)|abs(T)]; abs([H|T]) -> [H|abs(T)]; abs([]) -> []; abs(X) -> erlang:abs(X). ">>, - {[nowarn_bif_clash]}, - {warnings,[{11,erl_lint,{redefine_bif,{abs,1}}}, - {11,erl_lint,{unused_function,{abs,1}}}]}}], + {[nowarn_unused_function,nowarn_bif_clash]}, + {errors,[{erl_lint,disallowed_nowarn_bif_clash}],[]}}, + %% As long as noone calls an overridden BIF, it's totally OK + {clash3, + <<"-export([size/1]). + size({N,_}) -> + N; + size(X) -> + erlang:size(X). + ">>, + [], + []}, + %% But this is totally wrong - meaning of the program changed in R14, so this is an error + {clash4, + <<"-export([size/1]). + size({N,_}) -> + N; + size(X) -> + size(X). + ">>, + [], + {errors,[{5,erl_lint,{call_to_redefined_old_bif,{size,1}}}],[]}}, + %% For a post R14 bif, its only a warning + {clash5, + <<"-export([binary_part/2]). + binary_part({B,_},{X,Y}) -> + binary_part(B,{X,Y}); + binary_part(B,{X,Y}) -> + binary:part(B,X,Y). + ">>, + [], + {warnings,[{3,erl_lint,{call_to_redefined_bif,{binary_part,2}}}]}}, + %% If you really mean to call yourself here, you can "unimport" size/1 + {clash6, + <<"-export([size/1]). + -compile({no_auto_import,[size/1]}). + size([]) -> + 0; + size({N,_}) -> + N; + size([_|T]) -> + 1+size(T). + ">>, + [], + []}, + %% Same for the post R14 autoimport warning + {clash7, + <<"-export([binary_part/2]). + -compile({no_auto_import,[binary_part/2]}). + binary_part({B,_},{X,Y}) -> + binary_part(B,{X,Y}); + binary_part(B,{X,Y}) -> + binary:part(B,X,Y). + ">>, + [], + []}, + %% but this doesn't mean the local function is allowed in a guard... + {clash8, + <<"-export([x/1]). + -compile({no_auto_import,[binary_part/2]}). + x(X) when binary_part(X,{1,2}) =:= <<1,2>> -> + hej. + binary_part({B,_},{X,Y}) -> + binary_part(B,{X,Y}); + binary_part(B,{X,Y}) -> + binary:part(B,X,Y). + ">>, + [], + {errors,[{3,erl_lint,illegal_guard_expr}],[]}}, + %% no_auto_import is not like nowarn_bif_clash, it actually removes the autoimport + {clash9, + <<"-export([x/1]). + -compile({no_auto_import,[binary_part/2]}). + x(X) -> + binary_part(X,{1,2}) =:= <<1,2>>. + ">>, + [], + {errors,[{4,erl_lint,{undefined_function,{binary_part,2}}}],[]}}, + %% but we could import it again... + {clash10, + <<"-export([x/1]). + -compile({no_auto_import,[binary_part/2]}). + -import(erlang,[binary_part/2]). + x(X) -> + binary_part(X,{1,2}) =:= <<1,2>>. + ">>, + [], + []}, + %% and actually use it in a guard... + {clash11, + <<"-export([x/1]). + -compile({no_auto_import,[binary_part/2]}). + -import(erlang,[binary_part/2]). + x(X) when binary_part(X,{0,1}) =:= <<0>> -> + binary_part(X,{1,2}) =:= <<1,2>>. + ">>, + [], + []}, + %% but for non-obvious historical reasons, imported functions cannot be used in + %% fun construction without the module name... + {clash12, + <<"-export([x/1]). + -compile({no_auto_import,[binary_part/2]}). + -import(erlang,[binary_part/2]). + x(X) when binary_part(X,{0,1}) =:= <<0>> -> + binary_part(X,{1,2}) =:= fun binary_part/2. + ">>, + [], + {errors,[{5,erl_lint,{undefined_function,{binary_part,2}}}],[]}}, + %% Not from erlang and not from anywhere else + {clash13, + <<"-export([x/1]). + -compile({no_auto_import,[binary_part/2]}). + -import(x,[binary_part/2]). + x(X) -> + binary_part(X,{1,2}) =:= fun binary_part/2. + ">>, + [], + {errors,[{5,erl_lint,{undefined_function,{binary_part,2}}}],[]}}, + %% ...while real auto-import is OK. + {clash14, + <<"-export([x/1]). + x(X) when binary_part(X,{0,1}) =:= <<0>> -> + binary_part(X,{1,2}) =:= fun binary_part/2. + ">>, + [], + []}, + %% Import directive clashing with old bif is an error, regardless of if it's called or not + {clash15, + <<"-export([x/1]). + -import(x,[abs/1]). + x(X) -> + binary_part(X,{1,2}). + ">>, + [], + {errors,[{2,erl_lint,{redefine_old_bif_import,{abs,1}}}],[]}}, + %% For a new BIF, it's only a warning + {clash16, + <<"-export([x/1]). + -import(x,[binary_part/3]). + x(X) -> + abs(X). + ">>, + [], + {warnings,[{2,erl_lint,{redefine_bif_import,{binary_part,3}}}]}}, + %% And, you cannot redefine already imported things that aren't auto-imported + {clash17, + <<"-export([x/1]). + -import(x,[binary_port/3]). + -import(y,[binary_port/3]). + x(X) -> + abs(X). + ">>, + [], + {errors,[{3,erl_lint,{redefine_import,{{binary_port,3},x}}}],[]}}, + %% Not with local functions either + {clash18, + <<"-export([x/1]). + -import(x,[binary_port/3]). + binary_port(A,B,C) -> + binary_part(A,B,C). + x(X) -> + abs(X). + ">>, + [], + {errors,[{3,erl_lint,{define_import,{binary_port,3}}}],[]}}, + %% Like clash8: Dont accept a guard if it's explicitly module-name called either + {clash19, + <<"-export([binary_port/3]). + -compile({no_auto_import,[binary_part/3]}). + -import(x,[binary_part/3]). + binary_port(A,B,C) when x:binary_part(A,B,C) -> + binary_part(A,B,C+1). + ">>, + [], + {errors,[{4,erl_lint,illegal_guard_expr}],[]}} + ], ?line [] = run(Config, Ts), ok. |