123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434(**************************************************************************)(* *)(* This file is part of WP plug-in of Frama-C. *)(* *)(* Copyright (C) 2007-2023 *)(* CEA (Commissariat a l'energie atomique et aux energies *)(* 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 licenses/LGPLv2.1). *)(* *)(**************************************************************************)openCil_typesopenCil_datatype(* -------------------------------------------------------------------------- *)(* --- Compute Reachability for Smoke Tests --- *)(* -------------------------------------------------------------------------- *)typeflow=|F_goto(* single successor for node *)|F_effect(* single successor with must-be-reach effect *)|F_call(* multiple successors with must-be-reach effect *)|F_branch(* branching node *)|F_return(* return node *)|F_entry(* function or loop entry node *)|F_dead(* truly dead code *)typenode={id:int;mutableflow:flow;mutableprev:nodelist;mutableprotected:booloption;(* whether the node is dominated by unreachable node or by a smoke test *)mutableunreachable:booloption;(* whether the node is unreachable from the entry point *)}letkid=ref0letnode()=incrkid;{id=!kid;prev=[];protected=None;unreachable=None;flow=F_goto}(* -------------------------------------------------------------------------- *)(* --- Unrolled Loop --- *)(* -------------------------------------------------------------------------- *)letis_unrolled_completelyspec=matchspec.term_nodewith|TConst(LStr"completely")->true|_->falseletrecis_predicatecondp=matchp.pred_contentwith|Pfalse->notcond|Ptrue->cond|Pnotp->is_predicate(notcond)p|Pforall(_,p)|Pexists(_,p)|Plet(_,p)->is_predicatecondp|Pif(_,p,q)->is_predicatecondp&&is_predicatecondq|Pat(p,_)->is_predicatecondp|Pand(p,q)->ifcondthenis_predicatetruep&&is_predicatetrueqelseis_predicatefalsep||is_predicatefalseq|Por(p,q)->ifcondthenis_predicatetruep&&is_predicatetrueqelseis_predicatefalsep&&is_predicatefalseq|Pimplies(p,q)->ifcondthenis_predicatefalsep||is_predicatetrueqelseis_predicatetruep&&is_predicatefalseq|_->falseletis_dead_annotca=matchca.annot_contentwith|APragma(Loop_pragma(Unroll_specs[spec;_]))->is_unrolled_completelyspec|AAssert([],p)|AInvariant([],_,p)->Logic_utils.use_predicatep.tp_kind&&is_predicatefalsep.tp_statement|_->falseletis_dead_codestmt=letexceptionDeadcodeintryAnnotations.iter_code_annot(fun_emitterca->ifis_dead_annotcathenraiseDeadcode)stmt;falsewithDeadcode->true(* -------------------------------------------------------------------------- *)(* --- Compute CFG --- *)(* -------------------------------------------------------------------------- *)typereachability=nodeStmt.Map.ttypecfg=reachabilityref(* working cfg during compilation *)letof_stmtcfgs=tryStmt.Map.finds!cfgwithNot_found->letn=node()incfg:=Stmt.Map.addsn!cfg;nletgotoab=b.prev<-a::b.prev;ifb.flow=F_deadthenF_deadelseF_gotoletflowif=iff=F_deadthenF_deadelsematchiwith|Asm_|Set_->F_effect|Local_init_->ifWp_parameters.SmokeDeadlocalinit.get()thenF_effectelseF_goto|Call_->F_call|Skip_|Code_annot_->F_gotoletmergeab=matcha,bwith|F_dead,F_dead->F_dead|_->F_branchtypeenv={cfg:cfg;break:node;continue:node;}letrecstmtenvsb=leta=of_stmtenv.cfgsinletf=skindenvabs.skindina.flow<-ifis_dead_codesthenF_deadelsef;aandskindenvab=function|Instri->flowi(gotoab)|Return(None,_)->F_goto|Return(Some_,_)->F_return|Goto(lbl,_)->gotoa(of_stmtenv.cfg!lbl)|Break_->gotoaenv.break|Continue_->gotoaenv.continue|If(_,bthen,belse,_)->letft=gotoa(blockenvbthenb)inletfe=gotoa(blockenvbelseb)inmergeftfe|Switch(_,body,cases,_)->ignore(block{envwithbreak=b}bodyb);List.fold_left(funfs->mergef(gotoa(of_stmtenv.cfgs)))F_deadcases|Loop(_,body,_,_,_)->letcontinue=node()inletlenv={envwithcontinue;break=b}inletflow=gotoa(blocklenvbodycontinue)inifflow=F_deadthenF_deadelseF_entry|Blockbody->gotoa(blockenvbodyb)|UnspecifiedSequences->letbody=Cil.block_from_unspecified_sequencesingotoa(blockenvbodyb)|Throw_|TryCatch_|TryFinally_|TryExcept_->Wp_parameters.not_yet_implemented"try-catch blocks"andblockenvblkb=sequenceenvblk.bstmtsbandsequenceenvseqb=matchseqwith|[]->b|s::seq->stmtenvs(sequenceenvseqb)(* -------------------------------------------------------------------------- *)(* --- Compute Reachability --- *)(* -------------------------------------------------------------------------- *)letrecunreachablenode=matchnode.unreachablewith|Somer->r|None->node.unreachable<-Sometrue;(* cut loops *)letr=matchnode.flowwith|F_dead->true|F_entry->false|F_goto|F_effect|F_return|F_branch|F_call->List.for_allunreachablenode.previnnode.unreachable<-Somer;rletrecprotectednode=matchnode.protectedwith|Somer->r|None->node.protected<-Somefalse;(* cut loops *)letr=matchnode.flowwith|F_dead|F_entry->true|F_goto|F_effect|F_return|F_branch|F_call->node.prev<>[]&&List.for_allprotected_bynode.previnnode.protected<-Somer;randprotected_byprev=matchprev.flowwith|F_return|F_dead|F_entry|F_effect->true|F_goto->protectedprev|F_call|F_branch->unreachableprevletsmoking_noden=matchn.flowwith|F_effect|F_call|F_return->not(protectedn)|F_goto|F_branch|F_entry|F_dead->false(* returns true if the stmt requires a reachability smoke test *)letsmokingreachabilitystmt=tryStmt.Map.findstmtreachability|>smoking_nodewithNot_found->trueletcomputekf=tryletf=Kernel_function.get_definitionkfinletcfg=refStmt.Map.emptyinletreturned=node()inletcontinue=node()inletbreak=node()inletentry=node()inletbody=block{cfg;break;continue}f.sbodyreturnedinlet_=gotoentrybodyinentry.flow<-F_entry;!cfgwithKernel_function.No_Definition->Stmt.Map.empty(* ---------------------------------------------------------------------- *)(* --- Dump for debugging --- *)(* ---------------------------------------------------------------------- *)moduleG=DotgraphmoduleNmap=Map.Make(structtypet=nodeletcompareab=a.id-b.idend)moduleN=Dotgraph.Node(Nmap)letdump~dirkfreached=letname=Kernel_function.get_namekfinletfile=Format.asprintf"%a/%s.dot"Datatype.Filepath.prettydirnameinletdot=G.open_dot~file~name()inN.definedot(funana->letattr=ifsmoking_nodeathen[`Filled;`Fillcolor"orange"]elseifprotectedathen[`Filled;`Fillcolor"green"]elseifunreachableathen[`Filled;`Fillcolor"red"]else[]inG.nodedotnaattr;List.iter(funb->letattr=matchb.flowwith|F_call|F_branch|F_return|F_dead->[`Dotted;`ArrowForward]|F_effect|F_entry|F_goto->[`ArrowForward]inG.edgedot(N.getb)naattr)a.prev);Stmt.Map.iter(funsn->letlabel=letmodulePu=Pretty_utilsinletmodulePr=Printerinmatchs.skindwith|Instr_|Return_|Break_|Continue_|Goto_->Pu.to_stringPr.pp_stmts|If(e,_,_,_)->Format.asprintf"@[<hov 2>if (%a)@]"Pr.pp_expe|Switch(e,_,_,_)->Format.asprintf"@[<hov 2>switch (%a)@]"Pr.pp_expe|Loop_->Printf.sprintf"Loop s%d"s.sid|Block_->Printf.sprintf"Block s%d"s.sid|UnspecifiedSequence_->Printf.sprintf"Seq. s%d"s.sid|Throw_|TryExcept_|TryCatch_|TryFinally_->Printf.sprintf"Exn. s%d"s.sidinG.nodedot(N.getn)[`Box;`Label(Printf.sprintf"s%d n%d: %s"s.sidn.idlabel)])reached;G.rundot;G.closedot;letout=G.layoutdotinWp_parameters.result"Reached Graph: %s"out(* ---------------------------------------------------------------------- *)(* --- Projectified Analysis Result --- *)(* ---------------------------------------------------------------------- *)moduleFRmap=Kernel_function.Make_Table(Datatype.Make(structtypet=reachabilityincludeDatatype.Serializable_undefinedletreprs=[Stmt.Map.empty]letname="WpReachable.reached"end))(structletname="WpReachable.compute"letdependencies=[Ast.self]letsize=17end)letdkey=Wp_parameters.register_category"reached"letreachability=FRmap.memobeginfunkf->letr=computekfin(ifWp_parameters.has_dkeydkeythenletdir=Wp_parameters.get_session_dir~force:true"reach"indump~dirkfr);rend(* -------------------------------------------------------------------------- *)(* --- Doome Status --- *)(* -------------------------------------------------------------------------- *)moduleInvalid_behaviors=structmoduleString_set=Datatype.String.SetincludeState_builder.Hashtbl(Kernel_function.Hashtbl)(String_set)(structletname="Wp.WpReached.Invalid_behavior"letdependencies=[Ast.self]letsize=32end)letaddkfbhv=letset=tryfindkfwithNot_found->String_set.emptyinaddkf(String_set.addbhv.b_nameset)letmemkfbhv=tryString_set.membhv.b_name(findkf)withNot_found->falseendletset_invalidemittertgt=letopenProperty_statusinmatchtgtwith(* For invalid assumes, introduce "ensures false" in behavior on need *)|Property.IPPredicate{ip_kind=PKAssumes(bhv);ip_kf;ip_pred}->ifnot(Invalid_behaviors.memip_kfbhv)thenbeginInvalid_behaviors.addip_kfbhv;letpred_name=["Wp";"SmokeTest"]inletpred_loc=ip_pred.ip_content.tp_statement.pred_locinletp={Logic_const.pfalsewithpred_loc;pred_name}inletp=Logic_const.(new_predicatep)inletpid=Property.ip_of_ensuresip_kfKglobalbhv(Normal,p)inAnnotations.add_ensuresemitterip_kf~behavior:bhv.b_name[Normal,p];emitemitter~hyps:[]pidFalse_if_reachableend|p->emitemitter~hyps:[]pFalse_if_reachableletset_doomedemitterpid=List.iter(set_invalidemitter)(WpPropId.doomed_if_validpid);matchWpPropId.unreachable_if_validpidwith|Property.OLStmt(kf,stmt)->letca=matchAnnotations.code_annot~emitter~filter:is_dead_annotstmtwith|ca::_->ca|[]->letpred_loc=Stmt.locstmtinletpred_name=["Wp";"SmokeTest"]inletpf={Logic_const.pfalsewithpred_loc;pred_name}inletpf=Logic_const.toplevel_predicatepfinletca=Logic_const.new_code_annotation(AAssert([],pf))inAnnotations.add_code_annotemitter~kfstmtca;cainList.iter(set_invalidemitter)(Property.ip_of_code_annotkfstmtca)|Property.OLGlob_|Property.OLContract_->()(* -------------------------------------------------------------------------- *)(* --- Status of Unreachable Annotations --- *)(* -------------------------------------------------------------------------- *)letdkey=Wp_parameters.register_category"reach"(* debugging key *)letdebugfmt=Wp_parameters.debug~dkeyfmtletunreachable_proved=ref0letunreachable_failed=ref0letwp_unreachable=Emitter.create"Unreachable Annotations"[Emitter.Property_status]~correctness:[](* TBC *)~tuning:[](* TBC *)letset_unreachablepid=ifWpPropId.is_smoke_testpidthenbeginletsource=WpPropId.source_of_idpidinset_doomedwp_unreachablepid;incrunreachable_failed;Wp_parameters.warning~source"Failed smoke-test"endelseletopenPropertyinletemit=function|IPPredicate{ip_kind=PKAssumes_}->()|p->debug"unreachable annotation %a@."Property.prettyp;Property_status.emitwp_unreachable~hyps:[]pProperty_status.Trueinletpids=matchWpPropId.property_of_idpidwith|IPPredicate{ip_kind=PKAssumes_}->[]|IPBehavior{ib_kf;ib_kinstr;ib_active;ib_bhv}->letactive=Datatype.String.Set.elementsib_activein(ip_post_cond_of_behaviorib_kfib_kinstr~activeib_bhv)@(ip_requires_of_behaviorib_kfib_kinstrib_bhv)|IPExtended_->[](* Extended clauses might concern anything. Don't validate them
unless we know exactly what is going on. *)|p->incrunreachable_proved;ifWp_parameters.has_dkeyVCS.dkey_shellthenWp_parameters.feedback"[Valid] Goal %a (Cfg) (Unreachable)"WpPropId.pp_propidpid;[p]inList.iteremitpids(* -------------------------------------------------------------------------- *)