123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581(****************************************************************************)(* *)(* 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/>. *)(* *)(****************************************************************************)(** Reduced product combiner with n-ary reduction rules *)openMopsa_utilsopenCore.AllopenSig.Reduction.ExecopenSig.Reduction.EvalopenSig.Combiner.StackedopenCommonopenLocation(** Signature of a pool of domains with pointwise transfer functions *)moduletypePOOL=sigincludeSTACKED_COMBINERvalchecks:checklistlistvalmembers:DomainSet.tlistvalexec:DomainSet.toption->stmt->('a,t)man->'aflow->'apostoptionlistvaleval:DomainSet.toption->expr->('a,t)man->'aflow->'aevaloptionlistend(** Empty pool *)moduleEmptyPool:POOL=structtypet=unitletid=C_emptyletname="()"letdomains=DomainSet.emptyletmembers=[]letsemantics=SemanticSet.emptyletrouting_table=empty_routing_tableletchecks=[[]]letbottom=()lettop=()letis_bottom_=falseletsubset__((),s)((),s')=true,s,s'letjoin__((),s)((),s')=(),s,s'letmeet__((),s)((),s')=(),s,s'letwiden__((),s)((),s')=(),s,s',trueletmerge___=()letinit__flow=flowletexec___flow=[]leteval___flow=[]letask____=Noneletprint_state__()=()letprint_expr_____=()end(** Add a domain to a pool *)moduleMakePairPool(S:STACKED_COMBINER)(P:POOL):POOLwithtypet=S.t*P.t=structtypet=S.t*P.tletid=C_pair(Product,S.id,P.id)letdomains=DomainSet.unionS.domainsP.domainsletmembers=S.domains::P.membersletsemantics=SemanticSet.unionS.semanticsP.semanticsletrouting_table=join_routing_tableS.routing_tableP.routing_tableletchecks=S.checks::P.checksletname=S.name^" ∧ "^P.nameletbottom=S.bottom,P.bottomlettop=S.top,P.topletis_bottom(s,p)=S.is_bottoms||P.is_bottompletsubsetmanctx((a1,a2),s)((a1',a2'),s')=letb1,s,s'=S.subset(fst_pair_manman)ctx(a1,s)(a1',s')inletb2,s,s'=P.subset(snd_pair_manman)ctx(a2,s)(a2',s')inb1&&b2,s,s'letjoinmanctx((a1,a2),s)((a1',a2'),s')=letaa1,s,s'=S.join(fst_pair_manman)ctx(a1,s)(a1',s')inletaa2,s,s'=P.join(snd_pair_manman)ctx(a2,s)(a2',s')in(aa1,aa2),s,s'letmeetmanctx((a1,a2),s)((a1',a2'),s')=letaa1,s,s'=S.meet(fst_pair_manman)ctx(a1,s)(a1',s')inletaa2,s,s'=P.meet(snd_pair_manman)ctx(a2,s)(a2',s')in(aa1,aa2),s,s'letwidenmanctx((a1,a2),s)((a1',a2'),s')=letaa1,s,s',stable1=S.widen(fst_pair_manman)ctx(a1,s)(a1',s')inletaa2,s,s',stable2=P.widen(snd_pair_manman)ctx(a2,s)(a2',s')in(aa1,aa2),s,s',stable1&&stable2letmerge(pre1,pre2)((a1,a2),te)((a1',a2'),te')=S.mergepre1(a1,get_left_teffectte)(a1',get_left_teffectte'),P.mergepre2(a2,get_right_teffectte)(a2',get_right_teffectte')letinitprogmanflow=S.initprog(fst_pair_manman)flow|>P.initprog(snd_pair_manman)letexectargets=letf2=P.exectargetsinifnot(sat_targets~targets~domains:S.domains)then(funstmtmanflow->None::f2stmt(snd_pair_manman)flow)elseletf1=S.exectargetsin(funstmtmanflow->letpost=f1stmt(fst_pair_manman)flowinletctx=OptionExt.applyCases.get_ctx(Flow.get_ctxflow)postinletflow=Flow.set_ctxctxflowinpost::f2stmt(snd_pair_manman)flow)letevaltargets=letf2=P.evaltargetsinifnot(sat_targets~targets~domains:S.domains)then(funexpmanflow->None::f2exp(snd_pair_manman)flow)elseletf1=S.evaltargetsin(funexpmanflow->leteval=f1exp(fst_pair_manman)flowinletctx=OptionExt.applyCases.get_ctx(Flow.get_ctxflow)evalinletflow=Flow.set_ctxctxflowineval::f2exp(snd_pair_manman)flow)letasktargets=letf2=P.asktargetsinifnot(sat_targets~targets~domains:S.domains)then(funquerymanflow->f2query(snd_pair_manman)flow)elseletf1=S.asktargetsin(funquerymanflow->OptionExt.neutral2Cases.meet(f1query(fst_pair_manman)flow)(f2query(snd_pair_manman)flow))letprint_statetargets=letf2=P.print_statetargetsinifnot(sat_targets~targets~domains:S.domains)then(funprinter(s,p)->f2printerp)elseletf1=S.print_statetargetsin(funprinter(s,p)->f1printers;f2printerp)letprint_exprtargets=letf2=P.print_exprtargetsinifnot(sat_targets~targets~domains:S.domains)then(funmanflowprintere->f2(snd_pair_manman)flowprintere)elseletf1=S.print_exprtargetsin(funmanflowprintere->f1(fst_pair_manman)flowprintere;f2(snd_pair_manman)flowprintere)end(** Create a reduced product over a pool and a list of reduction rules *)moduleMake(Pool:POOL)(Rules:sigvalerules:(moduleEVAL_REDUCTION)listvalsrules:(moduleEXEC_REDUCTION)listend):STACKED_COMBINERwithtypet=Pool.t=structincludePoolletchecks=List.flattenPool.checksletdebugfmt=Debug.debug~channel:"framework.combiners.domain.product"fmt(** {2 Merging functions} *)(** ********************* *)(* Combine the reports of the two functions *)letmerge_reportchecks1checks2report1report2=map2zo_report(fundiag1->(* Check performed only by the left domain. Here, we are in trouble if the
right domain is responsible for this check and did nothing! *)ifList.memdiag1.diag_checkchecks2thenExceptions.panic"%a: check %a is unsound"pp_relative_rangediag1.diag_rangepp_checkdiag1.diag_check(* Otherwise, this is fine! The second domain is not responsible for
this check, so add it to the result *)elsediag1)(fundiag2->(* Same dual reasoning here *)ifList.memdiag2.diag_checkchecks1thenExceptions.panic"%a: check %a is unsound"pp_relative_rangediag2.diag_rangepp_checkdiag2.diag_checkelsediag2)(fundiag1diag2->(* Check domains responsibility *)matchList.memdiag2.diag_checkchecks1,List.memdiag1.diag_checkchecks2with(* Both domains handle the check => both diagnostics are up-to-date *)|true,true->meet_diagnosticdiag1diag2(* Only left domain handles it => return its diagnostic *)|true,false->diag1(* Only right domain handles it => return its diagnostic *)|false,true->diag2(* None of the domains handle the check, however the check changed!
This happens when a computation (exec/eval) is performed by one domain only.
The computation triggered the check and updated the diagnostic.
One easy and sound solution is to join the diagnostics. *)(* XXX Maybe we can be more precise by keeping the newest diagnostic,
which can be done by comparing with the pre-state report? *)|false,false->join_diagnosticdiag1diag2)report1report2(** Merge the conflicts between distinct domains.
These conflicts arise from two situations:
1. When a domain changes its local state, this change is not present in
the post-state of the other domains. In this case, we need to put the new
local state of every domain in all other post-states.
2. When two domains change (independently) the state of a shared sub-abstraction.
In this case, we use effects to merge the two diverging states.
*)letmerge_inter_conflictsmanpre(pointwise:('a,'r)casesoptionlist):('a,'roptionlist)cases=letrecaux:typet.tid->('a,t)man->('a,'r)casesoptionlist->checklistlist->('a,'roptionlist)cases=funidmanpointwisechecks->matchpointwise,id,checkswith(* Last domain returned no answer *)|[None],C_pair(_,s,_),_->Cases.return[None]pre(* Last domain returned an answer *)|[Somer],C_pair(_,s,_),_->r>>=funcaseflow->beginmatchcasewith|Result(res,_,_)->Cases.return[Someres]flow|Empty->Cases.emptyflow|NotHandled->Cases.return[None]flowend(* One domain returned no answer *)|None::tl,C_pair(_,s,pid),_::tlchecks->auxpid(snd_pair_manman)tltlchecks>>$funafterflow->Cases.return(None::after)flow(* One domain returned an answer.
Here we need to merge this answer with the answers of the next domains *)|Somer::tl,C_pair(_,s,pid),hdchecks::tlchecks->(* Compute the answer of the next domains *)auxpid(snd_pair_manman)tltlchecks>>=funafter_caseafter_flow->(* Compute the answer of this domain *)r>>=funcaseflow->(* Now combine the two answers *)beginmatchcase,after_casewith(* If at least one answer is empty, all the conjunction is empty *)|Empty,_|_,Empty->letreport=merge_reporthdchecks(List.flattentlchecks)(Flow.get_reportflow)(Flow.get_reportafter_flow)inletflow=Flow.set_reportreportflowinletafter_flow=Flow.set_reportreportflowinCases.empty(Flow.meetman.latticeflowafter_flow)(* NotHandled is transformed to None *)|NotHandled,Result(after_res,_,_)->Cases.return(None::after_res)after_flow(* Both domains replied, so merge the results *)|Result(res,effects,cleaners),Result(after_res,after_effects,after_cleaners)->(* Merge only when the next domains provided some answers *)ifafter_res|>List.exists(functionSome_->true|None->false)then(* Resolve the first conflict situation:
put the post-state of the current domain in the answer of the next domains *)letfst_pair_man=fst_pair_manmaninletafter_flow=Flow.setT_cur(letcur=Flow.getT_curman.latticeflowinletafter_cur=Flow.getT_curman.latticeafter_flowinfst_pair_man.set(fst_pair_man.getcur)after_cur)man.latticeafter_flowin(* Resolve the second conflict situation:
merge the post-states of any shared sub-abstraction *)letflow=Flow.merge~merge_report:(merge_reporthdchecks(List.flattentlchecks))man.latticepre(flow,effects)(after_flow,after_effects)inleteffects=meet_teffecteffectsafter_effectsinletcleaners=StmtSet.unioncleanersafter_cleanersinCases.case(Result(Someres::after_res,effects,cleaners))flowelse(* Next domains returned no answer, so no merging *)letreport=merge_reporthdchecks(List.flattentlchecks)(Flow.get_reportflow)(Flow.get_reportafter_flow)inletflow=Flow.set_reportreportflowinCases.case(Result(Someres::after_res,effects,cleaners))flow|_->assertfalseend|_->assertfalseinauxPool.idmanpointwisePool.checks(** Merge the conflicts emerging from the same domain.
This kind of conflicts arises when the same domain produces a conjunction of
post-states. Since these conjunctions are from the same domain, there is
no need to merge its local state; we just merge any shared sub-abstraction.
*)letmerge_intra_conflictsmanpre(r:('a,'r)cases):('a,'r)cases=Cases.map_conjunction(funconj->letreciter=function|[]->assertfalse|[case,flow]->Cases.get_case_effectscase,Cases.get_case_cleanerscase,flow|(case,flow)::tl->leteffects',cleaners',flow'=itertlinleteffects,cleaners=Cases.get_case_effectscase,Cases.get_case_cleanerscaseinletflow''=Flow.mergeman.lattice~merge_report:meet_reportpre(flow,effects)(flow',effects')inmeet_teffecteffectseffects',StmtSet.unioncleanerscleaners',flow''inleteffects,cleaners,flow=iterconjinList.map(fun(case,_)->letcase=Cases.set_case_effectseffectscase|>Cases.set_case_cleanerscleanersincase,flow)conj)r(** {2 Generic pointwise processing of transfer functions *)(** ***************************************************** *)(** The successor domain is the domain below the reduced
product. Since all member domains in the reduced product are at the
same level, we can pick any one of them *)letsuccessor=(* XXX This is a hack to be sure to take a member that is a user
domain, not a composed domain, because `BelowOf` routes are
defined for user domains only *)letmember=List.find(fundomains->DomainSet.cardinaldomains=1)Pool.members|>DomainSet.chooseinBelowmember(** Get the context of a pointwise result *)letrecget_pointwise_ctx~defaultpointwise=matchpointwisewith|[]->default|None::tl->get_pointwise_ctx~defaulttl|Somecases::tl->most_recent_ctx(Cases.get_ctxcases)(get_pointwise_ctx~defaulttl)(** Set the context of a pointwise result *)letrecset_pointwise_ctxctxpointwise=matchpointwisewith|[]->[]|None::tl->None::set_pointwise_ctxctxtl|Somecases::tl->Some(Cases.set_ctxctxcases)::set_pointwise_ctxctxtl(** Apply transfer function [f] pointwise over all domains *)letapply_pointwisefargmanflow=letpointwise=fargmanflowinifList.exists(functionSome_->true|None->false)pointwisethenletctx=get_pointwise_ctxpointwise~default:(Flow.get_ctxflow)inSome(set_pointwise_ctxctxpointwise)elseNone(** Replace missing pointwise results by calling the successor
domain. Missing results are functions returning [None] or
[NotHandled] cases. *)letadd_missing_pointwise_resultsfsuccessorargpointwisemanflow=(* Separate handled and not-handled cases *)lethandled_pointwise,not_handled=List.fold_left(fun(acc1,acc2)->function|None->(None,true)::acc1,acc2|Somer->leth,nh=Cases.partition(func_->matchcwithNotHandled->false|_->true)rinletacc1'=(h,(ifnh=Nonethenfalseelsetrue))::acc1inletacc2'=OptionExt.neutral2Cases.joinnhacc2inacc1',acc2')([],None)pointwiseinlethandled_pointwise=List.revhandled_pointwiseinletnot_handled=ifList.exists(functionNone->true|_->false)pointwisethenOptionExt.neutral2Cases.joinnot_handled(Some(Cases.not_handledflow))elsenot_handledinmatchnot_handledwith|None->pointwise|Somecases->(* Merge all cases in one before calling successor domain *)letsuccessor_res=Cases.remove_duplicatescompareman.latticecases>>=fun_flow->fsuccessorargflowin(* Put successor's result back in the pointwise results *)letpointwise'=List.map(fun(r,has_not_handled_cases)->matchrwith|None->Somesuccessor_res|Somerrwhennothas_not_handled_cases->Somerr|Somerr->Some(Cases.joinrrsuccessor_res))handled_pointwiseinset_pointwise_ctx(Cases.get_ctxsuccessor_res)pointwise'(** {2 Abstract transformer} *)(** ************************ *)(** Manager used by reductions *)letexec_reduction_man(man:('a,t)man):'aexec_reduction_man={get_man=(funid->find_domain_man~target:id~tree:Pool.idman);}(** Simplify a pointwise post-state by changing lists of unit into unit *)letsimplify_pointwise_post(pointwise:('a,unitoptionlist)cases):'apost=Cases.map_result(fun_->())pointwise(** Apply reduction rules on a post-conditions *)letreduce_poststmtmanprepost=letrman=exec_reduction_manmanin(* Reduce each case separately *)post|>Cases.bind@@funcaseflow->matchcasewith|Empty->Cases.emptyflow|NotHandled->Cases.not_handledflow|Result((),effects,_)->(* Iterate over rules *)letreciter=function|[]->Post.returnflow|rule::tl->letmoduleR=(valrule:EXEC_REDUCTION)inmatchR.reducestmtmanrmanprefloweffectswith|None->itertl|Somepost->postiniterRules.srules(** Entry point of abstract transformers *)letexectargets=letf=Pool.exectargetsin(funstmtmanflow->with_effects(fun()->apply_pointwisefstmtmanflow|>OptionExt.lift@@funpointwise->add_missing_pointwise_results(man.exec~route:successor)stmtpointwisemanflow|>merge_inter_conflictsmanflow|>simplify_pointwise_post|>merge_intra_conflictsmanflow|>reduce_poststmtmanflow))(** {2 Abstract evaluations} *)(** ************************ *)(** Manager used by reductions *)leteval_reduction_man(man:('a,t)man):'aeval_reduction_man={get_man=(funid->find_domain_man~target:id~tree:Pool.idman);}(** Apply reduction rules on a pointwise evaluation *)letreduce_pointwise_evalexpmaninput(pointwise:('a,exproptionlist)cases):'aeval=pointwise>>$funelflow->(* Keep only cases with non-empty results and remove duplicates *)letel'=List.filter(functionSome_->true|_->false)el|>List.map(functionSomee->e|_->assertfalse)|>List.sort_uniqcompare_exprinifel'=[]thenEval.emptyflowelseletrman=eval_reduction_manmaninletreciter=function|[]->(* XXX For performance reasons, we keep only one evaluation in
each conjunction.
THE CHOICE IS ARBITRARY!
*)Eval.singleton(List.hdel')flow|rule::tl->letmoduleR=(valrule:EVAL_REDUCTION)inmatchR.reduceexpmanrmaninputel'flowwith|None->itertl|Someevl->evliniterRules.erules(** Entry point of abstract evaluations *)letevaltargets=letf=Pool.evaltargetsin(funexpmanflow->with_effects(fun()->apply_pointwisefexpmanflow|>OptionExt.lift@@funpointwise->add_missing_pointwise_results(man.eval~route:successor)exppointwisemanflow|>merge_inter_conflictsmanflow|>reduce_pointwise_evalexpmanflow|>Eval.remove_duplicatesman.lattice|>merge_intra_conflictsmanflow))endletrecmake_pool:(moduleSTACKED_COMBINER)list->(modulePOOL)=function|[]->(moduleEmptyPool)|hd::tl->letmoduleS=(valhd)inletp=make_pooltlin(moduleMakePairPool(S)(valp))letmake(domains:(moduleSTACKED_COMBINER)list)~(eval_rules:(moduleEVAL_REDUCTION)list)~(exec_rules:(moduleEXEC_REDUCTION)list):(moduleSTACKED_COMBINER)=letp=make_pooldomainsin(moduleMake(valp)(structleterules=eval_rulesletsrules=exec_rulesend))