1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 2001-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%% 20-module(cover). 21 22%% 23%% This module implements the Erlang coverage tool. 24%% 25%% ARCHITECTURE 26%% The coverage tool consists of one process on each node involved in 27%% coverage analysis. The process is registered as 'cover_server' 28%% (?SERVER). The cover_server on the 'main' node is in charge, and 29%% it monitors the cover_servers on all remote nodes. When it gets a 30%% 'DOWN' message for another cover_server, it marks the node as 31%% 'lost'. If a nodeup is received for a lost node the main node 32%% ensures that the cover compiled modules are loaded again. If the 33%% remote node was alive during the disconnected periode, cover data 34%% for this periode will also be included in the analysis. 35%% 36%% The cover_server process on the main node is implemented by the 37%% functions init_main/1 and main_process_loop/1. The cover_server on 38%% the remote nodes are implemented by the functions init_remote/2 and 39%% remote_process_loop/1. 40%% 41%% TABLES 42%% Each nodes has two tables: cover_internal_data_table (?COVER_TABLE) and. 43%% cover_internal_clause_table (?COVER_CLAUSE_TABLE). 44%% ?COVER_TABLE contains the bump data i.e. the data about which lines 45%% have been executed how many times. 46%% ?COVER_CLAUSE_TABLE contains information about which clauses in which modules 47%% cover is currently collecting statistics. 48%% 49%% The main node owns tables named 50%% 'cover_collected_remote_data_table' (?COLLECTION_TABLE) and 51%% 'cover_collected_remote_clause_table' (?COLLECTION_CLAUSE_TABLE). 52%% These tables contain data which is collected from remote nodes (either when a 53%% remote node is stopped with cover:stop/1 or when analysing). When 54%% analysing, data is even moved from the COVER tables on the main 55%% node to the COLLECTION tables. 56%% 57%% The main node also has a table named 'cover_binary_code_table' 58%% (?BINARY_TABLE). This table contains the binary code for each cover 59%% compiled module. This is necessary so that the code can be loaded 60%% on remote nodes that are started after the compilation. 61%% 62%% PARALLELISM 63%% To take advantage of SMP when doing the cover analysis both the data 64%% collection and analysis has been parallelized. One process is spawned for 65%% each node when collecting data, and on the remote node when collecting data 66%% one process is spawned per module. 67%% 68%% When analyzing data it is possible to issue multiple analyse(_to_file)/X 69%% calls at once. They are however all calls (for backwards compatibility 70%% reasons) so the user of cover will have to spawn several processes to to the 71%% calls ( or use async_analyse_to_file ). 72%% 73 74%% External exports 75-export([start/0, start/1, 76 compile/1, compile/2, compile_module/1, compile_module/2, 77 compile_directory/0, compile_directory/1, compile_directory/2, 78 compile_beam/1, compile_beam_directory/0, compile_beam_directory/1, 79 analyse/0, analyse/1, analyse/2, analyse/3, 80 analyze/0, analyze/1, analyze/2, analyze/3, 81 analyse_to_file/0, 82 analyse_to_file/1, analyse_to_file/2, analyse_to_file/3, 83 analyze_to_file/0, 84 analyze_to_file/1, analyze_to_file/2, analyze_to_file/3, 85 async_analyse_to_file/1,async_analyse_to_file/2, 86 async_analyse_to_file/3, async_analyze_to_file/1, 87 async_analyze_to_file/2, async_analyze_to_file/3, 88 export/1, export/2, import/1, 89 modules/0, imported/0, imported_modules/0, which_nodes/0, is_compiled/1, 90 reset/1, reset/0, 91 flush/1, 92 stop/0, stop/1]). 93-export([remote_start/1,get_main_node/0]). 94 95%% Used internally to ensure we upgrade the code to the latest version. 96-export([main_process_loop/1,remote_process_loop/1]). 97 98-record(main_state, {compiled=[], % [{Module,File}] 99 imported=[], % [{Module,File,ImportFile}] 100 stopper, % undefined | pid() 101 nodes=[], % [Node] 102 lost_nodes=[]}). % [Node] 103 104-record(remote_state, {compiled=[], % [{Module,File}] 105 main_node}). % atom() 106 107-record(bump, {module = '_', % atom() 108 function = '_', % atom() 109 arity = '_', % integer() 110 clause = '_', % integer() 111 line = '_' % integer() 112 }). 113-define(BUMP_REC_NAME,bump). 114-define(CHUNK_SIZE, 20000). 115 116-record(vars, {module, % atom() Module name 117 118 init_info=[], % [{M,F,A,C,L}] 119 120 function, % atom() 121 arity, % int() 122 clause, % int() 123 lines, % [int()] 124 no_bump_lines, % [int()] 125 depth, % int() 126 is_guard=false % boolean 127 }). 128 129-define(COVER_TABLE, 'cover_internal_data_table'). 130-define(COVER_CLAUSE_TABLE, 'cover_internal_clause_table'). 131-define(BINARY_TABLE, 'cover_binary_code_table'). 132-define(COLLECTION_TABLE, 'cover_collected_remote_data_table'). 133-define(COLLECTION_CLAUSE_TABLE, 'cover_collected_remote_clause_table'). 134-define(TAG, cover_compiled). 135-define(SERVER, cover_server). 136 137%% Line doesn't matter. 138-define(BLOCK(Expr), {block,erl_anno:new(0),[Expr]}). 139-define(BLOCK1(Expr), 140 if 141 element(1, Expr) =:= block -> 142 Expr; 143 true -> ?BLOCK(Expr) 144 end). 145 146-define(SPAWN_DBG(Tag,Value),put(Tag,Value)). 147-define(STYLESHEET, "styles.css"). 148-define(TOOLS_APP, tools). 149 150-include_lib("stdlib/include/ms_transform.hrl"). 151 152%%%---------------------------------------------------------------------- 153%%% External exports 154%%%---------------------------------------------------------------------- 155 156%% start() -> {ok,Pid} | {error,Reason} 157%% Pid = pid() 158%% Reason = {already_started,Pid} | term() 159start() -> 160 case whereis(?SERVER) of 161 undefined -> 162 Starter = self(), 163 Pid = spawn(fun() -> 164 ?SPAWN_DBG(start,[]), 165 init_main(Starter) 166 end), 167 Ref = erlang:monitor(process,Pid), 168 Return = 169 receive 170 {?SERVER,started} -> 171 {ok,Pid}; 172 {?SERVER,{error,Error}} -> 173 {error,Error}; 174 {'DOWN', Ref, _Type, _Object, Info} -> 175 {error,Info} 176 end, 177 erlang:demonitor(Ref), 178 Return; 179 Pid -> 180 {error,{already_started,Pid}} 181 end. 182 183%% start(Nodes) -> {ok,StartedNodes} 184%% Nodes = Node | [Node,...] 185%% Node = atom() 186start(Node) when is_atom(Node) -> 187 start([Node]); 188start(Nodes) -> 189 call({start_nodes,remove_myself(Nodes,[])}). 190 191%% compile(ModFiles) -> 192%% compile(ModFiles, Options) -> 193%% compile_module(ModFiles) -> Result 194%% compile_module(ModFiles, Options) -> Result 195%% ModFiles = ModFile | [ModFile] 196%% ModFile = Module | File 197%% Module = atom() 198%% File = string() 199%% Options = [Option] 200%% Option = {i,Dir} | {d,Macro} | {d,Macro,Value} 201%% Result = {ok,Module} | {error,File} 202compile(ModFile) -> 203 compile_module(ModFile, []). 204compile(ModFile, Options) -> 205 compile_module(ModFile, Options). 206compile_module(ModFile) when is_atom(ModFile); 207 is_list(ModFile) -> 208 compile_module(ModFile, []). 209compile_module(ModFile, Options) when is_atom(ModFile); 210 is_list(ModFile), is_integer(hd(ModFile)) -> 211 [R] = compile_module([ModFile], Options), 212 R; 213compile_module(ModFiles, Options) when is_list(Options) -> 214 AbsFiles = 215 [begin 216 File = 217 case ModFile of 218 _ when is_atom(ModFile) -> atom_to_list(ModFile); 219 _ when is_list(ModFile) -> ModFile 220 end, 221 WithExt = case filename:extension(File) of 222 ".erl" -> 223 File; 224 _ -> 225 File++".erl" 226 end, 227 filename:absname(WithExt) 228 end || ModFile <- ModFiles], 229 compile_modules(AbsFiles, Options). 230 231%% compile_directory() -> 232%% compile_directory(Dir) -> 233%% compile_directory(Dir, Options) -> [Result] | {error,Reason} 234%% Dir = string() 235%% Options - see compile/1 236%% Result - see compile/1 237%% Reason = eacces | enoent 238compile_directory() -> 239 case file:get_cwd() of 240 {ok, Dir} -> 241 compile_directory(Dir, []); 242 Error -> 243 Error 244 end. 245compile_directory(Dir) when is_list(Dir) -> 246 compile_directory(Dir, []). 247compile_directory(Dir, Options) when is_list(Dir), is_list(Options) -> 248 case file:list_dir(Dir) of 249 {ok, Files} -> 250 ErlFiles = [filename:join(Dir, File) || 251 File <- Files, 252 filename:extension(File) =:= ".erl"], 253 compile_modules(ErlFiles, Options); 254 Error -> 255 Error 256 end. 257 258compile_modules(Files,Options) -> 259 Options2 = filter_options(Options), 260 %% compile_modules(Files,Options2,[]). 261 call({compile, Files, Options2}). 262 263%% compile_modules([File|Files], Options, Result) -> 264%% R = call({compile, File, Options}), 265%% compile_modules(Files,Options,[R|Result]); 266%% compile_modules([],_Opts,Result) -> 267%% lists:reverse(Result). 268 269filter_options(Options) -> 270 lists:filter(fun(Option) -> 271 case Option of 272 {i, Dir} when is_list(Dir) -> true; 273 {d, _Macro} -> true; 274 {d, _Macro, _Value} -> true; 275 export_all -> true; 276 _ -> false 277 end 278 end, 279 Options). 280 281%% compile_beam(ModFile) -> Result | {error,Reason} 282%% ModFile - see compile/1 283%% Result - see compile/1 284%% Reason = non_existing | already_cover_compiled 285compile_beam(ModFile0) when is_atom(ModFile0); 286 is_list(ModFile0), is_integer(hd(ModFile0)) -> 287 case compile_beams([ModFile0]) of 288 [{error,{non_existing,_}}] -> 289 %% Backwards compatibility 290 {error,non_existing}; 291 [Result] -> 292 Result 293 end; 294compile_beam(ModFiles) when is_list(ModFiles) -> 295 compile_beams(ModFiles). 296 297 298%% compile_beam_directory(Dir) -> [Result] | {error,Reason} 299%% Dir - see compile_directory/1 300%% Result - see compile/1 301%% Reason = eacces | enoent 302compile_beam_directory() -> 303 case file:get_cwd() of 304 {ok, Dir} -> 305 compile_beam_directory(Dir); 306 Error -> 307 Error 308 end. 309compile_beam_directory(Dir) when is_list(Dir) -> 310 case file:list_dir(Dir) of 311 {ok, Files} -> 312 BeamFiles = [filename:join(Dir, File) || 313 File <- Files, 314 filename:extension(File) =:= ".beam"], 315 compile_beams(BeamFiles); 316 Error -> 317 Error 318 end. 319 320compile_beams(ModFiles0) -> 321 ModFiles = get_mods_and_beams(ModFiles0,[]), 322 call({compile_beams,ModFiles}). 323 324get_mods_and_beams([Module|ModFiles],Acc) when is_atom(Module) -> 325 case code:which(Module) of 326 non_existing -> 327 get_mods_and_beams(ModFiles,[{error,{non_existing,Module}}|Acc]); 328 File -> 329 get_mods_and_beams([{Module,File}|ModFiles],Acc) 330 end; 331get_mods_and_beams([File|ModFiles],Acc) when is_list(File) -> 332 {WithExt,WithoutExt} 333 = case filename:rootname(File,".beam") of 334 File -> 335 {File++".beam",File}; 336 Rootname -> 337 {File,Rootname} 338 end, 339 AbsFile = filename:absname(WithExt), 340 Module = list_to_atom(filename:basename(WithoutExt)), 341 get_mods_and_beams([{Module,AbsFile}|ModFiles],Acc); 342get_mods_and_beams([{Module,File}|ModFiles],Acc) -> 343 %% Check for duplicates 344 case lists:keyfind(Module,2,Acc) of 345 {ok,Module,File} -> 346 %% Duplicate, but same file so ignore 347 get_mods_and_beams(ModFiles,Acc); 348 {ok,Module,_OtherFile} -> 349 %% Duplicate and differnet file - error 350 get_mods_and_beams(ModFiles,[{error,{duplicate,Module}}|Acc]); 351 _ -> 352 get_mods_and_beams(ModFiles,[{ok,Module,File}|Acc]) 353 end; 354get_mods_and_beams([],Acc) -> 355 lists:reverse(Acc). 356 357 358%% analyse(Modules) -> 359%% analyse(Analysis) -> 360%% analyse(Level) -> 361%% analyse(Modules, Analysis) -> 362%% analyse(Modules, Level) -> 363%% analyse(Analysis, Level) 364%% analyse(Modules, Analysis, Level) -> {ok,Answer} | {error,Error} 365%% Modules = Module | [Module] 366%% Module = atom() 367%% Analysis = coverage | calls 368%% Level = line | clause | function | module 369%% Answer = {Module,Value} | [{Item,Value}] 370%% Item = Line | Clause | Function 371%% Line = {M,N} 372%% Clause = {M,F,A,C} 373%% Function = {M,F,A} 374%% M = F = atom() 375%% N = A = C = integer() 376%% Value = {Cov,NotCov} | Calls 377%% Cov = NotCov = Calls = integer() 378%% Error = {not_cover_compiled,Module} | not_main_node 379-define(is_analysis(__A__), 380 (__A__=:=coverage orelse __A__=:=calls)). 381-define(is_level(__L__), 382 (__L__=:=line orelse __L__=:=clause orelse 383 __L__=:=function orelse __L__=:=module)). 384analyse() -> 385 analyse('_'). 386 387analyse(Analysis) when ?is_analysis(Analysis) -> 388 analyse('_', Analysis); 389analyse(Level) when ?is_level(Level) -> 390 analyse('_', Level); 391analyse(Module) -> 392 analyse(Module, coverage). 393 394analyse(Analysis, Level) when ?is_analysis(Analysis) andalso 395 ?is_level(Level) -> 396 analyse('_', Analysis, Level); 397analyse(Module, Analysis) when ?is_analysis(Analysis) -> 398 analyse(Module, Analysis, function); 399analyse(Module, Level) when ?is_level(Level) -> 400 analyse(Module, coverage, Level). 401 402analyse(Module, Analysis, Level) when ?is_analysis(Analysis), 403 ?is_level(Level) -> 404 call({{analyse, Analysis, Level}, Module}). 405 406analyze() -> analyse( ). 407analyze(Module) -> analyse(Module). 408analyze(Module, Analysis) -> analyse(Module, Analysis). 409analyze(Module, Analysis, Level) -> analyse(Module, Analysis, Level). 410 411%% analyse_to_file() -> 412%% analyse_to_file(Modules) -> 413%% analyse_to_file(Modules, Options) -> 414%% Modules = Module | [Module] 415%% Module = atom() 416%% OutFile = string() 417%% Options = [Option] 418%% Option = html | {outfile,filename()} | {outdir,dirname()} 419%% Error = {not_cover_compiled,Module} | no_source_code_found | 420%% {file,File,Reason} 421%% File = string() 422%% Reason = term() 423%% 424%% Kept for backwards compatibility: 425%% analyse_to_file(Modules, OutFile) -> 426%% analyse_to_file(Modules, OutFile, Options) -> {ok,OutFile} | {error,Error} 427analyse_to_file() -> 428 analyse_to_file('_'). 429analyse_to_file(Arg) -> 430 case is_options(Arg) of 431 true -> 432 analyse_to_file('_',Arg); 433 false -> 434 analyse_to_file(Arg,[]) 435 end. 436analyse_to_file(Module, OutFile) when is_list(OutFile), is_integer(hd(OutFile)) -> 437 %% Kept for backwards compatibility 438 analyse_to_file(Module, [{outfile,OutFile}]); 439analyse_to_file(Module, Options) when is_list(Options) -> 440 call({{analyse_to_file, Options}, Module}). 441analyse_to_file(Module, OutFile, Options) when is_list(OutFile) -> 442 %% Kept for backwards compatibility 443 analyse_to_file(Module,[{outfile,OutFile}|Options]). 444 445analyze_to_file() -> analyse_to_file(). 446analyze_to_file(Module) -> analyse_to_file(Module). 447analyze_to_file(Module, OptOrOut) -> analyse_to_file(Module, OptOrOut). 448analyze_to_file(Module, OutFile, Options) -> 449 analyse_to_file(Module, OutFile, Options). 450 451async_analyse_to_file(Module) -> 452 do_spawn(?MODULE, analyse_to_file, [Module]). 453async_analyse_to_file(Module, OutFileOrOpts) -> 454 do_spawn(?MODULE, analyse_to_file, [Module, OutFileOrOpts]). 455async_analyse_to_file(Module, OutFile, Options) -> 456 do_spawn(?MODULE, analyse_to_file, [Module, OutFile, Options]). 457 458is_options([html]) -> 459 true; % this is not 100% safe - could be a module named html... 460is_options([html|Opts]) -> 461 is_options(Opts); 462is_options([{Opt,_}|_]) when Opt==outfile; Opt==outdir -> 463 true; 464is_options(_) -> 465 false. 466 467do_spawn(M,F,A) -> 468 spawn_link(fun() -> 469 case apply(M,F,A) of 470 {ok, _} -> 471 ok; 472 {error, Reason} -> 473 exit(Reason) 474 end 475 end). 476 477async_analyze_to_file(Module) -> 478 async_analyse_to_file(Module). 479async_analyze_to_file(Module, OutFileOrOpts) -> 480 async_analyse_to_file(Module, OutFileOrOpts). 481async_analyze_to_file(Module, OutFile, Options) -> 482 async_analyse_to_file(Module, OutFile, Options). 483 484outfilename(undefined, Module, HTML) -> 485 outfilename(Module, HTML); 486outfilename(OutDir, Module, HTML) -> 487 filename:join(OutDir, outfilename(Module, HTML)). 488 489outfilename(Module, true) -> 490 atom_to_list(Module)++".COVER.html"; 491outfilename(Module, false) -> 492 atom_to_list(Module)++".COVER.out". 493 494 495%% export(File) 496%% export(File,Module) -> ok | {error,Reason} 497%% File = string(); file to write the exported data to 498%% Module = atom() 499export(File) -> 500 export(File, '_'). 501export(File, Module) -> 502 call({export,File,Module}). 503 504%% import(File) -> ok | {error, Reason} 505%% File = string(); file created with cover:export/1,2 506import(File) -> 507 call({import,File}). 508 509%% modules() -> [Module] 510%% Module = atom() 511modules() -> 512 call(modules). 513 514%% imported_modules() -> [Module] 515%% Module = atom() 516imported_modules() -> 517 call(imported_modules). 518 519%% imported() -> [ImportFile] 520%% ImportFile = string() 521imported() -> 522 call(imported). 523 524%% which_nodes() -> [Node] 525%% Node = atom() 526which_nodes() -> 527 call(which_nodes). 528 529%% is_compiled(Module) -> {file,File} | false 530%% Module = atom() 531%% File = string() 532is_compiled(Module) when is_atom(Module) -> 533 call({is_compiled, Module}). 534 535%% reset(Module) -> ok | {error,Error} 536%% reset() -> ok 537%% Module = atom() 538%% Error = {not_cover_compiled,Module} 539reset(Module) when is_atom(Module) -> 540 call({reset, Module}). 541reset() -> 542 call(reset). 543 544%% stop() -> ok 545stop() -> 546 call(stop). 547 548stop(Node) when is_atom(Node) -> 549 stop([Node]); 550stop(Nodes) -> 551 call({stop,remove_myself(Nodes,[])}). 552 553%% flush(Nodes) -> ok | {error,not_main_node} 554%% Nodes = [Node] | Node 555%% Node = atom() 556%% Error = {not_cover_compiled,Module} 557flush(Node) when is_atom(Node) -> 558 flush([Node]); 559flush(Nodes) -> 560 call({flush,remove_myself(Nodes,[])}). 561 562%% Used by test_server only. Not documented. 563get_main_node() -> 564 call(get_main_node). 565 566%% bump(Module, Function, Arity, Clause, Line) 567%% Module = Function = atom() 568%% Arity = Clause = Line = integer() 569%% This function is inserted into Cover compiled modules, once for each 570%% executable line. 571%bump(Module, Function, Arity, Clause, Line) -> 572% Key = #bump{module=Module, function=Function, arity=Arity, clause=Clause, 573% line=Line}, 574% ets:update_counter(?COVER_TABLE, Key, 1). 575 576call(Request) -> 577 Ref = erlang:monitor(process,?SERVER), 578 receive {'DOWN', Ref, _Type, _Object, noproc} -> 579 erlang:demonitor(Ref), 580 {ok,_} = start(), 581 call(Request) 582 after 0 -> 583 ?SERVER ! {self(),Request}, 584 Return = 585 receive 586 {'DOWN', Ref, _Type, _Object, Info} -> 587 exit(Info); 588 {?SERVER,Reply} -> 589 Reply 590 end, 591 erlang:demonitor(Ref, [flush]), 592 Return 593 end. 594 595reply(From, Reply) -> 596 From ! {?SERVER,Reply}, 597 ok. 598 599is_from(From) -> 600 is_pid(From). 601 602remote_call(Node,Request) -> 603 Ref = erlang:monitor(process,{?SERVER,Node}), 604 receive {'DOWN', Ref, _Type, _Object, noproc} -> 605 erlang:demonitor(Ref), 606 {error,node_dead} 607 after 0 -> 608 {?SERVER,Node} ! Request, 609 Return = 610 receive 611 {'DOWN', Ref, _Type, _Object, _Info} -> 612 case Request of 613 {remote,stop} -> ok; 614 _ -> {error,node_dead} 615 end; 616 {?SERVER,Reply} -> 617 Reply 618 end, 619 erlang:demonitor(Ref, [flush]), 620 Return 621 end. 622 623remote_reply(Proc,Reply) when is_pid(Proc) -> 624 Proc ! {?SERVER,Reply}, 625 ok; 626remote_reply(MainNode,Reply) -> 627 {?SERVER,MainNode} ! {?SERVER,Reply}, 628 ok. 629 630%%%---------------------------------------------------------------------- 631%%% cover_server on main node 632%%%---------------------------------------------------------------------- 633 634init_main(Starter) -> 635 try register(?SERVER,self()) of 636 true -> 637 %% Having write concurrancy here gives a 40% performance boost 638 %% when collect/1 is called. 639 ?COVER_TABLE = ets:new(?COVER_TABLE, [set, public, named_table, 640 {write_concurrency, true}]), 641 ?COVER_CLAUSE_TABLE = ets:new(?COVER_CLAUSE_TABLE, [set, public, 642 named_table]), 643 ?BINARY_TABLE = ets:new(?BINARY_TABLE, [set, public, named_table]), 644 ?COLLECTION_TABLE = ets:new(?COLLECTION_TABLE, [set, public, 645 named_table]), 646 ?COLLECTION_CLAUSE_TABLE = ets:new(?COLLECTION_CLAUSE_TABLE, 647 [set, public, named_table]), 648 ok = net_kernel:monitor_nodes(true), 649 Starter ! {?SERVER,started}, 650 main_process_loop(#main_state{}) 651 catch 652 error:badarg -> 653 %% The server's already registered; either report that it's already 654 %% started or try again if it died before we could find its pid. 655 case whereis(?SERVER) of 656 undefined -> 657 init_main(Starter); 658 Pid -> 659 Starter ! {?SERVER, {error, {already_started, Pid}}} 660 end 661 end. 662 663main_process_loop(State) -> 664 receive 665 {From, {start_nodes,Nodes}} -> 666 {StartedNodes,State1} = do_start_nodes(Nodes, State), 667 reply(From, {ok,StartedNodes}), 668 main_process_loop(State1); 669 670 {From, {compile, Files, Options}} -> 671 {R,S} = do_compile(Files, Options, State), 672 reply(From,R), 673 %% This module (cover) could have been reloaded. Make 674 %% sure we run the new code. 675 ?MODULE:main_process_loop(S); 676 677 {From, {compile_beams, ModsAndFiles}} -> 678 {R,S} = do_compile_beams(ModsAndFiles,State), 679 reply(From,R), 680 %% This module (cover) could have been reloaded. Make 681 %% sure we run the new code. 682 ?MODULE:main_process_loop(S); 683 684 {From, {export,OutFile,Module}} -> 685 spawn(fun() -> 686 ?SPAWN_DBG(export,{OutFile, Module}), 687 do_export(Module, OutFile, From, State) 688 end), 689 main_process_loop(State); 690 691 {From, {import,File}} -> 692 case file:open(File,[read,binary,raw]) of 693 {ok,Fd} -> 694 Imported = do_import_to_table(Fd,File, 695 State#main_state.imported), 696 reply(From, ok), 697 ok = file:close(Fd), 698 main_process_loop(State#main_state{imported=Imported}); 699 {error,Reason} -> 700 reply(From, {error, {cant_open_file,File,Reason}}), 701 main_process_loop(State) 702 end; 703 704 {From, modules} -> 705 %% Get all compiled modules which are still loaded 706 {LoadedModules,Compiled} = 707 get_compiled_still_loaded(State#main_state.nodes, 708 State#main_state.compiled), 709 710 reply(From, LoadedModules), 711 main_process_loop(State#main_state{compiled=Compiled}); 712 713 {From, imported_modules} -> 714 %% Get all modules with imported data 715 ImportedModules = lists:map(fun({Mod,_File,_ImportFile}) -> Mod end, 716 State#main_state.imported), 717 reply(From, ImportedModules), 718 main_process_loop(State); 719 720 {From, imported} -> 721 %% List all imported files 722 reply(From, get_all_importfiles(State#main_state.imported,[])), 723 main_process_loop(State); 724 725 {From, which_nodes} -> 726 %% List all imported files 727 reply(From, State#main_state.nodes), 728 main_process_loop(State); 729 730 {From, reset} -> 731 lists:foreach( 732 fun({Module,_File}) -> 733 do_reset_main_node(Module,State#main_state.nodes) 734 end, 735 State#main_state.compiled), 736 reply(From, ok), 737 main_process_loop(State#main_state{imported=[]}); 738 739 {From, {stop,Nodes}} -> 740 remote_collect('_',Nodes,true), 741 reply(From, ok), 742 Nodes1 = State#main_state.nodes--Nodes, 743 LostNodes1 = State#main_state.lost_nodes--Nodes, 744 main_process_loop(State#main_state{nodes=Nodes1, 745 lost_nodes=LostNodes1}); 746 747 {From, {flush,Nodes}} -> 748 remote_collect('_',Nodes,false), 749 reply(From, ok), 750 main_process_loop(State); 751 752 {From, stop} -> 753 lists:foreach( 754 fun(Node) -> 755 remote_call(Node,{remote,stop}) 756 end, 757 State#main_state.nodes), 758 reload_originals(State#main_state.compiled), 759 ets:delete(?COVER_TABLE), 760 ets:delete(?COVER_CLAUSE_TABLE), 761 ets:delete(?BINARY_TABLE), 762 ets:delete(?COLLECTION_TABLE), 763 ets:delete(?COLLECTION_CLAUSE_TABLE), 764 unregister(?SERVER), 765 reply(From, ok); 766 767 {From, {{analyse, Analysis, Level}, '_'}} -> 768 R = analyse_all(Analysis, Level, State), 769 reply(From, R), 770 main_process_loop(State); 771 772 {From, {{analyse, Analysis, Level}, Modules}} when is_list(Modules) -> 773 R = analyse_list(Modules, Analysis, Level, State), 774 reply(From, R), 775 main_process_loop(State); 776 777 {From, {{analyse, Analysis, Level}, Module}} -> 778 S = try 779 Loaded = is_loaded(Module, State), 780 spawn(fun() -> 781 ?SPAWN_DBG(analyse,{Module,Analysis, Level}), 782 do_parallel_analysis( 783 Module, Analysis, Level, 784 Loaded, From, State) 785 end), 786 State 787 catch throw:Reason -> 788 reply(From,{error, {not_cover_compiled,Module}}), 789 not_loaded(Module, Reason, State) 790 end, 791 main_process_loop(S); 792 793 {From, {{analyse_to_file, Opts},'_'}} -> 794 R = analyse_all_to_file(Opts, State), 795 reply(From,R), 796 main_process_loop(State); 797 798 {From, {{analyse_to_file, Opts},Modules}} when is_list(Modules) -> 799 R = analyse_list_to_file(Modules, Opts, State), 800 reply(From,R), 801 main_process_loop(State); 802 803 {From, {{analyse_to_file, Opts},Module}} -> 804 S = try 805 Loaded = is_loaded(Module, State), 806 spawn_link(fun() -> 807 ?SPAWN_DBG(analyse_to_file,{Module,Opts}), 808 do_parallel_analysis_to_file( 809 Module, Opts, Loaded, From, State) 810 end), 811 State 812 catch throw:Reason -> 813 reply(From,{error, {not_cover_compiled,Module}}), 814 not_loaded(Module, Reason, State) 815 end, 816 main_process_loop(S); 817 818 {From, {is_compiled, Module}} -> 819 S = try is_loaded(Module, State) of 820 {loaded, File} -> 821 reply(From,{file, File}), 822 State; 823 {imported,_File,_ImportFiles} -> 824 reply(From,false), 825 State 826 catch throw:Reason -> 827 reply(From,false), 828 not_loaded(Module, Reason, State) 829 end, 830 main_process_loop(S); 831 832 {From, {reset, Module}} -> 833 S = try 834 Loaded = is_loaded(Module,State), 835 R = case Loaded of 836 {loaded, _File} -> 837 do_reset_main_node( 838 Module, State#main_state.nodes); 839 {imported, _File, _} -> 840 do_reset_collection_table(Module) 841 end, 842 Imported = 843 remove_imported(Module, 844 State#main_state.imported), 845 reply(From, R), 846 State#main_state{imported=Imported} 847 catch throw:Reason -> 848 reply(From,{error, {not_cover_compiled,Module}}), 849 not_loaded(Module, Reason, State) 850 end, 851 main_process_loop(S); 852 853 {'DOWN', _MRef, process, {?SERVER,Node}, _Info} -> 854 %% A remote cover_server is down, mark as lost 855 {Nodes,Lost} = 856 case lists:member(Node,State#main_state.nodes) of 857 true -> 858 N = State#main_state.nodes--[Node], 859 L = [Node|State#main_state.lost_nodes], 860 {N,L}; 861 false -> % node stopped 862 {State#main_state.nodes,State#main_state.lost_nodes} 863 end, 864 main_process_loop(State#main_state{nodes=Nodes,lost_nodes=Lost}); 865 866 {nodeup,Node} -> 867 State1 = 868 case lists:member(Node,State#main_state.lost_nodes) of 869 true -> 870 sync_compiled(Node,State); 871 false -> 872 State 873 end, 874 main_process_loop(State1); 875 876 {nodedown,_} -> 877 %% Will be taken care of when 'DOWN' message arrives 878 main_process_loop(State); 879 880 {From, get_main_node} -> 881 reply(From, node()), 882 main_process_loop(State); 883 884 get_status -> 885 io:format("~tp~n",[State]), 886 main_process_loop(State) 887 end. 888 889%%%---------------------------------------------------------------------- 890%%% cover_server on remote node 891%%%---------------------------------------------------------------------- 892 893init_remote(Starter,MainNode) -> 894 register(?SERVER,self()), 895 %% write_concurrency here makes otp_8270 break :( 896 ?COVER_TABLE = ets:new(?COVER_TABLE, [set, public, named_table 897 %,{write_concurrency, true} 898 ]), 899 ?COVER_CLAUSE_TABLE = ets:new(?COVER_CLAUSE_TABLE, [set, public, 900 named_table]), 901 Starter ! {self(),started}, 902 remote_process_loop(#remote_state{main_node=MainNode}). 903 904 905 906remote_process_loop(State) -> 907 receive 908 {remote,load_compiled,Compiled} -> 909 Compiled1 = load_compiled(Compiled,State#remote_state.compiled), 910 remote_reply(State#remote_state.main_node, ok), 911 ?MODULE:remote_process_loop(State#remote_state{compiled=Compiled1}); 912 913 {remote,unload,UnloadedModules} -> 914 unload(UnloadedModules), 915 Compiled = 916 update_compiled(UnloadedModules, State#remote_state.compiled), 917 remote_reply(State#remote_state.main_node, ok), 918 remote_process_loop(State#remote_state{compiled=Compiled}); 919 920 {remote,reset,Module} -> 921 do_reset(Module), 922 remote_reply(State#remote_state.main_node, ok), 923 remote_process_loop(State); 924 925 {remote,collect,Module,CollectorPid} -> 926 self() ! {remote,collect,Module,CollectorPid, ?SERVER}; 927 928 {remote,collect,Modules0,CollectorPid,From} -> 929 Modules = case Modules0 of 930 '_' -> [M || {M,_} <- State#remote_state.compiled]; 931 _ -> Modules0 932 end, 933 spawn(fun() -> 934 ?SPAWN_DBG(remote_collect, 935 {Modules, CollectorPid, From}), 936 do_collect(Modules, CollectorPid, From) 937 end), 938 remote_process_loop(State); 939 940 {remote,stop} -> 941 reload_originals(State#remote_state.compiled), 942 ets:delete(?COVER_TABLE), 943 ets:delete(?COVER_CLAUSE_TABLE), 944 unregister(?SERVER), 945 ok; % not replying since 'DOWN' message will be received anyway 946 947 {remote,get_compiled} -> 948 remote_reply(State#remote_state.main_node, 949 State#remote_state.compiled), 950 remote_process_loop(State); 951 952 {From, get_main_node} -> 953 remote_reply(From, State#remote_state.main_node), 954 remote_process_loop(State); 955 956 get_status -> 957 io:format("~tp~n",[State]), 958 remote_process_loop(State); 959 960 M -> 961 io:format("WARNING: remote cover_server received\n~p\n",[M]), 962 case M of 963 {From,_} -> 964 case is_from(From) of 965 true -> 966 reply(From,{error,not_main_node}); 967 false -> 968 ok 969 end; 970 _ -> 971 ok 972 end, 973 remote_process_loop(State) 974 975 end. 976 977do_collect(Modules, CollectorPid, From) -> 978 _ = pmap( 979 fun(Module) -> 980 Pattern = {#bump{module=Module, _='_'}, '$1'}, 981 MatchSpec = [{Pattern,[{'=/=','$1',0}],['$_']}], 982 Match = ets:select(?COVER_TABLE,MatchSpec,?CHUNK_SIZE), 983 send_chunks(Match, CollectorPid, []) 984 end,Modules), 985 CollectorPid ! done, 986 remote_reply(From, ok). 987 988send_chunks('$end_of_table', _CollectorPid, Mons) -> 989 get_downs(Mons); 990send_chunks({Chunk,Continuation}, CollectorPid, Mons) -> 991 Mon = spawn_monitor( 992 fun() -> 993 lists:foreach(fun({Bump,_N}) -> 994 ets:insert(?COVER_TABLE, {Bump,0}) 995 end, 996 Chunk) end), 997 send_chunk(CollectorPid,Chunk), 998 send_chunks(ets:select(Continuation), CollectorPid, [Mon|Mons]). 999 1000send_chunk(CollectorPid,Chunk) -> 1001 CollectorPid ! {chunk,Chunk,self()}, 1002 receive continue -> ok end. 1003 1004get_downs([]) -> 1005 ok; 1006get_downs(Mons) -> 1007 receive 1008 {'DOWN', Ref, _Type, Pid, _Reason} = Down -> 1009 case lists:member({Pid,Ref},Mons) of 1010 true -> 1011 get_downs(lists:delete({Pid,Ref},Mons)); 1012 false -> 1013 %% This should be handled somewhere else 1014 self() ! Down, 1015 get_downs(Mons) 1016 end 1017 end. 1018 1019reload_originals(Compiled) -> 1020 _ = pmap(fun do_reload_original/1, [M || {M,_} <- Compiled]), 1021 ok. 1022 1023do_reload_original(Module) -> 1024 case code:which(Module) of 1025 ?TAG -> 1026 _ = code:purge(Module), % remove code marked as 'old' 1027 _ = code:delete(Module), % mark cover compiled code as 'old' 1028 %% Note: original beam code must be loaded before the cover 1029 %% compiled code is purged, in order to for references to 1030 %% 'fun M:F/A' and %% 'fun F/A' funs to be correct (they 1031 %% refer to (M:)F/A in the *latest* version of the module) 1032 _ = code:load_file(Module), % load original code 1033 _ = code:purge(Module); % remove cover compiled code 1034 _ -> 1035 ignore 1036 end. 1037 1038load_compiled([{Module,File,Binary,InitialTable}|Compiled],Acc) -> 1039 %% Make sure the #bump{} records are available *before* the 1040 %% module is loaded. 1041 insert_initial_data(InitialTable), 1042 Sticky = case code:is_sticky(Module) of 1043 true -> 1044 code:unstick_mod(Module), 1045 true; 1046 false -> 1047 false 1048 end, 1049 NewAcc = case code:load_binary(Module, ?TAG, Binary) of 1050 {module,Module} -> 1051 add_compiled(Module, File, Acc); 1052 _ -> 1053 do_clear(Module), 1054 Acc 1055 end, 1056 case Sticky of 1057 true -> code:stick_mod(Module); 1058 false -> ok 1059 end, 1060 load_compiled(Compiled,NewAcc); 1061load_compiled([],Acc) -> 1062 Acc. 1063 1064insert_initial_data([Item|Items]) when is_atom(element(1,Item)) -> 1065 ets:insert(?COVER_CLAUSE_TABLE, Item), 1066 insert_initial_data(Items); 1067insert_initial_data([Item|Items]) -> 1068 ets:insert(?COVER_TABLE, Item), 1069 insert_initial_data(Items); 1070insert_initial_data([]) -> 1071 ok. 1072 1073 1074unload([Module|Modules]) -> 1075 do_clear(Module), 1076 do_reload_original(Module), 1077 unload(Modules); 1078unload([]) -> 1079 ok. 1080 1081%%%---------------------------------------------------------------------- 1082%%% Internal functions 1083%%%---------------------------------------------------------------------- 1084 1085%%%--Handling of remote nodes-------------------------------------------- 1086 1087do_start_nodes(Nodes, State) -> 1088 ThisNode = node(), 1089 StartedNodes = 1090 lists:foldl( 1091 fun(Node,Acc) -> 1092 case rpc:call(Node,cover,remote_start,[ThisNode]) of 1093 {ok,_RPid} -> 1094 erlang:monitor(process,{?SERVER,Node}), 1095 [Node|Acc]; 1096 Error -> 1097 io:format("Could not start cover on ~w: ~tp\n", 1098 [Node,Error]), 1099 Acc 1100 end 1101 end, 1102 [], 1103 Nodes), 1104 1105 %% In case some of the compiled modules have been unloaded they 1106 %% should not be loaded on the new node. 1107 {_LoadedModules,Compiled} = 1108 get_compiled_still_loaded(State#main_state.nodes, 1109 State#main_state.compiled), 1110 remote_load_compiled(StartedNodes,Compiled), 1111 1112 State1 = 1113 State#main_state{nodes = State#main_state.nodes ++ StartedNodes, 1114 compiled = Compiled}, 1115 {StartedNodes, State1}. 1116 1117%% start the cover_server on a remote node 1118remote_start(MainNode) -> 1119 case whereis(?SERVER) of 1120 undefined -> 1121 Starter = self(), 1122 Pid = spawn(fun() -> 1123 ?SPAWN_DBG(remote_start,{MainNode}), 1124 init_remote(Starter,MainNode) 1125 end), 1126 Ref = erlang:monitor(process,Pid), 1127 Return = 1128 receive 1129 {Pid,started} -> 1130 {ok,Pid}; 1131 {'DOWN', Ref, _Type, _Object, Info} -> 1132 {error,Info} 1133 end, 1134 erlang:demonitor(Ref), 1135 Return; 1136 Pid -> 1137 {error,{already_started,Pid}} 1138 end. 1139 1140%% If a lost node comes back, ensure that main and remote node has the 1141%% same cover compiled modules. Note that no action is taken if the 1142%% same {Mod,File} eksists on both, i.e. code change is not handled! 1143sync_compiled(Node,State) -> 1144 #main_state{compiled=Compiled0,nodes=Nodes,lost_nodes=Lost}=State, 1145 State1 = 1146 case remote_call(Node,{remote,get_compiled}) of 1147 {error,node_dead} -> 1148 {_,S} = do_start_nodes([Node],State), 1149 S; 1150 {error,_} -> 1151 State; 1152 RemoteCompiled -> 1153 {_,Compiled} = get_compiled_still_loaded(Nodes,Compiled0), 1154 Unload = [UM || {UM,_}=U <- RemoteCompiled, 1155 false == lists:member(U,Compiled)], 1156 remote_unload([Node],Unload), 1157 Load = [L || L <- Compiled, 1158 false == lists:member(L,RemoteCompiled)], 1159 remote_load_compiled([Node],Load), 1160 State#main_state{compiled=Compiled, nodes=[Node|Nodes]} 1161 end, 1162 State1#main_state{lost_nodes=Lost--[Node]}. 1163 1164%% Load a set of cover compiled modules on remote nodes, 1165%% We do it ?MAX_MODS modules at a time so that we don't 1166%% run out of memory on the cover_server node. 1167-define(MAX_MODS, 10). 1168remote_load_compiled(Nodes,Compiled) -> 1169 remote_load_compiled(Nodes, Compiled, [], 0). 1170remote_load_compiled(_Nodes, [], [], _ModNum) -> 1171 ok; 1172remote_load_compiled(Nodes, Compiled, Acc, ModNum) 1173 when Compiled == []; ModNum == ?MAX_MODS -> 1174 RemoteLoadData = get_downs_r(Acc), 1175 lists:foreach( 1176 fun(Node) -> 1177 remote_call(Node,{remote,load_compiled,RemoteLoadData}) 1178 end, 1179 Nodes), 1180 remote_load_compiled(Nodes, Compiled, [], 0); 1181remote_load_compiled(Nodes, [MF | Rest], Acc, ModNum) -> 1182 remote_load_compiled( 1183 Nodes, Rest, 1184 [spawn_job_r(fun() -> get_data_for_remote_loading(MF) end) | Acc], 1185 ModNum + 1). 1186 1187spawn_job_r(Fun) -> 1188 spawn_monitor(fun() -> exit(Fun()) end). 1189 1190get_downs_r([]) -> 1191 []; 1192get_downs_r(Mons) -> 1193 receive 1194 {'DOWN', Ref, _Type, Pid, R={_,_,_,_}} -> 1195 [R|get_downs_r(lists:delete({Pid,Ref},Mons))]; 1196 {'DOWN', Ref, _Type, Pid, Reason} = Down -> 1197 case lists:member({Pid,Ref},Mons) of 1198 true -> 1199 %% Something went really wrong - don't hang! 1200 exit(Reason); 1201 false -> 1202 %% This should be handled somewhere else 1203 self() ! Down, 1204 get_downs_r(Mons) 1205 end 1206 end. 1207 1208 1209%% Read all data needed for loading a cover compiled module on a remote node 1210%% Binary is the beam code for the module and InitialTable is the initial 1211%% data to insert in ?COVER_TABLE. 1212get_data_for_remote_loading({Module,File}) -> 1213 [{Module,Binary}] = ets:lookup(?BINARY_TABLE,Module), 1214 %%! The InitialTable list will be long if the module is big - what to do?? 1215 InitialBumps = ets:select(?COVER_TABLE,ms(Module)), 1216 InitialClauses = ets:lookup(?COVER_CLAUSE_TABLE,Module), 1217 1218 {Module,File,Binary,InitialBumps ++ InitialClauses}. 1219 1220%% Create a match spec which returns the clause info {Module,InitInfo} and 1221%% all #bump keys for the given module with 0 number of calls. 1222ms(Module) -> 1223 ets:fun2ms(fun({Key,_}) when Key#bump.module=:=Module -> 1224 {Key,0} 1225 end). 1226 1227%% Unload modules on remote nodes 1228remote_unload(Nodes,UnloadedModules) -> 1229 lists:foreach( 1230 fun(Node) -> 1231 remote_call(Node,{remote,unload,UnloadedModules}) 1232 end, 1233 Nodes). 1234 1235%% Reset one or all modules on remote nodes 1236remote_reset(Module,Nodes) -> 1237 lists:foreach( 1238 fun(Node) -> 1239 remote_call(Node,{remote,reset,Module}) 1240 end, 1241 Nodes). 1242 1243%% Collect data from remote nodes - used for analyse or stop(Node) 1244remote_collect(Modules,Nodes,Stop) -> 1245 _ = pmap( 1246 fun(Node) -> 1247 ?SPAWN_DBG(remote_collect, 1248 {Modules, Nodes, Stop}), 1249 do_collection(Node, Modules, Stop) 1250 end, Nodes), 1251 ok. 1252 1253do_collection(Node, Module, Stop) -> 1254 CollectorPid = spawn(fun collector_proc/0), 1255 case remote_call(Node,{remote,collect,Module,CollectorPid, self()}) of 1256 {error,node_dead} -> 1257 CollectorPid ! done, 1258 ok; 1259 ok when Stop -> 1260 remote_call(Node,{remote,stop}); 1261 ok -> 1262 ok 1263 end. 1264 1265%% Process which receives chunks of data from remote nodes - either when 1266%% analysing or when stopping cover on the remote nodes. 1267collector_proc() -> 1268 ?SPAWN_DBG(collector_proc, []), 1269 receive 1270 {chunk,Chunk,From} -> 1271 insert_in_collection_table(Chunk), 1272 From ! continue, 1273 collector_proc(); 1274 done -> 1275 ok 1276 end. 1277 1278insert_in_collection_table([{Key,Val}|Chunk]) -> 1279 insert_in_collection_table(Key,Val), 1280 insert_in_collection_table(Chunk); 1281insert_in_collection_table([]) -> 1282 ok. 1283 1284insert_in_collection_table(Key,Val) -> 1285 case ets:member(?COLLECTION_TABLE,Key) of 1286 true -> 1287 _ = ets:update_counter(?COLLECTION_TABLE, Key,Val), 1288 ok; 1289 false -> 1290 %% Make sure that there are no race conditions from ets:member 1291 case ets:insert_new(?COLLECTION_TABLE,{Key,Val}) of 1292 false -> 1293 insert_in_collection_table(Key,Val); 1294 _ -> 1295 ok 1296 end 1297 end. 1298 1299 1300remove_myself([Node|Nodes],Acc) when Node=:=node() -> 1301 remove_myself(Nodes,Acc); 1302remove_myself([Node|Nodes],Acc) -> 1303 remove_myself(Nodes,[Node|Acc]); 1304remove_myself([],Acc) -> 1305 Acc. 1306 1307%%%--Handling of modules state data-------------------------------------- 1308 1309analyse_info(_Module,[]) -> 1310 ok; 1311analyse_info(Module,Imported) -> 1312 imported_info("Analysis",Module,Imported). 1313 1314export_info(_Module,[]) -> 1315 ok; 1316export_info(_Module,_Imported) -> 1317 %% Do not print that the export includes imported modules 1318 ok. 1319 1320export_info([]) -> 1321 ok; 1322export_info(_Imported) -> 1323 %% Do not print that the export includes imported modules 1324 ok. 1325 1326get_all_importfiles([{_M,_F,ImportFiles}|Imported],Acc) -> 1327 NewAcc = do_get_all_importfiles(ImportFiles,Acc), 1328 get_all_importfiles(Imported,NewAcc); 1329get_all_importfiles([],Acc) -> 1330 Acc. 1331 1332do_get_all_importfiles([ImportFile|ImportFiles],Acc) -> 1333 case lists:member(ImportFile,Acc) of 1334 true -> 1335 do_get_all_importfiles(ImportFiles,Acc); 1336 false -> 1337 do_get_all_importfiles(ImportFiles,[ImportFile|Acc]) 1338 end; 1339do_get_all_importfiles([],Acc) -> 1340 Acc. 1341 1342imported_info(Text,Module,Imported) -> 1343 case lists:keysearch(Module,1,Imported) of 1344 {value,{Module,_File,ImportFiles}} -> 1345 io:format("~ts includes data from imported files\n~tp\n", 1346 [Text,ImportFiles]); 1347 false -> 1348 ok 1349 end. 1350 1351 1352 1353add_imported(Module, File, ImportFile, Imported) -> 1354 add_imported(Module, File, filename:absname(ImportFile), Imported, []). 1355 1356add_imported(M, F1, ImportFile, [{M,_F2,ImportFiles}|Imported], Acc) -> 1357 case lists:member(ImportFile,ImportFiles) of 1358 true -> 1359 io:fwrite("WARNING: Module ~w already imported from ~tp~n" 1360 "Not importing again!~n",[M,ImportFile]), 1361 dont_import; 1362 false -> 1363 NewEntry = {M, F1, [ImportFile | ImportFiles]}, 1364 {ok, lists:reverse([NewEntry | Acc]) ++ Imported} 1365 end; 1366add_imported(M, F, ImportFile, [H|Imported], Acc) -> 1367 add_imported(M, F, ImportFile, Imported, [H|Acc]); 1368add_imported(M, F, ImportFile, [], Acc) -> 1369 {ok, lists:reverse([{M, F, [ImportFile]} | Acc])}. 1370 1371%% Removes a module from the list of imported modules and writes a warning 1372%% This is done when a module is compiled. 1373remove_imported(Module,Imported) -> 1374 case lists:keysearch(Module,1,Imported) of 1375 {value,{Module,_,ImportFiles}} -> 1376 io:fwrite("WARNING: Deleting data for module ~w imported from~n" 1377 "~tp~n",[Module,ImportFiles]), 1378 lists:keydelete(Module,1,Imported); 1379 false -> 1380 Imported 1381 end. 1382 1383%% Adds information to the list of compiled modules, preserving time order 1384%% and without adding duplicate entries. 1385add_compiled(Module, File1, [{Module,_File2}|Compiled]) -> 1386 [{Module,File1}|Compiled]; 1387add_compiled(Module, File, [H|Compiled]) -> 1388 [H|add_compiled(Module, File, Compiled)]; 1389add_compiled(Module, File, []) -> 1390 [{Module,File}]. 1391 1392are_loaded([Module|Modules], State, Loaded, Imported, Error) -> 1393 try is_loaded(Module,State) of 1394 {loaded,File} -> 1395 are_loaded(Modules, State, [{Module,File}|Loaded], Imported, Error); 1396 {imported,File,_} -> 1397 are_loaded(Modules, State, Loaded, [{Module,File}|Imported], Error) 1398 catch throw:_ -> 1399 are_loaded(Modules, State, Loaded, Imported, 1400 [{not_cover_compiled,Module}|Error]) 1401 end; 1402are_loaded([], _State, Loaded, Imported, Error) -> 1403 {Loaded, Imported, Error}. 1404 1405is_loaded(Module, State) -> 1406 case get_file(Module, State#main_state.compiled) of 1407 {ok, File} -> 1408 case code:which(Module) of 1409 ?TAG -> {loaded, File}; 1410 _ -> throw(unloaded) 1411 end; 1412 false -> 1413 case get_file(Module,State#main_state.imported) of 1414 {ok,File,ImportFiles} -> 1415 {imported, File, ImportFiles}; 1416 false -> 1417 throw(not_loaded) 1418 end 1419 end. 1420 1421get_file(Module, [{Module, File}|_T]) -> 1422 {ok, File}; 1423get_file(Module, [{Module, File, ImportFiles}|_T]) -> 1424 {ok, File, ImportFiles}; 1425get_file(Module, [_H|T]) -> 1426 get_file(Module, T); 1427get_file(_Module, []) -> 1428 false. 1429 1430get_beam_file(Module,?TAG,Compiled) -> 1431 {value,{Module,File}} = lists:keysearch(Module,1,Compiled), 1432 case filename:extension(File) of 1433 ".erl" -> {error,no_beam}; 1434 ".beam" -> {ok,File} 1435 end; 1436get_beam_file(_Module,BeamFile,_Compiled) -> 1437 {ok,BeamFile}. 1438 1439get_modules(Compiled) -> 1440 lists:map(fun({Module, _File}) -> Module end, Compiled). 1441 1442update_compiled([Module|Modules], [{Module,_File}|Compiled]) -> 1443 update_compiled(Modules, Compiled); 1444update_compiled(Modules, [H|Compiled]) -> 1445 [H|update_compiled(Modules, Compiled)]; 1446update_compiled(_Modules, []) -> 1447 []. 1448 1449%% Get all compiled modules which are still loaded, and possibly an 1450%% updated version of the Compiled list. 1451get_compiled_still_loaded(Nodes,Compiled0) -> 1452 %% Find all Cover compiled modules which are still loaded 1453 CompiledModules = get_modules(Compiled0), 1454 LoadedModules = lists:filter(fun(Module) -> 1455 case code:which(Module) of 1456 ?TAG -> true; 1457 _ -> false 1458 end 1459 end, 1460 CompiledModules), 1461 1462 %% If some Cover compiled modules have been unloaded, update the database. 1463 UnloadedModules = CompiledModules--LoadedModules, 1464 Compiled = 1465 case UnloadedModules of 1466 [] -> 1467 Compiled0; 1468 _ -> 1469 lists:foreach(fun(Module) -> do_clear(Module) end, 1470 UnloadedModules), 1471 remote_unload(Nodes,UnloadedModules), 1472 update_compiled(UnloadedModules, Compiled0) 1473 end, 1474 {LoadedModules,Compiled}. 1475 1476 1477%%%--Compilation--------------------------------------------------------- 1478 1479do_compile_beams(ModsAndFiles, State) -> 1480 Result0 = pmap(fun({ok,Module,File}) -> 1481 do_compile_beam(Module,File,State); 1482 (Error) -> 1483 Error 1484 end, 1485 ModsAndFiles), 1486 Compiled = [{M,F} || {ok,M,F} <- Result0], 1487 remote_load_compiled(State#main_state.nodes,Compiled), 1488 fix_state_and_result(Result0,State,[]). 1489 1490do_compile_beam(Module,BeamFile0,State) -> 1491 case get_beam_file(Module,BeamFile0,State#main_state.compiled) of 1492 {ok,BeamFile} -> 1493 UserOptions = get_compile_options(Module,BeamFile), 1494 case do_compile_beam1(Module,BeamFile,UserOptions) of 1495 {ok, Module} -> 1496 {ok,Module,BeamFile}; 1497 error -> 1498 {error, BeamFile}; 1499 {error,Reason} -> % no abstract code or no 'file' attribute 1500 {error, {Reason, BeamFile}} 1501 end; 1502 {error,no_beam} -> 1503 %% The module has first been compiled from .erl, and now 1504 %% someone tries to compile it from .beam 1505 {error,{already_cover_compiled,no_beam_found,Module}} 1506 end. 1507 1508fix_state_and_result([{ok,Module,BeamFile}|Rest],State,Acc) -> 1509 Compiled = add_compiled(Module,BeamFile,State#main_state.compiled), 1510 Imported = remove_imported(Module,State#main_state.imported), 1511 NewState = State#main_state{compiled=Compiled,imported=Imported}, 1512 fix_state_and_result(Rest,NewState,[{ok,Module}|Acc]); 1513fix_state_and_result([Error|Rest],State,Acc) -> 1514 fix_state_and_result(Rest,State,[Error|Acc]); 1515fix_state_and_result([],State,Acc) -> 1516 {lists:reverse(Acc),State}. 1517 1518 1519do_compile(Files, Options, State) -> 1520 Result0 = pmap(fun(File) -> 1521 do_compile(File, Options) 1522 end, 1523 Files), 1524 Compiled = [{M,F} || {ok,M,F} <- Result0], 1525 remote_load_compiled(State#main_state.nodes,Compiled), 1526 fix_state_and_result(Result0,State,[]). 1527 1528do_compile(File, Options) -> 1529 case do_compile1(File, Options) of 1530 {ok, Module} -> 1531 {ok,Module,File}; 1532 error -> 1533 {error,File} 1534 end. 1535 1536%% do_compile1(File, Options) -> {ok,Module} | error 1537do_compile1(File, UserOptions) -> 1538 Options = [debug_info,binary,report_errors,report_warnings] ++ UserOptions, 1539 case compile:file(File, Options) of 1540 {ok, Module, Binary} -> 1541 do_compile_beam1(Module,Binary,UserOptions); 1542 error -> 1543 error 1544 end. 1545 1546%% Beam is a binary or a .beam file name 1547do_compile_beam1(Module,Beam,UserOptions) -> 1548 %% Clear database 1549 do_clear(Module), 1550 1551 %% Extract the abstract format and insert calls to bump/6 at 1552 %% every executable line and, as a side effect, initiate 1553 %% the database 1554 1555 case get_abstract_code(Module, Beam) of 1556 no_abstract_code=E -> 1557 {error,E}; 1558 encrypted_abstract_code=E -> 1559 {error,E}; 1560 {raw_abstract_v1,Code} -> 1561 Forms0 = epp:interpret_file_attribute(Code), 1562 case find_main_filename(Forms0) of 1563 {ok,MainFile} -> 1564 do_compile_beam2(Module,Beam,UserOptions,Forms0,MainFile); 1565 Error -> 1566 Error 1567 end; 1568 {_VSN,_Code} -> 1569 %% Wrong version of abstract code. Just report that there 1570 %% is no abstract code. 1571 {error,no_abstract_code} 1572 end. 1573 1574get_abstract_code(Module, Beam) -> 1575 case beam_lib:chunks(Beam, [abstract_code]) of 1576 {ok, {Module, [{abstract_code, AbstractCode}]}} -> 1577 AbstractCode; 1578 {error,beam_lib,{key_missing_or_invalid,_,_}} -> 1579 encrypted_abstract_code; 1580 Error -> Error 1581 end. 1582 1583do_compile_beam2(Module,Beam,UserOptions,Forms0,MainFile) -> 1584 {Forms,Vars} = transform(Forms0, Module, MainFile), 1585 1586 %% We need to recover the source from the compilation 1587 %% info otherwise the newly compiled module will have 1588 %% source pointing to the current directory 1589 SourceInfo = get_source_info(Module, Beam), 1590 1591 %% Compile and load the result 1592 %% It's necessary to check the result of loading since it may 1593 %% fail, for example if Module resides in a sticky directory 1594 {ok, Module, Binary} = compile:forms(Forms, SourceInfo ++ UserOptions), 1595 case code:load_binary(Module, ?TAG, Binary) of 1596 {module, Module} -> 1597 1598 %% Store info about all function clauses in database 1599 InitInfo = lists:reverse(Vars#vars.init_info), 1600 ets:insert(?COVER_CLAUSE_TABLE, {Module, InitInfo}), 1601 1602 %% Store binary code so it can be loaded on remote nodes 1603 ets:insert(?BINARY_TABLE, {Module, Binary}), 1604 1605 {ok, Module}; 1606 1607 _Error -> 1608 do_clear(Module), 1609 error 1610 end. 1611 1612get_source_info(Module, Beam) -> 1613 Compile = get_compile_info(Module, Beam), 1614 case lists:keyfind(source, 1, Compile) of 1615 { source, _ } = Tuple -> [Tuple]; 1616 false -> [] 1617 end. 1618 1619get_compile_options(Module, Beam) -> 1620 Compile = get_compile_info(Module, Beam), 1621 case lists:keyfind(options, 1, Compile) of 1622 {options, Options } -> filter_options(Options); 1623 false -> [] 1624 end. 1625 1626get_compile_info(Module, Beam) -> 1627 case beam_lib:chunks(Beam, [compile_info]) of 1628 {ok, {Module, [{compile_info, Compile}]}} -> 1629 Compile; 1630 _ -> 1631 [] 1632 end. 1633 1634transform(Code, Module, MainFile) -> 1635 Vars0 = #vars{module=Module}, 1636 {ok,MungedForms,Vars} = transform_2(Code,[],Vars0,MainFile,on), 1637 {MungedForms,Vars}. 1638 1639%% Helpfunction which returns the first found file-attribute, which can 1640%% be interpreted as the name of the main erlang source file. 1641find_main_filename([{attribute,_,file,{MainFile,_}}|_]) -> 1642 {ok,MainFile}; 1643find_main_filename([_|Rest]) -> 1644 find_main_filename(Rest); 1645find_main_filename([]) -> 1646 {error, no_file_attribute}. 1647 1648 1649transform_2([Form0|Forms],MungedForms,Vars,MainFile,Switch) -> 1650 Form = expand(Form0), 1651 case munge(Form,Vars,MainFile,Switch) of 1652 ignore -> 1653 transform_2(Forms,MungedForms,Vars,MainFile,Switch); 1654 {MungedForm,Vars2,NewSwitch} -> 1655 transform_2(Forms,[MungedForm|MungedForms],Vars2,MainFile,NewSwitch) 1656 end; 1657transform_2([],MungedForms,Vars,_,_) -> 1658 {ok, lists:reverse(MungedForms), Vars}. 1659 1660%% Expand short-circuit Boolean expressions. 1661expand(Expr) -> 1662 AllVars = sets:from_list(ordsets:to_list(vars([], Expr))), 1663 {Expr1,_} = expand(Expr, AllVars, 1), 1664 Expr1. 1665 1666expand({clause,Line,Pattern,Guards,Body}, Vs, N) -> 1667 {ExpandedBody,N2} = expand(Body, Vs, N), 1668 {{clause,Line,Pattern,Guards,ExpandedBody},N2}; 1669expand({op,_Line,'andalso',ExprL,ExprR}, Vs, N) -> 1670 {ExpandedExprL,N2} = expand(ExprL, Vs, N), 1671 {ExpandedExprR,N3} = expand(ExprR, Vs, N2), 1672 Anno = element(2, ExpandedExprL), 1673 {bool_switch(ExpandedExprL, 1674 ExpandedExprR, 1675 {atom,Anno,false}, 1676 Vs, N3), 1677 N3 + 1}; 1678expand({op,_Line,'orelse',ExprL,ExprR}, Vs, N) -> 1679 {ExpandedExprL,N2} = expand(ExprL, Vs, N), 1680 {ExpandedExprR,N3} = expand(ExprR, Vs, N2), 1681 Anno = element(2, ExpandedExprL), 1682 {bool_switch(ExpandedExprL, 1683 {atom,Anno,true}, 1684 ExpandedExprR, 1685 Vs, N3), 1686 N3 + 1}; 1687expand(T, Vs, N) when is_tuple(T) -> 1688 {TL,N2} = expand(tuple_to_list(T), Vs, N), 1689 {list_to_tuple(TL),N2}; 1690expand([E|Es], Vs, N) -> 1691 {E2,N2} = expand(E, Vs, N), 1692 {Es2,N3} = expand(Es, Vs, N2), 1693 {[E2|Es2],N3}; 1694expand(T, _Vs, N) -> 1695 {T,N}. 1696 1697vars(A, {var,_,V}) when V =/= '_' -> 1698 [V|A]; 1699vars(A, T) when is_tuple(T) -> 1700 vars(A, tuple_to_list(T)); 1701vars(A, [E|Es]) -> 1702 vars(vars(A, E), Es); 1703vars(A, _T) -> 1704 A. 1705 1706bool_switch(E, T, F, AllVars, AuxVarN) -> 1707 Line = element(2, E), 1708 AuxVar = {var,Line,aux_var(AllVars, AuxVarN)}, 1709 {'case',Line,E, 1710 [{clause,Line,[{atom,Line,true}],[],[T]}, 1711 {clause,Line,[{atom,Line,false}],[],[F]}, 1712 {clause,Line,[AuxVar],[], 1713 [{call,Line, 1714 {remote,Line,{atom,Line,erlang},{atom,Line,error}}, 1715 [{tuple,Line,[{atom,Line,badarg},AuxVar]}]}]}]}. 1716 1717aux_var(Vars, N) -> 1718 Name = list_to_atom(lists:concat(['_', N])), 1719 case sets:is_element(Name, Vars) of 1720 true -> aux_var(Vars, N + 1); 1721 false -> Name 1722 end. 1723 1724%% This code traverses the abstract code, stored as the abstract_code 1725%% chunk in the BEAM file, as described in absform(3). 1726%% The switch is turned off when we encounter other files than the main file. 1727%% This way we will be able to exclude functions defined in include files. 1728munge({function,Line,Function,Arity,Clauses},Vars,_MainFile,on) -> 1729 Vars2 = Vars#vars{function=Function, 1730 arity=Arity, 1731 clause=1, 1732 lines=[], 1733 no_bump_lines=[], 1734 depth=1}, 1735 {MungedClauses, Vars3} = munge_clauses(Clauses, Vars2), 1736 {{function,Line,Function,Arity,MungedClauses},Vars3,on}; 1737munge(Form={attribute,_,file,{MainFile,_}},Vars,MainFile,_Switch) -> 1738 {Form,Vars,on}; % Switch on tranformation! 1739munge(Form={attribute,_,file,{_InclFile,_}},Vars,_MainFile,_Switch) -> 1740 {Form,Vars,off}; % Switch off transformation! 1741munge({attribute,_,compile,{parse_transform,_}},_Vars,_MainFile,_Switch) -> 1742 %% Don't want to run parse transforms more than once. 1743 ignore; 1744munge(Form,Vars,_MainFile,Switch) -> % Other attributes and skipped includes. 1745 {Form,Vars,Switch}. 1746 1747munge_clauses(Clauses, Vars) -> 1748 munge_clauses(Clauses, Vars, Vars#vars.lines, []). 1749 1750munge_clauses([Clause|Clauses], Vars, Lines, MClauses) -> 1751 {clause,Line,Pattern,Guards,Body} = Clause, 1752 {MungedGuards, _Vars} = munge_exprs(Guards, Vars#vars{is_guard=true},[]), 1753 1754 case Vars#vars.depth of 1755 1 -> % function clause 1756 {MungedBody, Vars2} = munge_body(Body, Vars#vars{depth=2}), 1757 ClauseInfo = {Vars2#vars.module, 1758 Vars2#vars.function, 1759 Vars2#vars.arity, 1760 Vars2#vars.clause, 1761 length(Vars2#vars.lines)}, % Not used? 1762 InitInfo = [ClauseInfo | Vars2#vars.init_info], 1763 Vars3 = Vars2#vars{init_info=InitInfo, 1764 clause=(Vars2#vars.clause)+1, 1765 lines=[], 1766 no_bump_lines=[], 1767 depth=1}, 1768 NewBumps = Vars2#vars.lines, 1769 NewLines = NewBumps ++ Lines, 1770 munge_clauses(Clauses, Vars3, NewLines, 1771 [{clause,Line,Pattern,MungedGuards,MungedBody}| 1772 MClauses]); 1773 1774 2 -> % receive-, case-, if-, or try-clause 1775 Lines0 = Vars#vars.lines, 1776 {MungedBody, Vars2} = munge_body(Body, Vars), 1777 NewBumps = new_bumps(Vars2, Vars), 1778 NewLines = NewBumps ++ Lines, 1779 munge_clauses(Clauses, Vars2#vars{lines=Lines0}, 1780 NewLines, 1781 [{clause,Line,Pattern,MungedGuards,MungedBody}| 1782 MClauses]) 1783 end; 1784munge_clauses([], Vars, Lines, MungedClauses) -> 1785 {lists:reverse(MungedClauses), Vars#vars{lines = Lines}}. 1786 1787munge_body(Expr, Vars) -> 1788 munge_body(Expr, Vars, [], []). 1789 1790munge_body([Expr|Body], Vars, MungedBody, LastExprBumpLines) -> 1791 %% Here is the place to add a call to cover:bump/6! 1792 Line = erl_anno:line(element(2, Expr)), 1793 Lines = Vars#vars.lines, 1794 case lists:member(Line,Lines) of 1795 true -> % already a bump at this line 1796 {MungedExpr, Vars2} = munge_expr(Expr, Vars), 1797 NewBumps = new_bumps(Vars2, Vars), 1798 NoBumpLines = [Line|Vars#vars.no_bump_lines], 1799 Vars3 = Vars2#vars{no_bump_lines = NoBumpLines}, 1800 MungedBody1 = 1801 maybe_fix_last_expr(MungedBody, Vars3, LastExprBumpLines), 1802 MungedExprs1 = [MungedExpr|MungedBody1], 1803 munge_body(Body, Vars3, MungedExprs1, NewBumps); 1804 false -> 1805 ets:insert(?COVER_TABLE, {#bump{module = Vars#vars.module, 1806 function = Vars#vars.function, 1807 arity = Vars#vars.arity, 1808 clause = Vars#vars.clause, 1809 line = Line}, 1810 0}), 1811 Bump = bump_call(Vars, Line), 1812% Bump = {call, 0, {remote, 0, {atom,0,cover}, {atom,0,bump}}, 1813% [{atom, 0, Vars#vars.module}, 1814% {atom, 0, Vars#vars.function}, 1815% {integer, 0, Vars#vars.arity}, 1816% {integer, 0, Vars#vars.clause}, 1817% {integer, 0, Line}]}, 1818 Lines2 = [Line|Lines], 1819 {MungedExpr, Vars2} = munge_expr(Expr, Vars#vars{lines=Lines2}), 1820 NewBumps = new_bumps(Vars2, Vars), 1821 NoBumpLines = subtract(Vars2#vars.no_bump_lines, NewBumps), 1822 Vars3 = Vars2#vars{no_bump_lines = NoBumpLines}, 1823 MungedBody1 = 1824 maybe_fix_last_expr(MungedBody, Vars3, LastExprBumpLines), 1825 MungedExprs1 = [MungedExpr,Bump|MungedBody1], 1826 munge_body(Body, Vars3, MungedExprs1, NewBumps) 1827 end; 1828munge_body([], Vars, MungedBody, _LastExprBumpLines) -> 1829 {lists:reverse(MungedBody), Vars}. 1830 1831%%% Fix last expression (OTP-8188). A typical example: 1832%%% 1833%%% 3: case X of 1834%%% 4: 1 -> a; % Bump line 5 after "a" has been evaluated! 1835%%% 5: 2 -> b; 3 -> c end, F() 1836%%% 1837%%% Line 5 wasn't bumped just before "F()" since it was already bumped 1838%%% before "b" (and before "c") (one mustn't bump a line more than 1839%%% once in a single "evaluation"). The expression "case X ... end" is 1840%%% now traversed again ("fixed"), this time adding bumps of line 5 1841%%% where appropriate, in this case when X matches 1. 1842%%% 1843%%% This doesn't solve all problems with expressions on the same line, 1844%%% though. 'case' and 'try' are tricky. An example: 1845%%% 1846%%% 7: case case X of 1 -> foo(); % ? 1847%%% 8: 2 -> bar() end of a -> 1; 1848%%% 9: b -> 2 end. 1849%%% 1850%%% If X matches 1 and foo() evaluates to a then line 8 should be 1851%%% bumped, but not if foo() evaluates to b. In other words, line 8 1852%%% cannot be bumped after "foo()" on line 7, so one has to bump line 1853%%% 8 before "begin 1 end". But if X matches 2 and bar evaluates to a 1854%%% then line 8 would be bumped twice (there has to be a bump before 1855%%% "bar()". It is like one would have to have two copies of the inner 1856%%% clauses, one for each outer clause. Maybe the munging should be 1857%%% done on some of the compiler's "lower level" format. 1858%%% 1859%%% 'fun' is also problematic since a bump inside the body "shadows" 1860%%% the rest of the line. 1861 1862maybe_fix_last_expr(MungedExprs, Vars, LastExprBumpLines) -> 1863 case last_expr_needs_fixing(Vars, LastExprBumpLines) of 1864 {yes, Line} -> 1865 fix_last_expr(MungedExprs, Line, Vars); 1866 no -> 1867 MungedExprs 1868 end. 1869 1870last_expr_needs_fixing(Vars, LastExprBumpLines) -> 1871 case common_elems(Vars#vars.no_bump_lines, LastExprBumpLines) of 1872 [Line] -> {yes, Line}; 1873 _ -> no 1874 end. 1875 1876fix_last_expr([MungedExpr|MungedExprs], Line, Vars) -> 1877 %% No need to update ?COVER_TABLE. 1878 Bump = bump_call(Vars, Line), 1879 [fix_expr(MungedExpr, Line, Bump)|MungedExprs]. 1880 1881fix_expr({'if',L,Clauses}, Line, Bump) -> 1882 FixedClauses = fix_clauses(Clauses, Line, Bump), 1883 {'if',L,FixedClauses}; 1884fix_expr({'case',L,Expr,Clauses}, Line, Bump) -> 1885 FixedExpr = fix_expr(Expr, Line, Bump), 1886 FixedClauses = fix_clauses(Clauses, Line, Bump), 1887 {'case',L,FixedExpr,FixedClauses}; 1888fix_expr({'receive',L,Clauses}, Line, Bump) -> 1889 FixedClauses = fix_clauses(Clauses, Line, Bump), 1890 {'receive',L,FixedClauses}; 1891fix_expr({'receive',L,Clauses,Expr,Body}, Line, Bump) -> 1892 FixedClauses = fix_clauses(Clauses, Line, Bump), 1893 FixedExpr = fix_expr(Expr, Line, Bump), 1894 FixedBody = fix_expr(Body, Line, Bump), 1895 {'receive',L,FixedClauses,FixedExpr,FixedBody}; 1896fix_expr({'try',L,Exprs,Clauses,CatchClauses,After}, Line, Bump) -> 1897 FixedExprs = fix_expr(Exprs, Line, Bump), 1898 FixedClauses = fix_clauses(Clauses, Line, Bump), 1899 FixedCatchClauses = fix_clauses(CatchClauses, Line, Bump), 1900 FixedAfter = fix_expr(After, Line, Bump), 1901 {'try',L,FixedExprs,FixedClauses,FixedCatchClauses,FixedAfter}; 1902fix_expr([E | Es], Line, Bump) -> 1903 [fix_expr(E, Line, Bump) | fix_expr(Es, Line, Bump)]; 1904fix_expr(T, Line, Bump) when is_tuple(T) -> 1905 list_to_tuple(fix_expr(tuple_to_list(T), Line, Bump)); 1906fix_expr(E, _Line, _Bump) -> 1907 E. 1908 1909fix_clauses([], _Line, _Bump) -> 1910 []; 1911fix_clauses(Cs, Line, Bump) -> 1912 case bumps_line(lists:last(Cs), Line) of 1913 true -> 1914 fix_cls(Cs, Line, Bump); 1915 false -> 1916 Cs 1917 end. 1918 1919fix_cls([], _Line, _Bump) -> 1920 []; 1921fix_cls([Cl | Cls], Line, Bump) -> 1922 case bumps_line(Cl, Line) of 1923 true -> 1924 [fix_expr(C, Line, Bump) || C <- [Cl | Cls]]; 1925 false -> 1926 {clause,CL,P,G,Body} = Cl, 1927 UniqueVarName = list_to_atom(lists:concat(["$cover$ ",Line])), 1928 A = erl_anno:new(0), 1929 V = {var,A,UniqueVarName}, 1930 [Last|Rest] = lists:reverse(Body), 1931 Body1 = lists:reverse(Rest, [{match,A,V,Last},Bump,V]), 1932 [{clause,CL,P,G,Body1} | fix_cls(Cls, Line, Bump)] 1933 end. 1934 1935bumps_line(E, L) -> 1936 try bumps_line1(E, L) catch true -> true end. 1937 1938bumps_line1({call,_,{remote,_,{atom,_,ets},{atom,_,update_counter}}, 1939 [{atom,_,?COVER_TABLE},{tuple,_,[_,_,_,_,_,{integer,_,Line}]},_]}, 1940 Line) -> 1941 throw(true); 1942bumps_line1([E | Es], Line) -> 1943 bumps_line1(E, Line), 1944 bumps_line1(Es, Line); 1945bumps_line1(T, Line) when is_tuple(T) -> 1946 bumps_line1(tuple_to_list(T), Line); 1947bumps_line1(_, _) -> 1948 false. 1949 1950%%% End of fix of last expression. 1951 1952bump_call(Vars, Line) -> 1953 A = erl_anno:new(0), 1954 {call,A,{remote,A,{atom,A,ets},{atom,A,update_counter}}, 1955 [{atom,A,?COVER_TABLE}, 1956 {tuple,A,[{atom,A,?BUMP_REC_NAME}, 1957 {atom,A,Vars#vars.module}, 1958 {atom,A,Vars#vars.function}, 1959 {integer,A,Vars#vars.arity}, 1960 {integer,A,Vars#vars.clause}, 1961 {integer,A,Line}]}, 1962 {integer,A,1}]}. 1963 1964munge_expr({match,Line,ExprL,ExprR}, Vars) -> 1965 {MungedExprL, Vars2} = munge_expr(ExprL, Vars), 1966 {MungedExprR, Vars3} = munge_expr(ExprR, Vars2), 1967 {{match,Line,MungedExprL,MungedExprR}, Vars3}; 1968munge_expr({tuple,Line,Exprs}, Vars) -> 1969 {MungedExprs, Vars2} = munge_exprs(Exprs, Vars, []), 1970 {{tuple,Line,MungedExprs}, Vars2}; 1971munge_expr({record,Line,Name,Exprs}, Vars) -> 1972 {MungedExprFields, Vars2} = munge_exprs(Exprs, Vars, []), 1973 {{record,Line,Name,MungedExprFields}, Vars2}; 1974munge_expr({record,Line,Arg,Name,Exprs}, Vars) -> 1975 {MungedArg, Vars2} = munge_expr(Arg, Vars), 1976 {MungedExprFields, Vars3} = munge_exprs(Exprs, Vars2, []), 1977 {{record,Line,MungedArg,Name,MungedExprFields}, Vars3}; 1978munge_expr({record_field,Line,ExprL,ExprR}, Vars) -> 1979 {MungedExprR, Vars2} = munge_expr(ExprR, Vars), 1980 {{record_field,Line,ExprL,MungedExprR}, Vars2}; 1981munge_expr({map,Line,Fields}, Vars) -> 1982 %% EEP 43 1983 {MungedFields, Vars2} = munge_exprs(Fields, Vars, []), 1984 {{map,Line,MungedFields}, Vars2}; 1985munge_expr({map,Line,Arg,Fields}, Vars) -> 1986 %% EEP 43 1987 {MungedArg, Vars2} = munge_expr(Arg, Vars), 1988 {MungedFields, Vars3} = munge_exprs(Fields, Vars2, []), 1989 {{map,Line,MungedArg,MungedFields}, Vars3}; 1990munge_expr({map_field_assoc,Line,Name,Value}, Vars) -> 1991 %% EEP 43 1992 {MungedName, Vars2} = munge_expr(Name, Vars), 1993 {MungedValue, Vars3} = munge_expr(Value, Vars2), 1994 {{map_field_assoc,Line,MungedName,MungedValue}, Vars3}; 1995munge_expr({map_field_exact,Line,Name,Value}, Vars) -> 1996 %% EEP 43 1997 {MungedName, Vars2} = munge_expr(Name, Vars), 1998 {MungedValue, Vars3} = munge_expr(Value, Vars2), 1999 {{map_field_exact,Line,MungedName,MungedValue}, Vars3}; 2000munge_expr({cons,Line,ExprH,ExprT}, Vars) -> 2001 {MungedExprH, Vars2} = munge_expr(ExprH, Vars), 2002 {MungedExprT, Vars3} = munge_expr(ExprT, Vars2), 2003 {{cons,Line,MungedExprH,MungedExprT}, Vars3}; 2004munge_expr({op,Line,Op,ExprL,ExprR}, Vars) -> 2005 {MungedExprL, Vars2} = munge_expr(ExprL, Vars), 2006 {MungedExprR, Vars3} = munge_expr(ExprR, Vars2), 2007 {{op,Line,Op,MungedExprL,MungedExprR}, Vars3}; 2008munge_expr({op,Line,Op,Expr}, Vars) -> 2009 {MungedExpr, Vars2} = munge_expr(Expr, Vars), 2010 {{op,Line,Op,MungedExpr}, Vars2}; 2011munge_expr({'catch',Line,Expr}, Vars) -> 2012 {MungedExpr, Vars2} = munge_expr(Expr, Vars), 2013 {{'catch',Line,MungedExpr}, Vars2}; 2014munge_expr({call,Line1,{remote,Line2,ExprM,ExprF},Exprs}, 2015 Vars) -> 2016 {MungedExprM, Vars2} = munge_expr(ExprM, Vars), 2017 {MungedExprF, Vars3} = munge_expr(ExprF, Vars2), 2018 {MungedExprs, Vars4} = munge_exprs(Exprs, Vars3, []), 2019 {{call,Line1,{remote,Line2,MungedExprM,MungedExprF},MungedExprs}, Vars4}; 2020munge_expr({call,Line,Expr,Exprs}, Vars) -> 2021 {MungedExpr, Vars2} = munge_expr(Expr, Vars), 2022 {MungedExprs, Vars3} = munge_exprs(Exprs, Vars2, []), 2023 {{call,Line,MungedExpr,MungedExprs}, Vars3}; 2024munge_expr({lc,Line,Expr,Qs}, Vars) -> 2025 {MungedExpr, Vars2} = munge_expr(?BLOCK1(Expr), Vars), 2026 {MungedQs, Vars3} = munge_qualifiers(Qs, Vars2), 2027 {{lc,Line,MungedExpr,MungedQs}, Vars3}; 2028munge_expr({bc,Line,Expr,Qs}, Vars) -> 2029 {MungedExpr,Vars2} = munge_expr(?BLOCK1(Expr), Vars), 2030 {MungedQs, Vars3} = munge_qualifiers(Qs, Vars2), 2031 {{bc,Line,MungedExpr,MungedQs}, Vars3}; 2032munge_expr({block,Line,Body}, Vars) -> 2033 {MungedBody, Vars2} = munge_body(Body, Vars), 2034 {{block,Line,MungedBody}, Vars2}; 2035munge_expr({'if',Line,Clauses}, Vars) -> 2036 {MungedClauses,Vars2} = munge_clauses(Clauses, Vars), 2037 {{'if',Line,MungedClauses}, Vars2}; 2038munge_expr({'case',Line,Expr,Clauses}, Vars) -> 2039 {MungedExpr,Vars2} = munge_expr(Expr, Vars), 2040 {MungedClauses,Vars3} = munge_clauses(Clauses, Vars2), 2041 {{'case',Line,MungedExpr,MungedClauses}, Vars3}; 2042munge_expr({'receive',Line,Clauses}, Vars) -> 2043 {MungedClauses,Vars2} = munge_clauses(Clauses, Vars), 2044 {{'receive',Line,MungedClauses}, Vars2}; 2045munge_expr({'receive',Line,Clauses,Expr,Body}, Vars) -> 2046 {MungedExpr, Vars1} = munge_expr(Expr, Vars), 2047 {MungedClauses,Vars2} = munge_clauses(Clauses, Vars1), 2048 {MungedBody,Vars3} = 2049 munge_body(Body, Vars2#vars{lines = Vars1#vars.lines}), 2050 Vars4 = Vars3#vars{lines = Vars2#vars.lines ++ new_bumps(Vars3, Vars2)}, 2051 {{'receive',Line,MungedClauses,MungedExpr,MungedBody}, Vars4}; 2052munge_expr({'try',Line,Body,Clauses,CatchClauses,After}, Vars) -> 2053 {MungedBody, Vars1} = munge_body(Body, Vars), 2054 {MungedClauses, Vars2} = munge_clauses(Clauses, Vars1), 2055 {MungedCatchClauses, Vars3} = munge_clauses(CatchClauses, Vars2), 2056 {MungedAfter, Vars4} = munge_body(After, Vars3), 2057 {{'try',Line,MungedBody,MungedClauses,MungedCatchClauses,MungedAfter}, 2058 Vars4}; 2059munge_expr({'fun',Line,{clauses,Clauses}}, Vars) -> 2060 {MungedClauses,Vars2}=munge_clauses(Clauses, Vars), 2061 {{'fun',Line,{clauses,MungedClauses}}, Vars2}; 2062munge_expr({named_fun,Line,Name,Clauses}, Vars) -> 2063 {MungedClauses,Vars2}=munge_clauses(Clauses, Vars), 2064 {{named_fun,Line,Name,MungedClauses}, Vars2}; 2065munge_expr({bin,Line,BinElements}, Vars) -> 2066 {MungedBinElements,Vars2} = munge_exprs(BinElements, Vars, []), 2067 {{bin,Line,MungedBinElements}, Vars2}; 2068munge_expr({bin_element,Line,Value,Size,TypeSpecifierList}, Vars) -> 2069 {MungedValue,Vars2} = munge_expr(Value, Vars), 2070 {MungedSize,Vars3} = munge_expr(Size, Vars2), 2071 {{bin_element,Line,MungedValue,MungedSize,TypeSpecifierList},Vars3}; 2072munge_expr(Form, Vars) -> 2073 {Form, Vars}. 2074 2075munge_exprs([Expr|Exprs], Vars, MungedExprs) when Vars#vars.is_guard=:=true, 2076 is_list(Expr) -> 2077 {MungedExpr, _Vars} = munge_exprs(Expr, Vars, []), 2078 munge_exprs(Exprs, Vars, [MungedExpr|MungedExprs]); 2079munge_exprs([Expr|Exprs], Vars, MungedExprs) -> 2080 {MungedExpr, Vars2} = munge_expr(Expr, Vars), 2081 munge_exprs(Exprs, Vars2, [MungedExpr|MungedExprs]); 2082munge_exprs([], Vars, MungedExprs) -> 2083 {lists:reverse(MungedExprs), Vars}. 2084 2085%% Every qualifier is decorated with a counter. 2086munge_qualifiers(Qualifiers, Vars) -> 2087 munge_qs(Qualifiers, Vars, []). 2088 2089munge_qs([{generate,Line,Pattern,Expr}|Qs], Vars, MQs) -> 2090 L = element(2, Expr), 2091 {MungedExpr, Vars2} = munge_expr(Expr, Vars), 2092 munge_qs1(Qs, L, {generate,Line,Pattern,MungedExpr}, Vars, Vars2, MQs); 2093munge_qs([{b_generate,Line,Pattern,Expr}|Qs], Vars, MQs) -> 2094 L = element(2, Expr), 2095 {MExpr, Vars2} = munge_expr(Expr, Vars), 2096 munge_qs1(Qs, L, {b_generate,Line,Pattern,MExpr}, Vars, Vars2, MQs); 2097munge_qs([Expr|Qs], Vars, MQs) -> 2098 L = element(2, Expr), 2099 {MungedExpr, Vars2} = munge_expr(Expr, Vars), 2100 munge_qs1(Qs, L, MungedExpr, Vars, Vars2, MQs); 2101munge_qs([], Vars, MQs) -> 2102 {lists:reverse(MQs), Vars}. 2103 2104munge_qs1(Qs, Line, NQ, Vars, Vars2, MQs) -> 2105 case new_bumps(Vars2, Vars) of 2106 [_] -> 2107 munge_qs(Qs, Vars2, [NQ | MQs]); 2108 _ -> 2109 {MungedTrue, Vars3} = munge_expr(?BLOCK({atom,Line,true}), Vars2), 2110 munge_qs(Qs, Vars3, [NQ, MungedTrue | MQs]) 2111 end. 2112 2113new_bumps(#vars{lines = New}, #vars{lines = Old}) -> 2114 subtract(New, Old). 2115 2116subtract(L1, L2) -> 2117 [E || E <- L1, not lists:member(E, L2)]. 2118 2119common_elems(L1, L2) -> 2120 [E || E <- L1, lists:member(E, L2)]. 2121 2122%%%--Analysis------------------------------------------------------------ 2123 2124%% Collect data for all modules 2125collect(Nodes) -> 2126 %% local node 2127 AllClauses = ets:tab2list(?COVER_CLAUSE_TABLE), 2128 Mon1 = spawn_monitor(fun() -> pmap(fun move_modules/1,AllClauses) end), 2129 2130 %% remote nodes 2131 Mon2 = spawn_monitor(fun() -> remote_collect('_',Nodes,false) end), 2132 get_downs([Mon1,Mon2]). 2133 2134%% Collect data for a list of modules 2135collect(Modules,Nodes) -> 2136 MS = [{{'$1','_'},[{'==','$1',M}],['$_']} || M <- Modules], 2137 Clauses = ets:select(?COVER_CLAUSE_TABLE,MS), 2138 Mon1 = spawn_monitor(fun() -> pmap(fun move_modules/1,Clauses) end), 2139 2140 %% remote nodes 2141 Mon2 = spawn_monitor(fun() -> remote_collect('_',Nodes,false) end), 2142 get_downs([Mon1,Mon2]). 2143 2144%% Collect data for one module 2145collect(Module,Clauses,Nodes) -> 2146 %% local node 2147 move_modules({Module,Clauses}), 2148 2149 %% remote nodes 2150 remote_collect([Module],Nodes,false). 2151 2152 2153%% When analysing, the data from the local ?COVER_TABLE is moved to the 2154%% ?COLLECTION_TABLE. Resetting data in ?COVER_TABLE 2155move_modules({Module,Clauses}) -> 2156 ets:insert(?COLLECTION_CLAUSE_TABLE,{Module,Clauses}), 2157 Pattern = {#bump{module=Module, _='_'}, '_'}, 2158 MatchSpec = [{Pattern,[],['$_']}], 2159 Match = ets:select(?COVER_TABLE,MatchSpec,?CHUNK_SIZE), 2160 do_move_module(Match). 2161 2162do_move_module({Bumps,Continuation}) -> 2163 lists:foreach(fun({Key,Val}) -> 2164 ets:insert(?COVER_TABLE, {Key,0}), 2165 insert_in_collection_table(Key,Val) 2166 end, 2167 Bumps), 2168 do_move_module(ets:select(Continuation)); 2169do_move_module('$end_of_table') -> 2170 ok. 2171 2172%% Given a .beam file, find the .erl file. Look first in same directory as 2173%% the .beam file, then in ../src, then in compile info. 2174find_source(Module, File0) -> 2175 try 2176 Root = filename:rootname(File0, ".beam"), 2177 Root == File0 andalso throw(File0), %% not .beam 2178 %% Look for .erl in pwd. 2179 File = Root ++ ".erl", 2180 throw_file(File), 2181 %% Not in pwd: look in ../src. 2182 BeamDir = filename:dirname(File), 2183 Base = filename:basename(File), 2184 throw_file(filename:join([BeamDir, "..", "src", Base])), 2185 %% Not in ../src: look for source path in compile info, but 2186 %% first look relative the beam directory. 2187 Info = 2188 try lists:keyfind(source, 1, Module:module_info(compile)) 2189 catch error:undef -> 2190 %% The module might have been imported 2191 %% and the beam not available 2192 throw({beam, File0}) 2193 end, 2194 false == Info andalso throw({beam, File0}), %% stripped 2195 {source, SrcFile} = Info, 2196 throw_file(splice(BeamDir, SrcFile)), %% below ../src 2197 throw_file(SrcFile), %% or absolute 2198 %% No success means that source is either not under ../src or 2199 %% its relative path differs from that of compile info. (For 2200 %% example, compiled under src/x but installed under src/y.) 2201 %% An option to specify an arbitrary source path explicitly is 2202 %% probably a better solution than either more heuristics or a 2203 %% potentially slow filesystem search. 2204 {beam, File0} 2205 catch 2206 Path -> Path 2207 end. 2208 2209throw_file(Path) -> 2210 false /= Path andalso filelib:is_file(Path) andalso throw(Path). 2211 2212%% Splice the tail of a source path, starting from the last "src" 2213%% component, onto the parent of a beam directory, or return false if 2214%% no "src" component is found. 2215%% 2216%% Eg. splice("/path/to/app-1.0/ebin", "/compiled/path/to/app/src/x/y.erl") 2217%% --> "/path/to/app-1.0/ebin/../src/x/y.erl" 2218%% 2219%% This handles the case of source in subdirectories of ../src with 2220%% beams that have moved since compilation. 2221%% 2222splice(BeamDir, SrcFile) -> 2223 case lists:splitwith(fun(C) -> C /= "src" end, revsplit(SrcFile)) of 2224 {T, [_|_]} -> %% found src component 2225 filename:join([BeamDir, "..", "src" | lists:reverse(T)]); 2226 {_, []} -> %% or not 2227 false 2228 end. 2229 2230revsplit(Path) -> 2231 lists:reverse(filename:split(Path)). 2232 2233analyse_list(Modules, Analysis, Level, State) -> 2234 {LoadedMF, ImportedMF, Error} = are_loaded(Modules, State, [], [], []), 2235 Loaded = [M || {M,_} <- LoadedMF], 2236 Imported = [M || {M,_} <- ImportedMF], 2237 collect(Loaded, State#main_state.nodes), 2238 MS = [{{'$1','_'},[{'==','$1',M}],['$_']} || M <- Loaded ++ Imported], 2239 AllClauses = ets:select(?COLLECTION_CLAUSE_TABLE,MS), 2240 Fun = fun({Module,Clauses}) -> 2241 do_analyse(Module, Analysis, Level, Clauses) 2242 end, 2243 {result, lists:flatten(pmap(Fun, AllClauses)), Error}. 2244 2245analyse_all(Analysis, Level, State) -> 2246 collect(State#main_state.nodes), 2247 AllClauses = ets:tab2list(?COLLECTION_CLAUSE_TABLE), 2248 Fun = fun({Module,Clauses}) -> 2249 do_analyse(Module, Analysis, Level, Clauses) 2250 end, 2251 {result, lists:flatten(pmap(Fun, AllClauses)), []}. 2252 2253do_parallel_analysis(Module, Analysis, Level, Loaded, From, State) -> 2254 analyse_info(Module,State#main_state.imported), 2255 C = case Loaded of 2256 {loaded, _File} -> 2257 [{Module,Clauses}] = 2258 ets:lookup(?COVER_CLAUSE_TABLE,Module), 2259 collect(Module,Clauses,State#main_state.nodes), 2260 Clauses; 2261 _ -> 2262 [{Module,Clauses}] = 2263 ets:lookup(?COLLECTION_CLAUSE_TABLE,Module), 2264 Clauses 2265 end, 2266 R = do_analyse(Module, Analysis, Level, C), 2267 reply(From, {ok,R}). 2268 2269%% do_analyse(Module, Analysis, Level, Clauses)-> {ok,Answer} | {error,Error} 2270%% Clauses = [{Module,Function,Arity,Clause,Lines}] 2271do_analyse(Module, Analysis, line, _Clauses) -> 2272 Pattern = {#bump{module=Module},'_'}, 2273 Bumps = ets:match_object(?COLLECTION_TABLE, Pattern), 2274 Fun = case Analysis of 2275 coverage -> 2276 fun({#bump{line=L}, 0}) -> 2277 {{Module,L}, {0,1}}; 2278 ({#bump{line=L}, _N}) -> 2279 {{Module,L}, {1,0}} 2280 end; 2281 calls -> 2282 fun({#bump{line=L}, N}) -> 2283 {{Module,L}, N} 2284 end 2285 end, 2286 lists:keysort(1, lists:map(Fun, Bumps)); 2287do_analyse(Module, Analysis, clause, _Clauses) -> 2288 Pattern = {#bump{module=Module},'_'}, 2289 Bumps = lists:keysort(1,ets:match_object(?COLLECTION_TABLE, Pattern)), 2290 analyse_clause(Analysis,Bumps); 2291do_analyse(Module, Analysis, function, Clauses) -> 2292 ClauseResult = do_analyse(Module, Analysis, clause, Clauses), 2293 merge_clauses(ClauseResult, merge_fun(Analysis)); 2294do_analyse(Module, Analysis, module, Clauses) -> 2295 FunctionResult = do_analyse(Module, Analysis, function, Clauses), 2296 Result = merge_functions(FunctionResult, merge_fun(Analysis)), 2297 {Module,Result}. 2298 2299analyse_clause(_,[]) -> 2300 []; 2301analyse_clause(coverage, 2302 [{#bump{module=M,function=F,arity=A,clause=C},_}|_]=Bumps) -> 2303 analyse_clause_cov(Bumps,{M,F,A,C},0,0,[]); 2304analyse_clause(calls,Bumps) -> 2305 analyse_clause_calls(Bumps,{x,x,x,x},[]). 2306 2307analyse_clause_cov([{#bump{module=M,function=F,arity=A,clause=C},N}|Bumps], 2308 {M,F,A,C}=Clause,Ls,NotCov,Acc) -> 2309 analyse_clause_cov(Bumps,Clause,Ls+1,if N==0->NotCov+1; true->NotCov end,Acc); 2310analyse_clause_cov([{#bump{module=M1,function=F1,arity=A1,clause=C1},_}|_]=Bumps, 2311 Clause,Ls,NotCov,Acc) -> 2312 analyse_clause_cov(Bumps,{M1,F1,A1,C1},0,0,[{Clause,{Ls-NotCov,NotCov}}|Acc]); 2313analyse_clause_cov([],Clause,Ls,NotCov,Acc) -> 2314 lists:reverse(Acc,[{Clause,{Ls-NotCov,NotCov}}]). 2315 2316analyse_clause_calls([{#bump{module=M,function=F,arity=A,clause=C},_}|Bumps], 2317 {M,F,A,C}=Clause,Acc) -> 2318 analyse_clause_calls(Bumps,Clause,Acc); 2319analyse_clause_calls([{#bump{module=M1,function=F1,arity=A1,clause=C1},N}|Bumps], 2320 _Clause,Acc) -> 2321 analyse_clause_calls(Bumps,{M1,F1,A1,C1},[{{M1,F1,A1,C1},N}|Acc]); 2322analyse_clause_calls([],_Clause,Acc) -> 2323 lists:reverse(Acc). 2324 2325merge_fun(coverage) -> 2326 fun({Cov1,NotCov1}, {Cov2,NotCov2}) -> 2327 {Cov1+Cov2, NotCov1+NotCov2} 2328 end; 2329merge_fun(calls) -> 2330 fun(Calls1, Calls2) -> 2331 Calls1+Calls2 2332 end. 2333 2334merge_clauses(Clauses, MFun) -> merge_clauses(Clauses, MFun, []). 2335merge_clauses([{{M,F,A,_C1},R1},{{M,F,A,C2},R2}|Clauses], MFun, Result) -> 2336 merge_clauses([{{M,F,A,C2},MFun(R1,R2)}|Clauses], MFun, Result); 2337merge_clauses([{{M,F,A,_C},R}|Clauses], MFun, Result) -> 2338 merge_clauses(Clauses, MFun, [{{M,F,A},R}|Result]); 2339merge_clauses([], _Fun, Result) -> 2340 lists:reverse(Result). 2341 2342merge_functions([{_MFA,R}|Functions], MFun) -> 2343 merge_functions(Functions, MFun, R); 2344merge_functions([],_MFun) -> % There are no clauses. 2345 {0,0}. % No function can be covered or notcov. 2346 2347merge_functions([{_MFA,R}|Functions], MFun, Result) -> 2348 merge_functions(Functions, MFun, MFun(Result, R)); 2349merge_functions([], _MFun, Result) -> 2350 Result. 2351 2352analyse_list_to_file(Modules, Opts, State) -> 2353 {LoadedMF, ImportedMF, Error} = are_loaded(Modules, State, [], [], []), 2354 collect([M || {M,_} <- LoadedMF], State#main_state.nodes), 2355 OutDir = proplists:get_value(outdir,Opts), 2356 HTML = lists:member(html,Opts), 2357 Fun = fun({Module,File}) -> 2358 OutFile = outfilename(OutDir,Module,HTML), 2359 do_analyse_to_file(Module,File,OutFile,HTML,State) 2360 end, 2361 {Ok,Error1} = split_ok_error(pmap(Fun, LoadedMF++ImportedMF),[],[]), 2362 {result,Ok,Error ++ Error1}. 2363 2364analyse_all_to_file(Opts, State) -> 2365 collect(State#main_state.nodes), 2366 AllModules = get_all_modules(State), 2367 OutDir = proplists:get_value(outdir,Opts), 2368 HTML = lists:member(html,Opts), 2369 Fun = fun({Module,File}) -> 2370 OutFile = outfilename(OutDir,Module,HTML), 2371 do_analyse_to_file(Module,File,OutFile,HTML,State) 2372 end, 2373 {Ok,Error} = split_ok_error(pmap(Fun, AllModules),[],[]), 2374 {result,Ok,Error}. 2375 2376get_all_modules(State) -> 2377 get_all_modules(State#main_state.compiled ++ State#main_state.imported,[]). 2378get_all_modules([{Module,File}|Rest],Acc) -> 2379 get_all_modules(Rest,[{Module,File}|Acc]); 2380get_all_modules([{Module,File,_}|Rest],Acc) -> 2381 case lists:keymember(Module,1,Acc) of 2382 true -> get_all_modules(Rest,Acc); 2383 false -> get_all_modules(Rest,[{Module,File}|Acc]) 2384 end; 2385get_all_modules([],Acc) -> 2386 Acc. 2387 2388split_ok_error([{ok,R}|Result],Ok,Error) -> 2389 split_ok_error(Result,[R|Ok],Error); 2390split_ok_error([{error,R}|Result],Ok,Error) -> 2391 split_ok_error(Result,Ok,[R|Error]); 2392split_ok_error([],Ok,Error) -> 2393 {Ok,Error}. 2394 2395do_parallel_analysis_to_file(Module, Opts, Loaded, From, State) -> 2396 File = case Loaded of 2397 {loaded, File0} -> 2398 [{Module,Clauses}] = 2399 ets:lookup(?COVER_CLAUSE_TABLE,Module), 2400 collect(Module, Clauses, 2401 State#main_state.nodes), 2402 File0; 2403 {imported, File0, _} -> 2404 File0 2405 end, 2406 HTML = lists:member(html,Opts), 2407 OutFile = 2408 case proplists:get_value(outfile,Opts) of 2409 undefined -> 2410 outfilename(proplists:get_value(outdir,Opts),Module,HTML); 2411 F -> 2412 F 2413 end, 2414 reply(From, do_analyse_to_file(Module,File,OutFile,HTML,State)). 2415 2416do_analyse_to_file(Module,File,OutFile,HTML,State) -> 2417 case find_source(Module, File) of 2418 {beam,_BeamFile} -> 2419 {error,{no_source_code_found,Module}}; 2420 ErlFile -> 2421 analyse_info(Module,State#main_state.imported), 2422 do_analyse_to_file1(Module,OutFile,ErlFile,HTML) 2423 end. 2424 2425%% do_analyse_to_file1(Module,OutFile,ErlFile) -> {ok,OutFile} | {error,Error} 2426%% Module = atom() 2427%% OutFile = ErlFile = string() 2428do_analyse_to_file1(Module, OutFile, ErlFile, HTML) -> 2429 case file:open(ErlFile, [read,raw,read_ahead]) of 2430 {ok, InFd} -> 2431 case file:open(OutFile, [write,raw,delayed_write]) of 2432 {ok, OutFd} -> 2433 Enc = encoding(ErlFile), 2434 if HTML -> 2435 Header = create_header(OutFile, Enc), 2436 H1Bin = unicode:characters_to_binary(Header,Enc,Enc), 2437 ok = file:write(OutFd,H1Bin); 2438 true -> ok 2439 end, 2440 2441 %% Write some initial information to the output file 2442 {{Y,Mo,D},{H,Mi,S}} = calendar:local_time(), 2443 Timestamp = 2444 io_lib:format("~p-~s-~s at ~s:~s:~s", 2445 [Y, 2446 string:pad(integer_to_list(Mo), 2, leading, $0), 2447 string:pad(integer_to_list(D), 2, leading, $0), 2448 string:pad(integer_to_list(H), 2, leading, $0), 2449 string:pad(integer_to_list(Mi), 2, leading, $0), 2450 string:pad(integer_to_list(S), 2, leading, $0)]), 2451 2452 OutFileInfo = 2453 if HTML -> 2454 create_footer(ErlFile, Timestamp); 2455 true -> 2456 ["File generated from ",ErlFile," by COVER ", 2457 Timestamp, "\n\n", 2458 "**************************************" 2459 "**************************************" 2460 "\n\n"] 2461 end, 2462 2463 H2Bin = unicode:characters_to_binary(OutFileInfo,Enc,Enc), 2464 ok = file:write(OutFd, H2Bin), 2465 2466 Pattern = {#bump{module=Module,line='$1',_='_'},'$2'}, 2467 MS = [{Pattern,[{is_integer,'$1'},{'>','$1',0}],[{{'$1','$2'}}]}], 2468 CovLines0 = 2469 lists:keysort(1, ets:select(?COLLECTION_TABLE, MS)), 2470 CovLines = merge_dup_lines(CovLines0), 2471 print_lines(Module, CovLines, InFd, OutFd, 1, HTML), 2472 2473 if HTML -> 2474 ok = file:write(OutFd, close_html()); 2475 true -> ok 2476 end, 2477 2478 ok = file:close(OutFd), 2479 ok = file:close(InFd), 2480 2481 {ok, OutFile}; 2482 2483 {error, Reason} -> 2484 {error, {file, OutFile, Reason}} 2485 end; 2486 2487 {error, Reason} -> 2488 {error, {file, ErlFile, Reason}} 2489 end. 2490 2491merge_dup_lines(CovLines) -> 2492 merge_dup_lines(CovLines, []). 2493merge_dup_lines([{L, N}|T], [{L, NAcc}|TAcc]) -> 2494 merge_dup_lines(T, [{L, NAcc + N}|TAcc]); 2495merge_dup_lines([{L, N}|T], Acc) -> 2496 merge_dup_lines(T, [{L, N}|Acc]); 2497merge_dup_lines([], Acc) -> 2498 lists:reverse(Acc). 2499 2500print_lines(Module, CovLines, InFd, OutFd, L, HTML) -> 2501 case file:read_line(InFd) of 2502 eof -> 2503 ignore; 2504 {ok,RawLine} -> 2505 Line = escape_lt_and_gt(RawLine,HTML), 2506 case CovLines of 2507 [{L,N}|CovLines1] -> 2508 if N=:=0, HTML=:=true -> 2509 MissedLine = table_row("miss", Line, L, N), 2510 ok = file:write(OutFd, MissedLine); 2511 HTML=:=true -> 2512 HitLine = table_row("hit", Line, L, N), 2513 ok = file:write(OutFd, HitLine); 2514 N < 1000000 -> 2515 Str = string:pad(integer_to_list(N), 6, leading, $\s), 2516 ok = file:write(OutFd, [Str,fill1(),Line]); 2517 N < 10000000 -> 2518 Str = integer_to_list(N), 2519 ok = file:write(OutFd, [Str,fill2(),Line]); 2520 true -> 2521 Str = integer_to_list(N), 2522 ok = file:write(OutFd, [Str,fill3(),Line]) 2523 end, 2524 print_lines(Module, CovLines1, InFd, OutFd, L+1, HTML); 2525 _ -> %Including comment lines 2526 NonCoveredContent = 2527 if HTML -> table_row(Line, L); 2528 true -> [tab(),Line] 2529 end, 2530 ok = file:write(OutFd, NonCoveredContent), 2531 print_lines(Module, CovLines, InFd, OutFd, L+1, HTML) 2532 end 2533 end. 2534 2535tab() -> " | ". 2536fill1() -> "..| ". 2537fill2() -> ".| ". 2538fill3() -> "| ". 2539 2540%% HTML sections 2541create_header(OutFile, Enc) -> 2542 ["<!doctype html>\n" 2543 "<html>\n" 2544 "<head>\n" 2545 "<meta charset=\"",html_encoding(Enc),"\">\n" 2546 "<title>",OutFile,"</title>\n" 2547 "<style>"] ++ 2548 read_stylesheet() ++ 2549 ["</style>\n", 2550 "</head>\n" 2551 "<body>\n" 2552 "<h1><code>",OutFile,"</code></h1>\n"]. 2553 2554create_footer(ErlFile, Timestamp) -> 2555 ["<footer><p>File generated from <code>",ErlFile, 2556 "</code> by <a href=\"http://erlang.org/doc/man/cover.html\">cover</a> at ", 2557 Timestamp,"</p></footer>\n<table>\n<tbody>\n"]. 2558 2559close_html() -> 2560 ["</tbody>\n", 2561 "<thead>\n", 2562 "<tr>\n", 2563 "<th>Line</th>\n", 2564 "<th>Hits</th>\n", 2565 "<th>Source</th>\n", 2566 "</tr>\n", 2567 "</thead>\n", 2568 "</table>\n", 2569 "</body>\n" 2570 "</html>\n"]. 2571 2572table_row(CssClass, Line, L, N) -> 2573 ["<tr class=\"",CssClass,"\">\n", table_data(Line, L, N)]. 2574table_row(Line, L) -> 2575 ["<tr>\n", table_data(Line, L, "")]. 2576 2577table_data(Line, L, N) -> 2578 LineNoNL = Line -- "\n", 2579 ["<td class=\"line\" id=\"L",integer_to_list(L),"\">", 2580 "<a href=\"#L",integer_to_list(L),"\">", 2581 integer_to_list(L), 2582 "</a></td>\n", 2583 "<td class=\"hits\">",maybe_integer_to_list(N),"</td>\n", 2584 "<td class=\"source\"><code>",LineNoNL,"</code></td>\n</tr>\n"]. 2585 2586maybe_integer_to_list(0) -> "<pre style=\"display: inline;\">:-(</pre>"; 2587maybe_integer_to_list(N) when is_integer(N) -> integer_to_list(N); 2588maybe_integer_to_list(_) -> "". 2589 2590read_stylesheet() -> 2591 PrivDir = code:priv_dir(?TOOLS_APP), 2592 {ok, Css} = file:read_file(filename:join(PrivDir, ?STYLESHEET)), 2593 [Css]. 2594 2595%%%--Export-------------------------------------------------------------- 2596do_export(Module, OutFile, From, State) -> 2597 case file:open(OutFile,[write,binary,raw,delayed_write]) of 2598 {ok,Fd} -> 2599 Reply = 2600 case Module of 2601 '_' -> 2602 export_info(State#main_state.imported), 2603 collect(State#main_state.nodes), 2604 do_export_table(State#main_state.compiled, 2605 State#main_state.imported, 2606 Fd); 2607 _ -> 2608 export_info(Module,State#main_state.imported), 2609 try is_loaded(Module, State) of 2610 {loaded, File} -> 2611 [{Module,Clauses}] = 2612 ets:lookup(?COVER_CLAUSE_TABLE,Module), 2613 collect(Module, Clauses, 2614 State#main_state.nodes), 2615 do_export_table([{Module,File}],[],Fd); 2616 {imported, File, ImportFiles} -> 2617 %% don't know if I should allow this - 2618 %% export a module which is only imported 2619 Imported = [{Module,File,ImportFiles}], 2620 do_export_table([],Imported,Fd) 2621 catch throw:_ -> 2622 {error,{not_cover_compiled,Module}} 2623 end 2624 end, 2625 ok = file:close(Fd), 2626 reply(From, Reply); 2627 {error,Reason} -> 2628 reply(From, {error, {cant_open_file,OutFile,Reason}}) 2629 2630 end. 2631 2632do_export_table(Compiled, Imported, Fd) -> 2633 ModList = merge(Imported,Compiled), 2634 write_module_data(ModList,Fd). 2635 2636merge([{Module,File,_ImportFiles}|Imported],ModuleList) -> 2637 case lists:keymember(Module,1,ModuleList) of 2638 true -> 2639 merge(Imported,ModuleList); 2640 false -> 2641 merge(Imported,[{Module,File}|ModuleList]) 2642 end; 2643merge([],ModuleList) -> 2644 ModuleList. 2645 2646write_module_data([{Module,File}|ModList],Fd) -> 2647 write({file,Module,File},Fd), 2648 [Clauses] = ets:lookup(?COLLECTION_CLAUSE_TABLE,Module), 2649 write(Clauses,Fd), 2650 ModuleData = ets:match_object(?COLLECTION_TABLE,{#bump{module=Module},'_'}), 2651 do_write_module_data(ModuleData,Fd), 2652 write_module_data(ModList,Fd); 2653write_module_data([],_Fd) -> 2654 ok. 2655 2656do_write_module_data([H|T],Fd) -> 2657 write(H,Fd), 2658 do_write_module_data(T,Fd); 2659do_write_module_data([],_Fd) -> 2660 ok. 2661 2662write(Element,Fd) -> 2663 Bin = term_to_binary(Element,[compressed]), 2664 case byte_size(Bin) of 2665 Size when Size > 255 -> 2666 SizeBin = term_to_binary({'$size',Size}), 2667 ok = file:write(Fd, <<(byte_size(SizeBin)):8,SizeBin/binary,Bin/binary>>); 2668 Size -> 2669 ok = file:write(Fd,<<Size:8,Bin/binary>>) 2670 end, 2671 ok. 2672 2673%%%--Import-------------------------------------------------------------- 2674do_import_to_table(Fd,ImportFile,Imported) -> 2675 do_import_to_table(Fd,ImportFile,Imported,[]). 2676do_import_to_table(Fd,ImportFile,Imported,DontImport) -> 2677 case get_term(Fd) of 2678 {file,Module,File} -> 2679 case add_imported(Module, File, ImportFile, Imported) of 2680 {ok,NewImported} -> 2681 do_import_to_table(Fd,ImportFile,NewImported,DontImport); 2682 dont_import -> 2683 do_import_to_table(Fd,ImportFile,Imported, 2684 [Module|DontImport]) 2685 end; 2686 {Key=#bump{module=Module},Val} -> 2687 case lists:member(Module,DontImport) of 2688 false -> 2689 insert_in_collection_table(Key,Val); 2690 true -> 2691 ok 2692 end, 2693 do_import_to_table(Fd,ImportFile,Imported,DontImport); 2694 {Module,Clauses} -> 2695 case lists:member(Module,DontImport) of 2696 false -> 2697 ets:insert(?COLLECTION_CLAUSE_TABLE,{Module,Clauses}); 2698 true -> 2699 ok 2700 end, 2701 do_import_to_table(Fd,ImportFile,Imported,DontImport); 2702 eof -> 2703 Imported 2704 end. 2705 2706 2707get_term(Fd) -> 2708 case file:read(Fd,1) of 2709 {ok,<<Size1:8>>} -> 2710 {ok,Bin1} = file:read(Fd,Size1), 2711 case binary_to_term(Bin1) of 2712 {'$size',Size2} -> 2713 {ok,Bin2} = file:read(Fd,Size2), 2714 binary_to_term(Bin2); 2715 Term -> 2716 Term 2717 end; 2718 eof -> 2719 eof 2720 end. 2721 2722%%%--Reset--------------------------------------------------------------- 2723 2724%% Reset main node and all remote nodes 2725do_reset_main_node(Module,Nodes) -> 2726 do_reset(Module), 2727 do_reset_collection_table(Module), 2728 remote_reset(Module,Nodes). 2729 2730do_reset_collection_table(Module) -> 2731 ets:delete(?COLLECTION_CLAUSE_TABLE,Module), 2732 ets:match_delete(?COLLECTION_TABLE, {#bump{module=Module},'_'}). 2733 2734%% do_reset(Module) -> ok 2735%% The reset is done on ?CHUNK_SIZE number of bumps to avoid building 2736%% long lists in the case of very large modules 2737do_reset(Module) -> 2738 Pattern = {#bump{module=Module, _='_'}, '$1'}, 2739 MatchSpec = [{Pattern,[{'=/=','$1',0}],['$_']}], 2740 Match = ets:select(?COVER_TABLE,MatchSpec,?CHUNK_SIZE), 2741 do_reset2(Match). 2742 2743do_reset2({Bumps,Continuation}) -> 2744 lists:foreach(fun({Bump,_N}) -> 2745 ets:insert(?COVER_TABLE, {Bump,0}) 2746 end, 2747 Bumps), 2748 do_reset2(ets:select(Continuation)); 2749do_reset2('$end_of_table') -> 2750 ok. 2751 2752do_clear(Module) -> 2753 ets:match_delete(?COVER_CLAUSE_TABLE, {Module,'_'}), 2754 ets:match_delete(?COVER_TABLE, {#bump{module=Module},'_'}), 2755 case lists:member(?COLLECTION_TABLE, ets:all()) of 2756 true -> 2757 %% We're on the main node 2758 ets:match_delete(?COLLECTION_TABLE, {#bump{module=Module},'_'}); 2759 false -> 2760 ok 2761 end. 2762 2763not_loaded(Module, unloaded, State) -> 2764 do_clear(Module), 2765 remote_unload(State#main_state.nodes,[Module]), 2766 Compiled = update_compiled([Module], 2767 State#main_state.compiled), 2768 State#main_state{ compiled = Compiled }; 2769not_loaded(_Module,_Else, State) -> 2770 State. 2771 2772 2773 2774%%%--Div----------------------------------------------------------------- 2775 2776escape_lt_and_gt(Rawline,HTML) when HTML =/= true -> 2777 Rawline; 2778escape_lt_and_gt(Rawline,_HTML) -> 2779 escape_lt_and_gt1(Rawline,[]). 2780 2781escape_lt_and_gt1([$<|T],Acc) -> 2782 escape_lt_and_gt1(T,[$;,$t,$l,$&|Acc]); 2783escape_lt_and_gt1([$>|T],Acc) -> 2784 escape_lt_and_gt1(T,[$;,$t,$g,$&|Acc]); 2785escape_lt_and_gt1([$&|T],Acc) -> 2786 escape_lt_and_gt1(T,[$;,$p,$m,$a,$&|Acc]); 2787escape_lt_and_gt1([],Acc) -> 2788 lists:reverse(Acc); 2789escape_lt_and_gt1([H|T],Acc) -> 2790 escape_lt_and_gt1(T,[H|Acc]). 2791 2792%%%--Internal functions for parallelization------------------------------ 2793pmap(Fun,List) -> 2794 NTot = length(List), 2795 NProcs = erlang:system_info(schedulers) * 2, 2796 NPerProc = (NTot div NProcs) + 1, 2797 Mons = pmap_spawn(Fun,NPerProc,List,[]), 2798 pmap_collect(Mons,[]). 2799 2800pmap_spawn(_,_,[],Mons) -> 2801 Mons; 2802pmap_spawn(Fun,NPerProc,List,Mons) -> 2803 {L1,L2} = if length(List)>=NPerProc -> lists:split(NPerProc,List); 2804 true -> {List,[]} % last chunk 2805 end, 2806 Mon = 2807 spawn_monitor( 2808 fun() -> 2809 exit({pmap_done,lists:map(Fun,L1)}) 2810 end), 2811 pmap_spawn(Fun,NPerProc,L2,[Mon|Mons]). 2812 2813pmap_collect([],Acc) -> 2814 lists:append(Acc); 2815pmap_collect(Mons,Acc) -> 2816 receive 2817 {'DOWN', Ref, process, Pid, {pmap_done,Result}} -> 2818 pmap_collect(lists:delete({Pid,Ref},Mons),[Result|Acc]); 2819 {'DOWN', Ref, process, Pid, Reason} = Down -> 2820 case lists:member({Pid,Ref},Mons) of 2821 true -> 2822 %% Something went really wrong - don't hang! 2823 exit(Reason); 2824 false -> 2825 %% This should be handled somewhere else 2826 self() ! Down, 2827 pmap_collect(Mons,Acc) 2828 end 2829 end. 2830 2831%%%----------------------------------------------------------------- 2832%%% Decide which encoding to use when analyzing to file. 2833%%% The target file contains the file path, so if either the file name 2834%%% encoding or the encoding of the source file is utf8, then we need 2835%%% to use utf8. 2836encoding(File) -> 2837 case file:native_name_encoding() of 2838 latin1 -> 2839 case epp:read_encoding(File) of 2840 none -> 2841 epp:default_encoding(); 2842 E -> 2843 E 2844 end; 2845 utf8 -> 2846 utf8 2847 end. 2848 2849html_encoding(latin1) -> 2850 "iso-8859-1"; 2851html_encoding(utf8) -> 2852 "utf-8". 2853