123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409(**************************************************************************)(* *)(* SPDX-License-Identifier LGPL-2.1 *)(* Copyright (C) *)(* CEA (Commissariat à l'énergie atomique et aux énergies alternatives) *)(* *)(**************************************************************************)openCil_typesmoduleM=Alarms.Maptypealarm=Alarms.ttypestatus=Abstract_interp.Comp.result=True|False|Unknowntypes=statusM.ttypet=Justofs|AllButofstype'aif_consistent=[`Valueof'a|`Inconsistent]letstring_of_predicate_status=function|Unknown->"unknown"|True->"valid"|False->"invalid"letpretty_statusfmtv=Format.fprintffmt"%s"(string_of_predicate_statusv)moduleStatus=structincludeDatatype.Make_with_collections(structtypet=statusincludeDatatype.Serializable_undefinedletname="Alarmset.status"letreprs=[True;False;False;Unknown]letmem_project=Datatype.never_any_projectletpretty=pretty_statusletcompare(s1:t)(s2:t)=Stdlib.compares1s2letequal(s1:t)(s2:t)=s1=s2lethash(s:t)=Hashtbl.hashsend)letjoinxy=matchx,ywith|True,True->True|False,False->False|True,False|False,True|Unknown,_|_,Unknown->Unknownletinterxy=matchx,ywith|Unknown,_->`Valuey|_,Unknown->`Valuex|True,True->`ValueTrue|False,False->`ValueFalse|True,False|False,True->`InconsistentendmoduleD=Alarms.Map.Make(Status)letprettyfmt=function|Justm->Format.fprintffmt"Just %a"D.prettym|AllButm->Format.fprintffmt"AllBut %a"D.prettymletnone=JustM.emptyletall=AllButM.emptyletequalab=matcha,bwith|Justm,Justn|AllButm,AllButn->M.equalStatus.equalmn|_,_->falseletget=functionJustm|AllButm->mletis_empty=function|AllBut_->false|Justm->M.is_emptymletsingleton?(status=Unknown)a=ifstatus=TruethenJustM.emptyelseJust(M.singletonastatus)letsetalarmstatus=function|Justm->ifstatus=TruethenJust(M.removealarmm)elseJust(M.addalarmstatusm)|AllButm->ifstatus=UnknownthenAllBut(M.removealarmm)elseAllBut(M.addalarmstatusm)letdefault=function|Just_->True|AllBut_->Unknownletfindalarmset=trymatchsetwith|Justm|AllButm->M.findalarmmwithNot_found->defaultset(* Merges two alarm maps [s1] and [s2], using [merge_status] to merge merge the
statuses bound to a same alarm.
If [combine] is true, both alarm maps [s1] and [s2] have been produced in
the same state but for different expressions [e1] and [e2]: they may contain
different alarms, but should always have the same status bound to a same
alarm. Do not use the default status for alarms in [s1] missing in [s2] (and
vice versa): such alarms may have no meaning for [e2], and should have the
same status in [s2] than in [s1] anyway.
If [combine] is false, both maps come from the evaluation of the same
expression in different states. In this case, use the default status for any
alarm missing in one side. *)letmerge~combinemerge_statuss1s2=letd1=defaults1andd2=defaults2inletm1=gets1andm2=gets2inletclosed_set=matchmerge_statusd1d2with|True->true|Unknown->false|False->assertfalseinletreturn=ifclosed_setthenfunctionTrue->None|p->SomepelsefunctionUnknown->None|p->Somepinletmerge_p1p2=matchp1,p2with|None,None->assertfalse|Somep,None->ifcombinethenSomepelsereturn(merge_statuspd2)|None,Somep->ifcombinethenSomepelsereturn(merge_statusd1p)|Somep1,Somep2->return(merge_statusp1p2)inifclosed_setthenJust(M.mergemergem1m2)elseAllBut(M.mergemergem1m2)letunion=merge~combine:falseStatus.joinexceptionInconsistentletintersectstatus1status2=matchStatus.interstatus1status2with|`Valuestatus->status|`Inconsistent->raiseInconsistentletinters1s2=try`Value(merge~combine:falseintersects1s2)withInconsistent->`Inconsistentletcombines1s2=(* [intersect] is only applied if both maps explicitly contain the same
alarm. As both maps have been produced in the same state, the statuses for
this alarm should be consistent. *)trymerge~combine:trueintersects1s2withInconsistent->Self.fatal~current:true"Inconsistent combination of two alarm maps %a and %a."prettys1prettys2letiterf=function|Justm->M.iterfm|AllBut_->assertfalseletfoldfacc=function|Justm->M.foldfmacc|AllBut_->assertfalseletexiststest~default=function|Justm->M.existstestm||defaultTrue|AllButm->M.existstestm||defaultUnknownletfor_alltest~default=function|Justm->M.for_alltestm&&defaultTrue|AllButm->M.for_alltestm&&defaultUnknown(* --------------------------------------------------------------------------
Alarms
------------------------------------------------------------------------ *)letemitter=Eva_utils.emitter(* Printer that shows additional information about temporaries *)letlocal_printer:Printer.extensible_printer=letopenCil_typesinobject(self)inheritPrinter.extensible_printer()assuper(* Temporary variables for which we want to print more information *)valmutabletemporaries=Cil_datatype.Varinfo.Set.emptymethod!code_annotationfmtca=temporaries<-Cil_datatype.Varinfo.Set.empty;matchca.annot_contentwith|AAssert(_,p)->(* ignore the ACSL name *)letp=p.tp_statement.pred_contentinFormat.fprintffmt"@[<v>@[assert@ %a;@]"self#predicate_nodep;(* print temporary variables information *)ifnot(Cil_datatype.Varinfo.Set.is_emptytemporaries)thenbeginFormat.fprintffmt"@ @[(%t)@]"self#pp_temporariesend;Format.fprintffmt"@]";|_->assertfalsemethodprivatepp_temporariesfmt=letpp_varfmtvi=Format.fprintffmt"%s from@ @[%s@]"vi.vname(Option.getvi.vdescr)inPretty_utils.pp_iterCil_datatype.Varinfo.Set.iter~pre:""~suf:""~sep:",@ "pp_varfmttemporariesmethod!logic_varfmtlvi=(matchlvi.lv_originwith|None|Some{vdescr=None}->()|Some({vdescr=Some_}asvi)->temporaries<-Cil_datatype.Varinfo.Set.addvitemporaries);super#logic_varfmtlviendletpr_annot=local_printer#code_annotation(* Default behaviour: print one alarm per kinstr. *)moduleAlarm_key=Datatype.Pair_with_collections(Cil_datatype.Kinstr)(Alarms)moduleAlarm_cache=State_builder.Hashtbl(Alarm_key.Hashtbl)(Datatype.Unit)(structletname="Value_messages.Alarm_cache"letdependencies=[Self.state]letsize=35end)(* Has the given alarm already been emitted by the current analysis?
For now, alarms emitted at Kglobal are bound to the first statement of the
main function by the kernel, so we also need to check this case. *)letalready_emittedstmtalarm=Alarm_cache.mem(Kstmtstmt,alarm)||Kernel_function.is_first_stmt(fst(Globals.entry_point()))stmt&&Alarm_cache.mem(Kglobal,alarm)letloc=function|Cil_types.Kglobal->(* can occur in case of obscure bugs (already happened)
with wacky initializers. Module Initial_state of
value analysis correctly positions the loc *)Current_loc.get()|Cil_types.Kstmts->Cil_datatype.Stmt.locsletreport_alarm~posannotmsg=Self.warning~wkey:Self.wkey_alarm~pos~stacktrace:true"@[%s.@ @[<hov 2>%a@]@]"msgpr_annotannotletstring_fkind=function|Cil_types.FFloat->"float"|Cil_types.FDouble->"double"|Cil_types.FLongDouble->"long double"(** Emitting alarms *)letregister_alarm~posalarmstatusstr=letki=Position.kinstrposinletstatus=matchstatuswith|True->Property_status.True|False->(* We cannot soundly emit a red status on an alarm, because we may have
emitted a green status earlier on. Thus we store the information
for logging purposes, and emit an Unknown status. *)Red_statuses.add_red_alarmkialarm;Property_status.Dont_know|Unknown->Property_status.Dont_knowinletannot,_is_new=Alarms.register~loc:(locki)~statusemitterkialarmin(* Report each alarm only once per analysis. The boolean [is_new] returned
by {{Alarms.register}} is inadequate, as an alarm emitted by another
plugin or by a previous run of Eva would be considered as not new. *)Alarm_cache.memo(fun(_ki,_alarm)->report_alarm~posannotstr)(ki,alarm)letemit_alarm~posalarm(status:status)=letregister_alarm=register_alarm~posalarmstatusinmatchalarmwith|Alarms.Pointer_comparison(_,_)->register_alarm"pointer comparison"|Alarms.Division_by_zero_->register_alarm"division by zero"|Alarms.Overflow(kind,_,_,_)->letstr=matchkindwith|Alarms.Signed->"signed overflow"|Alarms.Unsigned->"unsigned overflow"|Alarms.Signed_downcast->"signed downcast"|Alarms.Unsigned_downcast->"unsigned downcast"|Alarms.Pointer_downcast->"pointer downcast"inregister_alarmstr|Alarms.Float_to_int_->register_alarm"overflow in conversion from floating-point to integer"|Alarms.Invalid_shift(_,Some_)->register_alarm"invalid RHS operand for shift"|Alarms.Invalid_shift(_,None)->register_alarm"invalid LHS operand for left shift"|Alarms.Memory_access(_,access_kind)->letaccess=matchaccess_kindwith|Alarms.For_reading->"read"|Alarms.For_writing->"write"inregister_alarm(Format.sprintf"out of bounds %s"access)|Alarms.Index_out_of_bound_->register_alarm"accessing out of bounds index"|Alarms.Unaligned_pointer_->register_alarm"unaligned pointer creation"|Alarms.Invalid_pointer_->register_alarm"invalid pointer creation"|Alarms.Differing_blocks_->register_alarm"pointer subtraction"|Alarms.Is_nan_or_infinite(_,fkind)->letsfkind=string_fkindfkindinregister_alarm(Format.sprintf"non-finite %s value"sfkind)|Alarms.Is_nan(_,fkind)->letsfkind=string_fkindfkindinregister_alarm(Format.sprintf"NaN %s value"sfkind)|Alarms.Uninitialized_->register_alarm"accessing uninitialized left-value"|Alarms.Dangling_->register_alarm"accessing left-value that contains escaping addresses"|Alarms.Not_separated_->register_alarm"undefined multiple accesses in expression"|Alarms.Overlap_->register_alarm"partially overlapping lvalue assignment"|Alarms.Function_pointer_->register_alarm"pointer to function with incompatible type"|Alarms.Invalid_bool_->register_alarm"trap representation of a _Bool lvalue"letheight_alarm=letopenEva_utilsinfunction|Alarms.Division_by_zeroe|Alarms.Index_out_of_bound(e,_)|Alarms.Unaligned_pointer(e,_)|Alarms.Invalid_pointere|Alarms.Invalid_shift(e,_)|Alarms.Overflow(_,e,_,_)|Alarms.Float_to_int(e,_,_)|Alarms.Function_pointer(e,_)|Alarms.Pointer_comparison(None,e)->height_expre+2|Alarms.Memory_access(lv,_)|Alarms.Danglinglv|Alarms.Invalid_boollv->height_lvallv+1|Alarms.Uninitializedlv->height_lvallv|Alarms.Pointer_comparison(Somee1,e2)->max(height_expre1)(height_expre2)+2|Alarms.Differing_blocks(e1,e2)->max(height_expre1)(height_expre2)+1|Alarms.Not_separated(lv1,lv2)|Alarms.Overlap(lv1,lv2)->max(height_lvallv1)(height_lvallv2)+1|Alarms.Is_nan_or_infinite(e,fkind)|Alarms.Is_nan(e,fkind)->lettrivial=matchCil.(typeOfe).tnodewith|TFloatfk->fk=fkind|_->falseiniftrivialthenheight_expreelseheight_expre+1letcmpa1a2=Datatype.Int.compare(height_alarm(fsta1))(height_alarm(fsta2))letremove_redundant_alarmsmap=letfilteralarmstatus=matchalarmwith|Alarms.Invalid_pointerexpr->letlval=Memexpr,NoOffsetinletimpliesalarms=status=s&&matchalarmwith|Alarms.Memory_access(lv,_access_kind)->Cil_datatype.LvalStructEq.equallvlval|_->falseinnot(M.existsimpliesmap)|_->trueinM.filterfiltermapletemit_alarms~posmap=letmap=remove_redundant_alarmsmapinletlist=M.bindingsmapinletsorted_list=List.sortcmplistinList.iter(fun(alarm,status)->emit_alarm~posalarmstatus)sorted_list;ifAlarm_cache.length()>=Parameters.StopAtNthAlarm.get()thenSelf.abort"Stopping at nth alarm"letemit~pos=function|Justmap->ifnot(M.is_emptymap)thenemit_alarms~posmap(* TODO: use GADT to avoid this assert false ? *)|AllBut_->Self.abort~current:true~once:true"All alarms may arise: \
abstract state too imprecise to continue the analysis."