📄 cover.erl
字号:
end.%% Load a set of cover compiled modules on remote nodesremote_load_compiled(Nodes,Compiled0) -> Compiled = lists:map(fun get_data_for_remote_loading/1,Compiled0), lists:foreach( fun(Node) -> remote_call(Node,{remote,load_compiled,Compiled}) end, Nodes).%% Read all data needed for loading a cover compiled module on a remote node%% Binary is the beam code for the module and InitialTable is the initial%% data to insert in ?COVER_TABLE.get_data_for_remote_loading({Module,File}) -> [{Module,Binary}] = ets:lookup(?BINARY_TABLE,Module), %%! The InitialTable list will be long if the module is big - what to do?? InitialTable = ets:select(?COVER_TABLE,ms(Module)), {Module,File,Binary,InitialTable}.%% Create a match spec which returns the clause info {Module,InitInfo} and %% all #bump keys for the given module with 0 number of calls.ms(Module) -> ets:fun2ms(fun({Module,InitInfo}) -> {Module,InitInfo}; ({Key,_}) when is_record(Key,bump),Key#bump.module=:=Module -> {Key,0} end).%% Unload modules on remote nodesremote_unload(Nodes,UnloadedModules) -> lists:foreach( fun(Node) -> remote_call(Node,{remote,unload,UnloadedModules}) end, Nodes). %% Reset one or all modules on remote nodesremote_reset(Module,Nodes) -> lists:foreach( fun(Node) -> remote_call(Node,{remote,reset,Module}) end, Nodes). %% Collect data from remote nodes - used for analyse or stop(Node)remote_collect(Module,Nodes,Stop) -> CollectorPid = spawn(fun() -> collector_proc(length(Nodes)) end), lists:foreach( fun(Node) -> remote_call(Node,{remote,collect,Module,CollectorPid}), if Stop -> remote_call(Node,{remote,stop}); true -> ok end end, Nodes).%% Process which receives chunks of data from remote nodes - either when%% analysing or when stopping cover on the remote nodes.collector_proc(0) -> ok;collector_proc(N) -> receive {chunk,Chunk} -> insert_in_collection_table(Chunk), collector_proc(N); done -> collector_proc(N-1) end.insert_in_collection_table([{Key,Val}|Chunk]) -> insert_in_collection_table(Key,Val), insert_in_collection_table(Chunk);insert_in_collection_table([]) -> ok.insert_in_collection_table(Key,Val) -> case ets:member(?COLLECTION_TABLE,Key) of true -> ets:update_counter(?COLLECTION_TABLE, Key,Val); false -> ets:insert(?COLLECTION_TABLE,{Key,Val}) end.remove_myself([Node|Nodes],Acc) when Node=:=node() -> remove_myself(Nodes,Acc);remove_myself([Node|Nodes],Acc) -> remove_myself(Nodes,[Node|Acc]);remove_myself([],Acc) -> Acc. %%%--Handling of modules state data--------------------------------------analyse_info(_Module,[]) -> ok;analyse_info(Module,Imported) -> imported_info("Analysis",Module,Imported).export_info(_Module,[]) -> ok;export_info(Module,Imported) -> imported_info("Export",Module,Imported).export_info([]) -> ok;export_info(Imported) -> AllImportFiles = get_all_importfiles(Imported,[]), io:format("Export includes data from imported files\n~p\n",[AllImportFiles]).get_all_importfiles([{_M,_F,ImportFiles}|Imported],Acc) -> NewAcc = do_get_all_importfiles(ImportFiles,Acc), get_all_importfiles(Imported,NewAcc);get_all_importfiles([],Acc) -> Acc.do_get_all_importfiles([ImportFile|ImportFiles],Acc) -> case lists:member(ImportFile,Acc) of true -> do_get_all_importfiles(ImportFiles,Acc); false -> do_get_all_importfiles(ImportFiles,[ImportFile|Acc]) end;do_get_all_importfiles([],Acc) -> Acc.imported_info(Text,Module,Imported) -> case lists:keysearch(Module,1,Imported) of {value,{Module,_File,ImportFiles}} -> io:format("~s includes data from imported files\n~p\n", [Text,ImportFiles]); false -> ok end. add_imported(Module, File, ImportFile, Imported) -> add_imported(Module, File, filename:absname(ImportFile), Imported, []).add_imported(M, F1, ImportFile, [{M,_F2,ImportFiles}|Imported], Acc) -> case lists:member(ImportFile,ImportFiles) of true -> io:fwrite("WARNING: Module ~w already imported from ~p~n" "Not importing again!~n",[M,ImportFile]), dont_import; false -> NewEntry = {M, F1, [ImportFile | ImportFiles]}, {ok, reverse([NewEntry | Acc]) ++ Imported} end;add_imported(M, F, ImportFile, [H|Imported], Acc) -> add_imported(M, F, ImportFile, Imported, [H|Acc]);add_imported(M, F, ImportFile, [], Acc) -> {ok, reverse([{M, F, [ImportFile]} | Acc])}. %% Removes a module from the list of imported modules and writes a warning%% This is done when a module is compiled.remove_imported(Module,Imported) -> case lists:keysearch(Module,1,Imported) of {value,{Module,_,ImportFiles}} -> io:fwrite("WARNING: Deleting data for module ~w imported from~n" "~p~n",[Module,ImportFiles]), lists:keydelete(Module,1,Imported); false -> Imported end.%% Adds information to the list of compiled modules, preserving time order%% and without adding duplicate entries.add_compiled(Module, File1, [{Module,_File2}|Compiled]) -> [{Module,File1}|Compiled];add_compiled(Module, File, [H|Compiled]) -> [H|add_compiled(Module, File, Compiled)];add_compiled(Module, File, []) -> [{Module,File}].is_loaded(Module, State) -> case get_file(Module, State#main_state.compiled) of {ok, File} -> case code:which(Module) of ?TAG -> {loaded, File}; _ -> unloaded end; false -> case get_file(Module,State#main_state.imported) of {ok,File,ImportFiles} -> {imported, File, ImportFiles}; false -> false end end.get_file(Module, [{Module, File}|_T]) -> {ok, File};get_file(Module, [{Module, File, ImportFiles}|_T]) -> {ok, File, ImportFiles};get_file(Module, [_H|T]) -> get_file(Module, T);get_file(_Module, []) -> false.get_beam_file(Module,?TAG,Compiled) -> {value,{Module,File}} = lists:keysearch(Module,1,Compiled), case filename:extension(File) of ".erl" -> {error,no_beam}; ".beam" -> {ok,File} end;get_beam_file(_Module,BeamFile,_Compiled) -> {ok,BeamFile}.get_modules(Compiled) -> lists:map(fun({Module, _File}) -> Module end, Compiled).update_compiled([Module|Modules], [{Module,_File}|Compiled]) -> update_compiled(Modules, Compiled);update_compiled(Modules, [H|Compiled]) -> [H|update_compiled(Modules, Compiled)];update_compiled(_Modules, []) -> [].%% Get all compiled modules which are still loaded, and possibly an%% updated version of the Compiled list.get_compiled_still_loaded(Nodes,Compiled0) -> %% Find all Cover compiled modules which are still loaded CompiledModules = get_modules(Compiled0), LoadedModules = lists:filter(fun(Module) -> case code:which(Module) of ?TAG -> true; _ -> false end end, CompiledModules), %% If some Cover compiled modules have been unloaded, update the database. UnloadedModules = CompiledModules--LoadedModules, Compiled = case UnloadedModules of [] -> Compiled0; _ -> lists:foreach(fun(Module) -> do_clear(Module) end, UnloadedModules), remote_unload(Nodes,UnloadedModules), update_compiled(UnloadedModules, Compiled0) end, {LoadedModules,Compiled}.%%%--Compilation---------------------------------------------------------%% do_compile(File, Options) -> {ok,Module} | {error,Error}do_compile(File, UserOptions) -> Options = [debug_info,binary,report_errors,report_warnings] ++ UserOptions, case compile:file(File, Options) of {ok, Module, Binary} -> do_compile_beam(Module,Binary); error -> error end.%% Beam is a binary or a .beam file namedo_compile_beam(Module,Beam) -> %% Clear database do_clear(Module), %% Extract the abstract format and insert calls to bump/6 at %% every executable line and, as a side effect, initiate %% the database case get_abstract_code(Module, Beam) of no_abstract_code=E -> {error,E}; encrypted_abstract_code=E -> {error,E}; {Vsn,Code} -> Forms0 = epp:interpret_file_attribute(Code), {Forms,Vars} = transform(Vsn, Forms0, Module, Beam), %% Compile and load the result %% It's necessary to check the result of loading since it may %% fail, for example if Module resides in a sticky directory {ok, Module, Binary} = compile:forms(Forms, []), case code:load_binary(Module, ?TAG, Binary) of {module, Module} -> %% Store info about all function clauses in database InitInfo = reverse(Vars#vars.init_info), ets:insert(?COVER_TABLE, {Module, InitInfo}), %% Store binary code so it can be loaded on remote nodes ets:insert(?BINARY_TABLE, {Module, Binary}), {ok, Module}; _Error -> do_clear(Module), error end end.get_abstract_code(Module, Beam) -> case beam_lib:chunks(Beam, [abstract_code]) of {ok, {Module, [{abstract_code, AbstractCode}]}} -> AbstractCode; {error,beam_lib,{key_missing_or_invalid,_,_}} -> encrypted_abstract_code; Error -> Error end.transform(Vsn, Code, Module, Beam) when Vsn=:=abstract_v1; Vsn=:=abstract_v2 -> Vars0 = #vars{module=Module, vsn=Vsn}, MainFile=find_main_filename(Code), {ok, MungedForms,Vars} = transform_2(Code,[],Vars0,MainFile,on), %% Add module and export information to the munged forms %% Information about module_info must be removed as this function %% is added at compilation {ok, {Module, [{exports,Exports1}]}} = beam_lib:chunks(Beam, [exports]), Exports2 = lists:filter(fun(Export) -> case Export of {module_info,_} -> false; _ -> true end end, Exports1), Forms = [{attribute,1,module,Module}, {attribute,2,export,Exports2}]++ MungedForms, {Forms,Vars};transform(Vsn=raw_abstract_v1, Code, Module, _Beam) -> MainFile=find_main_filename(Code), Vars0 = #vars{module=Module, vsn=Vsn}, {ok,MungedForms,Vars} = transform_2(Code,[],Vars0,MainFile,on), {MungedForms,Vars}. %% Helpfunction which returns the first found file-attribute, which can%% be interpreted as the name of the main erlang source file.find_main_filename([{attribute,_,file,{MainFile,_}}|_]) -> MainFile;find_main_filename([_|Rest]) -> find_main_filename(Rest).transform_2([Form|Forms],MungedForms,Vars,MainFile,Switch) -> case munge(Form,Vars,MainFile,Switch) of ignore -> transform_2(Forms,MungedForms,Vars,MainFile,Switch); {MungedForm,Vars2,NewSwitch} -> transform_2(Forms,[MungedForm|MungedForms],Vars2,MainFile,NewSwitch) end;transform_2([],MungedForms,Vars,_,_) -> {ok, reverse(MungedForms), Vars}.%% This code traverses the abstract code, stored as the abstract_code%% chunk in the BEAM file, as described in absform(3) for Erlang/OTP R8B%% (Vsn=abstract_v2).%% The abstract format after preprocessing differs slightly from the abstract%% format given eg using epp:parse_form, this has been noted in comments.%% The switch is turned off when we encounter other files then the main file.%% This way we will be able to exclude functions defined in include files.munge({function,0,module_info,_Arity,_Clauses},_Vars,_MainFile,_Switch) -> ignore; % module_info will be added again when the forms are recompiledmunge(Form={function,_,'MNEMOSYNE QUERY',_,_},Vars,_MainFile,Switch) -> {Form,Vars,Switch}; % No bumps in Mnemosyne code.munge(Form={function,_,'MNEMOSYNE RULE',_,_},Vars,_MainFile,Switch) -> {Form,Vars,Switch};munge(Form={function,_,'MNEMOSYNE RECFUNDEF',_,_},Vars,_MainFile,Switch) -> {Form,Vars,Switch};munge({function,Line,Function,Arity,Clauses},Vars,_MainFile,on) -> Vars2 = Vars#vars{function=Function, arity=Arity, clause=1, lines=[], depth=1}, {MungedClauses, Vars3} = munge_clauses(Clauses, Vars2, []), {{function,Line,Function,Arity,MungedClauses},Vars3,on};munge(Form={attribute,_,file,{MainFile,_}},Vars,MainFile,_Switch) -> {Form,Vars,on}; % Switch on tranformation!munge(Form={attribute,_,file,{_InclFile,_}},Vars,_MainFile,_Switch) -> {Form,Vars,off}; % Switch off transformation!munge({attribute,_,compile,{parse_transform,_}},_Vars,_MainFile,_Switch) -> %% Don't want to run parse transforms more than once. ignore;munge(Form,Vars,_MainFile,Switch) -> % Other attributes and skipped includes. {Form,Vars,Switch}.munge_clauses([{clause,Line,Pattern,Guards,Body}|Clauses], Vars, MClauses) -> {MungedGuards, _Vars} = munge_exprs(Guards, Vars#vars{is_guard=true},[]), case Vars#vars.depth of 1 -> % function clause {MungedBody, Vars2} = munge_body(Body, Vars#vars{depth=2}, []), ClauseInfo = {Vars2#vars.module, Vars2#vars.function, Vars2#vars.arity, Vars2#vars.clause, length(Vars2#vars.lines)}, InitInfo = [ClauseInfo | Vars2#vars.init_info], Vars3 = Vars2#vars{init_info=InitInfo, clause=(Vars2#vars.clause)+1, lines=[], depth=1}, munge_clauses(Clauses, Vars3, [{clause,Line,Pattern,MungedGuards,MungedBody}| MClauses]); 2 -> % receive-, case- or if clause {MungedBody, Vars2} = munge_body(Body, Vars, []), munge_clauses(Clauses, Vars2, [{clause,Line,Pattern,MungedGuards,MungedBody}| MClauses]) end;munge_clauses([], Vars, MungedClauses) -> {reverse(MungedClauses), Vars}.munge_body([Expr|Body], Vars, MungedBody) -> %% Here is the place to add a call to cover:bump/6! Line = element(2, Expr), Lines = Vars#vars.lines, case lists:member(Line,Lines) of true -> % already a bump at this line! {MungedExpr, Vars2} = munge_expr(Expr, Vars), munge_body(Body, Vars2, [MungedExpr|MungedBody]); false -> ets:insert(?COVER_TABLE, {#bump{module = Vars#vars.module, function = Vars#vars.function, arity = Vars#vars.arity, clause = Vars#vars.clause, line = Line}, 0}), Bump={call,0,{remote,0,{atom,0,ets},{atom,0,update_counter}}, [{atom,0,?COVER_TABLE}, {tuple,0,[{atom,0,?BUMP_REC_NAME}, {atom,0,Vars#vars.module}, {atom,0,Vars#vars.function}, {integer,0,Vars#vars.arity}, {integer,0,Vars#vars.clause}, {integer,0,Line}]}, {integer,0,1}]},% Bump = {call, 0, {remote, 0, {atom,0,cover}, {atom,0,bump}},% [{atom, 0, Vars#vars.module},% {atom, 0, Vars#vars.function},% {integer, 0, Vars#vars.arity},% {integer, 0, Vars#vars.clause},% {integer, 0, Line}]}, Lines2 = [Line|Lines], {MungedExpr, Vars2} = munge_expr(Expr, Vars#vars{lines=Lines2}), munge_body(Body, Vars2, [MungedExpr,Bump|MungedBody]) end;munge_body([], Vars, MungedBody) -> {reverse(MungedBody), Vars}.munge_expr({match,Line,ExprL,ExprR}, Vars) -> {MungedExprL, Vars2} = munge_expr(ExprL, Vars), {MungedExprR, Vars3} = munge_expr(ExprR, Vars2), {{match,Line,MungedExprL,MungedExprR}, Vars3};munge_expr({tuple,Line,Exprs}, Vars) -> {MungedExprs, Vars2} = munge_exprs(Exprs, Vars, []), {{tuple,Line,MungedExprs}, Vars2};munge_expr({record,Line,Expr,Exprs}, Vars) -> %% Only for Vsn=raw_abstract_v1 {MungedExprName, Vars2} = munge_expr(Expr, Vars), {MungedExprFields, Vars3} = munge_exprs(Exprs, Vars2, []), {{record,Line,MungedExprName,MungedExprFields}, Vars3};munge_expr({record_field,Line,ExprL,ExprR}, Vars) -> %% Only for Vsn=raw_abstract_v1 {MungedExprL, Vars2} = munge_expr(ExprL, Vars), {MungedExprR, Vars3} = munge_expr(ExprR, Vars2), {{record_field,Line,MungedExprL,MungedExprR}, Vars3};munge_expr({cons,Line,ExprH,ExprT}, Vars) -> {MungedExprH, Vars2} = munge_expr(ExprH, Vars), {MungedExprT, Vars3} = munge_expr(ExprT, Vars2), {{cons,Line,MungedExprH,MungedExprT}, Vars3};munge_expr({op,Line,Op,ExprL,ExprR}, Vars) -> {MungedExprL, Vars2} = munge_expr(ExprL, Vars), {MungedExprR, Vars3} = munge_expr(ExprR, Vars2),
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -