123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983(**************************************************************************)(* *)(* This file is part of Frama-C. *)(* *)(* Copyright (C) 2007-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 licenses/LGPLv2.1). *)(* *)(**************************************************************************)openEva_astopenMt_libopenMt_cilopenMt_memory.TypesopenMt_typesopenMt_shared_vars_typesopenMt_threadletwrap_resres=Some(Mt_memory.int_to_valueres)letno_res=(None:valueoption)typehook_sig=(exp*value)list->state*valueoptionletcurrent_locanalysis=matchCallstack.top_callsiteanalysis.curr_stackwith|Kglobal->assertfalse(* The current stack must contain the call to the builting creating the thread *)|Kstmtstmt->stmt,Option.get(Callstack.popanalysis.curr_stack)(* -------------------------------------------------------------------------- *)(* --- Specialized logging functions --- *)(* -------------------------------------------------------------------------- *)(* To be used only inside hooks, as it makes pretty bold assumptions on
the shape of the stack *)letlog_poly?(pop_stack=true)?(kind=Log.Result)analysis=letstack=analysis.curr_stackinletstack=ifnotpop_stack||Mt_options.PopTopFunctionForCallbacks.get()thenstackelseOption.value(Callstack.popstack)~default:stackinletki=Callstack.top_callsitestackinletsource=kinstr_to_sourcekiinletpp_callstack=Mt_options.PrintCallstacks.get()||Mt_options.debug_level()>1inletappend=(funfmt->ifpp_callstackthenFormat.fprintffmt"@.%a"Callstack.prettystack)in{ppp=funfmt->Mt_options.MThread.log~kind~once:true?source~append("@["^^fmt^^"@]")}letlog?(pop_stack=true)?(kind=Log.Result)analysis=(log_poly~pop_stack~kindanalysis).pppexceptionHook_failureofintletdefault_err_code=-255lethook_fail?(code=default_err_code)()=raise(Hook_failurecode)(* Auxiliary function that aborts a hook when a conversion of something
into a proper value fails *)letcatch_conversionanalysismsg_mainv?(pop_stack=true)?(code=default_err_code)?msg()=letwarnfmmsg_end=letmsg=matchmsgwith|Somemsg->":@ "^^msg|None->""inlog~kind:Log.Warninganalysis~pop_stack"@[%(%)%(%).@ %t%(%)@]"msg_mainmsgfmmsg_end;inmatchvwith|`Successv->v|`WithWarning(w,v)->warnw"";v|`Failurew->warnw"@ Ignoring.";hook_fail~code()(* -------------------------------------------------------------------------- *)(* --- Specialization of id function *)(* -------------------------------------------------------------------------- *)letfind_failurekindid=letppfmt=Format.fprintffmt"Id %d for %s does not exists@ (incrementation@ inside@ program?)."idkindin`Failureppletfind_threadid=matchThread.findidwith|Someth->`Successth|None->find_failure"thread"idletfind_mutexid=matchMutex.findidwith|Somem->`Successm|None->find_failure"mutex"idletfind_queueid=matchMqueue.findidwith|Someq->`Successq|None->find_failure"queue"id(* -------------------------------------------------------------------------- *)(* --- Constants written in memory to store states --- *)(* -------------------------------------------------------------------------- *)(* Currently not used, because we would need them inside pattern-matching *)let_s_thread_unknown=0let_s_thread_started=1let_s_thread_cancelled=2let_s_mutex_not_init=0let_s_mutex_init=1let_s_mutex_locked=2let_queue_not_init=0let_queue_init=1(* -------------------------------------------------------------------------- *)(* --- Basic, per-thread, checking --- *)(* -------------------------------------------------------------------------- *)(* This section is used to check the consistency of the information we store
into the ids of threads, mutexes, etc. *)(* Auxiliary function which extracts the information into the id and
call dispatch functions, or return errors when the information is
not of the proper form *)letcheck_id_contentdefault_msgmsg_intidstate=letpbppv=default_msg.pfppvinletvalue=Mt_ids.read_id_statestateidinmatchLocations.Location_Bytes.fold_i(funbil->(b,i)::l)value[]with|[Base.Null,i]->(tryletr=Abstract_interp.Int.to_int_exn(Ival.project_inti)intrymsg_intrwithNot_found->pbFormat.pp_print_intrwithIval.Not_Singleton_Int->pbIval.prettyi)|_->pbCvalue.V.prettyvalue(** When a thread is created, it must not inherit from its creator the status
of mutexes. This function sets all mutexes passed as argument to 1
(unlocked). *)letreset_mutexesmutexesstate=Mutex.Set.fold(funmutexstate->Mt_ids.replace_id_valuestate(Mt_ids.of_mutexmutex)~before:2~after:1)mutexesstatelet_mutex_statefmt=function|0->Format.fprintffmt"not initialized"|1->Format.fprintffmt"unlocked"|2->Format.fprintffmt"locked"|k->Format.fprintffmt"in an@ unknown@ state (%d)"klet_thread_statefmt=function|0->Format.fprintffmt"not created"|1->Format.fprintffmt"started"|2->Format.fprintffmt"suspended"|3->Format.fprintffmt"cancelled"|k->Format.fprintffmt"in an@ unknown@ state (%d)"kletcheck_thread_not_already_startedwarnthstate=check_id_content{pf=funppv->warn.ppp"Unable to determine that thread %a@ has not already been started.@ \
%a should be 0@."Thread.prettythppv;}(function|2->()|0->warn.ppp"Thread %a@ might not be created yet@."Thread.prettyth;|1->warn.ppp"Thread %a@ might have already been started@ by the \
current thread.@."Thread.prettyth;|3->warn.ppp"Thread %a@ might have been cancelled @ by the \
current thread.@."Thread.prettyth;|_->raiseNot_found)(Mt_ids.of_threadth)stateletcheck_thread_not_already_suspendedwarnthstate=check_id_content{pf=funppv->warn.ppp"Unable to determine that thread %a@ has not already been suspended.@ \
%a should be 0@."Thread.prettythppv;}(function|1->()|0->warn.ppp"Thread %a@ might not be created yet@."Thread.prettyth;|2->warn.ppp"Thread %a@ might have already been suspended@ by the \
current thread.@."Thread.prettyth;|3->warn.ppp"Thread %a@ might have been cancelled @ by the \
current thread.@."Thread.prettyth;|_->raiseNot_found)(Mt_ids.of_threadth)stateletcheck_thread_not_already_cancelledwarnthstate=check_id_content{pf=funppv->warn.ppp"Unable to determine that thread %a@ has not already been cancelled.@ \
%a should be 0@."Thread.prettythppv;}(function|1|2->()|0->warn.ppp"Thread %a@ might not be created yet@."Thread.prettyth;|3->warn.ppp"Thread %a@ might have been already cancelled @ by the \
current thread.@."Thread.prettyth;|_->raiseNot_found)(Mt_ids.of_threadth)stateletcheck_mutex_not_already_initializedwarnmstate=check_id_content{pf=funppv->warn.ppp"@[<hov>Unable to check that mutex %a@ has not been already \
initialized;@ %a should be 0@]@."Mutex.prettymppv}(function|0->()|1->warn.ppp"@[<hov>Mutex %a@ might be already initialized@]@."Mutex.prettym|2->warn.ppp"@[<hov>Mutex %a@ might be already initialized \
(and locked)@]@."Mutex.prettym|_->raiseNot_found)(Mt_ids.of_mutexm)stateletcheck_mutex_not_already_lockedwarnmstate=check_id_content{pf=funppv->warn.ppp"@[<hov>Unable to check that mutex %a@ has not already been locked;@ \
%a should be 1@]@."Mutex.prettymppv}(function|1->()|0->warn.ppp"@[<hov>Mutex %a@ might have not been initialized@]@."Mutex.prettym|2->warn.ppp"@[<hov>Mutex %a@ might have already been locked@]@."Mutex.prettym|_->raiseNot_found)(Mt_ids.of_mutexm)stateletcheck_mutex_lockedwarnmstate=check_id_content{pf=funppv->warn.ppp"@[<hov>Unable to check that mutex %a@ has already been locked;@ \
%a should be 2@]@."Mutex.prettymppv}(function|2->()|0->warn.ppp"@[<hov>Mutex %a@ might be uninitialized@]@."Mutex.prettym|1->warn.ppp"@[<hov>Mutex %a@ might not be locked@]@."Mutex.prettym|_->raiseNot_found)(Mt_ids.of_mutexm)stateletcheck_queue_not_already_initializedwarnqstate=check_id_content{pf=funppv->warn.ppp"@[<hov>Unable to check that queue %a@ has not been already \
initialized;@ %a should be 0@]@."Mqueue.prettyqppv}(function|0->()|1->warn.ppp"@[<hov>Queue %a@ might be@ already@ initialized@]@."Mqueue.prettyq|_->raiseNot_found)(Mt_ids.of_queueq)stateletcheck_queue_already_initializedwarnqstate=check_id_content{pf=funppv->warn.ppp"@[<hov>Unable to check that queue %a@ is@ already \
initialized;@ %a should be 0@]@."Mqueue.prettyqppv}(function|1->()|0->warn.ppp"@[<hov>Queue %a@ might be@ uninitialized@]@."Mqueue.prettyq|_->raiseNot_found)(Mt_ids.of_queueq)state(* -------------------------------------------------------------------------- *)(* --- External values for shared zones --- *)(* -------------------------------------------------------------------------- *)(* XXX: we should sync values only for the threads that may be alive at this
point *)letsync_valuesanalysisstate=letjoin~written~state=Cvalue.Model.fold(funboffsmstate->letoffsm'=Cvalue.Model.find_base_or_defaultbstateinmatchoffsm'with|`Top->Mt_options.fatal"Top state"|`Bottom->state|`Valueoffsm'->letoffsm''=Cvalue.V_Offsetmap.joinoffsmoffsm'inCvalue.Model.add_baseboffsm''state)writtenstateinletv=Mt_shared_vars.var_thread_created()inletvalue=Results.(in_cvalue_statestate|>eval_varv|>as_cvalue)inmatchMt_memory.extract_intvaluewith|`Success0->(* As no thread is running, just skip the synchronization. *)state|_->fold_threadsanalysisstate(funthstate->matchth.th_values_writtenwith|Cvalue.Model.Bottom->state|Cvalue.Model.Top->Cvalue.Model.top|Cvalue.Model.Mapwritten->ifnot(ThreadState.equalanalysis.curr_threadth)thenjoin~written~stateelsestate)lethook_syncanalysisstate:hook_sig=function_->sync_valuesanalysisstate,no_res(* -------------------------------------------------------------------------- *)(* --- Creation of a thread --- *)(* -------------------------------------------------------------------------- *)letbasic_threadeva_threadstackfuncstateparamsparent={th_eva_thread=eva_thread;th_stack=stack;th_init_state=state;th_fun=func;th_params=params;th_parent=parent;th_to_recompute=SetRecomputeReason.empty;th_read_written=AccessesByZone.empty_map;th_amap=Trace.empty;th_cfg=Mt_cfg_types.CfgNode.dead;th_read_written_cfg=Mt_cfg_types.AccessesByZoneNode.empty_map;th_values_written=Cvalue.Model.empty_map;th_projects=[];th_value_results=None;th_priority=PDefault;}letspawn_threadanalysiseva_threadstackfuncstateparamsparent=tryletth'=Thread.Hashtbl.findanalysis.all_threadseva_threadinifOption.equalThreadState.equalparentth'.th_parent=falsethen(letpp_parent=Pretty_utils.pp_opt~none:"<none>"ThreadState.prettyinlog~kind:Log.Erroranalysis"Thread '%a' is launched@ by two different \
threads@ (%a and %a).@ Ignoring"Thread.prettyeva_threadpp_parentparentpp_parentth'.th_parent;hook_fail())elseifCallstack.equalstackth'.th_stack=falsethen(log~kind:Log.Erroranalysis"Thread '%a' is launched in two different contexts:@.\
Context 1:@.@[<hov 2> %a@]@.Context 2:@.@[<hov 2> %a@]@.Ignoring"Thread.prettyeva_threadCallstack.prettystackCallstack.prettyth'.th_stack;hook_fail())elseifKernel_function.get_idfunc<>Kernel_function.get_idth'.th_funthen(log~kind:Log.Erroranalysis"Thread '%a' can be two different functions@ \
(%s and %s).@ Imprecise pointer?@ Ignoring."Thread.prettyeva_thread(Kernel_function.get_namefunc)(Kernel_function.get_nameth'.th_fun);hook_fail())else((* Fields that are being joined *)letinit_state',ris=Mt_memory.join_stateth'.th_init_statestateandargs,ra=Mt_memory.join_paramsth'.th_paramsparamsinth'.th_init_state<-init_state';th'.th_params<-args;ifristhenThreadState.recompute_becauseth'InitialEnvChanged;ifrathenThreadState.recompute_becauseth'InitialArgsChanged;lettext=ifris||rathen"New context for"else"Thread"inlog~kind:Log.Resultanalysis"@[<hov 2>%s@ %a@]"textThreadState.pretty_detailedth';th')withNot_found->letth=basic_threadeva_threadstackfuncstateparamsparentinth.th_to_recompute<-SetRecomputeReason.singletonFirstIteration;Thread.Hashtbl.addanalysis.all_threadseva_threadth;log~kind:Log.Resultanalysis"@[<hov>New thread: %a@]"ThreadState.pretty_detailedth;thletstandalone_threadthkfinitial_state=matchkf.Cil_types.fundecwith|Declaration(_,f,_,_)->Mt_options.fatal"Entry point '%s' has no definition : cannot run %a."f.vnameThread.prettyth|Definition(fundec,_)->letformals=fundec.sformalsinleteval_argvi=Results.(in_cvalue_stateinitial_state|>eval_varvi|>as_cvalue)inletargs=List.mapeval_argformalsinletstack=Callstack.initkfinbasic_threadthstackkfinitial_stateargsNoneletmain_threadk_maininitial_state=standalone_threadThread.maink_maininitial_stateletinterrupt_threadkfinitial_state=letth=Thread.interrupt_handlerkfinstandalone_threadthkfinitial_state(** Set the global variable that indicates that at least one thread is running
to one *)letthread_is_runningstate=letp_thread_running=Mt_shared_vars.var_thread_created(),0inMt_memory.write_int_pointerp_thread_running1state(** Hook registered in the value analysis for the creation of thread *)lethook_thread_creationanalysisstate:hook_sig=function|(_,name)::(_,f)::params->letconvv=catch_conversionanalysis"During@ thread@ creation"vin(* We clean the state that will be used by the created thread *)letkf=conv(Mt_memory.extract_funf)~msg:"invalid@ thread@ function"()inletformals=Kernel_function.get_formalskfinletrectrunc_params=function|[],[]->[]|_formal::qf,param::qp->param::trunc_params(qf,qp)|[],(_::_asparams)->ifMt_options.ModerateWarnings.get()thenlog~kind:Log.Warninganalysis"@[During thread@ creation,@ mismatch@ between@ function \
'%s'@ signature and@ actual arguments.@ Ignoring@ last \
%d argument(s)@ and@ continuing.@]"(Kernel_function.get_namekf)(List.lengthparams);[]|_::_,[]->log~kind:Log.Erroranalysis"@[When creating@ thread@ from@ function %s:@ too@ few@ \
arguments,@ %d expected@ but@ %d given.@ Ignoring.@]"(Kernel_function.get_namekf)(List.lengthformals)(List.lengthparams);hook_fail()inletparams=List.mapsnd(trunc_params(formals,params))inleteva_thread=letname=Concurrency.Name.of_cvaluenameinletaloc=current_locanalysisinThread.spawnalocnamekfparamsinignore(spawn_threadanalysiseva_threadanalysis.curr_stackkfCvalue.Model.bottomparams(Someanalysis.curr_thread));register_eventanalysis(CreateThreadeva_thread);(* Thread is started as suspended *)Mt_ids.write_id_statestate(Mt_ids.of_threadeva_thread)2,wrap_res(Thread.ideva_thread)|_->Mt_options.fatal"Incorrect mthread binding for thread creation"(* By typing, Frama_C_thread_create must receive at least those
arguments *)letupdate_initial_stateanalysisthstate=(* From now on, at least two threads are running *)letstate=thread_is_runningstatein(* Remove references local to the parent thread *)letstate_started=Mt_memory.clear_non_globalsstatein(* Mutexes should be unlocked in the new threads *)letstate_started=reset_mutexesanalysis.all_mutexesstate_startedinletth=Thread.Hashtbl.findanalysis.all_threadsthinletinitial,changed=Mt_memory.join_stateth.th_init_statestate_startedinifchangedthen(ThreadState.recompute_becausethMt_thread.InitialEnvChanged;ifCvalue.Model.is_reachableth.th_init_statethenlog~kind:Log.Resultanalysis"@[<hov 2>New context for@ %a@]"ThreadState.pretty_detailedth;);th.th_init_state<-initial;(* Update the state of the creator too: more than one thread is running,
and the values written by the thread just created become visible. *)sync_valuesanalysisstatelethook_thread_start_suspendfnamecheckvaux_stateevtanalysisstate:hook_sig=function|[_,offset]->letconvv=catch_conversionanalysis("During@ thread@ "^^fname)vinletoffset=conv(Mt_memory.extract_intoffset)~msg:"invalid@ thread@ id"()inifoffset<>0thenletth=conv(find_threadoffset)~msg:"unkonwn@ thread"()in(check(log_poly~kind:Log.Warninganalysis)thstate:unit);letevt=evtthinlog~kind:Log.Resultanalysis"@[%a@]"Event.prettyevt;register_eventanalysisevt;letstate_started=aux_stateanalysisth(state:state)inMt_ids.write_id_statestate_started(Mt_ids.of_threadth)v,wrap_res0else(log~kind:Log.Warninganalysis"Trying to@ %(%)@ unknown thread.@ Ignoring."fname;hook_fail~code:(-1)())|_->Mt_options.fatal"Incorrect mthread binding for thread %(%)"fname(** Hook registered in the value analysis when a thread is started *)lethook_thread_start=hook_thread_start_suspend"start"check_thread_not_already_started1update_initial_state(funi->StartThreadi)lethook_thread_suspend=hook_thread_start_suspend"suspend"check_thread_not_already_suspended2(fun__s->s)(funi->SuspendThreadi)lethook_thread_cancellationanalysisstate:hook_sig=function|[_,offset]->letconvv=catch_conversionanalysis"During@ thread@ cancellation"vinletoffset=conv(Mt_memory.extract_intoffset)~msg:"invalid@ thread@ id"()inifoffset<>0thenletth=conv(find_threadoffset)~msg:"unkonwn@ thread"()incheck_thread_not_already_cancelled(log_poly~kind:Log.Warninganalysis)thstate;register_eventanalysis(CancelThreadth);Mt_ids.write_id_statestate(Mt_ids.of_threadth)2,wrap_res0else(log~kind:Log.Warninganalysis"Trying to@ cancel@ unknown thread.@ Ignoring.";hook_fail~code:(-1)())|_->Mt_options.fatal"Incorrect mthread binding for thread cancellation \
(only the thread id is expected)"lethook_thread_exitanalysis(_state:state):hook_sig=function|[_,v]->ifThreadState.is_mainanalysis.curr_threadthen(log~kind:Log.Erroranalysis"Call@ to@ thread@ exit@ primitive@ inside@ main@ thread. Ignoring";hook_fail())else(register_eventanalysis(ThreadExitv);log~kind:Log.Resultanalysis"Thread@ exiting@ with@ value %a"Cvalue.V.prettyv;Cvalue.Model.bottom,no_res)|_->Mt_options.fatal"Incorrect mthread binding for thread exit \
(only the return value is expected)"lethook_thread_idanalysisstate:hook_sig=fun_->state,wrap_res(Thread.idanalysis.curr_thread.th_eva_thread)lethook_thread_priorityanalysisstate:hook_sig=function|[_,p]->beginletp=catch_conversionanalysis"During@ thread@ priority@ definition"(Mt_memory.extract_intp)~msg:"invalid@ thread@ id"()inbeginmatchanalysis.curr_thread.th_prioritywith|PPriorityp'->ifp<>p'thenbeginlog~kind:Log.Warninganalysis"Conflicting priorities (previous: %d, new %d) for thread '%a'."pp'ThreadState.prettyanalysis.curr_thread;(* TODO: add an event + add a recompute reason *)analysis.curr_thread.th_priority<-PUnknown;end|PUnknown->()|PDefault->loganalysis"Setting priority to %d"p;analysis.curr_thread.th_priority<-PPriorityp;end;state,wrap_res0end|_->Mt_options.fatal"Incorrect mthread binding for thread priority \
(only a non negative integer is expected)"(* -------------------------------------------------------------------------- *)(** --- Hook registered in the value analysis related to messages --- *)(* -------------------------------------------------------------------------- *)lethook_queue_initanalysisstate:hook_sig=function|[_,name;_,size]->letconvv=catch_conversionanalysis"During@ queue@ initialization"vinletaloc=current_locanalysisandname=Concurrency.Name.of_cvaluenameandsize=conv(Mt_memory.extract_intsize)~msg:"invalid@ size"()inletq=Mqueue.createalocnameinanalysis.all_queues<-Mqueue.Set.addqanalysis.all_queues;check_queue_not_already_initialized(log_poly~kind:Log.Warninganalysis)qstate;letsize=ifsize<0thenNoneelseSomesizeinregister_eventanalysis(CreateQueue(q,size));Mt_ids.write_id_statestate(Mt_ids.of_queueq)1,wrap_res(Mqueue.idq)|_->Mt_options.fatal"Incorrect mthread binding for queue creation"lethook_send_msganalysisstate:hook_sig=function|[(_,offset);(_exp_content,content);(_exp_size,size)]->letconvv=catch_conversionanalysis"During@ message@ sending"vinletoffset=conv(Mt_memory.extract_intoffset)~msg:"invalid@ queue@ id"()inifoffset<>0thenletsbytes=conv(Mt_memory.extract_intsize)~msg:"invalid@ message@ size"()inifsbytes<=0thenconv(`Failure(funfmt->Format.fprintffmt"Invalid message length %d."sbytes))();letq=conv(find_queueoffset)()inletcontent=Mt_memory.read_slice~p:content~sbytesstateincheck_queue_already_initialized(log_poly~kind:Log.Warninganalysis)qstate;letaction=SendMsg(q,(content,sbytes))inlog~kind:Log.Resultanalysis"@[%a@]"Event.prettyaction;register_eventanalysisaction;state,wrap_res0else(log~kind:Log.Warninganalysis"@[<hov>Trying to@ send@ message@ on@ uninitialized@ queue.@ \
Ignoring@]";state,wrap_res(-1))|_->Mt_options.fatal"Incorrect mthread binding for message sending"letfind_msg_contentanalysisq=letextract_actionthacc=function|SendMsg(q',(v,size))->ifMqueue.equalqq'then(th,v,size)::accelseacc|_->accinfold_threadsanalysis[](fun{th_eva_thread=th;th_amap=m}->Trace.fold'm(funar->extract_actionthra))lethook_receive_msganalysisstate:hook_sig=function|[(_,offset);(_e_size,size);(e_loc,loc)]->letconvv=catch_conversionanalysis"During@ message@ reception"vinletoffset=conv(Mt_memory.extract_intoffset)~msg:"invalid@ queue@ id"()inifoffset<>0thenletsmax=conv(Mt_memory.extract_intsize)~msg:"invalid@ size"()andp=conv(Mt_memory.extract_pointerloc)~msg:"invalid@ destination@ buffer"()inletq=conv(find_queueoffset)()incheck_queue_already_initialized(log_poly~kind:Log.Warninganalysis)qstate;letaction=ReceiveMsg(q,p,smax)inregister_eventanalysisaction;letcontents=find_msg_contentanalysisqinletstate,res,pp=ifcontents<>[]thenletlength,kept_mess,_,state=List.fold_left(fun(length,kept_mess,exact,state)(_,slice,smessasmess)->letsbytes=minsmesssmaxinletstate'=Mt_memory.write_slice~p~sbytes~slice~exactstateinifCvalue.Model.is_reachablestate'thenletsbytes_val=Cvalue.V.inject_ival(Ival.of_intsbytes)inletlength'=Cvalue.V.joinsbytes_vallengthinlength',mess::kept_mess,false,state'else(log~kind:Log.Warninganalysis"Found message@ of length %d,@ which is@ too long@ \
for buffer '%a'. Execution@ will@ continue@ without@ \
those@ messages.@.(Ignore \"This path is assumed to \
be dead message if any\".)"smesspp_expe_loc;length,kept_mess,exact,state))(Cvalue.V.bottom,[],true,state)contentsinmatchkept_messwith|[]->Cvalue.Model.bottom,no_res,(funfmt->Format.fprintffmt"No valid value@ to receive.")|_::_->letppfmt=Format.fprintffmt"Possible %s values:@.%a"(ifList.lengthkept_mess=List.lengthcontentsthen""else"valid ")(Pretty_utils.pp_list~pre:"@[<v>"~sep:"@,"(funfmt(th,v,_)->Format.fprintffmt"@[From thread %a:@ %a@]"Thread.prettythMt_memory.pretty_slicev))kept_messinstate,Somelength,ppelseCvalue.Model.bottom,no_res,(funfmt->Format.fprintffmt"No value@ to receive (yet?).")inlog~kind:Log.Resultanalysis"@[<hov>%a@ %t@]"Event.prettyactionpp;state,reselse(log~kind:Log.Warninganalysis"Trying@ to@ receive@ value@ on@ non-initialized@ queue. Ignoring.";state,wrap_res(-2))|_->Mt_options.fatal"Incorrect mthread binding for message reception"(* Auxiliary functions for the functions that act on mutexes (currently
lock and release). [check] is the function that checks that the state
of the information stored in the mutex is consistent with the action
being performed, and the value with which to update the state.
[evt] returns the corresponding mthread event. *)letaux_mutex~operation:op~check~eventanalysisstate:hook_sig=function|[_,offset]->letf_check,value=checkinletconvv=catch_conversionanalysis("During@ mutex@ "^^op)vinletoffset,exact=conv(Mt_memory.extract_int_possibly_zerooffset)~msg:"invalid@ mutex@ id"()inifexact=`WithZerothenlog~kind:Log.Warninganalysis"@[<hov>Trying to@ %(%)@ a possibly@ uninitialized@ mutex.@]"op;ifoffset<>0thenletm=conv(find_mutexoffset)()inf_check(log_poly~kind:Log.Warninganalysis)mstate;letevt:event=eventminlog~kind:Log.Resultanalysis"%a"Event.prettyevt;register_eventanalysisevt;letstate_op=Mt_ids.write_id_statestate(Mt_ids.of_mutexm)valuein(* XXX: take which mutex is locked into account, and update only
those values *)letwith_external=sync_valuesanalysisstate_opinwith_external,wrap_res0else(log~kind:Log.Warninganalysis"@[<hov>Trying to@ %(%)@ uninitialized@ mutex.@ Ignoring@]"op;state,wrap_res(-1))|_->(* really unlikely unless the code and/or the C binding
are really strange *)Mt_options.fatal"Incorrect mthread binding for mutex function"lethook_init_mutexanalysisstate:hook_sig=function|[_,name]->letaloc=current_locanalysisandname=Concurrency.Name.of_cvaluenameinletmutex=Mutex.createalocnameinanalysis.all_mutexes<-Mutex.Set.addmutexanalysis.all_mutexes;check_mutex_not_already_initialized(log_poly~kind:Log.Warninganalysis)mutexstate;log~kind:Log.Resultanalysis"Initializing mutex %a"Mutex.prettymutex;Mt_ids.write_id_statestate(Mt_ids.of_mutexmutex)1,wrap_res(Mutex.idmutex)|_->(* really unlikely unless the code and/or the C binding
are really strange *)Mt_options.fatal"Incorrect mthread binding for mutex function"lethook_lock_mutex=aux_mutex~operation:"lock"~check:(check_mutex_not_already_locked,2)~event:(funid->MutexLockid)lethook_release_mutex=aux_mutex~operation:"unlock"~check:(check_mutex_locked,1)~event:(funid->MutexReleaseid)(* -------------------------------------------------------------------------- *)(** --- Misc --- *)(* -------------------------------------------------------------------------- *)lethook_dummy_messageanalysisstate:hook_sig=function|(_,name)::args->letconvv=catch_conversionanalysis~pop_stack:false"During@ unknown@ event"vinletname=conv(Mt_memory.extract_constant_stringname)~msg:"invalid@ event@ name"()inletevt=Dummy(name,List.mapsndargs)inregister_eventanalysisevt;log~pop_stack:false~kind:Log.Resultanalysis"Monitored event:@ %a"Event.prettyevt;state,no_res|_->Mt_options.fatal"Incorrect mthread binding for unknown event"(* -------------------------------------------------------------------------- *)(** --- Main declarations --- *)(* -------------------------------------------------------------------------- *)(* All the Mthread builtin functions, together with their C name.
The remainder of the conversion to the real type of the callback
{Builtins.register_builtin} occurs in [Mt_main] *)letmthread_builtins=[(* Threads *)"Frama_C_thread_create",hook_thread_creation,`Pop;"Frama_C_thread_start",hook_thread_start,`Pop;"Frama_C_thread_suspend",hook_thread_suspend,`Pop;"Frama_C_thread_cancel",hook_thread_cancellation,`Pop;"Frama_C_thread_exit",hook_thread_exit,`Pop;"Frama_C_thread_id",hook_thread_id,`Pop;"Frama_C_thread_priority",hook_thread_priority,`Pop;(* Mutexes *)"Frama_C_mutex_init",hook_init_mutex,`Pop;"Frama_C_mutex_lock",hook_lock_mutex,`Pop;"Frama_C_mutex_unlock",hook_release_mutex,`Pop;(* Message queues *)"Frama_C_queue_init",hook_queue_init,`Pop;"Frama_C_queue_send",hook_send_msg,`Pop;"Frama_C_queue_receive",hook_receive_msg,`Pop;(* Misc *)"Frama_C_mthread_show",hook_dummy_message,`NoPop;(* Shared values *)"Frama_C_mthread_sync",hook_sync,`Pop;];;letis_mthread_builtins=trylet(_,_,pop)=List.find(fun(s',_,_)->s=s')mthread_builtinsinpopwithNot_found->`NotBuiltinletmthread_builtins=List.map(fun(n,f,_)->(n,f))mthread_builtins(* Function to register as a callback of the Eva analysis if Mthread
is enabled *)letcatch_functions_callsanalysis(stack:Callstack.callstack)kfstatekind=analysis.curr_stack<-stack;ifCallstack.is_emptystackthen(* This is the entry point of the analysis, the events stack needs to be
empty. *)analysis.curr_events_stack<-[];letf=Kernel_function.get_namekfinletbuilt=is_mthread_builtinfin(ifbuilt<>`NotBuiltinthenmatchCallstack.popanalysis.curr_stackwith|None->(* A thread function has been called as main, and we fail
immediately. In fact, this case should not happen,
because we reject calls to __FRAMA_C_* functions as
main or during thread spawning. We could detect when
the stack has only one element (ie. pthread_* has
been called has main, but the error message arrives
too late, and is not really readable *)Mt_options.abort"Thread function %s called as starting thread \
function"f|Somestack->(* For mthread builtin functions, we may remove the top of the stack.
This way, the mthread events appear at the level of the C
function, instead of inside a function with a strange name *)ifMt_options.PopTopFunctionForCallbacks.get()&&built=`Popthenanalysis.curr_stack<-stack;);ifCallstack.is_emptyanalysis.curr_stack&&Thread.is_mainanalysis.curr_thread.th_eva_threadthenbegin(* Beginning of the main thread (kf being the entry point). For the main
thread, it might have not been registered yet if we are at the
first iteration. *)letth=main_threadkfstatein(* This call registers the main thread on the first run, and essentially
does nothing afterwards *)letth=spawn_threadanalysisth.th_eva_threadth.th_stackth.th_funth.th_init_stateth.th_paramsNoneinifanalysis.main_thread!=ththenbegin(* On the first run, the record [th] is created. It is not contained
anywhere else, so we update the fields below. *)analysis.main_thread<-th;analysis.curr_thread<-th;(* We are currently computing this thread (the main one) and we have
just started, no need to recompute it at the next iteration *)th.th_to_recompute<-SetRecomputeReason.empty;(* On the first iteration, also register the interrupt handlers *)letinterrupt_handlers=Mt_options.InterruptHandlers.get()inletinterrupts,state=Kernel_function.Set.fold(funkf_interrupt(interrupts,state)->(* Create and spawn the interrupt thread *)letth=interrupt_threadkf_interruptstateinletth=spawn_threadanalysisth.th_eva_threadth.th_stackth.th_funth.th_init_stateth.th_paramsNonein(* Start the interrupt thread *)letstate=Mt_ids.write_id_statestate(Mt_ids.of_threadth.th_eva_thread)1in(th::interrupts,state))interrupt_handlers([],state)inifinterrupts!=[]thenbegin(* If any interrupt handler has been registered, then their initial
state and the initial state of the main thread need to be updated
so that they all are considered running. *)List.iter(funth->let_=update_initial_stateanalysisth.th_eva_threadstatein())(th::interrupts)endendend;push_function_callanalysis;(* If the function is a leaf one, we register the accesses that occur
through \assigns ACSL specifications, then pop the stack. If there is a
definition, the registering will be done by another hook, at the end of
the execution of the function *)matchkindwith|`Spec|`Builtin->Mt_shared_vars.register_concurrent_var_accessesanalysis(`Leafstate);pop_function_callanalysis;|`Body|`Reuse->()(* Function registered by [Cvalue_callbacks.register_call_results_hook].
Given the end states of a function with a definition, records the variable
accesses it did. *)letcatch_functions_recordanalysisstack_kf_pre_state=function|`Body(Cvalue_callbacks.{before_stmts;after_stmts},i)->analysis.curr_stack<-stack;lethbefore=Lazy.forcebefore_stmtsinlethafter=Lazy.forceafter_stmtsinMt_shared_vars.register_concurrent_var_accessesanalysis(`Finalhbefore);register_memory_statesanalysis~before:hbefore~after:hafter;letcur_events=curr_eventsanalysisinDatatype.Int.Hashtbl.addanalysis.memexec_cacheicur_events;pop_function_callanalysis;|`Reusei->letevents=Datatype.Int.Hashtbl.findanalysis.memexec_cacheiin(* Merge the memoized results in the current analysis *)register_multiple_eventsanalysisevents;pop_function_callanalysis;|`Builtin_|`Spec_->()