123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207(**************************************************************************)(* This file is part of the Codex semantics library. *)(* *)(* Copyright (C) 2013-2025 *)(* CEA (Commissariat à l'énergie atomique et aux énergies *)(* alternatives) *)(* *)(* 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, version 2.1. *)(* *)(* It 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. *)(* *)(* See the GNU Lesser General Public License version 2.1 *)(* for more details (enclosed in the file LICENSE). *)(* *)(**************************************************************************)(** This module presents the implementation of a simple example analyzer for
{{!While_ast}the while language} using codex constructs. It is detailed in
the While tutorial's {!page-chapter4}. *)(* Tracelog output will be handly. *)moduleLog=Tracelog.Make(structletcategory="While_analysis"end)(* $MDX part-begin=make_terms *)moduleTerms=Terms.Builder.Make(Terms.Condition.ConditionCudd)(Terms.Relations.Equality)()(* $MDX part-end *)(* $MDX part-begin=make_sva *)moduleSVA:Single_value_abstraction.Sig.NUMERIC_ENUM=structincludeSingle_value_abstraction.IvalincludeSingle_value_abstraction.Bitfieldend(* $MDX part-end *)(* $MDX part-begin=make_nonrel *)moduleNonRelationalDomain=Codex.Domains.Term_based.Nonrelational.Make(Terms)(SVA)(* $MDX part-end *)(* $MDX part-begin=make_domain *)(* This is an instance of the numerical domain, of signature Domain_sig.Base. *)moduleDomain=Domains.Term_domain.Make(Terms)(NonRelationalDomain)(* $MDX part-end *)moduleVar=While_ast.VarmoduleStore=Analysis_sva.Stateletstore_ppctx=Analysis_sva.map_pp(Domain.integer_prettyctx)(* $MDX part-begin=state *)typestate={ctx:Domain.Context.t;store:Domain.integerStore.t}(* initial value at analysis start *)letinitial_state()={ctx=Domain.root_context();store=Store.empty}(* $MDX part-end *)letppfmt{ctx;store}=Format.fprintffmt"{@[<v>ctx:@[%a@]@,store:%a}@]"Domain.context_prettyctx(store_ppctx)store(* $MDX part-begin=serialize *)letserialize~widensstate_astate_b=(* for all While variables that are not bound to the same Domain.integer *)Store.fold_on_nonequal_union(funvari1i2(Domain.Context.Result(included,in_tuple,continue)asresult)->ifi1==i2thenresultelse(* check they are indeed bound to different integers *)(* If one of these variable is unbound in one branch, bind it to top *)leti1=matchi1withSomei1->i1|None->Domain.integer_emptystate_a.ctxinleti2=matchi2withSomei2->i2|None->Domain.integer_emptystate_b.ctxin(* Create a new join variable using Domain.serialize_integer *)letDomain.Context.Result(included,in_tuple,local_continue)=Domain.serialize_integer~widensstate_a.ctxi1state_b.ctxi2(included,in_tuple)inDomain.Context.Result(included,in_tuple,functxout_tuple->(* Call the local continuation which will give us the new integer value for our variable *)letinteger,out_tuple=local_continuectxout_tuplein(* Call the global continuation which will build the rest of the store *)letstore,out_tuple=continuectxout_tuplein(* Add the new value to the store *)(Store.addvarintegerstore,out_tuple)))state_a.storestate_b.store(* Initial result: returned when all variables are the same:
- inclusion is true
- the serialize tuple is empty
- the store is initialized to the left branch's store *)(Domain.Context.Result(true,Domain.Context.empty_tuple(),fun_ctxout->state_a.store,out))(* $MDX part-end *)(* $MDX part-begin=join *)letjoinstate_astate_b=letDomain.Context.Result(included,in_tuple,continue)=serialize~widens:falsestate_astate_binletctx,out=Domain.typed_nondet2state_a.ctxstate_b.ctxin_tupleinletstore,_=continuectxoutin{ctx;store}(* $MDX part-end *)(* $MDX part-begin=join_opt *)letjoin_optab=matcha,bwith|None,x|x,None->x|Somea,Someb->Some(joinab)(* $MDX part-end *)(* $MDX part-begin=widen *)letwidenwidening_idstate_astate_b=letDomain.Context.Result(included,in_tuple,continue)=serialize~widens:truestate_astate_binletctx,included,out=Domain.widened_fixpoint_step~widening_id~previous:state_a.ctx~next:state_b.ctx(included,in_tuple)inletstore,_=continuectxoutin{ctx;store},included(* $MDX part-end *)(* $MDX part-begin=aexp *)letrecaexp:state->While_ast.aexp->Domain.integer=funstateexp->matchexpwith|Varv->Store.findvstate.store|Intc->Domain.Integer_Forward.iconst(Z.of_intc)state.ctx|Add(e1,e2)->Domain.Integer_Forward.iaddstate.ctx(aexpstatee1)(aexpstatee2)|Sub(e1,e2)->Domain.Integer_Forward.isubstate.ctx(aexpstatee1)(aexpstatee2)|Mul(e1,e2)->Domain.Integer_Forward.imulstate.ctx(aexpstatee1)(aexpstatee2)(* $MDX part-end *)(* $MDX part-begin=bexp *)letrecbexp:state->While_ast.bexp->Domain.boolean=funstateexp->matchexpwith|True->Domain.Boolean_Forward.true_state.ctx|False->Domain.Boolean_Forward.false_state.ctx|Le(e1,e2)->Domain.Integer_Forward.ilestate.ctx(aexpstatee1)(aexpstatee2)|Eq(e1,e2)->Domain.Integer_Forward.ieqstate.ctx(aexpstatee1)(aexpstatee2)|And(e1,e2)->Domain.Boolean_Forward.(&&)state.ctx(bexpstatee1)(bexpstatee2)|Note1->Domain.Boolean_Forward.notstate.ctx(bexpstatee1)|Gt(e1,e2)->Domain.Boolean_Forward.notstate.ctx@@Domain.Integer_Forward.ilestate.ctx(aexpstatee1)(aexpstatee2)(* $MDX part-end *)letpp_retfmt=function|None->Format.fprintffmt"<bottom>"|Somex->ppfmtx(* $MDX part-begin=stmt *)letcopy{store;ctx}={store;ctx=Domain.Context.copyctx}let(let*)=Option.bindletrecanalyze_stmt:stateoption->While_ast.stmt->stateoption=funstate_optstmt->let*state=state_optinLog.trace(funp->p"analyze_stmt %a"While_ast.pp_stmtstmt)~pp_ret@@fun()->matchstmtwith|Skip->state_opt|Assign(var,exp)->letv=aexpstateexpinSome{statewithstore=Store.addvarvstate.store}|Seq(c1,c2)->letstate_opt'=analyze_stmtstate_optc1inanalyze_stmtstate_opt'c2|If(cond,if_true,if_false)->(letbexp_cond=bexpstatecondinletstate'=copystateinmatchDomain.assumestate.ctxbexp_cond,Domain.assumestate'.ctx(Domain.Boolean_Forward.notstate'.ctxbexp_cond)with|None,None(* bottom *)->None|Somectx,None(* true *)->analyze_stmt(Some{statewithctx=ctx})if_true|None,Somectx(* false *)->analyze_stmt(Some{state'withctx=ctx})if_false|Somectx_true,Somectx_false(* top *)->join_opt(analyze_stmt(Some{statewithctx=ctx_true})if_true)(analyze_stmt(Some{state'withctx=ctx_false})if_false))|While(cond,body)->letwidening_id=Domains.Sig.Widening_Id.fresh()inletone_iterationstate=(* update the state by one loop iteration: assume the condition and apply the body *)let*ctx=Domain.assumestate.ctx(bexpstatecond)inanalyze_stmt(Some{statewithctx})bodyinletinitial_state=copystateinletrecloophead=let*next_head=one_iterationheadinletnext_head=joininitial_statenext_headinletwidened,included=widenwidening_idheadnext_headinifnotincludedthenloopwidenedelse(* fixpoint reached: exit loop, assume condition is false *)let*ctx=bexpnext_headcond|>Domain.Boolean_Forward.notnext_head.ctx|>Domain.assumenext_head.ctxinSome{next_headwithctx}inloopstate(* $MDX part-end *)