123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857(****************************************************************************)(* *)(* This file is part of MOPSA, a Modular Open Platform for Static Analysis. *)(* *)(* Copyright (C) 2018-2019 The MOPSA Project. *)(* *)(* This program is free software: you can redistribute it and/or modify *)(* it under the terms of the GNU Lesser General Public License as published *)(* by the Free Software Foundation, either version 3 of the License, or *)(* (at your option) any later version. *)(* *)(* This program is distributed in the hope that it will be useful, *)(* but WITHOUT ANY WARRANTY; without even the implied warranty of *)(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *)(* GNU Lesser General Public License for more details. *)(* *)(* You should have received a copy of the GNU Lesser General Public License *)(* along with this program. If not, see <http://www.gnu.org/licenses/>. *)(* *)(****************************************************************************)(** A simple graph library to represent control-flow graphs.
Implementation.
*)openGraphSig(*==========================================================================*)(** {2 Ordered, hashable data types} *)(*==========================================================================*)(** Use the polymorphic comparison, equality, and hashing. *)moduleIdGeneric(T:sigtypetend):(ID_TYPEwithtypet=T.t)=structtypet=T.tletcompare(x:t)(y:t)=Stdlib.comparexyletequal(x:t)(y:t)=(x=y)lethash(x:t)=Hashtbl.hashxendmoduleIdInt=IdGeneric(structtypet=intend)moduleIdString=IdGeneric(structtypet=stringend)moduleIdUnit:(ID_TYPEwithtypet=unit)=structtypet=unitletcomparexy=0letequalxy=truelethashx=0endmoduleIdPair(A:ID_TYPE)(B:ID_TYPE):(ID_TYPEwithtypet=A.t*B.t)=structtypet=A.t*B.tletcompare(a1,b1)(a2,b2)=matchA.comparea1a2with|0->B.compareb2b2|x->xletequal(a1,b1)(a2,b2)=A.equala1a2&&B.equalb1b2lethash(a,b)=A.hasha+B.hashbend(*==========================================================================*)(** {2 Nested lists} *)(*==========================================================================*)(** Printers. *)letrecpp_nested_listpp_elemfmt=function|Simplex->pp_elemfmtx|Composedl->pp_nested_list_listpp_elemfmtlandpp_nested_list_listpp_elemfmtl=Format.fprintffmt"@[<hov 2>%a@]"(ListExt.fprintListExt.printer_list(pp_nested_listpp_elem))l(*==========================================================================*)(** {2 Graph Functor} *)(*==========================================================================*)moduleMake(P:P):(SwithmoduleP=P)=struct(*========================================================================*)(** {2 Types} *)(*========================================================================*)moduleP=Ptypenode_id=P.NodeId.ttypeedge_id=P.EdgeId.ttypeport=P.Port.tmoduleNodeHash=Hashtbl.Make(P.NodeId)moduleEdgeHash=Hashtbl.Make(P.EdgeId)moduleNodeMap=MapExt.Make(P.NodeId)moduleEdgeMap=MapExt.Make(P.EdgeId)moduleNodeSet=SetExt.Make(P.NodeId)moduleEdgeSet=SetExt.Make(P.EdgeId)type('n,'e)node={n_id:node_id;mutablen_data:'n;mutablen_in:(port*('n,'e)edge)list;mutablen_out:(port*('n,'e)edge)list;}and('n,'e)edge={e_id:edge_id;mutablee_data:'e;mutablee_src:(port*('n,'e)node)list;mutablee_dst:(port*('n,'e)node)list;}and('n,'e)graph={mutableg_entries:(port*('n,'e)node)list;mutableg_exits:(port*('n,'e)node)list;mutableg_nodes:('n,'e)nodeNodeHash.t;mutableg_edges:('n,'e)edgeEdgeHash.t;}(*========================================================================*)(** {2 Internal utilities} *)(*========================================================================*)letnode_eqn1n2=P.NodeId.equaln1.n_idn2.n_idletnode_neqn1n2=not(P.NodeId.equaln1.n_idn2.n_id)letedge_eqe1e2=P.EdgeId.equale1.e_ide2.e_idletedge_neqe1e2=not(P.EdgeId.equale1.e_ide2.e_id)letport_eqt1t2=P.Port.equalt1t2letport_neqt1t2=not(P.Port.equalt1t2)letport_node_eq(t1,n1)(t2,n2)=node_eqn1n2&&port_eqt1t2letport_node_neq(t1,n1)(t2,n2)=node_neqn1n2||port_neqt1t2letport_edge_eq(t1,e1)(t2,e2)=edge_eqe1e2&&port_eqt1t2letport_edge_neq(t1,e1)(t2,e2)=edge_neqe1e2||port_neqt1t2letnode_comparen1n2=P.NodeId.comparen1.n_idn2.n_idletedge_comparee1e2=P.EdgeId.comparee1.e_ide2.e_idletport_comparet1t2=P.Port.comparet1t2letport_node_compare(t1,n1)(t2,n2)=matchport_comparet1t2with0->node_comparen1n2|x->xletport_edge_compare(t1,e1)(t2,e2)=matchport_comparet1t2with0->edge_comparee1e2|x->xletfilter_portportl=List.mapsnd(List.filter(fun(port',_)->port_eqportport')l)(*========================================================================*)(** {2 Construction} *)(*========================================================================*)letcreate()={g_entries=[];g_exits=[];g_nodes=NodeHash.create16;g_edges=EdgeHash.create16;}letadd_nodegid?(inc=[])?(out=[])?entry?exitdata=ifNodeHash.memg.g_nodesidtheninvalid_arg"Node identifier already present in Graph.add_node";letn={n_id=id;n_data=data;n_in=inc;n_out=out;}inList.iter(fun(port,e)->e.e_dst<-(port,n)::e.e_dst)inc;List.iter(fun(port,e)->e.e_src<-(port,n)::e.e_src)out;(matchentrywith|Someentry->g.g_entries<-(entry,n)::g.g_entries|None->());(matchexitwith|Someexit->g.g_exits<-(exit,n)::g.g_exits|None->());NodeHash.addg.g_nodesidn;nletadd_edgegid?(src=[])?(dst=[])data=ifEdgeHash.memg.g_edgesidtheninvalid_arg"Edge identifier already present in Graph.add_node";lete={e_id=id;e_data=data;e_src=src;e_dst=dst;}inList.iter(fun(port,n)->n.n_out<-(port,e)::n.n_out)src;List.iter(fun(port,n)->n.n_in<-(port,e)::n.n_in)dst;EdgeHash.addg.g_edgeside;eletremove_nodegn=ifNodeHash.memg.g_nodesn.n_idthen(List.iter(fun(_,e)->e.e_dst<-List.filter(fun(_,n')->node_neqnn')e.e_dst)n.n_in;List.iter(fun(_,e)->e.e_src<-List.filter(fun(_,n')->node_neqnn')e.e_src)n.n_out;n.n_in<-[];n.n_out<-[];NodeHash.removeg.g_nodesn.n_id;g.g_entries<-List.filter(fun(_,n')->node_neqnn')g.g_entries;g.g_exits<-List.filter(fun(_,n')->node_neqnn')g.g_exits)letremove_edgege=ifEdgeHash.memg.g_edgese.e_idthen(List.iter(fun(_,n)->n.n_out<-List.filter(fun(_,e')->edge_neqee')n.n_out)e.e_src;List.iter(fun(_,n)->n.n_in<-List.filter(fun(_,e')->edge_neqee')n.n_in)e.e_dst;e.e_src<-[];e.e_dst<-[];EdgeHash.removeg.g_edgese.e_id;)letnode_set_entrygnentry=g.g_entries<-List.filter(fun(_,n')->node_neqnn')g.g_entries;(matchentrywith|Someentry->g.g_entries<-(entry,n)::g.g_entries|None->())letnode_set_exitgnexit=g.g_exits<-List.filter(fun(_,n')->node_neqnn')g.g_exits;(matchexitwith|Someexit->g.g_exits<-(exit,n)::g.g_exits|None->())letnode_add_innporte=n.n_in<-(port,e)::n.n_in;e.e_dst<-(port,n)::e.e_dstletnode_add_outnporte=n.n_out<-(port,e)::n.n_out;e.e_src<-(port,n)::e.e_srcletnode_add_in_listnv=List.iter(fun(port,e)->node_add_innporte)vletnode_add_out_listnv=List.iter(fun(port,e)->node_add_outnporte)vletedge_add_srceportn=node_add_outnporteletedge_add_dsteportn=node_add_innporteletedge_add_src_listev=List.iter(fun(port,n)->node_add_outnporte)vletedge_add_dst_listev=List.iter(fun(port,n)->node_add_innporte)vletnode_remove_in_portnporte=n.n_in<-List.filter(port_edge_neq(port,e))n.n_in;e.e_dst<-List.filter(port_node_neq(port,n))e.e_dstletnode_remove_out_portnporte=e.e_src<-List.filter(port_node_neq(port,n))e.e_src;n.n_out<-List.filter(port_edge_neq(port,e))n.n_outletnode_remove_inne=n.n_in<-List.filter(fun(_,e')->edge_neqee')n.n_in;e.e_dst<-List.filter(fun(_,n')->node_neqnn')e.e_dstletnode_remove_outne=n.n_out<-List.filter(fun(_,e')->edge_neqee')n.n_out;e.e_src<-List.filter(fun(_,n')->node_neqnn')e.e_srcletnode_remove_all_inn=List.iter(fun(port,e)->e.e_dst<-List.filter(port_node_neq(port,n))e.e_dst)n.n_in;n.n_in<-[]letnode_remove_all_outn=List.iter(fun(port,e)->e.e_src<-List.filter(port_node_neq(port,n))e.e_src)n.n_out;n.n_out<-[]letedge_remove_src_porteportn=node_remove_out_portnporteletedge_remove_dst_porteportn=node_remove_in_portnporteletedge_remove_srcen=node_remove_outneletedge_remove_dsten=node_remove_inneletedge_remove_all_srce=List.iter(fun(port,n)->n.n_out<-List.filter(port_edge_neq(port,e))n.n_out)e.e_src;e.e_src<-[]letedge_remove_all_dste=List.iter(fun(port,n)->n.n_in<-List.filter(port_edge_neq(port,e))n.n_in)e.e_dst;e.e_dst<-[]letnode_set_innv=node_remove_all_inn;node_add_in_listnvletnode_set_outnv=node_remove_all_inn;node_add_out_listnvletedge_set_srcev=edge_remove_all_srce;edge_add_src_listevletedge_set_dstev=edge_remove_all_dste;edge_add_dst_listev(*========================================================================*)(** {2 Exploration} *)(*========================================================================*)letnode_listg=NodeHash.fold(fun_nacc->n::acc)g.g_nodes[]letedge_listg=EdgeHash.fold(fun_eacc->e::acc)g.g_edges[]letnode_setg=NodeSet.of_list(NodeHash.fold(funid_acc->id::acc)g.g_nodes[])letedge_setg=EdgeSet.of_list(EdgeHash.fold(funid_acc->id::acc)g.g_edges[])letmap_nodesfg=NodeHash.fold(funidnacc->NodeMap.addid(fidn)acc)g.g_nodesNodeMap.emptyletmap_edgesfg=EdgeHash.fold(funidnacc->EdgeMap.addid(fidn)acc)g.g_edgesEdgeMap.emptyletnode_mapg=map_nodes(fun_n->n)gletedge_mapg=map_edges(fun_e->e)glethas_nodegid=NodeHash.memg.g_nodesidlethas_edgegid=EdgeHash.memg.g_edgesidletget_nodegid=NodeHash.findg.g_nodesidletget_edgegid=EdgeHash.findg.g_edgesidletentriesg=g.g_entriesletexitsg=g.g_exitsletedge_ide=e.e_idletedge_datae=e.e_dataletedge_set_dataedata=e.e_data<-dataletedge_srce=e.e_srcletedge_dste=e.e_dstletedge_src_porteport=filter_portport(edge_srce)letedge_dst_porteport=filter_portport(edge_dste)letedge_src_sizee=List.length(edge_srce)letedge_dst_sizee=List.length(edge_dste)letedge_src_port_sizeeport=List.length(edge_src_porteport)letedge_dst_port_sizeeport=List.length(edge_dst_porteport)letnode_idn=n.n_idletnode_datan=n.n_dataletnode_set_datandata=n.n_data<-dataletnode_inn=n.n_inletnode_outn=n.n_outletnode_in_portnport=filter_portport(node_inn)letnode_out_portnport=filter_portport(node_outn)letnode_in_sizen=List.length(node_inn)letnode_out_sizen=List.length(node_outn)letnode_in_port_sizenport=List.length(node_in_portnport)letnode_out_port_sizenport=List.length(node_out_portnport)letnode_entry_portgn=trySome(fst(List.find(fun(_,n')->node_eqnn')g.g_entries))withNot_found->Noneletnode_exit_portgn=trySome(fst(List.find(fun(_,n')->node_eqnn')g.g_exits))withNot_found->Noneletnode_has_outne=List.exists(fun(_,e')->edge_eqee')n.n_outletnode_has_out_portnporte=List.exists(port_edge_eq(port,e))n.n_outletnode_has_inne=List.exists(fun(_,e')->edge_eqee')n.n_inletnode_has_in_portnporte=List.exists(port_edge_eq(port,e))n.n_inletedge_has_srcen=List.exists(fun(_,n')->node_eqnn')e.e_srcletedge_has_src_porteportn=List.exists(port_node_eq(port,n))e.e_srcletedge_has_dsten=List.exists(fun(_,n')->node_eqnn')e.e_dstletedge_has_dst_porteportn=List.exists(port_node_eq(port,n))e.e_dstletnode_out_nodesn=List.concat(List.map(fun(port1,e)->List.map(fun(port2,n2)->(port1,e,port2,n2))e.e_dst)n.n_out)letnode_in_nodesn=List.concat(List.map(fun(port1,e)->List.map(fun(port2,n2)->(n2,port2,e,port1))e.e_src)n.n_in)letnode_out_nodes_portnport1port2=List.concat(List.map(fun(port,e)->ifport_neqportport1then[]elseList.map(fun(_,n2)->(e,n2))(List.filter(fun(port,n2)->port_eqportport2)e.e_dst))n.n_out)letnode_in_nodes_portnport1port2=List.concat(List.map(fun(port,e)->ifport_neqportport1then[]elseList.map(fun(_,n2)->(n2,e))(List.filter(fun(port,n2)->port_eqportport2)e.e_src))n.n_in)letnode_has_node_outn1n2=List.exists(fun(_,e)->List.exists(fun(_,n)->node_eqnn2)e.e_dst)n1.n_outletnode_has_node_inn1n2=List.exists(fun(_,e)->List.exists(fun(_,n)->node_eqnn2)e.e_src)n1.n_inletnode_has_node_out_portn1port1port2n2=List.exists(fun(port,e)->port_eqportport1&&List.exists(port_node_eq(port2,n2))e.e_dst)n1.n_outletnode_has_node_in_portn1port1port2n2=List.exists(fun(port,e)->port_eqportport1&&List.exists(port_node_eq(port2,n2))e.e_src)n1.n_inletnode_add_in_uniquenporte=ifnot(node_has_in_portnporte)thennode_add_innporteletnode_add_out_uniquenporte=ifnot(node_has_out_portnporte)thennode_add_outnporteletnode_add_in_list_uniquenv=List.iter(fun(port,e)->node_add_in_uniquenporte)vletnode_add_out_list_uniquenv=List.iter(fun(port,e)->node_add_out_uniquenporte)vletedge_add_src_uniqueeportn=node_add_out_uniquenporteletedge_add_dst_uniqueeportn=node_add_in_uniquenporteletedge_add_src_list_uniqueev=List.iter(fun(port,n)->node_add_out_uniquenporte)vletedge_add_dst_list_uniqueev=List.iter(fun(port,n)->node_add_in_uniquenporte)vletnode_set_in_uniquenv=node_remove_all_inn;node_add_in_list_uniquenvletnode_set_out_uniquenv=node_remove_all_inn;node_add_out_list_uniquenvletedge_set_src_uniqueev=edge_remove_all_srce;edge_add_src_list_uniqueevletedge_set_dst_uniqueev=edge_remove_all_dste;edge_add_dst_list_uniqueev(*========================================================================*)(** {2 Maps and folds} *)(*========================================================================*)letclone_mapnmapemapg=letgg=create()in(* map data *)NodeHash.iter(funidn->ignore(add_nodeggid(nmapn.n_data)))g.g_nodes;EdgeHash.iter(funide->ignore(add_edgeggid(emape.e_data)))g.g_edges;(* fix in/out, src/dst *)NodeHash.iter(funidn->letnn=get_nodeggidinnn.n_in<-List.map(fun(port,e)->port,get_edgegge.e_id)n.n_in;nn.n_out<-List.map(fun(port,e)->port,get_edgegge.e_id)n.n_out)g.g_nodes;EdgeHash.iter(funide->letee=get_edgeggidinee.e_src<-List.map(fun(port,n)->port,get_nodeggn.n_id)e.e_src;ee.e_dst<-List.map(fun(port,n)->port,get_nodeggn.n_id)e.e_dst)g.g_edges;gg.g_entries<-List.map(fun(port,n)->port,get_nodeggn.n_id)g.g_entries;gg.g_exits<-List.map(fun(port,n)->port,get_nodeggn.n_id)g.g_exits;ggletcloneg=clone_map(funn->n)(fune->e)glettransposeg=NodeHash.iter(fun_n->leta=n.n_ininn.n_in<-n.n_out;n.n_out<-a)g.g_nodes;EdgeHash.iter(fun_e->leta=e.e_srcine.e_src<-e.e_dst;e.e_dst<-a)g.g_edges;leta=g.g_entriesing.g_entries<-g.g_exits;g.g_exits<-aletiter_nodesfg=NodeHash.iter(funidn->fidn)g.g_nodesletiter_edgesfg=EdgeHash.iter(funide->fide)g.g_edgesletfold_nodesfga=NodeHash.fold(funidna->fidna)g.g_nodesaletfold_edgesfga=EdgeHash.fold(funidea->fidea)g.g_edgesaletmap_nodes_orderedfg=NodeMap.mapif(node_mapg)letmap_edges_orderedfg=EdgeMap.mapif(edge_mapg)letiter_nodes_orderedfg=NodeMap.iterf(node_mapg)letiter_edges_orderedfg=EdgeMap.iterf(edge_mapg)letfold_nodes_orderedfga=NodeMap.foldf(node_mapg)aletfold_edges_orderedfga=EdgeMap.foldf(edge_mapg)a(*========================================================================*)(** {Simplification} *)(*========================================================================*)letremove_orphang=iter_nodes(fun_n->ifn.n_in=[]&&n.n_out=[]thenremove_nodegn)g;iter_edges(fun_e->ife.e_src=[]&&e.e_dst=[]thenremove_edgege)g(*========================================================================*)(** {Topological ordering} *)(*========================================================================*)(* Bourdoncle's algorithm to compute a weak topological order by
hierarchical decomposition into strongly connected components
(FMPA'93, p. 128-141, 1993, Springer).
*)letweak_topological_orderg=letstack=Stack.create()inletindex=NodeHash.create16inletidx=ref0in(* Tarjan's strongly connected component algorithm *)letrecvisitnodeacc=Stack.push(node_idnode)stack;incridx;letorghead=!idxinNodeHash.replaceindex(node_idnode)orghead;letacc,head,loop=List.fold_left(fun(acc,head,loop)(_,_,_,succ)->letacc,min=ifNodeHash.memindex(node_idsucc)thenacc,NodeHash.findindex(node_idsucc)elsevisitsuccaccinifmin>=0&&min<=headthenacc,min,trueelseacc,head,loop)(acc,orghead,false)(node_out_nodesnode)inletacc=ifhead=orgheadthen(NodeHash.replaceindex(node_idnode)(-1);letelem=Stack.popstackinifloopthenletrecpop_allelem=ifnot(P.NodeId.equal(node_idnode)elem)then(NodeHash.removeindexelem;pop_all(Stack.popstack))inpop_allelem;(Composed(componentnode))::accelse(Simplenode)::acc)elseaccinacc,head(* recursively decompose a strongly connected component *)andcomponentnode=letacc=List.fold_left(funacc(_,_,_,succ)->ifNodeHash.memindex(node_idsucc)thenaccelsefst(visitsuccacc))[](node_out_nodesnode)in(Simplenode)::accinList.fold_left(funacc(_,node)->ifNodeHash.memindex(node_idnode)thenaccelsefst(visitnodeacc))[](entriesg)letwidening_pointsl=letrecadd_headacc=function|(Simplex)::_->x::acc|_->accanditeracc=function|Simple_->acc|Composedl->List.fold_leftiter(add_headaccl)linList.fold_leftiter[]l(*========================================================================*)(** {2 Printing} *)(*========================================================================*)type('n,'e)printer={print_node:Format.formatter->('n,'e)node->unit;print_edge:Format.formatter->('n,'e)edge->unit;print_src:Format.formatter->('n,'e)node->port->('n,'e)edge->unit;print_dst:Format.formatter->('n,'e)edge->port->('n,'e)node->unit;print_entry:Format.formatter->('n,'e)node->port->unit;print_exit:Format.formatter->('n,'e)node->port->unit;}letprintpfmtg=(* ordering *)letnodes=NodeHash.foldNodeMap.addg.g_nodesNodeMap.emptyin(* ensure that each edge is printer only once *)letedges=EdgeHash.create16in(* print each node *)NodeMap.iter(funidn->(matchnode_entry_portgnwith|None->()|Someport->p.print_entryfmtnport);p.print_nodefmtn;(matchnode_exit_portgnwith|None->()|Someport->p.print_exitfmtnport);List.iter(fun(_,e)->ifnot(EdgeHash.memedgese.e_id)then(EdgeHash.addedgese.e_id();List.iter(fun(port,n)->p.print_srcfmtnporte)(List.sortport_node_comparee.e_src);p.print_edgefmte;List.iter(fun(port,n)->p.print_dstfmteportn)(List.sortport_node_comparee.e_dst)))(List.sortport_edge_comparen.n_out))nodestype('n,'e)dot_printer={dot_pp_node:Format.formatter->('n,'e)node->unit;dot_pp_edge:Format.formatter->('n,'e)edge->unit;dot_pp_port:Format.formatter->port->unit;dot_filter_node:('n,'e)node->bool;dot_filter_edge:('n,'e)edge->bool;dot_filter_port:port->bool;}letprint_dotpnamefmtg=(* printing with escaped new lines *)letbuf=Buffer.create16inletsfmt=Format.formatter_of_bufferbufinletto_stringpx=Buffer.clearbuf;Format.fprintfsfmt"@[<v>%a@]@?"px;lets=Buffer.contentsbufinletss=String.split_on_char'\n'sinifList.lengthss<=1thenselse(String.concat"\\l"ss)^"\\l"in(* numbering node and edge id *)letnid=NodeHash.create16andeid=EdgeHash.create16andcount=ref0inEdgeHash.iter(funid_->incrcount;EdgeHash.addeidid!count)g.g_edges;(* header *)Format.fprintffmt"digraph %s {\n"name;(* emit dot nodes for nodes and edges *)NodeHash.iter(funidn->ifp.dot_filter_nodenthen(incrcount;NodeHash.addnidid!count;Format.fprintffmt" n%i [label=\"%s\"];\n"!count(to_stringp.dot_pp_noden)))g.g_nodes;EdgeHash.iter(funide->ifp.dot_filter_edgeethen(incrcount;EdgeHash.addeidid!count;Format.fprintffmt" n%i [shape=box label=\"%s\"];\n"!count(to_stringp.dot_pp_edgee)))g.g_edges;(* emit dot edges to connect nodes and edges *)EdgeHash.iter(funide->letdid1=EdgeHash.findeididinList.iter(fun(port,n)->ifp.dot_filter_noden&&p.dot_filter_portport&&p.dot_filter_edgeethen(letdid2=NodeHash.findnidn.n_idinFormat.fprintffmt" n%i -> n%i [label=\"%s\"];\n"did2did1(to_stringp.dot_pp_portport)))e.e_src;List.iter(fun(port,n)->ifp.dot_filter_noden&&p.dot_filter_portport&&p.dot_filter_edgeethen(letdid2=NodeHash.findnidn.n_idinFormat.fprintffmt" n%i -> n%i [label=\"%s\"];\n"did1did2(to_stringp.dot_pp_portport)))e.e_dst)g.g_edges;(* entry / exit nodes *)List.iter(fun(port,n)->ifp.dot_filter_noden&&p.dot_filter_portportthen(incrcount;letdid=NodeHash.findnidn.n_idinFormat.fprintffmt" n%i [shape=point label=\"\"];\n n%i -> n%i [label=\"%s\"];\n"!count!countdid(to_stringp.dot_pp_portport)))g.g_entries;List.iter(fun(port,n)->ifp.dot_filter_noden&&p.dot_filter_portportthen(incrcount;letdid=NodeHash.findnidn.n_idinFormat.fprintffmt" n%i [shape=point label=\"\"];\n n%i -> n%i [label=\"%s\"];\n"!countdid!count(to_stringp.dot_pp_portport)))g.g_exits;(* footer *)Format.fprintffmt"}\n"end