123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515openPpxlibmoduleBase_exp_context=Ppxlib.Expansion_context.BasemodulePprintast=Ppxlib_ast.PprintastmoduleConst=Ppxlib.Ast_helper.ConstmoduleExp=Ppxlib.Ast_helper.ExpmodulePat=Ppxlib.Ast_helper.PatmoduleVb=Ppxlib.Ast_helper.Vb(* Mutaml works by transforming an expression
[%expr e+1]
into a test
[%expr
if __is_mutaml_mutant__ "src/lib:42"
then e
else e+1]
thus effectively turning [%expr e+1] into [%expr e]
for mutant number 42 of source file src/lib.ml.
In addition, it records that mutant number 42 in 'src/lib.ml'
is associated with this transformation:
(src/lib,42) -> (loc, e+1, e)
To do so we need
- a generation-time counter (42)
- a reserved OCaml variable __MUTAML_MUTANT__, containing the value of
- an environment variable MUTAML_MUTANT
- a predicate __is_mutaml_mutant
- a store of mutations for each instrumented file
*)(** Returns a new structure with an added mutaml preamble *)letadd_preamblestructureinput_name=letloc=Location.in_fileinput_namein[%strilet__MUTAML_MUTANT__=Stdlib.Sys.getenv_opt"MUTAML_MUTANT"]::[%strilet__is_mutaml_mutant__m=match__MUTAML_MUTANT__withNone->false|Somemutant->String.equalmmutant]::structure(** Write mutations of a file 'src/lib.ml' to a 'src/lib.muts' *)letwrite_muts_fileinput_namemutations=letoutput_name=Filename.(remove_extensioninput_name)^".muts"inPrintf.printf"Writing mutation info to %s\n%!"output_name;letch=open_outoutput_nameinletys=mutations|>List.rev|>List.mapMutaml_common.yojson_of_mutantinYojson.Safe.to_channelch(`Listys);close_outch;output_name(** Appends a file name 'src/lib.muts' to the log-file Mutaml_common.mutaml_mut_file *)letappend_muts_file_to_logoutput_name=letch=open_out_gen[Open_wronly;Open_append;Open_creat;Open_text]0o660Mutaml_common.defaults.mutaml_mut_fileinoutput_stringch(output_name^"\n");close_outch(** Shorthand to ease string-conversion of surface changes *)letstring_of_exp=Pprintast.string_of_expressionmoduleOptions=structletseed=ref0letmut_rate=ref100letgadt=reffalseendmoduleMatch=struct(* exception patterns are only allowed as top-level pattern or inside a top-level or-pattern *)(* https://ocaml.org/manual/patterns.html#sss:exception-match *)letrecpat_matches_exceptionpat=matchpat.ppat_descwith|Ppat_any|Ppat_var_|Ppat_constant_|Ppat_interval_|Ppat_construct_|Ppat_variant_|Ppat_alias_|Ppat_tuple_|Ppat_record_|Ppat_array_|Ppat_constraint_|Ppat_type_|Ppat_lazy_|Ppat_unpack_|Ppat_extension_|Ppat_open_->false|Ppat_exception_->true|Ppat_or(p,p')->pat_matches_exceptionp||pat_matches_exceptionp'letrecpat_is_catch_allpat=matchpat.ppat_descwith|Ppat_constant_|Ppat_interval_|Ppat_construct_|Ppat_variant_|Ppat_array_|Ppat_type_|Ppat_unpack_|Ppat_exception_(* exceptions already filtered *)|Ppat_extension_->false(* safe fallback for extention nodes *)|Ppat_any|Ppat_var_->true(* can act as a catch all at top-level and in tuples+records *)|Ppat_tupleps->List.for_allpat_is_catch_allps|Ppat_record(entries,_flag)->List.for_all(fun(_,p)->pat_is_catch_allp)entries|Ppat_or(p,p')->pat_is_catch_allp||pat_is_catch_allp'|Ppat_alias(p,_)|Ppat_constraint(p,_)|Ppat_lazyp|Ppat_open(_,p)->pat_is_catch_allpletcase_is_catch_allcase=(* lhs when guard -> rhs *)pat_is_catch_allcase.pc_lhs&&case.pc_guard=None&&case.pc_rhs.pexp_desc<>Pexp_unreachableletcases_contain_catch_all=List.existscase_is_catch_allletrecpat_bind_freep=matchp.ppat_descwith|Ppat_any|Ppat_constant_|Ppat_interval_|Ppat_type_|Ppat_construct(_,None)->true|Ppat_var_|Ppat_alias_|Ppat_unpack_|Ppat_variant_(* No mutation of polymophic variants for now *)|Ppat_extension_->false(* safe fall back? *)|Ppat_tupleps|Ppat_arrayps->List.for_allpat_bind_freeps|Ppat_record(es,_)->List.for_all(fun(_,p)->pat_bind_freep)es|Ppat_or(p1,p2)->pat_bind_freep1&&pat_bind_freep2|Ppat_construct(_,Some(_,p'))|Ppat_constraint(p',_)|Ppat_lazyp'|Ppat_exceptionp'(* exceptions should have been filtered *)|Ppat_open(_,p')->pat_bind_freep'(* Two patterns agree for this mutation rewriting (sans GADTs)
- if they do not bind any variables -or-
- if they bind the same variables:
(x,0) and (x,1) agree
0::xs and 1::xs agree
None and Some [] agree
None and Some _ agree
With GADTs enabled constructors must also agree (only the two first above) *)letrecpatterns_agreep1p2=(not!Options.gadt&&(* only enabled with GADTs *)pat_bind_freep1&&pat_bind_freep2)||matchp1.ppat_desc,p2.ppat_descwith|(Ppat_any|Ppat_constant_|Ppat_interval_),(Ppat_any|Ppat_constant_|Ppat_interval_)->true|Ppat_any,Ppat_tupleps->List.for_all(funp2'->patterns_agreep1p2')ps|Ppat_tupleps,Ppat_any->List.for_all(funp1'->patterns_agreep1'p2)ps|Ppat_tupleps,Ppat_tupleps'->(tryList.for_all2patterns_agreepsps'withInvalid_argument_->false)|Ppat_varx,Ppat_vary->x.txt=y.txt|Ppat_alias(p,x),Ppat_alias(p',y)->x.txt=y.txt&&patterns_agreepp'(* GADT constructor can carry existential hidden types *)|Ppat_construct(c,Some(_,p1)),Ppat_construct(c',Some(_,p2))->c.txt=c'.txt&&patterns_agreep1p2|Ppat_any,Ppat_record(es,_fl)->List.for_all(fun(_i2,p2')->patterns_agreep1p2')es|Ppat_record(es,_fl),Ppat_any->List.for_all(fun(_i1,p1')->patterns_agreep1'p2)es|Ppat_record(es,fl),Ppat_record(es',fl')->(* { l1=P1; ...; ln=Pn } or { l1=P1; ...; ln=Pn; _} *)fl=fl'&&(tryList.for_all2(fun(i1,p1)(i2,p2)->i1.txt=i2.txt&&patterns_agreep1p2)(List.sort(fun(i,_)(i',_)->Stdlib.compareii')es)(List.sort(fun(i,_)(i',_)->Stdlib.compareii')es')withInvalid_argument_->false)|Ppat_or(p,p'),_->patterns_agreepp2&&patterns_agreep'p2|_,Ppat_or(p,p')->patterns_agreep1p&&patterns_agreep2p'|Ppat_arrayps,Ppat_arrayps'->(* pattern variables do not generally agree:
| [| (x,_);(0,y) |] -> ... | [| (y,"");(0,x) |] -> ...
but they may do so, despite different length:
| [| (0,_);(x,_);(0,_);_ |] -> ... | [| (x,"") |] -> ...
They can also contain GADT constructors:
| [| Int; x |] -> ... | [| Bool; x |] -> ...
We punt and go with a simple condition (same length)
enabling only few array-pattern collapses. *)(tryList.for_all2patterns_agreepsps'withInvalid_argument_->false)|Ppat_constraint(p,t),Ppat_constraint(p',t')->t.ptyp_desc=t'.ptyp_desc&&patterns_agreepp'|Ppat_lazyp,Ppat_lazyp'->patterns_agreepp'|Ppat_unpackm,Ppat_unpackm'->m.txt=m'.txt|Ppat_open(m,p),Ppat_open(m',p')->m.txt=m'.txt&&patterns_agreepp'|Ppat_variant_,Ppat_variant_(* No mutation of polymophic variants for now *)|_->false(* safe fallback *)letreccases_contain_matching_patternscs=matchcswith|[]|[_]->false|c1::(c2::_ascs')->(not(pat_is_catch_allc2.pc_lhs)(* mutation already covered by 'omit-pattern' *)&&patterns_agreec1.pc_lhsc2.pc_lhs&&c1.pc_rhs.pexp_desc<>Pexp_unreachable&&c2.pc_rhs.pexp_desc<>Pexp_unreachable)||cases_contain_matching_patternscs'end(* Monadic Ppxlib error handling *)letreturn=Ppxlib.With_errors.returnlet(>>=)=Ppxlib.With_errors.(>>=)let(>>|)=Ppxlib.With_errors.(>>|)classmutate_mapper(rs:RS.t)=object(self)inheritPpxlib.Ast_traverse.map_with_expansion_context_and_errorsassupervalmutablemut_count=0valmutablemutations=[]valmutabletmp_var_count=0methodchoose_to_mutate=RS.intrs100<=!Options.mut_ratemethodincr_count=letold_count=mut_countinmut_count<-mut_count+1;old_countmethodmake_tmp_var()=letold=tmp_var_countintmp_var_count<-tmp_var_count+1;Printf.sprintf"__MUTAML_TMP%i__"oldmethodlet_bind~locexp=matchexp.pexp_descwith|Pexp_ident_(* already an identifier - no need to introduce a new one *)|Pexp_constant_->(* no need to let-bind constants either *)Fun.id,exp|_->lettmp=self#make_tmp_var()inlettmp_id=Exp.ident{txt=Lidenttmp;loc}inletconte=Exp.let_~locNonrecursive[Vb.mk(Pat.var{txt=tmp;loc})exp]ein(*let tmp=[%e exp] in e *)cont,tmp_idmethodmake_mut_number_and_idlocctx=letmut_no=self#incr_countinletmut_id=Mutaml_common.make_mut_id(Base_exp_context.input_namectx)mut_noinmut_no,Ast_builder.Default.estring~locmut_idmethodmutaml_mutantctxloce_newe_recrepl_str=letmut_no,mut_id_exp=self#make_mut_number_and_idlocctxinletmutation=Mutaml_common.{number=mut_no;repl=Somerepl_str;loc}inmutations<-mutation::mutations;[%exprif__is_mutaml_mutant__[%emut_id_exp]then[%ee_new]else[%ee_rec]]method!constant_ctxe=returnemethodmutate_constant_ctxc=matchcwith|Pconst_integer(i,None)->(matchiwith(* replace 1 with 0 *)|"1"->Const.integer"0"(*FIXME: choose between this mutation and the below by coin flip *)(* replace literal i with [1+i] - but not l,L,n literals *)|_->Const.int(1+int_of_stringi))(* replace " " strings with "" *)|Pconst_string(" ",loc,None)->Const.string~loc""(* FIXME: add more constant mutations over char,float,int32,int64 *)|_->cmethodmutate_arithmeticctxe=letloc=e.pexp_locin(* arithmetic operator mutations *)(* problem: duplication between the recursively mutated e' (exp1 + exp2')
and the original e (exp + exp')
solution: let-name locally:
let __mutaml_tmp25 = exp2 in
let __mutaml_tmp26 = exp1 in
if __is_mutaml_mutant__ 17
then __mutaml_tmp26 - __mutaml_tmp25
else __mutaml_tmp26 + __mutaml_tmp25 *)matchewith(* A special case mutations: omit 1+ *)|[%expr1+[%e?exp]]->super#expressionctxexp>>|funexp'->(* super avoids mut of exp in 1 + exp *)letk,tmp_var=self#let_bind~loc:exp.pexp_locexp'ink(self#mutaml_mutantctxloc{ewithpexp_desc=tmp_var.pexp_desc}{ewithpexp_desc=[%expr1+[%etmp_var]].pexp_desc}(string_of_expexp))(* Two special case mutations: omit +1/-1 *)|[%expr[%e?exp]+1]|[%expr[%e?exp]-1]->letop=(matche.pexp_descwith|Pexp_apply(op,_args)->op|_->assertfalse)insuper#expressionctxexp>>|funexp'->(* super avoids mut of exp in exp +/- 1 *)letk,tmp_var=self#let_bind~loc:exp.pexp_locexp'ink(self#mutaml_mutantctxloc{ewithpexp_desc=tmp_var.pexp_desc}{ewithpexp_desc=[%expr[%eop][%etmp_var]1].pexp_desc}(string_of_expexp))(* General binary operator mutations:
turn "+" into "-", "-" into "+", "*" into "+", "/" into "mod", "mod" into "/" *)|[%expr[%e?op][%e?exp1][%e?exp2]]->letmut_op={opwithpexp_desc=(matchop.pexp_descwith|Pexp_ident({txt=Lident"+";loc})->Pexp_ident{txt=Lident"-";loc}|Pexp_ident({txt=Lident"-";loc})->Pexp_ident{txt=Lident"+";loc}|Pexp_ident({txt=Lident"*";loc})->Pexp_ident{txt=Lident"+";loc}|Pexp_ident({txt=Lident"/";loc})->Pexp_ident{txt=Lident"mod";loc}|Pexp_ident({txt=Lident"mod";loc})->Pexp_ident{txt=Lident"/";loc}|_->failwith("mutaml_ppx, mutate_arithmetic: found some other operator case: "^(string_of_expop)))}in(* Note: we bind exp2 before exp1 to preserve the current (unspecified) OCaml evaluation order. *)self#expressionctxexp2>>=funexp2'->letk2,tmp_var2=self#let_bind~loc:exp2.pexp_locexp2'inself#expressionctxexp1>>|funexp1'->letk1,tmp_var1=self#let_bind~loc:exp1.pexp_locexp1'ink2(k1(self#mutaml_mutantctxloc{ewithpexp_desc=[%expr[%emut_op][%etmp_var1][%etmp_var2]].pexp_desc}{ewithpexp_desc=[%expr[%eop][%etmp_var1][%etmp_var2]].pexp_desc}(string_of_exp[%expr[%emut_op][%eexp1][%eexp2]])))|_->failwith"mutaml_ppx, mutate_arithmetic: pattern matching on case is was not applied to"method!casesctxcases=super#casesctxcases>>|funcases->(* visit individual cases first *)letcases_exc,cases_pure=List.partition(func->Match.pat_matches_exceptionc.pc_lhs)casesinletcases_contain_catch_all=Match.cases_contain_catch_allcases_pure&&List.lengthcases_pure>=3inifcases_contain_catch_all||Match.cases_contain_matching_patternscases_purethenletinstr_cases=self#mutate_pure_casesctxcases_pure~cases_contain_catch_allininstr_cases@cases_excelsecasesmethodmutate_pure_casesctxcases~cases_contain_catch_all=matchcaseswith|[]|[_]->cases|case1::(case2::_ascases')->letcases'=self#mutate_pure_casesctxcases'~cases_contain_catch_allinifMatch.pat_is_catch_allcase1.pc_lhsthencase1::cases'(* neither match for omit-pattern or merge-consecutive *)elseif(notcases_contain_catch_all&&(not(Match.patterns_agreecase1.pc_lhscase2.pc_lhs)||Match.pat_is_catch_allcase2.pc_lhs))||notself#choose_to_mutatethencase1::cases'else(* Only allocate mutation if we are going to use it *)letloc={case1.pc_lhs.ppat_locwith(* location of entire case: lhs with guard -> rhs *)loc_end=case1.pc_rhs.pexp_loc.loc_end}inletmut_no,mut_id_exp=self#make_mut_number_and_idlocctxinletmut_guard=[%exprnot(__is_mutaml_mutant__[%emut_id_exp])]inletguard=(matchcase1.pc_guardwith|None->Somemut_guard|Someg->Some[%expr[%eg]&&[%emut_guard]])inletcase1'={case1withpc_guard=guard}inifcases_contain_catch_all||case1.pc_guard<>Nonethen(* drop case from pattern-match when there is a '_'-catch all case and >1 additional cases *)(* match f x with match f x with
| A -> g y | A when not (__is_mutaml_mutant__ "test:27") -> g y
| B -> h z ~~> | B when not (__is_mutaml_mutant__ "test:45") -> h z
| _ -> i q | _ -> i q *)(* or if there is pattern containing a 'when'-clause to drop *)(* match f x with match f x with
| B when c -> h z ~~> | B when c && not (__is_mutaml_mutant__ "test:45") -> h z
| B -> i q | B -> i q *)letmutation=Mutaml_common.{number=mut_no;repl=None;loc={locwithloc_end=case2.pc_lhs.ppat_loc.loc_start}}in(* | pat1 when guard1 -> rhs1 | pat2 when guard2 -> rhs2
^---------------------------^
replaced with (i.e. omitted):
| pat2 when guard2 -> rhs2 *)mutations<-mutation::mutations;case1'::cases'else(* merge consecutive cases into an or-pattern | p1 -> r1 | p2 -> r2 ~~> |p1|p2 -> r2 *)(* when no/same variables are bound in each pattern *)(* match f x with match f x with
| A -> g y | A when not (__is_mutaml_mutant__ "test:27") -> g y
| B -> h z ~~> | A | B when not (__is_mutaml_mutant__ "test:45") -> h z
| C -> i q | B | C -> i q *)(matchcases'with(* recurse and glue or-pattern on case2' *)|[]->failwith"mutaml_ppx, mutate_pure_cases: recursing on a non-empty list yielded back an empty one"|case2'::cs'->letor_pat=[%pat?[%pcase1.pc_lhs]|[%pcase2.pc_lhs]]inletrepl_str=Pprintast.patternFormat.str_formatteror_pat;Format.flush_str_formatter()in(* | pat1 when guard1 -> rhs1 | pat2 when guard2 -> rhs2
^------------------------------^
replaced with:
| pat1 | pat2 when guard2 -> rhs2 *)letmutation=Mutaml_common.{number=mut_no;repl=Somerepl_str;(* diff spans to end of pat2 *)loc={locwithloc_end=case2.pc_lhs.ppat_loc.loc_end}}inmutations<-mutation::mutations;letlhs={case2'.pc_lhswithppat_desc=Ppat_or(case1.pc_lhs,case2'.pc_lhs)}inletcase2'_with_or={case2'withpc_lhs=lhs}incase1'::case2'_with_or::cs')method!expressionctxe=letloc=e.pexp_locinmatche,e.pexp_descwith(* asserts represent inline sanity checks/tests - so don't mutate their expressions *)(* Furthermore, [assert false] is recognized as a special case and rewritten
to [raise (Assert_failure ...)] - which is polymorphic:
https://ocaml.org/manual/expr.html#sss:expr-assertion
All other forms of 'assert' have [unit] return type.
This means we can break typing by mutating 'assert false'
when it is used in a "this-should-never-happen"-case:
match something with
| Some x -> i+1
| None -> assert false
which is another reason to avoid mutating that particular form. *)|[%exprassert[%e?_]],_->returne(* swap bool constructors *)|[%exprtrue],_whenself#choose_to_mutate->letfalse_exp={ewithpexp_desc=[%exprfalse].pexp_desc}inreturn(self#mutaml_mutantctxlocfalse_expe(string_of_expfalse_exp))|[%exprfalse],_whenself#choose_to_mutate->lettrue_exp={ewithpexp_desc=[%exprtrue].pexp_desc}inreturn(self#mutaml_mutantctxloctrue_expe(string_of_exptrue_exp))|[%expr[%e?_]+[%e?_]],_|[%expr[%e?_]-[%e?_]],_|[%expr[%e?_]*[%e?_]],_|[%expr[%e?_]/[%e?_]],_|[%expr[%e?_]mod[%e?_]],_whenself#choose_to_mutate->self#mutate_arithmeticctxe|_,Pexp_constantcwhenself#choose_to_mutate->letc'=self#mutate_constantctxcinifc=c'thenreturneelselete_new={ewithpexp_desc=Pexp_constantc'}inreturn(self#mutaml_mutantctxloce_newe(string_of_expe_new))(* we negate an if's condition rather than swapping its branches:
* it avoids duplication
* it works for 1-armed ifs too
if
(let __MUTAML_TMP__ = e0 in
if e0 then e1 else e2 ~~> if __is_mutaml_mutant__ [%e mut_id_exp]
then not __MUTAML_TMP__ else __MUTAML_TMP__)
then e1
else e2 *)|_,Pexp_ifthenelse(e0,e1,e2_opt)whenself#choose_to_mutate->self#expressionctxe0>>=fune0'->self#expressionctxe1>>=fune1'->letconte2_opt'=letk,tmp_var=self#let_bind~loc:e0.pexp_loce0'inlete0'_guarded=k(self#mutaml_mutantctxe0.pexp_loc(*loc*)[%exprnot[%etmp_var]][%expr[%etmp_var]](string_of_exp[%exprnot[%ee0]]))in{ewithpexp_desc=Pexp_ifthenelse(e0'_guarded,e1',e2_opt')}in(matche2_optwith|None->return(contNone)|Somee2->self#expressionctxe2>>|fune2'->cont(Somee2'))(* omit a unit-expression in a sequence:
(if __is_mutaml_mutant__ [%e mut_id_exp]
e0; e1 ~~> then ()
else e0'); e' *)|_,Pexp_sequence(e0,e1)whenself#choose_to_mutate->self#expressionctxe0>>=fune0'->self#expressionctxe1>>|fune1'->lete0''=self#mutaml_mutantctxloc(*e0.pexp_loc*)[%expr()]e0'(string_of_expe1)in{e0withpexp_desc=Pexp_sequence(e0'',e1')}|_,Pexp_functioncases->self#casesctxcases>>|funcases_pure->(* all cases are pure in 'function' *)letfunction_={ewithpexp_desc=Pexp_functioncases_pure}inifMatch.cases_contain_matching_patternscases_purethenExp.attrfunction_(* disable pattern-match warning *){attr_name={txt="ocaml.warning";loc};attr_payload=PStr[[%stri"-8"]];attr_loc=loc}elsefunction_|_,Pexp_match(me,cases)->super#expressionctxme>>=funme->self#casesctxcases>>|funcases->letcases_pure=List.filter(func->not(Match.pat_matches_exceptionc.pc_lhs))casesinletmatch_={ewithpexp_desc=Pexp_match(me,cases)}inifMatch.cases_contain_matching_patternscases_purethenExp.attrmatch_(* disable pattern-match warning *){attr_name={txt="ocaml.warning";loc};attr_payload=PStr[[%stri"-8"]];attr_loc=loc}elsematch_|_->super#expressionctxe(* don't mutate attribute parameters such as 'false' in [@@deriving show {with_path=false}] *)method!attributes_ctxattrs=returnattrsmethodtransform_impl_filectximpl_ast=letinput_name=Base_exp_context.input_namectxinPrintf.printf"Running mutaml instrumentation on \"%s\"\n%!"input_name;Printf.printf"Randomness seed: %i %!"!Options.seed;Printf.printf"Mutation rate: %i %!"!Options.mut_rate;Printf.printf"GADTs enabled: %s\n%!"(Bool.to_string!Options.gadt);letinstrumented_ast,errs=super#structurectximpl_astinleterrs=List.map(funerror->Ast_builder.Default.pstr_extension~loc:(Location.Error.get_locationerror)(Location.Error.to_extensionerror)[])errsinletmut_count=List.lengthmutationsinPrintf.printf"Created %i mutation%s of %s\n%!"mut_count(ifmut_count=1then""else"s")input_name;letoutput_name=write_muts_fileinput_namemutationsinlet()=append_muts_file_to_logoutput_nameinerrs@(add_preambleinstrumented_astinput_name)end