Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
home:Ledest:erlang:26
erlang
2361-compiler-Add-support-for-coverage.patch
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File 2361-compiler-Add-support-for-coverage.patch of Package erlang
From d8ad1b0390a7678387d63f0476058420290336fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= <bjorn@erlang.org> Date: Fri, 3 Nov 2023 09:42:39 +0100 Subject: [PATCH 1/4] compiler: Add support for coverage Add the new compiler pass `sys_coverage`. It can be invoked by giving the compiler the `line_coverage`. It will emit `executable_line` BEAM instructions on each executable line. There is also an `sys_coverage:cover_transform/2` function, which is useful for `cover`. --- .gitignore | 1 + erts/emulator/beam/emu/ops.tab | 2 + .../emulator/beam/jit/arm/beam_asm_module.cpp | 3 + erts/emulator/beam/jit/arm/ops.tab | 2 + .../emulator/beam/jit/x86/beam_asm_module.cpp | 3 + erts/emulator/beam/jit/x86/ops.tab | 2 + lib/compiler/doc/src/compile.xml | 33 +- lib/compiler/src/Makefile | 1 + lib/compiler/src/beam_asm.erl | 29 +- lib/compiler/src/beam_block.erl | 1 + lib/compiler/src/beam_dict.erl | 33 +- lib/compiler/src/beam_disasm.erl | 7 + lib/compiler/src/beam_flatten.erl | 3 +- lib/compiler/src/beam_ssa.erl | 2 +- lib/compiler/src/beam_ssa_alias.erl | 2 + lib/compiler/src/beam_ssa_codegen.erl | 4 + lib/compiler/src/beam_ssa_opt.erl | 8 + lib/compiler/src/beam_ssa_pre_codegen.erl | 1 + lib/compiler/src/beam_ssa_share.erl | 31 +- lib/compiler/src/beam_validator.erl | 2 + lib/compiler/src/compile.erl | 2 + lib/compiler/src/compiler.app.src | 1 + lib/compiler/src/genop.tab | 6 + lib/compiler/src/sys_coverage.erl | 579 ++++++++++++++++++ lib/compiler/src/v3_core.erl | 4 + lib/compiler/test/Makefile | 12 +- lib/compiler/test/bs_bincomp_SUITE.erl | 2 + lib/compiler/test/test_lib.erl | 5 +- lib/stdlib/src/erl_expand_records.erl | 4 +- lib/stdlib/src/erl_lint.erl | 2 + lib/stdlib/src/erl_pp.erl | 2 + 31 files changed, 752 insertions(+), 37 deletions(-) create mode 100644 lib/compiler/src/sys_coverage.erl diff --git a/erts/emulator/beam/emu/ops.tab b/erts/emulator/beam/emu/ops.tab index 4d227cc89b..2b08dd60a9 100644 --- a/erts/emulator/beam/emu/ops.tab +++ b/erts/emulator/beam/emu/ops.tab @@ -95,6 +95,8 @@ move S X0=x==0 | line Loc => line Loc | move S X0 line n => _ line I +executable_line Line => _ + # For the JIT, the init_yregs/1 instruction allows generation of better code. # For the BEAM interpreter, though, it will probably be more efficient to # translate all uses of init_yregs/1 back to the instructions that the compiler diff --git a/erts/emulator/beam/jit/arm/beam_asm_module.cpp b/erts/emulator/beam/jit/arm/beam_asm_module.cpp index c66c10e6bf..c9eaff19d2 100644 --- a/erts/emulator/beam/jit/arm/beam_asm_module.cpp +++ b/erts/emulator/beam/jit/arm/beam_asm_module.cpp @@ -451,6 +451,9 @@ void BeamModuleAssembler::emit_func_line(const ArgWord &Loc) { void BeamModuleAssembler::emit_empty_func_line() { } +void BeamModuleAssembler::emit_executable_line(const ArgWord &Loc) { +} + /* * Here follows stubs for instructions that should never be called. */ diff --git a/erts/emulator/beam/jit/arm/ops.tab b/erts/emulator/beam/jit/arm/ops.tab index c5a7da3142..f3c99bac9f 100644 --- a/erts/emulator/beam/jit/arm/ops.tab +++ b/erts/emulator/beam/jit/arm/ops.tab @@ -86,6 +86,8 @@ func_line I line n => _ line I +executable_line I + allocate t t allocate_heap t I t diff --git a/erts/emulator/beam/jit/x86/beam_asm_module.cpp b/erts/emulator/beam/jit/x86/beam_asm_module.cpp index 7eb4e2d6be..cc4a4b7e74 100644 --- a/erts/emulator/beam/jit/x86/beam_asm_module.cpp +++ b/erts/emulator/beam/jit/x86/beam_asm_module.cpp @@ -383,6 +383,9 @@ void BeamModuleAssembler::emit_func_line(const ArgWord &Loc) { void BeamModuleAssembler::emit_empty_func_line() { } +void BeamModuleAssembler::emit_executable_line(const ArgWord &Loc) { +} + /* * Here follows stubs for instructions that should never be called. */ diff --git a/erts/emulator/beam/jit/x86/ops.tab b/erts/emulator/beam/jit/x86/ops.tab index 39b9f32ef3..b835114b2a 100644 --- a/erts/emulator/beam/jit/x86/ops.tab +++ b/erts/emulator/beam/jit/x86/ops.tab @@ -86,6 +86,8 @@ func_line I line n => _ line I +executable_line I + allocate t t allocate_heap t I t diff --git a/lib/compiler/doc/src/compile.xml b/lib/compiler/doc/src/compile.xml index a623281eb7..d7c5e34dd2 100644 --- a/lib/compiler/doc/src/compile.xml +++ b/lib/compiler/doc/src/compile.xml @@ -620,7 +620,38 @@ module.beam: module.erl \ option is internal to the compiler and can be changed or removed at any time without prior warning.</p> </item> - </taglist> + + <tag><c>line_coverage</c></tag> + <item> + <p><marker id="line_coverage"/>Instrument the compiled + code for line coverage by inserting an + <c>executable_line</c> instruction for each executable + line in the source code. By default, this instruction will + be ignored when loading the code.</p> <p>To activate the + <c>executable_line</c> instructions, the runtime system + must be started with the option <seecom + marker="erts:erl#+JPcover">+JPcover</seecom> to enable a + coverage mode. Alternatively, <seemfa + marker="kernel:code#set_coverage_mode/1">code:set_coverage_mode/1</seemfa> + can be used to set a coverage mode before + loading the code.</p> + <p>The coverage information gathered by the instrumented + code can be retrieved by calling <seemfa + marker="kernel:code#get_coverage/2">code:get_coverage(line, Module)</seemfa>.</p> + </item> + + <tag><c>force_line_counters</c></tag> + <item> + <p><marker id="force_line_counters"/> When combined with + option <c>line_coverage</c>, this module will be loaded in + the <c>line_counter</c> coverage mode, regardless of the + current <seemfa + marker="kernel:code#get_coverage_mode/0">coverage + mode</seemfa> in the runtime system. This option is used + by <seeerl marker="tools:cover">cover</seeerl> to load + cover-compiled code.</p> + </item> + </taglist> <p>If warnings are turned on (option <c>report_warnings</c> described earlier), the following options control what type of diff --git a/lib/compiler/src/Makefile b/lib/compiler/src/Makefile index 7e59241429..a33d14f2d5 100644 --- a/lib/compiler/src/Makefile +++ b/lib/compiler/src/Makefile @@ -100,6 +100,7 @@ MODULES = \ sys_core_fold_lists \ sys_core_inline \ sys_core_prepare \ + sys_coverage \ sys_messages \ sys_pre_attributes \ v3_core diff --git a/lib/compiler/src/beam_asm.erl b/lib/compiler/src/beam_asm.erl index 277c433013..0186ab3e04 100644 --- a/lib/compiler/src/beam_asm.erl +++ b/lib/compiler/src/beam_asm.erl @@ -55,6 +55,10 @@ -type module_code() :: {module(),[_],[_],[asm_function()],pos_integer()}. +%% Flags for the line table. +-define(BEAMFILE_EXECUTABLE_LINE, 1). +-define(BEAMFILE_FORCE_LINE_COUNTERS, 2). + -spec module(module_code(), [{binary(), binary()}], [{atom(),term()}], [compile:option()]) -> {'ok',binary()}. @@ -180,7 +184,7 @@ build_file(Code, Attr, Dict, NumLabels, NumFuncs, ExtraChunks0, CompileInfo, Com end, %% Create the line chunk. - LineChunk = chunk(<<"Line">>, build_line_table(Dict)), + LineChunk = chunk(<<"Line">>, build_line_table(Dict, CompilerOpts)), %% Create the type table chunk. {NumTypes, TypeTab} = beam_dict:type_table(Dict), @@ -282,8 +286,8 @@ build_attributes(Attr, Compile, MD5) -> CompileBinary = term_to_binary([{version,?COMPILER_VSN}|Compile]), {AttrBinary,CompileBinary}. -build_line_table(Dict) -> - {NumLineInstrs,NumFnames0,Fnames0,NumLines,Lines0} = +build_line_table(Dict, Options) -> + {NumLineInstrs,NumFnames0,Fnames0,NumLines,Lines0,ExecLine} = beam_dict:line_table(Dict), NumFnames = NumFnames0 - 1, [_|Fnames1] = Fnames0, @@ -292,10 +296,20 @@ build_line_table(Dict) -> Lines1 = encode_line_items(Lines0, 0), Lines = iolist_to_binary(Lines1), Ver = 0, - Bits = 0, + Bits = line_bits(ExecLine, Options), <<Ver:32,Bits:32,NumLineInstrs:32,NumLines:32,NumFnames:32, Lines/binary,Fnames/binary>>. +line_bits(ExecLine, Options) -> + case member(force_line_counters, Options) of + true -> + ?BEAMFILE_FORCE_LINE_COUNTERS bor ?BEAMFILE_EXECUTABLE_LINE; + false when ExecLine =:= true -> + ?BEAMFILE_EXECUTABLE_LINE; + false -> + 0 + end. + %% encode_line_items([{FnameIndex,Line}], PrevFnameIndex) %% Encode the line items compactly. Tag the FnameIndex with %% an 'a' tag (atom) and only include it when it has changed. @@ -347,9 +361,12 @@ bif_type(_, 2) -> bif2. make_op({'%',_}, Dict) -> {[],Dict}; -make_op({line,Location}, Dict0) -> - {Index,Dict} = beam_dict:line(Location, Dict0), +make_op({line=Op,Location}, Dict0) -> + {Index,Dict} = beam_dict:line(Location, Dict0, Op), encode_op(line, [Index], Dict); +make_op({executable_line=Op,Location}, Dict0) -> + {Index,Dict} = beam_dict:line(Location, Dict0, Op), + encode_op(executable_line, [Index], Dict); make_op({bif, Bif, {f,_}, [], Dest}, Dict) -> %% BIFs without arguments cannot fail. encode_op(bif0, [{extfunc, erlang, Bif, 0}, Dest], Dict); diff --git a/lib/compiler/src/beam_block.erl b/lib/compiler/src/beam_block.erl index b6d424d09c..9cd6d93783 100644 --- a/lib/compiler/src/beam_block.erl +++ b/lib/compiler/src/beam_block.erl @@ -169,6 +169,7 @@ collect({put_map,{f,0},Op,S,D,R,{list,Puts}}) -> {set,[D],[S|Puts],{alloc,R,{put_map,Op,{f,0}}}}; collect({fmove,S,D}) -> {set,[D],[S],fmove}; collect({fconv,S,D}) -> {set,[D],[S],fconv}; +collect({executable_line,Line}) -> {set,[],[],{executable_line,Line}}; collect(_) -> error. %% embed_lines([Instruction]) -> [Instruction] diff --git a/lib/compiler/src/beam_dict.erl b/lib/compiler/src/beam_dict.erl index 36bdb01df5..dbc74c1518 100644 --- a/lib/compiler/src/beam_dict.erl +++ b/lib/compiler/src/beam_dict.erl @@ -23,7 +23,7 @@ -export([new/0,opcode/2,highest_opcode/1, atom/2,local/4,export/4,import/4, - string/2,lambda/3,literal/2,line/2,fname/2,type/2, + string/2,lambda/3,literal/2,line/3,fname/2,type/2, atom_table/1,local_table/1,export_table/1,import_table/1, string_table/1,lambda_table/1,literal_table/1, line_table/1,type_table/1]). @@ -58,6 +58,7 @@ fnames = #{} :: fname_tab(), lines = #{} :: line_tab(), num_lines = 0 :: non_neg_integer(), %Number of line instructions + exec_line = false :: boolean(), next_import = 0 :: non_neg_integer(), string_offset = 0 :: non_neg_integer(), next_literal = 0 :: non_neg_integer(), @@ -201,23 +202,29 @@ literal1(Key, #asm{literals=Tab0,next_literal=NextIndex}=Dict) -> %% Returns the index for a line instruction (adding information %% to the location information table). --spec line(list(), bdict()) -> {non_neg_integer(), bdict()}. +-spec line(list(), bdict(), 'line' | 'executable_line') -> + {non_neg_integer(), bdict()}. -line([], #asm{num_lines=N}=Dict) -> +line([], #asm{num_lines=N}=Dict, Instr) when is_atom(Instr) -> %% No location available. Return the special pre-defined %% index 0. {0,Dict#asm{num_lines=N+1}}; -line([{location,Name,Line}|_], #asm{lines=Lines,num_lines=N}=Dict0) -> +line([{location,Name,Line}|_], #asm{lines=Lines,num_lines=N, + exec_line=ExecLine0}=Dict0, Instr) + when is_atom(Instr) -> {FnameIndex,Dict1} = fname(Name, Dict0), Key = {FnameIndex,Line}, + ExecLine = ExecLine0 or (Instr =:= executable_line), case Lines of - #{Key := Index} -> {Index,Dict1#asm{num_lines=N+1}}; + #{Key := Index} -> + {Index,Dict1#asm{num_lines=N+1,exec_line=ExecLine}}; _ -> - Index = maps:size(Lines) + 1, - {Index, Dict1#asm{lines=Lines#{Key=>Index},num_lines=N+1}} + Index = map_size(Lines) + 1, + {Index, Dict1#asm{lines=Lines#{Key=>Index},num_lines=N+1, + exec_line=ExecLine}} end; -line([_|T], #asm{}=Dict) -> - line(T, Dict). +line([_|T], #asm{}=Dict, Instr) -> + line(T, Dict, Instr). -spec fname(nonempty_string(), bdict()) -> {non_neg_integer(), bdict()}. @@ -337,16 +344,18 @@ build_type_table([], Acc) -> -spec line_table(bdict()) -> {non_neg_integer(), %Number of line instructions. non_neg_integer(),[string()], - non_neg_integer(),[{non_neg_integer(),non_neg_integer()}]}. + non_neg_integer(),[{non_neg_integer(),non_neg_integer()}], + boolean()}. -line_table(#asm{fnames=Fnames0,lines=Lines0,num_lines=NumLineInstrs}) -> +line_table(#asm{fnames=Fnames0,lines=Lines0, + num_lines=NumLineInstrs,exec_line=ExecLine}) -> NumFnames = maps:size(Fnames0), Fnames1 = lists:keysort(2, maps:to_list(Fnames0)), Fnames = [Name || {Name,_} <- Fnames1], NumLines = maps:size(Lines0), Lines1 = lists:keysort(2, maps:to_list(Lines0)), Lines = [L || {L,_} <- Lines1], - {NumLineInstrs,NumFnames,Fnames,NumLines,Lines}. + {NumLineInstrs,NumFnames,Fnames,NumLines,Lines,ExecLine}. %% Search for binary string Str in the binary string pool Pool. %% old_string(Str, Pool) -> none | Index diff --git a/lib/compiler/src/beam_disasm.erl b/lib/compiler/src/beam_disasm.erl index 35ba0ba82d..06064b1469 100644 --- a/lib/compiler/src/beam_disasm.erl +++ b/lib/compiler/src/beam_disasm.erl @@ -1303,6 +1303,13 @@ resolve_inst({bs_match,[{Fail,Ctx,{z,1},{u,_},Args}]},_,_,_) -> Commands = resolve_bs_match_commands(List), {bs_match,Fail,Ctx,{commands,Commands}}; +%% +%% OTP 27. +%% + +resolve_inst({executable_line,[Index]},_,_,_) -> + {line,resolve_arg(Index)}; + %% %% Catches instructions that are not yet handled. %% diff --git a/lib/compiler/src/beam_flatten.erl b/lib/compiler/src/beam_flatten.erl index c8298d5e6f..9a29b1ad50 100644 --- a/lib/compiler/src/beam_flatten.erl +++ b/lib/compiler/src/beam_flatten.erl @@ -62,7 +62,8 @@ norm({set,[D],[S],get_tl}) -> {get_tl,S,D}; norm({set,[D],[S|Puts],{alloc,R,{put_map,Op,F}}}) -> {put_map,F,Op,S,D,R,{list,Puts}}; norm({set,[],[],remove_message}) -> remove_message; -norm({set,[],[],{line,_}=Line}) -> Line. +norm({set,[],[],{line,_}=Line}) -> Line; +norm({set,[],[],{executable_line,_}=Line}) -> Line. norm_allocate({_Zero,nostack,Nh,[]}, Regs) -> [{test_heap,Nh,Regs}]; diff --git a/lib/compiler/src/beam_ssa.erl b/lib/compiler/src/beam_ssa.erl index db35a2fa41..4650def062 100644 --- a/lib/compiler/src/beam_ssa.erl +++ b/lib/compiler/src/beam_ssa.erl @@ -109,7 +109,7 @@ 'bs_match' | 'bs_start_match' | 'bs_test_tail' | 'build_stacktrace' | 'call' | 'catch_end' | - 'extract' | + 'executable_line' | 'extract' | 'get_hd' | 'get_map_element' | 'get_tl' | 'get_tuple_element' | 'has_map_field' | 'is_nonempty_list' | 'is_tagged_tuple' | diff --git a/lib/compiler/src/beam_ssa_alias.erl b/lib/compiler/src/beam_ssa_alias.erl index 6dd4b92e5d..554fbdcd68 100644 --- a/lib/compiler/src/beam_ssa_alias.erl +++ b/lib/compiler/src/beam_ssa_alias.erl @@ -512,6 +512,8 @@ aa_is([I=#b_set{dst=Dst,op=Op,args=Args,anno=Anno0}|Is], SS0, AAS0) -> {SS1, AAS0}; bs_test_tail -> {SS1, AAS0}; + executable_line -> + {SS1, AAS0}; has_map_field -> {SS1, AAS0}; is_nonempty_list -> diff --git a/lib/compiler/src/beam_ssa_codegen.erl b/lib/compiler/src/beam_ssa_codegen.erl index 0399d358de..739180d99c 100644 --- a/lib/compiler/src/beam_ssa_codegen.erl +++ b/lib/compiler/src/beam_ssa_codegen.erl @@ -389,6 +389,7 @@ classify_heap_need(build_stacktrace) -> gc; classify_heap_need(call) -> gc; classify_heap_need(catch_end) -> gc; classify_heap_need(copy) -> neutral; +classify_heap_need(executable_line) -> neutral; classify_heap_need(extract) -> gc; classify_heap_need(get_hd) -> neutral; classify_heap_need(get_map_element) -> neutral; @@ -1809,6 +1810,9 @@ cg_instr(bs_get_tail, [Src], Dst, Set) -> cg_instr(bs_get_position, [Ctx], Dst, Set) -> Live = get_live(Set), [{bs_get_position,Ctx,Dst,Live}]; +cg_instr(executable_line, [], _Dst, #cg_set{anno=Anno}) -> + {line,Location} = line(Anno), + [{executable_line,Location}]; cg_instr(put_map, [{atom,assoc},SrcMap|Ss], Dst, Set) -> Live = get_live(Set), [{put_map_assoc,{f,0},SrcMap,Dst,Live,{list,Ss}}]; diff --git a/lib/compiler/src/beam_ssa_opt.erl b/lib/compiler/src/beam_ssa_opt.erl index 06c1e79f20..620dcb941a 100644 --- a/lib/compiler/src/beam_ssa_opt.erl +++ b/lib/compiler/src/beam_ssa_opt.erl @@ -488,6 +488,14 @@ merge_updates_bs([{LblA, %% Note that we retain the first update_record in case it's used elsewhere, %% it's too rare to warrant special handling here. [{LblA, BlkA}, {LblB, BlkB#b_blk{is=[Update]}}| merge_updates_bs(Bs)]; +merge_updates_bs([{LblA, + #b_blk{is=[#b_set{op=update_record}]=IsA, + last=#b_br{bool=#b_literal{val=true}, + succ=LblB}}=BlkA}, + {LblB, + #b_blk{is=[#b_set{op=executable_line}]=IsB}=BlkB} | Bs0]) -> + Bs = [{LblB,BlkB#b_blk{is=IsA}} | Bs0], + [{LblA,BlkA#b_blk{is=IsB}} | merge_updates_bs(Bs)]; merge_updates_bs([{Lbl, Blk} | Bs]) -> [{Lbl, Blk} | merge_updates_bs(Bs)]; merge_updates_bs([]) -> diff --git a/lib/compiler/src/beam_ssa_pre_codegen.erl b/lib/compiler/src/beam_ssa_pre_codegen.erl index 8ca16374d0..572d771e98 100644 --- a/lib/compiler/src/beam_ssa_pre_codegen.erl +++ b/lib/compiler/src/beam_ssa_pre_codegen.erl @@ -2604,6 +2604,7 @@ use_zreg(bs_checked_skip) -> yes; use_zreg(bs_ensure) -> yes; use_zreg(bs_match_string) -> yes; use_zreg(bs_set_position) -> yes; +use_zreg(executable_line) -> yes; use_zreg(kill_try_tag) -> yes; use_zreg(landingpad) -> yes; use_zreg(recv_marker_bind) -> yes; diff --git a/lib/compiler/src/beam_ssa_share.erl b/lib/compiler/src/beam_ssa_share.erl index 13b6038868..8ef50eede2 100644 --- a/lib/compiler/src/beam_ssa_share.erl +++ b/lib/compiler/src/beam_ssa_share.erl @@ -284,8 +284,9 @@ canonical_block({L,VarMap0}, Blocks) -> %% * Variables defined in the instruction sequence are replaced with %% {var,0}, {var,1}, and so on. Free variables are not changed. %% -%% * `location` annotations that would produce a `line` instruction are -%% kept. All other annotations are cleared. +%% * `location` annotations that would produce `line` or +%% `executable_line` instructions are kept. All other annotations +%% are cleared. %% %% * Instructions are repackaged into tuples instead of into the %% usual records. The main reason is to avoid violating the types for @@ -300,17 +301,21 @@ canonical_is([#b_set{op=Op,dst=Dst,args=Args0}=I|Is], VarMap0, Acc) -> Args = [canonical_arg(Arg, VarMap0) || Arg <- Args0], Var = {var,map_size(VarMap0)}, VarMap = VarMap0#{Dst=>Var}, - LineAnno = case Op of - bs_match -> - %% The location annotation for a bs_match instruction - %% is only used in warnings, never to emit a `line` - %% instruction. Therefore, it should not be included. - []; - _ -> - %% The location annotation will be used in a `line` - %% instruction. It must be included. - beam_ssa:get_anno(location, I, none) - end, + LineAnno = + case {Op,Is} of + {executable_line, _} -> + %% The location annotation will be used in a + %% `executable_line` instruction. + beam_ssa:get_anno(location, I, none); + {_, [#b_set{op={succeeded,body},args=[Dst]}|_]} -> + %% The location annotation will be used in a `line` + %% instruction. + beam_ssa:get_anno(location, I, none); + {_, _} -> + %% The location annotation will not be included in + %% any BEAM instruction. + [] + end, canonical_is(Is, VarMap, {Op,LineAnno,Var,Args,Acc}); canonical_is([#b_ret{arg=Arg}], VarMap, Acc) -> {{ret,canonical_arg(Arg, VarMap),Acc},VarMap}; diff --git a/lib/compiler/src/beam_validator.erl b/lib/compiler/src/beam_validator.erl index ead9c12052..98bc729b18 100644 --- a/lib/compiler/src/beam_validator.erl +++ b/lib/compiler/src/beam_validator.erl @@ -372,6 +372,8 @@ vi({'%',_}, Vst) -> Vst; vi({line,_}, Vst) -> Vst; +vi({executable_line,_}, Vst) -> + Vst; vi(nif_start, Vst) -> Vst; %% diff --git a/lib/compiler/src/compile.erl b/lib/compiler/src/compile.erl index 33c47d68a7..d4e693aee0 100644 --- a/lib/compiler/src/compile.erl +++ b/lib/compiler/src/compile.erl @@ -830,6 +830,8 @@ abstr_passes(AbstrStatus) -> {delay,[{iff,debug_info,?pass(save_abstract_code)}]}, + {iff,line_coverage,{pass,sys_coverage}}, + ?pass(expand_records), {iff,'dexp',{listing,"expand"}}, {iff,'E',?pass(legalize_vars)}, diff --git a/lib/compiler/src/compiler.app.src b/lib/compiler/src/compiler.app.src index 90448a67a0..71588c0826 100644 --- a/lib/compiler/src/compiler.app.src +++ b/lib/compiler/src/compiler.app.src @@ -75,6 +75,7 @@ sys_core_fold_lists, sys_core_inline, sys_core_prepare, + sys_coverage, sys_messages, sys_pre_attributes, v3_core diff --git a/lib/compiler/src/genop.tab b/lib/compiler/src/genop.tab index f2d50f3bc2..aec38bb54c 100755 --- a/lib/compiler/src/genop.tab +++ b/lib/compiler/src/genop.tab @@ -689,3 +689,9 @@ BEAM_FORMAT_NUMBER=0 ## * {get_tail,Live,Unit,Dst} ## * {'=:=',Live,Size,Value}. 182: bs_match/3 + +# OTP 27 + +## @spec executable_line Location +## @doc Provide location for an executable line. +183: executable_line/1 diff --git a/lib/compiler/src/sys_coverage.erl b/lib/compiler/src/sys_coverage.erl new file mode 100644 index 0000000000..994fa7b016 --- /dev/null +++ b/lib/compiler/src/sys_coverage.erl @@ -0,0 +1,579 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2023. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% +%% Purpose: Instrument abstract code for coverage. + +-module(sys_coverage). +-export([module/2,cover_transform/2]). +-import(lists, [member/2,reverse/1,reverse/2]). + +-type attribute() :: atom(). +-type form() :: {function, integer(), atom(), arity(), _} + | {attribute, integer(), attribute(), _}. +-type index_fun() :: fun((module(), atom(), arity(), + non_neg_integer(), non_neg_integer()) -> + non_neg_integer()). + +-spec module([form()], [compile:option()]) -> + {'ok',[form()]}. + +module(Forms0, _Opts) when is_list(Forms0) -> + IndexFun = fun(_, _, _, _, _) -> 0 end, + transform(Forms0, IndexFun). + +%% Undocumented helper function for the `cover` module. +-spec cover_transform([form()], index_fun()) -> + {'ok',[form()]}. + +cover_transform(Forms, IndexFun) when is_list(Forms), + is_function(IndexFun, 5) -> + transform(Forms, IndexFun). + +%%% +%%% Local functions. +%%% + +%% Line doesn't matter. +-define(BLOCK(Expr), {block,erl_anno:new(0),[Expr]}). +-define(BLOCK1(Expr), + if + element(1, Expr) =:= block -> + Expr; + true -> + ?BLOCK(Expr) + end). +-define(EXECUTABLE_LINE, executable_line). + +-record(vars, + {module=[] :: module() | [], + function=none :: atom(), + arity=0 :: arity(), + clause=0 :: non_neg_integer(), + lines=[] :: [non_neg_integer()], + bump_lines=[] :: [non_neg_integer()], + in_guard=false :: boolean(), + index_fun :: index_fun() + }). + +transform(Code, IndexFun) -> + Vars = #vars{index_fun=IndexFun}, + transform(Code, [], Vars, none, on). + +transform([Form0|Forms], MungedForms, Vars0, MainFile0, Switch0) -> + Form = expand(Form0), + case munge(Form, Vars0, MainFile0, Switch0) of + ignore -> + transform(Forms, MungedForms, Vars0, MainFile0, Switch0); + {MungedForm, Vars, Switch} -> + transform(Forms, [MungedForm|MungedForms], Vars, MainFile0, Switch); + {MungedForm,Vars,MainFile,Switch} -> + transform(Forms, [MungedForm|MungedForms], Vars, MainFile, Switch) + end; +transform([], MungedForms, _Vars, _, _) -> + {ok, reverse(MungedForms)}. + +%% Expand short-circuit Boolean expressions. +expand(Expr0) -> + AllVars = sets:from_list(ordsets:to_list(vars([], Expr0)), [{version,2}]), + {Expr,_} = expand(Expr0, AllVars, 1), + Expr. + +expand({clause,Anno,Pattern,Guards,Body}, Vs, N) -> + %% We must not expand andalso/orelse in guards. + {ExpandedBody,N2} = expand(Body, Vs, N), + {{clause,Anno,Pattern,Guards,ExpandedBody},N2}; +expand({lc,Anno,Expr,Qs}, Vs, N) -> + {ExpandedExpr,N2} = expand(Expr, Vs, N), + {ExpandedQs,N3} = expand_qualifiers(Qs, Vs, N2), + {{lc,Anno,ExpandedExpr,ExpandedQs},N3}; +expand({bc,Anno,Expr,Qs}, Vs, N) -> + {ExpandedExpr,N2} = expand(Expr, Vs, N), + {ExpandedQs,N3} = expand_qualifiers(Qs, Vs, N2), + {{bc,Anno,ExpandedExpr,ExpandedQs},N3}; +expand({mc,Anno,Expr,Qs}, Vs, N) -> + {ExpandedExpr,N2} = expand(Expr, Vs, N), + {ExpandedQs,N3} = expand_qualifiers(Qs, Vs, N2), + {{mc,Anno,ExpandedExpr,ExpandedQs},N3}; +expand({op,_Anno,'andalso',ExprL,ExprR}, Vs, N) -> + {ExpandedExprL,N2} = expand(ExprL, Vs, N), + {ExpandedExprR,N3} = expand(ExprR, Vs, N2), + Anno = element(2, ExpandedExprL), + {bool_switch(ExpandedExprL, + ExpandedExprR, + {atom,Anno,false}, + Vs, N3), + N3 + 1}; +expand({op,_Anno,'orelse',ExprL,ExprR}, Vs, N) -> + {ExpandedExprL,N2} = expand(ExprL, Vs, N), + {ExpandedExprR,N3} = expand(ExprR, Vs, N2), + Anno = element(2, ExpandedExprL), + {bool_switch(ExpandedExprL, + {atom,Anno,true}, + ExpandedExprR, + Vs, N3), + N3 + 1}; +expand(T, Vs, N) when is_tuple(T) -> + {TL,N2} = expand(tuple_to_list(T), Vs, N), + {list_to_tuple(TL),N2}; +expand([E|Es], Vs, N) -> + {E2,N2} = expand(E, Vs, N), + {Es2,N3} = expand(Es, Vs, N2), + {[E2|Es2],N3}; +expand(T, _Vs, N) -> + {T,N}. + +expand_qualifiers([Q|Qs], Vs, N) -> + {Q2,N2} = case erl_lint:is_guard_test(Q) of + true -> + %% This qualifier is a guard test and will be + %% compiled as such. Don't expand andalso/orelse + %% because that would turn it into a body + %% expression that may raise an exception. Here + %% is an example of a filter where the error + %% behaviour would change: + %% + %% V == a orelse element(1, V) == a + %% + {Q,N}; + false -> + %% A generator or a filter that is not a guard + %% test. + expand(Q, Vs, N) + end, + {Qs2,N3} = expand_qualifiers(Qs, Vs, N2), + {[Q2|Qs2],N3}; +expand_qualifiers([], _Vs, N) -> + {[],N}. + +vars(A, {var,_,V}) when V =/= '_' -> + [V|A]; +vars(A, T) when is_tuple(T) -> + vars(A, tuple_to_list(T)); +vars(A, [E|Es]) -> + vars(vars(A, E), Es); +vars(A, _T) -> + A. + +bool_switch(E, T, F, AllVars, AuxVarN) -> + Anno = element(2, E), + AuxVar = {var,Anno,aux_var(AllVars, AuxVarN)}, + {'case',Anno,E, + [{clause,Anno,[{atom,Anno,true}],[],[T]}, + {clause,Anno,[{atom,Anno,false}],[],[F]}, + %% Mark the next clause as compiler-generated to suppress + %% a warning if the case expression is an obvious boolean + %% value. + {clause,erl_anno:set_generated(true, Anno),[AuxVar],[], + [{call,Anno, + {remote,Anno,{atom,Anno,erlang},{atom,Anno,error}}, + [{tuple,Anno,[{atom,Anno,badarg},AuxVar]}]}]}]}. + +aux_var(Vars, N) -> + Name = list_to_atom(lists:concat(['_', N])), + case sets:is_element(Name, Vars) of + true -> aux_var(Vars, N + 1); + false -> Name + end. + +%% This code traverses the abstract code, stored as the abstract_code +%% chunk in the BEAM file, as described in absform(3). The switch is +%% turned off when we encounter other files than the main file. This +%% way we will be able to exclude functions defined in include files. + +munge({attribute,_,file,{File,_}}=Form, Vars, none, _) -> + {Form,Vars,File,on}; +munge({attribute,_,module,Mod}=Form, Vars, MainFile, Switch) when is_atom(Mod) -> + {Form,Vars#vars{module=Mod},MainFile,Switch}; +munge({function,Anno,Function,Arity,Clauses}, Vars0, _MainFile, on) -> + Vars = Vars0#vars{function=Function, + arity=Arity, + clause=1, + lines=[], + bump_lines=[]}, + MungedClauses = munge_fun_clauses(Clauses, Vars), + {{function,Anno,Function,Arity,MungedClauses},Vars,on}; +munge({attribute,_,file,{MainFile,_}}=Form, Vars, MainFile, _Switch) -> + {Form,Vars,on}; % Switch on transformation! +munge({attribute,_,file,{_InclFile,_}}=Form, Vars, _MainFile, _Switch) -> + {Form,Vars,off}; % Switch off transformation! +munge({attribute,_,compile,{parse_transform,_}}, _, _, _) -> + %% 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_fun_clauses([Clause0|Clauses], #vars{clause=ClauseIndex}=Vars0) -> + {clause,Anno,Pattern,Guards,Body} = Clause0, + {MungedGuards,_} = munge_exprs(Guards, Vars0#vars{in_guard=true}), + {MungedBody,_} = munge_body(Body, Vars0), + Vars = Vars0#vars{clause=ClauseIndex + 1}, + Clause = {clause,Anno,Pattern,MungedGuards,MungedBody}, + [Clause|munge_fun_clauses(Clauses, Vars)]; +munge_fun_clauses([], _Vars) -> []. + +%% Munge clauses in case, if, maybe, receive, and try. +munge_clauses(Clauses, Vars) -> + munge_clauses(Clauses, Vars, Vars#vars.lines, []). + +munge_clauses([Clause|Clauses], Vars0, Lines0, MClauses) -> + {clause,Anno,Pattern,Guards,Body} = Clause, + {MungedGuards, _Vars} = munge_exprs(Guards, Vars0#vars{in_guard=true}), + {MungedBody, Vars} = munge_body(Body, Vars0), + NewBumps = new_bumps(Vars, Vars0), + Lines = NewBumps ++ Lines0, + munge_clauses(Clauses, Vars#vars{lines=Vars0#vars.lines}, + Lines, + [{clause,Anno,Pattern,MungedGuards,MungedBody}| + MClauses]); +munge_clauses([], Vars, Lines, MungedClauses) -> + {reverse(MungedClauses), Vars#vars{lines=Lines}}. + +munge_body(Expr, Vars) -> + munge_body(Expr, Vars, [], []). + +munge_body([E0|Es], Vars0, Acc0, LastExprBumpLines) -> + %% Here is the place to add an executable_line instruction. + Line = erl_anno:line(element(2, E0)), + Lines0 = Vars0#vars.lines, + case member(Line, Lines0) of + true -> + %% There is already a bump at this line. + {E1,Vars1} = munge_expr(E0, Vars0), + NewBumps = new_bumps(Vars1, Vars0), + BumpLines = [Line|Vars0#vars.bump_lines], + Vars2 = Vars1#vars{bump_lines=BumpLines}, + Acc1 = maybe_fix_last_expr(Acc0, Vars2, LastExprBumpLines), + Acc = [E1|Acc1], + munge_body(Es, Vars2, Acc, NewBumps); + false -> + %% Put a bump at this line. + Bump = bump_call(Vars0, Line), + Lines1 = [Line|Lines0], + {E1,Vars1} = munge_expr(E0, Vars0#vars{lines=Lines1}), + NewBumps = new_bumps(Vars1, Vars0), + BumpLines = subtract(Vars1#vars.bump_lines, NewBumps), + Vars2 = Vars1#vars{bump_lines=BumpLines}, + Acc1 = maybe_fix_last_expr(Acc0, Vars2, LastExprBumpLines), + Acc = [E1,Bump|Acc1], + munge_body(Es, Vars2, Acc, NewBumps) + end; +munge_body([], Vars, Acc, _LastExprBumpLines) -> + {reverse(Acc), Vars}. + +%%% Fix last expression (OTP-8188). A typical example: +%%% +%%% 3: case X of +%%% 4: 1 -> a; % Bump line 5 after "a" has been evaluated! +%%% 5: 2 -> b; 3 -> c end, F() +%%% +%%% Line 5 wasn't bumped just before "F()" since it was already bumped +%%% before "b" (and before "c") (one mustn't bump a line more than +%%% once in a single "evaluation"). The expression "case X ... end" is +%%% now traversed again ("fixed"), this time adding bumps of line 5 +%%% where appropriate, in this case when X matches 1. +%%% +%%% This doesn't solve all problems with expressions on the same line, +%%% though. 'case' and 'try' are tricky. An example: +%%% +%%% 7: case case X of 1 -> foo(); % ? +%%% 8: 2 -> bar() end of a -> 1; +%%% 9: b -> 2 end. +%%% +%%% If X matches 1 and foo() evaluates to a then line 8 should be +%%% bumped, but not if foo() evaluates to b. In other words, line 8 +%%% cannot be bumped after "foo()" on line 7, so one has to bump line +%%% 8 before "begin 1 end". But if X matches 2 and bar evaluates to a +%%% then line 8 would be bumped twice (there has to be a bump before +%%% "bar()". It is like one would have to have two copies of the inner +%%% clauses, one for each outer clause. Maybe the munging should be +%%% done on some of the compiler's "lower level" format. +%%% +%%% 'fun' is also problematic since a bump inside the body "shadows" +%%% the rest of the line. + +maybe_fix_last_expr(MungedExprs, Vars, LastExprBumpLines) -> + case last_expr_needs_fixing(Vars, LastExprBumpLines) of + {yes, Line} -> + fix_last_expr(MungedExprs, Line, Vars); + no -> + MungedExprs + end. + +last_expr_needs_fixing(Vars, LastExprBumpLines) -> + case common_elems(Vars#vars.bump_lines, LastExprBumpLines) of + [Line] -> + {yes, Line}; + _ -> + no + end. + +fix_last_expr([MungedExpr|MungedExprs], Line, Vars) -> + %% No need to update ?COVER_TABLE. + Bump = bump_call(Vars, Line), + [fix_expr(MungedExpr, Line, Bump)|MungedExprs]. + +fix_expr({'if',A,Clauses}, Line, Bump) -> + FixedClauses = fix_clauses(Clauses, Line, Bump), + {'if',A,FixedClauses}; +fix_expr({'case',A,Expr,Clauses}, Line, Bump) -> + FixedExpr = fix_expr(Expr, Line, Bump), + FixedClauses = fix_clauses(Clauses, Line, Bump), + {'case',A,FixedExpr,FixedClauses}; +fix_expr({'receive',A,Clauses}, Line, Bump) -> + FixedClauses = fix_clauses(Clauses, Line, Bump), + {'receive',A,FixedClauses}; +fix_expr({'receive',A,Clauses,Expr,Body}, Line, Bump) -> + FixedClauses = fix_clauses(Clauses, Line, Bump), + FixedExpr = fix_expr(Expr, Line, Bump), + FixedBody = fix_expr(Body, Line, Bump), + {'receive',A,FixedClauses,FixedExpr,FixedBody}; +fix_expr({'try',A,Exprs,Clauses,CatchClauses,After}, Line, Bump) -> + FixedExprs = fix_expr(Exprs, Line, Bump), + FixedClauses = fix_clauses(Clauses, Line, Bump), + FixedCatchClauses = fix_clauses(CatchClauses, Line, Bump), + FixedAfter = fix_expr(After, Line, Bump), + {'try',A,FixedExprs,FixedClauses,FixedCatchClauses,FixedAfter}; +fix_expr([E | Es], Line, Bump) -> + [fix_expr(E, Line, Bump) | fix_expr(Es, Line, Bump)]; +fix_expr(T, Line, Bump) when is_tuple(T) -> + list_to_tuple(fix_expr(tuple_to_list(T), Line, Bump)); +fix_expr(E, _Line, _Bump) -> + E. + +fix_clauses([], _Line, _Bump) -> + []; +fix_clauses(Cs, Line, Bump) -> + case bumps_line(lists:last(Cs), Line) of + true -> + fix_cls(Cs, Line, Bump); + false -> + Cs + end. + +fix_cls([], _Line, _Bump) -> + []; +fix_cls([Cl | Cls], Line, Bump) -> + case bumps_line(Cl, Line) of + true -> + [fix_expr(C, Line, Bump) || C <- [Cl | Cls]]; + false -> + {clause,CA,P,G,Body} = Cl, + UniqueVarName = list_to_atom(lists:concat(["$cover$ ",Line])), + A = erl_anno:new(0), + V = {var,A,UniqueVarName}, + [Last|Rest] = reverse(Body), + Body1 = reverse(Rest, [{match,A,V,Last},Bump,V]), + [{clause,CA,P,G,Body1} | fix_cls(Cls, Line, Bump)] + end. + +bumps_line(E, L) -> + try bumps_line1(E, L) catch true -> true end. + +bumps_line1({?EXECUTABLE_LINE,Line,_}, Line) -> + throw(true); +bumps_line1([E | Es], Line) -> + bumps_line1(E, Line), + bumps_line1(Es, Line); +bumps_line1(T, Line) when is_tuple(T) -> + bumps_line1(tuple_to_list(T), Line); +bumps_line1(_, _) -> + false. + +%% Insert an executable_line instruction in the abstract code. +bump_call(Vars, Line) -> + #vars{module=M,function=F,arity=A,clause=C,index_fun=GetIndex} = Vars, + Index = GetIndex(M, F, A, C, Line), + {?EXECUTABLE_LINE,Line,Index}. + +%%% End of fix of last expression. + +munge_expr({match,Anno,ExprL,ExprR}, Vars0) -> + {MungedExprL, Vars1} = munge_expr(ExprL, Vars0), + {MungedExprR, Vars2} = munge_expr(ExprR, Vars1), + {{match,Anno,MungedExprL,MungedExprR}, Vars2}; +munge_expr({maybe_match,Anno,ExprL,ExprR}, Vars0) -> + {MungedExprL, Vars1} = munge_expr(ExprL, Vars0), + {MungedExprR, Vars2} = munge_expr(ExprR, Vars1), + {{maybe_match,Anno,MungedExprL,MungedExprR}, Vars2}; +munge_expr({tuple,Anno,Exprs}, Vars0) -> + {MungedExprs, Vars1} = munge_exprs(Exprs, Vars0), + {{tuple,Anno,MungedExprs}, Vars1}; +munge_expr({record,Anno,Name,Exprs}, Vars0) -> + {MungedExprFields, Vars1} = munge_exprs(Exprs, Vars0), + {{record,Anno,Name,MungedExprFields}, Vars1}; +munge_expr({record,Anno,Arg,Name,Exprs}, Vars0) -> + {MungedArg, Vars1} = munge_expr(Arg, Vars0), + {MungedExprFields, Vars2} = munge_exprs(Exprs, Vars1), + {{record,Anno,MungedArg,Name,MungedExprFields}, Vars2}; +munge_expr({record_field,Anno,ExprL,ExprR}, Vars0) -> + {MungedExprR, Vars1} = munge_expr(ExprR, Vars0), + {{record_field,Anno,ExprL,MungedExprR}, Vars1}; +munge_expr({map,Anno,Fields}, Vars0) -> + {MungedFields, Vars1} = munge_exprs(Fields, Vars0), + {{map,Anno,MungedFields}, Vars1}; +munge_expr({map,Anno,Arg,Fields}, Vars0) -> + {MungedArg, Vars1} = munge_expr(Arg, Vars0), + {MungedFields, Vars2} = munge_exprs(Fields, Vars1), + {{map,Anno,MungedArg,MungedFields}, Vars2}; +munge_expr({map_field_assoc,Anno,Name,Value}, Vars0) -> + {MungedName, Vars1} = munge_expr(Name, Vars0), + {MungedValue, Vars2} = munge_expr(Value, Vars1), + {{map_field_assoc,Anno,MungedName,MungedValue}, Vars2}; +munge_expr({map_field_exact,Anno,Name,Value}, Vars0) -> + {MungedName, Vars1} = munge_expr(Name, Vars0), + {MungedValue, Vars2} = munge_expr(Value, Vars1), + {{map_field_exact,Anno,MungedName,MungedValue}, Vars2}; +munge_expr({cons,Anno,ExprH,ExprT}, Vars0) -> + {MungedExprH, Vars1} = munge_expr(ExprH, Vars0), + {MungedExprT, Vars2} = munge_expr(ExprT, Vars1), + {{cons,Anno,MungedExprH,MungedExprT}, Vars2}; +munge_expr({op,Anno,Op,ExprL,ExprR}, Vars0) -> + {MungedExprL, Vars1} = munge_expr(ExprL, Vars0), + {MungedExprR, Vars2} = munge_expr(ExprR, Vars1), + {{op,Anno,Op,MungedExprL,MungedExprR}, Vars2}; +munge_expr({op,Anno,Op,Expr}, Vars0) -> + {MungedExpr, Vars1} = munge_expr(Expr, Vars0), + {{op,Anno,Op,MungedExpr}, Vars1}; +munge_expr({'catch',Anno,Expr}, Vars0) -> + {MungedExpr, Vars1} = munge_expr(Expr, Vars0), + {{'catch',Anno,MungedExpr}, Vars1}; +munge_expr({call,Anno1,{remote,Anno2,ExprM,ExprF},Exprs}, Vars0) -> + {MungedExprM, Vars1} = munge_expr(ExprM, Vars0), + {MungedExprF, Vars2} = munge_expr(ExprF, Vars1), + {MungedExprs, Vars3} = munge_exprs(Exprs, Vars2), + {{call,Anno1,{remote,Anno2,MungedExprM,MungedExprF},MungedExprs}, Vars3}; +munge_expr({call,Anno,Expr,Exprs}, Vars0) -> + {MungedExpr, Vars1} = munge_expr(Expr, Vars0), + {MungedExprs, Vars2} = munge_exprs(Exprs, Vars1), + {{call,Anno,MungedExpr,MungedExprs}, Vars2}; +munge_expr({lc,Anno,Expr,Qs}, Vars0) -> + {MungedExpr, Vars1} = munge_expr(?BLOCK1(Expr), Vars0), + {MungedQs, Vars2} = munge_qualifiers(Qs, Vars1), + {{lc,Anno,MungedExpr,MungedQs}, Vars2}; +munge_expr({bc,Anno,Expr,Qs}, Vars0) -> + {MungedExpr,Vars1} = munge_expr(?BLOCK1(Expr), Vars0), + {MungedQs, Vars2} = munge_qualifiers(Qs, Vars1), + {{bc,Anno,MungedExpr,MungedQs}, Vars2}; +munge_expr({mc,Anno,{map_field_assoc,FAnno,K,V},Qs}, Vars0) -> + Expr = {map_field_assoc,FAnno,?BLOCK1(K),?BLOCK1(V)}, + {MungedExpr, Vars1} = munge_expr(Expr, Vars0), + {MungedQs, Vars2} = munge_qualifiers(Qs, Vars1), + {{mc,Anno,MungedExpr,MungedQs}, Vars2}; +munge_expr({block,Anno,Body}, Vars0) -> + {MungedBody, Vars1} = munge_body(Body, Vars0), + {{block,Anno,MungedBody}, Vars1}; +munge_expr({'if',Anno,Clauses}, Vars0) -> + {MungedClauses,Vars1} = munge_clauses(Clauses, Vars0), + {{'if',Anno,MungedClauses}, Vars1}; +munge_expr({'case',Anno,Expr,Clauses}, Vars0) -> + {MungedExpr,Vars1} = munge_expr(Expr, Vars0), + {MungedClauses,Vars2} = munge_clauses(Clauses, Vars1), + {{'case',Anno,MungedExpr,MungedClauses}, Vars2}; +munge_expr({'receive',Anno,Clauses}, Vars0) -> + {MungedClauses,Vars1} = munge_clauses(Clauses, Vars0), + {{'receive',Anno,MungedClauses}, Vars1}; +munge_expr({'receive',Anno,Clauses,Expr,Body}, Vars0) -> + {MungedExpr,Vars1} = munge_expr(Expr, Vars0), + {MungedClauses,Vars2} = munge_clauses(Clauses, Vars1), + {MungedBody,Vars3} = munge_body(Body, Vars2#vars{lines=Vars1#vars.lines}), + Vars4 = Vars3#vars{lines=Vars2#vars.lines ++ new_bumps(Vars3, Vars2)}, + {{'receive',Anno,MungedClauses,MungedExpr,MungedBody}, Vars4}; +munge_expr({'try',Anno,Body,Clauses,CatchClauses,After}, Vars0) -> + {MungedBody, Vars01} = munge_body(Body, Vars0), + {MungedClauses, Vars1} = munge_clauses(Clauses, Vars01), + {MungedCatchClauses, Vars2} = munge_clauses(CatchClauses, Vars1), + {MungedAfter, Vars3} = munge_body(After, Vars2), + {{'try',Anno,MungedBody,MungedClauses,MungedCatchClauses,MungedAfter}, + Vars3}; +munge_expr({'maybe',Anno,Exprs}, Vars0) -> + {MungedExprs, Vars1} = munge_body(Exprs, Vars0), + {{'maybe',Anno,MungedExprs}, Vars1}; +munge_expr({'maybe',MaybeAnno,Exprs,{'else',ElseAnno,Clauses}}, Vars0) -> + {MungedExprs, Vars1} = munge_body(Exprs, Vars0), + {MungedClauses, Vars2} = munge_clauses(Clauses, Vars1), + {{'maybe',MaybeAnno,MungedExprs,{'else',ElseAnno,MungedClauses}}, Vars2}; +munge_expr({'fun',Anno,{clauses,Clauses}}, Vars0) -> + {MungedClauses,Vars1} = munge_clauses(Clauses, Vars0), + {{'fun',Anno,{clauses,MungedClauses}}, Vars1}; +munge_expr({named_fun,Anno,Name,Clauses}, Vars0) -> + {MungedClauses,Vars1} = munge_clauses(Clauses, Vars0), + {{named_fun,Anno,Name,MungedClauses}, Vars1}; +munge_expr({bin,Anno,BinElements}, Vars0) -> + {MungedBinElements,Vars1} = munge_exprs(BinElements, Vars0), + {{bin,Anno,MungedBinElements}, Vars1}; +munge_expr({bin_element,Anno,Value,Size,TypeSpecifierList}, Vars0) -> + {MungedValue,Vars1} = munge_expr(Value, Vars0), + {MungedSize,Vars2} = munge_expr(Size, Vars1), + {{bin_element,Anno,MungedValue,MungedSize,TypeSpecifierList},Vars2}; +munge_expr(Form, Vars0) -> + {Form, Vars0}. + +munge_exprs(Exprs, Vars) -> + munge_exprs(Exprs, Vars, []). + +munge_exprs([Expr|Exprs], #vars{in_guard=true}=Vars0, MungedExprs) + when is_list(Expr) -> + {MungedExpr, _Vars0} = munge_exprs(Expr, Vars0), + munge_exprs(Exprs, Vars0, [MungedExpr|MungedExprs]); +munge_exprs([Expr|Exprs], Vars0, MungedExprs) -> + {MungedExpr, Vars1} = munge_expr(Expr, Vars0), + munge_exprs(Exprs, Vars1, [MungedExpr|MungedExprs]); +munge_exprs([], Vars0, MungedExprs) -> + {reverse(MungedExprs), Vars0}. + +%% Every qualifier is decorated with a counter. +munge_qualifiers(Qualifiers, Vars) -> + munge_qs(Qualifiers, Vars, []). + +munge_qs([{generate,Anno,Pattern,Expr}|Qs], Vars0, MQs) -> + A = element(2, Expr), + {MungedExpr, Vars1} = munge_expr(Expr, Vars0), + munge_qs1(Qs, A, {generate,Anno,Pattern,MungedExpr}, Vars0, Vars1, MQs); +munge_qs([{b_generate,Anno,Pattern,Expr}|Qs], Vars0, MQs) -> + A = element(2, Expr), + {MExpr, Vars1} = munge_expr(Expr, Vars0), + munge_qs1(Qs, A, {b_generate,Anno,Pattern,MExpr}, Vars0, Vars1, MQs); +munge_qs([{m_generate,Anno,Pattern,Expr}|Qs], Vars0, MQs) -> + A = element(2, Expr), + {MExpr, Vars1} = munge_expr(Expr, Vars0), + munge_qs1(Qs, A, {m_generate,Anno,Pattern,MExpr}, Vars0, Vars1, MQs); +munge_qs([Expr|Qs], Vars0, MQs) -> + A = element(2, Expr), + {MungedExpr, Vars1} = munge_expr(Expr, Vars0), + munge_qs1(Qs, A, MungedExpr, Vars0, Vars1, MQs); +munge_qs([], Vars0, MQs) -> + {reverse(MQs), Vars0}. + +munge_qs1(Qs, Anno, NQ, Vars0, Vars1, MQs) -> + case new_bumps(Vars1, Vars0) of + [_] -> + munge_qs(Qs, Vars1, [NQ | MQs]); + _ -> + {MungedTrue, Vars2} = munge_expr(?BLOCK({atom,Anno,true}), Vars1), + munge_qs(Qs, Vars2, [NQ, MungedTrue | MQs]) + end. + +new_bumps(#vars{lines=New}, #vars{lines=Old}) -> + subtract(New, Old). + +subtract(L1, L2) -> + [E || E <- L1, not member(E, L2)]. + +common_elems(L1, L2) -> + [E || E <- L1, member(E, L2)]. diff --git a/lib/compiler/src/v3_core.erl b/lib/compiler/src/v3_core.erl index 7a3c8738b2..2f5db48076 100644 --- a/lib/compiler/src/v3_core.erl +++ b/lib/compiler/src/v3_core.erl @@ -976,6 +976,10 @@ expr({op,L,Op,L0,R0}, St0) -> {#icall{anno=#a{anno=LineAnno}, %Must have an #a{} module=#c_literal{anno=LineAnno,val=erlang}, name=#c_literal{anno=LineAnno,val=Op},args=As},Aps,St1}; +expr({executable_line,L,_}, St0) -> + {#iprimop{anno=#a{anno=lineno_anno(L, St0)}, + name=#c_literal{val=executable_line}, + args=[]},[],St0}; expr({ssa_check_when,L,WantedResult,Args,Tag,Clauses}, St) -> {#c_opaque{anno=full_anno(L, St),val={ssa_check_when,WantedResult,Tag,Args,Clauses}}, [], St}. diff --git a/lib/compiler/test/Makefile b/lib/compiler/test/Makefile index 7efafc5e35..43c7ccec7a 100644 --- a/lib/compiler/test/Makefile +++ b/lib/compiler/test/Makefile @@ -122,6 +122,8 @@ R25= \ bs_utf \ bs_bincomp +COVER=$(NO_OPT) + DIALYZER = bs_match CORE_MODULES = \ @@ -156,6 +158,8 @@ NO_TYPE_OPT_MODULES= $(NO_TYPE_OPT:%=%_no_type_opt_SUITE) NO_TYPE_OPT_ERL_FILES= $(NO_TYPE_OPT_MODULES:%=%.erl) DIALYZER_MODULES= $(DIALYZER:%=%_dialyzer_SUITE) DIALYZER_ERL_FILES= $(DIALYZER_MODULES:%=%.erl) +COVER_MODULES= $(COVER:%=%_cover_SUITE) +COVER_ERL_FILES= $(COVER_MODULES:%=%.erl) ERL_FILES= $(MODULES:%=%.erl) CORE_FILES= $(CORE_MODULES:%=%.core) @@ -190,7 +194,7 @@ DISABLE_SSA_OPT = +no_bool_opt +no_share_opt +no_bsm_opt +no_fun_opt +no_ssa_opt $(NO_CORE_OPT_ERL_FILES) $(NO_CORE_SSA_OPT_ERL_FILES) \ $(INLINE_ERL_FILES) $(R23_ERL_FILES) \ $(NO_MOD_OPT_ERL_FILES) $(NO_TYPE_OPT_ERL_FILES) \ - $(DIALYZER_ERL_FILES) $(R24_ERL_FILES) $(R25_ERL_FILES) + $(DIALYZER_ERL_FILES) $(COVER_ERL_FILES) $(R24_ERL_FILES) $(R25_ERL_FILES) $(ERL_TOP)/make/make_emakefile $(ERL_COMPILE_FLAGS) -o$(EBIN) $(MODULES) \ > $(EMAKEFILE) $(ERL_TOP)/make/make_emakefile +no_copt $(DISABLE_SSA_OPT) +no_postopt \ @@ -215,6 +219,8 @@ make_emakefile: $(NO_OPT_ERL_FILES) $(POST_OPT_ERL_FILES) $(NO_SSA_OPT_ERL_FILES -o$(EBIN) $(CORE_MODULES) >> $(EMAKEFILE) $(ERL_TOP)/make/make_emakefile +no_type_opt $(ERL_COMPILE_FLAGS) \ -o$(EBIN) $(NO_TYPE_OPT_MODULES) >> $(EMAKEFILE) + $(ERL_TOP)/make/make_emakefile +line_coverage $(ERL_COMPILE_FLAGS) \ + -o$(EBIN) $(COVER_MODULES) >> $(EMAKEFILE) $(ERL_TOP)/make/make_emakefile +dialyzer $(ERL_COMPILE_FLAGS) \ -o$(EBIN) $(DIALYZER_MODULES) >> $(EMAKEFILE) @@ -265,6 +271,9 @@ docs: %_no_type_opt_SUITE.erl: %_SUITE.erl sed -e 's;-module($(basename $<));-module($(basename $@));' $< > $@ +%_cover_SUITE.erl: %_SUITE.erl + sed -e 's;-module($(basename $<));-module($(basename $@));' $< > $@ + %_dialyzer_SUITE.erl: %_SUITE.erl sed -e 's;-module($(basename $<));-module($(basename $@));' $< > $@ @@ -288,6 +297,7 @@ release_tests_spec: make_emakefile $(NO_MOD_OPT_ERL_FILES) \ $(NO_SSA_OPT_ERL_FILES) \ $(NO_TYPE_OPT_ERL_FILES) \ + $(COVER_ERL_FILES) \ $(DIALYZER_ERL_FILES) "$(RELSYSDIR)" $(INSTALL_DATA) $(CORE_FILES) "$(RELSYSDIR)" for file in $(ERL_DUMMY_FILES); do \ diff --git a/lib/compiler/test/bs_bincomp_SUITE.erl b/lib/compiler/test/bs_bincomp_SUITE.erl index 41877a9e84..897501338c 100644 --- a/lib/compiler/test/bs_bincomp_SUITE.erl +++ b/lib/compiler/test/bs_bincomp_SUITE.erl @@ -688,6 +688,8 @@ cs_end() -> %% Verify that the allocated size is exact (rounded up to the nearest byte). cs(Bin) -> case ?MODULE of + bs_bincomp_cover_SUITE -> + ok; bs_bincomp_no_opt_SUITE -> ok; bs_bincomp_no_ssa_opt_SUITE -> diff --git a/lib/compiler/test/test_lib.erl b/lib/compiler/test/test_lib.erl index de610062dc..205ec00a96 100644 --- a/lib/compiler/test/test_lib.erl +++ b/lib/compiler/test/test_lib.erl @@ -90,6 +90,7 @@ opt_opts(Mod) -> ({feature,_,enable}) -> true; ({feature,_,disable}) -> true; (inline) -> true; + (line_coverage) -> true; (no_bs_create_bin) -> true; (no_bsm_opt) -> true; (no_bs_match) -> true; @@ -123,7 +124,8 @@ get_data_dir(Config) -> "_inline_SUITE", "_no_module_opt_SUITE", "_no_type_opt_SUITE", - "_no_ssa_opt_SUITE"], + "_no_ssa_opt_SUITE", + "_cover_SUITE"], lists:foldl(fun(Suffix, Acc) -> Opts = [{return,list}], re:replace(Acc, Suffix, "_SUITE", Opts) @@ -144,6 +146,7 @@ is_cloned_mod_1("_no_type_opt_SUITE") -> true; is_cloned_mod_1("_post_opt_SUITE") -> true; is_cloned_mod_1("_inline_SUITE") -> true; is_cloned_mod_1("_no_module_opt_SUITE") -> true; +is_cloned_mod_1("_cover_SUITE") -> true; is_cloned_mod_1([_|T]) -> is_cloned_mod_1(T); is_cloned_mod_1([]) -> false. diff --git a/lib/stdlib/src/erl_expand_records.erl b/lib/stdlib/src/erl_expand_records.erl index 706b5fec6e..c0d2d1bcd3 100644 --- a/lib/stdlib/src/erl_expand_records.erl +++ b/lib/stdlib/src/erl_expand_records.erl @@ -452,7 +452,9 @@ expr({op,Anno,Op,L0,R0}, St0) -> {L,St1} = expr(L0, St0), {R,St2} = expr(R0, St1), {{op,Anno,Op,L,R},St2}; -expr(E={ssa_check_when,_,_,_,_,_}, St) -> +expr({executable_line,_,_}=E, St) -> + {E, St}; +expr({ssa_check_when,_,_,_,_,_}=E, St) -> {E, St}. expr_list([E0 | Es0], St0) -> diff --git a/lib/stdlib/src/erl_lint.erl b/lib/stdlib/src/erl_lint.erl index 107b0970cf..766aade3a0 100644 --- a/lib/stdlib/src/erl_lint.erl +++ b/lib/stdlib/src/erl_lint.erl @@ -2587,6 +2587,8 @@ expr({op,_Anno,_Op,L,R}, Vt, St) -> %% The following are not allowed to occur anywhere! expr({remote,_Anno,M,_F}, _Vt, St) -> {[],add_error(erl_parse:first_anno(M), illegal_expr, St)}; +expr({executable_line,_,_}, _Vt, St) -> + {[], St}; expr({ssa_check_when,_Anno,_WantedResult,_Args,_Tag,_Exprs}, _Vt, St) -> {[], St}. diff --git a/lib/stdlib/src/erl_pp.erl b/lib/stdlib/src/erl_pp.erl index 50ff87643c..a2399886ed 100644 --- a/lib/stdlib/src/erl_pp.erl +++ b/lib/stdlib/src/erl_pp.erl @@ -755,6 +755,8 @@ lexpr({remote,_,M,F}, Prec, Opts) -> %% BIT SYNTAX: lexpr({bin,_,Fs}, _, Opts) -> bit_grp(Fs, Opts); +lexpr({executable_line,Line,Index}, _Prec, _Opts) -> + leaf(format("beam_instruction:executable_line(~p, ~p)", [Line,Index])); %% Special case for straight values. lexpr({value,_,Val}, _,_) -> {value,Val}; -- 2.35.3
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor