Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source
Source file dataflow.ml
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507moduleIH=InthashmoduleE=ErrormsgopenCilopenPretty(** A framework for data flow analysis for CIL code. Before using
this framework, you must initialize the Control-flow Graph for your
program, e.g using {!Cfg.computeFileCFG} *)type'taction=Default(** The default action *)|Doneof't(** Do not do the default action. Use this result *)|Postof('t->'t)(** The default action, followed by the given
transformer *)type'tstmtaction=SDefault(** The default action *)|SDone(** Do not visit this statement or its successors *)|SUseof't(** Visit the instructions and successors of this statement
as usual, but use the specified state instead of the
one that was passed to doStmt *)(* For if statements *)type'tguardaction=GDefault(** The default state *)|GUseof't(** Use this data for the branch *)|GUnreachable(** The branch will never be taken. *)(******************************************************************
**********
********** FORWARDS
**********
********************************************************************)moduletypeForwardsTransfer=sigvalname:string(** For debugging purposes, the name of the analysis *)valdebug:boolref(** Whether to turn on debugging *)typet(** The type of the data we compute for each block start. May be
imperative. *)valcopy:t->t(** Make a deep copy of the data *)valstmtStartData:tInthash.t(** For each statement id, the data at the start. Not found in the hash
table means nothing is known about the state at this point. At the end
of the analysis this means that the block is not reachable. *)valpretty:unit->t->Pretty.doc(** Pretty-print the state *)valcomputeFirstPredecessor:Cil.stmt->t->t(** Give the first value for a predecessors, compute the value to be set
for the block *)valcombinePredecessors:Cil.stmt->old:t->t->toption(** Take some old data for the start of a statement, and some new data for
the same point. Return None if the combination is identical to the old
data. Otherwise, compute the combination, and return it. *)valdoInstr:Cil.instr->t->taction(** The (forwards) transfer function for an instruction. The
{!Cil.currentLoc} is set before calling this. The default action is to
continue with the state unchanged. *)valdoStmt:Cil.stmt->t->tstmtaction(** The (forwards) transfer function for a statement. The {!Cil.currentLoc}
is set before calling this. The default action is to do the instructions
in this statement, if applicable, and continue with the successors. *)valdoGuard:Cil.exp->t->tguardaction(** Generate the successor to an If statement assuming the given expression
is nonzero. Analyses that don't need guard information can return
GDefault; this is equivalent to returning GUse of the input.
A return value of GUnreachable indicates that this half of the branch
will not be taken and should not be explored. This will be called
twice per If, once for "then" and once for "else".
*)valfilterStmt:Cil.stmt->bool(** Whether to put this statement in the worklist. This is called when a
block would normally be put in the worklist. *)endmoduleForwardsDataFlow=functor(T:ForwardsTransfer)->struct(** Keep a worklist of statements to process. It is best to keep a queue,
because this way it is more likely that we are going to process all
predecessors of a statement before the statement itself. *)letworklist:Cil.stmtQueue.t=Queue.create()(** We call this function when we have encountered a statement, with some
state. *)letreachedStatement(s:stmt)(d:T.t):unit=letloc=get_stmtLocs.skindinifloc!=locUnknownthencurrentLoc:=get_stmtLocs.skind;(* see if we know about it already *)E.pushContext(fun_->dprintf"Reached statement %d with %a"s.sidT.prettyd);letnewdata:T.toption=tryletold=IH.findT.stmtStartDatas.sidinmatchT.combinePredecessorss~old:olddwithNone->(* We are done here *)if!T.debugthenignore(E.log"FF(%s): reached stmt %d with %a\n implies the old state %a\n"T.names.sidT.prettydT.prettyold);None|Somed'->begin(* We have changed the data *)if!T.debugthenignore(E.log"FF(%s): weaken data for block %d: %a\n"T.names.sidT.prettyd');Somed'endwithNot_found->(* was bottom before *)letd'=T.computeFirstPredecessorsdinif!T.debugthenignore(E.log"FF(%s): set data for block %d: %a\n"T.names.sidT.prettyd');Somed'inE.popContext();matchnewdatawithNone->()|Somed'->IH.replaceT.stmtStartDatas.sidd';ifT.filterStmts&¬(Queue.fold(funexistss'->exists||s'.sid=s.sid)falseworklist)thenQueue.addsworklist(** Get the two successors of an If statement *)letifSuccs(s:stmt):stmt*stmt=letfstStmtblk=matchblk.bstmtswith[]->Cil.dummyStmt|fst::_->fstinmatchs.skindwithIf(e,b1,b2,_,_)->letthenSucc=fstStmtb1inletelseSucc=fstStmtb2inletoneFallthrough()=letfallthrough=List.filter(funs'->thenSucc!=s'&&elseSucc!=s')s.succsinmatchfallthroughwith[]->E.s(bug"Bad CFG: missing fallthrough for If.")|[s']->s'|_->E.s(bug"Bad CFG: multiple fallthrough for If.")in(* If thenSucc or elseSucc is Cil.dummyStmt, it's an empty block.
So the successor is the statement after the if *)letstmtOrFallthroughs'=ifs'==Cil.dummyStmtthenoneFallthrough()elses'in(stmtOrFallthroughthenSucc,stmtOrFallthroughelseSucc)|_->E.s(bug"ifSuccs on a non-If Statement.")(** Process a statement *)letprocessStmt(s:stmt):unit=currentLoc:=get_stmtLocs.skind;if!T.debugthenignore(E.log"FF(%s).stmt %d at %t\n"T.names.sidd_thisloc);(* It must be the case that the block has some data *)letinit:T.t=tryT.copy(IH.findT.stmtStartDatas.sid)withNot_found->E.s(E.bug"FF(%s): processing block without data"T.name)in(* See what the custom says *)matchT.doStmtsinitwithSDone->()|(SDefault|SUse_)asact->beginletcurr=matchactwithSDefault->init|SUsed->d|SDone->E.s(bug"SDone")in(* Do the instructions in order *)lethandleInstruction(s:T.t)(i:instr):T.t=currentLoc:=get_instrLoci;(* Now handle the instruction itself *)lets'=letaction=T.doInstrisinmatchactionwith|Dones'->s'|Default->s(* do nothing *)|Postf->fsins'inletafter:T.t=matchs.skindwithInstril->(* Handle instructions starting with the first one *)List.fold_lefthandleInstructioncurril|Goto_|ComputedGoto_|Break_|Continue_|If_|Switch_|Loop_|Return_|Block_->currincurrentLoc:=get_stmtLocs.skind;(* Handle If guards *)letsuccsToReach=matchs.skindwithIf(e,_,_,_,_)->beginletnot_e=UnOp(LNot,e,intType)inletthenGuard=T.doGuardeafterinletelseGuard=T.doGuardnot_eafterinifthenGuard=GDefault&&elseGuard=GDefaultthen(* this is the common case *)s.succselsebeginletdoBranchsuccguard=matchguardwithGDefault->reachedStatementsuccafter|GUsed->reachedStatementsuccd|GUnreachable->if!T.debugthenignore(E.log"FF(%s): Not exploring branch to %d\n"T.namesucc.sid);()inletthenSucc,elseSucc=ifSuccssindoBranchthenSuccthenGuard;doBranchelseSuccelseGuard;[]endend|_->s.succsin(* Reach the successors *)List.iter(funs'->reachedStatements'after)succsToReach;end(** Compute the data flow. Must have the CFG initialized *)letcompute(sources:stmtlist)=Queue.clearworklist;List.iter(funs->Queue.addsworklist)sources;(* All initial stmts must have non-bottom data *)List.iter(funs->ifnot(IH.memT.stmtStartDatas.sid)thenE.s(E.error"FF(%s): initial stmt %d does not have data"T.names.sid))sources;if!T.debugthenignore(E.log"\nFF(%s): processing\n"T.name);letrecfixedpoint()=if!T.debug&¬(Queue.is_emptyworklist)thenignore(E.log"FF(%s): worklist= %a\n"T.name(docList(funs->nums.sid))(List.rev(Queue.fold(funaccs->s::acc)[]worklist)));letkeepgoing=trylets=Queue.takeworklistinprocessStmts;truewithQueue.Empty->if!T.debugthenignore(E.log"FF(%s): done\n\n"T.name);falseinifkeepgoingthenfixedpoint()infixedpoint()end(******************************************************************
**********
********** BACKWARDS
**********
********************************************************************)moduletypeBackwardsTransfer=sigvalname:string(* For debugging purposes, the name of the analysis *)valdebug:boolref(** Whether to turn on debugging *)typet(** The type of the data we compute for each block start. In many
presentations of backwards data flow analysis we maintain the
data at the block end. This is not easy to do with JVML because
a block has many exceptional ends. So we maintain the data for
the statement start. *)valpretty:unit->t->Pretty.doc(** Pretty-print the state *)valstmtStartData:tInthash.t(** For each block id, the data at the start. This data structure must be
initialized with the initial data for each block *)valfuncExitData:t(** The data at function exit. Used for statements with no successors.
This is usually bottom, since we'll also use doStmt on Return
statements. *)valcombineStmtStartData:Cil.stmt->old:t->t->toption(** When the analysis reaches the start of a block, combine the old data
with the one we have just computed. Return None if the combination is
the same as the old data, otherwise return the combination. In the
latter case, the predecessors of the statement are put on the working
list. *)valcombineSuccessors:t->t->t(** Take the data from two successors and combine it *)valdoStmt:Cil.stmt->taction(** The (backwards) transfer function for a branch. The {!Cil.currentLoc} is
set before calling this. If it returns None, then we have some default
handling. Otherwise, the returned data is the data before the branch
(not considering the exception handlers) *)valdoInstr:Cil.instr->t->taction(** The (backwards) transfer function for an instruction. The
{!Cil.currentLoc} is set before calling this. If it returns None, then we
have some default handling. Otherwise, the returned data is the data
before the branch (not considering the exception handlers) *)valfilterStmt:Cil.stmt->Cil.stmt->bool(** Whether to put this predecessor block in the worklist. We give the
predecessor and the block whose predecessor we are (and whose data has
changed) *)endmoduleBackwardsDataFlow=functor(T:BackwardsTransfer)->structletgetStmtStartData(s:stmt):T.t=tryIH.findT.stmtStartDatas.sidwithNot_found->E.s(E.bug"BF(%s): stmtStartData is not initialized for %d: %a"T.names.sidd_stmts)(** Process a statement and return true if the set of live return
addresses on its entry has changed. *)letprocessStmt(s:stmt):bool=if!T.debugthenignore(E.log"FF(%s).stmt %d\n"T.names.sid);(* Find the state before the branch *)currentLoc:=get_stmtLocs.skind;letd:T.t=matchT.doStmtswithDoned->d|(Default|Post_)asaction->begin(* Do the default one. Combine the successors *)letres=matchs.succswith[]->T.funcExitData|fst::rest->List.fold_left(funaccsucc->T.combineSuccessorsacc(getStmtStartDatasucc))(getStmtStartDatafst)restin(* Now do the instructions *)letres'=matchs.skindwithInstril->(* Now scan the instructions in reverse order. This may
Stack_overflow on very long blocks ! *)lethandleInstruction(i:instr)(s:T.t):T.t=currentLoc:=get_instrLoci;(* First handle the instruction itself *)letaction=T.doInstrisinmatchactionwith|Dones'->s'|Default->s(* do nothing *)|Postf->fsin(* Handle instructions starting with the last one *)List.fold_righthandleInstructionilres|_->resinmatchactionwithPostf->fres'|_->res'endin(* See if the state has changed. The only changes are that it may grow.*)lets0=getStmtStartDatasinmatchT.combineStmtStartDatas~old:s0dwithNone->(* The old data is good enough *)false|Somed'->(* We have changed the data *)if!T.debugthenignore(E.log"BF(%s): set data for block %d: %a\n"T.names.sidT.prettyd');IH.replaceT.stmtStartDatas.sidd';true(** Compute the data flow. Must have the CFG initialized *)letcompute(sinks:stmtlist)=letworklist:Cil.stmtQueue.t=Queue.create()inList.iter(funs->Queue.addsworklist)sinks;if!T.debug&¬(Queue.is_emptyworklist)thenignore(E.log"\nBF(%s): processing\n"T.name);letrecfixedpoint()=if!T.debug&¬(Queue.is_emptyworklist)thenignore(E.log"BF(%s): worklist= %a\n"T.name(docList(funs->nums.sid))(List.rev(Queue.fold(funaccs->s::acc)[]worklist)));letkeepgoing=trylets=Queue.takeworklistinletchanges=processStmtsinifchangesthenbegin(* We must add all predecessors of block b, only if not already
in and if the filter accepts them. *)List.iter(funp->ifnot(Queue.fold(funexistss'->exists||p.sid=s'.sid)falseworklist)&&T.filterStmtpsthenQueue.addpworklist)s.preds;end;truewithQueue.Empty->if!T.debugthenignore(E.log"BF(%s): done\n\n"T.name);falseinifkeepgoingthenfixedpoint();infixedpoint();end(** Helper utility that finds all of the statements of a function.
It also lists the return statments (including statements that
fall through the end of a void function). Useful when you need an
initial set of statements for BackwardsDataFlow.compute. *)letsink_stmts=ref[]letall_stmts=ref[]letsinkFinder=object(self)inheritnopCilVisitormethod!vstmts=all_stmts:=s::(!all_stmts);matchs.succswith[]->(sink_stmts:=s::(!sink_stmts);DoChildren)|_->DoChildrenend(* returns (all_stmts, return_stmts). *)letfind_stmts(fdec:fundec):(stmtlist*stmtlist)=ignore(visitCilFunction(sinkFinder)fdec);letall=!all_stmtsinletret=!sink_stmtsinall_stmts:=[];sink_stmts:=[];all,ret