123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512(**************************************************************************)(* *)(* 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) *)(* *)(**************************************************************************)openLogic_ptreeopenLogic_typingopenCil_typesopenMeta_optionsopenMeta_utils(* Define builtins *)typecontext=|Weak_invariant|Strong_invariant|Calling|Writing|Reading|Postcond|Precond|Conditional_invarianttypetarget=|TgAll|TgSetofStrSet.t|TgUnionoftargetlist|TgInteroftargetlist|TgDiffoftarget*target|TgCalleesoftarget|TgCallersoftarget|TgFileofstring(* How is the global status of the property obtained *)typemp_proof_method=|MmLocal|MmDeduction|MmAxiom(* How should a property be translated locally *)typemp_translation=|MtCheck|MtAssert|MtDefault(* translation activated, with unspecified mode *)|MtNone(* Kinds of keywords that can be replaced in a MP instantiation *)typereplaced_kind=|RepVariableofterm(* replace variable by term *)|RepAppof(string*term)list(* replace application with arg by associated term *)typemetaproperty={mp_name:string;mp_target:target;mp_context:context;mp_property:Kernel_function.t->(string*replaced_kind)list->predicate;mp_proof_method:mp_proof_method;mp_translation:mp_translation;mp_loc:Cil_types.location}letmp_commands=["\\prop"]letmp_contexts=["\\weak_invariant";"\\strong_invariant";"\\writing";"\\calling";"\\reading";"\\postcond";"\\precond";"\\conditional_invariant"]letmp_metavariables=["\\written";"\\lhost_written";"\\read";"\\lhost_read";"\\called";"\\called_arg";"\\func";]letmp_utils=["\\diff";"\\ALL"]letmp_preds=mp_commands@mp_contextsletmp_terms=mp_metavariables@mp_utils(* Gathered properties *)letgathered_props=ref[](* Generic typing for classic ACSL predicates, allowing the
* presence of custom predicates inside *)letmeta_type_predicatelocorig_ctxtmeta_ctxtenvexpr=matchexpr.lexpr_nodewith|PLapp(pname,_,args)whenList.mempnamemp_preds->letterms=List.map(meta_ctxt.type_termmeta_ctxtenv)argsinletlogic_info=List.hd(Logic_env.find_all_logic_functionspname)inLogic_const.papp(logic_info,[],terms)|PLapp("\\fguard",_,[dangerous])->begintrymeta_ctxt.type_predicatemeta_ctxtenvdangerouswith_->Logic_const.pfalseend|PLapp("\\tguard",_,[dangerous])->begintrymeta_ctxt.type_predicatemeta_ctxtenvdangerouswith_->Logic_const.ptrueend|PLapp("\\assert_type",_,[{lexpr_node=PLcast(ltyp,lexpr)}])->lettyped_term=meta_ctxt.type_termmeta_ctxtenvlexprinletdesired_type=meta_ctxt.logic_typemeta_ctxtlocenvltypinletreal_type=typed_term.term_typeinifCil_datatype.Logic_type_NoUnroll.equalreal_typedesired_typethenLogic_const.ptrueelsemeta_ctxt.errorloc"%a has type %a instead of %a"Printer.pp_termtyped_termPrinter.pp_logic_typereal_typePrinter.pp_logic_typedesired_type|_->orig_ctxt.type_predicatemeta_ctxtenvexpr(* Generic typing for classic ACSL terms, allowing the
* presence of custom term placeholders (\writing, ...). These will be
* replaced by an actual term as defined by the termassoc association table
*)letmeta_type_termtermassocquantifierskflocorig_ctxtmeta_ctxtenvexpr=matchexpr.lexpr_nodewith|PLat(e,l)whenList.meml["After";"Before"]->lete_t=meta_ctxt.type_termmeta_ctxtenveinletlabel=FormalLabellinLogic_const.tat(e_t,label)|PLapp("\\formal",_,[{lexpr_node=PLvarparam}])->(matchGlobals.Syntactic_search.find_in_scopeparam(Formalkf)with|Somevi->vi|>Cil.cvar_to_lvar|>Logic_const.tvar|None->meta_ctxt.errorloc"%s is not a formal in %a"paramKernel_function.prettykf)|PLapp("\\bound",_,[bound])->Meta_bindings.parse_boundmeta_ctxtlocquantifiersbound|PLapp(app_name,_,[{lexpr_node=PLvararg}])whenList.memapp_namemp_terms->beginmatchList.assoc_optapp_nametermassocwith|SomeRepAppl->beginmatchList.assoc_optarglwith|Someterm->term|None->meta_ctxt.errorloc"%s is not a valid argument for %s here"argapp_nameend|None->meta_ctxt.errorloc"Application of %s forbidden in this context"app_name|_->meta_ctxt.errorloc"%s expects no arguments but has been provided with one"app_nameend|PLvarvnamewhenList.memvnamemp_terms->beginmatchList.assoc_optvnametermassocwith|SomeRepVariablea->a|None->meta_ctxt.errorloc"Variable %s forbidden in this context"vname|_->meta_ctxt.errorloc"%s expects one argument but has been provided with none"vnameend|_->orig_ctxt.type_termmeta_ctxtenvexpr(* Given an association table, combine the previous functions to type
* a full custom predicate
*)letprocess_meta_termassoctyping_contextlockftermassocexpr=letquantifiers=Str_Hashtbl.create5inletmeta_tc={typing_contextwithtype_predicate=(meta_type_predicateloctyping_context);type_term=(meta_type_termtermassocquantifierskfloctyping_context)}inletfenv=Logic_typing.append_here_labelmeta_tc.pre_stateinletfenv2=Logic_typing.append_pre_labelfenvinletfenv3=Logic_typing.append_old_and_post_labelsfenv2inletpred=meta_tc.type_predicatemeta_tcfenv3exprinMeta_bindings.after_parsemeta_tcpredquantifiers(* Check that a given lexpr refer to a C function and return its varinfo *)letprocess_single_targettcexpr=matchexpr.lexpr_nodewith|PLvarfunc->func|_->tc.errorexpr.lexpr_loc"Target is not a variable"letprocess_stringtcfe=matche.lexpr_nodewith|PLconstant(StringConstantn)->n|PLvarn->n|_->tc.errore.lexpr_locf(* Process a set (+\diff) expression to a custom target type
* (because the computation of \ALL must be delegated)
*)letrecprocess_targetstcexpr=matchexpr.lexpr_nodewith|PLempty->TgSetStrSet.empty|PLvar"\\ALL"->TgAll|PLsetelems->lets=elems|>List.map(process_single_targettc)|>StrSet.of_listinTgSets|PLunionl->TgUnion(List.map(process_targetstc)l)|PLinterl->TgInter(List.map(process_targetstc)l)|PLapp("\\diff",_,[s1;s2])->TgDiff(process_targetstcs1,process_targetstcs2)|PLapp("\\callees",_,[t])->TgCallees(process_targetstct)|PLapp("\\callers",_,[t])->TgCallers(process_targetstct)|PLapp("\\in_file",_,[t])->TgFile(process_stringtc"Expected filename"t)(* Try to treat non-set expr as a singleton *)|_->TgSet(StrSet.singleton@@process_single_targettcexpr)letpp_aslistfmtl=letpp_replaced_kindfmt=function|RepVariablev->Printer.pp_termfmtv|RepAppl->letpp_singularfmt(a,b)=Format.fprintffmt"(%s, %a)"aPrinter.pp_termbinFormat.pp_print_listpp_singularfmtlinletpp_assocfmt(a,b)=Format.fprintffmt"(%s, %a)"app_replaced_kindbinFormat.pp_print_listpp_assocfmtl(* Display a typing error, trying to parse the uncatchable
Cabs2cil exception to avoid clutter *)lettyping_errorhlocmp_namekf_namemetavars(loc,error_msg)=letmetav_msg=ifmetavars=[]then"there were no meta-variables."elseFormat.asprintf"meta-variables were: %a"pp_aslistmetavarsinSelf.abort"Error during the typing of a HILARE!\n\
What: the HILARE named '%s' (%a)\n\
Where: in function '%s', at %a\n\
Why: %s\n\
Environment: %s"mp_nameCil_datatype.Location.prettyhlockf_nameCil_datatype.Location.prettylocerror_msgmetav_msgletdelay_proptcnamelexprkfaslist=letdfind_var?label:_var=(* Tweak find_var so it can find things after link *)trytc.find_varvarwithNot_found->tryletvi=Globals.Vars.find_from_astinfovarGlobalinCil.cvar_to_lvarviwithNot_found->letkf=Globals.Functions.find_by_namevarinletvi=Kernel_function.get_vikfinCil.cvar_to_lvarviinletdfets=trytc.find_enum_tagswith_->Globals.Types.find_enum_tagsinletdftab=Globals.Types.find_typeabinletdelayed_tc={tcwithfind_var=dfind_var;find_enum_tag=dfet;find_type=dft}inletloc=lexpr.lexpr_locindelayed_tc.on_error(process_meta_termassocdelayed_tclockfaslist)(typing_errorlocname(Kernel_function.get_namekf)aslist)lexprletrecmacro_replacerchangerlexpr=letf=macro_replacerchangerinletlexpr_node=matchchangerlexpr.lexpr_nodewith|PLapp(a,b,c)->PLapp(a,b,List.mapfc)|PLlambda(a,b)->PLlambda(a,fb)|PLlet(a,b,c)->PLlet(a,fb,fc)|PLunop(a,b)->PLunop(a,fb)|PLbinop(a,b,c)->PLbinop(fa,b,fc)|PLdot(a,b)->PLdot(fa,b)|PLarrow(a,b)->PLarrow(fa,b)|PLarrget(a,b)->PLarrget(fa,fb)|PLolda->PLold(fa)|PLat(a,b)->PLat(fa,b)|PLcast(a,b)->PLcast(a,fb)|PLrange(a,b)->PLrange(Option.mapfa,Option.mapfb)|PLsizeofEa->PLsizeofE(fa)|PLupdate(a,b,c)->PLupdate(fa,b,c)|PLtypeofa->PLtypeof(fa)|PLrel(a,b,c)->PLrel(fa,b,fc)|PLand(a,b)->PLand(fa,fb)|PLor(a,b)->PLor(fa,fb)|PLxor(a,b)->PLxor(fa,fb)|PLimplies(a,b)->PLimplies(fa,fb)|PLiff(a,b)->PLiff(fa,fb)|PLnota->PLnot(fa)|PLif(a,b,c)->PLif(fa,fb,fc)|PLforall(a,b)->PLforall(a,fb)|PLexists(a,b)->PLexists(a,fb)|PLbase_addr(a,b)->PLbase_addr(a,fb)|PLoffset(a,b)->PLoffset(a,fb)|PLblock_length(a,b)->PLblock_length(a,fb)|PLvalid(a,b)->PLvalid(a,fb)|PLvalid_read(a,b)->PLvalid_read(a,fb)|PLvalid_functiona->PLvalid_function(fa)|PLallocable(a,b)->PLallocable(a,fb)|PLfreeable(a,b)->PLfreeable(a,fb)|PLinitialized(a,b)->PLinitialized(a,fb)|PLdangling(a,b)->PLdangling(a,fb)|PLfresh(a,b,c)->PLfresh(a,fb,fc)|PLseparateda->PLseparated(List.mapfa)|PLnamed(a,b)->PLnamed(a,fb)|PLcomprehension(a,b,c)->PLcomprehension(fa,b,Option.mapfc)|PLseta->PLset(List.mapfa)|PLuniona->PLunion(List.mapfa)|PLintera->PLset(List.mapfa)|PLlista->PLset(List.mapfa)|PLrepeat(a,b)->PLrepeat(fa,fb)|l->lin{lexprwithlexpr_node}letmacro_table=Hashtbl.create2letprocess_macrotcloc=function|[{lexpr_node=PLapp("\\name",_,[ename])};{lexpr_node=PLapp("\\arg_nb",_,[eargnb])};etemplate]->letname=matchename.lexpr_nodewith|PLvarn->n|_->tc.errorename.lexpr_loc"Name must be a single word"inletarg_nb=matcheargnb.lexpr_nodewith|PLconstant(IntConstanti)->int_of_stringi|_->tc.erroreargnb.lexpr_loc"arg_nb must be a positive integer"inHashtbl.addmacro_tablename(arg_nb,etemplate);Ext_terms[Logic_const.tstring~loc("macro_"^name)]|_->tc.errorloc"Invalid macro shape"letexpand_macrostclocprop=letprefix="\\param_"inletmacro_filter=function|PLapp(name,_,params)asa->ifHashtbl.memmacro_tablenamethenletargnb,template=Hashtbl.findmacro_tablenameinifList.lengthparams=argnbthenletextract_paramp=letopenOption.Operatorsinlet*p'=Extlib.string_del_prefixprefixpinlet*n=int_of_string_optp'inifn<=argnb&&n>0thenSome(n-1)elseNoneinletparam_replacer=function|(PLvarv)ase->beginmatchextract_paramvwith|Somen->(List.nthparamsn).lexpr_node|None->eend|(PLlet(s,b,r))ase->beginmatchextract_paramswith|Somen->beginmatch(List.nthparamsn).lexpr_nodewith|PLvarna->PLlet(na,b,r)|_->tc.errorloc"Param replacement in \\let should be a simple var"end|None->eend|e->ein(macro_replacerparam_replacertemplate).lexpr_nodeelsetc.errorloc"%s takes %d args but %d were given"nameargnb(List.lengthparams)elsea|e->einmacro_replacermacro_filterpropletprocess_flagstcloc=function|{lexpr_node=PLapp("\\flags",_,l)}->List.map(funx->matchx.lexpr_nodewith|PLnamed("proof",{lexpr_node=PLvarv})->beginmatchvwith|"axiom"->`Axiom|"deduce"->`Deduction|"local"->`Local|_->tc.errorloc"Invalid value %s for flag proof"vend|PLnamed("translate",{lexpr_node=PLvarv})->beginmatchvwith|"true"|"yes"->`Translate|"false"|"no"->`NoTranslate|"check"->`ForceCheck|"assert"->`ForceAssert|_->tc.errorloc"Invalid value %s for flag translate"vend|PLnamed(k,{lexpr_node=PLvar_})->tc.errorloc"Unknown flag %s"k|_->tc.errorloc"A flag should has the shape key:value")l|_->tc.errorloc"Flags should be absent or listed with \\flags"letget_statustclocoptions=(* By default, translate locally with asserts *)letpm=refMmLocalinlettr=refMtDefaultinList.iter(function|`Axiom->pm:=MmAxiom|`Local->pm:=MmLocal|`Deduction->pm:=MmDeduction|`ForceAssert->tr:=MtAssert|`ForceCheck->tr:=MtCheck|`Translate->tr:=MtDefault|`NoTranslate->tr:=MtNone)options;if!pm=MmLocal&&!tr=MtNonethentc.errorloc"When proof method is local, translation must be enabled";(!pm,!tr)(* Take the params of \prop as lexprs, type it and fill the table.
* The last parameter is not typed, as we don't know yet how the placeholders
* will be replaced (thus the whole property is untypable yet. Instead its
* typing is wrapped into a function taking the correct association table, to
* delegate the task until instanciation
*)letprocess_propertytcloc=function|{lexpr_node=PLapp("\\name",_,[ename])}::{lexpr_node=PLapp("\\targets",_,[etargets])}::{lexpr_node=PLapp("\\context",_,[econtext])}::tail->letmp_name=process_stringtc"Prop name must be a string"enameinletmp_target=process_targetstcetargetsinletmp_context=matchecontext.lexpr_nodewith|PLvar"\\weak_invariant"->Weak_invariant|PLvar"\\strong_invariant"->Strong_invariant|PLvar"\\conditional_invariant"->Conditional_invariant|PLvar"\\writing"->Writing|PLvar"\\reading"->Reading|PLvar"\\calling"->Calling|PLvar"\\precond"->Precond|PLvar"\\postcond"->Postcond|_->tc.errorecontext.lexpr_loc"Invalid context %a"Logic_print.print_lexprecontextinleteproperty,options=matchtailwith|[]->tc.errorloc"Missing actual property"|[x]->x,[]|[o;x]->x,process_flagstcloco|_->tc.errorloc"Too many trailing arguments in MP"inletmp_proof_method,mp_translation=get_statustclocoptionsin(* Execute macros in property *)letexpanded=expand_macrostclocepropertyin(* Delegate typing of property *)letmp_property=delay_proptcmp_nameexpandedingathered_props:={mp_name;mp_target;mp_context;mp_proof_method;mp_translation;mp_property;mp_loc=loc}::!gathered_props;Ext_terms[Logic_const.tstring~locmp_name]|_->tc.errorloc"Invalid property shape"(* Process each command and fill the table *)letprocess_metatclocl=beginmatchlwith|command::t->beginmatchcommand.lexpr_nodewith|PLvar"\\prop"->process_propertytcloct|PLvar"\\macro"->process_macrotcloct(* Already processed file *)|PLconstantStringConstantstr->Ext_terms[Logic_const.tstring~locstr]|_->tc.errorloc"Invalid command"end|_->tc.errorloc"Missing command"endletprocess_inlinetcloc=function|[{lexpr_node=PLvar"lenient"}]->Ext_terms[Logic_const.tstring"lenient"]|[{lexpr_node=PLapp("\\bind",_,[{lexpr_node=PLvarcvar_name};{lexpr_node=PLvargvar_name}])}]->Meta_bindings.parse_bindtcloccvar_namegvar_name|_->Self.warning"%a: invalid inline annotation"Printer.pp_locationloc;Ext_id(-1)letregister_parsing()=(* Add builtins, as predicates and terms *)letbl_from_namepredicatebl_name={bl_name;bl_labels=[];bl_params=[];bl_profile=[];bl_type=ifpredicatethenNoneelseSome(Lvar"dummy")}inmp_preds|>List.map(bl_from_nametrue)|>List.iterLogic_builtin.register;mp_terms|>List.map(bl_from_namefalse)|>List.iterLogic_builtin.register;(* Register parser for meta global statements *)Acsl_extension.register_global~plugin:"meta""meta"process_metatrue;Acsl_extension.register_code_annot_next_stmt~plugin:"meta""imeta"process_inline~visitor:Meta_bindings.process_imetafalse(* !! Expects a list of props sorted by name !! *)letreccheck_for_duplicates=function|[]->None|[_]->None|h1::h2::_whenh1.mp_name=h2.mp_name->Some(h1,h2)|_::t->check_for_duplicatestletmetaproperties()=(* Check that there are no duplicate MPs *)letsorted_props=List.sort(funab->comparea.mp_nameb.mp_name)!gathered_propsinbeginmatchcheck_for_duplicatessorted_propswith|None->()|Some(m1,m2)->Self.abort"The meta-property named %s is defined at \
least twice :@,Here: %a@,and here: %a"m1.mp_namePrinter.pp_locationm1.mp_locPrinter.pp_locationm2.mp_locend;List.rev!gathered_props