123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401(** Abstract domains with Hoare ordering. *)modulePretty=GoblintCil.PrettyopenPrettyexceptionUnsupportedofstringletunsupporteds=raise(Unsupporteds)(* Hoare hash set for partial orders: keeps incomparable elements separate
- All comparable elements must have the same hash so that they land in the same bucket!
- Pairwise operations like join then only need to be done per bucket.
- E should throw Lattice.Incomparable if an operation is not defined for two elements.
In this case the operation will be done on the level of the set instead.
- Hoare set means that for comparable elements, we only keep the biggest one.
-> We only need to find the first comparable element for a join etc.
-> There should only be one element per bucket except for hash collisions.
*)moduleHoarePO(E:Lattice.PO)=structopenBatteriestypebucket=E.tlisttypet=bucketMap.Int.tmoduleMap=Map.IntmoduleB=struct(* bucket *)(* join element e with bucket using op *)letrecjoinope=function|[]->[e]|x::xs->tryopex::xswithLattice.Uncomparable->x::joinopexs(* widen new(!) element e with old(!) bucket using op *)letrecwidenope=function|[]->[]|x::xs->tryifE.leqxethen[opxe]elsewidenopexswithLattice.Uncomparable->widenopexs(* only widen if valid *)(* meet element e with bucket using op *)letrecmeetope=function|[]->[]|x::xs->try[opex]withLattice.Uncomparable->meetopexs(* merge element e into its bucket in m using f, discard bucket if empty *)letmerge_elementfem=leti=E.hasheinletb=fe(Map.find_default[]im)inifb=[]thenMap.removeimelseMap.addibmendletelementsm=m|>Map.to_seq|>Seq.mapsnd|>List.of_seq|>List.flatten(* merge elements in x and y by f *)(* TODO: unused, remove? *)letmergeopfxy=letg=matchopwith|`Join->B.join|`Meet->B.meetinMap.merge(funiab->matcha,bwith|Somea,Someb->letr=List.fold_left(flip(gf))abinifr=[]thenNoneelseSomer|Somex,None|None,Somexwhenop=`Join->Somex|_->None)xyletmerge_meetfxy=Map.merge(funiab->matcha,bwith|Somea,Someb->letr=List.concat_map(funx->B.meetfxa)binifr=[]thenNoneelseSomer|_->None)xyletmerge_widenfxy=Map.merge(funiab->matcha,bwith|Somea,Someb->letr=List.concat_map(funx->B.widenfxa)binletr=List.fold_left(funrx->B.joinE.joinxr)rbin(* join b per bucket *)ifr=[]thenNoneelseSomer|None,Someb->Someb(* join b per bucket *)|_->None)xy(* join all elements from the smaller map into their bucket in the other one.
* this doesn't need to go over all elements of both maps as the general merge above. *)letmerge_joinfxy=letx,y=ifMap.cardinalx<Map.cardinalythenx,yelsey,xinList.fold_left(flip(B.merge_element(B.joinf)))y(elementsx)letjoinxy=merge_joinE.joinxyletwidenxy=merge_widenE.widenxyletmeetxy=merge_meetE.meetxyletnarrowxy=merge_meetE.narrowxy(* TODO: fix narrow like widen? see Set *)(* Set *)letof_list_byfes=List.fold_left(flip(B.merge_element(B.joinf)))Map.emptyesletof_listes=of_list_byE.joinesletsingletone=of_list[e]letexistspm=List.existsp(elementsm)letfor_allpm=List.for_allp(elementsm)letmemem=exists(E.leqe)mletchoosem=List.hd(snd(Map.choosem))letapply_listfm=of_list(f(elementsm))letmapfm=(* Map.map (List.map f) m *)(* since hashes might change we need to rebuild: *)apply_list(List.mapf)mletfilterfm=apply_list(List.filterf)m(* TODO do something better? unused *)letremovexm=(* NB! strong removal *)letngreqxy=not(E.leqyx)inB.merge_element(fun_->List.filter(ngreqx))xm(* let add e m = if mem e m then m else B.merge List.cons e m *)letaddem=ifmememthenmelsejoin(singletone)mletfoldfma=Map.fold(fun_->List.fold_rightf)maletcardinalm=fold(constsucc)m0letdiffab=apply_list(List.filter(funx->not(memxb)))a(* NB! strong removal *)letempty()=Map.emptyletis_emptym=Map.is_emptym(* let union x y = merge (B.join keep_apart) x y *)letunionxy=joinxyletiterfm=Map.iter(fun_->List.iterf)m(* Lattice *)letbot()=Map.emptyletis_bot=Map.is_emptylettop()=unsupported"HoarePO.top"letis_top_=falseletleqxy=(* all elements in x must be leq than the ones in y *)for_all(flipmemy)x(* Printable *)letname()="Set ("^E.name()^")"(* let equal x y = try Map.equal (List.for_all2 E.equal) x y with Invalid_argument _ -> false *)letequalxy=leqxy&&leqyxlethashxs=fold(funva->a+E.hashv)xs0letcomparexy=ifequalxythen0else(letcaridnality_comp=compare(cardinalx)(cardinaly)inifcaridnality_comp<>0thencaridnality_compelseMap.compare(List.compareE.compare)xy)letshowx:string=letall_elems:stringlist=List.mapE.show(elementsx)inPrintable.get_short_list"{""}"all_elemsletto_yojsonx=[%to_yojson:E.tlist](elementsx)letpretty()x=letcontent=List.map(E.pretty())(elementsx)inletrecseparatex=matchxwith|[]->[]|[x]->[x]|(x::xs)->x++(text", ")::separatexsinletseparated=separatecontentinletcontent=List.fold_left(++)nilseparatedin(text"{")++content++(text"}")letpretty_diff()((x:t),(y:t)):Pretty.doc=Pretty.dprintf"HoarePO: %a not leq %a"prettyxprettyyletprintXmlfx=BatPrintf.fprintff"<value>\n<set>\n";List.iter(E.printXmlf)(elementsx);BatPrintf.fprintff"</set>\n</value>\n"end[@@deprecated]moduletypeSetS=sigincludeSetDomain.Svalapply_list:(eltlist->eltlist)->t->tend(** Set of [Lattice.S] elements with Hoare ordering.
This abstracts a set by its {e maximal} elements.
Element-wise {!SetDomain.S} operations only observe the maximal elements.
This has {e extrapolation heuristics} instead of a true [widen],
i.e. convergence is only guaranteed if the number of maximal
elements converges.
Otherwise use {!SetEM}.
@see <https://doi.org/10.1007/s10009-005-0215-8> Bagnara, R., Hill, P.M. & Zaffanella, E. Widening operators for powerset domains. *)moduleSet(B:Lattice.S):SetSwithtypeelt=B.t=structincludeSetDomain.Make(B)letmemxs=exists(B.leqx)sletleqab=for_all(funx->memxb)a(* mem uses B.leq! *)letlexy=B.leqxy&¬(B.equalxy)&¬(B.leqyx)letreduces=filter(funx->not(exists(lex)s))sletproduct_botopab=leta,b=elementsa,elementsbinList.concat_map(funx->List.map(funy->opxy)b)a|>funx->reduce(of_listx)letproduct_widenopab=(* assumes b to be bigger than a *)letxs,ys=elementsa,elementsbinList.concat_map(funx->List.map(funy->opxy)ys)xs|>funx->reduce(unionb(of_listx))letwiden=product_widen(funxy->ifB.leqxythenB.widenxyelseB.bot())letnarrow=product_bot(funxy->ifB.leqyxthenB.narrowxyelsex)letaddxa=ifmemxathenaelseaddxa(* special mem! *)letremovexa=filter(funy->not(B.leqyx))a(* NB! strong removal *)letjoinab=unionab|>reduceletunion__=unsupported"Set.union"letinter__=unsupported"Set.inter"letmeet=product_botB.meetletsubset__=unsupported"Set.subset"letmapfa=mapfa|>reduceletmin_elta=B.bot()letapply_listfs=elementss|>f|>of_listletdiffab=apply_list(List.filter(funx->not(memxb)))a(* NB! strong removal *)letof_listxs=List.fold_rightaddxs(empty())|>reduce(* TODO: why not use Make's of_list if reduce anyway, right now add also is special *)(* Copied from Make *)letarbitrary()=QCheck.map~rev:elementsof_list@@QCheck.small_list(B.arbitrary())letpretty_diff()((s1:t),(s2:t)):Pretty.doc=ifleqs1s2thendprintf"%s (%d and %d paths): These are fine!"(name())(cardinals1)(cardinals2)elsebegintryletpt=not(memts2)inletevil=choose(filterps1)indprintf"%a:\n"B.prettyevil++ifis_emptys2thentext"empty set s2"elsefold(funotheracc->(dprintf"not leq %a because %a\n"B.prettyotherB.pretty_diff(evil,other))++acc)s2nilwithNot_found->dprintf"choose failed b/c of empty set s1: %d s2: %d"(cardinals1)(cardinals2)endendmoduleSet_LiftTop(B:Lattice.S)(N:SetDomain.ToppedSetNames):SetSwithtypeelt=B.t=structmoduleS=Set(B)includeSetDomain.LiftTop(S)(N)letmin_elta=B.bot()letapply_listf=function|`Top->`Top|`Lifteds->`Lifted(S.apply_listfs)end(* TODO: weaken R to Lattice.S ? *)moduleMapBot(SpecD:Lattice.S)(R:SetDomain.S)=structincludeMapDomain.MapBot(SpecD)(R)(* TODO: get rid of these value-ignoring set-mimicing hacks *)letchoose'=chooseletchoose(s:t):SpecD.t=fst(choose's)letfilter'=filterletfilter(p:key->bool)(s:t):t=filter(funx_->px)sletiter'=iterletfor_all'=for_allletexists'=existsletexists(p:key->bool)(s:t):bool=exists(funx_->px)sletfold'=foldletfold(f:key->'a->'a)(s:t)(acc:'a):'a=fold(funx_acc->fxacc)saccletadd(x:key)(r:R.t)(s:t):t=addx(R.joinr(findxs))sletmap(f:key->key)(s:t):t=fold'(funxvacc->add(fx)vacc)s(empty())(* TODO: reducing map, like HoareSet *)letelements(s:t):(key*R.t)list=bindingssletof_list(l:(key*R.t)list):t=List.fold_left(funacc(x,r)->addxracc)(empty())lletunion=long_map2R.union(* copied & modified from SetDomain.Hoare_NoTop *)letmemxxrs=R.for_all(funvie->exists'(funyyr->SpecD.leqxy&&R.memvieyr)s)xrletleqab=for_all'(funxxr->memxxrb)a(* mem uses B.leq! *)letlexy=SpecD.leqxy&¬(SpecD.equalxy)&¬(SpecD.leqyx)letreduce(s:t):t=(* get map with just maximal keys and their ranges *)letmaximals=filter(funx->not(exists(lex)s))sin(* join le ranges also *)letmaximals=mapi(funxxr->fold'(funyyracc->ifleyxthenR.joinaccyrelseacc)sxr)maximalsinmaximalsletproduct_botopop2ab=leta,b=elementsa,elementsbinList.concat_map(fun(x,xr)->List.map(fun(y,yr)->(opxy,op2xryr))b)a|>funx->reduce(of_listx)letproduct_bot2op2ab=leta,b=elementsa,elementsbinList.concat_map(fun(x,xr)->List.map(fun(y,yr)->op2(x,xr)(y,yr))b)a|>funx->reduce(of_listx)(* why are type annotations needed for product_widen? *)(* TODO: unused now *)letproduct_widenopop2(a:t)(b:t):t=(* assumes b to be bigger than a *)letxs,ys=elementsa,elementsbinList.concat_map(fun(x,xr)->List.map(fun(y,yr)->(opxy,op2xryr))ys)xs|>funx->reduce(joinb(of_listx))(* join instead of union because R is HoareDomain.Set for witness generation *)letproduct_widen2op2(a:t)(b:t):t=(* assumes b to be bigger than a *)letxs,ys=elementsa,elementsbinList.concat_map(fun(x,xr)->List.map(fun(y,yr)->op2(x,xr)(y,yr))ys)xs|>funx->reduce(joinb(of_listx))(* join instead of union because R is HoareDomain.Set for witness generation *)letjoinab=joinab|>reduceletmeet=product_botSpecD.meetR.inter(* let narrow = product_bot (fun x y -> if SpecD.leq y x then SpecD.narrow x y else x) R.narrow *)(* TODO: move PathSensitive3-specific narrow out of HoareMap *)letnarrow=product_bot2(fun(x,xr)(y,yr)->ifSpecD.leqyxthen(SpecD.narrowxy,yr)else(x,xr))(* let widen = product_widen (fun x y -> if SpecD.leq x y then SpecD.widen x y else SpecD.bot ()) R.widen *)(* TODO: move PathSensitive3-specific widen out of HoareMap *)letwiden=product_widen2(fun(x,xr)(y,yr)->ifSpecD.leqxythen(SpecD.widenxy,yr)else(y,yr))(* TODO: is this right now? *)(* TODO: shouldn't this also reduce? *)letapply_listfs=elementss|>f|>of_listletpretty_diff()((s1:t),(s2:t)):Pretty.doc=ifleqs1s2thendprintf"%s (%d and %d paths): These are fine!"(name())(cardinals1)(cardinals2)elsebegintryletpttr=not(memttrs2)inlet(evil,evilr)=choose'(filter'ps1)inletevilr'=R.chooseevilrindprintf"%a -> %a:\n"SpecD.prettyevilR.pretty(R.singletonevilr')++ifis_emptys2thentext"empty set s2"elsefold'(funotherotherracc->(dprintf"not leq %a because %a\nand not mem %a because %a\n"SpecD.prettyotherSpecD.pretty_diff(evil,other)R.prettyotherrR.pretty_diff(R.singletonevilr',otherr))++acc)s2nilwithNot_found->dprintf"choose failed b/c of empty set s1: %d s2: %d"(cardinals1)(cardinals2)endend[@@deprecated](** Set of [Lattice.S] elements with Hoare ordering.
This abstracts a set by its {e maximal} elements.
Element-wise {!SetDomain.S} operations only observe the maximal elements.
This has a true [widen] using the trivial Egli-Milner connector,
i.e. convergence is even guaranteed if the number of maximal
elements does not converge.
Otherwise {!Set} is sufficient.
@see <https://doi.org/10.1007/s10009-005-0215-8> Bagnara, R., Hill, P.M. & Zaffanella, E. Widening operators for powerset domains. *)moduleSetEM(E:Lattice.S):SetDomain.Swithtypeelt=E.t=structmoduleH=Set(E)includeH(* version of widen which doesn't use E.bot *)(* TODO: move to Set above? *)letproduct_widen(op:elt->elt->eltoption)ab=(* assumes b to be bigger than a *)letxs,ys=elementsa,elementsbinList.concat_map(funx->List.filter_map(funy->opxy)ys)xs|>funx->joinb(of_listx)letwiden=product_widen(funxy->ifE.leqxythenSome(E.widenxy)elseNone)(* above widen is actually extrapolation operator, so define connector-based widening instead *)(** Egli-Milner partial order relation.
See Bagnara, Section 3. *)letleq_ems1s2=is_bots1||leqs1s2&&for_all(fune2->exists(fune1->E.leqe1e2)s1)s2(** Egli-Milner connector, i.e. {e any} upper bound operator for {!leq_em}.
Trivial connector, which joins all elements to a singleton set.
See Bagnara, Section 3. *)letjoin_ems1s2=joins1s2|>elements|>BatList.reduceE.join|>singleton(** Connector-based widening.
See Bagnara, Section 6. *)letwidens1s2=assert(leqs1s2);lets2'=ifleq_ems1s2thens2elsejoin_ems1s2inwidens1s2'end