123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826(* Yoann Padioleau
*
* Copyright (C) 2009, 2010, 2011 Facebook
* Copyright (C) 2019 r2c
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation, with the
* special exception on linking described in file license.txt.
*
* This library 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 file
* license.txt for more details.
*)openCommonopenAst_genericmoduleAst=Ast_genericmoduleF=Controlflow(*****************************************************************************)(* Prelude *)(*****************************************************************************)(*****************************************************************************)(* Types *)(*****************************************************************************)(* Information passed recursively in cfg_stmt or cfg_stmt_list below.
* The graph g is mutable, so most of the work is done by side effects on it.
* No need to return a new state.
*)typestate={g:F.flow;(* When there is a 'return' we need to know the exit node to link to *)exiti:F.nodei;(* Sometimes when there is a 'continue' or 'break' we must know where
* to jump and so we must know the node index for the end of the loop.
* The same kind of information is needed for 'switch' or 'try/throw'.
*
* Because loops can be inside switch or try, and vice versa, you need
* a stack of context.
*)ctx:contextCommon.stack;}andcontext=|NoCtx|LoopCtxofF.nodei(* head *)*F.nodei(* end *)|SwitchCtxofF.nodei(* end *)|TryCtxofF.nodei(* the first catch *)typeerror=error_kind*Parse_info.toptionanderror_kind=|NoEnclosingLoop|DynamicBreak|UnreachableStatementofControlflow.node_kindexceptionErroroferrorletverbose=reffalse(*****************************************************************************)(* Helpers *)(*****************************************************************************)letstmts_of_stmt_or_defsxs=xs|>Common.map_filter(funstmt_or_def->matchstmt_or_defwith|DefStmt(_,VarDef_)->Somestmt_or_def|DefStmt_|DirectiveStmt_->if!verbosethenpr2_once("ignoring nested func/class/directive in CFG");(* should be processed by the calling visitor *)None|st->Somest)(*
let rec intvalue_of_expr e =
match e with
| (Sc (C (Int (i_str, _)))) -> Some (s_to_i i_str)
| ParenExpr (_, e, _) -> intvalue_of_expr e
| _ -> None
*)letadd_arc(starti,nodei)g=g#add_arc((starti,nodei),F.Direct)letadd_arc_opt(starti_opt,nodei)g=starti_opt|>Common.do_option(funstarti->g#add_arc((starti,nodei),F.Direct))(*
* When there is a 'break', 'continue', or 'throw', we need to look up in the
* stack of contexts whether there is an appropriate one. In the case
* of 'break/continue', because some languages allow statements like
* 'break 2;', we also need to know how many upper contexts we need to
* look for.
*)let(lookup_some_ctx:?level:int->ctx_filter:(context->'aoption)->contextlist->'aoption)=fun?(level=1)~ctx_filterxs->letrecauxdepthxs=matchxswith|[]->None|x::xs->(matchctx_filterxwith|None->auxdepthxs|Somea->ifdepth=levelthen(Somea)elseaux(depth+1)xs)inaux1xsletinfo_optany=matchLib_ast.ii_of_anyanywith|[]->None|x::_xs->Somex(*****************************************************************************)(* Algorithm *)(*****************************************************************************)(*
* The CFG building algorithm works by iteratively visiting the
* statements in the AST of a function. At each statement,
* the cfg_stmt function is called, and passed the index of the
* previous node (if there is one), and returns the index of
* the created node (if there is one).
*
* history:
*
* ver1: old code was returning a nodei, but break has no end, so
* cfg_stmt should return a nodei option.
*
* ver2: old code was taking a nodei, but should also take a nodei
* option. There can be deadcode in the function.
*
* subtle: try/throw. The current algo is not very precise, but
* it's probably good enough for many analysis.
*)letrec(cfg_stmt:state->F.nodeioption->stmt->F.nodeioption)=funstateprevistmt->leti()=info_opt(Sstmt)inmatchstmtwith|Label_|Goto_->raiseTodo|ExprStmte->cfg_exprstateprevie(*
| StaticVars (_, static_vars, _) ->
let var_list = Ast.uncomma static_vars |> List.map (fun (v, _) -> v) in
List.fold_left (cfg_var_def state) previ var_list
*)|Blockxs->letstmts=stmts_of_stmt_or_defs(xs)incfg_stmt_liststateprevistmts|For_|While_->(* previ -> newi ---> newfakethen -> ... -> finalthen -
* |---|-----------------------------------|
* |-> newfakelse
*)letnode,stmt=(matchstmtwith|While(e,stmt)->F.WhileHeader(e),stmt|For(_forheader,stmt)->F.ForHeader,stmt|_->raiseImpossible)inletnewi=state.g#add_node{F.n=node;i=i()}instate.g|>add_arc_opt(previ,newi);letnewfakethen=state.g#add_node{F.n=F.TrueNode;i=None}inletnewfakeelse=state.g#add_node{F.n=F.FalseNode;i=None}instate.g|>add_arc(newi,newfakethen);state.g|>add_arc(newi,newfakeelse);letstate={statewithctx=LoopCtx(newi,newfakeelse)::state.ctx;}inletfinalthen=cfg_stmtstate(Somenewfakethen)stmtinstate.g|>add_arc_opt(finalthen,newi);Somenewfakeelse(* this was a tentative by jiao to work with dataflow_php.ml but it
has some regression so I've commented it out
| While (t1, e, colon_stmt) ->
(* previ -> newi ---> newfakethen -> ... -> finalthen
* |--|---------------------------------|
* |-> newfakelse -> <rest>
*)
let node = F.WhileHeader (Ast.unparen e) in
let newi = state.g#add_node { F.n = node; i=i() } in
state.g |> add_arc_opt (previ, newi);
let newfakethen = state.g#add_node { F.n = F.TrueNode;i=None } in
let newfakeelse = state.g#add_node { F.n = F.FalseNode;i=None } in
state.g |> add_arc (newi, newfakethen);
state.g |> add_arc (newi, newfakeelse);
let state = { state with
ctx = LoopCtx (newi, newfakeelse)::state.ctx;
}
in
let finalthen = cfg_colon_stmt state (Some newfakethen) colon_stmt in
(* let's loop *)
state.g |> add_arc_opt (finalthen, newi);
Some newfakeelse
| For (t1, t2, e1, t3, e2, t4, e5, t6, colon_stmt) ->
(* previ -> e1i ->newi -> e2i --> newfakethen -> ... -> finalthen -> e5i
* |--------|----------------------------------------|
* |-> newfakelse -> <rest>
*)
let exprs = Ast.uncomma e1 in
let e1i = List.fold_left (cfg_expr state F.SpecialMaybeUnused)
previ exprs in
let node = F.ForHeader in
let newi = state.g#add_node { F.n = node; i=i() } in
state.g |> add_arc_opt (e1i, newi);
let exprs = Ast.uncomma e2 in
let e2i = List.fold_left (cfg_expr state F.Normal)
(Some newi) exprs in
let newfakethen = state.g#add_node { F.n = F.TrueNode;i=None } in
let newfakeelse = state.g#add_node { F.n = F.FalseNode;i=None } in
state.g |> add_arc_opt (e2i, newfakethen);
state.g |> add_arc_opt (e2i, newfakeelse);
(* todo: the head should not be newi but the node just before
* the increment, see tests/php/controlflow/continue_for.php
*)
let state = { state with
ctx = LoopCtx (newi, newfakeelse)::state.ctx;
}
in
let finalthen = cfg_colon_stmt state (Some newfakethen) colon_stmt in
let exprs = Ast.uncomma e5 in
let e5i = List.fold_left (cfg_expr state F.Normal) finalthen exprs in
state.g |> add_arc_opt (e5i, newi);
Some newfakeelse
| Foreach (t1, t2, e1, t3, v_arrow_opt, t4, colon_stmt) ->
(* previ -> e1i ->newi ---> newfakethen -> ... -> finalthen
* |---|----------------------------------|
* |-> newfakelse -> <rest>
*)
let e1i = cfg_expr state F.Normal previ e1 in
let names =
match v_arrow_opt with
| ForeachVar (var) -> [var]
| ForeachArrow (var1, _, var2) ->
[var1;var2]
| ForeachList (_, xs) ->
failwith "Warning: list foreach"
in
let node = F.ForeachHeader names in
let newi = state.g#add_node { F.n = node; i=i() } in
state.g |> add_arc_opt (e1i, newi);
let newfakethen = state.g#add_node { F.n = F.TrueNode;i=None } in
let newfakeelse = state.g#add_node { F.n = F.FalseNode;i=None } in
state.g |> add_arc (newi, newfakethen);
state.g |> add_arc (newi, newfakeelse);
let state = { state with
ctx = LoopCtx (newi, newfakeelse)::state.ctx;
}
in
let finalthen =
cfg_colon_stmt state (Some newfakethen) colon_stmt
in
state.g |> add_arc_opt (finalthen, newi);
Some newfakeelse
*)(* This time, we may return None, for instance if return in body of dowhile
* (whereas While can't return None). But if we return None, certainly
* sign of buggy code.
*)|DoWhile(st,e)->(* previ -> doi ---> ... ---> finalthen (opt) ---> taili
* |--------- newfakethen ----------------| |-> newfakelse <rest>
*)letdoi=state.g#add_node{F.n=F.DoHeader;i=i()}instate.g|>add_arc_opt(previ,doi);lettaili=state.g#add_node{F.n=F.DoWhileTail(e);i=None}inletnewfakethen=state.g#add_node{F.n=F.TrueNode;i=None}inletnewfakeelse=state.g#add_node{F.n=F.FalseNode;i=None}instate.g|>add_arc(taili,newfakethen);state.g|>add_arc(taili,newfakeelse);state.g|>add_arc(newfakethen,doi);letstate={statewithctx=LoopCtx(taili,newfakeelse)::state.ctx;}inletfinalthen=cfg_stmtstate(Somedoi)stin(matchfinalthenwith|None->(* weird, probably wrong code *)None|Somefinalthen->state.g|>add_arc(finalthen,taili);Somenewfakeelse)|If(e,st_then,st_else)->(* previ -> newi ---> newfakethen -> ... -> finalthen --> lasti -> <rest>
* | |
* |-> newfakeelse -> ... -> finalelse -|
*
* Can generate either special nodes for elseif, or just consider
* elseif as syntactic sugar that translates into regular ifs, which
* is what I do for now.
* The lasti can be a Join when there is no return in either branch.
*)letnewi=state.g#add_node{F.n=F.IfHeader(e);i=i()}instate.g|>add_arc_opt(previ,newi);letnewfakethen=state.g#add_node{F.n=F.TrueNode;i=None}inletnewfakeelse=state.g#add_node{F.n=F.FalseNode;i=None}instate.g|>add_arc(newi,newfakethen);state.g|>add_arc(newi,newfakeelse);letfinalthen=cfg_stmtstate(Somenewfakethen)st_theninletfinalelse=cfg_stmtstate(Somenewfakeelse)st_elsein(matchfinalthen,finalelsewith|None,None->(* probably a return in both branches *)None|Somenodei,None|None,Somenodei->Somenodei|Somen1,Somen2->letlasti=state.g#add_node{F.n=F.Join;i=None}instate.g|>add_arc(n1,lasti);state.g|>add_arc(n2,lasti);Somelasti)|Return(e)->letnewi=state.g#add_node{F.n=F.Returne;i=i()}instate.g|>add_arc_opt(previ,newi);state.g|>add_arc(newi,state.exiti);(* the next statement if there is one will not be linked to
* this new node *)None|Continue(eopt)|Break(eopt)->letis_continue,node=matchstmtwith|Continue_->true,F.Continueeopt|Break_->false,F.Breakeopt|_->raiseImpossiblein(*
let depth =
match e with
| None -> 1
| Some e ->
(match intvalue_of_expr e with
| Some i -> i
| None ->
(* a dynamic variable ? *)
raise (Error (DynamicBreak, t1))
)
in
*)letnewi=state.g#add_node{F.n=node;i=i()}instate.g|>add_arc_opt(previ,newi);letnodei_to_jump_to=state.ctx|>lookup_some_ctx~level:1~ctx_filter:(function|LoopCtx(headi,endi)->ifis_continuethenSome(headi)elseSome(endi)|SwitchCtx(endi)->(* it's ugly but PHP allows to 'continue' inside 'switch' (even
* when the switch is not inside a loop) in which case
* it has the same semantic than 'break'.
*)Someendi|TryCtx_|NoCtx->None)in(matchnodei_to_jump_towith|Somenodei->state.g|>add_arc(newi,nodei);|None->raise(Error(NoEnclosingLoop,i())));None|Switch(e,cases_and_body)->letnewi=state.g#add_node{F.n=F.SwitchHeader(e);i=i()}instate.g|>add_arc_opt(previ,newi);(* note that if all cases have return, then we will remove
* this endswitch node later.
*)letendi=state.g#add_node{F.n=F.SwitchEnd;i=None}in(* if no default: then must add path from start to end directly
* todo? except if the cases cover the full spectrum ?
*)if(not(cases_and_body|>List.exists(fun(cases,_body)->cases|>List.exists(function|Ast.Default->true|_->false))))thenbeginstate.g|>add_arc(newi,endi);end;(* let's process all cases *)letlast_stmt_opt=cfg_cases(newi,endi)state(None)cases_and_bodyinstate.g|>add_arc_opt(last_stmt_opt,endi);(* remove endi if for instance all branches contained a return *)if(state.g#predecessorsendi)#nullthenbeginstate.g#del_nodeendi;NoneendelseSomeendi(*
* Handling try part 1. See the case for Throw below and the
* cfg_catches function for the second part.
*
* Any function call in the body of the try could potentially raise
* an exception, so should we add edges to the catch nodes ?
* In the same way any function call could potentially raise
* a divide by zero or call exit().
* For now we don't add all those edges. We do it only for explicit throw.
*
* todo? Maybe later the CFG could be extended with information
* computed by a global bottom-up analysis (so that we would add certain
* edges)
*
* todo? Maybe better to just add edges for all the nodes in the body
* of the try to all the catches ?
*
* So for now, we mostly consider catches as a serie of elseifs,
* and add some goto to be conservative at a few places. For instance
*
* try {
* ...;
* } catch (E1 $x) {
* throw $x;
* } catch (E2 $x) {
* ...
* }
* ...
*
* is rougly considered as this code:
*
* <tryheader> {
* if(true) goto catchstart;
* else {
* ...;
* goto tryend;
* }
* }
* <catchstart>
* if (is E1) {
* goto exit; /* or next handler if nested try */
* } elseif (is E2) {
* ...
* goto tryend;
* } else {
* goto exit; /* or next handler if nested try */
* }
*
* <tryend>
*)|Try(body,catches,_finallys)->(* TODO Task #3622443: Update the logic below to account for "finally"
clauses *)letnewi=state.g#add_node{F.n=F.TryHeader;i=i()}inletcatchi=state.g#add_node{F.n=F.CatchStart;i=None}instate.g|>add_arc_opt(previ,newi);(* may have to delete it later if nobody connected to it *)letendi=state.g#add_node{F.n=F.TryEnd;i=None}in(* for now we add a direct edge between the try and catch,
* as even the first statement in the body of the try could
* be a function raising internally an exception.
*
* I just don't want certain analysis like the deadcode-path
* to report that the code in catch are never executed. I want
* the catch nodes to have at least one parent. So I am
* kind of conservative.
*)state.g|>add_arc(newi,catchi);letstate'={statewithctx=TryCtx(catchi)::state.ctx;}inletlast_stmt_opt=cfg_stmtstate'(Somenewi)bodyinstate.g|>add_arc_opt(last_stmt_opt,endi);(* note that we use state, not state' here, as we want the possible
* throws inside catches to be themselves link to a possible surrounding
* try.
*)letlast_false_node=cfg_catchesstatecatchiendi(catches)in(* we want to connect the end of the catch list with
* the next handler, if try are nested, or to the exit if
* there is no more handler in this context
*)letnodei_to_jump_to=state.ctx|>lookup_some_ctx~ctx_filter:(function|TryCtx(nextcatchi)->Somenextcatchi|LoopCtx_|SwitchCtx_|NoCtx->None)in(matchnodei_to_jump_towith|Somenextcatchi->state.g|>add_arc(last_false_node,nextcatchi)|None->state.g|>add_arc(last_false_node,state.exiti));(* if nobody connected to endi erase the node. For instance
* if have only return in the try body.
*)if(state.g#predecessorsendi)#nullthenbeginstate.g#del_nodeendi;NoneendelseSomeendi(*
* For now we don't do any fancy analysis to statically detect
* which exn handler a throw should go to. The argument of throw can
* be static as in 'throw new ExnXXX' but it could also be dynamic. So for
* now we just branch to the first catch and make edges between
* the different catches in cfg_catches below
* (which is probably what is done at runtime by the PHP interpreter).
*
* todo? Again maybe later the CFG could be sharpened with
* path sensitive analysis to be more precise (so that we would remove
* certain edges)
*)|Throw(e)->letnewi=state.g#add_node{F.n=F.Throwe;i=i()}instate.g|>add_arc_opt(previ,newi);letnodei_to_jump_to=state.ctx|>lookup_some_ctx~ctx_filter:(function|TryCtx(catchi)->Somecatchi|LoopCtx_|SwitchCtx_|NoCtx->None)in(matchnodei_to_jump_towith|Somecatchi->state.g|>add_arc(newi,catchi)|None->(* no enclosing handler, branch to exit node of the function *)state.g|>add_arc(newi,state.exiti));None|Assert_|OtherStmt_->letsimple_stmt=F.TodoSimpleStmtinletnewi=state.g#add_node{F.n=F.SimpleStmtsimple_stmt;i=i()}instate.g|>add_arc_opt(previ,newi);Somenewi|DefStmt(_ent,VarDef_def)->raiseTodo(* should be filtered by stmts_of_stmt_or_defs *)|DefStmt_|DirectiveStmt_->raiseImpossibleandcfg_stmt_liststateprevixs=xs|>List.fold_left(funprevistmt->cfg_stmtstateprevistmt)previ(*
* Creating the CFG nodes and edges for the cases of a switch.
*
* PHP allows to write code like case X: case Y: ... This is
* parsed as a [Case (X, []); Case (Y, ...)] which means
* the statement list of the X case is empty. In this situation we just
* want to link the node for X directly to the node for Y.
*
* So cfg_cases works like cfg_stmt by optionally taking the index of
* the previous node (here for instance the node of X), and optionally
* returning a node (if the case contains a break, then this will be
* None)
*)and(cfg_cases:(F.nodei*F.nodei)->state->F.nodeioption->Ast.case_and_bodylist->F.nodeioption)=fun(switchi,endswitchi)stateprevicases->letstate={statewithctx=SwitchCtx(endswitchi)::state.ctx;}incases|>List.fold_left(funprevicase_and_body->let(cases,stmt)=case_and_bodyinletnode=(* TODO: attach expressions there *)matchcaseswith|[Default]->F.Default|_->F.Caseinleti()=info_opt(Sstmt)inletnewi=state.g#add_node{F.n=node;i=i()}instate.g|>add_arc_opt(previ,newi);(* connect SwitchHeader to Case node *)state.g|>add_arc(switchi,newi);(* the stmts can contain 'break' that will be linked to the endswitch *)cfg_stmtstate(Somenewi)stmt)previ(*
* Creating the CFG nodes and edges for the catches of a try.
*
* We will conside catch(Exn $e) as a kind of if, with a TrueNode for
* the case the thrown exn matched the specified class,
* and FalseNode otherwise.
*
* cfg_catches takes the nodei of the previous catch nodes (or false node
* of the previous catch node), process the catch body, and return
* a new False Node.
*)and(cfg_catches:state->F.nodei->F.nodei->Ast.catchlist->F.nodei)=funstateprevitryendicatches->catches|>List.fold_left(funprevicatch->let(_pattern,stmt)=catchinleti()=info_opt(Sstmt)inletnewi=state.g#add_node{F.n=F.Catch;i=i()}instate.g|>add_arc(previ,newi);(*
let ei = cfg_var_def state (Some newi) name in
*)letei=Somenewiinlettruei=state.g#add_node{F.n=F.TrueNode;i=None}inletfalsei=state.g#add_node{F.n=F.FalseNode;i=None}instate.g|>add_arc_opt(ei,truei);state.g|>add_arc_opt(ei,falsei);(* the stmts can contain 'throw' that will be linked to an upper try or
* exit node *)letlast_stmt_opt=cfg_stmtstate(Sometruei)stmtinstate.g|>add_arc_opt(last_stmt_opt,tryendi);(* we chain the catches together, like elseifs *)falsei)previandcfg_exprstatepreviexpr=leti=info_opt(Eexpr)inletnewi=state.g#add_node{F.n=F.SimpleStmt(F.ExprStmt(expr));i=i}instate.g|>add_arc_opt(previ,newi);Somenewi(*
and cfg_var_def state previ dname =
let i = Ast.info_of_dname dname in
let vari = state.g#add_node { F.n = F.Parameter dname; i=Some i } in
state.g |> add_arc_opt (previ, vari);
Some vari
*)(*****************************************************************************)(* Main entry point *)(*****************************************************************************)let(control_flow_graph_of_stmts:parameterlist->stmtlist->F.flow)=funparamsxs->(* yes, I sometimes use objects, and even mutable objects in OCaml ... *)letg=newOgraph_extended.ograph_mutableinletenteri=g#add_node{F.n=F.Enter;i=None}inletexiti=g#add_node{F.n=F.Exit;i=None}inletnewi=params|>List.fold_left(funpreviparam->letparami=g#add_node{F.n=F.Parameterparam;i=None}ing|>add_arc(previ,parami);parami)enteriinletstate={g=g;exiti=exiti;ctx=[NoCtx];(* could also remove NoCtx and use an empty list *)}inletlast_node_opt=cfg_stmt_liststate(Somenewi)xsin(* maybe the body does not contain a single 'return', so by default
* connect last stmt to the exit node
*)g|>add_arc_opt(last_node_opt,exiti);glet(cfg_of_func:function_definition->F.flow)=fundef->letparams=def.fparamsin(* less: could create a node with function name ? *)control_flow_graph_of_stmtsparams[def.fbody](* alias *)letcfg_of_stmts=control_flow_graph_of_stmts(*****************************************************************************)(* Deadcode stmts detection *)(*****************************************************************************)let(deadcode_detection:F.flow->unit)=funflow->flow#nodes#iter(fun(k,node)->letpred=flow#predecessorskinifpred#nullthen(matchnode.F.nwith|F.Enter->()|_->(matchnode.F.iwith|None->pr2(spf"CFG: PB, found dead node but no loc: %s"(Controlflow.short_string_of_nodenode))|Someinfo->raise(Error(UnreachableStatementnode.F.n,Someinfo)))))(*****************************************************************************)(* Error management *)(*****************************************************************************)letstring_of_error_kinderror_kind=matcherror_kindwith|UnreachableStatement(node_kind)->"Unreachable statement detected "^(F.short_string_of_node_kindnode_kind)|NoEnclosingLoop->"No enclosing loop found for break or continue"|DynamicBreak->"Dynamic break/continue are not supported"(* note that the output is emacs compile-mode compliant *)letstring_of_error(error_kind,info)=matchinfowith|None->spf"NOLOC: FLOW %s"(string_of_error_kinderror_kind)|Someinfo->letinfo=Parse_info.token_location_of_infoinfoinspf"%s:%d:%d: FLOW %s"info.Parse_info.fileinfo.Parse_info.lineinfo.Parse_info.column(string_of_error_kinderror_kind)(* old:
let error_from_info info =
let pinfo = Ast.parse_info_of_info info in
Parse_info.error_message_short
pinfo.Parse_info.file ("", pinfo.Parse_info.charpos)
in
*)let(report_error:error->unit)=funerr->pr2(string_of_errorerr)