123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545(****************************************************************************)(* *)(* This file is part of MOPSA, a Modular Open Platform for Static Analysis. *)(* *)(* Copyright (C) 2017-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/>. *)(* *)(****************************************************************************)(** Toplevel abstraction
There are two main differences with domains. First, transfer functions are
indexed by paths to enable a faster access. Second, transfer functions are
not partial functions and return always a result.
*)openMopsa_utilsopenCore.AllopenSig.Combiner.Domain(** Signature of the toplevel abstraction *)moduletypeTOPLEVEL=sig(** {2 Abstraction header} *)(** ********************** *)typetvalbottom:tvaltop:tvalis_bottom:t->bool(** {2 Lattice operators} *)(** ********************* *)valsubset:(t,t)man->tctx->t->t->boolvaljoin:(t,t)man->tctx->t->t->tvalmeet:(t,t)man->tctx->t->t->tvalwiden:(t,t)man->tctx->t->t->tvalmerge:t->t*change_map->t*change_map->t(** {2 Transfer functions} *)(** ********************** *)valinit:program->(t,t)man->tflowvalexec:?route:route->stmt->(t,t)man->tflow->tpostvaleval:?route:route->?translate:semantic->?translate_when:(semantic*(expr->bool))list->expr->(t,t)man->tflow->tevalvalask:?route:route->(t,'r)query->(t,t)man->tflow->(t,'r)cases(** {2 Pretty printing} *)(** ******************* *)valprint_state:?route:route->printer->t->unitvalprint_expr:?route:route->(t,t)man->tflow->printer->expr->unit(***************)(** Exceptions *)(***************)exceptionSysBreakoftflowexceptionGoBackwardend(*==========================================================================*)(** {2 Domain encapsulation into an abstraction} *)(*==========================================================================*)letdebugfmt=Debug.debug~channel:"framework.abstraction.toplevel"fmt(** Encapsulate a domain into a top-level abstraction *)moduleMake(Domain:DOMAIN_COMBINER):TOPLEVELwithtypet=Domain.t=structlet()=debug"routing table:@,%a"pp_routing_tableDomain.routing_table(** {2 Abstraction header} *)(** ********************** *)typet=Domain.tletbottom=Domain.bottomlettop=Domain.topletis_bottom=Domain.is_bottom(** {2 Lattice operators} *)(** ********************* *)letsubsetmanctxaa'=Domain.subsetmanctx(a,a)(a',a')letjoinmanctxaa'=Domain.joinmanctx(a,a)(a',a')letmeetmanctxaa'=Domain.meetmanctx(a,a)(a',a')letwidenmanctxaa'=Domain.widenmanctx(a,a)(a',a')letmerge=Domain.mergeempty_path(** {2 Caches and route maps} *)(** **************************** *)(* Cache of previous evaluations and post-conditions *)moduleCache=Core.Cache.Make(structtypet=Domain.tend)(** Map giving transfer functions of each route *)moduleRouteMap=MapExt.Make(structtypet=routeletcompare=compare_routeend)(** {2 Initialization} *)(** ****************** *)letinitprogman:Domain.tflow=(* Initialize the context with an empty callstack *)letctx=singleton_ctxContext.callstack_ctx_keyCallstack.empty_callstackin(* The initial flow is a singleton ⊤ environment *)letflow0=Flow.singletonctxT_curman.lattice.topin(* Initialize domains *)letres=matchDomain.initprogmanflow0with|None->flow0|Somepost->post_to_flowmanpostin(* Initialize hooks *)let()=Hook.init()inletctx=Hook.init_active_hooks(Flow.get_ctxres)inFlow.set_ctxctxres(** {2 Statement execution} *)(** *********************** *)(** Build the map of exec functions *)letexec_map:(stmt->(t,t)man->tflow->tpostoption)RouteMap.t=(* Add the initial implicit binding for toplevel route *)letmap=RouteMap.singletontoplevel(Domain.execNone)in(* Iterate over all routes *)get_routesDomain.routing_table|>List.fold_left(funmaproute->ifRouteMap.memroutemapthenmapelseletdomains=resolve_routerouteDomain.routing_tableinRouteMap.addroute(Domain.exec(Somedomains))map)map(** Hooks should not be activated within hooks exec/eval.
The flag [inside_hook_flag] is set whenever the analyzer emits a hook
event, in order to prevent subsequent exec/eval (i.e. called inside
the hook) to emit hook events.
*)letinside_hook_flag=reffalseletinside_hook()=!inside_hook_flagletenter_hook()=assert(not(inside_hook()));inside_hook_flag:=trueletexit_hook()=assert(inside_hook());inside_hook_flag:=falseexceptionSysBreakofDomain.tflowexceptionGoBackwardletexec?(route=toplevel)(stmt:stmt)man(flow:Domain.tflow):Domain.tpost=letflow=ifinside_hook()thenflowelselet()=enter_hook()inletx=Hook.on_before_execroutestmtmanflowinlet()=exit_hook()inmatchxwithNone->flow|Somectx->Flow.set_ctxctxflowinletfexec=tryRouteMap.findrouteexec_mapwithNot_found->Exceptions.panic_atstmt.srange"exec for %a not found"pp_routerouteintryletpost=matchskindstmtwith|S_breakpoint_->Post.returnflow|S_add_markermwhennot(is_marker_enabledm)->Post.returnflow|_->matchCache.execfexecroutestmtmanflowwith|None->ifFlow.is_bottomman.latticeflowthenPost.returnflowelse(matchskindstmtwith|S_add_marker_->Post.returnflow|_->Exceptions.panic_atstmt.srange"unable to analyze statement %a in %a"pp_stmtstmtpp_routeroute)|Somepost->(* Check that all cases were handled *)letnot_handled=Cases.exists(funcflow->matchcwith|NotHandled->(* Not handled cases with empty flows are OK *)not(Flow.is_bottomman.latticeflow)|_->false)postinifnot_handledthenExceptions.panic_atstmt.srange"unable to analyze statement %a in %a"pp_stmtstmtpp_routeroute;postinletclean_post=exec_cleanersmanpostinletminimized_post=Post.remove_duplicatesman.latticeclean_postinifinside_hook()thenminimized_postelselet()=enter_hook()inletx=Hook.on_after_execroutestmtmanflowminimized_postinlet()=exit_hook()inmatchxwithNone->minimized_post|Somectx->Cases.set_ctxctxminimized_postwith|Exceptions.Panic(msg,line)->Printexc.raise_with_backtrace(Exceptions.PanicAtFrame(stmt.srange,(Flow.get_callstackflow),msg,line))(Printexc.get_raw_backtrace())|Exceptions.PanicAtLocation(range,msg,line)->Printexc.raise_with_backtrace(Exceptions.PanicAtFrame(range,(Flow.get_callstackflow),msg,line))(Printexc.get_raw_backtrace())|Sys.Break->raise(SysBreakflow)|Apron.Manager.Errorexc->Printexc.raise_with_backtrace(Exceptions.PanicAtFrame(stmt.srange,(Flow.get_callstackflow),Format.asprintf"Apron.Manager.Error(%a)"Apron.Manager.print_exclogexc,""))(Printexc.get_raw_backtrace())|ewhen(matchewithExit|Exceptions.PanicAtFrame_|SysBreak_|GoBackward->false|_->true)->Printexc.raise_with_backtrace(Exceptions.PanicAtFrame(stmt.srange,(Flow.get_callstackflow),Printexc.to_stringe,""))(Printexc.get_raw_backtrace())(** {2 Evaluation of expressions} *)(** ***************************** *)(** Build the map of [eval] functions *)leteval_map:(expr->(t,t)man->tflow->tevaloption)RouteMap.t=(* Add the implicit eval for toplevel *)letmap=RouteMap.singletontoplevel(Domain.evalNone)in(* Iterate over all routes *)get_routesDomain.routing_table|>List.fold_left(funmaproute->ifRouteMap.memroutemapthenmapelseletdomains=resolve_routerouteDomain.routing_tableinRouteMap.addroute(Domain.eval(Somedomains))map)map(** Get the actual route of the expression in case of a
variable, since variable can have an intrinsic semantic *)letrefine_route_with_var_semanticroutee=ifcompare_routeroutetoplevel=0thenmatchekindewith|E_var(v,_)->Semanticv.vsemantic|_->routeelseroute(** Evaluate sub-nodes of an expression and rebuild it *)leteval_sub_expressions?(route=toplevel)expmanflow=letparts,builder=structure_of_exprexpinmatchpartswith|{exprs=[];stmts=[]}->Eval.singletonexpflow|{exprs;stmts=[]}->(* Evaluate each sub-expression *)Cases.bind_listexprs(man.eval~route)flow>>$funexprsflow->(* Normalize translation maps by ensuring that sub-expression should be
translated to the same semanitcs *)letsemantics=List.fold_left(funacce->SemanticSet.unionacc(SemanticMap.bindingse.etrans|>List.mapfst|>SemanticSet.of_list))SemanticSet.emptyexprsinletexprs=List.map(SemanticSet.fold(funse->ifSemanticMap.memse.etranstheneelse{ewithetrans=SemanticMap.addsee.etrans})semantics)exprsin(* Rebuild the expression [exp] from its evaluated parts *)letexp=builder{exprs;stmts=[]}in(* Rebuild also the translations of [exp] from the translations of its parts *)letetrans=SemanticSet.fold(funsetrans->letexprs=List.fold_left(funacce->SemanticMap.findse.etrans::acc)[]exprsinletexp=builder{exprs=List.revexprs;stmts=[]}inSemanticMap.addsexpetrans)semanticsSemanticMap.emptyinEval.singleton{expwithetrans}flow(* XXX sub-statements are not handled for the moment *)|_->Eval.singletonexpflow(** Evaluate not-handled cases *)leteval_not_handled?(route=toplevel)expmanevl=(* Separate handled and not-handled cases *)lethandled,not_handled=Cases.partition(funcflow->matchcwithNotHandled->false|_->true)evlinletnot_handled_ret=matchnot_handledwith|None->None|Someevl->(* Evaluate sub-expressions of the not-handled cases *)letevl=(* Merge all not-handled cases into one *)Eval.remove_duplicatesman.latticeevl>>=fun_flow->eval_sub_expressions~routeexpmanflowinSomeevlin(* Join handled and evaluated not-handled cases *)OptionExt.neutral2Cases.joinhandlednot_handled_ret|>OptionExt.none_to_exn(** Evaluation of expressions. *)leteval?(route=toplevel)?(translate=any_semantic)?(translate_when=[])expmanflow=(* Get the translation semantic if any *)letsemantic=ifnot(is_any_semantictranslate)thentranslateelsematchList.find_opt(fun(s,f)->fexp)translate_whenwith|Some(s,_)->s|None->any_semanticin(* Notify hooks *)letflow=ifinside_hook()thenflowelselet()=enter_hook()inletx=Hook.on_before_evalroutesemanticexpmanflowinlet()=exit_hook()inmatchxwith|None->flow|Somectx->Flow.set_ctxctxflowinletroute=refine_route_with_var_semanticrouteexpinletfeval=tryCache.eval(RouteMap.findrouteeval_map)routewithNot_found->Exceptions.panic_atexp.erange"eval for %a not found"pp_routerouteinletevl=matchfevalexpmanflowwith|None->eval_sub_expressionsexpmanflow|Someevl->eval_not_handled~routeexpmanevlin(* Update the expression history *)letret=Cases.map_result(funexp'->ifexp==exp'thenexp'elseletexp''={exp'withehistory=exp::exp'.ehistory}in(* Update history of the translations *){exp''withetrans=SemanticMap.map(fune->{ewithehistory=e.ehistory@exp''.ehistory})exp''.etrans})evlin(** Return a translation if it was requested *)letret'=ifis_any_semanticsemanticthenretelseCases.map_result(get_expr_translationsemantic)retin(* Notify hooks *)ifinside_hook()thenret'elselet()=enter_hook()inletx=Hook.on_after_evalroutesemanticexpmanflowret'inlet()=exit_hook()inmatchxwith|None->ret'|Somectx->Cases.set_ctxctxret'(** {2 Handler of queries} *)(** ********************** *)letask:typer.?route:route->(t,r)query->(t,t)man->tflow->(t,r)cases=fun?(route=toplevel)querymanflow->(* FIXME: the map of transfer functions indexed by routes is not constructed offline, due to the GADT query *)letdomains=ifcompare_routeroutetoplevel=0thenNoneelseSome(resolve_routerouteDomain.routing_table)inmatchDomain.askdomainsquerymanflowwith|Somer->r|None->matchquerywith|Sig.Abstraction.Partitioning.Q_partition_predicaterange->Cases.singleton(mk_constant(C_booltrue)~etyp:T_boolrange)flow|_->raiseNot_found(** {2 Pretty printer of states} *)(** **************************** *)(** Build the map of [print_state] functions *)letprint_state_map:(printer->t->unit)RouteMap.t=(* Add the implicit printer for toplevel *)letmap=RouteMap.singletontoplevel(Domain.print_stateNone)in(* Iterate over all routes *)get_routesDomain.routing_table|>List.fold_left(funmaproute->ifRouteMap.memroutemapthenmapelseletdomains=resolve_routerouteDomain.routing_tableinRouteMap.addroute(Domain.print_state(Somedomains))map)map(** Pretty print of states *)letprint_state?(route=toplevel)printera=matchRouteMap.find_optrouteprint_state_mapwith|Somef->fprintera|None->Exceptions.panic"pretty printer for %a not found"pp_routeroute(** {2 Pretty printer of expressions} *)(** ********************************* *)(** Build the map of [print_expr] functions *)letprint_expr_map:((t,t)man->tflow->printer->expr->unit)RouteMap.t=(* Add the implicit printer for toplevel *)letmap=RouteMap.singletontoplevel(Domain.print_exprNone)in(* Iterate over all routes *)get_routesDomain.routing_table|>List.fold_left(funmaproute->ifRouteMap.memroutemapthenmapelseletdomains=resolve_routerouteDomain.routing_tableinRouteMap.addroute(Domain.print_expr(Somedomains))map)map(** Pretty print of expression values *)letprint_expr?(route=toplevel)manflowprinterexp=ifPrint.mem_printed_exprprinterexpthen()elseletroute=refine_route_with_var_semanticrouteexpinmatchRouteMap.find_optrouteprint_expr_mapwith|Somef->Print.add_printed_exprprinterexp;fmanflowprinterexp|None->Exceptions.panic_atexp.erange"pretty printer for %a not found"pp_routerouteend