123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403(**************************************************************************)(* *)(* This file is part of the Frama-C's MetACSL plug-in. *)(* *)(* Copyright (C) 2018-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) *)(* *)(**************************************************************************)openCil_typesopenMeta_utilsopenMeta_optionsopenMeta_parse(** Offset types handled on variables *)typesupported_offset=NoOff|Fieldof(fieldinfo*supported_offset)letemitter=Emitter.create"Deduction engine"~correctness:[]~tuning:[][Emitter.Code_annot](** ==== PRINTERS FOR THE GENERATION OF PROLOG MODELS ==== *)(** Print variables as their name and unique id *)letpp_vifmtvi=Format.fprintffmt"%s_%d"(String.lowercase_asciivi.vname)vi.vid(** Handle offset when printing variables *)letrecpp_offfmtbase=function|NoOff->basefmt()|Field(fi,next)->letbase'fmt()=Format.fprintffmt"field(%a, %a)"base()Printer.pp_fieldfiinpp_offfmtbase'next(** Print variables with their offset handled *)letpp_vi_offfmt(vi,off)=pp_offfmt(funfmt()->pp_vifmtvi)off(** Print the name of a function *)letpp_tafmttarget=(* Avoid having targets beginning with a _ *)Format.fprintffmt"f_%s"(String.lowercase_asciitarget)(** List global variables of a program *)letget_globals()=letr=ref[]inGlobals.Vars.iter(funvi_->r:=vi::!r);!r(** Iter on f called on the name of a function, and returns the set *)letcallgraph_set_off?(orig=refStrSet.empty)funame=letkf=Globals.Functions.find_by_namefunameinf(funckf->letn=Kernel_function.get_nameckfinorig:=StrSet.addn!orig)kf;origletset_of_callees=callgraph_set_ofCallgraph.Uses.iter_on_calleesletset_of_callers=callgraph_set_ofCallgraph.Uses.iter_on_callers(** Get name of function *)letget_vi_namefuncacc=tryletkf=Globals.Functions.find_by_namefuncinletvi=Kernel_function.get_vikfinStrSet.addvi.vnameaccwithNot_found->Self.warning~wkey:Meta_options.unknown_func_wkey"%s is not a valid C function, ignoring it during target computation"func;acc(** Expand set formula defining the targets of an HILARE.
In particular, resolve \ALL, \callers, \callees, \in_file
and perform set operations
*)letreccompute_target=function|TgAll->Globals.Functions.fold(funkfacc->((Kernel_function.get_vikf).vname)::acc)[]|>StrSet.of_list|TgSets->StrSet.foldget_vi_namesStrSet.empty|TgIntersl->sl|>List.mapcompute_target|>List.fold_leftStrSet.interStrSet.empty|TgUnionsl->sl|>List.mapcompute_target|>List.fold_leftStrSet.unionStrSet.empty|TgDiff(s1,s2)->StrSet.diff(compute_targets1)(compute_targets2)|TgCalleess->letopenCallgraphinlett=compute_targetsinifnot(Services.is_computed())thenServices.compute();letinit=reftin!(StrSet.fold(funnr->set_of_callees~orig:rn)tinit)|TgCallerss->letopenCallgraphinlett=compute_targetsinifnot(Services.is_computed())thenServices.compute();letinit=reftin!(StrSet.fold(funnr->set_of_callers~orig:rn)tinit)|TgFilefile->Globals.Functions.fold(funkfacc->iffile=(Filepath.to_string(fst(Kernel_function.get_locationkf)).pos_path)then(Kernel_function.get_vikf).vname::accelseacc)[]|>StrSet.of_list(** Given a predicate, make the list of its C free variables *)letcompute_footprintpredglobals=Cil_datatype.Logic_var.Set.fold(funlvl->matchlv.lv_originwith|Somevi->ifList.exists(funx->x.vid=vi.vid)globalsthen(vi::l)elsel|None->l)(Cil.extract_free_logicvars_from_predicatepred)[](** Pretty-print the edges of the call graph as Prolog facts *)letgenerate_callgraphfmttargets=List.iter(funt->letkf=Globals.Functions.find_by_nametinList.iter(fun(caller,_)->letcaller_name=Kernel_function.get_namecallerinletcallee_name=Kernel_function.get_namekfinFormat.fprintffmt"calls(%a, %a).@."pp_tacaller_namepp_tacallee_name;)(Kernel_function.find_syntactic_callsiteskf))targets(** Generic printer for lists *)letprint_setlistpp=letopenFormatinletpp_sepfmt()=pp_print_stringfmt", "inpp_print_list~pp_seppp(** Generic printer for sets of strings *)letprint_setppfmts=StrSet.fold(funxl->x::l)s[]|>print_setlistppfmt(** In order to get the predicate of an HILARE, we must type it.
To type it, we must instanciate each of its meta-variables.
Normally, it should be replaced by actual C locations.
For the deduction, since we don't have any local term to plug in there, we
use a dummy generic term that can be easily recognized after typing.
If for any reason the subsequent HILARE does not type, then the user made
wrong assumptions on its predicate and should have guarded the predicate,
hence the error message.
*)letdummy_termloc={term_node=Tnull;term_loc=loc;term_type=CtypeCil_const.charPtrType;term_name=["YOUR META-PROPERTY SHOULD BE GUARDED WITH \\fguard OR \\tguard"]}(** Map every possible meta-variable to the dummy term and type the HILARE *)letdummy_unpackmp=letterms=["\\written";"\\read";"\\called"]inletassoc=List.map(funt->(t,RepVariable(dummy_termmp.mp_loc)))termsinmp.mp_property(Kernel_function.dummy())assoc(** Main pattern matcher, trying to identify a pattern of predicate in order to
translate id in Prolog.
We check each *pred* pattern passed as a parameter and match against the
HILARE.
*)letpredicate_counter=ref1letidentify_predmppredsglobals=letunpacked=dummy_unpackmpinletrecfind=function|[]->None|(p,print)::_whenLogic_utils.is_same_predicatepunpacked->Someprint|_::t->findtinmatchfindpredswith|Someprint->print,preds(* We found a match *)|None->(* No match, just compute the footprint and output a generic property *)letpname="p"^(string_of_int!predicate_counter)inincrpredicate_counter;letfp=compute_footprintunpackedglobalsinletprintfmt()=Format.fprintffmt"property({%a},%s)"(print_setlistpp_vi)fppnameinprint,(unpacked,print)::preds(** From a given lval, prepare translation to Prolog when supported *)letget_global_variableglobalslval=letwith_offseto=Option.map(funa->a,o)inmatchlvalwith|TVar({lv_origin=Some({vid})}),off->letv=List.find_opt(funx->x.vid=vid)globalsinletrecget_offset=function|TNoOffset->NoOff|TField(fi,next)->Field(fi,get_offsetnext)|_->Self.warning"Deduction patterns do not support offsets other than fields";NoOffinwith_offset(get_offsetoff)v|_->None(** Match predicates of the (exact) form \separated(\written, &X) *)letis_not_written_predicateglobalsmp=letunpacked=dummy_unpackmpinletdt=dummy_termmp.mp_locinbeginmatchunpacked.pred_contentwith|Pseparated[d;l]whenLogic_utils.is_same_termddt->beginmatchl.term_nodewith|TAddrOftlval->get_global_variableglobalstlval|_->Noneend|_->Noneend(** Match predicates of the form X == \old(X) *)letis_negative_assigns_predicateglobalsmp=letunpacked=dummy_unpackmpinletget_global_tlvalt=matchtwith|TLvaltlval->get_global_variableglobalstlval|_->Noneinmatchunpacked.pred_contentwith|Prel(Req,{term_node=t1},{term_node=t2})->letl1=get_global_tlvalt1inletl2=matcht2with|Tat({term_node=t2'},BuiltinLabelOld)->get_global_tlvalt2'|_->Noneinbeginmatchl1,l2with|Some(a,_),Some(b,_)whena.vid=b.vid->l1|_->Noneend|_->None(** Export a predicate, trying to match against known patterns *)letpp_propertypredsglobalsmp=letdefault()=letprint,preds=identify_predmppredsglobalsinprint,predsinbeginmatchmp.mp_contextwith|Postcond->beginmatchis_negative_assigns_predicateglobalsmpwith|Somevi_off->(funfmt()->Format.fprintffmt"negative_assigns(%a)"pp_vi_offvi_off),preds|None->default()end|Writing->beginmatchis_not_written_predicateglobalsmpwith|Somevi_off->(funfmt()->Format.fprintffmt"not_written(%a)"pp_vi_offvi_off),preds|None->default()end|_->default()end(** Export a whole HILARE: context (as a string), predicate, target set *)letgenerate_mpprefixpredsglobalsfmt(mp,tset)=letprint,preds=pp_propertypredsglobalsmpinFormat.fprintffmt"%% Export of HILARE %s@.meta_%s(\"%s\", %a, {%a}).@."mp.mp_nameprefix(matchmp.mp_contextwith|Weak_invariant->"Weak invariant"|Strong_invariant->"Strong invariant"|Conditional_invariant->"Conditional invariant"|Postcond->"Postcond"|Precond->"Precond"|Writing->"Writing"|Reading->"Reading"|Calling->"Calling")print()(print_setpp_ta)tset;preds(** Export all previous HILARE *)letall_preds=refNoneletgenerate_mpsfmt(mps,globals,tsets)=letrecauxpreds=function|[]->all_preds:=Somepreds|mp::t->letpreds=generate_mp"ground"predsglobalsfmt(mp,(List.assocmp.mp_nametsets))inauxpredstinaux[]mps(** Generic function to run a system command with a timeout and retries *)letrecrun_with_timeout_retry?(timeout=30)?(max_attempts=8)command=ifmax_attempts=0thenNoneelselettcommand=Format.asprintf"timeout %d %s"timeoutcommandinletresult=Sys.commandtcommandinifresult=124thenletmax_attempts=max_attempts-1inrun_with_timeout_retry~timeout~max_attemptscommandelseSomeresult(** Main deduction function *)letdeduceflagsmpipmps=(* Retrieve the preceding HILARE in the correct order *)letrectakeFirstMpsacc=function|h::_whenh.mp_name=mp.mp_name->List.revacc|h::t->takeFirstMps(h::acc)t|[]->assertfalseinletall_mps=takeFirstMps[]mpsinpredicate_counter:=1;letglobals=get_globals()inletmp_targets=List.map(funm->(m.mp_name,compute_targetm.mp_target))all_mpsin(* Prepare to print *)letbuffer=Buffer.create5000inletfmt=Format.formatter_of_bufferbufferinlettargets=Globals.Functions.fold(funkfacc->((Kernel_function.get_vikf).vname)::acc)[]inletprint_goalfmt()=match!all_predswith|Somep->ignore(generate_mp"valid"pglobalsfmt(mp,compute_targetmp.mp_target))|None->failwith"Oh no"inFormat.fprintffmt{|
%% This file is automatically generated by MetAcsl.
%% It is provided as an input to run.pl in order to attempt HILARE deduction.
%% == Export of the list of functions ==
targets({%a}).
%% == Export of the call graph's edges ==
%a
%% == Export already established HILAREs ==
%a
%% This is the HILARE we want to prove
go :- %a
|}(print_setlistpp_ta)targetsgenerate_callgraphtargetsgenerate_mps(all_mps,globals,mp_targets)print_goal();(* Stop printing and flush everything into a temporary file *)Format.pp_print_flushfmt();letcurrent_dir=Sys.getcwd()inletfilename=Format.asprintf"%s/%s_goal.slog"current_dirmp.mp_nameinletoc=open_outfilenameinoutput_stringoc(Buffer.contentsbuffer);close_outoc;(* Locate where the Prolog model is and go to it *)letdir=tryFindlib.package_directory"frama-c-metacsl"withFindlib.No_such_package(_,err)->Self.fatal"Could not locate my own directory: %s"errinSys.chdir(dir^"/deduce");(* Run the Prolog model on our file, with a 30s timeout *)letcommand=Format.asprintf"./run.pl prove %s 30 > /dev/null"filenameinletmax_attempts=8inletresult=matchrun_with_timeout_retry~max_attemptscommandwith|None->(* This happens if Prolog get stuck and is not interruptible, for an obuscure reaons *)Self.warning"%s : PROLOG FAILURE, (%d ATTEMPTS)"mp.mp_namemax_attempts;Property_status.emitemitter~hyps:[]ipProperty_status.False_and_reachable;false|Some0->(* Property could be deduced, propagate status *)Self.feedback"%s : OK"mp.mp_name;Property_status.emitemitter~hyps:[]ipProperty_status.True;true|Some_->(* Property could not be deduced, propagate FALSE to be safe *)Self.warning"%s : FAILURE"mp.mp_name;Property_status.emitemitter~hyps:[]ipProperty_status.False_and_reachable;falseinSys.chdircurrent_dir;ifnotflags.keep_proof_filesthenSys.removefilename;result