1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 1998-2018. All Rights Reserved.
5%%
6%% Licensed under the Apache License, Version 2.0 (the "License");
7%% you may not use this file except in compliance with the License.
8%% You may obtain a copy of the License at
9%%
10%%     http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing, software
13%% distributed under the License is distributed on an "AS IS" BASIS,
14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15%% See the License for the specific language governing permissions and
16%% limitations under the License.
17%%
18%% %CopyrightEnd%
19-module(error_SUITE).
20
21-include_lib("common_test/include/ct.hrl").
22
23-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
24	 init_per_group/2,end_per_group/2,
25	 head_mismatch_line/1,warnings_as_errors/1, bif_clashes/1,
26	 transforms/1,maps_warnings/1,bad_utf8/1,bad_decls/1]).
27
28%% Used by transforms/1 test case.
29-export([parse_transform/2]).
30
31suite() -> [{ct_hooks,[ts_install_cth]}].
32
33all() ->
34    [{group,p}].
35
36groups() ->
37    [{p,test_lib:parallel(),
38      [head_mismatch_line,warnings_as_errors,bif_clashes,
39       transforms,maps_warnings,bad_utf8,bad_decls]}].
40
41init_per_suite(Config) ->
42    test_lib:recompile(?MODULE),
43    Config.
44
45end_per_suite(_Config) ->
46    ok.
47
48init_per_group(_GroupName, Config) ->
49    Config.
50
51end_per_group(_GroupName, Config) ->
52    Config.
53
54
55bif_clashes(Config) when is_list(Config) ->
56    Ts = [{bif_clashes1,
57           <<"
58              -export([t/0]).
59              t() ->
60                 length([a,b,c]).
61
62              length(X) ->
63               erlang:length(X).
64             ">>,
65           [return_warnings],
66	   {error,
67	    [{{4,18}, erl_lint,{call_to_redefined_old_bif,{length,1}}}], []} }],
68    [] = run(Config, Ts),
69    Ts1 = [{bif_clashes2,
70           <<"
71              -export([t/0]).
72              -import(x,[length/1]).
73              t() ->
74                 length([a,b,c]).
75             ">>,
76           [return_warnings],
77	    {error,
78	     [{{3,16}, erl_lint,{redefine_old_bif_import,{length,1}}}], []} }],
79    [] = run(Config, Ts1),
80    Ts00 = [{bif_clashes3,
81           <<"
82              -export([t/0]).
83              -compile({no_auto_import,[length/1]}).
84              t() ->
85                 length([a,b,c]).
86
87              length(X) ->
88               erlang:length(X).
89             ">>,
90           [return_warnings],
91	   []}],
92    [] = run(Config, Ts00),
93    Ts11 = [{bif_clashes4,
94           <<"
95              -export([t/0]).
96              -compile({no_auto_import,[length/1]}).
97              -import(x,[length/1]).
98              t() ->
99                 length([a,b,c]).
100             ">>,
101           [return_warnings],
102	    []}],
103    [] = run(Config, Ts11),
104    Ts000 = [{bif_clashes5,
105           <<"
106              -export([t/0]).
107              t() ->
108                 binary_part(<<1,2,3,4>>,1,2).
109
110              binary_part(X,Y,Z) ->
111               erlang:binary_part(X,Y,Z).
112             ">>,
113           [return_warnings],
114	   {warning,
115	    [{{4,18}, erl_lint,{call_to_redefined_bif,{binary_part,3}}}]} }],
116    [] = run(Config, Ts000),
117    Ts111 = [{bif_clashes6,
118           <<"
119              -export([t/0]).
120              -import(x,[binary_part/3]).
121              t() ->
122                  binary_part(<<1,2,3,4>>,1,2).
123             ">>,
124           [return_warnings],
125	    {warning,
126	     [{{3,16}, erl_lint,{redefine_bif_import,{binary_part,3}}}]} }],
127    [] = run(Config, Ts111),
128    Ts2 = [{bif_clashes7,
129           <<"
130              -export([t/0]).
131              -compile({no_auto_import,[length/1]}).
132              -import(x,[length/1]).
133              t() ->
134                 length([a,b,c]).
135              length(X) ->
136                 erlang:length(X).
137             ">>,
138           [],
139          {error,
140           [{{7,15},erl_lint,{define_import,{length,1}}}],
141           []} }],
142    [] = run2(Config, Ts2),
143    Ts3 = [{bif_clashes8,
144           <<"
145              -export([t/1]).
146              -compile({no_auto_import,[length/1]}).
147              t(X) when length(X) > 3 ->
148                 length([a,b,c]).
149              length(X) ->
150                 erlang:length(X).
151             ">>,
152           [],
153          {error,
154           [{{4,25},erl_lint,{illegal_guard_local_call,{length,1}}}],
155           []} }],
156    [] = run2(Config, Ts3),
157    Ts4 = [{bif_clashes9,
158           <<"
159              -export([t/1]).
160              -compile({no_auto_import,[length/1]}).
161              -import(x,[length/1]).
162              t(X) when length(X) > 3 ->
163                 length([a,b,c]).
164             ">>,
165           [],
166          {error,
167           [{{5,25},erl_lint,{illegal_guard_local_call,{length,1}}}],
168           []} }],
169    [] = run2(Config, Ts4),
170
171    ok.
172
173
174
175
176%% Tests that a head mismatch is reported on the correct line (OTP-2125).
177head_mismatch_line(Config) when is_list(Config) ->
178    [E|_] = get_compilation_errors(Config, "head_mismatch_line"),
179    {{26,1}, Mod, Reason} = E,
180    Mod:format_error(Reason),
181    ok.
182
183%% Compiles a test file and returns the list of errors.
184
185get_compilation_errors(Config, Filename) ->
186    DataDir = proplists:get_value(data_dir, Config),
187    File = filename:join(DataDir, Filename),
188    {error, [{_Name, E}|_], []} = compile:file(File, [return_errors]),
189    E.
190
191warnings_as_errors(Config) when is_list(Config) ->
192    TestFile = test_filename(Config),
193    BeamFile = filename:rootname(TestFile, ".erl") ++ ".beam",
194    OutDir = proplists:get_value(priv_dir, Config),
195
196    Ts1 = [{warnings_as_errors,
197           <<"
198               t() ->
199                 A = unused,
200                 ok.
201             ">>,
202	    [warnings_as_errors, export_all, {outdir, OutDir}],
203	    {error,
204	     [],
205	     [{{3,18},erl_lint,{unused_var,'A'}}]} }],
206    [] = run(Ts1, TestFile, write_beam),
207    false = filelib:is_regular(BeamFile),
208
209    Ts2 = [{warning_unused_var,
210           <<"
211               t() ->
212                 A = unused,
213                 ok.
214             ">>,
215	    [return_warnings, export_all, {outdir, OutDir}],
216	    {warning,
217	       [{{3,18},erl_lint,{unused_var,'A'}}]} }],
218
219    [] = run(Ts2, TestFile, write_beam),
220    true = filelib:is_regular(BeamFile),
221    ok = file:delete(BeamFile),
222
223    ok.
224
225transforms(Config) ->
226    Ts1 = [{undef_parse_transform,
227	    <<"
228              -compile({parse_transform,non_existing}).
229             ">>,
230	    [return],
231	    {error,[{none,compile,{undef_parse_transform,non_existing}}],[]}}],
232    [] = run(Config, Ts1),
233
234    Ts2 = <<"
235              -compile({parse_transform,",?MODULE_STRING,"}).
236             ">>,
237
238    {error,[{none,compile,{parse_transform,?MODULE,{error,too_bad,_}}}],[]} =
239	run_test(Ts2, test_filename(Config), [{pt_error,error}], dont_write_beam),
240
241    {error,[{none,compile,{parse_transform,?MODULE,{error,undef,_}}}],[]} =
242        run_test(Ts2, test_filename(Config), [{pt_error,call_undef}], dont_write_beam),
243
244    {error,[{none,compile,{parse_transform,?MODULE,{exit,exited,_}}}],[]} =
245        run_test(Ts2, test_filename(Config), [{pt_error,exit}], dont_write_beam),
246
247    {error,[{none,compile,{parse_transform,?MODULE,{throw,thrown,[_|_]}}}],[]} =
248        run_test(Ts2, test_filename(Config), [{pt_error,throw}], dont_write_beam),
249
250    ok.
251
252parse_transform(_, Opts) ->
253    {_,Error} = lists:keyfind(pt_error, 1, Opts),
254    case Error of
255        call_undef ->
256            camembert:délicieux();
257        throw ->
258            throw(thrown);
259        exit ->
260            exit(exited);
261        error ->
262            error(too_bad)
263    end.
264
265maps_warnings(Config) when is_list(Config) ->
266    Ts1 = [{map_ok_use_of_pattern,
267	   <<"
268              -export([t/1]).
269              t(K) ->
270                 #{K := 1 = V} = id(#{<<\"hi all\">> => 1}),
271		 V.
272              id(I) -> I.
273             ">>,
274	    [return],
275	    []},
276	{map_illegal_use_of_pattern,
277	   <<"
278              -export([t/0,t/2]).
279	      t(K,#{ K := V }) -> V.
280              t() ->
281                 V = 32,
282                 #{<<\"hi\",V,\"all\">> := 1} = id(#{<<\"hi all\">> => 1}).
283              id(I) -> I.
284             ">>,
285	    [return],
286	    {error,[{{3,15},erl_lint,{unbound_var,'K'}}],[]}}
287    ],
288    [] = run2(Config, Ts1),
289    ok.
290
291bad_utf8(Config) ->
292    Ts = [{bad_explicit_utf8,
293	   %% If coding is specified explicitly as utf-8, there should be
294	   %% a compilation error for a latin-1 comment.
295	   <<"%% coding: utf-8
296              %% Bj",246,"rn
297              t() -> \"",246,"\".
298             ">>,
299	   [],
300	   {error,[{{2,15},epp,cannot_parse},
301		   {{2,15},file_io_server,invalid_unicode}],
302	    []}
303	  },
304
305          {bad_implicit_utf8,
306           %% If there is no coding comment given, encoding defaults to utf-8
307	   %% and there should be a compilation error for a latin-1 comment.
308	   <<"
309              %% Bj",246,"rn
310              t() -> \"",246,"\".
311             ">>,
312	   [],
313	   {error,[{{2,15},epp,cannot_parse},
314		   {{2,15},file_io_server,invalid_unicode}],
315	    []}
316          }
317         ],
318    [] = run2(Config, Ts),
319    ok.
320
321bad_decls(Config) ->
322    Ts = [{bad_decls_1,
323	   <<"\n-module({l}).
324             ">>,
325	   [],
326           {error,[{{2,9},erl_parse,"bad " ++ ["module"] ++ " declaration"}],
327            []}
328	  },
329          {bad_decls_2,
330	   <<"\n-module(l, m).
331             ">>,
332	   [],
333           {error,[{{2,12},erl_parse,"bad variable list"}],[]}
334	  },
335          {bad_decls_3,
336	   <<"\n-export([a/1], Y).
337             ">>,
338	   [],
339           {error,[{{2,16},erl_parse,"bad " ++ ["export"] ++ " declaration"}],
340            []}
341	  },
342          {bad_decls_4,
343	   <<"\n-import([a/1], Y).
344             ">>,
345	   [],
346           {error,[{{2,16},erl_parse,"bad " ++ ["import"] ++ " declaration"}],
347            []}
348	  },
349          {bad_decls_5,
350	   <<"\n-ugly({A,B}).
351             ">>,
352	   [],
353           {error,[{{2,7},erl_parse,"bad attribute"}],[]}
354	  },
355          {bad_decls_6,
356	   <<"\n-ugly(a, b).
357             ">>,
358	   [],
359           {error,[{{2,10},erl_parse,"bad attribute"}],[]}
360	  },
361          {bad_decls_7,
362	   <<"\n-export([A/1]).
363             ">>,
364	   [],
365           {error,[{{2,10},erl_parse,"bad function name"}],[]}
366	  },
367          {bad_decls_8,
368	   <<"\n-export([a/a]).
369             ">>,
370	   [],
371           {error,[{{2,12},erl_parse,"bad function arity"}],[]}
372	  },
373          {bad_decls_9,
374	   <<"\n-export([a/1, {3,4}]).
375             ">>,
376	   [],
377           {error,[{{2,15},erl_parse,"bad Name/Arity"}],[]}
378	  },
379          {bad_decls_10,
380	   <<"\n-record(A, {{bad,a}}).
381             ">>,
382	   [],
383           {error,[{{2,9},erl_parse,"bad " ++ ["record"] ++ " declaration"}],
384            []}
385           },
386          {bad_decls_11,
387	   <<"\n-record(a, [a,b,c,d]).
388             ">>,
389	   [],
390           {error,[{{2,12},erl_parse,"bad record declaration"}],[]}
391           },
392          {bad_decls_12,
393	   <<"\n-record(a).
394             ">>,
395	   [],
396           {error,[{{2,9},erl_parse,"bad " ++ ["record"] ++ " declaration"}],
397            []}
398           }
399          ],
400    [] = run2(Config, Ts),
401
402    {error,{{1,4},erl_parse,"bad term"}} = parse_string("1, 2 + 4."),
403    {error,{{1,1},erl_parse,"bad term"}} = parse_string("34 + begin 34 end."),
404    ok.
405
406parse_string(S) ->
407    {ok,Ts,_} = erl_scan:string(S, {1, 1}),
408    erl_parse:parse_term(Ts).
409
410
411run(Config, Tests) ->
412    File = test_filename(Config),
413    run(Tests, File, dont_write_beam).
414
415run(Tests, File, WriteBeam) ->
416    F = fun({N,P,Ws,E}, BadL) ->
417                case catch run_test(P, File, Ws, WriteBeam) of
418                    E ->
419                        BadL;
420                    Bad ->
421                        io:format("~nTest ~p failed. Expected~n  ~p~n"
422                                  "but got~n  ~p~n", [N, E, Bad]),
423			fail()
424                end
425        end,
426    lists:foldl(F, [], Tests).
427
428run2(Config, Tests) ->
429    File = test_filename(Config),
430    run2(Tests, File, dont_write_beam).
431
432run2(Tests, File, WriteBeam) ->
433    F = fun({N,P,Ws,E}, BadL) ->
434                case catch filter(run_test(P, File, Ws, WriteBeam)) of
435                    E ->
436                        BadL;
437                    Bad ->
438                        io:format("~nTest ~p failed. Expected~n  ~p~n"
439                                  "but got~n  ~p~n", [N, E, Bad]),
440			fail()
441                end
442        end,
443    lists:foldl(F, [], Tests).
444
445filter({error,Es,_Ws}) ->
446    {error,Es,[]};
447filter(X) ->
448    X.
449
450
451%% Compiles a test module and returns the list of errors and warnings.
452
453test_filename(Conf) ->
454    Filename = ["errors_test_",test_lib:uniq(),".erl"],
455    DataDir = proplists:get_value(priv_dir, Conf),
456    filename:join(DataDir, Filename).
457
458run_test(Test0, File, Warnings, WriteBeam) ->
459    ModName = filename:rootname(filename:basename(File), ".erl"),
460    Mod = list_to_atom(ModName),
461    Test = iolist_to_binary(["-module(",ModName,"). ",Test0]),
462    Opts = case WriteBeam of
463	       dont_write_beam ->
464		   [binary,return_errors|Warnings];
465	       write_beam ->
466		   [return_errors|Warnings]
467	   end,
468    ok = file:write_file(File, Test),
469
470    %% Compile once just to print all errors and warnings.
471    compile:file(File, [binary,report|Warnings]),
472
473    %% Test result of compilation.
474    io:format("~p\n", [Opts]),
475    Res = case compile:file(File, Opts) of
476	      {ok,Mod,_,[{_File,Ws}]} ->
477                  print_diagnostics(Ws, Test),
478		  {warning,Ws};
479	      {ok,Mod,_,[]} ->
480		  [];
481	      {ok,Mod,[{_File,Ws}]} ->
482		  {warning,Ws};
483	      {ok,Mod,[]} ->
484		  [];
485	      {error,[{XFile,Es}],Ws} = _ZZ when is_list(XFile) ->
486                  print_diagnostics(Es, Test),
487		  {error,Es,Ws};
488	      {error,[{XFile,Es1},{XFile,Es2}],Ws} = _ZZ
489		when is_list(XFile) ->
490                  Es = Es1 ++ Es2,
491                  print_diagnostics(Es, Test),
492		  {error,Es,Ws};
493	      {error,Es,[{_File,Ws}]} = _ZZ->
494                  print_diagnostics(Es ++ Ws, Test),
495		  {error,Es,Ws}
496	  end,
497    file:delete(File),
498    Res.
499
500print_diagnostics(Warnings, Source) ->
501    case binary:match(Source, <<"-file(">>) of
502        nomatch ->
503            Lines = binary:split(Source, <<"\n">>, [global]),
504            Cs = [print_diagnostic(W, Lines) || W <- Warnings],
505            io:put_chars(Cs);
506        _ ->
507            %% There are probably fake line numbers greater than
508            %% the number of actual lines.
509            ok
510    end.
511
512print_diagnostic({{LineNum,Column},Mod,Data}, Lines) ->
513    Line0 = lists:nth(LineNum, Lines),
514    <<Line1:(Column-1)/binary,_/binary>> = Line0,
515    Spaces = re:replace(Line1, <<"[^\t]">>, <<" ">>, [global]),
516    CaretLine = [Spaces,"^"],
517    [io_lib:format("~p:~p: ~ts\n", [LineNum,Column,Mod:format_error(Data)]),
518     Line0, "\n",
519     CaretLine, "\n\n"];
520print_diagnostic(_, _) ->
521    [].
522
523fail() ->
524    ct:fail(failed).
525