123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959(* This Source Code Form is subject to the terms of the Mozilla Public License,
v. 2.0. If a copy of the MPL was not distributed with this file, You can
obtain one at http://mozilla.org/MPL/2.0/. *)(* Overview
This is the core of Bisect_ppx: the instrumenter that runs on ASTs is defined
here. The instrumenter is divided into two major pieces:
1. The class [instrumenter] traverses ASTs. It decides where instrumentation
should be inserted.
2. The module [Generated_code] provides the helpers that actually insert the
instrumentation. In other words, they insert new leaves into the AST at
the places chosen by [instrumenter].
The code is structured to strongly reflect this division. It is recommended
to read this file with code folding.
Instrumented locations are called {e points}. When the instrumentation code
is executed, the point is {e visited}. Points appear as highlighted
characters in coverage reports.
All state is contained within instances of [instrumenter].
Instances are actually created in [register.ml], which is the "top-level"
side-effecting module of Bisect_ppx, when Bisect_ppx used as a PPX library
(i.e. by PPX drivers).
When Bisect_ppx is used as a standalone executable PPX, the top-level entry
point is in [bisect_ppx.ml]. It's basically a PPX driver that registers only
this instrumenter with itself, using [register.ml], and then runs it. *)(* From ocaml-migrate-parsetree. *)moduleAst=Ast_405moduleLocation=Ast.LocationmoduleParsetree=Ast.ParsetreemodulePat=Ast.Ast_helper.PatmoduleExp=Ast.Ast_helper.ExpmoduleStr=Ast.Ast_helper.StrmoduleCf=Ast.Ast_helper.Cf(* From ppx_tools_versioned. *)moduleAst_convenience=Ast_convenience_405moduleAst_mapper_class=Ast_mapper_class_405moduleGenerated_code:sigtypepointsvalinit:unit->pointsvalinstrument_expr:points->?override_loc:Location.t->Parsetree.expression->Parsetree.expressionvalinstrument_case:points->Parsetree.case->Parsetree.casevalinstrument_class_field_kind:points->Parsetree.class_field_kind->Parsetree.class_field_kindvalruntime_initialization:points->string->Parsetree.structure_itemlistend=structtypepoints=Bisect.Common.point_definitionlistrefletinit()=ref[](* Given an AST for an expression [e], replaces it by the sequence expression
[instrumentation; e], where [instrumentation] is some code that tells
Bisect_ppx, at runtime, that [e] has been visited. *)letinstrument_exprpoints?override_loce=letrecoutline()=letpoint_loc=choose_location_of_point~override_loceinifexpression_should_not_be_instrumented~point_loctheneelseletpoint_index=get_index_of_point_at_location~point_locin[%expr___bisect_visit___[%epoint_index];[%ee]][@metalocpoint_loc]andchoose_location_of_point~override_loce=matchoverride_locwith|Someoverride_loc->override_loc|_->Parsetree.(e.pexp_loc)andexpression_should_not_be_instrumented~point_loc:loc=ifLocation.(loc.loc_ghost)thentrueelse(* Retrieve the expression's file and line number. The file can be
different from the input file to Bisect_ppx, in case of the [#line]
directive.
That is typically emitted by cppo, ocamlyacc, and other
preprocessors. To be intuitive to the user, we need to make the
decision on ignoring this expression based on its original source
location, as seen by the user, not based on where it was spliced in
by another prerocessor that ran before Bisect_ppx. *)letfile,line=letstart=Location.(loc.loc_start)inLexing.(start.pos_fname,start.pos_lnum)inComments.getfile|>Comments.line_is_ignoredlineandget_index_of_point_at_location~point_loc:loc=letpoint_offset=Location.(Lexing.(loc.loc_start.pos_cnum))inletpoint=tryList.find(funpoint->Bisect.Common.(point.offset)=point_offset)!pointswithNot_found->letnew_index=List.length!pointsinletnew_point=Bisect.Common.{offset=point_offset;identifier=new_index}inpoints:=new_point::!points;new_pointinAst_convenience.intpoint.identifierinoutline()(* Instruments a case, as found in [match] and [function] expressions. Cases
contain patterns.
Bisect_ppx treats or-patterns specially. For example, suppose you have
match foo with
| A -> bar
| B -> baz
Both [bar] and [baz] get separate instrumentation points, so that if [A]
is passed, but [B] is never passed, during testing, you will know that [B]
was not tested with.
However, if you refactor to use an or-pattern,
match foo with
| A | B -> bar
and nothing is special is done, the instrumentation point on [bar] covers
both [A] and [B], so you lose the information that [B] is not tested.
The fix for this is a bit tricky, because patterns are not expressions. So,
they can't be instrumented directly. Bisect_ppx instead inserts a special
secondary [match] expression right in front of [bar]:
match foo with
| A | B as ___bisect_matched_value___ ->
(match ___bisect_matched_value___ with
| A -> visited "A"
| B -> visited "B");
bar
So, Bisect_ppx takes that or-pattern [A | B], rotates the "or" out to the
top level (it already is there), splits it into indepedent cases, and
creates a new [match] expression out of them, that allows it to
distinguish, after the fact, which branch was actually taken to reach
[bar].
There are actually several complications to this. The first is that the
generated [match] expression is generally not exhaustive: it only includes
the patterns from the case for which it was generated. This is solved by
adding a catch-all case, and locally suppressing a bunch of warnings:
match foo with
| A | B as ___bisect_matched_value___ ->
(match ___bisect_matched_value___ with
| A -> visited "A"
| B -> visited "B"
| _ (* for C, D, which can't happen here *) -> ())
[@ocaml.warning "..."];
bar
| C | D as ___bisect_matched_value___ ->
(match ___bisect_matched_value___ with
| C -> visited "C"
| D -> visited "D"
| _ (* for A, B, which can't happen here *) -> ())
[@ocaml.warning "..."];;
baz
Next, or-patterns might not be at the top level:
match foo with
| C (A | B) -> bar
has to become
match foo with
| C (A | B) as ___bisect_matched_value___ ->
(match ___bisect_matched_value___ with
| C A -> visited "A"
| C B -> visited "B"
| _ -> ());
bar
This is done by "rotating" the or-pattern to the top level. In this
example, [C (A | B)] is equivalent to [C A | C B]. The latter pattern can
easily be split into cases. This could also be done by aliasing individual
or-patterns, but we did not investigate it.
There might be multiple or-patterns:
match foo with
| C (A | B), D (A | B) -> bar
should become
match foo with
| C (A | B), D (A | B) as ___bisect_matched_value___ ->
(match ___bisect_matched_value___ with
| C A, D A -> visited "A1"; visited "A2"
| C A, D B -> visited "A1"; visited "B2"
| C B, D A -> visited "B1"; visited "A2"
| C B, D B -> visited "B1"; visited "B2"
| _ -> ());
bar
as you can see, or-patterns under and-like patterns (tuples, arrays,
records) get multiplied combinatorially.
The above example also shows that Bisect_ppx needs to mark visisted a
whole list of points in each of the generated cases. For that, the
function that rotates or-patterns to the top level also keeps track of the
original locations of each case of each or-pattern. Each of the resulting
top-level patterns is paired with the list of locations of the or-cases it
contains, visualised above as ["A1"; "A2"], ["A1"; "B2"], etc. These are
termed *location traces*.
Finally, there are some corner cases. First is the exception pattern:
match foo with
| exception (Exit | Failure _) -> bar
should become
match foo with
| exception ((Exit | Failure _) as ___bisect_matched_value___) ->
(match ___bisect_matched_value___ with
| Exit -> visited "Exit"
| Failure _ -> visited "Failure"
| _ -> ());
bar
note that the [as] alias is attached to the payload of [exception], not to
the outer pattern! The latter would be syntactically invalid. Also, we
don't want to generate [exception] cases in the nested [match]: the
exception has already been caught, we are not re-raising and re-catching
it, which just need to know which constructor it was. To deal with this,
we just need to check for the [exception] pattern, and work on its inside
if it is present.
The last corner case is the trivial one. If there no or-patterns, there is
no point in generating a nested [match]:
match foo with
| A as ___bisect_matched_value___ ->
(match ___bisect_matched_value___ with
| A -> visited "A" (* totally redundant *)
| _ -> ());
bar
It's enough to just do
match foo with
| A -> visited "A"; bar
which is pretty much just normal expression instrumentation, though with
location overridden to the location of the pattern.
This is detected when there is only one case after rotating all
or-patterns to the top. If there had been an or-pattern, there would be at
least two cases after rotation.
Handling or-patterns is the most challening thing done here. There are a
few simpler things to consider:
- Pattern guards ([when] clauses) should be instrumented if present.
- We don't instrument [assert false] cases.
- We also don't instrument refutation cases ([| -> .]).
So, without further ado, here is the function that does all this magic: *)letinstrument_casepointscase=letmoduleHelper_types=structtypelocation_trace=Location.tlisttyperotated_case=location_trace*Parsetree.pattern(* The [Parsetree.pattern] above will not contain or-patterns. *)endinletopenHelper_typesinletrecoutline()=ifis_assert_false_or_refutationcasethencaseelseletentire_pattern=Parsetree.(case.pc_lhs)inletloc=Parsetree.(entire_pattern.ppat_loc)inletnon_exception_pattern,reassemble_exception_pattern_if_present=go_into_exception_pattern_if_present~entire_patterninletrotated_cases:rotated_caselist=(* No or-patterns. *)rotate_or_patterns_to_toploc~non_exception_patterninmatchrotated_caseswith|[]->empty_case_listcase(* Should be unreachable. *)|[(location_trace,_)]->no_or_patternscaselocation_trace|_::_::_->letnew_case_pattern_with_alias=add_bisect_matched_value_aliasloc~non_exception_pattern|>reassemble_exception_pattern_if_presentinletnew_case_expr_with_nested_match=generate_nested_matchlocrotated_casesinExp.casenew_case_pattern_with_alias?guard:(instrument_when_clausecase)new_case_expr_with_nested_matchandis_assert_false_or_refutationcase=matchcase.pc_rhswith|[%exprassertfalse]->true|{pexp_desc=Pexp_unreachable;_}->true|_->falseandgo_into_exception_pattern_if_present~entire_pattern:Parsetree.pattern*(Parsetree.pattern->Parsetree.pattern)=matchentire_patternwith|[%pat?exception[%p?nested_pattern]]->(nested_pattern,(funp->Parsetree.{entire_patternwithppat_desc=Ppat_exceptionp}))|_->(entire_pattern,(funp->p))andempty_case_listcase=Parsetree.{casewithpc_rhs=instrument_exprpointscase.pc_rhs;pc_guard=instrument_when_clausecase}andno_or_patternscaselocation_trace=Parsetree.{casewithpc_rhs=instrumentation_for_location_tracecase.pc_rhslocation_trace;pc_guard=instrument_when_clausecase}andinstrument_when_clausecase=matchParsetree.(case.pc_guard)with|None->None|Someguard->Some(instrument_exprpointsguard)andinstrumentation_for_location_traceelocation_trace=location_trace|>List.sort_uniq(funll'->l.Location.loc_start.Lexing.pos_cnum-l'.Location.loc_start.Lexing.pos_cnum)|>List.fold_left(funel->instrument_exprpoints~override_loc:le)eandadd_bisect_matched_value_aliasloc~non_exception_pattern=[%pat?[%pnon_exception_pattern]as___bisect_matched_value___][@metalocloc]andgenerate_nested_matchlocrotated_cases=(rotated_cases|>List.map(fun(location_trace,rotated_pattern)->Exp.caserotated_pattern(instrumentation_for_location_trace[%expr()]location_trace))|>funnested_match_cases->nested_match_cases@[Exp.case[%pat?_][%expr()]]|>Exp.match_~loc([%expr___bisect_matched_value___])|>funnested_match->Exp.attrnested_match(Location.mkloc"ocaml.warning"loc,PStr[[%stri"-4-8-9-11-26-27-28"]])|>funnested_match_with_attribute->[%expr[%enested_match_with_attribute];[%ecase.pc_rhs]])[@metalocloc](* This function works recursively. It should be called with a pattern [p]
(second argument) and its location (first argument).
It evaluates to a list of patterns. Each of these resulting patterns
contains no nested or-patterns. Joining the resulting patterns in a
single or-pattern would create a pattern equivalent to [p].
Each pattern in the list is paired with a list of locations. These are
the locations of the original cases of or-patterns in [p] that were
chosen for the corresponding result pattern. For example:
C (A | B), D (E | F)
becomes the list of pairs
(C A, D E), [loc A, loc E]
(C A, D F), [loc A, loc F]
(C B, D E), [loc B, loc E]
(C B, D F), [loc B, loc F]
During recursion, the invariant on the location is that it is the
location of the nearest enclosing or-pattern, or the entire pattern, if
there is no enclosing or-pattern. *)androtate_or_patterns_to_toploc~non_exception_pattern:rotated_caselist=letrecrecurse~enclosing_locp:rotated_caselist=letloc=Parsetree.(p.ppat_loc)inletattrs=Parsetree.(p.ppat_attributes)inmatchp.ppat_descwith(* If the pattern ends with something trivial, that is not an
or-pattern, and has no nested patterns (so can't have a nested
or-pattern), then that pattern is the only top-level case. The
location trace is just the location of the overall pattern.
Here are some examples of how this plays out. Let's say the entire
pattern was "x". Then the case list will be just "x", with its own
location for the trace.
If the entire pattern was "x as y", this recursive call will return
just "x" with the location of "x as y" for the trace. The wrapping
recursive call will turn the "x" back into "x as y".
If the entire pattern was "A x | B", this recursive call will return
just "x" with the location of "A" (not the whole pattern!). The
wrapping recursive call, for constructor "A", will turn the "x" into
"A x". A yet-higher wrapping recursive call, for the actual
or-pattern, will concatenate this with a second top-level case,
corresponding to "B". *)|Ppat_any|Ppat_var_|Ppat_constant_|Ppat_interval_|Ppat_construct(_,None)|Ppat_variant(_,None)|Ppat_type_|Ppat_unpack_|Ppat_extension_->[([enclosing_loc],p)](* Recursively rotate or-patterns in [p'] to the top. Then, for each
one, re-wrap it in an alias pattern. The location traces are not
affected. *)|Ppat_alias(p',x)->recurse~enclosing_locp'|>List.map(fun(location_trace,p'')->(location_trace,Pat.alias~loc~attrsp''x))(* Same logic as [Ppat_alias]. *)|Ppat_construct(c,Somep')->recurse~enclosing_locp'|>List.map(fun(location_trace,p'')->(location_trace,Pat.construct~loc~attrsc(Somep'')))(* Same logic as [Ppat_alias]. *)|Ppat_variant(c,Somep')->recurse~enclosing_locp'|>List.map(fun(location_trace,p'')->(location_trace,Pat.variant~loc~attrsc(Somep'')))(* Same logic as [Ppat_alias]. *)|Ppat_constraint(p',t)->recurse~enclosing_locp'|>List.map(fun(location_trace,p'')->(location_trace,Pat.constraint_~loc~attrsp''t))(* Same logic as [Ppat_alias]. *)|Ppat_lazyp'->recurse~enclosing_locp'|>List.map(fun(location_trace,p'')->(location_trace,Pat.lazy_~loc~attrsp''))(* Same logic as [Ppat_alias]. *)|Ppat_open(c,p')->recurse~enclosing_locp'|>List.map(fun(location_trace,p'')->(location_trace,Pat.open_~loc~attrscp''))(* Recursively rotate or-patterns in each pattern in [ps] to the top.
Then, take a Cartesian product of the cases, and re-wrap each row in
a replacement tuple pattern.
For example, suppose we have the pair pattern
(A | B, C | D)
The recursive calls will produce lists of rotated cases for each
component pattern:
A | B => [A, loc A]; [B, loc B]
C | D => [C, loc C]; [D, loc D]
We now need every possible combination of one case from the first
component, one case from the second, and so on, and to concatenate
all the location traces accordingly:
[A; C, loc A; loc C]
[A; D, loc A; loc D]
[B; C, loc B; loc C]
[B; D, loc B; loc D]
This is performed by [all_combinations].
Finally, we need to take each one of these rows, and re-wrap the
pattern lists (on the left side) into tuples.
This is typical of "and-patterns", i.e. those that match various
product types (though that carry multiple pieces of data
simultaneously). *)|Ppat_tupleps->ps|>List.map(recurse~enclosing_loc)|>all_combinations|>List.map(fun(location_trace,ps')->(location_trace,Pat.tuple~loc~attrsps'))(* Same logic as for [Ppat_tuple]. *)|Ppat_record(entries,closed)->letlabels,ps=List.splitentriesinps|>List.map(recurse~enclosing_loc)|>all_combinations|>List.map(fun(location_trace,ps')->(location_trace,Pat.record~loc~attrs(List.combinelabelsps')closed))(* Same logic as for [Ppat_tuple]. *)|Ppat_arrayps->ps|>List.map(recurse~enclosing_loc)|>all_combinations|>List.map(fun(location_trace,ps')->location_trace,Pat.array~loc~attrsps')(* For or-patterns, recurse into each branch. Then, concatenate the
resulting case lists. Don't reassemble an or-pattern. *)|Ppat_or(p_1,p_2)->letps_1=recurse~enclosing_loc:p_1.ppat_locp_1inletps_2=recurse~enclosing_loc:p_2.ppat_locp_2inps_1@ps_2(* This should be unreachable in well-formed ASTs, because the caller
strips off the [exception] pattern, and [exception] patterns cannot
ordinarily be nested in other patterns. *)|Ppat_exception_->[](* Performs the Cartesian product operation described at [Ppat_tuple]
above, concatenating location traces along the way.
The argument is rows of top-level case lists (so a list of lists), each
case list resulting from rotating some nested pattern. Since tuples,
arrays, etc., have lists of nested patterns, we have a list of
case lists. *)andall_combinations:rotated_caselistlist->(location_trace*Parsetree.patternlist)list=function|[]->[]|cases::more->letmultiplyproductcases=product|>List.map(fun(location_trace_1,ps)->cases|>List.map(fun(location_trace_2,p)->location_trace_1@location_trace_2,ps@[p]))|>List.flatteninletinitial=cases|>List.map(fun(location_trace,p)->location_trace,[p])inList.fold_leftmultiplyinitialmoreinrecurse~enclosing_loc:locnon_exception_patterninoutline()letinstrument_class_field_kindpoints=function|Parsetree.Cfk_virtual_ascf->cf|Parsetree.Cfk_concrete(o,e)->Cf.concreteo(instrument_exprpointse)letruntime_initializationpointsfile=letloc=Location.in_filefileinletmangled_module_name=letbuffer=Buffer.create((String.lengthfile)*2)infile|>String.iter(function|'A'..'Z'|'a'..'z'|'0'..'9'|'_'asc->Buffer.add_charbufferc|_->Buffer.add_stringbuffer"___");"Bisect_visit___"^(Buffer.contentsbuffer)inletpoint_count=Ast_convenience.int~loc(List.length!points)inletpoints_data=Ast_convenience.str~loc(Bisect.Common.write_points!points)inletfile=Ast_convenience.str~locfilein(* ___bisect_visit___ is a function with a reference to a point count array.
It is called every time a point is visited.
It is scoped in a local module, to ensure that each compilation unit
calls its own ___bisect_visit___ function. In particular, if
___bisect_visit___ is unscoped, the following interaction is possible
between a.ml and b.ml:
a.ml:
let ___bisect_visit___ = (* ... *)
b.ml:
let ___bisect_visit___ = (* ... *)
open A
(* Further calls to ___bisect_visit___ are to A's instance of it! *)
To prevent this, Bisect_ppx generates:
a.ml:
module Bisect_visit___ =
struct
let ___bisect_visit___ = (* ... *)
end
open Bisect_visit___ (* Scope of open is only a.ml. *)
b.ml:
module Bisect_visit___ =
struct
let ___bisect_visit___ = (* ... *)
end
open Bisect_visit___
(* Since this open is prepended to b.ml, it is guaranteed to precede any
open A. At the same time, open A introduces Bisect_visit___ into
scope, not ___bisect_visit___. So, after this point, any unqualified
reference to ___bisect_visit___ is to b.ml's instance. *)
open A
Bisect_ppx needs to mangle the generated module names, to make them
unique. Otherwise, including A in B triggers a duplicate module
Bisect_visit___ error. This is better than mangling ___bisect_visit___
itself for two reasons:
1. A collision of mangled module names (due to include) is a compile-time
error. By comparison, a collusion of mangled function names will
result in one silently shadowing the other, which *may* produce a
runtime error if (1) the shadowing function has a smaller points array
than the shadowed function and (2) the shadowing function is actually
called with a large enough point index during testing. If shadowing
does not produce a runtime error, it can result in inaccurate coverage
statistics being silently accumulated.
2. ___bisect_visit___, sprinked throughout the code, can be kept
unmangled. This keeps the mangling generation code local to this
instrumentation function, which generates only the top of each
instrumented module. That keeps the instrumenter relatively simple.
For discussion, see
https://github.com/aantron/bisect_ppx/issues/160 *)letgenerated_module=letbisect_visit_function=[%strilet___bisect_visit___=letpoint_definitions=[%epoints_data]inlet`Stagedcb=Bisect.Runtime.register_file[%efile]~point_count:[%epoint_count]~point_definitionsincb][@metalocloc]inletopenAst.Ast_helperinStr.module_~loc@@Mb.mk~loc(Location.mklocmangled_module_nameloc)(Mod.structure~loc[bisect_visit_function])inletmodule_open=letopenAst.Ast_helperin(* This requires the assumption that the mangled module name doesn't have
any periods. *)Str.open_~loc@@Opn.mk~loc(Ast_convenience.lid~locmangled_module_name)in[generated_module;module_open]end(* The actual "instrumenter" object, instrumenting expressions. *)classinstrumenter=letpoints=Generated_code.init()inletinstrument_expr=Generated_code.instrument_exprpointsinletinstrument_case=Generated_code.instrument_casepointsinletinstrument_class_field_kind=Generated_code.instrument_class_field_kindpointsinobject(self)inheritAst_mapper_class.mapperassupermethod!class_exprce=letloc=ce.pcl_locinletce=super#class_exprceinmatchce.pcl_descwith|Pcl_apply(ce,args)->letargs=List.map(fun(label,e)->(label,(instrument_expre)))argsinAst.Ast_helper.Cl.apply~loc~attrs:ce.pcl_attributesceargs|_->cemethod!class_fieldcf=letloc=cf.pcf_locinletattrs=cf.pcf_attributesinletcf=super#class_fieldcfinmatchcf.pcf_descwith|Pcf_val(name,mutable_,cf)->Cf.val_~loc~attrsnamemutable_(instrument_class_field_kindcf)|Pcf_method(name,private_,cf)->Cf.method_~loc~attrsnameprivate_(instrument_class_field_kindcf)|Pcf_initializere->Cf.initializer_~loc~attrs(instrument_expre)|_->cfmethod!expre=letloc=e.pexp_locinletattrs=e.pexp_attributesinlete'=super#expreinmatche'.pexp_descwith|Pexp_let(rec_flag,bindings,e)->letbindings=List.map(funbinding->Parsetree.{bindingwithpvb_expr=instrument_exprbinding.pvb_expr})bindingsinExp.let_~loc~attrsrec_flagbindings(instrument_expre)|Pexp_poly(e,type_)->Exp.poly~loc~attrs(instrument_expre)type_|Pexp_fun(label,default_value,p,e)->letdefault_value=matchdefault_valuewith|None->None|Somedefault_value->Some(instrument_exprdefault_value)inExp.fun_~loc~attrslabeldefault_valuep(instrument_expre)|Pexp_apply(e_function,[label_1,e1;label_2,e2])->beginmatche_functionwith|[%expr(&&)]|[%expr(&)]|[%expr(||)]|[%expr(or)]->Exp.apply~loc~attrse_function[label_1,(instrument_expre1);label_2,(instrument_expre2)]|[%expr(|>)]->Exp.apply~loc~attrse_function[label_1,e1;label_2,(instrument_expre2)]|_->e'end|Pexp_match(e,cases)->List.mapinstrument_casecases|>Exp.match_~loc~attrse|Pexp_functioncases->List.mapinstrument_casecases|>Exp.function_~loc~attrs|Pexp_try(e,cases)->List.mapinstrument_casecases|>Exp.try_~loc~attrse|Pexp_ifthenelse(condition,then_,else_)->Exp.ifthenelse~loc~attrscondition(instrument_exprthen_)(matchelse_with|Somee->Some(instrument_expre)|None->None)|Pexp_sequence(e1,e2)->Exp.sequence~loc~attrse1(instrument_expre2)|Pexp_while(condition,body)->Exp.while_~loc~attrscondition(instrument_exprbody)|Pexp_for(variable,initial,bound,direction,body)->Exp.for_~loc~attrsvariableinitialbounddirection(instrument_exprbody)|_->e'method!structure_itemsi=letloc=si.pstr_locinmatchsi.pstr_descwith|Pstr_value(rec_flag,bindings)->letbindings=bindings|>List.mapbeginfunbinding->(* Only instrument things not excluded. *)letmaybe_name=letopenParsetreeinmatchbinding.pvb_pat.ppat_descwith|Ppat_varident|Ppat_constraint({ppat_desc=Ppat_varident;_},_)->Someident|_->Noneinletdo_not_instrument=matchmaybe_namewith|Somename->Exclusions.contains_valueLocation.(Lexing.(name.loc.loc_start.pos_fname))name.txt|None->falseinifdo_not_instrumentthenbindingelse{bindingwithpvb_expr=instrument_expr(self#exprbinding.pvb_expr)}endinStr.value~locrec_flagbindings|Pstr_eval(e,a)->Str.eval~loc~attrs:a(instrument_expr(self#expre))|_->super#structure_itemsi(* Don't instrument payloads of extensions and attributes. *)method!extensione=emethod!attributea=a(* This is set to [true] when the [structure] or [signature] method is
called the first time. It is used to determine whether Bisect_ppx is
looking at the top-level structure (module) in the file, or a nested
structure (module).
For [.mli] and [.rei] files, the [signature] method will be called first.
That method will set this variable to [true], and do nothing else.
The more interesting case is [.ml] and [.re] files. For those, the
[structure] method will be called first. That method will set this
variable to [true]. However, if the variable started out [false],
[structure] will insert Bisect_ppx initialization code into the
structure. *)valmutablesaw_top_level_structure_or_signature=falsemethod!signatureast=ifnotsaw_top_level_structure_or_signaturethensaw_top_level_structure_or_signature<-true;super#signatureastmethod!structureast=ifsaw_top_level_structure_or_signaturethensuper#structureast(* This is *not* the first structure we see, or we are inside an
interface file, so the structure is nested within the file, either
inside [struct]..[end] or in an attribute or extension point.
Traverse the structure recursively as normal. *)elsebegin(* This is the first structure we see in te file, and we are not in an
interface file, so Bisect_ppx is beginning to (potentially)
instrument the current file. We need to check whether this file is
excluded from instrumentation before proceeding. *)saw_top_level_structure_or_signature<-true;(* Bisect_ppx is hardcoded to ignore files with certain names. If we
have one of these, return the AST uninstrumented. In particular, do
not recurse into it. *)letalways_ignore_paths=["//toplevel//";"(stdin)"]inletalways_ignore_basenames=[".ocamlinit";"topfind"]inletalways_ignorepath=List.mempathalways_ignore_paths||List.mem(Filename.basenamepath)always_ignore_basenamesinifalways_ignore!Location.input_namethenastelse(* The file might also be excluded by the user. *)ifExclusions.contains_file!Location.input_namethenastelsebegin(* This file should be instrumented. Traverse the AST recursively,
then prepend some generated code for initializing the Bisect_ppx
runtime and telling it about the instrumentation points in this
file. *)letinstrumented_ast=super#structureastinletruntime_initialization=Generated_code.runtime_initializationpoints!Location.input_nameinruntime_initialization@instrumented_astendendend