%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2003-2023. 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. %% You may obtain a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, software %% distributed under the License is distributed on an "AS IS" BASIS, %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. %% %% %CopyrightEnd% %% -module(warnings_SUITE). %%-define(STANDALONE, true). -ifdef(STANDALONE). -define(line, put(line, ?LINE), ). -define(config(X,Y), foo). -define(privdir, "warnings_SUITE_priv"). -define(t, test_server). -else. -include_lib("common_test/include/ct.hrl"). -define(datadir, proplists:get_value(data_dir, Conf)). -define(privdir, proplists:get_value(priv_dir, Conf)). -endif. -export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, init_per_group/2,end_per_group/2, init_per_testcase/2,end_per_testcase/2]). -export([pattern/1,pattern2/1,pattern3/1,pattern4/1, guard/1,bad_arith/1,bool_cases/1,bad_apply/1, files/1,effect/1,bin_opt_info/1,bin_construction/1, comprehensions/1,maps/1,maps_bin_opt_info/1, redundant_boolean_clauses/1, underscore/1,no_warnings/1, bit_syntax/1,inlining/1,tuple_calls/1, recv_opt_info/1,opportunistic_warnings/1, eep49/1,inline_list_funcs/1]). init_per_testcase(_Case, Config) -> Config. end_per_testcase(_Case, _Config) -> ok. suite() -> [{ct_hooks,[ts_install_cth]}, {timetrap,{minutes,2}}]. all() -> [{group,p}]. groups() -> [{p,test_lib:parallel(), [pattern,pattern2,pattern3,pattern4,guard, bad_arith,bool_cases,bad_apply,files,effect, bin_opt_info,bin_construction,comprehensions,maps, maps_bin_opt_info, redundant_boolean_clauses, underscore,no_warnings,bit_syntax,inlining, tuple_calls,recv_opt_info,opportunistic_warnings, eep49,inline_list_funcs]}]. init_per_suite(Config) -> test_lib:recompile(?MODULE), Config. end_per_suite(_Config) -> ok. init_per_group(_GroupName, Config) -> Config. end_per_group(_GroupName, Config) -> Config. pattern(Config) when is_list(Config) -> %% Test warnings generated by v3_core. Ts = [{pattern, <<"%% Just a comment here. f(a={glurf,2}=A) -> A. g(A) -> case A of a=[_|_] -> error; Other -> true end. foo(X) -> a = {nisse,b} = X. ">>, [warn_unused_vars], {warnings, [{{2,15},v3_core,{nomatch,pattern}}, {{6,20},v3_core,{nomatch,pattern}} ]}}], [] = run(Config, Ts), ok. pattern2(Config) when is_list(Config) -> %% Test warnings generated by sys_core_fold. %% If we disable Core Erlang optimizations, we expect that %% v3_kernel should generate some of the warnings. Source = <<"f(A) -> ok; f(B) -> error. t(A, B, C) -> case {A,B,C} of {a,B} -> ok; {_,B} -> ok end. c(E) -> case E of _ -> ok; _ -> ok end. ">>, %% Test warnings from sys_core_fold. Ts = [{pattern2, Source, [nowarn_unused_vars], {warnings,[{{2,17},sys_core_fold,{nomatch,{shadow,1,{f,1}}}}, {{4,19},sys_core_fold,{nomatch,no_clause}}, {{5,21},sys_core_fold,{nomatch,clause_type}}, {{6,21},sys_core_fold,{nomatch,clause_type}}, {{11,21},sys_core_fold,{nomatch,{shadow,10}}} ]}}], [] = run(Config, Ts), %% Disable Core Erlang optimizations. v3_kernel should produce %% a warning for the clause that didn't match. Ts2 = [{pattern2, Source, [nowarn_unused_vars,no_copt], {warnings, [{{2,17},v3_kernel,{nomatch,{shadow,1}}}, {{11,21},v3_kernel,{nomatch,{shadow,10}}} ]}}], [] = run(Config, Ts2), ok. pattern3(Config) when is_list(Config) -> %% Test warnings generated by the pattern matching compiler %% in v3_kernel. Ts = [{pattern3, <<" f({A,_}) -> {ok,A}; f([_|_]=B) -> {ok,B}; f({urk,nisse}) -> urka_glurka. ">>, [nowarn_unused_vars], {warnings, [{{4,13},v3_kernel,{nomatch,{shadow,2}}}]}}], [] = run(Config, Ts), ok. pattern4(Config) when is_list(Config) -> %% Test warnings for clauses that cannot possibly match. Ts = [{pattern4, <<" t() -> case true of false -> a; true -> b end. fi() -> case true of false -> a; false -> b end, case true of true -> a; true -> b; X -> X end, case boolean of true -> a; false -> b end. int() -> case 42 of [a|b] -> no; <<1>> -> no; <> -> no; 17 -> no; [] -> no; a -> no; {a,b,c} -> no end. tuple() -> case {x,y,z} of \"xyz\" -> no; [a|b] -> no; <<1>> -> no; <> -> no; 17 -> no; [] -> no; a -> no; {a,b,c} -> no; {x,y} -> no end. ">>, [nowarn_unused_vars], {warnings, [{{9,16},sys_core_fold,{nomatch,no_clause}}, {{11,18},sys_core_fold,{nomatch,shadow}}, {{15,18},sys_core_fold,{nomatch,shadow}}, {{18,16},sys_core_fold,{nomatch,no_clause}}, {{23,16},sys_core_fold,{nomatch,no_clause}}, {{33,16},sys_core_fold,{nomatch,no_clause}} ]}}], [] = run(Config, Ts), ok. guard(Config) when is_list(Config) -> %% Test warnings for false guards. Ts = [{guard, <<" t(A, B) when element(x, dum) -> ok. tt(A, B) when 1 == 2 -> ok. ttt() when element(x, dum) -> ok. t4(T, F) when element({F}, T) -> ok. t5(T, F) when element([F], T) -> ok. t6(Pos, F) when element(Pos, [F]) -> ok. t7(Pos) when element(Pos, []) -> ok. ">>, [nowarn_unused_vars], {warnings, [{{2,28},sys_core_fold, {failed,{eval_failure, {erlang,element,2}, badarg}}}, {{4,15},sys_core_fold,{nomatch,guard}}, {{4,15},sys_core_fold,{nomatch,no_clause}}, {{6,26},sys_core_fold, {failed, {eval_failure, {erlang,element,2}, badarg}}} ]}}], [] = run(Config, Ts), ok. bad_arith(Config) when is_list(Config) -> Ts = [{bad_arith, <<"f() -> if a + 3 > 3 -> ok; true -> error end. g(A) -> if is_integer(A), a + 3 > 3 -> ok; a + 3 > 42, is_integer(A) -> ok; true -> error end. h(A) -> a + 3 + A. ">>, [], {warnings, [{{3,21},sys_core_fold, {failed,{eval_failure, {erlang,'+',2}, badarith}}}, {{9,36},sys_core_fold, {failed,{eval_failure, {erlang,'+',2}, badarith}}}, {{10,21},sys_core_fold, {failed,{eval_failure, {erlang,'+',2}, badarith}}}, {{15,19},sys_core_fold, {failed,{eval_failure, {erlang,'+',2}, badarith}}} ] }}], [] = run(Config, Ts), ok. bool_cases(Config) when is_list(Config) -> Ts = [{bool_cases, <<" f(A, B) -> case A > B of true -> true; false -> false; Other -> {error,not_bool} end. g(A, B) -> case A =/= B of false -> false; true -> true; Other -> {error,not_bool} end. h(Bool) -> case not Bool of maybe -> strange; false -> ok; true -> error end. ">>, [nowarn_unused_vars], {warnings, [{{6,18},sys_core_fold,{nomatch,shadow}}, {{13,18},sys_core_fold,{nomatch,shadow}}, {{18,18},sys_core_fold,{nomatch,clause_type}} ]} }], [] = run(Config, Ts), ok. bad_apply(Config) when is_list(Config) -> Ts = [{bad_apply, <<" t(1) -> 42:42(); t(2) -> erlang:42(); t(3) -> 42:start(); t(4) -> []:start(); t(5) -> erlang:[](); t(6) -> [a,b,c](). ">>, [], {warnings, [{{2,22},v3_kernel,{failed,bad_call}}, {{3,22},v3_kernel,{failed,bad_call}}, {{4,22},v3_kernel,{failed,bad_call}}, {{5,22},v3_kernel,{failed,bad_call}}, {{6,22},v3_kernel,{failed,bad_call}}, {{7,22},sys_core_fold,{failed,bad_call}} ]}}], [] = run(Config, Ts), %% Also verify that the generated code generates the correct error. try erlang:42() of _ -> ct:fail(should_fail) catch error:badarg -> ok end, ok. files(Config) when is_list(Config) -> Ts = [{files_1, <<" -file(\"file1\", 14). t1() -> 1/0. -file(\"file2\", 7). t2() -> 1/0. ">>, [], {warnings, [{"file1",[{{17,20},sys_core_fold, {failed,{eval_failure, {erlang,'/',2}, badarith}}}]}, {"file2",[{{10,20},sys_core_fold, {failed,{eval_failure, {erlang,'/',2}, badarith}}}]}]}}], [] = run(Config, Ts), ok. %% Test warnings for term construction and BIF calls in effect context. effect(Config) when is_list(Config) -> Ts = [{no_warnings, %% No warnings should be generated in the following functions. <<" m1(X, Sz) -> if Sz =:= 0 -> X = 0; true -> ok end, ok. m2(X, Sz) -> if Sz =:= 0 -> X = {a,Sz}; true -> ok end, ok. m3(X, Sz) -> if Sz =:= 0 -> X = [a,Sz]; true -> ok end, ok. m4(X, Sz, Var) -> if Sz =:= 0 -> X = Var; true -> ok end, ok. m5(X, Sz) -> if Sz =:= 0 -> X = {a,b,c}; true -> ok end, ok. m6(X, Sz) -> if Sz =:= 0 -> X = {a,Sz,[1,2,3]}; true -> ok end, ok. m7(X, Sz) -> if Sz =:= 0 -> X = {a,Sz,[1,2,3],abs(Sz)}; true -> ok end, ok. m8(A, B) -> case {A,B} of V -> V end, ok. m9(Bs) -> [{B,ok} = {B,foo:bar(B)} || B <- Bs], ok. m10(ConfigTableSize) -> case ConfigTableSize of apa -> CurrentConfig = {id(camel_phase3),id(sms)}, case CurrentConfig of {apa, bepa} -> ok; _ -> ok end end, ok. id(I) -> I. ">>, [],[]}, {basic, <<" t(X) -> case X of warn_lc -> [is_integer(Z) || Z <- [1,2,3]]; warn_lc_2 -> [{error,Z} || Z <- [1,2,3]]; warn_lc_3 -> [{error,abs(Z)} || Z <- [1,2,3]]; no_warn_lc -> [put(last_integer, Z) || Z <- [1,2,3]]; %no warning unused_tuple_literal -> {a,b,c}; unused_list_literal -> [1,2,3,4]; unused_integer -> 42; unused_arith -> X*X end, ok. ">>, [], {warnings,[{{5,22},sys_core_fold,{ignored,{no_effect,{erlang,is_integer,1}}}}, {{7,22},sys_core_fold,{ignored,useless_building}}, {{9,22},sys_core_fold,{ignored,useless_building}}, {{9,29},sys_core_fold,{ignored,{result,{erlang,abs,1}}}}, {{13,21},sys_core_fold,{ignored,useless_building}}, {{15,21},sys_core_fold,{ignored,useless_building}}, {{17,21},sys_core_fold,{ignored,useless_building}}, {{19,22},sys_core_fold,{ignored,{result, {erlang,'*',2}}}} ]}}, {nested, <<" t(X) -> case X of nested -> [{ok,node(),module:foo(),self(),[time(),date()],time()}, is_integer(X)]; unused_bit_syntax -> <>; unused_fun -> fun() -> {ok,X} end; unused_named_fun -> fun F(0) -> 1; F(N) -> N*F(N-1) end; unused_atom -> ignore; %no warning unused_nil -> []; %no warning comp_op -> X =:= 2; cookie -> erlang:get_cookie(); result_ignore -> _ = list_to_integer(X); warn_lc_4 -> %% No warning because of assignment to _. [_ = abs(Z) || Z <- [1,2,3]] end, ok. ">>, [], {warnings,[{{5,21},sys_core_fold,{ignored,useless_building}}, {{5,26},sys_core_fold,{ignored,{no_effect,{erlang,node,0}}}}, {{5,46},sys_core_fold,{ignored,{no_effect,{erlang,self,0}}}}, {{5,54},sys_core_fold,{ignored,{no_effect,{erlang,time,0}}}}, {{5,61},sys_core_fold,{ignored,{no_effect,{erlang,date,0}}}}, {{5,69},sys_core_fold,{ignored,{no_effect,{erlang,time,0}}}}, {{6,22},sys_core_fold,{ignored,{no_effect,{erlang,is_integer,1}}}}, {{8,21},sys_core_fold,{ignored,useless_building}}, {{10,21},sys_core_fold,{ignored,useless_building}}, {{12,21},sys_core_fold,{ignored,useless_building}}, {{20,23},sys_core_fold,{ignored,{no_effect,{erlang,'=:=',2}}}}, {{22,21},sys_core_fold,{ignored,{no_effect,{erlang,get_cookie,0}}}} ]}}, {seq, <<" t(T) -> [ {a,b,T} ], [ {x,y,T} ], ok. ">>, [], {warnings,[{{3,16},sys_core_fold,{ignored,useless_building}}, {{3,30},sys_core_fold,{ignored,useless_building}}]}}, {propagated_literal, <<" foo(X) -> Y = [$.], %% There must not be a warning for constructing a term that %% is never used. fun() -> X = Y ++ [$.] end(), ok. ">>, [], []} ], [] = run(Config, Ts), ok. bin_opt_info(Config) when is_list(Config) -> Code = <<" t1(Bin) -> case Bin of _ when byte_size(Bin) > 20 -> erlang:error(too_long); <<_,T/binary>> -> t1(T); <<>> -> ok end. %% We use a tail in a BIF instruction, remote call, function %% return, and an optimizable tail call for better coverage. t2(<>) -> if A > B -> t2(T); A =< B -> T end; t2(<<_,T/bytes>>) when byte_size(T) < 4 -> foo; t2(<<_,T/bytes>>) -> split_binary(T, 4). ">>, Ws = (catch run_test(Config, Code, [bin_opt_info])), %% This is an inexact match since the pass reports exact instructions as %% part of the warnings, which may include annotations that vary from run %% to run. {warnings, [{5,beam_ssa_bsm,{unsuitable_call, {{b_local,{b_literal,t1},1}, {used_before_match, {b_set,_,_,{bif,byte_size},[_]}}}}}, {5,beam_ssa_bsm,{binary_created,_,_}}, {11,beam_ssa_bsm,{binary_created,_,_}}, %% A =< B -> T {13,beam_ssa_bsm,context_reused}, %% A > B -> t2(T); {16,beam_ssa_bsm,{binary_created,_,_}}, %% when byte_size(T) < 4 -> {19,beam_ssa_bsm,{remote_call, {b_remote, {b_literal,erlang}, {b_literal,split_binary},2}}}, {19,beam_ssa_bsm,{binary_created,_,_}} %% split_binary(T, 4) ]} = Ws, %% For coverage: don't give the bin_opt_info option. [] = (catch run_test(Config, Code, [])), %% Now try with abstract code and no location. %% %% t1(Bin) -> %% case Bin of %% _ when byte_size(Bin) > 20 -> erlang:error(too_long); %% <<_,T/binary>> -> t1(T); %% <<>> -> ok %% end. Forms = [{attribute,0,module,nolocation_binary}, {attribute,0,export,[{t1,1}]}, {function,0,t1,1, [{clause,0,[{var,0,'Bin'}],[], [{'case',0,{var,0,'Bin'}, [{clause,0, [{var,0,'_'}], [[{op,0,'>', {call,0,{atom,0,byte_size},[{var,0,'Bin'}]}, {integer,0,20}}]], [{call,0, {remote,0,{atom,0,erlang},{atom,0,error}}, [{atom,0,too_long}]}]}, {clause,0, [{bin,0, [{bin_element,0,{var,0,'_'},default,default}, {bin_element,0,{var,0,'T'},default,[binary]}]}], [], [{call,0,{atom,0,t1},[{var,0,'T'}]}]}, {clause,0,[{bin,0,[]}],[],[{atom,0,ok}]}]}]}]}], Wsf = (catch run_forms(Forms, [bin_opt_info])), {warnings, [{none,beam_ssa_bsm,{unsuitable_call, {{b_local,{b_literal,t1},1}, {used_before_match, {b_set,_,_,{bif,byte_size},[_]}}}}}, {none,beam_ssa_bsm,{binary_created,_,_}} ]} = Wsf, ok. bin_construction(Config) when is_list(Config) -> Ts = [{bin_construction, <<" t() -> Bin = <<1,2,3>>, <>. x() -> Bin = <<1,2,3,7:4>>, <>. y() -> <<0.5>>. z() -> <<99999999999999/utf8>>. w() -> <<0.5:1/float>>. a() -> Size = bad_size, <<1:Size>>. ">>, [], {warnings,[{{4,18},sys_core_fold,{failed,embedded_binary_size}}, {{8,18},sys_core_fold,{failed,{embedded_unit,8,28}}}, {{10,21},v3_core,{failed,bad_binary}}, {{11,21},sys_core_fold,{failed,bad_unicode}}, {{12,21},sys_core_fold,{failed,bad_float_size}}, {{16,18},v3_kernel,{failed,bad_segment_size}} ]}}], [] = run(Config, Ts), ok. comprehensions(Config) when is_list(Config) -> Ts = [{tautologic_guards, <<" f() -> [ true || true ]. g() -> << <<1>> || true >>. ">>, [], []}], run(Config, Ts), ok. maps(Config) when is_list(Config) -> Ts = [{bad_map, <<" t() -> case maybe_map of #{} -> ok; not_map -> error end. x() -> case true of #{} -> error; true -> ok end. ">>, [], {warnings,[{{3,18},sys_core_fold,{nomatch,no_clause}}, {{9,22},sys_core_fold,{nomatch,clause_type}}]}}, {bad_map_src1, <<" t() -> M = {a,[]}, {'EXIT',{badarg,_}} = (catch(M#{ a => 1 })), ok. ">>, [], {warnings,[{{4,48},sys_core_fold,{failed,bad_map_update}}]}}, {bad_map_src2, <<" t() -> M = id({a,[]}), {'EXIT',{badarg,_}} = (catch(M#{ a => 1})), ok. id(I) -> I. ">>, [inline], []}, {bad_map_src3, <<" t() -> {'EXIT',{badarg,_}} = (catch <<>>#{ a := 1}), ok. ">>, [], {warnings,[{{3,51},sys_core_fold,{failed,bad_map_update}}]}}, {ok_map_literal_key, <<" t() -> V = id(1), M = id(#{ <<$h,$i>> => V }), V = case M of #{ <<0:257>> := Val } -> Val; #{ <<$h,$i>> := Val } -> Val end, ok. id(I) -> I. ">>, [], []}, {repeated_keys1, <<" foo1() -> #{a=>1, b=> 2, a=>3}. bar1(M) -> M#{a=>1, b=> 2, a:=3}. baz1(M) -> M#{a=>1, b=> 2, a:=3}. foo2() -> #{\"a\"=>1, \"b\"=> 2, \"a\"=>3}. bar2(M) -> M#{\"a\"=>1, \"b\"=> 2, \"a\":=3}. baz2(M) -> M#{\"a\"=>1, \"b\"=> 2, \"a\":=3}. foo3() -> #{\"a\"=>1, \"b\"=> 2, \"a\"=>3}. bar3(M) -> M#{\"a\"=>1, \"b\"=> 2, \"a\":=3}. baz3(M) -> M#{<<\"a\">>=>1, <<\"b\">>=> 2, <<\"a\">>:=3}. ">>, [], {warnings,[{{3,20},v3_core,{map_key_repeated,a}}, {{8,21},v3_core,{map_key_repeated,a}}, {{11,21},v3_core,{map_key_repeated,a}}, {{14,20},v3_core,{map_key_repeated,"a"}}, {{17,21},v3_core,{map_key_repeated,"a"}}, {{20,21},v3_core,{map_key_repeated,"a"}}, {{23,20},v3_core,{map_key_repeated,"a"}}, {{28,21},v3_core,{map_key_repeated,"a"}}, {{31,21},v3_core,{map_key_repeated,<<"a">>}} ]}}, {repeated_keys2, <<" foo4(K) -> #{\"a\"=>1, K => 1, \"b\"=> 2, \"a\"=>3, K=>2}. bar4(M,K) -> M#{a=>1, K =>1, b=> 2, a:=3, K=>2}. baz4(M,K) -> M#{<<\"a\">>=>1, K => 1, <<\"b\">>=> 2, <<\"a\">>:=3, K=>2}. foo5(K) -> #{{\"a\",1}=>1, K => 1, \"b\"=> 2, {\"a\",1}=>3, K=>2}. bar5(M,K) -> M#{{\"a\",<<\"b\">>}=>1, K =>1, \"b\"=> 2, {\"a\",<<\"b\">>}:=3, K=>2}. baz5(M,K) -> M#{{<<\"a\">>,1}=>1, K => 1, <<\"b\">>=> 2, {<<\"a\">>,1}:=3,K=>2}. foo6(K) -> #{#{\"a\"=>1}=>1, K => 1, \"b\"=> 2, #{\"a\"=>1}=>3, K=>2}. bar6(M,K) -> M#{#{\"a\"=><<\"b\">>}=>1, K =>1, \"b\"=> 2, #{\"a\"=><<\"b\">>}:=3, K=>2}. baz6(M,K) -> M#{#{<<\"a\">>=>1}=>1, K => 1, <<\"b\">>=> 2, #{<<\"a\">>=>1}:=3,K=>2}. foo7(K) -> M1 = #{#{\"a\"=>1}=>1, K => 1, \"b\"=> 2}, M1#{#{\"a\"=>1}=>3, K=>2}. bar7(M,K) -> M1 = M#{#{\"a\"=><<\"b\">>}=>1, K =>1, \"b\"=> 2}, M1#{#{\"a\"=><<\"b\">>}:=3, K=>2}. baz7(M,K) -> M1 = M#{#{<<\"a\">>=>1}=>1, K => 1, <<\"b\">>=> 2}, M1#{#{<<\"a\">>=>1}:=3,K=>2}. ">>, [], {warnings,[{{3,20},v3_core,{map_key_repeated,"a"}}, {{6,21},v3_core,{map_key_repeated,a}}, {{9,21},v3_core,{map_key_repeated,<<"a">>}}, {{14,20},v3_core,{map_key_repeated,{"a",1}}}, {{17,21},v3_core,{map_key_repeated,{"a",<<"b">>}}}, {{21,21},v3_core,{map_key_repeated,{<<"a">>,1}}}, {{25,20},v3_core,{map_key_repeated,#{"a" => 1}}}, {{28,21},v3_core,{map_key_repeated,#{"a" => <<"b">>}}}, {{32,21},v3_core,{map_key_repeated,#{<<"a">> => 1}}} ]}} ], run(Config, Ts), ok. maps_bin_opt_info(Config) when is_list(Config) -> Ts = [{map_bsm, <<" t1(<<0:8,7:8,T/binary>>,#{val := I}=M) -> t1(T, M#{val := I+1}); t1(<<_:8>>,M) -> M. ">>, [bin_opt_info], {warnings,[{3,beam_ssa_bsm,context_reused}]}}], [] = run(Config, Ts), ok. redundant_boolean_clauses(Config) when is_list(Config) -> Ts = [{redundant_boolean_clauses, <<" t(X) -> case X == 0 of false -> no; false -> no; true -> yes end. ">>, [], {warnings,[{{5,22},sys_core_fold,{nomatch,shadow}}]}}], run(Config, Ts), ok. underscore(Config) when is_list(Config) -> %% The code template. S0 = <<" f(A) -> _VAR1 = <>, _VAR2 = {ok,A}, _VAR3 = [A], ok. g(A) -> _VAR1 = A/0, _VAR2 = date(), ok. h() -> _VAR1 = fun() -> ok end, ok. i(A) -> _VAR1 = #{A=>42}, ok. ">>, %% Define all possible warnings. Warnings = [{{3,23},sys_core_fold,{ignored,useless_building}}, {{4,23},sys_core_fold,{ignored,useless_building}}, {{5,23},sys_core_fold,{ignored,useless_building}}, {{8,24},sys_core_fold,{ignored,{result,{erlang,'/',2}}}}, {{9,23},sys_core_fold,{ignored,{no_effect,{erlang,date,0}}}}, {{12,24},sys_core_fold,{ignored,useless_building}}, {{15,24},sys_core_fold,{ignored,useless_building}}], %% Compile the unmodified template. Assigning to variable that %% begins with '_' should suppress all warnings. Ts0 = [{underscore0,S0,[],[]}], [] = run(Config, Ts0), %% Replace all "_VAR" variables with a plain underscore. %% There should still be no warnings. S1 = re:replace(S0, "_VAR\\d+", "_", [global]), io:format("~s\n", [S1]), Ts1 = [{underscore1,S1,[],[]}], [] = run(Config, Ts1), %% Make sure that we get warnings if we remove "_VAR = ". S2 = re:replace(S0, "_VAR\\d = ", " ", [global]), io:format("~s\n", [S2]), Ts2 = [{underscore2,S2,[],{warnings,Warnings}}], [] = run(Config, Ts2), %% We should also get warnings if we assign to a variables that don't %% begin with underscore (as well as warnings for unused variables from %% erl_lint). S3 = re:replace(S0, "_(?=VAR\\d+)", " ", [global]), io:format("~s\n", [S3]), Ts3 = [{underscore2,S3,[],{warnings,Warnings}}], [] = run(Config, Ts3), ok. no_warnings(Config) when is_list(Config) -> Ts = [{no_warnings, <<"-record(r, {s=ordsets:new(),a,b}). a() -> R = #r{}, %No warning expected. {R#r.a,R#r.b}. b(X) -> T = true, Var = [X], %No warning expected. case T of false -> Var; true -> [] end. c() -> R0 = {r,\"abc\",undefined,os:timestamp()}, %No warning. case R0 of {r,V1,_V2,V3} -> {r,V1,\"def\",V3} end. d(In0, Bool) -> {In1,Int} = case id(Bool) of false -> {In0,0} end, [In1,Int]. id(I) -> I. ">>, [], []}], run(Config, Ts), ok. bit_syntax(Config) -> Ts = [{?FUNCTION_NAME, <<" a(<<-1>>) -> ok; a(<<1023>>) -> ok; a(<<777/signed>>) -> ok; a(<>) -> ok; a(<>) -> ok; a(<>) -> ok; a(<>) -> ok; a(<>) -> ok; a(<>) -> ok; a(<>) -> ok. b(Bin) -> Sz = bad, <<42:Sz>> = Bin. c(Sz, Bin) -> case Bin of <<-42:Sz/unsigned>> -> ok; <<42:Sz/float>> -> ok; <<42:Sz/binary>> -> ok end. d(<<16#110000/utf8>>) -> error; d(_) -> ok. ">>, [], {warnings,[{{2,15},sys_core_fold,{nomatch,no_clause}}, {{2,19},sys_core_fold,{nomatch,{bit_syntax_unsigned,-1}}}, {{3,19},sys_core_fold,{nomatch,{bit_syntax_truncated, unsigned,1023,8}}}, {{4,19},sys_core_fold,{nomatch,{bit_syntax_truncated, signed,777,8}}}, {{5,19},sys_core_fold,{nomatch,{bit_syntax_type,a,binary}}}, {{6,19},sys_core_fold,{nomatch,{bit_syntax_type,a,integer}}}, {{7,19},sys_core_fold,{nomatch,{bit_syntax_type,a,float}}}, {{8,19},sys_core_fold,{nomatch,{bit_syntax_type,a,utf8}}}, {{9,19},sys_core_fold,{nomatch,{bit_syntax_type,a,utf16}}}, {{10,19},sys_core_fold,{nomatch,{bit_syntax_type,a,utf32}}}, {{11,19},sys_core_fold,{nomatch,{bit_syntax_type,a,utf32}}}, {{12,35},sys_core_fold,{nomatch,no_clause}}, {{12,37},sys_core_fold,{nomatch,{bit_syntax_size,bad}}}, {{15,21},sys_core_fold,{nomatch,{bit_syntax_unsigned,-42}}}, {{17,21},sys_core_fold,{nomatch,{bit_syntax_type,42,binary}}}, {{19,19},sys_core_fold,{nomatch,{bit_syntax_unicode,1114112}}} ]} }], run(Config, Ts), ok. inlining(Config) -> %% Make sure that no spurious warnings are generated %% when inlining. Ts = [{inlining_1, <<"-compile(inline). compute1(X) -> add(X, 0). add(1, 0) -> 1; add(1, Y) -> 1 + Y; add(X, Y) -> X + Y. ">>, [], []}, {inlining_2, <<"-compile({inline,[add/2]}). compute1(X) -> add(X, 0). add(1, 0) -> 1; add(1, Y) -> 1 + Y; add(X, Y) -> X + Y. ">>, [], []} ], run(Config, Ts), ok. tuple_calls(Config) -> %% Make sure that no spurious warnings are generated. Ts = [{inlining_1, <<"-compile(tuple_calls). dispatch(X) -> (list_to_atom(\"prefix_\" ++ atom_to_list(suffix))):doit(X). ">>, [], []} ], run(Config, Ts), ok. recv_opt_info(Config) when is_list(Config) -> Code = <<" simple_receive() -> receive Message -> handle:msg(Message) end. selective_receive(Tag, Message) -> receive {Tag, Message} -> handle:msg(Message) end. cross_function_receive() -> cross_function_receive_1(make_ref()). cross_function_receive_1(Tag) -> receive {Tag, Message} -> handle:msg(Message) end. optimized_receive(Process, Request) -> MRef = monitor(process, Process), Process ! {self(), MRef, Request}, receive {MRef, Reply} -> erlang:demonitor(MRef, [flush]), handle:reply(Reply); {'DOWN', MRef, _, _, Reason} -> handle:error(Reason) end. ">>, Ws = (catch run_test(Config, Code, [recv_opt_info])), %% This is an inexact match since the pass reports exact instructions as %% part of the warnings, which may include annotations that vary from run %% to run. {warnings, [%% simple_receive/0 {3,beam_ssa_recv,matches_any_message}, %% selective_receive/2 {8,beam_ssa_recv,unoptimized_selective_receive}, {13,beam_ssa_recv,reserved_receive_marker}, %% cross_function_receive/0 {13,beam_ssa_recv,{passed_marker,_}}, %% cross_function_receive_1/1 {16,beam_ssa_recv,{used_receive_marker,{parameter,1}}}, %% optimized_receive/2 {21,beam_ssa_recv,reserved_receive_marker}, {23,beam_ssa_recv,{used_receive_marker,_}}]} = Ws, %% For coverage: don't give the recv_opt_info option. [] = (catch run_test(Config, Code, [])), %% Now try with abstract code and no location. %% %% simple_receive() -> %% receive %% Message -> handle:msg(Message) %% end. Forms = [{attribute,0,module,nolocation_recv}, {attribute,0,export,[{t1,0}]}, {function,0,t1,0, [{clause,0,[],[], [{'receive',0, [{clause,0, [{var,0,'Msg'}], [], [{call,0, {remote,0,{atom,0,handle},{atom,0,msg}}, [{var,0,'Msg'}]}]}]}]}]} ], Wsf = (catch run_forms(Forms, [recv_opt_info])), {warnings, [{none,beam_ssa_recv,matches_any_message}]} = Wsf, ok. %% OTP-17260: Test that opportunistic warnings can be disabled. opportunistic_warnings(Config) -> Source = <<"m(_) -> ok; m(_) -> error. a() -> <<0.5>>. b() -> Bin = <<1,2,3,7:4>>, <>. c() -> Size = bad_size, <<1:Size>>. i() -> {a,b,c}, ok. ">>, %% Don't disable any warnings. Ts1 = [{nothing_disabled, Source, [], {warnings,[{{2,17},sys_core_fold,{nomatch,{shadow,1,{m,1}}}}, {{4,24},v3_core,{failed,bad_binary}}, {{5,45},sys_core_fold,{failed,{embedded_unit,8,28}}}, {{6,43},v3_kernel,{failed,bad_segment_size}}, {{8,24},sys_core_fold,{ignored,useless_building}} ]}}], [] = run(Config, Ts1), %% Disable all opportunistic warnings. Ts2 = [{all_disabled, Source, [nowarn_opportunistic], []}], [] = run(Config, Ts2), %% Disable warnings for patterns that don't match. Ts3 = [{nomatch_disabled, Source, [nowarn_nomatch], {warnings,[{{4,24},v3_core,{failed,bad_binary}}, {{5,45},sys_core_fold,{failed,{embedded_unit,8,28}}}, {{6,43},v3_kernel,{failed,bad_segment_size}}, {{8,24},sys_core_fold,{ignored,useless_building}} ]}}], [] = run(Config, Ts3), %% Disable warnings for failures. Ts4 = [{failures_disabled, Source, [nowarn_failed], {warnings,[{{2,17},sys_core_fold,{nomatch,{shadow,1,{m,1}}}}, {{8,24},sys_core_fold,{ignored,useless_building}} ]}}], [] = run(Config, Ts4), %% Disable warnings for useless building. Ts5 = [{disabled_useless_building, Source, [nowarn_ignored], {warnings,[{{2,17},sys_core_fold,{nomatch,{shadow,1,{m,1}}}}, {{4,24},v3_core,{failed,bad_binary}}, {{5,45},sys_core_fold,{failed,{embedded_unit,8,28}}}, {{6,43},v3_kernel,{failed,bad_segment_size}} ]}}], [] = run(Config, Ts5), %% Disable warnings for useless building and failures. Ts6 = [{disabled_combination, Source, [nowarn_ignored,nowarn_failed], {warnings,[{{2,17},sys_core_fold,{nomatch,{shadow,1,{m,1}}}} ]}}], [] = run(Config, Ts6), ok. %% Test value-based error handling (EEP 49). eep49(Config) -> Ts = [{basic, <<"foo(X) -> maybe %% There should be no warning. Always ?= X, Always end. ">>, [{feature,maybe_expr,enable}], []}, {disabled, <<"foo() -> maybe. %Atom maybe. ">>, [{feature,maybe_expr,disable}], []} ], run(Config, Ts), ok. %% GH-6158: There would be a warning for a clause that could not match. inline_list_funcs(Config) -> Ts = [{basic, <<"all(L) -> lists:all(fun erlang:is_integer/1, L). any(L) -> lists:any(fun erlang:is_integer/1, L). foreach(L) -> lists:foreach(fun erlang:is_integer/1, L). map(L) -> lists:map(fun erlang:abs/1, L). filter(L) -> lists:map(fun erlang:is_integer/1, L). foldl(L) -> lists:foldl(fun erlang:is_function/2, L). foldr(L) -> lists:foldl(fun erlang:is_function/2, L). mapfoldl(L) -> lists:mapfoldl(fun erlang:is_function/2, L). mapfoldr(L) -> lists:mapfoldr(fun erlang:is_function/2, L). ">>, [inline_list_funcs], []} ], run(Config, Ts), ok. %%% %%% End of test cases. %%% run(Config, Tests0) -> do_run(Config, Tests0), %% Now test without column numbers. Tests = [lines_only(T) || T <- Tests0], do_run(Config, Tests). lines_only({Name,Test,Opts,{warnings,Result0}}) -> Result1 = lists:map(fun lines_only_1/1, Result0), Result = {warnings,lists:usort(Result1)}, {Name,Test,[{error_location,line}|Opts],Result}; lines_only(NoWarnings) -> NoWarnings. lines_only_1({File,Es0}) when is_list(Es0) -> Es = [lines_only_1(E) || E <- Es0], {File,Es}; lines_only_1({Loc,Mod,Error}) -> case Loc of {Line,_Col} -> {Line,Mod,Error}; Line when is_integer(Line) -> {Line,Mod,Error} end. do_run(Config, Tests) -> F = fun({N,P,Ws,E}, BadL) -> io:format("### ~s\n", [N]), case catch run_test(Config, P, Ws) of E -> BadL; Bad -> io:format("~nTest ~p failed. Expected~n ~p~n" "but got~n ~p~n", [N, E, Bad]), fail() end end, lists:foldl(F, [], Tests). %% Compiles a test module and returns the list of errors and warnings. run_test(Conf, Test0, Warnings) -> Module = "warnings" ++ test_lib:uniq(), Filename = Module ++ ".erl", DataDir = ?privdir, Test1 = ["-module(", Module, "). -file( \"", Filename, "\", 1). ", Test0], Test = iolist_to_binary(Test1), File = filename:join(DataDir, Filename), Opts = [binary,export_all,return|Warnings], ok = file:write_file(File, Test), %% Compile once just to print all warnings (and cover more code). compile:file(File, [binary,export_all,report|Warnings]), %% Test result of compilation. Res = get_warnings(compile:file(File, Opts)), case Res of [] -> []; {warnings, Ws} -> print_warnings(Ws, Test) end, file:delete(File), Res. run_forms(Forms, Warnings) -> get_warnings(compile:forms(Forms, [binary,return|Warnings])). get_warnings(Result) -> case Result of {ok, _M, Bin, []} when is_binary(Bin) -> []; {ok, _M, Bin, Ws0} when is_binary(Bin) -> %% We are not interested in warnings from %% erl_lint here. WsL = [{F,[W || {_,Mod,_}=W <- Ws, Mod =/= erl_lint]} || {F,Ws} <- Ws0], case WsL of [{_File,Ws}] -> {warnings, Ws}; _ -> list_to_tuple([warnings, WsL]) end end. print_warnings(Warnings, Source) -> Lines = binary:split(Source, <<"\n">>, [global]), Cs = [print_warning(W, Lines) || W <- Warnings], io:put_chars(Cs), ok. print_warning({{LineNum,Column},Mod,Data}, Lines) -> Line0 = lists:nth(LineNum, Lines), <> = Line0, Spaces = re:replace(Line1, <<"[^\t]">>, <<" ">>, [global]), CaretLine = [Spaces,"^"], [io_lib:format("~p:~p: ~ts\n", [LineNum,Column,Mod:format_error(Data)]), Line0, "\n", CaretLine, "\n\n"]; print_warning(_, _) -> []. fail() -> ct:fail(failed).